mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Merge pull request #2787 from rommapp/feat/patcher.js
[ROMM-2098] Integrated patcher.js
This commit is contained in:
2
.github/workflows/typecheck.yml
vendored
2
.github/workflows/typecheck.yml
vendored
@@ -37,5 +37,5 @@ jobs:
|
||||
|
||||
- name: Lockfile lint
|
||||
run: |
|
||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | contains("node_modules")) and (.value | has("resolved") and has("integrity") | not)) | .key' < package-lock.json)" ]
|
||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | contains("node_modules")) and (.value.resolved | contains("git+ssh") | not) and (.value | has("resolved") and has("integrity") | not)) | .key' < package-lock.json)" ]
|
||||
working-directory: frontend
|
||||
|
||||
@@ -37,8 +37,8 @@ lint:
|
||||
- prettier@3.7.4:
|
||||
packages:
|
||||
- "@trivago/prettier-plugin-sort-imports@6.0.0"
|
||||
- "@vue/compiler-sfc@3.5.25"
|
||||
- ruff@0.14.9
|
||||
- "@vue/compiler-sfc@3.5.26"
|
||||
- ruff@0.14.10
|
||||
- shellcheck@0.11.0
|
||||
- shfmt@3.6.0
|
||||
- taplo@0.10.0
|
||||
|
||||
BIN
frontend/assets/patcherjs/patcherjs.png
Normal file
BIN
frontend/assets/patcherjs/patcherjs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
23
frontend/package-lock.json
generated
23
frontend/package-lock.json
generated
@@ -20,6 +20,7 @@
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^3.0.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"rom-patcher": "github:marcrobledo/RomPatcher.js#v3.2.1",
|
||||
"semver": "^7.6.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"tailwindcss": "^4.0.0",
|
||||
@@ -4547,7 +4548,6 @@
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
@@ -5935,7 +5935,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -7836,6 +7835,25 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rom-patcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "git+ssh://git@github.com/marcrobledo/RomPatcher.js.git#91e522e247f709e894761157ccba3189004d0859",
|
||||
"dependencies": {
|
||||
"chalk": "4.1.2",
|
||||
"commander": "^11.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"RomPatcher": "index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/rom-patcher/node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -8366,7 +8384,6 @@
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^3.0.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"rom-patcher": "github:marcrobledo/RomPatcher.js#v3.2.1",
|
||||
"semver": "^7.6.2",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"tailwindcss": "^4.0.0",
|
||||
|
||||
161
frontend/public/assets/patcherjs/patcher.worker.js
Normal file
161
frontend/public/assets/patcherjs/patcher.worker.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
// Load all patcher scripts
|
||||
let scriptsLoaded = false;
|
||||
|
||||
async function loadScripts() {
|
||||
if (scriptsLoaded) return;
|
||||
|
||||
self.BinFile =
|
||||
self.IPS =
|
||||
self.UPS =
|
||||
self.APS =
|
||||
self.APSGBA =
|
||||
self.BPS =
|
||||
self.RUP =
|
||||
self.PPF =
|
||||
self.BDF =
|
||||
self.PMSR =
|
||||
self.VCDIFF =
|
||||
null;
|
||||
|
||||
try {
|
||||
importScripts(
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/BinFile.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/HashCalculator.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.aps_gba.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.aps_n64.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.bdf.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.bps.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.ips.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.pmsr.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.ppf.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.rup.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.ups.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/modules/RomPatcher.format.vcdiff.js",
|
||||
"/node_modules/rom-patcher/rom-patcher-js/RomPatcher.js",
|
||||
);
|
||||
scriptsLoaded = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load patcher scripts: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle messages from main thread
|
||||
self.addEventListener("message", async (e) => {
|
||||
const {
|
||||
type,
|
||||
romData,
|
||||
patchData,
|
||||
romFileName,
|
||||
patchFileName,
|
||||
customFileName,
|
||||
} = e.data;
|
||||
|
||||
if (type === "PATCH") {
|
||||
try {
|
||||
// Load scripts if not already loaded
|
||||
self.postMessage({
|
||||
type: "STATUS",
|
||||
message: "Loading patcher libraries...",
|
||||
});
|
||||
await loadScripts();
|
||||
|
||||
// Extract patch name without extension for custom suffix
|
||||
const patchNameWithoutExt = patchFileName.replace(/\.[^.]+$/, "");
|
||||
|
||||
// Try to create BinFile from Uint8Array
|
||||
self.postMessage({ type: "STATUS", message: "Reading ROM file..." });
|
||||
const romUint8 = new Uint8Array(romData);
|
||||
|
||||
const romBin = await new Promise((resolve, reject) => {
|
||||
try {
|
||||
new BinFile(romUint8, (bf) => {
|
||||
if (bf) {
|
||||
bf.fileName = romFileName;
|
||||
resolve(bf);
|
||||
} else {
|
||||
reject(new Error("Failed to create ROM BinFile"));
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
self.postMessage({ type: "STATUS", message: "Reading patch file..." });
|
||||
const patchUint8 = new Uint8Array(patchData);
|
||||
|
||||
const patchBin = await new Promise((resolve, reject) => {
|
||||
try {
|
||||
new BinFile(patchUint8, (bf) => {
|
||||
if (bf) {
|
||||
bf.fileName = patchFileName;
|
||||
resolve(bf);
|
||||
} else {
|
||||
reject(new Error("Failed to create patch BinFile"));
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Parse patch
|
||||
self.postMessage({ type: "STATUS", message: "Parsing patch format..." });
|
||||
const patch = RomPatcher.parsePatchFile(patchBin);
|
||||
if (!patch) {
|
||||
throw new Error("Unsupported or invalid patch format.");
|
||||
}
|
||||
|
||||
// Apply patch
|
||||
self.postMessage({
|
||||
type: "STATUS",
|
||||
message: "Applying patch (this may take a moment)...",
|
||||
});
|
||||
const patched = RomPatcher.applyPatch(romBin, patch, {
|
||||
requireValidation: false,
|
||||
fixChecksum: false,
|
||||
outputSuffix: false, // Don't add default suffix
|
||||
});
|
||||
|
||||
// Extract the patched binary data
|
||||
const patchedData = patched._u8array || patched.u8array || patched.data;
|
||||
if (!patchedData) {
|
||||
throw new Error("Failed to extract patched ROM data");
|
||||
}
|
||||
|
||||
// Create custom filename with patch name
|
||||
const romBaseName = romFileName.replace(/\.[^.]+$/, "");
|
||||
const romExtension = romFileName.match(/\.[^.]+$/)?.[0] || "";
|
||||
const defaultFileName = `${romBaseName} (patched-${patchNameWithoutExt})${romExtension}`;
|
||||
|
||||
// If custom filename provided, strip any extension and add ROM extension
|
||||
let finalFileName;
|
||||
if (customFileName && customFileName.trim()) {
|
||||
const customBase = customFileName.trim().replace(/\.[^.]+$/, "");
|
||||
finalFileName = `${customBase}${romExtension}`;
|
||||
} else {
|
||||
finalFileName = defaultFileName;
|
||||
}
|
||||
|
||||
// Send back the result
|
||||
self.postMessage(
|
||||
{
|
||||
type: "SUCCESS",
|
||||
patchedData: patchedData.buffer,
|
||||
fileName: finalFileName,
|
||||
},
|
||||
[patchedData.buffer],
|
||||
); // Transfer ownership of ArrayBuffer
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: "ERROR",
|
||||
error: error.message || String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import CollectionsBtn from "@/components/common/Navigation/CollectionsBtn.vue";
|
||||
import CollectionsDrawer from "@/components/common/Navigation/CollectionsDrawer.vue";
|
||||
import ConsoleModeBtn from "@/components/common/Navigation/ConsoleModeBtn.vue";
|
||||
import HomeBtn from "@/components/common/Navigation/HomeBtn.vue";
|
||||
import PatcherBtn from "@/components/common/Navigation/PatcherBtn.vue";
|
||||
import PlatformsBtn from "@/components/common/Navigation/PlatformsBtn.vue";
|
||||
import PlatformsDrawer from "@/components/common/Navigation/PlatformsDrawer.vue";
|
||||
import ScanBtn from "@/components/common/Navigation/ScanBtn.vue";
|
||||
@@ -46,7 +47,8 @@ function collapse() {
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<RandomBtn />
|
||||
<PatcherBtn class="mr-2" />
|
||||
<RandomBtn class="mr-2" />
|
||||
<UploadBtn class="mr-2" />
|
||||
<UserBtn class="mr-1" />
|
||||
</template>
|
||||
@@ -109,6 +111,7 @@ function collapse() {
|
||||
<ConsoleModeBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
|
||||
<template #append>
|
||||
<PatcherBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
<RandomBtn :with-tag="!mainBarCollapsed" rounded class="mt-2" block />
|
||||
<UploadBtn
|
||||
:with-tag="!mainBarCollapsed"
|
||||
|
||||
49
frontend/src/components/common/Navigation/PatcherBtn.vue
Normal file
49
frontend/src/components/common/Navigation/PatcherBtn.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import storeNavigation from "@/stores/navigation";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
block?: boolean;
|
||||
height?: string;
|
||||
rounded?: boolean;
|
||||
withTag?: boolean;
|
||||
}>(),
|
||||
{
|
||||
block: false,
|
||||
height: "",
|
||||
rounded: false,
|
||||
withTag: false,
|
||||
},
|
||||
);
|
||||
const { t } = useI18n();
|
||||
const navigationStore = storeNavigation();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-btn
|
||||
icon
|
||||
:block="block"
|
||||
variant="flat"
|
||||
color="background"
|
||||
:height="height"
|
||||
:class="{ rounded: rounded }"
|
||||
class="py-4 bg-background d-flex align-center justify-center"
|
||||
@click="navigationStore.goPatcher"
|
||||
>
|
||||
<div class="d-flex flex-column align-center">
|
||||
<v-icon :color="$route.path.startsWith('/patcher') ? 'primary' : ''">
|
||||
mdi-file-cog
|
||||
</v-icon>
|
||||
<v-expand-transition>
|
||||
<span
|
||||
v-if="withTag"
|
||||
class="text-caption text-center"
|
||||
:class="{ 'text-primary': $route.path.startsWith('/patcher') }"
|
||||
>
|
||||
{{ t("common.patcher") }}
|
||||
</span>
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "O aplikaci",
|
||||
"add": "Přidat",
|
||||
"administration": "Administrace",
|
||||
"and": "a",
|
||||
"apply": "Použít",
|
||||
"ascii-only": "Pouze ASCII znaky",
|
||||
"cancel": "Zrušit",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Správa knihovny",
|
||||
"logout": "Odhlásit se",
|
||||
"name": "Název",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "Heslo musí mít 6 až 255 znaků",
|
||||
"platform": "Platforma",
|
||||
"platforms": "Platformy",
|
||||
|
||||
34
frontend/src/locales/cs_CZ/patcher.json
Normal file
34
frontend/src/locales/cs_CZ/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM Patcher",
|
||||
"subtitle": "Vyberte základní ROM a patch soubor, poté aplikujte pro stažení opatchovaného ROMu.",
|
||||
"rom-file": "ROM soubor",
|
||||
"patch-file": "Patch soubor",
|
||||
"drop-rom-here": "Přetáhněte ROM sem",
|
||||
"drop-patch-here": "Přetáhněte patch sem",
|
||||
"drag-drop-rom": "Přetáhněte ROM soubor nebo klikněte pro procházení.",
|
||||
"drag-drop-patch": "Přetáhněte patch soubor nebo klikněte pro procházení.",
|
||||
"choose-rom": "Vybrat ROM",
|
||||
"choose-patch": "Vybrat patch",
|
||||
"replace": "Nahradit",
|
||||
"supported-formats": "Podporované formáty patchů",
|
||||
"download-locally": "Stáhnout opatchovaný ROM",
|
||||
"upload-to-romm": "Nahrát do RomM",
|
||||
"output-filename": "Název výstupního souboru (volitelné)",
|
||||
"apply-download-upload": "Aplikovat, Stáhnout a Nahrát",
|
||||
"apply-upload": "Aplikovat a Nahrát",
|
||||
"apply-download": "Aplikovat a Stáhnout",
|
||||
"powered-by": "Běží na patcherjs",
|
||||
"error-no-rom": "Prosím vyberte ROM soubor.",
|
||||
"error-no-patch": "Prosím vyberte patch soubor.",
|
||||
"error-no-platform": "Prosím vyberte platformu pro nahrání.",
|
||||
"error-no-action": "Prosím vyberte alespoň jednu akci: stažení nebo nahrání.",
|
||||
"status-preparing": "Příprava souborů...",
|
||||
"status-downloading": "Stahování opatchovaného ROMu...",
|
||||
"status-uploading": "Nahrávání do RomM...",
|
||||
"success-uploaded": "nahráno",
|
||||
"success-downloaded": "staženo",
|
||||
"success-message": "Opatchovaný ROM {actions} úspěšně!",
|
||||
"error-upload-failed": "Nelze nahrát ROM: {error}",
|
||||
"upload-success": "Opatchovaný ROM úspěšně nahrán{errors}. Spouštění skenování...",
|
||||
"upload-errors": " (s některými chybami)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Vybrat stav",
|
||||
"change-state": "Změnit stav",
|
||||
"deselect-state": "Zrušit výběr stavu",
|
||||
"no-save-selected": "Není vybrána žádná uložená pozice",
|
||||
"no-state-selected": "Není vybrán žádný stav",
|
||||
"no-saves-available": "Nejsou k dispozici žádné uložené pozice",
|
||||
"no-states-available": "Nejsou k dispozici žádné stavy",
|
||||
"background-color": "Barva pozadí",
|
||||
"select-background-color": "Vybrat barvu pozadí"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "Über",
|
||||
"add": "Hinzufügen",
|
||||
"administration": "Administration",
|
||||
"and": "und",
|
||||
"apply": "Anwenden",
|
||||
"ascii-only": "Nur ASCII-Zeichen",
|
||||
"cancel": "Abbrechen",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Bibliothek verwalten",
|
||||
"logout": "Ausloggen",
|
||||
"name": "Name",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "Passwort muss zwischen 6 und 255 Zeichen lang sein",
|
||||
"platform": "Plattform",
|
||||
"platforms": "Plattformen",
|
||||
|
||||
34
frontend/src/locales/de_DE/patcher.json
Normal file
34
frontend/src/locales/de_DE/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM Patcher",
|
||||
"subtitle": "Wählen Sie eine Basis-ROM und eine Patch-Datei aus und wenden Sie sie an, um die gepatchte ROM herunterzuladen.",
|
||||
"rom-file": "ROM-Datei",
|
||||
"patch-file": "Patch-Datei",
|
||||
"drop-rom-here": "ROM hier ablegen",
|
||||
"drop-patch-here": "Patch hier ablegen",
|
||||
"drag-drop-rom": "Ziehen Sie eine ROM-Datei hierher oder klicken Sie zum Durchsuchen.",
|
||||
"drag-drop-patch": "Ziehen Sie eine Patch-Datei hierher oder klicken Sie zum Durchsuchen.",
|
||||
"choose-rom": "ROM wählen",
|
||||
"choose-patch": "Patch wählen",
|
||||
"replace": "Ersetzen",
|
||||
"supported-formats": "Unterstützte Patch-Formate",
|
||||
"download-locally": "Gepatchte ROM herunterladen",
|
||||
"upload-to-romm": "Zu RomM hochladen",
|
||||
"output-filename": "Ausgabedateiname (optional)",
|
||||
"apply-download-upload": "Anwenden, Herunterladen & Hochladen",
|
||||
"apply-upload": "Anwenden & Hochladen",
|
||||
"apply-download": "Anwenden & Herunterladen",
|
||||
"powered-by": "Unterstützt von patcherjs",
|
||||
"error-no-rom": "Bitte wählen Sie eine ROM-Datei aus.",
|
||||
"error-no-patch": "Bitte wählen Sie eine Patch-Datei aus.",
|
||||
"error-no-platform": "Bitte wählen Sie eine Plattform zum Hochladen aus.",
|
||||
"error-no-action": "Bitte wählen Sie mindestens eine Aktion: Herunterladen oder Hochladen.",
|
||||
"status-preparing": "Dateien werden vorbereitet...",
|
||||
"status-downloading": "Gepatchte ROM wird heruntergeladen...",
|
||||
"status-uploading": "Wird zu RomM hochgeladen...",
|
||||
"success-uploaded": "hochgeladen",
|
||||
"success-downloaded": "heruntergeladen",
|
||||
"success-message": "Gepatchte ROM {actions} erfolgreich!",
|
||||
"error-upload-failed": "ROM kann nicht hochgeladen werden: {error}",
|
||||
"upload-success": "Gepatchte ROM erfolgreich hochgeladen{errors}. Scan wird gestartet...",
|
||||
"upload-errors": " (mit einigen Fehlern)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Speicherstand auswählen",
|
||||
"change-state": "Speicherstand ändern",
|
||||
"deselect-state": "Speicherstand abwählen",
|
||||
"no-save-selected": "Kein Speicherstand ausgewählt",
|
||||
"no-state-selected": "Kein Zustand ausgewählt",
|
||||
"no-saves-available": "Keine Speicherstände verfügbar",
|
||||
"no-states-available": "Keine Zustände verfügbar",
|
||||
"background-color": "Hintergrundfarbe",
|
||||
"select-background-color": "Hintergrundfarbe auswählen"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "About",
|
||||
"add": "Add",
|
||||
"administration": "Administration",
|
||||
"and": "and",
|
||||
"apply": "Apply",
|
||||
"ascii-only": "ASCII characters only",
|
||||
"cancel": "Cancel",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Library management",
|
||||
"logout": "Logout",
|
||||
"name": "Name",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "Password must be between 6 and 255 characters",
|
||||
"platform": "Platform",
|
||||
"platforms": "Platforms",
|
||||
|
||||
34
frontend/src/locales/en_GB/patcher.json
Normal file
34
frontend/src/locales/en_GB/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM Patcher",
|
||||
"subtitle": "Choose a base ROM and a patch file, then apply to download the patched ROM.",
|
||||
"rom-file": "ROM file",
|
||||
"patch-file": "Patch file",
|
||||
"drop-rom-here": "Drop ROM here",
|
||||
"drop-patch-here": "Drop patch here",
|
||||
"drag-drop-rom": "Drag & drop a ROM file or click to browse.",
|
||||
"drag-drop-patch": "Drag & drop a patch file or click to browse.",
|
||||
"choose-rom": "Choose ROM",
|
||||
"choose-patch": "Choose patch",
|
||||
"replace": "Replace",
|
||||
"supported-formats": "Supported patch formats",
|
||||
"download-locally": "Download patched ROM",
|
||||
"upload-to-romm": "Upload to RomM",
|
||||
"output-filename": "Output filename (optional)",
|
||||
"apply-download-upload": "Apply, Download & Upload",
|
||||
"apply-upload": "Apply & Upload",
|
||||
"apply-download": "Apply & Download",
|
||||
"powered-by": "Powered by patcherjs",
|
||||
"error-no-rom": "Please select a ROM file.",
|
||||
"error-no-patch": "Please select a patch file.",
|
||||
"error-no-platform": "Please select a platform to upload to.",
|
||||
"error-no-action": "Please select at least one action: download or upload.",
|
||||
"status-preparing": "Preparing files...",
|
||||
"status-downloading": "Downloading patched ROM...",
|
||||
"status-uploading": "Uploading to RomM...",
|
||||
"success-uploaded": "uploaded",
|
||||
"success-downloaded": "downloaded",
|
||||
"success-message": "Patched ROM {actions} successfully!",
|
||||
"error-upload-failed": "Unable to upload ROM: {error}",
|
||||
"upload-success": "Patched ROM uploaded successfully{errors}. Starting scan...",
|
||||
"upload-errors": " (with some errors)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Select state",
|
||||
"change-state": "Change state",
|
||||
"deselect-state": "Deselect state",
|
||||
"no-save-selected": "No save selected",
|
||||
"no-state-selected": "No state selected",
|
||||
"no-saves-available": "No saves available",
|
||||
"no-states-available": "No states available",
|
||||
"background-color": "Background color",
|
||||
"select-background-color": "Select background color"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "About",
|
||||
"add": "Add",
|
||||
"administration": "Administration",
|
||||
"and": "and",
|
||||
"apply": "Apply",
|
||||
"ascii-only": "ASCII characters only",
|
||||
"cancel": "Cancel",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Library management",
|
||||
"logout": "Logout",
|
||||
"name": "Name",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "Password must be between 6 and 255 characters",
|
||||
"platform": "Platform",
|
||||
"platforms": "Platforms",
|
||||
|
||||
34
frontend/src/locales/en_US/patcher.json
Normal file
34
frontend/src/locales/en_US/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM Patcher",
|
||||
"subtitle": "Choose a base ROM and a patch file, then apply to download the patched ROM.",
|
||||
"rom-file": "ROM file",
|
||||
"patch-file": "Patch file",
|
||||
"drop-rom-here": "Drop ROM here",
|
||||
"drop-patch-here": "Drop patch here",
|
||||
"drag-drop-rom": "Drag & drop a ROM file or click to browse.",
|
||||
"drag-drop-patch": "Drag & drop a patch file or click to browse.",
|
||||
"choose-rom": "Choose ROM",
|
||||
"choose-patch": "Choose patch",
|
||||
"replace": "Replace",
|
||||
"supported-formats": "Supported patch formats",
|
||||
"download-locally": "Download patched ROM",
|
||||
"upload-to-romm": "Upload to RomM",
|
||||
"output-filename": "Output filename (optional)",
|
||||
"apply-download-upload": "Apply, Download & Upload",
|
||||
"apply-upload": "Apply & Upload",
|
||||
"apply-download": "Apply & Download",
|
||||
"powered-by": "Powered by patcherjs",
|
||||
"error-no-rom": "Please select a ROM file.",
|
||||
"error-no-patch": "Please select a patch file.",
|
||||
"error-no-platform": "Please select a platform to upload to.",
|
||||
"error-no-action": "Please select at least one action: download or upload.",
|
||||
"status-preparing": "Preparing files...",
|
||||
"status-downloading": "Downloading patched ROM...",
|
||||
"status-uploading": "Uploading to RomM...",
|
||||
"success-uploaded": "uploaded",
|
||||
"success-downloaded": "downloaded",
|
||||
"success-message": "Patched ROM {actions} successfully!",
|
||||
"error-upload-failed": "Unable to upload ROM: {error}",
|
||||
"upload-success": "Patched ROM uploaded successfully{errors}. Starting scan...",
|
||||
"upload-errors": " (with some errors)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Select state",
|
||||
"change-state": "Change state",
|
||||
"deselect-state": "Deselect state",
|
||||
"no-save-selected": "No save selected",
|
||||
"no-state-selected": "No state selected",
|
||||
"no-saves-available": "No saves available",
|
||||
"no-states-available": "No states available",
|
||||
"background-color": "Background color",
|
||||
"select-background-color": "Select background color"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "Acerca de",
|
||||
"add": "Añadir",
|
||||
"administration": "Administración",
|
||||
"and": "y",
|
||||
"apply": "Aplicar",
|
||||
"ascii-only": "Solo caracteres ASCII",
|
||||
"cancel": "Cancelar",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Gestionar biblioteca",
|
||||
"logout": "Cerrar sesión",
|
||||
"name": "Nombre",
|
||||
"patcher": "Parchador",
|
||||
"password-length": "La contraseña debe tener entre 6 y 255 caracteres",
|
||||
"platform": "Plataforma",
|
||||
"platforms": "Plataformas",
|
||||
|
||||
34
frontend/src/locales/es_ES/patcher.json
Normal file
34
frontend/src/locales/es_ES/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Parchador de ROM",
|
||||
"subtitle": "Elige una ROM base y un archivo de parche, luego aplica para descargar la ROM parcheada.",
|
||||
"rom-file": "Archivo ROM",
|
||||
"patch-file": "Archivo de parche",
|
||||
"drop-rom-here": "Suelta la ROM aquí",
|
||||
"drop-patch-here": "Suelta el parche aquí",
|
||||
"drag-drop-rom": "Arrastra y suelta un archivo ROM o haz clic para explorar.",
|
||||
"drag-drop-patch": "Arrastra y suelta un archivo de parche o haz clic para explorar.",
|
||||
"choose-rom": "Elegir ROM",
|
||||
"choose-patch": "Elegir parche",
|
||||
"replace": "Reemplazar",
|
||||
"supported-formats": "Formatos de parche compatibles",
|
||||
"download-locally": "Descargar ROM parcheada",
|
||||
"upload-to-romm": "Subir a RomM",
|
||||
"output-filename": "Nombre del archivo de salida (opcional)",
|
||||
"apply-download-upload": "Aplicar, Descargar y Subir",
|
||||
"apply-upload": "Aplicar y Subir",
|
||||
"apply-download": "Aplicar y Descargar",
|
||||
"powered-by": "Con tecnología de patcherjs",
|
||||
"error-no-rom": "Por favor, selecciona un archivo ROM.",
|
||||
"error-no-patch": "Por favor, selecciona un archivo de parche.",
|
||||
"error-no-platform": "Por favor, selecciona una plataforma para subir.",
|
||||
"error-no-action": "Por favor, selecciona al menos una acción: descargar o subir.",
|
||||
"status-preparing": "Preparando archivos...",
|
||||
"status-downloading": "Descargando ROM parcheada...",
|
||||
"status-uploading": "Subiendo a RomM...",
|
||||
"success-uploaded": "subida",
|
||||
"success-downloaded": "descargada",
|
||||
"success-message": "ROM parcheada {actions} con éxito!",
|
||||
"error-upload-failed": "No se pudo subir la ROM: {error}",
|
||||
"upload-success": "ROM parcheada subida con éxito{errors}. Iniciando escaneo...",
|
||||
"upload-errors": " (con algunos errores)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Seleccionar estado",
|
||||
"change-state": "Cambiar estado",
|
||||
"deselect-state": "Deseleccionar estado",
|
||||
"no-save-selected": "Ningún guardado seleccionado",
|
||||
"no-state-selected": "Ningún estado seleccionado",
|
||||
"no-saves-available": "No hay guardados disponibles",
|
||||
"no-states-available": "No hay estados disponibles",
|
||||
"background-color": "Color de fondo",
|
||||
"select-background-color": "Seleccionar color de fondo"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "À propos",
|
||||
"add": "Ajouter",
|
||||
"administration": "Administration",
|
||||
"and": "et",
|
||||
"apply": "Appliquer",
|
||||
"ascii-only": "Uniquement des caractères ASCII",
|
||||
"cancel": "Annuler",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Gestion de la bibliothèque",
|
||||
"logout": "Se déconnecter",
|
||||
"name": "Nom",
|
||||
"patcher": "Patcheur",
|
||||
"password-length": "Le mot de passe doit contenir entre 6 et 255 caractères",
|
||||
"platform": "Plateforme",
|
||||
"platforms": "Plateformes",
|
||||
|
||||
34
frontend/src/locales/fr_FR/patcher.json
Normal file
34
frontend/src/locales/fr_FR/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Patcheur de ROM",
|
||||
"subtitle": "Choisissez une ROM de base et un fichier de patch, puis appliquez pour télécharger la ROM patchée.",
|
||||
"rom-file": "Fichier ROM",
|
||||
"patch-file": "Fichier de patch",
|
||||
"drop-rom-here": "Déposez la ROM ici",
|
||||
"drop-patch-here": "Déposez le patch ici",
|
||||
"drag-drop-rom": "Glissez-déposez un fichier ROM ou cliquez pour parcourir.",
|
||||
"drag-drop-patch": "Glissez-déposez un fichier de patch ou cliquez pour parcourir.",
|
||||
"choose-rom": "Choisir une ROM",
|
||||
"choose-patch": "Choisir un patch",
|
||||
"replace": "Remplacer",
|
||||
"supported-formats": "Formats de patch pris en charge",
|
||||
"download-locally": "Télécharger la ROM patchée",
|
||||
"upload-to-romm": "Téléverser vers RomM",
|
||||
"output-filename": "Nom du fichier de sortie (optionnel)",
|
||||
"apply-download-upload": "Appliquer, Télécharger et Téléverser",
|
||||
"apply-upload": "Appliquer et Téléverser",
|
||||
"apply-download": "Appliquer et Télécharger",
|
||||
"powered-by": "Propulsé par patcherjs",
|
||||
"error-no-rom": "Veuillez sélectionner un fichier ROM.",
|
||||
"error-no-patch": "Veuillez sélectionner un fichier de patch.",
|
||||
"error-no-platform": "Veuillez sélectionner une plateforme pour le téléversement.",
|
||||
"error-no-action": "Veuillez sélectionner au moins une action : télécharger ou téléverser.",
|
||||
"status-preparing": "Préparation des fichiers...",
|
||||
"status-downloading": "Téléchargement de la ROM patchée...",
|
||||
"status-uploading": "Téléversement vers RomM...",
|
||||
"success-uploaded": "téléversée",
|
||||
"success-downloaded": "téléchargée",
|
||||
"success-message": "ROM patchée {actions} avec succès !",
|
||||
"error-upload-failed": "Impossible de téléverser la ROM : {error}",
|
||||
"upload-success": "ROM patchée téléversée avec succès{errors}. Démarrage de l'analyse...",
|
||||
"upload-errors": " (avec quelques erreurs)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Sélectionner l'état",
|
||||
"change-state": "Changer l'état",
|
||||
"deselect-state": "Désélectionner l'état",
|
||||
"no-save-selected": "Aucune sauvegarde sélectionnée",
|
||||
"no-state-selected": "Aucun état sélectionné",
|
||||
"no-saves-available": "Aucune sauvegarde disponible",
|
||||
"no-states-available": "Aucun état disponible",
|
||||
"background-color": "Couleur d'arrière-plan",
|
||||
"select-background-color": "Sélectionner la couleur d'arrière-plan"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "Rólunk",
|
||||
"add": "Hozzáadás",
|
||||
"administration": "Adminisztráció",
|
||||
"and": "és",
|
||||
"apply": "Alkalmaz",
|
||||
"ascii-only": "Csak ASCII karakterek",
|
||||
"cancel": "Mégse",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Könyvtár Menedzsment",
|
||||
"logout": "Kijelentkezés",
|
||||
"name": "Név",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "A jelszó hossza 6 és 255 karakter hosszú lehet",
|
||||
"platform": "Platform",
|
||||
"platforms": "Platformok",
|
||||
|
||||
34
frontend/src/locales/hu_HU/patcher.json
Normal file
34
frontend/src/locales/hu_HU/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM Patcher",
|
||||
"subtitle": "Válasszon egy alap ROM-ot és egy patch fájlt, majd alkalmazza a patchelt ROM letöltéséhez.",
|
||||
"rom-file": "ROM fájl",
|
||||
"patch-file": "Patch fájl",
|
||||
"drop-rom-here": "ROM ide húzása",
|
||||
"drop-patch-here": "Patch ide húzása",
|
||||
"drag-drop-rom": "Húzza ide a ROM fájlt vagy kattintson a tallózáshoz.",
|
||||
"drag-drop-patch": "Húzza ide a patch fájlt vagy kattintson a tallózáshoz.",
|
||||
"choose-rom": "ROM kiválasztása",
|
||||
"choose-patch": "Patch kiválasztása",
|
||||
"replace": "Csere",
|
||||
"supported-formats": "Támogatott patch formátumok",
|
||||
"download-locally": "Patchelt ROM letöltése",
|
||||
"upload-to-romm": "Feltöltés RomM-be",
|
||||
"output-filename": "Kimeneti fájlnév (opcionális)",
|
||||
"apply-download-upload": "Alkalmazás, Letöltés és Feltöltés",
|
||||
"apply-upload": "Alkalmazás és Feltöltés",
|
||||
"apply-download": "Alkalmazás és Letöltés",
|
||||
"powered-by": "Működteti: patcherjs",
|
||||
"error-no-rom": "Kérjük, válasszon egy ROM fájlt.",
|
||||
"error-no-patch": "Kérjük, válasszon egy patch fájlt.",
|
||||
"error-no-platform": "Kérjük, válasszon egy platformot a feltöltéshez.",
|
||||
"error-no-action": "Kérjük, válasszon legalább egy műveletet: letöltés vagy feltöltés.",
|
||||
"status-preparing": "Fájlok előkészítése...",
|
||||
"status-downloading": "Patchelt ROM letöltése...",
|
||||
"status-uploading": "Feltöltés RomM-be...",
|
||||
"success-uploaded": "feltöltve",
|
||||
"success-downloaded": "letöltve",
|
||||
"success-message": "Patchelt ROM {actions} sikeresen!",
|
||||
"error-upload-failed": "Nem sikerült feltölteni a ROM-ot: {error}",
|
||||
"upload-success": "Patchelt ROM sikeresen feltöltve{errors}. Szkennelés indítása...",
|
||||
"upload-errors": " (néhány hibával)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Állás kiválasztása",
|
||||
"change-state": "Állás cseréje",
|
||||
"deselect-state": "Állás kiválasztásának törlése",
|
||||
"no-save-selected": "Nincs kiválasztott mentés",
|
||||
"no-state-selected": "Nincs kiválasztott állás",
|
||||
"no-saves-available": "Nincs elérhető mentés",
|
||||
"no-states-available": "Nincs elérhető állás",
|
||||
"background-color": "Háttérszín",
|
||||
"select-background-color": "Háttérszín kiválasztása"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "Informazioni",
|
||||
"add": "Aggiungi",
|
||||
"administration": "Amministrazione",
|
||||
"and": "e",
|
||||
"apply": "Applica",
|
||||
"ascii-only": "Solo caratteri ASCII",
|
||||
"cancel": "Annulla",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Gestione Libreria",
|
||||
"logout": "Logout",
|
||||
"name": "Nome",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "La password deve essere compresa tra 6 e 255 caratteri",
|
||||
"platform": "Piattaforma",
|
||||
"platforms": "Piattaforme",
|
||||
|
||||
34
frontend/src/locales/it_IT/patcher.json
Normal file
34
frontend/src/locales/it_IT/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Patcher ROM",
|
||||
"subtitle": "Scegli una ROM di base e un file di patch, quindi applica per scaricare la ROM patchata.",
|
||||
"rom-file": "File ROM",
|
||||
"patch-file": "File patch",
|
||||
"drop-rom-here": "Rilascia la ROM qui",
|
||||
"drop-patch-here": "Rilascia la patch qui",
|
||||
"drag-drop-rom": "Trascina e rilascia un file ROM o fai clic per sfogliare.",
|
||||
"drag-drop-patch": "Trascina e rilascia un file patch o fai clic per sfogliare.",
|
||||
"choose-rom": "Scegli ROM",
|
||||
"choose-patch": "Scegli patch",
|
||||
"replace": "Sostituisci",
|
||||
"supported-formats": "Formati patch supportati",
|
||||
"download-locally": "Scarica ROM patchata",
|
||||
"upload-to-romm": "Carica su RomM",
|
||||
"output-filename": "Nome file di output (opzionale)",
|
||||
"apply-download-upload": "Applica, Scarica e Carica",
|
||||
"apply-upload": "Applica e Carica",
|
||||
"apply-download": "Applica e Scarica",
|
||||
"powered-by": "Basato su patcherjs",
|
||||
"error-no-rom": "Seleziona un file ROM.",
|
||||
"error-no-patch": "Seleziona un file patch.",
|
||||
"error-no-platform": "Seleziona una piattaforma per il caricamento.",
|
||||
"error-no-action": "Seleziona almeno un'azione: scarica o carica.",
|
||||
"status-preparing": "Preparazione file...",
|
||||
"status-downloading": "Download ROM patchata...",
|
||||
"status-uploading": "Caricamento su RomM...",
|
||||
"success-uploaded": "caricata",
|
||||
"success-downloaded": "scaricata",
|
||||
"success-message": "ROM patchata {actions} con successo!",
|
||||
"error-upload-failed": "Impossibile caricare la ROM: {error}",
|
||||
"upload-success": "ROM patchata caricata con successo{errors}. Avvio scansione...",
|
||||
"upload-errors": " (con alcuni errori)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Seleziona Stato",
|
||||
"change-state": "Cambia Stato",
|
||||
"deselect-state": "Deseleziona Stato",
|
||||
"no-save-selected": "Nessun salvataggio selezionato",
|
||||
"no-state-selected": "Nessuno stato selezionato",
|
||||
"no-saves-available": "Nessun salvataggio disponibile",
|
||||
"no-states-available": "Nessuno stato disponibile",
|
||||
"background-color": "Colore di sfondo",
|
||||
"select-background-color": "Seleziona colore di sfondo"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "概要",
|
||||
"add": "追加",
|
||||
"administration": "管理者メニュー",
|
||||
"and": "と",
|
||||
"apply": "適用",
|
||||
"ascii-only": "ASCII文字のみ",
|
||||
"cancel": "キャンセル",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "ライブラリ管理",
|
||||
"logout": "ログアウト",
|
||||
"name": "名前",
|
||||
"patcher": "パッチャー",
|
||||
"password-length": "パスワードは6文字から255文字の間である必要があります",
|
||||
"platform": "プラットフォーム",
|
||||
"platforms": "プラットフォーム",
|
||||
|
||||
34
frontend/src/locales/ja_JP/patcher.json
Normal file
34
frontend/src/locales/ja_JP/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROMパッチャー",
|
||||
"subtitle": "ベースROMとパッチファイルを選択し、適用してパッチされたROMをダウンロードします。",
|
||||
"rom-file": "ROMファイル",
|
||||
"patch-file": "パッチファイル",
|
||||
"drop-rom-here": "ここにROMをドロップ",
|
||||
"drop-patch-here": "ここにパッチをドロップ",
|
||||
"drag-drop-rom": "ROMファイルをドラッグ&ドロップするか、クリックして参照してください。",
|
||||
"drag-drop-patch": "パッチファイルをドラッグ&ドロップするか、クリックして参照してください。",
|
||||
"choose-rom": "ROMを選択",
|
||||
"choose-patch": "パッチを選択",
|
||||
"replace": "置換",
|
||||
"supported-formats": "サポートされているパッチ形式",
|
||||
"download-locally": "パッチ済みROMをダウンロード",
|
||||
"upload-to-romm": "RomMにアップロード",
|
||||
"output-filename": "出力ファイル名(オプション)",
|
||||
"apply-download-upload": "適用、ダウンロード&アップロード",
|
||||
"apply-upload": "適用&アップロード",
|
||||
"apply-download": "適用&ダウンロード",
|
||||
"powered-by": "patcherjsで動作",
|
||||
"error-no-rom": "ROMファイルを選択してください。",
|
||||
"error-no-patch": "パッチファイルを選択してください。",
|
||||
"error-no-platform": "アップロード先のプラットフォームを選択してください。",
|
||||
"error-no-action": "少なくとも1つのアクション(ダウンロードまたはアップロード)を選択してください。",
|
||||
"status-preparing": "ファイルを準備中...",
|
||||
"status-downloading": "パッチ済みROMをダウンロード中...",
|
||||
"status-uploading": "RomMにアップロード中...",
|
||||
"success-uploaded": "アップロードされました",
|
||||
"success-downloaded": "ダウンロードされました",
|
||||
"success-message": "パッチ済みROMが{actions}成功しました!",
|
||||
"error-upload-failed": "ROMをアップロードできません:{error}",
|
||||
"upload-success": "パッチ済みROMが正常にアップロードされました{errors}。スキャンを開始しています...",
|
||||
"upload-errors": "(一部エラーあり)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "ステートを選択",
|
||||
"change-state": "ステートを変更",
|
||||
"deselect-state": "ステートを解除",
|
||||
"no-save-selected": "セーブデータが選択されていません",
|
||||
"no-state-selected": "ステートが選択されていません",
|
||||
"no-saves-available": "利用可能なセーブデータがありません",
|
||||
"no-states-available": "利用可能なステートがありません",
|
||||
"background-color": "背景色",
|
||||
"select-background-color": "背景色を選択"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "정보",
|
||||
"add": "추가",
|
||||
"administration": "유저 관리",
|
||||
"and": "및",
|
||||
"apply": "적용",
|
||||
"ascii-only": "ASCII 문자만",
|
||||
"cancel": "취소",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "라이브러리 관리",
|
||||
"logout": "로그아웃",
|
||||
"name": "이름",
|
||||
"patcher": "패처",
|
||||
"password-length": "비밀번호는 6자에서 255자 사이여야 합니다",
|
||||
"platform": "플랫폼",
|
||||
"platforms": "플랫폼",
|
||||
|
||||
34
frontend/src/locales/ko_KR/patcher.json
Normal file
34
frontend/src/locales/ko_KR/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM 패처",
|
||||
"subtitle": "기본 ROM과 패치 파일을 선택한 다음 적용하여 패치된 ROM을 다운로드하세요.",
|
||||
"rom-file": "ROM 파일",
|
||||
"patch-file": "패치 파일",
|
||||
"drop-rom-here": "여기에 ROM을 드롭하세요",
|
||||
"drop-patch-here": "여기에 패치를 드롭하세요",
|
||||
"drag-drop-rom": "ROM 파일을 드래그 앤 드롭하거나 클릭하여 찾아보세요.",
|
||||
"drag-drop-patch": "패치 파일을 드래그 앤 드롭하거나 클릭하여 찾아보세요.",
|
||||
"choose-rom": "ROM 선택",
|
||||
"choose-patch": "패치 선택",
|
||||
"replace": "교체",
|
||||
"supported-formats": "지원되는 패치 형식",
|
||||
"download-locally": "패치된 ROM 다운로드",
|
||||
"upload-to-romm": "RomM에 업로드",
|
||||
"output-filename": "출력 파일명 (선택사항)",
|
||||
"apply-download-upload": "적용, 다운로드 및 업로드",
|
||||
"apply-upload": "적용 및 업로드",
|
||||
"apply-download": "적용 및 다운로드",
|
||||
"powered-by": "patcherjs 기반",
|
||||
"error-no-rom": "ROM 파일을 선택하세요.",
|
||||
"error-no-patch": "패치 파일을 선택하세요.",
|
||||
"error-no-platform": "업로드할 플랫폼을 선택하세요.",
|
||||
"error-no-action": "최소 하나의 작업을 선택하세요: 다운로드 또는 업로드.",
|
||||
"status-preparing": "파일 준비 중...",
|
||||
"status-downloading": "패치된 ROM 다운로드 중...",
|
||||
"status-uploading": "RomM에 업로드 중...",
|
||||
"success-uploaded": "업로드됨",
|
||||
"success-downloaded": "다운로드됨",
|
||||
"success-message": "패치된 ROM이 성공적으로 {actions}되었습니다!",
|
||||
"error-upload-failed": "ROM을 업로드할 수 없습니다: {error}",
|
||||
"upload-success": "패치된 ROM이 성공적으로 업로드되었습니다{errors}. 스캔을 시작합니다...",
|
||||
"upload-errors": " (일부 오류 발생)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "상태 선택",
|
||||
"change-state": "상태 변경",
|
||||
"deselect-state": "상태 선택 해제",
|
||||
"no-save-selected": "선택된 세이브 없음",
|
||||
"no-state-selected": "선택된 상태 없음",
|
||||
"no-saves-available": "사용 가능한 세이브 없음",
|
||||
"no-states-available": "사용 가능한 상태 없음",
|
||||
"background-color": "배경색",
|
||||
"select-background-color": "배경색 선택"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "O aplikacji",
|
||||
"add": "Dodaj",
|
||||
"administration": "Administracja",
|
||||
"and": "i",
|
||||
"apply": "Zastosuj",
|
||||
"ascii-only": "Tylko znaki ASCII",
|
||||
"cancel": "Anuluj",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Zarządzanie biblioteką",
|
||||
"logout": "Wyloguj się",
|
||||
"name": "Nazwa",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "Hasło musi mieć od 6 do 255 znaków",
|
||||
"platform": "Platforma",
|
||||
"platforms": "Platformy",
|
||||
|
||||
34
frontend/src/locales/pl_PL/patcher.json
Normal file
34
frontend/src/locales/pl_PL/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Patcher ROM",
|
||||
"subtitle": "Wybierz podstawowy ROM i plik łatki, a następnie zastosuj, aby pobrać załatany ROM.",
|
||||
"rom-file": "Plik ROM",
|
||||
"patch-file": "Plik łatki",
|
||||
"drop-rom-here": "Upuść ROM tutaj",
|
||||
"drop-patch-here": "Upuść łatkę tutaj",
|
||||
"drag-drop-rom": "Przeciągnij i upuść plik ROM lub kliknij, aby przeglądać.",
|
||||
"drag-drop-patch": "Przeciągnij i upuść plik łatki lub kliknij, aby przeglądać.",
|
||||
"choose-rom": "Wybierz ROM",
|
||||
"choose-patch": "Wybierz łatkę",
|
||||
"replace": "Zamień",
|
||||
"supported-formats": "Obsługiwane formaty łatek",
|
||||
"download-locally": "Pobierz załatany ROM",
|
||||
"upload-to-romm": "Prześlij do RomM",
|
||||
"output-filename": "Nazwa pliku wyjściowego (opcjonalnie)",
|
||||
"apply-download-upload": "Zastosuj, Pobierz i Prześlij",
|
||||
"apply-upload": "Zastosuj i Prześlij",
|
||||
"apply-download": "Zastosuj i Pobierz",
|
||||
"powered-by": "Napędzane przez patcherjs",
|
||||
"error-no-rom": "Wybierz plik ROM.",
|
||||
"error-no-patch": "Wybierz plik łatki.",
|
||||
"error-no-platform": "Wybierz platformę do przesłania.",
|
||||
"error-no-action": "Wybierz co najmniej jedną akcję: pobierz lub prześlij.",
|
||||
"status-preparing": "Przygotowywanie plików...",
|
||||
"status-downloading": "Pobieranie załatanego ROM...",
|
||||
"status-uploading": "Przesyłanie do RomM...",
|
||||
"success-uploaded": "przesłano",
|
||||
"success-downloaded": "pobrano",
|
||||
"success-message": "Załatany ROM {actions} pomyślnie!",
|
||||
"error-upload-failed": "Nie można przesłać ROM: {error}",
|
||||
"upload-success": "Załatany ROM przesłano pomyślnie{errors}. Rozpoczynanie skanowania...",
|
||||
"upload-errors": " (z niektórymi błędami)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Wybierz stan",
|
||||
"change-state": "Zmień stan",
|
||||
"deselect-state": "Odznacz stan",
|
||||
"no-save-selected": "Nie wybrano zapisu",
|
||||
"no-state-selected": "Nie wybrano stanu",
|
||||
"no-saves-available": "Brak dostępnych zapisów",
|
||||
"no-states-available": "Brak dostępnych stanów",
|
||||
"background-color": "Kolor tła",
|
||||
"select-background-color": "Wybierz kolor tła"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "Sobre",
|
||||
"add": "Adicionar",
|
||||
"administration": "Administração",
|
||||
"and": "e",
|
||||
"apply": "Aplicar",
|
||||
"ascii-only": "Somente caracteres ASCII",
|
||||
"cancel": "Cancelar",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Gerenciamento de biblioteca",
|
||||
"logout": "Sair",
|
||||
"name": "Nome",
|
||||
"patcher": "Patchador",
|
||||
"password-length": "Senha deve ter entre 6 e 255 caracteres",
|
||||
"platform": "Plataforma",
|
||||
"platforms": "Plataformas",
|
||||
|
||||
34
frontend/src/locales/pt_BR/patcher.json
Normal file
34
frontend/src/locales/pt_BR/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Patchador de ROM",
|
||||
"subtitle": "Escolha uma ROM base e um arquivo de patch, depois aplique para baixar a ROM com patch.",
|
||||
"rom-file": "Arquivo ROM",
|
||||
"patch-file": "Arquivo de patch",
|
||||
"drop-rom-here": "Solte a ROM aqui",
|
||||
"drop-patch-here": "Solte o patch aqui",
|
||||
"drag-drop-rom": "Arraste e solte um arquivo ROM ou clique para navegar.",
|
||||
"drag-drop-patch": "Arraste e solte um arquivo de patch ou clique para navegar.",
|
||||
"choose-rom": "Escolher ROM",
|
||||
"choose-patch": "Escolher patch",
|
||||
"replace": "Substituir",
|
||||
"supported-formats": "Formatos de patch suportados",
|
||||
"download-locally": "Baixar ROM com patch",
|
||||
"upload-to-romm": "Enviar para RomM",
|
||||
"output-filename": "Nome do arquivo de saída (opcional)",
|
||||
"apply-download-upload": "Aplicar, Baixar e Enviar",
|
||||
"apply-upload": "Aplicar e Enviar",
|
||||
"apply-download": "Aplicar e Baixar",
|
||||
"powered-by": "Desenvolvido com patcherjs",
|
||||
"error-no-rom": "Por favor, selecione um arquivo ROM.",
|
||||
"error-no-patch": "Por favor, selecione um arquivo de patch.",
|
||||
"error-no-platform": "Por favor, selecione uma plataforma para enviar.",
|
||||
"error-no-action": "Por favor, selecione pelo menos uma ação: baixar ou enviar.",
|
||||
"status-preparing": "Preparando arquivos...",
|
||||
"status-downloading": "Baixando ROM com patch...",
|
||||
"status-uploading": "Enviando para RomM...",
|
||||
"success-uploaded": "enviada",
|
||||
"success-downloaded": "baixada",
|
||||
"success-message": "ROM com patch {actions} com sucesso!",
|
||||
"error-upload-failed": "Não foi possível enviar a ROM: {error}",
|
||||
"upload-success": "ROM com patch enviada com sucesso{errors}. Iniciando varredura...",
|
||||
"upload-errors": " (com alguns erros)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Selecionar estado",
|
||||
"change-state": "Alterar estado",
|
||||
"deselect-state": "Desmarcar estado",
|
||||
"no-save-selected": "Nenhum save selecionado",
|
||||
"no-state-selected": "Nenhum estado selecionado",
|
||||
"no-saves-available": "Nenhum save disponível",
|
||||
"no-states-available": "Nenhum estado disponível",
|
||||
"background-color": "Cor de fundo",
|
||||
"select-background-color": "Selecionar cor de fundo"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "Despre",
|
||||
"add": "Adaugă",
|
||||
"administration": "Administrare",
|
||||
"and": "și",
|
||||
"apply": "Aplică",
|
||||
"ascii-only": "Caractere ASCII numai",
|
||||
"cancel": "Anulează",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Gestionare bibliotecă",
|
||||
"logout": "Deconectare",
|
||||
"name": "Nume",
|
||||
"patcher": "Patcher",
|
||||
"password-length": "Parola trebuie să conțină între 6 și 255 de caractere",
|
||||
"platform": "Platformă",
|
||||
"platforms": "Platforme",
|
||||
|
||||
34
frontend/src/locales/ro_RO/patcher.json
Normal file
34
frontend/src/locales/ro_RO/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Patcher ROM",
|
||||
"subtitle": "Alegeți un ROM de bază și un fișier patch, apoi aplicați pentru a descărca ROM-ul patch-uit.",
|
||||
"rom-file": "Fișier ROM",
|
||||
"patch-file": "Fișier patch",
|
||||
"drop-rom-here": "Trageți ROM-ul aici",
|
||||
"drop-patch-here": "Trageți patch-ul aici",
|
||||
"drag-drop-rom": "Trageți și plasați un fișier ROM sau faceți clic pentru a naviga.",
|
||||
"drag-drop-patch": "Trageți și plasați un fișier patch sau faceți clic pentru a naviga.",
|
||||
"choose-rom": "Alegeți ROM",
|
||||
"choose-patch": "Alegeți patch",
|
||||
"replace": "Înlocuiți",
|
||||
"supported-formats": "Formate patch acceptate",
|
||||
"download-locally": "Descărcați ROM-ul patch-uit",
|
||||
"upload-to-romm": "Încărcați în RomM",
|
||||
"output-filename": "Nume fișier de ieșire (opțional)",
|
||||
"apply-download-upload": "Aplicați, Descărcați și Încărcați",
|
||||
"apply-upload": "Aplicați și Încărcați",
|
||||
"apply-download": "Aplicați și Descărcați",
|
||||
"powered-by": "Propulsat de patcherjs",
|
||||
"error-no-rom": "Vă rugăm să selectați un fișier ROM.",
|
||||
"error-no-patch": "Vă rugăm să selectați un fișier patch.",
|
||||
"error-no-platform": "Vă rugăm să selectați o platformă pentru încărcare.",
|
||||
"error-no-action": "Vă rugăm să selectați cel puțin o acțiune: descărcare sau încărcare.",
|
||||
"status-preparing": "Pregătirea fișierelor...",
|
||||
"status-downloading": "Descărcarea ROM-ului patch-uit...",
|
||||
"status-uploading": "Încărcarea în RomM...",
|
||||
"success-uploaded": "încărcat",
|
||||
"success-downloaded": "descărcat",
|
||||
"success-message": "ROM-ul patch-uit {actions} cu succes!",
|
||||
"error-upload-failed": "Nu s-a putut încărca ROM-ul: {error}",
|
||||
"upload-success": "ROM-ul patch-uit a fost încărcat cu succes{errors}. Se inițiază scanarea...",
|
||||
"upload-errors": " (cu unele erori)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Selectează stare",
|
||||
"change-state": "Schimbă stare",
|
||||
"deselect-state": "Deselectează stare",
|
||||
"no-save-selected": "Nicio salvare selectată",
|
||||
"no-state-selected": "Nicio stare selectată",
|
||||
"no-saves-available": "Nicio salvare disponibilă",
|
||||
"no-states-available": "Nicio stare disponibilă",
|
||||
"background-color": "Culoare de fundal",
|
||||
"select-background-color": "Selectează culoarea de fundal"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "О приложении",
|
||||
"add": "Добавить",
|
||||
"administration": "Администрирование",
|
||||
"and": "и",
|
||||
"apply": "Применить",
|
||||
"ascii-only": "Только ASCII символы",
|
||||
"cancel": "Отменить",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "Управление библиотекой",
|
||||
"logout": "Выйти",
|
||||
"name": "Имя",
|
||||
"patcher": "Патчер",
|
||||
"password-length": "Пароль должен содержать от 6 до 255 символов",
|
||||
"platform": "Платформа",
|
||||
"platforms": "Платформы",
|
||||
|
||||
34
frontend/src/locales/ru_RU/patcher.json
Normal file
34
frontend/src/locales/ru_RU/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "Пропатчивание ROM",
|
||||
"subtitle": "Выберите базовый ROM и файл патча, затем примените для загрузки пропатченного ROM.",
|
||||
"rom-file": "Файл ROM",
|
||||
"patch-file": "Файл патча",
|
||||
"drop-rom-here": "Перетащите ROM сюда",
|
||||
"drop-patch-here": "Перетащите патч сюда",
|
||||
"drag-drop-rom": "Перетащите файл ROM или нажмите для выбора.",
|
||||
"drag-drop-patch": "Перетащите файл патча или нажмите для выбора.",
|
||||
"choose-rom": "Выбрать ROM",
|
||||
"choose-patch": "Выбрать патч",
|
||||
"replace": "Заменить",
|
||||
"supported-formats": "Поддерживаемые форматы патчей",
|
||||
"download-locally": "Загрузить пропатченный ROM",
|
||||
"upload-to-romm": "Загрузить в RomM",
|
||||
"output-filename": "Имя выходного файла (необязательно)",
|
||||
"apply-download-upload": "Применить, Загрузить и Отправить",
|
||||
"apply-upload": "Применить и Отправить",
|
||||
"apply-download": "Применить и Загрузить",
|
||||
"powered-by": "Работает на patcherjs",
|
||||
"error-no-rom": "Пожалуйста, выберите файл ROM.",
|
||||
"error-no-patch": "Пожалуйста, выберите файл патча.",
|
||||
"error-no-platform": "Пожалуйста, выберите платформу для загрузки.",
|
||||
"error-no-action": "Пожалуйста, выберите хотя бы одно действие: загрузка или отправка.",
|
||||
"status-preparing": "Подготовка файлов...",
|
||||
"status-downloading": "Загрузка пропатченного ROM...",
|
||||
"status-uploading": "Загрузка в RomM...",
|
||||
"success-uploaded": "загружено",
|
||||
"success-downloaded": "скачано",
|
||||
"success-message": "Пропатченный ROM {actions} успешно!",
|
||||
"error-upload-failed": "Невозможно загрузить ROM: {error}",
|
||||
"upload-success": "Пропатченный ROM успешно загружен{errors}. Запуск сканирования...",
|
||||
"upload-errors": " (с некоторыми ошибками)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "Выбрать состояние",
|
||||
"change-state": "Изменить состояние",
|
||||
"deselect-state": "Снять выбор состояния",
|
||||
"no-save-selected": "Сохранение не выбрано",
|
||||
"no-state-selected": "Состояние не выбрано",
|
||||
"no-saves-available": "Нет доступных сохранений",
|
||||
"no-states-available": "Нет доступных состояний",
|
||||
"background-color": "Цвет фона",
|
||||
"select-background-color": "Выбрать цвет фона"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "关于",
|
||||
"add": "添加",
|
||||
"administration": "管理",
|
||||
"and": "和",
|
||||
"apply": "应用",
|
||||
"ascii-only": "仅限ASCII字符",
|
||||
"cancel": "取消",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "游戏库管理",
|
||||
"logout": "注销",
|
||||
"name": "名称",
|
||||
"patcher": "补丁工具",
|
||||
"password-length": "密码必须介于6到255个字符之间",
|
||||
"platform": "平台",
|
||||
"platforms": "平台",
|
||||
|
||||
34
frontend/src/locales/zh_CN/patcher.json
Normal file
34
frontend/src/locales/zh_CN/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM补丁工具",
|
||||
"subtitle": "选择基础ROM和补丁文件,然后应用以下载打过补丁的ROM。",
|
||||
"rom-file": "ROM文件",
|
||||
"patch-file": "补丁文件",
|
||||
"drop-rom-here": "将ROM拖放到此处",
|
||||
"drop-patch-here": "将补丁拖放到此处",
|
||||
"drag-drop-rom": "拖放ROM文件或点击浏览。",
|
||||
"drag-drop-patch": "拖放补丁文件或点击浏览。",
|
||||
"choose-rom": "选择ROM",
|
||||
"choose-patch": "选择补丁",
|
||||
"replace": "替换",
|
||||
"supported-formats": "支持的补丁格式",
|
||||
"download-locally": "下载打过补丁的ROM",
|
||||
"upload-to-romm": "上传到RomM",
|
||||
"output-filename": "输出文件名(可选)",
|
||||
"apply-download-upload": "应用、下载和上传",
|
||||
"apply-upload": "应用和上传",
|
||||
"apply-download": "应用和下载",
|
||||
"powered-by": "由patcherjs提供支持",
|
||||
"error-no-rom": "请选择一个ROM文件。",
|
||||
"error-no-patch": "请选择一个补丁文件。",
|
||||
"error-no-platform": "请选择一个平台进行上传。",
|
||||
"error-no-action": "请至少选择一个操作:下载或上传。",
|
||||
"status-preparing": "正在准备文件...",
|
||||
"status-downloading": "正在下载打过补丁的ROM...",
|
||||
"status-uploading": "正在上传到RomM...",
|
||||
"success-uploaded": "已上传",
|
||||
"success-downloaded": "已下载",
|
||||
"success-message": "打过补丁的ROM {actions} 成功!",
|
||||
"error-upload-failed": "无法上传ROM:{error}",
|
||||
"upload-success": "打过补丁的ROM上传成功{errors}。正在开始扫描...",
|
||||
"upload-errors": "(有一些错误)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "选择状态",
|
||||
"change-state": "更改状态",
|
||||
"deselect-state": "取消选择状态",
|
||||
"no-save-selected": "未选择存档",
|
||||
"no-state-selected": "未选择状态",
|
||||
"no-saves-available": "无可用存档",
|
||||
"no-states-available": "无可用状态",
|
||||
"background-color": "背景颜色",
|
||||
"select-background-color": "选择背景颜色"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"about": "關於",
|
||||
"add": "新增",
|
||||
"administration": "管理系統",
|
||||
"and": "和",
|
||||
"apply": "套用",
|
||||
"ascii-only": "僅限 ASCII 字元",
|
||||
"cancel": "取消",
|
||||
@@ -26,6 +27,7 @@
|
||||
"library-management": "管理遊戲庫",
|
||||
"logout": "登出",
|
||||
"name": "名稱",
|
||||
"patcher": "修補工具",
|
||||
"password-length": "密碼必須介於 6 到 255 個字元之間",
|
||||
"platform": "平台",
|
||||
"platforms": "平台",
|
||||
|
||||
34
frontend/src/locales/zh_TW/patcher.json
Normal file
34
frontend/src/locales/zh_TW/patcher.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "ROM修補程式",
|
||||
"subtitle": "選擇基礎ROM和修補檔案,然後應用以下載修補後的ROM。",
|
||||
"rom-file": "ROM檔案",
|
||||
"patch-file": "修補檔案",
|
||||
"drop-rom-here": "將ROM拖放到此處",
|
||||
"drop-patch-here": "將修補檔拖放到此處",
|
||||
"drag-drop-rom": "拖放ROM檔案或點擊瀏覽。",
|
||||
"drag-drop-patch": "拖放修補檔案或點擊瀏覽。",
|
||||
"choose-rom": "選擇ROM",
|
||||
"choose-patch": "選擇修補檔",
|
||||
"replace": "取代",
|
||||
"supported-formats": "支援的修補格式",
|
||||
"download-locally": "下載修補後的ROM",
|
||||
"upload-to-romm": "上傳到RomM",
|
||||
"output-filename": "輸出檔案名稱(選填)",
|
||||
"apply-download-upload": "應用、下載及上傳",
|
||||
"apply-upload": "應用及上傳",
|
||||
"apply-download": "應用及下載",
|
||||
"powered-by": "由patcherjs提供支援",
|
||||
"error-no-rom": "請選擇一個ROM檔案。",
|
||||
"error-no-patch": "請選擇一個修補檔案。",
|
||||
"error-no-platform": "請選擇一個平台進行上傳。",
|
||||
"error-no-action": "請至少選擇一個操作:下載或上傳。",
|
||||
"status-preparing": "正在準備檔案...",
|
||||
"status-downloading": "正在下載修補後的ROM...",
|
||||
"status-uploading": "正在上傳到RomM...",
|
||||
"success-uploaded": "已上傳",
|
||||
"success-downloaded": "已下載",
|
||||
"success-message": "修補後的ROM {actions} 成功!",
|
||||
"error-upload-failed": "無法上傳ROM:{error}",
|
||||
"upload-success": "修補後的ROM上傳成功{errors}。正在開始掃描...",
|
||||
"upload-errors": "(有一些錯誤)"
|
||||
}
|
||||
@@ -15,6 +15,10 @@
|
||||
"select-state": "選擇即時存檔",
|
||||
"change-state": "更改即時存檔",
|
||||
"deselect-state": "取消選擇即時存檔",
|
||||
"no-save-selected": "未選擇存檔",
|
||||
"no-state-selected": "未選擇即時存檔",
|
||||
"no-saves-available": "無可用存檔",
|
||||
"no-states-available": "無可用即時存檔",
|
||||
"background-color": "背景顏色",
|
||||
"select-background-color": "選擇背景顏色"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export const ROUTES = {
|
||||
EMULATORJS: "emulatorjs",
|
||||
RUFFLE: "ruffle",
|
||||
SCAN: "scan",
|
||||
PATCHER: "patcher",
|
||||
USER_PROFILE: "user-profile",
|
||||
USER_INTERFACE: "user-interface",
|
||||
LIBRARY_MANAGEMENT: "library-management",
|
||||
@@ -188,6 +189,14 @@ const routes = [
|
||||
},
|
||||
component: () => import("@/views/Scan.vue"),
|
||||
},
|
||||
{
|
||||
path: "patcher",
|
||||
name: ROUTES.PATCHER,
|
||||
meta: {
|
||||
title: i18n.global.t("common.patcher"),
|
||||
},
|
||||
component: () => import("@/views/Patcher.vue"),
|
||||
},
|
||||
{
|
||||
path: "user/:user",
|
||||
name: ROUTES.USER_PROFILE,
|
||||
|
||||
@@ -45,6 +45,10 @@ export default defineStore("navigation", {
|
||||
this.reset();
|
||||
this.$router.push({ name: ROUTES.SCAN });
|
||||
},
|
||||
goPatcher() {
|
||||
this.reset();
|
||||
this.$router.push({ name: ROUTES.PATCHER });
|
||||
},
|
||||
goSearch() {
|
||||
this.reset();
|
||||
this.$router.push({ name: ROUTES.SEARCH });
|
||||
|
||||
6
frontend/src/types/rompatcher.d.ts
vendored
Normal file
6
frontend/src/types/rompatcher.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
declare module "rom-patcher/rom-patcher-js/*" {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
894
frontend/src/views/Patcher.vue
Normal file
894
frontend/src/views/Patcher.vue
Normal file
@@ -0,0 +1,894 @@
|
||||
<script setup lang="ts">
|
||||
import { useDropZone } from "@vueuse/core";
|
||||
import type { Emitter } from "mitt";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { inject, ref, onMounted, watch, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import MissingFromFSIcon from "@/components/common/MissingFromFSIcon.vue";
|
||||
import PlatformIcon from "@/components/common/Platform/PlatformIcon.vue";
|
||||
import romApi from "@/services/api/rom";
|
||||
import socket from "@/services/socket";
|
||||
import storeHeartbeat from "@/stores/heartbeat";
|
||||
import { type Platform } from "@/stores/platforms";
|
||||
import storePlatforms from "@/stores/platforms";
|
||||
import storeScanning from "@/stores/scanning";
|
||||
import storeUpload from "@/stores/upload";
|
||||
import type { Events } from "@/types/emitter";
|
||||
import { formatBytes } from "@/utils";
|
||||
|
||||
// Declare global variables for RomPatcher
|
||||
declare global {
|
||||
interface Window {
|
||||
BinFile: any;
|
||||
IPS: any;
|
||||
UPS: any;
|
||||
APS: any;
|
||||
APSGBA: any;
|
||||
BPS: any;
|
||||
RUP: any;
|
||||
PPF: any;
|
||||
BDF: any;
|
||||
PMSR: any;
|
||||
VCDIFF: any;
|
||||
}
|
||||
}
|
||||
|
||||
const { t } = useI18n();
|
||||
const platformsStore = storePlatforms();
|
||||
const { filteredPlatforms } = storeToRefs(platformsStore);
|
||||
const loadError = ref<string | null>(null);
|
||||
const coreLoaded = ref(false);
|
||||
const romFile = ref<File | null>(null);
|
||||
const patchFile = ref<File | null>(null);
|
||||
const romBin = ref<any | null>(null);
|
||||
const patchBin = ref<any | null>(null);
|
||||
const romDropZoneRef = ref<HTMLDivElement | null>(null);
|
||||
const patchDropZoneRef = ref<HTMLDivElement | null>(null);
|
||||
const romInputRef = ref<HTMLInputElement | null>(null);
|
||||
const patchInputRef = ref<HTMLInputElement | null>(null);
|
||||
const applying = ref(false);
|
||||
const statusMessage = ref<string | null>(null);
|
||||
const downloadLocally = ref(true);
|
||||
const saveIntoRomM = ref(true);
|
||||
const selectedPlatform = ref<Platform | null>(null);
|
||||
const customFileName = ref("");
|
||||
const filenamePlaceholder = ref("");
|
||||
const emitter = inject<Emitter<Events>>("emitter");
|
||||
const heartbeat = storeHeartbeat();
|
||||
const scanningStore = storeScanning();
|
||||
const uploadStore = storeUpload();
|
||||
|
||||
const supportedPatchFormats = [
|
||||
".ips",
|
||||
".ups",
|
||||
".bps",
|
||||
".ppf",
|
||||
".rup",
|
||||
".aps",
|
||||
".bdf",
|
||||
".pmsr",
|
||||
".vcdiff",
|
||||
];
|
||||
|
||||
const { isOverDropZone: isOverRomDropZone } = useDropZone(romDropZoneRef, {
|
||||
onDrop: onRomDrop,
|
||||
multiple: false,
|
||||
preventDefaultForUnhandled: true,
|
||||
});
|
||||
|
||||
const { isOverDropZone: isOverPatchDropZone } = useDropZone(patchDropZoneRef, {
|
||||
onDrop: onPatchDrop,
|
||||
multiple: false,
|
||||
preventDefaultForUnhandled: true,
|
||||
});
|
||||
|
||||
// Computed property for ROM extension
|
||||
const romExtension = computed(() => {
|
||||
if (!romFile.value) return "";
|
||||
const match = romFile.value.name.match(/\.[^.]+$/);
|
||||
return match ? match[0] : "";
|
||||
});
|
||||
|
||||
// Update filename placeholder when files change
|
||||
watch([romFile, patchFile], ([rom, patch]) => {
|
||||
if (rom && patch) {
|
||||
const romBaseName = rom.name.replace(/\.[^.]+$/, "");
|
||||
const patchNameWithoutExt = patch.name.replace(/\.[^.]+$/, "");
|
||||
filenamePlaceholder.value = `${romBaseName} (patched-${patchNameWithoutExt})`;
|
||||
} else {
|
||||
filenamePlaceholder.value = "";
|
||||
}
|
||||
});
|
||||
|
||||
async function ensureCoreLoaded() {
|
||||
if (coreLoaded.value) return;
|
||||
try {
|
||||
window.BinFile =
|
||||
window.IPS =
|
||||
window.UPS =
|
||||
window.APS =
|
||||
window.APSGBA =
|
||||
window.BPS =
|
||||
window.RUP =
|
||||
window.PPF =
|
||||
window.BDF =
|
||||
window.PMSR =
|
||||
window.VCDIFF =
|
||||
null;
|
||||
|
||||
await Promise.all([
|
||||
import("rom-patcher/rom-patcher-js/modules/BinFile.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/HashCalculator.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.aps_gba.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.aps_n64.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.bdf.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.bps.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.ips.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.pmsr.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.ppf.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.rup.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.ups.js"),
|
||||
import("rom-patcher/rom-patcher-js/modules/RomPatcher.format.vcdiff.js"),
|
||||
import("rom-patcher/rom-patcher-js/RomPatcher.js"),
|
||||
]);
|
||||
|
||||
coreLoaded.value = true;
|
||||
} catch (e: any) {
|
||||
loadError.value = e?.message || String(e);
|
||||
}
|
||||
}
|
||||
|
||||
function setRomFile(file: File | null) {
|
||||
romFile.value = file;
|
||||
romBin.value = null;
|
||||
}
|
||||
|
||||
function setPatchFile(file: File | null) {
|
||||
patchFile.value = file;
|
||||
patchBin.value = null;
|
||||
}
|
||||
|
||||
function onRomInput(files: File[] | File | null) {
|
||||
const first = Array.isArray(files) ? (files[0] ?? null) : files;
|
||||
setRomFile(first ?? null);
|
||||
}
|
||||
|
||||
function onPatchInput(files: File[] | File | null) {
|
||||
const first = Array.isArray(files) ? (files[0] ?? null) : files;
|
||||
setPatchFile(first ?? null);
|
||||
}
|
||||
|
||||
function onRomChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
onRomInput(input.files ? Array.from(input.files) : null);
|
||||
if (input) input.value = "";
|
||||
}
|
||||
|
||||
function onPatchChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
onPatchInput(input.files ? Array.from(input.files) : null);
|
||||
if (input) input.value = "";
|
||||
}
|
||||
|
||||
function onRomDrop(files: File[] | null) {
|
||||
onRomInput(files);
|
||||
}
|
||||
|
||||
function onPatchDrop(files: File[] | null) {
|
||||
onPatchInput(files);
|
||||
}
|
||||
|
||||
function triggerRomInput() {
|
||||
romInputRef.value?.click();
|
||||
}
|
||||
|
||||
function triggerPatchInput() {
|
||||
patchInputRef.value?.click();
|
||||
}
|
||||
|
||||
async function patchRom() {
|
||||
loadError.value = null;
|
||||
statusMessage.value = null;
|
||||
if (!coreLoaded.value) await ensureCoreLoaded();
|
||||
if (!coreLoaded.value) return; // bail on error
|
||||
|
||||
if (!romFile.value) {
|
||||
loadError.value = t("patcher.error-no-rom");
|
||||
return;
|
||||
}
|
||||
if (!patchFile.value) {
|
||||
loadError.value = t("patcher.error-no-patch");
|
||||
return;
|
||||
}
|
||||
|
||||
if (saveIntoRomM.value && !selectedPlatform.value) {
|
||||
loadError.value = t("patcher.error-no-platform");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!downloadLocally.value && !saveIntoRomM.value) {
|
||||
loadError.value = t("patcher.error-no-action");
|
||||
return;
|
||||
}
|
||||
|
||||
applying.value = true;
|
||||
|
||||
try {
|
||||
// Read files as ArrayBuffers
|
||||
statusMessage.value = t("patcher.status-preparing");
|
||||
const romArrayBuffer = await romFile.value.arrayBuffer();
|
||||
const patchArrayBuffer = await patchFile.value.arrayBuffer();
|
||||
|
||||
// Create and use web worker for patching
|
||||
const worker = new Worker("/assets/patcherjs/patcher.worker.js");
|
||||
|
||||
const patchedResult = await new Promise<{
|
||||
data: Uint8Array;
|
||||
fileName: string;
|
||||
}>((resolve, reject) => {
|
||||
worker.onmessage = (e) => {
|
||||
const { type, message, patchedData, fileName, error } = e.data;
|
||||
|
||||
if (type === "STATUS") {
|
||||
statusMessage.value = message;
|
||||
} else if (type === "SUCCESS") {
|
||||
worker.terminate();
|
||||
resolve({
|
||||
data: new Uint8Array(patchedData),
|
||||
fileName: fileName,
|
||||
});
|
||||
} else if (type === "ERROR") {
|
||||
worker.terminate();
|
||||
reject(new Error(error));
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (error) => {
|
||||
worker.terminate();
|
||||
reject(new Error(`Worker error: ${error.message}`));
|
||||
};
|
||||
|
||||
// Send data to worker
|
||||
worker.postMessage(
|
||||
{
|
||||
type: "PATCH",
|
||||
romData: romArrayBuffer,
|
||||
patchData: patchArrayBuffer,
|
||||
romFileName: romFile.value?.name,
|
||||
patchFileName: patchFile.value?.name,
|
||||
customFileName: customFileName.value || "",
|
||||
},
|
||||
[romArrayBuffer, patchArrayBuffer],
|
||||
); // Transfer ownership
|
||||
});
|
||||
|
||||
// Handle the patched result
|
||||
let actions = [];
|
||||
|
||||
if (downloadLocally.value) {
|
||||
statusMessage.value = t("patcher.status-downloading");
|
||||
// Create blob and trigger download
|
||||
const copy = new Uint8Array(patchedResult.data);
|
||||
const blob = new Blob([copy], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = patchedResult.fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
actions.push(t("patcher.success-downloaded"));
|
||||
}
|
||||
|
||||
if (saveIntoRomM.value && selectedPlatform.value) {
|
||||
statusMessage.value = t("patcher.status-uploading");
|
||||
await uploadPatchedRom(patchedResult.data, patchedResult.fileName);
|
||||
actions.push(t("patcher.success-uploaded"));
|
||||
}
|
||||
|
||||
if (actions.length > 0) {
|
||||
statusMessage.value = t("patcher.success-message", {
|
||||
actions: actions.join(` ${t("common.and")} `),
|
||||
});
|
||||
setTimeout(() => {
|
||||
statusMessage.value = null;
|
||||
}, 3000);
|
||||
} else {
|
||||
statusMessage.value = null;
|
||||
}
|
||||
} catch (err: any) {
|
||||
loadError.value = err?.message || String(err);
|
||||
statusMessage.value = null;
|
||||
} finally {
|
||||
applying.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadPatchedRom(binaryData: Uint8Array, fileName: string) {
|
||||
if (!selectedPlatform.value) {
|
||||
throw new Error("No platform selected.");
|
||||
}
|
||||
const platformId = selectedPlatform.value.id;
|
||||
|
||||
// Convert the binary data to a File object
|
||||
const copy = new Uint8Array(binaryData);
|
||||
const file = new File([copy], fileName, { type: "application/octet-stream" });
|
||||
|
||||
// Upload the patched ROM
|
||||
await romApi
|
||||
.uploadRoms({
|
||||
filesToUpload: [file],
|
||||
platformId: platformId,
|
||||
})
|
||||
.then((responses: PromiseSettledResult<unknown>[]) => {
|
||||
const successfulUploads = responses.filter(
|
||||
(d) => d.status === "fulfilled",
|
||||
);
|
||||
const failedUploads = responses.filter((d) => d.status === "rejected");
|
||||
|
||||
if (successfulUploads.length === 0) {
|
||||
// Get detailed error message from the first failed upload
|
||||
const firstFailure = failedUploads[0] as PromiseRejectedResult;
|
||||
const errorDetail =
|
||||
firstFailure?.reason?.response?.data?.detail ||
|
||||
firstFailure?.reason?.message ||
|
||||
"Upload failed with unknown error";
|
||||
console.error("Upload failed:", firstFailure);
|
||||
throw new Error(errorDetail);
|
||||
}
|
||||
|
||||
if (failedUploads.length === 0) {
|
||||
uploadStore.reset();
|
||||
}
|
||||
|
||||
emitter?.emit("snackbarShow", {
|
||||
msg: t("patcher.upload-success", {
|
||||
errors: failedUploads.length > 0 ? t("patcher.upload-errors") : "",
|
||||
}),
|
||||
icon: "mdi-check-bold",
|
||||
color: "green",
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
// Clear form after successful upload
|
||||
romFile.value = null;
|
||||
patchFile.value = null;
|
||||
if (failedUploads.length === 0) {
|
||||
uploadStore.reset();
|
||||
// Clear form only on complete success
|
||||
romFile.value = null;
|
||||
patchFile.value = null;
|
||||
romBin.value = null;
|
||||
patchBin.value = null;
|
||||
selectedPlatform.value = null;
|
||||
saveIntoRomM.value = false;
|
||||
|
||||
scanningStore.setScanning(true);
|
||||
|
||||
if (!socket.connected) socket.connect();
|
||||
setTimeout(() => {
|
||||
socket.emit("scan", {
|
||||
platforms: [platformId],
|
||||
type: "quick",
|
||||
apis: heartbeat.getEnabledMetadataOptions().map((s) => s.value),
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
patchBin.value = null;
|
||||
selectedPlatform.value = null;
|
||||
saveIntoRomM.value = false;
|
||||
|
||||
scanningStore.setScanning(true);
|
||||
|
||||
if (!socket.connected) socket.connect();
|
||||
setTimeout(() => {
|
||||
socket.emit("scan", {
|
||||
platforms: [platformId],
|
||||
type: "quick",
|
||||
apis: heartbeat.getEnabledMetadataOptions().map((s) => s.value),
|
||||
});
|
||||
}, 2000);
|
||||
})
|
||||
.catch(({ response, message }) => {
|
||||
throw new Error(
|
||||
t("patcher.error-upload-failed", {
|
||||
error: response?.data?.detail || response?.statusText || message,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// Preload core for faster interaction
|
||||
await ensureCoreLoaded();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row class="align-center justify-center scroll h-100 px-4" no-gutters>
|
||||
<v-col cols="12" sm="10" md="8" xl="6">
|
||||
<v-card class="pa-4 bg-background" elevation="0">
|
||||
<v-card-title class="pb-2 px-0">{{ t("patcher.title") }}</v-card-title>
|
||||
<v-card-subtitle class="pb-2 px-0 text-body-2">
|
||||
{{ t("patcher.subtitle") }}
|
||||
</v-card-subtitle>
|
||||
<v-divider class="mt-2 mb-4" />
|
||||
|
||||
<v-card-text class="pa-0">
|
||||
<v-alert
|
||||
v-if="loadError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
density="compact"
|
||||
>{{ loadError }}</v-alert
|
||||
>
|
||||
|
||||
<v-alert
|
||||
v-if="statusMessage"
|
||||
class="mb-4 bg-primary"
|
||||
density="compact"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
size="20"
|
||||
width="2"
|
||||
class="mr-3"
|
||||
/>
|
||||
{{ statusMessage }}
|
||||
</div>
|
||||
</v-alert>
|
||||
|
||||
<v-row class="mb-2" dense>
|
||||
<v-col cols="12" md="6">
|
||||
<v-sheet class="pa-3" rounded="lg" border color="surface">
|
||||
<div class="text-subtitle-1">{{ t("patcher.rom-file") }}</div>
|
||||
<div
|
||||
ref="romDropZoneRef"
|
||||
class="dropzone-container rounded-lg transition-all duration-300 ease-in-out mt-4"
|
||||
:class="{
|
||||
'dropzone-active': isOverRomDropZone,
|
||||
'dropzone-has-files': !!romFile,
|
||||
}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="triggerRomInput"
|
||||
@keydown.enter.prevent="triggerRomInput"
|
||||
@keydown.space.prevent="triggerRomInput"
|
||||
>
|
||||
<div
|
||||
v-if="!romFile"
|
||||
class="flex flex-col items-center justify-center h-full min-h-[180px] p-6 text-center transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<v-icon
|
||||
:class="{ 'animate-pulse-glow': isOverRomDropZone }"
|
||||
size="40"
|
||||
color="primary"
|
||||
>
|
||||
{{ isOverRomDropZone ? "mdi-file" : "mdi-file-outline" }}
|
||||
</v-icon>
|
||||
<div class="text-subtitle-2 mt-3 mb-1">
|
||||
{{ t("patcher.drop-rom-here") }}
|
||||
</div>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
{{ t("patcher.drag-drop-rom") }}
|
||||
</p>
|
||||
<v-btn color="primary" variant="outlined" size="small">
|
||||
{{ t("patcher.choose-rom") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="d-flex align-center justify-space-between h-full min-h-[120px] px-4"
|
||||
>
|
||||
<div>
|
||||
<div class="text-subtitle-2">{{ romFile.name }}</div>
|
||||
<div class="text-caption text-medium-emphasis mt-2">
|
||||
<v-chip size="small" label>
|
||||
{{ formatBytes(romFile.size) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
@click.stop="triggerRomInput"
|
||||
>
|
||||
{{ t("patcher.replace") }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
variant="plain"
|
||||
@click.stop="onRomInput(null)"
|
||||
>
|
||||
<v-icon color="red"> mdi-close </v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="romInputRef"
|
||||
type="file"
|
||||
class="sr-only"
|
||||
style="display: none"
|
||||
@change="onRomChange"
|
||||
/>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-sheet class="pa-3" rounded="lg" border color="surface">
|
||||
<div class="text-subtitle-1">{{ t("patcher.patch-file") }}</div>
|
||||
<div
|
||||
ref="patchDropZoneRef"
|
||||
class="dropzone-container rounded-lg transition-all duration-300 ease-in-out mt-4"
|
||||
:class="{
|
||||
'dropzone-active': isOverPatchDropZone,
|
||||
'dropzone-has-files': !!patchFile,
|
||||
}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="triggerPatchInput"
|
||||
@keydown.enter.prevent="triggerPatchInput"
|
||||
@keydown.space.prevent="triggerPatchInput"
|
||||
>
|
||||
<div
|
||||
v-if="!patchFile"
|
||||
class="flex flex-col items-center justify-center h-full min-h-[180px] p-6 text-center transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<v-icon
|
||||
:class="{ 'animate-pulse-glow': isOverPatchDropZone }"
|
||||
size="40"
|
||||
color="primary"
|
||||
>
|
||||
{{
|
||||
isOverPatchDropZone
|
||||
? "mdi-file-cog"
|
||||
: "mdi-file-cog-outline"
|
||||
}}
|
||||
</v-icon>
|
||||
<div class="text-subtitle-2 mt-3 mb-1">
|
||||
{{ t("patcher.drop-patch-here") }}
|
||||
</div>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">
|
||||
{{ t("patcher.drag-drop-patch") }}
|
||||
</p>
|
||||
<v-btn color="primary" variant="outlined" size="small">
|
||||
{{ t("patcher.choose-patch") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="d-flex align-center justify-space-between h-full min-h-[120px] px-4"
|
||||
>
|
||||
<div>
|
||||
<div class="text-subtitle-2">{{ patchFile.name }}</div>
|
||||
<div class="text-caption text-medium-emphasis mt-2">
|
||||
<v-chip size="small" label>
|
||||
{{ formatBytes(patchFile.size) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
@click.stop="triggerPatchInput"
|
||||
>
|
||||
{{ t("patcher.replace") }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
variant="plain"
|
||||
@click.stop="onPatchInput(null)"
|
||||
>
|
||||
<v-icon color="red"> mdi-close </v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="patchInputRef"
|
||||
type="file"
|
||||
:accept="supportedPatchFormats.join(',')"
|
||||
class="sr-only"
|
||||
style="display: none"
|
||||
@change="onPatchChange"
|
||||
/>
|
||||
<div class="text-subtitle-2 text-medium-emphasis mt-4">
|
||||
{{ t("patcher.supported-formats") }}<br />
|
||||
<v-chip
|
||||
v-for="format in supportedPatchFormats"
|
||||
size="x-small"
|
||||
class="mr-1 mt-1"
|
||||
label
|
||||
>{{ format }}</v-chip
|
||||
>
|
||||
</div>
|
||||
</v-sheet>
|
||||
<div class="d-flex align-center justify-space-between mt-4">
|
||||
<v-switch
|
||||
v-model="downloadLocally"
|
||||
color="primary"
|
||||
inset
|
||||
hide-details
|
||||
:label="t('patcher.download-locally')"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="saveIntoRomM"
|
||||
color="primary"
|
||||
inset
|
||||
hide-details
|
||||
:label="t('patcher.upload-to-romm')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-expand-transition>
|
||||
<div v-if="saveIntoRomM" class="mt-4">
|
||||
<v-select
|
||||
v-model="selectedPlatform"
|
||||
:items="filteredPlatforms"
|
||||
:menu-props="{ maxHeight: 650 }"
|
||||
:label="t('common.platforms')"
|
||||
:disabled="!saveIntoRomM"
|
||||
item-title="name"
|
||||
return-object
|
||||
prepend-inner-icon="mdi-controller"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details
|
||||
clearable
|
||||
>
|
||||
<template #item="{ props, item }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
class="py-4"
|
||||
:title="item.raw.name ?? ''"
|
||||
:subtitle="item.raw.fs_slug"
|
||||
>
|
||||
<template #prepend>
|
||||
<PlatformIcon
|
||||
:key="item.raw.slug"
|
||||
:size="35"
|
||||
:slug="item.raw.slug"
|
||||
:name="item.raw.name"
|
||||
:fs-slug="item.raw.fs_slug"
|
||||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
<MissingFromFSIcon
|
||||
v-if="item.raw.missing_from_fs"
|
||||
text="Missing platform from filesystem"
|
||||
chip
|
||||
chip-label
|
||||
chip-density="compact"
|
||||
class="ml-2"
|
||||
/>
|
||||
<v-row
|
||||
v-if="item.raw.is_identified"
|
||||
class="text-white text-shadow text-center"
|
||||
no-gutters
|
||||
>
|
||||
<v-col cols="12">
|
||||
<v-avatar
|
||||
v-if="item.raw.igdb_id"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
>
|
||||
<v-img src="/assets/scrappers/igdb.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.ss_id"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
>
|
||||
<v-img src="/assets/scrappers/ss.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.moby_slug"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
>
|
||||
<v-img src="/assets/scrappers/moby.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.ra_id"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
>
|
||||
<v-img src="/assets/scrappers/ra.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.launchbox_id"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
style="background: #185a7c"
|
||||
>
|
||||
<v-img src="/assets/scrappers/launchbox.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.hasheous_id"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
>
|
||||
<v-img src="/assets/scrappers/hasheous.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.flashpoint_id"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
class="mr-1"
|
||||
>
|
||||
<v-img src="/assets/scrappers/flashpoint.png" />
|
||||
</v-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="item.raw.hltb_slug"
|
||||
class="bg-surface"
|
||||
variant="text"
|
||||
size="25"
|
||||
rounded
|
||||
>
|
||||
<v-img src="/assets/scrappers/hltb.png" />
|
||||
</v-avatar>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-else
|
||||
class="text-white text-shadow text-center"
|
||||
no-gutters
|
||||
>
|
||||
<v-chip color="red" size="small" label>
|
||||
<v-icon class="mr-1"> mdi-close </v-icon>
|
||||
{{ t("scan.not-identified").toUpperCase() }}
|
||||
</v-chip>
|
||||
</v-row>
|
||||
<v-chip class="ml-1" size="small" label>
|
||||
{{ item.raw.rom_count }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template #chip="{ item }">
|
||||
<v-chip>
|
||||
<PlatformIcon
|
||||
:key="item.raw.slug"
|
||||
:slug="item.raw.slug"
|
||||
:name="item.raw.name"
|
||||
:fs-slug="item.raw.fs_slug"
|
||||
:size="20"
|
||||
/>
|
||||
<div class="ml-1">
|
||||
{{ item.raw.name }}
|
||||
</div>
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-text-field
|
||||
v-model="customFileName"
|
||||
:placeholder="filenamePlaceholder"
|
||||
:suffix="romExtension"
|
||||
:label="t('patcher.output-filename')"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mt-4"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<div class="d-flex align-right justify-space-left mt-4">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
class="bg-toplayer text-primary"
|
||||
:disabled="
|
||||
!romFile ||
|
||||
!patchFile ||
|
||||
applying ||
|
||||
(!downloadLocally && !saveIntoRomM) ||
|
||||
(saveIntoRomM && !selectedPlatform)
|
||||
"
|
||||
:loading="applying"
|
||||
:variant="
|
||||
!romFile ||
|
||||
!patchFile ||
|
||||
applying ||
|
||||
(!downloadLocally && !saveIntoRomM) ||
|
||||
(saveIntoRomM && !selectedPlatform)
|
||||
? 'plain'
|
||||
: 'flat'
|
||||
"
|
||||
@click="patchRom"
|
||||
>
|
||||
{{
|
||||
downloadLocally && saveIntoRomM
|
||||
? t("patcher.apply-download-upload")
|
||||
: saveIntoRomM
|
||||
? t("patcher.apply-upload")
|
||||
: t("patcher.apply-download")
|
||||
}}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-row class="mb-8 px-4" no-gutters>
|
||||
<v-col class="text-right align-center">
|
||||
<span class="text-medium-emphasis text-caption font-italic mr-2">{{
|
||||
t("patcher.powered-by")
|
||||
}}</span>
|
||||
<v-avatar rounded="0">
|
||||
<v-img src="/assets/patcherjs/patcherjs.png" />
|
||||
</v-avatar>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dropzone-container {
|
||||
border: 2px dashed rgba(var(--v-theme-primary), 0.3);
|
||||
}
|
||||
|
||||
.dropzone-container.dropzone-active {
|
||||
border: 2px dashed rgba(var(--v-theme-primary));
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
}
|
||||
|
||||
.dropzone-container.dropzone-has-files {
|
||||
border: none;
|
||||
background-color: rgba(var(--v-theme-surface), 0.5);
|
||||
}
|
||||
|
||||
.animate-pulse-glow {
|
||||
animation: pulse-glow 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(var(--v-theme-primary), 0));
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
filter: brightness(1.2)
|
||||
drop-shadow(0 0 20px rgba(var(--v-theme-primary), 0.6));
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(var(--v-theme-primary), 0));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -304,6 +304,51 @@ function openCacheDialog() {
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-4" style="min-height: 200px">
|
||||
<!-- Saves Tab Content -->
|
||||
<div v-show="isSavesTabSelected">
|
||||
<!-- Selected Save Preview -->
|
||||
<div v-if="selectedSave" class="mb-3">
|
||||
<AssetCard
|
||||
:asset="selectedSave"
|
||||
type="save"
|
||||
:show-hover-actions="false"
|
||||
:show-close-button="true"
|
||||
:transform-scale="false"
|
||||
@close="unselectSave"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- No Save Selected Message -->
|
||||
<div v-else class="text-center py-8">
|
||||
<v-icon size="48" color="medium-emphasis"
|
||||
>mdi-content-save-outline</v-icon
|
||||
>
|
||||
<p class="text-body-2 text-medium-emphasis mt-2">
|
||||
{{ t("play.no-save-selected") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Select Save Button -->
|
||||
<v-btn
|
||||
block
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
:prepend-icon="
|
||||
selectedSave ? 'mdi-swap-horizontal' : 'mdi-plus'
|
||||
"
|
||||
:disabled="rom.user_saves.length == 0"
|
||||
@click="openSaveDialog"
|
||||
>
|
||||
{{
|
||||
rom.user_saves.length == 0
|
||||
? t("play.no-saves-available")
|
||||
: selectedSave
|
||||
? t("play.change-save")
|
||||
: t("play.select-save")
|
||||
}}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- States Tab Content -->
|
||||
<div v-show="!isSavesTabSelected">
|
||||
<!-- Selected State Preview -->
|
||||
@@ -344,49 +389,13 @@ function openCacheDialog() {
|
||||
@click="openStateDialog"
|
||||
>
|
||||
{{
|
||||
selectedState
|
||||
? t("play.change-state")
|
||||
: t("play.select-state")
|
||||
}}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Saves Tab Content -->
|
||||
<div v-show="isSavesTabSelected">
|
||||
<!-- Selected Save Preview -->
|
||||
<div v-if="selectedSave" class="mb-3">
|
||||
<AssetCard
|
||||
:asset="selectedSave"
|
||||
type="save"
|
||||
:show-hover-actions="false"
|
||||
:show-close-button="true"
|
||||
:transform-scale="false"
|
||||
@close="unselectSave"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- No Save Selected Message -->
|
||||
<div v-else class="text-center py-8">
|
||||
<v-icon size="48" color="medium-emphasis"
|
||||
>mdi-content-save-outline</v-icon
|
||||
>
|
||||
<p class="text-body-2 text-medium-emphasis mt-2">
|
||||
{{ t("play.no-save-selected") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Select Save Button -->
|
||||
<v-btn
|
||||
block
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
:prepend-icon="
|
||||
selectedSave ? 'mdi-swap-horizontal' : 'mdi-plus'
|
||||
"
|
||||
@click="openSaveDialog"
|
||||
>
|
||||
{{
|
||||
selectedSave ? t("play.change-save") : t("play.select-save")
|
||||
!rom.user_states.some(
|
||||
(s) => !s.emulator || s.emulator === selectedCore,
|
||||
)
|
||||
? t("play.no-states-available")
|
||||
: selectedState
|
||||
? t("play.change-state")
|
||||
: t("play.select-state")
|
||||
}}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user