silphscope 1.3.0 → 1.3.2

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/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  still a WIP however hopefully with some more work I can get this into a workable state...
2
2
 
3
+ (Even Newer!) Update:
4
+
5
+ moves now work... kinda... still working on getting it all the way done but it mostly works!
6
+
3
7
  (Newer!) Update:
4
8
 
5
9
  still a WIP :p but erm you can extract more graphics!
@@ -21,10 +25,11 @@ await renderAllGraphics(rom, {
21
25
  outputMonDir: "./Assets/monImages", // must I explain?
22
26
  outputIconDir: "./Assets/Icons", // same thing here :p
23
27
  outputTrainerDir: "./Assets/Trainers", // ...
28
+ outputMoveDir: "./Assets/Moves"
24
29
  });
25
30
  ```
26
31
 
27
- Of course though the above is for extracting all graphics (which is kinda a lie... In reality it only extracts mon images, item icons, and trainer images... but like I said this is a WIP :p so wait a bit please!).
32
+ Of course though the above is for extracting all graphics (which is kinda a lie... In reality it only extracts mon images, item icons, trainer images, and move images... but like I said this is a WIP :p so wait a bit please!).
28
33
 
29
34
  But if you want say just the mon images or item icons refer below:
30
35
 
@@ -62,4 +67,15 @@ await renderAllTrainers(rom, {
62
67
  outputDir: "./Assets/trainers", // more stuff
63
68
  trainerBackPics: true, // renders the like 8 trainer back pics
64
69
  })
70
+ ```
71
+
72
+ move image extraction:
73
+ ```JavaScript
74
+ import fs from "fs";
75
+ import { renderAllMoves } from "silphscope" // :O
76
+
77
+ const rom = fs.readFileSync("pokefirered.gba") // stuff stuff stuff (more stuff!)
78
+ await renderAllMoves(rom, {
79
+ outputDir: "./Assets/trainers", // (incredibly) more stuff
80
+ })
65
81
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silphscope",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "A firered/leafgreen ROM asset extractor for use in web applications",
5
5
  "main": "main.js",
6
6
  "exports": {
@@ -18,7 +18,11 @@
18
18
  "item-data/",
19
19
  "trainer-data",
20
20
  "rom-configs/",
21
- "move-data/"
21
+ "move-data/",
22
+ "!src/icon-data-parser.js",
23
+ "!src/mon-data-parser.js",
24
+ "!src/trainer-data-parser.js",
25
+ "!src/move-data-parser.js"
22
26
  ],
23
27
  "keywords": [],
24
28
  "author": "chickenPoo",
@@ -104,6 +104,7 @@ export async function renderAllMoves(rom, options = {}) {
104
104
  const {
105
105
  moves: providedMoves = moves,
106
106
  outputDir = "./out",
107
+ renderMasterImage = true,
107
108
  } = options;
108
109
 
109
110
  fs.mkdirSync(outputDir, { recursive: true });
@@ -112,7 +113,10 @@ export async function renderAllMoves(rom, options = {}) {
112
113
  const reader = new RomReader(rom, config);
113
114
 
114
115
  for (const moveName of Object.keys(providedMoves)) {
115
- await renderMove(moveName, providedMoves, reader, rom, { outputDir });
116
+ await renderMove(moveName, providedMoves, reader, rom, {
117
+ outputDir,
118
+ renderMasterImage,
119
+ });
116
120
  console.log(`Done: ${moveName}`);
117
121
  }
118
122
  }
@@ -146,6 +150,7 @@ export async function renderAllGraphics(rom, options = {}) { // eventually I wil
146
150
 
147
151
  await renderAllMoves(rom, {
148
152
  outputDir: outputMoveDir,
153
+ renderMasterImage: true,
149
154
  })
150
155
  }
151
156
 
@@ -37,7 +37,8 @@ const extractFrameFromImage = (imageData, fullWidth, frameData) => { // in theor
37
37
 
38
38
  export async function renderMove(moveName, moves, reader, rom, options = {}) {
39
39
  const {
40
- outputDir = null
40
+ outputDir = null,
41
+ renderMasterImage = false,
41
42
  } = options;
42
43
  if (!rom || !(rom instanceof Uint8Array || Buffer.isBuffer(rom))) {
43
44
  throw new TypeError("renderMove(..., rom) requires a ROM Buffer/Uint8Array");
@@ -69,7 +70,12 @@ export async function renderMove(moveName, moves, reader, rom, options = {}) {
69
70
  if (outputDir) { // I will update this later but in theory it should also work... eventually though it will need a split inside to handle full image generation :p
70
71
  const dir = `${outputDir}/${moveName}`;
71
72
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
72
-
73
+ if (renderMasterImage) {
74
+ const png = new PNG({ width, height });
75
+ png.data = image;
76
+ const pngBuffer = await streamToBuffer(png.pack());
77
+ fs.writeFileSync(`${dir}/master.png`, pngBuffer);
78
+ }
73
79
  for (let i = 0; i < move.frames.length; i++) {
74
80
  const frame = move.frames[i];
75
81
  const frameImageData = extractFrameFromImage(image, width, frame);
@@ -1,7 +1,7 @@
1
1
  export function resolveMovePal(move, reader, moveName) {
2
2
  const table = reader.getTable("moveAnimPaletteTable");
3
3
  const entryOffset = table + move.index * 8;
4
- const ptr = reader.readOffset(entryOffset);
4
+ const ptr = reader.readPointer(entryOffset);
5
5
  return {
6
6
  name: `move_${moveName}_pal`,
7
7
  offset: ptr,
@@ -1,7 +1,7 @@
1
1
  export function resolveMovePic(move, reader, moveName) {
2
2
  const table = reader.getTable("moveAnimPicTable");
3
3
  const entryOffset = table + move.index * 8;
4
- const ptr = reader.readOffset(entryOffset);
4
+ const ptr = reader.readPointer(entryOffset);
5
5
  return {
6
6
  name: `move_${moveName}_pic`,
7
7
  offset: ptr
@@ -1,30 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- const itemIconDataMap = path.resolve("../item_graphics/item_icon_table.h");
5
- const iconDataLines = fs.readFileSync(itemIconDataMap, "utf-8").split("\n");
6
-
7
- const itemData = {};
8
- let itemIndex = 0;
9
-
10
- for (let i = 0; i < iconDataLines.length; i++) {
11
- const match = iconDataLines[i].match(/\[(ITEMS?_[A-Z0-9_]+)\]\s*=\s*\{\s*([^,]+),\s*([^}]+)\}/);
12
- if (match) {
13
- let itemName = match[1]
14
- .replace("ITEM_", "")
15
- .replace("ITEMS_", "");
16
- const icon = match[2].trim();
17
- const palette = match[3].trim();
18
- if (!itemData[itemName]) {
19
- itemData[itemName] = {};
20
- }
21
- if (!itemData[itemName]["index"]) {
22
- itemData[itemName]["index"] = itemIndex;
23
- itemIndex++;
24
- }
25
- itemData[itemName]["icon"] = icon;
26
- itemData[itemName]["palette"] = palette;
27
- }
28
- }
29
-
30
- fs.writeFileSync("../item-data/itemData.json", JSON.stringify(itemData, null, 2));
@@ -1,118 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- const monFrontPicMap = path.resolve("../pokemon_graphics/front_pic_table.h");
5
- const monBackPicMap = path.resolve("../pokemon_graphics/back_pic_table.h");
6
- const monFootprintMap = path.resolve("../pokemon_graphics/footprint_table.h");
7
- const monPaletteMap = path.resolve("../pokemon_graphics/palette_table.h");
8
- const monShinyPaletteMap = path.resolve("../pokemon_graphics/shiny_palette_table.h");
9
- const monImageDimensionMap = path.resolve("../pokemon_graphics/front_pic_coordinates.h");
10
- const monIconPaletteIdxMap = path.resolve("../pokemon_icon.c");
11
- const frontPicLines = fs.readFileSync(monFrontPicMap, "utf-8").split("\n");
12
- const backPicLines = fs.readFileSync(monBackPicMap, "utf-8").split("\n");
13
- const footprintLines = fs.readFileSync(monFootprintMap, "utf8").split("\n");
14
- const paletteLines = fs.readFileSync(monPaletteMap, "utf-8").split("\n");
15
- const shinyPaletteLines = fs.readFileSync(monShinyPaletteMap, "utf-8").split("\n");
16
- const imageDimLines = fs.readFileSync(monImageDimensionMap, "utf-8").split("\n");
17
- const iconPalIdxLines = fs.readFileSync(monIconPaletteIdxMap, "utf-8").split("\n");
18
- const monData = {};
19
-
20
- function extractMonData(lines, category) {
21
- let monIndex = 0;
22
- for (let i = 0; i < lines.length; i++) {
23
- let match;
24
- let monName;
25
- let secondArgument;
26
- if (category !== "footprintPics" && category !== "imageDimensions" && category !== "iconPalIndex") {
27
- match = lines[i].match(/\(([^,]+),\s*([^,]+)\)/);
28
- if (match) {
29
- monName = match[1];
30
- secondArgument = match[2];
31
- }
32
- } else if (category === "footprintPics") {
33
- match = lines[i].match(/\[(SPECIES_\w+)\]\s*=\s*(gMonFootprint_\w+)/);
34
- if (match) {
35
- monName = match[1].replace("SPECIES_", "");
36
- secondArgument = match[2];
37
- }
38
- } else if (category === "imageDimensions" && lines[i].includes(".size = MON_COORDS_SIZE")) {
39
- const prevLine = lines[i - 2];
40
- match = lines[i].match(/MON_COORDS_SIZE\((\d+),\s*(\d+)\)/);
41
- if (match) {
42
- monName = prevLine.match(/\[(SPECIES_\w+)\]/)[1].replace("SPECIES_", "");
43
- const width = match[1];
44
- const height = match[2];
45
- if (!monData[monName]) {
46
- monData[monName] = {};
47
- }
48
- monData[monName]["width"] = width;
49
- monData[monName]["height"] = height;
50
- }
51
- } else if (category === "iconPalIndex") {
52
- match = lines[i].match(/\[(SPECIES_\w+)\]\s*=\s*(\d+)\s*,?/);
53
- if (match) {
54
- monName = match[1].replace("SPECIES_", "");
55
- secondArgument = match[2];
56
- }
57
- }
58
- if (monName && secondArgument) {
59
- if (!monData[monName]) {
60
- monData[monName] = {};
61
- }
62
- if (!monData[monName][category]) {
63
- monData[monName][category] = secondArgument;
64
- }
65
- if (!monData[monName]["index"]) {
66
- monData[monName]["index"] = monIndex;
67
- monIndex++
68
- }
69
- }
70
- }
71
- }
72
-
73
- extractMonData(frontPicLines, "frontPics");
74
- extractMonData(backPicLines, "backPics");
75
- extractMonData(paletteLines, "normPalette");
76
- extractMonData(shinyPaletteLines, "shinyPalette");
77
- extractMonData(footprintLines, "footprintPics");
78
- extractMonData(imageDimLines, "imageDimensions");
79
- extractMonData(iconPalIdxLines, "iconPalIndex");
80
- function uncapitalize(string) {
81
- return string.charAt(0) + string.slice(1).toLowerCase();
82
- }
83
- Object.entries(monData).forEach(([monName]) => {
84
- let monName2 = uncapitalize(monName);
85
- if (monName2.includes("Old_unown_") || monName2.includes("None")) {
86
- monName2 = "QuestionMark";
87
- }
88
- if (monName2.includes("Unown_")) {
89
- monName2 = monName2.slice(0, 5) + monName2[6].toUpperCase() + monName2.slice(7);
90
- if (monName2 === "UnownEmark") {
91
- monName2 = "UnownExclamationMark";
92
- } else if (monName2 === "UnownQmark") {
93
- monName2 = "UnownQuestionMark";
94
- }
95
- }
96
- if (monName2.includes("Nidoran")) {
97
- if (monName2.includes("Nidoran_f")) {
98
- monName2 = "NidoranF";
99
- }
100
- if (monName2.includes("Nidoran_m")) {
101
- monName2 = "NidoranM";
102
- }
103
- }
104
- if (monName2.includes("Mr_mime")) {
105
- monName2 = "Mrmime";
106
- }
107
- if (monName2 === "Unown") {
108
- monName2 = "UnownA";
109
- }
110
- if (monName2 === "Ho_oh") {
111
- monName2 = "HoOh";
112
- }
113
- console.log(monName2);
114
- const iconName = `gMonIcon_${monName2}`
115
- monData[monName]["Icon"] = iconName;
116
- })
117
-
118
- fs.writeFileSync("../mon-data/monData.json", JSON.stringify(monData, null, 2));
@@ -1,169 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- // === CONFIG ===
5
- const USE_PREBUILT_TEMPLATES = true; // Set to true to load from moveStructData.json instead of parsing .c files
6
- // ===
7
-
8
- // Paths
9
- const movePicMap = path.resolve("../move_graphics/gBattleAnimPicTable.h");
10
- const moveDataPath = "../move-data/moveData.json";
11
- const unusedTagData = JSON.parse(fs.readFileSync(path.resolve("../move-data/unusedTags.json"), "utf-8"));
12
-
13
- // Read animation pic table to get move animation tags
14
- const movePicLines = fs.readFileSync(movePicMap, "utf-8").split("\n");
15
-
16
- const moveData = {};
17
- let moveIndex = 0;
18
-
19
- // Tags that are defined but never assigned a SpriteTemplate
20
- const unusedTags = new Set(Object.keys(unusedTagData));
21
-
22
- // Parse gBattleAnimPicTable.h for animation tags
23
- for (const line of movePicLines) {
24
- const match = line.match(/\{\s*(gBattleAnimSpriteGfx_[A-Za-z0-9_]+)\s*,\s*(0x[0-9A-Fa-f]+)\s*,\s*(ANIM_TAG_[A-Za-z0-9_]+)\s*\},/);
25
- if (match) {
26
- const moveName = match[3].replace("ANIM_TAG_", "");
27
- const moveTag = match[3];
28
-
29
- if (!moveData[moveName]) {
30
- moveData[moveName] = { index: moveIndex++ };
31
- }
32
- moveData[moveName].animTag = moveTag;
33
-
34
- if (unusedTagData[moveTag]) {
35
- Object.assign(moveData[moveName], unusedTagData[moveTag]);
36
- moveData[moveName].unused = true;
37
- }
38
- }
39
- }
40
-
41
- // Parse SpriteTemplate structs from battle_anim_*.c files (or load prebuilt)
42
- let templates = {};
43
-
44
- if (USE_PREBUILT_TEMPLATES) {
45
- // Load from prebuilt JSON
46
- const prebuiltPath = "../move-data/moveStructData.json";
47
- templates = JSON.parse(fs.readFileSync(prebuiltPath, "utf-8"));
48
- console.log(`Loaded ${Object.keys(templates).length} templates from prebuilt file.`);
49
- } else {
50
- // Parse from source files
51
- const animFiles = [
52
- "../move_graphics/battle_anim_bug.c", "../move_graphics/battle_anim_dark.c",
53
- "../move_graphics/battle_anim_dragon.c", "../move_graphics/battle_anim_effects_1.c",
54
- "../move_graphics/battle_anim_effects_2.c", "../move_graphics/battle_anim_effects_3.c",
55
- "../move_graphics/battle_anim_electric.c", "../move_graphics/battle_anim_fight.c",
56
- "../move_graphics/battle_anim_fire.c", "../move_graphics/battle_anim_flying.c",
57
- "../move_graphics/battle_anim_ghost.c", "../move_graphics/battle_anim_ground.c",
58
- "../move_graphics/battle_anim_ice.c", "../move_graphics/battle_anim_normal.c",
59
- "../move_graphics/battle_anim_poison.c", "../move_graphics/battle_anim_psychic.c",
60
- "../move_graphics/battle_anim_rock.c", "../move_graphics/battle_anim_water.c",
61
- "../move_graphics/battle_anim_status_effects.c",
62
- ];
63
-
64
- for (const file of animFiles) {
65
- const lines = fs.readFileSync(file, "utf-8").split("\n");
66
- Object.assign(templates, parseStructs(lines));
67
- }
68
- console.log(`Parsed ${Object.keys(templates).length} templates from source files.`);
69
- }
70
-
71
- // Build template lookup by tileTag
72
- const templateByTag = Object.fromEntries(
73
- Object.values(templates).map(t => [t.tileTag, t])
74
- );
75
-
76
- // Check for missing animTags
77
- const animTagsInPicTable = Object.values(moveData).map(item => item.animTag);
78
- const tileTagsInTemplates = Object.values(templates).map(t => t.tileTag);
79
- const missingTags = animTagsInPicTable.filter(tag => !tileTagsInTemplates.includes(tag));
80
-
81
- if (missingTags.length > 0) {
82
- console.log("Missing animTags from templates:");
83
- missingTags.forEach(tag => {
84
- if (!unusedTags.has(tag)) console.log(tag);
85
- });
86
- } else {
87
- console.log("No missing animTags.");
88
- }
89
-
90
- // Extract OAM size info for each move
91
- for (const key in moveData) {
92
- const item = moveData[key];
93
- const template = templateByTag[item.animTag];
94
-
95
- if (template?.oam) {
96
- const size = extractOamSize(template.oam);
97
- if (size) {
98
- item.possibleFrameSize = size.raw;
99
- item.possibleFrameWidth = size.width;
100
- item.possibleFrameHeight = size.height;
101
- }
102
- }
103
- }
104
-
105
- // Write final output
106
- fs.writeFileSync(moveDataPath, JSON.stringify(moveData, null, 2));
107
- console.log(`Wrote ${Object.keys(moveData).length} moves to ${moveDataPath}`);
108
-
109
- // --- Helper Functions ---
110
-
111
- function parseStructs(fileLines) {
112
- const results = {};
113
- let current = null;
114
- let braceDepth = 0;
115
- let inStruct = false;
116
-
117
- for (const line of fileLines) {
118
- const trimmed = line.trim();
119
- const startMatch = trimmed.match(/^(static\s+)?const\s+struct\s+SpriteTemplate\s+(\w+)/);
120
-
121
- if (startMatch) {
122
- current = { name: startMatch[2] };
123
- inStruct = true;
124
- braceDepth = trimmed.includes("{") ? 1 : 0;
125
- continue;
126
- }
127
-
128
- if (!inStruct) continue;
129
-
130
- // Track brace depth
131
- for (const char of trimmed) {
132
- if (char === "{") braceDepth++;
133
- if (char === "}") braceDepth--;
134
- }
135
-
136
- // Parse struct fields
137
- if (trimmed.startsWith(".")) {
138
- const eqIndex = trimmed.indexOf("=");
139
- if (eqIndex === -1) continue;
140
-
141
- const key = trimmed.slice(1, eqIndex).trim();
142
- let value = trimmed.slice(eqIndex + 1).replace(",", "").trim();
143
- if (value === "NULL") value = null;
144
- else if (value.startsWith("&")) value = value.slice(1);
145
-
146
- current[key] = value;
147
- }
148
-
149
- // End of struct
150
- if (inStruct && braceDepth === 0) {
151
- results[current.name] = current;
152
- current = null;
153
- inStruct = false;
154
- }
155
- }
156
-
157
- return results;
158
- }
159
-
160
- function extractOamSize(oam) {
161
- const match = oam?.match(/(\d+)x(\d+)$/);
162
- if (!match) return null;
163
-
164
- return {
165
- width: parseInt(match[1], 10),
166
- height: parseInt(match[2], 10),
167
- raw: match[0]
168
- };
169
- }
@@ -1,29 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- const trainerFrontPicMap = path.resolve("../trainer_graphics/front_pic_tables.h");
5
- const frontPicLines = fs.readFileSync(trainerFrontPicMap, "utf-8").split("\n");
6
-
7
- const trainerData = {};
8
- let trainerIndex = 0;
9
-
10
- for (let i = 0; i < frontPicLines.length; i++) {
11
- const match = frontPicLines[i].match(/TRAINER_(SPRITE|PAL)\(\s*([A-Z0-9_]+)\s*,\s*(gTrainer(?:FrontPic|Palette)_[A-Za-z0-9_]+)/);
12
- if (match) {
13
- let trainerName = match[2];
14
- if (!trainerData[trainerName]) {
15
- trainerData[trainerName] = {};
16
- }
17
- if (match[1] === "SPRITE") {
18
- trainerData[trainerName]["Pic"] = match[3];
19
- } else {
20
- trainerData[trainerName]["Palette"] = match[3];
21
- }
22
- if (trainerData[trainerName]["index"] === undefined) {
23
- trainerData[trainerName]["index"] = trainerIndex;
24
- trainerIndex++;
25
- }
26
- }
27
- }
28
-
29
- fs.writeFileSync("../trainer-data/trainerData.json", JSON.stringify(trainerData, null, 2));