├── nodemon.json ├── favicon.ico ├── docs └── layout.png ├── frontend ├── icon.png ├── favicon.ico ├── manifest.json ├── css │ ├── fullscreen.svg │ ├── filebrowser.css │ ├── index.css │ ├── folder.svg │ └── vendor │ │ └── jquery.modal.min.css ├── index.html ├── filebrowser.html └── js │ ├── vendor │ ├── jquery.modal.min.js │ └── hammer.min.js │ └── filebrowser.js ├── .gitignore ├── .github └── workflows │ ├── call_issues_cron.yml │ └── call_issue_pr_tracker.yml ├── package.json ├── setup_dev.sh ├── metadata ├── doom.json ├── vb.json ├── vectrex.json ├── atari5200.json └── jaguar.json ├── public ├── js │ ├── vendor │ │ ├── theme-chrome.js │ │ ├── mode-json.js │ │ └── worker-json.js │ └── index.js ├── css │ └── index.css └── index.html ├── profile.js ├── README.md └── has_files.sh /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": "frontend/user" 3 | } 4 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/emulatorjs/HEAD/favicon.ico -------------------------------------------------------------------------------- /docs/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/emulatorjs/HEAD/docs/layout.png -------------------------------------------------------------------------------- /frontend/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/emulatorjs/HEAD/frontend/icon.png -------------------------------------------------------------------------------- /frontend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxserver/emulatorjs/HEAD/frontend/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | node_modules/ 4 | frontend/README.md 5 | frontend/user 6 | frontend/data/ 7 | frontend/decrypt\ tools/ 8 | -------------------------------------------------------------------------------- /.github/workflows/call_issues_cron.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | on: 3 | schedule: 4 | - cron: '35 15 * * *' 5 | workflow_dispatch: 6 | 7 | jobs: 8 | stale: 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | uses: linuxserver/github-workflows/.github/workflows/issues-cron.yml@v1 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /frontend/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EmulatorJS", 3 | "short_name": "EmulatorJS", 4 | "manifest_version": 2, 5 | "version": "1.9.2", 6 | "display": "standalone", 7 | "background_color": "#000000", 8 | "theme_color": "#000000", 9 | "icons": [ 10 | { 11 | "src": "icon.png", 12 | "type": "image/png", 13 | "sizes": "500x500" 14 | } 15 | ], 16 | "start_url": "/" 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emulatorjs", 3 | "version": "1.9.2", 4 | "description": "Rom and art management utility for generating configuration files for use with the EmulatorJS frontend", 5 | "main": "index.js", 6 | "author": "thelamer", 7 | "dependencies": { 8 | "cloudcmd": "^15.9.14", 9 | "deepmerge": "^4.2.2", 10 | "express": "^4.17.2", 11 | "ipfs-http-client": "51.0.0", 12 | "jszip": "^3.7.1", 13 | "socket.io": "^4.4.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/call_issue_pr_tracker.yml: -------------------------------------------------------------------------------- 1 | name: Issue & PR Tracker 2 | 3 | on: 4 | issues: 5 | types: [opened,reopened,labeled,unlabeled,closed] 6 | pull_request_target: 7 | types: [opened,reopened,review_requested,review_request_removed,labeled,unlabeled,closed] 8 | pull_request_review: 9 | types: [submitted,edited,dismissed] 10 | 11 | jobs: 12 | manage-project: 13 | permissions: 14 | issues: write 15 | uses: linuxserver/github-workflows/.github/workflows/issue-pr-tracker.yml@v1 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /setup_dev.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | IFS=$'\n' 4 | rootdir=$(pwd) 5 | # Install modules 6 | npm install 7 | ## Grab frontend blobs 8 | # EmulatorJS 9 | curl -o \ 10 | /tmp/emulatorjs-blob.tar.gz -L \ 11 | "https://github.com/thelamer/emulatorjs/archive/main.tar.gz" 12 | tar xf \ 13 | /tmp/emulatorjs-blob.tar.gz -C \ 14 | frontend/ --strip-components=1 15 | # Custom cores 16 | curl -o \ 17 | /tmp/custom-cores.tar.gz -L \ 18 | "https://github.com/linuxserver/libretro-cores/archive/master.tar.gz" && \ 19 | tar xf \ 20 | /tmp/custom-cores.tar.gz -C \ 21 | frontend/ --strip-components=1 22 | rm frontend/README.md 23 | # Default folders 24 | if [ ! -e 'fontend/user' ]; then 25 | if [ -d '/data' ]; then 26 | ln -s /data frontend/user 27 | else 28 | mkdir -p frontend/user 29 | fi 30 | fi 31 | -------------------------------------------------------------------------------- /frontend/css/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /metadata/doom.json: -------------------------------------------------------------------------------- 1 | { 2 | "2C8212631B37F21AD06D18B5638C733A75E179FF ": { 3 | "logo": "QmSNo8VrwLg1NRWAMhm2qc3zWFF7HK8563sAeU4Hu6DQ8u", 4 | "name": "Doom", 5 | "vid": "QmTuyvkgZ7H4kxLN5ws7k71uGsa2pc2rEBKCHTJY6mzvTs" 6 | }, 7 | "7EC7652FCFCE8DDC6E801839291F0E28EF1D5AE7": { 8 | "logo": "QmPZaGnVHshwHropCW95HtNTUQSDUbfK9vFa2oQjVqcVkZ", 9 | "name": "Doom II", 10 | "vid": "Qmamv4bz1HVQWHRKbrbEWPUuAtUEhEVXJxqDYz2ui4gCaD" 11 | }, 12 | "90361E2A538D2388506657252AE41ACEEB1BA360": { 13 | "logo": "QmQEN7NGWPw4YbvS49RoQBgKNbRT9V3EynijAFcxAiJKCE", 14 | "name": "Doom The Plutonia Experiment" 15 | }, 16 | "9B07B02AB3C275A6A7570C3F73CC20D63A0E3833": { 17 | "logo": "QmNyBkiShukMmib9qndrZbovqX9ycdQE3UzZSLHSFtXdU7", 18 | "name": "The Ultimate Doom" 19 | }, 20 | "9FBC66AEDEF7FE3BAE0986CDB9323D2B8DB4C9D3": { 21 | "logo": "Qma4PUdncsFtsWnFbeS1qVSyRpqsLD2J7JUaz37da2DtjQ", 22 | "name": "Doom TNT: Evilution" 23 | }, 24 | "C6612AC5A8AC2E2A1D707F9B2869AF820EFB7C50": { 25 | "name": "Doom (Demo)", 26 | "ref": "2C8212631B37F21AD06D18B5638C733A75E179FF" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /frontend/css/filebrowser.css: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none; 3 | } 4 | 5 | .right { 6 | float: right; 7 | margin-right: 5px; 8 | } 9 | 10 | .directory, .file { 11 | cursor: pointer; 12 | } 13 | 14 | button { 15 | border: 2px solid #000; 16 | background: transparent; 17 | cursor: pointer; 18 | margin: 5px; 19 | } 20 | 21 | .deleteButton { 22 | margin: 0px !important; 23 | float: right; 24 | } 25 | 26 | .fileTable { 27 | font-family: Arial, Helvetica, sans-serif; 28 | border-collapse: collapse; 29 | width: 100%; 30 | margin-top: 10px; 31 | } 32 | 33 | td, th { 34 | border: 2px solid #ddd; 35 | padding: 8px; 36 | } 37 | 38 | tr:nth-child(even){ 39 | background-color: #f2f2f2; 40 | } 41 | 42 | tr:hover, button:hover { 43 | background-color: #ddd; 44 | } 45 | 46 | #dropzone { 47 | position: fixed; top: 0; left: 0; 48 | z-index: 9999999999; 49 | width: 100%; height: 100%; 50 | background-color: rgba(0,0,0,0.5); 51 | transition: visibility 175ms, opacity 175ms; 52 | } 53 | 54 | #loading { 55 | display: inline-block; 56 | width: 50px; 57 | height: 50px; 58 | border: 3px solid rgba(0,0,0,.3); 59 | border-radius: 50%; 60 | border-top-color: black; 61 | animation: spin 1s ease-in-out infinite; 62 | -webkit-animation: spin 1s ease-in-out infinite; 63 | } 64 | 65 | @keyframes spin { 66 | to { -webkit-transform: rotate(360deg); } 67 | } 68 | @-webkit-keyframes spin { 69 | to { -webkit-transform: rotate(360deg); } 70 | } 71 | -------------------------------------------------------------------------------- /frontend/css/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: black; 3 | overflow: hidden; 4 | } 5 | 6 | canvas { 7 | border: none; 8 | outline: none; 9 | } 10 | 11 | #menu { 12 | visibility: visible; 13 | position: fixed; 14 | left: 0; 15 | top: 0; 16 | width: 100vw; 17 | height: 100vh; 18 | } 19 | 20 | .page-container { 21 | height: auto; 22 | } 23 | 24 | .games-list { 25 | position:fixed; 26 | top: 5px; 27 | right: 20px; 28 | height: 100vh; 29 | } 30 | 31 | .artwork { 32 | width: auto; 33 | } 34 | 35 | .hidden { 36 | visibility: hidden; 37 | } 38 | 39 | .menu-wrap { 40 | display:flex; 41 | justify-content: center; 42 | align-items: center; 43 | height: 100%; 44 | } 45 | 46 | .menu-img { 47 | color: white; 48 | font-family: Arial, Helvetica, sans-serif; 49 | font-size: 4vh; 50 | } 51 | 52 | #background { 53 | position: fixed; 54 | left: 0; 55 | top: 0; 56 | width: 100vw; 57 | height: 100vh; 58 | } 59 | 60 | #corner { 61 | position: fixed; 62 | left: 0; 63 | bottom: 0; 64 | height: 50vh; 65 | } 66 | 67 | #game { 68 | text-align: center; 69 | color: white; 70 | } 71 | 72 | #startEmu { 73 | position: absolute; 74 | top: 50%; 75 | left: 50%; 76 | transform: translate(-50%, -50%); 77 | color: white; 78 | background: transparent; 79 | cursor: pointer; 80 | font-size: 5vw; 81 | border: 3px solid; 82 | border-radius: 1vw; 83 | padding: 1vw; 84 | } 85 | 86 | .file-button { 87 | position: absolute; 88 | top: 1vh; 89 | left: 1vw; 90 | cursor: pointer; 91 | border: none; 92 | width: 1.7vw; 93 | z-index: 999999; 94 | filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(82deg) brightness(105%) contrast(105%); 95 | } 96 | 97 | .full-main { 98 | position: absolute; 99 | top: 1vh; 100 | left: 4vw; 101 | cursor: pointer; 102 | border: none; 103 | width: 1.7vw; 104 | z-index: 999999; 105 | filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(82deg) brightness(105%) contrast(105%); 106 | } 107 | 108 | @keyframes growonce { 109 | 0% { 110 | transform: scale(.5,.5) translate(15vw,0); 111 | } 112 | 100% { 113 | transform: scale(1.0,1.0) translate(0,0); 114 | } 115 | } 116 | 117 | .grow { 118 | animation: growonce .3s forwards; 119 | } 120 | 121 | .shrink { 122 | transform: scale(.5,.5) translate(15vw,0); 123 | } 124 | 125 | @keyframes growonce-mobile { 126 | 0% { 127 | transform: scale(.5,.5); 128 | } 129 | 100% { 130 | transform: scale(1.0,1.0); 131 | } 132 | } 133 | 134 | .grow-mobile { 135 | animation: growonce-mobile .3s forwards; 136 | } 137 | 138 | .shrink-mobile { 139 | transform: scale(.5,.5); 140 | } 141 | -------------------------------------------------------------------------------- /frontend/filebrowser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 33 | 45 |

