mirror of
https://github.com/felixrieseberg/windows95.git
synced 2026-05-14 18:31:59 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62f8eb2696 | ||
|
|
da4b0dd728 | ||
|
|
6cc05fa042 | ||
|
|
dda3707a23 | ||
|
|
a4bcd7fb61 | ||
|
|
17a8139346 | ||
|
|
489c7312d0 | ||
|
|
c3537ae330 | ||
|
|
c483871df9 | ||
|
|
e66cbd70db | ||
|
|
19a1bbc002 | ||
|
|
ef57e3a7fe | ||
|
|
7eae250c2a | ||
|
|
33db389d59 | ||
|
|
61f3269a45 | ||
|
|
e5d897c663 | ||
|
|
a7ae665adc | ||
|
|
bea2267f42 | ||
|
|
a55d08fafc | ||
|
|
97702cb01b | ||
|
|
12160a1ac4 | ||
|
|
3dd50db272 | ||
|
|
7b92d33584 | ||
|
|
24a1c30502 | ||
|
|
7ce0863ae8 | ||
|
|
90ec67fb16 | ||
|
|
9cab8e46f6 | ||
|
|
03b39d76b5 | ||
|
|
d8b4a139ac | ||
|
|
9f4771bf26 | ||
|
|
552b97eec5 | ||
|
|
6c0f00170c | ||
|
|
e3b9a839f5 | ||
|
|
238b07b7dd | ||
|
|
9dc1e422ff | ||
|
|
ebe7427385 | ||
|
|
3e3bee2062 | ||
|
|
c93b6878a9 | ||
|
|
d2e26ef5d1 | ||
|
|
c41befae64 | ||
|
|
8b720750db | ||
|
|
ee317ec5aa | ||
|
|
d7c657e671 | ||
|
|
7a8a54c76b | ||
|
|
c29f98b6bc | ||
|
|
8d1847a8d1 | ||
|
|
194f4fabaf | ||
|
|
3f4a5e97fa | ||
|
|
3eb789d055 | ||
|
|
8a8f064864 |
BIN
.github/images/linux.png
vendored
Normal file
BIN
.github/images/linux.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
BIN
.github/images/macos.png
vendored
Normal file
BIN
.github/images/macos.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
.github/images/windows.png
vendored
Normal file
BIN
.github/images/windows.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 18.x
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -28,22 +28,38 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install
|
||||
run: yarn
|
||||
run: yarn --frozen-lockfile
|
||||
- name: lint
|
||||
run: yarn lint
|
||||
build:
|
||||
needs: lint
|
||||
name: Build (${{ matrix.os }})
|
||||
name: Build (${{ matrix.os }} - ${{ matrix.arch }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# Build for supported platforms
|
||||
# https://github.com/electron/electron-packager/blob/ebcbd439ff3e0f6f92fa880ff28a8670a9bcf2ab/src/targets.js#L9
|
||||
# 32-bit Linux unsupported as of 2019: https://www.electronjs.org/blog/linux-32bit-support
|
||||
os: [ macOS-latest, ubuntu-latest, windows-latest ]
|
||||
arch: [ x64, arm64 ]
|
||||
include:
|
||||
- os: windows-latest
|
||||
arch: ia32
|
||||
- os: ubuntu-latest
|
||||
arch: armv7l
|
||||
# Publishing artifacts for multiple Windows architectures has
|
||||
# a bug which can cause the wrong architecture to be downloaded
|
||||
# for an update, so until that is fixed, only build Windows x64
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 18.x
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -83,23 +99,13 @@ jobs:
|
||||
run: yarn
|
||||
- name: Make
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: yarn make
|
||||
run: yarn make --arch=${{ matrix.arch }}
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WINDOWS_CODESIGN_FILE: ${{ steps.write_file.outputs.filePath }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
- name: Make (ia32)
|
||||
if: matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/')
|
||||
run: yarn make -- --arch=ia32
|
||||
env:
|
||||
WINDOWS_CODESIGN_FILE: ${{ steps.write_file.outputs.filePath }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
# - name: Archive production artifacts
|
||||
# uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: ${{ matrix.os }}
|
||||
# path: out/make/**/*
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,4 +3,8 @@ out
|
||||
src/images
|
||||
.DS_Store
|
||||
images
|
||||
dist
|
||||
images_new
|
||||
dist
|
||||
!.github/images
|
||||
*.code-workspace
|
||||
*.pfx
|
||||
97
README.md
97
README.md
@@ -3,10 +3,95 @@
|
||||
This is Windows 95, running in an [Electron](https://electronjs.org/) app. Yes, it's the full thing. I'm sorry.
|
||||
|
||||
## Downloads
|
||||
| | Windows | macOS | Linux |
|
||||
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Standalone Download | 📦[Standalone, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-2.2.1-win32-standalone-ia32.zip) <br /> 📦[Standalone, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-2.2.1-win32-standalone-x64.zip) | 📦[Standalone](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-macos-2.2.1.zip) | |
|
||||
| Installer | 💽[Setup, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-2.2.1-setup-win32-x64.exe) <br /> 💽[Setup, 32-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-2.2.1-setup-win32-ia32.exe) | | 💽[deb, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-linux-2.2.1_amd64.deb) <br /> 💽[rpm, 64-bit](https://github.com/felixrieseberg/windows95/releases/download/v2.2.1/windows95-linux-2.2.1.x86_64.rpm) |
|
||||
|
||||
<table class="is-fullwidth">
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./.github/images/windows.png" width="24"><br />
|
||||
Windows
|
||||
</td>
|
||||
<td>
|
||||
<span>32-bit</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-3.1.1-setup-ia32.exe">
|
||||
💿 Installer
|
||||
</a> |
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-win32-ia32-3.1.1.zip">
|
||||
📦 Standalone Zip
|
||||
</a>
|
||||
<br />
|
||||
<span>64-bit</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-3.1.1-setup-x64.exe">
|
||||
💿 Installer
|
||||
</a> |
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-win32-x64-3.1.1.zip">
|
||||
📦 Standalone Zip
|
||||
</a><br />
|
||||
<span>ARM64</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-3.1.1-setup-arm64.exe">
|
||||
💿 Installer
|
||||
</a> |
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-win32-arm64-3.1.1.zip">
|
||||
📦 Standalone Zip
|
||||
</a><br />
|
||||
<span>
|
||||
❓ Don't know what kind of chip you have? Hit start, enter "processor" for info.
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./.github/images/macos.png" width="24"><br />
|
||||
macOS
|
||||
</td>
|
||||
<td>
|
||||
<span>Intel Processor</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-darwin-x64-3.1.1.zip">
|
||||
📦 Standalone Zip
|
||||
</a><br />
|
||||
<span>Apple M1 Processor</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-darwin-arm64-3.1.1.zip">
|
||||
📦 Standalone Zip
|
||||
</a><br />
|
||||
<span>
|
||||
❓ Don't know what kind of chip you have? Learn more at <a href="https://support.apple.com/en-us/HT211814">apple.com</a>.
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="./.github/images/linux.png" width="24"><br />
|
||||
Linux
|
||||
</td>
|
||||
<td>
|
||||
<span>64-bit</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-3.1.1-1.x86_64.rpm">
|
||||
💿 rpm
|
||||
</a> |
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95_3.1.1_amd64.deb">
|
||||
💿 deb
|
||||
</a><br />
|
||||
<span>ARM64</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-3.1.1-1.arm64.rpm">
|
||||
💿 rpm
|
||||
</a> |
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95_3.1.1_arm64.deb">
|
||||
💿 deb
|
||||
</a><br />
|
||||
<span>ARMv7 (armhf)</span>
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95-3.1.1-1.armv7hl.rpm">
|
||||
💿 rpm
|
||||
</a> |
|
||||
<a href="https://github.com/felixrieseberg/windows95/releases/download/v3.1.1/windows95_3.1.1_armhf.deb">
|
||||
💿 deb
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
|
||||

|
||||
|
||||
@@ -23,7 +108,7 @@ You'll likely be better off with an actual virtualization app, but the short ans
|
||||
|
||||
## Credits
|
||||
|
||||
99% of the work was done over at [v86](https://github.com/copy/v86/) by Copy.
|
||||
99% of the work was done over at [v86](https://github.com/copy/v86/) by Copy aka Fabian Hemmer and his contributors.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -44,6 +129,8 @@ Unpack the `images` folder into the `src` folder, creating this layout:
|
||||
|
||||
Once you've done so, run `npm install` and `npm start` to run your local build.
|
||||
|
||||
If you want to tinker with the image or make a new one, check out the [QEMU docs](./docs/qemu.md).
|
||||
|
||||
## Other Questions
|
||||
|
||||
* [MS-DOS seems to brick the screen](./HELP.md#ms-dos-seems-to-brick-the-screen)
|
||||
|
||||
BIN
assets/boot.gif
Normal file
BIN
assets/boot.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
Binary file not shown.
Binary file not shown.
BIN
bios/seabios.bin
BIN
bios/seabios.bin
Binary file not shown.
BIN
bios/vgabios.bin
BIN
bios/vgabios.bin
Binary file not shown.
@@ -35,5 +35,5 @@ xhost +
|
||||
```
|
||||
4. run
|
||||
```
|
||||
docker run -it -e DISPLAY=host.docker.internal:1 toolboc/windows95
|
||||
docker run -it -e DISPLAY=host.docker.internal:0 toolboc/windows95
|
||||
```
|
||||
|
||||
28
docs/qemu.md
Normal file
28
docs/qemu.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# QEMU Instructions
|
||||
|
||||
The image built here was made with QEMU. In this doc, I'm keeping instructions
|
||||
around.
|
||||
|
||||
Disk image creation
|
||||
|
||||
```sh
|
||||
qemu-img create -f qcow2 win95.qcow2 1G
|
||||
```
|
||||
|
||||
Installation
|
||||
```sh
|
||||
qemu-system-i386 -netdev user,id=mynet0 -device ne2k_isa,netdev=mynet0 -hda win95.qcow2 -soundhw sb16 -m 128 -cpu pentium -device cirrus-vga,vgamem_mb=64 -fda boot_floppy.img -cdrom Win95_OSR25.iso -boot a -soundhw pcspk
|
||||
```
|
||||
|
||||
Running
|
||||
|
||||
With `ne2k_isa`
|
||||
```sh
|
||||
qemu-system-i386 -netdev user,id=mynet0 -device ne2k_isa,netdev=mynet0 -drive file=win95.img,format=raw,index=0,media=disk -soundhw sb16 -m 128 -cpu pentium -device cirrus-vga,vgamem_mb=16 -soundhw pcspk -cdrom Win95_OSR25.iso
|
||||
```
|
||||
|
||||
With `ne2k_pci`
|
||||
```sh
|
||||
qemu-system-i386 -net nic,model=ne2k_pci -net user -drive file=win95_ne2k_pci.img,format=raw,index=0,media=disk -soundhw sb16 -m 128 -cpu pentium -device cirrus-vga,vgamem_mb=16 -soundhw pcspk -cdrom Win95_OSR25.iso --enable-kvm
|
||||
```
|
||||
|
||||
@@ -62,6 +62,8 @@ module.exports = {
|
||||
exe: 'windows95.exe',
|
||||
noMsi: true,
|
||||
remoteReleases: '',
|
||||
iconUrl: 'https://raw.githubusercontent.com/felixrieseberg/windows95/master/assets/icon.ico',
|
||||
loadingGif: './assets/boot.gif',
|
||||
setupExe: `windows95-${package.version}-setup-${arch}.exe`,
|
||||
setupIcon: path.resolve(__dirname, 'assets', 'icon.ico'),
|
||||
certificateFile: process.env['WINDOWS_CODESIGN_FILE'],
|
||||
|
||||
57
package.json
57
package.json
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "windows95",
|
||||
"productName": "windows95",
|
||||
"version": "2.2.2",
|
||||
"version": "3.1.2",
|
||||
"description": "Windows 95, in an app. I'm sorry.",
|
||||
"main": "./dist/src/main/main",
|
||||
"main": "./dist/src/main/main.js",
|
||||
"scripts": {
|
||||
"start": "rimraf ./dist && electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"publish": "electron-forge publish",
|
||||
"lint": "prettier --write src/**/*.{ts,tsx}",
|
||||
"less": "node ./tools/lessc.js"
|
||||
"lint": "prettier --write src/**/*.{ts,tsx} && npm run check-links",
|
||||
"less": "node ./tools/lessc.js",
|
||||
"tsc": "tsc -p tsconfig.json --noEmit",
|
||||
"check-links": "node tools/check-links.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Felix Rieseberg, felix@felixrieseberg.com",
|
||||
@@ -20,31 +22,30 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"tslib": "^2.0.0",
|
||||
"update-electron-app": "^1.5.0"
|
||||
"fs-extra": "^10.1.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"tslib": "^2.4.0",
|
||||
"update-electron-app": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0-beta.52",
|
||||
"@electron-forge/maker-deb": "^6.0.0-beta.52",
|
||||
"@electron-forge/maker-flatpak": "^6.0.0-beta.52",
|
||||
"@electron-forge/maker-rpm": "^6.0.0-beta.52",
|
||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.52",
|
||||
"@electron-forge/maker-zip": "^6.0.0-beta.52",
|
||||
"@electron-forge/publisher-github": "^6.0.0-beta.52",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/node": "^12.12.14",
|
||||
"@types/react": "^16.9.44",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"electron": "9.1.2",
|
||||
"less": "^3.12.2",
|
||||
"node-abi": "^2.18.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^2.0.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"standard": "^14.3.4",
|
||||
"typescript": "^3.9.7"
|
||||
"@electron-forge/cli": "6.2.1",
|
||||
"@electron-forge/maker-deb": "6.2.1",
|
||||
"@electron-forge/maker-flatpak": "^6.2.1",
|
||||
"@electron-forge/maker-rpm": "^6.2.1",
|
||||
"@electron-forge/maker-squirrel": "^6.2.1",
|
||||
"@electron-forge/maker-zip": "^6.2.1",
|
||||
"@electron-forge/publisher-github": "^6.2.1",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/node": "^12.19.9",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"electron": "25.3.0",
|
||||
"less": "^3.13.0",
|
||||
"node-abi": "^3.45.0",
|
||||
"parcel-bundler": "^1.12.5",
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
37
src/cache.ts
37
src/cache.ts
@@ -11,27 +11,22 @@ export async function clearCache() {
|
||||
}
|
||||
}
|
||||
|
||||
export function clearStorageData() {
|
||||
return new Promise((resolve) => {
|
||||
if (!session.defaultSession) {
|
||||
return resolve();
|
||||
}
|
||||
export async function clearStorageData() {
|
||||
if (!session.defaultSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
session.defaultSession.clearStorageData(
|
||||
{
|
||||
storages: [
|
||||
"appcache",
|
||||
"cookies",
|
||||
"filesystem",
|
||||
"indexdb",
|
||||
"localstorage",
|
||||
"shadercache",
|
||||
"websql",
|
||||
"serviceworkers",
|
||||
],
|
||||
quotas: ["temporary", "persistent", "syncable"],
|
||||
},
|
||||
resolve
|
||||
);
|
||||
await session.defaultSession.clearStorageData({
|
||||
storages: [
|
||||
"appcache",
|
||||
"cookies",
|
||||
"filesystem",
|
||||
"indexdb",
|
||||
"localstorage",
|
||||
"shadercache",
|
||||
"websql",
|
||||
"serviceworkers",
|
||||
],
|
||||
quotas: ["temporary", "persistent", "syncable"],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { remote, app } from "electron";
|
||||
import * as path from "path";
|
||||
|
||||
const _app = app || remote.app;
|
||||
|
||||
export const CONSTANTS = {
|
||||
IMAGE_PATH: path.join(__dirname, "../../images/windows95.img"),
|
||||
IMAGE_DEFAULT_SIZE: 1073741824, // 1GB
|
||||
DEFAULT_STATE_PATH: path.join(__dirname, "../../images/default-state.bin"),
|
||||
STATE_PATH: path.join(_app.getPath("userData"), "state-v2.bin"),
|
||||
};
|
||||
|
||||
export const IPC_COMMANDS = {
|
||||
@@ -28,4 +24,7 @@ export const IPC_COMMANDS = {
|
||||
// Machine events
|
||||
MACHINE_STARTED: "MACHINE_STARTED",
|
||||
MACHINE_STOPPED: "MACHINE_STOPPED",
|
||||
// Else
|
||||
APP_QUIT: "APP_QUIT",
|
||||
GET_STATE_PATH: "GET_STATE_PATH",
|
||||
};
|
||||
|
||||
14
src/main/ipc.ts
Normal file
14
src/main/ipc.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ipcMain, app } from "electron";
|
||||
import * as path from "path";
|
||||
|
||||
import { IPC_COMMANDS } from "../constants";
|
||||
|
||||
export function setupIpcListeners() {
|
||||
ipcMain.handle(IPC_COMMANDS.GET_STATE_PATH, () => {
|
||||
return path.join(app.getPath("userData"), "state-v3.bin");
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_COMMANDS.APP_QUIT, () => {
|
||||
app.quit();
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { shouldQuit } from "./squirrel";
|
||||
import { setupUpdates } from "./update";
|
||||
import { getOrCreateWindow } from "./windows";
|
||||
import { setupMenu } from "./menu";
|
||||
import { setupIpcListeners } from "./ipc";
|
||||
|
||||
/**
|
||||
* Handle the app's "ready" event. This is essentially
|
||||
@@ -14,6 +15,7 @@ import { setupMenu } from "./menu";
|
||||
export async function onReady() {
|
||||
if (!isDevMode()) process.env.NODE_ENV = "production";
|
||||
|
||||
setupIpcListeners();
|
||||
getOrCreateWindow();
|
||||
setupAboutPanel();
|
||||
setupMenu();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { BrowserWindow, shell } from "electron";
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
@@ -14,14 +14,30 @@ export function getOrCreateWindow(): BrowserWindow {
|
||||
nodeIntegration: true,
|
||||
sandbox: false,
|
||||
webviewTag: false,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
// mainWindow.webContents.toggleDevTools();
|
||||
mainWindow.loadFile("./dist/static/index.html");
|
||||
|
||||
mainWindow.webContents.on("will-navigate", (event, url) =>
|
||||
handleNavigation(event, url)
|
||||
);
|
||||
mainWindow.webContents.on("new-window", (event, url) =>
|
||||
handleNavigation(event, url)
|
||||
);
|
||||
|
||||
mainWindow.on("closed", () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
function handleNavigation(event: Electron.Event, url: string) {
|
||||
if (url.startsWith("http")) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,14 @@ export class CardDrive extends React.Component<CardDriveProps, CardDriveState> {
|
||||
<p>
|
||||
Windows 10 cannot mount raw disk images (ironically, macOS and Linux
|
||||
can). However, tools exist that let you mount this drive, like the
|
||||
freeware tool <a href="https://google.com">OSFMount</a>. I am not
|
||||
affiliated with it, so please use it at your own risk.
|
||||
freeware tool{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://www.osforensics.com/tools/mount-disk-images.html"
|
||||
>
|
||||
OSFMount
|
||||
</a>
|
||||
. I am not affiliated with it, so please use it at your own risk.
|
||||
</p>
|
||||
{this.renderMountButton("Windows Explorer")}
|
||||
</fieldset>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import * as React from "react";
|
||||
import * as fs from "fs-extra";
|
||||
|
||||
import { CONSTANTS } from "../constants";
|
||||
import { getStatePath } from "./utils/get-state-path";
|
||||
|
||||
interface CardSettingsProps {
|
||||
bootFromScratch: () => void;
|
||||
setFloppy: (file: File) => void;
|
||||
setCdrom: (cdrom: File) => void;
|
||||
floppy?: File;
|
||||
cdrom?: File;
|
||||
}
|
||||
|
||||
interface CardSettingsState {
|
||||
@@ -21,6 +23,7 @@ export class CardSettings extends React.Component<
|
||||
super(props);
|
||||
|
||||
this.onChangeFloppy = this.onChangeFloppy.bind(this);
|
||||
this.onChangeCdrom = this.onChangeCdrom.bind(this);
|
||||
this.onResetState = this.onResetState.bind(this);
|
||||
|
||||
this.state = {
|
||||
@@ -39,6 +42,8 @@ export class CardSettings extends React.Component<
|
||||
</h2>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{this.renderCdrom()}
|
||||
<hr />
|
||||
{this.renderFloppy()}
|
||||
<hr />
|
||||
{this.renderState()}
|
||||
@@ -48,6 +53,44 @@ export class CardSettings extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
public renderCdrom() {
|
||||
// CD is currently not working, so.. let's return nothing.
|
||||
return null;
|
||||
|
||||
const { cdrom } = this.props;
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>
|
||||
<img src="../../static/cdrom.png" />
|
||||
CD-ROM
|
||||
</legend>
|
||||
<input
|
||||
id="cdrom-input"
|
||||
type="file"
|
||||
onChange={this.onChangeCdrom}
|
||||
style={{ display: "none" }}
|
||||
/>
|
||||
<p>
|
||||
windows95 comes with a virtual CD drive. It can mount images in the
|
||||
"iso" format.
|
||||
</p>
|
||||
<p id="floppy-path">
|
||||
{cdrom ? `Inserted CD: ${cdrom?.path}` : `No CD mounted`}
|
||||
</p>
|
||||
<button
|
||||
className="btn"
|
||||
onClick={() =>
|
||||
(document.querySelector("#cdrom-input") as any).click()
|
||||
}
|
||||
>
|
||||
<img src="../../static/select-cdrom.png" />
|
||||
<span>Mount CD</span>
|
||||
</button>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
public renderFloppy() {
|
||||
const { floppy } = this.props;
|
||||
|
||||
@@ -148,12 +191,32 @@ export class CardSettings extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change in the cdrom input
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
private onChangeCdrom(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const CdromFile =
|
||||
event.target.files && event.target.files.length > 0
|
||||
? event.target.files[0]
|
||||
: null;
|
||||
|
||||
if (CdromFile) {
|
||||
this.props.setCdrom(CdromFile);
|
||||
} else {
|
||||
console.log(`Cdrom: Input changed but no file selected`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the state reset
|
||||
*/
|
||||
private async onResetState() {
|
||||
if (fs.existsSync(CONSTANTS.STATE_PATH)) {
|
||||
await fs.remove(CONSTANTS.STATE_PATH);
|
||||
const statePath = await getStatePath();
|
||||
|
||||
if (fs.existsSync(statePath)) {
|
||||
await fs.remove(statePath);
|
||||
}
|
||||
|
||||
this.setState({ isStateReset: true });
|
||||
|
||||
@@ -85,7 +85,7 @@ export class EmulatorInfo extends React.Component<
|
||||
}
|
||||
|
||||
// TypeScript think's we're using a Node.js setInterval. We're not.
|
||||
this.cpuInterval = (setInterval(this.cpuCount, 500) as unknown) as number;
|
||||
this.cpuInterval = setInterval(this.cpuCount, 500) as unknown as number;
|
||||
|
||||
// Disk
|
||||
emulator.add_listener("ide-read-start", this.onIDEReadStart);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from "path";
|
||||
import { ipcRenderer, remote, shell } from "electron";
|
||||
import { ipcRenderer, shell } from "electron";
|
||||
|
||||
import { CONSTANTS, IPC_COMMANDS } from "../constants";
|
||||
import { getDiskImageSize } from "../utils/disk-image-size";
|
||||
@@ -10,12 +10,14 @@ import { StartMenu } from "./start-menu";
|
||||
import { CardSettings } from "./card-settings";
|
||||
import { EmulatorInfo } from "./emulator-info";
|
||||
import { CardDrive } from "./card-drive";
|
||||
import { getStatePath } from "./utils/get-state-path";
|
||||
|
||||
export interface EmulatorState {
|
||||
currentUiCard: string;
|
||||
emulator?: any;
|
||||
scale: number;
|
||||
floppyFile?: File;
|
||||
cdromFile?: File;
|
||||
isBootingFresh: boolean;
|
||||
isCursorCaptured: boolean;
|
||||
isInfoDisplayed: boolean;
|
||||
@@ -41,7 +43,9 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
isRunning: false,
|
||||
currentUiCard: "start",
|
||||
isInfoDisplayed: true,
|
||||
scale: 1,
|
||||
// We can start pretty large
|
||||
// If it's too large, it'll just grow until it hits borders
|
||||
scale: 2,
|
||||
};
|
||||
|
||||
this.setupInputListeners();
|
||||
@@ -95,11 +99,11 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
this.isQuitting = true;
|
||||
|
||||
setImmediate(() => {
|
||||
remote.app.quit();
|
||||
ipcRenderer.invoke(IPC_COMMANDS.APP_QUIT);
|
||||
});
|
||||
};
|
||||
|
||||
window.onbeforeunload = (event) => {
|
||||
window.onbeforeunload = (event: Event) => {
|
||||
if (this.isQuitting || this.isResetting) {
|
||||
console.log(`Unload: Not preventing`);
|
||||
return;
|
||||
@@ -178,7 +182,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
* 🤡
|
||||
*/
|
||||
public renderUI() {
|
||||
const { isRunning, currentUiCard, floppyFile } = this.state;
|
||||
const { isRunning, currentUiCard, floppyFile, cdromFile } = this.state;
|
||||
|
||||
if (isRunning) {
|
||||
return null;
|
||||
@@ -190,8 +194,10 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
card = (
|
||||
<CardSettings
|
||||
setFloppy={(floppyFile) => this.setState({ floppyFile })}
|
||||
setCdrom={(cdromFile) => this.setState({ cdromFile })}
|
||||
bootFromScratch={this.bootFromScratch}
|
||||
floppy={floppyFile}
|
||||
cdrom={cdromFile}
|
||||
/>
|
||||
);
|
||||
} else if (currentUiCard === "drive") {
|
||||
@@ -260,7 +266,6 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
const imagePath = path.join(__dirname, "../../images/windows95.img");
|
||||
|
||||
console.log(`Showing disk image in ${imagePath}`);
|
||||
``;
|
||||
|
||||
shell.showItemInFolder(imagePath);
|
||||
}
|
||||
@@ -271,26 +276,37 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
private async startEmulator() {
|
||||
document.body.classList.remove("paused");
|
||||
|
||||
const imageSize = await getDiskImageSize();
|
||||
const cdrom: any = {};
|
||||
const cdromFile: any = this.state.cdromFile;
|
||||
if (cdromFile?.path) {
|
||||
cdrom.url = cdromFile.path;
|
||||
cdrom.async = true;
|
||||
cdrom.size = await getDiskImageSize(cdromFile.path);
|
||||
}
|
||||
|
||||
const options = {
|
||||
wasm_path: path.join(__dirname, "build/v86.wasm"),
|
||||
memory_size: 128 * 1024 * 1024,
|
||||
video_memory_size: 32 * 1024 * 1024,
|
||||
vga_memory_size: 32 * 1024 * 1024,
|
||||
screen_container: document.getElementById("emulator"),
|
||||
bios: {
|
||||
url: "../../bios/seabios.bin",
|
||||
url: path.join(__dirname, "../../bios/seabios.bin"),
|
||||
},
|
||||
vga_bios: {
|
||||
url: "../../bios/vgabios.bin",
|
||||
url: path.join(__dirname, "../../bios/vgabios.bin"),
|
||||
},
|
||||
hda: {
|
||||
url: "../../images/windows95.img",
|
||||
url: CONSTANTS.IMAGE_PATH,
|
||||
async: true,
|
||||
size: imageSize,
|
||||
size: await getDiskImageSize(CONSTANTS.IMAGE_PATH),
|
||||
},
|
||||
fda: {
|
||||
buffer: this.state.floppyFile,
|
||||
},
|
||||
cdrom: cdrom,
|
||||
boot_order: 0x132,
|
||||
// One day, maybe!
|
||||
// network_relay_url: "ws://localhost:8080/"
|
||||
};
|
||||
|
||||
console.log(`🚜 Starting emulator with options`, options);
|
||||
@@ -315,6 +331,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
|
||||
this.lockMouse();
|
||||
this.state.emulator.run();
|
||||
this.state.emulator.screen_set_scale(this.state.scale);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@@ -344,7 +361,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
|
||||
await this.saveState();
|
||||
this.unlockMouse();
|
||||
emulator.stop();
|
||||
await emulator.stop();
|
||||
this.setState({ isRunning: false });
|
||||
|
||||
document.body.classList.add("paused");
|
||||
@@ -366,34 +383,27 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
*/
|
||||
private async saveState(): Promise<void> {
|
||||
const { emulator } = this.state;
|
||||
const statePath = await getStatePath();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (!emulator || !emulator.save_state) {
|
||||
console.log(`restoreState: No emulator present`);
|
||||
return resolve();
|
||||
}
|
||||
if (!emulator || !emulator.save_state) {
|
||||
console.log(`restoreState: No emulator present`);
|
||||
return;
|
||||
}
|
||||
|
||||
emulator.save_state(async (error: Error, newState: ArrayBuffer) => {
|
||||
if (error) {
|
||||
console.warn(`saveState: Could not save state`, error);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
await fs.outputFile(CONSTANTS.STATE_PATH, Buffer.from(newState));
|
||||
|
||||
console.log(`saveState: Saved state to ${CONSTANTS.STATE_PATH}`);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
try {
|
||||
const newState = await emulator.save_state();
|
||||
await fs.outputFile(statePath, Buffer.from(newState));
|
||||
} catch (error) {
|
||||
console.warn(`saveState: Could not save state`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores state to the emulator.
|
||||
*/
|
||||
private restoreState() {
|
||||
private async restoreState() {
|
||||
const { emulator } = this.state;
|
||||
const state = this.getState();
|
||||
const state = await this.getState();
|
||||
|
||||
// Nothing to do with if we don't have a state
|
||||
if (!state) {
|
||||
@@ -405,7 +415,7 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
}
|
||||
|
||||
try {
|
||||
this.state.emulator.restore_state(state);
|
||||
await this.state.emulator.restore_state(state);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`State: Could not read state file. Maybe none exists?`,
|
||||
@@ -420,9 +430,10 @@ export class Emulator extends React.Component<{}, EmulatorState> {
|
||||
*
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
private getState(): ArrayBuffer | null {
|
||||
const statePath = fs.existsSync(CONSTANTS.STATE_PATH)
|
||||
? CONSTANTS.STATE_PATH
|
||||
private async getState(): Promise<ArrayBuffer | null> {
|
||||
const expectedStatePath = await getStatePath();
|
||||
const statePath = fs.existsSync(expectedStatePath)
|
||||
? expectedStatePath
|
||||
: CONSTANTS.DEFAULT_STATE_PATH;
|
||||
|
||||
if (fs.existsSync(statePath)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012-2018, Fabian Hemmer
|
||||
Copyright (c) 2012, The v86 contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -19,8 +19,4 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
BIN
src/renderer/lib/build/v86.wasm
Executable file
BIN
src/renderer/lib/build/v86.wasm
Executable file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
13
src/renderer/utils/get-state-path.ts
Normal file
13
src/renderer/utils/get-state-path.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IPC_COMMANDS } from "../../constants";
|
||||
|
||||
let _statePath = "";
|
||||
|
||||
export async function getStatePath(): Promise<string> {
|
||||
if (_statePath) {
|
||||
return _statePath;
|
||||
}
|
||||
|
||||
const statePath = await ipcRenderer.invoke(IPC_COMMANDS.GET_STATE_PATH);
|
||||
return (_statePath = statePath);
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import { CONSTANTS } from "../constants";
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
export async function getDiskImageSize() {
|
||||
export async function getDiskImageSize(path: string) {
|
||||
try {
|
||||
const stats = await fs.stat(CONSTANTS.IMAGE_PATH);
|
||||
const stats = await fs.stat(path);
|
||||
|
||||
if (stats) {
|
||||
return stats.size;
|
||||
|
||||
BIN
static/cdrom.png
Normal file
BIN
static/cdrom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 672 B |
@@ -7,7 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../src/less/vendor/95css.css">
|
||||
<link rel="stylesheet" href="../src/less/root.less">
|
||||
<script src="../src/renderer/lib/libv86.js"></script>
|
||||
<!-- libv86 -->
|
||||
</head>
|
||||
<body class="paused windows95">
|
||||
<div id="app"></div>
|
||||
|
||||
BIN
static/select-cdrom.png
Normal file
BIN
static/select-cdrom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 B |
38
tools/check-links.js
Normal file
38
tools/check-links.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const fs = require('fs/promises')
|
||||
const path = require('path')
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
const LINK_RGX = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/g;
|
||||
|
||||
async function main() {
|
||||
const readmePath = path.join(__dirname, '../README.md')
|
||||
const readme = await fs.readFile(readmePath, 'utf-8')
|
||||
const links = readme.match(LINK_RGX)
|
||||
let failed = false
|
||||
|
||||
for (const link of links) {
|
||||
try {
|
||||
const response = await fetch(link, { method: 'HEAD' })
|
||||
|
||||
if (!response.ok) {
|
||||
// If we're inside GitHub's release asset server, we just ran into AWS not allowing
|
||||
// HEAD requests, which is different from a 404.
|
||||
if (!response.url.startsWith('https://github-production-release-asset')) {
|
||||
throw new Error (`HTTP Error Response: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ ${link}`);
|
||||
} catch (error) {
|
||||
failed = true
|
||||
|
||||
console.log(`❌ ${link}\n${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -2,6 +2,27 @@
|
||||
|
||||
const Bundler = require('parcel-bundler')
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
|
||||
async function copyLib() {
|
||||
const target = path.join(__dirname, '../dist/static')
|
||||
const lib = path.join(__dirname, '../src/renderer/lib')
|
||||
const index = path.join(target, 'index.html')
|
||||
|
||||
// Copy in lib
|
||||
await fs.copy(lib, target)
|
||||
|
||||
// Patch so that fs.read is used
|
||||
const libv86path = path.join(target, 'libv86.js')
|
||||
const libv86 = fs.readFileSync(libv86path, 'utf-8')
|
||||
const patchedLibv86 = libv86.replace('v86util.load_file="undefined"===typeof XMLHttpRequest', 'v86util.load_file="undefined"!==typeof XMLHttpRequest')
|
||||
fs.writeFileSync(libv86path, patchedLibv86)
|
||||
|
||||
// Overwrite
|
||||
const indexContents = fs.readFileSync(index, 'utf-8');
|
||||
const replacedContents = indexContents.replace('<!-- libv86 -->', '<script src="libv86.js"></script>')
|
||||
fs.writeFileSync(index, replacedContents)
|
||||
}
|
||||
|
||||
async function compileParcel (options = {}) {
|
||||
const entryFiles = [
|
||||
@@ -38,8 +59,12 @@ async function compileParcel (options = {}) {
|
||||
// Run the bundler, this returns the main bundle
|
||||
// Use the events if you're using watch mode as this promise will only trigger once and not for every rebuild
|
||||
await bundler.bundle()
|
||||
|
||||
await copyLib();
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
compileParcel
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user