nitro-patcher 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-patcher",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Native Node.js Nintendo DS ROM reader, repacker and ZIP/Xdelta patcher",
5
5
  "type": "module",
6
6
  "engines": {
@@ -50,15 +50,19 @@
50
50
  "@noble/hashes": "2.4.0",
51
51
  "@types/node": "22.20.2",
52
52
  "buffer": "6.0.3",
53
- "esbuild": "0.27.3",
53
+ "esbuild": "0.28.1",
54
54
  "eslint": "10.9.1",
55
55
  "eslint-config-prettier": "10.1.8",
56
+ "eslint-plugin-import-x": "4.17.1",
57
+ "eslint-plugin-prettier": "5.5.5",
58
+ "eslint-plugin-unused-imports": "4.4.1",
56
59
  "fflate": "0.8.3",
57
60
  "globals": "16.5.0",
58
61
  "jiti": "2.7.0",
59
62
  "prettier": "3.9.6",
60
63
  "tsup": "8.5.1",
61
64
  "tsx": "4.23.13",
65
+ "type-fest": "4.41.0",
62
66
  "typescript": "5.9.3",
63
67
  "typescript-eslint": "8.70.0"
64
68
  },
@@ -68,6 +72,9 @@
68
72
  "#browser-hmac": "@noble/hashes/hmac.js",
69
73
  "#browser-aes": "@noble/ciphers/aes.js"
70
74
  },
