diff --git a/page/package-lock.json b/page/package-lock.json index 69568032..9ebe7d75 100644 --- a/page/package-lock.json +++ b/page/package-lock.json @@ -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", diff --git a/page/package.json b/page/package.json index b428945a..2d5911ea 100644 --- a/page/package.json +++ b/page/package.json @@ -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", diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index 1a0db87c..a747d0ce 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -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 { + 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 { + 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 ( this.setState({ createFolder: true })} - removeElementHandler={(e) => this.setState({ removeFile: e.name })} - renameElementHandler={(e) => this.setState({ renameFile: e.name })} - /> + + {({ getRootProps, getInputProps }) => ( +
+ + + this.setState({ createFolder: true }) + } + removeElementHandler={(e) => + this.setState({ removeFile: e.name }) + } + renameElementHandler={(e) => + this.setState({ renameFile: e.name }) + } + /> +
+ )} +
); } diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index e4bbb80c..04748301 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -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 { +/*function selectAndReadFile(): Promise { return new Promise((resolve, reject) => { const fileInput = document.createElement("input"); fileInput.type = "file"; @@ -81,7 +81,7 @@ function selectAndReadFile(): Promise { fileInput.click(); }); -} +}*/ export function Playground() { const output = useRef(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() { Run Application - createEmulator()}> + createEmulator("c:/test-sample.exe")} + > Select Sample @@ -233,7 +235,10 @@ export function Playground() { - + diff --git a/page/src/emulator.ts b/page/src/emulator.ts index 35647fa8..63962ed7 100644 --- a/page/src/emulator.ts +++ b/page/src/emulator.ts @@ -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", diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index b7165a49..45d2c3c8 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -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(); } diff --git a/src/tools/create-root.bat b/src/tools/create-root.bat index 07936796..5c2fe140 100644 --- a/src/tools/create-root.bat +++ b/src/tools/create-root.bat @@ -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