mirror of
https://github.com/felixrieseberg/macintosh.js.git
synced 2024-06-07 16:46:28 +00:00
feat: We can get files into the vm, too
This commit is contained in:
parent
a23fcb1a1e
commit
67a2adf76e
|
@ -1,31 +1,93 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const memAllocSet = new Set();
|
||||
const memAllocSetPersistent = new Set();
|
||||
const homeDir = require("os").homedir();
|
||||
const macDir = path.join(homeDir, "macintosh.js");
|
||||
const macintoshCopyPath = path.join(__dirname, "user_files");
|
||||
|
||||
var pathGetFilenameRegex = /\/([^\/]+)$/;
|
||||
function cleanupCopyPath() {
|
||||
try {
|
||||
if (fs.existsSync(macintoshCopyPath)) {
|
||||
fs.rmdirSync(macintoshCopyPath, { recursive: true });
|
||||
}
|
||||
|
||||
function pathGetFilename(path) {
|
||||
var matches = path.match(pathGetFilenameRegex);
|
||||
if (matches && matches.length) {
|
||||
return matches[1];
|
||||
} else {
|
||||
return path;
|
||||
fs.mkdirSync(macintoshCopyPath);
|
||||
} catch (error) {
|
||||
console.error(`cleanupCopyPath: Failed to remove`, error);
|
||||
}
|
||||
}
|
||||
|
||||
function addAutoloader(module) {
|
||||
var loadDatafiles = function () {
|
||||
module.autoloadFiles.forEach(function (filepath) {
|
||||
const copyFilesAtPath = function (sourcePath) {
|
||||
try {
|
||||
const absoluteSourcePath = path.join(macDir, sourcePath);
|
||||
const absoluteTargetPath = path.join(macintoshCopyPath, sourcePath);
|
||||
const targetPath = `/macintosh.js${sourcePath ? `/${sourcePath}` : '' }`;
|
||||
const files = fs.readdirSync(absoluteSourcePath);
|
||||
|
||||
(files || []).forEach((fileName) => {
|
||||
try {
|
||||
// If not, let's move on
|
||||
const fileSourcePath = path.join(absoluteSourcePath, fileName);
|
||||
const copyPath = path.join(absoluteTargetPath, fileName);
|
||||
const relativeSourcePath = `${sourcePath ? `${sourcePath}/` : ''}${fileName}`;
|
||||
const fileUrl = `user_files/${relativeSourcePath}`
|
||||
|
||||
// Check if directory
|
||||
if (fs.statSync(fileSourcePath).isDirectory()) {
|
||||
if (!fs.existsSync(copyPath)) {
|
||||
fs.mkdirSync(copyPath);
|
||||
}
|
||||
|
||||
try {
|
||||
const virtualDirPath = `${targetPath}/${fileName}`;
|
||||
module.FS.mkdir(virtualDirPath);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
copyFilesAtPath(relativeSourcePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// We copy the files over and then add them as preload
|
||||
console.log(`loadDatafiles: Adding ${fileName}`);
|
||||
fs.copyFileSync(fileSourcePath, copyPath);
|
||||
|
||||
module.FS_createPreloadedFile(
|
||||
targetPath,
|
||||
fileName,
|
||||
fileUrl,
|
||||
true,
|
||||
true
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`loadDatafiles: Failed to preload ${fileName}`, error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`loadDatafiles: Failed to copyFilesAtPath`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const loadDatafiles = function () {
|
||||
module.autoloadFiles.forEach((filepath) => {
|
||||
module.FS_createPreloadedFile(
|
||||
"/",
|
||||
pathGetFilename(filepath),
|
||||
path.basename(filepath),
|
||||
filepath,
|
||||
true,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
// If the user has a macintosh.js dir, we'll copy over user
|
||||
// data
|
||||
if (!fs.existsSync(macDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
copyFilesAtPath('');
|
||||
};
|
||||
|
||||
if (module.autoloadFiles) {
|
||||
|
@ -49,7 +111,59 @@ function addCustomAsyncInit(module) {
|
|||
}
|
||||
}
|
||||
|
||||
var InputBufferAddresses = {
|
||||
function writeSafely(filePath, fileData) {
|
||||
return new Promise((resolve) => {
|
||||
fs.writeFile(filePath, fileData, (error) => {
|
||||
if (error) {
|
||||
console.error(`Disk save: Encountered error for ${filePath}`, error);
|
||||
} else {
|
||||
console.log(`Disk save: Finished writing ${filePath}`);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function saveFilesInPath(folderPath) {
|
||||
const entries = (Module.FS.readdir(folderPath) || []).filter(
|
||||
(v) => !v.startsWith(".")
|
||||
);
|
||||
|
||||
if (!entries || entries.length === 0) return;
|
||||
|
||||
// Ensure directory
|
||||
const targetDir = path.join(homeDir, folderPath);
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir);
|
||||
}
|
||||
|
||||
for (const file of entries) {
|
||||
try {
|
||||
const fileSourcePath = `${folderPath}/${file}`;
|
||||
const stat = Module.FS.analyzePath(fileSourcePath);
|
||||
|
||||
if (stat && stat.object && stat.object.isFolder) {
|
||||
// This is a folder, step into
|
||||
await saveFilesInPath(fileSourcePath);
|
||||
} else if (stat && stat.object && stat.object.contents) {
|
||||
const fileData = stat.object.contents;
|
||||
const filePath = path.join(targetDir, file);
|
||||
|
||||
await writeSafely(filePath, fileData);
|
||||
} else {
|
||||
console.log(
|
||||
`Disk save: Object at ${fileSourcePath} is something, but we don't know what`,
|
||||
stat
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Disk save: Could not write ${file}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let InputBufferAddresses = {
|
||||
globalLockAddr: 0,
|
||||
mouseMoveFlagAddr: 1,
|
||||
mouseMoveXDeltaAddr: 2,
|
||||
|
@ -60,7 +174,7 @@ var InputBufferAddresses = {
|
|||
keyStateAddr: 7,
|
||||
};
|
||||
|
||||
var LockStates = {
|
||||
let LockStates = {
|
||||
READY_FOR_UI_THREAD: 0,
|
||||
UI_THREAD_LOCK: 1,
|
||||
READY_FOR_EMUL_THREAD: 2,
|
||||
|
@ -69,7 +183,7 @@ var LockStates = {
|
|||
|
||||
var Module = null;
|
||||
|
||||
self.onmessage = function (msg) {
|
||||
self.onmessage = async function (msg) {
|
||||
console.log("Worker message received", msg.data);
|
||||
|
||||
// If it's a config object, start the show
|
||||
|
@ -84,39 +198,47 @@ self.onmessage = function (msg) {
|
|||
const diskData = Module.FS.readFile("/disk");
|
||||
const diskPath = path.join(__dirname, "disk");
|
||||
|
||||
fs.writeFile(diskPath, diskData, (error) => {
|
||||
// I wish we could do this with promises, but OOM crashes kill that idea
|
||||
try {
|
||||
console.log(`Trying to save disk`);
|
||||
fs.writeFileSync(diskPath, diskData);
|
||||
console.log(`Finished writing disk`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to write disk`, error);
|
||||
}
|
||||
|
||||
postMessage({ type: "disk_saved" });
|
||||
// Now, user files
|
||||
console.log(`Saving user files`);
|
||||
await saveFilesInPath("/macintosh.js");
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
// Clean up old copy dir
|
||||
cleanupCopyPath();
|
||||
|
||||
postMessage({ type: "disk_saved" });
|
||||
}
|
||||
};
|
||||
|
||||
function startEmulator(parentConfig) {
|
||||
var screenBufferView = new Uint8Array(
|
||||
let screenBufferView = new Uint8Array(
|
||||
parentConfig.screenBuffer,
|
||||
0,
|
||||
parentConfig.screenBufferSize
|
||||
);
|
||||
|
||||
var videoModeBufferView = new Int32Array(
|
||||
let videoModeBufferView = new Int32Array(
|
||||
parentConfig.videoModeBuffer,
|
||||
0,
|
||||
parentConfig.videoModeBufferSize
|
||||
);
|
||||
|
||||
var inputBufferView = new Int32Array(
|
||||
let inputBufferView = new Int32Array(
|
||||
parentConfig.inputBuffer,
|
||||
0,
|
||||
parentConfig.inputBufferSize
|
||||
);
|
||||
|
||||
var nextAudioChunkIndex = 0;
|
||||
var audioDataBufferView = new Uint8Array(
|
||||
let nextAudioChunkIndex = 0;
|
||||
let audioDataBufferView = new Uint8Array(
|
||||
parentConfig.audioDataBuffer,
|
||||
0,
|
||||
parentConfig.audioDataBufferSize
|
||||
|
@ -145,7 +267,7 @@ function startEmulator(parentConfig) {
|
|||
}
|
||||
|
||||
function tryToAcquireCyclicalLock(bufferView, lockIndex) {
|
||||
var res = Atomics.compareExchange(
|
||||
let res = Atomics.compareExchange(
|
||||
bufferView,
|
||||
lockIndex,
|
||||
LockStates.READY_FOR_EMUL_THREAD,
|
||||
|
@ -181,9 +303,9 @@ function startEmulator(parentConfig) {
|
|||
releaseCyclicalLock(inputBufferView, InputBufferAddresses.globalLockAddr);
|
||||
}
|
||||
|
||||
var AudioConfig = null;
|
||||
let AudioConfig = null;
|
||||
|
||||
var AudioBufferQueue = [];
|
||||
let AudioBufferQueue = [];
|
||||
|
||||
Module = {
|
||||
autoloadFiles: ["disk", "rom", "prefs"],
|
||||
|
@ -200,8 +322,8 @@ function startEmulator(parentConfig) {
|
|||
videoModeBufferView[1] = height;
|
||||
videoModeBufferView[2] = depth;
|
||||
videoModeBufferView[3] = usingPalette;
|
||||
var length = width * height * (depth === 32 ? 4 : 1); // 32bpp or 8bpp
|
||||
for (var i = 0; i < length; i++) {
|
||||
let length = width * height * (depth === 32 ? 4 : 1); // 32bpp or 8bpp
|
||||
for (let i = 0; i < length; i++) {
|
||||
screenBufferView[i] = Module.HEAPU8[bufPtr + i];
|
||||
}
|
||||
// releaseTwoStateLock(videoModeBufferView, 9);
|
||||
|
@ -223,14 +345,14 @@ function startEmulator(parentConfig) {
|
|||
},
|
||||
|
||||
enqueueAudio: function enqueueAudio(bufPtr, nbytes, type) {
|
||||
var newAudio = Module.HEAPU8.slice(bufPtr, bufPtr + nbytes);
|
||||
let newAudio = Module.HEAPU8.slice(bufPtr, bufPtr + nbytes);
|
||||
// console.assert(
|
||||
// nbytes == parentConfig.audioBlockBufferSize,
|
||||
// `emulator wrote ${nbytes}, expected ${parentConfig.audioBlockBufferSize}`
|
||||
// );
|
||||
|
||||
var writingChunkIndex = nextAudioChunkIndex;
|
||||
var writingChunkAddr =
|
||||
let writingChunkIndex = nextAudioChunkIndex;
|
||||
let writingChunkAddr =
|
||||
writingChunkIndex * parentConfig.audioBlockChunkSize;
|
||||
|
||||
if (audioDataBufferView[writingChunkAddr] === LockStates.UI_THREAD_LOCK) {
|
||||
|
@ -241,7 +363,7 @@ function startEmulator(parentConfig) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
var nextNextChunkIndex = writingChunkIndex + 1;
|
||||
let nextNextChunkIndex = writingChunkIndex + 1;
|
||||
if (
|
||||
nextNextChunkIndex * parentConfig.audioBlockChunkSize >
|
||||
audioDataBufferView.length - 1
|
||||
|
@ -297,7 +419,6 @@ function startEmulator(parentConfig) {
|
|||
releaseInputLock: releaseInputLock,
|
||||
};
|
||||
|
||||
// inject extra behaviours
|
||||
addAutoloader(Module);
|
||||
addCustomAsyncInit(Module);
|
||||
|
||||
|
|
|
@ -3963,6 +3963,7 @@ function copyTempDouble(ptr) {
|
|||
FS.mkdir('/tmp');
|
||||
FS.mkdir('/home');
|
||||
FS.mkdir('/home/electron');
|
||||
FS.mkdir('/macintosh.js')
|
||||
},createDefaultDevices:function () {
|
||||
// create /dev
|
||||
FS.mkdir('/dev');
|
||||
|
|
|
@ -17,4 +17,4 @@ async function start() {
|
|||
asyncLoop();
|
||||
}
|
||||
|
||||
//start();
|
||||
start();
|
||||
|
|
|
@ -5,13 +5,17 @@
|
|||
<title>macintosh.js</title>
|
||||
<link rel="stylesheet" href="style/index.css">
|
||||
</head>
|
||||
<body class="emulator_running">
|
||||
<body class="emulator_loading">
|
||||
<canvas id="canvas" width="800" height="600" oncontextmenu="event.preventDefault()"></canvas>
|
||||
<div id="disk_saving" class="dialog absolute_center hidden">
|
||||
<p>Saving virtual machine disk.</p>
|
||||
<p>Saving virtual machine disk and files.</p>
|
||||
<p>We'll quit once done.</p>
|
||||
<div class="lds-spinner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div id="progress" class="dialog absolute_center">
|
||||
<p>Loading emulator dependencies...</p>
|
||||
<progress id="progressbar" value="0" max="100"></progress>
|
||||
</div>
|
||||
<div class="controls garamond">
|
||||
<div class="clear">
|
||||
<a id="close" href="#">Quit</a>
|
||||
|
|
|
@ -49,10 +49,29 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.emulator_running #turned_off {
|
||||
#progress {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.emulator_loading #progress {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#warning {
|
||||
width: var(--warning-width);
|
||||
}
|
||||
|
||||
progress {
|
||||
margin-bottom: 20px;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-bar {
|
||||
background-color: #CCCCFF;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-value {
|
||||
background-color: #444444;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const {
|
|||
AUDIO_DATA_BUFFER_SIZE,
|
||||
} = require("./audio");
|
||||
const { quit } = require("./ipc");
|
||||
const { isDevMode } = require("../main/devmode");
|
||||
|
||||
let isWorkerRunning = false;
|
||||
let isWorkerSaving = false;
|
||||
|
@ -34,10 +35,14 @@ function saveDisk() {
|
|||
}
|
||||
|
||||
function handleDiskSaved() {
|
||||
console.log(`All files saved`);
|
||||
|
||||
isWorkerSaving = false;
|
||||
|
||||
// We're just gonna quit
|
||||
quit();
|
||||
if (!isDevMode) {
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWorkerShutdown() {
|
||||
|
@ -52,7 +57,7 @@ async function handleWorkerShutdown() {
|
|||
}
|
||||
|
||||
function registerWorker() {
|
||||
var workerConfig = {
|
||||
const workerConfig = {
|
||||
inputBuffer: inputBuffer,
|
||||
inputBufferSize: INPUT_BUFFER_SIZE,
|
||||
screenBuffer: screenBuffer,
|
||||
|
@ -76,44 +81,41 @@ function registerWorker() {
|
|||
|
||||
worker.postMessage(workerConfig);
|
||||
worker.onmessage = function (e) {
|
||||
if (
|
||||
e.data.type === "emulator_ready" ||
|
||||
e.data.type === "emulator_loading"
|
||||
) {
|
||||
// document.body.className =
|
||||
// e.data.type === 'emulator_ready' ? '' : 'loading';
|
||||
// const progressElement = document.getElementById('progress');
|
||||
// if (progressElement && e.data.type === 'emulator_loading') {
|
||||
// progressElement.value = Math.max(10, e.data.completion * 100);
|
||||
// progressElement.max = 100;
|
||||
// progressElement.hidden = false;
|
||||
// } else {
|
||||
// progressElement.value = null;
|
||||
// progressElement.max = null;
|
||||
// progressElement.hidden = true;
|
||||
// }
|
||||
}
|
||||
if (e.data.type === "emulator_loading") {
|
||||
const progressElement = document.querySelector("#progressbar");
|
||||
const progressDialog = document.querySelector("#progress p")
|
||||
|
||||
if (e.data.type === "TTY") {
|
||||
if (progressElement && e.data.type === "emulator_loading") {
|
||||
const val = Math.max(10, e.data.completion * 100);
|
||||
console.log(`Loading progress: ${val}`);
|
||||
|
||||
progressElement.value = val;
|
||||
progressElement.max = 100;
|
||||
} else {
|
||||
progressDialog.innerText = `Files loaded, now booting`;
|
||||
}
|
||||
} else if (e.data.type === "TTY") {
|
||||
// If we're shutting down, Basilisk II will send
|
||||
// close_audio to TTY - our signal that we can
|
||||
// save the disk image
|
||||
if (e.data.data === "close_audio") {
|
||||
handleWorkerShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.data.type === "disk_saved") {
|
||||
// If we're ready, Basilisk II will send
|
||||
// video_open()
|
||||
if (e.data.data === "video_open()") {
|
||||
document.body.classList.remove('emulator_loading');
|
||||
document.body.classList.add("emulator_running");
|
||||
}
|
||||
} else if (e.data.type === "disk_saved") {
|
||||
handleDiskSaved();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
window.setCanvasBlank = setCanvasBlank;
|
||||
|
||||
module.exports = {
|
||||
registerWorker,
|
||||
getIsWorkerRunning,
|
||||
getIsWorkerSaving,
|
||||
setCanvasBlank,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user