Compare commits
10 commits
a9ccb6e5c2
...
786ff257c3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
786ff257c3 | ||
|
|
a05958bd86 | ||
|
|
efa9fecad9 | ||
|
|
55be676c0c | ||
|
|
dcddbbfe97 | ||
|
|
4c11efe83f | ||
|
|
f6c4ad3747 | ||
|
|
5adf55e80f | ||
|
|
46baf7ad5f | ||
| b08c5bfae0 |
220 changed files with 14181 additions and 1976 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Everything make writes: programs, bakes, disk images, the harness.
|
||||
build/
|
||||
|
||||
# The original game's memory image (scripts/importData.sh installs it as
|
||||
# stuff/raw.bin) and the disassembly work around it. A copy of a commercial
|
||||
# game: never committed.
|
||||
stuff/
|
||||
|
||||
# stsim's host binaries and its captured golden traces (gate.sh capture;
|
||||
# gateHashes.txt is the committed reference).
|
||||
tools/stsim/stsim
|
||||
tools/stsim/recorder
|
||||
tools/stsim/golden/
|
||||
|
||||
screenshots/
|
||||
__pycache__/
|
||||
|
||||
# Editor / OS cruft
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
BIN
21_!.wav
Normal file
BIN
21_!.wav
Normal file
Binary file not shown.
BIN
31_1.wav
Normal file
BIN
31_1.wav
Normal file
Binary file not shown.
BIN
32_2.wav
Normal file
BIN
32_2.wav
Normal file
Binary file not shown.
BIN
33_3.wav
Normal file
BIN
33_3.wav
Normal file
Binary file not shown.
BIN
34_4.wav
Normal file
BIN
34_4.wav
Normal file
Binary file not shown.
BIN
35_5.wav
Normal file
BIN
35_5.wav
Normal file
Binary file not shown.
BIN
36_6.wav
Normal file
BIN
36_6.wav
Normal file
Binary file not shown.
BIN
37_7.wav
Normal file
BIN
37_7.wav
Normal file
Binary file not shown.
BIN
38_8.wav
Normal file
BIN
38_8.wav
Normal file
Binary file not shown.
BIN
39_9.wav
Normal file
BIN
39_9.wav
Normal file
Binary file not shown.
BIN
3F_x.wav
Normal file
BIN
3F_x.wav
Normal file
Binary file not shown.
BIN
48_H.wav
Normal file
BIN
48_H.wav
Normal file
Binary file not shown.
BIN
50_P.wav
Normal file
BIN
50_P.wav
Normal file
Binary file not shown.
BIN
54_T.wav
Normal file
BIN
54_T.wav
Normal file
Binary file not shown.
BIN
55_U.wav
Normal file
BIN
55_U.wav
Normal file
Binary file not shown.
12
MECHANICS.md
12
MECHANICS.md
|
|
@ -1,8 +1,8 @@
|
|||
# Space Taxi Mechanics (research notes from C64 disassembly)
|
||||
|
||||
Source: VICE memory dumps in `stuff/spacetaxi/` decoded via the
|
||||
Source: VICE memory dumps in `stuff/` decoded via the
|
||||
`dis6502.py` / `disLive.py` helpers. Full live-RAM listings are at
|
||||
`stuff/spacetaxi/live-level1.lst` (40,069 instructions) and
|
||||
`stuff/live-level1.lst` (40,069 instructions) and
|
||||
`live-raw.lst` (titlescreen snapshot). This doc captures observed
|
||||
behavior in plain English to drive the JoeyLib port without dragging
|
||||
the original 6502 source into chat or comments.
|
||||
|
|
@ -68,7 +68,7 @@ acceleration always applied to Y.
|
|||
### Per-frame algorithm (`$6032`, full trace) -- VERIFIED via emulator
|
||||
|
||||
Verified by running `$6032` through the 6502 emulator with various
|
||||
joystick / sprite-ptr states. See `stuff/spacetaxi/trace.py`.
|
||||
joystick / sprite-ptr states. See `stuff/trace.py`.
|
||||
|
||||
```
|
||||
1. Clear instant accel: $7148/49 = 0; $714A/4B = 0 [$6032-$603F]
|
||||
|
|
@ -1069,7 +1069,7 @@ IEC fast-loader (raw `$FFA8` CIOUT / `$FFAE` UNLSN serial traffic)
|
|||
that pulls the song file off the disk. **The music DATA is not in
|
||||
the resident program at all: each song ships as its own disk file**
|
||||
-- the `SPACETAXI 01..04` / `SPACETAXI HI` PRGs (present in
|
||||
`stuff/spacetaxi/extract/`). Whatever song was resident when the
|
||||
`stuff/extract/`). Whatever song was resident when the
|
||||
raw.bin dump was taken is what sits in its load area there.
|
||||
|
||||
Timing base: `$A2` in the tick compare is the KERNAL jiffy clock low
|
||||
|
|
@ -1770,7 +1770,7 @@ Char-code interpretation (via $4345 validation):
|
|||
- Other chars: undefined behavior
|
||||
|
||||
Verified by running `$4354` through the 6502 emulator (see
|
||||
`stuff/spacetaxi/trace.py`) with each blob against an all-blank
|
||||
`stuff/trace.py`) with each blob against an all-blank
|
||||
HUD. The actual byte-level result for each:
|
||||
|
||||
| Blob | HUD after BCD-add (chars) | Numeric effect |
|
||||
|
|
@ -2098,7 +2098,7 @@ Key gameplay-affecting observations:
|
|||
- **Border/BG colors** vary; most levels are black (0/0). Level S
|
||||
has yellow/orange (`$07/$08`), level L is dark gray on dark gray.
|
||||
|
||||
The .dat files in `examples/spacetaxi/generated/levels/` carry the
|
||||
The .dat files in `generated/levels/` carry the
|
||||
same xAccel/yAccel/xGrav/yGrav values verbatim (verified by parsing
|
||||
the binary headers).
|
||||
|
||||
|
|
|
|||
26
Makefile
Normal file
26
Makefile
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Space Taxi, built with JoeyLib: a JoeyLib checkout (JOEYLIB, beside this
|
||||
# one by default) owns the toolchains, the library and every target's rules,
|
||||
# and program.mk describes the game to them.
|
||||
#
|
||||
# make every target whose toolchain is installed
|
||||
# make <port> one target: iigs amiga atarist dos x68000 dvx
|
||||
# make check build, then run the gates (scripts/verify.sh)
|
||||
# make clean remove build/
|
||||
# make help JoeyLib's project targets
|
||||
#
|
||||
# README.md covers setting up JoeyLib, importing the game data and running.
|
||||
|
||||
JOEYLIB ?= ../joeylib
|
||||
include $(JOEYLIB)/make/project.mk
|
||||
|
||||
include $(JOEY_PROJECT)/program.mk
|
||||
|
||||
# What DVX's help and resource texts call the program, and its author.
|
||||
DVX_KIND := JoeyLib game
|
||||
DVX_AUTHOR := Scott Duensing
|
||||
|
||||
include $(JOEYLIB)/make/programs.mk
|
||||
|
||||
.PHONY: check
|
||||
check: all
|
||||
JOEYLIB="$(abspath $(JOEYLIB))" $(JOEY_PROJECT)/scripts/verify.sh
|
||||
6
PLAN.md
6
PLAN.md
|
|
@ -15,7 +15,7 @@ Same loop every time, so it stays cheap to iterate:
|
|||
2. **Read** -- pull the routine into a window via `disLive.py
|
||||
<dump> <out> <start> <end>`, study the annotated listing.
|
||||
3. **Document** -- append behavior summary to `MECHANICS.md` in
|
||||
plain English. Add new labels/vars to `stuff/spacetaxi/labels.txt`.
|
||||
plain English. Add new labels/vars to `stuff/labels.txt`.
|
||||
4. **Reannotate** -- `python3 disLive.py mem0000-level1.bin
|
||||
live-level1.lst` to regenerate the (annotated) listing from
|
||||
the dump + labels.txt in one step. The .lst file is the only
|
||||
|
|
@ -25,12 +25,12 @@ Same loop every time, so it stays cheap to iterate:
|
|||
`stLevel.c`, `spacetaxi.c`).
|
||||
6. **Build + ship** -- `make -f make/dos.mk EXAMPLE=spacetaxi`
|
||||
first, then once stable also amiga / atarist / iigs.
|
||||
7. **User test** -- run `scripts/run-dos.sh staxi`, report
|
||||
7. **User test** -- run `./joey-run --port dos staxi`, report
|
||||
behavior. Do not start the next phase before this.
|
||||
|
||||
## Phase 0 -- foundation (DONE)
|
||||
|
||||
- Disassembly toolchain in `stuff/spacetaxi/`
|
||||
- Disassembly toolchain in `stuff/`
|
||||
- Full live-RAM listing + annotated variant
|
||||
- `MECHANICS.md` baseline
|
||||
- Asset pipeline (`tools/assetbake/assetbake.py`, extractor realigned)
|
||||
|
|
|
|||
123
README.md
Normal file
123
README.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Space Taxi
|
||||
|
||||
A port of Space Taxi (John Kutcher, Muse Software, 1984), the Commodore 64
|
||||
game, to every machine [JoeyLib](../joeylib) targets: the Apple IIgs,
|
||||
Commodore Amiga, Atari ST, MS-DOS, Sharp X68000 and DVX. One C codebase
|
||||
builds for all six.
|
||||
|
||||
The game is a routine-for-routine re-expression of the C64 original in C,
|
||||
written from its disassembly (MECHANICS.md): the flight physics, the fare
|
||||
machine, the 24 levels' hook programs, the title and level intros, the
|
||||
high-score table and name entry, the music (captured from the game's own
|
||||
SID player) and the speech. The simulation (stSim.c) is verified tick for
|
||||
tick against VICE traces of the C64's four attract-demo rides (VERIFIED.md),
|
||||
and the gates below hold it there.
|
||||
|
||||
## Setting up
|
||||
|
||||
Space Taxi builds with JoeyLib, which owns the cross compilers, the
|
||||
library, the asset tools and every machine's build rules. Check JoeyLib out
|
||||
beside this repository and install its toolchains (its README covers the
|
||||
installer):
|
||||
|
||||
```
|
||||
games/
|
||||
joeylib/ a JoeyLib checkout, set up with ./install.sh
|
||||
spacetaxi/ this repository
|
||||
```
|
||||
|
||||
```console
|
||||
$ cd ../joeylib
|
||||
$ ./install.sh install toolchains # the cross compilers
|
||||
$ ./install.sh install emulators # to run it and for the gates
|
||||
```
|
||||
|
||||
`JOEYLIB=PATH` on the make command line (or in the environment, for the
|
||||
scripts) points at a checkout somewhere else.
|
||||
|
||||
## The game data
|
||||
|
||||
The levels and the title screen are extracted at build time from a memory
|
||||
image of the original C64 game. It is a copy of a commercial game, so it is
|
||||
not in the repository; supply your own:
|
||||
|
||||
```console
|
||||
$ scripts/importData.sh PATH...
|
||||
```
|
||||
|
||||
PATH is the image itself, a folder holding it, or a `.zip` or `.tar.gz`
|
||||
with it inside. It is recognised by its checksum and installed as
|
||||
`stuff/raw.bin`, which git ignores. Without it the game still builds; only
|
||||
the levels (and the per-level sprite banks baked from them) are left out,
|
||||
and make says so.
|
||||
|
||||
## Building
|
||||
|
||||
```console
|
||||
$ make # every machine whose toolchain is installed
|
||||
$ make dos # one: iigs amiga atarist dos x68000 dvx
|
||||
$ make clean
|
||||
```
|
||||
|
||||
Everything lands in `build/<machine>/bin/`:
|
||||
|
||||
| Machine | Program |
|
||||
|---------|---------|
|
||||
| Apple IIgs | `STAXI`, and `STAXI.2mg`: a bootable 2 MB volume (stripped GS/OS that launches the game from its own folder) |
|
||||
| Amiga | `Taxi/Taxi` with its `DATA/` |
|
||||
| Atari ST | `STAXI/STAXI.PRG` with its `DATA/` |
|
||||
| MS-DOS | `STAXI/STAXI.EXE` with its `DATA/` |
|
||||
| Sharp X68000 | `STAXI/STAXI.X` with its `DATA/` |
|
||||
| DVX | `apps/joeylib/staxi/`: `staxi.app`, its help file and `DATA/` |
|
||||
|
||||
The bakes (levels, the pre-compiled sprite cels, the speech) are made per
|
||||
machine in `build/generated/<machine>/`. JoeyLib's library is built on
|
||||
demand in the JoeyLib checkout's own `build/`.
|
||||
|
||||
## Running
|
||||
|
||||
JoeyLib's `joey-run` starts a build in the right emulator:
|
||||
|
||||
```console
|
||||
$ ../joeylib/joey-run --project . --port dos staxi
|
||||
$ ../joeylib/joey-run --project . --port amiga taxi
|
||||
$ ../joeylib/joey-run build/iigs/bin/STAXI.2mg
|
||||
$ ../joeylib/joey-run --headless --seconds 90 --snapshot taxi.png --project . --port atarist staxi
|
||||
```
|
||||
|
||||
The IIgs volume is larger than an 800 KB floppy, so MAME boots it off a
|
||||
CFFA2 card (`mame apple2gs -sl7 cffa2 -hard1 build/iigs/bin/STAXI.2mg`).
|
||||
|
||||
## The gates
|
||||
|
||||
```console
|
||||
$ make check # build, then every gate
|
||||
$ scripts/verify.sh disk # or name the ones to run
|
||||
```
|
||||
|
||||
| Gate | What it proves |
|
||||
|------|----------------|
|
||||
| `stsim` | Every hook level's 4000-tick simulation trace is identical to its golden (`tools/stsim/gate.sh`; the four C64 attract rides and thirteen synthesised ones), and every cel a ride shows is pre-compiled (`celGate.sh`) |
|
||||
| `harness` | The screens above the simulation -- the high-score table, name entry, its save -- on JoeyLib's host BLANK port (`tools/staxiharness`) |
|
||||
| `disk`, `disk01` | `STAXI.2mg` boots, launches the game and draws on a ROM 3 and a ROM 01 IIgs |
|
||||
| `iigsaudio` | The IIgs sound interrupt is NTP's, the DOC is audible and its sound slots carry waveforms |
|
||||
| `x68kaudio` | Every X68000 noise write is a code from the port's table, and the ADPCM runs at 7812 Hz to both speakers while the game speaks |
|
||||
|
||||
`scripts/verify.sh` exits 0 when every gate passed, 1 when one failed, and 3
|
||||
when none failed but one was skipped (an emulator or a machine's build
|
||||
missing, or the game data not imported).
|
||||
|
||||
## Tools
|
||||
|
||||
- `tools/stsim` -- the simulation on the host: prints the per-tick state a
|
||||
VICE trace records, so `compare.py` can diff the port against the C64.
|
||||
- `tools/staxiharness` -- the front-end screen checks.
|
||||
- `tools/staxibake` -- writes the cel bank the build pre-compiles per machine.
|
||||
- `assets/` -- the generators behind the committed C64 tables
|
||||
(`genC64Data.py`, `genDemoStreams.py`, `genSongData.py`, `extractSpeech.py`)
|
||||
and the level extractor the build runs (`romToLevel.py`).
|
||||
- `scripts/bench-staxi-title.sh`, `scripts/diag-staxi-screen.sh` -- IIgs
|
||||
measurement and diagnosis under headless MAME.
|
||||
|
||||
MECHANICS.md and VERIFIED.md are the disassembly notes; TODO.md lists what is
|
||||
still open.
|
||||
21
TODO.md
Normal file
21
TODO.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Space Taxi -- open items
|
||||
|
||||
Things worth doing, not commitments. Started 2026-10-05, when Space Taxi
|
||||
moved out of JoeyLib's tree into this repository; JoeyLib's own list
|
||||
(library and platform work) stays in JoeyLib.
|
||||
|
||||
- [ ] **The C64's flown-sprite warp.** The level-name intro animates its
|
||||
warp by cycling the colours of a static star field painted into
|
||||
screen RAM, on every target (stTitle.c, `starPaletteApply` in
|
||||
stRender.c). The C64 flies seven star sprites out along its rays
|
||||
instead. That version, carry-flag exact ($44F8/$4500/$450C/$4513/
|
||||
$467A, the star velocity tables back in genC64Data.py), is kept in
|
||||
`patches/spriteWarp.patch` for reference: it was made against the
|
||||
working tree of 2026-10-05 before the warp's layout and pacing were
|
||||
redone, so it no longer applies as it stands. Measured then, the
|
||||
sprite warp ran 16.6-17.0 fps on the IIgs against the palette's 29.2
|
||||
(the bar was 30); ST 29.2 against the palette's 23.3, Amiga PAL 12.3
|
||||
against 18.8/13.6, DOS ~29.9 both ways. On the IIgs the cost is
|
||||
per-call overhead in save-and-draw and restore (~1.75k and ~1.25k
|
||||
cycles a star), not bytes moved, so it becomes feasible with a leaner
|
||||
save/draw/restore path for small cels in JoeyLib.
|
||||
10
VERIFIED.md
10
VERIFIED.md
|
|
@ -1,7 +1,7 @@
|
|||
# Space Taxi disassembly: verified claims
|
||||
|
||||
Each row below references a claim in `MECHANICS.md` and the trace
|
||||
that verifies it. Trace scripts live in `stuff/spacetaxi/trace.py`
|
||||
that verifies it. Trace scripts live in `stuff/trace.py`
|
||||
plus the inline scripts run during verification sessions.
|
||||
|
||||
## VERIFIED via emulator trace
|
||||
|
|
@ -81,10 +81,10 @@ plus the inline scripts run during verification sessions.
|
|||
|
||||
## TRACE INFRASTRUCTURE
|
||||
|
||||
- `stuff/spacetaxi/trace.py`: instrumented wrapper around `cpu6502.Cpu6502`. `Tracer.from_dump('raw.bin').call(addr)` runs a subroutine and snapshots memory + JSR chain.
|
||||
- `stuff/spacetaxi/cpu6502.py`: minimal 6502 emulator. Sufficient for all routines tested above; throws `NotImplementedError` for unsupported opcodes (none hit so far in gameplay paths).
|
||||
- `stuff/spacetaxi/dumpScenes.py` + `dumpAllLevels.py`: pre-existing scene-decompress drivers.
|
||||
- `stuff/spacetaxi/romToLevel.py`: emits the `.dat` files from raw.bin via the same emulator.
|
||||
- `stuff/trace.py`: instrumented wrapper around `cpu6502.Cpu6502`. `Tracer.from_dump('raw.bin').call(addr)` runs a subroutine and snapshots memory + JSR chain.
|
||||
- `assets/cpu6502.py`: minimal 6502 emulator. Sufficient for all routines tested above; throws `NotImplementedError` for unsupported opcodes (none hit so far in gameplay paths).
|
||||
- `assets/dumpScenes.py` + `stuff/dumpAllLevels.py`: pre-existing scene-decompress drivers.
|
||||
- `assets/romToLevel.py`: emits the `.dat` files from raw.bin via the same emulator.
|
||||
|
||||
## METHODOLOGY (for future verification sessions)
|
||||
|
||||
|
|
|
|||
BIN
assets/RawTitle.png
(Stored with Git LFS)
Normal file
BIN
assets/RawTitle.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/TitleShr.png
(Stored with Git LFS)
Normal file
BIN
assets/TitleShr.png
(Stored with Git LFS)
Normal file
Binary file not shown.
560
assets/cpu6502.py
Normal file
560
assets/cpu6502.py
Normal file
|
|
@ -0,0 +1,560 @@
|
|||
#!/usr/bin/env python3
|
||||
# Minimal 6502 CPU emulator -- just enough to run the Space Taxi
|
||||
# level decompressor at $6283 against a captured 64KB RAM dump.
|
||||
#
|
||||
# Scope: implements the opcodes used by $6283 / $9656 / $964A / $96A3
|
||||
# and their helpers. Not a general-purpose 6502 emulator; it has no
|
||||
# decimal mode, no BRK handling, no NMI/IRQ -- the routine runs with
|
||||
# SEI in effect and never returns control.
|
||||
#
|
||||
# Flags tracked: N Z C. V (overflow) is computed for ADC/SBC since
|
||||
# those instructions set it, even if no branch tests it. D (decimal)
|
||||
# is ignored.
|
||||
|
||||
class Cpu6502:
|
||||
def __init__(self, ram):
|
||||
# ram is a bytearray of 65536 bytes. Caller pre-loaded the dump.
|
||||
self.ram = ram
|
||||
self.a = 0
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.sp = 0xFF
|
||||
self.pc = 0
|
||||
self.n = self.z = self.c = self.v = False
|
||||
# Stop tracking when RTS unwinds to this depth (set by run()).
|
||||
self._depth_floor = 0
|
||||
|
||||
# ---- memory ----
|
||||
def read8(self, addr):
|
||||
return self.ram[addr & 0xFFFF]
|
||||
|
||||
def write8(self, addr, val):
|
||||
self.ram[addr & 0xFFFF] = val & 0xFF
|
||||
|
||||
def read16(self, addr):
|
||||
return self.ram[addr & 0xFFFF] | (self.ram[(addr + 1) & 0xFFFF] << 8)
|
||||
|
||||
# ---- stack ----
|
||||
def push8(self, val):
|
||||
self.ram[0x100 + self.sp] = val & 0xFF
|
||||
self.sp = (self.sp - 1) & 0xFF
|
||||
|
||||
def pop8(self):
|
||||
self.sp = (self.sp + 1) & 0xFF
|
||||
return self.ram[0x100 + self.sp]
|
||||
|
||||
def push16(self, val):
|
||||
self.push8((val >> 8) & 0xFF)
|
||||
self.push8(val & 0xFF)
|
||||
|
||||
def pop16(self):
|
||||
lo = self.pop8()
|
||||
hi = self.pop8()
|
||||
return (hi << 8) | lo
|
||||
|
||||
# ---- flag helpers ----
|
||||
def set_nz(self, val):
|
||||
val &= 0xFF
|
||||
self.z = (val == 0)
|
||||
self.n = (val & 0x80) != 0
|
||||
return val
|
||||
|
||||
def pack_p(self):
|
||||
# B and U are usually set when pushed by PHP/BRK.
|
||||
p = 0x20
|
||||
if self.n: p |= 0x80
|
||||
if self.v: p |= 0x40
|
||||
if self.z: p |= 0x02
|
||||
if self.c: p |= 0x01
|
||||
return p | 0x10 # B = 1 when pushed by PHP
|
||||
|
||||
def unpack_p(self, p):
|
||||
self.n = (p & 0x80) != 0
|
||||
self.v = (p & 0x40) != 0
|
||||
self.z = (p & 0x02) != 0
|
||||
self.c = (p & 0x01) != 0
|
||||
|
||||
# ---- addressing helpers (return effective address) ----
|
||||
def addr_imm(self):
|
||||
a = self.pc; self.pc = (self.pc + 1) & 0xFFFF; return a
|
||||
|
||||
def addr_zp(self):
|
||||
a = self.read8(self.pc); self.pc = (self.pc + 1) & 0xFFFF; return a
|
||||
|
||||
def addr_zpx(self):
|
||||
a = (self.read8(self.pc) + self.x) & 0xFF; self.pc = (self.pc + 1) & 0xFFFF; return a
|
||||
|
||||
def addr_abs(self):
|
||||
a = self.read16(self.pc); self.pc = (self.pc + 2) & 0xFFFF; return a
|
||||
|
||||
def addr_absx(self):
|
||||
a = (self.read16(self.pc) + self.x) & 0xFFFF; self.pc = (self.pc + 2) & 0xFFFF; return a
|
||||
|
||||
def addr_absy(self):
|
||||
a = (self.read16(self.pc) + self.y) & 0xFFFF; self.pc = (self.pc + 2) & 0xFFFF; return a
|
||||
|
||||
def addr_indy(self):
|
||||
zp = self.read8(self.pc); self.pc = (self.pc + 1) & 0xFFFF
|
||||
base = self.ram[zp] | (self.ram[(zp + 1) & 0xFF] << 8)
|
||||
return (base + self.y) & 0xFFFF
|
||||
|
||||
# ---- branch helper ----
|
||||
def branch(self, cond):
|
||||
off = self.read8(self.pc); self.pc = (self.pc + 1) & 0xFFFF
|
||||
if off >= 0x80: off -= 0x100
|
||||
if cond:
|
||||
self.pc = (self.pc + off) & 0xFFFF
|
||||
|
||||
# ---- step ----
|
||||
def step(self):
|
||||
op = self.read8(self.pc)
|
||||
self.pc = (self.pc + 1) & 0xFFFF
|
||||
f = _DISPATCH.get(op)
|
||||
if f is None:
|
||||
raise NotImplementedError(f"opcode ${op:02X} at PC ${(self.pc-1)&0xFFFF:04X}")
|
||||
f(self)
|
||||
|
||||
def call(self, target, max_steps=10_000_000):
|
||||
"""Call subroutine at `target` as if via JSR. Returns when the
|
||||
matching RTS pops the stack back below the original SP. Hard
|
||||
cap on steps to avoid runaway."""
|
||||
# Push a sentinel return address. When RTS pops to this, stop.
|
||||
# Use $FFFF as sentinel: subsequent step would attempt to fetch
|
||||
# from $FFFF; we detect via depth floor instead.
|
||||
original_sp = self.sp
|
||||
self.push16(0xFFFE) # RTS will pop this, then step will land at $FFFF
|
||||
self.pc = target & 0xFFFF
|
||||
for _ in range(max_steps):
|
||||
if self.pc == 0xFFFF:
|
||||
return
|
||||
self.step()
|
||||
raise RuntimeError(f"6502 call exceeded {max_steps} steps; PC=${self.pc:04X}")
|
||||
|
||||
|
||||
# ---- opcode implementations ----
|
||||
# Each takes the cpu and reads remaining operand bytes via cpu.addr_*.
|
||||
|
||||
def _NOP(cpu): pass
|
||||
def _SEI(cpu): pass # we don't model interrupts
|
||||
def _CLD(cpu): pass # no decimal mode
|
||||
def _SEC(cpu): cpu.c = True
|
||||
def _CLC(cpu): cpu.c = False
|
||||
def _PHP(cpu): cpu.push8(cpu.pack_p())
|
||||
def _PLP(cpu): cpu.unpack_p(cpu.pop8())
|
||||
def _PHA(cpu): cpu.push8(cpu.a)
|
||||
def _PLA(cpu): cpu.a = cpu.set_nz(cpu.pop8())
|
||||
def _TXA(cpu): cpu.a = cpu.set_nz(cpu.x)
|
||||
def _TAX(cpu): cpu.x = cpu.set_nz(cpu.a)
|
||||
def _TYA(cpu): cpu.a = cpu.set_nz(cpu.y)
|
||||
def _TAY(cpu): cpu.y = cpu.set_nz(cpu.a)
|
||||
def _INX(cpu): cpu.x = cpu.set_nz((cpu.x + 1) & 0xFF)
|
||||
def _INY(cpu): cpu.y = cpu.set_nz((cpu.y + 1) & 0xFF)
|
||||
def _DEX(cpu): cpu.x = cpu.set_nz((cpu.x - 1) & 0xFF)
|
||||
def _DEY(cpu): cpu.y = cpu.set_nz((cpu.y - 1) & 0xFF)
|
||||
|
||||
def _LDA_imm(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_imm()))
|
||||
def _LDA_zp(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_zp()))
|
||||
def _LDA_zpx(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_zpx()))
|
||||
def _LDA_abs(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_abs()))
|
||||
def _LDA_absx(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_absx()))
|
||||
def _LDA_absy(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_absy()))
|
||||
def _LDA_indy(cpu): cpu.a = cpu.set_nz(cpu.read8(cpu.addr_indy()))
|
||||
|
||||
def _LDX_imm(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_imm()))
|
||||
def _LDX_abs(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_abs()))
|
||||
def _LDX_absy(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_absy()))
|
||||
def _LDX_zp(cpu): cpu.x = cpu.set_nz(cpu.read8(cpu.addr_zp()))
|
||||
|
||||
def _LDY_imm(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_imm()))
|
||||
def _LDY_abs(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_abs()))
|
||||
def _LDY_absx(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_absx()))
|
||||
def _LDY_zp(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_zp()))
|
||||
|
||||
def _STA_zp(cpu): cpu.write8(cpu.addr_zp(), cpu.a)
|
||||
def _STA_zpx(cpu): cpu.write8(cpu.addr_zpx(), cpu.a)
|
||||
def _STA_abs(cpu): cpu.write8(cpu.addr_abs(), cpu.a)
|
||||
def _STA_absx(cpu): cpu.write8(cpu.addr_absx(), cpu.a)
|
||||
def _STA_absy(cpu): cpu.write8(cpu.addr_absy(), cpu.a)
|
||||
def _STA_indy(cpu): cpu.write8(cpu.addr_indy(), cpu.a)
|
||||
|
||||
def _STX_zp(cpu): cpu.write8(cpu.addr_zp(), cpu.x)
|
||||
def _STX_abs(cpu): cpu.write8(cpu.addr_abs(), cpu.x)
|
||||
|
||||
def _STY_zp(cpu): cpu.write8(cpu.addr_zp(), cpu.y)
|
||||
def _STY_abs(cpu): cpu.write8(cpu.addr_abs(), cpu.y)
|
||||
|
||||
def _AND_imm(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_imm()))
|
||||
def _AND_zp(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_zp()))
|
||||
def _AND_abs(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_abs()))
|
||||
def _ORA_imm(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_imm()))
|
||||
def _ORA_zp(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_zp()))
|
||||
def _EOR_imm(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_imm()))
|
||||
def _EOR_abs(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_abs()))
|
||||
|
||||
def _adc(cpu, m):
|
||||
r = cpu.a + m + (1 if cpu.c else 0)
|
||||
cpu.c = r > 0xFF
|
||||
cpu.v = ((cpu.a ^ r) & (m ^ r) & 0x80) != 0
|
||||
cpu.a = cpu.set_nz(r & 0xFF)
|
||||
|
||||
def _ADC_imm(cpu): _adc(cpu, cpu.read8(cpu.addr_imm()))
|
||||
def _ADC_zp(cpu): _adc(cpu, cpu.read8(cpu.addr_zp()))
|
||||
def _ADC_abs(cpu): _adc(cpu, cpu.read8(cpu.addr_abs()))
|
||||
def _ADC_absy(cpu): _adc(cpu, cpu.read8(cpu.addr_absy()))
|
||||
|
||||
def _sbc(cpu, m):
|
||||
_adc(cpu, m ^ 0xFF)
|
||||
|
||||
def _SBC_imm(cpu): _sbc(cpu, cpu.read8(cpu.addr_imm()))
|
||||
def _SBC_zp(cpu): _sbc(cpu, cpu.read8(cpu.addr_zp()))
|
||||
def _SBC_abs(cpu): _sbc(cpu, cpu.read8(cpu.addr_abs()))
|
||||
def _SBC_absx(cpu): _sbc(cpu, cpu.read8(cpu.addr_absx()))
|
||||
def _SBC_absy(cpu): _sbc(cpu, cpu.read8(cpu.addr_absy()))
|
||||
|
||||
def _AND_absx(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_absx()))
|
||||
def _AND_absy(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_absy()))
|
||||
def _AND_indy(cpu): cpu.a = cpu.set_nz(cpu.a & cpu.read8(cpu.addr_indy()))
|
||||
def _ORA_absx(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_absx()))
|
||||
def _ORA_abs(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_abs()))
|
||||
def _ORA_zpx(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_zpx()))
|
||||
def _ORA_indy(cpu): cpu.a = cpu.set_nz(cpu.a | cpu.read8(cpu.addr_indy()))
|
||||
def _EOR_zp(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_zp()))
|
||||
def _EOR_zpx(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_zpx()))
|
||||
def _EOR_absx(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_absx()))
|
||||
def _EOR_absy(cpu): cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(cpu.addr_absy()))
|
||||
def _ADC_zpx(cpu): _adc(cpu, cpu.read8(cpu.addr_zpx()))
|
||||
def _ADC_absx(cpu): _adc(cpu, cpu.read8(cpu.addr_absx()))
|
||||
def _CMP_absx(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_absx()))
|
||||
def _CMP_absy(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_absy()))
|
||||
def _CMP_zpx(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_zpx()))
|
||||
|
||||
def _JMP_ind(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
cpu.pc = cpu.read8(addr) | (cpu.read8((addr & 0xFF00) | ((addr + 1) & 0xFF)) << 8)
|
||||
|
||||
def _BIT_zp(cpu):
|
||||
v = cpu.read8(cpu.addr_zp())
|
||||
cpu.z = (cpu.a & v) == 0
|
||||
cpu.n = (v & 0x80) != 0
|
||||
cpu.v = (v & 0x40) != 0
|
||||
|
||||
def _BIT_abs(cpu):
|
||||
v = cpu.read8(cpu.addr_abs())
|
||||
cpu.z = (cpu.a & v) == 0
|
||||
cpu.n = (v & 0x80) != 0
|
||||
cpu.v = (v & 0x40) != 0
|
||||
|
||||
def _ASL_zp(cpu):
|
||||
addr = cpu.addr_zp()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x80) != 0
|
||||
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||
|
||||
def _ASL_abs(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x80) != 0
|
||||
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||
|
||||
def _LSR_abs(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x01) != 0
|
||||
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||
|
||||
def _ROL_abs(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x80) != 0
|
||||
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROR_abs(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x01) != 0
|
||||
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROR_zp(cpu):
|
||||
addr = cpu.addr_zp()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x01) != 0
|
||||
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _STA_zpx(cpu_): cpu_.write8(cpu_.addr_zpx(), cpu_.a)
|
||||
def _TSX(cpu): cpu.x = cpu.set_nz(cpu.sp)
|
||||
def _TXS(cpu): cpu.sp = cpu.x
|
||||
def _CLI(cpu): pass
|
||||
def _CLV(cpu): cpu.v = False
|
||||
def _SED(cpu): pass # decimal mode ignored
|
||||
def _STX_zpy(cpu):
|
||||
a = (cpu.read8(cpu.pc) + cpu.y) & 0xFF; cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
cpu.write8(a, cpu.x)
|
||||
def _STY_zpx(cpu): cpu.write8(cpu.addr_zpx(), cpu.y)
|
||||
def _LDX_zpy(cpu):
|
||||
a = (cpu.read8(cpu.pc) + cpu.y) & 0xFF; cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
cpu.x = cpu.set_nz(cpu.read8(a))
|
||||
def _LDY_zpx(cpu): cpu.y = cpu.set_nz(cpu.read8(cpu.addr_zpx()))
|
||||
|
||||
def _cmp_with(cpu, lhs, m):
|
||||
r = (lhs - m) & 0x1FF
|
||||
cpu.c = lhs >= m
|
||||
cpu.set_nz(r & 0xFF)
|
||||
|
||||
def _CMP_imm(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_imm()))
|
||||
def _CMP_zp(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_zp()))
|
||||
def _CMP_abs(cpu): _cmp_with(cpu, cpu.a, cpu.read8(cpu.addr_abs()))
|
||||
def _CPX_imm(cpu): _cmp_with(cpu, cpu.x, cpu.read8(cpu.addr_imm()))
|
||||
def _CPX_abs(cpu): _cmp_with(cpu, cpu.x, cpu.read8(cpu.addr_abs()))
|
||||
def _CPY_imm(cpu): _cmp_with(cpu, cpu.y, cpu.read8(cpu.addr_imm()))
|
||||
|
||||
def _ASL_a(cpu):
|
||||
cpu.c = (cpu.a & 0x80) != 0
|
||||
cpu.a = cpu.set_nz((cpu.a << 1) & 0xFF)
|
||||
|
||||
def _LSR_a(cpu):
|
||||
cpu.c = (cpu.a & 0x01) != 0
|
||||
cpu.a = cpu.set_nz(cpu.a >> 1)
|
||||
|
||||
def _LSR_zp(cpu):
|
||||
addr = cpu.addr_zp()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x01) != 0
|
||||
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||
|
||||
def _ROL_a(cpu):
|
||||
new_c = (cpu.a & 0x80) != 0
|
||||
cpu.a = cpu.set_nz(((cpu.a << 1) | (1 if cpu.c else 0)) & 0xFF)
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROL_zp(cpu):
|
||||
addr = cpu.addr_zp()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x80) != 0
|
||||
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROR_a(cpu):
|
||||
new_c = (cpu.a & 0x01) != 0
|
||||
cpu.a = cpu.set_nz(((cpu.a >> 1) | (0x80 if cpu.c else 0)) & 0xFF)
|
||||
cpu.c = new_c
|
||||
|
||||
def _INC_abs(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||
|
||||
def _INC_absx(cpu):
|
||||
addr = cpu.addr_absx()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||
|
||||
def _INC_zp(cpu):
|
||||
addr = cpu.addr_zp()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||
|
||||
def _INC_zpx(cpu):
|
||||
addr = cpu.addr_zpx()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) + 1) & 0xFF))
|
||||
|
||||
def _DEC_abs(cpu):
|
||||
addr = cpu.addr_abs()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||
|
||||
def _DEC_absx(cpu):
|
||||
addr = cpu.addr_absx()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||
|
||||
def _DEC_zp(cpu):
|
||||
addr = cpu.addr_zp()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||
|
||||
def _DEC_zpx(cpu):
|
||||
addr = cpu.addr_zpx()
|
||||
cpu.write8(addr, cpu.set_nz((cpu.read8(addr) - 1) & 0xFF))
|
||||
|
||||
def _ASL_zpx(cpu):
|
||||
addr = cpu.addr_zpx()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x80) != 0
|
||||
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||
|
||||
def _ASL_absx(cpu):
|
||||
addr = cpu.addr_absx()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x80) != 0
|
||||
cpu.write8(addr, cpu.set_nz((v << 1) & 0xFF))
|
||||
|
||||
def _LSR_zpx(cpu):
|
||||
addr = cpu.addr_zpx()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x01) != 0
|
||||
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||
|
||||
def _LSR_absx(cpu):
|
||||
addr = cpu.addr_absx()
|
||||
v = cpu.read8(addr)
|
||||
cpu.c = (v & 0x01) != 0
|
||||
cpu.write8(addr, cpu.set_nz(v >> 1))
|
||||
|
||||
def _ROL_zpx(cpu):
|
||||
addr = cpu.addr_zpx()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x80) != 0
|
||||
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROL_absx(cpu):
|
||||
addr = cpu.addr_absx()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x80) != 0
|
||||
v = ((v << 1) | (1 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROR_zpx(cpu):
|
||||
addr = cpu.addr_zpx()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x01) != 0
|
||||
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _ROR_absx(cpu):
|
||||
addr = cpu.addr_absx()
|
||||
v = cpu.read8(addr)
|
||||
new_c = (v & 0x01) != 0
|
||||
v = ((v >> 1) | (0x80 if cpu.c else 0)) & 0xFF
|
||||
cpu.write8(addr, cpu.set_nz(v))
|
||||
cpu.c = new_c
|
||||
|
||||
def _LDA_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
cpu.a = cpu.set_nz(cpu.read8(addr))
|
||||
|
||||
def _STA_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
cpu.write8(addr, cpu.a)
|
||||
|
||||
def _AND_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
cpu.a = cpu.set_nz(cpu.a & cpu.read8(addr))
|
||||
|
||||
def _ORA_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
cpu.a = cpu.set_nz(cpu.a | cpu.read8(addr))
|
||||
|
||||
def _EOR_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
cpu.a = cpu.set_nz(cpu.a ^ cpu.read8(addr))
|
||||
|
||||
def _ADC_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
_adc(cpu, cpu.read8(addr))
|
||||
|
||||
def _SBC_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
_sbc(cpu, cpu.read8(addr))
|
||||
|
||||
def _CMP_indx(cpu):
|
||||
zp = cpu.read8(cpu.pc); cpu.pc = (cpu.pc + 1) & 0xFFFF
|
||||
addr_ptr = (zp + cpu.x) & 0xFF
|
||||
addr = cpu.ram[addr_ptr] | (cpu.ram[(addr_ptr + 1) & 0xFF] << 8)
|
||||
_cmp_with(cpu, cpu.a, cpu.read8(addr))
|
||||
|
||||
def _CPX_zp(cpu):
|
||||
_cmp_with(cpu, cpu.x, cpu.read8(cpu.addr_zp()))
|
||||
|
||||
def _CPY_zp(cpu):
|
||||
_cmp_with(cpu, cpu.y, cpu.read8(cpu.addr_zp()))
|
||||
|
||||
def _BPL(cpu): cpu.branch(not cpu.n)
|
||||
def _BMI(cpu): cpu.branch(cpu.n)
|
||||
def _BNE(cpu): cpu.branch(not cpu.z)
|
||||
def _BEQ(cpu): cpu.branch(cpu.z)
|
||||
def _BCC(cpu): cpu.branch(not cpu.c)
|
||||
def _BCS(cpu): cpu.branch(cpu.c)
|
||||
def _BVC(cpu): cpu.branch(not cpu.v)
|
||||
def _BVS(cpu): cpu.branch(cpu.v)
|
||||
|
||||
def _JMP_abs(cpu): cpu.pc = cpu.addr_abs()
|
||||
def _JSR(cpu):
|
||||
target = cpu.addr_abs()
|
||||
cpu.push16((cpu.pc - 1) & 0xFFFF)
|
||||
cpu.pc = target
|
||||
def _RTS(cpu):
|
||||
cpu.pc = (cpu.pop16() + 1) & 0xFFFF
|
||||
|
||||
|
||||
# Build dispatch table
|
||||
_DISPATCH = {
|
||||
0x00: _NOP, # treat BRK as NOP (we never expect it)
|
||||
0x18: _CLC, 0x38: _SEC, 0x58: _NOP, 0x78: _SEI, 0xD8: _CLD,
|
||||
0x08: _PHP, 0x28: _PLP, 0x48: _PHA, 0x68: _PLA,
|
||||
0x8A: _TXA, 0xAA: _TAX, 0x98: _TYA, 0xA8: _TAY,
|
||||
0xE8: _INX, 0xC8: _INY, 0xCA: _DEX, 0x88: _DEY,
|
||||
0xA9: _LDA_imm, 0xA5: _LDA_zp, 0xB5: _LDA_zpx, 0xAD: _LDA_abs,
|
||||
0xBD: _LDA_absx, 0xB9: _LDA_absy, 0xB1: _LDA_indy,
|
||||
0xA2: _LDX_imm, 0xAE: _LDX_abs, 0xBE: _LDX_absy, 0xA6: _LDX_zp,
|
||||
0xA0: _LDY_imm, 0xAC: _LDY_abs, 0xBC: _LDY_absx, 0xA4: _LDY_zp,
|
||||
0x85: _STA_zp, 0x95: _STA_zpx, 0x8D: _STA_abs,
|
||||
0x9D: _STA_absx, 0x99: _STA_absy, 0x91: _STA_indy,
|
||||
0x86: _STX_zp, 0x8E: _STX_abs,
|
||||
0x84: _STY_zp, 0x8C: _STY_abs,
|
||||
0x29: _AND_imm, 0x25: _AND_zp, 0x2D: _AND_abs,
|
||||
0x3D: _AND_absx, 0x39: _AND_absy, 0x31: _AND_indy,
|
||||
0x09: _ORA_imm, 0x05: _ORA_zp, 0x0D: _ORA_abs, 0x15: _ORA_zpx,
|
||||
0x1D: _ORA_absx, 0x11: _ORA_indy,
|
||||
0x49: _EOR_imm, 0x4D: _EOR_abs, 0x45: _EOR_zp, 0x55: _EOR_zpx,
|
||||
0x5D: _EOR_absx, 0x59: _EOR_absy,
|
||||
0x69: _ADC_imm, 0x65: _ADC_zp, 0x6D: _ADC_abs, 0x79: _ADC_absy,
|
||||
0x75: _ADC_zpx, 0x7D: _ADC_absx,
|
||||
0xE9: _SBC_imm, 0xE5: _SBC_zp, 0xED: _SBC_abs, 0xFD: _SBC_absx, 0xF9: _SBC_absy,
|
||||
0xC9: _CMP_imm, 0xC5: _CMP_zp, 0xCD: _CMP_abs, 0xD5: _CMP_zpx,
|
||||
0xDD: _CMP_absx, 0xD9: _CMP_absy,
|
||||
0xE0: _CPX_imm, 0xEC: _CPX_abs,
|
||||
0xC0: _CPY_imm,
|
||||
0x24: _BIT_zp, 0x2C: _BIT_abs,
|
||||
0x0A: _ASL_a, 0x06: _ASL_zp, 0x0E: _ASL_abs,
|
||||
0x4A: _LSR_a, 0x46: _LSR_zp, 0x4E: _LSR_abs,
|
||||
0x2A: _ROL_a, 0x26: _ROL_zp, 0x2E: _ROL_abs,
|
||||
0x6A: _ROR_a, 0x66: _ROR_zp, 0x6E: _ROR_abs,
|
||||
0xEE: _INC_abs, 0xFE: _INC_absx, 0xE6: _INC_zp, 0xF6: _INC_zpx,
|
||||
0xCE: _DEC_abs, 0xDE: _DEC_absx, 0xC6: _DEC_zp, 0xD6: _DEC_zpx,
|
||||
0x16: _ASL_zpx, 0x1E: _ASL_absx,
|
||||
0x56: _LSR_zpx, 0x5E: _LSR_absx,
|
||||
0x36: _ROL_zpx, 0x3E: _ROL_absx,
|
||||
0x76: _ROR_zpx, 0x7E: _ROR_absx,
|
||||
0xA1: _LDA_indx, 0x81: _STA_indx, 0x21: _AND_indx,
|
||||
0x01: _ORA_indx, 0x41: _EOR_indx,
|
||||
0x61: _ADC_indx, 0xE1: _SBC_indx, 0xC1: _CMP_indx,
|
||||
0xE4: _CPX_zp, 0xC4: _CPY_zp,
|
||||
0x6C: _JMP_ind,
|
||||
0x9A: _TXS, 0xBA: _TSX,
|
||||
0x58: _CLI, 0xB8: _CLV, 0xF8: _SED,
|
||||
0x96: _STX_zpy, 0x94: _STY_zpx,
|
||||
0xB6: _LDX_zpy, 0xB4: _LDY_zpx,
|
||||
0x10: _BPL, 0x30: _BMI, 0xD0: _BNE, 0xF0: _BEQ,
|
||||
0x90: _BCC, 0xB0: _BCS, 0x50: _BVC, 0x70: _BVS,
|
||||
0x4C: _JMP_abs, 0x20: _JSR, 0x60: _RTS,
|
||||
0xEA: _NOP,
|
||||
}
|
||||
78
assets/depackSpeechRam.py
Executable file
78
assets/depackSpeechRam.py
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run Space Taxi's depacker to produce a FLAT 64K RAM image.
|
||||
|
||||
Why this exists: the disk's "SPACE TAXI+" is a crunched single file (it
|
||||
matches the running game's memory in 0.6% of bytes), so the speech
|
||||
samples cannot be read out of it statically. And a VICE memory dump is
|
||||
no good either -- it captures the banked-IN view, so $A000-$BFFF comes
|
||||
back as BASIC ROM and 13 of the 15 utterances are missing. Every
|
||||
mem0000*.bin in this tree has that problem.
|
||||
|
||||
The way out needs no emulator install: cpu6502.py (next to this script) is a
|
||||
plain 6502 core over a flat 64K bytearray with no banking and no I/O,
|
||||
which is all a depacker needs. Load the .prg, run from the BASIC SYS
|
||||
entry, and the depacked image IS the RAM -- $A000-$BFFF included.
|
||||
|
||||
Feed the result to extractSpeech.py.
|
||||
|
||||
Usage: depackSpeechRam.py [spaceTaxiPlus.prg] [out.bin]
|
||||
Defaults resolve inside stuff/ (gitignored research
|
||||
area, same as genC64Data.py's inputs).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
STUFF = os.path.join(HERE, "..", "stuff")
|
||||
|
||||
# The BASIC stub is `0B08 D307 9E "2059" 00` -- SYS 2059.
|
||||
ENTRY = 0x080B
|
||||
MAX_STEPS = 60000000
|
||||
CHECK_MASK = 0xFFFF # how often to test for completion
|
||||
|
||||
# Completion test: the $9B00 index table is populated and the first
|
||||
# utterance's $FE lead-in is in place.
|
||||
TABLE_ADDR = 0x9B00
|
||||
FIRST_UTT = 0x9B40
|
||||
|
||||
|
||||
def main():
|
||||
prgPath = sys.argv[1] if len(sys.argv) > 1 else os.path.join(STUFF, "extract", "SPACE TAXI+.prg")
|
||||
outPath = sys.argv[2] if len(sys.argv) > 2 else os.path.join(STUFF, "staxi-ram.bin")
|
||||
|
||||
sys.path.insert(0, HERE)
|
||||
from cpu6502 import Cpu6502
|
||||
|
||||
prg = open(prgPath, "rb").read()
|
||||
load = prg[0] | (prg[1] << 8)
|
||||
ram = bytearray(65536)
|
||||
ram[load:load + len(prg) - 2] = prg[2:]
|
||||
print("loaded %s at $%04X..$%04X" % (os.path.basename(prgPath), load, load + len(prg) - 3))
|
||||
|
||||
cpu = Cpu6502(ram)
|
||||
cpu.pc = ENTRY
|
||||
steps = 0
|
||||
done = False
|
||||
try:
|
||||
while steps < MAX_STEPS:
|
||||
cpu.step()
|
||||
steps += 1
|
||||
if (steps & CHECK_MASK) == 0:
|
||||
if ram[TABLE_ADDR] != 0 and ram[FIRST_UTT] == 0xFE:
|
||||
done = True
|
||||
break
|
||||
except Exception as exc:
|
||||
# A depacker that runs off into I/O or an undocumented opcode
|
||||
# still usually gets the data down first, so report and carry on
|
||||
# to the write rather than throwing the work away.
|
||||
print("6502 stopped after %d steps at PC=$%04X: %s" % (steps, cpu.pc, exc))
|
||||
|
||||
open(outPath, "wb").write(bytes(ram))
|
||||
print("%d steps, depack %s -> %s" % (steps, "detected" if done else "NOT detected", outPath))
|
||||
if not done:
|
||||
print("WARNING: completion check never fired; the image may be partial.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
115
assets/dumpScenes.py
Normal file
115
assets/dumpScenes.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env python3
|
||||
# Drive the captured Space Taxi RAM through the 6502 emulator to
|
||||
# decompress every scene the game knows how to load.
|
||||
#
|
||||
# Approach:
|
||||
# 1. Load a VICE memory dump as flat 64KB RAM. The dump captured at
|
||||
# runtime contains the decompressor at $9656, the scene pointer
|
||||
# table at $9600, and (we hope) the compressed data the pointers
|
||||
# reach into.
|
||||
# 2. Sanity check: run scene ID 1 (the captured live level) through
|
||||
# $6283 and compare the resulting screen buffer at $7530-$7929
|
||||
# with the live screen RAM at $0400-$07E7 already in the dump.
|
||||
# If they match, the emulator + dump together can reproduce any
|
||||
# scene. If not, we know something's missing (likely hidden RAM
|
||||
# under ROMs that VICE didn't capture).
|
||||
# 3. If sanity passes: iterate scene IDs 0..23 (lettered levels)
|
||||
# plus known special IDs (title=$24, level-complete=$19) and
|
||||
# dump each scene's screen + color buffer.
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from cpu6502 import Cpu6502
|
||||
|
||||
|
||||
def load_dump(path):
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
if len(raw) == 0x10000:
|
||||
return bytearray(raw)
|
||||
if len(raw) == 0x10002:
|
||||
# 2-byte LE load-addr header
|
||||
return bytearray(raw[2:])
|
||||
raise SystemExit(f'{path}: unexpected dump size {len(raw)}')
|
||||
|
||||
|
||||
# Memory map landmarks (from MECHANICS.md):
|
||||
ADDR_DECOMP_ENTRY = 0x6283 # bank-switch wrapper around $9656
|
||||
SCREEN_BUF_BASE = 0x7530 # decompressed screen RAM target
|
||||
SCREEN_BUF_SIZE = 0x400 # 4 pages = 1024 bytes
|
||||
COLOR_BUF_BASE = 0x7918 # decompressed color RAM target
|
||||
COLOR_BUF_SIZE = 0x400
|
||||
|
||||
|
||||
def run_scene(ram, scene_id):
|
||||
"""Run $6283(X=scene_id) on a fresh copy of `ram`. Returns a tuple
|
||||
(screen_buf, color_buf, final_ram) so the caller can compare or
|
||||
examine state."""
|
||||
work = bytearray(ram) # copy so callers can keep the original
|
||||
cpu = Cpu6502(work)
|
||||
cpu.a = scene_id
|
||||
cpu.x = scene_id
|
||||
cpu.y = 0
|
||||
cpu.call(ADDR_DECOMP_ENTRY)
|
||||
screen = bytes(work[SCREEN_BUF_BASE:SCREEN_BUF_BASE + SCREEN_BUF_SIZE])
|
||||
color = bytes(work[COLOR_BUF_BASE:COLOR_BUF_BASE + COLOR_BUF_SIZE])
|
||||
return screen, color, work
|
||||
|
||||
|
||||
def compare_screen(decomp_screen, live_screen, label):
|
||||
"""Compare 1024-byte decompressed screen vs live screen RAM.
|
||||
Only the first 1000 bytes are visible (40x25). Bytes 1000..1023
|
||||
are unused tail / overlap with color RAM, so we ignore them."""
|
||||
a = decomp_screen[:1000]
|
||||
b = live_screen[:1000]
|
||||
if a == b:
|
||||
print(f' {label}: MATCH')
|
||||
return True
|
||||
# Show how many cells differ and a sample
|
||||
diffs = [(i, a[i], b[i]) for i in range(1000) if a[i] != b[i]]
|
||||
print(f' {label}: {len(diffs)} / 1000 cells differ')
|
||||
for i, av, bv in diffs[:6]:
|
||||
row, col = divmod(i, 40)
|
||||
print(f' cell ({col:2d},{row:2d}): decomp=${av:02X} live=${bv:02X}')
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print('usage: dumpScenes.py <dump-path> [scene-id]', file=sys.stderr)
|
||||
sys.exit(2)
|
||||
ram = load_dump(sys.argv[1])
|
||||
|
||||
# Figure out what the captured state is, then pick the scene to
|
||||
# test against. $7215 = current level number (0..23 = A..X),
|
||||
# but the title screen / specials don't update it -- the
|
||||
# most-recent JSR $6283 used scene $24 (title) or $19 (other
|
||||
# special). Default to the level-number scene, but allow override.
|
||||
state_level = ram[0x7215]
|
||||
if len(sys.argv) >= 3:
|
||||
scene = int(sys.argv[2], 0)
|
||||
else:
|
||||
# Heuristic: if state sentinel $07A6 says we're on the title,
|
||||
# the live screen matches scene 36 ($24). Otherwise we're
|
||||
# mid-gameplay and the live screen matches scene = level.
|
||||
# Title state in this game: top-left tile is the inner frame
|
||||
# corner $5D / $5C. For now just try scene 36 then scene N.
|
||||
scene = 36
|
||||
print(f'=== Sanity test: scene {scene} (${scene:02X}) vs live screen RAM ===')
|
||||
print(f' captured state: $7215={state_level} $07A6=${ram[0x07A6]:02X}')
|
||||
try:
|
||||
scr, col, _ = run_scene(ram, scene)
|
||||
except Exception as e:
|
||||
print(f' emulator raised: {type(e).__name__}: {e}')
|
||||
return
|
||||
live_scr = bytes(ram[0x0400:0x0400 + 1024])
|
||||
live_col = bytes(b & 0x0F for b in ram[0xD800:0xD800 + 1024])
|
||||
compare_screen(scr, live_scr, 'screen')
|
||||
masked_col = bytes(b & 0x0F for b in col)
|
||||
compare_screen(masked_col, live_col, 'color ')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# extractFlame.py -- wire the engine-flame sprite cels from the C64 ROM
|
||||
# image (stuff/spacetaxi/raw.bin) into the sprite sheet's flame row.
|
||||
#
|
||||
# The flame is C64 sprite 2, MULTICOLOR (verified: $D01C bit2 set). Its
|
||||
# per-direction cel is chosen from the pointer table at $6DB0 (indexed by
|
||||
# the direction mask $716A). Reading raw.bin at $6DB0 (2-byte load-addr
|
||||
# header -> +2) gives, and the port's kFlameCelByDirMask mirrors exactly:
|
||||
# mask 1 UP -> block $D8 mask 8 RIGHT -> block $D7
|
||||
# mask 2 DOWN -> block $D4 mask 9 UP+RIGHT -> block $D3
|
||||
# mask 4 LEFT -> block $D5 mask 10 DOWN+RIGHT-> block $D6
|
||||
# mask 5 UP+LEFT -> block $D2
|
||||
# mask 6 DOWN+LEFT -> block $D1
|
||||
# flameCels[0..7] load from .spr cels 18..25 (sheet row 2, cols 0..7), so
|
||||
# the cel order below is the kFlameCelByDirMask index order.
|
||||
#
|
||||
# Sprite data lives at block*64 in VIC bank 0 ($3000-$37FF). Each C64
|
||||
# multicolor sprite is 12 MC-pixels x 21 rows (3 bytes/row, 2 bits/pixel);
|
||||
# each MC pixel is 2 hi-res pixels, so it maps 1 MC pixel -> 2 of the
|
||||
# port's 24-wide 4bpp cel pixels. Colors are the live-level flame palette
|
||||
# ($7D05->$D025=$02 red = MC0, $7D06->$D026=$07 yellow = MC1; sprite-2
|
||||
# colour = orange), mapped to sprites.png's indexed palette.
|
||||
#
|
||||
# Patches ONLY the flame row (row 2) of sprites.png -- taxi (row 0),
|
||||
# passenger (row 1), and font.png are left untouched. Re-run the .spr
|
||||
# bake afterwards (make/<port>.mk STAXI_SPR rule).
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
sys.exit("extractFlame.py: needs Pillow (pip install pillow)")
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
RAW = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin")
|
||||
PNG = os.path.join(HERE, "sprites", "sprites.png")
|
||||
|
||||
CEL = 24 # cel is 24x24 px
|
||||
LOAD_HDR = 2 # raw.bin 2-byte load-address header
|
||||
|
||||
# flameCels index -> C64 sprite block (from $6DB0 / kFlameCelByDirMask).
|
||||
FLAME_BLOCKS = [0xD8, 0xD4, 0xD5, 0xD2, 0xD1, 0xD7, 0xD3, 0xD6]
|
||||
|
||||
# Multicolor 2-bit pixel -> palette index. The engine renders sprite
|
||||
# cels through kDefaultPalette (stRender.c), whose index IS the C64 VIC-II
|
||||
# colour code -- the sprite's own baked palette is ignored (loadSpriteSheet
|
||||
# passes NULL). So the C64 flame colours map DIRECTLY by code:
|
||||
# 00 transparent(0), 01 MC0=$D025=$02 red(2), 11 MC1=$D026=$07 yellow(7),
|
||||
# 10 sprite-2 colour -> orange(8) (barely used by these cels).
|
||||
# (NOT the sprites.png preview-palette indices, which differ.)
|
||||
MC_TO_INDEX = {0: 0, 1: 2, 2: 8, 3: 7}
|
||||
|
||||
FLAME_ROW_Y = 2 * CEL # sheet row 2 (flame row) top edge
|
||||
|
||||
|
||||
def loadRaw():
|
||||
with open(RAW, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def celFromBlock(raw, block):
|
||||
# Return a 24x24 list-of-rows of palette indices for one flame block.
|
||||
base = LOAD_HDR + block * 64
|
||||
cel = [[0] * CEL for _ in range(CEL)]
|
||||
for row in range(21): # C64 sprite is 21 rows tall
|
||||
for byteIdx in range(3): # 3 bytes/row
|
||||
b = raw[base + row * 3 + byteIdx]
|
||||
for pair in range(4): # 4 MC pixels per byte (MSB first)
|
||||
mc = (b >> ((3 - pair) * 2)) & 3
|
||||
idx = MC_TO_INDEX[mc]
|
||||
px = (byteIdx * 4 + pair) * 2 # MC pixel -> 2 cel pixels
|
||||
cel[row][px] = idx
|
||||
cel[row][px + 1] = idx
|
||||
return cel
|
||||
|
||||
|
||||
def main():
|
||||
raw = loadRaw()
|
||||
im = Image.open(PNG)
|
||||
if im.mode != "P":
|
||||
sys.exit(f"extractFlame.py: {PNG} is {im.mode}, expected indexed 'P'")
|
||||
px = im.load()
|
||||
nonEmpty = 0
|
||||
for celIdx, block in enumerate(FLAME_BLOCKS):
|
||||
cel = celFromBlock(raw, block)
|
||||
x0 = celIdx * CEL
|
||||
opaque = 0
|
||||
for y in range(CEL):
|
||||
for x in range(CEL):
|
||||
v = cel[y][x]
|
||||
px[x0 + x, FLAME_ROW_Y + y] = v
|
||||
if v != 0:
|
||||
opaque += 1
|
||||
nonEmpty += 1 if opaque else 0
|
||||
print(f" flameCels[{celIdx}] block ${block:02X}: {opaque} opaque px -> cel {18 + celIdx}")
|
||||
im.save(PNG)
|
||||
print(f"extractFlame: patched {nonEmpty}/8 flame cels into {os.path.relpath(PNG, HERE)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
225
assets/extractSpeech.py
Executable file
225
assets/extractSpeech.py
Executable file
|
|
@ -0,0 +1,225 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Extract Space Taxi's digitised speech from a C64 memory dump.
|
||||
|
||||
FORMAT (reverse-engineered from bankedSfxLoad at $9802, see
|
||||
stuff/live-level1.lst). It is NOT 4-bit $D418 digi and it is
|
||||
NOT the SID speech synth. It is 1-BIT RUN-LENGTH audio:
|
||||
|
||||
* An index table at $9B00 holds [code, ptrLo, ptrHi] triples,
|
||||
terminated by a zero code. `code` is the PETSCII character the game
|
||||
asks for -- 'H' 'T' '!' spell "Hey Taxi!", 'P' + digit + '?' spell
|
||||
"Pad two, please", 'U' is "up".
|
||||
* Each utterance is a byte stream. For every byte the player busy-waits
|
||||
that many passes of a fixed delay loop and then TOGGLES bit 0 of
|
||||
$D417, which flips voice 1 in and out of the filter -- a 1-bit
|
||||
speaker. So each byte is the length of one half-cycle.
|
||||
* $FE is a raster sync (the player waits on $D011), $FF ends the
|
||||
utterance.
|
||||
|
||||
The delay loop at $98A1..$98AA costs 27 cycles per pass and the
|
||||
toggle/advance/fetch tail ($98AC..$987F) another 33, hence CYCLES().
|
||||
|
||||
LIMITATION: a plain VICE memory dump captures the BANKED-IN view, so
|
||||
$A000-$BFFF comes back as BASIC ROM ("CBMBASIC" at $A004) and most of the
|
||||
speech is missing -- the player banks BASIC out ($01 = $06) precisely
|
||||
because the samples live in the RAM underneath. Dump it with the ROMs
|
||||
banked out, e.g. in the VICE monitor:
|
||||
|
||||
bank ram
|
||||
save "staxi-ram.bin" 0 0000 ffff
|
||||
|
||||
Usage: extractSpeech.py <dump.bin> [outDir]
|
||||
The dump may have a 2-byte load address or not; both are handled.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import wave
|
||||
|
||||
NTSC_HZ = 1022727.0 # the port already uses this (ST_SID_HZ_NUM)
|
||||
OUT_RATE = 22050
|
||||
TABLE_ADDR = 0x9B00
|
||||
END_MARK = 0xFF
|
||||
SYNC_MARK = 0xFE
|
||||
|
||||
|
||||
def cyclesFor(count):
|
||||
"""Half-cycle length in CPU cycles for one stream byte."""
|
||||
return 27 * count + 33
|
||||
|
||||
|
||||
def loadDump(path):
|
||||
"""Return a 64K-addressable bytes object from a dump with or without
|
||||
a load-address header."""
|
||||
raw = open(path, "rb").read()
|
||||
if len(raw) in (65538, 65536 + 2):
|
||||
return raw[2:]
|
||||
if len(raw) == 65536:
|
||||
return raw
|
||||
# Headerless dump starting at $0801 (what mem0801.prg turned out to be).
|
||||
pad = bytearray(65536)
|
||||
pad[0x0801:0x0801 + len(raw)] = raw[:65536 - 0x0801]
|
||||
return bytes(pad)
|
||||
|
||||
|
||||
def readTable(mem):
|
||||
"""[(code, pointer)] from the $9B00 index."""
|
||||
out = []
|
||||
y = 0
|
||||
while y < 0x300:
|
||||
code = mem[TABLE_ADDR + y]
|
||||
if code == 0 or code == 0xFF:
|
||||
break
|
||||
out.append((code, mem[TABLE_ADDR + y + 1] | (mem[TABLE_ADDR + y + 2] << 8)))
|
||||
y += 3
|
||||
return out
|
||||
|
||||
|
||||
def decode(mem, ptr, limit=0x4000):
|
||||
"""Return (runs, byteCount, sawEnd). runs is [(level, cycles)]."""
|
||||
runs = []
|
||||
level = 0
|
||||
addr = ptr
|
||||
while addr - ptr < limit:
|
||||
b = mem[addr]
|
||||
addr += 1
|
||||
if b == END_MARK:
|
||||
return runs, addr - ptr, True
|
||||
if b == SYNC_MARK:
|
||||
runs.append((level, int(NTSC_HZ / 60.0)))
|
||||
continue
|
||||
runs.append((level, cyclesFor(b)))
|
||||
level ^= 1
|
||||
return runs, addr - ptr, False
|
||||
|
||||
|
||||
def writeWav(runs, path):
|
||||
samples = bytearray()
|
||||
carry = 0.0
|
||||
perOut = NTSC_HZ / OUT_RATE
|
||||
for level, cyc in runs:
|
||||
carry += cyc
|
||||
n = int(carry / perOut)
|
||||
carry -= n * perOut
|
||||
samples += struct.pack("<h", 12000 if level else -12000) * n
|
||||
w = wave.open(path, "wb")
|
||||
w.setnchannels(1)
|
||||
w.setsampwidth(2)
|
||||
w.setframerate(OUT_RATE)
|
||||
w.writeframes(bytes(samples))
|
||||
w.close()
|
||||
return len(samples) // 2 / float(OUT_RATE)
|
||||
|
||||
|
||||
def emitC(mem, table, romLo, outDir):
|
||||
"""Write stSpeechData.c/.h: the raw run-length streams plus an index.
|
||||
|
||||
The streams ship as-is (about 7.4 KB for all 15) and the game expands
|
||||
them to PCM on the fly -- rendering them at bake time would be ~38 KB
|
||||
at 5 kHz, which will not fit an IIgs bank alongside everything else.
|
||||
"""
|
||||
blobs = []
|
||||
index = []
|
||||
for code, ptr in table:
|
||||
if ptr >= romLo:
|
||||
continue
|
||||
runs, nbytes, sawEnd = decode(mem, ptr)
|
||||
nxt = next((q for _, q in sorted(table, key=lambda e: e[1]) if q > ptr), None)
|
||||
if not sawEnd or (nxt is not None and nbytes > nxt - ptr):
|
||||
continue
|
||||
index.append((code, sum(len(b) for b in blobs), nbytes))
|
||||
blobs.append(bytes(mem[ptr:ptr + nbytes]))
|
||||
data = b"".join(blobs)
|
||||
|
||||
h = os.path.join(outDir, "stSpeechData.h")
|
||||
c = os.path.join(outDir, "stSpeechData.c")
|
||||
with open(h, "w") as fp:
|
||||
fp.write("// Generated by assets/extractSpeech.py. Do not hand-edit.\n"
|
||||
"//\n"
|
||||
"// Space Taxi's digitised speech as the C64 stores it: 1-bit\n"
|
||||
"// run-length streams. Each byte is one half-cycle length; the\n"
|
||||
"// player toggles its output after each. $FE is a raster sync\n"
|
||||
"// and $FF ends an utterance. stAudio.c expands these to PCM\n"
|
||||
"// through the jlAudioPlaySfxStream fill callback.\n\n"
|
||||
"#ifndef ST_SPEECH_DATA_H\n#define ST_SPEECH_DATA_H\n\n"
|
||||
"#include <stdint.h>\n\n"
|
||||
"typedef struct {\n"
|
||||
" uint8_t code; // PETSCII: 'H' 'T' '!' 'P' '1'..'9' '?' 'U'\n"
|
||||
" uint16_t offset; // into kStSpeechRle\n"
|
||||
" uint16_t length;\n"
|
||||
"} StSpeechEntryT;\n\n"
|
||||
"#define ST_SPEECH_COUNT %du\n"
|
||||
"#define ST_SPEECH_RLE_BYTES %du\n\n"
|
||||
"extern const StSpeechEntryT kStSpeechIndex[ST_SPEECH_COUNT];\n"
|
||||
"extern const uint8_t kStSpeechRle[ST_SPEECH_RLE_BYTES];\n\n"
|
||||
"#endif\n" % (len(index), len(data)))
|
||||
with open(c, "w") as fp:
|
||||
fp.write('// Generated by assets/extractSpeech.py. Do not hand-edit.\n\n'
|
||||
'#include "stSpeechData.h"\n\n'
|
||||
'const StSpeechEntryT kStSpeechIndex[ST_SPEECH_COUNT] = {\n')
|
||||
for code, off, ln in index:
|
||||
fp.write(" { 0x%02Xu, %5du, %5du }, // '%s'\n"
|
||||
% (code, off, ln, chr(code) if 33 <= code < 127 else "?"))
|
||||
fp.write("};\n\nconst uint8_t kStSpeechRle[ST_SPEECH_RLE_BYTES] = {\n")
|
||||
for i in range(0, len(data), 16):
|
||||
fp.write(" " + " ".join("0x%02X," % b for b in data[i:i + 16]) + "\n")
|
||||
fp.write("};\n")
|
||||
print("\nemitted %s (%d entries) and %s (%d bytes)" % (h, len(index), c, len(data)))
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit(__doc__)
|
||||
emit = "--c" in sys.argv
|
||||
args = [a for a in sys.argv[1:] if a != "--c"]
|
||||
mem = loadDump(args[0])
|
||||
outDir = args[1] if len(args) > 1 else "speech"
|
||||
os.makedirs(outDir, exist_ok=True)
|
||||
|
||||
if mem[0xA004:0xA00C] == b"CBMBASIC":
|
||||
sys.stderr.write("WARNING: $A000 holds BASIC ROM, not the RAM under it.\n"
|
||||
" Utterances at $A000 and above will not decode.\n"
|
||||
" Re-dump with `bank ram` (see the module docstring).\n\n")
|
||||
|
||||
table = readTable(mem)
|
||||
romLo = 0xA000 if mem[0xA004:0xA00C] == b"CBMBASIC" else 0x10000
|
||||
ordered = sorted(p for _, p in table)
|
||||
good = 0
|
||||
|
||||
print("%-4s %-6s %7s %7s %s" % ("code", "ptr", "bytes", "dur(s)", "file"))
|
||||
for code, ptr in table:
|
||||
label = chr(code) if 33 <= code < 127 else "?"
|
||||
# Utterances are stored back to back, so the next pointer is the
|
||||
# expected end. Both checks matter: BASIC ROM is full of $FF, so
|
||||
# "found a terminator" alone happily reports success on garbage --
|
||||
# it claimed all 15 decoded from a ROM-shadowed dump.
|
||||
nxt = next((q for q in ordered if q > ptr), None)
|
||||
expected = (nxt - ptr) if nxt else None
|
||||
if ptr >= romLo:
|
||||
print("%-4s $%04X %7s %7s -- SKIPPED (shadowed by BASIC ROM in this dump)"
|
||||
% (label, ptr, "-", "-"))
|
||||
continue
|
||||
runs, nbytes, sawEnd = decode(mem, ptr)
|
||||
if not sawEnd:
|
||||
print("%-4s $%04X %7d %7s -- SKIPPED (no $FF terminator)" % (label, ptr, nbytes, "-"))
|
||||
continue
|
||||
# The terminator must land BEFORE the next utterance starts.
|
||||
# (Entries are back to back but may carry a pad byte, so this is
|
||||
# <= rather than ==.) Overrunning means the $FF we found was not
|
||||
# ours -- which is exactly what happens against a ROM shadow.
|
||||
if expected is not None and nbytes > expected:
|
||||
print("%-4s $%04X %7d %7s -- SKIPPED (ran %d bytes, next entry is %d away)"
|
||||
% (label, ptr, nbytes, "-", nbytes, expected))
|
||||
continue
|
||||
path = os.path.join(outDir, "%02X_%s.wav" % (code, label if label != "?" else "x"))
|
||||
dur = writeWav(runs, path)
|
||||
good += 1
|
||||
print("%-4s $%04X %7d %7.2f %s" % (label, ptr, nbytes, dur, os.path.basename(path)))
|
||||
print("\n%d of %d utterances decoded into %s" % (good, len(table), outDir))
|
||||
if emit:
|
||||
emitC(mem, table, romLo, outDir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
assets/font.png
BIN
assets/font.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -4,19 +4,20 @@
|
|||
# exact sprite-to-background collision bits), the standard sprite
|
||||
# bitmaps (cab, passenger, flame, warp, death, intro star, leaving
|
||||
# cels), the demo-mode RNG table ($446A), the SID SFX programs (the
|
||||
# 9-byte blocks fed to $42E9), the engine-flame pointer table ($6DB0)
|
||||
# and the level-intro star velocity tables ($450C/$4513).
|
||||
# 9-byte blocks fed to $42E9) and the engine-flame pointer table ($6DB0).
|
||||
# (The level-intro star velocity tables, $450C/$4513, went with the moving
|
||||
# warp; claudeDocs/spacetaxiSpriteWarp.patch puts them back.)
|
||||
#
|
||||
# Everything comes straight out of stuff/spacetaxi/raw.bin (a VICE
|
||||
# Everything comes straight out of stuff/raw.bin (a VICE
|
||||
# "bank ram" dump, 2-byte load-address header) so the port has one
|
||||
# source of truth: the original bytes. Output:
|
||||
# examples/spacetaxi/stC64Data.c / stC64Data.h (committed, do not edit)
|
||||
# stC64Data.c / stC64Data.h (committed, do not edit)
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
RAW = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin")
|
||||
RAW = os.path.join(HERE, "..", "stuff", "raw.bin")
|
||||
OUT_C = os.path.join(HERE, "..", "stC64Data.c")
|
||||
OUT_H = os.path.join(HERE, "..", "stC64Data.h")
|
||||
|
||||
|
|
@ -55,12 +56,27 @@ def c_bytes(data, per_line=16, indent=" "):
|
|||
return "\n".join(lines)
|
||||
|
||||
|
||||
# The cross-segment accessors the IIgs needs (see the block this emits
|
||||
# into stC64Data.c). These used to be hand-added to the GENERATED file,
|
||||
# which meant re-running this script silently deleted them and broke the
|
||||
# IIgs build -- the header kept declaring them and nothing defined them.
|
||||
# Prototype and return expression per accessor; the header prints the
|
||||
# prototypes aligned, so keep the spacing.
|
||||
ACCESSORS = [
|
||||
("const uint8_t *stC64Charset(void);", "&kStCharset[0][0]"),
|
||||
("const uint8_t *stC64SpriteBitmap(uint8_t idx);", "kStSpriteBitmaps[idx]"),
|
||||
("uint8_t stC64RngByte(uint8_t idx);", "kStRngTable[idx]"),
|
||||
("const uint8_t *stC64SfxProgram(uint8_t idx);", "kStSfxPrograms[idx]"),
|
||||
("uint8_t stC64FlameCel(uint8_t dirMask);", "kStFlameCelByDirMask[dirMask]"),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
raw = load_raw()
|
||||
sprite_count = SPRITE_PTR_LAST - SPRITE_PTR_FIRST + 1
|
||||
|
||||
h = []
|
||||
h.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.")
|
||||
h.append("// Generated by assets/genC64Data.py from stuff/raw.bin.")
|
||||
h.append("// Do not hand-edit; re-run the generator.")
|
||||
h.append("")
|
||||
h.append("#ifndef ST_C64_DATA_H")
|
||||
|
|
@ -100,14 +116,16 @@ def main():
|
|||
h.append("// 8=RIGHT); 0 = no cel for that combination.")
|
||||
h.append("extern const uint8_t kStFlameCelByDirMask[16];")
|
||||
h.append("")
|
||||
h.append("// Level-intro star velocities per sprite 0..6 ($450C / $4513).")
|
||||
h.append("extern const int8_t kStIntroStarDx[7];")
|
||||
h.append("extern const int8_t kStIntroStarDy[7];")
|
||||
h.append("// Cross-segment-safe accessors (see stC64Data.c): reach the tables above")
|
||||
h.append("// by an inter-segment call on the IIgs, where a direct 2-byte data")
|
||||
h.append("// reference from another bank is not relocatable.")
|
||||
for decl in ACCESSORS:
|
||||
h.append(decl[0])
|
||||
h.append("")
|
||||
h.append("#endif")
|
||||
|
||||
c = []
|
||||
c.append("// Generated by assets/genC64Data.py from stuff/spacetaxi/raw.bin.")
|
||||
c.append("// Generated by assets/genC64Data.py from stuff/raw.bin.")
|
||||
c.append("// Do not hand-edit; re-run the generator.")
|
||||
c.append("")
|
||||
c.append('#include "stC64Data.h"')
|
||||
|
|
@ -143,11 +161,25 @@ def main():
|
|||
c.append(c_bytes(flame))
|
||||
c.append("};")
|
||||
c.append("")
|
||||
dx = [b - 256 if b >= 128 else b for b in raw[0x450C:0x450C + 7]]
|
||||
dy = [b - 256 if b >= 128 else b for b in raw[0x4513:0x4513 + 7]]
|
||||
c.append("const int8_t kStIntroStarDx[7] = { " + ", ".join(str(v) for v in dx) + " };")
|
||||
c.append("const int8_t kStIntroStarDy[7] = { " + ", ".join(str(v) for v in dy) + " };")
|
||||
c.append("")
|
||||
c.append("// ---------------------------------------------------------------------------")
|
||||
c.append("// Accessors. On the IIgs the program is split across banks and a 2-byte")
|
||||
c.append("// data reference cannot cross a segment (only a 3-byte JSL/JML can). These")
|
||||
c.append("// tiny functions live in the same segment as the tables, so their reads")
|
||||
c.append("// are intra-segment; callers in other segments reach them by a supported")
|
||||
c.append("// inter-segment call. On the other ports they inline away to nothing.")
|
||||
c.append("// ---------------------------------------------------------------------------")
|
||||
first = True
|
||||
for decl, body in ACCESSORS:
|
||||
c.append("")
|
||||
if not first:
|
||||
c.append("")
|
||||
first = False
|
||||
# The header pads return types to align its prototypes; the
|
||||
# definitions here must not inherit that padding.
|
||||
c.append(" ".join(decl[:-1].split()) + " {")
|
||||
c.append(" return " + body + ";")
|
||||
c.append("}")
|
||||
|
||||
with open(OUT_H, "w", encoding="ascii") as fp:
|
||||
fp.write("\n".join(h) + "\n")
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
import os
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
EXTRACT = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "extract")
|
||||
EXTRACT = os.path.join(HERE, "..", "stuff", "extract")
|
||||
OUT = os.path.join(HERE, "..", "stDemoStreams.h")
|
||||
|
||||
# (file, symbol, 0-based level index, screen letter) in ROTATION order.
|
||||
|
|
@ -50,7 +50,7 @@ def main():
|
|||
# The playback buffer at $0902 as the boot leaves it (demo H
|
||||
# loaded over older RAM): recordings shorter than the previous one
|
||||
# leave its tail in place, and the game reads on into it.
|
||||
raw = open(os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi", "raw.bin"), "rb").read()[2:]
|
||||
raw = open(os.path.join(HERE, "..", "stuff", "raw.bin"), "rb").read()[2:]
|
||||
buf = raw[0x0902:0x0902 + 640]
|
||||
f.write("#define ST_DEMO_BUFFER_BYTES 640u\n")
|
||||
f.write("static const uint8_t kDemoBufferInit[ST_DEMO_BUFFER_BYTES] = {\n")
|
||||
|
|
|
|||
|
|
@ -1,301 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# genPlaceholderArt.py -- generate generic geometric placeholder art
|
||||
# for the Space Taxi port. Output is 320x200 indexed PNG, 16-color.
|
||||
# Replace these with hand-authored art any time; the build pipeline
|
||||
# picks up whichever PNG sits in assets/.
|
||||
|
||||
import os
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# 16-color palette matching the engine's kDefaultPalette (see stRender.c).
|
||||
# Each entry is (R, G, B) in 0..255. The .jas format will store the
|
||||
# 4-bit-per-channel quantization automatically via joeyasset.
|
||||
PALETTE_RGB = [
|
||||
(0x00, 0x00, 0x00), # 0 black
|
||||
(0x00, 0x00, 0x77), # 1 dark blue
|
||||
(0xCC, 0x00, 0x00), # 2 red
|
||||
(0x00, 0x77, 0x00), # 3 dark green
|
||||
(0x66, 0x44, 0x00), # 4 brown
|
||||
(0xEE, 0xEE, 0x00), # 5 yellow
|
||||
(0x88, 0x88, 0x88), # 6 mid gray
|
||||
(0xBB, 0xBB, 0xBB), # 7 light gray
|
||||
(0x44, 0x66, 0xFF), # 8 sky blue
|
||||
(0x77, 0x88, 0xFF), # 9 light blue
|
||||
(0xFF, 0x88, 0x88), # 10 light red
|
||||
(0x88, 0xFF, 0x44), # 11 light green
|
||||
(0xFF, 0x88, 0x00), # 12 orange
|
||||
(0xFF, 0x00, 0xFF), # 13 magenta
|
||||
(0x00, 0xFF, 0xFF), # 14 cyan
|
||||
(0xFF, 0xFF, 0xFF), # 15 white
|
||||
]
|
||||
|
||||
|
||||
def new_canvas(w=320, h=200):
|
||||
"""Returns a fresh w x h indexed PIL image with the palette installed."""
|
||||
img = Image.new('P', (w, h), 0)
|
||||
flat = []
|
||||
for r, g, b in PALETTE_RGB:
|
||||
flat.extend([r, g, b])
|
||||
flat.extend([0] * (768 - len(flat))) # pad to 256 entries
|
||||
img.putpalette(flat)
|
||||
return img
|
||||
|
||||
|
||||
def write_indexed_png(img, path):
|
||||
"""Save with mode='P' so PNG output is indexed (joeyasset wants this
|
||||
when converted through ppm; ImageMagick's `convert` preserves
|
||||
indexed mode when writing PPM)."""
|
||||
img.save(path, 'PNG', optimize=True)
|
||||
print(f" wrote {path}")
|
||||
|
||||
|
||||
def gen_tilebank():
|
||||
"""tilebank0.png: 320x200, 40x25 cells of 8x8 each (= 1000 tile
|
||||
slots). The engine convention is:
|
||||
index 0 = empty (non-solid)
|
||||
index 1..63 = solid walls
|
||||
index 64..127 = landing-pad surfaces
|
||||
index 128..255 = decorative non-solid
|
||||
We fill a few representative slots; the rest stay black so they
|
||||
show as "empty" if a level references them.
|
||||
"""
|
||||
img = new_canvas()
|
||||
px = img.load()
|
||||
|
||||
def draw_tile(idx, painter):
|
||||
bx = (idx % 40) * 8
|
||||
by = (idx // 40) * 8
|
||||
painter(bx, by)
|
||||
|
||||
def block(color):
|
||||
return lambda x, y: [px.__setitem__((x+dx, y+dy), color)
|
||||
for dx in range(8) for dy in range(8)]
|
||||
|
||||
def bricks(color_a, color_b):
|
||||
def paint(x, y):
|
||||
for dy in range(8):
|
||||
row_offset = 4 if (y // 8 + dy // 4) % 2 == 0 else 0
|
||||
for dx in range(8):
|
||||
is_grout = (dx == (row_offset % 8)) or (dy == 3 or dy == 7)
|
||||
px[x + dx, y + dy] = color_b if is_grout else color_a
|
||||
return paint
|
||||
|
||||
def stripes(color_a, color_b):
|
||||
def paint(x, y):
|
||||
for dy in range(8):
|
||||
c = color_a if (dy + (x // 8)) % 2 == 0 else color_b
|
||||
for dx in range(8):
|
||||
px[x + dx, y + dy] = c
|
||||
return paint
|
||||
|
||||
def landing_pad(color_a, color_b):
|
||||
# diagonal stripes signal a landing-pad surface
|
||||
def paint(x, y):
|
||||
for dy in range(8):
|
||||
for dx in range(8):
|
||||
px[x + dx, y + dy] = color_a if ((dx + dy) // 2) % 2 == 0 else color_b
|
||||
return paint
|
||||
|
||||
# tile 0 -> already black (empty); leave as-is
|
||||
|
||||
# walls 1..7: a variety of brick/stripe patterns
|
||||
draw_tile(1, bricks(6, 0)) # gray brick
|
||||
draw_tile(2, bricks(4, 0)) # brown brick
|
||||
draw_tile(3, block(6)) # solid gray
|
||||
draw_tile(4, block(4)) # solid brown
|
||||
draw_tile(5, stripes(6, 7)) # gray stripes (ceiling)
|
||||
draw_tile(6, stripes(1, 8)) # blue stripes (sky deco)
|
||||
draw_tile(7, block(7)) # solid light gray
|
||||
|
||||
# landing pad tiles 64..67
|
||||
draw_tile(64, landing_pad(5, 12)) # yellow/orange pad
|
||||
draw_tile(65, landing_pad(11, 3)) # green pad
|
||||
draw_tile(66, landing_pad(9, 8)) # blue pad
|
||||
draw_tile(67, landing_pad(15, 7)) # white pad
|
||||
|
||||
write_indexed_png(img, os.path.join(ROOT, 'tiles', 'tilebank0.png'))
|
||||
|
||||
|
||||
def gen_sprites():
|
||||
"""RETIRED 2026-07-20: sprites.png is now fully extracted from the
|
||||
C64 dump by stuff/spacetaxi/extractSprites.py (taxi $C1/$C0/$DC/
|
||||
$DD, passenger $C4-$CB/$D9, flame $6DB0 table, warp $E2-$E8 --
|
||||
216x96, 4 rows). Regenerating placeholder art here would clobber
|
||||
the real sheet, the same trap gen_font has with the hand-edited
|
||||
font.png. Refuses to run; use extractSprites.py instead."""
|
||||
print("gen_sprites: REFUSING -- sprites.png is real C64 art now.")
|
||||
print(" regenerate via: python3 stuff/spacetaxi/extractSprites.py "
|
||||
"stuff/spacetaxi/raw.bin examples/spacetaxi/assets/sprites/sprites.png")
|
||||
return
|
||||
|
||||
|
||||
def gen_sprites_retired():
|
||||
img = new_canvas(9 * 24, 3 * 24) # 216x72: 9 cols x 3 rows of 24x24
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
# ---- Taxi cels: simple side-view "pod with thruster" ----
|
||||
# Cel 0 = idle (thruster off)
|
||||
# Cel 1..3 = thrust-on, alternating flame frames
|
||||
for cel in range(4):
|
||||
x = cel * 24
|
||||
# Pod body: rounded rectangle in gray
|
||||
d.rectangle([x+2, 4, x+21, 16], fill=7, outline=15)
|
||||
# Cockpit window in blue
|
||||
d.rectangle([x+13, 6, x+19, 10], fill=8, outline=15)
|
||||
# Landing skid
|
||||
d.line([(x+3, 17), (x+20, 17)], fill=6)
|
||||
d.line([(x+3, 17), (x+3, 19)], fill=6)
|
||||
d.line([(x+20,17), (x+20, 19)], fill=6)
|
||||
# Thruster flame (cels 1..3 only, alternating shape)
|
||||
if cel >= 1:
|
||||
flame_color = [2, 12, 5][(cel - 1) % 3]
|
||||
flame_top = 18
|
||||
flame_bot = 22 if cel != 2 else 23
|
||||
d.polygon([(x+8, flame_top),
|
||||
(x+15, flame_top),
|
||||
(x+12, flame_bot)], fill=flame_color)
|
||||
|
||||
# ---- Taxi shrink cels (transporter warp): the cab receding into the
|
||||
# distance, 5 progressively smaller sizes, centered in the 24x24 cell.
|
||||
# Placed in the free row-0 slots (cels 4..8). No flame -- it is warping
|
||||
# away, not thrusting. The renderer draws one per warp step ($E2..$E9
|
||||
# on the C64 = 8 shrink cels; the port approximates with 5 sizes).
|
||||
for i in range(5):
|
||||
cx = (4 + i) * 24 + 12
|
||||
scale = 1.0 - i * 0.19 # 1.0, 0.81, 0.62, 0.43, 0.24
|
||||
bw = max(3, int(round(19 * scale)))
|
||||
bh = max(2, int(round(12 * scale)))
|
||||
x0 = cx - bw // 2
|
||||
y0 = 12 - bh // 2
|
||||
# Pod body (gray, white outline)
|
||||
d.rectangle([x0, y0, x0 + bw, y0 + bh], fill=7, outline=15)
|
||||
# Cockpit window (blue) while big enough to show
|
||||
if bw >= 8:
|
||||
d.rectangle([x0 + bw - 5, y0 + 1, x0 + bw - 1, y0 + 3], fill=8)
|
||||
|
||||
# ---- Passenger cels: small stick figure, walk cycle ----
|
||||
# Centered in 16x16, walking right.
|
||||
for cel in range(4):
|
||||
x = cel * 16
|
||||
y = 24
|
||||
# Head
|
||||
d.ellipse([x+5, y+1, x+10, y+6], fill=10, outline=15)
|
||||
# Body
|
||||
d.line([(x+7, y+7), (x+7, y+12)], fill=15)
|
||||
# Arms (swing alternately based on cel)
|
||||
if cel == 0 or cel == 2:
|
||||
d.line([(x+4, y+10), (x+10, y+10)], fill=15)
|
||||
else:
|
||||
d.line([(x+3, y+9), (x+11, y+11)], fill=15)
|
||||
# Legs (alternating stride)
|
||||
if cel == 0 or cel == 2:
|
||||
d.line([(x+7, y+12), (x+4, y+15)], fill=15)
|
||||
d.line([(x+7, y+12), (x+10,y+15)], fill=15)
|
||||
else:
|
||||
d.line([(x+7, y+12), (x+5, y+15)], fill=15)
|
||||
d.line([(x+7, y+12), (x+9, y+15)], fill=15)
|
||||
|
||||
write_indexed_png(img, os.path.join(ROOT, 'sprites', 'sprites.png'))
|
||||
|
||||
|
||||
def gen_font():
|
||||
"""font.png: 320x200 (40x25 grid of 8x8 ASCII glyphs).
|
||||
Authored manually for the printable subset (0x20..0x7E).
|
||||
Each glyph cell is at (ascii%40, ascii/40) when ascii<128.
|
||||
Unfilled cells stay black (treated as TILE_NO_GLYPH at runtime).
|
||||
|
||||
WARNING: this generator is UPPERCASE-ONLY (lowercase maps to the
|
||||
uppercase glyphs). The COMMITTED font.png has been hand-extended with
|
||||
real lowercase glyphs, so running this clobbers it -- the HUD reverts
|
||||
to all-caps. Regenerate SPRITES only (gen_sprites) unless you also add
|
||||
the lowercase glyphs here first; then `git checkout` font.png.
|
||||
"""
|
||||
# A compact 5-pixel-wide bitmap font, drawn in 7 rows with 1px
|
||||
# padding on all sides (so 5x7 active inside an 8x8 cell, baseline
|
||||
# at row 6). The font supports digits, uppercase letters, space,
|
||||
# and basic punctuation -- enough for HUD text. Lowercase letters
|
||||
# map to the same glyphs as uppercase (room to add later).
|
||||
GLYPHS = {
|
||||
' ': [],
|
||||
'!': ['..#..', '..#..', '..#..', '..#..', '.....', '..#..', '.....'],
|
||||
'"': ['.#.#.', '.#.#.', '.....', '.....', '.....', '.....', '.....'],
|
||||
'0': ['.###.', '#...#', '#..##', '#.#.#', '##..#', '#...#', '.###.'],
|
||||
'1': ['..#..', '.##..', '..#..', '..#..', '..#..', '..#..', '.###.'],
|
||||
'2': ['.###.', '#...#', '....#', '...#.', '..#..', '.#...', '#####'],
|
||||
'3': ['.###.', '#...#', '....#', '..##.', '....#', '#...#', '.###.'],
|
||||
'4': ['...#.', '..##.', '.#.#.', '#..#.', '#####', '...#.', '...#.'],
|
||||
'5': ['#####', '#....', '####.', '....#', '....#', '#...#', '.###.'],
|
||||
'6': ['.###.', '#....', '#....', '####.', '#...#', '#...#', '.###.'],
|
||||
'7': ['#####', '....#', '...#.', '..#..', '..#..', '..#..', '..#..'],
|
||||
'8': ['.###.', '#...#', '#...#', '.###.', '#...#', '#...#', '.###.'],
|
||||
'9': ['.###.', '#...#', '#...#', '.####', '....#', '....#', '.###.'],
|
||||
':': ['.....', '..#..', '.....', '.....', '.....', '..#..', '.....'],
|
||||
'.': ['.....', '.....', '.....', '.....', '.....', '..#..', '.....'],
|
||||
'-': ['.....', '.....', '.....', '#####', '.....', '.....', '.....'],
|
||||
'/': ['....#', '...#.', '...#.', '..#..', '.#...', '.#...', '#....'],
|
||||
'A': ['.###.', '#...#', '#...#', '#####', '#...#', '#...#', '#...#'],
|
||||
'B': ['####.', '#...#', '#...#', '####.', '#...#', '#...#', '####.'],
|
||||
'C': ['.###.', '#...#', '#....', '#....', '#....', '#...#', '.###.'],
|
||||
'D': ['####.', '#...#', '#...#', '#...#', '#...#', '#...#', '####.'],
|
||||
'E': ['#####', '#....', '#....', '####.', '#....', '#....', '#####'],
|
||||
'F': ['#####', '#....', '#....', '####.', '#....', '#....', '#....'],
|
||||
'G': ['.###.', '#...#', '#....', '#..##', '#...#', '#...#', '.###.'],
|
||||
'H': ['#...#', '#...#', '#...#', '#####', '#...#', '#...#', '#...#'],
|
||||
'I': ['.###.', '..#..', '..#..', '..#..', '..#..', '..#..', '.###.'],
|
||||
'J': ['..###', '...#.', '...#.', '...#.', '...#.', '#..#.', '.##..'],
|
||||
'K': ['#...#', '#..#.', '#.#..', '##...', '#.#..', '#..#.', '#...#'],
|
||||
'L': ['#....', '#....', '#....', '#....', '#....', '#....', '#####'],
|
||||
'M': ['#...#', '##.##', '#.#.#', '#.#.#', '#...#', '#...#', '#...#'],
|
||||
'N': ['#...#', '##..#', '#.#.#', '#.#.#', '#..##', '#...#', '#...#'],
|
||||
'O': ['.###.', '#...#', '#...#', '#...#', '#...#', '#...#', '.###.'],
|
||||
'P': ['####.', '#...#', '#...#', '####.', '#....', '#....', '#....'],
|
||||
'Q': ['.###.', '#...#', '#...#', '#...#', '#.#.#', '#..#.', '.##.#'],
|
||||
'R': ['####.', '#...#', '#...#', '####.', '#.#..', '#..#.', '#...#'],
|
||||
'S': ['.###.', '#...#', '#....', '.###.', '....#', '#...#', '.###.'],
|
||||
'T': ['#####', '..#..', '..#..', '..#..', '..#..', '..#..', '..#..'],
|
||||
'U': ['#...#', '#...#', '#...#', '#...#', '#...#', '#...#', '.###.'],
|
||||
'V': ['#...#', '#...#', '#...#', '#...#', '.#.#.', '.#.#.', '..#..'],
|
||||
'W': ['#...#', '#...#', '#...#', '#.#.#', '#.#.#', '##.##', '#...#'],
|
||||
'X': ['#...#', '#...#', '.#.#.', '..#..', '.#.#.', '#...#', '#...#'],
|
||||
'Y': ['#...#', '#...#', '.#.#.', '..#..', '..#..', '..#..', '..#..'],
|
||||
'Z': ['#####', '....#', '...#.', '..#..', '.#...', '#....', '#####'],
|
||||
}
|
||||
|
||||
img = new_canvas()
|
||||
px = img.load()
|
||||
|
||||
def paint_glyph(ascii_code, glyph_rows):
|
||||
col = ascii_code % 40
|
||||
row = ascii_code // 40
|
||||
bx = col * 8
|
||||
by = row * 8
|
||||
# active 5x7 area: cell-x 1..5, cell-y 0..6 (top row, 1px left pad)
|
||||
for ry, line in enumerate(glyph_rows):
|
||||
for rx, ch in enumerate(line):
|
||||
if ch == '#':
|
||||
px[bx + 1 + rx, by + ry] = 15 # white
|
||||
|
||||
for ascii_code in range(32, 127):
|
||||
c = chr(ascii_code)
|
||||
if c in GLYPHS:
|
||||
paint_glyph(ascii_code, GLYPHS[c])
|
||||
elif 'a' <= c <= 'z':
|
||||
paint_glyph(ascii_code, GLYPHS[c.upper()])
|
||||
|
||||
write_indexed_png(img, os.path.join(ROOT, 'font.png'))
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(os.path.join(ROOT, 'tiles'), exist_ok=True)
|
||||
os.makedirs(os.path.join(ROOT, 'sprites'), exist_ok=True)
|
||||
print("genPlaceholderArt: emitting 320x200 indexed PNGs...")
|
||||
gen_tilebank()
|
||||
gen_sprites()
|
||||
gen_font()
|
||||
print("done.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
77
assets/genSongData.py
Normal file
77
assets/genSongData.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python3
|
||||
# Bake assets/songs/song*.song into stSongData.h with JoeyLib's songbake
|
||||
# (JOEYLIB names the checkout, ../joeylib by default).
|
||||
#
|
||||
# python3 assets/genSongData.py
|
||||
#
|
||||
# The .song sources come from the game's own player run under a 6502
|
||||
# emulator: assets/sidCapture.py logs every SID write per PAL frame and
|
||||
# assets/sidToSong.py turns that into songbake text.
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
joeylib = os.environ.get("JOEYLIB", os.path.join(here, "..", "..", "joeylib"))
|
||||
songbake = os.path.join(joeylib, "tools", "songbake", "songbake.py")
|
||||
out = os.path.join(here, "..", "stSongData.h")
|
||||
count = 9
|
||||
|
||||
header = """// Space Taxi (C64) tunes as JoeyLib JYM1 streams. GENERATED by
|
||||
// assets/genSongData.py -- do not hand-edit. The songs were extracted by
|
||||
// running the game's own music player under a 6502 emulator
|
||||
// (assets/sidCapture.py logs every SID write per PAL frame;
|
||||
// assets/sidToSong.py turns that into songbake text in assets/songs/).
|
||||
//
|
||||
// Where the C64 plays them:
|
||||
// BLOCKING via $44CF (the game spins until the tune ends; the port
|
||||
// parks in ST_STATE_JINGLE with the sim frozen):
|
||||
// 0..3 start of every screen, rotating on $5E9B ($6906)
|
||||
// 4 level 25 start ($6935) and the bonus cab ($70B1)
|
||||
// 5 game over ($5C1F), after the two-second GAME OVER! wait
|
||||
// NON-BLOCKING via $CB02 (started and left to the IRQ player):
|
||||
// 6 the high-score name entry screen ($4E35) -- no port screen yet
|
||||
// 7 the high-score table ($4C1F, joystick UP on the title)
|
||||
// 8 the level intro while the cab flies in ($459C); endless, so
|
||||
// the source is trimmed to 30 s and looped
|
||||
#ifndef ST_SONG_DATA_H
|
||||
#define ST_SONG_DATA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
"""
|
||||
|
||||
footer = """typedef struct {
|
||||
const uint8_t *data;
|
||||
uint16_t length;
|
||||
} StSongT;
|
||||
|
||||
#define ST_SONG_COUNT %du
|
||||
#define ST_SONG_BONUS 4u
|
||||
#define ST_SONG_GAME_OVER 5u
|
||||
#define ST_SONG_NAME_ENTRY 6u
|
||||
#define ST_SONG_SCORES 7u
|
||||
#define ST_SONG_INTRO 8u
|
||||
|
||||
static const StSongT kStSongs[ST_SONG_COUNT] = {
|
||||
%s};
|
||||
|
||||
#endif // ST_SONG_DATA_H
|
||||
"""
|
||||
|
||||
parts = []
|
||||
for n in range(count):
|
||||
src = os.path.join(here, "songs", "song%d.song" % n)
|
||||
with tempfile.NamedTemporaryFile(suffix=".h", delete=False) as tmp:
|
||||
path = tmp.name
|
||||
subprocess.run([sys.executable, songbake, "-c", "gStSong%d" % n, src, path], check=True, stderr=subprocess.DEVNULL)
|
||||
body = open(path).read()
|
||||
os.unlink(path)
|
||||
body = "\n".join(l for l in body.split("\n") if l.startswith("static") or l.startswith(" ") or l.startswith("}"))
|
||||
parts.append(body.rstrip() + "\n\n")
|
||||
|
||||
table = "".join(" { gStSong%d, (uint16_t)sizeof(gStSong%d) },\n" % (n, n) for n in range(count))
|
||||
with open(out, "w") as fp:
|
||||
fp.write(header + "".join(parts) + footer % (count, table))
|
||||
print("wrote", os.path.normpath(out))
|
||||
113
assets/genSpeechPcm.py
Executable file
113
assets/genSpeechPcm.py
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python3
|
||||
"""genSpeechPcm.py - bake Space Taxi's speech to 8-bit PCM.
|
||||
Reads the C64 run-length speech streams from stSpeechData.c (the output
|
||||
of extractSpeech.py) and writes speech.bin: every utterance decoded to
|
||||
5 kHz sample bytes for each passenger pitch the game can select, in BOTH
|
||||
polarities. The decode is the exact algorithm stAudio.c used to run per
|
||||
sample at play time; on the 65816 that took 2.4 s of CPU per second of
|
||||
speech (three run bytes per sample, each a multiply) and stalled the ride
|
||||
for half a second on every "HEY TAXI". A 1-bit intermediate format came
|
||||
next, but expanding bits still needed a per-byte loop (and an asm copy of
|
||||
it on the IIgs); with sample bytes the player is a block copy.
|
||||
Polarity: an utterance is decoded from a +100 start. The C64 keeps the
|
||||
output level running from one utterance into the next, so a phrase whose
|
||||
first word ends on the low level plays its second word inverted. Both
|
||||
versions are stored and the player picks by the running parity.
|
||||
speech.bin (little-endian):
|
||||
"STSP" u8 version(3) u8 pitchBase u8 pitchCount u8 count
|
||||
u8 format (0 = signed +/-100, 1 = 0x80-biased for the IIgs raw stream)
|
||||
u8 polarities (1 or 2) u8 pad[2] u32 dataBytes
|
||||
entries[pitchCount * count * polarities]: u8 code, u8 parity,
|
||||
u16 samples, u32 offset -- polarity is the innermost index
|
||||
(0 = as decoded, 1 = inverted). parity = 1 when the utterance
|
||||
ends on the opposite level from the one it started on.
|
||||
data: sample bytes
|
||||
With --single only polarity 0 is written, halving the file; the player
|
||||
negates the samples itself when the running parity calls for it. The
|
||||
X68000 needs that: its Human68k floppy cannot spare the other 60 KB.
|
||||
usage: genSpeechPcm.py stSpeechData.c speech.bin [--biased] [--single]
|
||||
"""
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
SID_HZ = 1022727 # ST_SID_HZ_NUM
|
||||
RATE = 5000 # ST_SPEECH_RATE
|
||||
CYC_PER_SAMPLE = SID_HZ // RATE
|
||||
SYNC_CYCLES = SID_HZ // 60 // 8
|
||||
CYCLE_TAIL = 33
|
||||
SYNC = 0xFE
|
||||
END = 0xFF
|
||||
PITCH_BASE = 2 # stFare.c: stAudioSpeech(rng(3) + 2)
|
||||
PITCH_COUNT = 3
|
||||
|
||||
|
||||
def parseSource(path):
|
||||
text = open(path).read()
|
||||
idx = re.search(r"kStSpeechIndex\[[^\]]*\]\s*=\s*\{(.*?)\};", text, re.S).group(1)
|
||||
entries = [(int(c, 16), int(o), int(n)) for c, o, n in
|
||||
re.findall(r"\{\s*0x([0-9A-Fa-f]+)u,\s*(\d+)u,\s*(\d+)u\s*\}", idx)]
|
||||
rle = re.search(r"kStSpeechRle\[[^\]]*\]\s*=\s*\{(.*?)\};", text, re.S).group(1)
|
||||
data = bytes(int(x, 16) for x in re.findall(r"0x([0-9A-Fa-f]{2})", rle))
|
||||
return entries, data
|
||||
|
||||
|
||||
def decode(rle, perOn):
|
||||
"""speechDecode() for one utterance from a +100 start, remain 0."""
|
||||
remain = 0
|
||||
level = 100
|
||||
out = []
|
||||
i = 0
|
||||
n = len(rle)
|
||||
while True:
|
||||
while remain <= 0:
|
||||
if i >= n:
|
||||
return out, level
|
||||
b = rle[i]
|
||||
i += 1
|
||||
if b == END:
|
||||
i = n
|
||||
continue
|
||||
if b == SYNC:
|
||||
remain += SYNC_CYCLES
|
||||
continue
|
||||
remain += perOn * b + CYCLE_TAIL
|
||||
level = -100 if level > 0 else 100
|
||||
out.append(level)
|
||||
remain -= CYC_PER_SAMPLE
|
||||
|
||||
|
||||
def main():
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
biased = "--biased" in sys.argv[1:]
|
||||
single = "--single" in sys.argv[1:]
|
||||
polarities = 1 if single else 2
|
||||
src, dst = args[0], args[1]
|
||||
entries, data = parseSource(src)
|
||||
records = []
|
||||
blob = bytearray()
|
||||
for p in range(PITCH_COUNT):
|
||||
perOn = 5 * (PITCH_BASE + p) + 12
|
||||
for code, off, length in entries:
|
||||
samples, level = decode(data[off:off + length], perOn)
|
||||
parity = 1 if level < 0 else 0
|
||||
for pol in range(polarities):
|
||||
pcm = bytearray()
|
||||
for s in samples:
|
||||
v = -s if pol else s
|
||||
pcm.append((v + 0x80) & 0xFF if biased else v & 0xFF)
|
||||
records.append((code, parity, len(samples), len(blob)))
|
||||
blob += pcm
|
||||
with open(dst, "wb") as f:
|
||||
f.write(b"STSP" + struct.pack("<BBBBBBBBI", 3, PITCH_BASE, PITCH_COUNT, len(entries), 1 if biased else 0, polarities, 0, 0, len(blob)))
|
||||
for code, parity, n, off in records:
|
||||
f.write(struct.pack("<BBHI", code, parity, n, off))
|
||||
f.write(blob)
|
||||
total = sum(n for _, _, n, _ in records)
|
||||
print(f"{dst}: {len(entries)} utterances x {PITCH_COUNT} pitches x {polarities} "
|
||||
f"polarit{'y' if polarities == 1 else 'ies'}, {total} samples, {len(blob)} data bytes, "
|
||||
f"{'biased' if biased else 'signed'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Space Taxi level .dat format (STL2)
|
||||
|
||||
A small custom binary format that `stLevelLoad()` reads at scene
|
||||
boot. Output files land in `examples/spacetaxi/generated/levels/`
|
||||
boot. Output files land in `generated/levels/`
|
||||
(from `romToLevel.py` for the 24 canonical C64 levels, or from
|
||||
`mkstlevel` for hand-authored levels in `assets/levels/*.txt`). The
|
||||
build copies them under `DATA/levels/levelNN.dat` in the runtime
|
||||
|
|
@ -74,15 +74,15 @@ without changes.
|
|||
1. Author a 256-tile (or smaller) tile sheet as an indexed PNG. Each
|
||||
tile is 8x8 px. Arrange in a grid; the bank loader assumes row-
|
||||
major, tile-index = `ty * tilesPerRow + tx`. Drop the PNG at
|
||||
`examples/spacetaxi/assets/tiles/tbank<N>.png`.
|
||||
`assets/tiles/tbank<N>.png`.
|
||||
2. The Makefile bakes per-target via `tools/assetbake/assetbake.py
|
||||
--type tile --target <port> tbank<N>.png tbank<N>.tbk`. Output
|
||||
lands in `examples/spacetaxi/generated/<port>/tiles/` and is
|
||||
lands in `generated/<port>/tiles/` and is
|
||||
staged into the runtime tree at `build/<port>/.../DATA/tiles/`.
|
||||
3. Author each level layout in a text editor or grid tool, save as a
|
||||
.dat per this spec. The helper `examples/spacetaxi/mkstlevel`
|
||||
.dat per this spec. The helper `mkstlevel`
|
||||
converts from a human-readable text grid to .dat. The 24 canonical
|
||||
C64 levels are emitted by `stuff/spacetaxi/romToLevel.py` directly
|
||||
C64 levels are emitted by `assets/romToLevel.py` directly
|
||||
from a raw VICE dump of the original .prg.
|
||||
|
||||
## Naming
|
||||
|
|
|
|||
205
assets/romToLevel.py
Normal file
205
assets/romToLevel.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/env python3
|
||||
# Convert ROM-extracted Space Taxi scenes directly into the runtime
|
||||
# STL1 binary format (see stLevel.c). Uses the
|
||||
# 6502 emulator + raw RAM dump to decompress each scene, then writes
|
||||
# screen + color RAM bytes straight into the .dat layout the JoeyLib
|
||||
# port loads at runtime.
|
||||
#
|
||||
# Replaces the .txt -> mkstlevel pipeline for canonical levels. The C
|
||||
# mkstlevel tool stays in tree for hand-authored .txt levels.
|
||||
#
|
||||
# Usage:
|
||||
# romToLevel.py <raw-dump> <out-dir> # all 24 levels
|
||||
# romToLevel.py <raw-dump> <out-dir> <scene_id> # one specific
|
||||
#
|
||||
# Writes:
|
||||
# <out-dir>/level01.dat ... level24.dat (scene 0 = "A" = level01)
|
||||
# <out-dir>/tbank0.png (charset extracted from $2800)
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from cpu6502 import Cpu6502
|
||||
from dumpScenes import load_dump, run_scene
|
||||
|
||||
|
||||
# STL2 binary layout (see stLevel.c):
|
||||
# "STL2"
|
||||
# nameLen + name
|
||||
# tileBank, music, bgColor, borderColor, taxiSpawnX, taxiSpawnY
|
||||
# xAccel, yAccel, xGrav (int8), yGrav (int8)
|
||||
# bgColor1, bgColor2, bgColor3, spriteMc0, spriteMc1, sprite0Color, sprite1Color
|
||||
# padCount + padCount * (letter, tileX, tileY, tileW)
|
||||
# fareCount + fareCount * (spawnPad, destPad)
|
||||
# tilemap[40 * 25]
|
||||
# colormap[40 * 25]
|
||||
|
||||
ST_TILEMAP_W = 40
|
||||
ST_PLAYFIELD_ROWS = 25
|
||||
ST_TILE_PX = 8
|
||||
|
||||
# VIC-II sprite-register origins: sprite-X $18 (24) is display column 0,
|
||||
# sprite-Y $32 (50) is the first visible raster line. All in-game pad/taxi
|
||||
# positions are stored in this sprite-register space, so tile conversion
|
||||
# subtracts these origins. PAD_SURFACE_ORIGIN folds in the ~14px landing-
|
||||
# gear offset (50-14=36) so a pad's row byte maps to its contact-surface
|
||||
# tile row rather than the sprite-top row.
|
||||
VIC_X_ORIGIN = 24
|
||||
VIC_Y_ORIGIN = 50
|
||||
PAD_SURFACE_ORIGIN = 36
|
||||
LEVEL_LETTERS = [chr(ord('A') + i) for i in range(24)]
|
||||
|
||||
|
||||
def write_level(path, level_index, after, screen, colormap, sprite_blocks):
|
||||
"""Write an STL4 level .dat straight from post-decompression RAM.
|
||||
|
||||
Everything stays in the C64's own units so the runtime can mirror the
|
||||
original routines byte for byte:
|
||||
name[22] $7D78-$7D8D level name (screen codes, ASCII-compatible)
|
||||
header[9] $7D00-$7D08 VIC colours: border, bg, bg1-3, mc0, mc1,
|
||||
sprite-0, sprite-1
|
||||
padCount $7D5A pads in the table
|
||||
specialPad $7D09 fare pads (== padCount, or padCount-1 when
|
||||
the last pad is the fuel station)
|
||||
fuelRate $7D60 ticks of flight per half fuel cell
|
||||
spawn[5] $7D5B-$7D5F X frac, X lo, X hi, Y frac, Y hi (sprite coords)
|
||||
templates[8] $7D8F-$7D96 Y accel, X accel, Y gravity, X gravity
|
||||
(16-bit little-endian each)
|
||||
pads[10][8] $7D0A-$7D59 raw slots: x1 hi/lo, x2 hi/lo, row,
|
||||
passenger X frac, passenger X col, unused
|
||||
tilemap[1000] screen RAM (all 25 rows, the HUD rows included)
|
||||
colormap[1000] colour RAM low nibbles
|
||||
levelIndex 0..23 = A..X, 24 = title
|
||||
spriteCount N, then N x (ptr, 63 bytes): the per-level sprite
|
||||
bitmaps the level's extra scene loads at $2000+
|
||||
hookData[616] $7D98-$7FFF: the level's hook code and tables
|
||||
"""
|
||||
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||
assert len(screen) == cells, f'screen is {len(screen)}, need {cells}'
|
||||
assert len(colormap) == cells, f'colormap is {len(colormap)}, need {cells}'
|
||||
with open(path, 'wb') as fp:
|
||||
fp.write(b'STL4')
|
||||
fp.write(bytes(after[0x7D78:0x7D78 + 22]))
|
||||
fp.write(bytes(after[0x7D00:0x7D09]))
|
||||
fp.write(bytes([after[0x7D5A], after[0x7D09], after[0x7D60]]))
|
||||
fp.write(bytes(after[0x7D5B:0x7D60]))
|
||||
fp.write(bytes(after[0x7D8F:0x7D97]))
|
||||
pad_count = min(after[0x7D5A], 10)
|
||||
for pad_idx in range(10):
|
||||
if pad_idx < pad_count:
|
||||
off = 0x7D0A + pad_idx * 8
|
||||
fp.write(bytes(after[off:off + 8]))
|
||||
else:
|
||||
fp.write(bytes(8))
|
||||
fp.write(bytes(screen))
|
||||
fp.write(bytes(colormap))
|
||||
fp.write(bytes([level_index]))
|
||||
fp.write(bytes([len(sprite_blocks)]))
|
||||
for ptr, data in sprite_blocks:
|
||||
fp.write(bytes([ptr]))
|
||||
fp.write(bytes(data))
|
||||
fp.write(bytes(after[0x7D98:0x8000]))
|
||||
|
||||
|
||||
# Levels whose data blob flags an extra scene ($7D97 != 0) load a second
|
||||
# scene from this table ($6F55, indexed by level) right after the level
|
||||
# itself. Those extra scenes only write sprite bitmaps into $2000-$27FF
|
||||
# (block pointers $80-$9F) for the level's hook code to animate.
|
||||
_EXTRA_SCENE = [0, 0, 0, 0, 0, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0, 0x1F, 0, 0, 0,
|
||||
0x20, 0x21, 0, 0, 0, 0x22, 0, 0, 0, 0x23]
|
||||
|
||||
|
||||
def extract_one_level(ram, scene_id, out_dir):
|
||||
"""Decompress `scene_id` and write out_dir/level<NN>.dat (NN = scene+1)."""
|
||||
letter = LEVEL_LETTERS[scene_id]
|
||||
screen_buf, color_buf, after = run_scene(ram, scene_id)
|
||||
sprite_blocks = []
|
||||
if after[0x7D97] != 0 and _EXTRA_SCENE[scene_id] != 0:
|
||||
# Start the extra scene from a zeroed sprite window so the blocks
|
||||
# it writes are exactly the ones the level ships with.
|
||||
work = bytearray(after)
|
||||
for i in range(0x2000, 0x2800):
|
||||
work[i] = 0
|
||||
_s, _c, after2 = run_scene(bytes(work), _EXTRA_SCENE[scene_id])
|
||||
for ptr in range(0x80, 0xA0):
|
||||
block = bytes(after2[ptr * 64:ptr * 64 + 63])
|
||||
if any(block):
|
||||
sprite_blocks.append((ptr, block))
|
||||
# The level data itself (pads, hooks) is untouched by the extra
|
||||
# scene; keep reading it from the level's own image.
|
||||
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||
screen = list(screen_buf[:cells])
|
||||
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||
level_num = scene_id + 1
|
||||
out_path = os.path.join(out_dir, f'level{level_num:02d}.dat')
|
||||
write_level(out_path, scene_id, after, screen, colormap, sprite_blocks)
|
||||
return out_path, after[0x7D5A], len(sprite_blocks)
|
||||
|
||||
|
||||
def extract_screen_y(ram, out_dir):
|
||||
"""Screen Y, the 25th screen ("MYSTERY SCREEN !!!"), is scene 24.
|
||||
|
||||
$5010 dispatches on the screens-completed counter: at 24 it jumps to
|
||||
$5167, which loads #$59 ('Y') as the pending screen; the shift only
|
||||
ends when the counter reaches 25. So a full 24-hour shift plays A..X
|
||||
and then Y -- the 25 screens the intro advertises.
|
||||
"""
|
||||
screen_buf, color_buf, after = run_scene(ram, 24)
|
||||
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||
screen = list(screen_buf[:cells])
|
||||
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||
out_path = os.path.join(out_dir, 'level25.dat')
|
||||
write_level(out_path, 24, after, screen, colormap, [])
|
||||
return out_path
|
||||
|
||||
|
||||
def extract_title(ram, out_dir):
|
||||
"""The title screen is scene 25 (what $477A decompresses).
|
||||
Pads, spawn and templates are dummies."""
|
||||
screen_buf, color_buf, after = run_scene(ram, 25)
|
||||
cells = ST_TILEMAP_W * ST_PLAYFIELD_ROWS
|
||||
screen = list(screen_buf[:cells])
|
||||
colormap = [(b & 0x0F) for b in color_buf[:cells]]
|
||||
out_path = os.path.join(out_dir, 'title.dat')
|
||||
# The title scene writes no level name, so $7D78 still holds whatever
|
||||
# the previously decompressed scene left there (PUZZLER, level H).
|
||||
# The title never draws the name; ship it blank rather than stale.
|
||||
work = bytearray(after)
|
||||
work[0x7D78:0x7D78 + 22] = b' ' * 22
|
||||
write_level(out_path, 25, work, screen, colormap, [])
|
||||
return out_path
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print('usage: romToLevel.py <raw-dump> <out-dir> [scene_id]', file=sys.stderr)
|
||||
sys.exit(2)
|
||||
ram = load_dump(sys.argv[1])
|
||||
out_dir = sys.argv[2]
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
if len(sys.argv) >= 4:
|
||||
scene_ids = [int(sys.argv[3], 0)]
|
||||
else:
|
||||
# Levels A..X = scenes 0..23. The title comes from screen RAM
|
||||
# at $0400 (the dump was taken on the title screen), not from
|
||||
# a decompressed scene.
|
||||
scene_ids = list(range(24))
|
||||
|
||||
for sid in scene_ids:
|
||||
path, pad_count, nspr = extract_one_level(ram, sid, out_dir)
|
||||
size = os.path.getsize(path)
|
||||
print(f' scene {sid:2d} (level {LEVEL_LETTERS[sid]}): pads={pad_count} sprites={nspr} size={size} -> {path}')
|
||||
|
||||
# Screen Y and the title are not part of the A..X sweep.
|
||||
if len(sys.argv) < 4:
|
||||
y_path = extract_screen_y(ram, out_dir)
|
||||
print(f' scene 24 (screen Y) : -> {y_path} ({os.path.getsize(y_path)} bytes)')
|
||||
title_path = extract_title(ram, out_dir)
|
||||
print(f' title (screen RAM $0400) : -> {title_path} ({os.path.getsize(title_path)} bytes)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
57
assets/sidCapture.py
Normal file
57
assets/sidCapture.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run the C64 Space Taxi music player and log every SID write per frame.
|
||||
|
||||
The player lives in the TITLE memory image at $CB00-$CFFF (its own relocated
|
||||
copy; the $7500 copy is gameplay-side). Rather than hand-decode its byte
|
||||
code, drive the real routines under cpu6502.py -- the same trick that
|
||||
extracted the speech:
|
||||
$CB00 select song ($CB01 = song number) and init (hooks the IRQ vector)
|
||||
$CF52 the IRQ body: first frame calls start ($CC28), then ticks the three
|
||||
voices via $CC6C, then JMPs to the KERNAL IRQ ($EA31) which is what
|
||||
advances the jiffy clock $A2. We poke an RTS at $EA31 and bump $A2
|
||||
ourselves, once per frame.
|
||||
A song is over when all three voice stream pointers' high bytes are zero
|
||||
(that is exactly what the game's play-and-wait loop at $44D5 spins on).
|
||||
"""
|
||||
import json, os, sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from cpu6502 import Cpu6502
|
||||
|
||||
RAM = bytearray(open(sys.argv[1], "rb").read()[:65536])
|
||||
OUT = sys.argv[2]
|
||||
MAX_FRAMES = 4000
|
||||
|
||||
|
||||
class SidTrap(Cpu6502):
|
||||
def __init__(self, ram):
|
||||
super().__init__(ram)
|
||||
self.log = []
|
||||
self.frame = 0
|
||||
|
||||
def write8(self, addr, val):
|
||||
a = addr & 0xFFFF
|
||||
if 0xD400 <= a <= 0xD418:
|
||||
self.log.append((self.frame, a - 0xD400, val & 0xFF))
|
||||
super().write8(addr, val)
|
||||
|
||||
|
||||
songs = {}
|
||||
for song in range(0, 9):
|
||||
ram = bytearray(RAM)
|
||||
ram[0xEA31] = 0x60 # KERNAL IRQ tail -> RTS
|
||||
cpu = SidTrap(ram)
|
||||
cpu.call(0xCC00) # full reset first
|
||||
ram[0xCB01] = song
|
||||
cpu.call(0xCB00) # select + arm
|
||||
for frame in range(MAX_FRAMES):
|
||||
cpu.frame = frame
|
||||
ram[0xA2] = (ram[0xA2] + 1) & 0xFF
|
||||
cpu.call(0xCF52)
|
||||
if ram[0xCB81] == 0 and ram[0xCB88] == 0 and ram[0xCB8F] == 0:
|
||||
break
|
||||
songs[song] = {"frames": frame + 1, "writes": cpu.log,
|
||||
"ptr": ram[0xCB40 + song * 2] | (ram[0xCB41 + song * 2] << 8)}
|
||||
print(f"song {song}: ptr=${songs[song]['ptr']:04X} frames={frame+1} "
|
||||
f"({(frame+1)/50:.1f}s PAL) sidWrites={len(cpu.log)}")
|
||||
|
||||
json.dump(songs, open(OUT, "w"))
|
||||
111
assets/sidToSong.py
Normal file
111
assets/sidToSong.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python3
|
||||
"""SID register log (sidCapture.py) -> JoeyLib .song text for songbake.
|
||||
|
||||
One row per PAL frame (tickms 20). Per voice, a note-on is a gate rising
|
||||
edge or a pitch change while gated; gate falling is 'off'. SID frequency ->
|
||||
Hz exactly (@Hz cells), so no equal-temperament rounding. Attenuation comes
|
||||
from the sustain nibble. A voice whose waveform is NOISE goes to the noise
|
||||
column instead of a tone column, with a pitch scaled from its frequency.
|
||||
Loop detection: the player is deterministic, so once the (frame-relative)
|
||||
event rows repeat with a fixed period, that period is the loop.
|
||||
"""
|
||||
import json, sys
|
||||
|
||||
PAL_CLOCK = 985248.0
|
||||
songs = json.load(open(sys.argv[1]))
|
||||
outDir = sys.argv[2]
|
||||
|
||||
|
||||
def sidHz(lo, hi):
|
||||
return ((hi << 8) | lo) * PAL_CLOCK / 16777216.0
|
||||
|
||||
|
||||
def rowsFor(writes, nframes):
|
||||
# replay register writes frame by frame, producing per-frame voice state
|
||||
regs = [0] * 0x19
|
||||
state = []
|
||||
byFrame = {}
|
||||
for f, r, v in writes:
|
||||
byFrame.setdefault(f, []).append((r, v))
|
||||
for f in range(nframes):
|
||||
for r, v in byFrame.get(f, []):
|
||||
regs[r] = v
|
||||
vs = []
|
||||
for vc in range(3):
|
||||
b = vc * 7
|
||||
vs.append(dict(hz=sidHz(regs[b], regs[b + 1]), gate=regs[b + 4] & 1,
|
||||
wave=regs[b + 4] & 0xF0, sus=regs[b + 6] >> 4))
|
||||
state.append(vs)
|
||||
return state
|
||||
|
||||
|
||||
def atten(sus):
|
||||
# SID sustain 0..15 -> AGI attenuation 14..0 (0 loudest)
|
||||
return max(0, min(14, round(14 - sus * 14 / 15)))
|
||||
|
||||
|
||||
def noisePitch(hz):
|
||||
# brighter hiss for higher SID "frequency"; 0..31, 0 = brightest
|
||||
p = int(31 - min(31, hz / 400.0))
|
||||
return max(0, min(31, p))
|
||||
|
||||
|
||||
def toRows(state):
|
||||
rows = []
|
||||
prev = [dict(hz=None, gate=0, wave=0, sus=0) for _ in range(3)]
|
||||
for vs in state:
|
||||
tone = ["---", "---", "---"]
|
||||
noise = "---"
|
||||
for vc in range(3):
|
||||
cur, was = vs[vc], prev[vc]
|
||||
isNoise = (cur["wave"] & 0x80) != 0
|
||||
on = cur["gate"] and (not was["gate"] or abs(cur["hz"] - was["hz"]) > 0.5
|
||||
or cur["wave"] != was["wave"])
|
||||
off = was["gate"] and not cur["gate"]
|
||||
if on:
|
||||
if isNoise:
|
||||
noise = f"N{noisePitch(cur['hz'])}:{atten(cur['sus'])}"
|
||||
else:
|
||||
hz = max(1, min(65535, int(round(cur["hz"]))))
|
||||
tone[vc] = f"@{hz}:{atten(cur['sus'])}"
|
||||
elif off:
|
||||
if (was["wave"] & 0x80):
|
||||
noise = "off"
|
||||
else:
|
||||
tone[vc] = "off"
|
||||
prev[vc] = dict(cur)
|
||||
rows.append((tone, noise))
|
||||
return rows
|
||||
|
||||
|
||||
def findLoop(rows):
|
||||
# smallest (offset, period) with rows[o+k] == rows[o+p+k] for the tail
|
||||
n = len(rows)
|
||||
for p in range(8, n // 2):
|
||||
for o in range(0, n - 2 * p):
|
||||
if rows[o:o + p] == rows[o + p:o + 2 * p] and rows[o:n - p] == rows[o + p:n]:
|
||||
return o, p
|
||||
return None
|
||||
|
||||
|
||||
for num, s in sorted(songs.items(), key=lambda kv: int(kv[0])):
|
||||
num = int(num)
|
||||
st = rowsFor(s["writes"], s["frames"])
|
||||
rows = toRows(st)
|
||||
loop = findLoop(rows) if s["frames"] >= 4000 else None
|
||||
if loop:
|
||||
o, p = loop
|
||||
rows = rows[:o + p]
|
||||
lines = ["; Space Taxi (C64) song %d, extracted by running the game's own" % num,
|
||||
"; player ($CB00/$CF52) under cpu6502.py and logging SID writes.",
|
||||
"; One row = one PAL frame.", "tickms 20"]
|
||||
for i, (tone, noise) in enumerate(rows):
|
||||
if loop and i == loop[0]:
|
||||
lines.append("loop")
|
||||
cells = tone + ([noise] if noise != "---" or True else [])
|
||||
lines.append(" ".join(cells))
|
||||
open(f"{outDir}/song{num}.song", "w").write("\n".join(lines) + "\n")
|
||||
events = sum(1 for t, n in rows for c in t + [n] if c != "---")
|
||||
waves = sorted({hex(v["wave"]) for vs in st for v in vs if v["gate"]})
|
||||
print(f"song {num}: {len(rows)} rows, {events} events, waveforms {waves}"
|
||||
+ (f", LOOP at row {loop[0]} period {loop[1]}" if loop else ""))
|
||||
94
assets/songs/song0.song
Normal file
94
assets/songs/song0.song
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
; Space Taxi (C64) song 0, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@336:2 @424:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:2 @424:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@336:2 @424:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:2 @476:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@318:2 @252:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:2 @252:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@252:2 @252:2 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
94
assets/songs/song1.song
Normal file
94
assets/songs/song1.song
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
; Space Taxi (C64) song 1, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@252:7 @159:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@283:7 @168:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@252:7 @189:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@283:7 @212:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@318:7 @189:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@336:7 @168:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@318:7 @159:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@336:7 @168:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@378:7 @189:7 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
86
assets/songs/song2.song
Normal file
86
assets/songs/song2.song
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
; Space Taxi (C64) song 2, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@126:0 @159:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@126:0 @168:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@141:0 @189:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@159:0 @159:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@159:0 @168:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@168:0 @189:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@168:0 @159:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
@189:0 @168:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- @159:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @126:0 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
86
assets/songs/song3.song
Normal file
86
assets/songs/song3.song
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
; Space Taxi (C64) song 3, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@252:0 @336:2 @336:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off off ---
|
||||
--- --- --- ---
|
||||
@283:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
@318:0 @336:2 @336:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off off ---
|
||||
--- --- --- ---
|
||||
@252:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @336:2 @336:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off off ---
|
||||
--- --- --- ---
|
||||
@252:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @336:2 @336:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off off ---
|
||||
--- --- --- ---
|
||||
@476:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- @318:2 @378:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @336:2 @378:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @283:2 @252:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @252:2 @476:2 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
126
assets/songs/song4.song
Normal file
126
assets/songs/song4.song
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
; Space Taxi (C64) song 4, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@504:5 @449:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@504:5 @449:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@673:5 @504:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@504:5 @424:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@673:5 @504:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
@848:5 @673:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @635:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @673:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
736
assets/songs/song5.song
Normal file
736
assets/songs/song5.song
Normal file
|
|
@ -0,0 +1,736 @@
|
|||
; Space Taxi (C64) song 5, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@504:2 @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
off --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@504:2 @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
off --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@504:2 @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @159:2 @106:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
off --- --- ---
|
||||
--- --- @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@566:2 @141:2 @100:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
off --- --- ---
|
||||
--- --- @200:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@566:2 @141:2 @100:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @200:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @141:2 @100:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @200:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @141:2 @100:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- @200:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- @141:2 @100:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
off --- --- ---
|
||||
--- --- @200:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@635:2 @504:2 @94:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @94:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@673:2 @566:2 @84:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @168:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @84:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @168:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@755:2 @635:2 @79:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @159:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @79:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @159:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@800:2 @635:2 @71:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @141:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @71:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @141:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@848:2 @635:2 @67:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @133:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @67:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @133:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @67:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @133:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @67:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @133:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@424:2 @318:2 @67:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @133:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@848:2 @336:2 @252:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@848:2 @336:2 @252:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@848:2 @336:2 @252:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@755:2 @318:2 @225:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@755:2 @318:2 @225:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@673:2 @283:2 @212:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@635:2 @252:2 @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@566:2 @283:2 @168:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@755:2 @318:2 @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@635:2 --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@504:2 --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@635:2 --- @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@755:2 @283:2 @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@848:2 --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@755:2 --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@673:2 --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@635:2 --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@566:2 --- @119:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- @189:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
@504:2 @318:2 @126:10 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
840
assets/songs/song6.song
Normal file
840
assets/songs/song6.song
Normal file
|
|
@ -0,0 +1,840 @@
|
|||
; Space Taxi (C64) song 6, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@94:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@126:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@159:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@189:0 @336:1 @449:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @300:1 @400:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @336:1 @449:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@212:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@189:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@159:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@141:0 @424:1 @566:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @378:1 @504:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @424:1 @566:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@119:0 @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@59:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @424:1 @566:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @476:1 @635:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@126:0 @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@63:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@126:0 @566:1 @755:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @504:1 @673:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @566:1 @755:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@141:0 @635:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@71:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @635:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off @635:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@141:0 @566:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @566:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @566:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- off off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@159:0 @534:1 @848:1 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- off ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
852
assets/songs/song7.song
Normal file
852
assets/songs/song7.song
Normal file
|
|
@ -0,0 +1,852 @@
|
|||
; Space Taxi (C64) song 7, extracted by running the game's own
|
||||
; player ($CB00/$CF52) under cpu6502.py and logging SID writes.
|
||||
; One row = one PAL frame.
|
||||
tickms 20
|
||||
--- --- --- ---
|
||||
@378:0 @1270:5 @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1346:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- @1132:5 @119:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1346:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@252:0 @1270:5 @106:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1346:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @1008:5 @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@318:0 --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@336:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1008:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 --- @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @952:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @848:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @755:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1132:5 @119:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1346:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@424:0 @1132:5 @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@378:0 --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@336:0 @1132:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 @1270:5 @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@336:0 @952:5 @119:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@318:0 --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @952:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@318:0 @1008:5 @106:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @755:5 @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@252:0 --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@238:0 @755:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@252:0 @1008:5 @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @952:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@318:0 @1008:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@189:0 @1132:5 @119:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1008:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1132:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 @106:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1132:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@238:0 @1346:5 @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@252:0 @1270:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @1346:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@252:0 @1270:5 @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@238:0 @1510:5 @119:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@252:0 --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@318:0 --- @106:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@283:0 @1510:5 @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@283:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 @1132:5 @141:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1008:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @952:5 @119:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1008:5 off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1132:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @755:5 @106:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 @1132:5 --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 @1199:5 @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
@378:0 --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
@378:0 @1270:5 @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @94:7 ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @126:7 ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- @94:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1510:5 --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- @1270:5 --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- @126:7 ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- off --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
off --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- off ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
--- --- --- ---
|
||||
1508
assets/songs/song8.song
Normal file
1508
assets/songs/song8.song
Normal file
File diff suppressed because it is too large
Load diff
BIN
assets/sprites/sprites.png
(Stored with Git LFS)
BIN
assets/sprites/sprites.png
(Stored with Git LFS)
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1 KiB |
|
|
@ -1,496 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# extractFromDump.py -- convert a VICE C64 memory dump into JoeyLib
|
||||
# Space Taxi assets.
|
||||
#
|
||||
# Reads: /tmp/spacetaxi/mem0000-<labelN>.bin (VICE `save` output;
|
||||
# 2-byte LE start-addr header + 64 KB RAM).
|
||||
# Writes: examples/spacetaxi/assets/tiles/tilebank<N>.png
|
||||
# examples/spacetaxi/assets/levels/level<NN>.txt
|
||||
# examples/spacetaxi/assets/sprites/sprites.png (level1 only)
|
||||
# examples/spacetaxi/assets/font.png (level1 only)
|
||||
#
|
||||
# Per-level:
|
||||
# - Custom charset at $2800-$2FFF (2 KB, 256 chars * 8 bytes mono)
|
||||
# becomes a tile bank PNG: 40 cols x 25 rows of 8x8 cells, with
|
||||
# tile index N at cell (N%40, N/40). The PNG uses the EGA-ish
|
||||
# 16-color palette we already install on stage.
|
||||
# - Screen RAM at $0400-$07E7 (1000 bytes = 40x25 char codes) and
|
||||
# color RAM at $D800-$DBE7 (1000 bytes = 40x25 palette nybbles)
|
||||
# become the tilemap+colormap in the level .txt source.
|
||||
#
|
||||
# Sprite data at $3000-$37FF + sprite pointer table at $07F8-$07FF
|
||||
# is decoded once (it's the same across levels for the active set):
|
||||
# - sprite 0 / 7: the taxi cab body & shadow
|
||||
# - sprite 1..6: passenger frames / animation cels
|
||||
# 64 bytes per sprite, 24x21 1-bit, MSB-leftmost. Color comes from
|
||||
# $D027-$D02E (one color per sprite).
|
||||
#
|
||||
# Usage:
|
||||
# python3 extractFromDump.py stuff/spacetaxi/mem0000-level1.bin level01 1
|
||||
# python3 extractFromDump.py stuff/spacetaxi/mem0000-level2.bin level02 2
|
||||
# args: <dump-path> <level-name-without-extension> <tilebank-id>
|
||||
#
|
||||
# Dumps live in stuff/spacetaxi/ (committed-ish, repo-local) rather
|
||||
# than /tmp so they survive reboots and stay with the project.
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# C64 standard color palette (EGA-ish 16-color JoeyLib mapping).
|
||||
# Indices 0..15 match the C64 VIC-II color register order so a value
|
||||
# read from $D8xx or $D02x maps directly to a JoeyLib palette slot.
|
||||
# Colors below approximate Pepto's standard PAL palette in $RGB form.
|
||||
C64_PALETTE_RGB = [
|
||||
(0x00, 0x00, 0x00), # 0 black
|
||||
(0xFF, 0xFF, 0xFF), # 1 white
|
||||
(0x88, 0x39, 0x32), # 2 red
|
||||
(0x67, 0xB6, 0xBD), # 3 cyan
|
||||
(0x8B, 0x3F, 0x96), # 4 purple
|
||||
(0x55, 0xA0, 0x49), # 5 green
|
||||
(0x40, 0x31, 0x8D), # 6 blue
|
||||
(0xBF, 0xCE, 0x72), # 7 yellow
|
||||
(0x8B, 0x54, 0x29), # 8 orange
|
||||
(0x57, 0x42, 0x00), # 9 brown
|
||||
(0xB8, 0x69, 0x62), # 10 light red
|
||||
(0x50, 0x50, 0x50), # 11 dark gray
|
||||
(0x78, 0x78, 0x78), # 12 mid gray
|
||||
(0x94, 0xE0, 0x89), # 13 light green
|
||||
(0x78, 0x69, 0xC4), # 14 light blue
|
||||
(0x9F, 0x9F, 0x9F), # 15 light gray
|
||||
]
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def load_dump(path):
|
||||
with open(path, "rb") as f:
|
||||
raw = f.read()
|
||||
start = raw[0] | (raw[1] << 8)
|
||||
if start != 0:
|
||||
raise SystemExit(f"{path}: unexpected start ${start:04X}, want $0000")
|
||||
if len(raw) - 2 != 0x10000:
|
||||
raise SystemExit(f"{path}: not a 64K dump (got {len(raw) - 2} bytes)")
|
||||
return raw[2:]
|
||||
|
||||
|
||||
def new_canvas(w, h):
|
||||
img = Image.new("P", (w, h), 0)
|
||||
flat = bytearray()
|
||||
for r, g, b in C64_PALETTE_RGB:
|
||||
flat.extend((r, g, b))
|
||||
flat.extend(b"\x00" * (768 - len(flat)))
|
||||
img.putpalette(bytes(flat))
|
||||
return img
|
||||
|
||||
|
||||
def paint_charset_glyph(px, mem, src_code, bx, by):
|
||||
"""Plot the 8x8 glyph for C64 screen code `src_code` at pixel
|
||||
(bx, by) of the indexed-palette PIL image. Foreground = palette
|
||||
index 1, background untouched (already index 0 from new_canvas).
|
||||
"""
|
||||
src = 0x2800 + src_code * 8
|
||||
for r in range(8):
|
||||
byte = mem[src + r]
|
||||
for c in range(8):
|
||||
if byte & (0x80 >> c):
|
||||
px[bx + c, by + r] = 1
|
||||
|
||||
|
||||
def extract_tilebank(mem, out_path, code_to_slot=None):
|
||||
"""Render the custom charset at $2800-$2FFF as a 320x200 PNG.
|
||||
When `code_to_slot` is None, lays out all 256 chars at their raw
|
||||
C64 positions (40 cols x 25 rows). When `code_to_slot` is given,
|
||||
writes each charset glyph at the slot the level loader expects
|
||||
-- i.e. the bank tile at position N is the glyph for screen-code
|
||||
`code_for_slot[N]`. That keeps level data and tilebank aligned
|
||||
after the level extractor's frequency-sorted code remapping.
|
||||
"""
|
||||
img = new_canvas(320, 200)
|
||||
px = img.load()
|
||||
if code_to_slot is None:
|
||||
for ch in range(256):
|
||||
paint_charset_glyph(px, mem, ch, (ch % 40) * 8, (ch // 40) * 8)
|
||||
else:
|
||||
# Invert: slot -> code. Multiple codes can collide into slot 0
|
||||
# (background); only the first one we see "owns" that slot for
|
||||
# painting purposes -- the rest just fall through to the same
|
||||
# blank tile. Highest priority is the explicit space mapping.
|
||||
slot_to_code = {}
|
||||
for code, slot in code_to_slot.items():
|
||||
slot_to_code.setdefault(slot, code)
|
||||
for slot, code in slot_to_code.items():
|
||||
if slot >= 256:
|
||||
continue
|
||||
paint_charset_glyph(px, mem, code, (slot % 40) * 8, (slot // 40) * 8)
|
||||
img.save(out_path, "PNG", optimize=True)
|
||||
|
||||
|
||||
def build_code_to_slot(mem):
|
||||
"""Build the screen-code -> alphabet-slot map shared between the
|
||||
tilebank emission and the level emission. The slot index is what
|
||||
the runtime sees in level.tilemap[]; the tilebank must mirror this
|
||||
ordering so a level-data slot-N cell renders the charset glyph
|
||||
that was actually used in the original screen RAM at that code.
|
||||
"""
|
||||
screen = mem[0x0400:0x0400 + 1000]
|
||||
codes = sorted(Counter(screen).items(), key=lambda kv: (-kv[1], kv[0]))
|
||||
code_to_slot = {}
|
||||
next_slot = 0
|
||||
for code, _count in codes:
|
||||
if code == 0x20: # space -> always index 0
|
||||
code_to_slot[code] = 0
|
||||
continue
|
||||
if code in code_to_slot:
|
||||
continue
|
||||
if next_slot >= len(ALPHABET):
|
||||
code_to_slot[code] = 0
|
||||
continue
|
||||
# Reserve slot 0 for background (space). The very first
|
||||
# non-space code claims slot 1, etc.
|
||||
if next_slot == 0:
|
||||
next_slot = 1
|
||||
code_to_slot[code] = next_slot
|
||||
next_slot += 1
|
||||
return code_to_slot
|
||||
|
||||
|
||||
def screencode_to_char(code):
|
||||
"""Convert a C64 screen code into a printable tilemap character
|
||||
using the encoding the .txt format already understands:
|
||||
0 -> '.' (treated as 0)
|
||||
1..9 -> '1'..'9' (digits encode the lower 10 indices)
|
||||
but we shift to use up most of the index space below.
|
||||
For arbitrary 0..255 we need a 256-symbol alphabet -- not feasible
|
||||
in a single character per cell. So we encode as TWO chars per cell
|
||||
using base-16 hex would still only cover 0..255. Decision: keep
|
||||
.txt cells one-char and map screen codes via a custom 64-char
|
||||
alphabet plus an @char directive table emitted into the file so
|
||||
the loader knows exactly which screen code each char represents.
|
||||
"""
|
||||
# This function is unused as a one-char encoder; we encode via the
|
||||
# generic alphabet below instead.
|
||||
return code
|
||||
|
||||
|
||||
# Generic 1-char alphabet (62 codes); the level loader's index map
|
||||
# was: '.' or ' '=0, '0'..'9'=0..9, 'A'..'Z'=10..35, 'a'..'z'=36..61.
|
||||
# That's only 62 codes, not 256. The original C64 levels use ~30
|
||||
# distinct screen codes per level so most levels fit. We map the N
|
||||
# most-frequent screen codes to those alphabet slots.
|
||||
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
|
||||
def extract_level(mem, level_name, tilebank_id, out_path, code_to_alpha):
|
||||
"""Render screen RAM + color RAM as an STL1-format .txt source
|
||||
using the prebuilt screen-code -> alphabet-slot mapping. Caller
|
||||
must pass the same mapping that was used to reorder the tilebank
|
||||
PNG; otherwise the level tilemap and tilebank diverge.
|
||||
"""
|
||||
screen = mem[0x0400:0x0400 + 1000] # 40x25
|
||||
color = mem[0xD800:0xD800 + 1000] # 40x25, low nybble = palette slot
|
||||
codes = sorted(Counter(screen).items(), key=lambda kv: (-kv[1], kv[0]))
|
||||
|
||||
# Full 25-row C64 screen. The title screen uses all 25 rows
|
||||
# (PUBLISHED ... SOFTWARE credit wraps across rows 21, 22, 24
|
||||
# with frame chars interleaved). Gameplay HUD draws over the
|
||||
# bottom 3 rows at runtime.
|
||||
playfield_rows = 25
|
||||
|
||||
lines = []
|
||||
lines.append(f"# Extracted from VICE memory dump. Edit at your own risk;")
|
||||
lines.append(f"# regenerating will overwrite. Keep the @tile mappings in")
|
||||
lines.append(f"# sync with the tilebank PNG (extracted from $2800).")
|
||||
lines.append(f"")
|
||||
lines.append(f"@name {level_name.upper()}")
|
||||
lines.append(f"@tilebank {tilebank_id}")
|
||||
lines.append(f"@music 0")
|
||||
bg = mem[0xD021] & 0x0F
|
||||
border = mem[0xD020] & 0x0F
|
||||
lines.append(f"@bgColor {bg}")
|
||||
lines.append(f"@borderColor {border}")
|
||||
|
||||
# Best-effort taxi spawn: middle-top of playfield. The original
|
||||
# script-driven spawn position would need to be lifted out of
|
||||
# game state at dump time; for now hand-edit if you want it
|
||||
# to match the cracker's preferred spawn.
|
||||
lines.append(f"@taxiSpawn 18 2")
|
||||
lines.append(f"")
|
||||
|
||||
# Pads: scan the playfield for "landable surfaces" -- runs of
|
||||
# >= 3 identical non-background tile cells with two or more
|
||||
# empty rows directly above (so the taxi can approach from the
|
||||
# top). Per-row scanning runs top-to-bottom; for the same
|
||||
# surface tile across rows we only take the topmost contiguous
|
||||
# platform edge.
|
||||
bg_code = codes[0][0] if codes else 0x20
|
||||
def is_bg(code):
|
||||
return code == bg_code or code == 0x20
|
||||
|
||||
pads = []
|
||||
seen_used = [False] * (playfield_rows * 40)
|
||||
for row in range(2, playfield_rows):
|
||||
line = screen[row * 40:(row + 1) * 40]
|
||||
prev = screen[(row - 1) * 40:row * 40]
|
||||
prev2 = screen[(row - 2) * 40:(row - 1) * 40]
|
||||
col = 0
|
||||
while col < 40 and len(pads) < 8:
|
||||
if is_bg(line[col]):
|
||||
col += 1
|
||||
continue
|
||||
j = col
|
||||
while j < 40 and line[j] == line[col] and not seen_used[row * 40 + j]:
|
||||
j += 1
|
||||
width = j - col
|
||||
# Approachable from above? At least 2/3 of cells in the
|
||||
# 2 rows above this stretch must be background.
|
||||
above_clear = 0
|
||||
for k in range(col, j):
|
||||
if is_bg(prev[k]):
|
||||
above_clear += 1
|
||||
if is_bg(prev2[k]):
|
||||
above_clear += 1
|
||||
if width >= 3 and above_clear >= width:
|
||||
pads.append((row, col, width))
|
||||
for k in range(col, j):
|
||||
seen_used[row * 40 + k] = True
|
||||
col = j
|
||||
|
||||
lines.append("# Pads auto-detected from landable surfaces (>= 3 wide,")
|
||||
lines.append("# approachable from above). Verify and adjust as needed.")
|
||||
pad_letter = ord("A")
|
||||
for (row, col, width) in pads:
|
||||
if pad_letter > ord("H"):
|
||||
break
|
||||
lines.append(f"@pad {chr(pad_letter)} {col} {row} {width}")
|
||||
pad_letter += 1
|
||||
if pad_letter == ord("A"):
|
||||
lines.append("# (No landable surfaces auto-detected; emitting placeholders.)")
|
||||
lines.append("@pad A 4 10 4")
|
||||
lines.append("@pad B 32 10 4")
|
||||
pad_letter = ord("C")
|
||||
|
||||
lines.append("")
|
||||
# Fare list: simple round-robin between detected pads.
|
||||
pad_count = pad_letter - ord("A")
|
||||
if pad_count >= 2:
|
||||
for k in range(pad_count):
|
||||
src = chr(ord("A") + k)
|
||||
dst = chr(ord("A") + ((k + 1) % pad_count))
|
||||
lines.append(f"@fare {src} {dst} 45")
|
||||
else:
|
||||
lines.append("@fare A B 45")
|
||||
lines.append("")
|
||||
|
||||
# Emit the @tile mapping for round-trip clarity (and so the
|
||||
# author can read a level .txt and tell which screen code maps
|
||||
# to which char). Format: @tile <char> <screencode> -- the
|
||||
# mkstlevel parser ignores these today but they're useful
|
||||
# documentation, and a future loader could honor them.
|
||||
lines.append("# Screen-code -> alphabet-char mapping (one per unique code).")
|
||||
inv = {v: k for k, v in code_to_alpha.items()}
|
||||
for idx in sorted(inv):
|
||||
ch = '.' if idx == 0 else ALPHABET[idx]
|
||||
code = inv[idx]
|
||||
lines.append(f"# @tile '{ch}' = $C64_screencode_{code:02X}")
|
||||
lines.append("")
|
||||
|
||||
# Tilemap (22 rows of 40 chars each).
|
||||
lines.append("@tilemap")
|
||||
for r in range(playfield_rows):
|
||||
row = screen[r * 40:(r + 1) * 40]
|
||||
s = ""
|
||||
for code in row:
|
||||
idx = code_to_alpha.get(code, 0)
|
||||
s += '.' if idx == 0 else ALPHABET[idx]
|
||||
lines.append(s)
|
||||
|
||||
lines.append("")
|
||||
lines.append("@colormap")
|
||||
for r in range(playfield_rows):
|
||||
row = color[r * 40:(r + 1) * 40]
|
||||
s = "".join(
|
||||
('0' if (c & 0x0F) == 0
|
||||
else ALPHABET[(c & 0x0F)]
|
||||
if (c & 0x0F) < len(ALPHABET) else '0')
|
||||
for c in row
|
||||
)
|
||||
lines.append(s)
|
||||
|
||||
with open(out_path, "w") as fp:
|
||||
fp.write("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def extract_sprites(mem, out_path):
|
||||
"""Render C64 hardware sprites into the JoeyLib-expected sprite
|
||||
sheet layout (320x200, 16-color indexed PNG):
|
||||
y= 0..23 : 1 taxi cel (24x24, x = 0)
|
||||
sprite 0 ptr (in level1 dump: $DC -> $3700)
|
||||
The adjacent slot $DD is the landing-gear-down
|
||||
pose, not a thrust frame -- ignored for now.
|
||||
y= 24..47 : 4 passenger cels (16x16 each, x = 0, 16, 32, 48)
|
||||
Sprite 3 ptr ($07FB) is the passenger data; sprites 3-6 share it.
|
||||
"""
|
||||
img = new_canvas(320, 200)
|
||||
px = img.load()
|
||||
|
||||
# Shared multicolor sprite colors from VIC registers.
|
||||
mcm_color0 = mem[0xD025] & 0x0F # 2-bit value 01
|
||||
mcm_color1 = mem[0xD026] & 0x0F # 2-bit value 11
|
||||
mcm_mask = mem[0xD01C] # bit per sprite: 1 = multicolor
|
||||
|
||||
def paint_sprite_at(data_addr, sprite_color, dst_x, dst_y, dst_w, dst_h, multicolor=False):
|
||||
# Mono C64 sprite is 24x21, 1 bit per pixel, 3 bytes per row.
|
||||
# Multicolor C64 sprite is 12 "double-wide pixels" x 21 rows,
|
||||
# 2 bits per pixel, still 3 bytes per row. Color mapping:
|
||||
# 00 -> transparent
|
||||
# 01 -> mcm_color0 ($D025)
|
||||
# 10 -> sprite_color (sprite's own $D027+sp)
|
||||
# 11 -> mcm_color1 ($D026)
|
||||
if sprite_color == 0:
|
||||
sprite_color = 1
|
||||
sx_max = min(24, dst_w)
|
||||
sy_max = min(21, dst_h)
|
||||
for r in range(sy_max):
|
||||
if multicolor:
|
||||
for byte_idx in range(3):
|
||||
byte = mem[data_addr + r * 3 + byte_idx]
|
||||
# 4 multicolor pixels per byte; each spans 2
|
||||
# hardware pixels wide.
|
||||
for pp in range(4):
|
||||
bits = (byte >> ((3 - pp) * 2)) & 0x03
|
||||
if bits == 0:
|
||||
continue
|
||||
if bits == 1:
|
||||
col = mcm_color0
|
||||
elif bits == 2:
|
||||
col = sprite_color
|
||||
else:
|
||||
col = mcm_color1
|
||||
sx0 = byte_idx * 8 + pp * 2
|
||||
if sx0 + 1 >= sx_max:
|
||||
break
|
||||
px[dst_x + sx0, dst_y + r] = col
|
||||
px[dst_x + sx0 + 1, dst_y + r] = col
|
||||
else:
|
||||
for byte_idx in range(3):
|
||||
byte = mem[data_addr + r * 3 + byte_idx]
|
||||
for bit in range(8):
|
||||
sx = byte_idx * 8 + bit
|
||||
if sx >= sx_max:
|
||||
break
|
||||
if byte & (0x80 >> bit):
|
||||
px[dst_x + sx, dst_y + r] = sprite_color
|
||||
|
||||
def paint_c64_sprite(sp_index, dst_x, dst_y, dst_w, dst_h):
|
||||
ptr = mem[0x07F8 + sp_index]
|
||||
if ptr == 0:
|
||||
return False
|
||||
sprite_color = mem[0xD027 + sp_index] & 0x0F
|
||||
is_multicolor = bool(mcm_mask & (1 << sp_index))
|
||||
paint_sprite_at(ptr * 64, sprite_color, dst_x, dst_y, dst_w, dst_h,
|
||||
multicolor=is_multicolor)
|
||||
return True
|
||||
|
||||
# Single taxi cel from sprite 0's actual pointer.
|
||||
paint_c64_sprite(0, 0, 0, 24, 24)
|
||||
|
||||
# 4 passenger cels = the shared C64 sprite 3 (sprites 3..6 all
|
||||
# point to the same data in the level-1 dump).
|
||||
for cel in range(4):
|
||||
paint_c64_sprite(3, cel * 16, 24, 16, 16)
|
||||
|
||||
img.save(out_path, "PNG", optimize=True)
|
||||
|
||||
|
||||
def extract_font(mem, out_path):
|
||||
"""Render the custom charset (which IS the in-game font in C64
|
||||
text-mode games) as the 320x200 font.png. Same layout as
|
||||
tilebank, but here we want each glyph at cell (ascii%40,
|
||||
ascii/40) for jlDrawText's ASCII map.
|
||||
|
||||
For Space Taxi specifically, the charset doubles as the level
|
||||
tile bank AND any in-screen text Sierra-style. We map the C64
|
||||
screen codes used for ASCII display (codes 1..26 = A..Z,
|
||||
32..63 = punctuation/digits) directly to their ASCII positions
|
||||
in the JoeyLib font sheet.
|
||||
"""
|
||||
img = new_canvas(320, 200)
|
||||
px = img.load()
|
||||
|
||||
def render_at(ascii_code, charset_idx):
|
||||
if ascii_code < 32 or ascii_code >= 128:
|
||||
return
|
||||
src = 0x2800 + (charset_idx & 0xFF) * 8
|
||||
col = ascii_code % 40
|
||||
row = ascii_code // 40
|
||||
bx = col * 8
|
||||
by = row * 8
|
||||
for r in range(8):
|
||||
byte = mem[src + r]
|
||||
for c in range(8):
|
||||
if byte & (0x80 >> c):
|
||||
px[bx + c, by + r] = 1
|
||||
|
||||
# C64 upper-only screen codes: 1..26 = A..Z, 27=[ 28=£ 29=] 30=↑ 31=←
|
||||
# 32 = space, 33..63 = !"# ... ?, etc. We map ASCII directly.
|
||||
for c in range(32, 64):
|
||||
render_at(c, c) # symbols + digits 1:1
|
||||
for c, code in enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
|
||||
render_at(ord(code), 1 + c) # screen code 1..26 -> A..Z
|
||||
|
||||
img.save(out_path, "PNG", optimize=True)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print("usage: extractFromDump.py <dump-path> <level-name> <tilebank-id>", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
dump_path = sys.argv[1]
|
||||
level_name = sys.argv[2]
|
||||
tilebank_id = int(sys.argv[3])
|
||||
|
||||
mem = load_dump(dump_path)
|
||||
print(f"loaded {dump_path}: 64 KB OK")
|
||||
|
||||
tiles_dir = os.path.join(ROOT, "assets", "tiles")
|
||||
sprites_dir = os.path.join(ROOT, "assets", "sprites")
|
||||
levels_dir = os.path.join(ROOT, "assets", "levels")
|
||||
os.makedirs(tiles_dir, exist_ok=True)
|
||||
os.makedirs(sprites_dir, exist_ok=True)
|
||||
os.makedirs(levels_dir, exist_ok=True)
|
||||
|
||||
# Build the screen-code -> bank-slot mapping once and feed it to
|
||||
# both extractors so the tilemap and tilebank stay aligned.
|
||||
code_to_slot = build_code_to_slot(mem)
|
||||
|
||||
# 8.3 DOS naming -- "tilebank0" is 9 chars and fopen fails under
|
||||
# DOSBox strict 8.3 mode. Keep the short form across the pipeline.
|
||||
tilebank_path = os.path.join(tiles_dir, f"tbank{tilebank_id}.png")
|
||||
extract_tilebank(mem, tilebank_path, code_to_slot)
|
||||
print(f" wrote {tilebank_path}")
|
||||
|
||||
level_path = os.path.join(levels_dir, f"{level_name}.txt")
|
||||
extract_level(mem, level_name, tilebank_id, level_path, code_to_slot)
|
||||
print(f" wrote {level_path}")
|
||||
|
||||
# Sprites + font come from level 1 only (sprite pointer table is
|
||||
# script-driven so it differs by scene; we use the first dump's
|
||||
# set as the canonical sheet, plus its charset as the font).
|
||||
if tilebank_id == 1:
|
||||
sprite_path = os.path.join(sprites_dir, "sprites.png")
|
||||
extract_sprites(mem, sprite_path)
|
||||
print(f" wrote {sprite_path}")
|
||||
|
||||
font_path = os.path.join(ROOT, "assets", "font.png")
|
||||
extract_font(mem, font_path)
|
||||
print(f" wrote {font_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
generated/amiga/sprites/sprites.spc
(Stored with Git LFS)
BIN
generated/amiga/sprites/sprites.spc
(Stored with Git LFS)
Binary file not shown.
BIN
generated/amiga/sprites/sprites.spr
(Stored with Git LFS)
BIN
generated/amiga/sprites/sprites.spr
(Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue