Skip to content

Commit 8c4d576

Browse files
committed
Add Dockerfile, read raw romfs with zstd+sarc+byml
1 parent 46b3b16 commit 8c4d576

File tree

4 files changed

+67
-23
lines changed

4 files changed

+67
-23
lines changed

Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM node:18
2+
WORKDIR /radar
3+
COPY . .
4+
RUN npm install && npm install typescript -g
5+
RUN apt-get update && apt-get install -y zstd python3-pip && pip3 install sarc byml --break-system-packages
6+
CMD ./node_modules/.bin/ts-node ./build.ts -r /romfs -e ./tools && \
7+
npm run dev

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ A server for querying placement objects in *The Legend of Zelda: Tears of the Ki
33

44
Run build.ts to generate a map database before starting the server for the first time.
55

6-
ts-node build.ts -d ../totk/Banc
6+
ts-node build.ts -r ../totk -e tools
77

8-
This assumes the `totk/Banc` directory contains the YAML data object map files
8+
This assumes the `totk` directory contains the unaltered romfs contents.
9+
10+
For docker usage: `docker build -t radar .; docker run -it --rm --name radar -v /path/to/your/romfs:/romfs radar`
11+
12+
It's possible to build the db within docker and copy it out for the server to use, if you'd rather not install the extraction tools used in build.ts on your local machine.

