Compare commits

..

No commits in common. "master" and "1.2.0" have entirely different histories.

62 changed files with 169474 additions and 201829 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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); }
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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()">&uarr; Push to Server</button>
<button class="right" onclick="pullProfile()">&darr; 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

View File

@ -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>

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

204
frontend/js/vendor/jquery.hotkeys.js vendored Normal file
View 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);

File diff suppressed because one or more lines are too long

View File

@ -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": "/"
}

View File

@ -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
View File

@ -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
}
}));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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"

View File

@ -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"
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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"
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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"
} }
} }

View File

@ -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"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -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"
} }
} }

View File

@ -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);

View File

@ -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;
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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 \