Now with working network

This commit is contained in:
Felix Rieseberg
2025-02-18 22:39:47 -08:00
parent c9e45a9f39
commit 5c946bbca4
26 changed files with 688 additions and 32 deletions

21
HELP.md
View File

@@ -11,25 +11,4 @@ back to Window Mode. (Thanks to @DisplacedGamers for that wisdom)
On the app's home screen, select "Settings" in the lower menu. Then, delete your On the app's home screen, select "Settings" in the lower menu. Then, delete your
machine's state before starting it again - this time hopefully without issues. machine's state before starting it again - this time hopefully without issues.
## I want to install additional apps or games
If you are running macOS, or Linux, you can probably "mount" the
virtual hard drive used by `windows95` to add files. Hit the "Modify C: Drive"
button, which will take you to the disk image.
On macOS, double-click the disk image to open it.
On Windows 10, Windows will _think_ that it can open up the image, but will
actually fail to do so. Use a tool [like OSFMount][osfmount] to mount your
disk image.
On Linux, search the Internet for instructions on how to mount an `img` disk
image on your distribution. It's likely that you'll be able to run `mount`
with the image as input.
[osfmount]: https://www.osforensics.com/tools/mount-disk-images.html
## What's the FrontPage Username and Password?
Username: windows95
Password: password

View File

@@ -1,7 +1,7 @@
import * as path from "path"; import * as path from "path";
export const CONSTANTS = { export const CONSTANTS = {
IMAGE_PATH: path.join(__dirname, "../../images/windows95.img"), IMAGE_PATH: path.join(__dirname, "../../images/windows95_v4.raw"),
IMAGE_DEFAULT_SIZE: 1073741824, // 1GB IMAGE_DEFAULT_SIZE: 1073741824, // 1GB
DEFAULT_STATE_PATH: path.join(__dirname, "../../images/default-state.bin"), DEFAULT_STATE_PATH: path.join(__dirname, "../../images/default-state.bin"),
}; };

View File

@@ -0,0 +1,15 @@
export function encode(text: string) {
// Convert to windows-1252 compatible string by removing unsupported chars
let result = text.replaceAll(/[^\x00-\xFF]/g, '');
// If result would be empty, return original
if (!result.trim()) {
return text;
}
return result;
}
export function getEncoding() {
return `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">`;
}

View File

