mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-26 07:01:02 +00:00
Finish filesystem explorer
This commit is contained in:
39
page/package-lock.json
generated
39
page/package-lock.json
generated
@@ -31,6 +31,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap-icons": "^1.11.5",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-resizable-panels": "^2.1.9",
|
||||
"react-router-dom": "^7.5.3",
|
||||
@@ -3074,6 +3075,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/attr-accept": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
|
||||
"integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -3660,6 +3670,18 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-selector": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
|
||||
"integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -4647,6 +4669,23 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dropzone": {
|
||||
"version": "14.3.8",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
|
||||
"integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"attr-accept": "^2.2.4",
|
||||
"file-selector": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8 || 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap-icons": "^1.11.5",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-resizable-panels": "^2.1.9",
|
||||
"react-router-dom": "^7.5.3",
|
||||
|
||||
@@ -14,8 +14,11 @@ import { Button } from "./components/ui/button";
|
||||
import { Input } from "./components/ui/input";
|
||||
import { DialogDescription } from "@radix-ui/react-dialog";
|
||||
|
||||
import Dropzone from "react-dropzone";
|
||||
|
||||
export interface FilesystemExplorerProps {
|
||||
filesystem: Filesystem;
|
||||
runFile: (file: string) => void;
|
||||
}
|
||||
export interface FilesystemExplorerState {
|
||||
path: string[];
|
||||
@@ -40,6 +43,17 @@ function makeFullPathWithState(
|
||||
return makeFullPathAndJoin(state.path, element);
|
||||
}
|
||||
|
||||
function relativePathToWindowsPath(fullPath: string) {
|
||||
if (fullPath.length == 0) {
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
const drive = fullPath.substring(0, 1);
|
||||
const rest = fullPath.substring(1);
|
||||
|
||||
return `${drive}:${rest}`;
|
||||
}
|
||||
|
||||
function makeRelativePathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
@@ -47,6 +61,14 @@ function makeRelativePathWithState(
|
||||
return [...state.path, element].join("/");
|
||||
}
|
||||
|
||||
function makeWindowsPathWithState(
|
||||
state: FilesystemExplorerState,
|
||||
element: string,
|
||||
) {
|
||||
const fullPath = makeRelativePathWithState(state, element);
|
||||
return relativePathToWindowsPath(fullPath);
|
||||
}
|
||||
|
||||
function getFolderElements(filesystem: Filesystem, path: string[]) {
|
||||
const fullPath = makeFullPath(path);
|
||||
const files = filesystem.readDir(fullPath);
|
||||
@@ -75,6 +97,37 @@ function getFolderElements(filesystem: Filesystem, path: string[]) {
|
||||
});
|
||||
}
|
||||
|
||||
interface FileWithData {
|
||||
file: File;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
function readFile(file: File): Promise<FileWithData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (reader.readyState === FileReader.DONE) {
|
||||
resolve({
|
||||
file,
|
||||
data: reader.result as ArrayBuffer,
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
async function readFiles(files: FileList | File[]): Promise<FileWithData[]> {
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
promises.push(readFile(files[i]));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
export class FilesystemExplorer extends React.Component<
|
||||
FilesystemExplorerProps,
|
||||
FilesystemExplorerState
|
||||
@@ -82,6 +135,7 @@ export class FilesystemExplorer extends React.Component<
|
||||
constructor(props: FilesystemExplorerProps) {
|
||||
super(props);
|
||||
|
||||
this._onFileDrop = this._onFileDrop.bind(this);
|
||||
this._onElementSelect = this._onElementSelect.bind(this);
|
||||
|
||||
this.state = {
|
||||
@@ -99,6 +153,11 @@ export class FilesystemExplorer extends React.Component<
|
||||
|
||||
_onElementSelect(element: FolderElement) {
|
||||
if (element.type != FolderElementType.Folder) {
|
||||
if (element.name.endsWith(".exe")) {
|
||||
const file = makeWindowsPathWithState(this.state, element.name);
|
||||
this.props.runFile(file);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,6 +210,18 @@ export class FilesystemExplorer extends React.Component<
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
async _onFileDrop(files: FileList | File[]) {
|
||||
const fileData = (await readFiles(files)).map((f) => {
|
||||
return {
|
||||
name: makeFullPathWithState(this.state, f.file.name.toLowerCase()),
|
||||
data: f.data,
|
||||
};
|
||||
});
|
||||
|
||||
await this.props.filesystem.storeFiles(fileData);
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_renderCreateFolderDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
@@ -310,13 +381,26 @@ export class FilesystemExplorer extends React.Component<
|
||||
{this._renderErrorDialog()}
|
||||
{this._renderRemoveDialog()}
|
||||
|
||||
<Folder
|
||||
elements={elements}
|
||||
clickHandler={this._onElementSelect}
|
||||
createFolderHandler={() => this.setState({ createFolder: true })}
|
||||
removeElementHandler={(e) => this.setState({ removeFile: e.name })}
|
||||
renameElementHandler={(e) => this.setState({ renameFile: e.name })}
|
||||
/>
|
||||
<Dropzone onDrop={this._onFileDrop} noClick={true}>
|
||||
{({ getRootProps, getInputProps }) => (
|
||||
<div {...getRootProps()}>
|
||||
<input {...getInputProps()} />
|
||||
<Folder
|
||||
elements={elements}
|
||||
clickHandler={this._onElementSelect}
|
||||
createFolderHandler={() =>
|
||||
this.setState({ createFolder: true })
|
||||
}
|
||||
removeElementHandler={(e) =>
|
||||
this.setState({ removeFile: e.name })
|
||||
}
|
||||
renameElementHandler={(e) =>
|
||||
this.setState({ renameFile: e.name })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Output } from "@/components/output";
|
||||
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
import { Emulator, UserFile, EmulationState } from "./emulator";
|
||||
import { Emulator, EmulationState } from "./emulator";
|
||||
import { Filesystem, setupFilesystem } from "./filesystem";
|
||||
|
||||
import "./App.css";
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
} from "@/components/ui/drawer";
|
||||
import { FilesystemExplorer } from "./FilesystemExplorer";
|
||||
|
||||
function selectAndReadFile(): Promise<UserFile> {
|
||||
/*function selectAndReadFile(): Promise<UserFile> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
@@ -81,7 +81,7 @@ function selectAndReadFile(): Promise<UserFile> {
|
||||
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
export function Playground() {
|
||||
const output = useRef<Output>(null);
|
||||
@@ -124,26 +124,26 @@ export function Playground() {
|
||||
}
|
||||
}
|
||||
|
||||
async function createEmulator(userFile: UserFile | null = null) {
|
||||
async function createEmulator(userFile: string) {
|
||||
emulator?.stop();
|
||||
output.current?.clear();
|
||||
|
||||
logLine("Starting emulation...");
|
||||
|
||||
const fs = await setupFilesystem((current, total, file) => {
|
||||
logLine(`Processing filesystem (${current}/${total}): ${file}`);
|
||||
});
|
||||
if (filesystemPromise) {
|
||||
await filesystemPromise;
|
||||
}
|
||||
|
||||
const new_emulator = new Emulator(logLines, (_) => forceUpdate());
|
||||
new_emulator.onTerminate().then(() => setEmulator(null));
|
||||
setEmulator(new_emulator);
|
||||
|
||||
new_emulator.start(settings, userFile, fs);
|
||||
new_emulator.start(settings, userFile);
|
||||
}
|
||||
|
||||
async function loadAndRunUserFile() {
|
||||
const fileBuffer = await selectAndReadFile();
|
||||
await createEmulator(fileBuffer);
|
||||
//const fileBuffer = await selectAndReadFile();
|
||||
//await createEmulator(fileBuffer);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -164,7 +164,9 @@ export function Playground() {
|
||||
<DropdownMenuLabel>Run Application</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => createEmulator()}>
|
||||
<DropdownMenuItem
|
||||
onClick={() => createEmulator("c:/test-sample.exe")}
|
||||
>
|
||||
<ImageFill className="mr-2" />
|
||||
<span>Select Sample</span>
|
||||
</DropdownMenuItem>
|
||||
@@ -233,7 +235,10 @@ export function Playground() {
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<DrawerFooter>
|
||||
<FilesystemExplorer filesystem={filesystem} />
|
||||
<FilesystemExplorer
|
||||
filesystem={filesystem}
|
||||
runFile={createEmulator}
|
||||
/>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -9,11 +9,6 @@ import { Filesystem } from "./filesystem";
|
||||
|
||||
type LogHandler = (lines: string[]) => void;
|
||||
|
||||
export interface UserFile {
|
||||
name: string;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
export enum EmulationState {
|
||||
Stopped,
|
||||
Paused,
|
||||
@@ -77,20 +72,7 @@ export class Emulator {
|
||||
this.worker.onmessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
async start(
|
||||
settings: Settings = createDefaultSettings(),
|
||||
userFile: UserFile | null = null,
|
||||
fs: Filesystem,
|
||||
) {
|
||||
var file = "c:/test-sample.exe";
|
||||
if (userFile) {
|
||||
const filename = userFile.name.split("/").pop()?.split("\\").pop();
|
||||
const canonicalName = filename?.toLowerCase();
|
||||
file = "c:/" + canonicalName;
|
||||
|
||||
await fs.storeFile("root/filesys/c/" + canonicalName, userFile.data);
|
||||
}
|
||||
|
||||
async start(settings: Settings = createDefaultSettings(), file: string) {
|
||||
this._setState(EmulationState.Running);
|
||||
this.worker.postMessage({
|
||||
message: "run",
|
||||
|
||||
@@ -38,6 +38,11 @@ async function initializeIDBFS() {
|
||||
return idbfs;
|
||||
}
|
||||
|
||||
export interface FileWithData {
|
||||
name: string;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
|
||||
export class Filesystem {
|
||||
private idbfs: MainModule;
|
||||
|
||||
@@ -45,9 +50,21 @@ export class Filesystem {
|
||||
this.idbfs = idbfs;
|
||||
}
|
||||
|
||||
async storeFiles(file: string, data: ArrayBuffer) {
|
||||
const buffer = new Uint8Array(data);
|
||||
this.idbfs.FS.writeFile(file, buffer);
|
||||
_storeFile(file: FileWithData) {
|
||||
if (file.name.includes("/")) {
|
||||
const folder = file.name.split("/").slice(0, -1).join("/");
|
||||
this._createFolder(folder);
|
||||
}
|
||||
|
||||
const buffer = new Uint8Array(file.data);
|
||||
this.idbfs.FS.writeFile(file.name, buffer);
|
||||
}
|
||||
|
||||
async storeFiles(files: FileWithData[]) {
|
||||
files.forEach((f) => {
|
||||
this._storeFile(f);
|
||||
});
|
||||
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
@@ -76,8 +93,12 @@ export class Filesystem {
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
_createFolder(folder: string) {
|
||||
this.idbfs.FS.mkdirTree(folder, 0o777);
|
||||
}
|
||||
|
||||
async createFolder(folder: string) {
|
||||
this.idbfs.FS.mkdir(folder, 777);
|
||||
this._createFolder(folder);
|
||||
await this.sync();
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ CALL :collect d3d10.dll
|
||||
CALL :collect d3d10core.dll
|
||||
CALL :collect cabinet.dll
|
||||
CALL :collect msacm32.dll
|
||||
CALL :collect coloradapterclient.dll
|
||||
|
||||
CALL :collect locale.nls
|
||||
CALL :collect c_1252.nls
|
||||
|
||||
Reference in New Issue
Block a user