New asset manager (#1343)

This aims to deprecate all the Splat tools in `tools/splat_ext` in
favour of a more centralised asset manager. This brings the following
advantages:

* Much faster extraction
* Faster build
* Automatically define `static` symbols or unique names whenever
`static` is not possible
* Allow to embed assets into the output binary
* Drastically simplify `Makefile` by removing all the asset build rules
* Avoid situations where it is not possible to extract and build assets
that is not 4-byte aligned

This is achieved by having the splat YAML targeting a normal C file as
data and have an external tool to take care of the following:

1. Extract asset files straight from the overlay binary file into human
readable file in `assets/st/STAGE_NAME`
2. Build assets as header files that go into `src/st/STAGE_NAME` to just
include them from any C file

This requires each stage header to have the following new format: please
see `src/st/nz0/header.c`

Built assets in `src/st` are ignored by Git.

As for now, for simplicity sake, the steps `make extract_assets` and
`make build_assets` are just executed within `make extract` exclusively
for the US version.

I plan to auto-generate files such as `src/st/nz0/tile_data.c`.

For a first iteration I am aiming to handle the following:

* [X] Extract rooms: `assets/st/*/rooms.json`
* [X] Extract room layers: `assets/st/*/entity_layouts.json`
* [X] Extract tilemap data: `assets/st/*/tilemap_*.bin`
* [X] Extract tilemap definitions: `assets/st/*/tiledef_*.json`
* [X] Extract sprites: `assets/st/*/sprites.json`
* [x] Extract entity layouts
* [X] Build rooms: `src/st/*/rooms.h`
* [X] Build room layers: `src/st/*/layers.h`
* [X] Build tilemap data: `src/st/*/tilemap_*.h`
* [X] Build tilemap definitions: `src/st/*/tiledef_*.h`
* [x] Build sprites (aka `g_SpriteBanks`)
* [x] Build entity layouts
* [x] Allow the tool to suggest how to adjust the Splat config for each
overlay

I want the tool to cover the following stages:
* [x] CEN
* [x] DRE
* ~MAD~ I do not think this can be done, it is way too different from
the other overlays
* [x] NO3
* [x] NP3
* [X] NZ0
* [x] ST0
* [X] WRP
* [x] RWRP
* ~WRP (PSP)~ Maybe in a follow-up PR

For a later iteration I plan on extracting and build:

* Entity GFX thingie
* The CLUT thingie in the header
* Uncompressed GFX data
* Cutscene data
* Blueprints
* The `src/config_us.h` thingie

---------

Co-authored-by: Josh Lory <josh.lory@outlook.com>
This commit is contained in:
Luciano Ciccariello 2024-07-03 04:38:36 +08:00 committed by GitHub
parent f7882814b0
commit de46f99e2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 2627 additions and 1510 deletions

View File

@ -7,19 +7,49 @@ on:
paths:
- 'include/**'
- 'src/**'
- 'tools/sotn-assets/**'
- 'cmake/**'
- 'CMakeLists*'
pull_request:
paths:
- 'include/**'
- 'src/**'
- 'tools/sotn-assets/**'
- 'cmake/**'
- 'CMakeLists*'
workflow_dispatch:
jobs:
extract-assets:
runs-on: ubuntu-latest
steps:
- name: Clone main repo
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
submodules: false
- name: Get dependencies
uses: actions/cache@v4
id: get-dependencies
with:
path: 'disks/dependencies'
key: sotn-us-deps
- name: Setting up dependencies
working-directory: disks
run: cat dependencies/* | tar -zxf -
- name: Extract dependencies
run: make extract_disk
- name: Extract game assets
run: make -j extract_assets
- name: Export assets
uses: actions/upload-artifact@v4
with:
name: assets
path: assets
build-linux:
runs-on: ubuntu-latest
needs: extract-assets
steps:
- name: Install requirements
run: sudo apt-get update && sudo apt-get install build-essential libsdl2-dev
@ -28,6 +58,13 @@ jobs:
with:
ref: ${{ github.ref }}
submodules: false
- name: Get assets
uses: actions/download-artifact@v4
with:
name: assets
path: assets
- name: Build assets
run: make -j build_assets
- name: Build
run: |
cmake -B ${{github.workspace}}/pc
@ -35,14 +72,27 @@ jobs:
build-macos:
runs-on: macos-latest
needs: extract-assets
steps:
- name: Install requirements
run: brew install SDL2
- name: Install Go
run: |
curl -L -o go.tar.gz https://go.dev/dl/go1.22.4.darwin-amd64.tar.gz
tar -C ~ -xzf go.tar.gz
rm go.tar.gz
- name: Clone repo
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
submodules: false
- name: Get assets
uses: actions/download-artifact@v4
with:
name: assets
path: assets
- name: Build assets
run: make -j build_assets
- name: Build
run: |
cmake -B ${{github.workspace}}/pc
@ -50,6 +100,7 @@ jobs:
build-windows:
runs-on: windows-latest
needs: extract-assets
steps:
- name: Install requirements
run: |
@ -58,6 +109,22 @@ jobs:
7z x C:\temp-sdl2\SDL2-devel-2.28.5-VC.zip -oC:\temp-sdl2
- name: Clone repo
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
submodules: false
- name: Get assets
uses: actions/download-artifact@v4
with:
name: assets
path: assets
- uses: actions/setup-go@v5
with:
go-version: '^1.22.4'
- name: Build assets
run: |
cd tools/sotn-assets; go install; cd -
$SOTN_ASSETS = (Get-Command sotn-assets).Source
& make -j build_assets SOTNASSETS=$SOTN_ASSETS
- name: Build
run: |
cmake -B ${{github.workspace}}/pc -DCMAKE_BUILD_TYPE=Release -DSDL2_PATH=C:\temp-sdl2\SDL2-2.28.5
@ -65,6 +132,7 @@ jobs:
build-linux-lle:
runs-on: ubuntu-latest
needs: extract-assets
steps:
- name: Install requirements
run: sudo apt-get update && sudo apt-get install build-essential libsdl2-dev
@ -73,6 +141,13 @@ jobs:
with:
ref: ${{ github.ref }}
submodules: false
- name: Get assets
uses: actions/download-artifact@v4
with:
name: assets
path: assets
- name: Build assets
run: make -j build_assets
- name: Build
run: |
cmake -B ${{github.workspace}}/pc -DWANT_LIBSND_LLE=1

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ expected/
disks/
.vscode/settings.json
.idea/
tools/go
!tools/sotn-debugmodule/sotn-debugmodule.ld

View File

@ -220,10 +220,16 @@ set(SOURCE_FILES_STAGE_SEL
set(SOURCE_FILES_STAGE_WRP
src/pc/stages/stage_wrp.c
src/st/wrp/6FD0.c
src/st/wrp/d_1b8.c
src/st/wrp/e_laydef.c
src/st/wrp/d_3e4.c
src/st/wrp/st_debug.c
src/st/wrp/e_breakable.c
src/st/wrp/d_608.c
src/st/wrp/rooms.c
src/st/wrp/e_layout.c
src/st/wrp/tile_data.c
src/st/wrp/sprites.c
src/st/wrp/warp.c
src/st/wrp/st_update.c
src/st/wrp/collision.c

View File

@ -67,6 +67,7 @@ MASPSX := $(PYTHON) $(MASPSX_APP) --expand-div --aspsx-version=2.34
GO := $(HOME)/go/bin/go
GOPATH := $(HOME)/go
SOTNDISK := $(GOPATH)/bin/sotn-disk
SOTNASSETS := $(GOPATH)/bin/sotn-assets
GFXSTAGE := $(PYTHON) $(TOOLS_DIR)/gfxstage.py
PNG2S := $(PYTHON) $(TOOLS_DIR)/png2s.py
ICONV := iconv --from-code=UTF-8 --to-code=Shift-JIS
@ -81,11 +82,21 @@ define list_src_files
$(foreach dir,$(SRC_DIR)/$(1)/psxsdk,$(wildcard $(dir)/**.c))
$(foreach dir,$(ASSETS_DIR)/$(1),$(wildcard $(dir)/**))
endef
define list_st_src_files
$(foreach dir,$(ASM_DIR)/$(1),$(wildcard $(dir)/**.s))
$(foreach dir,$(ASM_DIR)/$(1)/data,$(wildcard $(dir)/**.s))
$(foreach dir,$(SRC_DIR)/$(1),$(wildcard $(dir)/**.c))
$(foreach dir,$(ASSETS_DIR)/$(1),$(wildcard $(dir)/D_8018*.bin))
endef
define list_o_files
$(foreach file,$(call list_src_files,$(1)),$(BUILD_DIR)/$(file).o)
endef
define list_st_o_files
$(foreach file,$(call list_st_src_files,$(1)),$(BUILD_DIR)/$(file).o)
endef
define list_shared_src_files
$(foreach dir,$(SRC_DIR)/$(1),$(wildcard $(dir)/*.c))
endef
@ -104,6 +115,7 @@ define link
endef
SOTNDISK_SOURCES := $(shell find tools/sotn-disk -name '*.go')
SOTNASSETS_SOURCES := $(shell find tools/sotn-assets -name '*.go')
CHECK_FILES := $(shell cut -d' ' -f3 config/check.$(VERSION).sha)
@ -191,6 +203,7 @@ dra: $(BUILD_DIR)/DRA.BIN
$(BUILD_DIR)/DRA.BIN: $(BUILD_DIR)/$(DRA).elf
$(OBJCOPY) -O binary $< $@
$(BUILD_DIR)/$(DRA).elf: $(call list_o_files,dra)
echo $(call list_o_files,dra)
$(call link,dra,$@)
ric: $(BUILD_DIR)/RIC.BIN
@ -291,7 +304,9 @@ $(BUILD_DIR)/stmad.elf: $$(call list_o_files,st/mad) $$(call list_shared_o_files
-T $(CONFIG_DIR)/undefined_syms.beta.txt \
-T $(CONFIG_DIR)/undefined_syms_auto.stmad.txt \
-T $(CONFIG_DIR)/undefined_funcs_auto.stmad.txt
$(BUILD_DIR)/st%.elf: $$(call list_o_files,st/$$*) $$(call list_shared_o_files,st)
$(BUILD_DIR)/stsel.elf: $$(call list_o_files,st/sel) $$(call list_shared_o_files,st)
$(call link,stsel,$@)
$(BUILD_DIR)/st%.elf: $$(call list_st_o_files,st/$$*) $$(call list_shared_o_files,st)
$(call link,st$*,$@)
# Weapon overlays
@ -425,8 +440,8 @@ update-dependencies: $(ASMDIFFER_APP) $(M2CTX_APP) $(M2C_APP) $(MASPSX_APP) $(SA
cd $(SATURN_SPLITTER_DIR)/rust-dis && cargo build --release
cd $(SATURN_SPLITTER_DIR)/adpcm-extract && cargo build --release
pip3 install -r $(TOOLS_DIR)/requirements-python.txt
$(GO) install github.com/xeeynamo/sotn-decomp/tools/gfxsotn@latest
$(GO) install github.com/xeeynamo/sotn-decomp/tools/sotn-disk@latest
rm $(SOTNDISK) && make $(SOTNDISK) || true
rm $(SOTNASSETS) && make $(SOTNASSETS) || true
git clean -fd bin/
bin/%: bin/%.tar.gz
@ -449,11 +464,13 @@ $(MASPSX_APP):
git submodule init $(MASPSX_DIR)
git submodule update $(MASPSX_DIR)
$(GO):
curl -L -o go1.19.7.linux-amd64.tar.gz https://go.dev/dl/go1.19.7.linux-amd64.tar.gz
tar -C $(HOME) -xzf go1.19.7.linux-amd64.tar.gz
rm go1.19.7.linux-amd64.tar.gz
curl -L -o go1.22.4.linux-amd64.tar.gz https://go.dev/dl/go1.22.4.linux-amd64.tar.gz
tar -C $(HOME) -xzf go1.22.4.linux-amd64.tar.gz
rm go1.22.4.linux-amd64.tar.gz
$(SOTNDISK): $(GO) $(SOTNDISK_SOURCES)
cd tools/sotn-disk; $(GO) install
$(SOTNASSETS): $(GO) $(SOTNASSETS_SOURCES)
cd tools/sotn-assets; $(GO) install
$(BUILD_DIR)/%.s.o: %.s
mkdir -p $(dir $@)
@ -463,29 +480,18 @@ $(BUILD_DIR)/%.c.o: %.c $(MASPSX_APP) $(CC1PSX)
$(CPP) $(CPP_FLAGS) -lang-c $< | $(SOTNSTR) | $(ICONV) | $(CC) $(CC_FLAGS) $(PSXCC_FLAGS) | $(MASPSX) | $(AS) $(AS_FLAGS) -o $@
# Handles assets
$(BUILD_DIR)/$(ASSETS_DIR)/%.layoutobj.json.o: $(ASSETS_DIR)/%.layoutobj.json
./tools/splat_ext/layoutobj.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(LD) -r -b binary -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(BUILD_DIR)/$(ASSETS_DIR)/%.roomdef.json.o: $(ASSETS_DIR)/%.roomdef.json
./tools/splat_ext/roomdef.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(LD) -r -b binary -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(BUILD_DIR)/$(ASSETS_DIR)/%.layers.json.o: $(ASSETS_DIR)/%.layers.json
./tools/splat_ext/layers.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/%.tiledef.json.o: $(ASSETS_DIR)/%.tiledef.json
./tools/splat_ext/tiledef.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/%.spritesheet.json.o: $(ASSETS_DIR)/%.spritesheet.json
./tools/splat_ext/spritesheet.py encode $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/%.animset.json.o: $(ASSETS_DIR)/%.animset.json
./tools/splat_ext/animset.py gen-asm $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/%.json.o: $(ASSETS_DIR)/%.json
./tools/splat_ext/assets.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/%.tilelayout.bin.o: $(ASSETS_DIR)/%.tilelayout.bin
$(LD) -r -b binary -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(ASSETS_DIR)/$*.tilelayout.bin
$(BUILD_DIR)/$(ASSETS_DIR)/dra/%.json.o: $(ASSETS_DIR)/dra/%.json
./tools/splat_ext/assets.py $< $(BUILD_DIR)/$(ASSETS_DIR)/dra/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/dra/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/dra/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/ric/%.json.o: $(ASSETS_DIR)/ric/%.json
./tools/splat_ext/assets.py $< $(BUILD_DIR)/$(ASSETS_DIR)/ric/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/ric/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/ric/$*.s
$(BUILD_DIR)/$(ASSETS_DIR)/%.bin.o: $(ASSETS_DIR)/%.bin
$(LD) -r -b binary -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $<
$(BUILD_DIR)/$(ASSETS_DIR)/%.dec.o: $(ASSETS_DIR)/%.dec

View File

@ -12,6 +12,8 @@ PSX_BASE_SYMS := $(CONFIG_DIR)/symbols.$(VERSION).txt
extract_us: $(addprefix $(BUILD_DIR)/,$(addsuffix .ld,$(PSX_US_TARGETS)))
$(PNG2S) bdecode config/gfx.game.json disks/us assets/game
make extract_assets
make build_assets
extract_hd: $(addprefix $(BUILD_DIR)/,$(addsuffix .ld,$(PSX_HD_TARGETS)))
extract_disk_us: extract_disk_psxus
@ -46,6 +48,25 @@ $(BUILD_DIR)/weapon.ld: $(CONFIG_DIR)/splat.$(VERSION).weapon.yaml $(PSX_BASE_SY
$(SPLAT) $<
touch $@
extract_assets: $(SOTNASSETS)
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/CEN/CEN.BIN -o assets/st/cen
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/DRE/DRE.BIN -o assets/st/dre
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/NO3/NO3.BIN -o assets/st/no3
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/NP3/NP3.BIN -o assets/st/np3
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/NZ0/NZ0.BIN -o assets/st/nz0
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/ST0/ST0.BIN -o assets/st/st0
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/WRP/WRP.BIN -o assets/st/wrp
$(SOTNASSETS) extract -stage_ovl disks/$(VERSION)/ST/RWRP/RWRP.BIN -o assets/st/rwrp
build_assets: $(SOTNASSETS)
$(SOTNASSETS) build_all -i assets/st/cen -o src/st/cen/
$(SOTNASSETS) build_all -i assets/st/dre -o src/st/dre/
$(SOTNASSETS) build_all -i assets/st/no3 -o src/st/no3/
$(SOTNASSETS) build_all -i assets/st/np3 -o src/st/np3/
$(SOTNASSETS) build_all -i assets/st/nz0 -o src/st/nz0/
$(SOTNASSETS) build_all -i assets/st/st0 -o src/st/st0/
$(SOTNASSETS) build_all -i assets/st/wrp -o src/st/wrp/
$(SOTNASSETS) build_all -i assets/st/rwrp -o src/st/rwrp/
$(BUILD_DIR)/assets/dra/memcard_%.png.o: assets/dra/memcard_%.png
mkdir -p $(dir $@)
$(PNG2S) encode $< \

View File

@ -34,21 +34,22 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0xDC, layers, rooms]
- [0xDC, .data, header] # layers
- [0x134, data]
- [0x1EC, .data, e_laydef] # layout entries header
- [0x394, data]
- [0x7E4, .data, collision]
- [0xBA4, .data, e_red_door]
- [0xBBC, data]
- [0x10AC, .data, entity_relic_orb]
- [0x1110, data]
- [0x12D4, roomdef, g_Rooms]
- [0x1300, data]
- [0x69EC, tilelayout, D_801869EC]
- [0x6DEC, tilelayout, D_80186DEC]
- [0x7FEC, tilelayout, D_80187FEC]
- [0x81EC, tiledef, t_801884EC, D_801884EC]
- [0x85FC, tiledef, t_8018C5FC, D_8018C5FC]
- [0xC60C, data]
- [0x12D4, .data, rooms]
- [0x1300, .data, e_layout] # layout entries data
- [0x13F0, data]
- [0x69EC, .data, tile_data] # tile data
- [0x81EC, .data, tile_data] # tile definitions
- [0xC60C, .data, sprites]
- [0xD414, rodata]
- [0xD42C, .rodata, holyglassescutscene]
- [0xD4B0, .rodata, F890]
- [0xD4D8, .rodata, e_red_door] # EntityRedDoor

View File

@ -34,8 +34,10 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0xE8, layers, rooms]
- [0xE8, .data, header] # layers
- [0x128, data]
- [0x220, .data, e_laydef] # layout entries header
- [0x3C8, data]
- [0x590, data]
- [0x610, data]
- [0x630, data]
@ -50,14 +52,13 @@ segments:
- [0xD80, data]
- [0x1270, .data, entity_relic_orb]
- [0x12D4, data]
- [0x1498, roomdef, g_Rooms]
- [0x1498, .data, rooms]
- [0x14AC, data]
- [0x1608, .data, e_layout] # layout entries data
- [0x16C0, data]
- [0xB548, tilelayout, D_8018B548]
- [0xB948, tilelayout, D_8018B948]
- [0xBD48, tiledef, t_8018C148, D_8018C148]
- [0xC158, tiledef, t_80190158, D_80190158]
- [0x10168, data]
- [0xB548, .data, tile_data] # tile data
- [0xBD48, .data, tile_data] # tile definitions
- [0x10168, .data, sprites]
- [0x1171C, .rodata, 11A64]
- [0x11728, .rodata, succubus] # EntitySuccubus
- [0x11808, .rodata, succubuscutscene]

View File

@ -43,7 +43,7 @@ segments:
- [0xE40, data]
- [0xEF8, .data, entity_relic_orb]
- [0xF5C, data]
- [0x1130, roomdef, g_Rooms]
- [0x1130, data, g_Rooms]
- [0x11D4, data] # assets/st/mad/0.objlayout
# - [0x11E8, data] # assets/st/mad/1.objlayout
# - [0x1206, data] # assets/st/mad/2.objlayout

View File

@ -34,9 +34,10 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0x1C4, layers, rooms]
- [0x1C4, .data, header] # layers
- [0x5A4, data]
- [0x920, data]
- [0x77C, .data, e_laydef] # layout entries header
- [0x924, data]
- [0xAAC, data]
- [0xDC0, data]
- [0xDD0, data]
@ -57,56 +58,12 @@ segments:
- [0x3AC0, data]
- [0x3AF0, data]
- [0x3B30, data]
- [0x3CC4, roomdef, g_Rooms]
- [0x3DC8, data]
- [0x3CC4, .data, rooms]
- [0x3DC8, .data, e_layout] # layout entries data
- [0x4CE0, data]
- [0x18838, tilelayout, D_80198838]
- [0x19438, tilelayout, D_80199438]
- [0x19638, tilelayout, D_80199638]
- [0x19A38, tilelayout, D_80199A38]
- [0x19E38, tilelayout, D_80199E38]
- [0x1AC38, tilelayout, D_8019AC38]
- [0x1AE38, tilelayout, D_8019AE38]
- [0x1B038, tilelayout, D_8019B038]
- [0x1B238, tilelayout, D_8019B238]
- [0x1B638, tilelayout, D_8019B638]
- [0x1BA38, tilelayout, D_8019BA38]
- [0x1C238, tilelayout, D_8019C238]
- [0x1CA38, tilelayout, D_8019CA38]
- [0x1CC38, tilelayout, D_8019CC38]
- [0x1CE38, tilelayout, D_8019CE38]
- [0x1DA38, tilelayout, D_8019DA38]
- [0x1E638, tilelayout, D_8019E638]
- [0x1E838, tilelayout, D_8019E838]
- [0x1EA38, tilelayout, D_8019EA38]
- [0x1F638, tilelayout, D_8019F638]
- [0x1F838, tilelayout, D_8019F838]
- [0x1FA38, tilelayout, D_8019FA38]
- [0x1FC38, tilelayout, D_8019FC38]
- [0x20038, tilelayout, D_801A0038]
- [0x20238, tilelayout, D_801A0238]
- [0x20838, tilelayout, D_801A0838]
- [0x20E38, tilelayout, D_801A0E38]
- [0x21038, tilelayout, D_801A1038]
- [0x21238, tilelayout, D_801A1238]
- [0x21438, tilelayout, D_801A1438]
- [0x21638, tilelayout, D_801A1638]
- [0x22238, tilelayout, D_801A2238]
- [0x22E38, tilelayout, D_801A2E38]
- [0x23038, tilelayout, D_801A3038]
- [0x23238, tilelayout, D_801A3238]
- [0x23438, tilelayout, D_801A3438]
- [0x23638, tilelayout, D_801A3638]
- [0x25A38, tilelayout, D_801A5A38]
- [0x25C38, tilelayout, D_801A5C38]
- [0x25E38, tilelayout, D_801A5E38]
- [0x26038, tilelayout, D_801A6038]
- [0x26238, tilelayout, D_801A6238]
- [0x26438, tilelayout, D_801A6438]
- [0x26638, tiledef, t_801A6A38, D_801A6A38]
- [0x26A48, tiledef, t_801AAA48, D_801AAA48]
- [0x2AA58, tiledef, t_801AEA58, D_801AEA58]
- [0x2EA68, data]
- [0x18838, .data, tile_data] # tile data
- [0x26638, .data, tile_data] # tile definitions
- [0x2EA68, .data, sprites]
- [0x373E0, data]
- [0x373E8, .rodata, 377D4] # EntityCastleDoor
- [0x37400, .rodata, 377D4] # EntityStairwayPiece

View File

@ -34,8 +34,10 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0x1D0, layers, rooms]
- [0x1D0, .data, header] # layers
- [0x558, data]
- [0x728, .data, e_laydef] # layout entries header
- [0x8D0, data]
- [0xE50, data]
- [0x10C0, data]
- [0x1300, data]
@ -60,51 +62,12 @@ segments:
- [0x3580, data]
- [0x3720, data]
- [0x3760, data]
- [0x3A7C, roomdef, g_Rooms]
- [0x3B68, data]
- [0x168F4, tilelayout, D_801968F4]
- [0x174F4, tilelayout, D_801974F4]
- [0x176F4, tilelayout, D_801976F4]
- [0x17AF4, tilelayout, D_80197AF4]
- [0x17EF4, tilelayout, D_80197EF4]
- [0x18CF4, tilelayout, D_80198CF4]
- [0x18EF4, tilelayout, D_80198EF4]
- [0x190F4, tilelayout, D_801990F4]
- [0x192F4, tilelayout, D_801992F4]
- [0x196F4, tilelayout, D_801996F4]
- [0x19AF4, tilelayout, D_80199AF4]
- [0x1A2F4, tilelayout, D_8019A2F4]
- [0x1AAF4, tilelayout, D_8019AAF4]
- [0x1ACF4, tilelayout, D_8019ACF4]
- [0x1AEF4, tilelayout, D_8019AEF4]
- [0x1BAF4, tilelayout, D_8019BAF4]
- [0x1C6F4, tilelayout, D_8019C6F4]
- [0x1C8F4, tilelayout, D_8019C8F4]
- [0x1CAF4, tilelayout, D_8019CAF4]
- [0x1D6F4, tilelayout, D_8019D6F4]
- [0x1D8F4, tilelayout, D_8019D8F4]
- [0x1DAF4, tilelayout, D_8019DAF4]
- [0x1DCF4, tilelayout, D_8019DCF4]
- [0x1E0F4, tilelayout, D_8019E0F4]
- [0x1E2F4, tilelayout, D_8019E2F4]
- [0x1E8F4, tilelayout, D_8019E8F4]
- [0x1EEF4, tilelayout, D_8019EEF4]
- [0x1F0F4, tilelayout, D_8019F0F4]
- [0x1F2F4, tilelayout, D_8019F2F4]
- [0x1F4F4, tilelayout, D_8019F4F4]
- [0x1F6F4, tilelayout, D_8019F6F4]
- [0x202F4, tilelayout, D_801A02F4]
- [0x20EF4, tilelayout, D_801A0EF4]
- [0x210F4, tilelayout, D_801A10F4]
- [0x212F4, tilelayout, D_801A12F4]
- [0x214F4, tilelayout, D_801A14F4]
- [0x216F4, tilelayout, D_801A16F4]
- [0x218F4, tilelayout, D_801A18F4]
- [0x21AF4, tilelayout, D_801A1AF4]
- [0x21CF4, tiledef, t_801A20F4, D_801A20F4]
- [0x22104, tiledef, t_801A6104, D_801A6104]
- [0x26114, tiledef, t_801AA114, D_801AA114]
- [0x2A124, data]
- [0x3A7C, .data, rooms]
- [0x3B68, .data, e_layout] # layout entries data
- [0x49E0, data]
- [0x168F4, .data, tile_data] # tile data
- [0x21CF4, .data, tile_data] # tile definitions
- [0x2A124, .data, sprites]
- [0x31EA0, data]
- [0x31EA8, .rodata, 3246C] # EntityStairwayPiece
- [0x31EBC, .rodata, 365FC] # func_801B65FC

View File

@ -33,10 +33,11 @@ segments:
align: 4
subalign: 4
subsegments:
- [0x0, .data, d_0]
- [0x40, data]
- [0x164, layers, rooms]
- [0x0, .data, header]
- [0x164, .data, header] # layers
- [0x47C, data]
- [0x8EC, .data, e_laydef] # layout entries header
- [0xA94, data]
- [0x15A0, .data, collision]
- [0x1960, .data, e_red_door]
- [0x1978, data]
@ -50,46 +51,14 @@ segments:
- [0x2220, data]
- [0x2600, data]
- [0x2710, data]
- [0x272C, roomdef, g_Rooms]
- [0x2830, data]
- [0x2840, data]
- [0x2850, data]
- [0x16A5C, tilelayout, D_80196A5C]
- [0x16C5C, tilelayout, D_80196C5C]
- [0x16E5C, tilelayout, D_80196E5C]
- [0x1725C, tilelayout, D_8019725C]
- [0x17A5C, tilelayout, D_80197A5C]
- [0x17C5C, tilelayout, D_80197C5C]
- [0x17E5C, tilelayout, D_80197E5C]
- [0x1805C, tilelayout, D_8019805C]
- [0x1845C, tilelayout, D_8019845C]
- [0x1865C, tilelayout, D_8019865C]
- [0x1885C, tilelayout, D_8019885C]
- [0x18A5C, tilelayout, D_80198A5C]
- [0x1905C, tilelayout, D_8019905C]
- [0x1925C, tilelayout, D_8019925C]
- [0x19C5C, tilelayout, D_80199C5C]
- [0x1A05C, tilelayout, D_8019A05C]
- [0x1A45C, tilelayout, D_8019A45C]
- [0x1AA5C, tilelayout, D_8019AA5C]
- [0x1AE5C, tilelayout, D_8019AE5C]
- [0x1BE5C, tilelayout, D_8019BE5C]
- [0x1C65C, tilelayout, D_8019C65C]
- [0x1C85C, tilelayout, D_8019C85C]
- [0x1D45C, tilelayout, D_8019D45C]
- [0x1D65C, tilelayout, D_8019D65C]
- [0x1E25C, tilelayout, D_8019E25C]
- [0x1EE5C, tilelayout, D_8019EE5C]
- [0x1F25C, tilelayout, D_8019F25C]
- [0x2005C, tilelayout, D_801A005C]
- [0x2025C, tilelayout, D_801A025C]
- [0x2045C, tilelayout, D_801A045C]
- [0x2065C, tilelayout, D_801A065C]
- [0x2085C, tilelayout, D_801A085C]
- [0x20A5C, tiledef, t_801A0E5C, D_801A0E5C]
- [0x20E6C, tiledef, t_801A4E6C, D_801A4E6C]
- [0x24E7C, tiledef, t_801A6E7C, D_801A6E7C]
- [0x26E8C, data]
- [0x272C, .data, rooms]
- [0x2830, data, D_80182830]
- [0x2884, .data, e_layout] # layout entries data
- [0x3B0C, data]
- [0x16A5C, .data, tile_data] # tile data
- [0x20A5C, .data, tile_data] # tile definitions
- [0x26E8C, .data, sprites]
- [0x3058C, rodata]
- [0x305A4, .rodata, 30958] # func_801B3C38
- [0x305B8, .rodata, bossfight] # EntityBossFightManager
- [0x305E8, .rodata, bossfight] # EntityBossRoomBlock

View File

@ -34,7 +34,10 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0xB8, data]
- [0xB8, .data, header] # layers
- [0x1B8, data]
- [0x23C, .data, e_laydef] # layout entries header
- [0x3E4, data]
- [0x4E0, data]
- [0x5E0, data]
- [0x6BC, .data, collision]
@ -43,8 +46,13 @@ segments:
- [0xF84, .data, entity_relic_orb]
- [0xFE8, data]
- [0x10A0, data]
- [0x11AC, roomdef, g_Rooms]
- [0x1228, data]
- [0x11AC, .data, rooms]
- [0x1228, .data, e_layout] # layout entries data
- [0x1420, data]
- [0x1FC8, .data, tile_data] # tile data
- [0x2BC8, .data, tile_data] # tile definitions
- [0x6FE8, .data, sprites]
- [0x8C48, data]
- [0x8C88, rodata]
- [0x8CC8, .rodata, e_red_door]
- [0x8CE0, rodata]

View File

@ -34,8 +34,10 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0x124, layers, rooms]
- [0x124, .data, header] # layers
- [0x1A4, data]
- [0x314, .data, e_laydef] # layout entries header
- [0x4BC, data]
- [0x580, data]
- [0x770, data]
- [0x828, .data, draculacutscene] # "Richter, Dracula"
@ -45,21 +47,19 @@ segments:
- [0x1618, .data, collision]
- [0x1978, .data, e_red_door]
- [0x1990, data]
- [0x2060, roomdef, g_Rooms]
- [0x2060, .data, rooms]
- [0x2084, data]
- [0x26B8, .data, e_layout] # layout entries data
- [0x29D8, data]
#- [0x3D1C, cmp]
- [0x487C, data]
#- [0x17F80, cmp]
- [0x187BC, data]
#- [0x1A40C, cmp]
- [0x1A750, data]
- [0x1B2D0, tilelayout, D_8019B2D0]
- [0x1C6D0, tilelayout, D_8019C6D0]
- [0x1DAD0, tilelayout, D_8019DAD0]
- [0x1E0D0, tilelayout, D_8019E0D0]
- [0x1E2D0, tilelayout, D_8019E2D0]
- [0x1E6D0, tiledef, t_801A26D0, D_801A26D0]
- [0x226E0, data]
- [0x1B2D0, .data, tile_data] # tile data
- [0x1E6D0, .data, tile_data] # tile definitions
- [0x226E0, .data, sprites]
- [0x27984, .rodata, 27D64]
- [0x279E8, .rodata, draculacutscene] # EntityDraculaCutscene
- [0x27A74, .rodata, 2A218] # EntityCutscene

View File

@ -34,8 +34,10 @@ segments:
subalign: 4
subsegments:
- [0x0, .data, header]
- [0xB8, layers, rooms]
- [0x1B8, .data, 6FD0]
- [0xB8, .data, header] # layers
- [0x1B8, .data, d_1b8] # entity gfx
- [0x23C, .data, e_laydef] # layout entries header
- [0x3E4, .data, d_3e4] # entity inits
- [0x528, .data, st_debug]
- [0x5A8, .data, e_breakable]
- [0x608, .data, d_608]
@ -48,34 +50,15 @@ segments:
- [0xF84, .data, e_misc]
- [0xFE8, .data, e_particles]
- [0x1120, .data, e_room_fg]
- [0x11AC, roomdef, g_Rooms]
- [0x1228, layoutobj, D_80181228]
- [0x1250, layoutobj, D_80181250]
- [0x1278, layoutobj, D_80181278]
- [0x12A0, layoutobj, D_801812A0]
- [0x12C8, layoutobj, D_801812C8]
- [0x12F0, layoutobj, D_801812F0]
- [0x1304, layoutobj, D_80181304]
- [0x1324, layoutobj, D_80181324]
- [0x134C, layoutobj, D_8018134C]
- [0x1374, layoutobj, D_80181374]
- [0x139C, layoutobj, D_8018139C]
- [0x13C4, layoutobj, D_801813C4]
- [0x13EC, layoutobj, D_801813EC]
- [0x1400, layoutobj, D_80181400]
- [0x11AC, .data, rooms]
- [0x1228, .data, e_layout] # layout entries data
- [0x1420, cmp, D_80181420]
- [0x1764, cmp, D_80181764]
- [0x1D08, cmp, D_80181D08]
- [0x1D68, tilelayout, D_80181D68]
- [0x1F68, tilelayout, D_80181F68]
- [0x2168, tilelayout, D_80182168]
- [0x2368, tilelayout, D_80182368]
- [0x2568, tilelayout, D_80182568]
- [0x2768, tilelayout, D_80182768]
- [0x2968, tiledef, t_80182D68, D_80182D68]
- [0x2D78, tiledef, t_80186D78, D_80186D78]
- [0x6D88, animset, D_80186D88]
- [0x6E30, .rodata, 6FD0]
- [0x1D68, .data, tile_data] # tile data
- [0x2968, .data, tile_data] # tile definitions
- [0x6D88, .data, sprites]
- [0x6E30, .rodata, warp] # warp strings
- [0x6E70, .rodata, warp] # EntityWarpRoom
- [0x6E90, .rodata, warp] # EntityWarpSmallRocks
- [0x6EA8, .rodata, e_red_door] # EntityRedDoor

View File

@ -1,5 +1,3 @@
g_pStObjLayoutHorizontal = 0x801801EC;
g_pStObjLayoutVertical = 0x801802C0;
PfnEntityUpdates = 0x80180394;
g_InitializeData0 = 0x80180410;
g_InitializeEntityData0 = 0x8018041C;
@ -28,7 +26,6 @@ g_UnkRecursPrim2Inds = 0x801811A4;
g_ESoulStealOrbAngles = 0x801811C8;
g_ESoulStealOrbSprt = 0x801811D8;
g_ESoulStealOrbAnim = 0x80181238;
g_Rooms = 0x801812D4;
D_8018D4F0 = 0x8018D4F0;
D_8018D4F8 = 0x8018D4F8;
D_8018D500 = 0x8018D500;

View File

@ -1,6 +1,4 @@
g_GfxBanks = 0x801801D0;
g_pStObjLayoutHorizontal = 0x80180220;
g_pStObjLayoutVertical = 0x801802F4;
PfnEntityUpdates = 0x801803C8;
g_eBreakableInit = 0x80180458;
g_InitializeData0 = 0x80180464;
@ -35,7 +33,6 @@ g_UnkRecursPrim2Inds = 0x80181368;
g_ESoulStealOrbAngles = 0x8018138C;
g_ESoulStealOrbSprt = 0x8018139C;
g_ESoulStealOrbAnim = 0x801813FC;
g_Rooms = 0x80181498;
EntityUnkId11 = 0x80191A64;
EntityBreakable = 0x80191D00;
EntityBackgroundClouds = 0x80191E34;

View File

@ -1,6 +1,4 @@
g_GfxBanks = 0x8018072C;
g_pStObjLayoutHorizontal = 0x8018077C;
g_pStObjLayoutVertical = 0x80180850;
PfnEntityUpdates = 0x80180924;
g_eBreakableInit = 0x80180AAC;
g_InitializeData0 = 0x80180AB8;
@ -35,7 +33,6 @@ g_UnkRecursPrim2Inds = 0x801826AC;
g_ESoulStealOrbAngles = 0x801826D0;
g_ESoulStealOrbSprt = 0x801826E0;
g_ESoulStealOrbAnim = 0x80182740;
g_Rooms = 0x80183CC4;
EntityCavernDoorVase = 0x801B77D4;
EntityUnkId12 = 0x801B78A8;
EntityBreakable = 0x801B7A64;

View File

@ -1,6 +1,4 @@
g_GfxBanks = 0x801806DC;
g_pStObjLayoutHorizontal = 0x80180728;
g_pStObjLayoutVertical = 0x801807FC;
PfnEntityUpdates = 0x801808D0;
g_eBreakableInit = 0x80180A3C;
g_InitializeData0 = 0x80180A48;
@ -36,7 +34,6 @@ g_ESoulStealOrbAngles = 0x8018205C;
g_ESoulStealOrbSprt = 0x8018206C;
g_ESoulStealOrbAnim = 0x801820CC;
D_801828C8 = 0x801828C8;
g_Rooms = 0x80183A7C;
EntityBreakable = 0x801B26FC;
EntityShuttingWindow = 0x801B2C20;
EntityCastleDoor = 0x801B2F30;

View File

@ -1,7 +1,4 @@
g_Cluts = 0x80180160;
g_EntityGfxs = 0x80180888;
g_pStObjLayoutHorizontal = 0x801808EC;
g_pStObjLayoutVertical = 0x801809C0;
PfnEntityUpdates = 0x80180A94;
g_InitializeData0 = 0x80180BD4;
g_InitializeEntityData0 = 0x80180BE0;
@ -26,7 +23,6 @@ g_UnkRecursPrimVecOrder = 0x80181F40;
g_UnkRecursPrim2Inds = 0x80181F60;
g_eBlueDoorUV = 0x801826B8;
g_eBlueDoorTiles = 0x801826D0;
g_Rooms = 0x8018272C;
EntityBreakable = 0x801B0EEC;
EntityRedEyeBust = 0x801B11C0;
EntityPurpleBrickScrollingBackground = 0x801B12E8;

View File

@ -1,7 +1,4 @@
g_TileLayers = 0x80180168;
g_GfxBanks = 0x801801EC;
g_pStObjLayoutHorizontal = 0x8018023C;
g_pStObjLayoutVertical = 0x80180310;
PfnEntityUpdates = 0x801803E4;
g_InitializeData0 = 0x8018044C;
g_InitializeEntityData0 = 0x80180458;
@ -29,7 +26,6 @@ g_UnkRecursPrim2Inds = 0x8018107C;
g_ESoulStealOrbAngles = 0x801810A0;
g_ESoulStealOrbSprt = 0x801810B0;
g_ESoulStealOrbAnim = 0x80181110;
g_Rooms = 0x801811AC;
EntityBreakable = 0x8018908C;
Random = 0x8018A168;
EntityDamageDisplay = 0x8018B6B4;

View File

@ -1,6 +1,4 @@
g_EntityGfxs = 0x801802C4;
g_pStObjLayoutHorizontal = 0x80180314;
g_pStObjLayoutVertical = 0x801803E8;
PfnEntityUpdates = 0x801804BC;
g_InitializeData0 = 0x80180580;
g_InitializeEntityData0 = 0x8018058C;
@ -27,17 +25,9 @@ g_UnkRecursPrim2Inds = 0x80181F34;
g_ESoulStealOrbAngles = 0x80181F54;
g_ESoulStealOrbSprt = 0x80181F64;
g_ESoulStealOrbAnim = 0x80181FC4;
g_Rooms = 0x80182060;
D_80183D1C = 0x80183D1C;
D_80197F80 = 0x80197F80;
D_8019A40C = 0x8019A40C;
D_801A26E0 = 0x801A26E0;
D_801A4298 = 0x801A4298;
D_801A6A68 = 0x801A6A68;
D_801A6F90 = 0x801A6F90;
D_801A7430 = 0x801A7430;
D_801A7728 = 0x801A7728;
D_801A77CC = 0x801A77CC;
c_HeartPrizes = 0x801A7C84;
EntityLockCamera = 0x801A7EB0;
EntityDraculaCutscene = 0x801A9210;

View File

@ -1,5 +1,7 @@
go 1.19
go 1.22
use ./tools/gfxsotn
use ./tools/sotn-disk
use ./tools/sotn-assets

View File

@ -16,6 +16,19 @@ typedef struct {
/* 0x8 */ u16 params;
} LayoutEntity; // size = 0xA
typedef struct {
u16* layout;
TileDefinition* tileDef;
u32 params;
u16 zPriority;
u8 unkE;
u8 unkF;
} MyLayer;
typedef struct {
MyLayer* fg;
MyLayer* bg;
} MyRoomDef;
#if defined(VERSION_PSP)
// A horizontally ordered array with head and tail sigils in the 1st field
extern LayoutEntity** g_pStObjLayoutHorizontal;

