mirror of
https://github.com/linuxserver/emulatorjs.git
synced 2026-02-23 00:01:26 +08:00
Compare commits
No commits in common. "master" and "1.2.0" have entirely different histories.
16
.github/workflows/call_issue_pr_tracker.yml
vendored
16
.github/workflows/call_issue_pr_tracker.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Issue & PR Tracker
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened,reopened,labeled,unlabeled,closed]
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened,reopened,review_requested,review_request_removed,labeled,unlabeled,closed]
|
|
||||||
pull_request_review:
|
|
||||||
types: [submitted,edited,dismissed]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
manage-project:
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
uses: linuxserver/github-workflows/.github/workflows/issue-pr-tracker.yml@v1
|
|
||||||
secrets: inherit
|
|
||||||
13
.github/workflows/call_issues_cron.yml
vendored
13
.github/workflows/call_issues_cron.yml
vendored
@ -1,13 +0,0 @@
|
|||||||
name: Mark stale issues and pull requests
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '35 15 * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
uses: linuxserver/github-workflows/.github/workflows/issues-cron.yml@v1
|
|
||||||
secrets: inherit
|
|
||||||
14
README.md
14
README.md
@ -1,12 +1,14 @@
|
|||||||
# EmulatorJS helper
|
# EmulatorJS helper
|
||||||
|
|
||||||
The purpose of this application is to provide a self hosted solution for people looking to run Retro games in their web browsers. It consists of a backend application for scanning roms and ingesting art assets with a static frontend application for serving those files via any basic webserver. The backend application is more or less a run once deal, when you have finalized the menus how you like them the resulting static files of the frontend no longer require the backend helper.
|
The purpose of this application is to provide a self hosted solution for people looking to run Retro games in their web browsers. It consists of a backend application for scanning roms and ingesting art assets with a static frontend application for serving those files via any basic webserver. The backend appplication is more or less a run once deal, when you have finalized the menus how you like them the resulting static files of the frontend no longer require the backend helper.
|
||||||
|
|
||||||
This idea was born from a single need, I wanted to run retro games on my Xbox which now includes a modern chromium based web browser with Microsoft Edge. Web based emulators are popular online but always ingesting roms from external sources is a pain not to mention prone to being taken down at any time, also their interfaces are never designed around basic controller input making navigation difficult with something like an Xbox controller.
|
This idea was born from a single need, I wanted to run retro games on my Xbox which now includes a modern chromium based web browser with Microsoft Edge. Web based emulators are popular online but always ingesting roms from external sources is a pain not to mention prone to being taken down at any time, also their interfaces are never designed around basic controller input making navigation difficult with something like an Xbox controller.
|
||||||
|
|
||||||
|
**It is important to note that the current emulator used for this frontend is obfuscated code, efforts are being made to [reverese engineer it](https://github.com/ethanaobrien/emulatorjs/) but you should know it can potentially reach out to third party services if you manually enable features like netplay (this should never happen in a stock setup). We are in the process to transitioning to libretro cores for emulators, currently 23/31 emulators have been replaced.**
|
||||||
|
|
||||||
## For Users
|
## For Users
|
||||||
|
|
||||||
We recommend using the docker container located [here](https://github.com/linuxserver/docker-emulatorjs). This will spin up:
|
We reccomend using the docker container located [here](https://github.com/linuxserver/docker-emulatorjs). This will spin up:
|
||||||
|
|
||||||
* The nodejs backend used for managing your configuration files and rom art assets.
|
* The nodejs backend used for managing your configuration files and rom art assets.
|
||||||
* An NGINX webserver to serve the static frontend files.
|
* An NGINX webserver to serve the static frontend files.
|
||||||
@ -27,7 +29,7 @@ All images should be a png and run through pngquant for web optimization IE:
|
|||||||
pngquant yourimage.png
|
pngquant yourimage.png
|
||||||
```
|
```
|
||||||
|
|
||||||
Videos should be mp4 format and compressed pretty heavily to be optimized for web while load quickly in the web interface, IE using ffmpeg: (aac is also acceptable, but I had better results with mp3)
|
Videos should be mp4 format and compressed pretty heavily to be optimized for web while load quickly in the web interface, IE using ffmpeg: (aac is also acceptible, but I had better results with mp3)
|
||||||
|
|
||||||
```
|
```
|
||||||
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 32 -acodec libmp3lame -qscale:a 7 output.mp4
|
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 32 -acodec libmp3lame -qscale:a 7 output.mp4
|
||||||
@ -57,10 +59,6 @@ While the `video_position` variable is used to place the video in the correct lo
|
|||||||
|
|
||||||
All of the metadata entries are key based off public lists of rom/cd dumps from [https://no-intro.org/](https://no-intro.org/) and [http://redump.org/](http://redump.org/) using the sha1 of the files. This way when a user is scanning in their roms we can easily link them to the correct art assets for the menu entries.
|
All of the metadata entries are key based off public lists of rom/cd dumps from [https://no-intro.org/](https://no-intro.org/) and [http://redump.org/](http://redump.org/) using the sha1 of the files. This way when a user is scanning in their roms we can easily link them to the correct art assets for the menu entries.
|
||||||
|
|
||||||
## For websites
|
|
||||||
|
|
||||||
The libretro cores that are tested and functional are published as an easy to embed library [here](https://github.com/linuxserver/libretrojs) .
|
|
||||||
|
|
||||||
## Developing and testing assets
|
## Developing and testing assets
|
||||||
|
|
||||||
### Testing assets
|
### Testing assets
|
||||||
@ -145,4 +143,4 @@ The application can be accesed at:
|
|||||||
* Backend - http://localhost:3000
|
* Backend - http://localhost:3000
|
||||||
* Frontend - http://localhost:3000/frontend/index.html
|
* Frontend - http://localhost:3000/frontend/index.html
|
||||||
|
|
||||||
The application is written in jQuery and scanning is backed by a simple bash helper. The idea is to keep it as simple as possible, people will not be spending hours in the backend interface. The frontend is all designed around converting json configs, images, videos, and roms into a useable web based emulator.
|
The application is written in jqueary and scanning is backed by a simple bash helper. The idea is to keep it as simple as possible, people will not be spending hours in the backend interface. The frontend is all designed around converting json configs, images, videos, and roms into a useable web based emulator.
|
||||||
|
|||||||
@ -1,70 +0,0 @@
|
|||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
float: right;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.directory, .file {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border: 2px solid #000;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteButton {
|
|
||||||
margin: 0px !important;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileTable {
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
border: 2px solid #ddd;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(even){
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover, button:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dropzone {
|
|
||||||
position: fixed; top: 0; left: 0;
|
|
||||||
z-index: 9999999999;
|
|
||||||
width: 100%; height: 100%;
|
|
||||||
background-color: rgba(0,0,0,0.5);
|
|
||||||
transition: visibility 175ms, opacity 175ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading {
|
|
||||||
display: inline-block;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 3px solid rgba(0,0,0,.3);
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top-color: black;
|
|
||||||
animation: spin 1s ease-in-out infinite;
|
|
||||||
-webkit-animation: spin 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { -webkit-transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
@-webkit-keyframes spin {
|
|
||||||
to { -webkit-transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
|
||||||
<svg enable-background="new 0 0 60 60" version="1.1" viewBox="0 0 60 60" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m59.188 41.363-1.444-0.277c-0.677-0.13-1.21-0.573-1.462-1.216-0.253-0.642-0.163-1.329 0.245-1.885l0.907-1.236c0.292-0.398 0.25-0.95-0.1-1.299l-1.959-1.958c-0.339-0.339-0.872-0.389-1.268-0.121l-1.217 0.824c-0.331 0.224-0.71 0.343-1.099 0.343-0.844 0-1.787-0.576-1.956-1.676l-0.233-1.515c-0.075-0.487-0.495-0.848-0.988-0.848h-2.77c-0.479 0-0.892 0.341-0.982 0.812l-0.358 1.865c-0.201 1.051-1.13 1.601-1.942 1.601-0.389 0-0.769-0.118-1.099-0.342l-1.572-1.064c-0.397-0.268-0.929-0.218-1.268 0.121l-1.959 1.958c-0.35 0.349-0.392 0.9-0.1 1.299l0.907 1.236c0.408 0.556 0.498 1.243 0.245 1.885-0.252 0.643-0.785 1.086-1.462 1.216l-1.444 0.277c-0.469 0.091-0.81 0.503-0.81 0.983v2.77c0 0.493 0.36 0.913 0.848 0.988l1.515 0.233c0.683 0.105 1.232 0.528 1.508 1.16 0.276 0.633 0.212 1.323-0.175 1.895l-0.824 1.217c-0.269 0.396-0.218 0.929 0.121 1.268l1.958 1.959c0.348 0.349 0.899 0.391 1.299 0.1l1.236-0.907c0.342-0.252 0.742-0.385 1.156-0.385 0.813 0 1.742 0.551 1.944 1.602l0.277 1.444c0.091 0.471 0.503 0.812 0.982 0.812h2.77c0.493 0 0.913-0.36 0.988-0.848l0.164-1.065c0.169-1.1 1.113-1.676 1.958-1.676 0.413 0 0.813 0.134 1.156 0.386l0.869 0.638c0.4 0.292 0.951 0.25 1.299-0.1l1.958-1.959c0.339-0.339 0.39-0.871 0.121-1.268l-0.824-1.217c-0.387-0.571-0.451-1.262-0.175-1.895 0.275-0.632 0.825-1.055 1.508-1.16l1.515-0.233c0.488-0.077 0.848-0.497 0.848-0.99v-2.77c0-0.479-0.341-0.891-0.812-0.982zm-1.188 2.895-0.667 0.103c-1.354 0.208-2.489 1.082-3.036 2.337-0.548 1.256-0.416 2.682 0.352 3.815l0.361 0.533-0.768 0.768-0.177-0.13c-0.689-0.505-1.498-0.772-2.34-0.772-1.979 0-3.635 1.418-3.935 3.371l-0.032 0.217h-1.086l-0.121-0.633c-0.359-1.868-2.004-3.225-3.909-3.225-0.843 0-1.651 0.268-2.34 0.771l-0.544 0.399-0.768-0.768 0.361-0.533c0.768-1.134 0.899-2.56 0.352-3.815s-1.683-2.129-3.036-2.337l-0.667-0.101v-1.086l0.633-0.121c1.346-0.259 2.447-1.175 2.947-2.449 0.5-1.275 0.315-2.695-0.494-3.8l-0.399-0.544 0.768-0.768 0.889 0.602c0.662 0.448 1.43 0.686 2.22 0.686 1.905 0 3.548-1.355 3.907-3.224l0.202-1.054h1.086l0.103 0.667c0.301 1.953 1.955 3.371 3.934 3.371 0.789 0 1.556-0.237 2.219-0.687l0.533-0.361 0.768 0.768-0.399 0.544c-0.81 1.104-0.994 2.524-0.494 3.8 0.5 1.274 1.602 2.19 2.947 2.449l0.63 0.121v1.086z"/><path d="m47 38.5c-2.757 0-5 2.243-5 5s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5zm0 8c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3z"/><path d="m26.515 10.5-5-7h-21.515v53h37.372l-4.253-4.256c-0.339-0.339-0.39-0.871-0.121-1.268l1.303-1.923-2.453-0.377c-0.488-0.074-0.848-0.494-0.848-0.988v-7.821c0-0.479 0.341-0.892 0.812-0.982l2.28-0.438-1.469-2c-0.292-0.398-0.25-0.95 0.1-1.299l5.532-5.529c0.34-0.339 0.87-0.389 1.268-0.121l2.338 1.583 0.054-0.315 0.471-2.454c0.091-0.471 0.503-0.812 0.982-0.812h7.821c0.493 0 0.913 0.36 0.988 0.848l0.378 2.453 1.922-1.303c0.396-0.268 0.929-0.218 1.268 0.121l4.255 4.253v-23.372h-33.485zm31.485 7h-26.485l-3.571-5h30.056v5z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB |
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?><svg enable-background="new 0 0 512 512" version="1.1" viewBox="0 0 512 512" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M0,0v512h512V0H0z M477.867,477.867H34.133V34.133h443.733V477.867z"/><polygon points="126.53 102.4 199.11 102.4 199.11 68.267 68.267 68.267 68.267 199.11 102.4 199.11 102.4 126.54 198.42 222.56 222.56 198.42"/><polygon points="222.56 313.58 198.42 289.44 102.4 385.47 102.4 312.89 68.267 312.89 68.267 443.73 199.11 443.73 199.11 409.6 126.54 409.6"/><polygon points="409.6 312.89 409.6 385.47 313.58 289.44 289.44 313.58 385.46 409.6 312.89 409.6 312.89 443.73 443.73 443.73 443.73 312.89"/><polygon points="312.89 68.267 312.89 102.4 385.47 102.4 289.44 198.42 313.58 222.56 409.6 126.54 409.6 199.11 443.73 199.11 443.73 68.267"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 820 B |
@ -69,40 +69,15 @@ canvas {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#startEmu {
|
.full-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
bottom: 0;
|
||||||
left: 50%;
|
right: 0;
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
color: white;
|
color: white;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 5vw;
|
|
||||||
border: 3px solid;
|
|
||||||
border-radius: 1vw;
|
|
||||||
padding: 1vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 1vh;
|
|
||||||
left: 1vw;
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
border: none;
|
||||||
width: 1.7vw;
|
font-size: 3vw;
|
||||||
z-index: 999999;
|
|
||||||
filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(82deg) brightness(105%) contrast(105%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-main {
|
|
||||||
position: absolute;
|
|
||||||
top: 1vh;
|
|
||||||
left: 4vw;
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
width: 1.7vw;
|
|
||||||
z-index: 999999;
|
|
||||||
filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(82deg) brightness(105%) contrast(105%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes growonce {
|
@keyframes growonce {
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="css/filebrowser.css">
|
|
||||||
<link rel="stylesheet" href="css/vendor/jquery.modal.min.css">
|
|
||||||
<script type="text/javascript" src="js/vendor/jquery.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/vendor/jquery.modal.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/vendor/browserfs.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/vendor/jszip.min.js"></script>
|
|
||||||
<script type="text/javascript" src="js/filebrowser.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="popup" class="modal">
|
|
||||||
<div id="popupContents"></div>
|
|
||||||
</div>
|
|
||||||
<div class="hidden" id="profile">
|
|
||||||
<span id=profileButtons">
|
|
||||||
<span id="username"></span>
|
|
||||||
<span id="loginEntry">
|
|
||||||
<input id="user" type="text" placeholder="user"></input>
|
|
||||||
<input id="pass" type="password" placeholder="pass"></input>
|
|
||||||
<button id="login" onclick="login()">Login</button>
|
|
||||||
</span>
|
|
||||||
<button id="logout" class="hidden" onclick="logout()">Logout</button>
|
|
||||||
<span id="syncButtons" class="hidden">
|
|
||||||
<button class="right" onclick="pushProfile()">↑ Push to Server</button>
|
|
||||||
<button class="right" onclick="pullProfile()">↓ Pull from Server</button>
|
|
||||||
</span>
|
|
||||||
<button id="defaultPull" class="right" onclick="defaultProfile()">Pull Default Config</button>
|
|
||||||
</span>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="hidden" id="touchpad">
|
|
||||||
<p>TouchScreen control type: <select name="touch" id="touch">
|
|
||||||
<option value="auto">Auto</option>
|
|
||||||
<option value="false">Disabled</option>
|
|
||||||
<option value="default">Default</option>
|
|
||||||
<option value="simple">Simple</option>
|
|
||||||
<option value="modern">Modern</option>
|
|
||||||
</select>
|
|
||||||
<button onclick="touchSave()">Save</button>
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<p>File browser:</p>
|
|
||||||
<span id="buttons">
|
|
||||||
<input id="folderName" type="text" placeholder="Enter Directory Name"></input>
|
|
||||||
<button onclick="createFolder()">Create Folder</button>
|
|
||||||
<button onclick="$('#uploadInput').trigger( 'click' )">Upload Files</button>
|
|
||||||
<input class="hidden" id="uploadInput" type='file' onchange="upload(this);" multiple>
|
|
||||||
<button class="right" onclick="downloadBackup()">DL Full Backup</button>
|
|
||||||
<button class="right" onclick="$('#uploadBackup').trigger( 'click' )">Upload Full Backup</button>
|
|
||||||
<input class="hidden" id="uploadBackup" type='file' onchange="uploadBackup(this);" />
|
|
||||||
</span>
|
|
||||||
<div id="filebrowser"></div>
|
|
||||||
<div ondrop="dropFiles(event)" ondragover="allowDrop(event)" style="visibility:hidden;opacity:0" id="dropzone">
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
@ -1,22 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
||||||
<link rel="apple-touch-icon" href="icon.png" crossorigin="use-credentials">
|
|
||||||
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
|
|
||||||
<link rel="stylesheet" href="css/index.css">
|
<link rel="stylesheet" href="css/index.css">
|
||||||
<script src="js/vendor/jquery.min.js"></script>
|
<script src="js/vendor/jquery.min.js"></script>
|
||||||
|
<script src="js/vendor/jquery.hotkeys.js"></script>
|
||||||
|
<script src="js/vendor/browserfs.min.js"></script>
|
||||||
<script src="js/vendor/hammer.min.js"></script>
|
<script src="js/vendor/hammer.min.js"></script>
|
||||||
<script src="js/index.js"></script>
|
<script src="js/index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<a target="_blank" href="filebrowser.html">
|
|
||||||
<img class="file-button" src="css/folder.svg"/>
|
|
||||||
</a>
|
|
||||||
<img class="full-main" onclick="fullscreen()" src="css/fullscreen.svg"/>
|
|
||||||
<video autoplay muted loop id="bgvid">
|
<video autoplay muted loop id="bgvid">
|
||||||
<source id="vid" type="video/mp4">
|
<source id="vid" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
|
|||||||
@ -1,585 +0,0 @@
|
|||||||
//Default vars
|
|
||||||
var endPoint = 'profile'
|
|
||||||
var storeName = 'RetroArch';
|
|
||||||
var afs;
|
|
||||||
BrowserFS.install(window);
|
|
||||||
var fs = require('fs');
|
|
||||||
var mfs = new BrowserFS.FileSystem.MountableFileSystem();
|
|
||||||
var postSettings = {method:'POST',headers:{Accept:'application/json','Content-Type':'application/json'}};
|
|
||||||
|
|
||||||
// Render file list
|
|
||||||
async function renderFiles(directory) {
|
|
||||||
directory = directory.replace("//","/");
|
|
||||||
directory = directory.replace("|","'");
|
|
||||||
let directoryClean = directory.replace("'","|");
|
|
||||||
if ((directory !== '/') && (directory.endsWith('/'))) {
|
|
||||||
directory = directory.slice(0, -1);
|
|
||||||
}
|
|
||||||
$('#filebrowser').empty();
|
|
||||||
$('#filebrowser').data('directory', directory);
|
|
||||||
$('#filebrowser').append($('<div>').text(directory));
|
|
||||||
let items = await fs.readdirSync(directory);
|
|
||||||
let baseName = directory.split('/').slice(-1)[0];
|
|
||||||
let parentFolder = directory.replace(baseName,'');
|
|
||||||
let parentLink = $('<td>').addClass('directory').attr('onclick', 'renderFiles(\'' + parentFolder + '\');').text('..');
|
|
||||||
if (directoryClean == '/') {
|
|
||||||
directoryClean = '';
|
|
||||||
}
|
|
||||||
let table = $('<table>').addClass('fileTable');
|
|
||||||
let tableHeader = $('<tr>');
|
|
||||||
for await (name of ['Name', 'Type', 'Delete (NO WARNING)']) {
|
|
||||||
tableHeader.append($('<th>').text(name));
|
|
||||||
}
|
|
||||||
let parentRow = $('<tr>');
|
|
||||||
for await (item of [parentLink, $('<td>').text('Parent'), $('<td>')]) {
|
|
||||||
parentRow.append(item);
|
|
||||||
}
|
|
||||||
table.append(tableHeader,parentRow);
|
|
||||||
$('#filebrowser').append(table);
|
|
||||||
items.sort();
|
|
||||||
if (items.length > 0) {
|
|
||||||
let dirs = [];
|
|
||||||
let files = [];
|
|
||||||
for await (let item of items) {
|
|
||||||
if (fs.lstatSync(directory + '/' + item).isDirectory()) {
|
|
||||||
dirs.push(item)
|
|
||||||
} else {
|
|
||||||
files.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dirs.length > 0) {
|
|
||||||
for await (let dir of dirs) {
|
|
||||||
let tableRow = $('<tr>');
|
|
||||||
let dirClean = dir.replace("'","|");
|
|
||||||
let link = $('<td>').addClass('directory').attr('onclick', 'renderFiles(\'' + directoryClean + '/' + dirClean + '\');').text(dir);
|
|
||||||
let type = $('<td>').text('Dir');
|
|
||||||
let del = $('<td>').append($('<button>').addClass('deleteButton').attr('onclick', 'deleter(\'' + directoryClean + '/' + dirClean + '\');').text('Delete'));
|
|
||||||
for await (item of [link, type, del]) {
|
|
||||||
tableRow.append(item);
|
|
||||||
}
|
|
||||||
table.append(tableRow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (files.length > 0) {
|
|
||||||
for await (let file of files) {
|
|
||||||
let tableRow = $('<tr>');
|
|
||||||
let fileClean = file.replace("'","|");
|
|
||||||
let link = $('<td>').addClass('file').attr('onclick', 'downloadFile(\'' + directoryClean + '/' + fileClean + '\');').text(file);
|
|
||||||
let type = $('<td>').text('File');
|
|
||||||
let del = $('<td>').append($('<button>').addClass('deleteButton').attr('onclick', 'deleter(\'' + directoryClean + '/' + fileClean + '\');').text('Delete'));
|
|
||||||
for await (item of [link, type, del]) {
|
|
||||||
tableRow.append(item);
|
|
||||||
}
|
|
||||||
table.append(tableRow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download file when clicked
|
|
||||||
async function downloadFile(file) {
|
|
||||||
file = file.replace("|","'");
|
|
||||||
let fileName = file.split('/').slice(-1)[0];
|
|
||||||
let data = fs.readFileSync(file);
|
|
||||||
let blob = new Blob([data], { type: "application/octetstream" });
|
|
||||||
let url = window.URL || window.webkitURL;
|
|
||||||
link = url.createObjectURL(blob);
|
|
||||||
let a = $("<a />");
|
|
||||||
a.attr("download", fileName);
|
|
||||||
a.attr("href", link);
|
|
||||||
$("body").append(a);
|
|
||||||
a[0].click();
|
|
||||||
$("body").remove(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload file to current directory
|
|
||||||
async function upload(input) {
|
|
||||||
let directory = $('#filebrowser').data('directory');
|
|
||||||
if (directory == '/') {
|
|
||||||
directoryUp = '';
|
|
||||||
} else {
|
|
||||||
directoryUp = directory;
|
|
||||||
}
|
|
||||||
if (input.files && input.files[0]) {
|
|
||||||
for await (let file of input.files) {
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
let fileName = file.name;
|
|
||||||
let data = e.target.result;
|
|
||||||
fs.writeFileSync(directoryUp + '/' + fileName, Buffer.from(data));
|
|
||||||
if (file == input.files[input.files.length - 1]) {
|
|
||||||
renderFiles(directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a directory
|
|
||||||
async function createFolder() {
|
|
||||||
let folderName = $('#folderName').val();
|
|
||||||
$('#folderName').val('');
|
|
||||||
if ((folderName.length == 0) || (folderName.includes('/'))) {
|
|
||||||
alert('Bad or Null Directory Name');
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
let directory = $('#filebrowser').data('directory');
|
|
||||||
if (directory == '/') {
|
|
||||||
directoryUp = '';
|
|
||||||
} else {
|
|
||||||
directoryUp = directory;
|
|
||||||
}
|
|
||||||
let createD = directoryUp + '/' + folderName;
|
|
||||||
if (!fs.existsSync(createD)){
|
|
||||||
fs.mkdirSync(createD);
|
|
||||||
}
|
|
||||||
renderFiles(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle drag and drop
|
|
||||||
async function dropFiles(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
let directory = $('#filebrowser').data('directory');
|
|
||||||
if (directory == '/') {
|
|
||||||
directoryUp = '';
|
|
||||||
} else {
|
|
||||||
directoryUp = directory;
|
|
||||||
}
|
|
||||||
let items = await getAllFileEntries(event.dataTransfer.items);
|
|
||||||
for await (let item of items) {
|
|
||||||
let fullPath = item.fullPath;
|
|
||||||
let dirPath = fullPath.split('/');
|
|
||||||
// Check if directories need to be created
|
|
||||||
if (dirPath.length > 2) {
|
|
||||||
var startDir = directoryUp;
|
|
||||||
dirPath.splice(dirPath.length - 1);
|
|
||||||
dirPath.shift();
|
|
||||||
for await (let dir of dirPath) {
|
|
||||||
startDir = startDir + '/' + dir;
|
|
||||||
if (!fs.existsSync(startDir)){
|
|
||||||
fs.mkdirSync(startDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write file
|
|
||||||
item.file(async function(file) {
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
let data = e.target.result;
|
|
||||||
fs.writeFileSync(directoryUp + '/' + fullPath, Buffer.from(data));
|
|
||||||
if (item == items[items.length - 1]) {
|
|
||||||
$('#dropzone').css({'visibility':'hidden','opacity':0});
|
|
||||||
renderFiles(directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Drop handler function to get all files
|
|
||||||
async function getAllFileEntries(dataTransferItemList) {
|
|
||||||
let fileEntries = [];
|
|
||||||
// Use BFS to traverse entire directory/file structure
|
|
||||||
let queue = [];
|
|
||||||
// Unfortunately dataTransferItemList is not iterable i.e. no forEach
|
|
||||||
for (let i = 0; i < dataTransferItemList.length; i++) {
|
|
||||||
queue.push(dataTransferItemList[i].webkitGetAsEntry());
|
|
||||||
}
|
|
||||||
while (queue.length > 0) {
|
|
||||||
let entry = queue.shift();
|
|
||||||
if (entry.isFile) {
|
|
||||||
fileEntries.push(entry);
|
|
||||||
} else if (entry.isDirectory) {
|
|
||||||
let reader = entry.createReader();
|
|
||||||
queue.push(...await readAllDirectoryEntries(reader));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileEntries;
|
|
||||||
}
|
|
||||||
// Get all the entries (files or sub-directories) in a directory by calling readEntries until it returns empty array
|
|
||||||
async function readAllDirectoryEntries(directoryReader) {
|
|
||||||
let entries = [];
|
|
||||||
let readEntries = await readEntriesPromise(directoryReader);
|
|
||||||
while (readEntries.length > 0) {
|
|
||||||
entries.push(...readEntries);
|
|
||||||
readEntries = await readEntriesPromise(directoryReader);
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
// Wrap readEntries in a promise to make working with readEntries easier
|
|
||||||
async function readEntriesPromise(directoryReader) {
|
|
||||||
try {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
directoryReader.readEntries(resolve, reject);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastTarget;
|
|
||||||
// Change style when hover files
|
|
||||||
window.addEventListener('dragenter', function(ev) {
|
|
||||||
lastTarget = ev.target;
|
|
||||||
$('#dropzone').css({'visibility':'','opacity':1});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change style when leave hover files
|
|
||||||
window.addEventListener("dragleave", function(ev) {
|
|
||||||
if(ev.target == lastTarget || ev.target == document) {
|
|
||||||
$('#dropzone').css({'visibility':'hidden','opacity':0});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Disabled default drag and drop
|
|
||||||
function allowDrop(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete file or folder
|
|
||||||
async function deleter(item) {
|
|
||||||
let directory = $('#filebrowser').data('directory');
|
|
||||||
item = item.replace("|","'");
|
|
||||||
if (fs.lstatSync(item).isDirectory()) {
|
|
||||||
await rmDir(item);
|
|
||||||
} else {
|
|
||||||
fs.unlinkSync(item);
|
|
||||||
}
|
|
||||||
renderFiles(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download a full backup of all files
|
|
||||||
async function downloadBackup() {
|
|
||||||
let zip = new JSZip();
|
|
||||||
let items = await fs.readdirSync('/');
|
|
||||||
async function addToZip(item) {
|
|
||||||
if (fs.lstatSync(item).isDirectory()) {
|
|
||||||
let items = await fs.readdirSync(item);
|
|
||||||
if (items.length > 0) {
|
|
||||||
for await (let subPath of items) {
|
|
||||||
await addToZip(item + '/' + subPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let data = fs.readFileSync(item);
|
|
||||||
let zipPath = item.replace(/^\//,'');
|
|
||||||
zip.file(zipPath, data);
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
for await (let item of items) {
|
|
||||||
await addToZip(item);
|
|
||||||
}
|
|
||||||
zip.generateAsync({type:"blob"}).then(function callback(blob) {
|
|
||||||
let url = window.URL || window.webkitURL;
|
|
||||||
link = url.createObjectURL(blob);
|
|
||||||
let a = $("<a />");
|
|
||||||
a.attr("download", storeName + '.zip');
|
|
||||||
a.attr("href", link);
|
|
||||||
$("body").append(a);
|
|
||||||
a[0].click();
|
|
||||||
$("body").remove(a);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full delete directory
|
|
||||||
async function rmDir(dirPath) {
|
|
||||||
try {
|
|
||||||
let files = fs.readdirSync(dirPath);
|
|
||||||
if (files.length > 0) {
|
|
||||||
for await (let file of files) {
|
|
||||||
var filePath = dirPath + '/' + file;
|
|
||||||
if (fs.statSync(filePath).isFile()) {
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
} else {
|
|
||||||
await rmDir(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dirPath !== '/') {
|
|
||||||
fs.rmdirSync(dirPath);
|
|
||||||
}
|
|
||||||
if (fs.readdirSync(dirPath).length !== 0) {
|
|
||||||
rmDir(dirPath);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Upload a full backup
|
|
||||||
async function uploadBackup(input) {
|
|
||||||
if (input.files && input.files[0]) {
|
|
||||||
$('#filebrowser').empty();
|
|
||||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = async function(e) {
|
|
||||||
let data = e.target.result;
|
|
||||||
let zip = new JSZip();
|
|
||||||
// Load zip from data
|
|
||||||
zip.loadAsync(data).then(async function(contents) {
|
|
||||||
// Purge current storage
|
|
||||||
await rmDir('/');
|
|
||||||
// Unzip the files to the FS by name
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (fileName.endsWith('/')) {
|
|
||||||
if (! fs.existsSync('/' + fileName)) {
|
|
||||||
fs.mkdirSync('/' + fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (! fileName.endsWith('/')) {
|
|
||||||
zip.file(fileName).async('arraybuffer').then(function(content) {
|
|
||||||
fs.writeFileSync('/' + fileName, Buffer.from(content));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
reader.readAsArrayBuffer(input.files[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render profile
|
|
||||||
async function loadProfile() {
|
|
||||||
let res = await fetch(endPoint);
|
|
||||||
let ping = await res.text();
|
|
||||||
if (ping == 'pong') {
|
|
||||||
$('#profile').removeClass('hidden');
|
|
||||||
}
|
|
||||||
if ((localStorage.getItem('user')) && (localStorage.getItem('pass'))) {
|
|
||||||
loggedIn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login
|
|
||||||
async function login() {
|
|
||||||
try {
|
|
||||||
let user = $('#user').val();
|
|
||||||
let pass = $('#pass').val();
|
|
||||||
$('#user').val('');
|
|
||||||
$('#pass').val('');
|
|
||||||
let loginSettings = postSettings;
|
|
||||||
loginSettings.body = JSON.stringify({user:user,pass:pass,type:'login'});
|
|
||||||
let res = await fetch(endPoint,loginSettings);
|
|
||||||
let json = await res.json();
|
|
||||||
if (json.status == 'success') {
|
|
||||||
localStorage.setItem('user',json.user);
|
|
||||||
localStorage.setItem('pass',pass);
|
|
||||||
loggedIn();
|
|
||||||
} else {
|
|
||||||
alert('Bad login');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout
|
|
||||||
function logout() {
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
localStorage.removeItem('pass');
|
|
||||||
loggedOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render as logged in
|
|
||||||
function loggedIn() {
|
|
||||||
let user = localStorage.getItem('user');
|
|
||||||
$('#loginEntry').addClass('hidden');
|
|
||||||
$('#defaultPull').addClass('hidden');
|
|
||||||
$('#logout').removeClass('hidden');
|
|
||||||
$('#syncButtons').removeClass('hidden');
|
|
||||||
$('#username').text(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render as logged out
|
|
||||||
function loggedOut() {
|
|
||||||
$('#username').empty();
|
|
||||||
$('#syncButtons').addClass('hidden');
|
|
||||||
$('#logout').addClass('hidden');
|
|
||||||
$('#defaultPull').removeClass('hidden');
|
|
||||||
$('#loginEntry').removeClass('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull profile from server
|
|
||||||
async function pullProfile() {
|
|
||||||
$('#filebrowser').empty();
|
|
||||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
|
||||||
let user = localStorage.getItem('user');
|
|
||||||
let pass = localStorage.getItem('pass');
|
|
||||||
let loginSettings = postSettings;
|
|
||||||
loginSettings.body = JSON.stringify({user:user,pass:pass,type:'pull'});
|
|
||||||
let res = await fetch(endPoint,loginSettings);
|
|
||||||
let json = await res.json();
|
|
||||||
if (json.status == 'success') {
|
|
||||||
let zip = new JSZip();
|
|
||||||
// Purge current storage
|
|
||||||
await rmDir('/');
|
|
||||||
let baseData = json.data;
|
|
||||||
// Load zip from data
|
|
||||||
zip.loadAsync(baseData, {base64: true}).then(async function(contents) {
|
|
||||||
// Unzip the files to the FS by name
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (fileName.endsWith('/')) {
|
|
||||||
if (! fs.existsSync('/' + fileName)) {
|
|
||||||
fs.mkdirSync('/' + fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (! fileName.endsWith('/')) {
|
|
||||||
zip.file(fileName).async('arraybuffer').then(function(content) {
|
|
||||||
fs.writeFileSync('/' + fileName, Buffer.from(content));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert('Error pulling profile');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function defaultProfile() {
|
|
||||||
$('#filebrowser').empty();
|
|
||||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
|
||||||
let loginSettings = postSettings;
|
|
||||||
loginSettings.body = JSON.stringify({type:'default'});
|
|
||||||
let res = await fetch(endPoint,loginSettings);
|
|
||||||
let json = await res.json();
|
|
||||||
if (json.status == 'success') {
|
|
||||||
let zip = new JSZip();
|
|
||||||
// Purge current storage
|
|
||||||
await rmDir('/');
|
|
||||||
let baseData = json.data;
|
|
||||||
// Load zip from data
|
|
||||||
zip.loadAsync(baseData, {base64: true}).then(async function(contents) {
|
|
||||||
// Unzip the files to the FS by name
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (fileName.endsWith('/')) {
|
|
||||||
if (! fs.existsSync('/' + fileName)) {
|
|
||||||
fs.mkdirSync('/' + fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (! fileName.endsWith('/')) {
|
|
||||||
zip.file(fileName).async('arraybuffer').then(function(content) {
|
|
||||||
fs.writeFileSync('/' + fileName, Buffer.from(content));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert('Error pulling profile');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push profile to server
|
|
||||||
async function pushProfile() {
|
|
||||||
$('#filebrowser').empty();
|
|
||||||
$('#filebrowser').append($('<div>').attr('id','loading'));
|
|
||||||
let zip = new JSZip();
|
|
||||||
let items = await fs.readdirSync('/');
|
|
||||||
async function addToZip(item) {
|
|
||||||
if (fs.lstatSync(item).isDirectory()) {
|
|
||||||
let items = await fs.readdirSync(item);
|
|
||||||
if (items.length > 0) {
|
|
||||||
for await (let subPath of items) {
|
|
||||||
await addToZip(item + '/' + subPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let data = fs.readFileSync(item);
|
|
||||||
let zipPath = item.replace(/^\//,'');
|
|
||||||
zip.file(zipPath, data);
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
for await (let item of items) {
|
|
||||||
await addToZip(item);
|
|
||||||
}
|
|
||||||
zip.generateAsync({type:"base64"}).then(async function callback(base64) {
|
|
||||||
try {
|
|
||||||
let user = localStorage.getItem('user');
|
|
||||||
let pass = localStorage.getItem('pass');
|
|
||||||
let loginSettings = postSettings;
|
|
||||||
loginSettings.body = JSON.stringify({user:user,pass:pass,type:'push',data:base64});
|
|
||||||
let res = await fetch(endPoint,loginSettings);
|
|
||||||
let json = await res.json();
|
|
||||||
if (json.status == 'success') {
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Error uploading profile');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
alert('Error uploading profile');
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load and show touch input options on touch devices
|
|
||||||
function loadTouchInput() {
|
|
||||||
if (window.orientation !== undefined) {
|
|
||||||
$('#touchpad').removeClass('hidden');
|
|
||||||
if (localStorage.getItem('touchpad') !== null) {
|
|
||||||
if (localStorage.getItem('touchpad') == 'false') {
|
|
||||||
$('#touch').val('false');
|
|
||||||
} else if (localStorage.getItem('touchpad') == 'default') {
|
|
||||||
$('#touch').val('default');
|
|
||||||
} else if (localStorage.getItem('touchpad') == 'simple') {
|
|
||||||
$('#touch').val('simple');
|
|
||||||
} else if (localStorage.getItem('touchpad') == 'modern') {
|
|
||||||
$('#touch').val('modern');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Save touch input option
|
|
||||||
function touchSave() {
|
|
||||||
let option = $('#touch').val();
|
|
||||||
if (option == 'auto') {
|
|
||||||
localStorage.removeItem('touchpad');
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('touchpad',option);
|
|
||||||
}
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Async filestore
|
|
||||||
async function setupFileSystem() {
|
|
||||||
var imfs = new BrowserFS.FileSystem.InMemory();
|
|
||||||
afs = new BrowserFS.FileSystem.AsyncMirror(imfs,
|
|
||||||
new BrowserFS.FileSystem.IndexedDB(async function(e, fs) {
|
|
||||||
afs.initialize(async function(e) {
|
|
||||||
console.log('IndexedDB setup successful');
|
|
||||||
setupMounts();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
storeName));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup mounts
|
|
||||||
async function setupMounts() {
|
|
||||||
mfs.mount('/', afs);
|
|
||||||
BrowserFS.initialize(mfs);
|
|
||||||
renderFiles('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// On page load
|
|
||||||
window.onload = function() {
|
|
||||||
setupFileSystem();
|
|
||||||
loadProfile();
|
|
||||||
loadTouchInput();
|
|
||||||
}
|
|
||||||
@ -13,12 +13,6 @@ var defaultKeys = [
|
|||||||
'has_video',
|
'has_video',
|
||||||
'multi_disc'
|
'multi_disc'
|
||||||
];
|
];
|
||||||
localStorage.setItem('retroArch',true);
|
|
||||||
var gamePadType;
|
|
||||||
var isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 &&
|
|
||||||
navigator.userAgent &&
|
|
||||||
navigator.userAgent.indexOf('CriOS') == -1 &&
|
|
||||||
navigator.userAgent.indexOf('FxiOS') == -1;
|
|
||||||
|
|
||||||
//// Helper functions ////
|
//// Helper functions ////
|
||||||
// Debounce calls to functions that run heavy
|
// Debounce calls to functions that run heavy
|
||||||
@ -131,7 +125,7 @@ function launch(active_item) {
|
|||||||
config.items['Disc ' + count.toString()].has_video = false;
|
config.items['Disc ' + count.toString()].has_video = false;
|
||||||
config.items['Disc ' + count.toString()].multi_disc = 0;
|
config.items['Disc ' + count.toString()].multi_disc = 0;
|
||||||
};
|
};
|
||||||
rendermenu([config, 0]);
|
rendermenu(config, 0);
|
||||||
} else if (type == 'game') {
|
} else if (type == 'game') {
|
||||||
// Disable keyevents and hash watching
|
// Disable keyevents and hash watching
|
||||||
window.exit = false;
|
window.exit = false;
|
||||||
@ -144,24 +138,13 @@ function launch(active_item) {
|
|||||||
if (emulator.startsWith('libretro-')) {
|
if (emulator.startsWith('libretro-')) {
|
||||||
var emulator = emulator.replace('libretro-','');
|
var emulator = emulator.replace('libretro-','');
|
||||||
var script = 'js/libretro.js'
|
var script = 'js/libretro.js'
|
||||||
var EJSemu = false;
|
|
||||||
EJS_onGameStart = function() {
|
EJS_onGameStart = function() {
|
||||||
gameStarted = true;
|
Module.requestFullscreen(false);
|
||||||
let gps = navigator.getGamepads();
|
|
||||||
if (gps) {
|
|
||||||
for (let gp of gps) {
|
|
||||||
if (gp) {
|
|
||||||
let gpEvt = new GamepadEvent("gamepadconnected",{gamepad: gp});
|
|
||||||
window.dispatchEvent(gpEvt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var script = 'data/loader.js'
|
var script = 'data/loader.js'
|
||||||
var EJSemu = true;
|
var EJSemu = true;
|
||||||
EJS_onGameStart = function() {
|
EJS_onGameStart = function() {
|
||||||
gameStarted = true;
|
|
||||||
document.querySelectorAll('[data-btn="fullscreen"]')[0].click();
|
document.querySelectorAll('[data-btn="fullscreen"]')[0].click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -179,33 +162,9 @@ function launch(active_item) {
|
|||||||
EJS_biosUrl = bios;
|
EJS_biosUrl = bios;
|
||||||
}
|
}
|
||||||
EJS_player = '#game';
|
EJS_player = '#game';
|
||||||
EJS_gameUrl = encodeURI(rom_path + name + rom_extension);
|
EJS_gameUrl = rom_path + name + rom_extension;
|
||||||
EJS_core = emulator;
|
EJS_core = emulator;
|
||||||
EJS_pathtodata = 'data/';
|
EJS_pathtodata = 'data/';
|
||||||
// Load touch screen interface
|
|
||||||
if ((! EJSemu) && (window.orientation !== undefined) && localStorage.getItem('touchpad') !== 'false' && !navigator.getGamepads()?.[0]) {
|
|
||||||
// Determine type to render
|
|
||||||
if (localStorage.getItem('touchpad') !== null) {
|
|
||||||
if (localStorage.getItem('touchpad') == 'simple') {
|
|
||||||
gamePadType = 'simple';
|
|
||||||
} else if (localStorage.getItem('touchpad') == 'modern') {
|
|
||||||
gamePadType = 'modern';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((emulator == 'gearboy') || (emulator == 'fceumm') || (emulator == 'mednafen_vb') || (emulator == 'gambatte') || (emulator == 'stella2014') || (emulator == 'prosystem') || (emulator == 'mednafen_pce_fast')) {
|
|
||||||
gamePadType = 'simple';
|
|
||||||
} else if ((emulator == 'prboom') || (emulator == 'mednafen_psx') || (emulator == 'tyrquake') || (emulator == 'melonds') || (emulator == 'melonds_threaded')) {
|
|
||||||
gamePadType = 'modern';
|
|
||||||
} else if (emulator == 'mupen64plus_next') {
|
|
||||||
gamePadType = 'n64';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var touchDiv = $('<div>').attr('id','gamepad');
|
|
||||||
$('body').append(touchDiv);
|
|
||||||
var touchscript = document.createElement('script');
|
|
||||||
touchscript.src = 'js/touchpad.js';
|
|
||||||
document.head.append(touchscript);
|
|
||||||
}
|
|
||||||
// Load in EJS loader
|
// Load in EJS loader
|
||||||
var loaderscript = document.createElement('script');
|
var loaderscript = document.createElement('script');
|
||||||
loaderscript.src = script;
|
loaderscript.src = script;
|
||||||
@ -242,9 +201,7 @@ function launch(active_item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//// Page rendering logic ////
|
//// Page rendering logic ////
|
||||||
async function rendermenu(datas) {
|
async function rendermenu(data, active_item) {
|
||||||
var data = datas[0];
|
|
||||||
var active_item = datas[1];
|
|
||||||
// Set default variables
|
// Set default variables
|
||||||
var portrait = window.orientation;
|
var portrait = window.orientation;
|
||||||
$('#menu').data('config', data);
|
$('#menu').data('config', data);
|
||||||
@ -252,10 +209,6 @@ async function rendermenu(datas) {
|
|||||||
$('#menu').data('root', root);
|
$('#menu').data('root', root);
|
||||||
var parent = data.parent;
|
var parent = data.parent;
|
||||||
var items = data.items;
|
var items = data.items;
|
||||||
if (Object.keys(items).length == 0) {
|
|
||||||
alert('No items to load, please add some games');
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
for await (var name of Object.keys(items)) {
|
for await (var name of Object.keys(items)) {
|
||||||
var item = data.items[name];
|
var item = data.items[name];
|
||||||
if ((item.hasOwnProperty('cloneof')) && (items.hasOwnProperty(item.cloneof))) {
|
if ((item.hasOwnProperty('cloneof')) && (items.hasOwnProperty(item.cloneof))) {
|
||||||
@ -291,22 +244,7 @@ async function rendermenu(datas) {
|
|||||||
};
|
};
|
||||||
// Loop items loaded from json to build the game menu divs
|
// Loop items loaded from json to build the game menu divs
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var jumpIndex = {};
|
|
||||||
var letters = "abcdefghijklmnopqrstuvwxyz".split("");
|
|
||||||
for await (var name of Object.keys(items)) {
|
for await (var name of Object.keys(items)) {
|
||||||
// Generate an index table based on alphabetical order ignoring numbers
|
|
||||||
if (count == 0) {
|
|
||||||
jumpIndex['0'] = count;
|
|
||||||
} else {
|
|
||||||
letterLoop:
|
|
||||||
for (let letter of letters) {
|
|
||||||
let startLetter = name.charAt(0).toLowerCase();
|
|
||||||
if ((! jumpIndex.hasOwnProperty(startLetter)) && (letter == startLetter)) {
|
|
||||||
jumpIndex[letter] = count;
|
|
||||||
break letterLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var item = data.items[name];
|
var item = data.items[name];
|
||||||
// Use text or image tag based on logo
|
// Use text or image tag based on logo
|
||||||
if (item.hasOwnProperty('has_logo')) {
|
if (item.hasOwnProperty('has_logo')) {
|
||||||
@ -321,7 +259,7 @@ async function rendermenu(datas) {
|
|||||||
var romName = name;
|
var romName = name;
|
||||||
};
|
};
|
||||||
if (has_logo == true) {
|
if (has_logo == true) {
|
||||||
logo_html = '<img class="menu-img" alt="'+ romName +'" title="'+ romName +'">';
|
logo_html = '<img class="menu-img">';
|
||||||
} else {
|
} else {
|
||||||
logo_html = '<p class="menu-img">' + name + '</p>';
|
logo_html = '<p class="menu-img">' + name + '</p>';
|
||||||
};
|
};
|
||||||
@ -344,7 +282,7 @@ async function rendermenu(datas) {
|
|||||||
</div>\
|
</div>\
|
||||||
</div>');
|
</div>');
|
||||||
count++
|
count++
|
||||||
};
|
};
|
||||||
// Render active list
|
// Render active list
|
||||||
for await (var active_num of [...Array(display_items).keys()]) {
|
for await (var active_num of [...Array(display_items).keys()]) {
|
||||||
$('#active-list').append('<div id="active' + active_num + '" class="menu-div"></div>');
|
$('#active-list').append('<div id="active' + active_num + '" class="menu-div"></div>');
|
||||||
@ -364,16 +302,10 @@ async function rendermenu(datas) {
|
|||||||
$('.menu-img').css({'max-height': image_height});
|
$('.menu-img').css({'max-height': image_height});
|
||||||
highlight(active_item);
|
highlight(active_item);
|
||||||
// Move items up
|
// Move items up
|
||||||
function moveUp(num) {
|
function moveUp() {
|
||||||
if (! isSafari) {
|
$('#bgvid').prop('muted', false);
|
||||||
$('#bgvid').prop('muted', false);
|
$('#bgvid').prop('volume', 0.5);
|
||||||
$('#bgvid').prop('volume', 0.5);
|
active_item--
|
||||||
}
|
|
||||||
if (typeof num == 'number') {
|
|
||||||
active_item = (active_item - num);
|
|
||||||
} else {
|
|
||||||
active_item--
|
|
||||||
}
|
|
||||||
if (active_item < 0) {
|
if (active_item < 0) {
|
||||||
active_item = items_length;
|
active_item = items_length;
|
||||||
}
|
}
|
||||||
@ -387,16 +319,10 @@ async function rendermenu(datas) {
|
|||||||
highlight(active_item);
|
highlight(active_item);
|
||||||
};
|
};
|
||||||
// Move items down
|
// Move items down
|
||||||
function moveDown(num) {
|
function moveDown() {
|
||||||
if (! isSafari) {
|
$('#bgvid').prop('muted', false);
|
||||||
$('#bgvid').prop('muted', false);
|
$('#bgvid').prop('volume', 0.5);
|
||||||
$('#bgvid').prop('volume', 0.5);
|
active_item++
|
||||||
}
|
|
||||||
if (typeof num == 'number') {
|
|
||||||
active_item = (active_item + num);
|
|
||||||
} else {
|
|
||||||
active_item++
|
|
||||||
}
|
|
||||||
if (active_item > items_length) {
|
if (active_item > items_length) {
|
||||||
active_item = 0;
|
active_item = 0;
|
||||||
}
|
}
|
||||||
@ -409,83 +335,34 @@ async function rendermenu(datas) {
|
|||||||
}
|
}
|
||||||
highlight(active_item);
|
highlight(active_item);
|
||||||
};
|
};
|
||||||
// Jump items up
|
|
||||||
async function indexUp() {
|
|
||||||
for await (index of Object.keys(jumpIndex).reverse()) {
|
|
||||||
if (active_item > jumpIndex[index]) {
|
|
||||||
let jumpNum = (active_item - jumpIndex[index]);
|
|
||||||
moveUp(jumpNum);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Jump items down
|
|
||||||
async function indexDown() {
|
|
||||||
for await (index of Object.keys(jumpIndex)) {
|
|
||||||
if (jumpIndex[index] > active_item) {
|
|
||||||
let jumpNum = (jumpIndex[index] - active_item);
|
|
||||||
moveDown(jumpNum);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Capture key events for menu navigation
|
// Capture key events for menu navigation
|
||||||
let upPressed = false;
|
|
||||||
let downPressed = false;
|
|
||||||
$(document).keydown(function(event) {
|
$(document).keydown(function(event) {
|
||||||
// Scroll on keypress
|
|
||||||
if (event.key == 'ArrowDown') {
|
if (event.key == 'ArrowDown') {
|
||||||
downPressed = true;
|
|
||||||
moveDown();
|
moveDown();
|
||||||
}
|
}
|
||||||
if (event.key == 'ArrowUp') {
|
if (event.key == 'ArrowUp') {
|
||||||
upPressed = true;
|
|
||||||
moveUp();
|
moveUp();
|
||||||
}
|
}
|
||||||
// Scroll faster with diagnols
|
|
||||||
if ((event.key == 'ArrowRight') && ((upPressed == false) && (downPressed == true))) {
|
|
||||||
moveDown(10);
|
|
||||||
}
|
|
||||||
if ((event.key == 'ArrowRight') && ((upPressed == true) && (downPressed == false))) {
|
|
||||||
moveUp(10);
|
|
||||||
}
|
|
||||||
// Load item
|
// Load item
|
||||||
if (((event.key == 'ArrowRight') || (event.key == 'Enter')) && ((upPressed == false) && (downPressed == false))) {
|
if ((event.key == 'ArrowRight') || (event.key == 'Enter')) {
|
||||||
$('#i' + active_item).click();
|
$('#i' + active_item).click();
|
||||||
}
|
}
|
||||||
// Go to Parent
|
// Go to Parent
|
||||||
if (event.key == 'ArrowLeft') {
|
if (event.key == 'ArrowLeft') {
|
||||||
window.location.href = '#' + parent;
|
window.location.href = '#' + parent;
|
||||||
}
|
}
|
||||||
// Jump Down
|
|
||||||
if (event.key == 'PageDown') {
|
|
||||||
indexDown();
|
|
||||||
}
|
|
||||||
// Jump Up
|
|
||||||
if (event.key == 'PageUp') {
|
|
||||||
indexUp();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Remove events for multi key presses
|
|
||||||
$(document).keyup(function(event) {
|
|
||||||
if (event.key == 'ArrowDown') {
|
|
||||||
downPressed = false;
|
|
||||||
}
|
|
||||||
if (event.key == 'ArrowUp') {
|
|
||||||
upPressed = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
//// Touch controls ////
|
//// Touch controls ////
|
||||||
// Scroll wheel
|
// Scroll up wheel
|
||||||
async function scroll(ev) {
|
async function scroll(ev) {
|
||||||
window.scrollKill = false;
|
window.scrollKill = false;
|
||||||
var scrolling = setInterval(() => {
|
var scrolling = setInterval(() => {
|
||||||
if (window.scrollKill == false) {
|
if (window.scrollKill == false) {
|
||||||
if (ev.additionalEvent == 'panup') {
|
if (ev.additionalEvent == 'panup') {
|
||||||
moveDown(5);
|
moveDown();
|
||||||
} else if (ev.additionalEvent == 'pandown') {
|
} else if (ev.additionalEvent == 'pandown') {
|
||||||
moveUp(5);
|
moveUp();
|
||||||
} else {
|
} else {
|
||||||
clearInterval(scrolling);
|
clearInterval(scrolling);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -499,7 +376,7 @@ async function rendermenu(datas) {
|
|||||||
};
|
};
|
||||||
var mc = new Hammer(document.getElementById('menu'));
|
var mc = new Hammer(document.getElementById('menu'));
|
||||||
mc.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
|
mc.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
|
||||||
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL, threshold: 180 });
|
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL, threshold: 250 });
|
||||||
mc.on("swipeup", moveDown);
|
mc.on("swipeup", moveDown);
|
||||||
mc.on("swipedown", moveUp);
|
mc.on("swipedown", moveUp);
|
||||||
mc.on("panstart", scroll);
|
mc.on("panstart", scroll);
|
||||||
@ -526,168 +403,16 @@ async function rendermenu(datas) {
|
|||||||
};
|
};
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
//// GamePad controls ////
|
};
|
||||||
let scrollDelay
|
|
||||||
let animReq
|
|
||||||
let homeTimer;
|
|
||||||
let home = 0;
|
|
||||||
let homePressed = false;
|
|
||||||
let gpUpdate;
|
|
||||||
function gameLoop() {
|
|
||||||
// Handle if buttons are missing
|
|
||||||
function buttonsMissing(axes,buttons) {
|
|
||||||
var missing = true;
|
|
||||||
axes.forEach(function(i) {
|
|
||||||
if (typeof gp.axes[i] === 'undefined') {
|
|
||||||
missing = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
buttons.forEach(function(i) {
|
|
||||||
if (typeof gp.buttons[i] === 'undefined') {
|
|
||||||
missing = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return missing;
|
|
||||||
}
|
|
||||||
let gamePads = navigator.getGamepads();
|
|
||||||
if (!gamePads?.[0]) return;
|
|
||||||
let gp = gamePads[0];
|
|
||||||
if (window.location.hash != "#game") {
|
|
||||||
gameStarted = false;
|
|
||||||
if (!scrollDelay) {
|
|
||||||
// Analog down
|
|
||||||
if ((buttonsMissing([1,3],[])) && (gp.axes[1] > .5 || gp.axes[3] > .5)) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 200);
|
|
||||||
moveDown();
|
|
||||||
// Analog up
|
|
||||||
} else if ((buttonsMissing([1,3],[])) && (gp.axes[1] < -.5 || gp.axes[3] < -.5)) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 200);
|
|
||||||
moveUp();
|
|
||||||
// D-pad down
|
|
||||||
} else if ((buttonsMissing([],[13])) && (gp.buttons[13].pressed)) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 200);
|
|
||||||
moveDown();
|
|
||||||
// D-pad up
|
|
||||||
} else if ((buttonsMissing([],[12])) && (gp.buttons[12].pressed)) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 200);
|
|
||||||
moveUp();
|
|
||||||
// R1 index down
|
|
||||||
} else if ((buttonsMissing([],[5])) && (gp.buttons[5].pressed)) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 200);
|
|
||||||
indexDown();
|
|
||||||
// L1 index up
|
|
||||||
} else if ((buttonsMissing([],[4])) && (gp.buttons[4].pressed)) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 200);
|
|
||||||
indexUp();
|
|
||||||
// Analog L2 scroll up by strength
|
|
||||||
} else if ((buttonsMissing([],[6])) && (gp.buttons[6].pressed)) {
|
|
||||||
if (gp.buttons[6].value) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, (Math.abs(gp.buttons[6].value - 1) * 200) + 40);
|
|
||||||
} else {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 40);
|
|
||||||
}
|
|
||||||
moveUp();
|
|
||||||
// Analog R2 scroll down by strength
|
|
||||||
} else if ((buttonsMissing([],[7])) && (gp.buttons[7].pressed)) {
|
|
||||||
if (gp.buttons[7].value) {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, (Math.abs(gp.buttons[7].value - 1) * 200) + 40);
|
|
||||||
} else {
|
|
||||||
scrollDelay = setTimeout(() => scrollDelay = undefined, 40);
|
|
||||||
}
|
|
||||||
moveDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gp.timestamp == gpUpdate) {
|
|
||||||
animReq = requestAnimationFrame(gameLoop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gpUpdate = gp.timestamp
|
|
||||||
if (gp.buttons[0].pressed) {
|
|
||||||
if ($('#i' + active_item.toString()).data('type') == "game") {
|
|
||||||
cancelAnimationFrame(animReq);
|
|
||||||
}
|
|
||||||
$('#i' + active_item).click();
|
|
||||||
return;
|
|
||||||
} else if (gp.buttons[1].pressed && parent && '#' + parent != window.location.hash) {
|
|
||||||
window.location.href = '#' + parent;
|
|
||||||
return;
|
|
||||||
} else if ((buttonsMissing([],[16])) && (gp.buttons[16].pressed && window.location.hash != '#main')) {
|
|
||||||
window.location.href = '#main';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (gp.timestamp == gpUpdate) {
|
|
||||||
animReq = requestAnimationFrame(gameLoop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gpUpdate = gp.timestamp
|
|
||||||
try {
|
|
||||||
if (!gameStarted && gp.buttons[1].pressed && parent) {
|
|
||||||
window.location.href = '#' + parent;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
// Press home button 3 times to exit game
|
|
||||||
if ((buttonsMissing([],[16])) && (!gp.buttons[16].pressed && homePressed)) {
|
|
||||||
home++;
|
|
||||||
homePressed = false;
|
|
||||||
}
|
|
||||||
if ((buttonsMissing([],[16])) && (gp.buttons[16].pressed)) {
|
|
||||||
clearTimeout(homeTimer)
|
|
||||||
homeTimer = setTimeout(() => home = 0, 500)
|
|
||||||
homePressed = true
|
|
||||||
}
|
|
||||||
if ((buttonsMissing([],[16])) && (gp.buttons[16].pressed && home >= 2)) {
|
|
||||||
window.location.href = '#' + parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animReq = requestAnimationFrame(gameLoop);
|
|
||||||
}
|
|
||||||
window.addEventListener("gamepadconnected", gameLoop)
|
|
||||||
window.addEventListener("gamepaddisconnected", cancelAnimationFrame(animReq))
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
var gameStarted = false;
|
|
||||||
let gps = navigator.getGamepads();
|
|
||||||
if (gps) {
|
|
||||||
for (let gp of gps) {
|
|
||||||
let gpEvt = new GamepadEvent("gamepadconnected", {
|
|
||||||
gamepad: gp
|
|
||||||
})
|
|
||||||
window.dispatchEvent(gpEvt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.addEventListener("hashchange", gameLoop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go fullscreen
|
|
||||||
function fullscreen() {
|
|
||||||
let page = document.documentElement;
|
|
||||||
if (document.fullscreenElement) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
if (page.requestFullscreen) {
|
|
||||||
page.requestFullscreen();
|
|
||||||
} else if (page.webkitRequestFullscreen) {
|
|
||||||
page.webkitRequestFullscreen();
|
|
||||||
} else if (page.msRequestFullscreen) {
|
|
||||||
page.msRequestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the json profile selected
|
// Load the json profile selected
|
||||||
async function loadjson(name, active_item) {
|
function loadjson(name, active) {
|
||||||
if (name == 'preview') {
|
var url = 'user/config/' + name + '.json';
|
||||||
var url = 'user/hashes/preview.json';
|
fetch(url,Init)
|
||||||
} else {
|
.then((resp) => resp.json())
|
||||||
var url = 'user/config/' + name + '.json';
|
.then((data) => {
|
||||||
}
|
rendermenu(data, active)
|
||||||
let response = await fetch(url,Init);
|
});
|
||||||
let data = await response.json();
|
|
||||||
rendermenu([data, active_item]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
@ -696,8 +421,8 @@ window.onload = function() {
|
|||||||
} else {
|
} else {
|
||||||
var hash = window.location.hash.replace('#','');
|
var hash = window.location.hash.replace('#','');
|
||||||
var name = hash.split('---')[0];
|
var name = hash.split('---')[0];
|
||||||
let active_item = hash.split('---')[1];
|
var active = hash.split('---')[1];
|
||||||
loadjson(name, active_item);
|
loadjson(name, active);
|
||||||
}
|
}
|
||||||
$(window).on('hashchange', function() {
|
$(window).on('hashchange', function() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,704 +0,0 @@
|
|||||||
(function(global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
||||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.screenGamepad = {}));
|
|
||||||
}(this, (function(exports) {
|
|
||||||
var extendStatics = function(d, b) {
|
|
||||||
extendStatics = Object.setPrototypeOf ||
|
|
||||||
({
|
|
||||||
__proto__: []
|
|
||||||
}
|
|
||||||
instanceof Array && function(d, b) {
|
|
||||||
d.__proto__ = b;
|
|
||||||
}) ||
|
|
||||||
function(d, b) {
|
|
||||||
for (var p in b)
|
|
||||||
if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
|
|
||||||
};
|
|
||||||
return extendStatics(d, b);
|
|
||||||
};
|
|
||||||
|
|
||||||
function __extends(d, b) {
|
|
||||||
extendStatics(d, b);
|
|
||||||
|
|
||||||
function __() {
|
|
||||||
this.constructor = d;
|
|
||||||
}
|
|
||||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
||||||
}
|
|
||||||
|
|
||||||
var EventDispatcher = (function() {
|
|
||||||
function EventDispatcher() {
|
|
||||||
this._listeners = {};
|
|
||||||
}
|
|
||||||
EventDispatcher.prototype.addEventListener = function(type, listener) {
|
|
||||||
var listeners = this._listeners;
|
|
||||||
if (listeners[type] === undefined)
|
|
||||||
listeners[type] = [];
|
|
||||||
if (listeners[type].indexOf(listener) === -1)
|
|
||||||
listeners[type].push(listener);
|
|
||||||
};
|
|
||||||
EventDispatcher.prototype.removeEventListener = function(type, listener) {
|
|
||||||
var listeners = this._listeners;
|
|
||||||
var listenerArray = listeners[type];
|
|
||||||
if (listenerArray !== undefined) {
|
|
||||||
var index = listenerArray.indexOf(listener);
|
|
||||||
if (index !== -1)
|
|
||||||
listenerArray.splice(index, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
EventDispatcher.prototype.removeAllEventListeners = function(type) {
|
|
||||||
if (!type) {
|
|
||||||
this._listeners = {};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Array.isArray(this._listeners[type]))
|
|
||||||
this._listeners[type].length = 0;
|
|
||||||
};
|
|
||||||
EventDispatcher.prototype.dispatchEvent = function(event) {
|
|
||||||
var listeners = this._listeners;
|
|
||||||
var listenerArray = listeners[event.type];
|
|
||||||
if (listenerArray !== undefined) {
|
|
||||||
event.target = this;
|
|
||||||
var array = listenerArray.slice(0);
|
|
||||||
for (var i = 0, l = array.length; i < l; i++) {
|
|
||||||
array[i].call(this, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return EventDispatcher;
|
|
||||||
}());
|
|
||||||
|
|
||||||
function findTouchEventById(event, identifier) {
|
|
||||||
for (var i = 0, l = event.changedTouches.length; i < l; i++) {
|
|
||||||
if (identifier === event.changedTouches[i].identifier) {
|
|
||||||
return event.changedTouches[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTouchEvent(event) {
|
|
||||||
return 'TouchEvent' in window && event instanceof TouchEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $style = document.createElement('style');
|
|
||||||
$style.innerHTML = `
|
|
||||||
.screenGamepad-Joystick {
|
|
||||||
cursor: pointer;
|
|
||||||
-ms-touch-action : none;
|
|
||||||
touch-action : none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2147483647;
|
|
||||||
}
|
|
||||||
.screenGamepad-Joystick__Button {
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 20%;
|
|
||||||
left: 20%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 60%;
|
|
||||||
height: 60%;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid #333;
|
|
||||||
background: rgba( 255, 255, 255, .5 );
|
|
||||||
z-index: 2147483647;
|
|
||||||
}`;
|
|
||||||
document.head.insertBefore($style, document.head.firstChild);
|
|
||||||
var Joystick = (function(_super) {
|
|
||||||
__extends(Joystick, _super);
|
|
||||||
|
|
||||||
function Joystick(options) {
|
|
||||||
if (options === void 0) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
var _this = _super.call(this) || this;
|
|
||||||
_this.domElement = document.createElement('div');
|
|
||||||
_this._size = 128;
|
|
||||||
_this._x = 0;
|
|
||||||
_this._y = 0;
|
|
||||||
_this._angle = 0;
|
|
||||||
_this._isActive = false;
|
|
||||||
_this._pointerId = -1;
|
|
||||||
_this._elRect = new DOMRect();
|
|
||||||
_this._$button = document.createElement('div');
|
|
||||||
if (options.size)
|
|
||||||
_this._size = options.size;
|
|
||||||
_this.domElement.classList.add('screenGamepad-Joystick');
|
|
||||||
_this.domElement.style.width = _this._size + "px";
|
|
||||||
_this.domElement.style.height = _this._size + "px";
|
|
||||||
_this._$button.classList.add('screenGamepad-Joystick__Button');
|
|
||||||
_this.domElement.appendChild(_this._$button);
|
|
||||||
var computePosition = function(offsetX, offsetY) {
|
|
||||||
var x = offsetX / _this._size * 2 - 1;
|
|
||||||
var y = -offsetY / _this._size * 2 + 1;
|
|
||||||
if (x === 0 && y === 0) {
|
|
||||||
_this._angle = 0;
|
|
||||||
_this._x = 0;
|
|
||||||
_this._y = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_this._angle = Math.atan2(-y, -x) + Math.PI;
|
|
||||||
var length = Math.min(Math.sqrt(x * x + y * y), 1);
|
|
||||||
_this._x = Math.cos(_this._angle) * length;
|
|
||||||
_this._y = Math.sin(_this._angle) * length;
|
|
||||||
};
|
|
||||||
var onButtonMove = function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var _isTouchEvent = isTouchEvent(event);
|
|
||||||
var _event = _isTouchEvent ?
|
|
||||||
findTouchEventById(event, _this._pointerId) :
|
|
||||||
event;
|
|
||||||
if (!_event)
|
|
||||||
return;
|
|
||||||
var lastX = _this._x;
|
|
||||||
var lastY = _this._y;
|
|
||||||
var offsetX = (_event.clientX - window.pageXOffset - _this._elRect.left);
|
|
||||||
var offsetY = (_event.clientY - window.pageYOffset - _this._elRect.top);
|
|
||||||
computePosition(offsetX, offsetY);
|
|
||||||
if (_this._x === lastX && _this._y === lastY)
|
|
||||||
return;
|
|
||||||
_this._update();
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var onButtonMoveEnd = function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var _isTouchEvent = isTouchEvent(event);
|
|
||||||
var _event = _isTouchEvent ?
|
|
||||||
event.changedTouches[0] :
|
|
||||||
event;
|
|
||||||
if (_isTouchEvent && _event.identifier !== _this._pointerId)
|
|
||||||
return;
|
|
||||||
document.removeEventListener('mousemove', onButtonMove);
|
|
||||||
document.removeEventListener('touchmove', onButtonMove, {
|
|
||||||
passive: false
|
|
||||||
});
|
|
||||||
document.removeEventListener('mouseup', onButtonMoveEnd);
|
|
||||||
document.removeEventListener('touchend', onButtonMoveEnd);
|
|
||||||
_this._pointerId = -1;
|
|
||||||
_this._isActive = false;
|
|
||||||
_this._angle = 0;
|
|
||||||
_this._x = 0;
|
|
||||||
_this._y = 0;
|
|
||||||
_this._update();
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'inactive'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var onButtonMoveStart = function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var _isTouchEvent = isTouchEvent(event);
|
|
||||||
var _event = _isTouchEvent ?
|
|
||||||
event.changedTouches[0] :
|
|
||||||
event;
|
|
||||||
if (_isTouchEvent) {
|
|
||||||
_this._pointerId = _event.identifier;
|
|
||||||
}
|
|
||||||
_this._elRect = _this.domElement.getBoundingClientRect();
|
|
||||||
_this._isActive = true;
|
|
||||||
var offsetX = (_event.clientX - window.pageXOffset - _this._elRect.left);
|
|
||||||
var offsetY = (_event.clientY - window.pageYOffset - _this._elRect.top);
|
|
||||||
computePosition(offsetX, offsetY);
|
|
||||||
_this._update();
|
|
||||||
document.addEventListener('mousemove', onButtonMove);
|
|
||||||
document.addEventListener('touchmove', onButtonMove, {
|
|
||||||
passive: false
|
|
||||||
});
|
|
||||||
document.addEventListener('mouseup', onButtonMoveEnd);
|
|
||||||
document.addEventListener('touchend', onButtonMoveEnd);
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'active'
|
|
||||||
});
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
_this.domElement.addEventListener('mousedown', onButtonMoveStart);
|
|
||||||
_this.domElement.addEventListener('touchstart', onButtonMoveStart);
|
|
||||||
return _this;
|
|
||||||
}
|
|
||||||
Object.defineProperty(Joystick.prototype, "x", {
|
|
||||||
get: function() {
|
|
||||||
return this._x;
|
|
||||||
},
|
|
||||||
set: function(x) {
|
|
||||||
this._x = x;
|
|
||||||
this._update();
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
Object.defineProperty(Joystick.prototype, "y", {
|
|
||||||
get: function() {
|
|
||||||
return this._y;
|
|
||||||
},
|
|
||||||
set: function(y) {
|
|
||||||
this._y = y;
|
|
||||||
this._update();
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
Object.defineProperty(Joystick.prototype, "angle", {
|
|
||||||
get: function() {
|
|
||||||
return this._angle;
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
Object.defineProperty(Joystick.prototype, "isActive", {
|
|
||||||
get: function() {
|
|
||||||
return this._isActive;
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
Joystick.prototype._update = function() {
|
|
||||||
this._$button.style.transition = this._isActive ? '' : 'transform .1s';
|
|
||||||
if (this._x === 0 && this._y === 0) {
|
|
||||||
this._$button.style.transform = "translate( 0px, 0px )";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var radius = this._size / 2;
|
|
||||||
var x = this._x * radius;
|
|
||||||
var y = -this._y * radius;
|
|
||||||
this._$button.style.transform = "translate( " + x + "px, " + y + "px )";
|
|
||||||
};
|
|
||||||
return Joystick;
|
|
||||||
}(EventDispatcher));
|
|
||||||
|
|
||||||
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
||||||
var $style$1 = document.createElement('style');
|
|
||||||
$style$1.innerHTML = `
|
|
||||||
.screenGamepad-Button {
|
|
||||||
cursor: pointer;
|
|
||||||
-ms-touch-action : none;
|
|
||||||
touch-action : none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2147483647;
|
|
||||||
}
|
|
||||||
.screenGamepad-Button__HitArea {
|
|
||||||
color: rgba( 255, 255, 255, .5 );
|
|
||||||
z-index: 2147483647;
|
|
||||||
}`;
|
|
||||||
document.head.insertBefore($style$1, document.head.firstChild);
|
|
||||||
var Button = (function(_super) {
|
|
||||||
__extends(Button, _super);
|
|
||||||
|
|
||||||
function Button(options) {
|
|
||||||
if (options === void 0) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
var _this = _super.call(this) || this;
|
|
||||||
_this.domElement = document.createElementNS(SVG_NS, 'svg');
|
|
||||||
_this._size = 48;
|
|
||||||
_this._isActive = false;
|
|
||||||
_this._pointerId = -1;
|
|
||||||
_this._$hitArea = document.createElementNS(SVG_NS, 'a');
|
|
||||||
if (options.size)
|
|
||||||
_this._size = options.size;
|
|
||||||
_this._$hitArea.innerHTML = options.shape || Button.BUTTON_SHAPE_CIRCLE;
|
|
||||||
_this.domElement.classList.add('screenGamepad-Button');
|
|
||||||
_this.domElement.setAttribute('viewBox', '0 0 1 1');
|
|
||||||
_this.domElement.style.width = _this._size + "px";
|
|
||||||
_this.domElement.style.height = _this._size + "px";
|
|
||||||
_this._$hitArea.classList.add('screenGamepad-Button__HitArea');
|
|
||||||
_this.domElement.appendChild(_this._$hitArea);
|
|
||||||
var hitRect = _this.domElement.createSVGRect();
|
|
||||||
hitRect.width = 1;
|
|
||||||
hitRect.height = 1;
|
|
||||||
var onButtonMove = function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var _isTouchEvent = isTouchEvent(event);
|
|
||||||
var _event = _isTouchEvent ?
|
|
||||||
findTouchEventById(event, _this._pointerId) :
|
|
||||||
event;
|
|
||||||
if (!_event)
|
|
||||||
return;
|
|
||||||
var x = _event.clientX;
|
|
||||||
var y = _event.clientY;
|
|
||||||
var $intersectedElement = document.elementFromPoint(x, y);
|
|
||||||
var isIntersected = _this._$hitArea.contains($intersectedElement);
|
|
||||||
if (isIntersected && !_this._isActive) {
|
|
||||||
_this._isActive = true;
|
|
||||||
_this._update();
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'active'
|
|
||||||
});
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isIntersected && _this._isActive) {
|
|
||||||
_this._isActive = false;
|
|
||||||
_this._update();
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'inactive'
|
|
||||||
});
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var onButtonUp = function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
document.removeEventListener('mousemove', onButtonMove);
|
|
||||||
document.removeEventListener('touchmove', onButtonMove, {
|
|
||||||
passive: false
|
|
||||||
});
|
|
||||||
document.removeEventListener('mouseup', onButtonUp);
|
|
||||||
document.removeEventListener('touchend', onButtonUp);
|
|
||||||
_this._pointerId = -1;
|
|
||||||
if (!_this._isActive)
|
|
||||||
return;
|
|
||||||
_this._isActive = false;
|
|
||||||
_this._update();
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'inactive'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var onButtonDown = function(event) {
|
|
||||||
document.removeEventListener('mousemove', onButtonMove);
|
|
||||||
document.removeEventListener('touchmove', onButtonMove, {
|
|
||||||
passive: false
|
|
||||||
});
|
|
||||||
document.removeEventListener('mouseup', onButtonUp);
|
|
||||||
document.removeEventListener('touchend', onButtonUp);
|
|
||||||
event.preventDefault();
|
|
||||||
var _isTouchEvent = isTouchEvent(event);
|
|
||||||
if (_isTouchEvent) {
|
|
||||||
var changedTouches = event.changedTouches;
|
|
||||||
_this._pointerId = changedTouches[changedTouches.length - 1].identifier;
|
|
||||||
}
|
|
||||||
_this._isActive = true;
|
|
||||||
_this._update();
|
|
||||||
document.addEventListener('mousemove', onButtonMove);
|
|
||||||
document.addEventListener('touchmove', onButtonMove, {
|
|
||||||
passive: false
|
|
||||||
});
|
|
||||||
document.addEventListener('mouseup', onButtonUp);
|
|
||||||
document.addEventListener('touchend', onButtonUp);
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'active'
|
|
||||||
});
|
|
||||||
_this.dispatchEvent({
|
|
||||||
type: 'change'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
_this.domElement.addEventListener('mousedown', onButtonDown);
|
|
||||||
_this.domElement.addEventListener('touchstart', onButtonDown);
|
|
||||||
return _this;
|
|
||||||
}
|
|
||||||
Object.defineProperty(Button, "BUTTON_SHAPE_CIRCLE", {
|
|
||||||
get: function() {
|
|
||||||
return '<circle cx="0.5" cy="0.5" r="0.5" fill="currentColor" />';
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
Object.defineProperty(Button.prototype, "isActive", {
|
|
||||||
get: function() {
|
|
||||||
return this._isActive;
|
|
||||||
},
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
Button.prototype._update = function() {};
|
|
||||||
return Button;
|
|
||||||
}(EventDispatcher));
|
|
||||||
|
|
||||||
var utils = {
|
|
||||||
roundToStep: function(number, step) {
|
|
||||||
return step * Math.round(number / step);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Button = Button;
|
|
||||||
exports.Joystick = Joystick;
|
|
||||||
exports.utils = utils;
|
|
||||||
|
|
||||||
Object.defineProperty(exports, '__esModule', {
|
|
||||||
value: true
|
|
||||||
});
|
|
||||||
|
|
||||||
})));
|
|
||||||
|
|
||||||
//// Fake touch controller ////
|
|
||||||
var touchController = {
|
|
||||||
axes: [0, 0, 0, 0],
|
|
||||||
buttons: [],
|
|
||||||
connected: false,
|
|
||||||
id: "HTML5 Touch GamePad",
|
|
||||||
index: 0,
|
|
||||||
mapping: "standard",
|
|
||||||
timestamp: Math.floor(Date.now() / 1000),
|
|
||||||
};
|
|
||||||
for (let i of Array(18).keys()) {
|
|
||||||
touchController.buttons.push({
|
|
||||||
pressed: false,
|
|
||||||
touched: false,
|
|
||||||
value: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//// GamePads ////
|
|
||||||
var directionStore;
|
|
||||||
|
|
||||||
//// Standard default D-pad snes style
|
|
||||||
if (typeof gamePadType == 'undefined') {
|
|
||||||
// Buttons
|
|
||||||
var buttons = [
|
|
||||||
[0, 'bottom', '40px', 'right', '80px', 'a'],
|
|
||||||
[1, 'bottom', '70px', 'right', '30px', 'b'],
|
|
||||||
[2, 'bottom', '90px', 'right', '110px', 'x'],
|
|
||||||
[3, 'bottom', '120px', 'right', '60px', 'y'],
|
|
||||||
[4, 'bottom', '210px', 'left', '30px', 'L1'],
|
|
||||||
[5, 'bottom', '210px', 'right', '30px', 'R1'],
|
|
||||||
[8, 'bottom', '50px', 'left', '42%', 'select'],
|
|
||||||
[9, 'bottom', '50px', 'right', '42%', 'start']
|
|
||||||
]
|
|
||||||
// Joystick
|
|
||||||
var joystick = new screenGamepad.Joystick();
|
|
||||||
joystick.domElement.style.position = 'absolute';
|
|
||||||
joystick.domElement.style.bottom = '40px';
|
|
||||||
joystick.domElement.style.left = '20px';
|
|
||||||
document.getElementById("gamepad").appendChild(joystick.domElement);
|
|
||||||
joystick.addEventListener('change', function() {
|
|
||||||
axesToDpad();
|
|
||||||
});
|
|
||||||
//// Basic controller type with no L+R and two buttons
|
|
||||||
} else if (gamePadType == 'simple') {
|
|
||||||
// Buttons
|
|
||||||
var buttons = [
|
|
||||||
[0, 'bottom', '40px', 'right', '80px', 'a'],
|
|
||||||
[1, 'bottom', '70px', 'right', '30px', 'b'],
|
|
||||||
[8, 'bottom', '50px', 'left', '42%', 'select'],
|
|
||||||
[9, 'bottom', '50px', 'right', '42%', 'start']
|
|
||||||
]
|
|
||||||
// Joystick
|
|
||||||
var joystick = new screenGamepad.Joystick();
|
|
||||||
joystick.domElement.style.position = 'absolute';
|
|
||||||
joystick.domElement.style.bottom = '40px';
|
|
||||||
joystick.domElement.style.left = '20px';
|
|
||||||
document.getElementById("gamepad").appendChild(joystick.domElement);
|
|
||||||
joystick.addEventListener('change', function() {
|
|
||||||
axesToDpad();
|
|
||||||
});
|
|
||||||
//// Dual Analog modern contoller
|
|
||||||
} else if (gamePadType == 'modern') {
|
|
||||||
// Buttons
|
|
||||||
var buttons = [
|
|
||||||
[0, 'bottom', '40px', 'right', '80px', 'a'],
|
|
||||||
[1, 'bottom', '70px', 'right', '30px', 'b'],
|
|
||||||
[2, 'bottom', '90px', 'right', '110px', 'x'],
|
|
||||||
[3, 'bottom', '120px', 'right', '60px', 'y'],
|
|
||||||
[4, 'bottom', '210px', 'left', '30px', 'L1'],
|
|
||||||
[5, 'bottom', '210px', 'right', '30px', 'R1'],
|
|
||||||
[6, 'bottom', '270px', 'left', '30px', 'L2'],
|
|
||||||
[7, 'bottom', '270px', 'right', '30px', 'R2'],
|
|
||||||
[8, 'bottom', '50px', 'left', '42%', 'select'],
|
|
||||||
[9, 'bottom', '50px', 'right', '42%', 'start']
|
|
||||||
]
|
|
||||||
// Joystick
|
|
||||||
var joystick = new screenGamepad.Joystick();
|
|
||||||
joystick.domElement.style.position = 'absolute';
|
|
||||||
joystick.domElement.style.bottom = '40px';
|
|
||||||
joystick.domElement.style.left = '20px';
|
|
||||||
document.getElementById("gamepad").appendChild(joystick.domElement);
|
|
||||||
joystick.addEventListener('change', function() {
|
|
||||||
axesToDpad();
|
|
||||||
});
|
|
||||||
// Analogs
|
|
||||||
var left = new screenGamepad.Joystick();
|
|
||||||
left.domElement.style.position = 'absolute';
|
|
||||||
left.domElement.style.bottom = '40px';
|
|
||||||
left.domElement.style.left = '200px';
|
|
||||||
document.getElementById("gamepad").appendChild(left.domElement);
|
|
||||||
left.addEventListener('change', function() {
|
|
||||||
touchController.axes[0] = screenGamepad.utils.roundToStep(left.x, 0.1);
|
|
||||||
touchController.axes[1] = screenGamepad.utils.roundToStep(left.x, 0.1);
|
|
||||||
});
|
|
||||||
var right = new screenGamepad.Joystick();
|
|
||||||
right.domElement.style.position = 'absolute';
|
|
||||||
right.domElement.style.bottom = '40px';
|
|
||||||
right.domElement.style.right = '200px';
|
|
||||||
document.getElementById("gamepad").appendChild(right.domElement);
|
|
||||||
right.addEventListener('change', function() {
|
|
||||||
touchController.axes[2] = screenGamepad.utils.roundToStep(right.x, 0.1);
|
|
||||||
touchController.axes[3] = screenGamepad.utils.roundToStep(right.x, 0.1);
|
|
||||||
});
|
|
||||||
} else if (gamePadType == 'n64') {
|
|
||||||
// Buttons
|
|
||||||
var buttons = [
|
|
||||||
[0, 'bottom', '40px', 'right', '110px', 'a'],
|
|
||||||
[2, 'bottom', '90px', 'right', '140px', 'x'],
|
|
||||||
[6, 'bottom', '60px', 'right', '200px', 'z'],
|
|
||||||
[5, 'bottom', '230px', 'right', '30px', 'R1'],
|
|
||||||
[4, 'bottom', '230px', 'left', '30px', 'L1'],
|
|
||||||
[9, 'bottom', '50px', 'right', '50%', 'start']
|
|
||||||
|
|
||||||
]
|
|
||||||
// Joystick
|
|
||||||
var joystick = new screenGamepad.Joystick();
|
|
||||||
joystick.domElement.style.position = 'absolute';
|
|
||||||
joystick.domElement.style.bottom = '130px';
|
|
||||||
joystick.domElement.style.left = '30px';
|
|
||||||
document.getElementById("gamepad").appendChild(joystick.domElement);
|
|
||||||
joystick.addEventListener('change', function() {
|
|
||||||
axesToDpad();
|
|
||||||
});
|
|
||||||
// Analogs
|
|
||||||
var left = new screenGamepad.Joystick();
|
|
||||||
left.domElement.style.position = 'absolute';
|
|
||||||
left.domElement.style.bottom = '30px';
|
|
||||||
left.domElement.style.left = '30px';
|
|
||||||
document.getElementById("gamepad").appendChild(left.domElement);
|
|
||||||
left.addEventListener('change', function() {
|
|
||||||
touchController.axes[0] = screenGamepad.utils.roundToStep(left.x, 0.1);
|
|
||||||
touchController.axes[1] = screenGamepad.utils.roundToStep(left.x, 0.1);
|
|
||||||
});
|
|
||||||
// Default Retroarch uses the R analogue for the C buttons
|
|
||||||
var right = new screenGamepad.Joystick();
|
|
||||||
right.domElement.style.position = 'absolute';
|
|
||||||
right.domElement.style.bottom = '120px';
|
|
||||||
right.domElement.style.right = '30px';
|
|
||||||
document.getElementById("gamepad").appendChild(right.domElement);
|
|
||||||
right.addEventListener('change', function() {
|
|
||||||
touchController.axes[2] = screenGamepad.utils.roundToStep(right.x, 0.1);
|
|
||||||
touchController.axes[3] = screenGamepad.utils.roundToStep(right.x, 0.1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//// Render Buttons ////
|
|
||||||
var vars = {};
|
|
||||||
for (let button of buttons) {
|
|
||||||
vars[button[0]] = new screenGamepad.Button();
|
|
||||||
vars[button[0]].domElement.style.position = 'absolute';
|
|
||||||
vars[button[0]].domElement.style[button[1]] = button[2];
|
|
||||||
vars[button[0]].domElement.style[button[3]] = button[4];
|
|
||||||
document.getElementById("gamepad").appendChild(vars[button[0]].domElement);
|
|
||||||
vars[button[0]].addEventListener('change', function() {
|
|
||||||
touchController.buttons[button[0]].pressed = vars[button[0]].isActive;
|
|
||||||
connectGamePad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Retroarch menu button
|
|
||||||
if (localStorage.getItem('retroArch') == 'true') {
|
|
||||||
menuButton = new screenGamepad.Button();
|
|
||||||
menuButton.domElement.style.position = 'absolute';
|
|
||||||
menuButton.domElement.style.top = '10px';
|
|
||||||
menuButton.domElement.style.left = '10px';
|
|
||||||
document.getElementById("gamepad").appendChild(menuButton.domElement);
|
|
||||||
menuButton.addEventListener('change', function() {
|
|
||||||
touchController.buttons[4].pressed = menuButton.isActive;
|
|
||||||
touchController.buttons[5].pressed = menuButton.isActive;
|
|
||||||
touchController.buttons[8].pressed = menuButton.isActive;
|
|
||||||
touchController.buttons[9].pressed = menuButton.isActive;
|
|
||||||
connectGamePad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//// Logic to convert an axes joystick to dpad
|
|
||||||
function axesToDpad() {
|
|
||||||
// 8 way direction with .24 deadzone
|
|
||||||
let direction = axesToDirection({
|
|
||||||
x: screenGamepad.utils.roundToStep(joystick.x, 0.1),
|
|
||||||
y: screenGamepad.utils.roundToStep(joystick.y, 0.1)
|
|
||||||
}, 0.24, 4);
|
|
||||||
if (directionStore !== direction) {
|
|
||||||
directionStore = direction;
|
|
||||||
// Clear d-pad press
|
|
||||||
touchController.buttons[12].pressed = false;
|
|
||||||
touchController.buttons[13].pressed = false;
|
|
||||||
touchController.buttons[14].pressed = false;
|
|
||||||
touchController.buttons[15].pressed = false;
|
|
||||||
// Press d-pad
|
|
||||||
if (direction == 'up') {
|
|
||||||
touchController.buttons[12].pressed = true;
|
|
||||||
} else if (direction == 'down') {
|
|
||||||
touchController.buttons[13].pressed = true;
|
|
||||||
} else if (direction == 'left') {
|
|
||||||
touchController.buttons[14].pressed = true;
|
|
||||||
} else if (direction == 'right') {
|
|
||||||
touchController.buttons[15].pressed = true;
|
|
||||||
} else if (direction == 'upleft') {
|
|
||||||
touchController.buttons[12].pressed = true;
|
|
||||||
touchController.buttons[14].pressed = true;
|
|
||||||
} else if (direction == 'upright') {
|
|
||||||
touchController.buttons[12].pressed = true;
|
|
||||||
touchController.buttons[15].pressed = true;
|
|
||||||
} else if (direction == 'downright') {
|
|
||||||
touchController.buttons[13].pressed = true;
|
|
||||||
touchController.buttons[15].pressed = true;
|
|
||||||
} else if (direction == 'downleft') {
|
|
||||||
touchController.buttons[13].pressed = true;
|
|
||||||
touchController.buttons[14].pressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function axesToDirection(coord, deadzone, axes) {
|
|
||||||
const angle = Math.atan2(coord.y, coord.x);
|
|
||||||
const snapRadians = Math.PI / axes;
|
|
||||||
const newAngle = snapRadians * Math.round(angle / snapRadians);
|
|
||||||
let magnitude = Math.sqrt(coord.x * coord.x + coord.y * coord.y);
|
|
||||||
if (magnitude <= deadzone) {
|
|
||||||
return 'center';
|
|
||||||
}
|
|
||||||
if (magnitude > 1) {
|
|
||||||
magnitude = 1;
|
|
||||||
}
|
|
||||||
let x = Math.cos(newAngle).toFixed(2);
|
|
||||||
let y = Math.sin(newAngle).toFixed(2);
|
|
||||||
if ((x == '0.00') && (y == '1.00')) {
|
|
||||||
return 'up';
|
|
||||||
} else if ((x == '0.00') && (y == '-1.00')) {
|
|
||||||
return 'down';
|
|
||||||
} else if (((x == '-1.00') && (y == '-0.00')) || ((x == '-1.00') && (y == '0.00'))) {
|
|
||||||
return 'left';
|
|
||||||
} else if ((x == '1.00') && (y == '0.00')) {
|
|
||||||
return 'right';
|
|
||||||
} else if ((x == '-0.71') && (y == '0.71')) {
|
|
||||||
return 'upleft';
|
|
||||||
} else if ((x == '0.71') && (y == '0.71')) {
|
|
||||||
return 'upright';
|
|
||||||
} else if ((x == '0.71') && (y == '-0.71')) {
|
|
||||||
return 'downright';
|
|
||||||
} else if ((x == '-0.71') && (y == '-0.71')) {
|
|
||||||
return 'downleft';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//// Replace default contoller logic with ours ////
|
|
||||||
navigator.getGamepads = function() {
|
|
||||||
return {
|
|
||||||
0: touchController,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
function connectGamePad() {
|
|
||||||
let event = new Event("gamepadconnected");
|
|
||||||
touchController.connected = true;
|
|
||||||
touchController.timestamp = Math.floor(Date.now() / 1000);
|
|
||||||
event.gamepad = touchController;
|
|
||||||
window.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
function disconnectGamePad() {
|
|
||||||
let event = new Event("gamepaddisconnected");
|
|
||||||
touchController.connected = false;
|
|
||||||
touchController.timestamp = Math.floor(Date.now() / 1000);
|
|
||||||
event.gamepad = touchController;
|
|
||||||
window.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
24
frontend/js/vendor/browserfs.min.js
vendored
24
frontend/js/vendor/browserfs.min.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/js/vendor/hammer.min.js
vendored
1
frontend/js/vendor/hammer.min.js
vendored
File diff suppressed because one or more lines are too long
28
frontend/js/vendor/ipfs-http-client.min.js
vendored
28
frontend/js/vendor/ipfs-http-client.min.js
vendored
File diff suppressed because one or more lines are too long
204
frontend/js/vendor/jquery.hotkeys.js
vendored
Normal file
204
frontend/js/vendor/jquery.hotkeys.js
vendored
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/*jslint browser: true*/
|
||||||
|
/*jslint jquery: true*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* jQuery Hotkeys Plugin
|
||||||
|
* Copyright 2010, John Resig
|
||||||
|
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||||
|
*
|
||||||
|
* Based upon the plugin by Tzury Bar Yochay:
|
||||||
|
* https://github.com/tzuryby/jquery.hotkeys
|
||||||
|
*
|
||||||
|
* Original idea by:
|
||||||
|
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One small change is: now keys are passed by object { keys: '...' }
|
||||||
|
* Might be useful, when you want to pass some other data to your handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(jQuery) {
|
||||||
|
|
||||||
|
jQuery.hotkeys = {
|
||||||
|
version: "0.2.0",
|
||||||
|
|
||||||
|
specialKeys: {
|
||||||
|
8: "backspace",
|
||||||
|
9: "tab",
|
||||||
|
10: "return",
|
||||||
|
13: "return",
|
||||||
|
16: "shift",
|
||||||
|
17: "ctrl",
|
||||||
|
18: "alt",
|
||||||
|
19: "pause",
|
||||||
|
20: "capslock",
|
||||||
|
27: "esc",
|
||||||
|
32: "space",
|
||||||
|
33: "pageup",
|
||||||
|
34: "pagedown",
|
||||||
|
35: "end",
|
||||||
|
36: "home",
|
||||||
|
37: "left",
|
||||||
|
38: "up",
|
||||||
|
39: "right",
|
||||||
|
40: "down",
|
||||||
|
45: "insert",
|
||||||
|
46: "del",
|
||||||
|
59: ";",
|
||||||
|
61: "=",
|
||||||
|
96: "0",
|
||||||
|
97: "1",
|
||||||
|
98: "2",
|
||||||
|
99: "3",
|
||||||
|
100: "4",
|
||||||
|
101: "5",
|
||||||
|
102: "6",
|
||||||
|
103: "7",
|
||||||
|
104: "8",
|
||||||
|
105: "9",
|
||||||
|
106: "*",
|
||||||
|
107: "+",
|
||||||
|
109: "-",
|
||||||
|
110: ".",
|
||||||
|
111: "/",
|
||||||
|
112: "f1",
|
||||||
|
113: "f2",
|
||||||
|
114: "f3",
|
||||||
|
115: "f4",
|
||||||
|
116: "f5",
|
||||||
|
117: "f6",
|
||||||
|
118: "f7",
|
||||||
|
119: "f8",
|
||||||
|
120: "f9",
|
||||||
|
121: "f10",
|
||||||
|
122: "f11",
|
||||||
|
123: "f12",
|
||||||
|
144: "numlock",
|
||||||
|
145: "scroll",
|
||||||
|
173: "-",
|
||||||
|
186: ";",
|
||||||
|
187: "=",
|
||||||
|
188: ",",
|
||||||
|
189: "-",
|
||||||
|
190: ".",
|
||||||
|
191: "/",
|
||||||
|
192: "`",
|
||||||
|
219: "[",
|
||||||
|
220: "\\",
|
||||||
|
221: "]",
|
||||||
|
222: "'"
|
||||||
|
},
|
||||||
|
|
||||||
|
shiftNums: {
|
||||||
|
"`": "~",
|
||||||
|
"1": "!",
|
||||||
|
"2": "@",
|
||||||
|
"3": "#",
|
||||||
|
"4": "$",
|
||||||
|
"5": "%",
|
||||||
|
"6": "^",
|
||||||
|
"7": "&",
|
||||||
|
"8": "*",
|
||||||
|
"9": "(",
|
||||||
|
"0": ")",
|
||||||
|
"-": "_",
|
||||||
|
"=": "+",
|
||||||
|
";": ": ",
|
||||||
|
"'": "\"",
|
||||||
|
",": "<",
|
||||||
|
".": ">",
|
||||||
|
"/": "?",
|
||||||
|
"\\": "|"
|
||||||
|
},
|
||||||
|
|
||||||
|
// excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url
|
||||||
|
textAcceptingInputTypes: [
|
||||||
|
"text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime",
|
||||||
|
"datetime-local", "search", "color", "tel"],
|
||||||
|
|
||||||
|
// default input types not to bind to unless bound directly
|
||||||
|
textInputTypes: /textarea|input|select/i,
|
||||||
|
|
||||||
|
options: {
|
||||||
|
filterInputAcceptingElements: true,
|
||||||
|
filterTextInputs: true,
|
||||||
|
filterContentEditable: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function keyHandler(handleObj) {
|
||||||
|
if (typeof handleObj.data === "string") {
|
||||||
|
handleObj.data = {
|
||||||
|
keys: handleObj.data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only care when a possible input has been specified
|
||||||
|
if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var origHandler = handleObj.handler,
|
||||||
|
keys = handleObj.data.keys.toLowerCase().split(" ");
|
||||||
|
|
||||||
|
handleObj.handler = function(event) {
|
||||||
|
// Don't fire in text-accepting inputs that we didn't directly bind to
|
||||||
|
if (this !== event.target &&
|
||||||
|
(jQuery.hotkeys.options.filterInputAcceptingElements &&
|
||||||
|
jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||
|
||||||
|
(jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||
|
||||||
|
(jQuery.hotkeys.options.filterTextInputs &&
|
||||||
|
jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
|
||||||
|
character = String.fromCharCode(event.which).toLowerCase(),
|
||||||
|
modif = "",
|
||||||
|
possible = {};
|
||||||
|
|
||||||
|
jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) {
|
||||||
|
|
||||||
|
if (event[specialKey + 'Key'] && special !== specialKey) {
|
||||||
|
modif += specialKey + '+';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// metaKey is triggered off ctrlKey erronously
|
||||||
|
if (event.metaKey && !event.ctrlKey && special !== "meta") {
|
||||||
|
modif += "meta+";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) {
|
||||||
|
modif = modif.replace("alt+ctrl+shift+", "hyper+");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (special) {
|
||||||
|
possible[modif + special] = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
possible[modif + character] = true;
|
||||||
|
possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
|
||||||
|
|
||||||
|
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
|
||||||
|
if (modif === "shift+") {
|
||||||
|
possible[jQuery.hotkeys.shiftNums[character]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0, l = keys.length; i < l; i++) {
|
||||||
|
if (possible[keys[i]]) {
|
||||||
|
return origHandler.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery.each(["keydown", "keyup", "keypress"], function() {
|
||||||
|
jQuery.event.special[this] = {
|
||||||
|
add: keyHandler
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery || this.jQuery || window.jQuery);
|
||||||
13
frontend/js/vendor/jszip.min.js
vendored
13
frontend/js/vendor/jszip.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "EmulatorJS",
|
|
||||||
"short_name": "EmulatorJS",
|
|
||||||
"manifest_version": 2,
|
|
||||||
"version": "1.9.2",
|
|
||||||
"display": "standalone",
|
|
||||||
"background_color": "#000000",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "icon.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "500x500"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": "/"
|
|
||||||
}
|
|
||||||
121
has_files.sh
121
has_files.sh
@ -1,36 +1,28 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
set -e
|
set -e
|
||||||
set -o pipefail
|
|
||||||
rom_path=$1
|
rom_path=$1
|
||||||
rom_type=$2
|
rom_type=$2
|
||||||
full_scan=$3
|
|
||||||
# Set user folder
|
# Set user folder
|
||||||
if [ -d '/data' ]; then
|
if [ -d '/data' ]; then
|
||||||
user_folder='/data'
|
user_folder='/data'
|
||||||
else
|
else
|
||||||
user_folder='frontend/user'
|
user_folder='frontend/user'
|
||||||
fi
|
fi
|
||||||
check_files=$(find "${user_folder}${rom_path}" -maxdepth 1 -not -type d -and -not -name '.*' -exec basename {} \; | sort)
|
check_files=$(ls -1 "${user_folder}${rom_path}")
|
||||||
|
|
||||||
# Clear out old hashes
|
# Clear out old hashes
|
||||||
if [ "${full_scan}" = "true" ]; then
|
if [ -d "${user_folder}/hashes/${rom_path}" ]; then
|
||||||
if [ -d "${user_folder}/hashes/${rom_path}" ]; then
|
rm -Rf "${user_folder}/hashes/${rom_path}"
|
||||||
rm -Rf "${user_folder}/hashes/${rom_path}"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean up for bad scans
|
|
||||||
rm -Rf "${user_folder}/hashes/${rom_path}/tmp/"
|
|
||||||
|
|
||||||
# Process zip file hashes
|
# Process zip file hashes
|
||||||
process_zip () {
|
process_zip () {
|
||||||
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
||||||
echo "unzipping ${file}"
|
echo "unzipping ${file}"
|
||||||
unzip -j -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp"
|
unzip -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp"
|
||||||
rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || :
|
rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || :
|
||||||
echo "hashing ${file}"
|
echo "hashing ${file}"
|
||||||
firstfile=( "${user_folder}/hashes/${rom_path}/tmp/"* )
|
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/"* | awk '{print $1;exit}')
|
||||||
sum=$(sha1sum "$firstfile" | awk '{print $1;exit}')
|
|
||||||
rm -R "${user_folder}/hashes/${rom_path}/tmp/"
|
rm -R "${user_folder}/hashes/${rom_path}/tmp/"
|
||||||
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
||||||
}
|
}
|
||||||
@ -39,12 +31,10 @@ process_zip () {
|
|||||||
process_7z () {
|
process_7z () {
|
||||||
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
||||||
echo "unzipping ${file}"
|
echo "unzipping ${file}"
|
||||||
7z e "${user_folder}${rom_path}/${file}" -o"${user_folder}/hashes/${rom_path}/tmp"
|
7z x "${user_folder}${rom_path}/${file}" -o"${user_folder}/hashes/${rom_path}/tmp"
|
||||||
rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || :
|
rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || :
|
||||||
find "${user_folder}/hashes/${rom_path}/tmp/" -empty -type d -delete
|
|
||||||
echo "hashing ${file}"
|
echo "hashing ${file}"
|
||||||
firstfile=( "${user_folder}/hashes/${rom_path}/tmp/"* )
|
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/"* | awk '{print $1;exit}')
|
||||||
sum=$(sha1sum "$firstfile" | awk '{print $1;exit}')
|
|
||||||
rm -R "${user_folder}/hashes/${rom_path}/tmp/"
|
rm -R "${user_folder}/hashes/${rom_path}/tmp/"
|
||||||
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
||||||
}
|
}
|
||||||
@ -62,7 +52,7 @@ process_nes () {
|
|||||||
if [ $file_type == 'zip' ]; then
|
if [ $file_type == 'zip' ]; then
|
||||||
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
||||||
echo "unzipping ${file}"
|
echo "unzipping ${file}"
|
||||||
unzip -j -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp"
|
unzip -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp"
|
||||||
rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || :
|
rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || :
|
||||||
file_to_sha="${user_folder}/hashes/${rom_path}/tmp/"*
|
file_to_sha="${user_folder}/hashes/${rom_path}/tmp/"*
|
||||||
elif [ $file_type == 'x-7z-compressed' ]; then
|
elif [ $file_type == 'x-7z-compressed' ]; then
|
||||||
@ -76,7 +66,7 @@ process_nes () {
|
|||||||
file_to_sha="${user_folder}/${rom_path}/${file}"
|
file_to_sha="${user_folder}/${rom_path}/${file}"
|
||||||
fi
|
fi
|
||||||
echo "hashing ${file}"
|
echo "hashing ${file}"
|
||||||
sum=$(NES20Tool -operation rominfo -rom-file ${file_to_sha} |awk '/ROM SHA1/ {print $3;exit}' || sha1sum ${file_to_sha} | awk '{print $1;exit}')
|
sum=$(NES20Tool -operation rominfo -rom-file ${file_to_sha} |awk '/ROM SHA1/ {print $3;exit}')
|
||||||
wait
|
wait
|
||||||
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
||||||
if [ -d "${user_folder}/hashes/${rom_path}/tmp" ]; then
|
if [ -d "${user_folder}/hashes/${rom_path}/tmp" ]; then
|
||||||
@ -97,18 +87,12 @@ process_chd () {
|
|||||||
if grep -q "TRACK 02" "${user_folder}/hashes/${rom_path}/tmp/FILE.cue"; then
|
if grep -q "TRACK 02" "${user_folder}/hashes/${rom_path}/tmp/FILE.cue"; then
|
||||||
echo "${file} is multi track need to split"
|
echo "${file} is multi track need to split"
|
||||||
mkdir -p "${user_folder}/hashes/${rom_path}/tmp/split"
|
mkdir -p "${user_folder}/hashes/${rom_path}/tmp/split"
|
||||||
NOSPLIT=false
|
binmerge -s "${user_folder}/hashes/${rom_path}/tmp/FILE.cue" FILE -o "${user_folder}/hashes/${rom_path}/tmp/split"
|
||||||
binmerge -s "${user_folder}/hashes/${rom_path}/tmp/FILE.cue" FILE -o "${user_folder}/hashes/${rom_path}/tmp/split" || NOSPLIT=true
|
echo "hashing ${file} (Track 1)"
|
||||||
if [ "${NOSPLIT}" == "false" ]; then
|
if [ -f "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 1).bin" ]; then
|
||||||
echo "hashing ${file} (Track 1)"
|
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 1).bin" | awk '{print $1;exit}')
|
||||||
if [ -f "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 1).bin" ]; then
|
elif [ -f "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 01).bin" ];then
|
||||||
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 1).bin" | awk '{print $1;exit}')
|
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 01).bin" | awk '{print $1;exit}')
|
||||||
elif [ -f "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 01).bin" ];then
|
|
||||||
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 01).bin" | awk '{print $1;exit}')
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "splitting failed hashing full bin ${file}"
|
|
||||||
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/FILE.cue" | awk '{print $1;exit}')
|
|
||||||
fi
|
fi
|
||||||
elif grep -q "TRACK 01" "${user_folder}/hashes/${rom_path}/tmp/FILE.cue"; then
|
elif grep -q "TRACK 01" "${user_folder}/hashes/${rom_path}/tmp/FILE.cue"; then
|
||||||
echo "hashing ${file}"
|
echo "hashing ${file}"
|
||||||
@ -132,18 +116,12 @@ process_bin () {
|
|||||||
if grep -q "TRACK 02" "${user_folder}/${rom_path}/${cuefile}"; then
|
if grep -q "TRACK 02" "${user_folder}/${rom_path}/${cuefile}"; then
|
||||||
echo "${file} is multi track need to split"
|
echo "${file} is multi track need to split"
|
||||||
mkdir -p "${user_folder}/${rom_path}/tmp/"
|
mkdir -p "${user_folder}/${rom_path}/tmp/"
|
||||||
NOSPLIT=false
|
binmerge -s "${user_folder}/${rom_path}/${cuefile}" FILE -o "${user_folder}/${rom_path}/tmp/"
|
||||||
binmerge -s "${user_folder}/${rom_path}/${cuefile}" FILE -o "${user_folder}/${rom_path}/tmp/" || NOSPLIT=true
|
echo "hashing ${file} (Track 1)"
|
||||||
if [ "${NOSPLIT}" == "false" ]; then
|
if [ -f "${user_folder}/${rom_path}/tmp/FILE (Track 1).bin" ]; then
|
||||||
echo "hashing ${file} (Track 1)"
|
sum=$(sha1sum "${user_folder}/${rom_path}/tmp/FILE (Track 1).bin" | awk '{print $1;exit}')
|
||||||
if [ -f "${user_folder}/${rom_path}/tmp/FILE (Track 1).bin" ]; then
|
elif [ -f "${user_folder}/${rom_path}/tmp/FILE (Track 01).bin" ];then
|
||||||
sum=$(sha1sum "${user_folder}/${rom_path}/tmp/FILE (Track 1).bin" | awk '{print $1;exit}')
|
sum=$(sha1sum "${user_folder}/${rom_path}/tmp/FILE (Track 01).bin" | awk '{print $1;exit}')
|
||||||
elif [ -f "${user_folder}/${rom_path}/tmp/FILE (Track 01).bin" ];then
|
|
||||||
sum=$(sha1sum "${user_folder}/${rom_path}/tmp/FILE (Track 01).bin" | awk '{print $1;exit}')
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "splitting failed hashing full bin ${file}"
|
|
||||||
sum=$(sha1sum "${user_folder}/${rom_path}/${file}" | awk '{print $1;exit}')
|
|
||||||
fi
|
fi
|
||||||
elif grep -q "TRACK 01" "${user_folder}/${rom_path}/${cuefile}"; then
|
elif grep -q "TRACK 01" "${user_folder}/${rom_path}/${cuefile}"; then
|
||||||
echo "hashing ${file}"
|
echo "hashing ${file}"
|
||||||
@ -155,46 +133,31 @@ process_bin () {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Process special zip file hashes
|
|
||||||
process_zip_by_name () {
|
|
||||||
mkdir -p "${user_folder}/hashes/${rom_path}/tmp"
|
|
||||||
echo "unzipping ${file}"
|
|
||||||
unzip -j -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp"
|
|
||||||
echo "hashing ${file}"
|
|
||||||
sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/${file%.*}."* | awk '{print $1;exit}')
|
|
||||||
rm -R "${user_folder}/hashes/${rom_path}/tmp/"
|
|
||||||
printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1"
|
|
||||||
}
|
|
||||||
|
|
||||||
IFS=$'\n'
|
IFS=$'\n'
|
||||||
mkdir -p "${user_folder}/hashes/${rom_path}"
|
mkdir -p "${user_folder}/hashes/${rom_path}"
|
||||||
for file in $check_files; do
|
for file in $check_files; do
|
||||||
if [ ! -f "${user_folder}/hashes/${rom_path}/${file}.sha1" ]; then
|
file_extension="${file##*.}"
|
||||||
file_extension="${file##*.}"
|
if [ $rom_type == 'arcade' ] || [ $rom_type == 'segaSaturn' ]; then
|
||||||
if [ "${file_extension,,}" = 'multiwad' ]; then
|
process_name
|
||||||
process_zip_by_name
|
elif [ "${file_extension,,}" = 'chd' ] && [ $rom_type == 'pce' ]; then
|
||||||
elif [ $rom_type == 'arcade' ] || [ $rom_type == 'segaSaturn' ] && [[ "${file_extension,,}" != @(img|cue|ccd|disk*|sub) ]]; then
|
process_name
|
||||||
process_name
|
elif [ "${file_extension,,}" = 'chd' ] || ([ $rom_type == 'psx' ] && [[ "${file_extension,,}" == "disk1" ]]); then
|
||||||
elif [ "${file_extension,,}" = 'chd' ] && [ $rom_type == 'pce' ]; then
|
process_chd
|
||||||
process_name
|
elif [ "${file_extension,,}" = 'bin' ] && [ $rom_type != '3do' ]; then
|
||||||
elif [ "${file_extension,,}" = 'chd' ] || ([ $rom_type == 'psx' ] && [[ "${file_extension,,}" == "disk1" ]]); then
|
process_bin
|
||||||
process_chd
|
elif [[ "${file_extension,,}" = @(img|cue|ccd|disk*|sub) ]]; then
|
||||||
elif [ "${file_extension,,}" = 'bin' ] && [ $rom_type != '3do' ]; then
|
echo "Filetype ${file_extension} not supported"
|
||||||
process_bin
|
elif [ $rom_type == 'nes' ]; then
|
||||||
elif [[ "${file_extension,,}" = @(img|cue|ccd|disk*|sub) ]]; then
|
file_type=$(file -b --mime-type "${user_folder}${rom_path}/${file}" | awk -F'/' '{print $2}')
|
||||||
echo "Filetype ${file_extension} not supported"
|
process_nes
|
||||||
elif [ $rom_type == 'nes' ]; then
|
else
|
||||||
file_type=$(file -b --mime-type "${user_folder}${rom_path}/${file}" | awk -F'/' '{print $2}')
|
file_type=$(file -b --mime-type "${user_folder}${rom_path}/${file}" | awk -F'/' '{print $2}')
|
||||||
process_nes
|
if [ $file_type == 'zip' ]; then
|
||||||
|
process_zip
|
||||||
|
elif [ $file_type == 'x-7z-compressed' ]; then
|
||||||
|
process_7z
|
||||||
else
|
else
|
||||||
file_type=$(file -b --mime-type "${user_folder}${rom_path}/${file}" | awk -F'/' '{print $2}')
|
just_hash
|
||||||
if [ $file_type == 'zip' ]; then
|
|
||||||
process_zip
|
|
||||||
elif [ $file_type == 'x-7z-compressed' ]; then
|
|
||||||
process_7z
|
|
||||||
else
|
|
||||||
just_hash
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
567
index.js
567
index.js
@ -1,5 +1,4 @@
|
|||||||
// NPM modules
|
// NPM modules
|
||||||
var home = require('os').homedir();
|
|
||||||
var socketIO = require('socket.io');
|
var socketIO = require('socket.io');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var fsw = require('fs').promises;
|
var fsw = require('fs').promises;
|
||||||
@ -12,14 +11,9 @@ var http = require('http').Server(app);
|
|||||||
var baserouter = express.Router();
|
var baserouter = express.Router();
|
||||||
var { spawn } = require('child_process');
|
var { spawn } = require('child_process');
|
||||||
var { create } = require('ipfs-http-client');
|
var { create } = require('ipfs-http-client');
|
||||||
var crypto = require('crypto');
|
|
||||||
var ipfs = create();
|
var ipfs = create();
|
||||||
var merge = require('deepmerge');
|
|
||||||
|
|
||||||
// Default vars
|
// Default vars
|
||||||
if (home == '/data') {
|
|
||||||
home = '/config'
|
|
||||||
}
|
|
||||||
var baseUrl = process.env.SUBFOLDER || '/';
|
var baseUrl = process.env.SUBFOLDER || '/';
|
||||||
if (fs.existsSync('/data')) {
|
if (fs.existsSync('/data')) {
|
||||||
var dataRoot = '/data/';
|
var dataRoot = '/data/';
|
||||||
@ -28,8 +22,7 @@ if (fs.existsSync('/data')) {
|
|||||||
};
|
};
|
||||||
var configPath = dataRoot + 'config/';
|
var configPath = dataRoot + 'config/';
|
||||||
var hashPath = dataRoot + 'hashes/';
|
var hashPath = dataRoot + 'hashes/';
|
||||||
var metaPath = dataRoot + 'metadata/';
|
var defaultPeer = '/ip4/206.189.169.226/tcp/4001/p2p/12D3KooWHRqeK6as7tbuoaQdoTUNayUQzfY5aUtTa9GpYzdWNPqU';
|
||||||
var defaultPeer = '/ip4/65.109.29.184/tcp/4001/p2p/12D3KooWAQZgCmhRo6V6yzGWTtw57xSRBnTn5kGMqzahFKyt5CW3';
|
|
||||||
var metaVariables = [
|
var metaVariables = [
|
||||||
['vid', 'videos', '.mp4'],
|
['vid', 'videos', '.mp4'],
|
||||||
['logo', 'logos', '.png'],
|
['logo', 'logos', '.png'],
|
||||||
@ -37,47 +30,13 @@ var metaVariables = [
|
|||||||
['corner', 'corners', '.png']
|
['corner', 'corners', '.png']
|
||||||
];
|
];
|
||||||
var emus = [
|
var emus = [
|
||||||
{'name': '3do', 'video_position': 'left:11.5vw;top:30vh;width:36.3vw;height:45.5vh;'},
|
'3do', 'arcade', 'atari2600', 'atari7800', 'colecovision',
|
||||||
{'name': 'arcade', 'video_position': 'left:10.3vw;top:30.5vh;width:36.5vw;height:48vh;'},
|
'doom', 'gb', 'gba', 'gbc', 'jaguar', 'lynx',
|
||||||
{'name': 'atari2600', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
'msx', 'n64', 'nds', 'nes', 'ngp', 'odyssey2',
|
||||||
{'name': 'atari5200', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
'pce', 'psx', 'sega32x', 'segaCD',
|
||||||
{'name': 'atari7800', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
'segaGG', 'segaMD', 'segaMS', 'segaSG',
|
||||||
{'name': 'colecovision', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
'segaSaturn', 'snes', 'vb', 'vectrex', 'ws'
|
||||||
{'name': 'doom', 'video_position': 'left:11.5vw;top:30vh;width:36.3vw;height:45.5vh;'},
|
|
||||||
{'name': 'gb', 'video_position': 'left:14.5vw;top:31vh;width:26vw;height:43.5vh;'},
|
|
||||||
{'name': 'gba', 'video_position': 'left:13.5vw;top:36vh;width:31.7vw;height:38.3vh;'},
|
|
||||||
{'name': 'gbc', 'video_position': 'left:15.5vw;top:31.2vh;width:28vw;height:44.7vh;'},
|
|
||||||
{'name': 'jaguar', 'video_position': 'left:11.5vw;top:30vh;width:36.3vw;height:45.5vh;'},
|
|
||||||
{'name': 'lynx', 'video_position': 'left:11vw;top:31vh;width:36vw;height:44vh;'},
|
|
||||||
{'name': 'msx', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'n64', 'video_position': 'left:11.5vw;top:30vh;width:36.3vw;height:45.5vh;'},
|
|
||||||
{'name': 'nds', 'video_position': 'left:23.8vw;top:25.7vh;width:20vw;height:56vh;'},
|
|
||||||
{'name': 'nes', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'ngp', 'video_position': 'left:15vw;top:34vh;width:25vw;height:40vh;'},
|
|
||||||
{'name': 'odyssey2', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'pce', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'psx', 'video_position': 'left:11.5vw;top:30vh;width:36.3vw;height:45.5vh;'},
|
|
||||||
{'name': 'sega32x', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'segaCD', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'segaGG', 'video_position': 'left:12.3vw;top:31.5vh;width:33.4vw;height:43.3vh;'},
|
|
||||||
{'name': 'segaMD', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'segaMS', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'segaSaturn', 'video_position': 'left:11.5vw;top:30vh;width:36.3vw;height:45.5vh;'},
|
|
||||||
{'name': 'segaSG', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'snes', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'vb', 'video_position': 'left:11.5vw;top:31.5vh;width:36vw;height:43vh;'},
|
|
||||||
{'name': 'vectrex', 'video_position': 'left:18vw;top:30vh;width:22vw;height:46vh;'},
|
|
||||||
{'name': 'ws', 'video_position': 'left:11.5vw;top:31vh;width:35vw;height:43vh;'}
|
|
||||||
];
|
];
|
||||||
var retroArchCfg = `
|
|
||||||
input_menu_toggle_gamepad_combo = 3
|
|
||||||
system_directory = /home/web_user/retroarch/system/`
|
|
||||||
|
|
||||||
app.use(function(req, res, next) {
|
|
||||||
res.header("Cross-Origin-Embedder-Policy", "require-corp");
|
|
||||||
res.header("Cross-Origin-Opener-Policy", "same-origin");
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
//// Http server ////
|
//// Http server ////
|
||||||
baserouter.use('/public', express.static(__dirname + '/public'));
|
baserouter.use('/public', express.static(__dirname + '/public'));
|
||||||
@ -89,7 +48,7 @@ app.use(baseUrl, baserouter);
|
|||||||
http.listen(3000);
|
http.listen(3000);
|
||||||
|
|
||||||
//// socketIO comms ////
|
//// socketIO comms ////
|
||||||
io = socketIO(http, {path: baseUrl + 'socket.io',maxHttpBufferSize: 100000000});
|
io = socketIO(http, {path: baseUrl + 'socket.io'});
|
||||||
io.on('connection', async function (socket) {
|
io.on('connection', async function (socket) {
|
||||||
//// Functions ////
|
//// Functions ////
|
||||||
// Send config list to client
|
// Send config list to client
|
||||||
@ -97,46 +56,31 @@ io.on('connection', async function (socket) {
|
|||||||
var files = await fsw.readdir(configPath);
|
var files = await fsw.readdir(configPath);
|
||||||
socket.emit('renderconfigs', files);
|
socket.emit('renderconfigs', files);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send MetaData list to client
|
|
||||||
async function renderMeta() {
|
|
||||||
await fsw.mkdir(metaPath, { recursive: true });
|
|
||||||
var files = await fsw.readdir(metaPath);
|
|
||||||
socket.emit('rendermeta', files);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send rom directories to client
|
// Send rom directories to client
|
||||||
async function renderRomsDir() {
|
async function renderRomsDir() {
|
||||||
let dirData = [];
|
|
||||||
if (fs.existsSync(hashPath)) {
|
if (fs.existsSync(hashPath)) {
|
||||||
let dirs = await fsw.readdir(hashPath);
|
var dirs = await fsw.readdir(hashPath);
|
||||||
for await (let dir of dirs) {
|
socket.emit('renderromsdir', dirs);
|
||||||
if (fs.lstatSync(hashPath + dir).isDirectory()) {
|
|
||||||
dirData.push(dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
socket.emit('renderromsdir', dirData);
|
|
||||||
} else {
|
} else {
|
||||||
socket.emit('renderromsdir', dirData);
|
socket.emit('renderromsdir', []);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tell client to render rom scanners
|
// Tell client to render rom scanners
|
||||||
async function renderRoms() {
|
async function renderRoms() {
|
||||||
var romData = {}
|
var romData = {}
|
||||||
for await (var emu of emus) {
|
for await (var emu of emus) {
|
||||||
var romCount = 0;
|
var romCount = 0;
|
||||||
var hashCount = 0;
|
var hashCount = 0;
|
||||||
var romPath = dataRoot + emu.name + '/roms/';
|
var romPath = dataRoot + emu + '/roms/';
|
||||||
if (fs.existsSync(hashPath + emu.name)) {
|
if (fs.existsSync(hashPath + emu)) {
|
||||||
var hashes = await fsw.readdir(hashPath + emu.name + '/roms/');
|
var hashes = await fsw.readdir(hashPath + emu + '/roms/');
|
||||||
var hashCount = hashes.length;
|
var hashCount = hashes.length;
|
||||||
};
|
};
|
||||||
if (fs.existsSync(romPath)) {
|
if (fs.existsSync(romPath)) {
|
||||||
var roms = await fsw.readdir(romPath);
|
var roms = await fsw.readdir(romPath);
|
||||||
var romCount = roms.length;
|
var romCount = roms.length;
|
||||||
};
|
};
|
||||||
romData[emu.name] = {'roms': romCount,'hashes': hashCount};
|
romData[emu] = {'roms': romCount,'hashes': hashCount};
|
||||||
};
|
};
|
||||||
// Grab default files data
|
// Grab default files data
|
||||||
var defaultFiles = await fsw.readFile('./metadata/default_files.json', 'utf8');
|
var defaultFiles = await fsw.readFile('./metadata/default_files.json', 'utf8');
|
||||||
@ -152,54 +96,49 @@ io.on('connection', async function (socket) {
|
|||||||
renderRomsDir();
|
renderRomsDir();
|
||||||
socket.emit('renderromslanding', romData);
|
socket.emit('renderromslanding', romData);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tell client to render landing
|
// Tell client to render landing
|
||||||
function renderLanding() {
|
function renderLanding() {
|
||||||
socket.emit('renderlanding');
|
socket.emit('renderlanding');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send file contents to client
|
// Send file contents to client
|
||||||
async function getConfig(file) {
|
async function getConfig(file) {
|
||||||
file = file + '.json';
|
var file = file + '.json';
|
||||||
let fileContents = await fsw.readFile(configPath + file, 'utf8');
|
var fileContents = await fsw.readFile(configPath + file, 'utf8');
|
||||||
socket.emit('renderconfig', JSON.parse(fileContents));
|
socket.emit('renderconfig', JSON.parse(fileContents));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send meta contents to client
|
|
||||||
async function getMetaJSON(file) {
|
|
||||||
file = file + '.json';
|
|
||||||
let fileContents = await fsw.readFile(metaPath + file, 'utf8');
|
|
||||||
socket.emit('rendermetajson', JSON.parse(fileContents));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save sent config file
|
// Save sent config file
|
||||||
async function saveConfig(data) {
|
async function saveConfig(data) {
|
||||||
var fileName = data.name + '.json';
|
var fileName = data.name + '.json';
|
||||||
configFile = JSON.stringify(data.config, null, 2);
|
configFile = JSON.stringify(data.config, null, 2);
|
||||||
await fsw.writeFile(configPath + fileName, configFile);
|
await fsw.writeFile(configPath + fileName, configFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Organize rom data to send to client
|
// Organize rom data to send to client
|
||||||
async function getRoms(dir) {
|
async function getRoms(dir) {
|
||||||
let metaData = await getMeta(dir);
|
var metaData = await fsw.readFile('./metadata/' + dir + '.json', 'utf8');
|
||||||
let shaPath = hashPath + dir + '/roms/';
|
var metaData = JSON.parse(metaData);
|
||||||
let files = await fsw.readdir(shaPath);
|
if (fs.existsSync(dataRoot + 'metadata/' + dir + '.json')) {
|
||||||
let identified = {};
|
var userMetaData = await fsw.readFile(dataRoot + 'metadata/' + dir + '.json', 'utf8');
|
||||||
let unidentified = {};
|
var userMetaData = JSON.parse(userMetaData);
|
||||||
let metaVars = [];
|
Object.assign(metaData, userMetaData);
|
||||||
|
};
|
||||||
|
var shaPath = hashPath + dir + '/roms/';
|
||||||
|
var files = await fsw.readdir(shaPath);
|
||||||
|
var identified = {};
|
||||||
|
var unidentified = {};
|
||||||
|
var metaVars = [];
|
||||||
metaVars.push(...metaVariables);
|
metaVars.push(...metaVariables);
|
||||||
metaVars.push(['video_position', 'videos', '.position']);
|
metaVars.push(['video_position', 'videos', '.position']);
|
||||||
for (var file of files) {
|
for (var file of files) {
|
||||||
let fileName = file.replace('.sha1','');
|
var fileName = file.replace('.sha1','');
|
||||||
let fileExtension = path.extname(fileName);
|
var fileExtension = path.extname(fileName);
|
||||||
let name = path.basename(fileName, fileExtension);
|
var name = path.basename(fileName, fileExtension);
|
||||||
var sha = await fsw.readFile(shaPath + file, 'utf8');
|
var sha = await fsw.readFile(shaPath + file, 'utf8');
|
||||||
if (metaData.hasOwnProperty(sha)) {
|
if (metaData.hasOwnProperty(sha)) {
|
||||||
if (metaData[sha].hasOwnProperty('ref')) {
|
if (metaData[sha].hasOwnProperty('ref')) {
|
||||||
var sha = metaData[sha].ref;
|
var sha = metaData[sha].ref;
|
||||||
};
|
};
|
||||||
identified[fileName] = {'has_art': 'none'};
|
identified[fileName] = {'has_art': 'none'};
|
||||||
for await (let variable of metaVars) {
|
for await (var variable of metaVars) {
|
||||||
if (metaData[sha].hasOwnProperty(variable[0])) {
|
if (metaData[sha].hasOwnProperty(variable[0])) {
|
||||||
if (fs.existsSync(dataRoot + dir + '/' + variable[1] + '/' + name + variable[2])) {
|
if (fs.existsSync(dataRoot + dir + '/' + variable[1] + '/' + name + variable[2])) {
|
||||||
identified[fileName] = {'has_art': true};
|
identified[fileName] = {'has_art': true};
|
||||||
@ -213,6 +152,7 @@ io.on('connection', async function (socket) {
|
|||||||
unidentified[fileName] = sha;
|
unidentified[fileName] = sha;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
var nameList = {};
|
||||||
socket.emit('renderrom', [identified, unidentified, metaData]);
|
socket.emit('renderrom', [identified, unidentified, metaData]);
|
||||||
}
|
}
|
||||||
// IPFS downloading
|
// IPFS downloading
|
||||||
@ -222,7 +162,7 @@ io.on('connection', async function (socket) {
|
|||||||
let writeStream = fs.createWriteStream(file);
|
let writeStream = fs.createWriteStream(file);
|
||||||
socket.emit('modaldata', 'Downloading: ' + file);
|
socket.emit('modaldata', 'Downloading: ' + file);
|
||||||
try {
|
try {
|
||||||
for await (var fileStream of ipfs.cat(cid, {'timeout': 20000})) {
|
for await (var fileStream of ipfs.cat(cid, {'timeout': 10000})) {
|
||||||
writeStream.write(fileStream);
|
writeStream.write(fileStream);
|
||||||
};
|
};
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
@ -247,12 +187,10 @@ io.on('connection', async function (socket) {
|
|||||||
};
|
};
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set default ipfs peer if DL times out
|
// Set default ipfs peer if DL times out
|
||||||
async function ipfsDefaultPeer() {
|
async function ipfsDefaultPeer() {
|
||||||
await ipfs.swarm.connect(defaultPeer);
|
await ipfs.swarm.connect(defaultPeer);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Download default files for user directory
|
// Download default files for user directory
|
||||||
async function dlDefaultFiles() {
|
async function dlDefaultFiles() {
|
||||||
var metaData = await fsw.readFile('./metadata/default_files.json', 'utf8');
|
var metaData = await fsw.readFile('./metadata/default_files.json', 'utf8');
|
||||||
@ -268,25 +206,22 @@ io.on('connection', async function (socket) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
for await (var dir of emus) {
|
for await (var dir of emus) {
|
||||||
var path = dataRoot + 'hashes/' + dir.name + '/roms/';
|
var path = dataRoot + 'hashes/' + dir + '/roms/';
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
var roms = await fsw.readdir(path);
|
var roms = await fsw.readdir(path);
|
||||||
if (roms.length > 0) {
|
if (roms.length > 0) {
|
||||||
socket.emit('modaldata', 'Processing Config for ' + dir.name);
|
socket.emit('modaldata', 'Processing Config for ' + dir);
|
||||||
await addToConfig(dir.name, true);
|
await addToConfig(dir, true);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
socket.emit('modaldata', 'Downloaded All Files');
|
socket.emit('modaldata', 'Downloaded All Files');
|
||||||
renderRoms();
|
renderRoms();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scan roms directory using helper script
|
// Scan roms directory using helper script
|
||||||
function scanRoms(data) {
|
function scanRoms(folder) {
|
||||||
let folder = data[0];
|
|
||||||
let fullScan = data[1];
|
|
||||||
socket.emit('emptymodal');
|
socket.emit('emptymodal');
|
||||||
let scanProcess = spawn('./has_files.sh', ['/' + folder + '/roms/', folder, fullScan]);
|
var scanProcess = spawn('./has_files.sh', ['/' + folder + '/roms/', folder]);
|
||||||
scanProcess.stdout.setEncoding('utf8');
|
scanProcess.stdout.setEncoding('utf8');
|
||||||
scanProcess.stderr.setEncoding('utf8');
|
scanProcess.stderr.setEncoding('utf8');
|
||||||
scanProcess.stdout.on('data', function(data) {
|
scanProcess.stdout.on('data', function(data) {
|
||||||
@ -297,26 +232,20 @@ io.on('connection', async function (socket) {
|
|||||||
});
|
});
|
||||||
scanProcess.on('close', function(code) {
|
scanProcess.on('close', function(code) {
|
||||||
socket.emit('modaldata', 'Scan exited with code: ' + code);
|
socket.emit('modaldata', 'Scan exited with code: ' + code);
|
||||||
if (fullScan) {
|
renderRoms();
|
||||||
renderRoms();
|
|
||||||
} else {
|
|
||||||
getRoms(folder);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add roms to config file
|
// Add roms to config file
|
||||||
async function addToConfig(dir, render) {
|
async function addToConfig(dir, render) {
|
||||||
// For arcade roms we need clone info
|
|
||||||
if (dir == 'arcade') {
|
if (dir == 'arcade') {
|
||||||
var metaData = await getMeta(dir);
|
var metaData = await fsw.readFile('./metadata/' + dir + '.json', 'utf8');
|
||||||
|
var metaData = JSON.parse(metaData);
|
||||||
};
|
};
|
||||||
// Update config file with current rom files
|
var configFile = configPath + dir + '.json';
|
||||||
let configFile = configPath + dir + '.json';
|
var shaPath = hashPath + dir + '/roms/';
|
||||||
let shaPath = hashPath + dir + '/roms/';
|
var files = await fsw.readdir(shaPath);
|
||||||
let files = await fsw.readdir(shaPath);
|
var config = await fsw.readFile(configFile, 'utf8');
|
||||||
let config = await fsw.readFile(configFile, 'utf8');
|
var config = JSON.parse(config);
|
||||||
config = JSON.parse(config);
|
|
||||||
config.items = {};
|
config.items = {};
|
||||||
if (files.length < 9) {
|
if (files.length < 9) {
|
||||||
var itemsLength = files.length;
|
var itemsLength = files.length;
|
||||||
@ -324,15 +253,15 @@ io.on('connection', async function (socket) {
|
|||||||
var itemsLength = 9;
|
var itemsLength = 9;
|
||||||
};
|
};
|
||||||
config.display_items = itemsLength;
|
config.display_items = itemsLength;
|
||||||
for await (let file of files) {
|
for await (var file of files) {
|
||||||
let fileName = file.replace('.sha1','');
|
var fileName = file.replace('.sha1','');
|
||||||
let fileExtension = path.extname(fileName);
|
var fileExtension = path.extname(fileName);
|
||||||
let name = path.basename(fileName, fileExtension);
|
var name = path.basename(fileName, fileExtension);
|
||||||
let has_logo = fs.existsSync(dataRoot + dir + '/logos/' + name + '.png');
|
var has_logo = fs.existsSync(dataRoot + dir + '/logos/' + name + '.png');
|
||||||
let has_back = fs.existsSync(dataRoot + dir + '/backgrounds/' + name + '.png');
|
var has_back = fs.existsSync(dataRoot + dir + '/backgrounds/' + name + '.png');
|
||||||
let has_corner = fs.existsSync(dataRoot + dir + '/corners/' + name + '.png');
|
var has_corner = fs.existsSync(dataRoot + dir + '/corners/' + name + '.png');
|
||||||
let has_video = fs.existsSync(dataRoot + dir + '/videos/' + name + '.mp4');
|
var has_video = fs.existsSync(dataRoot + dir + '/videos/' + name + '.mp4');
|
||||||
let positionFile = dataRoot + dir + '/videos/' + name + '.position';
|
var positionFile = dataRoot + dir + '/videos/' + name + '.position';
|
||||||
config.items[name] = {};
|
config.items[name] = {};
|
||||||
var multi_disc = 0;
|
var multi_disc = 0;
|
||||||
if (fileExtension == '.disk1') {
|
if (fileExtension == '.disk1') {
|
||||||
@ -352,8 +281,7 @@ io.on('connection', async function (socket) {
|
|||||||
Object.assign(config.items[name], {'multi_disc': multi_disc});
|
Object.assign(config.items[name], {'multi_disc': multi_disc});
|
||||||
};
|
};
|
||||||
if (fs.existsSync(positionFile)) {
|
if (fs.existsSync(positionFile)) {
|
||||||
let video_position = await fsw.readFile(positionFile, 'utf8');
|
var video_position = await fsw.readFile(positionFile, 'utf8');
|
||||||
Object.assign(config.items[name], {'video_position': video_position});
|
|
||||||
};
|
};
|
||||||
if (has_logo !== config.defaults.has_logo) {
|
if (has_logo !== config.defaults.has_logo) {
|
||||||
Object.assign(config.items[name], {'has_logo': has_logo});
|
Object.assign(config.items[name], {'has_logo': has_logo});
|
||||||
@ -370,58 +298,27 @@ io.on('connection', async function (socket) {
|
|||||||
if (fileExtension !== config.defaults.rom_extension) {
|
if (fileExtension !== config.defaults.rom_extension) {
|
||||||
Object.assign(config.items[name], {'rom_extension': fileExtension});
|
Object.assign(config.items[name], {'rom_extension': fileExtension});
|
||||||
};
|
};
|
||||||
|
if ((video_position) && (video_position !== config.defaults.video_position)) {
|
||||||
|
Object.assign(config.items[name], {'video_position': video_position});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
var configContents = JSON.stringify(config, null, 2);
|
var configContents = JSON.stringify(config, null, 2);
|
||||||
await fsw.writeFile(configFile, configContents);
|
await fsw.writeFile(configFile, configContents);
|
||||||
// Update main to include stuff with roms
|
|
||||||
var mainFile = configPath + 'main.json';
|
|
||||||
var main = await fsw.readFile(mainFile, 'utf8');
|
|
||||||
var main = JSON.parse(main);
|
|
||||||
main.items = {};
|
|
||||||
for await (var emu of emus) {
|
|
||||||
var emuPath = dataRoot + 'hashes/' + emu.name + '/roms/';
|
|
||||||
if (fs.existsSync(emuPath)) {
|
|
||||||
var roms = await fsw.readdir(emuPath);
|
|
||||||
if (roms.length > 0) {
|
|
||||||
main.items[emu.name] = {'video_position': emu.video_position};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (Object.keys(main.items).length < 9) {
|
|
||||||
main.display_items = Object.keys(main.items).length;
|
|
||||||
} else {
|
|
||||||
main.display_items = 9;
|
|
||||||
};
|
|
||||||
var mainContents = JSON.stringify(main, null, 2);
|
|
||||||
await fsw.writeFile(mainFile, mainContents);
|
|
||||||
// Render page for user if needed
|
|
||||||
if (render) {
|
if (render) {
|
||||||
return '';
|
return '';
|
||||||
} else {
|
} else {
|
||||||
renderRoms();
|
renderRoms();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove config items without art
|
|
||||||
async function purgeNoArt(dir) {
|
|
||||||
var configFile = configPath + dir + '.json';
|
|
||||||
var config = await fsw.readFile(configFile, 'utf8');
|
|
||||||
var config = JSON.parse(config);
|
|
||||||
for await (let item of Object.keys(config.items)) {
|
|
||||||
if ((config.items[item].hasOwnProperty('has_logo')) || (config.items[item].hasOwnProperty('has_video'))) {
|
|
||||||
if ((config.items[item].has_logo == false) || (config.items[item].has_video == false)) {
|
|
||||||
delete config.items[item];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var configContents = JSON.stringify(config, null, 2);
|
|
||||||
await fsw.writeFile(configFile, configContents);
|
|
||||||
renderRoms();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download art assets from IPFS
|
// Download art assets from IPFS
|
||||||
async function downloadArt(dir) {
|
async function downloadArt(dir) {
|
||||||
var metaData = await getMeta(dir);
|
var metaData = await fsw.readFile('./metadata/' + dir + '.json', 'utf8');
|
||||||
|
var metaData = JSON.parse(metaData);
|
||||||
|
if (fs.existsSync('/data/metadata/' + dir + '.json')) {
|
||||||
|
var userMetaData = await fsw.readFile(dataRoot + 'metadata/' + dir + '.json', 'utf8');
|
||||||
|
var userMetaData = JSON.parse(userMetaData);
|
||||||
|
Object.assign(metaData, userMetaData);
|
||||||
|
};
|
||||||
var shaPath = hashPath + dir + '/roms/';
|
var shaPath = hashPath + dir + '/roms/';
|
||||||
var files = await fsw.readdir(shaPath);
|
var files = await fsw.readdir(shaPath);
|
||||||
socket.emit('emptymodal');
|
socket.emit('emptymodal');
|
||||||
@ -439,91 +336,33 @@ io.on('connection', async function (socket) {
|
|||||||
await ipfsDownload(metaData[sha][variable[0]], dataRoot + dir + '/' + variable[1] + '/' + name + variable[2], 0)
|
await ipfsDownload(metaData[sha][variable[0]], dataRoot + dir + '/' + variable[1] + '/' + name + variable[2], 0)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (metaData[sha].hasOwnProperty('video_position')) {
|
if (metaData[sha].hasOwnProperty('position')) {
|
||||||
await fsw.writeFile(dataRoot + dir + '/videos/' + name + '.position', metaData[sha].video_position);
|
await fsw.writeFile(dataRoot + dir + '/videos/' + name + '.position', metaData[sha].position);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
socket.emit('modaldata', 'Downloaded All Files');
|
socket.emit('modaldata', 'Downloaded All Files');
|
||||||
getRoms(dir);
|
getRoms(dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set user linked metadata
|
// Set user linked metadata
|
||||||
async function userMeta(data) {
|
async function userMeta(data) {
|
||||||
await fsw.mkdir(metaPath, { recursive: true });
|
await fsw.mkdir(dataRoot + 'metadata', { recursive: true })
|
||||||
let romSha = data[0];
|
var romSha = data[0];
|
||||||
let linkSha = data[1];
|
var linkSha = data[1];
|
||||||
let dir = data[2];
|
var dir = data[2];
|
||||||
if (fs.existsSync(metaPath + dir + '.json')) {
|
if (fs.existsSync(dataRoot + 'metadata/' + dir + '.json')) {
|
||||||
var metaData = await fsw.readFile(metaPath + dir + '.json', 'utf8');
|
var metaData = await fsw.readFile(dataRoot + 'metadata/' + dir + '.json', 'utf8');
|
||||||
var metaData = JSON.parse(metaData);
|
var metaData = JSON.parse(metaData);
|
||||||
} else {
|
} else {
|
||||||
var metaData = {};
|
var metaData = {};
|
||||||
};
|
};
|
||||||
let link = {};
|
var link = {};
|
||||||
link[romSha] = {'ref': linkSha};
|
link[romSha] = {'ref': linkSha};
|
||||||
Object.assign(metaData, link);
|
Object.assign(metaData, link);
|
||||||
userMetadataFile = JSON.stringify(metaData, null, 2);
|
userMetadataFile = JSON.stringify(metaData, null, 2);
|
||||||
await fsw.writeFile(metaPath + dir + '.json', userMetadataFile);
|
await fsw.writeFile(dataRoot + 'metadata/' + dir + '.json', userMetadataFile);
|
||||||
getRoms(dir);
|
getRoms(dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove user linked metadata
|
|
||||||
async function removeMeta(data) {
|
|
||||||
await fsw.mkdir(metaPath, { recursive: true });
|
|
||||||
let romSha = data[0];
|
|
||||||
let dir = data[1];
|
|
||||||
let file = data[2];
|
|
||||||
let purge = data[3];
|
|
||||||
let fileExtension = path.extname(file);
|
|
||||||
let name = path.basename(file, fileExtension);
|
|
||||||
if (fs.existsSync(metaPath + dir + '.json')) {
|
|
||||||
var metaData = await fsw.readFile(metaPath + dir + '.json', 'utf8');
|
|
||||||
var metaData = JSON.parse(metaData);
|
|
||||||
} else {
|
|
||||||
var metaData = {};
|
|
||||||
};
|
|
||||||
if (metaData.hasOwnProperty(romSha)) {
|
|
||||||
delete metaData[romSha];
|
|
||||||
userMetadataFile = JSON.stringify(metaData, null, 2);
|
|
||||||
await fsw.writeFile(metaPath + dir + '.json', userMetadataFile);
|
|
||||||
}
|
|
||||||
// Delete any downloaded or uploaded art
|
|
||||||
for await (let variable of metaVariables) {
|
|
||||||
let artFile = dataRoot + dir + '/' + variable[1] + '/' + name + variable[2];
|
|
||||||
if (fs.existsSync(artFile)) {
|
|
||||||
fs.unlinkSync(artFile);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let vidPosFile = dataRoot + dir + '/videos/' + name + '.position';
|
|
||||||
if (fs.existsSync(vidPosFile)) {
|
|
||||||
fs.unlinkSync(vidPosFile);
|
|
||||||
}
|
|
||||||
// Delete rom and sha if requested
|
|
||||||
if (purge) {
|
|
||||||
let romFile = dataRoot + dir + '/roms/' + file;
|
|
||||||
let shaFile = dataRoot + 'hashes/' + dir + '/roms/' + file + '.sha1';
|
|
||||||
for await (var delFile of [romFile, shaFile]) {
|
|
||||||
if (fs.existsSync(delFile)) {
|
|
||||||
fs.unlinkSync(delFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Tell client to render
|
|
||||||
getRoms(dir);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get combined metadata
|
|
||||||
async function getMeta(dir) {
|
|
||||||
let metaDataRaw = await fsw.readFile('./metadata/' + dir + '.json', 'utf8');
|
|
||||||
let metaData = JSON.parse(metaDataRaw);
|
|
||||||
if (fs.existsSync(metaPath + dir + '.json')) {
|
|
||||||
let userMetaDataRaw = await fsw.readFile(metaPath + dir + '.json', 'utf8');
|
|
||||||
let userMetaData = JSON.parse(userMetaDataRaw);
|
|
||||||
metaData = merge(metaData, userMetaData);
|
|
||||||
};
|
|
||||||
return metaData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render files page
|
// Render files page
|
||||||
async function renderFiles() {
|
async function renderFiles() {
|
||||||
@ -537,230 +376,45 @@ io.on('connection', async function (socket) {
|
|||||||
socket.emit('renderfiledirs', dirs);
|
socket.emit('renderfiledirs', dirs);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send profile data to client
|
// Delete rom
|
||||||
async function renderProfiles() {
|
async function deleteRom(data) {
|
||||||
let profilesData = await fsw.readFile(home + '/profile/profile.json', 'utf8');
|
var fileName = data[0].replace("|","'");
|
||||||
let profilesJson = JSON.parse(profilesData);
|
var dir = data[1];
|
||||||
let profiles = [];
|
var fileExtension = path.extname(fileName);
|
||||||
if (Object.keys(profilesJson).length > 0) {
|
var name = path.basename(fileName, fileExtension);
|
||||||
for await (let profile of Object.keys(profilesJson)) {
|
let metaVars = [];
|
||||||
profiles.push(profilesJson[profile].username)
|
metaVars.push(...metaVariables);
|
||||||
}
|
metaVars.push(['video_position', 'videos', '.position']);
|
||||||
}
|
for await (var variable of metaVars) {
|
||||||
socket.emit('renderprofiles', profiles);
|
var file = dataRoot + dir + '/' + variable[1] + '/' + name + variable[2];
|
||||||
}
|
if (fs.existsSync(file)) {
|
||||||
|
fs.unlinkSync(file);
|
||||||
// Create a blank profile
|
};
|
||||||
async function createProfile(data) {
|
};
|
||||||
let user = data[0];
|
var romFile = dataRoot + dir + '/roms/' + fileName;
|
||||||
let pass = data[1];
|
var shaFile = dataRoot + 'hashes/' + dir + '/roms/' + fileName + '.sha1';
|
||||||
let auth = user + pass;
|
for await (var file of [romFile, shaFile]) {
|
||||||
// Create hash and store user in profile.json
|
if (fs.existsSync(file)) {
|
||||||
let hash = crypto.createHash('sha256').update(auth).digest('hex');
|
fs.unlinkSync(file);
|
||||||
let profilesData = await fsw.readFile(home + '/profile/profile.json', 'utf8');
|
};
|
||||||
let profilesJson = JSON.parse(profilesData);
|
|
||||||
profilesJson[hash] = {username: user};
|
|
||||||
profileFile = JSON.stringify(profilesJson, null, 2);
|
|
||||||
await fsw.writeFile(home + '/profile/profile.json', profileFile);
|
|
||||||
// Make directory for user with default config
|
|
||||||
await fsw.mkdir(home + '/profile/' + user);
|
|
||||||
await fsw.writeFile(home + '/profile/' + user + '/retroarch.cfg', retroArchCfg);
|
|
||||||
// Tell client to render profiles
|
|
||||||
let profiles = [];
|
|
||||||
for await (let profile of Object.keys(profilesJson)) {
|
|
||||||
profiles.push(profilesJson[profile].username)
|
|
||||||
}
|
|
||||||
socket.emit('renderprofiles', profiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a profile
|
|
||||||
async function deleteProfile(user) {
|
|
||||||
let profilesData = await fsw.readFile(home + '/profile/profile.json', 'utf8');
|
|
||||||
let profilesJson = JSON.parse(profilesData);
|
|
||||||
for await (let profile of Object.keys(profilesJson)) {
|
|
||||||
if (profilesJson[profile].username == user) {
|
|
||||||
delete profilesJson[profile];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
profileFile = JSON.stringify(profilesJson, null, 2);
|
|
||||||
await fsw.writeFile(home + '/profile/profile.json', profileFile);
|
|
||||||
if (user !== 'default') {
|
|
||||||
await fsw.rm(home + '/profile/' + user, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
let profiles = [];
|
|
||||||
for await (let profile of Object.keys(profilesJson)) {
|
|
||||||
profiles.push(profilesJson[profile].username)
|
|
||||||
}
|
|
||||||
socket.emit('renderprofiles', profiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send individual rom data
|
|
||||||
async function getRomData(data) {
|
|
||||||
let dir = data[0];
|
|
||||||
let file = data[1];
|
|
||||||
let fileExtension = path.extname(file);
|
|
||||||
let name = path.basename(file, fileExtension);
|
|
||||||
// Create the preview json
|
|
||||||
let rawConfig = await fsw.readFile(dataRoot + 'config/' + dir + '.json', 'utf8');
|
|
||||||
let config = JSON.parse(rawConfig);
|
|
||||||
let defaultVidPos = config.defaults.video_position;
|
|
||||||
config.display_items = 1;
|
|
||||||
config.items = {};
|
|
||||||
config.items[name] = {};
|
|
||||||
// Assemble metdata for client
|
|
||||||
let romData = {};
|
|
||||||
romData.file = file;
|
|
||||||
let hash = await fsw.readFile(hashPath + dir + '/roms/' + file + '.sha1', 'utf8');
|
|
||||||
romData.hash = hash;
|
|
||||||
let metaData = await getMeta(dir);
|
|
||||||
if (metaData.hasOwnProperty(hash)) {
|
|
||||||
if (metaData[hash].hasOwnProperty('ref')) {
|
|
||||||
romData.metadata = metaData[metaData[hash].ref];
|
|
||||||
if (metaData[hash].hasOwnProperty('name')) {
|
|
||||||
romData.metadata.name = metaData[hash].name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
romData.metadata = metaData[hash];
|
|
||||||
}
|
|
||||||
if (metaData[hash].hasOwnProperty('video_position')) {
|
|
||||||
romData.vidpos = true;
|
|
||||||
romData.metadata.video_position = metaData[hash].video_position;
|
|
||||||
config.items[name].video_position = metaData[hash].video_position;
|
|
||||||
} else {
|
|
||||||
romData.vidpos = false;
|
|
||||||
romData.metadata.video_position = defaultVidPos;
|
|
||||||
config.items[name].video_position = defaultVidPos;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
romData.metadata = false;
|
|
||||||
}
|
|
||||||
for await (let variable of metaVariables) {
|
|
||||||
if (fs.existsSync(dataRoot + dir + '/' + variable[1] + '/' + name + variable[2])) {
|
|
||||||
romData[variable[0]] = dir + '/' + variable[1] + '/' + name + variable[2];
|
|
||||||
config.items[name]['has_' + variable[0].replace('vid','video')] = true;
|
|
||||||
} else {
|
|
||||||
romData[variable[0]] = false;
|
|
||||||
config.items[name]['has_' + variable[0].replace('vid','video')] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write the preview config
|
|
||||||
await fsw.writeFile(hashPath + 'preview.json', JSON.stringify(config, null, 2));
|
|
||||||
// Send data to client
|
|
||||||
socket.emit('romdata', romData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process upload and add to custom user metadata
|
|
||||||
async function uploadArt(data) {
|
|
||||||
// Parse vars
|
|
||||||
let fileType = data[0];
|
|
||||||
let dir = data[1];
|
|
||||||
let file = data[2];
|
|
||||||
let hash = data[3];
|
|
||||||
// Upload data to local ipfs node
|
|
||||||
let fileData = Buffer.from(data[4]);
|
|
||||||
let ipfsRes = await ipfs.add(fileData);
|
|
||||||
let cid = ipfsRes.path;
|
|
||||||
await ipfs.pin.add(cid);
|
|
||||||
// Build and write custom metadata
|
|
||||||
if (fs.existsSync(metaPath + dir + '.json')) {
|
|
||||||
let rawMetaData = await fsw.readFile(metaPath + dir + '.json', 'utf8');
|
|
||||||
var metaData = JSON.parse(rawMetaData);
|
|
||||||
} else {
|
|
||||||
var metaData = {};
|
|
||||||
}
|
|
||||||
let metaUpdate = {};
|
|
||||||
metaUpdate[hash] = {};
|
|
||||||
metaUpdate[hash][fileType] = cid;
|
|
||||||
metaData = merge(metaData, metaUpdate);
|
|
||||||
await fsw.writeFile(metaPath + dir + '.json', JSON.stringify(metaData, null, 2));
|
|
||||||
// Write actual file
|
|
||||||
if (fileType == 'vid') {
|
|
||||||
var extension = '.mp4';
|
|
||||||
var filePath = '/videos/';
|
|
||||||
} else if (fileType == 'back') {
|
|
||||||
var extension = '.png';
|
|
||||||
var filePath = '/backgrounds/';
|
|
||||||
} else if (fileType == 'corner') {
|
|
||||||
var extension = '.png';
|
|
||||||
var filePath = '/corners/';
|
|
||||||
} else if (fileType == 'logo') {
|
|
||||||
var extension = '.png';
|
|
||||||
var filePath = '/logos/';
|
|
||||||
}
|
|
||||||
let fileExtension = path.extname(file);
|
|
||||||
let name = path.basename(file, fileExtension);
|
|
||||||
if (! fs.existsSync(dataRoot + dir + filePath)) {
|
|
||||||
await fsw.mkdir(dataRoot + dir + filePath);
|
|
||||||
}
|
|
||||||
await fsw.writeFile(dataRoot + dir + filePath + name + extension, fileData);
|
|
||||||
// Render game for client
|
|
||||||
getRomData([dir, file]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update video position metadata for rom
|
|
||||||
async function updateVidPosition(data) {
|
|
||||||
let dir = data[0];
|
|
||||||
let file = data[1];
|
|
||||||
let hash = data[2];
|
|
||||||
let position = data[3];
|
|
||||||
// Build and write custom metadata
|
|
||||||
if (fs.existsSync(metaPath + dir + '.json')) {
|
|
||||||
let rawMetaData = await fsw.readFile(metaPath + dir + '.json', 'utf8');
|
|
||||||
var metaData = JSON.parse(rawMetaData);
|
|
||||||
} else {
|
|
||||||
var metaData = {};
|
|
||||||
}
|
|
||||||
let metaUpdate = {};
|
|
||||||
metaUpdate[hash] = {};
|
|
||||||
metaUpdate[hash].video_position = position;
|
|
||||||
metaData = merge(metaData, metaUpdate);
|
|
||||||
await fsw.writeFile(metaPath + dir + '.json', JSON.stringify(metaData, null, 2));
|
|
||||||
getRomData([dir, file]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a custom metadata entry
|
|
||||||
async function customMeta(data) {
|
|
||||||
await fsw.mkdir(metaPath, { recursive: true });
|
|
||||||
let romSha = data[0];
|
|
||||||
let dir = data[1];
|
|
||||||
let name = data[2];
|
|
||||||
if (fs.existsSync(metaPath + dir + '.json')) {
|
|
||||||
var metaData = await fsw.readFile(metaPath + dir + '.json', 'utf8');
|
|
||||||
var metaData = JSON.parse(metaData);
|
|
||||||
} else {
|
|
||||||
var metaData = {};
|
|
||||||
};
|
};
|
||||||
metaData[romSha] = {};
|
|
||||||
metaData[romSha].name = name;
|
|
||||||
userMetadataFile = JSON.stringify(metaData, null, 2);
|
|
||||||
await fsw.writeFile(metaPath + dir + '.json', userMetadataFile);
|
|
||||||
getRoms(dir);
|
getRoms(dir);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Incoming socket requests
|
// Incoming socket requests
|
||||||
socket.on('renderconfigs', renderConfigs);
|
socket.on('renderconfigs', renderConfigs);
|
||||||
socket.on('renderroms', renderRoms);
|
socket.on('renderroms', renderRoms);
|
||||||
socket.on('renderromsdir', renderRomsDir);
|
socket.on('renderromsdir', renderRomsDir);
|
||||||
socket.on('getconfig', getConfig);
|
socket.on('getconfig', getConfig);
|
||||||
socket.on('getmeta', getMetaJSON);
|
|
||||||
socket.on('getroms', getRoms);
|
socket.on('getroms', getRoms);
|
||||||
socket.on('saveconfig', saveConfig);
|
socket.on('saveconfig', saveConfig);
|
||||||
socket.on('dldefaultfiles', dlDefaultFiles);
|
socket.on('dldefaultfiles', dlDefaultFiles);
|
||||||
socket.on('scanroms', scanRoms);
|
socket.on('scanroms', scanRoms);
|
||||||
socket.on('addtoconfig', addToConfig);
|
socket.on('addtoconfig', addToConfig);
|
||||||
socket.on('purgenoart', purgeNoArt);
|
|
||||||
socket.on('downloadart', downloadArt);
|
socket.on('downloadart', downloadArt);
|
||||||
socket.on('usermeta', userMeta);
|
socket.on('usermeta', userMeta);
|
||||||
socket.on('renderfiles', renderFiles);
|
socket.on('renderfiles', renderFiles);
|
||||||
socket.on('renderprofiles', renderProfiles);
|
socket.on('deleterom', deleteRom);
|
||||||
socket.on('createprofile', createProfile);
|
|
||||||
socket.on('deleteprofile', deleteProfile);
|
|
||||||
socket.on('getromdata', getRomData);
|
|
||||||
socket.on('uploadart', uploadArt);
|
|
||||||
socket.on('updatevidposition', updateVidPosition);
|
|
||||||
socket.on('removemeta', removeMeta);
|
|
||||||
socket.on('custommeta', customMeta);
|
|
||||||
socket.on('rendermeta', renderMeta);
|
|
||||||
// Render landing page
|
// Render landing page
|
||||||
if (fs.existsSync(dataRoot + 'config/main.json')) {
|
if (fs.existsSync(dataRoot + 'config/main.json')) {
|
||||||
renderRoms();
|
renderRoms();
|
||||||
@ -769,7 +423,7 @@ io.on('connection', async function (socket) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cloudcmd File browser data
|
// Cloudcmd File browser
|
||||||
baserouter.use('/files', cloudcmd({
|
baserouter.use('/files', cloudcmd({
|
||||||
config: {
|
config: {
|
||||||
root: dataRoot,
|
root: dataRoot,
|
||||||
@ -787,20 +441,3 @@ baserouter.use('/files', cloudcmd({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Cloudcmd File browser profile
|
|
||||||
baserouter.use('/profile', cloudcmd({
|
|
||||||
config: {
|
|
||||||
root: home + '/profile',
|
|
||||||
prefix: baseUrl + 'profile',
|
|
||||||
terminal: false,
|
|
||||||
console: false,
|
|
||||||
configDialog: false,
|
|
||||||
contact: false,
|
|
||||||
auth: false,
|
|
||||||
name: 'Files',
|
|
||||||
log: false,
|
|
||||||
keysPanel: false,
|
|
||||||
oneFilePanel: true,
|
|
||||||
zip: false
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|||||||
4606
metadata/3do.json
4606
metadata/3do.json
File diff suppressed because it is too large
Load Diff
46498
metadata/arcade.json
46498
metadata/arcade.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,367 +0,0 @@
|
|||||||
{
|
|
||||||
"18697DD8E640FA1578B3B4AE09AF484C14FB8410": {
|
|
||||||
"logo": "QmSJBF5SmRF17pvFzsRTSGeh1gmWsGh1HiULWUVh9G3pMe",
|
|
||||||
"name": "Asteroids (USA)",
|
|
||||||
"vid": "Qmc98EC4j5G5BgRLHVHNDw6se8D54v5xT48GxMRkA1iJud"
|
|
||||||
},
|
|
||||||
"67E405C37E55DEC4071018E8166AB4FAF9F21DF5": {
|
|
||||||
"logo": "QmURfdssjDm9P6KcxaMaLXKvBw8vzDQdZiCuBj8Ct8kXPJ",
|
|
||||||
"name": "Astro Chase (USA)",
|
|
||||||
"vid": "QmWnLvshgj6DBZLea8WRRyTqCNv7YXdSovTFERuCQnrm2p"
|
|
||||||
},
|
|
||||||
"1DFD9CB5EB608590C84E14376291F8C02FB1AB93": {
|
|
||||||
"logo": "QmaLmD1rS78PgkjFGDcBNgq8ijUxnvVJ3bu1tb2fDmnXpn",
|
|
||||||
"name": "Ballblazer (USA)",
|
|
||||||
"vid": "QmcR5V87KAkwhwey3xaiG9p2JYuaXMvDDEobjknmenw6oh"
|
|
||||||
},
|
|
||||||
"A339CDDFC28477C21C58783D2D3B0CF0F4626DDF": {
|
|
||||||
"logo": "QmdRwsaJGfSPwDdhdJ6XfDkQCb5Rep3i12Zt1Uo13rNEKM",
|
|
||||||
"name": "Battlezone",
|
|
||||||
"vid": "QmZ1vyBji3DLKU5ZF31txZfLApYP3wcE35gGP2trh6XKFR"
|
|
||||||
},
|
|
||||||
"B75D555D059AD61992F9F5A234129ECBF9504C35": {
|
|
||||||
"logo": "QmXiPjkDS2K7MwJ1fDM2AWoXNdKpfkLUnETEWUiYQKE3bv",
|
|
||||||
"name": "Beamrider (USA)",
|
|
||||||
"vid": "QmXyKVbo5UiMjzYZLTqq29uaSG4ooSFrbDgxsgp2njVRdq"
|
|
||||||
},
|
|
||||||
"E56CCC6BF427A832CE00540B02D065A2688260DC": {
|
|
||||||
"logo": "QmYnnuTWrzwQisvTizoP8KHg5aJ9yhGTTsx4wGwhLg34mK",
|
|
||||||
"name": "BerZerk (USA)",
|
|
||||||
"vid": "QmUsQPp9wiKET7oDpUvxVE8tsQff8JVgqJBuPrr861gbsm"
|
|
||||||
},
|
|
||||||
"1E1A20C104BF4D55ECA0CA1E72DC5DC64FA6B1B5": {
|
|
||||||
"logo": "QmcsCBdtDATdz8A5CTFkJ4MvzPx34RoSuh44uXh9Ptr6xi",
|
|
||||||
"name": "Blue Print (USA)",
|
|
||||||
"vid": "QmT4bBdaASSHyVkgRwau3KeFRtdS3E824mJTqA6EmVMNXz"
|
|
||||||
},
|
|
||||||
"BC7C81F758DD3E224E42AC8A16C1A12D69AED722": {
|
|
||||||
"logo": "QmbThDjtbpx22KZDE6mX3K6ttHG3HgaA4bJJoKVCFLxaTG",
|
|
||||||
"name": "Buck Rogers - Planet of Zoom (USA)",
|
|
||||||
"vid": "QmSL9HQHitQGqK8iK64CwUPCp1sNhPoyTXUHoTMd3W7yvS"
|
|
||||||
},
|
|
||||||
"C1A56A1FC51E09B1D8E93AF40DDD7596847E6164": {
|
|
||||||
"logo": "QmXi2AbqLgjot7KEk2nFqaJNCe3CGX7gjxrFRtN6GQbw7i",
|
|
||||||
"name": "Castle Blast (USA) (Unl)",
|
|
||||||
"vid": "Qmf8omSFm43MtM7bkZUQgnJwGooGGSeuzGQSsKm9LcY8MN"
|
|
||||||
},
|
|
||||||
"4E022E101D7346A0A8618018372740E7333B4A36": {
|
|
||||||
"logo": "QmUM15yrj5Bu67ZeyJJsTrJbu7ZGpbjnhWaEZHDZeiiGH1",
|
|
||||||
"name": "Castle Crisis (USA) (Unl)",
|
|
||||||
"vid": "QmVJNgSN5JsML5TGcgYnh3UqaUTSXeE1MYHP5VJ8VCFQJi"
|
|
||||||
},
|
|
||||||
"EB343B90D3DE696E69B1F039D4FB2C6AFB0D80C0": {
|
|
||||||
"logo": "QmXUQmwGzuFdi5QpL5SNmxdemp8Wjd82dnizxQTgMxPxPb",
|
|
||||||
"name": "Centipede (USA)",
|
|
||||||
"vid": "QmPZ5sEE8fPwCwVWtWxypvczsjoKpqhi8UZ6PCzd5CLirV"
|
|
||||||
},
|
|
||||||
"9F1EE191319EF0261CC7074A81C0E6D6B7464CD2": {
|
|
||||||
"logo": "QmdeFYnVUHGthGzpKBpQromfqbvQ4UWhfvUqhtRUCeSC6E",
|
|
||||||
"name": "Choplifter! (USA)",
|
|
||||||
"vid": "QmahcPZcQfLeq1spMGT2GyYxS1gWUivTMnH6Wm8zDApKJg"
|
|
||||||
},
|
|
||||||
"E0203C2124127EBA2D679BE548FA257ECC5B2607": {
|
|
||||||
"logo": "QmWpfd6F8GED6g86YZpjEVQhZZZUgzzBRWoMzAEc7w66Cs",
|
|
||||||
"name": "Congo Bongo (USA)",
|
|
||||||
"vid": "QmYR83qSqmoe6b52vHuCLLQjBruMzN7oJhs8uKgkrhgScD"
|
|
||||||
},
|
|
||||||
"FA6D77E6E9AFD0EA42FC9F9807932BF9E8FB170E": {
|
|
||||||
"logo": "QmaqFcdnztqSpjJ5Hp3ZXFfHujfpf2WFZu9vC7kpS97T2J",
|
|
||||||
"name": "Countermeasure (USA)",
|
|
||||||
"vid": "QmcmVTHavNERbjqhoKX6L17TsNYgnjfHBKekNz5hX5acvF"
|
|
||||||
},
|
|
||||||
"DB203973DF13B071DA46D52FDB078A76F4DB428D": {
|
|
||||||
"logo": "QmYYExhk1ArSCNVigKF6DAAiEQsxBYrMREALDpfeuBEP9k",
|
|
||||||
"name": "Decathlon (USA)",
|
|
||||||
"vid": "QmPs9sSC9UFqCY7S2Zq6SDhsGVsGmsMjqdZo1JJvWrjTYx"
|
|
||||||
},
|
|
||||||
"44F6DBF5013A70E646D5C5232981D517069FC449": {
|
|
||||||
"logo": "QmbBS3c5tRGGw6TegcQZrwiEBK3zEPR1Qwej6G2TexiZVm",
|
|
||||||
"name": "Defender (USA)",
|
|
||||||
"vid": "QmaT1ptxTbQes8Tu7C47oRCGQjwAABsGy5kEe8vx8BaRk6"
|
|
||||||
},
|
|
||||||
"387A76BA5912251E2285B7722004AD75E9EDE523": {
|
|
||||||
"logo": "QmUmC5paAFvbzaL4ADJ4x7mfuhwUaLicxGjHnwhC6fNVqs",
|
|
||||||
"name": "Dig Dug (USA)",
|
|
||||||
"vid": "QmdnXdS2Hkva6thusggxswndoVtMX6oBFGFu2pgH48L29Z"
|
|
||||||
},
|
|
||||||
"EA7B40CE1106378E1D409CAB6634FB2E483FF238": {
|
|
||||||
"logo": "QmRnZr6cVYDssaiyQg4dHMWeEfo14Uqr3hmDmjG6qFegDt",
|
|
||||||
"name": "Dreadnaught Factor, The (USA)",
|
|
||||||
"vid": "Qmc2KWinbeZtqwJthgyLPLeGoDxce9GRefuHrmfjVbuMgc"
|
|
||||||
},
|
|
||||||
"3D815594CF674694B04BB0FF90BF635D7D402FC2": {
|
|
||||||
"logo": "QmXahWf2Q5H9nqmT9zvbDXATnMzERa4rDMeUgVjYBYciJ9",
|
|
||||||
"name": "Frogger II - Threeedeep! (USA)",
|
|
||||||
"vid": "QmashLCC6yAWu9Y2NUgiwGBYrmE3bm8vSVKXqZy6SCSmtp"
|
|
||||||
},
|
|
||||||
"D9A695757F0494AEE92A50AB8A49558745E0CEDF": {
|
|
||||||
"logo": "QmfRb2WVd7yXBbywJJxMpAj8vYogVYgYBZa8FSGLTGfD4x",
|
|
||||||
"name": "Frogger (USA)",
|
|
||||||
"vid": "QmUkMmQALzoexfb1uH1pEhPP1gkNCy6JRrxn39zX28nGpy"
|
|
||||||
},
|
|
||||||
"0DF0D7F11D89E2C5B59CC9F98D67D98E203831AA": {
|
|
||||||
"logo": "QmQTqXAHzDhUroESmQMuyLACw4YLDc7qrEyiJxyPPufjY2",
|
|
||||||
"name": "Galaxian (USA)",
|
|
||||||
"vid": "QmdKPFabRNmS3u9cesVTx8DhKYfD3i382RFUi73UYxtCbS"
|
|
||||||
},
|
|
||||||
"F3C32B39580D589799DCEAD98BB206FBF3DF0504": {
|
|
||||||
"logo": "QmZnKTnvTsX6uh3kegW1ftpc5Zfw6stVq8EpbTigQJ42iA",
|
|
||||||
"name": "Gremlins (USA)",
|
|
||||||
"vid": "QmfJokqMPk28mKxhB5jfCEsV7dmKnF9KeLkAS3cepjbFMo"
|
|
||||||
},
|
|
||||||
"D0D8650EB07D1679004D491360C2C041E37C61CD": {
|
|
||||||
"logo": "QmVgDXYMZNB6UtMBxyX2j4Lq2BVgsm4Qh8xjYQza3f7EjU",
|
|
||||||
"name": "Gyruss (USA)",
|
|
||||||
"vid": "QmWyPUhSYdczNUFEptr8NKFL957rhKgPUd5DfXxsdmGVgt"
|
|
||||||
},
|
|
||||||
"D286ECB6793A0DDA9C9C68F6355DBE3ADE6922F9": {
|
|
||||||
"logo": "QmdJumA1Ng7XjdwM9ZtB1kBr6DEtR6ATznvKN4xnowxgjs",
|
|
||||||
"name": "H.E.R.O. (USA)",
|
|
||||||
"vid": "QmezV56L8QhPPYZi4n5oTq3DG5fhiZD82U8vZnT3BMp8GC"
|
|
||||||
},
|
|
||||||
"AE765943AE8D909B6CA6FF1560BA027ADEEAA515": {
|
|
||||||
"logo": "QmXeo5oW1HirBWVvgb8jTXod87keHLWphTG29gNUwX8Bhh",
|
|
||||||
"name": "James Bond 007 (USA)",
|
|
||||||
"vid": "QmQH9YwezVS5JBFsvgYPV8KzMZqimunmo8oaLDHeZ6aut8"
|
|
||||||
},
|
|
||||||
"0F74125EDAC90FFD1274A128DF2970C239348FB5": {
|
|
||||||
"logo": "QmWuSgH2U2NbG22F86bcDjQPW3QuLocsKtjcFWDWX1PrXW",
|
|
||||||
"name": "Joust (USA)",
|
|
||||||
"vid": "QmSwHpb7mzqDLzXMg43uVKTh86QrQMcueXSrLXZfscVZt1"
|
|
||||||
},
|
|
||||||
"FA02EE04CFAD90C6CCF9D9B984E3B7B3327FEC12": {
|
|
||||||
"logo": "QmbVZ4N7n7UWz7AHU7DczSZ643c7wsikQLwz9XdtAwfMHF",
|
|
||||||
"name": "Jungle Hunt (USA)",
|
|
||||||
"vid": "QmWQroeUL3EZYkGi6SgJRhtNaRdSRB37sNiKNqorExtgHT"
|
|
||||||
},
|
|
||||||
"A3E760D09E4DC6053286967576B5B7D42EE2FD38": {
|
|
||||||
"logo": "QmRqe5PY3wUMFHAL47ZQdoMwrwVgKGfv9GWyTMHBtC7faA",
|
|
||||||
"name": "JuniorPacMan",
|
|
||||||
"vid": "QmdmpG4aDxPSQ8G38AWxCW9JSb66DRaDGJRzzABzppfZY7"
|
|
||||||
},
|
|
||||||
"A80F876A89A8CEACC71DA5D9C02B546970B17730": {
|
|
||||||
"logo": "QmR7DDdCKUmb51ZLfgDbPfHLk6AS4bYLQGbEnHjGXhBSmo",
|
|
||||||
"name": "Kaboom! (USA)",
|
|
||||||
"vid": "QmeBMT6czaD6VTXyPHGbndYciTh6JoZsn3SJCJpGdFDKc3"
|
|
||||||
},
|
|
||||||
"C3DBEED7ADC0E282BF76332AB5A3209550E26D02": {
|
|
||||||
"logo": "QmdLoZBTM2FVMR4mXJMRajmZj6MzM1v8iAetFbED5obvo1",
|
|
||||||
"name": "Kangaroo (USA)",
|
|
||||||
"vid": "QmPZEobH9ykcMmdAWTGKrntLgykQjKwiWiukG2U8wMdJXj"
|
|
||||||
},
|
|
||||||
"9F31C85A13E919135438590601E33BEF11A8DF8A": {
|
|
||||||
"logo": "QmbksYNgsB29NSMtyP5FJ1tktPjZdAqCoc5xdo2etx8vBa",
|
|
||||||
"name": "Keystone Kapers (USA)",
|
|
||||||
"vid": "QmdkWAKm7ixom612e27LoqZdR5h18bNgaZfLxjwgofRBCo"
|
|
||||||
},
|
|
||||||
"9547C56E2951E1B3A713B0B99D3EDB43C9D3CDD8": {
|
|
||||||
"logo": "QmabvY8TyRDbEuCRFYKTu73P9UaFLrjdWHE6tc5XrneQoX",
|
|
||||||
"name": "Koffi - Yellow Kopter (USA) (Unl)",
|
|
||||||
"vid": "QmNYxQYQz7nNm2LxcR7mFcb12KFXZLdwvWBTGSMt4EzEjq"
|
|
||||||
},
|
|
||||||
"C7EB2B4A46A197ADFEDF33C2CCC17D8424D681C4": {
|
|
||||||
"logo": "QmSoroGf1gZgSEJTv7b4x5gz4R7ZTuFstTbf5JG93ScMAd",
|
|
||||||
"name": "K-Razy Shoot-Out (USA)",
|
|
||||||
"vid": "QmaYe4g5vrTeSb7EkX6fizB5Mjfm8qMwkv2mJ1urEVPrsQ"
|
|
||||||
},
|
|
||||||
"A293B6B06050E11029D97137F2B1B36059567273": {
|
|
||||||
"logo": "QmeKUDqJaispbQcSYmmZSwhbwpU7iaitT3VBhAZQm5dBKj",
|
|
||||||
"name": "Mario Bros. (USA)",
|
|
||||||
"vid": "QmWd5t6Q4b1ELMwLFmEqQpxVMXFnL8eH6kWcSvcynW6TUh"
|
|
||||||
},
|
|
||||||
"F5512460C6B6D3A1BAA0B23ADF6A3A37FE308EB3": {
|
|
||||||
"logo": "QmS6Ax6RpdG5wLNq2p7c7JnrSUps6q9NJVNZJD6zvXxsiw",
|
|
||||||
"name": "MegaMania (USA)",
|
|
||||||
"vid": "QmV6sV7kXUPd5eGf4oo97nwRkomNHVK8BKDBFe2Fnj8rnC"
|
|
||||||
},
|
|
||||||
"32D86A381FEFF43149B66336DCC4BA2523273228": {
|
|
||||||
"logo": "QmeBpap7TRpUwFRvfgb2YcgbdiEK4H3kvD4uju7oxkBu9z",
|
|
||||||
"name": "Meteorites (USA)",
|
|
||||||
"vid": "QmaxTgcSwkZSLY9mo6UMpz2Kxq2ck4vwMKg2AZUn7j3xWU"
|
|
||||||
},
|
|
||||||
"1243A8ECFC02A74AE4E1F4FCDA38502776F45CB0": {
|
|
||||||
"logo": "QmdQ9j2WTo5gjt1TmuS5WEPmzZCnyFZnZVWQGpEiNpuuoT",
|
|
||||||
"name": "Millipede (USA)",
|
|
||||||
"vid": "QmS9LMkk4EPSJCTs72HGq5nzVjdjagw7oQLPHwUTGCs2Tz"
|
|
||||||
},
|
|
||||||
"0564B1867A0B570D66DFCBC11ADC3E51A2C6F28C": {
|
|
||||||
"logo": "QmeiYfTs4NsivMGtVPCNBRzM5XEB2MMhTZjy8hmXHqvLva",
|
|
||||||
"name": "Miner 2049er Starring Bounty Bob (USA)",
|
|
||||||
"vid": "QmYnYpiQX5Au2bL6Y7Gu8EsnF6uUaz2RwHi68mFKq8ADcv"
|
|
||||||
},
|
|
||||||
"7E2A05625598C7E6615F969AA695E64315CD9846": {
|
|
||||||
"logo": "Qmc2M6T5bN7dN65GGs5QwkvyPtJMgpN1WeeFMLYZMw6Hng",
|
|
||||||
"name": "Montezuma's Revenge featuring Panama Joe (USA)",
|
|
||||||
"vid": "QmPgzVcooQNG4bHxUEehkn9EAdQZszuCR3RhRKsHTXxsAE"
|
|
||||||
},
|
|
||||||
"02DD5B9278C06AE8CBB6DACC094E238FBACF08D2": {
|
|
||||||
"logo": "QmbCgUbzYBHEC7UgwHyAMKgbbDPYtb3HPwzmS9Pubft9EQ",
|
|
||||||
"name": "Moon Patrol (USA)",
|
|
||||||
"vid": "QmUf8DgWXyL1eyQyHZdM1yvYzDHhep7Xs6G3YJgnvdKLkB"
|
|
||||||
},
|
|
||||||
"2E7AE57260624D1F5710D445A0936F0C280A6655": {
|
|
||||||
"logo": "QmY8iAcgbkK8u93bs31Ufy3r1VHPMUjuWRUBtiC8pSefQ9",
|
|
||||||
"name": "Mountain King (USA)",
|
|
||||||
"vid": "QmW1pFdy7shVd3hJZiVTkWZGSBe7TTKwTDZrKczSe9Lgmr"
|
|
||||||
},
|
|
||||||
"002E6F8C7533B03F8041E7F1896589C0AB0E17A6": {
|
|
||||||
"logo": "QmeXVArivdsCKhB6Fr4SYwvsKEPkBsxDqQYqxs3g6XZHoz",
|
|
||||||
"name": "Mr. Do!'s Castle (USA)",
|
|
||||||
"vid": "QmTzFpQEGbSCtB1Noxn8CjZvusaS1uN3ms8P19t3ReL7tA"
|
|
||||||
},
|
|
||||||
"F93D72444D76075273E508555423936530875148": {
|
|
||||||
"logo": "QmRsmAGf4MnHd9HyCRw44uq869ZySzMkZTB1KTpp4Z9Y1K",
|
|
||||||
"name": "Ms. Pac-Man (USA)",
|
|
||||||
"vid": "QmaZsa9y9HnvKkXoh9TiDiB5qVBCM6WYqDb13kUYNHykYy"
|
|
||||||
},
|
|
||||||
"EE4B3C6F08AA9FDFAB532EEC98136306B6923F75": {
|
|
||||||
"logo": "QmVGMiNFNA5BXihh62Royzo5NmWiUX4bnnCmgrq9paq7rJ",
|
|
||||||
"name": "Pac-Man (USA)",
|
|
||||||
"vid": "Qmd9FrnaZxie1kjaCwcrVEQwFYYV8rJ8Y1wtEUMNN1jgV5"
|
|
||||||
},
|
|
||||||
"B051F0FB2E816787AB6EED0CB008B9F8CC9963DC": {
|
|
||||||
"logo": "QmTHKZHB2DiJYxjayL1Udo3oF6zgazmBMgAfexxMb1Fwpv",
|
|
||||||
"name": "Pitfall II - Lost Caverns (USA)",
|
|
||||||
"vid": "QmWvq7C2tpimgY8yTEoTVwbUBrMSaU5micsJazYMsB3Zwn"
|
|
||||||
},
|
|
||||||
"D1C7DC3DE097A4BFAB82235ACEC69A97E6E05F18": {
|
|
||||||
"logo": "QmT6der1wYz5G9wWvC8BD76417RXt4zmzz3Sjk6dhyLR81",
|
|
||||||
"name": "Pitfall! (USA)",
|
|
||||||
"vid": "QmWnriRFBrDE2YhKUYKam31ohiU3TT6k1XBb7BZrggHtLa"
|
|
||||||
},
|
|
||||||
"D504764C5894EE99C0B5E2024CBE833EBBA3AB17": {
|
|
||||||
"logo": "QmeFSsfRnJrCo85pJxUzWLmKwLqP456QXMCUkA5Vog7eav",
|
|
||||||
"name": "Pole Position (USA)",
|
|
||||||
"vid": "QmdH7VbvPSh6kudeZNMt8emHSNiiu4EABfeMUUXu3Gx94S"
|
|
||||||
},
|
|
||||||
"E615AAA0C1D56AC83B91F6087970FA708D6433BF": {
|
|
||||||
"logo": "QmQEQAvhKGK9oSw7TQxvhqLDfaLgyiMAdB99RYe9XirDdy",
|
|
||||||
"name": "Popeye (USA)",
|
|
||||||
"vid": "QmeQJeRMbFyLMrS8kkxNvB3Ts4woTtNmYVoZQDytnRZXTk"
|
|
||||||
},
|
|
||||||
"E6966BDE5CD4167020C9D21BAB613AF1648B4D75": {
|
|
||||||
"logo": "QmV2U7z5Xvc92keWQss7bEJavf22G153ENhTiVM8DFrkth",
|
|
||||||
"name": "Q-bert (USA)",
|
|
||||||
"vid": "QmP3LK8fTjyG9ToJZugmwm7kicr8uHEFdyw1RiiqwqqrmX"
|
|
||||||
},
|
|
||||||
"786CDF54D1625E0D3EA8814506FDEEBA5E4ECE48": {
|
|
||||||
"logo": "QmcTdE9fG2W42AWWGanSDueyzEbFoDZADZtH8dDBt1DFR1",
|
|
||||||
"name": "Qix (USA)",
|
|
||||||
"vid": "QmedGY8aE5Do1N91CZw2Ki8FhTiMTq8qs8cyC5aocH4WV2"
|
|
||||||
},
|
|
||||||
"A94D06739FA19453FA586A836EAACAE11FE93989": {
|
|
||||||
"logo": "QmNTBNDoGMjxk7ZBe3thDhcZHPfr1K6LUK3Cv7mmq1Rdon",
|
|
||||||
"name": "RealSports Baseball (USA)",
|
|
||||||
"vid": "QmVi7v5sHztkdZN5exui5LpWhRXqm1hMkNHwicjNHhP3zw"
|
|
||||||
},
|
|
||||||
"A0DBD654ED3BA2583CA175A1356924D9529884DF": {
|
|
||||||
"logo": "QmUgfKdaMbzbrQ47ByYrpkrXKsASyaiydXonYTAEynMUMp",
|
|
||||||
"name": "RealSports Football (USA)",
|
|
||||||
"vid": "QmQz26rhmE1UxrxwqwyEZm6Q75amkVjHitUvVQXkTtrk8o"
|
|
||||||
},
|
|
||||||
"C911CC914F3923D5AEB1A897E6DC4A551A9A53DA": {
|
|
||||||
"logo": "QmWRmftRZSt9HpFQuFrKgxbQXeCyyqxV1fgwCaWBWvZDHM",
|
|
||||||
"name": "RealSports Soccer (USA)",
|
|
||||||
"vid": "QmNq69X2m7xWogqkqcThaGzEDhWEc6HRBNAZDPZxFMkxmP"
|
|
||||||
},
|
|
||||||
"9253D7286CF87999474B37DF242BA23B4358EF4A": {
|
|
||||||
"logo": "QmdggyhfUoj3kDxyykeyPQLHhKo8iAPK8qqGTfybKg6Y8N",
|
|
||||||
"name": "Rescue on Fractalus! (USA)",
|
|
||||||
"vid": "QmdAhDGegbXeyfGPhxUgx29TFHHxDWPDBwdCFcDcUzwbkV"
|
|
||||||
},
|
|
||||||
"CE9BD3B0847C5E187C3E8F667C8F5EA771C49965": {
|
|
||||||
"logo": "QmTMtgzQzdkRbucQ2UnrXLN3cnQzA9y3BcWcy85xN3Ddef",
|
|
||||||
"name": "River Raid (USA)",
|
|
||||||
"vid": "QmdiQJGpsQXesH6hSF1iUMcbVEPYj1gNAMP97ypH8Jg36E"
|
|
||||||
},
|
|
||||||
"9175E6DACA58102AC8B9729D7B0336CF09977D12": {
|
|
||||||
"logo": "Qma6EtHyQw89y6AEGTTd9oTTjfvY6JjcmfvyT5CFwAET1c",
|
|
||||||
"name": "Robotron 2084 (USA)",
|
|
||||||
"vid": "QmW5W8tfaE8zVGB5Dy7XyMVDd6FcbRjoYP8aHLFT561y8j"
|
|
||||||
},
|
|
||||||
"127ED1C31C3E85D10212EEE7B9E6D71AEF7DD891": {
|
|
||||||
"logo": "QmVFirQhANh8PAwebsPg4p622eQTUr8SUoq5tijWu88Dux",
|
|
||||||
"name": "Space Dungeon (USA)",
|
|
||||||
"vid": "QmQZPvKr4BWSwEKs3bNGPG3ARBZiM3uQNvgXS4x3F3jb3f"
|
|
||||||
},
|
|
||||||
"CF411DB770217B5AF5FCFA32725CC587A8B9E710": {
|
|
||||||
"logo": "QmbvpjyfFAdh78FncJXi4yiR5uP5r2EAjZZUsgUixbij5k",
|
|
||||||
"name": "Space Invaders (USA)",
|
|
||||||
"vid": "QmY86x5hrs7vCpJ2roHc6QuBiqDVLU1N8KJLr7zfGRUh4i"
|
|
||||||
},
|
|
||||||
"5AE20E767D9819FE601F3B1EA6D4506AE2F2CEEF": {
|
|
||||||
"logo": "QmSyML8DYf5FKoartTHUoGSaUcftHdx3LVwmspFYrTnm7q",
|
|
||||||
"name": "Space Shuttle - A Journey Into Space (USA)",
|
|
||||||
"vid": "QmcWizEUkRraMAvciPpsVWrUzpYq1Y8B7KfHCGJf8r13WQ"
|
|
||||||
},
|
|
||||||
"09D192A3598CDADFB61ECEB430B3D5892E40962F": {
|
|
||||||
"logo": "QmWte466CT5962MQqx9TbG4qKxAreP1zvwUsk7dB1jS7FR",
|
|
||||||
"name": "Stargate (USA)",
|
|
||||||
"vid": "QmPFnrGz7FQUootHdY5ZbLkz2hQ7tur2JUTqmfHoeaHS6r"
|
|
||||||
},
|
|
||||||
"57DFC22C9805555F957ED51A2DC7BF35AD00C9D9": {
|
|
||||||
"logo": "QmWVN7QL5yz4GXAjGJ15YUXtMxpVUmYVGi2ceGbfnZrdZD",
|
|
||||||
"name": "Star Raiders (USA)",
|
|
||||||
"vid": "QmSr6WuMCx5X52h8c2kmeNJg5fWJdTSHeuVu7QxK327vHD"
|
|
||||||
},
|
|
||||||
"19801454AB86F2A5CF691FCD577A71AD2B8DC7D5": {
|
|
||||||
"logo": "QmQuH8XDbmMTag4a6JMNMHtxan6itTidDdFm2K9bUc8Ye7",
|
|
||||||
"name": "Star Trek - Strategic Operations Simulator (USA)",
|
|
||||||
"vid": "QmUzzCnJkWb5vdV1FY3k3sUQD3u63joTYR4XVDe6zda32y"
|
|
||||||
},
|
|
||||||
"BF77048ABC36BA2CBA5EEEAC812802D6A0EBA891": {
|
|
||||||
"logo": "QmegpAeew8eytGwBtYgArwx1CqFYS1buxafQcs9yAiUGYE",
|
|
||||||
"name": "Star Wars - Return of the Jedi - Death Star Battle (USA)",
|
|
||||||
"vid": "QmTWG7Hx8fvdWixRaLKhpMhq5VWzjnzWF5b1JqN1W8C4FU"
|
|
||||||
},
|
|
||||||
"AB676FFE054B23BDD43CEE2498AFEFAABBF9355B": {
|
|
||||||
"logo": "QmduE8bZMQ1DLUn7VVT8MRpyZ31wBKS2NVZciH7J5j3zBd",
|
|
||||||
"name": "Star Wars - The Arcade Game (USA)",
|
|
||||||
"vid": "QmaDuPMh32BjZgJN6VsGca9n7jaoUXvF1tWiHpa78VkaWd"
|
|
||||||
},
|
|
||||||
"8205093F5FB1FDF0CCB71D667EA0A3849FB0A0F2": {
|
|
||||||
"logo": "QmdiTUqMwbuCiSpBT4XBGyVfF5HuGpdTtrMLrgYV1dwqxD",
|
|
||||||
"name": "Super Cobra (USA)",
|
|
||||||
"vid": "Qmb62ZEBSQhcCGjtqgtKSKH6tRhNbqZeNdQNsHk7SFwe58"
|
|
||||||
},
|
|
||||||
"238CB448555D8BB5C96AA0354718411E8777C613": {
|
|
||||||
"logo": "QmNUacpZwsrCNQc2MrDBj2YNyqKrq9gVoWykm5CMRm2Du3",
|
|
||||||
"name": "Super Pac-Man (USA)",
|
|
||||||
"vid": "Qme6UNBn1ZK1cW7scsSfBeQNcvEgRQ4A9myix6jThR5MMg"
|
|
||||||
},
|
|
||||||
"4046F60602254A7D86D2A6EFD1A330455EAEC119": {
|
|
||||||
"logo": "Qmbndnwr65MpH2ZKH7gYgXjihYKLSuQUfaJpuA1DUnPUjP",
|
|
||||||
"name": "The Last Starfighter (USA)",
|
|
||||||
"vid": "QmaG6SgHsE1ZSYjTgtK5zfoL1CzVd2rYtNfXAgTL5w55H1"
|
|
||||||
},
|
|
||||||
"6C633DE50E6CC0C0C40061CD24E6F78F552A438F": {
|
|
||||||
"logo": "QmRnpaubP1ek7Cemrw9v6wGHnED29CtMaQQrtmmp7yRHSq",
|
|
||||||
"name": "Vanguard (USA)",
|
|
||||||
"vid": "QmSAtWhrDVu9W21Ru9iUQW4ftksTRX63NSUGthCRHyJjkv"
|
|
||||||
},
|
|
||||||
"49DEBDF3C133FDE9905D6D9137DA5D8DFF8D6283": {
|
|
||||||
"logo": "QmdR2fqAUZAmBZMpM3DdquW463kzMhqVwPbCCS1RyWLk4U",
|
|
||||||
"name": "Wizard of Wor (USA)",
|
|
||||||
"vid": "Qme2stUTVaQix1sTDVujXt7vjYLbKWPiRprSjaFFFnMKhw"
|
|
||||||
},
|
|
||||||
"C3CF33216DFA484309B923F21829D040486BC481": {
|
|
||||||
"logo": "QmSEYo3vsjWY7QDEe84CPzRrtEdFBp6VZd4nQizXhv4CRn",
|
|
||||||
"name": "Xevious",
|
|
||||||
"vid": "QmP4nVrr7pQuo2Ao483fPhpTeu2wKHkCV5fPMmC1XkqyRn"
|
|
||||||
},
|
|
||||||
"4A4C4A25C8ED1B0AE79CA22684D7D311A564A7B5": {
|
|
||||||
"logo": "Qmc18GGCAdh1S3h5hhFtjxBeArTdNU7iwPdWDu1574APLe",
|
|
||||||
"name": "Zaxxon (USA)",
|
|
||||||
"vid": "QmQuxstBK3mjjoNKnyEYoxtphgFR2Xt3CybvzLb82Uyfws"
|
|
||||||
},
|
|
||||||
"55DA563B4394508C23D866FB1E543262FB45536D": {
|
|
||||||
"logo": "QmRfqMXY7gdGB6mzNrr8XGRWRHXKbWAJxtQ1ZmUW2qoVSs",
|
|
||||||
"name": "Zenji (USA)",
|
|
||||||
"vid": "QmRQrC3tXVi9KkEnLEiqCq8pYSdHr68X7oDLDs2xwTmktG"
|
|
||||||
},
|
|
||||||
"18315BEC1E62102290FEEB77FB290E1266CF5BFC": {
|
|
||||||
"logo": "QmVzAYCnfJAXXVJsvqvcxrvK75ZHkr4zvoGW8rQJitnSvr",
|
|
||||||
"name": "Zone Ranger (USA)",
|
|
||||||
"vid": "QmX6eHZqHk2adF7dz9afyedH3FioHwvw77r3pAdmt1YGfP"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"file":"/data/main/videos/nds.mp4",
|
"file":"/data/main/videos/nds.mp4",
|
||||||
"cid":"QmXX9BebDhPr2oMuv5L9SizTwsbhvR9J3sYPMSKaRAu11Y"
|
"cid":"QmfP1qiC1ej9dCeX1dBNuRyMXhQTDsHKwiYi2cBpJxmvf6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/main/videos/psx.mp4",
|
"file":"/data/main/videos/psx.mp4",
|
||||||
@ -119,10 +119,6 @@
|
|||||||
"file":"/data/main/videos/atari2600.mp4",
|
"file":"/data/main/videos/atari2600.mp4",
|
||||||
"cid":"QmXR4JG2XMjUu5qwBkR6HBR8fC8MkqL1cCZDFJGpcMuENv"
|
"cid":"QmXR4JG2XMjUu5qwBkR6HBR8fC8MkqL1cCZDFJGpcMuENv"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/main/videos/atari5200.mp4",
|
|
||||||
"cid":"QmedGY8aE5Do1N91CZw2Ki8FhTiMTq8qs8cyC5aocH4WV2"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/main/backgrounds/psx.png",
|
"file":"/data/main/backgrounds/psx.png",
|
||||||
"cid":"QmXtrPJCdPuBPemYLxC8shQYHNHk59ES26FyGqR12bZiD2"
|
"cid":"QmXtrPJCdPuBPemYLxC8shQYHNHk59ES26FyGqR12bZiD2"
|
||||||
@ -183,10 +179,6 @@
|
|||||||
"file":"/data/main/backgrounds/atari2600.png",
|
"file":"/data/main/backgrounds/atari2600.png",
|
||||||
"cid":"QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D"
|
"cid":"QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/main/backgrounds/atari5200.png",
|
|
||||||
"cid":"QmNoDmFBcrE2wDHMVaZxCvRQsua1UjuYSfnxGshKY8YLKA"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/main/backgrounds/segaCD.png",
|
"file":"/data/main/backgrounds/segaCD.png",
|
||||||
"cid":"Qme8N7PzVPstNCQ3V5PpRmmBzokwRsHPjJQJgDbjBxnpmR"
|
"cid":"Qme8N7PzVPstNCQ3V5PpRmmBzokwRsHPjJQJgDbjBxnpmR"
|
||||||
@ -225,7 +217,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/main/backgrounds/nds.png",
|
"file":"/data/main/backgrounds/nds.png",
|
||||||
"cid":"QmeEfewgssCsPhzzfc5ZG4hVx8xgsvEKof7yc8rgKE6e8u"
|
"cid":"QmRi2JqtZojLMAfcjgZjDBuTJzMoosJoDz3fcmaG4StyJi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/main/backgrounds/pce.png",
|
"file":"/data/main/backgrounds/pce.png",
|
||||||
@ -311,10 +303,6 @@
|
|||||||
"file":"/data/main/logos/atari2600.png",
|
"file":"/data/main/logos/atari2600.png",
|
||||||
"cid":"QmTKD7vsGBwTkRgaxV7mFjgxHrYrkiAnLTjNXs4HV9kTQV"
|
"cid":"QmTKD7vsGBwTkRgaxV7mFjgxHrYrkiAnLTjNXs4HV9kTQV"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/main/logos/atari5200.png",
|
|
||||||
"cid":"QmQZRWCPp2wRVztiWgcAKNKFuRXBTNCpiaeSxw7LEhS2bc"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/main/logos/segaCD.png",
|
"file":"/data/main/logos/segaCD.png",
|
||||||
"cid":"QmV1b4fCtBo4iRqZDLTYctWE9PdKqVRqi3ZfgjffTKzCsD"
|
"cid":"QmV1b4fCtBo4iRqZDLTYctWE9PdKqVRqi3ZfgjffTKzCsD"
|
||||||
@ -439,10 +427,6 @@
|
|||||||
"file":"/data/main/corners/atari2600.png",
|
"file":"/data/main/corners/atari2600.png",
|
||||||
"cid":"QmarPeRQzXTGPY88h744vhgCu1RGJtpV9HyTtjhrQmnQhq"
|
"cid":"QmarPeRQzXTGPY88h744vhgCu1RGJtpV9HyTtjhrQmnQhq"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/main/corners/atari5200.png",
|
|
||||||
"cid":"QmfTa42eaceCXkdg9NDkAziPNAfKUfAFdftrbFt2XYrkaU"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/main/corners/segaCD.png",
|
"file":"/data/main/corners/segaCD.png",
|
||||||
"cid":"Qmc39zPV8PwrZTnMHNqk4mPXZpLkhkTAgkaF2B5f7WKhiq"
|
"cid":"Qmc39zPV8PwrZTnMHNqk4mPXZpLkhkTAgkaF2B5f7WKhiq"
|
||||||
@ -511,6 +495,10 @@
|
|||||||
"file":"/data/segaCD/backgrounds/default.png",
|
"file":"/data/segaCD/backgrounds/default.png",
|
||||||
"cid":"Qme8N7PzVPstNCQ3V5PpRmmBzokwRsHPjJQJgDbjBxnpmR"
|
"cid":"Qme8N7PzVPstNCQ3V5PpRmmBzokwRsHPjJQJgDbjBxnpmR"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/segaCD/bios/bios_CD_U.bin",
|
||||||
|
"cid":"QmR3LZ4wsd8HAEfpYrWHt4KSKSwkcLt243dETYCr9VwRAo"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/segaCD/corners/default.png",
|
"file":"/data/segaCD/corners/default.png",
|
||||||
"cid":"Qmc39zPV8PwrZTnMHNqk4mPXZpLkhkTAgkaF2B5f7WKhiq"
|
"cid":"Qmc39zPV8PwrZTnMHNqk4mPXZpLkhkTAgkaF2B5f7WKhiq"
|
||||||
@ -535,13 +523,17 @@
|
|||||||
"file":"/data/gb/backgrounds/default.png",
|
"file":"/data/gb/backgrounds/default.png",
|
||||||
"cid":"QmWcgbLd6rKAUGp1Q2DEfEQf1t8p8g2HN1XaHL8ZPVGWPU"
|
"cid":"QmWcgbLd6rKAUGp1Q2DEfEQf1t8p8g2HN1XaHL8ZPVGWPU"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/gb/bios/dmg_boot.bin",
|
||||||
|
"cid":"QmXzNc3D578bHUgLwD5mAkQHzAVhmsbL1jqhfJqJ5AEN9T"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/gb/corners/default.png",
|
"file":"/data/gb/corners/default.png",
|
||||||
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/nds/backgrounds/default.png",
|
"file":"/data/nds/backgrounds/default.png",
|
||||||
"cid":"QmeEfewgssCsPhzzfc5ZG4hVx8xgsvEKof7yc8rgKE6e8u"
|
"cid":"QmRi2JqtZojLMAfcjgZjDBuTJzMoosJoDz3fcmaG4StyJi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/nds/corners/default.png",
|
"file":"/data/nds/corners/default.png",
|
||||||
@ -551,6 +543,10 @@
|
|||||||
"file":"/data/gba/backgrounds/default.png",
|
"file":"/data/gba/backgrounds/default.png",
|
||||||
"cid":"QmQWF2Dr3QnwJhzaoptJUF2XzCHJzwoiKxuReDshBDr6Yf"
|
"cid":"QmQWF2Dr3QnwJhzaoptJUF2XzCHJzwoiKxuReDshBDr6Yf"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/gba/bios/gba_bios.bin",
|
||||||
|
"cid":"QmVWJiqWxzExiLq7T2j9ADp3H3rDoNnAkAr1sttcsw8Utq"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/gba/corners/default.png",
|
"file":"/data/gba/corners/default.png",
|
||||||
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
||||||
@ -575,6 +571,10 @@
|
|||||||
"file":"/data/segaGG/backgrounds/default.png",
|
"file":"/data/segaGG/backgrounds/default.png",
|
||||||
"cid":"QmV8LcGgjpqsXGqMewWhJVCbaSJn5QfzitcSmtPy974HaZ"
|
"cid":"QmV8LcGgjpqsXGqMewWhJVCbaSJn5QfzitcSmtPy974HaZ"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/segaGG/bios/bios.gg",
|
||||||
|
"cid":"QmcM47ctYJG2ijtuWA5QcjsKXonoGKTiSKQ5j9uzRVUueP"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/segaGG/corners/default.png",
|
"file":"/data/segaGG/corners/default.png",
|
||||||
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
||||||
@ -607,6 +607,10 @@
|
|||||||
"file":"/data/pce/backgrounds/default.png",
|
"file":"/data/pce/backgrounds/default.png",
|
||||||
"cid":"QmXVxvCqNDhTYpEvweW6hGDNG5ojpsLhsGF4MEChsma2JW"
|
"cid":"QmXVxvCqNDhTYpEvweW6hGDNG5ojpsLhsGF4MEChsma2JW"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/pce/bios/syscard3.pce",
|
||||||
|
"cid":"QmU5f43aAAQtAtrDVqcq5BWz95HSNa4wvqJwgpjbK5xVTx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/pce/corners/default.png",
|
"file":"/data/pce/corners/default.png",
|
||||||
"cid":"QmeVdvxJt2tcUWQJubJamt4MSuq4A1SfTvSz6F3J5XzhMo"
|
"cid":"QmeVdvxJt2tcUWQJubJamt4MSuq4A1SfTvSz6F3J5XzhMo"
|
||||||
@ -617,35 +621,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/psx.json",
|
"file":"/data/config/psx.json",
|
||||||
"cid":"Qma4M5jcHXMWiHAVSchgF6kZ4sBzSS3q5kZKWpYHxWsCTj"
|
"cid":"QmUBMVyuhrKd6sY7CCSubfb5cEUQXrRoJdP99EgMTDRUVi"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/pce.json",
|
"file":"/data/config/pce.json",
|
||||||
"cid":"QmNmCQQAae4QG6JxNvzqfMP2XgsF8w6gDzeXiyKKz7JwTo"
|
"cid":"QmPgmpYgb7eymXmTWiWWyXkcY6ACtwC4v4D74xbsPaad37"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/pce/bios/pce.zip",
|
|
||||||
"cid":"QmVZuSTmjWBbFvsgkV7fp4R1ZmjveK9Fap4Nxi6DhbpZPT"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/gbc.json",
|
"file":"/data/config/gbc.json",
|
||||||
"cid":"QmYb7Qs81M6v2AR9Fe1i2hS4ff1K8NWdBe3ZEHBoxFUL44"
|
"cid":"QmVELYAWho5xuwSo8hpZbiH8BoHTBxMS5pDSBtVpwYheLY"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/gbc/bios/gb.zip",
|
|
||||||
"cid":"QmYXjcLTGdtG9JtDGVg42cWFkW6feYufRPQYGSe6ifESY8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/lynx.json",
|
"file":"/data/config/lynx.json",
|
||||||
"cid":"QmYsDZRt9vk4WPgMpbDwqtXWZmMvxyJTPVFsSA7YvBBYMd"
|
"cid":"QmQrZSx1W6qKjdXmCJFX26Kg8ZN9XtahFgNaRyacs4kbzb"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/lynx/bios/lynxboot.img",
|
|
||||||
"cid":"QmTaUUheMwLj5JqTqiZLzUfdUBj9xWikyZY4s72RGfKb9d"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/segaCD.json",
|
"file":"/data/config/segaCD.json",
|
||||||
"cid":"QmZDGK5aFqMoTpzZJz9V8S5P6pxuqDP3RwDWYrGpiBMhdY"
|
"cid":"QmddqqPJ8m92LvaiM5RaPhPrj43khMavb58QVgfnC4DrV8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/vb.json",
|
"file":"/data/config/vb.json",
|
||||||
@ -653,15 +645,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/doom.json",
|
"file":"/data/config/doom.json",
|
||||||
"cid":"QmXc3LLZkTnvz6GSftb8LycevPyFnHGwyBMayyyD1AuV5J"
|
"cid":"QmdbtjxaChjJqanfFarHJw7nYwxdsnSgYRCaKKtGkzyguX"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/gba.json",
|
"file":"/data/config/gba.json",
|
||||||
"cid":"QmSdiXmb2538xy4jSvy2Uy74AxyZvcra4jXYZWY9UQCQrt"
|
"cid":"QmX3ETMRooA2YSJoHfVyThTFE1bWiYCA9Gog8dKziRJbu1"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/gba/bios/gba.zip",
|
|
||||||
"cid":"QmYtDaErc2RzWBYLmcwfKD81NSVjqBsGD6cJCk3kNzDyuk"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/vectrex.json",
|
"file":"/data/config/vectrex.json",
|
||||||
@ -677,19 +665,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/main.json",
|
"file":"/data/config/main.json",
|
||||||
"cid":"QmfTg6rcFprn1jQVaX6CJFE3f73sshVwVyjyg37JZ4cLPM"
|
"cid":"QmYUtztFTLXbgyu9KVcmtVtsgqTw7ty26ju2GPwVfwCkSX"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/segaGG.json",
|
"file":"/data/config/segaGG.json",
|
||||||
"cid":"QmZjX8naFHrD16kXasyJBZGwB1xvzjUjN5J9xsqjcgeLpA"
|
"cid":"QmPfQUHixS1pRoKUDdgawFSMsFKpjt46DFyW9P9fbCVHKF"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/odyssey2.json",
|
"file":"/data/config/odyssey2.json",
|
||||||
"cid":"QmQuJmH6vhymWQp1oWAaPjL9MSx9k2LgVFGLq9iH4rN2EA"
|
"cid":"QmXmFBV2RdXP2ukA6r1AeuqvTVvHTdYntQF4jtjwUonuvP"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/odyssey2/bios/odyssey2.zip",
|
|
||||||
"cid":"QmcXRfDzhsEnWnMykpVwQc86kZXKXgbWMKuXf3HNzXnWKN"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/colecovision.json",
|
"file":"/data/config/colecovision.json",
|
||||||
@ -697,11 +681,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/segaSaturn.json",
|
"file":"/data/config/segaSaturn.json",
|
||||||
"cid":"Qmeqe5vdKuVUNPNbiotcBMZVVihegyuzKjDS8zehQLbgZQ"
|
"cid":"QmNVCVsgyJLL5WViNoU6dWTUMthvgfYtSN3icgHCJBiFMi"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/segaSaturn/bios/saturn.zip",
|
|
||||||
"cid":"QmWfQo5YDDx7w4PRHFCLUcWWuhiD6L42h2AGm3RojJwXKo"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/ws.json",
|
"file":"/data/config/ws.json",
|
||||||
@ -713,55 +693,35 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/segaSG.json",
|
"file":"/data/config/segaSG.json",
|
||||||
"cid":"QmXD3EUaQbv7jUMSTaAY7ViCnDAwu53qpFMJeLdvdn6jpb"
|
"cid":"QmboVi6qLgZ9YSj4CnkPgJg5c6wnS88SCB7wiagSPA7CaR"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/nes.json",
|
"file":"/data/config/nes.json",
|
||||||
"cid":"Qmb94JynyJ5FfLxfpbhCp4vLxmJWarKbesiNa7M81u8i5w"
|
"cid":"QmTYx6yK6zB5fpC6Z9xswge42JS8TMzfpad3rXhYNCFYV4"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/nes/bios/nes.zip",
|
|
||||||
"cid":"QmPk9EKQ2aRVHJX91PTJfmfBDiHRzeWuZExT36BAk2sNZL"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/segaMS.json",
|
"file":"/data/config/segaMS.json",
|
||||||
"cid":"QmVrZCSBsWdztUtmPrALKZPfTcJk2NUDNszKRqZko4SoH5"
|
"cid":"QmXzi2gw4oTtFEqBH8JhuLXDVAzKmN3PGfTCDhkWKBaDqQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/gb.json",
|
"file":"/data/config/gb.json",
|
||||||
"cid":"QmZyDRXQdoFg8izaPURzEQ8Zv4trVVk2G5jspCng7ruaVZ"
|
"cid":"QmScD8DusjpqSuCKT42bqe9v1uH5p4xxEYBnAYeMhtrHzA"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/gb/bios/gb.zip",
|
|
||||||
"cid":"QmYXjcLTGdtG9JtDGVg42cWFkW6feYufRPQYGSe6ifESY8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/arcade.json",
|
"file":"/data/config/arcade.json",
|
||||||
"cid":"QmTvpfgqX9in7kGyuKPSrjjwJeRzZ4fHxZas7bARUUZ7MM"
|
"cid":"QmUXaDzeCediDpefawxTvVFdrZs2PJ3vgsUoUpQkeLYrNd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/snes.json",
|
"file":"/data/config/snes.json",
|
||||||
"cid":"QmPTSsowVAeMTUh8b31S1xb6uAmJVJq4TM6qYAKPrmNbqa"
|
"cid":"QmW2bp9HCL6ZbheYVwkCbczGwo2jEuuz6VEqMJ72bzzpLN"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/snes/bios/snes.zip",
|
|
||||||
"cid":"Qme4WxcE38pAZePr3jbHp4h3c9Bu23K7Gt7PNkVHU4x14e"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/atari2600.json",
|
"file":"/data/config/atari2600.json",
|
||||||
"cid":"Qma9uNpoCSNGfrxF8KxhZGCjqmvnXqk6n4DDHwW69WR2ui"
|
"cid":"Qma9uNpoCSNGfrxF8KxhZGCjqmvnXqk6n4DDHwW69WR2ui"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/config/atari5200.json",
|
|
||||||
"cid":"Qmb5Adw5BUopBCtGxu9X1J4XS28n6aMMeTPUZpVAxb4nQc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/atari5200/bios/5200.rom",
|
|
||||||
"cid":"QmbDoTr3Vu3B4vqRkH75kiFHk7ChSJmzS367mc1qxVkw5V"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/config/segaMD.json",
|
"file":"/data/config/segaMD.json",
|
||||||
"cid":"QmUNbgxrPrT5E8dZ8hzjMSLyRgUY4RDJVSVozwmkNjBsKB"
|
"cid":"QmSzmETrvkLfFgr3bPbwEm8tBhY6deuzQiUQZmCLdPb93F"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/msx.json",
|
"file":"/data/config/msx.json",
|
||||||
@ -769,23 +729,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/atari7800.json",
|
"file":"/data/config/atari7800.json",
|
||||||
"cid":"QmSLHkLrMNwwDTaz9gTVVWKGQZRwTx2VrzuUVg5dDAxtNe"
|
"cid":"QmZDZ5ENbes4ATmt4yXyPoTjaKKT1FDyyMUaaeEVuHX3vX"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/atari7800/bios/atari7800.zip",
|
|
||||||
"cid":"QmcHsJd8dzc4gKwd3cmibFBAgBw3X7pswzdLsWSBG8Ngs7"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/n64.json",
|
"file":"/data/config/n64.json",
|
||||||
"cid":"Qmd7dS5VeSTfkd5R5aZvWx8vYfWSVfayFf71uWTooMpzFv"
|
"cid":"QmePgQExUzCZCDx5sD1kdMDQaEoCQRj5BUjhxLUGDN4TUD"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/config/nds.json",
|
"file":"/data/config/nds.json",
|
||||||
"cid":"Qmbhz89RLAYiux8YjqXwqXxHwxtTghFnd5RERUJohphYsY"
|
"cid":"QmXYhmPpPMKHZaAi7vKm67jMnkjn7HdQXAUvNyPZETCcC7"
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/nds/bios/nds.zip",
|
|
||||||
"cid":"QmciBpXM6i1nPSyjKv7tJYmWS7NGXtfKX2EtFmTJ4iomas"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/msx/backgrounds/default.png",
|
"file":"/data/msx/backgrounds/default.png",
|
||||||
@ -811,6 +763,10 @@
|
|||||||
"file":"/data/gbc/backgrounds/default.png",
|
"file":"/data/gbc/backgrounds/default.png",
|
||||||
"cid":"QmfCkpZzcRaJYuCxuNBWtjVA28T9M25hNWQeqUQYZbZij9"
|
"cid":"QmfCkpZzcRaJYuCxuNBWtjVA28T9M25hNWQeqUQYZbZij9"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/gbc/bios/gbc_bios.bin",
|
||||||
|
"cid":"QmNQJie3w8pXAjXWWXRdJrsw8JfF6AZ2UwN4MQtDyPrz6U"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/gbc/corners/default.png",
|
"file":"/data/gbc/corners/default.png",
|
||||||
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
||||||
@ -820,8 +776,8 @@
|
|||||||
"cid":"QmXtrPJCdPuBPemYLxC8shQYHNHk59ES26FyGqR12bZiD2"
|
"cid":"QmXtrPJCdPuBPemYLxC8shQYHNHk59ES26FyGqR12bZiD2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/psx/bios/psx.zip",
|
"file":"/data/psx/bios/scph5501.bin",
|
||||||
"cid":"QmfQTsRzaE4T9LUWnKara8KVyXmoUn2RXzmSHYtDYN8Jr6"
|
"cid":"QmVqWZXiKdhcTFYnwU8iCYnPSBwAijsQx7c2kBDixRv9bL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/psx/corners/default.png",
|
"file":"/data/psx/corners/default.png",
|
||||||
@ -847,6 +803,10 @@
|
|||||||
"file":"/data/segaSaturn/backgrounds/default.png",
|
"file":"/data/segaSaturn/backgrounds/default.png",
|
||||||
"cid":"QmQg9KY4puPcxj1KpdfV49uq6z8PcEk8Fbq7uTdj8aYQSx"
|
"cid":"QmQg9KY4puPcxj1KpdfV49uq6z8PcEk8Fbq7uTdj8aYQSx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/segaSaturn/bios/saturn_bios.bin",
|
||||||
|
"cid":"QmPAPRxLu2vwr1LDX6JFa2kmC1LaufZVV2WF67emaf6hAJ"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/segaSaturn/corners/default.png",
|
"file":"/data/segaSaturn/corners/default.png",
|
||||||
"cid":"Qmdynxj7tJmnLcMCJz1gYafXLwAY4jHHyYyqFPXzE7To7P"
|
"cid":"Qmdynxj7tJmnLcMCJz1gYafXLwAY4jHHyYyqFPXzE7To7P"
|
||||||
@ -871,18 +831,10 @@
|
|||||||
"file":"/data/atari2600/backgrounds/default.png",
|
"file":"/data/atari2600/backgrounds/default.png",
|
||||||
"cid":"QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D"
|
"cid":"QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/atari5200/backgrounds/default.png",
|
|
||||||
"cid":"QmNoDmFBcrE2wDHMVaZxCvRQsua1UjuYSfnxGshKY8YLKA"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/atari2600/corners/default.png",
|
"file":"/data/atari2600/corners/default.png",
|
||||||
"cid":"QmarPeRQzXTGPY88h744vhgCu1RGJtpV9HyTtjhrQmnQhq"
|
"cid":"QmarPeRQzXTGPY88h744vhgCu1RGJtpV9HyTtjhrQmnQhq"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/atari5200/corners/default.png",
|
|
||||||
"cid":"QmfTa42eaceCXkdg9NDkAziPNAfKUfAFdftrbFt2XYrkaU"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/lynx/backgrounds/default.png",
|
"file":"/data/lynx/backgrounds/default.png",
|
||||||
"cid":"QmXnPKdmdsPKfmcvh63xwE5A19iMMAFz2HXHGBgJ7wANmP"
|
"cid":"QmXnPKdmdsPKfmcvh63xwE5A19iMMAFz2HXHGBgJ7wANmP"
|
||||||
@ -903,6 +855,10 @@
|
|||||||
"file":"/data/odyssey2/backgrounds/default.png",
|
"file":"/data/odyssey2/backgrounds/default.png",
|
||||||
"cid":"QmXxgaCQQ5SonNQJ5ajiNDnk7Zf8CStFL8HRodY9mGRsQJ"
|
"cid":"QmXxgaCQQ5SonNQJ5ajiNDnk7Zf8CStFL8HRodY9mGRsQJ"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file":"/data/odyssey2/bios/o2rom.bin",
|
||||||
|
"cid":"QmY8LpCqtwRzYBFUrkADcTTwJHwD9Q2oFPakxw1cpS1WJz"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file":"/data/odyssey2/corners/default.png",
|
"file":"/data/odyssey2/corners/default.png",
|
||||||
"cid":"QmXHdF3vrctFfovgndZfLXEKS5sjRTSinjPK7LmJwSM9Y5"
|
"cid":"QmXHdF3vrctFfovgndZfLXEKS5sjRTSinjPK7LmJwSM9Y5"
|
||||||
@ -951,30 +907,6 @@
|
|||||||
"file":"/data/doom/corners/default.png",
|
"file":"/data/doom/corners/default.png",
|
||||||
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
"cid":"QmQuTP338HcMh9QLEXDZdVLKbf6YCQV7dmx1UcPsdiL5DX"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/segaCD/bios/sega.zip",
|
|
||||||
"cid":"Qmct6NWgAbSvWW9XenBfJ8U15GiwEgDLNFKhCnhL6TFiad"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/segaGG/bios/sega.zip",
|
|
||||||
"cid":"Qmct6NWgAbSvWW9XenBfJ8U15GiwEgDLNFKhCnhL6TFiad"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/segaMD/bios/sega.zip",
|
|
||||||
"cid":"Qmct6NWgAbSvWW9XenBfJ8U15GiwEgDLNFKhCnhL6TFiad"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/segaMS/bios/sega.zip",
|
|
||||||
"cid":"Qmct6NWgAbSvWW9XenBfJ8U15GiwEgDLNFKhCnhL6TFiad"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/segaSG/bios/sega.zip",
|
|
||||||
"cid":"Qmct6NWgAbSvWW9XenBfJ8U15GiwEgDLNFKhCnhL6TFiad"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file":"/data/arcade/bios/samples.zip",
|
|
||||||
"cid":"QmNMsqvdXobsGYmQ7nWEF63BEWYjQER4d9H3cLFZWpnLtm"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/3do/roms/",
|
"file":"/data/3do/roms/",
|
||||||
"cid":"directory"
|
"cid":"directory"
|
||||||
@ -987,10 +919,6 @@
|
|||||||
"file":"/data/atari2600/roms/",
|
"file":"/data/atari2600/roms/",
|
||||||
"cid":"directory"
|
"cid":"directory"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file":"/data/atari5200/roms/",
|
|
||||||
"cid":"directory"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file":"/data/atari7800/roms/",
|
"file":"/data/atari7800/roms/",
|
||||||
"cid":"directory"
|
"cid":"directory"
|
||||||
|
|||||||
@ -1,28 +1,24 @@
|
|||||||
{
|
{
|
||||||
"2C8212631B37F21AD06D18B5638C733A75E179FF ": {
|
"C6612AC5A8AC2E2A1D707F9B2869AF820EFB7C50": {
|
||||||
"logo": "QmSNo8VrwLg1NRWAMhm2qc3zWFF7HK8563sAeU4Hu6DQ8u",
|
|
||||||
"name": "Doom",
|
"name": "Doom",
|
||||||
"vid": "QmTuyvkgZ7H4kxLN5ws7k71uGsa2pc2rEBKCHTJY6mzvTs"
|
"vid": "QmTuyvkgZ7H4kxLN5ws7k71uGsa2pc2rEBKCHTJY6mzvTs",
|
||||||
|
"logo": "QmSNo8VrwLg1NRWAMhm2qc3zWFF7HK8563sAeU4Hu6DQ8u"
|
||||||
},
|
},
|
||||||
"7EC7652FCFCE8DDC6E801839291F0E28EF1D5AE7": {
|
"7EC7652FCFCE8DDC6E801839291F0E28EF1D5AE7": {
|
||||||
"logo": "QmPZaGnVHshwHropCW95HtNTUQSDUbfK9vFa2oQjVqcVkZ",
|
|
||||||
"name": "Doom II",
|
"name": "Doom II",
|
||||||
"vid": "Qmamv4bz1HVQWHRKbrbEWPUuAtUEhEVXJxqDYz2ui4gCaD"
|
"vid": "Qmamv4bz1HVQWHRKbrbEWPUuAtUEhEVXJxqDYz2ui4gCaD",
|
||||||
},
|
"logo": "QmPZaGnVHshwHropCW95HtNTUQSDUbfK9vFa2oQjVqcVkZ"
|
||||||
"90361E2A538D2388506657252AE41ACEEB1BA360": {
|
|
||||||
"logo": "QmQEN7NGWPw4YbvS49RoQBgKNbRT9V3EynijAFcxAiJKCE",
|
|
||||||
"name": "Doom The Plutonia Experiment"
|
|
||||||
},
|
},
|
||||||
"9B07B02AB3C275A6A7570C3F73CC20D63A0E3833": {
|
"9B07B02AB3C275A6A7570C3F73CC20D63A0E3833": {
|
||||||
"logo": "QmNyBkiShukMmib9qndrZbovqX9ycdQE3UzZSLHSFtXdU7",
|
"name": "The Ultimate Doom",
|
||||||
"name": "The Ultimate Doom"
|
"logo": "QmNyBkiShukMmib9qndrZbovqX9ycdQE3UzZSLHSFtXdU7"
|
||||||
|
},
|
||||||
|
"90361E2A538D2388506657252AE41ACEEB1BA360": {
|
||||||
|
"name": "Doom The Plutonia Experiment",
|
||||||
|
"logo": "QmQEN7NGWPw4YbvS49RoQBgKNbRT9V3EynijAFcxAiJKCE"
|
||||||
},
|
},
|
||||||
"9FBC66AEDEF7FE3BAE0986CDB9323D2B8DB4C9D3": {
|
"9FBC66AEDEF7FE3BAE0986CDB9323D2B8DB4C9D3": {
|
||||||
"logo": "Qma4PUdncsFtsWnFbeS1qVSyRpqsLD2J7JUaz37da2DtjQ",
|
"name": "Doom TNT: Evilution",
|
||||||
"name": "Doom TNT: Evilution"
|
"logo": "Qma4PUdncsFtsWnFbeS1qVSyRpqsLD2J7JUaz37da2DtjQ"
|
||||||
},
|
|
||||||
"C6612AC5A8AC2E2A1D707F9B2869AF820EFB7C50": {
|
|
||||||
"name": "Doom (Demo)",
|
|
||||||
"ref": "2C8212631B37F21AD06D18B5638C733A75E179FF"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12943
metadata/gb.json
12943
metadata/gb.json
File diff suppressed because it is too large
Load Diff
25169
metadata/gba.json
25169
metadata/gba.json
File diff suppressed because it is too large
Load Diff
11447
metadata/gbc.json
11447
metadata/gbc.json
File diff suppressed because it is too large
Load Diff
@ -1,435 +1,415 @@
|
|||||||
{
|
{
|
||||||
"01DC37E7A9D03246A93BCB8C6A3116CB6391ED31": {
|
"F8991B0C385F4E5002FA2A7E2F5E61E8C5213356": {
|
||||||
"logo": "QmfKeLsqS1SBMQFwr4dRzZLuhQdskMwEFDr9jDaCMbo4wN",
|
"name": "[BIOS] Atari Jaguar (World)"
|
||||||
"name": "Attack of the Mutant Penguins (World)",
|
|
||||||
"vid": "QmUZbbybLfEngb3KCkBMJ76XK7b6Wt97zo18XV9sXEWZSk"
|
|
||||||
},
|
|
||||||
"02253D93EEF375C8334F1F77A2D8B72FFF7A94C6": {
|
|
||||||
"name": "Val d'Isere Skiing and Snowboarding (World)"
|
|
||||||
},
|
|
||||||
"032AA31CEF116BFBA8632C14BC2FCE5BC44FF137": {
|
|
||||||
"name": "Plucky Duck in Hollywood Hijinks (USA) (Proto) (1994-10-28)"
|
|
||||||
},
|
|
||||||
"037396347D65D1A8B56E7DB361D737AB2070118F": {
|
|
||||||
"name": "Hyper Force (World)"
|
|
||||||
},
|
|
||||||
"07C76D3F4AE05B75EA7AD62147172FD146F4F876": {
|
|
||||||
"name": "Thea Realm Fighters (USA) (Proto 2)"
|
|
||||||
},
|
|
||||||
"0C195676BE099F4594CE1D5A38303F6CCE899DC5": {
|
|
||||||
"logo": "QmRFsbJZ7WKewZsSVWy5qJA6SxJbnSQDFDsRnpdXseL44K",
|
|
||||||
"name": "Cannon Fodder (World)",
|
|
||||||
"vid": "Qmb8XZcpytbb8jDyW617RgCtRfBUKzxx971Fac4AUh2UUa"
|
|
||||||
},
|
|
||||||
"0E95613020D1B3A0AC76D994423C6F15DDFE9703": {
|
|
||||||
"name": "Missile Command VR (World) (Beta) (1995-07-05)"
|
|
||||||
},
|
|
||||||
"1189AA22A427D81EA0BD68BF58258C8B1089614B": {
|
|
||||||
"name": "Alien vs Predator (World) (Beta) (1994-04-08)",
|
|
||||||
"ref": "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02"
|
|
||||||
},
|
|
||||||
"13A4084BD5BE1CCA21D83D4C26B5B8E5CF1A3E30": {
|
|
||||||
"logo": "QmVCsuGdk9mepCZiZ9iD7owQ4SKYSuQSomP7WMXvub5rfn",
|
|
||||||
"name": "Defender 2000 (World)",
|
|
||||||
"vid": "QmRw7miWyxxP1jJ3UdnXNhaattqf8j72nu5wiqmmPkdQZF"
|
|
||||||
},
|
|
||||||
"179B511464ABCDA5A5DE9F971BD5CB5775D8C313": {
|
|
||||||
"logo": "QmVZaMvFkoR1LxXRogBRMsMaG5pe71JJVzusfSkerk8aPB",
|
|
||||||
"name": "Evolution - Dino Dudes (World)",
|
|
||||||
"vid": "QmfGyzVC5mrtMxEv78czZmhzmphoEGKRrEMM7nPDp6FXbP"
|
|
||||||
},
|
|
||||||
"18535F5985C840AEFCE0FA639131CB83A8A647F9": {
|
|
||||||
"name": "Barkley Shut Up and Jam (World)"
|
|
||||||
},
|
|
||||||
"1BDAAA42B38F4C93A4ABA34930E3CA406AC976C9": {
|
|
||||||
"name": "Atari Karts (World) (Beta)",
|
|
||||||
"ref": "9C96E6C2195CE56AA930E677A041B35BD9B8BC7E"
|
|
||||||
},
|
|
||||||
"1EF1B86ABF725F32267BFA140DC049CE9BA92AEA": {
|
|
||||||
"name": "Total Carnage (World) (Beta)",
|
|
||||||
"ref": "4566B33799A7C65B3E9643383D0282172FF619A5"
|
|
||||||
},
|
|
||||||
"20DDF412E42A50D526CBBB6411BF0EC4516DB283": {
|
|
||||||
"logo": "QmZTx6wneGVU4zJTghJF7VARH89djP1RNdBUaSV8ykpMwc",
|
|
||||||
"name": "Doom (World)",
|
|
||||||
"vid": "QmSZgM6UhZ399co6dhiB3aGmvtq1iVr5N1SpeC792NAhUk"
|
|
||||||
},
|
|
||||||
"2327112B9C6F9B0E49C55DEF0ADB8119EE39CE29": {
|
|
||||||
"logo": "QmaCEAzbaV8gTJtEhwc7ewwQegMZbGjHAngC6jQfS6UF9T",
|
|
||||||
"name": "Kasumi Ninja (World)",
|
|
||||||
"vid": "QmV3CbiaPDF11B52Sx6DzV4d1ZQueBztnpSDfqRWn51tHz"
|
|
||||||
},
|
|
||||||
"24DA4FE776AE9DAA86CF643C703EC0F399C77521": {
|
|
||||||
"logo": "QmdaJGvXuh8dFDgpAdW2jXSnr4pj6e7uSXULthA8t6STKq",
|
|
||||||
"name": "Theme Park (World)",
|
|
||||||
"vid": "QmQJG7EmAZwZWjbZ4zL9umjpTww6pR9HdRvVgfxAZwCTn2"
|
|
||||||
},
|
|
||||||
"25611B1CFF1384DB72CC5422DBE70555E5BBD32C": {
|
|
||||||
"logo": "QmXFSYwvRJqkviBDVgicJu2E2rhxYmNXhDi8dRqPtoZr8a",
|
|
||||||
"name": "Pitfall - The Mayan Adventure (USA, Europe)",
|
|
||||||
"vid": "QmPniHfXG46CEwZcGVnw9X4MxXYJd8RRe9T5qLGwT2qMUK"
|
|
||||||
},
|
|
||||||
"261667E4AAB6239ECF3A416F5C2443BF092735CC": {
|
|
||||||
"name": "Alien vs Predator (World) (Beta) (1993-10-13)",
|
|
||||||
"ref": "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02"
|
|
||||||
},
|
|
||||||
"271E9411EB9E6C74240D254BDD7BAB9A039BC6D9": {
|
|
||||||
"name": "Jack Nicklaus Cyber Golf (USA) (Proto) (1994-07-04)"
|
|
||||||
},
|
|
||||||
"298148131AF2A86BA5D5430B1B8982710CE68874": {
|
|
||||||
"name": "Dragon - The Bruce Lee Story (World) (Beta) (1994-09-26)",
|
|
||||||
"ref": "DA7F5DAC4317AB0258F54DA436ABB8D89E530F27"
|
|
||||||
},
|
|
||||||
"2AB78090E8AFD985CA4FE54DA8DAEF7E125550FA": {
|
|
||||||
"name": "Virtual VCS (USA) (Proto)"
|
|
||||||
},
|
|
||||||
"2F0E950517D26C83408E5CF210098C0897725537": {
|
|
||||||
"name": "Trevor McFur in the Crescent Galaxy (World) (Beta)",
|
|
||||||
"ref": "88E86085932AF10DFB8C94032F73A5992A30B55C"
|
|
||||||
},
|
|
||||||
"311E41EBBA1B78F514BF16789DDB7E7E8DFB47FF": {
|
|
||||||
"logo": "QmbKxD6MNUuRDLX8Q5Syzcwdjdfot4wjADemAf7wnD6rZw",
|
|
||||||
"name": "Ultra Vortek (World)",
|
|
||||||
"vid": "QmVjygYsL4ZXhhUp1V7W2FRQq6kcB8Gi8XQGfLMf1x3paU"
|
|
||||||
},
|
|
||||||
"32FF3811F8F6DF8A2BB56165C62227A3A480E55C": {
|
|
||||||
"logo": "QmSRcjgwAWLouMTYq6GFQQSghH28yg8fGQHwYjbj7nA2w7",
|
|
||||||
"name": "Zool 2 (World)",
|
|
||||||
"vid": "Qmerp8YE5HYS4MyP8jnjY2YMNJbChZr2jGbuttMsXz16F4"
|
|
||||||
},
|
|
||||||
"34A9941459E3B66C836F25B3EF55303734B9583E": {
|
|
||||||
"name": "Skyhammer (World)"
|
|
||||||
},
|
|
||||||
"3650143BD057DD39B96E0AD0207778353F17ABE7": {
|
|
||||||
"name": "Kasumi Ninja (World) (Demo)",
|
|
||||||
"ref": "2327112B9C6F9B0E49C55DEF0ADB8119EE39CE29"
|
|
||||||
},
|
|
||||||
"37FE344383D7991D19BF2C862D29A20EAF1DA8EF": {
|
|
||||||
"name": "Fight for Life (USA, Europe) (Beta) (1995-03-22)",
|
|
||||||
"ref": "9F5B8844D58BB1C174DA4824EABBFD8616BA28D2"
|
|
||||||
},
|
|
||||||
"3828D2F953224D06B5FE7AB3644D6A75FDFE79E3": {
|
|
||||||
"logo": "QmcVjqiqF82wvqVkucrthJwhT8BsUUowFwNLYMkCNmpjtK",
|
|
||||||
"name": "Tempest 2000 (World)",
|
|
||||||
"vid": "QmXvDFkX7YmDsHvUiJupbTiXEefFzqTQecktmnzFMRjkff"
|
|
||||||
},
|
|
||||||
"3D3E6C7E552CBA5F99B6EBC15B951CE4BD956766": {
|
|
||||||
"name": "Native (USA) (Proto)"
|
|
||||||
},
|
|
||||||
"3DB485AAA28A817F468739A85E3F25741C3855AD": {
|
|
||||||
"logo": "QmW7ooQuquXj9SmMRnm9jzanXbyFoiXKwkczFMr38nhUet",
|
|
||||||
"name": "I-War (USA, Europe)",
|
|
||||||
"vid": "QmXXvJayEvptD1RNg5RBwYYdSv2hfV4RGRnbqVbHu5twiB"
|
|
||||||
},
|
|
||||||
"3EA9053A63A3B0165CCEC91E85F0E699A998DC0A": {
|
|
||||||
"logo": "QmcffN7HbkieG9gXHkZ72nviP425m8xMbYh2BxCwnvLAvN",
|
|
||||||
"name": "Missile Command 3D (World)",
|
|
||||||
"vid": "QmZGwx2ZMDBK2XsPyrvGAujU34Fx6fqWDP85SKg8yyxLxr"
|
|
||||||
},
|
|
||||||
"3EC9524921D07647F527E0A82A831F794A7D6D9B": {
|
|
||||||
"name": "Phase Zero (World)"
|
|
||||||
},
|
|
||||||
"4566B33799A7C65B3E9643383D0282172FF619A5": {
|
|
||||||
"logo": "QmZfJ4Z8e4CNcmLgPKQAWEqjpRt6sqkdoKRkNyDtYdXq8o",
|
|
||||||
"name": "Total Carnage (World)",
|
|
||||||
"vid": "QmaDdW5niQbiwpN4T5CDGi9x6iFtWsVLokF8vCviKZgHf5"
|
|
||||||
},
|
|
||||||
"4733CF2EDE2713467F2A80B052AB94EDF6DFE534": {
|
|
||||||
"logo": "QmNmKQXYJMkmwnj6mmgPAM6rsmftpNeqrh4ATzggKKJnfo",
|
|
||||||
"name": "Pinball Fantasies (World)",
|
|
||||||
"vid": "QmUX4sQRATEybMWuzwogeUjUZ7vFxGnYyhncrze5MmZ9WK"
|
|
||||||
},
|
|
||||||
"47B9580EB9EEA121CF080CDBDC4AFE7FB7A19AB5": {
|
|
||||||
"name": "Iron Soldier 2 (USA, Europe)"
|
|
||||||
},
|
|
||||||
"489B0FE9F070AD27F63A17AA8F0D2AFAA4DABDB3": {
|
|
||||||
"name": "Trevor McFur in the Crescent Galaxy (World)",
|
|
||||||
"ref": "88E86085932AF10DFB8C94032F73A5992A30B55C"
|
|
||||||
},
|
|
||||||
"49716A6A3A4C6ABEEA36040F26C4E8CF2A545DF7": {
|
|
||||||
"name": "Battle Sphere Gold (World)"
|
|
||||||
},
|
|
||||||
"4C6A6432EA0BC198F900F4EBDB8A3A7A34475D84": {
|
|
||||||
"logo": "QmYFg6g3WcSCA7Akoq8dMrVCR27XxUC13iEminXUFWWXJf",
|
|
||||||
"name": "Troy Aikman NFL Football (World)",
|
|
||||||
"vid": "QmTyfEp2q2FLfEX6gH27aufVfydfEtPfjYm2aJsp7LbgAj"
|
|
||||||
},
|
|
||||||
"4D0CC8D248A8BD0FC4B8169C5D5772E59C073C59": {
|
|
||||||
"logo": "QmYKfShHBmErdpVQVdWaF1bwSb6xwexpjrPrKKjRextc9t",
|
|
||||||
"name": "Checkered Flag (World)",
|
|
||||||
"vid": "Qma4iVerm6svScmwC83dpydmAYLQR1NUHTuNkns1JxEJRe"
|
|
||||||
},
|
|
||||||
"4D16596AC2D991435599E1FE03292ADFB25C346D": {
|
|
||||||
"name": "White Men Can't Jump (USA, Europe)"
|
|
||||||
},
|
|
||||||
"4E55A5991725A4B557F4DAF75A0FA38EC667217A": {
|
|
||||||
"name": "Aircars (USA) (Beta) (1994-11-14)"
|
|
||||||
},
|
|
||||||
"4F87982B6E16072E58A5653A3F763C8B4B3BFFCC": {
|
|
||||||
"name": "[BIOS] Atari Jaguar Stubulator '94 (World)"
|
|
||||||
},
|
|
||||||
"50CCF7043A702531A0E9EF86D629D5EDE5255C13": {
|
|
||||||
"name": "Raiden (World) (Rev A)"
|
|
||||||
},
|
|
||||||
"52EC786F7536AB043DC24725AF94068CAD2B0EC3": {
|
|
||||||
"name": "Zoop (USA, Europe)"
|
|
||||||
},
|
|
||||||
"533C497761CC802624708108BC9EF6E7C4F40296": {
|
|
||||||
"name": "Aircars (USA)"
|
|
||||||
},
|
|
||||||
"542063A5B27659781A85821F61E85CD89EA59AB3": {
|
|
||||||
"name": "Arena Football '95 (World)"
|
|
||||||
},
|
|
||||||
"589EFBC772360FA51EDDC096A162C8D9FE7838D3": {
|
|
||||||
"name": "Cybermorph (World) (Beta) (1993-08-23)",
|
|
||||||
"ref": "E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E"
|
|
||||||
},
|
|
||||||
"5B2295907F6FDADF5F44B1E392DCD6F5471FD095": {
|
|
||||||
"name": "Frog Feast (World)"
|
|
||||||
},
|
|
||||||
"5BE3DA69A64B779D064D139968FADBE1C475C715": {
|
|
||||||
"logo": "QmdZbMCrPHFmjWxsdwAi9VTroT7ADXG7ZmVpF97yWjjuWq",
|
|
||||||
"name": "Fever Pitch Soccer (World) (En,Fr,De,Es,It)",
|
|
||||||
"vid": "QmZRt4vXXPvkKiLihkBuHgCNfnso14YPHbqhD8WsaXzw9f"
|
|
||||||
},
|
|
||||||
"61D4DB841C090AE3104EB20B6087160FF80E7A12": {
|
|
||||||
"ref": "886CBBAA434D6E1E7FC4401BB6D176C2788C4D19"
|
|
||||||
},
|
|
||||||
"641B07F93E8DF03AB8B9BF1E8DC56E2889F247F8": {
|
|
||||||
"logo": "QmNXexYonZXnNi4kKfDezNkX1Dg8tuU3iN7518sFtupyre",
|
|
||||||
"name": "Worms (USA, Europe)",
|
|
||||||
"vid": "QmcahTbiuzvNm1ZXSoVKiULmxpGGjCAN3ZQpcxgHZFQ6RB"
|
|
||||||
},
|
|
||||||
"6588F7D677463D0EF10CF905D44A2581FD9DD055": {
|
|
||||||
"name": "Club Drive (World)"
|
|
||||||
},
|
|
||||||
"6B010477155F30F1ECEC7EAB2C9A7544242BBD19": {
|
|
||||||
"logo": "QmNym4uVncwQDYRwoHNMv3YHA7SeeecLqbxVPc7XLrRwgQ",
|
|
||||||
"name": "Space War 2000 (World)",
|
|
||||||
"vid": "QmPJW65sMdxtqCWQUFfwHyHU9WPuA1PNcwipA3SZ7fwwVb"
|
|
||||||
},
|
},
|
||||||
"73883E7A6E9B132452436F7AB1AEAEB0776428E5": {
|
"73883E7A6E9B132452436F7AB1AEAEB0776428E5": {
|
||||||
"name": "[BIOS] Atari Jaguar CD (World)"
|
"name": "[BIOS] Atari Jaguar CD (World)"
|
||||||
},
|
},
|
||||||
"752D6E1ED185F9DB49230F42319A573A44E32397": {
|
"D61B7B5912118F114EF00CF44966A5EF62E455A5": {
|
||||||
"logo": "QmcUtsD4orEMZjoPbjk3h2YTS3Ug55pF4RhvGFCbExUBcV",
|
"name": "[BIOS] Atari Jaguar Developer CD (World)"
|
||||||
"name": "Bubsy in Fractured Furry Tales (World)",
|
|
||||||
"vid": "Qme3hphX7hkwVJWpYwcFnxU6u2Y8J7TB9JFpKcAsMuxDQh"
|
|
||||||
},
|
|
||||||
"75EE0AB0DABA31284A1DFF27F3C247885F5A427A": {
|
|
||||||
"logo": "QmWmPqprXS7jj5SmZB1cpBNTVaDxqdMzpfbMj8ZPj9iQer",
|
|
||||||
"name": "Power Drive Rally (World)",
|
|
||||||
"vid": "QmRfWpZnaUhLZjmPDuMk1JKkrvJ8EM7S72vsB64hZZveED"
|
|
||||||
},
|
|
||||||
"7E4356FECBEADD741C2BF9868A37B2956BD27D5E": {
|
|
||||||
"name": "Zzyorxx II (USA) (Demo)"
|
|
||||||
},
|
|
||||||
"7F21C6DE18C46629C0D54E68143D619093B4B8D6": {
|
|
||||||
"name": "Brett Hull Hockey (World)"
|
|
||||||
},
|
|
||||||
"8043E3B521EA00E806D3AB79C6C548A140944DB1": {
|
|
||||||
"name": "Protector (World)"
|
|
||||||
},
|
|
||||||
"80ED1EE5B8ACADDFB64A5C0BBDA2BFC14BDB5DA5": {
|
|
||||||
"name": "Alien vs Predator (World) (Beta) (1993-08-18)",
|
|
||||||
"ref": "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02"
|
|
||||||
},
|
|
||||||
"824613451BE0B99020FC02E5DBA15C45E76FC8C2": {
|
|
||||||
"logo": "QmTGeRg29B8rinMjgnRXE352VnZn2qe1kaRGdwkyLbSPug",
|
|
||||||
"name": "Flashback - The Quest for Identity (World) (En,Fr)",
|
|
||||||
"vid": "QmSUe7urtRuLe4KkZpqXoVPsFHYsU7FiYLrwmwsfXD4QnU"
|
|
||||||
},
|
},
|
||||||
"8377FF6C6527721E2E7F8458314D782925CE4590": {
|
"8377FF6C6527721E2E7F8458314D782925CE4590": {
|
||||||
"name": "[BIOS] Atari Jaguar Stubulator '93 (World)"
|
"name": "[BIOS] Atari Jaguar Stubulator '93 (World)"
|
||||||
},
|
},
|
||||||
"841CE8AE1B71955D9B8DF3B80DA11FD40308EF5F": {
|
"4F87982B6E16072E58A5653A3F763C8B4B3BFFCC": {
|
||||||
"name": "Tiny Toon Adventures (USA) (Proto)"
|
"name": "[BIOS] Atari Jaguar Stubulator '94 (World)"
|
||||||
},
|
},
|
||||||
"886CBBAA434D6E1E7FC4401BB6D176C2788C4D19": {
|
"533C497761CC802624708108BC9EF6E7C4F40296": {
|
||||||
"logo": "QmW8jpj4BKCKmEd2FjGEV14rRd4mMWcmALzW1ULdaF7k3J",
|
"name": "Aircars (USA)"
|
||||||
"name": "Flip Out! (World)",
|
|
||||||
"vid": "QmZDAGfvPwuWWzcn2PtWJZgrPNbnccfHj6yg8WSWvmJPyX"
|
|
||||||
},
|
},
|
||||||
"88E86085932AF10DFB8C94032F73A5992A30B55C": {
|
"4E55A5991725A4B557F4DAF75A0FA38EC667217A": {
|
||||||
"logo": "QmQkNeADHymrTFwyueXEemhWeZssNK8mkrqn5KQGipogox",
|
"name": "Aircars (USA) (Beta) (1994-11-14)"
|
||||||
"name": "Trevor McFur in the Crescent Galaxy (World) (Rev A)",
|
|
||||||
"vid": "QmaDA59oWsebKK5xJm4MMtcE26smcfjd2CbfTGesKCuP9Q"
|
|
||||||
},
|
},
|
||||||
"89306ADA943C87DA740E6166F1F53837566513E2": {
|
"FD8C89250EBC1E403838B2E589D1F69E3FE2FE02": {
|
||||||
"name": "Jack Nicklaus Cyber Golf (USA) (Demo) (1995-01-13)"
|
"name": "Alien vs Predator (World)",
|
||||||
|
"vid": "QmPF7SYwe3KYS9DxYHoNsVTnhwwFe9sdHkvnLdG7BS3Ff3",
|
||||||
|
"logo": "QmcevUQ5YkCiWcNov4grof4VwqeqR8P2DH5kr7AG6E2kPM"
|
||||||
},
|
},
|
||||||
"8BDEFC60DD1734315E1C145E29ABF41800F908CB": {
|
"80ED1EE5B8ACADDFB64A5C0BBDA2BFC14BDB5DA5": {
|
||||||
"name": "Iron Soldier (World)"
|
"name": "Alien vs Predator (World) (Beta) (1993-08-18)"
|
||||||
},
|
},
|
||||||
"8F4C964D2411A52C64EEC1D1A3061F77117BB7A6": {
|
"261667E4AAB6239ECF3A416F5C2443BF092735CC": {
|
||||||
"name": "Battle Sphere (World)"
|
"name": "Alien vs Predator (World) (Beta) (1993-10-13)"
|
||||||
},
|
},
|
||||||
"8F556976F42C393858127D99112264E6A5A9AE19": {
|
"1189AA22A427D81EA0BD68BF58258C8B1089614B": {
|
||||||
"name": "Dragon - The Bruce Lee Story (World) (Beta) (1994-10-06)",
|
"name": "Alien vs Predator (World) (Beta) (1994-04-08)"
|
||||||
"ref": "DA7F5DAC4317AB0258F54DA436ABB8D89E530F27"
|
|
||||||
},
|
},
|
||||||
"93A29D894F5472EB755D13E721414A6BD9686C02": {
|
"542063A5B27659781A85821F61E85CD89EA59AB3": {
|
||||||
"name": "Club Drive (World) (Beta)"
|
"name": "Arena Football '95 (World)"
|
||||||
},
|
},
|
||||||
"94E615E7A57F7C08645322870121BB32310298F5": {
|
"9C96E6C2195CE56AA930E677A041B35BD9B8BC7E": {
|
||||||
"name": "Hover Strike (World)"
|
"name": "Atari Karts (World)",
|
||||||
|
"vid": "QmYYH6BQAhCkGpidXcKgqf1xnxLzynkuvTBBdBdG4K5tzo",
|
||||||
|
"logo": "QmP4Lo8YAX9ZJ6rUJdGCoKfLByc4RKfqQt8Xd4C89J5wjy"
|
||||||
|
},
|
||||||
|
"1BDAAA42B38F4C93A4ABA34930E3CA406AC976C9": {
|
||||||
|
"name": "Atari Karts (World) (Beta)"
|
||||||
|
},
|
||||||
|
"01DC37E7A9D03246A93BCB8C6A3116CB6391ED31": {
|
||||||
|
"name": "Attack of the Mutant Penguins (World)",
|
||||||
|
"vid": "QmUZbbybLfEngb3KCkBMJ76XK7b6Wt97zo18XV9sXEWZSk",
|
||||||
|
"logo": "QmfKeLsqS1SBMQFwr4dRzZLuhQdskMwEFDr9jDaCMbo4wN"
|
||||||
|
},
|
||||||
|
"18535F5985C840AEFCE0FA639131CB83A8A647F9": {
|
||||||
|
"name": "Barkley Shut Up and Jam (World)"
|
||||||
},
|
},
|
||||||
"988897FD517EBD86742971205F3B29F9D70B5686": {
|
"988897FD517EBD86742971205F3B29F9D70B5686": {
|
||||||
"name": "Barkley Shut Up and Jam (World) (Beta)"
|
"name": "Barkley Shut Up and Jam (World) (Beta)"
|
||||||
},
|
},
|
||||||
"9A58BC0C8843FD00A2871C01B91FEC206408BF80": {
|
"8F4C964D2411A52C64EEC1D1A3061F77117BB7A6": {
|
||||||
"name": "Super Burnout (World)"
|
"name": "Battle Sphere (World)"
|
||||||
},
|
},
|
||||||
"9AD03858597AA1E1FFF26250996B07D1A70D1296": {
|
"49716A6A3A4C6ABEEA36040F26C4E8CF2A545DF7": {
|
||||||
"name": "Thea Realm Fighters (USA) (Proto 1)"
|
"name": "Battle Sphere Gold (World)"
|
||||||
},
|
|
||||||
"9C96E6C2195CE56AA930E677A041B35BD9B8BC7E": {
|
|
||||||
"logo": "QmP4Lo8YAX9ZJ6rUJdGCoKfLByc4RKfqQt8Xd4C89J5wjy",
|
|
||||||
"name": "Atari Karts (World)",
|
|
||||||
"vid": "QmYYH6BQAhCkGpidXcKgqf1xnxLzynkuvTBBdBdG4K5tzo"
|
|
||||||
},
|
|
||||||
"9D2F894DA3F25944C2F33E3A109AE8D0D951D329": {
|
|
||||||
"name": "Raiden (World)"
|
|
||||||
},
|
|
||||||
"9F5B8844D58BB1C174DA4824EABBFD8616BA28D2": {
|
|
||||||
"logo": "QmTGRDYyEoyCXgZrKXbC9F1K42JujeWeYhzux3qbW7vZxh",
|
|
||||||
"name": "Fight for Life (USA, Europe)",
|
|
||||||
"vid": "QmehEeSFr4pVNWtSjqg6SWJNwe1eo3nJNtoQGW4pspgZ68"
|
|
||||||
},
|
|
||||||
"A226086571C857F3796FD664BBC2C8FE99C78224": {
|
|
||||||
"logo": "QmQwKmMx7Rbvz3J7w4ChsrjoBFh2vcYEx3A8m1GdcTX8Ed",
|
|
||||||
"name": "Syndicate (World)",
|
|
||||||
"vid": "QmZcBy1r8Tg2KdVPUniJHyf75LXPxhjEX1NEkksZMVWNSa"
|
|
||||||
},
|
|
||||||
"A738A2DF47F09C3BFDEDDFC01B35F3E8B5C5D99C": {
|
|
||||||
"logo": "QmYTz3N7NpzHJQYGZ1WJ4UhBe3wFcrWHL8YMwJcZBzBEZT",
|
|
||||||
"name": "Soccer Kid (World) (Beta)",
|
|
||||||
"vid": "QmVQshtfgntHzVn8ZVZdf8i5A36YYNNqUJaLJuF6KdG1GC"
|
|
||||||
},
|
|
||||||
"AE497F4839BDE6C12C87C651A80633A23D6D0713": {
|
|
||||||
"name": "Defender 2000 (World) (Beta)",
|
|
||||||
"ref": "13A4084BD5BE1CCA21D83D4C26B5B8E5CF1A3E30"
|
|
||||||
},
|
|
||||||
"B5DA0B1231A6E6532268C4E04F74E56F047DD5A5": {
|
|
||||||
"ref": "6B010477155F30F1ECEC7EAB2C9A7544242BBD19"
|
|
||||||
},
|
|
||||||
"B615FE0F229EEE89ABE4A6FCE8E4CE63D2F92C6F": {
|
|
||||||
"name": "Soccer Kid (World)",
|
|
||||||
"ref": "A738A2DF47F09C3BFDEDDFC01B35F3E8B5C5D99C"
|
|
||||||
},
|
|
||||||
"B68DE2CD49CB70E92F2415837EDFA7AB46770A78": {
|
|
||||||
"logo": "QmawCN7c7wHCrM4e6XHixQgDxLyQUXbfXWZk8Wc3Zakbeg",
|
|
||||||
"name": "Zero 5 (USA, Europe)",
|
|
||||||
"vid": "QmQjnfGuuznkcmCTP54nKsEMfvPVDKiBvYvWE4KwauxdAF"
|
|
||||||
},
|
|
||||||
"B9C8FF2D26FE1E91ECD6D59B9F3F76C5B5650F26": {
|
|
||||||
"logo": "QmbFV1DVEettBHxrAs5YfdHmKXVRhKtEVnrig3afbN5Emp",
|
|
||||||
"name": "NBA Jam - Tournament Edition (USA, Europe)",
|
|
||||||
"vid": "QmcYQvFP6WC8y6nAhVVBEjGSK1uMrof5fjSFGnNNDAosto"
|
|
||||||
},
|
|
||||||
"BA34DE71A8D278BA76BAD19964D2D886DDB94A2E": {
|
|
||||||
"name": "Rayman (World) (Beta 2)",
|
|
||||||
"ref": "D22912992FD966365A1221886E0FC5303EF04A59"
|
|
||||||
},
|
|
||||||
"BBE7FFB97FC07CEBE53099C8ACCE01366D7F7E15": {
|
|
||||||
"name": "Towers II - Plight of the Stargazer (USA, Europe)"
|
|
||||||
},
|
|
||||||
"BF00EC1FCA2A97BEAF28D52140DF2006B1120C67": {
|
|
||||||
"name": "Checkered Flag (World) (Beta) (CES 1993)",
|
|
||||||
"ref": "4D0CC8D248A8BD0FC4B8169C5D5772E59C073C59"
|
|
||||||
},
|
|
||||||
"C71E0BC9EEED8670C6E7CCC7A0FE3B815D7BAC83": {
|
|
||||||
"logo": "QmWNmY4HTPqNX4kC7yc9UahevNSptFdC7BCg1g49c855Ye",
|
|
||||||
"name": "Supercross 3D (USA, Europe)",
|
|
||||||
"vid": "QmcDoGsfHELmkyfUoTmXBwLcM83eRrwwmp8mtxjwNAD4ca"
|
|
||||||
},
|
|
||||||
"C8F29D04DACCE1A83A1C93AB351737795D0F55DB": {
|
|
||||||
"name": "Breakout 2000 (USA, Europe) (Beta)"
|
|
||||||
},
|
|
||||||
"C92F1FF8EC83EBBE0F77F5A665169408DF53B528": {
|
|
||||||
"name": "Fight for Life (USA, Europe) (Beta) (1995-09-07)",
|
|
||||||
"ref": "9F5B8844D58BB1C174DA4824EABBFD8616BA28D2"
|
|
||||||
},
|
|
||||||
"C9AA59769DF207D85D9212D619C266C793C9063B": {
|
|
||||||
"name": "Cybermorph (World) (Rev B)",
|
|
||||||
"ref": "E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E"
|
|
||||||
},
|
|
||||||
"CDFEFDF28DD9127F7A9425088A1EE3EA88DFB618": {
|
|
||||||
"logo": "QmaqeNB3qRTvDxozJ8p7rJZKzX57TpnDjVS8JBcEkmPBs9",
|
|
||||||
"name": "Brutal Sports Football (World)",
|
|
||||||
"vid": "QmPbw6drqyPkDuiKHThjNFH7SmPxsSDS7ago76MQwpprCX"
|
|
||||||
},
|
|
||||||
"CECEC725301D7540F9392618F4FC948DEF88D398": {
|
|
||||||
"name": "Jack Nicklaus Cyber Golf (USA) (Demo) (1995-01-16)"
|
|
||||||
},
|
|
||||||
"D22912992FD966365A1221886E0FC5303EF04A59": {
|
|
||||||
"logo": "QmXtq7N3BL8y2BkN2juk7Rk9FqJSFkj267XxKmP4hsNMY6",
|
|
||||||
"name": "Rayman (World)",
|
|
||||||
"vid": "QmbzbRxhpyotMMDtuXHruJvN1sjh1JNP8dBEtejf87cdEr"
|
|
||||||
},
|
|
||||||
"D61B7B5912118F114EF00CF44966A5EF62E455A5": {
|
|
||||||
"name": "[BIOS] Atari Jaguar Developer CD (World)"
|
|
||||||
},
|
|
||||||
"D66B4E5F9AC27D53F0D781C741E420698630B9FC": {
|
|
||||||
"logo": "QmXuVrogv2aTxNg6DFFLgei1cuCKyEH7uWdSebHFwCFF4P",
|
|
||||||
"name": "Double Dragon V - The Shadow Falls (World)",
|
|
||||||
"vid": "QmRR3VtTRZu5DTAqFoms9VGq57x1NLsSKxnvrgWvqJiW4c"
|
|
||||||
},
|
|
||||||
"DA7F5DAC4317AB0258F54DA436ABB8D89E530F27": {
|
|
||||||
"logo": "QmWkpKyEEyizMT6vMyaUQcMpAWdW8x5ErjXqM8H84CkXZw",
|
|
||||||
"name": "Dragon - The Bruce Lee Story (World)",
|
|
||||||
"vid": "QmWPaYLEJ4X7obTauVgYqhA3dKjnGNEMYEK6TA8zRB3wM7"
|
|
||||||
},
|
|
||||||
"DD6F15CDA5A84E0F93BB04D24F61727A8276AE58": {
|
|
||||||
"name": "Rayman (World) (Beta 1)",
|
|
||||||
"ref": "D22912992FD966365A1221886E0FC5303EF04A59"
|
|
||||||
},
|
|
||||||
"DDEA86820DCAA1A67BD82CF8023F9B3C494357B0": {
|
|
||||||
"logo": "Qmf9zXG3HqDCwME9Nwft7BNP1MBNTNBhLwCC2NWP3EkML1",
|
|
||||||
"name": "Protector - Special Edition (World)",
|
|
||||||
"vid": "QmNeW9TBPfpVywZ7H7YSQuGgc8bqLAHy5DWwtbPPw9eY47"
|
|
||||||
},
|
|
||||||
"E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E": {
|
|
||||||
"logo": "QmPAtutb1eQvfsnHRLRxk6Na5DUVNGfksxfCqsqi7CT6pE",
|
|
||||||
"name": "Cybermorph (World) (Rev A)",
|
|
||||||
"vid": "QmdrHLeu5mMh6pGU1aodsTVt96UYZCwPN6H2Zi1mpZU2PG"
|
|
||||||
},
|
|
||||||
"E0DB2FDEA7F95743C1EF452B7D289F5FE8158E33": {
|
|
||||||
"name": "Ruiner Pinball (USA, Europe)"
|
|
||||||
},
|
|
||||||
"E3BD433BC4A573F3B05652239A6614C3878A04D1": {
|
|
||||||
"name": "Ultra Vortex (World) (Beta) (1995-03-20)"
|
|
||||||
},
|
|
||||||
"E99FF0ADD256BD45394C9ED8728E38B458CFE5E6": {
|
|
||||||
"name": "High Voltage C Maze (USA) (Demo)"
|
|
||||||
},
|
|
||||||
"EE553176F0A32683B517B84B12C6FAE13C15C3D0": {
|
|
||||||
"name": "Wolfenstein 3D (World)"
|
|
||||||
},
|
|
||||||
"EE5FEA3B99177D9E88AEEDCAD8F01173F1062B87": {
|
|
||||||
"name": "Super Burnout (World) (Demo) (WCES 1995)"
|
|
||||||
},
|
|
||||||
"F0914BE41331E3045BBC04225044DC3C73FD9DE1": {
|
|
||||||
"logo": "QmQ7UpHJPARawoiRfrVnheX2m3tLvFTctpFwz94gory8CT",
|
|
||||||
"name": "International Sensible Soccer (World)",
|
|
||||||
"vid": "QmRCxSGrsU4owDTtGhg278xZhiCScgHPQFgwk9SzTrFdvs"
|
|
||||||
},
|
|
||||||
"F8991B0C385F4E5002FA2A7E2F5E61E8C5213356": {
|
|
||||||
"name": "[BIOS] Atari Jaguar (World)"
|
|
||||||
},
|
|
||||||
"FB03CDB0B9C8171831C0E385C27BEC2D71C39400": {
|
|
||||||
"name": "Zero 5 (USA, Europe) (Beta)",
|
|
||||||
"ref": "B68DE2CD49CB70E92F2415837EDFA7AB46770A78"
|
|
||||||
},
|
|
||||||
"FD8C89250EBC1E403838B2E589D1F69E3FE2FE02": {
|
|
||||||
"logo": "QmcevUQ5YkCiWcNov4grof4VwqeqR8P2DH5kr7AG6E2kPM",
|
|
||||||
"name": "Alien vs Predator (World)",
|
|
||||||
"vid": "QmPF7SYwe3KYS9DxYHoNsVTnhwwFe9sdHkvnLdG7BS3Ff3"
|
|
||||||
},
|
},
|
||||||
"FE4C35C24BE8DF92F867B4FB42EB75A3FCC875E8": {
|
"FE4C35C24BE8DF92F867B4FB42EB75A3FCC875E8": {
|
||||||
"name": "Breakout 2000 (USA, Europe)"
|
"name": "Breakout 2000 (USA, Europe)"
|
||||||
},
|
},
|
||||||
|
"C8F29D04DACCE1A83A1C93AB351737795D0F55DB": {
|
||||||
|
"name": "Breakout 2000 (USA, Europe) (Beta)"
|
||||||
|
},
|
||||||
|
"7F21C6DE18C46629C0D54E68143D619093B4B8D6": {
|
||||||
|
"name": "Brett Hull Hockey (World)"
|
||||||
|
},
|
||||||
|
"CDFEFDF28DD9127F7A9425088A1EE3EA88DFB618": {
|
||||||
|
"name": "Brutal Sports Football (World)",
|
||||||
|
"vid": "QmPbw6drqyPkDuiKHThjNFH7SmPxsSDS7ago76MQwpprCX",
|
||||||
|
"logo": "QmaqeNB3qRTvDxozJ8p7rJZKzX57TpnDjVS8JBcEkmPBs9"
|
||||||
|
},
|
||||||
|
"752D6E1ED185F9DB49230F42319A573A44E32397": {
|
||||||
|
"name": "Bubsy in Fractured Furry Tales (World)",
|
||||||
|
"vid": "Qme3hphX7hkwVJWpYwcFnxU6u2Y8J7TB9JFpKcAsMuxDQh",
|
||||||
|
"logo": "QmcUtsD4orEMZjoPbjk3h2YTS3Ug55pF4RhvGFCbExUBcV"
|
||||||
|
},
|
||||||
|
"0C195676BE099F4594CE1D5A38303F6CCE899DC5": {
|
||||||
|
"name": "Cannon Fodder (World)",
|
||||||
|
"vid": "Qmb8XZcpytbb8jDyW617RgCtRfBUKzxx971Fac4AUh2UUa",
|
||||||
|
"logo": "QmRFsbJZ7WKewZsSVWy5qJA6SxJbnSQDFDsRnpdXseL44K"
|
||||||
|
},
|
||||||
|
"4D0CC8D248A8BD0FC4B8169C5D5772E59C073C59": {
|
||||||
|
"name": "Checkered Flag (World)",
|
||||||
|
"vid": "Qma4iVerm6svScmwC83dpydmAYLQR1NUHTuNkns1JxEJRe",
|
||||||
|
"logo": "QmYKfShHBmErdpVQVdWaF1bwSb6xwexpjrPrKKjRextc9t"
|
||||||
|
},
|
||||||
|
"BF00EC1FCA2A97BEAF28D52140DF2006B1120C67": {
|
||||||
|
"name": "Checkered Flag (World) (Beta) (CES 1993)"
|
||||||
|
},
|
||||||
|
"6588F7D677463D0EF10CF905D44A2581FD9DD055": {
|
||||||
|
"name": "Club Drive (World)"
|
||||||
|
},
|
||||||
|
"93A29D894F5472EB755D13E721414A6BD9686C02": {
|
||||||
|
"name": "Club Drive (World) (Beta)"
|
||||||
|
},
|
||||||
|
"E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E": {
|
||||||
|
"name": "Cybermorph (World) (Rev A)",
|
||||||
|
"vid": "QmdrHLeu5mMh6pGU1aodsTVt96UYZCwPN6H2Zi1mpZU2PG",
|
||||||
|
"logo": "QmPAtutb1eQvfsnHRLRxk6Na5DUVNGfksxfCqsqi7CT6pE"
|
||||||
|
},
|
||||||
|
"C9AA59769DF207D85D9212D619C266C793C9063B": {
|
||||||
|
"name": "Cybermorph (World) (Rev B)"
|
||||||
|
},
|
||||||
|
"589EFBC772360FA51EDDC096A162C8D9FE7838D3": {
|
||||||
|
"name": "Cybermorph (World) (Beta) (1993-08-23)"
|
||||||
|
},
|
||||||
|
"13A4084BD5BE1CCA21D83D4C26B5B8E5CF1A3E30": {
|
||||||
|
"name": "Defender 2000 (World)",
|
||||||
|
"vid": "QmRw7miWyxxP1jJ3UdnXNhaattqf8j72nu5wiqmmPkdQZF",
|
||||||
|
"logo": "QmVCsuGdk9mepCZiZ9iD7owQ4SKYSuQSomP7WMXvub5rfn"
|
||||||
|
},
|
||||||
|
"AE497F4839BDE6C12C87C651A80633A23D6D0713": {
|
||||||
|
"name": "Defender 2000 (World) (Beta)"
|
||||||
|
},
|
||||||
|
"20DDF412E42A50D526CBBB6411BF0EC4516DB283": {
|
||||||
|
"name": "Doom (World)",
|
||||||
|
"vid": "QmSZgM6UhZ399co6dhiB3aGmvtq1iVr5N1SpeC792NAhUk",
|
||||||
|
"logo": "QmZTx6wneGVU4zJTghJF7VARH89djP1RNdBUaSV8ykpMwc"
|
||||||
|
},
|
||||||
|
"D66B4E5F9AC27D53F0D781C741E420698630B9FC": {
|
||||||
|
"name": "Double Dragon V - The Shadow Falls (World)",
|
||||||
|
"vid": "QmRR3VtTRZu5DTAqFoms9VGq57x1NLsSKxnvrgWvqJiW4c",
|
||||||
|
"logo": "QmXuVrogv2aTxNg6DFFLgei1cuCKyEH7uWdSebHFwCFF4P"
|
||||||
|
},
|
||||||
|
"DA7F5DAC4317AB0258F54DA436ABB8D89E530F27": {
|
||||||
|
"name": "Dragon - The Bruce Lee Story (World)",
|
||||||
|
"vid": "QmWPaYLEJ4X7obTauVgYqhA3dKjnGNEMYEK6TA8zRB3wM7",
|
||||||
|
"logo": "QmWkpKyEEyizMT6vMyaUQcMpAWdW8x5ErjXqM8H84CkXZw"
|
||||||
|
},
|
||||||
|
"298148131AF2A86BA5D5430B1B8982710CE68874": {
|
||||||
|
"name": "Dragon - The Bruce Lee Story (World) (Beta) (1994-09-26)"
|
||||||
|
},
|
||||||
|
"8F556976F42C393858127D99112264E6A5A9AE19": {
|
||||||
|
"name": "Dragon - The Bruce Lee Story (World) (Beta) (1994-10-06)"
|
||||||
|
},
|
||||||
|
"179B511464ABCDA5A5DE9F971BD5CB5775D8C313": {
|
||||||
|
"name": "Evolution - Dino Dudes (World)",
|
||||||
|
"vid": "QmfGyzVC5mrtMxEv78czZmhzmphoEGKRrEMM7nPDp6FXbP",
|
||||||
|
"logo": "QmVZaMvFkoR1LxXRogBRMsMaG5pe71JJVzusfSkerk8aPB"
|
||||||
|
},
|
||||||
|
"5BE3DA69A64B779D064D139968FADBE1C475C715": {
|
||||||
|
"name": "Fever Pitch Soccer (World) (En,Fr,De,Es,It)",
|
||||||
|
"vid": "QmZRt4vXXPvkKiLihkBuHgCNfnso14YPHbqhD8WsaXzw9f",
|
||||||
|
"logo": "QmdZbMCrPHFmjWxsdwAi9VTroT7ADXG7ZmVpF97yWjjuWq"
|
||||||
|
},
|
||||||
|
"9F5B8844D58BB1C174DA4824EABBFD8616BA28D2": {
|
||||||
|
"name": "Fight for Life (USA, Europe)",
|
||||||
|
"vid": "QmehEeSFr4pVNWtSjqg6SWJNwe1eo3nJNtoQGW4pspgZ68",
|
||||||
|
"logo": "QmTGRDYyEoyCXgZrKXbC9F1K42JujeWeYhzux3qbW7vZxh"
|
||||||
|
},
|
||||||
|
"37FE344383D7991D19BF2C862D29A20EAF1DA8EF": {
|
||||||
|
"name": "Fight for Life (USA, Europe) (Beta) (1995-03-22)"
|
||||||
|
},
|
||||||
|
"C92F1FF8EC83EBBE0F77F5A665169408DF53B528": {
|
||||||
|
"name": "Fight for Life (USA, Europe) (Beta) (1995-09-07)"
|
||||||
|
},
|
||||||
|
"824613451BE0B99020FC02E5DBA15C45E76FC8C2": {
|
||||||
|
"name": "Flashback - The Quest for Identity (World) (En,Fr)",
|
||||||
|
"vid": "QmSUe7urtRuLe4KkZpqXoVPsFHYsU7FiYLrwmwsfXD4QnU",
|
||||||
|
"logo": "QmTGeRg29B8rinMjgnRXE352VnZn2qe1kaRGdwkyLbSPug"
|
||||||
|
},
|
||||||
|
"886CBBAA434D6E1E7FC4401BB6D176C2788C4D19": {
|
||||||
|
"name": "Flip Out! (World)",
|
||||||
|
"vid": "QmZDAGfvPwuWWzcn2PtWJZgrPNbnccfHj6yg8WSWvmJPyX",
|
||||||
|
"logo": "QmW8jpj4BKCKmEd2FjGEV14rRd4mMWcmALzW1ULdaF7k3J"
|
||||||
|
},
|
||||||
|
"5B2295907F6FDADF5F44B1E392DCD6F5471FD095": {
|
||||||
|
"name": "Frog Feast (World)"
|
||||||
|
},
|
||||||
|
"E99FF0ADD256BD45394C9ED8728E38B458CFE5E6": {
|
||||||
|
"name": "High Voltage C Maze (USA) (Demo)"
|
||||||
|
},
|
||||||
|
"94E615E7A57F7C08645322870121BB32310298F5": {
|
||||||
|
"name": "Hover Strike (World)"
|
||||||
|
},
|
||||||
|
"037396347D65D1A8B56E7DB361D737AB2070118F": {
|
||||||
|
"name": "Hyper Force (World)"
|
||||||
|
},
|
||||||
|
"3DB485AAA28A817F468739A85E3F25741C3855AD": {
|
||||||
|
"name": "I-War (USA, Europe)",
|
||||||
|
"vid": "QmXXvJayEvptD1RNg5RBwYYdSv2hfV4RGRnbqVbHu5twiB",
|
||||||
|
"logo": "QmW7ooQuquXj9SmMRnm9jzanXbyFoiXKwkczFMr38nhUet"
|
||||||
|
},
|
||||||
|
"F0914BE41331E3045BBC04225044DC3C73FD9DE1": {
|
||||||
|
"name": "International Sensible Soccer (World)",
|
||||||
|
"vid": "QmRCxSGrsU4owDTtGhg278xZhiCScgHPQFgwk9SzTrFdvs",
|
||||||
|
"logo": "QmQ7UpHJPARawoiRfrVnheX2m3tLvFTctpFwz94gory8CT"
|
||||||
|
},
|
||||||
|
"8BDEFC60DD1734315E1C145E29ABF41800F908CB": {
|
||||||
|
"name": "Iron Soldier (World)"
|
||||||
|
},
|
||||||
|
"47B9580EB9EEA121CF080CDBDC4AFE7FB7A19AB5": {
|
||||||
|
"name": "Iron Soldier 2 (USA, Europe)"
|
||||||
|
},
|
||||||
|
"271E9411EB9E6C74240D254BDD7BAB9A039BC6D9": {
|
||||||
|
"name": "Jack Nicklaus Cyber Golf (USA) (Proto) (1994-07-04)"
|
||||||
|
},
|
||||||
|
"89306ADA943C87DA740E6166F1F53837566513E2": {
|
||||||
|
"name": "Jack Nicklaus Cyber Golf (USA) (Demo) (1995-01-13)"
|
||||||
|
},
|
||||||
|
"CECEC725301D7540F9392618F4FC948DEF88D398": {
|
||||||
|
"name": "Jack Nicklaus Cyber Golf (USA) (Demo) (1995-01-16)"
|
||||||
|
},
|
||||||
|
"2327112B9C6F9B0E49C55DEF0ADB8119EE39CE29": {
|
||||||
|
"name": "Kasumi Ninja (World)",
|
||||||
|
"vid": "QmV3CbiaPDF11B52Sx6DzV4d1ZQueBztnpSDfqRWn51tHz",
|
||||||
|
"logo": "QmaCEAzbaV8gTJtEhwc7ewwQegMZbGjHAngC6jQfS6UF9T"
|
||||||
|
},
|
||||||
|
"3650143BD057DD39B96E0AD0207778353F17ABE7": {
|
||||||
|
"name": "Kasumi Ninja (World) (Demo)"
|
||||||
|
},
|
||||||
|
"3EA9053A63A3B0165CCEC91E85F0E699A998DC0A": {
|
||||||
|
"name": "Missile Command 3D (World)",
|
||||||
|
"vid": "QmZGwx2ZMDBK2XsPyrvGAujU34Fx6fqWDP85SKg8yyxLxr",
|
||||||
|
"logo": "QmcffN7HbkieG9gXHkZ72nviP425m8xMbYh2BxCwnvLAvN"
|
||||||
|
},
|
||||||
|
"0E95613020D1B3A0AC76D994423C6F15DDFE9703": {
|
||||||
|
"name": "Missile Command VR (World) (Beta) (1995-07-05)"
|
||||||
|
},
|
||||||
|
"3D3E6C7E552CBA5F99B6EBC15B951CE4BD956766": {
|
||||||
|
"name": "Native (USA) (Proto)"
|
||||||
|
},
|
||||||
|
"B9C8FF2D26FE1E91ECD6D59B9F3F76C5B5650F26": {
|
||||||
|
"name": "NBA Jam - Tournament Edition (USA, Europe)",
|
||||||
|
"vid": "QmcYQvFP6WC8y6nAhVVBEjGSK1uMrof5fjSFGnNNDAosto",
|
||||||
|
"logo": "QmbFV1DVEettBHxrAs5YfdHmKXVRhKtEVnrig3afbN5Emp"
|
||||||
|
},
|
||||||
|
"3EC9524921D07647F527E0A82A831F794A7D6D9B": {
|
||||||
|
"name": "Phase Zero (World)"
|
||||||
|
},
|
||||||
|
"4733CF2EDE2713467F2A80B052AB94EDF6DFE534": {
|
||||||
|
"name": "Pinball Fantasies (World)",
|
||||||
|
"vid": "QmUX4sQRATEybMWuzwogeUjUZ7vFxGnYyhncrze5MmZ9WK",
|
||||||
|
"logo": "QmNmKQXYJMkmwnj6mmgPAM6rsmftpNeqrh4ATzggKKJnfo"
|
||||||
|
},
|
||||||
|
"25611B1CFF1384DB72CC5422DBE70555E5BBD32C": {
|
||||||
|
"name": "Pitfall - The Mayan Adventure (USA, Europe)",
|
||||||
|
"vid": "QmPniHfXG46CEwZcGVnw9X4MxXYJd8RRe9T5qLGwT2qMUK",
|
||||||
|
"logo": "QmXFSYwvRJqkviBDVgicJu2E2rhxYmNXhDi8dRqPtoZr8a"
|
||||||
|
},
|
||||||
|
"032AA31CEF116BFBA8632C14BC2FCE5BC44FF137": {
|
||||||
|
"name": "Plucky Duck in Hollywood Hijinks (USA) (Proto) (1994-10-28)"
|
||||||
|
},
|
||||||
|
"75EE0AB0DABA31284A1DFF27F3C247885F5A427A": {
|
||||||
|
"name": "Power Drive Rally (World)",
|
||||||
|
"vid": "QmRfWpZnaUhLZjmPDuMk1JKkrvJ8EM7S72vsB64hZZveED",
|
||||||
|
"logo": "QmWmPqprXS7jj5SmZB1cpBNTVaDxqdMzpfbMj8ZPj9iQer"
|
||||||
|
},
|
||||||
|
"8043E3B521EA00E806D3AB79C6C548A140944DB1": {
|
||||||
|
"name": "Protector (World)"
|
||||||
|
},
|
||||||
|
"DDEA86820DCAA1A67BD82CF8023F9B3C494357B0": {
|
||||||
|
"name": "Protector - Special Edition (World)",
|
||||||
|
"vid": "QmNeW9TBPfpVywZ7H7YSQuGgc8bqLAHy5DWwtbPPw9eY47",
|
||||||
|
"logo": "Qmf9zXG3HqDCwME9Nwft7BNP1MBNTNBhLwCC2NWP3EkML1"
|
||||||
|
},
|
||||||
|
"9D2F894DA3F25944C2F33E3A109AE8D0D951D329": {
|
||||||
|
"name": "Raiden (World)"
|
||||||
|
},
|
||||||
|
"50CCF7043A702531A0E9EF86D629D5EDE5255C13": {
|
||||||
|
"name": "Raiden (World) (Rev A)"
|
||||||
|
},
|
||||||
|
"D22912992FD966365A1221886E0FC5303EF04A59": {
|
||||||
|
"name": "Rayman (World)",
|
||||||
|
"vid": "QmbzbRxhpyotMMDtuXHruJvN1sjh1JNP8dBEtejf87cdEr",
|
||||||
|
"logo": "QmXtq7N3BL8y2BkN2juk7Rk9FqJSFkj267XxKmP4hsNMY6"
|
||||||
|
},
|
||||||
|
"BA34DE71A8D278BA76BAD19964D2D886DDB94A2E": {
|
||||||
|
"name": "Rayman (World) (Beta 2)"
|
||||||
|
},
|
||||||
|
"DD6F15CDA5A84E0F93BB04D24F61727A8276AE58": {
|
||||||
|
"name": "Rayman (World) (Beta 1)"
|
||||||
|
},
|
||||||
|
"E0DB2FDEA7F95743C1EF452B7D289F5FE8158E33": {
|
||||||
|
"name": "Ruiner Pinball (USA, Europe)"
|
||||||
|
},
|
||||||
|
"34A9941459E3B66C836F25B3EF55303734B9583E": {
|
||||||
|
"name": "Skyhammer (World)"
|
||||||
|
},
|
||||||
"FE66FFDC9323CFCFC64DA06E16F2D5A4A4E1A236": {
|
"FE66FFDC9323CFCFC64DA06E16F2D5A4A4E1A236": {
|
||||||
"name": "Slam Racer (USA) (Proto)"
|
"name": "Slam Racer (USA) (Proto)"
|
||||||
|
},
|
||||||
|
"A738A2DF47F09C3BFDEDDFC01B35F3E8B5C5D99C": {
|
||||||
|
"name": "Soccer Kid (World) (Beta)",
|
||||||
|
"vid": "QmVQshtfgntHzVn8ZVZdf8i5A36YYNNqUJaLJuF6KdG1GC",
|
||||||
|
"logo": "QmYTz3N7NpzHJQYGZ1WJ4UhBe3wFcrWHL8YMwJcZBzBEZT"
|
||||||
|
},
|
||||||
|
"B615FE0F229EEE89ABE4A6FCE8E4CE63D2F92C6F": {
|
||||||
|
"name": "Soccer Kid (World)"
|
||||||
|
},
|
||||||
|
"6B010477155F30F1ECEC7EAB2C9A7544242BBD19": {
|
||||||
|
"name": "Space War 2000 (World)",
|
||||||
|
"vid": "QmPJW65sMdxtqCWQUFfwHyHU9WPuA1PNcwipA3SZ7fwwVb",
|
||||||
|
"logo": "QmNym4uVncwQDYRwoHNMv3YHA7SeeecLqbxVPc7XLrRwgQ"
|
||||||
|
},
|
||||||
|
"9A58BC0C8843FD00A2871C01B91FEC206408BF80": {
|
||||||
|
"name": "Super Burnout (World)"
|
||||||
|
},
|
||||||
|
"EE5FEA3B99177D9E88AEEDCAD8F01173F1062B87": {
|
||||||
|
"name": "Super Burnout (World) (Demo) (WCES 1995)"
|
||||||
|
},
|
||||||
|
"C71E0BC9EEED8670C6E7CCC7A0FE3B815D7BAC83": {
|
||||||
|
"name": "Supercross 3D (USA, Europe)",
|
||||||
|
"vid": "QmcDoGsfHELmkyfUoTmXBwLcM83eRrwwmp8mtxjwNAD4ca",
|
||||||
|
"logo": "QmWNmY4HTPqNX4kC7yc9UahevNSptFdC7BCg1g49c855Ye"
|
||||||
|
},
|
||||||
|
"A226086571C857F3796FD664BBC2C8FE99C78224": {
|
||||||
|
"name": "Syndicate (World)",
|
||||||
|
"vid": "QmZcBy1r8Tg2KdVPUniJHyf75LXPxhjEX1NEkksZMVWNSa",
|
||||||
|
"logo": "QmQwKmMx7Rbvz3J7w4ChsrjoBFh2vcYEx3A8m1GdcTX8Ed"
|
||||||
|
},
|
||||||
|
"3828D2F953224D06B5FE7AB3644D6A75FDFE79E3": {
|
||||||
|
"name": "Tempest 2000 (World)",
|
||||||
|
"vid": "QmXvDFkX7YmDsHvUiJupbTiXEefFzqTQecktmnzFMRjkff",
|
||||||
|
"logo": "QmcVjqiqF82wvqVkucrthJwhT8BsUUowFwNLYMkCNmpjtK"
|
||||||
|
},
|
||||||
|
"9AD03858597AA1E1FFF26250996B07D1A70D1296": {
|
||||||
|
"name": "Thea Realm Fighters (USA) (Proto 1)"
|
||||||
|
},
|
||||||
|
"07C76D3F4AE05B75EA7AD62147172FD146F4F876": {
|
||||||
|
"name": "Thea Realm Fighters (USA) (Proto 2)"
|
||||||
|
},
|
||||||
|
"24DA4FE776AE9DAA86CF643C703EC0F399C77521": {
|
||||||
|
"name": "Theme Park (World)",
|
||||||
|
"vid": "QmQJG7EmAZwZWjbZ4zL9umjpTww6pR9HdRvVgfxAZwCTn2",
|
||||||
|
"logo": "QmdaJGvXuh8dFDgpAdW2jXSnr4pj6e7uSXULthA8t6STKq"
|
||||||
|
},
|
||||||
|
"841CE8AE1B71955D9B8DF3B80DA11FD40308EF5F": {
|
||||||
|
"name": "Tiny Toon Adventures (USA) (Proto)"
|
||||||
|
},
|
||||||
|
"4566B33799A7C65B3E9643383D0282172FF619A5": {
|
||||||
|
"name": "Total Carnage (World)",
|
||||||
|
"vid": "QmaDdW5niQbiwpN4T5CDGi9x6iFtWsVLokF8vCviKZgHf5",
|
||||||
|
"logo": "QmZfJ4Z8e4CNcmLgPKQAWEqjpRt6sqkdoKRkNyDtYdXq8o"
|
||||||
|
},
|
||||||
|
"1EF1B86ABF725F32267BFA140DC049CE9BA92AEA": {
|
||||||
|
"name": "Total Carnage (World) (Beta)"
|
||||||
|
},
|
||||||
|
"BBE7FFB97FC07CEBE53099C8ACCE01366D7F7E15": {
|
||||||
|
"name": "Towers II - Plight of the Stargazer (USA, Europe)"
|
||||||
|
},
|
||||||
|
"88E86085932AF10DFB8C94032F73A5992A30B55C": {
|
||||||
|
"name": "Trevor McFur in the Crescent Galaxy (World) (Rev A)",
|
||||||
|
"vid": "QmaDA59oWsebKK5xJm4MMtcE26smcfjd2CbfTGesKCuP9Q",
|
||||||
|
"logo": "QmQkNeADHymrTFwyueXEemhWeZssNK8mkrqn5KQGipogox"
|
||||||
|
},
|
||||||
|
"2F0E950517D26C83408E5CF210098C0897725537": {
|
||||||
|
"name": "Trevor McFur in the Crescent Galaxy (World) (Beta)"
|
||||||
|
},
|
||||||
|
"489B0FE9F070AD27F63A17AA8F0D2AFAA4DABDB3": {
|
||||||
|
"name": "Trevor McFur in the Crescent Galaxy (World)"
|
||||||
|
},
|
||||||
|
"4C6A6432EA0BC198F900F4EBDB8A3A7A34475D84": {
|
||||||
|
"name": "Troy Aikman NFL Football (World)",
|
||||||
|
"vid": "QmTyfEp2q2FLfEX6gH27aufVfydfEtPfjYm2aJsp7LbgAj",
|
||||||
|
"logo": "QmYFg6g3WcSCA7Akoq8dMrVCR27XxUC13iEminXUFWWXJf"
|
||||||
|
},
|
||||||
|
"311E41EBBA1B78F514BF16789DDB7E7E8DFB47FF": {
|
||||||
|
"name": "Ultra Vortek (World)",
|
||||||
|
"vid": "QmVjygYsL4ZXhhUp1V7W2FRQq6kcB8Gi8XQGfLMf1x3paU",
|
||||||
|
"logo": "QmbKxD6MNUuRDLX8Q5Syzcwdjdfot4wjADemAf7wnD6rZw"
|
||||||
|
},
|
||||||
|
"E3BD433BC4A573F3B05652239A6614C3878A04D1": {
|
||||||
|
"name": "Ultra Vortex (World) (Beta) (1995-03-20)"
|
||||||
|
},
|
||||||
|
"02253D93EEF375C8334F1F77A2D8B72FFF7A94C6": {
|
||||||
|
"name": "Val d'Isere Skiing and Snowboarding (World)"
|
||||||
|
},
|
||||||
|
"2AB78090E8AFD985CA4FE54DA8DAEF7E125550FA": {
|
||||||
|
"name": "Virtual VCS (USA) (Proto)"
|
||||||
|
},
|
||||||
|
"4D16596AC2D991435599E1FE03292ADFB25C346D": {
|
||||||
|
"name": "White Men Can't Jump (USA, Europe)"
|
||||||
|
},
|
||||||
|
"EE553176F0A32683B517B84B12C6FAE13C15C3D0": {
|
||||||
|
"name": "Wolfenstein 3D (World)"
|
||||||
|
},
|
||||||
|
"641B07F93E8DF03AB8B9BF1E8DC56E2889F247F8": {
|
||||||
|
"name": "Worms (USA, Europe)",
|
||||||
|
"vid": "QmcahTbiuzvNm1ZXSoVKiULmxpGGjCAN3ZQpcxgHZFQ6RB",
|
||||||
|
"logo": "QmNXexYonZXnNi4kKfDezNkX1Dg8tuU3iN7518sFtupyre"
|
||||||
|
},
|
||||||
|
"B68DE2CD49CB70E92F2415837EDFA7AB46770A78": {
|
||||||
|
"name": "Zero 5 (USA, Europe)",
|
||||||
|
"vid": "QmQjnfGuuznkcmCTP54nKsEMfvPVDKiBvYvWE4KwauxdAF",
|
||||||
|
"logo": "QmawCN7c7wHCrM4e6XHixQgDxLyQUXbfXWZk8Wc3Zakbeg"
|
||||||
|
},
|
||||||
|
"FB03CDB0B9C8171831C0E385C27BEC2D71C39400": {
|
||||||
|
"name": "Zero 5 (USA, Europe) (Beta)"
|
||||||
|
},
|
||||||
|
"32FF3811F8F6DF8A2BB56165C62227A3A480E55C": {
|
||||||
|
"name": "Zool 2 (World)",
|
||||||
|
"vid": "Qmerp8YE5HYS4MyP8jnjY2YMNJbChZr2jGbuttMsXz16F4",
|
||||||
|
"logo": "QmSRcjgwAWLouMTYq6GFQQSghH28yg8fGQHwYjbj7nA2w7"
|
||||||
|
},
|
||||||
|
"52EC786F7536AB043DC24725AF94068CAD2B0EC3": {
|
||||||
|
"name": "Zoop (USA, Europe)"
|
||||||
|
},
|
||||||
|
"7E4356FECBEADD741C2BF9868A37B2956BD27D5E": {
|
||||||
|
"name": "Zzyorxx II (USA) (Demo)"
|
||||||
|
},
|
||||||
|
"61D4DB841C090AE3104EB20B6087160FF80E7A12": {
|
||||||
|
"ref": "886CBBAA434D6E1E7FC4401BB6D176C2788C4D19"
|
||||||
|
},
|
||||||
|
"B5DA0B1231A6E6532268C4E04F74E56F047DD5A5": {
|
||||||
|
"ref": "6B010477155F30F1ECEC7EAB2C9A7544242BBD19"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1414
metadata/lynx.json
1414
metadata/lynx.json
File diff suppressed because it is too large
Load Diff
8129
metadata/msx.json
8129
metadata/msx.json
File diff suppressed because it is too large
Load Diff
8259
metadata/n64.json
8259
metadata/n64.json
File diff suppressed because it is too large
Load Diff
52003
metadata/nds.json
52003
metadata/nds.json
File diff suppressed because it is too large
Load Diff
26661
metadata/nes.json
26661
metadata/nes.json
File diff suppressed because it is too large
Load Diff
1074
metadata/ngp.json
1074
metadata/ngp.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6684
metadata/pce.json
6684
metadata/pce.json
File diff suppressed because it is too large
Load Diff
68539
metadata/psx.json
68539
metadata/psx.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3903
metadata/segaCD.json
3903
metadata/segaCD.json
File diff suppressed because it is too large
Load Diff
6127
metadata/segaGG.json
6127
metadata/segaGG.json
File diff suppressed because it is too large
Load Diff
19838
metadata/segaMD.json
19838
metadata/segaMD.json
File diff suppressed because it is too large
Load Diff
4929
metadata/segaMS.json
4929
metadata/segaMS.json
File diff suppressed because it is too large
Load Diff
1733
metadata/segaSG.json
1733
metadata/segaSG.json
File diff suppressed because it is too large
Load Diff
14152
metadata/segaSaturn.json
14152
metadata/segaSaturn.json
File diff suppressed because it is too large
Load Diff
27999
metadata/snes.json
27999
metadata/snes.json
File diff suppressed because it is too large
Load Diff
232
metadata/vb.json
232
metadata/vb.json
@ -1,149 +1,143 @@
|
|||||||
{
|
{
|
||||||
"0E086D7EF2BD8B97315196943FD0B71DA07AA8F1": {
|
"5177015A91442E56BD76AF39447BCA365E06C272": {
|
||||||
"logo": "QmZPPnoAR8NcAps63MrRsxN3A9kej6ghDunoRg77fF4ZvA",
|
"name": "3-D Tetris (USA)",
|
||||||
"name": "Jack Bros. (USA)",
|
"vid": "QmcsARDkyozauAacURMi33H5TXAtraTWGJzPmJv2onvGQJ",
|
||||||
"vid": "QmfYBbFiGsguYMxDoJZQGyzYCcDMhqvLynivFXj9QPgrbp"
|
"logo": "QmYB88E8a8SfCrFhJ7jaEkU6arv4prof96iMbKQSUrD5Ww"
|
||||||
},
|
},
|
||||||
"1196EF8B9A96A0FF2F3634F8EB1C4865C0FB6B01": {
|
"1196EF8B9A96A0FF2F3634F8EB1C4865C0FB6B01": {
|
||||||
"name": "Bound High (Japan) (En) (Proto)"
|
"name": "Bound High (Japan) (En) (Proto)"
|
||||||
},
|
},
|
||||||
"16AD904FAAFD24642DFD056D10A768E81F0D9BFA": {
|
"93A25ABF4F86E71A49C1C3A470140BB40CB693D6": {
|
||||||
"logo": "QmUpBKPjXguAkS69F1ZQRoX5D3kRznYoEVYF8bFqLpBA1R",
|
"name": "Galactic Pinball (Japan, USA)",
|
||||||
"name": "Space Invaders - Virtual Collection (Japan)",
|
"vid": "QmcZ6zTS3VPzSMRjTxQUkD5zeRssNosfL1BKbPvbHeRzXi",
|
||||||
"vid": "QmUe2GCnx99s9DZxaQtHmtNrzRJ7YvHGi1NuqiAtDf46cj"
|
"logo": "QmfPLMFW7RHrNQbZKj6kME6fA3p1rQJvvPzCdMYVmkf5Bf"
|
||||||
},
|
|
||||||
"1A67F6BDB45DB46E22B8AADEA754A68AD379AE29": {
|
|
||||||
"logo": "QmPoFytQpiAoJ9nW4ZrjjJMVdAcZyME7SRRVDSnK5dMkPc",
|
|
||||||
"name": "SD Gundam - Dimension War (Japan)",
|
|
||||||
"vid": "QmR2dDWQ3GrT842baDxNQHP6N6wmfheZGZ2fqYWHRK2yYF"
|
|
||||||
},
|
|
||||||
"217CA062F65D4E8B2848085918AF5AC4B33B12D4": {
|
|
||||||
"name": "Niko-chan Battle (Japan) (Proto)"
|
|
||||||
},
|
},
|
||||||
"23CE3C174789CDD306497D86CB2C4E76BA8B06E5": {
|
"23CE3C174789CDD306497D86CB2C4E76BA8B06E5": {
|
||||||
"logo": "Qmdwyb2mL1zDo1BMnNVFTGBgsrzZZjmjWeop9pcfP88zhK",
|
|
||||||
"name": "Golf (USA)",
|
"name": "Golf (USA)",
|
||||||
"vid": "QmVvzYoiK3xesgZveFqRhmQ14v11jcdAsTQj9rTf5DyW7v"
|
"vid": "QmVvzYoiK3xesgZveFqRhmQ14v11jcdAsTQj9rTf5DyW7v",
|
||||||
},
|
"logo": "Qmdwyb2mL1zDo1BMnNVFTGBgsrzZZjmjWeop9pcfP88zhK"
|
||||||
"266FE615EE3DF1160A20B456824699F16810FA28": {
|
|
||||||
"logo": "QmXNUHwFo4822jWfXpwutWJTsdLu5JasjiAT1aPNkvqT56",
|
|
||||||
"name": "Virtual League Baseball (USA)",
|
|
||||||
"vid": "QmZXfBoZfHppqtL4XwrPLALomir7ZsdQ7GehvkB6y2TwRP"
|
|
||||||
},
|
|
||||||
"274C328FBD904F20E69172AB826BF8F94CED1BDB": {
|
|
||||||
"logo": "QmNvnaDejYEdsowxN8KZaj6qdj4vqVv8btBy4pSxdyJHNW",
|
|
||||||
"name": "Virtual Boy Wario Land (Japan, USA)",
|
|
||||||
"vid": "QmYxxf3mjbPhA8JKkn8yZh73LC1UHCdWrtviMvjTg8VqRe"
|
|
||||||
},
|
|
||||||
"322DC4C2DF74BF19D8A30D43C489401BE5E76CA5": {
|
|
||||||
"name": "Hyper Fighting (World) (Beta) (Aftermarket) (Unl) (Pirate)",
|
|
||||||
"ref": "578355DE83822CE67522033DD7545631124A71AB"
|
|
||||||
},
|
|
||||||
"37E06E53CE2810E870835C48DECDD6C72079BFDA": {
|
|
||||||
"logo": "QmV2hr6f1Sf2ZkfmtjYXDPTRjXyQ3qa3XvmxHqmYcUnLMP",
|
|
||||||
"name": "Innsmouth no Yakata (Japan)",
|
|
||||||
"vid": "QmS7VL589BMEasXVM3tBDwfEjqUpcATcMP4GN4RMauh5yw"
|
|
||||||
},
|
|
||||||
"38F9008F60B09DEEF1151D46B905B090A0338200": {
|
|
||||||
"logo": "QmSYrvARB5C2QFF1MjTjGnrr3Vwh42iXXd9uXFKbYHF1tm",
|
|
||||||
"name": "Vertical Force (USA)",
|
|
||||||
"vid": "QmPpDpBSRP8UZidJLsJiRaWoFXY1M5WKnU2ZApzSzshwJB"
|
|
||||||
},
|
|
||||||
"3F08CAE2B6F9E386529949FF954220860DB1AF64": {
|
|
||||||
"name": "Space Squash (Japan)"
|
|
||||||
},
|
|
||||||
"494FA60CB8997880B4E8CD3270B21D6C2768A48A": {
|
|
||||||
"logo": "Qmcz6tW9nEPT5Vp4KYvBw5nspXfo6M5e2WtSuKoThEsZiE",
|
|
||||||
"name": "Red Alarm (USA)",
|
|
||||||
"vid": "QmRidMNd9q92SawBCir2rW6EJsjm7SRjUpgkXmUTUCBGCV"
|
|
||||||
},
|
|
||||||
"5162F7FA9E3EAE4338C53CCBA641BA080C768754": {
|
|
||||||
"logo": "QmeeJjkBUR8vXjvxo8xwixChLpeNShxiWT9wjeaafNu54x",
|
|
||||||
"name": "Mario's Tennis (Japan, USA)",
|
|
||||||
"vid": "QmV46x6EkJUYw69Li1KC2yGuDWKcoPVRAvXoUZGfr58Sjq"
|
|
||||||
},
|
|
||||||
"5177015A91442E56BD76AF39447BCA365E06C272": {
|
|
||||||
"logo": "QmYB88E8a8SfCrFhJ7jaEkU6arv4prof96iMbKQSUrD5Ww",
|
|
||||||
"name": "3-D Tetris (USA)",
|
|
||||||
"vid": "QmcsARDkyozauAacURMi33H5TXAtraTWGJzPmJv2onvGQJ"
|
|
||||||
},
|
},
|
||||||
"578355DE83822CE67522033DD7545631124A71AB": {
|
"578355DE83822CE67522033DD7545631124A71AB": {
|
||||||
"logo": "QmQPUUg8cHNUmbwChaYV8ejbdoo8kW3G6smRsauUVKectJ",
|
|
||||||
"name": "Hyper Fighting (World) (Aftermarket) (Unl) (Pirate)",
|
"name": "Hyper Fighting (World) (Aftermarket) (Unl) (Pirate)",
|
||||||
"vid": "QmfBs2QTLxhsN5LtpVxW4NUUWp9qEn5o69xXKc8Qd4cCHg"
|
"vid": "QmfBs2QTLxhsN5LtpVxW4NUUWp9qEn5o69xXKc8Qd4cCHg",
|
||||||
|
"logo": "QmQPUUg8cHNUmbwChaYV8ejbdoo8kW3G6smRsauUVKectJ"
|
||||||
},
|
},
|
||||||
"583B409B7215159219D08E789DB46140062095F2": {
|
"322DC4C2DF74BF19D8A30D43C489401BE5E76CA5": {
|
||||||
"name": "Virtual Fishing (Japan)"
|
"name": "Hyper Fighting (World) (Beta) (Aftermarket) (Unl) (Pirate)"
|
||||||
},
|
},
|
||||||
"7556A778B60490BDB81774BCBAA7413FC84CB985": {
|
"37E06E53CE2810E870835C48DECDD6C72079BFDA": {
|
||||||
"logo": "QmXiFK6k798oYjYPYpG4zZ3wc6YnhcmrM2Mw4XHNxXyyXr",
|
"name": "Innsmouth no Yakata (Japan)",
|
||||||
"name": "Mario Clash (Japan, USA)",
|
"vid": "QmS7VL589BMEasXVM3tBDwfEjqUpcATcMP4GN4RMauh5yw",
|
||||||
"vid": "QmXMujzzbxGLUNjVeFY9rexf8F2HuVyUR53tM3dLmHw6Up"
|
"logo": "QmV2hr6f1Sf2ZkfmtjYXDPTRjXyQ3qa3XvmxHqmYcUnLMP"
|
||||||
},
|
},
|
||||||
"80216B2874CF162F301D571BB8AEBC98D80B4F3E": {
|
"0E086D7EF2BD8B97315196943FD0B71DA07AA8F1": {
|
||||||
"logo": "Qmf9kBpHUhQwzRV43ChazPudR82N3wyTDh4rZsWj2UAma2",
|
"name": "Jack Bros. (USA)",
|
||||||
"name": "Panic Bomber (USA)",
|
"vid": "QmfYBbFiGsguYMxDoJZQGyzYCcDMhqvLynivFXj9QPgrbp",
|
||||||
"vid": "QmS46otKp9pMfQFz147Qdp7QRq9LF4k1wHKU7nFq9pKc3F"
|
"logo": "QmZPPnoAR8NcAps63MrRsxN3A9kej6ghDunoRg77fF4ZvA"
|
||||||
},
|
|
||||||
"8BB91E681959207BE068796D540120565C174D37": {
|
|
||||||
"ref": "37E06E53CE2810E870835C48DECDD6C72079BFDA"
|
|
||||||
},
|
|
||||||
"93A25ABF4F86E71A49C1C3A470140BB40CB693D6": {
|
|
||||||
"logo": "QmfPLMFW7RHrNQbZKj6kME6fA3p1rQJvvPzCdMYVmkf5Bf",
|
|
||||||
"name": "Galactic Pinball (Japan, USA)",
|
|
||||||
"vid": "QmcZ6zTS3VPzSMRjTxQUkD5zeRssNosfL1BKbPvbHeRzXi"
|
|
||||||
},
|
|
||||||
"A5BE7654037050F0A781E70EFEA0191F43D26F06": {
|
|
||||||
"logo": "QmRMLBV9VMwafg6FEE2EioGGmAZiDvnCwcjk89aPzBbZKc",
|
|
||||||
"name": "Virtual Bowling (Japan)",
|
|
||||||
"vid": "Qmcys2TXepzdxM1pr3TuFybyCSqPtqErTHP8XNqiRzDHiC"
|
|
||||||
},
|
},
|
||||||
"A973406590382EE503037271330D68D2834B79DB": {
|
"A973406590382EE503037271330D68D2834B79DB": {
|
||||||
"name": "Jack Bros. no Meiro de Hiihoo! (Japan)"
|
"name": "Jack Bros. no Meiro de Hiihoo! (Japan)"
|
||||||
},
|
},
|
||||||
"AB8FA82B79512EEFEFCCDCCEA6768078A374C4AA": {
|
"7556A778B60490BDB81774BCBAA7413FC84CB985": {
|
||||||
"name": "Virtual Pro Yakyuu '95 (Japan)"
|
"name": "Mario Clash (Japan, USA)",
|
||||||
|
"vid": "QmXMujzzbxGLUNjVeFY9rexf8F2HuVyUR53tM3dLmHw6Up",
|
||||||
|
"logo": "QmXiFK6k798oYjYPYpG4zZ3wc6YnhcmrM2Mw4XHNxXyyXr"
|
||||||
},
|
},
|
||||||
"B8A12A9677AFA5CBCE6ED43EB0EFFB5A02875534": {
|
"5162F7FA9E3EAE4338C53CCBA641BA080C768754": {
|
||||||
"name": "Tobidase! Panibon (Japan)"
|
"name": "Mario's Tennis (Japan, USA)",
|
||||||
|
"vid": "QmV46x6EkJUYw69Li1KC2yGuDWKcoPVRAvXoUZGfr58Sjq",
|
||||||
|
"logo": "QmeeJjkBUR8vXjvxo8xwixChLpeNShxiWT9wjeaafNu54x"
|
||||||
|
},
|
||||||
|
"F4A4C7928F15102CF14C90C5F044E5F7CC7C32F9": {
|
||||||
|
"name": "Nester's Funky Bowling (USA)",
|
||||||
|
"vid": "Qmd115CebnftLvbWYyiNdBXSgj45tHPqezmqgZF4EaHhkp",
|
||||||
|
"logo": "QmYhPDmeG8cj88j2JMyvtgRqXD6Jj5ee4g55mfRAphVeUS"
|
||||||
|
},
|
||||||
|
"217CA062F65D4E8B2848085918AF5AC4B33B12D4": {
|
||||||
|
"name": "Niko-chan Battle (Japan) (Proto)"
|
||||||
|
},
|
||||||
|
"80216B2874CF162F301D571BB8AEBC98D80B4F3E": {
|
||||||
|
"name": "Panic Bomber (USA)",
|
||||||
|
"vid": "QmS46otKp9pMfQFz147Qdp7QRq9LF4k1wHKU7nFq9pKc3F",
|
||||||
|
"logo": "Qmf9kBpHUhQwzRV43ChazPudR82N3wyTDh4rZsWj2UAma2"
|
||||||
|
},
|
||||||
|
"F5057FA9BFD9D60B9DCFC004CFDD12AA8EB1CB4A": {
|
||||||
|
"name": "Red Alarm (Japan)"
|
||||||
|
},
|
||||||
|
"494FA60CB8997880B4E8CD3270B21D6C2768A48A": {
|
||||||
|
"name": "Red Alarm (USA)",
|
||||||
|
"vid": "QmRidMNd9q92SawBCir2rW6EJsjm7SRjUpgkXmUTUCBGCV",
|
||||||
|
"logo": "Qmcz6tW9nEPT5Vp4KYvBw5nspXfo6M5e2WtSuKoThEsZiE"
|
||||||
|
},
|
||||||
|
"1A67F6BDB45DB46E22B8AADEA754A68AD379AE29": {
|
||||||
|
"name": "SD Gundam - Dimension War (Japan)",
|
||||||
|
"vid": "QmR2dDWQ3GrT842baDxNQHP6N6wmfheZGZ2fqYWHRK2yYF",
|
||||||
|
"logo": "QmPoFytQpiAoJ9nW4ZrjjJMVdAcZyME7SRRVDSnK5dMkPc"
|
||||||
|
},
|
||||||
|
"16AD904FAAFD24642DFD056D10A768E81F0D9BFA": {
|
||||||
|
"name": "Space Invaders - Virtual Collection (Japan)",
|
||||||
|
"vid": "QmUe2GCnx99s9DZxaQtHmtNrzRJ7YvHGi1NuqiAtDf46cj",
|
||||||
|
"logo": "QmUpBKPjXguAkS69F1ZQRoX5D3kRznYoEVYF8bFqLpBA1R"
|
||||||
|
},
|
||||||
|
"DC182360C7C8D26323DB8921F09DB76638E81CED": {
|
||||||
|
"name": "Space Pinball (Japan) (En) (Proto)"
|
||||||
|
},
|
||||||
|
"3F08CAE2B6F9E386529949FF954220860DB1AF64": {
|
||||||
|
"name": "Space Squash (Japan)"
|
||||||
},
|
},
|
||||||
"C595285D42C69F14B2B418C1EDFBE4A7F9A624B6": {
|
"C595285D42C69F14B2B418C1EDFBE4A7F9A624B6": {
|
||||||
"name": "T&E Virtual Golf (Japan)"
|
"name": "T&E Virtual Golf (Japan)"
|
||||||
},
|
},
|
||||||
"C59E020F9674774C5CBC022317EBBA0EB1D744F7": {
|
"C59E020F9674774C5CBC022317EBBA0EB1D744F7": {
|
||||||
"logo": "QmfUGi2NxBeuL2zQJfKsXxxVgYKZDkxMS4uyunb5Ssbtdg",
|
|
||||||
"name": "Teleroboxer (Japan, USA)",
|
"name": "Teleroboxer (Japan, USA)",
|
||||||
"vid": "QmYyjbruy62ZupgBpJswyj6M41FhL6PWpx88P5TjYcWga1"
|
"vid": "QmYyjbruy62ZupgBpJswyj6M41FhL6PWpx88P5TjYcWga1",
|
||||||
|
"logo": "QmfUGi2NxBeuL2zQJfKsXxxVgYKZDkxMS4uyunb5Ssbtdg"
|
||||||
},
|
},
|
||||||
"C7204BA5CFE7D26394B5E22BADC580C8ED8C0B37": {
|
"B8A12A9677AFA5CBCE6ED43EB0EFFB5A02875534": {
|
||||||
"name": "Vertical Force (Japan)",
|
"name": "Tobidase! Panibon (Japan)"
|
||||||
"ref": "38F9008F60B09DEEF1151D46B905B090A0338200"
|
|
||||||
},
|
|
||||||
"D96C9F8AAC5B4EA012A8FE659BB68FB1159A9C6D": {
|
|
||||||
"logo": "QmZ57YMihbyobJp947F4gKUivX5EnGcpmSn1gQiCLwSn5y",
|
|
||||||
"name": "Virtual Lab (Japan)",
|
|
||||||
"vid": "QmTJM6eCADPf7zacHNzchYeQ9SRaAepeqX8NmrtUX1PSAf"
|
|
||||||
},
|
|
||||||
"DC182360C7C8D26323DB8921F09DB76638E81CED": {
|
|
||||||
"name": "Space Pinball (Japan) (En) (Proto)"
|
|
||||||
},
|
|
||||||
"DCC46484BD0ACAB0AC1EA178F425A0F5CCFB8DC2": {
|
|
||||||
"logo": "QmdU2xf9SKUxijX6tWzKrGx8nTNhmPh9V1EhNkQFYXYfwJ",
|
|
||||||
"name": "Waterworld (USA)",
|
|
||||||
"vid": "QmXksqwSJtyVEWQScPRbiEcsDw1vZgrwjD1sbPzPzZHCmh"
|
|
||||||
},
|
|
||||||
"F4A4C7928F15102CF14C90C5F044E5F7CC7C32F9": {
|
|
||||||
"logo": "QmYhPDmeG8cj88j2JMyvtgRqXD6Jj5ee4g55mfRAphVeUS",
|
|
||||||
"name": "Nester's Funky Bowling (USA)",
|
|
||||||
"vid": "Qmd115CebnftLvbWYyiNdBXSgj45tHPqezmqgZF4EaHhkp"
|
|
||||||
},
|
|
||||||
"F5057FA9BFD9D60B9DCFC004CFDD12AA8EB1CB4A": {
|
|
||||||
"name": "Red Alarm (Japan)",
|
|
||||||
"ref": "494FA60CB8997880B4E8CD3270B21D6C2768A48A"
|
|
||||||
},
|
},
|
||||||
"FF7D4DFC467E6E0D3FE8517102132A65A9D17589": {
|
"FF7D4DFC467E6E0D3FE8517102132A65A9D17589": {
|
||||||
"logo": "QmcBz5NZZN6UuKVDvFMjnxFfvfsCjxZvWXFjMLKJunMXqg",
|
|
||||||
"name": "V-Tetris (Japan)",
|
"name": "V-Tetris (Japan)",
|
||||||
"vid": "QmZEKGFQkgeQzPkuShnx456pqcf94oqus3Fv2SyHzKMjiq"
|
"vid": "QmZEKGFQkgeQzPkuShnx456pqcf94oqus3Fv2SyHzKMjiq",
|
||||||
|
"logo": "QmcBz5NZZN6UuKVDvFMjnxFfvfsCjxZvWXFjMLKJunMXqg"
|
||||||
|
},
|
||||||
|
"38F9008F60B09DEEF1151D46B905B090A0338200": {
|
||||||
|
"name": "Vertical Force (USA)",
|
||||||
|
"vid": "QmPpDpBSRP8UZidJLsJiRaWoFXY1M5WKnU2ZApzSzshwJB",
|
||||||
|
"logo": "QmSYrvARB5C2QFF1MjTjGnrr3Vwh42iXXd9uXFKbYHF1tm"
|
||||||
|
},
|
||||||
|
"C7204BA5CFE7D26394B5E22BADC580C8ED8C0B37": {
|
||||||
|
"name": "Vertical Force (Japan)"
|
||||||
|
},
|
||||||
|
"A5BE7654037050F0A781E70EFEA0191F43D26F06": {
|
||||||
|
"name": "Virtual Bowling (Japan)",
|
||||||
|
"vid": "Qmcys2TXepzdxM1pr3TuFybyCSqPtqErTHP8XNqiRzDHiC",
|
||||||
|
"logo": "QmRMLBV9VMwafg6FEE2EioGGmAZiDvnCwcjk89aPzBbZKc"
|
||||||
|
},
|
||||||
|
"274C328FBD904F20E69172AB826BF8F94CED1BDB": {
|
||||||
|
"name": "Virtual Boy Wario Land (Japan, USA)",
|
||||||
|
"vid": "QmYxxf3mjbPhA8JKkn8yZh73LC1UHCdWrtviMvjTg8VqRe",
|
||||||
|
"logo": "QmNvnaDejYEdsowxN8KZaj6qdj4vqVv8btBy4pSxdyJHNW"
|
||||||
|
},
|
||||||
|
"583B409B7215159219D08E789DB46140062095F2": {
|
||||||
|
"name": "Virtual Fishing (Japan)"
|
||||||
|
},
|
||||||
|
"D96C9F8AAC5B4EA012A8FE659BB68FB1159A9C6D": {
|
||||||
|
"name": "Virtual Lab (Japan)",
|
||||||
|
"vid": "QmTJM6eCADPf7zacHNzchYeQ9SRaAepeqX8NmrtUX1PSAf",
|
||||||
|
"logo": "QmZ57YMihbyobJp947F4gKUivX5EnGcpmSn1gQiCLwSn5y"
|
||||||
|
},
|
||||||
|
"266FE615EE3DF1160A20B456824699F16810FA28": {
|
||||||
|
"name": "Virtual League Baseball (USA)",
|
||||||
|
"vid": "QmZXfBoZfHppqtL4XwrPLALomir7ZsdQ7GehvkB6y2TwRP",
|
||||||
|
"logo": "QmXNUHwFo4822jWfXpwutWJTsdLu5JasjiAT1aPNkvqT56"
|
||||||
|
},
|
||||||
|
"AB8FA82B79512EEFEFCCDCCEA6768078A374C4AA": {
|
||||||
|
"name": "Virtual Pro Yakyuu '95 (Japan)"
|
||||||
|
},
|
||||||
|
"DCC46484BD0ACAB0AC1EA178F425A0F5CCFB8DC2": {
|
||||||
|
"name": "Waterworld (USA)",
|
||||||
|
"vid": "QmXksqwSJtyVEWQScPRbiEcsDw1vZgrwjD1sbPzPzZHCmh",
|
||||||
|
"logo": "QmdU2xf9SKUxijX6tWzKrGx8nTNhmPh9V1EhNkQFYXYfwJ"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,209 +1,206 @@
|
|||||||
{
|
{
|
||||||
"0406C13952FB0BD6ADC68298AE163EBC6160484C": {
|
"65D07426B520DDD3115D40F255511E0FD2E20AE7": {
|
||||||
"logo": "QmPM6vguoNJ2jf7ZDVLiv9ZPbTxSP7SGSgu5c5ZutkGW4L",
|
"name": "[BIOS] GCE Vectrex (World)"
|
||||||
"name": "Heads-Up - Action Soccer (USA)",
|
|
||||||
"vid": "QmSeDTnrAZsZdS9X3EZGycQiWHmnCyYFhmsrbobu2oVUdt"
|
|
||||||
},
|
|
||||||
"05F56E708F752CA801F8013C7DA9B0DB01508BA5": {
|
|
||||||
"name": "Mail Plane (USA) (Proto) (DA1AC0DB)"
|
|
||||||
},
|
|
||||||
"08E2DF9F7769C16A4D730ADF8D659F8EA4BE7F9D": {
|
|
||||||
"name": "WebWars (USA)"
|
|
||||||
},
|
|
||||||
"0C229DD12A55044681585F7FDABD37DBC1243B59": {
|
|
||||||
"logo": "QmWy88c5QpxqpFXa36Z5hSRjRuZ5tTJsM61ypyCdePaKK2",
|
|
||||||
"name": "Space Wars (World)",
|
|
||||||
"vid": "QmTFQHny9TGVGTLf4iMqjp92izzxLVB7AEefntitV6gQxr"
|
|
||||||
},
|
|
||||||
"1731E892DA8945FE4945B43847D23960C581682A": {
|
|
||||||
"name": "3D Mine Storm (USA)"
|
|
||||||
},
|
|
||||||
"1FDCC6E54AE5177BC9CDC79CE616AE3401E5C229": {
|
|
||||||
"name": "Mine Storm II (USA) (Rev 2)"
|
|
||||||
},
|
|
||||||
"22F7A490F6F7AAA31E892B03A85178AF211F62E3": {
|
|
||||||
"logo": "QmdH43xxRiRcH1QH1eK54F6VPsMru9VFZCjyetGqhRnS2m",
|
|
||||||
"name": "Rip Off (World)",
|
|
||||||
"vid": "QmUkKdrEFZ6pNQXqMjvDS1xav4CZoU8NQWZCeLcSzjQoLB"
|
|
||||||
},
|
|
||||||
"286E061331289B031DE33725A4966C379C1DF47F": {
|
|
||||||
"logo": "QmaSxgcqfeZz3KvgTiK9uWqm6zdw25i8Zw6khLS35FzzNd",
|
|
||||||
"name": "Fortress of Narzod (USA, Europe)",
|
|
||||||
"vid": "QmQd5CNoL4FNqqr4Euw9kK6mNkzibGmTXZ8YeAocxMLTsX"
|
|
||||||
},
|
|
||||||
"299841A116E59399AF4E74CCAE23DDFC078E007B": {
|
|
||||||
"name": "Mr. Boston Clean Sweep (Europe)"
|
|
||||||
},
|
|
||||||
"2E8285061530CCADFA2D8D11C374AE244449EBFA": {
|
|
||||||
"logo": "QmRtXFBb1LxYizttvoTquUdnxxmMbBpDgCJbPNF1LimAXy",
|
|
||||||
"name": "Spin ball (USA)",
|
|
||||||
"vid": "QmdbR1gfqid1H4iSUDtLbUWL6rRFq8aW39k1DHULcAP4C6"
|
|
||||||
},
|
|
||||||
"38072DC491D5C729C06F4C50BC310D361E0FF62A": {
|
|
||||||
"logo": "QmRbeyGtPXFNa8Yd7pvgSHCWHQhimdSbzUcajSjuFkpoRM",
|
|
||||||
"name": "Solar Quest (World)",
|
|
||||||
"vid": "QmeafNyYnAWfgdavXZbdXopzgvsYjzPnhktM52ko1gUmq9"
|
|
||||||
},
|
|
||||||
"38E38B5C60466146D4648F8929B5CE3A08DCBE0D": {
|
|
||||||
"logo": "QmaXvh9s38tXrrdZpLJcisAdCmGKJZsAqs23uRrcGa4ZMw",
|
|
||||||
"name": "Scramble (USA, Europe)",
|
|
||||||
"vid": "QmQ7urxD1C6p4WzXPABtLbkokCNFQ9RZmeTXdvofRQbkA8"
|
|
||||||
},
|
|
||||||
"3A2EE61316FB316D9E9EFDF5EE405E813475B466": {
|
|
||||||
"logo": "QmVtydkMEKK3MNFJM9aVcppUPLSVtZ3Nrq7JM2NrDvBSYj",
|
|
||||||
"name": "Star Trek - The Motion Picture (USA)",
|
|
||||||
"vid": "QmX3x5Ftz7N9XEn48EdwdcXUKcankDLULGMFdhoEKSuCEA"
|
|
||||||
},
|
|
||||||
"3FF3FA4FEDA6D562DEDFF2A10CFA8679D8318044": {
|
|
||||||
"logo": "QmNNDtCUNmUzDH2TYkFCTojijTaUgfRVNd72XpZ1exV2fx",
|
|
||||||
"name": "Bedlam (USA, Europe)",
|
|
||||||
"vid": "QmXQbibFnpksG3DqupoXs9gBzexvwC3gW1zdcYrX9oSwa9"
|
|
||||||
},
|
},
|
||||||
"4A833E4869221F832DACB7EF0FB2997C870B7D7F": {
|
"4A833E4869221F832DACB7EF0FB2997C870B7D7F": {
|
||||||
"name": "3D Crazy Coaster (USA)"
|
"name": "3D Crazy Coaster (USA)"
|
||||||
},
|
},
|
||||||
"4B1E43808AEFC8235B130D2DBAC41FA087D9AEDF": {
|
"1731E892DA8945FE4945B43847D23960C581682A": {
|
||||||
"logo": "QmcwcZax6zKfh1hELpA7v7vWSrRTA6P4GcbvGNWXDY2bVi",
|
"name": "3D Mine Storm (USA)"
|
||||||
"name": "Vectrex Pong (1998) (PD)",
|
|
||||||
"vid": "QmPPCkKNMtttNQx7rc6c773PvHz8CdrvKtPFbsoMYsDvph"
|
|
||||||
},
|
},
|
||||||
"4F461B0B51C82433E70130EAC5494F26239B4453": {
|
"9E5D23CB7EF111394E1804E105B6DD277C062DF6": {
|
||||||
"logo": "QmTxYL5HLGHDJqqUAEhWZpEMAYhdAYg4CwebGy2cddJoxD",
|
"name": "3D Narrow Escape (USA)"
|
||||||
"name": "Tour De France (USA) (Proto)",
|
|
||||||
"vid": "QmXQ2KdKwYS7b4zAfgWQXxUo6jhQPKjDfYrmNzS2BtL72B"
|
|
||||||
},
|
},
|
||||||
"526B347BC02A47C8BDA326B1F6DA50284127F038": {
|
"71946BE47DEBAE89AD191AC382C2015D6A862072": {
|
||||||
"name": "Sledge 3D (USA) (Sample)"
|
"name": "AnimAction - Advanced Animation (USA)"
|
||||||
|
},
|
||||||
|
"EB76F860130F8CC87826FA931350467760511213": {
|
||||||
|
"name": "Armor..Attack (World)",
|
||||||
|
"vid": "QmNjJ6K9gL5U3N4atFXs3FBeuaZNJKXuPMcNkU2ALVfcC8",
|
||||||
|
"logo": "QmTaFSMSwyHEmbsJiQz5Fopa3pJJJFoGViVARw5fhDH3db"
|
||||||
|
},
|
||||||
|
"8F35BAC5E8E0FC4AD7291BBFE5AE6E693272DE06": {
|
||||||
|
"name": "Art Master (USA)"
|
||||||
|
},
|
||||||
|
"3FF3FA4FEDA6D562DEDFF2A10CFA8679D8318044": {
|
||||||
|
"name": "Bedlam (USA, Europe)",
|
||||||
|
"vid": "QmXQbibFnpksG3DqupoXs9gBzexvwC3gW1zdcYrX9oSwa9",
|
||||||
|
"logo": "QmNNDtCUNmUzDH2TYkFCTojijTaUgfRVNd72XpZ1exV2fx"
|
||||||
|
},
|
||||||
|
"E4BADD494ED548721328EF869D33AD70E3E522C9": {
|
||||||
|
"name": "Berzerk (World)",
|
||||||
|
"vid": "QmTBGNBX64jyNVhW3QmJyXWzrXMayNunhg7cZ5ixYCi5Ew",
|
||||||
|
"logo": "Qmb3D82JxKqf7KFHXGSbTR5cBojXbZYUjwRqhpV8NPUPwn"
|
||||||
|
},
|
||||||
|
"AA1AA811C9CE5E19F43B78A6531CDD0E707337BD": {
|
||||||
|
"name": "Blitz! - Action Football (USA, Europe) (881B27C5)"
|
||||||
|
},
|
||||||
|
"60893DFADA42CF50C94083D45AD5BD14D0C031C8": {
|
||||||
|
"name": "Blitz! - Action Football (USA, Europe) (0F11CE0C)",
|
||||||
|
"vid": "QmebzT27GkPGZeMzQk4Go4MdDV8LpMZAhMgJVPASTtGejB",
|
||||||
|
"logo": "QmTnugssuLSRDNiRLjcV8bFLfpixsbZW8nhpfPpMHQtZmA"
|
||||||
|
},
|
||||||
|
"67F8513958C04E936B135740ED4EC6E6FA1763D5": {
|
||||||
|
"name": "Clean Sweep (World)",
|
||||||
|
"vid": "QmTvzqndwrtA8onrSazzqZ9hqpzc32z2G4UFA3BEagLmvK",
|
||||||
|
"logo": "QmYRFHuN1Pr6VZ1VyTSNzoCBzXSXUWNhWbsrrsTYWuGFDX"
|
||||||
},
|
},
|
||||||
"56A7F277BF09F94461DDA542C04C36266D838B86": {
|
"56A7F277BF09F94461DDA542C04C36266D838B86": {
|
||||||
"logo": "QmUzBST6yYnmLp7eTjhARck67Sa2MdH4Pzxnr8LLWBK1wG",
|
|
||||||
"name": "Cosmic Chasm (World)",
|
"name": "Cosmic Chasm (World)",
|
||||||
"vid": "QmUju26mxReyJZrjkbNWPwyeWLeKcZVGSdsvaqu49L4hVj"
|
"vid": "QmUju26mxReyJZrjkbNWPwyeWLeKcZVGSdsvaqu49L4hVj",
|
||||||
|
"logo": "QmUzBST6yYnmLp7eTjhARck67Sa2MdH4Pzxnr8LLWBK1wG"
|
||||||
},
|
},
|
||||||
"56CEA447704286D88533DB99FD209816AE1B96A4": {
|
"DBB0B378AACC2A18DFB476AA99AB00D6AB395205": {
|
||||||
"logo": "Qmf5keLWEGScQ3q8WV7BC5yYmfVLMfbHz6odtUWGQNVQfL",
|
"name": "Dark Tower (USA) (Proto)",
|
||||||
"name": "Star Wars Theme by Chris Salomon (1998) (PD)",
|
"vid": "QmdgHceUf86DcvZYmZTWhMZVj6WMWELC8ugFB6DgE3U4T5",
|
||||||
"vid": "QmPSj39boR1gkrVfKh38iEKx6GpKwNTS851oCbYeFXCrQh"
|
"logo": "QmdQZhuLH5nzGTSX9rw6Co6dsTMySmurQ2kWXSesmCvfYw"
|
||||||
|
},
|
||||||
|
"C226D78301DBE40F2194C165F2124D5AFA885BE4": {
|
||||||
|
"name": "Engine Analyzer (USA) (Proto)"
|
||||||
|
},
|
||||||
|
"286E061331289B031DE33725A4966C379C1DF47F": {
|
||||||
|
"name": "Fortress of Narzod (USA, Europe)",
|
||||||
|
"vid": "QmQd5CNoL4FNqqr4Euw9kK6mNkzibGmTXZ8YeAocxMLTsX",
|
||||||
|
"logo": "QmaSxgcqfeZz3KvgTiK9uWqm6zdw25i8Zw6khLS35FzzNd"
|
||||||
|
},
|
||||||
|
"0406C13952FB0BD6ADC68298AE163EBC6160484C": {
|
||||||
|
"name": "Heads-Up - Action Soccer (USA)",
|
||||||
|
"vid": "QmSeDTnrAZsZdS9X3EZGycQiWHmnCyYFhmsrbobu2oVUdt",
|
||||||
|
"logo": "QmPM6vguoNJ2jf7ZDVLiv9ZPbTxSP7SGSgu5c5ZutkGW4L"
|
||||||
|
},
|
||||||
|
"A246F1D94A5B062B60784787A1D6D499B21C10CD": {
|
||||||
|
"name": "HyperChase - Auto Race (World)",
|
||||||
|
"vid": "Qmafws3VndL7PSxxzZLx6KpLL2Nf8NxZU2NZZPDgNqnxBj",
|
||||||
|
"logo": "QmXfyfqTdJMRN5j5heiiqkiicQFyppQFPpPhNR7mEZcxLn"
|
||||||
|
},
|
||||||
|
"05F56E708F752CA801F8013C7DA9B0DB01508BA5": {
|
||||||
|
"name": "Mail Plane (USA) (Proto) (DA1AC0DB)"
|
||||||
|
},
|
||||||
|
"969D5EC7D7ABA9AC25E54069149D9586217AB63D": {
|
||||||
|
"name": "Mail Plane (USA) (Proto) (05838962)"
|
||||||
},
|
},
|
||||||
"58AECC7FB1D26A22B09E9A13D845E4ED12E2449D": {
|
"58AECC7FB1D26A22B09E9A13D845E4ED12E2449D": {
|
||||||
"name": "Melody Master - Music Composition and Entertainment (USA)"
|
"name": "Melody Master - Music Composition and Entertainment (USA)"
|
||||||
},
|
},
|
||||||
"5F00ED35F189357B4A16A190520166371CA57906": {
|
"5F00ED35F189357B4A16A190520166371CA57906": {
|
||||||
"logo": "QmX4wEb2TkewvTSAz6L6Ezdwx8SJeWPw1j3Npmjwm9AFYR",
|
|
||||||
"name": "Mine Storm (World)",
|
"name": "Mine Storm (World)",
|
||||||
"vid": "QmQfbMcJPPjnd1jihNsXxdvyWRqvBhhWFYH9yui8A2Sohf"
|
"vid": "QmQfbMcJPPjnd1jihNsXxdvyWRqvBhhWFYH9yui8A2Sohf",
|
||||||
|
"logo": "QmX4wEb2TkewvTSAz6L6Ezdwx8SJeWPw1j3Npmjwm9AFYR"
|
||||||
},
|
},
|
||||||
"60893DFADA42CF50C94083D45AD5BD14D0C031C8": {
|
"1FDCC6E54AE5177BC9CDC79CE616AE3401E5C229": {
|
||||||
"logo": "QmTnugssuLSRDNiRLjcV8bFLfpixsbZW8nhpfPpMHQtZmA",
|
"name": "Mine Storm II (USA) (Rev 2)"
|
||||||
"name": "Blitz! - Action Football (USA, Europe) (0F11CE0C)",
|
|
||||||
"vid": "QmebzT27GkPGZeMzQk4Go4MdDV8LpMZAhMgJVPASTtGejB"
|
|
||||||
},
|
},
|
||||||
"65D07426B520DDD3115D40F255511E0FD2E20AE7": {
|
"299841A116E59399AF4E74CCAE23DDFC078E007B": {
|
||||||
"name": "[BIOS] GCE Vectrex (World)"
|
"name": "Mr. Boston Clean Sweep (Europe)"
|
||||||
},
|
},
|
||||||
"67F8513958C04E936B135740ED4EC6E6FA1763D5": {
|
"F6F26BEB20BB364761A9466FF4087C7B9713D048": {
|
||||||
"logo": "QmYRFHuN1Pr6VZ1VyTSNzoCBzXSXUWNhWbsrrsTYWuGFDX",
|
"name": "Pitcher's Duel (USA) (Proto)"
|
||||||
"name": "Clean Sweep (World)",
|
|
||||||
"vid": "QmTvzqndwrtA8onrSazzqZ9hqpzc32z2G4UFA3BEagLmvK"
|
|
||||||
},
|
},
|
||||||
"71946BE47DEBAE89AD191AC382C2015D6A862072": {
|
"87248DCD3DB51C1EBF3F8E07E21EC021EEC0B9BE": {
|
||||||
"name": "AnimAction - Advanced Animation (USA)"
|
"name": "Polar Rescue (USA)",
|
||||||
|
"vid": "QmNmQyQSUy4kf2nbsqbQ9zTWw5VLSNCXDK3GnL8WhvNJVj",
|
||||||
|
"logo": "QmRVhA3yZJbSdkD1evpnpKHutDxdwAtEpWGJwF4fDCV3JQ"
|
||||||
},
|
},
|
||||||
"745DDBDC285F1DA4F7C3EB5E44FE252D503C7A15": {
|
"B7BD8C8003C60535E1E776758F5C289AA9A77E81": {
|
||||||
"name": "Wormhole by John Dondzila (2001) (PD)",
|
"name": "Polar Rescue (USA) (Beta)"
|
||||||
"vid": "Qmd6pfnvEjTddPaZQddxNNiA3rkHQVshbtZekKqfmfTdBg"
|
},
|
||||||
|
"9F5B0B5E6CAF6D887D3637ACE70AD6A6258B999D": {
|
||||||
|
"name": "Pole Position (USA) (C10F37D8)"
|
||||||
|
},
|
||||||
|
"8454D472435E7A6A4CA5FE129162480CA6E84B87": {
|
||||||
|
"name": "Pole Position (USA) (A00ED3D6)",
|
||||||
|
"vid": "QmUSws3tEN8qcFUCcS7vegLDs4gRwsudDcD8PAE94vHkeu",
|
||||||
|
"logo": "QmdKNUFPsB3qUDzNniq1Z5UNgCd6NkeucAdr2y7H2FmnJF"
|
||||||
},
|
},
|
||||||
"7CE85DB8DD32687AD7629631AE113820371FAF7C": {
|
"7CE85DB8DD32687AD7629631AE113820371FAF7C": {
|
||||||
"name": "RA+A Spectrum I+ Stress Tester (USA) (Proto)"
|
"name": "RA+A Spectrum I+ Stress Tester (USA) (Proto)"
|
||||||
},
|
},
|
||||||
"83A6269A9275D8CE48C58B5560FB2D7324EAEEA0": {
|
"22F7A490F6F7AAA31E892B03A85178AF211F62E3": {
|
||||||
"logo": "QmahNM3mUSdGTSHWu146L7hf5HZsYvMPRUJyQn1w7pJ2zr",
|
"name": "Rip Off (World)",
|
||||||
"name": "Vector Vaders by John Dondzila (1996)",
|
"vid": "QmUkKdrEFZ6pNQXqMjvDS1xav4CZoU8NQWZCeLcSzjQoLB",
|
||||||
"vid": "QmSyQ7zP7FZEPLHQvHdUWe5YDy73NFhrpHG1vtZL8zxzjh"
|
"logo": "QmdH43xxRiRcH1QH1eK54F6VPsMru9VFZCjyetGqhRnS2m"
|
||||||
},
|
},
|
||||||
"8454D472435E7A6A4CA5FE129162480CA6E84B87": {
|
"38E38B5C60466146D4648F8929B5CE3A08DCBE0D": {
|
||||||
"logo": "QmdKNUFPsB3qUDzNniq1Z5UNgCd6NkeucAdr2y7H2FmnJF",
|
"name": "Scramble (USA, Europe)",
|
||||||
"name": "Pole Position (USA) (A00ED3D6)",
|
"vid": "QmQ7urxD1C6p4WzXPABtLbkokCNFQ9RZmeTXdvofRQbkA8",
|
||||||
"vid": "QmUSws3tEN8qcFUCcS7vegLDs4gRwsudDcD8PAE94vHkeu"
|
"logo": "QmaXvh9s38tXrrdZpLJcisAdCmGKJZsAqs23uRrcGa4ZMw"
|
||||||
},
|
},
|
||||||
"87248DCD3DB51C1EBF3F8E07E21EC021EEC0B9BE": {
|
"526B347BC02A47C8BDA326B1F6DA50284127F038": {
|
||||||
"logo": "QmRVhA3yZJbSdkD1evpnpKHutDxdwAtEpWGJwF4fDCV3JQ",
|
"name": "Sledge 3D (USA) (Sample)"
|
||||||
"name": "Polar Rescue (USA)",
|
},
|
||||||
"vid": "QmNmQyQSUy4kf2nbsqbQ9zTWw5VLSNCXDK3GnL8WhvNJVj"
|
"38072DC491D5C729C06F4C50BC310D361E0FF62A": {
|
||||||
|
"name": "Solar Quest (World)",
|
||||||
|
"vid": "QmeafNyYnAWfgdavXZbdXopzgvsYjzPnhktM52ko1gUmq9",
|
||||||
|
"logo": "QmRbeyGtPXFNa8Yd7pvgSHCWHQhimdSbzUcajSjuFkpoRM"
|
||||||
|
},
|
||||||
|
"0C229DD12A55044681585F7FDABD37DBC1243B59": {
|
||||||
|
"name": "Space Wars (World)",
|
||||||
|
"vid": "QmTFQHny9TGVGTLf4iMqjp92izzxLVB7AEefntitV6gQxr",
|
||||||
|
"logo": "QmWy88c5QpxqpFXa36Z5hSRjRuZ5tTJsM61ypyCdePaKK2"
|
||||||
},
|
},
|
||||||
"8BBA6705E6F69F55AB6D105EAC2FD5B0C0D16D9F": {
|
"8BBA6705E6F69F55AB6D105EAC2FD5B0C0D16D9F": {
|
||||||
"logo": "QmNhbeQnhDomVvUjN4g98dNQgnpGfnHjdxhnZH2vy11WtB",
|
|
||||||
"name": "Spike (USA, Europe)",
|
"name": "Spike (USA, Europe)",
|
||||||
"vid": "QmNP7rHCqhNAPNYAhYoSke21dEGJdmY4HdX6CgD1wX2ScZ"
|
"vid": "QmNP7rHCqhNAPNYAhYoSke21dEGJdmY4HdX6CgD1wX2ScZ",
|
||||||
|
"logo": "QmNhbeQnhDomVvUjN4g98dNQgnpGfnHjdxhnZH2vy11WtB"
|
||||||
},
|
},
|
||||||
"8F35BAC5E8E0FC4AD7291BBFE5AE6E693272DE06": {
|
"2E8285061530CCADFA2D8D11C374AE244449EBFA": {
|
||||||
"name": "Art Master (USA)"
|
"name": "Spin ball (USA)",
|
||||||
},
|
"vid": "QmdbR1gfqid1H4iSUDtLbUWL6rRFq8aW39k1DHULcAP4C6",
|
||||||
"9681DC5D38A31CB0D0C6C3BD0FC76BFDDE531CCE": {
|
"logo": "QmRtXFBb1LxYizttvoTquUdnxxmMbBpDgCJbPNF1LimAXy"
|
||||||
"logo": "QmQmenQKUtDTuzGcrXCFWQ5mddcJGnXfCdk2g2KvSikT3A",
|
|
||||||
"name": "WebWarp (Europe)",
|
|
||||||
"vid": "QmZ6wtHLWez5EWXpK9DLKW4Bbwn2MHAJqmzNneb93S97kn"
|
|
||||||
},
|
|
||||||
"969D5EC7D7ABA9AC25E54069149D9586217AB63D": {
|
|
||||||
"name": "Mail Plane (USA) (Proto) (05838962)"
|
|
||||||
},
|
|
||||||
"9E5D23CB7EF111394E1804E105B6DD277C062DF6": {
|
|
||||||
"name": "3D Narrow Escape (USA)"
|
|
||||||
},
|
|
||||||
"9F5B0B5E6CAF6D887D3637ACE70AD6A6258B999D": {
|
|
||||||
"name": "Pole Position (USA) (C10F37D8)",
|
|
||||||
"ref": "8454D472435E7A6A4CA5FE129162480CA6E84B87"
|
|
||||||
},
|
|
||||||
"A246F1D94A5B062B60784787A1D6D499B21C10CD": {
|
|
||||||
"logo": "QmXfyfqTdJMRN5j5heiiqkiicQFyppQFPpPhNR7mEZcxLn",
|
|
||||||
"name": "HyperChase - Auto Race (World)",
|
|
||||||
"vid": "Qmafws3VndL7PSxxzZLx6KpLL2Nf8NxZU2NZZPDgNqnxBj"
|
|
||||||
},
|
|
||||||
"AA1AA811C9CE5E19F43B78A6531CDD0E707337BD": {
|
|
||||||
"name": "Blitz! - Action Football (USA, Europe) (881B27C5)",
|
|
||||||
"ref": "60893DFADA42CF50C94083D45AD5BD14D0C031C8"
|
|
||||||
},
|
|
||||||
"B7BD8C8003C60535E1E776758F5C289AA9A77E81": {
|
|
||||||
"name": "Polar Rescue (USA) (Beta)",
|
|
||||||
"ref": "87248DCD3DB51C1EBF3F8E07E21EC021EEC0B9BE"
|
|
||||||
},
|
|
||||||
"C226D78301DBE40F2194C165F2124D5AFA885BE4": {
|
|
||||||
"name": "Engine Analyzer (USA) (Proto)"
|
|
||||||
},
|
},
|
||||||
"C9B79F1BA30DCBCE5A068B606A97775CF863FE0A": {
|
"C9B79F1BA30DCBCE5A068B606A97775CF863FE0A": {
|
||||||
"logo": "QmZTwQkihBa2VeUgHKGVAJnQ2o1x8cL7sLqtWR8AizhWBL",
|
|
||||||
"name": "Star Castle (USA)",
|
"name": "Star Castle (USA)",
|
||||||
"vid": "QmUFK8JWdwMnToSanY6mKcPPCURXepY71ug4dkSgeWFkip"
|
"vid": "QmUFK8JWdwMnToSanY6mKcPPCURXepY71ug4dkSgeWFkip",
|
||||||
|
"logo": "QmZTwQkihBa2VeUgHKGVAJnQ2o1x8cL7sLqtWR8AizhWBL"
|
||||||
},
|
},
|
||||||
"D182EAC5A54FFC642FCBE4A495C7C09F87B43585": {
|
"D182EAC5A54FFC642FCBE4A495C7C09F87B43585": {
|
||||||
"logo": "QmTnYiP8VBd3tXtJKhRpM1d3fmeeUBDTB1H3NTmav7hwNN",
|
|
||||||
"name": "Star Ship (Europe)",
|
"name": "Star Ship (Europe)",
|
||||||
"vid": "QmfA9z3dvG6HRtyzKgChGSLvqVFMGFGSzREwbKE9JuDim2"
|
"vid": "QmfA9z3dvG6HRtyzKgChGSLvqVFMGFGSzREwbKE9JuDim2",
|
||||||
|
"logo": "QmTnYiP8VBd3tXtJKhRpM1d3fmeeUBDTB1H3NTmav7hwNN"
|
||||||
|
},
|
||||||
|
"3A2EE61316FB316D9E9EFDF5EE405E813475B466": {
|
||||||
|
"name": "Star Trek - The Motion Picture (USA)",
|
||||||
|
"vid": "QmX3x5Ftz7N9XEn48EdwdcXUKcankDLULGMFdhoEKSuCEA",
|
||||||
|
"logo": "QmVtydkMEKK3MNFJM9aVcppUPLSVtZ3Nrq7JM2NrDvBSYj"
|
||||||
},
|
},
|
||||||
"D6B388B3951E34971C1B00C7FDF8DD00AD52DF09": {
|
"D6B388B3951E34971C1B00C7FDF8DD00AD52DF09": {
|
||||||
"logo": "QmZYtvLf6opgdsBMUUic88kGmdjz1iSE5prHwQvjDVYEa5",
|
|
||||||
"name": "StarHawk (World)",
|
"name": "StarHawk (World)",
|
||||||
"vid": "QmTkv8Lsq78Ht3TKP89Jjor3nR4MhT245MMDtxa3jGsBmb"
|
"vid": "QmTkv8Lsq78Ht3TKP89Jjor3nR4MhT245MMDtxa3jGsBmb",
|
||||||
},
|
"logo": "QmZYtvLf6opgdsBMUUic88kGmdjz1iSE5prHwQvjDVYEa5"
|
||||||
"DBB0B378AACC2A18DFB476AA99AB00D6AB395205": {
|
|
||||||
"logo": "QmdQZhuLH5nzGTSX9rw6Co6dsTMySmurQ2kWXSesmCvfYw",
|
|
||||||
"name": "Dark Tower (USA) (Proto)",
|
|
||||||
"vid": "QmdgHceUf86DcvZYmZTWhMZVj6WMWELC8ugFB6DgE3U4T5"
|
|
||||||
},
|
},
|
||||||
"E3418055F1E79A2300572C557E71BF2CE8BADBFA": {
|
"E3418055F1E79A2300572C557E71BF2CE8BADBFA": {
|
||||||
"name": "Test Cartridge (USA) (Rev 4) (Proto)"
|
"name": "Test Cartridge (USA) (Rev 4) (Proto)"
|
||||||
},
|
},
|
||||||
"E4BADD494ED548721328EF869D33AD70E3E522C9": {
|
"4F461B0B51C82433E70130EAC5494F26239B4453": {
|
||||||
"logo": "Qmb3D82JxKqf7KFHXGSbTR5cBojXbZYUjwRqhpV8NPUPwn",
|
"name": "Tour De France (USA) (Proto)",
|
||||||
"name": "Berzerk (World)",
|
"vid": "QmXQ2KdKwYS7b4zAfgWQXxUo6jhQPKjDfYrmNzS2BtL72B",
|
||||||
"vid": "QmTBGNBX64jyNVhW3QmJyXWzrXMayNunhg7cZ5ixYCi5Ew"
|
"logo": "QmTxYL5HLGHDJqqUAEhWZpEMAYhdAYg4CwebGy2cddJoxD"
|
||||||
},
|
},
|
||||||
"EB76F860130F8CC87826FA931350467760511213": {
|
"9681DC5D38A31CB0D0C6C3BD0FC76BFDDE531CCE": {
|
||||||
"logo": "QmTaFSMSwyHEmbsJiQz5Fopa3pJJJFoGViVARw5fhDH3db",
|
"name": "WebWarp (Europe)",
|
||||||
"name": "Armor..Attack (World)",
|
"vid": "QmZ6wtHLWez5EWXpK9DLKW4Bbwn2MHAJqmzNneb93S97kn",
|
||||||
"vid": "QmNjJ6K9gL5U3N4atFXs3FBeuaZNJKXuPMcNkU2ALVfcC8"
|
"logo": "QmQmenQKUtDTuzGcrXCFWQ5mddcJGnXfCdk2g2KvSikT3A"
|
||||||
},
|
},
|
||||||
"F6F26BEB20BB364761A9466FF4087C7B9713D048": {
|
"08E2DF9F7769C16A4D730ADF8D659F8EA4BE7F9D": {
|
||||||
"name": "Pitcher's Duel (USA) (Proto)"
|
"name": "WebWars (USA)"
|
||||||
|
},
|
||||||
|
"56CEA447704286D88533DB99FD209816AE1B96A4": {
|
||||||
|
"name": "Star Wars Theme by Chris Salomon (1998) (PD)",
|
||||||
|
"vid": "QmPSj39boR1gkrVfKh38iEKx6GpKwNTS851oCbYeFXCrQh",
|
||||||
|
"logo": "Qmf5keLWEGScQ3q8WV7BC5yYmfVLMfbHz6odtUWGQNVQfL"
|
||||||
|
},
|
||||||
|
"83A6269A9275D8CE48C58B5560FB2D7324EAEEA0": {
|
||||||
|
"name": "Vector Vaders by John Dondzila (1996)",
|
||||||
|
"vid": "QmSyQ7zP7FZEPLHQvHdUWe5YDy73NFhrpHG1vtZL8zxzjh",
|
||||||
|
"logo": "QmahNM3mUSdGTSHWu146L7hf5HZsYvMPRUJyQn1w7pJ2zr"
|
||||||
|
},
|
||||||
|
"4B1E43808AEFC8235B130D2DBAC41FA087D9AEDF": {
|
||||||
|
"name": "Vectrex Pong (1998) (PD)",
|
||||||
|
"vid": "QmPPCkKNMtttNQx7rc6c773PvHz8CdrvKtPFbsoMYsDvph",
|
||||||
|
"logo": "QmcwcZax6zKfh1hELpA7v7vWSrRTA6P4GcbvGNWXDY2bVi"
|
||||||
|
},
|
||||||
|
"745DDBDC285F1DA4F7C3EB5E44FE252D503C7A15": {
|
||||||
|
"name": "Wormhole by John Dondzila (2001) (PD)",
|
||||||
|
"vid": "Qmd6pfnvEjTddPaZQddxNNiA3rkHQVshbtZekKqfmfTdBg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2261
metadata/ws.json
2261
metadata/ws.json
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -1,15 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "emulatorjs",
|
"name": "emulatorjs",
|
||||||
"version": "1.9.2",
|
"version": "1.1.3",
|
||||||
"description": "Rom and art management utility for generating configuration files for use with the EmulatorJS frontend",
|
"description": "Rom and art management utility for generating configuration files for use with the EmulatorJS frontend",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "thelamer",
|
"author": "thelamer",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cloudcmd": "^15.9.14",
|
"cloudcmd": "^15.9.8",
|
||||||
"deepmerge": "^4.2.2",
|
"express": "^4.17.1",
|
||||||
"express": "^4.17.2",
|
"ipfs-http-client": "^53.0.1",
|
||||||
"ipfs-http-client": "51.0.0",
|
"socket.io": "^4.3.1"
|
||||||
"jszip": "^3.7.1",
|
|
||||||
"socket.io": "^4.4.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
profile.js
145
profile.js
@ -1,145 +0,0 @@
|
|||||||
// NPM modules
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var home = require('os').homedir();
|
|
||||||
var express = require('express');
|
|
||||||
var app = express();
|
|
||||||
var fs = require('fs');
|
|
||||||
var fsw = require('fs').promises;
|
|
||||||
var path = require('path');
|
|
||||||
var JSZip = require('jszip');
|
|
||||||
|
|
||||||
// Default vars
|
|
||||||
var error = {status: 'error'};
|
|
||||||
app.use(express.json({ limit: '500MB' }));
|
|
||||||
|
|
||||||
// Catch all to detect endpoint
|
|
||||||
app.get('/*', function(req, res) {
|
|
||||||
res.send('pong');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Catch all for any post
|
|
||||||
app.post('/*', async function(req, res) {
|
|
||||||
try {
|
|
||||||
let type = req.body.type;
|
|
||||||
// Send default profile unauthenticated
|
|
||||||
if (type == 'default') {
|
|
||||||
try {
|
|
||||||
let profilePath = home + '/profile/default/';
|
|
||||||
let zip = new JSZip();
|
|
||||||
let items = await fs.readdirSync(profilePath);
|
|
||||||
async function addToZip(item) {
|
|
||||||
if (fs.lstatSync(item).isDirectory()) {
|
|
||||||
let items = await fs.readdirSync(item);
|
|
||||||
if (items.length > 0) {
|
|
||||||
for await (let subPath of items) {
|
|
||||||
await addToZip(item + '/' + subPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let data = fs.readFileSync(item);
|
|
||||||
let zipPath = item.replace(profilePath,'');
|
|
||||||
zip.file(zipPath, data);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for await (let item of items) {
|
|
||||||
await addToZip(profilePath + item);
|
|
||||||
}
|
|
||||||
zip.generateAsync({type:"base64"}).then(function callback(base64) {
|
|
||||||
res.json({status: 'success',data: base64});
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
res.json(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let auth = req.body.user + req.body.pass;
|
|
||||||
// Simple hash auth
|
|
||||||
let hash = crypto.createHash('sha256').update(auth).digest('hex');
|
|
||||||
let profileJson = await fsw.readFile(home + '/profile/profile.json', 'utf8');
|
|
||||||
let profile = JSON.parse(profileJson);
|
|
||||||
if (profile.hasOwnProperty(hash)) {
|
|
||||||
// Return username if found
|
|
||||||
if (type == 'login') {
|
|
||||||
res.json({status: 'success', user: profile[hash].username});
|
|
||||||
// Take client data and write it to profile
|
|
||||||
} else if (type == 'push') {
|
|
||||||
let profilePath = home + '/profile/' + profile[hash].username + '/';
|
|
||||||
let baseData = req.body.data;
|
|
||||||
// Purge current storage
|
|
||||||
let items = await fsw.readdir(profilePath);
|
|
||||||
if (items.length > 0) {
|
|
||||||
for await (let item of items) {
|
|
||||||
var filePath = profilePath + item;
|
|
||||||
if (fs.statSync(filePath).isFile()) {
|
|
||||||
await fsw.rm(filePath);
|
|
||||||
} else {
|
|
||||||
await fsw.rm(filePath, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Load zip from data
|
|
||||||
let zip = new JSZip();
|
|
||||||
zip.loadAsync(baseData, {base64: true}).then(async function(contents) {
|
|
||||||
// Unzip the files to the FS by name
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (fileName.endsWith('/')) {
|
|
||||||
if (! fs.existsSync(profilePath + fileName)) {
|
|
||||||
await fsw.mkdir(profilePath + fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for await (let fileName of Object.keys(contents.files)) {
|
|
||||||
if (! fileName.endsWith('/')) {
|
|
||||||
zip.file(fileName).async('arraybuffer').then(async function(content) {
|
|
||||||
await fsw.writeFile(profilePath + fileName, Buffer.from(content));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.json({status: 'success',user: profile[hash].username});
|
|
||||||
// Send client data to write to indexedDB
|
|
||||||
} else if (type == 'pull') {
|
|
||||||
try {
|
|
||||||
let profilePath = home + '/profile/' + profile[hash].username + '/';
|
|
||||||
let zip = new JSZip();
|
|
||||||
let items = await fs.readdirSync(profilePath);
|
|
||||||
async function addToZip(item) {
|
|
||||||
if (fs.lstatSync(item).isDirectory()) {
|
|
||||||
let items = await fs.readdirSync(item);
|
|
||||||
if (items.length > 0) {
|
|
||||||
for await (let subPath of items) {
|
|
||||||
await addToZip(item + '/' + subPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let data = fs.readFileSync(item);
|
|
||||||
let zipPath = item.replace(profilePath,'');
|
|
||||||
zip.file(zipPath, data);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for await (let item of items) {
|
|
||||||
await addToZip(profilePath + item);
|
|
||||||
}
|
|
||||||
zip.generateAsync({type:"base64"}).then(function callback(base64) {
|
|
||||||
res.json({status: 'success',data: base64});
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
res.json(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.json(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.json(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
res.json(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3001);
|
|
||||||
@ -51,14 +51,21 @@
|
|||||||
padding: 10px
|
padding: 10px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#modal p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
max-width: 800px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.centered {
|
.centered {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Roms page 2 column layout */
|
/* Roms page 2 column layout */
|
||||||
h3 {
|
#header {
|
||||||
margin-top: .25em;
|
grid-area: header;
|
||||||
margin-bottom: .25em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#right {
|
#right {
|
||||||
@ -71,8 +78,9 @@ h3 {
|
|||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 49.5% 49.5%;
|
grid-template-columns: 50% 50%;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
"header header"
|
||||||
"identified unidentified";
|
"identified unidentified";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,12 +94,6 @@ h3 {
|
|||||||
|
|
||||||
.itemwrapper {
|
.itemwrapper {
|
||||||
display: table;
|
display: table;
|
||||||
border: 1px solid black;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemwrapper + .itemwrapper {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
@ -99,9 +101,10 @@ h3 {
|
|||||||
display: table-cell;
|
display: table-cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemwrapper:hover {
|
.rombutton {
|
||||||
opacity: .8;
|
padding: 0;
|
||||||
cursor: pointer;
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.identifybutton {
|
.identifybutton {
|
||||||
@ -259,83 +262,3 @@ h3 {
|
|||||||
height: 5em;
|
height: 5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#close {
|
|
||||||
position: absolute;
|
|
||||||
background: black;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#modal {
|
|
||||||
display: none;
|
|
||||||
margin: auto;
|
|
||||||
position: absolute;
|
|
||||||
width: 70vw;
|
|
||||||
height: 90vh;
|
|
||||||
top: 0; left: 0; bottom: 0; right: 0;
|
|
||||||
background: white;
|
|
||||||
z-index: 2;
|
|
||||||
border: 1px solid black;
|
|
||||||
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
|
|
||||||
overflow: clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
#modal-content p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#preview-iframe {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 60%;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rom-manage {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rom-manage p {
|
|
||||||
margin: 3px;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#manage-buttons {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height 30%;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
justify-content: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.manage-button {
|
|
||||||
border: 2px solid #000;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 3%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.manage-button:hover {
|
|
||||||
background-color: #D3D3D3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#vidPos {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,15 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>EmulatorJS Manager</title>
|
<title>EmulatorJS Manager</title>
|
||||||
<link rel="stylesheet" href="public/css/index.css">
|
<link rel="stylesheet" href="public/css/index.css">
|
||||||
|
<link rel="stylesheet" href="public/css/vendor/jquery.modal.min.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="modal" class="modal"></div>
|
||||||
<div id="top" class="top">
|
<div id="top" class="top">
|
||||||
<div id="nav" class="nav">
|
<div id="nav" class="nav">
|
||||||
<div onclick="renderRoms()" class="top-item">Rom Management</div>
|
<div onclick="renderRoms()" class="top-item">Rom Management</div>
|
||||||
<div onclick="renderConfigss()" class="top-item">Config Management</div>
|
<div onclick="renderConfigss()" class="top-item">Config Management</div>
|
||||||
<div onclick="renderFiles()" class="top-item">File Management</div>
|
<div onclick="renderFiles()" class="top-item">File Management</div>
|
||||||
<div onclick="renderProfile()" class="top-item">Profile Management</div>
|
|
||||||
<div onclick="renderMeta()" class="top-item">Custom Metadata</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="nav-buttons" class="nav-buttons"></div>
|
<div id="nav-buttons" class="nav-buttons"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -19,41 +19,21 @@
|
|||||||
<div id="side"></div>
|
<div id="side"></div>
|
||||||
<div id="main"></div>
|
<div id="main"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal">
|
|
||||||
<div id="modal-content"></div>
|
|
||||||
<div id="close" onclick="closeModal()"></div>
|
|
||||||
</div>
|
|
||||||
<div id="rominfo" class="hidden">
|
<div id="rominfo" class="hidden">
|
||||||
<div class="readme">
|
<div class="readme">
|
||||||
<h2>Support</h2>
|
|
||||||
<p>For general support please use our <a href="https://discord.gg/YWrKVTn">Discord Server</a>, if you would like to report a bug or have a feature request please do that on <a href="https://github.com/linuxserver/emulatorjs/issues">GitHub</a>.</p>
|
|
||||||
<p>If you are upgrading from previous versions and not seeing a performance increase please do a hard reload on your browser and delete the retroarch.cfg file from the file browser icon (top left of frontent interface folder icon).</p>
|
|
||||||
<p>If you are upgrading from previous versions the n64 core has been switched to a libretro core you will need to change the config file to have '"emulator": "libretro-mupen64plus_next"' under the defaults section of the n64 config to use the new core.</p>
|
|
||||||
<h2>Basic controls</h2>
|
<h2>Basic controls</h2>
|
||||||
<p>For all libretro based emulators you can use F1 on your keyboard, start+select+L+R on a gamepad , or the touch button on the top left in touchscreen devices to bring up the in game menu.</p>
|
<p>For all libretro based emulators you can use F1 on your keyboard or start+select+L+R on a gamepad to bring up the in game menu.</p>
|
||||||
<p>All saves and any settings are stored in your actual web browser by the URL you are using to access this container, to ensure sram is dumped on exit always try to exit the game with back instead of closing the browser window. On libretro based emulators you can perform saves and savestates from the menu.</p>
|
<p>All saves and any settings are stored in your actual web browser by the URL you are using to access this container, to ensure sram is dumped on exit always try to exit the game with back instead of closing the browser window. On libretro based emulators you can perform saves and savestates from the menu.</p>
|
||||||
<p>Xbox users on Edge hold the start button for 3 seconds to exit gamepad controls and if you want to re-enter again press the start button again with your cursor hovering over the game and select the option to use gamepad controls. After you launch a game press the select button a couple times to ensure the B button does not trigger a back action or drop you from fullscreen.</p>
|
<p>Xbox users on Edge hold the start button for 3 seconds to exit gamepad controls and if you want to re-enter again press the start button again with your cursor hovering over the game and select the option to use gamepad controls. After you launch a game press the select button a couple times to ensure the B button does not trigger a back action or drop you from fullscreen.</p>
|
||||||
<p>Scrolling large collections can be sped up by holding right while scrolling up or down when using a keyboard or your controller on Xbox. For touch devices panning is supported by holding and dragging up or down.</p>
|
|
||||||
<p>You can also scroll by letter using the right and left shoulder buttons on a controller or Page Up/Page Down on a keyboard.</p>
|
|
||||||
<h2>Browser support</h2>
|
|
||||||
<p>Only Safari for Apple devices and Chromium based browsers are supported. Other browsers might work, but have not been tested and likely never will be. Gamepads do not function on Firefox, this is a known issue as they do very non standard mapping like dpad button presses to a fake anaolgue joypad.</p>
|
|
||||||
<h2>If you enjoy this</h2>
|
<h2>If you enjoy this</h2>
|
||||||
<p>Consider <a href="https://retroarch.com/index.php?page=donate">supporting</a> libretro they do the heavy lifting. Also take a look at helping with art assets <a href="https://github.com/linuxserver/emulatorjs">Here</a>.</p>
|
<p>Consider <a href="https://retroarch.com/index.php?page=donate">supporting</a> libretro they do the heavy lifting. Also take a look at helping with art assets <a href="https://github.com/linuxserver/emulatorjs">Here</a>.</p>
|
||||||
<h2>Experimental Emulators</h2>
|
|
||||||
<p>More emulators are included than listed below please see <a href="https://github.com/linuxserver/libretrojs">HERE</a> for a complete list of libretro cores. To use an alternative/experimental core just add "libretro-" to the beginning of it's name for the emulator setting for your system config.</p>
|
|
||||||
<h3>Full speed Nintendo DS</h3>
|
|
||||||
<p>Threaded Nintendo DS emulation is available but requires you serve the content from an HTTPS endpoint which means you will need to put this application behind a reverse proxy, we highly recommend <a href="https://github.com/linuxserver/docker-swag">SWAG</a> to achieve this. DS game compatibility and functionality is greatly expanded by using this threaded emulator particularly on an Xbox console the games run at full speed.</p>
|
|
||||||
<p>To enable this emulator just change <span style="background-color:silver;">"emulator": "libretro-melonds"</span> to <span style="background-color:silver;">"emulator": "libretro-melonds_threaded"</span> in the config file.</p>
|
|
||||||
<h3>Full speed Sega Saturn</h3>
|
|
||||||
<p>Threaded Saturn is available but requires you serve the content from an HTTPS endpoint which means you will need to put this application behind a reverse proxy, we highly recommend <a href="https://github.com/linuxserver/docker-swag">SWAG</a> to achieve this. The non threaded emulator is essentially useless unless running some 2D games, threaded many titles run full speed especially on Xbox, if you find any game lacking try setting the threads to 8 in the RetorArch options menu.</p>
|
|
||||||
<p>To enable this emulator just change <span style="background-color:silver;">"emulator": "libretro-yabause"</span> to <span style="background-color:silver;">"emulator": "libretro-yabause_threaded"</span> in the config file.</p>
|
|
||||||
<h2>File Downloading</h2>
|
<h2>File Downloading</h2>
|
||||||
<p>As the network of peers grows, art and default file downloading might not be a 100% perfect. You can always click download multiple times, items that have allready been downloaded will pull from your local IPFS node running in this container while missing items will be retried. Please try to download a couple times before reaching out for support. If you are able to we would appreciate if you can forward port 4001 from this container to the public internet, it decentralizes the distribution of files leading to a more robust CDN for all users.</p>
|
<p>As the network of peers grows, art and default file downloading might not be a 100% perfect. You can always click download multiple times, items that have allready been downloaded will pull from your local IPFS node running in this container while missing items will be retried. Please try to download a couple times before reaching out for support. If you are able to we would appreciate if you can forward port 4001 from this container to the public internet, it decentralizes the distribution of files leading to a more robust CDN for all users.</p>
|
||||||
<h2>Rom Identification basics</h2>
|
<h2>Rom Identification basics</h2>
|
||||||
<p>The basic steps for scanning in roms is:</p>
|
<p>The basic steps for scanning in roms is:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Place the roms in the "roms" directory for your system in your mounted /data/ directory.</li>
|
<li>Place the roms in the "roms" directory for your system in your mounted /data/ directory.</li>
|
||||||
<li>Click on the corresponding scan button above.</li>
|
<li>Click one the corresponding scan button above.</li>
|
||||||
<li>Open the item from the menu on the left.</li>
|
<li>Open the item from the menu on the left.</li>
|
||||||
<li>If any of your roms are unidentified take the time to link them to the correct rom name.</li>
|
<li>If any of your roms are unidentified take the time to link them to the correct rom name.</li>
|
||||||
<li>Click to download all the art items for your identified roms.(logos, videos, etc)</li>
|
<li>Click to download all the art items for your identified roms.(logos, videos, etc)</li>
|
||||||
@ -61,29 +41,8 @@
|
|||||||
</ol>
|
</ol>
|
||||||
<p>From there you can hop into the frontend interface (default port 80) to browse and play your collection.</p>
|
<p>From there you can hop into the frontend interface (default port 80) to browse and play your collection.</p>
|
||||||
<p>In general this application is referencing metadata based on commonly used identifying metrics for rom files, mostly these revolve around checksums (specifically sha1) of the rom files themselves. This metadata is compiled from lists on no-intro.org for roms and redump.org for CD based games. Now no system is perfect, but the aim is to have community participation expand these lists with the data from their own rom sets. After scanning in your roms you may notice that many of them are un-identified using this metadata, if you took the time to link them to the correct reference information please consider providing your resulting reference data to Github <a href="https://github.com/linuxserver/emulatorjs">Here</a>.</p>
|
<p>In general this application is referencing metadata based on commonly used identifying metrics for rom files, mostly these revolve around checksums (specifically sha1) of the rom files themselves. This metadata is compiled from lists on no-intro.org for roms and redump.org for CD based games. Now no system is perfect, but the aim is to have community participation expand these lists with the data from their own rom sets. After scanning in your roms you may notice that many of them are un-identified using this metadata, if you took the time to link them to the correct reference information please consider providing your resulting reference data to Github <a href="https://github.com/linuxserver/emulatorjs">Here</a>.</p>
|
||||||
<h2>Rom Hacks Support</h2>
|
<h2>Multi Disk PSX</h2>
|
||||||
<p>Retroarch supports <a href="https://docs.libretro.com/guides/softpatching/">Softpatching</a>, which will apply a patchfile to the base rom on launch. In order to leverage this feature you will need to prepare a custom zip file ending with the file extension ".patchzip". Lets take a popular hack "Invictus" as an example for snes. You will need to create a zip file named "Invictus.smc.patchzip" (note the two file extensions with smc indicating the extension of the base rom) containing the following files:</p>
|
<p>The only web emulator that supports multiple CD games is psx, in order to function please rename your chd files before scanning them in to the same name with the extension .disk1,.disk2,.disk3,etc. IE:</p>
|
||||||
<ul>
|
|
||||||
<li>Invictus.smc - base rom file to be patched in this case SMW.</li>
|
|
||||||
<li>Invictus.bps - Patch file to be applied.</li>
|
|
||||||
</ul>
|
|
||||||
<p>Multiple patch files can be applied as long as they follow this naming scheme ending in the order to be loaded IE:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Invictus.smc - base rom file to be patched in this case SMW.</li>
|
|
||||||
<li>Invictus.bps - First patch file to be applied.</li>
|
|
||||||
<li>Invictus.bps1 - Second patch file to be applied.</li>
|
|
||||||
</ul>
|
|
||||||
<p>This patchzip format can also be used for any multifile rom like SNES MSU1 Enhanced games that are supported by Snes9X, for example A Link to the Past MSU1 create a folder containing: (this is just an example some of these files might not be required)</p>
|
|
||||||
<ul>
|
|
||||||
<li>alttp_msu.sfc - base rom file that has been patched to support enhanced audio.</li>
|
|
||||||
<li>manifest.bml - MSU1 manifest with tracklist and memory addresses.</li>
|
|
||||||
<li>alttp_msu.xml - XML manifest.</li>
|
|
||||||
<li>alttp_msu.msu - Video data.</li>
|
|
||||||
<li>alttp_msu-[1-xx].pcm - Audio data files.</li>
|
|
||||||
</ul>
|
|
||||||
<p>Just like the rom hacks name the zip file the same as the rom name in this example "alttp_msu.sfc.patchzip"</p>
|
|
||||||
<h2>Multi Disk Games</h2>
|
|
||||||
<p>The only web emulators that supports multiple CD games are psx and saturn, in order to function please rename your chd files before scanning them in to the same name with the extension .disk1,.disk2,.disk3,etc. IE:</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>/data/psx/roms/GAMENAME.disk1</li>
|
<li>/data/psx/roms/GAMENAME.disk1</li>
|
||||||
<li>/data/psx/roms/GAMENAME.disk2</li>
|
<li>/data/psx/roms/GAMENAME.disk2</li>
|
||||||
@ -91,8 +50,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<h2>MAME roms with chds</h2>
|
<h2>MAME roms with chds</h2>
|
||||||
<p>The web based arcade emulator is MAME 2003-Plus, in general most games will work if you use full non-merged roms. Some games require additional chd files though. In order to get these to load with the web frontend we have a custom file format that can be loaded by the extension ".multizip". To create this file simply zip the rom zip and the chd file (no folders) into a single file. IE if you had the file kinst.zip and the folder kinst/kinst.chd simply move the kinst.zip file into the kinst folder and create the archive using <b>zip kinst.multizip kinst.zip kinst.chd</b> then move the resulting file to your roms directory. This multizip file will be unpacked before the emulator is loaded into the directory it is looking for the files.</p>
|
<p>The web based arcade emulator is MAME 2003-Plus, in general most games will work if you use full non-merged roms. Some games require additional chd files though. In order to get these to load with the web frontend we have a custom file format that can be loaded by the extension ".multizip". To create this file simply zip the rom zip and the chd file (no folders) into a single file. IE if you had the file kinst.zip and the folder kinst/kinst.chd simply move the kinst.zip file into the kinst folder and create the archive using <b>zip kinst.multizip kinst.zip kinst.chd</b> then move the resulting file to your roms directory. This multizip file will be unpacked before the emulator is loaded into the directory it is looking for the files.</p>
|
||||||
<h2>Doom wads with multiple wads</h2>
|
|
||||||
<p>The web based emulator is prboom, Zdoom pk3 style mods are not supported. Most community wads out there will also require stock doom2 or doomu wads to function. In order to get these to load with the web frontend we have a custom file format that can be loaded by the extension ".multiwad". To create this file simply zip the multiple wads into a single file. IE if you had the file Valiant.wad needing doom2.wad simply move them into the same directory and run <b>zip Valiant.multiwad Valiant.wad doom2.wad</b> then move the resulting file to your roms directory. This multiwad file will be unpacked before the emulator is loaded and all files made available.</p>
|
|
||||||
<h2>Expanding on identification</h2>
|
<h2>Expanding on identification</h2>
|
||||||
<p>For all CD based system only bin/cue and chd files are supported for identification purposes, you may be able to use iso files, but automatic identification will not be possible. The only system without chd support is 3do, in all other cases you are best off using chd based roms.<p>
|
<p>For all CD based system only bin/cue and chd files are supported for identification purposes, you may be able to use iso files, but automatic identification will not be possible. The only system without chd support is 3do, in all other cases you are best off using chd based roms.<p>
|
||||||
<p>Depending on the type of game you are trying to scan in this process can take some time, particularly for CD based games in CHD format as we need to pull out individual tracks in many cases to sha1sum the resulting data. In general identifying CD based games is very difficult and in some cases these will simply be lists of file names as there is not a reliable way to compare the sums of different tracks from the CDs. We use the following methods to try to create unique metadata strings to look up for each rom. If a rom is zipped or 7zipped it will be extracted and the resulting file checksummed.</p>
|
<p>Depending on the type of game you are trying to scan in this process can take some time, particularly for CD based games in CHD format as we need to pull out individual tracks in many cases to sha1sum the resulting data. In general identifying CD based games is very difficult and in some cases these will simply be lists of file names as there is not a reliable way to compare the sums of different tracks from the CDs. We use the following methods to try to create unique metadata strings to look up for each rom. If a rom is zipped or 7zipped it will be extracted and the resulting file checksummed.</p>
|
||||||
@ -131,7 +88,7 @@
|
|||||||
<h2>General Emulation</h2>
|
<h2>General Emulation</h2>
|
||||||
<p>For the most part all non CD based systems will work fine on mobile devices and the Xbox web browser. In general CD emulation for full sized non stripped games is not going to function outside of a decent PC setup. Mobile and game console browsers lack the ability to dedicate the 100s of megs of memory needed to store and run a CD based game. Sound crackling is normal in web based emulation, in many cases it will actually improve as the game is played longer. Please keep in mind what this is, these are full emulators re-compiled into javascript do not expect perfection. Emulators used with this project:</p>
|
<p>For the most part all non CD based systems will work fine on mobile devices and the Xbox web browser. In general CD emulation for full sized non stripped games is not going to function outside of a decent PC setup. Mobile and game console browsers lack the ability to dedicate the 100s of megs of memory needed to store and run a CD based game. Sound crackling is normal in web based emulation, in many cases it will actually improve as the game is played longer. Please keep in mind what this is, these are full emulators re-compiled into javascript do not expect perfection. Emulators used with this project:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>3DO- <a href="https://emulatorjs.org/github/docs/Systems/3DO.html">https://emulatorjs.org/github/docs/Systems/3DO.html</a></li>
|
<li>3DO- <a href="https://www.emulatorjs.com/3do.html">https://www.emulatorjs.com/3do.html</a></li>
|
||||||
<li>Arcade- <a href="https://github.com/libretro/mame2003-plus-libretro">https://github.com/libretro/mame2003-plus-libretro</a></li>
|
<li>Arcade- <a href="https://github.com/libretro/mame2003-plus-libretro">https://github.com/libretro/mame2003-plus-libretro</a></li>
|
||||||
<li>Atari 2600- <a href="https://github.com/libretro/stella2014-libretro">https://github.com/libretro/stella2014-libretro</a></li>
|
<li>Atari 2600- <a href="https://github.com/libretro/stella2014-libretro">https://github.com/libretro/stella2014-libretro</a></li>
|
||||||
<li>Atari 7800- <a href="https://github.com/libretro/prosystem-libretro">https://github.com/libretro/prosystem-libretro</a></li>
|
<li>Atari 7800- <a href="https://github.com/libretro/prosystem-libretro">https://github.com/libretro/prosystem-libretro</a></li>
|
||||||
@ -143,19 +100,19 @@
|
|||||||
<li>Jaguar- <a href="https://github.com/libretro/virtualjaguar-libretro">https://github.com/libretro/virtualjaguar-libretro</a></li>
|
<li>Jaguar- <a href="https://github.com/libretro/virtualjaguar-libretro">https://github.com/libretro/virtualjaguar-libretro</a></li>
|
||||||
<li>Atari LYNX- <a href="https://github.com/libretro/libretro-handy">https://github.com/libretro/libretro-handy</a></li>
|
<li>Atari LYNX- <a href="https://github.com/libretro/libretro-handy">https://github.com/libretro/libretro-handy</a></li>
|
||||||
<li>MSX- <a href="https://github.com/libretro/blueMSX-libretro">https://github.com/libretro/blueMSX-libretro</a></li>
|
<li>MSX- <a href="https://github.com/libretro/blueMSX-libretro">https://github.com/libretro/blueMSX-libretro</a></li>
|
||||||
<li>Nintendo 64- <a href="https://github.com/libretro/mupen64plus-libretro-nx">https://github.com/libretro/mupen64plus-libretro-nx</a></li>
|
<li>Nintendo 64- <a href="https://www.emulatorjs.com/n64.html">https://www.emulatorjs.com/n64.html</a></li>
|
||||||
<li>Nintendo DS- <a href="https://github.com/libretro/melonDS">https://github.com/libretro/melonDS</a></li>
|
<li>Nintendo DS- <a href="https://www.emulatorjs.com/nds.html">https://www.emulatorjs.com/nds.html</a></li>
|
||||||
<li>Nintendo Entertainment System- <a href="https://github.com/libretro/libretro-fceumm">https://github.com/libretro/libretro-fceumm</a></li>
|
<li>Nintendo Entertainment System- <a href="https://github.com/libretro/libretro-fceumm">https://github.com/libretro/libretro-fceumm</a></li>
|
||||||
<li>Neo Geo Pocket (color)- <a href="https://github.com/libretro/beetle-ngp-libretro">https://github.com/libretro/beetle-ngp-libretro</a></li>
|
<li>Neo Geo Pocket (color)- <a href="https://github.com/libretro/beetle-ngp-libretro">https://github.com/libretro/beetle-ngp-libretro</a></li>
|
||||||
<li>Odyssey2- <a href="https://github.com/libretro/libretro-o2em">https://github.com/libretro/libretro-o2em</a></li>
|
<li>Odyssey2- <a href="https://github.com/libretro/libretro-o2em">https://github.com/libretro/libretro-o2em</a></li>
|
||||||
<li>PC Engine (TurboGrafx-16)- <a href="https://github.com/libretro/beetle-pce-fast-libretro">https://github.com/libretro/beetle-pce-fast-libretro</a></li>
|
<li>PC Engine (TurboGrafx-16)- <a href="https://www.emulatorjs.com/tg16.html">https://www.emulatorjs.com/tg16.html</a></li>
|
||||||
<li>Playstation- <a href="https://github.com/thelamer/retrostash/tree/master/beetle-psx-libretro">https://github.com/thelamer/retrostash/tree/master/beetle-psx-libretro</a></li>
|
<li>Playstation- <a href="https://www.emulatorjs.com/psx.html">https://www.emulatorjs.com/psx.html</a></li>
|
||||||
<li>Sega 32x- 32x currently has no functional emulator I can distribute.</a></li>
|
<li>Sega 32x- <a href="https://www.emulatorjs.com/sega32x.html">https://www.emulatorjs.com/sega32x.html</a></li>
|
||||||
<li>Sega CD- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
<li>Sega CD- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
||||||
<li>Sega Game Gear- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
<li>Sega Game Gear- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
||||||
<li>Sega Mega Drive- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
<li>Sega Mega Drive- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
||||||
<li>Sega Master System- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
<li>Sega Master System- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
||||||
<li>Sega Saturn- <a href="https://github.com/libretro/yabause">https://github.com/libretro/yabause</a></li>
|
<li>Sega Saturn- <a href="https://www.emulatorjs.com/segasaturn.html">https://www.emulatorjs.com/segasaturn.html</a></li>
|
||||||
<li>Sega SG-1000- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
<li>Sega SG-1000- <a href="https://github.com/libretro/Genesis-Plus-GX">https://github.com/libretro/Genesis-Plus-GX</a></li>
|
||||||
<li>Super Nintendo- <a href="https://github.com/libretro/snes9x">https://github.com/libretro/snes9x</a></li>
|
<li>Super Nintendo- <a href="https://github.com/libretro/snes9x">https://github.com/libretro/snes9x</a></li>
|
||||||
<li>Vectrex- <a href="https://github.com/libretro/libretro-vecx">https://github.com/libretro/libretro-vecx</a></li>
|
<li>Vectrex- <a href="https://github.com/libretro/libretro-vecx">https://github.com/libretro/libretro-vecx</a></li>
|
||||||
@ -175,16 +132,9 @@
|
|||||||
<h1>Choose a folder. Drag and drop files or right click to upload.</h1>
|
<h1>Choose a folder. Drag and drop files or right click to upload.</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="metainfo" class="hidden">
|
|
||||||
<div class="readme">
|
|
||||||
<h2>Custom Metadata</h2>
|
|
||||||
<p>When you identify roms or upload custom artwork from the rom management interface all of that data is stored as custom metadata in your container. You can see the systems you have put the work into either skinning or identifying on the left. On top of that when you upload custom art assets from this web interface those items are added to the local IPFS server running in this Docker container. To share this data with the world you will need to forward port 4001 to a public IP address.</p>
|
|
||||||
<h2>Help us out</h2>
|
|
||||||
<p>You went through the process of linking up roms and cleaning up the artwork the way you like it, odds are another person would appreciate not doing the same work you did. If you are able to please share your metadata to <a href="https://github.com/linuxserver/emulatorjs/issues">GitHub</a> or hop on our <a href="https://discord.gg/YWrKVTn">Discord Server</a> and share it there. We can help with file optimization and getting stuff pinned to IPFS permanently. Just copy and paste the metadata to something like <a href="https://pastebin.com/">Paste Bin</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="socket.io/socket.io.js"></script>
|
<script src="socket.io/socket.io.js"></script>
|
||||||
<script src="public/js/vendor/jquery.min.js"></script>
|
<script src="public/js/vendor/jquery.min.js"></script>
|
||||||
|
<script src="public/js/vendor/jquery.modal.min.js"></script>
|
||||||
<script src="public/js/vendor/ace.js"></script>
|
<script src="public/js/vendor/ace.js"></script>
|
||||||
<script src="public/js/index.js"></script>
|
<script src="public/js/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -23,14 +23,6 @@ socket.on('modaldata', modalData);
|
|||||||
socket.on('emptymodal', emptyModal);
|
socket.on('emptymodal', emptyModal);
|
||||||
// Render file directories
|
// Render file directories
|
||||||
socket.on('renderfiledirs', renderFileDirs);
|
socket.on('renderfiledirs', renderFileDirs);
|
||||||
// Render file directories
|
|
||||||
socket.on('renderprofiles', renderProfiles);
|
|
||||||
// Render in rom data
|
|
||||||
socket.on('romdata', renderRomData);
|
|
||||||
// Render in custom metadata page
|
|
||||||
socket.on('rendermeta', renderMetaPage);
|
|
||||||
// Render meta JSON
|
|
||||||
socket.on('rendermetajson', renderMetaJSON);
|
|
||||||
|
|
||||||
//// Functions ////
|
//// Functions ////
|
||||||
// Grab a json file from the server
|
// Grab a json file from the server
|
||||||
@ -39,39 +31,18 @@ function getConfig(file) {
|
|||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
$('#main').data('name', file);
|
$('#main').data('name', file);
|
||||||
socket.emit('getconfig', file);
|
socket.emit('getconfig', file);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Grab a json file from the server
|
|
||||||
function getMeta(file) {
|
|
||||||
$('#main').empty();
|
|
||||||
$('#main').append('<div class="loader"></div>');
|
|
||||||
$('#main').data('name', file);
|
|
||||||
socket.emit('getmeta', file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get list of rom shas to process
|
// Get list of rom shas to process
|
||||||
function getRomShas(dir) {
|
function getRomShas(dir) {
|
||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
$('#main').data('name', dir);
|
$('#main').data('name', dir);
|
||||||
socket.emit('getroms', dir);
|
socket.emit('getroms', dir);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render configs page
|
// Render configs page
|
||||||
function renderConfigss() {
|
function renderConfigss() {
|
||||||
socket.emit('renderconfigs');
|
socket.emit('renderconfigs');
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render meta page
|
|
||||||
function renderMeta() {
|
|
||||||
socket.emit('rendermeta');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render profiles page
|
|
||||||
function renderProfile() {
|
|
||||||
socket.emit('renderprofiles');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render roms landing page
|
// Render roms landing page
|
||||||
async function renderRomsLanding(counts) {
|
async function renderRomsLanding(counts) {
|
||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
@ -102,7 +73,7 @@ async function renderRomsLanding(counts) {
|
|||||||
$(cardContainer).append(scanWarning);
|
$(cardContainer).append(scanWarning);
|
||||||
};
|
};
|
||||||
$('#main').append(cardContainer, $('#rominfo').html());
|
$('#main').append(cardContainer, $('#rominfo').html());
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render roms page
|
// Render roms page
|
||||||
function renderRoms() {
|
function renderRoms() {
|
||||||
@ -111,41 +82,62 @@ function renderRoms() {
|
|||||||
|
|
||||||
// Scan in a roms directory
|
// Scan in a roms directory
|
||||||
function scanRoms(folder) {
|
function scanRoms(folder) {
|
||||||
socket.emit('scanroms', [folder, true]);
|
socket.emit('scanroms', folder);
|
||||||
$('#modal').toggle(100);
|
$('#modal').modal();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Scan in a roms directory
|
// Prompt user to identify rom
|
||||||
function newScan(folder) {
|
async function identifyRom(sha, name) {
|
||||||
socket.emit('scanroms', [folder, false]);
|
$('#modal').modal();
|
||||||
$('#modal').toggle(100);
|
$('#modal').empty();
|
||||||
}
|
$('#modal').data('sha', sha.replace("|","'"));
|
||||||
|
$('#modal').append('<p>Identify: ' + name.replace("|","'") + '</p>');
|
||||||
|
var metaData = $('#main').data('metadata');
|
||||||
|
var options = [];
|
||||||
|
for await (sha of Object.keys(metaData)) {
|
||||||
|
if (metaData[sha].hasOwnProperty('name')) {
|
||||||
|
options.push({'sha': sha, 'name': metaData[sha].name});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var sorted = options.sort(function(a, b){
|
||||||
|
const name1 = a.name.toUpperCase();
|
||||||
|
const name2 = b.name.toUpperCase();
|
||||||
|
let comparison = 0;
|
||||||
|
if (name1 > name2) {
|
||||||
|
comparison = 1;
|
||||||
|
} else if (name1 < name2) {
|
||||||
|
comparison = -1;
|
||||||
|
}
|
||||||
|
return comparison;
|
||||||
|
})
|
||||||
|
var sel = $('<select>').attr('id', 'gameselection');
|
||||||
|
for await (var option of sorted) {
|
||||||
|
sel.append($("<option>").attr('value',option.sha).text(option.name));
|
||||||
|
};
|
||||||
|
$('#modal').append(sel);
|
||||||
|
var setMetaButton = $('<button>').addClass('button hover').attr('onclick', 'setMeta()').text('Link Item');
|
||||||
|
$('#modal').append(setMetaButton);
|
||||||
|
};
|
||||||
|
|
||||||
// Link metadata for selected rom
|
// Link metadata for selected rom
|
||||||
function setMeta() {
|
function setMeta() {
|
||||||
var linkHash = $('#gameselection').val();
|
var foundGameSha = $('#gameselection').val();
|
||||||
var hash = $('#modal').data('hash');
|
var romSha = $('#modal').data('sha');
|
||||||
var dir = $('#main').data('name');
|
var dir = $('#main').data('name');
|
||||||
closeModal();
|
|
||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
socket.emit('usermeta', [hash, linkHash, dir]);
|
$.modal.close();
|
||||||
}
|
socket.emit('usermeta', [romSha, foundGameSha, dir]);
|
||||||
|
};
|
||||||
|
|
||||||
// Send output to modal
|
// Send output to modal
|
||||||
function modalData(data) {
|
function modalData(data) {
|
||||||
$('#modal-content').prepend('<p>' + data + '</p>');
|
$('#modal').prepend('<p>' + data + '</p>');
|
||||||
}
|
};
|
||||||
|
|
||||||
// Empty modal
|
// Empty modal
|
||||||
function emptyModal() {
|
function emptyModal() {
|
||||||
$('#modal-content').empty();
|
$('#modal').empty();
|
||||||
}
|
|
||||||
|
|
||||||
// Close modal
|
|
||||||
function closeModal() {
|
|
||||||
emptyModal();
|
|
||||||
$('#modal').toggle(100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render config file list
|
// Render config file list
|
||||||
@ -159,20 +151,7 @@ function renderConfigs(files) {
|
|||||||
$('#side').append(sideLink);
|
$('#side').append(sideLink);
|
||||||
});
|
});
|
||||||
$('#main').append($('<h1>').addClass('readme').text('Open a config to edit (main for landing page)'));
|
$('#main').append($('<h1>').addClass('readme').text('Open a config to edit (main for landing page)'));
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render meta file list
|
|
||||||
function renderMetaPage(files) {
|
|
||||||
$('#main').empty();
|
|
||||||
$('#main').append($('#metainfo').html());
|
|
||||||
$('#side').empty();
|
|
||||||
$('#nav-buttons').empty();
|
|
||||||
$.each(files, function(index, file) {
|
|
||||||
var file = file.replace('.json','');
|
|
||||||
var sideLink = $('<div>').addClass('sideitem hover').attr('onclick', "getMeta('" + file + "')").text(file);
|
|
||||||
$('#side').append(sideLink);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render Roms file list
|
// Render Roms file list
|
||||||
function renderRomsDir(dirs) {
|
function renderRomsDir(dirs) {
|
||||||
@ -182,7 +161,7 @@ function renderRomsDir(dirs) {
|
|||||||
var sideLink = $('<div>').addClass('sideitem hover').attr('onclick', "getRomShas('" + dir + "')").text(dir);
|
var sideLink = $('<div>').addClass('sideitem hover').attr('onclick', "getRomShas('" + dir + "')").text(dir);
|
||||||
$('#side').append(sideLink);
|
$('#side').append(sideLink);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// Save modified config
|
// Save modified config
|
||||||
async function saveConfig() {
|
async function saveConfig() {
|
||||||
@ -192,37 +171,30 @@ async function saveConfig() {
|
|||||||
var config = JSON.parse(json);
|
var config = JSON.parse(json);
|
||||||
socket.emit('saveconfig', {'config': config, 'name': name});
|
socket.emit('saveconfig', {'config': config, 'name': name});
|
||||||
socket.emit('renderconfigs');
|
socket.emit('renderconfigs');
|
||||||
}
|
};
|
||||||
|
|
||||||
// Save romlist to config file
|
// Save romlist to config file
|
||||||
function addToConfig(name) {
|
function addToConfig(name) {
|
||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
socket.emit('addtoconfig', name);
|
socket.emit('addtoconfig', name);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Remove roms with no art from config
|
|
||||||
function purgeNoArt(name) {
|
|
||||||
$('#main').empty();
|
|
||||||
$('#main').append('<div class="loader"></div>');
|
|
||||||
socket.emit('purgenoart', name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download art for all identified roms
|
// Download art for all identified roms
|
||||||
function downloadArt(name) {
|
function downloadArt(name) {
|
||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
$('#modal').toggle(100);
|
$('#modal').modal();
|
||||||
socket.emit('downloadart', name);
|
socket.emit('downloadart', name);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Tell server to download the default file set
|
// Tell server to download the default file set
|
||||||
function dlDefaultFiles() {
|
function dlDefaultFiles() {
|
||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
$('#modal').toggle(100);
|
$('#modal').modal();
|
||||||
socket.emit('dldefaultfiles');
|
socket.emit('dldefaultfiles');
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render in a config file
|
// Render in a config file
|
||||||
async function renderConfig(config) {
|
async function renderConfig(config) {
|
||||||
@ -242,23 +214,7 @@ async function renderConfig(config) {
|
|||||||
});
|
});
|
||||||
var json = JSON.stringify(config, null, 2);
|
var json = JSON.stringify(config, null, 2);
|
||||||
editor.setValue(json, -1);
|
editor.setValue(json, -1);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render in a config file
|
|
||||||
async function renderMetaJSON(meta) {
|
|
||||||
// Main edit window
|
|
||||||
$('#main').empty();
|
|
||||||
$('#main').append($('<div>').attr('id', 'editor'));
|
|
||||||
editor = ace.edit('editor');
|
|
||||||
editor.setTheme('ace/theme/chrome');
|
|
||||||
editor.session.setMode('ace/mode/json');
|
|
||||||
editor.$blockScrolling = Infinity;
|
|
||||||
editor.setOptions({
|
|
||||||
readOnly: false,
|
|
||||||
});
|
|
||||||
var json = JSON.stringify(meta, null, 2);
|
|
||||||
editor.setValue(json, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render in roms data
|
// Render in roms data
|
||||||
async function renderRom(data) {
|
async function renderRom(data) {
|
||||||
@ -267,24 +223,26 @@ async function renderRom(data) {
|
|||||||
$('#main').data('metadata', data[2]);
|
$('#main').data('metadata', data[2]);
|
||||||
$('#side').empty();
|
$('#side').empty();
|
||||||
// Render Legend
|
// Render Legend
|
||||||
let container = $('<div>').addClass('wrapper');
|
var keys = [['All Art Downloaded', 'green'], ['Art Available for Download', 'red'], ['No Art Available', 'gray']]
|
||||||
|
var header = $('<div>').attr('id', 'header');
|
||||||
|
for await (var key of keys) {
|
||||||
|
var item = $('<div>').css('background-color',key[1]).text(key[0]);
|
||||||
|
header.append(item);
|
||||||
|
}
|
||||||
|
var container = $('<div>').addClass('wrapper');
|
||||||
|
container.append(header);
|
||||||
$('#main').append(container);
|
$('#main').append(container);
|
||||||
let identified = $('<div>').attr('id','left').append('<h3>Identified Roms:</h3>');
|
var identified = $('<div>').attr('id','left').append('<p>Identified Roms:</p>');
|
||||||
let unidentified = $('<div>').attr('id','right').append('<h3>Unidentified Roms:</h3>');
|
var unidentified = $('<div>').attr('id','right').append('<p>Unidentified Roms:</p>');
|
||||||
container.append(identified,unidentified);
|
container.append(identified,unidentified);
|
||||||
// Process buttons
|
// Process buttons
|
||||||
let folderName = $('#main').data('name');
|
var folderName = $('#main').data('name');
|
||||||
let downloadArtButton = $('<button>').addClass('button hover').attr('onclick', 'downloadArt(\'' + folderName + '\');').text('Download All Available Art');
|
var downloadArtButton = $('<button>').addClass('button hover').attr('onclick', 'downloadArt(\'' + folderName + '\');').text('Download All Available Art');
|
||||||
$('#side').append($('<p>').text('Step 1:'));
|
$('#side').append($('<p>').text('Step 1:'));
|
||||||
$('#side').append(downloadArtButton);
|
$('#side').append(downloadArtButton);
|
||||||
let configButton = $('<button>').addClass('button hover').attr('onclick', 'addToConfig(\'' + folderName + '\');').text('Add All Roms to Config');
|
var configButton = $('<button>').addClass('button hover').attr('onclick', 'addToConfig(\'' + folderName + '\');').text('Add All Roms to Config');
|
||||||
$('#side').append($('<p>').text('Step 2:'));
|
$('#side').append($('<p>').text('Step 2:'));
|
||||||
$('#side').append(configButton);
|
$('#side').append(configButton);
|
||||||
$('#side').append($('<p>').text('Optional:'));
|
|
||||||
let noArtButton = $('<button>').addClass('button hover').attr('onclick', 'purgeNoArt(\'' + folderName + '\');').text('Remove Roms with No Art');
|
|
||||||
$('#side').append(noArtButton);
|
|
||||||
let newScanButton = $('<button>').addClass('button hover').attr('onclick', 'newScan(\'' + folderName + '\');').text('Scan for New Items');
|
|
||||||
$('#side').append(newScanButton);
|
|
||||||
// Render items
|
// Render items
|
||||||
for await (var idItem of Object.keys(data[0])) {
|
for await (var idItem of Object.keys(data[0])) {
|
||||||
if (data[0][idItem].has_art == true) {
|
if (data[0][idItem].has_art == true) {
|
||||||
@ -294,183 +252,37 @@ async function renderRom(data) {
|
|||||||
} else if (data[0][idItem].has_art == 'none') {
|
} else if (data[0][idItem].has_art == 'none') {
|
||||||
var color = 'gray';
|
var color = 'gray';
|
||||||
};
|
};
|
||||||
var item = $('<div>').addClass('itemwrapper').css('background-color', color).html($('<p>').addClass('item').text(idItem)).attr('onclick', 'romMenu(\'' + idItem.replaceAll("'","|") + '\')');
|
var deleteButton = $('<button>').addClass('rombutton item').attr('onclick', 'deleteRom(\'' + idItem.replace("'","|") + '\')').text('delete');
|
||||||
|
var item = $('<div>').addClass('itemwrapper').css('background-color', color).html($('<p>').addClass('item').text(idItem));
|
||||||
|
item.append(deleteButton);
|
||||||
identified.append(item)
|
identified.append(item)
|
||||||
};
|
};
|
||||||
for await (var noIdItem of Object.keys(data[1])) {
|
for await (var noIdItem of Object.keys(data[1])) {
|
||||||
var item = $('<div>').addClass('itemwrapper').css('background-color', 'gray').html($('<p>').addClass('item').text(noIdItem)).attr('onclick', 'romMenu(\'' + noIdItem.replaceAll("'","|") + '\')');
|
var identifyButton = $('<button>').addClass('identifybutton').attr('onclick', 'identifyRom(\'' + data[1][noIdItem].replace("'","|") + '\',\'' + noIdItem.replace("'","|") + '\');').text('identify');
|
||||||
|
var deleteButton = $('<button>').addClass('rombutton item').attr('onclick', 'deleteRom(\'' + noIdItem.replace("'","|") + '\')').text('delete');
|
||||||
|
var item = $('<div>').addClass('itemwrapper').html($('<p>').addClass('item').text(noIdItem).append(identifyButton));
|
||||||
|
item.append(deleteButton);
|
||||||
unidentified.append(item)
|
unidentified.append(item)
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render rom menu modal
|
// Render confirm delete rom
|
||||||
function romMenu(cleanName) {
|
function deleteRom(name) {
|
||||||
let dir = $('#main').data('name');
|
$('#modal').modal();
|
||||||
name = cleanName.replaceAll("|","'");
|
$('#modal').empty();
|
||||||
$('#modal').data('name', name);
|
var dir = $('#main').data('name');
|
||||||
emptyModal();
|
var realName = name.replace("|","'");
|
||||||
$('#modal-content').append('<div class="loader"></div>');
|
$('#modal').data('delete', [name, dir]);
|
||||||
$('#modal').toggle(100);
|
var message = $('<p>').text('Really delete ' + realName + ' ?');
|
||||||
socket.emit('getromdata', [dir, name]);
|
var deletebutton = $('<button>').attr('onclick', 'deleteRomReal()').addClass('button hover').text('delete');
|
||||||
}
|
$('#modal').append(message, deletebutton);
|
||||||
|
};
|
||||||
|
|
||||||
// Render rom data we get
|
// Delete rom
|
||||||
async function renderRomData(data) {
|
function deleteRomReal(data) {
|
||||||
$('#modal').data('hash', data.hash);
|
socket.emit('deleterom', $('#modal').data('delete'));
|
||||||
let metaVars = ['back','corner','logo','vid'];
|
$.modal.close();
|
||||||
let dir = $('#main').data('name');
|
};
|
||||||
let basePath = 'frontend/user/';
|
|
||||||
emptyModal();
|
|
||||||
previewFrame = $('<iframe>').attr({src: 'frontend/index.html#preview', id: 'preview-iframe'});
|
|
||||||
$('#modal-content').append(previewFrame);
|
|
||||||
let manage = $('<div>').attr('id', 'rom-manage');
|
|
||||||
let fileLink = $('<a>').attr('href', basePath + dir + '/roms/' + data.file).text(data.file);
|
|
||||||
let fileName = $('<p>').text('Rom File: ').append(fileLink);
|
|
||||||
manage.append(fileName);
|
|
||||||
if (data.metadata.hasOwnProperty('name')) {
|
|
||||||
let name = $('<p>').text('Meta Name: ' + data.metadata.name);
|
|
||||||
manage.append(name);
|
|
||||||
} else {
|
|
||||||
let name = $('<p>').text('Meta Name: Unidentified or NA');
|
|
||||||
manage.append(name);
|
|
||||||
}
|
|
||||||
let hash = $('<p>').text('Scanned Hash: ' + data.hash);
|
|
||||||
manage.append(hash);
|
|
||||||
for await (let asset of metaVars) {
|
|
||||||
if (data[asset]) {
|
|
||||||
let link = $('<a>').attr('href', basePath + data[asset]).text(data[asset]);
|
|
||||||
let text = $('<p>').text(asset + ': ').append(link);
|
|
||||||
manage.append(text);
|
|
||||||
} else {
|
|
||||||
let text = $('<p>').text(asset + ': Default or not found');
|
|
||||||
manage.append(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.metadata.hasOwnProperty('video_position')) {
|
|
||||||
var vidPos = data.metadata.video_position;
|
|
||||||
} else {
|
|
||||||
var vidPos = '';
|
|
||||||
}
|
|
||||||
if (data.vid) {
|
|
||||||
let vidInput = $('<p>').text('Video Position: ');
|
|
||||||
let posInput = $('<input>').attr({id: 'vidPos', type: 'text', value: vidPos});
|
|
||||||
let posButton = $('<button>').addClass('button hover').text('Update').attr('onclick', 'updateVidPos()');
|
|
||||||
vidInput.append(posInput,posButton);
|
|
||||||
manage.append(vidInput);
|
|
||||||
}
|
|
||||||
let buttonWrapper = $('<div>').attr('id', 'manage-buttons');
|
|
||||||
if (data.metadata) {
|
|
||||||
for await (let upload of metaVars) {
|
|
||||||
let uploadButton = $('<button>').addClass('manage-button').text('Upload ' + upload);
|
|
||||||
uploadButton.attr('onclick',"$('#" + upload + "').trigger('click')");
|
|
||||||
let uploadInput = $('<input>').addClass('hidden').attr({id: upload, type: 'file', onchange: 'upload(this)'});
|
|
||||||
buttonWrapper.append(uploadButton,uploadInput);
|
|
||||||
}
|
|
||||||
let unIdentifyButton = $('<button>').addClass('manage-button').text('Purge Art/Metadata');
|
|
||||||
buttonWrapper.append(unIdentifyButton.attr('onclick','unIdentify(false)'));
|
|
||||||
} else {
|
|
||||||
let identifyButton = $('<button>').addClass('manage-button').text('Identify Rom');
|
|
||||||
buttonWrapper.append(identifyButton.attr('onclick','identify()'));
|
|
||||||
}
|
|
||||||
let deleteButton = $('<button>').addClass('manage-button').text('Delete Everything');
|
|
||||||
buttonWrapper.append(deleteButton.attr('onclick','unIdentify(true)'));
|
|
||||||
manage.append(buttonWrapper);
|
|
||||||
$('#modal-content').append(manage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt user to identify rom
|
|
||||||
async function identify() {
|
|
||||||
let file = $('#modal').data('name');
|
|
||||||
$('#rom-manage').empty();
|
|
||||||
$('#rom-manage').append('<p>Identify: ' + file + '</p>');
|
|
||||||
var metaData = $('#main').data('metadata');
|
|
||||||
var options = [];
|
|
||||||
for await (sha of Object.keys(metaData)) {
|
|
||||||
if (metaData[sha].hasOwnProperty('name')) {
|
|
||||||
options.push({'sha': sha, 'name': metaData[sha].name});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var sel = $('<select>').attr('id', 'gameselection');
|
|
||||||
|
|
||||||
// Bump an exact match on the filename to the top of the list
|
|
||||||
var mostLikely = options.filter(opt => file.startsWith(opt.name))
|
|
||||||
for await (var option of mostLikely) {
|
|
||||||
sel.append($("<option>").attr('value',option.sha).text(option.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
var sorted = options.sort((a,b) => (a.name > b.name) ? 1 : -1)
|
|
||||||
for await (var option of sorted) {
|
|
||||||
sel.append($("<option>").attr('value',option.sha).text(option.name));
|
|
||||||
};
|
|
||||||
$('#rom-manage').append(sel);
|
|
||||||
var setMetaButton = $('<button>').addClass('button hover').attr('onclick', 'setMeta()').text('Link Item');
|
|
||||||
$('#rom-manage').append(setMetaButton);
|
|
||||||
$('#rom-manage').append($('<h3>').text('Rom not found?:'));
|
|
||||||
let idInput = $('<p>').text('Custom Rom: ');
|
|
||||||
let nameInput = $('<input>').attr({id: 'customName', type: 'text', placeholder: 'Enter a custom name'});
|
|
||||||
let idButton = $('<button>').addClass('button hover').text('Identify').attr('onclick', 'setCustomMeta()');
|
|
||||||
idInput.append(nameInput,idButton);
|
|
||||||
$('#rom-manage').append(idInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove identified roms link if it exists
|
|
||||||
function unIdentify(purge) {
|
|
||||||
let hash = $('#modal').data('hash');
|
|
||||||
let dir = $('#main').data('name');
|
|
||||||
let file = $('#modal').data('name');
|
|
||||||
closeModal();
|
|
||||||
$('#main').empty();
|
|
||||||
$('#main').append('<div class="loader"></div>');
|
|
||||||
socket.emit('removemeta', [hash, dir, file, purge]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set custom metadata for a rom
|
|
||||||
function setCustomMeta() {
|
|
||||||
let hash = $('#modal').data('hash');
|
|
||||||
let dir = $('#main').data('name');
|
|
||||||
let name = $('#customName').val();
|
|
||||||
closeModal();
|
|
||||||
$('#main').empty();
|
|
||||||
$('#main').append('<div class="loader"></div>');
|
|
||||||
socket.emit('custommeta', [hash, dir, name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle art uploads
|
|
||||||
async function upload(input) {
|
|
||||||
let fileType = $(input).attr('id');
|
|
||||||
let dir = $('#main').data('name');
|
|
||||||
let file = $('#modal').data('name');
|
|
||||||
let hash = $('#modal').data('hash');
|
|
||||||
if (input.files && input.files[0]) {
|
|
||||||
emptyModal();
|
|
||||||
$('#modal-content').append('<div class="loader"></div>');
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = async function(e) {
|
|
||||||
if (e.total < 100000000) {
|
|
||||||
let data = e.target.result;
|
|
||||||
socket.emit('uploadart', [fileType, dir, file, hash, data]);
|
|
||||||
} else {
|
|
||||||
emptyModal();
|
|
||||||
$('#modal-content').append($('<h2>').text('File too big'));
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
socket.emit('getromdata', [dir, name]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.readAsArrayBuffer(input.files[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle request to change video position
|
|
||||||
function updateVidPos() {
|
|
||||||
let dir = $('#main').data('name');
|
|
||||||
let file = $('#modal').data('name');
|
|
||||||
let hash = $('#modal').data('hash');
|
|
||||||
let position = $('#vidPos').val();
|
|
||||||
emptyModal();
|
|
||||||
$('#modal-content').append('<div class="loader"></div>');
|
|
||||||
socket.emit('updatevidposition', [dir, file, hash, position]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render in landing
|
// Render in landing
|
||||||
function renderLanding() {
|
function renderLanding() {
|
||||||
@ -478,7 +290,7 @@ function renderLanding() {
|
|||||||
$('#main').empty();
|
$('#main').empty();
|
||||||
$('#side').empty();
|
$('#side').empty();
|
||||||
$('#main').append($('#landing').html());
|
$('#main').append($('#landing').html());
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render file management
|
// Render file management
|
||||||
function renderFiles() {
|
function renderFiles() {
|
||||||
@ -486,7 +298,7 @@ function renderFiles() {
|
|||||||
$('#side').empty();
|
$('#side').empty();
|
||||||
$('#main').append('<div class="loader"></div>');
|
$('#main').append('<div class="loader"></div>');
|
||||||
socket.emit('renderfiles');
|
socket.emit('renderfiles');
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render file directories
|
// Render file directories
|
||||||
function renderFileDirs(dirs) {
|
function renderFileDirs(dirs) {
|
||||||
@ -498,7 +310,7 @@ function renderFileDirs(dirs) {
|
|||||||
var sideLink = $('<div>').addClass('sideitem hover').attr('onclick', "renderFileBrowser('" + dir + "')").text(dir);
|
var sideLink = $('<div>').addClass('sideitem hover').attr('onclick', "renderFileBrowser('" + dir + "')").text(dir);
|
||||||
$('#side').append(sideLink);
|
$('#side').append(sideLink);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// Tell server to configure a file browser
|
// Tell server to configure a file browser
|
||||||
function renderFileBrowser(dir) {
|
function renderFileBrowser(dir) {
|
||||||
@ -509,59 +321,4 @@ function renderFileBrowser(dir) {
|
|||||||
};
|
};
|
||||||
var browser = $('<iframe>').attr('src', url + 'files/fs/' + dir).addClass('browser');
|
var browser = $('<iframe>').attr('src', url + 'files/fs/' + dir).addClass('browser');
|
||||||
$('#main').append(browser);
|
$('#main').append(browser);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Render profiles
|
|
||||||
function renderProfiles(profiles) {
|
|
||||||
$('#side').empty();
|
|
||||||
$('#main').empty();
|
|
||||||
$('#nav-buttons').empty();
|
|
||||||
$('#main').append($('<h1>').addClass('readme').text('Choose a Profile or Create one:'));
|
|
||||||
let userForm = $('<span>').addClass('readme');
|
|
||||||
userForm.append($('<input>').attr({id:'user',type:'text',placeholder:'username'}));
|
|
||||||
userForm.append($('<input>').attr({id:'pass',type:'password',placeholder:'password'}));
|
|
||||||
userForm.append($('<button>').addClass('button hover').attr('onclick','createProfile()').text('Create Profile'));
|
|
||||||
$('#main').append(userForm);
|
|
||||||
profiles.push('default');
|
|
||||||
profiles.sort();
|
|
||||||
$.each(profiles, function(index, profile) {
|
|
||||||
var sideLink = $('<div>').addClass('sideitem hover').attr('onclick', "renderUserProfile('" + profile + "')").text(profile);
|
|
||||||
$('#side').append(sideLink);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render a profile
|
|
||||||
function renderUserProfile(dir) {
|
|
||||||
$('#main').empty();
|
|
||||||
$('#nav-buttons').empty();
|
|
||||||
var url = window.location.href;
|
|
||||||
if (! url.endsWith('/')) {
|
|
||||||
var url = url + '/';
|
|
||||||
};
|
|
||||||
var browser = $('<iframe>').attr('src', url + 'profile/fs/' + dir).addClass('browser');
|
|
||||||
$('#main').append(browser);
|
|
||||||
// Delete Button
|
|
||||||
if (dir !== 'default') {
|
|
||||||
var deleteButton = $('<button>').addClass('button hover').attr('onclick', 'deleteProfile(\'' + dir + '\')').text('Delete ' + dir);
|
|
||||||
$('#nav-buttons').append(deleteButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a blank profile
|
|
||||||
function createProfile() {
|
|
||||||
let user = $('#user').val();
|
|
||||||
let pass = $('#pass').val();
|
|
||||||
$('#main').empty();
|
|
||||||
$('#side').empty();
|
|
||||||
$('#main').append('<div class="loader"></div>');
|
|
||||||
socket.emit('createprofile', [user,pass]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete a profile
|
|
||||||
function deleteProfile(user) {
|
|
||||||
$('#main').empty();
|
|
||||||
$('#side').empty();
|
|
||||||
$('#main').append('<div class="loader"></div>');
|
|
||||||
socket.emit('deleteprofile', user);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
34
setup_dev.sh
34
setup_dev.sh
@ -1,5 +1,23 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
|
# Working emulators for retroarch
|
||||||
|
retroarchemus="\
|
||||||
|
fceumm
|
||||||
|
snes9x
|
||||||
|
mednafen_vb
|
||||||
|
gearboy
|
||||||
|
vba_next
|
||||||
|
gambatte
|
||||||
|
genesis_plus_gx
|
||||||
|
handy
|
||||||
|
mame2003_plus
|
||||||
|
mednafen_ngp
|
||||||
|
mednafen_pce_fast
|
||||||
|
mednafen_wswan
|
||||||
|
o2em
|
||||||
|
prboom
|
||||||
|
vecx
|
||||||
|
bluemsx"
|
||||||
IFS=$'\n'
|
IFS=$'\n'
|
||||||
rootdir=$(pwd)
|
rootdir=$(pwd)
|
||||||
# Install modules
|
# Install modules
|
||||||
@ -8,10 +26,24 @@ npm install
|
|||||||
# EmulatorJS
|
# EmulatorJS
|
||||||
curl -o \
|
curl -o \
|
||||||
/tmp/emulatorjs-blob.tar.gz -L \
|
/tmp/emulatorjs-blob.tar.gz -L \
|
||||||
"https://github.com/thelamer/emulatorjs/archive/main.tar.gz"
|
"https://github.com/ethanaobrien/emulatorjs/archive/main.tar.gz"
|
||||||
tar xf \
|
tar xf \
|
||||||
/tmp/emulatorjs-blob.tar.gz -C \
|
/tmp/emulatorjs-blob.tar.gz -C \
|
||||||
frontend/ --strip-components=1
|
frontend/ --strip-components=1
|
||||||
|
# Libretro emscripten
|
||||||
|
mkdir retrotmp
|
||||||
|
cd retrotmp
|
||||||
|
wget https://buildbot.libretro.com/nightly/emscripten/RetroArch.7z
|
||||||
|
7z x RetroArch.7z
|
||||||
|
sed -i 's/wasmBinaryFile="/wasmBinaryFile="data\//g' retroarch/*.js
|
||||||
|
for emu in $retroarchemus; do
|
||||||
|
mv retroarch/${emu}_libretro.* "${rootdir}/frontend/data/"
|
||||||
|
done
|
||||||
|
cd retroarch/assets/frontend/bundle/
|
||||||
|
zip -r frontend.zip assets/xmb/monochrome assets/ozone shaders filters info autoconfig overlay assets/menu_widgets
|
||||||
|
mv frontend.zip "${rootdir}/frontend/data/"
|
||||||
|
cd "${rootdir}"
|
||||||
|
rm -Rf retrotmp
|
||||||
# Custom cores
|
# Custom cores
|
||||||
curl -o \
|
curl -o \
|
||||||
/tmp/custom-cores.tar.gz -L \
|
/tmp/custom-cores.tar.gz -L \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user