mirror of
https://github.com/felixrieseberg/macintosh.js.git
synced 2024-06-13 23:29:36 +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
|
# Custom stuff
|
||||||
src/basilisk/user_files
|
src/basilisk/user_files
|
||||||
|
src/basilisk/user_image_*
|
||||||
src/basilisk/disk
|
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]()
|
![Screenshot]()
|
||||||
|
|
||||||
## Does it work?
|
## 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?
|
## Should this have been a native app?
|
||||||
Absolutely.
|
Absolutely.
|
||||||
|
|
||||||
## Does it run my favorite game or app?
|
## 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.
|
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?
|
## 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?
|
## Can I connect to the Internet?
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ module.exports = {
|
||||||
appCategoryType: 'public.app-category.developer-tools',
|
appCategoryType: 'public.app-category.developer-tools',
|
||||||
win32metadata: {
|
win32metadata: {
|
||||||
CompanyName: 'Felix Rieseberg',
|
CompanyName: 'Felix Rieseberg',
|
||||||
OriginalFilename: 'macintosh.js'
|
OriginalFilename: 'macintoshjs'
|
||||||
},
|
},
|
||||||
osxSign: {
|
osxSign: {
|
||||||
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
|
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
|
||||||
|
@ -41,7 +41,7 @@ module.exports = {
|
||||||
return {
|
return {
|
||||||
name: 'macintosh.js',
|
name: 'macintosh.js',
|
||||||
authors: 'Felix Rieseberg',
|
authors: 'Felix Rieseberg',
|
||||||
exe: 'macintoshjs.exe',
|
exe: 'macintosh.js.exe',
|
||||||
noMsi: true,
|
noMsi: true,
|
||||||
remoteReleases: '',
|
remoteReleases: '',
|
||||||
setupExe: `macintoshjs-${package.version}-setup-${arch}.exe`,
|
setupExe: `macintoshjs-${package.version}-setup-${arch}.exe`,
|
||||||
|
|
|
@ -22,16 +22,21 @@ function addAutoloader(module) {
|
||||||
try {
|
try {
|
||||||
const absoluteSourcePath = path.join(macDir, sourcePath);
|
const absoluteSourcePath = path.join(macDir, sourcePath);
|
||||||
const absoluteTargetPath = path.join(macintoshCopyPath, sourcePath);
|
const absoluteTargetPath = path.join(macintoshCopyPath, sourcePath);
|
||||||
const targetPath = `/macintosh.js${sourcePath ? `/${sourcePath}` : '' }`;
|
const targetPath = `/macintosh.js${sourcePath ? `/${sourcePath}` : ""}`;
|
||||||
const files = fs.readdirSync(absoluteSourcePath);
|
const files = fs.readdirSync(absoluteSourcePath).filter((v) => {
|
||||||
|
// Remove iso and img files
|
||||||
|
return !v.endsWith(".iso") && !v.endsWith(".img");
|
||||||
|
});
|
||||||
|
|
||||||
(files || []).forEach((fileName) => {
|
(files || []).forEach((fileName) => {
|
||||||
try {
|
try {
|
||||||
// If not, let's move on
|
// If not, let's move on
|
||||||
const fileSourcePath = path.join(absoluteSourcePath, fileName);
|
const fileSourcePath = path.join(absoluteSourcePath, fileName);
|
||||||
const copyPath = path.join(absoluteTargetPath, fileName);
|
const copyPath = path.join(absoluteTargetPath, fileName);
|
||||||
const relativeSourcePath = `${sourcePath ? `${sourcePath}/` : ''}${fileName}`;
|
const relativeSourcePath = `${
|
||||||
const fileUrl = `user_files/${relativeSourcePath}`
|
sourcePath ? `${sourcePath}/` : ""
|
||||||
|
}${fileName}`;
|
||||||
|
const fileUrl = `user_files/${relativeSourcePath}`;
|
||||||
|
|
||||||
// Check if directory
|
// Check if directory
|
||||||
if (fs.statSync(fileSourcePath).isDirectory()) {
|
if (fs.statSync(fileSourcePath).isDirectory()) {
|
||||||
|
@ -63,9 +68,9 @@ function addAutoloader(module) {
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
postMessage("showMessageBoxSync", {
|
postMessage("showMessageBoxSync", {
|
||||||
type: 'error',
|
type: "error",
|
||||||
title: 'Could not transfer file',
|
title: "Could not transfer file",
|
||||||
message: `We tried to transfer ${fileName} to the virtual machine, but failed. The error was: ${error}`
|
message: `We tried to transfer ${fileName} to the virtual machine, but failed. The error was: ${error}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.error(`loadDatafiles: Failed to preload ${fileName}`, error);
|
console.error(`loadDatafiles: Failed to preload ${fileName}`, error);
|
||||||
|
@ -73,20 +78,25 @@ function addAutoloader(module) {
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
postMessage("showMessageBoxSync", {
|
postMessage("showMessageBoxSync", {
|
||||||
type: 'error',
|
type: "error",
|
||||||
title: 'Could not transfer files',
|
title: "Could not transfer files",
|
||||||
message: `We tried to transfer files to the virtual machine, but failed. The error was: ${error}`
|
message: `We tried to transfer files to the virtual machine, but failed. The error was: ${error}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.error(`loadDatafiles: Failed to copyFilesAtPath`, error);
|
console.error(`loadDatafiles: Failed to copyFilesAtPath`, error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const loadDatafiles = function () {
|
const loadDatafiles = function () {
|
||||||
module.autoloadFiles.forEach((filepath) => {
|
module.autoloadFiles.forEach((filepath) => {
|
||||||
|
const parent = `/`;
|
||||||
|
const name = path.basename(filepath);
|
||||||
|
|
||||||
|
console.log(`Adding preload file`, { parent, name, url: filepath });
|
||||||
|
|
||||||
module.FS_createPreloadedFile(
|
module.FS_createPreloadedFile(
|
||||||
"/",
|
parent,
|
||||||
path.basename(filepath),
|
name,
|
||||||
filepath,
|
filepath,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
|
@ -99,7 +109,7 @@ function addAutoloader(module) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
copyFilesAtPath('');
|
copyFilesAtPath("");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (module.autoloadFiles) {
|
if (module.autoloadFiles) {
|
||||||
|
@ -128,9 +138,9 @@ function writeSafely(filePath, fileData) {
|
||||||
fs.writeFile(filePath, fileData, (error) => {
|
fs.writeFile(filePath, fileData, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
postMessage("showMessageBoxSync", {
|
postMessage("showMessageBoxSync", {
|
||||||
type: 'error',
|
type: "error",
|
||||||
title: 'Could not save files',
|
title: "Could not save files",
|
||||||
message: `We tried to save files from the virtual machine, but failed. The error was: ${error}`
|
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);
|
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) {
|
async function saveFilesInPath(folderPath) {
|
||||||
const entries = (Module.FS.readdir(folderPath) || []).filter(
|
const entries = (Module.FS.readdir(folderPath) || []).filter(
|
||||||
(v) => !v.startsWith(".")
|
(v) => !v.startsWith(".")
|
||||||
|
@ -177,9 +283,9 @@ async function saveFilesInPath(folderPath) {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
postMessage("showMessageBoxSync", {
|
postMessage("showMessageBoxSync", {
|
||||||
type: 'error',
|
type: "error",
|
||||||
title: 'Could not safe file',
|
title: "Could not safe file",
|
||||||
message: `We tried to save the file "${file}" from the virtual machine, but failed. The error was: ${error}`
|
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);
|
console.error(`Disk save: Could not write ${file}`, error);
|
||||||
|
@ -328,20 +434,18 @@ function startEmulator(parentConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let AudioConfig = null;
|
let AudioConfig = null;
|
||||||
|
|
||||||
let AudioBufferQueue = [];
|
let AudioBufferQueue = [];
|
||||||
|
const userImages = copyUserImages();
|
||||||
|
|
||||||
Module = {
|
Module = {
|
||||||
autoloadFiles: ["disk", "rom", "prefs"],
|
autoloadFiles: getAutoLoadFiles(userImages),
|
||||||
|
|
||||||
arguments: ["--config", "prefs"],
|
userImages: userImages,
|
||||||
|
|
||||||
|
arguments: ["--config", getPrefs(userImages)],
|
||||||
canvas: null,
|
canvas: null,
|
||||||
|
|
||||||
blit: function blit(bufPtr, width, height, depth, usingPalette) {
|
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[0] = width;
|
||||||
videoModeBufferView[1] = height;
|
videoModeBufferView[1] = height;
|
||||||
videoModeBufferView[2] = depth;
|
videoModeBufferView[2] = depth;
|
||||||
|
@ -370,11 +474,6 @@ function startEmulator(parentConfig) {
|
||||||
|
|
||||||
enqueueAudio: function enqueueAudio(bufPtr, nbytes, type) {
|
enqueueAudio: function enqueueAudio(bufPtr, nbytes, type) {
|
||||||
let 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}`
|
|
||||||
// );
|
|
||||||
|
|
||||||
let writingChunkIndex = nextAudioChunkIndex;
|
let writingChunkIndex = nextAudioChunkIndex;
|
||||||
let writingChunkAddr =
|
let writingChunkAddr =
|
||||||
writingChunkIndex * parentConfig.audioBlockChunkSize;
|
writingChunkIndex * parentConfig.audioBlockChunkSize;
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</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>
|
<h2>Developer Tools</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user