@@ -0,0 +1,157 @@
import { protocol, net } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { generateDirectoryListing } from './page-directory-listing';
import { generateErrorPage } from './page-error';
import { log } from '../logging';
export interface FileEntry {
name: string;
fullPath: string;
stats: fs.Stats;
}
export const APP_INTERCEPT = 'http://windows95/';
export const MY_COMPUTER_INTERCEPT = 'http://my-computer/';
const interceptedUrls = [
MY_COMPUTER_INTERCEPT,
APP_INTERCEPT
];
export function setupFileServer() {
// Register protocol handler for our custom schema
protocol.handle('http', async (request) => {
if (!interceptedUrls.some(url => request.url.startsWith(url))) {
return fetch(request.url, {
headers: request.headers,
method: request.method,
body: request.body,
});
}
try {
const { fullPath, decodedPath } = getFilePath(request.url);
log(`FileServer: Handling request for ${request.url}`, { fullPath, decodedPath });
// Check if path exists
if (!fs.existsSync(fullPath)) {
return new Response(generateErrorPage(
'File or Directory Not Found',
decodedPath
), {
status: 404,
headers: {
'Content-Type': 'text/html'
}
});
}
// Check if it's a directory
const stats = await fs.promises.stat(fullPath);
if (stats.isDirectory()) {
// If we're in an app-intercept, check if there's an index.htm file in the directory
if (request.url.startsWith(APP_INTERCEPT)) {
const indexHtmlPath = path.join(fullPath, 'index.htm');
if (fs.existsSync(indexHtmlPath)) {
return serveFile(indexHtmlPath);
}
}
// Generate directory listing
const files = await fs.promises.readdir(fullPath);
const listing = generateDirectoryListing(fullPath, files);
return new Response(listing, {
status: 200,
headers: {
'Content-Type': 'text/html'
}
});
} else {
try {
return await serveFile(fullPath);
} catch (error) {
// Handle specific file read errors
if (error.code === 'EACCES') {
return new Response(generateErrorPage(
'Access Denied',
'You do not have permission to access this file'
), {
status: 403,
headers: {
'Content-Type': 'text/html'
}
});
}
// Re-throw other errors to be caught by outer try-catch
throw error;
}
}
} catch (error) {
const errorPage = generateErrorPage(
'Internal Server Error',
`An error occurred while processing your request: ${error.message}`
);
return new Response(errorPage, {
status: 500,
headers: {
'Content-Type': 'text/html'
}
});
}
});
}
function getFilePath(url: string) {
let urlPath: string;
let fullPath: string;
let decodedPath: string;
if (url.startsWith(APP_INTERCEPT)) {
fullPath = path.resolve(__dirname, '../../../static/www', url.replace(APP_INTERCEPT, ''));
decodedPath = '.';
} else if (url.startsWith(MY_COMPUTER_INTERCEPT)) {
urlPath = url.replace(MY_COMPUTER_INTERCEPT, '');
decodedPath = decodeURIComponent(urlPath);
fullPath = path.join('/', decodedPath);
} else {
throw new Error('Invalid URL');
}
return { fullPath, decodedPath };
}
async function serveFile(fullPath: string): Promise<Response> {
const fileData = await fs.promises.readFile(fullPath);
// Determine content type based on file extension
const ext = path.extname(fullPath).toLowerCase();
let contentType = 'application/octet-stream';
// Common content types
const contentTypes: Record<string, string> = {
'.htm': 'text/html',
'.html': 'text/html',
'.txt': 'text/plain',
'.css': 'text/css',
'.js': 'text/javascript',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif'
};
if (ext in contentTypes) {
contentType = contentTypes[ext];
}
return new Response(fileData, {
status: 200,
headers: {
'Content-Type': contentType
}
});
}

View File

@@ -0,0 +1,72 @@
import { Stats } from "fs";
import { settings } from "../settings";
import { FileEntry } from "./fileserver";
const FILES_TO_HIDE_ON_DARWIN: string[] = [
'.DS_Store',
'.localized',
'.Trashes',
'.fseventsd',
'.Spotlight-V100',
'.file',
'.hotfiles.btree',
'.DocumentRevisions-V100',
'.TemporaryItems',
'.file (resource fork files)',
'.VolumeIcon.icns',
];
const FILES_TO_HIDE_ON_WINDOWS: string[] = [
'desktop.ini',
'Thumbs.db',
'ehthumbs.db',
'ehthumbs.db-shm',
'ehthumbs.db-wal',
];
const FILES_TO_HIDE_ON_LINUX: string[] = [];
export function shouldHideFile(file: FileEntry) {
if (isHiddenFile(file) && !settings.get('isFileServerShowingHiddenFiles')) {
return true;
}
if (isSystemHiddenFile(file) && !settings.get('isFileServerShowingSystemHiddenFiles')) {
return true;
}
return false;
}
export function isHiddenFile(file: FileEntry) {
if (process.platform === 'win32') {
return (file.stats.mode & 0x2) === 0x2;
} else {
return file.name.startsWith('.');
}
}
export function isSystemHiddenFile(file: FileEntry) {
return getFilesToHide().some(hiddenFile => file.name.endsWith(hiddenFile));
}
let _filesToHide: string[];
function getFilesToHide() {
if (_filesToHide) {
return _filesToHide;
}
if (process.platform === 'darwin') {
_filesToHide = FILES_TO_HIDE_ON_DARWIN;
} else if (process.platform === 'win32') {
_filesToHide = FILES_TO_HIDE_ON_WINDOWS;
} else {
_filesToHide = FILES_TO_HIDE_ON_LINUX;
}
return _filesToHide;
}

View File

