mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-08-22 00:24:34 -05:00
Add BaseRomErrorModal on bad rom selections
This commit is contained in:
123
package-lock.json
generated
123
package-lock.json
generated
@@ -20,6 +20,7 @@
|
||||
"@vercel/edge-config": "^1.4.3",
|
||||
"chart.js": "^4.5.1",
|
||||
"embla-carousel-react": "8.6.0",
|
||||
"js-sha1": "^0.7.0",
|
||||
"mdast-util-to-hast": "^13.2.1",
|
||||
"minio": "^8.0.6",
|
||||
"next": "^15.5.8",
|
||||
@@ -838,7 +839,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -1424,6 +1424,7 @@
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
@@ -2381,7 +2382,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.74.0.tgz",
|
||||
"integrity": "sha512-IEMM/V6gKdP+N/X31KDIczVzghDpiPWFGLNjS8Rus71KvV6y6ueLrrE/JGCHDrU+9pq5copF3iCa0YQh+9Lq9Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@supabase/auth-js": "2.74.0",
|
||||
"@supabase/functions-js": "2.74.0",
|
||||
@@ -2726,6 +2726,7 @@
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
@@ -2737,6 +2738,7 @@
|
||||
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
@@ -2819,7 +2821,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -2935,6 +2936,7 @@
|
||||
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
|
||||
@@ -2945,21 +2947,24 @@
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
|
||||
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
|
||||
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
|
||||
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.13.2",
|
||||
@@ -2967,6 +2972,7 @@
|
||||
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
@@ -2978,7 +2984,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
|
||||
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.14.1",
|
||||
@@ -2986,6 +2993,7 @@
|
||||
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
@@ -2999,6 +3007,7 @@
|
||||
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
@@ -3009,6 +3018,7 @@
|
||||
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
@@ -3018,7 +3028,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
|
||||
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.14.1",
|
||||
@@ -3026,6 +3037,7 @@
|
||||
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
@@ -3043,6 +3055,7 @@
|
||||
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
@@ -3057,6 +3070,7 @@
|
||||
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
@@ -3070,6 +3084,7 @@
|
||||
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
@@ -3085,6 +3100,7 @@
|
||||
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@xtuc/long": "4.2.2"
|
||||
@@ -3095,14 +3111,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@xtuc/long": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
@@ -3138,6 +3156,7 @@
|
||||
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
@@ -3151,7 +3170,6 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -3169,6 +3187,7 @@
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
@@ -3187,6 +3206,7 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -3203,7 +3223,8 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
@@ -3267,6 +3288,7 @@
|
||||
"integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
@@ -3365,7 +3387,8 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
@@ -3505,7 +3528,6 @@
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
@@ -3519,6 +3541,7 @@
|
||||
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
@@ -3751,14 +3774,14 @@
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz",
|
||||
"integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/embla-carousel": {
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "8.6.0",
|
||||
@@ -3829,7 +3852,8 @@
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
@@ -3849,6 +3873,7 @@
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -3871,6 +3896,7 @@
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
@@ -3885,6 +3911,7 @@
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
@@ -3898,6 +3925,7 @@
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -3908,6 +3936,7 @@
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -3934,6 +3963,7 @@
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
@@ -3973,7 +4003,8 @@
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "4.5.3",
|
||||
@@ -4121,7 +4152,8 @@
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
@@ -4494,6 +4526,7 @@
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -4509,6 +4542,7 @@
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
@@ -4529,12 +4563,19 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sha1": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha1/-/js-sha1-0.7.0.tgz",
|
||||
"integrity": "sha512-oQZ1Mo7440BfLSv9TX87VNEyU52pXPVG19F9PL3gTgNt0tVxlZ8F4O6yze3CLuLx28TxotxvlyepCNaaV0ZjMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
@@ -4876,6 +4917,7 @@
|
||||
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
},
|
||||
@@ -5219,7 +5261,8 @@
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/micromark": {
|
||||
"version": "4.0.2",
|
||||
@@ -5883,14 +5926,14 @@
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.9",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
|
||||
"integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.9",
|
||||
"@swc/helpers": "0.5.15",
|
||||
@@ -5982,7 +6025,8 @@
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "7.0.11",
|
||||
@@ -6200,6 +6244,7 @@
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@@ -6230,7 +6275,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.2.tgz",
|
||||
"integrity": "sha512-MdWVitvLbQULD+4DP8GYjZUrepGW7d+GQkNVqJEzNxE+e9WIa4egVFE/RDfVb1u9u/Jw7dNMmPB4IqxzbFYJ0w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -6250,7 +6294,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.2.tgz",
|
||||
"integrity": "sha512-dEoydsCp50i7kS1xHOmPXq4zQYoGWedUsvqv9H6zdif2r7yLHygyfP9qou71TulRN0d6ng9EbRVsQhSqfUc19g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -6397,6 +6440,7 @@
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -6609,6 +6653,7 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -6628,6 +6673,7 @@
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
@@ -6775,8 +6821,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
|
||||
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -6798,6 +6843,7 @@
|
||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
@@ -6817,6 +6863,7 @@
|
||||
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
@@ -6870,6 +6917,7 @@
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
@@ -6882,7 +6930,8 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "4.3.3",
|
||||
@@ -6890,6 +6939,7 @@
|
||||
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
@@ -6910,6 +6960,7 @@
|
||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -6919,7 +6970,8 @@
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/through2": {
|
||||
"version": "4.0.2",
|
||||
@@ -7122,6 +7174,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.1"
|
||||
@@ -7218,6 +7271,7 @@
|
||||
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.1.2"
|
||||
@@ -7250,6 +7304,7 @@
|
||||
"integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.8",
|
||||
@@ -7299,6 +7354,7 @@
|
||||
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
@@ -7327,6 +7383,7 @@
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
@@ -7339,7 +7396,8 @@
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/webpack/node_modules/schema-utils": {
|
||||
"version": "4.3.3",
|
||||
@@ -7347,6 +7405,7 @@
|
||||
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@vercel/edge-config": "^1.4.3",
|
||||
"chart.js": "^4.5.1",
|
||||
"embla-carousel-react": "8.6.0",
|
||||
"js-sha1": "^0.7.0",
|
||||
"mdast-util-to-hast": "^13.2.1",
|
||||
"minio": "^8.0.6",
|
||||
"next": "^15.5.8",
|
||||
|
||||
214
src/components/Hack/BaseRomErrorModal.tsx
Normal file
214
src/components/Hack/BaseRomErrorModal.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { FiX } from "react-icons/fi";
|
||||
import { FaCircleCheck, FaCircleXmark } from "react-icons/fa6";
|
||||
|
||||
export type BaseRomErrorModalState =
|
||||
| { kind: "archive"; fileName: string; requiredExtensionPhrase: string; requiredRomName: string }
|
||||
| { kind: "unrecognized"; fileName: string; requiredExtensionPhrase: string; requiredRomName: string }
|
||||
| {
|
||||
kind: "hash_mismatch";
|
||||
fileName: string;
|
||||
selectedHash: string;
|
||||
requiredHash: string;
|
||||
requiredRomName: string;
|
||||
matchedRomName?: string;
|
||||
};
|
||||
|
||||
interface BaseRomErrorModalProps {
|
||||
state: BaseRomErrorModalState;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function getTitle(state: BaseRomErrorModalState): string {
|
||||
switch (state.kind) {
|
||||
case "archive":
|
||||
return "Extract your ROM first";
|
||||
case "unrecognized":
|
||||
return "Unrecognized file type";
|
||||
case "hash_mismatch":
|
||||
return "This isn't the correct base ROM";
|
||||
}
|
||||
}
|
||||
|
||||
function SectionLabel({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-foreground/60">
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
function HashBlock({
|
||||
label,
|
||||
hash,
|
||||
variant,
|
||||
}: {
|
||||
label: string;
|
||||
hash: string;
|
||||
variant: "wrong" | "correct";
|
||||
}) {
|
||||
const isWrong = variant === "wrong";
|
||||
const Icon = isWrong ? FaCircleXmark : FaCircleCheck;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-medium text-foreground/70">{label}</span>
|
||||
<div
|
||||
className={`flex items-center gap-2.5 rounded-md bg-[var(--surface-1)] px-3 py-2 ring-1 ring-inset ${
|
||||
isWrong ? "ring-red-500/30" : "ring-emerald-500/30"
|
||||
}`}
|
||||
>
|
||||
<Icon
|
||||
className={`shrink-0 ${isWrong ? "text-red-500" : "text-emerald-500"}`}
|
||||
size={14}
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="min-w-0 flex-1 overflow-x-auto">
|
||||
<code className="whitespace-nowrap text-xs">{hash}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalBody({ state }: { state: BaseRomErrorModalState }) {
|
||||
switch (state.kind) {
|
||||
case "archive":
|
||||
return (
|
||||
<p className="text-sm leading-relaxed text-foreground/80">
|
||||
Archive files like <code>.zip</code> need to be extracted first. After extracting, upload{" "}
|
||||
<strong>{state.requiredExtensionPhrase}</strong> for <strong>{state.requiredRomName}</strong>.
|
||||
</p>
|
||||
);
|
||||
case "unrecognized":
|
||||
return (
|
||||
<p className="text-sm leading-relaxed text-foreground/80">
|
||||
<code>{state.fileName}</code> isn't a ROM file. This hack requires{" "}
|
||||
<strong>{state.requiredRomName}</strong> — please select{" "}
|
||||
<strong>{state.requiredExtensionPhrase}</strong>.
|
||||
</p>
|
||||
);
|
||||
case "hash_mismatch":
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<section className="flex flex-col gap-2">
|
||||
<SectionLabel>What happened</SectionLabel>
|
||||
<p className="text-base leading-relaxed text-foreground/90">
|
||||
{state.matchedRomName ? (
|
||||
<>
|
||||
This file looks like <strong>{state.matchedRomName}</strong>, but this hack needs{" "}
|
||||
<strong>{state.requiredRomName}</strong>.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
This file doesn't match a known clean copy of <strong>{state.requiredRomName}</strong>.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="rounded-md border border-amber-500/50 bg-amber-500/10 p-3 text-sm leading-relaxed text-amber-900 dark:text-amber-100">
|
||||
Patching against a non-matching ROM (even if that ROM is fully playable) can cause crashes, glitches, or broken saves.
|
||||
</div>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<SectionLabel>Compare checksums</SectionLabel>
|
||||
<div className="flex flex-col gap-3">
|
||||
<HashBlock label="Your file" hash={state.selectedHash} variant="wrong" />
|
||||
<HashBlock label={`Required — ${state.requiredRomName}`} hash={state.requiredHash} variant="correct" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<SectionLabel>Common reasons</SectionLabel>
|
||||
<ul className="list-disc space-y-1.5 pl-5 text-sm leading-relaxed text-foreground/80">
|
||||
<li>Wrong revision or region</li>
|
||||
<li>The ROM was modified or trimmed</li>
|
||||
<li>The file is corrupted or incomplete</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div className="rounded-md border border-[var(--border)] bg-[var(--surface-2)] p-3 text-xs leading-relaxed text-foreground/60">
|
||||
Hackdex cannot advise on where to find ROMs. The only legal source is ripping from your own cartridge.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const BaseRomErrorModal: React.FC<BaseRomErrorModalProps> = ({ state, onClose }) => {
|
||||
useEffect(() => {
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
const previousHtmlOverflow = html.style.overflow;
|
||||
const previousBodyOverflow = body.style.overflow;
|
||||
const previousBodyPaddingRight = body.style.paddingRight;
|
||||
const scrollBarWidth = window.innerWidth - html.clientWidth;
|
||||
|
||||
html.style.overflow = "hidden";
|
||||
body.style.overflow = "hidden";
|
||||
if (scrollBarWidth > 0) {
|
||||
body.style.paddingRight = `${scrollBarWidth}px`;
|
||||
}
|
||||
|
||||
return () => {
|
||||
html.style.overflow = previousHtmlOverflow;
|
||||
body.style.overflow = previousBodyOverflow;
|
||||
body.style.paddingRight = previousBodyPaddingRight;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const title = getTitle(state);
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex md:items-center md:justify-center md:p-4">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 dark:bg-black/60 md:backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="base-rom-error-title"
|
||||
className="relative z-[101] flex h-full w-full flex-col card backdrop-blur-lg dark:!bg-black/70 rounded-none md:h-auto md:max-h-[85vh] md:w-full md:max-w-lg md:rounded-lg"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="absolute top-4 right-4 p-1.5 rounded-md text-foreground/60 hover:text-foreground hover:bg-[var(--surface-2)] transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--accent)]"
|
||||
>
|
||||
<FiX size={20} />
|
||||
</button>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-6 pb-[max(1.5rem,env(safe-area-inset-bottom))]">
|
||||
<h2 id="base-rom-error-title" className="text-xl font-semibold pr-8">
|
||||
{title}
|
||||
</h2>
|
||||
{state.kind !== "unrecognized" && (
|
||||
<p className="mt-2 text-sm text-foreground/80">
|
||||
Selected file: <code>{state.fileName}</code>
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-6">
|
||||
<ModalBody state={state} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[var(--border)] p-4 pb-[max(1rem,env(safe-area-inset-bottom))] md:border-0 md:p-6 md:pt-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="inline-flex h-14 md:h-11 w-full items-center justify-center rounded-md bg-[var(--accent)] px-4 text-sm font-semibold text-[var(--accent-foreground)] hover:bg-[var(--accent-700)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]"
|
||||
>
|
||||
Choose a different file
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseRomErrorModal;
|
||||
@@ -2,12 +2,19 @@
|
||||
|
||||
import React from "react";
|
||||
import StickyActionBar from "@/components/Hack/StickyActionBar";
|
||||
import BaseRomErrorModal, { type BaseRomErrorModalState } from "@/components/Hack/BaseRomErrorModal";
|
||||
import { useBaseRoms } from "@/contexts/BaseRomContext";
|
||||
import { baseRoms } from "@/data/baseRoms";
|
||||
import BinFile from "rom-patcher-js/rom-patcher-js/modules/BinFile.js";
|
||||
import BPS from "rom-patcher-js/rom-patcher-js/modules/RomPatcher.format.bps.js";
|
||||
import type { DownloadEventDetail } from "@/types/util";
|
||||
import { getSignedPatchUrl, updatePatchDownloadCount } from "@/app/hack/[slug]/actions";
|
||||
import { sha1Hex } from "@/utils/hash";
|
||||
import {
|
||||
formatRequiredRomExtension,
|
||||
isArchiveFile,
|
||||
isAnyRomExtension,
|
||||
} from "@/utils/romFile";
|
||||
|
||||
interface HackActionsProps {
|
||||
title: string;
|
||||
@@ -37,7 +44,12 @@ const HackActions: React.FC<HackActionsProps> = ({
|
||||
const [patchBlob, setPatchBlob] = React.useState<Blob | null>(null);
|
||||
const [patchUrl, setPatchUrl] = React.useState<string | null>(null);
|
||||
const [termsAgreed, setTermsAgreed] = React.useState(false);
|
||||
const [romErrorModal, setRomErrorModal] = React.useState<BaseRomErrorModalState | null>(null);
|
||||
const baseRomName = React.useMemo(() => baseRoms.find(r => r.id === baseRomId)?.name || null, [baseRomId]);
|
||||
const effectivePlatform = React.useMemo(
|
||||
() => platform ?? baseRoms.find(r => r.id === baseRomId)?.platform,
|
||||
[platform, baseRomId],
|
||||
);
|
||||
|
||||
// Basic client-side bot detection
|
||||
React.useEffect(() => {
|
||||
@@ -95,23 +107,74 @@ const HackActions: React.FC<HackActionsProps> = ({
|
||||
|
||||
async function onSelectFile(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const f = e.target.files?.[0] ?? null;
|
||||
setFile(f);
|
||||
if (f) {
|
||||
const id = await importUploadedBlob(f);
|
||||
if (!id) {
|
||||
setError("That ROM doesn't match any supported base ROM.");
|
||||
setStatus("idle");
|
||||
e.target.value = "";
|
||||
return;
|
||||
}
|
||||
if (id !== baseRomId) {
|
||||
setError(`This ROM matches "${id}", but this hack requires "${baseRomName}".`);
|
||||
setStatus("idle");
|
||||
e.target.value = "";
|
||||
return;
|
||||
}
|
||||
setStatus("ready");
|
||||
setFile(null);
|
||||
if (!f) return;
|
||||
|
||||
const requiredRomName = baseRomName ?? "this hack's base ROM";
|
||||
const requiredExtensionPhrase = effectivePlatform
|
||||
? formatRequiredRomExtension(effectivePlatform)
|
||||
: "a ROM file";
|
||||
|
||||
const resetInput = () => {
|
||||
e.target.value = "";
|
||||
setStatus("idle");
|
||||
};
|
||||
|
||||
if (isArchiveFile(f.name)) {
|
||||
setRomErrorModal({
|
||||
kind: "archive",
|
||||
fileName: f.name,
|
||||
requiredExtensionPhrase,
|
||||
requiredRomName,
|
||||
});
|
||||
resetInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAnyRomExtension(f.name)) {
|
||||
setRomErrorModal({
|
||||
kind: "unrecognized",
|
||||
fileName: f.name,
|
||||
requiredExtensionPhrase,
|
||||
requiredRomName,
|
||||
});
|
||||
resetInput();
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedHash = await sha1Hex(f);
|
||||
const match = baseRoms.find((r) => r.sha1.toLowerCase() === selectedHash.toLowerCase());
|
||||
const requiredHash = baseRoms.find((r) => r.id === baseRomId)?.sha1.toLowerCase() ?? "";
|
||||
|
||||
if (!match) {
|
||||
setRomErrorModal({
|
||||
kind: "hash_mismatch",
|
||||
fileName: f.name,
|
||||
selectedHash,
|
||||
requiredHash,
|
||||
requiredRomName,
|
||||
});
|
||||
resetInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.id !== baseRomId) {
|
||||
await importUploadedBlob(f);
|
||||
setRomErrorModal({
|
||||
kind: "hash_mismatch",
|
||||
fileName: f.name,
|
||||
selectedHash,
|
||||
requiredHash,
|
||||
requiredRomName,
|
||||
matchedRomName: match.name,
|
||||
});
|
||||
resetInput();
|
||||
return;
|
||||
}
|
||||
|
||||
await importUploadedBlob(f);
|
||||
setFile(f);
|
||||
setStatus("ready");
|
||||
}
|
||||
|
||||
async function onAgreeToTerms() {
|
||||
@@ -244,23 +307,31 @@ const HackActions: React.FC<HackActionsProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<StickyActionBar
|
||||
title={title}
|
||||
version={version}
|
||||
author={author}
|
||||
filename={patchFilename}
|
||||
baseRomName={baseRomName}
|
||||
baseRomPlatform={platform}
|
||||
onPatch={onPatch}
|
||||
status={status}
|
||||
error={error}
|
||||
isLinked={isLinked(baseRomId)}
|
||||
romReady={hasPermission(baseRomId) || hasCached(baseRomId)}
|
||||
onClickLink={() => (isLinked(baseRomId) ? ensurePermission(baseRomId, true) : linkRom(baseRomId))}
|
||||
supported={supported}
|
||||
onUploadChange={onSelectFile}
|
||||
termsAgreed={termsAgreed}
|
||||
/>
|
||||
<>
|
||||
<StickyActionBar
|
||||
title={title}
|
||||
version={version}
|
||||
author={author}
|
||||
filename={patchFilename}
|
||||
baseRomName={baseRomName}
|
||||
baseRomPlatform={platform}
|
||||
onPatch={onPatch}
|
||||
status={status}
|
||||
error={error}
|
||||
isLinked={isLinked(baseRomId)}
|
||||
romReady={hasPermission(baseRomId) || hasCached(baseRomId)}
|
||||
onClickLink={() => (isLinked(baseRomId) ? ensurePermission(baseRomId, true) : linkRom(baseRomId))}
|
||||
supported={supported}
|
||||
onUploadChange={onSelectFile}
|
||||
termsAgreed={termsAgreed}
|
||||
/>
|
||||
{romErrorModal && (
|
||||
<BaseRomErrorModal
|
||||
state={romErrorModal}
|
||||
onClose={() => setRomErrorModal(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export async function sha1Hex(blob: Blob): Promise<string> {
|
||||
const buf = await blob.arrayBuffer();
|
||||
// @ts-ignore - subtle may be undefined in non-browser
|
||||
const digest = await (crypto.subtle as SubtleCrypto).digest("SHA-1", buf);
|
||||
import { sha1 } from "js-sha1";
|
||||
|
||||
async function subtleSha1Hex(buf: ArrayBuffer): Promise<string> {
|
||||
const digest = await crypto.subtle.digest("SHA-1", buf);
|
||||
const bytes = new Uint8Array(digest);
|
||||
let hex = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
@@ -10,4 +10,16 @@ export async function sha1Hex(blob: Blob): Promise<string> {
|
||||
return hex;
|
||||
}
|
||||
|
||||
export async function sha1Hex(blob: Blob): Promise<string> {
|
||||
const buf = await blob.arrayBuffer();
|
||||
|
||||
if (typeof crypto !== "undefined" && typeof crypto.subtle?.digest === "function") {
|
||||
try {
|
||||
return await subtleSha1Hex(buf);
|
||||
} catch {
|
||||
// crypto.subtle can fail outside a secure context (e.g. http dev)
|
||||
}
|
||||
}
|
||||
|
||||
return sha1(buf);
|
||||
}
|
||||
|
||||
50
src/utils/romFile.ts
Normal file
50
src/utils/romFile.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { Platform } from "@/data/baseRoms";
|
||||
import { platformAccept, platformAcceptAll } from "@/utils/idb";
|
||||
|
||||
const ARCHIVE_EXTENSIONS = new Set([
|
||||
".zip",
|
||||
".7z",
|
||||
".rar",
|
||||
".tar",
|
||||
".gz",
|
||||
".gzip",
|
||||
".bz2",
|
||||
".xz",
|
||||
".cab",
|
||||
]);
|
||||
|
||||
export function getFileExtension(name: string): string {
|
||||
const lower = name.toLowerCase();
|
||||
const lastDot = lower.lastIndexOf(".");
|
||||
if (lastDot <= 0) return "";
|
||||
return lower.slice(lastDot);
|
||||
}
|
||||
|
||||
export function isArchiveFile(name: string): boolean {
|
||||
return ARCHIVE_EXTENSIONS.has(getFileExtension(name));
|
||||
}
|
||||
|
||||
function platformExtensions(platform: Platform): string[] {
|
||||
return platformAccept(platform)
|
||||
.split(",")
|
||||
.map((ext) => ext.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/** Any extension Hackdex accepts across all platforms (.gb, .gbc, .gba, .nds). */
|
||||
export function isAnyRomExtension(name: string): boolean {
|
||||
const ext = getFileExtension(name);
|
||||
if (!ext) return false;
|
||||
return platformAcceptAll()
|
||||
.split(",")
|
||||
.map((e) => e.trim())
|
||||
.includes(ext);
|
||||
}
|
||||
|
||||
export function formatRequiredRomExtension(platform: Platform): string {
|
||||
const exts = platformExtensions(platform);
|
||||
if (exts.length === 0) return "a ROM file";
|
||||
if (exts.length === 1) return `a ${exts[0]} file`;
|
||||
const formatted = exts.map((ext) => ext).join(" or ");
|
||||
return `a ${formatted} file`;
|
||||
}
|
||||
Reference in New Issue
Block a user