View File

@ -4,6 +4,7 @@
#include <string.h>
#include "stage_loader.h"
#include "sfx.h"
#include "../../st/wrp/wrp.h"
void Update(void);
void HitDetection(void);
@ -11,35 +12,6 @@ void UpdateRoomPosition(void);
void UpdateStageEntities(void);
static void MyInitRoomEntities(s32 objLayoutId);
// TODO populate from assets/st/wrp/D_80186D88.animset.json
static SpriteParts* D_80186D88[] = {NULL, NULL, NULL, NULL};
static u_long* sprite_banks[] = {
/* 0x040 */ 0x00000000,
/* 0x044 */ D_80186D88,
/* 0x048 */ 0x00000000,
/* 0x04C */ 0x00000000,
/* 0x050 */ 0x00000000,
/* 0x054 */ 0x00000000,
/* 0x058 */ 0x00000000,
/* 0x05C */ 0x00000000,
/* 0x060 */ 0x00000000,
/* 0x064 */ 0x00000000,
/* 0x068 */ 0x00000000,
/* 0x06C */ 0x00000000,
/* 0x070 */ 0x00000000,
/* 0x074 */ 0x00000000,
/* 0x078 */ 0x00000000,
/* 0x07C */ 0x00000000,
/* 0x080 */ 0x00000000,
/* 0x084 */ 0x00000000,
/* 0x088 */ 0x00000000,
/* 0x08C */ 0x00000000,
/* 0x090 */ 0x00000000,
/* 0x094 */ 0x00000000,
/* 0x098 */ 0x00000000,
/* 0x09C */ 0x00000000,
};
u32 D_80181420[2048];
u32 D_80181764[2048];
@ -55,17 +27,19 @@ static void* clut_anims[] = {
};
extern void* WRP_g_EntityGfxs[];
extern RoomHeader OVL_EXPORT(rooms)[];
#include "../../st/wrp/sprite_banks.h" // TODO OVL_EXPORT
#include "../../st/wrp/layers.h" // TODO OVL_EXPORT
static Overlay g_StageDesc = {
Update,
HitDetection,
UpdateRoomPosition,
MyInitRoomEntities,
NULL, // set in InitStageWrp
sprite_banks,
OVL_EXPORT(rooms),
spriteBanks,
clut_anims,
NULL,
NULL, // set in InitStageWrp
rooms_layers,
WRP_g_EntityGfxs,
UpdateStageEntities,
NULL,
@ -75,22 +49,6 @@ static Overlay g_StageDesc = {
NULL,
};
LayoutEntity* D_80181228;
LayoutEntity* D_80181250;
LayoutEntity* D_801812A0;
LayoutEntity* D_801812C8;
LayoutEntity* D_80181278;
LayoutEntity* D_801812F0;
LayoutEntity* D_80181304;
LayoutEntity* D_80181324;
LayoutEntity* D_8018134C;
LayoutEntity* D_80181374;
LayoutEntity* D_8018139C;
LayoutEntity* D_801813C4;
LayoutEntity* D_801813EC;
LayoutEntity* D_80181400;
void InitStageWrp(Overlay* o) {
LoadReset();
@ -121,136 +79,8 @@ void InitStageWrp(Overlay* o) {
fclose(f);
}
D_80181228 = LoadObjLayout("assets/st/wrp/D_80181228.layoutobj.json");
D_80181250 = LoadObjLayout("assets/st/wrp/D_80181250.layoutobj.json");
D_801812A0 = LoadObjLayout("assets/st/wrp/D_801812A0.layoutobj.json");
D_801812C8 = LoadObjLayout("assets/st/wrp/D_801812C8.layoutobj.json");
D_80181278 = LoadObjLayout("assets/st/wrp/D_80181278.layoutobj.json");
D_801812F0 = LoadObjLayout("assets/st/wrp/D_801812F0.layoutobj.json");
D_80181304 = LoadObjLayout("assets/st/wrp/D_80181304.layoutobj.json");
//
D_80181324 = LoadObjLayout("assets/st/wrp/D_80181324.layoutobj.json");
D_8018134C = LoadObjLayout("assets/st/wrp/D_8018134C.layoutobj.json");
D_80181374 = LoadObjLayout("assets/st/wrp/D_80181374.layoutobj.json");
D_8018139C = LoadObjLayout("assets/st/wrp/D_8018139C.layoutobj.json");
D_801813C4 = LoadObjLayout("assets/st/wrp/D_801813C4.layoutobj.json");
D_801813EC = LoadObjLayout("assets/st/wrp/D_801813EC.layoutobj.json");
D_80181400 = LoadObjLayout("assets/st/wrp/D_80181400.layoutobj.json");
int layoutId = 0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_80181228;
g_pStObjLayoutHorizontal[layoutId++] = D_80181250;
g_pStObjLayoutHorizontal[layoutId++] = D_801812A0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812C8;
g_pStObjLayoutHorizontal[layoutId++] = D_80181278;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_801812F0;
g_pStObjLayoutHorizontal[layoutId++] = D_80181304;
g_pStObjLayoutHorizontal[layoutId++] = D_80181304;
g_pStObjLayoutHorizontal[layoutId++] = D_80181304;
g_pStObjLayoutHorizontal[layoutId++] = D_80181304;
g_pStObjLayoutHorizontal[layoutId++] = D_80181304;
layoutId = 0;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_80181324;
g_pStObjLayoutVertical[layoutId++] = D_8018134C;
g_pStObjLayoutVertical[layoutId++] = D_8018139C;
g_pStObjLayoutVertical[layoutId++] = D_801813C4;
g_pStObjLayoutVertical[layoutId++] = D_80181374;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_801813EC;
g_pStObjLayoutVertical[layoutId++] = D_80181400;
g_pStObjLayoutVertical[layoutId++] = D_80181400;
g_pStObjLayoutVertical[layoutId++] = D_80181400;
g_pStObjLayoutVertical[layoutId++] = D_80181400;
g_pStObjLayoutVertical[layoutId++] = D_80181400;
sprite_banks[1] = LoadSpriteParts("assets/st/wrp/D_80186D88.animset.json");
g_StageDesc.tileLayers = LoadRoomsLayers("assets/st/wrp/rooms.layers.json");
g_StageDesc.rooms = LoadRoomDefs("assets/st/wrp/g_Rooms.roomdef.json");
memcpy(o, &g_StageDesc, sizeof(Overlay));
}

8
src/st/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
rooms.c
sprites.c
e_laydef.c
e_layout.c
layers.h
sprite_banks.h
tilemap_*.h
tiledef_*.h

View File

@ -4,6 +4,8 @@
#include "common.h"
#include "stage.h"
#define OVL_EXPORT(x) CEN_##x
#define CASTLE_FLAG_BANK 0x00
// CEN Sound IDs

View File

@ -1,40 +1,27 @@
#include "cen.h"
extern RoomHeader g_Rooms[];
extern SpriteParts** SpriteBanks[];
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern RoomDef g_TileLayers[];
extern MyRoomDef rooms_layers[];
extern GfxBank* D_8018019C[];
void UpdateStageEntities();
static Overlay StageOverlay = {
Overlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = g_Rooms,
.spriteBanks = SpriteBanks,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = Cluts,
.objLayoutHorizontal = NULL,
.tileLayers = g_TileLayers,
.tileLayers = rooms_layers,
.gfxBanks = D_8018019C,
.UpdateStageEntities = UpdateStageEntities,
.unk2c = NULL,
.unk30 = NULL,
.unk34 = NULL,
.unk38 = NULL,
.StageEndCutScene = NULL,
};
extern SpriteParts* D_8018C754[];
extern SpriteParts* D_8018C60C[];
extern SpriteParts* D_8018CED8[];
static SpriteParts** SpriteBanks[] = {
NULL, D_8018C754, D_8018C60C, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, D_8018CED8, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
};
#include "sprite_banks.h"
extern u16* D_8018658C[0x80];
extern u16* D_8018678C[0x80];
@ -50,3 +37,5 @@ static u16** D_801800A0[] = {
static void* Cluts[] = {
D_801800A0,
};
#include "layers.h"

7
src/st/cen/tile_data.c Normal file
View File

@ -0,0 +1,7 @@
#include <stage.h>
#include "tilemap_069EC.h"
#include "tilemap_06DEC.h"
#include "tilemap_07FEC.h"
#include "tiledef_085EC.h"
#include "tiledef_0C5FC.h"

View File

@ -1,6 +1,8 @@
#include "stage.h"
#define STAGE_DRE_H
#define OVL_EXPORT(x) DRE_##x
#define CASTLE_FLAG_BANK 0xD3
typedef enum {

View File

@ -1,38 +1,27 @@
#include "dre.h"
extern RoomHeader g_Rooms[];
extern SpriteParts** SpriteBanks[];
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern RoomDef g_TileLayers[];
extern MyRoomDef rooms_layers[];
extern GfxBank* g_GfxBanks[];
void UpdateStageEntities();
static Overlay StageOverlay = {
Overlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = g_Rooms,
.spriteBanks = SpriteBanks,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = Cluts,
.objLayoutHorizontal = NULL,
.tileLayers = g_TileLayers,
.tileLayers = rooms_layers,
.gfxBanks = g_GfxBanks,
.UpdateStageEntities = UpdateStageEntities,
.unk2c = NULL,
.unk30 = NULL,
.unk34 = NULL,
.unk38 = NULL,
.StageEndCutScene = NULL,
};
extern SpriteParts* D_80190168[];
static SpriteParts** SpriteBanks[] = {
NULL, D_80190168, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
};
#include "sprite_banks.h"
extern u16* D_8018AEA8[0x50];
extern u16* D_8018AF48[0x80];
@ -53,3 +42,5 @@ static u16** Clut[] = {
static void* Cluts[] = {
Clut,
};
#include "layers.h"

6
src/st/dre/tile_data.c Normal file
View File

@ -0,0 +1,6 @@
#include <stage.h>
#include "tilemap_0B548.h"
#include "tilemap_0B948.h"
#include "tiledef_0C148.h"
#include "tiledef_10158.h"

View File

@ -8,7 +8,7 @@ extern GfxBank* g_pStTileset[];
void UpdateStageEntities();
void func_8018E1D4();
static Overlay StageOverlay = {
static Overlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,

View File

@ -10,6 +10,8 @@
#include "stage.h"
#define OVL_EXPORT(x) MAD_##x
typedef enum {
E_NONE,
E_BREAKABLE,

View File

@ -1,44 +1,27 @@
#include "no3.h"
extern RoomHeader g_Rooms[];
extern SpriteParts** SpriteBanks[];
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern RoomDef g_TileLayers[];
extern MyRoomDef rooms_layers[];
extern GfxBank* g_GfxBanks[];
void UpdateStageEntities();
static AbbreviatedOverlay StageOverlay = {
AbbreviatedOverlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = g_Rooms,
.spriteBanks = SpriteBanks,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = Cluts,
.objLayoutHorizontal = g_pStObjLayoutHorizontal,
.tileLayers = g_TileLayers,
.tileLayers = rooms_layers,
.gfxBanks = g_GfxBanks,
.UpdateStageEntities = UpdateStageEntities,
};
extern SpriteParts* D_801AEA68[];
extern SpriteParts* D_801B009C[];
extern SpriteParts* D_801AF754[];
extern SpriteParts* D_801AFB3C[];
extern SpriteParts* D_801B077C[];
extern SpriteParts* D_801B0B80[];
extern SpriteParts* D_801B57C0[];
extern SpriteParts* D_801B5904[];
extern SpriteParts* D_801B721C[];
static SpriteParts** SpriteBanks[] = {
NULL, D_801AEA68, D_801B009C, D_801AF754, D_801AFB3C, D_801B077C,
D_801B0B80, D_801B57C0, D_801B5904, D_801B721C, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
};
extern u16* table[];
#include "sprite_banks.h"
extern u16* D_80198578[0x100];
extern u16* D_801966B8[0x8C0];
@ -100,3 +83,5 @@ static u16** Clut[] = {
static void* Cluts[] = {
Clut,
};
#include "layers.h"

View File

@ -1,5 +1,7 @@
#include "stage.h"
#define OVL_EXPORT(x) NO3_##x
#define CASTLE_FLAG_BANK 0x34
// NO3 Sound IDs

48
src/st/no3/tile_data.c Normal file
View File

@ -0,0 +1,48 @@
#include <stage.h>
#include "tilemap_18838.h"
#include "tilemap_19438.h"
#include "tilemap_19638.h"
#include "tilemap_19A38.h"
#include "tilemap_19E38.h"
#include "tilemap_1AC38.h"
#include "tilemap_1AE38.h"
#include "tilemap_1B038.h"
#include "tilemap_1B238.h"
#include "tilemap_1B638.h"
#include "tilemap_1BA38.h"
#include "tilemap_1C238.h"
#include "tilemap_1CA38.h"
#include "tilemap_1CC38.h"
#include "tilemap_1CE38.h"
#include "tilemap_1DA38.h"
#include "tilemap_1E638.h"
#include "tilemap_1E838.h"
#include "tilemap_1EA38.h"
#include "tilemap_1F638.h"
#include "tilemap_1F838.h"
#include "tilemap_1FA38.h"
#include "tilemap_1FC38.h"
#include "tilemap_20038.h"
#include "tilemap_20238.h"
#include "tilemap_20838.h"
#include "tilemap_20E38.h"
#include "tilemap_21038.h"
#include "tilemap_21238.h"
#include "tilemap_21438.h"
#include "tilemap_21638.h"
#include "tilemap_22238.h"
#include "tilemap_22E38.h"
#include "tilemap_23038.h"
#include "tilemap_23238.h"
#include "tilemap_23438.h"
#include "tilemap_23638.h"
#include "tilemap_25A38.h"
#include "tilemap_25C38.h"
#include "tilemap_25E38.h"
#include "tilemap_26038.h"
#include "tilemap_26238.h"
#include "tilemap_26438.h"
#include "tiledef_26A38.h"
#include "tiledef_2AA48.h"
#include "tiledef_2EA58.h"

View File

@ -1,45 +1,27 @@
#include "np3.h"
extern RoomHeader g_Rooms[];
extern SpriteParts** SpriteBanks[];
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern RoomDef g_TileLayers[];
extern MyRoomDef rooms_layers[];
extern GfxBank* g_GfxBanks[];
void UpdateStageEntities();
static AbbreviatedOverlay StageOverlay = {
static AbbreviatedOverlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = g_Rooms,
.spriteBanks = SpriteBanks,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = Cluts,
.objLayoutHorizontal = g_pStObjLayoutHorizontal,
.tileLayers = g_TileLayers,
.tileLayers = rooms_layers,
.gfxBanks = g_GfxBanks,
.UpdateStageEntities = UpdateStageEntities,
};
extern SpriteParts* D_801AA124[];
extern SpriteParts* D_801AB758[];
extern SpriteParts* D_801AAE10[];
extern SpriteParts* D_801AB1F8[];
extern SpriteParts* D_801ABE38[];
extern SpriteParts* D_801AED34[];
extern SpriteParts* D_801AC23C[];
extern SpriteParts* D_801AD278[];
extern SpriteParts* D_801AC380[];
extern SpriteParts* D_801AEEF8[];
extern SpriteParts* D_801B08B0[];
extern SpriteParts* D_801B1B30[];
static SpriteParts** SpriteBanks[] = {
NULL, D_801AA124, D_801AB758, D_801AAE10, D_801AB1F8, D_801ABE38,
D_801AED34, D_801AC23C, D_801AD278, D_801AC380, D_801AEEF8, D_801B08B0,
D_801B1B30, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
};
#include "sprite_banks.h"
extern u16* D_801963B4[0x100];
extern u16* D_80194914[0x8C0];
@ -88,3 +70,5 @@ static u16** Clut[] = {
static void* Cluts[] = {
Clut,
};
#include "layers.h"

View File

@ -1,5 +1,7 @@
#include "stage.h"
#define OVL_EXPORT(x) NP3_##x
#define CASTLE_FLAG_BANK 0x34
typedef enum {

44
src/st/np3/tile_data.c Normal file
View File

@ -0,0 +1,44 @@
#include <stage.h>
#include "tilemap_168F4.h"
#include "tilemap_174F4.h"
#include "tilemap_176F4.h"
#include "tilemap_17AF4.h"
#include "tilemap_17EF4.h"
#include "tilemap_18CF4.h"
#include "tilemap_18EF4.h"
#include "tilemap_190F4.h"
#include "tilemap_192F4.h"
#include "tilemap_196F4.h"
#include "tilemap_19AF4.h"
#include "tilemap_1A2F4.h"
#include "tilemap_1AAF4.h"
#include "tilemap_1ACF4.h"
#include "tilemap_1AEF4.h"
#include "tilemap_1BAF4.h"
#include "tilemap_1C6F4.h"
#include "tilemap_1C8F4.h"
#include "tilemap_1CAF4.h"
#include "tilemap_1D6F4.h"
#include "tilemap_1D8F4.h"
#include "tilemap_1DAF4.h"
#include "tilemap_1DCF4.h"
#include "tilemap_1E0F4.h"
#include "tilemap_1E2F4.h"
#include "tilemap_1E8F4.h"
#include "tilemap_1EEF4.h"
#include "tilemap_1F0F4.h"
#include "tilemap_1F2F4.h"
#include "tilemap_1F4F4.h"
#include "tilemap_1F6F4.h"
#include "tilemap_202F4.h"
#include "tilemap_20EF4.h"
#include "tilemap_210F4.h"
#include "tilemap_212F4.h"
#include "tilemap_214F4.h"
#include "tilemap_216F4.h"
#include "tilemap_218F4.h"
#include "tilemap_21AF4.h"
#include "tiledef_220F4.h"
#include "tiledef_26104.h"
#include "tiledef_2A114.h"

View File

@ -50,6 +50,7 @@ void func_801C3F9C(AxePrim* prim) {
}
// Called by EntityAxeKnight
extern s16* sprites_nz0_3[];
s32 func_801C4198(Entity* axeKnight) {
Primitive* prim;
s32 primIndex;
@ -63,7 +64,7 @@ s32 func_801C4198(Entity* axeKnight) {
switch (axeKnight->step_s) {
case 0:
clutBase = D_80180C6A;
dataPtr = D_801A79E4[axeKnight->animCurFrame];
dataPtr = sprites_nz0_3[axeKnight->animCurFrame];
primIndex = g_api.AllocPrimitives(PRIM_GT4, *dataPtr * 2);
if (primIndex != -1) {
axeKnight->flags |= FLAG_HAS_PRIMS;

View File

@ -1,35 +0,0 @@
#include "nz0.h"
void Update(void);
void HitDetection(void);
void UpdateRoomPosition(void);
void InitRoomEntities(s32 objLayoutId);
extern RoomHeader g_Rooms[];
extern s16** g_SpriteBanks[];
extern void* g_Cluts[];
extern RoomDef g_TileLayers[];
extern void* g_EntityGfxs[];
void UpdateStageEntities(void);
extern u8** D_801A6E8C;
extern s32* D_801A6FCC;
extern s32* D_801A7304;
Overlay g_StageOverlay = {
/* 0x00 */ Update,
/* 0x04 */ HitDetection,
/* 0x08 */ UpdateRoomPosition,
/* 0x0C */ InitRoomEntities,
/* 0x10 */ g_Rooms,
/* 0x14 */ 0x8018002C,
/* 0x18 */ g_Cluts,
/* 0x1C */ g_pStObjLayoutHorizontal,
/* 0x20 */ g_TileLayers,
/* 0x24 */ g_EntityGfxs,
/* 0x28 */ UpdateStageEntities,
/* 0x2C */ NULL,
/* 0x30 */ &D_801A6E8C,
/* 0x34 */ &D_801A6FCC,
/* 0x38 */ &D_801A7304,
/* 0x3C */ 0x801A79E4,
};

64
src/st/nz0/header.c Normal file
View File

@ -0,0 +1,64 @@
#include "nz0.h"
extern signed short* spriteBanks[];
extern void* g_Cluts[];
extern void* g_EntityGfxs[];
extern MyRoomDef rooms_layers[];
extern RoomHeader OVL_EXPORT(rooms)[];
void Update();
void HitDetection();
void UpdateRoomPosition();
void InitRoomEntities();
void UpdateStageEntities();
AbbreviatedOverlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = g_Cluts,
.objLayoutHorizontal = g_pStObjLayoutHorizontal,
.tileLayers = rooms_layers,
.gfxBanks = g_EntityGfxs,
.UpdateStageEntities = UpdateStageEntities,
};
#include "sprite_banks.h"
extern u_long* D_80195C3C;
extern u_long* D_80195CDC;
extern u_long* D_80195D3C;
extern u_long* D_80195DBC;
extern u_long* D_80195E1C;
extern u_long* D_80195E3C;
extern u_long* D_80195E9C;
extern u_long* D_80195F1C;
extern u_long* D_80195F9C;
extern u_long* D_8019601C;
extern u_long* D_801962DC;
extern u_long* D_8019641C;
extern u_long* D_8019647C;
extern u_long* D_8019657C;
extern u_long* D_8019663C;
extern u_long* D_8019665C;
extern u_long* D_8019685C;
static u_long* D_8018008C[] = {
0x00000005, 0x00002000, 0x00000040, &D_80195C3C, 0x00002040, 0x00000010,
&D_80195CDC, 0x00002050, 0x00000040, &D_80195D3C, 0x00002090, 0x00000030,
&D_80195DBC, 0x000020C0, 0x00000010, &D_80195E1C, 0x000020D0, 0x00000030,
&D_80195E3C, 0x00002110, 0x00000040, &D_80195F1C, 0x00002150, 0x00000040,
&D_80195F9C, 0x00002190, 0x00000040, &D_80195E9C, 0x000021D0, 0x000000A0,
&D_801962DC, 0x00002270, 0x00000030, &D_8019641C, 0x000022A0, 0x00000080,
&D_8019647C, 0x00002320, 0x00000050, &D_8019657C, 0x00002370, 0x00000010,
&D_8019663C, 0x00002380, 0x00000080, &D_8019665C, 0x00002400, 0x00000080,
&D_8019685C, 0x00002E00, 0x00000100, &D_8019601C, 0xFFFFFFFF,
};
static void* g_Cluts[] = {
&D_8018008C,
};
#include "layers.h"

View File

@ -1,5 +1,7 @@
#include "stage.h"
#define OVL_EXPORT(x) NZ0_##x
#define CASTLE_FLAG_BANK 0x7E
typedef enum {
@ -402,7 +404,6 @@ extern u8 D_80181538[];
// for func_801C4198
extern u16 D_80180C6A;
extern s16* D_801A79E4[];
// for EntityMagicallySealedDoor
extern u8 g_eBlueDoorUV[3][8];

37
src/st/nz0/tile_data.c Normal file
View File

@ -0,0 +1,37 @@
#include <stage.h>
#include "tilemap_16A5C.h"
#include "tilemap_16C5C.h"
#include "tilemap_16E5C.h"
#include "tilemap_1725C.h"
#include "tilemap_17A5C.h"
#include "tilemap_17C5C.h"
#include "tilemap_17E5C.h"
#include "tilemap_1805C.h"
#include "tilemap_1845C.h"
#include "tilemap_1865C.h"
#include "tilemap_1885C.h"
#include "tilemap_18A5C.h"
#include "tilemap_1905C.h"
#include "tilemap_1925C.h"
#include "tilemap_19C5C.h"
#include "tilemap_1A05C.h"
#include "tilemap_1A45C.h"
#include "tilemap_1AA5C.h"
#include "tilemap_1AE5C.h"
#include "tilemap_1BE5C.h"
#include "tilemap_1C65C.h"
#include "tilemap_1C85C.h"
#include "tilemap_1D45C.h"
#include "tilemap_1D65C.h"
#include "tilemap_1E25C.h"
#include "tilemap_1EE5C.h"
#include "tilemap_1F25C.h"
#include "tilemap_2005C.h"
#include "tilemap_2025C.h"
#include "tilemap_2045C.h"
#include "tilemap_2065C.h"
#include "tilemap_2085C.h"
#include "tiledef_20E5C.h"
#include "tiledef_24E6C.h"
#include "tiledef_26E7C.h"

View File

@ -192,7 +192,26 @@ u8 func_80191CC8(s32 arg0) {
INCLUDE_ASM("st/rwrp/nonmatchings/113A0", func_80192348);
INCLUDE_ASM("st/rwrp/nonmatchings/113A0", func_80192414);
extern PfnEntityUpdate PfnEntityUpdates[];
void func_80192414(u16 entityId, Entity* src, Entity* dst) {
DestroyEntity(dst);
dst->entityId = entityId;
dst->pfnUpdate = PfnEntityUpdates[entityId - 1];
dst->posX.i.hi = src->posX.i.hi;
dst->posY.i.hi = src->posY.i.hi;
dst->unk5A = src->unk5A;
dst->zPriority = src->zPriority;
dst->animSet = src->animSet;
dst->flags = FLAG_UNK_2000 | FLAG_UNK_01000000 | FLAG_UNK_04000000 |
FLAG_UNK_08000000 | FLAG_DESTROY_IF_BARELY_OUT_OF_CAMERA |
FLAG_DESTROY_IF_OUT_OF_CAMERA;
if (src->palette & PAL_OVL_FLAG) {
dst->palette = src->hitEffect;
} else {
dst->palette = src->palette;
}
}
void func_801924DC(void) {
Entity* entity;

View File

@ -1,40 +1,27 @@
#include "rwrp.h"
extern RoomHeader g_Rooms[];
extern SpriteParts** SpriteBanks[];
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern RoomDef g_TileLayers[];
extern MyRoomDef rooms_layers[];
extern GfxBank* g_GfxBanks[];
void UpdateStageEntities();
static Overlay StageOverlay = {
static Overlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = g_Rooms,
.spriteBanks = SpriteBanks,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = Cluts,
.objLayoutHorizontal = NULL,
.tileLayers = g_TileLayers,
.tileLayers = rooms_layers,
.gfxBanks = g_GfxBanks,
.UpdateStageEntities = UpdateStageEntities,
.unk2c = NULL,
.unk30 = NULL,
.unk34 = NULL,
.unk38 = NULL,
.StageEndCutScene = NULL,
};
extern SpriteParts* D_80186FE8[];
extern SpriteParts* D_80187090[];
static SpriteParts** SpriteBanks[] = {
NULL, D_80186FE8, D_80187090, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
};
#include "sprite_banks.h"
extern u16* D_80181D08[0x100];
@ -47,3 +34,5 @@ static u16** Clut[] = {
static void* Cluts[] = {
Clut,
};
#include "layers.h"

View File

@ -4,6 +4,8 @@
#include "common.h"
#include "stage.h"
#define OVL_EXPORT(x) RWRP_##x
#define CASTLE_FLAG_BANK 0x00
// RWRP Sound IDs

10
src/st/rwrp/tile_data.c Normal file
View File

@ -0,0 +1,10 @@
#include <stage.h>
#include "tilemap_01FC8.h"
#include "tilemap_021C8.h"
#include "tilemap_023C8.h"
#include "tilemap_025C8.h"
#include "tilemap_027C8.h"
#include "tilemap_029C8.h"
#include "tiledef_02FC8.h"
#include "tiledef_06FD8.h"

View File

@ -1,46 +1,29 @@
#include "st0.h"
extern RoomHeader g_Rooms[];
extern SpriteParts** SpriteBanks[];
extern RoomHeader OVL_EXPORT(rooms)[];
extern signed short* spriteBanks[];
extern void* Cluts[];
extern RoomDef g_TileLayers[];
extern MyRoomDef rooms_layers[];
extern GfxBank* g_EntityGfxs[];
void UpdateStageEntities();
void PrologueScroll();
static Overlay StageOverlay = {
static Overlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = g_Rooms,
.spriteBanks = SpriteBanks,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = Cluts,
.objLayoutHorizontal = g_pStObjLayoutHorizontal,
.tileLayers = g_TileLayers,
.tileLayers = rooms_layers,
.gfxBanks = g_EntityGfxs,
.UpdateStageEntities = UpdateStageEntities,
.unk2c = NULL,
.unk30 = NULL,
.unk34 = NULL,
.unk38 = NULL,
.StageEndCutScene = PrologueScroll,
};
extern SpriteParts* D_801A26E0[];
extern SpriteParts* D_801A4298[];
extern SpriteParts* D_801A6A68[];
extern SpriteParts* D_801A6F90[];
extern SpriteParts* D_801A7430[];
extern SpriteParts* D_801A7728[];
extern SpriteParts* D_801A77CC[];
static SpriteParts** SpriteBanks[] = {
NULL, D_801A26E0, D_801A4298, D_801A6A68, D_801A6F90, D_801A7430,
D_801A7728, D_801A77CC, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
};
#include "sprite_banks.h"
extern u16* D_8019A750[0x70];
extern u16* D_8019AD30[0x20];
@ -64,3 +47,5 @@ static u16** Clut[] = {
static void* Cluts[] = {
Clut,
};
#include "layers.h"

View File

@ -3,6 +3,8 @@
#undef STAGE
#define STAGE STAGE_ST0
#define OVL_EXPORT(x) ST0_##x
typedef enum {
E_NONE,
E_BREAKABLE,

8
src/st/st0/tile_data.c Normal file
View File

@ -0,0 +1,8 @@
#include <stage.h>
#include "tilemap_1B2D0.h"
#include "tilemap_1C6D0.h"
#include "tilemap_1DAD0.h"
#include "tilemap_1E0D0.h"
#include "tilemap_1E2D0.h"
#include "tiledef_226D0.h"

View File

@ -1,269 +0,0 @@
/*
* File: 6FD0.c
* Overlay: WRP
* Description: All warp rooms.
*/
#include "wrp.h"
#include "../st_private.h"
// *** Overlay exports start ***
void CreateEntityWhenInHorizontalRange(LayoutEntity*);
void func_8018A520(s16);
void func_8018CAB0(void);
void func_801916C4(u16);
void BottomCornerText(u8*, u8);
extern u32 D_80181420[];
extern u32 D_80181764[];
static void* D_801801B8[] = {
/* 0x1B8 */ (void*)0x00000000,
/* 0x1BC */ (void*)0x00000000,
/* 0x1C0 */ (void*)0x00000000,
/* 0x1C4 */ (void*)0x00000000,
/* 0x1C8 */ (void*)0xFFFFFFFF,
};
static void* D_801801CC[] = {
/* 0x1CC */ (void*)0x00000004,
/* 0x1D0 */ (void*)0x00400100,
/* 0x1D4 */ (void*)0x00800080,
/* 0x1D8 */ (void*)D_80181420,
/* 0x1DC */ (void*)0x00600100,
/* 0x1E0 */ (void*)0x00800080,
/* 0x1E4 */ (void*)D_80181764,
/* 0x1E8 */ (void*)0xFFFFFFFF,
};
void* OVL_EXPORT(g_EntityGfxs)[] = {
/* 0x1EC */ D_801801B8,
/* 0x1F0 */ D_801801B8,
/* 0x1F4 */ D_801801B8,
/* 0x1F8 */ D_801801B8,
/* 0x1FC */ D_801801B8,
/* 0x200 */ D_801801CC,
/* 0x204 */ D_801801B8,
/* 0x208 */ D_801801B8,
/* 0x20C */ D_801801B8,
/* 0x210 */ D_801801B8,
/* 0x214 */ D_801801B8,
/* 0x218 */ D_801801B8,
/* 0x21C */ D_801801B8,
/* 0x220 */ D_801801B8,
/* 0x224 */ D_801801B8,
/* 0x228 */ D_801801B8,
/* 0x22C */ D_801801B8,
/* 0x230 */ D_801801B8,
/* 0x234 */ D_801801B8,
/* 0x238 */ D_801801B8,
};
// *** Layout definition end ***
// *** Layout entity definition start ***
extern LayoutEntity D_80181228[];
extern LayoutEntity D_80181250[];
extern LayoutEntity D_801812A0[];
extern LayoutEntity D_801812C8[];
extern LayoutEntity D_80181278[];
extern LayoutEntity D_801812F0[];
extern LayoutEntity D_80181304[];
LayoutEntity* g_pStObjLayoutHorizontal[] = {
/* 0x23C */ D_801812F0,
/* 0x240 */ D_80181228,
/* 0x244 */ D_80181250,
/* 0x248 */ D_801812A0,
/* 0x24C */ D_801812C8,
/* 0x250 */ D_80181278,
/* 0x254 */ D_801812F0,
/* 0x258 */ D_801812F0,
/* 0x25C */ D_801812F0,
/* 0x260 */ D_801812F0,
/* 0x264 */ D_801812F0,
/* 0x268 */ D_801812F0,
/* 0x26C */ D_801812F0,
/* 0x270 */ D_801812F0,
/* 0x274 */ D_801812F0,
/* 0x278 */ D_801812F0,
/* 0x27C */ D_801812F0,
/* 0x280 */ D_801812F0,
/* 0x284 */ D_801812F0,
/* 0x288 */ D_801812F0,
/* 0x28C */ D_801812F0,
/* 0x290 */ D_801812F0,
/* 0x294 */ D_801812F0,
/* 0x298 */ D_801812F0,
/* 0x29C */ D_801812F0,
/* 0x2A0 */ D_801812F0,
/* 0x2A4 */ D_801812F0,
/* 0x2A8 */ D_801812F0,
/* 0x2AC */ D_801812F0,
/* 0x2B0 */ D_801812F0,
/* 0x2B4 */ D_801812F0,
/* 0x2B8 */ D_801812F0,
/* 0x2BC */ D_801812F0,
/* 0x2C0 */ D_801812F0,
/* 0x2C4 */ D_801812F0,
/* 0x2C8 */ D_801812F0,
/* 0x2CC */ D_801812F0,
/* 0x2D0 */ D_801812F0,
/* 0x2D4 */ D_801812F0,
/* 0x2D8 */ D_801812F0,
/* 0x2DC */ D_801812F0,
/* 0x2E0 */ D_801812F0,
/* 0x2E4 */ D_801812F0,
/* 0x2E8 */ D_801812F0,
/* 0x2EC */ D_801812F0,
/* 0x2F0 */ D_801812F0,
/* 0x2F4 */ D_801812F0,
/* 0x2F8 */ D_801812F0,
/* 0x2FC */ D_80181304,
/* 0x300 */ D_80181304,
/* 0x304 */ D_80181304,
/* 0x308 */ D_80181304,
/* 0x30C */ D_80181304,
};
extern LayoutEntity D_80181324[];
extern LayoutEntity D_8018134C[];
extern LayoutEntity D_80181374[];
extern LayoutEntity D_8018139C[];
extern LayoutEntity D_801813C4[];
extern LayoutEntity D_801813EC[];
extern LayoutEntity D_80181400[];
LayoutEntity* g_pStObjLayoutVertical[] = {
/* 310 */ D_801813EC,
/* 314 */ D_80181324,
/* 318 */ D_8018134C,
/* 31C */ D_8018139C,
/* 320 */ D_801813C4,
/* 324 */ D_80181374,
/* 328 */ D_801813EC,
/* 32C */ D_801813EC,
/* 330 */ D_801813EC,
/* 334 */ D_801813EC,
/* 338 */ D_801813EC,
/* 33C */ D_801813EC,
/* 340 */ D_801813EC,
/* 344 */ D_801813EC,
/* 348 */ D_801813EC,
/* 34C */ D_801813EC,
/* 350 */ D_801813EC,
/* 354 */ D_801813EC,
/* 358 */ D_801813EC,
/* 35C */ D_801813EC,
/* 360 */ D_801813EC,
/* 364 */ D_801813EC,
/* 368 */ D_801813EC,
/* 36C */ D_801813EC,
/* 370 */ D_801813EC,
/* 374 */ D_801813EC,
/* 378 */ D_801813EC,
/* 37C */ D_801813EC,
/* 380 */ D_801813EC,
/* 384 */ D_801813EC,
/* 388 */ D_801813EC,
/* 38C */ D_801813EC,
/* 390 */ D_801813EC,
/* 394 */ D_801813EC,
/* 398 */ D_801813EC,
/* 39C */ D_801813EC,
/* 3A0 */ D_801813EC,
/* 3A4 */ D_801813EC,
/* 3A8 */ D_801813EC,
/* 3AC */ D_801813EC,
/* 3B0 */ D_801813EC,
/* 3B4 */ D_801813EC,
/* 3B8 */ D_801813EC,
/* 3BC */ D_801813EC,
/* 3C0 */ D_801813EC,
/* 3C4 */ D_801813EC,
/* 3C8 */ D_801813EC,
/* 3CC */ D_801813EC,
/* 3D0 */ D_80181400,
/* 3D4 */ D_80181400,
/* 3D8 */ D_80181400,
/* 3DC */ D_80181400,
/* 3E0 */ D_80181400,
};
// *** Layout entity definition end ***
// *** entity definition start ***
void func_80186FD0(Entity*);
void func_801870B0(Entity*);
void EntityUnkId13(Entity*);
void EntityUnkId14(Entity*);
void EntityUnkId15(Entity*);
void EntityWarpRoom(Entity*);
void EntityWarpSmallRocks(Entity*);
void EntityPrizeDrop(Entity*);
PfnEntityUpdate PfnEntityUpdates[] = {
/* 3E4 */ (PfnEntityUpdate)EntityBreakable,
/* 3E8 */ (PfnEntityUpdate)EntityExplosion,
/* 3EC */ (PfnEntityUpdate)EntityPrizeDrop,
/* 3F0 */ (PfnEntityUpdate)EntityDamageDisplay,
/* 3F4 */ (PfnEntityUpdate)EntityRedDoor,
/* 3F8 */ (PfnEntityUpdate)EntityIntenseExplosion,
/* 3FC */ (PfnEntityUpdate)EntitySoulStealOrb,
/* 400 */ (PfnEntityUpdate)EntityRoomForeground,
/* 404 */ (PfnEntityUpdate)EntityStageNamePopup,
/* 408 */ (PfnEntityUpdate)EntityEquipItemDrop,
/* 40C */ (PfnEntityUpdate)EntityRelicOrb,
/* 410 */ (PfnEntityUpdate)EntityHeartDrop,
/* 414 */ (PfnEntityUpdate)EntityEnemyBlood,
/* 418 */ (PfnEntityUpdate)EntityMessageBox,
/* 41C */ (PfnEntityUpdate)EntityDummy,
/* 420 */ (PfnEntityUpdate)EntityDummy,
/* 424 */ (PfnEntityUpdate)func_80186FD0, // unused
/* 428 */ (PfnEntityUpdate)func_801870B0, // unused? looks debugging stuff
/* 42C */ (PfnEntityUpdate)EntityUnkId13,
/* 430 */ (PfnEntityUpdate)EntityUnkId14,
/* 434 */ (PfnEntityUpdate)EntityUnkId15,
/* 438 */ (PfnEntityUpdate)EntityWarpRoom,
/* 43C */ (PfnEntityUpdate)EntityWarpSmallRocks,
};
// *** Group here all the Entity Init ***
u16 g_eBreakableInit[] = {
0x8001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
u16 g_InitializeData0[] = {
0x0003, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000,
};
u16 g_InitializeEntityData0[] = {
0x0003, 0x0000, 0x0000, 0x0000, 0x0002, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0004, 0x0000,
};
u16 g_EInitGeneric[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0005, 0x0000,
};
u16 g_InitDataEnt13[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0002, 0x0000,
};
u16 D_80180488[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000,
};
u16 g_eInitGeneric2[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0003, 0x0000,
};
u16 g_eDamageDisplayInit[] = {
/**/ 0x0000, 0x0000, 0x0000, 0x0000, 0x0003, 0x0000,
/**/ 0x8001, 0x0000, 0x0000, 0x0000, 0x0003, 0x0000,
/**/ 0x800B, 0x0001, 0x0048, 0x021A, 0x0061, 0x0000,
};
u16 D_801804C4[] = {
0x8001, 0x0000, 0x0000, 0x0000, 0x0005, 0x0000,
};
// ******
static u32 D_801804D0[] = {0x00FF0140};
static u32 D_801804D4[] = {0x26022502, 0x26022702, 0x00000000};
ObjInit2 D_801804E0[] = {
{0x0006, 0x01FA, 0x0000, 0x0000, 0x00, 0x10, 0x00000000, D_801804D0},
{0x8001, 0x00C0, 0x0000, 0x0000, 0x03, 0x30, 0x00000000, D_801804D4},
};
// Owned by EntityRedDoor to animate the tiles behind the door itself.
// There is a loop in EntityRedDoor that forces to write those tiles
// at every frame based on the door state to create the animation.
u16 g_eRedDoorTiles[2][8] = {
{0x1D, 0x25, 0x75, 0x7D, 0xC6, 0xC7, 0xC8, 0xC9},
{0x1F, 0x27, 0x77, 0x7F, 0xCA, 0xCB, 0xCC, 0xCD},
};

44
src/st/wrp/d_1b8.c Normal file
View File

@ -0,0 +1,44 @@
#include "wrp.h"
#include "../st_private.h"
extern u32 D_80181420[];
extern u32 D_80181764[];
static void* D_801801B8[] = {
/* 0x1B8 */ (void*)0x00000000,
/* 0x1BC */ (void*)0x00000000,
/* 0x1C0 */ (void*)0x00000000,
/* 0x1C4 */ (void*)0x00000000,
/* 0x1C8 */ (void*)0xFFFFFFFF,
};
static void* D_801801CC[] = {
/* 0x1CC */ (void*)0x00000004,
/* 0x1D0 */ (void*)0x00400100,
/* 0x1D4 */ (void*)0x00800080,
/* 0x1D8 */ (void*)D_80181420,
/* 0x1DC */ (void*)0x00600100,
/* 0x1E0 */ (void*)0x00800080,
/* 0x1E4 */ (void*)D_80181764,
/* 0x1E8 */ (void*)0xFFFFFFFF,
};
void* OVL_EXPORT(g_EntityGfxs)[] = {
/* 0x1EC */ D_801801B8,
/* 0x1F0 */ D_801801B8,
/* 0x1F4 */ D_801801B8,
/* 0x1F8 */ D_801801B8,
/* 0x1FC */ D_801801B8,
/* 0x200 */ D_801801CC,
/* 0x204 */ D_801801B8,
/* 0x208 */ D_801801B8,
/* 0x20C */ D_801801B8,
/* 0x210 */ D_801801B8,
/* 0x214 */ D_801801B8,
/* 0x218 */ D_801801B8,
/* 0x21C */ D_801801B8,
/* 0x220 */ D_801801B8,
/* 0x224 */ D_801801B8,
/* 0x228 */ D_801801B8,
/* 0x22C */ D_801801B8,
/* 0x230 */ D_801801B8,
/* 0x234 */ D_801801B8,
/* 0x238 */ D_801801B8,
};

84
src/st/wrp/d_3e4.c Normal file
View File

@ -0,0 +1,84 @@
#include "wrp.h"
#include "../st_private.h"
void func_80186FD0(Entity*);
void func_801870B0(Entity*);
void EntityUnkId13(Entity*);
void EntityUnkId14(Entity*);
void EntityUnkId15(Entity*);
void EntityWarpRoom(Entity*);
void EntityWarpSmallRocks(Entity*);
void EntityPrizeDrop(Entity*);
PfnEntityUpdate PfnEntityUpdates[] = {
/* 3E4 */ (PfnEntityUpdate)EntityBreakable,
/* 3E8 */ (PfnEntityUpdate)EntityExplosion,
/* 3EC */ (PfnEntityUpdate)EntityPrizeDrop,
/* 3F0 */ (PfnEntityUpdate)EntityDamageDisplay,
/* 3F4 */ (PfnEntityUpdate)EntityRedDoor,
/* 3F8 */ (PfnEntityUpdate)EntityIntenseExplosion,
/* 3FC */ (PfnEntityUpdate)EntitySoulStealOrb,
/* 400 */ (PfnEntityUpdate)EntityRoomForeground,
/* 404 */ (PfnEntityUpdate)EntityStageNamePopup,
/* 408 */ (PfnEntityUpdate)EntityEquipItemDrop,
/* 40C */ (PfnEntityUpdate)EntityRelicOrb,
/* 410 */ (PfnEntityUpdate)EntityHeartDrop,
/* 414 */ (PfnEntityUpdate)EntityEnemyBlood,
/* 418 */ (PfnEntityUpdate)EntityMessageBox,
/* 41C */ (PfnEntityUpdate)EntityDummy,
/* 420 */ (PfnEntityUpdate)EntityDummy,
/* 424 */ (PfnEntityUpdate)func_80186FD0, // unused
/* 428 */ (PfnEntityUpdate)func_801870B0, // unused? looks debugging stuff
/* 42C */ (PfnEntityUpdate)EntityUnkId13,
/* 430 */ (PfnEntityUpdate)EntityUnkId14,
/* 434 */ (PfnEntityUpdate)EntityUnkId15,
/* 438 */ (PfnEntityUpdate)EntityWarpRoom,
/* 43C */ (PfnEntityUpdate)EntityWarpSmallRocks,
};
// *** Group here all the Entity Init ***
u16 g_eBreakableInit[] = {
0x8001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
u16 g_InitializeData0[] = {
0x0003, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000,
};
u16 g_InitializeEntityData0[] = {
0x0003, 0x0000, 0x0000, 0x0000, 0x0002, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0004, 0x0000,
};
u16 g_EInitGeneric[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0005, 0x0000,
};
u16 g_InitDataEnt13[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0002, 0x0000,
};
u16 D_80180488[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000,
};
u16 g_eInitGeneric2[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0003, 0x0000,
};
u16 g_eDamageDisplayInit[] = {
/**/ 0x0000, 0x0000, 0x0000, 0x0000, 0x0003, 0x0000,
/**/ 0x8001, 0x0000, 0x0000, 0x0000, 0x0003, 0x0000,
/**/ 0x800B, 0x0001, 0x0048, 0x021A, 0x0061, 0x0000,
};
u16 D_801804C4[] = {
0x8001, 0x0000, 0x0000, 0x0000, 0x0005, 0x0000,
};
// ******
static u32 D_801804D0[] = {0x00FF0140};
static u32 D_801804D4[] = {0x26022502, 0x26022702, 0x00000000};
ObjInit2 D_801804E0[] = {
{0x0006, 0x01FA, 0x0000, 0x0000, 0x00, 0x10, 0x00000000, D_801804D0},
{0x8001, 0x00C0, 0x0000, 0x0000, 0x03, 0x30, 0x00000000, D_801804D4},
};
// Owned by EntityRedDoor to animate the tiles behind the door itself.
// There is a loop in EntityRedDoor that forces to write those tiles
// at every frame based on the door state to create the animation.
u16 g_eRedDoorTiles[2][8] = {
{0x1D, 0x25, 0x75, 0x7D, 0xC6, 0xC7, 0xC8, 0xC9},
{0x1F, 0x27, 0x77, 0x7F, 0xCA, 0xCB, 0xCC, 0xCD},
};

View File

@ -10,59 +10,30 @@ void InitRoomEntities(s32 objLayoutId);
void func_801916C4(u16);
void BottomCornerText(u8*, u8);
extern RoomHeader g_Rooms[];
extern s16** g_SpriteBanks[];
extern MyRoomDef rooms_layers[];
extern RoomHeader OVL_EXPORT(rooms)[];
static signed short* spriteBanks[];
extern void* g_Cluts[];
extern RoomDef g_TileLayers[];
extern void* OVL_EXPORT(g_EntityGfxs)[];
void UpdateStageEntities(void);
Overlay g_StageOverlay = {
/* 0x00 */ Update,
/* 0x04 */ HitDetection,
/* 0x08 */ UpdateRoomPosition,
/* 0x0C */ InitRoomEntities,
/* 0x10 */ g_Rooms,
/* 0x14 */ g_SpriteBanks,
/* 0x18 */ g_Cluts,
/* 0x1C */ NULL,
/* 0x20 */ g_TileLayers,
/* 0x24 */ OVL_EXPORT(g_EntityGfxs),
/* 0x28 */ UpdateStageEntities,
/* 0x2C */ 0x00000000,
/* 0x30 */ 0x00000000,
/* 0x34 */ 0x00000000,
/* 0x38 */ 0x00000000,
/* 0x3C */ 0x00000000,
static Overlay OVL_EXPORT(Overlay) = {
.Update = Update,
.HitDetection = HitDetection,
.UpdateRoomPosition = UpdateRoomPosition,
.InitRoomEntities = InitRoomEntities,
.rooms = OVL_EXPORT(rooms),
.spriteBanks = spriteBanks,
.cluts = g_Cluts,
.objLayoutHorizontal = NULL,
.tileLayers = rooms_layers,
.gfxBanks = OVL_EXPORT(g_EntityGfxs),
.UpdateStageEntities = UpdateStageEntities,
};
extern SpriteParts* D_80186D88[];
s16** g_SpriteBanks[] = {
/* 0x040 */ 0x00000000,
/* 0x044 */ D_80186D88,
/* 0x048 */ 0x00000000,
/* 0x04C */ 0x00000000,
/* 0x050 */ 0x00000000,
/* 0x054 */ 0x00000000,
/* 0x058 */ 0x00000000,
/* 0x05C */ 0x00000000,
/* 0x060 */ 0x00000000,
/* 0x064 */ 0x00000000,
/* 0x068 */ 0x00000000,
/* 0x06C */ 0x00000000,
/* 0x070 */ 0x00000000,
/* 0x074 */ 0x00000000,
/* 0x078 */ 0x00000000,
/* 0x07C */ 0x00000000,
/* 0x080 */ 0x00000000,
/* 0x084 */ 0x00000000,
/* 0x088 */ 0x00000000,
/* 0x08C */ 0x00000000,
/* 0x090 */ 0x00000000,
/* 0x094 */ 0x00000000,
/* 0x098 */ 0x00000000,
/* 0x09C */ 0x00000000,
};
#include "sprite_banks.h"
extern u16 D_80181D08[16];
void* D_801800A0[] = {
@ -74,34 +45,4 @@ void* g_Cluts[] = {
/* 0x0B4 */ D_801800A0,
};
// TileDefinition D_80182D68[];
// TileDefinition D_80186D78;
// u16 D_80181D68[];
// u16 D_80181F68[];
// u16 D_80182168[];
// u16 D_80182368[];
// u16 D_80182568[];
// u16 D_80182768[];
// LayerDef D_801800B8 = {0, 0, 0, 0, 0, 0};
// LayerDef D_801800C8 = {D_80181D68, &D_80186D78, 0x01328328, 0x60, 3, 0};
// LayerDef D_801800D8 = {D_80181F68, &D_80186D78, 0x01565565, 0x60, 3, 0};
// LayerDef D_801800E8 = {D_80182168, &D_80186D78, 0x0147B47B, 0x60, 3, 0};
// LayerDef D_801800F8 = {D_80182368, &D_80186D78, 0x0198F98F, 0x60, 3, 0};
// LayerDef D_80180108 = {D_80182568, &D_80186D78, 0x01B23B23, 0x60, 3, 0};
// LayerDef D_80180118 = {D_80182768, &D_80182D68, 0x40B22B22, 0x20, 3, 2};
// LayerDef D_80180128 = {D_80182768, &D_80182D68, 0x41990990, 0x1F, 3, 2};
// LayerDef D_80180138 = {D_80182768, &D_80182D68, 0x40564564, 0x1E, 3, 2};
// LayerDef D_80180148 = {D_80182768, &D_80182D68, 0x4147C47C, 0x1D, 3, 2};
// LayerDef D_80180158 = {D_80182768, &D_80182D68, 0x40327327, 0x1C, 3, 2};
// RoomDef g_TileLayers[] = {
// /* 0x168 */ {&D_801800C8, &D_801800B8},
// /* 0x170 */ {&D_801800D8, &D_801800B8},
// /* 0x178 */ {&D_801800E8, &D_801800B8},
// /* 0x180 */ {&D_801800F8, &D_801800B8},
// /* 0x188 */ {&D_80180108, &D_801800B8},
// /* 0x190 */ {&D_80180118, &D_801800B8},
// /* 0x198 */ {&D_80180128, &D_801800B8},
// /* 0x1A0 */ {&D_80180138, &D_801800B8},
// /* 0x1A8 */ {&D_80180148, &D_801800B8},
// /* 0x1B0 */ {&D_80180158, &D_801800B8},
// };
#include "layers.h"

10
src/st/wrp/tile_data.c Normal file
View File

@ -0,0 +1,10 @@
#include <stage.h>
#include "tilemap_01D68.h"
#include "tilemap_01F68.h"
#include "tilemap_02168.h"
#include "tilemap_02368.h"
#include "tilemap_02568.h"
#include "tilemap_02768.h"
#include "tiledef_02D68.h"
#include "tiledef_06D78.h"

533
tools/sotn-assets/build.go Normal file
View File

@ -0,0 +1,533 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"io"
"io/fs"
"math/rand"
"os"
"path"
"sort"
"strconv"
"strings"
"golang.org/x/sync/errgroup"
)
func makeSymbolFromFileName(fileName string) string {
return strings.Split(path.Base(fileName), ".")[0]
}
func writeStaticU8(w io.Writer, fileName string, symbol string) error {
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var sb strings.Builder
sb.WriteString("// clang-format off\n")
sb.WriteString(fmt.Sprintf("static unsigned char %s[] = {\n", symbol))
for i := 0; i < len(data); i++ {
sb.WriteString(fmt.Sprintf("0x%02X,", data[i]))
if (i & 15) == 15 {
sb.WriteString("\n")
}
}
sb.WriteString("};\n")
_, err = w.Write([]byte(sb.String()))
return err
}
func buildGenericU16(fileName string, symbol string, outputDir string) error {
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var sb strings.Builder
sb.WriteString("// clang-format off\n")
sb.WriteString(fmt.Sprintf("unsigned short %s[] = {\n", symbol))
for i := 0; i < len(data); i += 2 {
sb.WriteString(fmt.Sprintf("0x%02X%02X,", data[i+1], data[i]))
if (i & 31) == 30 {
sb.WriteString("\n")
}
}
sb.WriteString("};\n")
return os.WriteFile(path.Join(outputDir, fmt.Sprintf("%s.h", symbol)), []byte(sb.String()), 0644)
}
func buildRooms(fileName string, outputDir string) error {
ovlName := path.Base(outputDir)
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var rooms []room
if err := json.Unmarshal(data, &rooms); err != nil {
return err
}
content := strings.Builder{}
content.WriteString("// clang-format off\n")
content.WriteString(fmt.Sprintf("unsigned char %s_rooms[] = {\n", strings.ToUpper(ovlName)))
for _, room := range rooms {
s := fmt.Sprintf(" %d, %d, %d, %d, %d, %d, %d, %d,\n",
room.Left, room.Top, room.Right, room.Bottom,
room.LayerID, room.TileDefID, room.EntityGfxID, room.EntityLayoutID)
content.WriteString(s)
}
content.WriteString(" 0x40\n};\n")
return os.WriteFile(path.Join(outputDir, "rooms.c"), []byte(content.String()), 0644)
}
func buildTiledefs(fileName string, symbol string, outputDir string) error {
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var tiledef tileDefPaths
if err := json.Unmarshal(data, &tiledef); err != nil {
return err
}
f, err := os.Create(path.Join(outputDir, fmt.Sprintf("%s.h", symbol)))
if err != nil {
return err
}
defer f.Close()
tilesSymbol := makeSymbolFromFileName(tiledef.Tiles)
tilesFileName := path.Join(path.Dir(fileName), tiledef.Tiles)
if err := writeStaticU8(f, tilesFileName, tilesSymbol); err != nil {
return err
}
pagesSymbol := makeSymbolFromFileName(tiledef.Pages)
pagesFileName := path.Join(path.Dir(fileName), tiledef.Pages)
if err := writeStaticU8(f, pagesFileName, pagesSymbol); err != nil {
return err
}
clutsSymbol := makeSymbolFromFileName(tiledef.Cluts)
clutsFileName := path.Join(path.Dir(fileName), tiledef.Cluts)
if err := writeStaticU8(f, clutsFileName, clutsSymbol); err != nil {
return err
}
colsSymbol := makeSymbolFromFileName(tiledef.Collisions)
colsFileName := path.Join(path.Dir(fileName), tiledef.Collisions)
if err := writeStaticU8(f, colsFileName, colsSymbol); err != nil {
return err
}
_, _ = f.WriteString("// clang-format off\n")
_, _ = f.WriteString(fmt.Sprintf("TileDefinition %s[] = {\n", symbol))
_, _ = f.WriteString(fmt.Sprintf(" %s,\n", tilesSymbol))
_, _ = f.WriteString(fmt.Sprintf(" %s,\n", pagesSymbol))
_, _ = f.WriteString(fmt.Sprintf(" %s,\n", clutsSymbol))
_, _ = f.WriteString(fmt.Sprintf(" %s,\n", colsSymbol))
_, _ = f.WriteString("};\n")
return nil
}
func buildLayers(inputDir string, fileName string, outputDir string) error {
getHash := func(l layerUnpacked) string {
return fmt.Sprintf("%s-%s-%d-%d-%d-%d", l.Data, l.Tiledef, l.Left, l.Top, l.Right, l.Bottom)
}
pack := func(l layerUnpacked) map[string]interface{} {
return map[string]interface{}{
"data": makeSymbolFromFileName(l.Data),
"tiledef": makeSymbolFromFileName(l.Tiledef),
"params": l.Left | (l.Top << 6) | (l.Right << 12) | (l.Bottom << 18) | (l.ScrollMode << 24) |
(btoi(l.IsSaveRoom) << 29) | (btoi(l.IsLoadingRoom) << 30) | (btoi(l.UnusedFlag) << 31),
"zPriority": l.ZPriority,
"unkE": l.UnkE,
"unkF": l.UnkF,
}
}
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var roomsLayers []map[string]*layerUnpacked
if err := json.Unmarshal(data, &roomsLayers); err != nil {
return err
}
tilemaps := map[string]struct{}{}
tiledefs := map[string]struct{}{}
for _, room := range roomsLayers {
// the split on '.' is to remove the extension
if layer, found := room["fg"]; found {
tilemaps[layer.Data] = struct{}{}
tiledefs[layer.Tiledef] = struct{}{}
}
if layer, found := room["bg"]; found {
tilemaps[layer.Data] = struct{}{}
tiledefs[layer.Tiledef] = struct{}{}
}
}
// use unused tiledefs
files, err := os.ReadDir(inputDir)
if err != nil {
return err
}
for _, file := range files {
if !file.IsDir() && strings.HasPrefix(file.Name(), "tiledef_") && strings.HasSuffix(file.Name(), ".json") {
tiledefs[file.Name()] = struct{}{}
}
}
eg := errgroup.Group{}
for name := range tilemaps {
fullPath := path.Join(path.Dir(fileName), name)
symbol := makeSymbolFromFileName(name)
eg.Go(func() error {
return buildGenericU16(fullPath, symbol, outputDir)
})
}
for name := range tiledefs {
fullPath := path.Join(path.Dir(fileName), name)
symbol := makeSymbolFromFileName(name)
eg.Go(func() error {
return buildTiledefs(fullPath, symbol, outputDir)
})
}
if err := eg.Wait(); err != nil {
return err
}
layers := []map[string]interface{}{} // first layer is always empty
layers = append(layers, map[string]interface{}{})
roomLayersId := make([]int, len(roomsLayers)*2)
pool := map[string]int{}
pool[""] = 0
for _, rl := range roomsLayers {
if l, fgFound := rl["fg"]; fgFound {
hash := getHash(*l)
if index, found := pool[hash]; !found {
pool[hash] = len(layers)
roomLayersId = append(roomLayersId, len(layers))
layers = append(layers, pack(*l))
} else {
roomLayersId = append(roomLayersId, index)
}
} else {
roomLayersId = append(roomLayersId, 0)
}
if l, bgFound := rl["bg"]; bgFound {
hash := getHash(*l)
if index, found := pool[hash]; !found {
pool[hash] = len(layers)
roomLayersId = append(roomLayersId, len(layers))
layers = append(layers, pack(*l))
} else {
roomLayersId = append(roomLayersId, index)
}
} else {
roomLayersId = append(roomLayersId, 0)
}
}
sb := strings.Builder{}
sb.WriteString("// clang-format off\n")
for name := range tilemaps {
symbol := makeSymbolFromFileName(name)
sb.WriteString(fmt.Sprintf("extern unsigned short %s[];\n", symbol))
}
for name := range tiledefs {
symbol := makeSymbolFromFileName(name)
sb.WriteString(fmt.Sprintf("extern TileDefinition %s[];\n", symbol))
}
sb.WriteString("static MyLayer layers[] = {\n")
sb.WriteString(" {},\n")
for _, l := range layers[1:] {
sb.WriteString(fmt.Sprintf(" { %s, %s, 0x%08X, 0x%02X, %d, %d },\n",
makeSymbolFromFileName(l["data"].(string)),
makeSymbolFromFileName(l["tiledef"].(string)),
l["params"],
l["zPriority"],
l["unkE"],
l["unkF"],
))
}
sb.WriteString("};\n")
sb.WriteString("static MyRoomDef rooms_layers[] = {\n")
for _, rl := range roomsLayers {
if l, found := rl["fg"]; found {
sb.WriteString(fmt.Sprintf(" { &layers[%d], ", pool[getHash(*l)]))
} else {
sb.WriteString(fmt.Sprintf(" { &layers[0], "))
}
if l, found := rl["bg"]; found {
sb.WriteString(fmt.Sprintf("&layers[%d] },\n", pool[getHash(*l)]))
} else {
sb.WriteString(fmt.Sprintf("&layers[0] },\n"))
}
}
sb.WriteString("};\n")
return os.WriteFile(path.Join(outputDir, "layers.h"), []byte(sb.String()), 0644)
}
func buildSpriteGroup(sb *strings.Builder, sprites []*[]sprite, mainSymbol string, r *rand.Rand) {
symbols := []string{}
for _, spriteGroup := range sprites {
if spriteGroup != nil {
symbol := fmt.Sprintf("spriteGroup_%08X", r.Int31())
size := len(*spriteGroup)*11 + 1
if (len(*spriteGroup) & 1) == 1 { // perform alignment at the end
size += 2
} else {
size += 1
}
sb.WriteString(fmt.Sprintf("static signed short %s[%d];\n", symbol, size))
symbols = append(symbols, symbol)
} else {
symbols = append(symbols, "")
}
}
sb.WriteString(fmt.Sprintf("signed short* %s[] = {\n", mainSymbol))
for _, symbol := range symbols {
if len(symbol) > 0 {
sb.WriteString(fmt.Sprintf(" %s,\n", symbol))
} else {
sb.WriteString(" 0,\n")
}
}
sb.WriteString("};\n")
for i, spriteGroup := range sprites {
if spriteGroup == nil {
continue
}
sb.WriteString(fmt.Sprintf("static signed short %s[] = {\n", symbols[i]))
sb.WriteString(fmt.Sprintf(" %d,\n", len(*spriteGroup)))
for _, sprite := range *spriteGroup {
sb.WriteString(fmt.Sprintf(" %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d,\n",
sprite.Flags, sprite.X, sprite.Y, sprite.Width, sprite.Height,
sprite.Clut, sprite.Tileset, sprite.Left, sprite.Top, sprite.Right, sprite.Bottom))
}
if (len(*spriteGroup) & 1) == 1 { // perform alignment at the end
sb.WriteString(" 0, 0\n")
} else {
sb.WriteString(" 0\n")
}
sb.WriteString("};\n")
}
}
func buildSprites(fileName string, outputDir string) error {
ovlName := path.Base(outputDir)
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var spritesBanks spriteDefs
if err := json.Unmarshal(data, &spritesBanks); err != nil {
return err
}
r := rand.New(rand.NewSource(int64(len(data))))
sbHeader := strings.Builder{}
sbHeader.WriteString("// clang-format off\n")
sbData := strings.Builder{}
sbData.WriteString("// clang-format off\n")
symbols := []string{}
for i, sprites := range spritesBanks.Banks {
if len(sprites) == 0 {
symbols = append(symbols, "")
continue
}
symbol := fmt.Sprintf("sprites_%s_%d", ovlName, i)
sbHeader.WriteString(fmt.Sprintf("extern signed short* %s;\n", symbol))
buildSpriteGroup(&sbData, sprites, symbol, r)
symbols = append(symbols, symbol)
}
sbHeader.WriteString("static signed short* spriteBanks[] = {\n")
for _, index := range spritesBanks.Indices {
if index >= 0 {
sbHeader.WriteString(fmt.Sprintf(" &%s,\n", symbols[index]))
} else {
sbHeader.WriteString(fmt.Sprintf(" 0,\n"))
}
}
sbHeader.WriteString("};\n")
if err := os.WriteFile(path.Join(outputDir, "sprites.c"), []byte(sbData.String()), 0644); err != nil {
return err
}
return os.WriteFile(path.Join(outputDir, "sprite_banks.h"), []byte(sbHeader.String()), 0644)
}
func buildEntityLayouts(fileName string, outputDir string) error {
writeLayoutEntries := func(sb *strings.Builder, banks [][]layoutEntry, align4 bool) error {
nWritten := 0
for i, entries := range banks {
// do a sanity check on the entries as we do not want to build something that will cause the game to crash
if entries[0].X != -2 || entries[0].Y != -2 {
return fmt.Errorf("layout entity bank %d needs to have a X:-2 and Y:-2 entry at the beginning", i)
}
lastEntry := entries[len(entries)-1]
if lastEntry.X != -1 || lastEntry.Y != -1 {
return fmt.Errorf("layout entity bank %d needs to have a X:-1 and Y:-1 entry at the end", i)
}
for _, e := range entries {
sb.WriteString(fmt.Sprintf(" 0x%04X, 0x%04X, 0x%04X, 0x%04X, 0x%04X,\n",
uint16(e.X), uint16(e.Y), int(e.ID)|(int(e.Flags)<<8), int(e.Slot)|(int(e.SpawnID)<<8), e.Params))
}
nWritten += len(entries)
}
if align4 && nWritten%2 != 0 {
sb.WriteString(" 0, // padding\n")
}
return nil
}
makeSortedBanks := func(banks [][]layoutEntry, sortByX bool) [][]layoutEntry {
var toSort []layoutEntry
var less func(i, j int) bool
if sortByX {
less = func(i, j int) bool {
return toSort[i].X < toSort[j].X
}
} else {
less = func(i, j int) bool {
if toSort[i].Y < toSort[j].Y {
return true
}
if toSort[i].Y > toSort[j].Y {
return false
}
if toSort[i].YOrder != nil && toSort[j].YOrder != nil {
return *toSort[i].YOrder < *toSort[j].YOrder
}
return i < j
}
}
sorting := make([][]layoutEntry, len(banks))
for i, entries := range banks {
sorting[i] = make([]layoutEntry, len(entries)-2)
if len(sorting[i]) > 0 { // do not sort if the list is empty
copy(sorting[i], entries[1:len(entries)-1]) // do not sort the -2 and -1 entries
toSort = sorting[i]
sort.SliceStable(toSort, less)
}
// put back the -2 and -1
sorting[i] = append([]layoutEntry{entries[0]}, sorting[i]...)
sorting[i] = append(sorting[i], entries[len(entries)-1])
}
return sorting
}
data, err := os.ReadFile(fileName)
if err != nil {
return err
}
var el layouts
if err := json.Unmarshal(data, &el); err != nil {
return err
}
h := fnv.New32()
h.Write([]byte(outputDir))
symbolVariant := strconv.FormatUint(uint64(h.Sum32()), 16)
symbolName := fmt.Sprintf("entity_layout_%s", symbolVariant)
offsets := make([]int, len(el.Entities))
offsetCur := 0
for i := 0; i < len(el.Entities); i++ {
offsets[i] = offsetCur
offsetCur += len(el.Entities[i]) * 5
}
sbHeader := strings.Builder{}
sbHeader.WriteString("#include <stage.h>\n\n")
sbHeader.WriteString("// clang-format off\n")
sbHeader.WriteString(fmt.Sprintf("extern u16 %s_x[];\n", symbolName))
sbHeader.WriteString("LayoutEntity* g_pStObjLayoutHorizontal[] = {\n")
for _, i := range el.Indices {
sbHeader.WriteString(fmt.Sprintf(" (LayoutEntity*)&%s_x[%d],\n", symbolName, offsets[i]))
}
sbHeader.WriteString(fmt.Sprintf("};\n"))
sbHeader.WriteString(fmt.Sprintf("extern u16 %s_y[];\n", symbolName))
sbHeader.WriteString("LayoutEntity* g_pStObjLayoutVertical[] = {\n")
for _, i := range el.Indices {
sbHeader.WriteString(fmt.Sprintf(" (LayoutEntity*)&%s_y[%d],\n", symbolName, offsets[i]))
}
sbHeader.WriteString(fmt.Sprintf("};\n"))
sbData := strings.Builder{}
sbData.WriteString("// clang-format off\n")
sbData.WriteString(fmt.Sprintf("unsigned short %s_x[] = {\n", symbolName))
if err := writeLayoutEntries(&sbData, makeSortedBanks(el.Entities, true), false); err != nil {
return fmt.Errorf("unable to build X entity layout: %w", err)
}
sbData.WriteString(fmt.Sprintf("};\n"))
sbData.WriteString(fmt.Sprintf("unsigned short %s_y[] = {\n", symbolName))
if err := writeLayoutEntries(&sbData, makeSortedBanks(el.Entities, false), true); err != nil {
return fmt.Errorf("unable to build Y entity layout: %w", err)
}
sbData.WriteString(fmt.Sprintf("};\n"))
if err := os.WriteFile(path.Join(outputDir, "e_layout.c"), []byte(sbData.String()), 0644); err != nil {
return err
}
return os.WriteFile(path.Join(outputDir, "e_laydef.c"), []byte(sbHeader.String()), 0644)
}
func buildAll(inputDir string, outputDir string) error {
if err := os.MkdirAll(outputDir, 0755); err != nil {
return err
}
eg := errgroup.Group{}
eg.Go(func() error {
if err := buildRooms(path.Join(inputDir, "rooms.json"), outputDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
}
return nil
})
eg.Go(func() error {
if err := buildLayers(inputDir, path.Join(inputDir, "layers.json"), outputDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
}
return nil
})
eg.Go(func() error {
if err := buildSprites(path.Join(inputDir, "sprites.json"), outputDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
}
return nil
})
eg.Go(func() error {
if err := buildEntityLayouts(path.Join(inputDir, "entity_layouts.json"), outputDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return err
}
}
return nil
})
return eg.Wait()
}

View File

@ -0,0 +1,79 @@
package main
import (
"fmt"
"sort"
)
type dataRange struct {
begin PsxOffset
end PsxOffset
}
func (r dataRange) Format(f fmt.State, c rune) {
f.Write([]byte(fmt.Sprintf("(%s, %s)", r.begin, r.end)))
}
func (r dataRange) empty() bool {
return r.begin == RamNull && r.end == RamNull
}
func mergeDataRanges(ranges []dataRange) dataRange {
if len(ranges) == 0 {
err := fmt.Errorf("no datarange, bug?!")
panic(err)
}
sort.Slice(ranges, func(i, j int) bool {
return ranges[i].begin < ranges[j].begin
})
// performs a sanity check before merging everything
for i := 0; i < len(ranges)-1; i++ {
if ranges[i].end != ranges[i+1].begin {
var err error
if ranges[i].end < ranges[i+1].begin {
err = fmt.Errorf("gap between data detected: %s != %s", ranges[i].end, ranges[i+1].begin)
} else {
err = fmt.Errorf("overlap between data detected: %s != %s", ranges[i].end, ranges[i+1].begin)
}
panic(err)
}
}
return dataRange{
begin: ranges[0].begin,
end: ranges[len(ranges)-1].end,
}
}
func consolidateDataRanges(ranges []dataRange) []dataRange {
if len(ranges) == 0 {
return []dataRange{}
}
sort.Slice(ranges, func(i, j int) bool {
return ranges[i].begin < ranges[j].begin
})
for ranges[0].empty() {
ranges = ranges[1:]
}
consolidated := []dataRange{}
first := 0
for i := 0; i < len(ranges)-1; i++ {
if ranges[i].end != ranges[i+1].begin {
consolidated = append(consolidated, dataRange{
begin: ranges[first].begin,
end: ranges[i].end,
})
first = i + 1
}
}
return append(consolidated, dataRange{
begin: ranges[first].begin,
end: ranges[len(ranges)-1].end,
})
}

5
tools/sotn-assets/go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/xeeynamo/sotn-decomp/tools/sotn-assets
go 1.21
require golang.org/x/sync v0.7.0 // indirect

2
tools/sotn-assets/go.sum Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

View File

@ -0,0 +1,112 @@
package main
import (
"encoding/binary"
"os"
)
type gfxKind uint16
const (
gfxBankNone = gfxKind(iota)
gfxBank4bpp
gfxBank8bpp
gfxBank16bpp
gfxBankCompressed
)
type gfxEntry struct {
X uint16
Y uint16
Width uint16
Height uint16
Data PsxOffset
}
type gfxBlock struct {
kind gfxKind
flags uint16
entries []gfxEntry
}
type gfx struct {
blocks []gfxBlock
indices []int
}
func readGraphics(file *os.File, off PsxOffset) (gfx, dataRange, error) {
if err := off.moveFile(file); err != nil {
return gfx{}, dataRange{}, err
}
// all the offsets are before the array, so it is easy to find where the offsets array ends
blockOffsets := []PsxOffset{}
for {
var offBank PsxOffset
if err := binary.Read(file, binary.LittleEndian, &offBank); err != nil {
return gfx{}, dataRange{}, err
}
if offBank >= off {
break
}
blockOffsets = append(blockOffsets, offBank)
}
// the order of each gfxBlock must be preserved
pool := map[PsxOffset]int{}
pool[RamNull] = -1
blocks := []gfxBlock{}
ranges := []dataRange{}
for _, blockOffset := range sortUniqueOffsets(blockOffsets) {
if blockOffset == RamNull { // exception for ST0
continue
}
if err := blockOffset.moveFile(file); err != nil {
return gfx{}, dataRange{}, err
}
var block gfxBlock
if err := binary.Read(file, binary.LittleEndian, &block.kind); err != nil {
return gfx{}, dataRange{}, err
}
if err := binary.Read(file, binary.LittleEndian, &block.flags); err != nil {
return gfx{}, dataRange{}, err
}
if block.kind == gfxKind(0xFFFF) && block.flags == 0xFFFF { // exception for ST0
pool[blockOffset] = len(blocks)
blocks = append(blocks, block)
ranges = append(ranges, dataRange{
begin: blockOffset,
end: blockOffset.sum(4),
})
continue
}
for {
var entry gfxEntry
if err := binary.Read(file, binary.LittleEndian, &entry); err != nil {
return gfx{}, dataRange{}, err
}
if entry.X == 0xFFFF && entry.Y == 0xFFFF {
break
}
block.entries = append(block.entries, entry)
}
pool[blockOffset] = len(blocks)
blocks = append(blocks, block)
ranges = append(ranges, dataRange{
begin: blockOffset,
end: blockOffset.sum(4 + len(block.entries)*12 + 4),
})
}
var g gfx
for _, blockOffset := range blockOffsets {
g.indices = append(g.indices, pool[blockOffset])
}
return g, mergeDataRanges(append(ranges, dataRange{
begin: off,
end: off.sum(len(blockOffsets) * 4),
})), nil
}

134
tools/sotn-assets/layer.go Normal file
View File

@ -0,0 +1,134 @@
package main
import (
"encoding/binary"
"encoding/json"
"os"
"slices"
)
type layer struct {
Data PsxOffset
Tiledef PsxOffset
PackedInfo uint32
ZPriority uint16
UnkE uint8
UnkF uint8
}
type layerUnpacked struct {
Data string `json:"data"`
Tiledef string `json:"tiledef"`
Left int `json:"left"`
Top int `json:"top"`
Right int `json:"right"`
Bottom int `json:"bottom"`
ScrollMode int `json:"scrollMode"`
IsSaveRoom bool `json:"isSaveRoom"`
IsLoadingRoom bool `json:"isLoadingRoom"`
UnusedFlag bool `json:"unusedFlag"`
ZPriority int `json:"zPriority"`
UnkE int `json:"unkE"`
UnkF int `json:"unkF"`
}
type roomLayers struct {
fg *layer
bg *layer
}
func (l *layer) tilemapFileSize() int {
sx := int((l.PackedInfo >> 0) & 0x3F)
sy := int((l.PackedInfo >> 6) & 0x3F)
ex := int((l.PackedInfo >> 12) & 0x3F)
ey := int((l.PackedInfo >> 18) & 0x3F)
w := ex - sx + 1
h := ey - sy + 1
return w * h * 512
}
func (l *layer) unpack() layerUnpacked {
return layerUnpacked{
Data: getTilemapFileName(l.Data),
Tiledef: getTiledefFileName(l.Tiledef),
Left: int((l.PackedInfo >> 0) & 0x3F),
Top: int((l.PackedInfo >> 6) & 0x3F),
Right: int((l.PackedInfo >> 12) & 0x3F),
Bottom: int((l.PackedInfo >> 18) & 0x3F),
ScrollMode: int((l.PackedInfo >> 24) & 0x1F),
IsSaveRoom: int((l.PackedInfo>>24)&0x20) != 0,
IsLoadingRoom: int((l.PackedInfo>>24)&0x40) != 0,
UnusedFlag: int((l.PackedInfo>>24)&0x80) != 0,
ZPriority: int(l.ZPriority),
UnkE: int(l.UnkE),
UnkF: int(l.UnkF),
}
}
func (r roomLayers) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{}
if r.fg != nil {
m["fg"] = r.fg.unpack()
}
if r.bg != nil {
m["bg"] = r.bg.unpack()
}
return json.Marshal(m)
}
func readLayers(file *os.File, off PsxOffset) ([]roomLayers, dataRange, error) {
if off == 0 {
return nil, dataRange{}, nil
}
if err := off.moveFile(file); err != nil {
return nil, dataRange{}, err
}
// when the data starts to no longer makes sense, we can assume we reached the end of the array
layerOffsets := []PsxOffset{}
layersOff := make([]PsxOffset, 2)
for {
if err := binary.Read(file, binary.LittleEndian, layersOff); err != nil {
return nil, dataRange{}, err
}
if layersOff[0] <= RamStageBegin || layersOff[0] >= off ||
layersOff[1] <= RamStageBegin || layersOff[1] >= off {
break
}
layerOffsets = append(layerOffsets, layersOff...)
}
// Creates a map of layers, so we can re-use them when a layer is used by multiple rooms
pool := map[PsxOffset]*layer{}
pool[PsxOffset(0)] = nil
for _, layerOffset := range layerOffsets {
if _, exists := pool[layerOffset]; exists {
continue
}
if err := layerOffset.moveFile(file); err != nil {
return nil, dataRange{}, err
}
var l layer
if err := binary.Read(file, binary.LittleEndian, &l); err != nil {
return nil, dataRange{}, err
}
if l.Data != RamNull || l.Tiledef != RamNull || l.PackedInfo != 0 {
pool[layerOffset] = &l
} else {
pool[layerOffset] = nil
}
}
// creates the real array with all the layers mapped
count := len(layerOffsets) >> 1
roomsLayers := make([]roomLayers, count)
for i := 0; i < count; i++ {
roomsLayers[i].fg = pool[layerOffsets[i*2+0]]
roomsLayers[i].bg = pool[layerOffsets[i*2+1]]
}
return roomsLayers, dataRange{
begin: slices.Min(layerOffsets),
end: off.sum(count * 8),
}, nil
}

160
tools/sotn-assets/layout.go Normal file
View File

@ -0,0 +1,160 @@
package main
import (
"encoding/binary"
"fmt"
"io"
"os"
)
type layoutEntry struct {
X int16 `json:"x"`
Y int16 `json:"y"`
ID uint8 `json:"id"`
Flags uint8 `json:"flags"` // TODO properly de-serialize this
Slot uint8 `json:"slot"`
SpawnID uint8 `json:"spawnId"`
Params uint16 `json:"params"`
YOrder *int `json:"yOrder,omitempty"`
}
type layouts struct {
Entities [][]layoutEntry `json:"entities"`
Indices []int `json:"indices"`
}
func readEntityLayoutEntry(file *os.File) (layoutEntry, error) {
bs := make([]byte, 10)
if _, err := io.ReadFull(file, bs); err != nil {
return layoutEntry{}, err
}
return layoutEntry{
X: int16(binary.LittleEndian.Uint16(bs[0:2])),
Y: int16(binary.LittleEndian.Uint16(bs[2:4])),
ID: bs[4],
Flags: bs[5],
Slot: bs[6],
SpawnID: bs[7],
Params: binary.LittleEndian.Uint16(bs[8:10]),
}, nil
}
// the Y-ordered entries list has a different order than the X-ordered one. The order cannot consistently get
// restored by just sorting entries by Y as usually entries with the same Y results swapped.
// This algorithm will fill the optional field YOrder, only useful to restore the original order.
func hydrateYOrderFields(x layouts, y layouts) error {
if len(x.Indices) != len(y.Indices) {
return fmt.Errorf("number of X and Y layout indices do not match")
}
if len(x.Entities) != len(y.Entities) {
return fmt.Errorf("number of X and Y layout entries do not match")
}
populateYOrderField := func(xEntries []layoutEntry, yEntries []layoutEntry) {
yIndexMap := make(map[layoutEntry]int, len(yEntries))
for i, e := range yEntries {
yIndexMap[e] = i
}
for i := 0; i < len(xEntries); i++ {
if yOrder, found := yIndexMap[xEntries[i]]; found {
xEntries[i].YOrder = &yOrder
}
}
}
for i := 0; i < len(x.Entities); i++ {
xList := x.Entities[i]
yList := y.Entities[i]
if len(xList) != len(yList) {
return fmt.Errorf("number of X and Y entries do not match")
}
populateYOrderField(xList, yList)
}
return nil
}
func readEntityLayout(file *os.File, off PsxOffset, count int, isX bool) (layouts, []dataRange, error) {
if err := off.moveFile(file); err != nil {
return layouts{}, nil, err
}
// there are two copies of the layout, one ordered by X and the other one ordered by Y
// we will only read the first one, which is ordered by Y
blockOffsets := make([]PsxOffset, count)
if err := binary.Read(file, binary.LittleEndian, blockOffsets); err != nil {
return layouts{}, nil, err
}
// the order of each layout entry must be preserved
pool := map[PsxOffset]int{}
blocks := [][]layoutEntry{}
xRanges := []dataRange{}
for _, blockOffset := range sortUniqueOffsets(blockOffsets) {
if err := blockOffset.moveFile(file); err != nil {
return layouts{}, nil, err
}
entries := []layoutEntry{}
for {
entry, err := readEntityLayoutEntry(file)
if err != nil {
return layouts{}, nil, err
}
if entry.X == -1 && entry.Y == -1 {
entries = append(entries, entry)
break
}
entries = append(entries, entry)
}
// sanity check on the first entry
if entries[0].X != -2 || entries[0].Y != -2 {
err := fmt.Errorf("first layout entry does not mark the beginning of the array: %v", entries[0])
return layouts{}, nil, err
}
pool[blockOffset] = len(blocks)
blocks = append(blocks, entries)
xRanges = append(xRanges, dataRange{
begin: blockOffset,
end: blockOffset.sum(len(entries) * 10),
})
}
// the very last entry needs to be aligned by 4
xRanges[len(xRanges)-1].end = xRanges[len(xRanges)-1].end.align4()
l := layouts{Entities: blocks}
for _, blockOffset := range blockOffsets {
l.Indices = append(l.Indices, pool[blockOffset])
}
endOfArray := off.sum(count * 4)
if isX { // we want to do the same thing with the vertically aligned layout
yLayouts, yRanges, err := readEntityLayout(file, endOfArray, count, false)
if err != nil {
return layouts{}, nil, fmt.Errorf("readEntityLayout failed on Y: %w", err)
}
if err := hydrateYOrderFields(l, yLayouts); err != nil {
return layouts{}, nil, fmt.Errorf("unable to populate YOrder field: %w", err)
}
xMerged := mergeDataRanges(xRanges)
yMerged := yRanges[1]
return l, []dataRange{
mergeDataRanges([]dataRange{
{
begin: off,
end: endOfArray,
},
yRanges[0],
}),
mergeDataRanges([]dataRange{xMerged, yMerged}),
}, nil
} else {
return l, []dataRange{
{
begin: off,
end: endOfArray,
},
mergeDataRanges(xRanges),
}, nil
}
}

379
tools/sotn-assets/main.go Normal file
View File

@ -0,0 +1,379 @@
package main
import (
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"os"
"path"
)
type dataContainer[T any] struct {
dataRange dataRange
content T
}
type ovl struct {
ranges []dataRange
rooms dataContainer[[]room]
layers dataContainer[[]roomLayers]
sprites dataContainer[spriteDefs]
graphics dataContainer[gfx]
layouts dataContainer[layouts]
layoutsExtraRange dataRange
tileMaps dataContainer[map[PsxOffset][]byte]
tileDefs dataContainer[map[PsxOffset]tileDef]
}
func getOvlAssets(fileName string) (ovl, error) {
type stageHeader struct {
FnUpdate PsxOffset
FnHitDetection PsxOffset
FnUpdateRoomPos PsxOffset
FnInitRoomEntities PsxOffset
Rooms PsxOffset // ✅
Sprites PsxOffset // ✅
Cluts PsxOffset // 🫥
Layouts PsxOffset // ✅
Layers PsxOffset // ✅
Graphics PsxOffset // 🫥 WIP
FnUpdateStageEntities PsxOffset
}
file, err := os.Open(fileName)
if err != nil {
return ovl{}, err
}
defer file.Close()
var header stageHeader
if err := binary.Read(file, binary.LittleEndian, &header); err != nil {
return ovl{}, err
}
rooms, roomsRange, err := readRooms(file, header.Rooms)
if err != nil {
return ovl{}, fmt.Errorf("unable to read rooms: %w", err)
}
layers, layersRange, err := readLayers(file, header.Layers)
if err != nil {
return ovl{}, fmt.Errorf("unable to read layers: %w", err)
}
tileMaps, tileMapsRange, err := readAllTileMaps(file, layers)
if err != nil {
return ovl{}, fmt.Errorf("unable to gather all the tile maps: %w", err)
}
tileDefs, tileDefsRange, err := readAllTiledefs(file, layers)
if err != nil {
return ovl{}, fmt.Errorf("unable to gather all the tile defs: %w", err)
}
// check for unused tile defs (CEN has one)
for tileMapsRange.end < tileDefsRange.begin {
offset := tileDefsRange.begin.sum(-0x10)
unusedTileDef, unusedTileDefRange, err := readTiledef(file, offset)
if err != nil {
return ovl{}, fmt.Errorf("there is a gap between tileMaps and tileDefs: %w", err)
}
tileDefs[offset] = unusedTileDef
tileDefsRange = mergeDataRanges([]dataRange{tileDefsRange, unusedTileDefRange})
}
sprites, spritesRange, err := readSpritesBanks(file, header.Sprites)
if err != nil {
return ovl{}, fmt.Errorf("unable to gather all sprites: %w", err)
}
graphics, graphicsRange, err := readGraphics(file, header.Graphics)
if err != nil {
return ovl{}, fmt.Errorf("unable to gather all graphics: %w", err)
}
layoutOff := header.Layouts
if layoutOff == RamNull {
// some overlays have this field nulled, we have to find the offset ourselves
// it should be usually be right after header.Graphics
layoutOff = graphicsRange.end // ⚠️ assumption
}
nLayouts := maxBy(rooms, func(r room) int { // ⚠️ assumption
return int(r.EntityLayoutID)
}) + 1
nLayouts = 53 // it seems there are always 53 elements?!
entityLayouts, layoutsRange, err := readEntityLayout(file, layoutOff, nLayouts, true)
if err != nil {
return ovl{}, fmt.Errorf("unable to gather all entity layouts: %w", err)
}
return ovl{
ranges: consolidateDataRanges([]dataRange{
roomsRange,
layersRange,
spritesRange,
graphicsRange,
layoutsRange[0],
layoutsRange[1],
tileMapsRange,
tileDefsRange,
}),
rooms: dataContainer[[]room]{dataRange: roomsRange, content: rooms},
layers: dataContainer[[]roomLayers]{dataRange: layersRange, content: layers},
sprites: dataContainer[spriteDefs]{dataRange: spritesRange, content: sprites},
graphics: dataContainer[gfx]{dataRange: graphicsRange, content: graphics},
layouts: dataContainer[layouts]{dataRange: layoutsRange[1], content: entityLayouts},
layoutsExtraRange: layoutsRange[0],
tileMaps: dataContainer[map[PsxOffset][]byte]{dataRange: tileMapsRange, content: tileMaps},
tileDefs: dataContainer[map[PsxOffset]tileDef]{dataRange: tileDefsRange, content: tileDefs},
}, nil
}
func extractOvlAssets(o ovl, outputDir string) error {
if err := os.MkdirAll(outputDir, 0755); err != nil {
return err
}
content, err := json.MarshalIndent(o.rooms.content, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(path.Join(outputDir, "rooms.json"), content, 0644); err != nil {
return fmt.Errorf("unable to create rooms file: %w", err)
}
content, err = json.MarshalIndent(o.layers.content, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(path.Join(outputDir, "layers.json"), content, 0644); err != nil {
return fmt.Errorf("unable to create layers file: %w", err)
}
content, err = json.MarshalIndent(o.layouts.content, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(path.Join(outputDir, "entity_layouts.json"), content, 0644); err != nil {
return fmt.Errorf("unable to create entity layouts file: %w", err)
}
for offset, bytes := range o.tileMaps.content {
fileName := path.Join(outputDir, getTilemapFileName(offset))
if err := os.WriteFile(fileName, bytes, 0644); err != nil {
return fmt.Errorf("unable to create %q: %w", fileName, err)
}
}
for offset, tileDefsData := range o.tileDefs.content {
defs := tileDefPaths{
Tiles: getTiledefIndicesFileName(offset),
Pages: getTiledefPagesFileName(offset),
Cluts: getTiledefClutsFileName(offset),
Collisions: getTiledefCollisionsFileName(offset),
}
if err := os.WriteFile(path.Join(outputDir, defs.Tiles), tileDefsData.tiles, 0644); err != nil {
return fmt.Errorf("unable to create %q: %w", defs.Tiles, err)
}
if err := os.WriteFile(path.Join(outputDir, defs.Pages), tileDefsData.pages, 0644); err != nil {
return fmt.Errorf("unable to create %q: %w", defs.Pages, err)
}
if err := os.WriteFile(path.Join(outputDir, defs.Cluts), tileDefsData.cluts, 0644); err != nil {
return fmt.Errorf("unable to create %q: %w", defs.Cluts, err)
}
if err := os.WriteFile(path.Join(outputDir, defs.Collisions), tileDefsData.cols, 0644); err != nil {
return fmt.Errorf("unable to create %q: %w", defs.Collisions, err)
}
content, err = json.MarshalIndent(defs, "", " ")
if err != nil {
return err
}
fileName := path.Join(outputDir, getTiledefFileName(offset))
if err := os.WriteFile(fileName, content, 0644); err != nil {
return fmt.Errorf("unable to create %q: %w", fileName, err)
}
}
content, err = json.MarshalIndent(o.sprites.content, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(path.Join(outputDir, "sprites.json"), content, 0644); err != nil {
return fmt.Errorf("unable to create sprites file: %w", err)
}
return nil
}
func extract(fileName string, outputDir string) error {
o, err := getOvlAssets(fileName)
if err != nil {
return fmt.Errorf("unable to retrieve OVL assets: %w", err)
}
err = extractOvlAssets(o, outputDir)
if err != nil {
return fmt.Errorf("unable to extract OVL assets: %w", err)
}
return nil
}
func info(fileName string) error {
o, err := getOvlAssets(fileName)
if err != nil {
return fmt.Errorf("unable to retrieve OVL assets: %w", err)
}
entries := []struct {
dataRange dataRange
name string
comment string
}{
{o.layers.dataRange, "header", "layers"},
{o.layoutsExtraRange, "e_laydef", "layout entries header"},
{o.rooms.dataRange, "rooms", ""},
{o.layouts.dataRange, "e_layout", "layout entries data"},
{o.tileMaps.dataRange, "tile_data", "tile data"},
{o.tileDefs.dataRange, "tile_data", "tile definitions"},
{o.sprites.dataRange, "sprites", ""},
}
fmt.Printf("data coverage: %+v\n", o.ranges)
fmt.Println("subsegment hints:")
fmt.Println(" - [0x0, .data, header]")
for i := 0; i < len(entries); i++ {
e := entries[i]
s := fmt.Sprintf(" - [0x%X, .data, %s]", e.dataRange.begin.real(), e.name)
if e.comment != "" {
s = fmt.Sprintf("%s # %s", s, e.comment)
}
fmt.Println(s)
// if there is a gap between the current entry and the next one, mark it as unrecognized data
if i == len(entries)-1 || e.dataRange.end != entries[i+1].dataRange.begin {
fmt.Printf(" - [0x%X, data]\n", e.dataRange.end.real())
}
}
return nil
}
func testStuff() {
_ = []string{
"ARE", "CAT", "CEN", "CHI", "DAI", "DRE", "LIB", "MAD",
"NO0", "NO1", "NO2", "NO3", "NO4", "NP3", "NZ0", "NZ1",
"ST0", "TE1", "TE2", "TE3", "TE4", "TE5", "TOP", "WRP",
"RARE", "RCAT", "RCEN", "RCHI", "RDAI", "RLIB", "RNO0", "RNO1",
"RNO2", "RNO3", "RNO4", "RNZ0", "RNZ1", "RTOP", "RWRP"}
//ovls := []string{
// /*"ARE",*/ "CAT", "CEN", "CHI" /*"DAI",*/, "DRE", "LIB", /*"MAD",*/
// /*"NO0",*/ "NO1", "NO2", "NO3" /*"NO4",*/, "NP3", "NZ0", "NZ1",
// "ST0", "TE1", "TE2", "TE3", "TE4", "TE5" /*"TOP",*/, "WRP",
// "RARE", "RCAT" /*"RCEN",*/, "RCHI" /*"RDAI",*/ /*"RLIB",*/ /*"RNO0",*/, "RNO1",
// /*"RNO2",*/ "RNO3" /*"RNO4",*/ /*"RNZ0",*/ /*"RNZ1",*/ /*"RTOP",*/, "RWRP"}
ovls := []string{"NZ0"}
for _, ovl := range ovls {
fmt.Printf("processing %s...\n", ovl)
fileName := fmt.Sprintf("../../disks/us/ST/%s/%s.BIN", ovl, ovl)
if err := extract(fileName, "sample/"+ovl); err != nil {
panic(err)
}
}
if err := buildAll("sample/NZ0", "buildAll/nz0"); err != nil {
panic(err)
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("expected 'info', 'extract', 'build' or 'build_all' subcommands")
os.Exit(1)
}
switch os.Args[1] {
case "info":
extractCmd := flag.NewFlagSet("info", flag.ExitOnError)
var stageOvl string
extractCmd.StringVar(&stageOvl, "stage_ovl", "", "The overlay file to process")
extractCmd.Parse(os.Args[2:])
if err := info(stageOvl); err != nil {
panic(err)
}
case "extract":
extractCmd := flag.NewFlagSet("extract", flag.ExitOnError)
var stageOvl string
var assetDir string
extractCmd.StringVar(&stageOvl, "stage_ovl", "", "The overlay file to process")
extractCmd.StringVar(&assetDir, "o", "", "Where to extract the asset files")
extractCmd.Parse(os.Args[2:])
if stageOvl == "" || assetDir == "" {
fmt.Println("stage_ovl and asset_dir are required for extract")
extractCmd.PrintDefaults()
os.Exit(1)
}
if err := extract(stageOvl, assetDir); err != nil {
panic(err)
}
case "build":
buildCmd := flag.NewFlagSet("build", flag.ExitOnError)
var file string
var kind string
var outputDir string
buildCmd.StringVar(&file, "file", "", "File to process")
buildCmd.StringVar(&kind, "kind", "", "Kind of the file to process")
buildCmd.StringVar(&outputDir, "o", "", "Where to store the processed source files")
buildCmd.Parse(os.Args[2:])
if file == "" || kind == "" || outputDir == "" {
fmt.Println("file, kind, and output_dir are required for build")
buildCmd.PrintDefaults()
os.Exit(1)
}
var err error
switch kind {
case "rooms":
err = buildRooms(file, outputDir)
case "layers":
err = buildLayers(path.Base(file), file, outputDir)
case "sprites":
err = buildSprites(file, outputDir)
default:
fmt.Println("unknown kind, valid values are 'room', 'layer', 'sprites'")
}
if err != nil {
panic(err)
}
case "build_all":
buildCmd := flag.NewFlagSet("build_all", flag.ExitOnError)
var inputDir string
var outputDir string
buildCmd.StringVar(&inputDir, "i", "", "Folder where all the assets are located")
buildCmd.StringVar(&outputDir, "o", "", "Where to store the processed source files")
buildCmd.Parse(os.Args[2:])
if inputDir == "" || outputDir == "" {
fmt.Println("input_dir and output_dir are required for build")
buildCmd.PrintDefaults()
os.Exit(1)
}
if err := buildAll(inputDir, outputDir); err != nil {
panic(err)
}
default:
fmt.Println("expected 'info', 'extract', 'build' or 'build_all' subcommands")
os.Exit(1)
}
}

View File

@ -0,0 +1,27 @@
package main
import "fmt"
func getTilemapFileName(off PsxOffset) string {
return fmt.Sprintf("tilemap_%05X.bin", off.real())
}
func getTiledefFileName(off PsxOffset) string {
return fmt.Sprintf("tiledef_%05X.json", off.real())
}
func getTiledefIndicesFileName(off PsxOffset) string {
return fmt.Sprintf("tiledef_%05X_tiles.bin", off.real())
}
func getTiledefPagesFileName(off PsxOffset) string {
return fmt.Sprintf("tiledef_%05X_pages.bin", off.real())
}
func getTiledefClutsFileName(off PsxOffset) string {
return fmt.Sprintf("tiledef_%05X_cluts.bin", off.real())
}
func getTiledefCollisionsFileName(off PsxOffset) string {
return fmt.Sprintf("tiledef_%05X_cols.bin", off.real())
}

View File

@ -0,0 +1,84 @@
package main
import (
"fmt"
"os"
"slices"
)
type PsxOffset uint32
const (
RamNull = PsxOffset(0)
RamStageBegin = PsxOffset(0x80180000)
RamStageEnd = PsxOffset(0x801C0000)
)
func (off PsxOffset) Format(f fmt.State, c rune) {
f.Write([]byte(fmt.Sprintf("0x%08X", uint32(off))))
}
func (off PsxOffset) real() int {
return int(off - RamStageBegin)
}
func (off PsxOffset) align4() PsxOffset {
if (off & 3) != 0 {
return (off | 3) + 1
}
return off
}
func (off PsxOffset) sum(x int) PsxOffset {
return PsxOffset(uint32(off) + uint32(x))
}
func (off PsxOffset) distanceTo(x PsxOffset) int {
return int(x - off)
}
func (off PsxOffset) valid() bool {
return off >= RamStageBegin && off < RamStageEnd
}
func (off PsxOffset) moveFile(file *os.File) error {
if !off.valid() {
err := fmt.Errorf("offset %08X is outside the stage boundaries", off)
panic(err)
return err
}
stats, err := file.Stat()
if err != nil {
return err
}
fileOffset := int64(off - RamStageBegin)
if fileOffset >= stats.Size() {
return fmt.Errorf("offset %08X is outside the file boundaries", off)
}
file.Seek(fileOffset, 0)
return nil
}
func sortUniqueOffsets(slice []PsxOffset) []PsxOffset {
unique := map[PsxOffset]struct{}{}
for _, v := range slice {
unique[v] = struct{}{}
}
newSlice := make([]PsxOffset, 0, len(unique))
for offset := range unique {
newSlice = append(newSlice, offset)
}
slices.SortFunc(newSlice, func(a, b PsxOffset) int {
if a < b {
return -1
} else if a > b {
return 1
}
return 0
})
return newSlice
}

46
tools/sotn-assets/room.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"encoding/binary"
"os"
)
type room struct {
Left int8 `json:"left"`
Top int8 `json:"top"`
Right int8 `json:"right"`
Bottom int8 `json:"bottom"`
LayerID int8 `json:"layerId"`
TileDefID int8 `json:"tileDefId"`
EntityGfxID int8 `json:"entityGfxId"`
EntityLayoutID int8 `json:"entityLayoutId"`
}
func (r room) isTerminator() bool {
return r.Left == 0x40
}
func readRooms(file *os.File, off PsxOffset) ([]room, dataRange, error) {
if off == 0 {
return nil, dataRange{}, nil
}
if err := off.moveFile(file); err != nil {
return nil, dataRange{}, err
}
rooms := []room{}
for {
var room room
if err := binary.Read(file, binary.LittleEndian, &room); err != nil {
return nil, dataRange{}, err
}
if room.isTerminator() {
break
}
rooms = append(rooms, room)
}
return rooms, dataRange{
begin: off,
end: off.sum(len(rooms)*8 + 4),
}, nil
}

View File

@ -0,0 +1,159 @@
package main
import (
"encoding/binary"
"fmt"
"os"
"sort"
)
type sprite struct {
Flags uint16 `json:"flags"`
X int16 `json:"x"`
Y int16 `json:"y"`
Width uint16 `json:"width"`
Height uint16 `json:"height"`
Clut uint16 `json:"clut"`
Tileset uint16 `json:"tileset"`
Left uint16 `json:"left"`
Top uint16 `json:"top"`
Right uint16 `json:"right"`
Bottom uint16 `json:"bottom"`
}
type spriteDefs struct {
Banks [][]*[]sprite `json:"banks"`
Indices []int `json:"indices"`
}
func readSprites(file *os.File, off PsxOffset) ([]sprite, dataRange, error) {
if err := off.moveFile(file); err != nil {
return nil, dataRange{}, fmt.Errorf("invalid sprites: %w", err)
}
var count uint16
if err := binary.Read(file, binary.LittleEndian, &count); err != nil {
return nil, dataRange{}, err
}
sprites := make([]sprite, count)
if err := binary.Read(file, binary.LittleEndian, sprites); err != nil {
return nil, dataRange{}, err
}
return sprites, dataRange{
begin: off,
end: off.sum(4 + 0x16*int(count)).align4(),
}, nil
}
func readSpriteBank(file *os.File, off PsxOffset) ([]*[]sprite, dataRange, error) {
if err := off.moveFile(file); err != nil {
return nil, dataRange{}, fmt.Errorf("invalid sprite Indices: %w", err)
}
// the end of the sprite array is the beginning of the earliest sprite offset
earliestSpriteOff := RamStageEnd
currentOff := off
spriteOffsets := make([]PsxOffset, 0)
for {
if currentOff == earliestSpriteOff {
break
}
currentOff += 4
var spriteOffset PsxOffset
if err := binary.Read(file, binary.LittleEndian, &spriteOffset); err != nil {
return nil, dataRange{}, err
}
spriteOffsets = append(spriteOffsets, spriteOffset)
if spriteOffset != RamNull {
if !spriteOffset.valid() {
err := fmt.Errorf("sprite offset %s is not valid", spriteOffset)
return nil, dataRange{}, err
}
earliestSpriteOff = min(earliestSpriteOff, spriteOffset)
}
}
headerRange := dataRange{
begin: off,
end: earliestSpriteOff,
}
spriteBank := make([]*[]sprite, len(spriteOffsets))
spriteRanges := []dataRange{}
for i, offset := range spriteOffsets {
if offset == RamNull {
spriteBank[i] = nil
continue
}
sprites, ranges, err := readSprites(file, offset)
if err != nil {
return nil, dataRange{}, fmt.Errorf("unable to read sprites: %w", err)
}
spriteBank[i] = &sprites
spriteRanges = append(spriteRanges, ranges)
}
return spriteBank, mergeDataRanges(append(spriteRanges, headerRange)), nil
}
func readSpritesBanks(file *os.File, off PsxOffset) (spriteDefs, dataRange, error) {
if err := off.moveFile(file); err != nil {
return spriteDefs{}, dataRange{}, err
}
offBanks := make([]PsxOffset, 24)
if err := binary.Read(file, binary.LittleEndian, offBanks); err != nil {
return spriteDefs{}, dataRange{}, err
}
// the order sprites are stored must be preserved
pool := map[PsxOffset][]*[]sprite{}
spriteRanges := []dataRange{}
for _, offset := range offBanks {
if offset == RamNull {
continue
}
if _, found := pool[offset]; found {
continue
}
bank, bankRange, err := readSpriteBank(file, offset)
if err != nil {
return spriteDefs{}, dataRange{}, fmt.Errorf("unable to read sprite Indices: %w", err)
}
pool[offset] = bank
spriteRanges = append(spriteRanges, bankRange)
}
// the indices do not guarantee sprites to be stored in a linear order
// we must sort the offsets to preserve the order sprites are stored
sortedOffsets := make([]PsxOffset, 0, len(pool))
for offset := range pool {
sortedOffsets = append(sortedOffsets, offset)
}
sort.Slice(sortedOffsets, func(i, j int) bool { return sortedOffsets[i] < sortedOffsets[j] })
// create a list of indices to replace the original pointers
indices := make([]int, len(offBanks))
for i, offset := range offBanks {
if offset == RamNull {
indices[i] = -1
}
for j, sortedOffset := range sortedOffsets {
if offset == sortedOffset {
indices[i] = j
}
}
}
banks := make([][]*[]sprite, len(sortedOffsets))
for i, offset := range sortedOffsets {
banks[i] = pool[offset]
}
return spriteDefs{
Banks: banks,
Indices: indices,
}, mergeDataRanges(spriteRanges), nil
}

View File

@ -0,0 +1,100 @@
package main
import (
"encoding/binary"
"fmt"
"os"
)
type tileDef struct {
tiles []byte
pages []byte
cluts []byte
cols []byte
}
type tileDefPaths struct {
Tiles string `json:"tiles"`
Pages string `json:"pages"`
Cluts string `json:"cluts"`
Collisions string `json:"collisions"`
}
func readTiledef(file *os.File, off PsxOffset) (tileDef, dataRange, error) {
if err := off.moveFile(file); err != nil {
return tileDef{}, dataRange{}, err
}
offsets := make([]PsxOffset, 4)
if err := binary.Read(file, binary.LittleEndian, offsets); err != nil {
return tileDef{}, dataRange{}, err
}
td := tileDef{
tiles: make([]byte, offsets[1]-offsets[0]),
pages: make([]byte, offsets[2]-offsets[1]),
cluts: make([]byte, offsets[3]-offsets[2]),
cols: make([]byte, off-offsets[3]),
}
if err := offsets[0].moveFile(file); err != nil {
return tileDef{}, dataRange{}, err
}
if _, err := file.Read(td.tiles); err != nil {
return tileDef{}, dataRange{}, err
}
if err := offsets[1].moveFile(file); err != nil {
return tileDef{}, dataRange{}, err
}
if _, err := file.Read(td.pages); err != nil {
return tileDef{}, dataRange{}, err
}
if err := offsets[2].moveFile(file); err != nil {
return tileDef{}, dataRange{}, err
}
if _, err := file.Read(td.cluts); err != nil {
return tileDef{}, dataRange{}, err
}
if err := offsets[3].moveFile(file); err != nil {
return tileDef{}, dataRange{}, err
}
if _, err := file.Read(td.cols); err != nil {
return tileDef{}, dataRange{}, err
}
return td, dataRange{
begin: offsets[0],
end: off.sum(0x10),
}, nil
}
func readAllTiledefs(file *os.File, roomLayers []roomLayers) (map[PsxOffset]tileDef, dataRange, error) {
ranges := []dataRange{}
processed := map[PsxOffset]tileDef{}
for _, rl := range roomLayers {
if rl.fg != nil {
if _, found := processed[rl.fg.Tiledef]; !found {
td, r, err := readTiledef(file, rl.fg.Tiledef)
if err != nil {
return nil, dataRange{}, fmt.Errorf("unable to read fg tiledef: %w", err)
}
processed[rl.fg.Tiledef] = td
ranges = append(ranges, r)
}
}
if rl.bg != nil {
if _, found := processed[rl.bg.Tiledef]; !found {
td, r, err := readTiledef(file, rl.bg.Tiledef)
if err != nil {
return nil, dataRange{}, fmt.Errorf("unable to read fg tiledef: %w", err)
}
processed[rl.bg.Tiledef] = td
ranges = append(ranges, r)
}
}
}
return processed, mergeDataRanges(ranges), nil
}

View File

@ -0,0 +1,48 @@
package main
import (
"fmt"
"os"
)
func readTilemap(file *os.File, layer *layer) ([]byte, dataRange, error) {
if err := layer.Data.moveFile(file); err != nil {
return nil, dataRange{}, err
}
data := make([]byte, layer.tilemapFileSize())
if _, err := file.Read(data); err != nil {
return nil, dataRange{}, err
}
return data, dataRange{
begin: layer.Data,
end: layer.Data.sum(len(data)),
}, nil
}
func readAllTileMaps(file *os.File, roomLayers []roomLayers) (map[PsxOffset][]byte, dataRange, error) {
ranges := []dataRange{}
processed := map[PsxOffset][]byte{}
for _, rl := range roomLayers {
if rl.fg != nil {
if _, found := processed[rl.fg.Data]; !found {
td, r, err := readTilemap(file, rl.fg)
if err != nil {
return nil, dataRange{}, fmt.Errorf("unable to read fg tilemap: %w", err)
}
processed[rl.fg.Data] = td
ranges = append(ranges, r)
}
}
if rl.bg != nil {
if _, found := processed[rl.bg.Data]; !found {
td, r, err := readTilemap(file, rl.bg)
if err != nil {
return nil, dataRange{}, fmt.Errorf("unable to read fg tilemap: %w", err)
}
processed[rl.bg.Data] = td
ranges = append(ranges, r)
}
}
}
return processed, mergeDataRanges(ranges), nil
}

View File

@ -0,0 +1,36 @@
package main
func minBy[T any](slice []T, getter func(T) int) (max int) {
if len(slice) == 0 {
return max
}
max = getter(slice[0])
for _, item := range slice[1:] {
val := getter(item)
if val < max {
max = val
}
}
return max
}
func maxBy[T any](slice []T, getter func(T) int) (max int) {
if len(slice) == 0 {
return max
}
max = getter(slice[0])
for _, item := range slice[1:] {
val := getter(item)
if val > max {
max = val
}
}
return max
}
func btoi(b bool) int {
if b {
return 1
}
return 0
}

View File

@ -1,232 +0,0 @@
#!/usr/bin/python3
import json
import io
import os
import sys
from typing import Optional
from pathlib import Path
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from splat.util import options, log
from splat.segtypes.n64.segment import N64Segment
from splat.util.symbols import spim_context
import utils
def raise_err(str):
sys.stderr.write("\033[91merror: " + str + "\u001b[0m" + "\n")
raise Exception(str)
def generate_assembly_layers(writer: io.BufferedWriter, name: str, content: str):
obj = json.loads(content)
layer_set = {}
def get_int(myobj, name, min, max):
value = myobj.get(name)
if value == None:
raise_err(f"expect '{name}' but found nothing\ncurrent object: {myobj}")
if value < min:
raise_err(f"expect '{name}' to be at least 0\ncurrent object: {myobj}")
if value > max:
raise_err(f"expect '{name}' to be less than {max}\ncurrent object: {myobj}")
return value
def get_bool(myobj, name):
value = myobj.get(name)
if value == None:
raise_err(f"expect '{name}' but found nothing\ncurrent object: {myobj}")
return value
def get_str(myobj, name):
value = myobj.get(name)
if value == None:
raise_err(f"expect '{name}' but found nothing\ncurrent object: {myobj}")
return value
def write_layer(layer):
hash = json.dumps(layer) # 🤮
if hash in layer_set:
return layer_set[hash]
id = f"DATA_LAYER_{len(layer_set)}"
layer_set[hash] = id
writer.write(f".global {id}\n")
writer.write(f"{id}:\n")
if layer == None:
writer.write(f"\t.word 0, 0, 0, 0\n")
else:
data = get_str(layer, "data")
tiledef = get_str(layer, "tiledef")
flags = (
get_int(layer, "left", 0, 0x3F)
| get_int(layer, "top", 0, 0x3F) << 6
| get_int(layer, "right", 0, 0x3F) << 12
| get_int(layer, "bottom", 0, 0x3F) << 18
| get_int(layer, "scrollMode", 0, 0x1F) << 24
| (1 if get_bool(layer, "isSaveRoom") else 0) << 29
| (1 if get_bool(layer, "isLoadingRoom") else 0) << 30
| (1 if get_bool(layer, "unusedFlag") else 0) << 31
)
zPriority = get_int(layer, "zPriority", 0, 32768) # s16 or u16?
unkE = get_int(layer, "unkE", 0, 255)
unkF = get_int(layer, "unkF", 0, 255)
writer.write(f"\t.word {data}, {tiledef}, 0x{flags:08X}\n")
writer.write(f"\t.short 0x{zPriority:04X}\n")
writer.write(f"\t.byte 0x{unkE:02X}, 0x{unkF:02X}\n")
return id
writer.write(".section .data\n")
write_layer(None)
buf = ""
for room in obj:
fgSym = write_layer(room.get("fg"))
bgSym = write_layer(room.get("bg"))
buf += f".word {fgSym}, {bgSym}\n"
writer.write(f".global g_TileLayers\n") # TODO: symbol name hardcoded 🤮
writer.write(f"g_TileLayers:\n{buf}")
class PSXSegLayers(N64Segment):
def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml):
super().__init__(rom_start, rom_end, type, name, vram_start, args, yaml),
def out_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / self.name
def src_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}.layers.json"
def split(self, rom_bytes):
path = self.src_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = self.parse_layers(rom_bytes[self.rom_start : self.rom_end])
with open(path, "w") as f:
f.write(json.dumps(data, indent=4))
def get_symbol(self, offset):
if offset == 0:
return 0
symbol = super().get_symbol(offset)
if symbol == None or symbol.given_name == None:
return f"D_{offset:08X}"
return symbol.given_name
def parse_layers(self, data: bytearray):
err = Exception(f"unable to decode '{self.name}'")
roomCount = 0
ram_start = self.vram_start
ram_end = self.vram_start + self.rom_end - self.rom_start
roomOff = len(data) - 8
if roomOff < 0:
log.error(f"data for '{self.name}' is to small")
# Since we do not know beforehand how many rooms there are (we do, but
# we want it to be automatic), this algo performs validation, room
# count and defines exactly where is the starting LayerDef and
# the starting RoomDef. This is possible because the list of LayerDef
# always comes right before RoomDef and it has a different structure.
layerDefOffsets = set({})
roomDefStart = ram_start
while True:
fgPtr = utils.to_u32(data[roomOff:])
bgPtr = utils.to_u32(data[roomOff + 4 :])
if (
bgPtr < 0x80180000
or fgPtr >= 0x801E0000
or bgPtr < 0x80180000
or fgPtr >= 0x801E0000
or bgPtr >= ram_end
or fgPtr >= ram_end
):
log.error(
f"data for '{self.name}' does not describe a set of room layers"
)
raise err
if fgPtr < ram_start or bgPtr < ram_start:
min_start = min(fgPtr, bgPtr)
log.error(
f"data for '{self.name}' needs to start at least from 0x{min_start:X}"
)
raise err
layerDefOffsets.add(fgPtr)
layerDefOffsets.add(bgPtr)
roomCount += 1
roomDefStart = max(roomDefStart, max(fgPtr, bgPtr))
if ram_start + roomOff - 16 == roomDefStart:
break
roomOff -= 8
if roomOff <= 0:
log.error(
f"data for '{self.name}' needs to start at least from 0x{min_start:X}"
)
raise err
# Creates a dictionary of layers, so we can re-use them when a layer is
# used by multiple rooms.
layerPool = {}
for off in layerDefOffsets:
layerDefData = data[off - ram_start :]
dataOff = utils.to_u32(layerDefData[0:])
tiledefOff = utils.to_u32(layerDefData[4:])
flags = utils.to_u32(layerDefData[8:])
if dataOff != 0 or tiledefOff != 0 or flags != 0:
layerPool[off] = {
"data": self.get_symbol(dataOff),
"tiledef": self.get_symbol(tiledefOff),
"left": flags & 0x3F,
"top": (flags >> 6) & 0x3F,
"right": (flags >> 12) & 0x3F,
"bottom": (flags >> 18) & 0x3F,
"scrollMode": (flags >> 24) & 0x1F,
"isSaveRoom": ((flags >> 24) & 0x20) != 0,
"isLoadingRoom": ((flags >> 24) & 0x40) != 0,
"unusedFlag": ((flags >> 24) & 0x80) != 0,
"zPriority": utils.to_u16(layerDefData[12:]),
"unkE": utils.to_u8(layerDefData[14:]),
"unkF": utils.to_u8(layerDefData[15:]),
}
else:
layerPool[off] = None
# NOTE: I am not sure if LayerDef{0, 0, 0, 0, 0, 0} comes always first
# or it is just a coincidence due to the fact the first room of the
# two stage I analalysed always contains an empty bg??
rooms = []
for i in range(0, roomCount):
room = {}
fg = layerPool[utils.to_u32(data[roomOff:])]
if fg != None:
room["fg"] = fg
bg = layerPool[utils.to_u32(data[roomOff + 4 :])]
if bg != None:
room["bg"] = bg
rooms.append(room)
roomOff += 8
return rooms
if __name__ == "__main__":
def get_file_name(full_path):
file_name = os.path.basename(full_path)
exts = os.path.splitext(file_name)
if len(exts) > 1 and len(exts[1]) > 0:
return get_file_name(exts[0])
return exts[0]
input_file_name = sys.argv[1]
output_file_name = sys.argv[2]
with open(input_file_name, "r") as f_in:
name = get_file_name(input_file_name)
with open(output_file_name, "w") as f_out:
generate_assembly_layers(f_out, name, f_in.read())

View File

@ -1,85 +0,0 @@
#!/usr/bin/python3
import json
import os
import sys
from typing import Optional
from pathlib import Path
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from splat.util import options, log
from splat.segtypes.n64.segment import N64Segment
import utils
item_size = 0xA # sizeof(LayoutEntity)
def parse_layoutobj(data: bytearray) -> list:
count = int(len(data) / item_size)
items = []
for i in range(0, count):
item = {
"x": utils.to_s16(data[i * item_size + 0 :]),
"y": utils.to_s16(data[i * item_size + 2 :]),
"entityId": utils.to_s16(data[i * item_size + 4 :]),
"entityRoomIndex": utils.to_s16(data[i * item_size + 6 :]),
"subId": utils.to_s16(data[i * item_size + 8 :]),
}
if item["x"] == -1 and item["y"] == -1:
break
items.append(item)
return items
def serialize_layoutobj(content: str) -> bytearray:
def align(value: int, align: int):
return value + (value % align)
def from_16(dst: bytearray, idx, value: int):
dst[idx + 0] = value & 0xFF
dst[idx + 1] = (value >> 8) & 0xFF
obj = json.loads(content)
item_count = len(obj)
byte_size = align((item_count + 1) * item_size, 4)
data = bytearray(byte_size)
for i in range(0, item_count):
item = obj[i]
from_16(data, i * item_size + 0, item["x"])
from_16(data, i * item_size + 2, item["y"])
from_16(data, i * item_size + 4, item["entityId"])
from_16(data, i * item_size + 6, item["entityRoomIndex"])
from_16(data, i * item_size + 8, item["subId"])
from_16(data, item_count * item_size + 0, -1)
from_16(data, item_count * item_size + 2, -1)
return data
class PSXSegLayoutobj(N64Segment):
def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml):
super().__init__(rom_start, rom_end, type, name, vram_start, args, yaml),
def out_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / self.name
def src_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}.layoutobj.json"
def split(self, rom_bytes):
path = self.src_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = parse_layoutobj(rom_bytes[self.rom_start : self.rom_end])
with open(path, "w") as f:
f.write(json.dumps(data, indent=4))
if __name__ == "__main__":
input_file_name = sys.argv[1]
output_file_name = sys.argv[2]
with open(input_file_name, "r") as f_in:
data = serialize_layoutobj(f_in.read())
with open(output_file_name, "wb") as f_out:
f_out.write(data)

View File

@ -1,97 +0,0 @@
#!/usr/bin/python3
import json
import os
import sys
from typing import Optional
from pathlib import Path
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from splat.util import options, log
from splat.segtypes.n64.segment import N64Segment
item_size = 0x8 # sizeof(RoomHeader)
def serialize_roomdef(content: str) -> bytearray:
obj = json.loads(content)
item_count = len(obj)
byte_size = item_count * item_size + 4
data = bytearray(byte_size)
for i in range(0, item_count):
item = obj[i]
data[i * item_size + 0] = item["left"]
data[i * item_size + 1] = item["top"]
data[i * item_size + 2] = item["right"]
data[i * item_size + 3] = item["bottom"]
data[i * item_size + 4] = item["tileLayoutId"]
data[i * item_size + 5] = item["tilesetId"]
data[i * item_size + 6] = item["objGfxId"]
data[i * item_size + 7] = item["objLayoutId"]
data[byte_size - 4] = 0x40 # marks the end of the room array
return data
class PSXSegRoomdef(N64Segment):
def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml):
super().__init__(rom_start, rom_end, type, name, vram_start, args, yaml),
def out_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / self.name
def src_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}.roomdef.json"
def split(self, rom_bytes):
path = self.src_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = self.parse_roomdef(rom_bytes[self.rom_start : self.rom_end])
with open(path, "w") as f:
f.write(json.dumps(data, indent=4))
def parse_roomdef(self, data: bytearray) -> list:
count = int(len(data) / item_size)
expected_data_size = count * item_size + 4
if len(data) != expected_data_size:
log.write(
f"data for '{self.name}' is {expected_data_size - len(data)} too long. Data might look incorrect.",
status="warn",
)
items = []
for i in range(0, count):
if data[i * item_size + 0] == 64:
log.write(
f"data for '{self.name}' includes the array terminator. Try reducing the size of the subsegment.",
status="warn",
)
item = {
"left": data[i * item_size + 0],
"top": data[i * item_size + 1],
"right": data[i * item_size + 2],
"bottom": data[i * item_size + 3],
"tileLayoutId": data[i * item_size + 4],
"tilesetId": data[i * item_size + 5],
"objGfxId": data[i * item_size + 6],
"objLayoutId": data[i * item_size + 7],
}
items.append(item)
if data[count * item_size] != 64:
log.write(
f"data for '{self.name}' does not end with a terminator.", status="warn"
)
return items
if __name__ == "__main__":
input_file_name = sys.argv[1]
output_file_name = sys.argv[2]
with open(input_file_name, "r") as f_in:
data = serialize_roomdef(f_in.read())
with open(output_file_name, "wb") as f_out:
f_out.write(data)

View File

@ -1,129 +0,0 @@
#!/usr/bin/python3
import ctypes
import json
import io
import os
import sys
from typing import Optional
from pathlib import Path
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from splat.util import options
from splat.segtypes.n64.segment import N64Segment
from splat.util.symbols import spim_context
import utils
def generate_assembly_tiledef(
writer: io.BufferedWriter, base_dir: str, name: str, content: str
):
def incbin(writer: io.BufferedWriter, path: str):
with open(path, "rb") as f:
for ch in f.read():
writer.write(f".byte {int(ch)}\n")
obj = json.loads(content)
symbol_name = obj["name"]
gfxPage = obj["gfxPage"]
gfxIndex = obj["gfxIndex"]
clut = obj["clut"]
collision = obj["collision"]
writer.write(".section .data\n")
writer.write(f"gfxPage:\n")
incbin(writer, f"{base_dir}/{gfxPage}")
writer.write(f"gfxIndex:\n")
incbin(writer, f"{base_dir}/{gfxIndex}")
writer.write(f"clut:\n")
incbin(writer, f"{base_dir}/{clut}")
writer.write(f"collision:\n")
incbin(writer, f"{base_dir}/{collision}")
writer.write(f".global {symbol_name}\n")
writer.write(f"{symbol_name}:\n")
writer.write(f".word gfxPage, gfxIndex, clut, collision\n")
class PSXSegTiledef(N64Segment):
def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml):
super().__init__(rom_start, rom_end, type, name, vram_start, args, yaml),
def out_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}"
def src_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}.tiledef.json"
def split(self, rom_bytes):
if len(self.args) != 1:
utils.log_fatal(f"unable to determine the real symbol name for {self.name}")
symbol_name = self.args[0]
out_file_name = self.src_path()
out_file_name.parent.mkdir(parents=True, exist_ok=True)
header_data = rom_bytes[self.rom_end - 0x10 : self.rom_end]
gfxPage = utils.to_u32(header_data[0:]) - self.vram_start
gfxIndex = utils.to_u32(header_data[4:]) - self.vram_start
clut = utils.to_u32(header_data[8:]) - self.vram_start
collision = utils.to_u32(header_data[12:]) - self.vram_start
header_start = self.rom_end - 0x10 - self.rom_start
expected_start = min(gfxPage, min(gfxIndex, min(clut, collision)))
if expected_start != 0:
expected_start += self.rom_start
actual_start = self.rom_start
utils.log_fatal(
f"{self.name} should start from 0x{expected_start:X} but got 0x{actual_start:X}\n"
f"- [0x{expected_start:X}, tiledef, {self.name}]"
)
descriptor = {
"name": symbol_name,
"gfxPage": f"{self.name}.page.bin",
"gfxIndex": f"{self.name}.tile.bin",
"clut": f"{self.name}.clut.bin",
"collision": f"{self.name}.col.bin",
}
file_sizes = [
gfxIndex - gfxPage,
clut - gfxIndex,
collision - clut,
header_start - collision,
]
raw_data = rom_bytes[self.rom_start : self.rom_end]
out_dir = options.opts.asset_path / self.dir
with open(out_dir / descriptor["gfxPage"], "wb") as f:
f.write(raw_data[gfxPage:][: file_sizes[0]])
with open(out_dir / descriptor["gfxIndex"], "wb") as f:
f.write(raw_data[gfxIndex:][: file_sizes[1]])
with open(out_dir / descriptor["clut"], "wb") as f:
f.write(raw_data[clut:][: file_sizes[2]])
with open(out_dir / descriptor["collision"], "wb") as f:
f.write(raw_data[collision:][: file_sizes[3]])
with open(out_file_name, "w") as f:
f.write(json.dumps(descriptor, indent=4))
if __name__ == "__main__":
def get_file_name(full_path):
file_name = os.path.basename(full_path)
exts = os.path.splitext(file_name)
if len(exts) > 1 and len(exts[1]) > 0:
return get_file_name(exts[0])
return exts[0]
input_file_name = sys.argv[1]
output_file_name = sys.argv[2]
with open(input_file_name, "r") as f_in:
base_path = os.path.dirname(input_file_name)
name = get_file_name(input_file_name)
with open(output_file_name, "w") as f_out:
generate_assembly_tiledef(f_out, base_path, name, f_in.read())

View File

@ -1,15 +0,0 @@
import os
import sys
from splat.util import options
from typing import Optional
from pathlib import Path
sys.path.append(f"{os.getcwd()}/{options.opts.extensions_path}")
from raw import PSXSegRaw
class PSXSegTilelayout(PSXSegRaw):
pass
def src_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}.tilelayout.bin"