File browser:

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /public/js/vendor/theme-chrome.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass,!1)}); (function() { 2 | window.require(["ace/theme/chrome"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /frontend/css/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/css/vendor/jquery.modal.min.css: -------------------------------------------------------------------------------- 1 | .blocker{position:fixed;top:0;right:0;bottom:0;left:0;width:100%;height:100%;overflow:auto;z-index:1;padding:20px;box-sizing:border-box;background-color:#000;background-color:rgba(0,0,0,0.75);text-align:center}.blocker:before{content:"";display:inline-block;height:100%;vertical-align:middle;margin-right:-0.05em}.blocker.behind{background-color:transparent}.modal{display:none;vertical-align:middle;position:relative;z-index:2;max-width:500px;box-sizing:border-box;width:90%;background:#fff;padding:15px 30px;-webkit-border-radius:8px;-moz-border-radius:8px;-o-border-radius:8px;-ms-border-radius:8px;border-radius:8px;-webkit-box-shadow:0 0 10px #000;-moz-box-shadow:0 0 10px #000;-o-box-shadow:0 0 10px #000;-ms-box-shadow:0 0 10px #000;box-shadow:0 0 10px #000;text-align:left}.modal a.close-modal{position:absolute;top:-12.5px;right:-12.5px;display:block;width:30px;height:30px;text-indent:-9999px;background-size:contain;background-repeat:no-repeat;background-position:center center;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAAXNSR0IArs4c6QAAA3hJREFUaAXlm8+K00Acx7MiCIJH/yw+gA9g25O49SL4AO3Bp1jw5NvktC+wF88qevK4BU97EmzxUBCEolK/n5gp3W6TTJPfpNPNF37MNsl85/vN/DaTmU6PknC4K+pniqeKJ3k8UnkvDxXJzzy+q/yaxxeVHxW/FNHjgRSeKt4rFoplzaAuHHDBGR2eS9G54reirsmienDCTRt7xwsp+KAoEmt9nLaGitZxrBbPFNaGfPloGw2t4JVamSt8xYW6Dg1oCYo3Yv+rCGViV160oMkcd8SYKnYV1Nb1aEOjCe6L5ZOiLfF120EjWhuBu3YIZt1NQmujnk5F4MgOpURzLfAwOBSTmzp3fpDxuI/pabxpqOoz2r2HLAb0GMbZKlNV5/Hg9XJypguryA7lPF5KMdTZQzHjqxNPhWhzIuAruOl1eNqKEx1tSh5rfbxdw7mOxCq4qS68ZTjKS1YVvilu559vWvFHhh4rZrdyZ69Vmpgdj8fJbDZLJpNJ0uv1cnr/gjrUhQMuI+ANjyuwftQ0bbL6Erp0mM/ny8Fg4M3LtdRxgMtKl3jwmIHVxYXChFy94/Rmpa/pTbNUhstKV+4Rr8lLQ9KlUvJKLyG8yvQ2s9SBy1Jb7jV5a0yapfF6apaZLjLLcWtd4sNrmJUMHyM+1xibTjH82Zh01TNlhsrOhdKTe00uAzZQmN6+KW+sDa/JD2PSVQ873m29yf+1Q9VDzfEYlHi1G5LKBBWZbtEsHbFwb1oYDwr1ZiF/2bnCSg1OBE/pfr9/bWx26UxJL3ONPISOLKUvQza0LZUxSKyjpdTGa/vDEr25rddbMM0Q3O6Lx3rqFvU+x6UrRKQY7tyrZecmD9FODy8uLizTmilwNj0kraNcAJhOp5aGVwsAGD5VmJBrWWbJSgWT9zrzWepQF47RaGSiKfeGx6Szi3gzmX/HHbihwBser4B9UJYpFBNX4R6vTn3VQnez0SymnrHQMsRYGTr1dSk34ljRqS/EMd2pLQ8YBp3a1PLfcqCpo8gtHkZFHKkTX6fs3MY0blKnth66rKCnU0VRGu37ONrQaA4eZDFtWAu2fXj9zjFkxTBOo8F7t926gTp/83Kyzzcy2kZD6xiqxTYnHLRFm3vHiRSwNSjkz3hoIzo8lCKWUlg/YtGs7tObunDAZfpDLbfEI15zsEIY3U/x/gHHc/G1zltnAgAAAABJRU5ErkJggg==')}.modal-spinner{display:none;position:fixed;top:50%;left:50%;transform:translateY(-50%) translateX(-50%);padding:12px 16px;border-radius:5px;background-color:#111;height:20px}.modal-spinner>div{border-radius:100px;background-color:#fff;height:20px;width:2px;margin:0 1px;display:inline-block;-webkit-animation:sk-stretchdelay 1.2s infinite ease-in-out;animation:sk-stretchdelay 1.2s infinite ease-in-out}.modal-spinner .rect2{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.modal-spinner .rect3{-webkit-animation-delay:-1.0s;animation-delay:-1.0s}.modal-spinner .rect4{-webkit-animation-delay:-0.9s;animation-delay:-0.9s}@-webkit-keyframes sk-stretchdelay{0%,40%,100%{-webkit-transform:scaleY(0.5)}20%{-webkit-transform:scaleY(1.0)}}@keyframes sk-stretchdelay{0%,40%,100%{transform:scaleY(0.5);-webkit-transform:scaleY(0.5)}20%{transform:scaleY(1.0);-webkit-transform:scaleY(1.0)}} -------------------------------------------------------------------------------- /frontend/js/vendor/jquery.modal.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | A simple jQuery modal (http://github.com/kylefox/jquery-modal) 3 | Version 0.9.1 4 | */ 5 | !function(o){"object"==typeof module&&"object"==typeof module.exports?o(require("jquery"),window,document):o(jQuery,window,document)}(function(o,t,i,e){var s=[],l=function(){return s.length?s[s.length-1]:null},n=function(){var o,t=!1;for(o=s.length-1;o>=0;o--)s[o].$blocker&&(s[o].$blocker.toggleClass("current",!t).toggleClass("behind",t),t=!0)};o.modal=function(t,i){var e,n;if(this.$body=o("body"),this.options=o.extend({},o.modal.defaults,i),this.options.doFade=!isNaN(parseInt(this.options.fadeDuration,10)),this.$blocker=null,this.options.closeExisting)for(;o.modal.isActive();)o.modal.close();if(s.push(this),t.is("a"))if(n=t.attr("href"),this.anchor=t,/^#/.test(n)){if(this.$elm=o(n),1!==this.$elm.length)return null;this.$body.append(this.$elm),this.open()}else this.$elm=o("
"),this.$body.append(this.$elm),e=function(o,t){t.elm.remove()},this.showSpinner(),t.trigger(o.modal.AJAX_SEND),o.get(n).done(function(i){if(o.modal.isActive()){t.trigger(o.modal.AJAX_SUCCESS);var s=l();s.$elm.empty().append(i).on(o.modal.CLOSE,e),s.hideSpinner(),s.open(),t.trigger(o.modal.AJAX_COMPLETE)}}).fail(function(){t.trigger(o.modal.AJAX_FAIL);var i=l();i.hideSpinner(),s.pop(),t.trigger(o.modal.AJAX_COMPLETE)});else this.$elm=t,this.anchor=t,this.$body.append(this.$elm),this.open()},o.modal.prototype={constructor:o.modal,open:function(){var t=this;this.block(),this.anchor.blur(),this.options.doFade?setTimeout(function(){t.show()},this.options.fadeDuration*this.options.fadeDelay):this.show(),o(i).off("keydown.modal").on("keydown.modal",function(o){var t=l();27===o.which&&t.options.escapeClose&&t.close()}),this.options.clickClose&&this.$blocker.click(function(t){t.target===this&&o.modal.close()})},close:function(){s.pop(),this.unblock(),this.hide(),o.modal.isActive()||o(i).off("keydown.modal")},block:function(){this.$elm.trigger(o.modal.BEFORE_BLOCK,[this._ctx()]),this.$body.css("overflow","hidden"),this.$blocker=o('
').appendTo(this.$body),n(),this.options.doFade&&this.$blocker.css("opacity",0).animate({opacity:1},this.options.fadeDuration),this.$elm.trigger(o.modal.BLOCK,[this._ctx()])},unblock:function(t){!t&&this.options.doFade?this.$blocker.fadeOut(this.options.fadeDuration,this.unblock.bind(this,!0)):(this.$blocker.children().appendTo(this.$body),this.$blocker.remove(),this.$blocker=null,n(),o.modal.isActive()||this.$body.css("overflow",""))},show:function(){this.$elm.trigger(o.modal.BEFORE_OPEN,[this._ctx()]),this.options.showClose&&(this.closeButton=o(''+this.options.closeText+""),this.$elm.append(this.closeButton)),this.$elm.addClass(this.options.modalClass).appendTo(this.$blocker),this.options.doFade?this.$elm.css({opacity:0,display:"inline-block"}).animate({opacity:1},this.options.fadeDuration):this.$elm.css("display","inline-block"),this.$elm.trigger(o.modal.OPEN,[this._ctx()])},hide:function(){this.$elm.trigger(o.modal.BEFORE_CLOSE,[this._ctx()]),this.closeButton&&this.closeButton.remove();var t=this;this.options.doFade?this.$elm.fadeOut(this.options.fadeDuration,function(){t.$elm.trigger(o.modal.AFTER_CLOSE,[t._ctx()])}):this.$elm.hide(0,function(){t.$elm.trigger(o.modal.AFTER_CLOSE,[t._ctx()])}),this.$elm.trigger(o.modal.CLOSE,[this._ctx()])},showSpinner:function(){this.options.showSpinner&&(this.spinner=this.spinner||o('
').append(this.options.spinnerHtml),this.$body.append(this.spinner),this.spinner.show())},hideSpinner:function(){this.spinner&&this.spinner.remove()},_ctx:function(){return{elm:this.$elm,$elm:this.$elm,$blocker:this.$blocker,options:this.options}}},o.modal.close=function(t){if(o.modal.isActive()){t&&t.preventDefault();var i=l();return i.close(),i.$elm}},o.modal.isActive=function(){return s.length>0},o.modal.getCurrent=l,o.modal.defaults={closeExisting:!0,escapeClose:!0,clickClose:!0,closeText:"Close",closeClass:"",modalClass:"modal",blockerClass:"jquery-modal",spinnerHtml:'
',showSpinner:!0,showClose:!0,fadeDuration:null,fadeDelay:1},o.modal.BEFORE_BLOCK="modal:before-block",o.modal.BLOCK="modal:block",o.modal.BEFORE_OPEN="modal:before-open",o.modal.OPEN="modal:open",o.modal.BEFORE_CLOSE="modal:before-close",o.modal.CLOSE="modal:close",o.modal.AFTER_CLOSE="modal:after-close",o.modal.AJAX_SEND="modal:ajax:send",o.modal.AJAX_SUCCESS="modal:ajax:success",o.modal.AJAX_FAIL="modal:ajax:fail",o.modal.AJAX_COMPLETE="modal:ajax:complete",o.fn.modal=function(t){return 1===this.length&&new o.modal(this,t),this},o(i).on("click.modal",'a[rel~="modal:close"]',o.modal.close),o(i).on("click.modal",'a[rel~="modal:open"]',function(t){t.preventDefault(),o(this).modal()})}); -------------------------------------------------------------------------------- /public/js/vendor/mode-json.js: -------------------------------------------------------------------------------- 1 | define("ace/mode/json_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"variable",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)'},{token:"string",regex:'"',next:"string"},{token:"constant.numeric",regex:"0[xX][0-9a-fA-F]+\\b"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:"constant.language.boolean",regex:"(?:true|false)\\b"},{token:"text",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"comment",regex:"\\/\\/.*$"},{token:"comment.start",regex:"\\/\\*",next:"comment"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:"\\s+"}],string:[{token:"constant.language.escape",regex:/\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|["\\\/bfnrt])/},{token:"string",regex:'"|$',next:"start"},{defaultToken:"string"}],comment:[{token:"comment.end",regex:"\\*\\/",next:"start"},{defaultToken:"comment"}]}};r.inherits(s,i),t.JsonHighlightRules=s}),define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/,this.singleLineBlockCommentRe=/^\s*(\/\*).*\*\/\s*$/,this.tripleStarBlockCommentRe=/^\s*(\/\*\*\*).*\*\/\s*$/,this.startRegionRe=/^\s*(\/\*|\/\/)#?region\b/,this._getFoldWidgetBase=this.getFoldWidget,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);if(this.singleLineBlockCommentRe.test(r)&&!this.startRegionRe.test(r)&&!this.tripleStarBlockCommentRe.test(r))return"";var i=this._getFoldWidgetBase(e,t,n);return!i&&this.startRegionRe.test(r)?"start":i},this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n);if(this.startRegionRe.test(i))return this.getCommentRegionBlock(e,i,n);var s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++tf)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)},this.getCommentRegionBlock=function(e,t,n){var r=t.search(/\s*$/),s=e.getLength(),o=n,u=/^\s*(?:\/\*|\/\/|--)#?(end)?region\b/,a=1;while(++no)return new i(o,r,l,t.length)}}.call(o.prototype)}),define("ace/mode/json",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/json_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle","ace/worker/worker_client"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./json_highlight_rules").JsonHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./behaviour/cstyle").CstyleBehaviour,a=e("./folding/cstyle").FoldMode,f=e("../worker/worker_client").WorkerClient,l=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new u,this.foldingRules=new a};r.inherits(l,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new f(["ace"],"ace/mode/json_worker","JsonWorker");return t.attachToDocument(e.getDocument()),t.on("annotate",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/json"}.call(l.prototype),t.Mode=l}); (function() { 2 | window.require(["ace/mode/json"], function(m) { 3 | if (typeof module == "object" && typeof exports == "object" && module) { 4 | module.exports = m; 5 | } 6 | }); 7 | })(); 8 | -------------------------------------------------------------------------------- /profile.js: -------------------------------------------------------------------------------- 1 | // NPM modules 2 | var crypto = require('crypto'); 3 | var home = require('os').homedir(); 4 | var express = require('express'); 5 | var app = express(); 6 | var fs = require('fs'); 7 | var fsw = require('fs').promises; 8 | var path = require('path'); 9 | var JSZip = require('jszip'); 10 | 11 | // Default vars 12 | var error = {status: 'error'}; 13 | app.use(express.json({ limit: '500MB' })); 14 | 15 | // Catch all to detect endpoint 16 | app.get('/*', function(req, res) { 17 | res.send('pong'); 18 | }); 19 | 20 | // Catch all for any post 21 | app.post('/*', async function(req, res) { 22 | try { 23 | let type = req.body.type; 24 | // Send default profile unauthenticated 25 | if (type == 'default') { 26 | try { 27 | let profilePath = home + '/profile/default/'; 28 | let zip = new JSZip(); 29 | let items = await fs.readdirSync(profilePath); 30 | async function addToZip(item) { 31 | if (fs.lstatSync(item).isDirectory()) { 32 | let items = await fs.readdirSync(item); 33 | if (items.length > 0) { 34 | for await (let subPath of items) { 35 | await addToZip(item + '/' + subPath); 36 | } 37 | } 38 | } else { 39 | let data = fs.readFileSync(item); 40 | let zipPath = item.replace(profilePath,''); 41 | zip.file(zipPath, data); 42 | } 43 | return; 44 | } 45 | for await (let item of items) { 46 | await addToZip(profilePath + item); 47 | } 48 | zip.generateAsync({type:"base64"}).then(function callback(base64) { 49 | res.json({status: 'success',data: base64}); 50 | }); 51 | } catch (e) { 52 | console.log(e); 53 | res.json(error); 54 | } 55 | } else { 56 | let auth = req.body.user + req.body.pass; 57 | // Simple hash auth 58 | let hash = crypto.createHash('sha256').update(auth).digest('hex'); 59 | let profileJson = await fsw.readFile(home + '/profile/profile.json', 'utf8'); 60 | let profile = JSON.parse(profileJson); 61 | if (profile.hasOwnProperty(hash)) { 62 | // Return username if found 63 | if (type == 'login') { 64 | res.json({status: 'success', user: profile[hash].username}); 65 | // Take client data and write it to profile 66 | } else if (type == 'push') { 67 | let profilePath = home + '/profile/' + profile[hash].username + '/'; 68 | let baseData = req.body.data; 69 | // Purge current storage 70 | let items = await fsw.readdir(profilePath); 71 | if (items.length > 0) { 72 | for await (let item of items) { 73 | var filePath = profilePath + item; 74 | if (fs.statSync(filePath).isFile()) { 75 | await fsw.rm(filePath); 76 | } else { 77 | await fsw.rm(filePath, { recursive: true, force: true }); 78 | } 79 | } 80 | } 81 | // Load zip from data 82 | let zip = new JSZip(); 83 | zip.loadAsync(baseData, {base64: true}).then(async function(contents) { 84 | // Unzip the files to the FS by name 85 | for await (let fileName of Object.keys(contents.files)) { 86 | if (fileName.endsWith('/')) { 87 | if (! fs.existsSync(profilePath + fileName)) { 88 | await fsw.mkdir(profilePath + fileName); 89 | } 90 | } 91 | } 92 | for await (let fileName of Object.keys(contents.files)) { 93 | if (! fileName.endsWith('/')) { 94 | zip.file(fileName).async('arraybuffer').then(async function(content) { 95 | await fsw.writeFile(profilePath + fileName, Buffer.from(content)); 96 | }); 97 | } 98 | } 99 | }); 100 | res.json({status: 'success',user: profile[hash].username}); 101 | // Send client data to write to indexedDB 102 | } else if (type == 'pull') { 103 | try { 104 | let profilePath = home + '/profile/' + profile[hash].username + '/'; 105 | let zip = new JSZip(); 106 | let items = await fs.readdirSync(profilePath); 107 | async function addToZip(item) { 108 | if (fs.lstatSync(item).isDirectory()) { 109 | let items = await fs.readdirSync(item); 110 | if (items.length > 0) { 111 | for await (let subPath of items) { 112 | await addToZip(item + '/' + subPath); 113 | } 114 | } 115 | } else { 116 | let data = fs.readFileSync(item); 117 | let zipPath = item.replace(profilePath,''); 118 | zip.file(zipPath, data); 119 | } 120 | return; 121 | } 122 | for await (let item of items) { 123 | await addToZip(profilePath + item); 124 | } 125 | zip.generateAsync({type:"base64"}).then(function callback(base64) { 126 | res.json({status: 'success',data: base64}); 127 | }); 128 | } catch (e) { 129 | console.log(e); 130 | res.json(error); 131 | } 132 | } else { 133 | res.json(error); 134 | } 135 | } else { 136 | res.json(error); 137 | } 138 | } 139 | } catch (e) { 140 | console.log(e) 141 | res.json(error); 142 | } 143 | }); 144 | 145 | app.listen(3001); 146 | -------------------------------------------------------------------------------- /public/css/index.css: -------------------------------------------------------------------------------- 1 | /* Main page layout not mobile compatible */ 2 | #side { 3 | position:fixed; 4 | width: 15vw; 5 | top: 40px; 6 | left: 0; 7 | height:calc(100vh - 40px); 8 | overflow: auto; 9 | } 10 | 11 | #main { 12 | position:fixed; 13 | width: 85vw; 14 | top: 40px; 15 | right: 0; 16 | height:calc(100vh - 40px); 17 | overflow: auto; 18 | } 19 | 20 | .top { 21 | position:fixed; 22 | width: 100vw; 23 | height: 40px; 24 | top: 0; 25 | left: 0; 26 | border-bottom: 2px solid #000; 27 | } 28 | 29 | .nav { 30 | display: flex; 31 | float: left; 32 | } 33 | 34 | .nav-buttons { 35 | display: flex; 36 | float: right; 37 | padding: 8px; 38 | } 39 | 40 | .top-item { 41 | cursor: pointer; 42 | padding: 10px; 43 | border-right: 2px solid #000; 44 | } 45 | 46 | .top-item:hover { 47 | background-color: #D3D3D3; 48 | } 49 | 50 | .readme { 51 | padding: 10px 52 | } 53 | 54 | .centered { 55 | text-align: center; 56 | } 57 | 58 | /* Roms page 2 column layout */ 59 | h3 { 60 | margin-top: .25em; 61 | margin-bottom: .25em; 62 | } 63 | 64 | #right { 65 | grid-area: unidentified; 66 | } 67 | 68 | #left { 69 | grid-area: identified; 70 | } 71 | 72 | .wrapper { 73 | display: grid; 74 | grid-template-columns: 49.5% 49.5%; 75 | grid-template-areas: 76 | "identified unidentified"; 77 | } 78 | 79 | .wrapper { 80 | margin: 0; 81 | } 82 | 83 | .wrapper p { 84 | margin: 0; 85 | } 86 | 87 | .itemwrapper { 88 | display: table; 89 | border: 1px solid black; 90 | width: 100%; 91 | } 92 | 93 | .itemwrapper + .itemwrapper { 94 | border-top: 0; 95 | } 96 | 97 | .item { 98 | width: 100%; 99 | display: table-cell; 100 | } 101 | 102 | .itemwrapper:hover { 103 | opacity: .8; 104 | cursor: pointer; 105 | } 106 | 107 | .identifybutton { 108 | padding: 0; 109 | margin-left: 20px; 110 | } 111 | 112 | /* Hidden elements */ 113 | .hidden { 114 | display: none; 115 | } 116 | 117 | /* Json editor */ 118 | #editor { 119 | width: 100%; 120 | height: 100%; 121 | } 122 | 123 | /* Button/Links hover effect */ 124 | .hover:hover { 125 | background-color: #D3D3D3; 126 | } 127 | 128 | /* Rom folder cards */ 129 | .card { 130 | box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); 131 | border: 1px solid #000; 132 | transition: 0.3s; 133 | width: 130px; 134 | text-align: center; 135 | margin: 5px; 136 | padding-bottom: 5px; 137 | } 138 | 139 | .card p { 140 | margin: 1px; 141 | } 142 | 143 | .card h2 { 144 | margin: 5px; 145 | } 146 | 147 | .scanbutton { 148 | width: 80px; 149 | height: 40px; 150 | border: 2px solid #000; 151 | background: transparent; 152 | cursor: pointer; 153 | margin: 5px; 154 | } 155 | 156 | .card:hover { 157 | box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); 158 | } 159 | 160 | .cardcontainer { 161 | display: flex; 162 | flex-flow: row wrap; 163 | } 164 | 165 | /* General purpose buttons */ 166 | 167 | .button { 168 | border: 2px solid #000; 169 | background: transparent; 170 | cursor: pointer; 171 | margin: 5px; 172 | } 173 | 174 | /* Side menu table */ 175 | 176 | .sideitem { 177 | width:calc("100% - 4px"); 178 | height: 30px; 179 | line-height: 30px; 180 | border: 2px solid #000; 181 | border-top: 0; 182 | background: transparent; 183 | cursor: pointer; 184 | text-align: center; 185 | } 186 | 187 | /* File browsers */ 188 | .browser { 189 | position:fixed; 190 | width: 85vw; 191 | top: 40px; 192 | right: 0; 193 | height:calc(100vh - 40px); 194 | } 195 | 196 | /* CSS loading indicator */ 197 | .loader, 198 | .loader:before, 199 | .loader:after { 200 | background: #000000; 201 | -webkit-animation: load1 1s infinite ease-in-out; 202 | animation: load1 1s infinite ease-in-out; 203 | width: 1em; 204 | height: 4em; 205 | } 206 | 207 | .loader { 208 | color: #000000; 209 | text-indent: -9999em; 210 | margin: 88px auto; 211 | position: relative; 212 | font-size: 11px; 213 | -webkit-transform: translateZ(0); 214 | -ms-transform: translateZ(0); 215 | transform: translateZ(0); 216 | -webkit-animation-delay: -0.16s; 217 | animation-delay: -0.16s; 218 | } 219 | 220 | .loader:before, 221 | .loader:after { 222 | position: absolute; 223 | top: 0; 224 | content: ''; 225 | } 226 | 227 | .loader:before { 228 | left: -1.5em; 229 | -webkit-animation-delay: -0.32s; 230 | animation-delay: -0.32s; 231 | } 232 | 233 | .loader:after { 234 | left: 1.5em; 235 | } 236 | 237 | @-webkit-keyframes load1 { 238 | 0%, 239 | 80%, 240 | 100% { 241 | box-shadow: 0 0; 242 | height: 4em; 243 | } 244 | 40% { 245 | box-shadow: 0 -2em; 246 | height: 5em; 247 | } 248 | } 249 | 250 | @keyframes load1 { 251 | 0%, 252 | 80%, 253 | 100% { 254 | box-shadow: 0 0; 255 | height: 4em; 256 | } 257 | 40% { 258 | box-shadow: 0 -2em; 259 | height: 5em; 260 | } 261 | } 262 | 263 | #close { 264 | position: absolute; 265 | background: black; 266 | top: 0; 267 | right: 0; 268 | cursor: pointer; 269 | width: 20px; 270 | height: 20px; 271 | z-index: 3; 272 | } 273 | 274 | #modal { 275 | display: none; 276 | margin: auto; 277 | position: absolute; 278 | width: 70vw; 279 | height: 90vh; 280 | top: 0; left: 0; bottom: 0; right: 0; 281 | background: white; 282 | z-index: 2; 283 | border: 1px solid black; 284 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5); 285 | overflow: clip; 286 | } 287 | 288 | #modal-content p { 289 | margin: 0; 290 | } 291 | 292 | #preview-iframe { 293 | position: absolute; 294 | bottom: 0; 295 | left: 0; 296 | width: 100%; 297 | height: 60%; 298 | border: none; 299 | } 300 | 301 | #rom-manage { 302 | position: absolute; 303 | top: 0; 304 | left: 0; 305 | width: 100%; 306 | height: 40%; 307 | } 308 | 309 | #rom-manage p { 310 | margin: 3px; 311 | margin-left: 5px; 312 | } 313 | 314 | #manage-buttons { 315 | position: absolute; 316 | width: 100%; 317 | height 30%; 318 | bottom: 0; 319 | left: 0; 320 | justify-content: center; 321 | display: flex; 322 | } 323 | 324 | .manage-button { 325 | border: 2px solid #000; 326 | background: transparent; 327 | cursor: pointer; 328 | padding: 3%; 329 | top: 50%; 330 | transform: translateY(-50%); 331 | margin-left: 2px; 332 | margin-right: 2px; 333 | } 334 | 335 | .manage-button:hover { 336 | background-color: #D3D3D3; 337 | } 338 | 339 | #vidPos { 340 | width: 300px; 341 | } 342 | -------------------------------------------------------------------------------- /metadata/vb.json: -------------------------------------------------------------------------------- 1 | { 2 | "0E086D7EF2BD8B97315196943FD0B71DA07AA8F1": { 3 | "logo": "QmZPPnoAR8NcAps63MrRsxN3A9kej6ghDunoRg77fF4ZvA", 4 | "name": "Jack Bros. (USA)", 5 | "vid": "QmfYBbFiGsguYMxDoJZQGyzYCcDMhqvLynivFXj9QPgrbp" 6 | }, 7 | "1196EF8B9A96A0FF2F3634F8EB1C4865C0FB6B01": { 8 | "name": "Bound High (Japan) (En) (Proto)" 9 | }, 10 | "16AD904FAAFD24642DFD056D10A768E81F0D9BFA": { 11 | "logo": "QmUpBKPjXguAkS69F1ZQRoX5D3kRznYoEVYF8bFqLpBA1R", 12 | "name": "Space Invaders - Virtual Collection (Japan)", 13 | "vid": "QmUe2GCnx99s9DZxaQtHmtNrzRJ7YvHGi1NuqiAtDf46cj" 14 | }, 15 | "1A67F6BDB45DB46E22B8AADEA754A68AD379AE29": { 16 | "logo": "QmPoFytQpiAoJ9nW4ZrjjJMVdAcZyME7SRRVDSnK5dMkPc", 17 | "name": "SD Gundam - Dimension War (Japan)", 18 | "vid": "QmR2dDWQ3GrT842baDxNQHP6N6wmfheZGZ2fqYWHRK2yYF" 19 | }, 20 | "217CA062F65D4E8B2848085918AF5AC4B33B12D4": { 21 | "name": "Niko-chan Battle (Japan) (Proto)" 22 | }, 23 | "23CE3C174789CDD306497D86CB2C4E76BA8B06E5": { 24 | "logo": "Qmdwyb2mL1zDo1BMnNVFTGBgsrzZZjmjWeop9pcfP88zhK", 25 | "name": "Golf (USA)", 26 | "vid": "QmVvzYoiK3xesgZveFqRhmQ14v11jcdAsTQj9rTf5DyW7v" 27 | }, 28 | "266FE615EE3DF1160A20B456824699F16810FA28": { 29 | "logo": "QmXNUHwFo4822jWfXpwutWJTsdLu5JasjiAT1aPNkvqT56", 30 | "name": "Virtual League Baseball (USA)", 31 | "vid": "QmZXfBoZfHppqtL4XwrPLALomir7ZsdQ7GehvkB6y2TwRP" 32 | }, 33 | "274C328FBD904F20E69172AB826BF8F94CED1BDB": { 34 | "logo": "QmNvnaDejYEdsowxN8KZaj6qdj4vqVv8btBy4pSxdyJHNW", 35 | "name": "Virtual Boy Wario Land (Japan, USA)", 36 | "vid": "QmYxxf3mjbPhA8JKkn8yZh73LC1UHCdWrtviMvjTg8VqRe" 37 | }, 38 | "322DC4C2DF74BF19D8A30D43C489401BE5E76CA5": { 39 | "name": "Hyper Fighting (World) (Beta) (Aftermarket) (Unl) (Pirate)", 40 | "ref": "578355DE83822CE67522033DD7545631124A71AB" 41 | }, 42 | "37E06E53CE2810E870835C48DECDD6C72079BFDA": { 43 | "logo": "QmV2hr6f1Sf2ZkfmtjYXDPTRjXyQ3qa3XvmxHqmYcUnLMP", 44 | "name": "Innsmouth no Yakata (Japan)", 45 | "vid": "QmS7VL589BMEasXVM3tBDwfEjqUpcATcMP4GN4RMauh5yw" 46 | }, 47 | "38F9008F60B09DEEF1151D46B905B090A0338200": { 48 | "logo": "QmSYrvARB5C2QFF1MjTjGnrr3Vwh42iXXd9uXFKbYHF1tm", 49 | "name": "Vertical Force (USA)", 50 | "vid": "QmPpDpBSRP8UZidJLsJiRaWoFXY1M5WKnU2ZApzSzshwJB" 51 | }, 52 | "3F08CAE2B6F9E386529949FF954220860DB1AF64": { 53 | "name": "Space Squash (Japan)" 54 | }, 55 | "494FA60CB8997880B4E8CD3270B21D6C2768A48A": { 56 | "logo": "Qmcz6tW9nEPT5Vp4KYvBw5nspXfo6M5e2WtSuKoThEsZiE", 57 | "name": "Red Alarm (USA)", 58 | "vid": "QmRidMNd9q92SawBCir2rW6EJsjm7SRjUpgkXmUTUCBGCV" 59 | }, 60 | "5162F7FA9E3EAE4338C53CCBA641BA080C768754": { 61 | "logo": "QmeeJjkBUR8vXjvxo8xwixChLpeNShxiWT9wjeaafNu54x", 62 | "name": "Mario's Tennis (Japan, USA)", 63 | "vid": "QmV46x6EkJUYw69Li1KC2yGuDWKcoPVRAvXoUZGfr58Sjq" 64 | }, 65 | "5177015A91442E56BD76AF39447BCA365E06C272": { 66 | "logo": "QmYB88E8a8SfCrFhJ7jaEkU6arv4prof96iMbKQSUrD5Ww", 67 | "name": "3-D Tetris (USA)", 68 | "vid": "QmcsARDkyozauAacURMi33H5TXAtraTWGJzPmJv2onvGQJ" 69 | }, 70 | "578355DE83822CE67522033DD7545631124A71AB": { 71 | "logo": "QmQPUUg8cHNUmbwChaYV8ejbdoo8kW3G6smRsauUVKectJ", 72 | "name": "Hyper Fighting (World) (Aftermarket) (Unl) (Pirate)", 73 | "vid": "QmfBs2QTLxhsN5LtpVxW4NUUWp9qEn5o69xXKc8Qd4cCHg" 74 | }, 75 | "583B409B7215159219D08E789DB46140062095F2": { 76 | "name": "Virtual Fishing (Japan)" 77 | }, 78 | "7556A778B60490BDB81774BCBAA7413FC84CB985": { 79 | "logo": "QmXiFK6k798oYjYPYpG4zZ3wc6YnhcmrM2Mw4XHNxXyyXr", 80 | "name": "Mario Clash (Japan, USA)", 81 | "vid": "QmXMujzzbxGLUNjVeFY9rexf8F2HuVyUR53tM3dLmHw6Up" 82 | }, 83 | "80216B2874CF162F301D571BB8AEBC98D80B4F3E": { 84 | "logo": "Qmf9kBpHUhQwzRV43ChazPudR82N3wyTDh4rZsWj2UAma2", 85 | "name": "Panic Bomber (USA)", 86 | "vid": "QmS46otKp9pMfQFz147Qdp7QRq9LF4k1wHKU7nFq9pKc3F" 87 | }, 88 | "8BB91E681959207BE068796D540120565C174D37": { 89 | "ref": "37E06E53CE2810E870835C48DECDD6C72079BFDA" 90 | }, 91 | "93A25ABF4F86E71A49C1C3A470140BB40CB693D6": { 92 | "logo": "QmfPLMFW7RHrNQbZKj6kME6fA3p1rQJvvPzCdMYVmkf5Bf", 93 | "name": "Galactic Pinball (Japan, USA)", 94 | "vid": "QmcZ6zTS3VPzSMRjTxQUkD5zeRssNosfL1BKbPvbHeRzXi" 95 | }, 96 | "A5BE7654037050F0A781E70EFEA0191F43D26F06": { 97 | "logo": "QmRMLBV9VMwafg6FEE2EioGGmAZiDvnCwcjk89aPzBbZKc", 98 | "name": "Virtual Bowling (Japan)", 99 | "vid": "Qmcys2TXepzdxM1pr3TuFybyCSqPtqErTHP8XNqiRzDHiC" 100 | }, 101 | "A973406590382EE503037271330D68D2834B79DB": { 102 | "name": "Jack Bros. no Meiro de Hiihoo! (Japan)" 103 | }, 104 | "AB8FA82B79512EEFEFCCDCCEA6768078A374C4AA": { 105 | "name": "Virtual Pro Yakyuu '95 (Japan)" 106 | }, 107 | "B8A12A9677AFA5CBCE6ED43EB0EFFB5A02875534": { 108 | "name": "Tobidase! Panibon (Japan)" 109 | }, 110 | "C595285D42C69F14B2B418C1EDFBE4A7F9A624B6": { 111 | "name": "T&E Virtual Golf (Japan)" 112 | }, 113 | "C59E020F9674774C5CBC022317EBBA0EB1D744F7": { 114 | "logo": "QmfUGi2NxBeuL2zQJfKsXxxVgYKZDkxMS4uyunb5Ssbtdg", 115 | "name": "Teleroboxer (Japan, USA)", 116 | "vid": "QmYyjbruy62ZupgBpJswyj6M41FhL6PWpx88P5TjYcWga1" 117 | }, 118 | "C7204BA5CFE7D26394B5E22BADC580C8ED8C0B37": { 119 | "name": "Vertical Force (Japan)", 120 | "ref": "38F9008F60B09DEEF1151D46B905B090A0338200" 121 | }, 122 | "D96C9F8AAC5B4EA012A8FE659BB68FB1159A9C6D": { 123 | "logo": "QmZ57YMihbyobJp947F4gKUivX5EnGcpmSn1gQiCLwSn5y", 124 | "name": "Virtual Lab (Japan)", 125 | "vid": "QmTJM6eCADPf7zacHNzchYeQ9SRaAepeqX8NmrtUX1PSAf" 126 | }, 127 | "DC182360C7C8D26323DB8921F09DB76638E81CED": { 128 | "name": "Space Pinball (Japan) (En) (Proto)" 129 | }, 130 | "DCC46484BD0ACAB0AC1EA178F425A0F5CCFB8DC2": { 131 | "logo": "QmdU2xf9SKUxijX6tWzKrGx8nTNhmPh9V1EhNkQFYXYfwJ", 132 | "name": "Waterworld (USA)", 133 | "vid": "QmXksqwSJtyVEWQScPRbiEcsDw1vZgrwjD1sbPzPzZHCmh" 134 | }, 135 | "F4A4C7928F15102CF14C90C5F044E5F7CC7C32F9": { 136 | "logo": "QmYhPDmeG8cj88j2JMyvtgRqXD6Jj5ee4g55mfRAphVeUS", 137 | "name": "Nester's Funky Bowling (USA)", 138 | "vid": "Qmd115CebnftLvbWYyiNdBXSgj45tHPqezmqgZF4EaHhkp" 139 | }, 140 | "F5057FA9BFD9D60B9DCFC004CFDD12AA8EB1CB4A": { 141 | "name": "Red Alarm (Japan)", 142 | "ref": "494FA60CB8997880B4E8CD3270B21D6C2768A48A" 143 | }, 144 | "FF7D4DFC467E6E0D3FE8517102132A65A9D17589": { 145 | "logo": "QmcBz5NZZN6UuKVDvFMjnxFfvfsCjxZvWXFjMLKJunMXqg", 146 | "name": "V-Tetris (Japan)", 147 | "vid": "QmZEKGFQkgeQzPkuShnx456pqcf94oqus3Fv2SyHzKMjiq" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EmulatorJS helper 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | ## For Users 8 | 9 | We recommend using the docker container located [here](https://github.com/linuxserver/docker-emulatorjs). This will spin up: 10 | 11 | * The nodejs backend used for managing your configuration files and rom art assets. 12 | * An NGINX webserver to serve the static frontend files. 13 | * An IPFS daemon for ingesting decentralized art asset files. 14 | 15 | For the most part most things should be point and click, hop into port 3000, click buttons, add roms, hop over to the frontend on port 80, and play. 16 | All navigation should be compatible with the up/down/left/right arrow keys on a keyboard thus working with a standard gamepad. 17 | 18 | ## For asset creators 19 | 20 | The frontend is relatively simple it displays a psuedo wheel (popular with emulation frontends like Hyperspin and Coinops) layering pngs and videos on top of that based on the currently selected menu item. The layout of these items can be seen below: 21 | 22 | ![layout](https://github.com/linuxserver/emulatorjs/raw/master/docs/layout.png) 23 | 24 | All images should be a png and run through pngquant for web optimization IE: 25 | 26 | ``` 27 | pngquant yourimage.png 28 | ``` 29 | 30 | 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) 31 | 32 | ``` 33 | ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 32 -acodec libmp3lame -qscale:a 7 output.mp4 34 | ``` 35 | 36 | If you wish to participate in improving and expanding the asset collection for games you will need to have a basic understanding of the json in the `metadata` folder of this project. Let's take a look at an example entry in Atari 2600: 37 | 38 | ``` 39 | "2A9647E27AB27E6CF82B3BF122EDF212FA34AE86": { 40 | "name": "Halo 2600 (USA) (Unl)", 41 | "vid": "QmWNCjneSTECeGueXrCMHLrs4qHpvWfV6Lee3y5PQ9UTgN", 42 | "logo": "QmfVui1hFHNEUPpdGsvfFGFpxdAG2DNHhazDFKfFKRwiEE", 43 | "back": "QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D", 44 | "corner: "QmarPeRQzXTGPY88h744vhgCu1RGJtpV9HyTtjhrQmnQhq", 45 | "video_position": "left:11.5vw;top:31.5vh;width:36vw;height:43vh;" 46 | } 47 | ``` 48 | 49 | This metadata entry links to the following files consisting of a full interface rendering: 50 | 51 | https://ipfs.infura.io/ipfs/QmWNCjneSTECeGueXrCMHLrs4qHpvWfV6Lee3y5PQ9UTgN 52 | https://ipfs.infura.io/ipfs/QmfVui1hFHNEUPpdGsvfFGFpxdAG2DNHhazDFKfFKRwiEE 53 | https://ipfs.infura.io/ipfs/QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D 54 | https://ipfs.infura.io/ipfs/QmarPeRQzXTGPY88h744vhgCu1RGJtpV9HyTtjhrQmnQhq 55 | 56 | While the `video_position` variable is used to place the video in the correct location on the screen. In general the only really "needed" art assets are the logo and video. The background, corner, and position entry can all be rendered from the default files for the emulated system in question. If you are making a highly custom screen for a particular game the video position might need to be set to something non default, but it always needs to be in the vh and vw unit format to support any size scaling the end user might have. It is impossible to assume a perfect 1080p or 16:9 aspect ratio on a users web browser. The video is rendered by determining a starting top left corner position with "left:11.5vw" being the distance from the left of the browser window, "top:31.5vh" being the distance from the top of the browser window, and "width:36vw;height:43vh" being the relative size. 57 | 58 | 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. 59 | 60 | ## For websites 61 | 62 | The libretro cores that are tested and functional are published as an easy to embed library [here](https://github.com/linuxserver/libretrojs) . 63 | 64 | ## Developing and testing assets 65 | 66 | ### Testing assets 67 | 68 | If you want to participate in contributing assets to this frontend you will need to test them locally first and know a couple things to get them properly ingested into the P2P ecosystem. 69 | 70 | To test your local pngs and mp4s, first place the files in the system directories for the game in question. We will continue with the example of "Halo 2600 (USA)", the file structure would look like: 71 | 72 | ``` 73 | /data/atari2600/roms/Halo 2600 (USA).zip 74 | /data/atari2600/backgrounds/Halo 2600 (USA).png 75 | /data/atari2600/logos/Halo 2600 (USA).png 76 | /data/atari2600/corners/Halo 2600 (USA).png 77 | /data/atari2600/videos/Halo 2600 (USA).mp4 78 | ``` 79 | 80 | Then edit the corresponding config file for the system, in this case atari2600: 81 | 82 | ``` 83 | /data/config/atari2600.json 84 | ``` 85 | 86 | And the actual game entry: 87 | 88 | ``` 89 | { 90 | "title": "Atari 2600", 91 | "root": "atari2600", 92 | "parent": "main", 93 | "display_items": 1, 94 | "defaults": { 95 | "emulator": "libretro-stella2014", 96 | "bios": "", 97 | "path": "atari2600", 98 | "rom_extension": ".zip", 99 | "video_position": "left:11.5vw;top:31.5vh;width:36vw;height:43vh;", 100 | "type": "game", 101 | "has_back": false, 102 | "has_corner": false, 103 | "has_logo": true, 104 | "has_video": true, 105 | "multi_disc": 0 106 | }, 107 | "items": { 108 | "Halo 2600 (USA)": { 109 | "video_position": "left:11.5vw;top:31.5vh;width:40vw;height:50vh;", 110 | "has_back": true, 111 | "has_corner": true 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | In this config we are overriding the default video position, but any of the "defaults" entries can be overridden on a per game basis. This is also useful for mixing menus and games in the same interface. 118 | We are also overriding the back and corner settings to tell the frontend to stop using the "default.png" for the system and pull by the game's name. 119 | 120 | ### Pushing assets for network ingestion 121 | 122 | So you have the menu entry setup with new art assets locally. In order to push changes to this repository you will first need to get your files into IPFS, probably the easiest way to achieve that is with this bash command: 123 | 124 | ``` 125 | curl "https://ipfs.infura.io:5001/api/v0/add?pin=true" \ 126 | -X POST -H "Content-Type: multipart/form-data" \ 127 | -F "file=@\"/data/atari2600/backgrounds/Halo 2600 (USA).png\"" 128 | ``` 129 | 130 | Which should return something like: 131 | 132 | ``` 133 | {"Name":"Halo 2600 (USA).png","Hash":"QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D","Size":"127764"} 134 | ``` 135 | 136 | `QmSXacVTbftY1vsoK9RDJ47Jz2yeJB18td3rtTPmpfTw7D` is the IPFS cid you need to submit as new metadata in the `metadata/atari2600.json` file of this repository for the asset you uploaded. Once added to the metadata this art will be downloaded and config setup just as you do locally for users scanning in their own roms. 137 | 138 | # Development 139 | 140 | Simply run `./setup_dev.sh` and use node or nodemon to start the application with `nodemon index.js`. 141 | 142 | **You will need a local IPFS node running to use any of the download features of the application** 143 | 144 | The application can be accesed at: 145 | * Backend - http://localhost:3000 146 | * Frontend - http://localhost:3000/frontend/index.html 147 | 148 | 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. 149 | -------------------------------------------------------------------------------- /has_files.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | set -o pipefail 4 | rom_path=$1 5 | rom_type=$2 6 | full_scan=$3 7 | # Set user folder 8 | if [ -d '/data' ]; then 9 | user_folder='/data' 10 | else 11 | user_folder='frontend/user' 12 | fi 13 | check_files=$(find "${user_folder}${rom_path}" -maxdepth 1 -not -type d -and -not -name '.*' -exec basename {} \; | sort) 14 | 15 | # Clear out old hashes 16 | if [ "${full_scan}" = "true" ]; then 17 | if [ -d "${user_folder}/hashes/${rom_path}" ]; then 18 | rm -Rf "${user_folder}/hashes/${rom_path}" 19 | fi 20 | fi 21 | 22 | # Clean up for bad scans 23 | rm -Rf "${user_folder}/hashes/${rom_path}/tmp/" 24 | 25 | # Process zip file hashes 26 | process_zip () { 27 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp" 28 | echo "unzipping ${file}" 29 | unzip -j -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp" 30 | rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || : 31 | echo "hashing ${file}" 32 | firstfile=( "${user_folder}/hashes/${rom_path}/tmp/"* ) 33 | sum=$(sha1sum "$firstfile" | awk '{print $1;exit}') 34 | rm -R "${user_folder}/hashes/${rom_path}/tmp/" 35 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 36 | } 37 | 38 | # Process 7zip file hashes 39 | process_7z () { 40 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp" 41 | echo "unzipping ${file}" 42 | 7z e "${user_folder}${rom_path}/${file}" -o"${user_folder}/hashes/${rom_path}/tmp" 43 | rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || : 44 | find "${user_folder}/hashes/${rom_path}/tmp/" -empty -type d -delete 45 | echo "hashing ${file}" 46 | firstfile=( "${user_folder}/hashes/${rom_path}/tmp/"* ) 47 | sum=$(sha1sum "$firstfile" | awk '{print $1;exit}') 48 | rm -R "${user_folder}/hashes/${rom_path}/tmp/" 49 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 50 | } 51 | 52 | # Just hash the file 53 | just_hash () { 54 | mkdir -p "${user_folder}/hashes/${rom_path}" 55 | echo "hashing ${file}" 56 | sum=$(sha1sum "${user_folder}/${rom_path}/${file}" | awk '{print $1;exit}') 57 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 58 | } 59 | 60 | # For NES roms strip headers to get raw sha1 61 | process_nes () { 62 | if [ $file_type == 'zip' ]; then 63 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp" 64 | echo "unzipping ${file}" 65 | unzip -j -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp" 66 | rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || : 67 | file_to_sha="${user_folder}/hashes/${rom_path}/tmp/"* 68 | elif [ $file_type == 'x-7z-compressed' ]; then 69 | echo "unzipping ${file}" 70 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp" 71 | echo "unzipping ${file}" 72 | 7z x "${user_folder}${rom_path}/${file}" -o"${user_folder}/hashes/${rom_path}/tmp" 73 | rm "${user_folder}/hashes/${rom_path}/tmp/"*.{txt,nfo,xml,readme,README} &> /dev/null || : 74 | file_to_sha="${user_folder}/hashes/${rom_path}/tmp/"* 75 | else 76 | file_to_sha="${user_folder}/${rom_path}/${file}" 77 | fi 78 | echo "hashing ${file}" 79 | sum=$(NES20Tool -operation rominfo -rom-file ${file_to_sha} |awk '/ROM SHA1/ {print $3;exit}' || sha1sum ${file_to_sha} | awk '{print $1;exit}') 80 | wait 81 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 82 | if [ -d "${user_folder}/hashes/${rom_path}/tmp" ]; then 83 | rm -R "${user_folder}/hashes/${rom_path}/tmp" 84 | fi 85 | } 86 | 87 | # Use the file name for a key for arcade games and pceCD 88 | process_name () { 89 | printf "${file%.*}" > "${user_folder}/hashes/${rom_path}/${file}.sha1" 90 | } 91 | 92 | process_chd () { 93 | echo "processing ${file}" 94 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp" 95 | chdman extractcd -i "${user_folder}${rom_path}/${file}" -o "${user_folder}/hashes/${rom_path}/tmp/FILE.cue" 96 | # Check if file has a track 2 97 | if grep -q "TRACK 02" "${user_folder}/hashes/${rom_path}/tmp/FILE.cue"; then 98 | echo "${file} is multi track need to split" 99 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp/split" 100 | NOSPLIT=false 101 | binmerge -s "${user_folder}/hashes/${rom_path}/tmp/FILE.cue" FILE -o "${user_folder}/hashes/${rom_path}/tmp/split" || NOSPLIT=true 102 | if [ "${NOSPLIT}" == "false" ]; then 103 | echo "hashing ${file} (Track 1)" 104 | if [ -f "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 1).bin" ]; then 105 | sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 1).bin" | awk '{print $1;exit}') 106 | elif [ -f "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 01).bin" ];then 107 | sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/split/FILE (Track 01).bin" | awk '{print $1;exit}') 108 | fi 109 | else 110 | echo "splitting failed hashing full bin ${file}" 111 | sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/FILE.cue" | awk '{print $1;exit}') 112 | fi 113 | elif grep -q "TRACK 01" "${user_folder}/hashes/${rom_path}/tmp/FILE.cue"; then 114 | echo "hashing ${file}" 115 | sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/FILE.bin" | awk '{print $1;exit}') 116 | fi 117 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 118 | if [ -d "${user_folder}/hashes/${rom_path}/tmp" ]; then 119 | rm -R "${user_folder}/hashes/${rom_path}/tmp" 120 | fi 121 | } 122 | 123 | process_bin () { 124 | echo "processing ${file}" 125 | # Make sure we have a cue file 126 | cuefile=$(echo "${file}" | sed 's/.bin$/.cue/') 127 | if [ ! -f "${user_folder}/${rom_path}/${cuefile}" ]; then 128 | echo "No cue found" 129 | return 0 130 | fi 131 | # Check if file has a track 2 132 | if grep -q "TRACK 02" "${user_folder}/${rom_path}/${cuefile}"; then 133 | echo "${file} is multi track need to split" 134 | mkdir -p "${user_folder}/${rom_path}/tmp/" 135 | NOSPLIT=false 136 | binmerge -s "${user_folder}/${rom_path}/${cuefile}" FILE -o "${user_folder}/${rom_path}/tmp/" || NOSPLIT=true 137 | if [ "${NOSPLIT}" == "false" ]; then 138 | echo "hashing ${file} (Track 1)" 139 | if [ -f "${user_folder}/${rom_path}/tmp/FILE (Track 1).bin" ]; then 140 | sum=$(sha1sum "${user_folder}/${rom_path}/tmp/FILE (Track 1).bin" | awk '{print $1;exit}') 141 | elif [ -f "${user_folder}/${rom_path}/tmp/FILE (Track 01).bin" ];then 142 | sum=$(sha1sum "${user_folder}/${rom_path}/tmp/FILE (Track 01).bin" | awk '{print $1;exit}') 143 | fi 144 | else 145 | echo "splitting failed hashing full bin ${file}" 146 | sum=$(sha1sum "${user_folder}/${rom_path}/${file}" | awk '{print $1;exit}') 147 | fi 148 | elif grep -q "TRACK 01" "${user_folder}/${rom_path}/${cuefile}"; then 149 | echo "hashing ${file}" 150 | sum=$(sha1sum "${user_folder}/${rom_path}/${file}" | awk '{print $1;exit}') 151 | fi 152 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 153 | if [ -d "${user_folder}/${rom_path}/tmp" ]; then 154 | rm -R "${user_folder}/${rom_path}/tmp" 155 | fi 156 | } 157 | 158 | # Process special zip file hashes 159 | process_zip_by_name () { 160 | mkdir -p "${user_folder}/hashes/${rom_path}/tmp" 161 | echo "unzipping ${file}" 162 | unzip -j -q "${user_folder}${rom_path}/${file}" -d "${user_folder}/hashes/${rom_path}/tmp" 163 | echo "hashing ${file}" 164 | sum=$(sha1sum "${user_folder}/hashes/${rom_path}/tmp/${file%.*}."* | awk '{print $1;exit}') 165 | rm -R "${user_folder}/hashes/${rom_path}/tmp/" 166 | printf ${sum^^} > "${user_folder}/hashes/${rom_path}/${file}.sha1" 167 | } 168 | 169 | IFS=$'\n' 170 | mkdir -p "${user_folder}/hashes/${rom_path}" 171 | for file in $check_files; do 172 | if [ ! -f "${user_folder}/hashes/${rom_path}/${file}.sha1" ]; then 173 | file_extension="${file##*.}" 174 | if [ "${file_extension,,}" = 'multiwad' ]; then 175 | process_zip_by_name 176 | elif [ $rom_type == 'arcade' ] || [ $rom_type == 'segaSaturn' ] && [[ "${file_extension,,}" != @(img|cue|ccd|disk*|sub) ]]; then 177 | process_name 178 | elif [ "${file_extension,,}" = 'chd' ] && [ $rom_type == 'pce' ]; then 179 | process_name 180 | elif [ "${file_extension,,}" = 'chd' ] || ([ $rom_type == 'psx' ] && [[ "${file_extension,,}" == "disk1" ]]); then 181 | process_chd 182 | elif [ "${file_extension,,}" = 'bin' ] && [ $rom_type != '3do' ]; then 183 | process_bin 184 | elif [[ "${file_extension,,}" = @(img|cue|ccd|disk*|sub) ]]; then 185 | echo "Filetype ${file_extension} not supported" 186 | elif [ $rom_type == 'nes' ]; then 187 | file_type=$(file -b --mime-type "${user_folder}${rom_path}/${file}" | awk -F'/' '{print $2}') 188 | process_nes 189 | else 190 | file_type=$(file -b --mime-type "${user_folder}${rom_path}/${file}" | awk -F'/' '{print $2}') 191 | if [ $file_type == 'zip' ]; then 192 | process_zip 193 | elif [ $file_type == 'x-7z-compressed' ]; then 194 | process_7z 195 | else 196 | just_hash 197 | fi 198 | fi 199 | fi 200 | done 201 | -------------------------------------------------------------------------------- /metadata/vectrex.json: -------------------------------------------------------------------------------- 1 | { 2 | "0406C13952FB0BD6ADC68298AE163EBC6160484C": { 3 | "logo": "QmPM6vguoNJ2jf7ZDVLiv9ZPbTxSP7SGSgu5c5ZutkGW4L", 4 | "name": "Heads-Up - Action Soccer (USA)", 5 | "vid": "QmSeDTnrAZsZdS9X3EZGycQiWHmnCyYFhmsrbobu2oVUdt" 6 | }, 7 | "05F56E708F752CA801F8013C7DA9B0DB01508BA5": { 8 | "name": "Mail Plane (USA) (Proto) (DA1AC0DB)" 9 | }, 10 | "08E2DF9F7769C16A4D730ADF8D659F8EA4BE7F9D": { 11 | "name": "WebWars (USA)" 12 | }, 13 | "0C229DD12A55044681585F7FDABD37DBC1243B59": { 14 | "logo": "QmWy88c5QpxqpFXa36Z5hSRjRuZ5tTJsM61ypyCdePaKK2", 15 | "name": "Space Wars (World)", 16 | "vid": "QmTFQHny9TGVGTLf4iMqjp92izzxLVB7AEefntitV6gQxr" 17 | }, 18 | "1731E892DA8945FE4945B43847D23960C581682A": { 19 | "name": "3D Mine Storm (USA)" 20 | }, 21 | "1FDCC6E54AE5177BC9CDC79CE616AE3401E5C229": { 22 | "name": "Mine Storm II (USA) (Rev 2)" 23 | }, 24 | "22F7A490F6F7AAA31E892B03A85178AF211F62E3": { 25 | "logo": "QmdH43xxRiRcH1QH1eK54F6VPsMru9VFZCjyetGqhRnS2m", 26 | "name": "Rip Off (World)", 27 | "vid": "QmUkKdrEFZ6pNQXqMjvDS1xav4CZoU8NQWZCeLcSzjQoLB" 28 | }, 29 | "286E061331289B031DE33725A4966C379C1DF47F": { 30 | "logo": "QmaSxgcqfeZz3KvgTiK9uWqm6zdw25i8Zw6khLS35FzzNd", 31 | "name": "Fortress of Narzod (USA, Europe)", 32 | "vid": "QmQd5CNoL4FNqqr4Euw9kK6mNkzibGmTXZ8YeAocxMLTsX" 33 | }, 34 | "299841A116E59399AF4E74CCAE23DDFC078E007B": { 35 | "name": "Mr. Boston Clean Sweep (Europe)" 36 | }, 37 | "2E8285061530CCADFA2D8D11C374AE244449EBFA": { 38 | "logo": "QmRtXFBb1LxYizttvoTquUdnxxmMbBpDgCJbPNF1LimAXy", 39 | "name": "Spin ball (USA)", 40 | "vid": "QmdbR1gfqid1H4iSUDtLbUWL6rRFq8aW39k1DHULcAP4C6" 41 | }, 42 | "38072DC491D5C729C06F4C50BC310D361E0FF62A": { 43 | "logo": "QmRbeyGtPXFNa8Yd7pvgSHCWHQhimdSbzUcajSjuFkpoRM", 44 | "name": "Solar Quest (World)", 45 | "vid": "QmeafNyYnAWfgdavXZbdXopzgvsYjzPnhktM52ko1gUmq9" 46 | }, 47 | "38E38B5C60466146D4648F8929B5CE3A08DCBE0D": { 48 | "logo": "QmaXvh9s38tXrrdZpLJcisAdCmGKJZsAqs23uRrcGa4ZMw", 49 | "name": "Scramble (USA, Europe)", 50 | "vid": "QmQ7urxD1C6p4WzXPABtLbkokCNFQ9RZmeTXdvofRQbkA8" 51 | }, 52 | "3A2EE61316FB316D9E9EFDF5EE405E813475B466": { 53 | "logo": "QmVtydkMEKK3MNFJM9aVcppUPLSVtZ3Nrq7JM2NrDvBSYj", 54 | "name": "Star Trek - The Motion Picture (USA)", 55 | "vid": "QmX3x5Ftz7N9XEn48EdwdcXUKcankDLULGMFdhoEKSuCEA" 56 | }, 57 | "3FF3FA4FEDA6D562DEDFF2A10CFA8679D8318044": { 58 | "logo": "QmNNDtCUNmUzDH2TYkFCTojijTaUgfRVNd72XpZ1exV2fx", 59 | "name": "Bedlam (USA, Europe)", 60 | "vid": "QmXQbibFnpksG3DqupoXs9gBzexvwC3gW1zdcYrX9oSwa9" 61 | }, 62 | "4A833E4869221F832DACB7EF0FB2997C870B7D7F": { 63 | "name": "3D Crazy Coaster (USA)" 64 | }, 65 | "4B1E43808AEFC8235B130D2DBAC41FA087D9AEDF": { 66 | "logo": "QmcwcZax6zKfh1hELpA7v7vWSrRTA6P4GcbvGNWXDY2bVi", 67 | "name": "Vectrex Pong (1998) (PD)", 68 | "vid": "QmPPCkKNMtttNQx7rc6c773PvHz8CdrvKtPFbsoMYsDvph" 69 | }, 70 | "4F461B0B51C82433E70130EAC5494F26239B4453": { 71 | "logo": "QmTxYL5HLGHDJqqUAEhWZpEMAYhdAYg4CwebGy2cddJoxD", 72 | "name": "Tour De France (USA) (Proto)", 73 | "vid": "QmXQ2KdKwYS7b4zAfgWQXxUo6jhQPKjDfYrmNzS2BtL72B" 74 | }, 75 | "526B347BC02A47C8BDA326B1F6DA50284127F038": { 76 | "name": "Sledge 3D (USA) (Sample)" 77 | }, 78 | "56A7F277BF09F94461DDA542C04C36266D838B86": { 79 | "logo": "QmUzBST6yYnmLp7eTjhARck67Sa2MdH4Pzxnr8LLWBK1wG", 80 | "name": "Cosmic Chasm (World)", 81 | "vid": "QmUju26mxReyJZrjkbNWPwyeWLeKcZVGSdsvaqu49L4hVj" 82 | }, 83 | "56CEA447704286D88533DB99FD209816AE1B96A4": { 84 | "logo": "Qmf5keLWEGScQ3q8WV7BC5yYmfVLMfbHz6odtUWGQNVQfL", 85 | "name": "Star Wars Theme by Chris Salomon (1998) (PD)", 86 | "vid": "QmPSj39boR1gkrVfKh38iEKx6GpKwNTS851oCbYeFXCrQh" 87 | }, 88 | "58AECC7FB1D26A22B09E9A13D845E4ED12E2449D": { 89 | "name": "Melody Master - Music Composition and Entertainment (USA)" 90 | }, 91 | "5F00ED35F189357B4A16A190520166371CA57906": { 92 | "logo": "QmX4wEb2TkewvTSAz6L6Ezdwx8SJeWPw1j3Npmjwm9AFYR", 93 | "name": "Mine Storm (World)", 94 | "vid": "QmQfbMcJPPjnd1jihNsXxdvyWRqvBhhWFYH9yui8A2Sohf" 95 | }, 96 | "60893DFADA42CF50C94083D45AD5BD14D0C031C8": { 97 | "logo": "QmTnugssuLSRDNiRLjcV8bFLfpixsbZW8nhpfPpMHQtZmA", 98 | "name": "Blitz! - Action Football (USA, Europe) (0F11CE0C)", 99 | "vid": "QmebzT27GkPGZeMzQk4Go4MdDV8LpMZAhMgJVPASTtGejB" 100 | }, 101 | "65D07426B520DDD3115D40F255511E0FD2E20AE7": { 102 | "name": "[BIOS] GCE Vectrex (World)" 103 | }, 104 | "67F8513958C04E936B135740ED4EC6E6FA1763D5": { 105 | "logo": "QmYRFHuN1Pr6VZ1VyTSNzoCBzXSXUWNhWbsrrsTYWuGFDX", 106 | "name": "Clean Sweep (World)", 107 | "vid": "QmTvzqndwrtA8onrSazzqZ9hqpzc32z2G4UFA3BEagLmvK" 108 | }, 109 | "71946BE47DEBAE89AD191AC382C2015D6A862072": { 110 | "name": "AnimAction - Advanced Animation (USA)" 111 | }, 112 | "745DDBDC285F1DA4F7C3EB5E44FE252D503C7A15": { 113 | "name": "Wormhole by John Dondzila (2001) (PD)", 114 | "vid": "Qmd6pfnvEjTddPaZQddxNNiA3rkHQVshbtZekKqfmfTdBg" 115 | }, 116 | "7CE85DB8DD32687AD7629631AE113820371FAF7C": { 117 | "name": "RA+A Spectrum I+ Stress Tester (USA) (Proto)" 118 | }, 119 | "83A6269A9275D8CE48C58B5560FB2D7324EAEEA0": { 120 | "logo": "QmahNM3mUSdGTSHWu146L7hf5HZsYvMPRUJyQn1w7pJ2zr", 121 | "name": "Vector Vaders by John Dondzila (1996)", 122 | "vid": "QmSyQ7zP7FZEPLHQvHdUWe5YDy73NFhrpHG1vtZL8zxzjh" 123 | }, 124 | "8454D472435E7A6A4CA5FE129162480CA6E84B87": { 125 | "logo": "QmdKNUFPsB3qUDzNniq1Z5UNgCd6NkeucAdr2y7H2FmnJF", 126 | "name": "Pole Position (USA) (A00ED3D6)", 127 | "vid": "QmUSws3tEN8qcFUCcS7vegLDs4gRwsudDcD8PAE94vHkeu" 128 | }, 129 | "87248DCD3DB51C1EBF3F8E07E21EC021EEC0B9BE": { 130 | "logo": "QmRVhA3yZJbSdkD1evpnpKHutDxdwAtEpWGJwF4fDCV3JQ", 131 | "name": "Polar Rescue (USA)", 132 | "vid": "QmNmQyQSUy4kf2nbsqbQ9zTWw5VLSNCXDK3GnL8WhvNJVj" 133 | }, 134 | "8BBA6705E6F69F55AB6D105EAC2FD5B0C0D16D9F": { 135 | "logo": "QmNhbeQnhDomVvUjN4g98dNQgnpGfnHjdxhnZH2vy11WtB", 136 | "name": "Spike (USA, Europe)", 137 | "vid": "QmNP7rHCqhNAPNYAhYoSke21dEGJdmY4HdX6CgD1wX2ScZ" 138 | }, 139 | "8F35BAC5E8E0FC4AD7291BBFE5AE6E693272DE06": { 140 | "name": "Art Master (USA)" 141 | }, 142 | "9681DC5D38A31CB0D0C6C3BD0FC76BFDDE531CCE": { 143 | "logo": "QmQmenQKUtDTuzGcrXCFWQ5mddcJGnXfCdk2g2KvSikT3A", 144 | "name": "WebWarp (Europe)", 145 | "vid": "QmZ6wtHLWez5EWXpK9DLKW4Bbwn2MHAJqmzNneb93S97kn" 146 | }, 147 | "969D5EC7D7ABA9AC25E54069149D9586217AB63D": { 148 | "name": "Mail Plane (USA) (Proto) (05838962)" 149 | }, 150 | "9E5D23CB7EF111394E1804E105B6DD277C062DF6": { 151 | "name": "3D Narrow Escape (USA)" 152 | }, 153 | "9F5B0B5E6CAF6D887D3637ACE70AD6A6258B999D": { 154 | "name": "Pole Position (USA) (C10F37D8)", 155 | "ref": "8454D472435E7A6A4CA5FE129162480CA6E84B87" 156 | }, 157 | "A246F1D94A5B062B60784787A1D6D499B21C10CD": { 158 | "logo": "QmXfyfqTdJMRN5j5heiiqkiicQFyppQFPpPhNR7mEZcxLn", 159 | "name": "HyperChase - Auto Race (World)", 160 | "vid": "Qmafws3VndL7PSxxzZLx6KpLL2Nf8NxZU2NZZPDgNqnxBj" 161 | }, 162 | "AA1AA811C9CE5E19F43B78A6531CDD0E707337BD": { 163 | "name": "Blitz! - Action Football (USA, Europe) (881B27C5)", 164 | "ref": "60893DFADA42CF50C94083D45AD5BD14D0C031C8" 165 | }, 166 | "B7BD8C8003C60535E1E776758F5C289AA9A77E81": { 167 | "name": "Polar Rescue (USA) (Beta)", 168 | "ref": "87248DCD3DB51C1EBF3F8E07E21EC021EEC0B9BE" 169 | }, 170 | "C226D78301DBE40F2194C165F2124D5AFA885BE4": { 171 | "name": "Engine Analyzer (USA) (Proto)" 172 | }, 173 | "C9B79F1BA30DCBCE5A068B606A97775CF863FE0A": { 174 | "logo": "QmZTwQkihBa2VeUgHKGVAJnQ2o1x8cL7sLqtWR8AizhWBL", 175 | "name": "Star Castle (USA)", 176 | "vid": "QmUFK8JWdwMnToSanY6mKcPPCURXepY71ug4dkSgeWFkip" 177 | }, 178 | "D182EAC5A54FFC642FCBE4A495C7C09F87B43585": { 179 | "logo": "QmTnYiP8VBd3tXtJKhRpM1d3fmeeUBDTB1H3NTmav7hwNN", 180 | "name": "Star Ship (Europe)", 181 | "vid": "QmfA9z3dvG6HRtyzKgChGSLvqVFMGFGSzREwbKE9JuDim2" 182 | }, 183 | "D6B388B3951E34971C1B00C7FDF8DD00AD52DF09": { 184 | "logo": "QmZYtvLf6opgdsBMUUic88kGmdjz1iSE5prHwQvjDVYEa5", 185 | "name": "StarHawk (World)", 186 | "vid": "QmTkv8Lsq78Ht3TKP89Jjor3nR4MhT245MMDtxa3jGsBmb" 187 | }, 188 | "DBB0B378AACC2A18DFB476AA99AB00D6AB395205": { 189 | "logo": "QmdQZhuLH5nzGTSX9rw6Co6dsTMySmurQ2kWXSesmCvfYw", 190 | "name": "Dark Tower (USA) (Proto)", 191 | "vid": "QmdgHceUf86DcvZYmZTWhMZVj6WMWELC8ugFB6DgE3U4T5" 192 | }, 193 | "E3418055F1E79A2300572C557E71BF2CE8BADBFA": { 194 | "name": "Test Cartridge (USA) (Rev 4) (Proto)" 195 | }, 196 | "E4BADD494ED548721328EF869D33AD70E3E522C9": { 197 | "logo": "Qmb3D82JxKqf7KFHXGSbTR5cBojXbZYUjwRqhpV8NPUPwn", 198 | "name": "Berzerk (World)", 199 | "vid": "QmTBGNBX64jyNVhW3QmJyXWzrXMayNunhg7cZ5ixYCi5Ew" 200 | }, 201 | "EB76F860130F8CC87826FA931350467760511213": { 202 | "logo": "QmTaFSMSwyHEmbsJiQz5Fopa3pJJJFoGViVARw5fhDH3db", 203 | "name": "Armor..Attack (World)", 204 | "vid": "QmNjJ6K9gL5U3N4atFXs3FBeuaZNJKXuPMcNkU2ALVfcC8" 205 | }, 206 | "F6F26BEB20BB364761A9466FF4087C7B9713D048": { 207 | "name": "Pitcher's Duel (USA) (Proto)" 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /metadata/atari5200.json: -------------------------------------------------------------------------------- 1 | { 2 | "18697DD8E640FA1578B3B4AE09AF484C14FB8410": { 3 | "logo": "QmSJBF5SmRF17pvFzsRTSGeh1gmWsGh1HiULWUVh9G3pMe", 4 | "name": "Asteroids (USA)", 5 | "vid": "Qmc98EC4j5G5BgRLHVHNDw6se8D54v5xT48GxMRkA1iJud" 6 | }, 7 | "67E405C37E55DEC4071018E8166AB4FAF9F21DF5": { 8 | "logo": "QmURfdssjDm9P6KcxaMaLXKvBw8vzDQdZiCuBj8Ct8kXPJ", 9 | "name": "Astro Chase (USA)", 10 | "vid": "QmWnLvshgj6DBZLea8WRRyTqCNv7YXdSovTFERuCQnrm2p" 11 | }, 12 | "1DFD9CB5EB608590C84E14376291F8C02FB1AB93": { 13 | "logo": "QmaLmD1rS78PgkjFGDcBNgq8ijUxnvVJ3bu1tb2fDmnXpn", 14 | "name": "Ballblazer (USA)", 15 | "vid": "QmcR5V87KAkwhwey3xaiG9p2JYuaXMvDDEobjknmenw6oh" 16 | }, 17 | "A339CDDFC28477C21C58783D2D3B0CF0F4626DDF": { 18 | "logo": "QmdRwsaJGfSPwDdhdJ6XfDkQCb5Rep3i12Zt1Uo13rNEKM", 19 | "name": "Battlezone", 20 | "vid": "QmZ1vyBji3DLKU5ZF31txZfLApYP3wcE35gGP2trh6XKFR" 21 | }, 22 | "B75D555D059AD61992F9F5A234129ECBF9504C35": { 23 | "logo": "QmXiPjkDS2K7MwJ1fDM2AWoXNdKpfkLUnETEWUiYQKE3bv", 24 | "name": "Beamrider (USA)", 25 | "vid": "QmXyKVbo5UiMjzYZLTqq29uaSG4ooSFrbDgxsgp2njVRdq" 26 | }, 27 | "E56CCC6BF427A832CE00540B02D065A2688260DC": { 28 | "logo": "QmYnnuTWrzwQisvTizoP8KHg5aJ9yhGTTsx4wGwhLg34mK", 29 | "name": "BerZerk (USA)", 30 | "vid": "QmUsQPp9wiKET7oDpUvxVE8tsQff8JVgqJBuPrr861gbsm" 31 | }, 32 | "1E1A20C104BF4D55ECA0CA1E72DC5DC64FA6B1B5": { 33 | "logo": "QmcsCBdtDATdz8A5CTFkJ4MvzPx34RoSuh44uXh9Ptr6xi", 34 | "name": "Blue Print (USA)", 35 | "vid": "QmT4bBdaASSHyVkgRwau3KeFRtdS3E824mJTqA6EmVMNXz" 36 | }, 37 | "BC7C81F758DD3E224E42AC8A16C1A12D69AED722": { 38 | "logo": "QmbThDjtbpx22KZDE6mX3K6ttHG3HgaA4bJJoKVCFLxaTG", 39 | "name": "Buck Rogers - Planet of Zoom (USA)", 40 | "vid": "QmSL9HQHitQGqK8iK64CwUPCp1sNhPoyTXUHoTMd3W7yvS" 41 | }, 42 | "C1A56A1FC51E09B1D8E93AF40DDD7596847E6164": { 43 | "logo": "QmXi2AbqLgjot7KEk2nFqaJNCe3CGX7gjxrFRtN6GQbw7i", 44 | "name": "Castle Blast (USA) (Unl)", 45 | "vid": "Qmf8omSFm43MtM7bkZUQgnJwGooGGSeuzGQSsKm9LcY8MN" 46 | }, 47 | "4E022E101D7346A0A8618018372740E7333B4A36": { 48 | "logo": "QmUM15yrj5Bu67ZeyJJsTrJbu7ZGpbjnhWaEZHDZeiiGH1", 49 | "name": "Castle Crisis (USA) (Unl)", 50 | "vid": "QmVJNgSN5JsML5TGcgYnh3UqaUTSXeE1MYHP5VJ8VCFQJi" 51 | }, 52 | "EB343B90D3DE696E69B1F039D4FB2C6AFB0D80C0": { 53 | "logo": "QmXUQmwGzuFdi5QpL5SNmxdemp8Wjd82dnizxQTgMxPxPb", 54 | "name": "Centipede (USA)", 55 | "vid": "QmPZ5sEE8fPwCwVWtWxypvczsjoKpqhi8UZ6PCzd5CLirV" 56 | }, 57 | "9F1EE191319EF0261CC7074A81C0E6D6B7464CD2": { 58 | "logo": "QmdeFYnVUHGthGzpKBpQromfqbvQ4UWhfvUqhtRUCeSC6E", 59 | "name": "Choplifter! (USA)", 60 | "vid": "QmahcPZcQfLeq1spMGT2GyYxS1gWUivTMnH6Wm8zDApKJg" 61 | }, 62 | "E0203C2124127EBA2D679BE548FA257ECC5B2607": { 63 | "logo": "QmWpfd6F8GED6g86YZpjEVQhZZZUgzzBRWoMzAEc7w66Cs", 64 | "name": "Congo Bongo (USA)", 65 | "vid": "QmYR83qSqmoe6b52vHuCLLQjBruMzN7oJhs8uKgkrhgScD" 66 | }, 67 | "FA6D77E6E9AFD0EA42FC9F9807932BF9E8FB170E": { 68 | "logo": "QmaqFcdnztqSpjJ5Hp3ZXFfHujfpf2WFZu9vC7kpS97T2J", 69 | "name": "Countermeasure (USA)", 70 | "vid": "QmcmVTHavNERbjqhoKX6L17TsNYgnjfHBKekNz5hX5acvF" 71 | }, 72 | "DB203973DF13B071DA46D52FDB078A76F4DB428D": { 73 | "logo": "QmYYExhk1ArSCNVigKF6DAAiEQsxBYrMREALDpfeuBEP9k", 74 | "name": "Decathlon (USA)", 75 | "vid": "QmPs9sSC9UFqCY7S2Zq6SDhsGVsGmsMjqdZo1JJvWrjTYx" 76 | }, 77 | "44F6DBF5013A70E646D5C5232981D517069FC449": { 78 | "logo": "QmbBS3c5tRGGw6TegcQZrwiEBK3zEPR1Qwej6G2TexiZVm", 79 | "name": "Defender (USA)", 80 | "vid": "QmaT1ptxTbQes8Tu7C47oRCGQjwAABsGy5kEe8vx8BaRk6" 81 | }, 82 | "387A76BA5912251E2285B7722004AD75E9EDE523": { 83 | "logo": "QmUmC5paAFvbzaL4ADJ4x7mfuhwUaLicxGjHnwhC6fNVqs", 84 | "name": "Dig Dug (USA)", 85 | "vid": "QmdnXdS2Hkva6thusggxswndoVtMX6oBFGFu2pgH48L29Z" 86 | }, 87 | "EA7B40CE1106378E1D409CAB6634FB2E483FF238": { 88 | "logo": "QmRnZr6cVYDssaiyQg4dHMWeEfo14Uqr3hmDmjG6qFegDt", 89 | "name": "Dreadnaught Factor, The (USA)", 90 | "vid": "Qmc2KWinbeZtqwJthgyLPLeGoDxce9GRefuHrmfjVbuMgc" 91 | }, 92 | "3D815594CF674694B04BB0FF90BF635D7D402FC2": { 93 | "logo": "QmXahWf2Q5H9nqmT9zvbDXATnMzERa4rDMeUgVjYBYciJ9", 94 | "name": "Frogger II - Threeedeep! (USA)", 95 | "vid": "QmashLCC6yAWu9Y2NUgiwGBYrmE3bm8vSVKXqZy6SCSmtp" 96 | }, 97 | "D9A695757F0494AEE92A50AB8A49558745E0CEDF": { 98 | "logo": "QmfRb2WVd7yXBbywJJxMpAj8vYogVYgYBZa8FSGLTGfD4x", 99 | "name": "Frogger (USA)", 100 | "vid": "QmUkMmQALzoexfb1uH1pEhPP1gkNCy6JRrxn39zX28nGpy" 101 | }, 102 | "0DF0D7F11D89E2C5B59CC9F98D67D98E203831AA": { 103 | "logo": "QmQTqXAHzDhUroESmQMuyLACw4YLDc7qrEyiJxyPPufjY2", 104 | "name": "Galaxian (USA)", 105 | "vid": "QmdKPFabRNmS3u9cesVTx8DhKYfD3i382RFUi73UYxtCbS" 106 | }, 107 | "F3C32B39580D589799DCEAD98BB206FBF3DF0504": { 108 | "logo": "QmZnKTnvTsX6uh3kegW1ftpc5Zfw6stVq8EpbTigQJ42iA", 109 | "name": "Gremlins (USA)", 110 | "vid": "QmfJokqMPk28mKxhB5jfCEsV7dmKnF9KeLkAS3cepjbFMo" 111 | }, 112 | "D0D8650EB07D1679004D491360C2C041E37C61CD": { 113 | "logo": "QmVgDXYMZNB6UtMBxyX2j4Lq2BVgsm4Qh8xjYQza3f7EjU", 114 | "name": "Gyruss (USA)", 115 | "vid": "QmWyPUhSYdczNUFEptr8NKFL957rhKgPUd5DfXxsdmGVgt" 116 | }, 117 | "D286ECB6793A0DDA9C9C68F6355DBE3ADE6922F9": { 118 | "logo": "QmdJumA1Ng7XjdwM9ZtB1kBr6DEtR6ATznvKN4xnowxgjs", 119 | "name": "H.E.R.O. (USA)", 120 | "vid": "QmezV56L8QhPPYZi4n5oTq3DG5fhiZD82U8vZnT3BMp8GC" 121 | }, 122 | "AE765943AE8D909B6CA6FF1560BA027ADEEAA515": { 123 | "logo": "QmXeo5oW1HirBWVvgb8jTXod87keHLWphTG29gNUwX8Bhh", 124 | "name": "James Bond 007 (USA)", 125 | "vid": "QmQH9YwezVS5JBFsvgYPV8KzMZqimunmo8oaLDHeZ6aut8" 126 | }, 127 | "0F74125EDAC90FFD1274A128DF2970C239348FB5": { 128 | "logo": "QmWuSgH2U2NbG22F86bcDjQPW3QuLocsKtjcFWDWX1PrXW", 129 | "name": "Joust (USA)", 130 | "vid": "QmSwHpb7mzqDLzXMg43uVKTh86QrQMcueXSrLXZfscVZt1" 131 | }, 132 | "FA02EE04CFAD90C6CCF9D9B984E3B7B3327FEC12": { 133 | "logo": "QmbVZ4N7n7UWz7AHU7DczSZ643c7wsikQLwz9XdtAwfMHF", 134 | "name": "Jungle Hunt (USA)", 135 | "vid": "QmWQroeUL3EZYkGi6SgJRhtNaRdSRB37sNiKNqorExtgHT" 136 | }, 137 | "A3E760D09E4DC6053286967576B5B7D42EE2FD38": { 138 | "logo": "QmRqe5PY3wUMFHAL47ZQdoMwrwVgKGfv9GWyTMHBtC7faA", 139 | "name": "JuniorPacMan", 140 | "vid": "QmdmpG4aDxPSQ8G38AWxCW9JSb66DRaDGJRzzABzppfZY7" 141 | }, 142 | "A80F876A89A8CEACC71DA5D9C02B546970B17730": { 143 | "logo": "QmR7DDdCKUmb51ZLfgDbPfHLk6AS4bYLQGbEnHjGXhBSmo", 144 | "name": "Kaboom! (USA)", 145 | "vid": "QmeBMT6czaD6VTXyPHGbndYciTh6JoZsn3SJCJpGdFDKc3" 146 | }, 147 | "C3DBEED7ADC0E282BF76332AB5A3209550E26D02": { 148 | "logo": "QmdLoZBTM2FVMR4mXJMRajmZj6MzM1v8iAetFbED5obvo1", 149 | "name": "Kangaroo (USA)", 150 | "vid": "QmPZEobH9ykcMmdAWTGKrntLgykQjKwiWiukG2U8wMdJXj" 151 | }, 152 | "9F31C85A13E919135438590601E33BEF11A8DF8A": { 153 | "logo": "QmbksYNgsB29NSMtyP5FJ1tktPjZdAqCoc5xdo2etx8vBa", 154 | "name": "Keystone Kapers (USA)", 155 | "vid": "QmdkWAKm7ixom612e27LoqZdR5h18bNgaZfLxjwgofRBCo" 156 | }, 157 | "9547C56E2951E1B3A713B0B99D3EDB43C9D3CDD8": { 158 | "logo": "QmabvY8TyRDbEuCRFYKTu73P9UaFLrjdWHE6tc5XrneQoX", 159 | "name": "Koffi - Yellow Kopter (USA) (Unl)", 160 | "vid": "QmNYxQYQz7nNm2LxcR7mFcb12KFXZLdwvWBTGSMt4EzEjq" 161 | }, 162 | "C7EB2B4A46A197ADFEDF33C2CCC17D8424D681C4": { 163 | "logo": "QmSoroGf1gZgSEJTv7b4x5gz4R7ZTuFstTbf5JG93ScMAd", 164 | "name": "K-Razy Shoot-Out (USA)", 165 | "vid": "QmaYe4g5vrTeSb7EkX6fizB5Mjfm8qMwkv2mJ1urEVPrsQ" 166 | }, 167 | "A293B6B06050E11029D97137F2B1B36059567273": { 168 | "logo": "QmeKUDqJaispbQcSYmmZSwhbwpU7iaitT3VBhAZQm5dBKj", 169 | "name": "Mario Bros. (USA)", 170 | "vid": "QmWd5t6Q4b1ELMwLFmEqQpxVMXFnL8eH6kWcSvcynW6TUh" 171 | }, 172 | "F5512460C6B6D3A1BAA0B23ADF6A3A37FE308EB3": { 173 | "logo": "QmS6Ax6RpdG5wLNq2p7c7JnrSUps6q9NJVNZJD6zvXxsiw", 174 | "name": "MegaMania (USA)", 175 | "vid": "QmV6sV7kXUPd5eGf4oo97nwRkomNHVK8BKDBFe2Fnj8rnC" 176 | }, 177 | "32D86A381FEFF43149B66336DCC4BA2523273228": { 178 | "logo": "QmeBpap7TRpUwFRvfgb2YcgbdiEK4H3kvD4uju7oxkBu9z", 179 | "name": "Meteorites (USA)", 180 | "vid": "QmaxTgcSwkZSLY9mo6UMpz2Kxq2ck4vwMKg2AZUn7j3xWU" 181 | }, 182 | "1243A8ECFC02A74AE4E1F4FCDA38502776F45CB0": { 183 | "logo": "QmdQ9j2WTo5gjt1TmuS5WEPmzZCnyFZnZVWQGpEiNpuuoT", 184 | "name": "Millipede (USA)", 185 | "vid": "QmS9LMkk4EPSJCTs72HGq5nzVjdjagw7oQLPHwUTGCs2Tz" 186 | }, 187 | "0564B1867A0B570D66DFCBC11ADC3E51A2C6F28C": { 188 | "logo": "QmeiYfTs4NsivMGtVPCNBRzM5XEB2MMhTZjy8hmXHqvLva", 189 | "name": "Miner 2049er Starring Bounty Bob (USA)", 190 | "vid": "QmYnYpiQX5Au2bL6Y7Gu8EsnF6uUaz2RwHi68mFKq8ADcv" 191 | }, 192 | "7E2A05625598C7E6615F969AA695E64315CD9846": { 193 | "logo": "Qmc2M6T5bN7dN65GGs5QwkvyPtJMgpN1WeeFMLYZMw6Hng", 194 | "name": "Montezuma's Revenge featuring Panama Joe (USA)", 195 | "vid": "QmPgzVcooQNG4bHxUEehkn9EAdQZszuCR3RhRKsHTXxsAE" 196 | }, 197 | "02DD5B9278C06AE8CBB6DACC094E238FBACF08D2": { 198 | "logo": "QmbCgUbzYBHEC7UgwHyAMKgbbDPYtb3HPwzmS9Pubft9EQ", 199 | "name": "Moon Patrol (USA)", 200 | "vid": "QmUf8DgWXyL1eyQyHZdM1yvYzDHhep7Xs6G3YJgnvdKLkB" 201 | }, 202 | "2E7AE57260624D1F5710D445A0936F0C280A6655": { 203 | "logo": "QmY8iAcgbkK8u93bs31Ufy3r1VHPMUjuWRUBtiC8pSefQ9", 204 | "name": "Mountain King (USA)", 205 | "vid": "QmW1pFdy7shVd3hJZiVTkWZGSBe7TTKwTDZrKczSe9Lgmr" 206 | }, 207 | "002E6F8C7533B03F8041E7F1896589C0AB0E17A6": { 208 | "logo": "QmeXVArivdsCKhB6Fr4SYwvsKEPkBsxDqQYqxs3g6XZHoz", 209 | "name": "Mr. Do!'s Castle (USA)", 210 | "vid": "QmTzFpQEGbSCtB1Noxn8CjZvusaS1uN3ms8P19t3ReL7tA" 211 | }, 212 | "F93D72444D76075273E508555423936530875148": { 213 | "logo": "QmRsmAGf4MnHd9HyCRw44uq869ZySzMkZTB1KTpp4Z9Y1K", 214 | "name": "Ms. Pac-Man (USA)", 215 | "vid": "QmaZsa9y9HnvKkXoh9TiDiB5qVBCM6WYqDb13kUYNHykYy" 216 | }, 217 | "EE4B3C6F08AA9FDFAB532EEC98136306B6923F75": { 218 | "logo": "QmVGMiNFNA5BXihh62Royzo5NmWiUX4bnnCmgrq9paq7rJ", 219 | "name": "Pac-Man (USA)", 220 | "vid": "Qmd9FrnaZxie1kjaCwcrVEQwFYYV8rJ8Y1wtEUMNN1jgV5" 221 | }, 222 | "B051F0FB2E816787AB6EED0CB008B9F8CC9963DC": { 223 | "logo": "QmTHKZHB2DiJYxjayL1Udo3oF6zgazmBMgAfexxMb1Fwpv", 224 | "name": "Pitfall II - Lost Caverns (USA)", 225 | "vid": "QmWvq7C2tpimgY8yTEoTVwbUBrMSaU5micsJazYMsB3Zwn" 226 | }, 227 | "D1C7DC3DE097A4BFAB82235ACEC69A97E6E05F18": { 228 | "logo": "QmT6der1wYz5G9wWvC8BD76417RXt4zmzz3Sjk6dhyLR81", 229 | "name": "Pitfall! (USA)", 230 | "vid": "QmWnriRFBrDE2YhKUYKam31ohiU3TT6k1XBb7BZrggHtLa" 231 | }, 232 | "D504764C5894EE99C0B5E2024CBE833EBBA3AB17": { 233 | "logo": "QmeFSsfRnJrCo85pJxUzWLmKwLqP456QXMCUkA5Vog7eav", 234 | "name": "Pole Position (USA)", 235 | "vid": "QmdH7VbvPSh6kudeZNMt8emHSNiiu4EABfeMUUXu3Gx94S" 236 | }, 237 | "E615AAA0C1D56AC83B91F6087970FA708D6433BF": { 238 | "logo": "QmQEQAvhKGK9oSw7TQxvhqLDfaLgyiMAdB99RYe9XirDdy", 239 | "name": "Popeye (USA)", 240 | "vid": "QmeQJeRMbFyLMrS8kkxNvB3Ts4woTtNmYVoZQDytnRZXTk" 241 | }, 242 | "E6966BDE5CD4167020C9D21BAB613AF1648B4D75": { 243 | "logo": "QmV2U7z5Xvc92keWQss7bEJavf22G153ENhTiVM8DFrkth", 244 | "name": "Q-bert (USA)", 245 | "vid": "QmP3LK8fTjyG9ToJZugmwm7kicr8uHEFdyw1RiiqwqqrmX" 246 | }, 247 | "786CDF54D1625E0D3EA8814506FDEEBA5E4ECE48": { 248 | "logo": "QmcTdE9fG2W42AWWGanSDueyzEbFoDZADZtH8dDBt1DFR1", 249 | "name": "Qix (USA)", 250 | "vid": "QmedGY8aE5Do1N91CZw2Ki8FhTiMTq8qs8cyC5aocH4WV2" 251 | }, 252 | "A94D06739FA19453FA586A836EAACAE11FE93989": { 253 | "logo": "QmNTBNDoGMjxk7ZBe3thDhcZHPfr1K6LUK3Cv7mmq1Rdon", 254 | "name": "RealSports Baseball (USA)", 255 | "vid": "QmVi7v5sHztkdZN5exui5LpWhRXqm1hMkNHwicjNHhP3zw" 256 | }, 257 | "A0DBD654ED3BA2583CA175A1356924D9529884DF": { 258 | "logo": "QmUgfKdaMbzbrQ47ByYrpkrXKsASyaiydXonYTAEynMUMp", 259 | "name": "RealSports Football (USA)", 260 | "vid": "QmQz26rhmE1UxrxwqwyEZm6Q75amkVjHitUvVQXkTtrk8o" 261 | }, 262 | "C911CC914F3923D5AEB1A897E6DC4A551A9A53DA": { 263 | "logo": "QmWRmftRZSt9HpFQuFrKgxbQXeCyyqxV1fgwCaWBWvZDHM", 264 | "name": "RealSports Soccer (USA)", 265 | "vid": "QmNq69X2m7xWogqkqcThaGzEDhWEc6HRBNAZDPZxFMkxmP" 266 | }, 267 | "9253D7286CF87999474B37DF242BA23B4358EF4A": { 268 | "logo": "QmdggyhfUoj3kDxyykeyPQLHhKo8iAPK8qqGTfybKg6Y8N", 269 | "name": "Rescue on Fractalus! (USA)", 270 | "vid": "QmdAhDGegbXeyfGPhxUgx29TFHHxDWPDBwdCFcDcUzwbkV" 271 | }, 272 | "CE9BD3B0847C5E187C3E8F667C8F5EA771C49965": { 273 | "logo": "QmTMtgzQzdkRbucQ2UnrXLN3cnQzA9y3BcWcy85xN3Ddef", 274 | "name": "River Raid (USA)", 275 | "vid": "QmdiQJGpsQXesH6hSF1iUMcbVEPYj1gNAMP97ypH8Jg36E" 276 | }, 277 | "9175E6DACA58102AC8B9729D7B0336CF09977D12": { 278 | "logo": "Qma6EtHyQw89y6AEGTTd9oTTjfvY6JjcmfvyT5CFwAET1c", 279 | "name": "Robotron 2084 (USA)", 280 | "vid": "QmW5W8tfaE8zVGB5Dy7XyMVDd6FcbRjoYP8aHLFT561y8j" 281 | }, 282 | "127ED1C31C3E85D10212EEE7B9E6D71AEF7DD891": { 283 | "logo": "QmVFirQhANh8PAwebsPg4p622eQTUr8SUoq5tijWu88Dux", 284 | "name": "Space Dungeon (USA)", 285 | "vid": "QmQZPvKr4BWSwEKs3bNGPG3ARBZiM3uQNvgXS4x3F3jb3f" 286 | }, 287 | "CF411DB770217B5AF5FCFA32725CC587A8B9E710": { 288 | "logo": "QmbvpjyfFAdh78FncJXi4yiR5uP5r2EAjZZUsgUixbij5k", 289 | "name": "Space Invaders (USA)", 290 | "vid": "QmY86x5hrs7vCpJ2roHc6QuBiqDVLU1N8KJLr7zfGRUh4i" 291 | }, 292 | "5AE20E767D9819FE601F3B1EA6D4506AE2F2CEEF": { 293 | "logo": "QmSyML8DYf5FKoartTHUoGSaUcftHdx3LVwmspFYrTnm7q", 294 | "name": "Space Shuttle - A Journey Into Space (USA)", 295 | "vid": "QmcWizEUkRraMAvciPpsVWrUzpYq1Y8B7KfHCGJf8r13WQ" 296 | }, 297 | "09D192A3598CDADFB61ECEB430B3D5892E40962F": { 298 | "logo": "QmWte466CT5962MQqx9TbG4qKxAreP1zvwUsk7dB1jS7FR", 299 | "name": "Stargate (USA)", 300 | "vid": "QmPFnrGz7FQUootHdY5ZbLkz2hQ7tur2JUTqmfHoeaHS6r" 301 | }, 302 | "57DFC22C9805555F957ED51A2DC7BF35AD00C9D9": { 303 | "logo": "QmWVN7QL5yz4GXAjGJ15YUXtMxpVUmYVGi2ceGbfnZrdZD", 304 | "name": "Star Raiders (USA)", 305 | "vid": "QmSr6WuMCx5X52h8c2kmeNJg5fWJdTSHeuVu7QxK327vHD" 306 | }, 307 | "19801454AB86F2A5CF691FCD577A71AD2B8DC7D5": { 308 | "logo": "QmQuH8XDbmMTag4a6JMNMHtxan6itTidDdFm2K9bUc8Ye7", 309 | "name": "Star Trek - Strategic Operations Simulator (USA)", 310 | "vid": "QmUzzCnJkWb5vdV1FY3k3sUQD3u63joTYR4XVDe6zda32y" 311 | }, 312 | "BF77048ABC36BA2CBA5EEEAC812802D6A0EBA891": { 313 | "logo": "QmegpAeew8eytGwBtYgArwx1CqFYS1buxafQcs9yAiUGYE", 314 | "name": "Star Wars - Return of the Jedi - Death Star Battle (USA)", 315 | "vid": "QmTWG7Hx8fvdWixRaLKhpMhq5VWzjnzWF5b1JqN1W8C4FU" 316 | }, 317 | "AB676FFE054B23BDD43CEE2498AFEFAABBF9355B": { 318 | "logo": "QmduE8bZMQ1DLUn7VVT8MRpyZ31wBKS2NVZciH7J5j3zBd", 319 | "name": "Star Wars - The Arcade Game (USA)", 320 | "vid": "QmaDuPMh32BjZgJN6VsGca9n7jaoUXvF1tWiHpa78VkaWd" 321 | }, 322 | "8205093F5FB1FDF0CCB71D667EA0A3849FB0A0F2": { 323 | "logo": "QmdiTUqMwbuCiSpBT4XBGyVfF5HuGpdTtrMLrgYV1dwqxD", 324 | "name": "Super Cobra (USA)", 325 | "vid": "Qmb62ZEBSQhcCGjtqgtKSKH6tRhNbqZeNdQNsHk7SFwe58" 326 | }, 327 | "238CB448555D8BB5C96AA0354718411E8777C613": { 328 | "logo": "QmNUacpZwsrCNQc2MrDBj2YNyqKrq9gVoWykm5CMRm2Du3", 329 | "name": "Super Pac-Man (USA)", 330 | "vid": "Qme6UNBn1ZK1cW7scsSfBeQNcvEgRQ4A9myix6jThR5MMg" 331 | }, 332 | "4046F60602254A7D86D2A6EFD1A330455EAEC119": { 333 | "logo": "Qmbndnwr65MpH2ZKH7gYgXjihYKLSuQUfaJpuA1DUnPUjP", 334 | "name": "The Last Starfighter (USA)", 335 | "vid": "QmaG6SgHsE1ZSYjTgtK5zfoL1CzVd2rYtNfXAgTL5w55H1" 336 | }, 337 | "6C633DE50E6CC0C0C40061CD24E6F78F552A438F": { 338 | "logo": "QmRnpaubP1ek7Cemrw9v6wGHnED29CtMaQQrtmmp7yRHSq", 339 | "name": "Vanguard (USA)", 340 | "vid": "QmSAtWhrDVu9W21Ru9iUQW4ftksTRX63NSUGthCRHyJjkv" 341 | }, 342 | "49DEBDF3C133FDE9905D6D9137DA5D8DFF8D6283": { 343 | "logo": "QmdR2fqAUZAmBZMpM3DdquW463kzMhqVwPbCCS1RyWLk4U", 344 | "name": "Wizard of Wor (USA)", 345 | "vid": "Qme2stUTVaQix1sTDVujXt7vjYLbKWPiRprSjaFFFnMKhw" 346 | }, 347 | "C3CF33216DFA484309B923F21829D040486BC481": { 348 | "logo": "QmSEYo3vsjWY7QDEe84CPzRrtEdFBp6VZd4nQizXhv4CRn", 349 | "name": "Xevious", 350 | "vid": "QmP4nVrr7pQuo2Ao483fPhpTeu2wKHkCV5fPMmC1XkqyRn" 351 | }, 352 | "4A4C4A25C8ED1B0AE79CA22684D7D311A564A7B5": { 353 | "logo": "Qmc18GGCAdh1S3h5hhFtjxBeArTdNU7iwPdWDu1574APLe", 354 | "name": "Zaxxon (USA)", 355 | "vid": "QmQuxstBK3mjjoNKnyEYoxtphgFR2Xt3CybvzLb82Uyfws" 356 | }, 357 | "55DA563B4394508C23D866FB1E543262FB45536D": { 358 | "logo": "QmRfqMXY7gdGB6mzNrr8XGRWRHXKbWAJxtQ1ZmUW2qoVSs", 359 | "name": "Zenji (USA)", 360 | "vid": "QmRQrC3tXVi9KkEnLEiqCq8pYSdHr68X7oDLDs2xwTmktG" 361 | }, 362 | "18315BEC1E62102290FEEB77FB290E1266CF5BFC": { 363 | "logo": "QmVzAYCnfJAXXVJsvqvcxrvK75ZHkr4zvoGW8rQJitnSvr", 364 | "name": "Zone Ranger (USA)", 365 | "vid": "QmX6eHZqHk2adF7dz9afyedH3FioHwvw77r3pAdmt1YGfP" 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /metadata/jaguar.json: -------------------------------------------------------------------------------- 1 | { 2 | "01DC37E7A9D03246A93BCB8C6A3116CB6391ED31": { 3 | "logo": "QmfKeLsqS1SBMQFwr4dRzZLuhQdskMwEFDr9jDaCMbo4wN", 4 | "name": "Attack of the Mutant Penguins (World)", 5 | "vid": "QmUZbbybLfEngb3KCkBMJ76XK7b6Wt97zo18XV9sXEWZSk" 6 | }, 7 | "02253D93EEF375C8334F1F77A2D8B72FFF7A94C6": { 8 | "name": "Val d'Isere Skiing and Snowboarding (World)" 9 | }, 10 | "032AA31CEF116BFBA8632C14BC2FCE5BC44FF137": { 11 | "name": "Plucky Duck in Hollywood Hijinks (USA) (Proto) (1994-10-28)" 12 | }, 13 | "037396347D65D1A8B56E7DB361D737AB2070118F": { 14 | "name": "Hyper Force (World)" 15 | }, 16 | "07C76D3F4AE05B75EA7AD62147172FD146F4F876": { 17 | "name": "Thea Realm Fighters (USA) (Proto 2)" 18 | }, 19 | "0C195676BE099F4594CE1D5A38303F6CCE899DC5": { 20 | "logo": "QmRFsbJZ7WKewZsSVWy5qJA6SxJbnSQDFDsRnpdXseL44K", 21 | "name": "Cannon Fodder (World)", 22 | "vid": "Qmb8XZcpytbb8jDyW617RgCtRfBUKzxx971Fac4AUh2UUa" 23 | }, 24 | "0E95613020D1B3A0AC76D994423C6F15DDFE9703": { 25 | "name": "Missile Command VR (World) (Beta) (1995-07-05)" 26 | }, 27 | "1189AA22A427D81EA0BD68BF58258C8B1089614B": { 28 | "name": "Alien vs Predator (World) (Beta) (1994-04-08)", 29 | "ref": "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02" 30 | }, 31 | "13A4084BD5BE1CCA21D83D4C26B5B8E5CF1A3E30": { 32 | "logo": "QmVCsuGdk9mepCZiZ9iD7owQ4SKYSuQSomP7WMXvub5rfn", 33 | "name": "Defender 2000 (World)", 34 | "vid": "QmRw7miWyxxP1jJ3UdnXNhaattqf8j72nu5wiqmmPkdQZF" 35 | }, 36 | "179B511464ABCDA5A5DE9F971BD5CB5775D8C313": { 37 | "logo": "QmVZaMvFkoR1LxXRogBRMsMaG5pe71JJVzusfSkerk8aPB", 38 | "name": "Evolution - Dino Dudes (World)", 39 | "vid": "QmfGyzVC5mrtMxEv78czZmhzmphoEGKRrEMM7nPDp6FXbP" 40 | }, 41 | "18535F5985C840AEFCE0FA639131CB83A8A647F9": { 42 | "name": "Barkley Shut Up and Jam (World)" 43 | }, 44 | "1BDAAA42B38F4C93A4ABA34930E3CA406AC976C9": { 45 | "name": "Atari Karts (World) (Beta)", 46 | "ref": "9C96E6C2195CE56AA930E677A041B35BD9B8BC7E" 47 | }, 48 | "1EF1B86ABF725F32267BFA140DC049CE9BA92AEA": { 49 | "name": "Total Carnage (World) (Beta)", 50 | "ref": "4566B33799A7C65B3E9643383D0282172FF619A5" 51 | }, 52 | "20DDF412E42A50D526CBBB6411BF0EC4516DB283": { 53 | "logo": "QmZTx6wneGVU4zJTghJF7VARH89djP1RNdBUaSV8ykpMwc", 54 | "name": "Doom (World)", 55 | "vid": "QmSZgM6UhZ399co6dhiB3aGmvtq1iVr5N1SpeC792NAhUk" 56 | }, 57 | "2327112B9C6F9B0E49C55DEF0ADB8119EE39CE29": { 58 | "logo": "QmaCEAzbaV8gTJtEhwc7ewwQegMZbGjHAngC6jQfS6UF9T", 59 | "name": "Kasumi Ninja (World)", 60 | "vid": "QmV3CbiaPDF11B52Sx6DzV4d1ZQueBztnpSDfqRWn51tHz" 61 | }, 62 | "24DA4FE776AE9DAA86CF643C703EC0F399C77521": { 63 | "logo": "QmdaJGvXuh8dFDgpAdW2jXSnr4pj6e7uSXULthA8t6STKq", 64 | "name": "Theme Park (World)", 65 | "vid": "QmQJG7EmAZwZWjbZ4zL9umjpTww6pR9HdRvVgfxAZwCTn2" 66 | }, 67 | "25611B1CFF1384DB72CC5422DBE70555E5BBD32C": { 68 | "logo": "QmXFSYwvRJqkviBDVgicJu2E2rhxYmNXhDi8dRqPtoZr8a", 69 | "name": "Pitfall - The Mayan Adventure (USA, Europe)", 70 | "vid": "QmPniHfXG46CEwZcGVnw9X4MxXYJd8RRe9T5qLGwT2qMUK" 71 | }, 72 | "261667E4AAB6239ECF3A416F5C2443BF092735CC": { 73 | "name": "Alien vs Predator (World) (Beta) (1993-10-13)", 74 | "ref": "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02" 75 | }, 76 | "271E9411EB9E6C74240D254BDD7BAB9A039BC6D9": { 77 | "name": "Jack Nicklaus Cyber Golf (USA) (Proto) (1994-07-04)" 78 | }, 79 | "298148131AF2A86BA5D5430B1B8982710CE68874": { 80 | "name": "Dragon - The Bruce Lee Story (World) (Beta) (1994-09-26)", 81 | "ref": "DA7F5DAC4317AB0258F54DA436ABB8D89E530F27" 82 | }, 83 | "2AB78090E8AFD985CA4FE54DA8DAEF7E125550FA": { 84 | "name": "Virtual VCS (USA) (Proto)" 85 | }, 86 | "2F0E950517D26C83408E5CF210098C0897725537": { 87 | "name": "Trevor McFur in the Crescent Galaxy (World) (Beta)", 88 | "ref": "88E86085932AF10DFB8C94032F73A5992A30B55C" 89 | }, 90 | "311E41EBBA1B78F514BF16789DDB7E7E8DFB47FF": { 91 | "logo": "QmbKxD6MNUuRDLX8Q5Syzcwdjdfot4wjADemAf7wnD6rZw", 92 | "name": "Ultra Vortek (World)", 93 | "vid": "QmVjygYsL4ZXhhUp1V7W2FRQq6kcB8Gi8XQGfLMf1x3paU" 94 | }, 95 | "32FF3811F8F6DF8A2BB56165C62227A3A480E55C": { 96 | "logo": "QmSRcjgwAWLouMTYq6GFQQSghH28yg8fGQHwYjbj7nA2w7", 97 | "name": "Zool 2 (World)", 98 | "vid": "Qmerp8YE5HYS4MyP8jnjY2YMNJbChZr2jGbuttMsXz16F4" 99 | }, 100 | "34A9941459E3B66C836F25B3EF55303734B9583E": { 101 | "name": "Skyhammer (World)" 102 | }, 103 | "3650143BD057DD39B96E0AD0207778353F17ABE7": { 104 | "name": "Kasumi Ninja (World) (Demo)", 105 | "ref": "2327112B9C6F9B0E49C55DEF0ADB8119EE39CE29" 106 | }, 107 | "37FE344383D7991D19BF2C862D29A20EAF1DA8EF": { 108 | "name": "Fight for Life (USA, Europe) (Beta) (1995-03-22)", 109 | "ref": "9F5B8844D58BB1C174DA4824EABBFD8616BA28D2" 110 | }, 111 | "3828D2F953224D06B5FE7AB3644D6A75FDFE79E3": { 112 | "logo": "QmcVjqiqF82wvqVkucrthJwhT8BsUUowFwNLYMkCNmpjtK", 113 | "name": "Tempest 2000 (World)", 114 | "vid": "QmXvDFkX7YmDsHvUiJupbTiXEefFzqTQecktmnzFMRjkff" 115 | }, 116 | "3D3E6C7E552CBA5F99B6EBC15B951CE4BD956766": { 117 | "name": "Native (USA) (Proto)" 118 | }, 119 | "3DB485AAA28A817F468739A85E3F25741C3855AD": { 120 | "logo": "QmW7ooQuquXj9SmMRnm9jzanXbyFoiXKwkczFMr38nhUet", 121 | "name": "I-War (USA, Europe)", 122 | "vid": "QmXXvJayEvptD1RNg5RBwYYdSv2hfV4RGRnbqVbHu5twiB" 123 | }, 124 | "3EA9053A63A3B0165CCEC91E85F0E699A998DC0A": { 125 | "logo": "QmcffN7HbkieG9gXHkZ72nviP425m8xMbYh2BxCwnvLAvN", 126 | "name": "Missile Command 3D (World)", 127 | "vid": "QmZGwx2ZMDBK2XsPyrvGAujU34Fx6fqWDP85SKg8yyxLxr" 128 | }, 129 | "3EC9524921D07647F527E0A82A831F794A7D6D9B": { 130 | "name": "Phase Zero (World)" 131 | }, 132 | "4566B33799A7C65B3E9643383D0282172FF619A5": { 133 | "logo": "QmZfJ4Z8e4CNcmLgPKQAWEqjpRt6sqkdoKRkNyDtYdXq8o", 134 | "name": "Total Carnage (World)", 135 | "vid": "QmaDdW5niQbiwpN4T5CDGi9x6iFtWsVLokF8vCviKZgHf5" 136 | }, 137 | "4733CF2EDE2713467F2A80B052AB94EDF6DFE534": { 138 | "logo": "QmNmKQXYJMkmwnj6mmgPAM6rsmftpNeqrh4ATzggKKJnfo", 139 | "name": "Pinball Fantasies (World)", 140 | "vid": "QmUX4sQRATEybMWuzwogeUjUZ7vFxGnYyhncrze5MmZ9WK" 141 | }, 142 | "47B9580EB9EEA121CF080CDBDC4AFE7FB7A19AB5": { 143 | "name": "Iron Soldier 2 (USA, Europe)" 144 | }, 145 | "489B0FE9F070AD27F63A17AA8F0D2AFAA4DABDB3": { 146 | "name": "Trevor McFur in the Crescent Galaxy (World)", 147 | "ref": "88E86085932AF10DFB8C94032F73A5992A30B55C" 148 | }, 149 | "49716A6A3A4C6ABEEA36040F26C4E8CF2A545DF7": { 150 | "name": "Battle Sphere Gold (World)" 151 | }, 152 | "4C6A6432EA0BC198F900F4EBDB8A3A7A34475D84": { 153 | "logo": "QmYFg6g3WcSCA7Akoq8dMrVCR27XxUC13iEminXUFWWXJf", 154 | "name": "Troy Aikman NFL Football (World)", 155 | "vid": "QmTyfEp2q2FLfEX6gH27aufVfydfEtPfjYm2aJsp7LbgAj" 156 | }, 157 | "4D0CC8D248A8BD0FC4B8169C5D5772E59C073C59": { 158 | "logo": "QmYKfShHBmErdpVQVdWaF1bwSb6xwexpjrPrKKjRextc9t", 159 | "name": "Checkered Flag (World)", 160 | "vid": "Qma4iVerm6svScmwC83dpydmAYLQR1NUHTuNkns1JxEJRe" 161 | }, 162 | "4D16596AC2D991435599E1FE03292ADFB25C346D": { 163 | "name": "White Men Can't Jump (USA, Europe)" 164 | }, 165 | "4E55A5991725A4B557F4DAF75A0FA38EC667217A": { 166 | "name": "Aircars (USA) (Beta) (1994-11-14)" 167 | }, 168 | "4F87982B6E16072E58A5653A3F763C8B4B3BFFCC": { 169 | "name": "[BIOS] Atari Jaguar Stubulator '94 (World)" 170 | }, 171 | "50CCF7043A702531A0E9EF86D629D5EDE5255C13": { 172 | "name": "Raiden (World) (Rev A)" 173 | }, 174 | "52EC786F7536AB043DC24725AF94068CAD2B0EC3": { 175 | "name": "Zoop (USA, Europe)" 176 | }, 177 | "533C497761CC802624708108BC9EF6E7C4F40296": { 178 | "name": "Aircars (USA)" 179 | }, 180 | "542063A5B27659781A85821F61E85CD89EA59AB3": { 181 | "name": "Arena Football '95 (World)" 182 | }, 183 | "589EFBC772360FA51EDDC096A162C8D9FE7838D3": { 184 | "name": "Cybermorph (World) (Beta) (1993-08-23)", 185 | "ref": "E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E" 186 | }, 187 | "5B2295907F6FDADF5F44B1E392DCD6F5471FD095": { 188 | "name": "Frog Feast (World)" 189 | }, 190 | "5BE3DA69A64B779D064D139968FADBE1C475C715": { 191 | "logo": "QmdZbMCrPHFmjWxsdwAi9VTroT7ADXG7ZmVpF97yWjjuWq", 192 | "name": "Fever Pitch Soccer (World) (En,Fr,De,Es,It)", 193 | "vid": "QmZRt4vXXPvkKiLihkBuHgCNfnso14YPHbqhD8WsaXzw9f" 194 | }, 195 | "61D4DB841C090AE3104EB20B6087160FF80E7A12": { 196 | "ref": "886CBBAA434D6E1E7FC4401BB6D176C2788C4D19" 197 | }, 198 | "641B07F93E8DF03AB8B9BF1E8DC56E2889F247F8": { 199 | "logo": "QmNXexYonZXnNi4kKfDezNkX1Dg8tuU3iN7518sFtupyre", 200 | "name": "Worms (USA, Europe)", 201 | "vid": "QmcahTbiuzvNm1ZXSoVKiULmxpGGjCAN3ZQpcxgHZFQ6RB" 202 | }, 203 | "6588F7D677463D0EF10CF905D44A2581FD9DD055": { 204 | "name": "Club Drive (World)" 205 | }, 206 | "6B010477155F30F1ECEC7EAB2C9A7544242BBD19": { 207 | "logo": "QmNym4uVncwQDYRwoHNMv3YHA7SeeecLqbxVPc7XLrRwgQ", 208 | "name": "Space War 2000 (World)", 209 | "vid": "QmPJW65sMdxtqCWQUFfwHyHU9WPuA1PNcwipA3SZ7fwwVb" 210 | }, 211 | "73883E7A6E9B132452436F7AB1AEAEB0776428E5": { 212 | "name": "[BIOS] Atari Jaguar CD (World)" 213 | }, 214 | "752D6E1ED185F9DB49230F42319A573A44E32397": { 215 | "logo": "QmcUtsD4orEMZjoPbjk3h2YTS3Ug55pF4RhvGFCbExUBcV", 216 | "name": "Bubsy in Fractured Furry Tales (World)", 217 | "vid": "Qme3hphX7hkwVJWpYwcFnxU6u2Y8J7TB9JFpKcAsMuxDQh" 218 | }, 219 | "75EE0AB0DABA31284A1DFF27F3C247885F5A427A": { 220 | "logo": "QmWmPqprXS7jj5SmZB1cpBNTVaDxqdMzpfbMj8ZPj9iQer", 221 | "name": "Power Drive Rally (World)", 222 | "vid": "QmRfWpZnaUhLZjmPDuMk1JKkrvJ8EM7S72vsB64hZZveED" 223 | }, 224 | "7E4356FECBEADD741C2BF9868A37B2956BD27D5E": { 225 | "name": "Zzyorxx II (USA) (Demo)" 226 | }, 227 | "7F21C6DE18C46629C0D54E68143D619093B4B8D6": { 228 | "name": "Brett Hull Hockey (World)" 229 | }, 230 | "8043E3B521EA00E806D3AB79C6C548A140944DB1": { 231 | "name": "Protector (World)" 232 | }, 233 | "80ED1EE5B8ACADDFB64A5C0BBDA2BFC14BDB5DA5": { 234 | "name": "Alien vs Predator (World) (Beta) (1993-08-18)", 235 | "ref": "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02" 236 | }, 237 | "824613451BE0B99020FC02E5DBA15C45E76FC8C2": { 238 | "logo": "QmTGeRg29B8rinMjgnRXE352VnZn2qe1kaRGdwkyLbSPug", 239 | "name": "Flashback - The Quest for Identity (World) (En,Fr)", 240 | "vid": "QmSUe7urtRuLe4KkZpqXoVPsFHYsU7FiYLrwmwsfXD4QnU" 241 | }, 242 | "8377FF6C6527721E2E7F8458314D782925CE4590": { 243 | "name": "[BIOS] Atari Jaguar Stubulator '93 (World)" 244 | }, 245 | "841CE8AE1B71955D9B8DF3B80DA11FD40308EF5F": { 246 | "name": "Tiny Toon Adventures (USA) (Proto)" 247 | }, 248 | "886CBBAA434D6E1E7FC4401BB6D176C2788C4D19": { 249 | "logo": "QmW8jpj4BKCKmEd2FjGEV14rRd4mMWcmALzW1ULdaF7k3J", 250 | "name": "Flip Out! (World)", 251 | "vid": "QmZDAGfvPwuWWzcn2PtWJZgrPNbnccfHj6yg8WSWvmJPyX" 252 | }, 253 | "88E86085932AF10DFB8C94032F73A5992A30B55C": { 254 | "logo": "QmQkNeADHymrTFwyueXEemhWeZssNK8mkrqn5KQGipogox", 255 | "name": "Trevor McFur in the Crescent Galaxy (World) (Rev A)", 256 | "vid": "QmaDA59oWsebKK5xJm4MMtcE26smcfjd2CbfTGesKCuP9Q" 257 | }, 258 | "89306ADA943C87DA740E6166F1F53837566513E2": { 259 | "name": "Jack Nicklaus Cyber Golf (USA) (Demo) (1995-01-13)" 260 | }, 261 | "8BDEFC60DD1734315E1C145E29ABF41800F908CB": { 262 | "name": "Iron Soldier (World)" 263 | }, 264 | "8F4C964D2411A52C64EEC1D1A3061F77117BB7A6": { 265 | "name": "Battle Sphere (World)" 266 | }, 267 | "8F556976F42C393858127D99112264E6A5A9AE19": { 268 | "name": "Dragon - The Bruce Lee Story (World) (Beta) (1994-10-06)", 269 | "ref": "DA7F5DAC4317AB0258F54DA436ABB8D89E530F27" 270 | }, 271 | "93A29D894F5472EB755D13E721414A6BD9686C02": { 272 | "name": "Club Drive (World) (Beta)" 273 | }, 274 | "94E615E7A57F7C08645322870121BB32310298F5": { 275 | "name": "Hover Strike (World)" 276 | }, 277 | "988897FD517EBD86742971205F3B29F9D70B5686": { 278 | "name": "Barkley Shut Up and Jam (World) (Beta)" 279 | }, 280 | "9A58BC0C8843FD00A2871C01B91FEC206408BF80": { 281 | "name": "Super Burnout (World)" 282 | }, 283 | "9AD03858597AA1E1FFF26250996B07D1A70D1296": { 284 | "name": "Thea Realm Fighters (USA) (Proto 1)" 285 | }, 286 | "9C96E6C2195CE56AA930E677A041B35BD9B8BC7E": { 287 | "logo": "QmP4Lo8YAX9ZJ6rUJdGCoKfLByc4RKfqQt8Xd4C89J5wjy", 288 | "name": "Atari Karts (World)", 289 | "vid": "QmYYH6BQAhCkGpidXcKgqf1xnxLzynkuvTBBdBdG4K5tzo" 290 | }, 291 | "9D2F894DA3F25944C2F33E3A109AE8D0D951D329": { 292 | "name": "Raiden (World)" 293 | }, 294 | "9F5B8844D58BB1C174DA4824EABBFD8616BA28D2": { 295 | "logo": "QmTGRDYyEoyCXgZrKXbC9F1K42JujeWeYhzux3qbW7vZxh", 296 | "name": "Fight for Life (USA, Europe)", 297 | "vid": "QmehEeSFr4pVNWtSjqg6SWJNwe1eo3nJNtoQGW4pspgZ68" 298 | }, 299 | "A226086571C857F3796FD664BBC2C8FE99C78224": { 300 | "logo": "QmQwKmMx7Rbvz3J7w4ChsrjoBFh2vcYEx3A8m1GdcTX8Ed", 301 | "name": "Syndicate (World)", 302 | "vid": "QmZcBy1r8Tg2KdVPUniJHyf75LXPxhjEX1NEkksZMVWNSa" 303 | }, 304 | "A738A2DF47F09C3BFDEDDFC01B35F3E8B5C5D99C": { 305 | "logo": "QmYTz3N7NpzHJQYGZ1WJ4UhBe3wFcrWHL8YMwJcZBzBEZT", 306 | "name": "Soccer Kid (World) (Beta)", 307 | "vid": "QmVQshtfgntHzVn8ZVZdf8i5A36YYNNqUJaLJuF6KdG1GC" 308 | }, 309 | "AE497F4839BDE6C12C87C651A80633A23D6D0713": { 310 | "name": "Defender 2000 (World) (Beta)", 311 | "ref": "13A4084BD5BE1CCA21D83D4C26B5B8E5CF1A3E30" 312 | }, 313 | "B5DA0B1231A6E6532268C4E04F74E56F047DD5A5": { 314 | "ref": "6B010477155F30F1ECEC7EAB2C9A7544242BBD19" 315 | }, 316 | "B615FE0F229EEE89ABE4A6FCE8E4CE63D2F92C6F": { 317 | "name": "Soccer Kid (World)", 318 | "ref": "A738A2DF47F09C3BFDEDDFC01B35F3E8B5C5D99C" 319 | }, 320 | "B68DE2CD49CB70E92F2415837EDFA7AB46770A78": { 321 | "logo": "QmawCN7c7wHCrM4e6XHixQgDxLyQUXbfXWZk8Wc3Zakbeg", 322 | "name": "Zero 5 (USA, Europe)", 323 | "vid": "QmQjnfGuuznkcmCTP54nKsEMfvPVDKiBvYvWE4KwauxdAF" 324 | }, 325 | "B9C8FF2D26FE1E91ECD6D59B9F3F76C5B5650F26": { 326 | "logo": "QmbFV1DVEettBHxrAs5YfdHmKXVRhKtEVnrig3afbN5Emp", 327 | "name": "NBA Jam - Tournament Edition (USA, Europe)", 328 | "vid": "QmcYQvFP6WC8y6nAhVVBEjGSK1uMrof5fjSFGnNNDAosto" 329 | }, 330 | "BA34DE71A8D278BA76BAD19964D2D886DDB94A2E": { 331 | "name": "Rayman (World) (Beta 2)", 332 | "ref": "D22912992FD966365A1221886E0FC5303EF04A59" 333 | }, 334 | "BBE7FFB97FC07CEBE53099C8ACCE01366D7F7E15": { 335 | "name": "Towers II - Plight of the Stargazer (USA, Europe)" 336 | }, 337 | "BF00EC1FCA2A97BEAF28D52140DF2006B1120C67": { 338 | "name": "Checkered Flag (World) (Beta) (CES 1993)", 339 | "ref": "4D0CC8D248A8BD0FC4B8169C5D5772E59C073C59" 340 | }, 341 | "C71E0BC9EEED8670C6E7CCC7A0FE3B815D7BAC83": { 342 | "logo": "QmWNmY4HTPqNX4kC7yc9UahevNSptFdC7BCg1g49c855Ye", 343 | "name": "Supercross 3D (USA, Europe)", 344 | "vid": "QmcDoGsfHELmkyfUoTmXBwLcM83eRrwwmp8mtxjwNAD4ca" 345 | }, 346 | "C8F29D04DACCE1A83A1C93AB351737795D0F55DB": { 347 | "name": "Breakout 2000 (USA, Europe) (Beta)" 348 | }, 349 | "C92F1FF8EC83EBBE0F77F5A665169408DF53B528": { 350 | "name": "Fight for Life (USA, Europe) (Beta) (1995-09-07)", 351 | "ref": "9F5B8844D58BB1C174DA4824EABBFD8616BA28D2" 352 | }, 353 | "C9AA59769DF207D85D9212D619C266C793C9063B": { 354 | "name": "Cybermorph (World) (Rev B)", 355 | "ref": "E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E" 356 | }, 357 | "CDFEFDF28DD9127F7A9425088A1EE3EA88DFB618": { 358 | "logo": "QmaqeNB3qRTvDxozJ8p7rJZKzX57TpnDjVS8JBcEkmPBs9", 359 | "name": "Brutal Sports Football (World)", 360 | "vid": "QmPbw6drqyPkDuiKHThjNFH7SmPxsSDS7ago76MQwpprCX" 361 | }, 362 | "CECEC725301D7540F9392618F4FC948DEF88D398": { 363 | "name": "Jack Nicklaus Cyber Golf (USA) (Demo) (1995-01-16)" 364 | }, 365 | "D22912992FD966365A1221886E0FC5303EF04A59": { 366 | "logo": "QmXtq7N3BL8y2BkN2juk7Rk9FqJSFkj267XxKmP4hsNMY6", 367 | "name": "Rayman (World)", 368 | "vid": "QmbzbRxhpyotMMDtuXHruJvN1sjh1JNP8dBEtejf87cdEr" 369 | }, 370 | "D61B7B5912118F114EF00CF44966A5EF62E455A5": { 371 | "name": "[BIOS] Atari Jaguar Developer CD (World)" 372 | }, 373 | "D66B4E5F9AC27D53F0D781C741E420698630B9FC": { 374 | "logo": "QmXuVrogv2aTxNg6DFFLgei1cuCKyEH7uWdSebHFwCFF4P", 375 | "name": "Double Dragon V - The Shadow Falls (World)", 376 | "vid": "QmRR3VtTRZu5DTAqFoms9VGq57x1NLsSKxnvrgWvqJiW4c" 377 | }, 378 | "DA7F5DAC4317AB0258F54DA436ABB8D89E530F27": { 379 | "logo": "QmWkpKyEEyizMT6vMyaUQcMpAWdW8x5ErjXqM8H84CkXZw", 380 | "name": "Dragon - The Bruce Lee Story (World)", 381 | "vid": "QmWPaYLEJ4X7obTauVgYqhA3dKjnGNEMYEK6TA8zRB3wM7" 382 | }, 383 | "DD6F15CDA5A84E0F93BB04D24F61727A8276AE58": { 384 | "name": "Rayman (World) (Beta 1)", 385 | "ref": "D22912992FD966365A1221886E0FC5303EF04A59" 386 | }, 387 | "DDEA86820DCAA1A67BD82CF8023F9B3C494357B0": { 388 | "logo": "Qmf9zXG3HqDCwME9Nwft7BNP1MBNTNBhLwCC2NWP3EkML1", 389 | "name": "Protector - Special Edition (World)", 390 | "vid": "QmNeW9TBPfpVywZ7H7YSQuGgc8bqLAHy5DWwtbPPw9eY47" 391 | }, 392 | "E00AB55F555FD1FE63B4FCE66A9B8FFE3485963E": { 393 | "logo": "QmPAtutb1eQvfsnHRLRxk6Na5DUVNGfksxfCqsqi7CT6pE", 394 | "name": "Cybermorph (World) (Rev A)", 395 | "vid": "QmdrHLeu5mMh6pGU1aodsTVt96UYZCwPN6H2Zi1mpZU2PG" 396 | }, 397 | "E0DB2FDEA7F95743C1EF452B7D289F5FE8158E33": { 398 | "name": "Ruiner Pinball (USA, Europe)" 399 | }, 400 | "E3BD433BC4A573F3B05652239A6614C3878A04D1": { 401 | "name": "Ultra Vortex (World) (Beta) (1995-03-20)" 402 | }, 403 | "E99FF0ADD256BD45394C9ED8728E38B458CFE5E6": { 404 | "name": "High Voltage C Maze (USA) (Demo)" 405 | }, 406 | "EE553176F0A32683B517B84B12C6FAE13C15C3D0": { 407 | "name": "Wolfenstein 3D (World)" 408 | }, 409 | "EE5FEA3B99177D9E88AEEDCAD8F01173F1062B87": { 410 | "name": "Super Burnout (World) (Demo) (WCES 1995)" 411 | }, 412 | "F0914BE41331E3045BBC04225044DC3C73FD9DE1": { 413 | "logo": "QmQ7UpHJPARawoiRfrVnheX2m3tLvFTctpFwz94gory8CT", 414 | "name": "International Sensible Soccer (World)", 415 | "vid": "QmRCxSGrsU4owDTtGhg278xZhiCScgHPQFgwk9SzTrFdvs" 416 | }, 417 | "F8991B0C385F4E5002FA2A7E2F5E61E8C5213356": { 418 | "name": "[BIOS] Atari Jaguar (World)" 419 | }, 420 | "FB03CDB0B9C8171831C0E385C27BEC2D71C39400": { 421 | "name": "Zero 5 (USA, Europe) (Beta)", 422 | "ref": "B68DE2CD49CB70E92F2415837EDFA7AB46770A78" 423 | }, 424 | "FD8C89250EBC1E403838B2E589D1F69E3FE2FE02": { 425 | "logo": "QmcevUQ5YkCiWcNov4grof4VwqeqR8P2DH5kr7AG6E2kPM", 426 | "name": "Alien vs Predator (World)", 427 | "vid": "QmPF7SYwe3KYS9DxYHoNsVTnhwwFe9sdHkvnLdG7BS3Ff3" 428 | }, 429 | "FE4C35C24BE8DF92F867B4FB42EB75A3FCC875E8": { 430 | "name": "Breakout 2000 (USA, Europe)" 431 | }, 432 | "FE66FFDC9323CFCFC64DA06E16F2D5A4A4E1A236": { 433 | "name": "Slam Racer (USA) (Proto)" 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /frontend/js/vendor/hammer.min.js: -------------------------------------------------------------------------------- 1 | /*! Hammer.JS - v2.0.8 - 2016-04-23 2 | * http://hammerjs.github.io/ 3 | * 4 | * Copyright (c) 2016 Jorik Tangelder; 5 | * Licensed under the MIT license */ 6 | !function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance').text(directory)); 21 | let items = await fs.readdirSync(directory); 22 | let baseName = directory.split('/').slice(-1)[0]; 23 | let parentFolder = directory.replace(baseName,''); 24 | let parentLink = $('').addClass('directory').attr('onclick', 'renderFiles(\'' + parentFolder + '\');').text('..'); 25 | if (directoryClean == '/') { 26 | directoryClean = ''; 27 | } 28 | let table = $('').addClass('fileTable'); 29 | let tableHeader = $(''); 30 | for await (name of ['Name', 'Type', 'Delete (NO WARNING)']) { 31 | tableHeader.append($(''); 34 | for await (item of [parentLink, $(''); 53 | let dirClean = dir.replace("'","|"); 54 | let link = $(''); 66 | let fileClean = file.replace("'","|"); 67 | let link = $('
').text(name)); 32 | } 33 | let parentRow = $('
').text('Parent'), $('')]) { 35 | parentRow.append(item); 36 | } 37 | table.append(tableHeader,parentRow); 38 | $('#filebrowser').append(table); 39 | items.sort(); 40 | if (items.length > 0) { 41 | let dirs = []; 42 | let files = []; 43 | for await (let item of items) { 44 | if (fs.lstatSync(directory + '/' + item).isDirectory()) { 45 | dirs.push(item) 46 | } else { 47 | files.push(item) 48 | } 49 | } 50 | if (dirs.length > 0) { 51 | for await (let dir of dirs) { 52 | let tableRow = $('
').addClass('directory').attr('onclick', 'renderFiles(\'' + directoryClean + '/' + dirClean + '\');').text(dir); 55 | let type = $('').text('Dir'); 56 | let del = $('').append($('
').addClass('file').attr('onclick', 'downloadFile(\'' + directoryClean + '/' + fileClean + '\');').text(file); 68 | let type = $('').text('File'); 69 | let del = $('').append($('