@@ -0,0 +1,120 @@
import path from "path";
import fs from "fs";
import { APP_INTERCEPT, FileEntry, MY_COMPUTER_INTERCEPT } from "./fileserver";
import { shouldHideFile } from "./hide-files";
import { encode, getEncoding } from "./encoding";
import { log } from "console";
import { app } from "electron";
export function generateDirectoryListing(currentPath: string, files: string[]): string {
const parentPath = path.dirname(currentPath || '/');
const title = currentPath === '/' ? 'My Host Computer' : `Directory: ${encode(currentPath)}`;
// Get file info and sort (directories first, then alphabetically)
const items = files
.map(name => {
const fullPath = path.join(currentPath, name);
let stats: fs.Stats;
try {
stats = fs.statSync(fullPath);
} catch (error) {
log(`FileServer: Failed to get stats for ${fullPath}: ${error}`);
stats = new fs.Stats();
}
return {
name,
fullPath,
stats
} as FileEntry;
})
.filter(entry => entry.stats && !shouldHideFile(entry))
.sort((a, b) => {
if (a.stats.isDirectory() !== b.stats.isDirectory()) {
return a.stats.isDirectory() ? -1 : 1;
}
return a.name.localeCompare(b.name);
})
.map(getFileLiHtml)
.join('')
// Generate very simple HTML that works in IE 5.5
return `
<html>
<head>
${getEncoding()}
<title>${title}</title>
</head>
<body>
<h2>${title}</h2>
<p>${getParentFolderLinkHtml(parentPath)} | ${getDesktopLinkHtml()} | ${getDownloadsLinkHtml()}</p>
<p>
<ul>
${items}
</ul>
</body>
</html>
`;
}
function getParentFolderLinkHtml(parentPath: string) {
return `
${getIconHtml('folder.gif')}
<a href="${MY_COMPUTER_INTERCEPT}${encodeURI(parentPath)}">
[Parent Directory]
</a>
`;
}
function getDesktopLinkHtml() {
const desktopPath = app.getPath('desktop');
return `
${getIconHtml('desktop.gif')}
<a href="${MY_COMPUTER_INTERCEPT}${encodeURI(desktopPath)}">
Desktop
</a>
`;
}
function getDownloadsLinkHtml() {
const downloadsPath = app.getPath('downloads');
return `
${getIconHtml('network.gif')}
<a href="${MY_COMPUTER_INTERCEPT}${encodeURI(downloadsPath)}">
Downloads
</a>
`;
}
function getIconHtml(icon: string) {
return `<img src="${APP_INTERCEPT}images/${icon}" style="vertical-align: middle; margin-right: 5px;" width="16" height="16">`;
}
function getFileLiHtml(entry: FileEntry) {
const encodedPath = encodeURI(entry.fullPath);
const sizeDisplay = entry.stats.isDirectory() ? '' : ` (${formatFileSize(entry.stats.size)})`;
const icon = entry.stats.isDirectory() ? getIconHtml('folder.gif') : getIconHtml('doc.gif');
return `<li>
${icon}
<a href="${MY_COMPUTER_INTERCEPT}${encodedPath}">
${getDisplayName(entry)}
</a>
${sizeDisplay}
</li>`;
}
function getDisplayName(entry: FileEntry) {
return encode(entry.stats.isDirectory() ? `[${entry.name}]` : entry.name);
}
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

View File

@@ -0,0 +1,22 @@
import { getEncoding } from "./encoding";
import { MY_COMPUTER_INTERCEPT } from "./fileserver";
export function generateErrorPage(errorMessage: string, requestedPath: string): string {
return `
<html>
<head>
${getEncoding()}
<title>Error - File Not Found</title>
</head>
<body>
<h2>Error: ${errorMessage}</h2>
<p>windows95 failed to find the file or directory on your host computer: <code>${requestedPath}</code></p>
<p>Options:</p>
<ul>
<li><a href="${MY_COMPUTER_INTERCEPT}">Return to root directory</a></li>
<li><a href="javascript:history.back()">Go back to previous page</a></li>
</ul>
</body>
</html>
`;
}

