Skip to content

Commit d9a9605

Browse files
committed
bundle a WASM version of ca65, convert build scripts to a universal node format - only dependency now is node
1 parent b8d0316 commit d9a9605

File tree

12 files changed

+11346
-182
lines changed

12 files changed

+11346
-182
lines changed

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,11 +440,9 @@ Dictate if the NTSC or PAL gameplay mechanics should be used. Should automatical
440440

441441
## Development
442442

443-
To build, you need a copy of `node` and `cc65` installed on your system.
443+
To build, you need a copy of `node` installed on your system. No other dependencies are required.
444444

445-
Provide a `clean.nes` file of the unpatched ROM and run `sh build.sh` in a shell or `build.bat` on Windows.
446-
447-
CHR files are autogenerated from PNG on build, and nametables are generated via JS.
445+
Provide a `clean.nes` file of the unpatched ROM and run `node build.js`
448446

449447
This project descends from the TAUS disassembly of NES Tetris and has been heavily modified. Large parts have been replaced, lots of optimisations, removal of unused code, non-game-mechanics related bugfixes, tooling, and different approaches to the disassembly work itself has taken place.
450448

build.bat

Lines changed: 0 additions & 26 deletions
This file was deleted.

build.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const crypto = require('crypto');
4+
const { spawnSync } = require('child_process');
5+
6+
console.log('TetrisGYM buildscript');
7+
console.time('build');
8+
9+
const mappers = [1, 3, 4, 5];
10+
11+
// options handling
12+
13+
const args = process.argv.slice(2);
14+
15+
if (args.includes('-h')) {
16+
console.log(`usage: node [-h] [-v] [-m<${mappers.join('|')}>] [-a] [-s] [-k]
17+
18+
-m mapper
19+
-a faster aeppoz + press select to end game
20+
-s disable highscores/SRAM
21+
-k Famicom Keyboard support
22+
-h you are here
23+
`);
24+
process.exit(0);
25+
}
26+
27+
const compileFlags = [];
28+
29+
// compiler options
30+
31+
const nativeCC65 = process.env.PATH.split(path.delimiter).some((dir) =>
32+
fs.statSync(path.join(dir, 'cc65'), { throwIfNoEntry: false })?.isFile(),
33+
);
34+
35+
console.log(`using ${nativeCC65 ? 'system' : 'wasm'} ca65/ld65`);
36+
37+
// mapper options
38+
39+
const mapper = args.find((d) => d.startsWith('-m'))?.slice(2) ?? 1;
40+
41+
if (!mappers.includes(+mapper)) {
42+
console.error(
43+
`Invalid INES_MAPPER - options are ${mappers
44+
.map((d) => `-m${d}`)
45+
.join(', ')}`,
46+
);
47+
process.exit(0);
48+
}
49+
50+
// compileFlags
51+
52+
compileFlags.push('-D', `INES_MAPPER=${mapper}`);
53+
54+
console.log(`using mapper ${mapper}`);
55+
56+
if (args.includes('-a')) {
57+
compileFlags.push('-D', 'AUTO_WIN=1');
58+
console.log('using fast aeppoz');
59+
}
60+
61+
if (args.includes('-k')) {
62+
compileFlags.push('-D', 'KEYBOARD=1');
63+
console.log('using Famicom Keyboard support');
64+
}
65+
66+
if (args.includes('-s')) {
67+
compileFlags.push('-D', 'SAVE_HIGHSCORES=0');
68+
console.log('highscore saving disabled');
69+
}
70+
71+
console.log();
72+
73+
// build / compress nametables
74+
75+
console.time('nametables');
76+
require('./src/nametables/build');
77+
console.timeEnd('nametables');
78+
79+
// PNG -> CHR
80+
81+
console.time('CHR');
82+
83+
const png2chr = require('./tools/png2chr/convert');
84+
85+
const dir = path.join(__dirname, 'src', 'chr');
86+
87+
fs.readdirSync(dir)
88+
.filter((name) => name.endsWith('.png'))
89+
.forEach((name) => {
90+
const chr = png2chr(fs.readFileSync(path.join(dir, name)));
91+
const dst = path.join(dir, name.replace('.png', '.chr'));
92+
fs.writeFileSync(dst, chr);
93+
});
94+
95+
console.timeEnd('CHR');
96+
97+
// build object files
98+
99+
function handleSpawn(exe, ...args) {
100+
const output = spawnSync(exe, args).output.flatMap(
101+
(d) => d?.toString() || [],
102+
);
103+
if (output.length) {
104+
console.log(output.join('\n'));
105+
process.exit(0);
106+
}
107+
}
108+
109+
const ca65bin = nativeCC65 ? ['ca65'] : ['node', './tools/assemble/ca65.js'];
110+
111+
console.time('assemble');
112+
113+
handleSpawn(
114+
...ca65bin,
115+
...compileFlags,
116+
...'-g src/header.asm -o header.o'.split(' '),
117+
);
118+
119+
handleSpawn(
120+
...ca65bin,
121+
...compileFlags,
122+
...'-l tetris.lst -g src/main.asm -o main.o'.split(' '),
123+
);
124+
125+
console.timeEnd('assemble');
126+
127+
// link object files
128+
129+
const ld65bin = nativeCC65 ? ['ld65'] : ['node', './tools/assemble/ld65.js'];
130+
131+
console.time('link');
132+
133+
handleSpawn(
134+
...ld65bin,
135+
...'-m tetris.map -Ln tetris.lbl --dbgfile tetris.dbg -o tetris.nes -C src/tetris.nes.cfg main.o header.o'.split(
136+
' ',
137+
),
138+
);
139+
140+
console.timeEnd('link');
141+
142+
// create patch
143+
144+
if (!fs.existsSync('clean.nes')) {
145+
console.log('clean.nes not found, skipping patch creation');
146+
} else {
147+
console.time('patch');
148+
const patcher = require('./tools/patch/create');
149+
patcher('clean.nes', 'tetris.nes', 'tetris.bps');
150+
console.timeEnd('patch');
151+
}
152+
153+
// stats
154+
155+
console.log();
156+
157+
if (fs.existsSync('tetris.map')) {
158+
const memMap = fs.readFileSync('tetris.map', 'utf8');
159+
160+
console.log((memMap.match(/PRG_chunk\d+\s+0.+$/gm) ?? []).join('\n'));
161+
}
162+
163+
function hashFile(filename) {
164+
if (fs.existsSync(filename)) {
165+
const shasum = crypto.createHash('sha1');
166+
shasum.update(fs.readFileSync(filename));
167+
console.log(`${filename} => ${shasum.digest('hex')}`);
168+
}
169+
}
170+
171+
hashFile('tetris.nes');
172+
hashFile('tetris.bps');
173+
174+
console.log();
175+
176+
console.timeEnd('build');

