silphscope 1.2.27 → 1.2.29

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": "silphscope",
3
- "version": "1.2.27",
3
+ "version": "1.2.29",
4
4
  "description": "A firered/leafgreen ROM asset extractor for use in web applications",
5
5
  "main": "main.js",
6
6
  "exports": {
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  // I have an idea but it needs math D:
2
5
 
3
6
  export const firered = {
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  import { firered } from "../rom-configs/firered.js";
2
5
 
3
6
  function detectRomCode(rom) {
@@ -21,6 +21,7 @@ const assets = loadDefaultJson("../graphics-maps/fr-graphic-map.json");
21
21
  const mons = loadDefaultJson("../mon-data/monData.json");
22
22
  const icons = loadDefaultJson("../item-data/itemData.json");
23
23
  const trainers = loadDefaultJson("../trainer-data/trainerData.json");
24
+ const trainersBack = loadDefaultJson("../trainer-data/trainerBackData.json");
24
25
 
25
26
  export async function renderAllMons(rom, options = {}) {
26
27
  if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
@@ -40,34 +41,13 @@ export async function renderAllMons(rom, options = {}) {
40
41
  const reader = new RomReader(rom, config);
41
42
 
42
43
  for (const monName of Object.keys(providedMons)) {
43
- await renderMon(monName, providedMons, reader, rom, {
44
- side: "front",
45
- variant: "normal",
44
+ await renderMon(monName, providedMons, reader, rom, { // hopefully this is faster since we are no longer calling the function 4 times lol
45
+ side: ["front", "back"],
46
+ variant: ["normal", "shiny"],
46
47
  icon,
47
48
  footprint,
48
49
  outputDir,
49
50
  });
50
- await renderMon(monName, providedMons, reader, rom, {
51
- side: "front",
52
- variant: "shiny",
53
- icon: false,
54
- footprint: false,
55
- outputDir,
56
- });
57
- await renderMon(monName, providedMons, reader, rom, {
58
- side: "back",
59
- variant: "normal",
60
- icon: false,
61
- footprint: false,
62
- outputDir,
63
- });
64
- await renderMon(monName, providedMons, reader, rom, {
65
- side: "back",
66
- variant: "shiny",
67
- icon: false,
68
- footprint: false,
69
- outputDir,
70
- });
71
51
  console.log(`Done: ${monName}`);
72
52
  }
73
53
  }
@@ -100,16 +80,20 @@ export async function renderAllTrainers(rom, options = {}) {
100
80
  }
101
81
 
102
82
  const {
103
- assets: providedAssets = assets,
104
83
  trainers: providedTrainers = trainers,
84
+ trainersBack: providedBackTrainers = trainersBack,
105
85
  trainerBackPics = true,
106
86
  outputDir = "./out",
107
87
  } = options;
108
88
 
109
89
  fs.mkdirSync(outputDir, { recursive: true });
90
+ const backTrainerName = Object.keys(providedBackTrainers);
91
+
92
+ const config = getRomConfig(rom);
93
+ const reader = new RomReader(rom, config);
110
94
 
111
95
  for (const trainerName of Object.keys(providedTrainers)) {
112
- await renderTrainer(trainerName, providedTrainers, providedAssets, rom, {
96
+ await renderTrainer(trainerName, providedTrainers, backTrainerName, providedBackTrainers, reader, rom, {
113
97
  trainerBackPics,
114
98
  outputDir
115
99
  });
@@ -1,17 +1,15 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  export function resolveItemIconObject(item, reader, itemName, gfxOrPal) {
2
5
  const table = reader.getTable("itemIconTable");
3
6
  const entrySize = 8; // why did I make this variable... hmmm... I really can't remember... eh it doesn't do any harm other than being kinda useless since we could just pass 8 as is...
4
7
  const entryOffset = table + item.index * entrySize;
5
- const rawPtr = reader.readU32(entryOffset);
6
- console.log(`${itemName} raw table value: 0x${rawPtr.toString(16)}`);
7
- const fileOffset = rawPtr - 0x08000000;
8
- console.log(`${itemName} file offset: 0x${fileOffset.toString(16)}`);
9
8
  const iconPtr = reader.readPointer(entryOffset);
10
9
  const palettePtr = reader.readPointer(entryOffset + 4);
11
10
  let finalPtr;
12
11
  if (gfxOrPal === "gfx") {
13
12
  finalPtr = iconPtr;
14
- console.log(`gfx ${itemName} asset offset should be 0x${finalPtr.toString(16)}`);
15
13
  return {
16
14
  name: `item_${itemName}_${gfxOrPal}`,
17
15
  offset: finalPtr,
@@ -19,7 +17,6 @@ export function resolveItemIconObject(item, reader, itemName, gfxOrPal) {
19
17
  }
20
18
  } else if (gfxOrPal === "pal") {
21
19
  finalPtr = palettePtr;
22
- console.log(`pal ${itemName} asset offset should be 0x${finalPtr.toString(16)}`);
23
20
  return {
24
21
  name: `item_${itemName}_${gfxOrPal}`,
25
22
  offset: finalPtr,
@@ -30,6 +30,9 @@ export async function renderMon(monName, mons, reader, rom, options = {}) {
30
30
  throw new TypeError("renderMon(..., rom) requires a ROM Buffer/Uint8Array");
31
31
  }
32
32
 
33
+ const sides = Array.isArray(side) ? side : [side];
34
+ const variants = Array.isArray(variant) ? variant : [variant];
35
+
33
36
  if (icon === true) {
34
37
  await renderMonIcon(monName, mons, reader, rom, { outputDir });
35
38
  }
@@ -42,35 +45,45 @@ export async function renderMon(monName, mons, reader, rom, options = {}) {
42
45
  throw new Error(`Missing mon: ${monName}`);
43
46
  }
44
47
 
45
- const monPic = resolveMonSprite(mon, reader, monName, side); // I wonder if this will work :O
46
- const monPal = resolveMonPalette(mon, reader, monName, variant);
47
-
48
- if (!monPic || !monPal) {
49
- throw new Error(`Missing assets for: ${monName}`);
48
+ const picCache = {};
49
+ const palCache = {};
50
+ for (const side of sides) {
51
+ const monPic = resolveMonSprite(mon, reader, monName, side);
52
+ if (!monPic) throw new Error(`Missing sprite data for: ${monName} ${side}`);
53
+ picCache[side] = extract(monPic, rom).data;
54
+ }
55
+ for (const variant of variants) {
56
+ const monPal = resolveMonPalette(mon, reader, monName, variant);
57
+ if (!monPal) throw new Error(`Missing palette data for: ${monName} ${variant}`);
58
+ palCache[variant] = extract(monPal, rom).data;
50
59
  }
51
60
 
52
- const monImageData = extract(monPic, rom);
53
- const rawMonPalData = extract(monPal, rom);
61
+ const results = [];
54
62
  const width = 64;
55
63
  const height = 64;
64
+ for (const side of sides) {
65
+ for (const variant of variants) {
66
+ const image = render4bppImage({
67
+ tileData: picCache[side],
68
+ paletteData: palCache[variant],
69
+ width,
70
+ height,
71
+ });
56
72
 
57
- const image = render4bppImage({
58
- tileData: monImageData.data,
59
- paletteData: rawMonPalData.data,
60
- width,
61
- height,
62
- });
73
+ const png = new PNG({ width, height });
74
+ png.data = image;
75
+ const pngBuffer = await streamToBuffer(png.pack());
63
76
 
64
- const png = new PNG({ width, height });
65
- png.data = image;
66
- const pngBuffer = await streamToBuffer(png.pack());
77
+ if (outputDir) {
78
+ const dir = `${outputDir}/${monName}`;
79
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); // I suppose it is better to have this out of the loop for performance but erm I would say its fine for now :p
80
+ const fileName = `${dir}/${side}${variant === "shiny" ? "_shiny" : ""}.png`;
81
+ fs.writeFileSync(fileName, pngBuffer);
82
+ }
67
83
 
68
- if (outputDir) {
69
- const dir = `${outputDir}/${monName}`;
70
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
71
- const fileName = `${dir}/${side}${variant === "shiny" ? "_shiny" : ""}.png`;
72
- fs.writeFileSync(fileName, pngBuffer);
84
+ results.push(pngBuffer);
85
+ }
73
86
  }
74
87
 
75
- return pngBuffer;
88
+ return results;
76
89
  }
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  export function resolveMonFootprint(mon, reader, monName) {
2
5
  const table = reader.getTable("monFootprintTable");
3
6
  const entryOffset = table + mon.index * 4;
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  export function resolveMonIconPalette(mon, reader, monName) { // I kinda think this might be wrong... but erm who knows :o maybe I got it right first try lol
2
5
  const indexTable = reader.getTable("monIconPaletteIndices");
3
6
  const paletteIndex = reader.readU8(indexTable + mon.index);
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  export function resolveMonIcon(mon, reader, monName) {
2
5
  const table = reader.getTable("monIconTable");
3
6
  const entryOffset = table + mon.index * 4;
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  export function resolveMonPalette(mon, romReader, monName, variant) {
2
5
  const tableName = variant === "shiny"
3
6
  ? "monShinyPalettes"
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  // so the idea of "resolvers" is to essentially replace this line in the asset extraction:
2
5
  // const monPic = assets.find(a => a.name === picName);
3
6
  // of course though thats for the mon stuff specifically however!
@@ -5,6 +5,8 @@ import { PNG } from "pngjs";
5
5
  import fs from "fs";
6
6
  import { extract } from "../extract.js";
7
7
  import { render4bppImage } from "../render-4bpp-image.js";
8
+ import { resolveTrainerBackPic } from "./resolvers/trainer-back-pic-resolver.js";
9
+ import { resolveTrainerBackPicPal } from "./resolvers/trainer-back-pic-pal-resolver.js"
8
10
 
9
11
  const streamToBuffer = (stream) => new Promise((resolve, reject) => {
10
12
  const chunks = [];
@@ -13,7 +15,7 @@ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
13
15
  stream.on("error", reject);
14
16
  });
15
17
 
16
- export async function renderTrainerBackPic(trainerName, trainers, assets, rom, options = {}) {
18
+ export async function renderTrainerBackPic(trainerName, trainers, reader, rom, options = {}) {
17
19
  const { outputDir = null } = options;
18
20
 
19
21
  if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
@@ -25,20 +27,8 @@ export async function renderTrainerBackPic(trainerName, trainers, assets, rom, o
25
27
  throw new Error(`Missing trainer entry for ${trainerName}`);
26
28
  }
27
29
 
28
- if (trainerName !== "OLDMAN" &&
29
- trainerName !== "POKEDUDE" &&
30
- trainerName !== "RS_BRENDAN_1" &&
31
- trainerName !== "RS_BRENDAN_2" &&
32
- trainerName !== "RS_MAY_1" &&
33
- trainerName !== "RS_MAY_2" && // I wonder if there is a more compact way to write this without using a array and `.includes`
34
- trainerName !== "RED" &&
35
- trainerName !== "LEAF"
36
- ) {
37
- return;
38
- }
39
-
40
- const trainerBackPic = assets.find(a => a.name === trainer.BackPic);
41
- const trainerBackPal = assets.find(a => a.name === trainer.BackPal);
30
+ const trainerBackPic = resolveTrainerBackPic(trainer, reader, trainerName);
31
+ const trainerBackPal = resolveTrainerBackPicPal(trainer, reader, trainerName);
42
32
  if (!trainerBackPic || !trainerBackPal) {
43
33
  throw new Error(`Missing assets for: ${trainerName}`);
44
34
  }
@@ -6,6 +6,8 @@ import { render4bppImage } from "../render-4bpp-image.js";
6
6
  import { PNG } from "pngjs";
7
7
  import fs from "fs";
8
8
  import { renderTrainerBackPic } from "./render-trainer-back-pics.js";
9
+ import { resolveTrainerFrontPic } from "./resolvers/trainer-front-pic-resolver.js";
10
+ import { resolveTrainerFrontPicPal } from "./resolvers/trainer-front-pic-pal-resolver.js";
9
11
 
10
12
  const streamToBuffer = (stream) => new Promise((resolve, reject) => {
11
13
  const chunks = [];
@@ -14,7 +16,7 @@ const streamToBuffer = (stream) => new Promise((resolve, reject) => {
14
16
  stream.on("error", reject);
15
17
  });
16
18
 
17
- export async function renderTrainer(trainerName, trainers, assets, rom, options = {}) {
19
+ export async function renderTrainer(trainerName, trainers, backTrainerName, backTrainers, reader, rom, options = {}) {
18
20
  const {
19
21
  trainerBackPics = false,
20
22
  outputDir = null,
@@ -28,13 +30,10 @@ export async function renderTrainer(trainerName, trainers, assets, rom, options
28
30
  throw new Error(`Missing Trainer: ${trainerName}`);
29
31
  }
30
32
  if (trainerBackPics) {
31
- renderTrainerBackPic(trainerName, trainers, assets, rom, { outputDir });
33
+ renderTrainerBackPic(backTrainerName, backTrainers, reader, rom, { outputDir });
32
34
  }
33
- if (trainerName === "OLDMAN" || trainerName === "POKEDUDE") {
34
- return;
35
- }
36
- const trainerPal = assets.find(a => a.name === trainer.Palette);
37
- const trainerPic = assets.find(a => a.name === trainer.Pic);
35
+ const trainerPal = resolveTrainerFrontPicPal(trainer, reader, trainerName);
36
+ const trainerPic = resolveTrainerFrontPic(trainer, reader, trainerName);
38
37
 
39
38
  if (!trainerPal || !trainerPic) {
40
39
  throw new Error(`Missing assets for: ${trainerName}`);
@@ -0,0 +1,10 @@
1
+ export function resolveTrainerBackPicPal(trainer, reader, trainerName) { // I probably could generalize this stuff but erm... thats more work :p
2
+ const table = reader.getTable("trainerBackPicPaletteTable");
3
+ const entryOffset = table + trainer.index * 8;
4
+ const ptr = reader.readPointer(entryOffset);
5
+ return {
6
+ name: `trainer_${trainerName}_back_pic_pal`,
7
+ offset: ptr,
8
+ size: 40,
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ export function resolveTrainerBackPic(trainer, reader, trainerName) {
2
+ const table = reader.getTable("trainerBackPicTable");
3
+ const entryOffset = table + trainer.index * 8;
4
+ const ptr = reader.readPointer(entryOffset);
5
+ return {
6
+ name: `trainer_${trainerName}_back_pic`,
7
+ offset: ptr,
8
+ }
9
+ }
@@ -0,0 +1,10 @@
1
+ export function resolveTrainerFrontPicPal(trainer, reader, trainerName) {
2
+ const table = reader.getTable("trainerFrontPicPaletteTable");
3
+ const entryOffset = table + trainer.index * 8;
4
+ const ptr = reader.readPointer(entryOffset);
5
+ return {
6
+ name: `trainer_${trainerName}_front_pic_pal`,
7
+ offset: ptr,
8
+ size: 40,
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ export function resolveTrainerFrontPic(trainer, reader, trainerName) {
2
+ const table = reader.getTable("trainerFrontPicTable");
3
+ const entryOffset = table + trainer.index * 8;
4
+ const ptr = reader.readPointer(entryOffset);
5
+ return {
6
+ name: `trainer_${trainerName}_front_pic`,
7
+ offset: ptr,
8
+ }
9
+ }
package/src/rom-reader.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright (c) 2026 chickenPoo
2
+ // Licensed under the MIT License. See LICENSE file in project root.
3
+
1
4
  export class RomReader {
2
5
  constructor(rom, mapConfig) {
3
6
  this.rom = rom;
@@ -19,7 +19,7 @@ for (let i = 0; i < frontPicLines.length; i++) {
19
19
  } else {
20
20
  trainerData[trainerName]["Palette"] = match[3];
21
21
  }
22
- if (!trainerData[trainerName]["index"]) {
22
+ if (trainerData[trainerName]["index"] === undefined) {
23
23
  trainerData[trainerName]["index"] = trainerIndex;
24
24
  trainerIndex++;
25
25
  }
@@ -0,0 +1,26 @@
1
+ {
2
+ "RED": {
3
+ "index": 0
4
+ },
5
+ "LEAF": {
6
+ "index": 1
7
+ },
8
+ "RS_BRENDAN_1": {
9
+ "index": 2
10
+ },
11
+ "RS_BRENDAN_2": {
12
+ "index": 2
13
+ },
14
+ "RS_MAY_1": {
15
+ "index": 3
16
+ },
17
+ "RS_MAY_2": {
18
+ "index": 3
19
+ },
20
+ "POKEDUDE": {
21
+ "index": 4
22
+ },
23
+ "OLDMAN": {
24
+ "index": 5
25
+ }
26
+ }