diff --git a/.editorconfig b/.editorconfig index 74fb9e0be..ad2ef34d7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,6 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true end_of_line = lf + +[*.rom] +insert_final_newline = false diff --git a/docs/rom-patching.md b/docs/rom-patching.md index 3483905d2..84db6b10f 100644 --- a/docs/rom-patching.md +++ b/docs/rom-patching.md @@ -24,8 +24,8 @@ Not all patch types are created equal. Here are some tables of some existing for | Type | Supported | CRC32 in patch contents | Notes | |---------------------|--------------------------------------------------------|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------| -| `.aps` (GBA) | ❌ | ❌ | | -| `.aps` (N64) | ❌ | ⚠️ only type 1 patches | | +| `.aps` (GBA) | ✅ | ❌ | | +| `.aps` (N64) | ✅ simple & N64 | ❌ | | | `.bdf` (BSDiff) | ❌ | ❓ | | | `.bsp` | ❌ | ❌ | Binary Script Patching will probably never be supported, the implementation is [non-trivial](https://github.com/aaaaaa123456789/bsp). | | `.dps` | ❌ | ❌ | | diff --git a/src/polyfill/fsPoly.ts b/src/polyfill/fsPoly.ts index 84b0148ca..0b87d5de4 100644 --- a/src/polyfill/fsPoly.ts +++ b/src/polyfill/fsPoly.ts @@ -134,7 +134,7 @@ export default class FsPoly { static async mktemp(prefix: string): Promise { /* eslint-disable no-constant-condition, no-await-in-loop */ while (true) { - const randomExtension = crypto.randomBytes(4).readUInt32LE(0).toString(36); + const randomExtension = crypto.randomBytes(4).readUInt32LE().toString(36); const filePath = `${prefix.replace(/\.+$/, '')}.${randomExtension}`; if (!await this.exists(filePath)) { return filePath; diff --git a/src/types/patches/apsGbaPatch.ts b/src/types/patches/apsGbaPatch.ts new file mode 100644 index 000000000..ec44bf65f --- /dev/null +++ b/src/types/patches/apsGbaPatch.ts @@ -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 { + 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 { + 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 { + 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 { + /* 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); + } + } +} diff --git a/src/types/patches/apsN64Patch.ts b/src/types/patches/apsN64Patch.ts new file mode 100644 index 000000000..a4e34d1c7 --- /dev/null +++ b/src/types/patches/apsN64Patch.ts @@ -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 { + 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 { + 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 { + 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 { + /* 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); + } + } +} diff --git a/src/types/patches/apsPatch.ts b/src/types/patches/apsPatch.ts new file mode 100644 index 000000000..05ba6da35 --- /dev/null +++ b/src/types/patches/apsPatch.ts @@ -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 { + 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); + }); + } +} diff --git a/src/types/patches/bpsPatch.ts b/src/types/patches/bpsPatch.ts index bb3278177..c50122406 100644 --- a/src/types/patches/bpsPatch.ts +++ b/src/types/patches/bpsPatch.ts @@ -24,7 +24,7 @@ export default class BPSPatch extends Patch { let targetSize = 0; await file.extractToTempFilePoly('r', async (patchFile) => { - patchFile.seek(4); // header + patchFile.seek(BPSPatch.FILE_SIGNATURE.length); await Patch.readUpsUint(patchFile); // source size targetSize = await Patch.readUpsUint(patchFile); // target size @@ -45,7 +45,6 @@ export default class BPSPatch extends Patch { // Skip header info const header = await patchFile.readNext(4); if (!header.equals(BPSPatch.FILE_SIGNATURE)) { - await patchFile.close(); throw new Error(`BPS patch header is invalid: ${this.getFile().toString()}`); } await Patch.readUpsUint(patchFile); // source size diff --git a/src/types/patches/ipsPatch.ts b/src/types/patches/ipsPatch.ts index 804f9ae11..8a9b19445 100644 --- a/src/types/patches/ipsPatch.ts +++ b/src/types/patches/ipsPatch.ts @@ -20,7 +20,6 @@ export default class IPSPatch extends Patch { return this.getFile().extractToTempFilePoly('r', async (patchFile) => { const header = await patchFile.readNext(5); if (IPSPatch.FILE_SIGNATURES.every((fileSignature) => !header.equals(fileSignature))) { - await patchFile.close(); throw new Error(`IPS patch header is invalid: ${this.getFile().toString()}`); } @@ -71,19 +70,18 @@ export default class IPSPatch extends Patch { break; } - const offset = (await patchFile.readNext(offsetSize)).readUintBE(0, offsetSize); + const offset = (await patchFile.readNext(offsetSize)).readUIntBE(0, offsetSize); const size = (await patchFile.readNext(2)).readUInt16BE(); + let data: Buffer; if (size === 0) { // Run-length encoding record const rleSize = (await patchFile.readNext(2)).readUInt16BE(); - const data = Buffer.from((await patchFile.readNext(1)).toString('hex') - .repeat(rleSize), 'hex'); - await targetFile.writeAt(data, offset); + data = Buffer.from((await patchFile.readNext(1)).toString('hex').repeat(rleSize), 'hex'); } else { // Standard record - const data = await patchFile.readNext(size); - await targetFile.writeAt(data, offset); + data = await patchFile.readNext(size); } + await targetFile.writeAt(data, offset); } } } diff --git a/src/types/patches/ninjaPatch.ts b/src/types/patches/ninjaPatch.ts index 96f7d968d..6f31b4f3b 100644 --- a/src/types/patches/ninjaPatch.ts +++ b/src/types/patches/ninjaPatch.ts @@ -38,12 +38,10 @@ export default class NinjaPatch extends Patch { return this.getFile().extractToTempFilePoly('r', async (patchFile) => { const header = await patchFile.readNext(5); if (!header.equals(NinjaPatch.FILE_SIGNATURE)) { - 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()}`); } @@ -80,7 +78,7 @@ export default class NinjaPatch extends Patch { } private async applyCommand(patchFile: FilePoly, targetFile: FilePoly): Promise { - const command = (await patchFile.readNext(1)).readUint8(); + const command = (await patchFile.readNext(1)).readUInt8(); if (command === NinjaCommand.TERMINATE) { // Nothing @@ -92,7 +90,7 @@ export default class NinjaPatch extends Patch { } private async applyCommandOpen(patchFile: FilePoly, targetFile: FilePoly): Promise { - const multiFile = (await patchFile.readNext(1)).readUint8(); + const multiFile = (await patchFile.readNext(1)).readUInt8(); if (multiFile > 0) { throw new Error(`Multi-file NINJA patches aren't supported: ${this.getFile().toString()}`); } @@ -101,14 +99,14 @@ export default class NinjaPatch extends Patch { ? (await patchFile.readNext(multiFile)).readUIntLE(0, multiFile) : 0; patchFile.skipNext(fileNameLength); // file name - const fileType = (await patchFile.readNext(1)).readUint8(); + const fileType = (await patchFile.readNext(1)).readUInt8(); if (fileType > 0) { throw new Error(`Unsupported NINJA file type ${NinjaFileType[fileType]}: ${this.getFile().toString()}`); } - const sourceFileSizeLength = (await patchFile.readNext(1)).readUint8(); + const sourceFileSizeLength = (await patchFile.readNext(1)).readUInt8(); const sourceFileSize = (await patchFile.readNext(sourceFileSizeLength)) .readUIntLE(0, sourceFileSizeLength); - const modifiedFileSizeLength = (await patchFile.readNext(1)).readUint8(); + const modifiedFileSizeLength = (await patchFile.readNext(1)).readUInt8(); const modifiedFileSize = (await patchFile.readNext(modifiedFileSizeLength)) .readUIntLE(0, modifiedFileSizeLength); patchFile.skipNext(16); // source MD5 @@ -116,7 +114,7 @@ export default class NinjaPatch extends Patch { if (sourceFileSize !== modifiedFileSize) { patchFile.skipNext(1); // "M" or "A" - const overflowSizeLength = (await patchFile.readNext(1)).readUint8(); + const overflowSizeLength = (await patchFile.readNext(1)).readUInt8(); const overflowSize = overflowSizeLength > 0 ? (await patchFile.readNext(overflowSizeLength)).readUIntLE(0, overflowSizeLength) : 0; @@ -134,17 +132,16 @@ export default class NinjaPatch extends Patch { } private static async applyCommandXor(patchFile: FilePoly, targetFile: FilePoly): Promise { - const offsetLength = (await patchFile.readNext(1)).readUint8(); + 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 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]; } diff --git a/src/types/patches/patch.ts b/src/types/patches/patch.ts index ead3e7e50..a71b62e82 100644 --- a/src/types/patches/patch.ts +++ b/src/types/patches/patch.ts @@ -76,7 +76,7 @@ export default abstract class Patch { /* eslint-disable no-await-in-loop, no-bitwise */ while (!fp.isEOF()) { - const bits = (await fp.readNext(1)).readUint8(); + const bits = (await fp.readNext(1)).readUInt8(); num = (num << 7) + (bits & 0x7f); if (!(bits & 0x80)) { // left-most bit is telling us to keep going break; @@ -91,7 +91,7 @@ export default abstract class Patch { let lastOffset = offset; while (lastOffset < buffer.length) { - const bits = buffer.readUint8(lastOffset); + const bits = buffer.readUInt8(lastOffset); lastOffset += 1; num = (num << 7) + (bits & 0x7f); if (!(bits & 0x80)) { // left-most bit is telling us to keep going diff --git a/src/types/patches/patchFactory.ts b/src/types/patches/patchFactory.ts index 7c615fb58..79442278f 100644 --- a/src/types/patches/patchFactory.ts +++ b/src/types/patches/patchFactory.ts @@ -1,6 +1,7 @@ import { Readable } from 'stream'; import File from '../files/file.js'; +import APSPatch from './apsPatch.js'; import BPSPatch from './bpsPatch.js'; import IPSPatch from './ipsPatch.js'; import NinjaPatch from './ninjaPatch.js'; @@ -20,6 +21,11 @@ interface PatchParser { */ export default class PatchFactory { private static readonly PATCH_PARSERS: PatchParser[] = [ + { + extensions: APSPatch.SUPPORTED_EXTENSIONS, + fileSignatures: [APSPatch.FILE_SIGNATURE], + factory: APSPatch.patchFrom, + }, { extensions: BPSPatch.SUPPORTED_EXTENSIONS, fileSignatures: [BPSPatch.FILE_SIGNATURE], diff --git a/src/types/patches/upsPatch.ts b/src/types/patches/upsPatch.ts index 8bfd7c389..b3ffbf86f 100644 --- a/src/types/patches/upsPatch.ts +++ b/src/types/patches/upsPatch.ts @@ -23,7 +23,7 @@ export default class UPSPatch extends Patch { let targetSize = 0; await file.extractToTempFilePoly('r', async (patchFile) => { - patchFile.seek(4); // header + patchFile.seek(UPSPatch.FILE_SIGNATURE.length); await Patch.readUpsUint(patchFile); // source size targetSize = await Patch.readUpsUint(patchFile); // target size @@ -43,7 +43,6 @@ export default class UPSPatch extends Patch { return this.getFile().extractToTempFilePoly('r', async (patchFile) => { const header = await patchFile.readNext(4); if (!header.equals(UPSPatch.FILE_SIGNATURE)) { - await patchFile.close(); throw new Error(`UPS patch header is invalid: ${this.getFile().toString()}`); } await Patch.readUpsUint(patchFile); // source size @@ -99,14 +98,14 @@ export default class UPSPatch extends Patch { const buffer: Buffer[] = []; while (patchFile.getPosition() < patchFile.getSize() - 12) { - const xorByte = (await patchFile.readNext(1)).readUint8(); + const xorByte = (await patchFile.readNext(1)).readUInt8(); if (!xorByte) { // terminating byte 0x00 return Buffer.concat(buffer); } const sourceByte = sourceFile.isEOF() ? 0x00 - : (await sourceFile.readNext(1)).readUint8(); + : (await sourceFile.readNext(1)).readUInt8(); buffer.push(Buffer.of(sourceByte ^ xorByte)); } diff --git a/src/types/patches/vcdiffPatch.ts b/src/types/patches/vcdiffPatch.ts index b4ccd7a0f..b310747df 100644 --- a/src/types/patches/vcdiffPatch.ts +++ b/src/types/patches/vcdiffPatch.ts @@ -131,10 +131,10 @@ class VcdiffHeader { } patchFile.skipNext(1); // version - const hdrIndicator = (await patchFile.readNext(1)).readUint8(); + const hdrIndicator = (await patchFile.readNext(1)).readUInt8(); let secondaryDecompressorId = 0; if (hdrIndicator & VcdiffHdrIndicator.DECOMPRESS) { - secondaryDecompressorId = (await patchFile.readNext(1)).readUint8(); + secondaryDecompressorId = (await patchFile.readNext(1)).readUInt8(); if (secondaryDecompressorId) { /** * TODO(cemmer): notes for later on LZMA (the default for the xdelta3 tool): @@ -208,7 +208,7 @@ class VcdiffWindow { /* eslint-disable no-bitwise */ static async fromFilePoly(patchFile: FilePoly): Promise { - const winIndicator = (await patchFile.readNext(1)).readUint8(); + const winIndicator = (await patchFile.readNext(1)).readUInt8(); let sourceSegmentSize = 0; let sourceSegmentPosition = 0; if (winIndicator & (VcdiffWinIndicator.SOURCE | VcdiffWinIndicator.TARGET)) { @@ -218,7 +218,7 @@ class VcdiffWindow { await Patch.readVcdiffUintFromFile(patchFile); // delta encoding length const deltaEncodingTargetWindowSize = await Patch.readVcdiffUintFromFile(patchFile); - const deltaEncodingIndicator = (await patchFile.readNext(1)).readUint8(); + const deltaEncodingIndicator = (await patchFile.readNext(1)).readUInt8(); const addsAndRunsDataLength = await Patch.readVcdiffUintFromFile(patchFile); const instructionsAndSizesLength = await Patch.readVcdiffUintFromFile(patchFile); @@ -260,7 +260,7 @@ class VcdiffWindow { readInstructionIndex(): number { const instructionCodeIdx = this.instructionsAndSizesData - .readUint8(this.instructionsAndSizeOffset); + .readUInt8(this.instructionsAndSizeOffset); this.instructionsAndSizeOffset += 1; return instructionCodeIdx; } @@ -413,7 +413,7 @@ class VcdiffCache { addr = this.near[m] + readValue; } else { const m = mode - (2 + this.sNear); - readValue = copyAddressesData.readUint8(copyAddressesOffset); + readValue = copyAddressesData.readUInt8(copyAddressesOffset); copyAddressesOffsetAfter += 1; addr = this.same[m * 256 + readValue]; } diff --git a/test/fixtures/dats/patchable.dat b/test/fixtures/dats/patchable.dat index c345c53a1..9d4c0554f 100644 --- a/test/fixtures/dats/patchable.dat +++ b/test/fixtures/dats/patchable.dat @@ -8,6 +8,19 @@ emmercm + + + 92C85C9 + + + + + + 3708F2C + + + + Before diff --git a/test/fixtures/patches/04C896D-GBA 06692159.aps b/test/fixtures/patches/04C896D-GBA 06692159.aps new file mode 100644 index 000000000..88fc7d096 Binary files /dev/null and b/test/fixtures/patches/04C896D-GBA 06692159.aps differ diff --git a/test/fixtures/patches/DFF7872-N64-SIMPLE 20891c9f.aps b/test/fixtures/patches/DFF7872-N64-SIMPLE 20891c9f.aps new file mode 100644 index 000000000..65aa4716f Binary files /dev/null and b/test/fixtures/patches/DFF7872-N64-SIMPLE 20891c9f.aps differ diff --git a/test/fixtures/roms/patchable/3708F2C.rom b/test/fixtures/roms/patchable/3708F2C.rom new file mode 100644 index 000000000..175fdbec3 --- /dev/null +++ b/test/fixtures/roms/patchable/3708F2C.rom @@ -0,0 +1 @@ +3708F2CCBA870221816DC7AAAD2AD2591736CB694F39D31B09F414A19653C5CF4E1D0E297BE85F92C1A2218C759FB4F772CAF007E3DC334B74204892C819BF619AF55AE0061D7C6F483E12BBD0C63E59C52F574AE14949D0BF236D44F7B3BCCB68A6A10C283BD2ED8BD4191A5B8B6702D1ECB931BCA9E3C21915788267743078ADB5ABED17BAC0B080FF71EDE3C2D1748BC685CD9FD3326358E21559A546B1E08E06EA523A84143FF8497FBE437C71E1643624EE1937E67CD00DD40FFA8ACEDD87313ACAE933BDD0467F5C7459073C9B1E911E03E911F3D1170DC31BF9FAF5FCBE2D9E15AA0C273E1E5802EFD5B01651E57234F9A091151D5644CF6D162C0C3629DF7D9AE768EE874751FAFB012D7130A907B92AB84C73DEA09149ECD0744F95A8CD2650D40F63AD16A437C6C95AF10916BDB3CBFF1EC1C67E582BD9EE01168ED8F611AE733F15AE16E84D20013373F735A32B22E8E81735F229526793EAB83B9BD775F7AB4708AECF0F978B0BB699449E5171A0C01221E729DAEBCD575D2C58DAF582A745610F7CD0FC6B6FA98B82FFE0DE76A27E2ED5AA185A6064CAE032B440F05EAB6626FF4D02AD4DB04DB2C9C2A8E5022DEDC6933F5AE1057510D5CF80561F2194DB551F1BB1749BC9C08E66E5C5AC55B6929C779B3023C2E8A2AE2F109FC3AD0FDC6FBD36785FCF41E9B329C9BF2ECEC41543FD9A145E3815BB1EC808 diff --git a/test/fixtures/roms/patchable/92C85C9.rom b/test/fixtures/roms/patchable/92C85C9.rom new file mode 100644 index 000000000..2cf520226 --- /dev/null +++ b/test/fixtures/roms/patchable/92C85C9.rom @@ -0,0 +1 @@ +92C85C9D77B1DC6F6C296F4D991B65EC35CA0A4DD885214B746D2B9BE98DC298316943E9BA5707DCDBF05CC5A41FB95329159A98E6A4BFC7A26A475FE8239EE50457A05D0A27C1C241D0708975A3E624E1E0F6B8F49385F94E5FF0C15FC4A8699578A67DEDCDC6DC09FDC11649F7E0431EAAD577714E18D030F4E5951AD715D81E8345301D8D4D31A86763C53DC3F1F2F74DD3F1811C76C9611F924080ED10AFD884F9EAF7D591DDEF469091D7E4C4CBB093514FF7FC1D11324E50530AE90EDC1D35BB9D86629A40C877BD1A56499DBBEC2802C90C5EB7DA41FA0F2B68A302D9F307A1384E4F86E7D8C6E5DA7A8CB50D6BA348F71D41FDA040BEBEA7F56F9CFE1F8143FA4AB098C4E509834EAAB0664883C0596D9928691D86C03C0664CB144D1936317A1EE201BF8BB5FA893116E90C2BB867CF8E1A4B46BB03A68E36CA65A201468CEF380E174CE67ADF52408A52234E9CF3F88863DAD0699D4F38516E150D8ADA1E1458F0BAC7FAA4A9F3DE33C3D75DD63254434A3DA613C1F2ABFA879DE03CE934DC3F90E902081F87E64EED22ED58CD11F6C4F973D2B45B32834905001DFE6BAE2933ED8D1D276831316DF7B07781C0B680E8FD779D4C16072683525A7CE4EEA2D6C6B2E4D962795453534F54D0DA1602A94C728F03FA50AD49E16D70B8349D5EC6AF190EF7B35C4305647861780C8A75F3F58FB471851F6587F76B373AA5E10549BAB9711C7B404C5E9EFF7AB29781C540DF2D2F61E5B93620D538BD782228B8E56B305752EB9E5B0752678915790B4A2F0408F36D8859C301439B43E81722C3E9A5DFD84698372F834CB911C56B78EF6779E90F17762B41024CADE94AD2735DD6D739EE05D452EBF41E31A307144655023E74E8924AA643CDDF8B4BBFBF22E565F22F24D9DC29857F9C32DEE59776941266B8E48C21EFB2587D62BBC730E6C4A0C8E5D6E844DE250AC33B47BE3DDAE927C958AA3D970BD9B109832F86404F27AF0C624D44FA0F063521E71D1219475BBCBB2472C07072D9EF2A62492B1084B0583437540D52D24F648CBDF5BF607C384E734E1198ACE4D8C85111F732EBB6575BE1C9FDE199FA253BA567EA1A023DCA6249288DD2D5B6FF855676F83A56470CF48A2419B900C71ABC8B470B9675C2457331CF15E5EB6C6056F703B0F5ED80F08181EBEEFD8FBB134AF9829905360478170192B010AC47A4CA05C8350A45F1CE383AB6D5C1D64E499A34038F2793E17C928ACA893996B91E59DE79609AE7BEF8CA2E0834896E0A95CBE697E5664F3146D73F79B919C03676BAA912F2DCFF15A65D82F2BF08DEA7BBC7FDE5A5B96BDAF39A5A27A40D9B9EA88AFB6AD81748EB584811551C338DE1F9373462B95F9EED43A0A29FAD31EF3651A7451DA76283EF7C5E0C40D8B55D18C366990CCD70206A6165D960458578319B9A81CC6998D56041501B7F05D2DBA88D1041B0E86AC031B3EFF9994514163F52936D89136F6A418637F80929802C9FAF923AE1A4666E1498DDAA52637FBCC88592A09E5ED22F588A7CE6AC21C64D7B709DB393E81D821188B3E13C591AD6A737752CD7E2D29FE3761340F2328426706B9CD64F31B5B664DE4EFCB1C97968F9805DCFECA03071F9495E6587408456A997491A83F6C9A454E452DE304D3DD645861529170487AC97CB423A0502B7806ADB8C8CBEC2B990258052A30715A212FADE976ED9297214CCF5321C16B0FE678D95E517D95D8432C5B696EDB59BEAEAFBE095B059A48BC6C86874D7D85B783C00F01E77F0683F0307E0BB93F26379981A35C197B0291FC51BF2CB40A2D3F98C444E9F67978705FCBA6F1287EA153CAE505490C9546A7B639B36352C91F9E73F5876151A557038175F47F7A223BCEF2FF4CFB5226725F77991F8DD2ACDAB07A5E2DF65FCDC6BCF06D3CACCA0FCBE4AC1C4CF4EFC3FAA03BF39CDEBD4BDD8B292DA0DDD746E0AA8E3725BD5F2F7ED6EF07D3C744CA89832EE32A48FD32FD73109FB8F43EED8014D622A963C98E345618A9F71027DEEBCE1A062634FA34A30CBED146746A9A8469121885986D7E8DE9FE1B6B4F0BFF91D4701F38CF736C60ABB39A3625C0040ED738C8D468CB98F9792F7F5FB238DD57F301DBDC9A2A566B73CD3EEE1308F8A1AEAF7E6F244750CE454F7BCB63117D7055E8329159ED92659C2CEB80018BC521E1280FF648D6A758BA921EB977CE7C27AA3FF93DA2F448C57615F0AEA43C08B7AB3A7A18EC69DC8BED7A8E648D3AFEE78E9C46B09EE39CAFBC9BA60D1FD91E170E84DF8BB980D3DEC162E466596A272CFA6E805EBA3B6EB1D77824423A31C558FE80CA2D940AFA3332CC33D531F3E049D721493EAAED2E092F4742CDF467556ED91A54543BEA8CE594B8061538893DE5F7B39E82FB8863A636F7BB8703B577BBA7230F9973CF618CF08303346C63A0C4386B659B3B7DDA2D2A8123EC8368F2466652FF0659BE9D441846770D2A6E7A1DE92695239F97D2FC6EDD7A9B6218BE10F45C28728EBD1B3252FDD1ECD539285A9F7BA39BCAEC454A1D4230B4D9099CD6B12E3E29FF9A3A2959285DED30F57DE331E6A506E5757460AD59DD0C05A9DD020A68A14887CC33CE5B5BAD3E41E7E65511CFF2CACFF04B7C85A2DD985E5F3D7E55B1B7EBCDF773965324CA354AB2A3A2FD69A409075766B2D977585AF52AA3676579312FA8E3174381B1292F0B7B1B8941752D018F07196C7F598F0168897F587423D6550115535081227201D08596AD9B2F01F80289286AF1DC1812710AEA4C34C085C13D0B7B34FF0EDCCA37F48DCC58560D3E85580DCADD4FFF0A3E0E40E23C87209B1316A40BD243740CDF672E1DFB83C1B97B85D1AB26F2F3F33BEE0C31236B2F3AAFB6D310DB7593D9BEABCF6D1D3B50A9BEBB7ADCC05AB2EC988F75B11F889D859E884ECFF4DD906E4EC5EEA349818B0AEA98C184BB85FBF48CF02FFE4C0C96F5AB77B8D153F71A30E849E6A7646EB49997AEDD4C5F89B68DF0909110EB0F65459D02C50B09182D890BF904F8D862115C7381FF7C0AF261D449A202A5F0BC8E30F3A8B41D22216D1B1B414801417F303B05525D01B463A71CD97AFBB98ECE9B733301D5CB92D10060F096F71930EA3FC79F2291F0E9C3D73ABB23FEE5E96F448009A171C3E076895F3A2FC89CF4975D513EC7A9F2F6D03B36D0631DB6344F0ADCE6BA6B17201A16B12542D72AD93F022984921D5BC51C226281233C2ED666F2302A64E0EA7E6381F50113C5F3F3ECAD9F4B23A16937EE17C7699916CEA63BEF4983EC4910F4C14530DD421A0ADC8B81BA68113BCBBE56273CE398A203799919E9632737A63744C962A1CD98C5B205D4162123CAA14C4CC1BC38CE8BD64F90989D910F89269536E7EA1AFC3CE2F1B61BF3D3E3D1A8670E590D8B0001B9F884D4ED5C8BDC9A9F3AE96DDB94FCB9FCA13F2CC23DEBE16728F52FB286DDD51AB3BF34B05D43D422D498774B686EAA426879DA0C92D7B6E01F824746F4C1FEEA63927759B7EAB2EAAE716B53275AFBC4399FA9FEB20EE5C77E714DD1BCDD6CD2196B71AC4976995A0DE36E071BF0D1E2E40057F83C8200A8CB373A906F693215FBC193F547B60DC8207C7C0548D2C94B27773C38ACEB1D94149A2034B088AA098F742B7812059B812FABD78AB29B0E1D14EC3580D650C2734746765651927CC87BABE89214B6775C771A1F78F54B0F6C5E05523521C3A4736128170CBFACA5C93E93FE62F67B77AE45EDA7020481B0D59831C032704696ADB5480E8C762A88C8ABA9EA69774129701031165C7CE55787155AE8F68AE441DBCCE66E0E86598B99FD71B289543FFE623C96B80923CA78C868086845E1FA9D440655303ACD757CE6FED2B836020C11A415D48794C1D7677163F3BB455BE6D1C362DFE080272E56ECBED6CFE7679CEC120D8293D0F357E7D77A08BDCD0000F72F1684996D56CA3D969B915F50DC0A137ACDC86E37F0E76F0A16EB113E245FA2B619E9CAC4FEACFB97C9F7B711FA7870BBAFF696B4903105389B0BE9D6DBAE0A871A09EEDFBA622B296D0627FE01FFDDDFA065988376A0E1433AC7A7BF8F4DE45D2A5CB26F8824CB8E14EF0BE86E5F7EB7CDEA0B9FA7F8FE951C6FF5247BCBA719C39B9790B9A5EEB18DC3F626F74B3F005C08BD61691127F532EABE3429CB12107638D848C78FA7E27B842073DD9BFD1B20086B0AA4CC7BDAF14FD2FBF7FEC1555D9F12A1834F6307CE27B19B8D95433EB60F96E37E9531AA9B59AAEDE9537CFB285FF9ED740C202F1D60206D5006B17783A1A9AD016841F643FAFAC82EE78E09793010A3CFCE824E082ED91C744AB0622C01812A0AF4E3468928F41D2BF94E0F2E046BA4829949D9EDE608B64ECFC260C3CB679BE12D0CB883861006B371AAF44C724327D3E391EC2BE9B8EC07322120F2320B240CED4E3709094377BFE9DBA3627A60AA7E09FDB413C63DAE94E1920047747EE327E77EF337418E9DB83BC0169E91E4BC6AC157415374F0D221F83A1115CE701B30AAF86436916D0958F558D76027FAD21B7074160C51236FEB391798D8E904C8028325B3C64DE593E1967183DED59B401653422BD3D955C16B89C82F3076E8CCF80D2F1C3B32BB3E35EB8A189FCA40A4F25E5CD7D877E2169CCF844F85BEDEBFE7000869CA2B50C1EC0791DB6D4717D6709D4ECD99C33BE0C2D920B8F8918DEB7A6BF1C7A7D1C19F6AF5BACA654A3EE827D503AD151B494FCF9E512ECBD5D758D23F179C065A40B5B8DBA354E0B4BB2F80CC37D2B868000E19F67832D6FAD5A9D69F98FBD9A6704BED15C412F69951700DF6799AE13326E1EFCF831664DCFE0289EC0A8FF4ED2E6BA44CA17B69BC2DC4B345677EC13C6FFE7E61C8B1F4E2F6203B42376B6ED398CCD1C7DC4D0EC9B03065227862ED33FF468BCF5F65D138A6F0B273B0A35AB2CB8BB2DA2A62F66CF9B931689B04192FA1A101B0B6B4EFE262B363FFF7C2FAAA86209C29F7334F9E10B1FD68BA74B6E145F5D84C59EDC2FAB78BF1D5352AAA74FCF26BC848F9CE6769B675D0CE480FBF897C0BF0301E4F3925B3667681C2FC687A901BA06BB2BDB4849C19325571CFC1C20101616AFCA6004BDC7050ED34BC2711E63114DBDE3C584C139065BDE09A287A3CBFED134579A852C5022B02E59915220E42D76CD25DE78A68D867377283CEB18CC9CFD17C9C39C33D88478BD2FC5A2FD7EA889FBF616AF40ECE6875516DA16625D325AB62AD71F7FF42060288AD7AFB001B5C9FF44F570C99E20C915C546441F98519B0FCD9513EA35BB30EDD3E2902684F3CA2950F97546806A23F09527A2F30D8AAB38B646121E7E035077B8DA5AA52C05FE38DA92AF92DDAEB69237E1BA1A1EB9603F37EB235FB28118F8B4FA6DB3A8EE1BE58ACAB82AD6CBF8C86768ED64F44CEB2F17561A5C0BF7B6654405D6376EA8B0A278B457BA712EC8AD1A2C778C6B306999802EF55B9D8BEE46689E4472D1A5A56C03B8F8A45E6C0AF958B76032DC4E1ECCD35B454BDDCC5701E0F28BE89AF17EF1C08ED35D35ABE562FD26F5719A4C6E3607844A82CBB80D5D6B471C83D885BFFA4403DA2BEC0A81729E1A6F818CC609BD094A9EB86C5E8958BC96931232622FB2D2EB6BA089A0AC262C2E57D3F68879D693B93ED805EBE7A6E1484F47734E862E318E0E4CE6E0EDA7374E5D84956D26328F424A83AD4DB551C7E7A65F1AFEF686D0E1AFEC3C6F9F0B4141AC9EF092FBD59CB80197FF4782548BAE8F522917F78E8174989AC33A93847EBCC26C4AD40BE1F707862D41E8CC5E9DA75EEC2587BE46A863C6B36EA2F364A681960E4A5824FADA7564287C948A60295845E34E28A4C018EE46C9A09C0055F20FF275B5E7B03A36B6E2E78837FDC04A779E05B35FCD28AFE300E36F1251112A059A1F001801CEA9D871B05BA9BCF7351B3F3459CD0ADCB320E9FEFE0928E5D743FC971B8D2053A0F710BE5D9B6E8B1068F6638ACEAABD35A56539A19307D9118555C5C74CAF435D1EE6DC4457F66B67B743C7F74AE57722E93E07D9141F0A551A9F74EEB87B4D19909D44302391B4F429ADB37B8AFC4B1005332853D601FA753FFE378D4DE3E16C362B81E4B7A12FD7883502BD3D667ACE12206FA5E5F8C063EE5B182F8C5431B6142259C95198626C7EFC04013436F01A70DBE169EA9CB37BA8D41E213633784365FD4946EB3E4CA00AAB21E55AABB3FEAF7F9E541190CFA474AC2AB5AB9DD46D18A3C824201347F44D27447EB77DE231142E3E7590ACD893C02008C64A996024DD4460AA928F2EF65F7B237F8C696332B8D8B2E37F31D1854A286E9B99CAC1522171A078D6453B51E55074C230E0FC794347E568A4D55A0C65F10F740141B75E631744FE2C0A46188F60F05CB3221706229F590D1773663F3E38B7B4EEFEE797B6B0B98788A96055BF0727E9DFD178FF9740B6F64E2F74EF12E325E69434AF8601DBFB05321BA0F1B8347A1D1A8D3A18FD93AEC522FD000FDA687CF629434B30425ABC823ED5644D3738B30A19007373500B4D065B40A32FD4A3899F4AE6C6F2F46E32D59CB2A8FD01345099CB79E5ABEA5C212E80EEE304FDC493C58515F3B594A5025ACE23038A7CBE304D07BA5838929FC6D84F420F85AA6F698B02979C327C156918BE60EB096E30152E0258B3D3F8A647DD881A41EDB97363D4C9323A97223349C6BAAA3601AE0B4D78229F5E4A10002E146160BDD605D78626553E21D36D99C0266EB335F72BF57DB75DF6DA6D9C302C7D3A71764DB261F19FDDA5A36358DCB76B60A64F32A77602DFC5D22357E2EF1CF93161B1B94F3B80696AB6633EAA4DB36906DA3940D20928F226F83605B4116DCBFDE2973BEEBF1DD660BE9DD72DE56E1A840800666D5794E50D6DDB04EA42F5331EDC300F516E071984A68ABEEA9C6FACACAC2DCE765DA618DD13B230F85C8B2D23932261C79C9F1109C3A57DF660D2E5242BC5EC5AA355297C2E7F96587C2138BAFD06DBB9AF23A336C7706A5730EA3E19C228AC3223D0F82FB108867F3BA94633B013AEE8221DB2676BC7CA9AA42B10C8812CB9A0C2CB35B8E2BBEA0F8621EDE9E6C6C2B4712F43093EDD34C3475DCE14D677579C5365945F775B945721898FC176442657197E2A240B89120E72596045F83AD352D9D8BFC8DD22D2F668EC78C7EAE91C27385E05B392A763C1F24E72B69095DB79C74C81D0CB4F9B5A434F4C57F60C7DA54C41FF4A0075500165E346491B316B2869AD858DCD0C3781074BB4116D11D0AC4AB56FBD4CD2A70749FADAD02CCA09544AB18183A9CB37A1FFF72D99D5B4B002B577090B7CB4F880B754D4EE888C1DD1E293D8918DA732283DCE0BBC53425B89EC9A50C35393E01F18175DCAA8BC6575F3DC6474520C7972BAED0099177DF634EEA55E099398C72600D2ECD6AD7E71363657580A9CB03C2DBF7DFD859A89C1D8E26FC0DD411976D2448F4FFD8E15C87FF6CA1DEA592E4FF69E0E81A4107BE057B805B9BC203F104A55C156EFBB04A1EEF70B8234176A23F91A8005C427846CD36C2E4728BC3973E09FAE8571FCC39CC7CBCC6D3B59E7F731EB9EA04FF495D65A946E77506A7AEC4CCE2F21FBD4B6933C3A8E8E7A73852789C91DDC1752600BAB4E28A0F42F3B261AD19320F0498AAFE4BB0D84411095D6CF27F61C09CBD99F786F58E88B2F4A106EFC8BC6C1E415F272B98DC23F54AA571ED140F0B66B79F20B1FA89AFAE5F855DC2D53FCE4A23A7468E35E63335D88663F3215529467E860C905659D0EBDAB97D3208A3A1E29256BBB7DCDE39BC9E7D29B6AB4DB5762E40BF59475C81C34744F8BADE7D3960D2C651AF8F9DF7D99E5EDF4674C8DB130E393B0F5E8E1B984E0E5C2EBB8DD4365FB016EF9F20E22A41AED2F83F8D3AAA39059C6A7C30EFF041C8C1F18F3867E947E715449BB90B829034D53EE12429684F733F6326BFC22FDE4FA417C3B99964EAB37E1941E7A74C7E5154C0BB44D1F0325027FDEF679CB223C29D1B6EAE8E7707AA1EB596183F83C5A38624D71DC1596FF5BAB20A900D115BF08EEA19439847F2ADB03BCF812BBB610425D612B0E76C91929467EC894D2A789F2FD1940B78DA6FB1580508CFA8714892ACEB63FF36ED4FB723D8BDFBE3EBD7A7D1E26853501A0F97736E3A99E24659A04F76ED8FE9839A5DBD5BBF0F12C33380E69472A7E6E9CEAE4D62DE2C9B990C0CE5613987BF5F330A443B56A6D9EC7AA09CF9C3FD2CF93B10BE3F1355898D196C60FEAC66AB6884AE0A00C93AF2167DCF228078E3313919BB60F2DB3A26F1CEF3EA47DD21076BB1353B5551DC0A4773AB4747143BD26FFE5D944AE94068FEBFD63B42C42E87F81DED498C3D3C6BDDC2B95831E589FA567623FB00FE812614E6FD54A696D3A11CBC370D68E64ED2B29413BB27DBC0B394FE761386317B733FA7B9549B23F0A2AD95FCBC8BDEFB95A61A27F8E67E2D68596A0AD6E33C68E88F32098D5DE39755A68DFBAD6D8AB572DFE1D720F5CACC60D5E740C6BF3315FA3B9158BE42C2CA8CB1E28C778E31895CEFF56AB3E6E7D00D8DACCF932BCB9BD87B1C8B99C98613B05C6DB13CEB92EC116F36B282F993B5CE6C39B522751F40B3803E859818B9A3D67222F8751C7CEC6961F318643EC5CA603CBD12ED07DC29272993EACB4D45B2B3231915345BF84E14259FE820F02D4D1844ECECC6933C19A10F992A57AD446BB5F1F5EBFF233DD36EDC1E8790396DB995797ADF28BFFFD2B7ED8A02E251F7C5B14395211C6F0A0A035AE9875EC165B6519A7C65383E694665A5E6F84D837A648D84D24F48B9C18A738F04D4593BF125281E67921246E437EB92DAB3BA59292069AC52F82FB4AEB42B862D450C1AE6F52C2375912517692A71FE3E783E624E9B6BCCB4E8467AE558341D6BFCD6043F51328AAFC42268683A5C91F9374E639A3F25A9531397C250E798DFAF10E7381DB845B85CF9104A7F0296088BB83A29DE39842ECA59534117889F210B6B946E42C707D668987D992FC8FE20CA5F794356E45D79E180DF74D6957DC44F3906DCA5273C35D6F90FEEB2E37B60DBF2FA7E300254DD2C752596147711DBA66C8F0651524ABD18602B0BDE03E41EDE18E128A7F91565D871CCBAD26DC9CFCC39FEC65D66926DC1F7B1391F59C07061BF2AF0ED3DB6F1A62DFB882EA0A0BD6F527D4457862942CF1E0E5B9C9FD887845B20397DDA704C30294C3ADAD4E4495E795A999CFDA9C21FE488230B40378AF5DF064BA8E34C3C41E843B4E5609FBA96ECD3BB6FFE9371DD14FDF59E1BE89231AB87D1A08B4666519F723A93D1022F3A0E5B5B135C123BED99F3963781FD5D939DAA596C6F33F07D367071EBB4C2CFF95613C9C42884BF501770B5E250A55C5D4BD39BA92F8F3FE90F9426E0ED9DC61DD8988D5267E93EE702F6F323AC4AF55EEF222C75EAEB2239AB4244F0B1F6E658775C78416F72CC4588208B44AF6C65C9B6D106E64055A5AB33B7857BCC26B1DC6ED09FE7F4F8B0A99863A4EF5668D00650627B3D40E9875083B4A9BA23E8106E64A0DA8A93D1D4B05AA316C76E6235D97B17B912D8129595FF0B25DA5E898D09AF6F8C8954F0A2AA24CAA20CDDD59066D588B67276A8EE7AB896225FC545C0E6D71077B82ECDCBC8036947313E0D6AD4CBF72C627D34D6FB22D9C641C380CDE137A1267DBAF2A40D9E90EE4E2E7A1BBE9D40A31101FDF080D2924378AF4F4A1EA61427F6D5B7B68DBCA9448DE19D1244F7DA6564938C3286F95A6590B4492A6B8552EB3F4B1A93FC64B4F9DFB6599F3E4F5CB705D72CCB955F414DA6C2A5EA3C3B2EEFDA01AD5F8F00834CFDD85C9324463211496224842AF53E9D50C72E0ABE41827BEA8BB19D16FCC60289EA4E2DFE5DFB27DC2D40B8E75C7CCF118C2B0EB1B6F529E13258D060BC172912971A39414785060F7F0FAD68200FE59B649B464688BA0C15A2D53C237CBECB6C49517B7233D388CD6D3D175934ADEA5F1313AD4AB7DED043C9D09C21B66D37AE721D7E7C8076A0181D9A73855042449DCEF221BC945683BCABF617A9B7B8A12DA1B95FD3F92B8A424B8A709F2A5E3E63138DD983847A705730D90365BCF7416D4DA57ECB0394F121FBABD1F828E5D60211CA2D9BEFBBB4E273470038194FFA59D58074280559C5162713EEE670DB64CD3BFB2CE3DE711B8340EFFB2E41E011691EED4251F2B02DB40081C1459F3D131730796A5000246A0C9CC8A83CF2B4C58BF9DC243AFC6917860617B34DA917B0960AE737794E3CA3FBC86C8B73DCDB20B5DD023611865E00D916286F7A2289D1067065D431DDD0C8C820EBBD82ED9A7CD6DC4E6E03FACC8FCB11FC06C13FB97A9D63446EDF07BD01F513AA0712B2B4DA203E1F34CB54E08C3DDAA61B6E1848911D739550091D79570828C9277B2FEB31CEC3EA89A2F01028A0D1DBB2DE2FD29DACE1A2A456FC45A1FD8488CEAE617853F433952F9F3563BFD389365C508EA5377DA6A9740B856E941701E4D31E8C24432C6FC52ECB178DE31345F4A3213ED69C3106C30D0BE3334BEF21840B41F8B442E352CB3743C2BF2CF7C089950C9A7DB11C3FB858A9C1CBC09A11A4C96461E8BFD5A7712EC68BD3F703121275612537C996DFB72120FA96D44C466F6C73FE3F06719F2E9BDF689B7CF17D111697017B62B97E2A361DA7CB922A0641826A430C53825DCF830A5DFE426DB40DE5E0CE38B18CCA33F41B85A5C70D6EB12AF19A37D5522B53467A8207E148C7E8D65CEE7326E8E7E0A54DE5D5CA5E67B4606136D100EBC3CC9D82E19D7DA9B21E11AD0BA4A4996730E6BF67AC2D573BFB80B36EC79560C7B13D49A60D3F7C27ED01EF616D39E8C37297499702BAA5B2A3FE4581C6FA5EEF39BD7EDA64E832700A173F1AB26806E27B28D1BD68C897C051CEF31E0907B937434B9E5F110065183379F399B8FC4C91CA07A393C6D16ED4407C20ED60EEB2C313BF26813D0614442D2B91A340189576405345E6687F6CEC03FBA157C8229B451F6CD4DC8BB7D10EDB8AE5611A1310F612FF948CBB71F0A72947A875A125898E12228C6463D6799EF138D96769C603BE7DB0CCB94F7F191BC89AAA55F4A631D0C03B3B94B84720DF0E095D001B44D27CF67D5372BC487567975B3028E0DF71406EE7D3E4A011FEC5FEC5FCCA36A51DBF777BA8271B51C9A1F15DC3F190243B55C406F421FA35C9BF2A5CAD77E57B8F846A97BB1824288F96FABC3094DB8C537D5A3FDE1F6E15C3EC1A47764B92ED9C18D77F94D95663497327B879D3C52BA8DCACF82CFA77E68E39FDFE6980D1E0800B3E13A900BD86C88E48F3643DC8731C09F90D77881A37754F956B8CFED4B9DC50072139F72FA5E067E4872057DBFCEAC992165FA90214A9B228E995E43D51B22D232CBA2CE22D828CFBBC59A7552B289994FC2888B8C645BFAC9C3BB8B49E944EA2F48AC18AF1056933581C693794D68C1AB395AEEDF366003AFDB2E446A1CDD77778FFF2640C77526426A39D56CD38177D96272FF55A577B36004A237C402B3B1736FF262796DE083CB98CEA21D71CEEB57A9226CE6B21A27D4682861185B9E7C346CF56954110C1C0F0ED73684DD86A4D8F0EA27FEAEAEED3F5D6ADE5019B78D5966CB633D830A3C1CDF7AD63AAA0077BC2E1B7C8E6E294EE58C1333F6CFB2C54A43B531178177F747F54F09D26634445C6D912511F266E9A62EC8B460C414FEDDC9AE804FDFFA94572A2DB1392F6B257A412D2E55A7D2344C1FA787BE12918751669A6E81F4E2129C6E4A86EE501864317C28952800E32906EF4E76108D32D870214F1E0BBCBA60C73409CE0755A022B47422F31D6C050056294D7E58B50B805186AE1C6A97FA5BC5700BD6CA978B9B9A29467A09BE8D43B341E57F6EB0329882822791CCBD70D757A7C3CD6449BFA228C419B31119CE18038952734379446FD56F4828E3B43DCA8B685AB7558C313B8AB5CE08EC6241D80EA1C789DAD92577E1A1DA6CE17D328C61FD154926698634CE58841838B3DC53A0FE3B8123673EE6253B82EEF14ABE045DC395C1EBF03E631B84BD07FC8FF87A5A9895E33971EE26F4FC8A358A71DEC63BB425CC325DB6ABC7E1F17C9BE19920FEDBAC8CC7018DC5D23BC0A3C6156B4F2FC74290820C88FFD59CDF0824EF92057E1231AE4F14664067FBDE2780BCCE80FF06714B9B25F9C857E60468ED6E509D1B3041ED65815F98F76956C1903E56E23DE88E12F7CC662A737751A6E337DC978D4C5BD828832E8ED121FB2D658283BEE4642B4FE585249441E0C07E85119F87A862470C3F73A1B5E8545F4B9CEEB0648B6A128CCB2ED561EEF8293B2592F873A7CB3785E1BFEAE50B3C00C95F4967E7859934AB646F424A38E3B573B0687282C66E36AE50A2CE62FC77EFF20E97496C1AD14CE8858FE4664F9856380F9A269AE7B644034D42BFD98DEA558F4F4A6E7BFEEEE1F13954A4A28CED9F3107F5AAED389C533DCF913E4E862FB2E5345012CFAE505B2170E3F4C341E1504800A9C7D2EF7FFBD88E7EC55B523E9344E217DD181A238DAE98CC02CC04D4413D06559610BA19FFF4209120B8C86C050DFE6830B0EC5DAB7DEB04B335A9B6D8EE357804A433CA56D7DFE14AA0BA47277CE14A5C5B7404D7277910E154179A7558A6182A65AC30C696C226D385149816B1C8662909A71B73A3FA735AF2AC407A490612D89EFE026CBAB7E974A45F7828BDE99368AB3C166DDC9C3AF6930B9261EB2034800D289F56A3C7105B39EF7DA5452928C361737D2F753D20C79DBBA42006C1A5DCF988653568392A7F59C6CD76612E65A7943F65DB5D57D0C9FA606E047172C7EC4695C930722FAFFD7391B38934697755AA81BFF0BBEAB5C191713319C2BA3637C98A351B32212F1252438978E770A6AD66F1591E24A6651259283B660A1AA876CAB612D18DE2F23A544CFA60A58DA3BEFD25ED5271BC2037912145680733E87A75BC340199BF5A376C697823DA651E6A7EA6057BF9DB7D8ECB6C00AF60DBCE4F77CE92FD9ADB841178F76F1C62861CCFE1EA35A2280D10524AE0D40559D3768491217AE6D1A01110D5D60046ACBC14BAA09740B0D5539358D2F1FA50090FB51E60A13E8496ACD02258BA6F9412C769AA2B48453B20F4631665A1CC0257757D9AFC11F41FBAF0480D3496A70FE5F095A79612C654BD093A43DCCB931EAA0223012A41F0E1CDFF4646618C6E0DD261669CD9775B7A880E5B4D46F5D0B5744DD957A9C2A2CE038FB1ABC898591B9862CBBF9F3C832394D0CEDA547EF490E7BFD243F5D97B74814164ECB951BF5121B30E480789F4E4F6C97C071185DD1FE8440AD7841DAC4EF9722E55AC623125A46CB478D6E5F72F5962DEDDF3CF47279C8DBAF79E1FB6AEEC16443F043F3C8B50EFF895A9A4AEA56627A8A91A1649FF3D52ABB13CCC8E22E7E636E0F7A13B464968D6D30FFD3380EAD5629ED912D448ADB281B6A07F50C04D9BB63A75AF7B7CFCA94513B15CDB22DBE5BC84F1F18B09EAA2E01198101CCB91A72D5BC2B64AE54CD6B7C324E60FE301C9B6D86AA573DD722309CB80D73DEF9B5F03D028E175BAF1417FBAEA349FD94962AD53624FFF29296E8480D1B60A58B4DADB1A6C86EB523B89D21C7A3EA60E12097B9FEEAE85225E311886D0946A2D6E6C2DA26DA4A04AC7774F35E6B11E7039D3E762657BD78519F23C2B2DF4006A991C20E89B78020384CB3F1CC57CDC0FB26C4E8954CC4DA494FE9D2E6A53C5E91E26E152B8D37406A55D2EB8220E8DC6A7F320809791D0A4BA53B5F8E73BE44EE0630F582E1FBCA42ADFC971BAD94E8CB10C74C857630D7CE2EE9869AFC01D5855C78D4E7FB6636016F7891CBACF48132D599AD67BCFC24144F8FD80D622B8F7B6E8204D9FE46C6A3F384E44FF8C1B39372BBC3CD6C2903DE05BE6103FE4262E0D75F0B7236877978CB191BD903691700AD90B65C6EAD615E11DB05AA4FA7AC34BF7BE30E50446DAE49DB3257E982EDB3EFFFF0710FC813585FC43E543ABE4D6D3ABAE6AA0CA1FA2DA6991A796D1D07FC1AAB486E4184872844B09C8E2111412EE54EF6CEDC491D2C68AB33D5197022C0A5D0CD66B2D72C111AA47404F0D23215AEE463253FD8AB8B58832BA0BBFD9D685C31A029495AB29B257C8502932A98645C3724A02E9F6749CC735B60E8823FF125844CE4A01741163212AA50C0158458B8C6EE1677E2D930BDBCC9059F096FF681593E8A3BF9BE3B2A176DE08325B661972F9AFE653190D23D2AED054A056505802E8D06D0501665CEE668ABC52F388201F1C857A13C17EA597B22E62A0B7980FA60F0981447618D3EC6B01E85EF621E8F6619EA1A3BC3FB52C5FB4A700F4A639F2FE797BBC777FC7AF9824569550EDE7B231E13394F71576CCF326834728DE30D71EB5DB216C38EC57235DEFC9B8C8664680AB7C94BD1292EF3365958302A70FCC9396AB63349EB9D27DA3BFBCF62455912846C64E04F7CB51FC3E1251B67EACB7ACDC64F6D7AC171EC7B3BB4BE8BC2207AB3456B619918AE465642D184C5B3955573AE1FC6A74E1977701DE2A45BAC5DEFE80037CFF45C2EE2E86AF4200FAB86C4F9A9E86EC96B5FC895B56277249033DF2FC8D95460845DAA56E036D9E1767FDCE37366A68DBB7BB8770C5D08D9E6C7A3391E5B137E2D9DD4D5ADD65E04DDE1E025EF5E4ECA4EBA81023D590762EE4A0D43C813AC29A9DE441279B48CB26EC1AD0153534A50DB00D2732935CE3E857622DDEC76ACB3F8919655727C8FF8AB4A34A90C665E138FB2CBBC4E7C3DCD6EEBAF2DF66D3560D427D89BE0D5C85BD947141FEB0C34B39D57665A47D31A1B650F4FCCF1D41E6C356F5B1F0FA8B1262772C4B48A9CF32B58E11CF3E8E587615733C04BB2569A6E5CF3D880D8E291BEFA79D1C3240AAAF71756A1BEE98A1BD0DDDEAD4E7BD7640527E2CE4C70C110D57B1DA314DE778AFB89E8A34E9A2AFABA7D8DA1A8A59E852C639D5BD5BC1407362D21C1CD5E99F706CC0C5CC8C582579588AF1D3A173AF1595950EEF8B228BCB76D75DD192005F61AE45213E95B5C9CD22CD49CB67DE9180F0DF6BB5A7F32396248F591ED2FE2EC6E9121C3792E903666DDB175CD6FCCC0E522E09515C7DC29A672E2F5897676724534ACF9520E669E41906C393A93C8A4382E9A62FD0F370D61A70110A4D33C92827850946B39C989C2791EA7AE0BD989E1F0A7D3B94B2DF3D8E4473300301C38E8D70ADC6565A46FF0F029D77BF4E43BD4DDDAECA8BAD227702DF8FF8FBDACB78F7C8A2F6C0757A338186A51E294CB6699AAAEA4B403A53014F76767C5EB2789A1A6091DA4289FD4D14A815C27DCEF551BBB4D8BA878138A1512E92F8C9D98DB8F1E36DED66E7C8CFE9A7D9B6D0F8BBC4E930D0877DBEB0EBB8AFC18ED34AF35C22F8F84B3097F5FB0D2BBF8DB78A81B0F86524B9C9C5EC2C98BCB2A14FBFB6410E104A1DEB0E94608254FF34C4F7E1B6DE597582FDCAC60A6FC6C0973A85103E795CF4EFE75FDFA7502FC8C188136FC1FE6DCB5B63F128F098A5C16ED81EF590DB67D7AA2EC55777CF28FF0FDBC7E0237A2BDEEBD1F918D84475D88174B07BA826663EC6450171529CE828ED311846560450D2A9D40C3ECCCFB7AAF7C129356856E0A166B526508FD404C2CE1D20CD62F0F3E4365F57F5B1CB8AD6A221AE7E4CB3324040A1A85B62E3EAF0A74157657B4F2A16493104DB2704E357CEDC1E4215F39C2CA3079FF5896EA7D09B905B34DC349DBC980C0D0BA4BED1A399F042B004BE2C7D856D0EA3B210A3C22AABA0DD6B3CDE33B828EE09D5E72EDFEB6433A95E720DD546F456EA2A1D507B791C95A55F10FDE18A9ADF60D28F7744D65374BEDDBB4DE555CF5DE4F86016DC098EB80FF1E7BC589D06D01821883FC1D1740739B9D95B71AB145FBC69E89FF3E9C69293F8E82B1CDCC3C81D81BCAF2C76DAA70BF2060A56D5F2CE67E8430BECF2718537C1F8C3249A4575A9B4C21FF7A22B144F60600C59B54E0425D85DE17AD663BE90F6F8C3D35C92340EDCA188062970FA68C1088EF351F0637A38092D2D382AE2EF495E905835B63F5B185E8833D5846D8F2292DCAB08EEBFF194824B7D6E6DBF070F377F75CEA846F6AAF069D6686CF4FB208AF095B65DD754169C3B4CD5BF645CB55DC88215DF999C5793422C3D4DEEF81B73B6F0CA6CB93309A54ABA97A495D199DC7756FAE35E7485F5D034D375DE79D59F24E3A2876D0297CD358A85D876074D019E7323BFEA3B06F3C0D47823C19717BB0284F6767493D2A648A2C469BF02093BD737BF5689796F79EE49F440759C43F6B71F5F8F2BD18C85A2C844DEE11FDA86E627C2A47E696B2A95A7E0F47A328B55D2286E30C95A067562CA6D7C6EC8A9BA69EFC76A6C0F77BDF3D29446F32611E0E0A727411515B1688491C2E09C7DC6560AC7E3E9FF1D3387EC749206AEDB0DC2E03C3AD114DCEB8E90C901F7CE46F9EA94392094DCDF642CAE5D005D1D678C802271B6E1731D26008A8D420FB390C3448F213E22FE8ACEF2FD2D42A49D9C7D5D5478906A5670FB0A66BA00BE5EB80D2709D59A60551D13B28F945C3259A5EA874A64D86EF2937C8326CAC6205D95285B09F675529ECD0BBA49DBD68ADF5F210AB17C9D3DBB2560E713083D4222C816BE20F29D57023D76EDF6B1713779875FD006AA9FFCB593C78D9622C975B554CB10FAF01BECD5957AEFC8D947E1B9E64DC2B0FF864F78005EAC55A000A501518182E56C7C5773B5A856ABC62E0BC1AB060CFED603E315B8D62D9BDEAAD837C4EB0F38055531242D3C5532336AE2504D17EB7DF99179B79B2EFDDB1560DBDB6F0077CCD01492FE214E77F9B6ADF836426A32C4F3E03722168AF5E18BB4B1B1887A3C073820A28C8257D7484D224D8F729457D550954B0D9EF9AF4BE194789F769147484DA790BCF59AE40537E7FB5BF2A8CE60C891ECF399A828DD504B48A00B52B692FB02EDF033DEA31A96F57BEA8A70431238CF725B897E5EB2986707B70DF74347280BE421FD24732F6C3B0A137A623F55AA43A27301DBE1EF38927A703038E27EFA25D7CD506F814A6EC2954C7FAA256B33750DE65388ECB54DFC58CA6AA681C8609E034DE513768CBDFF3093B9B1DF78B76B84DC8DEDED02AA88D029D9A0F213E0358CAE6198555E8DF795F7D6CF5BE1C5AD9D815D6ED2784B8266F409187719F21101D53E4F6CF7CEAD7575B6A631486B8312EA0E02CBB8E833E72FCCEF6A815AA0E7D0691031858F019BA5720C180AD8596881ADFE38AC044B41FE9E09CB323AD88DDE1D749706364F47832F2E17249103DE309DCB66E0BB9E66F7BB6C1D60FC62D075C3C95F56EA5394D50C4AA5D0F9FDBD5E3544B7CF14DBD0BA7EFC30F199F17A5E220D997188907961A80FC60F5F9D01773C89ED01BB19674F8E5C65BBD95C8ADAAAF1583824957BD9DFBFE4DA61C4B04B7E2998D8AC5654F30EC11B6CF25E5454E75C049D86611C1C6CFA60DDD673A0B5693BFAB577F305967A0D2B78F45AC9C9AB85FB5D3AC5986411116BF504C25C63F10E628640431EB1262B72A6FD4035B68DEF1DE4C3E24D2387D34554220374599541CDCE50B7E3789B5A4CC4D58B7B943FF79A7D4FF9193C53144FA7A24A97A88427F18AAB2D47F37A8080B146F3F31766FF645AD209CAD24EE574DD697C3461A17B31525CEFC16F9A110E188C7839C7C75C696557A794C5BD57917B0796684BA4D16975422B1F95438F2C1D4AD9D408918E768D3786082B7EEED2BFBD9F3149E21C1FCE6EE25BB3F32961F5C085C0C751B020C588C3F68983FABB7529585C9AAF3F8165A5D7C92B89F7B062C06A562A06D80BAD1888AA8B14D673382DA6855991D550317BD85DF5CCDBAA60BA6111147A6B69626157A096E4B177B7836E3A3578DA3229E17D61226195A1DBF831A332E6698BB898A6C1B1A608F8BD9F1714BD64FA3CB82EAFBA9BE2F143D942ECC5420ED9DBF6A504D0449F08AA40DD6EAA7DF80AF6720787B1EEFB16981E28614374D67707358535C9233279BAABAD1C53F788947FD7C1A75AF92010D365628EB49C89CE9038A62A5B6BF9E916E462360C47E4BD198BF662EEC775E04723BA5BA916506EB97A385AEC5F2775E5B87E936290925619BC94905B0E0A18248056839236E5A7715287EA5733F9B06D49DF2BFB86DB5F389478DEABA33B7A369904C1244C016A7767245BBB42F476FEBA755AAFAF6951BC57EE10CAB4162CC00D4AB8E7EBC4526C9997294319D41FB2BB6E968F608514AB9DE937D79ED51FAA610AC98A67EFCEA912C6A9E7935D34EE09EF2DF7C1FCB1D7EE22AC5AED56432F2CB9AE9CD47EDF1C40947075D41C921D25E027D5E91F263C12306247D3387E055DDE9166D2871B27E794DE746A4F5A7A419D6066DC7BFC277B0325EBD5D95E2779D4E8DC8EC5CF0BCA87256B9DD4C20DEB9D50E5D653DE13D3746F84EE586B8A6EFA8693F27C7CB42505A3CA4D91B87CA340EA7E8E6B80BB0FCBF79494D5299447CB176CE7404EBA9C7F5189BFA4C087CFA77BB2FA847840053718D575D8128834CEC06ED542B21AF82F95A7F7B7D5861E1BCC401BC9D7F1ADCE414F4F158FD401AF842348A0B2BE844C6A299735F3352A520E2899413780D27D30B4EBE8F9D2D16D96995F7BB52E72D207159889978762336846C186253C4DA389DDC4D2B957B2ECBAD5BC6B28D7A1E7AD2BF14E43213143B063473193E0CC41194741A5C42EF2A3B89100DF0BFCF93CC4C6615C85A59BA517617B3CDEC693419CB8BCE07C9BD74FC916168442A01F1327695EEDF0C4322E29D01B743F56A42C260ADF0D77DB8B4FDCF9E701B6FE952DD5CEA9DD680B7BA68762B42582B6C5349C6DC1095F87426CCB29416D42E109055DB3943748389F42B7E96DB09FE5AE403AB0AE54B26FC676D47F8C1B4DB82BD3B9AE59C112F03E5E9ACACF4EDF64C0C2A87203F8BA9412833762A15AF3EE9F25ECAF0AD7B72607B4C970D20CE5D41CD3263C44374ADF341BA3D9D155670951967AA0476503723C986A8E4C0DF8ABE867454BBFA2F32D340C45789257486C3816827D270493784A7CD3C8C3C4FB78BDEF0040E50D314FC1CB9DA305D91848BBF4CC5E5A7C6C3C020EC6A47628F54D987E643AC4569409F007B70E283437BC55DA18DE0C82BC8DD8D95E1789C168892B3587DC6CC94C8BDD88A7A187D1ACD12A6B133A32C0F28B354019C4A545085E44B66B6942552EE27236EDC028A09E3BCF2438D2824EB568A1301250CBC01583FED75247DC7931DAC15994E5186F3AAEFA0DC10686911EA839BFC5044499C930526B7F1252D8CB9E292FD304E02924F430AFF6F1D30F69A3DABEC32CB9A8D3651DDC9AD31C87A88A0E8726373EF456B956DD8A10FB582DFB135EC4C1007726D945F149F82C885FF92C42FAAE3F18EF7C7AF0B46292A32FF740500617A909FE1BE3822A0F5267FBA281B680491FAFB2694970208C7B7F325703B28042ABEEEA566B18CD3EBD262F086860FE047D48FAB6A41F4D1B2A950706D24F76BF4B454F6C1DEF630A049AE9D952EA3339A36F525A434DABC049FEAE502F1C4454F8ADFE3BDBD0257D4237FEDAD1B692DC662EF2FCC06623D9C3A5E93945CEB307B359FD1413A1BE9F230D6389AFB44CDE083232B3F0FDBD4E60026FD8CF0407BDF899C23652AD49A13551F2506EF0CA771954842B7BA9958E7E5B05E101E59FEC509502617B6E22C657F2FEDE1A3B49C4D0DCA567BED82F1927A1E7E022521BD71D5C7E3006BFA888521BD1C5AE3EA4AAD31AEC405FAAA489BAAD3782B0B9C106A9D0E7A1ED1462BF0C86120FF3C00FC369F6B98463ED911C075DBB3600B1C5384844A7EB306E4675AA5FD1B579E02BAD83B53CFE524EC1DF39BF173BFFC090FF821FACE1656A03DFC43DF2A37535798A4479B506F2DAF61D1696475F012B2388B1D9AC1A5F1F5015D007699047F32EF987F1393F388D476FD31B8206269AF07E812FF147BB0C6B99B8FFB550C1CBFF974CB5959689D3DB5F7FAE9E0749D542F341267930725FAA460D5E6A8419D84AAC17A53909BA147775CF3BBD1FDA2F3727A4DC782867642321F25E6CCCAAB24B3A4F3F434FB15B01CF8B7534558734762BF60DEB3E453D681D67F22AC0D5CCDB1110C978DE4F98DE65C9EDF6501292E7493CBC14C0C1F314CD2DB0B869D24F7F7D312824E6A745376ABDB4FFEFCC9CB0ED90C286B96E025FB9A72F4D949E3FCE3AF604F1BFD7CE4B48D9B798DB9F69AB8A8C24A50988EA37C53C3D545794CF14D27BF37D2DBD2D42ED918E0E35291A90278053606F55AC971896715F76AA085AB3B948648C87D39698A5451BA15A5D7A4FB2781F6481B9E99B48F5F0D737197997B97061D0833847DBC05AE4CCA94A2106960DCEEEA5974DC87D9328896EBB792F17DA7CF6CEACA561F64A00FD11186AE611D014BBF08FF0E9CE3B44E132CEBB9A9ACDDBD77D3E49F442A5C7400C7125D3AD9A5ADF1735CEDB07B3FE8F9A0562CB52572FCBEF6AB0C64C488BF627EE4499039C9B783F8C4677A478FAE893EA6E0F7BF791513148287DBBAEC9D746F53D0BEC1931AC7AD2F0526FAAE3ACE4A1C35D45A4B22ED23EBC8AA3F5859978B5BEB321BEF6A29BA5DF4F81368858C8FF5043560739D62ACEA6DEA7A96638C08489B431EFD4CF193629C284177BC94C14478B874F0DCA48F450FB3880974DDF7FB170EA28C53281E708315163C07A06761411F6C7AFE6A3B07DAA9986ED961FF91A533FD5915EAE1A5D998A2C606ADC85F4508203A0CE8930CFC3697A6003170517853828A2B9F26244ECABD7F136BF121418330E8C3220CC4EF7034032449445B8D3E5FB25E3D699FAE432D7C9D4565E9E1D116C8D8F61D4C0717BEF6A439B42088DD6BC9A984863256DBBCA514ED8C50013975C7884B8CD389E547029F24F971F7FA8D5503057586C1AB215FDEE5148801C833B0DE80440C74DF78014E749A0EFED75B2647CD0D3D06C8257D49F065A7F8E48D9BA22A244CC8D7643EFE0EBF0E68628E89B6156198550E62B914911266E6529C8A8378ED6DE660A79FF08F1D9DFCD62846F8293288D55B52E0CA0932705606D8CA541779D3120921C9C2BE083CD7FBDC8FE72B756EE31023960FB8BE08A3D7B23F401845B053AC8D1511DEF4C4FBE060A45DA538195A524FBB01F8BA26DC94B314090868BF33A05D81220EC924A73A50D05CD2179401B6DCA759FEAB5BEBE86DBEBE9574CECE044F253416BEAAF7E7FC60B53C1651665C449D47CA5196C9B875CF1F97219F2FD7CDECFAF4A5AE9F7FE74FB36CC963480E16442540718419D951A7A65608EC60834C04138148251CBF417EE05BE2E2C7505D0874D1809FC9A5B614A8263F6513388F765E91DC263927AA68F48A19119D86CEB0E5EE42EB124A80A32284F56FEDCA437E3A6C0BEB3AFCA5A9FAB089F4B0A914699600B841C20DA1C99432BF07EA5E67A75F66E600FA3875DDA3025899F93DBE44E63A8772445729C0524D2CD430297A1B1B1DA575F34246ED5FF336E50C2CC7FA074984010D81D1541562F5EFB46373069F1274AE955AE12117AC1C5E61599C08B026A9238B483AA1CA6CE2A776B651DDBCC99400B7D45C462557FB5F722307E991CF9428E9D49F093E3FC2559FC9ED332B667E47F26EDBED1544E1BFF54CB815809FE4DFC41106DEBD7B65EED5540C6D932BF27FD7FCEA7D7B55C87B3A6762E8E3EA98363DD1B72BDAC38E464CCCCE60E131523533F0D11238DA8F648E95FD1BAAE967D9103125A8F5A67158444037CB998C3CFE305E883A174154AC723F81B0B4549D5AE7AEAEDBB19F99A7E6B578B7D4E2B60758B0CC839E10C3626E0E1E0FC440D925231141213D922A188162906F40C6DB1F44978421A88B575254BFA0990C05934340B24930E3F9DE769620BA322D94FEE3F12A6FB4BD5EA2FB46146204552006960493167A455F47DA7E8A75540CBD6B6C6AA01964BBD06129478D247EB29C19B7F518E0E7E6BEB1E84F2E0A0BABB823D035404F7FDEB5AE2DB659B8ACB1B87A5EE25F8F9AC3873F093A8E5C6DC9B64B573900E4D277D5A682654D266E9C6A3B65CD6706B5831F4A3904FA345405E2D81D98468497B055816632E0497EC3DAC77207700DE88A65168559692F0281CB1176F4E66B42FDCE500095E9214B20A4F596C6995C9848E75572487AF02333EA93640263304102CCECBA7BD64DBD8D3F66898FF61C4F2A76082F235093FE06B0B598004E019FCA3D9594DED417D92E1EF9E6FB82931B5D095C6A084ABEF056DA5261DC758C174D15A077D393F2ACA2FF90806EDD6099520ED5B408B85233581CD01E325A2566F173C418C3E05B6A41B6C1F5A1C6F3E74F60C7318E302942B90563EB8728A5F942D79EBC636F041F6D35321CF3C2CA940600EFE7556FF3B196C1AE7E3020B049CBDB156D191CD13B1BFF6CED7578820FBB6B30F16BC089E9137D0B883C6427746D557EF9CEA04EAC438182E27DBE30D888D98B9484D6B30990F4FE38091E988B05227BCAA947D5224003910634CF9DD73E238085CA5E9BC94D936CCB59A4B3C4466D5E13BB0D3987FB8A056019B426024F84B5D6B264E564BD3CBDD613DDF7B2A700F9FAFD7848B29DE69C7CFF6002BE90B2A751F1FC70D8B78BB3C03436C4B304382F7CAC5CD87E72968D07CB240FD42EA9E007AAA5CB06F3D329180F10E4908C8D2B3215D7E85B18030F7D6938DD1F5D002318CB9F7EBFD2C606CED239F8CDCF36BDA8769608DAF913F0C98946EB0CF8C2C01B0B8EC8746D1EE52758AB7D616A2DB405B6ABCE827D7B57AD3691DDDCA71915D9050326AFBBCC9C472A47A3F2F68872146FA2FEC4712B8EE017D5C8EE802057311D288F3E81AF12E593A69DAD336859020FFD96A6BBB64E95B1FDC10935E034E50B039E07B9C6D11B28571995061BCB0500A0295E662E250327476FC24D7E39CEC1033F4E61D9C02F2EDDD3F19163246F9BFF402B4BC8474D9F6FF65C2FC6D673829209446B9941A55722163053E23348680C53124F1FE600F2163AEF4C14562020E1C100FFA6664611A176F8FBF77067AEA57AC0B860F17A1C9939DAD3E2B4F6EF0F6EE38245184AFA9E56F01D3DD32E985CC73D30125A251DE6473D14126D64A6A7535B264B5F7FC98838C0C94F883F57989CF42F2B1539F932C686ABD233ED39FD97ADE25427F184AF8B32957E36F8BBACA6C50ECA175A7993911B3AB9C83622F4F3D0DEFA23A6680DEE46E4E8C71CA9DDE902DF00C2CA23710D86DAA62E58A586A3D6DE8FF7496AECC435CDEBF0E6E410D25867069606419FDAF2A8D685033B83B1030477AAEF74F54074597E74821CC26EF69766F6941818E23B65BEB06A6A525B9372AC31FEB112C7068077942400C9EF7C1AC0AD946AC6BE4832C21AE102A7B0F2CC83EC677545304441F7AAF53170B74494F06FD5D7987EFEA3944C094997F21DE0378835B3C47B3DD057D110FD33BF026AF20F4CBB7F1BCDE14F0547CBC212168C0E1072DF3C5B4AD5E0A4BE0C43323243C103FA91B492FEE19E19248BE63A4F7961ACE654F0836BDD9BE2E8774F2629F41D36C7C68BB85161EE7E5A5430297B0F2FC836A06387784D4C85C27FA9D1276A96102E05695102BBD1384BF4BB7B00D76284221667EF55B6545C9BF453C3B61FBD67158BA5F1EAC505C9FACC4EB7244D88FE353D165F04E16C9B3C6F9B5590D6548EC5B3AF965AC5A78556475F63785A397541DD5F11AC24B70053EBAD4A44D91870B9D0EF8576A82B3C9E19ABB056E76E066703B6D76B23E2DB1D0E392CCBA8FF303D650E83B66C8E5E2EA8EED5039CA4A84B5C887C100DD16A10B1D6609CA91CBF30AE75E99E3806725BCE2EE209057BBDD5F4A141C3BE0AE525031FE7FD853374D77B08B6624DD79581654DDEB177F1098282A50E404894A330A333E3617AF9DE8D5E113FFB2F9C10D62699BD4CD78DA8ACCAB03C0FA87473E8DB1DC51BDE2C07B1D530AE17C6EFEEB8AAEC93AD2C97646C532E4FBA116543D18EDD1A1287262A68BFC307F768A2B99CA0502A8DED4FF7ABBCCEFD88DD3150C7E837FEF833F4BCCDF4CEFA6E9CF5DE8F1D00477FCCE80C0618677E3BC12BCB7218A7EEDA3ABD04A8B0B58532D35983738815D346876E97E80CE1ECC4393616689B60880858A654FA3D67217CA5B834952772AE9277377769F707A33E7E6FEA91575C401B9F880566151970DB0B76A3A72556B272EC2A8459BC246F9AF8A41B39778EDC5188F677CFF17A32224561FEAA6084431A93B98BBFB27540F8A45D639664536F1054CF07DCA530C78DF4A12944E8F833490450F6B1B280AFD4BEF16C57F9FB14F8E38FD42D76774CCE728CC4D37296012A78E18B4ABB9673B5ED35830C6313203044361E37FE41189E5613300C23BAE7C22383A1E38CDF6CACA8DB4FF43AB35F20D056C14E0CCEAE0C000CD2FBF8D3FB9410D469B1C82DCF246E5669F3DC29969ADCA7E6005840F21917F031144D8A92E38963ACEB60560597B9DA91F8771E5DE27A4C3098697581DF84CF9CB23A50EF9246074F189007855E71416F34BEA49B640E2ABFE2281ECF7A05E04A1B28D12D943B01B96F93E1CA9A32C10DBB72C3E6FB584F75574C3205DFCFD200826AFCD966E5D0688DF78A31A64CC59861FDAE46518C67917EFF0B8C473B6D0E3871626D1BD424716F1768347B26775BA26AAC766967670E102C423F4F1DB73D948F6A6DABCD53A053A6A195354E7DF16075EC633ACB15F48B3F36D4715EF92A7ADEB4A788593649657C6DD46CA381E595A27D0FD8195862A4F89E453AFDC3E464B423C16D48A5F6BC8ABB83EF8B665986578AE17CB2A2C13C8D4B579723AFD39280AC0D5E4322F7D83C9D0FE1C24A5A0188161E1FBDAC570EF7769FE9EC76AEF991794A20E4BCE13959424F06EF3ACC16EBF0820AB33B6D9071D6A835EB767C9026D6147D49C479287906264E81B6E8DE6C795923E99E16D3913E0346D6C3DD141642C1113C64E6D502142E9D92331EBE67F9B23809F7469BAF1633482DE70D9AFABEEC0EDFDC78B27605A2D5A3A531D7EABADA67BBA1E73AB8383B1FD688D5187B879F2C3AF214721D9E44CDB591FB2DB0A335ACD85FDEF18A8E6EC8BECF5EE7698981F592FA1216A102FE9C9DC393ACAC8FB8973620F4B6B0E644CA6E6BAF27E47313BDB3477AA9631635D79D2DD15037F27BE847F8D673DEC4C4D8CFF138237F49B70EA37A628510711538E60574207F886073E4194D2EE4C5BD6B90B7CA964D5FFE2CC7D2B9042BA83810BC806093F3CBDBD5458C71650FDCF8E89CAFED24B3A88C2EAB74EC4D885376EE0472E4D7C5229562398EF0785E6EAC1F9047667DCA6FA7164328357E9AA021D334CA53A93C1895105F42DAECDF2F91FA665D821679D22BD2AB8116BCB7575C5BCB4F6341B54262992C1A3B97E0F6700C2D44C54CE91FDA75D74D0AA2B7D260C7DE6D233040439C46E509793B3BA4CB6851FB5EFC16F14B10E08DA91E48032D829F00C7BC368B2DA73CC7EE59CAA57FE48F05075ADFF53967AA6FECCA7603AF7E8D500A09BBE4612CE4C022DE03A93274A715ADEBE5B7F11B938FF5C53EC48DA8ACC686C4B78502221CC066E1459D297793465C59126E40A91BE89577B24D3F0FEC4622915401B653700504FC4156440C531B21EB66B64835047FA563644DA1707E0545F9ADE621E37F26DFFC61205F48C1654BE3412C67064DB3E0184C94E44FA06031BD1B209DAF703078DC9A518EF8E0815262A09908B32BC52B1655A4DB4C4870A7F1AB03D13DA5B89A6A0E407035337C16D41270BDA1DC65410EF892BC8852ECF17EF90FEB3E1FC07DDA6CACADEE4029B318F438C6A8DCDF3589BACDB39F97652915D94DF0AAF8066765F843FE716DB323FCB8729F6DA5BFA1B73B24E363B71148DDB571D505718EDEE10037561E5ED5111BD5FE8A0864E240E6EF752454D19838AE0BC9C06DF023367B7A135A524DB2C87A3DB60D0205D3DA793F54855E60DA9C44FF65BF23A6A60FAAFE1FD0AB39596670B3BE085616C120AD6846AACCEF3081AC92C7118F43A0BDB7C6A31D1E4930C8D6E4B2FA975637D36F4E7699A2021F7BE6DA4DAC99154AB99B1B56F36054F32B95307864B52B2719AA4157BFA79ACA0552356C9500DE2D2E07F81375C333D703B998D80A7CFBF72D40C9D4D68EA5349A7E465D46C62005544F683AD28A78FF9EE4C68283A9F5CF2DFF65CA5C31FB9CADB5579FB4C56355F080A55C3B6E000D7C4DC18925EF37EA56E7FF179CB0D0EB95AE7197CD4FC77454F1CCDE9C77ECD08377F4447942977F602962CD3ACF5F172E9FBC7AB7E785CF1033EA4F13BB65FCF67276FD9E2C511DCEBFECE19F5BC7FCDA2564EC54FF066E3A12D4B4ED765A981A207DC358BB5F2E0605E088276DFBECD3410529BB96CC2713585D5F78EAB3C00DB35E598E1B91278FF62F59E84CBF84747A09C7DAD757C2793E3C2E169A52530C28F5D573C230B8ED6F477A92304912FB86BFFF2758183C8554CD88065788482E871358B264902795BDE6832F8D8B11F9C68222817083ADFAE63CDE41F94A877981BB4F88216AECDD6E6ABCACB01A58DEEDFC837777E279DFEDD83EEE40164E81E4184D78B3889CAF6E143D61AA4DF3736182DC0CCBB6F9B34559BA86D5967A119635E61AB5155B96C536B5BA7D6641A1367C60B76A8C825E2CAE9D7ED5C7F37565944566A6E9E555E9A026EAC8071ECC09515638A5E0BFB55F5529509734DCF42784D1248207B2DD84BA8568DC532C2635CD690F25E00BA9282C86E21F08000A802261E0494ED260CF94D77E58E57CF4DF2C87FA3C0B68743A1A6594934E1A935FEB3A886F3EA97A06B67B901B4D9813D5A57B214D891D952BC321F0A82879C0B8E115C6C95A2C04C220E30725840F954897CA8F1864015A827147094913A989B9A14C6EE8BF398AB13C9BD7EC9A27A3A2AA21EE70BC97517069ADFB344C16F89BD5E764D1B4737DA28CC0C4637FB2FA098AD3984BA40A6D71C35CF52AFFB18CC41381B3C1A692FD703E6383E4575C1271C8817D695EFD7D395CEDF72477EAAB7F84C400AD9BBAE5DFAF80FE1F098B1D1122919744612766800BECFA5799FCC0A2CB142AC527A0BCE55103F5F4580E61497974A8C1A08CA90D5F2C16C7E72B8649ABDEDED2DE4A4756B4DCE06802E9A77F52840AA221BA2E1108348A9A86C7B7B854A9CA8B80C780A44C07E688A981CCED282FB9FE61D1FE4F1E272E5FB6C3FF35F07D229E8DB9B661AB9391B2D26D094344D57B25970ADDB794213F0367AF56346C11B5630E8793A985832EABDC09EAA2E819BBBCBCB0C9D157DB51AD61C797C1674E831CC190C37AA7B47A16E2D7FB3803DDF11A348B2AFF52301D2934224FD9F983D4D324023F8EECC23E99D062DD13D03FF6F2463B6436997BCD2986E2935290BEE40DA5E56C69CF262395EDD3EAC80A3C311D27142E917AF513D019882E9AFD63D7A4F25D75440709EAA1F4A1FCED5991A0301245A372780E194DD2C0ADBFE2527C9012CF964F384B191746F8074BD3477F609ED612392304F0A75A3924DCD54FEBEA49F7BCD000302297EC99504E52E0B4997B33A4AD54BAE52C6EDEE7726180E1C4E91050407495A3B22C2304BC4B6DE67BB9D861A391615961594ED76F0DB03888A9867C931647DD4765AF76A273FF52C5E284933279E3F8D799AA75533D2F2E1E922FDFA607950A6A8F24D0B823E8A8592FF0CE08C051BBCBA5B8E3D65823F1FE0E20C895533CB1D56F643DDACFBD40CD0C6295AF8FB4236B633720B9D790187147709ECE9452AFAFA7D9961427BCA96A71B75BC1A135791D863A1EF4DB8EBAA5740AEF6A22BD120F60DEABD9E6136EF227B788EDAF0A407601855CFFE76F530F844112258A17B837E0BF7104BB6217BEDC584DCF3B172D485DACBC22511C1AED92C76E696D6F7D15F02509E5AE5DA1632E6E44CE1500CFDF79F573471D1B03679116A29890EAF9745D639B98E000EEE96D240AF96DBC7029B588E6C7FC3129F3625651FCB84D4713FABF04FC98813DE20DF7CF2059CBDA8448E9C0CE5CD8318494693FA6EDB6C8F19FCB4797B56DA14071C4941A7A1A5F5344368C04A45BB0E3848E544FF684ED68FBFE0C440BB2AEE900F6B3163A0B478DDB862E44FDA2A4629911A0B93342CEF68A879D2DB084734E1AD7329C95DA13A15F864BAB918799CD59E2C862F8F040A5832D5235E3EB86CC995F9D24CAC34D5A23E106C1F5687706E1F77BF5F4D1D67AFB362854830613C73A4544D0433461A19E43F477793BFA9F3AFA37ECF951EDFB954C56311FEFB67F6DBE79B00B07E3FF54B9DDF4E2173109B5ABD3B51F1380C00A98DBB285B2D9D49811BA7F95793C9FBE2231AA2D1AF3D14FC0CC1DBBFED132A5C0E34C8815B6D37099181B56576A4A74D6F2ED001A14EFF1ED9D65D8C94A433A717A7B9B09874B66D0FF0FDEDE9A0D081994B747FF569047FC48BE98AF41204DC3487181C49AB4E335661C10634D0E9D9FC368BCF74A4FE28B6E63AEC9C38608269946DBDD72EB47928034A738F5A208DDB3DFD816A2F55FC78EAB8B4F078748D1D5E224FCA3C9715685668DC70BAED5130E0576BEF94F6017E8E6C844E2F8302268A4E6B224F232E14DC0705B22B1864D970E9255F60C4F15FC720743D3BDF1F9C357998DFD9A4231CBFB3342D5C002ACF95DB4FCC54F591E79FE3FB0439D6B10F072DE19C6D0E368F8BD2200C6DD4CCF51F3F10FEC54895364E7B67D44F64EB0CE5265764B83A1FB8B286244687FF18809FF2C16D61BDD88C520F531B04A9CC18897A14FDB631C0B14D05F36D749743FE07F8705CC276F6675DE29790D9F01B88B61A8A6B6B457643628F30B4A972BB0120B7397A3C21C5C2DF7C75B9FE46ABBD1BF9B961AC5D2EDFBDF597284E53BD8E95CDEC33AAA22D5120F894993C4FCCB5BE0C30C077483ED46623B7A7C9DA7B893805E466C97FD52133144CB9FC22BFA3B310468972805B0B15B689A92524EE6FC084EF29D15D684BA96596E4C6B572F9E47C083FE5558E96A6F9C84C4C5A1BB3E901E3759E431F7DA3B2E99E40674E00F805D30EDE3BB21023BD854BBBD0A4949FE1457D2A2AACE77D74EC258D8B6E7CF1D44DE3E4D2A30488A96206637A9E17E89420A65145631CC79C6B1D87E1508E834F86BA7D7E65959E13C886B0B8B4EB4D6C606522B34CB04DE217477C03D6F211D14648BD1929FA836F4BDC6EF170ED564FD10E1905B432FC8DE1F16A060E2C4A0BD1A47EA3A1AD141C3AC24D66B6D1116AA908A0AEFB4D43DB04573652D38E2055C011E96E602F2B124AE3714024B3FA57F14AAC096F9D20E6AF959281F005F6DAF1FD30F25587F578AE846811C9E8A5DB3FC898BC286E8E0826CA2C16776C2C453176EC2655114B1C5BB23CEE4D06543BF7C9A0BCACF8815CD63C62D6F550029EBDD2CE31004F1F9CD79B4EB3D1DA38B6041F63441FF8BB0A8AFE096C6A15657B85C92B610FD7729FEEC449E25D5C86C05CF2B257A775205CAC37485D255BE71AE21612B3E8923FCE43D23550B42675DD7D86CDD1C743A2EA4E4B45EDF101B2CBE17B32C5CAFEC733CEA102BC2495A98DCDEAA538C969850FAA540526FD150A7261004F640116BF1F448DF1AAF0ACECC99026FD194B70311DF22982FB776046C460FF72B5A081806A5CA988E9A78EA01F2A69B0F21A91C95BF64842D4B32ACB5D4081D36E2ED1941E8FA113E9970984A2C0FF50CF70064B4E93F403EE2D57ECB2AAB1E239A3B4925D8446D38FF19855D4EEDD0077B644B6F3D247148326186EEDEA55AF3D227F3AEE551366CB1F865269EF3281D767A0F61301F1DBD4E99F63CD988E395088BBB4B1AF1E04BDD31AC9FF60D76917BA7571F8854C555A90CE6312BFE3851D0002D3DA12DED097FB3D9DFB9BC83564DABD8CC5F481804B1EFCF69754E6DE162411D9574C1CD3AF96B1D6BF74131BCB8EE646C9710AA268879F21E20A0F30B41C56E1A6C1DA02E3089CEFEDB4BE528FD07A2179BEFF6D2F9C3FCAF32E08D4C168348073CF15E711DCDB5F4817482E43BE5AD77634C2A2E517EB013AEEBE8CADE1B8DFA443A2FCAE351D446D1F89E2F4FE161DE95D0D224D49850307F1E192C4106BDAF6F0C19EC379E8A2470465D415D1A14348D88BDB35CA5785C3D9E349876F1A046B3568212851EB6E018321F7968A0849A530295866FD92704820717243B263EB5C5F4419D4A30A2E19F3B79087CB1D580BCDDD4D878BCAC5E342539534BEA2F8F9A7D61FA1C595C36F9805A94EE2D7DC1C19B2B988F139551EB1026B6ECBB81E6F3CB1750A274F121B50634851BF49839CCBDB1A305AEEDA6D04A432F317CB9C7BE9E90243A786F2B4C11EBE68C3DE12234901DBAF48D38FBCD6B42F061F918AE33017A9412643B52053E49C670435DADB13C1978EBC61769DCDF4474649FC61FEBAFA8CD2F5FF3386CC0F0453470C6DAA596738CB7B594C003CFD8DD651634470C36E6F6CAF0D1B776ED99E69057AB173D283BC25D1AD62B9552B262DB0222BBD99C1339D152002771F85A025BCE7DCF2D20F948901023D8D4EDA84977B02841567308AE1D722C515311F6D51AC8A41F7AC06326D252F8EF87206AB8887F772721F52040DEFE30A25ED492539E5A97F0D01196FEEA3090AFBB7DFDF96A6DB69C6E8ED0CE130E09DD394A69A9B1B6AFD5945E94DEAECFDD3284BBFE895E314FB283C175F119257EFC9E628238ACAB589E94ADF392DECFC86F15E02FEB1B3891C87237021C4A25D298EBB1C51761BFD87A9FC5A594284B51F11780C56BB00A510EF5D7DE3FB61F9C7660D6EFBC25C39D08D79FF9BC3739B4A2CF38ECB55105F2DF80AF18F97D67AD4CD4E73DDEF90D380CF95A73CCFCB8943E362A77DBC90AC8BD7DBF2DAC92AF4738FBF16EE2ABB472D4504FFF879857CD9FC640367F573AB700162EA1C83B479262966D359097CAAF1B308958AA67392E8F89A8D33121206CE1E0F082649566225898CBDA72EFF0EAD4C0A18444203B97ADD9A1ADB14642D86FC2C70B1602E92AF3330681FE6B223AC20642427CC0ECF524CCAD8EBD43AB615FCB493B5EF4ACAE4332C313195501DF81F55680ECCDF05A46BFB4F3F3E7E05CC0A39D4EFA7F52BDFF1EC27DFE512F5DD54F42A131619466057316CF42D1310923F0BB2ABB1955E3813227D83600EFCDB9F191A0F5D28788CE6DFC207FBB7521A15CDC7123DAE90DCA699BE6EEF068BA37385488CAF919554383AA02A20A2DD1AD89B8AA11C20EBCA152F0F0A5C2AC2F5A08C18816F0666B2CE25B10F6B52583F19CEBFC90364649BEAEF99575F48AFFA842872C1AE946D4C51F3140E0A7725947E9914BBA4D4992516264A3F707B405785A01E04F9F36D9F38FF1BE522D8E74672189625ADD002909502D3687B9C3896CCA9B20B721E4D0FAA91D81EB434C3EB63B799E4B7A725A0FDA739FF6C5EA4CCD43A6F69C4D83650B00CABB89BCE42059A5DD701FCADD4FEB4F4A98FECAD1FD286863EEE3A333332881F400A283C6A37A58A07C51065E75BFF382C2DB2DEB9F8F480222916FDCF6C1F2AA1E115A521F25BE95552E2AD989594C0EF01FE06AEF944F4B94419E687C3736C9AD02EFC2E461D552ADFBB7F19674C85DD220FD1FCBB41CD16E0FE752E2F5F59E65EC04A36DB50F2F8B968557E63C32C94D930E929DECD78B2B3A8C6B9499D2C1D719198F6C2F3F0D3945C3C064ACF8B18B716D537A98A8DA9CAC2CFAD27A741EBECE4A689C6073764E9DEB4AA6408A0B5969675AB68A080A0BB28F092933F7932DAD94FFF7009C31864A9C61DAE9C79E9750BA333437364CF64C8B5BEA7BB75A489BF95F06EE50038B177FAE6A62D09B898514EC4879579B432D053977636DFBB5385CE1EB3DD8966D940A37527E2841C46B5B929A3BA0E00489F7F5BF874F7CC8DA0D95CCF46EFC40F5F1BAFA8C1FE0D5CC198DA2D3A0417DC98397DC282EC69659A69DCEFDC1271A239E016346F8B873C2A6527FB6E2E03C551B42929536EE8467786BAB5DAE294E73AC96C492C5F64FC6BC14E0B9C160BA4550D91570D54B2A3C3CACF6BDD21A1068FF777AAD68A6CCAD3D0DA1548F7D40FE89B3E811660E61E70F020608E56FB2E17A74D3F19CD87FAA0CCB0BEB882C5648C4710C9D32F3106640D5CFB916B8E3436BE3A585DCA56620F13C3E93E8F134E0FFBAD5796F91CE046DFDEE2F2E984C91151810C14EFB9047141D81048F6199187CCB12927990FD52BB4AD3F9B821D264085AE6EEA62B125F0C227150AEE164107BEB5B81CF941F51BA8EC8964A300B80A417CEAD0A2F11456A1E7D1730F1C0E477B782C9223173EB85ECA9DA462A1909670DF6CD5E1C717D85B618E8302CA27283F9FFD5C3C2177D5E1E91CC47CED091E8C346B54F1D0EFF56399DE8E47057196DC7BB19B56841919818EFF53EF280B5CD777F76FC3DC912E0FAF75FC2D876ED6893D944C367DFFF3957CFA45DEDF760087CC607B8AEA06717FDFA210839E3CFE8ACD07AF0D483DFCB5382097AAF8EDAB64E24D5B8F8A6FC321A9D87AE4FDD6E078E032CF15ADDF099B1480E43AE8347E0F92CE0DD4C819DF28232B7DB46766A0C2CF5448688A9C411CB426872622467B1D8F195944347F7D024B5A26561A04B771374410F670DA05AFEDBCAADE7946DF5B997BD3FD8314A6AF508D2ACE87651D6E9079DBF69A50C9FE302DF65E3558E1FDC10BC8604EE6774533EA6A10DE42CE7010A3032F58505F756AEE94A012A393A476F3E538AF816FB5E4CB90E64A867573862FF16719BC7658EB104AE0093160383E6795B9ABFC391CD07C18BB02DBEF1875CE34280CBEB63EC438F6F6C145C81CCE7D5F5D395FB1D01C590C435CC65DEB246253897AE344CA6F60D9248E951E27731580BB6D8CEB87C36BF29F64FBF66DAF765CBCB7AEA2B3C0C5D514F6B201EEAB4980A4A4786D6CB1B5F08C8148AA2ABBEACB48B232F3AC061D3A563D8D6227D38E3EADB0963F2BABEDF1B6B2D74A1918843AC36A0E190A733C3152C64C8C646659001E6044457F94939F191D39D58BDF97B5B4BFB1D30473783253070E2CDD4A7AEAEF5E84F59D48EF49E7FE4938A7AB6B9D2F18C707E703FFBED8BAF6BAEA417C27DFCFD6364411788B23E49695C64403283C29BE41E993029995F10026D077DE7035B82A86C0E9FAD8A2DC97AE4DCDD3E354E884E15E070DAAE8C54C2E0415F0C8F3CD678E5E6B44E7FE1A4D6A1DC328BAA08590FA99D7E20E1B3D810E909C46CE0169C64B8D7ED5A2A7496C4AFB6B9AB58113DBFD1901FA264516312762605A96B634D1D77C7F9A2D67ED7F7C71FB5695EBA48C583B1BDBD4F4905A5594AA7E49D18CBB851EF51C915240B75E667858FD94A405D35550824C1C2B5206840BF061615577AA25E9E96383F67E262F7AB46750E90A9F6E24E4F90E5FAB58CCEDAA44CEE26AE86D850CE18F889603973D02C241E5E2AE0F90A774030C23D346D2030E57364BC351220870BA91A85AA6C4CCC03D0621D2A8FF5D7DC4589ED38A101A5EF7333BEE202A171D47A9BDF5905E62B6E2524CF5CE7790A1E2E6A46EE1B96E7CF2131151E436BBEB93406CE36C83C748A9B48653352E351FFD26C36F0ABD9E33FC122891FD528200BF783A9C624E4B65AD19B6DA1440F28629739BB75D09AEBD03775151301A9FB6D87B038436FC0C654479175D9EBE57D6B1A8D676FBF7C97D9276E3B43E9A0F826BF585BB55E484287B9989F087FB5713FD12BDE96F91FAAFE80EB82DAD6DA8C4DCAC65B6A797F9E91DD1D8F101E5FC4B6F82C619E0B0641602C40A863458EB617697A5D92594F4D54F885ABF39578A32104CB6B032A62832ABF9E830525A414CF45F4206FD0D0789AC6B15771AEE30DE8DE08EAACBFF91BAE15D2911AEA5780178992E472FEA7E2CC21EDF98A2CF87FD2B7F41FEBCC61C911B76D8F637BC5B52332EC3C1A4E46F53F7B496CDF6AC3790CBA5B7CC57D57C215B7B262BF2D4BC75535CDB08A5D11274F5608743A6944260FF5DD6AD5840EE9044E29B1F06B73D8CCABC6B66415D68E0A58822945A3AFEACC823860D4CF78CB79CAE9DC2081C8A5B0FBD70BF1CC968D4DD4433A01AC1B3BED91B124A819D5ADEB32FBA58CDE101D899D9F14511443FBDA79708CF5693E1388FFDD46C02534E1F4F90B514DCB09FC002495B535E15AFD0651DA5FFEF8733D7C489ED96D0EC4EE3027064FE5E9F5997D3DFF57B5FB36C08FD774C23D3C470EB7DCF8E5F0F57F0C507C07FFC44598C411A5A0BCB79784C66BBD86C665513699786C43A9609CC9AA1FC1657BD718C88BE9E141365E971FA7DD4DE70560DECB59776F7473749D2E243C1AD62A9FEEADC7E6F9B219405740E7C97D291B81D956A0C44B451DA01736D53CDC932FF7C9AB164B41F1A75C91CA47077A8DE3A2160B78E020FF195EE40865743E01809DF1127A4ACE233F67F4A39C0C30A5421D29D129AF69D5C70D8A055968E25DD1CB2B218489470A6568CF3E3CCAA7EFE57E4307F3DD67DACB8B514401AD262604F618CE1E3ED1972C141F421676CAAC3B1F6931BEEE8A8EB2C65BBEFA26FB3ECA770B41D87D8E294D31FE3C1D4B30C5CB6D384B147FBEEC1E202A6A085A615466D60C7282AEA06648B4B771C9D7FB513225C51CD7DD152AF9EE5DCD599D15E826E2275312F9FBAEE25D5A8BF3464EA0DA81AA3062804EF372CFF022D7456A89AD58630821B1E1639A62A57C7CFAD49F3E37466345247776F1E056C772E7C744ADC8AB15127E6FCFB91FEBD297E5DD00D21EB466849AC31B0833B60A9A84985A8A2D6E7B622E10FA2E8FB4D37614BC9A89B595A0B7979DB513EFD96CEED85E93CEA0E5143186C43D15AA95CC290AD44239025072C1FED1A415CA04CBCF82823A8269FA5A585C3939AA37B21889D026735F176460971227A2A0B17A0DAEE594A68248B4DFFB00F12006376B50F5DC8CB8A8901ADACF0FAB0BE42E1CDA90915D8F3D96AC34018F1152F08C45B5C69AB4F2C66D0DE6001035E867EE7DB082ECEAE47357D9F294B725057B4084C5D966BAF7E8B90E4CD8CE09048CCD006E0F801DE10463A7EFFAB732B6CA48260C2F738AA473E82D004CE5A2AD694EB407AD5245CD7986B6F07B0071C9320BD8A64A5EBFB3798B681D499E9300A09252E96D87E832AEF148AB56B204342609863B7758D466687A0338F55EDD794F56D6E33F687F7F0D2CC8BEDC745D17B87C53A96E682F96158FE658FC2221D2CE9801BF2D52B43AEFD8E9967D56FA32E03218CF0391F0476BE6830AFE32D82ABE1135F0FB7A32A8306F666150E3284A1D8C20172726CAB3514682ECF52862921EA3F1A24A4F13A5EFF857C5A929065B26E1E88EA87AA3E6DE46E6C6C6E40FB07F74B8887B74BDE7C28ACEAE2D215EC3AAEBB4D48B19E87E07075B704D2978D451DFC68081896177AA49E07E0AFA497531D3E6335EAD7E4D0061BA5C6B882E19DFB3EBB9897AEF704674CA33DE4A379E6D68527D9316F2CD574FCBEFF9D4583D86687820560AB4C07C467A88A278311119B511C62EB17580AC453365B2D08D6D39FBF76531FB66D189A4E559BCFBB6C6DF83C6EF738E42C4533596F8F0D40EA4AE1F2FFE5A3EF4DB8A28EC264517D3A53A8AD73FC543CC4352A6F8D3F0C687FFEB58D5459F871A759319D2E7372EBDF029E72CF884D08979142F3EF34F20F30568DCCB97732EF80B887EF821981C860FC0B420CC16E3C60932E554C74AA8E14950020EC4A8B7C5361F335642CED223A9785635C9FCA8F3A22C8E41C688C6A60D1D90D3FCD5B658AFF9789AB5DDDAFA414B24DCA8067419111A146D08F1DE005260BD24AD4C6D202D3186EF84E50F57AC6ECA87B0424B9DDFF0F210433906FE00B38D4DE3760D00EA8A73285ABB1698FBA67C08AB1B28F9A64C9E95312C2E8D7752799EEC938C57F2F4D112F3F7BD570C4503C3974056A7D701C33843C514B83D56F7A06851B11D3E32379786D55AAF4F36F7442AA0F71F3EEF88B90C21936DA5A15AC5C9321ADAD4389C7461B904649E162F8026C7AC25BA656AD51533FB718A91C43D1D8D39F7B48EADE4CD3FFB31422EDEC6BEAB72A80BD3AF8F86A4CAC33F4C53C13E911AEC0ACDBDAC1EC9A33436E5E72B543C427757C5CE878C762735391EAEE1BEAD5049583D0E32A071B375266888D47A217A35BF88EEA4AB4915FA89678FAFEB6353495E260D147B39CBE0B8AEEFFC5A3FAB5BF3BDC81FF74EAB8628B6FA2BF79C5A30CBF1E797243E2B5B46F97212A86949015950637C418CDC5BB2D5380923A3B65A4E5CFD5364D69272C292330EACAEA646CBC53ECA7AFAD94F9388CCF49E871570AB83C89545D922AC3CE5F40C052B2206E4D55602CBB52B7FEC994B4CF14D030626C22EF600879D1702893F0D31C32CA673DD2BD150F38A373BAB680B34E65464ED681E67CB709F6AD1A9C565DEBB39DA1C85690A4CFDE9FBB315AC3D2223BD612F1C8F0B230D0428D122ACCBFB3B83144204C07998695C764171B770B08458C35EBE300A8BB08BA7A443B91EB14BBF300B90815AF1D4437098619006D0EA2BEA4D0E9F62200866F58A58B8D15AFB23CFB80E23FFD71273AD22AFFE74C2D8EE2839F76C8EC2E3A441C80482BBBC0E05FE3DE6C88B3C91D97E53937D8C5E5B3D33BD7F7D0F1F38AF34F327B3834B5C71649CB963EA022B9FA0246EEEBB0075C680D3E723319A6321D1B13059744848EFB3B708277AAD2A30FBE3EE266156D9F85820C8B841DE1DBDA77E9A39F1B42C1FD62E6E6372257D217F58DD9DEB978424E7304EBA742270BB26CEB01891530DBE41E49C2E243B8CB4B30A573BF76084A861A5A8C898884243BABE3D24A2D45DE9E7E73E70A85546FA5292974EEB48478B61733465871D91A228109E0D21AB65DEB224144AEBE71E891EC286477024AAB7951457B0B61DD289F7B22012E825724DDB3BB3CA46B77307B91C00F55B82D6DBA9BA26CD7B010E626789E0998CD40F1C7BF4128D55416FEED562DC2424314A2097FAA45C2CB52A48C54BADD4B9E95F081F841D0F22613619A1A03971DB6E49B24A00431EA969A6887F2D9166A1DAA5E2579999AC1A14DFCF4FBE516FE2032C69F20E6C9B27BC56F35B60352F2D8B8D72EA46BFBD33C2818DB62A98715603D91CBD58185951AF7321DF97C4EA01EA14439581EA9198565DB7785DA70C0B53DB5AA436DE7902A5CC1DE6B04DC8729BDB41668B18B9C2FD1B8855E816B7118BA60C69CE754A954BA6343BE7454B4BDCD08ED96E9D378956E66AE08C60682D36FFE9BB94C509950F9717752FFAA4BFFD6D99D5F29AACA8FFE05E0637F0C65EDF7FF1DCAD41E19EBDF08A93B2EFCFC56D32AE49A94131CA89B8A59515EABD13A89762F0A1F83B4580FC243D3BEBF1CDA6D1E87A22AE4A2FCE4C06B2FE80D1E5BE9451C3D05725BBBDA52660DAAD2F7B168D87A21720B83F215C869E287EAE412F1D750CC710B244D4270D19296FAE72E44E5D261C5B56494BBD1E7F23D729D27E5BF4252246B5413C5144BCCFE82F74973570F0F0661A3D3C6C06438F4468F56128C6426BFC6F1FB33DF18EE107D675FC4CCD814D1163B33C72C715B930D2C223BC165420381B9EFF484AFAC2F0BA86A5B041F22F6EE8B7CBF1C091206DAFDE13BFB1CDA61BF5DA8F288DCCA7C3E65B95D0F0238D538558073453D837A92A75AA33F2C713DA4CB1CB158B4F8F44819CBDD9BAFC5DBE625701E4D8DEE015424F936C50C42B9FAEDCF6BB063B6A39921A038692C97AC2FDC1913CFCC58CF4FFD964E83683FFF311D82AEB07F25DA31FFE7296D178F467FB0283696D8160B2180AB169B1C60A92B9837D05DD79DC685244F30A87753BD455747A5ECCDE04D9EEEF1EABDF4D098C0DC1F87278D2326ED7A562308A27330EEAFDD8D04B3B9A86A09B070F0C23135DD4BA54470AB0950DF8C563D76686678E31120ABB5CEA897CB5171F3C4582698F61AD5586A64E4524788CB56104D0A5A16AAC8A6E73A178CA9F6435FE312F69DBB3B7B84F3E0A82B7EE49C8BCFA1F678106B78C7B1E9727F78D75312053F022EBF3ED0651031CEE2A4D4D45A77E9F1DACCA6F3E43068412D4AEC4F6FEC06191C2B0ACE076488200526B156E0024D158DFB33C887696C695CED0B37BD884E6CE9A509AC7D39A4C038EED9662925FDC7A96D6472A716F9F9C6465F8DA29CC7CB023A2D780CED91BB212F501A96117E3EE98614F65E3A39A9C4A3929AED30DC4D0E0B98A2C66D1E90B9A5E1BB6F1F73191C7A2A72E56082E794B9E4F94174C6ADF4C3F36CC14EBFDD694D6B67151A9C989C09A12448464FE664DB21653267C0AD3E1D4D5A37E201198F6A0A6DA3AC0EC170E649EFCC39B9E00F7E62C1FD2131123F57F1299CCA8581FDBB84E0474C935D24CCC08A98E73DDF7544340490A508638CB45EA0D3145BCCC260E7D57C81AC72EDDA3E7CA04B83DC36433F0AB2C3F60751C88366EA1DC85D34F40DDBA7D13441A3F573465C839A004B82FB306875473954F25966F4399EC4A526BE145CB700EE3ABCD857F1E9DABFC79AB08E6E9061C7F836ACA3A37BFF335A8B7D7261AC9D9D57F1F2EBBEF5EAC8E74A215258CC20EBECA042EC106B155E89087A1365A4C327C1C73C83B5D028DBA7BA30C3689F3EF1D0E304332DC17371CBC879F1AB5FA766144F820FD94AA8BDF14FCE45F98FD6F1DAFB9F4474480B1EC7C28A584C06772314815809D8DAA2E5271CCB9D2C01527D143824FFB82700CF3D024AF38F14548E18E233527D64C35CB638A2B535420CF07FE31E160682BE1FBEF27B73E7A599327A8533744775F6D98E4B38A51BE9B0AACBFBD320A7B192A4598A62911A650DEC98CFB87A29CB144FC98EFCF7B4F8747433CFDB30C1051A694700B729FFBDDE3E94D3C00CE141D26BF98B796AC5F7E71BA0E5B3FF591B4A015E0FE1E20BA0903C6C978D1C85037F025E01BC3BF679A42BB9884BCE9DE42C9A6E8C31C4E7693F54BA513F980DBC904B601A9D8E54681D1350F222AADE4FCC0591454A4F97589B01FE0571BE710AC8AB3D9C9FC172EA5A1F83403A74483D7D9192E806B43CCF2183BED9251843EBD928CEB436E3071E1923FB8BA4B8C646C761532FAD4331DC811CA41174B4ACCC62AFA6A17F20750E9FF61C1BB998B6F82CB6A4C0AFD1AA9B1AFC7C03E87BB6678CFB2CCD24412D69EE98A503CE2C100E34C9F7E1C0CEAD7E418EA092088E90A83165CB87B667E53B75BB8CD0D6AA05219E86FB0F27EDC6CD912DC49821A41E1944851D288F2A394D725F91723FA4C654709B0D3206D9225BD4F7AC4D8458D5E53B47AC387BBF6DB4AD787DF1332E477C02A059CE8C6DC55E0F2841EA5E951DE717F05802EC2208F8381790D36122217118E6C1F7AF83875F9F0068E219C455072BBE77386AB2A5A30267BB5BCB1564358164F0D20ACF63E045CDFD0994C308CB58369FDF7AC1A3CE0BEDB7DE98DE250030D234CC475072146AA29E160EE35F97063C20E6F4B534644138355A276504D812184DFAF9EA220C935077D703C4082F029391EE5AC32903A606ECCF855DD72E64D58A9D6D155E381434D2CE4E9AD955B51B7FB48B5652EF812A0FE0FD9FA3F3FC092B711B547E84131C5B28A49A98CCB1E1FDEFFB9BD5ED32DE7311C857A54C9B16675B6895524E5745CA5CD633ADB1B82521E6DEAF811E2B77C424F74DCD8D08E5486A2FBBF901404E88680A659230E9C3077FD5AE1077FD855C0B7B69593E162C24A7189D93AD054850221AC5A9A6E9A4A72B7BF3E637798F302172159A3B4CF1632216F3E7839FFBB50EDFCE0D643B778D8445F64A2D03353FB9A50641D91348E870D9D3A8E024D7606B32E15D855A451C69F0690E5B0B7718AED3B0E6189163DCE68FB2E89BAAD8F522ECFB975DB50EF741512E0E2C252092191036EEDE6A72E612650D99E4E3A730388ED2DF939ED78F8A2B29A30B2AB29938A437FB755E91E11BDD263B5778FE1DFCB91ABC45999210CA534CBDB0587494976E230F7001C7F434E35C852DC13E2E4C7EB41D80DC9781DBA8D697EA935556A7F72F1F62C04AD0502C30F576E9958C1A84241A64BAD16ECB3293DCDD9FE7EBEC066BCEBCDB29E8C4F05E0076DABCC0EEA2C1D70B054C66D6C55D1F59C9084BAACB2D923BC5BBB1AB40459F1133B5E860C916BA00779EDDBD7FA38D921794162E4A838607A8986708E6E23FA5B9E441D18F56EE7683711F7E2D1E19AF24505DF674B270FDA53B92EFA578C689714ADA5E062C8807266EEC3B42251502004A24B80B683ED17FAE5B97D9BA03274986521D123C97C84E21AE684087FB22C90048760A70272CE68379765E5F19C2791E24BD0E14002A88823C391E454A27B9F7EAEBCA26F03D8F4E3EA995AAD4A27291BDD99D59BF1D3C66E80D6F400F114506E3BA98F426CB7F0278B24D2206ED4BB27EFB456998FAE54EA0F23ED295AFD0F45D4D54CCB8756B5DE11FC9F7ADB8F397F3B89F3A897E1E9BFB50C3BE7EF7FCB55F39032C66D41589D39931464E4000807B9371A233338D9D7163A2BDD150DD92F9B46FDD096CFA1158DEB93B3345E149A6225A2D3CA475D8863AF351551A39FDA40B7649AB3F414119F114BD6FE1304F4731E8B965BD5E2F6B179D34BEA3148554FDD389A133A3B3C23E40AC8F3D606A0EDE456BFA210AA1EBE6CABA1AED4C1D1624532844181D081C601190536C438E9679CAFE85AF5885F3B9E3610262DF1903487F2E31FDFD4A391C4FC0287915846A9E65DD88DE8C87CB99263893FDEBC609A2C57DB9603B5982F132948C799BC309E2D6A33C8282B3D0A627E60150485B26E16287CBC78E2F553DC51AF4C15921AD552B2CDB057DA3837D2FC38315EB504AF9C8E343D5ADA7F7F6EB61779315D3D02CEF49019A9A06D981019FB37725782FA5A96463A8E23FD12BAC92F41EC825C8E48159AF9137B77270424BFD83EF976A0D561E1972ECDA7D543327E715A3630082FC7A071B4849A86F5967A6E3313F48C64767FE45B047A9331E8AC3390724F7449804D811B86B5DBE077F01F90501B52F8C0BDEF43B494728408F66026BBBFC3DD816BD461FA872E1B4C8D92FAF511EDBE9A032EE0D37120F3108F0F3E21AA800AF8F4683373AFD8C7E414261173109DD2897823C73C46BC9C2EBE5255BADBE7E0800D48E3ED889E7FBCC68BF688E0EDE5F3934575490F6ECB5708C194E85723C86A8906FED22D55555662319ECA5C7C142D08E76FD9A18819FE053E66BCBF001C42B52F65A17DC2AB91410158931E8069ED43BE5EC1725233D0C30499A878963A3B6E41C8ECDC1CCF2BCBE3FF88778DFA33388AFF247F1C994360786E3BA2D32854C75DE01DD9032FF12AAAED6D746CD63D19C01CB27B70559E54FF5BA93FD1BA83666B1C8B771409602AEDC1DC8BE0A43A89A844665DF6B5CFDA4496EA5025C3EFEC5F0186D2629687B2428F2DF75FA13F6795B8A1D0939BBF88BDC885BAD00A685617DD88FC01E674098B9B112A7684865D58F64FA1E7A1D92E3171F9A8F89C49E330784717B5A2A1D38E224A09F18231C0ABFE82B3373E0BB06E40EC74C69C3BC80A8DEFC28E9B057FC361A8EABD460AFAAAD351EA61B22B8052FDDAFEF3B83503D992E63DE8E00EC91D99E3EE25FC69BC41C3940534282B15912DE9A900CCDD700CE831DDF77B0F4C59AEEEE1ECA3D5733C702CDB3C54676DD5B1FC428BCD9594295601CE921B8E297CD1D41CAC081B9841A461F40D3DA002AD7954B933867759F2B5B3CC78623080348440DB3DFEFC9DA67C6806BEB73867BE2DE34A7920D996924646FF63CA1F293DCCF90923286BA5BF351CD27D5CD0C281BC9F0DE6CE64806B4CD1DAA23BD97C5366A377EB2352FBC877223413A16BCCD8C53438AA1EBDC767483B037CFEA56D3BB86BDA8577A3700B5607404F49ED342AF8254489BCC9435EA744169E234332773839563BD1DC448039B1B1C24C413E44DF86DBD7A67B0BF05F961B3F1A51F1F3938B56AEAB7808885F7AD04FA3A7F2E57B141D5F225CE1BB552FB263262FBE6AABA513313EFAAED1C4309EB18F1C45593F1EC1C58E3A60F713C16C390C5434A2728008E580748A1E67E2CB268DE4A230909C808FD86F2128C7388E7D69C1E5A8F9371400A1FDBAE5259A5FEAB0D45CA8DE1A987C101848F3D9BD18C06946DB598D08D67A44A685F563C47D9056E5DD7E9EC045C0834F79D407D40CCD27AD033D0661368BA7E3967B87C15A5DFAF60BC008535491BC0F6A0DC9EB64EC151403DFB670829FFE07099897472A23D13806AB320C08C42E4DB7D9EB80B81B2AE13AB48EF4556B16B2EC6D87F1B081577DE18F78EF808D29FF9774E3B719277A69C379BBCEAD5405654AC5CF3131304D48C7EF45FB584FEFDE25FC19CB0E88B9A73DE87F689A38962AFEC60D904AE8FDDA809590CCFB6F9504718D9720D014681CDD2DA05D6461395C51172DA3B8D5B4C028F4895F2F3199F1A946616BC969327C7490C4228FC5F63B3FE99945FFFA874CCBE65D96EAACA64B1F5D274A3ED354434E65B3230D07A27AA4A1B1856FE0867E68A8D995A4AFF996CD294C9CCEB08B7C72749935ACCC89442870459AAEF9F78C24187DA57C9ADA74DEE9E49BE6F4AF7A16A08482CB0F2E43F11EAF0BD58F1F410B5469417D478A87EC6BF1A1DF041671AD123415DFCFD1B8AD9A71EA0BAB7E20E7A86DCBA371B0DB4EADDDEB8608A2CF4BEE0C2B0FE8CB6E8237D8B97EBB07C16634649B780EC55EFDD3ED3C3DAC0B25DE476005D05B7C6F1C16453C60A7ADF45A9BABC2A962458153C76A70CEFDA6F3B6CBA6CB275B2EC6BBCD42EC8AF7A11A1177B11FE88BEB026589A3068E8A882ED382C817B0583779D21CEA74541A3D3D37E2EA0E1C95DBC824B7992E2023F41707214101EEBB55CA6B2E2D5C38A3EF5E00D3D331E6A49C617A8CC136AFA28F19AD5076C8AC7DEEDA7DABCF7F7F0CB70E2CC4AA6555AA6BFD2E8BCE90CC0F728B93A8664478170D17E458A341FB9637DE388A8E789A504AEE16AE45E5B57B8B1120AE63AA5EEA58CBB3958E755E9B28D19E6D3FDCA50E692EA3BF77621D5788DE0BAC3F66ED16FE8920D7D0745CA5B165FBBA962097FF100513A3B5BA941EA4A4617A1B79EB84352BFB5B05B07CF1BA97FC1CF3B2C087620EA22ECB7323F0C56C3C843705E8E8A6B9FB5621672C3C15B30B28FE8B2B365FA543ED376F05031A526371013118FF50C2A47BAD8E457149E22E766561BAF022546CF26D10C741532F0C33BE281DD83803A7F912C289204859BDD8D79BAE1BBB22A9845AD7CF18E8C5E31AF75F23E751083F9F32E85FFB2DEBE2277D9C18D83F179A745297F2B4144AAA8B3EE164D5CC4A758FF2857AD73560730A4B9945360541DF7BD8CCC9B1780A23E6430DB264CD715857F074418E360CC7944852DD3F6CBEF8AB6BCCD3735D3FED13C5767305A448E8439ECE8BB850503AD9B596409F16F513E5DA2C7BFD3819DC05D8DD355EAF93D4ACC26D2D10010A20E62133A9838C109EF76D44CC929C0AD0D8ABB9665F78B03A1C1B6F3EAFDD3E23333952282BFA51855710B06304DC7F8EB262E5ED1C3EA7CE97469DB3D3FD875ACB21E840E81C2CA8AAE40CB58E85DA33308B9469E0A8ED0C65F95AF69E3DE264F448D2332B4B9EF7AC471A31C48FB0F85A62325E78E47E14E1C2FFFECABD51DB7CCB0E1EEF99522712E02B119BCC28239C98A434A4C9B31BF884230E8030350B87B2F21A9A8C54AD75EFA1B8AB356383FD330CC2634FF2076DAA1519D20534ED1606220AB80FF794AA522622DDE8F56C064523CF13F178CB2763AE0AD62AB1B5EB6C887133B4FBA633D67AF23D73EFCF255179025DE68D97682ABFEB23B160B88DD331919AEA5BD5D1ED3A1354974C35FD164AC4C727B86260430A3B42788D536D1621A507E1119F6D7CFCA7BDA84777B1512F268F3DC1736B8BE7970E7E6381E386612A5381E469C737E1C20706EC931BA23F5B3D661B9BC1C0A7163987E9E1695A043B7B39208438BB6BBE29B4226CBB590A70A8EA1F96C36E69A49A1262031F4BEE3EC4AD727A7885F115DE12E9B63DF99BB43640790AAF98561E26E1B1116BF0CD54687DCDD03078A8B01960AA6ED263D3AFFCBDFE0D6DE4EB33401644642B994896D32139DE0E504164A44562AC168AC7F8BD0DCF3D525A1FFDC383F796BEA4CC7EF05AB67EE7BE95E51582054E4501C8D479C175576FD1811EC493CF03B37D029AF7FBC78A007B5894D9B1733223B131BC8ECBA40A34615DAAC87955D1FC628005F168ABE6FCCD48D05C74E6A9F98C31EF00A798DC3E283108FD18F49260935F6115C7FE2AFCD21D30C03D24769B1C6091FAEFA6A869061BE87C43153F4F685399CD32CAC07D3959CF9523C5296293501AD62D54CF16E270C1ED32F25F645DC44A4912F34BD041BE13BF750D36A03F35245C0ED8D0938C92FBB475FEFD4261045AABCF774310120E2B4122F98DE809A76956AB84154394BDF35C6F4904372780A03651DAB8D46B1F1C3187DEF2479D7DEB2CC4C154CB94BD5C36407DD11E0D72A810202860067D5AE8A253B86104463822E783125D986271F31D7C90BBC89F37993E33AAD9EAB447F1011D53808D68C461A004EB0F167AA51F30EFCFDA7D1C4D668B205E37F4EE6012B4CB9685545AEA94BACCB18E4DCC41809EF6C6F58E6B9162E8F13D9F077255EB449DBC4EBB082ACC065469A20F9F768BE7EBB9DC5E0A7845695078523F1118270BB59AD3508332D66DA72FFD889BA5713DAF2DE35B451CBDCC4C4DEA7B04BB71D4F9C2822DBD0DC1D75A126834BE49C5D4C67218FA7949F592795C5574B499F937B5BFE2056E84079FCC01485ED320B3D6A7F373F8E96BC8E94867088E3BFE4AA93C1BB6DCED8EF6F32E1EC6CE7C4CE166E4D863C18226329AC0BABE4DCF9D56E2F2E685CEA57EBE40A5DA68B412FA0845A8CFA5773DF6A6709980EED96DF61E01AE7E73A86E4705B516EF33E1FFF848DD55208DC65B1A4B323ED5F783500B377482607EC54E6F8050332685D9531545104C7E29CAF635914D34EDF7137D5C9E14374D1F1A3061969CCB2331FCEBBA7ED239EC55A549D7D7B72CACAB3A905F4B512A51F5E643DB7A3E5C0158185AFA851B06CF12DD8AFF69418DA47EFA6AA60AD5F63446A0F96F6679E2AB564FBCB72928495A5660659CA894687EEDD18FC707FF9CE8504754050B47428E785F99F1E1427FECAB4A1B65798977AF67FE414DA1F278FEC4B0D290F582018F533F15B3FB13A8E0B2E30EA6375BF3DB305101F2D46BBBBA649D50CEF5E8543DFDFE858CCA30B3E9DE1CF4E131B2F77A7D8D4D5A4FB39A3BA60369AD1E68D8E8AB263C0DB7CC9FE627A596F9E94B0EAF004392F333A1BF65388E6D5AC2EC66C82BDBCF74F3352DA38578967201DD4520A57F7FC448CB1F20854B161A58BEA88223C370F62222FAAEE67B7098A57BDF80140053C66AE69ED1D44C4BD8D06349F649298B21D38FF38B4ED2381955EE4E4B2A5D18655716EE641ED8A996102296FB0D6B7AFC65DD0D1524A9D76B817C1D11A3F66E027B60CE26722CFD792AF018C1E04AEB8666B5837636FB904C502C4E01692CE0D09980AE2F8B615B88F1D2FCC8311561729F4C231020F836170A40E22F1B50469B13A12B9633C54729F4C1D6F0007F561C0C3802342A69C6D160F242E51B9A42843C25354B3FC822593917A5B78A86226CB59B2CBACAE43C777F2664E41FA1CA3E01A91D9E1054DFE093C00D34E3676D042E04339BFAECDBC601A06691A20E87591CADB310C161663B300808B2F807A1FBAA1353976CCC12FE5FB5AB0D78DA8AE07E8ECE1F483481F1112C9B28D9F1FE55597A3E99FBD81D3A86FBB01B4E4B5F18BCEE8DBD4803D87724AEDD34C140C9190878261D23EE7D5FB7E005E53F0F87CD3FDF4636652C1EDE5CAC7B4FECFB426A731CD72C0FC0E1CF5CFB1BB463B4008D1A3E413399FD1B750F11DC0755060380967936C9AD77B2059913CE5BF91679A1DE12D0EC5E7997DEE806908F2322328FCD52AD9851314E198398A701F01AF1CE2583B8FF8E891A3AFFD88D7DDBBE0C0D9873E930964DFADC7EE7523AC54E087BA128F2D8D8A855E04B76CA309A863F0AF94EEB8BDB41675ED4D2E3740BAA1706344391345169789C29E3BF186964A562621FF9AD9449BA0EB9962A3547D95E923BEE844722675AA95EDD1ECC0A1758AAB09BBB3CC94F4231C577AC9579A7FAA90836829176133F5FB3D39578BC38E63974D12C11137DA402ACF4E9F47B769244C4BFC20E569B11B34DC3F56F2FEA5509A5EFFF441EFF908BAC4481E7D0F251D14EE760F16 \ No newline at end of file diff --git a/test/igir.test.ts b/test/igir.test.ts index f7cf82622..27bdc884d 100644 --- a/test/igir.test.ts +++ b/test/igir.test.ts @@ -94,8 +94,10 @@ describe('with explicit dats', () => { [path.join('One', 'One Three', 'One.rom'), 'f817a89f'], [path.join('One', 'One Three', 'Three.rom'), 'ff46c5d8'], [path.join('Patchable', '0F09A40.rom'), '2f943e86'], + [path.join('Patchable', '3708F2C.rom'), '20891c9f'], [path.join('Patchable', '612644F.rom'), 'f7591b29'], [path.join('Patchable', '65D1206.rom'), '20323455'], + [path.join('Patchable', '92C85C9.rom'), '06692159'], [path.join('Patchable', 'Before.rom'), '0361b321'], [path.join('Patchable', 'Best.gz|best.rom'), '1e3d78cf'], [path.join('Patchable', 'C01173E.rom'), 'dfaebe28'], @@ -121,8 +123,10 @@ describe('with explicit dats', () => { [path.join('One', 'One Three', 'One.rom'), 'f817a89f'], [path.join('One', 'One Three', 'Three.rom'), 'ff46c5d8'], [path.join('Patchable', '0F09A40.rom'), '2f943e86'], + [path.join('Patchable', '3708F2C.rom'), '20891c9f'], [path.join('Patchable', '612644F.rom'), 'f7591b29'], [path.join('Patchable', '65D1206.rom'), '20323455'], + [path.join('Patchable', '92C85C9.rom'), '06692159'], [path.join('Patchable', 'Before.rom'), '0361b321'], [path.join('Patchable', 'Best.rom'), '1e3d78cf'], [path.join('Patchable', 'C01173E.rom'), 'dfaebe28'], @@ -148,8 +152,10 @@ describe('with explicit dats', () => { [path.join('One', 'One Three.zip|One.rom'), 'f817a89f'], [path.join('One', 'One Three.zip|Three.rom'), 'ff46c5d8'], [path.join('Patchable', '0F09A40.zip|0F09A40.rom'), '2f943e86'], + [path.join('Patchable', '3708F2C.zip|3708F2C.rom'), '20891c9f'], [path.join('Patchable', '612644F.zip|612644F.rom'), 'f7591b29'], [path.join('Patchable', '65D1206.zip|65D1206.rom'), '20323455'], + [path.join('Patchable', '92C85C9.zip|92C85C9.rom'), '06692159'], [path.join('Patchable', 'Before.zip|Before.rom'), '0361b321'], [path.join('Patchable', 'Best.zip|Best.rom'), '1e3d78cf'], [path.join('Patchable', 'C01173E.zip|C01173E.rom'), 'dfaebe28'], @@ -175,8 +181,10 @@ describe('with explicit dats', () => { [`${path.join('One', 'One Three', 'One.rom')} -> ${path.join('roms', 'raw', 'one.rom')}`, 'f817a89f'], [`${path.join('One', 'One Three', 'Three.rom')} -> ${path.join('roms', 'raw', 'three.rom')}`, 'ff46c5d8'], [`${path.join('Patchable', '0F09A40.rom')} -> ${path.join('roms', 'patchable', '0F09A40.rom')}`, '2f943e86'], + [`${path.join('Patchable', '3708F2C.rom')} -> ${path.join('roms', 'patchable', '3708F2C.rom')}`, '20891c9f'], [`${path.join('Patchable', '612644F.rom')} -> ${path.join('roms', 'patchable', '612644F.rom')}`, 'f7591b29'], [`${path.join('Patchable', '65D1206.rom')} -> ${path.join('roms', 'patchable', '65D1206.rom')}`, '20323455'], + [`${path.join('Patchable', '92C85C9.rom')} -> ${path.join('roms', 'patchable', '92C85C9.rom')}`, '06692159'], [`${path.join('Patchable', 'Before.rom')} -> ${path.join('roms', 'patchable', 'before.rom')}`, '0361b321'], [`${path.join('Patchable', 'Best.gz|best.rom')} -> ${path.join('roms', 'patchable', 'best.gz|best.rom')}`, '1e3d78cf'], [`${path.join('Patchable', 'C01173E.rom')} -> ${path.join('roms', 'patchable', 'C01173E.rom')}`, 'dfaebe28'], @@ -203,10 +211,13 @@ describe('with explicit dats', () => { [path.join('One', 'Lorem Ipsum.rom'), '70856527'], [path.join('One', 'One Three', 'One.rom'), 'f817a89f'], [path.join('One', 'One Three', 'Three.rom'), 'ff46c5d8'], + [path.join('Patchable', '04C896D-GBA.rom'), 'b13eb478'], [path.join('Patchable', '0F09A40.rom'), '2f943e86'], + [path.join('Patchable', '3708F2C.rom'), '20891c9f'], [path.join('Patchable', '4FE952A.rom'), '1fb4f81f'], [path.join('Patchable', '612644F.rom'), 'f7591b29'], [path.join('Patchable', '65D1206.rom'), '20323455'], + [path.join('Patchable', '92C85C9.rom'), '06692159'], [path.join('Patchable', '949F2B7.rom'), '95284ab4'], [path.join('Patchable', '9A71FA5.rom'), '922f5181'], [path.join('Patchable', '9E66269.rom'), '8bb5cc63'], @@ -215,6 +226,7 @@ describe('with explicit dats', () => { [path.join('Patchable', 'Best.rom'), '1e3d78cf'], [path.join('Patchable', 'C01173E.rom'), 'dfaebe28'], [path.join('Patchable', 'DDSK3AN.rom'), 'e02c6dbb'], + [path.join('Patchable', 'DFF7872-N64-SIMPLE.rom'), 'caaaf550'], [path.join('Patchable', 'KDULVQN.rom'), 'b1c303e4'], [path.join('Patchable', 'Worst.rom'), '6ff9ef96'], ]); @@ -241,8 +253,10 @@ describe('with inferred dats', () => { commands: ['copy', 'test', 'clean'], }, [ ['0F09A40.rom', '2f943e86'], + ['3708F2C.rom', '20891c9f'], ['612644F.rom', 'f7591b29'], ['65D1206.rom', '20323455'], + ['92C85C9.rom', '06692159'], ['allpads.nes', '9180a163'], ['before.rom', '0361b321'], ['best.gz|best.rom', '1e3d78cf'], @@ -273,8 +287,10 @@ describe('with inferred dats', () => { commands: ['copy', 'extract', 'test', 'clean'], }, [ ['0F09A40.rom', '2f943e86'], + ['3708F2C.rom', '20891c9f'], ['612644F.rom', 'f7591b29'], ['65D1206.rom', '20323455'], + ['92C85C9.rom', '06692159'], ['allpads.nes', '9180a163'], ['before.rom', '0361b321'], ['best.rom', '1e3d78cf'], @@ -305,8 +321,10 @@ describe('with inferred dats', () => { commands: ['copy', 'zip', 'test', 'clean'], }, [ ['0F09A40.zip|0F09A40.rom', '2f943e86'], + ['3708F2C.zip|3708F2C.rom', '20891c9f'], ['612644F.zip|612644F.rom', 'f7591b29'], ['65D1206.zip|65D1206.rom', '20323455'], + ['92C85C9.zip|92C85C9.rom', '06692159'], ['allpads.zip|allpads.nes', '9180a163'], ['before.zip|before.rom', '0361b321'], ['best.zip|best.rom', '1e3d78cf'], @@ -337,8 +355,10 @@ describe('with inferred dats', () => { symlinkRelative: true, }, [ [`0F09A40.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', '0F09A40.rom')}`, '2f943e86'], + [`3708F2C.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', '3708F2C.rom')}`, '20891c9f'], [`612644F.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', '612644F.rom')}`, 'f7591b29'], [`65D1206.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', '65D1206.rom')}`, '20323455'], + [`92C85C9.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', '92C85C9.rom')}`, '06692159'], [`allpads.nes -> ${path.join('..', '..', 'input', '', 'roms', 'headered', 'allpads.nes')}`, '9180a163'], [`before.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', 'before.rom')}`, '0361b321'], [`best.gz|best.rom -> ${path.join('..', '..', 'input', '', 'roms', 'patchable', 'best.gz|best.rom')}`, '1e3d78cf'], diff --git a/test/modules/datInferrer.test.ts b/test/modules/datInferrer.test.ts index fff075575..bdf678bff 100644 --- a/test/modules/datInferrer.test.ts +++ b/test/modules/datInferrer.test.ts @@ -7,7 +7,7 @@ test.each([ ['test/fixtures/roms/**/*', { '7z': 5, headered: 6, - patchable: 7, + patchable: 9, rar: 5, raw: 8, roms: 5, diff --git a/test/modules/patchScanner.test.ts b/test/modules/patchScanner.test.ts index cb7681bf8..b2d34e767 100644 --- a/test/modules/patchScanner.test.ts +++ b/test/modules/patchScanner.test.ts @@ -37,10 +37,10 @@ it('should scan single files', async () => { }); it('should scan multiple files', async () => { - const expectedPatchFiles = 7; + const expectedPatchFiles = 9; await expect(createPatchScanner(['test/fixtures/patches/*']).scan()).resolves.toHaveLength(expectedPatchFiles); await expect(createPatchScanner(['test/fixtures/patches/**/*']).scan()).resolves.toHaveLength(expectedPatchFiles); - await expect(createPatchScanner(['test/fixtures/*/*.{bps,ips,ips32,ppf,rup,ups,vcdiff,xdelta}']).scan()).resolves.toHaveLength(expectedPatchFiles); + await expect(createPatchScanner(['test/fixtures/*/*.{aps,bps,ips,ips32,ppf,rup,ups,vcdiff,xdelta}']).scan()).resolves.toHaveLength(expectedPatchFiles); }); it('should scan multiple files of incorrect extensions', async () => { diff --git a/test/modules/romScanner.test.ts b/test/modules/romScanner.test.ts index 4da93858b..d98b83a28 100644 --- a/test/modules/romScanner.test.ts +++ b/test/modules/romScanner.test.ts @@ -33,7 +33,7 @@ it('should not throw on bad archives', async () => { describe('multiple files', () => { it('no files are path excluded', async () => { - const expectedRomFiles = 55; + const expectedRomFiles = 57; await expect(createRomScanner(['test/fixtures/roms']).scan()).resolves.toHaveLength(expectedRomFiles); await expect(createRomScanner(['test/fixtures/roms/*', 'test/fixtures/roms/**/*']).scan()).resolves.toHaveLength(expectedRomFiles); await expect(createRomScanner(['test/fixtures/roms/**/*']).scan()).resolves.toHaveLength(expectedRomFiles); diff --git a/test/modules/romWriter.test.ts b/test/modules/romWriter.test.ts index 48de4cc0c..3e98acac5 100644 --- a/test/modules/romWriter.test.ts +++ b/test/modules/romWriter.test.ts @@ -375,7 +375,7 @@ describe('zip', () => { test.each([ [ '**/!(*headered)/*', - ['0F09A40.zip', '612644F.zip', '65D1206.zip', 'C01173E.zip', 'KDULVQN.zip', 'before.zip', 'best.zip', 'empty.zip', 'fizzbuzz.zip', 'foobar.zip', 'loremipsum.zip', 'one.zip', 'three.zip', 'two.zip', 'unknown.zip'], + ['0F09A40.zip', '3708F2C.zip', '612644F.zip', '65D1206.zip', '92C85C9.zip', 'C01173E.zip', 'KDULVQN.zip', 'before.zip', 'best.zip', 'empty.zip', 'fizzbuzz.zip', 'foobar.zip', 'loremipsum.zip', 'one.zip', 'three.zip', 'two.zip', 'unknown.zip'], ], [ '7z/*', @@ -419,8 +419,8 @@ describe('zip', () => { test.each([ [ '**/!(*headered)/*', - ['0F09A40.zip', '612644F.zip', '65D1206.zip', 'C01173E.zip', 'KDULVQN.zip', 'before.zip', 'best.zip', 'empty.zip', 'fizzbuzz.zip', 'foobar.zip', 'loremipsum.zip', 'one.zip', 'three.zip', 'two.zip', 'unknown.zip'], - ['patchable/0F09A40.rom', 'patchable/612644F.rom', 'patchable/65D1206.rom', 'patchable/C01173E.rom', 'patchable/KDULVQN.rom', 'patchable/before.rom', 'patchable/best.gz', 'raw/empty.rom', 'raw/fizzbuzz.nes', 'raw/foobar.lnx', 'raw/loremipsum.rom', 'raw/one.rom', 'raw/three.rom', 'raw/two.rom', 'raw/unknown.rom'], + ['0F09A40.zip', '3708F2C.zip', '612644F.zip', '65D1206.zip', '92C85C9.zip', 'C01173E.zip', 'KDULVQN.zip', 'before.zip', 'best.zip', 'empty.zip', 'fizzbuzz.zip', 'foobar.zip', 'loremipsum.zip', 'one.zip', 'three.zip', 'two.zip', 'unknown.zip'], + ['patchable/0F09A40.rom', 'patchable/3708F2C.rom', 'patchable/612644F.rom', 'patchable/65D1206.rom', 'patchable/92C85C9.rom', 'patchable/C01173E.rom', 'patchable/KDULVQN.rom', 'patchable/before.rom', 'patchable/best.gz', 'raw/empty.rom', 'raw/fizzbuzz.nes', 'raw/foobar.lnx', 'raw/loremipsum.rom', 'raw/one.rom', 'raw/three.rom', 'raw/two.rom', 'raw/unknown.rom'], ], [ '7z/*', @@ -480,8 +480,10 @@ describe('zip', () => { test.each([ ['**/*', [ ['ROMWriter Test.zip|0F09A40.rom', '2f943e86'], + ['ROMWriter Test.zip|3708F2C.rom', '20891c9f'], ['ROMWriter Test.zip|612644F.rom', 'f7591b29'], ['ROMWriter Test.zip|65D1206.rom', '20323455'], + ['ROMWriter Test.zip|92C85C9.rom', '06692159'], ['ROMWriter Test.zip|allpads.nes', '9180a163'], ['ROMWriter Test.zip|before.rom', '0361b321'], ['ROMWriter Test.zip|best.rom', '1e3d78cf'], @@ -692,7 +694,7 @@ describe('extract', () => { test.each([ [ '**/!(*headered)/*', - ['0F09A40.rom', '612644F.rom', '65D1206.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.rom', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], + ['0F09A40.rom', '3708F2C.rom', '612644F.rom', '65D1206.rom', '92C85C9.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.rom', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], ], [ '7z/*', @@ -736,8 +738,8 @@ describe('extract', () => { test.each([ [ '**/!(*headered)/*', - ['0F09A40.rom', '612644F.rom', '65D1206.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.rom', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], - ['patchable/0F09A40.rom', 'patchable/612644F.rom', 'patchable/65D1206.rom', 'patchable/C01173E.rom', 'patchable/KDULVQN.rom', 'patchable/before.rom', 'patchable/best.gz', 'raw/empty.rom', 'raw/fizzbuzz.nes', 'raw/foobar.lnx', 'raw/loremipsum.rom', 'raw/one.rom', 'raw/three.rom', 'raw/two.rom', 'raw/unknown.rom'], + ['0F09A40.rom', '3708F2C.rom', '612644F.rom', '65D1206.rom', '92C85C9.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.rom', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], + ['patchable/0F09A40.rom', 'patchable/3708F2C.rom', 'patchable/612644F.rom', 'patchable/65D1206.rom', 'patchable/92C85C9.rom', 'patchable/C01173E.rom', 'patchable/KDULVQN.rom', 'patchable/before.rom', 'patchable/best.gz', 'raw/empty.rom', 'raw/fizzbuzz.nes', 'raw/foobar.lnx', 'raw/loremipsum.rom', 'raw/one.rom', 'raw/three.rom', 'raw/two.rom', 'raw/unknown.rom'], ], [ '7z/*', @@ -945,7 +947,7 @@ describe('raw', () => { test.each([ [ '**/!(*headered)/*', - ['0F09A40.rom', '612644F.rom', '65D1206.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.gz', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], + ['0F09A40.rom', '3708F2C.rom', '612644F.rom', '65D1206.rom', '92C85C9.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.gz', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], ], [ '7z/*', @@ -989,8 +991,8 @@ describe('raw', () => { test.each([ [ '**/!(*headered)/*', - ['0F09A40.rom', '612644F.rom', '65D1206.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.gz', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], - ['patchable/0F09A40.rom', 'patchable/612644F.rom', 'patchable/65D1206.rom', 'patchable/C01173E.rom', 'patchable/KDULVQN.rom', 'patchable/before.rom', 'patchable/best.gz', 'raw/empty.rom', 'raw/fizzbuzz.nes', 'raw/foobar.lnx', 'raw/loremipsum.rom', 'raw/one.rom', 'raw/three.rom', 'raw/two.rom', 'raw/unknown.rom'], + ['0F09A40.rom', '3708F2C.rom', '612644F.rom', '65D1206.rom', '92C85C9.rom', 'C01173E.rom', 'KDULVQN.rom', 'before.rom', 'best.gz', 'empty.rom', 'fizzbuzz.nes', 'foobar.lnx', 'loremipsum.rom', 'one.rom', 'three.rom', 'two.rom', 'unknown.rom'], + ['patchable/0F09A40.rom', 'patchable/3708F2C.rom', 'patchable/612644F.rom', 'patchable/65D1206.rom', 'patchable/92C85C9.rom', 'patchable/C01173E.rom', 'patchable/KDULVQN.rom', 'patchable/before.rom', 'patchable/best.gz', 'raw/empty.rom', 'raw/fizzbuzz.nes', 'raw/foobar.lnx', 'raw/loremipsum.rom', 'raw/one.rom', 'raw/three.rom', 'raw/two.rom', 'raw/unknown.rom'], ], [ '7z/*',