-
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
302 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import FilePoly from '../../polyfill/filePoly.js'; | ||
import fsPoly from '../../polyfill/fsPoly.js'; | ||
import File from '../files/file.js'; | ||
import Patch from './patch.js'; | ||
|
||
/** | ||
* @see https://github.com/btimofeev/UniPatcher/wiki/APS-(GBA) | ||
* @see https://github.com/Gamer2020/Unofficial-A-ptch | ||
*/ | ||
export default class APSGBAPatch extends Patch { | ||
static readonly FILE_SIGNATURE = Buffer.from('APS1'); | ||
|
||
static async patchFrom(file: File): Promise<APSGBAPatch> { | ||
const crcBefore = Patch.getCrcFromPath(file.getExtractedFilePath()); | ||
let targetSize = 0; | ||
|
||
await file.extractToTempFilePoly('r', async (patchFile) => { | ||
patchFile.seek(APSGBAPatch.FILE_SIGNATURE.length); | ||
patchFile.skipNext(4); // original file size | ||
targetSize = (await patchFile.readNext(4)).readUInt32LE(); | ||
}); | ||
|
||
return new APSGBAPatch(file, crcBefore, undefined, targetSize); | ||
} | ||
|
||
async createPatchedFile(inputRomFile: File, outputRomPath: string): Promise<void> { | ||
return this.getFile().extractToTempFilePoly('r', async (patchFile) => { | ||
const header = await patchFile.readNext(APSGBAPatch.FILE_SIGNATURE.length); | ||
if (!header.equals(APSGBAPatch.FILE_SIGNATURE)) { | ||
throw new Error(`APS (GBA) patch header is invalid: ${this.getFile().toString()}`); | ||
} | ||
|
||
patchFile.skipNext(4); // original size | ||
patchFile.skipNext(4); // patched size | ||
|
||
return APSGBAPatch.writeOutputFile(inputRomFile, outputRomPath, patchFile); | ||
}); | ||
} | ||
|
||
private static async writeOutputFile( | ||
inputRomFile: File, | ||
outputRomPath: string, | ||
patchFile: FilePoly, | ||
): Promise<void> { | ||
return inputRomFile.extractToTempFile(async (tempRomFile) => { | ||
const sourceFile = await FilePoly.fileFrom(tempRomFile, 'r'); | ||
|
||
await fsPoly.copyFile(tempRomFile, outputRomPath); | ||
const targetFile = await FilePoly.fileFrom(outputRomPath, 'r+'); | ||
|
||
try { | ||
await APSGBAPatch.applyPatch(patchFile, sourceFile, targetFile); | ||
} finally { | ||
await targetFile.close(); | ||
await sourceFile.close(); | ||
} | ||
}); | ||
} | ||
|
||
private static async applyPatch( | ||
patchFile: FilePoly, | ||
sourceFile: FilePoly, | ||
targetFile: FilePoly, | ||
): Promise<void> { | ||
/* eslint-disable no-await-in-loop, no-bitwise */ | ||
while (patchFile.getPosition() < patchFile.getSize()) { | ||
const offset = (await patchFile.readNext(4)).readUInt32LE(); | ||
patchFile.skipNext(2); // CRC16 of original 64KiB block | ||
patchFile.skipNext(2); // CRC16 of patched 64KiB block | ||
const xorData = await patchFile.readNext(1024 * 1024); | ||
|
||
const sourceData = await sourceFile.readAt(offset, xorData.length); | ||
const targetData = Buffer.allocUnsafe(xorData.length); | ||
for (let i = 0; i < xorData.length; i += 1) { | ||
targetData[i] = (i < sourceData.length ? sourceData[i] : 0x00) ^ xorData[i]; | ||
} | ||
await targetFile.writeAt(targetData, offset); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import FilePoly from '../../polyfill/filePoly.js'; | ||
import File from '../files/file.js'; | ||
import Patch from './patch.js'; | ||
|
||
enum APSN64PatchType { | ||
SIMPLE = 0, | ||
N64 = 1, | ||
} | ||
|
||
/** | ||
* @see https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) | ||
*/ | ||
export default class APSN64Patch extends Patch { | ||
static readonly FILE_SIGNATURE = Buffer.from('APS10'); | ||
|
||
private readonly patchType: APSN64PatchType; | ||
|
||
protected constructor( | ||
patchType: APSN64PatchType, | ||
file: File, | ||
crcBefore: string, | ||
sizeAfter: number, | ||
) { | ||
super(file, crcBefore, undefined, sizeAfter); | ||
this.patchType = patchType; | ||
} | ||
|
||
static async patchFrom(file: File): Promise<APSN64Patch> { | ||
let patchType = APSN64PatchType.SIMPLE; | ||
const crcBefore = Patch.getCrcFromPath(file.getExtractedFilePath()); | ||
let targetSize = 0; | ||
|
||
await file.extractToTempFilePoly('r', async (patchFile) => { | ||
patchFile.seek(APSN64Patch.FILE_SIGNATURE.length); | ||
patchType = (await patchFile.readNext(1)).readUInt8(); | ||
patchFile.skipNext(1); // encoding method | ||
patchFile.skipNext(50); // description | ||
|
||
if (patchType === APSN64PatchType.SIMPLE) { | ||
targetSize = (await patchFile.readNext(4)).readUInt32LE(); | ||
} else if (patchType === APSN64PatchType.N64) { | ||
patchFile.skipNext(1); // ROM format | ||
patchFile.skipNext(2); // cart ID string (*'s from: NUS-N**X-XXX) | ||
patchFile.skipNext(1); // country string (* from: NUS-NXX*-XXX) | ||
patchFile.skipNext(8); // CRC within the ROM (NOT the entire ROM CRC) | ||
patchFile.skipNext(5); // padding | ||
targetSize = (await patchFile.readNext(4)).readUInt32LE(); | ||
} else { | ||
throw new Error(`APS (N64) patch type ${patchType} isn't supported: ${patchFile.getPathLike()}`); | ||
} | ||
}); | ||
|
||
return new APSN64Patch(patchType, file, crcBefore, targetSize); | ||
} | ||
|
||
async createPatchedFile(inputRomFile: File, outputRomPath: string): Promise<void> { | ||
return this.getFile().extractToTempFilePoly('r', async (patchFile) => { | ||
const header = await patchFile.readNext(APSN64Patch.FILE_SIGNATURE.length); | ||
if (!header.equals(APSN64Patch.FILE_SIGNATURE)) { | ||
throw new Error(`APS (N64) patch header is invalid: ${this.getFile().toString()}`); | ||
} | ||
|
||
if (this.patchType === APSN64PatchType.SIMPLE) { | ||
patchFile.seek(61); | ||
} else if (this.patchType === APSN64PatchType.N64) { | ||
patchFile.seek(78); | ||
} else { | ||
throw new Error(`APS (N64) patch type ${this.patchType} isn't supported: ${patchFile.getPathLike()}`); | ||
} | ||
|
||
return APSN64Patch.writeOutputFile(inputRomFile, outputRomPath, patchFile); | ||
}); | ||
} | ||
|
||
private static async writeOutputFile( | ||
inputRomFile: File, | ||
outputRomPath: string, | ||
patchFile: FilePoly, | ||
): Promise<void> { | ||
await inputRomFile.extractToFile(outputRomPath); | ||
const targetFile = await FilePoly.fileFrom(outputRomPath, 'r+'); | ||
|
||
try { | ||
await APSN64Patch.applyPatch(patchFile, targetFile); | ||
} finally { | ||
await targetFile.close(); | ||
} | ||
} | ||
|
||
private static async applyPatch(patchFile: FilePoly, targetFile: FilePoly): Promise<void> { | ||
/* eslint-disable no-await-in-loop, no-bitwise */ | ||
while (patchFile.getPosition() < patchFile.getSize()) { | ||
const offset = (await patchFile.readNext(4)).readUInt32LE(); | ||
const size = (await patchFile.readNext(1)).readUInt8(); | ||
let data: Buffer; | ||
if (size === 0) { | ||
// Run-length encoding record | ||
const byte = await patchFile.readNext(1); | ||
const rleSize = (await patchFile.readNext(1)).readUInt8(); | ||
data = Buffer.from(byte.toString('hex').repeat(rleSize), 'hex'); | ||
} else { | ||
// Standard record | ||
data = await patchFile.readNext(size); | ||
} | ||
await targetFile.writeAt(data, offset); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import File from '../files/file.js'; | ||
import APSGBAPatch from './apsGbaPatch.js'; | ||
import APSN64Patch from './apsN64Patch.js'; | ||
import Patch from './patch.js'; | ||
|
||
/** | ||
* @see https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) | ||
*/ | ||
export default abstract class APSPatch extends Patch { | ||
static readonly SUPPORTED_EXTENSIONS = ['.aps']; | ||
|
||
static readonly FILE_SIGNATURE = Buffer.from('APS1'); | ||
|
||
static async patchFrom(file: File): Promise<Patch> { | ||
return file.extractToTempFilePoly('r', async (patchFile) => { | ||
patchFile.seek(APSPatch.FILE_SIGNATURE.length); | ||
|
||
const byteFive = (await patchFile.readNext(1)).toString(); | ||
const byteSix = (await patchFile.readNext(1)).readUInt8(); | ||
|
||
if (byteFive === '0' && (byteSix === 0 || byteSix === 1)) { | ||
return APSN64Patch.patchFrom(file); | ||
} | ||
return APSGBAPatch.patchFrom(file); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.