Skip to content

Commit

Permalink
Feature: correct invalid extensions when raw-copying archives (#1128)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmercm committed May 10, 2024
1 parent 7adcf96 commit 9832172
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 148 deletions.
18 changes: 8 additions & 10 deletions src/modules/candidateGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,14 @@ export default class CandidateGenerator extends Module {
&& !this.options.shouldExtract()
) {
try {
if (this.options.shouldTest() || this.options.getOverwriteInvalid()) {
// If we're testing, then we need to calculate the archive's checksums
inputFile = await FileFactory.fileFrom(
inputFile.getFilePath(),
inputFile.getChecksumBitmask(),
);
} else {
// Otherwise, we can skip calculating checksums for efficiency
inputFile = await FileFactory.fileFrom(inputFile.getFilePath(), ChecksumBitmask.NONE);
}
inputFile = await FileFactory.archiveFileFrom(
inputFile.getArchive(),
// If we're testing, then we need to calculate the archive's checksums, otherwise we
// can skip calculating checksums for efficiency
this.options.shouldTest() || this.options.getOverwriteInvalid()
? inputFile.getChecksumBitmask()
: ChecksumBitmask.NONE,
);
} catch (error) {
this.progressBar.logWarn(`${dat.getNameShort()}: ${game.getName()}: ${error}`);
return [rom, undefined];
Expand Down
2 changes: 2 additions & 0 deletions src/types/files/archives/archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export default abstract class Archive {

protected abstract new(filePath: string): Archive;

abstract getExtension(): string;

getFilePath(): string {
return this.filePath;
}
Expand Down
15 changes: 15 additions & 0 deletions src/types/files/archives/archiveFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import File, { FileProps } from '../file.js';
import Archive from './archive.js';

export default class ArchiveFile extends File {
private readonly archive: Archive;

public constructor(archive: Archive, fileProps: FileProps) {
super(fileProps);
this.archive = archive;
}

getArchive(): Archive {
return this.archive;
}
}
21 changes: 21 additions & 0 deletions src/types/files/archives/gzip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import SevenZip from './sevenZip.js';

export default class Gzip extends SevenZip {
// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): SevenZip {
return new Gzip(filePath);
}

static getExtensions(): string[] {
return ['.gz', '.gzip'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return Gzip.getExtensions()[0];
}

static getFileSignatures(): Buffer[] {
return [Buffer.from('1F8B', 'hex')];
}
}
23 changes: 16 additions & 7 deletions src/types/files/archives/rar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,29 @@ import Archive from './archive.js';
import ArchiveEntry from './archiveEntry.js';

export default class Rar extends Archive {
static readonly SUPPORTED_FILES: [string[], Buffer[]][] = [
[['.rar'], [
Buffer.from('526172211A0700', 'hex'), // v1.50+
Buffer.from('526172211A070100', 'hex'), // v5.00+
]],
];

private static readonly EXTRACT_MUTEX = new Mutex();

// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): Archive {
return new Rar(filePath);
}

static getExtensions(): string[] {
return ['.rar'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return Rar.getExtensions()[0];
}

static getFileSignatures(): Buffer[] {
return [
Buffer.from('526172211A0700', 'hex'), // v1.50+
Buffer.from('526172211A070100', 'hex'), // v5.00+
];
}

async getArchiveEntries(checksumBitmask: number): Promise<ArchiveEntry<this>[]> {
const rar = await unrar.createExtractorFromFile({
filepath: this.getFilePath(),
Expand Down
70 changes: 13 additions & 57 deletions src/types/files/archives/sevenZip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,26 @@ import Archive from './archive.js';
import ArchiveEntry from './archiveEntry.js';

export default class SevenZip extends Archive {
// p7zip `7za i`
// WARNING: tar+compression doesn't work, you'll be left with a tar file output
static readonly SUPPORTED_FILES: [string[], Buffer[]][] = [
[['.7z'], [
Buffer.from('377ABCAF271C', 'hex'),
]],
// [['.bz2', '.bzip2'], [
// 'BZh',
// ]],
// [['.cab'], [
// 'MSCF',
// ]],
[['.gz', '.gzip'], [
Buffer.from('1F8B', 'hex'),
]],
// [['.lzma'], [
// // ???
// ]],
// [['.lzma86'], [
// // ???
// ]],
// [['.pmd'], [
// // ???
// ]],
// [['.tar', '.ova'], [
// Buffer.from('7573746172003030', 'hex'),
// Buffer.from('7573746172202000', 'hex'),
// ]],
// [['.xz'], [
// Buffer.from('FD377A585A00', 'hex'), // LZMA2 compression
// ]],
[['.z'], [
Buffer.from('1F9D', 'hex'), // LZW compression
Buffer.from('1FA0', 'hex'), // LZH compression
]],
[['.zip', '.zip.001', '.z01'], [
Buffer.from('504B0304', 'hex'),
Buffer.from('504B0506', 'hex'), // empty archive
Buffer.from('504B0708', 'hex'), // spanned archive
]],
[['.zipx', '.zx01'], [
// ???
]],
// [['.zst'], [
// Buffer.from('28B52FFD', 'hex'),
// ]],
// [['.lz4'], [
// Buffer.from('04224D18', 'hex'),
// ]],
// [['.lz5'], [
// // ???
// ]],
// [['.liz'], [
// // ???
// ]],
];

private static readonly LIST_MUTEX = new Mutex();

// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): Archive {
return new SevenZip(filePath);
}

static getExtensions(): string[] {
return ['.7z'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return SevenZip.getExtensions()[0];
}

static getFileSignatures(): Buffer[] {
return [Buffer.from('377ABCAF271C', 'hex')];
}

async getArchiveEntries(checksumBitmask: number): Promise<ArchiveEntry<this>[]> {
/**
* WARN(cemmer): even with the above mutex, {@link _7z.list} will still sometimes return no
Expand Down
29 changes: 19 additions & 10 deletions src/types/files/archives/tar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,30 @@ import Archive from './archive.js';
import ArchiveEntry from './archiveEntry.js';

export default class Tar extends Archive {
static readonly SUPPORTED_FILES: [string[], Buffer[]][] = [
[['.tar'], [
Buffer.from('7573746172003030', 'hex'),
Buffer.from('7573746172202000', 'hex'),
]],
[['.tar.gz', '.tgz'], [
Buffer.from('1F8B', 'hex'),
]],
];

// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): Archive {
return new Tar(filePath);
}

static getExtensions(): string[] {
return ['.tar', '.tar.gz', '.tgz'];
}

getExtension(): string {
// We can't reliably know the extension
return path.parse(this.getFilePath()).ext;
}

static getFileSignatures(): Buffer[] {
return [
// .tar
Buffer.from('7573746172003030', 'hex'),
Buffer.from('7573746172202000', 'hex'),
// .tar.gz / .tgz
Buffer.from('1F8B', 'hex'),
];
}

async getArchiveEntries(checksumBitmask: number): Promise<ArchiveEntry<this>[]> {
const archiveEntryPromises: Promise<ArchiveEntry<this>>[] = [];

Expand Down
24 changes: 24 additions & 0 deletions src/types/files/archives/z.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SevenZip from './sevenZip.js';

export default class Z extends SevenZip {
// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): SevenZip {
return new Z(filePath);
}

static getExtensions(): string[] {
return ['.z'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return Z.getExtensions()[0];
}

static getFileSignatures(): Buffer[] {
return [
Buffer.from('1F9D', 'hex'), // LZW compression
Buffer.from('1FA0', 'hex'), // LZH compression
];
}
}
23 changes: 16 additions & 7 deletions src/types/files/archives/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,27 @@ import Archive from './archive.js';
import ArchiveEntry from './archiveEntry.js';

export default class Zip extends Archive {
static readonly SUPPORTED_FILES: [string[], Buffer[]][] = [
[['.zip'], [
Buffer.from('504B0304', 'hex'),
Buffer.from('504B0506', 'hex'), // empty archive
]],
];

// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): Archive {
return new Zip(filePath);
}

static getExtensions(): string[] {
return ['.zip'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return Zip.getExtensions()[0];
}

static getFileSignatures(): Buffer[] {
return [
Buffer.from('504B0304', 'hex'),
Buffer.from('504B0506', 'hex'), // empty archive
];
}

async getArchiveEntries(checksumBitmask: number): Promise<ArchiveEntry<this>[]> {
// https://github.com/ZJONSSON/node-unzipper/issues/280
// UTF-8 entry names are not decoded correctly
Expand Down
21 changes: 21 additions & 0 deletions src/types/files/archives/zipSpanned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import SevenZip from './sevenZip.js';

export default class ZipSpanned extends SevenZip {
// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): SevenZip {
return new ZipSpanned(filePath);
}

static getExtensions(): string[] {
return ['.zip.001', '.z01'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return ZipSpanned.getExtensions()[0];
}

static getFileSignatures(): Buffer[] {
return [Buffer.from('504B0708', 'hex')];
}
}
23 changes: 23 additions & 0 deletions src/types/files/archives/zipX.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import path from 'node:path';

import SevenZip from './sevenZip.js';

export default class ZipX extends SevenZip {
// eslint-disable-next-line class-methods-use-this
protected new(filePath: string): SevenZip {
return new ZipX(filePath);
}

static getExtensions(): string[] {
return ['.zipx', '.zx01'];
}

// eslint-disable-next-line class-methods-use-this
getExtension(): string {
return path.parse(this.getFilePath()).ext;
}

static getFileSignatures(): Buffer[] {
return [];
}
}
Loading

0 comments on commit 9832172

Please sign in to comment.