Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbbb3f967a | ||
|
|
43db8cc0f5 | ||
|
|
a23632ae6c | ||
|
|
83714135f3 | ||
|
|
03bf63f40e | ||
|
|
f9982dda65 | ||
|
|
71b1c3c020 | ||
|
|
aee281aa1f | ||
|
|
95f757309c | ||
|
|
9dde4a7a07 | ||
|
|
366da3c3d2 | ||
|
|
b559b93eeb | ||
|
|
5defa99a11 | ||
|
|
aaf43972b8 | ||
|
|
bf35146ea4 | ||
|
|
97ea397610 | ||
|
|
958ccf9458 | ||
|
|
25e745fcff | ||
|
|
8a12c573f2 | ||
|
|
6bf39a0617 | ||
|
|
5e63ff7863 | ||
|
|
3388cedba9 | ||
|
|
9aa38e0a65 | ||
|
|
72b193d52b | ||
|
|
aefcb0828b | ||
|
|
9b4d7febd2 | ||
|
|
e463564ffe | ||
|
|
f17503ce2c | ||
|
|
8d3028bd5f | ||
|
|
4693920efc | ||
|
|
5737c15614 | ||
|
|
4f97999d66 | ||
|
|
70a08f116c | ||
|
|
31cba4c664 | ||
|
|
d3ab969a0b | ||
|
|
afc7f9b313 | ||
|
|
c056291dd1 | ||
|
|
c487a04d8f | ||
|
|
91d771ecc4 | ||
|
|
c9c6843f7d | ||
|
|
825f449e58 | ||
|
|
e4febafd54 | ||
|
|
8bee71746f | ||
|
|
87685c8b06 | ||
|
|
da4aac425f | ||
|
|
27cc763c2e | ||
|
|
6ad3eafc21 | ||
|
|
c08fcb7d88 | ||
|
|
c3f084da18 | ||
|
|
e72f93c47a | ||
|
|
d4f9f78c9d | ||
|
|
18c351f101 | ||
|
|
3072b8ae0c | ||
|
|
3a2f900b31 | ||
|
|
70df79a4f0 | ||
|
|
3b7f2a6828 | ||
|
|
3da7cb4231 | ||
|
|
fd83a854d9 | ||
|
|
4f62538f8c | ||
|
|
c136d2c85d | ||
|
|
97f28c534d | ||
|
|
3ef8be9e8d | ||
|
|
ea788824b2 | ||
|
|
eb7471b924 | ||
|
|
b1b1871fb7 | ||
|
|
ca656497b4 | ||
|
|
41367329f9 | ||
|
|
7bb7bf23e8 | ||
|
|
5e5a1587e1 | ||
|
|
28b7801e74 | ||
|
|
a0b6a04562 | ||
|
|
bf30ca0548 | ||
|
|
863a2b9da7 | ||
|
|
8f4b242dac | ||
|
|
ba4a5ff6d9 | ||
|
|
90276fc440 | ||
|
|
3ce06c541e | ||
|
|
9d083a7864 | ||
|
|
3be6ece9f9 | ||
|
|
ec08202f3a | ||
|
|
2cfb4cbaf3 | ||
|
|
5cd9010ecf | ||
|
|
540167a714 | ||
|
|
ece639e2f9 | ||
|
|
5ce0996691 | ||
|
|
2ff515fb66 | ||
|
|
128557c6f6 | ||
|
|
5e7bd07ee6 | ||
|
|
b584fd08c6 | ||
|
|
8c4e6c62b5 | ||
|
|
6297be577d | ||
|
|
5ae8a5cc71 | ||
|
|
f732352300 | ||
|
|
ee81f434cf | ||
|
|
b7be4d672b | ||
|
|
393e572945 | ||
|
|
95659d2f40 | ||
|
|
3f52f05e03 | ||
|
|
73a7155f7f | ||
|
|
5baed18b31 | ||
|
|
b67db8a713 | ||
|
|
c28692b32d | ||
|
|
dd8556eebe | ||
|
|
39c5060cbe | ||
|
|
3a080d6bb3 | ||
|
|
e272415f12 | ||
|
|
b23a298b54 | ||
|
|
12bb1f82bc | ||
|
|
9b608eb53a |
1
.github/ISSUE_TEMPLATE/bugs.yml
vendored
1
.github/ISSUE_TEMPLATE/bugs.yml
vendored
@@ -18,5 +18,6 @@ body:
|
||||
- "Windows"
|
||||
- "Linux"
|
||||
- "Mac"
|
||||
- "Nintendo Switch"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -7,24 +7,27 @@
|
||||
*.aps
|
||||
/obj/
|
||||
/bin/
|
||||
/tables/overworld/*.yaml
|
||||
/tables/dungeon/*.yaml
|
||||
/tables/img/
|
||||
/tables/old/
|
||||
/tables/zelda3.sfc
|
||||
/tables/zelda3.smc
|
||||
/assets/overworld/*.yaml
|
||||
/assets/dungeon/*.yaml
|
||||
/assets/img/
|
||||
/assets/old/
|
||||
/zelda3.sfc
|
||||
/zelda3.smc
|
||||
/saves/*.sav
|
||||
/saves/sram.dat
|
||||
/saves/sram.bak
|
||||
/zelda3
|
||||
__pycache__
|
||||
/*.o
|
||||
/src/*.o
|
||||
/*.exe
|
||||
/*.out
|
||||
/snes/*.o
|
||||
/msu/alttp_msu-*.pcm
|
||||
/msu/
|
||||
/tmp/
|
||||
/tables/zelda3_assets.dat
|
||||
/third_party/tcc/
|
||||
/third_party/SDL2-2.24.0/
|
||||
/SDL2.dll
|
||||
/zelda3_assets.dat
|
||||
/SDL2.dll
|
||||
/zelda3.*.ini
|
||||
/zelda3.wiki
|
||||
/sprites-gfx
|
||||
/glsl-shaders
|
||||
/token.txt
|
||||
|
||||
32
LICENSE.txt
32
LICENSE.txt
@@ -20,3 +20,35 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
This is the license for Opus:
|
||||
|
||||
/* Copyright (c) 2007-2008 CSIRO
|
||||
Copyright (c) 2007-2009 Xiph.Org Foundation
|
||||
Copyright (c) 2008-2009 Gregory Maxwell
|
||||
Written by Jean-Marc Valin and Gregory Maxwell */
|
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
36
Makefile
36
Makefile
@@ -1,28 +1,38 @@
|
||||
TARGET_EXEC:=zelda3
|
||||
ROM:=tables/zelda3.sfc
|
||||
SRCS:=$(wildcard *.c snes/*.c)
|
||||
SRCS:=$(wildcard src/*.c snes/*.c) third_party/gl_core/gl_core_3_1.c third_party/opus-1.3.1-stripped/opus_decoder_amalgam.c
|
||||
OBJS:=$(SRCS:%.c=%.o)
|
||||
PYTHON:=/usr/bin/env python3
|
||||
CFLAGS:=$(if $(CFLAGS),$(CFLAGS),-O2)
|
||||
|
||||
CFLAGS:=$(if $(CFLAGS),$(CFLAGS),-O2 -Werror) -I .
|
||||
CFLAGS:=${CFLAGS} $(shell sdl2-config --cflags) -DSYSTEM_VOLUME_MIXER_AVAILABLE=0
|
||||
LDFLAGS:=${LDFLAGS} $(shell sdl2-config --libs)
|
||||
|
||||
ifeq (${OS},Windows_NT)
|
||||
WINDRES:=windres
|
||||
RES:=zelda3.res
|
||||
SDLFLAGS:=-Wl,-Bstatic $(shell sdl2-config --static-libs)
|
||||
else
|
||||
SDLFLAGS:=$(shell sdl2-config --libs) -lm
|
||||
endif
|
||||
|
||||
.PHONY: all clean clean_obj clean_gen
|
||||
|
||||
all: $(TARGET_EXEC) tables/zelda3_assets.dat
|
||||
$(TARGET_EXEC): $(OBJS)
|
||||
$(CC) $(OBJS) -o $@ $(LDFLAGS)
|
||||
all: $(TARGET_EXEC) zelda3_assets.dat
|
||||
$(TARGET_EXEC): $(OBJS) $(RES)
|
||||
$(CC) $^ -o $@ $(LDFLAGS) $(SDLFLAGS)
|
||||
%.o : %.c
|
||||
$(CC) -c $(CFLAGS) $< -o $@
|
||||
|
||||
tables/zelda3_assets.dat: tables/dialogue.txt
|
||||
cd tables; $(PYTHON) compile_resources.py ../$(ROM)
|
||||
tables/dialogue.txt:
|
||||
cd tables; $(PYTHON) extract_resources.py ../$(ROM)
|
||||
$(RES): src/platform/win32/zelda3.rc
|
||||
@echo "Generating Windows resources"
|
||||
@$(WINDRES) $< -O coff -o $@
|
||||
|
||||
zelda3_assets.dat:
|
||||
@echo "Extracting game resources"
|
||||
$(PYTHON) assets/restool.py --extract-from-rom
|
||||
|
||||
clean: clean_obj clean_gen
|
||||
clean_obj:
|
||||
$(RM) $(OBJS) $(TARGET_EXEC)
|
||||
@$(RM) $(OBJS) $(TARGET_EXEC)
|
||||
clean_gen:
|
||||
$(RM) tables/zelda3_assets.dat
|
||||
@$(RM) $(RES) zelda3_assets.dat tables/zelda3_assets.dat tables/*.txt tables/*.png tables/sprites/*.png tables/*.yaml
|
||||
@rm -rf tables/__pycache__ tables/dungeon tables/img tables/overworld tables/sound
|
||||
|
||||
133
README.md
133
README.md
@@ -14,82 +14,80 @@ You need a copy of the ROM to extract game resources (levels, images). Then once
|
||||
It uses the PPU and DSP implementation from [LakeSnes](https://github.com/elzo-d/LakeSnes), but with lots of speed optimizations.
|
||||
Additionally, it can be configured to also run the original machine code side by side. Then the RAM state is compared after each frame, to verify that the C implementation is correct.
|
||||
|
||||
I got much assistance from spannierism's Zelda 3 JP disassembly and the other ones that documented loads of function names and variables.
|
||||
I got much assistance from spannerism's Zelda 3 JP disassembly and the other ones that documented loads of function names and variables.
|
||||
|
||||
## Additional features
|
||||
|
||||
Some features have been added that are not supported by the original game.
|
||||
A bunch of features have been added that are not supported by the original game. Some of them are:
|
||||
|
||||
Secondary item slot on button X (Hold X in inventory to select).
|
||||
|
||||
Displays max rupees, bombs and arrows with yellow or orange color.
|
||||
|
||||
Extends throwing bombs to four instead of two.
|
||||
|
||||
Support for MSU audio tracks.
|
||||
Support for pixel shaders.
|
||||
|
||||
Support for enhanced aspect ratios of 16:9 or 16:10.
|
||||
|
||||
Higher quality world map.
|
||||
|
||||
Support for MSU audio tracks.
|
||||
|
||||
Secondary item slot on button X (Hold X in inventory to select).
|
||||
|
||||
Switching current item with L/R keys.
|
||||
|
||||
Reordering of inventory by pressing Y+Arrows.
|
||||
## How to Play:
|
||||
|
||||
Higher quality map screen.
|
||||
Option 1: Launcher by RadzPrower (windows only) https://github.com/ajohns6/Zelda-3-Launcher
|
||||
|
||||
Disable low health beep.
|
||||
Option 2: Building it yourself
|
||||
|
||||
Pick up items and destroy pots with Sword.
|
||||
Visit Wiki for more info on building the project: https://github.com/snesrev/zelda3/wiki
|
||||
|
||||
## Dependencies
|
||||
## Installing Python & libraries on Windows (required for asset extraction steps)
|
||||
1. Download [Python](https://www.python.org/ftp/python/3.11.1/python-3.11.1-amd64.exe) installer and install with "Add to PATH" checkbox checked
|
||||
2. Open the command prompt
|
||||
3. Type `python -m pip install --upgrade pip pillow pyyaml` and hit enter
|
||||
4. Close the command prompt
|
||||
|
||||
- the `libsdl2-dev` library
|
||||
- Windows: automatically installed with NuGet
|
||||
- Ubuntu: `apt install libsdl2-dev`
|
||||
- macOS: `brew install sdl2`
|
||||
- a `tables/zelda3.sfc` US ROM file (for asset extraction step only)
|
||||
- SHA256 : `66871d66be19ad2c34c927d6b14cd8eb6fc3181965b6e517cb361f7316009cfb`.
|
||||
- The `pillow` and `pyyaml` python dependencies used by the assets extractor.
|
||||
- `python -m pip install -r requirements.txt`
|
||||
## Compiling on Windows with TCC (1mb Tiny C Compiler)
|
||||
1. Download the project by clicking "Code > Download ZIP" on the github page
|
||||
2. Extract the ZIP to your hard drive
|
||||
3. Place the USA rom named `zelda3.sfc` in the root directory.
|
||||
4. Double-click `extract_assets.bat` in the main dir to create `zelda3_assets.dat` in that same dir
|
||||
5. Download [TCC](https://github.com/FitzRoyX/tinycc/releases/download/tcc_20221020/tcc_20221020.zip) and extract to the "\third_party" subfolder
|
||||
6. Download [SDL2](https://github.com/libsdl-org/SDL/releases/download/release-2.26.3/SDL2-devel-2.26.3-VC.zip) and extract to the "\third_party" subfolder
|
||||
7. Double-click `run_with_tcc.bat` in the main dir to create `zelda3.exe` in that same dir
|
||||
8. Configure with `zelda3.ini` in the main dir
|
||||
|
||||
## Compiling
|
||||
## Compiling on Windows with Visual Studio (4.5gb IDE and compiler)
|
||||
Same Steps 1-4 above<br/>
|
||||
8. Double-click `Zelda3.sln`<br/>
|
||||
9. Install the **Desktop development with C++** workload with the VS Installer if you don't have it already (it should prompt you to do this).<br/>
|
||||
10. Change "debug" to "release" in the top dropdown<br/>
|
||||
12. Choose "build > build Zelda3" in the menu to create `zelda3.exe` in the "/bin/release" subfolder<br/>
|
||||
13. Configure with `zelda3.ini` in the main dir<br/>
|
||||
|
||||
Look at the wiki at https://github.com/snesrev/zelda3/wiki for more help.
|
||||
|
||||
### Windows
|
||||
First extract and compile resources.
|
||||
|
||||
`cd tables`
|
||||
|
||||
Run `python3 extract_resources.py` to extract resources from the ROM into a more human readable format.
|
||||
|
||||
Run `python3 compile_resources.py` to create a file called `zelda3_assets.dat` that gets loaded by the executable.
|
||||
|
||||
In case you're planning to move the .exe to a different folder, please include `zelda3_assets.dat`.
|
||||
|
||||
Then build the .sln file with Visual Studio.
|
||||
|
||||
### Building with TCC instead of Visual Studio on Windows
|
||||
|
||||
You can also build and run it through TCC in case you don't have Visual Studio. Note that TCC builds without optimizations so the game will run considerably slower.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Read about how to use TCC here...
|
||||
</summary>
|
||||
Extract the assets as detailed above.
|
||||
|
||||
Unzip [TCC for win64](http://download.savannah.gnu.org/releases/tinycc/tcc-0.9.27-win64-bin.zip) into `third_party/tcc`.
|
||||
|
||||
Unzip [SDL2](https://github.com/libsdl-org/SDL/releases/download/release-2.24.0/SDL2-devel-2.24.0-VC.zip) into `third_party/SDL2-2.24.0`.
|
||||
|
||||
Start `run_with_tcc.bat`. It will perform some basic error checks and then start the game.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### Linux/macOS
|
||||
## Installing libraries on Linux/MacOS
|
||||
1. Open a terminal
|
||||
2. Install pip if not already installed
|
||||
```sh
|
||||
python3 -m ensurepip
|
||||
```
|
||||
3. Clone the repo and `cd` into it
|
||||
```sh
|
||||
git clone https://github.com/snesrev/zelda3
|
||||
cd zelda3
|
||||
```
|
||||
4. Install requirements using pip
|
||||
```sh
|
||||
python3 -m pip install -r requirements.txt
|
||||
```
|
||||
5. Install SDL2
|
||||
* Ubuntu/Debian `sudo apt install libsdl2-dev`
|
||||
* Fedora Linux `sudo dnf install SDL2-devel`
|
||||
* Arch Linux `sudo pacman -S sdl2`
|
||||
* macOS: `brew install sdl2` (you can get homebrew [here](https://brew.sh/))
|
||||
|
||||
## Compiling on Linux/MacOS
|
||||
1. Place your US ROM file named `zelda3.sfc` in `zelda3`
|
||||
2. Compile
|
||||
```sh
|
||||
make
|
||||
```
|
||||
@@ -105,7 +103,7 @@ CC=clang make # specify compiler
|
||||
```
|
||||
</details>
|
||||
|
||||
### Nintendo switch
|
||||
## Nintendo Switch
|
||||
|
||||
You need [DevKitPro](https://devkitpro.org/wiki/Getting_Started) and [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere) installed.
|
||||
|
||||
@@ -117,7 +115,14 @@ make # Add -j$(nproc) to build using all cores ( Optional )
|
||||
nxlink -s zelda3.nro
|
||||
```
|
||||
|
||||
In case you're planning to move the executable to a different location, please include the file `tables/zelda3_assets.dat`.
|
||||
## More Compilation Help
|
||||
|
||||
Look at the wiki at https://github.com/snesrev/zelda3/wiki for more help.
|
||||
|
||||
The ROM needs to be named `zelda3.sfc` and has to be from the US region with this exact SHA256 hash
|
||||
`66871d66be19ad2c34c927d6b14cd8eb6fc3181965b6e517cb361f7316009cfb`
|
||||
|
||||
In case you're planning to move the executable to a different location, please include the file `zelda3_assets.dat`.
|
||||
|
||||
## Usage and controls
|
||||
|
||||
@@ -164,10 +169,10 @@ Additionally, the following commands are available:
|
||||
| Alt+Enter | Toggle Fullscreen |
|
||||
| Shift+F1-F10 | Save snapshot |
|
||||
| Ctrl+F1-F10 | Replay the snapshot |
|
||||
| 1-9 | run a dungeons playthrough snapshots |
|
||||
| Ctrl+1-9 | run a dungeons playthrough in turbo mode |
|
||||
| 1-9 | Load a dungeons playthrough snapshot |
|
||||
| Ctrl+1-9 | Run a dungeons playthrough in turbo mode |
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT license. See 'LICENSE.txt' for details.
|
||||
This project is licensed under the MIT license. See 'LICENSE.txt' for details.
|
||||
|
||||
7
tables/.gitignore → assets/.gitignore
vendored
7
tables/.gitignore → assets/.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
zelda3.sfc
|
||||
dialogue.txt
|
||||
dialogue*.txt
|
||||
generated_*.h
|
||||
linksprite.png
|
||||
map32_to_map16.txt
|
||||
@@ -8,4 +8,7 @@ sfx.txt
|
||||
/sound
|
||||
sound_ending.txt
|
||||
sound_indoor.txt
|
||||
sound_intro.txt
|
||||
sound_intro.txt
|
||||
/hud_icons.png
|
||||
/font*.png
|
||||
|
||||
@@ -6,10 +6,10 @@ import yaml
|
||||
import tables
|
||||
import compile_music
|
||||
import array, hashlib, struct
|
||||
|
||||
print_int_array = util.print_int_array
|
||||
|
||||
PATH = ''
|
||||
from util import cache
|
||||
import sprite_sheets
|
||||
import argparse
|
||||
import os
|
||||
|
||||
def flatten(xss):
|
||||
return [x for xs in xss for x in xs]
|
||||
@@ -19,7 +19,6 @@ def invert_dict(xs):
|
||||
|
||||
assets = {}
|
||||
|
||||
|
||||
def add_asset_uint8(name, data):
|
||||
assert name not in assets
|
||||
assets[name] = ('uint8', bytes(array.array('B', data)))
|
||||
@@ -36,9 +35,13 @@ def add_asset_int16(name, data):
|
||||
assert name not in assets
|
||||
assets[name] = ('int16', bytes(array.array('h', data)))
|
||||
|
||||
def add_asset_packed(name, data):
|
||||
assert name not in assets
|
||||
assets[name] = ('packed', pack_arrays(data))
|
||||
|
||||
def print_map32_to_map16():
|
||||
tab = {}
|
||||
for line in open(PATH + 'map32_to_map16.txt'):
|
||||
for line in open('map32_to_map16.txt'):
|
||||
line = line.strip()
|
||||
x, xs = line.split(':', 1)
|
||||
tab[int(x)] = [int(t) for t in xs.split(',')]
|
||||
@@ -63,57 +66,12 @@ def print_map32_to_map16():
|
||||
add_asset_uint8('kMap32ToMap16_2', res[2])
|
||||
add_asset_uint8('kMap32ToMap16_3', res[3])
|
||||
|
||||
|
||||
def print_dialogue():
|
||||
new_r = []
|
||||
offs = []
|
||||
for line in open(PATH + 'dialogue.txt'):
|
||||
line = line.strip('\n')
|
||||
def compress_dialogue(fname, lang):
|
||||
lines = []
|
||||
for line in open(fname, encoding='utf8').read().splitlines():
|
||||
a, b = line.split(': ', 1)
|
||||
index = int(a)
|
||||
offs.append(len(new_r))
|
||||
r = text_compression.compress_string(b)
|
||||
new_r.extend(r)
|
||||
|
||||
add_asset_uint16('kDialogueOffs', offs)
|
||||
add_asset_uint8('kDialogueText', new_r)
|
||||
|
||||
ROM = util.LoadedRom(sys.argv[1] if len(sys.argv) >= 2 else None)
|
||||
|
||||
kCompSpritePtrs = [
|
||||
0x10f000,0x10f600,0x10fc00,0x118200,0x118800,0x118e00,0x119400,0x119a00,
|
||||
0x11a000,0x11a600,0x11ac00,0x11b200,0x14fffc,0x1585d4,0x158ab6,0x158fbe,
|
||||
0x1593f8,0x1599a6,0x159f32,0x15a3d7,0x15a8f1,0x15aec6,0x15b418,0x15b947,
|
||||
0x15bed0,0x15c449,0x15c975,0x15ce7c,0x15d394,0x15d8ac,0x15ddc0,0x15e34c,
|
||||
0x15e8e8,0x15ee31,0x15f3a6,0x15f92d,0x15feba,0x1682ff,0x1688e0,0x168e41,
|
||||
0x1692df,0x169883,0x169cd0,0x16a26e,0x16a275,0x16a787,0x16aa06,0x16ae9d,
|
||||
0x16b3ff,0x16b87e,0x16be6b,0x16c13d,0x16c619,0x16cbbb,0x16d0f1,0x16d641,
|
||||
0x16d95a,0x16dd99,0x16e278,0x16e760,0x16ed25,0x16f20f,0x16f6b7,0x16fa5f,
|
||||
0x16fd29,0x1781cd,0x17868d,0x178b62,0x178fd5,0x179527,0x17994b,0x179ea7,
|
||||
0x17a30e,0x17a805,0x17acf8,0x17b2a2,0x17b7f9,0x17bc93,0x17c237,0x17c78e,
|
||||
0x17cd55,0x17d2bc,0x17d82f,0x17dcec,0x17e1cc,0x17e36b,0x17e842,0x17eb38,
|
||||
0x17ed58,0x17f06c,0x17f4fd,0x17fa39,0x17ff86,0x18845c,0x1889a1,0x188d64,
|
||||
0x18919d,0x189610,0x189857,0x189b24,0x189dd2,0x18a03f,0x18a4ed,0x18a7ba,
|
||||
0x18aedf,0x18af0d,0x18b520,0x18b953,
|
||||
]
|
||||
|
||||
kCompBgPtrs = [
|
||||
0x11b800,0x11bce2,0x11c15f,0x11c675,0x11cb84,0x11cf4c,0x11d2ce,0x11d726,
|
||||
0x11d9cf,0x11dec4,0x11e393,0x11e893,0x11ed7d,0x11f283,0x11f746,0x11fc21,
|
||||
0x11fff2,0x128498,0x128a0e,0x128f30,0x129326,0x129804,0x129d5b,0x12a272,
|
||||
0x12a6fe,0x12aa77,0x12ad83,0x12b167,0x12b51d,0x12b840,0x12bd54,0x12c1c9,
|
||||
0x12c73d,0x12cc86,0x12d198,0x12d6b1,0x12db6a,0x12e0ea,0x12e6bd,0x12eb51,
|
||||
0x12f135,0x12f6c5,0x12fc71,0x138129,0x138693,0x138bad,0x139117,0x139609,
|
||||
0x139b21,0x13a074,0x13a619,0x13ab2b,0x13b00c,0x13b4f5,0x13b9eb,0x13bebf,
|
||||
0x13c3ce,0x13c817,0x13cb68,0x13cfb5,0x13d460,0x13d8c2,0x13dd7a,0x13e266,
|
||||
0x13e7af,0x13ece5,0x13f245,0x13f6f0,0x13fc30,0x1480e9,0x14863b,0x148a7c,
|
||||
0x148f2a,0x149346,0x1497ed,0x149cc2,0x14a173,0x14a61d,0x14ab5d,0x14b083,
|
||||
0x14b4bd,0x14b94e,0x14be0e,0x14c291,0x14c7ba,0x14cce4,0x14d1db,0x14d6bd,
|
||||
0x14db77,0x14ded1,0x14e2ac,0x14e754,0x14ebae,0x14ef4e,0x14f309,0x14f6f4,
|
||||
0x14fa55,0x14ff8c,0x14ff93,0x14ff9a,0x14ffa1,0x14ffa8,0x14ffaf,0x14ffb6,
|
||||
0x14ffbd,0x14ffc4,0x14ffcb,0x14ffd2,0x14ffd9,0x14ffe0,0x14ffe7,0x14ffee,
|
||||
0x14fff5,0x18b520,0x18b953,
|
||||
]
|
||||
lines.append(b)
|
||||
return text_compression.compress_strings(lines, lang)
|
||||
|
||||
def compress_store(r):
|
||||
rr = []
|
||||
@@ -127,40 +85,73 @@ def compress_store(r):
|
||||
rr.append(0xff)
|
||||
return rr
|
||||
|
||||
def pack_u32_arrays(arr):
|
||||
all_offs, offs = [], len(arr) * 4
|
||||
for i in range(len(arr)):
|
||||
all_offs.append(offs)
|
||||
# Pack arrays and determine automatically the index size
|
||||
def pack_arrays(arr):
|
||||
if len(arr) == 0:
|
||||
return b''
|
||||
all_offs, offs = [], 0
|
||||
for i in range(len(arr) - 1):
|
||||
offs += len(arr[i])
|
||||
return b''.join([struct.pack('I', i) for i in all_offs] + arr)
|
||||
all_offs.append(offs)
|
||||
if offs < 65536 and len(arr) <= 8192:
|
||||
return b''.join([struct.pack('H', i) for i in all_offs] + arr + [struct.pack('H', len(arr) - 1)])
|
||||
else:
|
||||
return b''.join([struct.pack('I', i) for i in all_offs] + arr + [struct.pack('H', 8192 + len(arr) - 1)])
|
||||
|
||||
def print_images():
|
||||
lengths = b''
|
||||
all = []
|
||||
for i in range(12):
|
||||
all.append(bytes(ROM.get_bytes(kCompSpritePtrs[i], 0x600)))
|
||||
for i in range(12, 108):
|
||||
decomp, comp_len = util.decomp(kCompSpritePtrs[i], ROM.get_byte, False, True)
|
||||
all.append(bytes(ROM.get_bytes(kCompSpritePtrs[i], comp_len)))
|
||||
add_asset_uint8('kSprGfx', pack_u32_arrays(all))
|
||||
def print_images(args):
|
||||
sprsheet = sprite_sheets.load_sprite_sheets() if args.sprites_from_png else None
|
||||
|
||||
all = []
|
||||
for i in range(len(kCompBgPtrs)):
|
||||
decomp, comp_len = util.decomp(kCompBgPtrs[i], ROM.get_byte, False, True)
|
||||
all.append(bytes(ROM.get_bytes(kCompBgPtrs[i], comp_len)))
|
||||
add_asset_uint8('kBgGfx', pack_u32_arrays(all))
|
||||
for i in range(108):
|
||||
if sprsheet != None and i < 103:
|
||||
all.append(sprsheet.encode_sheet_in_snes_format(i))
|
||||
elif i < 12:
|
||||
all.append(bytes(ROM.get_bytes(tables.kCompSpritePtrs[i], 0x600)))
|
||||
else:
|
||||
decomp, comp_len = util.decomp(tables.kCompSpritePtrs[i], ROM.get_byte, False, True)
|
||||
all.append(bytes(ROM.get_bytes(tables.kCompSpritePtrs[i], comp_len)))
|
||||
add_asset_packed('kSprGfx', all)
|
||||
|
||||
all = []
|
||||
for i in range(len(tables.kCompBgPtrs)):
|
||||
decomp, comp_len = util.decomp(tables.kCompBgPtrs[i], ROM.get_byte, False, True)
|
||||
all.append(bytes(ROM.get_bytes(tables.kCompBgPtrs[i], comp_len)))
|
||||
add_asset_packed('kBgGfx', all)
|
||||
|
||||
def print_dialogue(args):
|
||||
from text_compression import dialogue_filename, kLanguages
|
||||
|
||||
def print_misc():
|
||||
languages = ['us']
|
||||
if args.languages:
|
||||
for a in args.languages.split(','):
|
||||
if a in languages or a not in kLanguages:
|
||||
raise Exception(f'Language {a} is not valid')
|
||||
name = dialogue_filename(a)
|
||||
if not os.path.exists(name):
|
||||
raise Exception(f'{name} not found. You need to extract it with --extract-dialogue using the ROM of that language.')
|
||||
languages.append(a)
|
||||
|
||||
all_langs, all_fonts, mappings = [], [], []
|
||||
for i, lang in enumerate(languages):
|
||||
dict_packed = pack_arrays(text_compression.encode_dictionary(lang))
|
||||
dialogue_packed = pack_arrays(compress_dialogue(dialogue_filename(lang), lang))
|
||||
all_langs.append(pack_arrays([dict_packed, dialogue_packed]))
|
||||
font_data, font_width = sprite_sheets.encode_font_from_png(lang)
|
||||
all_fonts.append(pack_arrays([font_data, font_width]))
|
||||
flags = text_compression.uses_new_format(lang)
|
||||
if i != 0: flags |= 2 # no us rom match?
|
||||
mappings.append(pack_arrays([lang.encode('utf8'), bytearray([i, i, flags])]))
|
||||
add_asset_packed('kDialogue', all_langs)
|
||||
add_asset_packed('kDialogueFont', all_fonts)
|
||||
add_asset_packed('kDialogueMap', mappings)
|
||||
|
||||
def print_misc(args):
|
||||
add_asset_uint8('kOverworldMapGfx', ROM.get_bytes(0x18c000, 0x4000))
|
||||
add_asset_uint8('kLightOverworldTilemap', ROM.get_bytes(0xac727, 4096))
|
||||
add_asset_uint8('kDarkOverworldTilemap', ROM.get_bytes(0xaD727, 1024))
|
||||
|
||||
add_asset_uint16('kPredefinedTileData', ROM.get_words(0x9B52, 6438))
|
||||
|
||||
add_asset_uint16('kFontData', ROM.get_words(0xe8000, 2048))
|
||||
|
||||
add_asset_uint16('kMap16ToMap8', ROM.get_words(0x8f8000, 3752 * 4))
|
||||
|
||||
add_asset_uint8('kGeneratedWishPondItem', ROM.get_bytes(0x888450, 256))
|
||||
@@ -176,7 +167,7 @@ def print_misc():
|
||||
add_asset_uint16('kPalette_DungBgMain', ROM.get_words(0x9BD734, 1800))
|
||||
add_asset_uint16('kPalette_MainSpr', ROM.get_words(0x9BD218, 120))
|
||||
|
||||
add_asset_uint16('kPalette_ArmorAndGloves', ROM.get_words(0x9BD308, 75))
|
||||
add_asset_uint16('kPalette_ArmorAndGloves', sprite_sheets.override_armor_palette or ROM.get_words(0x9BD308, 75))
|
||||
add_asset_uint16('kPalette_Sword', ROM.get_words(0x9BD630, 12))
|
||||
add_asset_uint16('kPalette_Shield', ROM.get_words(0x9BD648, 12))
|
||||
|
||||
@@ -193,27 +184,24 @@ def print_misc():
|
||||
|
||||
add_asset_uint16('kOverworldMapPaletteData', ROM.get_words(0x8ADB27, 256))
|
||||
|
||||
g_overworld_yaml_cache = {}
|
||||
@cache
|
||||
def load_overworld_yaml(room):
|
||||
if room not in g_overworld_yaml_cache:
|
||||
g_overworld_yaml_cache[room] = yaml.safe_load(open(PATH+'overworld/overworld-%d.yaml' % room, 'r'))
|
||||
return g_overworld_yaml_cache[room]
|
||||
return yaml.safe_load(open('overworld/overworld-%d.yaml' % room, 'r'))
|
||||
|
||||
|
||||
def print_overworld():
|
||||
r = []
|
||||
for i in range(160):
|
||||
addr = ROM.get_24(0x82F94D + i * 3)
|
||||
decomp, comp_len = util.decomp(addr, ROM.get_byte, True, True)
|
||||
r.append(bytes(ROM.get_bytes(addr, comp_len)))
|
||||
add_asset_uint8('kOverworld_Hibytes_Comp', pack_u32_arrays(r))
|
||||
add_asset_packed('kOverworld_Hibytes_Comp', r)
|
||||
|
||||
r = []
|
||||
for i in range(160):
|
||||
addr = ROM.get_24(0x82FB2D + i * 3)
|
||||
decomp, comp_len = util.decomp(addr, ROM.get_byte, True, True)
|
||||
r.append(bytes(ROM.get_bytes(addr, comp_len)))
|
||||
add_asset_uint8('kOverworld_Lobytes_Comp', pack_u32_arrays(r))
|
||||
add_asset_packed('kOverworld_Lobytes_Comp', r)
|
||||
|
||||
def is_area_head(i):
|
||||
return i >= 128 or ROM.get_byte(0x82A5EC + (i & 63)) == (i & 63)
|
||||
@@ -461,15 +449,13 @@ def print_dungeon_map():
|
||||
b = ROM.get_bytes(addr, nonzero_bytes)
|
||||
r2.append(bytes(b))
|
||||
|
||||
add_asset_uint8('kDungMap_FloorLayout', pack_u32_arrays(r))
|
||||
add_asset_uint8('kDungMap_Tiles', pack_u32_arrays(r2))
|
||||
add_asset_packed('kDungMap_FloorLayout', r)
|
||||
add_asset_packed('kDungMap_Tiles', r2)
|
||||
|
||||
|
||||
g_dungeon_yaml_cache = {}
|
||||
@cache
|
||||
def load_dungeon_yaml(room):
|
||||
if room not in g_dungeon_yaml_cache:
|
||||
g_dungeon_yaml_cache[room] = yaml.safe_load(open(PATH+'dungeon/dungeon-%d.yaml' % room, 'r'))
|
||||
return g_dungeon_yaml_cache[room]
|
||||
return yaml.safe_load(open('dungeon/dungeon-%d.yaml' % room, 'r'))
|
||||
|
||||
def print_dungeon_sprites():
|
||||
offsets=[0 for i in range(320)]
|
||||
@@ -700,7 +686,7 @@ def print_dungeon_rooms():
|
||||
|
||||
data = []
|
||||
offsets = [0] * 8
|
||||
default_yaml = yaml.safe_load(open(PATH+'dungeon/default_rooms.yaml', 'r'))
|
||||
default_yaml = yaml.safe_load(open('dungeon/default_rooms.yaml', 'r'))
|
||||
for i in range(len(offsets)):
|
||||
offsets[i] = len(data)
|
||||
print_layer(default_yaml['Default%d' % i], None)
|
||||
@@ -709,7 +695,7 @@ def print_dungeon_rooms():
|
||||
|
||||
data = []
|
||||
offsets = [0] * 19
|
||||
overlay_yaml = yaml.safe_load(open(PATH+'dungeon/overlay_rooms.yaml', 'r'))
|
||||
overlay_yaml = yaml.safe_load(open('dungeon/overlay_rooms.yaml', 'r'))
|
||||
for i in range(len(offsets)):
|
||||
offsets[i] = len(data)
|
||||
print_layer(overlay_yaml['Overlay%d' % i], None)
|
||||
@@ -724,8 +710,6 @@ def print_dungeon_rooms():
|
||||
add_asset_uint16('kMovableBlockDataInit', ROM.get_words(0x84f1de, 198))
|
||||
add_asset_uint16('kTorchDataInit', ROM.get_words(0x84F36A, 144))
|
||||
add_asset_uint16('kTorchDataJunk', ROM.get_words(0x84F48a, 48))
|
||||
|
||||
|
||||
|
||||
def print_enemy_damage_data():
|
||||
decomp, comp_len = util.decomp(0x83e800, ROM.get_byte, True, True)
|
||||
@@ -746,7 +730,7 @@ def print_tilemaps():
|
||||
add_asset_uint8('kBgTilemap_%d' % i, ROM.get_bytes(s, l))
|
||||
|
||||
def print_link_graphics():
|
||||
image = Image.open(PATH+'linksprite.png')
|
||||
image = Image.open('linksprite.png')
|
||||
data = image.tobytes()
|
||||
def encode_4bit_sprite(data, offset, pitch):
|
||||
b = [0] * 32
|
||||
@@ -769,25 +753,21 @@ def print_sound_banks():
|
||||
name, data = compile_music.print_song(song)
|
||||
add_asset_uint8(name, data)
|
||||
|
||||
|
||||
def print_all():
|
||||
def print_all(args):
|
||||
print_sound_banks()
|
||||
print_dungeon_rooms()
|
||||
print_enemy_damage_data()
|
||||
print_link_graphics()
|
||||
print_dungeon_sprites()
|
||||
print_map32_to_map16()
|
||||
print_dialogue()
|
||||
print_images()
|
||||
print_misc()
|
||||
print_images(args)
|
||||
print_misc(args)
|
||||
print_dialogue(args)
|
||||
print_dungeon_map()
|
||||
print_tilemaps()
|
||||
print_overworld()
|
||||
print_overworld_tables()
|
||||
|
||||
print_all()
|
||||
|
||||
|
||||
def write_assets_to_file(print_header = False):
|
||||
key_sig = b''
|
||||
all_data = []
|
||||
@@ -799,12 +779,17 @@ enum {
|
||||
kNumberOfAssets = %d
|
||||
};
|
||||
extern const uint8 *g_asset_ptrs[kNumberOfAssets];
|
||||
extern uint32 g_asset_sizes[kNumberOfAssets];''' % len(assets))
|
||||
extern uint32 g_asset_sizes[kNumberOfAssets];
|
||||
extern MemBlk FindInAssetArray(int asset, int idx);
|
||||
''' % len(assets))
|
||||
|
||||
for i, (k, (tp, data)) in enumerate(assets.items()):
|
||||
if print_header:
|
||||
print('#define %s ((%s*)g_asset_ptrs[%d])' % (k, tp, i))
|
||||
print('#define %s_SIZE (g_asset_sizes[%d])' % (k, i))
|
||||
if tp == 'packed':
|
||||
print('#define %s(idx) FindInAssetArray(%d, idx)' % (k, i))
|
||||
else:
|
||||
print('#define %s ((%s*)g_asset_ptrs[%d])' % (k, tp, i))
|
||||
print('#define %s_SIZE (g_asset_sizes[%d])' % (k, i))
|
||||
key_sig += k.encode('utf8') + b'\0'
|
||||
all_data.append(data)
|
||||
|
||||
@@ -824,8 +809,19 @@ extern uint32 g_asset_sizes[kNumberOfAssets];''' % len(assets))
|
||||
file_data += b'\0'
|
||||
file_data += v
|
||||
|
||||
open('zelda3_assets.dat', 'wb').write(file_data)
|
||||
open('../zelda3_assets.dat', 'wb').write(file_data)
|
||||
|
||||
write_assets_to_file(False)
|
||||
def main(args):
|
||||
print_all(args)
|
||||
write_assets_to_file(args.print_assets_header)
|
||||
|
||||
if __name__ == "__main__":
|
||||
ROM = util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None)
|
||||
class DefaultArgs:
|
||||
sprites_from_png = False
|
||||
languages = None
|
||||
print_assets_header = False
|
||||
main(DefaultArgs())
|
||||
else:
|
||||
ROM = util.ROM
|
||||
|
||||
@@ -2,30 +2,12 @@ from ast import literal_eval as make_tuple
|
||||
import sys
|
||||
import text_compression
|
||||
import util
|
||||
from PIL import Image
|
||||
from util import get_bytes, get_words, get_byte, get_word, get_int8, get_int16, cache
|
||||
import tables
|
||||
import yaml
|
||||
import extract_music
|
||||
import os
|
||||
|
||||
PATH=''
|
||||
|
||||
ROM = util.LoadedRom(sys.argv[1] if len(sys.argv) >= 2 else None)
|
||||
|
||||
get_byte = ROM.get_byte
|
||||
get_word = ROM.get_word
|
||||
get_bytes = ROM.get_bytes
|
||||
|
||||
def get_int8(ea):
|
||||
b = get_byte(ea)
|
||||
if b & 0x80: b -= 256
|
||||
return b
|
||||
|
||||
def get_int16(ea):
|
||||
b = get_word(ea)
|
||||
if b & 0x8000: b -= 65536
|
||||
return b
|
||||
|
||||
import sprite_sheets
|
||||
|
||||
def print_map32_to_map16(f):
|
||||
for i in range(2218):
|
||||
@@ -44,6 +26,7 @@ def print_map32_to_map16(f):
|
||||
for j in range(4):
|
||||
print('%5d: %4d, %4d, %4d, %4d' % (i * 4 + j, t0[j], t1[j], t2[j], t3[j]), file = f)
|
||||
|
||||
@cache
|
||||
def get_exit_datas():
|
||||
r = {}
|
||||
for i in range(79):
|
||||
@@ -99,7 +82,6 @@ def get_exit_datas():
|
||||
y['door'] = ['palace' if fdoor & 0x8000 else 'sanctuary', (fdoor & 0x7e) >> 1, (fdoor & 0x3f80) >> 7]
|
||||
r.setdefault(screen_index, []).append(y)
|
||||
return r
|
||||
EXIT_DATAS = get_exit_datas()
|
||||
|
||||
def get_loadoffs(c, d):
|
||||
x, y = c[0] >> 4, c[1] >> 4
|
||||
@@ -107,6 +89,7 @@ def get_loadoffs(c, d):
|
||||
y += d[1]
|
||||
return (y&0x3f) << 7 | (x&0x3f) << 1
|
||||
|
||||
@cache
|
||||
def get_ow_travel_infos():
|
||||
r = {}
|
||||
for i in range(17):
|
||||
@@ -138,8 +121,8 @@ def get_ow_travel_infos():
|
||||
y['unk'] = [unk1, unk3]
|
||||
r.setdefault(screen_index, []).append(y)
|
||||
return r
|
||||
OW_TRAVEL_INFOS = get_ow_travel_infos()
|
||||
|
||||
@cache
|
||||
def get_ow_entrance_info():
|
||||
r = {}
|
||||
for i in range(129):
|
||||
@@ -148,8 +131,8 @@ def get_ow_entrance_info():
|
||||
entrance_id = get_byte(0x9BBB73 + i)
|
||||
r.setdefault(area, []).append({'index' : i, 'x' : (pos >> 1) & 0x3f, 'y' : (pos >> 7) & 0x3f, 'entrance_id' : entrance_id})
|
||||
return r
|
||||
OW_ENTRANCE_INFO = get_ow_entrance_info()
|
||||
|
||||
@cache
|
||||
def get_hole_infos():
|
||||
r = {}
|
||||
for i in range(19):
|
||||
@@ -158,7 +141,6 @@ def get_hole_infos():
|
||||
entrance_id = get_byte(0x9BB84C + i)
|
||||
r.setdefault(area, []).append({'x' : (pos >> 1) & 0x3f, 'y' : (pos >> 7) & 0x3f, 'entrance_id' : entrance_id})
|
||||
return r
|
||||
HOLE_INFO = get_hole_infos()
|
||||
|
||||
def print_overworld_area(overworld_area):
|
||||
is_small = get_bytes(0x82F88D, 192)
|
||||
@@ -204,11 +186,12 @@ def print_overworld_area(overworld_area):
|
||||
}
|
||||
|
||||
y['Header'] = header
|
||||
y['Travel'] = OW_TRAVEL_INFOS.get(overworld_area, [])
|
||||
y['Entrances'] = OW_ENTRANCE_INFO.get(overworld_area, [])
|
||||
if overworld_area in HOLE_INFO:
|
||||
y['Holes'] = HOLE_INFO[overworld_area]
|
||||
y['Exits'] = EXIT_DATAS.get(overworld_area, [])
|
||||
y['Travel'] = get_ow_travel_infos().get(overworld_area, [])
|
||||
y['Entrances'] = get_ow_entrance_info().get(overworld_area, [])
|
||||
hole_infos = get_hole_infos()
|
||||
if overworld_area in hole_infos:
|
||||
y['Holes'] = hole_infos[overworld_area]
|
||||
y['Exits'] = get_exit_datas().get(overworld_area, [])
|
||||
y['Items'] = get_items()
|
||||
|
||||
def decode_sprites(base_addr):
|
||||
@@ -247,135 +230,17 @@ def print_overworld_area(overworld_area):
|
||||
}
|
||||
|
||||
s = yaml.dump(y, default_flow_style=None, sort_keys=False)
|
||||
open(PATH+'overworld/overworld-%d.yaml' % overworld_area, 'w').write(s)
|
||||
open('overworld/overworld-%d.yaml' % overworld_area, 'w').write(s)
|
||||
|
||||
def print_all_overworld_areas():
|
||||
area_heads = ROM.get_bytes(0x82A5EC, 64)
|
||||
area_heads = get_bytes(0x82A5EC, 64)
|
||||
|
||||
for i in range(160):
|
||||
if i >= 128 or area_heads[i&63] == (i&63):
|
||||
print_overworld_area(i)
|
||||
|
||||
def print_dialogue():
|
||||
text_compression.print_strings(open('dialogue.txt', 'w'), get_byte)
|
||||
|
||||
def decomp_one_spr_2bit(data, offs, target, toffs, pitch):
|
||||
for y in range(8):
|
||||
d0, d1 = data[offs + y * 2], data[offs + y * 2 + 1]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2
|
||||
target[toffs + y * pitch + (7 - x)] = t * 255 // 3
|
||||
|
||||
def decomp_one_spr_3bit(data, offs, target, toffs, pitch):
|
||||
for y in range(8):
|
||||
d0, d1, d2 = data[offs + y * 2], data[offs + y * 2 + 1], data[offs + y + 16]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2 + ((d2 >> x) & 1) * 4
|
||||
target[toffs + y * pitch + (7 - x)] = t * 255 // 7
|
||||
|
||||
def decomp_one_spr_4bit(data, offs, target, toffs, pitch):
|
||||
for y in range(8):
|
||||
d0, d1, d2, d3 = data[offs + y * 2 + 0], data[offs + y * 2 + 1], data[offs + y * 2 + 16], data[offs + y * 2 + 17]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2 + ((d2 >> x) & 1) * 4 + ((d3 >> x) & 1) * 8
|
||||
target[toffs + y * pitch + (7 - x)] = t
|
||||
|
||||
def save_array_as_image(dimensions, data, fname, palette = None):
|
||||
img = Image.new('L' if palette == None else 'P', dimensions)
|
||||
img.putdata(data)
|
||||
if palette != None:
|
||||
img.putpalette(palette)
|
||||
img.save(fname)
|
||||
|
||||
|
||||
def decomp_save(data, fname, func, step, height=32, palette=None):
|
||||
dst=[0]*128*height
|
||||
for i in range(16*height//8):
|
||||
x = i % 16
|
||||
y = i // 16
|
||||
func(data, i * step, dst, x * 8 + y * 8 * 128, 128)
|
||||
save_array_as_image((128, height), dst, fname, palette)
|
||||
|
||||
def convert_snes_palette(v):
|
||||
r=[]
|
||||
for x in v:
|
||||
r.extend(((x & 0x1f) << 3, (x >> 5 & 0x1f) << 3, (x >> 10 & 0x1f) << 3))
|
||||
return r
|
||||
|
||||
|
||||
def decode_link_sprites():
|
||||
kLinkPalette = [0, 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x2a5c, 0x1571, 0x7a18]
|
||||
decomp_save(get_bytes(0x108000, 32768), PATH+'linksprite.png',
|
||||
decomp_one_spr_4bit, 32, 448, convert_snes_palette(kLinkPalette))
|
||||
|
||||
def decomp_save_2bit(data, fname):
|
||||
dst=[0]*128*64
|
||||
for i in range(128):
|
||||
x = i % 16
|
||||
y = i // 16
|
||||
decomp_one_spr_2bit(data, i * 16, dst, x * 8 + y * 8 * 128, 128)
|
||||
img = Image.new("L", (128, 64))
|
||||
img.putdata(dst)
|
||||
img.save(fname)
|
||||
|
||||
|
||||
gfx_desc = {
|
||||
0x00 : ('3bpp_np', 'Half slot images'),
|
||||
|
||||
0x01 : ('3bpp_np', 'Half slot images'),
|
||||
0x02 : ('3bpp_np', 'Half slot images'),
|
||||
0x03 : ('3bpp_np', 'Half slot images'),
|
||||
0x04 : ('3bpp_np', 'Half slot images'),
|
||||
0x05 : ('3bpp_np', 'Half slot images'),
|
||||
|
||||
0x06 : ('3bpp_np', 'common spr gfx'),
|
||||
0x07 : ('3bpp_np', 'common spr gfx'),
|
||||
|
||||
0x08 : ('3bpp_np', 'Half slot images'),
|
||||
0x09 : ('3bpp_np', 'Half slot images'),
|
||||
0x0a : ('3bpp_np', 'Half slot images'),
|
||||
0x0b : ('3bpp_np', 'Half slot images'),
|
||||
|
||||
88 : ('3bpp', 'Tagalong gfx'),
|
||||
89 : ('3bpp', 'Tagalong gfx'),
|
||||
|
||||
90 : ('3bpp', 'animated sprites'),
|
||||
91 : ('3bpp', 'animated sprites'),
|
||||
92 : ('3bpp', 'animated sprites'),
|
||||
93 : ('3bpp', 'animated sprites'),
|
||||
94 : ('3bpp', 'sword/shield gfx'),
|
||||
95 : ('3bpp', 'sword/shield gfx'),
|
||||
|
||||
|
||||
100 : ('3bpp', 'Tagalong gfx'),
|
||||
101 : ('3bpp', 'Tagalong gfx'),
|
||||
102 : ('3bpp', 'Tagalong gfx'),
|
||||
|
||||
103 : ('2bpp', 'Attract images loaded to 7800'),
|
||||
104 : ('2bpp', 'empty'),
|
||||
105 : ('2bpp', 'hud icons loaded to 7xxx'),
|
||||
106 : ('2bpp', 'hud icons loaded to 7xxx'),
|
||||
107 : ('2bpp', 'hud icons loaded to 7xxx'),
|
||||
}
|
||||
|
||||
def decomp_generic(k, mode, fname_add = "misc"):
|
||||
fname = 'img/%.3d - %s.png' % (k, fname_add)
|
||||
if mode == '2bpp':
|
||||
r = util.decomp(kCompSpritePtrs[k], get_byte, False)
|
||||
decomp_save_2bit(r, fname)
|
||||
elif mode == '3bpp_np':
|
||||
print(k)
|
||||
r = get_bytes(kCompSpritePtrs[k], 0x600)
|
||||
decomp_save(r, fname, decomp_one_spr_3bit, 24)
|
||||
elif mode== '3bpp':
|
||||
r = util.decomp(kCompSpritePtrs[k], get_byte, False)
|
||||
decomp_save(r, fname, decomp_one_spr_3bit, 24)
|
||||
else:
|
||||
assert 0
|
||||
|
||||
if 0:
|
||||
for k, v in gfx_desc.items():
|
||||
decomp_generic(k, v[0])
|
||||
text_compression.print_strings(util.ROM, file = open(text_compression.dialogue_filename(util.ROM.language), 'w', encoding='utf8'))
|
||||
|
||||
def decode_room_objects(p):
|
||||
objs = []
|
||||
@@ -418,6 +283,7 @@ def decode_room_objects(p):
|
||||
doors.append({'type':get_byte(p + 1), 'pos' : get_byte(p) >> 4, 'dir' : A & 3})
|
||||
p += 2
|
||||
|
||||
@cache
|
||||
def get_chest_info():
|
||||
ea = 0x81e96e
|
||||
all = {}
|
||||
@@ -427,8 +293,6 @@ def get_chest_info():
|
||||
all.setdefault(room & 0x7fff, []).append((data, (room & 0x8000) != 0))
|
||||
return all
|
||||
|
||||
CHEST_INFO = get_chest_info()
|
||||
|
||||
def _get_entrance_info_one(i, set):
|
||||
def get_exit_door(i):
|
||||
x = get_word((0x82D724, 0x82DC32)[set] + i * 2)
|
||||
@@ -498,7 +362,7 @@ def _get_entrance_info_one(i, set):
|
||||
y['associated_entrance_index'] = get_word(0x82DC40 + i * 2)
|
||||
return room, y
|
||||
|
||||
|
||||
@cache
|
||||
def get_entrance_info(set):
|
||||
r = {}
|
||||
for i in range(133 if set == 0 else 7):
|
||||
@@ -506,9 +370,9 @@ def get_entrance_info(set):
|
||||
r.setdefault(room, []).append(y)
|
||||
return r
|
||||
|
||||
ENTRANCE_INFO = get_entrance_info(0)
|
||||
STARTING_POINT_INFO = get_entrance_info(1)
|
||||
PITS_HURT_PLAYER = set(get_word(0x80990C + i * 2) for i in range(57))
|
||||
@cache
|
||||
def pits_hurt_player():
|
||||
return set(get_word(0x80990C + i * 2) for i in range(57))
|
||||
|
||||
def print_room(room_index):
|
||||
p = 0x1f8000 + room_index * 3
|
||||
@@ -545,7 +409,7 @@ def print_room(room_index):
|
||||
'stair3_dest' : [get_byte(p+13),p8 & 3],
|
||||
'tele_msg' : get_word(0x87F61D+room_index*2),
|
||||
'sort_sprites' : sort_sprites_setting,
|
||||
'pits_hurt_player' : room_index in PITS_HURT_PLAYER
|
||||
'pits_hurt_player' : room_index in pits_hurt_player()
|
||||
}
|
||||
|
||||
def get_sprites():
|
||||
@@ -587,7 +451,7 @@ def print_room(room_index):
|
||||
|
||||
def get_chests():
|
||||
r = []
|
||||
for data, big in CHEST_INFO.get(room_index, []):
|
||||
for data, big in get_chest_info().get(room_index, []):
|
||||
if big:
|
||||
r.append('%d!' % data)
|
||||
else:
|
||||
@@ -598,9 +462,9 @@ def print_room(room_index):
|
||||
secrets = get_secrets()
|
||||
|
||||
data = {'Header' : header, 'Sprites' : sprites, 'Secrets' : secrets, 'Chests' : get_chests()}
|
||||
data['Entrances'] = ENTRANCE_INFO.get(room_index, [])
|
||||
if room_index in STARTING_POINT_INFO:
|
||||
data['StartingPoints'] = STARTING_POINT_INFO[room_index]
|
||||
data['Entrances'] = get_entrance_info(0).get(room_index, [])
|
||||
if room_index in get_entrance_info(1):
|
||||
data['StartingPoints'] = get_entrance_info(1)[room_index]
|
||||
|
||||
p = room_addr + 2
|
||||
p, objs, doors = decode_room_objects(p)
|
||||
@@ -615,13 +479,12 @@ def print_room(room_index):
|
||||
data['Layer3'] = objs
|
||||
if doors: data['Layer3.doors'] = doors
|
||||
|
||||
|
||||
return yaml.dump(data, default_flow_style=None, sort_keys=False)
|
||||
|
||||
def print_all_dungeon_rooms():
|
||||
for i in range(320):
|
||||
s = print_room(i)
|
||||
open(PATH + 'dungeon/dungeon-%d.yaml' % i, 'w').write(s)
|
||||
open( 'dungeon/dungeon-%d.yaml' % i, 'w').write(s)
|
||||
|
||||
def print_default_rooms():
|
||||
def print_default_room(idx):
|
||||
@@ -634,7 +497,7 @@ def print_default_rooms():
|
||||
for i in range(8):
|
||||
default_rooms['Default%d' % i] = print_default_room(i)
|
||||
s = yaml.dump(default_rooms, default_flow_style=None, sort_keys=False)
|
||||
open(PATH + 'dungeon/default_rooms.yaml', 'w').write(s)
|
||||
open('dungeon/default_rooms.yaml', 'w').write(s)
|
||||
|
||||
def print_overlay_rooms():
|
||||
def print_overlay_room(idx):
|
||||
@@ -647,21 +510,32 @@ def print_overlay_rooms():
|
||||
for i in range(19):
|
||||
default_rooms['Overlay%d' % i] = print_overlay_room(i)
|
||||
s = yaml.dump(default_rooms, default_flow_style=None, sort_keys=False)
|
||||
open(PATH + 'dungeon/overlay_rooms.yaml', 'w').write(s)
|
||||
open('dungeon/overlay_rooms.yaml', 'w').write(s)
|
||||
|
||||
def print_all():
|
||||
def make_directories():
|
||||
os.makedirs('img', exist_ok=True)
|
||||
os.makedirs('overworld', exist_ok=True)
|
||||
os.makedirs('dungeon', exist_ok=True)
|
||||
os.makedirs('sound', exist_ok=True)
|
||||
|
||||
def print_all_text_stuff():
|
||||
print_all_overworld_areas()
|
||||
decode_link_sprites()
|
||||
print_all_dungeon_rooms()
|
||||
print_overlay_rooms()
|
||||
print_default_rooms()
|
||||
print_dialogue()
|
||||
print_map32_to_map16(open(PATH+'map32_to_map16.txt', 'w'))
|
||||
extract_music.extract_sound_data(ROM)
|
||||
print_map32_to_map16(open('map32_to_map16.txt', 'w'))
|
||||
|
||||
def main():
|
||||
make_directories()
|
||||
print_all_text_stuff()
|
||||
extract_music.extract_sound_data(util.ROM)
|
||||
sprite_sheets.decode_link_sprites()
|
||||
sprite_sheets.decode_sprite_sheets()
|
||||
sprite_sheets.decode_hud_icons()
|
||||
sprite_sheets.decode_font()
|
||||
|
||||
if __name__ == "__main__":
|
||||
util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None)
|
||||
main()
|
||||
|
||||
print_all()
|
||||
BIN
assets/palette_usage.bin
Normal file
BIN
assets/palette_usage.bin
Normal file
Binary file not shown.
52
assets/restool.py
Normal file
52
assets/restool.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import argparse
|
||||
import util
|
||||
import sys
|
||||
import os
|
||||
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Resource tool used to build zelda3_assets.dat', allow_abbrev=False)
|
||||
parser.add_argument('-r', '--rom', nargs='?', metavar='ROM')
|
||||
parser.add_argument('--extract-from-rom', '-e', action='store_true', help='Extract/overwrite things from the ROM')
|
||||
|
||||
optional = parser.add_argument_group('Language settings')
|
||||
optional.add_argument('--extract-dialogue', action='store_true', help = 'Extract dialogue from a translated ROM')
|
||||
optional.add_argument('--languages', action='store', metavar='L1,L2', help = 'Comma separated list of additional languages to build (de,fr,fr-c,en,es,pl,pt,redux,nl,sv).')
|
||||
|
||||
optional = parser.add_argument_group('Debug things')
|
||||
optional.add_argument('--no-build', action='store_true', help="Don't actually build zelda3_assets.dat")
|
||||
optional.add_argument('--print-strings', action='store_true', help="Print all dialogue strings")
|
||||
optional.add_argument('--print-assets-header', action='store_true')
|
||||
|
||||
optional = parser.add_argument_group('Image handling')
|
||||
optional.add_argument('--sprites-from-png', action='store_true', help="When compiling, load sprites from png instead of from ROM")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.extract_dialogue:
|
||||
ROM = util.load_rom(args.rom, True)
|
||||
import extract_resources, sprite_sheets
|
||||
sprite_sheets.decode_font()
|
||||
extract_resources.print_dialogue()
|
||||
sys.exit(0)
|
||||
|
||||
ROM = util.load_rom(args.rom)
|
||||
|
||||
want_compile = True
|
||||
|
||||
if args.extract_from_rom:
|
||||
import extract_resources
|
||||
extract_resources.main()
|
||||
|
||||
if args.print_strings:
|
||||
import text_compression
|
||||
text_compression.print_strings(ROM)
|
||||
want_compile = False
|
||||
|
||||
if want_compile and not args.no_build:
|
||||
import compile_resources
|
||||
compile_resources.main(args)
|
||||
|
||||
|
||||
|
||||
1547
assets/sprite_sheet_info.py
Normal file
1547
assets/sprite_sheet_info.py
Normal file
File diff suppressed because it is too large
Load Diff
640
assets/sprite_sheets.py
Normal file
640
assets/sprite_sheets.py
Normal file
@@ -0,0 +1,640 @@
|
||||
from PIL import Image
|
||||
import sprite_sheet_info
|
||||
import util
|
||||
from util import get_bytes, get_words, get_byte, cache
|
||||
import array
|
||||
import tables
|
||||
import sys
|
||||
|
||||
override_armor_palette = None
|
||||
#override_armor_palette = [0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x2a5c, 0x1571, 0x7a18,
|
||||
# 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x6980, 0x7691, 0x26b8, 0x437f, 0x2a5c, 0x1199, 0x7a18,
|
||||
# 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x1057, 0x457e, 0x6df3, 0xfeb9, 0x2a5c, 0x2227, 0x7a18,
|
||||
# 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x3d97, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x567e, 0x1571, 0x7a18,
|
||||
# 0, 0x0EFA, 0x7DD1, 0, 0x7F1A, 0x7F1A,0, 0x716E, 0x7DD1, 0x40A7, 0x7DD1, 0x40A7, 0x48E9, 0x50CF, 0x7FFF]
|
||||
|
||||
|
||||
def save_as_png(dimensions, data, fname, palette = None):
|
||||
img = Image.new('L' if palette == None else 'P', dimensions)
|
||||
img.putdata(data)
|
||||
if palette != None:
|
||||
img.putpalette(palette)
|
||||
img.save(fname)
|
||||
|
||||
def save_as_24bpp_png(dimensions, data, fname):
|
||||
img = Image.new("RGB", dimensions)
|
||||
img.putdata(data)
|
||||
img.save(fname)
|
||||
|
||||
@cache
|
||||
def decode_2bit_tileset(tileset, height = 32, base = 0):
|
||||
data = util.decomp(tables.kCompSpritePtrs[tileset], get_byte, False)
|
||||
assert len(data) == 0x400 * height // 32
|
||||
dst = bytearray(128*height)
|
||||
def decode_2bit(offs, toffs, base):
|
||||
for y in range(8):
|
||||
d0, d1 = data[offs + y * 2], data[offs + y * 2 + 1]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2
|
||||
dst[toffs + y * 128 + (7 - x)] = t + base
|
||||
for i in range(16*height//8):
|
||||
x = i % 16
|
||||
y = i // 16
|
||||
decode_2bit(i * 16, x * 8 + y * 8 * 128, base[i] if isinstance(base, tuple) else base)
|
||||
return dst
|
||||
|
||||
def is_high_3bit_tileset(tileset):
|
||||
# is 0x5c, 0x5e, 0x5f included or not?
|
||||
return tileset in (0x52, 0x53, 0x5a, 0x5b, 0x5c, 0x5e, 0x5f)
|
||||
|
||||
def get_unpacked_snes_tileset(tileset):
|
||||
if tileset < 12:
|
||||
return get_bytes(tables.kCompSpritePtrs[tileset], 0x600)
|
||||
else:
|
||||
return util.decomp(tables.kCompSpritePtrs[tileset], get_byte, False)
|
||||
|
||||
@cache
|
||||
def decode_3bit_tileset(tileset):
|
||||
data = get_unpacked_snes_tileset(tileset)
|
||||
assert len(data) == 0x600
|
||||
base = 8 if is_high_3bit_tileset(tileset) else 0
|
||||
height = 32
|
||||
dst = bytearray(128*height)
|
||||
def decode_3bit(offs, toffs):
|
||||
for y in range(8):
|
||||
d0, d1, d2 = data[offs + y * 2], data[offs + y * 2 + 1], data[offs + y + 16]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2 + ((d2 >> x) & 1) * 4
|
||||
dst[toffs + y * 128 + (7 - x)] = base + t
|
||||
for i in range(16*height//8):
|
||||
x, y = i % 16, i // 16
|
||||
decode_3bit(i * 24, x * 8 + y * 8 * 128)
|
||||
return dst
|
||||
|
||||
def decode_4bit_tileset_link():
|
||||
height = 448
|
||||
data = get_bytes(0x108000, 0x800 * height // 32) # only link sprites for now
|
||||
dst = bytearray(128*height)
|
||||
def decode_4bit(offs, toffs):
|
||||
for y in range(8):
|
||||
d0, d1, d2, d3 = data[offs + y * 2 + 0], data[offs + y * 2 + 1], data[offs + y * 2 + 16], data[offs + y * 2 + 17]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2 + ((d2 >> x) & 1) * 4 + ((d3 >> x) & 1) * 8
|
||||
dst[toffs + y * 128 + (7 - x)] = t
|
||||
for i in range(16*height//8):
|
||||
x, y = i % 16, i // 16
|
||||
decode_4bit(i * 32, x * 8 + y * 8 * 128)
|
||||
return dst
|
||||
|
||||
def convert_snes_palette(v):
|
||||
rr=bytearray()
|
||||
for x in v:
|
||||
r, g, b = x & 0x1f, x >> 5 & 0x1f, x >> 10 & 0x1f
|
||||
rr.extend((r << 3 | r >> 2, g << 3 | g >> 2, b << 3 | b >> 2))
|
||||
return rr
|
||||
|
||||
def convert_int24_palette_to_bytes(v):
|
||||
rr=bytearray()
|
||||
for x in v:
|
||||
rr.extend((x & 0xff, x >> 8 & 0xff, x >> 16 & 0xff))
|
||||
return rr
|
||||
|
||||
def convert_snes_palette_to_int(v):
|
||||
rr=[]
|
||||
for x in v:
|
||||
r, g, b = x & 0x1f, x >> 5 & 0x1f, x >> 10 & 0x1f
|
||||
rr.append((r << 3 | r >> 2) | (g << 3 | g >> 2) << 8 | (b << 3 | b >> 2) << 16)
|
||||
return rr
|
||||
|
||||
def decode_link_sprites():
|
||||
kLinkPalette = [0, 0x7fff, 0x237e, 0x11b7, 0x369e, 0x14a5, 0x1ff, 0x1078, 0x599d, 0x3647, 0x3b68, 0xa4a, 0x12ef, 0x2a5c, 0x1571, 0x7a18]
|
||||
save_as_png((128, 448), decode_4bit_tileset_link(), 'linksprite.png', convert_snes_palette(kLinkPalette))
|
||||
|
||||
def get_hud_snes_palette():
|
||||
hud_palette = get_words(0x9BD660, 64)
|
||||
palette = [(31 << 10 | 31) for i in range(256)]
|
||||
for i in range(16):
|
||||
for j in range(1, 4):
|
||||
palette[i * 16 + j] = hud_palette[i * 4 + j]
|
||||
return palette
|
||||
|
||||
def decode_hud_icons():
|
||||
class PaletteUsage:
|
||||
def __init__(self):
|
||||
self.data = open('palette_usage.bin', 'rb').read()
|
||||
def get(self, icon):
|
||||
usage = self.data[icon]
|
||||
for j in range(8):
|
||||
if usage & (1 << j):
|
||||
return j
|
||||
return 0
|
||||
pu = PaletteUsage()
|
||||
dst = bytearray()
|
||||
for slot, image_set in enumerate([106, 107, 105]):
|
||||
tbase = tuple([pu.get(slot * 128 + i) * 16 for i in range(128)])
|
||||
dst += decode_2bit_tileset(image_set, height = 64, base = tbase)
|
||||
|
||||
save_as_png((128, 64 * 3), dst, 'hud_icons.png', convert_snes_palette(get_hud_snes_palette()[:128]))
|
||||
|
||||
def get_pt_remapper():
|
||||
b = util.ROM.get_bytes(0x8EFC09, 121 * 3)
|
||||
d = {}
|
||||
for i in range(121):
|
||||
ch = (i & 0xf) | (i << 1) & 0xe0
|
||||
d[ch] = b[i*3+0]
|
||||
d[ch|0x10] = b[i*3+1]
|
||||
return d
|
||||
|
||||
kFontTypes = {
|
||||
'us' : (0x8e8000, 256, 'font.png', (0x8ECADF, 99)),
|
||||
'de' : (0xCC6E8, 256, 'font_de.png', (0x8CDECF, 112)),
|
||||
'fr' : (0xCC6E8, 256, 'font_fr.png', (0x8CDEAF, 112)),
|
||||
'fr-c' : (0xCD078, 256, 'font_fr_c.png', (0x8CE83F, 112)),
|
||||
'en' : (0x8E8000, 256, 'font_en.png', (0x8ECAFF, 102)),
|
||||
'es' : (0x8e8000, 256, 'font_es.png', (0x8ECADF, 99)),
|
||||
'pl' : (0x8e8000, 256, 'font_pl.png', (0x8ECADF, 99)),
|
||||
'pt' : (0x8e8000, 256, 'font_pt.png', (0x8ECADF, 121)),
|
||||
'redux': (0x8e8000, 256, 'font_redux.png', (0x8ECADF, 99)),
|
||||
'nl': (0x8e8000, 256, 'font_nl.png', (0x8ECADF, 99)),
|
||||
'sv': (0x8e8000, 256, 'font_sv.png', (0x8ECADF, 99)),
|
||||
}
|
||||
|
||||
def decode_font():
|
||||
lang = util.ROM.language
|
||||
def decomp_one_spr_2bit(data, offs, target, toffs, pitch, palette_base):
|
||||
for y in range(8):
|
||||
d0, d1 = data[offs + y * 2], data[offs + y * 2 + 1]
|
||||
for x in range(8):
|
||||
t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2
|
||||
target[toffs + y * pitch + (7 - x)] = t + palette_base
|
||||
ft = kFontTypes[lang]
|
||||
if lang == 'pt':
|
||||
W = util.ROM.get_bytes(0x8EFC09, 121 * 3)
|
||||
W = [W[i*3+2] for i in range(121)]
|
||||
remapper = get_pt_remapper()
|
||||
else:
|
||||
W = get_bytes(*ft[3])
|
||||
remapper = {}
|
||||
w = 128 + 15
|
||||
hi = ft[1] // 32
|
||||
h = hi * 17
|
||||
data = get_bytes(ft[0], ft[1] * 16)
|
||||
dst = bytearray(w * h)
|
||||
|
||||
for i in range(ft[1]):
|
||||
x, y = i % 16, i // 16
|
||||
pal_base = 6 * 16
|
||||
base_offs = x * 9 + (y * 8 + (y >> 1)) * w
|
||||
decomp_one_spr_2bit(data, remapper.get(i, i) * 16, dst, base_offs + w, w, pal_base)
|
||||
if (y & 1) == 0:
|
||||
j = (y >> 1) * 16 + x
|
||||
if j < len(W):
|
||||
dst[base_offs + W[j] - 1] = 255
|
||||
pal = convert_snes_palette(get_hud_snes_palette()[:128])
|
||||
pal.extend([0] * 384)
|
||||
pal[0], pal[1], pal[2] = 192, 192, 192
|
||||
pal[255*3+0], pal[255*3+1], pal[255*3+2] = 128, 128, 128
|
||||
save_as_png((w, h), dst, ft[2], pal)
|
||||
if lang != 'pt':
|
||||
assert (data, W) == encode_font_from_png(lang)
|
||||
|
||||
def encode_font_from_png(lang):
|
||||
font_data = Image.open(kFontTypes[lang][2]).tobytes()
|
||||
def encode_one_spr_2bit(data, offs, target, toffs, pitch):
|
||||
for y in range(8):
|
||||
d0, d1 = 0, 0
|
||||
for x in range(8):
|
||||
pixel = data[offs + y * pitch + 7 - x]
|
||||
d0 |= (pixel & 1) << x
|
||||
d1 |= ((pixel >> 1) & 1) << x
|
||||
target[toffs + y * 2 + 0], target[toffs + y * 2 + 1] = d0, d1
|
||||
w = 128 + 15
|
||||
dst = bytearray(256 * 16)
|
||||
def get_width(base_offs):
|
||||
for i in range(8):
|
||||
if font_data[base_offs + i] == 255:
|
||||
break
|
||||
return i + 1
|
||||
W = bytearray()
|
||||
for i in range(256):
|
||||
x, y = i % 16, i // 16
|
||||
base_offs = x * 9 + (y * 8 + (y >> 1)) * w
|
||||
if (y & 1) == 0:
|
||||
W.append(get_width(base_offs))
|
||||
encode_one_spr_2bit(font_data, base_offs + w, dst, i * 16, w)
|
||||
chars_per_lang = kFontTypes[lang][3][1]
|
||||
return dst, W[:chars_per_lang]
|
||||
|
||||
# Returns the dungeon palette for the specified palette index
|
||||
# 0 = lightworld, 1 = darkworld, 2 = dungeon
|
||||
@cache
|
||||
def get_palette_subidx(palset_idx, dungeon_or_ow, which_palette):
|
||||
spmain = 1 if (dungeon_or_ow == 1) else 0
|
||||
if dungeon_or_ow == 2:
|
||||
main, sp0l, sp5l, sp6l = tables.kDungPalinfos[palset_idx]
|
||||
sp0r = (main // 2) + 11
|
||||
sp6r = 10
|
||||
else:
|
||||
sp5l, sp6l = tables.kOwSprPalInfo[palset_idx * 2 : palset_idx * 2 + 2]
|
||||
sp0l = 3 if dungeon_or_ow == 1 else 1
|
||||
sp0r = 9 if dungeon_or_ow == 1 else 7
|
||||
sp6r = 8 if dungeon_or_ow == 1 else 6
|
||||
pal_defs = (sp0l, sp0r,
|
||||
spmain, None, spmain, None, spmain, None, None, None,
|
||||
sp5l, None,
|
||||
sp6l, sp6r,
|
||||
None, None)
|
||||
return pal_defs[which_palette]
|
||||
|
||||
@cache
|
||||
def get_palette_subset(pal_idx, j):
|
||||
if j == None: j = 0
|
||||
pal = [0] * 7
|
||||
if pal_idx == 0: # 0
|
||||
kPalette_SpriteAux3 = get_words(0x9BD39E, 84)
|
||||
return kPalette_SpriteAux3[j * 7 : j * 7 + 7]
|
||||
if pal_idx == 1: # 0R
|
||||
if j < 11:
|
||||
kPalette_MiscSprite = get_words(0x9BD446, 77)
|
||||
return kPalette_MiscSprite[j * 7 : j * 7 + 7]
|
||||
else:
|
||||
kPalette_DungBgMain = get_words(0x9BD734, 1800)
|
||||
return kPalette_DungBgMain[(j - 11) * 90 : (j - 11) * 90 + 7]
|
||||
if pal_idx in (2, 3, 4, 5, 6, 7, 8, 9):
|
||||
o = j * 60 + ((pal_idx - 2) >> 1) * 15 + (pal_idx & 1) * 8
|
||||
kPalette_MainSpr = get_words(0x9BD218, 120)
|
||||
return kPalette_MainSpr[o : o + 7]
|
||||
if pal_idx in (10, 12):
|
||||
kPalette_SpriteAux1 = get_words(0x9BD4E0, 168)
|
||||
return kPalette_SpriteAux1[j * 7 : j * 7 + 7]
|
||||
assert pal_idx != 11
|
||||
if pal_idx == 13:
|
||||
kPalette_MiscSprite = get_words(0x9BD446, 77)
|
||||
return kPalette_MiscSprite[j * 7 : j * 7 + 7]
|
||||
elif pal_idx == 14:
|
||||
kPalette_ArmorAndGloves = get_words(0x9BD308, 75)
|
||||
return kPalette_ArmorAndGloves[1:8]
|
||||
elif pal_idx == 15:
|
||||
kPalette_ArmorAndGloves = get_words(0x9BD308, 75)
|
||||
return kPalette_ArmorAndGloves[9:16]
|
||||
|
||||
@cache
|
||||
def get_full_palette(pal_idx, pal_subidx):
|
||||
rv = []
|
||||
if pal_idx & 1:
|
||||
rv.extend([0x00fe00] * 9)
|
||||
else:
|
||||
rv.extend([0x00fe00] * 1)
|
||||
rv.extend(convert_snes_palette_to_int(get_palette_subset(pal_idx, pal_subidx)))
|
||||
rv += [0xe000e0] * (256 - len(rv))
|
||||
# change the transparent to teal #008080
|
||||
for i in range(0, 128, 8):
|
||||
rv[i] = 0x808000
|
||||
rv[251] = 0xe0c0c0 # blueish text
|
||||
rv[252] = 0xc0c0c0 # palette text
|
||||
rv[253] = 0xf0f0f0 # unallocated
|
||||
rv[254] = 0x404040 # lines
|
||||
rv[255] = 0xe0e0e0 # bg
|
||||
return rv
|
||||
|
||||
@cache
|
||||
def get_font_3x5():
|
||||
return Image.open('../other/3x5_font.png').tobytes()
|
||||
|
||||
def draw_letter3x5(dst, dst_pitch, dx, dy, ch, color):
|
||||
font = get_font_3x5()
|
||||
dst_offs = dy * dst_pitch + dx
|
||||
src_offs = (ord(ch) & 31) * 4 + (ord(ch) // 32) * 6 * 128
|
||||
for y in range(6):
|
||||
for x in range(3):
|
||||
if font[src_offs + y * 128 + x] == 0:
|
||||
dst[dst_offs + y * dst_pitch + x] = color
|
||||
|
||||
def draw_string3x5(dst, dst_pitch, dx, dy, s, color):
|
||||
for ch in s:
|
||||
draw_letter3x5(dst, dst_pitch, dx, dy, ch, color)
|
||||
dx += 4
|
||||
|
||||
def convert_to_24bpp(data, palette):
|
||||
out = [0] * len(data)
|
||||
for i in range(len(data)):
|
||||
out[i] = palette[data[i]]
|
||||
return out
|
||||
|
||||
def concat_byte_arrays(ar):
|
||||
r = bytearray()
|
||||
for a in ar:
|
||||
r += a
|
||||
return r
|
||||
|
||||
def fixup_sprite_set_entry(e, e_prev):
|
||||
sprite_index = int(e.name[:2], 16) if e.name[0] != 'X' else 10000
|
||||
|
||||
if e.dungeon_or_ow != 3:
|
||||
e.tileset = tables.kSpriteTilesets[e.tileset + (64 if e.dungeon_or_ow == 2 else 0)][e.ss_idx]
|
||||
e.high_palette = is_high_3bit_tileset(e.tileset)
|
||||
e.skip_header = False
|
||||
if e.pal_base == None:
|
||||
if sprite_index < len(tables.kSpriteInit_Flags3):
|
||||
e.pal_base = (tables.kSpriteInit_Flags3[sprite_index] >> 1) & 7
|
||||
else:
|
||||
e.pal_base = 4 # just default to some palette
|
||||
|
||||
e.pal_idx = e.pal_base * 2 + e.high_palette
|
||||
e.pal_subidx = get_palette_subidx(e.palset_idx, e.dungeon_or_ow, e.pal_idx)
|
||||
|
||||
if e_prev != None and e_prev.name == e.name and e_prev.pal_idx == e.pal_idx and e_prev.pal_subidx == e.pal_subidx:
|
||||
e.skip_header = True
|
||||
|
||||
# the (pal_idx, pal_subidx) tuple identifies the palette
|
||||
e.encoded_id = e.pal_base | e.tileset << 3 | e.skip_header << 10 | (e.pal_subidx or 0) << 11
|
||||
e.encoded_id = e.encoded_id << 8 | ((e.encoded_id + 41) % 255)
|
||||
|
||||
|
||||
BIGW = 148
|
||||
def save_sprite_set_entry(finaldst, e, master_tilesheets = None):
|
||||
empty_253 = [253] * 8
|
||||
def fill_with_empty(dst, dst_offs):
|
||||
for y in range(8):
|
||||
o = dst_offs + y * BIGW
|
||||
dst[o : o + 8] = empty_253
|
||||
|
||||
def copy_8x8(dst, dst_offs, src, src_offs, base):
|
||||
for y in range(8):
|
||||
for x in range(8):
|
||||
dst[dst_offs + y * BIGW + x] = src[src_offs + y * 128 + x] + base
|
||||
|
||||
def hline(dst, x1, x2, y, color):
|
||||
for x in range(x1, x2 + 1):
|
||||
dst[y * BIGW + x] = color
|
||||
|
||||
def vline(dst, x, y1, y2, color):
|
||||
for y in range(y1, y2 + 1):
|
||||
dst[y * BIGW + x] = color
|
||||
|
||||
def fillrect(dst, x1, x2, y1, y2, color):
|
||||
for y in range(y1, y2 + 1):
|
||||
hline(dst, x1, x2, y, color)
|
||||
|
||||
def encode_id(dst, x, y, v):
|
||||
while v:
|
||||
if v & 1:
|
||||
dst[y * BIGW + x] = 253
|
||||
x -= 1
|
||||
v >>= 1
|
||||
|
||||
palette = get_full_palette(e.pal_idx, e.pal_subidx)
|
||||
|
||||
if not e.skip_header:
|
||||
pal_subidx = e.pal_subidx
|
||||
if e.high_palette:
|
||||
pal_name = '%sR' % e.pal_base
|
||||
else:
|
||||
pal_name = '%s' % e.pal_base
|
||||
if e.pal_base in (1, 2, 3):
|
||||
assert e.pal_subidx in (0, 1)
|
||||
pal_subidx = 'LW' if pal_subidx == 0 else 'DW'
|
||||
if pal_subidx != None:
|
||||
pal_name = '%s-%s' % (pal_name, pal_subidx)
|
||||
|
||||
header = bytearray([255] * BIGW * 9)
|
||||
draw_string3x5(header, BIGW, 1, 3, e.name[:22], 254)
|
||||
|
||||
for i in range(7):
|
||||
xx = BIGW - 37 + i * 5 - 9
|
||||
fillrect(header, xx, xx + 4, 3, 7, i + (9 if e.high_palette else 1))
|
||||
|
||||
draw_string3x5(header, BIGW, BIGW - 9, 3, '%2s' % e.tileset, 252)
|
||||
draw_string3x5(header, BIGW, BIGW - 45 - 1 - len(pal_name) * 4, 3, pal_name, 252)
|
||||
finaldst.extend(convert_to_24bpp(header, palette))
|
||||
|
||||
bigdst = bytearray([255] * BIGW * 36)
|
||||
hline(bigdst, 1, 137, 0, 254)
|
||||
hline(bigdst, 1, 137, 34, 254)
|
||||
vline(bigdst, 1, 0, 34, 254)
|
||||
vline(bigdst, 137, 0, 34, 254)
|
||||
|
||||
encode_id(bigdst, 137, 35, e.encoded_id << 9 | 0x55)
|
||||
|
||||
src = decode_3bit_tileset(e.tileset) # returns 128x64
|
||||
|
||||
for i in range(16*4):
|
||||
x, y = i % 16, i // 16
|
||||
dst_offs = (y * 8 + 1 + (y >> 1)) * BIGW + x * 8 + 2 + (x >> 1)
|
||||
if x & 8: dst_offs
|
||||
m = e.matrix[y][x]
|
||||
if m == '.':
|
||||
fill_with_empty(bigdst, dst_offs)
|
||||
else:
|
||||
copy_8x8(bigdst, dst_offs, src, x * 8 + y * 8 * 128, 0)
|
||||
if master_tilesheets:
|
||||
master_tilesheets.insert(e.tileset, src, x, y, palette, 0)
|
||||
|
||||
# display vram addresses
|
||||
draw_string3x5(bigdst, BIGW, BIGW - 9, 4, '%Xx' % (e.ss_idx * 0x4), 251)
|
||||
draw_string3x5(bigdst, BIGW, BIGW - 9, 4 + 17, '%Xx' % (e.ss_idx * 0x4 + 2), 251)
|
||||
|
||||
# collapse unused matrix sections
|
||||
if all(m=='.' for m in e.matrix[0]) and all(m=='.' for m in e.matrix[1]):
|
||||
del bigdst[1*BIGW:17*BIGW]
|
||||
elif all(m=='.' for m in e.matrix[2]) and all(m=='.' for m in e.matrix[3]):
|
||||
del bigdst[18*BIGW:34*BIGW]
|
||||
|
||||
if e.skip_header:
|
||||
draw_string3x5(bigdst, BIGW, BIGW - 9, len(bigdst)//BIGW - 6, '%.2d' % e.tileset, 252)
|
||||
|
||||
finaldst.extend(convert_to_24bpp(bigdst, palette))
|
||||
|
||||
def fixup_entries():
|
||||
e_prev = None
|
||||
for e in sprite_sheet_info.entries:
|
||||
fixup_sprite_set_entry(e, e_prev)
|
||||
e_prev = e
|
||||
|
||||
class MasterTilesheets:
|
||||
def __init__(self):
|
||||
self.sheets = {}
|
||||
|
||||
def insert(self, tileset, src, xp, yp, palette, pal_base):
|
||||
sheet = self.sheets.get(tileset)
|
||||
if sheet == None:
|
||||
sheet = (array.array('I', [0x00f000] * 128 * 32), None)
|
||||
self.sheets[tileset] = sheet
|
||||
sheet24 = sheet[0]
|
||||
for y in range(yp * 8, yp * 8 + 8):
|
||||
for x in range(xp * 8, xp * 8 + 8):
|
||||
o = y * 128 + x
|
||||
sheet24[o] = palette[pal_base + src[o]]
|
||||
|
||||
def add_verify_8x8(self, tileset, pal_lut, img_data, pitch, dst_pos, src_pos):
|
||||
# add to the master sheet
|
||||
sheet = self.sheets.get(tileset)
|
||||
if sheet == None:
|
||||
sheet = (array.array('I', [0x00f000] * 128 * 32), bytearray([248] * 128 * 32))
|
||||
self.sheets[tileset] = sheet
|
||||
sheet24, sheet8 = sheet[0], sheet[1]
|
||||
for y in range(8):
|
||||
for x in range(8):
|
||||
pixel = img_data[src_pos + y * pitch + x]
|
||||
o = dst_pos + y * 128 + x
|
||||
sheet24[o] = pixel
|
||||
pixel8 = pal_lut.get(pixel)
|
||||
if pixel8 == None:
|
||||
raise Exception('Pixel #%.6x not found' % pixel)
|
||||
if sheet8[o] != 248 and pixel8 != sheet8[o]:
|
||||
raise Exception('Pixel has more than one value')
|
||||
sheet8[o] = pixel8
|
||||
|
||||
def save_to_all_sheets(self, save_also_8bit = False):
|
||||
rv = array.array('I')
|
||||
if save_also_8bit:
|
||||
rv8 = bytearray()
|
||||
|
||||
def extend_with_string_line(text):
|
||||
header = array.array('I', [0xf0f0f0] * 128 * 8)
|
||||
draw_string3x5(header, 128, 1, 2, text, 0x404040)
|
||||
rv.extend(header)
|
||||
if save_also_8bit:
|
||||
header8 = bytearray([255] * 128 * 8)
|
||||
draw_string3x5(header8, 128, 1, 2, text, 254)
|
||||
rv8.extend(header8)
|
||||
extend_with_string_line('AUTO GENERATED DO NOT EDIT')
|
||||
kk = 0
|
||||
for k in sorted(self.sheets.keys()):
|
||||
extend_with_string_line('Sheet %d' % k)
|
||||
rv.extend(self.sheets[k][0])
|
||||
if save_also_8bit:
|
||||
rv8.extend(self.sheets[k][1])
|
||||
if k != kk:
|
||||
print('Missing %d' % kk)
|
||||
kk = k + 1
|
||||
if save_also_8bit:
|
||||
palette8 = get_full_palette(4, 0)
|
||||
for i, v in enumerate(convert_snes_palette_to_int(get_palette_subset(5, 0))):
|
||||
palette8[i + 9] = v
|
||||
palette8[248] = 0x00f000
|
||||
save_as_png((128, len(rv)//128), rv8, 'sprites/all_sheets_8bit.png', palette = convert_int24_palette_to_bytes(palette8))
|
||||
self.verify_identical_to_snes()
|
||||
save_as_24bpp_png((128, len(rv)//128), rv, 'sprites/all_sheets.png')
|
||||
|
||||
def verify_identical_to_snes(self):
|
||||
for tileset in range(103):
|
||||
a = self.encode_sheet_in_snes_format(tileset)
|
||||
b = get_unpacked_snes_tileset(tileset)
|
||||
if a != b:
|
||||
print('Sheet %d mismatch' % tileset)
|
||||
break
|
||||
|
||||
# convert a tileset to the 8bit snes format, 24 bytes per tile
|
||||
def encode_sheet_in_snes_format(self, tileset):
|
||||
sheet8 = self.sheets[tileset][1]
|
||||
result = bytearray(24 * 16 * 4)
|
||||
def encode_into(dst_pos, src_pos):
|
||||
for y in range(8):
|
||||
for x in range(8):
|
||||
b = sheet8[src_pos + 128 * y + 7 - x]
|
||||
result[dst_pos+2*y] |= (b & 1) << x
|
||||
result[dst_pos+2*y+1] |= ((b & 2) >> 1) << x
|
||||
result[dst_pos+y+16] |= ((b & 4) >> 2) << x
|
||||
for y in range(4):
|
||||
for x in range(16):
|
||||
encode_into((y * 16 + x) * 24, y * 8 * 128 + x * 8)
|
||||
return result
|
||||
|
||||
def decode_sprite_sheets():
|
||||
master_tilesheets = MasterTilesheets()
|
||||
|
||||
fixup_entries()
|
||||
for char in "0123456789ABCDEFX":
|
||||
dst = []
|
||||
for e in sprite_sheet_info.entries:
|
||||
if e.name[0].upper() == char:
|
||||
save_sprite_set_entry(dst, e, master_tilesheets)
|
||||
if len(dst):
|
||||
save_as_24bpp_png((BIGW, len(dst) // BIGW), dst, 'sprites/sprites_%s.png' % char)
|
||||
master_tilesheets.save_to_all_sheets()
|
||||
|
||||
def load_sprite_sheets():
|
||||
master_tilesheets = MasterTilesheets()
|
||||
for char in "0123456789ABCDEFX":
|
||||
img = Image.open('sprites/sprites_%s.png' % char)
|
||||
img_size = img.size
|
||||
img_data = array.array('I', (a[0] | a[1] << 8 | a[2] << 16 for a in img.getdata()))
|
||||
pitch = img.size[0]
|
||||
|
||||
def find_next_tag(start_pos):
|
||||
for i in range(start_pos, len(img_data) - pitch * 2, pitch if start_pos != 0 else 1):
|
||||
if img_data[i+0] == 0x404040 and img_data[i+pitch] == 0x404040 and \
|
||||
img_data[i+pitch * 2 + 0] == 0xf0f0f0 and img_data[i+pitch * 2 - 1] == 0xe0e0e0 and \
|
||||
img_data[i+pitch * 2 - 2] == 0xf0f0f0 and img_data[i+pitch * 2 - 3] == 0xe0e0e0 and \
|
||||
img_data[i+pitch * 2 - 4] == 0xf0f0f0 and img_data[i+pitch * 2 - 5] == 0xe0e0e0 and \
|
||||
img_data[i+pitch * 2 - 6] == 0xf0f0f0 and img_data[i+pitch * 2 - 7] == 0xe0e0e0:
|
||||
return i + pitch * 2
|
||||
else:
|
||||
return None
|
||||
|
||||
def decode_tag(pos):
|
||||
r = 0
|
||||
for i in range(64):
|
||||
v = img_data[pos - 63 + i]
|
||||
assert v in (0xe0e0e0, 0xf0f0f0)
|
||||
r = r * 2 + (v == 0xf0f0f0)
|
||||
if (r & 0x1ff) != 0x55:
|
||||
raise Exception('Invalid tag')
|
||||
r >>= 9
|
||||
if (((r >> 8) + 41) % 255) != (r & 0xff):
|
||||
raise Exception('Invalid checksum')
|
||||
r >>= 8
|
||||
return r & 7, (r >> 3) & 127, (r >> 10) & 1, (r >> 11) & 31
|
||||
|
||||
def determine_image_rects(tag_pos):
|
||||
h = 1
|
||||
while (img_data[tag_pos - pitch * (h + 1)] == 0x404040):
|
||||
h += 1
|
||||
if h == 19:
|
||||
if img_data[tag_pos - pitch * 2 - 1] == 0xe0e0e0:
|
||||
image_rects = ((0, tag_pos - 135 - pitch * 18), )
|
||||
elif img_data[tag_pos - pitch * 18 - 1] == 0xe0e0e0:
|
||||
image_rects = ((1, tag_pos - 135 - pitch * 17), )
|
||||
else:
|
||||
raise Exception('cant uncompact')
|
||||
elif h == 35:
|
||||
image_rects = ((0, tag_pos - 135 - pitch * 34), (1, tag_pos - 135 - pitch * 17))
|
||||
else:
|
||||
raise Exception('height not found')
|
||||
return image_rects, h
|
||||
|
||||
def get_pal_lut(tag_pos, is_high):
|
||||
# read palette colors from the image pixels
|
||||
pal_lut = {img_data[tag_pos + 5 * i] : i + (9 if is_high else 1) for i in range(7)}
|
||||
pal_lut[0x808000] = 0 # transparent color
|
||||
return pal_lut
|
||||
|
||||
def is_empty(pos):
|
||||
return all(img_data[pos+y*pitch+x] == 0xf0f0f0 for x in range(8) for y in range(8))
|
||||
|
||||
pal_lut = None
|
||||
tag_pos = 0
|
||||
while True:
|
||||
tag_pos = find_next_tag(tag_pos)
|
||||
if tag_pos == None:
|
||||
break
|
||||
pal_base, tileset, headerless, pal_subidx = decode_tag(tag_pos)
|
||||
image_rects, box_height = determine_image_rects(tag_pos)
|
||||
if not headerless:
|
||||
pal_lut = get_pal_lut(tag_pos - pitch * (box_height + 3) - 34, is_high_3bit_tileset(tileset))
|
||||
for idx, sheet_pos in image_rects:
|
||||
for y in range(2):
|
||||
for x in range(16):
|
||||
src_pos = sheet_pos + (y * 8 + (y >> 1)) * pitch + (x * 8 + (x >> 1))
|
||||
dst_pos = (y + idx * 2) * 8 * 128 + x * 8
|
||||
if not is_empty(src_pos):
|
||||
master_tilesheets.add_verify_8x8(tileset, pal_lut, img_data, pitch, dst_pos, src_pos)
|
||||
return master_tilesheets
|
||||
|
||||
#if __name__ == "__main__":
|
||||
# ROM = util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None, True)
|
||||
# decode_font()
|
||||
|
||||
|
||||
|
||||
1
assets/sprites/.gitignore
vendored
Normal file
1
assets/sprites/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/*.png
|
||||
@@ -880,3 +880,253 @@ def get_secret_names():
|
||||
|
||||
kSecretNames = get_secret_names()
|
||||
kSecretNamesRev = invert_dict(kSecretNames)
|
||||
|
||||
kCompSpritePtrs = [
|
||||
0x10f000,0x10f600,0x10fc00,0x118200,0x118800,0x118e00,0x119400,0x119a00,
|
||||
0x11a000,0x11a600,0x11ac00,0x11b200,0x14fffc,0x1585d4,0x158ab6,0x158fbe,
|
||||
0x1593f8,0x1599a6,0x159f32,0x15a3d7,0x15a8f1,0x15aec6,0x15b418,0x15b947,
|
||||
0x15bed0,0x15c449,0x15c975,0x15ce7c,0x15d394,0x15d8ac,0x15ddc0,0x15e34c,
|
||||
0x15e8e8,0x15ee31,0x15f3a6,0x15f92d,0x15feba,0x1682ff,0x1688e0,0x168e41,
|
||||
0x1692df,0x169883,0x169cd0,0x16a26e,0x16a275,0x16a787,0x16aa06,0x16ae9d,
|
||||
0x16b3ff,0x16b87e,0x16be6b,0x16c13d,0x16c619,0x16cbbb,0x16d0f1,0x16d641,
|
||||
0x16d95a,0x16dd99,0x16e278,0x16e760,0x16ed25,0x16f20f,0x16f6b7,0x16fa5f,
|
||||
0x16fd29,0x1781cd,0x17868d,0x178b62,0x178fd5,0x179527,0x17994b,0x179ea7,
|
||||
0x17a30e,0x17a805,0x17acf8,0x17b2a2,0x17b7f9,0x17bc93,0x17c237,0x17c78e,
|
||||
0x17cd55,0x17d2bc,0x17d82f,0x17dcec,0x17e1cc,0x17e36b,0x17e842,0x17eb38,
|
||||
0x17ed58,0x17f06c,0x17f4fd,0x17fa39,0x17ff86,0x18845c,0x1889a1,0x188d64,
|
||||
0x18919d,0x189610,0x189857,0x189b24,0x189dd2,0x18a03f,0x18a4ed,0x18a7ba,
|
||||
0x18aedf,0x18af0d,0x18b520,0x18b953,
|
||||
]
|
||||
|
||||
kCompBgPtrs = [
|
||||
0x11b800,0x11bce2,0x11c15f,0x11c675,0x11cb84,0x11cf4c,0x11d2ce,0x11d726,
|
||||
0x11d9cf,0x11dec4,0x11e393,0x11e893,0x11ed7d,0x11f283,0x11f746,0x11fc21,
|
||||
0x11fff2,0x128498,0x128a0e,0x128f30,0x129326,0x129804,0x129d5b,0x12a272,
|
||||
0x12a6fe,0x12aa77,0x12ad83,0x12b167,0x12b51d,0x12b840,0x12bd54,0x12c1c9,
|
||||
0x12c73d,0x12cc86,0x12d198,0x12d6b1,0x12db6a,0x12e0ea,0x12e6bd,0x12eb51,
|
||||
0x12f135,0x12f6c5,0x12fc71,0x138129,0x138693,0x138bad,0x139117,0x139609,
|
||||
0x139b21,0x13a074,0x13a619,0x13ab2b,0x13b00c,0x13b4f5,0x13b9eb,0x13bebf,
|
||||
0x13c3ce,0x13c817,0x13cb68,0x13cfb5,0x13d460,0x13d8c2,0x13dd7a,0x13e266,
|
||||
0x13e7af,0x13ece5,0x13f245,0x13f6f0,0x13fc30,0x1480e9,0x14863b,0x148a7c,
|
||||
0x148f2a,0x149346,0x1497ed,0x149cc2,0x14a173,0x14a61d,0x14ab5d,0x14b083,
|
||||
0x14b4bd,0x14b94e,0x14be0e,0x14c291,0x14c7ba,0x14cce4,0x14d1db,0x14d6bd,
|
||||
0x14db77,0x14ded1,0x14e2ac,0x14e754,0x14ebae,0x14ef4e,0x14f309,0x14f6f4,
|
||||
0x14fa55,0x14ff8c,0x14ff93,0x14ff9a,0x14ffa1,0x14ffa8,0x14ffaf,0x14ffb6,
|
||||
0x14ffbd,0x14ffc4,0x14ffcb,0x14ffd2,0x14ffd9,0x14ffe0,0x14ffe7,0x14ffee,
|
||||
0x14fff5,0x18b520,0x18b953,
|
||||
]
|
||||
|
||||
kSpriteTilesets = [
|
||||
( 0, 73, 0, 0),
|
||||
(70, 73, 12, 29),
|
||||
(72, 73, 19, 29),
|
||||
(70, 73, 19, 14),
|
||||
(72, 73, 12, 17),
|
||||
(72, 73, 12, 16),
|
||||
(79, 73, 74, 80),
|
||||
(14, 73, 74, 17),
|
||||
(70, 73, 18, 0),
|
||||
( 0, 73, 0, 80),
|
||||
( 0, 73, 0, 17),
|
||||
(72, 73, 12, 0),
|
||||
( 0, 0, 55, 54),
|
||||
(72, 73, 76, 17),
|
||||
(93, 44, 12, 68),
|
||||
( 0, 0, 78, 0),
|
||||
(15, 0, 18, 16),
|
||||
( 0, 0, 0, 76),
|
||||
( 0, 13, 23, 0),
|
||||
(22, 13, 23, 27),
|
||||
(22, 13, 23, 20),
|
||||
(21, 13, 23, 21),
|
||||
(22, 13, 24, 25),
|
||||
(22, 13, 23, 25),
|
||||
(22, 13, 0, 0),
|
||||
(22, 13, 24, 27),
|
||||
(15, 73, 74, 17),
|
||||
(75, 42, 92, 21),
|
||||
(22, 73, 23, 29),
|
||||
( 0, 0, 0, 21),
|
||||
(22, 13, 23, 16),
|
||||
(22, 73, 18, 0),
|
||||
(22, 73, 12, 17),
|
||||
( 0, 0, 18, 16),
|
||||
(22, 13, 0, 17),
|
||||
(22, 73, 12, 0),
|
||||
(22, 13, 76, 17),
|
||||
(14, 13, 74, 17),
|
||||
(22, 26, 23, 27),
|
||||
(79, 52, 74, 80),
|
||||
(53, 77, 101, 54),
|
||||
(74, 52, 78, 0),
|
||||
(14, 52, 74, 17),
|
||||
(81, 52, 93, 89),
|
||||
(75, 73, 76, 17),
|
||||
(45, 0, 0, 0),
|
||||
(93, 0, 18, 89),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
(71, 73, 43, 45),
|
||||
(70, 73, 28, 82),
|
||||
( 0, 73, 28, 82),
|
||||
(93, 73, 0, 82),
|
||||
(70, 73, 19, 82),
|
||||
(75, 77, 74, 90),
|
||||
(71, 73, 28, 82),
|
||||
(75, 77, 57, 54),
|
||||
(31, 44, 46, 82),
|
||||
(31, 44, 46, 29),
|
||||
(47, 44, 46, 82),
|
||||
(47, 44, 46, 49),
|
||||
(31, 30, 48, 82),
|
||||
(81, 73, 19, 0),
|
||||
(79, 73, 19, 80),
|
||||
(79, 77, 74, 80),
|
||||
(75, 73, 76, 43),
|
||||
(31, 32, 34, 83),
|
||||
(85, 61, 66, 67),
|
||||
(31, 30, 35, 82),
|
||||
(31, 30, 57, 58),
|
||||
(31, 30, 58, 62),
|
||||
(31, 30, 60, 61),
|
||||
(64, 30, 39, 63),
|
||||
(85, 26, 66, 67),
|
||||
(31, 30, 42, 82),
|
||||
(31, 30, 56, 82),
|
||||
(31, 32, 40, 82),
|
||||
(31, 32, 38, 82),
|
||||
(31, 44, 37, 82),
|
||||
(31, 32, 39, 82),
|
||||
(31, 30, 41, 82),
|
||||
(31, 44, 59, 82),
|
||||
(70, 73, 36, 82),
|
||||
(33, 65, 69, 51),
|
||||
(31, 44, 40, 49),
|
||||
(31, 13, 41, 82),
|
||||
(31, 30, 39, 82),
|
||||
(31, 32, 39, 83),
|
||||
(72, 73, 19, 82),
|
||||
(14, 30, 74, 80),
|
||||
(31, 32, 38, 83),
|
||||
(21, 0, 0, 0),
|
||||
(31, 0, 42, 82),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
( 0, 0, 0, 0),
|
||||
(50, 0, 0, 8),
|
||||
(93, 73, 0, 82),
|
||||
(85, 73, 66, 67),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 86, 87, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 86, 87, 80),
|
||||
(97, 86, 99, 80),
|
||||
(97, 86, 87, 80),
|
||||
(97, 86, 51, 80),
|
||||
(97, 86, 87, 80),
|
||||
(97, 98, 99, 80),
|
||||
(97, 98, 99, 80),
|
||||
]
|
||||
|
||||
kDungPalinfos = [
|
||||
( 0, 0, 3, 1),
|
||||
( 2, 0, 3, 1),
|
||||
( 4, 0, 10, 1),
|
||||
( 6, 0, 1, 7),
|
||||
(10, 2, 2, 7),
|
||||
( 4, 4, 3, 10),
|
||||
(12, 5, 8, 20),
|
||||
(14, 0, 3, 10),
|
||||
( 2, 0, 15, 20),
|
||||
(10, 2, 0, 7),
|
||||
( 2, 0, 15, 12),
|
||||
( 6, 0, 6, 7),
|
||||
( 0, 0, 14, 18),
|
||||
(18, 5, 5, 11),
|
||||
(18, 0, 2, 12),
|
||||
(16, 5, 10, 7),
|
||||
(16, 0, 16, 12),
|
||||
(22, 7, 2, 7),
|
||||
(22, 0, 7, 15),
|
||||
( 8, 0, 4, 12),
|
||||
( 8, 0, 4, 9),
|
||||
( 4, 0, 3, 1),
|
||||
(20, 0, 4, 4),
|
||||
(20, 0, 20, 12),
|
||||
(24, 5, 7, 11),
|
||||
(24, 6, 16, 12),
|
||||
(26, 5, 8, 20),
|
||||
(26, 2, 0, 7),
|
||||
( 6, 0, 3, 10),
|
||||
(28, 0, 3, 1),
|
||||
(30, 0, 11, 17),
|
||||
( 4, 0, 11, 17),
|
||||
(14, 0, 0, 2),
|
||||
(32, 8, 19, 13),
|
||||
(10, 0, 3, 10),
|
||||
(20, 0, 4, 4),
|
||||
(26, 2, 2, 7),
|
||||
(26, 10, 0, 0),
|
||||
( 0, 0, 3, 2),
|
||||
(14, 0, 3, 7),
|
||||
(26, 5, 5, 11),
|
||||
]
|
||||
|
||||
kOwSprPalInfo = [
|
||||
-1, -1, 3, 10, 3, 6, 3, 1, 0, 2, 3, 14, 3, 2, 19, 1, 11, 12, 17, 1, 7, 5, 17, 0,
|
||||
9, 11, 15, 5, 3, 5, 3, 7, 15, 2, 10, 2, 5, 1, 12, 14,
|
||||
]
|
||||
|
||||
kSpriteInit_Flags3 = [
|
||||
0x19, 0xb, 0x1b, 0x4b, 0x41, 0x41, 0x41, 0x4d, 0x1d, 1, 0x1d, 0x19, 0x8d, 0x1b, 9, 0x9d,
|
||||
0x3d, 1, 9, 0x11, 0x40, 1, 0x4d, 0x19, 7, 0x1d, 0x59, 0x80, 0x4d, 0x40, 1, 0x49,
|
||||
0x1b, 0x41, 3, 0x13, 0x15, 0x41, 0x18, 0x1b, 0x41, 0x47, 0xf, 0x49, 0x4b, 0x4d, 0x41, 0x47,
|
||||
0x49, 0x4d, 0x49, 0x40, 0x4d, 0x47, 0x49, 0x41, 0x74, 0x47, 0x5b, 0x58, 0x51, 0x49, 0x1d, 0x5d,
|
||||
3, 0x19, 0x1b, 0x17, 0x19, 0x17, 0x19, 0x1b, 0x17, 0x17, 0x17, 0x1b, 0xd, 9, 0x19, 0x19,
|
||||
0x49, 0x5d, 0x5b, 0x49, 0xd, 3, 0x13, 0x41, 0x1b, 0x5b, 0x5d, 0x43, 0x43, 0x4d, 0x4d, 0x4d,
|
||||
0x4d, 0x4d, 0x49, 1, 0, 0x41, 0x4d, 0x4d, 0x4d, 0x4d, 0x1d, 9, 0xc4, 0xd, 0xd, 9,
|
||||
3, 3, 0x4b, 0x47, 0x47, 0x49, 0x49, 0x41, 0x47, 0x36, 0x8b, 0x49, 0x1d, 0x49, 0x43, 0x43,
|
||||
0x43, 0xb, 0x41, 0xd, 7, 0xb, 0x1d, 0x43, 0xd, 0x43, 0xd, 0x1d, 0x4d, 0x4d, 0x1b, 0x1b,
|
||||
0xa, 0xb, 0, 5, 0xd, 1, 1, 1, 1, 0xb, 5, 1, 1, 1, 7, 0x17,
|
||||
0x19, 0xd, 0xd, 0x80, 0x4d, 0x19, 0x17, 0x19, 0xb, 9, 0xd, 0x4a, 0x12, 0x49, 0xc3, 0xc3,
|
||||
0xc3, 0xc3, 0x76, 0x40, 0x59, 0x41, 0x58, 0x4f, 0x73, 0x5b, 0x44, 0x41, 0x51, 0xa, 0xb, 0xb,
|
||||
0x4b, 0, 0x40, 0x5b, 0xd, 0, 0, 0xd, 0x4b, 0xb, 0x59, 0x41, 0xb, 0xd, 1, 0xd,
|
||||
0xd, 0, 0x50, 0x4c, 0x44, 0x51, 1, 1, 0xf2, 0xf8, 0xf4, 0xf2, 0xd4, 0xd4, 0xd4, 0xf8,
|
||||
0xf8, 0xf4, 0xf4, 0xd8, 0xf8, 0xd8, 0xdf, 0xc8, 0x69, 0xc1, 0xd2, 0xd2, 0xdc, 0xc7, 0xc1, 0xc7,
|
||||
0xc7, 0xc7, 0xc1,
|
||||
]
|
||||
551
assets/text_compression.py
Normal file
551
assets/text_compression.py
Normal file
@@ -0,0 +1,551 @@
|
||||
import util, sys
|
||||
|
||||
kTextAlphabet_US = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
"-", ".", ",",
|
||||
# 64 - 79
|
||||
"[...]", ">", "(", ")",
|
||||
"[Ankh]", "[Waves]", "[Snake]", "[LinkL]", "[LinkR]",
|
||||
"\"", "[Up]", "[Down]", "[Left]",
|
||||
# 80 - 95
|
||||
"[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[4HeartL]", "[4HeartR]", " ", "<", "[A]", "[B]", "[X]", "[Y]",
|
||||
]
|
||||
|
||||
kTextAlphabet_DE = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
# 64 - 79
|
||||
"-", ".", ",", "[...]", ">", "(", ")",
|
||||
"[Ankh]", "[Waves]", "[Snake]", "[LinkL]", "[LinkR]",
|
||||
"\"", "[UpL]", "[UpR]", "[LeftL]",
|
||||
# 80 - 95
|
||||
"[LeftR]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[4HeartL]", "[4HeartR]", " ", "ö", "[A]", "[B]", "[X]", "[Y]", "ü",
|
||||
# 96-111
|
||||
"ß", ":", "[DownL]", "[DownR]", "[RightL]", "[RightR]",
|
||||
"è", "é", "ê", "à", "ù", "ç", "Ä", "Ö", "Ü", "ä"
|
||||
# 112-
|
||||
]
|
||||
|
||||
kTextAlphabet_FR = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
# 64 - 79
|
||||
"-", ".", ",", "[...]", ">", "(", ")",
|
||||
"[Ankh]", "[Waves]", "[Snake]", "[LinkL]", "[LinkR]",
|
||||
"\"", "[UpL]", "[UpR]", "[LeftL]",
|
||||
# 80 - 95
|
||||
"[LeftR]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[4HeartL]", "[4HeartR]", " ", "ö", "[A]", "[B]", "[X]", "[Y]", "ü",
|
||||
# 96-111
|
||||
"ô", ":", "[DownL]", "[DownR]", "[RightL]", "[RightR]",
|
||||
"è", "é", "ê", "à", "ù", "ç", "â", "û", "î", "ä"
|
||||
# 112-
|
||||
]
|
||||
|
||||
kText_CommandLengths_US = [1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, ]
|
||||
kText_CommandNames_US = [
|
||||
"NextPic", "Choose", "Item", "Name", "Window", "Number",
|
||||
"Position","ScrollSpd", "Selchg", "Unused_Crash", "Choose3",
|
||||
"Choose2", "Scroll", "1", "2", "3", "Color",
|
||||
"Wait", "Sound", "Speed", "Unused_Mark", "Unused_Mark2", "Unused_Clear",
|
||||
"Waitkey"
|
||||
]
|
||||
|
||||
kTextDictionary_US = [
|
||||
' ', ' ', ' ', "'s ", 'and ',
|
||||
'are ', 'all ', 'ain', 'and', 'at ',
|
||||
'ast', 'an', 'at', 'ble', 'ba',
|
||||
'be', 'bo', 'can ', 'che', 'com',
|
||||
'ck', 'des', 'di', 'do', 'en ',
|
||||
'er ', 'ear', 'ent', 'ed ', 'en',
|
||||
'er', 'ev', 'for', 'fro', 'give ',
|
||||
'get', 'go', 'have', 'has', 'her',
|
||||
'hi', 'ha', 'ight ', 'ing ', 'in',
|
||||
'is', 'it', 'just', 'know', 'ly ',
|
||||
'la', 'lo', 'man', 'ma', 'me',
|
||||
'mu', "n't ", 'non', 'not', 'open',
|
||||
'ound', 'out ', 'of', 'on', 'or',
|
||||
'per', 'ple', 'pow', 'pro', 're ',
|
||||
're', 'some', 'se', 'sh', 'so',
|
||||
'st', 'ter ', 'thin', 'ter', 'tha',
|
||||
'the', 'thi', 'to', 'tr', 'up',
|
||||
'ver', 'with', 'wa', 'we', 'wh',
|
||||
'wi', 'you', 'Her', 'Tha', 'The',
|
||||
'Thi', 'You',
|
||||
]
|
||||
|
||||
|
||||
kText_CommandLengths_EU = [1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2]
|
||||
kText_CommandNames_EU = [
|
||||
"Selchg", "Choose3", "Choose2", "Scroll", "1", "2", "3",
|
||||
"Color", "Wait", "Sound", "Speed", "Mark", "Mark2",
|
||||
"Clear", "Waitkey", "EndMessage", "NextPic", "Choose",
|
||||
"Item", "Name", "Window", "Number", "Position", "ScrollSpd",
|
||||
]
|
||||
|
||||
kTextDictionary_DE = [
|
||||
' ', ' ', ' ', '-Knopf', ' ich ',
|
||||
' Sch', ' Ver', ' zu ', ' es ', 'aber',
|
||||
'alle', 'auch', 'ang', 'aus', 'auf',
|
||||
'an', 'bist', 'bin', 'bei', 'der ',
|
||||
'die ', 'das ', 'den ', 'dem ', 'daß',
|
||||
'der', 'die', 'das', 'den', 'da',
|
||||
'etwas', 'ein ', 'ein', 'en ', 'er ',
|
||||
'es ', 'en', 'er', 'es', 'ei',
|
||||
'für', 'fe', 'habe', 'hier', 'hast',
|
||||
'her', 'ich ', 'icht', 'ich', 'ist',
|
||||
'ie ', 'im', 'ie', 'kannst ', 'kannst',
|
||||
'kommen', 'kann ', 'll', 'mich', 'mein',
|
||||
'mit', 'mal', 'mir', 'nicht ', 'nicht',
|
||||
'nen', 'nn', 'och ', 'och', 'or',
|
||||
'schon', 'sich', 'sein', 'sch', 'sie',
|
||||
'st', 'tte', 'te ', 'te', 'und ',
|
||||
'und', 'ung', 'um', 'von', 'ver',
|
||||
'vor', 'wird', 'zu ', 'Amulett', 'Aber',
|
||||
'Deine', 'Dich ', 'Dir ', 'Dir', 'Der',
|
||||
'Die', 'Das', 'Du ', 'Du', 'Da',
|
||||
'Ein', 'Hyrule', 'Hier', 'Ich ', 'Master-Schwert',
|
||||
'Mach', 'Rubine', 'Sch', 'Sie', 'Ver',
|
||||
'Weisen', 'Zelda',
|
||||
]
|
||||
|
||||
kTextDictionary_FR = [
|
||||
' ', ' de ', ' la ', ' le ', ' ! ',
|
||||
' d', ' p', ' t', ' !', ", c'est moi, Sahasrahla",
|
||||
', ', 'ais ', 'as ', 'an', 'ai',
|
||||
'a ', 'che', 'ce', 'ch', 'dans ',
|
||||
'des ', 'de ', 'de', 'est ', 'ent',
|
||||
'en ', 'er ', 'es ', 'en', 'es',
|
||||
'et', 'eu', 'e,', 'e ', 'ique',
|
||||
'ien', 'is ', 'ie', 'in', 'ir',
|
||||
'is', 'i ', 'les ', 'la ', 'le ',
|
||||
'le', 'll', 'maintenant', 'magique', 'ment',
|
||||
'mon', 'mai', 'me', 'ne ', 'onne',
|
||||
'oir', 'our', 'ouv', 'oi', 'on',
|
||||
'ou', 'or', 'pouvoir', 'pour', 'peux',
|
||||
'pas', 'que ', 'qu', 'rubis', 're ',
|
||||
'ra', 're', 'r ', 'sorcier', 's l',
|
||||
's d', 'se', 'so', 's ', 'tro',
|
||||
'te ', 'tu ', 'te', 't ', 'un',
|
||||
'ur', 'u ', 'ver', 'Ah ! Ah ! Ah !', "C'est",
|
||||
'Ganon', 'Maintenant', 'Merci', 'Monde', 'Perle de Lune',
|
||||
'Tu as trouvé ', 'Ténèbres', 'Tu peux', 'Tu ',
|
||||
]
|
||||
|
||||
|
||||
# Uses the original format
|
||||
def org_encoder(cmd, param):
|
||||
if cmd not in kText_CommandNames_US:
|
||||
raise Exception(f'Invalid cmd {cmd}')
|
||||
cmd_index = kText_CommandNames_US.index(cmd)
|
||||
if kText_CommandLengths_US[cmd_index] != (1 if param == None else 2):
|
||||
raise Exception(f'Invalid cmd params {cmd} {param}')
|
||||
if param == None:
|
||||
return [cmd_index + 0x67]
|
||||
return [cmd_index + 0x67, int(param)]
|
||||
|
||||
kCmdInfo = {
|
||||
"Scroll" : (0x80, ),
|
||||
"Waitkey" : (0x81, ),
|
||||
"1" : (0x82, ),
|
||||
"2" : (0x83, ),
|
||||
"3" : (0x84, ),
|
||||
"Name" : (0x85, ),
|
||||
"Wait" : (0x87, {i:i+0x00 for i in range(16)}),
|
||||
"Color" : (0x87, {i:i+0x10 for i in range(16)}),
|
||||
"Number" : (0x87, {i:i+0x20 for i in range(16)}),
|
||||
"Speed" : (0x87, {i:i+0x30 for i in range(16)}),
|
||||
"Sound" : (0x87, {45 : 0x40, 64 : None}), # pt uses 64 for some reason
|
||||
"Choose" : (0x87, 0x80),
|
||||
"Choose2" : (0x87, 0x81),
|
||||
"Choose3" : (0x87, 0x82),
|
||||
"Selchg" : (0x87, 0x83),
|
||||
"Item" : (0x87, 0x84),
|
||||
"NextPic" : (0x87, 0x85),
|
||||
"Window" : (0x87, {0 : None, 2 : 0x86}),
|
||||
"Position" : (0x87, {0: 0x87, 1: 0x88}),
|
||||
"ScrollSpd" : (0, {0 : None}),
|
||||
}
|
||||
|
||||
# Uses another format where there's more bytes left for characters
|
||||
def new_encoder(cmd, param):
|
||||
if cmd not in kCmdInfo:
|
||||
raise Exception(f'Invalid cmd {cmd}')
|
||||
info = kCmdInfo[cmd]
|
||||
if len(info) <= 1 or isinstance(info[1], int):
|
||||
if param != None:
|
||||
raise Exception(f'Invalid cmd params {cmd} {param}')
|
||||
return info
|
||||
else:
|
||||
if param == None or param not in info[1]:
|
||||
raise Exception(f'Invalid cmd params {cmd} {param}')
|
||||
r = info[1][param]
|
||||
return (info[0], r) if r != None else ()
|
||||
|
||||
kEncoders = {'org' : org_encoder, 'new' : new_encoder}
|
||||
|
||||
|
||||
class LangUS:
|
||||
alphabet = kTextAlphabet_US
|
||||
dictionary = kTextDictionary_US
|
||||
command_lengths = kText_CommandLengths_US
|
||||
command_names = kText_CommandNames_US
|
||||
rom_addrs = [0x9c8000, 0x8edf40]
|
||||
COMMAND_START = 0x67
|
||||
SWITCH_BANK = 0x80
|
||||
FINISH = 0xff
|
||||
DICT_BASE_ENC, DICT_BASE_DEC = 0x88, 0x88
|
||||
ESCAPE_CHARACTER = None
|
||||
encoder = 'org'
|
||||
|
||||
class LangEN(LangUS):
|
||||
alphabet = kTextAlphabet_DE
|
||||
dictionary = kTextDictionary_US
|
||||
rom_addrs = [0x9c8000, 0x8edf60]
|
||||
|
||||
class LangES(LangUS):
|
||||
alphabet = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "é", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"ó", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
"[Waves]", ".", ",",
|
||||
# 64 - 79
|
||||
"[...]", ">", "(", ")",
|
||||
"ñ", "ú", "á", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
|
||||
# 80 - 95
|
||||
"[Right]", "í", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[Ankh]", "[4HeartR]", " ", "[Snake]", "[A]", "[B]", "[X]", "[Y]", "[I]",
|
||||
"¡", "¿", "Ñ"
|
||||
]
|
||||
# "[Ankh]", "[Waves]", "[Snake]"
|
||||
|
||||
dictionary = [
|
||||
' ', ' ', ' ', ' en', ' la ', ' el ', ' de ', 'ien', 'tra', ' de',
|
||||
'te ', 'ar', 'a ', 'ada', 'es', 'as', 'o ', ' con', 'ero', 'ado',
|
||||
'e ', 'que', 'en', 'al', 'os ', 'ora', 'nte', ' al', 'lo ', 'or',
|
||||
'os', 'er', 'aci', 'res', ' que ', ' es', 'el', 'los ', 'tar', ' se',
|
||||
', ', 'ro', ' de l', ' est', 're', 'on', 'an', 'pued', ' del', 'ás ',
|
||||
'la', 'ti', 'la ', 'Es', 'to', 'ta', 'para', 'uer', 'ier', ' un ',
|
||||
' por', 'oder', 'da', 'in', 'cu', ' ha', 'per', 'ano', ' ve', 'cer',
|
||||
'lo', ' no ', 'ic', 'ra', 'ab', 'ir', ' una', 'undo', 'es ', 'as ',
|
||||
'con', 'a, ', 'te', ' m', 'gu', ' tu', 'ando', ' p', 'de', 'le',
|
||||
'ol', 'o, ', 'ten', 'lle', ' a ', 'aba', 'com',
|
||||
]
|
||||
rom_addrs = [0x9c8000, 0x8edf40]
|
||||
|
||||
|
||||
class LangNL(LangUS):
|
||||
alphabet = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
"-", ".", ",", "[...]", ">", "(", ")", "[Ankh]",
|
||||
"[Waves]", "[Snake]", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
|
||||
# 80 - 95
|
||||
"[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[4HeartL]", "[4HeartR]", " ", "<", "[A]", "[B]", "[X]", "[Y]",
|
||||
]
|
||||
|
||||
dictionary = [
|
||||
' ', ' ', ' ', "'s ", 'and ', 'are ', 'all ', 'ain', 'and', 'at ',
|
||||
'ast', 'an', 'at', 'ble', 'ba', 'be', 'bo', 'can ', 'che', 'com',
|
||||
'ck', 'des', 'di', 'do', 'en ', 'er ', 'ear', 'ent', 'ed ', 'en',
|
||||
'er', 'ev', 'for', 'fro', 'give ', 'get', 'go', 'have', 'has', 'her',
|
||||
'hi', 'ha', 'ight ', 'ing ', 'in', 'is', 'it', 'just', 'know', 'ly ',
|
||||
'la', 'lo', 'man', 'ma', 'me', 'mu', "n't ", 'non', 'not', 'open',
|
||||
'ound', 'out ', 'of', 'on', 'or', 'per', 'ple', 'pow', 'pro', 're ',
|
||||
're', 'some', 'se', 'sh', 'so', 'st', 'ter ', 'thin', 'ter', 'tha',
|
||||
'the', 'thi', 'to', 'tr', 'up', 'ver', 'with', 'wa', 'we', 'wh',
|
||||
'wi', 'you', 'Her', 'Tha', 'The', 'Thi', 'You',
|
||||
]
|
||||
rom_addrs = [0x9c8000, 0x8edf40]
|
||||
|
||||
|
||||
|
||||
class LangSV(LangUS):
|
||||
alphabet = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "Ö", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
"å", ".", ",", "ä", ">", "(", ")","ö",
|
||||
"Å", "Ä", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
|
||||
# 80 - 95
|
||||
"[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[4HeartL]", "[4HeartR]", " ", "<", "[Ankh]", "[Waves]", "[Snake]", "-", "[I]",
|
||||
"[i]", "…", " ",
|
||||
]
|
||||
|
||||
dictionary = [
|
||||
' ', ' ', ' ', 'Du ', 'till', 'vill', 'bara', 'det', 'den', 'och',
|
||||
'en ', 'r ', 'n ', 'ett', 'en', ' d', 'a ', 'Hjäl', 'har', 'ter',
|
||||
't ', 'var', ' s', 'de', 'kan', 'med', 'som', 'för', 'att', 'ar',
|
||||
' h', 'er', 'jag', 'dig', 'öppna', 'mig', 'är', 'inte', 'hit', 'på ',
|
||||
'an', 'e ', 'rupie', '0kej', ' m', 'et', ', ', 'gång', 'måst', 'ten',
|
||||
' f', 'u ', 'men', 'te', 'tt', 'ka', 'vara', 'ken', '0m ', 'från',
|
||||
'myck', 'någo', 'in', ' k', ' i', 'vil', 'bar', 'ond', 'För', 'Jag',
|
||||
'ra', 'tack', 'll', 'g ', 'ta', 'om', 'anna', 'alla', 'en,', 'ber',
|
||||
'hem', 'han', 'st', 'ig', ' t', 'tro', 'kraf', 'ör', ' v', 'ag',
|
||||
'… ', 'får', 'sin', 'mme', 'mma', 'en ', 'tat',
|
||||
]
|
||||
rom_addrs = [0x9c8000, 0x8edf40]
|
||||
|
||||
|
||||
class LangPL(LangUS):
|
||||
alphabet = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
"-", ".", ",", "ć", "[Right]", "(", ")", "[Ankh]",
|
||||
"[Waves]", "[Snake]", "[LinkL]", "[LinkR]","\"", "[Up]", "[Down]", "ę",
|
||||
|
||||
"ł", "ń", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"ą", "[4HeartR]", " ", "[Left]", "ó", "ś", "ż", "ź", "Ł",
|
||||
"Ś", "Ż", "Ź",
|
||||
] #
|
||||
dictionary = ['Trój', '...', 'ść', 'Nie', ' nie', ' się', 'może', ' że', 'and', 'at ',
|
||||
' ty', 'an', 'at', 'kus', 'ba', 'be', 'bo', 'chce', 'che', 'ki ',
|
||||
'za', 'des', 'di', 'do', 'en ', 'er ', 'sz ', 'ent', 'ed ', 'en',
|
||||
'er', ' w', 'moc', 'zię', 'przez', 'ale', 'go', 'dzie', 'has', 'rze',
|
||||
'hi', 'ha', 'który', 'aby ', 'in', 'is', 'it', 'twoj', 'Może', 'łeś',
|
||||
'la', 'lo', 'czn', 'ma', 'me', 'mu', 'szcz', 'ska', 'śli', 'przy',
|
||||
'znaj', 'iecz', 'of', 'on', 'or', ' ', 'ple', 'pow', 'pro', 're ',
|
||||
're', 'mnie', 'se', ' z', 'so', 'st', 'któr', ' jak', 'ksz', 'sze',
|
||||
'coś', ' je', 'to', 'tr', 'up', 'kie', 'praw', 'wa', 'we', 'mi',
|
||||
'wi', 'szy', 'chc', 'pra', 'cie', ' i ', 'esz',
|
||||
]
|
||||
|
||||
rom_addrs = [0x9c8000, 0x8edf40]
|
||||
|
||||
class LangPT(LangUS):
|
||||
alphabet = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
|
||||
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
|
||||
"-", ".", ",", "[...]", ">", "(", ")", "[Ankh]",
|
||||
"[Waves]", "[Snake]", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
|
||||
# 80 - 95
|
||||
"[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
|
||||
"[4HeartL]", "[4HeartR]", " ", "<", "[A]", "[B]", "[X]", "[Y]", "[I]",
|
||||
|
||||
"¡", "[!]", "Á", "À", "Â", "Ã", "É", "Ê", "Í", "Ó", "Ô", "Õ", "Ú", "á", "à", "â",
|
||||
"ã", "é", "ê", "í", "ó", "ô", "õ", "ú", "ç",
|
||||
]
|
||||
dictionary = [
|
||||
' ', ' ', ' ', ' ', 'o ', 'a ', 'e ', '..', 'de', 'ar',
|
||||
's ', 'ra', ' d', 'es', 'ocê ', 'do', ' a', ' p', 'er', ' e',
|
||||
'que', 'r ', 'os', 'te', ', ', 'as', 'or', 'm ', 'en', ' o',
|
||||
'nt', 're', ' s', 'co', 'da', 'se', 'st', ' c', ' m', 'em',
|
||||
'ma', 'ta', ' n', 'ad', 'on', 'al', 'ro', 'an', 'u ', 'nd',
|
||||
' um', 'pa', 'ca', 'el', ' f', 'to', 'in', ' t', 'ou', 'ei',
|
||||
'ss', 'ir', 'no', 'ri', 'tr', 'me', 'la', 'ia', 'le', 've',
|
||||
'is', 'sa', 'eu', 'pe', 'a.', 'na', 'so', 'mo', 'ga', 'o.',
|
||||
'á ', 'lo', 'ha', 'pr', 'ua', ' l', '! ', 'ui', 'am', 'ti',
|
||||
'io', 'gu', 'i ', 'di', 'nh', ' i', 'id',
|
||||
]
|
||||
ESCAPE_CHARACTER = 0x62
|
||||
rom_addrs = [0x9c8000, 0x8edf40]
|
||||
encoder = 'new'
|
||||
|
||||
def __init__(self):
|
||||
assert len(self.alphabet) == 121
|
||||
|
||||
class LangEU:
|
||||
command_lengths = kText_CommandLengths_EU
|
||||
command_names = kText_CommandNames_EU
|
||||
COMMAND_START = 0x70
|
||||
SWITCH_BANK = 0x88
|
||||
FINISH = 0x8f
|
||||
DICT_BASE_ENC, DICT_BASE_DEC = 0x88, 0x90
|
||||
ESCAPE_CHARACTER = None
|
||||
encoder = 'new'
|
||||
|
||||
class LangDE(LangEU):
|
||||
alphabet = kTextAlphabet_DE
|
||||
dictionary = kTextDictionary_DE
|
||||
rom_addrs = [0x9c8000, 0x8CEB00]
|
||||
|
||||
class LangFR(LangEU):
|
||||
alphabet = kTextAlphabet_FR
|
||||
dictionary = kTextDictionary_FR
|
||||
rom_addrs = [0x9c8000, 0x8CE800]
|
||||
|
||||
class LangFR_C(LangEU):
|
||||
alphabet = kTextAlphabet_FR
|
||||
dictionary = kTextDictionary_FR
|
||||
rom_addrs = [0x9c8000, 0x8CF150]
|
||||
|
||||
kLanguages = {
|
||||
'us' : LangUS(),
|
||||
'de' : LangDE(),
|
||||
'fr' : LangFR(),
|
||||
'fr-c' : LangFR_C(),
|
||||
'en' : LangEN(),
|
||||
'es' : LangES(),
|
||||
'pl' : LangPL(),
|
||||
'pt' : LangPT(),
|
||||
'redux' : LangUS(),
|
||||
'nl' : LangNL(),
|
||||
'sv' : LangSV(),
|
||||
}
|
||||
|
||||
def dialogue_filename(s):
|
||||
if s == 'us': return 'dialogue.txt'
|
||||
return f"dialogue_{s.replace('-', '_')}.txt"
|
||||
|
||||
def uses_new_format(lang):
|
||||
return kLanguages[lang].encoder == 'new'
|
||||
|
||||
dict_expansion = []
|
||||
|
||||
def decode_strings_generic(get_byte, lang):
|
||||
info = kLanguages[lang]
|
||||
p, rom_idx = info.rom_addrs[0], 1
|
||||
result = []
|
||||
while True:
|
||||
s, srcdata = '', []
|
||||
while True:
|
||||
c = get_byte(p)
|
||||
srcdata.append(c)
|
||||
l = info.command_lengths[c - info.COMMAND_START] if c >= info.COMMAND_START and c < info.SWITCH_BANK else 1
|
||||
|
||||
p += l
|
||||
if c == 0x7f: # EndMessage
|
||||
break
|
||||
if c < info.COMMAND_START:
|
||||
if c == info.ESCAPE_CHARACTER:
|
||||
c = get_byte(p); p += 1
|
||||
srcdata.append(c)
|
||||
s += info.alphabet[c]
|
||||
elif c < info.SWITCH_BANK:
|
||||
if l == 2:
|
||||
srcdata.append(get_byte(p - 1))
|
||||
s += '[%s %.2d]' % (info.command_names[c - info.COMMAND_START], get_byte(p - 1))
|
||||
else:
|
||||
s += '[%s]' % info.command_names[c - info.COMMAND_START]
|
||||
elif c == info.FINISH:
|
||||
return result # done
|
||||
elif c == info.SWITCH_BANK:
|
||||
p = info.rom_addrs[rom_idx]; rom_idx += 1
|
||||
s, srcdata = '', []
|
||||
elif c < info.SWITCH_BANK + 8:
|
||||
if lang != 'pt':
|
||||
assert 0
|
||||
else:
|
||||
s += info.dictionary[c - info.DICT_BASE_DEC]
|
||||
dict_expansion.append(len(info.dictionary[c - info.DICT_BASE_DEC]))
|
||||
result.append((s, srcdata))
|
||||
if len(result) >= 397 and lang == 'pt':
|
||||
return result
|
||||
# print(len(result), s)
|
||||
# if len(result) == 89:
|
||||
# print(s)
|
||||
# sys.exit(0)
|
||||
|
||||
|
||||
|
||||
def print_strings(rom, file = None):
|
||||
texts = decode_strings_generic(rom.get_byte, rom.language)
|
||||
if len(texts) == 396:
|
||||
extra_str = "[Speed 00]0- [Number 00]. 1- [Number 01][2]2- [Number 02]. 3- [Number 03]"
|
||||
texts = texts[:4] + [(extra_str, None)] + texts[4:]
|
||||
|
||||
for i, s in enumerate(texts):
|
||||
print('%s: %s' % (i + 1, s[0]), file = file)
|
||||
|
||||
|
||||
def encode_greedy_from_dict(s, i, rev, a2i, info):
|
||||
a = s[i:]
|
||||
if r := rev.get(a[0]):
|
||||
for k, v in r.items():
|
||||
if a.startswith(k):
|
||||
return [v + info.DICT_BASE_ENC], len(k)
|
||||
|
||||
if a[0] == '[':
|
||||
cmd, param = a[1:a.index(']')], None
|
||||
cmdlen = len(cmd)
|
||||
if r := a2i.get(a[:cmdlen+2]):
|
||||
return [r], cmdlen+2
|
||||
if ' ' in cmd:
|
||||
cmd, param = cmd.split(' ', 1)
|
||||
param = int(param)
|
||||
return kEncoders[info.encoder](cmd, param), cmdlen + 2
|
||||
else:
|
||||
return [a2i[a[0]]], 1
|
||||
|
||||
print('substr %s not found' % a)
|
||||
assert 0
|
||||
|
||||
def compress_strings(xs, lang = 'us'):
|
||||
info = kLanguages[lang]
|
||||
rev = {}
|
||||
for a,b in enumerate(info.dictionary):
|
||||
rev.setdefault(b[0], {})[b] = a
|
||||
#rev = {b:a for a,b in enumerate(info.dictionary)}
|
||||
a2i = {e:i for i,e in enumerate(info.alphabet)}
|
||||
def compress_string(s):
|
||||
i = 0
|
||||
r = bytearray()
|
||||
while i < len(s):
|
||||
what, num = encode_greedy_from_dict(s, i, rev, a2i, info)
|
||||
r.extend(what)
|
||||
i += num
|
||||
return r
|
||||
return [compress_string(x) for x in xs]
|
||||
|
||||
def verify(get_byte):
|
||||
for i, (decoded, original) in enumerate(decode_strings_generic(get_byte, 'us')):
|
||||
c = compress_strings([decoded])[0]
|
||||
if c != original:
|
||||
print('String %s not match: %s, %s' % (decoded, c, original))
|
||||
break
|
||||
else:
|
||||
pass
|
||||
|
||||
def encode_dictionary(lang = 'us'):
|
||||
info = kLanguages[lang]
|
||||
rev = {b:a for a,b in enumerate(info.alphabet)}
|
||||
return [bytearray(rev[c] for c in line) for line in info.dictionary]
|
||||
|
||||
if __name__ == "__main__":
|
||||
ROM = util.load_rom(sys.argv[1] if len(sys.argv) >= 2 else None, True)
|
||||
|
||||
decoded = decode_strings_generic(ROM.get_byte, 'de')
|
||||
print('Total bytes: %d' % sum(len(a[1]) for a in decoded))
|
||||
|
||||
print('Dict tokens: %d' % len(dict_expansion))
|
||||
print('Dict save: %d' % (sum(dict_expansion) - len(dict_expansion)))
|
||||
|
||||
print('US size ', len(kTextDictionary_US))
|
||||
print('DE size ', len(kTextDictionary_DE))
|
||||
|
||||
texts = [a[0] for a in decoded]
|
||||
|
||||
|
||||
# Pal seems to have one string too little
|
||||
if len(texts) == 396:
|
||||
extra_str = "[Speed 00]0- [Number 00]. 1- [Number 01][2]2- [Number 02]. 3- [Number 03]"
|
||||
texts = texts[:4] + [extra_str] + texts[4:]
|
||||
|
||||
#for i, s in enumerate(texts):
|
||||
# print('%s: %s' % (i + 1, s), file = None)
|
||||
|
||||
|
||||
#encode_dictionary()
|
||||
compr = compress_strings(texts, 'de')
|
||||
print(f'Compressed size (excl eof): {sum(len(a) for a in compr)}')
|
||||
|
||||
|
||||
@@ -2,19 +2,89 @@ import array
|
||||
import sys
|
||||
import hashlib
|
||||
import os
|
||||
from functools import lru_cache
|
||||
|
||||
def cache(user_function):
|
||||
'Simple lightweight unbounded cache. Sometimes called "memoize".'
|
||||
return lru_cache(maxsize=None)(user_function)
|
||||
|
||||
# Both are common SNES rom extensions. For Zelda3 (NA), they are equivalent files.
|
||||
COMMON_ROM_NAMES = ['zelda3.sfc', 'zelda3.smc']
|
||||
DEFAULT_ROM_DIRECTORY = os.path.dirname(__file__)
|
||||
ZELDA3_SHA256 = '66871d66be19ad2c34c927d6b14cd8eb6fc3181965b6e517cb361f7316009cfb'
|
||||
DEFAULT_ROM_DIRECTORY = os.path.join(os.path.dirname(__file__), '..')
|
||||
|
||||
ZELDA3_SHA1_US = '6D4F10A8B10E10DBE624CB23CF03B88BB8252973'
|
||||
ZELDA3_SHA1 = {
|
||||
ZELDA3_SHA1_US : ('us', 'Legend of Zelda, The - A Link to the Past (USA)'),
|
||||
'2E62494967FB0AFDF5DA1635607F9641DF7C6559' : ('de', 'Legend of Zelda, The - A Link to the Past (Germany)'),
|
||||
'229364A1B92A05167CD38609B1AA98F7041987CC' : ('fr', 'Legend of Zelda, The - A Link to the Past (France)'),
|
||||
'C1C6C7F76FFF936C534FF11F87A54162FC0AA100' : ('fr-c', 'Legend of Zelda, The - A Link to the Past (Canada)'),
|
||||
'7C073A222569B9B8E8CA5FCB5DFEC3B5E31DA895' : ('en', 'Legend of Zelda, The - A Link to the Past (Europe)'),
|
||||
'461FCBD700D1332009C0E85A7A136E2A8E4B111E' : ('es', 'Spanish - https://www.romhacking.net/translations/2195/'),
|
||||
'3C4D605EEFDA1D76F101965138F238476655B11D' : ('pl', 'Polish - https://www.romhacking.net/translations/5760/'),
|
||||
'D0D09ED41F9C373FE6AFDCCAFBF0DA8C88D3D90D' : ('pt', 'Portuguese - https://www.romhacking.net/translations/6530/'),
|
||||
'B2A07A59E64C498BC1B2F28728F9BF4014C8D582' : ('redux', 'English Redux - https://www.romhacking.net/translations/6657/'),
|
||||
'9325C22EB0A2A1F0017157C8B620BC3A605CEDE1' : ('redux', 'English Redux - https://www.romhacking.net/hacks/2594/'),
|
||||
'FA8ADFDBA2697C9A54D583A1284A22AC764C7637' : ('nl', 'Dutch - https://www.romhacking.net/translations/1124/'),
|
||||
'43CD3438469B2C3FE879EA2F410B3EF3CB3F1CA4' : ('sv', 'Swedish - https://www.romhacking.net/translations/982/'),
|
||||
}
|
||||
|
||||
def load_rom(filename, support_multilanguage = False):
|
||||
global ROM
|
||||
ROM = LoadedRom(filename, support_multilanguage)
|
||||
return ROM
|
||||
|
||||
def get_byte(addr):
|
||||
return ROM.get_byte(addr)
|
||||
|
||||
@cache
|
||||
def get_bytes(addr, n):
|
||||
return ROM.get_bytes(addr, n)
|
||||
|
||||
@cache
|
||||
def get_words(addr, n):
|
||||
return ROM.get_words(addr, n)
|
||||
|
||||
def get_int8(ea):
|
||||
b = get_byte(ea)
|
||||
if b & 0x80: b -= 256
|
||||
return b
|
||||
|
||||
def get_int16(ea):
|
||||
b = get_word(ea)
|
||||
if b & 0x8000: b -= 65536
|
||||
return b
|
||||
|
||||
def get_word(addr):
|
||||
return ROM.get_word(addr)
|
||||
|
||||
|
||||
class LoadedRom:
|
||||
def __init__(self, path = None):
|
||||
def __init__(self, path = None, support_multilanguage = False):
|
||||
rom_path = self.__get_rom_path(path)
|
||||
self.ROM = open(rom_path, 'rb').read()
|
||||
hash = hashlib.sha256(self.ROM).hexdigest()
|
||||
if hash != ZELDA3_SHA256:
|
||||
raise Exception(f"ROM with hash {hash} not supported. Expected {ZELDA3_SHA256}. Please verify your ROM is the NA 1.0 version.");
|
||||
|
||||
# Remove the SMC header?
|
||||
if (len(self.ROM) & 0xfffff) == 0x200:
|
||||
self.ROM = self.ROM[0x200:]
|
||||
|
||||
hash = hashlib.sha1(self.ROM).hexdigest().upper()
|
||||
entry = ZELDA3_SHA1.get(hash)
|
||||
self.language = entry[0] if entry != None else None
|
||||
|
||||
# Workaround for swedish rom with broken size
|
||||
if self.language == 'sv' and len(self.ROM) == 0x10083b:
|
||||
self.ROM = self.ROM[0x200:]
|
||||
|
||||
if support_multilanguage:
|
||||
if self.language == None:
|
||||
msg = f"\n\nROM with hash {hash} not supported.\n\nYou need one of the following ROMs to extract the resources:\n"
|
||||
for k, v in ZELDA3_SHA1.items():
|
||||
msg += '%5s: %s: %s\n' % (v[0], k, v[1])
|
||||
raise Exception(msg)
|
||||
print('Identified ROM as: %s - "%s"' % entry)
|
||||
else:
|
||||
if self.language != 'us':
|
||||
raise Exception(f"\n\nROM with hash {hash} not supported.\n\nExpected {ZELDA3_SHA1_US}.\nPlease verify your ROM is \"Legend of Zelda, The - A Link to the Past (USA)\"");
|
||||
|
||||
def get_byte(self, ea):
|
||||
assert (ea & 0x8000)
|
||||
@@ -28,7 +98,7 @@ class LoadedRom:
|
||||
return self.get_byte(ea) + self.get_byte(ea + 1) * 256 + self.get_byte(ea + 2) * 65536
|
||||
|
||||
def get_bytes(self, addr, n):
|
||||
r = []
|
||||
r = bytearray()
|
||||
for i in range(n):
|
||||
r.append(self.get_byte(addr))
|
||||
addr += 1
|
||||
25
extract_assets.bat
Normal file
25
extract_assets.bat
Normal file
@@ -0,0 +1,25 @@
|
||||
@echo off
|
||||
|
||||
2>nul (del zelda3_assets.dat)
|
||||
python assets/restool.py --extract-from-rom
|
||||
IF NOT ERRORLEVEL 0 goto ERROR
|
||||
|
||||
IF NOT EXIST "zelda3_assets.dat" (
|
||||
ECHO ERROR: The python program didn't generate zelda3_assets.dat successfully.
|
||||
goto ERROR
|
||||
) ELSE (
|
||||
REM
|
||||
)
|
||||
|
||||
goto DONE
|
||||
|
||||
|
||||
:ERROR
|
||||
ECHO:
|
||||
ECHO ERROR: Asset extraction failed!
|
||||
pause
|
||||
EXIT /B 1
|
||||
|
||||
:DONE
|
||||
echo Complete!
|
||||
pause
|
||||
BIN
other/3x5_font.png
Normal file
BIN
other/3x5_font.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 590 B |
117
other/make_text_dict.py
Normal file
117
other/make_text_dict.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import array
|
||||
|
||||
memos = {}
|
||||
memoslist = []
|
||||
def memo(s):
|
||||
m = memos.get(s)
|
||||
if m == None:
|
||||
m = len(memoslist)
|
||||
memos[s] = m
|
||||
memoslist.append(s)
|
||||
return m
|
||||
|
||||
def tos(s): return "".join(memoslist[c] for c in s)
|
||||
|
||||
lines = []
|
||||
for line in open('dialogue.txt', 'r').read().splitlines():
|
||||
line = line.split(': ')[1]
|
||||
|
||||
r = array.array('H')
|
||||
|
||||
i = 0
|
||||
while i < len(line):
|
||||
if line[i] == '[':
|
||||
j = line.index(']', i + 1)
|
||||
r.append(memo(line[i:j+1]))
|
||||
i = j + 1
|
||||
else:
|
||||
r.append(memo(line[i]))
|
||||
i += 1
|
||||
|
||||
#print(repr(line))
|
||||
#print(r)
|
||||
lines.append(list(r))
|
||||
import collections
|
||||
|
||||
|
||||
def find_all_ngrams(lines, N, cost):
|
||||
ctr = collections.Counter()
|
||||
for line in lines:
|
||||
for i in range(len(line) - N + 1):
|
||||
if line[i] != line[i+1]:
|
||||
ctr[tuple(line[i:i+N])] += 1
|
||||
r = list((b, a) for a, b in ctr.items() if b >= 2)
|
||||
if len(r) == 0:
|
||||
return None, 0
|
||||
b, a = max(r)
|
||||
return a, (N - cost) * b - N - 2 # 2 is the overhead of the dict
|
||||
|
||||
def find_best_ngram(cost):
|
||||
best_score=0
|
||||
|
||||
for i in range(2, 32):
|
||||
text, score = find_all_ngrams(lines, i, cost)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_text = text
|
||||
return best_score, best_text
|
||||
|
||||
def update_ngrams(lines, replace_from, replace_to):
|
||||
for line in lines:
|
||||
for i in range(len(line) - len(replace_from) + 1):
|
||||
if tuple(line[i:i+len(replace_from)]) == replace_from:
|
||||
line[i:i+len(replace_from)] = replace_to
|
||||
|
||||
total_gain = 0
|
||||
|
||||
original_tokens = sum(len(line) for line in lines)
|
||||
|
||||
|
||||
kTextDictionary_US = [
|
||||
' ', ' ', ' ', "'s ", 'and ',
|
||||
'are ', 'all ', 'ain', 'and', 'at ',
|
||||
'ast', 'an', 'at', 'ble', 'ba',
|
||||
'be', 'bo', 'can ', 'che', 'com',
|
||||
'ck', 'des', 'di', 'do', 'en ',
|
||||
'er ', 'ear', 'ent', 'ed ', 'en',
|
||||
'er', 'ev', 'for', 'fro', 'give ',
|
||||
'get', 'go', 'have', 'has', 'her',
|
||||
'hi', 'ha', 'ight ', 'ing ', 'in',
|
||||
'is', 'it', 'just', 'know', 'ly ',
|
||||
'la', 'lo', 'man', 'ma', 'me',
|
||||
'mu', "n't ", 'non', 'not', 'open',
|
||||
'ound', 'out ', 'of', 'on', 'or',
|
||||
'per', 'ple', 'pow', 'pro', 're ',
|
||||
're', 'some', 'se', 'sh', 'so',
|
||||
'st', 'ter ', 'thin', 'ter', 'tha',
|
||||
'the', 'thi', 'to', 'tr', 'up',
|
||||
'ver', 'with', 'wa', 'we', 'wh',
|
||||
'wi', 'you', 'Her', 'Tha', 'The',
|
||||
'Thi', 'You',
|
||||
]
|
||||
|
||||
|
||||
dictionary = []
|
||||
|
||||
for i in range(111+256):
|
||||
best_score, best_text = find_best_ngram(1 if i < 111 else 2)
|
||||
if best_score == 0:
|
||||
break
|
||||
|
||||
total_gain += best_score
|
||||
|
||||
print(f'Removed best bigram "{tos(best_text)}" with gain {best_score}, total gain {total_gain} / {original_tokens}')
|
||||
|
||||
dictionary.append(best_text)
|
||||
|
||||
update_ngrams(lines, best_text, [memo('{%s}' % tos(best_text))])
|
||||
|
||||
#print('kTextDictionary_NEW = [')
|
||||
#for i, d in enumerate(dictionary):
|
||||
# repl = tos(d).replace('{', '').replace('}', '')
|
||||
# print(f'{repr(repl)},')
|
||||
#print(']')
|
||||
|
||||
|
||||
for i, a in enumerate(lines):
|
||||
print(i, tos(a))
|
||||
128
other/msu/encode_opus.py
Normal file
128
other/msu/encode_opus.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
import concurrent.futures
|
||||
import opuslib
|
||||
import struct
|
||||
import samplerate # pip install samplerate
|
||||
import numpy as np
|
||||
|
||||
# File format
|
||||
# 4 byte: OPUZ
|
||||
# 4 byte: VERSION = 0
|
||||
# Foreach Repeat Entry {
|
||||
# 4 byte: seek to file offset
|
||||
# 4 byte: num pcm samples to play from here
|
||||
# 2 byte: # samples to skip + flags
|
||||
# }
|
||||
|
||||
def encode_to_msu_opus(msu_infile, bitrate = 128000):
|
||||
raw_msu1 = bytearray(open(msu_infile, 'rb').read())
|
||||
|
||||
if raw_msu1[:4] != b'MSU1':
|
||||
raise Exception('Expecting MSU1 file')
|
||||
msu_repeat_pos, = struct.unpack_from('<I', raw_msu1, 4)
|
||||
|
||||
np_audio = np.frombuffer(raw_msu1[8:], dtype=np.int16)
|
||||
np_audio = np.reshape(np_audio, (-1, 2)).astype(np.float32) * (1.0/32768.0)
|
||||
assert np_audio.shape[1] == 2
|
||||
|
||||
#print('Song has %d samples in 44.1khz: %s' % (np_audio.shape[0], np_audio.dtype))
|
||||
np_audio = samplerate.resample(np_audio, 48000 / 44100, 'sinc_best')
|
||||
source_samples = np_audio.shape[0]
|
||||
msu_repeat_pos = msu_repeat_pos * 48000 // 44100
|
||||
#print('Song has %d samples in 48khz: %s' % (np_audio.shape[0], np_audio.dtype))
|
||||
|
||||
audio = bytearray(np_audio.tobytes())
|
||||
|
||||
FRAME_SIZE = 960
|
||||
BYTES_PER_SAMPLE = 8
|
||||
BYTES_PER_FRAME = FRAME_SIZE * BYTES_PER_SAMPLE
|
||||
|
||||
def create_encoder(bitrate):
|
||||
enc = opuslib.Encoder(48000, 2, 'audio')
|
||||
enc.vbr = True
|
||||
enc.complexity = 10
|
||||
enc.bitrate = bitrate
|
||||
opuslib.api.encoder.encoder_ctl(enc.encoder_state, opuslib.api.ctl.ctl_set(11002), 1002) # force celt encoding
|
||||
return enc
|
||||
|
||||
def pad_data_to_frame_size(audio, lookahead):
|
||||
audio.extend(bytearray(lookahead * BYTES_PER_SAMPLE))
|
||||
rest = len(audio) % BYTES_PER_FRAME
|
||||
if rest:
|
||||
audio.extend(bytearray(BYTES_PER_FRAME - rest))
|
||||
|
||||
enc = create_encoder(bitrate)
|
||||
pad_data_to_frame_size(audio, enc.lookahead)
|
||||
|
||||
assert len(audio) % BYTES_PER_FRAME == 0
|
||||
|
||||
result = bytearray()
|
||||
framelist = []
|
||||
for i in range(len(audio)//BYTES_PER_FRAME):
|
||||
l = enc.encode_float(bytes(audio[i*BYTES_PER_FRAME:i*BYTES_PER_FRAME+BYTES_PER_FRAME]), frame_size = FRAME_SIZE)
|
||||
code = len(l)
|
||||
if l[0] == 0xfc:
|
||||
l = l[1:]
|
||||
code = (code - 1) | 0x8000
|
||||
framelist.append((i*FRAME_SIZE, len(result)))
|
||||
result.extend(struct.pack('<H', code))
|
||||
result.extend(l)
|
||||
# print(len(l), '%.2x'%l[0])
|
||||
return result, framelist, enc.lookahead, msu_repeat_pos, source_samples
|
||||
|
||||
def convert_to_opuz(filename, repeat):
|
||||
print('Converting %s' % filename)
|
||||
result, framelist, preskip, msu_repeat_pos, songlength = encode_to_msu_opus(filename, 128000)
|
||||
|
||||
# lookup what sample to seek to based on a sample nr
|
||||
def lookup_sample(framelist, sample_nr):
|
||||
last = None
|
||||
for sample, fileoffs in framelist:
|
||||
if sample_nr < sample:
|
||||
break
|
||||
last = sample, fileoffs
|
||||
return last
|
||||
|
||||
def calc_play_ranges(xs):
|
||||
r = []
|
||||
for i, (start, end, repeat_from_here) in enumerate(xs):
|
||||
frame = lookup_sample(framelist, start)
|
||||
r.append((frame[1], end - start, preskip + start - frame[0], start, repeat_from_here))
|
||||
return r
|
||||
|
||||
def serialize_header(xs):
|
||||
r = b'OPUZ' + struct.pack('<I', 0)
|
||||
offs = len(r) + len(xs) * 10
|
||||
for i, (a, b, c, d, e) in enumerate(xs):
|
||||
f = c | (0x4000 if e else 0x0) | (0x8000 if i == len(xs) - 1 else 0)
|
||||
r += struct.pack('<IIH', a + offs, b, f)
|
||||
return r
|
||||
|
||||
# lookup the frame that contains msu_repeat_pos
|
||||
if repeat:
|
||||
play_ranges = [(0, songlength, False), (msu_repeat_pos, songlength, True)]
|
||||
else:
|
||||
play_ranges = [(0, songlength, False)]
|
||||
|
||||
play_ranges = calc_play_ranges(play_ranges)
|
||||
|
||||
header = serialize_header(play_ranges)
|
||||
if filename.endswith('.pcm'):
|
||||
filename = filename[:-4]
|
||||
open(filename + '.opuz', 'wb').write(header + result)
|
||||
|
||||
kMsuTracksWithRepeat = [
|
||||
1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,0,
|
||||
1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,
|
||||
1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
]
|
||||
|
||||
def task(i):
|
||||
convert_to_opuz(r"../../msu/ALttP-msu-Deluxe-%d.pcm" % i, i >= 48 or kMsuTracksWithRepeat[i])
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
results = [executor.submit(task, i) for i in range(1, 115)]
|
||||
|
||||
for future in concurrent.futures.as_completed(results):
|
||||
result = future.result()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
@echo off
|
||||
|
||||
set SDL2=third_party\SDL2-2.24.0
|
||||
set SDL2=third_party\SDL2-2.26.3
|
||||
|
||||
IF NOT EXIST "third_party\tcc\tcc.exe" (
|
||||
ECHO:
|
||||
ECHO ERROR: third_party\tcc\tcc.exe doesn't exist. Please verify that you have put it in the right location.
|
||||
ECHO Download it from http://download.savannah.gnu.org/releases/tinycc/tcc-0.9.27-win64-bin.zip
|
||||
ECHO Download it from https://github.com/FitzRoyX/tinycc/releases/download/tcc_20221020/tcc_20221020.zip
|
||||
ECHO It needs to be the 64-bit version.
|
||||
ECHO:
|
||||
PAUSE
|
||||
@@ -17,7 +17,7 @@ IF NOT EXIST "third_party\tcc\tcc.exe" (
|
||||
IF NOT EXIST "%SDL2%\lib\x64\SDL2.dll" (
|
||||
ECHO:
|
||||
ECHO ERROR: SDL is not unzipped properly into %SDL2%
|
||||
ECHO Download it from https://github.com/libsdl-org/SDL/releases/download/release-2.24.0/SDL2-devel-2.24.0-VC.zip
|
||||
ECHO Download it from https://github.com/libsdl-org/SDL/releases/download/release-2.26.3/SDL2-devel-2.26.3-VC.zip
|
||||
ECHO:
|
||||
PAUSE
|
||||
EXIT /B 1
|
||||
@@ -25,9 +25,9 @@ IF NOT EXIST "%SDL2%\lib\x64\SDL2.dll" (
|
||||
REM
|
||||
)
|
||||
|
||||
IF NOT EXIST "tables\zelda3_assets.dat" (
|
||||
IF NOT EXIST "zelda3_assets.dat" (
|
||||
ECHO:
|
||||
ECHO ERROR: tables\zelda3_assets.dat was not found.
|
||||
ECHO ERROR: zelda3_assets.dat was not found.
|
||||
ECHO You need to extract assets from the ROM first, or get this file from a friend. Please see README.md
|
||||
ECHO:
|
||||
PAUSE
|
||||
@@ -38,7 +38,7 @@ IF NOT EXIST "tables\zelda3_assets.dat" (
|
||||
|
||||
|
||||
echo Building with TCC...
|
||||
third_party\tcc\tcc.exe -ozelda3.exe -DHAVE_STDINT_H=1 -D_HAVE_STDINT_H=1 -DSYSTEM_VOLUME_MIXER_AVAILABLE=0 -I%SDL2%/include -L%SDL2%/lib/x64 -lSDL2 *.c snes/*.c
|
||||
third_party\tcc\tcc.exe -ozelda3.exe -DCOMPILER_TCC=1 -DSTBI_NO_SIMD=1 -DHAVE_STDINT_H=1 -D_HAVE_STDINT_H=1 -DSYSTEM_VOLUME_MIXER_AVAILABLE=0 -I%SDL2%/include -L%SDL2%/lib/x64 -lSDL2 -I. src/*.c snes/*.c third_party/gl_core/gl_core_3_1.c third_party/opus-1.3.1-stripped/opus_decoder_amalgam.c
|
||||
IF ERRORLEVEL 1 goto GETOUT
|
||||
|
||||
copy %SDL2%\lib\x64\SDL2.dll .
|
||||
|
||||
@@ -735,10 +735,16 @@ static void cpu_trb(Cpu* cpu, uint32_t low, uint32_t high) {
|
||||
void HookedFunctionRts(int is_long);
|
||||
|
||||
static void cpu_doOpcode(Cpu* cpu, uint8_t opcode) {
|
||||
RESTART:
|
||||
switch(opcode) {
|
||||
case 0x00: { // brk imp
|
||||
uint32_t addr = (cpu->k << 16) | cpu->pc;
|
||||
switch (addr - 1) {
|
||||
case 0x7B269: // Link_APress_LiftCarryThrow reads OOB
|
||||
if ((cpu->x & 0xff) >= 28)
|
||||
cpu->pc = 0xB280; // RTS
|
||||
opcode = 0xE8;
|
||||
goto RESTART;
|
||||
|
||||
// Uncle_AtHome case 3 will read random memory.
|
||||
case 0x5DEC7:
|
||||
|
||||
126
snes/ppu.c
126
snes/ppu.c
@@ -6,7 +6,7 @@
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include "ppu.h"
|
||||
#include "../types.h"
|
||||
#include "src/types.h"
|
||||
|
||||
static const uint8 kSpriteSizes[8][2] = {
|
||||
{8, 16}, {8, 32}, {8, 64}, {16, 32},
|
||||
@@ -122,10 +122,14 @@ void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx) {
|
||||
func(ctx, tmp, 123);
|
||||
}
|
||||
|
||||
bool PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags) {
|
||||
int PpuGetCurrentRenderScale(Ppu *ppu, uint32_t render_flags) {
|
||||
bool hq = ppu->mode == 7 && !ppu->forcedBlank &&
|
||||
(render_flags & (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer)) == (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer);
|
||||
return hq ? 4 : 1;
|
||||
}
|
||||
|
||||
void PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags) {
|
||||
ppu->renderFlags = render_flags;
|
||||
bool hq = ppu->mode == 7 && !ppu->forcedBlank &&
|
||||
(ppu->renderFlags & (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer)) == (kPpuRenderFlags_4x4Mode7 | kPpuRenderFlags_NewRenderer);
|
||||
ppu->renderPitch = (uint)pitch;
|
||||
ppu->renderBuffer = pixels;
|
||||
|
||||
@@ -140,14 +144,12 @@ bool PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_fl
|
||||
memset(&ppu->brightnessMult[32], ppu->brightnessMult[31], 31);
|
||||
}
|
||||
|
||||
if (hq) {
|
||||
if (PpuGetCurrentRenderScale(ppu, ppu->renderFlags) == 4) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
uint32 color = ppu->cgram[i];
|
||||
ppu->colorMapRgb[i] = ppu->brightnessMult[color & 0x1f] << 16 | ppu->brightnessMult[(color >> 5) & 0x1f] << 8 | ppu->brightnessMult[(color >> 10) & 0x1f];
|
||||
}
|
||||
}
|
||||
|
||||
return hq;
|
||||
}
|
||||
|
||||
static inline void ClearBackdrop(PpuPixelPrioBufs *buf) {
|
||||
@@ -172,10 +174,7 @@ void ppu_runLine(Ppu *ppu, int line) {
|
||||
|
||||
// outside of visible range?
|
||||
if (line >= 225 + ppu->extraBottomCur) {
|
||||
uint8 *dst = &ppu->renderBuffer[(line - 1) * 2 * ppu->renderPitch];
|
||||
size_t n = sizeof(uint32) * 2 * (256 + ppu->extraLeftRight * 2);
|
||||
memset(dst, 0, n);
|
||||
memset(dst + ppu->renderPitch, 0, n);
|
||||
memset(&ppu->renderBuffer[(line - 1) * ppu->renderPitch], 0, sizeof(uint32) * (256 + ppu->extraLeftRight * 2));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,13 +186,11 @@ void ppu_runLine(Ppu *ppu, int line) {
|
||||
for (int x = 0; x < 256; x++)
|
||||
ppu_handlePixel(ppu, x, line);
|
||||
|
||||
uint8 *dst = ppu->renderBuffer + ((line - 1) * 2 * ppu->renderPitch);
|
||||
|
||||
uint8 *dst = ppu->renderBuffer + ((line - 1) * ppu->renderPitch);
|
||||
if (ppu->extraLeftRight != 0) {
|
||||
memset(dst, 0, 2 * sizeof(uint32) * ppu->extraLeftRight);
|
||||
memset(dst + 2 * sizeof(uint32) * (256 + ppu->extraLeftRight), 0, 2 * sizeof(uint32) * ppu->extraLeftRight);
|
||||
memset(dst, 0, sizeof(uint32) * ppu->extraLeftRight);
|
||||
memset(dst + sizeof(uint32) * (256 + ppu->extraLeftRight), 0, sizeof(uint32) * ppu->extraLeftRight);
|
||||
}
|
||||
memcpy(dst + ppu->renderPitch, dst, 2 * sizeof(uint32) * (256 + ppu->extraLeftRight * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +217,6 @@ static void PpuWindows_Calc(PpuWindows *win, Ppu *ppu, uint layer) {
|
||||
int window_right = 256 + (layer != 2 ? ppu->extraRightCur : 0);
|
||||
win->edges[0] = - (layer != 2 ? ppu->extraLeftCur : 0);
|
||||
win->edges[1] = window_right;
|
||||
uint8 window_bits = 0;
|
||||
uint i, j;
|
||||
int t;
|
||||
bool w1_ena = (winflags & kWindow1Enabled) && ppu->window1left <= ppu->window1right;
|
||||
@@ -474,8 +470,8 @@ static void PpuDrawBackground_2bpp(Ppu *ppu, uint y, bool sub, uint layer, PpuZb
|
||||
|
||||
// Draw a whole line of a 4bpp background layer into bgBuffers, with mosaic applied
|
||||
static void PpuDrawBackground_4bpp_mosaic(Ppu *ppu, uint y, bool sub, uint layer, PpuZbufType zhi, PpuZbufType zlo) {
|
||||
#define GET_PIXEL(i) pixel = (bits) & 1 | (bits >> 7) & 2 | (bits >> 14) & 4 | (bits >> 21) & 8
|
||||
#define GET_PIXEL_HFLIP(i) pixel = (bits >> 7) & 1 | (bits >> 14) & 2 | (bits >> 21) & 4 | (bits >> 28) & 8
|
||||
#define GET_PIXEL() pixel = (bits) & 1 | (bits >> 7) & 2 | (bits >> 14) & 4 | (bits >> 21) & 8
|
||||
#define GET_PIXEL_HFLIP() pixel = (bits >> 7) & 1 | (bits >> 14) & 2 | (bits >> 21) & 4 | (bits >> 28) & 8
|
||||
#define READ_BITS(ta, tile) (addr = &ppu->vram[((ta) + (tile) * 16) & 0x7fff], addr[0] | addr[8] << 16)
|
||||
enum { kPaletteShift = 6 };
|
||||
if (!IS_SCREEN_ENABLED(ppu, sub, layer))
|
||||
@@ -533,8 +529,8 @@ static void PpuDrawBackground_4bpp_mosaic(Ppu *ppu, uint y, bool sub, uint layer
|
||||
|
||||
// Draw a whole line of a 2bpp background layer into bgBuffers, with mosaic applied
|
||||
static void PpuDrawBackground_2bpp_mosaic(Ppu *ppu, int y, bool sub, uint layer, PpuZbufType zhi, PpuZbufType zlo) {
|
||||
#define GET_PIXEL(i) pixel = (bits) & 1 | (bits >> 7) & 2
|
||||
#define GET_PIXEL_HFLIP(i) pixel = (bits >> 7) & 1 | (bits >> 14) & 2
|
||||
#define GET_PIXEL() pixel = (bits) & 1 | (bits >> 7) & 2
|
||||
#define GET_PIXEL_HFLIP() pixel = (bits >> 7) & 1 | (bits >> 14) & 2
|
||||
#define READ_BITS(ta, tile) (addr = &ppu->vram[((ta) + (tile) * 8) & 0x7fff], addr[0])
|
||||
enum { kPaletteShift = 8 };
|
||||
if (!IS_SCREEN_ENABLED(ppu, sub, layer))
|
||||
@@ -570,7 +566,7 @@ static void PpuDrawBackground_2bpp_mosaic(Ppu *ppu, int y, bool sub, uint layer,
|
||||
int ta = (tile & 0x8000) ? tileadr1 : tileadr0;
|
||||
PpuZbufType z = (tile & 0x2000) ? zhi : zlo;
|
||||
uint32 bits = READ_BITS(ta, tile & 0x3ff);
|
||||
if (tile & 0x4000) bits >>= x, GET_PIXEL(0); else bits <<= x, GET_PIXEL_HFLIP(0);
|
||||
if (tile & 0x4000) bits >>= x, GET_PIXEL(); else bits <<= x, GET_PIXEL_HFLIP();
|
||||
if (pixel) {
|
||||
pixel += (tile & 0x1c00) >> kPaletteShift;
|
||||
uint i = 0;
|
||||
@@ -714,9 +710,6 @@ static void PpuDrawMode7Upsampled(Ppu *ppu, uint y) {
|
||||
uint32 xCenter = ((int16_t)(ppu->m7matrix[4] << 3)) >> 3, yCenter = ((int16_t)(ppu->m7matrix[5] << 3)) >> 3;
|
||||
uint32 clippedH = (((int16_t)(ppu->m7matrix[6] << 3)) >> 3) - xCenter;
|
||||
uint32 clippedV = (((int16_t)(ppu->m7matrix[7] << 3)) >> 3) - yCenter;
|
||||
uint32 m0 = ppu->m7matrix[0]; // xpos increment per horiz movement
|
||||
uint32 m3 = ppu->m7matrix[3]; // ypos increment per vert movement
|
||||
uint8 *dst_start = &ppu->renderBuffer[(y - 1) * 4 * ppu->renderPitch], *dst_end, *dst = dst_start + ppu->extraLeftRight * 4 * 4;
|
||||
int32 m0v[4];
|
||||
if (*(uint32*)&ppu->mode7PerspectiveLow == 0) {
|
||||
m0v[0] = m0v[1] = m0v[2] = m0v[3] = ppu->m7matrix[0] << 12;
|
||||
@@ -725,11 +718,15 @@ static void PpuDrawMode7Upsampled(Ppu *ppu, uint y) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
m0v[i] = 4096.0f / FloatInterpolate((int)y + kInterpolateOffsets[i], 0, 223, ppu->mode7PerspectiveLow, ppu->mode7PerspectiveHigh);
|
||||
}
|
||||
|
||||
size_t pitch = ppu->renderPitch;
|
||||
uint8 *render_buffer_ptr = &ppu->renderBuffer[(y - 1) * 4 * pitch];
|
||||
uint8 *dst_start = render_buffer_ptr + (ppu->extraLeftRight - ppu->extraLeftCur) * 16;
|
||||
size_t draw_width = 256 + ppu->extraLeftCur + ppu->extraRightCur;
|
||||
uint8 *dst_curline = dst_start;
|
||||
uint32 m1 = ppu->m7matrix[1] << 12; // xpos increment per vert movement
|
||||
uint32 m2 = ppu->m7matrix[2] << 12; // ypos increment per horiz movement
|
||||
for (int j = 0; j < 4; j++) {
|
||||
m0 = m3 = m0v[j];
|
||||
uint32 m1 = ppu->m7matrix[1] << 12; // xpos increment per vert movement
|
||||
uint32 m2 = ppu->m7matrix[2] << 12; // ypos increment per horiz movement
|
||||
uint32 m0 = m0v[j], m3 = m0;
|
||||
uint32 xpos = m0 * clippedH + m1 * (clippedV + y) + (xCenter << 20), xcur;
|
||||
uint32 ypos = m2 * clippedH + m3 * (clippedV + y) + (yCenter << 20), ycur;
|
||||
|
||||
@@ -738,11 +735,17 @@ static void PpuDrawMode7Upsampled(Ppu *ppu, uint y) {
|
||||
ypos -= (m2 + m3) >> 1;
|
||||
xcur = (xpos << 2) + j * m1;
|
||||
ycur = (ypos << 2) + j * m3;
|
||||
dst_end = dst + 4096;
|
||||
|
||||
xcur -= ppu->extraLeftCur * 4 * m0;
|
||||
ycur -= ppu->extraLeftCur * 4 * m2;
|
||||
|
||||
uint8 *dst = dst_curline;
|
||||
uint8 *dst_end = dst_curline + draw_width * 16;
|
||||
|
||||
#define DRAW_PIXEL(mode) \
|
||||
tile = ppu->vram[(ycur >> 25 & 0x7f) * 128 + (xcur >> 25 & 0x7f)] & 0xff; \
|
||||
pixel = ppu->vram[tile * 64 + (ycur >> 22 & 7) * 8 + (xcur >> 22 & 7)] >> 8; \
|
||||
pixel = (xcur & 0x80000000) ? 0 : pixel; \
|
||||
*(uint32*)dst = (mode ? (ppu->colorMapRgb[pixel] & 0xfefefe) >> 1 : ppu->colorMapRgb[pixel]); \
|
||||
xcur += m0, ycur += m2, dst += 4;
|
||||
|
||||
@@ -760,18 +763,17 @@ static void PpuDrawMode7Upsampled(Ppu *ppu, uint y) {
|
||||
DRAW_PIXEL(1);
|
||||
DRAW_PIXEL(1);
|
||||
} while (dst != dst_end);
|
||||
|
||||
}
|
||||
#undef DRAW_PIXEL
|
||||
dst += (ppu->renderPitch - 4096);
|
||||
|
||||
dst_curline += pitch;
|
||||
}
|
||||
|
||||
if (ppu->lineHasSprites) {
|
||||
PpuZbufType *pixels = ppu->objBuffer.data;
|
||||
size_t pitch = ppu->renderPitch;
|
||||
uint8 *dst = dst_start + ppu->extraLeftRight * 16;
|
||||
for (size_t i = 0; i < 256; i++, dst += 16) {
|
||||
uint32 pixel = pixels[i + kPpuExtraLeftRight] & 0xff;
|
||||
uint8 *dst = dst_start;
|
||||
PpuZbufType *pixels = ppu->objBuffer.data + (kPpuExtraLeftRight - ppu->extraLeftCur);
|
||||
for (size_t i = 0; i < draw_width; i++, dst += 16) {
|
||||
uint32 pixel = pixels[i] & 0xff;
|
||||
if (pixel) {
|
||||
uint32 color = ppu->colorMapRgb[pixel];
|
||||
((uint32 *)dst)[3] = ((uint32 *)dst)[2] = ((uint32 *)dst)[1] = ((uint32 *)dst)[0] = color;
|
||||
@@ -784,15 +786,13 @@ static void PpuDrawMode7Upsampled(Ppu *ppu, uint y) {
|
||||
|
||||
if (ppu->extraLeftRight - ppu->extraLeftCur != 0) {
|
||||
size_t n = 4 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraLeftCur);
|
||||
size_t pitch = ppu->renderPitch;
|
||||
for(int i = 0; i < 4; i++)
|
||||
memset(dst_start + pitch * i, 0, n);
|
||||
memset(render_buffer_ptr + pitch * i, 0, n);
|
||||
}
|
||||
if (ppu->extraLeftRight - ppu->extraRightCur != 0) {
|
||||
size_t n = 4 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraRightCur);
|
||||
size_t pitch = ppu->renderPitch;
|
||||
for (int i = 0; i < 4; i++)
|
||||
memset(dst_start + pitch * i + (256 + ppu->extraLeftRight * 2 - (ppu->extraLeftRight - ppu->extraRightCur)) * 4 * sizeof(uint32), 0, n);
|
||||
memset(render_buffer_ptr + pitch * i + (256 + ppu->extraLeftRight * 2 - (ppu->extraLeftRight - ppu->extraRightCur)) * 4 * sizeof(uint32), 0, n);
|
||||
}
|
||||
#undef DRAW_PIXEL
|
||||
}
|
||||
@@ -833,7 +833,7 @@ static void PpuDrawBackgrounds(Ppu *ppu, int y, bool sub) {
|
||||
PpuDrawBackground_2bpp(ppu, y, sub, 2, 0xf200, 0x1200);
|
||||
} else {
|
||||
// mode 7
|
||||
PpuDrawBackground_mode7(ppu, y, sub, 0xc0);
|
||||
PpuDrawBackground_mode7(ppu, y, sub, 0xc000);
|
||||
if (ppu->lineHasSprites)
|
||||
PpuDrawSprites(ppu, y, sub, false);
|
||||
}
|
||||
@@ -841,10 +841,9 @@ static void PpuDrawBackgrounds(Ppu *ppu, int y, bool sub) {
|
||||
|
||||
static NOINLINE void PpuDrawWholeLine(Ppu *ppu, uint y) {
|
||||
if (ppu->forcedBlank) {
|
||||
uint8 *dst = &ppu->renderBuffer[(y - 1) * 2 * ppu->renderPitch];
|
||||
size_t n = sizeof(uint32) * 2 * (256 + ppu->extraLeftRight * 2);
|
||||
uint8 *dst = &ppu->renderBuffer[(y - 1) * ppu->renderPitch];
|
||||
size_t n = sizeof(uint32) * (256 + ppu->extraLeftRight * 2);
|
||||
memset(dst, 0, n);
|
||||
memset(dst + ppu->renderPitch, 0, n);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -882,9 +881,9 @@ static NOINLINE void PpuDrawWholeLine(Ppu *ppu, uint y) {
|
||||
uint32 cw_clip_math = ((cwin.bits & kCwBitsMod[ppu->clipMode]) ^ kCwBitsMod[ppu->clipMode + 4]) |
|
||||
((cwin.bits & kCwBitsMod[ppu->preventMathMode]) ^ kCwBitsMod[ppu->preventMathMode + 4]) << 8;
|
||||
|
||||
uint32 *dst = (uint32*)&ppu->renderBuffer[(y - 1) * 2 * ppu->renderPitch], *dst_org = dst;
|
||||
uint32 *dst = (uint32*)&ppu->renderBuffer[(y - 1) * ppu->renderPitch], *dst_org = dst;
|
||||
|
||||
dst += 2 * (ppu->extraLeftRight - ppu->extraLeftCur);
|
||||
dst += (ppu->extraLeftRight - ppu->extraLeftCur);
|
||||
|
||||
uint32 windex = 0;
|
||||
do {
|
||||
@@ -898,10 +897,10 @@ static NOINLINE void PpuDrawWholeLine(Ppu *ppu, uint y) {
|
||||
uint32 i = left;
|
||||
do {
|
||||
uint32 color = ppu->cgram[ppu->bgBuffers[0].data[i] & 0xff];
|
||||
dst[1] = dst[0] = ppu->brightnessMult[color & clip_color_mask] << 16 |
|
||||
ppu->brightnessMult[(color >> 5) & clip_color_mask] << 8 |
|
||||
ppu->brightnessMult[(color >> 10) & clip_color_mask];
|
||||
} while (dst += 2, ++i < right);
|
||||
dst[0] = ppu->brightnessMult[color & clip_color_mask] << 16 |
|
||||
ppu->brightnessMult[(color >> 5) & clip_color_mask] << 8 |
|
||||
ppu->brightnessMult[(color >> 10) & clip_color_mask];
|
||||
} while (dst++, ++i < right);
|
||||
} else {
|
||||
uint8 *half_color_map = ppu->halfColor ? ppu->brightnessMultHalf : ppu->brightnessMult;
|
||||
// Store this in locals
|
||||
@@ -935,20 +934,17 @@ static NOINLINE void PpuDrawWholeLine(Ppu *ppu, uint y) {
|
||||
b += b2;
|
||||
}
|
||||
}
|
||||
dst[0] = dst[1] = color_map[b] | color_map[g] << 8 | color_map[r] << 16;
|
||||
} while (dst += 2, ++i < right);
|
||||
dst[0] = color_map[b] | color_map[g] << 8 | color_map[r] << 16;
|
||||
} while (dst++, ++i < right);
|
||||
}
|
||||
} while (cw_clip_math >>= 1, ++windex < cwin.nr);
|
||||
|
||||
// Clear out stuff on the sides.
|
||||
if (ppu->extraLeftRight - ppu->extraLeftCur != 0)
|
||||
memset(dst_org, 0, 2 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraLeftCur));
|
||||
memset(dst_org, 0, sizeof(uint32) * (ppu->extraLeftRight - ppu->extraLeftCur));
|
||||
if (ppu->extraLeftRight - ppu->extraRightCur != 0)
|
||||
memset(dst_org + 2 * (256 + ppu->extraLeftRight * 2 - (ppu->extraLeftRight - ppu->extraRightCur)), 0,
|
||||
2 * sizeof(uint32) * (ppu->extraLeftRight - ppu->extraRightCur));
|
||||
|
||||
// Duplicate one line
|
||||
memcpy((uint8*)dst_org + ppu->renderPitch, dst_org, (ppu->extraLeftRight * 2 + 256) * 2 * sizeof(uint32));
|
||||
memset(dst_org + (256 + ppu->extraLeftRight * 2 - (ppu->extraLeftRight - ppu->extraRightCur)), 0,
|
||||
sizeof(uint32) * (ppu->extraLeftRight - ppu->extraRightCur));
|
||||
}
|
||||
|
||||
static void ppu_handlePixel(Ppu* ppu, int x, int y) {
|
||||
@@ -1004,15 +1000,11 @@ static void ppu_handlePixel(Ppu* ppu, int x, int y) {
|
||||
}
|
||||
}
|
||||
int row = y - 1;
|
||||
uint8 *pixelBuffer = (uint8*) &ppu->renderBuffer[row * 2 * ppu->renderPitch + (x + ppu->extraLeftRight) * 8];
|
||||
pixelBuffer[0] = ((b2 << 3) | (b2 >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[1] = ((g2 << 3) | (g2 >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[2] = ((r2 << 3) | (r2 >> 2)) * ppu->brightness / 15;
|
||||
uint8 *pixelBuffer = (uint8*) &ppu->renderBuffer[row * ppu->renderPitch + (x + ppu->extraLeftRight) * 4];
|
||||
pixelBuffer[0] = ((b << 3) | (b >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[1] = ((g << 3) | (g >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[2] = ((r << 3) | (r >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[3] = 0;
|
||||
pixelBuffer[4] = ((b << 3) | (b >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[5] = ((g << 3) | (g >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[6] = ((r << 3) | (r >> 2)) * ppu->brightness / 15;
|
||||
pixelBuffer[7] = 0;
|
||||
}
|
||||
|
||||
static const int bitDepthsPerMode[10][4] = {
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "saveload.h"
|
||||
#include "snes/saveload.h"
|
||||
typedef struct Ppu Ppu;
|
||||
|
||||
#include "../types.h"
|
||||
#include "src/types.h"
|
||||
|
||||
typedef struct BgLayer {
|
||||
uint16_t hScroll;
|
||||
@@ -135,7 +135,10 @@ void ppu_runLine(Ppu* ppu, int line);
|
||||
uint8_t ppu_read(Ppu* ppu, uint8_t adr);
|
||||
void ppu_write(Ppu* ppu, uint8_t adr, uint8_t val);
|
||||
void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx);
|
||||
bool PpuBeginDrawing(Ppu *ppu, uint8_t *buffer, size_t pitch, uint32_t render_flags);
|
||||
void PpuBeginDrawing(Ppu *ppu, uint8_t *buffer, size_t pitch, uint32_t render_flags);
|
||||
|
||||
// Returns the current render scale, 1x = 256px, 2x=512px, 4x=1024px
|
||||
int PpuGetCurrentRenderScale(Ppu *ppu, uint32_t render_flags);
|
||||
|
||||
void PpuSetMode7PerspectiveCorrection(Ppu *ppu, int low, int high);
|
||||
void PpuSetExtraSideSpace(Ppu *ppu, int left, int right, int bottom);
|
||||
|
||||
20
snes/snes.c
20
snes/snes.c
@@ -18,9 +18,6 @@
|
||||
#include "tracing.h"
|
||||
#include "snes_regs.h"
|
||||
|
||||
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
|
||||
|
||||
static void snes_runCpu(Snes* snes);
|
||||
static void snes_catchupApu(Snes* snes);
|
||||
static uint8_t snes_readReg(Snes* snes, uint16_t adr);
|
||||
static void snes_writeReg(Snes* snes, uint16_t adr, uint8_t val);
|
||||
@@ -104,8 +101,6 @@ void snes_reset(Snes* snes, bool hard) {
|
||||
snes->openBus = 0;
|
||||
}
|
||||
|
||||
static uint8_t g_last_module;
|
||||
|
||||
void snes_printCpuLine(Snes *snes) {
|
||||
if (snes->debug_cycles) {
|
||||
static FILE *fout;
|
||||
@@ -121,21 +116,6 @@ void snes_printCpuLine(Snes *snes) {
|
||||
}
|
||||
}
|
||||
|
||||
static void snes_runCpu(Snes* snes) {
|
||||
if(snes->cpuCyclesLeft == 0) {
|
||||
snes->cpuMemOps = 0;
|
||||
uint32_t pc = snes->cpu->pc | snes->cpu->k << 16;
|
||||
if (snes->debug_cycles) {
|
||||
char line[80];
|
||||
getProcessorStateCpu(snes, line);
|
||||
puts(line);
|
||||
}
|
||||
int cycles = cpu_runOpcode(snes->cpu);
|
||||
snes->cpuCyclesLeft += (cycles - snes->cpuMemOps) * 6;
|
||||
}
|
||||
snes->cpuCyclesLeft -= 2;
|
||||
}
|
||||
|
||||
static void snes_catchupApu(Snes* snes) {
|
||||
int catchupCycles = (int) snes->apuCatchupCycles;
|
||||
for(int i = 0; i < catchupCycles; i++) {
|
||||
|
||||
@@ -346,7 +346,7 @@ int Ancilla_AllocHigh() {
|
||||
|
||||
static void Ancilla_SetOam(OamEnt *oam, uint16 x, uint16 y, uint8 charnum, uint8 flags, uint8 big) {
|
||||
uint8 yval = 0xf0;
|
||||
int xt = enhanced_features0 ? 0x40 : 0;
|
||||
int xt = enhanced_features0 & kFeatures0_ExtendScreen64 ? 0x40 : 0;
|
||||
if ((uint16)(x + xt) < 256 + xt * 2 && y < 256) {
|
||||
big |= (x >> 8) & 1;
|
||||
oam->x = x;
|
||||
@@ -362,7 +362,8 @@ static void Ancilla_SetOam(OamEnt *oam, uint16 x, uint16 y, uint8 charnum, uint8
|
||||
static void Ancilla_SetOam_Safe(OamEnt *oam, uint16 x, uint16 y, uint8 charnum, uint8 flags, uint8 big) {
|
||||
uint8 yval = 0xf0;
|
||||
oam->x = x;
|
||||
if ((uint16)(x + 0x80) < 0x180) {
|
||||
int xt = enhanced_features0 & kFeatures0_ExtendScreen64 ? 0x48 : 0;
|
||||
if ((uint16)(x + 0x80) < (0x180 + xt)) {
|
||||
big |= (x >> 8) & 1;
|
||||
if ((uint16)(y + 0x10) < 0x100)
|
||||
yval = y;
|
||||
@@ -3742,7 +3743,7 @@ void Ancilla29_MilestoneItemReceipt(int k) { // 88ca8c
|
||||
}
|
||||
if (!ancilla_arr3[k] && ancilla_item_to_link[k] == 0x20) {
|
||||
ancilla_arr3[k] = 1;
|
||||
palette_sp6 = 4;
|
||||
palette_sp6r_indoors = 4;
|
||||
overworld_palette_aux_or_main = 0x200;
|
||||
Palette_Load_SpriteEnvironment_Dungeon();
|
||||
flag_update_cgram_in_nmi++;
|
||||
@@ -3902,7 +3903,7 @@ lbl_else:
|
||||
ancilla_x_lo[k] = bak0;
|
||||
ancilla_x_hi[k] = bak1;
|
||||
if (breaktowerseal_var4 >= 240) {
|
||||
palette_sp6 = 0;
|
||||
palette_sp6r_indoors = 0;
|
||||
overworld_palette_aux_or_main = 0x200;
|
||||
Palette_Load_SpriteEnvironment_Dungeon();
|
||||
flag_update_cgram_in_nmi++;
|
||||
@@ -4753,12 +4754,10 @@ endif_5:
|
||||
}
|
||||
}
|
||||
|
||||
void AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
|
||||
int AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
|
||||
int k = AncillaAdd_AddAncilla_Bank08(type, y);
|
||||
if (k < 0) {
|
||||
Refund_Magic(4);
|
||||
return;
|
||||
}
|
||||
if (k < 0)
|
||||
return k;
|
||||
for (int j = 4; j >= 0; j--) {
|
||||
if (j == k || ancilla_type[j] != 0x2c)
|
||||
continue;
|
||||
@@ -4771,7 +4770,7 @@ void AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
|
||||
bitmask_of_dragstate = 0;
|
||||
link_speed_setting = 0;
|
||||
}
|
||||
return;
|
||||
return k;
|
||||
}
|
||||
|
||||
Ancilla_Sfx3_Near(0x2a);
|
||||
@@ -4804,6 +4803,7 @@ void AncillaAdd_SomariaBlock(uint8 type, uint8 y) { // 88e078
|
||||
Ancilla_SetY(k, link_y_coord + kCaneOfSomaria_Y[j]);
|
||||
SomariaBlock_CheckForTransitTile(k);
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
void SomariaBlock_CheckForTransitTile(int k) { // 88e191
|
||||
@@ -5165,7 +5165,7 @@ void Ancilla2E_SomariaBlockFission(int k) { // 88eb3e
|
||||
}
|
||||
Point16U pt;
|
||||
Ancilla_PrepAdjustedOamCoord(k, &pt);
|
||||
OamEnt *oam = GetOamCurPtr(), *oam_org = oam;
|
||||
OamEnt *oam = GetOamCurPtr();
|
||||
|
||||
int8 z = ancilla_z[k] + (ancilla_K[k] == 3 && BYTE(link_z_coord) != 0xff ? BYTE(link_z_coord) : 0);
|
||||
int j = ancilla_item_to_link[k] * 8;
|
||||
@@ -5377,8 +5377,11 @@ void Ancilla3A_BigBombExplosion(int k) { // 88f18d
|
||||
}
|
||||
}
|
||||
if (ancilla_item_to_link[k] == 3 && ancilla_arr3[k] == 1) {
|
||||
// Changed so this is reset elsewhere. Some code depends on the value 13.
|
||||
uint8 old = (enhanced_features0 & kFeatures0_MiscBugFixes) ? follower_indicator : 0;
|
||||
follower_indicator = 13;
|
||||
Bomb_CheckForDestructibles(Ancilla_GetX(k), Ancilla_GetY(k), 0); // r14?
|
||||
follower_indicator = 0;
|
||||
follower_indicator = old;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5484,6 +5487,9 @@ void RevivalFairy_MonitorHP() { // 88f430
|
||||
} else if (link_is_bunny) {
|
||||
link_player_handler_state = kPlayerState_PermaBunny;
|
||||
link_is_bunny_mirror = 1;
|
||||
//bugfix: dying as permabunny doesn't restore link palette during death animation
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes)
|
||||
LoadGearPalettes_bunny();
|
||||
} else {
|
||||
link_player_handler_state = kPlayerState_Ground;
|
||||
}
|
||||
@@ -5920,7 +5926,6 @@ void AncillaAdd_TossedPondItem(uint8 a, uint8 xin, uint8 yin) { // 898a32
|
||||
ancilla_z[k] = 0;
|
||||
ancilla_timer[k] = 16;
|
||||
ancilla_item_to_link[k] = link_receiveitem_index;
|
||||
int j = link_receiveitem_index;
|
||||
Ancilla_SetXY(k,
|
||||
link_x_coord + kWishPondItem_X[link_receiveitem_index],
|
||||
link_y_coord + kWishPondItem_Y[link_receiveitem_index]);
|
||||
@@ -6087,7 +6092,7 @@ void AncillaAdd_SomariaPlatformPoof(int k) { // 898dd2
|
||||
Player_TileDetectNearby();
|
||||
}
|
||||
|
||||
void AncillaAdd_SuperBombExplosion(uint8 a, uint8 y) { // 898df9
|
||||
int AncillaAdd_SuperBombExplosion(uint8 a, uint8 y) { // 898df9
|
||||
int k = Ancilla_AddAncilla(a, y);
|
||||
if (k >= 0) {
|
||||
ancilla_R[k] = 0;
|
||||
@@ -6101,6 +6106,7 @@ void AncillaAdd_SuperBombExplosion(uint8 a, uint8 y) { // 898df9
|
||||
int x = tagalong_x_lo[j] | tagalong_x_hi[j] << 8;
|
||||
Ancilla_SetXY(k, x + 8, y + 16);
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
void ConfigureRevivalAncillae() { // 898e4e
|
||||
@@ -6364,8 +6370,6 @@ void AncillaAdd_SwordSwingSparkle(uint8 a, uint8 y) { // 8993c2
|
||||
}
|
||||
|
||||
void AncillaAdd_DashTremor(uint8 a, uint8 y) { // 8993f3
|
||||
static const uint8 kAddDashingDust_X[4] = {4, 4, 6, 0};
|
||||
static const uint8 kAddDashingDust_Y[4] = {20, 4, 16, 16};
|
||||
static const uint8 kAddDashTremor_Dir[4] = {2, 2, 0, 0};
|
||||
static const uint8 kAddDashTremor_Tab[2] = {0x80, 0x78};
|
||||
if (AncillaAdd_CheckForPresence(a))
|
||||
@@ -6713,7 +6717,7 @@ void AncillaAdd_GTCutscene() { // 899b83
|
||||
for (int i = 0x17; i >= 0; i--)
|
||||
breaktowerseal_sparkle_var1[i] = 0xff;
|
||||
DecodeAnimatedSpriteTile_variable(0x28);
|
||||
palette_sp6 = 4;
|
||||
palette_sp6r_indoors = 4;
|
||||
overworld_palette_aux_or_main = 0x200;
|
||||
Palette_Load_SpriteEnvironment_Dungeon();
|
||||
flag_update_cgram_in_nmi++;
|
||||
@@ -173,7 +173,7 @@ void Ancilla31_ByrnaSpark(int k);
|
||||
void Ancilla_SwordBeam(int k);
|
||||
void Ancilla0D_SpinAttackFullChargeSpark(int k);
|
||||
void Ancilla27_Duck(int k);
|
||||
void AncillaAdd_SomariaBlock(uint8 type, uint8 y);
|
||||
int AncillaAdd_SomariaBlock(uint8 type, uint8 y);
|
||||
void SomariaBlock_CheckForTransitTile(int k);
|
||||
int Ancilla_CheckBasicSpriteCollision(int k);
|
||||
bool Ancilla_CheckBasicSpriteCollision_Single(int k, int j);
|
||||
@@ -219,7 +219,7 @@ void AncillaAdd_ChargedSpinAttackSparkle();
|
||||
void AncillaAdd_ExplodingWeatherVane(uint8 a, uint8 y);
|
||||
void AncillaAdd_CutsceneDuck(uint8 a, uint8 y);
|
||||
void AncillaAdd_SomariaPlatformPoof(int k);
|
||||
void AncillaAdd_SuperBombExplosion(uint8 a, uint8 y);
|
||||
int AncillaAdd_SuperBombExplosion(uint8 a, uint8 y);
|
||||
void ConfigureRevivalAncillae();
|
||||
void AncillaAdd_LampFlame(uint8 a, uint8 y);
|
||||
void AncillaAdd_MSCutscene(uint8 a, uint8 y);
|
||||
@@ -1,339 +1,332 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
enum {
|
||||
kNumberOfAssets = 165
|
||||
};
|
||||
extern const uint8 *g_asset_ptrs[kNumberOfAssets];
|
||||
extern uint32 g_asset_sizes[kNumberOfAssets];
|
||||
#define kSoundBank_intro ((uint8*)g_asset_ptrs[0])
|
||||
#define kSoundBank_intro_SIZE (g_asset_sizes[0])
|
||||
#define kSoundBank_indoor ((uint8*)g_asset_ptrs[1])
|
||||
#define kSoundBank_indoor_SIZE (g_asset_sizes[1])
|
||||
#define kSoundBank_ending ((uint8*)g_asset_ptrs[2])
|
||||
#define kSoundBank_ending_SIZE (g_asset_sizes[2])
|
||||
#define kDungeonRoom ((uint8*)g_asset_ptrs[3])
|
||||
#define kDungeonRoom_SIZE (g_asset_sizes[3])
|
||||
#define kDungeonRoomOffs ((uint16*)g_asset_ptrs[4])
|
||||
#define kDungeonRoomOffs_SIZE (g_asset_sizes[4])
|
||||
#define kDungeonRoomDoorOffs ((uint16*)g_asset_ptrs[5])
|
||||
#define kDungeonRoomDoorOffs_SIZE (g_asset_sizes[5])
|
||||
#define kDungeonRoomHeaders ((uint8*)g_asset_ptrs[6])
|
||||
#define kDungeonRoomHeaders_SIZE (g_asset_sizes[6])
|
||||
#define kDungeonRoomHeadersOffs ((uint16*)g_asset_ptrs[7])
|
||||
#define kDungeonRoomHeadersOffs_SIZE (g_asset_sizes[7])
|
||||
#define kDungeonRoomChests ((uint8*)g_asset_ptrs[8])
|
||||
#define kDungeonRoomChests_SIZE (g_asset_sizes[8])
|
||||
#define kDungeonRoomTeleMsg ((uint16*)g_asset_ptrs[9])
|
||||
#define kDungeonRoomTeleMsg_SIZE (g_asset_sizes[9])
|
||||
#define kDungeonPitsHurtPlayer ((uint16*)g_asset_ptrs[10])
|
||||
#define kDungeonPitsHurtPlayer_SIZE (g_asset_sizes[10])
|
||||
#define kEntranceData_rooms ((uint16*)g_asset_ptrs[11])
|
||||
#define kEntranceData_rooms_SIZE (g_asset_sizes[11])
|
||||
#define kEntranceData_relativeCoords ((uint8*)g_asset_ptrs[12])
|
||||
#define kEntranceData_relativeCoords_SIZE (g_asset_sizes[12])
|
||||
#define kEntranceData_scrollX ((uint16*)g_asset_ptrs[13])
|
||||
#define kEntranceData_scrollX_SIZE (g_asset_sizes[13])
|
||||
#define kEntranceData_scrollY ((uint16*)g_asset_ptrs[14])
|
||||
#define kEntranceData_scrollY_SIZE (g_asset_sizes[14])
|
||||
#define kEntranceData_playerX ((uint16*)g_asset_ptrs[15])
|
||||
#define kEntranceData_playerX_SIZE (g_asset_sizes[15])
|
||||
#define kEntranceData_playerY ((uint16*)g_asset_ptrs[16])
|
||||
#define kEntranceData_playerY_SIZE (g_asset_sizes[16])
|
||||
#define kEntranceData_cameraX ((uint16*)g_asset_ptrs[17])
|
||||
#define kEntranceData_cameraX_SIZE (g_asset_sizes[17])
|
||||
#define kEntranceData_cameraY ((uint16*)g_asset_ptrs[18])
|
||||
#define kEntranceData_cameraY_SIZE (g_asset_sizes[18])
|
||||
#define kEntranceData_blockset ((uint8*)g_asset_ptrs[19])
|
||||
#define kEntranceData_blockset_SIZE (g_asset_sizes[19])
|
||||
#define kEntranceData_floor ((int8*)g_asset_ptrs[20])
|
||||
#define kEntranceData_floor_SIZE (g_asset_sizes[20])
|
||||
#define kEntranceData_palace ((int8*)g_asset_ptrs[21])
|
||||
#define kEntranceData_palace_SIZE (g_asset_sizes[21])
|
||||
#define kEntranceData_doorwayOrientation ((uint8*)g_asset_ptrs[22])
|
||||
#define kEntranceData_doorwayOrientation_SIZE (g_asset_sizes[22])
|
||||
#define kEntranceData_startingBg ((uint8*)g_asset_ptrs[23])
|
||||
#define kEntranceData_startingBg_SIZE (g_asset_sizes[23])
|
||||
#define kEntranceData_quadrant1 ((uint8*)g_asset_ptrs[24])
|
||||
#define kEntranceData_quadrant1_SIZE (g_asset_sizes[24])
|
||||
#define kEntranceData_quadrant2 ((uint8*)g_asset_ptrs[25])
|
||||
#define kEntranceData_quadrant2_SIZE (g_asset_sizes[25])
|
||||
#define kEntranceData_doorSettings ((uint16*)g_asset_ptrs[26])
|
||||
#define kEntranceData_doorSettings_SIZE (g_asset_sizes[26])
|
||||
#define kEntranceData_musicTrack ((uint8*)g_asset_ptrs[27])
|
||||
#define kEntranceData_musicTrack_SIZE (g_asset_sizes[27])
|
||||
#define kStartingPoint_rooms ((uint16*)g_asset_ptrs[28])
|
||||
#define kStartingPoint_rooms_SIZE (g_asset_sizes[28])
|
||||
#define kStartingPoint_relativeCoords ((uint8*)g_asset_ptrs[29])
|
||||
#define kStartingPoint_relativeCoords_SIZE (g_asset_sizes[29])
|
||||
#define kStartingPoint_scrollX ((uint16*)g_asset_ptrs[30])
|
||||
#define kStartingPoint_scrollX_SIZE (g_asset_sizes[30])
|
||||
#define kStartingPoint_scrollY ((uint16*)g_asset_ptrs[31])
|
||||
#define kStartingPoint_scrollY_SIZE (g_asset_sizes[31])
|
||||
#define kStartingPoint_playerX ((uint16*)g_asset_ptrs[32])
|
||||
#define kStartingPoint_playerX_SIZE (g_asset_sizes[32])
|
||||
#define kStartingPoint_playerY ((uint16*)g_asset_ptrs[33])
|
||||
#define kStartingPoint_playerY_SIZE (g_asset_sizes[33])
|
||||
#define kStartingPoint_cameraX ((uint16*)g_asset_ptrs[34])
|
||||
#define kStartingPoint_cameraX_SIZE (g_asset_sizes[34])
|
||||
#define kStartingPoint_cameraY ((uint16*)g_asset_ptrs[35])
|
||||
#define kStartingPoint_cameraY_SIZE (g_asset_sizes[35])
|
||||
#define kStartingPoint_blockset ((uint8*)g_asset_ptrs[36])
|
||||
#define kStartingPoint_blockset_SIZE (g_asset_sizes[36])
|
||||
#define kStartingPoint_floor ((int8*)g_asset_ptrs[37])
|
||||
#define kStartingPoint_floor_SIZE (g_asset_sizes[37])
|
||||
#define kStartingPoint_palace ((int8*)g_asset_ptrs[38])
|
||||
#define kStartingPoint_palace_SIZE (g_asset_sizes[38])
|
||||
#define kStartingPoint_doorwayOrientation ((uint8*)g_asset_ptrs[39])
|
||||
#define kStartingPoint_doorwayOrientation_SIZE (g_asset_sizes[39])
|
||||
#define kStartingPoint_startingBg ((uint8*)g_asset_ptrs[40])
|
||||
#define kStartingPoint_startingBg_SIZE (g_asset_sizes[40])
|
||||
#define kStartingPoint_quadrant1 ((uint8*)g_asset_ptrs[41])
|
||||
#define kStartingPoint_quadrant1_SIZE (g_asset_sizes[41])
|
||||
#define kStartingPoint_quadrant2 ((uint8*)g_asset_ptrs[42])
|
||||
#define kStartingPoint_quadrant2_SIZE (g_asset_sizes[42])
|
||||
#define kStartingPoint_doorSettings ((uint16*)g_asset_ptrs[43])
|
||||
#define kStartingPoint_doorSettings_SIZE (g_asset_sizes[43])
|
||||
#define kStartingPoint_entrance ((uint8*)g_asset_ptrs[44])
|
||||
#define kStartingPoint_entrance_SIZE (g_asset_sizes[44])
|
||||
#define kStartingPoint_musicTrack ((uint8*)g_asset_ptrs[45])
|
||||
#define kStartingPoint_musicTrack_SIZE (g_asset_sizes[45])
|
||||
#define kDungeonRoomDefault ((uint8*)g_asset_ptrs[46])
|
||||
#define kDungeonRoomDefault_SIZE (g_asset_sizes[46])
|
||||
#define kDungeonRoomDefaultOffs ((uint16*)g_asset_ptrs[47])
|
||||
#define kDungeonRoomDefaultOffs_SIZE (g_asset_sizes[47])
|
||||
#define kDungeonRoomOverlay ((uint8*)g_asset_ptrs[48])
|
||||
#define kDungeonRoomOverlay_SIZE (g_asset_sizes[48])
|
||||
#define kDungeonRoomOverlayOffs ((uint16*)g_asset_ptrs[49])
|
||||
#define kDungeonRoomOverlayOffs_SIZE (g_asset_sizes[49])
|
||||
#define kDungeonSecrets ((uint8*)g_asset_ptrs[50])
|
||||
#define kDungeonSecrets_SIZE (g_asset_sizes[50])
|
||||
#define kDungAttrsForTile_Offs ((uint16*)g_asset_ptrs[51])
|
||||
#define kDungAttrsForTile_Offs_SIZE (g_asset_sizes[51])
|
||||
#define kDungAttrsForTile ((uint8*)g_asset_ptrs[52])
|
||||
#define kDungAttrsForTile_SIZE (g_asset_sizes[52])
|
||||
#define kMovableBlockDataInit ((uint16*)g_asset_ptrs[53])
|
||||
#define kMovableBlockDataInit_SIZE (g_asset_sizes[53])
|
||||
#define kTorchDataInit ((uint16*)g_asset_ptrs[54])
|
||||
#define kTorchDataInit_SIZE (g_asset_sizes[54])
|
||||
#define kTorchDataJunk ((uint16*)g_asset_ptrs[55])
|
||||
#define kTorchDataJunk_SIZE (g_asset_sizes[55])
|
||||
#define kEnemyDamageData ((uint8*)g_asset_ptrs[56])
|
||||
#define kEnemyDamageData_SIZE (g_asset_sizes[56])
|
||||
#define kLinkGraphics ((uint8*)g_asset_ptrs[57])
|
||||
#define kLinkGraphics_SIZE (g_asset_sizes[57])
|
||||
#define kDungeonSprites ((uint8*)g_asset_ptrs[58])
|
||||
#define kDungeonSprites_SIZE (g_asset_sizes[58])
|
||||
#define kDungeonSpriteOffs ((uint16*)g_asset_ptrs[59])
|
||||
#define kDungeonSpriteOffs_SIZE (g_asset_sizes[59])
|
||||
#define kMap32ToMap16_0 ((uint8*)g_asset_ptrs[60])
|
||||
#define kMap32ToMap16_0_SIZE (g_asset_sizes[60])
|
||||
#define kMap32ToMap16_1 ((uint8*)g_asset_ptrs[61])
|
||||
#define kMap32ToMap16_1_SIZE (g_asset_sizes[61])
|
||||
#define kMap32ToMap16_2 ((uint8*)g_asset_ptrs[62])
|
||||
#define kMap32ToMap16_2_SIZE (g_asset_sizes[62])
|
||||
#define kMap32ToMap16_3 ((uint8*)g_asset_ptrs[63])
|
||||
#define kMap32ToMap16_3_SIZE (g_asset_sizes[63])
|
||||
#define kDialogueOffs ((uint16*)g_asset_ptrs[64])
|
||||
#define kDialogueOffs_SIZE (g_asset_sizes[64])
|
||||
#define kDialogueText ((uint8*)g_asset_ptrs[65])
|
||||
#define kDialogueText_SIZE (g_asset_sizes[65])
|
||||
#define kSprGfx ((uint8*)g_asset_ptrs[66])
|
||||
#define kSprGfx_SIZE (g_asset_sizes[66])
|
||||
#define kBgGfx ((uint8*)g_asset_ptrs[67])
|
||||
#define kBgGfx_SIZE (g_asset_sizes[67])
|
||||
#define kOverworldMapGfx ((uint8*)g_asset_ptrs[68])
|
||||
#define kOverworldMapGfx_SIZE (g_asset_sizes[68])
|
||||
#define kLightOverworldTilemap ((uint8*)g_asset_ptrs[69])
|
||||
#define kLightOverworldTilemap_SIZE (g_asset_sizes[69])
|
||||
#define kDarkOverworldTilemap ((uint8*)g_asset_ptrs[70])
|
||||
#define kDarkOverworldTilemap_SIZE (g_asset_sizes[70])
|
||||
#define kPredefinedTileData ((uint16*)g_asset_ptrs[71])
|
||||
#define kPredefinedTileData_SIZE (g_asset_sizes[71])
|
||||
#define kFontData ((uint16*)g_asset_ptrs[72])
|
||||
#define kFontData_SIZE (g_asset_sizes[72])
|
||||
#define kMap16ToMap8 ((uint16*)g_asset_ptrs[73])
|
||||
#define kMap16ToMap8_SIZE (g_asset_sizes[73])
|
||||
#define kGeneratedWishPondItem ((uint8*)g_asset_ptrs[74])
|
||||
#define kGeneratedWishPondItem_SIZE (g_asset_sizes[74])
|
||||
#define kGeneratedBombosArr ((uint8*)g_asset_ptrs[75])
|
||||
#define kGeneratedBombosArr_SIZE (g_asset_sizes[75])
|
||||
#define kGeneratedEndSequence15 ((uint8*)g_asset_ptrs[76])
|
||||
#define kGeneratedEndSequence15_SIZE (g_asset_sizes[76])
|
||||
#define kEnding_Credits_Text ((uint8*)g_asset_ptrs[77])
|
||||
#define kEnding_Credits_Text_SIZE (g_asset_sizes[77])
|
||||
#define kEnding_Credits_Offs ((uint16*)g_asset_ptrs[78])
|
||||
#define kEnding_Credits_Offs_SIZE (g_asset_sizes[78])
|
||||
#define kEnding_MapData ((uint16*)g_asset_ptrs[79])
|
||||
#define kEnding_MapData_SIZE (g_asset_sizes[79])
|
||||
#define kEnding0_Offs ((uint16*)g_asset_ptrs[80])
|
||||
#define kEnding0_Offs_SIZE (g_asset_sizes[80])
|
||||
#define kEnding0_Data ((uint8*)g_asset_ptrs[81])
|
||||
#define kEnding0_Data_SIZE (g_asset_sizes[81])
|
||||
#define kPalette_DungBgMain ((uint16*)g_asset_ptrs[82])
|
||||
#define kPalette_DungBgMain_SIZE (g_asset_sizes[82])
|
||||
#define kPalette_MainSpr ((uint16*)g_asset_ptrs[83])
|
||||
#define kPalette_MainSpr_SIZE (g_asset_sizes[83])
|
||||
#define kPalette_ArmorAndGloves ((uint16*)g_asset_ptrs[84])
|
||||
#define kPalette_ArmorAndGloves_SIZE (g_asset_sizes[84])
|
||||
#define kPalette_Sword ((uint16*)g_asset_ptrs[85])
|
||||
#define kPalette_Sword_SIZE (g_asset_sizes[85])
|
||||
#define kPalette_Shield ((uint16*)g_asset_ptrs[86])
|
||||
#define kPalette_Shield_SIZE (g_asset_sizes[86])
|
||||
#define kPalette_SpriteAux3 ((uint16*)g_asset_ptrs[87])
|
||||
#define kPalette_SpriteAux3_SIZE (g_asset_sizes[87])
|
||||
#define kPalette_MiscSprite_Indoors ((uint16*)g_asset_ptrs[88])
|
||||
#define kPalette_MiscSprite_Indoors_SIZE (g_asset_sizes[88])
|
||||
#define kPalette_SpriteAux1 ((uint16*)g_asset_ptrs[89])
|
||||
#define kPalette_SpriteAux1_SIZE (g_asset_sizes[89])
|
||||
#define kPalette_OverworldBgMain ((uint16*)g_asset_ptrs[90])
|
||||
#define kPalette_OverworldBgMain_SIZE (g_asset_sizes[90])
|
||||
#define kPalette_OverworldBgAux12 ((uint16*)g_asset_ptrs[91])
|
||||
#define kPalette_OverworldBgAux12_SIZE (g_asset_sizes[91])
|
||||
#define kPalette_OverworldBgAux3 ((uint16*)g_asset_ptrs[92])
|
||||
#define kPalette_OverworldBgAux3_SIZE (g_asset_sizes[92])
|
||||
#define kPalette_PalaceMapBg ((uint16*)g_asset_ptrs[93])
|
||||
#define kPalette_PalaceMapBg_SIZE (g_asset_sizes[93])
|
||||
#define kPalette_PalaceMapSpr ((uint16*)g_asset_ptrs[94])
|
||||
#define kPalette_PalaceMapSpr_SIZE (g_asset_sizes[94])
|
||||
#define kHudPalData ((uint16*)g_asset_ptrs[95])
|
||||
#define kHudPalData_SIZE (g_asset_sizes[95])
|
||||
#define kOverworldMapPaletteData ((uint16*)g_asset_ptrs[96])
|
||||
#define kOverworldMapPaletteData_SIZE (g_asset_sizes[96])
|
||||
#define kDungMap_FloorLayout ((uint8*)g_asset_ptrs[97])
|
||||
#define kDungMap_FloorLayout_SIZE (g_asset_sizes[97])
|
||||
#define kDungMap_Tiles ((uint8*)g_asset_ptrs[98])
|
||||
#define kDungMap_Tiles_SIZE (g_asset_sizes[98])
|
||||
#define kBgTilemap_0 ((uint8*)g_asset_ptrs[99])
|
||||
#define kBgTilemap_0_SIZE (g_asset_sizes[99])
|
||||
#define kBgTilemap_1 ((uint8*)g_asset_ptrs[100])
|
||||
#define kBgTilemap_1_SIZE (g_asset_sizes[100])
|
||||
#define kBgTilemap_2 ((uint8*)g_asset_ptrs[101])
|
||||
#define kBgTilemap_2_SIZE (g_asset_sizes[101])
|
||||
#define kBgTilemap_3 ((uint8*)g_asset_ptrs[102])
|
||||
#define kBgTilemap_3_SIZE (g_asset_sizes[102])
|
||||
#define kBgTilemap_4 ((uint8*)g_asset_ptrs[103])
|
||||
#define kBgTilemap_4_SIZE (g_asset_sizes[103])
|
||||
#define kBgTilemap_5 ((uint8*)g_asset_ptrs[104])
|
||||
#define kBgTilemap_5_SIZE (g_asset_sizes[104])
|
||||
#define kOverworld_Hibytes_Comp ((uint8*)g_asset_ptrs[105])
|
||||
#define kOverworld_Hibytes_Comp_SIZE (g_asset_sizes[105])
|
||||
#define kOverworld_Lobytes_Comp ((uint8*)g_asset_ptrs[106])
|
||||
#define kOverworld_Lobytes_Comp_SIZE (g_asset_sizes[106])
|
||||
#define kOverworldMapIsSmall ((uint8*)g_asset_ptrs[107])
|
||||
#define kOverworldMapIsSmall_SIZE (g_asset_sizes[107])
|
||||
#define kOverworldAuxTileThemeIndexes ((uint8*)g_asset_ptrs[108])
|
||||
#define kOverworldAuxTileThemeIndexes_SIZE (g_asset_sizes[108])
|
||||
#define kOverworldBgPalettes ((uint8*)g_asset_ptrs[109])
|
||||
#define kOverworldBgPalettes_SIZE (g_asset_sizes[109])
|
||||
#define kOverworld_SignText ((uint16*)g_asset_ptrs[110])
|
||||
#define kOverworld_SignText_SIZE (g_asset_sizes[110])
|
||||
#define kOwMusicSets ((uint8*)g_asset_ptrs[111])
|
||||
#define kOwMusicSets_SIZE (g_asset_sizes[111])
|
||||
#define kOwMusicSets2 ((uint8*)g_asset_ptrs[112])
|
||||
#define kOwMusicSets2_SIZE (g_asset_sizes[112])
|
||||
#define kBirdTravel_ScreenIndex ((uint16*)g_asset_ptrs[113])
|
||||
#define kBirdTravel_ScreenIndex_SIZE (g_asset_sizes[113])
|
||||
#define kBirdTravel_Map16LoadSrcOff ((uint16*)g_asset_ptrs[114])
|
||||
#define kBirdTravel_Map16LoadSrcOff_SIZE (g_asset_sizes[114])
|
||||
#define kBirdTravel_ScrollX ((uint16*)g_asset_ptrs[115])
|
||||
#define kBirdTravel_ScrollX_SIZE (g_asset_sizes[115])
|
||||
#define kBirdTravel_ScrollY ((uint16*)g_asset_ptrs[116])
|
||||
#define kBirdTravel_ScrollY_SIZE (g_asset_sizes[116])
|
||||
#define kBirdTravel_LinkXCoord ((uint16*)g_asset_ptrs[117])
|
||||
#define kBirdTravel_LinkXCoord_SIZE (g_asset_sizes[117])
|
||||
#define kBirdTravel_LinkYCoord ((uint16*)g_asset_ptrs[118])
|
||||
#define kBirdTravel_LinkYCoord_SIZE (g_asset_sizes[118])
|
||||
#define kBirdTravel_CameraXScroll ((uint16*)g_asset_ptrs[119])
|
||||
#define kBirdTravel_CameraXScroll_SIZE (g_asset_sizes[119])
|
||||
#define kBirdTravel_CameraYScroll ((uint16*)g_asset_ptrs[120])
|
||||
#define kBirdTravel_CameraYScroll_SIZE (g_asset_sizes[120])
|
||||
#define kBirdTravel_Unk1 ((int8*)g_asset_ptrs[121])
|
||||
#define kBirdTravel_Unk1_SIZE (g_asset_sizes[121])
|
||||
#define kBirdTravel_Unk3 ((int8*)g_asset_ptrs[122])
|
||||
#define kBirdTravel_Unk3_SIZE (g_asset_sizes[122])
|
||||
#define kWhirlpoolAreas ((uint16*)g_asset_ptrs[123])
|
||||
#define kWhirlpoolAreas_SIZE (g_asset_sizes[123])
|
||||
#define kOverworld_Entrance_Area ((uint16*)g_asset_ptrs[124])
|
||||
#define kOverworld_Entrance_Area_SIZE (g_asset_sizes[124])
|
||||
#define kOverworld_Entrance_Pos ((uint16*)g_asset_ptrs[125])
|
||||
#define kOverworld_Entrance_Pos_SIZE (g_asset_sizes[125])
|
||||
#define kOverworld_Entrance_Id ((uint8*)g_asset_ptrs[126])
|
||||
#define kOverworld_Entrance_Id_SIZE (g_asset_sizes[126])
|
||||
#define kFallHole_Area ((uint16*)g_asset_ptrs[127])
|
||||
#define kFallHole_Area_SIZE (g_asset_sizes[127])
|
||||
#define kFallHole_Pos ((uint16*)g_asset_ptrs[128])
|
||||
#define kFallHole_Pos_SIZE (g_asset_sizes[128])
|
||||
#define kFallHole_Entrances ((uint8*)g_asset_ptrs[129])
|
||||
#define kFallHole_Entrances_SIZE (g_asset_sizes[129])
|
||||
#define kExitData_ScreenIndex ((uint8*)g_asset_ptrs[130])
|
||||
#define kExitData_ScreenIndex_SIZE (g_asset_sizes[130])
|
||||
#define kExitDataRooms ((uint16*)g_asset_ptrs[131])
|
||||
#define kExitDataRooms_SIZE (g_asset_sizes[131])
|
||||
#define kExitData_Map16LoadSrcOff ((uint16*)g_asset_ptrs[132])
|
||||
#define kExitData_Map16LoadSrcOff_SIZE (g_asset_sizes[132])
|
||||
#define kExitData_ScrollX ((uint16*)g_asset_ptrs[133])
|
||||
#define kExitData_ScrollX_SIZE (g_asset_sizes[133])
|
||||
#define kExitData_ScrollY ((uint16*)g_asset_ptrs[134])
|
||||
#define kExitData_ScrollY_SIZE (g_asset_sizes[134])
|
||||
#define kExitData_XCoord ((uint16*)g_asset_ptrs[135])
|
||||
#define kExitData_XCoord_SIZE (g_asset_sizes[135])
|
||||
#define kExitData_YCoord ((uint16*)g_asset_ptrs[136])
|
||||
#define kExitData_YCoord_SIZE (g_asset_sizes[136])
|
||||
#define kExitData_CameraXScroll ((uint16*)g_asset_ptrs[137])
|
||||
#define kExitData_CameraXScroll_SIZE (g_asset_sizes[137])
|
||||
#define kExitData_CameraYScroll ((uint16*)g_asset_ptrs[138])
|
||||
#define kExitData_CameraYScroll_SIZE (g_asset_sizes[138])
|
||||
#define kExitData_NormalDoor ((uint16*)g_asset_ptrs[139])
|
||||
#define kExitData_NormalDoor_SIZE (g_asset_sizes[139])
|
||||
#define kExitData_FancyDoor ((uint16*)g_asset_ptrs[140])
|
||||
#define kExitData_FancyDoor_SIZE (g_asset_sizes[140])
|
||||
#define kExitData_Unk1 ((int8*)g_asset_ptrs[141])
|
||||
#define kExitData_Unk1_SIZE (g_asset_sizes[141])
|
||||
#define kExitData_Unk3 ((int8*)g_asset_ptrs[142])
|
||||
#define kExitData_Unk3_SIZE (g_asset_sizes[142])
|
||||
#define kSpExit_Top ((uint16*)g_asset_ptrs[143])
|
||||
#define kSpExit_Top_SIZE (g_asset_sizes[143])
|
||||
#define kSpExit_Bottom ((uint16*)g_asset_ptrs[144])
|
||||
#define kSpExit_Bottom_SIZE (g_asset_sizes[144])
|
||||
#define kSpExit_Left ((uint16*)g_asset_ptrs[145])
|
||||
#define kSpExit_Left_SIZE (g_asset_sizes[145])
|
||||
#define kSpExit_Right ((uint16*)g_asset_ptrs[146])
|
||||
#define kSpExit_Right_SIZE (g_asset_sizes[146])
|
||||
#define kSpExit_Tab4 ((int16*)g_asset_ptrs[147])
|
||||
#define kSpExit_Tab4_SIZE (g_asset_sizes[147])
|
||||
#define kSpExit_Tab5 ((int16*)g_asset_ptrs[148])
|
||||
#define kSpExit_Tab5_SIZE (g_asset_sizes[148])
|
||||
#define kSpExit_Tab6 ((int16*)g_asset_ptrs[149])
|
||||
#define kSpExit_Tab6_SIZE (g_asset_sizes[149])
|
||||
#define kSpExit_Tab7 ((int16*)g_asset_ptrs[150])
|
||||
#define kSpExit_Tab7_SIZE (g_asset_sizes[150])
|
||||
#define kSpExit_LeftEdgeOfMap ((uint16*)g_asset_ptrs[151])
|
||||
#define kSpExit_LeftEdgeOfMap_SIZE (g_asset_sizes[151])
|
||||
#define kSpExit_Dir ((uint8*)g_asset_ptrs[152])
|
||||
#define kSpExit_Dir_SIZE (g_asset_sizes[152])
|
||||
#define kSpExit_SprGfx ((uint8*)g_asset_ptrs[153])
|
||||
#define kSpExit_SprGfx_SIZE (g_asset_sizes[153])
|
||||
#define kSpExit_AuxGfx ((uint8*)g_asset_ptrs[154])
|
||||
#define kSpExit_AuxGfx_SIZE (g_asset_sizes[154])
|
||||
#define kSpExit_PalBg ((uint8*)g_asset_ptrs[155])
|
||||
#define kSpExit_PalBg_SIZE (g_asset_sizes[155])
|
||||
#define kSpExit_PalSpr ((uint8*)g_asset_ptrs[156])
|
||||
#define kSpExit_PalSpr_SIZE (g_asset_sizes[156])
|
||||
#define kOverworldSecrets_Offs ((uint16*)g_asset_ptrs[157])
|
||||
#define kOverworldSecrets_Offs_SIZE (g_asset_sizes[157])
|
||||
#define kOverworldSecrets ((uint8*)g_asset_ptrs[158])
|
||||
#define kOverworldSecrets_SIZE (g_asset_sizes[158])
|
||||
#define kOverworldSpriteOffs ((uint16*)g_asset_ptrs[159])
|
||||
#define kOverworldSpriteOffs_SIZE (g_asset_sizes[159])
|
||||
#define kOverworldSprites ((uint8*)g_asset_ptrs[160])
|
||||
#define kOverworldSprites_SIZE (g_asset_sizes[160])
|
||||
#define kOverworldSpriteGfx ((uint8*)g_asset_ptrs[161])
|
||||
#define kOverworldSpriteGfx_SIZE (g_asset_sizes[161])
|
||||
#define kOverworldSpritePalettes ((uint8*)g_asset_ptrs[162])
|
||||
#define kOverworldSpritePalettes_SIZE (g_asset_sizes[162])
|
||||
#define kMap8DataToTileAttr ((uint8*)g_asset_ptrs[163])
|
||||
#define kMap8DataToTileAttr_SIZE (g_asset_sizes[163])
|
||||
#define kSomeTileAttr ((uint8*)g_asset_ptrs[164])
|
||||
#define kSomeTileAttr_SIZE (g_asset_sizes[164])
|
||||
#define kAssets_Sig 90, 101, 108, 100, 97, 51, 95, 118, 48, 32, 32, 32, 32, 32, 10, 0, 140, 60, 238, 107, 183, 109, 113, 156, 115, 98, 147, 236, 79, 160, 95, 71, 26, 185, 82, 158, 43, 82, 22, 60, 62, 92, 250, 152, 23, 230, 97, 235
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
enum {
|
||||
kNumberOfAssets = 165
|
||||
};
|
||||
extern const uint8 *g_asset_ptrs[kNumberOfAssets];
|
||||
extern uint32 g_asset_sizes[kNumberOfAssets];
|
||||
extern MemBlk FindInAssetArray(int asset, int idx);
|
||||
|
||||
#define kSoundBank_intro ((uint8*)g_asset_ptrs[0])
|
||||
#define kSoundBank_intro_SIZE (g_asset_sizes[0])
|
||||
#define kSoundBank_indoor ((uint8*)g_asset_ptrs[1])
|
||||
#define kSoundBank_indoor_SIZE (g_asset_sizes[1])
|
||||
#define kSoundBank_ending ((uint8*)g_asset_ptrs[2])
|
||||
#define kSoundBank_ending_SIZE (g_asset_sizes[2])
|
||||
#define kDungeonRoom ((uint8*)g_asset_ptrs[3])
|
||||
#define kDungeonRoom_SIZE (g_asset_sizes[3])
|
||||
#define kDungeonRoomOffs ((uint16*)g_asset_ptrs[4])
|
||||
#define kDungeonRoomOffs_SIZE (g_asset_sizes[4])
|
||||
#define kDungeonRoomDoorOffs ((uint16*)g_asset_ptrs[5])
|
||||
#define kDungeonRoomDoorOffs_SIZE (g_asset_sizes[5])
|
||||
#define kDungeonRoomHeaders ((uint8*)g_asset_ptrs[6])
|
||||
#define kDungeonRoomHeaders_SIZE (g_asset_sizes[6])
|
||||
#define kDungeonRoomHeadersOffs ((uint16*)g_asset_ptrs[7])
|
||||
#define kDungeonRoomHeadersOffs_SIZE (g_asset_sizes[7])
|
||||
#define kDungeonRoomChests ((uint8*)g_asset_ptrs[8])
|
||||
#define kDungeonRoomChests_SIZE (g_asset_sizes[8])
|
||||
#define kDungeonRoomTeleMsg ((uint16*)g_asset_ptrs[9])
|
||||
#define kDungeonRoomTeleMsg_SIZE (g_asset_sizes[9])
|
||||
#define kDungeonPitsHurtPlayer ((uint16*)g_asset_ptrs[10])
|
||||
#define kDungeonPitsHurtPlayer_SIZE (g_asset_sizes[10])
|
||||
#define kEntranceData_rooms ((uint16*)g_asset_ptrs[11])
|
||||
#define kEntranceData_rooms_SIZE (g_asset_sizes[11])
|
||||
#define kEntranceData_relativeCoords ((uint8*)g_asset_ptrs[12])
|
||||
#define kEntranceData_relativeCoords_SIZE (g_asset_sizes[12])
|
||||
#define kEntranceData_scrollX ((uint16*)g_asset_ptrs[13])
|
||||
#define kEntranceData_scrollX_SIZE (g_asset_sizes[13])
|
||||
#define kEntranceData_scrollY ((uint16*)g_asset_ptrs[14])
|
||||
#define kEntranceData_scrollY_SIZE (g_asset_sizes[14])
|
||||
#define kEntranceData_playerX ((uint16*)g_asset_ptrs[15])
|
||||
#define kEntranceData_playerX_SIZE (g_asset_sizes[15])
|
||||
#define kEntranceData_playerY ((uint16*)g_asset_ptrs[16])
|
||||
#define kEntranceData_playerY_SIZE (g_asset_sizes[16])
|
||||
#define kEntranceData_cameraX ((uint16*)g_asset_ptrs[17])
|
||||
#define kEntranceData_cameraX_SIZE (g_asset_sizes[17])
|
||||
#define kEntranceData_cameraY ((uint16*)g_asset_ptrs[18])
|
||||
#define kEntranceData_cameraY_SIZE (g_asset_sizes[18])
|
||||
#define kEntranceData_blockset ((uint8*)g_asset_ptrs[19])
|
||||
#define kEntranceData_blockset_SIZE (g_asset_sizes[19])
|
||||
#define kEntranceData_floor ((int8*)g_asset_ptrs[20])
|
||||
#define kEntranceData_floor_SIZE (g_asset_sizes[20])
|
||||
#define kEntranceData_palace ((int8*)g_asset_ptrs[21])
|
||||
#define kEntranceData_palace_SIZE (g_asset_sizes[21])
|
||||
#define kEntranceData_doorwayOrientation ((uint8*)g_asset_ptrs[22])
|
||||
#define kEntranceData_doorwayOrientation_SIZE (g_asset_sizes[22])
|
||||
#define kEntranceData_startingBg ((uint8*)g_asset_ptrs[23])
|
||||
#define kEntranceData_startingBg_SIZE (g_asset_sizes[23])
|
||||
#define kEntranceData_quadrant1 ((uint8*)g_asset_ptrs[24])
|
||||
#define kEntranceData_quadrant1_SIZE (g_asset_sizes[24])
|
||||
#define kEntranceData_quadrant2 ((uint8*)g_asset_ptrs[25])
|
||||
#define kEntranceData_quadrant2_SIZE (g_asset_sizes[25])
|
||||
#define kEntranceData_doorSettings ((uint16*)g_asset_ptrs[26])
|
||||
#define kEntranceData_doorSettings_SIZE (g_asset_sizes[26])
|
||||
#define kEntranceData_musicTrack ((uint8*)g_asset_ptrs[27])
|
||||
#define kEntranceData_musicTrack_SIZE (g_asset_sizes[27])
|
||||
#define kStartingPoint_rooms ((uint16*)g_asset_ptrs[28])
|
||||
#define kStartingPoint_rooms_SIZE (g_asset_sizes[28])
|
||||
#define kStartingPoint_relativeCoords ((uint8*)g_asset_ptrs[29])
|
||||
#define kStartingPoint_relativeCoords_SIZE (g_asset_sizes[29])
|
||||
#define kStartingPoint_scrollX ((uint16*)g_asset_ptrs[30])
|
||||
#define kStartingPoint_scrollX_SIZE (g_asset_sizes[30])
|
||||
#define kStartingPoint_scrollY ((uint16*)g_asset_ptrs[31])
|
||||
#define kStartingPoint_scrollY_SIZE (g_asset_sizes[31])
|
||||
#define kStartingPoint_playerX ((uint16*)g_asset_ptrs[32])
|
||||
#define kStartingPoint_playerX_SIZE (g_asset_sizes[32])
|
||||
#define kStartingPoint_playerY ((uint16*)g_asset_ptrs[33])
|
||||
#define kStartingPoint_playerY_SIZE (g_asset_sizes[33])
|
||||
#define kStartingPoint_cameraX ((uint16*)g_asset_ptrs[34])
|
||||
#define kStartingPoint_cameraX_SIZE (g_asset_sizes[34])
|
||||
#define kStartingPoint_cameraY ((uint16*)g_asset_ptrs[35])
|
||||
#define kStartingPoint_cameraY_SIZE (g_asset_sizes[35])
|
||||
#define kStartingPoint_blockset ((uint8*)g_asset_ptrs[36])
|
||||
#define kStartingPoint_blockset_SIZE (g_asset_sizes[36])
|
||||
#define kStartingPoint_floor ((int8*)g_asset_ptrs[37])
|
||||
#define kStartingPoint_floor_SIZE (g_asset_sizes[37])
|
||||
#define kStartingPoint_palace ((int8*)g_asset_ptrs[38])
|
||||
#define kStartingPoint_palace_SIZE (g_asset_sizes[38])
|
||||
#define kStartingPoint_doorwayOrientation ((uint8*)g_asset_ptrs[39])
|
||||
#define kStartingPoint_doorwayOrientation_SIZE (g_asset_sizes[39])
|
||||
#define kStartingPoint_startingBg ((uint8*)g_asset_ptrs[40])
|
||||
#define kStartingPoint_startingBg_SIZE (g_asset_sizes[40])
|
||||
#define kStartingPoint_quadrant1 ((uint8*)g_asset_ptrs[41])
|
||||
#define kStartingPoint_quadrant1_SIZE (g_asset_sizes[41])
|
||||
#define kStartingPoint_quadrant2 ((uint8*)g_asset_ptrs[42])
|
||||
#define kStartingPoint_quadrant2_SIZE (g_asset_sizes[42])
|
||||
#define kStartingPoint_doorSettings ((uint16*)g_asset_ptrs[43])
|
||||
#define kStartingPoint_doorSettings_SIZE (g_asset_sizes[43])
|
||||
#define kStartingPoint_entrance ((uint8*)g_asset_ptrs[44])
|
||||
#define kStartingPoint_entrance_SIZE (g_asset_sizes[44])
|
||||
#define kStartingPoint_musicTrack ((uint8*)g_asset_ptrs[45])
|
||||
#define kStartingPoint_musicTrack_SIZE (g_asset_sizes[45])
|
||||
#define kDungeonRoomDefault ((uint8*)g_asset_ptrs[46])
|
||||
#define kDungeonRoomDefault_SIZE (g_asset_sizes[46])
|
||||
#define kDungeonRoomDefaultOffs ((uint16*)g_asset_ptrs[47])
|
||||
#define kDungeonRoomDefaultOffs_SIZE (g_asset_sizes[47])
|
||||
#define kDungeonRoomOverlay ((uint8*)g_asset_ptrs[48])
|
||||
#define kDungeonRoomOverlay_SIZE (g_asset_sizes[48])
|
||||
#define kDungeonRoomOverlayOffs ((uint16*)g_asset_ptrs[49])
|
||||
#define kDungeonRoomOverlayOffs_SIZE (g_asset_sizes[49])
|
||||
#define kDungeonSecrets ((uint8*)g_asset_ptrs[50])
|
||||
#define kDungeonSecrets_SIZE (g_asset_sizes[50])
|
||||
#define kDungAttrsForTile_Offs ((uint16*)g_asset_ptrs[51])
|
||||
#define kDungAttrsForTile_Offs_SIZE (g_asset_sizes[51])
|
||||
#define kDungAttrsForTile ((uint8*)g_asset_ptrs[52])
|
||||
#define kDungAttrsForTile_SIZE (g_asset_sizes[52])
|
||||
#define kMovableBlockDataInit ((uint16*)g_asset_ptrs[53])
|
||||
#define kMovableBlockDataInit_SIZE (g_asset_sizes[53])
|
||||
#define kTorchDataInit ((uint16*)g_asset_ptrs[54])
|
||||
#define kTorchDataInit_SIZE (g_asset_sizes[54])
|
||||
#define kTorchDataJunk ((uint16*)g_asset_ptrs[55])
|
||||
#define kTorchDataJunk_SIZE (g_asset_sizes[55])
|
||||
#define kEnemyDamageData ((uint8*)g_asset_ptrs[56])
|
||||
#define kEnemyDamageData_SIZE (g_asset_sizes[56])
|
||||
#define kLinkGraphics ((uint8*)g_asset_ptrs[57])
|
||||
#define kLinkGraphics_SIZE (g_asset_sizes[57])
|
||||
#define kDungeonSprites ((uint8*)g_asset_ptrs[58])
|
||||
#define kDungeonSprites_SIZE (g_asset_sizes[58])
|
||||
#define kDungeonSpriteOffs ((uint16*)g_asset_ptrs[59])
|
||||
#define kDungeonSpriteOffs_SIZE (g_asset_sizes[59])
|
||||
#define kMap32ToMap16_0 ((uint8*)g_asset_ptrs[60])
|
||||
#define kMap32ToMap16_0_SIZE (g_asset_sizes[60])
|
||||
#define kMap32ToMap16_1 ((uint8*)g_asset_ptrs[61])
|
||||
#define kMap32ToMap16_1_SIZE (g_asset_sizes[61])
|
||||
#define kMap32ToMap16_2 ((uint8*)g_asset_ptrs[62])
|
||||
#define kMap32ToMap16_2_SIZE (g_asset_sizes[62])
|
||||
#define kMap32ToMap16_3 ((uint8*)g_asset_ptrs[63])
|
||||
#define kMap32ToMap16_3_SIZE (g_asset_sizes[63])
|
||||
#define kSprGfx(idx) FindInAssetArray(64, idx)
|
||||
#define kBgGfx(idx) FindInAssetArray(65, idx)
|
||||
#define kOverworldMapGfx ((uint8*)g_asset_ptrs[66])
|
||||
#define kOverworldMapGfx_SIZE (g_asset_sizes[66])
|
||||
#define kLightOverworldTilemap ((uint8*)g_asset_ptrs[67])
|
||||
#define kLightOverworldTilemap_SIZE (g_asset_sizes[67])
|
||||
#define kDarkOverworldTilemap ((uint8*)g_asset_ptrs[68])
|
||||
#define kDarkOverworldTilemap_SIZE (g_asset_sizes[68])
|
||||
#define kPredefinedTileData ((uint16*)g_asset_ptrs[69])
|
||||
#define kPredefinedTileData_SIZE (g_asset_sizes[69])
|
||||
#define kMap16ToMap8 ((uint16*)g_asset_ptrs[70])
|
||||
#define kMap16ToMap8_SIZE (g_asset_sizes[70])
|
||||
#define kGeneratedWishPondItem ((uint8*)g_asset_ptrs[71])
|
||||
#define kGeneratedWishPondItem_SIZE (g_asset_sizes[71])
|
||||
#define kGeneratedBombosArr ((uint8*)g_asset_ptrs[72])
|
||||
#define kGeneratedBombosArr_SIZE (g_asset_sizes[72])
|
||||
#define kGeneratedEndSequence15 ((uint8*)g_asset_ptrs[73])
|
||||
#define kGeneratedEndSequence15_SIZE (g_asset_sizes[73])
|
||||
#define kEnding_Credits_Text ((uint8*)g_asset_ptrs[74])
|
||||
#define kEnding_Credits_Text_SIZE (g_asset_sizes[74])
|
||||
#define kEnding_Credits_Offs ((uint16*)g_asset_ptrs[75])
|
||||
#define kEnding_Credits_Offs_SIZE (g_asset_sizes[75])
|
||||
#define kEnding_MapData ((uint16*)g_asset_ptrs[76])
|
||||
#define kEnding_MapData_SIZE (g_asset_sizes[76])
|
||||
#define kEnding0_Offs ((uint16*)g_asset_ptrs[77])
|
||||
#define kEnding0_Offs_SIZE (g_asset_sizes[77])
|
||||
#define kEnding0_Data ((uint8*)g_asset_ptrs[78])
|
||||
#define kEnding0_Data_SIZE (g_asset_sizes[78])
|
||||
#define kPalette_DungBgMain ((uint16*)g_asset_ptrs[79])
|
||||
#define kPalette_DungBgMain_SIZE (g_asset_sizes[79])
|
||||
#define kPalette_MainSpr ((uint16*)g_asset_ptrs[80])
|
||||
#define kPalette_MainSpr_SIZE (g_asset_sizes[80])
|
||||
#define kPalette_ArmorAndGloves ((uint16*)g_asset_ptrs[81])
|
||||
#define kPalette_ArmorAndGloves_SIZE (g_asset_sizes[81])
|
||||
#define kPalette_Sword ((uint16*)g_asset_ptrs[82])
|
||||
#define kPalette_Sword_SIZE (g_asset_sizes[82])
|
||||
#define kPalette_Shield ((uint16*)g_asset_ptrs[83])
|
||||
#define kPalette_Shield_SIZE (g_asset_sizes[83])
|
||||
#define kPalette_SpriteAux3 ((uint16*)g_asset_ptrs[84])
|
||||
#define kPalette_SpriteAux3_SIZE (g_asset_sizes[84])
|
||||
#define kPalette_MiscSprite_Indoors ((uint16*)g_asset_ptrs[85])
|
||||
#define kPalette_MiscSprite_Indoors_SIZE (g_asset_sizes[85])
|
||||
#define kPalette_SpriteAux1 ((uint16*)g_asset_ptrs[86])
|
||||
#define kPalette_SpriteAux1_SIZE (g_asset_sizes[86])
|
||||
#define kPalette_OverworldBgMain ((uint16*)g_asset_ptrs[87])
|
||||
#define kPalette_OverworldBgMain_SIZE (g_asset_sizes[87])
|
||||
#define kPalette_OverworldBgAux12 ((uint16*)g_asset_ptrs[88])
|
||||
#define kPalette_OverworldBgAux12_SIZE (g_asset_sizes[88])
|
||||
#define kPalette_OverworldBgAux3 ((uint16*)g_asset_ptrs[89])
|
||||
#define kPalette_OverworldBgAux3_SIZE (g_asset_sizes[89])
|
||||
#define kPalette_PalaceMapBg ((uint16*)g_asset_ptrs[90])
|
||||
#define kPalette_PalaceMapBg_SIZE (g_asset_sizes[90])
|
||||
#define kPalette_PalaceMapSpr ((uint16*)g_asset_ptrs[91])
|
||||
#define kPalette_PalaceMapSpr_SIZE (g_asset_sizes[91])
|
||||
#define kHudPalData ((uint16*)g_asset_ptrs[92])
|
||||
#define kHudPalData_SIZE (g_asset_sizes[92])
|
||||
#define kOverworldMapPaletteData ((uint16*)g_asset_ptrs[93])
|
||||
#define kOverworldMapPaletteData_SIZE (g_asset_sizes[93])
|
||||
#define kDialogue(idx) FindInAssetArray(94, idx)
|
||||
#define kDialogueFont(idx) FindInAssetArray(95, idx)
|
||||
#define kDialogueMap(idx) FindInAssetArray(96, idx)
|
||||
#define kDungMap_FloorLayout(idx) FindInAssetArray(97, idx)
|
||||
#define kDungMap_Tiles(idx) FindInAssetArray(98, idx)
|
||||
#define kBgTilemap_0 ((uint8*)g_asset_ptrs[99])
|
||||
#define kBgTilemap_0_SIZE (g_asset_sizes[99])
|
||||
#define kBgTilemap_1 ((uint8*)g_asset_ptrs[100])
|
||||
#define kBgTilemap_1_SIZE (g_asset_sizes[100])
|
||||
#define kBgTilemap_2 ((uint8*)g_asset_ptrs[101])
|
||||
#define kBgTilemap_2_SIZE (g_asset_sizes[101])
|
||||
#define kBgTilemap_3 ((uint8*)g_asset_ptrs[102])
|
||||
#define kBgTilemap_3_SIZE (g_asset_sizes[102])
|
||||
#define kBgTilemap_4 ((uint8*)g_asset_ptrs[103])
|
||||
#define kBgTilemap_4_SIZE (g_asset_sizes[103])
|
||||
#define kBgTilemap_5 ((uint8*)g_asset_ptrs[104])
|
||||
#define kBgTilemap_5_SIZE (g_asset_sizes[104])
|
||||
#define kOverworld_Hibytes_Comp(idx) FindInAssetArray(105, idx)
|
||||
#define kOverworld_Lobytes_Comp(idx) FindInAssetArray(106, idx)
|
||||
#define kOverworldMapIsSmall ((uint8*)g_asset_ptrs[107])
|
||||
#define kOverworldMapIsSmall_SIZE (g_asset_sizes[107])
|
||||
#define kOverworldAuxTileThemeIndexes ((uint8*)g_asset_ptrs[108])
|
||||
#define kOverworldAuxTileThemeIndexes_SIZE (g_asset_sizes[108])
|
||||
#define kOverworldBgPalettes ((uint8*)g_asset_ptrs[109])
|
||||
#define kOverworldBgPalettes_SIZE (g_asset_sizes[109])
|
||||
#define kOverworld_SignText ((uint16*)g_asset_ptrs[110])
|
||||
#define kOverworld_SignText_SIZE (g_asset_sizes[110])
|
||||
#define kOwMusicSets ((uint8*)g_asset_ptrs[111])
|
||||
#define kOwMusicSets_SIZE (g_asset_sizes[111])
|
||||
#define kOwMusicSets2 ((uint8*)g_asset_ptrs[112])
|
||||
#define kOwMusicSets2_SIZE (g_asset_sizes[112])
|
||||
#define kBirdTravel_ScreenIndex ((uint16*)g_asset_ptrs[113])
|
||||
#define kBirdTravel_ScreenIndex_SIZE (g_asset_sizes[113])
|
||||
#define kBirdTravel_Map16LoadSrcOff ((uint16*)g_asset_ptrs[114])
|
||||
#define kBirdTravel_Map16LoadSrcOff_SIZE (g_asset_sizes[114])
|
||||
#define kBirdTravel_ScrollX ((uint16*)g_asset_ptrs[115])
|
||||
#define kBirdTravel_ScrollX_SIZE (g_asset_sizes[115])
|
||||
#define kBirdTravel_ScrollY ((uint16*)g_asset_ptrs[116])
|
||||
#define kBirdTravel_ScrollY_SIZE (g_asset_sizes[116])
|
||||
#define kBirdTravel_LinkXCoord ((uint16*)g_asset_ptrs[117])
|
||||
#define kBirdTravel_LinkXCoord_SIZE (g_asset_sizes[117])
|
||||
#define kBirdTravel_LinkYCoord ((uint16*)g_asset_ptrs[118])
|
||||
#define kBirdTravel_LinkYCoord_SIZE (g_asset_sizes[118])
|
||||
#define kBirdTravel_CameraXScroll ((uint16*)g_asset_ptrs[119])
|
||||
#define kBirdTravel_CameraXScroll_SIZE (g_asset_sizes[119])
|
||||
#define kBirdTravel_CameraYScroll ((uint16*)g_asset_ptrs[120])
|
||||
#define kBirdTravel_CameraYScroll_SIZE (g_asset_sizes[120])
|
||||
#define kBirdTravel_Unk1 ((int8*)g_asset_ptrs[121])
|
||||
#define kBirdTravel_Unk1_SIZE (g_asset_sizes[121])
|
||||
#define kBirdTravel_Unk3 ((int8*)g_asset_ptrs[122])
|
||||
#define kBirdTravel_Unk3_SIZE (g_asset_sizes[122])
|
||||
#define kWhirlpoolAreas ((uint16*)g_asset_ptrs[123])
|
||||
#define kWhirlpoolAreas_SIZE (g_asset_sizes[123])
|
||||
#define kOverworld_Entrance_Area ((uint16*)g_asset_ptrs[124])
|
||||
#define kOverworld_Entrance_Area_SIZE (g_asset_sizes[124])
|
||||
#define kOverworld_Entrance_Pos ((uint16*)g_asset_ptrs[125])
|
||||
#define kOverworld_Entrance_Pos_SIZE (g_asset_sizes[125])
|
||||
#define kOverworld_Entrance_Id ((uint8*)g_asset_ptrs[126])
|
||||
#define kOverworld_Entrance_Id_SIZE (g_asset_sizes[126])
|
||||
#define kFallHole_Area ((uint16*)g_asset_ptrs[127])
|
||||
#define kFallHole_Area_SIZE (g_asset_sizes[127])
|
||||
#define kFallHole_Pos ((uint16*)g_asset_ptrs[128])
|
||||
#define kFallHole_Pos_SIZE (g_asset_sizes[128])
|
||||
#define kFallHole_Entrances ((uint8*)g_asset_ptrs[129])
|
||||
#define kFallHole_Entrances_SIZE (g_asset_sizes[129])
|
||||
#define kExitData_ScreenIndex ((uint8*)g_asset_ptrs[130])
|
||||
#define kExitData_ScreenIndex_SIZE (g_asset_sizes[130])
|
||||
#define kExitDataRooms ((uint16*)g_asset_ptrs[131])
|
||||
#define kExitDataRooms_SIZE (g_asset_sizes[131])
|
||||
#define kExitData_Map16LoadSrcOff ((uint16*)g_asset_ptrs[132])
|
||||
#define kExitData_Map16LoadSrcOff_SIZE (g_asset_sizes[132])
|
||||
#define kExitData_ScrollX ((uint16*)g_asset_ptrs[133])
|
||||
#define kExitData_ScrollX_SIZE (g_asset_sizes[133])
|
||||
#define kExitData_ScrollY ((uint16*)g_asset_ptrs[134])
|
||||
#define kExitData_ScrollY_SIZE (g_asset_sizes[134])
|
||||
#define kExitData_XCoord ((uint16*)g_asset_ptrs[135])
|
||||
#define kExitData_XCoord_SIZE (g_asset_sizes[135])
|
||||
#define kExitData_YCoord ((uint16*)g_asset_ptrs[136])
|
||||
#define kExitData_YCoord_SIZE (g_asset_sizes[136])
|
||||
#define kExitData_CameraXScroll ((uint16*)g_asset_ptrs[137])
|
||||
#define kExitData_CameraXScroll_SIZE (g_asset_sizes[137])
|
||||
#define kExitData_CameraYScroll ((uint16*)g_asset_ptrs[138])
|
||||
#define kExitData_CameraYScroll_SIZE (g_asset_sizes[138])
|
||||
#define kExitData_NormalDoor ((uint16*)g_asset_ptrs[139])
|
||||
#define kExitData_NormalDoor_SIZE (g_asset_sizes[139])
|
||||
#define kExitData_FancyDoor ((uint16*)g_asset_ptrs[140])
|
||||
#define kExitData_FancyDoor_SIZE (g_asset_sizes[140])
|
||||
#define kExitData_Unk1 ((int8*)g_asset_ptrs[141])
|
||||
#define kExitData_Unk1_SIZE (g_asset_sizes[141])
|
||||
#define kExitData_Unk3 ((int8*)g_asset_ptrs[142])
|
||||
#define kExitData_Unk3_SIZE (g_asset_sizes[142])
|
||||
#define kSpExit_Top ((uint16*)g_asset_ptrs[143])
|
||||
#define kSpExit_Top_SIZE (g_asset_sizes[143])
|
||||
#define kSpExit_Bottom ((uint16*)g_asset_ptrs[144])
|
||||
#define kSpExit_Bottom_SIZE (g_asset_sizes[144])
|
||||
#define kSpExit_Left ((uint16*)g_asset_ptrs[145])
|
||||
#define kSpExit_Left_SIZE (g_asset_sizes[145])
|
||||
#define kSpExit_Right ((uint16*)g_asset_ptrs[146])
|
||||
#define kSpExit_Right_SIZE (g_asset_sizes[146])
|
||||
#define kSpExit_Tab4 ((int16*)g_asset_ptrs[147])
|
||||
#define kSpExit_Tab4_SIZE (g_asset_sizes[147])
|
||||
#define kSpExit_Tab5 ((int16*)g_asset_ptrs[148])
|
||||
#define kSpExit_Tab5_SIZE (g_asset_sizes[148])
|
||||
#define kSpExit_Tab6 ((int16*)g_asset_ptrs[149])
|
||||
#define kSpExit_Tab6_SIZE (g_asset_sizes[149])
|
||||
#define kSpExit_Tab7 ((int16*)g_asset_ptrs[150])
|
||||
#define kSpExit_Tab7_SIZE (g_asset_sizes[150])
|
||||
#define kSpExit_LeftEdgeOfMap ((uint16*)g_asset_ptrs[151])
|
||||
#define kSpExit_LeftEdgeOfMap_SIZE (g_asset_sizes[151])
|
||||
#define kSpExit_Dir ((uint8*)g_asset_ptrs[152])
|
||||
#define kSpExit_Dir_SIZE (g_asset_sizes[152])
|
||||
#define kSpExit_SprGfx ((uint8*)g_asset_ptrs[153])
|
||||
#define kSpExit_SprGfx_SIZE (g_asset_sizes[153])
|
||||
#define kSpExit_AuxGfx ((uint8*)g_asset_ptrs[154])
|
||||
#define kSpExit_AuxGfx_SIZE (g_asset_sizes[154])
|
||||
#define kSpExit_PalBg ((uint8*)g_asset_ptrs[155])
|
||||
#define kSpExit_PalBg_SIZE (g_asset_sizes[155])
|
||||
#define kSpExit_PalSpr ((uint8*)g_asset_ptrs[156])
|
||||
#define kSpExit_PalSpr_SIZE (g_asset_sizes[156])
|
||||
#define kOverworldSecrets_Offs ((uint16*)g_asset_ptrs[157])
|
||||
#define kOverworldSecrets_Offs_SIZE (g_asset_sizes[157])
|
||||
#define kOverworldSecrets ((uint8*)g_asset_ptrs[158])
|
||||
#define kOverworldSecrets_SIZE (g_asset_sizes[158])
|
||||
#define kOverworldSpriteOffs ((uint16*)g_asset_ptrs[159])
|
||||
#define kOverworldSpriteOffs_SIZE (g_asset_sizes[159])
|
||||
#define kOverworldSprites ((uint8*)g_asset_ptrs[160])
|
||||
#define kOverworldSprites_SIZE (g_asset_sizes[160])
|
||||
#define kOverworldSpriteGfx ((uint8*)g_asset_ptrs[161])
|
||||
#define kOverworldSpriteGfx_SIZE (g_asset_sizes[161])
|
||||
#define kOverworldSpritePalettes ((uint8*)g_asset_ptrs[162])
|
||||
#define kOverworldSpritePalettes_SIZE (g_asset_sizes[162])
|
||||
#define kMap8DataToTileAttr ((uint8*)g_asset_ptrs[163])
|
||||
#define kMap8DataToTileAttr_SIZE (g_asset_sizes[163])
|
||||
#define kSomeTileAttr ((uint8*)g_asset_ptrs[164])
|
||||
#define kSomeTileAttr_SIZE (g_asset_sizes[164])
|
||||
#define kAssets_Sig 90, 101, 108, 100, 97, 51, 95, 118, 48, 32, 32, 32, 32, 32, 10, 0, 27, 174, 233, 45, 74, 174, 252, 50, 49, 27, 153, 197, 27, 43, 216, 197, 132, 101, 173, 169, 36, 108, 15, 155, 176, 169, 57, 131, 174, 101, 51, 207
|
||||
@@ -360,10 +360,10 @@ void Dungeon_SaveAndLoadLoadAllPalettes(uint8 a, uint8 k) { // 82c546
|
||||
overworld_palette_aux_or_main = 0x200;
|
||||
flag_update_cgram_in_nmi++;
|
||||
Palette_BgAndFixedColor_Black();
|
||||
Palette_Load_SpritePal0Left();
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_SpriteMain();
|
||||
Palette_Load_SpriteAux1();
|
||||
Palette_Load_SpriteAux2();
|
||||
Palette_Load_Sp5L();
|
||||
Palette_Load_Sp6L();
|
||||
Palette_Load_SpriteEnvironment_Dungeon();
|
||||
Palette_Load_HUD();
|
||||
Palette_Load_DungeonSet();
|
||||
@@ -524,10 +524,10 @@ void AttractScene_ThroneRoom() { // 8cef4e
|
||||
Dungeon_LoadAndDrawEntranceRoom(0x74);
|
||||
WORD(attract_state) = bak1;
|
||||
attract_var12 = bak0;
|
||||
dung_hdr_palette_1 = 0;
|
||||
overworld_palette_sp0 = 0;
|
||||
sprite_aux1_palette = 14;
|
||||
sprite_aux2_palette = 3;
|
||||
palette_main_indoors = 0;
|
||||
palette_sp0l = 0;
|
||||
palette_sp5l = 14;
|
||||
palette_sp6l = 3;
|
||||
Dungeon_SaveAndLoadLoadAllPalettes(0, 0x7e);
|
||||
|
||||
main_palette_buffer[0x1d] = 0x3800;
|
||||
@@ -560,10 +560,10 @@ void Attract_PrepZeldaPrison() { // 8cefe3
|
||||
WORD(attract_state) = bak1;
|
||||
attract_var12 = bak0;
|
||||
|
||||
dung_hdr_palette_1 = 2;
|
||||
overworld_palette_sp0 = 0;
|
||||
sprite_aux1_palette = 14;
|
||||
sprite_aux2_palette = 3;
|
||||
palette_main_indoors = 2;
|
||||
palette_sp0l = 0;
|
||||
palette_sp5l = 14;
|
||||
palette_sp6l = 3;
|
||||
Dungeon_SaveAndLoadLoadAllPalettes(1, 0x7f);
|
||||
main_palette_buffer[0x1d] = 0x3800;
|
||||
|
||||
@@ -589,16 +589,16 @@ void Attract_PrepMaidenWarp() { // 8cf058
|
||||
WORD(attract_state) = bak1;
|
||||
attract_var12 = bak0;
|
||||
|
||||
dung_hdr_palette_1 = 0;
|
||||
overworld_palette_sp0 = 0;
|
||||
sprite_aux1_palette = 14;
|
||||
sprite_aux2_palette = 3;
|
||||
palette_main_indoors = 0;
|
||||
palette_sp0l = 0;
|
||||
palette_sp5l = 14;
|
||||
palette_sp6l = 3;
|
||||
|
||||
overworld_palette_aux_or_main = 0;
|
||||
Palette_Load_SpritePal0Left();
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_SpriteMain();
|
||||
Palette_Load_SpriteAux1();
|
||||
Palette_Load_SpriteAux2();
|
||||
Palette_Load_Sp5L();
|
||||
Palette_Load_Sp6L();
|
||||
Palette_Load_SpriteEnvironment_Dungeon();
|
||||
Palette_Load_HUD();
|
||||
Palette_Load_DungeonSet();
|
||||
542
src/audio.c
Normal file
542
src/audio.c
Normal file
@@ -0,0 +1,542 @@
|
||||
#include "audio.h"
|
||||
#include "zelda_rtl.h"
|
||||
#include "variables.h"
|
||||
#include "features.h"
|
||||
#include "snes/snes_regs.h"
|
||||
#include "spc_player.h"
|
||||
#include "third_party/opus-1.3.1-stripped/opus.h"
|
||||
#include "config.h"
|
||||
#include "assets.h"
|
||||
|
||||
// This needs to hold a lot more things than with just PCM
|
||||
typedef struct MsuPlayerResumeInfo {
|
||||
uint32 tag;
|
||||
uint32 offset;
|
||||
uint32 samples_until_repeat;
|
||||
uint16 range_cur, range_repeat;
|
||||
uint64 initial_packet_bytes; // To verify we seeked right
|
||||
uint8 orig_track; // Using the old zelda track numbers
|
||||
uint8 actual_track; // The MSU track index we're playing (Different if using msu deluxe)
|
||||
} MsuPlayerResumeInfo;
|
||||
|
||||
enum {
|
||||
kMsuState_Idle = 0,
|
||||
kMsuState_FinishedPlaying = 1,
|
||||
kMsuState_Resuming = 2,
|
||||
kMsuState_Playing = 3,
|
||||
};
|
||||
|
||||
typedef struct MsuPlayer {
|
||||
FILE *f;
|
||||
uint32 buffer_size, buffer_pos;
|
||||
uint32 preskip, samples_until_repeat;
|
||||
uint32 total_samples_in_file, repeat_position;
|
||||
uint32 cur_file_offs;
|
||||
MsuPlayerResumeInfo resume_info;
|
||||
uint8 enabled;
|
||||
uint8 state;
|
||||
float volume, volume_step, volume_target;
|
||||
uint16 range_cur, range_repeat;
|
||||
OpusDecoder *opus;
|
||||
int16 buffer[960 * 2];
|
||||
} MsuPlayer;
|
||||
|
||||
static MsuPlayer g_msu_player;
|
||||
|
||||
static void MsuPlayer_Open(MsuPlayer *mp, int orig_track, bool resume_from_snapshot);
|
||||
|
||||
static const uint8 kMsuTracksWithRepeat[48] = {
|
||||
1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,0,
|
||||
1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,
|
||||
1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
};
|
||||
|
||||
static const uint8 kIsMusicOwOrDungeon[] = {
|
||||
0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, // 1 = ow songs : 2 = lw, 5 = forest, 7 = town, 9 = dark world, 13 = mountain
|
||||
2, 2, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 0, // 2 = indoor songs : 16, 17, 18, 22, 23, 27
|
||||
};
|
||||
|
||||
|
||||
static const uint8 kMsuDeluxe_OW_Songs[] = {
|
||||
37, 37, 42, 38, 38, 38, 38, 39, 37, 37, 42, 38, 38, 38, 38, 41, // lw
|
||||
42, 42, 42, 42, 42, 42, 40, 40, 43, 43, 42, 47, 47, 42, 45, 45,
|
||||
43, 43, 43, 47, 47, 42, 45, 45, 112, 112, 48, 42, 42, 42, 42, 45,
|
||||
44, 44, 48, 48, 48, 46, 46, 46, 44, 44, 44, 48, 48, 46, 46, 46,
|
||||
49, 49, 51, 50, 50, 50, 50, 50, 49, 49, 51, 50, 50, 50, 50, 51, // dw
|
||||
51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 51, 56, 56, 51, 54, 54,
|
||||
52, 52, 52, 56, 56, 51, 54, 54, 58, 52, 57, 51, 51, 51, 51, 54,
|
||||
53, 53, 57, 57, 57, 55, 55, 110, 53, 53, 57, 57, 57, 55, 55, 110,
|
||||
37, 41, 41, 42, 42, 42, 42, 42, 42, 41, 41, 42, 42, 42, 42, 42, // special
|
||||
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
|
||||
};
|
||||
|
||||
static const uint8 kMsuDeluxe_Entrance_Songs[] = {
|
||||
59, 59, 60, 61, 61, 61, 62, 62, 63, 64, 64, 64, 105, 65, 65, 66,
|
||||
66, 62, 67, 62, 62, 68, 62, 62, 68, 68, 62, 62, 62, 62, 62, 62,
|
||||
62, 62, 62, 62, 69, 70, 71, 72, 73, 73, 73, 106, 102, 74, 62, 62,
|
||||
75, 75, 76, 77, 78, 68, 79, 80, 81, 62, 62, 62, 82, 75, 242, 59,
|
||||
59, 76, 242, 242, 242, 96, 83, 99, 59, 242, 242, 242, 84, 95, 104, 62,
|
||||
85, 62, 62, 86, 242, 67, 103, 83, 83, 87, 76, 88, 81, 98, 81, 88,
|
||||
83, 89, 75, 97, 90, 91, 91, 100, 92, 93, 92, 242, 93, 107, 62, 75,
|
||||
62, 67, 62, 242, 242, 242, 73, 73, 73, 73, 102, 114, 81, 76, 62, 67,
|
||||
62, 61, 94, 62, 103,
|
||||
};
|
||||
|
||||
|
||||
// Remap an track number into a potentially different track number (used for msu deluxe)
|
||||
static uint8 RemapMsuDeluxeTrack(MsuPlayer *mp, uint8 track) {
|
||||
if (!(mp->enabled & kMsuEnabled_MsuDeluxe) || track >= sizeof(kIsMusicOwOrDungeon))
|
||||
return track;
|
||||
switch (kIsMusicOwOrDungeon[track]) {
|
||||
case 1:
|
||||
return BYTE(overworld_area_index) < sizeof(kMsuDeluxe_OW_Songs) ? kMsuDeluxe_OW_Songs[BYTE(overworld_area_index)] : track;
|
||||
case 2:
|
||||
if (which_entrance >= sizeof(kMsuDeluxe_Entrance_Songs) || kMsuDeluxe_Entrance_Songs[which_entrance] == 242)
|
||||
return track;
|
||||
return kMsuDeluxe_Entrance_Songs[which_entrance];
|
||||
default:
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
||||
bool ZeldaIsPlayingMusicTrack(uint8 track) {
|
||||
MsuPlayer *mp = &g_msu_player;
|
||||
if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe)
|
||||
return RemapMsuDeluxeTrack(mp, track) == mp->resume_info.actual_track;
|
||||
else
|
||||
return track == music_unk1;
|
||||
}
|
||||
|
||||
bool ZeldaIsPlayingMusicTrackWithBug(uint8 track) {
|
||||
MsuPlayer *mp = &g_msu_player;
|
||||
if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe)
|
||||
return RemapMsuDeluxeTrack(mp, track) == mp->resume_info.actual_track;
|
||||
else
|
||||
return track == (enhanced_features0 & kFeatures0_MiscBugFixes ? music_unk1 : last_music_control);
|
||||
}
|
||||
|
||||
uint8 ZeldaGetEntranceMusicTrack(int i) {
|
||||
MsuPlayer *mp = &g_msu_player;
|
||||
uint8 rv = kEntranceData_musicTrack[i];
|
||||
|
||||
// For some entrances the original performs a fade out, while msu deluxe has new tracks.
|
||||
if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe) {
|
||||
if (rv == 242 && kMsuDeluxe_Entrance_Songs[which_entrance] != 242)
|
||||
rv = 16;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static const uint8 kVolumeTransitionTarget[4] = { 0, 64, 255, 255};
|
||||
static const uint8 kVolumeTransitionStep[4] = { 7, 3, 3, 24};
|
||||
// These are precomputed in the config parse
|
||||
static float kVolumeTransitionStepFloat[4];
|
||||
static float kVolumeTransitionTargetFloat[4];
|
||||
|
||||
void ZeldaPlayMsuAudioTrack(uint8 music_ctrl) {
|
||||
MsuPlayer *mp = &g_msu_player;
|
||||
if (!mp->enabled) {
|
||||
mp->resume_info.tag = 0;
|
||||
zelda_apu_write(APUI00, music_ctrl);
|
||||
return;
|
||||
}
|
||||
ZeldaApuLock();
|
||||
if ((music_ctrl & 0xf0) != 0xf0)
|
||||
MsuPlayer_Open(mp, music_ctrl, false);
|
||||
else if (music_ctrl >= 0xf1 && music_ctrl <= 0xf3) {
|
||||
mp->volume_target = kVolumeTransitionTargetFloat[music_ctrl - 0xf1];
|
||||
mp->volume_step = kVolumeTransitionStepFloat[music_ctrl - 0xf1];
|
||||
}
|
||||
|
||||
if (mp->state == 0) {
|
||||
zelda_apu_write(APUI00, music_ctrl);
|
||||
} else {
|
||||
zelda_apu_write(APUI00, 0xf0); // pause spc player
|
||||
}
|
||||
ZeldaApuUnlock();
|
||||
}
|
||||
|
||||
static void MsuPlayer_CloseFile(MsuPlayer *mp) {
|
||||
if (mp->f)
|
||||
fclose(mp->f);
|
||||
opus_decoder_destroy(mp->opus);
|
||||
mp->opus = NULL;
|
||||
mp->f = NULL;
|
||||
if (mp->state != kMsuState_FinishedPlaying)
|
||||
mp->state = kMsuState_Idle;
|
||||
memset(&mp->resume_info, 0, sizeof(mp->resume_info));
|
||||
}
|
||||
|
||||
static void MsuPlayer_Open(MsuPlayer *mp, int orig_track, bool resume_from_snapshot) {
|
||||
MsuPlayerResumeInfo resume;
|
||||
int actual_track = RemapMsuDeluxeTrack(mp, orig_track);
|
||||
|
||||
if (!resume_from_snapshot) {
|
||||
resume.tag = 0;
|
||||
// Attempt to resume MSU playback when exiting back to the overworld.
|
||||
if (main_module_index == 9 &&
|
||||
actual_track == ((MsuPlayerResumeInfo *)msu_resume_info_alt)->actual_track && g_config.resume_msu) {
|
||||
memcpy(&resume, msu_resume_info_alt, sizeof(mp->resume_info));
|
||||
}
|
||||
if (mp->state >= kMsuState_Resuming)
|
||||
memcpy(msu_resume_info_alt, &mp->resume_info, sizeof(mp->resume_info));
|
||||
} else {
|
||||
memcpy(&resume, msu_resume_info, sizeof(mp->resume_info));
|
||||
}
|
||||
|
||||
mp->volume_target = kVolumeTransitionTargetFloat[3];
|
||||
mp->volume_step = kVolumeTransitionStepFloat[3];
|
||||
|
||||
mp->state = kMsuState_Idle;
|
||||
MsuPlayer_CloseFile(mp);
|
||||
if (actual_track == 0)
|
||||
return;
|
||||
char fname[256], buf[8];
|
||||
snprintf(fname, sizeof(fname), "%s%d.%s", g_config.msu_path ? g_config.msu_path : "", actual_track, mp->enabled & kMsuEnabled_Opuz ? "opuz" : "pcm");
|
||||
printf("Loading MSU %s\n", fname);
|
||||
mp->f = fopen(fname, "rb");
|
||||
if (mp->f == NULL)
|
||||
goto READ_ERROR;
|
||||
setvbuf(mp->f, NULL, _IOFBF, 16384);
|
||||
if (fread(buf, 1, 8, mp->f) != 8) READ_ERROR: {
|
||||
fprintf(stderr, "Unable to read MSU file %s\n", fname);
|
||||
MsuPlayer_CloseFile(mp);
|
||||
return;
|
||||
}
|
||||
uint32 file_tag = *(uint32 *)(buf + 0);
|
||||
mp->repeat_position = *(uint32 *)(buf + 4);
|
||||
mp->state = (resume.actual_track == actual_track && resume.tag == file_tag) ? kMsuState_Resuming : kMsuState_Playing;
|
||||
if (mp->state == kMsuState_Resuming) {
|
||||
memcpy(&mp->resume_info, &resume, sizeof(mp->resume_info));
|
||||
} else {
|
||||
mp->resume_info.orig_track = orig_track;
|
||||
mp->resume_info.actual_track = actual_track;
|
||||
mp->resume_info.tag = file_tag;
|
||||
mp->resume_info.range_cur = 8;
|
||||
}
|
||||
mp->cur_file_offs = mp->resume_info.offset;
|
||||
mp->samples_until_repeat = mp->resume_info.samples_until_repeat;
|
||||
mp->range_cur = mp->resume_info.range_cur;
|
||||
mp->range_repeat = mp->resume_info.range_repeat;
|
||||
mp->buffer_size = mp->buffer_pos = 0;
|
||||
mp->preskip = 0;
|
||||
if (file_tag == (('Z' << 24) | ('U' << 16) | ('P' << 8) | 'O')) {
|
||||
mp->opus = opus_decoder_create(48000, 2, NULL);
|
||||
if (!mp->opus)
|
||||
goto READ_ERROR;
|
||||
if (mp->state == kMsuState_Resuming)
|
||||
fseek(mp->f, mp->cur_file_offs, SEEK_SET);
|
||||
} else if (file_tag == (('1' << 24) | ('U' << 16) | ('S' << 8) | 'M')) {
|
||||
fseek(mp->f, 0, SEEK_END);
|
||||
mp->total_samples_in_file = (ftell(mp->f) - 8) / 4;
|
||||
mp->samples_until_repeat = mp->total_samples_in_file - mp->cur_file_offs;
|
||||
fseek(mp->f, mp->cur_file_offs * 4 + 8, SEEK_SET);
|
||||
} else {
|
||||
goto READ_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static void MixToBufferWithVolume(int16 *dst, const int16 *src, size_t n, float volume) {
|
||||
if (volume == 1.0f) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
dst[i * 2 + 0] += src[i * 2 + 0];
|
||||
dst[i * 2 + 1] += src[i * 2 + 1];
|
||||
}
|
||||
} else {
|
||||
uint32 vol = (int32)(65536 * volume);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
dst[i * 2 + 0] += src[i * 2 + 0] * vol >> 16;
|
||||
dst[i * 2 + 1] += src[i * 2 + 1] * vol >> 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void MixToBufferWithVolumeRamp(int16 *dst, const int16 *src, size_t n, float volume, float volume_step, float ideal_target) {
|
||||
int64 vol = volume * 281474976710656.0f;
|
||||
int64 step = volume_step * 281474976710656.0f;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
uint32 v = (vol >> 32);
|
||||
dst[i * 2 + 0] += src[i * 2 + 0] * v >> 16;
|
||||
dst[i * 2 + 1] += src[i * 2 + 1] * v >> 16;
|
||||
vol += step;
|
||||
}
|
||||
}
|
||||
|
||||
static void MixToBuffer(MsuPlayer *mp, int16 *dst, const int16 *src, uint32 n) {
|
||||
if (mp->volume != mp->volume_target) {
|
||||
float step = mp->volume < mp->volume_target ? mp->volume_step : -mp->volume_step;
|
||||
float new_vol = mp->volume + step * n;
|
||||
uint32 curn = n;
|
||||
if (step >= 0 ? new_vol >= mp->volume_target : new_vol < mp->volume_target) {
|
||||
uint32 maxn = (uint32)((mp->volume_target - mp->volume) / step);
|
||||
curn = UintMin(maxn, curn);
|
||||
new_vol = mp->volume_target;
|
||||
}
|
||||
float vol = mp->volume;
|
||||
mp->volume = new_vol;
|
||||
MixToBufferWithVolumeRamp(dst, src, curn, vol, step, new_vol);
|
||||
dst += curn * 2, src += curn * 2, n -= curn;
|
||||
}
|
||||
MixToBufferWithVolume(dst, src, n, mp->volume);
|
||||
}
|
||||
|
||||
void MsuPlayer_Mix(MsuPlayer *mp, int16 *audio_buffer, int audio_samples) {
|
||||
int r;
|
||||
|
||||
do {
|
||||
if (mp->buffer_size - mp->buffer_pos == 0) {
|
||||
if (mp->opus != NULL) {
|
||||
if (mp->samples_until_repeat == 0) {
|
||||
if (mp->range_cur == 0) FINISHED_PLAYING: {
|
||||
mp->state = kMsuState_FinishedPlaying;
|
||||
MsuPlayer_CloseFile(mp);
|
||||
return;
|
||||
}
|
||||
opus_decoder_ctl(mp->opus, OPUS_RESET_STATE);
|
||||
fseek(mp->f, mp->range_cur, SEEK_SET);
|
||||
uint8 *file_data = (uint8 *)mp->buffer;
|
||||
if (fread(file_data, 1, 10, mp->f) != 10) READ_ERROR: {
|
||||
fprintf(stderr, "MSU read/decode error!\n");
|
||||
zelda_apu_write(APUI00, mp->resume_info.orig_track);
|
||||
MsuPlayer_CloseFile(mp);
|
||||
return;
|
||||
}
|
||||
uint32 file_offs = *(uint32 *)&file_data[0];
|
||||
assert((file_offs & 0xF0000000) == 0);
|
||||
uint32 samples_until_repeat = *(uint32 *)&file_data[4];
|
||||
uint16 preskip = *(uint32 *)&file_data[8];
|
||||
mp->samples_until_repeat = samples_until_repeat;
|
||||
mp->preskip = preskip & 0x3fff;
|
||||
if (preskip & 0x4000)
|
||||
mp->range_repeat = mp->range_cur;
|
||||
mp->range_cur = (preskip & 0x8000) ? mp->range_repeat : mp->range_cur + 10;
|
||||
mp->cur_file_offs = file_offs;
|
||||
mp->resume_info.range_repeat = mp->range_repeat;
|
||||
mp->resume_info.range_cur = mp->range_cur;
|
||||
fseek(mp->f, file_offs, SEEK_SET);
|
||||
}
|
||||
assert(mp->samples_until_repeat != 0);
|
||||
for (;;) {
|
||||
uint8 *file_data = (uint8 *)mp->buffer;
|
||||
*(uint64 *)file_data = 0;
|
||||
if (fread(file_data, 1, 2, mp->f) != 2)
|
||||
goto READ_ERROR;
|
||||
int size = *(uint16 *)file_data & 0x7fff;
|
||||
if (size > 1275)
|
||||
goto READ_ERROR;
|
||||
int n = (*(uint16 *)file_data >> 15);
|
||||
if (fread(&file_data[2], 1, size, mp->f) != size)
|
||||
goto READ_ERROR;
|
||||
// Verify if the snapshot matches the file on disk.
|
||||
uint64 initial_file_data = *(uint64 *)file_data;
|
||||
if (mp->state == kMsuState_Resuming) {
|
||||
mp->state = kMsuState_Playing;
|
||||
if (mp->resume_info.initial_packet_bytes != initial_file_data)
|
||||
goto READ_ERROR;
|
||||
}
|
||||
mp->resume_info.initial_packet_bytes = initial_file_data;
|
||||
mp->resume_info.samples_until_repeat = mp->samples_until_repeat + mp->preskip;
|
||||
mp->resume_info.offset = mp->cur_file_offs;
|
||||
mp->cur_file_offs += 2 + size;
|
||||
file_data[1] = 0xfc;
|
||||
r = opus_decode(mp->opus, &file_data[2 - n], size + n, mp->buffer, 960, 0);
|
||||
if (r <= 0)
|
||||
goto READ_ERROR;
|
||||
if (r > mp->preskip)
|
||||
break;
|
||||
mp->preskip -= r;
|
||||
}
|
||||
} else {
|
||||
if (mp->samples_until_repeat == 0) {
|
||||
if (mp->resume_info.actual_track < sizeof(kMsuTracksWithRepeat) && !kMsuTracksWithRepeat[mp->resume_info.actual_track])
|
||||
goto FINISHED_PLAYING;
|
||||
mp->samples_until_repeat = mp->total_samples_in_file - mp->repeat_position;
|
||||
if (mp->samples_until_repeat == 0)
|
||||
goto READ_ERROR; // impossible to make progress
|
||||
mp->cur_file_offs = mp->repeat_position;
|
||||
fseek(mp->f, mp->cur_file_offs * 4 + 8, SEEK_SET);
|
||||
}
|
||||
r = UintMin(960, mp->samples_until_repeat);
|
||||
if (fread(mp->buffer, 4, r, mp->f) != r)
|
||||
goto READ_ERROR;
|
||||
mp->resume_info.offset = mp->cur_file_offs;
|
||||
mp->cur_file_offs += r;
|
||||
}
|
||||
uint32 n = UintMin(r - mp->preskip, mp->samples_until_repeat);
|
||||
mp->samples_until_repeat -= n;
|
||||
mp->buffer_pos = mp->preskip;
|
||||
mp->buffer_size = mp->buffer_pos + n;
|
||||
mp->preskip = 0;
|
||||
}
|
||||
#if 0
|
||||
if (mp->samples_to_play > 44100 * 5) {
|
||||
mp->buffer_pos = mp->buffer_size;
|
||||
}
|
||||
#endif
|
||||
int nr = IntMin(audio_samples, mp->buffer_size - mp->buffer_pos);
|
||||
int16 *buf = mp->buffer + mp->buffer_pos * 2;
|
||||
mp->buffer_pos += nr;
|
||||
|
||||
#if 0
|
||||
static int t;
|
||||
for (int i = 0; i < nr; i++) {
|
||||
buf[i * 2 + 0] = buf[i * 2 + 1] = 5000 * sinf(2 * 3.1415 * t++ / 440);
|
||||
}
|
||||
#endif
|
||||
MixToBuffer(mp, audio_buffer, buf, nr);
|
||||
|
||||
#if 0
|
||||
static FILE *f;
|
||||
if (!f)f = fopen("out.pcm", "wb");
|
||||
fwrite(audio_buffer, 4, nr, f);
|
||||
fflush(f);
|
||||
#endif
|
||||
audio_samples -= nr, audio_buffer += nr * 2;
|
||||
} while (audio_samples != 0);
|
||||
}
|
||||
|
||||
// Maintain a queue cause the snes and audio callback are not in sync.
|
||||
struct ApuWriteEnt {
|
||||
uint8 ports[4];
|
||||
};
|
||||
static struct ApuWriteEnt g_apu_write_ents[16], g_apu_write;
|
||||
static uint8 g_apu_write_ent_pos, g_apu_write_count, g_apu_total_write;
|
||||
void zelda_apu_write(uint32_t adr, uint8_t val) {
|
||||
g_apu_write.ports[adr & 0x3] = val;
|
||||
}
|
||||
|
||||
void ZeldaPushApuState() {
|
||||
ZeldaApuLock();
|
||||
g_apu_write_ents[g_apu_write_ent_pos++ & 0xf] = g_apu_write;
|
||||
if (g_apu_write_count < 16)
|
||||
g_apu_write_count++;
|
||||
g_apu_total_write++;
|
||||
ZeldaApuUnlock();
|
||||
}
|
||||
|
||||
static void ZeldaPopApuState() {
|
||||
if (g_apu_write_count != 0)
|
||||
memcpy(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count--) & 0xf], 4);
|
||||
}
|
||||
|
||||
void ZeldaDiscardUnusedAudioFrames() {
|
||||
if (g_apu_write_count != 0 && memcmp(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count) & 0xf], 4) == 0) {
|
||||
if (g_apu_total_write >= 16) {
|
||||
g_apu_total_write = 14;
|
||||
g_apu_write_count--;
|
||||
}
|
||||
} else {
|
||||
g_apu_total_write = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void ZeldaResetApuQueue() {
|
||||
g_apu_write_ent_pos = g_apu_total_write = g_apu_write_count = 0;
|
||||
}
|
||||
|
||||
uint8_t zelda_read_apui00() {
|
||||
// This needs to be here because the ancilla code reads
|
||||
// from the apu and we don't want to make the core code
|
||||
// dependent on the apu timings, so relocated this value
|
||||
// to 0x648.
|
||||
return g_ram[kRam_APUI00];
|
||||
}
|
||||
|
||||
uint8_t zelda_apu_read(uint32_t adr) {
|
||||
return g_zenv.player->port_to_snes[adr & 0x3];
|
||||
}
|
||||
|
||||
void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels) {
|
||||
ZeldaApuLock();
|
||||
ZeldaPopApuState();
|
||||
SpcPlayer_GenerateSamples(g_zenv.player);
|
||||
dsp_getSamples(g_zenv.player->dsp, audio_buffer, samples, channels);
|
||||
if (g_msu_player.f && channels == 2)
|
||||
MsuPlayer_Mix(&g_msu_player, audio_buffer, samples);
|
||||
ZeldaApuUnlock();
|
||||
}
|
||||
|
||||
bool ZeldaIsMusicPlaying() {
|
||||
if (g_msu_player.state != kMsuState_Idle) {
|
||||
return g_msu_player.state != kMsuState_FinishedPlaying;
|
||||
} else {
|
||||
return g_zenv.player->port_to_snes[0] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ZeldaRestoreMusicAfterLoad_Locked(bool is_reset) {
|
||||
// Restore spc variables from the ram dump.
|
||||
SpcPlayer_CopyVariablesFromRam(g_zenv.player);
|
||||
// This is not stored in the snapshot
|
||||
g_zenv.player->timer_cycles = 0;
|
||||
|
||||
// Restore input ports state
|
||||
SpcPlayer *spc_player = g_zenv.player;
|
||||
memcpy(spc_player->input_ports, &spc_player->ram[0x410], 4);
|
||||
memcpy(g_apu_write.ports, spc_player->input_ports, 4);
|
||||
|
||||
if (is_reset) {
|
||||
SpcPlayer_Initialize(g_zenv.player);
|
||||
}
|
||||
|
||||
MsuPlayer *mp = &g_msu_player;
|
||||
if (mp->enabled) {
|
||||
mp->volume = 0.0;
|
||||
MsuPlayer_Open(mp, (music_unk1 == 0xf1) ? ((MsuPlayerResumeInfo*)msu_resume_info)->orig_track :
|
||||
music_unk1, true);
|
||||
|
||||
// If resuming in the middle of a transition, then override
|
||||
// the volume with that of the transition.
|
||||
if (last_music_control >= 0xf1 && last_music_control <= 0xf3) {
|
||||
uint8 target = kVolumeTransitionTarget[last_music_control - 0xf1];
|
||||
if (target != msu_volume) {
|
||||
float f = kVolumeTransitionTargetFloat[3] * (1.0f / 255);
|
||||
mp->volume = msu_volume * f;
|
||||
mp->volume_target = target * f;
|
||||
mp->volume_step = kVolumeTransitionStepFloat[last_music_control - 0xf1];
|
||||
}
|
||||
}
|
||||
|
||||
if (g_msu_player.state)
|
||||
zelda_apu_write(APUI00, 0xf0); // pause spc player
|
||||
}
|
||||
ZeldaResetApuQueue();
|
||||
}
|
||||
|
||||
void ZeldaSaveMusicStateToRam_Locked() {
|
||||
SpcPlayer_CopyVariablesToRam(g_zenv.player);
|
||||
// SpcPlayer.input_ports is not saved to the SpcPlayer ram by SpcPlayer_CopyVariablesToRam,
|
||||
// in any case, we want to save the most recently written data, and that might still
|
||||
// be in the queue. 0x410 is a free memory location in the SPC ram, so store it there.
|
||||
SpcPlayer *spc_player = g_zenv.player;
|
||||
memcpy(&spc_player->ram[0x410], g_apu_write.ports, 4);
|
||||
|
||||
msu_volume = g_msu_player.volume * 255;
|
||||
memcpy(msu_resume_info, &g_msu_player.resume_info, sizeof(g_msu_player.resume_info));
|
||||
}
|
||||
|
||||
void ZeldaEnableMsu(uint8 enable) {
|
||||
g_msu_player.volume = 1.0f;
|
||||
g_msu_player.enabled = enable;
|
||||
if (enable & kMsuEnabled_Opuz) {
|
||||
if (g_config.audio_freq != 48000)
|
||||
fprintf(stderr, "Warning: MSU Opuz requires: AudioFreq = 48000\n");
|
||||
} else if (enable) {
|
||||
if (g_config.audio_freq != 44100)
|
||||
fprintf(stderr, "Warning: MSU requires: AudioFreq = 44100\n");
|
||||
}
|
||||
|
||||
float volscale = g_config.msuvolume * (1.0f / 255 / 100);
|
||||
float stepscale = g_config.msuvolume * (60.0f / 256 / 100) / g_config.audio_freq;
|
||||
for (size_t i = 0; i != countof(kVolumeTransitionStepFloat); i++) {
|
||||
kVolumeTransitionStepFloat[i] = kVolumeTransitionStep[i] * stepscale;
|
||||
kVolumeTransitionTargetFloat[i] = kVolumeTransitionTarget[i] * volscale;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSongBank(const uint8 *p) { // 808888
|
||||
ZeldaApuLock();
|
||||
SpcPlayer_Upload(g_zenv.player, p);
|
||||
ZeldaApuUnlock();
|
||||
}
|
||||
20
src/audio.h
Normal file
20
src/audio.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef ZELDA3_AUDIO_H_
|
||||
#define ZELDA3_AUDIO_H_
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Things for msu
|
||||
bool ZeldaIsPlayingMusicTrack(uint8 track);
|
||||
bool ZeldaIsPlayingMusicTrackWithBug(uint8 track);
|
||||
void ZeldaPlayMsuAudioTrack(uint8 track);
|
||||
bool ZeldaIsMusicPlaying();
|
||||
|
||||
void ZeldaEnableMsu(uint8 enable);
|
||||
|
||||
void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels);
|
||||
void ZeldaDiscardUnusedAudioFrames();
|
||||
void ZeldaRestoreMusicAfterLoad_Locked(bool is_reset);
|
||||
void ZeldaSaveMusicStateToRam_Locked();
|
||||
void ZeldaPushApuState();
|
||||
|
||||
#endif // ZELDA3_AUDIO_H_
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include "features.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
enum {
|
||||
kKeyMod_ScanCode = 0x200,
|
||||
@@ -22,6 +22,7 @@ Config g_config;
|
||||
#define C(x) REMAP_SDL_KEYCODE(x) | kKeyMod_Ctrl
|
||||
#define N 0
|
||||
static const uint16 kDefaultKbdControls[kKeys_Total] = {
|
||||
0,
|
||||
// Controls
|
||||
_(SDLK_UP), _(SDLK_DOWN), _(SDLK_LEFT), _(SDLK_RIGHT), _(SDLK_RSHIFT), _(SDLK_RETURN), _(SDLK_x), _(SDLK_z), _(SDLK_s), _(SDLK_a), _(SDLK_c), _(SDLK_v),
|
||||
// LoadState
|
||||
@@ -31,9 +32,9 @@ static const uint16 kDefaultKbdControls[kKeys_Total] = {
|
||||
// Replay State
|
||||
C(SDLK_F1), C(SDLK_F2), C(SDLK_F3), C(SDLK_F4), C(SDLK_F5), C(SDLK_F6), C(SDLK_F7), C(SDLK_F8), C(SDLK_F9), C(SDLK_F10), N, N, N, N, N, N, N, N, N, N,
|
||||
// Load Ref State
|
||||
_(SDLK_1), _(SDLK_2), _(SDLK_3), _(SDLK_4), _(SDLK_5), _(SDLK_6), _(SDLK_7), _(SDLK_8), _(SDLK_9), _(SDLK_0), _(SDLK_MINUS), _(SDLK_EQUALS), _(SDLK_BACKSPACE), N, N, N, N, N, N, N,
|
||||
N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
|
||||
// Replay Ref State
|
||||
C(SDLK_1), C(SDLK_2), C(SDLK_3), C(SDLK_4), C(SDLK_5), C(SDLK_6), C(SDLK_7), C(SDLK_8), C(SDLK_9), C(SDLK_0), C(SDLK_MINUS), C(SDLK_EQUALS), C(SDLK_BACKSPACE), N, N, N, N, N, N, N,
|
||||
N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N,
|
||||
// CheatLife, CheatKeys, CheatEquipment, CheatWalkThroughWalls
|
||||
_(SDLK_w), _(SDLK_o), S(SDLK_w), C(SDLK_e),
|
||||
// ClearKeyLog, StopReplay, Fullscreen, Reset, Pause, PauseDimmed, Turbo, ReplayTurbo, WindowBigger, WindowSmaller, DisplayPerf, ToggleRenderer
|
||||
@@ -53,6 +54,7 @@ typedef struct KeyNameId {
|
||||
#define M(n) {#n, kKeys_##n, kKeys_##n##_Last - kKeys_##n + 1}
|
||||
#define S(n) {#n, kKeys_##n, 1}
|
||||
static const KeyNameId kKeyNameId[] = {
|
||||
{"Null", kKeys_Null, 65535},
|
||||
M(Controls), M(Load), M(Save), M(Replay), M(LoadRef), M(ReplayRef),
|
||||
S(CheatLife), S(CheatKeys), S(CheatEquipment), S(CheatWalkThroughWalls),
|
||||
S(ClearKeyLog), S(StopReplay), S(Fullscreen), S(Reset),
|
||||
@@ -69,7 +71,7 @@ static KeyMapHashEnt *keymap_hash;
|
||||
static int keymap_hash_size;
|
||||
static bool has_keynameid[countof(kKeyNameId)];
|
||||
|
||||
bool KeyMapHash_Add(uint16 key, uint16 cmd) {
|
||||
static bool KeyMapHash_Add(uint16 key, uint16 cmd) {
|
||||
if ((keymap_hash_size & 0xff) == 0) {
|
||||
if (keymap_hash_size > 10000)
|
||||
Die("Too many keys");
|
||||
@@ -101,58 +103,27 @@ static int KeyMapHash_Find(uint16 key) {
|
||||
return ent->cmd;
|
||||
i = ent->next;
|
||||
}
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod) {
|
||||
if (code & ~(SDLK_SCANCODE_MASK | 0x1ff))
|
||||
return -1;
|
||||
int key = mod & KMOD_ALT ? kKeyMod_Alt : 0;
|
||||
key |= mod & KMOD_CTRL ? kKeyMod_Ctrl : 0;
|
||||
key |= mod & KMOD_SHIFT ? kKeyMod_Shift : 0;
|
||||
return 0;
|
||||
int key = 0;
|
||||
if (code != SDLK_LALT && code != SDLK_RALT)
|
||||
key |= mod & KMOD_ALT ? kKeyMod_Alt : 0;
|
||||
if (code != SDLK_LCTRL && code != SDLK_RCTRL)
|
||||
key |= mod & KMOD_CTRL ? kKeyMod_Ctrl : 0;
|
||||
if (code != SDLK_LSHIFT && code != SDLK_RSHIFT)
|
||||
key |= mod & KMOD_SHIFT ? kKeyMod_Shift : 0;
|
||||
key |= REMAP_SDL_KEYCODE(code);
|
||||
return KeyMapHash_Find(key);
|
||||
}
|
||||
|
||||
static char *NextDelim(char **s, int sep) {
|
||||
char *r = *s;
|
||||
if (r) {
|
||||
while (r[0] == ' ' || r[0] == '\t')
|
||||
r++;
|
||||
char *t = strchr(r, sep);
|
||||
*s = t ? (*t++ = 0, t) : NULL;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline int ToLower(int a) {
|
||||
return a + (a >= 'A' && a <= 'Z') * 32;
|
||||
}
|
||||
|
||||
static bool StringEqualsNoCase(const char *a, const char *b) {
|
||||
for (;;) {
|
||||
int aa = ToLower(*a++), bb = ToLower(*b++);
|
||||
if (aa != bb)
|
||||
return false;
|
||||
if (aa == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool StringStartsWithNoCase(const char *a, const char *b) {
|
||||
for (;;) {
|
||||
int aa = ToLower(*a++), bb = ToLower(*b++);
|
||||
if (bb == 0)
|
||||
return true;
|
||||
if (aa != bb)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void ParseKeyArray(char *value, int cmd, int size) {
|
||||
char *s;
|
||||
int i = 0;
|
||||
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd++) {
|
||||
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd += (cmd != 0)) {
|
||||
if (*s == 0)
|
||||
continue;
|
||||
int key_with_mod = 0;
|
||||
@@ -177,18 +148,122 @@ static void ParseKeyArray(char *value, int cmd, int size) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct GamepadMapEnt {
|
||||
uint32 modifiers;
|
||||
uint16 cmd, next;
|
||||
} GamepadMapEnt;
|
||||
|
||||
static uint16 joymap_first[kGamepadBtn_Count];
|
||||
static GamepadMapEnt *joymap_ents;
|
||||
static int joymap_size;
|
||||
static bool has_joypad_controls;
|
||||
|
||||
static int CountBits32(uint32 n) {
|
||||
int count = 0;
|
||||
for (; n != 0; count++)
|
||||
n &= (n - 1);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void GamepadMap_Add(int button, uint32 modifiers, uint16 cmd) {
|
||||
if ((joymap_size & 0xff) == 0) {
|
||||
if (joymap_size > 1000)
|
||||
Die("Too many joypad keys");
|
||||
joymap_ents = realloc(joymap_ents, sizeof(GamepadMapEnt) * (joymap_size + 64));
|
||||
if (!joymap_ents) Die("realloc failure");
|
||||
}
|
||||
uint16 *p = &joymap_first[button];
|
||||
// Insert it as early as possible but before after any entry with more modifiers.
|
||||
int cb = CountBits32(modifiers);
|
||||
while (*p && cb < CountBits32(joymap_ents[*p - 1].modifiers))
|
||||
p = &joymap_ents[*p - 1].next;
|
||||
int i = joymap_size++;
|
||||
GamepadMapEnt *ent = &joymap_ents[i];
|
||||
ent->modifiers = modifiers;
|
||||
ent->cmd = cmd;
|
||||
ent->next = *p;
|
||||
*p = i + 1;
|
||||
}
|
||||
|
||||
int FindCmdForGamepadButton(int button, uint32 modifiers) {
|
||||
GamepadMapEnt *ent;
|
||||
for(int e = joymap_first[button]; e != 0; e = ent->next) {
|
||||
ent = &joymap_ents[e - 1];
|
||||
if ((modifiers & ent->modifiers) == ent->modifiers)
|
||||
return ent->cmd;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ParseGamepadButtonName(const char **value) {
|
||||
const char *s = *value;
|
||||
// Longest substring first
|
||||
static const char *const kGamepadKeyNames[] = {
|
||||
"Back", "Guide", "Start", "L3", "R3",
|
||||
"L1", "R1", "DpadUp", "DpadDown", "DpadLeft", "DpadRight", "L2", "R2",
|
||||
"Lb", "Rb", "A", "B", "X", "Y"
|
||||
};
|
||||
static const uint8 kGamepadKeyIds[] = {
|
||||
kGamepadBtn_Back, kGamepadBtn_Guide, kGamepadBtn_Start, kGamepadBtn_L3, kGamepadBtn_R3,
|
||||
kGamepadBtn_L1, kGamepadBtn_R1, kGamepadBtn_DpadUp, kGamepadBtn_DpadDown, kGamepadBtn_DpadLeft, kGamepadBtn_DpadRight, kGamepadBtn_L2, kGamepadBtn_R2,
|
||||
kGamepadBtn_L1, kGamepadBtn_R1, kGamepadBtn_A, kGamepadBtn_B, kGamepadBtn_X, kGamepadBtn_Y,
|
||||
};
|
||||
for (size_t i = 0; i != countof(kGamepadKeyNames); i++) {
|
||||
const char *r = StringStartsWithNoCase(s, kGamepadKeyNames[i]);
|
||||
if (r) {
|
||||
*value = r;
|
||||
return kGamepadKeyIds[i];
|
||||
}
|
||||
}
|
||||
return kGamepadBtn_Invalid;
|
||||
}
|
||||
|
||||
static const uint8 kDefaultGamepadCmds[] = {
|
||||
kGamepadBtn_DpadUp, kGamepadBtn_DpadDown, kGamepadBtn_DpadLeft, kGamepadBtn_DpadRight, kGamepadBtn_Back, kGamepadBtn_Start,
|
||||
kGamepadBtn_B, kGamepadBtn_A, kGamepadBtn_Y, kGamepadBtn_X, kGamepadBtn_L1, kGamepadBtn_R1,
|
||||
};
|
||||
|
||||
static void ParseGamepadArray(char *value, int cmd, int size) {
|
||||
char *s;
|
||||
int i = 0;
|
||||
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd += (cmd != 0)) {
|
||||
if (*s == 0)
|
||||
continue;
|
||||
uint32 modifiers = 0;
|
||||
const char *ss = s;
|
||||
for (;;) {
|
||||
int button = ParseGamepadButtonName(&ss);
|
||||
if (button == kGamepadBtn_Invalid) BAD: {
|
||||
fprintf(stderr, "Unknown gamepad button: '%s'\n", s);
|
||||
break;
|
||||
}
|
||||
while (*ss == ' ' || *ss == '\t') ss++;
|
||||
if (*ss == '+') {
|
||||
ss++;
|
||||
modifiers |= 1 << button;
|
||||
} else if (*ss == 0) {
|
||||
GamepadMap_Add(button, modifiers, cmd);
|
||||
break;
|
||||
} else
|
||||
goto BAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RegisterDefaultKeys() {
|
||||
for (int i = 0; i < countof(kKeyNameId); i++) {
|
||||
for (int i = 1; i < countof(kKeyNameId); i++) {
|
||||
if (!has_keynameid[i]) {
|
||||
int size = kKeyNameId[i].size, k = kKeyNameId[i].id;
|
||||
for (int j = 0; j < size; j++, k++)
|
||||
KeyMapHash_Add(kDefaultKbdControls[k], k);
|
||||
}
|
||||
}
|
||||
if (!has_joypad_controls) {
|
||||
for (int i = 0; i < countof(kDefaultGamepadCmds); i++)
|
||||
GamepadMap_Add(kDefaultGamepadCmds[i], 0, kKeys_Controls + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int GetIniSection(const char *s) {
|
||||
if (StringEqualsNoCase(s, "[KeyMap]"))
|
||||
return 0;
|
||||
@@ -200,18 +275,31 @@ static int GetIniSection(const char *s) {
|
||||
return 3;
|
||||
if (StringEqualsNoCase(s, "[Features]"))
|
||||
return 4;
|
||||
if (StringEqualsNoCase(s, "[GamepadMap]"))
|
||||
return 5;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool ParseBool(const char *value, bool *result) {
|
||||
if (StringEqualsNoCase(value, "0") || StringEqualsNoCase(value, "false")) {
|
||||
*result = false;
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(value, "1") || StringEqualsNoCase(value, "true")) {
|
||||
*result = true;
|
||||
bool ParseBool(const char *value, bool *result) {
|
||||
bool rv = false;
|
||||
switch (*value++ | 32) {
|
||||
case '0': if (*value == 0) break; return false;
|
||||
case 'f': if (StringEqualsNoCase(value, "alse")) break; return false;
|
||||
case 'n': if (StringEqualsNoCase(value, "o")) break; return false;
|
||||
case 'o':
|
||||
rv = (*value | 32) == 'n';
|
||||
if (StringEqualsNoCase(value, rv ? "n" : "ff")) break;
|
||||
return false;
|
||||
case '1': rv = true; if (*value == 0) break; return false;
|
||||
case 'y': rv = true; if (StringEqualsNoCase(value, "es")) break; return false;
|
||||
case 't': rv = true; if (StringEqualsNoCase(value, "rue")) break; return false;
|
||||
default: return false;
|
||||
}
|
||||
if (result) {
|
||||
*result = rv;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool ParseBoolBit(const char *value, uint32 *data, uint32 mask) {
|
||||
@@ -231,6 +319,15 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (section == 5) {
|
||||
for (int i = 0; i < countof(kKeyNameId); i++) {
|
||||
if (StringEqualsNoCase(key, kKeyNameId[i].name)) {
|
||||
if (i == 1)
|
||||
has_joypad_controls = true;
|
||||
ParseGamepadArray(value, kKeyNameId[i].id, kKeyNameId[i].size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (section == 1) {
|
||||
if (StringEqualsNoCase(key, "WindowSize")) {
|
||||
char *s;
|
||||
@@ -259,11 +356,24 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
|
||||
} else if (StringEqualsNoCase(key, "WindowScale")) {
|
||||
g_config.window_scale = (uint8)strtol(value, (char**)NULL, 10);
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "OutputMethod")) {
|
||||
g_config.output_method = StringEqualsNoCase(value, "SDL-Software") ? kOutputMethod_SDLSoftware :
|
||||
StringEqualsNoCase(value, "OpenGL") ? kOutputMethod_OpenGL :
|
||||
StringEqualsNoCase(value, "OpenGL ES") ? kOutputMethod_OpenGL_ES :
|
||||
kOutputMethod_SDL;
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "LinearFiltering")) {
|
||||
return ParseBool(value, &g_config.linear_filtering);
|
||||
} else if (StringEqualsNoCase(key, "NoSpriteLimits")) {
|
||||
return ParseBool(value, &g_config.no_sprite_limits);
|
||||
} else if (StringEqualsNoCase(key, "LinkGraphics")) {
|
||||
g_config.link_graphics = value;
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "Shader")) {
|
||||
g_config.shader = *value ? value : NULL;
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "DimFlashes")) {
|
||||
return ParseBoolBit(value, &g_config.features0, kFeatures0_DimFlashes);
|
||||
}
|
||||
} else if (section == 2) {
|
||||
if (StringEqualsNoCase(key, "EnableAudio")) {
|
||||
@@ -278,7 +388,23 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
|
||||
g_config.audio_samples = (uint16)strtol(value, (char**)NULL, 10);
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "EnableMSU")) {
|
||||
return ParseBool(value, &g_config.enable_msu);
|
||||
if (StringEqualsNoCase(value, "opuz"))
|
||||
g_config.enable_msu = kMsuEnabled_Opuz;
|
||||
else if (StringEqualsNoCase(value, "deluxe"))
|
||||
g_config.enable_msu = kMsuEnabled_MsuDeluxe;
|
||||
else if (StringEqualsNoCase(value, "deluxe-opuz"))
|
||||
g_config.enable_msu = kMsuEnabled_MsuDeluxe | kMsuEnabled_Opuz;
|
||||
else
|
||||
return ParseBool(value, (bool*)&g_config.enable_msu);
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "MSUPath")) {
|
||||
g_config.msu_path = value;
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "MSUVolume")) {
|
||||
g_config.msuvolume = atoi(value);
|
||||
return true;
|
||||
} else if (StringEqualsNoCase(key, "ResumeMSU")) {
|
||||
return ParseBool(value, &g_config.resume_msu);
|
||||
}
|
||||
} else if (section == 3) {
|
||||
if (StringEqualsNoCase(key, "Autosave")) {
|
||||
@@ -316,10 +442,15 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
|
||||
return ParseBool(value, &g_config.display_perf_title);
|
||||
} else if (StringEqualsNoCase(key, "DisableFrameDelay")) {
|
||||
return ParseBool(value, &g_config.disable_frame_delay);
|
||||
} else if (StringEqualsNoCase(key, "Language")) {
|
||||
g_config.language = value;
|
||||
return true;
|
||||
}
|
||||
} else if (section == 4) {
|
||||
if (StringEqualsNoCase(key, "ItemSwitchLR")) {
|
||||
return ParseBoolBit(value, &g_config.features0, kFeatures0_SwitchLR);
|
||||
} else if (StringEqualsNoCase(key, "ItemSwitchLRLimit")) {
|
||||
return ParseBoolBit(value, &g_config.features0, kFeatures0_SwitchLRLimit);
|
||||
} else if (StringEqualsNoCase(key, "TurnWhileDashing")) {
|
||||
return ParseBoolBit(value, &g_config.features0, kFeatures0_TurnWhileDashing);
|
||||
} else if (StringEqualsNoCase(key, "MirrorToDarkworld")) {
|
||||
@@ -349,79 +480,50 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint8 *ReadFile(const char *name, size_t *length) {
|
||||
FILE *f = fopen(name, "rb");
|
||||
if (f == NULL)
|
||||
return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
size_t size = ftell(f);
|
||||
rewind(f);
|
||||
uint8 *buffer = (uint8 *)malloc(size + 1);
|
||||
if (!buffer) Die("malloc failed");
|
||||
// Always zero terminate so this function can be used also for strings.
|
||||
buffer[size] = 0;
|
||||
if (fread(buffer, 1, size, f) != size)
|
||||
Die("fread failed");
|
||||
fclose(f);
|
||||
if (length) *length = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ParseConfigFile() {
|
||||
uint8 *file = ReadFile("zelda3.user.ini", NULL);
|
||||
if (!file) {
|
||||
file = ReadFile("zelda3.ini", NULL);
|
||||
if (!file)
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "Loading zelda3.ini\n");
|
||||
static bool ParseOneConfigFile(const char *filename, int depth) {
|
||||
char *filedata = (char*)ReadWholeFile(filename, NULL), *p;
|
||||
if (!filedata)
|
||||
return false;
|
||||
|
||||
int section = -2;
|
||||
int lineno = 1;
|
||||
char *p, *next_p = (char*)file;
|
||||
for (; (p = next_p) != NULL; lineno++) {
|
||||
// find end of line
|
||||
char *eol = strchr(p, '\n');
|
||||
next_p = eol ? eol + 1 : NULL;
|
||||
eol = eol ? eol : p + strlen(p);
|
||||
// strip comments
|
||||
char *comment = memchr(p, '#', eol - p);
|
||||
eol = (comment != 0) ? comment : eol;
|
||||
// strip trailing whitespace
|
||||
while (eol > p && (eol[-1] == '\r' || eol[-1] == ' ' || eol[-1] == '\t'))
|
||||
eol--;
|
||||
*eol = 0;
|
||||
if (p == eol)
|
||||
g_config.memory_buffer = filedata;
|
||||
|
||||
for (int lineno = 1; (p = NextLineStripComments(&filedata)) != NULL; lineno++) {
|
||||
if (*p == 0)
|
||||
continue; // empty line
|
||||
// strip leading whitespace
|
||||
while (p[0] == ' ' || p[0] == '\t')
|
||||
p++;
|
||||
if (*p == '[') {
|
||||
section = GetIniSection(p);
|
||||
if (section < 0)
|
||||
fprintf(stderr, "zelda3.ini:%d: Invalid .ini section %s\n", lineno, p);
|
||||
fprintf(stderr, "%s:%d: Invalid .ini section %s\n", filename, lineno, p);
|
||||
} else if (*p == '!' && SkipPrefix(p + 1, "include ")) {
|
||||
char *tt = p + 8;
|
||||
char *new_filename = ReplaceFilenameWithNewPath(filename, NextPossiblyQuotedString(&tt));
|
||||
if (depth > 10 || !ParseOneConfigFile(new_filename, depth + 1))
|
||||
fprintf(stderr, "Warning: Unable to read %s\n", new_filename);
|
||||
free(new_filename);
|
||||
} else if (section == -2) {
|
||||
fprintf(stderr, "zelda3.ini:%d: Expecting [section]\n", lineno);
|
||||
fprintf(stderr, "%s:%d: Expecting [section]\n", filename, lineno);
|
||||
} else {
|
||||
char *equals = memchr(p, '=', eol - p);
|
||||
if (equals == NULL) {
|
||||
fprintf(stderr, "zelda3.ini:%d: Expecting 'key=value'\n", lineno);
|
||||
} else {
|
||||
char *kr = equals;
|
||||
while (kr > p && (kr[-1] == ' ' || kr[-1] == '\t'))
|
||||
kr--;
|
||||
*kr = 0;
|
||||
char *v = equals + 1;
|
||||
while (v[0] == ' ' || v[0] == '\t')
|
||||
v++;
|
||||
if (section >= 0 && !HandleIniConfig(section, p, v))
|
||||
fprintf(stderr, "zelda3.ini:%d: Can't parse '%s'\n", lineno, p);
|
||||
char *v = SplitKeyValue(p);
|
||||
if (v == NULL) {
|
||||
fprintf(stderr, "%s:%d: Expecting 'key=value'\n", filename, lineno);
|
||||
continue;
|
||||
}
|
||||
if (section >= 0 && !HandleIniConfig(section, p, v))
|
||||
fprintf(stderr, "%s:%d: Can't parse '%s'\n", filename, lineno, p);
|
||||
}
|
||||
}
|
||||
g_config.memory_buffer = file;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AfterConfigParse() {
|
||||
void ParseConfigFile(const char *filename) {
|
||||
g_config.msuvolume = 100; // default msu volume, 100%
|
||||
|
||||
if (filename != NULL || !ParseOneConfigFile("zelda3.user.ini", 0)) {
|
||||
if (filename == NULL)
|
||||
filename = "zelda3.ini";
|
||||
if (!ParseOneConfigFile(filename, 0))
|
||||
fprintf(stderr, "Warning: Unable to read config file %s\n", filename);
|
||||
}
|
||||
RegisterDefaultKeys();
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
#include <SDL_keycode.h>
|
||||
|
||||
enum {
|
||||
kKeys_Controls = 0,
|
||||
kKeys_Null,
|
||||
kKeys_Controls,
|
||||
kKeys_Controls_Last = kKeys_Controls + 11,
|
||||
kKeys_Load,
|
||||
kKeys_Load_Last = kKeys_Load + 19,
|
||||
@@ -36,6 +37,13 @@ enum {
|
||||
kKeys_Total,
|
||||
};
|
||||
|
||||
enum {
|
||||
kOutputMethod_SDL,
|
||||
kOutputMethod_SDLSoftware,
|
||||
kOutputMethod_OpenGL,
|
||||
kOutputMethod_OpenGL_ES,
|
||||
};
|
||||
|
||||
typedef struct Config {
|
||||
int window_width;
|
||||
int window_height;
|
||||
@@ -45,6 +53,8 @@ typedef struct Config {
|
||||
uint8 fullscreen;
|
||||
uint8 window_scale;
|
||||
bool enable_audio;
|
||||
bool linear_filtering;
|
||||
uint8 output_method;
|
||||
uint16 audio_freq;
|
||||
uint8 audio_channels;
|
||||
uint16 audio_samples;
|
||||
@@ -53,18 +63,48 @@ typedef struct Config {
|
||||
bool extend_y;
|
||||
bool no_sprite_limits;
|
||||
bool display_perf_title;
|
||||
bool enable_msu;
|
||||
uint8 enable_msu;
|
||||
bool resume_msu;
|
||||
bool disable_frame_delay;
|
||||
uint8 msuvolume;
|
||||
uint32 features0;
|
||||
|
||||
const char *link_graphics;
|
||||
uint8 *memory_buffer;
|
||||
char *memory_buffer;
|
||||
const char *shader;
|
||||
const char *msu_path;
|
||||
const char *language;
|
||||
} Config;
|
||||
|
||||
enum {
|
||||
kMsuEnabled_Msu = 1,
|
||||
kMsuEnabled_MsuDeluxe = 2,
|
||||
kMsuEnabled_Opuz = 4,
|
||||
};
|
||||
enum {
|
||||
kGamepadBtn_Invalid = -1,
|
||||
kGamepadBtn_A,
|
||||
kGamepadBtn_B,
|
||||
kGamepadBtn_X,
|
||||
kGamepadBtn_Y,
|
||||
kGamepadBtn_Back,
|
||||
kGamepadBtn_Guide,
|
||||
kGamepadBtn_Start,
|
||||
kGamepadBtn_L3,
|
||||
kGamepadBtn_R3,
|
||||
kGamepadBtn_L1,
|
||||
kGamepadBtn_R1,
|
||||
kGamepadBtn_DpadUp,
|
||||
kGamepadBtn_DpadDown,
|
||||
kGamepadBtn_DpadLeft,
|
||||
kGamepadBtn_DpadRight,
|
||||
kGamepadBtn_L2,
|
||||
kGamepadBtn_R2,
|
||||
kGamepadBtn_Count,
|
||||
};
|
||||
|
||||
extern Config g_config;
|
||||
|
||||
uint8 *ReadFile(const char *name, size_t *length);
|
||||
void ParseConfigFile();
|
||||
void AfterConfigParse();
|
||||
|
||||
int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod);
|
||||
void ParseConfigFile(const char *filename);
|
||||
int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod);
|
||||
int FindCmdForGamepadButton(int button, uint32 modifiers);
|
||||
@@ -2052,13 +2052,14 @@ void RoomBounds_SubA(RoomBounds *r) {
|
||||
}
|
||||
|
||||
void Dungeon_StartInterRoomTrans_Left() {
|
||||
assert(submodule_index == 0);
|
||||
link_quadrant_x ^= 1;
|
||||
Dungeon_AdjustQuadrant();
|
||||
RoomBounds_SubA(&room_bounds_x);
|
||||
Dung_SaveDataForCurrentRoom();
|
||||
DungeonTransition_AdjustCamera_X(link_quadrant_x ^ 1);
|
||||
HandleEdgeTransition_AdjustCameraBoundaries(3);
|
||||
submodule_index++;
|
||||
submodule_index = 1;
|
||||
if (link_quadrant_x) {
|
||||
RoomBounds_SubB(&room_bounds_x);
|
||||
BYTE(dungeon_room_index_prev) = dungeon_room_index;
|
||||
@@ -2072,7 +2073,7 @@ void Dungeon_StartInterRoomTrans_Left() {
|
||||
}
|
||||
dungeon_room_index--;
|
||||
}
|
||||
submodule_index += 1;
|
||||
submodule_index = 2;
|
||||
if (room_transitioning_flags & 1) {
|
||||
link_is_on_lower_level ^= 1;
|
||||
link_is_on_lower_level_mirror = link_is_on_lower_level;
|
||||
@@ -2091,13 +2092,14 @@ void Dung_StartInterRoomTrans_Left_Plus() {
|
||||
}
|
||||
|
||||
void Dungeon_StartInterRoomTrans_Up() {
|
||||
assert(submodule_index == 0);
|
||||
link_quadrant_y ^= 2;
|
||||
Dungeon_AdjustQuadrant();
|
||||
RoomBounds_SubA(&room_bounds_y);
|
||||
Dung_SaveDataForCurrentRoom();
|
||||
DungeonTransition_AdjustCamera_Y(link_quadrant_y ^ 2);
|
||||
HandleEdgeTransition_AdjustCameraBoundaries(1);
|
||||
submodule_index++;
|
||||
submodule_index = 1;
|
||||
if (link_quadrant_y) {
|
||||
RoomBounds_SubB(&room_bounds_y);
|
||||
BYTE(dungeon_room_index_prev) = dungeon_room_index;
|
||||
@@ -2119,7 +2121,7 @@ void Dungeon_StartInterRoomTrans_Up() {
|
||||
Dungeon_AdjustAfterSpiralStairs();
|
||||
}
|
||||
BYTE(dungeon_room_index) -= 0x10;
|
||||
submodule_index += 1;
|
||||
submodule_index = 2;
|
||||
if (room_transitioning_flags & 1) {
|
||||
link_is_on_lower_level ^= 1;
|
||||
link_is_on_lower_level_mirror = link_is_on_lower_level;
|
||||
@@ -2133,13 +2135,14 @@ void Dungeon_StartInterRoomTrans_Up() {
|
||||
}
|
||||
|
||||
void Dungeon_StartInterRoomTrans_Down() {
|
||||
assert(submodule_index == 0);
|
||||
link_quadrant_y ^= 2;
|
||||
Dungeon_AdjustQuadrant();
|
||||
RoomBounds_AddA(&room_bounds_y);
|
||||
Dung_SaveDataForCurrentRoom();
|
||||
DungeonTransition_AdjustCamera_Y(link_quadrant_y);
|
||||
HandleEdgeTransition_AdjustCameraBoundaries(0);
|
||||
submodule_index++;
|
||||
submodule_index = 1;
|
||||
if (!link_quadrant_y) {
|
||||
RoomBounds_AddB(&room_bounds_y);
|
||||
BYTE(dungeon_room_index_prev) = dungeon_room_index;
|
||||
@@ -2153,7 +2156,7 @@ void Dungeon_StartInterRoomTrans_Down() {
|
||||
Dungeon_AdjustAfterSpiralStairs();
|
||||
}
|
||||
BYTE(dungeon_room_index) += 16;
|
||||
submodule_index += 1;
|
||||
submodule_index = 2;
|
||||
if (room_transitioning_flags & 1) {
|
||||
link_is_on_lower_level ^= 1;
|
||||
link_is_on_lower_level_mirror = link_is_on_lower_level;
|
||||
@@ -2378,10 +2381,6 @@ uint16 Dungeon_GetTeleMsg(int room) {
|
||||
return kDungeonRoomTeleMsg[room];
|
||||
}
|
||||
|
||||
uint8 GetEntranceMusicTrack(int entrance) {
|
||||
return kEntranceData_musicTrack[entrance];
|
||||
}
|
||||
|
||||
bool Dungeon_IsPitThatHurtsPlayer() {
|
||||
for (int i = kDungeonPitsHurtPlayer_SIZE / 2 - 1; i >= 0; i--) {
|
||||
if (kDungeonPitsHurtPlayer[i] == dungeon_room_index)
|
||||
@@ -3699,10 +3698,10 @@ void Dungeon_LoadHeader() { // 81b564
|
||||
dung_want_lights_out_copy = dung_want_lights_out;
|
||||
dung_want_lights_out = hdr_ptr[0] & 1;
|
||||
const DungPalInfo *dpi = &kDungPalinfos[hdr_ptr[1]];
|
||||
dung_hdr_palette_1 = dpi->pal0;
|
||||
overworld_palette_sp0 = dpi->pal1;
|
||||
sprite_aux1_palette = dpi->pal2;
|
||||
sprite_aux2_palette = dpi->pal3;
|
||||
palette_main_indoors = dpi->pal0;
|
||||
palette_sp0l = dpi->pal1;
|
||||
palette_sp5l = dpi->pal2;
|
||||
palette_sp6l = dpi->pal3;
|
||||
aux_tile_theme_index = hdr_ptr[2];
|
||||
sprite_graphics_index = hdr_ptr[3] + 0x40;
|
||||
dung_hdr_collision_2 = hdr_ptr[4];
|
||||
@@ -4281,6 +4280,14 @@ void Dungeon_FlipCrystalPegAttribute() { // 81c22a
|
||||
void Dungeon_HandleRoomTags() { // 81c2fd
|
||||
if (!flag_skip_call_tag_routines) {
|
||||
Dungeon_DetectStaircase();
|
||||
|
||||
// Dungeon_DetectStaircase might change the submodule, so avoid
|
||||
// calling the tag routines cause they could also change the submodule,
|
||||
// causing items to spawn in incorrect locations cause link_x/y_coord gets
|
||||
// out of sync if you enter a staircase exactly when a room tag triggers.
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes && submodule_index != 0)
|
||||
return;
|
||||
|
||||
g_ram[14] = 0;
|
||||
kDungTagroutines[dung_hdr_tag[0]](0);
|
||||
g_ram[14] = 1;
|
||||
@@ -6421,7 +6428,7 @@ void Module_PreDungeon() { // 82821e
|
||||
Dungeon_LoadAttributeTable();
|
||||
misc_sprites_graphics_index = 10;
|
||||
InitializeTilesets();
|
||||
palette_sp6 = 10;
|
||||
palette_sp6r_indoors = 10;
|
||||
Dungeon_LoadPalettes();
|
||||
if (link_is_bunny_mirror | link_is_bunny)
|
||||
LoadGearPalettes_bunny();
|
||||
@@ -6906,7 +6913,7 @@ table:
|
||||
}
|
||||
|
||||
void Module07_0E_01_HandleMusicAndResetProps() { // 828c78
|
||||
if ((dungeon_room_index == 7 || dungeon_room_index == 23 && music_unk1 != 17) && !(link_which_pendants & 1))
|
||||
if ((dungeon_room_index == 7 || dungeon_room_index == 23 && !ZeldaIsPlayingMusicTrack(17)) && !(link_which_pendants & 1))
|
||||
music_control = 0xf1;
|
||||
staircase_var1 = (which_staircase_index & 4) ? 106 : 88;
|
||||
overworld_map_state = 0;
|
||||
@@ -7340,7 +7347,7 @@ void Dungeon_SetBossMusicUnorthodox() { // 829165
|
||||
x = 0x15;
|
||||
if (dungeon_room_index != 7) {
|
||||
x = 0x11;
|
||||
if (dungeon_room_index != 23 || music_unk1 == 17)
|
||||
if (dungeon_room_index != 23 || ZeldaIsPlayingMusicTrack(17))
|
||||
return;
|
||||
}
|
||||
if (music_unk1 != 0xf1 && (link_which_pendants & 1))
|
||||
@@ -7456,8 +7463,8 @@ void Module07_0F_01_OperateSpotlight() { // 829334
|
||||
TMW_copy = 0;
|
||||
TSW_copy = 0;
|
||||
subsubmodule_index = 0;
|
||||
if (buffer_for_playing_songs != 0xff)
|
||||
music_control = buffer_for_playing_songs;
|
||||
if (queued_music_control != 0xff)
|
||||
music_control = queued_music_control;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7732,7 +7739,7 @@ void Module07_19_MirrorFade() { // 8298f7
|
||||
submodule_index = 0;
|
||||
nmi_load_bg_from_vram = 0;
|
||||
last_music_control = music_unk1;
|
||||
if (overworld_palette_swap_flag)
|
||||
if (palette_swap_flag)
|
||||
Palette_RevertTranslucencySwap();
|
||||
}
|
||||
}
|
||||
@@ -7813,7 +7820,7 @@ void Module11_DungeonFallingEntrance() { // 829af9
|
||||
flag_skip_call_tag_routines++;
|
||||
Dungeon_PlayBlipAndCacheQuadrantVisits();
|
||||
ResetThenCacheRoomEntryProperties();
|
||||
music_control = buffer_for_playing_songs;
|
||||
music_control = queued_music_control;
|
||||
last_music_control = music_unk1;
|
||||
break;
|
||||
}
|
||||
@@ -7850,7 +7857,7 @@ void Module11_02_LoadEntrance() { // 829b1c
|
||||
subsubmodule_index = bak + 1;
|
||||
misc_sprites_graphics_index = 10;
|
||||
InitializeTilesets();
|
||||
palette_sp6 = 10;
|
||||
palette_sp6r_indoors = 10;
|
||||
Dungeon_LoadPalettes();
|
||||
Hud_RestoreTorchBackground();
|
||||
button_mask_b_y = 0;
|
||||
@@ -7866,10 +7873,10 @@ void Module11_02_LoadEntrance() { // 829b1c
|
||||
}
|
||||
|
||||
void Dungeon_LoadSongBankIfNeeded() { // 829bd7
|
||||
if (buffer_for_playing_songs == 0xff || buffer_for_playing_songs == 0xf2)
|
||||
if (queued_music_control == 0xff || queued_music_control == 0xf2)
|
||||
return;
|
||||
|
||||
if (buffer_for_playing_songs == 3 || buffer_for_playing_songs == 7 || buffer_for_playing_songs == 14) {
|
||||
if (queued_music_control == 3 || queued_music_control == 7 || queued_music_control == 14) {
|
||||
LoadOWMusicIfNeeded();
|
||||
} else {
|
||||
if (flag_which_music_type)
|
||||
@@ -7958,13 +7965,14 @@ void HandleEdgeTransitionMovementEast_RightBy8() { // 82b62e
|
||||
}
|
||||
|
||||
void Dungeon_StartInterRoomTrans_Right() { // 82b63a
|
||||
assert(submodule_index == 0);
|
||||
link_quadrant_x ^= 1;
|
||||
Dungeon_AdjustQuadrant();
|
||||
RoomBounds_AddA(&room_bounds_x);
|
||||
Dung_SaveDataForCurrentRoom();
|
||||
DungeonTransition_AdjustCamera_X(link_quadrant_x);
|
||||
HandleEdgeTransition_AdjustCameraBoundaries(2);
|
||||
submodule_index++;
|
||||
submodule_index = 1;
|
||||
if (!link_quadrant_x) {
|
||||
RoomBounds_AddB(&room_bounds_x);
|
||||
BYTE(dungeon_room_index_prev) = dungeon_room_index;
|
||||
@@ -7978,7 +7986,7 @@ void Dungeon_StartInterRoomTrans_Right() { // 82b63a
|
||||
}
|
||||
dungeon_room_index += 1;
|
||||
}
|
||||
submodule_index += 1;
|
||||
submodule_index = 2;
|
||||
if (room_transitioning_flags & 1) {
|
||||
link_is_on_lower_level ^= 1;
|
||||
link_is_on_lower_level_mirror = link_is_on_lower_level;
|
||||
@@ -8356,9 +8364,9 @@ void Dungeon_LoadEntrance() { // 82d8b3
|
||||
link_quadrant_x = kStartingPoint_quadrant2[i] >> 4;
|
||||
link_quadrant_y = kStartingPoint_quadrant2[i] & 0xf;
|
||||
|
||||
buffer_for_playing_songs = kStartingPoint_musicTrack[i];
|
||||
queued_music_control = kStartingPoint_musicTrack[i];
|
||||
if (i == 0 && sram_progress_indicator == 0)
|
||||
buffer_for_playing_songs = 0xff;
|
||||
queued_music_control = 0xff;
|
||||
death_var4 = 0;
|
||||
} else {
|
||||
int i = which_entrance;
|
||||
@@ -8393,9 +8401,9 @@ void Dungeon_LoadEntrance() { // 82d8b3
|
||||
|
||||
link_direction_facing = (i == 0 || i == 0x43) ? 2 : 0;
|
||||
main_tile_theme_index = kEntranceData_blockset[i];
|
||||
buffer_for_playing_songs = kEntranceData_musicTrack[i];
|
||||
if (buffer_for_playing_songs == 3 && sram_progress_indicator >= 2)
|
||||
buffer_for_playing_songs = 18;
|
||||
queued_music_control = ZeldaGetEntranceMusicTrack(i);
|
||||
if (queued_music_control == 3 && sram_progress_indicator >= 2)
|
||||
queued_music_control = 18;
|
||||
|
||||
dung_cur_floor = kEntranceData_floor[i];
|
||||
BYTE(cur_palace_index_x2) = kEntranceData_palace[i];
|
||||
@@ -8591,7 +8599,7 @@ void HandleLinkOnSpiralStairs() { // 87f2c1
|
||||
link_actual_vel_x = 2;
|
||||
}
|
||||
}
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
Link_HandleMovingAnimation_StartWithDash();
|
||||
if (!link_timer_push_get_tired && sign8(--countdown_timer_for_staircases)) {
|
||||
countdown_timer_for_staircases = 0;
|
||||
@@ -8630,7 +8638,7 @@ void SpiralStairs_FindLandingSpot() { // 87f391
|
||||
link_actual_vel_x = -4, link_actual_vel_y = 2;
|
||||
if (some_animation_timer_steps == 2)
|
||||
link_actual_vel_x = 0, link_actual_vel_y = 16;
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
Link_HandleMovingAnimation_StartWithDash();
|
||||
if ((uint8)link_x_coord == (uint8)tiledetect_which_y_pos[1])
|
||||
some_animation_timer_steps = 2;
|
||||
@@ -82,7 +82,6 @@ void Object_Draw_DoorRight_3x4(uint16 src, int door);
|
||||
void Dungeon_OpeningLockedDoor_Combined(bool skip_anim);
|
||||
const DungPalInfo *GetDungPalInfo(int idx);
|
||||
uint16 Dungeon_GetTeleMsg(int room);
|
||||
uint8 GetEntranceMusicTrack(int entrance);
|
||||
bool Dungeon_IsPitThatHurtsPlayer();
|
||||
void Dungeon_PrepareNextRoomQuadrantUpload();
|
||||
void WaterFlood_BuildOneQuadrantForVRAM();
|
||||
@@ -246,11 +246,11 @@ void Credits_LoadScene_Dungeon() { // 8286fd
|
||||
int i = submodule_index >> 1;
|
||||
sprite_graphics_index = kEnding_SpritePack[i];
|
||||
const DungPalInfo *dpi = GetDungPalInfo(kEnding_SpritePal[i] & 0x3f);
|
||||
sprite_aux1_palette = dpi->pal2;
|
||||
sprite_aux2_palette = dpi->pal3;
|
||||
palette_sp5l = dpi->pal2;
|
||||
palette_sp6l = dpi->pal3;
|
||||
misc_sprites_graphics_index = 10;
|
||||
InitializeTilesets();
|
||||
palette_sp6 = 10;
|
||||
palette_sp6r_indoors = 10;
|
||||
Dungeon_LoadPalettes();
|
||||
BGMODE_copy = 9;
|
||||
R16 = 0;
|
||||
@@ -578,7 +578,7 @@ void Intro_InitializeMemory_darken() { // 8cc1f5
|
||||
DecompressAnimatedDungeonTiles(0x5d);
|
||||
bg_tile_animation_countdown = 2;
|
||||
BYTE(overworld_screen_index) = 0;
|
||||
dung_hdr_palette_1 = 0;
|
||||
palette_main_indoors = 0;
|
||||
overworld_palette_aux3_bp7_lo = 0;
|
||||
R16 = 0;
|
||||
R18 = 0;
|
||||
@@ -1211,7 +1211,7 @@ void Intro_PeriodicSwordAndIntroFlash() { // 8cfe56
|
||||
SetBackdropcolorBlack();
|
||||
if (intro_times_pal_flash) {
|
||||
if ((intro_times_pal_flash & 3) != 0) {
|
||||
(&COLDATA_copy0)[intro_sword_24] |= 0x1f;
|
||||
(&COLDATA_copy0)[intro_sword_24] |= (enhanced_features0 & kFeatures0_DimFlashes) ? 0x05 : 0x1f;
|
||||
intro_sword_24 = (intro_sword_24 == 2) ? 0 : intro_sword_24 + 1;
|
||||
}
|
||||
intro_times_pal_flash--;
|
||||
@@ -42,17 +42,24 @@ enum {
|
||||
kFeatures0_CancelBirdTravel = 8192,
|
||||
|
||||
kFeatures0_GameChangingBugFixes = 16384,
|
||||
|
||||
kFeatures0_SwitchLRLimit = 32768,
|
||||
|
||||
kFeatures0_DimFlashes = 65536,
|
||||
};
|
||||
|
||||
#define enhanced_features0 (*(uint32*)(g_ram+0x64c))
|
||||
#define msu_curr_sample (*(uint32*)(g_ram+0x650))
|
||||
#define msu_volume (*(uint8*)(g_ram+0x654))
|
||||
#define msu_track (*(uint8*)(g_ram+0x655))
|
||||
#define hud_cur_item_x (*(uint8*)(g_ram+0x656))
|
||||
#define hud_inventory_order ((uint8*)(g_ram + 0x225)) // 4x6 bytes
|
||||
#define hud_cur_item_x (*(uint8*)(g_ram+0x656))
|
||||
#define hud_cur_item_l (*(uint8*)(g_ram+0x657))
|
||||
#define hud_cur_item_r (*(uint8*)(g_ram+0x658))
|
||||
|
||||
|
||||
|
||||
extern uint32 g_wanted_zelda_features;
|
||||
extern bool msu_enabled;
|
||||
|
||||
|
||||
#endif // ZELDA3_FEATURES_H_
|
||||
659
src/glsl_shader.c
Normal file
659
src/glsl_shader.c
Normal file
@@ -0,0 +1,659 @@
|
||||
// This file is heavily influenced by Snes9x
|
||||
#include "third_party/gl_core/gl_core_3_1.h"
|
||||
#include "glsl_shader.h"
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_THREAD_LOCALS
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_MAX_DIMENSIONS 4096
|
||||
#define STBI_NO_FAILURE_STRINGS
|
||||
#include "third_party/stb/stb_image.h"
|
||||
|
||||
static GlslPass *ParseConfigKeyPass(GlslShader *gs, const char *key, const char *match) {
|
||||
char *endp;
|
||||
for (; *match; key++, match++) {
|
||||
if (*key != *match)
|
||||
return NULL;
|
||||
}
|
||||
if ((uint8)(*key - '0') >= 10)
|
||||
return NULL;
|
||||
uint pass = strtoul(key, &endp, 10);
|
||||
if (pass >= gs->n_pass || *endp != 0)
|
||||
return NULL;
|
||||
return gs->pass + pass + 1;
|
||||
}
|
||||
|
||||
static uint8 ParseScaleType(const char *s) {
|
||||
return StringEqualsNoCase(s, "source") ? GLSL_SOURCE :
|
||||
StringEqualsNoCase(s, "viewport") ? GLSL_VIEWPORT :
|
||||
StringEqualsNoCase(s, "absolute") ? GLSL_ABSOLUTE : GLSL_NONE;
|
||||
}
|
||||
|
||||
static uint ParseWrapMode(const char *s) {
|
||||
return StringEqualsNoCase(s, "repeat") ? GL_REPEAT :
|
||||
StringEqualsNoCase(s, "clamp_to_edge") ? GL_CLAMP_TO_EDGE :
|
||||
StringEqualsNoCase(s, "clamp") ? GL_CLAMP : GL_CLAMP_TO_BORDER;
|
||||
}
|
||||
|
||||
static void GlslPass_Initialize(GlslPass *pass) {
|
||||
pass->scale_x = 1.0f;
|
||||
pass->scale_y = 1.0f;
|
||||
pass->wrap_mode = GL_CLAMP_TO_BORDER;
|
||||
}
|
||||
|
||||
static void ParseTextures(GlslShader *gs, char *value) {
|
||||
char *id;
|
||||
GlslTexture **nextp = &gs->first_texture;
|
||||
for (int num = 0; (id = NextDelim(&value, ';')) != NULL && num < kGlslMaxTextures; num++) {
|
||||
GlslTexture *t = calloc(sizeof(GlslTexture), 1);
|
||||
t->id = strdup(id);
|
||||
t->wrap_mode = GL_CLAMP_TO_BORDER;
|
||||
t->filter = GL_NEAREST;
|
||||
*nextp = t;
|
||||
nextp = &t->next;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ParseTextureKeyValue(GlslShader *gs, const char *key, const char *value) {
|
||||
for (GlslTexture *t = gs->first_texture; t != NULL; t = t->next) {
|
||||
const char *key2 = SkipPrefix(key, t->id);
|
||||
if (!key2) continue;
|
||||
if (*key2 == 0) {
|
||||
StrSet(&t->filename, value);
|
||||
return true;
|
||||
} else if (!strcmp(key2, "_wrap_mode")) {
|
||||
t->wrap_mode = ParseWrapMode(value);
|
||||
return true;
|
||||
} else if (!strcmp(key2, "_mipmap")) {
|
||||
t->mipmap = ParseBool(value, NULL);
|
||||
return true;
|
||||
} else if (!strcmp(key2, "_linear")) {
|
||||
t->filter = ParseBool(value, NULL) ? GL_LINEAR : GL_NEAREST;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static GlslParam *GlslShader_GetParam(GlslShader *gs, const char *id) {
|
||||
GlslParam **pp = &gs->first_param;
|
||||
for (; (*pp) != NULL; pp = &(*pp)->next)
|
||||
if (!strcmp((*pp)->id, id))
|
||||
return *pp;
|
||||
GlslParam *p = (GlslParam *)calloc(1, sizeof(GlslParam));
|
||||
*pp = p;
|
||||
p->id = strdup(id);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ParseParameters(GlslShader *gs, char *value) {
|
||||
char *id;
|
||||
while ((id = NextDelim(&value, ';')) != NULL)
|
||||
GlslShader_GetParam(gs, id);
|
||||
}
|
||||
|
||||
static bool ParseParameterKeyValue(GlslShader *gs, const char *key, const char *value) {
|
||||
for (GlslParam *p = gs->first_param; p != NULL; p = p->next) {
|
||||
if (strcmp(p->id, key) == 0) {
|
||||
p->value = atof(value);
|
||||
p->has_value = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void GlslShader_InitializePasses(GlslShader *gs, int passes) {
|
||||
gs->n_pass = passes;
|
||||
gs->pass = (GlslPass *)calloc(gs->n_pass + 1, sizeof(GlslPass));
|
||||
for (int i = 0; i < gs->n_pass; i++)
|
||||
GlslPass_Initialize(gs->pass + i + 1);
|
||||
}
|
||||
|
||||
static bool GlslShader_ReadPresetFile(GlslShader *gs, const char *filename) {
|
||||
char *data = (char *)ReadWholeFile(filename, NULL), *data_org = data, *line;
|
||||
GlslPass *pass;
|
||||
if (data == NULL)
|
||||
return false;
|
||||
for (int lineno = 1; (line = NextLineStripComments(&data)) != NULL; lineno++) {
|
||||
char *value = SplitKeyValue(line), *t;
|
||||
if (value == NULL) {
|
||||
if (*line)
|
||||
fprintf(stderr, "%s:%d: Expecting key=value\n", filename, lineno);
|
||||
continue;
|
||||
}
|
||||
if (*value == '"') {
|
||||
for (t = ++value; *t && *t != '"'; t++);
|
||||
if (*t) *t = 0;
|
||||
}
|
||||
|
||||
if (gs->n_pass == 0) {
|
||||
if (strcmp(line, "shaders") != 0) {
|
||||
fprintf(stderr, "%s:%d: Expecting 'shaders'\n", filename, lineno);
|
||||
break;
|
||||
}
|
||||
int passes = strtoul(value, NULL, 10);
|
||||
if (passes < 1 || passes > kGlslMaxPasses)
|
||||
break;
|
||||
GlslShader_InitializePasses(gs, passes);
|
||||
continue;
|
||||
}
|
||||
if ((pass = ParseConfigKeyPass(gs, line, "filter_linear")) != NULL)
|
||||
pass->filter = ParseBool(value, NULL) ? GL_LINEAR : GL_NEAREST;
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "scale_type")) != NULL)
|
||||
pass->scale_type_x = pass->scale_type_y = ParseScaleType(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "scale_type_x")) != NULL)
|
||||
pass->scale_type_x = ParseScaleType(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "scale_type_y")) != NULL)
|
||||
pass->scale_type_y = ParseScaleType(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "scale")) != NULL)
|
||||
pass->scale_x = pass->scale_y = atof(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "scale_x")) != NULL)
|
||||
pass->scale_x = atof(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "scale_y")) != NULL)
|
||||
pass->scale_y = atof(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "shader")) != NULL)
|
||||
StrSet(&pass->filename, value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "wrap_mode")) != NULL)
|
||||
pass->wrap_mode = ParseWrapMode(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "mipmap_input")) != NULL)
|
||||
pass->mipmap_input = ParseBool(value, NULL);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "frame_count_mod")) != NULL)
|
||||
pass->frame_count_mod = atoi(value);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "float_framebuffer")) != NULL)
|
||||
pass->float_framebuffer = ParseBool(value, NULL);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "srgb_framebuffer")) != NULL)
|
||||
pass->srgb_framebuffer = ParseBool(value, NULL);
|
||||
else if ((pass = ParseConfigKeyPass(gs, line, "alias")) != NULL)
|
||||
;
|
||||
else if (strcmp(line, "textures") == 0 && gs->first_texture == NULL)
|
||||
ParseTextures(gs, value);
|
||||
else if (strcmp(line, "parameters") == 0)
|
||||
ParseParameters(gs, value);
|
||||
else if (!ParseTextureKeyValue(gs, line, value) && !ParseParameterKeyValue(gs, line, value))
|
||||
fprintf(stderr, "%s:%d: Unknown key '%s'\n", filename, lineno, line);
|
||||
}
|
||||
free(data_org);
|
||||
return gs->n_pass != 0;
|
||||
}
|
||||
|
||||
void GlslShader_ReadShaderFile(GlslShader *gs, const char *filename, ByteArray *result) {
|
||||
char *data = (char *)ReadWholeFile(filename, NULL), *data_org = data, *line;
|
||||
if (data == NULL) {
|
||||
fprintf(stderr, "Unable to read file '%s'\n", filename);
|
||||
return;
|
||||
}
|
||||
while ((line = NextDelim(&data, '\n')) != NULL) {
|
||||
size_t linelen = strlen(line);
|
||||
if (linelen >= 8 && memcmp(line, "#include", 8) == 0) {
|
||||
char *tt = line + 8;
|
||||
char *new_filename = ReplaceFilenameWithNewPath(filename, NextPossiblyQuotedString(&tt));
|
||||
GlslShader_ReadShaderFile(gs, new_filename, result);
|
||||
free(new_filename);
|
||||
} else if (linelen >= 17 && memcmp(line, "#pragma parameter", 17) == 0) {
|
||||
char *tt = line + 17;
|
||||
GlslParam *param = GlslShader_GetParam(gs, NextPossiblyQuotedString(&tt));
|
||||
NextPossiblyQuotedString(&tt); // skip name
|
||||
float value = atof(NextPossiblyQuotedString(&tt));
|
||||
if (!param->has_value)
|
||||
param->value = value;
|
||||
param->min = atof(NextPossiblyQuotedString(&tt));
|
||||
param->max = atof(NextPossiblyQuotedString(&tt));
|
||||
// skip step
|
||||
} else {
|
||||
line[linelen] = '\n';
|
||||
ByteArray_AppendData(result, (uint8 *)line, linelen + 1);
|
||||
}
|
||||
}
|
||||
free(data_org);
|
||||
}
|
||||
|
||||
size_t LengthOfInitialComments(const uint8 *data, size_t size) {
|
||||
const uint8 *data_org = data, *data_end = data + size;
|
||||
while (data != data_end) {
|
||||
uint8 c = *data++;
|
||||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
|
||||
continue;
|
||||
if (c != '/' || data == data_end) {
|
||||
data--;
|
||||
break;
|
||||
}
|
||||
c = *data++;
|
||||
if (c == '/') {
|
||||
while (data != data_end && *data++ != '\n') {}
|
||||
} else if (c == '*') {
|
||||
do {
|
||||
if (data_end - data < 2)
|
||||
return 0;
|
||||
} while (*data++ != '*' || data[0] != '/');
|
||||
data++;
|
||||
} else {
|
||||
data--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return data - data_org;
|
||||
}
|
||||
|
||||
static bool GlslPass_Compile(GlslPass *p, uint type, const uint8 *data, size_t size, bool use_opengl_es) {
|
||||
static const char kVertexPrefix[] = "#define VERTEX\n#define PARAMETER_UNIFORM\n";
|
||||
static const char kFragmentPrefixCore[] = "#define FRAGMENT\n#define PARAMETER_UNIFORM\n";
|
||||
static const char kFragmentPrefixEs[] = "#define FRAGMENT\n#define PARAMETER_UNIFORM\n" \
|
||||
"precision mediump float;";
|
||||
const GLchar *strings[3];
|
||||
GLint lengths[3];
|
||||
char buffer[256];
|
||||
GLint compile_status = 0;
|
||||
size_t skip = 0;
|
||||
|
||||
size_t commsize = LengthOfInitialComments(data, size);
|
||||
data += commsize, size -= commsize;
|
||||
|
||||
if (size < 8 || memcmp(data, "#version", 8) != 0) {
|
||||
if (!use_opengl_es) {
|
||||
strings[0] = "#version 330\n";
|
||||
lengths[0] = sizeof("#version 330\n") - 1;
|
||||
} else {
|
||||
strings[0] = "#version 300 es\n";
|
||||
lengths[0] = sizeof("#version 300 es\n") - 1;
|
||||
}
|
||||
} else {
|
||||
while (skip < size && data[skip++] != '\n') {}
|
||||
strings[0] = (char*)data;
|
||||
lengths[0] = (int)skip;
|
||||
}
|
||||
if (type == GL_VERTEX_SHADER) {
|
||||
strings[1] = (char *)kVertexPrefix;
|
||||
lengths[1] = sizeof(kVertexPrefix) - 1;
|
||||
} else {
|
||||
strings[1] = (use_opengl_es) ? (char *)kFragmentPrefixEs : kFragmentPrefixCore;
|
||||
lengths[1] = (use_opengl_es) ? sizeof(kFragmentPrefixEs) - 1 : sizeof(kFragmentPrefixCore) - 1;
|
||||
}
|
||||
strings[2] = (GLchar *)data + skip;
|
||||
lengths[2] = (int)(size - skip);
|
||||
uint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 3, strings, lengths);
|
||||
glCompileShader(shader);
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
|
||||
buffer[0] = 0;
|
||||
glGetShaderInfoLog(shader, sizeof(buffer), NULL, buffer);
|
||||
if (compile_status != GL_TRUE || buffer[0]) {
|
||||
fprintf(stderr, "%s compiling %s shader in file '%s':\n%s\n",
|
||||
compile_status != GL_TRUE ? "Error" : "While",
|
||||
type == GL_VERTEX_SHADER ? "vertex" : "fragment", p->filename, buffer);
|
||||
}
|
||||
if (compile_status == GL_TRUE)
|
||||
glAttachShader(p->gl_program, shader);
|
||||
glDeleteShader(shader);
|
||||
return (compile_status == GL_TRUE);
|
||||
}
|
||||
|
||||
static void GlslTextureUniform_Read(uint program, const char *prefix, int i, GlslTextureUniform *result) {
|
||||
char buf[40];
|
||||
char *e = &buf[snprintf(buf, sizeof(buf), i >= 0 ? "%s%u" : "%s", prefix, i)];
|
||||
memcpy(e, "Texture", 8);
|
||||
result->Texture = glGetUniformLocation(program, buf);
|
||||
memcpy(e, "InputSize", 10);
|
||||
result->InputSize = glGetUniformLocation(program, buf);
|
||||
memcpy(e, "TextureSize", 12);
|
||||
result->TextureSize = glGetUniformLocation(program, buf);
|
||||
memcpy(e, "TexCoord", 9);
|
||||
result->TexCoord = glGetAttribLocation(program, buf);
|
||||
}
|
||||
|
||||
static const float kMvpMatrixOrtho[16] = {
|
||||
2.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 2.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, -1.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f, 1.0f
|
||||
};
|
||||
|
||||
static void GlslShader_GetUniforms(GlslShader *gs) {
|
||||
int pass_idx = 1;
|
||||
gs->max_prev_frame = 0;
|
||||
for (GlslPass *p = gs->pass + 1, *p_end = p + gs->n_pass; p < p_end; p++, pass_idx++) {
|
||||
uint program = p->gl_program;
|
||||
glUseProgram(program);
|
||||
|
||||
GLint MVPMatrix = glGetUniformLocation(program, "MVPMatrix");
|
||||
if (MVPMatrix >= 0)
|
||||
glUniformMatrix4fv(MVPMatrix, 1, GL_FALSE, kMvpMatrixOrtho);
|
||||
|
||||
GlslTextureUniform_Read(program, "", -1, &p->unif.Top);
|
||||
p->unif.OutputSize = glGetUniformLocation(program, "OutputSize");
|
||||
p->unif.FrameCount = glGetUniformLocation(program, "FrameCount");
|
||||
p->unif.FrameDirection = glGetUniformLocation(program, "FrameDirection");
|
||||
p->unif.LUTTexCoord = glGetAttribLocation(program, "LUTTexCoord");
|
||||
p->unif.VertexCoord = glGetAttribLocation(program, "VertexCoord");
|
||||
GlslTextureUniform_Read(program, "Orig", -1, &p->unif.Orig);
|
||||
for (int j = 0; j < 7; j++) {
|
||||
GlslTextureUniform_Read(program, "Prev", j ? j : -1, &p->unif.Prev[j]);
|
||||
if (p->unif.Prev[j].Texture >= 0)
|
||||
gs->max_prev_frame = j + 1;
|
||||
}
|
||||
for (int j = 0; j < gs->n_pass; j++) {
|
||||
GlslTextureUniform_Read(program, "Pass", j, &p->unif.Pass[j]);
|
||||
GlslTextureUniform_Read(program, "PassPrev", j, &p->unif.PassPrev[j]);
|
||||
}
|
||||
GlslTexture *t = gs->first_texture;
|
||||
for (int j = 0; t != NULL; t = t->next, j++)
|
||||
p->unif.Texture[j] = glGetUniformLocation(program, t->id);
|
||||
for (GlslParam *pa = gs->first_param; pa != NULL; pa = pa->next)
|
||||
pa->uniform[pass_idx] = glGetUniformLocation(program, pa->id);
|
||||
}
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
static bool IsGlslFilename(const char *filename) {
|
||||
size_t len = strlen(filename);
|
||||
return len >= 5 && memcmp(filename + len - 5, ".glsl", 5) == 0;
|
||||
}
|
||||
|
||||
GlslShader *GlslShader_CreateFromFile(const char *filename, bool opengl_es) {
|
||||
char buffer[256];
|
||||
GLint link_status;
|
||||
ByteArray shader_code = { 0 };
|
||||
bool success = false;
|
||||
GlslShader *gs = (GlslShader *)calloc(sizeof(GlslShader), 1);
|
||||
if (!gs)
|
||||
return gs;
|
||||
|
||||
if (IsGlslFilename(filename)) {
|
||||
GlslShader_InitializePasses(gs, 1);
|
||||
gs->pass[1].filename = strdup(filename);
|
||||
filename = "";
|
||||
} else {
|
||||
if (!GlslShader_ReadPresetFile(gs, filename)) {
|
||||
fprintf(stderr, "Unable to read file '%s'\n", filename);
|
||||
goto FAIL;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i <= gs->n_pass; i++) {
|
||||
GlslPass *p = gs->pass + i;
|
||||
shader_code.size = 0;
|
||||
|
||||
if (p->filename == NULL) {
|
||||
fprintf(stderr, "shader%d attribute missing\n", i - 1);
|
||||
goto FAIL;
|
||||
}
|
||||
|
||||
char *new_filename = ReplaceFilenameWithNewPath(filename, p->filename);
|
||||
GlslShader_ReadShaderFile(gs, new_filename, &shader_code);
|
||||
free(new_filename);
|
||||
|
||||
if (shader_code.size == 0) {
|
||||
fprintf(stderr, "Couldn't read shader in file '%s'\n", p->filename);
|
||||
goto FAIL;
|
||||
}
|
||||
p->gl_program = glCreateProgram();
|
||||
if (!GlslPass_Compile(p, GL_VERTEX_SHADER, shader_code.data, shader_code.size, opengl_es) ||
|
||||
!GlslPass_Compile(p, GL_FRAGMENT_SHADER, shader_code.data, shader_code.size, opengl_es)) {
|
||||
goto FAIL;
|
||||
}
|
||||
glLinkProgram(p->gl_program);
|
||||
glGetProgramiv(p->gl_program, GL_LINK_STATUS, &link_status);
|
||||
buffer[0] = 0;
|
||||
glGetProgramInfoLog(p->gl_program, sizeof(buffer), NULL, buffer);
|
||||
if (link_status != GL_TRUE || buffer[0])
|
||||
fprintf(stderr, "%s linking shader in file '%s':\n%s\n",
|
||||
link_status != GL_TRUE ? "Error" : "While", p->filename, buffer);
|
||||
if (link_status != GL_TRUE)
|
||||
goto FAIL;
|
||||
glGenFramebuffers(1, &p->gl_fbo);
|
||||
glGenTextures(1, &p->gl_texture);
|
||||
}
|
||||
for (GlslTexture *t = gs->first_texture; t; t = t->next) {
|
||||
glGenTextures(1, &t->gl_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, t->gl_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, t->wrap_mode);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, t->wrap_mode);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, t->filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, t->mipmap ?
|
||||
(t->filter == GL_LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) : t->filter);
|
||||
if (t->filename) {
|
||||
char *new_filename = ReplaceFilenameWithNewPath(filename, t->filename);
|
||||
int imw, imh, imn;
|
||||
unsigned char *data = stbi_load(new_filename, &imw, &imh, &imn, 0);
|
||||
if (!data) {
|
||||
fprintf(stderr, "Unable to read PNG '%s'\n", new_filename);
|
||||
} else {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imw, imh, 0,
|
||||
(imn == 4) ? GL_RGBA : (imn == 3) ? GL_RGB : GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
|
||||
}
|
||||
free(data);
|
||||
free(new_filename);
|
||||
}
|
||||
if (t->mipmap)
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
for (GlslParam *p = gs->first_param; p; p = p->next)
|
||||
p->value = (p->value < p->min) ? p->min : (p->value > p->max) ? p->max : p->value;
|
||||
|
||||
GlslShader_GetUniforms(gs);
|
||||
|
||||
for (int i = 0; i < gs->max_prev_frame; i++)
|
||||
glGenTextures(1, &gs->prev_frame[-i - 1 & 7].gl_texture);
|
||||
|
||||
static const GLfloat kTexCoords[16] = {
|
||||
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
|
||||
0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
|
||||
};
|
||||
glGenBuffers(1, &gs->gl_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, gs->gl_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(kTexCoords), kTexCoords, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
success = true;
|
||||
FAIL:
|
||||
if (!success) {
|
||||
GlslShader_Destroy(gs);
|
||||
gs = NULL;
|
||||
}
|
||||
ByteArray_Destroy(&shader_code);
|
||||
return gs;
|
||||
}
|
||||
|
||||
void GlslShader_Destroy(GlslShader *gs) {
|
||||
for (GlslPass *p = gs->pass + 1, *p_end = p + gs->n_pass; p < p_end; p++) {
|
||||
glDeleteProgram(p->gl_program);
|
||||
glDeleteTextures(1, &p->gl_texture);
|
||||
glDeleteFramebuffers(1, &p->gl_fbo);
|
||||
free(p->filename);
|
||||
}
|
||||
free(gs->pass);
|
||||
GlslTexture *t;
|
||||
while ((t = gs->first_texture) != NULL) {
|
||||
gs->first_texture = t->next;
|
||||
glDeleteTextures(1, &t->gl_texture);
|
||||
free(t->id);
|
||||
free(t->filename);
|
||||
free(t);
|
||||
}
|
||||
GlslParam *pp;
|
||||
while ((pp = gs->first_param) != NULL) {
|
||||
gs->first_param = pp->next;
|
||||
free(pp->id);
|
||||
free(pp);
|
||||
}
|
||||
for (int i = 0; i < 8; i++)
|
||||
glDeleteTextures(1, &gs->prev_frame[i].gl_texture);
|
||||
glDeleteBuffers(1, &gs->gl_vbo);
|
||||
free(gs);
|
||||
}
|
||||
|
||||
enum {
|
||||
kMaxVaosInRenderCtx = 11 + kGlslMaxPasses * 2
|
||||
};
|
||||
|
||||
typedef struct RenderCtx {
|
||||
uint texture_unit;
|
||||
uint offset;
|
||||
uint num_vaos;
|
||||
uint vaos[kMaxVaosInRenderCtx];
|
||||
} RenderCtx;
|
||||
|
||||
static void RenderCtx_SetTexture(RenderCtx *ctx, int textureu, uint texture_id) {
|
||||
if (textureu >= 0) {
|
||||
glActiveTexture(GL_TEXTURE0 + ctx->texture_unit);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id);
|
||||
glUniform1i(textureu, ctx->texture_unit++);
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderCtx_SetTexCoords(RenderCtx *ctx, int tex_coord, uintptr_t offset) {
|
||||
if (tex_coord >= 0) {
|
||||
assert(ctx->num_vaos < kMaxVaosInRenderCtx);
|
||||
ctx->vaos[ctx->num_vaos++] = tex_coord;
|
||||
glVertexAttribPointer(tex_coord, 2, GL_FLOAT, GL_FALSE, 0, (void *)offset);
|
||||
glEnableVertexAttribArray(tex_coord);
|
||||
}
|
||||
}
|
||||
|
||||
static void RenderCtx_SetGlslTextureUniform(RenderCtx *ctx, GlslTextureUniform *u,
|
||||
int width, int height, uint texture) {
|
||||
float size[2] = { width, height };
|
||||
RenderCtx_SetTexture(ctx, u->Texture, texture);
|
||||
if (u->InputSize >= 0)
|
||||
glUniform2fv(u->InputSize, 1, size);
|
||||
if (u->TextureSize >= 0)
|
||||
glUniform2fv(u->TextureSize, 1, size);
|
||||
RenderCtx_SetTexCoords(ctx, u->TexCoord, ctx->offset);
|
||||
}
|
||||
|
||||
static void GlslShader_SetShaderVars(GlslShader *gs, RenderCtx *ctx, int pass) {
|
||||
GlslPass *p = &gs->pass[pass];
|
||||
|
||||
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Top, p[-1].width, p[-1].height, p[-1].gl_texture);
|
||||
if (p->unif.OutputSize >= 0) {
|
||||
float output_size[2] = { (float)p[0].width, (float)p[0].height };
|
||||
glUniform2fv(p->unif.OutputSize, 1, output_size);
|
||||
}
|
||||
if (p->unif.FrameCount >= 0)
|
||||
glUniform1i(p->unif.FrameCount, p->frame_count_mod ?
|
||||
gs->frame_count % p->frame_count_mod : gs->frame_count);
|
||||
if (p->unif.FrameDirection >= 0)
|
||||
glUniform1i(p->unif.FrameDirection, 1);
|
||||
RenderCtx_SetTexCoords(ctx, p->unif.LUTTexCoord, ctx->offset);
|
||||
RenderCtx_SetTexCoords(ctx, p->unif.VertexCoord, 0);
|
||||
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Orig, gs->pass[0].width, gs->pass[0].height, gs->pass[0].gl_texture);
|
||||
// Prev, Prev1-Prev6 uniforms
|
||||
for (int i = 0; i < gs->max_prev_frame; i++) {
|
||||
GlTextureWithSize *t = &gs->prev_frame[(gs->frame_count - 1 - i) & 7];
|
||||
assert(t->gl_texture != 0);
|
||||
if (t->width)
|
||||
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Prev[i], t->width, t->height, t->gl_texture);
|
||||
}
|
||||
// Texture uniforms
|
||||
int tctr = 0;
|
||||
for (GlslTexture *t = gs->first_texture; t; t = t->next, tctr++)
|
||||
RenderCtx_SetTexture(ctx, p->unif.Texture[tctr], t->gl_texture);
|
||||
// PassX uniforms
|
||||
for (int i = 1; i < pass; i++)
|
||||
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Pass[i], gs->pass[i].width, gs->pass[i].height, gs->pass[i].gl_texture);
|
||||
// PassPrevX uniforms
|
||||
for (int i = 1; i < pass; i++)
|
||||
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.PassPrev[pass - i], gs->pass[i].width, gs->pass[i].height, gs->pass[i].gl_texture);
|
||||
// #parameter uniforms
|
||||
for (GlslParam *pa = gs->first_param; pa != NULL; pa = pa->next)
|
||||
if (pa->uniform[pass] >= 0)
|
||||
glUniform1f(pa->uniform[pass], pa->value);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void GlslShader_Render(GlslShader *gs, GlTextureWithSize *tex, int viewport_x, int viewport_y, int viewport_width, int viewport_height) {
|
||||
gs->pass[0].gl_texture = tex->gl_texture;
|
||||
gs->pass[0].width = tex->width;
|
||||
gs->pass[0].height = tex->height;
|
||||
|
||||
GLint previous_framebuffer;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previous_framebuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, gs->gl_vbo);
|
||||
|
||||
for (int pass = 1; pass <= gs->n_pass; pass++) {
|
||||
bool last_pass = pass == gs->n_pass;
|
||||
GlslPass *p = gs->pass + pass;
|
||||
|
||||
switch (p->scale_type_x) {
|
||||
case GLSL_ABSOLUTE: p->width = (uint16)p->scale_x; break;
|
||||
case GLSL_SOURCE: p->width = (uint16)(p[-1].width * p->scale_x); break;
|
||||
case GLSL_VIEWPORT: p->width = (uint16)(viewport_width * p->scale_x); break;
|
||||
default: p->width = (uint16)(last_pass ? viewport_width : (p[-1].width * p->scale_x)); break;
|
||||
}
|
||||
|
||||
switch (p->scale_type_y) {
|
||||
case GLSL_ABSOLUTE: p->height = (uint16)p->scale_y; break;
|
||||
case GLSL_SOURCE: p->height = (uint16)(p[-1].height * p->scale_y); break;
|
||||
case GLSL_VIEWPORT: p->height = (uint16)(viewport_height * p->scale_y); break;
|
||||
default: p->height = (uint16)(last_pass ? viewport_height : (p[-1].height * p->scale_y)); break;
|
||||
}
|
||||
|
||||
if (!last_pass) {
|
||||
// output to a texture
|
||||
glBindTexture(GL_TEXTURE_2D, p->gl_texture);
|
||||
if (p->srgb_framebuffer) {
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, p->width, p->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, NULL);
|
||||
} else {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, p->float_framebuffer ? GL_RGBA32F : GL_RGBA,
|
||||
p->width, p->height, 0, GL_RGBA,
|
||||
p->float_framebuffer ? GL_FLOAT : GL_UNSIGNED_INT_8_8_8_8, NULL);
|
||||
}
|
||||
glViewport(0, 0, p->width, p->height);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, p->gl_fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p->gl_texture, 0);
|
||||
} else {
|
||||
// output to screen
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, previous_framebuffer);
|
||||
glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, p[-1].gl_texture);
|
||||
|
||||
uint filter = p->filter ? p->filter : (last_pass && g_config.linear_filtering) ? GL_LINEAR : GL_NEAREST;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, p->mipmap_input ?
|
||||
(filter == GL_LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) : filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, p->wrap_mode);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, p->wrap_mode);
|
||||
if (p->mipmap_input)
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glUseProgram(p->gl_program);
|
||||
|
||||
RenderCtx ctx;
|
||||
ctx.texture_unit = ctx.num_vaos = 0;
|
||||
ctx.offset = last_pass ? sizeof(float) * 8 : 0;
|
||||
GlslShader_SetShaderVars(gs, &ctx, pass);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
for(int i = 0; i < ctx.num_vaos; i++)
|
||||
glDisableVertexAttribArray(ctx.vaos[i]);
|
||||
if (p->srgb_framebuffer)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, previous_framebuffer);
|
||||
glUseProgram(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Store the input frame in the prev array, and extract the next one.
|
||||
if (gs->max_prev_frame != 0) {
|
||||
// 01234567
|
||||
// 43210
|
||||
// ^-- store pos
|
||||
// ^-- load pos
|
||||
GlTextureWithSize *store_pos = &gs->prev_frame[gs->frame_count & 7];
|
||||
GlTextureWithSize *load_pos = &gs->prev_frame[gs->frame_count - gs->max_prev_frame & 7];
|
||||
assert(store_pos->gl_texture == 0);
|
||||
*store_pos = *tex;
|
||||
*tex = *load_pos;
|
||||
memset(load_pos, 0, sizeof(GlTextureWithSize));
|
||||
}
|
||||
|
||||
gs->frame_count++;
|
||||
}
|
||||
|
||||
99
src/glsl_shader.h
Normal file
99
src/glsl_shader.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef ZELDA3_GLSL_SHADER_H_
|
||||
#define ZELDA3_GLSL_SHADER_H_
|
||||
|
||||
#include "types.h"
|
||||
|
||||
enum {
|
||||
kGlslMaxPasses = 20,
|
||||
kGlslMaxTextures = 10,
|
||||
};
|
||||
|
||||
enum GLSLScaleType {
|
||||
GLSL_NONE,
|
||||
GLSL_SOURCE,
|
||||
GLSL_VIEWPORT,
|
||||
GLSL_ABSOLUTE
|
||||
};
|
||||
|
||||
typedef struct GlslTextureUniform {
|
||||
int Texture;
|
||||
int InputSize;
|
||||
int TextureSize;
|
||||
int TexCoord;
|
||||
} GlslTextureUniform;
|
||||
|
||||
typedef struct GlslUniforms {
|
||||
GlslTextureUniform Top;
|
||||
int OutputSize;
|
||||
int FrameCount, FrameDirection;
|
||||
int LUTTexCoord;
|
||||
int VertexCoord;
|
||||
GlslTextureUniform Orig;
|
||||
GlslTextureUniform Prev[7];
|
||||
GlslTextureUniform Pass[kGlslMaxPasses];
|
||||
GlslTextureUniform PassPrev[kGlslMaxPasses];
|
||||
int Texture[kGlslMaxTextures];
|
||||
} GlslUniforms;
|
||||
|
||||
typedef struct GlslPass {
|
||||
char *filename;
|
||||
uint8 scale_type_x, scale_type_y;
|
||||
bool float_framebuffer;
|
||||
bool srgb_framebuffer;
|
||||
bool mipmap_input;
|
||||
float scale_x, scale_y;
|
||||
uint wrap_mode;
|
||||
uint frame_count_mod;
|
||||
uint frame_count;
|
||||
uint gl_program, gl_fbo;
|
||||
uint filter;
|
||||
uint gl_texture;
|
||||
uint16 width, height;
|
||||
GlslUniforms unif;
|
||||
} GlslPass;
|
||||
|
||||
typedef struct GlTextureWithSize {
|
||||
uint gl_texture;
|
||||
uint16 width, height;
|
||||
} GlTextureWithSize;
|
||||
|
||||
typedef struct GlslTexture {
|
||||
struct GlslTexture *next;
|
||||
char *id;
|
||||
char *filename;
|
||||
uint filter;
|
||||
uint gl_texture;
|
||||
uint wrap_mode;
|
||||
bool mipmap;
|
||||
int width;
|
||||
int height;
|
||||
} GlslTexture;
|
||||
|
||||
typedef struct GlslParam {
|
||||
struct GlslParam *next;
|
||||
char *id;
|
||||
bool has_value;
|
||||
float value;
|
||||
float min;
|
||||
float max;
|
||||
uint uniform[kGlslMaxPasses];
|
||||
} GlslParam;
|
||||
|
||||
typedef struct GlslShader {
|
||||
int n_pass;
|
||||
GlslPass *pass;
|
||||
GlslParam *first_param;
|
||||
GlslTexture *first_texture;
|
||||
uint *gl_vao;
|
||||
uint gl_vbo;
|
||||
uint frame_count;
|
||||
int max_prev_frame;
|
||||
GlTextureWithSize prev_frame[8];
|
||||
} GlslShader;
|
||||
|
||||
GlslShader *GlslShader_CreateFromFile(const char *filename, bool opengl_es);
|
||||
void GlslShader_Destroy(GlslShader *gs);
|
||||
void GlslShader_Render(GlslShader *gs, GlTextureWithSize *tex, int viewport_x, int viewport_y, int viewport_width, int viewport_height);
|
||||
|
||||
|
||||
#endif // ZELDA3_GLSL_SHADER_H_
|
||||
@@ -46,150 +46,139 @@ static const uint16 kHudItemInVramPtr_Old[20] = {
|
||||
|
||||
#define kHudItemInVramPtr (kNewStyleInventory ? kHudItemInVramPtr_New : kHudItemInVramPtr_Old)
|
||||
|
||||
static const uint16 kHudBottlesGfx[128] = {
|
||||
0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x255c, 0x2564, 0x2562, 0x2557, 0x2561, 0x255e, 0x255e, 0x255c,
|
||||
0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x2551, 0x255e, 0x2563, 0x2563, 0x255b, 0x2554, 0x24f5, 0x24f5,
|
||||
0x255b, 0x2558, 0x2555, 0x2554, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x255c, 0x2554, 0x2553, 0x2558, 0x2552, 0x2558, 0x255d, 0x2554,
|
||||
0x255c, 0x2550, 0x2556, 0x2558, 0x2552, 0x24f5, 0x24f5, 0x24f5, 0x255c, 0x2554, 0x2553, 0x2558, 0x2552, 0x2558, 0x255d, 0x2554,
|
||||
0x2552, 0x2564, 0x2561, 0x2554, 0x256a, 0x2550, 0x255b, 0x255b, 0x255c, 0x2554, 0x2553, 0x2558, 0x2552, 0x2558, 0x255d, 0x2554,
|
||||
0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x2555, 0x2550, 0x2554, 0x2561, 0x2558, 0x2554, 0x24f5, 0x24f5,
|
||||
0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x2551, 0x2554, 0x2554, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5,
|
||||
0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x2556, 0x255e, 0x255e, 0x2553, 0x24f5, 0x2551, 0x2554, 0x2554,
|
||||
};
|
||||
|
||||
static const ItemBoxGfx kHudItemBottles[9] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2044, 0x2045, 0x2046, 0x2047},
|
||||
{0x2837, 0x2838, 0x2cc3, 0x2cd3},
|
||||
{0x24d2, 0x64d2, 0x24e2, 0x24e3},
|
||||
{0x3cd2, 0x7cd2, 0x3ce2, 0x3ce3},
|
||||
{0x2cd2, 0x6cd2, 0x2ce2, 0x2ce3},
|
||||
{0x2855, 0x6855, 0x2c57, 0x2c5a},
|
||||
{0x2837, 0x2838, 0x2839, 0x283a},
|
||||
{0x2837, 0x2838, 0x2839, 0x283a},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2044, 0x2045, 0x2046, 0x2047}},
|
||||
{{0x2837, 0x2838, 0x2cc3, 0x2cd3}},
|
||||
{{0x24d2, 0x64d2, 0x24e2, 0x24e3}},
|
||||
{{0x3cd2, 0x7cd2, 0x3ce2, 0x3ce3}},
|
||||
{{0x2cd2, 0x6cd2, 0x2ce2, 0x2ce3}},
|
||||
{{0x2855, 0x6855, 0x2c57, 0x2c5a}},
|
||||
{{0x2837, 0x2838, 0x2839, 0x283a}},
|
||||
{{0x2837, 0x2838, 0x2839, 0x283a}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBow[5] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x28ba, 0x28e9, 0x28e8, 0x28cb},
|
||||
{0x28ba, 0x284a, 0x2849, 0x28cb},
|
||||
{0x28ba, 0x28e9, 0x28e8, 0x28cb},
|
||||
{0x28ba, 0x28bb, 0x24ca, 0x28cb},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x28ba, 0x28e9, 0x28e8, 0x28cb}},
|
||||
{{0x28ba, 0x284a, 0x2849, 0x28cb}},
|
||||
{{0x28ba, 0x28e9, 0x28e8, 0x28cb}},
|
||||
{{0x28ba, 0x28bb, 0x24ca, 0x28cb}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBoomerang[3] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2cb8, 0x2cb9, 0x2cf5, 0x2cc9},
|
||||
{0x24b8, 0x24b9, 0x24f5, 0x24c9},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2cb8, 0x2cb9, 0x2cf5, 0x2cc9}},
|
||||
{{0x24b8, 0x24b9, 0x24f5, 0x24c9}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemHookshot[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x24f5, 0x24f6, 0x24c0, 0x24f5},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x24f5, 0x24f6, 0x24c0, 0x24f5}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBombs[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2cb2, 0x2cb3, 0x2cc2, 0x6cc2},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2cb2, 0x2cb3, 0x2cc2, 0x6cc2}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemMushroom[3] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2444, 0x2445, 0x2446, 0x2447},
|
||||
{0x203b, 0x203c, 0x203d, 0x203e},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2444, 0x2445, 0x2446, 0x2447}},
|
||||
{{0x203b, 0x203c, 0x203d, 0x203e}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemFireRod[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x24b0, 0x24b1, 0x24c0, 0x24c1},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x24b0, 0x24b1, 0x24c0, 0x24c1}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemIceRod[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2cb0, 0x2cbe, 0x2cc0, 0x2cc1},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2cb0, 0x2cbe, 0x2cc0, 0x2cc1}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBombos[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x287d, 0x287e, 0xe87e, 0xe87d},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x287d, 0x287e, 0xe87e, 0xe87d}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemEther[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2876, 0x2877, 0xE877, 0xE876},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2876, 0x2877, 0xE877, 0xE876}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemQuake[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2866, 0x2867, 0xE867, 0xE866},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2866, 0x2867, 0xE867, 0xE866}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemTorch[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x24bc, 0x24bd, 0x24cc, 0x24cd},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x24bc, 0x24bd, 0x24cc, 0x24cd}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemHammer[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x20b6, 0x20b7, 0x20c6, 0x20c7},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x20b6, 0x20b7, 0x20c6, 0x20c7}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemFlute[4] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x20d0, 0x20d1, 0x20e0, 0x20e1},
|
||||
{0x2cd4, 0x2cd5, 0x2ce4, 0x2ce5},
|
||||
{0x2cd4, 0x2cd5, 0x2ce4, 0x2ce5},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x20d0, 0x20d1, 0x20e0, 0x20e1}},
|
||||
{{0x2cd4, 0x2cd5, 0x2ce4, 0x2ce5}},
|
||||
{{0x2cd4, 0x2cd5, 0x2ce4, 0x2ce5}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBugNet[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x3c40, 0x3c41, 0x2842, 0x3c43},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x3c40, 0x3c41, 0x2842, 0x3c43}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBookMudora[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x3ca5, 0x3ca6, 0x3cd8, 0x3cd9},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x3ca5, 0x3ca6, 0x3cd8, 0x3cd9}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemCaneSomaria[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x24dc, 0x24dd, 0x24ec, 0x24ed},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x24dc, 0x24dd, 0x24ec, 0x24ed}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemCaneByrna[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2cdc, 0x2cdd, 0x2cec, 0x2ced},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2cdc, 0x2cdd, 0x2cec, 0x2ced}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemCape[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x24b4, 0x24b5, 0x24c4, 0x24c5},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x24b4, 0x24b5, 0x24c4, 0x24c5}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemMirror[4] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x28de, 0x28df, 0x28ee, 0x28ef},
|
||||
{0x2c62, 0x2c63, 0x2c72, 0x2c73},
|
||||
{0x2886, 0x2887, 0x2888, 0x2889},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x28de, 0x28df, 0x28ee, 0x28ef}},
|
||||
{{0x2c62, 0x2c63, 0x2c72, 0x2c73}},
|
||||
{{0x2886, 0x2887, 0x2888, 0x2889}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemGloves[3] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2130, 0x2131, 0x2140, 0x2141},
|
||||
{0x28da, 0x28db, 0x28ea, 0x28eb},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2130, 0x2131, 0x2140, 0x2141}},
|
||||
{{0x28da, 0x28db, 0x28ea, 0x28eb}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemBoots[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x3429, 0x342a, 0x342b, 0x342c},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x3429, 0x342a, 0x342b, 0x342c}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemFlippers[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2c9a, 0x2c9b, 0x2c9d, 0x2c9e},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2c9a, 0x2c9b, 0x2c9d, 0x2c9e}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemMoonPearl[2] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2433, 0x2434, 0x2435, 0x2436},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2433, 0x2434, 0x2435, 0x2436}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemEmpty[1] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemSword[5] = {
|
||||
{0x20f5, 0x20f5, 0x20f5, 0x20f5},
|
||||
{0x2c64, 0x2cce, 0x2c75, 0x3d25},
|
||||
{0x2c8a, 0x2c65, 0x2474, 0x3d26},
|
||||
{0x248a, 0x2465, 0x3c74, 0x2d48},
|
||||
{0x288a, 0x2865, 0x2c74, 0x2d39},
|
||||
{{0x20f5, 0x20f5, 0x20f5, 0x20f5}},
|
||||
{{0x2c64, 0x2cce, 0x2c75, 0x3d25}},
|
||||
{{0x2c8a, 0x2c65, 0x2474, 0x3d26}},
|
||||
{{0x248a, 0x2465, 0x3c74, 0x2d48}},
|
||||
{{0x288a, 0x2865, 0x2c74, 0x2d39}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemShield[4] = {
|
||||
{0x24f5, 0x24f5, 0x24f5, 0x24f5},
|
||||
{0x2cfd, 0x6cfd, 0x2cfe, 0x6cfe},
|
||||
{0x34ff, 0x74ff, 0x349f, 0x749f},
|
||||
{0x2880, 0x2881, 0x288d, 0x288e},
|
||||
{{0x24f5, 0x24f5, 0x24f5, 0x24f5}},
|
||||
{{0x2cfd, 0x6cfd, 0x2cfe, 0x6cfe}},
|
||||
{{0x34ff, 0x74ff, 0x349f, 0x749f}},
|
||||
{{0x2880, 0x2881, 0x288d, 0x288e}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemArmor[5] = {
|
||||
{0x3c68, 0x7c68, 0x3c78, 0x7c78},
|
||||
{0x2c68, 0x6c68, 0x2c78, 0x6c78},
|
||||
{0x2468, 0x6468, 0x2478, 0x6478},
|
||||
{{0x3c68, 0x7c68, 0x3c78, 0x7c78}},
|
||||
{{0x2c68, 0x6c68, 0x2c78, 0x6c78}},
|
||||
{{0x2468, 0x6468, 0x2478, 0x6478}},
|
||||
};
|
||||
|
||||
|
||||
@@ -605,46 +594,62 @@ static void Hud_EquipItemBelow(uint8 *item) { // 8ddf00
|
||||
} while (!Hud_DoWeHaveThisItem(*item));
|
||||
}
|
||||
|
||||
int GetCurrentItemButtonIndex() {
|
||||
if (enhanced_features0 & kFeatures0_SwitchLR) {
|
||||
return (joypad1L_last & kJoypadL_X) ? 1 :
|
||||
(joypad1L_last & kJoypadL_L) ? 2 :
|
||||
(joypad1L_last & kJoypadL_R) ? 3 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8 *GetCurrentItemButtonPtr(int i) {
|
||||
return (i == 0) ? &hud_cur_item :
|
||||
(i == 1) ? &hud_cur_item_x :
|
||||
(i == 2) ? &hud_cur_item_l : &hud_cur_item_r;
|
||||
}
|
||||
|
||||
void Hud_NormalMenu() { // 8ddf15
|
||||
timer_for_flashing_circle++;
|
||||
if (!BYTE(joypad1H_last))
|
||||
BYTE(hud_tmp1) = 0;
|
||||
|
||||
if (filtered_joypad_H & 0x10) { // start
|
||||
if (filtered_joypad_H & kJoypadH_Start) {
|
||||
overworld_map_state = 5;
|
||||
sound_effect_2 = 18;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow select to open the save/exit thing
|
||||
if (joypad1H_last & 0x20 && sram_progress_indicator) { // select
|
||||
if (joypad1H_last & kJoypadH_Select && sram_progress_indicator) {
|
||||
BG3VOFS_copy2 = -8;
|
||||
Hud_CloseMenu();
|
||||
DisplaySelectMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (joypad1H_last & 0x40 && !(joypad1L_last & 0x40) && (enhanced_features0 & kFeatures0_SwitchLR)) {
|
||||
if (filtered_joypad_H & 8) {
|
||||
if (joypad1H_last & kJoypadH_Y && !(joypad1L_last & kJoypadL_X) && (enhanced_features0 & kFeatures0_SwitchLR)) {
|
||||
if (filtered_joypad_H & kJoypadH_Up) {
|
||||
Hud_ReorderItem(kNewStyleInventory ? -6 : -5);
|
||||
} else if (filtered_joypad_H & 4) {
|
||||
} else if (filtered_joypad_H & kJoypadH_Down) {
|
||||
Hud_ReorderItem(kNewStyleInventory ? 6 : 5);
|
||||
} else if (filtered_joypad_H & 2) {
|
||||
} else if (filtered_joypad_H & kJoypadH_Left) {
|
||||
Hud_ReorderItem(-1);
|
||||
} else if (filtered_joypad_H & 1) {
|
||||
} else if (filtered_joypad_H & kJoypadH_Right) {
|
||||
Hud_ReorderItem(1);
|
||||
}
|
||||
} else if (!BYTE(hud_tmp1)) {
|
||||
// If the x button is down, then move the blue circle
|
||||
uint8 *item_p = (joypad1L_last & 0x40 && (enhanced_features0 & kFeatures0_SwitchLR)) ? &hud_cur_item_x : &hud_cur_item;
|
||||
// If Special Key button is down, then move their circle
|
||||
int btn_index = GetCurrentItemButtonIndex();
|
||||
uint8 *item_p = GetCurrentItemButtonPtr(btn_index);
|
||||
uint16 old_item = *item_p;
|
||||
if (filtered_joypad_H & 8) {
|
||||
if (filtered_joypad_H & kJoypadH_Up) {
|
||||
Hud_EquipItemAbove(item_p);
|
||||
} else if (filtered_joypad_H & 4) {
|
||||
} else if (filtered_joypad_H & kJoypadH_Down) {
|
||||
Hud_EquipItemBelow(item_p);
|
||||
} else if (filtered_joypad_H & 2) {
|
||||
} else if (filtered_joypad_H & kJoypadH_Left) {
|
||||
Hud_EquipPrevItem(item_p);
|
||||
} else if (filtered_joypad_H & 1) {
|
||||
} else if (filtered_joypad_H & kJoypadH_Right) {
|
||||
Hud_EquipNextItem(item_p);
|
||||
}
|
||||
BYTE(hud_tmp1) = filtered_joypad_H;
|
||||
@@ -753,11 +758,11 @@ void Hud_ExpandBottleMenu() { // 8de08c
|
||||
|
||||
void Hud_BottleMenu() { // 8de0df
|
||||
timer_for_flashing_circle++;
|
||||
if (filtered_joypad_H & 0x10) {
|
||||
if (filtered_joypad_H & kJoypadH_Start) {
|
||||
sound_effect_2 = 18;
|
||||
overworld_map_state = 5;
|
||||
} else if (filtered_joypad_H & 3) {
|
||||
if (filtered_joypad_H & 2) {
|
||||
} else if (filtered_joypad_H & (kJoypadH_Left | kJoypadH_Right)) {
|
||||
if (filtered_joypad_H & kJoypadH_Left) {
|
||||
Hud_EquipPrevItem(&hud_cur_item);
|
||||
} else {
|
||||
Hud_EquipNextItem(&hud_cur_item);
|
||||
@@ -771,10 +776,10 @@ void Hud_BottleMenu() { // 8de0df
|
||||
return;
|
||||
}
|
||||
Hud_DrawBottleMenu_Update();
|
||||
if (filtered_joypad_H & 12) {
|
||||
if (filtered_joypad_H & (kJoypadH_Down | kJoypadH_Up)) {
|
||||
uint8 old_val = link_item_bottle_index - 1, val = old_val;
|
||||
|
||||
if (filtered_joypad_H & 8) {
|
||||
if (filtered_joypad_H & kJoypadH_Up) {
|
||||
do {
|
||||
val = (val - 1) & 3;
|
||||
} while (!link_bottle_info[val]);
|
||||
@@ -923,22 +928,80 @@ static const ItemBoxGfx *Hud_GetIconForItem(int i) {
|
||||
return &kHudItemBoxGfxPtrs[i - 1][item_val];
|
||||
}
|
||||
|
||||
static void CopyTilesForSwitchLR(int switch_lr) {
|
||||
#define PV(a0,a1,a2,a3,a4,a5,a6,a7) ((a0 & 1) << 7 | (a0 >> 1 & 1) << 15 | (a1 & 1) << 6 | (a1 >> 1 & 1) << 14 | (a2 & 1) <<5 | (a2 >> 1&1) <<13 | (a3 & 1) << 4 | (a3>> 1 & 1) << 12 | (a4 & 1) << 3 | (a4 >> 1 & 1) << 11 | (a5 & 1) << 2 | (a5 >> 1 & 1) << 10 | (a6 & 1) << 1 | (a6 >> 1 & 1) << 9 | (a7 & 1) << 0 | (a7 >> 1 & 1) << 8)
|
||||
|
||||
if (switch_lr == 3) {
|
||||
static const uint16 kBytesForNewTile0xC_TopOfR[8] = {
|
||||
PV(1,1,1,1,1,1,3,3),
|
||||
PV(1,1,1,1,1,1,1,3),
|
||||
PV(1,1,1,1,1,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,1,1,1,1,3)
|
||||
};
|
||||
memcpy(&g_zenv.vram[0x7000 + 0xc * 8], kBytesForNewTile0xC_TopOfR, sizeof(kBytesForNewTile0xC_TopOfR));
|
||||
|
||||
static const uint16 kBytesForNewTile0xD_BottomofR[8] = {
|
||||
PV(1,1,1,1,1,1,3,3),
|
||||
PV(1,1,1,3,1,1,1,3),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1),
|
||||
PV(1,1,1,3,3,1,1,1)
|
||||
};
|
||||
memcpy(&g_zenv.vram[0x7000 + 0xd * 8], kBytesForNewTile0xD_BottomofR, sizeof(kBytesForNewTile0xD_BottomofR));
|
||||
} else if (switch_lr == 2) {
|
||||
static const uint16 kBytesForNewTile0xE_TopOfL[8] = {
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3)
|
||||
};
|
||||
memcpy(&g_zenv.vram[0x7000 + 0xe * 8], kBytesForNewTile0xE_TopOfL, sizeof(kBytesForNewTile0xE_TopOfL));
|
||||
|
||||
static const uint16 kBytesForNewTile0xF_BottomofL[8] = {
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,3,3,3,3,3),
|
||||
PV(1,1,1,1,1,1,1,1),
|
||||
PV(1,1,1,1,1,1,1,1),
|
||||
PV(1,1,1,1,1,1,1,1)
|
||||
};
|
||||
memcpy(&g_zenv.vram[0x7000 + 0xf * 8], kBytesForNewTile0xF_BottomofL, sizeof(kBytesForNewTile0xF_BottomofL));
|
||||
}
|
||||
#undef PV
|
||||
}
|
||||
|
||||
static const uint8 kSwitchLR_palettes[] = { 7, 3, 4, 4 };
|
||||
|
||||
void Hud_DrawYButtonItems() { // 8de3d9
|
||||
uint16 *dst = uvram_screen.row[0].col;
|
||||
int x = kNewStyleInventory ? 0 : 1;
|
||||
|
||||
bool is_x = (joypad1L_last & 0x40 && (enhanced_features0 & kFeatures0_SwitchLR));
|
||||
uint8 palette = is_x ? 3 : 7;
|
||||
Hud_DrawBox(dst, x, 5, 20 - x, 19, palette);
|
||||
int btn_index = GetCurrentItemButtonIndex();
|
||||
CopyTilesForSwitchLR(btn_index);
|
||||
Hud_DrawBox(dst, x, 5, 20 - x, 19, kSwitchLR_palettes[btn_index]);
|
||||
static const uint16 kEquipmentLetterTiles[4][2] = {
|
||||
{0x3CF0, 0x3CF1}, // Y
|
||||
{0x2CF0, 0x2CF0 | 0x8000}, // X
|
||||
{0x200E | 4 << 10, 0x200F | 4 << 10}, // L
|
||||
{0x200C | 4 << 10, 0x200D | 4 << 10} // R
|
||||
};
|
||||
|
||||
if (!kNewStyleInventory) {
|
||||
if (palette == 3) {
|
||||
dst[HUDXY(2, 6)] = 0x2CF0;
|
||||
dst[HUDXY(2, 7)] = 0x2CF0 | 0x8000;
|
||||
} else {
|
||||
dst[HUDXY(2, 6)] = 0x3CF0;
|
||||
dst[HUDXY(2, 7)] = 0x3CF1;
|
||||
}
|
||||
dst[HUDXY(2, 6)] = kEquipmentLetterTiles[btn_index][0];
|
||||
dst[HUDXY(2, 7)] = kEquipmentLetterTiles[btn_index][1];
|
||||
}
|
||||
dst[HUDXY(x + 2, 5)] = 0x246E;
|
||||
dst[HUDXY(x + 3, 5)] = 0x246F;
|
||||
@@ -1013,16 +1076,16 @@ void Hud_DrawProgressIcons_Pendants() { // 8de9d3
|
||||
0xa8fb, 0xa8f9, 0xa8f9, 0xa8f9, 0xa8f9, 0xa8f9, 0xa8f9, 0xa8f9, 0xa8f9, 0xe8fb,
|
||||
};
|
||||
static const ItemBoxGfx kHudPendants0[2] = {
|
||||
{0x313b, 0x313c, 0x313d, 0x313e},
|
||||
{0x252b, 0x252c, 0x252d, 0x252e}
|
||||
{{0x313b, 0x313c, 0x313d, 0x313e}},
|
||||
{{0x252b, 0x252c, 0x252d, 0x252e}},
|
||||
};
|
||||
static const ItemBoxGfx kHudPendants1[2] = {
|
||||
{0x313b, 0x313c, 0x313d, 0x313e},
|
||||
{0x2d2b, 0x2d2c, 0x2d2d, 0x2d2e}
|
||||
{{0x313b, 0x313c, 0x313d, 0x313e}},
|
||||
{{0x2d2b, 0x2d2c, 0x2d2d, 0x2d2e}},
|
||||
};
|
||||
static const ItemBoxGfx kHudPendants2[2] = {
|
||||
{0x313b, 0x313c, 0x313d, 0x313e},
|
||||
{0x3d2b, 0x3d2c, 0x3d2d, 0x3d2e}
|
||||
{{0x313b, 0x313c, 0x313d, 0x313e}},
|
||||
{{0x3d2b, 0x3d2c, 0x3d2d, 0x3d2e}},
|
||||
};
|
||||
uint16 *dst = uvram_screen.row[0].col + (kNewStyleInventory ? HUDXY(22, 11) : HUDXY(21, 11));
|
||||
Hud_DrawNxN(dst, kProgressIconPendantsBg, 10, 9);
|
||||
@@ -1139,20 +1202,18 @@ void Hud_DrawSelectedYButtonItem() { // 8deb3a
|
||||
|
||||
uint16 *dst_org = uvram_screen.row[0].col;
|
||||
uint16 *dst_box = dst_org + (kNewStyleInventory ? 1 : 0);
|
||||
|
||||
bool is_x = (joypad1L_last & 0x40 && (enhanced_features0 & kFeatures0_SwitchLR));
|
||||
uint8 palette = is_x ? 3 : 7;
|
||||
Hud_DrawBox(dst_box, 21, 5, 21 + 9, 10, palette);
|
||||
|
||||
int btn_index = GetCurrentItemButtonIndex();
|
||||
int item = *GetCurrentItemButtonPtr(btn_index);
|
||||
Hud_DrawBox(dst_box, 21, 5, 21 + 9, 10, kSwitchLR_palettes[btn_index]);
|
||||
|
||||
// Display either the current item or the item assigned
|
||||
// to the x key.
|
||||
int item = is_x ? hud_cur_item_x : hud_cur_item;
|
||||
|
||||
// to the x, l, or r key.
|
||||
if (item != 0) {
|
||||
uint16 *p = dst_org + kHudItemInVramPtr[Hud_GetItemPosition(item)];
|
||||
Hud_Copy2x2(dst_box + HUDXY(25, 6), p);
|
||||
if (timer_for_flashing_circle & 0x10)
|
||||
Hud_DrawFlashingCircle(p, palette);
|
||||
Hud_DrawFlashingCircle(p, kSwitchLR_palettes[btn_index]);
|
||||
}
|
||||
|
||||
const uint16 *src_p;
|
||||
@@ -1171,7 +1232,7 @@ void Hud_DrawSelectedYButtonItem() { // 8deb3a
|
||||
} else if (item == kHudItem_Shovel) {
|
||||
src_p = &kHudItemText[(13 - 1) * 16];
|
||||
} else if (item == 0) {
|
||||
src_p = is_x ? kNotAssignedItemText : &kHudItemText[(20 - 1) * 16];
|
||||
src_p = btn_index ? kNotAssignedItemText : &kHudItemText[(20 - 1) * 16];
|
||||
} else {
|
||||
src_p = &kHudItemText[(item - 1) * 16];
|
||||
}
|
||||
@@ -1195,10 +1256,10 @@ void Hud_DrawEquipmentBox() { // 8ded29
|
||||
memcpy(dst + HUDXY(22, 26), &kHudEquipmentDungeonItemText[8], 8 * sizeof(uint16));
|
||||
|
||||
static const ItemBoxGfx kHudItemHeartPieces[4] = {
|
||||
{0x2484, 0x6484, 0x2485, 0x6485},
|
||||
{0x24ad, 0x6484, 0x2485, 0x6485},
|
||||
{0x24ad, 0x6484, 0x24ae, 0x6485},
|
||||
{0x24ad, 0x64ad, 0x24ae, 0x6485},
|
||||
{{0x2484, 0x6484, 0x2485, 0x6485}},
|
||||
{{0x24ad, 0x6484, 0x2485, 0x6485}},
|
||||
{{0x24ad, 0x6484, 0x24ae, 0x6485}},
|
||||
{{0x24ad, 0x64ad, 0x24ae, 0x6485}},
|
||||
};
|
||||
if (cur_palace_index_x2 == 0xff) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
@@ -1210,14 +1271,14 @@ void Hud_DrawEquipmentBox() { // 8ded29
|
||||
Hud_DrawItem(dst + HUDXY(28, 23), &kHudItemArmor[link_armor]);
|
||||
|
||||
static const ItemBoxGfx kHudItemPalaceItem[2] = {
|
||||
{0x28d6, 0x68d6, 0x28e6, 0x28e7},
|
||||
{0x354b, 0x354c, 0x354d, 0x354e},
|
||||
{{0x28d6, 0x68d6, 0x28e6, 0x28e7}},
|
||||
{{0x354b, 0x354c, 0x354d, 0x354e}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemDungeonMap[1] = {
|
||||
{0x28de, 0x28df, 0x28ee, 0x28ef},
|
||||
{{0x28de, 0x28df, 0x28ee, 0x28ef}},
|
||||
};
|
||||
static const ItemBoxGfx kHudItemDungeonCompass[1] = {
|
||||
{0x24bf, 0x64bf, 0x2ccf, 0x6ccf},
|
||||
{{0x24bf, 0x64bf, 0x2ccf, 0x6ccf}},
|
||||
};
|
||||
if (cur_palace_index_x2 != 0xff &&
|
||||
(link_bigkey << (cur_palace_index_x2 >> 1)) & 0x8000) {
|
||||
@@ -1445,25 +1506,31 @@ const uint16 *Hud_GetItemBoxPtr(int item) {
|
||||
void Hud_HandleItemSwitchInputs() {
|
||||
if (!(enhanced_features0 & kFeatures0_SwitchLR))
|
||||
return;
|
||||
if (filtered_joypad_L & (0x20 | 0x10)) { // left/right shoulder
|
||||
int old_item = hud_cur_item;
|
||||
for (int i = 0; ; i++) {
|
||||
if (i >= kHudItemCount) {
|
||||
hud_cur_item = 0;
|
||||
break;
|
||||
|
||||
bool direction;
|
||||
|
||||
if (filtered_joypad_L & kJoypadL_L && (hud_cur_item_l == 0))
|
||||
direction = (hud_cur_item_r != 0);
|
||||
else if (filtered_joypad_L & kJoypadL_R && (hud_cur_item_r == 0))
|
||||
direction = true;
|
||||
else
|
||||
return;
|
||||
|
||||
uint8 item = hud_cur_item;
|
||||
for (int i = 0; i < kHudItemCount; i++) {
|
||||
if (!direction)
|
||||
Hud_GotoPrevItem(&item, 1);
|
||||
else
|
||||
Hud_GotoNextItem(&item, 1);
|
||||
if (Hud_DoWeHaveThisItem(item) && (!(enhanced_features0 & kFeatures0_SwitchLRLimit) || Hud_GetItemPosition(item) <= 3)) {
|
||||
if (item != hud_cur_item) {
|
||||
hud_cur_item = item;
|
||||
sound_effect_2 = 32;
|
||||
Hud_UpdateEquippedItem();
|
||||
Hud_UpdateItemBox();
|
||||
flag_update_hud_in_nmi++;
|
||||
}
|
||||
if (filtered_joypad_L & 0x20)
|
||||
Hud_GotoPrevItem(&hud_cur_item, 1);
|
||||
else
|
||||
Hud_GotoNextItem(&hud_cur_item, 1);
|
||||
if (Hud_DoWeHaveThisItem(hud_cur_item))
|
||||
break;
|
||||
}
|
||||
if (hud_cur_item != old_item) {
|
||||
sound_effect_2 = 32;
|
||||
Hud_UpdateEquippedItem();
|
||||
Hud_UpdateItemBox();
|
||||
flag_update_hud_in_nmi++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,5 +56,7 @@ void Hud_RestoreTorchBackground();
|
||||
void Hud_RebuildIndoor();
|
||||
void Hud_Rebuild();
|
||||
const uint16 *Hud_GetItemBoxPtr(int item);
|
||||
int GetCurrentItemButtonIndex();
|
||||
uint8 *GetCurrentItemButtonPtr(int i);
|
||||
|
||||
void Hud_HandleItemSwitchInputs();
|
||||
@@ -325,7 +325,7 @@ static const int8 kGraphicsLoadSp6[20] = {
|
||||
static const uint8 kMirrorWarp_LoadNext_NmiLoad[15] = {0, 14, 15, 16, 17, 0, 0, 0, 0, 0, 0, 18, 19, 20, 0};
|
||||
|
||||
static const uint8 *GetCompSpritePtr(int i) {
|
||||
return kSprGfx + *(uint32 *)(kSprGfx + i * 4);
|
||||
return kSprGfx(i).ptr;
|
||||
}
|
||||
|
||||
void ApplyPaletteFilter_bounce() {
|
||||
@@ -877,9 +877,9 @@ void Graphics_LoadChrHalfSlot() { // 80e3fa
|
||||
|
||||
int8 sp6 = kGraphicsLoadSp6[k - 1];
|
||||
if (sp6 >= 0) {
|
||||
palette_sp6 = sp6;
|
||||
palette_sp6r_indoors = sp6;
|
||||
if (k == 1) {
|
||||
palette_sp6 = 10;
|
||||
palette_sp6r_indoors = 10;
|
||||
overworld_palette_aux_or_main = 0x200;
|
||||
Palette_Load_SpriteEnvironment();
|
||||
flag_update_cgram_in_nmi++;
|
||||
@@ -932,7 +932,7 @@ void Graphics_LoadChrHalfSlot() { // 80e3fa
|
||||
}
|
||||
|
||||
void TransferFontToVRAM() { // 80e556
|
||||
memcpy(&g_zenv.vram[0x7000], kFontData, 0x800 * sizeof(uint16));
|
||||
memcpy(&g_zenv.vram[0x7000], FindIndexInMemblk(kDialogueFont(0), 0).ptr, 0x800 * sizeof(uint16));
|
||||
}
|
||||
|
||||
void Do3To4High(uint16 *vram_ptr, const uint8 *decomp_addr) { // 80e5af
|
||||
@@ -989,12 +989,19 @@ void LoadCommonSprites() { // 80e6b7
|
||||
}
|
||||
|
||||
int Decomp_spr(uint8 *dst, int gfx) { // 80e772
|
||||
return Decompress(dst, GetCompSpritePtr(gfx));
|
||||
if (gfx < 12)
|
||||
gfx = 12; // ensure it wont decode bad sheets.
|
||||
MemBlk blk = kSprGfx(gfx);
|
||||
const uint8 *sprite_data = GetCompSpritePtr(gfx);
|
||||
// If the size is not 0x600 then it's compressed
|
||||
if (gfx >= 103 || blk.size != 0x600)
|
||||
return Decompress(dst, blk.ptr);
|
||||
memcpy(dst, blk.ptr, 0x600);
|
||||
return 0x600;
|
||||
}
|
||||
|
||||
int Decomp_bg(uint8 *dst, int gfx) { // 80e78f
|
||||
const uint8 *p = kBgGfx + *(uint32 *)(kBgGfx + gfx * 4);
|
||||
return Decompress(dst, p);
|
||||
return Decompress(dst, kBgGfx(gfx).ptr);
|
||||
}
|
||||
|
||||
int Decompress(uint8 *dst, const uint8 *src) { // 80e79e
|
||||
@@ -1487,8 +1494,8 @@ void IrisSpotlight_ConfigureTable() { // 80f312
|
||||
if (main_module_index == 7 || main_module_index == 16) {
|
||||
if (!player_is_indoors)
|
||||
sound_effect_ambient = overworld_music[BYTE(overworld_screen_index)] >> 4;
|
||||
if (buffer_for_playing_songs != 0xff)
|
||||
music_control = buffer_for_playing_songs;
|
||||
if (queued_music_control != 0xff)
|
||||
music_control = queued_music_control;
|
||||
}
|
||||
main_module_index = saved_module_for_menu;
|
||||
if (main_module_index == 6)
|
||||
@@ -1649,7 +1656,7 @@ void Dungeon_UpdatePegGFXBuffer(int x, int y) { // 829773
|
||||
}
|
||||
|
||||
void Dungeon_HandleTranslucencyAndPalette() { // 82a1e9
|
||||
if (overworld_palette_swap_flag)
|
||||
if (palette_swap_flag)
|
||||
Palette_RevertTranslucencySwap();
|
||||
|
||||
CGWSEL_copy = 2;
|
||||
@@ -1683,9 +1690,9 @@ void Dungeon_HandleTranslucencyAndPalette() { // 82a1e9
|
||||
darkening_or_lightening_screen = 2;
|
||||
overworld_palette_aux_or_main = 0;
|
||||
Palette_Load_DungeonSet();
|
||||
Palette_Load_SpritePal0Left();
|
||||
Palette_Load_SpriteAux1();
|
||||
Palette_Load_SpriteAux2();
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_Sp5L();
|
||||
Palette_Load_Sp6L();
|
||||
subsubmodule_index += 1;
|
||||
}
|
||||
|
||||
@@ -1697,12 +1704,12 @@ void Overworld_LoadAllPalettes() { // 82c5b2
|
||||
overworld_palette_aux1_bp2to4_hi = 3;
|
||||
overworld_palette_aux2_bp5to7_hi = 3;
|
||||
overworld_palette_aux3_bp7_lo = 0;
|
||||
palette_sp6 = 5;
|
||||
overworld_palette_sp0 = 11;
|
||||
overworld_palette_swap_flag = 0;
|
||||
palette_sp6r_indoors = 5;
|
||||
palette_sp0l = 11;
|
||||
palette_swap_flag = 0;
|
||||
overworld_palette_aux_or_main = 0;
|
||||
Palette_BgAndFixedColor_Black();
|
||||
Palette_Load_SpritePal0Left();
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_SpriteMain();
|
||||
Palette_Load_OWBGMain();
|
||||
Palette_Load_OWBG1();
|
||||
@@ -1718,10 +1725,10 @@ void Overworld_LoadAllPalettes() { // 82c5b2
|
||||
void Dungeon_LoadPalettes() { // 82c630
|
||||
overworld_palette_aux_or_main = 0;
|
||||
Palette_BgAndFixedColor_Black();
|
||||
Palette_Load_SpritePal0Left();
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_SpriteMain();
|
||||
Palette_Load_SpriteAux1();
|
||||
Palette_Load_SpriteAux2();
|
||||
Palette_Load_Sp5L();
|
||||
Palette_Load_Sp6L();
|
||||
Palette_Load_Sword();
|
||||
Palette_Load_Shield();
|
||||
Palette_Load_SpriteEnvironment();
|
||||
@@ -1732,7 +1739,7 @@ void Dungeon_LoadPalettes() { // 82c630
|
||||
}
|
||||
|
||||
void Overworld_LoadPalettesInner() { // 82c65f
|
||||
overworld_pal_unk1 = dung_hdr_palette_1;
|
||||
overworld_pal_unk1 = palette_main_indoors;
|
||||
overworld_pal_unk2 = overworld_palette_aux3_bp7_lo;
|
||||
overworld_pal_unk3 = byte_7E0AB7;
|
||||
darkening_or_lightening_screen = 2;
|
||||
@@ -1753,13 +1760,13 @@ void Overworld_LoadAreaPalettesEx(uint8 x) { // 82c6ad
|
||||
overworld_palette_aux_or_main &= 0xff;
|
||||
Palette_Load_SpriteMain();
|
||||
Palette_Load_SpriteEnvironment();
|
||||
Palette_Load_SpriteAux1();
|
||||
Palette_Load_SpriteAux2();
|
||||
Palette_Load_Sp5L();
|
||||
Palette_Load_Sp6L();
|
||||
Palette_Load_Sword();
|
||||
Palette_Load_Shield();
|
||||
Palette_Load_LinkArmorAndGloves();
|
||||
overworld_palette_sp0 = (savegame_is_darkworld & 0x40) ? 3 : 1;
|
||||
Palette_Load_SpritePal0Left();
|
||||
palette_sp0l = (savegame_is_darkworld & 0x40) ? 3 : 1;
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_HUD();
|
||||
Palette_Load_OWBGMain();
|
||||
}
|
||||
@@ -1800,14 +1807,14 @@ void Overworld_LoadPalettes(uint8 bg, uint8 spr) { // 8ed5a8
|
||||
|
||||
d = kOwSprPalInfo + spr * 2;
|
||||
if (d[0] >= 0)
|
||||
sprite_aux1_palette = d[0];
|
||||
palette_sp5l = d[0];
|
||||
if (d[1] >= 0)
|
||||
sprite_aux2_palette = d[1];
|
||||
palette_sp6l = d[1];
|
||||
Palette_Load_OWBG1();
|
||||
Palette_Load_OWBG2();
|
||||
Palette_Load_OWBG3();
|
||||
Palette_Load_SpriteAux1();
|
||||
Palette_Load_SpriteAux2();
|
||||
Palette_Load_Sp5L();
|
||||
Palette_Load_Sp6L();
|
||||
}
|
||||
|
||||
void Palette_BgAndFixedColor_Black() { // 8ed5f4
|
||||
@@ -1852,7 +1859,7 @@ void Palette_AssertTranslucencySwap() { // 8ed657
|
||||
}
|
||||
|
||||
void Palette_SetTranslucencySwap(bool v) { // 8ed65c
|
||||
overworld_palette_swap_flag = v;
|
||||
palette_swap_flag = v;
|
||||
uint16 a, b;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
a = aux_palette_buffer[i + 0x80];
|
||||
@@ -1915,11 +1922,12 @@ void Filter_Majorly_Whiten_Bg() { // 8ed757
|
||||
}
|
||||
|
||||
uint16 Filter_Majorly_Whiten_Color(uint16 c) { // 8ed7fe
|
||||
int r = (c & 0x1f) + 14;
|
||||
int amt = (enhanced_features0 & kFeatures0_DimFlashes) ? 3 : 14;
|
||||
int r = (c & 0x1f) + amt;
|
||||
int g = (c & 0x3e0) + (amt << 5);
|
||||
int b = (c & 0x7c00) + (amt << 10);
|
||||
if (r > 0x1f) r = 0x1f;
|
||||
int g = (c & 0x3e0) + 0x1c0;
|
||||
if (g > 0x3e0) g = 0x3e0;
|
||||
int b = (c & 0x7c00) + 0x3800;
|
||||
if (b > 0x7c00) b = 0x7c00;
|
||||
return r | g | b;
|
||||
}
|
||||
@@ -1959,35 +1967,64 @@ void Palette_Restore_BG_And_HUD() { // 8ed8fb
|
||||
Palette_Restore_Coldata();
|
||||
}
|
||||
|
||||
void Palette_Load_SpritePal0Left() { // 9bec77
|
||||
const uint16 *src = kPalette_SpriteAux3 + overworld_palette_sp0 * 7;
|
||||
Palette_LoadSingle(src, overworld_palette_swap_flag ? 0x1e2 : 0x102, 6);
|
||||
/* Summary of sprite palette usage
|
||||
0l: kPalette_SpriteAux3[palette_sp0l]
|
||||
0r: kPalette_MiscSprite[7 / 9] or kPalette_DungBgMain[(palette_main_indoors >> 1) * 90]
|
||||
1 : common sprites
|
||||
2 : -"-
|
||||
3 : -"-
|
||||
4 : -"-
|
||||
5l: palette_sp5l
|
||||
5r: link sword/shield
|
||||
6l: palette_sp6l
|
||||
6r: kPalette_MiscSprite[6 / 8] or kPalette_MiscSprite[palette_sp6r_indoors]
|
||||
7 : link armor and gloves
|
||||
*/
|
||||
|
||||
enum {
|
||||
kPal_sp0l = 0x102,
|
||||
kPal_sp0r = 0x112,
|
||||
kPal_sp1to4 = 0x122, // This is used for 64 colors, colors switched if in darkworld mode
|
||||
kPal_sp5l = 0x1a2,
|
||||
kPal_Sword = 0x1b2,
|
||||
kPal_Shield = 0x1b8,
|
||||
kPal_sp6l = 0x1c2,
|
||||
kPal_sp6r = 0x1d2,
|
||||
kPal_sp7l = 0x1e2,
|
||||
kPal_sp7r = 0x1f2,
|
||||
kPal_ArmorGloves = 0x1e2,
|
||||
kPal_PalaceMap = 0x182,
|
||||
};
|
||||
|
||||
void Palette_Load_Sp0L() { // 9bec77
|
||||
const uint16 *src = kPalette_SpriteAux3 + palette_sp0l * 7;
|
||||
Palette_LoadSingle(src, palette_swap_flag ? kPal_sp7l : kPal_sp0l, 6);
|
||||
}
|
||||
|
||||
void Palette_Load_SpriteMain() { // 9bec9e
|
||||
const uint16 *src = kPalette_MainSpr + (overworld_screen_index & 0x40 ? 60 : 0);
|
||||
Palette_LoadMultiple(src, 0x122, 14, 3);
|
||||
Palette_LoadMultiple(src, kPal_sp1to4, 14, 3);
|
||||
}
|
||||
|
||||
void Palette_Load_SpriteAux1() { // 9becc5
|
||||
const uint16 *src = kPalette_SpriteAux1 + (sprite_aux1_palette) * 7;
|
||||
Palette_LoadSingle(src, 0x1A2, 6);
|
||||
void Palette_Load_Sp5L() { // 9becc5
|
||||
const uint16 *src = kPalette_SpriteAux1 + (palette_sp5l) * 7;
|
||||
Palette_LoadSingle(src, kPal_sp5l, 6);
|
||||
}
|
||||
|
||||
void Palette_Load_SpriteAux2() { // 9bece4
|
||||
const uint16 *src = kPalette_SpriteAux1 + (sprite_aux2_palette) * 7;
|
||||
Palette_LoadSingle(src, 0x1C2, 6);
|
||||
void Palette_Load_Sp6L() { // 9bece4
|
||||
const uint16 *src = kPalette_SpriteAux1 + (palette_sp6l) * 7;
|
||||
Palette_LoadSingle(src, kPal_sp6l, 6);
|
||||
}
|
||||
|
||||
void Palette_Load_Sword() { // 9bed03
|
||||
const uint16 *src = kPalette_Sword + ((int8)link_sword_type > 0 ? link_sword_type - 1 : 0) * 3; // wtf: zelda reads offset 0xff
|
||||
Palette_LoadMultiple_Arbitrary(src, 0x1b2, 2);
|
||||
Palette_LoadMultiple_Arbitrary(src, kPal_Sword, 2);
|
||||
flag_update_cgram_in_nmi += 1;
|
||||
}
|
||||
|
||||
void Palette_Load_Shield() { // 9bed29
|
||||
const uint16 *src = kPalette_Shield + (link_shield_type ? link_shield_type - 1 : 0) * 4;
|
||||
Palette_LoadMultiple_Arbitrary(src, 0x1b8, 3);
|
||||
Palette_LoadMultiple_Arbitrary(src, kPal_Shield, 3);
|
||||
flag_update_cgram_in_nmi += 1;
|
||||
}
|
||||
|
||||
@@ -1998,26 +2035,28 @@ void Palette_Load_SpriteEnvironment() { // 9bed6e
|
||||
Palette_MiscSprite_Outdoors();
|
||||
}
|
||||
|
||||
// avoid renaming in assets.dat
|
||||
#define kPalette_MiscSprite kPalette_MiscSprite_Indoors
|
||||
|
||||
void Palette_Load_SpriteEnvironment_Dungeon() { // 9bed72
|
||||
const uint16 *src = kPalette_MiscSprite_Indoors + palette_sp6 * 7;
|
||||
Palette_LoadSingle(src, 0x1d2, 6);
|
||||
const uint16 *src = kPalette_MiscSprite + palette_sp6r_indoors * 7;
|
||||
Palette_LoadSingle(src, kPal_sp6r, 6);
|
||||
}
|
||||
|
||||
void Palette_MiscSprite_Outdoors() { // 9bed91
|
||||
int t = (overworld_screen_index & 0x40) ? 9 : 7;
|
||||
const uint16 *src = kPalette_MiscSprite_Indoors + t * 7;
|
||||
Palette_LoadSingle(src, overworld_palette_swap_flag ? 0x1f2 : 0x112, 6);
|
||||
src = kPalette_MiscSprite_Indoors + (t - 1) * 7;
|
||||
Palette_LoadSingle(src, 0x1d2, 6);
|
||||
const uint16 *src = kPalette_MiscSprite + t * 7;
|
||||
Palette_LoadSingle(src, palette_swap_flag ? kPal_sp7r : kPal_sp0r, 6);
|
||||
Palette_LoadSingle(src - 7, kPal_sp6r, 6);
|
||||
}
|
||||
|
||||
void Palette_Load_DungeonMapSprite() { // 9beddd
|
||||
Palette_LoadMultiple(kPalette_PalaceMapSpr, 0x182, 6, 2);
|
||||
Palette_LoadMultiple(kPalette_PalaceMapSpr, kPal_PalaceMap, 6, 2);
|
||||
}
|
||||
|
||||
void Palette_Load_LinkArmorAndGloves() { // 9bedf9
|
||||
const uint16 *src = kPalette_ArmorAndGloves + link_armor * 15;
|
||||
Palette_LoadMultiple_Arbitrary(src, 0x1e2, 14);
|
||||
Palette_LoadMultiple_Arbitrary(src, kPal_ArmorGloves, 14);
|
||||
Palette_UpdateGlovesColor();
|
||||
}
|
||||
|
||||
@@ -2037,9 +2076,9 @@ void Palette_Load_HUD() { // 9bee52
|
||||
}
|
||||
|
||||
void Palette_Load_DungeonSet() { // 9bee74
|
||||
const uint16 *src = kPalette_DungBgMain + (dung_hdr_palette_1 >> 1) * 90;
|
||||
const uint16 *src = kPalette_DungBgMain + (palette_main_indoors >> 1) * 90;
|
||||
Palette_LoadMultiple(src, 0x42, 14, 5);
|
||||
Palette_LoadSingle(src, overworld_palette_swap_flag ? 0x1f2 : 0x112, 6);
|
||||
Palette_LoadSingle(src, palette_swap_flag ? kPal_sp7r : kPal_sp0r, 6);
|
||||
}
|
||||
|
||||
void Palette_Load_OWBG3() { // 9beea8
|
||||
@@ -132,10 +132,10 @@ uint16 Filter_Majorly_Whiten_Color(uint16 c);
|
||||
void Palette_Restore_BG_From_Flash();
|
||||
void Palette_Restore_Coldata();
|
||||
void Palette_Restore_BG_And_HUD();
|
||||
void Palette_Load_SpritePal0Left();
|
||||
void Palette_Load_Sp0L();
|
||||
void Palette_Load_SpriteMain();
|
||||
void Palette_Load_SpriteAux1();
|
||||
void Palette_Load_SpriteAux2();
|
||||
void Palette_Load_Sp5L();
|
||||
void Palette_Load_Sp6L();
|
||||
void Palette_Load_Sword();
|
||||
void Palette_Load_Shield();
|
||||
void Palette_Load_SpriteEnvironment();
|
||||
@@ -24,24 +24,27 @@
|
||||
#include "config.h"
|
||||
#include "assets.h"
|
||||
#include "load_gfx.h"
|
||||
#include "util.h"
|
||||
#include "audio.h"
|
||||
|
||||
static bool g_run_without_emu = 0;
|
||||
|
||||
// Forwards
|
||||
static bool LoadRom(const char *filename);
|
||||
static void LoadLinkGraphics();
|
||||
static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer);
|
||||
static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture *texture, bool fullscreen);
|
||||
static void RenderNumber(uint8 *dst, size_t pitch, int n, bool big);
|
||||
static void HandleInput(int keyCode, int modCode, bool pressed);
|
||||
static void HandleCommand(uint32 j, bool pressed);
|
||||
static int RemapSdlButton(int button);
|
||||
static void HandleGamepadInput(int button, bool pressed);
|
||||
static void HandleGamepadAxisInput(int gamepad_id, int axis, int value);
|
||||
static void OpenOneGamepad(int i);
|
||||
static void HandleVolumeAdjustment(int volume_adjustment);
|
||||
static void LoadAssets();
|
||||
static void SwitchDirectory();
|
||||
|
||||
enum {
|
||||
kDefaultFullscreen = 0,
|
||||
kDefaultWindowScale = 2,
|
||||
kMaxWindowScale = 10,
|
||||
kDefaultFreq = 44100,
|
||||
kDefaultChannels = 2,
|
||||
@@ -49,10 +52,9 @@ enum {
|
||||
};
|
||||
|
||||
static const char kWindowTitle[] = "The Legend of Zelda: A Link to the Past";
|
||||
|
||||
static uint32 g_win_flags = SDL_WINDOW_RESIZABLE;
|
||||
static SDL_Window *g_window;
|
||||
static SDL_Renderer *g_renderer;
|
||||
|
||||
static uint8 g_paused, g_turbo, g_replay_turbo = true, g_cursor = true;
|
||||
static uint8 g_current_window_scale;
|
||||
static uint8 g_gamepad_buttons;
|
||||
@@ -60,26 +62,23 @@ static int g_input1_state;
|
||||
static bool g_display_perf;
|
||||
static int g_curr_fps;
|
||||
static int g_ppu_render_flags = 0;
|
||||
static bool g_run_without_emu = false;
|
||||
static int g_snes_width, g_snes_height;
|
||||
static int g_sdl_audio_mixer_volume = SDL_MIX_MAXVOLUME;
|
||||
static struct RendererFuncs g_renderer_funcs;
|
||||
static uint32 g_gamepad_modifiers;
|
||||
static uint16 g_gamepad_last_cmd[kGamepadBtn_Count];
|
||||
|
||||
void NORETURN Die(const char *error) {
|
||||
#if defined(NDEBUG) && defined(_WIN32)
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, kWindowTitle, error, NULL);
|
||||
#endif
|
||||
fprintf(stderr, "Error: %s\n", error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void SetButtonState(int button, bool pressed) {
|
||||
// set key in constroller
|
||||
if (pressed) {
|
||||
g_input1_state |= 1 << button;
|
||||
} else {
|
||||
g_input1_state &= ~(1 << button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ChangeWindowScale(int scale_step) {
|
||||
if ((SDL_GetWindowFlags(g_window) & (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED)) != 0)
|
||||
return;
|
||||
int screen = SDL_GetWindowDisplayIndex(g_window);
|
||||
if (screen < 0) screen = 0;
|
||||
int max_scale = kMaxWindowScale;
|
||||
@@ -94,14 +93,14 @@ void ChangeWindowScale(int scale_step) {
|
||||
bt = 31;
|
||||
}
|
||||
// Allow a scale level slightly above the max that fits on screen
|
||||
int mw = (bounds.w - bl - br + (g_snes_width / kDefaultWindowScale) / 4) / (g_snes_width / kDefaultWindowScale);
|
||||
int mh = (bounds.h - bt - bb + (g_snes_height / kDefaultWindowScale) / 4) / (g_snes_height / kDefaultWindowScale);
|
||||
int mw = (bounds.w - bl - br + g_snes_width / 4) / g_snes_width;
|
||||
int mh = (bounds.h - bt - bb + g_snes_height / 4) / g_snes_height;
|
||||
max_scale = IntMin(mw, mh);
|
||||
}
|
||||
int new_scale = IntMax(IntMin(g_current_window_scale + scale_step, max_scale), 1);
|
||||
g_current_window_scale = new_scale;
|
||||
int w = new_scale * (g_snes_width / kDefaultWindowScale);
|
||||
int h = new_scale * (g_snes_height / kDefaultWindowScale);
|
||||
int w = new_scale * g_snes_width;
|
||||
int h = new_scale * g_snes_height;
|
||||
|
||||
//SDL_RenderSetLogicalSize(g_renderer, w, h);
|
||||
SDL_SetWindowSize(g_window, w, h);
|
||||
@@ -117,19 +116,47 @@ void ChangeWindowScale(int scale_step) {
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_HitTestResult HitTestCallback(SDL_Window *win, const SDL_Point *area, void *data) {
|
||||
#define RESIZE_BORDER 20
|
||||
static SDL_HitTestResult HitTestCallback(SDL_Window *win, const SDL_Point *pt, void *data) {
|
||||
uint32 flags = SDL_GetWindowFlags(win);
|
||||
return ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0 || (flags & SDL_WINDOW_FULLSCREEN) == 0) &&
|
||||
(SDL_GetModState() & KMOD_CTRL) != 0 ? SDL_HITTEST_DRAGGABLE : SDL_HITTEST_NORMAL;
|
||||
if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0 || (flags & SDL_WINDOW_FULLSCREEN) != 0)
|
||||
return SDL_HITTEST_NORMAL;
|
||||
|
||||
if ((SDL_GetModState() & KMOD_CTRL) != 0)
|
||||
return SDL_HITTEST_DRAGGABLE;
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSize(win, &w, &h);
|
||||
|
||||
if (pt->y < RESIZE_BORDER) {
|
||||
return (pt->x < RESIZE_BORDER) ? SDL_HITTEST_RESIZE_TOPLEFT :
|
||||
(pt->x >= w - RESIZE_BORDER) ? SDL_HITTEST_RESIZE_TOPRIGHT : SDL_HITTEST_RESIZE_TOP;
|
||||
} else if (pt->y >= h - RESIZE_BORDER) {
|
||||
return (pt->x < RESIZE_BORDER) ? SDL_HITTEST_RESIZE_BOTTOMLEFT :
|
||||
(pt->x >= w - RESIZE_BORDER) ? SDL_HITTEST_RESIZE_BOTTOMRIGHT : SDL_HITTEST_RESIZE_BOTTOM;
|
||||
} else {
|
||||
if (pt->x < RESIZE_BORDER) {
|
||||
return SDL_HITTEST_RESIZE_LEFT;
|
||||
} else if (pt->x >= w - RESIZE_BORDER) {
|
||||
return SDL_HITTEST_RESIZE_RIGHT;
|
||||
}
|
||||
}
|
||||
return SDL_HITTEST_NORMAL;
|
||||
}
|
||||
|
||||
static bool RenderScreenWithPerf(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
|
||||
bool rv;
|
||||
static void DrawPpuFrameWithPerf() {
|
||||
int render_scale = PpuGetCurrentRenderScale(g_zenv.ppu, g_ppu_render_flags);
|
||||
uint8 *pixel_buffer = 0;
|
||||
int pitch = 0;
|
||||
|
||||
g_renderer_funcs.BeginDraw(g_snes_width * render_scale,
|
||||
g_snes_height * render_scale,
|
||||
&pixel_buffer, &pitch);
|
||||
if (g_display_perf || g_config.display_perf_title) {
|
||||
static float history[64], average;
|
||||
static int history_pos;
|
||||
uint64 before = SDL_GetPerformanceCounter();
|
||||
rv = ZeldaDrawPpuFrame(pixel_buffer, pitch, render_flags);
|
||||
ZeldaDrawPpuFrame(pixel_buffer, pitch, g_ppu_render_flags);
|
||||
uint64 after = SDL_GetPerformanceCounter();
|
||||
float v = (double)SDL_GetPerformanceFrequency() / (after - before);
|
||||
average += v - history[history_pos];
|
||||
@@ -137,35 +164,11 @@ static bool RenderScreenWithPerf(uint8 *pixel_buffer, size_t pitch, uint32 rende
|
||||
history_pos = (history_pos + 1) & 63;
|
||||
g_curr_fps = average * (1.0f / 64);
|
||||
} else {
|
||||
rv = ZeldaDrawPpuFrame(pixel_buffer, pitch, render_flags);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Go some steps up and find zelda3.ini
|
||||
static void SwitchDirectory() {
|
||||
char buf[4096];
|
||||
if (!getcwd(buf, sizeof(buf) - 32))
|
||||
return;
|
||||
size_t pos = strlen(buf);
|
||||
|
||||
for (int step = 0; pos != 0 && step < 3; step++) {
|
||||
memcpy(buf + pos, "/zelda3.ini", 12);
|
||||
FILE *f = fopen(buf, "rb");
|
||||
if (f) {
|
||||
fclose(f);
|
||||
buf[pos] = 0;
|
||||
if (step != 0) {
|
||||
printf("Found zelda3.ini in %s\n", buf);
|
||||
int err = chdir(buf);
|
||||
(void)err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
pos--;
|
||||
while (pos != 0 && buf[pos] != '/' && buf[pos] != '\\')
|
||||
pos--;
|
||||
ZeldaDrawPpuFrame(pixel_buffer, pitch, g_ppu_render_flags);
|
||||
}
|
||||
if (g_display_perf)
|
||||
RenderNumber(pixel_buffer + pitch * render_scale, pitch, g_curr_fps, render_scale == 4);
|
||||
g_renderer_funcs.EndDraw();
|
||||
}
|
||||
|
||||
static SDL_mutex *g_audio_mutex;
|
||||
@@ -197,20 +200,100 @@ static void SDLCALL AudioCallback(void *userdata, Uint8 *stream, int len) {
|
||||
SDL_UnlockMutex(g_audio_mutex);
|
||||
}
|
||||
|
||||
// State for sdl renderer
|
||||
static SDL_Renderer *g_renderer;
|
||||
static SDL_Texture *g_texture;
|
||||
static SDL_Rect g_sdl_renderer_rect;
|
||||
|
||||
static bool SdlRenderer_Init(SDL_Window *window) {
|
||||
|
||||
if (g_config.shader)
|
||||
fprintf(stderr, "Warning: Shaders are supported only with the OpenGL backend\n");
|
||||
|
||||
SDL_Renderer *renderer = SDL_CreateRenderer(g_window, -1,
|
||||
g_config.output_method == kOutputMethod_SDLSoftware ? SDL_RENDERER_SOFTWARE :
|
||||
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if (renderer == NULL) {
|
||||
printf("Failed to create renderer: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_RendererInfo renderer_info;
|
||||
SDL_GetRendererInfo(renderer, &renderer_info);
|
||||
if (kDebugFlag) {
|
||||
printf("Supported texture formats:");
|
||||
for (int i = 0; i < renderer_info.num_texture_formats; i++)
|
||||
printf(" %s", SDL_GetPixelFormatName(renderer_info.texture_formats[i]));
|
||||
printf("\n");
|
||||
}
|
||||
g_renderer = renderer;
|
||||
if (!g_config.ignore_aspect_ratio)
|
||||
SDL_RenderSetLogicalSize(renderer, g_snes_width, g_snes_height);
|
||||
if (g_config.linear_filtering)
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
|
||||
|
||||
int tex_mult = (g_ppu_render_flags & kPpuRenderFlags_4x4Mode7) ? 4 : 1;
|
||||
g_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
|
||||
g_snes_width * tex_mult, g_snes_height * tex_mult);
|
||||
if (g_texture == NULL) {
|
||||
printf("Failed to create texture: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SdlRenderer_Destroy() {
|
||||
SDL_DestroyTexture(g_texture);
|
||||
SDL_DestroyRenderer(g_renderer);
|
||||
}
|
||||
|
||||
static void SdlRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) {
|
||||
g_sdl_renderer_rect.w = width;
|
||||
g_sdl_renderer_rect.h = height;
|
||||
if (SDL_LockTexture(g_texture, &g_sdl_renderer_rect, (void **)pixels, pitch) != 0) {
|
||||
printf("Failed to lock texture: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void SdlRenderer_EndDraw() {
|
||||
|
||||
// uint64 before = SDL_GetPerformanceCounter();
|
||||
SDL_UnlockTexture(g_texture);
|
||||
// uint64 after = SDL_GetPerformanceCounter();
|
||||
// float v = (double)(after - before) / SDL_GetPerformanceFrequency();
|
||||
// printf("%f ms\n", v * 1000);
|
||||
SDL_RenderClear(g_renderer);
|
||||
SDL_RenderCopy(g_renderer, g_texture, &g_sdl_renderer_rect, NULL);
|
||||
SDL_RenderPresent(g_renderer); // vsyncs to 60 FPS?
|
||||
}
|
||||
|
||||
static const struct RendererFuncs kSdlRendererFuncs = {
|
||||
&SdlRenderer_Init,
|
||||
&SdlRenderer_Destroy,
|
||||
&SdlRenderer_BeginDraw,
|
||||
&SdlRenderer_EndDraw,
|
||||
};
|
||||
|
||||
void OpenGLRenderer_Create(struct RendererFuncs *funcs, bool use_opengl_es);
|
||||
|
||||
#undef main
|
||||
int main(int argc, char** argv) {
|
||||
SwitchDirectory();
|
||||
ParseConfigFile();
|
||||
AfterConfigParse();
|
||||
argc--, argv++;
|
||||
const char *config_file = NULL;
|
||||
if (argc >= 2 && strcmp(argv[0], "--config") == 0) {
|
||||
config_file = argv[1];
|
||||
argc -= 2, argv += 2;
|
||||
} else {
|
||||
SwitchDirectory();
|
||||
}
|
||||
ParseConfigFile(config_file);
|
||||
LoadAssets();
|
||||
LoadLinkGraphics();
|
||||
|
||||
ZeldaInitialize();
|
||||
g_zenv.ppu->extraLeftRight = UintMin(g_config.extended_aspect_ratio, kPpuExtraLeftRight);
|
||||
g_snes_width = 2 * (g_config.extended_aspect_ratio * 2 + 256);
|
||||
g_snes_height = (g_config.extend_y ? 240 : 224) * 2;
|
||||
g_snes_width = (g_config.extended_aspect_ratio * 2 + 256);
|
||||
g_snes_height = (g_config.extend_y ? 240 : 224);
|
||||
|
||||
|
||||
// Delay actually setting those features in ram until any snapshots finish playing.
|
||||
@@ -220,7 +303,8 @@ int main(int argc, char** argv) {
|
||||
g_config.enhanced_mode7 * kPpuRenderFlags_4x4Mode7 |
|
||||
g_config.extend_y * kPpuRenderFlags_Height240 |
|
||||
g_config.no_sprite_limits * kPpuRenderFlags_NoSpriteLimits;
|
||||
msu_enabled = g_config.enable_msu;
|
||||
ZeldaEnableMsu(g_config.enable_msu);
|
||||
ZeldaSetLanguage(g_config.language);
|
||||
|
||||
if (g_config.fullscreen == 1)
|
||||
g_win_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
@@ -249,8 +333,16 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
bool custom_size = g_config.window_width != 0 && g_config.window_height != 0;
|
||||
int window_width = custom_size ? g_config.window_width : g_current_window_scale * (g_snes_width / kDefaultWindowScale);
|
||||
int window_height = custom_size ? g_config.window_height : g_current_window_scale * (g_snes_height / kDefaultWindowScale);
|
||||
int window_width = custom_size ? g_config.window_width : g_current_window_scale * g_snes_width;
|
||||
int window_height = custom_size ? g_config.window_height : g_current_window_scale * g_snes_height;
|
||||
|
||||
if (g_config.output_method == kOutputMethod_OpenGL ||
|
||||
g_config.output_method == kOutputMethod_OpenGL_ES) {
|
||||
g_win_flags |= SDL_WINDOW_OPENGL;
|
||||
OpenGLRenderer_Create(&g_renderer_funcs, (g_config.output_method == kOutputMethod_OpenGL_ES));
|
||||
} else {
|
||||
g_renderer_funcs = kSdlRendererFuncs;
|
||||
}
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow(kWindowTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height, g_win_flags);
|
||||
if(window == NULL) {
|
||||
@@ -259,30 +351,11 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
g_window = window;
|
||||
SDL_SetWindowHitTest(window, HitTestCallback, NULL);
|
||||
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if(renderer == NULL) {
|
||||
printf("Failed to create renderer: %s\n", SDL_GetError());
|
||||
|
||||
if (!g_renderer_funcs.Initialize(window))
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_RendererInfo renderer_info;
|
||||
SDL_GetRendererInfo(renderer, &renderer_info);
|
||||
printf("Supported texture formats:");
|
||||
for (int i = 0; i < renderer_info.num_texture_formats; i++)
|
||||
printf(" %s", SDL_GetPixelFormatName(renderer_info.texture_formats[i]));
|
||||
printf("\n");
|
||||
|
||||
g_renderer = renderer;
|
||||
if (!g_config.ignore_aspect_ratio)
|
||||
SDL_RenderSetLogicalSize(renderer, g_snes_width, g_snes_height);
|
||||
SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, g_snes_width * 2, g_snes_height * 2);
|
||||
if(texture == NULL) {
|
||||
printf("Failed to create texture: %s\n", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
|
||||
|
||||
SDL_AudioDeviceID device;
|
||||
SDL_AudioDeviceID device = 0;
|
||||
SDL_AudioSpec want = { 0 }, have;
|
||||
g_audio_mutex = SDL_CreateMutex();
|
||||
if (!g_audio_mutex) Die("No mutex");
|
||||
@@ -303,8 +376,8 @@ int main(int argc, char** argv) {
|
||||
g_audiobuffer = malloc(g_frames_per_block * have.channels * sizeof(int16));
|
||||
}
|
||||
|
||||
if (argc >= 2 && !g_run_without_emu)
|
||||
LoadRom(argv[1]);
|
||||
if (argc >= 1 && !g_run_without_emu)
|
||||
LoadRom(argv[0]);
|
||||
|
||||
#if defined(_WIN32)
|
||||
_mkdir("saves");
|
||||
@@ -337,18 +410,19 @@ int main(int argc, char** argv) {
|
||||
HandleGamepadAxisInput(event.caxis.which, event.caxis.axis, event.caxis.value);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
HandleGamepadInput(event.cbutton.button, event.cbutton.state == SDL_PRESSED);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
HandleGamepadInput(event.cbutton.button, event.cbutton.state == SDL_PRESSED);
|
||||
case SDL_CONTROLLERBUTTONUP: {
|
||||
int b = RemapSdlButton(event.cbutton.button);
|
||||
if (b >= 0)
|
||||
HandleGamepadInput(b, event.type == SDL_CONTROLLERBUTTONDOWN);
|
||||
break;
|
||||
}
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (((g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0 || (g_win_flags & SDL_WINDOW_FULLSCREEN) == 0) && event.wheel.y != 0 && SDL_GetModState() & KMOD_CTRL)
|
||||
if (SDL_GetModState() & KMOD_CTRL && event.wheel.y != 0)
|
||||
ChangeWindowScale(event.wheel.y > 0 ? 1 : -1);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if (event.button.button == SDL_BUTTON_LEFT && event.button.state == SDL_PRESSED && event.button.clicks == 2) {
|
||||
if (((g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0 || (g_win_flags & SDL_WINDOW_FULLSCREEN) == 0) && SDL_GetModState() & KMOD_SHIFT) {
|
||||
if ((g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0 && (g_win_flags & SDL_WINDOW_FULLSCREEN) == 0 && SDL_GetModState() & KMOD_SHIFT) {
|
||||
g_win_flags ^= SDL_WINDOW_BORDERLESS;
|
||||
SDL_SetWindowBordered(g_window, (g_win_flags & SDL_WINDOW_BORDERLESS) == 0);
|
||||
}
|
||||
@@ -368,7 +442,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
if (g_paused != audiopaused) {
|
||||
audiopaused = g_paused;
|
||||
SDL_PauseAudioDevice(device, audiopaused);
|
||||
if (device)
|
||||
SDL_PauseAudioDevice(device, audiopaused);
|
||||
}
|
||||
|
||||
if (g_paused) {
|
||||
@@ -392,13 +467,17 @@ int main(int argc, char** argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderScreen(window, renderer, texture, (g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0);
|
||||
SDL_RenderPresent(renderer); // vsyncs to 60 FPS?
|
||||
DrawPpuFrameWithPerf();
|
||||
|
||||
if (g_config.display_perf_title) {
|
||||
char title[60];
|
||||
snprintf(title, sizeof(title), "%s | FPS: %d", kWindowTitle, g_curr_fps);
|
||||
SDL_SetWindowTitle(g_window, title);
|
||||
}
|
||||
|
||||
// if vsync isn't working, delay manually
|
||||
curTick = SDL_GetTicks();
|
||||
|
||||
|
||||
if (!g_config.disable_frame_delay) {
|
||||
static const uint8 delays[3] = { 17, 17, 16 }; // 60 fps
|
||||
lastTick += delays[frameCtr % 3];
|
||||
@@ -409,6 +488,7 @@ int main(int argc, char** argv) {
|
||||
lastTick = curTick - 500;
|
||||
delta = 500;
|
||||
}
|
||||
// printf("Sleeping %d\n", delta);
|
||||
SDL_Delay(delta);
|
||||
} else if (curTick - lastTick > 500) {
|
||||
lastTick = curTick;
|
||||
@@ -424,11 +504,11 @@ int main(int argc, char** argv) {
|
||||
SDL_CloseAudioDevice(device);
|
||||
}
|
||||
|
||||
|
||||
SDL_DestroyMutex(g_audio_mutex);
|
||||
free(g_audiobuffer);
|
||||
SDL_DestroyTexture(texture);
|
||||
SDL_DestroyRenderer(renderer);
|
||||
|
||||
g_renderer_funcs.Destroy();
|
||||
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
//SaveConfigFile();
|
||||
@@ -480,51 +560,15 @@ static void RenderNumber(uint8 *dst, size_t pitch, int n, bool big) {
|
||||
RenderDigit(dst + (i << big), pitch, *s - '0', 0xffffff, big);
|
||||
}
|
||||
|
||||
static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture *texture, bool fullscreen) {
|
||||
uint8* pixels = NULL;
|
||||
int pitch = 0;
|
||||
uint64 t0 = SDL_GetPerformanceCounter();
|
||||
if(SDL_LockTexture(texture, NULL, (void**)&pixels, &pitch) != 0) {
|
||||
printf("Failed to lock texture: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
uint64 t1 = SDL_GetPerformanceCounter();
|
||||
bool hq = RenderScreenWithPerf(pixels, pitch, g_ppu_render_flags);
|
||||
if (g_display_perf) {
|
||||
RenderNumber(pixels + (pitch * 2 << hq), pitch, g_curr_fps, hq);
|
||||
}
|
||||
if (g_config.display_perf_title) {
|
||||
char title[60];
|
||||
snprintf(title, sizeof(title), "%s | FPS: %d", kWindowTitle, g_curr_fps);
|
||||
SDL_SetWindowTitle(window, title);
|
||||
}
|
||||
uint64 t2 = SDL_GetPerformanceCounter();
|
||||
SDL_UnlockTexture(texture);
|
||||
uint64 t3 = SDL_GetPerformanceCounter();
|
||||
SDL_RenderClear(renderer);
|
||||
uint64 t4 = SDL_GetPerformanceCounter();
|
||||
SDL_Rect src_rect = { 0, 0, g_snes_width, g_snes_height };
|
||||
SDL_RenderCopy(renderer, texture, hq ? NULL : &src_rect, NULL);
|
||||
uint64 t5 = SDL_GetPerformanceCounter();
|
||||
|
||||
double f = 1e3 / (double)SDL_GetPerformanceFrequency();
|
||||
if (0) printf("RenderPerf %6.2f %6.2f %6.2f %6.2f %6.2f\n",
|
||||
(t1 - t0) * f,
|
||||
(t2 - t1) * f,
|
||||
(t3 - t2) * f,
|
||||
(t4 - t3) * f,
|
||||
(t5 - t4) * f
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void HandleCommand_Locked(uint32 j, bool pressed);
|
||||
|
||||
static void HandleCommand(uint32 j, bool pressed) {
|
||||
if (j <= kKeys_Controls_Last) {
|
||||
static const uint8 kKbdRemap[12] = { 4, 5, 6, 7, 2, 3, 8, 0, 9, 1, 10, 11 };
|
||||
SetButtonState(kKbdRemap[j], pressed);
|
||||
static const uint8 kKbdRemap[] = { 0, 4, 5, 6, 7, 2, 3, 8, 0, 9, 1, 10, 11 };
|
||||
if (pressed)
|
||||
g_input1_state |= 1 << kKbdRemap[j];
|
||||
else
|
||||
g_input1_state &= ~(1 << kKbdRemap[j]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -540,6 +584,15 @@ static void HandleCommand(uint32 j, bool pressed) {
|
||||
SDL_UnlockMutex(g_audio_mutex);
|
||||
}
|
||||
|
||||
void ZeldaApuLock() {
|
||||
SDL_LockMutex(g_audio_mutex);
|
||||
}
|
||||
|
||||
void ZeldaApuUnlock() {
|
||||
SDL_UnlockMutex(g_audio_mutex);
|
||||
}
|
||||
|
||||
|
||||
static void HandleCommand_Locked(uint32 j, bool pressed) {
|
||||
if (!pressed)
|
||||
return;
|
||||
@@ -598,7 +651,7 @@ static void HandleCommand_Locked(uint32 j, bool pressed) {
|
||||
|
||||
static void HandleInput(int keyCode, int keyMod, bool pressed) {
|
||||
int j = FindCmdForSdlKey(keyCode, keyMod);
|
||||
if (j >= 0)
|
||||
if (j != 0)
|
||||
HandleCommand(j, pressed);
|
||||
}
|
||||
|
||||
@@ -610,23 +663,37 @@ static void OpenOneGamepad(int i) {
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleGamepadInput(int button, bool pressed) {
|
||||
static int RemapSdlButton(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: SetButtonState(0, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_X: SetButtonState(1, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: SetButtonState(2, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_START: SetButtonState(3, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: SetButtonState(4, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: SetButtonState(5, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: SetButtonState(6, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: SetButtonState(7, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_B: SetButtonState(8, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_Y: SetButtonState(9, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: SetButtonState(10, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: SetButtonState(11, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_A: return kGamepadBtn_A;
|
||||
case SDL_CONTROLLER_BUTTON_B: return kGamepadBtn_B;
|
||||
case SDL_CONTROLLER_BUTTON_X: return kGamepadBtn_X;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return kGamepadBtn_Y;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: return kGamepadBtn_Back;
|
||||
case SDL_CONTROLLER_BUTTON_GUIDE: return kGamepadBtn_Guide;
|
||||
case SDL_CONTROLLER_BUTTON_START: return kGamepadBtn_Start;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return kGamepadBtn_L3;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return kGamepadBtn_R3;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return kGamepadBtn_L1;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return kGamepadBtn_R1;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return kGamepadBtn_DpadUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return kGamepadBtn_DpadDown;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return kGamepadBtn_DpadLeft;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return kGamepadBtn_DpadRight;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleGamepadInput(int button, bool pressed) {
|
||||
if (!!(g_gamepad_modifiers & (1 << button)) == pressed)
|
||||
return;
|
||||
g_gamepad_modifiers ^= 1 << button;
|
||||
if (pressed)
|
||||
g_gamepad_last_cmd[button] = FindCmdForGamepadButton(button, g_gamepad_modifiers);
|
||||
if (g_gamepad_last_cmd[button] != 0)
|
||||
HandleCommand(g_gamepad_last_cmd[button], pressed);
|
||||
}
|
||||
|
||||
static void HandleVolumeAdjustment(int volume_adjustment) {
|
||||
#if SYSTEM_VOLUME_MIXER_AVAILABLE
|
||||
int current_volume = GetApplicationVolume();
|
||||
@@ -690,12 +757,15 @@ static void HandleGamepadAxisInput(int gamepad_id, int axis, int value) {
|
||||
buttons = kSegmentToButtons[(uint8)(angle + 16 + 64) >> 5];
|
||||
}
|
||||
g_gamepad_buttons = buttons;
|
||||
} else if ((axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) {
|
||||
if (value < 12000 || value >= 16000) // hysteresis
|
||||
HandleGamepadInput(axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT ? kGamepadBtn_L2 : kGamepadBtn_R2, value >= 12000);
|
||||
}
|
||||
}
|
||||
|
||||
static bool LoadRom(const char *filename) {
|
||||
size_t length = 0;
|
||||
uint8 *file = ReadFile(filename, &length);
|
||||
uint8 *file = ReadWholeFile(filename, &length);
|
||||
if(!file) Die("Failed to read file");
|
||||
bool result = EmuInitialize(file, length);
|
||||
free(file);
|
||||
@@ -727,7 +797,7 @@ static void LoadLinkGraphics() {
|
||||
if (g_config.link_graphics) {
|
||||
fprintf(stderr, "Loading Link Graphics: %s\n", g_config.link_graphics);
|
||||
size_t length = 0;
|
||||
uint8 *file = ReadFile(g_config.link_graphics, &length);
|
||||
uint8 *file = ReadWholeFile(g_config.link_graphics, &length);
|
||||
if (file == NULL || !ParseLinkGraphics(file, length))
|
||||
Die("Unable to load file");
|
||||
free(file);
|
||||
@@ -740,10 +810,20 @@ uint32 g_asset_sizes[kNumberOfAssets];
|
||||
|
||||
static void LoadAssets() {
|
||||
size_t length = 0;
|
||||
uint8 *data = ReadFile("tables/zelda3_assets.dat", &length);
|
||||
if (!data)
|
||||
data = ReadFile("zelda3_assets.dat", &length);
|
||||
if (!data) Die("Failed to read zelda3_assets.dat");
|
||||
uint8 *data = ReadWholeFile("zelda3_assets.dat", &length);
|
||||
if (!data) {
|
||||
size_t bps_length, bps_src_length;
|
||||
uint8 *bps, *bps_src;
|
||||
bps = ReadWholeFile("zelda3_assets.bps", &bps_length);
|
||||
if (!bps)
|
||||
Die("Failed to read zelda3_assets.dat. Please see the README for information about how you get this file.");
|
||||
bps_src = ReadWholeFile("zelda3.sfc", &bps_src_length);
|
||||
if (!bps_src)
|
||||
Die("Missing file: zelda3.sfc");
|
||||
data = ApplyBps(bps_src, bps_src_length, bps, bps_length, &length);
|
||||
if (!data)
|
||||
Die("Unable to apply zelda3_assets.bps. Please make sure you got the right version of 'zelda3.sfc'");
|
||||
}
|
||||
|
||||
static const char kAssetsSig[] = { kAssets_Sig };
|
||||
|
||||
@@ -763,4 +843,40 @@ static void LoadAssets() {
|
||||
g_asset_ptrs[i] = data + offset;
|
||||
offset += size;
|
||||
}
|
||||
|
||||
if (g_config.features0 & kFeatures0_DimFlashes) { // patch dungeon floor palettes
|
||||
kPalette_DungBgMain[0x484] = 0x70;
|
||||
kPalette_DungBgMain[0x485] = 0x95;
|
||||
kPalette_DungBgMain[0x486] = 0x57;
|
||||
}
|
||||
}
|
||||
|
||||
// Go some steps up and find zelda3.ini
|
||||
static void SwitchDirectory() {
|
||||
char buf[4096];
|
||||
if (!getcwd(buf, sizeof(buf) - 32))
|
||||
return;
|
||||
size_t pos = strlen(buf);
|
||||
|
||||
for (int step = 0; pos != 0 && step < 3; step++) {
|
||||
memcpy(buf + pos, "/zelda3.ini", 12);
|
||||
FILE *f = fopen(buf, "rb");
|
||||
if (f) {
|
||||
fclose(f);
|
||||
buf[pos] = 0;
|
||||
if (step != 0) {
|
||||
printf("Found zelda3.ini in %s\n", buf);
|
||||
int err = chdir(buf);
|
||||
(void)err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
pos--;
|
||||
while (pos != 0 && buf[pos] != '/' && buf[pos] != '\\')
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
|
||||
MemBlk FindInAssetArray(int asset, int idx) {
|
||||
return FindIndexInMemblk((MemBlk) { g_asset_ptrs[asset], g_asset_sizes[asset] }, idx);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@
|
||||
const uint8 *GetDungmapFloorLayout();
|
||||
uint8 GetOtherDungmapInfo(int count);
|
||||
void DungMap_4();
|
||||
const uint8 *GetCurrentTextPtr();
|
||||
void Module_Messaging_6();
|
||||
void OverworldMap_SetupHdma();
|
||||
const uint8 *GetLightOverworldTilemap();
|
||||
@@ -63,8 +62,6 @@ void WorldMap_ExitMap();
|
||||
void WorldMap_SetUpHDMA();
|
||||
void WorldMap_FillTilemapWithEF();
|
||||
void WorldMap_HandleSprites();
|
||||
bool WorldMap_CalculateOamCoordinates(PointU8 *pt);
|
||||
void WorldMap_HandleSpriteBlink(int spr, uint8 r11_ext, uint8 r12_flags, uint8 r13_char, uint8 r14_x, uint8 r15_y);
|
||||
bool OverworldMap_CheckForPendant(int k);
|
||||
bool OverworldMap_CheckForCrystal(int k);
|
||||
void Module0E_03_DungeonMap();
|
||||
@@ -110,9 +107,7 @@ void RenderText_Draw_BorderIncremental();
|
||||
void RenderText_Draw_CharacterTilemap();
|
||||
void RenderText_Draw_MessageCharacters();
|
||||
void RenderText_Draw_Finish();
|
||||
void RenderText_Draw_RenderCharacter_All();
|
||||
void VWF_RenderSingle();
|
||||
void VWF_RenderCharacter();
|
||||
void RenderText_Draw_Choose2LowOr3();
|
||||
void RenderText_Draw_ChooseItem();
|
||||
void RenderText_FindYItem_Previous();
|
||||
@@ -121,10 +116,7 @@ void RenderText_DrawSelectedYItem();
|
||||
void RenderText_Draw_Choose2HiOr3();
|
||||
void RenderText_Draw_Choose3();
|
||||
void RenderText_Draw_Choose1Or2();
|
||||
void RenderText_Draw_Scroll();
|
||||
void RenderText_Draw_Command7B();
|
||||
void RenderText_Draw_ABunchOfSpaces();
|
||||
void RenderText_Draw_EmptyBuffer();
|
||||
bool RenderText_Draw_Scroll();
|
||||
void RenderText_SetDefaultWindowPosition();
|
||||
void RenderText_DrawBorderInitialize();
|
||||
uint16 *RenderText_DrawBorderRow(uint16 *d, int y);
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "snes/snes_regs.h"
|
||||
#include "snes/ppu.h"
|
||||
#include "assets.h"
|
||||
#include "audio.h"
|
||||
|
||||
static const uint8 kNmiVramAddrs[] = {
|
||||
0, 0, 4, 8, 12, 8, 12, 0, 4, 0, 8, 4, 12, 4, 12, 0,
|
||||
@@ -105,15 +106,15 @@ void WritePpuRegisters() {
|
||||
zelda_ppu_write(BG34NBA, 7);
|
||||
}
|
||||
|
||||
void Interrupt_NMI(uint16 joypad_input) { // 8080c9
|
||||
static void Interrupt_NMI_AudioParts_Locked() {
|
||||
if (music_control == 0) {
|
||||
if (zelda_apu_read(APUI00) == last_music_control)
|
||||
zelda_apu_write(APUI00, 0);
|
||||
// Zelda causes unwanted music change when going in a portal. last_music_control doesn't hold the
|
||||
// song but the last applied effect
|
||||
} else if (music_control != (enhanced_features0 & kFeatures0_MiscBugFixes ? music_unk1 : last_music_control)) {
|
||||
// if (zelda_apu_read(APUI00) == last_music_control)
|
||||
// zelda_apu_write(APUI00, 0);
|
||||
// Zelda causes unwanted music change when going in a portal. last_music_control doesn't hold the
|
||||
// song but the last applied effect
|
||||
} else if (!ZeldaIsPlayingMusicTrackWithBug(music_control)) {
|
||||
last_music_control = music_control;
|
||||
ZeldaPlayMsuAudioTrack();
|
||||
ZeldaPlayMsuAudioTrack(music_control);
|
||||
if (music_control < 0xf2)
|
||||
music_unk1 = music_control;
|
||||
music_control = 0;
|
||||
@@ -132,6 +133,12 @@ void Interrupt_NMI(uint16 joypad_input) { // 8080c9
|
||||
sound_effect_1 = 0;
|
||||
sound_effect_2 = 0;
|
||||
|
||||
}
|
||||
|
||||
void Interrupt_NMI(uint16 joypad_input) { // 8080c9
|
||||
|
||||
Interrupt_NMI_AudioParts_Locked();
|
||||
|
||||
if (!nmi_boolean) {
|
||||
nmi_boolean = true;
|
||||
NMI_DoUpdates();
|
||||
@@ -281,7 +288,7 @@ void NMI_UploadBG3Text() { // 808ce4
|
||||
}
|
||||
|
||||
void NMI_UpdateOWScroll() { // 808d13
|
||||
uint8 *src = (uint8 *)uvram.data, *src_org = src;
|
||||
uint8 *src = (uint8 *)uvram.data;
|
||||
int f = WORD(src[0]);
|
||||
int step = (f & 0x8000) ? 32 : 1;
|
||||
int len = f & 0x3fff;
|
||||
@@ -384,7 +391,6 @@ void NMI_RunTileMapUpdateDMA(int dst) { // 808fc9
|
||||
}
|
||||
|
||||
void NMI_UploadDarkWorldMap() { // 808ff3
|
||||
static const uint16 kLightWorldTileMapSrcs[4] = { 0, 0x20, 0x1000, 0x1020 };
|
||||
const uint8 *src = g_ram + 0x1000;
|
||||
int t = 0x810;
|
||||
for (int i = 0x20; i; i--) {
|
||||
266
src/opengl.c
Normal file
266
src/opengl.c
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "third_party/gl_core/gl_core_3_1.h"
|
||||
#include <SDL.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include "types.h"
|
||||
#include "util.h"
|
||||
#include "glsl_shader.h"
|
||||
#include "config.h"
|
||||
|
||||
#define CODE(...) #__VA_ARGS__
|
||||
|
||||
static SDL_Window *g_window;
|
||||
static uint8 *g_screen_buffer;
|
||||
static size_t g_screen_buffer_size;
|
||||
static int g_draw_width, g_draw_height;
|
||||
static unsigned int g_program, g_VAO;
|
||||
static GlTextureWithSize g_texture;
|
||||
static GlslShader *g_glsl_shader;
|
||||
static bool g_opengl_es;
|
||||
|
||||
static void GL_APIENTRY MessageCallback(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
GLenum severity,
|
||||
GLsizei length,
|
||||
const GLchar *message,
|
||||
const void *userParam) {
|
||||
if (type == GL_DEBUG_TYPE_OTHER)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
|
||||
(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
|
||||
type, severity, message);
|
||||
if (type == GL_DEBUG_TYPE_ERROR)
|
||||
Die("OpenGL error!\n");
|
||||
}
|
||||
|
||||
static bool OpenGLRenderer_Init(SDL_Window *window) {
|
||||
g_window = window;
|
||||
SDL_GLContext context = SDL_GL_CreateContext(window);
|
||||
(void)context;
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
ogl_LoadFunctions();
|
||||
|
||||
if (!g_opengl_es) {
|
||||
if (!ogl_IsVersionGEQ(3, 3))
|
||||
Die("You need OpenGL 3.3");
|
||||
} else {
|
||||
int majorVersion = 0, minorVersion = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion);
|
||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion);
|
||||
if (majorVersion < 3)
|
||||
Die("You need OpenGL ES 3.0");
|
||||
|
||||
}
|
||||
|
||||
if (kDebugFlag) {
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
glDebugMessageCallback(MessageCallback, 0);
|
||||
}
|
||||
|
||||
glGenTextures(1, &g_texture.gl_texture);
|
||||
|
||||
static const float kVertices[] = {
|
||||
// positions // texture coords
|
||||
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // bottom left
|
||||
1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // top right
|
||||
1.0f, -1.0f, 0.0f, 1.0f, 1.0f, // bottom right
|
||||
};
|
||||
|
||||
// create a vertex buffer object
|
||||
unsigned int vbo;
|
||||
glGenBuffers(1, &vbo);
|
||||
|
||||
// vertex array object
|
||||
glGenVertexArrays(1, &g_VAO);
|
||||
// 1. bind Vertex Array Object
|
||||
glBindVertexArray(g_VAO);
|
||||
// 2. copy our vertices array in a buffer for OpenGL to use
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW);
|
||||
// position attribute
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
// texture coord attribute
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// vertex shader
|
||||
const GLchar *vs_code_core = "#version 330 core\n" CODE(
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
out vec2 TexCoord;
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
|
||||
}
|
||||
);
|
||||
|
||||
const GLchar *vs_code_es = "#version 300 es\n" CODE(
|
||||
layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
out vec2 TexCoord;
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
|
||||
}
|
||||
);
|
||||
|
||||
const GLchar *vs_code = g_opengl_es ? vs_code_es : vs_code_core;
|
||||
unsigned int vs = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vs, 1, &vs_code, NULL);
|
||||
glCompileShader(vs);
|
||||
|
||||
int success;
|
||||
char infolog[512];
|
||||
glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
glGetShaderInfoLog(vs, 512, NULL, infolog);
|
||||
printf("%s\n", infolog);
|
||||
}
|
||||
|
||||
// fragment shader
|
||||
const GLchar *fs_code_core = "#version 330 core\n" CODE(
|
||||
out vec4 FragColor;
|
||||
in vec2 TexCoord;
|
||||
// texture samplers
|
||||
uniform sampler2D texture1;
|
||||
void main() {
|
||||
FragColor = texture(texture1, TexCoord);
|
||||
}
|
||||
);
|
||||
|
||||
const GLchar *fs_code_es = "#version 300 es\n" CODE(
|
||||
precision mediump float;
|
||||
out vec4 FragColor;
|
||||
in vec2 TexCoord;
|
||||
// texture samplers
|
||||
uniform sampler2D texture1;
|
||||
void main() {
|
||||
FragColor = texture(texture1, TexCoord);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const GLchar *fs_code = g_opengl_es ? fs_code_es : fs_code_core;
|
||||
unsigned int fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fs, 1, &fs_code, NULL);
|
||||
glCompileShader(fs);
|
||||
|
||||
glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
glGetShaderInfoLog(fs, 512, NULL, infolog);
|
||||
printf("%s\n", infolog);
|
||||
}
|
||||
|
||||
// create program
|
||||
int program = g_program = glCreateProgram();
|
||||
glAttachShader(program, vs);
|
||||
glAttachShader(program, fs);
|
||||
glLinkProgram(program);
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &success);
|
||||
|
||||
if (!success) {
|
||||
glGetProgramInfoLog(program, 512, NULL, infolog);
|
||||
printf("%s\n", infolog);
|
||||
}
|
||||
|
||||
if (g_config.shader)
|
||||
g_glsl_shader = GlslShader_CreateFromFile(g_config.shader, g_opengl_es);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void OpenGLRenderer_Destroy() {
|
||||
}
|
||||
|
||||
static void OpenGLRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) {
|
||||
int size = width * height;
|
||||
|
||||
if (size > g_screen_buffer_size) {
|
||||
g_screen_buffer_size = size;
|
||||
free(g_screen_buffer);
|
||||
g_screen_buffer = malloc(size * 4);
|
||||
}
|
||||
|
||||
g_draw_width = width;
|
||||
g_draw_height = height;
|
||||
*pixels = g_screen_buffer;
|
||||
*pitch = width * 4;
|
||||
}
|
||||
|
||||
static void OpenGLRenderer_EndDraw() {
|
||||
int drawable_width, drawable_height;
|
||||
|
||||
SDL_GL_GetDrawableSize(g_window, &drawable_width, &drawable_height);
|
||||
|
||||
int viewport_width = drawable_width, viewport_height = drawable_height;
|
||||
|
||||
if (!g_config.ignore_aspect_ratio) {
|
||||
if (viewport_width * g_draw_height < viewport_height * g_draw_width)
|
||||
viewport_height = viewport_width * g_draw_height / g_draw_width; // limit height
|
||||
else
|
||||
viewport_width = viewport_height * g_draw_width / g_draw_height; // limit width
|
||||
}
|
||||
|
||||
int viewport_x = (drawable_width - viewport_width) >> 1;
|
||||
int viewport_y = (viewport_height - viewport_height) >> 1;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, g_texture.gl_texture);
|
||||
if (g_draw_width == g_texture.width && g_draw_height == g_texture.height) {
|
||||
if (!g_opengl_es)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_draw_width, g_draw_height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, g_screen_buffer);
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_draw_width, g_draw_height, GL_BGRA, GL_UNSIGNED_BYTE, g_screen_buffer);
|
||||
} else {
|
||||
g_texture.width = g_draw_width;
|
||||
g_texture.height = g_draw_height;
|
||||
if (!g_opengl_es)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g_draw_width, g_draw_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, g_screen_buffer);
|
||||
else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g_draw_width, g_draw_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, g_screen_buffer);
|
||||
}
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (g_glsl_shader == NULL) {
|
||||
glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
|
||||
glUseProgram(g_program);
|
||||
int filter = g_config.linear_filtering ? GL_LINEAR : GL_NEAREST;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
glBindVertexArray(g_VAO);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
} else {
|
||||
GlslShader_Render(g_glsl_shader, &g_texture, viewport_x, viewport_y, viewport_width, viewport_height);
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(g_window);
|
||||
}
|
||||
|
||||
static const struct RendererFuncs kOpenGLRendererFuncs = {
|
||||
&OpenGLRenderer_Init,
|
||||
&OpenGLRenderer_Destroy,
|
||||
&OpenGLRenderer_BeginDraw,
|
||||
&OpenGLRenderer_EndDraw,
|
||||
};
|
||||
|
||||
void OpenGLRenderer_Create(struct RendererFuncs *funcs, bool use_opengl_es) {
|
||||
g_opengl_es = use_opengl_es;
|
||||
if (!g_opengl_es) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
} else {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
}
|
||||
*funcs = kOpenGLRendererFuncs;
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ void PreOverworld_LoadProperties() { // 8283c7
|
||||
if (dr != 0 && dr != 0xe1) {
|
||||
dark:
|
||||
xt = 0xf3;
|
||||
if (buffer_for_playing_songs == 0xf2)
|
||||
if (queued_music_control == 0xf2)
|
||||
goto setsong;
|
||||
xt = sram_progress_indicator < 2 ? 3 : 2;
|
||||
}
|
||||
@@ -456,7 +456,7 @@ dark:
|
||||
xt = 4;
|
||||
}
|
||||
setsong:
|
||||
buffer_for_playing_songs = xt;
|
||||
queued_music_control = xt;
|
||||
DecompressAnimatedOverworldTiles(ow_anim_tiles);
|
||||
InitializeTilesets();
|
||||
OverworldLoadScreensPaletteSet();
|
||||
@@ -605,11 +605,11 @@ void Dungeon_PrepExitWithSpotlight() { // 8299ca
|
||||
Ancilla_TerminateWaterfallSplashes();
|
||||
link_y_coord_exit = link_y_coord;
|
||||
}
|
||||
uint8 m = GetEntranceMusicTrack(which_entrance);
|
||||
uint8 m = ZeldaGetEntranceMusicTrack(which_entrance);
|
||||
if (m != 3 || (m = sram_progress_indicator) >= 2) {
|
||||
if (m != 0xf2)
|
||||
m = 0xf1;
|
||||
else if (music_unk1 == 0xc)
|
||||
if (m != 0xf2) // fade to 0x40
|
||||
m = 0xf1; // fade to zero
|
||||
else if (music_unk1 == 12)
|
||||
m = 7;
|
||||
music_control = m;
|
||||
}
|
||||
@@ -813,21 +813,21 @@ after:
|
||||
y >>= 1;
|
||||
Dungeon_ResetTorchBackgroundAndPlayerInner();
|
||||
map16_load_src_off &= kSwitchAreaTab0[y];
|
||||
uint16 pushed = current_area_of_player + kSwitchAreaTab3[y];
|
||||
map16_load_src_off += kSwitchAreaTab1[(y * 128 | pushed) >> 1];
|
||||
int pushed = ((current_area_of_player + kSwitchAreaTab3[y]) >> 1) & 0x3f;
|
||||
map16_load_src_off += kSwitchAreaTab1[y * 64 + pushed];
|
||||
|
||||
uint8 old_screen = overworld_screen_index;
|
||||
if (old_screen == 0x2a)
|
||||
sound_effect_ambient = 0x80;
|
||||
|
||||
uint8 new_area = kOverworldAreaHeads[pushed >> 1] | savegame_is_darkworld;
|
||||
uint8 new_area = kOverworldAreaHeads[pushed] | savegame_is_darkworld;
|
||||
BYTE(overworld_screen_index) = new_area;
|
||||
BYTE(overworld_area_index) = new_area;
|
||||
if (!savegame_is_darkworld || link_item_moon_pearl) {
|
||||
uint8 music = overworld_music[new_area];
|
||||
if ((music & 0xf0) == 0)
|
||||
sound_effect_ambient = 5;
|
||||
if ((music & 0xf) != music_unk1)
|
||||
if (!ZeldaIsPlayingMusicTrack(music & 0xf))
|
||||
music_control = 0xf1;
|
||||
}
|
||||
Overworld_LoadGFXAndScreenSize();
|
||||
@@ -1055,7 +1055,7 @@ void Overworld_StartMosaicTransition() { // 82ae5e
|
||||
switch (subsubmodule_index) {
|
||||
case 0:
|
||||
if (BYTE(overworld_screen_index) != 0x80) {
|
||||
if ((overworld_music[BYTE(overworld_screen_index)] & 0xf) != music_unk1)
|
||||
if (!ZeldaIsPlayingMusicTrack(overworld_music[BYTE(overworld_screen_index)] & 0xf))
|
||||
music_control = 0xf1;
|
||||
}
|
||||
ResetTransitionPropsAndAdvance_ResetInterface();
|
||||
@@ -1202,7 +1202,7 @@ void Module09_FadeBackInFromMosaic() { // 82b0d2
|
||||
if (BYTE(overworld_screen_index) != 0x80 && BYTE(overworld_screen_index) != 0x2a) {
|
||||
uint8 m = overworld_music[BYTE(overworld_screen_index)];
|
||||
sound_effect_ambient = (m >> 4) ? (m >> 4) : 5;
|
||||
if ((m & 0xf) != music_unk1)
|
||||
if (!ZeldaIsPlayingMusicTrack(m & 0xf))
|
||||
music_control = (m & 0xf);
|
||||
}
|
||||
submodule_index = 8;
|
||||
@@ -1449,7 +1449,7 @@ void Module09_2E_Whirlpool() { // 82b40f
|
||||
overworld_palette_aux_or_main = 0;
|
||||
Palette_Load_SpriteMain();
|
||||
Palette_Load_SpriteEnvironment();
|
||||
Palette_Load_SpritePal0Left();
|
||||
Palette_Load_Sp0L();
|
||||
Palette_Load_HUD();
|
||||
Palette_Load_OWBGMain();
|
||||
uint8 sc = overworld_screen_index;
|
||||
@@ -1506,14 +1506,16 @@ void Module09_2A_00_ScrollToLand() { // 82b532
|
||||
uint16 x = link_x_coord, xd = 0;
|
||||
if (x != link_x_coord_cached) {
|
||||
int16 d = (x > link_x_coord_cached) ? -1 : 1;
|
||||
((x += d) != link_x_coord_cached) && (x += d);
|
||||
if ((x += d) != link_x_coord_cached)
|
||||
x += d;
|
||||
xd = x - link_x_coord;
|
||||
link_x_coord = x;
|
||||
}
|
||||
uint16 y = link_y_coord, yd = 0;
|
||||
if (y != link_y_coord_cached) {
|
||||
int16 d = (y > link_y_coord_cached) ? -1 : 1;
|
||||
((y += d) != link_y_coord_cached) && (y += d);
|
||||
if ((y += d) != link_y_coord_cached)
|
||||
y += d;
|
||||
yd = y - link_y_coord;
|
||||
link_y_coord = y;
|
||||
}
|
||||
@@ -2447,23 +2449,20 @@ void Overworld_DecompressAndDrawAllQuadrants() { // 82f54a
|
||||
}
|
||||
|
||||
static const uint8 *GetOverworldHibytes(int i) {
|
||||
return kOverworld_Hibytes_Comp + *(uint32 *)(kOverworld_Hibytes_Comp + i * 4);
|
||||
return kOverworld_Hibytes_Comp(i).ptr;
|
||||
}
|
||||
|
||||
static const uint8 *GetOverworldLobytes(int i) {
|
||||
return kOverworld_Lobytes_Comp + *(uint32 *)(kOverworld_Lobytes_Comp + i * 4);
|
||||
return kOverworld_Lobytes_Comp(i).ptr;
|
||||
}
|
||||
|
||||
|
||||
void Overworld_DecompressAndDrawOneQuadrant(uint16 *dst, int screen) { // 82f595
|
||||
int rv;
|
||||
|
||||
|
||||
rv = Decompress_bank02(&g_ram[0x14400], GetOverworldHibytes(screen));
|
||||
Decompress_bank02(&g_ram[0x14400], GetOverworldHibytes(screen));
|
||||
for (int i = 0; i < 256; i++)
|
||||
g_ram[0x14001 + i * 2] = g_ram[0x14400 + i];
|
||||
|
||||
rv = Decompress_bank02(&g_ram[0x14400], GetOverworldLobytes(screen));
|
||||
Decompress_bank02(&g_ram[0x14400], GetOverworldLobytes(screen));
|
||||
for (int i = 0; i < 256; i++)
|
||||
g_ram[0x14000 + i * 2] = g_ram[0x14400 + i];
|
||||
|
||||
@@ -40,7 +40,10 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||
SRC_DIR := ../..
|
||||
TARGET := zelda3
|
||||
BUILD := bin
|
||||
SOURCES := $(SRC_DIR) $(SRC_DIR)/snes
|
||||
SOURCES := $(SRC_DIR) $(SRC_DIR)/snes $(SRC_DIR)/third_party/gl_core $(SRC_DIR)/third_party/opus-1.3.1-stripped
|
||||
|
||||
CFILES := $(wildcard $(SRC_DIR)/*.c $(SRC_DIR)/snes/*.c) $(SRC_DIR)/third_party/gl_core/gl_core_3_1.c $(SRC_DIR)/third_party/opus-1.3.1-stripped/opus_decoder_amalgam.c
|
||||
|
||||
INCLUDES := include
|
||||
APP_TITLE := The Legend of Zelda: A Link to the Past
|
||||
APP_AUTHOR := snesrev & Lywx
|
||||
@@ -51,7 +54,7 @@ APP_VERSION := $(shell git rev-parse --short HEAD) $(shell git rev-parse --abbr
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections -Wno-parentheses \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += -D__SWITCH__ $(INCLUDE) -DSTBI_NO_THREAD_LOCALS `sdl2-config --cflags`
|
||||
@@ -81,13 +84,15 @@ export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
|
||||
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
#CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
#CFILES := $(wildcard ../../*.c ../../snes/*.c) ../../third_party/gl_core/gl_core_3_1.c ../../third_party/opus-1.3.1-stripped/opus_decoder_amalgam.c
|
||||
#CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
#SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
@@ -97,7 +102,7 @@ export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(notdir $(CFILES:.c=.o)) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
|
||||
@@ -157,6 +162,7 @@ endif
|
||||
all: $(BUILD)
|
||||
|
||||
$(BUILD):
|
||||
@echo $(CFILES) ...
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
@@ -215,4 +221,4 @@ $(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
||||
#---------------------------------------------------------------------------------------
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 228 KiB |
@@ -27,7 +27,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"platform\\win32\\resource.h\0"
|
||||
"src\\platform\\win32\\resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
@@ -52,7 +52,7 @@ END
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_ICON1 ICON "platform\\win32\\triforce.ico"
|
||||
IDI_ICON1 ICON "src\\platform\\win32\\triforce.ico"
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "player_oam.h"
|
||||
#include "sprite_main.h"
|
||||
|
||||
static bool g_ApplyLinksMovementToCamera_called;
|
||||
|
||||
static const uint8 kSpinAttackDelays[] = { 1, 0, 0, 0, 0, 3, 0, 0, 1, 0, 3, 3, 3, 3, 4, 4, 1, 5 };
|
||||
static const uint8 kFireBeamSounds[] = { 1, 2, 3, 4, 0, 9, 18, 27 };
|
||||
static const int8 kTagalongArr1[] = { -1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
@@ -75,6 +77,7 @@ static const uint8 kLinkItem_MagicCosts[] = { 16, 8, 4, 32, 16, 8, 8, 4, 2, 8, 4
|
||||
static const uint8 kBombosAnimDelays[] = { 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 7, 1, 1, 1, 1, 1, 13 };
|
||||
static const uint8 kBombosAnimStates[] = { 0, 1, 2, 3, 0, 1, 2, 3, 8, 9, 10, 11, 12, 10, 8, 13, 14, 15, 16, 17 };
|
||||
static const uint8 kEtherAnimDelays[] = { 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 3, 3 };
|
||||
static const uint8 kEtherAnimDelaysNoFlash[] = { 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 24, 24 };
|
||||
static const uint8 kEtherAnimStates[] = { 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||
static const uint8 kQuakeAnimDelays[] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 19 };
|
||||
static const uint8 kQuakeAnimStates[] = { 0, 1, 2, 3, 0, 1, 2, 3, 18, 19, 20, 22 };
|
||||
@@ -177,7 +180,6 @@ void Link_ControlHandler() { // 87807f
|
||||
}
|
||||
if (link_player_handler_state)
|
||||
Player_CheckHandleCapeStuff();
|
||||
|
||||
kPlayerHandlers[link_player_handler_state]();
|
||||
}
|
||||
|
||||
@@ -232,20 +234,29 @@ void HandleLink_From1D() { // 878130
|
||||
}
|
||||
|
||||
void PlayerHandler_00_Ground_3() { // 8781a0
|
||||
g_ApplyLinksMovementToCamera_called = false;
|
||||
|
||||
link_z_coord = 0xffff;
|
||||
link_actual_vel_z = 0xff;
|
||||
link_recoilmode_timer = 0;
|
||||
|
||||
if (!Link_HandleToss()) {
|
||||
Link_HandleAPress();
|
||||
if ((link_state_bits | link_grabbing_wall) == 0 && link_unk_master_sword == 0 && link_player_handler_state != 17) {
|
||||
if ((link_state_bits | link_grabbing_wall) == 0 && link_unk_master_sword == 0 && link_player_handler_state != kPlayerState_StartDash) {
|
||||
Link_HandleYItem();
|
||||
// Ensure we're not handling potions. Things further
|
||||
// down don't assume this and change the module indexes randomly.
|
||||
// This also fixes a bug where bombos, ether, quake get aborted if you use spin attack at the same time.
|
||||
if ((enhanced_features0 & kFeatures0_MiscBugFixes) && (
|
||||
main_module_index == 14 && submodule_index != 2 ||
|
||||
link_player_handler_state == kPlayerState_Bombos ||
|
||||
link_player_handler_state == kPlayerState_Ether ||
|
||||
link_player_handler_state == kPlayerState_Quake))
|
||||
goto getout_clear_vel;
|
||||
if (sram_progress_indicator != 0) {
|
||||
Link_HandleSwordCooldown();
|
||||
if (link_player_handler_state == 3) {
|
||||
link_x_vel = link_y_vel = 0;
|
||||
goto getout_dostuff;
|
||||
}
|
||||
if (link_player_handler_state == 3)
|
||||
goto getout_clear_vel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +295,7 @@ void PlayerHandler_00_Ground_3() { // 8781a0
|
||||
if ((dir = (force_move_any_direction & 0xf)) == 0) {
|
||||
if (link_grabbing_wall & 2)
|
||||
goto endif_3;
|
||||
if ((dir = (joypad1H_last & 0xf)) == 0) {
|
||||
if ((dir = (joypad1H_last & kJoypadH_AnyDir)) == 0) {
|
||||
link_x_vel = 0;
|
||||
link_y_vel = 0;
|
||||
link_direction = 0;
|
||||
@@ -312,11 +323,18 @@ endif_3:
|
||||
Link_HandleVelocity();
|
||||
Link_HandleCardinalCollision();
|
||||
Link_HandleMovingAnimation_FullLongEntry();
|
||||
if (link_unk_master_sword)
|
||||
if (link_unk_master_sword) getout_clear_vel: {
|
||||
link_y_vel = link_x_vel = 0;
|
||||
}
|
||||
|
||||
getout_dostuff:
|
||||
fallhole_var1 = 0;
|
||||
|
||||
// HandleIndoorCameraAndDoors must not be called twice in the same frame,
|
||||
// this might mess up camera positioning. For example when using spin attack
|
||||
// in between bumpers.
|
||||
if (g_ApplyLinksMovementToCamera_called && (enhanced_features0 & kFeatures0_MiscBugFixes))
|
||||
return;
|
||||
|
||||
HandleIndoorCameraAndDoors();
|
||||
}
|
||||
|
||||
@@ -427,7 +445,7 @@ void Link_TempBunny_Func2() { // 8783fa
|
||||
ResetAllAcceleration();
|
||||
Link_HandleYItem();
|
||||
uint8 dir;
|
||||
if (!(dir = force_move_any_direction & 0xf) && !(dir = joypad1H_last & 0xf)) {
|
||||
if (!(dir = force_move_any_direction & 0xf) && !(dir = joypad1H_last & kJoypadH_AnyDir)) {
|
||||
link_x_vel = link_y_vel = 0;
|
||||
link_direction = link_direction_last = 0;
|
||||
link_animation_steps = 0;
|
||||
@@ -505,7 +523,7 @@ void LinkState_HoldingBigRock() { // 878481
|
||||
}
|
||||
|
||||
Link_HandleAPress();
|
||||
if (!(joypad1H_last & 0xf)) {
|
||||
if (!(joypad1H_last & kJoypadH_AnyDir)) {
|
||||
link_y_vel = 0;
|
||||
link_x_vel = 0;
|
||||
link_direction = 0;
|
||||
@@ -515,7 +533,7 @@ void LinkState_HoldingBigRock() { // 878481
|
||||
link_timer_push_get_tired = 32;
|
||||
link_timer_jump_ledge = 19;
|
||||
} else {
|
||||
link_direction = joypad1H_last & 0xf;
|
||||
link_direction = joypad1H_last & kJoypadH_AnyDir;
|
||||
if (link_direction != link_direction_last) {
|
||||
link_direction_last = link_direction;
|
||||
link_subpixel_x = 0;
|
||||
@@ -737,7 +755,7 @@ lbl_jump_into_middle:
|
||||
if ((link_direction & 0xc) == 0)
|
||||
link_actual_vel_y = 0;
|
||||
}
|
||||
LinkHop_FindArbitraryLandingSpot(); // not
|
||||
Link_MovePosition(); // not
|
||||
timer_running:
|
||||
if (link_player_handler_state != 6) {
|
||||
Link_HandleCardinalCollision(); // not
|
||||
@@ -747,7 +765,7 @@ timer_running:
|
||||
if (BYTE(link_z_coord) == 0 || BYTE(link_z_coord) >= 0xe0) {
|
||||
Player_TileDetectNearby();
|
||||
if ((tiledetect_pit_tile & 0xf) == 0xf) {
|
||||
link_player_handler_state = 1;
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
link_speed_setting = 4;
|
||||
}
|
||||
}
|
||||
@@ -791,13 +809,15 @@ void LinkHop_HoppingSouthOW() { // 87894e
|
||||
link_actual_vel_z_copy = link_actual_vel_z_copy_mirror;
|
||||
link_z_coord = link_z_coord_mirror;
|
||||
link_actual_vel_z -= 2;
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
if (sign8(link_actual_vel_z)) {
|
||||
if (link_actual_vel_z < 0xa0)
|
||||
link_actual_vel_z = 0xa0;
|
||||
if (link_z_coord >= 0xfff0) {
|
||||
link_z_coord = 0;
|
||||
Link_SplashUponLanding();
|
||||
// This is the place that caused the water walking bug after bonk,
|
||||
// player_near_pit_state was not reset.
|
||||
if (player_near_pit_state)
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
if (link_player_handler_state != kPlayerState_Swimming &&
|
||||
@@ -827,7 +847,7 @@ void LinkState_HandlingJump() { // 878a05
|
||||
link_actual_vel_z_copy = link_actual_vel_z_copy_mirror;
|
||||
BYTE(link_z_coord) = link_z_coord_mirror;
|
||||
link_actual_vel_z -= 2;
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
if (sign8(link_actual_vel_z)) {
|
||||
if (link_actual_vel_z < 0xa0)
|
||||
link_actual_vel_z = 0xa0;
|
||||
@@ -1014,7 +1034,7 @@ finish:
|
||||
void LinkState_HoppingDiagonallyUpOW() { // 878dc6
|
||||
draw_water_ripples_or_grass = 0;
|
||||
Player_ChangeZ(2);
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
if (sign8(link_z_coord)) {
|
||||
Link_SplashUponLanding();
|
||||
if (link_player_handler_state != kPlayerState_Swimming && !link_is_in_deep_water)
|
||||
@@ -1041,9 +1061,15 @@ void LinkState_HoppingDiagonallyDownOW() { // 878e15
|
||||
LinkHop_FindLandingSpotDiagonallyDown();
|
||||
link_x_coord = old_x;
|
||||
|
||||
static const uint8 kLedgeVelX[] = { 4, 4, 4, 10, 10, 10, 11, 18, 18, 18, 20, 20, 20, 20, 22, 22, 26, 26, 26, 26, 28, 28, 28, 28 };
|
||||
static const uint8 kLedgeVelX[] = {
|
||||
4, 4, 4, 10, 10, 10, 11, 18,
|
||||
18, 18, 20, 20, 20, 20, 22, 22,
|
||||
26, 26, 26, 26, 28, 28, 28, 28
|
||||
};
|
||||
|
||||
int8 velx = kLedgeVelX[(uint16)(link_y_coord - link_y_coord_original) >> 3];
|
||||
int t = (uint16)(link_y_coord - link_y_coord_original);
|
||||
// Fix out of bounds read
|
||||
int8 velx = kLedgeVelX[IntMin(t >> 3, 23)];
|
||||
link_actual_vel_x = (dir != 2) ? velx : -velx;
|
||||
if (!player_is_indoors)
|
||||
link_is_on_lower_level = 2;
|
||||
@@ -1167,7 +1193,7 @@ void LinkState_Dashing() { // 878f86
|
||||
follower_indicator = kTagalongArr2[follower_indicator];
|
||||
} else {
|
||||
index_of_dashing_sfx = 0;
|
||||
if (!(joypad1L_last & 0x80)) {
|
||||
if (!(joypad1L_last & kJoypadL_A)) {
|
||||
link_animation_steps = 0;
|
||||
link_countdown_for_dash = 0;
|
||||
link_speed_setting = 0;
|
||||
@@ -1182,7 +1208,7 @@ void LinkState_Dashing() { // 878f86
|
||||
link_dash_ctr = 64;
|
||||
link_speed_setting = 16;
|
||||
uint8 dir;
|
||||
if (button_mask_b_y & 0x80 || is_standing_in_doorway || (dir = joypad1H_last & 0xf) == 0)
|
||||
if (button_mask_b_y & 0x80 || is_standing_in_doorway || (dir = joypad1H_last & kJoypadH_AnyDir) == 0)
|
||||
dir = kDashTab2[link_direction_facing >> 1];
|
||||
link_some_direction_bits = link_direction = link_direction_last = dir;
|
||||
link_moving_against_diag_tile = 0;
|
||||
@@ -1226,12 +1252,12 @@ void LinkState_Dashing() { // 878f86
|
||||
bool want_stop_dash = false;
|
||||
|
||||
if (enhanced_features0 & kFeatures0_TurnWhileDashing) {
|
||||
if (!(joypad1L_last & 0x80)) {
|
||||
if (!(joypad1L_last & kJoypadL_A)) {
|
||||
link_countdown_for_dash = 0x11;
|
||||
want_stop_dash = true;
|
||||
} else {
|
||||
static const uint8 kDashCtrlsToDir[16] = { 0, 1, 2, 0, 4, 4, 4, 0, 8, 8, 8, 0, 0, 0, 0, 0 };
|
||||
uint8 t = kDashCtrlsToDir[joypad1H_last & 0xf];
|
||||
uint8 t = kDashCtrlsToDir[joypad1H_last & kJoypadH_AnyDir];
|
||||
if (t != 0 && t != link_direction_last) {
|
||||
link_direction = link_direction_last = t;
|
||||
link_some_direction_bits = t;
|
||||
@@ -1239,7 +1265,7 @@ void LinkState_Dashing() { // 878f86
|
||||
}
|
||||
}
|
||||
} else {
|
||||
want_stop_dash = (joypad1H_last & 0xf) && (joypad1H_last & 0xf) != kDashTab2[link_direction_facing >> 1];
|
||||
want_stop_dash = (joypad1H_last & kJoypadH_AnyDir) && (joypad1H_last & kJoypadH_AnyDir) != kDashTab2[link_direction_facing >> 1];
|
||||
}
|
||||
|
||||
if (want_stop_dash) {
|
||||
@@ -1250,6 +1276,10 @@ void LinkState_Dashing() { // 878f86
|
||||
LinkState_ExitingDash();
|
||||
return;
|
||||
}
|
||||
|
||||
if (link_speed_setting == 0 && (enhanced_features0 & kFeatures0_TurnWhileDashing))
|
||||
link_speed_setting = 16;
|
||||
|
||||
uint8 dir = force_move_any_direction & 0xf;
|
||||
if (dir == 0)
|
||||
dir = kDashTab2[link_direction_facing >> 1];
|
||||
@@ -1264,7 +1294,7 @@ void LinkState_Dashing() { // 878f86
|
||||
|
||||
void LinkState_ExitingDash() { // 87915e
|
||||
CacheCameraPropertiesIfOutdoors();
|
||||
if (joypad1H_last & 0xf || link_countdown_for_dash >= 16) {
|
||||
if (joypad1H_last & kJoypadH_AnyDir || link_countdown_for_dash >= 16) {
|
||||
link_countdown_for_dash = 0;
|
||||
link_speed_setting = 0;
|
||||
link_player_handler_state = kPlayerState_Ground;
|
||||
@@ -1359,19 +1389,29 @@ void LinkState_Pits() { // 8792d3
|
||||
} else {
|
||||
if (!link_is_running)
|
||||
goto aux_state;
|
||||
if (link_countdown_for_dash) {
|
||||
// If you use a turbo controller to perfectly spam the dash button,
|
||||
// the check for Link being in a hole is endlessly skipped and you
|
||||
// can levitate across chasms.
|
||||
// Fix this by ensuring that the dash button is held down before proceeding to the dash state.
|
||||
if (link_countdown_for_dash &&
|
||||
(!(enhanced_features0 & kFeatures0_MiscBugFixes) || (joypad1L_last & kJoypadL_A))) {
|
||||
LinkState_Dashing();
|
||||
return;
|
||||
}
|
||||
if (joypad1H_last & 0xf && !(joypad1H_last & 0xf & link_direction)) {
|
||||
if (joypad1H_last & kJoypadH_AnyDir && !(joypad1H_last & kJoypadH_AnyDir & link_direction)) {
|
||||
Link_CancelDash();
|
||||
aux_state:
|
||||
if (link_auxiliary_state != 1)
|
||||
link_direction = joypad1H_last & 0xF;
|
||||
link_direction = joypad1H_last & kJoypadH_AnyDir;
|
||||
}
|
||||
}
|
||||
TileDetect_MainHandler(4);
|
||||
if (!(tiledetect_pit_tile & 1)) {
|
||||
// Reset player_near_pit_state if we're no longer near a hole.
|
||||
// This fixes a bug where you could walk on water
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes)
|
||||
player_near_pit_state = 0;
|
||||
|
||||
if (link_is_running) {
|
||||
LinkState_Dashing();
|
||||
return;
|
||||
@@ -1444,6 +1484,8 @@ endif_1:
|
||||
ApplyLinksMovementToCamera();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initiate fall down
|
||||
if (player_near_pit_state != 2) {
|
||||
if (link_item_moon_pearl) {
|
||||
link_need_for_poof_for_transform = 0;
|
||||
@@ -1547,6 +1589,11 @@ void HandleDungeonLandingFromPit() { // 879520
|
||||
if (tiledetect_which_y_pos[0] >= link_y_coord)
|
||||
return;
|
||||
}
|
||||
//exploration glitch could also be armed without quitting
|
||||
//by jumping off a dungeon ledge into an access pit
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
|
||||
about_to_jump_off_ledge = 0;
|
||||
}
|
||||
link_y_coord = tiledetect_which_y_pos[0];
|
||||
link_animation_steps = 0;
|
||||
link_speed_modifier = 0;
|
||||
@@ -1633,7 +1680,7 @@ void PlayerHandler_04_Swimming() { // 87963b
|
||||
|
||||
if (!link_swim_hard_stroke) {
|
||||
uint8 t;
|
||||
if (!(swimcoll_var7[0] | swimcoll_var7[1]) || (t = ((filtered_joypad_L & 0x80) | filtered_joypad_H) & 0xc0) == 0) {
|
||||
if (!(swimcoll_var7[0] | swimcoll_var7[1]) || (t = ((filtered_joypad_L & kJoypadL_A) | filtered_joypad_H) & 0xc0) == 0) {
|
||||
Link_HandleSwimMovements();
|
||||
return;
|
||||
}
|
||||
@@ -1657,7 +1704,7 @@ void PlayerHandler_04_Swimming() { // 87963b
|
||||
void Link_HandleSwimMovements() { // 879715
|
||||
uint8 t;
|
||||
|
||||
if (!(t = force_move_any_direction & 0xf) && !(t = joypad1H_last & 0xf)) {
|
||||
if (!(t = force_move_any_direction & 0xf) && !(t = joypad1H_last & kJoypadH_AnyDir)) {
|
||||
link_y_vel = link_x_vel = 0;
|
||||
Link_FlagMaxAccels();
|
||||
if (link_flag_moving) {
|
||||
@@ -1714,7 +1761,7 @@ void Link_SetIceMaxAccel() { // 8797a6
|
||||
}
|
||||
|
||||
void Link_SetMomentum() { // 8797c7
|
||||
uint8 joy = joypad1H_last & 0xf;
|
||||
uint8 joy = joypad1H_last & kJoypadH_AnyDir;
|
||||
uint8 mask = 12, bit = 8;
|
||||
for (int i = 0; i < 2; i++, mask >>= 2, bit >>= 2) {
|
||||
if (joy & mask) {
|
||||
@@ -1944,16 +1991,20 @@ void Link_HandleYItem() { // 879b0e
|
||||
}
|
||||
|
||||
uint8 old_down = joypad1H_last, old_pressed = filtered_joypad_H, old_bottle = link_item_bottle_index;
|
||||
if ((link_item_in_hand | link_position_mode) == 0) {
|
||||
// Is X held down?
|
||||
if (joypad1L_last & 0x40 && !(old_down & 0x40) && hud_cur_item_x != 0) {
|
||||
|
||||
if (hud_cur_item_x >= kHudItem_Bottle1)
|
||||
link_item_bottle_index = hud_cur_item_x - kHudItem_Bottle1 + 1;
|
||||
item = Hud_LookupInventoryItem(hud_cur_item_x);
|
||||
// Pretend it's actually Y that's down
|
||||
joypad1H_last = old_down | 0x40;
|
||||
filtered_joypad_H = old_pressed | filtered_joypad_L & 0x40;
|
||||
if ((link_item_in_hand | link_position_mode) == 0 && !(old_down & kJoypadH_Y)) {
|
||||
// Is any special key held down?
|
||||
int btn_index = GetCurrentItemButtonIndex();
|
||||
if (btn_index != 0) {
|
||||
uint8 *cur_item_ptr = GetCurrentItemButtonPtr(btn_index);
|
||||
if (*cur_item_ptr) {
|
||||
if (*cur_item_ptr >= kHudItem_Bottle1)
|
||||
link_item_bottle_index = *cur_item_ptr - kHudItem_Bottle1 + 1;
|
||||
item = Hud_LookupInventoryItem(*cur_item_ptr);
|
||||
// Pretend it's actually Y that's down
|
||||
joypad1H_last = old_down | kJoypadH_Y;
|
||||
static const uint8 kButtonIndexKeys[4] = { 0, kJoypadL_X, kJoypadL_L, kJoypadL_R };
|
||||
filtered_joypad_H = old_pressed | ((filtered_joypad_L & kButtonIndexKeys[btn_index]) ? kJoypadH_Y : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2079,10 +2130,6 @@ void Link_APress_PerformBasic(uint8 action_x2) { // 879c5f
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
// Zelda vanilla bug: Disallow dragging while in cape mode, it uses no magic.
|
||||
if (link_cape_mode && enhanced_features0 & kFeatures0_MiscBugFixes)
|
||||
link_grabbing_wall = 0;
|
||||
}
|
||||
|
||||
void HandleSwordSfxAndBeam() { // 879c66
|
||||
@@ -2124,7 +2171,7 @@ void Link_CheckForSwordSwing() { // 879cd9
|
||||
link_animation_steps = 0;
|
||||
}
|
||||
|
||||
if (!(joypad1H_last & 0x80))
|
||||
if (!(joypad1H_last & kJoypadH_B))
|
||||
button_mask_b_y |= 1;
|
||||
HaltLinkWhenUsingItems();
|
||||
link_direction &= ~0xf;
|
||||
@@ -2139,7 +2186,7 @@ void Link_CheckForSwordSwing() { // 879cd9
|
||||
AncillaAdd_SwordSwingSparkle(0x26, 4);
|
||||
if (link_sword_type != 0 && link_sword_type != 0xff)
|
||||
TileDetect_MainHandler(link_sword_type == 1 ? 1 : 6);
|
||||
} else if (button_b_frames >= 4 && (button_mask_b_y & 1) && (joypad1H_last & 0x80)) {
|
||||
} else if (button_b_frames >= 4 && (button_mask_b_y & 1) && (joypad1H_last & kJoypadH_B)) {
|
||||
button_mask_b_y &= ~1;
|
||||
HandleSwordSfxAndBeam();
|
||||
return;
|
||||
@@ -2149,7 +2196,7 @@ void Link_CheckForSwordSwing() { // 879cd9
|
||||
}
|
||||
|
||||
void HandleSwordControls() { // 879d72
|
||||
if (joypad1H_last & 0x80) {
|
||||
if (joypad1H_last & kJoypadH_B) {
|
||||
Player_Sword_SpinAttackJerks_HoldDown();
|
||||
} else {
|
||||
if (link_spin_attack_step_counter < 48) {
|
||||
@@ -2265,7 +2312,7 @@ void LinkItem_Hammer() { // 879f7b
|
||||
if (link_item_in_hand & 0x10)
|
||||
return;
|
||||
if (!(button_mask_b_y & 0x40)) {
|
||||
if (is_standing_in_doorway || !(filtered_joypad_H & 0x40))
|
||||
if (is_standing_in_doorway || !(filtered_joypad_H & kJoypadH_Y))
|
||||
return;
|
||||
button_mask_b_y |= 0x40;
|
||||
link_delay_timer_spin_attack = kHammerAnimDelays[0];
|
||||
@@ -2361,7 +2408,7 @@ void LinkItem_Boomerang() { // 87a0bb
|
||||
}
|
||||
|
||||
if (!s0) {
|
||||
link_direction_last = joypad1H_last & 0xf;
|
||||
link_direction_last = joypad1H_last & kJoypadH_AnyDir;
|
||||
} else {
|
||||
link_cant_change_direction |= 1;
|
||||
}
|
||||
@@ -2640,7 +2687,8 @@ void LinkState_UsingEther() { // 87a50f
|
||||
} else if (step_counter_for_spin_attack == 12) {
|
||||
step_counter_for_spin_attack = 10;
|
||||
}
|
||||
link_delay_timer_spin_attack = kEtherAnimDelays[step_counter_for_spin_attack];
|
||||
const uint8 *table = (enhanced_features0 & kFeatures0_DimFlashes) ? kEtherAnimDelaysNoFlash : kEtherAnimDelays;
|
||||
link_delay_timer_spin_attack = table[step_counter_for_spin_attack];
|
||||
state_for_spin_attack = kEtherAnimStates[step_counter_for_spin_attack];
|
||||
if (!byte_7E0324 && step_counter_for_spin_attack == 10) {
|
||||
byte_7E0324 = 1;
|
||||
@@ -2736,7 +2784,7 @@ void LinkState_UsingQuake() { // 87a6d6
|
||||
BYTE(link_z_coord) = link_z_coord_mirror;
|
||||
link_auxiliary_state = 2;
|
||||
Player_ChangeZ(2);
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
link_actual_vel_z_mirror = link_actual_vel_z;
|
||||
link_actual_vel_z_copy_mirror = link_actual_vel_z_copy;
|
||||
BYTE(link_z_coord_mirror) = link_z_coord;
|
||||
@@ -2849,7 +2897,7 @@ void LinkState_SpinAttack() { // 87a804
|
||||
state_for_spin_attack = 0;
|
||||
step_counter_for_spin_attack = 0;
|
||||
if (link_player_handler_state != kPlayerState_SpinAttackMotion) {
|
||||
button_mask_b_y = (button_b_frames) ? (joypad1H_last & 0x80) : 0; // wtf, it's zero,
|
||||
button_mask_b_y = (button_b_frames) ? (joypad1H_last & kJoypadH_B) : 0; // wtf, it's zero,
|
||||
}
|
||||
link_player_handler_state = kPlayerState_Ground;
|
||||
} else {
|
||||
@@ -3144,7 +3192,7 @@ void LinkState_Hookshotting() { // 87ab7c
|
||||
}
|
||||
return;
|
||||
loc_87AD49:
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
TileDetect_MainHandler(5);
|
||||
if (player_is_indoors) {
|
||||
uint8 x = tiledetect_vertical_ledge >> 4 | tiledetect_vertical_ledge | detection_of_ledge_tiles_horiz_uphoriz;
|
||||
@@ -3197,14 +3245,16 @@ void LinkItem_Cape() { // 87adc1
|
||||
link_direction &= ~0xf;
|
||||
if (!--cape_decrement_counter) {
|
||||
cape_decrement_counter = kCapeDepletionTimers[link_magic_consumption];
|
||||
if (!--link_magic_power) {
|
||||
// Avoid magic underflow if an anti-fairy consumes magic.
|
||||
if (link_magic_power == 0 && (enhanced_features0 & kFeatures0_MiscBugFixes) ||
|
||||
!--link_magic_power) {
|
||||
Link_ForceUnequipCape();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (sign8(--link_bunny_transform_timer)) {
|
||||
link_bunny_transform_timer = 0;
|
||||
if (filtered_joypad_H & 0x40)
|
||||
if (filtered_joypad_H & kJoypadH_Y)
|
||||
Link_ForceUnequipCape();
|
||||
}
|
||||
}
|
||||
@@ -3237,7 +3287,8 @@ void HaltLinkWhenUsingItems() { // 87ae65
|
||||
}
|
||||
|
||||
void Link_HandleCape_passive_LiftCheck() { // 87ae88
|
||||
if (link_state_bits & 0x80)
|
||||
//bugfix: grabbing or pulling while wearing cape didn't drain magic
|
||||
if (link_state_bits & 0x80 || (enhanced_features0 & kFeatures0_MiscBugFixes && link_grabbing_wall))
|
||||
Player_CheckHandleCapeStuff();
|
||||
}
|
||||
|
||||
@@ -3260,15 +3311,30 @@ void LinkItem_CaneOfSomaria() { // 87aec0
|
||||
if (player_on_somaria_platform || is_standing_in_doorway || !CheckYButtonPress())
|
||||
return;
|
||||
int i = 4;
|
||||
bool did_charge_magic = false;
|
||||
|
||||
while (ancilla_type[i] != 0x2c) {
|
||||
if (--i < 0) {
|
||||
if (!LinkCheckMagicCost(4))
|
||||
if (!LinkCheckMagicCost(4)) {
|
||||
// If you use the Cane of Somaria with an empty magic meter,
|
||||
// then quickly switch to the mushroom or magic powder after
|
||||
// the "no magic" prompt, you will automatically sprinkle magic powder
|
||||
// despite pressing no button and having no magic.
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes)
|
||||
goto out;
|
||||
return;
|
||||
}
|
||||
did_charge_magic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
link_debug_value_2 = 1;
|
||||
AncillaAdd_SomariaBlock(44, 1);
|
||||
if (AncillaAdd_SomariaBlock(0x2c, 1) < 0) {
|
||||
// If you use the Cane of Somaria while two bombs and the boomerang are active,
|
||||
// magic will be refunded instead of used.
|
||||
if (did_charge_magic || !(enhanced_features0 & kFeatures0_MiscBugFixes))
|
||||
Refund_Magic(4);
|
||||
}
|
||||
link_delay_timer_spin_attack = kRodAnimDelays[0];
|
||||
link_animation_steps = 0;
|
||||
player_handler_timer = 0;
|
||||
@@ -3289,8 +3355,9 @@ void LinkItem_CaneOfSomaria() { // 87aec0
|
||||
player_handler_timer = 0;
|
||||
link_delay_timer_spin_attack = 0;
|
||||
link_debug_value_2 = 0;
|
||||
button_mask_b_y &= ~0x40;
|
||||
link_position_mode &= ~8;
|
||||
out:
|
||||
button_mask_b_y &= ~0x40;
|
||||
}
|
||||
|
||||
void LinkItem_CaneOfByrna() { // 87af3e
|
||||
@@ -3376,7 +3443,7 @@ void LinkItem_Net() { // 87aff8
|
||||
}
|
||||
|
||||
bool CheckYButtonPress() { // 87b073
|
||||
if (button_mask_b_y & 0x40 || link_incapacitated_timer || !(filtered_joypad_H & 0x40))
|
||||
if (button_mask_b_y & 0x40 || link_incapacitated_timer || !(filtered_joypad_H & kJoypadH_Y))
|
||||
return false;
|
||||
button_mask_b_y |= 0x40;
|
||||
return true;
|
||||
@@ -3441,7 +3508,7 @@ void Link_PerformThrow() { // 87b11c
|
||||
|
||||
flag_is_sprite_to_pick_up = 1;
|
||||
Sprite_SpawnThrowableTerrain(i, pt.x, pt.y);
|
||||
filtered_joypad_L &= ~0x80;
|
||||
filtered_joypad_L &= ~kJoypadL_A;
|
||||
player_handler_timer = 0;
|
||||
}
|
||||
} else {
|
||||
@@ -3489,8 +3556,8 @@ void Link_APress_LiftCarryThrow() { // 87b1ca
|
||||
} else {
|
||||
static const uint8 kLiftTab0[10] = { 8, 24, 8, 24, 8, 32, 6, 8, 13, 13 };
|
||||
static const uint8 kLiftTab1[10] = { 0, 1, 0, 1, 0, 1, 0, 1, 2, 3 };
|
||||
static const uint8 kLiftTab2[] = { 6, 7, 7, 5, 10, 0, 23, 0, 18, 0, 18, 0, 8, 0, 8, 0, 254, 255, 17, 0,
|
||||
0x54, 0x52, 0x50, 0xFF, 0x51, 0x53, 0x55, 0x56, 0x57};
|
||||
static const uint8 kLiftTab2[29] = { 6, 7, 7, 5, 10, 0, 23, 0, 18, 0, 18, 0, 8, 0, 8, 0, 254, 255, 17, 0,
|
||||
0x54, 0x52, 0x50, 0xFF, 0x51, 0x53, 0x55, 0x56, 0x57 };
|
||||
|
||||
if (player_handler_timer != 0) {
|
||||
if (player_handler_timer + 1 != 9) {
|
||||
@@ -3504,13 +3571,14 @@ void Link_APress_LiftCarryThrow() { // 87b1ca
|
||||
link_player_handler_state = 24;
|
||||
flag_is_sprite_to_pick_up = 1;
|
||||
Sprite_SpawnThrowableTerrain((what & 0xf) + 1, pt.x, pt.y);
|
||||
filtered_joypad_L &= ~0x80;
|
||||
filtered_joypad_L &= ~kJoypadL_A;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
// todo: This is an OOB read triggered when lifting for too long
|
||||
// fix OOB read triggered when lifting for too long
|
||||
if (some_animation_timer_steps >= sizeof(kLiftTab2) - 1)
|
||||
return;
|
||||
some_animation_timer = kLiftTab2[++some_animation_timer_steps];
|
||||
assert(some_animation_timer_steps < arraysize(kLiftTab2));
|
||||
if (some_animation_timer_steps != 3)
|
||||
@@ -3573,7 +3641,7 @@ set:
|
||||
some_animation_timer = kGrabWall_AnimTimer[link_var30d];
|
||||
}
|
||||
|
||||
if (!(joypad1L_last & 0x80)) {
|
||||
if (!(joypad1L_last & kJoypadL_A)) {
|
||||
link_var30d = 0;
|
||||
some_animation_timer_steps = 0;
|
||||
link_grabbing_wall = 0;
|
||||
@@ -3608,7 +3676,7 @@ void Link_APress_StatueDrag() { // 87b389
|
||||
some_animation_timer_steps = kGrabWall_AnimSteps[link_var30d];
|
||||
some_animation_timer = kGrabWall_AnimTimer[link_var30d];
|
||||
skip_set:
|
||||
if (!(joypad1L_last & 0x80)) {
|
||||
if (!(joypad1L_last & kJoypadL_A)) {
|
||||
link_speed_setting = 0;
|
||||
link_is_near_moveable_statue = 0;
|
||||
link_var30d = 0;
|
||||
@@ -3645,7 +3713,7 @@ void LinkState_TreePull() { // 87b416
|
||||
|
||||
if (link_grabbing_wall) {
|
||||
if (!button_mask_b_y) {
|
||||
if (!(joypad1L_last & 0x80)) {
|
||||
if (!(joypad1L_last & kJoypadL_A)) {
|
||||
link_grabbing_wall = 0;
|
||||
link_var30d = 0;
|
||||
some_animation_timer = 2;
|
||||
@@ -3655,7 +3723,7 @@ void LinkState_TreePull() { // 87b416
|
||||
LinkState_Default();
|
||||
return;
|
||||
}
|
||||
if (!(joypad1H_last & 4))
|
||||
if (!(joypad1H_last & kJoypadH_Down))
|
||||
goto out;
|
||||
button_mask_b_y = 4;
|
||||
Ancilla_Sfx2_Near(0x22);
|
||||
@@ -3686,7 +3754,7 @@ reset_to_normal:
|
||||
return;
|
||||
}
|
||||
if (link_var30d == 9) {
|
||||
if (!(filtered_joypad_H & 0xf))
|
||||
if (!(filtered_joypad_H & kJoypadH_AnyDir))
|
||||
goto out2;
|
||||
link_player_handler_state = kPlayerState_Ground;
|
||||
LinkState_Default();
|
||||
@@ -3708,7 +3776,7 @@ reset_to_normal:
|
||||
if (!(link_direction & 0xc))
|
||||
link_actual_vel_y = 0;
|
||||
out:
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
out2:
|
||||
Link_HandleCardinalCollision();
|
||||
HandleIndoorCameraAndDoors();
|
||||
@@ -3748,14 +3816,14 @@ void Link_PerformOpenChest() { // 87b574
|
||||
}
|
||||
|
||||
bool Link_CheckNewAPress() { // 87b5c0
|
||||
if (bitfield_for_a_button & 0x80 || link_incapacitated_timer || !(filtered_joypad_L & 0x80))
|
||||
if (bitfield_for_a_button & 0x80 || link_incapacitated_timer || !(filtered_joypad_L & kJoypadL_A))
|
||||
return false;
|
||||
bitfield_for_a_button |= 0x80;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Link_HandleToss() { // 87b5d6
|
||||
if (!(bitfield_for_a_button & 0x80) || !(filtered_joypad_L & 0x80) || (link_picking_throw_state & 1))
|
||||
if (!(bitfield_for_a_button & 0x80) || !(filtered_joypad_L & kJoypadL_A) || (link_picking_throw_state & 1))
|
||||
return false;
|
||||
link_var30d = 0;
|
||||
link_var30e = 0;
|
||||
@@ -3917,7 +3985,7 @@ xy: RunSlopeCollisionChecks_HorizontalFirst();
|
||||
if (st != 19 && st != 8 && st != 9 && st != 10 && st != 3) {
|
||||
Player_TileDetectNearby();
|
||||
if (tiledetect_pit_tile & 0xf) {
|
||||
link_player_handler_state = 1;
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
if (!link_is_running)
|
||||
link_speed_setting = 4;
|
||||
}
|
||||
@@ -4028,7 +4096,7 @@ void StartMovementCollisionChecks_Y_HandleIndoors() { // 87ba35
|
||||
} // endif_6
|
||||
|
||||
is_standing_in_doorway = 1;
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
if ((R14 & 0x70) != 0x70) {
|
||||
if (R14 & 5) { // if_7
|
||||
link_moving_against_diag_tile = 0;
|
||||
@@ -4065,7 +4133,7 @@ endif_1b:
|
||||
label_3:
|
||||
|
||||
if ((R14 & 7) == 0 && (R12 & 5) != 0) {
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
FlagMovingIntoSlopes_Y();
|
||||
if ((link_moving_against_diag_tile & 0xf) != 0)
|
||||
return;
|
||||
@@ -4108,12 +4176,12 @@ label_3:
|
||||
} // endif_12_norupee
|
||||
|
||||
if (tiledetect_var4 & 0x22) {
|
||||
byte_7E03F3 = tiledetect_var4 & 0x20 ? 2 : 1;
|
||||
link_on_conveyor_belt = tiledetect_var4 & 0x20 ? 2 : 1;
|
||||
} else if (tiledetect_var4 & 0x2200) {
|
||||
byte_7E03F3 = tiledetect_var4 & 0x2000 ? 4 : 3;
|
||||
link_on_conveyor_belt = tiledetect_var4 & 0x2000 ? 4 : 3;
|
||||
} else {
|
||||
if (!(bitfield_spike_cactus_tiles & 7) && !(R14 & 2))
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
} // endif_15
|
||||
|
||||
if ((tiledetect_vertical_ledge & 7) == 7 && RunLedgeHopTimer()) {
|
||||
@@ -4197,7 +4265,7 @@ endif_19:
|
||||
byte_7E005C = 9;
|
||||
link_this_controls_sprite_oam = 0;
|
||||
player_near_pit_state = 1;
|
||||
link_player_handler_state = 1;
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
return;
|
||||
} // endif_23
|
||||
|
||||
@@ -4338,7 +4406,7 @@ void StartMovementCollisionChecks_Y_HandleOutdoors() { // 87beaf
|
||||
byte_7E005C = 9;
|
||||
link_this_controls_sprite_oam = 0;
|
||||
player_near_pit_state = 1;
|
||||
link_player_handler_state = 1;
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -4714,7 +4782,7 @@ void StartMovementCollisionChecks_X_HandleIndoors() { // 87c4ff
|
||||
} // endif_6
|
||||
|
||||
is_standing_in_doorway = 2;
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
if ((R14 & 0x70) != 0x70) {
|
||||
if (R14 & 7) { // if_7
|
||||
link_moving_against_diag_tile = 0;
|
||||
@@ -4744,7 +4812,7 @@ else_7:
|
||||
label_3:
|
||||
|
||||
if ((R14 & 2) == 0 && (R12 & 5) != 0) {
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
FlagMovingIntoSlopes_X();
|
||||
if ((link_moving_against_diag_tile & 0xf) != 0)
|
||||
return;
|
||||
@@ -4780,12 +4848,12 @@ label_3:
|
||||
} // endif_12_norupee
|
||||
|
||||
if (tiledetect_var4 & 0x22) {
|
||||
byte_7E03F3 = tiledetect_var4 & 0x20 ? 2 : 1;
|
||||
link_on_conveyor_belt = tiledetect_var4 & 0x20 ? 2 : 1;
|
||||
} else if (tiledetect_var4 & 0x2200) {
|
||||
byte_7E03F3 = tiledetect_var4 & 0x2000 ? 4 : 3;
|
||||
link_on_conveyor_belt = tiledetect_var4 & 0x2000 ? 4 : 3;
|
||||
} else {
|
||||
if (!(bitfield_spike_cactus_tiles & 7) && !(R14 & 2))
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
} // endif_15
|
||||
|
||||
if ((detection_of_ledge_tiles_horiz_uphoriz & 7) == 7 && RunLedgeHopTimer()) {
|
||||
@@ -4837,7 +4905,7 @@ endif_19:
|
||||
byte_7E005C = 9;
|
||||
link_this_controls_sprite_oam = 0;
|
||||
player_near_pit_state = 1;
|
||||
link_player_handler_state = 1;
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
return;
|
||||
} // endif_23
|
||||
|
||||
@@ -4977,7 +5045,7 @@ void StartMovementCollisionChecks_X_HandleOutdoors() { // 87c8e9
|
||||
byte_7E005C = 9;
|
||||
link_this_controls_sprite_oam = 0;
|
||||
player_near_pit_state = 1;
|
||||
link_player_handler_state = 1;
|
||||
link_player_handler_state = kPlayerState_FallingIntoHole;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -5099,11 +5167,17 @@ void StartMovementCollisionChecks_X_HandleOutdoors() { // 87c8e9
|
||||
return;
|
||||
} // endif_8
|
||||
|
||||
if ((R14 & 2) == 0 && (R12 & 5) != 0 && (!link_is_running || (link_direction_facing & 4))) {
|
||||
FlagMovingIntoSlopes_X();
|
||||
if ((link_moving_against_diag_tile & 0xf) != 0)
|
||||
return;
|
||||
} // endif_9
|
||||
// If force facing down (hold B button), while turboing on the Run key, we'll never
|
||||
// reach FlagMovingIntoSlopes_X causing a Dash Buffering glitch.
|
||||
// Fix by always calling it, not sure why you wouldn't always want to call it.
|
||||
if ((R14 & 2) == 0 && (R12 & 5) != 0) {
|
||||
bool skip_check = link_is_running && !(link_direction_facing & 4);
|
||||
if (!skip_check || (enhanced_features0 & kFeatures0_MiscBugFixes)) {
|
||||
FlagMovingIntoSlopes_X();
|
||||
if ((link_moving_against_diag_tile & 0xf) != 0)
|
||||
return;
|
||||
} // endif_9
|
||||
}
|
||||
|
||||
link_moving_against_diag_tile = 0;
|
||||
|
||||
@@ -5214,8 +5288,9 @@ void Link_HandleDiagonalKickback() { // 87ccab
|
||||
static const int8 x1[] = { 0, -1, -1, -1, -2, -2, -2, -3, -3, -3 };
|
||||
link_x_coord += sign8(link_x_vel) ? x1[-(int8)link_x_vel] : x0[link_x_vel];
|
||||
|
||||
static const int8 y0[] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3 };
|
||||
static const int8 y1[] = { 0, 1, 1, 2, 2, 2, 3, 3, 3, 3 };
|
||||
static const int8 y0[10] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3 };
|
||||
// Bug in zelda, might read index 15
|
||||
static const int8 y1[16] = { 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 0xa5, 0x30, 0xf0, 0x04, 0xa5, 0x31 };
|
||||
link_y_coord += sign8(link_y_vel) ? y1[-(int8)link_y_vel] : y0[link_y_vel];
|
||||
} else {
|
||||
noHorizOrNoVertical:
|
||||
@@ -5511,12 +5586,25 @@ void FlagMovingIntoSlopes_Y() { // 87e076
|
||||
if (tiledetect_diagonal_tile & 5) {
|
||||
int8 ym = tiledetect_which_y_pos[0] & 7;
|
||||
|
||||
if (!(tiledetect_diag_state & 2)) {
|
||||
ym = 8 - ym;
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
|
||||
if (tiledetect_diag_state & 2) {
|
||||
ym = -ym;
|
||||
} else {
|
||||
ym = kAvoidJudder1[o] - (8 - ym);
|
||||
}
|
||||
} else {
|
||||
ym += 8;
|
||||
// This code is bad because it could cause the player
|
||||
// to move up to 15 pixels, causing an array out bounds read.
|
||||
// Not sure how it works, but changed it to look more like the X version.
|
||||
if (!(tiledetect_diag_state & 2)) {
|
||||
ym = 8 - ym; // 0 to 8
|
||||
} else {
|
||||
ym += 8; // 8 to 15
|
||||
}
|
||||
// -15 to 7
|
||||
ym = kAvoidJudder1[o] - ym;
|
||||
}
|
||||
ym = kAvoidJudder1[o] - ym;
|
||||
|
||||
if (link_y_vel == 0)
|
||||
return;
|
||||
if (sign8(link_y_vel))
|
||||
@@ -5548,12 +5636,9 @@ void FlagMovingIntoSlopes_X() { // 87e112
|
||||
int8 xm = link_x_coord & 7;
|
||||
|
||||
if (tiledetect_diag_state != 4 && tiledetect_diag_state != 6) {
|
||||
o ^= 7;
|
||||
xm = -xm;
|
||||
} else {
|
||||
xm -= 8;
|
||||
xm = -xm;
|
||||
xm = kAvoidJudder1[o] - xm;
|
||||
xm = kAvoidJudder1[o] - (8 - xm);
|
||||
} // endif_5
|
||||
if (link_x_vel == 0)
|
||||
return;
|
||||
@@ -5677,10 +5762,10 @@ void Link_HandleVelocity() { // 87e245
|
||||
link_actual_vel_z = 0xff;
|
||||
link_z_coord = 0xffff;
|
||||
link_subpixel_z = 0;
|
||||
LinkHop_FindArbitraryLandingSpot();
|
||||
Link_MovePosition();
|
||||
}
|
||||
|
||||
void LinkHop_FindArbitraryLandingSpot() { // 87e370
|
||||
void Link_MovePosition() { // 87e370
|
||||
uint16 x = link_x_coord, y = link_y_coord;
|
||||
link_y_coord_safe_return_lo = link_y_coord;
|
||||
link_y_coord_safe_return_hi = link_y_coord >> 8;
|
||||
@@ -5819,14 +5904,14 @@ void Link_ApplyConveyor() { // 87e5f0
|
||||
static const int8 kMovingBeltY[4] = { -8, 8, 0, 0 };
|
||||
static const int8 kMovingBeltX[4] = { 0, 0, -8, 8 };
|
||||
|
||||
if (!byte_7E03F3)
|
||||
if (!link_on_conveyor_belt)
|
||||
return;
|
||||
if (BYTE(link_z_coord) != 0 && BYTE(link_z_coord) != 0xff)
|
||||
return;
|
||||
if (link_grabbing_wall & 1 || link_player_handler_state == kPlayerState_Hookshot || link_auxiliary_state)
|
||||
return;
|
||||
|
||||
int j = byte_7E03F3 - 1;
|
||||
int j = link_on_conveyor_belt - 1;
|
||||
if (link_is_running && link_dash_ctr == 32 && (link_direction & kMovePosDirFlag[j]))
|
||||
return;
|
||||
|
||||
@@ -5891,7 +5976,7 @@ void Link_HandleMovingAnimation_StartWithDash() { // 87e704
|
||||
if (link_speed_setting == 6) {
|
||||
x += 4;
|
||||
} else if (link_flag_moving) {
|
||||
if (!(joypad1H_last & 0xf)) {
|
||||
if (!(joypad1H_last & kJoypadH_AnyDir)) {
|
||||
link_animation_steps = 0;
|
||||
return;
|
||||
}
|
||||
@@ -5901,7 +5986,8 @@ void Link_HandleMovingAnimation_StartWithDash() { // 87e704
|
||||
static const uint8 tab2[16] = { 4, 4, 4, 4, 1, 1, 1, 1, 2, 2, 2, 2, 8, 8, 8, 8 };
|
||||
static const uint8 tab3[24] = { 1, 2, 3, 2, 2, 2, 3, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 2, 2, 3, 2 };
|
||||
|
||||
if (link_player_handler_state == 23) { // kPlayerState_PermaBunny
|
||||
//bugfix: tempbunny animation steps are wrong due to missing check
|
||||
if (link_player_handler_state == 23 || (enhanced_features0 & kFeatures0_MiscBugFixes && link_player_handler_state == 28)) { // bunny states
|
||||
if (link_animation_steps < 4 && player_on_somaria_platform != 2) {
|
||||
if (++link_counter_var1 >= tab2[x]) {
|
||||
link_counter_var1 = 0;
|
||||
@@ -6015,6 +6101,14 @@ void HandleDoorTransitions() { // 87e901
|
||||
link_x_page_movement_delta = 0;
|
||||
link_y_page_movement_delta = 0;
|
||||
|
||||
// Using a potion might have changed us into a different module, and the routines
|
||||
// below just increment the submodule value, causing all kinds of havoc.
|
||||
// There's an added return to catch the same behavior a bit up, but this one catches more cases,
|
||||
// at the expense of link already having done his movement, so by returning here we might
|
||||
// miss handling the door causing other kinds of issues.
|
||||
if ((enhanced_features0 & kFeatures0_MiscBugFixes) && !(main_module_index == 7 && submodule_index == 0))
|
||||
return;
|
||||
|
||||
if (link_direction_last & 0xC && is_standing_in_doorway == 1) {
|
||||
if (link_direction_last & 4) {
|
||||
if (((t = link_y_coord + 28) & 0xfc) == 0)
|
||||
@@ -6057,6 +6151,10 @@ void HandleDoorTransitions() { // 87e901
|
||||
}
|
||||
|
||||
void ApplyLinksMovementToCamera() { // 87e9d3
|
||||
// Sometimes, when using spin attack, this routine will end up getting
|
||||
// called twice in the same frame, which messes up things.
|
||||
g_ApplyLinksMovementToCamera_called = true;
|
||||
|
||||
link_y_page_movement_delta = (link_y_coord >> 8) - link_y_coord_safe_return_hi;
|
||||
link_x_page_movement_delta = (link_x_coord >> 8) - link_x_coord_safe_return_hi;
|
||||
|
||||
@@ -6162,9 +6260,29 @@ void Link_Initialize() { // 87f13c
|
||||
player_on_somaria_platform = 0;
|
||||
link_spin_attack_step_counter = 0;
|
||||
|
||||
// This fixes the jump ledge exploration glitch
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes)
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
|
||||
// If you quit while jumping from a ledge and get hit on a platform you can go under solid layers
|
||||
about_to_jump_off_ledge = 0;
|
||||
|
||||
// If you use the mirror near a moveable statue you can pull thin air and glitch the camera
|
||||
link_is_near_moveable_statue = 0;
|
||||
|
||||
// If you use the mirror on a conveyor belt you will retain momentum and clip into the entrance wall
|
||||
link_on_conveyor_belt = 0;
|
||||
|
||||
// bugfix: you use the mirror on ice, you retain momentum
|
||||
link_flag_moving = 0;
|
||||
|
||||
// If you quit in the middle of red armos knight stomp the lumberjack tree will fall on its own
|
||||
bg1_y_offset = bg1_x_offset = 0;
|
||||
//bugfix: if you die in a dungeon as a permabunny and continue, you revert back to link
|
||||
if (!link_item_moon_pearl && savegame_is_darkworld) {
|
||||
link_player_handler_state = kPlayerState_PermaBunny;
|
||||
link_is_bunny = 1;
|
||||
link_is_bunny_mirror = 1;
|
||||
LoadGearPalettes_bunny();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Link_ResetProperties_A() { // 87f1a3
|
||||
@@ -6205,6 +6323,11 @@ void Link_ResetProperties_B() { // 87f1e6
|
||||
}
|
||||
|
||||
void Link_ResetProperties_C() { // 87f1fa
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
|
||||
// Fix save menu lockout when dying after medallion cast (#126)
|
||||
flag_custom_spell_anim_active = 0;
|
||||
}
|
||||
|
||||
tile_action_index = 0;
|
||||
state_for_spin_attack = 0;
|
||||
step_counter_for_spin_attack = 0;
|
||||
@@ -6265,7 +6388,7 @@ void SomariaBlock_HandlePlayerInteraction(int k) { // 88e7e6
|
||||
if (!ancilla_H[k]) {
|
||||
if (link_auxiliary_state || (link_state_bits & 1) || ancilla_z[k] != 0 && ancilla_z[k] != 0xff || ancilla_K[k] || ancilla_L[k])
|
||||
return;
|
||||
if (!(joypad1H_last & 0xf)) {
|
||||
if (!(joypad1H_last & kJoypadH_AnyDir)) {
|
||||
ancilla_arr3[k] = 0;
|
||||
bitmask_of_dragstate = 0;
|
||||
ancilla_A[k] = 255;
|
||||
@@ -6273,11 +6396,11 @@ void SomariaBlock_HandlePlayerInteraction(int k) { // 88e7e6
|
||||
link_speed_setting = 0;
|
||||
return;
|
||||
}
|
||||
} else if ((joypad1H_last & 0xf) == ancilla_arr3[k]) {
|
||||
} else if ((joypad1H_last & kJoypadH_AnyDir) == ancilla_arr3[k]) {
|
||||
if (link_speed_setting == 18)
|
||||
bitmask_of_dragstate |= 0x81;
|
||||
} else {
|
||||
ancilla_arr3[k] = (joypad1H_last & 0xf);
|
||||
ancilla_arr3[k] = (joypad1H_last & kJoypadH_AnyDir);
|
||||
link_speed_setting = 0;
|
||||
}
|
||||
|
||||
@@ -6289,7 +6412,7 @@ void SomariaBlock_HandlePlayerInteraction(int k) { // 88e7e6
|
||||
uint8 t;
|
||||
ancilla_x_vel[k] = 0;
|
||||
ancilla_y_vel[k] = 0;
|
||||
ancilla_arr3[k] = t = joypad1H_last & 15;
|
||||
ancilla_arr3[k] = t = joypad1H_last & kJoypadH_AnyDir;
|
||||
if (t & 3) {
|
||||
ancilla_x_vel[k] = t & 1 ? 16 : -16;
|
||||
ancilla_dir[k] = t & 1 ? 3 : 2;
|
||||
@@ -6453,10 +6576,10 @@ void AncillaAdd_Hookshot(uint8 a, uint8 y) { // 899b10
|
||||
void ResetSomeThingsAfterDeath(uint8 a) { // 8bffbf
|
||||
link_is_in_deep_water = 0;
|
||||
link_speed_setting = a;
|
||||
byte_7E03F3 = 0;
|
||||
link_on_conveyor_belt = 0;
|
||||
byte_7E0322 = 0;
|
||||
flag_is_link_immobilized = 0;
|
||||
overworld_palette_swap_flag = 0;
|
||||
palette_swap_flag = 0;
|
||||
player_unk1 = 0;
|
||||
link_give_damage = 0;
|
||||
link_actual_vel_y = 0;
|
||||
@@ -207,7 +207,7 @@ void FlagMovingIntoSlopes_X();
|
||||
void Link_HandleRecoiling();
|
||||
void Player_HandleIncapacitated_Inner2();
|
||||
void Link_HandleVelocity();
|
||||
void LinkHop_FindArbitraryLandingSpot();
|
||||
void Link_MovePosition();
|
||||
void Link_HandleVelocityAndSandDrag(uint16 x, uint16 y);
|
||||
void HandleSwimStrokeAndSubpixels();
|
||||
void Player_SomethingWithVelocity_TiredOrSwim(uint16 xvel, uint16 yvel);
|
||||
@@ -922,12 +922,11 @@ continue_after_set:
|
||||
BYTE(index_of_interacting_tile) = rt;
|
||||
|
||||
int dir = link_direction_facing >> 1;
|
||||
int scratch_1_var = dir * 14;
|
||||
|
||||
int r2 = kPlayerOamOtherOffs[dir * 40 + yt] + rt;
|
||||
int r4loc = kPlayerOamSpriteLocs[r2];
|
||||
|
||||
link_palette_bits_of_oam = overworld_palette_swap_flag ? 0 : 0xe00;
|
||||
link_palette_bits_of_oam = palette_swap_flag ? 0 : 0xe00;
|
||||
link_dma_var1 = link_dma_var2 = 0;
|
||||
|
||||
int xt = FindInByteArray(kPlayerOam_Tab5, yt, 7);
|
||||
@@ -1097,19 +1096,23 @@ continue_after_set:
|
||||
}
|
||||
|
||||
uint16 t;
|
||||
bool skip_erase = true;
|
||||
bool hide_shadow = true;
|
||||
if (is_standing_in_doorway && ((t = link_x_coord - BG2HOFS_copy2) < 4 || t >= 252 || (t = link_y_coord - BG2VOFS_copy2) < 4 || t >= 224) ||
|
||||
(skip_erase = false,
|
||||
(hide_shadow = false,
|
||||
submodule_index == 0 && countdown_for_blink && --countdown_for_blink >= 4 && (countdown_for_blink & 1) == 0 ||
|
||||
link_visibility_status == 12 ||
|
||||
link_cape_mode != 0)) {
|
||||
int shadow_oam_pos = (!hide_shadow && link_visibility_status != 12) ?
|
||||
(scratch_0_var ? kShadow_oam_indexes_1 : kShadow_oam_indexes_0)[r4loc] >> 2 : -10;
|
||||
|
||||
// This appears to hide link by setting the extended bits of the oam to hide them from the screen.
|
||||
// It doesn't really play well with the widescreen modes, so change how it's done.
|
||||
if (enhanced_features0 & kFeatures0_WidescreenVisualFixes) {
|
||||
OamEnt *oam = &oam_buf[sort_sprites_offset_into_oam_buffer >> 2];
|
||||
oam[0].y = oam[1].y = oam[2].y = oam[3].y = 0xf0;
|
||||
oam[4].y = oam[5].y = oam[6].y = oam[7].y = 0xf0;
|
||||
oam[8].y = oam[9].y = oam[10].y = oam[11].y = 0xf0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (i < shadow_oam_pos || i > shadow_oam_pos + 1)
|
||||
oam[i].y = 0xf0;
|
||||
}
|
||||
} else {
|
||||
uint8 *p = &bytewise_extended_oam[sort_sprites_offset_into_oam_buffer >> 2];
|
||||
WORD(p[0]) = 0x101;
|
||||
@@ -1118,10 +1121,9 @@ continue_after_set:
|
||||
WORD(p[6]) = 0x101;
|
||||
WORD(p[8]) = 0x101;
|
||||
WORD(p[10]) = 0x101;
|
||||
}
|
||||
if (link_visibility_status != 12 && !skip_erase) {
|
||||
int oam_pos = ((scratch_0_var ? kShadow_oam_indexes_1 : kShadow_oam_indexes_0)[r4loc] + sort_sprites_offset_into_oam_buffer)>>2;
|
||||
WORD(bytewise_extended_oam[oam_pos]) = 0;
|
||||
// Clear the bit again for the shadow oam so it's not hidden?
|
||||
if (shadow_oam_pos >= 0)
|
||||
WORD(p[shadow_oam_pos]) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1248,7 +1250,6 @@ void LinkOam_DrawFootObject(int r4loc, uint8 oam_x, uint8 oam_y) { // 8daed1
|
||||
oam_y += kOffsToShadowGivenDir_Y[i];
|
||||
|
||||
int oam_pos = (kShadow_oam_indexes_1[r4loc] + sort_sprites_offset_into_oam_buffer)>>2;
|
||||
uint8 animst = secondary_water_grass_timer;
|
||||
|
||||
uint8 yv;
|
||||
if (draw_water_ripples_or_grass == 2) {
|
||||
@@ -211,7 +211,7 @@ void Module_SelectFile_0() { // 8ccd9d
|
||||
music_control = 11;
|
||||
submodule_index++;
|
||||
overworld_palette_aux_or_main = 0x200;
|
||||
dung_hdr_palette_1 = 6;
|
||||
palette_main_indoors = 6;
|
||||
nmi_disable_core_updates = 6;
|
||||
Palette_Load_DungeonSet();
|
||||
Palette_Load_OWBG3();
|
||||
@@ -780,7 +780,7 @@ static void PlayNote(SpcPlayer *p, Channel *c, uint8 note) {
|
||||
if (note >= 0xc8 || p->is_chan_on & p->cur_chan_bit)
|
||||
return;
|
||||
|
||||
static const char *const kNoteNames[] = { "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" };
|
||||
//static const char *const kNoteNames[] = { "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" };
|
||||
//if (c->index==0)
|
||||
// printf("%s%d\n", kNoteNames[(note & 0x7f) % 12], (note & 0x7f) / 12 + 1);
|
||||
|
||||
@@ -1119,8 +1119,6 @@ static void ReadPortFromSnes(SpcPlayer *p, int port) {
|
||||
}
|
||||
|
||||
static void Spc_Loop_Part1(SpcPlayer *p) {
|
||||
static const uint8 kRegAddrs0[10] = {EVOLL, EVOLR, EFB, EON, FLG, KON, KOF, NON, PMON, KOF};
|
||||
|
||||
Dsp_Write(p, KOF, p->key_OFF);
|
||||
Dsp_Write(p, PMON, p->reg_PMON);
|
||||
Dsp_Write(p, NON, p->reg_NON);
|
||||
@@ -1427,8 +1427,14 @@ bool SpriteDraw_AbsorbableTransient(int k, bool transient) { // 86d22f
|
||||
return false;
|
||||
if (sprite_delay_aux2[k] != 0)
|
||||
Oam_AllocateFromRegionC(12);
|
||||
if (sprite_E[k] != 0)
|
||||
if (sprite_E[k] != 0) {
|
||||
// This code runs when an absorbable is hidden under say a rock.
|
||||
// sprite_B holds the sprite that grabbed us with a hookshot.
|
||||
// Cancel the grab if we're hidden.
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes)
|
||||
sprite_B[k] = 0;
|
||||
return true;
|
||||
}
|
||||
uint8 j = sprite_type[k];
|
||||
assert(j >= 0xd8 && j < 0xd8 + 19);
|
||||
uint8 a = kAbsorbable_Tab2[j - 0xd8];
|
||||
@@ -73,58 +73,6 @@ static const uint8 kWishPondItemData[50] = {
|
||||
0x09, 0x13, 0x14, 0x4a, 0x21, 0x1d, 0x15, 0x18, 0x19, 0x31, 0x1a, 0x1a, 0x1b, 0x1c, 0x4b, 0x1e,
|
||||
0x1f, 0x49, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x22, 0x23, 0x29, 0x16, 0x2b, 0x2c, 0x2d, 0x3d, 0x3c, 0x48
|
||||
};
|
||||
static const DrawMultipleData kUncleDraw_Table[48] = {
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ -7, 2, 0x0d07, 2},
|
||||
{ -7, 2, 0x0d07, 2},
|
||||
{ 10, 12, 0x8d05, 0},
|
||||
{ 10, 4, 0x8d15, 0},
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c04, 2},
|
||||
{ -7, 1, 0x0d07, 2},
|
||||
{ -7, 1, 0x0d07, 2},
|
||||
{ 10, 13, 0x8d05, 0},
|
||||
{ 10, 5, 0x8d15, 0},
|
||||
{ 0, -9, 0x0e00, 2},
|
||||
{ 0, 1, 0x4c04, 2},
|
||||
{ -7, 8, 0x8d05, 0},
|
||||
{ 1, 8, 0x8d06, 0},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ -6, -1, 0x4d07, 2},
|
||||
{ 0, 0, 0x0c23, 2},
|
||||
{ 0, 0, 0x0c23, 2},
|
||||
{ -9, 7, 0x8d05, 0},
|
||||
{ -1, 7, 0x8d06, 0},
|
||||
{ 0, -9, 0x0e02, 2},
|
||||
{ -6, 0, 0x4d07, 2},
|
||||
{ 0, 1, 0x0c25, 2},
|
||||
{ 0, 1, 0x0c25, 2},
|
||||
{-10, -17, 0x0d07, 2},
|
||||
{ 15, -12, 0x8d15, 0},
|
||||
{ 15, -4, 0x8d05, 0},
|
||||
{ 0, -28, 0x0e08, 2},
|
||||
{ -8, -19, 0x0c20, 2},
|
||||
{ 8, -19, 0x4c20, 2},
|
||||
{ 0, -28, 0x0e08, 2},
|
||||
{ 0, -28, 0x0e08, 2},
|
||||
{ -8, -19, 0x0c20, 2},
|
||||
{ 8, -19, 0x4c20, 2},
|
||||
{ -8, -19, 0x0c20, 2},
|
||||
{ 8, -19, 0x4c20, 2},
|
||||
};
|
||||
static const uint8 kUncleDraw_Dma3[8] = {8, 8, 0, 0, 6, 6, 0, 0};
|
||||
static const uint8 kUncleDraw_Dma4[8] = {0, 0, 0, 0, 4, 4, 0, 0x8b}; // wtf
|
||||
static const uint8 kUncle_LeaveHouse_Delay[2] = {64, 224};
|
||||
static const uint8 kUncle_LeaveHouse_Dir[2] = {2, 1};
|
||||
static const int8 kUncle_LeaveHouse_Xvel[4] = {0, 0, -12, 12};
|
||||
@@ -535,7 +483,7 @@ static HandlerFuncK *const kSpriteActiveRoutines[243] = {
|
||||
&Sprite_11_Hinox,
|
||||
&Sprite_12_Moblin,
|
||||
&Sprite_13_MiniHelmasaur,
|
||||
&Sprite_14_ThievesTownGrate_bounce,
|
||||
&Sprite_14_ThievesTownGrate,
|
||||
&Sprite_15_Antifairy,
|
||||
&Sprite_16_Elder_bounce,
|
||||
&Sprite_17_Hoarder,
|
||||
@@ -552,7 +500,7 @@ static HandlerFuncK *const kSpriteActiveRoutines[243] = {
|
||||
&Sprite_22_Ropa,
|
||||
&Sprite_23_RedBari,
|
||||
&Sprite_23_RedBari,
|
||||
&Sprite_25_TalkingTree_bounce,
|
||||
&Sprite_25_TalkingTree,
|
||||
&Sprite_26_HardhatBeetle,
|
||||
&Sprite_27_Deadrock,
|
||||
&Sprite_28_DarkWorldHintNPC,
|
||||
@@ -560,32 +508,32 @@ static HandlerFuncK *const kSpriteActiveRoutines[243] = {
|
||||
&Sprite_SweepingLady,
|
||||
&Sprite_2B_Hobo,
|
||||
&Sprite_Lumberjacks,
|
||||
&Sprite_2D_TelepathicTile_bounce,
|
||||
&Sprite_2D_TelepathicTile,
|
||||
&Sprite_2E_FluteKid,
|
||||
&Sprite_MazeGameLady,
|
||||
&Sprite_MazeGameGuy,
|
||||
&Sprite_FortuneTeller,
|
||||
&Sprite_QuarrelBros,
|
||||
&Sprite_33_RupeePull_bounce,
|
||||
&Sprite_33_RupeePull,
|
||||
&Sprite_YoungSnitchLady,
|
||||
&Sprite_InnKeeper,
|
||||
&Sprite_Witch,
|
||||
&Sprite_37_Waterfall_bounce,
|
||||
&Sprite_38_EyeStatue_bounce,
|
||||
&Sprite_37_Waterfall,
|
||||
&Sprite_38_EyeStatue,
|
||||
&Sprite_39_Locksmith,
|
||||
&Sprite_3A_MagicBat_bounce,
|
||||
&Sprite_3A_MagicBat,
|
||||
&Sprite_DashItem,
|
||||
&Sprite_TroughBoy,
|
||||
&Sprite_OldSnitchLady,
|
||||
&Sprite_17_Hoarder,
|
||||
&Sprite_TutorialGuardOrBarrier_bounce,
|
||||
&Sprite_TutorialGuardOrBarrier_bounce,
|
||||
&Sprite_TutorialGuardOrBarrier,
|
||||
&Sprite_TutorialGuardOrBarrier,
|
||||
// Trampoline 48 entries
|
||||
&Sprite_41_BlueGuard,
|
||||
&Sprite_41_BlueGuard,
|
||||
&Sprite_41_BlueGuard,
|
||||
&Sprite_44_BluesainBolt,
|
||||
&Sprite_45_UsainBolt,
|
||||
&Sprite_45_HogSpearMan,
|
||||
&Sprite_46_BlueArcher,
|
||||
&Sprite_47_GreenBushGuard,
|
||||
&Sprite_48_RedJavelinGuard,
|
||||
@@ -623,20 +571,20 @@ static HandlerFuncK *const kSpriteActiveRoutines[243] = {
|
||||
&Sprite_66_WallCannonVerticalLeft,
|
||||
&Sprite_66_WallCannonVerticalLeft,
|
||||
&Sprite_6A_BallNChain,
|
||||
&Sprite_CannonTrooper,
|
||||
&Sprite_6B_CannonTrooper,
|
||||
&Sprite_6C_MirrorPortal,
|
||||
&Sprite_6D_Rat,
|
||||
&Sprite_6E_Rope,
|
||||
&Sprite_6F_Keese,
|
||||
&Sprite_70_KingHelmasaurFireball_bounce,
|
||||
&Sprite_70_KingHelmasaurFireball,
|
||||
&Sprite_71_Leever,
|
||||
&Sprite_72_FairyPond,
|
||||
&Sprite_73_UncleAndPriest_bounce,
|
||||
&Sprite_73_UncleAndPriest,
|
||||
&Sprite_RunningMan,
|
||||
&Sprite_BottleVendor,
|
||||
&Sprite_76_Zelda,
|
||||
&Sprite_15_Antifairy,
|
||||
&Sprite_78_MrsSahasrahla_bounce,
|
||||
&Sprite_78_MrsSahasrahla,
|
||||
// Trampoline 68 entries
|
||||
&Sprite_79_Bee,
|
||||
&Sprite_7A_Agahnim,
|
||||
@@ -749,18 +697,18 @@ static HandlerFuncK *const kSpriteActiveRoutines[243] = {
|
||||
&Sprite_E4_SmallKey,
|
||||
&Sprite_E4_SmallKey,
|
||||
&Sprite_D9_GreenRupee,
|
||||
&Sprite_Mushroom,
|
||||
&Sprite_FakeSword,
|
||||
&Sprite_PotionShop,
|
||||
&Sprite_E7_Mushroom,
|
||||
&Sprite_E8_FakeSword,
|
||||
&Sprite_E9_PotionShop,
|
||||
&Sprite_HeartContainer,
|
||||
&Sprite_HeartPiece,
|
||||
&Sprite_EC_ThrownItem,
|
||||
&Sprite_SomariaPlatform,
|
||||
&Sprite_MovableMantleTrampoline,
|
||||
&Sprite_SomariaPlatform,
|
||||
&Sprite_SomariaPlatform,
|
||||
&Sprite_SomariaPlatform,
|
||||
&Sprite_F2_MedallionTablet_bounce,
|
||||
&Sprite_ED_SomariaPlatform,
|
||||
&Sprite_EE_MovableMantle,
|
||||
&Sprite_ED_SomariaPlatform,
|
||||
&Sprite_ED_SomariaPlatform,
|
||||
&Sprite_ED_SomariaPlatform,
|
||||
&Sprite_F2_MedallionTablet,
|
||||
};
|
||||
static HandlerFuncK *const kSpritePrep_Main[243] = {
|
||||
&SpritePrep_Raven,
|
||||
@@ -772,7 +720,7 @@ static HandlerFuncK *const kSpritePrep_Main[243] = {
|
||||
&SpritePrep_Switch,
|
||||
&SpritePrep_SwitchFacingUp,
|
||||
&SpritePrep_Octorok,
|
||||
&SpritePrep_Moldorm_bounce,
|
||||
&SpritePrep_Moldorm,
|
||||
&SpritePrep_Octorok,
|
||||
&SpritePrep_DoNothingA,
|
||||
&SpritePrep_DoNothingA,
|
||||
@@ -847,7 +795,7 @@ static HandlerFuncK *const kSpritePrep_Main[243] = {
|
||||
&SpritePrep_DoNothingD,
|
||||
&SpritePrep_KingZora,
|
||||
&SpritePrep_ArmosKnight,
|
||||
&SpritePrep_Lanmolas_bounce,
|
||||
&SpritePrep_Lanmolas,
|
||||
&SpritePrep_SwimmingZora,
|
||||
&SpritePrep_WalkingZora,
|
||||
&SpritePrep_DesertStatue,
|
||||
@@ -1110,8 +1058,8 @@ void FortuneTeller_LightOrDarkWorld(int k, bool dark_world) {
|
||||
if (!dark_world)
|
||||
sprite_graphics[k] = 0;
|
||||
j = kFortuneTeller_Prices[sprite_A[k]>>1];
|
||||
byte_7E1CF2[0] = (j / 10) | (j % 10)<< 4 ;
|
||||
byte_7E1CF2[1] = 0;
|
||||
dialogue_number[0] = (j / 10) | (j % 10)<< 4 ;
|
||||
dialogue_number[1] = 0;
|
||||
Sprite_ShowMessageUnconditional(0xf4);
|
||||
sprite_ai_state[k]++;
|
||||
break;
|
||||
@@ -1371,7 +1319,7 @@ void HeartUpgrade_CheckIfAlreadyObtained(int k) {
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_MovableMantleTrampoline(int k) {
|
||||
void Sprite_EE_MovableMantle(int k) {
|
||||
MovableMantle_Draw(k);
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
return;
|
||||
@@ -1467,7 +1415,7 @@ void ChainBallTrooper_Draw(int k) {
|
||||
SpriteDraw_Shadow_custom(k, &info, kSoldier_DrawShadow[sprite_D[k]]);
|
||||
}
|
||||
|
||||
void Sprite_CannonTrooper(int k) {
|
||||
void Sprite_6B_CannonTrooper(int k) {
|
||||
if (sprite_C[k] != 0) {
|
||||
Sprite_Cannonball(k);
|
||||
return;
|
||||
@@ -1716,7 +1664,7 @@ void Sprite_SpawnSparkleGarnish(int k) { // 858008
|
||||
garnish_countdown[j] = 15;
|
||||
}
|
||||
|
||||
void Sprite_70_KingHelmasaurFireball_bounce(int k) { // 85807f
|
||||
void Sprite_70_KingHelmasaurFireball(int k) { // 85807f
|
||||
static const uint8 kHelmasaurFireball_Char[3] = {0xcc, 0xcc, 0xca};
|
||||
static const uint8 kHelmasaurFireball_Flags[2] = {0x33, 0x73};
|
||||
static const uint8 kHelmasaurFireball_Gfx[4] = {2, 2, 1, 0};
|
||||
@@ -2590,8 +2538,6 @@ void SpriteDraw_Beamos_Eyeball(int k, PrepOamCoordsRet *info) { // 859151
|
||||
}
|
||||
|
||||
void Sprite_Beamos_Laser(int k) { // 8591b5
|
||||
|
||||
|
||||
if (sprite_delay_aux1[k])
|
||||
return;
|
||||
BeamosLaser_Draw(k);
|
||||
@@ -4025,7 +3971,6 @@ void SpriteDraw_BNCFlail(int k, PrepOamCoordsRet *info) { // 85b468
|
||||
uint8 r12 = kFlailTrooperWeapon_Tab1[sprite_D[k]];
|
||||
uint8 r13 = kFlailTrooperWeapon_Tab2[sprite_D[k]];
|
||||
|
||||
uint16 r10 = (r0 & 0x1ff) >> 6;
|
||||
uint16 r2 = (r0 + 0x80) & 0x1ff;
|
||||
|
||||
uint8 r14 = ChainBallMult(kSinusLookupTable[r0 & 0xff], qq);
|
||||
@@ -4918,7 +4863,7 @@ void Guard_AnimateWeapon(int k, const PrepOamCoordsRet *poc) { // 85cb64
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_45_UsainBolt(int k) { // 85cbe0
|
||||
void Sprite_45_HogSpearMan(int k) { // 85cbe0
|
||||
Guard_HandleAllAnimation(k);
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
return;
|
||||
@@ -6427,7 +6372,7 @@ void SpritePrep_Mushroom(int k) { // 85ee53
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_Mushroom(int k) { // 85ee78
|
||||
void Sprite_E7_Mushroom(int k) { // 85ee78
|
||||
SpriteDraw_SingleLarge(k);
|
||||
if (Sprite_CheckIfLinkIsBusy())
|
||||
return;
|
||||
@@ -6445,7 +6390,7 @@ void Sprite_Mushroom(int k) { // 85ee78
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_FakeSword(int k) { // 85eeaf
|
||||
void Sprite_E8_FakeSword(int k) { // 85eeaf
|
||||
FakeSword_Draw(k);
|
||||
if (Sprite_ReturnIfPaused(k))
|
||||
return;
|
||||
@@ -6826,7 +6771,7 @@ void MagicShopAssistant_SpawnRedCauldron(int k) { // 85f5f0
|
||||
sprite_defl_bits[j] |= 0x20;
|
||||
}
|
||||
|
||||
void Sprite_PotionShop(int k) { // 85f633
|
||||
void Sprite_E9_PotionShop(int k) { // 85f633
|
||||
switch(sprite_subtype2[k]) {
|
||||
case 0: Sprite_MagicShopAssistant_Main(k); return;
|
||||
case 1: Sprite_BagOfPowder(k); return;
|
||||
@@ -7996,14 +7941,14 @@ void SpritePrep_Octorok(int k) { // 868f71
|
||||
sprite_delay_main[k] = GetRandomNumber() & 127;
|
||||
}
|
||||
|
||||
void SpritePrep_Moldorm_bounce(int k) { // 868f8a
|
||||
void SpritePrep_Moldorm(int k) { // 868f8a
|
||||
if (Sprite_ReturnIfBossFinished(k))
|
||||
return;
|
||||
sprite_ignore_projectile[k]++;
|
||||
Sprite_InitializedSegmented(k);
|
||||
}
|
||||
|
||||
void SpritePrep_Lanmolas_bounce(int k) { // 868f95
|
||||
void SpritePrep_Lanmolas(int k) { // 868f95
|
||||
static const uint8 kLanmola_InitDelay[3] = {128, 192, 255};
|
||||
|
||||
if (Sprite_ReturnIfBossFinished(k))
|
||||
@@ -9755,8 +9700,6 @@ void Sprite_28_DarkWorldHintNPC(int k) { // 86ad6f
|
||||
if (!sprite_delay_main[k])
|
||||
sprite_graphics[k] = frame_counter >> 4 & 1;
|
||||
|
||||
int type = sprite_subtype2[k];
|
||||
|
||||
switch(sprite_subtype2[k]) {
|
||||
case 0:
|
||||
switch (sprite_ai_state[k]) {
|
||||
@@ -10586,7 +10529,7 @@ void PushSwitch_Draw(int k) { // 86bb22
|
||||
if (Sprite_PrepOamCoordOrDoubleRet(k, &info))
|
||||
return;
|
||||
uint8 flags;
|
||||
sprite_oam_flags[k] = flags = overworld_palette_swap_flag ? sprite_oam_flags[k] | 0xe : sprite_oam_flags[k] & ~0xe;
|
||||
sprite_oam_flags[k] = flags = palette_swap_flag ? sprite_oam_flags[k] | 0xe : sprite_oam_flags[k] & ~0xe;
|
||||
uint8 r1 = sprite_B[k] >> 2 & 3;
|
||||
|
||||
oam_cur_ptr += 4, oam_ext_cur_ptr += 1;
|
||||
@@ -10899,7 +10842,7 @@ void Hobo_SpawnSmoke(int k) { // 86bfaf
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_73_UncleAndPriest_bounce(int k) { // 86bfe0
|
||||
void Sprite_73_UncleAndPriest(int k) { // 86bfe0
|
||||
switch (sprite_E[k]) {
|
||||
case 0:
|
||||
Sprite_Uncle(k);
|
||||
@@ -10980,7 +10923,7 @@ void SpritePrep_OldMan_bounce(int k) { // 86bff9
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_TutorialGuardOrBarrier_bounce(int k) { // 86bffe
|
||||
void Sprite_TutorialGuardOrBarrier(int k) { // 86bffe
|
||||
if (sprite_type[k] == 0x40) {
|
||||
Sprite_EvilBarrier(k);
|
||||
return;
|
||||
@@ -11013,7 +10956,7 @@ void Sprite_TutorialGuardOrBarrier_bounce(int k) { // 86bffe
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_F2_MedallionTablet_bounce(int k) { // 86c00d
|
||||
void Sprite_F2_MedallionTablet(int k) { // 86c00d
|
||||
switch (sprite_subtype2[k]) {
|
||||
case 0:
|
||||
MedallionTablet_Main(k);
|
||||
@@ -11024,7 +10967,7 @@ void Sprite_F2_MedallionTablet_bounce(int k) { // 86c00d
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_33_RupeePull_bounce(int k) { // 86c017
|
||||
void Sprite_33_RupeePull(int k) { // 86c017
|
||||
PrepOamCoordsRet info;
|
||||
Sprite_PrepOamCoord(k, &info);
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
@@ -11046,7 +10989,7 @@ void Sprite_33_RupeePull_bounce(int k) { // 86c017
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_14_ThievesTownGrate_bounce(int k) { // 86c01c
|
||||
void Sprite_14_ThievesTownGrate(int k) { // 86c01c
|
||||
PrepOamCoordsRet info;
|
||||
Sprite_PrepOamCoord(k, &info);
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
@@ -11079,14 +11022,14 @@ void SpritePrep_Snitch_bounce_3(int k) { // 86c030
|
||||
SpritePrep_Snitches(k);
|
||||
}
|
||||
|
||||
void Sprite_37_Waterfall_bounce(int k) { // 86c03a
|
||||
void Sprite_37_Waterfall(int k) { // 86c03a
|
||||
switch (sprite_subtype2[k]) {
|
||||
case 0: Waterfall(k); break;
|
||||
case 1: Sprite_BatCrash(k); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_38_EyeStatue_bounce(int k) { // 86c03f
|
||||
void Sprite_38_EyeStatue(int k) { // 86c03f
|
||||
if (!sprite_B[k]) {
|
||||
PrepOamCoordsRet info;
|
||||
Sprite_PrepOamCoord(k, &info);
|
||||
@@ -11099,7 +11042,7 @@ void Sprite_38_EyeStatue_bounce(int k) { // 86c03f
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_3A_MagicBat_bounce(int k) { // 86c044
|
||||
void Sprite_3A_MagicBat(int k) { // 86c044
|
||||
if (sprite_head_dir[k]) {
|
||||
Sprite_MadBatterBolt(k);
|
||||
return;
|
||||
@@ -11206,7 +11149,7 @@ void SpritePrep_Zelda_bounce(int k) { // 86c06c
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite_78_MrsSahasrahla_bounce(int k) { // 86c071
|
||||
void Sprite_78_MrsSahasrahla(int k) { // 86c071
|
||||
ElderWife_Draw(k);
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
return;
|
||||
@@ -11259,11 +11202,11 @@ void SpritePrep_HeartPiece(int k) { // 86c0a8
|
||||
HeartUpgrade_CheckIfAlreadyObtained(k);
|
||||
}
|
||||
|
||||
void Sprite_2D_TelepathicTile_bounce(int k) { // 86c0b2
|
||||
void Sprite_2D_TelepathicTile(int k) { // 86c0b2
|
||||
assert(0);
|
||||
}
|
||||
|
||||
void Sprite_25_TalkingTree_bounce(int k) { // 86c0d5
|
||||
void Sprite_25_TalkingTree(int k) { // 86c0d5
|
||||
switch (sprite_subtype2[k]) {
|
||||
case 0: TalkingTree_Mouth(k); break;
|
||||
case 1: TalkingTree_Eye(k); break;
|
||||
@@ -11453,7 +11396,7 @@ void Sprite_HappinessPond(int k) { // 86c44c
|
||||
if (choice_in_multiselect_box == 0) {
|
||||
int i = (link_bomb_upgrades | link_arrow_upgrades) != 0;
|
||||
sprite_graphics[k] = i * 2;
|
||||
WORD(byte_7E1CF2[0]) = WORD(kHappinessPondCostHex[i * 2]);
|
||||
WORD(dialogue_number[0]) = WORD(kHappinessPondCostHex[i * 2]);
|
||||
Sprite_ShowMessageUnconditional(0x14e);
|
||||
sprite_ai_state[k] = 2;
|
||||
flag_is_link_immobilized = 1;
|
||||
@@ -11466,7 +11409,7 @@ show_later_msg:
|
||||
break;
|
||||
case 2: {
|
||||
int i = sprite_graphics[k] + choice_in_multiselect_box;
|
||||
byte_7E1CF2[1] = kHappinessPondCostHex[i];
|
||||
dialogue_number[1] = kHappinessPondCostHex[i];
|
||||
if (link_rupees_goal < kHappinessPondCost[i]) {
|
||||
goto show_later_msg;
|
||||
} else {
|
||||
@@ -11487,7 +11430,7 @@ show_later_msg:
|
||||
sprite_ai_state[k] = 5;
|
||||
return;
|
||||
}
|
||||
byte_7E1CF2[0] = (link_rupees_in_pond / 10) * 16 + (link_rupees_in_pond % 10);
|
||||
dialogue_number[0] = (link_rupees_in_pond / 10) * 16 + (link_rupees_in_pond % 10);
|
||||
sprite_ai_state[k] = 4;
|
||||
break;
|
||||
}
|
||||
@@ -11538,7 +11481,7 @@ show_later_msg:
|
||||
int i = link_bomb_upgrades + 1;
|
||||
if (i != 8) {
|
||||
link_bomb_upgrades = i;
|
||||
byte_7E1CF2[0] = link_bomb_filler = kMaxBombsForLevelHex[i];
|
||||
dialogue_number[0] = link_bomb_filler = kMaxBombsForLevelHex[i];
|
||||
Sprite_ShowMessageUnconditional(0x96);
|
||||
} else {
|
||||
link_rupees_goal += 100;
|
||||
@@ -11575,7 +11518,7 @@ show_later_msg:
|
||||
int i = link_arrow_upgrades + 1;
|
||||
if (i != 8) {
|
||||
link_arrow_upgrades = i;
|
||||
byte_7E1CF2[0] = link_arrow_filler = kMaxArrowsForLevelHex[i];
|
||||
dialogue_number[0] = link_arrow_filler = kMaxArrowsForLevelHex[i];
|
||||
Sprite_ShowMessageUnconditional(0x97);
|
||||
} else {
|
||||
link_rupees_goal += 100;
|
||||
@@ -11804,6 +11747,12 @@ void Sprite_D8_Heart(int k) { // 86cec0
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
return;
|
||||
Sprite_CheckAbsorptionByPlayer(k);
|
||||
|
||||
// Avoid calling Sprite_HandleAbsorptionByPlayer twice, it's called
|
||||
// also from within Sprite_HandleDraggingByAncilla
|
||||
if (sprite_state[k] == 0 && (enhanced_features0 & kFeatures0_MiscBugFixes))
|
||||
return;
|
||||
|
||||
if (Sprite_HandleDraggingByAncilla(k))
|
||||
return;
|
||||
Sprite_MoveXY(k);
|
||||
@@ -11871,6 +11820,10 @@ void Sprite_E3_Fairy(int k) { // 86cf94
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Avoid calling Sprite_HandleAbsorptionByPlayer twice, it's called
|
||||
// also from within Sprite_HandleDraggingByAncilla
|
||||
if (sprite_state[k] == 0 && (enhanced_features0 & kFeatures0_MiscBugFixes))
|
||||
return;
|
||||
if (Sprite_HandleDraggingByAncilla(k))
|
||||
return;
|
||||
Faerie_HandleMovement(k);
|
||||
@@ -12994,8 +12947,8 @@ void Sprite_MazeGameGuy(int k) { // 8dcbf2
|
||||
t %= 60;
|
||||
int c = t / 10;
|
||||
t %= 10;
|
||||
byte_7E1CF2[0] = t | c << 4;
|
||||
byte_7E1CF2[1] = b | a << 4;
|
||||
dialogue_number[0] = t | c << 4;
|
||||
dialogue_number[1] = b | a << 4;
|
||||
t = Sprite_ShowMessageOnContact(k, 0xcb);
|
||||
if (t & 0x100) {
|
||||
sprite_D[k] = sprite_head_dir[k] = (uint8)t;
|
||||
@@ -13170,6 +13123,59 @@ void MedallionTablet_Draw(int k) { // 8dd1e2
|
||||
}
|
||||
|
||||
void Uncle_Draw(int k) { // 8dd391
|
||||
static const DrawMultipleData kUncleDraw_Table[48] = {
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ 0, 0, 0x0c06, 2},
|
||||
{ -7, 2, 0x0d07, 2},
|
||||
{ -7, 2, 0x0d07, 2},
|
||||
{ 10, 12, 0x8d05, 0},
|
||||
{ 10, 4, 0x8d15, 0},
|
||||
{ 0, -10, 0x0e00, 2},
|
||||
{ 0, 0, 0x0c04, 2},
|
||||
{ -7, 1, 0x0d07, 2},
|
||||
{ -7, 1, 0x0d07, 2},
|
||||
{ 10, 13, 0x8d05, 0},
|
||||
{ 10, 5, 0x8d15, 0},
|
||||
{ 0, -9, 0x0e00, 2},
|
||||
{ 0, 1, 0x4c04, 2},
|
||||
{ -7, 8, 0x8d05, 0},
|
||||
{ 1, 8, 0x8d06, 0},
|
||||
{ 0, -10, 0x0e02, 2},
|
||||
{ -6, -1, 0x4d07, 2},
|
||||
{ 0, 0, 0x0c23, 2},
|
||||
{ 0, 0, 0x0c23, 2},
|
||||
{ -9, 7, 0x8d05, 0},
|
||||
{ -1, 7, 0x8d06, 0},
|
||||
{ 0, -9, 0x0e02, 2},
|
||||
{ -6, 0, 0x4d07, 2},
|
||||
{ 0, 1, 0x0c25, 2},
|
||||
{ 0, 1, 0x0c25, 2},
|
||||
{-10, -17, 0x0d07, 2},
|
||||
{ 15, -12, 0x8d15, 0},
|
||||
{ 15, -4, 0x8d05, 0},
|
||||
{ 0, -28, 0x0e08, 2},
|
||||
{ -8, -19, 0x0c20, 2},
|
||||
{ 8, -19, 0x4c20, 2},
|
||||
{ 0, -28, 0x0e08, 2},
|
||||
{ 0, -28, 0x0e08, 2},
|
||||
{ -8, -19, 0x0c20, 2},
|
||||
{ 8, -19, 0x4c20, 2},
|
||||
{ -8, -19, 0x0c20, 2},
|
||||
{ 8, -19, 0x4c20, 2},
|
||||
};
|
||||
static const uint8 kUncleDraw_Dma3[8] = { 8, 8, 0, 0, 6, 6, 0, 0 };
|
||||
static const uint8 kUncleDraw_Dma4[8] = { 0, 0, 0, 0, 4, 4, 0, 0x8b }; // wtf
|
||||
|
||||
Oam_AllocateFromRegionB(0x18);
|
||||
const DrawMultipleData *src = &kUncleDraw_Table[sprite_D[k] * 12 + sprite_graphics[k] * 6];
|
||||
|
||||
@@ -13819,7 +13825,7 @@ void BottleMerchant_BuyBee(int k) { // 9afe88
|
||||
SpriteSfx_QueueSfx3WithPan(k, 0x13);
|
||||
tmp_counter = 4;
|
||||
do {
|
||||
int j = Sprite_SpawnDynamically(k, 0xd8, &info);
|
||||
int j = Sprite_SpawnDynamically(k, 0xdb, &info);
|
||||
if (j >= 0) {
|
||||
Sprite_SetSpawnedCoordinates(j, &info);
|
||||
sprite_x_lo[j] = info.r0_x + 4;
|
||||
@@ -18030,7 +18036,6 @@ void SpriteDraw_Moldorm_Eyeballs(int k, PrepOamCoordsRet *info) { // 9ddb9e
|
||||
static const uint8 kGiantMoldorm_Eye_Char[16] = {0xaa, 0xaa, 0xa8, 0xa8, 0x8a, 0x8a, 0xa8, 0xa8, 0xaa, 0xaa, 0xa8, 0xa8, 0x8a, 0x8a, 0xa8, 0xa8};
|
||||
static const uint8 kGiantMoldorm_Eye_Flags[16] = {0, 0, 0, 0, 0x80, 0x80, 0x40, 0x40, 0x40, 0x40, 0xc0, 0xc0, 0, 0, 0x80, 0x80};
|
||||
OamEnt *oam = GetOamCurPtr();
|
||||
uint8 yoff = kBadPullSwitch_Tab5[kBadPullSwitch_Tab4[sprite_graphics[k]]];
|
||||
int r7 = sprite_F[k] ? frame_counter : 0;
|
||||
int r6 = sprite_D[k] - 1;
|
||||
for (int i = 1; i >= 0; i--, oam++, r6 += 2) {
|
||||
@@ -20939,7 +20944,7 @@ void Sprite_9A_Kyameron(int k) { // 9e9e7b
|
||||
}
|
||||
if (sign8(--sprite_subtype2[k])) {
|
||||
sprite_subtype2[k] = 5;
|
||||
sprite_graphics[k] = (++sprite_graphics[k] & 3) + 8;
|
||||
sprite_graphics[k] = (sprite_graphics[k] + 1 & 3) + 8;
|
||||
}
|
||||
break;
|
||||
case 2: { // coagulate
|
||||
@@ -22810,7 +22815,7 @@ void Sprite_85_YellowStalfos(int k) { // 9ec37f
|
||||
}
|
||||
|
||||
sprite_obj_prio[k] |= kYellowStalfos_ObjPrio[sprite_ai_state[k]];
|
||||
YellowStalfos_Draw(k);
|
||||
YellowStalfos_Draw(k);
|
||||
if (Sprite_ReturnIfInactive(k))
|
||||
return;
|
||||
if (link_sword_type >= 3) {
|
||||
@@ -25460,7 +25465,7 @@ void SomariaPlatform_LocatePath(int k) { // 9ef640
|
||||
sprite_graphics[k]++;
|
||||
}
|
||||
|
||||
void Sprite_SomariaPlatform(int k) { // 9ef6d4
|
||||
void Sprite_ED_SomariaPlatform(int k) { // 9ef6d4
|
||||
switch(sprite_graphics[k]) {
|
||||
case 0: {
|
||||
SomariaPlatform_LocatePath(k);
|
||||
@@ -20,10 +20,10 @@ void Garnish_SetY(int k, uint16 y);
|
||||
void Sprite_WishPond3(int k);
|
||||
int Sprite_SpawnSmallSplash(int k);
|
||||
void HeartUpgrade_CheckIfAlreadyObtained(int k);
|
||||
void Sprite_MovableMantleTrampoline(int k);
|
||||
void Sprite_EE_MovableMantle(int k);
|
||||
void Sprite_GoodOrBadArcheryTarget(int k);
|
||||
void ChainBallTrooper_Draw(int k);
|
||||
void Sprite_CannonTrooper(int k);
|
||||
void Sprite_6B_CannonTrooper(int k);
|
||||
void Bee_PutInBottle(int k);
|
||||
void Sprite_Wizzbeam(int k);
|
||||
void Kiki_LyingInwait(int k);
|
||||
@@ -37,7 +37,7 @@ void Hobo_Draw(int k);
|
||||
bool Landmine_CheckDetonationFromHammer(int k);
|
||||
void Sprite_DrawLargeWaterTurbulence(int k);
|
||||
void Sprite_SpawnSparkleGarnish(int k);
|
||||
void Sprite_70_KingHelmasaurFireball_bounce(int k);
|
||||
void Sprite_70_KingHelmasaurFireball(int k);
|
||||
void Sprite_66_WallCannonVerticalLeft(int k);
|
||||
void Sprite_65_ArcheryGame(int k);
|
||||
void ArcheryGame_Host(int k);
|
||||
@@ -142,7 +142,7 @@ void Guard_HandleAllAnimation(int k);
|
||||
void Guard_AnimateHead(int k, int oam_offs, const PrepOamCoordsRet *poc);
|
||||
void Guard_AnimateBody(int k, int oam_idx, const PrepOamCoordsRet *poc);
|
||||
void Guard_AnimateWeapon(int k, const PrepOamCoordsRet *poc);
|
||||
void Sprite_45_UsainBolt(int k);
|
||||
void Sprite_45_HogSpearMan(int k);
|
||||
void BoltGuard_TriggerChaseTheme(int k);
|
||||
void Sprite_44_BluesainBolt(int k);
|
||||
void PsychoTrooper_Draw(int k);
|
||||
@@ -200,8 +200,8 @@ void Zelda_InCell(int k);
|
||||
void Zelda_EnteringSanctuary(int k);
|
||||
void Zelda_AtSanctuary(int k);
|
||||
void SpritePrep_Mushroom(int k);
|
||||
void Sprite_Mushroom(int k);
|
||||
void Sprite_FakeSword(int k);
|
||||
void Sprite_E7_Mushroom(int k);
|
||||
void Sprite_E8_FakeSword(int k);
|
||||
void FakeSword_Draw(int k);
|
||||
void SpritePrep_HeartContainer(int k);
|
||||
void Sprite_HeartContainer(int k);
|
||||
@@ -222,7 +222,7 @@ void MagicShopAssistant_SpawnPowder(int k);
|
||||
void MagicShopAssistant_SpawnGreenCauldron(int k);
|
||||
void MagicShopAssistant_SpawnBlueCauldron(int k);
|
||||
void MagicShopAssistant_SpawnRedCauldron(int k);
|
||||
void Sprite_PotionShop(int k);
|
||||
void Sprite_E9_PotionShop(int k);
|
||||
void Sprite_BagOfPowder(int k);
|
||||
void MagicPowderItem_Draw(int k);
|
||||
void Sprite_GreenCauldron(int k);
|
||||
@@ -331,8 +331,8 @@ void SpritePrep_ArmosKnight(int k);
|
||||
void SpritePrep_DesertStatue(int k);
|
||||
void SpritePrep_DoNothingD(int k);
|
||||
void SpritePrep_Octorok(int k);
|
||||
void SpritePrep_Moldorm_bounce(int k);
|
||||
void SpritePrep_Lanmolas_bounce(int k);
|
||||
void SpritePrep_Moldorm(int k);
|
||||
void SpritePrep_Lanmolas(int k);
|
||||
void SpritePrep_BigSpike(int k);
|
||||
void SpritePrep_SwimmingZora(int k);
|
||||
void SpritePrep_Geldman(int k);
|
||||
@@ -459,24 +459,24 @@ void Sprite_Hobo_Fire(int k);
|
||||
void SpritePrep_Hobo_SpawnFire(int k);
|
||||
void Sprite_Hobo_Smoke(int k);
|
||||
void Hobo_SpawnSmoke(int k);
|
||||
void Sprite_73_UncleAndPriest_bounce(int k);
|
||||
void Sprite_73_UncleAndPriest(int k);
|
||||
void SpritePrep_UncleAndPriest_bounce(int k);
|
||||
void SpritePrep_OldMan_bounce(int k);
|
||||
void Sprite_TutorialGuardOrBarrier_bounce(int k);
|
||||
void Sprite_F2_MedallionTablet_bounce(int k);
|
||||
void Sprite_33_RupeePull_bounce(int k);
|
||||
void Sprite_14_ThievesTownGrate_bounce(int k);
|
||||
void Sprite_TutorialGuardOrBarrier(int k);
|
||||
void Sprite_F2_MedallionTablet(int k);
|
||||
void Sprite_33_RupeePull(int k);
|
||||
void Sprite_14_ThievesTownGrate(int k);
|
||||
void SpritePrep_Snitch_bounce_2(int k);
|
||||
void SpritePrep_Snitch_bounce_3(int k);
|
||||
void Sprite_37_Waterfall_bounce(int k);
|
||||
void Sprite_38_EyeStatue_bounce(int k);
|
||||
void Sprite_3A_MagicBat_bounce(int k);
|
||||
void Sprite_37_Waterfall(int k);
|
||||
void Sprite_38_EyeStatue(int k);
|
||||
void Sprite_3A_MagicBat(int k);
|
||||
void SpritePrep_Zelda_bounce(int k);
|
||||
void Sprite_78_MrsSahasrahla_bounce(int k);
|
||||
void Sprite_78_MrsSahasrahla(int k);
|
||||
void Sprite_16_Elder_bounce(int k);
|
||||
void SpritePrep_HeartPiece(int k);
|
||||
void Sprite_2D_TelepathicTile_bounce(int k);
|
||||
void Sprite_25_TalkingTree_bounce(int k);
|
||||
void Sprite_2D_TelepathicTile(int k);
|
||||
void Sprite_25_TalkingTree(int k);
|
||||
void Sprite_1C_Statue(int k);
|
||||
bool Statue_CheckForSwitch(int k);
|
||||
void MovableStatue_Draw(int k);
|
||||
@@ -939,7 +939,7 @@ void SpawnApple(int k);
|
||||
void Sprite_Apple(int k);
|
||||
void Sprite_BC_Drunkard(int k);
|
||||
void SomariaPlatform_LocatePath(int k);
|
||||
void Sprite_SomariaPlatform(int k);
|
||||
void Sprite_ED_SomariaPlatform(int k);
|
||||
void SomariaPlatformAndPipe_HandleMovement(int k);
|
||||
uint8 SomariaPlatformAndPipe_CheckTile(int k);
|
||||
void SomariaPlatform_Draw(int k);
|
||||
@@ -426,8 +426,23 @@ void Follower_NotFollowing() { // 89a2b2
|
||||
Tagalong_Draw();
|
||||
} else {
|
||||
if (follower_indicator == 13 && !player_is_indoors && !super_bomb_indicator_unk2) {
|
||||
AncillaAdd_SuperBombExplosion(0x3a, 0);
|
||||
follower_dropped = 0;
|
||||
// Fixed so we wait a little bit if we can't spawn the ancilla
|
||||
if (AncillaAdd_SuperBombExplosion(0x3a, 0) >= 0) {
|
||||
follower_dropped = 0;
|
||||
|
||||
// A ticking super bomb will cancel and teleport back to you as a follower if you do
|
||||
// any of these at count 0: (1) change screen via walking, mirror, or bird travel
|
||||
// (2) fill all ancillary slots
|
||||
// (3) die with a bottled faerie.
|
||||
// Fixed this by clearing the follower indicator here, instead of in the ancilla
|
||||
// bomb code.
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes) {
|
||||
follower_indicator = 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
super_bomb_indicator_unk1 = 1;
|
||||
}
|
||||
}
|
||||
Follower_DoLayers();
|
||||
}
|
||||
@@ -647,11 +662,14 @@ incr:
|
||||
uint8 pal;
|
||||
skip_first_sprites:
|
||||
pal = kTagalongDraw_Pals[follower_indicator];
|
||||
if (pal == 7 && overworld_palette_swap_flag)
|
||||
if (pal == 7 && palette_swap_flag)
|
||||
pal = 0;
|
||||
|
||||
if (follower_indicator == 13 && super_bomb_indicator_unk2 == 1)
|
||||
pal = (frame_counter & 7);
|
||||
if (follower_indicator == 13) {
|
||||
// Display colorful superbomb palette also on frame 0.
|
||||
if (enhanced_features0 & kFeatures0_MiscBugFixes ? (super_bomb_indicator_unk2 <= 1) : (super_bomb_indicator_unk2 == 1))
|
||||
pal = (frame_counter & 7);
|
||||
}
|
||||
|
||||
const TagalongSprXY *sprd = kTagalongDraw_SprXY + frame + (kTagalongDraw_Offs[follower_indicator] >> 3);
|
||||
const TagalongDmaFlags *sprf = kTagalongDmaAndFlags + frame;
|
||||
@@ -19,6 +19,7 @@ typedef int16_t int16;
|
||||
typedef uint32_t uint32;
|
||||
typedef int32_t int32;
|
||||
typedef uint64_t uint64;
|
||||
typedef int64_t int64;
|
||||
typedef unsigned int uint;
|
||||
|
||||
#define arraysize(x) sizeof(x)/sizeof(x[0])
|
||||
@@ -38,6 +39,12 @@ typedef unsigned int uint;
|
||||
#define NOINLINE
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define kDebugFlag 1
|
||||
#else
|
||||
#define kDebugFlag 0
|
||||
#endif
|
||||
|
||||
static FORCEINLINE uint16 abs16(uint16 t) { return sign16(t) ? -t : t; }
|
||||
static FORCEINLINE uint8 abs8(uint8 t) { return sign8(t) ? -t : t; }
|
||||
static FORCEINLINE int IntMin(int a, int b) { return a < b ? a : b; }
|
||||
@@ -45,13 +52,20 @@ static FORCEINLINE int IntMax(int a, int b) { return a > b ? a : b; }
|
||||
static FORCEINLINE uint UintMin(uint a, uint b) { return a < b ? a : b; }
|
||||
static FORCEINLINE uint UintMax(uint a, uint b) { return a > b ? a : b; }
|
||||
|
||||
// windows.h defines this too
|
||||
#ifdef HIBYTE
|
||||
#undef HIBYTE
|
||||
#endif
|
||||
|
||||
#define BYTE(x) (*(uint8*)&(x))
|
||||
#define HIBYTE(x) (((uint8*)&(x))[1])
|
||||
#define WORD(x) (*(uint16*)&(x))
|
||||
#define DWORD(x) (*(uint32*)&(x))
|
||||
#define XY(x, y) ((y)*64+(x))
|
||||
|
||||
#ifndef swap16
|
||||
static inline uint16 swap16(uint16 v) { return (v << 8) | (v >> 8); }
|
||||
#endif
|
||||
|
||||
typedef struct Point16U {
|
||||
uint16 x, y;
|
||||
@@ -78,6 +92,12 @@ typedef struct OamEnt {
|
||||
uint8 x, y, charnum, flags;
|
||||
} OamEnt;
|
||||
|
||||
typedef struct MemBlk {
|
||||
const uint8 *ptr;
|
||||
size_t size;
|
||||
} MemBlk;
|
||||
MemBlk FindIndexInMemblk(MemBlk data, size_t i);
|
||||
|
||||
void NORETURN Die(const char *error);
|
||||
|
||||
#endif // ZELDA3_TYPES_H_
|
||||
280
src/util.c
Normal file
280
src/util.c
Normal file
@@ -0,0 +1,280 @@
|
||||
#include "util.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
char *NextDelim(char **s, int sep) {
|
||||
char *r = *s;
|
||||
if (r) {
|
||||
while (r[0] == ' ' || r[0] == '\t')
|
||||
r++;
|
||||
char *t = strchr(r, sep);
|
||||
*s = t ? (*t++ = 0, t) : NULL;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline int ToLower(int a) {
|
||||
return a + (a >= 'A' && a <= 'Z') * 32;
|
||||
}
|
||||
|
||||
bool StringEqualsNoCase(const char *a, const char *b) {
|
||||
for (;;) {
|
||||
int aa = ToLower(*a++), bb = ToLower(*b++);
|
||||
if (aa != bb)
|
||||
return false;
|
||||
if (aa == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const char *StringStartsWithNoCase(const char *a, const char *b) {
|
||||
for (;; a++, b++) {
|
||||
int aa = ToLower(*a), bb = ToLower(*b);
|
||||
if (bb == 0)
|
||||
return a;
|
||||
if (aa != bb)
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 *ReadWholeFile(const char *name, size_t *length) {
|
||||
FILE *f = fopen(name, "rb");
|
||||
if (f == NULL)
|
||||
return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
size_t size = ftell(f);
|
||||
rewind(f);
|
||||
uint8 *buffer = (uint8 *)malloc(size + 1);
|
||||
if (!buffer) Die("malloc failed");
|
||||
// Always zero terminate so this function can be used also for strings.
|
||||
buffer[size] = 0;
|
||||
if (fread(buffer, 1, size, f) != size)
|
||||
Die("fread failed");
|
||||
fclose(f);
|
||||
if (length) *length = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
char *NextLineStripComments(char **s) {
|
||||
char *p = *s;
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
// find end of line
|
||||
char *eol = strchr(p, '\n');
|
||||
*s = eol ? eol + 1 : NULL;
|
||||
eol = eol ? eol : p + strlen(p);
|
||||
// strip comments
|
||||
char *comment = memchr(p, '#', eol - p);
|
||||
eol = comment ? comment : eol;
|
||||
// strip trailing whitespace
|
||||
while (eol > p && (eol[-1] == '\r' || eol[-1] == ' ' || eol[-1] == '\t'))
|
||||
eol--;
|
||||
*eol = 0;
|
||||
// strip leading whitespace
|
||||
while (p[0] == ' ' || p[0] == '\t')
|
||||
p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Return the next possibly quoted string, space separated, or the empty string
|
||||
char *NextPossiblyQuotedString(char **s) {
|
||||
char *r = *s, *t;
|
||||
while (*r == ' ' || *r == '\t')
|
||||
r++;
|
||||
if (*r == '"') {
|
||||
for (t = ++r; *t && *t != '"'; t++);
|
||||
} else {
|
||||
for (t = r; *t && *t != ' ' && *t != '\t'; t++);
|
||||
}
|
||||
if (*t) *t++ = 0;
|
||||
while (*t == ' ' || *t == '\t')
|
||||
t++;
|
||||
*s = t;
|
||||
return r;
|
||||
}
|
||||
|
||||
char *ReplaceFilenameWithNewPath(const char *old_path, const char *new_path) {
|
||||
size_t olen = strlen(old_path);
|
||||
size_t nlen = strlen(new_path) + 1;
|
||||
while (olen && old_path[olen - 1] != '/' && old_path[olen - 1] != '\\')
|
||||
olen--;
|
||||
char *result = malloc(olen + nlen);
|
||||
memcpy(result, old_path, olen);
|
||||
memcpy(result + olen, new_path, nlen);
|
||||
return result;
|
||||
}
|
||||
|
||||
char *SplitKeyValue(char *p) {
|
||||
char *equals = strchr(p, '=');
|
||||
if (equals == NULL)
|
||||
return NULL;
|
||||
char *kr = equals;
|
||||
while (kr > p && (kr[-1] == ' ' || kr[-1] == '\t'))
|
||||
kr--;
|
||||
*kr = 0;
|
||||
char *v = equals + 1;
|
||||
while (v[0] == ' ' || v[0] == '\t')
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
const char *SkipPrefix(const char *big, const char *little) {
|
||||
for (; *little; big++, little++) {
|
||||
if (*little != *big)
|
||||
return NULL;
|
||||
}
|
||||
return big;
|
||||
}
|
||||
|
||||
void StrSet(char **rv, const char *s) {
|
||||
char *news = strdup(s);
|
||||
char *old = *rv;
|
||||
*rv = news;
|
||||
free(old);
|
||||
}
|
||||
|
||||
char *StrFmt(const char *fmt, ...) {
|
||||
char buf[4096];
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
int n = vsnprintf(buf, sizeof(buf), fmt, va);
|
||||
if (n < 0 || n >= sizeof(buf)) Die("vsnprintf failed");
|
||||
va_end(va);
|
||||
return strdup(buf);
|
||||
}
|
||||
|
||||
void ByteArray_Resize(ByteArray *arr, size_t new_size) {
|
||||
arr->size = new_size;
|
||||
if (new_size > arr->capacity) {
|
||||
size_t minsize = arr->capacity + (arr->capacity >> 1) + 8;
|
||||
arr->capacity = new_size < minsize ? minsize : new_size;
|
||||
void *data = realloc(arr->data, arr->capacity);
|
||||
if (!data) Die("memory allocation failed");
|
||||
arr->data = data;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteArray_Destroy(ByteArray *arr) {
|
||||
void *data = arr->data;
|
||||
arr->data = NULL;
|
||||
free(data);
|
||||
}
|
||||
|
||||
void ByteArray_AppendData(ByteArray *arr, const uint8 *data, size_t data_size) {
|
||||
ByteArray_Resize(arr, arr->size + data_size);
|
||||
memcpy(arr->data + arr->size - data_size, data, data_size);
|
||||
}
|
||||
|
||||
void ByteArray_AppendByte(ByteArray *arr, uint8 v) {
|
||||
ByteArray_Resize(arr, arr->size + 1);
|
||||
arr->data[arr->size - 1] = v;
|
||||
}
|
||||
|
||||
// Automatically selects between 16 or 32 bit indexes. Can hold up to 8192 elements in 16-bit mode.
|
||||
MemBlk FindIndexInMemblk(MemBlk data, size_t i) {
|
||||
if (data.size < 2)
|
||||
return (MemBlk) { 0, 0 };
|
||||
size_t end = data.size - 2, left_off, right_off;
|
||||
size_t mx = *(uint16 *)(data.ptr + end);
|
||||
if (mx < 8192) {
|
||||
if (i > mx || mx * 2 > end)
|
||||
return (MemBlk) { 0, 0 };
|
||||
left_off = ((i == 0) ? mx * 2 : mx * 2 + *(uint16 *)(data.ptr + i * 2 - 2));
|
||||
right_off = (i == mx) ? end : mx * 2 + *(uint16 *)(data.ptr + i * 2);
|
||||
} else {
|
||||
mx -= 8192;
|
||||
if (i > mx || mx * 4 > end)
|
||||
return (MemBlk) { 0, 0 };
|
||||
left_off = ((i == 0) ? mx * 4 : mx * 4 + *(uint32 *)(data.ptr + i * 4 - 4));
|
||||
right_off = (i == mx) ? end : mx * 4 + *(uint32 *)(data.ptr + i * 4);
|
||||
}
|
||||
if (left_off > right_off || right_off > end)
|
||||
return (MemBlk) { 0, 0 };
|
||||
return (MemBlk) { data.ptr + left_off, right_off - left_off };
|
||||
}
|
||||
|
||||
|
||||
static uint64 BpsDecodeInt(const uint8 **src) {
|
||||
uint64 data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8 x = *(*src)++;
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
#define CRC32_POLYNOMIAL 0xEDB88320
|
||||
|
||||
static uint32 crc32(const void *data, size_t length) {
|
||||
uint32 crc = 0xFFFFFFFF;
|
||||
const uint8 *byteData = (const uint8 *)data;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
crc ^= byteData[i];
|
||||
for (int j = 0; j < 8; j++)
|
||||
crc = (crc >> 1) ^ ((crc & 1) * CRC32_POLYNOMIAL);
|
||||
}
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
uint8 *ApplyBps(const uint8 *src, size_t src_size_in,
|
||||
const uint8 *bps, size_t bps_size, size_t *length_out) {
|
||||
const uint8 *bps_end = bps + bps_size - 12;
|
||||
|
||||
if (memcmp(bps, "BPS1", 4))
|
||||
return NULL;
|
||||
if (crc32(src, src_size_in) != *(uint32 *)(bps_end))
|
||||
return NULL;
|
||||
if (crc32(bps, bps_size - 4) != *(uint32 *)(bps_end + 8))
|
||||
return NULL;
|
||||
|
||||
bps += 4;
|
||||
uint32 src_size = BpsDecodeInt(&bps);
|
||||
uint32 dst_size = BpsDecodeInt(&bps);
|
||||
uint32 meta_size = BpsDecodeInt(&bps);
|
||||
uint32 outputOffset = 0;
|
||||
uint32 sourceRelativeOffset = 0;
|
||||
uint32 targetRelativeOffset = 0;
|
||||
if (src_size != src_size_in)
|
||||
return NULL;
|
||||
*length_out = dst_size;
|
||||
uint8 *dst = malloc(dst_size);
|
||||
if (!dst)
|
||||
return NULL;
|
||||
while (bps < bps_end) {
|
||||
uint32 cmd = BpsDecodeInt(&bps);
|
||||
uint32 length = (cmd >> 2) + 1;
|
||||
switch (cmd & 3) {
|
||||
case 0:
|
||||
while(length--) {
|
||||
dst[outputOffset] = src[outputOffset];
|
||||
outputOffset++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
while (length--)
|
||||
dst[outputOffset++] = *bps++;
|
||||
break;
|
||||
case 2:
|
||||
cmd = BpsDecodeInt(&bps);
|
||||
sourceRelativeOffset += (cmd & 1 ? -1 : +1) * (cmd >> 1);
|
||||
while (length--)
|
||||
dst[outputOffset++] = src[sourceRelativeOffset++];
|
||||
break;
|
||||
default:
|
||||
cmd = BpsDecodeInt(&bps);
|
||||
targetRelativeOffset += (cmd & 1 ? -1 : +1) * (cmd >> 1);
|
||||
while(length--)
|
||||
dst[outputOffset++] = dst[targetRelativeOffset++];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dst_size != outputOffset)
|
||||
return NULL;
|
||||
if (crc32(dst, dst_size) != *(uint32 *)(bps_end + 4))
|
||||
return NULL;
|
||||
return dst;
|
||||
}
|
||||
41
src/util.h
Normal file
41
src/util.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef ZELDA3_UTIL_H_
|
||||
#define ZELDA3_UTIL_H_
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef struct SDL_Window SDL_Window;
|
||||
|
||||
struct RendererFuncs {
|
||||
bool (*Initialize)(SDL_Window *window);
|
||||
void (*Destroy)();
|
||||
void (*BeginDraw)(int width, int height, uint8 **pixels, int *pitch);
|
||||
void (*EndDraw)();
|
||||
};
|
||||
|
||||
|
||||
typedef struct ByteArray {
|
||||
uint8 *data;
|
||||
size_t size, capacity;
|
||||
} ByteArray;
|
||||
|
||||
void ByteArray_Resize(ByteArray *arr, size_t new_size);
|
||||
void ByteArray_Destroy(ByteArray *arr);
|
||||
void ByteArray_AppendData(ByteArray *arr, const uint8 *data, size_t data_size);
|
||||
void ByteArray_AppendByte(ByteArray *arr, uint8 v);
|
||||
|
||||
uint8 *ReadWholeFile(const char *name, size_t *length);
|
||||
char *NextDelim(char **s, int sep);
|
||||
char *NextLineStripComments(char **s);
|
||||
char *NextPossiblyQuotedString(char **s);
|
||||
char *SplitKeyValue(char *p);
|
||||
bool StringEqualsNoCase(const char *a, const char *b);
|
||||
const char *StringStartsWithNoCase(const char *a, const char *b);
|
||||
bool ParseBool(const char *value, bool *result);
|
||||
const char *SkipPrefix(const char *big, const char *little);
|
||||
void StrSet(char **rv, const char *s);
|
||||
char *StrFmt(const char *fmt, ...);
|
||||
char *ReplaceFilenameWithNewPath(const char *old_path, const char *new_path);
|
||||
uint8 *ApplyBps(const uint8 *src, size_t src_size_in,
|
||||
const uint8 *bps, size_t bps_size, size_t *length_out);
|
||||
|
||||
#endif // ZELDA3_UTIL_H_
|
||||
@@ -180,7 +180,7 @@
|
||||
#define sound_effect_2 (*(uint8*)(g_ram+0x12F))
|
||||
#define music_unk1 (*(uint8*)(g_ram+0x130))
|
||||
#define sound_effect_ambient_last (*(uint8*)(g_ram+0x131))
|
||||
#define buffer_for_playing_songs (*(uint8*)(g_ram+0x132))
|
||||
#define queued_music_control (*(uint8*)(g_ram+0x132))
|
||||
#define last_music_control (*(uint8*)(g_ram+0x133))
|
||||
#define animated_tile_vram_addr (*(uint16*)(g_ram+0x134))
|
||||
#define flag_which_music_type (*(uint8*)(g_ram+0x136))
|
||||
@@ -388,7 +388,7 @@
|
||||
#define link_force_hold_sword_up (*(uint8*)(g_ram+0x3EF))
|
||||
#define flute_countdown (*(uint8*)(g_ram+0x3F0))
|
||||
#define tiledetect_var4 (*(uint16*)(g_ram+0x3F1))
|
||||
#define byte_7E03F3 (*(uint8*)(g_ram+0x3F3))
|
||||
#define link_on_conveyor_belt (*(uint8*)(g_ram+0x3F3))
|
||||
#define dung_unk6 (*(uint8*)(g_ram+0x3F4))
|
||||
#define link_timer_tempbunny (*(uint16*)(g_ram+0x3F5))
|
||||
#define link_need_for_poof_for_transform (*(uint8*)(g_ram+0x3F7))
|
||||
@@ -603,19 +603,19 @@
|
||||
#define unused_config_gfx (*(uint16*)(g_ram+0xAA6))
|
||||
#define overworld_palette_aux_or_main (*(uint16*)(g_ram+0xAA8))
|
||||
#define load_chr_halfslot_even_odd (*(uint8*)(g_ram+0xAAA))
|
||||
#define overworld_palette_sp0 (*(uint8*)(g_ram+0xAAC))
|
||||
#define sprite_aux1_palette (*(uint8*)(g_ram+0xAAD))
|
||||
#define sprite_aux2_palette (*(uint8*)(g_ram+0xAAE))
|
||||
#define palette_sp0l (*(uint8*)(g_ram+0xAAC))
|
||||
#define palette_sp5l (*(uint8*)(g_ram+0xAAD))
|
||||
#define palette_sp6l (*(uint8*)(g_ram+0xAAE))
|
||||
#define byte_7E0AB0 (*(uint8*)(g_ram+0xAB0))
|
||||
#define palette_sp6 (*(uint8*)(g_ram+0xAB1))
|
||||
#define palette_sp6r_indoors (*(uint8*)(g_ram+0xAB1))
|
||||
#define hud_palette (*(uint8*)(g_ram+0xAB2))
|
||||
#define overworld_palette_mode (*(uint8*)(g_ram+0xAB3))
|
||||
#define overworld_palette_aux1_bp2to4_hi (*(uint8*)(g_ram+0xAB4))
|
||||
#define overworld_palette_aux2_bp5to7_hi (*(uint8*)(g_ram+0xAB5))
|
||||
#define dung_hdr_palette_1 (*(uint8*)(g_ram+0xAB6))
|
||||
#define palette_main_indoors (*(uint8*)(g_ram+0xAB6))
|
||||
#define byte_7E0AB7 (*(uint8*)(g_ram+0xAB7))
|
||||
#define overworld_palette_aux3_bp7_lo (*(uint8*)(g_ram+0xAB8))
|
||||
#define overworld_palette_swap_flag (*(uint8*)(g_ram+0xABD))
|
||||
#define palette_swap_flag (*(uint8*)(g_ram+0xABD))
|
||||
#define flag_overworld_area_did_change (*(uint8*)(g_ram+0xABF))
|
||||
#define dma_source_addr_6 (*(uint16*)(g_ram+0xAC0))
|
||||
#define dma_source_addr_11 (*(uint16*)(g_ram+0xAC2))
|
||||
@@ -814,12 +814,12 @@
|
||||
#define text_msgbox_topleft_copy (*(uint16*)(g_ram+0x1CD0))
|
||||
#define text_msgbox_topleft (*(uint16*)(g_ram+0x1CD2))
|
||||
#define text_render_state (*(uint8*)(g_ram+0x1CD4))
|
||||
#define vwf_line_mode (*(uint8*)(g_ram+0x1CD5))
|
||||
#define vwf_line_speed_cur (*(uint8*)(g_ram+0x1CD5))
|
||||
#define vwf_line_speed (*(uint8*)(g_ram+0x1CD6))
|
||||
#define text_incremental_state (*(uint8*)(g_ram+0x1CD7))
|
||||
#define messaging_module (*(uint8*)(g_ram+0x1CD8))
|
||||
#define dialogue_msg_dst_offs (*(uint16*)(g_ram+0x1CD9))
|
||||
#define byte_7E1CDC (*(uint8*)(g_ram+0x1CDC))
|
||||
#define dialogue_msg_read_pos (*(uint16*)(g_ram+0x1CD9))
|
||||
#define dialogue_text_color (*(uint8*)(g_ram+0x1CDC))
|
||||
#define dialogue_msg_src_offs (*(uint16*)(g_ram+0x1CDD))
|
||||
#define byte_7E1CDF (*(uint8*)(g_ram+0x1CDF))
|
||||
#define text_wait_countdown (*(uint16*)(g_ram+0x1CE0))
|
||||
@@ -827,9 +827,11 @@
|
||||
#define text_next_position (*(uint8*)(g_ram+0x1CE6))
|
||||
#define choice_in_multiselect_box (*(uint8*)(g_ram+0x1CE8))
|
||||
#define text_wait_countdown2 (*(uint8*)(g_ram+0x1CE9))
|
||||
#define byte_7E1CEA (*(uint8*)(g_ram+0x1CEA))
|
||||
|
||||
// This seems never nonzero
|
||||
#define dialogue_scroll_speed (*(uint8*)(g_ram+0x1CEA))
|
||||
#define dialogue_message_index (*(uint16*)(g_ram+0x1CF0))
|
||||
#define byte_7E1CF2 ((uint8*)(g_ram+0x1CF2))
|
||||
#define dialogue_number ((uint8*)(g_ram+0x1CF2))
|
||||
#define choice_in_multiselect_box_bak (*(uint8*)(g_ram+0x1CF4))
|
||||
#define alt_sprite_state ((uint8*)(g_ram+0x1D00))
|
||||
#define alt_sprite_type ((uint8*)(g_ram+0x1D10))
|
||||
@@ -1430,7 +1432,10 @@
|
||||
#define hdma_table_dynamic_orig_pos ((uint16*)(g_ram+0x1B00))
|
||||
#define hdma_table_dynamic ((uint16*)(g_ram+0x1DBA0))
|
||||
|
||||
// Allocate 64 bytes that we can use for msu resume.
|
||||
#define msu_resume_info ((uint8*)(g_ram+0x1DB60))
|
||||
|
||||
#define msu_resume_info_alt ((uint8*)(g_ram+0x1DB20))
|
||||
|
||||
|
||||
typedef struct MovableBlockData {
|
||||
@@ -46,12 +46,14 @@ static void MakeSnapshot(Snapshot *s) {
|
||||
memcpy(s->ram, g_snes->ram, 0x20000);
|
||||
memcpy(s->sram, g_snes->cart->ram, g_snes->cart->ramSize);
|
||||
memcpy(s->vram, g_snes->ppu->vram, sizeof(uint16) * 0x8000);
|
||||
memcpy(s->ram + 0x1DBA0, s->ram + 0x1B00, 224 * 2); // hdma_table (partial)
|
||||
}
|
||||
|
||||
static void MakeMySnapshot(Snapshot *s) {
|
||||
memcpy(s->ram, g_zenv.ram, 0x20000);
|
||||
memcpy(s->sram, g_zenv.sram, 0x2000);
|
||||
memcpy(s->vram, g_zenv.ppu->vram, sizeof(uint16) * 0x8000);
|
||||
memcpy(s->ram + 0x1B00, s->ram + 0x1DBA0, 224 * 2); // hdma_table (partial)
|
||||
}
|
||||
|
||||
static void RestoreMySnapshot(Snapshot *s) {
|
||||
@@ -105,11 +107,14 @@ static void VerifySnapshotsEq(Snapshot *b, Snapshot *a, Snapshot *prev) {
|
||||
memcpy(&b->ram[0x1f0d], &a->ram[0x1f0d], 0x3f - 0xd);
|
||||
memcpy(b->ram + 0x138, a->ram + 0x138, 256 - 0x38); // copy the stack over
|
||||
|
||||
memcpy(a->ram + 0x1DBA0, b->ram + 0x1DBA0, 240 * 2); // hdma_table
|
||||
memcpy(b->ram + 0x1B00, b->ram + 0x1DBA0, 224 * 2); // hdma_table (partial)
|
||||
|
||||
memcpy(a->ram + 0x1cc0, b->ram + 0x1cc0, 2); // some leftover stuff in hdma table
|
||||
memcpy(a->ram + 0x1dd60, b->ram + 0x1dd60, 16 * 2); // some leftover stuff in hdma table
|
||||
|
||||
memcpy(a->ram + 0x1db20, b->ram + 0x1db20, 64 * 2); // msu
|
||||
a->ram[0x654] = b->ram[0x654]; // msu_volume
|
||||
|
||||
memcpy(a->ram + 0x1CDD, b->ram + 0x1CDD, 2); // dialogue_msg_src_offs
|
||||
|
||||
if (memcmp(b->ram, a->ram, 0x20000)) {
|
||||
fprintf(stderr, "@%d: Memory compare failed (mine != theirs, prev):\n", frame_counter);
|
||||
int j = 0;
|
||||
@@ -165,7 +170,7 @@ static void VerifySnapshotsEq(Snapshot *b, Snapshot *a, Snapshot *prev) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t *RomByte(Cart *cart, uint32_t addr) {
|
||||
uint8_t *RomByte(Cart *cart, uint32_t addr) {
|
||||
return &cart->rom[(((addr >> 16) << 15) | (addr & 0x7fff)) & (cart->romSize - 1)];
|
||||
}
|
||||
|
||||
@@ -351,7 +356,7 @@ again_mine:
|
||||
VerifySnapshotsEq(&g_snapshot_mine, &g_snapshot_theirs, &g_snapshot_before);
|
||||
|
||||
if (g_fail) {
|
||||
// g_fail = false;
|
||||
g_fail = false;
|
||||
if (1) {
|
||||
RestoreMySnapshot(&g_snapshot_before);
|
||||
//SaveLoadSlot(kSaveLoad_Save, 0);
|
||||
@@ -382,6 +387,14 @@ static void PatchRomWord(uint8_t *rom, uint32_t addr, uint16 old_value, uint16 v
|
||||
WORD(rom[(addr >> 16) << 15 | (addr & 0x7fff)]) = value;
|
||||
}
|
||||
|
||||
static void PatchRomArray(uint8_t *rom, uint32_t addr, const uint8 *values, int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
rom[(addr >> 16) << 15 | (addr & 0x7fff)] = values[i];
|
||||
addr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void PatchRom(uint8_t *rom) {
|
||||
// fix a bug with unitialized memory
|
||||
@@ -506,6 +519,8 @@ static void PatchRom(uint8_t *rom) {
|
||||
|
||||
PatchRomBP(rom, 0x1DCDEB); // y is destroyed earlier, restore it..
|
||||
|
||||
PatchRomBP(rom, 0x7B269); // Link_APress_LiftCarryThrow oob
|
||||
|
||||
// Smithy_Frog doesn't save X
|
||||
memmove(rom + 0x332b8, rom + 0x332b7, 4); rom[0x332b7] = 0xfa;
|
||||
|
||||
@@ -552,6 +567,10 @@ static void PatchRom(uint8_t *rom) {
|
||||
|
||||
PatchRomWord(rom, 0xddfac + 1, 0xfa85, 0xfa70); // call Hud_Rebuild instead of Hud_UpdateOnly
|
||||
|
||||
// Make sure it's not calling Decomp_spr on tilesheets less than 12
|
||||
PatchRomWord(rom, 0xe589, 0xe772, 0xe852); // call New addr
|
||||
static const uint8 kFixSoItWontDecodeSheetLessThan12[] = { 0xc0, 0x0c, 0xb0, 0x02, 0xa0, 0x0c, 0x4c, 0x72, 0xe7 };
|
||||
PatchRomArray(rom, 0xe852, kFixSoItWontDecodeSheetLessThan12, sizeof(kFixSoItWontDecodeSheetLessThan12));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include "snes/snes_regs.h"
|
||||
#include "snes/dma.h"
|
||||
#include "spc_player.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "audio.h"
|
||||
#include "assets.h"
|
||||
ZeldaEnv g_zenv;
|
||||
uint8 g_ram[131072];
|
||||
|
||||
@@ -58,57 +60,6 @@ static const uint8 kMapModeHdma1[7] = {0xf0, AT_WORD(0xdee7), 0xf0, AT_WORD(0xdf
|
||||
static const uint8 kAttractIndirectHdmaTab[7] = {0xf0, AT_WORD(0x1b00), 0xf0, AT_WORD(0x1be0), 0};
|
||||
static const uint8 kHdmaTableForPrayingScene[7] = {0xf8, AT_WORD(0x1b00), 0xf8, AT_WORD(0x1bf0), 0};
|
||||
|
||||
|
||||
// Maintain a queue cause the snes and audio callback are not in sync.
|
||||
struct ApuWriteEnt {
|
||||
uint8 ports[4];
|
||||
};
|
||||
static struct ApuWriteEnt g_apu_write_ents[16], g_apu_write;
|
||||
static uint8 g_apu_write_ent_pos, g_apu_write_count, g_apu_total_write;
|
||||
void zelda_apu_write(uint32_t adr, uint8_t val) {
|
||||
g_apu_write.ports[adr & 0x3] = val;
|
||||
}
|
||||
|
||||
void ZeldaPushApuState() {
|
||||
g_apu_write_ents[g_apu_write_ent_pos++ & 0xf] = g_apu_write;
|
||||
if (g_apu_write_count < 16)
|
||||
g_apu_write_count++;
|
||||
g_apu_total_write++;
|
||||
}
|
||||
|
||||
void ZeldaPopApuState() {
|
||||
if (g_apu_write_count != 0)
|
||||
memcpy(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count--) & 0xf], 4);
|
||||
}
|
||||
|
||||
void ZeldaDiscardUnusedAudioFrames() {
|
||||
if (g_apu_write_count != 0 && memcmp(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count) & 0xf], 4) == 0) {
|
||||
if (g_apu_total_write >= 16) {
|
||||
g_apu_total_write = 14;
|
||||
g_apu_write_count--;
|
||||
}
|
||||
} else {
|
||||
g_apu_total_write = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ZeldaResetApuQueue() {
|
||||
g_apu_write_ent_pos = g_apu_total_write = g_apu_write_count = 0;
|
||||
}
|
||||
|
||||
|
||||
uint8_t zelda_read_apui00() {
|
||||
// This needs to be here because the ancilla code reads
|
||||
// from the apu and we don't want to make the core code
|
||||
// dependent on the apu timings, so relocated this value
|
||||
// to 0x648.
|
||||
return g_ram[kRam_APUI00];
|
||||
}
|
||||
|
||||
uint8_t zelda_apu_read(uint32_t adr) {
|
||||
return g_zenv.player->port_to_snes[adr & 0x3];
|
||||
}
|
||||
|
||||
void zelda_ppu_write(uint32_t adr, uint8_t val) {
|
||||
assert(adr >= INIDISP && adr <= STAT78);
|
||||
ppu_write(g_zenv.ppu, (uint8)adr, val);
|
||||
@@ -194,10 +145,16 @@ static void ConfigurePpuSideSpace() {
|
||||
if (mod == 14)
|
||||
mod = saved_module_for_menu;
|
||||
if (mod == 9) {
|
||||
// outdoors
|
||||
extra_left = BG2HOFS_copy2 - ow_scroll_vars0.xstart;
|
||||
extra_right = ow_scroll_vars0.xend - BG2HOFS_copy2;
|
||||
extra_bottom = ow_scroll_vars0.yend - BG2VOFS_copy2;
|
||||
if (main_module_index == 14 && submodule_index == 7 && overworld_map_state >= 4) {
|
||||
// World map
|
||||
extra_left = kPpuExtraLeftRight, extra_right = kPpuExtraLeftRight;
|
||||
extra_bottom = 16;
|
||||
} else {
|
||||
// outdoors
|
||||
extra_left = BG2HOFS_copy2 - ow_scroll_vars0.xstart;
|
||||
extra_right = ow_scroll_vars0.xend - BG2HOFS_copy2;
|
||||
extra_bottom = ow_scroll_vars0.yend - BG2VOFS_copy2;
|
||||
}
|
||||
} else if (mod == 7) {
|
||||
// indoors, except when the light cone is in use
|
||||
if (!(hdr_dungeon_dark_with_lantern && TS_copy != 0)) {
|
||||
@@ -215,10 +172,10 @@ static void ConfigurePpuSideSpace() {
|
||||
PpuSetExtraSideSpace(g_zenv.ppu, extra_left, extra_right, extra_bottom);
|
||||
}
|
||||
|
||||
bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
|
||||
void ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
|
||||
SimpleHdma hdma_chans[2];
|
||||
|
||||
bool rv = PpuBeginDrawing(g_zenv.ppu, pixel_buffer, pitch, render_flags);
|
||||
PpuBeginDrawing(g_zenv.ppu, pixel_buffer, pitch, render_flags);
|
||||
|
||||
dma_startDma(g_zenv.dma, HDMAEN_copy, true);
|
||||
|
||||
@@ -257,8 +214,6 @@ bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags) {
|
||||
SimpleHdma_DoLine(&hdma_chans[0]);
|
||||
SimpleHdma_DoLine(&hdma_chans[1]);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void HdmaSetup(uint32 addr6, uint32 addr7, uint8 transfer_unit, uint8 reg6, uint8 reg7, uint8 indirect_bank) {
|
||||
@@ -283,12 +238,9 @@ static void ZeldaInitializationCode() {
|
||||
zelda_snes_dummy_write(NMITIMEN, 0);
|
||||
zelda_snes_dummy_write(HDMAEN, 0);
|
||||
zelda_snes_dummy_write(MDMAEN, 0);
|
||||
zelda_apu_write(APUI00, 0);
|
||||
zelda_apu_write(APUI01, 0);
|
||||
zelda_apu_write(APUI02, 0);
|
||||
zelda_apu_write(APUI03, 0);
|
||||
|
||||
Sound_LoadIntroSongBank();
|
||||
|
||||
Startup_InitializeMemory();
|
||||
|
||||
animated_tile_data_src = 0xa680;
|
||||
@@ -373,7 +325,6 @@ static void EmuSyncMemoryRegion(void *ptr, size_t n) {
|
||||
memcpy(g_emu_memory_ptr + (data - g_ram), data, n);
|
||||
}
|
||||
|
||||
|
||||
static void Startup_InitializeMemory() { // 8087c0
|
||||
memset(g_ram + 0x0, 0, 0x2000);
|
||||
main_palette_buffer[0] = 0;
|
||||
@@ -389,38 +340,6 @@ static void Startup_InitializeMemory() { // 8087c0
|
||||
flag_update_cgram_in_nmi++;
|
||||
}
|
||||
|
||||
|
||||
typedef struct ByteArray {
|
||||
uint8 *data;
|
||||
size_t size, capacity;
|
||||
} ByteArray;
|
||||
|
||||
void ByteArray_Resize(ByteArray *arr, size_t new_size) {
|
||||
arr->size = new_size;
|
||||
if (new_size > arr->capacity) {
|
||||
size_t minsize = arr->capacity + (arr->capacity >> 1) + 8;
|
||||
arr->capacity = new_size < minsize ? minsize : new_size;
|
||||
void *data = realloc(arr->data, arr->capacity);
|
||||
if (!data) Die("memory allocation failed");
|
||||
arr->data = data;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteArray_Destroy(ByteArray *arr) {
|
||||
free(arr->data);
|
||||
arr->data = NULL;
|
||||
}
|
||||
|
||||
void ByteArray_AppendData(ByteArray *arr, const uint8 *data, size_t data_size) {
|
||||
ByteArray_Resize(arr, arr->size + data_size);
|
||||
memcpy(arr->data + arr->size - data_size, data, data_size);
|
||||
}
|
||||
|
||||
void ByteArray_AppendByte(ByteArray *arr, uint8 v) {
|
||||
ByteArray_Resize(arr, arr->size + 1);
|
||||
arr->data[arr->size - 1] = v;
|
||||
}
|
||||
|
||||
void ByteArray_AppendVl(ByteArray *arr, uint32 v) {
|
||||
for (; v >= 255; v -= 255)
|
||||
ByteArray_AppendByte(arr, 255);
|
||||
@@ -464,33 +383,30 @@ void ZeldaReset(bool preserve_sram) {
|
||||
memset(g_zenv.ram, 0, 0x20000);
|
||||
if (!preserve_sram)
|
||||
memset(g_zenv.sram, 0, 0x2000);
|
||||
|
||||
SpcPlayer_Initialize(g_zenv.player);
|
||||
ZeldaApuLock();
|
||||
ZeldaRestoreMusicAfterLoad_Locked(true);
|
||||
ZeldaApuUnlock();
|
||||
EmuSynchronizeWholeState();
|
||||
ZeldaResetApuQueue();
|
||||
ZeldaOpenMsuFile();
|
||||
|
||||
}
|
||||
|
||||
static void LoadSnesState(SaveLoadFunc *func, void *ctx) {
|
||||
// Do the actual loading
|
||||
ZeldaApuLock();
|
||||
InternalSaveLoad(func, ctx);
|
||||
memcpy(g_zenv.ram + 0x1DBA0, g_zenv.ram + 0x1b00, 224 * 2); // hdma table was moved
|
||||
|
||||
// Restore spc variables from the ram dump.
|
||||
SpcPlayer_CopyVariablesFromRam(g_zenv.player);
|
||||
// This is not stored in the snapshot
|
||||
g_zenv.player->timer_cycles = 0;
|
||||
|
||||
// Ensure emulator has the up-to-date state too
|
||||
ZeldaRestoreMusicAfterLoad_Locked(false);
|
||||
ZeldaApuUnlock();
|
||||
EmuSynchronizeWholeState();
|
||||
ZeldaResetApuQueue();
|
||||
ZeldaOpenMsuFile();
|
||||
}
|
||||
|
||||
static void SaveSnesState(SaveLoadFunc *func, void *ctx) {
|
||||
memcpy(g_zenv.ram + 0x1b00, g_zenv.ram + 0x1DBA0, 224 * 2); // hdma table was moved
|
||||
SpcPlayer_CopyVariablesToRam(g_zenv.player);
|
||||
ZeldaApuLock();
|
||||
ZeldaSaveMusicStateToRam_Locked();
|
||||
InternalSaveLoad(func, ctx);
|
||||
ZeldaApuUnlock();
|
||||
}
|
||||
|
||||
typedef struct StateRecorder {
|
||||
@@ -583,7 +499,6 @@ void StateRecorder_Load(StateRecorder *sr, FILE *f, bool replay_mode) {
|
||||
|
||||
sr->replay_next_cmd_at = 0;
|
||||
|
||||
bool is_reset = false;
|
||||
sr->replay_mode = replay_mode;
|
||||
if (replay_mode) {
|
||||
sr->frames_since_last = 0;
|
||||
@@ -598,7 +513,6 @@ void StateRecorder_Load(StateRecorder *sr, FILE *f, bool replay_mode) {
|
||||
assert(state.p == state.pend);
|
||||
} else {
|
||||
ZeldaReset(false);
|
||||
is_reset = true;
|
||||
}
|
||||
} else {
|
||||
// Resume replay from the saved position?
|
||||
@@ -825,7 +739,7 @@ bool ZeldaRunFrame(int inputs) {
|
||||
EmuSyncMemoryRegion(&g_ram[kRam_CrystalRotateCounter], 1);
|
||||
}
|
||||
|
||||
if (g_emu_runframe == NULL || enhanced_features0 != 0) {
|
||||
if (g_emu_runframe == NULL || enhanced_features0 != 0 || g_zenv.dialogue_flags) {
|
||||
// can't compare against real impl when running with extra features.
|
||||
ZeldaRunFrameInternal(inputs, run_what);
|
||||
} else {
|
||||
@@ -837,7 +751,28 @@ bool ZeldaRunFrame(int inputs) {
|
||||
return is_replay;
|
||||
}
|
||||
|
||||
|
||||
void ZeldaSetLanguage(const char *language) {
|
||||
static const uint8 kDefaultConf[3] = { 0, 0, 0 };
|
||||
MemBlk found = { kDefaultConf, 3 };
|
||||
if (language) {
|
||||
size_t n = strlen(language);
|
||||
for (int i = 0; ; i++) {
|
||||
MemBlk mb = kDialogueMap(i);
|
||||
if (mb.ptr == 0) {
|
||||
fprintf(stderr, "Unable to find language '%s'\n", language);
|
||||
break;
|
||||
}
|
||||
MemBlk name = FindIndexInMemblk(mb, 0);
|
||||
if (name.size == n && !memcmp(name.ptr, language, n)) {
|
||||
found = FindIndexInMemblk(mb, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_zenv.dialogue_blk = kDialogue(found.ptr[0]);
|
||||
g_zenv.dialogue_font_blk = kDialogueFont(found.ptr[1]);
|
||||
g_zenv.dialogue_flags = found.ptr[2];
|
||||
}
|
||||
|
||||
|
||||
static const char *const kReferenceSaves[] = {
|
||||
@@ -879,8 +814,6 @@ void SaveLoadSlot(int cmd, int which) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
typedef struct StateRecoderMultiPatch {
|
||||
uint32 count;
|
||||
uint32 addr;
|
||||
@@ -935,136 +868,6 @@ void PatchCommand(char c) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LoadSongBank(const uint8 *p) { // 808888
|
||||
SpcPlayer_Upload(g_zenv.player, p);
|
||||
}
|
||||
|
||||
bool msu_enabled;
|
||||
static FILE *msu_file;
|
||||
static uint32 msu_loop_start;
|
||||
static uint32 msu_buffer_size, msu_buffer_pos;
|
||||
static uint8 msu_buffer[65536];
|
||||
|
||||
static const uint8 kMsuTracksWithRepeat[48] = {
|
||||
1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,0,
|
||||
1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,
|
||||
1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
};
|
||||
|
||||
bool ZeldaIsMusicPlaying() {
|
||||
if (msu_track) {
|
||||
return msu_file != NULL;
|
||||
} else {
|
||||
return g_zenv.player->port_to_snes[0] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ZeldaOpenMsuFile() {
|
||||
if (msu_file) fclose(msu_file), msu_file = NULL;
|
||||
if (msu_track == 0)
|
||||
return;
|
||||
char buf[40], hdr[8];
|
||||
sprintf(buf, "msu/alttp_msu-%d.pcm", msu_track);
|
||||
msu_file = fopen(buf, "rb");
|
||||
if (msu_file == NULL || fread(hdr, 1, 8, msu_file) != 8 || *(uint32 *)(hdr + 0) != (('1' << 24) | ('U' << 16) | ('S' << 8) | 'M')) {
|
||||
if (msu_file != NULL) fclose(msu_file), msu_file = NULL;
|
||||
zelda_apu_write(APUI00, msu_track);
|
||||
msu_track = 0;
|
||||
return;
|
||||
}
|
||||
if (msu_curr_sample != 0)
|
||||
fseek(msu_file, msu_curr_sample * 4 + 8, SEEK_SET);
|
||||
printf("Loading MSU PCM file: %s\n", buf);
|
||||
msu_loop_start = *(uint32 *)(hdr + 4);
|
||||
msu_buffer_size = msu_buffer_pos = 0;
|
||||
}
|
||||
|
||||
void ZeldaPlayMsuAudioTrack() {
|
||||
if (!msu_enabled) normal_playback: {
|
||||
msu_track = 0;
|
||||
zelda_apu_write(APUI00, music_control);
|
||||
return;
|
||||
}
|
||||
if ((music_control & 0xf0) != 0xf0) {
|
||||
msu_track = music_control;
|
||||
msu_volume = 255;
|
||||
msu_curr_sample = 0;
|
||||
ZeldaOpenMsuFile();
|
||||
} else if (msu_file == NULL) {
|
||||
goto normal_playback;
|
||||
}
|
||||
zelda_apu_write(APUI00, 0xf1); // pause spc player
|
||||
}
|
||||
|
||||
void MixinMsuAudioData(int16 *audio_buffer, int audio_samples) {
|
||||
if (msu_file == NULL)
|
||||
return; // msu inactive
|
||||
// handle volume fade
|
||||
if (last_music_control >= 0xf1) {
|
||||
if (last_music_control == 0xf1)
|
||||
msu_volume = IntMax(msu_volume - 3, 0);
|
||||
else if (last_music_control == 0xf2)
|
||||
msu_volume = IntMax(msu_volume - 3, 0x40);
|
||||
else if (last_music_control == 0xf3)
|
||||
msu_volume = IntMin(msu_volume + 3, 0xff);
|
||||
}
|
||||
if (msu_volume == 0)
|
||||
return;
|
||||
int last_audio_samples = 0;
|
||||
for (;;) {
|
||||
if (msu_buffer_pos >= msu_buffer_size) {
|
||||
msu_buffer_size = (int)fread(msu_buffer, 4, sizeof(msu_buffer) / 4, msu_file);
|
||||
msu_buffer_pos = 0;
|
||||
}
|
||||
int nr = IntMin(audio_samples, msu_buffer_size - msu_buffer_pos);
|
||||
uint8 *buf = msu_buffer + msu_buffer_pos * 4;
|
||||
msu_buffer_pos += nr;
|
||||
msu_curr_sample += nr;
|
||||
int volume = msu_volume + 1;
|
||||
if (volume == 256) {
|
||||
for (int i = 0; i < nr; i++) {
|
||||
audio_buffer[i * 2 + 0] += ((int16 *)buf)[i * 2 + 0];
|
||||
audio_buffer[i * 2 + 1] += ((int16 *)buf)[i * 2 + 1];
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < nr; i++) {
|
||||
audio_buffer[i * 2 + 0] += ((int16 *)buf)[i * 2 + 0] * volume >> 8;
|
||||
audio_buffer[i * 2 + 1] += ((int16 *)buf)[i * 2 + 1] * volume >> 8;
|
||||
}
|
||||
}
|
||||
audio_samples -= nr, audio_buffer += nr * 2;
|
||||
if (audio_samples == 0)
|
||||
break;
|
||||
if (nr != 0)
|
||||
continue;
|
||||
|
||||
if (last_audio_samples == audio_samples) { // error?
|
||||
zelda_apu_write(APUI00, msu_track);
|
||||
fclose(msu_file), msu_file = NULL;
|
||||
return;
|
||||
}
|
||||
last_audio_samples = audio_samples;
|
||||
|
||||
if (!kMsuTracksWithRepeat[msu_track]) {
|
||||
fclose(msu_file), msu_file = NULL;
|
||||
return;
|
||||
}
|
||||
fseek(msu_file, msu_loop_start * 4 + 8, SEEK_SET);
|
||||
msu_curr_sample = msu_loop_start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels) {
|
||||
ZeldaPopApuState();
|
||||
SpcPlayer_GenerateSamples(g_zenv.player);
|
||||
dsp_getSamples(g_zenv.player->dsp, audio_buffer, samples, channels);
|
||||
if (channels == 2)
|
||||
MixinMsuAudioData(audio_buffer, samples);
|
||||
}
|
||||
|
||||
|
||||
void ZeldaReadSram() {
|
||||
FILE *f = fopen("saves/sram.dat", "rb");
|
||||
if (f) {
|
||||
@@ -1084,4 +887,4 @@ void ZeldaWriteSram() {
|
||||
} else {
|
||||
fprintf(stderr, "Unable to write saves/sram.dat\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@ typedef struct ZeldaEnv {
|
||||
struct Ppu *ppu;
|
||||
struct SpcPlayer *player;
|
||||
struct Dma *dma;
|
||||
|
||||
MemBlk dialogue_blk;
|
||||
MemBlk dialogue_font_blk;
|
||||
uint8 dialogue_flags;
|
||||
} ZeldaEnv;
|
||||
extern ZeldaEnv g_zenv;
|
||||
extern int frame_ctr_dbg;
|
||||
@@ -42,20 +46,17 @@ void HdmaSetup(uint32 addr6, uint32 addr7, uint8 transfer_unit, uint8 reg6, uint
|
||||
|
||||
void ZeldaInitialize();
|
||||
void ZeldaReset(bool preserve_sram);
|
||||
bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags);
|
||||
void ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags);
|
||||
void ZeldaRunFrameInternal(uint16 input, int run_what);
|
||||
bool ZeldaRunFrame(int input_state);
|
||||
void LoadSongBank(const uint8 *p);
|
||||
|
||||
void ZeldaApuLock();
|
||||
void ZeldaApuUnlock();
|
||||
bool ZeldaIsPlayingMusicTrack(uint8 track);
|
||||
uint8 ZeldaGetEntranceMusicTrack(int track);
|
||||
void ZeldaSetLanguage(const char *language);
|
||||
void PatchCommand(char cmd);
|
||||
|
||||
// Things for msu
|
||||
void ZeldaPlayMsuAudioTrack();
|
||||
void MixinMsuAudioData(int16 *audio_buffer, int audio_samples);
|
||||
void ZeldaOpenMsuFile();
|
||||
bool ZeldaIsMusicPlaying();
|
||||
|
||||
|
||||
// Things for state management
|
||||
|
||||
enum {
|
||||
@@ -68,12 +69,29 @@ void SaveLoadSlot(int cmd, int which);
|
||||
void ZeldaWriteSram();
|
||||
void ZeldaReadSram();
|
||||
|
||||
void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels);
|
||||
void ZeldaDiscardUnusedAudioFrames();
|
||||
|
||||
typedef void ZeldaRunFrameFunc(uint16 input, int run_what);
|
||||
typedef void ZeldaSyncAllFunc();
|
||||
|
||||
void ZeldaSetupEmuCallbacks(uint8 *emu_ram, ZeldaRunFrameFunc *func, ZeldaSyncAllFunc *sync_all);
|
||||
|
||||
// Button definitions, zelda splits them in separate 8-bit high/low
|
||||
enum {
|
||||
kJoypadL_A = 0x80,
|
||||
kJoypadL_X = 0x40,
|
||||
kJoypadL_L = 0x20,
|
||||
kJoypadL_R = 0x10,
|
||||
|
||||
kJoypadH_B = 0x80,
|
||||
kJoypadH_Y = 0x40,
|
||||
kJoypadH_Select = 0x20,
|
||||
kJoypadH_Start = 0x10,
|
||||
|
||||
kJoypadH_Up = 0x8,
|
||||
kJoypadH_Down = 0x4,
|
||||
kJoypadH_Left = 0x2,
|
||||
kJoypadH_Right = 0x1,
|
||||
|
||||
kJoypadH_AnyDir = 0xf,
|
||||
};
|
||||
|
||||
#endif // ZELDA3_ZELDA_RTL_H_
|
||||
@@ -1,255 +0,0 @@
|
||||
|
||||
kTextAlphabet = [
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
|
||||
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
|
||||
# codes 0x3E and up
|
||||
"!", "?", "-", ".", ",",
|
||||
|
||||
# codes 0x43 and up
|
||||
"[...]", ">", "(", ")",
|
||||
|
||||
# codes 0x47 and up
|
||||
"[Ankh]", "[Waves]", "[Snake]", "[LinkL]", "[LinkR]",
|
||||
"\"", "[Up]", "[Down]", "[Left]", "[Right]", "'",
|
||||
|
||||
# codes 0x52 and up
|
||||
"[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]",
|
||||
"[3HeartR]", "[4HeartL]", "[4HeartR]",
|
||||
|
||||
" ", "<", "[A]", "[B]", "[X]", "[Y]",
|
||||
]
|
||||
|
||||
kText_CommandLengths = [1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, ]
|
||||
kText_CommandNames = [
|
||||
"NextPic",
|
||||
"Choose",
|
||||
"Item",
|
||||
"Name",
|
||||
"Window",
|
||||
"Number",
|
||||
"Position",
|
||||
"ScrollSpd",
|
||||
"Selchg",
|
||||
"Crash",
|
||||
"Choose3",
|
||||
"Choose2",
|
||||
"Scroll",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"Color",
|
||||
"Wait",
|
||||
"Sound",
|
||||
"Speed",
|
||||
"Mark",
|
||||
"Mark2",
|
||||
"Clear",
|
||||
"Waitkey",
|
||||
"EndMessage"
|
||||
]
|
||||
|
||||
kTextDictionary = [ 0x59, 0x59, 0x59, 0x59,
|
||||
0x59, 0x59, 0x59,
|
||||
0x59, 0x59,
|
||||
0x51, 0x2c, 0x59,
|
||||
0x1a, 0x27, 0x1d, 0x59,
|
||||
0x1a, 0x2b, 0x1e, 0x59,
|
||||
0x1a, 0x25, 0x25, 0x59,
|
||||
0x1a, 0x22, 0x27,
|
||||
0x1a, 0x27, 0x1d,
|
||||
0x1a, 0x2d, 0x59,
|
||||
0x1a, 0x2c, 0x2d,
|
||||
0x1a, 0x27,
|
||||
0x1a, 0x2d,
|
||||
0x1b, 0x25, 0x1e,
|
||||
0x1b, 0x1a,
|
||||
0x1b, 0x1e,
|
||||
0x1b, 0x28,
|
||||
0x1c, 0x1a, 0x27, 0x59,
|
||||
0x1c, 0x21, 0x1e,
|
||||
0x1c, 0x28, 0x26,
|
||||
0x1c, 0x24,
|
||||
0x1d, 0x1e, 0x2c,
|
||||
0x1d, 0x22,
|
||||
0x1d, 0x28,
|
||||
0x1e, 0x27, 0x59,
|
||||
0x1e, 0x2b, 0x59,
|
||||
0x1e, 0x1a, 0x2b,
|
||||
0x1e, 0x27, 0x2d,
|
||||
0x1e, 0x1d, 0x59,
|
||||
0x1e, 0x27,
|
||||
0x1e, 0x2b,
|
||||
0x1e, 0x2f,
|
||||
0x1f, 0x28, 0x2b,
|
||||
0x1f, 0x2b, 0x28,
|
||||
0x20, 0x22, 0x2f, 0x1e, 0x59,
|
||||
0x20, 0x1e, 0x2d,
|
||||
0x20, 0x28,
|
||||
0x21, 0x1a, 0x2f, 0x1e,
|
||||
0x21, 0x1a, 0x2c,
|
||||
0x21, 0x1e, 0x2b,
|
||||
0x21, 0x22,
|
||||
0x21, 0x1a,
|
||||
0x22, 0x20, 0x21, 0x2d, 0x59,
|
||||
0x22, 0x27, 0x20, 0x59,
|
||||
0x22, 0x27,
|
||||
0x22, 0x2c,
|
||||
0x22, 0x2d,
|
||||
0x23, 0x2e, 0x2c, 0x2d,
|
||||
0x24, 0x27, 0x28, 0x30,
|
||||
0x25, 0x32, 0x59,
|
||||
0x25, 0x1a,
|
||||
0x25, 0x28,
|
||||
0x26, 0x1a, 0x27,
|
||||
0x26, 0x1a,
|
||||
0x26, 0x1e,
|
||||
0x26, 0x2e,
|
||||
0x27, 0x51, 0x2d, 0x59,
|
||||
0x27, 0x28, 0x27,
|
||||
0x27, 0x28, 0x2d,
|
||||
0x28, 0x29, 0x1e, 0x27,
|
||||
0x28, 0x2e, 0x27, 0x1d,
|
||||
0x28, 0x2e, 0x2d, 0x59,
|
||||
0x28, 0x1f,
|
||||
0x28, 0x27,
|
||||
0x28, 0x2b,
|
||||
0x29, 0x1e, 0x2b,
|
||||
0x29, 0x25, 0x1e,
|
||||
0x29, 0x28, 0x30,
|
||||
0x29, 0x2b, 0x28,
|
||||
0x2b, 0x1e, 0x59,
|
||||
0x2b, 0x1e,
|
||||
0x2c, 0x28, 0x26, 0x1e,
|
||||
0x2c, 0x1e,
|
||||
0x2c, 0x21,
|
||||
0x2c, 0x28,
|
||||
0x2c, 0x2d,
|
||||
0x2d, 0x1e, 0x2b, 0x59,
|
||||
0x2d, 0x21, 0x22, 0x27,
|
||||
0x2d, 0x1e, 0x2b,
|
||||
0x2d, 0x21, 0x1a,
|
||||
0x2d, 0x21, 0x1e,
|
||||
0x2d, 0x21, 0x22,
|
||||
0x2d, 0x28,
|
||||
0x2d, 0x2b,
|
||||
0x2e, 0x29,
|
||||
0x2f, 0x1e, 0x2b,
|
||||
0x30, 0x22, 0x2d, 0x21,
|
||||
0x30, 0x1a,
|
||||
0x30, 0x1e,
|
||||
0x30, 0x21,
|
||||
0x30, 0x22,
|
||||
0x32, 0x28, 0x2e,
|
||||
0x7, 0x1e, 0x2b,
|
||||
0x13, 0x21, 0x1a,
|
||||
0x13, 0x21, 0x1e,
|
||||
0x13, 0x21, 0x22,
|
||||
0x18, 0x28, 0x2e,
|
||||
]
|
||||
|
||||
kTextDictionary_Idx = [
|
||||
0, 4, 7, 9, 12, 16, 20, 24, 27, 30, 33, 36, 38, 40, 43, 45, 47, 49, 53, 56, 59, 61, 64, 66, 68, 71, 74, 77, 80, 83, 85, 87, 89, 92, 95, 100, 103, 105, 109, 112, 115, 117, 119, 124, 128, 130, 132, 134, 138, 142, 145, 147, 149, 152, 154, 156, 158, 162, 165, 168, 172, 176, 180, 182, 184, 186, 189, 192, 195, 198, 201, 203, 207, 209, 211, 213, 215, 219, 223, 226, 229, 232, 235, 237, 239, 241, 244, 248, 250, 252, 254, 256, 259, 262, 265, 268, 271, 274
|
||||
]
|
||||
|
||||
def make_dict():
|
||||
r, rinv = {}, {}
|
||||
for i in range(len(kTextDictionary_Idx) - 1):
|
||||
ln = kTextDictionary_Idx[i + 1] - kTextDictionary_Idx[i]
|
||||
idx = kTextDictionary_Idx[i]
|
||||
s = "".join(kTextAlphabet[kTextDictionary[idx+i]] for i in range(ln))
|
||||
r[i] = s
|
||||
rinv[s] = i
|
||||
return r, rinv
|
||||
|
||||
kTextDictionary_Ascii, kTextDictionary_AsciiBack = make_dict()
|
||||
|
||||
def decode_strings(get_byte):
|
||||
p = 0x9c8000
|
||||
result = []
|
||||
while True:
|
||||
org_p = p
|
||||
#print('0x%x' % p)
|
||||
s = ''
|
||||
srcdata = []
|
||||
while True:
|
||||
c = get_byte(p)
|
||||
srcdata.append(c)
|
||||
l = kText_CommandLengths[c - 0x67] if c >= 0x67 and c < 0x80 else 1
|
||||
p += l
|
||||
if c == 0x7f:
|
||||
break
|
||||
if c < 0x67:
|
||||
s += kTextAlphabet[c]
|
||||
elif c < 0x80:
|
||||
if l == 2:
|
||||
srcdata.append(get_byte(p-1))
|
||||
s += '[%s %.2d]' % (kText_CommandNames[c - 0x67], get_byte(p-1))
|
||||
else:
|
||||
s += '[%s]' % kText_CommandNames[c - 0x67]
|
||||
elif c == 0x80:
|
||||
p = 0x8edf40
|
||||
s = None
|
||||
break
|
||||
elif c > 0x80 and c < 0x88:
|
||||
assert 0
|
||||
elif c == 0xff:
|
||||
return result
|
||||
else:
|
||||
s += kTextDictionary_Ascii[c - 0x88]
|
||||
if s != None:
|
||||
result.append((s, srcdata))
|
||||
|
||||
def print_strings(f, get_byte):
|
||||
for i, s in enumerate(decode_strings(get_byte)):
|
||||
print('%s: %s' % (i + 1, s[0]), file = f)
|
||||
|
||||
def find_string_char_at(s, i):
|
||||
a = s[i:]
|
||||
|
||||
for k, v in kTextDictionary_AsciiBack.items():
|
||||
if a.startswith(k):
|
||||
return [v + 0x88], len(k)
|
||||
|
||||
for i, s in enumerate(kTextAlphabet):
|
||||
if a.startswith(s):
|
||||
return [i], len(s)
|
||||
|
||||
if a.startswith('['):
|
||||
cmd = a[1:a.index(']')]
|
||||
if cmd in kText_CommandNames:
|
||||
i = kText_CommandNames.index(cmd)
|
||||
return [i + 0x67], len(cmd) + 2
|
||||
|
||||
for i, s in enumerate(kText_CommandNames):
|
||||
if kText_CommandLengths[i] == 2 and cmd.startswith(s):
|
||||
e = cmd[len(s):].strip()
|
||||
return [i + 0x67, int(e)], len(cmd) + 2
|
||||
|
||||
print('substr %s not found' % a)
|
||||
assert 0
|
||||
|
||||
def compress_string(s):
|
||||
# find the greedy best match
|
||||
i = 0
|
||||
r = []
|
||||
while i < len(s):
|
||||
what, num = find_string_char_at(s, i)
|
||||
r.extend(what)
|
||||
i += num
|
||||
r.append(0x7f)
|
||||
return r
|
||||
|
||||
def verify(get_byte):
|
||||
for i, (decoded, original) in enumerate(decode_strings(get_byte)):
|
||||
c = compress_string(decoded)
|
||||
if c != original:
|
||||
print('String %s not match: %s, %s' % (decoded, c, original))
|
||||
break
|
||||
else:
|
||||
pass
|
||||
3
third_party/.gitignore
vendored
Normal file
3
third_party/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/tcc/
|
||||
/SDL2-2.*/
|
||||
/gl_core/*.o
|
||||
3189
third_party/gl_core/gl_core_3_1.c
vendored
Normal file
3189
third_party/gl_core/gl_core_3_1.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3703
third_party/gl_core/gl_core_3_1.h
vendored
Normal file
3703
third_party/gl_core/gl_core_3_1.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
third_party/opus-1.3.1-stripped/.gitignore
vendored
Normal file
1
third_party/opus-1.3.1-stripped/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/opus_decoder_amalgam.o
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user