emmercm/igir

View on GitHub
src/types/dats/dat.ts

Summary

Maintainability
D
1 day
Test Coverage
import { Memoize } from 'typescript-memoize';
import xml2js from 'xml2js';
 
import FsPoly from '../../polyfill/fsPoly.js';
import { ChecksumBitmask } from '../files/fileChecksums.js';
import Game from './game.js';
import Header from './logiqx/header.js';
import Parent from './parent.js';
 
/**
* The base class for other DAT classes.
*/
export default abstract class DAT {
private parents: Parent[] = [];
 
abstract getHeader(): Header;
 
abstract getGames(): Game[];
 
/**
* Group all {@link Game} clones together into one {@link Parent}. If no parent/clone information
* exists, then there will be one {@link Parent} for every {@link Game}.
*/
protected generateGameNamesToParents(): this {
const gameNamesToParents: Map<string, Parent> = new Map();
 
// Find all parents
this.getGames()
.filter((game) => game.isParent())
.forEach((game: Game) => {
const parent = gameNamesToParents.get(game.getName());
if (parent) {
// Two games have the same name, assume this one is a clone
parent.addChild(game);
} else {
gameNamesToParents.set(game.getName(), new Parent(game));
}
});
 
// Find all clones
this.getGames()
.filter((game) => game.isClone())
.forEach((game: Game) => {
const parent = gameNamesToParents.get(game.getParent());
if (parent) {
parent.addChild(game);
} else {
// The DAT is bad, the game is referencing a parent that doesn't exist
gameNamesToParents.set(game.getName(), new Parent(game));
}
});
 
this.parents = [...gameNamesToParents.values()];
 
return this;
}
 
getParents(): Parent[] {
return this.parents;
}
 
/**
* Does any {@link Game} in this {@link DAT} have clone information.
*/
hasParentCloneInfo(): boolean {
return this.getParents().length > 0 && this.getParents().length !== this.getGames().length;
}
 
getName(): string {
return this.getHeader().getName();
}
 
@Memoize()
getNameShort(): string {
return (
this.getName()
// Prefixes
.replace('FinalBurn Neo', '')
.replace('Non-Redump', '')
.replace('Source Code', '')
.replace('Unofficial', '')
// Suffixes
.replace('Datfile', '')
.replace('(Deprecated)', '')
.replace(/\(Parent-Clone\)/g, '')
.replace('(WIP)', '')
// Cleanup
.replace(/-( +-)+/g, '- ')
.replace(/^[ -]+/, '')
.replace(/[ -]+$/, '')
.replace(/ +/g, ' ')
.trim()
);
}
 
getDescription(): string | undefined {
return this.getHeader().getDescription();
}
 
/**
* Get a No-Intro style filename.
*/
getFilename(): string {
let filename = this.getName();
if (this.getHeader().getVersion()) {
filename += ` (${this.getHeader().getVersion()})`;
}
filename += '.dat';
return FsPoly.makeLegal(filename.trim());
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
getRequiredRomChecksumBitmask(): number {
let checksumBitmask = 0;
this.getGames().forEach((game) =>
game.getRoms().forEach((rom) => {
if (rom.getCrc32() && rom.getSize()) {
checksumBitmask |= ChecksumBitmask.CRC32;
} else if (rom.getMd5()) {
checksumBitmask |= ChecksumBitmask.MD5;
} else if (rom.getSha1()) {
checksumBitmask |= ChecksumBitmask.SHA1;
} else if (rom.getSha256()) {
checksumBitmask |= ChecksumBitmask.SHA256;
}
}),
);
return checksumBitmask;
}
 
Similar blocks of code found in 2 locations. Consider refactoring.
getRequiredDiskChecksumBitmask(): number {
let checksumBitmask = 0;
this.getGames().forEach((game) =>
game.getDisks().forEach((disk) => {
if (disk.getCrc32() && disk.getSize()) {
checksumBitmask |= ChecksumBitmask.CRC32;
} else if (disk.getMd5()) {
checksumBitmask |= ChecksumBitmask.MD5;
} else if (disk.getSha1()) {
checksumBitmask |= ChecksumBitmask.SHA1;
} else if (disk.getSha256()) {
checksumBitmask |= ChecksumBitmask.SHA256;
}
}),
);
return checksumBitmask;
}
 
/**
* Serialize this {@link DAT} to the file contents of an XML file.
*/
toXmlDat(): string {
// TODO(cemmer): replace with fast-xml-parser https://github.com/NaturalIntelligence/fast-xml-parser/issues/639
return new xml2js.Builder({
renderOpts: { pretty: true, indent: '\t', newline: '\n' },
xmldec: { version: '1.0' },
doctype: {
pubID: '-//Logiqx//DTD ROM Management Datafile//EN',
sysID: 'http://www.logiqx.com/Dats/datafile.dtd',
},
cdata: true,
}).buildObject(this.toXmlDatObj());
}
 
private toXmlDatObj(): object {
const parentNames = new Set(this.getParents().map((parent) => parent.getName()));
return {
datafile: {
header: this.getHeader().toXmlDatObj(),
game: this.getGames().map((game) => game.toXmlDatObj(parentNames)),
},
};
}
 
/**
* Return a short string representation of this {@link DAT}.
*/
toString(): string {
return `{"header": ${this.getHeader().toString()}, "games": ${this.getGames().length}}`;
}
}