beco.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ export class Beco {
2828
// Offsets to row data, divided by 2 and relative to the start of the row section
2929
offsets: number[]; // u32, size num_rows
3030
segments: BecoSegment[][]; // Rows x Segments
31-
constructor(file: string) {
31+
constructor(buf: Buffer) {
3232
let little = true;
33-
let buf = fs.readFileSync(file);
3433
let arr = new Uint8Array(buf.byteLength);
3534
buf.copy(arr, 0, 0, buf.byteLength);
3635
let dv = new DataView(arr.buffer);

build.ts

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { execSync } from 'child_process';
12
import sqlite3 from 'better-sqlite3';
23
import fs from 'fs';
34
import yaml from 'js-yaml';
@@ -6,17 +7,17 @@ import { Beco } from './beco';
67

78
let parseArgs = require('minimist');
89
let argv = parseArgs(process.argv);
9-
if (!argv.d || !argv.b || !argv.e) {
10+
if (!argv.e || !argv.r) {
1011
console.log("Error: Must specify paths to directories with ");
11-
console.log(" -d Banc extracted YAML files");
12-
console.log(" -b field map area beco files");
1312
console.log(" -e Ecosystem json files");
14-
console.log(" e.g. % ts-node build.ts -d path/to/Banc -b path/to/beco -e path/to/Ecosystem")
13+
console.log(" -r Bare game romfs");
14+
console.log(" e.g. % ts-node build.ts -r path/to/romfs -e tools")
1515
process.exit(1);
1616
}
17-
const totkData = argv.d
18-
const becoPath = argv.b;
1917
const ecoPath = argv.e;
18+
const romfsPath = argv.r;
19+
const totkData = path.join(romfsPath, 'Banc');
20+
const becoPath = path.join(romfsPath, 'Ecosystem', 'FieldMapArea');
2021

2122
fs.rmSync('map.db.tmp', { force: true });
2223
const db = sqlite3('map.db.tmp');
@@ -85,14 +86,29 @@ const LOCATIONS = JSON.parse(fs.readFileSync('LocationMarker.json', 'utf8'))
8586
const KOROKS = JSON.parse(fs.readFileSync('koroks_id.json', 'utf8'))
8687
const DROP_TABLES = JSON.parse(fs.readFileSync('drop_tables.json', 'utf8'))
8788

89+
const BCETT_YAML_SUFFIXES = /\.bcett\.b?yml(\.zs)?$/;
90+
8891
const DropTableDefault = "Default";
8992
const DROP_TYPE_ACTOR = "Actor";
9093
const DROP_TYPE_TABLE = "Table";
9194

92-
const BecoGround = new Beco(path.join(becoPath, 'Ground.beco'));
93-
const BecoMinus = new Beco(path.join(becoPath, 'MinusField.beco'));
94-
const BecoSky = new Beco(path.join(becoPath, 'Sky.beco'));
95-
const BecoCave = new Beco(path.join(becoPath, 'Cave.beco'));
95+
const getZsDicPath = (function() {
96+
// Only call these tools when we need to use them, only extract zsdics once
97+
let zsDicPath: string = "";
98+
return function(): string {
99+
if (!zsDicPath) {
100+
zsDicPath = fs.mkdtempSync('zsdicpack');
101+
execSync(`zstd -d "${romfsPath}/Pack/ZsDic.pack.zs" -o "${zsDicPath}/ZsDic.pack"`);
102+
execSync(`sarc x --directory "${zsDicPath}" "${zsDicPath}/ZsDic.pack"`);
103+
}
104+
return zsDicPath;
105+
};
106+
})();
107+
108+
const BecoGround = new Beco(readRawBeco('Ground'));
109+
const BecoMinus = new Beco(readRawBeco('MinusField'));
110+
const BecoSky = new Beco(readRawBeco('Sky'));
111+
const BecoCave = new Beco(readRawBeco('Cave'));
96112

97113
// Should probably be yaml not json for consistency
98114
const Ecosystem = Object.fromEntries(['Cave', 'Ground', 'MinusField', 'Sky'].map(name => {
@@ -192,6 +208,27 @@ function parseHash(hash: string) {
192208
return '0x' + BigInt(hash).toString(16).padStart(16, '0');
193209
}
194210

211+
function readRawBeco(name: string): Buffer {
212+
let filePath = path.join(becoPath, name + '.beco');
213+
if (fs.existsSync(filePath)) {
214+
return fs.readFileSync(filePath);
215+
} else if (fs.existsSync(filePath + '.zs')) {
216+
return execSync(`zstd -D "${getZsDicPath()}/zs.zsdic" -d ${filePath}.zs -c`, {maxBuffer: 1073741824});
217+
}
218+
throw Error(`No beco file found for ${name}`);
219+
}
220+
221+
function readRawYaml(filePath: string): string {
222+
if (filePath.endsWith('.yml')) {
223+
return fs.readFileSync(filePath, 'utf-8').toString();
224+
} else if (filePath.endsWith('.byml')) {
225+
return execSync(`byml_to_yml ${filePath} -`, {maxBuffer: 1073741824}).toString();
226+
} else if (filePath.endsWith('.byml.zs')) {
227+
return execSync(`zstd -D "${getZsDicPath()}/bcett.byml.zsdic" -d ${filePath} -c | byml_to_yml - -`, {maxBuffer: 1073741824}).toString();
228+
}
229+
throw Error(`No yml file found at ${filePath}`);
230+
}
231+
195232
function getKorokType(hideType: number | undefined, name: string) {
196233
if (name == 'KorokCarryProgressKeeper') {
197234
return 'Korok Friends';
@@ -215,9 +252,7 @@ function getKorokType(hideType: number | undefined, name: string) {
215252
function processBanc(filePath: string, mapType: string, mapName: string) {
216253
let doc: any = null;
217254
try {
218-
doc = yaml.load(fs.readFileSync(filePath, 'utf-8'),
219-
{ schema: schema }
220-
);
255+
doc = yaml.load(readRawYaml(filePath), { schema: schema });
221256
} catch (e: any) {
222257
console.log("Error: ", e);
223258
process.exit(1);
@@ -443,13 +478,13 @@ function processBancs() {
443478
const dirPath = path.join(totkData, field);
444479
let files = fs.readdirSync(dirPath);
445480
for (const file of files) {
446-
if (!file.endsWith('.bcett.yml'))
481+
if (!file.match(BCETT_YAML_SUFFIXES))
447482
continue;
448483
let filePath = path.join(dirPath, file);
449484

450485
const fieldParts = field.split("/");
451486
let mapName = file
452-
.replace(".bcett.yml", "")
487+
.replace(BCETT_YAML_SUFFIXES, "")
453488
.replace("_Static", "")
454489
.replace("_Dynamic", "");
455490
const mapType = fieldParts[0];
@@ -463,12 +498,12 @@ function processBancs() {
463498
for (const mapType of ["SmallDungeon", "LargeDungeon", "NormalStage"]) {
464499
const dirPath = path.join(totkData, mapType);
465500
for (const file of fs.readdirSync(dirPath)) {
466-
if (!file.endsWith('.bcett.yml'))
501+
if (!file.match(BCETT_YAML_SUFFIXES))
467502
continue;
468503

469504
const filePath = path.join(dirPath, file);
470505
const mapName = file
471-
.replace(".bcett.yml", "")
506+
.replace(BCETT_YAML_SUFFIXES, "")
472507
.replace("_Static", "")
473508
.replace("_Dynamic", "");
474509
processBanc(filePath, mapType, mapName);
@@ -496,8 +531,7 @@ function processRecycleBox() {
496531
console.log("process recyclebox: ", filePath)
497532
let doc: any = null;
498533
try {
499-
doc = yaml.load(fs.readFileSync(filePath, 'utf-8'),
500-
{ schema: schema });
534+
doc = yaml.load(readRawYaml(filePath), { schema: schema });
501535
} catch (e: any) {
502536
console.log("Error: ", e);
503537
process.exit(1);

0 commit comments

Comments
 (0)