1
0
mirror of https://github.com/zeldaret/oot.git synced 2024-09-21 04:24:43 +00:00

Setup rom decompression and compression (#1614)

* decompress baserom

* cleanup

* specific hash check

* rename baserom

* git subrepo clone (merge) --branch=5da3132606e4fd427a8d72b8428e4f921cd6e56f git@github.com:z64tools/z64compress.git tools/z64compress

subrepo:
  subdir:   "tools/z64compress"
  merged:   "5da313260"
upstream:
  origin:   "git@github.com:z64tools/z64compress.git"
  branch:   "5da3132606e4fd427a8d72b8428e4f921cd6e56f"
  commit:   "5da313260"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"

* setup compression

* Add all compressed segments to the spec

* Update md5 files

* readme instructions

* cleanup

* Setup python dependencies on Jenkinsfile

* Update Makefile

Co-authored-by: cadmic <cadmic24@gmail.com>

* review

* . .venv/bin/activate

* update readme

* whoops

* Yeet other versions from decompress_baserom.py

* my bad

* Move everything to baseroms/VERSION/

* Active venv on each command

* jenkinsfile: use multiline syntax

* Put the correct path on the jenkinsfile

* Forgot to call per_version_fixes

* CC0

* Update readme

* Change where baserom segments are put into

* Update Makefile

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

* Update crunch64

* Label compressed instead of uncompressed

* Update README.md

Co-authored-by: fig02 <fig02srl@gmail.com>

* Fix

* `make rom`

* baserom-uncompressed

* Update README.md

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

* Update README.md

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

* Update README.md

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

* Update README.md

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

* Update README.md

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

* review

* baserom-decompressed.z64

* ignore baseroms

* rm -rf tools/z64compress

* wip crunch64-based compress.py

* OK compress

* use ipl3checksum sum directly for cic update on compressed rom

* multithreading

* "docs"

* fix MT: move set_sigint_ignored to global level for pickling

* license compress.py

* rm junk

* Fix (or at least sort out) compress_ranges.txt dependencies

* Update tools/overlayhelpers/damage_table.py

Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>

---------

Co-authored-by: cadmic <cadmic24@gmail.com>
Co-authored-by: Dragorn421 <Dragorn421@users.noreply.github.com>
Co-authored-by: fig02 <fig02srl@gmail.com>
This commit is contained in:
Anghelo Carvajal 2024-01-24 15:00:10 -03:00 committed by GitHub
parent 5e406f754e
commit 3d9db8d34d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 2282 additions and 187 deletions

3
.gitignore vendored
View File

@ -10,12 +10,14 @@ __pycache__/
CMakeLists.txt CMakeLists.txt
cmake-build-debug cmake-build-debug
venv/ venv/
.venv/
# Project-specific ignores # Project-specific ignores
build/ build/
expected/ expected/
notes/ notes/
baserom/ baserom/
baseroms/*/segments/
docs/doxygen/ docs/doxygen/
*.elf *.elf
*.sra *.sra
@ -25,6 +27,7 @@ docs/doxygen/
*.map *.map
*.dump *.dump
out.txt out.txt
*.ram
# Tool artifacts # Tool artifacts
tools/mipspro7.2_compiler/ tools/mipspro7.2_compiler/

23
Jenkinsfile vendored
View File

@ -4,10 +4,21 @@ pipeline {
} }
stages { stages {
stage('Install Python dependencies') {
steps {
echo 'Installing Python dependencies'
sh 'python3 -m venv .venv'
sh '''. .venv/bin/activate
python3 -m pip install -U -r requirements.txt
'''
}
}
stage('Setup') { stage('Setup') {
steps { steps {
sh 'cp /usr/local/etc/roms/baserom_oot.z64 baserom_original.z64' sh 'cp /usr/local/etc/roms/baserom_oot.z64 baseroms/gc-eu-mq-dbg/baserom.z64'
sh 'make -j setup' sh '''. .venv/bin/activate
make -j setup
'''
} }
} }
stage('Build (qemu-irix)') { stage('Build (qemu-irix)') {
@ -15,7 +26,9 @@ pipeline {
branch 'main' branch 'main'
} }
steps { steps {
sh 'ORIG_COMPILER=1 make -j' sh '''. .venv/bin/activate
make -j ORIG_COMPILER=1
'''
} }
} }
stage('Build') { stage('Build') {
@ -25,7 +38,9 @@ pipeline {
} }
} }
steps { steps {
sh 'make -j' sh '''. .venv/bin/activate
make -j
'''
} }
} }
stage('Report Progress') { stage('Report Progress') {

View File

@ -17,6 +17,8 @@ COMPILER := ido
# Target game version. Currently only the following version is supported: # Target game version. Currently only the following version is supported:
# gc-eu-mq-dbg GameCube Europe/PAL Master Quest Debug (default) # gc-eu-mq-dbg GameCube Europe/PAL Master Quest Debug (default)
VERSION := gc-eu-mq-dbg VERSION := gc-eu-mq-dbg
# Number of threads to extract and compress with
N_THREADS := $(shell nproc)
CFLAGS ?= CFLAGS ?=
CPPFLAGS ?= CPPFLAGS ?=
@ -72,8 +74,6 @@ else
endif endif
endif endif
N_THREADS := $(shell nproc)
#### Tools #### #### Tools ####
ifneq ($(shell type $(MIPS_BINUTILS_PREFIX)ld >/dev/null 2>/dev/null; echo $$?), 0) ifneq ($(shell type $(MIPS_BINUTILS_PREFIX)ld >/dev/null 2>/dev/null; echo $$?), 0)
$(error Unable to find $(MIPS_BINUTILS_PREFIX)ld. Please install or build MIPS binutils, commonly mips-linux-gnu. (or set MIPS_BINUTILS_PREFIX if your MIPS binutils install uses another prefix)) $(error Unable to find $(MIPS_BINUTILS_PREFIX)ld. Please install or build MIPS binutils, commonly mips-linux-gnu. (or set MIPS_BINUTILS_PREFIX if your MIPS binutils install uses another prefix))
@ -105,6 +105,7 @@ AS := $(MIPS_BINUTILS_PREFIX)as
LD := $(MIPS_BINUTILS_PREFIX)ld LD := $(MIPS_BINUTILS_PREFIX)ld
OBJCOPY := $(MIPS_BINUTILS_PREFIX)objcopy OBJCOPY := $(MIPS_BINUTILS_PREFIX)objcopy
OBJDUMP := $(MIPS_BINUTILS_PREFIX)objdump OBJDUMP := $(MIPS_BINUTILS_PREFIX)objdump
NM := $(MIPS_BINUTILS_PREFIX)nm
N64_EMULATOR ?= N64_EMULATOR ?=
@ -119,6 +120,7 @@ MKDMADATA := tools/mkdmadata
ELF2ROM := tools/elf2rom ELF2ROM := tools/elf2rom
ZAPD := tools/ZAPD/ZAPD.out ZAPD := tools/ZAPD/ZAPD.out
FADO := tools/fado/fado.elf FADO := tools/fado/fado.elf
PYTHON ?= python3
# Command to replace path variables in the spec file. We can't use the C # Command to replace path variables in the spec file. We can't use the C
# preprocessor for this because it won't substitute inside string literals. # preprocessor for this because it won't substitute inside string literals.
@ -161,6 +163,7 @@ OBJDUMP_FLAGS := -d -r -z -Mreg-names=32
#### Files #### #### Files ####
# ROM image # ROM image
ROMC := oot-$(VERSION)-compressed.z64
ROM := oot-$(VERSION).z64 ROM := oot-$(VERSION).z64
ELF := $(ROM:.z64=.elf) ELF := $(ROM:.z64=.elf)
# description of ROM segments # description of ROM segments
@ -184,9 +187,10 @@ UNDECOMPILED_DATA_DIRS := $(shell find data -type d)
# source files # source files
C_FILES := $(filter-out %.inc.c,$(foreach dir,$(SRC_DIRS) $(ASSET_BIN_DIRS),$(wildcard $(dir)/*.c))) C_FILES := $(filter-out %.inc.c,$(foreach dir,$(SRC_DIRS) $(ASSET_BIN_DIRS),$(wildcard $(dir)/*.c)))
S_FILES := $(foreach dir,$(SRC_DIRS) $(UNDECOMPILED_DATA_DIRS),$(wildcard $(dir)/*.s)) S_FILES := $(foreach dir,$(SRC_DIRS) $(UNDECOMPILED_DATA_DIRS),$(wildcard $(dir)/*.s))
BASEROM_BIN_FILES := $(wildcard baseroms/$(VERSION)/segments/*)
O_FILES := $(foreach f,$(S_FILES:.s=.o),$(BUILD_DIR)/$f) \ O_FILES := $(foreach f,$(S_FILES:.s=.o),$(BUILD_DIR)/$f) \
$(foreach f,$(C_FILES:.c=.o),$(BUILD_DIR)/$f) \ $(foreach f,$(C_FILES:.c=.o),$(BUILD_DIR)/$f) \
$(foreach f,$(wildcard baserom/*),$(BUILD_DIR)/$f.o) $(foreach f,$(BASEROM_BIN_FILES),$(BUILD_DIR)/baserom/$(notdir $f).o)
OVL_RELOC_FILES := $(shell $(CPP) $(CPPFLAGS) $(SPEC) | $(SPEC_REPLACE_VARS) | grep -o '[^"]*_reloc.o' ) OVL_RELOC_FILES := $(shell $(CPP) $(CPPFLAGS) $(SPEC) | $(SPEC_REPLACE_VARS) | grep -o '[^"]*_reloc.o' )
@ -246,11 +250,11 @@ $(BUILD_DIR)/src/libultra/rmon/%.o: CC := $(CC_OLD)
$(BUILD_DIR)/src/code/jpegutils.o: CC := $(CC_OLD) $(BUILD_DIR)/src/code/jpegutils.o: CC := $(CC_OLD)
$(BUILD_DIR)/src/code/jpegdecoder.o: CC := $(CC_OLD) $(BUILD_DIR)/src/code/jpegdecoder.o: CC := $(CC_OLD)
$(BUILD_DIR)/src/boot/%.o: CC := python3 tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) -- $(BUILD_DIR)/src/boot/%.o: CC := $(PYTHON) tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) --
$(BUILD_DIR)/src/code/%.o: CC := python3 tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) -- $(BUILD_DIR)/src/code/%.o: CC := $(PYTHON) tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) --
$(BUILD_DIR)/src/overlays/%.o: CC := python3 tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) -- $(BUILD_DIR)/src/overlays/%.o: CC := $(PYTHON) tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) --
$(BUILD_DIR)/assets/%.o: CC := python3 tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) -- $(BUILD_DIR)/assets/%.o: CC := $(PYTHON) tools/asm_processor/build.py $(CC) -- $(AS) $(ASFLAGS) --
else else
$(BUILD_DIR)/src/libultra/libc/ll.o: OPTFLAGS := -Ofast $(BUILD_DIR)/src/libultra/libc/ll.o: OPTFLAGS := -Ofast
$(BUILD_DIR)/src/%.o: CC := $(CC) -fexec-charset=euc-jp $(BUILD_DIR)/src/%.o: CC := $(CC) -fexec-charset=euc-jp
@ -258,14 +262,20 @@ endif
#### Main Targets ### #### Main Targets ###
all: $(ROM) rom: $(ROM)
ifeq ($(COMPARE),1) ifneq ($(COMPARE),0)
@md5sum $(ROM) @md5sum $(ROM)
@md5sum -c checksum.md5 @md5sum -c baseroms/$(VERSION)/checksum.md5
endif
compressed: $(ROMC)
ifneq ($(COMPARE),0)
@md5sum $(ROMC)
@md5sum -c baseroms/$(VERSION)/checksum-compressed.md5
endif endif
clean: clean:
$(RM) -r $(ROM) $(ELF) $(BUILD_DIR) $(RM) -r $(ROMC) $(ROM) $(ELF) $(BUILD_DIR)
assetclean: assetclean:
$(RM) -r $(ASSET_BIN_DIRS) $(RM) -r $(ASSET_BIN_DIRS)
@ -274,14 +284,14 @@ assetclean:
$(RM) -r .extracted-assets.json $(RM) -r .extracted-assets.json
distclean: clean assetclean distclean: clean assetclean
$(RM) -r baserom/ $(RM) -r baseroms/$(VERSION)/segments
$(MAKE) -C tools distclean $(MAKE) -C tools distclean
setup: setup:
$(MAKE) -C tools $(MAKE) -C tools
python3 fixbaserom.py $(PYTHON) tools/decompress_baserom.py $(VERSION)
python3 extract_baserom.py $(PYTHON) extract_baserom.py
python3 extract_assets.py -j$(N_THREADS) $(PYTHON) extract_assets.py -j$(N_THREADS)
run: $(ROM) run: $(ROM)
ifeq ($(N64_EMULATOR),) ifeq ($(N64_EMULATOR),)
@ -290,13 +300,20 @@ endif
$(N64_EMULATOR) $< $(N64_EMULATOR) $<
.PHONY: all clean setup run distclean assetclean .PHONY: all rom compressed clean setup run distclean assetclean
.DEFAULT_GOAL := rom
all: rom compressed
#### Various Recipes #### #### Various Recipes ####
$(ROM): $(ELF) $(ROM): $(ELF)
$(ELF2ROM) -cic 6105 $< $@ $(ELF2ROM) -cic 6105 $< $@
$(ROMC): $(ROM) $(ELF) $(BUILD_DIR)/compress_ranges.txt
# note: $(BUILD_DIR)/compress_ranges.txt should only be used for nonmatching builds. it works by chance for matching builds too though
$(PYTHON) tools/compress.py --in $(ROM) --out $@ --dma-range `./tools/dmadata_range.sh $(NM) $(ELF)` --compress `cat $(BUILD_DIR)/compress_ranges.txt` --threads $(N_THREADS)
$(PYTHON) -m ipl3checksum sum --cic 6105 --update $@
$(ELF): $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT) $(O_FILES) $(OVL_RELOC_FILES) $(BUILD_DIR)/ldscript.txt $(BUILD_DIR)/undefined_syms.txt $(ELF): $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT) $(O_FILES) $(OVL_RELOC_FILES) $(BUILD_DIR)/ldscript.txt $(BUILD_DIR)/undefined_syms.txt
$(LD) -T $(BUILD_DIR)/undefined_syms.txt -T $(BUILD_DIR)/ldscript.txt --no-check-sections --accept-unknown-input-arch --emit-relocs -Map $(BUILD_DIR)/z64.map -o $@ $(LD) -T $(BUILD_DIR)/undefined_syms.txt -T $(BUILD_DIR)/ldscript.txt --no-check-sections --accept-unknown-input-arch --emit-relocs -Map $(BUILD_DIR)/z64.map -o $@
@ -321,14 +338,14 @@ $(BUILD_DIR)/ldscript.txt: $(BUILD_DIR)/$(SPEC)
$(BUILD_DIR)/undefined_syms.txt: undefined_syms.txt $(BUILD_DIR)/undefined_syms.txt: undefined_syms.txt
$(CPP) $(CPPFLAGS) $< > $@ $(CPP) $(CPPFLAGS) $< > $@
$(BUILD_DIR)/baserom/%.o: baserom/% $(BUILD_DIR)/baserom/%.o: baseroms/$(VERSION)/segments/%
$(OBJCOPY) -I binary -O elf32-big $< $@ $(OBJCOPY) -I binary -O elf32-big $< $@
$(BUILD_DIR)/data/%.o: data/%.s $(BUILD_DIR)/data/%.o: data/%.s
$(AS) $(ASFLAGS) $< -o $@ $(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/assets/text/%.enc.h: assets/text/%.h assets/text/charmap.txt $(BUILD_DIR)/assets/text/%.enc.h: assets/text/%.h assets/text/charmap.txt
python3 tools/msgenc.py assets/text/charmap.txt $< $@ $(PYTHON) tools/msgenc.py assets/text/charmap.txt $< $@
# Dependencies for files including message data headers # Dependencies for files including message data headers
# TODO remove when full header dependencies are used. # TODO remove when full header dependencies are used.
@ -345,8 +362,8 @@ $(BUILD_DIR)/assets/%.o: assets/%.c
$(BUILD_DIR)/src/%.o: src/%.s $(BUILD_DIR)/src/%.o: src/%.s
$(CPP) $(CPPFLAGS) -Iinclude $< | $(AS) $(ASFLAGS) -o $@ $(CPP) $(CPPFLAGS) -Iinclude $< | $(AS) $(ASFLAGS) -o $@
$(BUILD_DIR)/dmadata_table_spec.h: $(BUILD_DIR)/$(SPEC) $(BUILD_DIR)/dmadata_table_spec.h $(BUILD_DIR)/compress_ranges.txt: $(BUILD_DIR)/$(SPEC)
$(MKDMADATA) $< $@ $(MKDMADATA) $< $(BUILD_DIR)/dmadata_table_spec.h $(BUILD_DIR)/compress_ranges.txt
# Dependencies for files that may include the dmadata header automatically generated from the spec file # Dependencies for files that may include the dmadata header automatically generated from the spec file
$(BUILD_DIR)/src/boot/z_std_dma.o: $(BUILD_DIR)/dmadata_table_spec.h $(BUILD_DIR)/src/boot/z_std_dma.o: $(BUILD_DIR)/dmadata_table_spec.h
@ -370,13 +387,13 @@ $(BUILD_DIR)/src/%.o: src/%.c
$(BUILD_DIR)/src/libultra/libc/ll.o: src/libultra/libc/ll.c $(BUILD_DIR)/src/libultra/libc/ll.o: src/libultra/libc/ll.c
$(CC_CHECK) $< $(CC_CHECK) $<
$(CC) -c $(CFLAGS) $(MIPS_VERSION) $(OPTFLAGS) -o $@ $< $(CC) -c $(CFLAGS) $(MIPS_VERSION) $(OPTFLAGS) -o $@ $<
python3 tools/set_o32abi_bit.py $@ $(PYTHON) tools/set_o32abi_bit.py $@
@$(OBJDUMP) $(OBJDUMP_FLAGS) $@ > $(@:.o=.s) @$(OBJDUMP) $(OBJDUMP_FLAGS) $@ > $(@:.o=.s)
$(BUILD_DIR)/src/libultra/libc/llcvt.o: src/libultra/libc/llcvt.c $(BUILD_DIR)/src/libultra/libc/llcvt.o: src/libultra/libc/llcvt.c
$(CC_CHECK) $< $(CC_CHECK) $<
$(CC) -c $(CFLAGS) $(MIPS_VERSION) $(OPTFLAGS) -o $@ $< $(CC) -c $(CFLAGS) $(MIPS_VERSION) $(OPTFLAGS) -o $@ $<
python3 tools/set_o32abi_bit.py $@ $(PYTHON) tools/set_o32abi_bit.py $@
@$(OBJDUMP) $(OBJDUMP_FLAGS) $@ > $(@:.o=.s) @$(OBJDUMP) $(OBJDUMP_FLAGS) $@ > $(@:.o=.s)
$(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/$(SPEC) $(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/$(SPEC)
@ -393,3 +410,6 @@ $(BUILD_DIR)/assets/%.jpg.inc.c: assets/%.jpg
$(ZAPD) bren -eh -i $< -o $@ $(ZAPD) bren -eh -i $< -o $@
-include $(DEP_FILES) -include $(DEP_FILES)
# Print target for debugging
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true

View File

@ -68,13 +68,15 @@ The build process has the following package requirements:
* build-essential * build-essential
* binutils-mips-linux-gnu * binutils-mips-linux-gnu
* python3 * python3
* python3-pip
* python3-venv
* libpng-dev * libpng-dev
Under Debian / Ubuntu (which we recommend using), you can install them with the following commands: Under Debian / Ubuntu (which we recommend using), you can install them with the following commands:
```bash ```bash
sudo apt-get update sudo apt-get update
sudo apt-get install git build-essential binutils-mips-linux-gnu python3 libpng-dev sudo apt-get install git build-essential binutils-mips-linux-gnu python3 python3-pip python3-venv libpng-dev
``` ```
If you are using GCC as the compiler for Ocarina of Time, you will also need: If you are using GCC as the compiler for Ocarina of Time, you will also need:
@ -98,12 +100,37 @@ This will copy the GitHub repository contents into a new folder in the current d
cd oot cd oot
``` ```
#### 3. Prepare a base ROM #### 3. Install python dependencies
Copy over your copy of the Master Quest (Debug) ROM inside the root of this new project directory. The build process has a few python packages required that are located in `requirements.txt`.
Rename the file to "baserom_original.z64", "baserom_original.n64" or "baserom_original.v64", depending on the original extension.
#### 4. Setup the ROM and build process It is recommended to set up a virtual environment for python to contain all dependencies. To create a virtual environment:
```bash
python3 -m venv .venv
```
To start using the virtual environment in your current terminal run:
```bash
. .venv/bin/activate
```
Keep in mind that for each new terminal session, you will need to **activate** the Python virtual environment again. That is, run the above `. .venv/bin/activate` command.
Now you can install the Python dependencies, to do so run:
```bash
python3 -m pip install -U -r requirements.txt
```
#### 4. Prepare a base ROM
Place a copy of the Master Quest (Debug) ROM inside the `baseroms/gc-eu-mq-dbg/` folder.
Rename the file to `baserom.z64`, `baserom.n64` or `baserom.v64`, depending on the original extension.
#### 5. Setup the ROM and build process
Setup and extract everything from your ROM with the following command: Setup and extract everything from your ROM with the following command:
@ -111,10 +138,10 @@ Setup and extract everything from your ROM with the following command:
make setup make setup
``` ```
This will generate a new ROM called "baserom.z64" that will have the overdump removed and the header patched. This will generate a new ROM "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64" that will have the overdump removed and the header patched.
It will also extract the individual assets from the ROM. It will also extract the individual assets from the ROM.
#### 5. Build the ROM #### 6. Build the ROM
Run make to build the ROM. Run make to build the ROM.
Make sure your path to the project is not too long, otherwise this process may error. Make sure your path to the project is not too long, otherwise this process may error.
@ -145,7 +172,6 @@ This means that the built ROM isn't the same as the base one, so something went
Both of these have the disadvantage that the ordering of the terminal output is scrambled, so for debugging it is best to stick to one thread (i.e. not pass `-j` or `-jN`). Both of these have the disadvantage that the ordering of the terminal output is scrambled, so for debugging it is best to stick to one thread (i.e. not pass `-j` or `-jN`).
## Contributing ## Contributing
All contributions are welcome. This is a group effort, and even small contributions can make a difference. All contributions are welcome. This is a group effort, and even small contributions can make a difference.

View File

@ -0,0 +1 @@
5831385a7f216370cdbea55616b12fed oot-gc-eu-mq-dbg-compressed.z64

View File

@ -10,13 +10,13 @@
.balign 16 .balign 16
glabel gSoundFontTable glabel gSoundFontTable
.incbin "baserom.z64", 0xBCC270, 0x270 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCC270, 0x270
glabel gSequenceFontTable glabel gSequenceFontTable
.incbin "baserom.z64", 0xBCC4E0, 0x1C0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCC4E0, 0x1C0
glabel gSequenceTable glabel gSequenceTable
.incbin "baserom.z64", 0xBCC6A0, 0x6F0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCC6A0, 0x6F0
glabel gSampleBankTable glabel gSampleBankTable
.incbin "baserom.z64", 0xBCCD90, 0x80 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCCD90, 0x80

View File

@ -10,21 +10,21 @@
.balign 16 .balign 16
glabel aspMainDataStart glabel aspMainDataStart
.incbin "baserom.z64", 0xBCCE10, 0x2E0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCCE10, 0x2E0
glabel aspMainDataEnd glabel aspMainDataEnd
glabel gspF3DZEX2_NoN_PosLight_fifoTextStart glabel gspF3DZEX2_NoN_PosLight_fifoTextStart
.incbin "baserom.z64", 0xBCD0F0, 0x1630 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCD0F0, 0x1630
glabel gspF3DZEX2_NoN_PosLight_fifoTextEnd glabel gspF3DZEX2_NoN_PosLight_fifoTextEnd
glabel gspF3DZEX2_NoN_PosLight_fifoDataStart glabel gspF3DZEX2_NoN_PosLight_fifoDataStart
.incbin "baserom.z64", 0xBCE720, 0x420 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCE720, 0x420
glabel gspF3DZEX2_NoN_PosLight_fifoDataEnd glabel gspF3DZEX2_NoN_PosLight_fifoDataEnd
glabel gspS2DEX2d_fifoDataStart glabel gspS2DEX2d_fifoDataStart
.incbin "baserom.z64", 0xBCEB40, 0x390 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCEB40, 0x390
glabel gspS2DEX2d_fifoDataEnd glabel gspS2DEX2d_fifoDataEnd
glabel njpgdspMainDataStart glabel njpgdspMainDataStart
.incbin "baserom.z64", 0xBCEED0, 0x60 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBCEED0, 0x60
glabel njpgdspMainDataEnd glabel njpgdspMainDataEnd

View File

@ -10,13 +10,13 @@
.balign 16 .balign 16
glabel aspMainTextStart glabel aspMainTextStart
.incbin "baserom.z64", 0xB89260, 0xFB0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xB89260, 0xFB0
glabel aspMainTextEnd glabel aspMainTextEnd
glabel gspS2DEX2d_fifoTextStart glabel gspS2DEX2d_fifoTextStart
.incbin "baserom.z64", 0xB8A210, 0x18C0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xB8A210, 0x18C0
glabel gspS2DEX2d_fifoTextEnd glabel gspS2DEX2d_fifoTextEnd
glabel njpgdspMainTextStart glabel njpgdspMainTextStart
.incbin "baserom.z64", 0xB8BAD0, 0xAF0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xB8BAD0, 0xAF0
glabel njpgdspMainTextEnd glabel njpgdspMainTextEnd

View File

@ -10,5 +10,5 @@
.balign 16 .balign 16
glabel rspbootTextStart glabel rspbootTextStart
.incbin "baserom.z64", 0x9F20, 0xD0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0x9F20, 0xD0
glabel rspbootTextEnd glabel rspbootTextEnd

View File

@ -12,7 +12,7 @@
# temporary file name, rename to something more appropriate when decompiled # temporary file name, rename to something more appropriate when decompiled
glabel gMojiFontTLUTs glabel gMojiFontTLUTs
.incbin "baserom.z64", 0xBA18E0, 0x80 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBA18E0, 0x80
glabel gMojiFontTex glabel gMojiFontTex
.incbin "baserom.z64", 0xBA1960, 0x400 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0xBA1960, 0x400

View File

@ -1,6 +1,6 @@
def apply(config, args): def apply(config, args):
config['mapfile'] = 'build/gc-eu-mq-dbg/z64.map' config['mapfile'] = 'build/gc-eu-mq-dbg/z64.map'
config['myimg'] = 'oot-gc-eu-mq-dbg.z64' config['myimg'] = 'oot-gc-eu-mq-dbg.z64'
config['baseimg'] = 'baserom.z64' config['baseimg'] = 'baseroms/gc-eu-mq-dbg/baserom-decompressed.z64'
config['makeflags'] = [] config['makeflags'] = []
config['source_directories'] = ['src', 'include', 'spec'] config['source_directories'] = ['src', 'include', 'spec']

View File

@ -229,7 +229,7 @@ which initialises common properties of actor using an InitChain, which is usuall
The InitChain script is also in the tools directory, and is called `ichaindis.py`. Simply passing it the ROM address will spit out the entire contents of the InitChain, in this case: The InitChain script is also in the tools directory, and is called `ichaindis.py`. Simply passing it the ROM address will spit out the entire contents of the InitChain, in this case:
``` ```
$ ./tools/ichaindis.py baserom.z64 80A88CE0 $ ./tools/ichaindis.py baseroms/gc-eu-mq-dbg/baserom-decompressed.z64 80A88CE0
static InitChainEntry sInitChain[] = { static InitChainEntry sInitChain[] = {
ICHAIN_VEC3F_DIV1000(unk_50, 87, ICHAIN_CONTINUE), ICHAIN_VEC3F_DIV1000(unk_50, 87, ICHAIN_CONTINUE),
ICHAIN_F32(unk_F4, 4000, ICHAIN_CONTINUE), ICHAIN_F32(unk_F4, 4000, ICHAIN_CONTINUE),
@ -240,7 +240,7 @@ static InitChainEntry sInitChain[] = {
However, some of these variables have now been given names in the Actor struct. Pass it `--names` to fill these in automatically: However, some of these variables have now been given names in the Actor struct. Pass it `--names` to fill these in automatically:
``` ```
$ ./tools/ichaindis.py --names baserom.z64 80A88CE0 $ ./tools/ichaindis.py --names baseroms/gc-eu-mq-dbg/baserom-decompressed.z64 80A88CE0
static InitChainEntry sInitChain[] = { static InitChainEntry sInitChain[] = {
ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE), ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),

View File

@ -557,7 +557,7 @@ Ignore the first line: `gDmaDataTable` is always different if the ROM is shifted
To fix this, we use a binary diff program. A suitable one is `vbindiff`: run it on the baserom and the zelda_whatever one the compiler generates: To fix this, we use a binary diff program. A suitable one is `vbindiff`: run it on the baserom and the zelda_whatever one the compiler generates:
``` ```
vbindiff baserom.z64 oot-gc-eu-mq-dbg.z64 vbindiff baseroms/gc-eu-mq-dbg/baserom-decompressed.z64 oot-gc-eu-mq-dbg.z64
``` ```
In this, press `g` to open up goto position, and paste in the address `0xE3ED10` from the first important line of the `first_diff` output. This gives us the following: In this, press `g` to open up goto position, and paste in the address `0xE3ED10` from the first important line of the `first_diff` output. This gives us the following:

View File

@ -74,7 +74,7 @@ Add the following to (or create) the `.vscode/settings.json` file for VSCode to
"search.useIgnoreFiles": false, "search.useIgnoreFiles": false,
"search.exclude": { "search.exclude": {
"**/.git": true, "**/.git": true,
"baserom/**": true, "baseroms/**": true,
"build/**": true, "build/**": true,
"expected/**": true, "expected/**": true,
}, },

