mirror of
https://github.com/felixrieseberg/macintosh.js.git
synced 2025-01-14 08:32:10 +00:00
feat: Auto-mount disk images
This commit is contained in:
parent
bf1f043bc4
commit
438dd38ab6
3
.gitignore
vendored
3
.gitignore
vendored
@ -90,5 +90,6 @@ out/
|
||||
|
||||
# Custom stuff
|
||||
src/basilisk/user_files
|
||||
src/basilisk/user_image_*
|
||||
src/basilisk/disk
|
||||
|
||||
src/basilisk/prefs
|
||||
|
@ -11,19 +11,19 @@ This is Mac OS 8, running in an [Electron](https://electronjs.org/) app. Yes, it
|
||||
![Screenshot]()
|
||||
|
||||
## Does it work?
|
||||
Yes! Quite well, actually - on macOS, Windows, and Linux. Bear in mind that this is written entirely in JavaScript, so please adjust your expectations. The virtual machine is emulating a Motorola CPU, which Apple used before switching to IBM's PowerPC architecture in the late 1990s.
|
||||
Yes! Quite well, actually - on macOS, Windows, and Linux. Bear in mind that this is written entirely in JavaScript, so please adjust your expectations. The virtual machine is emulating a 1991 Macintosh Quadra 900 with a Motorola CPU, which Apple used before switching to IBM's PowerPC architecture in the late 1990s.
|
||||
|
||||
## Should this have been a native app?
|
||||
Absolutely.
|
||||
|
||||
## Does it run my favorite game or app?
|
||||
You'll likely be better off with an actual virtualization app, but the short answer is yes. In fact, you'll find various games and demos preinstalled, thanks to an old MacWorld Demo CD from 1997. Namely, Oregon Trail, Duke Nukem 3D, Civilization II, Alley 19 Bowling, Damage Incorporated, and Dungeons & Dragons.
|
||||
The short answer is "Yes". In fact, you'll find various games and demos preinstalled, thanks to an old MacWorld Demo CD from 1997. Namely, Oregon Trail, Duke Nukem 3D, Civilization II, Alley 19 Bowling, Damage Incorporated, and Dungeons & Dragons.
|
||||
|
||||
There are also various apps and trials preinstalled, including Photoshop 3, Premiere 4, Illustrator 5.5, StuffIt Expander, the Apple Web Page Construction Kit, and more.
|
||||
|
||||
## Can I transfer files from and to the machine?
|
||||
|
||||
Yes, you can. Click on the "Help" button at the bottom of the running app to see instructions.
|
||||
Yes, you can. Click on the "Help" button at the bottom of the running app to see instructions. You can transfer files directly - or mount disk images.
|
||||
|
||||
## Can I connect to the Internet?
|
||||
|
||||
|
@ -12,7 +12,7 @@ module.exports = {
|
||||
appCategoryType: 'public.app-category.developer-tools',
|
||||
win32metadata: {
|
||||
CompanyName: 'Felix Rieseberg',
|
||||
OriginalFilename: 'macintosh.js'
|
||||
OriginalFilename: 'macintoshjs'
|
||||
},
|
||||
osxSign: {
|
||||
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
|
||||
@ -41,7 +41,7 @@ module.exports = {
|
||||
return {
|
||||
name: 'macintosh.js',
|
||||
authors: 'Felix Rieseberg',
|
||||
exe: 'macintoshjs.exe',
|
||||
exe: 'macintosh.js.exe',
|
||||
noMsi: true,
|
||||
remoteReleases: '',
|
||||
setupExe: `macintoshjs-${package.version}-setup-${arch}.exe`,
|
||||
|
@ -22,16 +22,21 @@ function addAutoloader(module) {
|
||||
try {
|
||||
const absoluteSourcePath = path.join(macDir, sourcePath);
|
||||
const absoluteTargetPath = path.join(macintoshCopyPath, sourcePath);
|
||||
const targetPath = `/macintosh.js${sourcePath ? `/${sourcePath}` : '' }`;
|
||||
const files = fs.readdirSync(absoluteSourcePath);
|
||||
const targetPath = `/macintosh.js${sourcePath ? `/${sourcePath}` : ""}`;
|
||||
const files = fs.readdirSync(absoluteSourcePath).filter((v) => {
|
||||
// Remove iso and img files
|
||||
return !v.endsWith(".iso") && !v.endsWith(".img");
|
||||
});
|
||||
|
||||
(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}`
|
||||
const relativeSourcePath = `${
|
||||
sourcePath ? `${sourcePath}/` : ""
|
||||
}${fileName}`;
|
||||
const fileUrl = `user_files/${relativeSourcePath}`;
|
||||
|
||||
// Check if directory
|
||||
if (fs.statSync(fileSourcePath).isDirectory()) {
|
||||
@ -63,9 +68,9 @@ function addAutoloader(module) {
|
||||
);
|
||||
} catch (error) {
|
||||
postMessage("showMessageBoxSync", {
|
||||
type: 'error',
|
||||
title: 'Could not transfer file',
|
||||
message: `We tried to transfer ${fileName} to the virtual machine, but failed. The error was: ${error}`
|
||||
type: "error",
|
||||
title: "Could not transfer file",
|
||||
message: `We tried to transfer ${fileName} to the virtual machine, but failed. The error was: ${error}`,
|
||||
});
|
||||
|
||||
console.error(`loadDatafiles: Failed to preload ${fileName}`, error);
|
||||
@ -73,20 +78,25 @@ function addAutoloader(module) {
|
||||
});
|
||||
} catch (error) {
|
||||
postMessage("showMessageBoxSync", {
|
||||
type: 'error',
|
||||
title: 'Could not transfer files',
|
||||
message: `We tried to transfer files to the virtual machine, but failed. The error was: ${error}`
|
||||
type: "error",
|
||||
title: "Could not transfer files",
|
||||
message: `We tried to transfer files to the virtual machine, but failed. The error was: ${error}`,
|
||||
});
|
||||
|
||||
console.error(`loadDatafiles: Failed to copyFilesAtPath`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadDatafiles = function () {
|
||||
module.autoloadFiles.forEach((filepath) => {
|
||||
const parent = `/`;
|
||||
const name = path.basename(filepath);
|
||||
|
||||
console.log(`Adding preload file`, { parent, name, url: filepath });
|
||||
|
||||
module.FS_createPreloadedFile(
|
||||
"/",
|
||||
path.basename(filepath),
|
||||
parent,
|
||||
name,
|
||||
filepath,
|
||||
true,
|
||||
true
|
||||
@ -99,7 +109,7 @@ function addAutoloader(module) {
|
||||
return;
|
||||
}
|
||||
|
||||
copyFilesAtPath('');
|
||||
copyFilesAtPath("");
|
||||
};
|
||||
|
||||
if (module.autoloadFiles) {
|
||||
@ -128,9 +138,9 @@ function writeSafely(filePath, fileData) {
|
||||
fs.writeFile(filePath, fileData, (error) => {
|
||||
if (error) {
|
||||
postMessage("showMessageBoxSync", {
|
||||
type: 'error',
|
||||
title: 'Could not save files',
|
||||
message: `We tried to save files from the virtual machine, but failed. The error was: ${error}`
|
||||
type: "error",
|
||||
title: "Could not save files",
|
||||
message: `We tried to save files from the virtual machine, but failed. The error was: ${error}`,
|
||||
});
|
||||
|
||||
console.error(`Disk save: Encountered error for ${filePath}`, error);
|
||||
@ -143,6 +153,102 @@ function writeSafely(filePath, fileData) {
|
||||
});
|
||||
}
|
||||
|
||||
function getPrefs(userImages = []) {
|
||||
try {
|
||||
const prefsTemplatePath = path.join(__dirname, "prefs_template");
|
||||
const prefsPath = path.join(__dirname, "prefs");
|
||||
let prefs = fs.readFileSync(prefsTemplatePath, { encoding: "utf-8" });
|
||||
|
||||
if (userImages && userImages.length > 0) {
|
||||
console.log(`getPrefs: Found ${userImages.length} user images`);
|
||||
userImages.forEach((file) => {
|
||||
if (file.endsWith(".iso")) {
|
||||
prefs += `\ncdrom ${file}`;
|
||||
} else if (file.endsWith(".img")) {
|
||||
prefs += `\ndisk ${file}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
prefs += `\n`;
|
||||
|
||||
fs.writeFileSync(prefsPath, prefs);
|
||||
} catch (error) {
|
||||
console.error(`getPrefs: Failed to set prefs`, error);
|
||||
}
|
||||
|
||||
return "prefs";
|
||||
}
|
||||
|
||||
function isMacDirFileOfType(extension = '', v = '') {
|
||||
const isType = v.endsWith(`.${extension}`);
|
||||
const isMatch = isType && fs.statSync(path.join(macDir, v)).isFile();
|
||||
|
||||
console.log(`isMacDirFileOfType: ${v} is file and ${extension}: ${isMatch}`);
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
function copyUserImages() {
|
||||
const result = [];
|
||||
|
||||
try {
|
||||
// No need if the macDir doesn't exist
|
||||
if (!fs.existsSync(macDir)) {
|
||||
console.log(`autoMountImageFiles: ${macDir} does not exist, exit`);
|
||||
return result;
|
||||
}
|
||||
|
||||
const macDirFiles = fs.readdirSync(macDir);
|
||||
const imgFiles = macDirFiles.filter((v) => isMacDirFileOfType('img', v));
|
||||
const isoFiles = macDirFiles.filter((v) => isMacDirFileOfType('iso', v));
|
||||
const isoImgFiles = [...isoFiles, ...imgFiles];
|
||||
|
||||
console.log(`copyUserImages: iso and img files`, isoImgFiles);
|
||||
|
||||
isoImgFiles.forEach((fileName, i) => {
|
||||
const sourcePath = path.join(macDir, fileName);
|
||||
const sanitizedFileName = `user_image_${i}_${fileName.replace(/[^\w\s\.]/gi, '')}`;
|
||||
const targetPath = path.join(__dirname, sanitizedFileName);
|
||||
|
||||
if (fs.existsSync(targetPath)) {
|
||||
const sourceStat = fs.statSync(sourcePath);
|
||||
const targetStat = fs.statSync(targetPath);
|
||||
|
||||
// Copy if the length is different
|
||||
if (sourceStat.size !== targetStat.size) {
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
} else {
|
||||
console.log(
|
||||
`autoMountImageFiles: ${sourcePath} already exists in ${targetPath}, not copying`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
}
|
||||
|
||||
console.log(`Copied over ${targetPath}`);
|
||||
result.push(sanitizedFileName);
|
||||
});
|
||||
|
||||
// Delete all old files
|
||||
const imagesCopyFiles = fs.readdirSync(__dirname);
|
||||
imagesCopyFiles.forEach((v) => {
|
||||
if (v.startsWith('user_image_') && !result.includes(v)) {
|
||||
fs.unlinkSync(path.join(__dirname, v));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`copyUserImages: Encountered error`, error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getAutoLoadFiles(userImages = []) {
|
||||
const autoLoadFiles = ["disk", "rom", "prefs", ...userImages];
|
||||
return autoLoadFiles;
|
||||
}
|
||||
|
||||
async function saveFilesInPath(folderPath) {
|
||||
const entries = (Module.FS.readdir(folderPath) || []).filter(
|
||||
(v) => !v.startsWith(".")
|
||||
@ -177,9 +283,9 @@ async function saveFilesInPath(folderPath) {
|
||||
}
|
||||
} catch (error) {
|
||||
postMessage("showMessageBoxSync", {
|
||||
type: 'error',
|
||||
title: 'Could not safe file',
|
||||
message: `We tried to save the file "${file}" from the virtual machine, but failed. The error was: ${error}`
|
||||
type: "error",
|
||||
title: "Could not safe file",
|
||||
message: `We tried to save the file "${file}" from the virtual machine, but failed. The error was: ${error}`,
|
||||
});
|
||||
|
||||
console.error(`Disk save: Could not write ${file}`, error);
|
||||
@ -328,20 +434,18 @@ function startEmulator(parentConfig) {
|
||||
}
|
||||
|
||||
let AudioConfig = null;
|
||||
|
||||
let AudioBufferQueue = [];
|
||||
const userImages = copyUserImages();
|
||||
|
||||
Module = {
|
||||
autoloadFiles: ["disk", "rom", "prefs"],
|
||||
autoloadFiles: getAutoLoadFiles(userImages),
|
||||
|
||||
arguments: ["--config", "prefs"],
|
||||
userImages: userImages,
|
||||
|
||||
arguments: ["--config", getPrefs(userImages)],
|
||||
canvas: null,
|
||||
|
||||
blit: function blit(bufPtr, width, height, depth, usingPalette) {
|
||||
// console.time('await worker video lock');
|
||||
// waitForTwoStateLock(videoModeBufferView, 9);
|
||||
// console.timeEnd('await worker video lock');
|
||||
|
||||
videoModeBufferView[0] = width;
|
||||
videoModeBufferView[1] = height;
|
||||
videoModeBufferView[2] = depth;
|
||||
@ -370,11 +474,6 @@ function startEmulator(parentConfig) {
|
||||
|
||||
enqueueAudio: function enqueueAudio(bufPtr, nbytes, type) {
|
||||
let newAudio = Module.HEAPU8.slice(bufPtr, bufPtr + nbytes);
|
||||
// console.assert(
|
||||
// nbytes == parentConfig.audioBlockBufferSize,
|
||||
// `emulator wrote ${nbytes}, expected ${parentConfig.audioBlockBufferSize}`
|
||||
// );
|
||||
|
||||
let writingChunkIndex = nextAudioChunkIndex;
|
||||
let writingChunkAddr =
|
||||
writingChunkIndex * parentConfig.audioBlockChunkSize;
|
||||
|
@ -37,6 +37,12 @@
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h2>How can I mount disk images?</h2>
|
||||
|
||||
<p>
|
||||
If you have files in the "iso" or "img" format, place them in the "macintosh.js" folder in your user directory. We will automatically mount them as virtual drives. There's a considerable performance hit for each mounted virtual drive and disk, so be careful.
|
||||
</p>
|
||||
|
||||
<h2>Developer Tools</h2>
|
||||
|
||||
<p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user