-
-
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
13 changed files
with
253 additions
and
26 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import FilePoly from '../../polyfill/filePoly.js'; | ||
import File from '../files/file.js'; | ||
import Patch from './patch.js'; | ||
|
||
enum NinjaCommand { | ||
TERMINATE = 0x00, | ||
OPEN = 0x01, | ||
XOR = 0x02, | ||
} | ||
|
||
enum NinjaFileType { | ||
RAW = 0, | ||
NES = 1, | ||
FDS = 2, | ||
SNES = 3, | ||
N64 = 4, | ||
GB = 5, | ||
SMS = 6, | ||
MEGA = 7, | ||
PCE = 8, | ||
LYNX = 9, | ||
} | ||
|
||
/** | ||
* @link https://www.romhacking.net/utilities/329/ | ||
*/ | ||
export default class NinjaPatch extends Patch { | ||
static readonly SUPPORTED_EXTENSIONS = ['.rup']; | ||
|
||
static patchFrom(file: File): NinjaPatch { | ||
const crcBefore = Patch.getCrcFromPath(file.getExtractedFilePath()); | ||
return new NinjaPatch(file, crcBefore); | ||
} | ||
|
||
async apply<T>(file: File, callback: (tempFile: string) => (Promise<T> | T)): Promise<T> { | ||
return this.getFile().extractToFile(async (patchFilePath) => { | ||
const patchFile = await FilePoly.fileFrom(patchFilePath, 'r'); | ||
|
||
const header = (await patchFile.readNext(5)).toString(); | ||
if (header !== 'NINJA') { | ||
await patchFile.close(); | ||
throw new Error(`NINJA patch header is invalid: ${this.getFile().toString()}`); | ||
} | ||
const version = parseInt((await patchFile.readNext(1)).toString(), 10); | ||
if (version !== 2) { | ||
await patchFile.close(); | ||
throw new Error(`NINJA v${version} isn't supported: ${this.getFile().toString()}`); | ||
} | ||
|
||
patchFile.skipNext(1); // encoding | ||
patchFile.skipNext(84); // author | ||
patchFile.skipNext(11); // version | ||
patchFile.skipNext(256); // title | ||
patchFile.skipNext(48); // genre | ||
patchFile.skipNext(48); // language | ||
patchFile.skipNext(8); // date | ||
patchFile.skipNext(512); // website | ||
patchFile.skipNext(1074); // info | ||
|
||
const result = await file.extractToFile(async (tempFile) => { | ||
const targetFile = await FilePoly.fileFrom(tempFile, 'r+'); | ||
|
||
/* eslint-disable no-await-in-loop */ | ||
while (!patchFile.isEOF()) { | ||
const command = (await patchFile.readNext(1)).readUint8(); | ||
|
||
if (command === NinjaCommand.TERMINATE) { | ||
break; | ||
} else if (command === NinjaCommand.OPEN) { | ||
const multiFile = (await patchFile.readNext(1)).readUint8(); | ||
if (multiFile > 0) { | ||
await targetFile.close(); | ||
throw new Error(`Multi-file NINJA patches aren't supported: ${this.getFile().toString()}`); | ||
} | ||
|
||
const fileNameLength = multiFile > 0 | ||
? (await patchFile.readNext(multiFile)).readUIntLE(0, multiFile) | ||
: 0; | ||
patchFile.skipNext(fileNameLength); // file name | ||
const fileType = (await patchFile.readNext(1)).readUint8(); | ||
if (fileType > 0) { | ||
await targetFile.close(); | ||
throw new Error(`Unsupported NINJA file type ${NinjaFileType[fileType]}: ${this.getFile().toString()}`); | ||
} | ||
const sourceFileSizeLength = (await patchFile.readNext(1)).readUint8(); | ||
const sourceFileSize = (await patchFile.readNext(sourceFileSizeLength)) | ||
.readUIntLE(0, sourceFileSizeLength); | ||
const modifiedFileSizeLength = (await patchFile.readNext(1)).readUint8(); | ||
const modifiedFileSize = (await patchFile.readNext(modifiedFileSizeLength)) | ||
.readUIntLE(0, modifiedFileSizeLength); | ||
patchFile.skipNext(16); // source MD5 | ||
patchFile.skipNext(16); // modified MD5 | ||
|
||
if (sourceFileSize !== modifiedFileSize) { | ||
patchFile.skipNext(1); // "M" or "A" | ||
const overflowSizeLength = (await patchFile.readNext(1)).readUint8(); | ||
const overflowSize = overflowSizeLength > 0 | ||
? (await patchFile.readNext(overflowSizeLength)).readUIntLE(0, overflowSizeLength) | ||
: 0; | ||
const overflow = overflowSize > 0 | ||
? await patchFile.readNext(overflowSize) | ||
: Buffer.alloc(overflowSize); | ||
/* eslint-disable no-bitwise */ | ||
for (let i = 0; i < overflow.length; i += 1) { | ||
overflow[i] ^= 255; // NOTE(cemmer): this isn't documented anywhere | ||
} | ||
if (modifiedFileSize > sourceFileSize) { | ||
await targetFile.writeAt(overflow, targetFile.getSize()); | ||
} | ||
} | ||
} else if (command === NinjaCommand.XOR) { | ||
const offsetLength = (await patchFile.readNext(1)).readUint8(); | ||
const offset = (await patchFile.readNext(offsetLength)).readUIntLE(0, offsetLength); | ||
targetFile.seek(offset); | ||
|
||
const lengthLength = (await patchFile.readNext(1)).readUint8(); | ||
const length = (await patchFile.readNext(lengthLength)).readUIntLE(0, lengthLength); | ||
const sourceData = await targetFile.readNext(length); | ||
|
||
const xorData = await patchFile.readNext(length); | ||
const targetData = Buffer.allocUnsafe(length); | ||
/* eslint-disable no-bitwise */ | ||
for (let i = 0; i < length; i += 1) { | ||
targetData[i] = (i < sourceData.length ? sourceData[i] : 0x00) ^ xorData[i]; | ||
} | ||
await targetFile.writeAt(targetData, offset); | ||
} | ||
} | ||
|
||
await targetFile.close(); | ||
|
||
return callback(tempFile); | ||
}); | ||
|
||
await patchFile.close(); | ||
|
||
return result; | ||
}); | ||
} | ||
} |
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
Binary file not shown.
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 @@ | ||
612644F1BA96586196EC27060CAE9D3F147729D8857899F16BAEC1C41A78FEB7E64333FECBDAB8A499328A099441899A9C30A741A55709647250264D061A06B413F3197E3AF5316D29246217C7C4792E5436AFE1F36E91C2963E31845FE4941A23DD63CD9D4E78497A33BA785FF5042CDC3A208C12CD6E3C5F9312C0411BDA969D175E380CBC3DAE9A708A3F6D885B21BBF3DCF68A1264F89741020AC332400FFB65A0927871BD766B115E26E4FD5262EA9E41CA5BF6849CC0AAFF14247646EB680FAA5E8C3299C9FAE9B0B57A02F3781C1658B8EE3BA9842AE698C0C5D166F44078B2E0F3D53614FFA593CA48C006F0471F474DA91889FD4593AD1809EE1BDC5A3764B0EE21D4264D65ACEB4C20758E5522D2AB29914B23DBD4190ECE41E4CFFCBF3578C8B740A6417BFF288224FA93B99A67C664E9D825497C40E32AB82508CC860085651056EADB232D93F7FB63A6CA67CD7C02FB466613A2FE6310F7AA7ECEFF5119DDB9F4D7022DE678E05D83C6D8A3C1B01E44E20ED6040C9938FD5E5017A6AE602DF579A5E4B91F88B3E93096742EB75664FF5155B5D90AB728578BBF700588B3678076EAD8F44AF7592AEFB6FEFF445FC07187BB2670C63C972A662C03421AB023CD8323EA6801112C3BE14C36479BCB24B000E99D0B073BC9881A03E69AF5BDA011DA3B220B3BD250B2CF5354D5DE85C0294701E878643015362B8B |
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
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.