From 0e04763209b23ebfc4f117993733b23d4f52faba Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 24 Jun 2026 13:56:21 +0300 Subject: [PATCH 1/3] fix: resolve cue FILE refs by basename against cue dir PS1/PS2 SKU extraction returned null when a .cue FILE entry pointed to an absolute or foreign path (e.g. C:\WINDOWS\DESKTOP\CASTLEVANIA.BIN). path.resolve kept the bogus path and the sibling .bin was never scanned. Strip the FILE value to its basename (normalizing \ to /) and resolve it against the cue's own directory. When the resolved path is missing on a case-sensitive filesystem, scan the directory and match the basename case-insensitively, returning the real on-disk name. Also warn explicitly in extractPs12Sku when the resolved sniff target does not exist, instead of failing silently in the generic catch. --- .../services/emulators/extract-disc-sku.ts | 12 ++++++++++ .../services/emulators/sniff-disc-platform.ts | 23 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/services/emulators/extract-disc-sku.ts b/src/main/services/emulators/extract-disc-sku.ts index 76dc45a3d1..e4cd20028f 100644 --- a/src/main/services/emulators/extract-disc-sku.ts +++ b/src/main/services/emulators/extract-disc-sku.ts @@ -72,6 +72,18 @@ const extractPs12Sku = async (filePath: string): Promise => { return null; } + const targetExists = await fs + .access(target) + .then(() => true) + .catch(() => false); + if (!targetExists) { + logger.warn("[extract-sku] sniff target missing on disk", { + filePath, + target, + }); + return null; + } + let fh: import("node:fs/promises").FileHandle | null = null; try { fh = await fs.open(target, "r"); diff --git a/src/main/services/emulators/sniff-disc-platform.ts b/src/main/services/emulators/sniff-disc-platform.ts index 9d96db61b9..d9943736b6 100644 --- a/src/main/services/emulators/sniff-disc-platform.ts +++ b/src/main/services/emulators/sniff-disc-platform.ts @@ -37,6 +37,27 @@ export const sniffDiscImage = async ( } }; +const resolveCueRef = async (dir: string, ref: string): Promise => { + const base = path.basename(ref.replace(/\\/g, "/")); + const resolved = path.resolve(dir, base); + + try { + await fs.access(resolved); + return resolved; + } catch { + try { + const entries = await fs.readdir(dir); + const match = entries.find( + (entry) => entry.toLowerCase() === base.toLowerCase() + ); + if (match) return path.resolve(dir, match); + } catch { + return resolved; + } + return resolved; + } +}; + export const parseCueReferencedFiles = async ( cuePath: string ): Promise => { @@ -44,7 +65,7 @@ export const parseCueReferencedFiles = async ( const content = await fs.readFile(cuePath, "utf-8"); const dir = path.dirname(cuePath); const matches = [...content.matchAll(/FILE\s+"(.+?)"\s+\w+/gi)]; - return matches.map((m) => path.resolve(dir, m[1])); + return Promise.all(matches.map((m) => resolveCueRef(dir, m[1]))); } catch { return []; } From dd88d3b574117fd1ac5cc4127ecd165cc8047119 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 24 Jun 2026 14:14:14 +0300 Subject: [PATCH 2/3] refactor: return null from resolveCueRef when ref unresolvable So the exported parseCueReferencedFiles never hands a non-existent path to callers; unresolvable refs are filtered out instead. --- src/main/services/emulators/sniff-disc-platform.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/services/emulators/sniff-disc-platform.ts b/src/main/services/emulators/sniff-disc-platform.ts index d9943736b6..6827f01a97 100644 --- a/src/main/services/emulators/sniff-disc-platform.ts +++ b/src/main/services/emulators/sniff-disc-platform.ts @@ -37,7 +37,10 @@ export const sniffDiscImage = async ( } }; -const resolveCueRef = async (dir: string, ref: string): Promise => { +const resolveCueRef = async ( + dir: string, + ref: string +): Promise => { const base = path.basename(ref.replace(/\\/g, "/")); const resolved = path.resolve(dir, base); @@ -52,9 +55,9 @@ const resolveCueRef = async (dir: string, ref: string): Promise => { ); if (match) return path.resolve(dir, match); } catch { - return resolved; + return null; } - return resolved; + return null; } }; @@ -65,7 +68,10 @@ export const parseCueReferencedFiles = async ( const content = await fs.readFile(cuePath, "utf-8"); const dir = path.dirname(cuePath); const matches = [...content.matchAll(/FILE\s+"(.+?)"\s+\w+/gi)]; - return Promise.all(matches.map((m) => resolveCueRef(dir, m[1]))); + const resolved = await Promise.all( + matches.map((m) => resolveCueRef(dir, m[1])) + ); + return resolved.filter((p): p is string => p !== null); } catch { return []; } From 2656b254b9e12119a5414127e042820068bb7b1f Mon Sep 17 00:00:00 2001 From: Moyasee Date: Wed, 24 Jun 2026 14:22:26 +0300 Subject: [PATCH 3/3] refactor: extract scan loop from extractPs12Sku, use replaceAll Move the file open/scan loop into scanTargetForSku so extractPs12Sku stays under the cognitive-complexity threshold, and switch the cue ref backslash normalization to String#replaceAll. --- .../services/emulators/extract-disc-sku.ts | 64 ++++++++++--------- .../services/emulators/sniff-disc-platform.ts | 2 +- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/main/services/emulators/extract-disc-sku.ts b/src/main/services/emulators/extract-disc-sku.ts index e4cd20028f..6491589a53 100644 --- a/src/main/services/emulators/extract-disc-sku.ts +++ b/src/main/services/emulators/extract-disc-sku.ts @@ -54,36 +54,7 @@ const extractCsoSku = async (filePath: string): Promise => { return sku; }; -const extractPs12Sku = async (filePath: string): Promise => { - const lower = filePath.toLowerCase(); - if (lower.endsWith(".chd")) { - return extractChdSku(filePath); - } - if (lower.endsWith(".cso")) { - return extractCsoSku(filePath); - } - - const target = await resolveSniffTarget(filePath); - logger.log("[extract-sku] start", { filePath, target }); - if (!target) { - logger.log("[extract-sku] no sniff target (unsupported format)", { - filePath, - }); - return null; - } - - const targetExists = await fs - .access(target) - .then(() => true) - .catch(() => false); - if (!targetExists) { - logger.warn("[extract-sku] sniff target missing on disk", { - filePath, - target, - }); - return null; - } - +const scanTargetForSku = async (target: string): Promise => { let fh: import("node:fs/promises").FileHandle | null = null; try { fh = await fs.open(target, "r"); @@ -155,6 +126,39 @@ const extractPs12Sku = async (filePath: string): Promise => { } }; +const extractPs12Sku = async (filePath: string): Promise => { + const lower = filePath.toLowerCase(); + if (lower.endsWith(".chd")) { + return extractChdSku(filePath); + } + if (lower.endsWith(".cso")) { + return extractCsoSku(filePath); + } + + const target = await resolveSniffTarget(filePath); + logger.log("[extract-sku] start", { filePath, target }); + if (!target) { + logger.log("[extract-sku] no sniff target (unsupported format)", { + filePath, + }); + return null; + } + + const targetExists = await fs + .access(target) + .then(() => true) + .catch(() => false); + if (!targetExists) { + logger.warn("[extract-sku] sniff target missing on disk", { + filePath, + target, + }); + return null; + } + + return scanTargetForSku(target); +}; + const findKeyOffset = ( data: Buffer, keyTableStart: number, diff --git a/src/main/services/emulators/sniff-disc-platform.ts b/src/main/services/emulators/sniff-disc-platform.ts index 6827f01a97..e76e664665 100644 --- a/src/main/services/emulators/sniff-disc-platform.ts +++ b/src/main/services/emulators/sniff-disc-platform.ts @@ -41,7 +41,7 @@ const resolveCueRef = async ( dir: string, ref: string ): Promise => { - const base = path.basename(ref.replace(/\\/g, "/")); + const base = path.basename(ref.replaceAll("\\", "/")); const resolved = path.resolve(dir, base); try {