89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
import { XMLParser } from 'fast-xml-parser';
|
|
import type { DatRom, DatGame, DatDatabase, DatVerifyResult, XmlGame, XmlRom } from '../types';
|
|
|
|
export type { DatRom, DatGame, DatDatabase };
|
|
|
|
function ensureArray<T>(v: T | T[] | undefined): T[] {
|
|
if (v === undefined || v === null) return [];
|
|
return Array.isArray(v) ? v : [v];
|
|
}
|
|
|
|
function normalizeHex(v: unknown): string | undefined {
|
|
if (!v || typeof v !== 'string') return undefined;
|
|
return v.trim().toLowerCase();
|
|
}
|
|
|
|
export function parseDat(xml: string): DatDatabase {
|
|
const parser = new XMLParser({
|
|
ignoreAttributes: false,
|
|
attributeNamePrefix: '',
|
|
trimValues: true,
|
|
});
|
|
|
|
const parsed: Record<string, unknown> = parser.parse(xml);
|
|
const datafile =
|
|
(parsed?.datafile as { game?: XmlGame | XmlGame[] } | undefined) ??
|
|
(parsed as { game?: XmlGame | XmlGame[] });
|
|
|
|
const rawGames = ensureArray<XmlGame>(datafile?.game);
|
|
|
|
const games: DatGame[] = rawGames.map((g) => {
|
|
// game name may be an attribute or a child node
|
|
const nameAttr = g?.name ?? g?.['@_name'] ?? g?.$?.name;
|
|
|
|
const romNodes = ensureArray<XmlRom>(g?.rom as XmlRom | XmlRom[] | undefined);
|
|
|
|
const roms: DatRom[] = romNodes.map((r) => {
|
|
const rname = r?.name ?? r?.['@_name'] ?? r?.['@name'];
|
|
const sizeRaw = r?.size ?? r?.['@_size'];
|
|
const parsedSize = sizeRaw != null ? Number(sizeRaw) : undefined;
|
|
|
|
return {
|
|
name: String(rname ?? ''),
|
|
size: typeof parsedSize === 'number' && !Number.isNaN(parsedSize) ? parsedSize : undefined,
|
|
crc: normalizeHex(r?.crc ?? r?.['@_crc'] ?? r?.CRC ?? r?.['CRC']),
|
|
md5: normalizeHex(r?.md5 ?? r?.['@_md5'] ?? r?.MD5 ?? r?.['MD5']),
|
|
sha1: normalizeHex(r?.sha1 ?? r?.['@_sha1'] ?? r?.SHA1 ?? r?.['SHA1']),
|
|
};
|
|
});
|
|
|
|
return {
|
|
name: String(nameAttr ?? ''),
|
|
roms,
|
|
};
|
|
});
|
|
|
|
return { games };
|
|
}
|
|
|
|
export function verifyHashesAgainstDat(
|
|
datDb: DatDatabase,
|
|
hashes: { crc?: string; md5?: string; sha1?: string; size?: number }
|
|
): DatVerifyResult | null {
|
|
const cmp = {
|
|
crc: normalizeHex(hashes.crc),
|
|
md5: normalizeHex(hashes.md5),
|
|
sha1: normalizeHex(hashes.sha1),
|
|
size: hashes.size,
|
|
};
|
|
|
|
for (const g of datDb.games) {
|
|
for (const r of g.roms) {
|
|
if (cmp.crc && r.crc && cmp.crc === r.crc) {
|
|
return { gameName: g.name, romName: r.name, matchedOn: 'crc' };
|
|
}
|
|
if (cmp.md5 && r.md5 && cmp.md5 === r.md5) {
|
|
return { gameName: g.name, romName: r.name, matchedOn: 'md5' };
|
|
}
|
|
if (cmp.sha1 && r.sha1 && cmp.sha1 === r.sha1) {
|
|
return { gameName: g.name, romName: r.name, matchedOn: 'sha1' };
|
|
}
|
|
if (cmp.size !== undefined && r.size !== undefined && Number(cmp.size) === Number(r.size)) {
|
|
return { gameName: g.name, romName: r.name, matchedOn: 'size' };
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|