├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bugs.yml │ ├── config.yml │ └── feature-request.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── BUILDING.md ├── LICENSE.txt ├── Makefile ├── README.md ├── assets ├── .gitignore ├── animtiles_decode.py ├── consts.py ├── decode_common.py ├── enemy_instr_decode.py ├── eproj_decode.py ├── names.txt ├── palfx_decode.py ├── plm_decode.py ├── projectile_decode.py ├── restool.py └── text_lexer.py ├── other ├── annotate_funcs.py ├── strip_some.py └── useful_regexps.txt ├── run_with_tcc.bat ├── sm.ini ├── sm.sln ├── src ├── .gitignore ├── config.c ├── config.h ├── enemy_types.h ├── features.h ├── funcs.h ├── glsl_shader.c ├── glsl_shader.h ├── ida_types.h ├── main.c ├── move_defines.py ├── opengl.c ├── packages.config ├── platform │ ├── switch │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── icon.jpg │ │ ├── sm.ini │ │ └── src │ │ │ ├── switch_impl.c │ │ │ └── switch_impl.h │ └── win32 │ │ ├── resource.h │ │ ├── sm.rc │ │ ├── volume_control.c │ │ └── volume_control.h ├── sm.vcxproj ├── sm.vcxproj.filters ├── sm_80.c ├── sm_81.c ├── sm_82.c ├── sm_84.c ├── sm_85.c ├── sm_86.c ├── sm_87.c ├── sm_88.c ├── sm_89.c ├── sm_8b.c ├── sm_8d.c ├── sm_8f.c ├── sm_90.c ├── sm_91.c ├── sm_92.c ├── sm_93.c ├── sm_94.c ├── sm_9b.c ├── sm_a0.c ├── sm_a2.c ├── sm_a3.c ├── sm_a4.c ├── sm_a5.c ├── sm_a6.c ├── sm_a7.c ├── sm_a8.c ├── sm_a9.c ├── sm_aa.c ├── sm_ad.c ├── sm_b2.c ├── sm_b3.c ├── sm_b4.c ├── sm_cpu_infra.c ├── sm_cpu_infra.h ├── sm_rtl.c ├── sm_rtl.h ├── snes │ ├── apu.c │ ├── apu.h │ ├── cart.c │ ├── cart.h │ ├── cpu.c │ ├── cpu.h │ ├── dma.c │ ├── dma.h │ ├── dsp.c │ ├── dsp.h │ ├── dsp_regs.h │ ├── input.c │ ├── input.h │ ├── ppu.c │ ├── ppu.h │ ├── saveload.h │ ├── snes.c │ ├── snes.h │ ├── snes_other.c │ ├── spc.c │ └── spc.h ├── spc_player.c ├── spc_player.h ├── spc_variables.h ├── tracing.c ├── tracing.h ├── types.h ├── util.c ├── util.h ├── variables.h └── variables_extra.h └── third_party ├── .gitignore ├── gl_core ├── gl_core_3_1.c └── gl_core_3_1.h └── stb └── stb_image.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.h text 8 | *.cpp text 9 | 10 | # Declare files that will always have CRLF line endings on checkout. 11 | *.sln text eol=crlf 12 | 13 | # Denote all files that are truly binary and should not be modified. 14 | *.png binary 15 | *.jpg binary 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bugs.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report bugs here. 3 | labels: [bug] 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: "Describe your bug here. And how to reproduce it. (If possible, please send the save that the bug occurs.)" 9 | validations: 10 | required: true 11 | 12 | 13 | - type: dropdown 14 | id: btarget 15 | attributes: 16 | label: "What is your build target?" 17 | options: 18 | - "Windows" 19 | - "Linux" 20 | - "Mac" 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: [] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Ask for a feature or just give ideas! 3 | labels: [enhancement] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: What feature do you want to get added? And how it will work? 8 | validations: 9 | required: true 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | ### Will this Pull Request break anything? 5 | 6 | 7 | ### Suggested Testing Steps 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.dSYM 3 | /.vs/ 4 | /packages/ 5 | /saves/ 6 | *.o 7 | /sm 8 | /build/ 9 | /sm.smc 10 | SDL2.dll 11 | /sm.exe 12 | /glsl-shaders/ -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | * A Super Metroid rom (Make sure to rename it to `sm.smc`) 3 | * libsdl2-dev 4 | * Super Metroid repo `git clone --recursive https://github.com/snesrev/sm` 5 | 6 | For Linux/MacOS you must install these for your desired OS: 7 | * Ubuntu/Debian: `sudo apt install libsdl2-dev` 8 | * Fedora Linux: `sudo dnf in sdl2-devel` 9 | * Arch Linux: `sudo pacman -S sdl2` 10 | * macOS: `brew install sdl2` 11 | 12 | # Windows 13 | 14 | ## Building with MSYS2 15 | 16 | Dependencies and requirements: 17 | 18 | * The `libsdl2-dev` library 19 | * [MSYS2](https://www.msys2.org) 20 | 21 | Note: *Make sure you're using MINGW64 and you're in `sm` folder in the terminal.* 22 | 23 | 1. Install MSYS2 on your machine. 24 | 2. Place the copy of your rom in the main directory. 25 | 3. Install the necessary dependencies by inputting this command in the terminal. 26 | 27 | ```sh 28 | pacman -S mingw-w64-x86_64-SDL2 && pacman -S make && pacman -S mingw-w64-x86_64-gcc 29 | ``` 30 | 4. Type `sdl2-config --cflags`, it should output: 31 | ```sh 32 | -IC:/msys64/mingw64/include/SDL2 -Dmain=SDL_main 33 | ``` 34 | 5. After that type `sdl2-config --libs`, should output: 35 | ```sh 36 | -LC:/msys64/mingw64/lib -lmingw32 -mwindows -lSDL2main -lSDL2 37 | ``` 38 | 39 | After you've done installing everything, cd to `sm` folder. Type `make` 40 | In order to speed up the compilation, type `make -j16` 41 | 42 | ## Building with Visual Studio 43 | 44 | Dependencies and requirements: 45 | * The `libsdl2-dev` library, which is automatically installed with NuGet. 46 | * [Visual Studio Community 2022](https://visualstudio.microsoft.com) 47 | 48 | Download VS installer. On installer prompt, make sure you're on "Workloads" and check `Desktop Development with C++` this will install the necessary deps for compilation. 49 | 50 | 1. Open `sm.sln` solution. 51 | 2. Change the build target from `Debug` to `Release` 52 | 3. Build the solution. 53 | 54 | ## Building with Tiny C Compiler 55 | 56 | Dependencies and requirements: 57 | * You'll need [TCC](https://github.com/FitzRoyX/tinycc/releases/download/tcc_20221020/tcc_20221020.zip) and [SDL2](https://github.com/libsdl-org/SDL/releases/download/release-2.24.1/SDL2-devel-2.24.1-VC.zip) in order to compile using TCC. 58 | 59 | 1. Unzip both TCC and SDL and place them in `third_party` folder. 60 | 2. Double click `run_with_tcc.bat` 61 | 3. Wait for it to compile and the game will automatically boot-up. 62 | 63 | # Linux/MacOS 64 | 65 | CD to your SM root folder and open the terminal and type: 66 | ```sh 67 | make 68 | ``` 69 | 70 | For more advanced usage: 71 | ```sh 72 | make -j$(nproc) # run on all core 73 | make clean all # clear gen+obj and rebuild 74 | CC=clang make # specify compiler 75 | ``` 76 | 77 | # Nintendo Switch 78 | 79 | Dependencies and requirements: 80 | 81 | * The `switch-sdl2` library 82 | * [DevKitPro](https://github.com/devkitPro/installer) 83 | * [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere) 84 | 85 | 1. Make sure you've installed Atmosphere on your Switch. 86 | 2. Please download the DevKitPro version of MSYS2 through their installer, as the default MSYS2 causes issues with windows compiling. 87 | 3. Now that you've installed DevKitPro, open up the location you've installed DevKitPro to, then find `mingw64.exe` inside `msys2` located in `devkitPro` folder. 88 | 4. Type `pacman -S git switch-dev switch-sdl2 switch-tools` in the terminal to install the `switch-sdl2` library. 89 | 5. CD to `switch` folder by typing `cd src/platfrom/switch` in the terminal on the `sm` root folder. 90 | 6. type `make` to compile the Switch Port. 91 | 7. Transfer the `.ini`, `nro`, `ncap` and your rom file to the Switch. 92 | 93 | **OPTIONAL STEP** 94 | 95 | ```sh 96 | make -j$(nproc) # To build using all cores 97 | ``` 98 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 snesrev 4 | Copyright (c) 2021 elzo_d 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | 25 | This is the license for Opus: 26 | 27 | /* Copyright (c) 2007-2008 CSIRO 28 | Copyright (c) 2007-2009 Xiph.Org Foundation 29 | Copyright (c) 2008-2009 Gregory Maxwell 30 | Written by Jean-Marc Valin and Gregory Maxwell */ 31 | /* 32 | Redistribution and use in source and binary forms, with or without 33 | modification, are permitted provided that the following conditions 34 | are met: 35 | 36 | - Redistributions of source code must retain the above copyright 37 | notice, this list of conditions and the following disclaimer. 38 | 39 | - Redistributions in binary form must reproduce the above copyright 40 | notice, this list of conditions and the following disclaimer in the 41 | documentation and/or other materials provided with the distribution. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 47 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 48 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 49 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 50 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 51 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 52 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 53 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | */ 55 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET_EXEC:=sm 2 | 3 | SRCS:=$(wildcard src/*.c src/snes/*.c) third_party/gl_core/gl_core_3_1.c 4 | OBJS:=$(SRCS:%.c=%.o) 5 | 6 | PYTHON:=/usr/bin/env python3 7 | CFLAGS:=$(if $(CFLAGS),$(CFLAGS),-O2 -fno-strict-aliasing -Werror ) 8 | CFLAGS:=${CFLAGS} $(shell sdl2-config --cflags) -DSYSTEM_VOLUME_MIXER_AVAILABLE=0 -I. 9 | 10 | ifeq (${OS},Windows_NT) 11 | WINDRES:=windres 12 | # RES:=sm.res 13 | SDLFLAGS:=-Wl,-Bstatic $(shell sdl2-config --static-libs) 14 | else 15 | SDLFLAGS:=$(shell sdl2-config --libs) -lm 16 | endif 17 | 18 | .PHONY: all clean clean_obj 19 | 20 | all: $(TARGET_EXEC) 21 | $(TARGET_EXEC): $(OBJS) $(RES) 22 | $(CC) $^ -o $@ $(LDFLAGS) $(SDLFLAGS) 23 | 24 | %.o : %.c 25 | $(CC) -c $(CFLAGS) $< -o $@ 26 | 27 | #$(RES): src/platform/win32/sm.rc 28 | # @echo "Generating Windows resources" 29 | # @$(WINDRES) $< -O coff -o $@ 30 | 31 | clean: clean_obj 32 | clean_obj: 33 | @$(RM) $(OBJS) $(TARGET_EXEC) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sm 2 | 3 | Our discord server is: https://discord.gg/AJJbJAzNNJ 4 | 5 | Early version. It has bugs and the code is messy. 6 | 7 | For building instructions, see: https://github.com/snesrev/sm/blob/main/BUILDING.md 8 | 9 | Put sm.smc (sha1 hash da957f0d63d14cb441d215462904c4fa8519c613) in the root folder. When running, it will run both versions and compare frame by frame. If it detects a mismatch, it saves a snapshot in saves/ and displays a counter on screen counting down from 300. 10 | 11 | -------------------------------------------------------------------------------- /assets/.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__/ 2 | /defs/* 3 | /out.bin 4 | /out_is_set.bin 5 | -------------------------------------------------------------------------------- /assets/animtiles_decode.py: -------------------------------------------------------------------------------- 1 | from decode_common import * 2 | 3 | BANK = 0x87 4 | 5 | kInstructionSet = { 6 | 0x8780b2: ('Delete', ''), 7 | 0x8780b7: ('Goto', 'G'), 8 | 0x87813f: ('GotoIfEventHappened', 'HG'), 9 | 0x878150: ('SetEventHappened', 'H'), 10 | 0x8781ba: ('WaitUntilAreaBossDead_DoubleRet', ''), 11 | 0x878303: ('GotoIfBossBitSetInArea', 'BBG'), 12 | 0x878320: ('SpawnTourianStatueEyeGlow', 'H'), 13 | 0x87832f: ('SpawnTourianStatueSoul', 'H'), 14 | 0x87833e: ('GotoIfTourianStatueBusy', 'G'), 15 | 0x878349: ('TourianStatueSetState', 'H'), 16 | 0x878352: ('TourianStatueClearState', 'H'), 17 | 0x87835b: ('Clear3PaletteColors', 'H'), 18 | 0x878372: ('SpawnPalfxObj', 'H'), 19 | 0x87837f: ('Write8PaletteColors', 'H'), 20 | } 21 | 22 | kCommandByName = {v[0] : (k, v[1]) for k, v in kInstructionSet.items()} 23 | 24 | class AnimtilesParser(InstrParserCommon): 25 | instruction_set = kInstructionSet 26 | instruction_set_term = {0x8780b7, 0x8780b2} 27 | TAG = 'animtiles' 28 | 29 | def __init__(self): 30 | super().__init__() 31 | self.current_array_size = None 32 | 33 | def handle_draw_command(self, ins, ea): 34 | drawp = get_word(ea + 2) | (BANK << 16) 35 | assert self.current_array_size != None 36 | self.blobs[drawp] = self.current_array_size 37 | self.print_line(ea, f' {ins} ! {get_ea_name(drawp)}') 38 | return 4 39 | 40 | def visit(self, ea, cur_size = None): 41 | if cur_size == None: 42 | super().visit(ea) 43 | return 44 | self.current_array_size = cur_size 45 | super().visit(ea) 46 | self.process_queue(final = False) 47 | 48 | def process_queue_entry(self, ea): 49 | assert ea & 0x8000 50 | while ea not in self.visited: 51 | self.visited.add(ea) 52 | ins = get_word(ea) 53 | if ins & 0x8000: 54 | ea_org = ea 55 | ins = (BANK << 16) | ins 56 | if ins not in self.instruction_set: 57 | raise Exception(f'Ins {ins:X} not in iset at {ea:X}') 58 | name, operands = self.instruction_set[ins] 59 | ea += 2 60 | r = [] 61 | for op in operands: 62 | if op == 'B': 63 | r.append('%d' % get_byte(ea)) 64 | ea += 1 65 | elif op == 'H': 66 | r.append('%d' % get_word(ea)) 67 | ea += 2 68 | elif op == 'G': 69 | addr = (BANK << 16) | get_word(ea) 70 | r.append(get_ea_name(addr, short_label=True)) 71 | self.visit(addr) 72 | ea += 2 73 | else: 74 | assert 0 75 | self.print_line(ea_org, f' {name}({", ".join(r)})') 76 | if ins in self.instruction_set_term: 77 | self.print_line(ea_org + 1, '') 78 | break 79 | else: 80 | ea += self.handle_draw_command(ins, ea) 81 | -------------------------------------------------------------------------------- /assets/decode_common.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | def load_names(): 4 | xs = [a.split(' ') for a in open('names.txt', 'r').read().splitlines()] 5 | return {b : int(a, 16) for a, b in xs} 6 | 7 | name2ea = load_names() 8 | name2ea = {k.replace('?', '') : v for k, v in name2ea.items()} 9 | ea2name = {v:k for k,v in name2ea.items()} 10 | 11 | g_marked_addresses = set() 12 | 13 | def mark_address(ea): 14 | g_marked_addresses.add(ea) 15 | return ea 16 | 17 | def get_ea_name(v, short_label = False, unknown_prefix = 'unk'): 18 | if v == None: return None 19 | if v == 0: return 'null' 20 | r = ea2name.get(v) 21 | if r == None: 22 | if short_label and v not in g_marked_addresses: 23 | r = 'lbl_%X' % (v) 24 | else: 25 | r = '%s_%X' % (unknown_prefix, v) 26 | return r 27 | 28 | class Rom: 29 | def __init__(self): 30 | self.data = open('../sm.smc', 'rb').read() 31 | 32 | def map(self, addr): 33 | assert addr & 0x8000 34 | return (((addr >> 16) << 15) | (addr & 0x7fff)) & 0x3fffff 35 | 36 | def get_byte(self, addr): 37 | return self.data[self.map(addr)] 38 | 39 | def get_word(self, addr): 40 | addr = self.map(addr) 41 | return self.data[addr] + self.data[addr + 1] * 256 42 | 43 | def get_long(self, addr): 44 | addr = self.map(addr) 45 | return self.data[addr] + self.data[addr + 1] * 256 + self.data[addr + 2] * 65536 46 | 47 | def get_bytes(self, addr, n): 48 | addr = self.map(addr) 49 | return self.data[addr:addr+n] 50 | 51 | ROM = Rom() 52 | get_byte = ROM.get_byte 53 | get_word = ROM.get_word 54 | 55 | 56 | def translate_old_layout(): 57 | for k, v in kInstructionSet.items(): 58 | k = k | 0x8D0000 59 | t = ea2name[k] 60 | name = t.replace('PalInstr_', '') 61 | args, _ = v 62 | tostr = {1 : 'B', 2 : 'H', 3 : 'L'} 63 | args = ''.join(tostr[a] for a in args) 64 | if name.startswith('Goto'): 65 | args = args[:-1] + 'G' 66 | print(" 0x%x: ('%s', '%s')," % (k, name, args)) 67 | 68 | def sex8(v): 69 | return v if v < 0x80 else v - 0x100 70 | def sex16(v): 71 | return v if v < 0x8000 else v - 0x10000 72 | 73 | class InstrParserCommon: 74 | forbidden_addrs = set() 75 | def __init__(self): 76 | self.visited = set() 77 | self.missing_isets = set() 78 | self.queue = [] 79 | self.queue_pos = 0 80 | self.label = set() 81 | self.lines = [] 82 | self.blobs = {} 83 | 84 | def print_line(self, ea, line): 85 | self.lines.append((ea, line)) 86 | 87 | def visit(self, ea): 88 | assert ea & 0x8000 89 | if ea in self.forbidden_addrs: 90 | return 91 | self.label.add(ea) 92 | if ea not in self.visited: 93 | self.queue.append(ea) 94 | 95 | def process_queue(self, final = True): 96 | while self.queue_pos < len(self.queue): 97 | if (ea := self.queue[self.queue_pos]) not in self.visited: 98 | self.process_queue_entry(ea) 99 | self.queue_pos += 1 100 | if final: 101 | self.queue = None 102 | 103 | def print(self, file): 104 | print('', file = file) 110 | print('', file = file) 111 | 112 | def decompress(rom, ea): 113 | ea = rom.map(ea) 114 | ea_start = ea 115 | data = rom.data 116 | rv = bytearray() 117 | while True: 118 | b = data[ea]; ea += 1 119 | if b == 0xff: 120 | return rv, ea - ea_start 121 | if (b & 0xe0) == 0xe0: 122 | cmd = (8 * b) & 0xe0 123 | size = ((b & 3) << 8 | data[ea]) + 1; ea += 1 124 | else: 125 | cmd = b & 0xe0 126 | size = (b & 0x1f) + 1 127 | if cmd & 0x80: 128 | want_xor = 0xff if cmd & 0x20 else 0x00 129 | if cmd >= 0xc0: 130 | src_pos = len(rv) - data[ea]; ea += 1 131 | else: 132 | src_pos = data[ea] | data[ea + 1] * 256; ea += 2 133 | for i in range(size): 134 | rv.append(rv[src_pos + i] ^ want_xor) 135 | elif cmd == 0x20: 136 | b = data[ea]; ea += 1 137 | for i in range(size): 138 | rv.append(b) 139 | elif cmd == 0x40: 140 | b, b2 = data[ea], data[ea + 1]; ea += 2 141 | while size: 142 | rv.append(b) 143 | if size <= 1: 144 | break 145 | rv.append(b2) 146 | size -= 2 147 | elif cmd == 0x60: 148 | b = data[ea]; ea += 1 149 | for i in range(size): 150 | rv.append(b) 151 | b = (b + 1) & 0xff 152 | else: 153 | for i in range(size): 154 | rv.append(data[ea]); ea += 1 155 | 156 | 157 | def get_compressed_size(rom, ea): 158 | ea = rom.map(ea) 159 | ea_start = ea 160 | data = rom.data 161 | while True: 162 | b = data[ea]; ea += 1 163 | if b == 0xff: 164 | return ea - ea_start 165 | if (b & 0xe0) == 0xe0: 166 | cmd = (8 * b) & 0xe0 167 | size = ((b & 3) << 8 | data[ea]) + 1; ea += 1 168 | else: 169 | cmd = b & 0xe0 170 | size = (b & 0x1f) + 1 171 | if cmd & 0x80: 172 | if cmd >= 0xc0: 173 | ea += 1 174 | else: 175 | ea += 2 176 | elif cmd == 0x20: 177 | ea += 1 178 | elif cmd == 0x40: 179 | ea += 2 180 | elif cmd == 0x60: 181 | ea += 1 182 | else: 183 | ea += size 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /assets/eproj_decode.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from decode_common import * 3 | 4 | BANK = 0x86 5 | 6 | kEprojInstructionSet = { 7 | 0x868154: ('Delete', ''), 8 | 0x868159: ('Sleep', ''), 9 | 0x868161: ('SetPreInstr_', 'A'), 10 | 0x86816a: ('ClearPreInstr', ''), 11 | 0x868171: ('CallFunc', 'L'), 12 | 0x86818b: ('CallFuncWithParam', 'LH'), 13 | 0x8681ab: ('Goto', 'G'), 14 | 0x8681c6: ('DecTimerAndGotoIfNonZero', 'G'), 15 | 0x8681d5: ('SetTimer', 'H'), 16 | 0x8681de: ('nullsub_82', ''), 17 | 0x8681df: ('MoveRandomlyWithinRadius', 'BBBB'), 18 | 0x868230: ('SetProjectileProperties', 'X'), 19 | 0x86823c: ('ClearProjectileProperties', 'X'), 20 | 0x868248: ('EnableCollisionWithSamusProj', ''), 21 | 0x868252: ('DisableCollisionWithSamusProj', ''), 22 | 0x86825c: ('DisableCollisionWithSamus', ''), 23 | 0x868266: ('EnableCollisionWithSamus', ''), 24 | 0x868270: ('SetToNotDieOnContact', ''), 25 | 0x86827a: ('SetToDieOnContact', ''), 26 | 0x868284: ('SetLowPriority', ''), 27 | 0x86828e: ('SetHighPriority', ''), 28 | 0x868298: ('SetXyRadius', 'BB'), 29 | 0x8682a1: ('SetXyRadiusZero', ''), 30 | 0x8682a5: ('CalculateDirectionTowardsSamus', ''), 31 | 0x8682d5: ('WriteColorsToPalette', 'HHH'), 32 | 0x8682fd: ('QueueMusic', 'B'), 33 | 0x868309: ('QueueSfx1_Max6', 'B'), 34 | 0x868312: ('QueueSfx2_Max6', 'B'), 35 | 0x86831b: ('QueueSfx3_Max6', 'B'), 36 | 0x868324: ('QueueSfx1_Max15', 'B'), 37 | 0x86832d: ('QueueSfx2_Max15', 'B'), 38 | 0x868336: ('QueueSfx3_Max15', 'B'), 39 | 0x86833f: ('QueueSfx1_Max3', 'B'), 40 | 0x868348: ('QueueSfx2_Max3', 'B'), 41 | 0x868351: ('QueueSfx3_Max3', 'B'), 42 | 0x86835a: ('QueueSfx1_Max9', 'B'), 43 | 0x868363: ('QueueSfx2_Max9', 'B'), 44 | 0x86836c: ('QueueSfx3_Max9', 'B'), 45 | 0x868375: ('QueueSfx1_Max1', 'B'), 46 | 0x86837e: ('QueueSfx2_Max1', 'B'), 47 | 0x868387: ('QueueSfx3_Max1', 'B'), 48 | 0x868c68: ('SpawnEnemyDropsWithDraygonsEyeDrops', ''), 49 | 0x868cf6: ('EnemyProjInstr_SetPreInstrA', ''), 50 | 0x868d99: ('EprojInstr_868D99', ''), 51 | 0x869270: ('EprojInstr_9270', ''), 52 | 0x8695ba: ('EprojInstr_95BA', ''), 53 | 0x8695ed: ('EprojInstr_95ED', ''), 54 | 0x869620: ('EprojInstr_9620', ''), 55 | 0x86980e: ('EprojInstr_980E', ''), 56 | 0x86a050: ('SetPreInstrAndRun', 'A'), 57 | 0x86a3be: ('EprojInstr_A3BE', ''), 58 | 0x86ab8a: ('SpawnEnemyDrops', 'aa'), 59 | 0x86af36: ('ResetXYpos1', ''), 60 | 0x86b269: ('SetVelTowardsSamus1', ''), 61 | 0x86b7ea: ('SpawnTourianStatueUnlockingParticle', ''), 62 | 0x86b7f5: ('Earthquake', ''), 63 | 0x86b818: ('SpawnTourianStatueUnlockingParticleTail', ''), 64 | 0x86c173: ('SwitchJump', ''), 65 | 0x86c1b4: ('UserPalette0', ''), 66 | 0x86caee: ('MotherBrainPurpleBreathIsActive', ''), 67 | 0x86c42e: ('sub_86C42E', ''), 68 | 0x86c8d0: ('Add12ToY', ''), 69 | 0x86d1c7: ('EprojInstr_D1C7', ''), 70 | 0x86d1ce: ('EprojInstr_D1CE', ''), 71 | 0x86d5f2: ('SetN00bTubeShardX', 'SS'), 72 | 0x86d5e1: ('AssignNewN00bTubeShardVelocity', ''), 73 | 0x86d69a: ('SetXvelRandom', ''), 74 | 0x86dc5a: ('EprojInstr_DC5A', ''), 75 | 0x86dc61: ('SpawnEnemyDrops_0', ''), 76 | 0x86dfea: ('EprojInstr_DFEA', ''), 77 | 0x86e533: ('SetYVel', 'I'), 78 | 0x86ee8b: ('QueueSfx2_9', ''), 79 | 0x86b13e: ('sub_86B13E', ''), 80 | 0x86b272: ('SetVelTowardsSamus2', ''), 81 | 0x86b3b8: ('GotoIfFunc1', 'G'), 82 | 0x86d15c: ('EprojInstr_D15C', ''), 83 | 0x86d1b6: ('EprojInstr_D1B6', ''), 84 | 0x86d62a: ('EprojInstr_D62A', 'H'), 85 | 0x86dc77: ('SpawnSporesEproj', ''), 86 | 0x86ef10: ('HandleRespawningEnemy', ''), 87 | 0x86eeaf: ('EprojInstr_EEAF', ''), 88 | 0x86eea3: ('QueueSfx2_B', ''), 89 | 0x86ee97: ('QueueSfx2_24', ''), 90 | 0x86ece3: ('EprojInstr_ECE3', 'H'), 91 | 0x86ed17: ('EprojInstr_ED17', 'H'), 92 | 0x86ad92: ('GotoDependingOnXDirection', 'HG'), 93 | 0x869475: ('DisableCollisionsWithSamus', ''), 94 | 0x86a456: ('GotoWithProbability25', 'G'), 95 | 0x86af92: ('MoveY_Minus4', ''), 96 | 0x86b841: ('AddToYpos', 'H'), 97 | } 98 | 99 | kCommandByName = {v[0] : (k, v[1]) for k, v in kEprojInstructionSet.items()} 100 | 101 | 102 | kEprojTerminator = {0x868154, 0x868159, 0x8681AB, 0x86c173} 103 | kEprojTerminatorEA = {0x86B818} 104 | 105 | if 0: 106 | name2ea = pickle.loads(open('names.pickle', 'rb').read()) 107 | name2ea = {k.replace('?', '') : v for k, v in name2ea.items()} 108 | ea2name = {v:k for k,v in name2ea.items()} 109 | for k, v in kEprojInstructionSet.items(): 110 | k = k | 0x860000 111 | t = ea2name[k] 112 | name = t.replace('EprojInstr_', '') 113 | args, _ = v 114 | tostr = {1 : 'B', 2 : 'H', 3 : 'L'} 115 | args = ''.join(tostr[a] for a in args) 116 | if name.startswith('Goto'): 117 | args = args[:-1] + 'G' 118 | print(" 0x%x: ('%s', '%s')," % (k, name, args)) 119 | 120 | class EprojParser(InstrParserCommon): 121 | forbidden_addrs = {0x868a39} 122 | 123 | def __init__(self): 124 | super().__init__() 125 | self.sprite_maps = set() 126 | 127 | def print(self, file): 128 | print('', file = file) 134 | print('', file = file) 135 | 136 | def process_queue_entry(self, ea): 137 | assert ea & 0x8000 138 | while True: 139 | if ea in self.visited or ea in kEprojTerminatorEA: 140 | break 141 | 142 | self.visited.add(ea) 143 | ins = get_word(ea) 144 | if ins & 0x8000: 145 | ea_org = ea 146 | ins = 0x860000 | ins 147 | if ins not in kEprojInstructionSet: 148 | print(f'// Ins {ins:X} not in iset at {ea:X}') 149 | self.missing_isets.add(ea & 0xff0000 | ins) 150 | return 151 | name, operands = kEprojInstructionSet[ins] 152 | ea += 2 153 | r = [] 154 | for op in operands: 155 | if op == 'B': 156 | r.append('%d' % get_byte(ea)) 157 | ea += 1 158 | elif op == 'H': 159 | r.append('%d' % get_word(ea)) 160 | ea += 2 161 | elif op == 'I': 162 | r.append('%d' % sex16(get_word(ea))) 163 | ea += 2 164 | elif op == 'X': 165 | r.append('0x%x' % get_word(ea)) 166 | ea += 2 167 | elif op == 'L': 168 | r.append(get_ea_name(get_word(ea) + get_byte(ea+2) * 65536)) 169 | ea += 3 170 | elif op == 'G': 171 | addr = 0x860000 | get_word(ea) 172 | assert addr & 0x8000 173 | r.append(get_ea_name(addr, short_label=True)) 174 | self.visit(addr) 175 | ea += 2 176 | elif op == 'A': 177 | addr = 0x860000 | get_word(ea) 178 | assert addr & 0x8000 179 | r.append(get_ea_name(addr, short_label=True)) 180 | ea += 2 181 | elif op == 'a': 182 | addr = 0xa00000 | get_word(ea) 183 | assert addr & 0x8000 184 | r.append(get_ea_name(addr)) 185 | ea += 2 186 | elif op == 'S': 187 | addr = 0x8D0000 | get_word(ea) 188 | assert addr & 0x8000 189 | r.append(get_ea_name(addr)) 190 | self.sprite_maps.add(addr) 191 | ea += 2 192 | else: 193 | assert 0 194 | self.print_line(ea_org, f' {name}({", ".join(r)})') 195 | if ins in kEprojTerminator: 196 | self.print_line(ea_org + 1, '') 197 | break 198 | else: 199 | drawp = get_word(ea + 2) | 0x8D0000 200 | assert drawp & 0x8000, hex(ea) 201 | assert drawp != 0x868000 202 | self.sprite_maps.add(drawp) 203 | self.print_line(ea, f' {ins} ! {get_ea_name(drawp)}') 204 | ea += 4 205 | -------------------------------------------------------------------------------- /assets/palfx_decode.py: -------------------------------------------------------------------------------- 1 | from decode_common import * 2 | 3 | BANK = 0x8D 4 | 5 | kInstructionSet = { 6 | 0x8dc595: ('Wait', ''), 7 | 0x8dc599: ('ColorPlus2', ''), 8 | 0x8dc5a2: ('ColorPlus3', ''), 9 | 0x8dc5ab: ('ColorPlus4', ''), 10 | 0x8dc5b4: ('ColorPlus8', ''), 11 | 0x8dc5bd: ('ColorPlus9', ''), 12 | 0x8dc5c6: ('ColorPlus15', ''), 13 | 0x8dc5cf: ('Delete', ''), 14 | 0x8dc5d4: ('SetPreInstr', 'A'), 15 | 0x8dc5dd: ('ClearPreInstr', ''), 16 | 0x8dc61e: ('Goto', 'G'), 17 | 0x8dc639: ('DecTimerGoto', 'G'), 18 | 0x8dc648: ('SetTimer', 'B'), 19 | 0x8dc655: ('SetColorIndex', 'H'), 20 | 0x8dc65e: ('QueueMusic', 'B'), 21 | 0x8dc66a: ('QueueSfx1', 'B'), 22 | 0x8dc673: ('QueueSfx2', 'B'), 23 | 0x8dc67c: ('QueueSfx3', 'B'), 24 | 0x8df1c6: ('SetPalfxIndex', 'B'), 25 | } 26 | 27 | kCommandByName = {v[0] : (k, v[1]) for k, v in kInstructionSet.items()} 28 | 29 | class PalfxParser(InstrParserCommon): 30 | instruction_set = kInstructionSet 31 | instruction_set_term = {0x8DC5CF, 0x8DC61E} 32 | TAG = 'palfx' 33 | 34 | def __init__(self): 35 | super().__init__() 36 | 37 | def handle_draw_command(self, ins, ea): 38 | xs = [] 39 | while (x := get_word(ea + 2 + len(xs) * 2)) & 0x8000 == 0: 40 | xs.append('%.4x' % x) 41 | s = f'{ins:2} ' if ins != None else '' 42 | s = f' {s}! {" ".join(xs)}' 43 | if x != 0xc595: 44 | s += ' !' 45 | self.print_line(ea, s) 46 | return 2 + len(xs) * 2 47 | 48 | def process_queue_entry(self, ea): 49 | assert ea & 0x8000 50 | is_finish = True 51 | while ea not in self.visited: 52 | self.visited.add(ea) 53 | ins = get_word(ea) 54 | if ins & 0x8000: 55 | if ins == 0xc595: 56 | assert not is_finish 57 | ea += 2 58 | is_finish = True 59 | continue 60 | ea_org = ea 61 | ins = (BANK << 16) | ins 62 | if ins not in self.instruction_set: 63 | raise Exception(f'Ins {ins:X} not in iset at {ea:X}') 64 | name, operands = self.instruction_set[ins] 65 | ea += 2 66 | r = [] 67 | for op in operands: 68 | if op == 'B': 69 | r.append('%d' % get_byte(ea)) 70 | ea += 1 71 | elif op == 'H': 72 | r.append('%d' % get_word(ea)) 73 | ea += 2 74 | elif op == 'G': 75 | addr = (BANK << 16) | get_word(ea) 76 | r.append(get_ea_name(addr, short_label=True)) 77 | self.visit(addr) 78 | ea += 2 79 | elif op == 'A': 80 | addr = (BANK << 16) | get_word(ea) 81 | assert addr & 0x8000 82 | r.append(get_ea_name(addr, short_label=True)) 83 | ea += 2 84 | else: 85 | assert 0 86 | self.print_line(ea_org, f' {name}({", ".join(r)})') 87 | if ins in self.instruction_set_term: 88 | self.print_line(ea_org + 1, '') 89 | break 90 | else: 91 | if not is_finish: 92 | ea -= 2 93 | ins = None 94 | ea += self.handle_draw_command(ins, ea) 95 | is_finish = False 96 | -------------------------------------------------------------------------------- /assets/plm_decode.py: -------------------------------------------------------------------------------- 1 | from decode_common import * 2 | 3 | BANK = 0x84 4 | 5 | kPlmInstructionSet = { 6 | 0x8486b4: ('Sleep', ''), 7 | 0x8486bc: ('Delete', ''), 8 | 0x8486c1: ('PreInstr', 'A'), 9 | 0x8486ca: ('ClearPreInstr', ''), 10 | 0x8486d1: ('UNUSED_CallFunction', 'L'), 11 | 0x8486eb: ('CallFunctionWithArg', 'LH'), 12 | 0x84870b: ('CallFunction', 'L'), 13 | 0x848724: ('Goto', 'G'), 14 | 0x848729: ('UNUSED_sub_848729', 'B'), 15 | 0x84873f: ('DecrementAndGotoIfNonzero', 'G'), 16 | 0x84874e: ('SetTimer', 'B'), 17 | 0x84875a: ('UNUSED_sub_84875A', 'H'), 18 | 0x848764: ('LoadItemPlmGfx', '9BBBBBBBB'), 19 | 0x8487e5: ('CopyFromRamToVram', 'HLH'), 20 | 0x84880e: ('GotoIfBossBitSet', 'BG'), 21 | 0x848821: ('UNUSED_sub_848821', 'B'), 22 | 0x84882d: ('GotoIfEventSet', 'HG'), 23 | 0x84883e: ('SetEvent', 'H'), 24 | 0x848848: ('GotoIfChozoSet', 'G'), 25 | 0x848865: ('SetRoomChozoBit', ''), 26 | 0x84887c: ('GotoIfItemBitSet', 'G'), 27 | 0x848899: ('SetItemBit', ''), 28 | 0x8488b0: ('PickupBeamAndShowMessage', 'HB'), 29 | 0x8488f3: ('PickupEquipmentAndShowMessage', 'HB'), 30 | 0x84891a: ('PickupEquipmentAddGrappleShowMessage', 'H'), 31 | 0x848941: ('PickupEquipmentAddXrayShowMessage', 'H'), 32 | 0x848968: ('CollectHealthEnergyTank', 'H'), 33 | 0x848986: ('CollectHealthReserveTank', 'H'), 34 | 0x8489a9: ('CollectAmmoMissileTank', 'H'), 35 | 0x8489d2: ('CollectAmmoSuperMissileTank', 'H'), 36 | 0x8489fb: ('CollectAmmoPowerBombTank', 'H'), 37 | 0x848a24: ('SetLinkReg', 'G'), 38 | 0x848a2e: ('Call', 'G'), 39 | 0x848a3a: ('Return', ''), 40 | 0x848a40: ('UNUSED_sub_848A40', ''), 41 | 0x848a59: ('UNUSED_sub_848A59', ''), 42 | 0x848a72: ('GotoIfDoorBitSet', 'G'), 43 | 0x848a91: ('IncrementDoorHitCounterAndJGE', 'BG'), 44 | 0x848acd: ('IncrementArgumentAndJGE', 'BG'), 45 | 0x848af1: ('SetBTS', 'B'), 46 | 0x848b05: ('DrawPlmBlock', ''), 47 | 0x848b17: ('DrawPlmBlock_', ''), 48 | 0x848b55: ('ProcessAirScrollUpdate', ''), 49 | 0x848b93: ('ProcessSolidScrollUpdate', ''), 50 | 0x848bd1: ('QueueMusic', 'B'), 51 | 0x848bdd: ('ClearMusicQueueAndQueueTrack', 'B'), 52 | 0x848c07: ('QueueSfx1_Max6', 'B'), 53 | 0x848c10: ('QueueSfx2_Max6', 'B'), 54 | 0x848c19: ('QueueSfx3_Max6', 'B'), 55 | 0x848c22: ('QueueSfx1_Max15', 'B'), 56 | 0x848c2b: ('QueueSfx2_Max15', 'B'), 57 | 0x848c34: ('QueueSfx3_Max15', 'B'), 58 | 0x848c3d: ('QueueSfx1_Max3', 'B'), 59 | 0x848c46: ('QueueSfx2_Max3', 'B'), 60 | 0x848c4f: ('QueueSfx_Max3', 'B'), 61 | 0x848c58: ('QueueSfx1_Max9', 'B'), 62 | 0x848c61: ('QueueSfx2_Max9', 'B'), 63 | 0x848c6a: ('QueueSfx3_Max9', 'B'), 64 | 0x848c73: ('QueueSfx1_Max1', 'B'), 65 | 0x848c7c: ('QueueSfx2_Max1', 'B'), 66 | 0x848c85: ('QueueSfx3_Max1', 'B'), 67 | 0x848c8f: ('ActivateMapStation', ''), 68 | 0x848caf: ('ActivateEnergyStation', ''), 69 | 0x848cd0: ('ActivateMissileStation', ''), 70 | 0x848cf1: ('ActivateSaveStationAndGotoIfNo', 'H'), 71 | 0x848d39: ('UNUSED_sub_848D39', ''), 72 | 0x848d41: ('GotoIfSamusNear', 'BBG'), 73 | 0x848d89: ('UNUSED_sub_848D89', ''), 74 | 0x84ab00: ('MovePlmDownOneBlock', ''), 75 | 0x84ab51: ('Scroll_0_1_Blue', ''), 76 | 0x84ab59: ('MovePlmDownOneBlock_0', ''), 77 | 0x84abd6: ('ABD6', ''), 78 | 0x84ac9d: ('DealDamage_2', ''), 79 | 0x84acb1: ('GiveInvincibility', ''), 80 | 0x84ad43: ('Draw0x38FramesOfRightTreadmill', ''), 81 | 0x84ad58: ('Draw0x38FramesOfLeftTreadmill', ''), 82 | 0x84ae35: ('GotoIfSamusHealthFull', 'G'), 83 | 0x84aebf: ('GotoIfMissilesFull', 'G'), 84 | 0x84b00e: ('PlaceSamusOnSaveStation', ''), 85 | 0x84b024: ('DisplayGameSavedMessageBox', ''), 86 | 0x84b030: ('EnableMovementAndSetSaveStationUsed', ''), 87 | 0x84b9b9: ('SetCrittersEscapedEvent', ''), 88 | 0x84ba6f: ('JumpIfSamusHasNoBombs', 'G'), 89 | 0x84bb25: ('MovePlmRight4Blocks', ''), 90 | 0x84bbdd: ('ClearTrigger', ''), 91 | 0x84bbe1: ('SpawnEproj', 'H'), 92 | 0x84bbf0: ('WakeEprojAtPlmPos', 'H'), 93 | 0x84be3f: ('SetGreyDoorPreInstr', ''), 94 | 0x84cd93: ('SetBtsTo1', ''), 95 | 0x84d155: ('FxBaseYPos_0x2D2', ''), 96 | 0x84d2f9: ('GotoIfRoomArgLess', 'HG'), 97 | 0x84d30b: ('SpawnFourMotherBrainGlass', 'HHHH'), 98 | 0x84d357: ('SpawnTorizoStatueBreaking', 'H'), 99 | 0x84d3c7: ('QueueSong1MusicTrack', ''), 100 | 0x84d3d7: ('TransferWreckedShipChozoSpikesToSlopes', ''), 101 | 0x84d3f4: ('TransferWreckedShipSlopesToChozoSpikes', ''), 102 | 0x84d476: ('UNUSED_sub_84D476', ''), 103 | 0x84d489: ('UNUSED_sub_84D489', ''), 104 | 0x84d4be: ('nullsub_70', ''), 105 | 0x84d525: ('EnableWaterPhysics', ''), 106 | 0x84d52c: ('SpawnN00bTubeCrackEproj', ''), 107 | 0x84d536: ('DiagonalEarthquake', ''), 108 | 0x84d543: ('Spawn10shardsAnd6n00bs', ''), 109 | 0x84d5e6: ('DisableSamusControls', ''), 110 | 0x84d5ee: ('EnableSamusControls', ''), 111 | 0x84d77a: ('ShootEyeDoorProjectileWithProjectileArg', 'H'), 112 | 0x84d790: ('SpawnEyeDoorSweatEproj', 'H'), 113 | 0x84d79f: ('SpawnTwoEyeDoorSmoke', ''), 114 | 0x84d7b6: ('SpawnEyeDoorSmokeProjectile', ''), 115 | 0x84d7c3: ('MoveUpAndMakeBlueDoorFacingRight', ''), 116 | 0x84d7da: ('MoveUpAndMakeBlueDoorFacingLeft', ''), 117 | 0x84db8e: ('DamageDraygonTurret', ''), 118 | 0x84dbb8: ('DamageDraygonTurretFacingDownRight', ''), 119 | 0x84dbf7: ('DamageDraygonTurretFacingUpRight', ''), 120 | 0x84dc36: ('DamageDraygonTurret2', ''), 121 | 0x84dc60: ('DamageDraygonTurretFacingDownLeft', ''), 122 | 0x84dc9f: ('DamageDraygonTurretFacingUpLeft', ''), 123 | 0x84e04f: ('DrawItemFrame0', ''), 124 | 0x84e067: ('DrawItemFrame1', ''), 125 | 0x84e29d: ('ClearChargeBeamCounter', ''), 126 | 0x84e63b: ('E63B', ''), 127 | } 128 | 129 | kPlmTerminator = { 0x8486BC, 0x848724, 0x848A3A } 130 | 131 | # Sometimes Sleep is used in a way assumes Sleep never wakes up, 132 | # unless we do this we'll decode random instructions. 133 | kPlmTerminatorAtEA = { 0x84B89A, 0x84B8DA, 0x84DB42, 0x84BB03, 0x84B7E9 } 134 | 135 | kCommandByName = {v[0] : (k, v[1]) for k, v in kPlmInstructionSet.items()} 136 | #for k, v in kPlmInstructionSet.items(): 137 | # k = k | 0x840000 138 | # t = ea2name[k] 139 | # t = t.replace('PlmInstr_', '') 140 | # print("0x%x: ('%s', %s)," % (k, t, repr(tuple(v[0])))) 141 | 142 | #for k, v in kPlmInstructionSet.items(): 143 | # name, args = v 144 | # tostr = {1 : 'B', 2 : 'H', 3 : 'L'} 145 | # args = ''.join(tostr[a] for a in args) 146 | # if name.startswith('Goto') or k == 0x848A24 or k == 0x848A2E: 147 | # args = args[:-1] + 'G' 148 | # print(" 0x%x: ('%s', '%s')," % (k, name, args)) 149 | 150 | def plm_header_size(ea): 151 | return 6 if (ea >= 0x84c842 and ea < 0x84c8ba) or ea in (0x84c8ca, 0x84BAF4) else 4 152 | 153 | class PlmParser(InstrParserCommon): 154 | instruction_set = kPlmInstructionSet 155 | instruction_set_term = {} 156 | TAG = 'plm' 157 | 158 | def __init__(self): 159 | super().__init__() 160 | self.draw_cmds = {} 161 | 162 | def parse_draw_commands(self, ea): 163 | assert ea & 0x8000 164 | if ea in self.draw_cmds: 165 | return 166 | last = None 167 | r = [] 168 | self.draw_cmds[ea] = r 169 | while True: 170 | n = get_word(ea) 171 | assert (n & 0x7f00) == 0 172 | cmd = '' 173 | if last != None: 174 | if last & 0xff != 0: cmd = f'{cmd}{sex8(last&0xff)}x ' 175 | if last >> 8 != 0: cmd = f'{cmd}{sex8(last>>8)}y ' 176 | cmd += 'v ' if n & 0x8000 else 'h ' 177 | cmd += " ".join('%.4x' % get_word(ea + 2 + i * 2) for i in range(n & 0xff)) 178 | r.append(cmd) 179 | ea += 2 + (n & 0xff) * 2 180 | last = get_word(ea) 181 | ea += 2 182 | if last == 0: 183 | break 184 | 185 | def print(self, file): 186 | draw_cmd_printed = set() 187 | output, queued = [], [] 188 | last_insertion_pos = 0 189 | def inject_queued(): 190 | if len(queued): 191 | queued.append('') 192 | output[last_insertion_pos:last_insertion_pos] = queued 193 | del queued[:] 194 | for ea, line in sorted(self.lines, key = lambda x: x[0]): 195 | if isinstance(line, str): 196 | if ea in self.label: 197 | output.append(f'{get_ea_name(ea, short_label = True)}:') 198 | output.append(line) 199 | if line == '': 200 | inject_queued() 201 | last_insertion_pos = len(output) 202 | elif line not in draw_cmd_printed: 203 | draw_cmd_printed.add(line) 204 | name = get_ea_name(line) 205 | for i, v in enumerate(self.draw_cmds[line]): 206 | t = name if i == 0 else (' ' * len(name)) 207 | queued.append(t + ' ! ' + v) 208 | inject_queued() 209 | print('', file = file) 213 | print('', file = file) 214 | 215 | def process_queue_entry(self, ea): 216 | assert ea & 0x8000 217 | while True: 218 | if ea in self.visited: 219 | break 220 | self.visited.add(ea) 221 | ins = get_word(ea) 222 | if ins & 0x8000: 223 | ea_org = ea 224 | ins = 0x840000 | ins 225 | if ins not in kPlmInstructionSet: 226 | print(f'// Ins {ins:X} not in iset at {ea:X}') 227 | self.missing_isets.add(ea & 0xff0000 | ins) 228 | return 229 | name, operands = kPlmInstructionSet[ins] 230 | ea += 2 231 | r = [] 232 | for op in operands: 233 | if op == 'B': 234 | r.append('%d' % get_byte(ea)) 235 | ea += 1 236 | elif op == 'H': 237 | r.append('%d' % get_word(ea)) 238 | ea += 2 239 | elif op == 'L': 240 | r.append(get_ea_name(get_word(ea) + get_byte(ea+2) * 65536)) 241 | ea += 3 242 | elif op == 'A': 243 | addr = 0x840000 | get_word(ea) 244 | r.append(get_ea_name(addr, short_label=True)) 245 | ea += 2 246 | elif op == '9': 247 | addr = 0x890000 | get_word(ea) 248 | r.append(get_ea_name(addr)) 249 | self.blobs[addr] = 256 250 | ea += 2 251 | elif op == 'G': 252 | addr = 0x840000 | get_word(ea) 253 | r.append(get_ea_name(addr, short_label=True)) 254 | self.visit(addr) 255 | ea += 2 256 | else: 257 | assert 0 258 | self.print_line(ea_org, f' {name}({", ".join(r)})') 259 | if ins in kPlmTerminator or ea_org in kPlmTerminatorAtEA: 260 | self.print_line(ea_org + 1, '') 261 | break 262 | else: 263 | drawp = get_word(ea + 2) | 0x840000 264 | self.print_line(ea, f' {ins} ! {get_ea_name(drawp)}') 265 | self.print_line(ea, drawp) 266 | try: 267 | drawp_end = self.parse_draw_commands(drawp) 268 | except: 269 | print(f'Crash while processing draw commants at {ea:X}') 270 | raise 271 | ea += 4 272 | -------------------------------------------------------------------------------- /assets/projectile_decode.py: -------------------------------------------------------------------------------- 1 | from decode_common import * 2 | 3 | BANK = 0x93 4 | 5 | kProjInstructionSet = { 6 | 0x93822F: ('Delete', ''), 7 | 0x938239: ('Goto', 'G'), 8 | } 9 | 10 | kCommandByName = {v[0] : (k, v[1]) for k, v in kProjInstructionSet.items()} 11 | kCommandByName['Draw'] = (None, 'HABBH') 12 | 13 | kProjTerminator = { 0x938239, 0x93822F} 14 | 15 | class ProjParser(InstrParserCommon): 16 | instruction_set = kProjInstructionSet 17 | instruction_set_term = kProjTerminator 18 | TAG = 'proj' 19 | 20 | def __init__(self): 21 | super().__init__() 22 | self.sprite_maps = set() 23 | 24 | def handle_draw_command(self, ins, ea): 25 | drawp = get_word(ea + 2) | (BANK << 16) 26 | self.sprite_maps.add(drawp) 27 | self.print_line(ea, f' Draw({ins}, {get_ea_name(drawp)}, {get_byte(ea + 4)}, {get_byte(ea + 5)}, {get_word(ea + 6)})') 28 | return 8 29 | 30 | def process_queue_entry(self, ea): 31 | assert ea & 0x8000 32 | while True: 33 | if ea in self.visited: 34 | break 35 | self.visited.add(ea) 36 | ins = get_word(ea) 37 | if ins & 0x8000: 38 | ea_org = ea 39 | ins = (BANK << 16) | ins 40 | if ins not in self.instruction_set: 41 | print(f'// Ins {ins:X} not in iset at {ea:X}') 42 | return 43 | name, operands = self.instruction_set[ins] 44 | ea += 2 45 | r = [] 46 | for op in operands: 47 | if op == 'G': 48 | addr = (BANK << 16) | get_word(ea) 49 | r.append(get_ea_name(addr, short_label=True)) 50 | self.visit(addr) 51 | ea += 2 52 | else: 53 | assert 0 54 | self.print_line(ea_org, f' {name}({", ".join(r)})') 55 | if ins in self.instruction_set_term: 56 | self.print_line(ea_org + 1, '') 57 | break 58 | else: 59 | ea += self.handle_draw_command(ins, ea) 60 | -------------------------------------------------------------------------------- /other/annotate_funcs.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class FuncParser: 4 | def __init__(self): 5 | self.functions = [] 6 | self.lines_prefix = [] 7 | 8 | def read(self, filename): 9 | current_func_name = None 10 | state = 0 11 | collect = [] 12 | c = re.compile('^(?:static )?(?:inline )?[a-zA-Z0-9_]+ \\*? *([a-zA-Z0-9_]+)\\(.*\\) {( *//.*)?$') 13 | for line in open(filename).read().splitlines(): 14 | # print(line) 15 | line = line.rstrip() 16 | if m := c.match(line): 17 | assert state != 1 18 | if state == 0: 19 | self.lines_prefix = collect 20 | collect = [] 21 | current_func_name = m.group(1) 22 | state = 1 23 | collect.append(line) 24 | 25 | if line == '}': 26 | assert state == 1 27 | self.functions.append((current_func_name, collect)) 28 | collect = [] 29 | state = 2 30 | 31 | funcs = {} 32 | 33 | for line in open('../funcs.h'): 34 | if m := re.match('^#define fn([^ ]+) (0x[0-9A-Fa-f]+)', line): 35 | name, addr = m.group(1), m.group(2) 36 | funcs[name] = addr 37 | 38 | import glob 39 | for fname in glob.glob('../sm_??.cpp'): 40 | fp = FuncParser() 41 | fp.read(fname) 42 | 43 | fout = open(fname, 'w') 44 | for c in fp.lines_prefix: 45 | print(c, file = fout) 46 | 47 | for a, b in fp.functions: 48 | if a.endswith('_Async'): 49 | a = a[:-6] 50 | cmt = funcs.get(a) 51 | if cmt == None: 52 | print('noname ', a) 53 | for line in b: 54 | if cmt != None and line.endswith('{'): 55 | print(line + ' // ' + cmt, file = fout) 56 | cmt = None 57 | else: 58 | print(line, file = fout) 59 | fout.close() -------------------------------------------------------------------------------- /other/strip_some.py: -------------------------------------------------------------------------------- 1 | import re 2 | import glob 3 | 4 | all_data = '' 5 | for fname in glob.glob('../sm_??.cpp'): 6 | all_data += open(fname).read() 7 | 8 | assert 'Eproj_NorfairLavaquakeRocks_Func1' in all_data 9 | 10 | 11 | for line in open('../funcs.h').read().splitlines(): 12 | if m := re.match('^#define fn([^ ]+) (0x[0-9A-Fa-f]+)', line): 13 | name, addr = m.group(1), m.group(2) 14 | test1 = ('fn' + name) 15 | test2 = f'FUNC16({name})' 16 | if (test1 in all_data) or (test2) in all_data: 17 | print(line) 18 | -------------------------------------------------------------------------------- /other/useful_regexps.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | (, *|[-=+!] *|\()\*\(\(uint16 \*\)(v[0-9]+) \+ 2\) $1GET_WORD($2 + 4) 5 | 6 | (\(|[-=+!] *|, *)\*\(uint16 \*\)(v[0-9]+) $1GET_WORD($2) 7 | 8 | (, *|[-=+!] *|\()\*\(uint16 \*\)\((v[0-9]+) \+ 3\) $1GET_WORD($2 + 3) 9 | 10 | RespawnEnemy 11 | 12 | -------------------------------------------------------------------------------- /run_with_tcc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set SDL2=third_party\SDL2-2.24.1 4 | 5 | IF NOT EXIST "third_party\tcc\tcc.exe" ( 6 | ECHO: 7 | ECHO ERROR: src\third_party\tcc\tcc.exe doesn't exist. Please verify that you have put it in the right location. 8 | ECHO Download it from https://github.com/FitzRoyX/tinycc/releases/download/tcc_20221020/tcc_20221020.zip 9 | ECHO It needs to be the 64-bit version. 10 | ECHO: 11 | PAUSE 12 | EXIT /B 1 13 | ) ELSE ( 14 | REM 15 | ) 16 | 17 | IF NOT EXIST "%SDL2%\lib\x64\SDL2.dll" ( 18 | ECHO: 19 | ECHO ERROR: SDL is not unzipped properly into %SDL2% 20 | ECHO Download it from https://github.com/libsdl-org/SDL/releases/download/release-2.24.1/SDL2-devel-2.24.1-VC.zip 21 | ECHO: 22 | PAUSE 23 | EXIT /B 1 24 | ) ELSE ( 25 | REM 26 | ) 27 | 28 | 29 | echo Building with TCC... 30 | third_party\tcc\tcc.exe -osm.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 src/snes/*.c third_party/gl_core/gl_core_3_1.c 31 | IF ERRORLEVEL 1 goto GETOUT 32 | 33 | copy %SDL2%\lib\x64\SDL2.dll . 34 | 35 | echo Running... 36 | sm.exe sm.smc 37 | 38 | :GETOUT 39 | pause 40 | -------------------------------------------------------------------------------- /sm.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | # Automatically save state on quit and reload on start 3 | Autosave = 0 4 | 5 | # Disable the SDL_Delay that happens each frame (Gives slightly better perf if your 6 | # display is set to exactly 60hz) 7 | DisableFrameDelay = 0 8 | 9 | [Graphics] 10 | # Window size ( Auto or WidthxHeight ) 11 | WindowSize = Auto 12 | 13 | # Fullscreen mode (0=windowed, 1=desktop fullscreen, 2=fullscreen w/mode change) 14 | Fullscreen = 0 15 | 16 | # Window scale (1=100%, 2=200%, 3=300%, etc.) 17 | WindowScale = 3 18 | 19 | # Use an optimized (but potentially more buggy) SNES PPU implementation 20 | NewRenderer = 1 21 | 22 | # Display the world map with higher resolution 23 | EnhancedMode7 = 1 24 | 25 | # Don't keep the aspect ratio 26 | IgnoreAspectRatio = 0 27 | 28 | # Enable this option to remove the sprite limits per scan line 29 | NoSpriteLimits = 1 30 | 31 | # Use either SDL, SDL-Software, or OpenGL as the output method 32 | # SDL-Software rendering might give better performance on Raspberry pi. 33 | OutputMethod = SDL 34 | 35 | # Set to true to use linear filtering. Gives less crisp pixels. Works with SDL and OpenGL. 36 | LinearFiltering = 0 37 | 38 | # Set a glsl shader. Only supported with the OpenGL output method 39 | # This can be the path to a .glsl or .glslp file 40 | # Get them with: git clone https://github.com/snesrev/glsl-shaders 41 | #Shader = glsl-shaders/hqx/hq4x.glslp 42 | 43 | [Sound] 44 | EnableAudio = 1 45 | 46 | # DSP frequency in samples per second (e.g. 48000, 44100, 32000, 22050, 11025) 47 | AudioFreq = 32000 48 | 49 | # number of separate sound channels (1=mono, 2=stereo) 50 | AudioChannels = 2 51 | 52 | # Audio buffer size in samples (power of 2; e.g., 4096, 2048, 1024) [try 1024 if sound is crackly]. The higher the more lag before you hear sounds. 53 | AudioSamples = 512 54 | 55 | [KeyMap] 56 | # Change what keyboard keys map to the joypad 57 | # Order: Up, Down, Left, Right, Select, Start, A, B, X, Y, L, R 58 | 59 | # This default is suitable for QWERTY keyboards. 60 | Controls = Up, Down, Left, Right, Right Shift, Return, x, z, s, a, c, v 61 | 62 | # This default is suitable for QWERTZ keyboards. 63 | #Controls = Up, Down, Left, Right, Right Shift, Return, x, y, s, a, c, v 64 | 65 | # This one is suitable for AZERTY keyboards. 66 | #Controls = Up, Down, Left, Right, Right Shift, Return, x, w, s, q, c, v 67 | 68 | CheatLife = w 69 | CheatJump = Ctrl+q 70 | ClearKeyLog = k 71 | StopReplay = l 72 | Fullscreen = Alt+Return 73 | Reset = Ctrl+r 74 | Pause = Shift+p 75 | PauseDimmed = p 76 | Turbo = Tab 77 | ReplayTurbo = t 78 | WindowBigger = Ctrl+Up 79 | WindowSmaller = Ctrl+Down 80 | 81 | VolumeUp = Shift+= 82 | VolumeDown = Shift+- 83 | 84 | Load = F1, F2, F3, F4, F5, F6, F7, F8, F9, F10 85 | Save = Shift+F1,Shift+F2,Shift+F3,Shift+F4,Shift+F5,Shift+F6,Shift+F7,Shift+F8,Shift+F9,Shift+F10 86 | Replay= Ctrl+F1,Ctrl+F2,Ctrl+F3,Ctrl+F4,Ctrl+F5,Ctrl+F6,Ctrl+F7,Ctrl+F8,Ctrl+F9,Ctrl+F10 87 | 88 | LoadRef = 1,2,3,4,5,6,7,8,9,0,-,=,Backspace 89 | ReplayRef = Ctrl+1,Ctrl+2,Ctrl+3,Ctrl+4,Ctrl+5,Ctrl+6,Ctrl+7,Ctrl+8,Ctrl+9,Ctrl+0,Ctrl+-,Ctrl+=,Ctrl+Backspace 90 | 91 | [GamepadMap] 92 | # Any keys used in KeyMap can be used also in this section. 93 | # The shoulder button is called L1/Lb and L2, and the thumbstick button is called L3 94 | Controls = DpadUp, DpadDown, DpadLeft, DpadRight, Back, Start, B, A, Y, X, Lb, Rb 95 | 96 | -------------------------------------------------------------------------------- /sm.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32825.248 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sm", "src/sm.vcxproj", "{1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Debug|x64.ActiveCfg = Debug|x64 17 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Debug|x64.Build.0 = Debug|x64 18 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Debug|x86.ActiveCfg = Debug|Win32 19 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Debug|x86.Build.0 = Debug|Win32 20 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Release|x64.ActiveCfg = Release|x64 21 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Release|x64.Build.0 = Release|x64 22 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Release|x86.ActiveCfg = Release|Win32 23 | {1F8AB1B4-DAFB-4D6E-BF1E-F802FF5A52EE}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8F47385F-600A-41BA-AEF8-C47A2C0D15E6} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /sm.vcxproj.user 2 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "types.h" 3 | #include 4 | 5 | enum { 6 | kKeys_Null, 7 | kKeys_Controls, 8 | kKeys_Controls_Last = kKeys_Controls + 11, 9 | kKeys_Load, 10 | kKeys_Load_Last = kKeys_Load + 19, 11 | kKeys_Save, 12 | kKeys_Save_Last = kKeys_Save + 19, 13 | kKeys_Replay, 14 | kKeys_Replay_Last = kKeys_Replay + 19, 15 | kKeys_LoadRef, 16 | kKeys_LoadRef_Last = kKeys_LoadRef + 19, 17 | kKeys_ReplayRef, 18 | kKeys_ReplayRef_Last = kKeys_ReplayRef + 19, 19 | kKeys_CheatLife, 20 | kKeys_CheatJump, 21 | kKeys_ToggleWhichFrame, 22 | kKeys_ClearKeyLog, 23 | kKeys_StopReplay, 24 | kKeys_Fullscreen, 25 | kKeys_Reset, 26 | kKeys_Pause, 27 | kKeys_PauseDimmed, 28 | kKeys_Turbo, 29 | kKeys_ReplayTurbo, 30 | kKeys_WindowBigger, 31 | kKeys_WindowSmaller, 32 | kKeys_DisplayPerf, 33 | kKeys_ToggleRenderer, 34 | kKeys_VolumeUp, 35 | kKeys_VolumeDown, 36 | kKeys_Total, 37 | }; 38 | 39 | enum { 40 | kOutputMethod_SDL, 41 | kOutputMethod_SDLSoftware, 42 | kOutputMethod_OpenGL, 43 | }; 44 | 45 | typedef struct Config { 46 | int window_width; 47 | int window_height; 48 | bool enhanced_mode7; 49 | bool new_renderer; 50 | bool ignore_aspect_ratio; 51 | uint8 fullscreen; 52 | uint8 window_scale; 53 | bool enable_audio; 54 | bool linear_filtering; 55 | uint8 output_method; 56 | uint16 audio_freq; 57 | uint8 audio_channels; 58 | uint16 audio_samples; 59 | bool autosave; 60 | uint8 extended_aspect_ratio; 61 | bool extend_y; 62 | bool no_sprite_limits; 63 | bool display_perf_title; 64 | uint8 enable_msu; 65 | bool resume_msu; 66 | bool disable_frame_delay; 67 | uint8 msuvolume; 68 | uint32 features0; 69 | 70 | const char *link_graphics; 71 | char *memory_buffer; 72 | const char *shader; 73 | const char *msu_path; 74 | } Config; 75 | 76 | enum { 77 | kMsuEnabled_Msu = 1, 78 | kMsuEnabled_MsuDeluxe = 2, 79 | kMsuEnabled_Opuz = 4, 80 | }; 81 | enum { 82 | kGamepadBtn_Invalid = -1, 83 | kGamepadBtn_A, 84 | kGamepadBtn_B, 85 | kGamepadBtn_X, 86 | kGamepadBtn_Y, 87 | kGamepadBtn_Back, 88 | kGamepadBtn_Guide, 89 | kGamepadBtn_Start, 90 | kGamepadBtn_L3, 91 | kGamepadBtn_R3, 92 | kGamepadBtn_L1, 93 | kGamepadBtn_R1, 94 | kGamepadBtn_DpadUp, 95 | kGamepadBtn_DpadDown, 96 | kGamepadBtn_DpadLeft, 97 | kGamepadBtn_DpadRight, 98 | kGamepadBtn_L2, 99 | kGamepadBtn_R2, 100 | kGamepadBtn_Count, 101 | }; 102 | 103 | extern Config g_config; 104 | 105 | void ParseConfigFile(const char *filename); 106 | int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod); 107 | int FindCmdForGamepadButton(int button, uint32 modifiers); 108 | -------------------------------------------------------------------------------- /src/features.h: -------------------------------------------------------------------------------- 1 | // This file declares extensions to the base game 2 | #ifndef ZELDA3_FEATURES_H_ 3 | #define ZELDA3_FEATURES_H_ 4 | 5 | #include "types.h" 6 | 7 | // Special RAM locations that are unused but I use for compat things. 8 | 9 | #endif // ZELDA3_FEATURES_H_ 10 | -------------------------------------------------------------------------------- /src/glsl_shader.h: -------------------------------------------------------------------------------- 1 | #ifndef ZELDA3_GLSL_SHADER_H_ 2 | #define ZELDA3_GLSL_SHADER_H_ 3 | 4 | #include "types.h" 5 | 6 | enum { 7 | kGlslMaxPasses = 20, 8 | kGlslMaxTextures = 10, 9 | }; 10 | 11 | enum GLSLScaleType { 12 | GLSL_NONE, 13 | GLSL_SOURCE, 14 | GLSL_VIEWPORT, 15 | GLSL_ABSOLUTE 16 | }; 17 | 18 | typedef struct GlslTextureUniform { 19 | int Texture; 20 | int InputSize; 21 | int TextureSize; 22 | int TexCoord; 23 | } GlslTextureUniform; 24 | 25 | typedef struct GlslUniforms { 26 | GlslTextureUniform Top; 27 | int OutputSize; 28 | int FrameCount, FrameDirection; 29 | int LUTTexCoord; 30 | int VertexCoord; 31 | GlslTextureUniform Orig; 32 | GlslTextureUniform Prev[7]; 33 | GlslTextureUniform Pass[kGlslMaxPasses]; 34 | GlslTextureUniform PassPrev[kGlslMaxPasses]; 35 | int Texture[kGlslMaxTextures]; 36 | } GlslUniforms; 37 | 38 | typedef struct GlslPass { 39 | char *filename; 40 | uint8 scale_type_x, scale_type_y; 41 | bool float_framebuffer; 42 | bool srgb_framebuffer; 43 | bool mipmap_input; 44 | float scale_x, scale_y; 45 | uint wrap_mode; 46 | uint frame_count_mod; 47 | uint frame_count; 48 | uint gl_program, gl_fbo; 49 | uint filter; 50 | uint gl_texture; 51 | uint16 width, height; 52 | GlslUniforms unif; 53 | } GlslPass; 54 | 55 | typedef struct GlTextureWithSize { 56 | uint gl_texture; 57 | uint16 width, height; 58 | } GlTextureWithSize; 59 | 60 | typedef struct GlslTexture { 61 | struct GlslTexture *next; 62 | char *id; 63 | char *filename; 64 | uint filter; 65 | uint gl_texture; 66 | uint wrap_mode; 67 | bool mipmap; 68 | int width; 69 | int height; 70 | } GlslTexture; 71 | 72 | typedef struct GlslParam { 73 | struct GlslParam *next; 74 | char *id; 75 | bool has_value; 76 | float value; 77 | float min; 78 | float max; 79 | uint uniform[kGlslMaxPasses]; 80 | } GlslParam; 81 | 82 | typedef struct GlslShader { 83 | int n_pass; 84 | GlslPass *pass; 85 | GlslParam *first_param; 86 | GlslTexture *first_texture; 87 | uint *gl_vao; 88 | uint gl_vbo; 89 | uint frame_count; 90 | int max_prev_frame; 91 | GlTextureWithSize prev_frame[8]; 92 | } GlslShader; 93 | 94 | GlslShader *GlslShader_CreateFromFile(const char *filename); 95 | void GlslShader_Destroy(GlslShader *gs); 96 | void GlslShader_Render(GlslShader *gs, GlTextureWithSize *tex, int viewport_x, int viewport_y, int viewport_width, int viewport_height); 97 | 98 | 99 | #endif // ZELDA3_GLSL_SHADER_H_ 100 | -------------------------------------------------------------------------------- /src/move_defines.py: -------------------------------------------------------------------------------- 1 | import re, glob 2 | 3 | 4 | def process(s): 5 | lines = s.splitlines() 6 | 7 | defines = [] 8 | insert_pos = None 9 | 10 | for i, line in enumerate(lines): 11 | if line.startswith('#define '): 12 | lines[i] = None 13 | defines.append(line.replace('RomPtr(', 'RomFixedPtr(')) 14 | 15 | if line.startswith('#include') and lines[i+1].strip()[:8] in ('', '#define ') and len(defines) == 0 and insert_pos == None: 16 | insert_pos = i + 1 17 | 18 | assert insert_pos != None 19 | lines = lines[:insert_pos] + ['\n'] + defines + lines[insert_pos:] 20 | 21 | return "\n".join(a for a in lines if a != None) 22 | 23 | 24 | for filename in list(glob.glob('sm_*.c')): 25 | print(filename) 26 | s = open(filename, 'r').read() 27 | s2 = process(s) 28 | if s != s2: 29 | open(filename, 'w').write(s2) 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/opengl.c: -------------------------------------------------------------------------------- 1 | #include "third_party/gl_core/gl_core_3_1.h" 2 | #include 3 | #include 4 | #include 5 | #include "types.h" 6 | #include "util.h" 7 | #include "glsl_shader.h" 8 | #include "config.h" 9 | 10 | #define CODE(...) #__VA_ARGS__ 11 | 12 | static SDL_Window *g_window; 13 | static uint8 *g_screen_buffer; 14 | static size_t g_screen_buffer_size; 15 | static int g_draw_width, g_draw_height; 16 | static unsigned int g_program, g_VAO; 17 | static GlTextureWithSize g_texture; 18 | static GlslShader *g_glsl_shader; 19 | 20 | static void GL_APIENTRY MessageCallback(GLenum source, 21 | GLenum type, 22 | GLuint id, 23 | GLenum severity, 24 | GLsizei length, 25 | const GLchar *message, 26 | const void *userParam) { 27 | if (type == GL_DEBUG_TYPE_OTHER) 28 | return; 29 | 30 | fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", 31 | (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), 32 | type, severity, message); 33 | if (type == GL_DEBUG_TYPE_ERROR) 34 | Die("OpenGL error!\n"); 35 | } 36 | 37 | static bool OpenGLRenderer_Init(SDL_Window *window) { 38 | g_window = window; 39 | SDL_GLContext context = SDL_GL_CreateContext(window); 40 | (void)context; 41 | 42 | SDL_GL_SetSwapInterval(1); 43 | ogl_LoadFunctions(); 44 | 45 | if (!ogl_IsVersionGEQ(3, 3)) 46 | Die("You need OpenGL 3.3"); 47 | 48 | if (kDebugFlag) { 49 | glEnable(GL_DEBUG_OUTPUT); 50 | glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 51 | glDebugMessageCallback(MessageCallback, 0); 52 | } 53 | 54 | glGenTextures(1, &g_texture.gl_texture); 55 | 56 | static const float kVertices[] = { 57 | // positions // texture coords 58 | -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, // top left 59 | -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // bottom left 60 | 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // top right 61 | 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, // bottom right 62 | }; 63 | 64 | // create a vertex buffer object 65 | unsigned int vbo; 66 | glGenBuffers(1, &vbo); 67 | 68 | // vertex array object 69 | glGenVertexArrays(1, &g_VAO); 70 | // 1. bind Vertex Array Object 71 | glBindVertexArray(g_VAO); 72 | // 2. copy our vertices array in a buffer for OpenGL to use 73 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 74 | glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW); 75 | // position attribute 76 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0); 77 | glEnableVertexAttribArray(0); 78 | // texture coord attribute 79 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float))); 80 | glEnableVertexAttribArray(1); 81 | 82 | // vertex shader 83 | const GLchar *vs_code = "#version 330 core\n" CODE( 84 | layout(location = 0) in vec3 aPos; 85 | layout(location = 1) in vec2 aTexCoord; 86 | out vec2 TexCoord; 87 | void main(void) { 88 | gl_Position = vec4(aPos, 1.0); 89 | TexCoord = vec2(aTexCoord.x, aTexCoord.y); 90 | } 91 | ); 92 | 93 | unsigned int vs = glCreateShader(GL_VERTEX_SHADER); 94 | glShaderSource(vs, 1, &vs_code, NULL); 95 | glCompileShader(vs); 96 | 97 | int success; 98 | char infolog[512]; 99 | glGetShaderiv(vs, GL_COMPILE_STATUS, &success); 100 | if (!success) { 101 | glGetShaderInfoLog(vs, 512, NULL, infolog); 102 | printf("%s\n", infolog); 103 | } 104 | 105 | // fragment shader 106 | const GLchar *fs_code = "#version 330 core\n" CODE( 107 | out vec4 FragColor; 108 | in vec2 TexCoord; 109 | // texture samplers 110 | uniform sampler2D texture1; 111 | void main(void) { 112 | FragColor = texture(texture1, TexCoord); 113 | } 114 | ); 115 | 116 | unsigned int fs = glCreateShader(GL_FRAGMENT_SHADER); 117 | glShaderSource(fs, 1, &fs_code, NULL); 118 | glCompileShader(fs); 119 | 120 | glGetShaderiv(fs, GL_COMPILE_STATUS, &success); 121 | if (!success) { 122 | glGetShaderInfoLog(fs, 512, NULL, infolog); 123 | printf("%s\n", infolog); 124 | } 125 | 126 | // create program 127 | int program = g_program = glCreateProgram(); 128 | glAttachShader(program, vs); 129 | glAttachShader(program, fs); 130 | glLinkProgram(program); 131 | glGetProgramiv(program, GL_LINK_STATUS, &success); 132 | 133 | if (!success) { 134 | glGetProgramInfoLog(program, 512, NULL, infolog); 135 | printf("%s\n", infolog); 136 | } 137 | 138 | if (g_config.shader) 139 | g_glsl_shader = GlslShader_CreateFromFile(g_config.shader); 140 | 141 | return true; 142 | } 143 | 144 | static void OpenGLRenderer_Destroy(void) { 145 | } 146 | 147 | static void OpenGLRenderer_BeginDraw(int width, int height, uint8 **pixels, int *pitch) { 148 | int size = width * height; 149 | 150 | if (size > g_screen_buffer_size) { 151 | g_screen_buffer_size = size; 152 | free(g_screen_buffer); 153 | g_screen_buffer = (uint8*)malloc(size * 4); 154 | } 155 | 156 | g_draw_width = width; 157 | g_draw_height = height; 158 | *pixels = g_screen_buffer; 159 | *pitch = width * 4; 160 | } 161 | 162 | static void OpenGLRenderer_EndDraw(void) { 163 | int drawable_width, drawable_height; 164 | 165 | SDL_GL_GetDrawableSize(g_window, &drawable_width, &drawable_height); 166 | 167 | int viewport_width = drawable_width, viewport_height = drawable_height; 168 | 169 | if (!g_config.ignore_aspect_ratio) { 170 | if (viewport_width * g_draw_height < viewport_height * g_draw_width) 171 | viewport_height = viewport_width * g_draw_height / g_draw_width; // limit height 172 | else 173 | viewport_width = viewport_height * g_draw_width / g_draw_height; // limit width 174 | } 175 | 176 | int viewport_x = (drawable_width - viewport_width) >> 1; 177 | int viewport_y = (viewport_height - viewport_height) >> 1; 178 | 179 | glBindTexture(GL_TEXTURE_2D, g_texture.gl_texture); 180 | if (g_draw_width == g_texture.width && g_draw_height == g_texture.height) { 181 | 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); 182 | } else { 183 | g_texture.width = g_draw_width; 184 | g_texture.height = g_draw_height; 185 | 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); 186 | } 187 | 188 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 189 | glClear(GL_COLOR_BUFFER_BIT); 190 | 191 | if (g_glsl_shader == NULL) { 192 | glViewport(viewport_x, viewport_y, viewport_width, viewport_height); 193 | glUseProgram(g_program); 194 | int filter = g_config.linear_filtering ? GL_LINEAR : GL_NEAREST; 195 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); 196 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); 197 | glBindVertexArray(g_VAO); 198 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 199 | } else { 200 | GlslShader_Render(g_glsl_shader, &g_texture, viewport_x, viewport_y, viewport_width, viewport_height); 201 | } 202 | 203 | SDL_GL_SwapWindow(g_window); 204 | } 205 | 206 | 207 | static const struct RendererFuncs kOpenGLRendererFuncs = { 208 | &OpenGLRenderer_Init, 209 | &OpenGLRenderer_Destroy, 210 | &OpenGLRenderer_BeginDraw, 211 | &OpenGLRenderer_EndDraw, 212 | }; 213 | 214 | void OpenGLRenderer_Create(struct RendererFuncs *funcs) { 215 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 216 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 217 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 218 | *funcs = kOpenGLRendererFuncs; 219 | } 220 | 221 | -------------------------------------------------------------------------------- /src/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/platform/switch/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /*.nacp 3 | /*.nro 4 | /*.elf 5 | /*.smc -------------------------------------------------------------------------------- /src/platform/switch/Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 19 | # 20 | # NO_ICON: if set to anything, do not use icon. 21 | # NO_NACP: if set to anything, no .nacp file is generated. 22 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 23 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 24 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 25 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 26 | # ICON is the filename of the icon (.jpg), relative to the project folder. 27 | # If not set, it attempts to use one of the following (in this order): 28 | # - .jpg 29 | # - icon.jpg 30 | # - /default_icon.jpg 31 | # 32 | # CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. 33 | # If not set, it attempts to use one of the following (in this order): 34 | # - .json 35 | # - config.json 36 | # If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead 37 | # of a homebrew executable (.nro). This is intended to be used for sysmodules. 38 | # NACP building is skipped as well. 39 | #--------------------------------------------------------------------------------- 40 | SRC_DIR := ../../ 41 | TARGET := sm 42 | BUILD := bin 43 | SOURCES := $(SRC_DIR) $(SRC_DIR)/snes $(SRC_DIR)/platform/switch/src $(SRC_DIR)/../third_party/gl_core 44 | 45 | CFILES := $(wildcard $(SRC_DIR)/*.c $(SRC_DIR)/snes/*.c $(SRC_DIR)/platform/switch/src/*.c) $(SRC_DIR)/../third_party/gl_core/gl_core_3_1.c 46 | 47 | INCLUDES := include $(SRC_DIR)/../ ./src/ 48 | APP_TITLE := Super Metroid 49 | APP_AUTHOR := snesrev & Lywx 50 | APP_VERSION := $(shell git rev-parse --short HEAD) $(shell git rev-parse --abbrev-ref HEAD) 51 | 52 | #--------------------------------------------------------------------------------- 53 | # options for code generation 54 | #--------------------------------------------------------------------------------- 55 | ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE 56 | 57 | CFLAGS := -g -Wall -O2 -ffunction-sections -Wno-parentheses \ 58 | $(ARCH) $(DEFINES) 59 | 60 | CFLAGS += -D__SWITCH__ $(INCLUDE) -DSTBI_NO_THREAD_LOCALS `sdl2-config --cflags` 61 | 62 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions 63 | CFLAGS += -std=gnu11 64 | 65 | ASFLAGS := -g $(ARCH) 66 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 67 | 68 | LIBS := `$(PREFIX)pkg-config --libs sdl2` -lnx -lm 69 | 70 | #--------------------------------------------------------------------------------- 71 | # list of directories containing libraries, this must be the top level containing 72 | # include and lib 73 | #--------------------------------------------------------------------------------- 74 | LIBDIRS := $(PORTLIBS) $(LIBNX) 75 | 76 | #--------------------------------------------------------------------------------- 77 | # no real need to edit anything past this point unless you need to add additional 78 | # rules for different file extensions 79 | #--------------------------------------------------------------------------------- 80 | ifneq ($(BUILD),$(notdir $(CURDIR))) 81 | #--------------------------------------------------------------------------------- 82 | 83 | export OUTPUT := $(CURDIR)/$(TARGET) 84 | export TOPDIR := $(CURDIR) 85 | 86 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 87 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ 88 | 89 | 90 | export DEPSDIR := $(CURDIR)/$(BUILD) 91 | 92 | #CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 93 | #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 94 | #CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 95 | #SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 96 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 97 | 98 | #--------------------------------------------------------------------------------- 99 | # use CXX for linking C++ projects, CC for standard C 100 | #--------------------------------------------------------------------------------- 101 | export LD := $(CXX) 102 | #--------------------------------------------------------------------------------- 103 | 104 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 105 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(notdir $(CFILES:.c=.o)) $(SFILES:.s=.o) 106 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 107 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 108 | 109 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 110 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 111 | -I$(CURDIR)/$(BUILD) 112 | 113 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 114 | 115 | $(info $(OFILES)) 116 | 117 | ifeq ($(strip $(CONFIG_JSON)),) 118 | jsons := $(wildcard *.json) 119 | ifneq (,$(findstring $(TARGET).json,$(jsons))) 120 | export APP_JSON := $(TOPDIR)/$(TARGET).json 121 | else 122 | ifneq (,$(findstring config.json,$(jsons))) 123 | export APP_JSON := $(TOPDIR)/config.json 124 | endif 125 | endif 126 | else 127 | export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) 128 | endif 129 | 130 | ifeq ($(strip $(ICON)),) 131 | icons := $(wildcard *.jpg) 132 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 133 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 134 | else 135 | ifneq (,$(findstring icon.jpg,$(icons))) 136 | export APP_ICON := $(TOPDIR)/icon.jpg 137 | endif 138 | endif 139 | else 140 | export APP_ICON := $(TOPDIR)/$(ICON) 141 | endif 142 | 143 | ifeq ($(strip $(NO_ICON)),) 144 | export NROFLAGS += --icon=$(APP_ICON) 145 | endif 146 | 147 | ifeq ($(strip $(NO_NACP)),) 148 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 149 | endif 150 | 151 | ifneq ($(APP_TITLEID),) 152 | export NACPFLAGS += --titleid=$(APP_TITLEID) 153 | endif 154 | 155 | ifneq ($(ROMFS),) 156 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 157 | endif 158 | 159 | .PHONY: $(BUILD) clean all 160 | 161 | #--------------------------------------------------------------------------------- 162 | all: $(BUILD) 163 | 164 | $(BUILD): 165 | @echo $(CFILES) ... 166 | @[ -d $@ ] || mkdir -p $@ 167 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 168 | 169 | #--------------------------------------------------------------------------------- 170 | clean: 171 | @echo clean ... 172 | ifeq ($(strip $(APP_JSON)),) 173 | @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf 174 | else 175 | @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf 176 | endif 177 | 178 | 179 | #--------------------------------------------------------------------------------- 180 | else 181 | .PHONY: all 182 | 183 | DEPENDS := $(OFILES:.o=.d) 184 | 185 | #--------------------------------------------------------------------------------- 186 | # main targets 187 | #--------------------------------------------------------------------------------- 188 | ifeq ($(strip $(APP_JSON)),) 189 | 190 | all : $(OUTPUT).nro 191 | 192 | ifeq ($(strip $(NO_NACP)),) 193 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp 194 | else 195 | $(OUTPUT).nro : $(OUTPUT).elf 196 | endif 197 | 198 | else 199 | 200 | all : $(OUTPUT).nsp 201 | 202 | $(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm 203 | 204 | $(OUTPUT).nso : $(OUTPUT).elf 205 | 206 | endif 207 | 208 | $(OUTPUT).elf : $(OFILES) 209 | 210 | $(OFILES_SRC) : $(HFILES_BIN) 211 | 212 | #--------------------------------------------------------------------------------- 213 | # you need a rule like this for each extension you use as binary data 214 | #--------------------------------------------------------------------------------- 215 | %.bin.o %_bin.h : %.bin 216 | #--------------------------------------------------------------------------------- 217 | @echo $(notdir $<) 218 | @$(bin2o) 219 | 220 | -include $(DEPENDS) 221 | 222 | #--------------------------------------------------------------------------------------- 223 | endif 224 | #--------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /src/platform/switch/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snesrev/sm/578f90b3cc49557bb70060ad033bb90b8cf8ac50/src/platform/switch/icon.jpg -------------------------------------------------------------------------------- /src/platform/switch/sm.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | 3 | # Disable the SDL_Delay that happens each frame (Gives slightly better perf if your 4 | # display is set to exactly 60hz) 5 | DisableFrameDelay = 1 6 | 7 | [Graphics] 8 | # Window size ( Auto or WidthxHeight ) 9 | WindowSize = 1920x1080 10 | 11 | # Fullscreen mode (0=windowed, 1=desktop fullscreen, 2=fullscreen w/mode change) 12 | Fullscreen = 0 13 | 14 | # Window scale (1=100%, 2=200%, 3=300%, etc.) 15 | WindowScale = 1 16 | 17 | # Use an optimized (but potentially more buggy) SNES PPU implementation 18 | NewRenderer = 1 19 | 20 | # Display the world map with higher resolution 21 | EnhancedMode7 = 1 22 | 23 | # Don't keep the aspect ratio 24 | IgnoreAspectRatio = 1 25 | 26 | # Enable this option to remove the sprite limits per scan line 27 | NoSpriteLimits = 1 28 | 29 | # Use either SDL, SDL-Software, or OpenGL as the output method 30 | # SDL-Software rendering might give better performance on Raspberry pi. 31 | OutputMethod = SDL 32 | 33 | # Set to true to use linear filtering. Gives less crisp pixels. Works with SDL and OpenGL. 34 | LinearFiltering = 0 35 | 36 | # Set a glsl shader. Only supported with the OpenGL output method 37 | # This can be the path to a .glsl or .glslp file 38 | # Get them with: git clone https://github.com/snesrev/glsl-shaders 39 | #Shader = glsl-shaders/hqx/hq4x.glslp 40 | 41 | [Sound] 42 | EnableAudio = 1 43 | 44 | # DSP frequency in samples per second (e.g. 48000, 44100, 32000, 22050, 11025) 45 | AudioFreq = 44100 46 | 47 | # number of separate sound channels (1=mono, 2=stereo) 48 | AudioChannels = 2 49 | 50 | # Audio buffer size in samples (power of 2; e.g., 4096, 2048, 1024) [try 1024 if sound is crackly]. The higher the more lag before you hear sounds. 51 | AudioSamples = 1024 52 | 53 | [KeyMap] 54 | # Change what keyboard keys map to the joypad 55 | # Order: Up, Down, Left, Right, Select, Start, A, B, X, Y, L, R 56 | 57 | # This default is suitable for QWERTY keyboards. 58 | Controls = Up, Down, Left, Right, Right Shift, Return, x, z, s, a, c, v 59 | 60 | # This default is suitable for QWERTZ keyboards. 61 | #Controls = Up, Down, Left, Right, Right Shift, Return, x, y, s, a, c, v 62 | 63 | # This one is suitable for AZERTY keyboards. 64 | #Controls = Up, Down, Left, Right, Right Shift, Return, x, w, s, q, c, v 65 | 66 | CheatLife = w 67 | CheatJump = Ctrl+q 68 | ClearKeyLog = k 69 | StopReplay = l 70 | Fullscreen = Alt+Return 71 | Reset = Ctrl+r 72 | Pause = Shift+p 73 | PauseDimmed = p 74 | Turbo = Tab 75 | ReplayTurbo = t 76 | WindowBigger = Ctrl+Up 77 | WindowSmaller = Ctrl+Down 78 | 79 | VolumeUp = Shift+= 80 | VolumeDown = Shift+- 81 | 82 | Load = F1, F2, F3, F4, F5, F6, F7, F8, F9, F10 83 | Save = Shift+F1,Shift+F2,Shift+F3,Shift+F4,Shift+F5,Shift+F6,Shift+F7,Shift+F8,Shift+F9,Shift+F10 84 | Replay= Ctrl+F1,Ctrl+F2,Ctrl+F3,Ctrl+F4,Ctrl+F5,Ctrl+F6,Ctrl+F7,Ctrl+F8,Ctrl+F9,Ctrl+F10 85 | 86 | LoadRef = 1,2,3,4,5,6,7,8,9,0,-,=,Backspace 87 | ReplayRef = Ctrl+1,Ctrl+2,Ctrl+3,Ctrl+4,Ctrl+5,Ctrl+6,Ctrl+7,Ctrl+8,Ctrl+9,Ctrl+0,Ctrl+-,Ctrl+=,Ctrl+Backspace 88 | 89 | [GamepadMap] 90 | # Any keys used in KeyMap can be used also in this section. 91 | # The shoulder button is called L1/Lb and L2, and the thumbstick button is called L3 92 | Controls = DpadUp, DpadDown, DpadLeft, DpadRight, Back, Start, B, A, Y, X, Lb, Rb 93 | -------------------------------------------------------------------------------- /src/platform/switch/src/switch_impl.c: -------------------------------------------------------------------------------- 1 | #include "switch_impl.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void SwitchImpl_Init() { 8 | appletInitializeGamePlayRecording(); 9 | appletSetGamePlayRecordingState(true); 10 | appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); 11 | socketInitializeDefault(); 12 | #ifdef DEBUG 13 | nxlinkStdio(); 14 | #endif 15 | } 16 | 17 | void SwitchImpl_Exit() { 18 | appletSetGamePlayRecordingState(false); 19 | socketExit(); 20 | } 21 | 22 | void PrintErrorMessageToScreen(const char* str, ...) { 23 | consoleInit(NULL); 24 | 25 | va_list args; 26 | va_start(args, str); 27 | vprintf(str, args); 28 | va_end(args); 29 | 30 | while (appletMainLoop()) { 31 | consoleUpdate(NULL); 32 | } 33 | 34 | consoleExit(NULL); 35 | } 36 | 37 | // Error messages 38 | 39 | void ThrowMissingROM() { 40 | PrintErrorMessageToScreen( 41 | "\x1b[2;2HYou've launched Super Metroid without the rom file." 42 | "\x1b[4;2HPlease relaunch making sure sm.smc exists." 43 | "\x1b[44;2HMade with <3 by snesrev and Lywx" 44 | ); 45 | } -------------------------------------------------------------------------------- /src/platform/switch/src/switch_impl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void SwitchImpl_Init(); 4 | void SwitchImpl_Exit(); 5 | 6 | // Error messages 7 | 8 | void ThrowMissingROM(); -------------------------------------------------------------------------------- /src/platform/win32/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by zelda3.rc 4 | // 5 | #define IDI_ICON1 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /src/platform/win32/sm.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (United States) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 19 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 20 | #pragma code_page(1252) 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "platform\\win32\\resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_ICON1 ICON "platform\\win32\\triforce.ico" 56 | 57 | #endif // English (United States) resources 58 | ///////////////////////////////////////////////////////////////////////////// 59 | 60 | 61 | 62 | #ifndef APSTUDIO_INVOKED 63 | ///////////////////////////////////////////////////////////////////////////// 64 | // 65 | // Generated from the TEXTINCLUDE 3 resource. 66 | // 67 | 68 | 69 | ///////////////////////////////////////////////////////////////////////////// 70 | #endif // not APSTUDIO_INVOKED 71 | 72 | -------------------------------------------------------------------------------- /src/platform/win32/volume_control.c: -------------------------------------------------------------------------------- 1 | #include "volume_control.h" 2 | 3 | #define COBJMACROS 4 | #define CINTERFACE 5 | 6 | #define MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS 0 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static ISimpleAudioVolume *GetSimpleAudioVolume(); 14 | 15 | DEFINE_GUID(IID_IMMDeviceEnumerator, 0XA95664D2, 0X9614, 0X4F35, 0XA7, 0X46, 0XDE, 0X8D, 0XB6, 0X36, 0X17, 0XE6); 16 | DEFINE_GUID(CLSID_MMDeviceEnumerator, 0XBCDE0395, 0XE52F, 0X467C, 0X8E, 0X3D, 0XC4, 0X57, 0X92, 0X91, 0X69, 0X2E); 17 | DEFINE_GUID(IID_IAudioSessionManager, 0X77AA99A0, 0X1BD6, 0X484F, 0X8B, 0XC7, 0X2C, 0X65, 0X4C, 0X9A, 0X9B, 0X6F); 18 | 19 | static void InitializeCom(void) { 20 | static bool com_initialized; 21 | if (!com_initialized) 22 | com_initialized = SUCCEEDED(CoInitialize(NULL)); 23 | } 24 | 25 | int GetApplicationVolume(void) { 26 | ISimpleAudioVolume *simple_audio_volume = GetSimpleAudioVolume(); 27 | if (!simple_audio_volume) 28 | return false; 29 | float volume_level = -1; 30 | HRESULT result = ISimpleAudioVolume_GetMasterVolume(simple_audio_volume, &volume_level); 31 | ISimpleAudioVolume_Release(simple_audio_volume); 32 | return (int)(volume_level * 100); 33 | } 34 | 35 | bool SetApplicationVolume(int volume_level) { 36 | ISimpleAudioVolume *simple_audio_volume = GetSimpleAudioVolume(); 37 | if (!simple_audio_volume) 38 | return false; 39 | HRESULT result = ISimpleAudioVolume_SetMasterVolume(simple_audio_volume, (float)(volume_level / 100.0), NULL); 40 | ISimpleAudioVolume_Release(simple_audio_volume); 41 | return SUCCEEDED(result); 42 | } 43 | 44 | bool SetApplicationMuted(bool muted) { 45 | ISimpleAudioVolume *simple_audio_volume = GetSimpleAudioVolume(); 46 | if (!simple_audio_volume) 47 | return false; 48 | HRESULT result = ISimpleAudioVolume_SetMute(simple_audio_volume, muted, NULL); 49 | ISimpleAudioVolume_Release(simple_audio_volume); 50 | return SUCCEEDED(result); 51 | } 52 | 53 | static ISimpleAudioVolume *GetSimpleAudioVolume(void) { 54 | HRESULT result; 55 | IMMDeviceEnumerator *device_enumerator = NULL; 56 | IMMDevice *device = NULL; 57 | IAudioSessionManager *audio_session_manager = NULL; 58 | ISimpleAudioVolume *simple_audio_volume = NULL; 59 | 60 | InitializeCom(); 61 | 62 | result = CoCreateInstance(&CLSID_MMDeviceEnumerator, 63 | NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &device_enumerator); 64 | if (FAILED(result) || device_enumerator == NULL) 65 | goto done; 66 | 67 | result = IMMDeviceEnumerator_GetDefaultAudioEndpoint(device_enumerator, eRender, eConsole, &device); 68 | if (FAILED(result) || device == NULL) 69 | goto done; 70 | 71 | result = IMMDevice_Activate(device, &IID_IAudioSessionManager, CLSCTX_INPROC_SERVER, NULL, &audio_session_manager); 72 | if (FAILED(result) || audio_session_manager == NULL) 73 | goto done; 74 | result = IAudioSessionManager_GetSimpleAudioVolume(audio_session_manager, &GUID_NULL, 0, &simple_audio_volume); 75 | 76 | done: 77 | if (device_enumerator) IMMDeviceEnumerator_Release(device_enumerator); 78 | if (device) IMMDevice_Release(device); 79 | if (audio_session_manager) IAudioSessionManager_Release(audio_session_manager); 80 | 81 | return simple_audio_volume; 82 | } 83 | -------------------------------------------------------------------------------- /src/platform/win32/volume_control.h: -------------------------------------------------------------------------------- 1 | #ifndef ZELDA3_PLATFORM_WIN32_VOLUME_CONTROL_H_ 2 | #define ZELDA3_PLATFORM_WIN32_VOLUME_CONTROL_H_ 3 | 4 | #include 5 | 6 | #ifndef SYSTEM_VOLUME_MIXER_AVAILABLE 7 | #define SYSTEM_VOLUME_MIXER_AVAILABLE 1 8 | #endif // SYSTEM_VOLUME_MIXER_AVAILABLE 9 | 10 | int GetApplicationVolume(); 11 | bool SetApplicationVolume(int volume_level); 12 | bool SetApplicationMuted(bool muted); 13 | 14 | #endif // ZELDA3_PLATFORM_WIN32_VOLUME_CONTROL_H_ 15 | -------------------------------------------------------------------------------- /src/sm.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | {e1763dbc-4fb3-417f-ad1a-8436411c3b7a} 18 | 19 | 20 | {2b72ed96-9194-4c2c-b1e5-15445f0a9550} 21 | 22 | 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files 53 | 54 | 55 | Source Files 56 | 57 | 58 | Source Files 59 | 60 | 61 | Source Files 62 | 63 | 64 | Source Files 65 | 66 | 67 | Source Files 68 | 69 | 70 | Source Files 71 | 72 | 73 | Source Files 74 | 75 | 76 | Source Files 77 | 78 | 79 | Source Files 80 | 81 | 82 | Source Files 83 | 84 | 85 | Source Files 86 | 87 | 88 | Source Files 89 | 90 | 91 | Source Files 92 | 93 | 94 | Source Files 95 | 96 | 97 | Source Files 98 | 99 | 100 | Source Files 101 | 102 | 103 | Source Files 104 | 105 | 106 | Source Files 107 | 108 | 109 | Source Files 110 | 111 | 112 | Source Files 113 | 114 | 115 | Source Files 116 | 117 | 118 | Source Files 119 | 120 | 121 | Source Files 122 | 123 | 124 | Source Files 125 | 126 | 127 | Source Files 128 | 129 | 130 | Source Files 131 | 132 | 133 | Source Files 134 | 135 | 136 | Source Files 137 | 138 | 139 | Source Files 140 | 141 | 142 | Snes 143 | 144 | 145 | Snes 146 | 147 | 148 | Snes 149 | 150 | 151 | Snes 152 | 153 | 154 | Snes 155 | 156 | 157 | Snes 158 | 159 | 160 | Snes 161 | 162 | 163 | Snes 164 | 165 | 166 | Snes 167 | 168 | 169 | Snes 170 | 171 | 172 | Snes 173 | 174 | 175 | Shader 176 | 177 | 178 | Shader 179 | 180 | 181 | Shader 182 | 183 | 184 | 185 | 186 | Source Files 187 | 188 | 189 | Source Files 190 | 191 | 192 | Source Files 193 | 194 | 195 | Source Files 196 | 197 | 198 | Source Files 199 | 200 | 201 | Source Files 202 | 203 | 204 | Source Files 205 | 206 | 207 | Source Files 208 | 209 | 210 | Source Files 211 | 212 | 213 | Header Files 214 | 215 | 216 | Header Files 217 | 218 | 219 | Header Files 220 | 221 | 222 | Header Files 223 | 224 | 225 | Snes 226 | 227 | 228 | Snes 229 | 230 | 231 | Snes 232 | 233 | 234 | Snes 235 | 236 | 237 | Snes 238 | 239 | 240 | Snes 241 | 242 | 243 | Snes 244 | 245 | 246 | Snes 247 | 248 | 249 | Snes 250 | 251 | 252 | Snes 253 | 254 | 255 | Snes 256 | 257 | 258 | Snes 259 | 260 | 261 | Shader 262 | 263 | 264 | Shader 265 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /src/sm_87.c: -------------------------------------------------------------------------------- 1 | // Animated tiles 2 | #include "ida_types.h" 3 | #include "funcs.h" 4 | #include "variables.h" 5 | 6 | 7 | 8 | 9 | 10 | void EnableAnimtiles(void) { // 0x878000 11 | animtiles_enable_flag |= 0x8000; 12 | } 13 | 14 | void DisableAnimtiles(void) { // 0x87800B 15 | animtiles_enable_flag &= ~0x8000; 16 | } 17 | 18 | void ClearAnimtiles(void) { // 0x878016 19 | for (int i = 10; i >= 0; i -= 2) 20 | animtiles_ids[i >> 1] = 0; 21 | } 22 | 23 | void SpawnAnimtiles(uint16 j) { // 0x878027 24 | uint16 v0 = j; 25 | 26 | uint16 v1 = 10; 27 | while (animtiles_ids[v1 >> 1]) { 28 | v1 -= 2; 29 | if ((v1 & 0x8000) != 0) 30 | return; 31 | } 32 | int v2 = v1 >> 1; 33 | animtiles_ids[v2] = v0; 34 | animtiles_timers[v2] = 0; 35 | const uint16 *v3 = (const uint16 *)RomPtr_87(v0); 36 | animtiles_instr_list_ptrs[v2] = *v3; 37 | animtiles_src_ptr[v2] = 0; 38 | animtiles_sizes[v2] = v3[1]; 39 | animtiles_vram_ptr[v2] = v3[2]; 40 | animtiles_instr_timers[v2] = 1; 41 | } 42 | 43 | void AnimtilesHandler(void) { // 0x878064 44 | if ((animtiles_enable_flag & 0x8000) != 0) { 45 | for (int i = 10; i >= 0; i -= 2) { 46 | animtiles_object_index = i; 47 | if (animtiles_ids[i >> 1]) { 48 | ProcessAnimtilesObject(); 49 | i = animtiles_object_index; 50 | } 51 | } 52 | } 53 | } 54 | 55 | uint16 CallAnimtilesInstr(uint32 ea, uint16 j, uint16 k) { 56 | switch (ea) { 57 | case fnAnimtilesInstr_Delete: return AnimtilesInstr_Delete(j, k); 58 | case fnAnimtilesInstr_Goto: return AnimtilesInstr_Goto(j, k); 59 | case fnAnimtilesInstr_GotoRel: return AnimtilesInstr_GotoRel(j, k); 60 | case fnAnimtilesInstr_DecrementTimerAndGoto: return AnimtilesInstr_DecrementTimerAndGoto(j, k); 61 | case fnAnimtilesInstr_DecrementTimerAndGotoRel: return AnimtilesInstr_DecrementTimerAndGotoRel(j, k); 62 | case fnAnimtilesInstr_SetTimer: return AnimtilesInstr_SetTimer(j, k); 63 | case fnAnimtilesInstr_QueueMusic: return AnimtilesInstr_QueueMusic(j, k); 64 | case fnAnimtilesInstr_QueueSfx1: return AnimtilesInstr_QueueSfx1(j, k); 65 | case fnAnimtilesInstr_QueueSfx2: return AnimtilesInstr_QueueSfx2(j, k); 66 | case fnAnimtilesInstr_QueueSfx3: return AnimtilesInstr_QueueSfx3(j, k); 67 | case fnAnimtilesInstr_GotoIfBossBitSet: return AnimtilesInstr_GotoIfBossBitSet(j, k); 68 | case fnAnimtilesInstr_SetBossBit: return AnimtilesInstr_SetBossBit(j, k); 69 | case fnAnimtilesInstr_GotoIfEventHappened: return AnimtilesInstr_GotoIfEventHappened(j, k); 70 | case fnAnimtilesInstr_SetEventHappened: return AnimtilesInstr_SetEventHappened(j, k); 71 | case fnAnimtilesInstr_WaitUntilAreaBossDead_DoubleRet: return AnimtilesInstr_WaitUntilAreaBossDead_DoubleRet(j, k); 72 | case fnAnimtilesInstr_GotoIfBossBitSetInArea: return AnimtilesInstr_GotoIfBossBitSetInArea(j, k); 73 | case fnAnimtilesInstr_SpawnTourianStatueEyeGlow: return AnimtilesInstr_SpawnTourianStatueEyeGlow(j, k); 74 | case fnAnimtilesInstr_SpawnTourianStatueSoul: return AnimtilesInstr_SpawnTourianStatueSoul(j, k); 75 | case fnAnimtilesInstr_GotoIfTourianStatueBusy: return AnimtilesInstr_GotoIfTourianStatueBusy(j, k); 76 | case fnAnimtilesInstr_TourianStatueSetState: return AnimtilesInstr_TourianStatueSetState(j, k); 77 | case fnAnimtilesInstr_TourianStatueClearState: return AnimtilesInstr_TourianStatueClearState(j, k); 78 | case fnAnimtilesInstr_Clear3PaletteColors: return AnimtilesInstr_Clear3PaletteColors(j, k); 79 | case fnAnimtilesInstr_SpawnPalfxObj: return AnimtilesInstr_SpawnPalfxObj(j, k); 80 | case fnAnimtilesInstr_Write8PaletteColors: return AnimtilesInstr_Write8PaletteColors(j, k); 81 | default: return Unreachable(); 82 | } 83 | } 84 | 85 | void ProcessAnimtilesObject(void) { // 0x878085 86 | uint16 v0 = animtiles_object_index; 87 | int v1 = animtiles_object_index >> 1; 88 | if (animtiles_instr_timers[v1]-- == 1) { 89 | uint16 v3 = animtiles_instr_list_ptrs[v1], v5; 90 | while (1) { 91 | const uint16 *v4 = (const uint16 *)RomPtr_87(v3); 92 | v5 = *v4; 93 | if ((*v4 & 0x8000) == 0) 94 | break; 95 | animtiles_instruction = *v4; 96 | v3 = CallAnimtilesInstr(v5 | 0x870000, v0, v3 + 2); 97 | if (!v3) 98 | return; 99 | } 100 | int v6 = v0 >> 1; 101 | animtiles_instr_timers[v6] = v5; 102 | animtiles_src_ptr[v6] = *((uint16 *)RomPtr_87(v3) + 1); 103 | animtiles_instr_list_ptrs[v6] = v3 + 4; 104 | } 105 | } 106 | 107 | uint16 AnimtilesInstr_Delete(uint16 k, uint16 j) { // 0x8780B2 108 | animtiles_ids[k >> 1] = 0; 109 | return 0; 110 | } 111 | 112 | uint16 AnimtilesInstr_Goto(uint16 k, uint16 j) { // 0x8780B7 113 | return *(uint16 *)RomPtr_87(j); 114 | } 115 | 116 | uint16 AnimtilesInstr_GotoRel(uint16 k, uint16 j) { // 0x8780BC 117 | animtiles_instruction = j; 118 | return j + (int8)*RomPtr_87(j); 119 | } 120 | 121 | uint16 AnimtilesInstr_DecrementTimerAndGoto(uint16 k, uint16 j) { // 0x8780D4 122 | int v2 = k >> 1; 123 | if (animtiles_timers[v2]-- == 1) 124 | return j + 2; 125 | else 126 | return AnimtilesInstr_Goto(k, j); 127 | } 128 | 129 | uint16 AnimtilesInstr_DecrementTimerAndGotoRel(uint16 k, uint16 j) { // 0x8780DC 130 | int v2 = k >> 1; 131 | if (animtiles_timers[v2]-- == 1) 132 | return j + 1; 133 | else 134 | return AnimtilesInstr_GotoRel(k, j); 135 | } 136 | 137 | uint16 AnimtilesInstr_SetTimer(uint16 k, uint16 j) { // 0x8780E3 138 | *((uint8 *)animtiles_timers + k) = *RomPtr_87(j); 139 | return j + 1; 140 | } 141 | 142 | uint16 AnimtilesInstr_QueueMusic(uint16 k, uint16 j) { // 0x8780F0 143 | const uint8 *v2 = RomPtr_87(j); 144 | QueueMusic_Delayed8(*v2); 145 | return j + 1; 146 | } 147 | 148 | uint16 AnimtilesInstr_QueueSfx1(uint16 k, uint16 j) { // 0x8780FC 149 | const uint8 *v2 = RomPtr_87(j); 150 | QueueSfx1_Max6(*v2); 151 | return j + 1; 152 | } 153 | 154 | uint16 AnimtilesInstr_QueueSfx2(uint16 k, uint16 j) { // 0x878108 155 | const uint8 *v2 = RomPtr_87(j); 156 | QueueSfx2_Max6(*v2); 157 | return j + 1; 158 | } 159 | 160 | uint16 AnimtilesInstr_QueueSfx3(uint16 k, uint16 j) { // 0x878114 161 | const uint8 *v2 = RomPtr_87(j); 162 | QueueSfx3_Max6(*v2); 163 | return j + 1; 164 | } 165 | 166 | uint16 AnimtilesInstr_GotoIfBossBitSet(uint16 k, uint16 j) { // 0x878120 167 | const uint8 *v2 = RomPtr_87(j); 168 | uint16 v3 = j + 1; 169 | if (CheckBossBitForCurArea((uint8) *(uint16 *)v2) & 1) 170 | return AnimtilesInstr_Goto(k, v3); 171 | else 172 | return v3 + 2; 173 | } 174 | 175 | uint16 AnimtilesInstr_SetBossBit(uint16 k, uint16 j) { // 0x878133 176 | const uint8 *v2 = RomPtr_87(j); 177 | SetBossBitForCurArea(*v2); 178 | return j + 1; 179 | } 180 | 181 | uint16 AnimtilesInstr_GotoIfEventHappened(uint16 k, uint16 j) { // 0x87813F 182 | const uint16 *v2 = (const uint16 *)RomPtr_87(j); 183 | uint16 v3 = j + 2; 184 | if (CheckEventHappened(*v2) & 1) 185 | return AnimtilesInstr_Goto(k, v3); 186 | else 187 | return v3 + 2; 188 | } 189 | 190 | uint16 AnimtilesInstr_SetEventHappened(uint16 k, uint16 j) { // 0x878150 191 | const uint16 *v2 = (const uint16 *)RomPtr_87(j); 192 | SetEventHappened(*v2); 193 | return j + 2; 194 | } 195 | 196 | void UNUSED_sub_87815A(void) { // 0x87815A 197 | CallSomeSamusCode(0); 198 | } 199 | 200 | void UNUSED_sub_878162(void) { // 0x878162 201 | CallSomeSamusCode(1); 202 | } 203 | 204 | uint16 AnimtilesInstr_WaitUntilAreaBossDead_DoubleRet(uint16 k, uint16 j) { // 0x8781BA 205 | if (!(CheckBossBitForCurArea(1) & 1)) { 206 | animtiles_instr_timers[k >> 1] = 1; 207 | return 0; 208 | } 209 | return j; 210 | } 211 | 212 | uint16 AnimtilesInstr_GotoIfBossBitSetInArea(uint16 k, uint16 j) { // 0x878303 213 | const uint8 *v2 = RomPtr_87(j); 214 | uint16 v3 = j + 2; 215 | if ((*v2 & boss_bits_for_area[v2[1]]) != 0) 216 | return AnimtilesInstr_Goto(k, v3); 217 | else 218 | return v3 + 2; 219 | } 220 | 221 | uint16 AnimtilesInstr_SpawnTourianStatueEyeGlow(uint16 k, uint16 j) { // 0x878320 222 | const uint16 *v2 = (const uint16 *)RomPtr_87(j); 223 | SpawnEprojWithRoomGfx(addr_kEproj_TourianStatueEyeGlow, *v2); 224 | return j + 2; 225 | } 226 | 227 | uint16 AnimtilesInstr_SpawnTourianStatueSoul(uint16 k, uint16 j) { // 0x87832F 228 | const uint16 *v2 = (const uint16 *)RomPtr_87(j); 229 | SpawnEprojWithRoomGfx(addr_kEproj_TourianStatueSoul, *v2); 230 | return j + 2; 231 | } 232 | 233 | uint16 AnimtilesInstr_GotoIfTourianStatueBusy(uint16 k, uint16 j) { // 0x87833E 234 | if ((tourian_entrance_statue_animstate & 0x8000) == 0) 235 | return j + 2; 236 | else 237 | return AnimtilesInstr_Goto(k, j); 238 | } 239 | 240 | uint16 AnimtilesInstr_TourianStatueSetState(uint16 k, uint16 j) { // 0x878349 241 | tourian_entrance_statue_animstate |= *(uint16 *)RomPtr_87(j); 242 | return j + 2; 243 | } 244 | 245 | uint16 AnimtilesInstr_TourianStatueClearState(uint16 k, uint16 j) { // 0x878352 246 | tourian_entrance_statue_animstate &= ~*(uint16 *)RomPtr_87(j); 247 | return j + 2; 248 | } 249 | 250 | uint16 AnimtilesInstr_Clear3PaletteColors(uint16 k, uint16 j) { // 0x87835B 251 | int v2 = *(uint16 *)RomPtr_87(j) >> 1; 252 | palette_buffer[v2] = 0; 253 | palette_buffer[v2 + 1] = 0; 254 | palette_buffer[v2 + 2] = 0; 255 | return j + 2; 256 | } 257 | 258 | uint16 AnimtilesInstr_SpawnPalfxObj(uint16 k, uint16 j) { // 0x878372 259 | const uint16 *v2 = (const uint16 *)RomPtr_87(j); 260 | SpawnPalfxObject(*v2); 261 | return j + 2; 262 | } 263 | 264 | uint16 AnimtilesInstr_Write8PaletteColors(uint16 k, uint16 j) { // 0x87837F 265 | static const uint16 kAnimtilesInstr_Write8PaletteColors[8] = { 0x3800, 0x7f58, 0x6ed5, 0x5a71, 0x49ee, 0x356a, 0x24e7, 0x1083 }; 266 | 267 | uint16 v2 = *(uint16 *)RomPtr_87(j); 268 | for (int i = 0; i != 16; i += 2) { 269 | target_palettes[v2 >> 1] = kAnimtilesInstr_Write8PaletteColors[i >> 1]; 270 | v2 += 2; 271 | } 272 | return j + 2; 273 | } 274 | -------------------------------------------------------------------------------- /src/sm_89.c: -------------------------------------------------------------------------------- 1 | // Item PLM graphics, FX loader 2 | 3 | #include "ida_types.h" 4 | #include "variables.h" 5 | #include "funcs.h" 6 | 7 | 8 | #define g_word_89AA02 ((uint16*)RomFixedPtr(0x89aa02)) 9 | #define kFxTypeTilemapPtrs ((uint16*)RomFixedPtr(0x83abf0)) 10 | #define kAreaPalFxListPointers ((uint16*)RomFixedPtr(0x83ac46)) 11 | #define kAreaAnimtilesListPtrs ((uint16*)RomFixedPtr(0x83ac56)) 12 | #define g_word_89AD5F ((uint16*)RomFixedPtr(0x89ad5f)) 13 | 14 | 15 | 16 | void LoadFxEntry(uint16 v0) { // 0x89AB02 17 | FxDef *FD = get_FxDef(room_layer3_asm_ptr + 16 * (v0 & 7)); 18 | fx_base_y_pos = FD->base_y_pos; 19 | fx_target_y_pos = FD->target_y_pos; 20 | fx_y_vel = FD->y_vel; 21 | fx_timer = FD->timer; 22 | fx_layer_blending_config_a = FD->default_layer_blend; 23 | fx_layer_blending_config_b = FD->layer3_layer_blend; 24 | fx_liquid_options = FD->fx_liquid_options_; 25 | if (FD->palette_blend) { 26 | int v2 = FD->palette_blend >> 1; 27 | palette_buffer[25] = g_word_89AA02[v2]; 28 | palette_buffer[26] = g_word_89AA02[v2 + 1]; 29 | palette_buffer[27] = g_word_89AA02[v2 + 2]; 30 | } else { 31 | palette_buffer[27] = 0; 32 | } 33 | } 34 | 35 | void nullsub_106(void) {} 36 | 37 | static Func_V *const kFxTypeFuncPtrs[23] = { // 0x89AB82 38 | nullsub_106, 39 | FxTypeFunc_2_Lava, 40 | FxTypeFunc_4_Acid, 41 | FxTypeFunc_6_Water, 42 | FxTypeFunc_8_Spores, 43 | FxTypeFunc_A_Rain, 44 | FxTypeFunc_C, 45 | nullsub_106, 46 | nullsub_106, 47 | nullsub_106, 48 | nullsub_106, 49 | nullsub_106, 50 | nullsub_106, 51 | nullsub_106, 52 | nullsub_106, 53 | nullsub_106, 54 | FxTypeFunc_20, 55 | FxTypeFunc_22_ScrollingSky, 56 | FxTypeFunc_24, 57 | FxTypeFunc_26_TourianEntranceStatue, 58 | FxTypeFunc_28_CeresRidley, 59 | FxTypeFunc_CeresElevator, 60 | FxTypeFunc_2C_Haze, 61 | }; 62 | void LoadFXHeader(void) { 63 | uint16 entry_ptr = room_layer3_asm_ptr; 64 | if (entry_ptr == 0) 65 | return; 66 | for(;;) { 67 | uint16 door_ptr = get_FxDef(entry_ptr)->door_ptr; 68 | if (!door_ptr) 69 | break; 70 | if (door_ptr == 0xFFFF) 71 | return; 72 | if (door_ptr == door_def_ptr) 73 | break; 74 | entry_ptr += 16; 75 | } 76 | FxDef *FD = get_FxDef(entry_ptr); 77 | fx_base_y_pos = FD->base_y_pos; 78 | fx_target_y_pos = FD->target_y_pos; 79 | fx_y_vel = FD->y_vel; 80 | fx_timer = FD->timer; 81 | fx_layer_blending_config_a = FD->default_layer_blend; 82 | fx_layer_blending_config_b = FD->layer3_layer_blend; 83 | fx_liquid_options = FD->fx_liquid_options_; 84 | if (FD->palette_blend) { 85 | int v4 = FD->palette_blend >> 1; 86 | target_palettes[25] = g_word_89AA02[v4]; 87 | target_palettes[26] = g_word_89AA02[v4 + 1]; 88 | target_palettes[27] = g_word_89AA02[v4 + 2]; 89 | } else { 90 | target_palettes[27] = 0; 91 | } 92 | fx_type = FD->type; 93 | if (FD->type) { 94 | fx_tilemap_ptr = kFxTypeTilemapPtrs[FD->type >> 1]; 95 | kFxTypeFuncPtrs[FD->type >> 1](); 96 | } 97 | if (FD->palette_fx_bitset) { 98 | int bits = FD->palette_fx_bitset; 99 | const uint8 *v11 = RomPtr_83(kAreaPalFxListPointers[area_index]); 100 | for (int j = 0; j != 16; j += 2, bits >>= 1) { 101 | if (bits & 1) 102 | SpawnPalfxObject(*(uint16 *)&v11[j]); 103 | } 104 | } 105 | if (FD->animtiles_bitset) { 106 | int bits = FD->animtiles_bitset; 107 | const uint8 *v14 = RomPtr_83(kAreaAnimtilesListPtrs[area_index]); 108 | for (int k = 0; k != 16; k += 2, bits >>= 1) { 109 | if (bits & 1) 110 | SpawnAnimtiles(*(uint16 *)&v14[k]); 111 | } 112 | } 113 | } 114 | void RoomCode_CeresElevatorShaft(void) { // 0x89ACC3 115 | int16 v1; 116 | 117 | if ((ceres_status & 0x8000) != 0) { 118 | if ((int16)(112 - samus_x_pos) < 0 119 | && (int16)(144 - samus_x_pos) >= 0 120 | && sign16(samus_y_pos - 128) 121 | && !sign16(samus_y_pos - 75) 122 | && !samus_y_speed 123 | && !samus_y_subspeed 124 | && game_state == kGameState_8_MainGameplay) { 125 | CallSomeSamusCode(2); 126 | screen_fade_delay = 0; 127 | screen_fade_counter = 0; 128 | game_state = 32; 129 | } 130 | -- *(uint16 *)&room_main_asm_variables[2]; 131 | if (*(int16 *)&room_main_asm_variables[2] < 0) { 132 | int v0 = (uint16)(6 * *(uint16 *)room_main_asm_variables) >> 1; 133 | *(uint16 *)&room_main_asm_variables[2] = g_word_89AD5F[v0]; 134 | reg_M7B = g_word_89AD5F[v0 + 1]; 135 | reg_M7C = -reg_M7B; 136 | reg_M7A = g_word_89AD5F[v0 + 2]; 137 | reg_M7D = reg_M7A; 138 | if (*(int16 *)room_main_asm_variables < 0) { 139 | v1 = *(uint16 *)room_main_asm_variables - 1; 140 | if (*(uint16 *)room_main_asm_variables == 0x8001) 141 | v1 = 0; 142 | } else { 143 | v1 = *(uint16 *)room_main_asm_variables + 1; 144 | if (*(uint16 *)room_main_asm_variables == 67) 145 | v1 = -32700; 146 | } 147 | *(uint16 *)room_main_asm_variables = v1; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/sm_8d.c: -------------------------------------------------------------------------------- 1 | // Enemy projectile spritemaps, palette FX objects 2 | #include "ida_types.h" 3 | #include "variables.h" 4 | #include "funcs.h" 5 | 6 | 7 | void CallPalfxFunc(uint32 ea, uint16 k, uint16 j); 8 | PairU16 CallPalInstr(uint32 ea, uint16 k, uint16 j); 9 | void CallPalFxPreInstr(uint32 ea, uint16 k); 10 | 11 | void EnablePaletteFx(void) { // 0x8DC4C2 12 | flag_for_palette_fx_objects |= 0x8000; 13 | } 14 | 15 | void DisablePaletteFx(void) { // 0x8DC4CD 16 | flag_for_palette_fx_objects &= ~0x8000; 17 | } 18 | 19 | void ClearPaletteFXObjects(void) { // 0x8DC4D8 20 | for (int i = 14; i >= 0; i -= 2) 21 | palettefx_ids[i >> 1] = 0; 22 | } 23 | 24 | void SpawnPalfxObject(uint16 j) { // 0x8DC4E9 25 | PalFxDef *PalFxDef; 26 | 27 | uint16 v1 = 14; 28 | while (palettefx_ids[v1 >> 1]) { 29 | v1 -= 2; 30 | if ((v1 & 0x8000) != 0) 31 | return; 32 | } 33 | int v2 = v1 >> 1; 34 | palettefx_ids[v2] = j; 35 | palettefx_color_indexes[v2] = 0; 36 | palettefx_pre_instr[v2] = FUNC16(PalPreInstr_nullsub_129); 37 | palettefx_instr_list_ptrs[v2] = get_PalFxDef(j)->instrs; 38 | palettefx_instr_timers[v2] = 1; 39 | palettefx_timers[v2] = 0; 40 | PalFxDef = get_PalFxDef(j); 41 | CallPalfxFunc(PalFxDef->func | 0x8D0000, -1, v1); 42 | } 43 | 44 | void PalPreInstr_nullsub_129(uint16 k) { // 0x8DC526 45 | } 46 | 47 | void PaletteFxHandler(void) { // 0x8DC527 48 | if ((flag_for_palette_fx_objects & 0x8000) != 0) { 49 | for (int i = 14; i >= 0; i -= 2) { 50 | palettefx_index = i; 51 | if (palettefx_ids[i >> 1]) { 52 | PalFx_ProcessOne(i); 53 | i = palettefx_index; 54 | } 55 | } 56 | } 57 | } 58 | 59 | void PalFx_ProcessOne(uint16 k) { // 0x8DC54A 60 | CallPalFxPreInstr(palettefx_pre_instr[k >> 1] | 0x8D0000, k); 61 | uint16 v1 = palettefx_index; 62 | if (palettefx_instr_timers[v1 >> 1]-- != 1) 63 | return; 64 | uint16 j = palettefx_instr_list_ptrs[v1 >> 1], v6; 65 | while (1) { 66 | const uint16 *p = (const uint16 *)RomPtr_8D(j); 67 | v6 = *p; 68 | if ((*p & 0x8000) == 0) 69 | break; 70 | PairU16 v7 = CallPalInstr(v6 | 0x8D0000, v1, j + 2); 71 | v1 = v7.k; 72 | j = v7.j; 73 | if (!v7.j) 74 | return; 75 | } 76 | int v8 = v1 >> 1; 77 | palettefx_instr_timers[v8] = v6; 78 | uint16 color_idx = palettefx_color_indexes[v8]; 79 | const uint8 *v10; 80 | uint16 v11; 81 | do { 82 | while (1) { 83 | v10 = RomPtr_8D(j); 84 | v11 = GET_WORD(v10 + 2); 85 | if (v11 & 0x8000) 86 | break; 87 | palette_buffer[color_idx >> 1] = v11; 88 | color_idx += 2; 89 | j += 2; 90 | } 91 | PairU16 v12 = CallPalInstr((uint16)v11 | 0x8D0000, color_idx, j); 92 | color_idx = v12.k; 93 | j = v12.j; 94 | } while (j); 95 | } 96 | 97 | PairU16 PalInstr_Wait(uint16 k, uint16 j) { // 0x8DC595 98 | palettefx_instr_list_ptrs[palettefx_index >> 1] = j + 4; 99 | return MakePairU16(0, 0); 100 | } 101 | 102 | PairU16 PalInstr_ColorPlus2(uint16 k, uint16 j) { // 0x8DC599 103 | return MakePairU16(k + 4, j + 2); 104 | } 105 | 106 | PairU16 PalInstr_ColorPlus3(uint16 k, uint16 j) { // 0x8DC5A2 107 | return MakePairU16(k + 6, j + 2); 108 | } 109 | 110 | PairU16 PalInstr_ColorPlus4(uint16 k, uint16 j) { // 0x8DC5AB 111 | return MakePairU16(k + 8, j + 2); 112 | } 113 | 114 | PairU16 PalInstr_ColorPlus8(uint16 k, uint16 j) { // 0x8DC5B4 115 | return MakePairU16(k + 16, j + 2); 116 | } 117 | 118 | PairU16 PalInstr_ColorPlus9(uint16 k, uint16 j) { // 0x8DC5BD 119 | return MakePairU16(k + 18, j + 2); 120 | } 121 | 122 | PairU16 PalInstr_ColorPlus15(uint16 k, uint16 j) { // 0x8DC5C6 123 | return MakePairU16(k + 30, j + 2); 124 | } 125 | 126 | PairU16 PalInstr_Delete(uint16 k, uint16 j) { // 0x8DC5CF 127 | palettefx_ids[k >> 1] = 0; 128 | return MakePairU16(k, 0); 129 | } 130 | 131 | PairU16 PalInstr_SetPreInstr(uint16 k, uint16 j) { // 0x8DC5D4 132 | palettefx_pre_instr[k >> 1] = *(uint16 *)RomPtr_8D(j); 133 | return MakePairU16(k, j + 2); 134 | } 135 | 136 | PairU16 PalInstr_ClearPreInstr(uint16 k, uint16 j) { // 0x8DC5DD 137 | palettefx_pre_instr[k >> 1] = addr_locret_8DC5E3; 138 | return MakePairU16(k, j); 139 | } 140 | 141 | PairU16 PalInstr_Goto(uint16 k, uint16 j) { // 0x8DC61E 142 | uint16 v2 = *(uint16 *)RomPtr_8D(j); 143 | return MakePairU16(k, v2); 144 | } 145 | 146 | PairU16 PalInstr_DecTimerGoto(uint16 k, uint16 j) { // 0x8DC639 147 | PairU16 v4; 148 | 149 | int v2 = k >> 1; 150 | if (palettefx_timers[v2]-- == 1) 151 | return MakePairU16(k, j + 2); 152 | v4 = PalInstr_Goto(k, j); 153 | return MakePairU16(v4.k, v4.j); 154 | } 155 | 156 | PairU16 PalInstr_SetTimer(uint16 k, uint16 j) { // 0x8DC648 157 | *((uint8 *)palettefx_timers + k) = *RomPtr_8D(j); 158 | return MakePairU16(k, j + 1); 159 | } 160 | 161 | PairU16 PalInstr_SetColorIndex(uint16 k, uint16 j) { // 0x8DC655 162 | palettefx_color_indexes[k >> 1] = *(uint16 *)RomPtr_8D(j); 163 | return MakePairU16(k, j + 2); 164 | } 165 | 166 | PairU16 PalInstr_QueueMusic(uint16 k, uint16 j) { // 0x8DC65E 167 | const uint8 *v2 = RomPtr_8D(j); 168 | QueueMusic_Delayed8(*v2); 169 | return MakePairU16(k, j + 1); 170 | } 171 | 172 | PairU16 PalInstr_QueueSfx1(uint16 k, uint16 j) { // 0x8DC66A 173 | const uint16 *v2 = (const uint16 *)RomPtr_8D(j); 174 | QueueSfx1_Max6(*v2); 175 | return MakePairU16(k, j + 1); 176 | } 177 | 178 | PairU16 PalInstr_QueueSfx2(uint16 k, uint16 j) { // 0x8DC673 179 | const uint16 *v2 = (const uint16 *)RomPtr_8D(j); 180 | QueueSfx2_Max6(*v2); 181 | return MakePairU16(k, j + 1); 182 | } 183 | 184 | PairU16 PalInstr_QueueSfx3(uint16 k, uint16 j) { // 0x8DC67C 185 | const uint16 *v2 = (const uint16 *)RomPtr_8D(j); 186 | QueueSfx3_Max6(*v2); 187 | return MakePairU16(k, j + 1); 188 | } 189 | 190 | void PalInit_E1BC(uint16 k, uint16 j) { // 0x8DE204 191 | palettefx_pre_instr[j >> 1] = FUNC16(PalPreInstr_E1BC); 192 | } 193 | 194 | void PalPreInstr_E1BC(uint16 k) { // 0x8DE20B 195 | if (cinematic_function == FUNC16(CinematicFunction_Intro_Page2)) { 196 | int v1 = k >> 1; 197 | palettefx_instr_list_ptrs[v1] = addr_off_8DE192; 198 | palettefx_instr_timers[v1] = 1; 199 | } 200 | } 201 | 202 | void PalPreInstr_CheckEnemy0Health(uint16 k) { // 0x8DE2E0 203 | if (!enemy_data[0].health) 204 | palettefx_ids[k >> 1] = 0; 205 | } 206 | 207 | void PalPreInstr_SamusInHeat(uint16 k) { // 0x8DE379 208 | if ((equipped_items & 0x21) == 0) { 209 | AddToHiLo(&samus_periodic_damage, &samus_periodic_subdamage, 0x4000); 210 | if ((nmi_frame_counter_word & 7) == 0 && samus_health > 0x46) 211 | QueueSfx3_Max6(0x2D); 212 | } 213 | if (samus_in_heat_palfx_index != samus_in_heat_palettefx_prev_index) { 214 | samus_in_heat_palettefx_prev_index = samus_in_heat_palfx_index; 215 | uint16 v2 = 2 * samus_in_heat_palfx_index, v4; 216 | int v3 = k >> 1; 217 | palettefx_instr_timers[v3] = 1; 218 | if ((equipped_items & 0x20) != 0) { 219 | v4 = addr_off_8DE3E0; 220 | } else if ((equipped_items & 1) != 0) { 221 | v4 = addr_off_8DE400; 222 | } else { 223 | v4 = addr_off_8DE420; 224 | } 225 | palettefx_instr_list_ptrs[v3] = *(uint16 *)&RomPtr_8D(v4)[v2]; 226 | } 227 | } 228 | 229 | void PalInit_F761_Norfair1(uint16 k, uint16 j) { // 0x8DE440 230 | uint16 v2; 231 | if ((equipped_items & 0x20) != 0) { 232 | v2 = addr_off_8DE8B6; 233 | } else if ((equipped_items & 1) != 0) { 234 | v2 = addr_off_8DE68A; 235 | } else { 236 | v2 = addr_kPalfxInstrList_E45E; 237 | } 238 | palettefx_instr_list_ptrs[j >> 1] = v2; 239 | } 240 | 241 | void PalPreInstr_SwitchIfYpos(uint16 k) { // 0x8DEC59 242 | if (samus_y_pos < 0x380) { 243 | int v2 = k >> 1; 244 | palettefx_instr_timers[v2] = 1; 245 | palettefx_instr_list_ptrs[v2] = addr_word_8DEB43; 246 | } 247 | } 248 | 249 | void PalPreInstr_SwitchIfYpos2(uint16 k) { // 0x8DED84 250 | if (samus_y_pos < 0x380) { 251 | int v2 = k >> 1; 252 | palettefx_instr_timers[v2] = 1; 253 | palettefx_instr_list_ptrs[v2] = addr_word_8DEC76; 254 | } 255 | } 256 | 257 | void PalPreInstr_DeletePalfxIfMinibossDead(uint16 k) { // 0x8DEEC5 258 | if ((*(uint16 *)&boss_bits_for_area[area_index] & 2) != 0) 259 | palettefx_ids[k >> 1] = 0; 260 | } 261 | 262 | PairU16 PalInstr_SetPalfxIndex(uint16 k, uint16 j) { // 0x8DF1C6 263 | samus_in_heat_palfx_index = *RomPtr_8D(j); 264 | return MakePairU16(k, j + 1); 265 | } 266 | 267 | void PalPreInstr_F621(uint16 k) { // 0x8DF621 268 | if (*(uint16 *)((uint8 *)&flag_for_palette_fx_objects + k)) 269 | palettefx_ids[k >> 1] = 0; 270 | } 271 | 272 | void PalInit_F779_Brinstar8(uint16 k, uint16 j) { // 0x8DF730 273 | if ((*(uint16 *)&boss_bits_for_area[area_index] & 2) != 0) 274 | palettefx_ids[j >> 1] = 0; 275 | } 276 | 277 | void CallPalFxPreInstr(uint32 ea, uint16 k) { 278 | switch (ea) { 279 | case fnPalPreInstr_nullsub_129: PalPreInstr_nullsub_129(k); return; 280 | case fnPalPreInstr_E1BC: PalPreInstr_E1BC(k); return; 281 | case fnPalPreInstr_CheckEnemy0Health: PalPreInstr_CheckEnemy0Health(k); return; 282 | case fnPalPreInstr_SamusInHeat: PalPreInstr_SamusInHeat(k); return; 283 | case fnPalPreInstr_DeletePalfxIfMinibossDead: PalPreInstr_DeletePalfxIfMinibossDead(k); return; 284 | case fnPalPreInstr_SwitchIfYpos: PalPreInstr_SwitchIfYpos(k); return; 285 | case fnPalPreInstr_SwitchIfYpos2: PalPreInstr_SwitchIfYpos2(k); return; 286 | case fnPalPreInstr_F621: PalPreInstr_F621(k); return; 287 | default: Unreachable(); 288 | } 289 | } 290 | 291 | PairU16 CallPalInstr(uint32 ea, uint16 k, uint16 j) { 292 | switch (ea) { 293 | case fnPalInstr_Wait: return PalInstr_Wait(k, j); 294 | case fnPalInstr_ColorPlus2: return PalInstr_ColorPlus2(k, j); 295 | case fnPalInstr_ColorPlus3: return PalInstr_ColorPlus3(k, j); 296 | case fnPalInstr_ColorPlus4: return PalInstr_ColorPlus4(k, j); 297 | case fnPalInstr_ColorPlus8: return PalInstr_ColorPlus8(k, j); 298 | case fnPalInstr_ColorPlus9: return PalInstr_ColorPlus9(k, j); 299 | case fnPalInstr_ColorPlus15: return PalInstr_ColorPlus15(k, j); 300 | case fnPalInstr_Delete: return PalInstr_Delete(k, j); 301 | case fnPalInstr_SetPreInstr: return PalInstr_SetPreInstr(k, j); 302 | case fnPalInstr_ClearPreInstr: return PalInstr_ClearPreInstr(k, j); 303 | case fnPalInstr_Goto: return PalInstr_Goto(k, j); 304 | case fnPalInstr_DecTimerGoto: return PalInstr_DecTimerGoto(k, j); 305 | case fnPalInstr_SetTimer: return PalInstr_SetTimer(k, j); 306 | case fnPalInstr_SetColorIndex: return PalInstr_SetColorIndex(k, j); 307 | case fnPalInstr_QueueMusic: return PalInstr_QueueMusic(k, j); 308 | case fnPalInstr_QueueSfx1: return PalInstr_QueueSfx1(k, j); 309 | case fnPalInstr_QueueSfx2: return PalInstr_QueueSfx2(k, j); 310 | case fnPalInstr_QueueSfx3: return PalInstr_QueueSfx3(k, j); 311 | case fnPalInstr_SetPalfxIndex: return PalInstr_SetPalfxIndex(k, j); 312 | default: Unreachable(); return (PairU16) { 0, 0 }; 313 | } 314 | } 315 | 316 | void CallPalfxFunc(uint32 ea, uint16 k, uint16 j) { 317 | switch (ea) { 318 | case fnnullsub_131: return; 319 | case fnPalInit_E1BC: PalInit_E1BC(k, j); return; 320 | case fnPalInit_F761_Norfair1: PalInit_F761_Norfair1(k, j); return; 321 | case fnPalInit_F779_Brinstar8: PalInit_F779_Brinstar8(k, j); return; 322 | default: Unreachable(); 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/sm_92.c: -------------------------------------------------------------------------------- 1 | // Samus animations 2 | 3 | #include "ida_types.h" 4 | #include "variables.h" 5 | #include "funcs.h" 6 | 7 | #define kSamus_AnimationDefinitionPtrs ((uint16*)RomFixedPtr(0x92d94e)) 8 | #define kSamus_TileDefs_TopHalf ((uint16*)RomFixedPtr(0x92d91e)) 9 | #define kSamus_TileDefs_BottomHalf ((uint16*)RomFixedPtr(0x92d938)) 10 | 11 | void SetSamusTilesDefsForCurAnim(void) { // 0x928000 12 | uint16 v0 = 4 * samus_anim_frame + kSamus_AnimationDefinitionPtrs[samus_pose]; 13 | SamusTileAnimationDefs *AD = get_SamusTileAnimationDefs(v0); 14 | nmi_copy_samus_top_half_src = 7 * AD->top_half_pos + kSamus_TileDefs_TopHalf[AD->top_half_idx]; 15 | LOBYTE(nmi_copy_samus_halves) = 1; 16 | if (AD->bottom_half_idx != 255) { 17 | nmi_copy_samus_bottom_half_src = 7 * AD->bottom_half_pos + kSamus_TileDefs_BottomHalf[AD->bottom_half_idx]; 18 | HIBYTE(nmi_copy_samus_halves) = 1; 19 | } 20 | } 21 | 22 | uint8 PlaySamusFanfare(void) { // 0x92ED24 23 | if (substate) { 24 | if (substate == 5) 25 | PlayRoomMusicTrackAfterAFrames(0x168); 26 | } else { 27 | QueueMusic_DelayedY(1, 0xE); 28 | } 29 | if (sign16(substate - 359)) { 30 | ++substate; 31 | return 0; 32 | } else { 33 | samus_last_different_pose = samus_prev_pose; 34 | *(uint16 *)&samus_last_different_pose_x_dir = *(uint16 *)&samus_prev_pose_x_dir; 35 | samus_prev_pose = samus_pose; 36 | *(uint16 *)&samus_prev_pose_x_dir = *(uint16 *)&samus_pose_x_dir; 37 | substate = 0; 38 | return 1; 39 | } 40 | } 41 | 42 | void Unused_SamusTileViewer(void) { // 0x92ED7A 43 | DrawSamusSpritemap(0x182, 0x40, 0x40); 44 | DrawSamusSpritemap(0x183, 0xC0, 0x40); 45 | DrawSamusSpritemap(0x184, 0x80, 0x60); 46 | DrawSamusSpritemap(0x185, 0x80, 0x50); 47 | } 48 | 49 | void DrawSamusSuitExploding(void) { // 0x92EDBE 50 | uint16 r18 = (samus_pose_x_dir == 4) ? g_word_7E0DE4 + 2085 : g_word_7E0DE4 + 2076; 51 | DrawSamusSpritemap(r18, samus_x_pos, samus_y_pos); 52 | } 53 | -------------------------------------------------------------------------------- /src/sm_93.c: -------------------------------------------------------------------------------- 1 | // Projectiles 2 | #include "ida_types.h" 3 | #include "variables.h" 4 | #include "funcs.h" 5 | 6 | 7 | #define kProjectileData_UnchargedBeams ((uint16*)RomFixedPtr(0x9383c1)) 8 | #define kProjectileData_ChargedBeams ((uint16*)RomFixedPtr(0x9383d9)) 9 | #define kProjectileData_NonBeams ((uint16*)RomFixedPtr(0x9383f1)) 10 | #define kShinesparkEchoSpazer_ProjectileData ((uint16*)RomFixedPtr(0x938403)) 11 | #define kRunInstrForSuperMissile ((uint16*)RomFixedPtr(0x93842b)) 12 | #define g_stru_938691 (*(ProjectileDamagesAndInstrPtr*)RomFixedPtr(0x938691)) 13 | #define g_stru_938679 (*(ProjectileDamagesAndInstrPtr*)RomFixedPtr(0x938679)) 14 | #define kProjInstrList_Explosion (*(ProjectileDamagesAndInstrPtr*)RomFixedPtr(0x938681)) 15 | #define g_off_938413 ((uint16*)RomFixedPtr(0x938413)) 16 | 17 | void InitializeProjectile(uint16 k) { // 0x938000 18 | int v1 = k >> 1; 19 | int r18 = (projectile_dir[v1] & 0xF); 20 | uint16 v2 = projectile_type[v1], v3; 21 | if ((v2 & 0xF00) != 0) { 22 | v3 = kProjectileData_NonBeams[HIBYTE(v2) & 0xF]; 23 | } else if ((v2 & 0x10) != 0) { 24 | v3 = kProjectileData_ChargedBeams[projectile_type[v1] & 0xF]; 25 | } else { 26 | v3 = kProjectileData_UnchargedBeams[projectile_type[v1] & 0xF]; 27 | } 28 | ProjectileDataTable *PD = get_ProjectileDataTable(v3); 29 | if (sign16(PD->damage)) 30 | InvalidInterrupt_Crash(); 31 | projectile_damage[v1] = PD->damage; 32 | uint16 v7 = PD->instr_ptrs[r18]; 33 | projectile_bomb_instruction_ptr[v1] = v7; 34 | const uint8 *v8 = RomPtr_93(v7); 35 | projectile_x_radius[v1] = v8[4]; 36 | projectile_y_radius[v1] = v8[5]; 37 | projectile_bomb_instruction_timers[v1] = 1; 38 | } 39 | 40 | void InitializeInstrForSuperMissile(uint16 v0) { // 0x938071 41 | int v1 = v0 >> 1; 42 | const uint8 *v3 = RomPtr_93(kRunInstrForSuperMissile[HIBYTE(projectile_type[v1]) & 0xF]); 43 | uint16 v4 = GET_WORD(v3); 44 | projectile_damage[v1] = v4; 45 | if (sign16(v4)) 46 | InvalidInterrupt_Crash(); 47 | projectile_bomb_instruction_ptr[v1] = GET_WORD(v3 + 2); 48 | projectile_bomb_instruction_timers[v1] = 1; 49 | } 50 | 51 | void InitializeInstrForMissile(uint16 v0) { // 0x9380A0 52 | int v1 = v0 >> 1; 53 | const uint8 *v3 = RomPtr_93(kProjectileData_NonBeams[HIBYTE(projectile_type[v1]) & 0xF]); 54 | uint16 v4 = GET_WORD(v3); 55 | projectile_damage[v1] = GET_WORD(v3); 56 | if (sign16(v4)) 57 | InvalidInterrupt_Crash(); 58 | projectile_bomb_instruction_ptr[v1] = GET_WORD(v3 + 2); 59 | projectile_bomb_instruction_timers[v1] = 1; 60 | } 61 | 62 | void KillProjectileInner(uint16 k) { // 0x9380CF 63 | int v1 = k >> 1; 64 | if ((projectile_type[v1] & 0xF00) != 0) { 65 | if (!cinematic_function) 66 | QueueSfx2_Max6(7); 67 | uint16 v2 = projectile_type[v1]; 68 | projectile_type[v1] = v2 & 0xF0FF | 0x800; 69 | if ((v2 & 0x200) != 0) { 70 | projectile_bomb_instruction_ptr[v1] = HIWORD(g_stru_938691.damages); 71 | earthquake_type = 20; 72 | earthquake_timer = 30; 73 | } else { 74 | projectile_bomb_instruction_ptr[v1] = *(&g_stru_938679.instr_ptr + 1); 75 | } 76 | if (!sign16(cooldown_timer - 21)) 77 | cooldown_timer = 20; 78 | } else { 79 | projectile_type[v1] = projectile_type[v1] & 0xF0FF | 0x700; 80 | projectile_bomb_instruction_ptr[v1] = HIWORD(g_stru_938679.damages); 81 | QueueSfx2_Max6(0xC); 82 | } 83 | projectile_bomb_instruction_timers[v1] = 1; 84 | projectile_damage[v1] = 8; 85 | } 86 | 87 | void InitializeBombExplosion(uint16 k) { // 0x93814E 88 | int v1 = k >> 1; 89 | projectile_bomb_instruction_ptr[v1] = HIWORD(kProjInstrList_Explosion.damages); 90 | projectile_bomb_instruction_timers[v1] = 1; 91 | } 92 | 93 | void InitializeShinesparkEchoOrSpazerSba(uint16 k) { // 0x938163 94 | int v1 = k >> 1; 95 | int r18 = projectile_dir[v1] & 0xF; 96 | ProjectileDataTable *PD = get_ProjectileDataTable(kShinesparkEchoSpazer_ProjectileData[LOBYTE(projectile_type[v1]) - 34]); 97 | projectile_damage[v1] = PD->damage; 98 | if (sign16(PD->damage)) 99 | InvalidInterrupt_Crash(); 100 | projectile_bomb_instruction_ptr[v1] = PD->instr_ptrs[r18]; 101 | projectile_bomb_instruction_timers[v1] = 1; 102 | } 103 | 104 | void InitializeSbaProjectile(uint16 k) { // 0x9381A4 105 | int v1 = k >> 1; 106 | const uint8 *v2 = RomPtr_93(g_off_938413[projectile_type[v1] & 0xF]); 107 | uint16 v3 = GET_WORD(v2); 108 | projectile_damage[v1] = v3; 109 | if (sign16(v3)) 110 | InvalidInterrupt_Crash(); 111 | projectile_bomb_instruction_ptr[v1] = GET_WORD(v2 + 2); 112 | projectile_bomb_instruction_timers[v1] = 1; 113 | } 114 | 115 | uint16 ProjectileInsts_GetValue(uint16 k) { // 0x9381D1 116 | int ip = projectile_bomb_instruction_ptr[k >> 1]; 117 | int delta = (projectile_bomb_instruction_timers[k >> 1] == 1 && !sign16(get_ProjectileInstr(ip)->timer)) ? 0 : -8; 118 | return get_ProjectileInstr(ip + delta)->field_6; 119 | } 120 | 121 | uint16 CallProj93Instr(uint32 ea, uint16 j, uint16 k) { 122 | switch (ea) { 123 | case fnProj93Instr_Delete: return Proj93Instr_Delete(j, k); 124 | case fnProj93Instr_Goto: return Proj93Instr_Goto(j, k); 125 | default: return Unreachable(); 126 | } 127 | } 128 | 129 | void RunProjectileInstructions(void) { // 0x9381E9 130 | ProjectileInstr *PI; 131 | 132 | uint16 v0 = projectile_index; 133 | int v1 = projectile_index >> 1; 134 | if (projectile_bomb_instruction_timers[v1]-- == 1) { 135 | uint16 v3 = projectile_bomb_instruction_ptr[v1]; 136 | while (1) { 137 | PI = get_ProjectileInstr(v3); 138 | if ((PI->timer & 0x8000) == 0) 139 | break; 140 | v3 = CallProj93Instr(PI->timer | 0x930000, v0, v3 + 2); 141 | if (!v3) 142 | return; 143 | } 144 | projectile_bomb_instruction_timers[v1] = PI->timer; 145 | projectile_spritemap_pointers[v1] = PI->spritemap_ptr; 146 | projectile_x_radius[v1] = PI->x_radius; 147 | projectile_y_radius[v1] = PI->y_radius; 148 | projectile_bomb_instruction_ptr[v1] = v3 + 8; 149 | } 150 | } 151 | 152 | uint16 Proj93Instr_Delete(uint16 k, uint16 j) { // 0x93822F 153 | ClearProjectile(k); 154 | return 0; 155 | } 156 | 157 | uint16 Proj93Instr_Goto(uint16 k, uint16 j) { // 0x938239 158 | return *(uint16 *)RomPtr_93(j); 159 | } 160 | 161 | void DrawPlayerExplosions2(void) { // 0x938254 162 | uint16 v0 = 8; 163 | uint16 r18, r20; 164 | projectile_index = 8; 165 | do { 166 | int v1 = v0 >> 1; 167 | if (!projectile_bomb_instruction_ptr[v1]) 168 | goto LABEL_25; 169 | uint16 v2, v3; 170 | v2 = projectile_type[v1]; 171 | if ((v2 & (kProjectileType_TypeMask | 0x10)) != 0) { 172 | if (!sign16((v2 & 0xF00) - 768)) 173 | goto LABEL_25; 174 | } else if ((v2 & 0xC) != 0) { 175 | if ((v0 & 2) != 0) { 176 | if ((nmi_frame_counter_word & 2) == 0) 177 | goto LABEL_25; 178 | } else if ((nmi_frame_counter_word & 2) != 0) { 179 | goto LABEL_25; 180 | } 181 | } else if ((v0 & 2) != 0) { 182 | if ((nmi_frame_counter_word & 1) != 0) 183 | goto LABEL_25; 184 | } else if ((nmi_frame_counter_word & 1) == 0) { 185 | goto LABEL_25; 186 | } 187 | if ((ceres_status & 0x8000) == 0) { 188 | r20 = projectile_x_pos[v1] - layer1_x_pos; 189 | v3 = projectile_y_pos[v1] - layer1_y_pos; 190 | r18 = v3; 191 | } else { 192 | Point16U pt = CalcExplosion_Mode7(v0); 193 | v3 = pt.y; 194 | r20 = pt.x; 195 | } 196 | if ((v3 & 0xFF00) == 0 && (projectile_spritemap_pointers[v1] & 0x8000) != 0) { 197 | DrawProjectileSpritemap(v0, r20, r18); 198 | } 199 | v0 = projectile_index; 200 | LABEL_25: 201 | v0 -= 2; 202 | projectile_index = v0; 203 | } while ((v0 & 0x8000) == 0); 204 | Samus_DrawShinesparkCrashEchoProjectiles(); 205 | HandleProjectileTrails(); 206 | } 207 | 208 | void sub_9382FD(void) { // 0x9382FD 209 | uint16 v0 = 8; 210 | projectile_index = 8; 211 | do { 212 | int v1 = v0 >> 1; 213 | if (projectile_bomb_instruction_ptr[v1]) { 214 | uint16 r20 = projectile_x_pos[v1] - layer1_x_pos; 215 | uint16 r18 = projectile_y_pos[v1] - 8 - layer1_y_pos; 216 | if ((r18 & 0xFF00) != 0) { 217 | } else if ((projectile_spritemap_pointers[v1] & 0x8000) != 0) { 218 | DrawProjectileSpritemap(v0, r20, r18); 219 | } 220 | v0 = projectile_index; 221 | } 222 | v0 -= 2; 223 | projectile_index = v0; 224 | } while ((v0 & 0x8000) == 0); 225 | HandleProjectileTrails(); 226 | } 227 | 228 | void DrawBombAndProjectileExplosions(void) { // 0x93834D 229 | uint16 v0 = 18, v2; 230 | uint16 r18, r20; 231 | projectile_index = 18; 232 | do { 233 | int v1 = v0 >> 1; 234 | if (!projectile_bomb_instruction_ptr[v1] || (int16)((projectile_type[v1] & 0xF00) - 768) < 0) 235 | goto LABEL_16; 236 | if ((projectile_type[v1] & 0xF00) == 768) { 237 | if (!projectile_variables[v1]) 238 | goto LABEL_16; 239 | LABEL_9:; 240 | uint16 v3 = projectile_x_pos[v1] - layer1_x_pos; 241 | r20 = v3; 242 | if (!sign16(v3 - 304) || sign16(v3 + 48)) 243 | goto LABEL_16; 244 | v2 = projectile_y_pos[v1] - layer1_y_pos; 245 | r18 = v2; 246 | goto LABEL_12; 247 | } 248 | if ((projectile_type[v1] & 0xF00) == 1280 || (ceres_status & 0x8000) == 0) 249 | goto LABEL_9; 250 | CalcExplosion_Mode7(v0); 251 | v2 = r18; 252 | LABEL_12: 253 | if ((v2 & 0xFF00) != 0) 254 | ; 255 | else 256 | DrawProjectileSpritemap(v0, r20, r18); 257 | v0 = projectile_index; 258 | LABEL_16: 259 | v0 -= 2; 260 | projectile_index = v0; 261 | } while ((v0 & 0x8000) == 0); 262 | } 263 | -------------------------------------------------------------------------------- /src/sm_b4.c: -------------------------------------------------------------------------------- 1 | // B4 - Enemy instructions, sets, drop chances, resistances (complete) 2 | #include "ida_types.h" 3 | #include "variables.h" 4 | #include "funcs.h" 5 | #include "enemy_types.h" 6 | 7 | 8 | #define kCreateSprite_Ilists ((uint16*)RomFixedPtr(0xb4bda8)) 9 | 10 | 11 | 12 | 13 | uint16 CreateSpriteAtPos(uint16 x_r18, uint16 y_r20, uint16 ilist_r22, uint16 pal_r24) { // 0xB4BC26 14 | int v0 = 62; 15 | while (sprite_instr_list_ptrs[v0 >> 1]) { 16 | v0 -= 2; 17 | if ((v0 & 0x8000) != 0) 18 | return 0xffff; 19 | } 20 | int v1 = v0 >> 1; 21 | sprite_palettes[v1] = 0; 22 | sprite_x_subpos[v1] = 0; 23 | sprite_y_subpos[v1] = 0; 24 | sprite_disable_flag[v1] = 0; 25 | sprite_x_pos[v1] = x_r18; 26 | sprite_y_pos[v1] = y_r20; 27 | sprite_palettes[v1] = pal_r24; 28 | uint16 v2 = kCreateSprite_Ilists[ilist_r22]; 29 | sprite_instr_list_ptrs[v1] = v2; 30 | sprite_instr_timer[v1] = *(uint16 *)RomPtr_B4(v2); 31 | // R18 = v0; 32 | return v0; 33 | } 34 | 35 | void CallSpriteObjectInstr(uint32 ea) { 36 | switch (ea) { 37 | case fnSpriteObject_Instr_RepeatLast: SpriteObject_Instr_RepeatLast(); return; // 0xb4bcf0 38 | case fnSpriteObject_Instr_Terminate: SpriteObject_Instr_Terminate(); return; // 0xb4bd07 39 | case fnSpriteObject_Instr_Goto: SpriteObject_Instr_Goto(); return; // 0xb4bd12 40 | default: Unreachable(); 41 | } 42 | } 43 | 44 | void HandleSpriteObjects(void) { // 0xB4BC82 45 | uint16 v1; 46 | 47 | if (debug_time_frozen_for_enemies | time_is_frozen_flag) 48 | return; 49 | 50 | sprite_object_index = 62; 51 | do { 52 | int v0; 53 | v0 = sprite_object_index >> 1; 54 | if (!sprite_instr_list_ptrs[v0] || (sprite_disable_flag[v0] & 1) != 0) 55 | continue; 56 | v1 = sprite_instr_timer[v0]; 57 | if (sign16(v1)) { 58 | BREAKLABEL: 59 | CallSpriteObjectInstr(v1 | 0xB40000); 60 | continue; 61 | } 62 | sprite_instr_timer[v0] = v1 - 1; 63 | if (v1 == 1) { 64 | uint16 v3 = sprite_instr_list_ptrs[v0] + 4; 65 | sprite_instr_list_ptrs[v0] = v3; 66 | v1 = *(uint16 *)RomPtr_B4(v3); 67 | if (sign16(v1)) 68 | goto BREAKLABEL; 69 | sprite_instr_timer[sprite_object_index >> 1] = v1; 70 | } 71 | } while (!sign16(sprite_object_index -= 2)); 72 | } 73 | 74 | void SpriteObject_Instr_RepeatLast(void) { // 0xB4BCF0 75 | int v2 = sprite_object_index >> 1; 76 | sprite_instr_list_ptrs[v2] -= 4; 77 | sprite_instr_timer[v2] = 0x7FFF; 78 | } 79 | 80 | void SpriteObject_Instr_Terminate(void) { // 0xB4BD07 81 | sprite_instr_list_ptrs[sprite_object_index >> 1] = 0; 82 | } 83 | 84 | void SpriteObject_Instr_Goto(void) { // 0xB4BD12 85 | uint16 v2 = GET_WORD(RomPtr_B4(sprite_instr_list_ptrs[sprite_object_index >> 1]) + 2); 86 | sprite_instr_list_ptrs[sprite_object_index >> 1] = v2; 87 | sprite_instr_timer[sprite_object_index >> 1] = GET_WORD(RomPtr_B4(v2)); 88 | } 89 | 90 | void DrawSpriteObjects(void) { // 0xB4BD32 91 | for (int i = 62; i >= 0; i -= 2) { 92 | int v1 = i >> 1; 93 | if (sprite_instr_list_ptrs[v1]) { 94 | uint16 x = sprite_x_pos[v1] - layer1_x_pos; 95 | if ((int16)(x + 16) >= 0) { 96 | if (sign16(x - 272)) { 97 | int16 y = sprite_y_pos[v1] - layer1_y_pos; 98 | if (y >= 0) { 99 | if (sign16(y - 272)) { 100 | uint16 r3 = sprite_palettes[v1] & 0xE00; 101 | uint16 r0 = sprite_palettes[v1] & 0x1FF; 102 | const uint8 *v3 = RomPtr_B4(sprite_instr_list_ptrs[v1]); 103 | DrawSpritemapWithBaseTile(0xB4, GET_WORD(v3 + 2), x, y, r3, r0); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | void ClearSpriteObjects(void) { // 0xB4BD97 113 | int v0 = 1022; 114 | do { 115 | sprite_instr_list_ptrs[v0 >> 1] = 0; 116 | v0 -= 2; 117 | } while (v0); 118 | } 119 | -------------------------------------------------------------------------------- /src/sm_cpu_infra.h: -------------------------------------------------------------------------------- 1 | #ifndef SM_CPU_INFRA_H_ 2 | #define SM_CPU_INFRA_H_ 3 | 4 | #include "types.h" 5 | #include "snes/cpu.h" 6 | #include "snes/snes.h" 7 | 8 | typedef struct Snes Snes; 9 | extern Snes *g_snes; 10 | extern bool g_fail; 11 | 12 | typedef struct Snes Snes; 13 | 14 | Snes *SnesInit(const char *filename); 15 | 16 | int RunAsmCode(uint32 pc, uint16 a, uint16 x, uint16 y, int flags); 17 | bool ProcessHook(uint32 v); 18 | 19 | void Call(uint32 addr); 20 | 21 | void RunOneFrameOfGame(); 22 | void ClearUnusedOam(); 23 | 24 | void RunOneFrameOfGame_Both(); 25 | 26 | #endif // SM_CPU_INFRA_H_ -------------------------------------------------------------------------------- /src/sm_rtl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "types.h" 3 | #include 4 | #include 5 | 6 | extern uint8 g_ram[0x20000]; 7 | extern void RtlApuWrite(uint32 adr, uint8 val); 8 | extern int snes_frame_counter; 9 | 10 | extern uint8 *g_sram; 11 | extern const uint8 *g_rom; 12 | extern bool g_use_my_apu_code; 13 | #define LONGPTR(t) {(t) & 0xffff, (t) >> 16} 14 | extern bool g_debug_flag; 15 | 16 | typedef void HandlerFunc(void); 17 | typedef uint8 Func_V_A(void); 18 | typedef void Func_V(void); 19 | typedef CoroutineRet Func_V_Coroutine(void); 20 | typedef uint8 Func_U8(void); 21 | typedef void Func_Y_V(uint16 j); 22 | typedef uint16 Func_Y_Y(uint16 j); 23 | typedef void FuncXY_V(uint16 k, uint16 j); 24 | typedef PairU16 Func_Y_To_PairU16(uint16 j); 25 | 26 | struct LongPtr; 27 | void mov24(LongPtr *dst, uint32 src); 28 | uint32 Load24(const LongPtr *src); 29 | void MemCpy(void *dst, const void *src, int size); 30 | void Call(uint32 addr); 31 | bool Unreachable(); 32 | 33 | #define INSTR_RETURN_ADDR(x) ((const uint16*)(uintptr_t)(x)) 34 | #define INSTR_INCR_BYTES(x, n) ((const uint16*)((uintptr_t)(x) + n)) 35 | #define INSTR_ADDR_TO_PTR(k, jp) ((uint8*)(jp) - (RomBankBase(gEnemyData(k)->bank))) 36 | 37 | #define INSTRB_RETURN_ADDR(x) ((const uint8*)(uintptr_t)(x)) 38 | 39 | #if defined(_DEBUG) 40 | // Gives better warning messages but non inlined on tcc 41 | static inline uint16 GET_WORD(const uint8 *p) { return *(uint16 *)(p); } 42 | static inline const uint8 *RomFixedPtr(uint32_t addr) { return &g_rom[(((addr >> 16) << 15) | (addr & 0x7fff)) & 0x3fffff]; } 43 | #else 44 | #define GET_WORD(p) (*(uint16*)(p)) 45 | #define RomFixedPtr(addr) (&g_rom[(((addr >> 16) << 15) | (addr & 0x7fff)) & 0x3fffff]) 46 | #endif 47 | 48 | #define GET_BYTE(p) (*(uint8*)(p)) 49 | 50 | const uint8 *RomPtr(uint32_t addr); 51 | static inline uint8 *RomPtr_RAM(uint16_t addr) { assert(addr < 0x2000); return g_ram + addr; } 52 | static inline const uint8 *RomPtr_80(uint16_t addr) { return RomPtr(0x800000 | addr); } 53 | static inline const uint8 *RomPtr_81(uint16_t addr) { return RomPtr(0x810000 | addr); } 54 | static inline const uint8 *RomPtr_82(uint16_t addr) { return RomPtr(0x820000 | addr); } 55 | static inline const uint8 *RomPtr_83(uint16_t addr) { return RomPtr(0x830000 | addr); } 56 | static inline const uint8 *RomPtr_84(uint16_t addr) { return RomPtr(0x840000 | addr); } 57 | static inline const uint8 *RomPtr_85(uint16_t addr) { return RomPtr(0x850000 | addr); } 58 | static inline const uint8 *RomPtr_86(uint16_t addr) { return RomPtr(0x860000 | addr); } 59 | static inline const uint8 *RomPtr_87(uint16_t addr) { return RomPtr(0x870000 | addr); } 60 | static inline const uint8 *RomPtr_88(uint16_t addr) { return RomPtr(0x880000 | addr); } 61 | static inline const uint8 *RomPtr_89(uint16_t addr) { return RomPtr(0x890000 | addr); } 62 | static inline const uint8 *RomPtr_8A(uint16_t addr) { return RomPtr(0x8a0000 | addr); } 63 | static inline const uint8 *RomPtr_8B(uint16_t addr) { return RomPtr(0x8b0000 | addr); } 64 | static inline const uint8 *RomPtr_8C(uint16_t addr) { return RomPtr(0x8c0000 | addr); } 65 | static inline const uint8 *RomPtr_8D(uint16_t addr) { return RomPtr(0x8d0000 | addr); } 66 | static inline const uint8 *RomPtr_8E(uint16_t addr) { return RomPtr(0x8e0000 | addr); } 67 | static inline const uint8 *RomPtr_8F(uint16_t addr) { return RomPtr(0x8f0000 | addr); } 68 | static inline const uint8 *RomPtr_90(uint16_t addr) { return RomPtr(0x900000 | addr); } 69 | static inline const uint8 *RomPtr_91(uint16_t addr) { return RomPtr(0x910000 | addr); } 70 | static inline const uint8 *RomPtr_92(uint16_t addr) { return RomPtr(0x920000 | addr); } 71 | static inline const uint8 *RomPtr_93(uint16_t addr) { return RomPtr(0x930000 | addr); } 72 | static inline const uint8 *RomPtr_94(uint16_t addr) { return RomPtr(0x940000 | addr); } 73 | static inline const uint8 *RomPtr_9B(uint16_t addr) { return RomPtr(0x9b0000 | addr); } 74 | static inline const uint8 *RomPtr_A0(uint16_t addr) { return RomPtr(0xa00000 | addr); } 75 | static inline const uint8 *RomPtr_A1(uint16_t addr) { return RomPtr(0xa10000 | addr); } 76 | static inline const uint8 *RomPtr_A2(uint16_t addr) { return RomPtr(0xa20000 | addr); } 77 | static inline const uint8 *RomPtr_A3(uint16_t addr) { return RomPtr(0xa30000 | addr); } 78 | static inline const uint8 *RomPtr_A4(uint16_t addr) { return RomPtr(0xa40000 | addr); } 79 | static inline const uint8 *RomPtr_A5(uint16_t addr) { return RomPtr(0xa50000 | addr); } 80 | static inline const uint8 *RomPtr_A6(uint16_t addr) { return RomPtr(0xa60000 | addr); } 81 | static inline const uint8 *RomPtr_A7(uint16_t addr) { return RomPtr(0xa70000 | addr); } 82 | static inline const uint8 *RomPtr_A8(uint16_t addr) { return RomPtr(0xa80000 | addr); } 83 | static inline const uint8 *RomPtr_A9(uint16_t addr) { return RomPtr(0xa90000 | addr); } 84 | static inline const uint8 *RomPtr_AD(uint16_t addr) { return RomPtr(0xad0000 | addr); } 85 | static inline const uint8 *RomPtr_B3(uint16_t addr) { return RomPtr(0xb30000 | addr); } 86 | static inline const uint8 *RomPtr_B4(uint16_t addr) { return RomPtr(0xb40000 | addr); } 87 | static inline const uint8 *RomPtr_B7(uint16_t addr) { return RomPtr(0xb70000 | addr); } 88 | static inline const uint8 *RomPtrWithBank(uint8 bank, uint16_t addr) { return RomPtr((bank << 16) | addr); } 89 | static inline const uint8 *RomBankBase(uint8 bank) { return RomPtr((bank << 16) + 0x8000) - 0x8000; } 90 | 91 | void WriteReg(uint16 reg, uint8 value); 92 | void WriteRegWord(uint16 reg, uint16 value); 93 | uint16 ReadRegWord(uint16 reg); 94 | uint8 ReadReg(uint16 reg); 95 | 96 | typedef void RunFrameFunc(uint16 input, int run_what); 97 | typedef void SyncAllFunc(); 98 | 99 | void RtlReset(int mode); 100 | void RtlSetupEmuCallbacks(uint8 *emu_ram, RunFrameFunc *func, SyncAllFunc *sync_all); 101 | void RtlClearKeyLog(); 102 | void RtlStopReplay(); 103 | 104 | enum { 105 | kSaveLoad_Save = 1, 106 | kSaveLoad_Load = 2, 107 | kSaveLoad_Replay = 3, 108 | }; 109 | 110 | void RtlSaveLoad(int cmd, int slot); 111 | void RtlCheat(char c); 112 | void RtlApuLock(); 113 | void RtlApuUnlock(); 114 | void RtlApuUpload(const uint8 *p); 115 | void RtlRenderAudio(int16 *audio_buffer, int samples, int channels); 116 | void RtlPushApuState(); 117 | bool RtlRunFrame(int inputs); 118 | void RtlReadSram(); 119 | void RtlWriteSram(); 120 | void RtlSaveSnapshot(const char *filename, bool saving_with_bug); 121 | void RtlUpdateSnesPatchForBugfix(); 122 | extern uint16 currently_installed_bug_fix_counter; 123 | 124 | uint16 Mult8x8(uint8 a, uint8 b); 125 | uint16 SnesDivide(uint16 a, uint8 b); 126 | uint16 SnesModulus(uint16 a, uint8 b); 127 | 128 | typedef struct SpcPlayer SpcPlayer; 129 | extern SpcPlayer *g_spc_player; 130 | 131 | enum { 132 | kJoypadL_A = 0x80, 133 | kJoypadL_X = 0x40, 134 | kJoypadL_L = 0x20, 135 | kJoypadL_R = 0x10, 136 | 137 | kJoypadH_B = 0x80, 138 | kJoypadH_Y = 0x40, 139 | kJoypadH_Select = 0x20, 140 | kJoypadH_Start = 0x10, 141 | 142 | kJoypadH_Up = 0x8, 143 | kJoypadH_Down = 0x4, 144 | kJoypadH_Left = 0x2, 145 | kJoypadH_Right = 0x1, 146 | 147 | kJoypadH_AnyDir = 0xf, 148 | }; 149 | 150 | struct OamEnt; 151 | struct VramWriteEntry; 152 | 153 | 154 | PairU16 MakePairU16(uint16 k, uint16 j); 155 | 156 | #define kPoseParams ((SamusPoseParams*)RomFixedPtr(0x91b629)) 157 | #define kAtmosphericGraphicAnimationTimers ((uint16*)RomFixedPtr(0x908b93)) 158 | #define kAtmosphericTypeNumFrames ((uint16*)RomFixedPtr(0x908bef)) 159 | #define g_off_908BFF ((uint16*)RomFixedPtr(0x908bff)) 160 | #define g_stru_90A83A ((DisableMinimapAndMarkBossRoomAsExploredEnt*)RomFixedPtr(0x90a83a)) 161 | #define kPlayerPoseToPtr ((uint16*)RomFixedPtr(0x90c7df)) 162 | #define kDrawArmCannon_Tab2 ((uint16*)RomFixedPtr(0x90c7a5)) 163 | extern const int16 kSinCosTable8bit_Sext[320]; 164 | #define kPoseTransitionTable ((uint16*)RomFixedPtr(0x919ee2)) 165 | #define kDemoSetDefPtrs ((uint16*)RomFixedPtr(0x918885)) 166 | #define kSpeedBoostToCtr ((uint16*)RomFixedPtr(0x91b61f)) 167 | #define kSpeedBoostToAnimFramePtr ((uint16 *)RomFixedPtr(0x91B5DE)) 168 | #define kSamusPoseToBaseSpritemapIndexTop ((uint16*)RomFixedPtr(0x929263)) 169 | #define kSamusPoseToBaseSpritemapIndexBottom ((uint16*)RomFixedPtr(0x92945d)) 170 | #define kSamusAnimationDelayData ((uint16*)RomFixedPtr(0x91b010)) 171 | #define kCommonEnemySpeeds_Linear ((uint16*)RomFixedPtr(0xa28187)) 172 | #define kCommonEnemySpeeds_Quadratic ((uint16*)RomFixedPtr(0xa2838f)) 173 | #define kCommonEnemySpeeds_Quadratic32 ((uint32*)RomFixedPtr(0xa0cbc7)) 174 | #define kSine16bit ((uint16*)RomFixedPtr(0xa0b1c3)) 175 | #define kTanTable ((uint16*)RomFixedPtr(0x91c9d4)) 176 | 177 | void CallEnemyAi(uint32 ea); 178 | void CallEnemyPreInstr(uint32 ea); 179 | const uint16 *CallEnemyInstr(uint32 ea, uint16 k, const uint16 *jp); 180 | 181 | void CalculateBlockContainingPixelPos(uint16 xpos, uint16 ypos); 182 | 183 | /* 148 */ 184 | typedef enum SnesRegs { 185 | INIDISP = 0x2100, 186 | OBSEL = 0x2101, 187 | OAMADDL = 0x2102, 188 | OAMADDH = 0x2103, 189 | OAMDATA = 0x2104, 190 | BGMODE = 0x2105, 191 | MOSAIC = 0x2106, 192 | BG1SC = 0x2107, 193 | BG2SC = 0x2108, 194 | BG3SC = 0x2109, 195 | BG4SC = 0x210A, 196 | BG12NBA = 0x210B, 197 | BG34NBA = 0x210C, 198 | BG1HOFS = 0x210D, 199 | BG1VOFS = 0x210E, 200 | BG2HOFS = 0x210F, 201 | BG2VOFS = 0x2110, 202 | BG3HOFS = 0x2111, 203 | BG3VOFS = 0x2112, 204 | BG4HOFS = 0x2113, 205 | BG4VOFS = 0x2114, 206 | VMAIN = 0x2115, 207 | VMADDL = 0x2116, 208 | VMADDH = 0x2117, 209 | VMDATAL = 0x2118, 210 | VMDATAH = 0x2119, 211 | M7SEL = 0x211A, 212 | M7A = 0x211B, 213 | M7B = 0x211C, 214 | M7C = 0x211D, 215 | M7D = 0x211E, 216 | M7X = 0x211F, 217 | M7Y = 0x2120, 218 | CGADD = 0x2121, 219 | CGDATA = 0x2122, 220 | W12SEL = 0x2123, 221 | W34SEL = 0x2124, 222 | WOBJSEL = 0x2125, 223 | WH0 = 0x2126, 224 | WH1 = 0x2127, 225 | WH2 = 0x2128, 226 | WH3 = 0x2129, 227 | WBGLOG = 0x212A, 228 | WOBJLOG = 0x212B, 229 | TM = 0x212C, 230 | TS = 0x212D, 231 | TMW = 0x212E, 232 | TSW = 0x212F, 233 | CGWSEL = 0x2130, 234 | CGADSUB = 0x2131, 235 | COLDATA = 0x2132, 236 | SETINI = 0x2133, 237 | MPYL = 0x2134, 238 | MPYM = 0x2135, 239 | MPYH = 0x2136, 240 | SLHV = 0x2137, 241 | RDOAM = 0x2138, 242 | RDVRAML = 0x2139, 243 | RDVRAMH = 0x213A, 244 | RDCGRAM = 0x213B, 245 | OPHCT = 0x213C, 246 | OPVCT = 0x213D, 247 | STAT77 = 0x213E, 248 | STAT78 = 0x213F, 249 | APUI00 = 0x2140, 250 | APUI01 = 0x2141, 251 | APUI02 = 0x2142, 252 | APUI03 = 0x2143, 253 | WMDATA = 0x2180, 254 | WMADDL = 0x2181, 255 | WMADDM = 0x2182, 256 | WMADDH = 0x2183, 257 | JOYA = 0x4016, 258 | JOYB = 0x4017, 259 | NMITIMEN = 0x4200, 260 | WRIO = 0x4201, 261 | WRMPYA = 0x4202, 262 | WRMPYB = 0x4203, 263 | WRDIVL = 0x4204, 264 | WRDIVH = 0x4205, 265 | WRDIVB = 0x4206, 266 | HTIMEL = 0x4207, 267 | HTIMEH = 0x4208, 268 | VTIMEL = 0x4209, 269 | VTIMEH = 0x420A, 270 | MDMAEN = 0x420B, 271 | HDMAEN = 0x420C, 272 | MEMSEL = 0x420D, 273 | RDNMI = 0x4210, 274 | TIMEUP = 0x4211, 275 | HVBJOY = 0x4212, 276 | RDIO = 0x4213, 277 | RDDIVL = 0x4214, 278 | RDDIVH = 0x4215, 279 | RDMPYL = 0x4216, 280 | RDMPYH = 0x4217, 281 | JOY1L = 0x4218, 282 | JOY1H = 0x4219, 283 | JOY2L = 0x421A, 284 | JOY2H = 0x421B, 285 | JOY3L = 0x421C, 286 | JOY3H = 0x421D, 287 | JOY4L = 0x421E, 288 | JOY4H = 0x421F, 289 | DMAP0 = 0x4300, 290 | BBAD0 = 0x4301, 291 | A1T0L = 0x4302, 292 | A1T0H = 0x4303, 293 | A1B0 = 0x4304, 294 | DAS0L = 0x4305, 295 | DAS0H = 0x4306, 296 | DAS00 = 0x4307, 297 | A2A0L = 0x4308, 298 | A2A0H = 0x4309, 299 | NTRL0 = 0x430A, 300 | UNUSED0 = 0x430B, 301 | MIRR0 = 0x430F, 302 | DMAP1 = 0x4310, 303 | BBAD1 = 0x4311, 304 | A1T1L = 0x4312, 305 | A1T1H = 0x4313, 306 | A1B1 = 0x4314, 307 | DAS1L = 0x4315, 308 | DAS1H = 0x4316, 309 | DAS10 = 0x4317, 310 | A2A1L = 0x4318, 311 | A2A1H = 0x4319, 312 | NTRL1 = 0x431A, 313 | UNUSED1 = 0x431B, 314 | MIRR1 = 0x431F, 315 | DMAP2 = 0x4320, 316 | BBAD2 = 0x4321, 317 | A1T2L = 0x4322, 318 | A1T2H = 0x4323, 319 | A1B2 = 0x4324, 320 | DAS2L = 0x4325, 321 | DAS2H = 0x4326, 322 | DAS20 = 0x4327, 323 | A2A2L = 0x4328, 324 | A2A2H = 0x4329, 325 | NTRL2 = 0x432A, 326 | UNUSED2 = 0x432B, 327 | MIRR2 = 0x432F, 328 | DMAP3 = 0x4330, 329 | BBAD3 = 0x4331, 330 | A1T3L = 0x4332, 331 | A1T3H = 0x4333, 332 | A1B3 = 0x4334, 333 | DAS3L = 0x4335, 334 | DAS3H = 0x4336, 335 | DAS30 = 0x4337, 336 | A2A3L = 0x4338, 337 | A2A3H = 0x4339, 338 | NTRL3 = 0x433A, 339 | UNUSED3 = 0x433B, 340 | MIRR3 = 0x433F, 341 | DMAP4 = 0x4340, 342 | BBAD4 = 0x4341, 343 | A1T4L = 0x4342, 344 | A1T4H = 0x4343, 345 | A1B4 = 0x4344, 346 | DAS4L = 0x4345, 347 | DAS4H = 0x4346, 348 | DAS40 = 0x4347, 349 | A2A4L = 0x4348, 350 | A2A4H = 0x4349, 351 | NTRL4 = 0x434A, 352 | UNUSED4 = 0x434B, 353 | MIRR4 = 0x434F, 354 | DMAP5 = 0x4350, 355 | BBAD5 = 0x4351, 356 | A1T5L = 0x4352, 357 | A1T5H = 0x4353, 358 | A1B5 = 0x4354, 359 | DAS5L = 0x4355, 360 | DAS5H = 0x4356, 361 | DAS50 = 0x4357, 362 | A2A5L = 0x4358, 363 | A2A5H = 0x4359, 364 | NTRL5 = 0x435A, 365 | UNUSED5 = 0x435B, 366 | MIRR5 = 0x435F, 367 | DMAP6 = 0x4360, 368 | BBAD6 = 0x4361, 369 | A1T6L = 0x4362, 370 | A1T6H = 0x4363, 371 | A1B6 = 0x4364, 372 | DAS6L = 0x4365, 373 | DAS6H = 0x4366, 374 | DAS60 = 0x4367, 375 | A2A6L = 0x4368, 376 | A2A6H = 0x4369, 377 | NTRL6 = 0x436A, 378 | UNUSED6 = 0x436B, 379 | MIRR6 = 0x436F, 380 | DMAP7 = 0x4370, 381 | BBAD7 = 0x4371, 382 | A1T7L = 0x4372, 383 | A1T7H = 0x4373, 384 | A1B7 = 0x4374, 385 | DAS7L = 0x4375, 386 | DAS7H = 0x4376, 387 | DAS70 = 0x4377, 388 | A2A7L = 0x4378, 389 | A2A7H = 0x4379, 390 | NTRL7 = 0x437A, 391 | UNUSED7 = 0x437B, 392 | MIRR7 = 0x437F, 393 | } SnesRegs; 394 | -------------------------------------------------------------------------------- /src/snes/apu.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #include "apu.h" 11 | #include "snes.h" 12 | #include "spc.h" 13 | #include "dsp.h" 14 | #include "../tracing.h" 15 | 16 | static const uint8_t bootRom[0x40] = { 17 | 0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa, 0xf4, 0x8f, 0xbb, 0xf5, 0x78, 18 | 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19, 0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 19 | 0xcb, 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e, 0xf4, 0x10, 0xeb, 0xba, 20 | 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4, 0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff 21 | }; 22 | 23 | Apu* apu_init(void) { 24 | Apu* apu = malloc(sizeof(Apu)); 25 | apu->spc = spc_init(apu); 26 | apu->dsp = dsp_init(apu->ram); 27 | return apu; 28 | } 29 | 30 | void apu_free(Apu* apu) { 31 | spc_free(apu->spc); 32 | dsp_free(apu->dsp); 33 | free(apu); 34 | } 35 | 36 | void apu_reset(Apu* apu) { 37 | apu->romReadable = true; // before resetting spc, because it reads reset vector from it 38 | spc_reset(apu->spc); 39 | dsp_reset(apu->dsp); 40 | memset(apu->ram, 0, sizeof(apu->ram)); 41 | apu->dspAdr = 0; 42 | apu->cycles = 0; 43 | memset(apu->inPorts, 0, sizeof(apu->inPorts)); 44 | memset(apu->outPorts, 0, sizeof(apu->outPorts)); 45 | for(int i = 0; i < 3; i++) { 46 | apu->timer[i].cycles = 0; 47 | apu->timer[i].divider = 0; 48 | apu->timer[i].target = 0; 49 | apu->timer[i].counter = 0; 50 | apu->timer[i].enabled = false; 51 | } 52 | apu->cpuCyclesLeft = 7; 53 | apu->hist.count = 0; 54 | } 55 | 56 | void apu_saveload(Apu *apu, SaveLoadFunc *func, void *ctx) { 57 | func(ctx, apu->ram, offsetof(Apu, pad) + 6 - offsetof(Apu, ram)); 58 | dsp_saveload(apu->dsp, func, ctx); 59 | spc_saveload(apu->spc, func, ctx); 60 | } 61 | 62 | bool g_debug_apu_cycles; 63 | 64 | void apu_cycle(Apu* apu) { 65 | if(apu->cpuCyclesLeft == 0) { 66 | if (g_debug_apu_cycles) { 67 | char line[80]; 68 | getProcessorStateSpc(apu, line); 69 | puts(line); 70 | } 71 | apu->cpuCyclesLeft = spc_runOpcode(apu->spc); 72 | } 73 | apu->cpuCyclesLeft--; 74 | 75 | if((apu->cycles & 0x1f) == 0) { 76 | // every 32 cycles 77 | dsp_cycle(apu->dsp); 78 | } 79 | 80 | // handle timers 81 | for(int i = 0; i < 3; i++) { 82 | if(apu->timer[i].cycles == 0) { 83 | apu->timer[i].cycles = i == 2 ? 16 : 128; 84 | if(apu->timer[i].enabled) { 85 | apu->timer[i].divider++; 86 | if(apu->timer[i].divider == apu->timer[i].target) { 87 | apu->timer[i].divider = 0; 88 | apu->timer[i].counter++; 89 | apu->timer[i].counter &= 0xf; 90 | } 91 | } 92 | } 93 | apu->timer[i].cycles--; 94 | } 95 | 96 | apu->cycles++; 97 | } 98 | 99 | uint8_t apu_cpuRead(Apu* apu, uint16_t adr) { 100 | switch(adr) { 101 | case 0xf0: 102 | case 0xf1: 103 | case 0xfa: 104 | case 0xfb: 105 | case 0xfc: { 106 | return 0; 107 | } 108 | case 0xf2: { 109 | return apu->dspAdr; 110 | } 111 | case 0xf3: { 112 | return dsp_read(apu->dsp, apu->dspAdr & 0x7f); 113 | } 114 | case 0xf4: 115 | case 0xf5: 116 | case 0xf6: 117 | case 0xf7: 118 | case 0xf8: 119 | case 0xf9: { 120 | return apu->inPorts[adr - 0xf4]; 121 | } 122 | case 0xfd: 123 | case 0xfe: 124 | case 0xff: { 125 | uint8_t ret = apu->timer[adr - 0xfd].counter; 126 | apu->timer[adr - 0xfd].counter = 0; 127 | return ret; 128 | } 129 | } 130 | if(apu->romReadable && adr >= 0xffc0) { 131 | return bootRom[adr - 0xffc0]; 132 | } 133 | return apu->ram[adr]; 134 | } 135 | 136 | void apu_cpuWrite(Apu* apu, uint16_t adr, uint8_t val) { 137 | switch(adr) { 138 | case 0xf0: { 139 | break; // test register 140 | } 141 | case 0xf1: { 142 | for(int i = 0; i < 3; i++) { 143 | if(!apu->timer[i].enabled && (val & (1 << i))) { 144 | apu->timer[i].divider = 0; 145 | apu->timer[i].counter = 0; 146 | } 147 | apu->timer[i].enabled = val & (1 << i); 148 | } 149 | if(val & 0x10) { 150 | apu->inPorts[0] = 0; 151 | apu->inPorts[1] = 0; 152 | } 153 | if(val & 0x20) { 154 | apu->inPorts[2] = 0; 155 | apu->inPorts[3] = 0; 156 | } 157 | apu->romReadable = val & 0x80; 158 | break; 159 | } 160 | case 0xf2: { 161 | apu->dspAdr = val; 162 | break; 163 | } 164 | case 0xf3: { 165 | int i = apu->hist.count; 166 | if (i != 256) { 167 | apu->hist.count = i + 1; 168 | apu->hist.addr[i] = (uint8_t)apu->dspAdr; 169 | apu->hist.val[i] = val; 170 | } 171 | if(apu->dspAdr < 0x80) dsp_write(apu->dsp, apu->dspAdr, val); 172 | break; 173 | } 174 | case 0xf4: 175 | case 0xf5: 176 | case 0xf6: 177 | case 0xf7: { 178 | apu->outPorts[adr - 0xf4] = val; 179 | break; 180 | } 181 | case 0xf8: 182 | case 0xf9: { 183 | apu->inPorts[adr - 0xf4] = val; 184 | break; 185 | } 186 | case 0xfa: 187 | case 0xfb: 188 | case 0xfc: { 189 | apu->timer[adr - 0xfa].target = val; 190 | break; 191 | } 192 | } 193 | apu->ram[adr] = val; 194 | } 195 | -------------------------------------------------------------------------------- /src/snes/apu.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef APU_H 3 | #define APU_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct Apu Apu; 12 | 13 | #include "snes.h" 14 | #include "spc.h" 15 | #include "dsp.h" 16 | 17 | typedef struct Timer { 18 | uint8_t cycles; 19 | uint8_t divider; 20 | uint8_t target; 21 | uint8_t counter; 22 | bool enabled; 23 | } Timer; 24 | 25 | struct Apu { 26 | Spc* spc; 27 | Dsp* dsp; 28 | uint8_t ram[0x10000]; 29 | bool romReadable; 30 | uint8_t dspAdr; 31 | uint32_t cycles; 32 | uint8_t inPorts[6]; // includes 2 bytes of ram 33 | uint8_t outPorts[4]; 34 | Timer timer[3]; 35 | uint8_t cpuCyclesLeft; 36 | uint8_t pad[6]; 37 | 38 | 39 | union { 40 | struct DspRegWriteHistory hist; 41 | void *padpad; 42 | }; 43 | }; 44 | 45 | Apu* apu_init(); 46 | void apu_free(Apu* apu); 47 | void apu_reset(Apu* apu); 48 | void apu_cycle(Apu* apu); 49 | uint8_t apu_cpuRead(Apu* apu, uint16_t adr); 50 | void apu_cpuWrite(Apu* apu, uint16_t adr, uint8_t val); 51 | void apu_saveload(Apu *apu, SaveLoadFunc *func, void *ctx); 52 | #endif 53 | -------------------------------------------------------------------------------- /src/snes/cart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "../types.h" 7 | #include "cart.h" 8 | #include "snes.h" 9 | 10 | static uint8_t cart_readLorom(Cart* cart, uint8_t bank, uint16_t adr); 11 | static void cart_writeLorom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val); 12 | static uint8_t cart_readHirom(Cart* cart, uint8_t bank, uint16_t adr); 13 | static void cart_writeHirom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val); 14 | 15 | Cart* cart_init(Snes* snes) { 16 | Cart* cart = malloc(sizeof(Cart)); 17 | cart->snes = snes; 18 | cart->type = 0; 19 | cart->rom = NULL; 20 | cart->romSize = 0; 21 | cart->ram = NULL; 22 | cart->ramSize = 0; 23 | return cart; 24 | } 25 | 26 | void cart_free(Cart* cart) { 27 | free(cart); 28 | } 29 | 30 | void cart_reset(Cart* cart) { 31 | //if(cart->ramSize > 0 && cart->ram != NULL) memset(cart->ram, 0, cart->ramSize); // for now 32 | } 33 | 34 | void cart_saveload(Cart *cart, SaveLoadFunc *func, void *ctx) { 35 | func(ctx, cart->ram, cart->ramSize); 36 | } 37 | 38 | void cart_load(Cart* cart, int type, uint8_t* rom, int romSize, int ramSize) { 39 | cart->type = type; 40 | if(cart->rom != NULL) free(cart->rom); 41 | if(cart->ram != NULL) free(cart->ram); 42 | cart->rom = malloc(romSize); 43 | cart->romSize = romSize; 44 | if(ramSize > 0) { 45 | cart->ram = malloc(ramSize); 46 | memset(cart->ram, 0, ramSize); 47 | } else { 48 | cart->ram = NULL; 49 | } 50 | cart->ramSize = ramSize; 51 | memcpy(cart->rom, rom, romSize); 52 | } 53 | 54 | uint8_t cart_read(Cart* cart, uint8_t bank, uint16_t adr) { 55 | switch(cart->type) { 56 | case 0: 57 | assert(0); 58 | return cart->snes->openBus; 59 | case 1: return cart_readLorom(cart, bank, adr); 60 | case 2: return cart_readHirom(cart, bank, adr); 61 | } 62 | assert(0); 63 | return cart->snes->openBus; 64 | } 65 | 66 | void cart_write(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val) { 67 | switch(cart->type) { 68 | case 0: break; 69 | case 1: cart_writeLorom(cart, bank, adr, val); break; 70 | case 2: cart_writeHirom(cart, bank, adr, val); break; 71 | } 72 | } 73 | 74 | void DumpCpuHistory(); 75 | 76 | static uint8_t cart_readLorom(Cart* cart, uint8_t bank, uint16_t adr) { 77 | if(((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 && cart->ramSize > 0) { 78 | // banks 70-7e and f0-ff, adr 0000-7fff 79 | return cart->ram[(((bank & 0xf) << 15) | adr) & (cart->ramSize - 1)]; 80 | } 81 | bank &= 0x7f; 82 | if(adr >= 0x8000 || bank >= 0x40) { 83 | // adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff 84 | return cart->rom[((bank << 15) | (adr & 0x7fff)) & (cart->romSize - 1)]; 85 | } 86 | printf("While trying to read from 0x%x\n", bank << 16 | adr); 87 | DumpCpuHistory(); 88 | Die("The game crashed in cart_readLorom"); 89 | return cart->snes->openBus; 90 | } 91 | 92 | static void cart_writeLorom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val) { 93 | if(((bank >= 0x70 && bank < 0x7e) || bank > 0xf0) && adr < 0x8000 && cart->ramSize > 0) { 94 | // banks 70-7e and f0-ff, adr 0000-7fff 95 | cart->ram[(((bank & 0xf) << 15) | adr) & (cart->ramSize - 1)] = val; 96 | } 97 | } 98 | 99 | static uint8_t cart_readHirom(Cart* cart, uint8_t bank, uint16_t adr) { 100 | bank &= 0x7f; 101 | if(bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && cart->ramSize > 0) { 102 | // banks 00-3f and 80-bf, adr 6000-7fff 103 | return cart->ram[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (cart->ramSize - 1)]; 104 | } 105 | if(adr >= 0x8000 || bank >= 0x40) { 106 | // adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff 107 | return cart->rom[(((bank & 0x3f) << 16) | adr) & (cart->romSize - 1)]; 108 | } 109 | assert(0); 110 | return cart->snes->openBus; 111 | } 112 | 113 | static void cart_writeHirom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val) { 114 | bank &= 0x7f; 115 | if(bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && cart->ramSize > 0) { 116 | // banks 00-3f and 80-bf, adr 6000-7fff 117 | cart->ram[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (cart->ramSize - 1)] = val; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/snes/cart.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CART_H 3 | #define CART_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct Cart Cart; 12 | 13 | #include "snes.h" 14 | 15 | struct Cart { 16 | Snes* snes; 17 | uint8_t type; 18 | 19 | uint8_t* rom; 20 | uint32_t romSize; 21 | uint8_t* ram; 22 | uint32_t ramSize; 23 | }; 24 | 25 | // TODO: how to handle reset & load? (especially where to init ram) 26 | 27 | Cart* cart_init(Snes* snes); 28 | void cart_free(Cart* cart); 29 | void cart_reset(Cart* cart); // will reset special chips etc, general reading is set up in load 30 | void cart_load(Cart* cart, int type, uint8_t* rom, int romSize, int ramSize); // TODO: figure out how to handle (battery, cart-chips etc) 31 | uint8_t cart_read(Cart* cart, uint8_t bank, uint16_t adr); 32 | void cart_write(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val); 33 | void cart_saveload(Cart *cart, SaveLoadFunc *func, void *ctx); 34 | #endif 35 | -------------------------------------------------------------------------------- /src/snes/cpu.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CPU_H 3 | #define CPU_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "saveload.h" 11 | 12 | typedef struct Cpu Cpu; 13 | 14 | struct Cpu { 15 | // reference to memory handler, for reading//writing 16 | void* mem; 17 | uint8_t memType; // used to define which type mem is 18 | // registers 19 | uint16_t a; 20 | uint16_t x; 21 | uint16_t y; 22 | uint16_t sp; 23 | uint16_t pc; 24 | uint16_t dp; // direct page (D) 25 | uint8_t k; // program bank (PB) 26 | uint8_t db; // data bank (B) 27 | // flags 28 | bool c; 29 | bool z; 30 | bool v; 31 | bool n; 32 | bool i; 33 | bool d; 34 | bool xf; 35 | bool mf; 36 | bool e; 37 | // interrupts 38 | bool irqWanted; 39 | bool nmiWanted; 40 | // power state (WAI/STP) 41 | bool waiting; 42 | bool stopped; 43 | // internal use 44 | uint8_t cyclesUsed; // indicates how many cycles an opcode used 45 | uint16_t spBreakpoint; 46 | bool in_emu; 47 | }; 48 | 49 | extern struct Cpu *g_cpu; 50 | bool HookedFunctionRts(int is_long); 51 | 52 | Cpu* cpu_init(void* mem, int memType); 53 | void cpu_free(Cpu* cpu); 54 | void cpu_reset(Cpu* cpu); 55 | int cpu_runOpcode(Cpu* cpu); 56 | uint8_t cpu_getFlags(Cpu *cpu); 57 | void cpu_setFlags(Cpu *cpu, uint8_t val); 58 | void cpu_saveload(Cpu *cpu, SaveLoadFunc *func, void *ctx); 59 | #endif 60 | -------------------------------------------------------------------------------- /src/snes/dma.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dma.h" 11 | #include "snes.h" 12 | 13 | static const int bAdrOffsets[8][4] = { 14 | {0, 0, 0, 0}, 15 | {0, 1, 0, 1}, 16 | {0, 0, 0, 0}, 17 | {0, 0, 1, 1}, 18 | {0, 1, 2, 3}, 19 | {0, 1, 0, 1}, 20 | {0, 0, 0, 0}, 21 | {0, 0, 1, 1} 22 | }; 23 | 24 | static const int transferLength[8] = { 25 | 1, 2, 2, 4, 4, 4, 2, 4 26 | }; 27 | 28 | static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAdr, bool fromB); 29 | 30 | Dma* dma_init(Snes* snes) { 31 | Dma* dma = malloc(sizeof(Dma)); 32 | dma->snes = snes; 33 | return dma; 34 | } 35 | 36 | void dma_free(Dma* dma) { 37 | free(dma); 38 | } 39 | 40 | void dma_reset(Dma* dma) { 41 | for(int i = 0; i < 8; i++) { 42 | dma->channel[i].bAdr = 0xff; 43 | dma->channel[i].aAdr = 0xffff; 44 | dma->channel[i].aBank = 0xff; 45 | dma->channel[i].size = 0xffff; 46 | dma->channel[i].indBank = 0xff; 47 | dma->channel[i].tableAdr = 0xffff; 48 | dma->channel[i].repCount = 0xff; 49 | dma->channel[i].unusedByte = 0xff; 50 | dma->channel[i].dmaActive = false; 51 | dma->channel[i].hdmaActive = false; 52 | dma->channel[i].mode = 7; 53 | dma->channel[i].fixed = true; 54 | dma->channel[i].decrement = true; 55 | dma->channel[i].indirect = true; 56 | dma->channel[i].fromB = true; 57 | dma->channel[i].unusedBit = true; 58 | dma->channel[i].doTransfer = false; 59 | dma->channel[i].terminated = false; 60 | dma->channel[i].offIndex = 0; 61 | } 62 | dma->hdmaTimer = 0; 63 | dma->dmaTimer = 0; 64 | dma->dmaBusy = false; 65 | } 66 | 67 | void dma_saveload(Dma *dma, SaveLoadFunc *func, void *ctx) { 68 | func(ctx, &dma->channel, offsetof(Dma, pad) + 7 - offsetof(Dma, channel)); 69 | } 70 | 71 | uint8_t dma_read(Dma* dma, uint16_t adr) { 72 | uint8_t c = (adr & 0x70) >> 4; 73 | switch(adr & 0xf) { 74 | case 0x0: { 75 | uint8_t val = dma->channel[c].mode; 76 | val |= dma->channel[c].fixed << 3; 77 | val |= dma->channel[c].decrement << 4; 78 | val |= dma->channel[c].unusedBit << 5; 79 | val |= dma->channel[c].indirect << 6; 80 | val |= dma->channel[c].fromB << 7; 81 | return val; 82 | } 83 | case 0x1: { 84 | return dma->channel[c].bAdr; 85 | } 86 | case 0x2: { 87 | return dma->channel[c].aAdr & 0xff; 88 | } 89 | case 0x3: { 90 | return dma->channel[c].aAdr >> 8; 91 | } 92 | case 0x4: { 93 | return dma->channel[c].aBank; 94 | } 95 | case 0x5: { 96 | return dma->channel[c].size & 0xff; 97 | } 98 | case 0x6: { 99 | return dma->channel[c].size >> 8; 100 | } 101 | case 0x7: { 102 | return dma->channel[c].indBank; 103 | } 104 | case 0x8: { 105 | return dma->channel[c].tableAdr & 0xff; 106 | } 107 | case 0x9: { 108 | return dma->channel[c].tableAdr >> 8; 109 | } 110 | case 0xa: { 111 | return dma->channel[c].repCount; 112 | } 113 | case 0xb: 114 | case 0xf: { 115 | return dma->channel[c].unusedByte; 116 | } 117 | default: { 118 | assert(0); 119 | return dma->snes->openBus; 120 | } 121 | } 122 | } 123 | 124 | void dma_write(Dma* dma, uint16_t adr, uint8_t val) { 125 | uint8_t c = (adr & 0x70) >> 4; 126 | switch(adr & 0xf) { 127 | case 0x0: { 128 | dma->channel[c].mode = val & 0x7; 129 | dma->channel[c].fixed = val & 0x8; 130 | dma->channel[c].decrement = val & 0x10; 131 | dma->channel[c].unusedBit = val & 0x20; 132 | dma->channel[c].indirect = val & 0x40; 133 | dma->channel[c].fromB = val & 0x80; 134 | break; 135 | } 136 | case 0x1: { 137 | dma->channel[c].bAdr = val; 138 | break; 139 | } 140 | case 0x2: { 141 | dma->channel[c].aAdr = (dma->channel[c].aAdr & 0xff00) | val; 142 | break; 143 | } 144 | case 0x3: { 145 | dma->channel[c].aAdr = (dma->channel[c].aAdr & 0xff) | (val << 8); 146 | break; 147 | } 148 | case 0x4: { 149 | dma->channel[c].aBank = val; 150 | break; 151 | } 152 | case 0x5: { 153 | dma->channel[c].size = (dma->channel[c].size & 0xff00) | val; 154 | break; 155 | } 156 | case 0x6: { 157 | dma->channel[c].size = (dma->channel[c].size & 0xff) | (val << 8); 158 | break; 159 | } 160 | case 0x7: { 161 | dma->channel[c].indBank = val; 162 | break; 163 | } 164 | case 0x8: { 165 | dma->channel[c].tableAdr = (dma->channel[c].tableAdr & 0xff00) | val; 166 | break; 167 | } 168 | case 0x9: { 169 | dma->channel[c].tableAdr = (dma->channel[c].tableAdr & 0xff) | (val << 8); 170 | break; 171 | } 172 | case 0xa: { 173 | dma->channel[c].repCount = val; 174 | break; 175 | } 176 | case 0xb: 177 | case 0xf: { 178 | dma->channel[c].unusedByte = val; 179 | break; 180 | } 181 | default: { 182 | break; 183 | } 184 | } 185 | } 186 | 187 | extern bool g_fail; 188 | 189 | void dma_doDma(Dma* dma) { 190 | if(dma->dmaTimer > 0) { 191 | dma->dmaTimer -= 2; 192 | return; 193 | } 194 | // figure out first channel that is active 195 | int i = 0; 196 | for(i = 0; i < 8; i++) { 197 | if(dma->channel[i].dmaActive) { 198 | break; 199 | } 200 | } 201 | if(i == 8) { 202 | // no active channels 203 | dma->dmaBusy = false; 204 | return; 205 | } 206 | 207 | if (!dma->channel[i].fromB && (dma->channel[i].aBank & 0x80) && !(dma->channel[i].aAdr & 0x8000) && !g_fail) { 208 | printf("Warning! DMA from addr 0x%x\n", dma->channel[i].aBank << 16 | dma->channel[i].aAdr); 209 | g_fail = true; 210 | } 211 | 212 | // do channel i 213 | dma_transferByte( 214 | dma, dma->channel[i].aAdr, dma->channel[i].aBank, 215 | dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][dma->channel[i].offIndex++], dma->channel[i].fromB 216 | ); 217 | dma->channel[i].offIndex &= 3; 218 | dma->dmaTimer += 6; // 8 cycles for each byte taken, -2 for this cycle 219 | if(!dma->channel[i].fixed) { 220 | dma->channel[i].aAdr += dma->channel[i].decrement ? -1 : 1; 221 | } 222 | dma->channel[i].size--; 223 | if(dma->channel[i].size == 0) { 224 | dma->channel[i].offIndex = 0; // reset offset index 225 | dma->channel[i].dmaActive = false; 226 | dma->dmaTimer += 8; // 8 cycle overhead per channel 227 | } 228 | } 229 | 230 | void dma_initHdma(Dma* dma) { 231 | dma->hdmaTimer = 0; 232 | bool hdmaHappened = false; 233 | for(int i = 0; i < 8; i++) { 234 | if(dma->channel[i].hdmaActive) { 235 | hdmaHappened = true; 236 | // terminate any dma 237 | dma->channel[i].dmaActive = false; 238 | dma->channel[i].offIndex = 0; 239 | // load address, repCount, and indirect address if needed 240 | dma->channel[i].tableAdr = dma->channel[i].aAdr; 241 | dma->channel[i].repCount = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); 242 | if (dma->channel[i].repCount == 0) { 243 | dma->channel[i].terminated = true; 244 | continue; 245 | } 246 | dma->hdmaTimer += 8; // 8 cycle overhead for each active channel 247 | if(dma->channel[i].indirect) { 248 | dma->channel[i].size = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); 249 | dma->channel[i].size |= snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++) << 8; 250 | dma->hdmaTimer += 16; // another 16 cycles for indirect (total 24) 251 | } 252 | dma->channel[i].doTransfer = true; 253 | } else { 254 | dma->channel[i].doTransfer = false; 255 | } 256 | dma->channel[i].terminated = false; 257 | } 258 | if(hdmaHappened) dma->hdmaTimer += 16; // 18 cycles overhead, -2 for this cycle 259 | } 260 | 261 | void dma_doHdma(Dma* dma) { 262 | dma->hdmaTimer = 0; 263 | bool hdmaHappened = false; 264 | for(int i = 0; i < 8; i++) { 265 | if(dma->channel[i].hdmaActive && !dma->channel[i].terminated) { 266 | // printf("DMA %d: 0x%x\n", i, dma->channel[i].bAdr); 267 | hdmaHappened = true; 268 | // terminate any dma 269 | dma->channel[i].dmaActive = false; 270 | dma->channel[i].offIndex = 0; 271 | // do the hdma 272 | dma->hdmaTimer += 8; // 8 cycles overhead for each active channel 273 | 274 | 275 | if(dma->channel[i].doTransfer) { 276 | for(int j = 0; j < transferLength[dma->channel[i].mode]; j++) { 277 | dma->hdmaTimer += 8; // 8 cycles for each byte transferred 278 | if(dma->channel[i].indirect) { 279 | dma_transferByte( 280 | dma, dma->channel[i].size++, dma->channel[i].indBank, 281 | dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][j], dma->channel[i].fromB 282 | ); 283 | } else { 284 | dma_transferByte( 285 | dma, dma->channel[i].tableAdr++, dma->channel[i].aBank, 286 | dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][j], dma->channel[i].fromB 287 | ); 288 | } 289 | } 290 | } 291 | dma->channel[i].repCount--; 292 | dma->channel[i].doTransfer = dma->channel[i].repCount & 0x80; 293 | if((dma->channel[i].repCount & 0x7f) == 0) { 294 | dma->channel[i].repCount = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); 295 | if(dma->channel[i].indirect) { 296 | // TODO: oddness with not fetching high byte if last active channel and reCount is 0 297 | dma->channel[i].size = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++); 298 | dma->channel[i].size |= snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++) << 8; 299 | dma->hdmaTimer += 16; // 16 cycles for new indirect address 300 | } 301 | if(dma->channel[i].repCount == 0) dma->channel[i].terminated = true; 302 | dma->channel[i].doTransfer = true; 303 | } 304 | } 305 | } 306 | if(hdmaHappened) dma->hdmaTimer += 16; // 18 cycles overhead, -2 for this cycle 307 | } 308 | 309 | static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAdr, bool fromB) { 310 | // TODO: invalid writes: 311 | // accesing b-bus via a-bus gives open bus, 312 | // $2180-$2183 while accessing ram via a-bus open busses $2180-$2183 313 | // cannot access $4300-$437f (dma regs), or $420b / $420c 314 | if(fromB) { 315 | snes_write(dma->snes, (aBank << 16) | aAdr, snes_readBBus(dma->snes, bAdr)); 316 | } else { 317 | snes_writeBBus(dma->snes, bAdr, snes_read(dma->snes, (aBank << 16) | aAdr)); 318 | } 319 | } 320 | 321 | bool dma_cycle(Dma* dma) { 322 | if(dma->hdmaTimer > 0) { 323 | dma->hdmaTimer -= 2; 324 | return true; 325 | } else if(dma->dmaBusy) { 326 | dma_doDma(dma); 327 | return true; 328 | } 329 | return false; 330 | } 331 | 332 | void dma_startDma(Dma* dma, uint8_t val, bool hdma) { 333 | for(int i = 0; i < 8; i++) { 334 | if(hdma) { 335 | dma->channel[i].hdmaActive = val & (1 << i); 336 | } else { 337 | dma->channel[i].dmaActive = val & (1 << i); 338 | } 339 | } 340 | if(!hdma) { 341 | dma->dmaBusy = val; 342 | dma->dmaTimer += dma->dmaBusy ? 16 : 0; // 12-24 cycle overhead for entire dma transfer 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/snes/dma.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef DMA_H 3 | #define DMA_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct Dma Dma; 12 | 13 | #include "snes.h" 14 | 15 | typedef struct DmaChannel { 16 | uint8_t bAdr; 17 | uint16_t aAdr; 18 | uint8_t aBank; 19 | uint16_t size; // also indirect hdma adr 20 | uint8_t indBank; // hdma 21 | uint16_t tableAdr; // hdma 22 | uint8_t repCount; // hdma 23 | uint8_t unusedByte; 24 | bool dmaActive; 25 | bool hdmaActive; 26 | uint8_t mode; 27 | bool fixed; 28 | bool decrement; 29 | bool indirect; // hdma 30 | bool fromB; 31 | bool unusedBit; 32 | bool doTransfer; // hdma 33 | bool terminated; // hdma 34 | uint8_t offIndex; 35 | } DmaChannel; 36 | 37 | struct Dma { 38 | Snes* snes; 39 | DmaChannel channel[8]; 40 | uint16_t hdmaTimer; 41 | uint32_t dmaTimer; 42 | bool dmaBusy; 43 | uint8_t pad[7]; 44 | }; 45 | 46 | Dma* dma_init(Snes* snes); 47 | void dma_free(Dma* dma); 48 | void dma_reset(Dma* dma); 49 | uint8_t dma_read(Dma* dma, uint16_t adr); // 43x0-43xf 50 | void dma_write(Dma* dma, uint16_t adr, uint8_t val); // 43x0-43xf 51 | void dma_doDma(Dma* dma); 52 | void dma_initHdma(Dma* dma); 53 | void dma_doHdma(Dma* dma); 54 | bool dma_cycle(Dma* dma); 55 | void dma_startDma(Dma* dma, uint8_t val, bool hdma); 56 | void dma_saveload(Dma *dma, SaveLoadFunc *func, void *ctx); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/snes/dsp.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef DSP_H 3 | #define DSP_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "saveload.h" 11 | 12 | typedef struct Dsp Dsp; 13 | 14 | typedef struct Apu Apu; 15 | 16 | typedef struct DspChannel { 17 | // pitch 18 | uint16_t pitch; 19 | uint16_t pitchCounter; 20 | bool pitchModulation; 21 | // brr decoding 22 | int16_t decodeBuffer[19]; // 16 samples per brr-block, +3 for interpolation 23 | uint8_t srcn; 24 | uint16_t decodeOffset; 25 | uint8_t previousFlags; // from last sample 26 | int16_t old; 27 | int16_t older; 28 | bool useNoise; 29 | // adsr, envelope, gain 30 | uint16_t adsrRates[4]; // attack, decay, sustain, gain 31 | uint16_t rateCounter; 32 | uint8_t adsrState; // 0: attack, 1: decay, 2: sustain, 3: gain, 4: release 33 | uint16_t sustainLevel; 34 | bool useGain; 35 | uint8_t gainMode; 36 | bool directGain; 37 | uint16_t gainValue; // for direct gain 38 | uint16_t gain; 39 | // keyon/off 40 | bool keyOn; 41 | bool keyOff; 42 | // output 43 | int16_t sampleOut; // final sample, to be multiplied by channel volume 44 | int8_t volumeL; 45 | int8_t volumeR; 46 | bool echoEnable; 47 | } DspChannel; 48 | 49 | struct Dsp { 50 | uint8_t *apu_ram; 51 | // mirror ram 52 | uint8_t ram[0x80]; 53 | // 8 channels 54 | DspChannel channel[8]; 55 | // overarching 56 | uint16_t dirPage; 57 | bool evenCycle; 58 | bool mute; 59 | bool reset; 60 | int8_t masterVolumeL; 61 | int8_t masterVolumeR; 62 | // noise 63 | int16_t noiseSample; 64 | uint16_t noiseRate; 65 | uint16_t noiseCounter; 66 | // echo 67 | bool echoWrites; 68 | int8_t echoVolumeL; 69 | int8_t echoVolumeR; 70 | int8_t feedbackVolume; 71 | uint16_t echoBufferAdr; 72 | uint16_t echoDelay; 73 | uint16_t echoRemain; 74 | uint16_t echoBufferIndex; 75 | uint8_t firBufferIndex; 76 | int8_t firValues[8]; 77 | int16_t firBufferL[8]; 78 | int16_t firBufferR[8]; 79 | // sample buffer (1 frame at 32040 Hz: 534 samples, *2 for stereo) 80 | int16_t sampleBuffer[534 * 2]; 81 | uint16_t sampleOffset; // current offset in samplebuffer 82 | }; 83 | 84 | typedef struct DspRegWriteHistory { 85 | uint32_t count; 86 | uint8_t addr[256]; 87 | uint8_t val[256]; 88 | } DspRegWriteHistory; 89 | 90 | Dsp *dsp_init(uint8_t *ram); 91 | void dsp_free(Dsp* dsp); 92 | void dsp_reset(Dsp* dsp); 93 | void dsp_cycle(Dsp* dsp); 94 | uint8_t dsp_read(Dsp* dsp, uint8_t adr); 95 | void dsp_write(Dsp* dsp, uint8_t adr, uint8_t val); 96 | void dsp_getSamples(Dsp* dsp, int16_t* sampleData, int samplesPerFrame); 97 | void dsp_saveload(Dsp *dsp, SaveLoadFunc *func, void *ctx); 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/snes/dsp_regs.h: -------------------------------------------------------------------------------- 1 | #ifndef DSP_REGS_H 2 | #define DSP_REGS_H 3 | 4 | enum DspReg { 5 | V0VOLL = 0x00, 6 | V0VOLR = 0x01, 7 | V0PITCHL = 0x02, 8 | V0PITCHH = 0x03, 9 | V0SRCN = 0x04, 10 | V0ADSR1 = 0x05, 11 | V0ADSR2 = 0x06, 12 | V0GAIN = 0x07, 13 | V0ENVX = 0x08, 14 | V0OUTX = 0x09, 15 | MVOLL = 0x0C, 16 | EFB = 0x0D, 17 | FIR0 = 0x0F, 18 | V1VOLL = 0x10, 19 | V1VOLR = 0x11, 20 | V1PL = 0x12, 21 | V1PH = 0x13, 22 | V1SRCN = 0x14, 23 | V1ADSR1 = 0x15, 24 | V1ADSR2 = 0x16, 25 | V1GAIN = 0x17, 26 | V1ENVX = 0x18, 27 | V1OUTX = 0x19, 28 | MVOLR = 0x1C, 29 | FIR1 = 0x1F, 30 | V2VOLL = 0x20, 31 | V2VOLR = 0x21, 32 | V2PL = 0x22, 33 | V2PH = 0x23, 34 | V2SRCN = 0x24, 35 | V2ADSR1 = 0x25, 36 | V2ADSR2 = 0x26, 37 | V2GAIN = 0x27, 38 | V2ENVX = 0x28, 39 | V2OUTX = 0x29, 40 | EVOLL = 0x2C, 41 | PMON = 0x2D, 42 | FIR2 = 0x2F, 43 | V3VOLL = 0x30, 44 | V3VOLR = 0x31, 45 | V3PL = 0x32, 46 | V3PH = 0x33, 47 | V3SRCN = 0x34, 48 | V3ADSR1 = 0x35, 49 | V3ADSR2 = 0x36, 50 | V3GAIN = 0x37, 51 | V3ENVX = 0x38, 52 | V3OUTX = 0x39, 53 | EVOLR = 0x3C, 54 | NON = 0x3D, 55 | FIR3 = 0x3F, 56 | V4VOLL = 0x40, 57 | V4VOLR = 0x41, 58 | V4PL = 0x42, 59 | V4PH = 0x43, 60 | V4SRCN = 0x44, 61 | V4ADSR1 = 0x45, 62 | V4ADSR2 = 0x46, 63 | V4GAIN = 0x47, 64 | V4ENVX = 0x48, 65 | V4OUTX = 0x49, 66 | KON = 0x4C, 67 | EON = 0x4D, 68 | FIR4 = 0x4F, 69 | V5VOLL = 0x50, 70 | V5VOLR = 0x51, 71 | V5PL = 0x52, 72 | V5PH = 0x53, 73 | V5SRCN = 0x54, 74 | V5ADSR1 = 0x55, 75 | V5ADSR2 = 0x56, 76 | V5GAIN = 0x57, 77 | V5ENVX = 0x58, 78 | V5OUTX = 0x59, 79 | KOF = 0x5C, 80 | DIR = 0x5D, 81 | FIR5 = 0x5F, 82 | V6VOLL = 0x60, 83 | V6VOLR = 0x61, 84 | V6PL = 0x62, 85 | V6PH = 0x63, 86 | V6SRCN = 0x64, 87 | V6ADSR1 = 0x65, 88 | V6ADSR2 = 0x66, 89 | V6GAIN = 0x67, 90 | V6ENVX = 0x68, 91 | V6OUTX = 0x69, 92 | FLG = 0x6C, 93 | ESA = 0x6D, 94 | FIR6 = 0x6F, 95 | V7VOLL = 0x70, 96 | V7VOLR = 0x71, 97 | V7PL = 0x72, 98 | V7PH = 0x73, 99 | V7SRCN = 0x74, 100 | V7ADSR1 = 0x75, 101 | V7ADSR2 = 0x76, 102 | V7GAIN = 0x77, 103 | V7ENVX = 0x78, 104 | V7OUTX = 0x79, 105 | ENDX = 0x7C, 106 | EDL = 0x7D, 107 | FIR7 = 0x7F, 108 | }; 109 | #endif // DSP_REGS_H -------------------------------------------------------------------------------- /src/snes/input.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "input.h" 9 | #include "snes.h" 10 | 11 | Input* input_init(Snes* snes) { 12 | Input* input = malloc(sizeof(Input)); 13 | input->snes = snes; 14 | // TODO: handle (where?) 15 | input->type = 1; 16 | input->currentState = 0; 17 | return input; 18 | } 19 | 20 | void input_free(Input* input) { 21 | free(input); 22 | } 23 | 24 | void input_reset(Input* input) { 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/snes/input.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef INPUT_H 3 | #define INPUT_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct Input Input; 12 | 13 | #include "snes.h" 14 | 15 | struct Input { 16 | Snes* snes; 17 | uint8_t type; 18 | // for controller 19 | uint16_t currentState; // actual state 20 | }; 21 | 22 | Input* input_init(Snes* snes); 23 | void input_free(Input* input); 24 | void input_reset(Input* input); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/snes/ppu.h: -------------------------------------------------------------------------------- 1 | #ifndef PPU_H 2 | #define PPU_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct Ppu Ppu; 11 | 12 | #include "snes.h" 13 | 14 | typedef struct BgLayer { 15 | uint16_t hScroll; 16 | uint16_t vScroll; 17 | bool tilemapWider; 18 | bool tilemapHigher; 19 | uint16_t tilemapAdr; 20 | uint16_t tileAdr; 21 | bool bigTiles; 22 | bool mosaicEnabled; 23 | } BgLayer; 24 | 25 | enum { 26 | kPpuXPixels = 256, 27 | kPpuExtraLeftRight = 0, 28 | }; 29 | 30 | typedef uint16_t PpuZbufType; 31 | 32 | typedef struct PpuPixelPrioBufs { 33 | // This holds the prio in the upper 8 bits and the color in the lower 8 bits. 34 | PpuZbufType data[kPpuXPixels]; 35 | } PpuPixelPrioBufs; 36 | 37 | enum { 38 | kPpuRenderFlags_NewRenderer = 1, 39 | // Render mode7 upsampled by 4x4 40 | kPpuRenderFlags_4x4Mode7 = 2, 41 | // Use 240 height instead of 224 42 | kPpuRenderFlags_Height240 = 4, 43 | // Disable sprite render limits 44 | kPpuRenderFlags_NoSpriteLimits = 8, 45 | }; 46 | 47 | 48 | 49 | typedef struct Layer { 50 | bool mainScreenEnabled; 51 | bool subScreenEnabled; 52 | bool mainScreenWindowed; 53 | bool subScreenWindowed; 54 | } Layer; 55 | 56 | typedef struct WindowLayer { 57 | bool window1enabled; 58 | bool window2enabled; 59 | bool window1inversed; 60 | bool window2inversed; 61 | uint8_t maskLogic; 62 | } WindowLayer; 63 | 64 | struct Ppu { 65 | Snes* snes; 66 | // vram access 67 | uint16_t vram[0x8000]; 68 | uint16_t vramPointer; 69 | bool vramIncrementOnHigh; 70 | uint16_t vramIncrement; 71 | uint8_t vramRemapMode; 72 | uint16_t vramReadBuffer; 73 | // cgram access 74 | uint16_t cgram[0x100]; 75 | uint8_t cgramPointer; 76 | bool cgramSecondWrite; 77 | uint8_t cgramBuffer; 78 | // oam access 79 | uint16_t oam[0x100]; 80 | uint8_t highOam[0x20]; 81 | uint8_t oamAdr; 82 | uint8_t oamAdrWritten; 83 | bool oamInHigh; 84 | bool oamInHighWritten; 85 | bool oamSecondWrite; 86 | uint8_t oamBuffer; 87 | // object/sprites 88 | bool objPriority; 89 | uint16_t objTileAdr1; 90 | uint16_t objTileAdr2; 91 | uint8_t objSize; 92 | uint8_t objPixelBufferXX[256]; // line buffers 93 | uint8_t objPriorityBufferXX[256]; 94 | bool timeOver; 95 | bool rangeOver; 96 | bool objInterlace; 97 | // background layers 98 | BgLayer bgLayer[4]; 99 | uint8_t scrollPrev; 100 | uint8_t scrollPrev2; 101 | uint8_t mosaicSize; 102 | uint8_t mosaicStartLine; 103 | // layers 104 | Layer layer[5]; 105 | // mode 7 106 | int16_t m7matrix[8]; // a, b, c, d, x, y, h, v 107 | uint8_t m7prev; 108 | bool m7largeField; 109 | bool m7charFill; 110 | bool m7xFlip; 111 | bool m7yFlip; 112 | bool m7extBg; 113 | // mode 7 internal 114 | int32_t m7startX; 115 | int32_t m7startY; 116 | // windows 117 | WindowLayer windowLayer[6]; 118 | uint8_t window1left; 119 | uint8_t window1right; 120 | uint8_t window2left; 121 | uint8_t window2right; 122 | // color math 123 | uint8_t clipMode; 124 | uint8_t preventMathMode; 125 | bool addSubscreen; 126 | bool subtractColor; 127 | bool halfColor; 128 | bool mathEnabled[6]; 129 | uint8_t fixedColorR; 130 | uint8_t fixedColorG; 131 | uint8_t fixedColorB; 132 | // settings 133 | bool forcedBlank; 134 | uint8_t brightness; 135 | uint8_t mode; 136 | bool bg3priority; 137 | bool evenFrame; 138 | bool pseudoHires; 139 | bool overscan; 140 | bool frameOverscan; // if we are overscanning this frame (determined at 0,225) 141 | bool interlace; 142 | bool frameInterlace; // if we are interlacing this frame (determined at start vblank) 143 | bool directColor; 144 | // latching 145 | uint16_t hCount; 146 | uint16_t vCount; 147 | bool hCountSecond; 148 | bool vCountSecond; 149 | bool countersLatched; 150 | uint8_t ppu1openBus; 151 | uint8_t ppu2openBus; 152 | // pixel buffer (xbgr) 153 | // times 2 for even and odd frame 154 | uint8_t pixelbuffer_placeholder; 155 | 156 | uint32_t windowsel; 157 | uint8_t extraLeftCur, extraRightCur, extraLeftRight; 158 | uint8_t screenEnabled[2]; 159 | uint8_t screenWindowed[2]; 160 | uint8_t mosaicEnabled; 161 | uint8_t lastBrightnessMult; 162 | bool lineHasSprites; 163 | PpuPixelPrioBufs bgBuffers[2]; 164 | PpuPixelPrioBufs objBuffer; 165 | uint32_t renderPitch; 166 | uint8_t *renderBuffer; 167 | uint8_t brightnessMult[32 + 31]; 168 | uint8_t brightnessMultHalf[32 * 2]; 169 | uint8_t mosaicModulo[kPpuXPixels]; 170 | 171 | }; 172 | 173 | Ppu* ppu_init(Snes* snes); 174 | void ppu_free(Ppu* ppu); 175 | void ppu_copy(Ppu *ppu, Ppu *ppu_src); 176 | void ppu_reset(Ppu* ppu); 177 | bool ppu_checkOverscan(Ppu* ppu); 178 | void ppu_handleVblank(Ppu* ppu); 179 | void ppu_runLine(Ppu* ppu, int line); 180 | uint8_t ppu_read(Ppu* ppu, uint8_t adr); 181 | void ppu_write(Ppu* ppu, uint8_t adr, uint8_t val); 182 | void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx); 183 | void PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags); 184 | 185 | int PpuGetCurrentRenderScale(Ppu *ppu, uint32_t render_flags); 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /src/snes/saveload.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef void SaveLoadFunc(void *ctx, void *data, size_t data_size); 4 | 5 | #define SL(x) func(ctx, &x, sizeof(x)) -------------------------------------------------------------------------------- /src/snes/snes.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SNES_H 3 | #define SNES_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct Snes Snes; 12 | 13 | #include "cpu.h" 14 | #include "apu.h" 15 | #include "dma.h" 16 | #include "ppu.h" 17 | #include "cart.h" 18 | #include "input.h" 19 | #include "saveload.h" 20 | 21 | struct Snes { 22 | Cpu* cpu; 23 | Apu* apu; 24 | Ppu* ppu, *snes_ppu, *my_ppu; 25 | Dma* dma; 26 | Cart* cart; 27 | Input *input1; 28 | Input *input2; 29 | // input 30 | bool debug_cycles; 31 | bool debug_apu_cycles; 32 | bool disableRender; 33 | uint8_t runningWhichVersion; 34 | 35 | // ram 36 | uint32_t ramAdr; 37 | uint8_t *ram; 38 | uint8_t padx[4]; 39 | 40 | // frame timing 41 | uint16_t hPos; 42 | uint16_t vPos; 43 | uint32_t frames; 44 | // cpu handling 45 | uint8_t cpuCyclesLeft; 46 | uint8_t cpuMemOps; 47 | uint8_t padpad[2]; 48 | double apuCatchupCycles; 49 | // nmi / irq 50 | bool hIrqEnabled; 51 | bool vIrqEnabled; 52 | bool nmiEnabled; 53 | uint16_t hTimer; 54 | uint16_t vTimer; 55 | bool inNmi; 56 | bool inIrq; 57 | bool inVblank; 58 | // joypad handling 59 | uint16_t portAutoReadX[4]; // as read by auto-joypad read 60 | bool autoJoyRead; 61 | uint16_t autoJoyTimer; // times how long until reading is done 62 | bool ppuLatch; 63 | // multiplication/division 64 | uint8_t multiplyA; 65 | uint16_t multiplyResult; 66 | uint16_t divideA; 67 | uint16_t divideResult; 68 | // misc 69 | bool fastMem; 70 | uint8_t openBus; 71 | }; 72 | 73 | Snes* snes_init(uint8_t *ram); 74 | void snes_free(Snes* snes); 75 | void snes_reset(Snes* snes, bool hard); 76 | void snes_runFrame(Snes* snes); 77 | // used by dma, cpu 78 | uint8_t snes_readBBus(Snes* snes, uint8_t adr); 79 | void snes_writeBBus(Snes* snes, uint8_t adr, uint8_t val); 80 | uint8_t snes_read(Snes* snes, uint32_t adr); 81 | void snes_write(Snes* snes, uint32_t adr, uint8_t val); 82 | uint8_t snes_cpuRead(Snes* snes, uint32_t adr); 83 | void snes_cpuWrite(Snes* snes, uint32_t adr, uint8_t val); 84 | // debugging 85 | void snes_debugCycle(Snes* snes, bool* cpuNext, bool* spcNext); 86 | 87 | void snes_handle_pos_stuff(Snes *snes); 88 | 89 | // snes_other.c functions: 90 | 91 | bool snes_loadRom(Snes* snes, const uint8_t* data, int length); 92 | void snes_setPixels(Snes* snes, uint8_t* pixelData); 93 | void snes_setSamples(Snes* snes, int16_t* sampleData, int samplesPerFrame); 94 | void snes_saveload(Snes *snes, SaveLoadFunc *func, void *ctx); 95 | uint8_t snes_readBBusOrg(Snes *snes, uint8_t adr); 96 | void snes_catchupApu(Snes *snes); 97 | 98 | extern int snes_frame_counter; 99 | #endif 100 | -------------------------------------------------------------------------------- /src/snes/snes_other.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "snes.h" 9 | #include "cart.h" 10 | #include "ppu.h" 11 | #include "dsp.h" 12 | 13 | typedef struct CartHeader { 14 | // normal header 15 | uint8_t headerVersion; // 1, 2, 3 16 | char name[22]; // $ffc0-$ffd4 (max 21 bytes + \0), $ffd4=$00: header V2 17 | uint8_t speed; // $ffd5.7-4 (always 2 or 3) 18 | uint8_t type; // $ffd5.3-0 19 | uint8_t coprocessor; // $ffd6.7-4 20 | uint8_t chips; // $ffd6.3-0 21 | uint32_t romSize; // $ffd7 (0x400 << x) 22 | uint32_t ramSize; // $ffd8 (0x400 << x) 23 | uint8_t region; // $ffd9 (also NTSC/PAL) 24 | uint8_t maker; // $ffda ($33: header V3) 25 | uint8_t version; // $ffdb 26 | uint16_t checksumComplement; // $ffdc,$ffdd 27 | uint16_t checksum; // $ffde,$ffdf 28 | // v2/v3 (v2 only exCoprocessor) 29 | char makerCode[3]; // $ffb0,$ffb1: (2 chars + \0) 30 | char gameCode[5]; // $ffb2-$ffb5: (4 chars + \0) 31 | uint32_t flashSize; // $ffbc (0x400 << x) 32 | uint32_t exRamSize; // $ffbd (0x400 << x) (used for GSU?) 33 | uint8_t specialVersion; // $ffbe 34 | uint8_t exCoprocessor; // $ffbf (if coprocessor = $f) 35 | // calculated stuff 36 | int16_t score; // score for header, to see which mapping is most likely 37 | bool pal; // if this is a rom for PAL regions instead of NTSC 38 | uint8_t cartType; // calculated type 39 | } CartHeader; 40 | 41 | static void readHeader(const uint8_t* data, int length, int location, CartHeader* header); 42 | 43 | bool snes_loadRom(Snes* snes, const uint8_t* data, int length) { 44 | // if smaller than smallest possible, don't load 45 | if(length < 0x8000) { 46 | printf("Failed to load rom: rom to small (%d bytes)\n", length); 47 | return false; 48 | } 49 | // check headers 50 | CartHeader headers[4]; 51 | memset(headers, 0, sizeof(headers)); 52 | for(int i = 0; i < 4; i++) { 53 | headers[i].score = -50; 54 | } 55 | if(length >= 0x8000) readHeader(data, length, 0x7fc0, &headers[0]); 56 | if(length >= 0x8200) readHeader(data, length, 0x81c0, &headers[1]); 57 | if(length >= 0x10000) readHeader(data, length, 0xffc0, &headers[2]); 58 | if(length >= 0x10200) readHeader(data, length, 0x101c0, &headers[3]); 59 | // see which it is 60 | int max = 0; 61 | int used = 0; 62 | for(int i = 0; i < 4; i++) { 63 | if(headers[i].score > max) { 64 | max = headers[i].score; 65 | used = i; 66 | } 67 | } 68 | if(used & 1) { 69 | // odd-numbered ones are for headered roms 70 | data += 0x200; // move pointer past header 71 | length -= 0x200; // and subtract from size 72 | } 73 | // check if we can load it 74 | if(headers[used].cartType > 2) { 75 | printf("Failed to load rom: unsupported type (%d)\n", headers[used].cartType); 76 | return false; 77 | } 78 | // expand to a power of 2 79 | int newLength = 0x8000; 80 | while(true) { 81 | if(length <= newLength) { 82 | break; 83 | } 84 | newLength *= 2; 85 | } 86 | uint8_t* newData = malloc(newLength); 87 | memcpy(newData, data, length); 88 | int test = 1; 89 | while(length != newLength) { 90 | if(length & test) { 91 | memcpy(newData + length, newData + length - test, test); 92 | length += test; 93 | } 94 | test *= 2; 95 | } 96 | // load it 97 | cart_load( 98 | snes->cart, headers[used].cartType, 99 | newData, newLength, headers[used].chips > 0 ? headers[used].ramSize : 0 100 | ); 101 | snes_reset(snes, true); // reset after loading 102 | free(newData); 103 | return true; 104 | } 105 | 106 | void snes_setSamples(Snes* snes, int16_t* sampleData, int samplesPerFrame) { 107 | // size is 2 (int16) * 2 (stereo) * samplesPerFrame 108 | // sets samples in the sampleData 109 | dsp_getSamples(snes->apu->dsp, sampleData, samplesPerFrame); 110 | } 111 | 112 | static void readHeader(const uint8_t* data, int length, int location, CartHeader* header) { 113 | // read name, TODO: non-ASCII names? 114 | for(int i = 0; i < 21; i++) { 115 | uint8_t ch = data[location + i]; 116 | if(ch >= 0x20 && ch < 0x7f) { 117 | header->name[i] = ch; 118 | } else { 119 | header->name[i] = '.'; 120 | } 121 | } 122 | header->name[21] = 0; 123 | // read rest 124 | header->speed = data[location + 0x15] >> 4; 125 | header->type = data[location + 0x15] & 0xf; 126 | header->coprocessor = data[location + 0x16] >> 4; 127 | header->chips = data[location + 0x16] & 0xf; 128 | header->romSize = 0x400 << data[location + 0x17]; 129 | header->ramSize = 0x400 << data[location + 0x18]; 130 | header->region = data[location + 0x19]; 131 | header->maker = data[location + 0x1a]; 132 | header->version = data[location + 0x1b]; 133 | header->checksumComplement = (data[location + 0x1d] << 8) + data[location + 0x1c]; 134 | header->checksum = (data[location + 0x1f] << 8) + data[location + 0x1e]; 135 | // read v3 and/or v2 136 | header->headerVersion = 1; 137 | if(header->maker == 0x33) { 138 | header->headerVersion = 3; 139 | // maker code 140 | for(int i = 0; i < 2; i++) { 141 | uint8_t ch = data[location - 0x10 + i]; 142 | if(ch >= 0x20 && ch < 0x7f) { 143 | header->makerCode[i] = ch; 144 | } else { 145 | header->makerCode[i] = '.'; 146 | } 147 | } 148 | header->makerCode[2] = 0; 149 | // game code 150 | for(int i = 0; i < 4; i++) { 151 | uint8_t ch = data[location - 0xe + i]; 152 | if(ch >= 0x20 && ch < 0x7f) { 153 | header->gameCode[i] = ch; 154 | } else { 155 | header->gameCode[i] = '.'; 156 | } 157 | } 158 | header->gameCode[4] = 0; 159 | header->flashSize = 0x400 << data[location - 4]; 160 | header->exRamSize = 0x400 << data[location - 3]; 161 | header->specialVersion = data[location - 2]; 162 | header->exCoprocessor = data[location - 1]; 163 | } else if(data[location + 0x14] == 0) { 164 | header->headerVersion = 2; 165 | header->exCoprocessor = data[location - 1]; 166 | } 167 | // get region 168 | header->pal = (header->region >= 0x2 && header->region <= 0xc) || header->region == 0x11; 169 | header->cartType = location < 0x9000 ? 1 : 2; 170 | // get score 171 | // TODO: check name, maker/game-codes (if V3) for ASCII, more vectors, 172 | // more first opcode, rom-sizes (matches?), type (matches header location?) 173 | int score = 0; 174 | score += (header->speed == 2 || header->speed == 3) ? 5 : -4; 175 | score += (header->type <= 3 || header->type == 5) ? 5 : -2; 176 | score += (header->coprocessor <= 5 || header->coprocessor >= 0xe) ? 5 : -2; 177 | score += (header->chips <= 6 || header->chips == 9 || header->chips == 0xa) ? 5 : -2; 178 | score += (header->region <= 0x14) ? 5 : -2; 179 | score += (header->checksum + header->checksumComplement == 0xffff) ? 8 : -6; 180 | uint16_t resetVector = data[location + 0x3c] | (data[location + 0x3d] << 8); 181 | score += (resetVector >= 0x8000) ? 8 : -20; 182 | // check first opcode after reset 183 | int opcodeLoc = location + 0x40 - 0x8000 + (resetVector & 0x7fff); 184 | uint8_t opcode = 0xff; 185 | if(opcodeLoc < length) { 186 | opcode = data[opcodeLoc]; 187 | } else { 188 | score -= 14; 189 | } 190 | if(opcode == 0x78 || opcode == 0x18) { 191 | // sei, clc (for clc:xce) 192 | score += 6; 193 | } 194 | if(opcode == 0x4c || opcode == 0x5c || opcode == 0x9c) { 195 | // jmp abs, jml abl, stz abs 196 | score += 3; 197 | } 198 | if(opcode == 0x00 || opcode == 0xff || opcode == 0xdb) { 199 | // brk, sbc alx, stp 200 | score -= 6; 201 | } 202 | header->score = score; 203 | } 204 | -------------------------------------------------------------------------------- /src/snes/spc.h: -------------------------------------------------------------------------------- 1 | #ifndef SPC_H 2 | #define SPC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct Spc Spc; 11 | 12 | #include "apu.h" 13 | #include "saveload.h" 14 | 15 | struct Spc { 16 | Apu* apu; 17 | // registers 18 | uint8_t a; 19 | uint8_t x; 20 | uint8_t y; 21 | uint8_t sp; 22 | uint16_t pc; 23 | // flags 24 | bool c; 25 | bool z; 26 | bool v; 27 | bool n; 28 | bool i; 29 | bool h; 30 | bool p; 31 | bool b; 32 | // stopping 33 | bool stopped; 34 | // internal use 35 | uint8_t cyclesUsed; // indicates how many cycles an opcode used 36 | }; 37 | 38 | Spc* spc_init(Apu* apu); 39 | void spc_free(Spc* spc); 40 | void spc_reset(Spc* spc); 41 | int spc_runOpcode(Spc* spc); 42 | void spc_saveload(Spc *spc, SaveLoadFunc *func, void *ctx); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/spc_player.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "types.h" 4 | #include "snes/dsp.h" 5 | 6 | typedef struct Channel { 7 | uint16 pattern_order_ptr_for_chan; 8 | uint8 index; 9 | uint8 note_ticks_left; 10 | uint8 note_keyoff_ticks_left; 11 | uint8 subroutine_num_loops; 12 | uint8 volume_fade_ticks; 13 | uint8 pan_num_ticks; 14 | uint8 pitch_slide_length; 15 | uint8 pitch_slide_delay_left; 16 | uint8 vibrato_hold_count; 17 | uint8 vib_depth; 18 | uint8 tremolo_hold_count; 19 | uint8 tremolo_depth; 20 | uint8 vibrato_change_count; 21 | uint8 note_length; 22 | uint8 note_gate_off_fixedpt; 23 | uint8 channel_volume_master; 24 | uint8 instrument_id; 25 | uint16 instrument_pitch_base; 26 | uint16 saved_pattern_ptr; 27 | uint16 pattern_start_ptr; 28 | uint8 pitch_envelope_num_ticks; 29 | uint8 pitch_envelope_delay; 30 | uint8 pitch_envelope_direction; 31 | uint8 pitch_envelope_slide_value; 32 | uint8 vibrato_count; 33 | uint8 vibrato_rate; 34 | uint8 vibrato_delay_ticks; 35 | uint8 vibrato_fade_num_ticks; 36 | uint8 vibrato_fade_add_per_tick; 37 | uint8 vibrato_depth_target; 38 | uint8 tremolo_count; 39 | uint8 tremolo_rate; 40 | uint8 tremolo_delay_ticks; 41 | uint8 channel_transposition; 42 | uint16 channel_volume; 43 | uint16 volume_fade_addpertick; 44 | uint8 volume_fade_target; 45 | uint8 final_volume; 46 | uint16 pan_value; 47 | uint16 pan_add_per_tick; 48 | uint8 pan_target_value; 49 | uint8 pan_flag_with_phase_invert; 50 | uint16 pitch; 51 | uint16 pitch_add_per_tick; 52 | uint8 pitch_target; 53 | uint8 fine_tune; 54 | uint8 cutk; 55 | } Channel; 56 | 57 | typedef struct SfxRegs { 58 | uint8 priority; 59 | uint8 chan_idx_x2; 60 | uint16 chan_voice_bitset_ptr; 61 | uint8 cur_sound; 62 | uint16 chan_voice_mask_ptr; 63 | uint16 chan_voice_index_ptr; 64 | uint8 some_value; 65 | uint16 cur_ptr; 66 | uint8 enabled_voices; 67 | uint8 voices_to_setup; 68 | uint8 chan_idx; 69 | uint8 voices_remaining; 70 | uint8 cur_sound_index; 71 | uint8 voice_id; 72 | uint8 init_flag; 73 | uint8 voice_index; 74 | } SfxRegs; 75 | typedef struct SfxChan { 76 | uint8 voice_bitset; 77 | uint8 instr_timer; 78 | uint8 subnote; 79 | uint8 repeat_pt; 80 | uint8 note; 81 | uint8 ptr_idx; 82 | uint8 voice_index; 83 | uint8 voice_index_mult8; 84 | uint8 release_timer; 85 | uint8 volume; 86 | uint8 target_note; 87 | uint8 phase_invert; 88 | uint16 ptr; 89 | uint8 repeat_ctr; 90 | uint8 enable_pitch_slide; 91 | uint8 release_flag; 92 | uint8 enable_pitch_slide_legato; 93 | uint16 adsr_settings; 94 | uint8 channel_mask; 95 | uint8 legato; 96 | uint8 disable; 97 | uint8 subnote_delta; 98 | uint8 update_adsr; 99 | } SfxChan; 100 | 101 | 102 | typedef struct SpcPlayer { 103 | DspRegWriteHistory *reg_write_history; 104 | uint8 timer_cycles; 105 | Dsp *dsp; 106 | 107 | uint8 port_to_snes[4]; 108 | uint8 last_value_from_snes[4]; 109 | uint16 counter_sf0c; 110 | uint16 always_zero; 111 | uint16 temp_accum; 112 | uint8 ttt; 113 | uint8 did_affect_volumepitch_flag; 114 | uint16 addr0; 115 | uint16 addr1; 116 | uint16 lfsr_value; 117 | uint8 is_chan_on; 118 | uint8 fast_forward; 119 | uint16 word_20; 120 | uint16 music_ptr_toplevel; 121 | uint8 block_count; 122 | uint8 sfx_timer_accum; 123 | uint8 chn; 124 | uint8 key_ON; 125 | uint8 key_OFF; 126 | uint8 cur_chan_bit; 127 | uint8 reg_FLG; 128 | uint8 reg_NON; 129 | uint8 reg_EON; 130 | uint8 echo_stored_time; 131 | uint8 echo_parameter_EDL; 132 | uint8 reg_EFB; 133 | uint8 global_transposition; 134 | uint8 main_tempo_accum; 135 | uint16 tempo; 136 | uint8 tempo_fade_num_ticks; 137 | uint8 tempo_fade_final; 138 | uint16 tempo_fade_add; 139 | uint16 master_volume; 140 | uint8 master_volume_fade_ticks; 141 | uint8 master_volume_fade_target; 142 | uint16 master_volume_fade_add_per_tick; 143 | uint8 vol_dirty; 144 | uint8 percussion_base_id; 145 | uint16 echo_volume_left; 146 | uint16 echo_volume_right; 147 | uint16 echo_volume_fade_add_left; 148 | uint16 echo_volume_fade_add_right; 149 | uint8 echo_volume_fade_ticks; 150 | uint8 echo_volume_fade_target_left; 151 | uint8 echo_volume_fade_target_right; 152 | uint16 p_echoBuffer; 153 | uint16 memset_ptr; 154 | uint8 memset_ctr; 155 | uint8 disable_sfx2; 156 | uint8 byte_4B1; 157 | Channel channel[8]; 158 | SfxRegs sfx1; 159 | SfxRegs sfx2; 160 | SfxRegs sfx3; 161 | SfxChan sfx_chans_1[4]; 162 | SfxChan sfx_chans_2[2]; 163 | SfxChan sfx_chans_3[2]; 164 | uint8 last_written_edl; 165 | uint8 input_ports[4]; 166 | uint8 ram[65536]; // rest of ram 167 | } SpcPlayer; 168 | 169 | SpcPlayer *SpcPlayer_Create(); 170 | void SpcPlayer_GenerateSamples(SpcPlayer *p); 171 | void SpcPlayer_Initialize(SpcPlayer *p); 172 | void SpcPlayer_Upload(SpcPlayer *p, const uint8_t *data); 173 | void SpcPlayer_CopyVariablesFromRam(SpcPlayer *p); 174 | void SpcPlayer_CopyVariablesToRam(SpcPlayer *p); 175 | -------------------------------------------------------------------------------- /src/tracing.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef TRACING_H 3 | #define TRACING_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "snes/snes.h" 12 | 13 | void getProcessorStateCpu(Snes* snes, char* line); 14 | void getProcessorStateSpc(Apu* snes, char* line); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #ifndef SM_TYPES_H_ 2 | #define SM_TYPES_H_ 3 | 4 | #pragma warning(disable: 4244) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined(_WIN32) && !COMPILER_TCC 11 | #include 12 | #define assert _ASSERTE 13 | #else 14 | #include 15 | #endif 16 | 17 | 18 | typedef uint8_t uint8; 19 | typedef int8_t int8; 20 | typedef uint16_t uint16; 21 | typedef int16_t int16; 22 | typedef uint32_t uint32; 23 | typedef int32_t int32; 24 | typedef uint64_t uint64; 25 | typedef int64_t int64; 26 | typedef unsigned int uint; 27 | 28 | typedef uint16 VoidP; 29 | 30 | #define arraysize(x) sizeof(x)/sizeof(x[0]) 31 | #define sign8(x) ((x) & 0x80) 32 | #define sign16(x) ((x) & 0x8000) 33 | #define sign32(x) ((x) & 0x80000000) 34 | #define load24(x) ((*(uint32*)&(x))&0xffffff) 35 | 36 | #ifdef _MSC_VER 37 | #define countof _countof 38 | #define NORETURN __declspec(noreturn) 39 | #define FORCEINLINE __forceinline 40 | #define NOINLINE __declspec(noinline) 41 | #else 42 | #define countof(a) (sizeof(a)/sizeof(*(a))) 43 | #define NORETURN 44 | #define FORCEINLINE inline 45 | #define NOINLINE 46 | #endif 47 | 48 | #ifdef _DEBUG 49 | #define kDebugFlag 1 50 | #else 51 | #define kDebugFlag 0 52 | #endif 53 | 54 | static FORCEINLINE uint16 abs16(uint16 t) { return sign16(t) ? -t : t; } 55 | static FORCEINLINE uint8 abs8(uint8 t) { return sign8(t) ? -t : t; } 56 | static FORCEINLINE int IntMin(int a, int b) { return a < b ? a : b; } 57 | static FORCEINLINE int IntMax(int a, int b) { return a > b ? a : b; } 58 | static FORCEINLINE uint UintMin(uint a, uint b) { return a < b ? a : b; } 59 | static FORCEINLINE uint UintMax(uint a, uint b) { return a > b ? a : b; } 60 | 61 | // windows.h defines this too 62 | #ifdef HIBYTE 63 | #undef HIBYTE 64 | #endif 65 | 66 | #define BYTE(x) (*(uint8*)&(x)) 67 | #define WORD(x) (*(uint16*)&(x)) 68 | #define DWORD(x) (*(uint32*)&(x)) 69 | #define XY(x, y) ((y)*64+(x)) 70 | 71 | static inline uint16 swap16(uint16 v) { return (v << 8) | (v >> 8); } 72 | 73 | void NORETURN Die(const char *error); 74 | void Warning(const char *error); 75 | 76 | // compile time assertion 77 | #define __CASSERT_N0__(l) COMPILE_TIME_ASSERT_ ## l 78 | #define __CASSERT_N1__(l) __CASSERT_N0__(l) 79 | #define CASSERT(cnd) typedef char __CASSERT_N1__(__LINE__) [(cnd) ? 1 : -1] 80 | 81 | #define USE_COROUTINES 1 82 | 83 | typedef uint8_t CoroutineRet; 84 | 85 | #define COROUTINE_BEGIN(state_var, start_pos) \ 86 | uint8 *_state_variable_ = &(state_var); \ 87 | switch(*_state_variable_) { \ 88 | case start_pos: 89 | 90 | #define COROUTINE_MANUAL_POS(position) \ 91 | case (position): 92 | 93 | #define COROUTINE_AWAIT(position, function) \ 94 | case (position): \ 95 | { uint8 _coret_ = (function); \ 96 | if (_coret_) { \ 97 | *_state_variable_ = (position); \ 98 | return _coret_; \ 99 | }} 100 | 101 | #define COROUTINE_AWAIT_ONLY(function) \ 102 | { uint8 _coret_ = (function); \ 103 | if (_coret_) \ 104 | return _coret_; \ 105 | } 106 | 107 | #define COROUTINE_END(position) \ 108 | } \ 109 | *_state_variable_ = (position); \ 110 | return 0 111 | 112 | #define kCoroutineNone 0 113 | 114 | /* 90 */ 115 | #pragma pack(push, 1) 116 | typedef struct LongPtr { 117 | VoidP addr; 118 | uint8 bank; 119 | } LongPtr; 120 | #pragma pack (pop) 121 | 122 | static inline bool __CFADD__uint16(uint16 x, uint16 y) { return (uint16)(x) > (uint16)(x + y); } 123 | static inline bool __CFADD__uint8(uint8 x, uint8 y) { return (uint8)(x) > (uint8)(x + y); } 124 | 125 | typedef struct PairU16 { 126 | uint16 k, j; 127 | } PairU16; 128 | 129 | typedef struct Point16U { 130 | uint16 x, y; 131 | } Point16U; 132 | 133 | typedef struct Rect16U { 134 | uint16 x, y, w, h; 135 | } Rect16U; 136 | 137 | // Some convenience macros to make partial accesses nicer 138 | #define LAST_IND(x,part_type) (sizeof(x)/sizeof(part_type) - 1) 139 | #define HIGH_IND(x,part_type) LAST_IND(x,part_type) 140 | #define LOW_IND(x,part_type) 0 141 | // first unsigned macros: 142 | #define BYTEn(x, n) (*((uint8*)&(x)+n)) 143 | #define WORDn(x, n) (*((uint16*)&(x)+n)) 144 | 145 | #define LOBYTE(x) BYTEn(x,LOW_IND(x,uint8)) 146 | #define LOWORD(x) WORDn(x,LOW_IND(x,uint16)) 147 | #define HIBYTE(x) BYTEn(x,HIGH_IND(x,uint8)) 148 | #define HIWORD(x) WORDn(x,HIGH_IND(x,uint16)) 149 | 150 | #define GET_HIBYTE(x) (((x) & 0xff00) >> 8) 151 | 152 | #define PAIR16(high, low) ((uint16)((high) << 8) | (uint8)low) 153 | 154 | // Generate a pair of operands. 155 | #define __PAIR32__(high, low) (((uint32) (high) << 16) | (uint16)(low)) 156 | #define IPAIR32(high, low) ((int32)(( (high) << 16) | (uint16)(low))) 157 | 158 | // Helper functions to represent some assembly instructions. 159 | 160 | // compile time assertion 161 | #define __CASSERT_N0__(l) COMPILE_TIME_ASSERT_ ## l 162 | #define __CASSERT_N1__(l) __CASSERT_N0__(l) 163 | #define CASSERT(cnd) typedef char __CASSERT_N1__(__LINE__) [(cnd) ? 1 : -1] 164 | 165 | 166 | #endif // SM_TYPES_H_ 167 | 168 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include 3 | #include 4 | #include 5 | 6 | char *NextDelim(char **s, int sep) { 7 | char *r = *s; 8 | if (r) { 9 | while (r[0] == ' ' || r[0] == '\t') 10 | r++; 11 | char *t = strchr(r, sep); 12 | *s = t ? (*t++ = 0, t) : NULL; 13 | } 14 | return r; 15 | } 16 | 17 | static inline int ToLower(int a) { 18 | return a + (a >= 'A' && a <= 'Z') * 32; 19 | } 20 | 21 | bool StringEqualsNoCase(const char *a, const char *b) { 22 | for (;;) { 23 | int aa = ToLower(*a++), bb = ToLower(*b++); 24 | if (aa != bb) 25 | return false; 26 | if (aa == 0) 27 | return true; 28 | } 29 | } 30 | 31 | const char *StringStartsWithNoCase(const char *a, const char *b) { 32 | for (;; a++, b++) { 33 | int aa = ToLower(*a), bb = ToLower(*b); 34 | if (bb == 0) 35 | return a; 36 | if (aa != bb) 37 | return NULL; 38 | } 39 | } 40 | 41 | uint8 *ReadWholeFile(const char *name, size_t *length) { 42 | FILE *f = fopen(name, "rb"); 43 | if (f == NULL) 44 | return NULL; 45 | fseek(f, 0, SEEK_END); 46 | size_t size = ftell(f); 47 | rewind(f); 48 | uint8 *buffer = (uint8 *)malloc(size + 1); 49 | if (!buffer) Die("malloc failed"); 50 | // Always zero terminate so this function can be used also for strings. 51 | buffer[size] = 0; 52 | if (fread(buffer, 1, size, f) != size) 53 | Die("fread failed"); 54 | fclose(f); 55 | if (length) *length = size; 56 | return buffer; 57 | } 58 | 59 | char *NextLineStripComments(char **s) { 60 | char *p = *s; 61 | if (p == NULL) 62 | return NULL; 63 | // find end of line 64 | char *eol = strchr(p, '\n'); 65 | *s = eol ? eol + 1 : NULL; 66 | eol = eol ? eol : p + strlen(p); 67 | // strip comments 68 | char *comment = (char*)memchr(p, '#', eol - p); 69 | eol = comment ? comment : eol; 70 | // strip trailing whitespace 71 | while (eol > p && (eol[-1] == '\r' || eol[-1] == ' ' || eol[-1] == '\t')) 72 | eol--; 73 | *eol = 0; 74 | // strip leading whitespace 75 | while (p[0] == ' ' || p[0] == '\t') 76 | p++; 77 | return p; 78 | } 79 | 80 | // Return the next possibly quoted string, space separated, or the empty string 81 | char *NextPossiblyQuotedString(char **s) { 82 | char *r = *s, *t; 83 | while (*r == ' ' || *r == '\t') 84 | r++; 85 | if (*r == '"') { 86 | for (t = ++r; *t && *t != '"'; t++); 87 | } else { 88 | for (t = r; *t && *t != ' ' && *t != '\t'; t++); 89 | } 90 | if (*t) *t++ = 0; 91 | while (*t == ' ' || *t == '\t') 92 | t++; 93 | *s = t; 94 | return r; 95 | } 96 | 97 | char *ReplaceFilenameWithNewPath(const char *old_path, const char *new_path) { 98 | size_t olen = strlen(old_path); 99 | size_t nlen = strlen(new_path) + 1; 100 | while (olen && old_path[olen - 1] != '/' && old_path[olen - 1] != '\\') 101 | olen--; 102 | char *result = (char *)malloc(olen + nlen); 103 | memcpy(result, old_path, olen); 104 | memcpy(result + olen, new_path, nlen); 105 | return result; 106 | } 107 | 108 | char *SplitKeyValue(char *p) { 109 | char *equals = strchr(p, '='); 110 | if (equals == NULL) 111 | return NULL; 112 | char *kr = equals; 113 | while (kr > p && (kr[-1] == ' ' || kr[-1] == '\t')) 114 | kr--; 115 | *kr = 0; 116 | char *v = equals + 1; 117 | while (v[0] == ' ' || v[0] == '\t') 118 | v++; 119 | return v; 120 | } 121 | 122 | const char *SkipPrefix(const char *big, const char *little) { 123 | for (; *little; big++, little++) { 124 | if (*little != *big) 125 | return NULL; 126 | } 127 | return big; 128 | } 129 | 130 | void StrSet(char **rv, const char *s) { 131 | char *news = strdup(s); 132 | char *old = *rv; 133 | *rv = news; 134 | free(old); 135 | } 136 | 137 | char *StrFmt(const char *fmt, ...) { 138 | char buf[4096]; 139 | va_list va; 140 | va_start(va, fmt); 141 | int n = vsnprintf(buf, sizeof(buf), fmt, va); 142 | if (n < 0 || n >= sizeof(buf)) Die("vsnprintf failed"); 143 | va_end(va); 144 | return strdup(buf); 145 | } 146 | 147 | void ByteArray_Resize(ByteArray *arr, size_t new_size) { 148 | arr->size = new_size; 149 | if (new_size > arr->capacity) { 150 | size_t minsize = arr->capacity + (arr->capacity >> 1) + 8; 151 | arr->capacity = new_size < minsize ? minsize : new_size; 152 | uint8 *data = (uint8*)realloc(arr->data, arr->capacity); 153 | if (!data) Die("memory allocation failed"); 154 | arr->data = data; 155 | } 156 | } 157 | 158 | void ByteArray_Destroy(ByteArray *arr) { 159 | void *data = arr->data; 160 | arr->data = NULL; 161 | free(data); 162 | } 163 | 164 | void ByteArray_AppendData(ByteArray *arr, const uint8 *data, size_t data_size) { 165 | ByteArray_Resize(arr, arr->size + data_size); 166 | memcpy(arr->data + arr->size - data_size, data, data_size); 167 | } 168 | 169 | void ByteArray_AppendByte(ByteArray *arr, uint8 v) { 170 | ByteArray_Resize(arr, arr->size + 1); 171 | arr->data[arr->size - 1] = v; 172 | } 173 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef ZELDA3_UTIL_H_ 2 | #define ZELDA3_UTIL_H_ 3 | 4 | #include "types.h" 5 | 6 | typedef struct SDL_Window SDL_Window; 7 | 8 | struct RendererFuncs { 9 | bool (*Initialize)(SDL_Window *window); 10 | void (*Destroy)(); 11 | void (*BeginDraw)(int width, int height, uint8 **pixels, int *pitch); 12 | void (*EndDraw)(); 13 | }; 14 | 15 | 16 | typedef struct ByteArray { 17 | uint8 *data; 18 | size_t size, capacity; 19 | } ByteArray; 20 | 21 | void ByteArray_Resize(ByteArray *arr, size_t new_size); 22 | void ByteArray_Destroy(ByteArray *arr); 23 | void ByteArray_AppendData(ByteArray *arr, const uint8 *data, size_t data_size); 24 | void ByteArray_AppendByte(ByteArray *arr, uint8 v); 25 | 26 | uint8 *ReadWholeFile(const char *name, size_t *length); 27 | char *NextDelim(char **s, int sep); 28 | char *NextLineStripComments(char **s); 29 | char *NextPossiblyQuotedString(char **s); 30 | char *SplitKeyValue(char *p); 31 | bool StringEqualsNoCase(const char *a, const char *b); 32 | const char *StringStartsWithNoCase(const char *a, const char *b); 33 | bool ParseBool(const char *value, bool *result); 34 | const char *SkipPrefix(const char *big, const char *little); 35 | void StrSet(char **rv, const char *s); 36 | char *StrFmt(const char *fmt, ...); 37 | char *ReplaceFilenameWithNewPath(const char *old_path, const char *new_path); 38 | 39 | #endif // ZELDA3_UTIL_H_ -------------------------------------------------------------------------------- /src/variables_extra.h: -------------------------------------------------------------------------------- 1 | 2 | // Cinematic stuff 3 | #define cinematic_var5 (*(uint16*)(g_ram+0x198D)) 4 | #define cinematic_var6 (*(uint16*)(g_ram+0x198F)) 5 | #define cinematic_var7 (*(uint16*)(g_ram+0x1991)) 6 | #define cinematic_var8 (*(uint16*)(g_ram+0x1993)) 7 | #define cinematic_var9 (*(uint16*)(g_ram+0x1995)) 8 | #define cinematic_var10 (*(uint16*)(g_ram+0x1997)) 9 | 10 | #define g_word_7E1999 (*(uint16*)(g_ram+0x1999)) 11 | #define g_word_7E199B (*(uint16*)(g_ram+0x199B)) 12 | #define g_word_7E199D (*(uint16*)(g_ram+0x199D)) 13 | #define g_word_7E199F (*(uint16*)(g_ram+0x199F)) 14 | #define mode7_obj_instr_ptr ((uint16*)(g_ram+0x19A1)) 15 | #define mode7_obj_preinstr_func ((uint16*)(g_ram+0x19A5)) 16 | #define mode7_obj_instr_timer ((uint16*)(g_ram+0x19A9)) 17 | #define mode7_obj_goto_timer ((uint16*)(g_ram+0x19AD)) 18 | #define mode7_cur_index (*(uint16*)(g_ram+0x19B1)) 19 | #define mode7_spawn_param (*(uint16*)(g_ram+0x19B3)) 20 | #define cinematicbg_arr1 ((uint16*)(g_ram+0x19B5)) 21 | #define cinematicbg_instr_ptr ((uint16*)(g_ram+0x19CD)) 22 | #define cinematicbg_preinstr ((uint16*)(g_ram+0x19D5)) 23 | #define cinematicbg_instr_timer ((uint16*)(g_ram+0x19DD)) 24 | #define cinematicbg_arr6 ((uint16*)(g_ram+0x19E5)) 25 | #define cinematicbg_var2 (*(uint16*)(g_ram+0x19ED)) 26 | #define cinematic_enable_objs (*(uint16*)(g_ram+0x19F1)) 27 | #define cinematic_enable_bg_tilemap (*(uint16*)(g_ram+0x19F3)) 28 | #define cinematicbg_var3 (*(uint16*)(g_ram+0x19F5)) 29 | #define cinematic_var21 (*(uint16*)(g_ram+0x19F7)) 30 | #define cinematic_var22 (*(uint16*)(g_ram+0x19F9)) 31 | #define cinematic_var23 (*(uint16*)(g_ram+0x19FB)) 32 | #define cinematic_var24 (*(uint16*)(g_ram+0x19FD)) 33 | #define cinematic_var25 (*(uint16*)(g_ram+0x19FF)) 34 | #define cinematic_var26 (*(uint16*)(g_ram+0x1A01)) 35 | #define eproj_pre_instr ((uint16*)(g_ram+0x1A03)) 36 | #define cinematic_var20 (*(uint16*)(g_ram+0x1A37)) 37 | #define cinematic_var19 (*(uint16*)(g_ram+0x1A47)) 38 | #define cinematic_var4 (*(uint16*)(g_ram+0x1A49)) 39 | #define cinematic_var13 (*(uint16*)(g_ram+0x1A4B)) 40 | #define cinematic_var14 (*(uint16*)(g_ram+0x1A4D)) 41 | #define cinematic_var17 (*(uint16*)(g_ram+0x1A4F)) 42 | #define cinematic_var12 (*(uint16*)(g_ram+0x1A51)) 43 | #define cinematic_var18 (*(uint16*)(g_ram+0x1A53)) 44 | #define cinematic_var15 (*(uint16*)(g_ram+0x1A57)) 45 | #define cinematic_obj_index (*(uint16*)(g_ram+0x1A59)) 46 | #define cinematicspr_whattodraw ((uint16*)(g_ram+0x1A5D)) 47 | #define cinematicbg_arr7 ((uint16*)(g_ram+0x1A7D)) 48 | #define cinematicbg_arr8 ((uint16*)(g_ram+0x1A9D)) 49 | #define cinematicbg_arr9 ((uint16*)(g_ram+0x1ABD)) 50 | #define cinematicspr_arr6 ((uint16*)(g_ram+0x1ADD)) 51 | #define cinematicspr_arr7 ((uint16*)(g_ram+0x1AFD)) 52 | #define cinematicspr_instr_ptr ((uint16*)(g_ram+0x1B1D)) 53 | #define cinematicspr_preinstr_func ((uint16*)(g_ram+0x1B3D)) 54 | #define cinematicspr_instr_timer ((uint16*)(g_ram+0x1B5D)) 55 | #define cinematicspr_goto_timer ((uint16*)(g_ram+0x1B7D)) 56 | #define cinematic_spawn_param (*(uint16*)(g_ram+0x1B9D)) 57 | #define cinematic_var11 (*(uint16*)(g_ram+0x1B9F)) 58 | #define cinematicbg_var1 (*(uint16*)(g_ram+0x1BA1)) 59 | #define cinematic_var16 (*(uint16*)(g_ram+0x1BA3)) 60 | 61 | 62 | #define optionsmenu_index (*(uint16*)(g_ram+0x1A8F)) 63 | #define options_menu_init_param (*(uint16*)(g_ram+0x1A93)) 64 | #define optionsmenu_cur_data ((uint16*)(g_ram+0x1A9D)) 65 | #define optionsmenu_arr8 ((uint16*)(g_ram+0x1AAD)) 66 | #define optionsmenu_arr9 ((uint16*)(g_ram+0x1ABD)) 67 | #define optionsmenu_arr10 ((uint16*)(g_ram+0x1ACD)) 68 | #define optionsmenu_arr6 ((uint16*)(g_ram+0x1ADD)) 69 | #define optionsmenu_arr7 ((uint16*)(g_ram+0x1AED)) 70 | #define optionsmenu_instr_ptr ((uint16*)(g_ram+0x1AFD)) 71 | #define optionsmenu_arr1 ((uint16*)(g_ram+0x1B0D)) 72 | #define optionsmenu_instr_timer ((uint16*)(g_ram+0x1B1D)) 73 | #define optionsmenu_arr5 ((uint16*)(g_ram+0x1B2D)) 74 | #define optionsmenu_arr11 ((uint16*)(g_ram+0x1B3D)) 75 | -------------------------------------------------------------------------------- /third_party/.gitignore: -------------------------------------------------------------------------------- 1 | /tcc/ 2 | /SDL2-2.24.1/ 3 | /gl_core/*.o --------------------------------------------------------------------------------