75
+ "dependencies": {
76
+ "markdansi": "0.3.3"
77
+ },
71
78
  "scripts": {
72
79
  "clean": "tsx scripts/clean.ts",
73
80
  "build": "pnpm run build:node && pnpm run build:browser",
@@ -1 +1 @@
1
- export { Buffer } from 'buffer';
1
+ export { Buffer } from "buffer";
package/src/browser.ts CHANGED
@@ -1,5 +1,28 @@
1
- import { NDSFile } from './nitro/nds-file';
2
- import { patchBuffer as patch } from './nitro-patch-helper';
1
+ import { NDSFile } from "./nitro/nds-file";
2
+ import {
3
+ extractZipEntry as extractEntry,
4
+ patchBuffer as patch,
5
+ readPatchInfo as readInfo,
6
+ readPatchMetadata as readMetadata,
7
+ readPatchReadme as readReadme,
8
+ } from "./nitro-patch-helper";
9
+
10
+ export interface PatchMetadata {
11
+ id?: string;
12
+ author?: string;
13
+ name?: string;
14
+ homepage?: string;
15
+ version?: string;
16
+ isBeta?: boolean;
17
+ }
18
+ export interface PatchReadme {
19
+ format: "markdown" | "plaintext";
20
+ content: string;
21
+ }
22
+ export interface PatchInfo {
23
+ metadata: PatchMetadata | null;
24
+ readme: PatchReadme | null;
25
+ }
3
26
 
4
27
  export interface BrowserPatchOptions {
5
28
  maxOutputSize?: number;
@@ -9,7 +32,7 @@ export interface BrowserPatchOptions {
9
32
  }
10
33
  export interface BrowserPatchResult {
11
34
  buffer: Uint8Array<ArrayBuffer>;
12
- returnValue: 'SUCCESS' | 'MD5_MISMATCH';
35
+ returnValue: "SUCCESS" | "MD5_MISMATCH";
13
36
  inputMd5: string;
14
37
  outputMd5: string;
15
38
  }
@@ -56,5 +79,27 @@ export const patchBuffer = (
56
79
  outputMd5: result.outputMd5,
57
80
  };
58
81
  };
59
- export const PatchHelper = Object.freeze({ patchBuffer });
82
+ /** Read and validate metadata.json from a patch package entirely in memory. */
83
+ export const readPatchMetadata = (archive: Uint8Array, options: BrowserPatchOptions = {}): PatchMetadata | null =>
84
+ readMetadata(archive, options);
85
+ /** Read a root README.md or README.txt from a patch package entirely in memory. */
86
+ export const readPatchReadme = (archive: Uint8Array, options: BrowserPatchOptions = {}): PatchReadme | null =>
87
+ readReadme(archive, options);
88
+ /** Read metadata and README content while parsing the patch package only once. */
89
+ export const readPatchInfo = (archive: Uint8Array, options: BrowserPatchOptions = {}): PatchInfo =>
90
+ readInfo(archive, options);
91
+ /** Extract one file from a ZIP archive entirely in memory. */
92
+ export const extractZipEntry = (
93
+ archive: Uint8Array,
94
+ entryName: string,
95
+ options: BrowserPatchOptions = {},
96
+ ): Uint8Array<ArrayBuffer> => new Uint8Array(extractEntry(archive, entryName, options));
97
+
98
+ export const PatchHelper = Object.freeze({
99
+ extractZipEntry,
100
+ patchBuffer,
101
+ readPatchInfo,
102
+ readPatchMetadata,
103
+ readPatchReadme,
104
+ });
60
105
  export const NitroHelper = Object.freeze({ inspectRom });
package/src/cli.ts CHANGED
@@ -1,16 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import { mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
3
- import { basename, dirname, join, resolve } from 'node:path';
4
- import { parseArgs } from 'node:util';
5
- import { patchIt } from './nitro-patch-helper';
2
+ import { render } from "markdansi";
3
+ import { mkdtemp, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { parseArgs } from "node:util";
6
+
7
+ import { type PatchMetadata, patchBuffer, readPatchInfo } from "./nitro-patch-helper";
6
8
 
7
9
  const help = `nitro-patcher — 原生 Node.js NDS ROM 补丁工具
8
10
 
9
- 用法:nitro-patcher [--json] <原始ROM> <补丁ZIP> <输出ROM>
11
+ 用法:
12
+ nitro-patcher [--json] [--dry-run] <原始ROM> <补丁ZIP> [输出ROM]
13
+ nitro-patcher [--json] [--dry-run] --input <原始ROM> --patch <补丁ZIP> [--output <输出ROM>]
10
14
 
11
- --json 输出 JSON 状态和 MD5
12
- -h, --help 显示帮助
13
- -v, --version 显示版本`;
15
+ --json 输出 JSON 状态和 MD5
16
+ --dry-run 完整执行补丁流程,但不写入输出 ROM
17
+ -i, --input 原始 ROM 路径
18
+ -p, --patch 补丁 ZIP 路径
19
+ -o, --output 输出 ROM 路径;试运行时可省略
20
+ -h, --help 显示帮助
21
+ -v, --version 显示版本`;
14
22
 
15
23
  const sameFile = async (input: string, output: string): Promise<boolean> => {
16
24
  if (resolve(input) === resolve(output)) return true;
@@ -19,18 +27,55 @@ const sameFile = async (input: string, output: string): Promise<boolean> => {
19
27
  const destination = await stat(output);
20
28
  return original.dev === destination.dev && original.ino === destination.ino;
21
29
  } catch (error) {
22
- if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) throw error;
30
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
23
31
  return false;
24
32
  }
25
33
  };
26
34
 
35
+ const safeTerminalText = (value: string): string => {
36
+ const characters: string[] = [];
37
+ for (const character of value.replaceAll("\r\n", "\n").replaceAll("\r", "\n")) {
38
+ const code = character.codePointAt(0)!;
39
+ if (
40
+ (code < 0x20 && code !== 0x09 && code !== 0x0a) ||
41
+ (code >= 0x7f && code <= 0x9f) ||
42
+ (code >= 0x202a && code <= 0x202e) ||
43
+ (code >= 0x2066 && code <= 0x2069)
44
+ )
45
+ continue;
46
+ characters.push(character);
47
+ }
48
+ return characters.join("");
49
+ };
50
+
51
+ const formatMetadata = (metadata: PatchMetadata): string => {
52
+ const labels = {
53
+ author: "作者",
54
+ name: "名称",
55
+ homepage: "主页",
56
+ version: "版本",
57
+ } as const;
58
+ const lines: string[] = [];
59
+ for (const field of ["author", "name", "homepage", "version"] as const) {
60
+ const value = metadata[field];
61
+ if (value === undefined) continue;
62
+ const suffix = field === "version" && metadata.isBeta ? "(测试版)" : "";
63
+ lines.push(`- ${labels[field]}:${safeTerminalText(value).replaceAll("\n", " ")}${suffix}`);
64
+ }
65
+ return lines.join("\n");
66
+ };
67
+
27
68
  const main = async (): Promise<void> => {
28
69
  const { values, positionals } = parseArgs({
29
70
  allowPositionals: true,
30
71
  options: {
31
- help: { type: 'boolean', short: 'h' },
32
- version: { type: 'boolean', short: 'v' },
33
- json: { type: 'boolean' },
72
+ help: { type: "boolean", short: "h" },
73
+ version: { type: "boolean", short: "v" },
74
+ json: { type: "boolean" },
75
+ "dry-run": { type: "boolean" },
76
+ input: { type: "string", short: "i" },
77
+ patch: { type: "string", short: "p" },
78
+ output: { type: "string", short: "o" },
34
79
  },
35
80
  });
36
81
  if (values.help) {
@@ -38,36 +83,77 @@ const main = async (): Promise<void> => {
38
83
  return;
39
84
  }
40
85
  if (values.version) {
41
- const metadata: unknown = JSON.parse(
42
- await readFile(new URL('../package.json', import.meta.url), 'utf8'),
43
- );
44
- if (
45
- !metadata ||
46
- typeof metadata !== 'object' ||
47
- !('version' in metadata) ||
48
- typeof metadata.version !== 'string'
49
- )
50
- throw new Error('Invalid package version metadata');
86
+ const metadata: unknown = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
87
+ if (!metadata || typeof metadata !== "object" || !("version" in metadata) || typeof metadata.version !== "string")
88
+ throw new Error("Invalid package version metadata");
51
89
  console.log(metadata.version);
52
90
  return;
53
91
  }
54
- if (positionals.length !== 3) throw new Error(help);
55
- const [original, patch, output] = positionals;
56
- if ((await sameFile(original, output)) || (await sameFile(patch, output)))
57
- throw new Error('输出路径不能覆盖原始 ROM 或补丁包。');
58
- const result = await patchIt(original, patch);
59
- const destination = join(await realpath(dirname(resolve(output))), basename(output));
60
- const temporary = await mkdtemp(join(dirname(destination), '.nitro-patcher-'));
61
- try {
62
- const file = join(temporary, 'output.nds');
63
- await writeFile(file, result.buffer);
64
- await rename(file, destination);
65
- } finally {
66
- await rm(temporary, { recursive: true, force: true });
92
+ const dryRun = values["dry-run"] ?? false;
93
+ const usesNamedPaths = values.input !== undefined || values.patch !== undefined || values.output !== undefined;
94
+ if (usesNamedPaths && positionals.length > 0) throw new Error(help);
95
+ if (
96
+ (usesNamedPaths && (!values.input || !values.patch || (!dryRun && !values.output))) ||
97
+ (!usesNamedPaths &&
98
+ ((!dryRun && positionals.length !== 3) || (dryRun && (positionals.length < 2 || positionals.length > 3))))
99
+ )
100
+ throw new Error(help);
101
+ const [original, patch, output] = usesNamedPaths ? [values.input, values.patch, values.output] : positionals;
102
+ if (!original || !patch) throw new Error(help);
103
+ if (!dryRun) {
104
+ if (!output) throw new Error(help);
105
+ if ((await sameFile(original, output)) || (await sameFile(patch, output)))
106
+ throw new Error("输出路径不能覆盖原始 ROM 或补丁包。");
107
+ }
108
+ const [originalBytes, patchBytes] = await Promise.all([readFile(original), readFile(patch)]);
109
+ const info = readPatchInfo(patchBytes);
110
+ const result = patchBuffer(originalBytes, patchBytes);
111
+ if (!dryRun) {
112
+ if (!output) throw new Error(help);
113
+ const destination = join(await realpath(dirname(resolve(output))), basename(output));
114
+ const temporary = await mkdtemp(join(dirname(destination), ".nitro-patcher-"));
115
+ try {
116
+ const file = join(temporary, "output.nds");
117
+ await writeFile(file, result.buffer);
118
+ await rename(file, destination);
119
+ } finally {
120
+ await rm(temporary, { recursive: true, force: true });
121
+ }
67
122
  }
68
123
  const { returnValue, inputMd5, outputMd5 } = result;
69
- if (values.json) console.log(JSON.stringify({ returnValue, inputMd5, outputMd5 }));
70
- else console.log(`${returnValue}\n\n原始 ROM 的 MD5:${inputMd5}\n生成 ROM 的 MD5:${outputMd5}`);
124
+ if (values.json) {
125
+ console.log(
126
+ JSON.stringify({
127
+ returnValue,
128
+ inputMd5,
129
+ outputMd5,
130
+ ...info,
131
+ ...(dryRun && { dryRun: true }),
132
+ }),
133
+ );
134
+ return;
135
+ }
136
+
137
+ const sections: string[] = [];
138
+ if (info.metadata) {
139
+ const metadata = formatMetadata(info.metadata);
140
+ if (metadata) sections.push(`补丁元数据:\n\n${metadata}`);
141
+ }
142
+ if (info.readme) {
143
+ const content = safeTerminalText(info.readme.content);
144
+ const rendered =
145
+ info.readme.format === "markdown"
146
+ ? render(content, {
147
+ width: process.stdout.columns ?? 80,
148
+ color: Boolean(process.stdout.isTTY),
149
+ hyperlinks: false,
150
+ })
151
+ : content;
152
+ sections.push(`补丁说明:\n\n${rendered.trim()}`);
153
+ }
154
+ if (dryRun) sections.push("试运行完成,未写入输出 ROM。");
155
+ sections.push(`${returnValue}\n\n原始 ROM 的 MD5:${inputMd5}\n生成 ROM 的 MD5:${outputMd5}`);
156
+ console.log(sections.join("\n\n"));
71
157
  };
72
158
 
73
159
  main().catch((error: unknown) => {
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './nitro-helper';
2
- export * from './nitro-patch-helper';
1
+ export * from "./nitro-helper";
2
+ export * from "./nitro-patch-helper";
@@ -1,16 +1,16 @@
1
1
  // Port of NitroHelper/Banner.cs. Copyright Xzonn and upstream contributors, GPL-3.0.
2
- import { bytes, range } from './binary';
3
- import { crc16 } from './crc';
2
+ import { bytes, range } from "./binary";
3
+ import { crc16 } from "./crc";
4
4
 
5
5
  const titles = [
6
- 'japaneseTitle',
7
- 'englishTitle',
8
- 'frenchTitle',
9
- 'germanTitle',
10
- 'italianTitle',
11
- 'spanishTitle',
12
- 'chineseTitle',
13
- 'koreanTitle',
6
+ "japaneseTitle",
7
+ "englishTitle",
8
+ "frenchTitle",
9
+ "germanTitle",
10
+ "italianTitle",
11
+ "spanishTitle",
12
+ "chineseTitle",
13
+ "koreanTitle",
14
14
  ] as const;
15
15
  export class Banner {
16
16
  version: number;
@@ -18,21 +18,20 @@ export class Banner {
18
18
  bannerCRC: boolean;
19
19
  tileData: Buffer;
20
20
  palette: Buffer;
21
- japaneseTitle = '';
22
- englishTitle = '';
23
- frenchTitle = '';
24
- germanTitle = '';
25
- italianTitle = '';
26
- spanishTitle = '';
27
- chineseTitle = '';
28
- koreanTitle = '';
21
+ japaneseTitle = "";
22
+ englishTitle = "";
23
+ frenchTitle = "";
24
+ germanTitle = "";
25
+ italianTitle = "";
26
+ spanishTitle = "";
27
+ chineseTitle = "";
28
+ koreanTitle = "";
29
29
  constructor(input: Uint8Array, offset = 0, hardSize = 0) {
30
- const data = bytes(input, 'banner');
31
- this.version = range(data, offset, 2, 'banner').readUInt16LE();
30
+ const data = bytes(input, "banner");
31
+ this.version = range(data, offset, 2, "banner").readUInt16LE();
32
32
  let size = this.getDefSize(hardSize);
33
- if (this.version >>> 8 === 1)
34
- size = hardSize > 0 && hardSize !== 0xffffffff ? Math.min(hardSize, 0x23c0) : 0x23c0;
35
- this.raw = Buffer.from(range(data, offset, size, 'banner'));
33
+ if (this.version >>> 8 === 1) size = hardSize > 0 && hardSize !== 0xffffffff ? Math.min(hardSize, 0x23c0) : 0x23c0;
34
+ this.raw = Buffer.from(range(data, offset, size, "banner"));
36
35
  this.bannerCRC = crc16(this.raw.subarray(0x20, 0x840)) === this.raw.readUInt16LE(2);
37
36
  this.tileData = Buffer.from(this.raw.subarray(0x20, 0x220));
38
37
  this.palette = Buffer.from(this.raw.subarray(0x220, 0x240));
@@ -41,9 +40,9 @@ export class Banner {
41
40
  i < this.titleCount
42
41
  ? this.raw
43
42
  .subarray(0x240 + i * 0x100, 0x340 + i * 0x100)
44
- .toString('utf16le')
45
- .split('\0')[0]
46
- : '';
43
+ .toString("utf16le")
44
+ .split("\0")[0]
45
+ : "";
47
46
  }
48
47
  get titleCount() {
49
48
  return this.version >= 3 ? 8 : this.version >= 2 ? 7 : 6;
@@ -57,14 +56,12 @@ export class Banner {
57
56
  toBuffer() {
58
57
  const output = Buffer.from(this.raw);
59
58
  output.writeUInt16LE(this.version);
60
- if (this.tileData.length !== 512 || this.palette.length !== 32)
61
- throw new RangeError('Invalid banner icon size');
59
+ if (this.tileData.length !== 512 || this.palette.length !== 32) throw new RangeError("Invalid banner icon size");
62
60
  this.tileData.copy(output, 0x20);
63
61
  this.palette.copy(output, 0x220);
64
62
  for (let i = 0; i < this.titleCount; i++) {
65
- const text = Buffer.from(this[titles[i]], 'utf16le');
66
- if (text.length > 256)
67
- throw new RangeError(`Banner ${titles[i]} exceeds 128 UTF-16 code units`);
63
+ const text = Buffer.from(this[titles[i]], "utf16le");
64
+ if (text.length > 256) throw new RangeError(`Banner ${titles[i]} exceeds 128 UTF-16 code units`);
68
65
  output.fill(0, 0x240 + i * 256, 0x340 + i * 256);
69
66
  text.copy(output, 0x240 + i * 256);
70
67
  }
@@ -1,16 +1,11 @@
1
1
  export const DEFAULT_MAX_SIZE = 0x40000000;
2
2
 
3
- export const bytes = (value: Uint8Array, name = 'input'): Buffer => {
3
+ export const bytes = (value: Uint8Array, name = "input"): Buffer => {
4
4
  if (!(value instanceof Uint8Array)) throw new TypeError(`${name} must be a Buffer or Uint8Array`);
5
5
  return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
6
6
  };
7
7
 
8
- export const range = (
9
- buffer: Buffer,
10
- offset: number,
11
- size: number,
12
- name = 'binary data',
13
- ): Buffer => {
8
+ export const range = (buffer: Buffer, offset: number, size: number, name = "binary data"): Buffer => {
14
9
  if (
15
10
  !Number.isSafeInteger(offset) ||
16
11
  !Number.isSafeInteger(size) ||
@@ -18,16 +13,12 @@ export const range = (
18
13
  size < 0 ||
19
14
  offset + size > buffer.length
20
15
  ) {
21
- throw new RangeError(
22
- `Invalid ${name} range: offset=${offset}, size=${size}, available=${buffer.length}`,
23
- );
16
+ throw new RangeError(`Invalid ${name} range: offset=${offset}, size=${size}, available=${buffer.length}`);
24
17
  }
25
18
  return buffer.subarray(offset, offset + size);
26
19
  };
27
20
 
28
- export const align = (value: number, alignment = 0x200): number => {
29
- return Math.ceil(value / alignment) * alignment;
30
- };
21
+ export const align = (value: number, alignment = 0x200): number => Math.ceil(value / alignment) * alignment;
31
22
 
32
23
  /** Seekable, bounded in-memory writer; unwritten gaps have MemoryStream's zero fill. */
33
24
  export class BinaryWriter {
@@ -37,7 +28,7 @@ export class BinaryWriter {
37
28
  length: number;
38
29
  constructor(initialSize = 65536, maxSize = DEFAULT_MAX_SIZE) {
39
30
  if (!Number.isSafeInteger(maxSize) || maxSize < 1 || maxSize > 0xffffffff)
40
- throw new RangeError('Invalid output size limit');
31
+ throw new RangeError("Invalid output size limit");
41
32
  this.maxSize = maxSize;
42
33
  this.buffer = Buffer.alloc(Math.min(initialSize, maxSize));
43
34
  this.position = 0;
@@ -55,8 +46,7 @@ export class BinaryWriter {
55
46
 
56
47
  write(data: Uint8Array) {
57
48
  data = bytes(data);
58
- if (!Number.isSafeInteger(this.position) || this.position < 0)
59
- throw new RangeError('Invalid writer position');
49
+ if (!Number.isSafeInteger(this.position) || this.position < 0) throw new RangeError("Invalid writer position");
60
50
  this.ensure(this.position + data.length);
61
51
  bytes(data).copy(this.buffer, this.position);
62
52
  this.position += data.length;
package/src/nitro/blz.ts CHANGED
@@ -7,7 +7,7 @@ export interface Arm9Header {
7
7
  // Native port of Xzonn/BlzHelper BLZ.cs (MIT), including overwrite-safe prefix.
8
8
  // Compression returns an empty Buffer when no smaller representation exists.
9
9
  export const decompressBLZ = (input: Buffer, maxSize = 0x10000000): Buffer => {
10
- if (input.length < 8) throw new Error('BLZ footer is truncated');
10
+ if (input.length < 8) throw new Error("BLZ footer is truncated");
11
11
  const diff = input.readUInt32LE(input.length - 4);
12
12
  const packed = input.readUInt32LE(input.length - 8),
13
13
  header = packed >>> 24,
@@ -15,7 +15,7 @@ export const decompressBLZ = (input: Buffer, maxSize = 0x10000000): Buffer => {
15
15
  const size = input.length + diff,
16
16
  prefix = input.length - length;
17
17
  if (header < 8 || header > length || length > input.length || size > maxSize)
18
- throw new Error('Invalid BLZ size or footer');
18
+ throw new Error("Invalid BLZ size or footer");
19
19
  const out = Buffer.alloc(size);
20
20
  input.copy(out, 0, 0, prefix);
21
21
  let src = input.length - header,
@@ -24,16 +24,15 @@ export const decompressBLZ = (input: Buffer, maxSize = 0x10000000): Buffer => {
24
24
  const flags = input[--src];
25
25
  for (let bit = 128; bit && src > prefix; bit >>= 1) {
26
26
  if (!(flags & bit)) {
27
- if (dst <= prefix) throw new Error('BLZ output overflow');
27
+ if (dst <= prefix) throw new Error("BLZ output overflow");
28
28
  out[--dst] = input[--src];
29
29
  } else {
30
- if (src - 2 < prefix) throw new Error('Truncated BLZ reference');
30
+ if (src - 2 < prefix) throw new Error("Truncated BLZ reference");
31
31
  const high = input[--src],
32
32
  low = input[--src];
33
33
  const count = (high >>> 4) + 3,
34
34
  offset = (((high & 15) << 8) | low) + 3;
35
- if (dst - count < prefix || dst - 1 + offset >= size)
36
- throw new Error('Invalid BLZ reference');
35
+ if (dst - count < prefix || dst - 1 + offset >= size) throw new Error("Invalid BLZ reference");
37
36
  for (let j = 0; j < count; j++) {
38
37
  --dst;
39
38
  out[dst] = out[dst + offset];
@@ -41,12 +40,12 @@ export const decompressBLZ = (input: Buffer, maxSize = 0x10000000): Buffer => {
41
40
  }
42
41
  }
43
42
  }
44
- if (dst !== prefix) throw new Error('BLZ decompressed size mismatch');
43
+ if (dst !== prefix) throw new Error("BLZ decompressed size mismatch");
45
44
  return out;
46
45
  };
47
46
 
48
47
  export const compressBLZ = (input: Buffer): Buffer => {
49
- if (input.length > 0xffffff) throw new Error('BLZ input exceeds 24-bit format limit');
48
+ if (input.length > 0xffffff) throw new Error("BLZ input exceeds 24-bit format limit");
50
49
  const temp = Buffer.alloc(input.length);
51
50
  let src = input.length,
52
51
  dst = input.length;
@@ -131,31 +130,24 @@ export const compressBLZ = (input: Buffer): Buffer => {
131
130
  return out;
132
131
  };
133
132
 
134
- export const decompressArm9 = (
135
- data: Buffer,
136
- header: Arm9Header,
137
- ): { compressed: boolean; data: Buffer } => {
138
- if (data.length < 0x18) throw new Error('ARM9 data too short');
133
+ export const decompressArm9 = (data: Buffer, header: Arm9Header): { compressed: boolean; data: Buffer } => {
134
+ if (data.length < 0x18) throw new Error("ARM9 data too short");
139
135
  const nitro = data.readUInt32LE(0xc) === 0xdec00621 ? 12 : 0;
140
136
  const init = header.reserved2.readUInt32LE(0) & 0x3fff;
141
137
  const ptr = init ? data.readUInt32LE(init + 0x14) : header.ARM9ramAddress + header.ARM9size;
142
- const compressed =
143
- ptr > header.ARM9ramAddress && ptr + nitro >= header.ARM9ramAddress + data.length;
138
+ const compressed = ptr > header.ARM9ramAddress && ptr + nitro >= header.ARM9ramAddress + data.length;
144
139
  return {
145
140
  compressed,
146
141
  data: compressed
147
- ? Buffer.concat([
148
- decompressBLZ(data.subarray(0, data.length - nitro)),
149
- data.subarray(data.length - nitro),
150
- ])
142
+ ? Buffer.concat([decompressBLZ(data.subarray(0, data.length - nitro)), data.subarray(data.length - nitro)])
151
143
  : Buffer.from(data),
152
144
  };
153
145
  };
154
146
  export const compressArm9 = (data: Buffer, header: Arm9Header, postSize = 0): Buffer => {
155
- if (data.length < 0x4000) throw new Error('ARM9 secure area is truncated');
147
+ if (data.length < 0x4000) throw new Error("ARM9 secure area is truncated");
156
148
  const nitro = data.readUInt32LE(0xc) === 0xdec00621 ? 12 : 0;
157
149
  const packed = compressBLZ(data.subarray(0x4000, data.length - nitro));
158
- if (!packed.length) throw new Error('ARM9 BLZ compression produced no smaller representation');
150
+ if (!packed.length) throw new Error("ARM9 BLZ compression produced no smaller representation");
159
151
  const out = Buffer.concat([data.subarray(0, 0x4000), packed, data.subarray(data.length - nitro)]);
160
152
  const init = header.reserved2.readUInt32LE(0) & 0x3fff;
161
153
  if (init) out.writeUInt32LE(out.length - postSize + header.ARM9ramAddress, init + 0x14);