View File

@@ -5,7 +5,7 @@ import { IPC_COMMANDS } from "../constants";
export function setupIpcListeners() { export function setupIpcListeners() {
ipcMain.handle(IPC_COMMANDS.GET_STATE_PATH, () => { ipcMain.handle(IPC_COMMANDS.GET_STATE_PATH, () => {
return path.join(app.getPath("userData"), "state-v3.bin"); return path.join(app.getPath("userData"), "state-v4.bin");
}); });
ipcMain.handle(IPC_COMMANDS.APP_QUIT, () => { ipcMain.handle(IPC_COMMANDS.APP_QUIT, () => {

3
src/main/logging.ts Normal file
View File

@@ -0,0 +1,3 @@
export function log(message: string, ...args: unknown[]) {
console.log(`[${new Date().toLocaleString()}] ${message}`, ...args);
}

View File

@@ -7,6 +7,8 @@ import { setupUpdates } from "./update";
import { getOrCreateWindow } from "./windows"; import { getOrCreateWindow } from "./windows";
import { setupMenu } from "./menu"; import { setupMenu } from "./menu";
import { setupIpcListeners } from "./ipc"; import { setupIpcListeners } from "./ipc";
import { setupSession } from "./session";
import { setupFileServer } from './fileserver/fileserver';
/** /**
* Handle the app's "ready" event. This is essentially * Handle the app's "ready" event. This is essentially
@@ -15,11 +17,13 @@ import { setupIpcListeners } from "./ipc";
export async function onReady() { export async function onReady() {
if (!isDevMode()) process.env.NODE_ENV = "production"; if (!isDevMode()) process.env.NODE_ENV = "production";
setupSession();
setupIpcListeners(); setupIpcListeners();
getOrCreateWindow(); getOrCreateWindow();
setupAboutPanel(); setupAboutPanel();
setupMenu(); setupMenu();
setupUpdates(); setupUpdates();
setupFileServer();
} }
/** /**

View File

@@ -3,6 +3,7 @@ import { app, shell, Menu, BrowserWindow, ipcMain } from "electron";
import { clearCaches } from "../cache"; import { clearCaches } from "../cache";
import { IPC_COMMANDS } from "../constants"; import { IPC_COMMANDS } from "../constants";
import { isDevMode } from "../utils/devmode"; import { isDevMode } from "../utils/devmode";
import { log } from "./logging";
const LINKS = { const LINKS = {
homepage: "https://www.twitter.com/felixrieseberg", homepage: "https://www.twitter.com/felixrieseberg",
@@ -26,10 +27,10 @@ function send(cmd: string) {
const windows = BrowserWindow.getAllWindows(); const windows = BrowserWindow.getAllWindows();
if (windows[0]) { if (windows[0]) {
console.log(`Sending "${cmd}"`); log(`Sending "${cmd}"`);
windows[0].webContents.send(cmd); windows[0].webContents.send(cmd);
} else { } else {
console.log(`Tried to send "${cmd}", but could not find window`); log(`Tried to send "${cmd}", but could not find window`);
} }
} }

20
src/main/session.ts Normal file
View File

@@ -0,0 +1,20 @@
import { session } from "electron";
export function setupSession() {
const s = session.defaultSession;
s.webRequest.onBeforeSendHeaders(
(details, callback) => {
callback({ requestHeaders: { Origin: '*', ...details.requestHeaders } });
},
);
s.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
'Access-Control-Allow-Origin': ['*'],
...details.responseHeaders,
},
});
});
}

72
src/main/settings.ts Normal file
View File

@@ -0,0 +1,72 @@
import * as fs from 'fs';
import * as path from 'path';
import { app } from 'electron';
export interface Settings {
isFileServerEnabled: boolean;
isFileServerShowingHiddenFiles: boolean;
isFileServerShowingSystemHiddenFiles: boolean;
}
const DEFAULT_SETTINGS: Settings = {
isFileServerEnabled: true,
isFileServerShowingHiddenFiles: false,
isFileServerShowingSystemHiddenFiles: false,
};
class SettingsManager {
private filePath: string;
private data: Settings;
constructor() {
this.filePath = path.join(app.getPath('userData'), 'settings.json');
this.data = this.load();
}
private load(): Settings {
try {
if (fs.existsSync(this.filePath)) {
const fileContent = fs.readFileSync(this.filePath, 'utf8');
const parsed = JSON.parse(fileContent);
return {
...DEFAULT_SETTINGS,
...parsed,
};
}
} catch (error) {
console.error('Error loading settings:', error);
}
return DEFAULT_SETTINGS;
}
private save(): void {
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
} catch (error) {
console.error('Error saving settings:', error);
}
}
get(key: keyof Settings): any {
return this.data[key];
}
set(key: keyof Settings, value: any): void {
this.data[key] = value;
this.save();
}
delete(key: keyof Settings): void {
delete this.data[key];
this.save();
}
clear(): void {
this.data = DEFAULT_SETTINGS;
this.save();
}
}
export const settings = new SettingsManager();

View File

@@ -24,9 +24,6 @@ export function getOrCreateWindow(): BrowserWindow {
mainWindow.webContents.on("will-navigate", (event, url) => mainWindow.webContents.on("will-navigate", (event, url) =>
handleNavigation(event, url), handleNavigation(event, url),
); );
mainWindow.webContents.on("new-window", (event, url) =>
handleNavigation(event, url),
);
mainWindow.on("closed", () => { mainWindow.on("closed", () => {
mainWindow = null; mainWindow = null;

View File

@@ -279,8 +279,13 @@ export class Emulator extends React.Component<{}, EmulatorState> {
const options = { const options = {
wasm_path: path.join(__dirname, "build/v86.wasm"), wasm_path: path.join(__dirname, "build/v86.wasm"),
memory_size: 128 * 1024 * 1024, memory_size: 128 * 1024 * 1024,
vga_memory_size: 32 * 1024 * 1024, vga_memory_size: 64 * 1024 * 1024,
screen_container: document.getElementById("emulator"), screen_container: document.getElementById("emulator"),
preserve_mac_from_state_image: true,
net_device: {
relay_url: "fetch",
type: "ne2k",
},
bios: { bios: {
url: path.join(__dirname, "../../bios/seabios.bin"), url: path.join(__dirname, "../../bios/seabios.bin"),
}, },
@@ -367,7 +372,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
} }
/** /**
* Reset the emulator by reloading the whole page (lol) * Reset the emulator by reloading the whole page
*/ */
private async resetEmulator() { private async resetEmulator() {
this.isResetting = true; this.isResetting = true;

20
static/www/apps.htm Normal file
View File

@@ -0,0 +1,20 @@
<html>
<head>
<title>windows95 Help</title>
</head>
<body bgcolor="#C0C0C0">
<table width="100%" cellpadding="10" cellspacing="0">
<tr>
<td>
<font face="Arial" color="#000000">
<font size="5"><b>windows95 Apps & Games</b></font>
<hr>
<p>I've installed a few apps and games for you to try out. Check out the Games folder on the desktop!</p>
<p>If you want to try other games, I recommend trying to find them on the Internet Archive. On your host computer, visit https://archive.org, then find the "Classic PC Games" category. Once downloaded, you can import them into windows95 from <a href="http://my-computer">your host's Download folder</a>.</p>
</font>
</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
static/www/buttons/msie.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

31
static/www/credits.htm Normal file
View File

@@ -0,0 +1,31 @@
<html>
<head>
<title>Windows 95 Credits</title>
</head>
<body bgcolor="#C0C0C0">
<table width="100%" cellpadding="10" cellspacing="0">
<tr>
<td>
<font face="Arial" color="#000000">
<font size="5"><b>windows95 Credits</b></font>
<hr>
<h3>Emulation Engine</h3>
<p>
None of this would be possible without the people working on v86, in particular Fabian Hemmer aka copy.
You can visit his website at <a href="http://copy.sh" target="_blank">copy.sh</a>. It also wouldn't be
possible without the QEMU project. If you enjoy running old systems, you probably want QEMU - windows95
is merely a toy, QEMU lets you actually run old systems in a stable manner.
</p>
<h3>Electron</h3>
<p>
Electron is a framework for building desktop applications using web technologies. It's what powers windows95.
You can visit the project's website at electronjs.org (in a modern browser, not in windows95).
</p>
</font>
</td>
</tr>
</table>
</body>
</html>

34
static/www/help.htm Normal file
View File

@@ -0,0 +1,34 @@
<html>
<head>
<title>windows95 Help</title>
</head>
<body bgcolor="#C0C0C0">
<table width="100%" cellpadding="10" cellspacing="0">
<tr>
<td>
<font face="Arial" color="#000000">
<font size="5"><b>windows95 Help</b></font>
<hr>
<h3>MS-DOS Display Issues</h3>
<p>If MS-DOS seems to mess up the screen:</p>
<ol>
<li>Hit <code>Alt + Enter</code> to make the command screen "Full Screen" (as far as Windows 95 is concerned)</li>
<li>This should restore the display from the garbled mess you see and allow you to access the Command Prompt</li>
<li>Press Alt-Enter again to leave Full Screen and go back to Window Mode</li>
</ol>
<p><i>(Thanks to @DisplacedGamers for that wisdom)</i></p>
<h3>windows95 Stuck in Bad State</h3>
<p>If windows95 becomes unresponsive or stuck:</p>
<ol>
<li>On the app's home screen, select "Settings" in the lower menu</li>
<li>Delete your machine's state before starting it again</li>
<li>This should resolve the issue when you restart</li>
</ol>
</font>
</td>
</tr>
</table>
</body>
</html>

37
static/www/home.htm Normal file
View File

@@ -0,0 +1,37 @@
<html>
<head>
<title>Welcome to Windows 95!</title>
</head>
<body bgcolor="#C0C0C0">
<table width="100%" cellpadding="10" cellspacing="0">
<tr>
<td>
<center>
<marquee scrollamount="3">
<font face="Arial" size="6" color="#000000">
<blink>Welcome to Windows 95!</blink>
</font>
</marquee>
</center>
<font face="Arial" color="#000000">
<p>Hi, I'm Felix, the maker behind windows95. I hope you're having fun!</p>
<p>Reach out to me in a modern browser (as in: not in windows95) on <font color="#0000FF">felixrieseberg.com</font> or find me on Bluesky at <font color="#0000FF">@felixrieseberg</font>.</p>
<hr width="75%">
<a name="internet"></a>
<font size="5" color="#000000"><img src="images/ie.gif" width="16" height="16" border="0" align="absmiddle"> <b>The Internet!</b></font>
<hr width="75%">
<p>In a major update since the last version, windows95 now has working Internet! That said, most modern websites will not work, so brace yourself. I recommend using <a href="http://theoldnet.com/" target="_blank">The Old Net</a> to travel back in time.</p>
</font>
<center>
<font size="2" color="#000000">Last updated: 2025</font>
</center>
</td>
</tr>
</table>
</body>
</html>

21
static/www/index.htm Normal file
View File

@@ -0,0 +1,21 @@
<html>
<head>
<title>Welcome to Windows 95!</title>
</head>
<frameset cols="200,*" border="0" framespacing="0" frameborder="NO">
<frame src="navigation.htm" name="nav" scrolling="auto" noresize>
<frame src="home.htm" name="main" scrolling="auto" noresize>
<noframes>
<body bgcolor="#000080">
<font face="Arial" color="#FFFFFF">
<h2>Frame Alert!</h2>
<p>This page uses frames, but your browser doesn't support them.</p>
<p>Please upgrade to Netscape Navigator 2.0 or Internet Explorer 3.0!</p>
</font>
</body>
</noframes>
</frameset>
</html>

46
static/www/navigation.htm Normal file
View File

@@ -0,0 +1,46 @@
<html>
<head>
<title>Navigation</title>
</head>
<body bgcolor="#C0C0C0" background="images/bg.gif">
<table width="100%" cellpadding="4" cellspacing="1" bgcolor="#000000">
<tr><td bgcolor="#C0C0C0">
<font face="Arial" size="2" color="#000000">
<img src="images/folder.gif" width="16" height="16" border="0" align="absmiddle"> <b>Navigation</b>
</font>
</td></tr>
<tr><td bgcolor="#C0C0C0">
<font face="Arial" size="2" color="#000000">
<img src="images/desktop.gif" width="16" height="16" border="0" align="absmiddle"> <a href="home.htm" target="main">Home</a>
</font>
</td></tr>
<tr><td bgcolor="#C0C0C0">
<font face="Arial" size="2" color="#000000">
<img src="images/programs.gif" width="16" height="16" border="0" align="absmiddle"> <a href="apps.htm" target="main">Apps & Games</a>
</font>
</td></tr>
<tr><td bgcolor="#C0C0C0">
<font face="Arial" size="2" color="#000000">
<img src="images/help.gif" width="16" height="16" border="0" align="absmiddle"> <a href="help.htm" target="main">Help</a>
</font>
</td></tr>
<tr><td bgcolor="#C0C0C0">
<font face="Arial" size="2" color="#000000">
<img src="images/doc.gif" width="16" height="16" border="0" align="absmiddle"> <a href="credits.htm" target="main">Credits</a>
</font>
</td></tr>
</table>
<br>
<center>
<p>
<font face="Arial" size="1" color="#000000">
Best viewed with<br>
Internet Explorer 5.5 and windows95
</font>
</p>
<img src="buttons/madewithelectron.gif" width="88" height="31" border="0" align="absmiddle">
<img src="buttons/macos.gif" width="88" height="31" border="0" align="absmiddle">
<img src="buttons/msie.gif" width="88" height="31" border="0" align="absmiddle">
</center>
</body>
</html>

View File

@@ -18,6 +18,7 @@ async function copyLib() {
let patchedLibv86 = libv86.replace('k.load_file="undefined"===typeof XMLHttpRequest?pa:qa', 'k.load_file=pa') let patchedLibv86 = libv86.replace('k.load_file="undefined"===typeof XMLHttpRequest?pa:qa', 'k.load_file=pa')
patchedLibv86 = patchedLibv86.replace('H.exportSymbol=function(a,b){"undefined"!==typeof module&&"undefined"!==typeof module.exports?module.exports[a]=b:"undefined"!==typeof window?window[a]=b:"function"===typeof importScripts&&(self[a]=b)}', 'H.exportSymbol=function(a,b){"undefined"!==typeof window?window[a]=b:"undefined"!==typeof module&&"undefined"!==typeof module.exports?module.exports[a]=b:"function"===typeof importScripts&&(self[a]=b)}') patchedLibv86 = patchedLibv86.replace('H.exportSymbol=function(a,b){"undefined"!==typeof module&&"undefined"!==typeof module.exports?module.exports[a]=b:"undefined"!==typeof window?window[a]=b:"function"===typeof importScripts&&(self[a]=b)}', 'H.exportSymbol=function(a,b){"undefined"!==typeof window?window[a]=b:"undefined"!==typeof module&&"undefined"!==typeof module.exports?module.exports[a]=b:"function"===typeof importScripts&&(self[a]=b)}')
patchedLibv86 = patchedLibv86.replace('this.fetch=fetch;', 'this.fetch=(...args)=>fetch(...args);')
fs.writeFileSync(libv86path, patchedLibv86) fs.writeFileSync(libv86path, patchedLibv86)

View File

@@ -8,12 +8,11 @@
"preserveConstEnums": true, "preserveConstEnums": true,
"sourceMap": true, "sourceMap": true,
"lib": [ "lib": [
"es2017", "es2021",
"dom" "dom"
], ],
"noImplicitAny": true, "noImplicitAny": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"suppressImplicitAnyIndexErrors": true,
"strictNullChecks": true, "strictNullChecks": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noImplicitThis": true, "noImplicitThis": true,