feat: Auto-mount disk images

This commit is contained in:
Felix Rieseberg 2020-07-27 15:07:47 -07:00
parent bf1f043bc4
commit 438dd38ab6
6 changed files with 145 additions and 39 deletions

3
.gitignore vendored
View File

@ -90,5 +90,6 @@ out/
# Custom stuff
src/basilisk/user_files
src/basilisk/user_image_*
src/basilisk/disk
src/basilisk/prefs

View File

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

View File

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

View File

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

View File

@ -30,4 +30,4 @@ mousewheellines 3
dsp /dev/dsp
mixer /dev/mixer
ignoresegv false
idlewait false
idlewait false

View File

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