View File

@ -28,7 +28,7 @@ def ExtractFile(xmlPath, outputPath, outputSourcePath):
Path(outputPath).mkdir(parents=True, exist_ok=True) Path(outputPath).mkdir(parents=True, exist_ok=True)
Path(outputSourcePath).mkdir(parents=True, exist_ok=True) Path(outputSourcePath).mkdir(parents=True, exist_ok=True)
execStr = f"{zapdPath} e -eh -i {xmlPath} -b baserom -o {outputPath} -osf {outputSourcePath} -gsf 1 -rconf {configPath} {ZAPDArgs}" execStr = f"{zapdPath} e -eh -i {xmlPath} -b baseroms/gc-eu-mq-dbg/segments -o {outputPath} -osf {outputSourcePath} -gsf 1 -rconf {configPath} {ZAPDArgs}"
if "overlays" in xmlPath: if "overlays" in xmlPath:
execStr += " --static" execStr += " --static"

View File

@ -1,12 +1,12 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os
import sys
import struct import struct
from multiprocessing import Pool, cpu_count from multiprocessing import Pool, cpu_count
from pathlib import Path
ROM_FILE_NAME = 'baserom.z64' ROM_FILE_PATH = Path('baseroms/gc-eu-mq-dbg/baserom-decompressed.z64')
SEGMENTS_PATH = Path('baseroms/gc-eu-mq-dbg/segments/')
FILE_TABLE_OFFSET = 0x12F70 FILE_TABLE_OFFSET = 0x12F70
FILE_NAMES = [ FILE_NAMES = [
@ -1562,7 +1562,7 @@ def write_output_file(name, offset, size):
print('failed to write file ' + name) print('failed to write file ' + name)
def ExtractFunc(i): def ExtractFunc(i):
filename = 'baserom/' + FILE_NAMES[i] filename = SEGMENTS_PATH / FILE_NAMES[i]
entryOffset = FILE_TABLE_OFFSET + 16 * i entryOffset = FILE_TABLE_OFFSET + 16 * i
virtStart = read_uint32_be(entryOffset + 0) virtStart = read_uint32_be(entryOffset + 0)
@ -1571,32 +1571,24 @@ def ExtractFunc(i):
physEnd = read_uint32_be(entryOffset + 12) physEnd = read_uint32_be(entryOffset + 12)
if physEnd == 0: # uncompressed if physEnd == 0: # uncompressed
compressed = False
size = virtEnd - virtStart size = virtEnd - virtStart
else: # compressed else: # compressed
compressed = True
size = physEnd - physStart size = physEnd - physStart
print('extracting ' + filename + " (0x%08X, 0x%08X)" % (virtStart, virtEnd)) print(f'extracting {filename} (0x{virtStart:08X}, 0x{virtEnd:08X})')
write_output_file(filename, physStart, size) write_output_file(filename, physStart, size)
if compressed:
os.system('tools/yaz0 -d ' + filename + ' ' + filename)
##################################################################### #####################################################################
def main(): def main():
try: SEGMENTS_PATH.mkdir(parents=True, exist_ok=True)
os.mkdir('baserom')
except:
pass
# read baserom data # read baserom data
try: try:
with open(ROM_FILE_NAME, 'rb') as f: rom_data = ROM_FILE_PATH.read_bytes()
rom_data = f.read()
except IOError: except IOError:
print('failed to read file' + ROM_FILE_NAME) print(f'failed to read file {ROM_FILE_PATH}')
sys.exit(1) exit(1)
# extract files # extract files
num_cores = cpu_count() num_cores = cpu_count()

View File

@ -49,7 +49,7 @@ def firstDiffMain():
BUILTROM = Path(f"oot-{args.version}.z64") BUILTROM = Path(f"oot-{args.version}.z64")
BUILTMAP = buildFolder / "z64.map" BUILTMAP = buildFolder / "z64.map"
EXPECTEDROM = Path("baserom.z64") EXPECTEDROM = Path(f"baseroms/{args.version}/baserom-decompressed.z64")
EXPECTEDMAP = "expected" / BUILTMAP EXPECTEDMAP = "expected" / BUILTMAP
mapfile_parser.frontends.first_diff.doFirstDiff(BUILTMAP, EXPECTEDMAP, BUILTROM, EXPECTEDROM, args.count, mismatchSize=True, addColons=args.add_colons, bytesConverterCallback=decodeInstruction) mapfile_parser.frontends.first_diff.doFirstDiff(BUILTMAP, EXPECTEDMAP, BUILTROM, EXPECTEDROM, args.count, mismatchSize=True, addColons=args.add_colons, bytesConverterCallback=decodeInstruction)

View File

@ -1,96 +0,0 @@
from os import path
import sys
import struct
import hashlib
def get_str_hash(byte_array):
return str(hashlib.md5(byte_array).hexdigest())
# If the baserom exists and is correct, we don't need to change anything
if path.exists("baserom.z64"):
with open("baserom.z64", mode="rb") as file:
fileContent = bytearray(file.read())
if get_str_hash(fileContent) == "f0b7f35375f9cc8ca1b2d59d78e35405":
print("Found valid baserom - exiting early")
sys.exit(0)
# Determine if we have a ROM file
romFileExtensions = ["z64", "n64", "v64"]
def find_baserom_original():
for romFileExtLower in romFileExtensions:
for romFileExt in (romFileExtLower, romFileExtLower.upper()):
romFileNameCandidate = "baserom_original." + romFileExt
if path.exists(romFileNameCandidate):
return romFileNameCandidate
return None
romFileName = find_baserom_original()
if romFileName is None:
print("Error: Could not find baserom_original.z64/baserom_original.n64/baserom_original.v64.")
sys.exit(1)
# Read in the original ROM
print("File '" + romFileName + "' found.")
with open(romFileName, mode="rb") as file:
fileContent = bytearray(file.read())
# Strip the overdump
print("Stripping overdump...")
fileContent = fileContent[0:0x3600000]
fileContentLen = len(fileContent)
# Check if ROM needs to be byte/word swapped
# Little-endian
if fileContent[0] == 0x40:
# Word Swap ROM
print("ROM needs to be word swapped...")
words = str(int(fileContentLen/4))
little_byte_format = "<" + words + "I"
big_byte_format = ">" + words + "I"
tmp = struct.unpack_from(little_byte_format, fileContent, 0)
struct.pack_into(big_byte_format, fileContent, 0, *tmp)
print("Word swapping done.")
# Byte-swapped
elif fileContent[0] == 0x37:
# Byte Swap ROM
print("ROM needs to be byte swapped...")
halfwords = str(int(fileContentLen/2))
little_byte_format = "<" + halfwords + "H"
big_byte_format = ">" + halfwords + "H"
tmp = struct.unpack_from(little_byte_format, fileContent, 0)
struct.pack_into(big_byte_format, fileContent, 0, *tmp)
print("Byte swapping done.")
# Patch the header
print("Patching header...")
fileContent[0x3E] = 0x50
for i in range(0x35CF000, len(fileContent)):
fileContent[i] = 0xFF
# Check to see if the ROM is a "vanilla" Debug ROM
str_hash = get_str_hash(bytearray(fileContent))
if str_hash != "f0b7f35375f9cc8ca1b2d59d78e35405":
print("Error: Expected a hash of f0b7f35375f9cc8ca1b2d59d78e35405 but got " + str_hash + ". " +
"The baserom has probably been tampered, find a new one")
if str_hash == "32fe2770c0f9b1a9cd2a4d449348c1cb":
print("The provided baserom is a rom which has been edited with ZeldaEdit and is not suitable for use with decomp. " +
"Find a new one.")
sys.exit(1)
# Write out our new ROM
print("Writing new ROM 'baserom.z64'.")
with open("baserom.z64", mode="wb") as file:
file.write(bytes(fileContent))
print("Done!")

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
# Setup and compression
crunch64>=0.3.1,<1.0.0
ipl3checksum>=1.2.0,<2.0.0

1456
spec

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
.section .text .section .text
.incbin "baserom.z64", 0x40, 0xFC0 .incbin "baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", 0x40, 0xFC0

View File

@ -13,7 +13,7 @@ asm_dir = root_dir + "asm/non_matchings/overlays/actors"
build_dir = root_dir + "build/gc-eu-mq-dbg/" build_dir = root_dir + "build/gc-eu-mq-dbg/"
def read_rom(): def read_rom():
with open("baserom.z64", "rb") as f: with open("baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", "rb") as f:
return f.read() return f.read()

342
tools/compress.py Normal file
View File

@ -0,0 +1,342 @@
# SPDX-FileCopyrightText: 2024 zeldaret
# SPDX-License-Identifier: CC0-1.0
from __future__ import annotations
import argparse
from pathlib import Path
import dataclasses
import struct
import time
import multiprocessing
import multiprocessing.pool
import crunch64
STRUCT_IIII = struct.Struct(">IIII")
@dataclasses.dataclass
class DmaEntry:
"""
A Python counterpart to the dmadata entry struct:
```c
typedef struct {
/* 0x00 */ uintptr_t vromStart;
/* 0x04 */ uintptr_t vromEnd;
/* 0x08 */ uintptr_t romStart;
/* 0x0C */ uintptr_t romEnd;
} DmaEntry;
```
"""
vromStart: int
vromEnd: int
romStart: int
romEnd: int
def __repr__(self):
return (
"DmaEntry("
f"vromStart=0x{self.vromStart:08X}, "
f"vromEnd=0x{self.vromEnd:08X}, "
f"romStart=0x{self.romStart:08X}, "
f"romEnd=0x{self.romEnd:08X}"
")"
)
SIZE_BYTES = STRUCT_IIII.size
def to_bin(self, data: memoryview):
STRUCT_IIII.pack_into(
data,
0,
self.vromStart,
self.vromEnd,
self.romStart,
self.romEnd,
)
@staticmethod
def from_bin(data: memoryview):
return DmaEntry(*STRUCT_IIII.unpack_from(data))
DMA_ENTRY_ZERO = DmaEntry(0, 0, 0, 0)
def align(v: int):
v += 0xF
return v // 0x10 * 0x10
@dataclasses.dataclass
class RomSegment:
vromStart: int
vromEnd: int
is_compressed: bool
data: memoryview | None
data_async: multiprocessing.pool.AsyncResult | None
@property
def uncompressed_size(self):
return self.vromEnd - self.vromStart
# Make interrupting the compression with ^C less jank
# https://stackoverflow.com/questions/72967793/keyboardinterrupt-with-python-multiprocessing-pool
def set_sigint_ignored():
import signal
signal.signal(signal.SIGINT, signal.SIG_IGN)
def compress_rom(
rom_data: memoryview,
dmadata_offset_start: int,
dmadata_offset_end: int,
compress_entries_indices: set[int],
n_threads: int = None,
):
"""
rom_data: the uncompressed rom data
dmadata_offset_start: the offset in the rom where the dmadata starts (inclusive)
dmadata_offset_end: the offset in the rom where the dmadata ends (exclusive)
compress_entries_indices: the indices in the dmadata of the segments that should be compressed
n_threads: how many cores to use for compression
"""
# Segments of the compressed rom (not all are compressed)
compressed_rom_segments: list[RomSegment] = []
with multiprocessing.Pool(n_threads, initializer=set_sigint_ignored) as p:
# Extract each segment from the input rom
for entry_index, dmadata_offset in enumerate(
range(dmadata_offset_start, dmadata_offset_end, DmaEntry.SIZE_BYTES)
):
dma_entry = DmaEntry.from_bin(rom_data[dmadata_offset:])
if dma_entry == DMA_ENTRY_ZERO:
continue
segment_rom_start = dma_entry.romStart
segment_rom_end = dma_entry.romStart + (
dma_entry.vromEnd - dma_entry.vromStart
)
segment_data_uncompressed = rom_data[segment_rom_start:segment_rom_end]
is_compressed = entry_index in compress_entries_indices
if is_compressed:
segment_data = None
segment_data_async = p.apply_async(
crunch64.yaz0.compress,
(bytes(segment_data_uncompressed),),
)
else:
segment_data = segment_data_uncompressed
segment_data_async = None
compressed_rom_segments.append(
RomSegment(
dma_entry.vromStart,
dma_entry.vromEnd,
is_compressed,
segment_data,
segment_data_async,
)
)
# Technically optional but required for matching.
compressed_rom_segments.sort(key=lambda segment: segment.vromStart)
# Wait on compression of all compressed segments
waiting_on_segments = [
segment for segment in compressed_rom_segments if segment.is_compressed
]
total_uncompressed_size_of_data_to_compress = sum(
segment.uncompressed_size for segment in waiting_on_segments
)
uncompressed_size_of_data_compressed_so_far = 0
while waiting_on_segments:
# Show progress
progress = (
uncompressed_size_of_data_compressed_so_far
/ total_uncompressed_size_of_data_to_compress
)
print(f"Compressing... {progress * 100:.1f}%", end="\r")
# The segments for which the compression is not finished yet are
# added to this list
still_waiting_on_segments = []
got_some_results = False
for segment in waiting_on_segments:
assert segment.data is None
assert segment.data_async is not None
try:
compressed_data = segment.data_async.get(0)
except multiprocessing.TimeoutError:
# Compression not finished yet
still_waiting_on_segments.append(segment)
else:
# Compression finished!
assert isinstance(compressed_data, bytes)
segment.data = memoryview(compressed_data)
uncompressed_size_of_data_compressed_so_far += (
segment.uncompressed_size
)
got_some_results = True
segment.data_async = None
if not got_some_results and still_waiting_on_segments:
# Nothing happened this wait iteration, idle a bit
time.sleep(0.010)
waiting_on_segments = still_waiting_on_segments
print("Putting together the compressed rom...")
# Put together the compressed rom
compressed_rom_size = sum(
align(len(segment.data)) for segment in compressed_rom_segments
)
pad_to_multiple_of = 8 * 2**20 # 8 MiB
compressed_rom_size_padded = (
(compressed_rom_size + pad_to_multiple_of - 1)
// pad_to_multiple_of
* pad_to_multiple_of
)
compressed_rom_data = memoryview(bytearray(compressed_rom_size_padded))
compressed_rom_dma_entries: list[DmaEntry] = []
rom_offset = 0
for segment in compressed_rom_segments:
assert segment.data is not None
segment_rom_start = rom_offset
segment_rom_end = align(segment_rom_start + len(segment.data))
i = segment_rom_start + len(segment.data)
assert i <= len(compressed_rom_data)
compressed_rom_data[segment_rom_start:i] = segment.data
compressed_rom_dma_entries.append(
DmaEntry(
segment.vromStart,
segment.vromEnd,
segment_rom_start,
segment_rom_end if segment.is_compressed else 0,
)
)
rom_offset = segment_rom_end
assert rom_offset == compressed_rom_size
# Pad the compressed rom with the pattern matching the baseroms
for i in range(compressed_rom_size, compressed_rom_size_padded):
compressed_rom_data[i] = i % 256
# Write the new dmadata
dmadata_offset = dmadata_offset_start
for dma_entry in compressed_rom_dma_entries:
assert dmadata_offset + DmaEntry.SIZE_BYTES <= dmadata_offset_end
dma_entry.to_bin(compressed_rom_data[dmadata_offset:])
dmadata_offset += DmaEntry.SIZE_BYTES
return compressed_rom_data
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--in",
dest="in_rom",
required=True,
help="path to an uncompressed rom to be compressed",
)
parser.add_argument(
"--out",
dest="out_rom",
required=True,
help="path of the compressed rom to write out",
)
parser.add_argument(
"--dma-range",
dest="dma_range",
required=True,
help=(
"The dmadata location in the rom, in format"
" 'start_inclusive-end_exclusive' and using hexadecimal offsets"
" (e.g. '0x12f70-0x19030')."
),
)
parser.add_argument(
"--compress",
dest="compress_ranges",
required=True,
help=(
"The indices in the dmadata of the entries to be compressed,"
" where 0 is the first entry."
" It is a comma-separated list of individual indices and inclusive ranges."
" e.g. '0-1,3,5,6-9' is all indices from 0 to 9 (included) except 2 and 4."
),
)
parser.add_argument(
"--threads",
dest="n_threads",
type=int,
default=1,
help="how many cores to use for parallel compression",
)
args = parser.parse_args()
in_rom_p = Path(args.in_rom)
if not in_rom_p.exists():
parser.error(f"Input rom file {in_rom_p} doesn't exist.")
out_rom_p = Path(args.out_rom)
dma_range_str: str = args.dma_range
dma_range_ends_str = dma_range_str.split("-")
assert len(dma_range_ends_str) == 2, dma_range_str
dmadata_offset_start, dmadata_offset_end = (
int(v_str, 16) for v_str in dma_range_ends_str
)
assert dmadata_offset_start < dmadata_offset_end, dma_range_str
compress_ranges_str: str = args.compress_ranges
compress_entries_indices = set()
for compress_range_str in compress_ranges_str.split(","):
compress_range_ends_str = compress_range_str.split("-")
assert len(compress_range_ends_str) <= 2, (
compress_range_ends_str,
compress_range_str,
compress_ranges_str,
)
compress_range_ends = [int(v_str) for v_str in compress_range_ends_str]
if len(compress_range_ends) == 1:
compress_entries_indices.add(compress_range_ends[0])
else:
assert len(compress_range_ends) == 2
compress_range_first, compress_range_last = compress_range_ends
compress_entries_indices.update(
range(compress_range_first, compress_range_last + 1)
)
n_threads = args.n_threads
in_rom_data = in_rom_p.read_bytes()
out_rom_data = compress_rom(
memoryview(in_rom_data),
dmadata_offset_start,
dmadata_offset_end,
compress_entries_indices,
n_threads,
)
out_rom_p.write_bytes(out_rom_data)
if __name__ == "__main__":
main()

View File

@ -496,7 +496,7 @@ def main():
script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.dirname(os.path.realpath(__file__))
cs_data = None cs_data = None
with open(script_dir + "/../baserom/" + file_result.name, "rb") as ovl_file: with open(script_dir + "/../baseroms/gc-eu-mq-dbg/segments/" + file_result.name, "rb") as ovl_file:
ovl_file.seek(file_result.offset) ovl_file.seek(file_result.offset)
cs_data = [i[0] for i in struct.iter_unpack(">I", bytearray(ovl_file.read()))] cs_data = [i[0] for i in struct.iter_unpack(">I", bytearray(ovl_file.read()))]
if cs_data is not None: if cs_data is not None:

273
tools/decompress_baserom.py Executable file
View File

@ -0,0 +1,273 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: © 2024 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
from __future__ import annotations
import hashlib
import io
import struct
from pathlib import Path
import argparse
import crunch64
import ipl3checksum
import zlib
def decompress_zlib(data: bytes) -> bytes:
decomp = zlib.decompressobj(-zlib.MAX_WBITS)
output = bytearray()
output.extend(decomp.decompress(data))
while decomp.unconsumed_tail:
output.extend(decomp.decompress(decomp.unconsumed_tail))
output.extend(decomp.flush())
return bytes(output)
def decompress(data: bytes, is_zlib_compressed: bool) -> bytes:
if is_zlib_compressed:
return decompress_zlib(data)
return crunch64.yaz0.decompress(data)
FILE_TABLE_OFFSET = {
"gc-eu-mq-dbg": 0x12F70,
}
VERSIONS_MD5S = {
"gc-eu-mq-dbg": "f0b7f35375f9cc8ca1b2d59d78e35405",
}
def round_up(n, shift):
mod = 1 << shift
return (n + mod - 1) >> shift << shift
def as_word_list(b) -> list[int]:
return [i[0] for i in struct.iter_unpack(">I", b)]
def read_dmadata_entry(file_content: bytearray, addr: int) -> list[int]:
return as_word_list(file_content[addr : addr + 0x10])
def read_dmadata(file_content: bytearray, start) -> list[list[int]]:
dmadata = []
addr = start
entry = read_dmadata_entry(file_content, addr)
i = 0
while any([e != 0 for e in entry]):
dmadata.append(entry)
addr += 0x10
i += 1
entry = read_dmadata_entry(file_content, addr)
return dmadata
def update_crc(decompressed: io.BytesIO) -> io.BytesIO:
print("Recalculating crc...")
calculated_checksum = ipl3checksum.CICKind.CIC_X105.calculateChecksum(
bytes(decompressed.getbuffer())
)
new_crc = struct.pack(f">II", calculated_checksum[0], calculated_checksum[1])
decompressed.seek(0x10)
decompressed.write(new_crc)
return decompressed
def decompress_rom(
file_content: bytearray, dmadata_addr: int, dmadata: list[list[int]], version: str
) -> bytearray:
rom_segments = {} # vrom start : data s.t. len(data) == vrom_end - vrom_start
new_dmadata = bytearray() # new dmadata: {vrom start , vrom end , vrom start , 0}
decompressed = io.BytesIO(b"")
for v_start, v_end, p_start, p_end in dmadata:
if p_start == 0xFFFFFFFF and p_end == 0xFFFFFFFF:
new_dmadata.extend(struct.pack(">IIII", v_start, v_end, p_start, p_end))
continue
if p_end == 0: # uncompressed
rom_segments.update(
{v_start: file_content[p_start : p_start + v_end - v_start]}
)
else: # compressed
rom_segments.update(
{
v_start: decompress(
file_content[p_start:p_end], version in {"ique-cn", "ique-zh"}
)
}
)
new_dmadata.extend(struct.pack(">IIII", v_start, v_end, v_start, 0))
# write rom segments to vaddrs
for vrom_st, data in rom_segments.items():
decompressed.seek(vrom_st)
decompressed.write(data)
# write new dmadata
decompressed.seek(dmadata_addr)
decompressed.write(new_dmadata)
# pad to size
padding_end = round_up(dmadata[-1][1], 14)
decompressed.seek(padding_end - 1)
decompressed.write(bytearray([0]))
# re-calculate crc
return bytearray(update_crc(decompressed).getbuffer())
def get_str_hash(byte_array):
return str(hashlib.md5(byte_array).hexdigest())
def check_existing_rom(rom_path: Path, correct_str_hash: str):
# If the baserom exists and is correct, we don't need to change anything
if rom_path.exists():
if get_str_hash(rom_path.read_bytes()) == correct_str_hash:
return True
return False
def word_swap(file_content: bytearray) -> bytearray:
words = str(int(len(file_content) / 4))
little_byte_format = "<" + words + "I"
big_byte_format = ">" + words + "I"
tmp = struct.unpack_from(little_byte_format, file_content, 0)
struct.pack_into(big_byte_format, file_content, 0, *tmp)
return file_content
def byte_swap(file_content: bytearray) -> bytearray:
halfwords = str(int(len(file_content) / 2))
little_byte_format = "<" + halfwords + "H"
big_byte_format = ">" + halfwords + "H"
tmp = struct.unpack_from(little_byte_format, file_content, 0)
struct.pack_into(big_byte_format, file_content, 0, *tmp)
return file_content
def per_version_fixes(file_content: bytearray, version: str) -> bytearray:
if version == "gc-eu-mq-dbg":
# Strip the overdump
print("Stripping overdump...")
file_content = file_content[0:0x3600000]
# Patch the header
print("Patching header...")
file_content[0x3E] = 0x50
return file_content
def pad_rom(file_content: bytearray, dmadata: list[list[int]]) -> bytearray:
padding_start = round_up(dmadata[-1][1], 12)
padding_end = round_up(dmadata[-1][1], 14)
print(f"Padding from {padding_start:X} to {padding_end:X}...")
for i in range(padding_start, padding_end):
file_content[i] = 0xFF
return file_content
# Determine if we have a ROM file
ROM_FILE_EXTENSIONS = ["z64", "n64", "v64"]
def find_baserom(version: str) -> Path | None:
for rom_file_ext_lower in ROM_FILE_EXTENSIONS:
for rom_file_ext in (rom_file_ext_lower, rom_file_ext_lower.upper()):
rom_file_name_candidate = Path(f"baseroms/{version}/baserom.{rom_file_ext}")
if rom_file_name_candidate.exists():
return rom_file_name_candidate
return None
def main():
description = "Convert a rom that uses dmadata to an uncompressed one."
parser = argparse.ArgumentParser(description=description)
parser.add_argument("version", help="Version of the game to decompress.", choices=list(VERSIONS_MD5S.keys()))
args = parser.parse_args()
version = args.version
uncompressed_path = Path(f"baseroms/{version}/baserom-decompressed.z64")
file_table_offset = FILE_TABLE_OFFSET[version]
correct_str_hash = VERSIONS_MD5S[version]
if check_existing_rom(uncompressed_path, correct_str_hash):
print("Found valid baserom - exiting early")
return
rom_file_name = find_baserom(version)
if rom_file_name is None:
path_list = [
f"baseroms/{version}/baserom.{rom_file_ext}" for rom_file_ext in ROM_FILE_EXTENSIONS
]
print(f"Error: Could not find {','.join(path_list)}.")
exit(1)
# Read in the original ROM
print(f"File '{rom_file_name}' found.")
file_content = bytearray(rom_file_name.read_bytes())
# Check if ROM needs to be byte/word swapped
# Little-endian
if file_content[0] == 0x40:
# Word Swap ROM
print("ROM needs to be word swapped...")
file_content = word_swap(file_content)
print("Word swapping done.")
# Byte-swapped
elif file_content[0] == 0x37:
# Byte Swap ROM
print("ROM needs to be byte swapped...")
file_content = byte_swap(file_content)
print("Byte swapping done.")
file_content = per_version_fixes(file_content, version)
dmadata = read_dmadata(file_content, file_table_offset)
# Decompress
if any(
[
b != 0
for b in file_content[
file_table_offset + 0xAC : file_table_offset + 0xAC + 0x4
]
]
):
print("Decompressing rom...")
file_content = decompress_rom(file_content, file_table_offset, dmadata, version)
file_content = pad_rom(file_content, dmadata)
# Check to see if the ROM is a "vanilla" ROM
str_hash = get_str_hash(file_content)
if str_hash != correct_str_hash:
print(
f"Error: Expected a hash of {correct_str_hash} but got {str_hash}. The baserom has probably been tampered, find a new one"
)
if version == "gc-eu-mq-dbg":
if str_hash == "32fe2770c0f9b1a9cd2a4d449348c1cb":
print(
"The provided baserom is a rom which has been edited with ZeldaEdit and is not suitable for use with decomp. Find a new one."
)
exit(1)
# Write out our new ROM
print(f"Writing new ROM {uncompressed_path}.")
uncompressed_path.write_bytes(file_content)
print("Done!")
if __name__ == "__main__":
main()

View File

@ -166,7 +166,7 @@ item_ids = {
def disas_elfmsgs(start): def disas_elfmsgs(start):
baserom = None baserom = None
with open("baserom.z64", "rb") as infile: with open("baseroms/gc-eu-mq-dbg/baserom-decompressed.z64", "rb") as infile:
baserom = bytearray(infile.read()) baserom = bytearray(infile.read())
branches = [] branches = []

16
tools/dmadata_range.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
NM=$1
ELF=$2
# dmadata_syms contents look like:
# ffffffff80015850 T _dmadataSegmentTextStart
# 00000000 A _dmadataSegmentTextSize
# 00011a40 A _dmadataSegmentRomStart
# ffffffff8001b920 T _dmadataSegmentRoDataEnd
dmadata_syms=`$NM $ELF --no-sort --radix=x --format=bsd | grep dmadata`
_dmadataSegmentRomStart=`echo "$dmadata_syms" | grep '\b_dmadataSegmentRomStart\b' | cut -d' ' -f1`
_dmadataSegmentRomEnd=` echo "$dmadata_syms" | grep '\b_dmadataSegmentRomEnd\b' | cut -d' ' -f1`
echo 0x"$_dmadataSegmentRomStart"-0x"$_dmadataSegmentRomEnd"

View File

@ -20,22 +20,53 @@ static void write_dmadata_table(FILE *fout)
fprintf(fout, "DEFINE_DMA_ENTRY(%s, \"%s\")\n", g_segments[i].name, g_segments[i].name); fprintf(fout, "DEFINE_DMA_ENTRY(%s, \"%s\")\n", g_segments[i].name, g_segments[i].name);
} }
static void write_compress_ranges(FILE *fout)
{
int i;
bool continue_list = false;
int stride_first = -1;
for (i = 0; i < g_segmentsCount; i++) {
if (g_segments[i].compress) {
if (stride_first == -1)
stride_first = i;
}
if (!g_segments[i].compress || i == g_segmentsCount - 1) {
if (stride_first != -1) {
int stride_last = i - 1;
if (continue_list) {
fprintf(fout, ",");
}
if (stride_first == stride_last) {
fprintf(fout, "%d", stride_first);
} else {
fprintf(fout, "%d-%d", stride_first, stride_last);
}
continue_list = true;
stride_first = -1;
}
}
}
}
static void usage(const char *execname) static void usage(const char *execname)
{ {
fprintf(stderr, "zelda64 dmadata generation tool v0.01\n" fprintf(stderr, "zelda64 dmadata generation tool v0.01\n"
"usage: %s SPEC_FILE DMADATA_TABLE\n" "usage: %s SPEC_FILE DMADATA_TABLE COMPRESS_RANGES\n"
"SPEC_FILE file describing the organization of object files into segments\n" "SPEC_FILE file describing the organization of object files into segments\n"
"DMADATA_TABLE filename of output dmadata table header\n", "DMADATA_TABLE filename of output dmadata table header\n",
"COMPRESS_RANGES filename to write which files are compressed (e.g. 0-5,7,10-20)\n",
execname); execname);
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
FILE *dmaout; FILE *dmaout;
FILE *compress_ranges_out;
void *spec; void *spec;
size_t size; size_t size;
if (argc != 3) if (argc != 4)
{ {
usage(argv[0]); usage(argv[0]);
return 1; return 1;
@ -49,6 +80,13 @@ int main(int argc, char **argv)
util_fatal_error("failed to open file '%s' for writing", argv[2]); util_fatal_error("failed to open file '%s' for writing", argv[2]);
write_dmadata_table(dmaout); write_dmadata_table(dmaout);
fclose(dmaout); fclose(dmaout);
compress_ranges_out = fopen(argv[3], "w");
if (compress_ranges_out == NULL)
util_fatal_error("failed to open file '%s' for writing", argv[3]);
write_compress_ranges(compress_ranges_out);
fclose(compress_ranges_out);
free_rom_spec(g_segments, g_segmentsCount); free_rom_spec(g_segments, g_segmentsCount);
free(spec); free(spec);

View File

@ -282,7 +282,7 @@ def read_tables():
global staff_message_entry_table global staff_message_entry_table
baserom = None baserom = None
with open("baserom.z64","rb") as infile: with open("baseroms/gc-eu-mq-dbg/baserom-decompressed.z64","rb") as infile:
baserom = infile.read() baserom = infile.read()
nes_message_entry_table = as_message_table_entry(baserom[nes_message_entry_table_addr:ger_message_entry_table_addr]) nes_message_entry_table = as_message_table_entry(baserom[nes_message_entry_table_addr:ger_message_entry_table_addr])
@ -326,7 +326,7 @@ def dump_all_text():
nes_offset = segmented_to_physical(entry[3]) nes_offset = segmented_to_physical(entry[3])
nes_length = next_entry[3] - entry[3] nes_length = next_entry[3] - entry[3]
nes_text = "" nes_text = ""
with open("baserom/nes_message_data_static","rb") as infile: with open("baseroms/gc-eu-mq-dbg/segments/nes_message_data_static","rb") as infile:
infile.seek(nes_offset) infile.seek(nes_offset)
nes_text = fixup_message(decode(infile.read(nes_length), entry[1]).replace("\x00","",-1)) nes_text = fixup_message(decode(infile.read(nes_length), entry[1]).replace("\x00","",-1))
@ -337,13 +337,13 @@ def dump_all_text():
next_entry = combined_message_entry_table[i+2] next_entry = combined_message_entry_table[i+2]
ger_offset = segmented_to_physical(entry[4]) ger_offset = segmented_to_physical(entry[4])
ger_length = next_entry[4] - entry[4] ger_length = next_entry[4] - entry[4]
with open("baserom/ger_message_data_static","rb") as infile: with open("baseroms/gc-eu-mq-dbg/segments/ger_message_data_static","rb") as infile:
infile.seek(ger_offset) infile.seek(ger_offset)
ger_text = fixup_message(decode(infile.read(ger_length), entry[1]).replace("\x00","",-1)) ger_text = fixup_message(decode(infile.read(ger_length), entry[1]).replace("\x00","",-1))
fra_offset = segmented_to_physical(entry[5]) fra_offset = segmented_to_physical(entry[5])
fra_length = next_entry[5] - entry[5] fra_length = next_entry[5] - entry[5]
with open("baserom/fra_message_data_static","rb") as infile: with open("baseroms/gc-eu-mq-dbg/segments/fra_message_data_static","rb") as infile:
infile.seek(fra_offset) infile.seek(fra_offset)
fra_text = fixup_message(decode(infile.read(fra_length), entry[1]).replace("\x00","",-1)) fra_text = fixup_message(decode(infile.read(fra_length), entry[1]).replace("\x00","",-1))
@ -352,7 +352,7 @@ def dump_all_text():
return messages return messages
def dump_staff_text(): def dump_staff_text():
staff_message_data_static_size = path.getsize("baserom/staff_message_data_static") staff_message_data_static_size = path.getsize("baseroms/gc-eu-mq-dbg/segments/staff_message_data_static")
# text id, ypos, type, staff # text id, ypos, type, staff
messages = [] messages = []
for i,entry in enumerate(staff_message_entry_table,0): for i,entry in enumerate(staff_message_entry_table,0):
@ -361,7 +361,7 @@ def dump_staff_text():
staff_offset = segmented_to_physical(entry[3]) staff_offset = segmented_to_physical(entry[3])
# hacky way to ensure the staff message entry table is read all the way to the end # hacky way to ensure the staff message entry table is read all the way to the end
staff_length = (staff_message_data_static_size if entry[0] == 0x052F else segmented_to_physical(next_entry[3])) - segmented_to_physical(entry[3]) staff_length = (staff_message_data_static_size if entry[0] == 0x052F else segmented_to_physical(next_entry[3])) - segmented_to_physical(entry[3])
with open("baserom/staff_message_data_static","rb") as infile: with open("baseroms/gc-eu-mq-dbg/segments/staff_message_data_static","rb") as infile:
infile.seek(staff_offset) infile.seek(staff_offset)
messages.append((entry[0], entry[1], entry[2], fixup_message(decode(infile.read(staff_length), entry[1]).replace("\x00","",-1)))) messages.append((entry[0], entry[1], entry[2], fixup_message(decode(infile.read(staff_length), entry[1]).replace("\x00","",-1))))
else: else:

View File

@ -387,7 +387,7 @@ def GetColliderInit(address, type, num, path):
else: else:
return("ItemInit type must specify number of elements") return("ItemInit type must specify number of elements")
ovlFile = open(path + "/baserom/" + fileResult.name, "rb") ovlFile = open(path + "/baseroms/gc-eu-mq-dbg/segments/" + fileResult.name, "rb")
ovlData = bytearray(ovlFile.read()) ovlData = bytearray(ovlFile.read())
ovlFile.close() ovlFile.close()
@ -470,7 +470,7 @@ print(GetColliderInitFull(args.address, args.type, args.num, repo))
# script_dir = os.path.dirname(os.path.realpath(__file__)) # script_dir = os.path.dirname(os.path.realpath(__file__))
# ovlFile = open(script_dir + "/../../baserom/" + fileResult.name, "rb") # ovlFile = open(script_dir + "/../../baseroms/" + fileResult.name, "rb")
# ovlData = bytearray(ovlFile.read()) # ovlData = bytearray(ovlFile.read())
# ovlFile.close() # ovlFile.close()

View File

@ -132,7 +132,7 @@ def get_damage_bytes(address, repo):
print(file_result) print(file_result)
with open(repo + os.sep + "baserom" + os.sep + file_result.name, "rb") as ovl_file: with open(repo + os.sep + "baseroms/gc-eu-mq-dbg/segments" + os.sep + file_result.name, "rb") as ovl_file:
ovl_data = bytearray(ovl_file.read()) ovl_data = bytearray(ovl_file.read())
damage_data = ovl_data[file_result.offset:file_result.offset+0x20] damage_data = ovl_data[file_result.offset:file_result.offset+0x20]

View File

@ -72,7 +72,7 @@ repo = scriptDir + os.sep + ".." + os.sep + ".."
kaleido_scope_data = [] kaleido_scope_data = []
with open(repo + "/baserom/ovl_kaleido_scope", "rb") as file: with open(repo + "/baseroms/gc-eu-mq-dbg/segments/ovl_kaleido_scope", "rb") as file:
kaleido_scope_data = bytearray(file.read()) kaleido_scope_data = bytearray(file.read())
scenemaps = [] scenemaps = []

View File

@ -97,7 +97,7 @@ repo = scriptDir + os.sep + ".." + os.sep + ".."
map_mark_data = [] map_mark_data = []
with open(repo + "/baserom/ovl_map_mark_data", "rb") as file: with open(repo + "/baseroms/gc-eu-mq-dbg/segments/ovl_map_mark_data", "rb") as file:
map_mark_data = bytearray(file.read()) map_mark_data = bytearray(file.read())
scenemaps = [] scenemaps = []

View File

@ -127,6 +127,7 @@ static const char *const stmtNames[] =
[STMT_after] = "after", [STMT_after] = "after",
[STMT_align] = "align", [STMT_align] = "align",
[STMT_beginseg] = "beginseg", [STMT_beginseg] = "beginseg",
[STMT_compress] = "compress",
[STMT_endseg] = "endseg", [STMT_endseg] = "endseg",
[STMT_entry] = "entry", [STMT_entry] = "entry",
[STMT_flags] = "flags", [STMT_flags] = "flags",
@ -216,10 +217,13 @@ bool parse_segment_statement(struct Segment *currSeg, STMTId stmt, char* args, i
currSeg->includes[currSeg->includesCount - 1].linkerPadding = 0; currSeg->includes[currSeg->includesCount - 1].linkerPadding = 0;
currSeg->includes[currSeg->includesCount - 1].dataWithRodata = (stmt == STMT_include_data_with_rodata); currSeg->includes[currSeg->includesCount - 1].dataWithRodata = (stmt == STMT_include_data_with_rodata);
break; break;
case STMT_increment: case STMT_increment:
if (!parse_number(args, &currSeg->increment)) if (!parse_number(args, &currSeg->increment))
util_fatal_error("line %i: expected number after 'increment'", lineNum); util_fatal_error("line %i: expected number after 'increment'", lineNum);
break; break;
case STMT_compress:
currSeg->compress = true;
break;
case STMT_pad_text: case STMT_pad_text:
currSeg->includes[currSeg->includesCount - 1].linkerPadding += 0x10; currSeg->includes[currSeg->includesCount - 1].linkerPadding += 0x10;
break; break;

View File

@ -9,6 +9,7 @@ typedef enum {
STMT_after, STMT_after,
STMT_align, STMT_align,
STMT_beginseg, STMT_beginseg,
STMT_compress,
STMT_endseg, STMT_endseg,
STMT_entry, STMT_entry,
STMT_flags, STMT_flags,
@ -48,6 +49,7 @@ typedef struct Segment {
uint32_t number; uint32_t number;
struct Include* includes; struct Include* includes;
int includesCount; int includesCount;
bool compress;
} Segment; } Segment;
void parse_rom_spec(char* spec, struct Segment** segments, int* segment_count); void parse_rom_spec(char* spec, struct Segment** segments, int* segment_count);