Showing preview only (500K chars total). Download the full file or copy to clipboard to get everything.
Repository: felixrieseberg/windows95
Branch: master
Commit: a6d57c6538da
Files: 77
Total size: 476.6 KB
Directory structure:
gitextract_9xzxgyl3/
├── .gitattributes
├── .github/
│ └── workflows/
│ └── build.yml
├── .gitignore
├── CREDITS.md
├── Dockerfile
├── HELP.md
├── LICENSE.md
├── README.md
├── assets/
│ ├── entitlements.plist
│ └── icon.icns
├── bios/
│ └── COPYING.LESSER
├── docs/
│ ├── docker-instructions.md
│ ├── docker-kubernetes-gitpod.md
│ └── qemu.md
├── forge.config.js
├── issue_template.md
├── package.json
├── patches/
│ └── @electron+packager+18.3.6.patch
├── src/
│ ├── cache.ts
│ ├── constants.ts
│ ├── less/
│ │ ├── emulator.less
│ │ ├── info.less
│ │ ├── root.less
│ │ ├── settings.less
│ │ ├── start.less
│ │ ├── status.less
│ │ └── vendor/
│ │ ├── 95css.css
│ │ └── LICENSE
│ ├── main/
│ │ ├── about-panel.ts
│ │ ├── fileserver/
│ │ │ ├── encoding.ts
│ │ │ ├── fileserver.ts
│ │ │ ├── hide-files.ts
│ │ │ ├── page-directory-listing.ts
│ │ │ └── page-error.ts
│ │ ├── ipc.ts
│ │ ├── logging.ts
│ │ ├── main.ts
│ │ ├── menu.ts
│ │ ├── session.ts
│ │ ├── settings.ts
│ │ ├── squirrel.ts
│ │ ├── update.ts
│ │ └── windows.ts
│ ├── renderer/
│ │ ├── app.tsx
│ │ ├── card-settings.tsx
│ │ ├── card-start.tsx
│ │ ├── emulator-info.tsx
│ │ ├── emulator.tsx
│ │ ├── global.d.ts
│ │ ├── lib/
│ │ │ ├── LICENSE.md
│ │ │ ├── build/
│ │ │ │ └── v86.wasm
│ │ │ └── libv86.js
│ │ ├── start-menu.tsx
│ │ ├── status.tsx
│ │ └── utils/
│ │ ├── get-state-path.ts
│ │ └── reset-state.ts
│ └── utils/
│ ├── devmode.ts
│ └── disk-image-size.ts
├── static/
│ ├── entitlements.plist
│ ├── index.html
│ └── www/
│ ├── apps.htm
│ ├── credits.htm
│ ├── help.htm
│ ├── home.htm
│ ├── index.htm
│ └── navigation.htm
├── tools/
│ ├── add-macos-cert.sh
│ ├── check-links.js
│ ├── download-disk.ps1
│ ├── download-disk.sh
│ ├── generateAssets.js
│ ├── parcel-build.js
│ ├── parcel-watch.js
│ ├── resedit.js
│ ├── run-bin.js
│ └── tsc.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
text eol=lf
================================================
FILE: .github/workflows/build.yml
================================================
name: Build & Release
on:
push:
branches:
- master
tags:
- v*
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: yarn --frozen-lockfile
- name: lint
run: yarn lint
build:
needs: lint
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: 18.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
if: matrix.os != 'macOS-latest'
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Set MacOS signing certs
if: matrix.os == 'macOS-latest'
run: chmod +x tools/add-macos-cert.sh && ./tools/add-macos-cert.sh
env:
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
- name: Set Windows signing certificate
if: matrix.os == 'windows-latest'
continue-on-error: true
id: write_file
uses: timheuer/base64-to-file@v1
with:
fileName: 'win-certificate.pfx'
encodedString: ${{ secrets.WINDOWS_CODESIGN_P12 }}
- name: Download disk image (ps1)
run: tools/download-disk.ps1
if: matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/')
env:
DISK_URL: ${{ secrets.DISK_URL }}
- name: Download disk image (sh)
run: chmod +x tools/download-disk.sh && ./tools/download-disk.sh
if: matrix.os != 'windows-latest' && startsWith(github.ref, 'refs/tags/')
env:
DISK_URL: ${{ secrets.DISK_URL }}
- name: Install
run: yarn
- name: Make
if: startsWith(github.ref, 'refs/tags/')
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: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
files: |
out/**/*.deb
out/**/*.dmg
out/**/*setup*.exe
out/**/*.rpm
out/**/*.zip
================================================
FILE: .gitignore
================================================
node_modules
out
.DS_Store
/images*/
/helper-images/
dist
!.github/images
*.code-workspace
*.pfx
Microsoft.Trusted.Signing.Client*
trusted-signing-metadata.json
.env
electron-windows-sign.log
================================================
FILE: CREDITS.md
================================================
# windows95 Credits
This app was made possible by three major engineering efforts:
* [v86 by Fabian Hemmer](https://github.com/copy/v86)
* [Electron by the Electron Maintainers](https://electronjs.org)
* Windows 95 by Microsoft
# v86 License and Copyright Notice
Copyright (c) 2012-2018, Fabian Hemmer
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
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.
# Electron License and Copyright Notice
Copyright (c) 2013-2018 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Dockerfile
================================================
# DESCRIPTION: Run Windows 95 in a container
# AUTHOR: Paul DeCarlo <toolboc@gmail.com>
#
# Made possible through prior art by:
# copy (v86 - x86 virtualization in JavaScript)
# felixrieseberg (Windows95 running in electron)
# Microsoft (Windows 95)
#
# ***Docker Run Command***
#
# docker run -it \
# -v /tmp/.X11-unix:/tmp/.X11-unix \ # mount the X11 socket
# -e DISPLAY=unix$DISPLAY \ # pass the display
# --device /dev/snd \ # sound
# --name windows95 \
# toolboc/windows95
#
# ***TroubleShooting***
# If you receive Gtk-WARNING **: cannot open display: unix:0
# Run:
# xhost +
#
FROM node:10.9-stretch
LABEL maintainer "Paul DeCarlo <toolboc@gmail.com>"
RUN apt update && apt install -y \
libgtk-3-0 \
libcanberra-gtk3-module \
libx11-xcb-dev \
libgconf2-dev \
libnss3 \
libasound2 \
libxtst-dev \
libxss1 \
git \
--no-install-recommends && \
rm -rf /var/lib/apt/lists/*
COPY . .
RUN npm install
ENTRYPOINT [ "npm", "start"]
================================================
FILE: HELP.md
================================================
# Help & Commonly Asked Questions
## MS-DOS seems to mess up the screen
Hit `Alt + Enter` to make the command screen "Full Screen" (as far as Windows 95 is
concerned). This should restore the display from the garbled mess you see and allow
you to access the Command Prompt. Press Alt-Enter again to leave Full Screen and go
back to Window Mode. (Thanks to @DisplacedGamers for that wisdom)
## Windows 95 is stuck in a bad state
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.
================================================
FILE: LICENSE.md
================================================
Copyright 2019 Felix Rieseberg
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
____
v86 Source Code
Copyright (c) 2012-2018, Fabian Hemmer
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
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.
================================================
FILE: README.md
================================================
# windows95
This is Windows 95, running in an [Electron](https://electronjs.org/) app. Yes, it's the full thing. I'm sorry.
## Downloads
<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/v4.0.0/windows95-4.0.0-setup-ia32.exe">
💿 Installer
</a> |
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-win32-ia32-4.0.0.zip">
📦 Standalone Zip
</a>
<br />
<span>64-bit</span>
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-4.0.0-setup-x64.exe">
💿 Installer
</a> |
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-win32-x64-4.0.0.zip">
📦 Standalone Zip
</a><br />
<span>ARM64</span>
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-4.0.0-setup-arm64.exe">
💿 Installer
</a> |
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-win32-arm64-4.0.0.zip">
📦 Standalone Zip
</a><br />
<span>
❓ Don't know what kind of chip you have? It's probably `x64`. To confirm, on your computer, hit Start, enter "processor" for info.
</span>
</td>
</tr>
<tr>
<td>
<img src="./.github/images/macos.png" width="24"><br />
macOS
</td>
<td>
<span>Apple Silicon Processor</span>
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-darwin-arm64-4.0.0.zip">
📦 Standalone Zip
</a><br />
<span>Intel Processor</span>
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95-darwin-x64-4.0.0.zip">
📦 Standalone Zip
</a>
<span>
❓ Don't know what kind of chip you have? If you bought your computer after 2020, select "Apple Silicon". 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/v4.0.0/windows95-4.0.0-1.x86_64.rpm">
💿 rpm
</a> |
<a href="https://github.com/felixrieseberg/windows95/releases/download/v4.0.0/windows95_4.0.0_amd64.deb">
💿 deb
</a><br />
</td>
</tr>
</table>
<hr />
<table width="100%">
<tr>
<td width="50%">
<img src="https://github.com/user-attachments/assets/43ab7126-765e-444b-ad14-27b1beadbc7c" width="100%" alt="Screenshot showing Windows 95">
</td>
<td width="50%">
<img src="https://github.com/user-attachments/assets/7ac5dc36-cbd4-4455-a616-0e5cca314b34" width="100%" alt="Screenshot showing Windows 95">
</td>
</tr>
</table>
## 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.
## Should this have been a native app?
Absolutely.
## Does it run Doom (or my other favorite game)?
You'll likely be better off with an actual virtualization app, but the short answer is yes. In fact, a few games are already preinstalled - and more can be found on the Internet, for instance at [archive.org](https://www.archive.org). [Thanks to
@DisplacedGamers](https://youtu.be/xDXqmdFxofM) I can recommend that you switch to a resolution of
640x480 @ 256 colors before starting DOS games - just like in the good ol' days.
## Credits
99% of the work was done over at [v86](https://github.com/copy/v86/) by Copy aka Fabian Hemmer and his contributors.
## Contributing
Before you can run this from source, you'll need the disk image. It's not part of the
repository, but you can grab it using the `Show Disk Image` button from the packaged
release, which does include the disk image. You can find that button in the
`Modify C: Drive` section.
Unpack the `images` folder into the `src` folder, creating this layout:
```
- /images/windows95.img
- /images/default-state.bin
- /assets/...
- /bios/...
- /docs/...
```
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)
* [Windows 95 is stuck in a bad state](./HELP.md#windows-95-is-stuck-in-a-bad-state)
* [I want to install additional apps or games](./HELP.md#i-want-to-install-additional-apps-or-games)
* [Running in Docker](./docs/docker-instructions.md)
* [Running in an online VM with Kubernetes and Gitpod](./docs/docker-kubernetes-gitpod.md)
## License
This project is provided for educational purposes only. It is not affiliated with and has
not been approved by Microsoft.
================================================
FILE: assets/entitlements.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
================================================
FILE: bios/COPYING.LESSER
================================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
================================================
FILE: docs/docker-instructions.md
================================================
# Running windows95 in Docker
## Display using a volume mount of the host X11 Unix Socket (Linux Only):
**Requirements:**
* Linux OS with a running X-Server Display
* [Docker](http://docker.io)
docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=unix$DISPLAY --device /dev/snd --name windows95 toolboc/windows95
Note: You may need to run `xhost +` on your system to allow connections to the X server running on the host.
## Display using Xming X11 Server over tcp Socket (Windows and beyond):
**Requirements:**
* [Xming](https://sourceforge.net/projects/xming/)
* [Docker](http://docker.io)
1. Start the Xming X11 Server
2. Run the command below:
docker run -e DISPLAY=host.docker.internal:0 --name windows95 toolboc/windows95
## Display using the host XQuartz Server (MacOS Only):
**Requirements:**
* [XQuartz](https://www.xquartz.org/)
* [Docker](http://docker.io)
1. Start XQuartz, go to `Preferences` -> `Security`, and check the box `Allow connections from network clients`
2. Restart XQuartz
3. In the terminal, run
```
xhost +
```
4. run
```
docker run -it -e DISPLAY=host.docker.internal:0 toolboc/windows95
```
================================================
FILE: docs/docker-kubernetes-gitpod.md
================================================
## Running an online version of windows95
You can also run windows95 in Electron, in a virtual X server, in a JavaScript VNC client, in a Kubernetes workspace. What could go wrong?
[](https://gitpod.io/#https://github.com/felixrieseberg/windows95)
================================================
FILE: docs/qemu.md
================================================
# 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 raw windows95_v4.raw 1G
```
ISO CD image creation
```sh
hdiutil makehybrid -o output.iso /path/to/folder -iso -joliet
```
Installation
```sh
qemu-system-i386 \
-cdrom Win95_OSR25.iso \
-m 128 \
-hda windows95.img \
-device sb16 \
-nic user,model=ne2k_pci \
-fda Win95_boot.img \
-boot a \
-M pc,acpi=off \
-cpu pentium
```
- Boot from floppy
- Run `fdisk` and `format c:`
- Run `D:\setup.exe` with `24796-OEM-0014736-66386`
- After completing setup and restarting your computer, you might get an IOS Windows protection error
- Use `fix95cpu.ima` as a bootable floppy to fix
- Use `vga-driver.iso` to install different video driver
```sh
qemu-system-i386 \
-m 128 \
-hda images/windows95.img \
-device sb16 \
-M pc,acpi=off \
-cpu pentium \
-netdev user,id=mynet0 \
-device ne2k_isa,netdev=mynet0,irq=10
```
================================================
FILE: forge.config.js
================================================
const path = require('path');
const fs = require('fs');
const package = require('./package.json');
require('dotenv').config()
process.env.TEMP = process.env.TMP = `C:\\Users\\FelixRieseberg\\AppData\\Local\\Temp`
const FLAGS = {
SIGNTOOL_PATH: process.env.SIGNTOOL_PATH,
AZURE_CODE_SIGNING_DLIB: process.env.AZURE_CODE_SIGNING_DLIB || path.join(__dirname, 'Microsoft.Trusted.Signing.Client.1.0.60/bin/x64/Azure.CodeSigning.Dlib.dll'),
AZURE_METADATA_JSON: process.env.AZURE_METADATA_JSON || path.resolve(__dirname, 'trusted-signing-metadata.json'),
AZURE_TENANT_ID: process.env.AZURE_TENANT_ID,
AZURE_CLIENT_ID: process.env.AZURE_CLIENT_ID,
AZURE_CLIENT_SECRET: process.env.AZURE_CLIENT_SECRET,
APPLE_ID: process.env.APPLE_ID,
APPLE_ID_PASSWORD: process.env.APPLE_ID_PASSWORD,
}
fs.writeFileSync(FLAGS.AZURE_METADATA_JSON, JSON.stringify({
Endpoint: process.env.AZURE_CODE_SIGNING_ENDPOINT || "https://wcus.codesigning.azure.net",
CodeSigningAccountName: process.env.AZURE_CODE_SIGNING_ACCOUNT_NAME,
CertificateProfileName: process.env.AZURE_CODE_SIGNING_CERTIFICATE_PROFILE_NAME,
}, null, 2));
const windowsSign = {
signToolPath: FLAGS.SIGNTOOL_PATH,
signWithParams: `/v /dlib ${FLAGS.AZURE_CODE_SIGNING_DLIB} /dmdf ${FLAGS.AZURE_METADATA_JSON}`,
timestampServer: "http://timestamp.acs.microsoft.com",
hashes: ["sha256"],
}
module.exports = {
hooks: {
generateAssets: require('./tools/generateAssets'),
},
packagerConfig: {
asar: false,
icon: path.resolve(__dirname, 'assets', 'icon'),
appBundleId: 'com.felixrieseberg.windows95',
appCategoryType: 'public.app-category.developer-tools',
win32metadata: {
CompanyName: 'Felix Rieseberg',
OriginalFilename: 'windows95'
},
osxSign: {
identity: 'Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)',
},
osxNotarize: {
appleId: FLAGS.APPLE_ID,
appleIdPassword: FLAGS.APPLE_ID_PASSWORD,
teamId: 'LT94ZKYDCJ'
},
windowsSign,
ignore: [
/\/assets(\/?)/,
/\/docs(\/?)/,
/\/tools(\/?)/,
/\/src\/.*\.ts/,
/\/test(\/?)/,
/\/@types(\/?)/,
/\/helper-images(\/?)/,
/package-lock\.json/,
/README\.md/,
/tsconfig\.json/,
/Dockerfile/,
/issue_template\.md/,
/HELP\.md/,
/forge\.config\.js/,
/\.github(\/?)/,
/\.circleci(\/?)/,
/\.vscode(\/?)/,
/\.gitignore/,
/\.gitattributes/,
/\.eslintignore/,
/\.eslintrc/,
/\.prettierrc/,
/\/Microsoft\.Trusted\.Signing\.Client.*/,
/\/trusted-signing-metadata/,
]
},
makers: [
{
name: '@electron-forge/maker-squirrel',
platforms: ['win32'],
config: (arch) => {
return {
name: 'windows95',
authors: 'Felix Rieseberg',
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'),
windowsSign
}
}
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin', 'win32']
},
{
name: '@electron-forge/maker-deb',
platforms: ['linux']
},
{
name: '@electron-forge/maker-rpm',
platforms: ['linux']
}
]
};
================================================
FILE: issue_template.md
================================================
⚠️ Thank you for reporting an issue!
Before we go any further, understand that I probably won't be able to fullfil feature requests.
Feel free to report what feature you'd love to see, just don't get angry when I don't have
time to implement it 🙇♂️
I will however _gladly_ help you make a pull request if you're willing to play with Javascript!
================================================
FILE: package.json
================================================
{
"name": "windows95",
"productName": "windows95",
"version": "4.0.0",
"description": "Windows 95, in an app. I'm sorry.",
"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} && npm run check-links",
"less": "node ./tools/lessc.js",
"tsc": "tsc -p tsconfig.json --noEmit",
"check-links": "node tools/check-links.js",
"postinstall": "patch-package"
},
"keywords": [],
"author": "Felix Rieseberg, felix@felixrieseberg.com",
"license": "MIT",
"config": {
"forge": "./forge.config.js"
},
"dependencies": {
"electron-squirrel-startup": "^1.0.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"update-electron-app": "^2.0.1"
},
"devDependencies": {
"@electron-forge/cli": "7.6.1",
"@electron-forge/maker-deb": "7.6.1",
"@electron-forge/maker-flatpak": "^7.6.1",
"@electron-forge/maker-rpm": "^7.6.1",
"@electron-forge/maker-squirrel": "^7.6.1",
"@electron-forge/maker-zip": "^7.6.1",
"@electron-forge/publisher-github": "^7.6.1",
"@types/node": "^20",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"dotenv": "^16.4.7",
"electron": "34.2.0",
"less": "^3.13.0",
"parcel-bundler": "^1.12.5",
"patch-package": "^8.0.0",
"prettier": "^3.5.1",
"rimraf": "^6.0.1",
"typescript": "^5.7.3"
}
}
================================================
FILE: patches/@electron+packager+18.3.6.patch
================================================
diff --git a/node_modules/@electron/packager/dist/win32.js b/node_modules/@electron/packager/dist/win32.js
index 5399b3e..f3b6e88 100644
--- a/node_modules/@electron/packager/dist/win32.js
+++ b/node_modules/@electron/packager/dist/win32.js
@@ -57,7 +57,26 @@ class WindowsApp extends platform_1.App {
resOpts.iconPath = icon;
}
(0, common_1.debug)(`Running resedit with the options ${JSON.stringify(resOpts)}`);
- await (0, resedit_1.resedit)(this.electronBinaryPath, resOpts);
+
+ // This causes segmentation faults for me on multiple machines
+ // It's unclear why exactly but this spawn hack fixes it
+ // await (0, resedit_1.resedit)(this.electronBinaryPath, resOpts);
+
+ const { spawnSync } = require('child_process');
+ const resEditProcess = spawnSync('node', [
+ 'C:\\Users\\FelixRieseberg\\Code\\windows95\\tools\\resedit.js',
+ this.electronBinaryPath
+ ], {
+ stdio: 'inherit'
+ });
+
+ if (resEditProcess.error) {
+ throw resEditProcess.error;
+ }
+
+ if (resEditProcess.status !== 0) {
+ throw new Error(`Resedit process exited with code ${resEditProcess.status}`);
+ }
}
async signAppIfSpecified() {
const windowsSignOpt = this.opts.windowsSign;
================================================
FILE: src/cache.ts
================================================
import { session } from "electron";
export async function clearCaches() {
await clearCache();
await clearStorageData();
}
export async function clearCache() {
if (session.defaultSession) {
await session.defaultSession.clearCache();
}
}
export async function clearStorageData() {
if (!session.defaultSession) {
return;
}
await session.defaultSession.clearStorageData({
storages: [
"appcache",
"cookies",
"filesystem",
"indexdb",
"localstorage",
"shadercache",
"websql",
"serviceworkers",
],
quotas: ["temporary", "persistent", "syncable"],
});
}
================================================
FILE: src/constants.ts
================================================
import * as path from "path";
const IMAGES_PATH = path.join(__dirname, "../../images");
export const CONSTANTS = {
IMAGES_PATH,
IMAGE_PATH: path.join(IMAGES_PATH, "windows95.img"),
IMAGE_DEFAULT_SIZE: 1073741824, // 1GB
DEFAULT_STATE_PATH: path.join(IMAGES_PATH, "default-state.bin"),
};
export const IPC_COMMANDS = {
TOGGLE_INFO: "TOGGLE_INFO",
SHOW_DISK_IMAGE: "SHOW_DISK_IMAGE",
ZOOM_IN: "ZOOM_IN",
ZOOM_OUT: "ZOOM_OUT",
ZOOM_RESET: "ZOOM_RESET",
// Machine instructions
MACHINE_START: "MACHINE_START",
MACHINE_RESTART: "MACHINE_RESTART",
MACHINE_STOP: "MACHINE_STOP",
MACHINE_RESET: "MACHINE_RESET",
MACHINE_ALT_F4: "MACHINE_ALT_F4",
MACHINE_ESC: "MACHINE_ESC",
MACHINE_ALT_ENTER: "MACHINE_ALT_ENTER",
MACHINE_CTRL_ALT_DEL: "MACHINE_CTRL_ALT_DEL",
// Machine events
MACHINE_STARTED: "MACHINE_STARTED",
MACHINE_STOPPED: "MACHINE_STOPPED",
// Else
APP_QUIT: "APP_QUIT",
GET_STATE_PATH: "GET_STATE_PATH",
};
================================================
FILE: src/less/emulator.less
================================================
#emulator {
height: 100vh;
width: 100vw;
display: flex;
> div {
white-space: pre;
font: 14px monospace;
line-height: 14px
}
> canvas {
display: none;
margin: auto;
}
}
.paused {
canvas {
opacity: 0.2;
filter: blur(2px);
z-index: -100;
}
#emulator-text-screen {
display: none;
visibility: hidden;
}
}
================================================
FILE: src/less/info.less
================================================
#information {
text-align: center;
position: absolute;
width: 100vw;
bottom: 50px;
font-size: 18px;
}
================================================
FILE: src/less/root.less
================================================
@import "./status.less";
@import "./emulator.less";
@import "./info.less";
@import "./settings.less";
@import "./start.less";
/* GENERAL RESETS */
html, body {
margin: 0;
padding: 0;
}
body {
background: #000;
}
body.paused > #emulator {
display: none;
}
body.paused {
background: #008080;
font-family: Courier;
}
#buttons {
user-select: none;
}
section {
display: flex;
position: absolute;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
}
.card {
width: 75%;
max-width: 700px;
min-width: 400px;
.card-title {
img {
margin-right: 5px;
}
}
}
.nav-link > img,
.btn > img {
height: 24px;
margin-right: 4px;
}
.windows95 {
* {
user-select: none;
}
*:focus {
outline: none;
}
nav .nav-link,
nav .nav-logo {
height: 37px;
display: flex;
}
nav .nav-logo img {
margin-left: 2px;
max-height: 20px;
}
nav .nav-logo > span {
position: absolute;
top: 9px;
left: 37px;
font-weight: bold;
}
.btn {
height: 40px;
padding-top: 3px;
}
.btn:focus {
border-color: #fff #000 #000 #fff;
outline: 5px auto -webkit-focus-ring-color;
}
.btn.active:before,
.btn:focus:before,
button.active:before,
button:focus:before,
input[type=submit].active:before,
input[type=submit]:focus:before {
border-color: #dedede grey grey #dedede;
}
.card {
// Fix link colors
.link, .link:active, .link:link, .link:visited, a, a:active, a:link, a:visited {
color: #008080;
text-decoration: underline;
cursor: pointer;
}
// Ensure a-elements in fieldsets receive click events
fieldset:before {
pointer-events: none;
}
}
}
================================================
FILE: src/less/settings.less
================================================
#floppy-path {
font-size: .6rem;
width: 100%;
height: 30px;
padding-left: 8px;
border-color: #000 #fff #fff #000;
border-style: solid;
border-width: 2px;
background-color: #c3c3c3;
line-height: 27px;
}
#file-input {
display: none;
}
.settings {
legend > img {
margin-right: 5px;
}
}
================================================
FILE: src/less/start.less
================================================
#section-start {
display: flex;
flex-direction: column;
> small {
margin-top: 25px;
font-size: .8rem;
}
}
================================================
FILE: src/less/status.less
================================================
#status {
user-select: none;
position: absolute;
z-index: 100;
left: calc(50vw - 110px);
background: white;
font-size: 10px;
padding-bottom: 3px;
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
overflow: hidden;
padding-left: 10px;
padding-right: 10px;
max-height: 18px;
top: 0;
}
================================================
FILE: src/less/vendor/95css.css
================================================
*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]),a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}html{box-sizing:border-box;-ms-overflow-style:scrollbar}*,:after,:before{box-sizing:inherit}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col-auto,.col-lg,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-auto,.col-md,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md-auto,.col-sm,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;max-width:100%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.333333%;max-width:8.333333%}.col-2{flex:0 0 16.666667%;max-width:16.666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.333333%;max-width:33.333333%}.col-5{flex:0 0 41.666667%;max-width:41.666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.333333%;max-width:58.333333%}.col-8{flex:0 0 66.666667%;max-width:66.666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.333333%;max-width:83.333333%}.col-11{flex:0 0 91.666667%;max-width:91.666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}@font-face{font-family:windows;src:url(windows.woff2) format("woff2"),url(windows.woff) format("woff")}body{font-family:windows,monospace;font-size:.8rem;line-height:1.7;padding:60px 0;color:#000;background-color:#008483}code,mark{background-color:#c3c3c3}code{padding:2px 4px;font-family:windows}.btn,button,input[type=submit]{font-size:.8rem;position:relative;display:inline-block;width:auto;height:30px;padding:2px 15px;border-color:#fff #000 #000 #fff;border-style:solid;border-width:2px;background-color:#c3c3c3}.btn:before,button:before,input[type=submit]:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";border-color:#dedede grey grey #dedede;border-style:solid;border-width:2px}.btn.active,.btn:focus,button.active,button:focus,input[type=submit].active,input[type=submit]:focus{border-color:#000 #fff #fff #000;outline:none}.btn.active:before,.btn:focus:before,button.active:before,button:focus:before,input[type=submit].active:before,input[type=submit]:focus:before{border-color:grey #dedede #dedede grey}.btn.disabled,button.disabled,input[type=submit].disabled{background-color:#c3c3c3}.input,input,select,textarea{font-size:.6rem;width:100%;height:30px;padding-left:8px;border-color:#000 #fff #fff #000;border-style:solid;border-width:2px;background-color:#fff}.input:focus,input:focus,select:focus,textarea:focus{outline:none}.disabled.input,.input:disabled,input.disabled,input:disabled,select.disabled,select:disabled,textarea.disabled,textarea:disabled{background-color:#c3c3c3}textarea{min-height:150px}select{padding:2px 32px 2px 4px;border-radius:0;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAO8AAADvCAYAAAAacIO5AAAAAXNSR0IArs4c6QAACaVJREFUeAHt3LFuU9sSBmDn3hshISGQQBRISFBR0tCmoOVZ6KGgCEhIUNFQ8hy8AHkQHoACCmi52Uc60vGRjeNktmftmc8SRRJ7Zs03+/d2YXH0+/yx8iBAYFECr1+/Xv1nUSd2WAIEVlNwT09Phde1QGBJAn8HdzqzO++SNuesrQX+GdwJQnhbXw6GX4rAv4M7nVt4l7I952wrsCm4E4bwtr0kDL4EgW3Bnc4uvEvYoDO2FPhTcCcQ4W15WRh6dIFdwZ3OL7yjb9H52glcJLgTivC2uzQMPLLARYM7zSC8I2/S2VoJ7BPcCUZ4W10ehh1VYN/gTnMI76jbdK42ApcJ7oQjvG0uEYOOKHDZ4E6zCO+IG3WmFgJXCe4EJLwtLhNDjiZw1eBO8wjvaFt1nvICEcGdkI6i/ieNs7Oz8ugGJBAhcHJyElHGnTdEURECCQI+Niega0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIEhDdCUQ0CCQLCm4CuJYEIAeGNUFSDQIKA8Caga0kgQkB4IxTVIJAgILwJ6FoSiBAQ3ghFNQgkCAhvArqWBCIE/hdRZCk1Tk5OlnJU57yCwJcvX67w6uW81J13ObtyUgJrAsK7xuEHAssREN7l7MpJCawJCO8ahx8ILEdAeJezKyclsCYgvGscfiCwHAHhXc6unJTAmoDwrnH4gcByBIR3ObtyUgJrAsK7xuEHAssREN7l7MpJCawJtPpu8+fPn9eGv8gPHz9+XF3mdRep7Tl/Fnj27Nnq+fPnf35S47+2Cu+NGzf2XvXx8fHer/GCGIHJ/jI7i+k+fhUfm8ffkRMS2CggvBtZ/JLA+ALCO/6OnJDARgHh3cjilwTGFxDe8XfkhAQ2CgjvRha/JDC+gPCOvyMnJLBRQHg3svglgfEFhHf8HTkhgY0CwruRxS8JjC9w9Pv8EXHMs7OziDLD1fj+/fvq169fe53r69evqxcvXuz1mupPfv/+/erBgwd7jXn9+vXVrVu39nrNEp4c9f+Ht/pu82UWO108+15A+4b9Muda2mvu3r27unfv3tKOPfR5fWweej0OR2C7gPBut/EXAkMLCO/Q63E4AtsFhHe7jb8QGFpAeIdej8MR2C4gvNtt/IXA0ALCO/R6HI7AdgHh3W7jLwSGFhDeodfjcAS2Cwjvdht/ITC0gK9HzrCe+/fvrz59+rSz8rdv31YvX77c+byRn/Du3bvVnTt3dh5xMvGIFRDeWM+/ql27dm316NGjnZUr/J/EDx8+9J3lnZue5wk+Ns/jqiqB2QWEd3ZiDQjMIyC887iqSmB2AeGdnVgDAvMICO88rqoSmF1AeGcn1oDAPALCO4+rqgRmFxDe2Yk1IDCPgPDO46oqgdkFhHd2Yg0IzCPg65HzuF6o6u3bt1cfPnzY+dyfP3+uXr16tfN5kU94+/btavp/k3c9phk8cgSEN8f9r67Td6CfPHmy8wQ/fvzY+ZzoJzx+/Hh18+bN6LLqBQr42ByIqRSBQwoI7yG19SIQKCC8gZhKETikgPAeUlsvAoECwhuIqRSBQwoI7yG19SIQKCC8gZhKETikgPAeUlsvAoECwhuIqRSBQwoI7yG19SIQKODrkYGYc5WavmP85s2bucpvrHuR7zVvfKFfHkxAeA9GfflGx8fHq6dPn16+gFeWFPCxueRaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMJQWEt+RaDdVBQHg7bNmMNQV+BzxOT09/n+v4x8A1cMhr4KrZFVxvWt64k66Bq4RXcJOWdsh3d73G/TRx2fAKruC64yZfA5cJr+AmL83dcNy74SF3s294BVdw3XEHuQb2Ca/gDrK0Q7676zXuXf6i4RVcwXXHHewauEh4BXewpbkbjns3PORudoVXcAXXHXfQa+BP4RXcQZd2yHd3vca9y28Lr+AKrjvu4NfApvAK7uBLczcc9254yN38O7yCK7juuAu5Bv4ZXsFdyNIO+e6u17h3+b/DK7iC6467sGtgCq/gLmxp7obj3g0PuJv/Hh0dnWf39PxN14MAgSUJ/B9DhDGbr5D4RQAAAABJRU5ErkJggg==");background-repeat:no-repeat;background-position:100%;background-size:26px;-webkit-appearance:none}fieldset{position:relative;margin-bottom:20px;padding:10px;border-color:grey #fff #fff grey;background-color:#c3c3c3}fieldset,fieldset:before{border-style:solid;border-width:2px}fieldset:before{position:absolute;top:11px;right:0;bottom:0;left:0;display:block;content:"";border-color:#dedede grey grey #dedede}legend{font-size:.9rem;font-weight:700;position:relative;display:inline-block;width:auto;padding:0 3px;background-color:#c3c3c3}.input-field{margin-bottom:20px}.link,.link:active,.link:hover,.link:link,.link:visited,a,a:active,a:hover,a:link,a:visited{color:#c3c3c3}::-webkit-scrollbar{width:20px;background-image:url(bg-pattern.png);background-size:20px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{position:relative;border-color:#fff #000 #000 #fff;border-style:solid;border-width:2px;background:#c3c3c3;background-color:#c3c3c3}::-webkit-scrollbar-thumb:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";border-color:#dedede grey grey #dedede;border-style:solid;border-width:2px}.card,.modal-dialog{position:relative;border-color:#fff #000 #000 #fff;border-style:solid;border-width:2px;background-color:#c3c3c3}.card:before,.modal-dialog:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";pointer-events:none;border-color:#dedede grey grey #dedede;border-style:solid;border-width:2px}.card-header,.modal-header{width:calc(100% - 4px);margin:2px;padding:4px 10px 6px;color:#fff;background-color:#0f0086}.card-title,.modal-title{font-size:1rem;display:block;margin:0}.card-body,.modal-body{margin:10px}.modal{display:none}.modal.is-open{position:fixed;top:0;right:0;bottom:0;left:0;display:block;width:100vw;height:100vh}.modal-bg{position:relative;width:100%;height:100%;background-color:rgba(0,0,0,.5)}.modal-dialog{position:absolute;top:50%;right:0;left:0;width:100%;max-width:600px;margin:0 auto;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.modal-header{position:relative}.modal-close{position:absolute;top:5px;right:4px;height:24px;padding:0 4px}.has-bottom-nav{padding-bottom:80px}.has-top-nav{padding-top:80px}.nav{position:relative;display:flex;margin-bottom:40px;padding:4px;border-bottom:2px solid #fff;background-color:#c3c3c3;box-shadow:0 2px 2px rgba(0,0,0,.2);justify-content:space-between;align-items:center}.nav:before{position:absolute;bottom:0;left:0;display:block;width:100%;height:2px;content:"";background-color:#dedede}.nav.nav-bottom,.nav.nav-top{position:fixed;z-index:20;width:100%;margin:0}.nav.nav-top{top:0}.nav.nav-bottom{bottom:0;border-top:2px solid #fff;border-bottom:none;box-shadow:0 -2px 2px rgba(0,0,0,.2)}.nav.nav-bottom:before{top:0;bottom:unset}.nav-logo,.nav-logo:active,.nav-logo:hover,.nav-logo:link,.nav-logo:visited{display:inline-block;min-width:150px;height:40px;margin-right:4px;padding:2px 20px 2px 2px;color:#000;border-color:#fff #000 #000 #fff;border-style:solid;border-width:2px;background-color:#c3c3c3}.nav-logo:active:before,.nav-logo:before,.nav-logo:hover:before,.nav-logo:link:before,.nav-logo:visited:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";border-color:#dedede grey grey #dedede;border-style:solid;border-width:2px}.nav-logo:active img,.nav-logo:hover img,.nav-logo:link img,.nav-logo:visited img,.nav-logo img{max-height:32px}.nav-menu-btn{display:block;height:40px}@media screen and (min-width:768px){.nav-menu-btn{display:none}}.nav-menu{position:absolute;bottom:100%;left:0;display:none;width:100%;color:#000;border-color:#fff #000 #000 #fff;border-style:solid;border-width:2px;background-color:#c3c3c3;flex-flow:column}.nav-menu.active{display:flex}.nav-menu:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";border-color:#dedede grey grey #dedede;border-style:solid;border-width:2px}@media screen and (min-width:768px){.nav-menu{position:relative;display:flex;border:none;flex-flow:row}.nav-menu:before{content:none}}.nav-link,.nav-link:active,.nav-link:hover,.nav-link:link,.nav-link:visited{line-height:2;position:relative;display:block;height:40px;margin:0 2px;padding:4px 40px 4px 10px;color:#000}.nav-link:active:hover,.nav-link:hover,.nav-link:hover:hover,.nav-link:link:hover,.nav-link:visited:hover{color:#fff;background-color:#0f0086}@media screen and (min-width:768px){.nav-link:active:hover,.nav-link:hover,.nav-link:hover:hover,.nav-link:link:hover,.nav-link:visited:hover{color:#000;background-color:#c3c3c3}}@media screen and (min-width:768px){.nav-link,.nav-link:active,.nav-link:hover,.nav-link:link,.nav-link:visited{border-color:#fff #000 #000 #fff;border-style:solid;border-width:2px;background-color:#c3c3c3}.nav-link:active:before,.nav-link:before,.nav-link:hover:before,.nav-link:link:before,.nav-link:visited:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";border-color:#dedede grey grey #dedede;border-style:solid;border-width:2px}.nav-link.active,.nav-link:active.active,.nav-link:hover.active,.nav-link:link.active,.nav-link:visited.active{border-color:#000 #fff #fff #000;background-image:url(bg-pattern.png)}.nav-link.active:before,.nav-link:active.active:before,.nav-link:hover.active:before,.nav-link:link.active:before,.nav-link:visited.active:before{border-color:grey #dedede #dedede grey}}
================================================
FILE: src/less/vendor/LICENSE
================================================
MIT License
Copyright (c) 2019 Yoshi Mannaert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: src/main/about-panel.ts
================================================
import { AboutPanelOptionsOptions, app } from "electron";
/**
* Sets Fiddle's About panel options on Linux and macOS
*
* @returns
*/
export function setupAboutPanel(): void {
if (process.platform === "win32") return;
const options: AboutPanelOptionsOptions = {
applicationName: "windows95",
applicationVersion: app.getVersion(),
version: process.versions.electron,
copyright: "Felix Rieseberg",
};
switch (process.platform) {
case "linux":
options.website = "https://github.com/felixrieseberg/windows95";
case "darwin":
options.credits = "https://github.com/felixrieseberg/windows95";
default:
// fallthrough
}
app.setAboutPanelOptions(options);
}
================================================
FILE: src/main/fileserver/encoding.ts
================================================
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">`;
}
================================================
FILE: src/main/fileserver/fileserver.ts
================================================
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
}
});
}
================================================
FILE: src/main/fileserver/hide-files.ts
================================================
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;
}
================================================
FILE: src/main/fileserver/page-directory-listing.ts
================================================
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];
}
================================================
FILE: src/main/fileserver/page-error.ts
================================================
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>
`;
}
================================================
FILE: src/main/ipc.ts
================================================
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-v4.bin");
});
ipcMain.handle(IPC_COMMANDS.APP_QUIT, () => {
app.quit();
});
}
================================================
FILE: src/main/logging.ts
================================================
export function log(message: string, ...args: unknown[]) {
console.log(`[${new Date().toLocaleString()}] ${message}`, ...args);
}
================================================
FILE: src/main/main.ts
================================================
import { app } from "electron";
import { isDevMode } from "../utils/devmode";
import { setupAboutPanel } from "./about-panel";
import { shouldQuit } from "./squirrel";
import { setupUpdates } from "./update";
import { getOrCreateWindow } from "./windows";
import { setupMenu } from "./menu";
import { setupIpcListeners } from "./ipc";
import { setupSession } from "./session";
import { setupFileServer } from './fileserver/fileserver';
/**
* Handle the app's "ready" event. This is essentially
* the method that takes care of booting the application.
*/
export async function onReady() {
if (!isDevMode()) process.env.NODE_ENV = "production";
setupSession();
setupIpcListeners();
getOrCreateWindow();
setupAboutPanel();
setupMenu();
setupUpdates();
setupFileServer();
}
/**
* Handle the "before-quit" event
*
* @export
*/
export function onBeforeQuit() {
(global as any).isQuitting = true;
}
/**
* All windows have been closed, quit on anything but
* macOS.
*/
export function onWindowsAllClosed() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== "darwin") {
app.quit();
}
}
/**
* The main method - and the first function to run
* when Fiddle is launched.
*
* Exported for testing purposes.
*/
export function main() {
// Handle creating/removing shortcuts on Windows when
// installing/uninstalling.
if (shouldQuit()) {
app.quit();
return;
}
// Set the app's name
app.setName("windows95");
// Launch
app.on("ready", onReady);
app.on("before-quit", onBeforeQuit);
app.on("window-all-closed", onWindowsAllClosed);
}
main();
================================================
FILE: src/main/menu.ts
================================================
import { app, shell, Menu, BrowserWindow, ipcMain, dialog } from "electron";
import { clearCaches } from "../cache";
import { IPC_COMMANDS } from "../constants";
import { isDevMode } from "../utils/devmode";
import { log } from "./logging";
const LINKS = {
homepage: "https://www.felixrieseberg.com",
repo: "https://github.com/felixrieseberg/windows95",
credits: "https://github.com/felixrieseberg/windows95/blob/master/CREDITS.md",
help: "https://github.com/felixrieseberg/windows95/blob/master/HELP.md",
};
export async function setupMenu() {
await createMenu();
ipcMain.on(IPC_COMMANDS.MACHINE_STARTED, () =>
createMenu({ isRunning: true }),
);
ipcMain.on(IPC_COMMANDS.MACHINE_STOPPED, () =>
createMenu({ isRunning: false }),
);
}
function send(cmd: string) {
const windows = BrowserWindow.getAllWindows();
if (windows[0]) {
log(`Sending "${cmd}"`);
windows[0].webContents.send(cmd);
} else {
log(`Tried to send "${cmd}", but could not find window`);
}
}
async function createMenu({ isRunning } = { isRunning: false }) {
const template: Array<Electron.MenuItemConstructorOptions> = [
{
label: "View",
submenu: [
{
label: "Toggle Full Screen",
accelerator: (function () {
if (process.platform === "darwin") {
return "Ctrl+Command+F";
} else {
return "F11";
}
})(),
click: function (_item, focusedWindow) {
if (focusedWindow) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
},
{
label: "Toggle Developer Tools",
accelerator: (function () {
if (process.platform === "darwin") {
return "Alt+Command+I";
} else {
return "Ctrl+Shift+I";
}
})(),
click: function (_item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
},
},
{
type: "separator",
},
{
label: "Toggle Emulator Info",
click: () => send(IPC_COMMANDS.TOGGLE_INFO),
},
{
type: "separator",
},
{
role: "reload",
},
],
},
{
role: "editMenu",
visible: isDevMode(),
},
{
label: "Window",
role: "window",
submenu: [
{
label: "Minimize",
accelerator: "CmdOrCtrl+M",
role: "minimize",
},
{
label: "Close",
accelerator: "CmdOrCtrl+W",
role: "close",
},
{
type: "separator",
},
{
label: "Zoom in",
click: () => send(IPC_COMMANDS.ZOOM_IN),
enabled: isRunning,
},
{
label: "Zoom out",
click: () => send(IPC_COMMANDS.ZOOM_OUT),
enabled: isRunning,
},
{
label: "Reset zoom",
click: () => send(IPC_COMMANDS.ZOOM_RESET),
enabled: isRunning,
},
],
},
{
label: "Machine",
submenu: [
{
label: "Send Ctrl+Alt+Del",
click: () => send(IPC_COMMANDS.MACHINE_CTRL_ALT_DEL),
enabled: isRunning,
},
{
label: "Send Alt+F4",
click: () => send(IPC_COMMANDS.MACHINE_ALT_F4),
enabled: isRunning,
},
{
label: "Send Alt+Enter",
click: () => send(IPC_COMMANDS.MACHINE_ALT_ENTER),
enabled: isRunning,
},
{
label: "Send Esc",
click: () => send(IPC_COMMANDS.MACHINE_ESC),
enabled: isRunning,
},
{
type: "separator",
},
isRunning
? {
label: "Stop",
click: () => send(IPC_COMMANDS.MACHINE_STOP),
}
: {
label: "Start",
click: () => send(IPC_COMMANDS.MACHINE_START),
},
{
label: "Restart",
click: () => send(IPC_COMMANDS.MACHINE_RESTART),
enabled: isRunning,
},
{
label: "Reset",
click: async () => {
const result = await dialog.showMessageBox({
type: 'warning',
buttons: ['Reset', 'Cancel'],
defaultId: 1,
title: 'Reset Machine',
message: 'Are you sure you want to reset the machine?',
detail: 'This will delete the machine state, including all changes you have made.',
});
if (result.response === 0) {
send(IPC_COMMANDS.MACHINE_RESET);
}
},
enabled: isRunning,
},
{
type: "separator",
},
{
label: "Go to Disk Image",
click: () => send(IPC_COMMANDS.SHOW_DISK_IMAGE),
},
],
},
{
label: "Help",
role: "help",
submenu: [
{
label: "Author",
click: () => shell.openExternal(LINKS.homepage),
},
{
label: "windows95 on GitHub",
click: () => shell.openExternal(LINKS.repo),
},
{
label: "Help",
click: () => shell.openExternal(LINKS.help),
},
{
type: "separator",
},
{
label: "Troubleshooting",
submenu: [
{
label: "Clear Cache and Restart",
async click() {
await clearCaches();
app.relaunch();
app.quit();
},
},
],
},
],
},
];
if (process.platform === "darwin") {
template.unshift({
label: "windows95",
submenu: [
{
role: "about",
},
{
type: "separator",
},
{
role: "services",
},
{
type: "separator",
},
{
label: "Hide windows95",
accelerator: "Command+H",
role: "hide",
},
{
label: "Hide Others",
accelerator: "Command+Shift+H",
role: "hideothers",
},
{
role: "unhide",
},
{
type: "separator",
},
{
label: "Quit",
accelerator: "Command+Q",
click() {
app.quit();
},
},
],
} as any);
}
Menu.setApplicationMenu(Menu.buildFromTemplate(template as any));
}
================================================
FILE: src/main/session.ts
================================================
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,
},
});
});
}
================================================
FILE: src/main/settings.ts
================================================
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();
================================================
FILE: src/main/squirrel.ts
================================================
export function shouldQuit() {
return require("electron-squirrel-startup");
}
================================================
FILE: src/main/update.ts
================================================
import { app } from "electron";
export function setupUpdates() {
if (app.isPackaged) {
require("update-electron-app")({
repo: "felixrieseberg/windows95",
updateInterval: "1 hour",
});
}
}
================================================
FILE: src/main/windows.ts
================================================
import { BrowserWindow, shell } from "electron";
let mainWindow: BrowserWindow | null = null;
export function getOrCreateWindow(): BrowserWindow {
if (mainWindow) return mainWindow;
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1024,
height: 768,
useContentSize: true,
webPreferences: {
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.on("closed", () => {
mainWindow = null;
});
return mainWindow;
}
function handleNavigation(event: Electron.Event, url: string) {
if (url.startsWith("http")) {
event.preventDefault();
shell.openExternal(url);
}
}
================================================
FILE: src/renderer/app.tsx
================================================
export interface Win95Window extends Window {
emulator: any;
win95: {
app: App;
};
}
declare let window: Win95Window;
/**
* The top-level class controlling the whole app. This is *not* a React component,
* but it does eventually render all components.
*
* @class App
*/
export class App {
/**
* Initial setup call, loading Monaco and kicking off the React
* render process.
*/
public async setup(): Promise<void | Element> {
const React = await import("react");
const { render } = await import("react-dom");
const { Emulator } = await import("./emulator");
const className = `${process.platform}`;
const app = (
<div className={className}>
<Emulator />
</div>
);
const rendered = render(app, document.getElementById("app"));
return rendered;
}
}
window.win95 = window.win95 || {
app: new App(),
};
window.win95.app.setup();
================================================
FILE: src/renderer/card-settings.tsx
================================================
import * as React from "react";
import { resetState } from "./utils/reset-state";
interface CardSettingsProps {
bootFromScratch: () => void;
setFloppy: (file: File) => void;
setCdrom: (cdrom: File) => void;
floppy?: File;
cdrom?: File;
}
interface CardSettingsState {
isStateReset: boolean;
}
export class CardSettings extends React.Component<
CardSettingsProps,
CardSettingsState
> {
constructor(props: CardSettingsProps) {
super(props);
this.onChangeFloppy = this.onChangeFloppy.bind(this);
this.onChangeCdrom = this.onChangeCdrom.bind(this);
this.onResetState = this.onResetState.bind(this);
this.state = {
isStateReset: false,
};
}
public render() {
return (
<section>
<div className="card settings">
<div className="card-header">
<h2 className="card-title">
<img src="../../static/settings.png" />
Settings
</h2>
</div>
<div className="card-body">
{this.renderCdrom()}
<hr />
{this.renderFloppy()}
<hr />
{this.renderState()}
</div>
</div>
</section>
);
}
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;
return (
<fieldset>
<legend>
<img src="../../static/floppy.png" />
Floppy
</legend>
<input
id="floppy-input"
type="file"
onChange={this.onChangeFloppy}
style={{ display: "none" }}
/>
<p>
windows95 comes with a virtual floppy drive. It can mount floppy disk
images in the "img" format.
</p>
<p>
Back in the 90s and before CD-ROMs became a popular, software was
typically distributed on floppy disks. Some developers have since
released their apps or games for free, usually on virtual floppy disks
using the "img" format.
</p>
<p>
Once you've mounted a disk image, you might have to boot your virtual
windows95 machine from scratch.
</p>
<p id="floppy-path">
{floppy
? `Inserted Floppy Disk: ${floppy.name}`
: `No floppy mounted`}
</p>
<button
className="btn"
onClick={() =>
(document.querySelector("#floppy-input") as any).click()
}
>
<img src="../../static/select-floppy.png" />
<span>Mount floppy disk</span>
</button>
</fieldset>
);
}
public renderState() {
const { isStateReset } = this.state;
const { bootFromScratch } = this.props;
return (
<fieldset>
<legend>
<img src="../../static/reset.png" />
Reset machine state
</legend>
<div>
<p>
windows95 stores changes to your machine (like saved files) in a
state file. If you encounter any trouble, you can reset your state
or boot Windows 95 from scratch.{" "}
<strong>All your changes will be lost.</strong>
</p>
<button
className="btn"
onClick={this.onResetState}
disabled={isStateReset}
style={{ marginRight: "5px" }}
>
<img src="../../static/reset-state.png" />
{isStateReset ? "State reset" : "Reset state"}
</button>
<button className="btn" onClick={bootFromScratch}>
<img src="../../static/boot-fresh.png" />
Boot from scratch
</button>
</div>
</fieldset>
);
}
/**
* Handle a change in the floppy input
*
* @param event
*/
private onChangeFloppy(event: React.ChangeEvent<HTMLInputElement>) {
const floppyFile =
event.target.files && event.target.files.length > 0
? event.target.files[0]
: null;
if (floppyFile) {
this.props.setFloppy(floppyFile);
} else {
console.log(`Floppy: Input changed but no file selected`);
}
}
/**
* 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() {
await resetState();
this.setState({ isStateReset: true });
}
}
================================================
FILE: src/renderer/card-start.tsx
================================================
import * as React from "react";
export interface CardStartProps {
startEmulator: () => void;
}
export class CardStart extends React.Component<CardStartProps, {}> {
public render() {
return (
<section id="section-start">
<button className="btn" id="win95" onClick={this.props.startEmulator}>
<img src="../../static/run.png" />
<span>Start Windows 95</span>
</button>
<small>Hit ESC to lock or unlock your mouse</small>
</section>
);
}
}
================================================
FILE: src/renderer/emulator-info.tsx
================================================
import * as React from "react";
interface EmulatorInfoProps {
toggleInfo: () => void;
emulator: any;
}
interface EmulatorInfoState {
cpu: number;
disk: string;
lastCounter: number;
lastTick: number;
}
export class EmulatorInfo extends React.Component<
EmulatorInfoProps,
EmulatorInfoState
> {
private cpuInterval = -1;
constructor(props: EmulatorInfoProps) {
super(props);
this.cpuCount = this.cpuCount.bind(this);
this.onIDEReadStart = this.onIDEReadStart.bind(this);
this.onIDEReadWriteEnd = this.onIDEReadWriteEnd.bind(this);
this.state = {
cpu: 0,
disk: "Idle",
lastCounter: 0,
lastTick: 0,
};
}
public render() {
const { cpu, disk } = this.state;
return (
<div id="status">
Disk: <span>{disk}</span> | CPU Speed: <span>{cpu}</span> |{" "}
<a href="#" onClick={this.props.toggleInfo}>
Hide
</a>
</div>
);
}
public componentWillUnmount() {
this.uninstallListeners();
}
/**
* The emulator starts whenever, so install or uninstall listeners
* at the right time
*
* @param newProps
*/
public componentDidUpdate(prevProps: EmulatorInfoProps) {
if (prevProps.emulator !== this.props.emulator) {
if (this.props.emulator) {
this.installListeners();
} else {
this.uninstallListeners();
}
}
}
/**
* Let's start listening to what the emulator is up to.
*/
private installListeners() {
const { emulator } = this.props;
if (!emulator) {
console.log(
`Emulator info: Tried to install listeners, but emulator not defined yet.`,
);
return;
}
// CPU
if (this.cpuInterval > -1) {
clearInterval(this.cpuInterval);
}
// TypeScript think's we're using a Node.js setInterval. We're not.
this.cpuInterval = setInterval(this.cpuCount, 500) as unknown as number;
// Disk
emulator.add_listener("ide-read-start", this.onIDEReadStart);
emulator.add_listener("ide-read-end", this.onIDEReadWriteEnd);
emulator.add_listener("ide-write-end", this.onIDEReadWriteEnd);
// Screen
emulator.add_listener("screen-set-size-graphical", console.log);
}
/**
* Stop listening to the emulator.
*/
private uninstallListeners() {
const { emulator } = this.props;
if (!emulator) {
console.log(
`Emulator info: Tried to uninstall listeners, but emulator not defined yet.`,
);
return;
}
// CPU
if (this.cpuInterval > -1) {
clearInterval(this.cpuInterval);
}
// Disk
emulator.remove_listener("ide-read-start", this.onIDEReadStart);
emulator.remove_listener("ide-read-end", this.onIDEReadWriteEnd);
emulator.remove_listener("ide-write-end", this.onIDEReadWriteEnd);
// Screen
emulator.remove_listener("screen-set-size-graphical", console.log);
}
/**
* The virtual IDE is handling read (start).
*/
private onIDEReadStart() {
this.requestIdle(() => this.setState({ disk: "Read" }));
}
/**
* The virtual IDE is handling read/write (end).
*/
private onIDEReadWriteEnd() {
this.requestIdle(() => this.setState({ disk: "Idle" }));
}
/**
* Request an idle callback with a 3s timeout.
*
* @param fn
*/
private requestIdle(fn: () => void) {
(window as any).requestIdleCallback(fn, { timeout: 3000 });
}
/**
* Calculates what's up with the virtual cpu.
*/
private cpuCount() {
const { lastCounter, lastTick } = this.state;
const now = Date.now();
const instructionCounter = this.props.emulator.get_instruction_counter();
const ips = instructionCounter - lastCounter;
const deltaTime = now - lastTick;
this.setState({
lastTick: now,
lastCounter: instructionCounter,
cpu: Math.round(ips / deltaTime),
});
}
}
================================================
FILE: src/renderer/emulator.tsx
================================================
import * as React from "react";
import * as fs from "fs";
import * as path from "path";
import { ipcRenderer, shell, webUtils } from "electron";
import { CONSTANTS, IPC_COMMANDS } from "../constants";
import { getDiskImageSize } from "../utils/disk-image-size";
import { CardStart } from "./card-start";
import { StartMenu } from "./start-menu";
import { CardSettings } from "./card-settings";
import { EmulatorInfo } from "./emulator-info";
import { getStatePath } from "./utils/get-state-path";
import { Win95Window } from "./app";
import { resetState } from "./utils/reset-state";
declare let window: Win95Window;
export interface EmulatorState {
currentUiCard: "start" | "settings";
emulator?: any;
scale: number;
floppyFile?: File;
cdromFile?: File;
isBootingFresh: boolean;
isCursorCaptured: boolean;
isInfoDisplayed: boolean;
isRunning: boolean;
}
export class Emulator extends React.Component<{}, EmulatorState> {
private isQuitting = false;
private isResetting = false;
constructor(props: {}) {
super(props);
this.startEmulator = this.startEmulator.bind(this);
this.stopEmulator = this.stopEmulator.bind(this);
this.restartEmulator = this.restartEmulator.bind(this);
this.resetEmulator = this.resetEmulator.bind(this);
this.bootFromScratch = this.bootFromScratch.bind(this);
this.state = {
isBootingFresh: false,
isCursorCaptured: false,
isRunning: false,
currentUiCard: "start",
isInfoDisplayed: true,
// We can start pretty large
// If it's too large, it'll just grow until it hits borders
scale: 2,
};
this.setupInputListeners();
this.setupIpcListeners();
this.setupUnloadListeners();
}
/**
* We want to capture and release the mouse at appropriate times.
*/
public setupInputListeners() {
// ESC
document.onkeydown = (evt) => {
const { isCursorCaptured } = this.state;
evt = evt || window.event;
if (evt.keyCode === 27) {
if (isCursorCaptured) {
this.unlockMouse();
} else {
this.lockMouse();
}
evt.stopPropagation();
}
};
// Click
document.addEventListener("click", () => {
const { isRunning } = this.state;
if (isRunning) {
this.lockMouse();
}
});
}
/**
* Save the emulator's state to disk during exit.
*/
public setupUnloadListeners() {
const handleClose = async () => {
await this.saveState();
console.log(`Unload: Now done, quitting again.`);
this.isQuitting = true;
setImmediate(() => {
ipcRenderer.invoke(IPC_COMMANDS.APP_QUIT);
});
};
window.onbeforeunload = (event: Event) => {
if (this.isQuitting || this.isResetting) {
console.log(`Unload: Not preventing`);
return;
}
console.log(`Unload: Preventing to first save state`);
handleClose();
event.preventDefault();
event.returnValue = false;
};
}
/**
* Setup the various IPC messages sent to the renderer
* from the main process
*/
public setupIpcListeners() {
ipcRenderer.on(IPC_COMMANDS.MACHINE_CTRL_ALT_DEL, () => {
this.sendKeys([
0x1d, // ctrl
0x38, // alt
0x53, // delete
]);
});
ipcRenderer.on(IPC_COMMANDS.MACHINE_ALT_F4, () => {
this.sendKeys([
0x38, // alt
0x3e, // f4
]);
});
ipcRenderer.on(IPC_COMMANDS.MACHINE_ALT_ENTER, () => {
this.sendKeys([
0x38, // alt
0, // enter
]);
});
ipcRenderer.on(IPC_COMMANDS.MACHINE_ESC, () => {
this.sendKeys([
0x18, // alt
]);
});
ipcRenderer.on(IPC_COMMANDS.MACHINE_STOP, this.stopEmulator);
ipcRenderer.on(IPC_COMMANDS.MACHINE_RESET, this.resetEmulator);
ipcRenderer.on(IPC_COMMANDS.MACHINE_START, this.startEmulator);
ipcRenderer.on(IPC_COMMANDS.MACHINE_RESTART, this.restartEmulator);
ipcRenderer.on(IPC_COMMANDS.TOGGLE_INFO, () => {
this.setState({ isInfoDisplayed: !this.state.isInfoDisplayed });
});
ipcRenderer.on(IPC_COMMANDS.SHOW_DISK_IMAGE, () => {
this.showDiskImage();
});
ipcRenderer.on(IPC_COMMANDS.ZOOM_IN, () => {
this.setScale(this.state.scale * 1.2);
});
ipcRenderer.on(IPC_COMMANDS.ZOOM_OUT, () => {
this.setScale(this.state.scale * 0.8);
});
ipcRenderer.on(IPC_COMMANDS.ZOOM_RESET, () => {
this.setScale(1);
});
}
/**
* If the emulator isn't running, this is rendering the, erm, UI.
*
* 🤡
*/
public renderUI() {
const { isRunning, currentUiCard, floppyFile, cdromFile } = this.state;
if (isRunning) {
return null;
}
let card;
if (currentUiCard === "settings") {
card = (
<CardSettings
setFloppy={(floppyFile) => this.setState({ floppyFile })}
setCdrom={(cdromFile) => this.setState({ cdromFile })}
bootFromScratch={this.bootFromScratch}
floppy={floppyFile}
cdrom={cdromFile}
/>
);
} else {
card = <CardStart startEmulator={this.startEmulator} />;
}
return (
<>
{card}
<StartMenu
navigate={(target) => this.setState({ currentUiCard: target as "start" | "settings" })}
/>
</>
);
}
/**
* Yaknow, render things and stuff.
*/
public render() {
return (
<>
{this.renderInfo()}
{this.renderUI()}
<div id="emulator">
<div id="emulator-text-screen"></div>
<canvas id="emulator-canvas"></canvas>
</div>
</>
);
}
/**
* Render the little info thingy
*/
public renderInfo() {
if (!this.state.isInfoDisplayed) {
return null;
}
return (
<EmulatorInfo
emulator={this.state.emulator}
toggleInfo={() => {
this.setState({ isInfoDisplayed: !this.state.isInfoDisplayed });
}}
/>
);
}
/**
* Boot the emulator without restoring state
*/
public bootFromScratch() {
this.setState({ isBootingFresh: true });
this.startEmulator();
}
/**
* Show the disk image on disk
*/
public showDiskImage() {
// Contents/Resources/app/dist/static
console.log(`Showing disk image in ${CONSTANTS.IMAGE_PATH}`);
shell.showItemInFolder(CONSTANTS.IMAGE_PATH);
}
/**
* Start the actual emulator
*/
private async startEmulator() {
document.body.classList.remove("paused");
const cdromPath = this.state.cdromFile
? webUtils.getPathForFile(this.state.cdromFile)
: null;
const options = {
wasm_path: path.join(__dirname, "build/v86.wasm"),
memory_size: 128 * 1024 * 1024,
vga_memory_size: 64 * 1024 * 1024,
screen: {
container: document.getElementById("emulator"),
scale: 0
},
preserve_mac_from_state_image: true,
net_device: {
relay_url: "fetch",
type: "ne2k",
},
bios: {
url: path.join(__dirname, "../../bios/seabios.bin"),
},
vga_bios: {
url: path.join(__dirname, "../../bios/vgabios.bin"),
},
hda: {
url: CONSTANTS.IMAGE_PATH,
async: true,
size: await getDiskImageSize(CONSTANTS.IMAGE_PATH),
},
fda: this.state.floppyFile
? {
buffer: this.state.floppyFile,
}
: undefined,
cdrom: cdromPath
? {
url: cdromPath,
async: true,
size: await getDiskImageSize(cdromPath),
}
: undefined,
boot_order: 0x132,
};
console.log(`🚜 Starting emulator with options`, options);
window["emulator"] = new V86(options);
// New v86 instance
this.setState({
emulator: window["emulator"],
isRunning: true,
});
ipcRenderer.send(IPC_COMMANDS.MACHINE_STARTED);
// Restore state. We can't do this right away
// and randomly chose 500ms as the appropriate
// wait time (lol)
setTimeout(async () => {
if (!this.state.isBootingFresh) {
this.restoreState();
}
this.lockMouse();
this.state.emulator.run();
this.state.emulator.screen_set_scale(this.state.scale);
}, 500);
}
/**
* Restart emulator
*/
private restartEmulator() {
if (this.state.emulator && this.state.isRunning) {
console.log(`🚜 Restarting emulator`);
this.state.emulator.restart();
} else {
console.log(`🚜 Restarting emulator failed: Emulator not running`);
}
}
/**
* Stop the emulator
*/
private async stopEmulator() {
const { emulator, isRunning } = this.state;
if (!emulator || !isRunning) {
return;
}
console.log(`🚜 Stopping emulator`);
await this.saveState();
this.unlockMouse();
await emulator.stop();
this.setState({ isRunning: false });
this.resetCanvas();
document.body.classList.add("paused");
ipcRenderer.send(IPC_COMMANDS.MACHINE_STOPPED);
}
/**
* Reset the emulator by reloading the whole page
*/
private async resetEmulator() {
this.isResetting = true;
await this.stopEmulator();
await resetState();
document.location.reload();
}
/**
* Take the emulators state and write it to disk. This is possibly
* a fairly big file.
*/
private async saveState(): Promise<void> {
const { emulator } = this.state;
const statePath = await getStatePath();
if (!emulator || !emulator.save_state) {
console.log(`restoreState: No emulator present`);
return;
}
try {
const newState = await emulator.save_state();
await fs.promises.writeFile(statePath, Buffer.from(newState), {
flush: true
});
} catch (error) {
console.warn(`saveState: Could not save state`, error);
}
}
/**
* Restores state to the emulator.
*/
private async restoreState() {
const { emulator, isBootingFresh } = this.state;
const state = await this.getState();
if (isBootingFresh) {
console.log(`restoreState: Booting fresh, not restoring.`);
return;
} else if (!state) {
console.log(`restoreState: No state present, not restoring.`);
return;
} else if (!emulator) {
console.log(`restoreState: No emulator present`);
return;
}
try {
await this.state.emulator.restore_state(state);
} catch (error) {
console.log(
`restoreState: Could not read state file. Maybe none exists?`,
error,
);
}
}
/**
* Returns the current machine's state - either what
* we have saved or alternatively the default state.
*
* @returns {ArrayBuffer}
*/
private async getState(): Promise<ArrayBuffer | null> {
const expectedStatePath = await getStatePath();
const statePath = fs.existsSync(expectedStatePath)
? expectedStatePath
: CONSTANTS.DEFAULT_STATE_PATH;
if (fs.existsSync(statePath)) {
return fs.readFileSync(statePath).buffer;
} else {
console.log(`getState: No state file found at ${statePath}`);
}
return null;
}
private unlockMouse() {
const { emulator } = this.state;
this.setState({ isCursorCaptured: false });
if (emulator) {
emulator.mouse_set_status(false);
}
document.exitPointerLock();
}
private lockMouse() {
const { emulator } = this.state;
if (emulator) {
this.setState({ isCursorCaptured: true });
emulator.mouse_set_status(true);
emulator.lock_mouse();
} else {
console.warn(
`Emulator: Tried to lock mouse, but no emulator or not running`,
);
}
}
/**
* Set the emulator's scale
*
* @param target
*/
private setScale(target: number) {
const { emulator, isRunning } = this.state;
if (emulator && isRunning) {
emulator.screen_set_scale(target);
this.setState({ scale: target });
}
}
/**
* Send keys to the emulator (including the key-up),
* if it's running
*
* @param {Array<number>} codes
*/
private sendKeys(codes: Array<number>) {
if (this.state.emulator && this.state.isRunning) {
const scancodes = codes;
// Push break codes (key-up)
for (const scancode of scancodes) {
scancodes.push(scancode | 0x80);
}
this.state.emulator.keyboard_send_scancodes(scancodes);
}
}
/**
* Reset the canvas
*/
private resetCanvas() {
const canvas = document.getElementById("emulator-canvas");
if (canvas instanceof HTMLCanvasElement) {
const ctx = canvas.getContext('2d');
ctx?.clearRect(0, 0, canvas.width, canvas.height);
}
}
}
================================================
FILE: src/renderer/global.d.ts
================================================
declare const V86: any;
declare const win95: any;
================================================
FILE: src/renderer/lib/LICENSE.md
================================================
Copyright (c) 2012, The v86 contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
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.
================================================
FILE: src/renderer/lib/libv86.js
================================================
;(function(){'use strict';function aa(a,b){function c(y){y=y.toString(16);return"#"+"0".repeat(6-y.length)+y}function d(y){var v=256*ma,F=8*S,J=Oa?Oa.canvas:null;J&&J.width===v&&J.height===F||(J?(J.width=v,J.height=F):(J=new OffscreenCanvas(v,F),Oa=J.getContext("2d")),sb=Oa.createImageData(v,F));const Q=sb.data;let P=0,T;F=tb?function(Y){T=T||Y;Q[P+3]=Y;Q[P+7]=Y;P+=8}:function(Y){T=T||Y;Q[P+3]=Y;P+=4};J=32-S;const ha=v*(S-1)*4;v=4*(ma-v*S);const ea=1020*ma;for(let Y=0,Aa=0;2048>Y;++Y,Aa+=J,P+=v){const Ba=Y%256;Y&&!Ba&&(P+=
ha);T=!1;for(let Ia=0;Ia<S;++Ia,++Aa,P+=ea){const Ja=y[Aa];for(let sa=128;0<sa;sa>>=1)F(Ja&sa?255:0);ub&&F(vb&&192<=Ba&&223>=Ba&&Ja&1?255:0)}Mb[Y]=T?1:0}Oa.putImageData(sb,0,0)}function e(y,v,F,J){if(v&&F){y.style.width="";y.style.height="";J&&(y.style.transform="");var Q=y.getBoundingClientRect();J?y.style.transform=(1===v?"":" scaleX("+v+")")+(1===F?"":" scaleY("+F+")"):(0===v%1&&0===F%1?(f.style.imageRendering="crisp-edges",f.style.imageRendering="pixelated",f.style["-ms-interpolation-mode"]="nearest-neighbor"):
(f.style.imageRendering="",f.style["-ms-interpolation-mode"]=""),J=window.devicePixelRatio||1,0!==J%1&&(v/=J,F/=J));1!==v&&(y.style.width=Q.width*v+"px");1!==F&&(y.style.height=Q.height*F+"px")}}const g=a.container;this.screen_fill_buffer=b;console.assert(g,"options.container must be provided");this.FLAG_BLINKING=1;this.FLAG_FONT_PAGE_B=2;var f=g.getElementsByTagName("canvas")[0],h=f.getContext("2d",{alpha:!1}),l=g.getElementsByTagName("div")[0],m=document.createElement("div"),n,p,q=void 0!==a.scale?
a.scale:1,r=void 0!==a.scale?a.scale:1,A=1,w,u,G,z,I,R,na,Oa,sb,Mb=new Int8Array(2048),S,ma,ub,tb,vb,wb=0,xb=0,eb,Nb=0,fb,yb,gb,zb=[],hb=zb,ib=0,Ab=!1;this.init=function(){const y=new Uint16Array([32,9786,9787,9829,9830,9827,9824,8226,9688,9675,9689,9794,9792,9834,9835,9788,9658,9668,8597,8252,182,167,9644,8616,8593,8595,8594,8592,8735,8596,9650,9660]),v=new Uint16Array([8962,199,252,233,226,228,224,229,231,234,235,232,239,238,236,196,197,201,230,198,244,246,242,251,249,255,214,220,162,163,165,8359,
402,225,237,243,250,241,209,170,186,191,8976,172,189,188,161,171,187,9617,9618,9619,9474,9508,9569,9570,9558,9557,9571,9553,9559,9565,9564,9563,9488,9492,9524,9516,9500,9472,9532,9566,9567,9562,9556,9577,9574,9568,9552,9580,9575,9576,9572,9573,9561,9560,9554,9555,9579,9578,9496,9484,9608,9604,9612,9616,9600,945,223,915,960,931,963,181,964,934,920,937,948,8734,966,949,8745,8801,177,8805,8804,8992,8993,247,8776,176,8729,183,8730,8319,178,9632,160]);for(var F=0,J;256>F;F++)J=126<F?v[F-127]:32>F?y[F]:
F,zb.push(String.fromCharCode(J));m.classList.add("cursor");m.style.position="absolute";m.style.backgroundColor="#ccc";m.style.width="7px";m.style.display="inline-block";this.set_mode(!1);this.set_size_text(80,25);2===u&&this.set_size_graphical(720,400,720,400);this.set_scale(q,r);this.timer()};this.make_screenshot=function(){const y=new Image;if(1===u||2===u)y.src=f.toDataURL("image/png");else{const v=[9,16],F=document.createElement("canvas");F.width=z*v[0];F.height=I*v[1];const J=F.getContext("2d");
J.imageSmoothingEnabled=!1;J.font=window.getComputedStyle(l).font;J.textBaseline="top";for(let Q=0;Q<I;Q++)for(let P=0;P<z;P++){const T=4*(Q*z+P),ha=G[T+0],ea=G[T+3];J.fillStyle=c(G[T+2]);J.fillRect(P*v[0],Q*v[1],v[0],v[1]);J.fillStyle=c(ea);J.fillText(hb[ha],P*v[0],Q*v[1])}"none"!==m.style.display&&n<I&&p<z&&(J.fillStyle=m.style.backgroundColor,J.fillRect(p*v[0],n*v[1]+parseInt(m.style.marginTop,10),parseInt(m.style.width,10),parseInt(m.style.height,10)));y.src=F.toDataURL("image/png")}return y};
this.put_char=function(y,v,F,J,Q,P){v=4*(y*z+v);G[v+0]=F;G[v+1]=J;G[v+2]=Q;G[v+3]=P;w[y]=1};this.timer=function(){ib=requestAnimationFrame(()=>this.update_screen())};this.update_screen=function(){Ab||(0===u?this.update_text():1===u?this.update_graphical():this.update_graphical_text());this.timer()};this.update_text=function(){for(var y=0;y<I;y++)w[y]&&(this.text_update_row(y),w[y]=0)};this.update_graphical=function(){this.screen_fill_buffer()};this.update_graphical_text=function(){if(R){var y=performance.now();
if(266<y-Nb){eb=!eb;gb&&(w[n]=1);var v=4*z;for(let ha=0,ea=0;ha<I;++ha)if(w[ha])ea+=v;else for(var F=0;F<z;++F,ea+=4)if(G[ea+1]&1){w[ha]=1;ea+=v-4*F;break}Nb=y}y=Oa.canvas;v=na.canvas;F=4*z;const Q=z*ma,P=S;let T=0;for(let ha=0,ea=0,Y=0;ha<I;++ha,ea+=S){if(!w[ha]){Y+=F;continue}++T;na.clearRect(0,P,Q,S);let Aa,Ba,Ia,Ja;for(let sa=0;sa<Q;sa+=ma,Y+=4){const Ob=G[Y+0];var J=G[Y+1];const Pb=G[Y+2],Qb=G[Y+3],Rb=J&2?xb:wb;J=(!(J&1)||eb)&&Mb[(Rb<<8)+Ob];Ia!==Pb&&(void 0!==Ia&&(R.fillStyle=c(Ia),R.fillRect(Ja,
ea,sa-Ja,S)),Ia=Pb,Ja=sa);Aa!==Qb&&(void 0!==Aa&&(na.fillStyle=c(Aa),na.fillRect(Ba,0,sa-Ba,S)),Aa=Qb,Ba=sa);J&&na.drawImage(y,Ob*ma,Rb*S,ma,S,sa,P,ma,S)}na.fillStyle=c(Aa);na.fillRect(Ba,0,Q-Ba,S);na.globalCompositeOperation="destination-in";na.drawImage(v,0,P,Q,S,0,0,Q,S);na.globalCompositeOperation="source-over";R.fillStyle=c(Ia);R.fillRect(Ja,ea,Q-Ja,S);R.drawImage(v,0,0,Q,S,0,ea,Q,S)}T&&(eb&&gb&&w[n]&&(R.fillStyle=c(G[4*(n*z+p)+3]),R.fillRect(p*ma,n*S+fb,ma,yb-fb+1)),w.fill(0));T&&h.drawImage(R.canvas,
0,0)}};this.destroy=function(){ib&&(cancelAnimationFrame(ib),ib=0)};this.pause=function(){Ab=!0;m.classList.remove("blinking-cursor")};this.continue=function(){Ab=!1;m.classList.add("blinking-cursor")};this.set_mode=function(y){u=y?1:a.use_graphical_text?2:0;0===u?(l.style.display="block",f.style.display="none"):(l.style.display="none",f.style.display="block",2===u&&w&&w.fill(1))};this.set_font_bitmap=function(y,v,F,J,Q,P){const T=F?16:v?9:8;if(S!==y||ma!==T||ub!==v||tb!==F||vb!==J||P)P=ma!==T||S!==
y,S=y,ma=T,ub=v,tb=F,vb=J,2===u&&(d(Q),w.fill(1),P&&this.set_size_graphical_text())};this.set_font_page=function(y,v){if(wb!==y||xb!==v)wb=y,xb=v,w.fill(1)};this.clear_screen=function(){h.fillStyle="#000";h.fillRect(0,0,f.width,f.height)};this.set_size_graphical_text=function(){if(Oa){var y=ma*z,v=S*I,F=2*S;R&&R.canvas.width===y&&R.canvas.height===v&&na.canvas.height===F||(R?(R.canvas.width=y,R.canvas.height=v,na.canvas.width=y,na.canvas.height=F):(R=(new OffscreenCanvas(y,v)).getContext("2d",{alpha:!1}),
na=(new OffscreenCanvas(y,F)).getContext("2d")),this.set_size_graphical(y,v,y,v),w.fill(1))}};this.set_size_text=function(y,v){if(y!==z||v!==I)if(w=new Int8Array(v),G=new Int32Array(y*v*4),z=y,I=v,0===u){for(;l.childNodes.length>v;)l.removeChild(l.firstChild);for(;l.childNodes.length<v;)l.appendChild(document.createElement("div"));for(y=0;y<v;y++)this.text_update_row(y);e(l,q,r,!0)}else 2===u&&this.set_size_graphical_text()};this.set_size_graphical=function(y,v){f.style.display="block";f.width=y;
f.height=v;h.imageSmoothingEnabled=!1;A=640>=y&&2*y<window.innerWidth*window.devicePixelRatio&&2*v<window.innerHeight*window.devicePixelRatio?2:1;e(f,q*A,r*A,!1)};this.set_charmap=function(y){hb=y||zb};this.set_scale=function(y,v){q=y;r=v;e(l,q,r,!0);e(f,q*A,r*A,!1)};this.update_cursor_scanline=function(y,v,F){if(y!==fb||v!==yb||F!==gb)0===u?F?(m.style.display="inline",m.style.height=v-y+"px",m.style.marginTop=y+"px"):m.style.display="none":2===u&&n<I&&(w[n]=1),fb=y,yb=v,gb=F};this.update_cursor=
function(y,v){if(y!==n||v!==p)y<I&&(w[y]=1),n<I&&(w[n]=1),n=y,p=v};this.text_update_row=function(y){var v=4*y*z,F;var J=l.childNodes[y];var Q=document.createElement("div");for(var P=0;P<z;){var T=document.createElement("span");var ha=G[v+1]&1;var ea=G[v+2];var Y=G[v+3];ha&&T.classList.add("blink");T.style.backgroundColor=c(ea);T.style.color=c(Y);for(F="";P<z&&(G[v+1]&1)===ha&&G[v+2]===ea&&G[v+3]===Y;)if(F+=hb[G[v+0]],P++,v+=4,y===n)if(P===p)break;else if(P===p+1){m.style.backgroundColor=T.style.color;
Q.appendChild(m);break}T.textContent=F;Q.appendChild(T)}J.parentNode.replaceChild(Q,J)};this.update_buffer=function(y){for(const v of y)h.putImageData(v.image_data,v.screen_x-v.buffer_x,v.screen_y-v.buffer_y,v.buffer_x,v.buffer_y,v.buffer_width,v.buffer_height)};this.get_text_screen=function(){for(var y=[],v=0;v<I;v++)y.push(this.get_text_row(v));return y};this.get_text_row=function(y){let v="";for(let F=0;F<z;F++)v+=hb[G[4*(y*z+F)]];return v};this.init()};const ba=["shared","exclusive","unlock"];
function ca(a,b,c){this.fs=a;this.bus=c;this.configspace_tagname=[104,111,115,116,57,112];this.configspace_taglen=this.configspace_tagname.length;this.VERSION="9P2000.L";this.msize=this.BLOCKSIZE=8192;this.replybuffer=new Uint8Array(2*this.msize);this.replybuffersize=0;this.fids=[];this.virtio=new da(b,{name:"virtio-9p",pci_id:48,device_id:4169,subsystem_device_id:9,common:{initial_port:43008,queues:[{size_supported:32,notify_offset:0}],features:[0,32,29,28],on_driver_ok:()=>{}},notification:{initial_port:43264,
single_handler:!1,handlers:[d=>{if(0===d){for(;this.virtqueue.has_request();)d=this.virtqueue.pop_request(),this.ReceiveRequest(d);this.virtqueue.notify_me_after(0)}}]},isr_status:{initial_port:42752},device_specific:{initial_port:42496,struct:[{bytes:2,name:"mount tag length",read:()=>this.configspace_taglen,write:()=>{}}].concat(k.range(254).map(d=>({bytes:1,name:"mount tag name "+d,read:()=>this.configspace_tagname[d]||0,write:()=>{}})))}});this.virtqueue=this.virtio.queues[0]}
ca.prototype.get_state=function(){var a=[];a[0]=this.configspace_tagname;a[1]=this.configspace_taglen;a[2]=this.virtio;a[3]=this.VERSION;a[4]=this.BLOCKSIZE;a[5]=this.msize;a[6]=this.replybuffer;a[7]=this.replybuffersize;a[8]=this.fids.map(function(b){return[b.inodeid,b.type,b.uid,b.dbg_name]});a[9]=this.fs;return a};
ca.prototype.set_state=function(a){this.configspace_tagname=a[0];this.configspace_taglen=a[1];this.virtio.set_state(a[2]);this.virtqueue=this.virtio.queues[0];this.VERSION=a[3];this.BLOCKSIZE=a[4];this.msize=a[5];this.replybuffer=a[6];this.replybuffersize=a[7];this.fids=a[8].map(function(b){return{inodeid:b[0],type:b[1],uid:b[2],dbg_name:b[3]}});this.fs.set_state(a[9])};ca.prototype.Createfid=function(a,b,c,d){return{inodeid:a,type:b,uid:c,dbg_name:d}};
ca.prototype.update_dbg_name=function(a,b){for(const c of this.fids)c.inodeid===a&&(c.dbg_name=b)};ca.prototype.reset=function(){this.fids=[];this.virtio.reset()};ca.prototype.BuildReply=function(a,b,c){t.Marshall(["w","b","h"],[c+7,a+1,b],this.replybuffer,0);c+7>=this.replybuffer.length&&x.Debug("Error in 9p: payloadsize exceeds maximum length");this.replybuffersize=c+7};ca.prototype.SendError=function(a,b,c){b=t.Marshall(["w"],[c],this.replybuffer,7);this.BuildReply(6,a,b)};
ca.prototype.SendReply=function(a){a.set_next_blob(this.replybuffer.subarray(0,this.replybuffersize));this.virtqueue.push_reply(a);this.virtqueue.flush_replies()};
ca.prototype.ReceiveRequest=async function(a){var b=new Uint8Array(a.length_readable);a.get_next_blob(b);var c={offset:0},d=t.Unmarshall(["w","b","h"],b,c),e=d[0],g=d[1],f=d[2];switch(g){case 8:e=this.fs.GetTotalSize();b=this.fs.GetSpace();d=[16914839];d[1]=this.BLOCKSIZE;d[2]=Math.floor(b/d[1]);d[3]=d[2]-Math.floor(e/d[1]);d[4]=d[2]-Math.floor(e/d[1]);d[5]=this.fs.CountUsedInodes();d[6]=this.fs.CountFreeInodes();d[7]=0;d[8]=256;e=t.Marshall("wwddddddw".split(""),d,this.replybuffer,7);this.BuildReply(g,
f,e);this.SendReply(a);break;case 112:case 12:d=t.Unmarshall(["w","w"],b,c);var h=d[0];c=d[1];x.Debug("[open] fid="+h+", mode="+c);b=this.fids[h].inodeid;var l=this.fs.GetInode(b);x.Debug("file open "+this.fids[h].dbg_name);e=this.fs.OpenInode(b,c);this.fs.AddEvent(this.fids[h].inodeid,function(){x.Debug("file opened "+this.fids[h].dbg_name+" tag:"+f);var q=[];q[0]=l.qid;q[1]=this.msize-24;t.Marshall(["Q","w"],q,this.replybuffer,7);this.BuildReply(g,f,17);this.SendReply(a)}.bind(this));break;case 70:d=
t.Unmarshall(["w","w","s"],b,c);b=d[0];h=d[1];e=d[2];x.Debug("[link] dfid="+b+", name="+e);e=this.fs.Link(this.fids[b].inodeid,this.fids[h].inodeid,e);if(0>e){this.SendError(f,-1===e?"Operation not permitted":"Unknown error: "+-e,-e);this.SendReply(a);break}this.BuildReply(g,f,0);this.SendReply(a);break;case 16:d=t.Unmarshall(["w","s","s","w"],b,c);h=d[0];e=d[1];b=d[2];d=d[3];x.Debug("[symlink] fid="+h+", name="+e+", symgt="+b+", gid="+d);b=this.fs.CreateSymlink(e,this.fids[h].inodeid,b);l=this.fs.GetInode(b);
l.uid=this.fids[h].uid;l.gid=d;t.Marshall(["Q"],[l.qid],this.replybuffer,7);this.BuildReply(g,f,13);this.SendReply(a);break;case 18:d=t.Unmarshall("wswwww".split(""),b,c);h=d[0];e=d[1];c=d[2];b=d[3];var m=d[4];d=d[5];x.Debug("[mknod] fid="+h+", name="+e+", major="+b+", minor="+m);b=this.fs.CreateNode(e,this.fids[h].inodeid,b,m);l=this.fs.GetInode(b);l.mode=c;l.uid=this.fids[h].uid;l.gid=d;t.Marshall(["Q"],[l.qid],this.replybuffer,7);this.BuildReply(g,f,13);this.SendReply(a);break;case 22:d=t.Unmarshall(["w"],
b,c);h=d[0];l=this.fs.GetInode(this.fids[h].inodeid);x.Debug("[readlink] fid="+h+" name="+this.fids[h].dbg_name+" target="+l.symlink);e=t.Marshall(["s"],[l.symlink],this.replybuffer,7);this.BuildReply(g,f,e);this.SendReply(a);break;case 72:d=t.Unmarshall(["w","s","w","w"],b,c);h=d[0];e=d[1];c=d[2];d=d[3];x.Debug("[mkdir] fid="+h+", name="+e+", mode="+c+", gid="+d);b=this.fs.CreateDirectory(e,this.fids[h].inodeid);l=this.fs.GetInode(b);l.mode=c|fa;l.uid=this.fids[h].uid;l.gid=d;t.Marshall(["Q"],[l.qid],
this.replybuffer,7);this.BuildReply(g,f,13);this.SendReply(a);break;case 14:d=t.Unmarshall(["w","s","w","w","w"],b,c);h=d[0];e=d[1];b=d[2];c=d[3];d=d[4];this.bus.send("9p-create",[e,this.fids[h].inodeid]);x.Debug("[create] fid="+h+", name="+e+", flags="+b+", mode="+c+", gid="+d);b=this.fs.CreateFile(e,this.fids[h].inodeid);this.fids[h].inodeid=b;this.fids[h].type=1;this.fids[h].dbg_name=e;l=this.fs.GetInode(b);l.uid=this.fids[h].uid;l.gid=d;l.mode=c|ia;t.Marshall(["Q","w"],[l.qid,this.msize-24],this.replybuffer,
7);this.BuildReply(g,f,17);this.SendReply(a);break;case 52:d=t.Unmarshall("wbwddws".split(""),b,c);h=d[0];b=d[2];e=0===d[4]?Infinity:d[4];d=this.fs.DescribeLock(d[1],d[3],e,d[5],d[6]);x.Debug("[lock] fid="+h+", type="+ba[d.type]+", start="+d.start+", length="+d.length+", proc_id="+d.proc_id);e=this.fs.Lock(this.fids[h].inodeid,d,b);t.Marshall(["b"],[e],this.replybuffer,7);this.BuildReply(g,f,1);this.SendReply(a);break;case 54:d=t.Unmarshall("wbddws".split(""),b,c);h=d[0];e=0===d[3]?Infinity:d[3];
d=this.fs.DescribeLock(d[1],d[2],e,d[4],d[5]);x.Debug("[getlock] fid="+h+", type="+ba[d.type]+", start="+d.start+", length="+d.length+", proc_id="+d.proc_id);e=this.fs.GetLock(this.fids[h].inodeid,d);e||(e=d,e.type=2);e=t.Marshall(["b","d","d","w","s"],[e.type,e.start,Infinity===e.length?0:e.length,e.proc_id,e.client_id],this.replybuffer,7);this.BuildReply(g,f,e);this.SendReply(a);break;case 24:d=t.Unmarshall(["w","d"],b,c);h=d[0];l=this.fs.GetInode(this.fids[h].inodeid);x.Debug("[getattr]: fid="+
h+" name="+this.fids[h].dbg_name+" request mask="+d[1]);if(!l||l.status===ja){x.Debug("getattr: unlinked");this.SendError(f,"No such file or directory",2);this.SendReply(a);break}d[0]=d[1];d[1]=l.qid;d[2]=l.mode;d[3]=l.uid;d[4]=l.gid;d[5]=l.nlinks;d[6]=l.major<<8|l.minor;d[7]=l.size;d[8]=this.BLOCKSIZE;d[9]=Math.floor(l.size/512+1);d[10]=l.atime;d[11]=0;d[12]=l.mtime;d[13]=0;d[14]=l.ctime;d[15]=0;d[16]=0;d[17]=0;d[18]=0;d[19]=0;t.Marshall("dQwwwddddddddddddddd".split(""),d,this.replybuffer,7);this.BuildReply(g,
f,153);this.SendReply(a);break;case 26:d=t.Unmarshall("wwwwwddddd".split(""),b,c);h=d[0];l=this.fs.GetInode(this.fids[h].inodeid);x.Debug("[setattr]: fid="+h+" request mask="+d[1]+" name="+this.fids[h].dbg_name);d[1]&1&&(l.mode=d[2]);d[1]&2&&(l.uid=d[3]);d[1]&4&&(l.gid=d[4]);d[1]&16&&(l.atime=Math.floor((new Date).getTime()/1E3));d[1]&32&&(l.mtime=Math.floor((new Date).getTime()/1E3));d[1]&64&&(l.ctime=Math.floor((new Date).getTime()/1E3));d[1]&128&&(l.atime=d[6]);d[1]&256&&(l.mtime=d[8]);d[1]&8&&
await this.fs.ChangeSize(this.fids[h].inodeid,d[5]);this.BuildReply(g,f,0);this.SendReply(a);break;case 50:d=t.Unmarshall(["w","d"],b,c);h=d[0];this.BuildReply(g,f,0);this.SendReply(a);break;case 40:case 116:d=t.Unmarshall(["w","d","w"],b,c);h=d[0];e=d[1];m=d[2];l=this.fs.GetInode(this.fids[h].inodeid);40===g&&x.Debug("[treaddir]: fid="+h+" offset="+e+" count="+m);116===g&&x.Debug("[read]: fid="+h+" ("+this.fids[h].dbg_name+") offset="+e+" count="+m+" fidtype="+this.fids[h].type);if(!l||l.status===
ja){x.Debug("read/treaddir: unlinked");this.SendError(f,"No such file or directory",2);this.SendReply(a);break}if(2===this.fids[h].type)for(l.caps.length<e+m&&(m=l.caps.length-e),d=0;d<m;d++)this.replybuffer[11+d]=l.caps[e+d];else this.fs.OpenInode(this.fids[h].inodeid,void 0),d=this.fids[h].inodeid,m=Math.min(m,this.replybuffer.length-11),l.size<e+m?m=l.size-e:40===g&&(m=this.fs.RoundToDirentry(d,e+m)-e),e>l.size&&(m=0),this.bus.send("9p-read-start",[this.fids[h].dbg_name]),d=await this.fs.Read(d,
e,m),this.bus.send("9p-read-end",[this.fids[h].dbg_name,m]),d&&this.replybuffer.set(d,11);t.Marshall(["w"],[m],this.replybuffer,7);this.BuildReply(g,f,4+m);this.SendReply(a);break;case 118:d=t.Unmarshall(["w","d","w"],b,c);h=d[0];e=d[1];m=d[2];d=this.fids[h].dbg_name;x.Debug("[write]: fid="+h+" ("+d+") offset="+e+" count="+m+" fidtype="+this.fids[h].type);if(2===this.fids[h].type){this.SendError(f,"Setxattr not supported",95);this.SendReply(a);break}else await this.fs.Write(this.fids[h].inodeid,e,
m,b.subarray(c.offset));this.bus.send("9p-write-end",[d,m]);t.Marshall(["w"],[m],this.replybuffer,7);this.BuildReply(g,f,4);this.SendReply(a);break;case 74:d=t.Unmarshall(["w","s","w","s"],b,c);e=d[0];b=d[1];c=d[2];d=d[3];x.Debug("[renameat]: oldname="+b+" newname="+d);e=await this.fs.Rename(this.fids[e].inodeid,b,this.fids[c].inodeid,d);if(0>e){this.SendError(f,-2===e?"No such file or directory":-1===e?"Operation not permitted":-39===e?"Directory not empty":"Unknown error: "+-e,-e);this.SendReply(a);
break}this.BuildReply(g,f,0);this.SendReply(a);break;case 76:d=t.Unmarshall(["w","s","w"],b,c);c=d[0];e=d[1];b=d[2];x.Debug("[unlink]: dirfd="+c+" name="+e+" flags="+b);h=this.fs.Search(this.fids[c].inodeid,e);if(-1===h){this.SendError(f,"No such file or directory",2);this.SendReply(a);break}e=this.fs.Unlink(this.fids[c].inodeid,e);if(0>e){this.SendError(f,-39===e?"Directory not empty":-1===e?"Operation not permitted":"Unknown error: "+-e,-e);this.SendReply(a);break}this.BuildReply(g,f,0);this.SendReply(a);
break;case 100:d=t.Unmarshall(["w","s"],b,c);x.Debug("[version]: msize="+d[0]+" version="+d[1]);this.msize!==d[0]&&(this.msize=d[0],this.replybuffer=new Uint8Array(Math.min(16777216,2*this.msize)));e=t.Marshall(["w","s"],[this.msize,this.VERSION],this.replybuffer,7);this.BuildReply(g,f,e);this.SendReply(a);break;case 104:d=t.Unmarshall(["w","w","s","s","w"],b,c);h=d[0];e=d[4];x.Debug("[attach]: fid="+h+" afid="+B(d[1])+" uname="+d[2]+" aname="+d[3]);this.fids[h]=this.Createfid(0,1,e,"");l=this.fs.GetInode(this.fids[h].inodeid);
t.Marshall(["Q"],[l.qid],this.replybuffer,7);this.BuildReply(g,f,13);this.SendReply(a);this.bus.send("9p-attach");break;case 108:d=t.Unmarshall(["h"],b,c);x.Debug("[flush] "+f);this.BuildReply(g,f,0);this.SendReply(a);break;case 110:d=t.Unmarshall(["w","w","h"],b,c);h=d[0];m=d[1];var n=d[2];x.Debug("[walk]: fid="+d[0]+" nwfid="+d[1]+" nwname="+n);if(0===n){this.fids[m]=this.Createfid(this.fids[h].inodeid,1,this.fids[h].uid,this.fids[h].dbg_name);t.Marshall(["h"],[0],this.replybuffer,7);this.BuildReply(g,
f,2);this.SendReply(a);break}e=[];for(d=0;d<n;d++)e.push("s");c=t.Unmarshall(e,b,c);b=this.fids[h].inodeid;e=9;var p=0;x.Debug("walk in dir "+this.fids[h].dbg_name+" to: "+c.toString());for(d=0;d<n;d++){b=this.fs.Search(b,c[d]);if(-1===b){x.Debug("Could not find: "+c[d]);break}e+=t.Marshall(["Q"],[this.fs.GetInode(b).qid],this.replybuffer,e);p++;this.fids[m]=this.Createfid(b,1,this.fids[h].uid,c[d])}t.Marshall(["h"],[p],this.replybuffer,7);this.BuildReply(g,f,e-7);this.SendReply(a);break;case 120:d=
t.Unmarshall(["w"],b,c);x.Debug("[clunk]: fid="+d[0]);this.fids[d[0]]&&0<=this.fids[d[0]].inodeid&&(await this.fs.CloseInode(this.fids[d[0]].inodeid),this.fids[d[0]].inodeid=-1,this.fids[d[0]].type=-1);this.BuildReply(g,f,0);this.SendReply(a);break;case 32:d=t.Unmarshall(["w","s","d","w"],b,c);h=d[0];e=d[1];c=d[2];b=d[3];x.Debug("[txattrcreate]: fid="+h+" name="+e+" attr_size="+c+" flags="+b);this.fids[h].type=2;this.BuildReply(g,f,0);this.SendReply(a);break;case 30:d=t.Unmarshall(["w","w","s"],b,
c);h=d[0];e=d[2];x.Debug("[xattrwalk]: fid="+d[0]+" newfid="+d[1]+" name="+d[2]);this.SendError(f,"Setxattr not supported",95);this.SendReply(a);break;default:x.Debug("Error in Virtio9p: Unknown id "+g+" received"),x.Abort()}};function C(a){this.ports=[];this.cpu=a;for(var b=0;65536>b;b++)this.ports[b]=this.create_empty_entry();var c=a.memory_size[0];for(b=0;b<<17<c;b++)a.memory_map_read8[b]=a.memory_map_write8[b]=void 0,a.memory_map_read32[b]=a.memory_map_write32[b]=void 0;this.mmap_register(c,4294967296-c,function(d){B(d>>>0,8);return 255},function(d,e){B(d>>>0,8);B(e,2)},function(d){B(d>>>0,8);return-1},function(d,e){B(d>>>0,8);B(e>>>0,8)})}
C.prototype.create_empty_entry=function(){return{read8:this.empty_port_read8,read16:this.empty_port_read16,read32:this.empty_port_read32,write8:this.empty_port_write,write16:this.empty_port_write,write32:this.empty_port_write,device:void 0}};C.prototype.empty_port_read8=function(){return 255};C.prototype.empty_port_read16=function(){return 65535};C.prototype.empty_port_read32=function(){return-1};C.prototype.empty_port_write=function(){};
C.prototype.register_read=function(a,b,c,d,e){c&&(this.ports[a].read8=c);d&&(this.ports[a].read16=d);e&&(this.ports[a].read32=e);this.ports[a].device=b};C.prototype.register_write=function(a,b,c,d,e){c&&(this.ports[a].write8=c);d&&(this.ports[a].write16=d);e&&(this.ports[a].write32=e);this.ports[a].device=b};
C.prototype.register_read_consecutive=function(a,b,c,d,e,g){function f(){return c.call(this)|d.call(this)<<8}function h(){return e.call(this)|g.call(this)<<8}function l(){return c.call(this)|d.call(this)<<8|e.call(this)<<16|g.call(this)<<24}e&&g?(this.register_read(a,b,c,f,l),this.register_read(a+1,b,d),this.register_read(a+2,b,e,h),this.register_read(a+3,b,g)):(this.register_read(a,b,c,f),this.register_read(a+1,b,d))};
C.prototype.register_write_consecutive=function(a,b,c,d,e,g){function f(m){c.call(this,m&255);d.call(this,m>>8&255)}function h(m){e.call(this,m&255);g.call(this,m>>8&255)}function l(m){c.call(this,m&255);d.call(this,m>>8&255);e.call(this,m>>16&255);g.call(this,m>>>24)}e&&g?(this.register_write(a,b,c,f,l),this.register_write(a+1,b,d),this.register_write(a+2,b,e,h),this.register_write(a+3,b,g)):(this.register_write(a,b,c,f),this.register_write(a+1,b,d))};
C.prototype.mmap_read32_shim=function(a){var b=this.cpu.memory_map_read8[a>>>17];return b(a)|b(a+1)<<8|b(a+2)<<16|b(a+3)<<24};C.prototype.mmap_write32_shim=function(a,b){var c=this.cpu.memory_map_write8[a>>>17];c(a,b&255);c(a+1,b>>8&255);c(a+2,b>>16&255);c(a+3,b>>>24)};
C.prototype.mmap_register=function(a,b,c,d,e,g){B(a>>>0,8);B(b,8);e||(e=this.mmap_read32_shim.bind(this));g||(g=this.mmap_write32_shim.bind(this));for(a>>>=17;0<b;a++)this.cpu.memory_map_read8[a]=c,this.cpu.memory_map_write8[a]=d,this.cpu.memory_map_read32[a]=e,this.cpu.memory_map_write32[a]=g,b-=131072};C.prototype.port_write8=function(a,b){var c=this.ports[a];c.write8===this.empty_port_write&&(B(a,4),B(b,2),this.get_port_description(a));return c.write8.call(c.device,b)};
C.prototype.port_write16=function(a,b){var c=this.ports[a];c.write16===this.empty_port_write&&(B(a,4),B(b,4),this.get_port_description(a));return c.write16.call(c.device,b)};C.prototype.port_write32=function(a,b){var c=this.ports[a];c.write32===this.empty_port_write&&(B(a,4),B(b>>>0,8),this.get_port_description(a));return c.write32.call(c.device,b)};
C.prototype.port_read8=function(a){var b=this.ports[a];b.read8===this.empty_port_read8&&(B(a,4),this.get_port_description(a));b=b.read8.call(b.device);B(a);return b};C.prototype.port_read16=function(a){var b=this.ports[a];b.read16===this.empty_port_read16&&(B(a,4),this.get_port_description(a));b=b.read16.call(b.device);B(a);return b};C.prototype.port_read32=function(a){var b=this.ports[a];b.read32===this.empty_port_read32&&(B(a,4),this.get_port_description(a));return b.read32.call(b.device)};
var ka={4:"PORT_DMA_ADDR_2",5:"PORT_DMA_CNT_2",10:"PORT_DMA1_MASK_REG",11:"PORT_DMA1_MODE_REG",12:"PORT_DMA1_CLEAR_FF_REG",13:"PORT_DMA1_MASTER_CLEAR",32:"PORT_PIC1_CMD",33:"PORT_PIC1_DATA",64:"PORT_PIT_COUNTER0",65:"PORT_PIT_COUNTER1",66:"PORT_PIT_COUNTER2",67:"PORT_PIT_MODE",96:"PORT_PS2_DATA",97:"PORT_PS2_CTRLB",100:"PORT_PS2_STATUS",112:"PORT_CMOS_INDEX",113:"PORT_CMOS_DATA",128:"PORT_DIAG",129:"PORT_DMA_PAGE_2",146:"PORT_A20",160:"PORT_PIC2_CMD",161:"PORT_PIC2_DATA",178:"PORT_SMI_CMD",179:"PORT_SMI_STATUS",
212:"PORT_DMA2_MASK_REG",214:"PORT_DMA2_MODE_REG",218:"PORT_DMA2_MASTER_CLEAR",240:"PORT_MATH_CLEAR",368:"PORT_ATA2_CMD_BASE",496:"PORT_ATA1_CMD_BASE",632:"PORT_LPT2",744:"PORT_SERIAL4",760:"PORT_SERIAL2",884:"PORT_ATA2_CTRL_BASE",888:"PORT_LPT1",1E3:"PORT_SERIAL3",1008:"PORT_FD_BASE",1010:"PORT_FD_DOR",1012:"PORT_FD_STATUS",1013:"PORT_FD_DATA",1014:"PORT_HD_DATA",1015:"PORT_FD_DIR",1016:"PORT_SERIAL1",3320:"PORT_PCI_CMD",3321:"PORT_PCI_REBOOT",3324:"PORT_PCI_DATA",1026:"PORT_BIOS_DEBUG",1296:"PORT_QEMU_CFG_CTL",
1297:"PORT_QEMU_CFG_DATA",45056:"PORT_ACPI_PM_BASE",45312:"PORT_SMB_BASE",35072:"PORT_BIOS_APM"};C.prototype.get_port_description=function(a){return ka[a]?" ("+ka[a]+")":""};function D(a,b){this.stopping=this.running=!1;this.idle=!0;this.tick_counter=0;this.worker=null;this.cpu=new E(a,b,()=>{this.idle&&this.next_tick(0)});this.bus=a;this.register_yield()}D.prototype.run=function(){this.stopping=!1;this.running||(this.running=!0,this.bus.send("emulator-started"));this.next_tick(0)};D.prototype.do_tick=function(){if(this.stopping||!this.running)this.stopping=this.running=!1,this.bus.send("emulator-stopped");else{this.idle=!1;var a=this.cpu.main_loop();this.next_tick(a)}};
D.prototype.next_tick=function(a){const b=++this.tick_counter;this.idle=!0;this.yield(a,b)};D.prototype.yield_callback=function(a){a===this.tick_counter&&this.do_tick()};D.prototype.stop=function(){this.running&&(this.stopping=!0)};D.prototype.destroy=function(){this.unregister_yield()};D.prototype.restart=function(){this.cpu.reset_cpu();this.cpu.load_bios()};D.prototype.init=function(a){this.cpu.init(a,this.bus);this.bus.send("emulator-ready")};
if("undefined"!==typeof process)D.prototype.yield=function(a,b){1>a?global.setImmediate(c=>this.yield_callback(c),b):setTimeout(c=>this.yield_callback(c),a,b)},D.prototype.register_yield=function(){},D.prototype.unregister_yield=function(){};else if("undefined"!==typeof Worker){function a(){let b;globalThis.onmessage=function(c){const d=c.data.t;b=b&&clearTimeout(b);1>d?postMessage(c.data.tick):b=setTimeout(()=>postMessage(c.data.tick),d)}}D.prototype.register_yield=function(){const b=URL.createObjectURL(new Blob(["("+
a.toString()+")()"],{type:"text/javascript"}));this.worker=new Worker(b);this.worker.onmessage=c=>this.yield_callback(c.data);URL.revokeObjectURL(b)};D.prototype.yield=function(b,c){this.worker.postMessage({t:b,tick:c})};D.prototype.unregister_yield=function(){this.worker&&this.worker.terminate();this.worker=null}}else D.prototype.yield=function(a){setTimeout(()=>{this.do_tick()},a)},D.prototype.register_yield=function(){},D.prototype.unregister_yield=function(){};D.prototype.save_state=function(){return this.cpu.save_state()};
D.prototype.restore_state=function(a){return this.cpu.restore_state(a)};if("object"===typeof performance&&performance.now)D.microtick=performance.now.bind(performance);else if("function"===typeof require){const {performance:a}=require("perf_hooks");D.microtick=a.now.bind(a)}else D.microtick="object"===typeof process&&process.hrtime?function(){var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:Date.now;var H=H||{};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.exportProperty=function(){};var k=k||{};k.pads=function(a,b){return(a||0===a?a+"":"").padEnd(b," ")};k.pad0=function(a,b){return(a||0===a?a+"":"").padStart(b,"0")};k.zeros=function(a){return Array(a).fill(0)};k.range=function(a){return Array.from(Array(a).keys())};
k.view=function(a,b,c,d){return new Proxy({},{get:function(e,g){e=new a(b.buffer,c,d);const f=e[g];if("function"===typeof f)return f.bind(e);/^\d+$/.test(g);return f},set:function(e,g,f){/^\d+$/.test(g);(new a(b.buffer,c,d))[g]=f;return!0}})};function B(a,b){a=a?a.toString(16):"";return"0x"+k.pad0(a.toUpperCase(),b||1)}
if("undefined"!==typeof crypto&&crypto.getRandomValues){const a=new Int32Array(1);k.get_rand_int=function(){crypto.getRandomValues(a);return a[0]}}else if("undefined"!==typeof require){const a=require("crypto");k.get_rand_int=function(){return a.randomBytes(4).readInt32LE(0)}}
(function(){if("function"===typeof Math.clz32)k.int_log2=function(d){return 31-Math.clz32(d)};else{for(var a=new Int8Array(256),b=0,c=-2;256>b;b++)b&b-1||c++,a[b]=c;k.int_log2=function(d){d>>>=0;var e=d>>>16;if(e){var g=e>>>8;return g?24+a[g]:16+a[e]}return(g=d>>>8)?8+a[g]:a[d]}}})();k.round_up_to_next_power_of_2=function(a){return 1>=a?1:1<<1+k.int_log2(a-1)};
function la(a){var b=new Uint8Array(a),c,d;this.length=0;this.push=function(e){this.length!==a&&this.length++;b[d]=e;d=d+1&a-1};this.shift=function(){if(this.length){var e=b[c];c=c+1&a-1;this.length--;return e}return-1};this.peek=function(){return this.length?b[c]:-1};this.clear=function(){this.length=d=c=0};this.clear()}function oa(a){this.size=a;this.data=new Float32Array(a);this.length=this.end=this.start=0}
oa.prototype.push=function(a){this.length===this.size?this.start=this.start+1&this.size-1:this.length++;this.data[this.end]=a;this.end=this.end+1&this.size-1};oa.prototype.shift=function(){if(this.length){var a=this.data[this.start];this.start=this.start+1&this.size-1;this.length--;return a}};
oa.prototype.shift_block=function(a){var b=new Float32Array(a);a>this.length&&(a=this.length);var c=this.start+a,d=this.data.subarray(this.start,c);b.set(d);c>=this.size&&(c-=this.size,b.set(this.data.subarray(0,c),d.length));this.start=c;this.length-=a;return b};oa.prototype.peek=function(){if(this.length)return this.data[this.start]};oa.prototype.clear=function(){this.length=this.end=this.start=0};
k.Bitmap=function(a){"number"===typeof a?this.view=new Uint8Array(a+7>>3):a instanceof ArrayBuffer&&(this.view=new Uint8Array(a))};k.Bitmap.prototype.set=function(a,b){const c=a>>3;a=1<<(a&7);this.view[c]=b?this.view[c]|a:this.view[c]&~a};k.Bitmap.prototype.get=function(a){return this.view[a>>3]>>(a&7)&1};k.Bitmap.prototype.get_buffer=function(){return this.view.buffer};k.load_file="undefined"===typeof XMLHttpRequest?pa:qa;
function qa(a,b,c){function d(){const l=c||0;setTimeout(()=>{qa(a,b,l+1)},1E3*([1,1,2,3,5,8,13,21][l]||34))}var e=new XMLHttpRequest;e.open(b.method||"get",a,!0);e.responseType=b.as_json?"json":"arraybuffer";if(b.headers)for(var g=Object.keys(b.headers),f=0;f<g.length;f++){var h=g[f];e.setRequestHeader(h,b.headers[h])}b.range&&(g=b.range.start,e.setRequestHeader("Range","bytes="+g+"-"+(g+b.range.length-1)),e.setRequestHeader("X-Accept-Encoding","identity"),e.onreadystatechange=function(){200===e.status&&
(console.error("Server sent full file in response to ranged request, aborting",{filename:a}),e.abort())});e.onload=function(){if(4===e.readyState)if(200!==e.status&&206!==e.status)console.error("Loading the image "+a+" failed (status %d)",e.status),500<=e.status&&600>e.status&&d();else if(e.response){if(b.range){const l=e.getResponseHeader("Content-Encoding");l&&"identity"!==l&&console.error("Server sent Content-Encoding in response to ranged request",{filename:a,enc:l})}b.done&&b.done(e.response,
e)}};e.onerror=function(l){console.error("Loading the image "+a+" failed",l);d()};b.progress&&(e.onprogress=function(l){b.progress(l)});e.send(null)}
function pa(a,b){const c=require("fs");b.range?c.open(a,"r",(d,e)=>{if(d)throw d;d=b.range.length;var g=Buffer.allocUnsafe(d);c.read(e,g,0,d,b.range.start,f=>{if(f)throw f;b.done&&b.done(new Uint8Array(g));c.close(e,h=>{if(h)throw h;})})}):c.readFile(a,{encoding:b.as_json?"utf-8":null},function(d,e){d?console.log("Could not read file:",a,d):(d=e,d=b.as_json?JSON.parse(d):(new Uint8Array(d)).buffer,b.done(d))})}
k.read_sized_string_from_mem=function(a,b,c){return String.fromCharCode(...(new Uint8Array(a.buffer,b>>>0,c>>>0)))};(function(){function a(f){this.buffer=f;this.byteLength=f.byteLength;this.onprogress=this.onload=void 0}function b(f,h,l){this.filename=f;this.byteLength=h;this.block_cache=new Map;this.block_cache_is_write=new Set;this.fixed_chunk_size=l;this.cache_reads=!!l;this.onprogress=this.onload=void 0}function c(f,h,l,m,n){const p=f.match(/\.[^\.]+(\.zst)?$/);this.extension=p?p[0]:"";this.basename=f.substring(0,f.length-this.extension.length);this.is_zstd=this.extension.endsWith(".zst");this.basename.endsWith("/")||
(this.basename+="-");this.block_cache=new Map;this.block_cache_is_write=new Set;this.byteLength=h;this.fixed_chunk_size=l;this.partfile_alt_format=!!m;this.zstd_decompress=n;this.cache_reads=!!l;this.onprogress=this.onload=void 0}function d(f){this.file=f;this.byteLength=f.size;1073741824<f.size&&console.warn("SyncFileBuffer: Allocating buffer of "+(f.size>>20)+" MB ...");this.buffer=new ArrayBuffer(f.size);this.onprogress=this.onload=void 0}function e(f){this.file=f;this.byteLength=f.size;this.block_cache=
new Map;this.block_cache_is_write=new Set;this.onprogress=this.onload=void 0}k.SyncBuffer=a;k.AsyncXHRBuffer=b;k.AsyncXHRPartfileBuffer=c;k.AsyncFileBuffer=e;k.SyncFileBuffer=d;k.buffer_from_object=function(f,h){if(f.buffer instanceof ArrayBuffer)return new k.SyncBuffer(f.buffer);if("undefined"!==typeof File&&f.buffer instanceof File)return h=f.async,void 0===h&&(h=268435456<=f.buffer.size),h?new k.AsyncFileBuffer(f.buffer):new k.SyncFileBuffer(f.buffer);if(f.url)return f.use_parts?new k.AsyncXHRPartfileBuffer(f.url,
f.size,f.fixed_chunk_size,!1,h):new k.AsyncXHRBuffer(f.url,f.size,f.fixed_chunk_size)};a.prototype.load=function(){this.onload&&this.onload({buffer:this.buffer})};a.prototype.get=function(f,h,l){l(new Uint8Array(this.buffer,f,h))};a.prototype.set=function(f,h,l){(new Uint8Array(this.buffer,f,h.byteLength)).set(h);l()};a.prototype.get_buffer=function(f){f(this.buffer)};a.prototype.get_state=function(){const f=[];f[0]=this.byteLength;f[1]=new Uint8Array(this.buffer);return f};a.prototype.set_state=
function(f){this.byteLength=f[0];this.buffer=f[1].slice().buffer};b.prototype.load=function(){void 0!==this.byteLength?this.onload&&this.onload(Object.create(null)):g(this.filename,(f,h)=>{if(f)throw Error("Cannot use: "+this.filename+". "+f);this.byteLength=h;this.onload&&this.onload(Object.create(null))})};b.prototype.get_from_cache=function(f,h){var l=h/256;f/=256;for(var m=0;m<l;m++)if(!this.block_cache.get(f+m))return;if(1===l)return this.block_cache.get(f);h=new Uint8Array(h);for(m=0;m<l;m++)h.set(this.block_cache.get(f+
m),256*m);return h};b.prototype.get=function(f,h,l){var m=this.get_from_cache(f,h);if(m)l(m);else{var n=f,p=h;this.fixed_chunk_size&&(n=f-f%this.fixed_chunk_size,p=Math.ceil((f-n+h)/this.fixed_chunk_size)*this.fixed_chunk_size);k.load_file(this.filename,{done:function(q){q=new Uint8Array(q);this.handle_read(n,p,q);n===f&&p===h?l(q):l(q.subarray(f-n,f-n+h))}.bind(this),range:{start:n,length:p}})}};b.prototype.set=function(f,h,l){f/=256;for(var m=h.length/256,n=0;n<m;n++){var p=this.block_cache.get(f+
n);if(void 0===p)p=h.slice(256*n,256*(n+1)),this.block_cache.set(f+n,p);else{const q=h.subarray(256*n,256*(n+1));p.set(q)}this.block_cache_is_write.add(f+n)}l()};b.prototype.handle_read=function(f,h,l){f/=256;h/=256;for(var m=0;m<h;m++){const n=this.block_cache.get(f+m);n?l.set(n,256*m):this.cache_reads&&this.block_cache.set(f+m,l.slice(256*m,256*(m+1)))}};b.prototype.get_buffer=function(f){f()};b.prototype.get_state=function(){const f=[],h=[];for(const [l,m]of this.block_cache)isFinite(l),this.block_cache_is_write.has(l)&&
h.push([l,m]);f[0]=h;return f};b.prototype.set_state=function(f){f=f[0];this.block_cache.clear();this.block_cache_is_write.clear();for(const [h,l]of f)isFinite(h),this.block_cache.set(h,l),this.block_cache_is_write.add(h)};c.prototype.load=function(){this.onload&&this.onload(Object.create(null))};c.prototype.get=function(f,h,l){var m=this.get_from_cache(f,h);if(m)l(m);else if(this.fixed_chunk_size){const p=Math.floor(f/this.fixed_chunk_size),q=f-p*this.fixed_chunk_size,r=Math.ceil((q+h)/this.fixed_chunk_size),
A=new Uint8Array(r*this.fixed_chunk_size);let w=0;for(let u=0;u<r;u++){var n=(p+u)*this.fixed_chunk_size;m=this.partfile_alt_format?this.basename+(p+u+"").padStart(8,"0")+this.extension:this.basename+n+"-"+(n+this.fixed_chunk_size)+this.extension;(n=this.get_from_cache(n,this.fixed_chunk_size))?(A.set(n,u*this.fixed_chunk_size),w++,w===r&&l(A.subarray(q,q+h))):k.load_file(m,{done:async function(G){G=new Uint8Array(G);this.is_zstd&&(G=await this.zstd_decompress(this.fixed_chunk_size,G),G=new Uint8Array(G));
A.set(G,u*this.fixed_chunk_size);this.handle_read((p+u)*this.fixed_chunk_size,this.fixed_chunk_size|0,G);w++;w===r&&l(A.subarray(q,q+h))}.bind(this)})}}else k.load_file(this.basename+f+"-"+(f+h)+this.extension,{done:function(p){p=new Uint8Array(p);this.handle_read(f,h,p);l(p)}.bind(this)})};c.prototype.get_from_cache=b.prototype.get_from_cache;c.prototype.set=b.prototype.set;c.prototype.handle_read=b.prototype.handle_read;c.prototype.get_state=b.prototype.get_state;c.prototype.set_state=b.prototype.set_state;
d.prototype.load=function(){this.load_next(0)};d.prototype.load_next=function(f){var h=new FileReader;h.onload=function(m){m=new Uint8Array(m.target.result);(new Uint8Array(this.buffer,f)).set(m);this.load_next(f+4194304)}.bind(this);if(this.onprogress)this.onprogress({loaded:f,total:this.byteLength,lengthComputable:!0});if(f<this.byteLength){var l=this.file.slice(f,Math.min(f+4194304,this.byteLength));h.readAsArrayBuffer(l)}else this.file=void 0,this.onload&&this.onload({buffer:this.buffer})};d.prototype.get=
a.prototype.get;d.prototype.set=a.prototype.set;d.prototype.get_buffer=a.prototype.get_buffer;d.prototype.get_state=a.prototype.get_state;d.prototype.set_state=a.prototype.set_state;e.prototype.load=function(){this.onload&&this.onload(Object.create(null))};e.prototype.get=function(f,h,l){var m=this.get_from_cache(f,h);m?l(m):(m=new FileReader,m.onload=function(n){n=new Uint8Array(n.target.result);this.handle_read(f,h,n);l(n)}.bind(this),m.readAsArrayBuffer(this.file.slice(f,f+h)))};e.prototype.get_from_cache=
b.prototype.get_from_cache;e.prototype.set=b.prototype.set;e.prototype.handle_read=b.prototype.handle_read;e.prototype.get_state=b.prototype.get_state;e.prototype.set_state=b.prototype.set_state;e.prototype.get_buffer=function(f){f()};e.prototype.get_as_file=function(f){for(var h=[],l=Array.from(this.block_cache.keys()).sort(function(r,A){return r-A}),m=0,n=0;n<l.length;n++){var p=l[n],q=this.block_cache.get(p);p*=256;p!==m&&(h.push(this.file.slice(m,p)),m=p);h.push(q);m+=q.length}m!==this.file.size&&
h.push(this.file.slice(m));return new File(h,f)};var g="undefined"===typeof XMLHttpRequest?function(f,h){require("fs").stat(f,(l,m)=>{l?h(l):h(null,m.size)})}:function(f,h){k.load_file(f,{done:(l,m)=>{l=m.getResponseHeader("Content-Range")||"";(m=l.match(/\/(\d+)\s*$/))?h(null,+m[1]):h("`Range: bytes=...` header not supported (Got `"+l+"`)")},headers:{Range:"bytes=0-0","X-Accept-Encoding":"identity"}})}})();function ra(a,b,c,d,e,g){this.master=new K(this,a,b,d,e,0,g);this.slave=new K(this,a,c,!1,e,1,g);this.current_interface=this.master;this.cpu=a;0===e?(this.ata_port=496,this.irq=14,this.pci_id=240):1===e&&(this.ata_port=368,this.irq=15,this.pci_id=248);this.ata_port_high=this.ata_port|516;this.master_port=46080;this.pci_space=[134,128,16,112,5,0,160,2,0,128,1,1,0,0,0,0,this.ata_port&255|1,this.ata_port>>8,0,0,this.ata_port_high&255|1,this.ata_port_high>>8,0,0,0,0,0,0,0,0,0,0,this.master_port&255|1,
this.master_port>>8,0,0,0,0,0,0,0,0,0,0,67,16,212,130,0,0,0,0,0,0,0,0,0,0,0,0,this.irq,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];this.pci_bars=[{size:8},{size:4},void 0,void 0,{size:16}];this.name="ide"+e;this.device_control=2;a.io.register_read(this.ata_port|7,this,function(){this.cpu.device_lower_irq(this.irq);return this.read_status()});a.io.register_read(this.ata_port_high|
2,this,this.read_status);a.io.register_write(this.ata_port_high|2,this,this.write_control);a.io.register_read(this.ata_port|0,this,function(){return this.current_interface.read_data(1)},function(){return this.current_interface.read_data(2)},function(){return this.current_interface.read_data(4)});a.io.register_read(this.ata_port|1,this,function(){B(this.current_interface.error&255);return this.current_interface.error&255});a.io.register_read(this.ata_port|2,this,function(){B(this.current_interface.bytecount&
255);return this.current_interface.bytecount&255});a.io.register_read(this.ata_port|3,this,function(){B(this.current_interface.sector&255);return this.current_interface.sector&255});a.io.register_read(this.ata_port|4,this,function(){B(this.current_interface.cylinder_low&255);return this.current_interface.cylinder_low&255});a.io.register_read(this.ata_port|5,this,function(){B(this.current_interface.cylinder_high&255);return this.current_interface.cylinder_high&255});a.io.register_read(this.ata_port|
6,this,function(){return this.current_interface.drive_head&255});a.io.register_write(this.ata_port|0,this,function(f){this.current_interface.write_data_port8(f)},function(f){this.current_interface.write_data_port16(f)},function(f){this.current_interface.write_data_port32(f)});a.io.register_write(this.ata_port|1,this,function(f){B(f);this.master.lba_count=(this.master.lba_count<<8|f)&65535;this.slave.lba_count=(this.slave.lba_count<<8|f)&65535});a.io.register_write(this.ata_port|2,this,function(f){B(f);
this.master.bytecount=(this.master.bytecount<<8|f)&65535;this.slave.bytecount=(this.slave.bytecount<<8|f)&65535});a.io.register_write(this.ata_port|3,this,function(f){B(f);this.master.sector=(this.master.sector<<8|f)&65535;this.slave.sector=(this.slave.sector<<8|f)&65535});a.io.register_write(this.ata_port|4,this,function(f){B(f);this.master.cylinder_low=(this.master.cylinder_low<<8|f)&65535;this.slave.cylinder_low=(this.slave.cylinder_low<<8|f)&65535});a.io.register_write(this.ata_port|5,this,function(f){B(f);
this.master.cylinder_high=(this.master.cylinder_high<<8|f)&65535;this.slave.cylinder_high=(this.slave.cylinder_high<<8|f)&65535});a.io.register_write(this.ata_port|6,this,function(f){var h=f&16;B(f,2);this.current_interface=h?this.slave:this.master;this.master.drive_head=f;this.slave.drive_head=f;this.master.is_lba=this.slave.is_lba=f>>6&1;this.master.head=this.slave.head=f&15});this.dma_command=this.dma_status=this.prdt_addr=0;a.io.register_write(this.ata_port|7,this,function(f){this.cpu.device_lower_irq(this.irq);
this.current_interface.ata_command(f)});a.io.register_read(this.master_port|4,this,void 0,void 0,this.dma_read_addr);a.io.register_write(this.master_port|4,this,void 0,void 0,this.dma_set_addr);a.io.register_read(this.master_port,this,this.dma_read_command8,void 0,this.dma_read_command);a.io.register_write(this.master_port,this,this.dma_write_command8,void 0,this.dma_write_command);a.io.register_read(this.master_port|2,this,this.dma_read_status);a.io.register_write(this.master_port|2,this,this.dma_write_status);
a.io.register_read(this.master_port|8,this,function(){return 0});a.io.register_read(this.master_port|10,this,function(){return 0});a.devices.pci.register_device(this)}ra.prototype.read_status=function(){if(this.current_interface.buffer){var a=this.current_interface.status;B(a,2);return a}return 0};ra.prototype.write_control=function(a){B(a,2);a&4&&(this.cpu.device_lower_irq(this.irq),this.master.device_reset(),this.slave.device_reset());this.device_control=a};
ra.prototype.dma_read_addr=function(){B(this.prdt_addr,8);return this.prdt_addr};ra.prototype.dma_set_addr=function(a){B(a,8);this.prdt_addr=a};ra.prototype.dma_read_status=function(){B(this.dma_status);return this.dma_status};ra.prototype.dma_write_status=function(a){B(a);this.dma_status&=~(a&6)};ra.prototype.dma_read_command=function(){return this.dma_read_command8()|this.dma_read_status()<<16};ra.prototype.dma_read_command8=function(){B(this.dma_command);return this.dma_command};
ra.prototype.dma_write_command=function(a){B(a);this.dma_write_command8(a&255);this.dma_write_status(a>>16&255)};
ra.prototype.dma_write_command8=function(a){B(a);const b=this.dma_command;this.dma_command=a&9;if((b&1)!==(a&1))if(0===(a&1))this.dma_status&=-2;else switch(this.dma_status|=1,this.current_interface.current_command){case 37:case 200:this.current_interface.do_ata_read_sectors_dma();break;case 202:case 53:this.current_interface.do_ata_write_sectors_dma();break;case 160:this.current_interface.do_atapi_dma();break;default:B(this.current_interface.current_command)}};
ra.prototype.push_irq=function(){0===(this.device_control&2)&&(this.dma_status|=4,this.cpu.device_raise_irq(this.irq))};ra.prototype.get_state=function(){var a=[];a[0]=this.master;a[1]=this.slave;a[2]=this.ata_port;a[3]=this.irq;a[4]=this.pci_id;a[5]=this.ata_port_high;a[6]=this.master_port;a[7]=this.name;a[8]=this.device_control;a[9]=this.prdt_addr;a[10]=this.dma_status;a[11]=this.current_interface===this.master;a[12]=this.dma_command;return a};
ra.prototype.set_state=function(a){this.master.set_state(a[0]);this.slave.set_state(a[1]);this.ata_port=a[2];this.irq=a[3];this.pci_id=a[4];this.ata_port_high=a[5];this.master_port=a[6];this.name=a[7];this.device_control=a[8];this.prdt_addr=a[9];this.dma_status=a[10];this.current_interface=a[11]?this.master:this.slave;this.dma_command=a[12]};
function K(a,b,c,d,e,g,f){this.device=a;this.bus=f;this.nr=e;this.cpu=b;this.buffer=c;this.sector_size=d?2048:512;this.is_atapi=d;this.cylinder_count=this.sectors_per_track=this.head_count=this.sector_count=0;this.buffer&&(this.sector_count=this.buffer.byteLength/this.sector_size,this.sector_count!==(this.sector_count|0)&&(this.sector_count=Math.ceil(this.sector_count)),d?(this.head_count=1,this.sectors_per_track=0):(this.head_count=16,this.sectors_per_track=63),this.cylinder_count=this.sector_count/
this.head_count/this.sectors_per_track,this.cylinder_count!==(this.cylinder_count|0)&&(this.cylinder_count=Math.floor(this.cylinder_count)),a=b.devices.rtc,a.cmos_write(57,a.cmos_read(57)|1<<4*this.nr),a.cmos_write(18,a.cmos_read(18)&15|240),a.cmos_write(27,this.cylinder_count&255),a.cmos_write(28,this.cylinder_count>>8&255),a.cmos_write(29,this.head_count&255),a.cmos_write(30,255),a.cmos_write(31,255),a.cmos_write(32,200),a.cmos_write(33,this.cylinder_count&255),a.cmos_write(34,this.cylinder_count>>
8&255),a.cmos_write(35,this.sectors_per_track&255));this.buffer=c;this.drive_head=this.head=this.cylinder_high=this.cylinder_low=this.lba_count=this.sector=this.bytecount=this.is_lba=0;this.status=80;this.sectors_per_drq=128;this.data_pointer=this.error=0;this.data=new Uint8Array(65536);this.data16=new Uint16Array(this.data.buffer);this.data32=new Int32Array(this.data.buffer);this.data_end=this.data_length=0;this.current_atapi_command=this.current_command=-1;this.last_io_id=this.write_dest=0;this.in_progress_io_ids=
new Set;this.cancelled_io_ids=new Set;Object.seal(this)}K.prototype.device_reset=function(){this.is_atapi?(this.status=0,this.sector=this.error=this.bytecount=1,this.cylinder_low=20,this.cylinder_high=235):(this.status=81,this.sector=this.error=this.bytecount=1,this.cylinder_high=this.cylinder_low=0);this.cancel_io_operations()};K.prototype.push_irq=function(){this.device.push_irq()};
K.prototype.ata_command=function(a){B(a);if(this.buffer)switch(this.current_command=a,this.error=0,a){case 8:this.data_length=this.data_end=this.data_pointer=0;this.device_reset();this.push_irq();break;case 16:this.status=80;this.cylinder_low=0;this.push_irq();break;case 248:this.status=80;a=this.sector_count-1;this.sector=a&255;this.cylinder_low=a>>8&255;this.cylinder_high=a>>16&255;this.drive_head=this.drive_head&240|a>>24&15;this.push_irq();break;case 39:this.status=80;a=this.sector_count-1;this.sector=
a&255;this.cylinder_low=a>>8&255;this.cylinder_high=a>>16&255;this.sector|=a>>24<<8&65280;this.push_irq();break;case 32:case 36:case 41:case 196:this.ata_read_sectors(a);break;case 48:case 52:case 57:case 197:this.ata_write_sectors(a);break;case 144:this.push_irq();this.error=257;this.status=80;break;case 145:this.status=80;this.push_irq();break;case 160:this.is_atapi&&(this.status=88,this.data_allocate(12),this.data_end=12,this.bytecount=1,this.push_irq());break;case 161:this.is_atapi?(this.create_identify_packet(),
this.status=88,this.cylinder_low=20,this.cylinder_high=235):this.status=65;this.push_irq();break;case 198:B(this.bytecount&255);this.sectors_per_drq=this.bytecount&255;this.status=80;this.push_irq();break;case 37:case 200:this.ata_read_sectors_dma(a);break;case 53:case 202:this.ata_write_sectors_dma(a);break;case 64:this.status=80;this.push_irq();break;case 218:this.status=65;this.error=4;this.push_irq();break;case 224:this.status=80;this.push_irq();break;case 225:this.status=80;this.push_irq();break;
case 231:this.status=80;this.push_irq();break;case 236:if(this.is_atapi){this.status=65;this.error=4;this.push_irq();break}this.create_identify_packet();this.status=88;this.push_irq();break;case 234:this.status=80;this.push_irq();break;case 239:B(this.bytecount&255);this.status=80;this.push_irq();break;case 222:this.status=80;this.push_irq();break;case 245:this.status=80;this.push_irq();break;case 249:this.status=65;this.error=4;break;default:B(a),this.status=65,this.error=4}else this.error=4,this.status=
65,this.push_irq()};
K.prototype.atapi_handle=function(){B(this.data[0]);this.data_pointer=0;this.current_atapi_command=this.data[0];switch(this.current_atapi_command){case 0:this.data_allocate(0);this.data_end=this.data_length;this.status=80;break;case 3:this.data_allocate(this.data[4]);this.data_end=this.data_length;this.status=88;this.data[0]=240;this.data[2]=5;this.data[7]=8;break;case 18:var a=this.data[4];this.status=88;B(this.data[1],2);this.data.set([5,128,1,49,31,0,0,0,83,79,78,89,32,32,32,32,67,68,45,82,79,
77,32,67,68,85,45,49,48,48,48,32,49,46,49,97]);this.data_end=this.data_length=Math.min(36,a);break;case 26:this.data_allocate(this.data[4]);this.data_end=this.data_length;this.status=88;break;case 30:this.data_allocate(0);this.data_end=this.data_length;this.status=80;break;case 37:a=this.sector_count-1;this.data_set(new Uint8Array([a>>24&255,a>>16&255,a>>8&255,a&255,0,0,this.sector_size>>8&255,this.sector_size&255]));this.data_end=this.data_length;this.status=88;break;case 40:this.lba_count&1?this.atapi_read_dma(this.data):
this.atapi_read(this.data);break;case 66:a=this.data[8];this.data_allocate(Math.min(8,a));this.data_end=this.data_length;this.status=88;break;case 67:a=this.data[8]|this.data[7]<<8;var b=this.data[9]>>6;this.data_allocate(a);this.data_end=this.data_length;B(b,2);B(this.data[6]);0===b?(a=this.sector_count,this.data.set(new Uint8Array([0,18,1,1,0,20,1,0,0,0,0,0,0,22,170,0,a>>24,a>>16&255,a>>8&255,a&255]))):1===b&&this.data.set(new Uint8Array([0,10,1,1,0,0,0,0,0,0,0,0]));this.status=88;break;case 70:a=
this.data[8]|this.data[7]<<8;a=Math.min(a,32);this.data_allocate(a);this.data_end=this.data_length;this.data[0]=a-4>>24&255;this.data[1]=a-4>>16&255;this.data[2]=a-4>>8&255;this.data[3]=a-4&255;this.data[6]=8;this.data[10]=3;this.status=88;break;case 81:this.data_allocate(0);this.data_end=this.data_length;this.status=80;break;case 82:B(this.data[0]);this.status=81;this.data_length=0;this.error=80;break;case 90:a=this.data[8]|this.data[7]<<8;b=this.data[2];B(b);42===b&&this.data_allocate(Math.min(30,
a));this.data_end=this.data_length;this.status=88;break;case 189:this.data_allocate(this.data[9]|this.data[8]<<8);this.data_end=this.data_length;this.data[5]=1;this.status=88;break;case 74:this.status=81;this.data_length=0;this.error=80;B(this.data[0]);break;case 190:B(this.data[0]);this.data_allocate(0);this.data_end=this.data_length;this.status=80;break;default:this.status=81,t
gitextract_9xzxgyl3/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── CREDITS.md ├── Dockerfile ├── HELP.md ├── LICENSE.md ├── README.md ├── assets/ │ ├── entitlements.plist │ └── icon.icns ├── bios/ │ └── COPYING.LESSER ├── docs/ │ ├── docker-instructions.md │ ├── docker-kubernetes-gitpod.md │ └── qemu.md ├── forge.config.js ├── issue_template.md ├── package.json ├── patches/ │ └── @electron+packager+18.3.6.patch ├── src/ │ ├── cache.ts │ ├── constants.ts │ ├── less/ │ │ ├── emulator.less │ │ ├── info.less │ │ ├── root.less │ │ ├── settings.less │ │ ├── start.less │ │ ├── status.less │ │ └── vendor/ │ │ ├── 95css.css │ │ └── LICENSE │ ├── main/ │ │ ├── about-panel.ts │ │ ├── fileserver/ │ │ │ ├── encoding.ts │ │ │ ├── fileserver.ts │ │ │ ├── hide-files.ts │ │ │ ├── page-directory-listing.ts │ │ │ └── page-error.ts │ │ ├── ipc.ts │ │ ├── logging.ts │ │ ├── main.ts │ │ ├── menu.ts │ │ ├── session.ts │ │ ├── settings.ts │ │ ├── squirrel.ts │ │ ├── update.ts │ │ └── windows.ts │ ├── renderer/ │ │ ├── app.tsx │ │ ├── card-settings.tsx │ │ ├── card-start.tsx │ │ ├── emulator-info.tsx │ │ ├── emulator.tsx │ │ ├── global.d.ts │ │ ├── lib/ │ │ │ ├── LICENSE.md │ │ │ ├── build/ │ │ │ │ └── v86.wasm │ │ │ └── libv86.js │ │ ├── start-menu.tsx │ │ ├── status.tsx │ │ └── utils/ │ │ ├── get-state-path.ts │ │ └── reset-state.ts │ └── utils/ │ ├── devmode.ts │ └── disk-image-size.ts ├── static/ │ ├── entitlements.plist │ ├── index.html │ └── www/ │ ├── apps.htm │ ├── credits.htm │ ├── help.htm │ ├── home.htm │ ├── index.htm │ └── navigation.htm ├── tools/ │ ├── add-macos-cert.sh │ ├── check-links.js │ ├── download-disk.ps1 │ ├── download-disk.sh │ ├── generateAssets.js │ ├── parcel-build.js │ ├── parcel-watch.js │ ├── resedit.js │ ├── run-bin.js │ └── tsc.js └── tsconfig.json
SYMBOL INDEX (243 symbols across 35 files)
FILE: forge.config.js
constant FLAGS (line 9) | const FLAGS = {
FILE: src/cache.ts
function clearCaches (line 3) | async function clearCaches() {
function clearCache (line 8) | async function clearCache() {
function clearStorageData (line 14) | async function clearStorageData() {
FILE: src/constants.ts
constant IMAGES_PATH (line 3) | const IMAGES_PATH = path.join(__dirname, "../../images");
constant CONSTANTS (line 5) | const CONSTANTS = {
constant IPC_COMMANDS (line 12) | const IPC_COMMANDS = {
FILE: src/main/about-panel.ts
function setupAboutPanel (line 8) | function setupAboutPanel(): void {
FILE: src/main/fileserver/encoding.ts
function encode (line 1) | function encode(text: string) {
function getEncoding (line 13) | function getEncoding() {
FILE: src/main/fileserver/fileserver.ts
type FileEntry (line 8) | interface FileEntry {
constant APP_INTERCEPT (line 14) | const APP_INTERCEPT = 'http://windows95/';
constant MY_COMPUTER_INTERCEPT (line 15) | const MY_COMPUTER_INTERCEPT = 'http://my-computer/';
function setupFileServer (line 22) | function setupFileServer() {
function getFilePath (line 107) | function getFilePath(url: string) {
function serveFile (line 126) | async function serveFile(fullPath: string): Promise<Response> {
FILE: src/main/fileserver/hide-files.ts
constant FILES_TO_HIDE_ON_DARWIN (line 5) | const FILES_TO_HIDE_ON_DARWIN: string[] = [
constant FILES_TO_HIDE_ON_WINDOWS (line 19) | const FILES_TO_HIDE_ON_WINDOWS: string[] = [
constant FILES_TO_HIDE_ON_LINUX (line 27) | const FILES_TO_HIDE_ON_LINUX: string[] = [];
function shouldHideFile (line 29) | function shouldHideFile(file: FileEntry) {
function isHiddenFile (line 41) | function isHiddenFile(file: FileEntry) {
function isSystemHiddenFile (line 49) | function isSystemHiddenFile(file: FileEntry) {
function getFilesToHide (line 55) | function getFilesToHide() {
FILE: src/main/fileserver/page-directory-listing.ts
function generateDirectoryListing (line 10) | function generateDirectoryListing(currentPath: string, files: string[]):...
function getParentFolderLinkHtml (line 61) | function getParentFolderLinkHtml(parentPath: string) {
function getDesktopLinkHtml (line 70) | function getDesktopLinkHtml() {
function getDownloadsLinkHtml (line 81) | function getDownloadsLinkHtml() {
function getIconHtml (line 92) | function getIconHtml(icon: string) {
function getFileLiHtml (line 96) | function getFileLiHtml(entry: FileEntry) {
function getDisplayName (line 110) | function getDisplayName(entry: FileEntry) {
function formatFileSize (line 114) | function formatFileSize(bytes: number): string {
FILE: src/main/fileserver/page-error.ts
function generateErrorPage (line 4) | function generateErrorPage(errorMessage: string, requestedPath: string):...
FILE: src/main/ipc.ts
function setupIpcListeners (line 6) | function setupIpcListeners() {
FILE: src/main/logging.ts
function log (line 1) | function log(message: string, ...args: unknown[]) {
FILE: src/main/main.ts
function onReady (line 17) | async function onReady() {
function onBeforeQuit (line 34) | function onBeforeQuit() {
function onWindowsAllClosed (line 42) | function onWindowsAllClosed() {
function main (line 56) | function main() {
FILE: src/main/menu.ts
constant LINKS (line 8) | const LINKS = {
function setupMenu (line 15) | async function setupMenu() {
function send (line 26) | function send(cmd: string) {
function createMenu (line 37) | async function createMenu({ isRunning } = { isRunning: false }) {
FILE: src/main/session.ts
function setupSession (line 3) | function setupSession() {
FILE: src/main/settings.ts
type Settings (line 5) | interface Settings {
constant DEFAULT_SETTINGS (line 11) | const DEFAULT_SETTINGS: Settings = {
class SettingsManager (line 17) | class SettingsManager {
method constructor (line 21) | constructor() {
method load (line 26) | private load(): Settings {
method save (line 44) | private save(): void {
method get (line 52) | get(key: keyof Settings): any {
method set (line 56) | set(key: keyof Settings, value: any): void {
method delete (line 61) | delete(key: keyof Settings): void {
method clear (line 66) | clear(): void {
FILE: src/main/squirrel.ts
function shouldQuit (line 1) | function shouldQuit() {
FILE: src/main/update.ts
function setupUpdates (line 3) | function setupUpdates() {
FILE: src/main/windows.ts
function getOrCreateWindow (line 5) | function getOrCreateWindow(): BrowserWindow {
function handleNavigation (line 35) | function handleNavigation(event: Electron.Event, url: string) {
FILE: src/renderer/app.tsx
type Win95Window (line 1) | interface Win95Window extends Window {
class App (line 16) | class App {
method setup (line 21) | public async setup(): Promise<void | Element> {
FILE: src/renderer/card-settings.tsx
type CardSettingsProps (line 5) | interface CardSettingsProps {
type CardSettingsState (line 13) | interface CardSettingsState {
class CardSettings (line 17) | class CardSettings extends React.Component<
method constructor (line 21) | constructor(props: CardSettingsProps) {
method render (line 33) | public render() {
method renderCdrom (line 55) | public renderCdrom() {
method renderFloppy (line 93) | public renderFloppy() {
method renderState (line 140) | public renderState() {
method onChangeFloppy (line 180) | private onChangeFloppy(event: React.ChangeEvent<HTMLInputElement>) {
method onChangeCdrom (line 198) | private onChangeCdrom(event: React.ChangeEvent<HTMLInputElement>) {
method onResetState (line 214) | private async onResetState() {
FILE: src/renderer/card-start.tsx
type CardStartProps (line 3) | interface CardStartProps {
class CardStart (line 7) | class CardStart extends React.Component<CardStartProps, {}> {
method render (line 8) | public render() {
FILE: src/renderer/emulator-info.tsx
type EmulatorInfoProps (line 3) | interface EmulatorInfoProps {
type EmulatorInfoState (line 8) | interface EmulatorInfoState {
class EmulatorInfo (line 15) | class EmulatorInfo extends React.Component<
method constructor (line 21) | constructor(props: EmulatorInfoProps) {
method render (line 36) | public render() {
method componentWillUnmount (line 49) | public componentWillUnmount() {
method componentDidUpdate (line 59) | public componentDidUpdate(prevProps: EmulatorInfoProps) {
method installListeners (line 72) | private installListeners() {
method uninstallListeners (line 102) | private uninstallListeners() {
method onIDEReadStart (line 129) | private onIDEReadStart() {
method onIDEReadWriteEnd (line 136) | private onIDEReadWriteEnd() {
method requestIdle (line 145) | private requestIdle(fn: () => void) {
method cpuCount (line 152) | private cpuCount() {
FILE: src/renderer/emulator.tsx
type EmulatorState (line 18) | interface EmulatorState {
class Emulator (line 30) | class Emulator extends React.Component<{}, EmulatorState> {
method constructor (line 34) | constructor(props: {}) {
method setupInputListeners (line 62) | public setupInputListeners() {
method setupUnloadListeners (line 93) | public setupUnloadListeners() {
method setupIpcListeners (line 123) | public setupIpcListeners() {
method renderUI (line 183) | public renderUI() {
method render (line 219) | public render() {
method renderInfo (line 235) | public renderInfo() {
method bootFromScratch (line 253) | public bootFromScratch() {
method showDiskImage (line 261) | public showDiskImage() {
method startEmulator (line 271) | private async startEmulator() {
method restartEmulator (line 346) | private restartEmulator() {
method stopEmulator (line 358) | private async stopEmulator() {
method resetEmulator (line 380) | private async resetEmulator() {
method saveState (line 393) | private async saveState(): Promise<void> {
method restoreState (line 415) | private async restoreState() {
method getState (line 446) | private async getState(): Promise<ArrayBuffer | null> {
method unlockMouse (line 461) | private unlockMouse() {
method lockMouse (line 473) | private lockMouse() {
method setScale (line 492) | private setScale(target: number) {
method sendKeys (line 507) | private sendKeys(codes: Array<number>) {
method resetCanvas (line 523) | private resetCanvas() {
FILE: src/renderer/lib/libv86.js
function aa (line 1) | function aa(a,b){function c(y){y=y.toString(16);return"#"+"0".repeat(6-y...
function ca (line 17) | function ca(a,b,c){this.fs=a;this.bus=c;this.configspace_tagname=[104,11...
function C (line 42) | function C(a){this.ports=[];this.cpu=a;for(var b=0;65536>b;b++)this.port...
function f (line 45) | function f(){return c.call(this)|d.call(this)<<8}
function h (line 45) | function h(){return e.call(this)|g.call(this)<<8}
function l (line 45) | function l(){return c.call(this)|d.call(this)<<8|e.call(this)<<16|g.call...
function f (line 46) | function f(m){c.call(this,m&255);d.call(this,m>>8&255)}
function h (line 46) | function h(m){e.call(this,m&255);g.call(this,m>>8&255)}
function l (line 46) | function l(m){c.call(this,m&255);d.call(this,m>>8&255);e.call(this,m>>16...
function D (line 53) | function D(a,b){this.stopping=this.running=!1;this.idle=!0;this.tick_cou...
function a (line 55) | function a(){let b;globalThis.onmessage=function(c){const d=c.data.t;b=b...
function B (line 58) | function B(a,b){a=a?a.toString(16):"";return"0x"+k.pad0(a.toUpperCase(),...
function la (line 61) | function la(a){var b=new Uint8Array(a),c,d;this.length=0;this.push=funct...
function oa (line 61) | function oa(a){this.size=a;this.data=new Float32Array(a);this.length=thi...
function qa (line 65) | function qa(a,b,c){function d(){const l=c||0;setTimeout(()=>{qa(a,b,l+1)...
function pa (line 68) | function pa(a,b){const c=require("fs");b.range?c.open(a,"r",(d,e)=>{if(d...
function a (line 69) | function a(f){this.buffer=f;this.byteLength=f.byteLength;this.onprogress...
function b (line 69) | function b(f,h,l){this.filename=f;this.byteLength=h;this.block_cache=new...
function c (line 69) | function c(f,h,l,m,n){const p=f.match(/\.[^\.]+(\.zst)?$/);this.extensio...
function d (line 70) | function d(f){this.file=f;this.byteLength=f.size;1073741824<f.size&&cons...
function e (line 70) | function e(f){this.file=f;this.byteLength=f.size;this.block_cache=
function ra (line 82) | function ra(a,b,c,d,e,g){this.master=new K(this,a,b,d,e,0,g);this.slave=...
function K (line 96) | function K(a,b,c,d,e,g,f){this.device=a;this.bus=f;this.nr=e;this.cpu=b;...
function va (line 139) | function va(a){this.pci_addr=new Uint8Array(4);this.pci_value=new Uint8A...
function L (line 150) | function L(a,b){this.io=a.io;this.cpu=a;this.dma=a.devices.dma;this.byte...
function M (line 166) | function M(a){this.cpu=a;this.channel_page=new Uint8Array(8);this.channe...
function wa (line 183) | function wa(a,b){this.cpu=a;this.bus=b;this.counter_start_time=new Float...
function N (line 192) | function N(a,b,c,d){this.cpu=a;this.bus=b;this.screen=c;this.vga_memory_...
function za (line 269) | function za(a,b){this.cpu=a;this.bus=b;this.use_mouse=this.enable_mouse_...
function Ca (line 291) | function Ca(a){this.cpu=a;this.cmos_index=0;this.cmos_data=new Uint8Arra...
function Da (line 300) | function Da(a,b,c){this.bus=c;this.cpu=a;this.ints=4;this.line_control=t...
function Ea (line 309) | function Ea(a){this.cpu=a;var b=a.io;a.devices.pci.register_device({pci_...
function Fa (line 314) | function Fa(a){this.cpu=a;this.timer_divider=this.apic_id=0;this.timer_d...
function Ga (line 327) | function Ga(a){this.cpu=a;this.ioredtbl_config=new Int32Array(24);this.i...
function Ha (line 333) | function Ha(a){this.message=a}
function La (line 334) | function La(a,b){if("object"!==typeof a||null===a)return a;if(Array.isAr...
function Ma (line 335) | function Ma(a,b){if("object"!==typeof a||null===a)return a;if(Array.isAr...
function b (line 337) | function b(p,q){const r=p.length;if(16>r)throw new Ha("Invalid length: "...
function c (line 337) | function c(p){p=(new TextDecoder).decode(p);return JSON.parse(p)}
function Na (line 340) | function Na(a,b,c){a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&a[3]===b[3]&&a...
function Pa (line 342) | function Pa(a){return[a[0].toString(16).padStart(2,"0"),a[1].toString(16...
function Qa (line 343) | function Qa(a,b,c,d,e){this.cpu=a;this.pci=a.devices.pci;this.id=e||0;th...
function O (line 362) | function O(a,b){this.cpu=a;this.bus=b;this.write_buffer=new la(64);this....
function U (line 382) | function U(a,b,c){c||(c=O.prototype.dsp_default_handler);for(var d=0;d<a...
function Xa (line 382) | function Xa(a){for(var b=[],c=0;16>c;c++)b.push(a+c);return b}
function $a (line 394) | function $a(a,b){b||(b=O.prototype.mixer_default_read);Ta[a]=b}
function ab (line 394) | function ab(a,b){b||(b=O.prototype.mixer_default_write);Ua[a]=b}
function bb (line 394) | function bb(a,b,c){Va[a]=1;Ta[a]=function(){return this.mixer_registers[...
function cb (line 395) | function cb(a,b,c){Ta[a]=O.prototype.mixer_default_read;Ua[a]=function(d...
function db (line 399) | function db(a,b){b||(b=O.prototype.fm_default_write);for(var c=0;c<a.len...
function jb (line 399) | function jb(a,b){for(var c=[];a<=b;a++)c.push(a);return c}
function Ya (line 406) | function Ya(a,b,c){return(a<b)*b+(a>c)*c+(b<=a&&a<=c)*a}
function da (line 406) | function da(a,b){this.cpu=a;this.pci=a.devices.pci;this.device_id=b.devi...
function V (line 425) | function V(a,b,c){this.cpu=a;this.virtio=b;this.size_supported=this.size...
function lb (line 434) | function lb(a,b){this.cpu=a.cpu;this.virtio=a.virtio;this.head_idx=b;thi...
function mb (line 437) | function mb(a,b){this.bus=b;this.rows=25;this.cols=80;this.ports=4;b=[{s...
function nb (line 443) | function nb(a,b,c){this.bus=b;this.id=a.devices.net?1:0;this.status=this...
function pb (line 449) | function pb(a,b){this.bus=b;this.zeroed=this.fp_cmd=this.actual=this.num...
function rb (line 454) | function rb(){this.listeners={};this.pair=void 0}
function ta (line 455) | function ta(){}
function ua (line 455) | function ua(){}
function E (line 455) | function E(a,b,c){this.stop_idling=c;this.wm=b;this.wasm_patch();this.cr...
function g (line 484) | function g(l){return new Uint8Array(Int32Array.of(l).buffer)}
function f (line 484) | function f(l){return l>>8|l<<8&65280}
function h (line 484) | function h(l){return l<<24|l<<8&16711680|l>>8&65280|l>>>24}
function p (line 496) | function p(q){B(n);B(q,2);q?this.device_raise_irq(n):
function Kb (line 505) | function Kb(a){return a.map(function(b){var c=Object.keys(b);console.ass...
function Cb (line 506) | function Cb(a,b){const c={};let d=0;for(const e of b)b=e.get.call(a,d,!0...
function Eb (line 506) | function Eb(a,b,c){const d=[];let e=0;for(var g=0;g<c;g++){const [f,h]=C...
function Bb (line 506) | function Bb(a,b,c,d){var e=new Uint8Array(b);const g=new Uint16Array(b);...
function Sb (line 509) | function Sb(a){function b(z){return z.shiftKey&&z.ctrlKey&&(73===z.keyCo...
function Tb (line 517) | function Tb(a,b){function c(u){if(!w.enabled||!w.emu_enabled)return!1;va...
function Ub (line 521) | function Ub(a){if("undefined"!==typeof window)if(window.AudioContext||wi...
function Xb (line 523) | function Xb(a,b){function c(d){return function(e){d.gain.setValueAtTime(...
function Zb (line 530) | function Zb(a,b,c,d){this.audio_context=a;this.connected_right=this.conn...
function Yb (line 533) | function Yb(a,b,c){this.node_oscillator=b.createOscillator();this.node_o...
function Vb (line 535) | function Vb(a,b,c){this.bus=a;this.audio_context=b;this.enabled=!1;this....
function Wb (line 545) | function Wb(a,b,c){this.bus=a;this.audio_context=b;this.enabled=!1;this....
function $b (line 548) | function $b(a,b){function c(h){f.bus&&f.enabled&&(f.send_char(h.which),h...
function ac (line 552) | function ac(a,b){this.element=a;if(window.Terminal){var c=this.term=new ...
function bc (line 552) | function bc(a,b,c){this.bus=b;this.socket=void 0;this.id=c||0;this.send_...
function X (line 555) | function X(a){this.cpu_is_running=!1;this.cpu_exception_hook=function(){...
function c (line 561) | function c(q,r){switch(q){case "hda":e.hda=this.disk_images.hda=r;break;...
function d (line 562) | async function d(){if(e.fs9p&&e.fs9p_json&&!e.initial_state&&(e.fs9p.loa...
function c (line 590) | function c(f){return"string"===typeof a?f.includes(a):a.test(f)}
function d (line 590) | function d(f){[f]=f;e.add(f)}
function jc (line 591) | function jc(a){this.message=a||"File already exists"}
function ic (line 591) | function ic(a){this.message=a||"File not found"}
function fc (line 591) | function fc(){var a,b=0,c=0;this.put_char=function(d,e,g){a[d*b+e]=g};th...
function dc (line 592) | function dc(a,b){b=b.id||0;this.bus=a;this.bus_send_msgid=`net${b}-send`...
function qc (line 593) | function qc(a){return[0,1,2,3,4,5].map(b=>a[b].toString(16)).map(b=>1===...
function rc (line 593) | function rc(a){return a[0]<<24|a[1]<<16|a[2]<<8|a[3]}
class sc (line 594) | class sc{constructor(a,b){a=Math.min(a,16);this.maximum_capacity=b?Math....
method constructor (line 594) | constructor(a,b){a=Math.min(a,16);this.maximum_capacity=b?Math.max(b,a...
method write (line 594) | write(a){const b=a.length;var c=this.length+b;let d=this.buffer.length...
method peek (line 595) | peek(a){const b=Math.min(this.length,a.length);if(b){const e=this.buff...
method remove (line 595) | remove(a){a>this.length&&(a=this.length);a&&(this.tail=(this.tail+a)%t...
function tc (line 596) | function tc(){const a=new Uint8Array(1518),b=a.buffer,c=a.byteOffset;ret...
function uc (line 596) | function uc(a,b,c,d){d.eth_frame.set(b,c.byteOffset+a);return b.length}
function vc (line 597) | function vc(a,b,c,d){const e=c.byteOffset+(a&-2);d=d.eth_frame;for(c=c.b...
function wc (line 598) | function wc(a,b){a.eth_frame.fill(0);var c=a.eth_frame,d=c.subarray,e=a....
function xc (line 605) | function xc(a,b){fetch(`https://${b.doh_server||"cloudflare-dns.com"}/dn...
function yc (line 606) | function yc(a,b){let c={};c.eth={ethertype:2048,src:b.router_mac,dest:a....
function zc (line 608) | function zc(a,b){let c={};var d=(new DataView(a.buffer,a.byteOffset,a.by...
function Ac (line 618) | function Ac(a,b){function c(){let m=[],n;do n=d.getUint8(h),m.push((new ...
function Bc (line 620) | function Bc(a,b){const c=b.vm_ip.join("."),d=b.router_ip.join("."),e=163...
function Cc (line 621) | function Cc(){this.state="closed";this.net=null;this.send_buffer=new sc(...
function ec (line 631) | function ec(a,b,c){this.register_ws(a);this.last_stream=1;this.connectio...
function cc (line 640) | function cc(a,b){b=b||{};this.bus=a;this.id=b.id||0;this.router_mac=new ...
function Dc (line 643) | async function Dc(a){this.read=this.read||"";if((this.read+=(new TextDec...
function gc (line 654) | function gc(){this.filedata=new Map}
function hc (line 654) | function hc(a,b){b.endsWith("/")||(b+="/");this.storage=a;this.baseurl=b}
function Z (line 655) | function Z(a,b){this.inodes=[];this.events=[];this.storage=a;this.qidcou...
function Fc (line 663) | function Fc(a){this.direntries=new Map;this.minor=this.major=this.mtime=...
function Ic (line 696) | function Ic(a){this.fs=a;this.backtrack=new Map}
function Gc (line 700) | function Gc(){this.type=2;this.start=0;this.length=Infinity;this.proc_id...
FILE: src/renderer/start-menu.tsx
type StartMenuProps (line 3) | interface StartMenuProps {
class StartMenu (line 7) | class StartMenu extends React.Component<StartMenuProps, {}> {
method constructor (line 8) | constructor(props: StartMenuProps) {
method render (line 14) | public render() {
method navigate (line 36) | private navigate(event: React.SyntheticEvent<HTMLAnchorElement>) {
FILE: src/renderer/utils/get-state-path.ts
function getStatePath (line 6) | async function getStatePath(): Promise<string> {
FILE: src/renderer/utils/reset-state.ts
function resetState (line 4) | async function resetState() {
FILE: src/utils/devmode.ts
function isDevMode (line 6) | function isDevMode() {
FILE: src/utils/disk-image-size.ts
function getDiskImageSize (line 10) | async function getDiskImageSize(path: string) {
FILE: tools/check-links.js
constant LINK_RGX (line 5) | const LINK_RGX = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,...
function main (line 7) | async function main() {
FILE: tools/parcel-build.js
function copyLib (line 7) | async function copyLib() {
function compileParcel (line 31) | async function compileParcel (options = {}) {
FILE: tools/parcel-watch.js
function watchParcel (line 3) | async function watchParcel () {
FILE: tools/resedit.js
function main (line 10) | async function main() {
FILE: tools/run-bin.js
function run (line 6) | async function run (name, bin, args = []) {
FILE: tools/tsc.js
function compileTypeScript (line 5) | async function compileTypeScript () {
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (500K chars).
[
{
"path": ".gitattributes",
"chars": 12,
"preview": "text eol=lf\n"
},
{
"path": ".github/workflows/build.yml",
"chars": 4341,
"preview": "name: Build & Release\n\non:\n push:\n branches:\n - master\n tags:\n - v*\n pull_request:\n\njobs:\n lint:\n "
},
{
"path": ".gitignore",
"chars": 195,
"preview": "node_modules\nout\n.DS_Store\n\n/images*/\n/helper-images/\n\ndist\n!.github/images\n*.code-workspace\n*.pfx\n\nMicrosoft.Trusted.Si"
},
{
"path": "CREDITS.md",
"chars": 2890,
"preview": "# windows95 Credits\n\nThis app was made possible by three major engineering efforts:\n\n * [v86 by Fabian Hemmer](https://g"
},
{
"path": "Dockerfile",
"chars": 1027,
"preview": "# DESCRIPTION:\t Run Windows 95 in a container\n# AUTHOR:\t\t Paul DeCarlo <toolboc@gmail.com>\n#\n# Made possible through"
},
{
"path": "HELP.md",
"chars": 593,
"preview": "# Help & Commonly Asked Questions\n\n## MS-DOS seems to mess up the screen\nHit `Alt + Enter` to make the command screen \"F"
},
{
"path": "LICENSE.md",
"chars": 2597,
"preview": "Copyright 2019 Felix Rieseberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this sof"
},
{
"path": "README.md",
"chars": 5144,
"preview": "# windows95\n\nThis is Windows 95, running in an [Electron](https://electronjs.org/) app. Yes, it's the full thing. I'm so"
},
{
"path": "assets/entitlements.plist",
"chars": 563,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "bios/COPYING.LESSER",
"chars": 7639,
"preview": "\t\t GNU LESSER GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software"
},
{
"path": "docs/docker-instructions.md",
"chars": 1159,
"preview": "# Running windows95 in Docker\n\n## Display using a volume mount of the host X11 Unix Socket (Linux Only):\n\n**Requirements"
},
{
"path": "docs/docker-kubernetes-gitpod.md",
"chars": 311,
"preview": "## Running an online version of windows95\nYou can also run windows95 in Electron, in a virtual X server, in a JavaScript"
},
{
"path": "docs/qemu.md",
"chars": 1032,
"preview": "# QEMU Instructions\n\nThe image built here was made with QEMU. In this doc, I'm keeping instructions\naround.\n\nDisk image"
},
{
"path": "forge.config.js",
"chars": 3496,
"preview": "const path = require('path');\nconst fs = require('fs');\nconst package = require('./package.json');\n\nrequire('dotenv').co"
},
{
"path": "issue_template.md",
"chars": 348,
"preview": "⚠️ Thank you for reporting an issue!\n\nBefore we go any further, understand that I probably won't be able to fullfil feat"
},
{
"path": "package.json",
"chars": 1544,
"preview": "{\n \"name\": \"windows95\",\n \"productName\": \"windows95\",\n \"version\": \"4.0.0\",\n \"description\": \"Windows 95, in an app. I'"
},
{
"path": "patches/@electron+packager+18.3.6.patch",
"chars": 1331,
"preview": "diff --git a/node_modules/@electron/packager/dist/win32.js b/node_modules/@electron/packager/dist/win32.js\nindex 5399b3e"
},
{
"path": "src/cache.ts",
"chars": 633,
"preview": "import { session } from \"electron\";\n\nexport async function clearCaches() {\n await clearCache();\n await clearStorageDat"
},
{
"path": "src/constants.ts",
"chars": 960,
"preview": "import * as path from \"path\";\n\nconst IMAGES_PATH = path.join(__dirname, \"../../images\");\n\nexport const CONSTANTS = {\n I"
},
{
"path": "src/less/emulator.less",
"chars": 366,
"preview": "#emulator {\n height: 100vh;\n width: 100vw;\n display: flex;\n\n > div {\n white-space: pre;\n font: 14px monospace;"
},
{
"path": "src/less/info.less",
"chars": 112,
"preview": "#information {\n text-align: center;\n position: absolute;\n width: 100vw;\n bottom: 50px;\n font-size: 18px;\n}\n"
},
{
"path": "src/less/root.less",
"chars": 1735,
"preview": "@import \"./status.less\";\n@import \"./emulator.less\";\n@import \"./info.less\";\n@import \"./settings.less\";\n@import \"./start.l"
},
{
"path": "src/less/settings.less",
"chars": 313,
"preview": "#floppy-path {\n font-size: .6rem;\n width: 100%;\n height: 30px;\n padding-left: 8px;\n border-color: #000 #fff #fff #0"
},
{
"path": "src/less/start.less",
"chars": 123,
"preview": "#section-start {\n display: flex;\n flex-direction: column;\n\n > small {\n margin-top: 25px;\n font-size: .8rem;\n }"
},
{
"path": "src/less/status.less",
"chars": 328,
"preview": "#status {\n user-select: none;\n position: absolute;\n z-index: 100;\n left: calc(50vw - 110px);\n background: white;\n "
},
{
"path": "src/less/vendor/95css.css",
"chars": 53107,
"preview": "*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webki"
},
{
"path": "src/less/vendor/LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2019 Yoshi Mannaert\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "src/main/about-panel.ts",
"chars": 712,
"preview": "import { AboutPanelOptionsOptions, app } from \"electron\";\n\n/**\n * Sets Fiddle's About panel options on Linux and macOS\n "
},
{
"path": "src/main/fileserver/encoding.ts",
"chars": 404,
"preview": "export function encode(text: string) {\n // Convert to windows-1252 compatible string by removing unsupported chars\n le"
},
{
"path": "src/main/fileserver/fileserver.ts",
"chars": 4370,
"preview": "import { protocol, net } from 'electron';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { generateDirec"
},
{
"path": "src/main/fileserver/hide-files.ts",
"chars": 1537,
"preview": "import { Stats } from \"fs\";\nimport { settings } from \"../settings\";\nimport { FileEntry } from \"./fileserver\";\n\nconst FIL"
},
{
"path": "src/main/fileserver/page-directory-listing.ts",
"chars": 3318,
"preview": "import path from \"path\";\nimport fs from \"fs\";\n\nimport { APP_INTERCEPT, FileEntry, MY_COMPUTER_INTERCEPT } from \"./filese"
},
{
"path": "src/main/fileserver/page-error.ts",
"chars": 699,
"preview": "import { getEncoding } from \"./encoding\";\nimport { MY_COMPUTER_INTERCEPT } from \"./fileserver\";\n\nexport function generat"
},
{
"path": "src/main/ipc.ts",
"chars": 352,
"preview": "import { ipcMain, app } from \"electron\";\nimport * as path from \"path\";\n\nimport { IPC_COMMANDS } from \"../constants\";\n\nex"
},
{
"path": "src/main/logging.ts",
"chars": 132,
"preview": "export function log(message: string, ...args: unknown[]) {\n console.log(`[${new Date().toLocaleString()}] ${message}`, "
},
{
"path": "src/main/main.ts",
"chars": 1706,
"preview": "import { app } from \"electron\";\n\nimport { isDevMode } from \"../utils/devmode\";\nimport { setupAboutPanel } from \"./about-"
},
{
"path": "src/main/menu.ts",
"chars": 6690,
"preview": "import { app, shell, Menu, BrowserWindow, ipcMain, dialog } from \"electron\";\n\nimport { clearCaches } from \"../cache\";\nim"
},
{
"path": "src/main/session.ts",
"chars": 470,
"preview": "import { session } from \"electron\";\n\nexport function setupSession() {\n const s = session.defaultSession;\n\n s.webReques"
},
{
"path": "src/main/settings.ts",
"chars": 1580,
"preview": "import * as fs from 'fs';\nimport * as path from 'path';\nimport { app } from 'electron';\n\nexport interface Settings {\n i"
},
{
"path": "src/main/squirrel.ts",
"chars": 80,
"preview": "export function shouldQuit() {\n return require(\"electron-squirrel-startup\");\n}\n"
},
{
"path": "src/main/update.ts",
"chars": 213,
"preview": "import { app } from \"electron\";\n\nexport function setupUpdates() {\n if (app.isPackaged) {\n require(\"update-electron-a"
},
{
"path": "src/main/windows.ts",
"chars": 903,
"preview": "import { BrowserWindow, shell } from \"electron\";\n\nlet mainWindow: BrowserWindow | null = null;\n\nexport function getOrCre"
},
{
"path": "src/renderer/app.tsx",
"chars": 915,
"preview": "export interface Win95Window extends Window {\n emulator: any;\n win95: {\n app: App;\n };\n}\n\ndeclare let window: Win9"
},
{
"path": "src/renderer/card-settings.tsx",
"chars": 5619,
"preview": "import * as React from \"react\";\n\nimport { resetState } from \"./utils/reset-state\";\n\ninterface CardSettingsProps {\n boot"
},
{
"path": "src/renderer/card-start.tsx",
"chars": 508,
"preview": "import * as React from \"react\";\n\nexport interface CardStartProps {\n startEmulator: () => void;\n}\n\nexport class CardStar"
},
{
"path": "src/renderer/emulator-info.tsx",
"chars": 3884,
"preview": "import * as React from \"react\";\n\ninterface EmulatorInfoProps {\n toggleInfo: () => void;\n emulator: any;\n}\n\ninterface E"
},
{
"path": "src/renderer/emulator.tsx",
"chars": 12738,
"preview": "import * as React from \"react\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { ipcRenderer, shell, web"
},
{
"path": "src/renderer/global.d.ts",
"chars": 50,
"preview": "declare const V86: any;\ndeclare const win95: any;\n"
},
{
"path": "src/renderer/lib/LICENSE.md",
"chars": 1305,
"preview": "Copyright (c) 2012, The v86 contributors\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with o"
},
{
"path": "src/renderer/lib/libv86.js",
"chars": 328208,
"preview": ";(function(){'use strict';function aa(a,b){function c(y){y=y.toString(16);return\"#\"+\"0\".repeat(6-y.length)+y}function d("
},
{
"path": "src/renderer/start-menu.tsx",
"chars": 973,
"preview": "import * as React from \"react\";\n\nexport interface StartMenuProps {\n navigate: (to: string) => void;\n}\n\nexport class Sta"
},
{
"path": "src/renderer/status.tsx",
"chars": 0,
"preview": ""
},
{
"path": "src/renderer/utils/get-state-path.ts",
"chars": 327,
"preview": "import { ipcRenderer } from \"electron\";\nimport { IPC_COMMANDS } from \"../../constants\";\n\nlet _statePath = \"\";\n\nexport as"
},
{
"path": "src/renderer/utils/reset-state.ts",
"chars": 334,
"preview": "import fs from \"fs\";\nimport { getStatePath } from \"./get-state-path\";\n\nexport async function resetState() {\n const stat"
},
{
"path": "src/utils/devmode.ts",
"chars": 145,
"preview": "/**\n * Are we currently running in development mode?\n *\n * @returns {boolean}\n */\nexport function isDevMode() {\n return"
},
{
"path": "src/utils/disk-image-size.ts",
"chars": 421,
"preview": "import * as fs from \"fs\";\n\nimport { CONSTANTS } from \"../constants\";\n\n/**\n * Get the size of the disk image\n *\n * @retur"
},
{
"path": "static/entitlements.plist",
"chars": 332,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "static/index.html",
"chars": 474,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <titl"
},
{
"path": "static/www/apps.htm",
"chars": 776,
"preview": "<html>\n<head>\n <title>windows95 Help</title>\n</head>\n<body bgcolor=\"#C0C0C0\">\n <table width=\"100%\" cellpadding=\"10\" ce"
},
{
"path": "static/www/credits.htm",
"chars": 1128,
"preview": "<html>\n<head>\n <title>Windows 95 Credits</title>\n</head>\n<body bgcolor=\"#C0C0C0\">\n <table width=\"100%\" cellpadding=\"10"
},
{
"path": "static/www/help.htm",
"chars": 1241,
"preview": "<html>\n<head>\n <title>windows95 Help</title>\n</head>\n<body bgcolor=\"#C0C0C0\">\n <table width=\"100%\" cellpadding=\"10\" ce"
},
{
"path": "static/www/home.htm",
"chars": 1390,
"preview": "<html>\n<head>\n <title>Welcome to Windows 95!</title>\n</head>\n<body bgcolor=\"#C0C0C0\">\n <table width=\"100%\" cellpadding"
},
{
"path": "static/www/index.htm",
"chars": 598,
"preview": "<html>\n\n<head>\n <title>Welcome to Windows 95!</title>\n</head>\n\n<frameset cols=\"200,*\" border=\"0\" framespacing=\"0\" frame"
},
{
"path": "static/www/navigation.htm",
"chars": 1858,
"preview": "<html>\n<head>\n <title>Navigation</title>\n</head>\n<body bgcolor=\"#C0C0C0\" background=\"images/bg.gif\">\n <table width=\"10"
},
{
"path": "tools/add-macos-cert.sh",
"chars": 648,
"preview": "#!/usr/bin/env sh\n\nKEY_CHAIN=build.keychain\nMACOS_CERT_P12_FILE=certificate.p12\n\n# Recreate the certificate from the sec"
},
{
"path": "tools/check-links.js",
"chars": 1058,
"preview": "const fs = require('fs/promises')\nconst path = require('path')\nconst fetch = require('node-fetch')\n\nconst LINK_RGX = /(h"
},
{
"path": "tools/download-disk.ps1",
"chars": 241,
"preview": "mkdir images\ncd images\n\n$wc = New-Object System.Net.WebClient\n$wc.DownloadFile($env:DISK_URL, \"$(Resolve-Path .)\\images."
},
{
"path": "tools/download-disk.sh",
"chars": 142,
"preview": "#!/usr/bin/env sh\n\nmkdir -p ./images\ncd ./images\nwget -O images.zip $DISK_URL\nunzip -o images.zip\nrm images.zip\nrm -r __"
},
{
"path": "tools/generateAssets.js",
"chars": 147,
"preview": "/* tslint:disable */\n\nconst { compileParcel } = require('./parcel-build')\n\nmodule.exports = async () => {\n await Promis"
},
{
"path": "tools/parcel-build.js",
"chars": 3716,
"preview": "/* tslint:disable */\n\nconst Bundler = require('parcel-bundler')\nconst path = require('path')\nconst fs = require('fs')\n\na"
},
{
"path": "tools/parcel-watch.js",
"chars": 207,
"preview": "const { compileParcel } = require('./parcel-build')\n\nasync function watchParcel () {\n return compileParcel({ watch: tru"
},
{
"path": "tools/resedit.js",
"chars": 720,
"preview": "const path = require('path');\n\nconst resedit = require('../node_modules/@electron/packager/dist/resedit.js')\nconst packa"
},
{
"path": "tools/run-bin.js",
"chars": 675,
"preview": "/* tslint:disable */\n\nconst childProcess = require('child_process')\nconst path = require('path')\n\nasync function run (na"
},
{
"path": "tools/tsc.js",
"chars": 251,
"preview": "/* tslint:disable */\n\nconst { run } = require('./run-bin')\n\nasync function compileTypeScript () {\n await run('TypeScrip"
},
{
"path": "tsconfig.json",
"chars": 829,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"./dist\",\n \"allowJs\": true,\n \"allowSyntheticDefaultImports\": true,\n \"exp"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the felixrieseberg/windows95 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (476.6 KB), approximately 153.8k tokens, and a symbol index with 243 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.