Finish filesystem explorer

This commit is contained in:
momo5502
2025-04-30 10:13:27 +02:00
parent 51971c5ec7
commit 62b06a1717
7 changed files with 175 additions and 42 deletions

39
page/package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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>
</>
);
}

View File

@@ -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>

View File

@@ -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",

View File

@@ -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();
}

View File

@@ -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