build.sh

Lines changed: 0 additions & 117 deletions
This file was deleted.

src/nametables/nametables.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ function drawRect(buffer, x, y, w, h, offset) {
4747

4848
function drawAttrs(buffer, attrs) {
4949
const palettes = p => p.trim().match(/.+\n.+$/gm)
50-
.flatMap(line=>(
51-
[t,b]=line.split('\n'),
52-
t.trim().match(r=/../g).map((d,i)=>d+b.trim().match(r)[i])
53-
))
50+
.flatMap(line=>{
51+
const [t,b]=line.split('\n');
52+
return t.trim().match(r=/../g).map((d,i)=>d+b.trim().match(r)[i])
53+
})
5454
.map(d=>+('0b'+[...d].reverse().map(d=>(+d).toString(2).padStart(2,0)).join``));
5555

5656
[

tools/assemble/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cc65 binaries ported to WebAssembly for TetrisGYM
2+
3+
generated with emscripten
4+
5+
```bash
6+
emmake make ld65 CC=emcc CFLAGS="-O3 -Wall" -Isrc/common/ LD=emcc OBJDIR="" HOST_OBJEXTENSION=".o" LDFLAGS="-sEXPORTED_RUNTIME_METHODS=FS -s FORCE_FILESYSTEM=1 -lnodefs.js -lnoderawfs.js"
7+
```
8+
9+
Original source: https://github.com/cc65/cc65

0 commit comments

Comments
 (0)