├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── data ├── 0 │ ├── stage00.stg │ ├── stage01.stg │ ├── stage02.stg │ ├── stage03.stg │ ├── stage04.stg │ ├── stage05.stg │ ├── stage06.stg │ ├── stage07.stg │ ├── stage08.stg │ ├── stage09.stg │ ├── stage0a.stg │ ├── stage0b.stg │ ├── stage0c.stg │ ├── stage0d.stg │ ├── stage0e.stg │ └── stage0f.stg ├── 1 │ ├── stage10.stg │ ├── stage11.stg │ ├── stage12.stg │ ├── stage13.stg │ ├── stage14.stg │ ├── stage15.stg │ ├── stage16.stg │ ├── stage17.stg │ ├── stage18.stg │ ├── stage19.stg │ ├── stage1a.stg │ ├── stage1b.stg │ ├── stage1c.stg │ ├── stage1d.stg │ ├── stage1e.stg │ └── stage1f.stg ├── 2 │ ├── stage20.stg │ ├── stage21.stg │ ├── stage22.stg │ ├── stage23.stg │ ├── stage24.stg │ ├── stage25.stg │ ├── stage26.stg │ ├── stage27.stg │ ├── stage28.stg │ ├── stage29.stg │ ├── stage2a.stg │ ├── stage2b.stg │ ├── stage2c.stg │ ├── stage2d.stg │ ├── stage2e.stg │ └── stage2f.stg ├── 3 │ ├── stage30.stg │ ├── stage31.stg │ ├── stage32.stg │ ├── stage33.stg │ ├── stage34.stg │ ├── stage35.stg │ ├── stage36.stg │ ├── stage37.stg │ ├── stage38.stg │ ├── stage39.stg │ ├── stage3a.stg │ ├── stage3b.stg │ ├── stage3c.stg │ ├── stage3d.stg │ ├── stage3e.stg │ └── stage3f.stg ├── 4 │ ├── stage40.stg │ ├── stage41.stg │ ├── stage42.stg │ ├── stage43.stg │ ├── stage44.stg │ ├── stage45.stg │ ├── stage46.stg │ ├── stage47.stg │ ├── stage48.stg │ ├── stage49.stg │ ├── stage4a.stg │ ├── stage4b.stg │ ├── stage4c.stg │ ├── stage4d.stg │ ├── stage4e.stg │ └── stage4f.stg ├── 5 │ ├── stage50.stg │ ├── stage51.stg │ ├── stage52.stg │ ├── stage53.stg │ ├── stage54.stg │ ├── stage55.stg │ ├── stage56.stg │ ├── stage57.stg │ ├── stage58.stg │ ├── stage59.stg │ ├── stage5a.stg │ ├── stage5b.stg │ ├── stage5c.stg │ ├── stage5d.stg │ └── stage5e.stg └── main │ ├── ArmsItem.bsc │ ├── Credit.bsc │ ├── Credit.crd │ ├── StageSel.bsc │ ├── main.gfx │ ├── main.sfx │ └── npc.tbl ├── doc └── screen.png ├── iso.xml ├── src ├── engine │ ├── common.c │ ├── common.h │ ├── common_a.s │ ├── exception.c │ ├── exception.h │ ├── filesystem.c │ ├── filesystem.h │ ├── graphics.c │ ├── graphics.h │ ├── input.c │ ├── input.h │ ├── loading.inc │ ├── math.c │ ├── math.h │ ├── mcrd.c │ ├── mcrd.h │ ├── memory.c │ ├── memory.h │ ├── org.c │ ├── org.h │ ├── saveicon.inc │ ├── sound.c │ ├── sound.h │ ├── spu.c │ ├── spu.h │ ├── surfacelist.h │ ├── timer.c │ └── timer.h ├── game │ ├── boss_act │ │ ├── boss_act.h │ │ ├── boss_act_balfrog.c │ │ ├── boss_act_ballos.c │ │ ├── boss_act_core.c │ │ ├── boss_act_heavy_press.c │ │ ├── boss_act_ironhead.c │ │ ├── boss_act_monster_x.c │ │ ├── boss_act_omega.c │ │ ├── boss_act_twins.c │ │ └── boss_act_undead_core.c │ ├── bullet.c │ ├── bullet.h │ ├── camera.c │ ├── camera.h │ ├── caret.c │ ├── caret.h │ ├── credits.c │ ├── credits.h │ ├── dmgnum.c │ ├── dmgnum.h │ ├── game.c │ ├── game.h │ ├── hit.h │ ├── hit_bullet.c │ ├── hit_npc.c │ ├── hit_player.c │ ├── hud.c │ ├── hud.h │ ├── menu.c │ ├── menu.h │ ├── npc.c │ ├── npc.h │ ├── npc_act │ │ ├── npc_act.h │ │ ├── npc_act_000.c │ │ ├── npc_act_020.c │ │ ├── npc_act_040.c │ │ ├── npc_act_060.c │ │ ├── npc_act_080.c │ │ ├── npc_act_100.c │ │ ├── npc_act_120.c │ │ ├── npc_act_140.c │ │ ├── npc_act_160.c │ │ ├── npc_act_180.c │ │ ├── npc_act_200.c │ │ ├── npc_act_220.c │ │ ├── npc_act_240.c │ │ ├── npc_act_260.c │ │ ├── npc_act_280.c │ │ ├── npc_act_300.c │ │ ├── npc_act_320.c │ │ └── npc_act_340.c │ ├── npctab.c │ ├── npctab.h │ ├── player.c │ ├── player.h │ ├── player_arms.c │ ├── profile.c │ ├── profile.h │ ├── stage.c │ ├── stage.h │ ├── tsc.c │ └── tsc.h └── main.c ├── system.cnf └── tools ├── Makefile ├── data ├── Fade4bpp.pbm ├── FontAscii.pbm └── SaveIcon.pbm ├── make_banks.sh ├── src ├── common │ ├── common.h │ ├── dr_wav.h │ ├── musiclist.h │ ├── stage.c │ ├── stage.h │ ├── stb_image.h │ ├── stb_image_write.h │ ├── surface.c │ ├── surface.h │ ├── tsc.c │ ├── tsc.h │ ├── vram.c │ └── vram.h ├── creditspack.c ├── fontgen.cpp ├── img2h.c ├── libpsxav │ ├── adpcm.c │ ├── cdrom.c │ └── libpsxav.h ├── orgconv.c ├── sfxconv.c ├── stagepack.c ├── surfpack.c ├── trigcalc.c └── tscc.c ├── stagelists └── stage_all.lst └── surflists └── surf_main.lst /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tools/data/* 3 | tools/out/* 4 | !tools/data/FontAscii.pbm 5 | !tools/data/Fade4bpp.pbm 6 | !tools/data/SaveIcon.pbm 7 | *.o 8 | *.elf 9 | *.exe 10 | *.iso 11 | *.psb 12 | *.bsp 13 | *.dep 14 | .vscode 15 | compile_flags.txt 16 | compile_commands.json 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nugget"] 2 | path = nugget 3 | url = https://github.com/pcsx-redux/nugget 4 | ignore = untracked 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is based on CSE2, a decompilation of the original freeware Cave Story executables. 2 | Original Cave Story code and assets belong to Daisuke "Pixel" Amaya. 3 | 4 | Any modifications, custom tools and PSX platform specific code are licensed under the 5 | following license: 6 | 7 | MIT License 8 | 9 | Copyright (c) 2019 Regan "cuckydev" Green 10 | Copyright (c) 2019-2020 Clownacy 11 | Copyright (c) 2019-2020 Gabriel Ravier 12 | Copyright (c) 2021 fgsfds 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = doukutsu 2 | TYPE = ps-exe 3 | 4 | LTOFLAGS ?= -flto 5 | 6 | CFLAGS := -Isrc -Inugget/psyq/include 7 | CFLAGS += -Wall -Wno-missing-braces -mno-check-zero-division 8 | 9 | LDFLAGS := -Lnugget/psyq/lib -Wl,--start-group -lcard -lapi -lc2 -lcd -letc -lgpu -lspu -Wl,--end-group 10 | LDFLAGS += $(LTOFLAGS) 11 | 12 | SRCDIR = src 13 | SRCSUB = engine game game/npc_act game/boss_act 14 | 15 | SRCDIRS = $(foreach dir,$(SRCSUB),$(SRCDIR)/$(dir)) $(SRCDIR) 16 | CFILES = $(foreach dir,$(SRCDIRS),$(wildcard $(dir)/*.c)) 17 | CPPFILES = $(foreach dir,$(SRCDIRS),$(wildcard $(dir)/*.cpp)) 18 | AFILES = $(foreach dir,$(SRCDIRS),$(wildcard $(dir)/*.s)) 19 | SRCS = $(CFILES) $(CPPFILES) $(AFILES) 20 | SRCS += nugget/common/crt0/crt0.s 21 | 22 | default: all 23 | 24 | iso: $(TARGET).iso 25 | 26 | $(TARGET).iso: all 27 | mkpsxiso -y -q iso.xml 28 | 29 | .PHONY: iso 30 | 31 | include nugget/common.mk 32 | 33 | CPPFLAGS_Release += -O3 $(LTOFLAGS) 34 | LDFLAGS_Release += -O3 $(LTOFLAGS) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DoukutsuPSX 2 | 3 | ![Screenshot](doc/screen.png) 4 | 5 | This is a port of Cave Story for the Sony PlayStation. 6 | It is based on CSE2, which is a decompilation of the original freeware Cave Story executables. 7 | The port is still very much a work-in-progress, but it should be possible to at least complete the game. 8 | 9 | ### How to run 10 | 11 | First, either build the game yourself (see instructions below) or download the latest release from the Releases page. 12 | This will yield an ISO image. 13 | 14 | To play the game on a modded PlayStation, burn the ISO image to a CD-R with something like ImgBurn (tested on my modchipped SCPH-9002). 15 | It should also work fine on most emulators (tested on PCSX-Redux, no$psx and DuckStation). 16 | Please **do not** use outdated emulators like ePSXe or PCSX Reloaded, I won't fix issues that only appear in those. 17 | 18 | You will need a memory card with at least 1 free block to properly save the game. You can continue the game from 19 | Save Points when you die even if you don't have a memory card, though. Multitap is currently not supported. 20 | 21 | #### Default controls 22 | * `DPAD`: movement; 23 | * `CROSS`: jump/accept; 24 | * `SQUARE`: fire; 25 | * `TRIANGLE`: previous weapon; 26 | * `CIRCLE`: next weapon/cancel; 27 | * `START`: pause menu; 28 | * `L2`: item menu; 29 | * `R2`: map screen. 30 | 31 | Controls can be rebound in the Options menu. 32 | 33 | ### How to build 34 | 35 | 1. Obtain GNU Make and GCC for targets `mipsel-none-elf` or `mipsel-linux-gnu`: 36 | * on Windows: see [this section](https://github.com/ABelliqueux/nolibgs_hello_worlds#mips-toolchain-setup) or use WSL; 37 | * on Linux: see [this section](https://github.com/ABelliqueux/nolibgs_hello_worlds#install-your-distributions-mips-toolchain). 38 | 2. Obtain [mkpsxiso](https://github.com/Lameguy64/mkpsxiso) and ensure it is in `PATH`. 39 | 3. Clone this repository: `git clone --recursive https://github.com/fgsfdsfgs/doukutsupsx && cd doukutsupsx` 40 | 4. Obtain [converted PsyQ 4.7 libraries](http://psx.arthus.net/sdk/Psy-Q/psyq-4.7-converted-full.7z) and extract them into `nugget/psyq/`. 41 | 5. (Optional) If you have the PsyQ license files (`LICENSEA.DAT`, `LICENSEE.DAT`, `LICENSEJ.DAT`), put them into this folder and uncomment 42 | one of the `` lines in `iso.xml` to inject it into the resulting ISO. This will give it a proper boot logo and might be required 43 | by some BIOS versions (?). 44 | 6. Run `make iso`. This should produce an ISO file called `doukutsu.iso`. 45 | 46 | ### Credits 47 | 48 | * Daisuke "Pixel" Amaya for the original Cave Story and Aeon Genesis for the English translation; 49 | * CuckyDev, Clownacy, Gabriel Ravier and probably others for CSE2; 50 | * [PCSX-Redux authors](https://github.com/grumpycoders/pcsx-redux/blob/main/AUTHORS) for Nugget and PCSX-Redux itself; 51 | * Schnappy for [nolibgs_hello_worlds](https://github.com/ABelliqueux/nolibgs_hello_worlds) and the toolchain setup instructions; 52 | * Lameguy64 for PSn00bSDK, which was used for the earlier versions of this port, and for mkpsxiso; 53 | * Adrian "asie" Siekierka and Ben "GreaseMonkey" Russell for libpsxav; 54 | * Sean Barrett for stb_image; 55 | * David Reid for dr_wav; 56 | * axetion, CuckyDev, impiaaa, Infu, Nicolas Noble, peach, Schnappy, sickle, Stenzek and other nice people from the PSXDEV Discord server 57 | for help and testing; 58 | * probably more people I'm forgetting. 59 | -------------------------------------------------------------------------------- /data/0/stage00.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage00.stg -------------------------------------------------------------------------------- /data/0/stage01.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage01.stg -------------------------------------------------------------------------------- /data/0/stage02.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage02.stg -------------------------------------------------------------------------------- /data/0/stage03.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage03.stg -------------------------------------------------------------------------------- /data/0/stage04.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage04.stg -------------------------------------------------------------------------------- /data/0/stage05.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage05.stg -------------------------------------------------------------------------------- /data/0/stage06.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage06.stg -------------------------------------------------------------------------------- /data/0/stage07.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage07.stg -------------------------------------------------------------------------------- /data/0/stage08.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage08.stg -------------------------------------------------------------------------------- /data/0/stage09.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage09.stg -------------------------------------------------------------------------------- /data/0/stage0a.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage0a.stg -------------------------------------------------------------------------------- /data/0/stage0b.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage0b.stg -------------------------------------------------------------------------------- /data/0/stage0c.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage0c.stg -------------------------------------------------------------------------------- /data/0/stage0d.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage0d.stg -------------------------------------------------------------------------------- /data/0/stage0e.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage0e.stg -------------------------------------------------------------------------------- /data/0/stage0f.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/0/stage0f.stg -------------------------------------------------------------------------------- /data/1/stage10.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage10.stg -------------------------------------------------------------------------------- /data/1/stage11.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage11.stg -------------------------------------------------------------------------------- /data/1/stage12.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage12.stg -------------------------------------------------------------------------------- /data/1/stage13.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage13.stg -------------------------------------------------------------------------------- /data/1/stage14.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage14.stg -------------------------------------------------------------------------------- /data/1/stage15.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage15.stg -------------------------------------------------------------------------------- /data/1/stage16.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage16.stg -------------------------------------------------------------------------------- /data/1/stage17.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage17.stg -------------------------------------------------------------------------------- /data/1/stage18.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage18.stg -------------------------------------------------------------------------------- /data/1/stage19.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage19.stg -------------------------------------------------------------------------------- /data/1/stage1a.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage1a.stg -------------------------------------------------------------------------------- /data/1/stage1b.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage1b.stg -------------------------------------------------------------------------------- /data/1/stage1c.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage1c.stg -------------------------------------------------------------------------------- /data/1/stage1d.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage1d.stg -------------------------------------------------------------------------------- /data/1/stage1e.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage1e.stg -------------------------------------------------------------------------------- /data/1/stage1f.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/1/stage1f.stg -------------------------------------------------------------------------------- /data/2/stage20.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage20.stg -------------------------------------------------------------------------------- /data/2/stage21.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage21.stg -------------------------------------------------------------------------------- /data/2/stage22.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage22.stg -------------------------------------------------------------------------------- /data/2/stage23.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage23.stg -------------------------------------------------------------------------------- /data/2/stage24.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage24.stg -------------------------------------------------------------------------------- /data/2/stage25.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage25.stg -------------------------------------------------------------------------------- /data/2/stage26.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage26.stg -------------------------------------------------------------------------------- /data/2/stage27.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage27.stg -------------------------------------------------------------------------------- /data/2/stage28.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage28.stg -------------------------------------------------------------------------------- /data/2/stage29.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage29.stg -------------------------------------------------------------------------------- /data/2/stage2a.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage2a.stg -------------------------------------------------------------------------------- /data/2/stage2b.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage2b.stg -------------------------------------------------------------------------------- /data/2/stage2c.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage2c.stg -------------------------------------------------------------------------------- /data/2/stage2d.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage2d.stg -------------------------------------------------------------------------------- /data/2/stage2e.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage2e.stg -------------------------------------------------------------------------------- /data/2/stage2f.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/2/stage2f.stg -------------------------------------------------------------------------------- /data/3/stage30.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage30.stg -------------------------------------------------------------------------------- /data/3/stage31.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage31.stg -------------------------------------------------------------------------------- /data/3/stage32.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage32.stg -------------------------------------------------------------------------------- /data/3/stage33.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage33.stg -------------------------------------------------------------------------------- /data/3/stage34.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage34.stg -------------------------------------------------------------------------------- /data/3/stage35.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage35.stg -------------------------------------------------------------------------------- /data/3/stage36.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage36.stg -------------------------------------------------------------------------------- /data/3/stage37.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage37.stg -------------------------------------------------------------------------------- /data/3/stage38.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage38.stg -------------------------------------------------------------------------------- /data/3/stage39.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage39.stg -------------------------------------------------------------------------------- /data/3/stage3a.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage3a.stg -------------------------------------------------------------------------------- /data/3/stage3b.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage3b.stg -------------------------------------------------------------------------------- /data/3/stage3c.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage3c.stg -------------------------------------------------------------------------------- /data/3/stage3d.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage3d.stg -------------------------------------------------------------------------------- /data/3/stage3e.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage3e.stg -------------------------------------------------------------------------------- /data/3/stage3f.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/3/stage3f.stg -------------------------------------------------------------------------------- /data/4/stage40.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage40.stg -------------------------------------------------------------------------------- /data/4/stage41.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage41.stg -------------------------------------------------------------------------------- /data/4/stage42.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage42.stg -------------------------------------------------------------------------------- /data/4/stage43.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage43.stg -------------------------------------------------------------------------------- /data/4/stage44.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage44.stg -------------------------------------------------------------------------------- /data/4/stage45.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage45.stg -------------------------------------------------------------------------------- /data/4/stage46.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage46.stg -------------------------------------------------------------------------------- /data/4/stage47.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage47.stg -------------------------------------------------------------------------------- /data/4/stage48.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage48.stg -------------------------------------------------------------------------------- /data/4/stage49.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage49.stg -------------------------------------------------------------------------------- /data/4/stage4a.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage4a.stg -------------------------------------------------------------------------------- /data/4/stage4b.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage4b.stg -------------------------------------------------------------------------------- /data/4/stage4c.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage4c.stg -------------------------------------------------------------------------------- /data/4/stage4d.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage4d.stg -------------------------------------------------------------------------------- /data/4/stage4e.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage4e.stg -------------------------------------------------------------------------------- /data/4/stage4f.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/4/stage4f.stg -------------------------------------------------------------------------------- /data/5/stage50.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage50.stg -------------------------------------------------------------------------------- /data/5/stage51.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage51.stg -------------------------------------------------------------------------------- /data/5/stage52.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage52.stg -------------------------------------------------------------------------------- /data/5/stage53.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage53.stg -------------------------------------------------------------------------------- /data/5/stage54.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage54.stg -------------------------------------------------------------------------------- /data/5/stage55.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage55.stg -------------------------------------------------------------------------------- /data/5/stage56.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage56.stg -------------------------------------------------------------------------------- /data/5/stage57.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage57.stg -------------------------------------------------------------------------------- /data/5/stage58.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage58.stg -------------------------------------------------------------------------------- /data/5/stage59.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage59.stg -------------------------------------------------------------------------------- /data/5/stage5a.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage5a.stg -------------------------------------------------------------------------------- /data/5/stage5b.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage5b.stg -------------------------------------------------------------------------------- /data/5/stage5c.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage5c.stg -------------------------------------------------------------------------------- /data/5/stage5d.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage5d.stg -------------------------------------------------------------------------------- /data/5/stage5e.stg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/5/stage5e.stg -------------------------------------------------------------------------------- /data/main/ArmsItem.bsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/ArmsItem.bsc -------------------------------------------------------------------------------- /data/main/Credit.bsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/Credit.bsc -------------------------------------------------------------------------------- /data/main/Credit.crd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/Credit.crd -------------------------------------------------------------------------------- /data/main/StageSel.bsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/StageSel.bsc -------------------------------------------------------------------------------- /data/main/main.gfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/main.gfx -------------------------------------------------------------------------------- /data/main/main.sfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/main.sfx -------------------------------------------------------------------------------- /data/main/npc.tbl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/data/main/npc.tbl -------------------------------------------------------------------------------- /doc/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/doc/screen.png -------------------------------------------------------------------------------- /iso.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/engine/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "engine/common.h" 11 | #include "spu.h" 12 | 13 | // error message buffer 14 | char error_msg[MAX_ERROR]; 15 | 16 | void do_panic(void) { 17 | // spew to TTY 18 | printf("ERROR: %s\n", error_msg); 19 | 20 | // setup graphics viewport and clear screen 21 | SetDispMask(0); 22 | DISPENV disp; 23 | DRAWENV draw; 24 | SetDefDispEnv(&disp, 0, 0, VID_WIDTH, VID_HEIGHT); 25 | SetDefDrawEnv(&draw, 0, 0, VID_WIDTH, VID_HEIGHT); 26 | setRGB0(&draw, 0x40, 0x00, 0x00); draw.isbg = 1; 27 | PutDispEnv(&disp); 28 | PutDrawEnv(&draw); 29 | 30 | // load built in font 31 | FntLoad(960, 0); 32 | long stream = FntOpen(8, 16, VID_WIDTH - 8, VID_HEIGHT - 16, 0, MAX_ERROR); 33 | 34 | // draw 35 | FntPrint(stream, "ERROR:\n%s", error_msg); 36 | FntFlush(stream); 37 | DrawSync(0); 38 | VSync(0); 39 | SetDispMask(1); 40 | 41 | while (1) VSync(0); 42 | } 43 | -------------------------------------------------------------------------------- /src/engine/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // some common constants and macros 8 | 9 | #define MAX_ERROR 512 10 | 11 | #ifndef NULL 12 | #define NULL ((void *)0) 13 | #endif 14 | 15 | #define VID_WIDTH 320 16 | #define VID_HEIGHT 240 17 | 18 | #define TILE_SIZE 16 19 | #define TILE_SHIFT 4 20 | 21 | #define CAM_WIDTH (VID_WIDTH / TILE_SIZE) 22 | #define CAM_HEIGHT (VID_HEIGHT / TILE_SIZE) 23 | 24 | #define PSX_SCRATCH ((void *)0x1F800000) 25 | 26 | #define ALIGN(x, align) (((x) + ((align) - 1)) & ~((align) - 1)) 27 | #define ASSERT(x) do_assert((int)(x), #x, __FILE__, __LINE__) 28 | 29 | // game operates on 1.22.9 fixed point numbers 30 | 31 | #define FIX_SCALE 0x200 32 | #define FIX_SHIFT 9 33 | #define TO_FIX(x) ((x) * FIX_SCALE) 34 | #define TO_INT(x) ((x) / FIX_SCALE) 35 | 36 | // some common types 37 | 38 | typedef unsigned char u8; 39 | typedef signed char s8; 40 | typedef unsigned short u16; 41 | typedef signed short s16; 42 | typedef unsigned int u32; 43 | typedef signed int s32; 44 | 45 | typedef int bool; 46 | 47 | #ifndef FALSE 48 | #define FALSE 0 49 | #define TRUE 1 50 | #endif 51 | 52 | // RECT-compatible rect type 53 | typedef union { 54 | struct { 55 | short x, y; 56 | short w, h; 57 | }; 58 | struct { 59 | short left, top; 60 | short right, bottom; 61 | }; 62 | short vec[4]; 63 | } rect_t; 64 | 65 | // OTHER_RECT in CSE2, used for bounding boxes 66 | typedef struct { 67 | int front; 68 | int top; 69 | int back; 70 | int bottom; 71 | } hitbox_t; 72 | 73 | // collision flags 74 | enum collision_flag { 75 | COLL_LEFT_WALL = 1, // Touching a left wall 76 | COLL_CEILING = 2, // Touching a ceiling 77 | COLL_RIGHT_WALL = 4, // Touching a right wall 78 | COLL_GROUND = 8, // Touching the ground 79 | }; 80 | 81 | // utilities 82 | 83 | // memcpy and memset operating on words (see common_a.s) 84 | // addresses and byte count must be multiples of 4 85 | extern void *memcpy_word(void *dst, const void *src, const int n); 86 | extern void *memset_word(void *dst, const u32 set, const int n); 87 | 88 | // since psyq lacks vs(n)printf, we'll have to replace PANIC() with a shitty macro 89 | extern char error_msg[MAX_ERROR]; 90 | 91 | #define PANIC(...) \ 92 | do { sprintf(error_msg, __VA_ARGS__); do_panic(); } while (0) 93 | 94 | void do_panic(void) __attribute__((noreturn)); 95 | 96 | static inline void do_assert(const int expr, const char *strexpr, const char *file, const int line) { 97 | if (!expr) 98 | PANIC("ASSERTION FAILED: %s:%d:\n%s", file, line, strexpr); 99 | } 100 | -------------------------------------------------------------------------------- /src/engine/common_a.s: -------------------------------------------------------------------------------- 1 | .set noreorder 2 | 3 | .section .text 4 | 5 | # copy of memcpy from PSn00bSDK's libc, but operating on words 6 | # byte count and addresses must be a multiple of 4 7 | # Arguments: 8 | # a0 - destination address 9 | # a1 - source adress 10 | # a2 - bytes to copy 11 | .global memcpy_word 12 | .type memcpy_word, @function 13 | memcpy_word: 14 | move $v0, $a0 15 | .Lcpy_loop: 16 | blez $a2, .Lcpy_exit 17 | addi $a2, -4 18 | lw $a3, 0($a1) 19 | addiu $a1, 4 20 | sw $a3, 0($a0) 21 | b .Lcpy_loop 22 | addiu $a0, 4 23 | .Lcpy_exit: 24 | jr $ra 25 | nop 26 | 27 | # copy of memset from PSn00bSDK's libc, but operating on words 28 | # byte count and address must be a multiple of 4 29 | # Arguments: 30 | # a0 - address to buffer 31 | # a1 - value to set 32 | # a2 - bytes to set 33 | .global memset_word 34 | .type memset_word, @function 35 | memset_word: 36 | move $v0, $a0 37 | blez $a2, .Lset_exit 38 | addi $a2, -4 39 | sw $a1, 0($a0) 40 | b memset_word 41 | addiu $a0, 4 42 | .Lset_exit: 43 | jr $ra 44 | nop 45 | -------------------------------------------------------------------------------- /src/engine/exception.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "engine/common.h" 11 | #include "engine/exception.h" 12 | 13 | static unsigned long exception_event; 14 | 15 | static inline const char *exception_cause(const unsigned long cr) { 16 | static const char *msg[] = { 17 | "EXT INT", 18 | "?", 19 | "?", 20 | "?", 21 | "READ ERR", 22 | "WRITE ERR", 23 | "CMD BUS ERR", 24 | "DATA BUS ERR", 25 | "SYSCALL", 26 | "BREAK", 27 | "UNK COMMAND", 28 | "NO COP", 29 | "OVERFLOW", 30 | }; 31 | return msg[(cr & 63) >> 2]; 32 | } 33 | 34 | static long exception_cb(void) { 35 | // get current thread's TCB 36 | const struct ToT *tot = (const struct ToT *)0x100; 37 | const struct TCB *tcb_list = (const struct TCB *)tot[2].head; 38 | const unsigned long status = tcb_list[0].status; 39 | const unsigned long cr = GetCr(); 40 | const unsigned long *regs = tcb_list[0].reg; 41 | 42 | // spew to tty 43 | printf("UH OH ZONE\nSTATUS=%08x\nCR=%08x\nPC=%08x\nRA=%08x\n", status, cr, regs[R_EPC], regs[R_RA]); 44 | 45 | // setup graphics viewport and clear screen 46 | SetDispMask(0); 47 | DISPENV disp; 48 | DRAWENV draw; 49 | SetDefDispEnv(&disp, 0, 0, VID_WIDTH, VID_HEIGHT); 50 | SetDefDrawEnv(&draw, 0, 0, VID_WIDTH, VID_HEIGHT); 51 | setRGB0(&draw, 0x40, 0x00, 0x00); draw.isbg = 1; 52 | PutDispEnv(&disp); 53 | PutDrawEnv(&draw); 54 | 55 | // load built in font 56 | FntLoad(960, 0); 57 | long stream = FntOpen(8, 16, VID_WIDTH - 8, VID_HEIGHT - 16, 0, 800); 58 | 59 | // the most important info 60 | const char *cause = exception_cause(cr); 61 | FntPrint(stream, "UH OH ZONE\n\n"); 62 | FntPrint(stream, "CAUSE: %s (%08x)\nSTATUS: %08x\n\n", cause, cr, status); 63 | FntPrint(stream, "PC=%08x RA=%08x\n\n", regs[R_EPC],regs[R_RA]); 64 | 65 | // GPR dump 66 | FntPrint(stream, "AT=%08x K0=%08x K1=%08x\n", regs[R_AT], regs[R_K0], regs[R_K1]); 67 | FntPrint(stream, "A0=%08x A1=%08x A2=%08x\n", regs[R_A0], regs[R_A1], regs[R_A2]); 68 | FntPrint(stream, "A3=%08x V0=%08x V1=%08x\n", regs[R_A3], regs[R_V0], regs[R_V1]); 69 | FntPrint(stream, "T0=%08x T1=%08x T2=%08x\n", regs[R_T0], regs[R_T1], regs[R_T2]); 70 | FntPrint(stream, "T3=%08x T4=%08x T5=%08x\n", regs[R_T3], regs[R_T4], regs[R_T5]); 71 | FntPrint(stream, "T6=%08x T7=%08x T8=%08x\n", regs[R_T6], regs[R_T7], regs[R_T8]); 72 | FntPrint(stream, "T9=%08x S0=%08x S1=%08x\n", regs[R_T9], regs[R_S0], regs[R_S1]); 73 | FntPrint(stream, "S2=%08x S3=%08x S4=%08x\n", regs[R_S2], regs[R_S3], regs[R_S4]); 74 | FntPrint(stream, "S5=%08x S6=%08x S7=%08x\n", regs[R_S5], regs[R_S6], regs[R_S7]); 75 | FntPrint(stream, "GP=%08x FP=%08x SP=%08x\n", regs[R_GP], regs[R_FP], regs[R_SP]); 76 | 77 | // dump stack if possible 78 | const u32 sp = ALIGN(regs[R_SP], 16); 79 | if (sp > 0x80100000 && sp < 0x80200000) { 80 | FntPrint(stream, "\nSTACK\n\n"); 81 | const u32 *data = (const u32 *)sp; 82 | for (u32 i = 0; data < (const u32 *)0x80200000 && i < 6; data += 4, ++i) 83 | FntPrint(stream, "%02x %08x %08x %08x %08x\n", (u32)data & 0xFF, data[0], data[1], data[2], data[3]); 84 | } 85 | 86 | FntFlush(stream); 87 | SetDispMask(1); 88 | 89 | while (1); 90 | 91 | return 0; // never gets here 92 | } 93 | 94 | void ex_install_handler(void) { 95 | // HwCPU fires when there's an unhandled CPU exception 96 | EnterCriticalSection(); 97 | exception_event = OpenEvent(HwCPU, EvSpTRAP, EvMdINTR, exception_cb); 98 | EnableEvent(exception_event); 99 | ExitCriticalSection(); 100 | printf("ex_install_handler(): opened HwCPU event %08x\n", exception_event); 101 | 102 | /* 103 | // alternatively: 104 | // A(40h) - SystemErrorUnresolvedException() 105 | void **a0tab = (void **)0x200; 106 | const void *old = a0tab[0x40]; 107 | a0tab[0x40] = exception_cb; 108 | printf("ex_install_handler(): hooked exception handler: %p -> %p\n", old, exception_cb); 109 | */ 110 | } 111 | -------------------------------------------------------------------------------- /src/engine/exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void ex_install_handler(void); 4 | -------------------------------------------------------------------------------- /src/engine/filesystem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "engine/common.h" 9 | #include "engine/filesystem.h" 10 | 11 | #define SECSIZE 2048 12 | #define BUFSECS 4 13 | #define BUFSIZE (BUFSECS * SECSIZE) 14 | #define MAX_FHANDLES 1 15 | 16 | static u_long cdmode = CdlModeSpeed; 17 | 18 | struct fs_file_s { 19 | char fname[CD_MAX_PATH]; 20 | CdlFILE cdf; 21 | int secstart, secend, seccur; 22 | int fp, bufp; 23 | int bufleft; 24 | u8 buf[BUFSIZE]; 25 | }; 26 | 27 | // lmao 1handle 28 | static fs_file_t fhandle; 29 | static int num_fhandles = 0; 30 | 31 | void cd_init(void) { 32 | // see if there was a CD in the drive during init, die if there wasn't 33 | if (!CdInit()) 34 | PANIC("bad CD or no CD in drive"); 35 | 36 | // look alive 37 | CdControl(CdlNop, 0, 0); 38 | CdStatus(); 39 | 40 | // set hispeed mode 41 | CdControlB(CdlSetmode, (u_char *)&cdmode, 0); 42 | VSync(3); // have to do this to not explode the drive apparently 43 | } 44 | 45 | fs_file_t *fs_fopen(const char *fname, const int reopen) { 46 | // check if the same file was just open and return it if allowed 47 | if (reopen && !strncmp(fhandle.fname, fname, sizeof(fhandle.fname))) { 48 | num_fhandles++; 49 | return &fhandle; 50 | } 51 | 52 | if (num_fhandles >= MAX_FHANDLES) { 53 | printf("fs_fopen(%s): too many file handles\n", fname); 54 | return NULL; 55 | } 56 | 57 | fs_file_t *f = &fhandle; 58 | memset(f, 0, sizeof(*f)); 59 | 60 | // good bye const 61 | if (CdSearchFile(&f->cdf, (char *)fname) == NULL) { 62 | printf("fs_fopen(%s): file not found\n", fname); 63 | return NULL; 64 | } 65 | 66 | // read first sector of the file 67 | CdControl(CdlSetloc, (u_char *)&f->cdf.pos, 0); 68 | CdRead(BUFSECS, (u_long *)f->buf, CdlModeSpeed); 69 | CdReadSync(0, NULL); 70 | 71 | // set fp and shit 72 | f->secstart = CdPosToInt(&f->cdf.pos); 73 | f->seccur = f->secstart; 74 | f->secend = f->secstart + (f->cdf.size + SECSIZE - 1) / SECSIZE; 75 | f->fp = 0; 76 | f->bufp = 0; 77 | f->bufleft = (f->cdf.size >= BUFSIZE) ? BUFSIZE : f->cdf.size; 78 | strncpy(fhandle.fname, fname, sizeof(fhandle.fname) - 1); 79 | 80 | num_fhandles++; 81 | printf("fs_fopen(%s): size %u bufleft %d secs %d %d\n", fname, f->cdf.size, 82 | f->bufleft, f->secstart, f->secend); 83 | 84 | return f; 85 | } 86 | 87 | int fs_fexists(const char *fname) { 88 | CdlFILE cdf; 89 | if (CdSearchFile(&cdf, (char *)fname) == NULL) { 90 | printf("fs_fexists(%s): file not found\n", fname); 91 | return FALSE; 92 | } 93 | return TRUE; 94 | } 95 | 96 | void fs_fclose(fs_file_t *f) { 97 | if (!f) return; 98 | num_fhandles--; 99 | } 100 | 101 | int fs_fread(void *ptr, int size, int num, fs_file_t *f) { 102 | int rx, rdbuf; 103 | int fleft; 104 | CdlLOC pos; 105 | 106 | if (!f || !ptr) return -1; 107 | if (!size) return 0; 108 | 109 | size *= num; 110 | rx = 0; 111 | 112 | while (size) { 113 | // first empty the buffer 114 | rdbuf = (size > f->bufleft) ? f->bufleft : size; 115 | memcpy(ptr, f->buf + f->bufp, rdbuf); 116 | rx += rdbuf; 117 | ptr += rdbuf; 118 | f->fp += rdbuf; 119 | f->bufp += rdbuf; 120 | f->bufleft -= rdbuf; 121 | size -= rdbuf; 122 | 123 | // if we went over, load next sector 124 | if (f->bufleft == 0) { 125 | f->seccur += BUFSECS; 126 | // check if we have reached the end 127 | if (f->seccur >= f->secend) return rx; 128 | // looks like you need to seek every time when you use CdRead 129 | CdIntToPos(f->seccur, &pos); 130 | CdControl(CdlSetloc, (u_char *)&pos, 0); 131 | CdRead(BUFSECS, (u_long *)f->buf, CdlModeSpeed); 132 | CdReadSync(0, 0); 133 | fleft = f->cdf.size - f->fp; 134 | f->bufleft = (fleft >= BUFSIZE) ? BUFSIZE : fleft; 135 | f->bufp = 0; 136 | } 137 | } 138 | 139 | return rx; 140 | } 141 | 142 | void fs_fread_or_die(void *ptr, int size, int num, fs_file_t *f) { 143 | if (fs_fread(ptr, size, num, f) < 0) 144 | PANIC("fs_fread_or_die(%.16s, %d, %d): fucking died", f->cdf.name, size, num); 145 | } 146 | 147 | int fs_fseek(fs_file_t *f, int ofs, int whence) { 148 | int fsec, bofs; 149 | CdlLOC pos; 150 | 151 | if (!f) return -1; 152 | 153 | if (whence == SEEK_CUR) ofs = f->fp + ofs; 154 | 155 | if (f->fp == ofs) return 0; 156 | 157 | fsec = f->secstart + (ofs / BUFSIZE) * BUFSECS; 158 | bofs = ofs % BUFSIZE; 159 | 160 | // fuck SEEK_END, it's only used to get file length here 161 | 162 | if (fsec != f->seccur) { 163 | // sector changed; seek to new one and buffer it 164 | CdIntToPos(fsec, &pos); 165 | CdControl(CdlSetloc, (u_char *)&pos, 0); 166 | CdRead(BUFSECS, (u_long *)f->buf, CdlModeSpeed); 167 | CdReadSync(0, 0); 168 | f->seccur = fsec; 169 | f->bufp = -1; // hack: see below 170 | } 171 | 172 | if (bofs != f->bufp) { 173 | // buffer offset changed (or new sector loaded); reset pointers 174 | f->bufp = bofs; 175 | f->bufleft = BUFSIZE - bofs; 176 | if (f->bufleft < 0) f->bufleft = 0; 177 | } 178 | 179 | f->fp = ofs; 180 | 181 | return 0; 182 | } 183 | 184 | int fs_ftell(fs_file_t *f) { 185 | if (!f) return -1; 186 | return f->fp; 187 | } 188 | 189 | int fs_fsize(fs_file_t *f) { 190 | if (!f) return -1; 191 | return f->cdf.size; 192 | } 193 | 194 | int fs_feof(fs_file_t *f) { 195 | if (!f) return -1; 196 | return (f->seccur >= f->secend); 197 | } 198 | 199 | u8 fs_fread_u8(fs_file_t *f) { 200 | u8 res = 0; 201 | fs_fread_or_die(&res, 1, 1, f); 202 | return res; 203 | } 204 | 205 | u16 fs_fread_u16(fs_file_t *f) { 206 | u16 res = 0; 207 | fs_fread_or_die(&res, 2, 1, f); 208 | return res; 209 | } 210 | 211 | u32 fs_fread_u32(fs_file_t *f) { 212 | u32 res = 0; 213 | fs_fread_or_die(&res, 4, 1, f); 214 | return res; 215 | } 216 | 217 | -------------------------------------------------------------------------------- /src/engine/filesystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "engine/common.h" 5 | 6 | #define CD_MAX_FILENAME 16 7 | #define CD_MAX_PATH (128 + CD_MAX_FILENAME) 8 | 9 | typedef struct fs_file_s fs_file_t; 10 | 11 | void cd_init(void); 12 | 13 | fs_file_t *fs_fopen(const char *fname, const int reopen); 14 | int fs_fexists(const char *fname); 15 | void fs_fclose(fs_file_t *f); 16 | int fs_fread(void *ptr, int size, int num, fs_file_t *f); 17 | void fs_fread_or_die(void *ptr, int size, int num, fs_file_t *f); 18 | int fs_fseek(fs_file_t *f, int ofs, int whence); 19 | int fs_ftell(fs_file_t *f); 20 | int fs_fsize(fs_file_t *f); 21 | int fs_feof(fs_file_t *f); 22 | u8 fs_fread_u8(fs_file_t *f); 23 | u16 fs_fread_u16(fs_file_t *f); 24 | u32 fs_fread_u32(fs_file_t *f); 25 | -------------------------------------------------------------------------------- /src/engine/graphics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "engine/filesystem.h" 8 | #include "engine/surfacelist.h" 9 | 10 | #define GFX_MAX_SURFACES 40 11 | #define GFX_MAIN_BANK "\\MAIN\\MAIN.GFX;1" 12 | #define GFX_FONT_WIDTH 6 13 | #define GFX_FONT_HEIGHT 12 14 | 15 | #define GFX_RGB(r, g, b) ((((b) >> 3) << 10) | (((g) >> 3) << 5) | ((r) >> 3)) 16 | 17 | // these are mostly for tile drawing 18 | enum gfx_layer { 19 | GFX_LAYER_BACK, 20 | GFX_LAYER_FRONT, 21 | GFX_NUM_LAYERS 22 | }; 23 | 24 | #pragma pack(push, 1) 25 | 26 | typedef struct { 27 | s8 id; // in-game surface id (or -1 if unloaded) 28 | u8 mode; // 1 for 256-color and 0 for 16-color 29 | u16 clut; // CLUT address as given by vram_fit_clut() 30 | u16 tex_x; // absolute X of top left corner in VRAM 31 | u16 tex_y; // absolute Y of top left corner in VRAM 32 | } gfx_surf_t; 33 | 34 | typedef struct { 35 | u16 numsurf; // total number of surfaces in bank 36 | u16 numclut; // total number of CLUTs in bank 37 | RECT clut_rect[2]; // where to copy the data for each CLUT page 38 | RECT surf_rect[2]; // where to copy the data for each surface page 39 | gfx_surf_t surf[]; // [numsurf] surface headers 40 | // [clutdata_h * VRAM_CLUT_PAGE_WIDTH] words of clut data follows 41 | // [surfdata_h * VRAM_PAGE_WIDTH] words of surface data follows 42 | } gfx_bank_t; 43 | 44 | /* essentially same shit but for RAM storage */ 45 | 46 | typedef struct { 47 | s16 mode; // 1 for 256-color, 0 for 16-color, -1 for no image 48 | u16 w; // width, in words 49 | u16 h; // height, in vram lines 50 | u16 size; // size, in words 51 | u32 ofs; // offset from beginning of ram_surfbank_t 52 | } gfx_ramsurf_t; 53 | 54 | typedef struct { 55 | u32 numsurf; // total number of surfaces in bank, each surface has its own clut 56 | gfx_ramsurf_t surf[]; // [numsurf] surface headers 57 | // surface data follows: 58 | // for each surface: 59 | // some words: image data \ total: `size` words 60 | // 16 or 256 words: clut data / 61 | } gfx_rambank_t; 62 | 63 | #pragma pack(pop) 64 | 65 | extern gfx_surf_t gfx_surf[]; 66 | extern const u8 gfx_clear_rgb[3]; 67 | extern bool gfx_suppress_loading; 68 | 69 | typedef struct { 70 | rect_t r; // rect inside surface 71 | u32 surf; // surface ID 72 | u16 tpage; // getTPage(rect.x, rect.y) 73 | u8 u; // starting UV inside surface 74 | u8 v; 75 | } gfx_texrect_t; 76 | 77 | int gfx_init(void); 78 | void gfx_init_fonts(void); 79 | 80 | // loads a surface bank into a gfx_bank_t struct and uploads it to VRAM 81 | int gfx_upload_gfx_bank(gfx_bank_t *bank, u8 *bank_data); 82 | int gfx_read_gfx_bank(fs_file_t *f); 83 | int gfx_load_gfx_bank(const char *path); 84 | 85 | void gfx_swap_buffers(void); 86 | void gfx_draw_texrect(const gfx_texrect_t *texrect, const int layer, const int x, const int y); 87 | void gfx_draw_texrect_wide(const gfx_texrect_t *texrect, const int layer, const int x, const int y); 88 | void gfx_draw_texrect_ofs(const gfx_texrect_t *texrect, const int layer, const int x, const int y, const int du, const int dv); 89 | void gfx_draw_texrect_scaled(const gfx_texrect_t *texrect, const int layer, const int x, const int y, const int scale); 90 | void gfx_draw_texrect_16x16(const gfx_texrect_t *texrect, const int layer, const int x, const int y); 91 | void gfx_draw_texrect_16x16_ofs(const gfx_texrect_t *texrect, const int layer, const int x, const int y, const int du, const int dv); 92 | void gfx_draw_texrect_8x8(const gfx_texrect_t *texrect, const int layer, const int x, const int y); 93 | void gfx_draw_tile(u8 tile_x, u8 tile_y, const int layer, const int x, const int y); 94 | void gfx_draw_string(const char *str, const int layer, int x, int y); 95 | void gfx_draw_string_rgb(const char *str, const u8 *rgb, const int layer, int x, int y) ; 96 | void gfx_draw_fillrect(const u8 *rgb, const int layer, const int x, const int y, const int w, const int h); 97 | void gfx_draw_loading(void); 98 | void gfx_draw_clear_immediate(const u8 *rgb); 99 | void gfx_draw_clear(const u8 *rgb, const int layer); 100 | 101 | void gfx_push_cliprect(const int layer, int x, int y, const int w, const int h); 102 | void gfx_pop_cliprect(const int layer); 103 | 104 | // if mode != 2, CLUT is immediately after the image (data + w * h) and may be up to w bytes in size 105 | // w must be aligned to 16 106 | void gfx_upload_image(u8 *data, int w, int h, const int mode, const int surf_id, const bool sync); 107 | 108 | // converts the `rect` field of `r` into tpage address, UVs and XYWH instead of LTRB 109 | static inline void gfx_set_texrect(gfx_texrect_t *tr, const int s) { 110 | const int mode = gfx_surf[s].mode; 111 | const int shift = 2 - mode; 112 | const int tpage_x = (gfx_surf[s].tex_x + (tr->r.x >> shift)) & 0x3C0; 113 | const int tpage_y = (gfx_surf[s].tex_y + tr->r.y) & 0x100; 114 | // align up to 16-bit pixel 115 | tr->r.w = tr->r.right - tr->r.left; 116 | tr->r.h = tr->r.bottom - tr->r.top; 117 | tr->surf = s; 118 | tr->tpage = getTPage(mode, 0, tpage_x, tpage_y); 119 | tr->u = ((gfx_surf[s].tex_x - tpage_x) << shift) + tr->r.x; 120 | tr->v = (gfx_surf[s].tex_y - tpage_y) + tr->r.y; 121 | } 122 | -------------------------------------------------------------------------------- /src/engine/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "engine/common.h" 6 | #include "engine/input.h" 7 | 8 | // and this struct def as well 9 | typedef struct { 10 | u8 stat; // Status 11 | u8 len :4; // Data length (in halfwords) 12 | u8 type : 4; // Device type 13 | u16 btn; // Button states 14 | u8 rs_x, rs_y; // Right stick coordinates 15 | u8 ls_x, ls_y; // Left stick coordinates 16 | } PADTYPE; 17 | 18 | // raw pad buttons currently held 19 | u16 input_pad; 20 | // IN_ flags of actions currently held 21 | u32 input_held; 22 | // IN_ flags of actions pressed this frame 23 | u32 input_trig; 24 | 25 | // don't raise trigger events for next frame 26 | bool input_suppress_trig; 27 | 28 | // IN_ flags of actions held on previous frame 29 | static u32 input_old; 30 | 31 | static char padbuf[2][34]; 32 | 33 | // pad->action bindings 34 | u16 input_binds[IN_NUM_ACTIONS]; 35 | 36 | // default bindings 37 | const u16 input_binds_default[IN_NUM_ACTIONS] = { 38 | PAD_LEFT, // IN_LEFT = 1 39 | PAD_RIGHT, // IN_RIGHT = 2 40 | PAD_UP, // IN_UP = 4 41 | PAD_DOWN, // IN_DOWN = 8 42 | PAD_CROSS, // IN_JUMP = 16 43 | PAD_SQUARE, // IN_FIRE = 32 44 | PAD_TRIANGLE, // IN_SWAP_L = 64 45 | PAD_CIRCLE, // IN_SWAP_R = 128 46 | PAD_L2, // IN_INVENTORY = 256 47 | PAD_R2, // IN_MAP = 512 48 | PAD_CROSS, // IN_OK = 4096 49 | PAD_CIRCLE, // IN_CANCEL = 8192 50 | PAD_START, // IN_PAUSE = 1024 51 | PAD_SELECT, // IN_DEBUG = 2048 52 | }; 53 | 54 | void in_init(void) { 55 | InitPAD(padbuf[0], 34, padbuf[1], 34); 56 | 57 | padbuf[0][0] = padbuf[0][1] = 0xFF; 58 | padbuf[1][0] = padbuf[1][1] = 0xFF; 59 | 60 | StartPAD(); 61 | ChangeClearPAD(0); 62 | 63 | memcpy(input_binds, input_binds_default, sizeof(input_binds)); 64 | } 65 | 66 | static inline u32 in_remap(const u32 held) { 67 | // TODO: proper bindings 68 | register u32 in = 0; 69 | for (u32 i = 0; i < IN_NUM_ACTIONS; ++i) { 70 | if (held & input_binds[i]) 71 | in |= 1 << i; 72 | } 73 | return in; 74 | } 75 | 76 | void in_update(void) { 77 | PADTYPE *pad = (PADTYPE *)&padbuf[0][0]; 78 | input_pad = ~pad->btn; 79 | input_old = input_held; 80 | input_held = in_remap(input_pad); 81 | if (input_suppress_trig) { 82 | input_trig = 0; 83 | input_suppress_trig = FALSE; 84 | } else { 85 | input_trig = ~input_old & input_held; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/engine/input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | // psyq lacks these 6 | enum pad_buttons { 7 | PAD_SELECT = 1, 8 | PAD_L3 = 2, 9 | PAD_R3 = 4, 10 | PAD_START = 8, 11 | PAD_UP = 16, 12 | PAD_RIGHT = 32, 13 | PAD_DOWN = 64, 14 | PAD_LEFT = 128, 15 | PAD_L2 = 256, 16 | PAD_R2 = 512, 17 | PAD_L1 = 1024, 18 | PAD_R1 = 2048, 19 | PAD_TRIANGLE = 4096, 20 | PAD_CIRCLE = 8192, 21 | PAD_CROSS = 16384, 22 | PAD_SQUARE = 32768, 23 | }; 24 | 25 | enum input_flags { 26 | IN_LEFT = 1, 27 | IN_RIGHT = 2, 28 | IN_UP = 4, 29 | IN_DOWN = 8, 30 | IN_JUMP = 16, 31 | IN_FIRE = 32, 32 | IN_SWAP_L = 64, 33 | IN_SWAP_R = 128, 34 | IN_INVENTORY = 256, 35 | IN_MAP = 512, 36 | IN_OK = 1024, 37 | IN_CANCEL = 2048, 38 | IN_PAUSE = 4096, 39 | IN_DEBUG = 8192, 40 | 41 | IN_NUM_ACTIONS = 14 42 | }; 43 | 44 | // action->pad bindings 45 | extern u16 input_binds[IN_NUM_ACTIONS]; 46 | // default bindings 47 | extern const u16 input_binds_default[IN_NUM_ACTIONS]; 48 | 49 | // IN_ flags of actions currently held 50 | extern u32 input_held; 51 | // IN_ flags of actions pressed this frame 52 | extern u32 input_trig; 53 | // raw pad buttons currently held 54 | extern u16 input_pad; 55 | 56 | // don't raise trigger events for next frame 57 | extern bool input_suppress_trig; 58 | 59 | void in_init(void); 60 | void in_update(void); 61 | -------------------------------------------------------------------------------- /src/engine/loading.inc: -------------------------------------------------------------------------------- 1 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 2 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 3 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 4 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 5 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 6 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 7 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 8 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 9 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 10 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 11 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 12 | 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 13 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 14 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 15 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 16 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 17 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 18 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 19 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 20 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 21 | 0x1000, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 22 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 23 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 24 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 25 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 26 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 27 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 28 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 29 | 0x1000, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 30 | 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x7F80, 0x7F80, 0x1000, 31 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x1000, 32 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 33 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 34 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 35 | 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 36 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 37 | 0x1000, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 38 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x1000, 39 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 40 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 41 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 42 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 43 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 44 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 45 | 0x1000, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x1000, 46 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 47 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 48 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 49 | 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 50 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 51 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 52 | 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 53 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 0x1000, 54 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 55 | 0x1000, 0x1000, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x7F80, 0x1000, 56 | 0x1000, 0x7F80, 0x7F80, 0x1000, 0x1000, 0x7F80, 0x7F80, 0x1000, 57 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 58 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 59 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 60 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 61 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 62 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 63 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 64 | 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 65 | -------------------------------------------------------------------------------- /src/engine/math.c: -------------------------------------------------------------------------------- 1 | #include "engine/common.h" 2 | #include "engine/math.h" 3 | 4 | // also known as Triangle.cpp 5 | // these can be generated with trigcalc.exe 6 | 7 | s16 m_sintab[0x100] = { 8 | 0, 12, 25, 37, 50, 62, 75, 87, 9 | 99, 112, 124, 136, 148, 160, 172, 184, 10 | 195, 207, 218, 230, 241, 252, 263, 273, 11 | 284, 294, 304, 314, 324, 334, 343, 353, 12 | 362, 370, 379, 387, 395, 403, 411, 418, 13 | 425, 432, 439, 445, 451, 457, 462, 468, 14 | 473, 477, 482, 486, 489, 493, 496, 499, 15 | 502, 504, 506, 508, 509, 510, 511, 511, 16 | 511, 511, 511, 510, 509, 508, 506, 504, 17 | 502, 499, 496, 493, 489, 486, 482, 477, 18 | 473, 468, 462, 457, 451, 445, 439, 432, 19 | 425, 418, 411, 403, 395, 387, 379, 370, 20 | 362, 353, 343, 334, 324, 314, 304, 294, 21 | 284, 273, 263, 252, 241, 230, 218, 207, 22 | 195, 184, 172, 160, 148, 136, 124, 112, 23 | 99, 87, 75, 62, 50, 37, 25, 12, 24 | 0, -12, -25, -37, -50, -62, -75, -87, 25 | -99, -112, -124, -136, -148, -160, -172, -184, 26 | -195, -207, -218, -230, -241, -252, -263, -273, 27 | -284, -294, -305, -315, -324, -334, -343, -353, 28 | -362, -370, -379, -387, -395, -403, -411, -418, 29 | -425, -432, -439, -445, -451, -457, -462, -468, 30 | -473, -477, -482, -486, -489, -493, -496, -499, 31 | -502, -504, -506, -508, -509, -510, -511, -511, 32 | -511, -511, -511, -510, -509, -508, -506, -504, 33 | -502, -499, -496, -493, -489, -486, -482, -477, 34 | -473, -468, -462, -457, -451, -445, -439, -432, 35 | -425, -418, -411, -403, -395, -387, -379, -370, 36 | -362, -353, -343, -334, -324, -314, -304, -294, 37 | -284, -273, -263, -252, -241, -230, -218, -207, 38 | -195, -184, -172, -160, -148, -136, -124, -112, 39 | }; 40 | 41 | s16 math_tantab[0x21] = { 42 | 0, 201, 402, 604, 806, 1010, 1215, 1421, 43 | 1629, 1839, 2051, 2267, 2485, 2706, 2931, 3160, 44 | 3393, 3631, 3874, 4123, 4378, 4640, 4910, 5187, 45 | 5473, 5769, 6075, 6393, 6723, 7066, 7424, 7799, 46 | 8192, 47 | }; 48 | 49 | // preserved as is 50 | u8 m_atan2(int x, int y) { 51 | register s16 k; 52 | register u8 a; 53 | 54 | x = -x; 55 | y = -y; 56 | 57 | a = 0; 58 | 59 | if (x > 0) { 60 | if (y > 0) { 61 | if (x > y) { 62 | k = (y * 0x2000) / x; 63 | while (k > math_tantab[a]) ++a; 64 | } else { 65 | k = (x * 0x2000) / y; 66 | while (k > math_tantab[a]) ++a; 67 | a = 0x40 - a; 68 | } 69 | } else { 70 | if (x > -y) { 71 | k = (-y * 0x2000) / x; 72 | while (k > math_tantab[a]) ++a; 73 | a = 0x100 - a; 74 | } else { 75 | k = (x * 0x2000) / -y; 76 | while (k > math_tantab[a]) ++a; 77 | a = 0x100 - 0x40 + a; 78 | } 79 | } 80 | } else { 81 | if (y > 0) { 82 | if (-x > y) { 83 | k = (y * 0x2000) / -x; 84 | while (k > math_tantab[a]) ++a; 85 | a = 0x80 - a; 86 | } else { 87 | k = (-x * 0x2000) / y; 88 | while (k > math_tantab[a]) ++a; 89 | a = 0x40 + a; 90 | } 91 | } else { 92 | if (-x > -y) { 93 | k = (-y * 0x2000) / -x; 94 | while (k > math_tantab[a]) ++a; 95 | a = 0x80 + a; 96 | } else { 97 | k = (-x * 0x2000) / -y; 98 | while (k > math_tantab[a]) ++a; 99 | a = 0x100 - 0x40 - a; 100 | } 101 | } 102 | } 103 | 104 | return a; 105 | } 106 | 107 | static u32 rand_seed = 0; 108 | 109 | void m_srand(const u32 seed) { 110 | rand_seed = seed; 111 | } 112 | 113 | int m_rand(const int min, const int max) { 114 | register int r, range; 115 | range = max - min + 1; 116 | r = (214013 * rand_seed + 2531011); 117 | r = (r >> 16) & 0x7FFF; 118 | rand_seed = r; 119 | return (r % range) + min; 120 | } 121 | -------------------------------------------------------------------------------- /src/engine/math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | // also known as Triangle.h 6 | 7 | extern s16 m_sintab[0x100]; 8 | extern s16 math_tantab[0x21]; 9 | 10 | static inline int m_sin(u8 deg) { 11 | return m_sintab[deg]; 12 | } 13 | 14 | static inline int m_cos(u8 deg) { 15 | deg += 0x40; 16 | return m_sintab[deg]; 17 | } 18 | 19 | static inline int m_sign(int x) { 20 | return (x > 0) - (x < 0); 21 | } 22 | 23 | u8 m_atan2(int x, int y); 24 | void m_srand(const u32 seed); 25 | int m_rand(const int min, const int max); 26 | -------------------------------------------------------------------------------- /src/engine/mcrd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | // max size of one profile 6 | #define MCRD_MAX_SAVE_SIZE 1536 7 | // max number of profiles in one save file 8 | #define MCRD_MAX_SAVES 4 9 | // max number of memcards per port (TODO: multitap support?) 10 | #define MCRD_CARDS_PER_PORT 1 11 | // max ports 12 | #define MCRD_NUM_PORTS 2 13 | 14 | #define MCRD_SAVE_ICON_FRAMES 3 15 | 16 | #define MCRD_SECSIZE 128 17 | #define MCRD_BLOCKSIZE 8192 18 | 19 | #pragma pack(push, 1) 20 | 21 | typedef struct { 22 | char id[2]; // "SC" 23 | u8 type; // number of icon frames (0x11 - one frame, 0x12 - two frames, 0x13 - three frames) 24 | u8 size; // size of save file in blocks 25 | u16 title[32]; // title of save file (encoded in Shift-JIS format) 26 | u8 pad[28]; // unused 27 | u16 clut[16]; // color palette of icon frames (16 RGB5X1 16-bit color entries) 28 | // save icon frames follow 29 | } SAVEHDR; 30 | 31 | typedef struct { 32 | char id[4]; // "CSS\x01" 33 | u32 slotmask; // bit N is 1 if slot N is occupied 34 | u8 pad[MCRD_SECSIZE - 4 * 2]; 35 | u8 data[]; // [MCRD_MAX_SAVES][MCRD_MAX_SAVE_SIZE] 36 | } mcrd_save_t; 37 | 38 | // save structure: 39 | // SAVEHDR savehdr; 1 + NUM_FRAMES sectors 40 | // mcrd_save_t hdr; 1 sector 41 | // u8 data[]; (MCRD_MAX_SAVE_SIZE / MCRD_SECSIZE) * MCRD_MAX_SAVES sectors 42 | 43 | #pragma pack(pop) 44 | 45 | typedef enum { 46 | MCRD_NO_SPACE = -5, 47 | MCRD_NO_SAVE = -4, 48 | MCRD_WRONG_CARD = -3, 49 | MCRD_NO_CARD = -2, 50 | MCRD_ERROR = -1, 51 | MCRD_SUCCESS, 52 | MCRD_UNFORMATTED, 53 | } mcrd_result_t; 54 | 55 | typedef struct { 56 | u8 port; 57 | u8 card; 58 | } mcrd_id_t; 59 | 60 | void mcrd_init(void); 61 | void mcrd_start(void); 62 | void mcrd_stop(void); 63 | 64 | int mcrd_cards_available(mcrd_id_t *out_cards); 65 | mcrd_result_t mcrd_card_open(const mcrd_id_t id); 66 | mcrd_result_t mcrd_card_close(void); 67 | mcrd_result_t mcrd_card_format(void); 68 | 69 | u32 mcrd_save_slots_available(void); 70 | mcrd_result_t mcrd_save_open(const char *name); 71 | mcrd_result_t mcrd_save_create(const char *name); 72 | mcrd_result_t mcrd_save_write_slot(const int slot, const void *data, const int size); 73 | mcrd_result_t mcrd_save_read_slot(const int slot, void *data, const int size); 74 | -------------------------------------------------------------------------------- /src/engine/memory.c: -------------------------------------------------------------------------------- 1 | #include "engine/memory.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "engine/common.h" 8 | 9 | #define RAM_END 0x80200000 10 | 11 | #define MEM_ALIGNMENT 8 12 | #define MEM_MAX_ALLOCS 128 13 | 14 | static u8 *mem_base; 15 | static u8 *mem_ptr; 16 | static u8 *mem_lastptr; 17 | static int mem_size; 18 | static int mem_left; 19 | 20 | static u32 mem_allocs[MEM_MAX_ALLOCS]; 21 | static u32 mem_numallocs; 22 | 23 | static struct { 24 | u8 *mem_ptr; 25 | u8 *mem_lastptr; 26 | s32 mem_left; 27 | u32 mem_numallocs; 28 | } mem_mark[MEM_MARK_COUNT]; 29 | 30 | // this should be defined somewhere 31 | extern u8 __bss_end[]; 32 | 33 | void mem_init(void) { 34 | mem_base = __bss_end + MALLOC_HEAP_SIZE; 35 | mem_base = (u8 *)ALIGN((u32)mem_base, MEM_ALIGNMENT); 36 | mem_size = mem_left = (u8 *)RAM_END - mem_base - STACK_SIZE; 37 | mem_ptr = mem_lastptr = mem_base; 38 | mem_numallocs = 0; 39 | 40 | printf("mem_init: mem_base=%08x mem_size=%u\n", (u32)mem_base, mem_size); 41 | } 42 | 43 | void *mem_alloc(const u32 size) { 44 | const u32 asize = ALIGN(size, MEM_ALIGNMENT); 45 | if (size == 0) 46 | PANIC("mem_alloc: size == 0"); 47 | if (mem_left < asize) 48 | PANIC("mem_alloc: failed to alloc %u bytes", size); 49 | if (mem_numallocs == MEM_MAX_ALLOCS) 50 | PANIC("mem_alloc: MAX_ALLOCS reached"); 51 | mem_lastptr = mem_ptr; 52 | mem_ptr += asize; 53 | mem_left -= asize; 54 | mem_allocs[mem_numallocs++] = asize; 55 | return mem_lastptr; 56 | } 57 | 58 | void *mem_zeroalloc(const u32 size) { 59 | void *buf = mem_alloc(size); 60 | memset(buf, 0, size); 61 | return buf; 62 | } 63 | 64 | void *mem_realloc(void *ptr, const u32 newsize) { 65 | if (!ptr) 66 | return mem_alloc(newsize); // just like realloc() 67 | const u32 anewsize = ALIGN(newsize, MEM_ALIGNMENT); 68 | const u32 oldsize = mem_allocs[mem_numallocs - 1]; 69 | if (ptr != mem_lastptr || !mem_numallocs) 70 | PANIC("mem_realloc: this is a stack allocator you dolt"); 71 | if (mem_left < anewsize) 72 | PANIC("mem_realloc: failed to realloc %p from %u to %u bytes", oldsize, anewsize); 73 | mem_left -= (int)newsize - (int)oldsize; 74 | mem_ptr = mem_lastptr + newsize; 75 | mem_allocs[mem_numallocs - 1] = newsize; 76 | return mem_lastptr; 77 | } 78 | 79 | void mem_free(void *ptr) { 80 | if (!ptr) 81 | return; // NULL free is a no-op 82 | if (ptr != mem_lastptr) 83 | PANIC("mem_free: this is a stack allocator you dolt"); 84 | if (mem_numallocs == 0) 85 | PANIC("mem_free: nothing to free"); 86 | const u32 size = mem_allocs[--mem_numallocs]; 87 | mem_left += size; 88 | mem_ptr = mem_lastptr; 89 | if (mem_numallocs) 90 | mem_lastptr -= mem_allocs[mem_numallocs - 1]; 91 | else 92 | ASSERT(mem_lastptr == mem_base); 93 | } 94 | 95 | void mem_set_mark(const int m) { 96 | mem_mark[m].mem_numallocs = mem_numallocs; 97 | mem_mark[m].mem_left = mem_left; 98 | mem_mark[m].mem_lastptr = mem_lastptr; 99 | mem_mark[m].mem_ptr = mem_ptr; 100 | printf("mem_set_mark: mark %d set at %p/%p, %d left\n", m, mem_lastptr, mem_ptr, mem_left); 101 | } 102 | 103 | void mem_free_to_mark(const int m) { 104 | mem_numallocs = mem_mark[m].mem_numallocs; 105 | mem_left = mem_mark[m].mem_left; 106 | mem_lastptr = mem_mark[m].mem_lastptr; 107 | mem_ptr = mem_mark[m].mem_ptr; 108 | printf("mem_free_to_mark: reset to mark %d: %p/%p, %d left\n", m, mem_lastptr, mem_ptr, mem_left); 109 | } 110 | 111 | u32 mem_get_free_space(void) { 112 | return mem_left; 113 | } 114 | -------------------------------------------------------------------------------- /src/engine/memory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | // we're rolling our own stack allocator, but we'll leave some space for malloc 6 | // in case libc uses it or something 7 | 8 | #define MALLOC_HEAP_SIZE 16384 9 | #define STACK_SIZE 16384 10 | 11 | enum mem_mark { 12 | MEM_MARK_LO, 13 | MEM_MARK_HI, 14 | MEM_MARK_COUNT, 15 | }; 16 | 17 | void mem_init(void); 18 | 19 | // simple stack type allocator 20 | void *mem_alloc(const u32 size); 21 | void mem_free(void *ptr); 22 | 23 | // same as `mem_alloc`, but zero-fills the allocated area 24 | void *mem_zeroalloc(const u32 size); 25 | 26 | // if `ptr` points to the top allocation, extends/shrinks it to `newsize` 27 | // if `ptr` is NULL, calls `mem_alloc(newsize)` 28 | void *mem_realloc(void *ptr, const u32 newsize); 29 | 30 | // marks current alloc position 31 | // mark can be MEM_MARK_LO or MEM_MARK_HI 32 | void mem_set_mark(const int mark); 33 | 34 | // frees everything allocated after the mark set by last MemSetMark call with the same mark 35 | void mem_free_to_mark(const int mark); 36 | 37 | // returns number of allocatable bytes 38 | u32 mem_get_free_space(void); 39 | -------------------------------------------------------------------------------- /src/engine/org.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/sound.h" 5 | 6 | #define ORG_START_CH 8 7 | #define ORG_MAX_TRACKS 16 8 | #define ORG_INVALID 0xFFFF 9 | #define ORG_PREVIOUS 0xFFFE 10 | #define ORG_MAX_VOLUME 0x7F 11 | 12 | typedef struct org_note { 13 | s32 pos; 14 | u8 len; 15 | u8 key; 16 | u8 vol; 17 | u8 pan; 18 | } org_note_t; 19 | 20 | void org_init(void); 21 | bool org_load(const u32 id, u8 *data, sfx_bank_t *bank); 22 | void org_free(void); 23 | void org_restart_from(const s32 pos); 24 | void org_tick(void); 25 | void org_pause(const bool paused); 26 | void org_start_fade(void); 27 | 28 | u32 org_get_id(void); 29 | int org_get_wait(void); 30 | int org_get_pos(void); 31 | u32 org_get_mute_mask(void); 32 | u32 org_set_mute_mask(const u32 mask); 33 | int org_get_master_volume(void); 34 | void org_set_master_volume(const int vol); 35 | 36 | org_note_t *org_get_track(const int tracknum, u32 *numnotes); 37 | org_note_t *org_get_track_pos(const int tracknum); 38 | -------------------------------------------------------------------------------- /src/engine/saveicon.inc: -------------------------------------------------------------------------------- 1 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 2 | 0x00, 0x00, 0x21, 0x22, 0x22, 0x22, 0x00, 0x00, 3 | 0x00, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01, 4 | 0x30, 0x10, 0x44, 0x44, 0x55, 0x55, 0x01, 0x00, 5 | 0x60, 0x40, 0x75, 0x22, 0x22, 0x75, 0x05, 0x00, 6 | 0x00, 0x46, 0x72, 0x22, 0x22, 0x72, 0x65, 0x00, 7 | 0x00, 0x46, 0x72, 0x22, 0x22, 0x72, 0x62, 0x00, 8 | 0x00, 0x40, 0x22, 0x22, 0x22, 0x22, 0x04, 0x00, 9 | 0x00, 0x00, 0x44, 0x44, 0x45, 0x44, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x20, 0x54, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x42, 0x44, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x42, 0x44, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x22, 0x11, 0x02, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x44, 0x44, 0x04, 0x00, 0x00, 17 | 0x00, 0x00, 0x21, 0x22, 0x22, 0x22, 0x00, 0x00, 18 | 0x00, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01, 19 | 0x30, 0x10, 0x44, 0x44, 0x55, 0x55, 0x01, 0x00, 20 | 0x60, 0x40, 0x75, 0x22, 0x22, 0x75, 0x05, 0x00, 21 | 0x00, 0x46, 0x72, 0x22, 0x22, 0x72, 0x65, 0x00, 22 | 0x00, 0x46, 0x72, 0x22, 0x22, 0x72, 0x62, 0x00, 23 | 0x00, 0x40, 0x22, 0x22, 0x22, 0x22, 0x04, 0x00, 24 | 0x00, 0x00, 0x44, 0x44, 0x45, 0x44, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x20, 0x54, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x10, 0x22, 0x01, 0x04, 0x00, 29 | 0x00, 0x00, 0x40, 0x11, 0x11, 0x11, 0x04, 0x00, 30 | 0x00, 0x00, 0x40, 0x11, 0x11, 0x11, 0x04, 0x00, 31 | 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x21, 0x22, 0x22, 0x22, 0x00, 0x00, 34 | 0x00, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x01, 35 | 0x30, 0x10, 0x44, 0x44, 0x55, 0x55, 0x01, 0x00, 36 | 0x60, 0x40, 0x75, 0x22, 0x22, 0x75, 0x05, 0x00, 37 | 0x00, 0x46, 0x72, 0x22, 0x22, 0x72, 0x65, 0x00, 38 | 0x00, 0x46, 0x72, 0x22, 0x22, 0x72, 0x62, 0x00, 39 | 0x00, 0x40, 0x22, 0x22, 0x22, 0x22, 0x04, 0x00, 40 | 0x00, 0x00, 0x44, 0x44, 0x45, 0x44, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x20, 0x54, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x42, 0x44, 0x00, 0x00, 0x00, 43 | 0x00, 0x00, 0x20, 0x40, 0x44, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x20, 0x10, 0x11, 0x42, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x14, 0x11, 0x41, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x14, 0x11, 0x41, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | -------------------------------------------------------------------------------- /src/engine/sound.c: -------------------------------------------------------------------------------- 1 | #include "engine/sound.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "engine/common.h" 7 | #include "engine/filesystem.h" 8 | #include "engine/memory.h" 9 | #include "spu.h" 10 | 11 | #define SFX_FREQ 22050 12 | 13 | // current sfx volume, 0-3FFF 14 | int snd_sfx_volume = 0x3FFF; 15 | 16 | sfx_bank_t *snd_main_bank; 17 | 18 | // channel each sample last played on 19 | static s16 sample_chan[MAX_SFX]; 20 | 21 | // last channel a sample was played on 22 | static int last_chan = CHAN_SOUND; 23 | 24 | // sfx channel state 25 | static struct { 26 | s32 sample; // sample playing (or last played) on this channel 27 | s16 prio; // priority of the sample playing (or last played) on this channel 28 | u16 loop; // is this channel looping? 29 | } chan_state[SFX_NUM_CHANNELS]; 30 | 31 | void snd_init(const char *mainbankpath) { 32 | spu_init(); 33 | 34 | snd_main_bank = snd_load_sfx_bank(mainbankpath); 35 | if (!snd_main_bank) PANIC("could not load\n%s", mainbankpath); 36 | 37 | // turn up all the SFX channels 38 | for (int i = 0; i < SFX_NUM_CHANNELS; ++i) 39 | spu_set_voice_volume(i, SPU_MAX_VOLUME); 40 | 41 | // clear sfx->channel mapping 42 | for (int i = 0; i < MAX_SFX; ++i) 43 | sample_chan[i] = -1; 44 | 45 | snd_sfx_volume = 0x3FFF; 46 | 47 | spu_flush_voices(); 48 | } 49 | 50 | int snd_upload_sfx_bank(sfx_bank_t *bank, u8 *bank_data) { 51 | if (!bank_data) 52 | bank_data = (u8 *)bank + sizeof(*bank) + sizeof(u32) * bank->num_sfx; 53 | 54 | ASSERT(spuram_ptr == bank->sfx_addr[0] || spuram_ptr == bank->sfx_addr[1]); 55 | 56 | SpuSetTransferMode(SPU_TRANSFER_BY_DMA); 57 | spu_set_transfer_addr(spuram_ptr); 58 | SpuWrite((void *)bank_data, bank->data_size); 59 | spu_wait_for_transfer(); 60 | 61 | spuram_ptr += bank->data_size; 62 | 63 | return TRUE; 64 | } 65 | 66 | sfx_bank_t *snd_read_sfx_bank(fs_file_t *f) { 67 | const u32 buflen = fs_fread_u32(f); 68 | const u32 num_sfx = fs_fread_u32(f); 69 | 70 | sfx_bank_t *bank = mem_alloc(sizeof(*bank) + sizeof(u32) * num_sfx); 71 | bank->data_size = buflen; 72 | bank->num_sfx = num_sfx; 73 | fs_fread_or_die(&bank->sfx_addr[0], sizeof(u32) * num_sfx, 1, f); 74 | 75 | u8 *buf = mem_alloc(buflen); 76 | fs_fread_or_die(buf, buflen, 1, f); 77 | 78 | snd_upload_sfx_bank(bank, buf); 79 | 80 | mem_free(buf); 81 | 82 | return bank; 83 | } 84 | 85 | sfx_bank_t *snd_load_sfx_bank(const char *path) { 86 | fs_file_t *f = fs_fopen(path, 0); 87 | if (!f) PANIC("could not open\n%s", path); 88 | sfx_bank_t *ret = snd_read_sfx_bank(f); 89 | fs_fclose(f); 90 | return ret; 91 | } 92 | 93 | void snd_free_sfx_bank_data(sfx_bank_t *bank) { 94 | const u32 prevaddr = spuram_ptr - bank->data_size; 95 | if (prevaddr == bank->sfx_addr[0] || prevaddr == bank->sfx_addr[1]) 96 | spuram_ptr = prevaddr; 97 | } 98 | 99 | void snd_free_sfx_bank(sfx_bank_t *bank) { 100 | ASSERT(bank); 101 | // free SPU RAM if this is the last loaded bank 102 | snd_free_sfx_bank_data(bank); 103 | // free the bank header 104 | mem_free(bank); 105 | } 106 | 107 | static inline int snd_find_chan(const int start_idx, const int prio, const int sfx) { 108 | // check if the sample is already playing somewhere; if so, override it like in the original game 109 | const int sample_ch = sample_chan[sfx]; 110 | if (sample_ch >= 0 && chan_state[sample_ch].sample == sfx) 111 | return sample_ch; 112 | 113 | // otherwise scan through channels and see what we can override 114 | const u32 endmask = spu_get_voice_end_mask(); 115 | for (int n = start_idx; n < start_idx + SFX_NUM_CHANNELS; ++n) { 116 | const int i = n % SFX_NUM_CHANNELS; 117 | // nothing ever played here OR channel was stopped OR playback complete; can be used 118 | if (chan_state[i].sample == 0 || ((endmask & SPU_VOICECH(i)) && !chan_state[i].loop)) 119 | return i; 120 | } 121 | 122 | for (int n = start_idx; n < start_idx + SFX_NUM_CHANNELS; ++n) { 123 | const int i = n % SFX_NUM_CHANNELS; 124 | // lower priority; can be overridden 125 | if (prio > chan_state[i].prio) 126 | return i; 127 | } 128 | 129 | // nothing found 130 | return -1; 131 | } 132 | 133 | int snd_play_sound_freq(int prio, const int no, const int freq, const bool loop) { 134 | ASSERT(snd_main_bank); 135 | 136 | if (no < 1) 137 | return -1; // null sound, do nothing 138 | 139 | if (no >= snd_main_bank->num_sfx) 140 | PANIC("unknown sound %03d (max is %03d)", no, snd_main_bank->num_sfx); 141 | 142 | int ch = snd_find_chan(last_chan + 1, prio, no); 143 | if (ch < 0) { 144 | if (prio > PRIO_LOW) 145 | ch = (last_chan + 1) % SFX_NUM_CHANNELS; // just override whatever 146 | else 147 | return -1; // don't play low priority sounds when out of channels 148 | } 149 | 150 | spu_set_voice_volume(ch, snd_sfx_volume); 151 | spu_play_sample(ch, snd_main_bank->sfx_addr[no], freq); 152 | 153 | sample_chan[no] = ch; 154 | chan_state[ch].sample = no; 155 | chan_state[ch].loop = loop; 156 | chan_state[ch].prio = prio; 157 | last_chan = ch; 158 | 159 | return ch; 160 | } 161 | 162 | int snd_play_sound(int prio, const int no, const bool loop) { 163 | return snd_play_sound_freq(prio, no, SFX_FREQ, loop); 164 | } 165 | 166 | void snd_stop_sound(const int no) { 167 | ASSERT(no > 0 && no < MAX_SFX); 168 | snd_stop_channel(sample_chan[no]); 169 | } 170 | 171 | void snd_stop_channel(const int ch) { 172 | spu_key_off(SPU_VOICECH(ch)); 173 | chan_state[ch].loop = FALSE; 174 | chan_state[ch].sample = 0; 175 | chan_state[ch].prio = 0; 176 | } 177 | 178 | void snd_set_sfx_volume(const int vol) { 179 | if (vol > SFX_MAX_VOLUME) 180 | snd_sfx_volume = SFX_MAX_VOLUME; 181 | else if (vol < 0) 182 | snd_sfx_volume = 0; 183 | else 184 | snd_sfx_volume = vol; 185 | } 186 | -------------------------------------------------------------------------------- /src/engine/sound.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/filesystem.h" 5 | 6 | #define MAX_SFX 160 7 | #define SFX_NUM_CHANNELS 8 8 | #define SFX_MAIN_BANK "\\MAIN\\MAIN.SFX;1" 9 | #define SFX_MAX_VOLUME 0x3FFF 10 | 11 | typedef enum { 12 | CHAN_SOUND = 0, // sfx channels from this to CHAN_MUSIC 13 | CHAN_MUSIC = 8, // music channels from this to CHAN_COUNT 14 | CHAN_COUNT = 24 15 | } sound_channel_t; 16 | 17 | typedef enum { 18 | PRIO_LOW = 1, 19 | PRIO_NORMAL = 2, 20 | PRIO_HIGH = 3, 21 | } sound_priority_t; 22 | 23 | typedef struct { 24 | u32 data_size; // size of raw SPU data at the end 25 | u32 num_sfx; // number of samples in bank, including #0 (dummy) and all the unused samples 26 | u32 sfx_addr[]; // address in SPU RAM of each sample, first one is always 0, others may be 0 (means it's unused) 27 | } sfx_bank_t; 28 | 29 | // main sample bank; never unloaded 30 | extern sfx_bank_t *snd_main_bank; 31 | 32 | // current sfx volume, 0-3FFF 33 | extern int snd_sfx_volume; 34 | 35 | // initializes the spu and loads the main sample bank 36 | void snd_init(const char *mainbankpath); 37 | 38 | // loads a sample bank into a sfx_bank struct and uploads it to spu ram 39 | int snd_upload_sfx_bank(sfx_bank_t *bank, u8 *bank_data); 40 | sfx_bank_t *snd_read_sfx_bank(fs_file_t *f); 41 | sfx_bank_t *snd_load_sfx_bank(const char *path); 42 | 43 | // frees a sfx_bank struct 44 | void snd_free_sfx_bank(sfx_bank_t *bank); 45 | void snd_free_sfx_bank_data(sfx_bank_t *bank); 46 | 47 | // plays a sample from the main sample bank 48 | // samples with higher `prio` override samples with lower `prio` 49 | int snd_play_sound(int prio, const int no, const bool loop); 50 | 51 | // plays a sample from the main sample bank with specified frequency 52 | // samples with higher `prio` override samples with lower `prio` 53 | int snd_play_sound_freq(int prio, const int no, const int freq, const bool loop); 54 | 55 | // stop sample `no` if it's playing 56 | void snd_stop_sound(const int no); 57 | 58 | // stops channel `ch` if something's playing on it 59 | void snd_stop_channel(const int ch); 60 | 61 | // set sfx volume, 0-3FFF 62 | void snd_set_sfx_volume(const int vol); 63 | -------------------------------------------------------------------------------- /src/engine/spu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "engine/common.h" 5 | #include "engine/spu.h" 6 | 7 | // still faster to operate the voices using the registers 8 | // than to get through libspu's attribute bullshit 9 | 10 | #define SPU_VOICE_BASE ((volatile u16 *)(0x1F801C00)) 11 | #define SPU_KEY_ON_LO ((volatile u16 *)(0x1F801D88)) 12 | #define SPU_KEY_ON_HI ((volatile u16 *)(0x1F801D8A)) 13 | #define SPU_KEY_OFF_LO ((volatile u16 *)(0x1F801D8C)) 14 | #define SPU_KEY_OFF_HI ((volatile u16 *)(0x1F801D8E)) 15 | #define SPU_KEY_END ((volatile u32 *)(0x1F801D9C)) 16 | 17 | struct spu_voice { 18 | volatile s16 vol_left; 19 | volatile s16 vol_right; 20 | volatile u16 sample_rate; 21 | volatile u16 sample_startaddr; 22 | volatile u16 attack_decay; 23 | volatile u16 sustain_release; 24 | volatile u16 vol_current; 25 | volatile u16 sample_repeataddr; 26 | }; 27 | #define SPU_VOICE(x) (((volatile struct spu_voice *)SPU_VOICE_BASE) + (x)) 28 | 29 | #define PAN_SHIFT 8 30 | 31 | u32 spuram_ptr = SPU_RAM_START; 32 | 33 | // saved state for stop/play 34 | static struct { 35 | u32 addr; 36 | s16 vol; // 0 to SPU_MAX_VOLUME 37 | s16 pan; // -255 to 255 38 | u16 freq; 39 | u16 dirty; 40 | } voice_state[SPU_NUM_VOICES]; 41 | 42 | void spu_init(void) { 43 | SpuInit(); 44 | SpuSetTransferMode(SpuTransByDMA); 45 | SpuSetCommonMasterVolume(0x3FFF, 0x3FFF); 46 | spu_clear_all_voices(); 47 | spuram_ptr = SPU_RAM_START; 48 | } 49 | 50 | void spu_key_on(const u32 mask) { 51 | *SPU_KEY_ON_LO = mask; 52 | *SPU_KEY_ON_HI = mask >> 16; 53 | } 54 | 55 | void spu_key_off(const u32 mask) { 56 | *SPU_KEY_OFF_LO = mask; 57 | *SPU_KEY_OFF_HI = mask >> 16; 58 | } 59 | 60 | void spu_clear_voice(const u32 v) { 61 | SPU_VOICE(v)->vol_left = 0; 62 | SPU_VOICE(v)->vol_right = 0; 63 | SPU_VOICE(v)->sample_rate = 0; 64 | SPU_VOICE(v)->sample_startaddr = 0; 65 | SPU_VOICE(v)->sample_repeataddr = 0; 66 | SPU_VOICE(v)->attack_decay = 0x000F; 67 | SPU_VOICE(v)->sustain_release = 0x0000; 68 | SPU_VOICE(v)->vol_current = 0; 69 | voice_state[v].vol = 0; 70 | voice_state[v].pan = 0; 71 | voice_state[v].addr = 0; 72 | voice_state[v].freq = 0; 73 | voice_state[v].dirty = 0; 74 | } 75 | 76 | void spu_clear_all_voices(void) { 77 | spu_key_off(0xFFFFFFFF); 78 | for (u32 i = 0; i < SPU_NUM_VOICES; ++i) 79 | spu_clear_voice(i); 80 | } 81 | 82 | void spu_set_voice_volume(const u32 v, const s16 vol) { 83 | voice_state[v].vol = vol; 84 | voice_state[v].dirty = 1; 85 | } 86 | 87 | void spu_set_voice_pan(const u32 v, const s16 pan) { 88 | voice_state[v].pan = pan; 89 | voice_state[v].dirty = 1; 90 | } 91 | 92 | void spu_set_voice_freq(const u32 v, const u32 hz) { 93 | voice_state[v].freq = freq_to_pitch(hz); 94 | voice_state[v].dirty = 1; 95 | } 96 | 97 | void spu_set_voice_pitch(const u32 v, const u32 pitch) { 98 | voice_state[v].freq = pitch; 99 | voice_state[v].dirty = 1; 100 | } 101 | 102 | void spu_set_voice_addr(const u32 v, const u32 addr) { 103 | voice_state[v].addr = (addr >> 3); 104 | voice_state[v].dirty = 1; 105 | } 106 | 107 | u32 spu_get_voice_end_mask(void) { 108 | return *SPU_KEY_END; 109 | } 110 | 111 | static inline void spu_update_voice_volume(const u32 v) { 112 | int vol_left = voice_state[v].vol; 113 | int vol_right = vol_left; 114 | const int pan = voice_state[v].pan; 115 | if (pan < 0) 116 | vol_right = (vol_right * -pan) >> PAN_SHIFT; 117 | else if (pan > 0) 118 | vol_left = (vol_left * pan) >> PAN_SHIFT; 119 | SPU_VOICE(v)->vol_left = vol_left; 120 | SPU_VOICE(v)->vol_right = vol_right; 121 | } 122 | 123 | void spu_flush_voices(void) { 124 | for (int v = 0; v < SPU_NUM_VOICES; ++v) { 125 | if (voice_state[v].dirty) { 126 | voice_state[v].dirty = 0; 127 | spu_update_voice_volume(v); 128 | SPU_VOICE(v)->sample_rate = voice_state[v].freq; 129 | SPU_VOICE(v)->sample_startaddr = voice_state[v].addr; 130 | } 131 | } 132 | } 133 | 134 | void spu_play_sample(const u32 ch, const u32 addr, const u32 freq) { 135 | voice_state[ch].freq = freq_to_pitch(freq); 136 | voice_state[ch].addr = (addr >> 3); 137 | SPU_VOICE(ch)->sample_rate = voice_state[ch].freq; 138 | SPU_VOICE(ch)->sample_startaddr = voice_state[ch].addr; 139 | spu_key_on(SPU_VOICECH(ch)); // this restarts the channel on the new address 140 | if (voice_state[ch].dirty) { 141 | // update volume in case it was changed 142 | spu_update_voice_volume(ch); 143 | voice_state[ch].dirty = 0; 144 | } 145 | } 146 | 147 | void spu_wait_for_transfer(void) { 148 | SpuIsTransferCompleted(SPU_TRANSFER_WAIT); 149 | } 150 | 151 | u32 spu_set_transfer_addr(u32 addr) { 152 | return SpuSetTransferStartAddr(addr); 153 | } 154 | -------------------------------------------------------------------------------- /src/engine/spu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "engine/common.h" 7 | 8 | #define SPU_NUM_VOICES 24 9 | #define SPU_MAX_VOLUME 0x3FFF 10 | #define SPU_RAM_START 0x1000 11 | 12 | extern u32 spuram_ptr; 13 | 14 | void spu_init(void); 15 | void spu_key_on(const u32 mask); 16 | void spu_key_off(const u32 mask); 17 | void spu_clear_voice(const u32 v) ; 18 | void spu_set_voice_volume(const u32 v, const s16 vol); 19 | void spu_set_voice_pan(const u32 v, const s16 pan); 20 | void spu_set_voice_freq(const u32 v, const u32 hz); 21 | void spu_set_voice_pitch(const u32 v, const u32 pitch); 22 | void spu_set_voice_addr(const u32 v, const u32 addr); 23 | u32 spu_get_voice_end_mask(void); 24 | void spu_flush_voices(void); 25 | void spu_play_sample(const u32 ch, const u32 addr, const u32 freq); 26 | void spu_wait_for_transfer(void); 27 | void spu_clear_all_voices(void); 28 | u32 spu_set_transfer_addr(u32 addr); 29 | 30 | static inline u16 freq_to_pitch(const u32 hz) { 31 | return (hz << 12) / 44100; 32 | } 33 | -------------------------------------------------------------------------------- /src/engine/surfacelist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum surface_id { 4 | SURFACE_ID_TITLE = 0, 5 | SURFACE_ID_PIXEL = 1, 6 | SURFACE_ID_LEVEL_TILESET = 2, 7 | SURFACE_ID_FADE = 6, 8 | SURFACE_ID_ITEM_IMAGE = 8, 9 | SURFACE_ID_MAP = 9, 10 | SURFACE_ID_SCREEN_GRAB = 10, 11 | SURFACE_ID_ARMS = 11, 12 | SURFACE_ID_ARMS_IMAGE = 12, 13 | SURFACE_ID_ROOM_NAME = 13, 14 | SURFACE_ID_STAGE_ITEM = 14, 15 | SURFACE_ID_LOADING = 15, 16 | SURFACE_ID_MY_CHAR = 16, 17 | SURFACE_ID_BULLET = 17, 18 | SURFACE_ID_CARET = 19, 19 | SURFACE_ID_NPC_SYM = 20, 20 | SURFACE_ID_LEVEL_SPRITESET_1 = 21, 21 | SURFACE_ID_LEVEL_SPRITESET_2 = 22, 22 | SURFACE_ID_NPC_REGU = 23, 23 | SURFACE_ID_TEXT_BOX = 26, 24 | SURFACE_ID_FACE = 27, 25 | SURFACE_ID_LEVEL_BACKGROUND = 28, 26 | SURFACE_ID_VALUE_VIEW = 29, 27 | SURFACE_ID_TEXT_LINE1 = 30, 28 | SURFACE_ID_TEXT_LINE2 = 31, 29 | SURFACE_ID_TEXT_LINE3 = 32, 30 | SURFACE_ID_TEXT_LINE4 = 33, 31 | SURFACE_ID_TEXT_LINE5 = 34, 32 | SURFACE_ID_CREDIT_CAST = 35, 33 | SURFACE_ID_CREDITS_IMAGE = 36, 34 | SURFACE_ID_CASTS = 37, 35 | SURFACE_ID_FONT1 = 38, 36 | SURFACE_ID_FONT2 = 39, 37 | SURFACE_ID_MAX = 40 38 | }; 39 | -------------------------------------------------------------------------------- /src/engine/timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "engine/common.h" 6 | #include "engine/org.h" 7 | #include "engine/timer.h" 8 | 9 | #define RATE_HBLANK 15625 10 | #define RATE_SYSCLK8 4233600 // ~(1sec / 0.23620559334us) 11 | 12 | // these are undeclared in psyq 13 | extern void *InterruptCallback(int irq, void (*func)(void)); 14 | extern void ChangeClearRCnt(int t, int m); 15 | 16 | // global timer; ticks at 100hz 17 | volatile u32 timer_ticks; 18 | // next music tick 19 | volatile u32 timer_org_nexttick; 20 | // music tickrate 21 | volatile u32 timer_org_delay; 22 | 23 | static void timer_cb(void) { 24 | const u32 now = ++timer_ticks; 25 | if (now >= timer_org_nexttick && timer_org_delay) { 26 | org_tick(); 27 | timer_org_nexttick = now + timer_org_delay; 28 | } 29 | } 30 | 31 | void timer_init(void) { 32 | timer_ticks = 0; 33 | timer_org_nexttick = 0; 34 | 35 | const u32 dt = RATE_SYSCLK8 / TIMER_RATE; 36 | 37 | EnterCriticalSection(); 38 | SetRCnt(RCntCNT2, dt, RCntMdINTR); 39 | InterruptCallback(6, timer_cb); // IRQ6 is RCNT2 40 | StartRCnt(RCntCNT2); 41 | ChangeClearRCnt(2, 0); 42 | ExitCriticalSection(); 43 | } 44 | -------------------------------------------------------------------------------- /src/engine/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | #define TIMER_RATE 100 6 | 7 | typedef void (*timer_func_t)(void); 8 | extern timer_func_t timer_callback; 9 | 10 | // global timer; ticks at 100hz 11 | extern volatile u32 timer_ticks; 12 | // next music tick 13 | extern volatile u32 timer_org_nexttick; 14 | // music tickrate 15 | extern volatile u32 timer_org_delay; 16 | 17 | void timer_init(void); 18 | -------------------------------------------------------------------------------- /src/game/boss_act/boss_act.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "engine/common.h" 6 | #include "engine/math.h" 7 | #include "engine/graphics.h" 8 | #include "engine/sound.h" 9 | #include "engine/input.h" 10 | 11 | #include "game/game.h" 12 | #include "game/npc.h" 13 | #include "game/caret.h" 14 | #include "game/bullet.h" 15 | #include "game/player.h" 16 | #include "game/stage.h" 17 | #include "game/camera.h" 18 | 19 | void boss_act_core(npc_t *root); 20 | void boss_act_undead_core(npc_t *root); 21 | void boss_act_balfrog(npc_t *root); 22 | void boss_act_ballos(npc_t *root); 23 | void boss_act_omega(npc_t *root); 24 | void boss_act_ironhead(npc_t *root); 25 | void boss_act_heavy_press(npc_t *root); 26 | void boss_act_twins(npc_t *root); 27 | void boss_act_monster_x(npc_t *root); 28 | -------------------------------------------------------------------------------- /src/game/boss_act/boss_act_heavy_press.c: -------------------------------------------------------------------------------- 1 | #include "game/boss_act/boss_act.h" 2 | 3 | void boss_act_heavy_press(npc_t *root) { 4 | npc_t *npc = npc_boss; 5 | static u8 flash; 6 | int i; 7 | int x; 8 | 9 | switch (npc->act) { 10 | case 0: 11 | npc->act = 10; 12 | npc->cond = NPCCOND_ALIVE; 13 | npc->exp = 1; 14 | npc->dir = 2; 15 | npc->x = 0; 16 | npc->y = 0; 17 | npc->view.front = 40 * 0x200; 18 | npc->view.top = 60 * 0x200; 19 | npc->view.back = 40 * 0x200; 20 | npc->view.bottom = 60 * 0x200; 21 | npc->snd_hit = 54; 22 | npc->hit.front = 49 * 0x200; 23 | npc->hit.top = 60 * 0x200; 24 | npc->hit.back = 40 * 0x200; 25 | npc->hit.bottom = 48 * 0x200; 26 | npc->bits = (NPC_IGNORE_SOLIDITY | NPC_SOLID_HARD | NPC_EVENT_WHEN_KILLED | NPC_SHOW_DAMAGE); 27 | npc->size = 3; 28 | npc->damage = 10; 29 | npc->event_num = 1000; 30 | npc->life = 700; 31 | npc_boss_max = 3; 32 | for (int i = 0; i <= npc_boss_max; ++i) 33 | npc_boss[i].surf = SURFACE_ID_LEVEL_SPRITESET_2; 34 | break; 35 | 36 | case 5: 37 | npc->act = 6; 38 | npc->x = 0; 39 | npc->y = 0; 40 | npc_boss[1].cond = 0; 41 | npc_boss[2].cond = 0; 42 | break; 43 | 44 | case 10: 45 | npc->act = 11; 46 | npc->x = 160 * 0x200; 47 | npc->y = 74 * 0x200; 48 | break; 49 | 50 | case 20: 51 | npc->damage = 0; 52 | npc->act = 21; 53 | npc->x = 160 * 0x200; 54 | npc->y = 413 * 0x200; 55 | npc->bits &= ~NPC_SOLID_HARD; 56 | npc_boss[1].cond = 0; 57 | npc_boss[2].cond = 0; 58 | // Fallthrough 59 | case 21: 60 | if (++npc->act_wait % 0x10 == 0) 61 | npc_spawn_death_fx(npc->x + (m_rand(-40, 40) * 0x200), npc->y + (m_rand(-60, 60) * 0x200), 1, 1, 0); 62 | 63 | break; 64 | 65 | case 30: 66 | npc->act = 31; 67 | npc->anim = 2; 68 | npc->x = 160 * 0x200; 69 | npc->y = 64 * 0x200; 70 | // Fallthrough 71 | case 31: 72 | npc->y += 4 * 0x200; 73 | 74 | if (npc->y >= 413 * 0x200) { 75 | npc->y = 413 * 0x200; 76 | npc->anim = 0; 77 | npc->act = 20; 78 | snd_play_sound(PRIO_NORMAL, 44, FALSE); 79 | 80 | for (i = 0; i < 5; ++i) { 81 | x = npc->x + (m_rand(-40, 40) * 0x200); 82 | npc_spawn(4, x, npc->y + (60 * 0x200), 0, 0, 0, NULL, 0x100); 83 | } 84 | } 85 | 86 | break; 87 | 88 | case 100: 89 | npc->act = 101; 90 | npc->count2 = 9; 91 | npc->act_wait = -100; 92 | 93 | npc_boss[1].cond = 0x80; 94 | npc_boss[1].hit.front = 14 * 0x200; 95 | npc_boss[1].hit.back = 14 * 0x200; 96 | npc_boss[1].hit.top = 8 * 0x200; 97 | npc_boss[1].hit.bottom = 8 * 0x200; 98 | npc_boss[1].bits = (NPC_INVULNERABLE | NPC_IGNORE_SOLIDITY); 99 | 100 | npc_boss[2] = npc_boss[1]; 101 | 102 | npc_boss[3].cond = 0x90; 103 | npc_boss[3].bits |= NPC_SHOOTABLE; 104 | npc_boss[3].hit.front = 6 * 0x200; 105 | npc_boss[3].hit.back = 6 * 0x200; 106 | npc_boss[3].hit.top = 8 * 0x200; 107 | npc_boss[3].hit.bottom = 8 * 0x200; 108 | 109 | npc_spawn(325, npc->x, npc->y + (60 * 0x200), 0, 0, 0, NULL, 0x100); 110 | // Fallthrough 111 | case 101: 112 | if (npc->count2 > 1 && npc->life < npc->count2 * 70) { 113 | --npc->count2; 114 | 115 | for (i = 0; i < 5; ++i) { 116 | stage_set_tile(i + 8, npc->count2, 0); 117 | npc_spawn_death_fx((i + 8) * 0x200 * 0x10, npc->count2 * 0x200 * 0x10, 0, 4, 0); 118 | snd_play_sound(PRIO_NORMAL, 12, FALSE); 119 | } 120 | } 121 | 122 | if (++npc->act_wait == 81 || npc->act_wait == 241) 123 | npc_spawn(323, 48 * 0x200, 240 * 0x200, 0, 0, 1, NULL, 0x100); 124 | 125 | if (npc->act_wait == 1 || npc->act_wait == 161) 126 | npc_spawn(323, 272 * 0x200, 240 * 0x200, 0, 0, 1, NULL, 0x100); 127 | 128 | if (npc->act_wait >= 300) { 129 | npc->act_wait = 0; 130 | npc_spawn(325, npc->x, npc->y + (60 * 0x200), 0, 0, 0, NULL, 0x100); 131 | } 132 | 133 | break; 134 | 135 | case 500: 136 | npc_boss[3].bits &= ~NPC_SHOOTABLE; 137 | 138 | npc->act = 501; 139 | npc->act_wait = 0; 140 | npc->count1 = 0; 141 | 142 | npc_delete_by_class(325, TRUE); 143 | npc_delete_by_class(330, TRUE); 144 | // Fallthrough 145 | case 501: 146 | if (++npc->act_wait % 0x10 == 0) { 147 | snd_play_sound(PRIO_NORMAL, 12, FALSE); 148 | npc_spawn_death_fx(npc->x + (m_rand(-40, 40) * 0x200), npc->y + (m_rand(-60, 60) * 0x200), 1, 1, 0); 149 | } 150 | 151 | if (npc->act_wait == 95) npc->anim = 1; 152 | if (npc->act_wait == 98) npc->anim = 2; 153 | 154 | if (npc->act_wait > 100) npc->act = 510; 155 | 156 | break; 157 | 158 | case 510: 159 | npc->yvel += 0x40; 160 | npc->damage = 0x7F; 161 | npc->y += npc->yvel; 162 | 163 | if (npc->count1 == 0 && npc->y > 160 * 0x200) { 164 | npc->count1 = 1; 165 | npc->yvel = -0x200; 166 | npc->damage = 0; 167 | 168 | for (i = 0; i < 7; ++i) { 169 | stage_set_tile(i + 7, 14, 0); 170 | npc_spawn_death_fx((i + 7) * 0x200 * 0x10, 224 * 0x200, 0, 0, 0); 171 | snd_play_sound(PRIO_NORMAL, 12, FALSE); 172 | } 173 | } 174 | 175 | if (npc->y > 480 * 0x200) npc->act = 520; 176 | 177 | break; 178 | } 179 | 180 | npc_boss[1].x = npc->x - (24 * 0x200); 181 | npc_boss[1].y = npc->y + (52 * 0x200); 182 | 183 | npc_boss[2].x = npc->x + (24 * 0x200); 184 | npc_boss[2].y = npc->y + (52 * 0x200); 185 | 186 | npc_boss[3].x = npc->x; 187 | npc_boss[3].y = npc->y + (40 * 0x200); 188 | 189 | static const rect_t rc[3] = { 190 | {0, 0, 80, 120}, 191 | {80, 0, 160, 120}, 192 | {160, 0, 240, 120}, 193 | }; 194 | 195 | static const rect_t rc_damage[3] = { 196 | {0, 120, 80, 240}, 197 | {80, 120, 160, 240}, 198 | {160, 120, 240, 240}, 199 | }; 200 | 201 | if (npc->shock != 0) { 202 | if (++flash / 2 % 2) 203 | npc->rect = &rc[npc->anim]; 204 | else 205 | npc->rect = &rc_damage[npc->anim]; 206 | } else { 207 | npc->rect = &rc[npc->anim]; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/game/boss_act/boss_act_ironhead.c: -------------------------------------------------------------------------------- 1 | #include "game/boss_act/boss_act.h" 2 | 3 | void boss_act_ironhead(npc_t *root) { 4 | int i; 5 | npc_t *npc = npc_boss; 6 | static u8 flash; 7 | 8 | switch (npc->act) { 9 | case 0: 10 | npc->cond = NPCCOND_ALIVE; 11 | npc->exp = 1; 12 | npc->dir = 2; 13 | npc->act = 100; 14 | npc->x = 160 * 0x200; 15 | npc->y = 128 * 0x200; 16 | npc->view.front = 40 * 0x200; 17 | npc->view.top = 12 * 0x200; 18 | npc->view.back = 24 * 0x200; 19 | npc->view.bottom = 12 * 0x200; 20 | npc->snd_hit = 54; 21 | npc->hit.front = 16 * 0x200; 22 | npc->hit.top = 10 * 0x200; 23 | npc->hit.back = 16 * 0x200; 24 | npc->hit.bottom = 10 * 0x200; 25 | npc->bits = (NPC_IGNORE_SOLIDITY | NPC_SHOOTABLE | NPC_EVENT_WHEN_KILLED | NPC_SHOW_DAMAGE); 26 | npc->size = 3; 27 | npc->damage = 10; 28 | npc->event_num = 1000; 29 | npc->life = 400; 30 | npc->surf = SURFACE_ID_LEVEL_SPRITESET_2; 31 | npc_boss_max = 0; 32 | break; 33 | 34 | case 100: 35 | npc->act = 101; 36 | npc->bits &= ~NPC_SHOOTABLE; 37 | npc->act_wait = 0; 38 | // Fallthrough 39 | case 101: 40 | ++npc->act_wait; 41 | 42 | if (npc->act_wait > 50) { 43 | npc->act = 250; 44 | npc->act_wait = 0; 45 | } 46 | 47 | if (npc->act_wait % 4 == 0) 48 | npc_spawn(197, m_rand(15, 18) * (16 * 0x200), m_rand(2, 13) * (16 * 0x200), 0, 0, 0, NULL, 0x100); 49 | 50 | break; 51 | 52 | case 250: 53 | npc->act = 251; 54 | 55 | if (npc->dir == 2) { 56 | npc->x = 240 * 0x200; 57 | npc->y = player.y; 58 | } else { 59 | npc->x = 720 * 0x200; 60 | npc->y = m_rand(2, 13) * (16 * 0x200); 61 | } 62 | 63 | npc->tgt_x = npc->x; 64 | npc->tgt_y = npc->y; 65 | 66 | npc->yvel = m_rand(-0x200, 0x200); 67 | npc->xvel = m_rand(-0x200, 0x200); 68 | 69 | npc->bits |= NPC_SHOOTABLE; 70 | // Fallthrough 71 | case 251: 72 | if (npc->dir == 2) { 73 | npc->tgt_x += 2 * 0x200; 74 | } else { 75 | npc->tgt_x -= 1 * 0x200; 76 | 77 | if (npc->tgt_y < player.y) 78 | npc->tgt_y += 1 * 0x200; 79 | else 80 | npc->tgt_y -= 1 * 0x200; 81 | } 82 | 83 | if (npc->x < npc->tgt_x) 84 | npc->xvel += 8; 85 | else 86 | npc->xvel -= 8; 87 | 88 | if (npc->y < npc->tgt_y) 89 | npc->yvel += 8; 90 | else 91 | npc->yvel -= 8; 92 | 93 | if (npc->yvel > 0x200) npc->yvel = 0x200; 94 | if (npc->yvel < -0x200) npc->yvel = -0x200; 95 | 96 | npc->x += npc->xvel; 97 | npc->y += npc->yvel; 98 | 99 | if (npc->dir == 2) { 100 | if (npc->x > 720 * 0x200) { 101 | npc->dir = 0; 102 | npc->act = 100; 103 | } 104 | } else { 105 | if (npc->x < 272 * 0x200) { 106 | npc->dir = 2; 107 | npc->act = 100; 108 | } 109 | } 110 | 111 | if (npc->dir == 0) { 112 | ++npc->act_wait; 113 | 114 | if (npc->act_wait == 300 || npc->act_wait == 310 || npc->act_wait == 320) { 115 | snd_play_sound(PRIO_NORMAL, 39, FALSE); 116 | npc_spawn(198, npc->x + (10 * 0x200), npc->y + (1 * 0x200), m_rand(-3, 0) * 0x200, m_rand(-3, 3) * 0x200, 2, NULL, 0x100); 117 | } 118 | } 119 | 120 | ++npc->anim_wait; 121 | 122 | if (npc->anim_wait > 2) { 123 | npc->anim_wait = 0; 124 | ++npc->anim; 125 | } 126 | 127 | if (npc->anim > 7) npc->anim = 0; 128 | 129 | break; 130 | 131 | case 1000: 132 | npc->bits &= ~NPC_SHOOTABLE; 133 | npc->anim = 8; 134 | npc->damage = 0; 135 | npc->act = 1001; 136 | npc->tgt_x = npc->x; 137 | npc->tgt_y = npc->y; 138 | cam_start_quake_small(20); 139 | 140 | for (i = 0; i < 0x20; ++i) 141 | npc_spawn(4, npc->x + (m_rand(-128, 128) * 0x200), npc->y + (m_rand(-64, 64) * 0x200), 142 | m_rand(-128, 128) * 0x200, m_rand(-128, 128) * 0x200, 0, NULL, 0x100); 143 | 144 | npc_delete_by_class(197, TRUE); 145 | npc_delete_by_class(271, TRUE); 146 | npc_delete_by_class(272, TRUE); 147 | // Fallthrough 148 | case 1001: 149 | npc->tgt_x -= 1 * 0x200; 150 | 151 | npc->x = npc->tgt_x + (m_rand(-1, 1) * 0x200); 152 | npc->y = npc->tgt_y + (m_rand(-1, 1) * 0x200); 153 | 154 | if (++npc->act_wait % 4 == 0) 155 | npc_spawn(4, npc->x + (m_rand(-128, 128) * 0x200), npc->y + (m_rand(-64, 64) * 0x200), 156 | m_rand(-128, 128) * 0x200, m_rand(-128, 128) * 0x200, 0, NULL, 0x100); 157 | 158 | break; 159 | } 160 | 161 | static const rect_t rc[9] = { 162 | {0, 0, 64, 24}, {64, 0, 128, 24}, {128, 0, 192, 24}, {64, 0, 128, 24}, {0, 0, 64, 24}, 163 | {192, 0, 256, 24}, {256, 0, 320, 24}, {192, 0, 256, 24}, {256, 48, 320, 72}, 164 | }; 165 | 166 | static const rect_t rc_damage[9] = { 167 | {0, 24, 64, 48}, {64, 24, 128, 48}, {128, 24, 192, 48}, {64, 24, 128, 48}, {0, 24, 64, 48}, 168 | {192, 24, 256, 48}, {256, 24, 320, 48}, {192, 24, 256, 48}, {256, 48, 320, 72}, 169 | }; 170 | 171 | if (npc->shock != 0) { 172 | if (++flash / 2 % 2) 173 | npc->rect = &rc[npc->anim]; 174 | else 175 | npc->rect = &rc_damage[npc->anim]; 176 | } else { 177 | npc->rect = &rc[npc->anim]; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/game/bullet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/graphics.h" 5 | 6 | #define BULLET_MAX 0x40 7 | 8 | enum bullet_cond_flags { 9 | BULLETCOND_ALIVE = 0x80, 10 | }; 11 | 12 | typedef struct { 13 | hitbox_t view; 14 | gfx_texrect_t texrect; 15 | 16 | u32 flags; 17 | u32 bits; 18 | 19 | s32 x; 20 | s32 y; 21 | s32 xvel; 22 | s32 yvel; 23 | s32 tgt_x; 24 | s32 tgt_y; 25 | s32 enemy_xl; 26 | s32 enemy_yl; 27 | s32 block_xl; 28 | s32 block_yl; 29 | 30 | u16 class_num; 31 | s16 act; 32 | s16 act_wait; 33 | s16 anim; 34 | s16 anim_wait; 35 | s16 life; 36 | s16 life_count; 37 | s16 count1; 38 | s16 count2; 39 | s16 damage; 40 | 41 | u8 cond; 42 | u8 dir; 43 | } bullet_t; 44 | 45 | typedef struct { 46 | s8 damage; 47 | s8 life; 48 | s16 life_count; 49 | s16 bits; 50 | s16 enemy_xl; 51 | s16 enemy_yl; 52 | s16 block_xl; 53 | s16 block_yl; 54 | hitbox_t view; 55 | } bullet_class_t; 56 | 57 | extern bullet_t bullet_list[BULLET_MAX]; 58 | extern int bullet_list_max; 59 | 60 | void bullet_init(void); 61 | bullet_t *bullet_spawn(int class_num, int x, int y, int dir); 62 | void bullet_act(void); 63 | void bullet_draw(int cam_x, int cam_y); 64 | void bullet_destroy(bullet_t *bul); 65 | int bullet_count_by_arm(const int arm_id); 66 | int bullet_count_by_class(const int class_num); 67 | bool bullet_any_exist(void); 68 | -------------------------------------------------------------------------------- /src/game/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | enum cam_flash_mode { 6 | FLASH_MODE_NONE = 0, 7 | FLASH_MODE_EXPLOSION = 1, 8 | FLASH_MODE_FLASH = 2, 9 | }; 10 | 11 | typedef struct { 12 | s32 x; 13 | s32 y; 14 | s32 *tgt_x; 15 | s32 *tgt_y; 16 | s32 wait; 17 | s32 quake; 18 | s32 quake2; 19 | } cam_t; 20 | 21 | extern cam_t camera; 22 | 23 | void cam_init(void); 24 | void cam_reset(void); 25 | void cam_update(void); 26 | void cam_center_on_player(void); 27 | void cam_target_player(const int delay); 28 | void cam_target_npc(const int event_num, const int delay); 29 | void cam_target_boss(const int boss_num, const int delay); 30 | void cam_set_pos(const int fx, const int fy); 31 | void cam_set_target(s32 *tx, s32 *ty, const int wait); 32 | 33 | bool cam_is_fading(void); 34 | void cam_start_fade_out(const int dir); 35 | void cam_start_fade_in(const int dir); 36 | void cam_clear_fade(void); 37 | void cam_complete_fade(void); 38 | void cam_draw_fade(void); 39 | void cam_update_fade(void); 40 | 41 | void cam_start_quake_small(const int duration); 42 | void cam_start_quake_big(const int duration); 43 | void cam_stop_quake(void); 44 | 45 | void cam_start_flash(const int x, const int y, const int mode); 46 | void cam_stop_flash(void); 47 | void cam_draw_flash(void); 48 | -------------------------------------------------------------------------------- /src/game/caret.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // also known as particles 4 | 5 | #define CARET_MAX 0x40 6 | 7 | enum caret_classnames { 8 | CARET_NULL = 0, 9 | CARET_BUBBLE = 1, 10 | CARET_PROJECTILE_DISSIPATION = 2, 11 | CARET_SHOOT = 3, 12 | CARET_SNAKE_AFTERIMAGE = 4, 13 | CARET_ZZZ = 5, 14 | CARET_SNAKE_AFTERIMAGE_DUPLICATE = 6, 15 | CARET_EXHAUST = 7, 16 | CARET_DROWNED_QUOTE = 8, 17 | CARET_QUESTION_MARK = 9, 18 | CARET_LEVEL_UP = 10, 19 | CARET_HURT_PARTICLES = 11, 20 | CARET_EXPLOSION = 12, 21 | CARET_TINY_PARTICLES = 13, 22 | CARET_UNKNOWN = 14, 23 | CARET_PROJECTILE_DISSIPATION_TINY = 15, 24 | CARET_EMPTY = 16, 25 | CARET_PUSH_JUMP_KEY = 17 26 | }; 27 | 28 | typedef struct { 29 | gfx_texrect_t texrect; 30 | s32 x; 31 | s32 y; 32 | s32 xvel; 33 | s32 yvel; 34 | s32 view_left; 35 | s32 view_top; 36 | s32 dir; 37 | u16 class_num; 38 | u16 cond; 39 | s16 act; 40 | s16 act_wait; 41 | s16 anim; 42 | s16 anim_wait; 43 | } caret_t; 44 | 45 | extern caret_t caret_list[CARET_MAX]; 46 | extern int caret_list_max; 47 | 48 | void caret_init(void); 49 | void caret_act(void); 50 | void caret_draw(int cam_x, int cam_y); 51 | void caret_spawn(int x, int y, int class_num, int dir); 52 | -------------------------------------------------------------------------------- /src/game/credits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | void credits_start(void); 6 | void credits_stop(void); 7 | void credits_update(void); 8 | void credits_draw(void); 9 | void credits_show_image(const int id); 10 | void credits_hide_image(void); 11 | -------------------------------------------------------------------------------- /src/game/dmgnum.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "engine/common.h" 4 | #include "engine/graphics.h" 5 | #include "engine/math.h" 6 | 7 | #include "game/dmgnum.h" 8 | #include "game/hud.h" 9 | 10 | dmgnum_t dmgnum_list[DMGNUM_MAX]; 11 | 12 | static int dmgnum_last = 0; 13 | 14 | void dmgnum_init(void) { 15 | memset_word(dmgnum_list, 0, sizeof(dmgnum_list)); 16 | } 17 | 18 | void dmgnum_spawn(int *tgt_x, int *tgt_y, int val) { 19 | if (val == 0) 20 | return; 21 | 22 | int i; 23 | for (i = 0; i < DMGNUM_MAX; ++i) { 24 | const dmgnum_t *tnum = &dmgnum_list[i]; 25 | if (tnum->cond && tnum->tgt_x == tgt_x && m_sign(val) == m_sign(tnum->val)) 26 | break; 27 | } 28 | 29 | dmgnum_t *dnum; 30 | if (i == DMGNUM_MAX) { 31 | // new damage display 32 | dnum = &dmgnum_list[dmgnum_last++]; 33 | if (dmgnum_last == DMGNUM_MAX) 34 | dmgnum_last = 0; 35 | dnum->count = 0; 36 | dnum->yofs = 0; 37 | dnum->val = val; 38 | } else { 39 | // accumulate into old damage display (e.g. exp values) 40 | dnum = &dmgnum_list[i]; 41 | dnum->count = 32; 42 | dnum->val += val; 43 | val = dnum->val; 44 | } 45 | 46 | dnum->cond = TRUE; 47 | dnum->tgt_x = tgt_x; 48 | dnum->tgt_y = tgt_y; 49 | dnum->vofs = 0; 50 | 51 | const int ofs = (val < 0) ? 10 : 0; 52 | int digits[5] = { 0, 0, 0, 0, 0 }; 53 | int first = 4; 54 | if (ofs) val = -val; 55 | 56 | for (; val && first > 0; --first) { 57 | digits[first] = ofs + (val % 10); 58 | val /= 10; 59 | } 60 | 61 | digits[first] = 20 + !!ofs; 62 | 63 | dnum->digits = 5 - first; 64 | dnum->xofs = -TO_FIX(8 * dnum->digits / 2); 65 | 66 | for (i = first; i < 5; ++i) 67 | dnum->texrects[i - first] = &hud_rc_digit[digits[i]]; 68 | } 69 | 70 | void dmgnum_act(void) { 71 | for (int i = 0; i < DMGNUM_MAX; ++i) { 72 | dmgnum_t *dnum = &dmgnum_list[i]; 73 | if (dnum->cond) { 74 | if (++dnum->count < 32) 75 | dnum->yofs -= 0x100; 76 | if (dnum->count > 80) 77 | dnum->cond = FALSE; 78 | else if (dnum->count > 72) 79 | ++dnum->vofs; 80 | } 81 | } 82 | } 83 | 84 | void dmgnum_draw(int cam_x, int cam_y) { 85 | cam_x = TO_INT(cam_x); 86 | cam_y = TO_INT(cam_y); 87 | for (int i = 0; i < DMGNUM_MAX; ++i) { 88 | dmgnum_t *dnum = &dmgnum_list[i]; 89 | if (dnum->cond) { 90 | int x = TO_INT(*dnum->tgt_x + dnum->xofs) - cam_x; 91 | const int y = TO_INT(*dnum->tgt_y + dnum->yofs) - cam_y - 4; 92 | for (int d = 0; d < dnum->digits; ++d, x += 8) 93 | gfx_draw_texrect_ofs(dnum->texrects[d], GFX_LAYER_FRONT, x, y, 0, dnum->vofs); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/game/dmgnum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/graphics.h" 5 | 6 | // also known as "value views" 7 | 8 | #define DMGNUM_MAX 0x10 9 | 10 | typedef struct { 11 | gfx_texrect_t *texrects[5]; // sign + digits 12 | bool cond; 13 | s32 *tgt_x; 14 | s32 *tgt_y; 15 | s32 xofs; 16 | s32 yofs; 17 | s32 vofs; 18 | s32 val; 19 | s32 count; 20 | s32 digits; 21 | } dmgnum_t; 22 | 23 | extern dmgnum_t dmgnum_list[DMGNUM_MAX]; 24 | 25 | void dmgnum_init(void); 26 | void dmgnum_spawn(int *tgt_x, int *tgt_y, int val); 27 | void dmgnum_act(void); 28 | void dmgnum_draw(int cam_x, int cam_y); 29 | -------------------------------------------------------------------------------- /src/game/game.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "engine/common.h" 4 | #include "engine/input.h" 5 | #include "engine/org.h" 6 | 7 | #include "game/npc.h" 8 | #include "game/player.h" 9 | #include "game/bullet.h" 10 | #include "game/caret.h" 11 | #include "game/hit.h" 12 | #include "game/camera.h" 13 | #include "game/stage.h" 14 | #include "game/tsc.h" 15 | #include "game/dmgnum.h" 16 | #include "game/hud.h" 17 | #include "game/menu.h" 18 | #include "game/credits.h" 19 | #include "game/game.h" 20 | 21 | u32 game_flags = GFLAG_INPUT_ENABLED; 22 | u32 game_tick = 0; 23 | u32 game_stopwatch = 0; 24 | 25 | u8 skip_flags[GAME_MAX_SKIPFLAGS]; 26 | u8 map_flags[GAME_MAX_MAPFLAGS]; 27 | 28 | // teleport destinations 29 | tele_dest_t tele_dest[GAME_MAX_TELEDEST]; 30 | int tele_dest_num; 31 | 32 | void game_init(void) { 33 | stage_init(); 34 | tsc_init(); 35 | npc_init(NPC_MAIN_TABLE); 36 | plr_init(); 37 | bullet_init(); 38 | caret_init(); 39 | hud_init(); 40 | cam_init(); 41 | menu_init(); 42 | 43 | // hide screen for now 44 | cam_complete_fade(); 45 | cam_target_player(16); 46 | 47 | game_tick = 0; 48 | } 49 | 50 | void game_start_intro(void) { 51 | // load title map 52 | stage_transition(STAGE_OPENING_ID, 100, 3, 3); 53 | // set player to be invisible and intangible 54 | player.cond |= PLRCOND_INVISIBLE; 55 | game_flags = GFLAG_INPUT_ENABLED | GFLAG_UPDATE_OBJECTS; 56 | // stage_transition(13, 200, 10, 8); 57 | // stage_transition(62, 90, 80, 9); // balcony 58 | // stage_transition(56, 90, 80, 9); 59 | // stage_transition(2, 90, 5, 6); 60 | // stage_transition(28, 94, 6, 13); // balfrog 61 | // stage_transition(10, 99, 36, 33); // sand 62 | // stage_transition(6, 98, 4, 18); // weed 63 | // stage_transition(48, 93, 155, 1); // river 64 | // stage_transition(47, 92, 4, 17); // core 65 | // stage_transition(53, 92, 4, 165); // oside 66 | // stage_transition(91, 100, 4, 4); // island 67 | // stage_transition(92, 500, 8, 52); // ballo2 68 | // stage_transition(79, 94, 10, 8); // prefa2 69 | // stage_transition(0, 100, 1, 15); // null 70 | } 71 | 72 | void game_start_new(void) { 73 | // load starting point 74 | stage_transition(13, 200, 10, 8); 75 | } 76 | 77 | void game_reset(const bool reset_skipflags) { 78 | stage_reset(); 79 | tsc_reset(); 80 | npc_reset(); 81 | plr_reset(); 82 | dmgnum_init(); 83 | cam_reset(); 84 | hud_clear(); 85 | caret_init(); 86 | bullet_init(); 87 | org_free(); 88 | 89 | // only clear skip flags if this is a "true" reset 90 | if (reset_skipflags) 91 | memset(skip_flags, 0, sizeof(skip_flags)); 92 | 93 | memset(map_flags, 0, sizeof(map_flags)); 94 | memset(tele_dest, 0, sizeof(tele_dest)); 95 | tele_dest_num = 0; 96 | 97 | // hide screen for now 98 | cam_complete_fade(); 99 | cam_target_player(16); 100 | 101 | game_flags = GFLAG_UPDATE_OBJECTS | GFLAG_INPUT_ENABLED; 102 | game_tick = 0; 103 | game_stopwatch = 0; 104 | } 105 | 106 | static inline void game_draw_common(void) { 107 | // stage draws into back and front 108 | stage_draw(camera.x, camera.y); 109 | // these are all on the back layer 110 | npc_draw(camera.x, camera.y); 111 | bullet_draw(camera.x, camera.y); 112 | plr_draw(camera.x, camera.y); 113 | // these are on the front layer 114 | cam_draw_flash(); 115 | caret_draw(camera.x, camera.y); 116 | dmgnum_draw(camera.x, camera.y); 117 | } 118 | 119 | static inline void game_update_objects(const bool input_enabled) { 120 | plr_act(input_enabled); 121 | npc_act(); 122 | stage_update(); 123 | 124 | // call `hit` on all entities 125 | hit_player(); 126 | hit_npc_map(); 127 | hit_boss_map(); 128 | hit_bullet_map(); 129 | hit_npc_bullet(); 130 | hit_boss_bullet(); 131 | 132 | if (input_enabled) 133 | plr_arm_shoot(); 134 | 135 | // bullets and particles update after other shit 136 | bullet_act(); 137 | caret_act(); 138 | dmgnum_act(); 139 | } 140 | 141 | void game_frame(void) { 142 | const bool input_enabled = (game_flags & GFLAG_INPUT_ENABLED); 143 | 144 | // if there's a menu open, update that and bail 145 | const int cur_menu = menu_active(); 146 | if (cur_menu) { 147 | menu_act(); 148 | game_draw_common(); 149 | cam_draw_fade(); 150 | menu_draw(); 151 | if (menu_uses_tsc()) { 152 | tsc_update(); 153 | tsc_draw(); 154 | } 155 | return; 156 | } 157 | 158 | // HACK: allow to skip title sequence if we're in the title map 159 | if (stage_data && stage_data->id == STAGE_OPENING_ID) { 160 | if (input_trig & (IN_OK | IN_CANCEL | IN_PAUSE)) { 161 | menu_open(MENU_TITLE); 162 | return; 163 | } 164 | } 165 | 166 | if (game_flags & GFLAG_UPDATE_OBJECTS) { 167 | // call `act` on all entities when game isn't frozen 168 | game_update_objects(input_enabled); 169 | // and move camera and update flash/quake effects 170 | cam_update(); 171 | } 172 | 173 | if (game_flags & GFLAG_SHOW_CREDITS) 174 | credits_update(); 175 | 176 | // fade always updates 177 | cam_update_fade(); 178 | 179 | // animate the player 180 | plr_animate(input_enabled); 181 | 182 | // call `draw` on all entities 183 | game_draw_common(); 184 | 185 | // the only reason this exists is that the boss life counter 186 | // has to be checked *before* tsc_update() 187 | hud_update(); 188 | 189 | if (!(game_flags & GFLAG_TSC_RUNNING)) { 190 | if (input_trig & IN_INVENTORY) { 191 | menu_open(MENU_INVENTORY); 192 | return; 193 | } else if ((input_trig & IN_MAP) && (player.equip & EQUIP_MAP)) { 194 | menu_open(MENU_MAP); 195 | return; 196 | } else if (input_trig & IN_PAUSE) { 197 | menu_open(MENU_PAUSE); 198 | return; 199 | } 200 | } 201 | 202 | if (input_enabled) { 203 | if (input_trig & IN_SWAP_L) 204 | plr_arm_swap_to_prev(); 205 | else if (input_trig & IN_SWAP_R) 206 | plr_arm_swap_to_next(); 207 | } 208 | 209 | tsc_update(); 210 | 211 | hud_draw(); 212 | cam_draw_fade(); 213 | hud_draw_map_name(); // this has to be above the fade 214 | tsc_draw(); 215 | 216 | if (game_flags & GFLAG_SHOW_CREDITS) 217 | credits_draw(); 218 | 219 | ++game_tick; 220 | } 221 | 222 | tele_dest_t *game_find_tele_dest(const u16 stage_id) { 223 | for (int i = 0; i < tele_dest_num; ++i) { 224 | if (stage_id == tele_dest[i].stage_num) 225 | return &tele_dest[i]; 226 | } 227 | return NULL; 228 | } 229 | 230 | tele_dest_t *game_add_tele_dest(const u16 stage_id, const u16 event_num) { 231 | tele_dest_t *dest = game_find_tele_dest(stage_id); 232 | if (!dest) dest = &tele_dest[tele_dest_num++]; 233 | dest->stage_num = stage_id; 234 | dest->event_num = event_num; 235 | return dest; 236 | } 237 | -------------------------------------------------------------------------------- /src/game/game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | // common game stuff 6 | 7 | #define GAME_MAX_SKIPFLAGS 8 8 | #define GAME_MAX_MAPFLAGS 0x10 9 | #define GAME_MAX_TELEDEST 8 10 | 11 | enum dir { 12 | DIR_LEFT = 0, 13 | DIR_UP = 1, 14 | DIR_RIGHT = 2, 15 | DIR_DOWN = 3, 16 | DIR_AUTO = 4, 17 | DIR_OTHER = 5 18 | }; 19 | 20 | enum game_flags { 21 | GFLAG_UPDATE_OBJECTS = 1, 22 | GFLAG_INPUT_ENABLED = 2, 23 | GFLAG_TSC_RUNNING = 4, 24 | GFLAG_SHOW_CREDITS = 8, 25 | }; 26 | 27 | typedef struct { 28 | u16 stage_num; 29 | u16 event_num; 30 | } tele_dest_t; 31 | 32 | extern u32 game_tick; 33 | extern u32 game_stopwatch; 34 | extern u32 game_flags; 35 | 36 | extern u8 skip_flags[GAME_MAX_SKIPFLAGS]; 37 | extern u8 map_flags[GAME_MAX_MAPFLAGS]; 38 | 39 | extern tele_dest_t tele_dest[GAME_MAX_TELEDEST]; 40 | extern int tele_dest_num; 41 | 42 | void game_init(void); 43 | void game_reset(const bool reset_skipflags); 44 | void game_start_intro(void); 45 | void game_start_new(void); 46 | void game_frame(void); 47 | 48 | tele_dest_t *game_add_tele_dest(const u16 stage_id, const u16 event_num); 49 | tele_dest_t *game_find_tele_dest(const u16 stage_id); 50 | 51 | static inline u32 game_get_skipflag(const u32 i) { 52 | return skip_flags[i >> 3] & (1 << (i & 7)); 53 | } 54 | 55 | static inline void game_set_skipflag(const u32 i) { 56 | skip_flags[i >> 3] |= (1 << (i & 7)); 57 | } 58 | 59 | static inline void game_clear_skipflag(const u32 i) { 60 | skip_flags[i >> 3] &= ~(1 << (i & 7)); 61 | } 62 | 63 | static inline u32 game_get_mapflag(const u32 i) { 64 | return map_flags[i >> 3] & (1 << (i & 7)); 65 | } 66 | 67 | static inline void game_set_mapflag(const u32 i) { 68 | map_flags[i >> 3] |= (1 << (i & 7)); 69 | } 70 | -------------------------------------------------------------------------------- /src/game/hit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | void hit_player(void); 6 | 7 | void hit_npc_map(void); 8 | void hit_npc_bullet(void); 9 | 10 | void hit_boss_map(void); 11 | void hit_boss_bullet(void); 12 | 13 | void hit_bullet_map(void); 14 | -------------------------------------------------------------------------------- /src/game/hud.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/graphics.h" 5 | 6 | #include "game/npc.h" 7 | 8 | extern gfx_texrect_t hud_rc_digit[]; 9 | extern gfx_texrect_t hud_rc_item[]; 10 | extern gfx_texrect_t hud_rc_arms[]; 11 | extern gfx_texrect_t hud_rc_ammo[]; 12 | 13 | void hud_init(void); 14 | void hud_update(void); 15 | void hud_draw(void); 16 | void hud_show_map_name(void); 17 | void hud_draw_map_name(void); 18 | void hud_init_boss_life(npc_t *npc); 19 | void hud_clear(void); 20 | void hud_draw_number(int val, int x, int y); 21 | void hud_draw_time(const int x, const int y); 22 | -------------------------------------------------------------------------------- /src/game/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | enum menu_type { 6 | MENU_NONE = 0, 7 | MENU_TITLE, 8 | MENU_PAUSE, 9 | MENU_INVENTORY, 10 | MENU_MAP, 11 | MENU_STAGESELECT, 12 | MENU_SAVE, 13 | MENU_LOAD, 14 | MENU_OPTIONS, 15 | // HACK: this behaves similar enough to a menu 16 | MENU_FALLING_ISLAND_0, 17 | MENU_FALLING_ISLAND_1, 18 | }; 19 | 20 | void menu_init(void); 21 | int menu_active(void); 22 | void menu_act(void); 23 | void menu_draw(void); 24 | void menu_open(const int type); 25 | void menu_close(void); 26 | 27 | bool menu_uses_tsc(void); 28 | -------------------------------------------------------------------------------- /src/game/npc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/graphics.h" 5 | 6 | #include "game/stage.h" 7 | #include "game/npctab.h" 8 | 9 | #define NPC_MAX 512 10 | #define NPC_MAX_BOSS 20 11 | #define NPC_MAX_FLAGS 1024 12 | #define NPC_STARTIDX_DYNAMIC 256 13 | #define NPC_STARTIDX_EVENT 170 14 | #define NPC_MAIN_TABLE "\\MAIN\\NPC.TBL;1" 15 | 16 | enum npc_cond_flags { 17 | NPCCOND_KILLED = 0x08, // Hit by a bullet and died 18 | NPCCOND_DAMAGE_BOSS = 0x10, // (boss npc exclusive) When set, damage the main boss 19 | NPCCOND_ALIVE = 0x80, // Whether the NPC is alive or not 20 | }; 21 | 22 | // Be careful when changing these: they're baked into the 'npc.tbl' file 23 | enum npc_bits { 24 | NPC_SOLID_SOFT = 1 << 0, // Pushes Quote out 25 | NPC_IGNORE_TILE_44 = 1 << 1, // Ignores tile 44, which normally blocks NPCs 26 | NPC_INVULNERABLE = 1 << 2, // Can't be hurt 27 | NPC_IGNORE_SOLIDITY = 1 << 3, // Doesn't collide with anything 28 | NPC_BOUNCY = 1 << 4, // Quote bounces on top of NPC 29 | NPC_SHOOTABLE = 1 << 5, // Can be shot 30 | NPC_SOLID_HARD = 1 << 6, // Essentially acts as level tiles 31 | NPC_REAR_AND_TOP_DONT_HURT = 1 << 7, // Rear and top don't hurt when touched 32 | NPC_EVENT_WHEN_TOUCHED = 1 << 8, // Run event when touched 33 | NPC_EVENT_WHEN_KILLED = 1 << 9, // Run event when killed 34 | NPC_APPEAR_WHEN_FLAG_SET = 1 << 11, // Only appear when flag is set 35 | NPC_SPAWN_IN_OTHER_DIRECTION = 1 << 12, // Spawn facing to the right (or however the NPC interprets the direction) 36 | NPC_INTERACTABLE = 1 << 13, // Run event when interacted with 37 | NPC_HIDE_WHEN_FLAG_SET = 1 << 14, // Hide when flag is set 38 | NPC_SHOW_DAMAGE = 1 << 15, // Show the number of damage taken when harmed 39 | }; 40 | 41 | enum npc_classnames { 42 | NPC_NULL = 0, 43 | NPC_EXP = 1, 44 | NPC_ENEMY_BEHEMOTH = 2, 45 | NPC_DAMAGE_TEXT_HOLDER = 3, 46 | NPC_SMOKE = 4, 47 | NPC_ENEMY_FROG = 104, 48 | NPC_SPEECH_BALLOON_HEY_LOW = 105, 49 | NPC_SPEECH_BALLOON_HEY_HIGH = 106, 50 | NPC_MALCO_UNDAMAGED = 107, 51 | NPC_PROJECTILE_BALFROG_SPITBALL = 108, 52 | NPC_MALCO_DAMAGED = 109, 53 | NPC_ENEMY_PUCHI = 110, 54 | NPC_KINGS_SWORD = 145 55 | }; 56 | 57 | typedef struct npc { 58 | gfx_texrect_t texrect; 59 | rect_t rect_delta; 60 | hitbox_t hit; 61 | hitbox_t view; 62 | 63 | const rect_t *rect; 64 | const rect_t *rect_prev; 65 | const npc_class_t *info; 66 | struct npc *parent; 67 | 68 | u32 flags; 69 | s32 x; 70 | s32 y; 71 | s32 xvel; 72 | s32 yvel; 73 | s32 xvel2; 74 | s32 yvel2; 75 | s32 tgt_x; 76 | s32 tgt_y; 77 | 78 | u16 class_num; 79 | u16 bits; 80 | s16 life; 81 | s16 damage; 82 | s16 damage_view; 83 | s16 event_flag; 84 | s16 event_num; 85 | s16 anim; 86 | s16 anim_wait; 87 | s16 act; 88 | s16 act_wait; 89 | s16 count1; 90 | s16 count2; 91 | s16 size; 92 | s16 dir; 93 | 94 | u8 cond; 95 | u8 shock; 96 | u8 snd_die; 97 | u8 snd_hit; 98 | u8 exp; 99 | u8 surf; 100 | } npc_t; 101 | 102 | typedef struct { 103 | s32 shoot_wait; 104 | s32 shoot_x; 105 | s32 shoot_y; 106 | } npc_curly_t; 107 | 108 | typedef struct { 109 | s32 crystal_x; 110 | s32 crystal_y; 111 | } npc_doctor_t; 112 | 113 | extern u8 npc_flags[NPC_MAX_FLAGS]; 114 | 115 | extern npc_t npc_list[NPC_MAX]; 116 | extern int npc_list_max; 117 | 118 | extern npc_t npc_boss[NPC_MAX_BOSS]; 119 | extern int npc_boss_max; 120 | 121 | extern npc_curly_t npc_curly_state; 122 | extern npc_doctor_t npc_doctor_state; 123 | 124 | void npc_init(const char *tabpath); 125 | void npc_reset(void); 126 | void npc_parse_event_list(const stage_event_t *ev, const int numev); 127 | npc_t *npc_spawn(int class_num, int x, int y, int xv, int yv, int dir, npc_t *parent, int startidx); 128 | void npc_delete(npc_t *npc); 129 | void npc_delete_by_class(const int class_num, const int spawn_smoke); 130 | void npc_delete_by_event_num(const int event_num); 131 | npc_t *npc_find_by_class(const int class_num); 132 | npc_t *npc_find_by_event_num(const int event_num); 133 | void npc_change_action(npc_t *npc, const int act, const int dir); 134 | void npc_change_class(npc_t *npc, const int class_num, const int dir, const u16 addbits); 135 | void npc_change_class_by_event_num(const int event_num, const int class_num, const int dir, const u16 addbits); 136 | void npc_set_pos(npc_t *npc, const int x, const int y, const int dir); 137 | void npc_show_death_damage(npc_t *npc); 138 | void npc_kill(npc_t *npc, bool show_damage); 139 | void npc_spawn_death_fx(int x, int y, int w, int num, int up); 140 | void npc_spawn_exp(int x, int y, int exp); 141 | npc_t *npc_spawn_life(int x, int y, int val); 142 | npc_t *npc_spawn_ammo(int x, int y, int val); 143 | npc_t *npc_spawn_boss(const int boss_id); 144 | void npc_set_boss_act(const int act); 145 | 146 | void npc_draw(int cam_x, int cam_y); 147 | void npc_act(void); 148 | 149 | static inline u32 npc_get_flag(const u32 i) { 150 | return npc_flags[i >> 3] & (1 << (i & 7)); 151 | } 152 | 153 | static inline void npc_set_flag(const u32 i) { 154 | npc_flags[i >> 3] |= (1 << (i & 7)); 155 | } 156 | 157 | static inline void npc_clear_flag(const u32 i) { 158 | npc_flags[i >> 3] &= ~(1 << (i & 7)); 159 | } 160 | 161 | -------------------------------------------------------------------------------- /src/game/npctab.c: -------------------------------------------------------------------------------- 1 | #include "engine/common.h" 2 | #include "engine/filesystem.h" 3 | #include "engine/memory.h" 4 | 5 | #include "game/npc.h" 6 | #include "game/npc_act/npc_act.h" 7 | #include "game/boss_act/boss_act.h" 8 | 9 | npc_class_t *npc_classtab; 10 | 11 | void npc_load_classtab(const char *tabpath) { 12 | fs_file_t *f = fs_fopen(tabpath, 0); 13 | if (!f) PANIC("could not open\n%s", tabpath); 14 | 15 | const int size = fs_fsize(f); 16 | const int count = size / sizeof(npc_class_t); 17 | ASSERT(count >= 1); 18 | 19 | npc_classtab = mem_alloc(size); 20 | 21 | // data in the table is stored column by column because fuck you 22 | // read all columns at once and hope there's enough memory 23 | u16 *p_bits = mem_alloc(size); 24 | fs_fread_or_die(p_bits, size, 1, f); 25 | fs_fclose(f); 26 | 27 | // reconfigure 28 | u16 *p_life = &p_bits[count]; 29 | u8 *p_surf_id = (u8 *)&p_life[count]; 30 | u8 *p_snd_die = (u8 *)&p_surf_id[count]; 31 | u8 *p_snd_hit = (u8 *)&p_snd_die[count]; 32 | u8 *p_size = (u8 *)&p_snd_hit[count]; 33 | s32 *p_exp = (s32 *)&p_size[count]; 34 | s32 *p_damage = (s32 *)&p_exp[count]; 35 | npc_tab_hitbox_t *p_hit = (npc_tab_hitbox_t *)&p_damage[count]; 36 | npc_tab_hitbox_t *p_view = (npc_tab_hitbox_t *)&p_hit[count]; 37 | for (int i = 0; i < count; ++i) { 38 | npc_class_t *c = &npc_classtab[i]; 39 | c->bits = p_bits[i]; 40 | c->life = p_life[i]; 41 | c->surf_id = p_surf_id[i]; 42 | c->snd_die = p_snd_die[i]; 43 | c->snd_hit = p_snd_hit[i]; 44 | c->size = p_size[i]; 45 | c->exp = p_exp[i]; 46 | c->damage = p_damage[i]; 47 | c->hit = p_hit[i]; 48 | c->view = p_view[i]; 49 | } 50 | 51 | mem_free(p_bits); 52 | } 53 | 54 | npc_func_t npc_functab[NPC_MAX_ACTFUNC] = { 55 | npc_act_000, 56 | npc_act_001, 57 | npc_act_002, 58 | npc_act_003, 59 | npc_act_004, 60 | npc_act_005, 61 | npc_act_006, 62 | npc_act_007, 63 | npc_act_008, 64 | npc_act_009, 65 | npc_act_010, 66 | npc_act_011, 67 | npc_act_012, 68 | npc_act_013, 69 | npc_act_014, 70 | npc_act_015, 71 | npc_act_016, 72 | npc_act_017, 73 | npc_act_018, 74 | npc_act_019, 75 | npc_act_020, 76 | npc_act_021, 77 | npc_act_022, 78 | npc_act_023, 79 | npc_act_024, 80 | npc_act_025, 81 | npc_act_026, 82 | npc_act_027, 83 | npc_act_028, 84 | npc_act_029, 85 | npc_act_030, 86 | npc_act_031, 87 | npc_act_032, 88 | npc_act_033, 89 | npc_act_034, 90 | npc_act_035, 91 | npc_act_036, 92 | npc_act_037, 93 | npc_act_038, 94 | npc_act_039, 95 | npc_act_040, 96 | npc_act_041, 97 | npc_act_042, 98 | npc_act_043, 99 | npc_act_044, 100 | npc_act_045, 101 | npc_act_046, 102 | npc_act_047, 103 | npc_act_048, 104 | npc_act_049, 105 | npc_act_050, 106 | npc_act_051, 107 | npc_act_052, 108 | npc_act_053, 109 | npc_act_054, 110 | npc_act_055, 111 | npc_act_056, 112 | npc_act_057, 113 | npc_act_058, 114 | npc_act_059, 115 | npc_act_060, 116 | npc_act_061, 117 | npc_act_062, 118 | npc_act_063, 119 | npc_act_064, 120 | npc_act_065, 121 | npc_act_066, 122 | npc_act_067, 123 | npc_act_068, 124 | npc_act_069, 125 | npc_act_070, 126 | npc_act_071, 127 | npc_act_072, 128 | npc_act_073, 129 | npc_act_074, 130 | npc_act_075, 131 | npc_act_076, 132 | npc_act_077, 133 | npc_act_078, 134 | npc_act_079, 135 | npc_act_080, 136 | npc_act_081, 137 | npc_act_082, 138 | npc_act_083, 139 | npc_act_084, 140 | npc_act_085, 141 | npc_act_086, 142 | npc_act_087, 143 | npc_act_088, 144 | npc_act_089, 145 | npc_act_090, 146 | npc_act_091, 147 | npc_act_092, 148 | npc_act_093, 149 | npc_act_094, 150 | npc_act_095, 151 | npc_act_096, 152 | npc_act_097, 153 | npc_act_098, 154 | npc_act_099, 155 | npc_act_100, 156 | npc_act_101, 157 | npc_act_102, 158 | npc_act_103, 159 | npc_act_104, 160 | npc_act_105, 161 | npc_act_106, 162 | npc_act_107, 163 | npc_act_108, 164 | npc_act_109, 165 | npc_act_110, 166 | npc_act_111, 167 | npc_act_112, 168 | npc_act_113, 169 | npc_act_114, 170 | npc_act_115, 171 | npc_act_116, 172 | npc_act_117, 173 | npc_act_118, 174 | npc_act_119, 175 | npc_act_120, 176 | npc_act_121, 177 | npc_act_122, 178 | npc_act_123, 179 | npc_act_124, 180 | npc_act_125, 181 | npc_act_126, 182 | npc_act_127, 183 | npc_act_128, 184 | npc_act_129, 185 | npc_act_130, 186 | npc_act_131, 187 | npc_act_132, 188 | npc_act_133, 189 | npc_act_134, 190 | npc_act_135, 191 | npc_act_136, 192 | npc_act_137, 193 | npc_act_138, 194 | npc_act_139, 195 | npc_act_140, 196 | npc_act_141, 197 | npc_act_142, 198 | npc_act_143, 199 | npc_act_144, 200 | npc_act_145, 201 | npc_act_146, 202 | npc_act_147, 203 | npc_act_148, 204 | npc_act_149, 205 | npc_act_150, 206 | npc_act_151, 207 | npc_act_152, 208 | npc_act_153, 209 | npc_act_154, 210 | npc_act_155, 211 | npc_act_156, 212 | npc_act_157, 213 | npc_act_158, 214 | npc_act_159, 215 | npc_act_160, 216 | npc_act_161, 217 | npc_act_162, 218 | npc_act_163, 219 | npc_act_164, 220 | npc_act_165, 221 | npc_act_166, 222 | npc_act_167, 223 | npc_act_168, 224 | npc_act_169, 225 | npc_act_170, 226 | npc_act_171, 227 | npc_act_172, 228 | npc_act_173, 229 | npc_act_174, 230 | npc_act_175, 231 | npc_act_176, 232 | npc_act_177, 233 | npc_act_178, 234 | npc_act_179, 235 | npc_act_180, 236 | npc_act_181, 237 | npc_act_182, 238 | npc_act_183, 239 | npc_act_184, 240 | npc_act_185, 241 | npc_act_186, 242 | npc_act_187, 243 | npc_act_188, 244 | npc_act_189, 245 | npc_act_190, 246 | npc_act_191, 247 | npc_act_192, 248 | npc_act_193, 249 | npc_act_194, 250 | npc_act_195, 251 | npc_act_196, 252 | npc_act_197, 253 | npc_act_198, 254 | npc_act_199, 255 | npc_act_200, 256 | npc_act_201, 257 | npc_act_202, 258 | npc_act_203, 259 | npc_act_204, 260 | npc_act_205, 261 | npc_act_206, 262 | npc_act_207, 263 | npc_act_208, 264 | npc_act_209, 265 | npc_act_210, 266 | npc_act_211, 267 | npc_act_212, 268 | npc_act_213, 269 | npc_act_214, 270 | npc_act_215, 271 | npc_act_216, 272 | npc_act_217, 273 | npc_act_218, 274 | npc_act_219, 275 | npc_act_220, 276 | npc_act_221, 277 | npc_act_222, 278 | npc_act_223, 279 | npc_act_224, 280 | npc_act_225, 281 | npc_act_226, 282 | npc_act_227, 283 | npc_act_228, 284 | npc_act_229, 285 | npc_act_230, 286 | npc_act_231, 287 | npc_act_232, 288 | npc_act_233, 289 | npc_act_234, 290 | npc_act_235, 291 | npc_act_236, 292 | npc_act_237, 293 | npc_act_238, 294 | npc_act_239, 295 | npc_act_240, 296 | npc_act_241, 297 | npc_act_242, 298 | npc_act_243, 299 | npc_act_244, 300 | npc_act_245, 301 | npc_act_246, 302 | npc_act_247, 303 | npc_act_248, 304 | npc_act_249, 305 | npc_act_250, 306 | npc_act_251, 307 | npc_act_252, 308 | npc_act_253, 309 | npc_act_254, 310 | npc_act_255, 311 | npc_act_256, 312 | npc_act_257, 313 | npc_act_258, 314 | npc_act_259, 315 | npc_act_260, 316 | npc_act_261, 317 | npc_act_262, 318 | npc_act_263, 319 | npc_act_264, 320 | npc_act_265, 321 | npc_act_266, 322 | npc_act_267, 323 | npc_act_268, 324 | npc_act_269, 325 | npc_act_270, 326 | npc_act_271, 327 | npc_act_272, 328 | npc_act_273, 329 | npc_act_274, 330 | npc_act_275, 331 | npc_act_276, 332 | npc_act_277, 333 | npc_act_278, 334 | npc_act_279, 335 | npc_act_280, 336 | npc_act_281, 337 | npc_act_282, 338 | npc_act_283, 339 | npc_act_284, 340 | npc_act_285, 341 | npc_act_286, 342 | npc_act_287, 343 | npc_act_288, 344 | npc_act_289, 345 | npc_act_290, 346 | npc_act_291, 347 | npc_act_292, 348 | npc_act_293, 349 | npc_act_294, 350 | npc_act_295, 351 | npc_act_296, 352 | npc_act_297, 353 | npc_act_298, 354 | npc_act_299, 355 | npc_act_300, 356 | npc_act_301, 357 | npc_act_302, 358 | npc_act_303, 359 | npc_act_304, 360 | npc_act_305, 361 | npc_act_306, 362 | npc_act_307, 363 | npc_act_308, 364 | npc_act_309, 365 | npc_act_310, 366 | npc_act_311, 367 | npc_act_312, 368 | npc_act_313, 369 | npc_act_314, 370 | npc_act_315, 371 | npc_act_316, 372 | npc_act_317, 373 | npc_act_318, 374 | npc_act_319, 375 | npc_act_320, 376 | npc_act_321, 377 | npc_act_322, 378 | npc_act_323, 379 | npc_act_324, 380 | npc_act_325, 381 | npc_act_326, 382 | npc_act_327, 383 | npc_act_328, 384 | npc_act_329, 385 | npc_act_330, 386 | npc_act_331, 387 | npc_act_332, 388 | npc_act_333, 389 | npc_act_334, 390 | npc_act_335, 391 | npc_act_336, 392 | npc_act_337, 393 | npc_act_338, 394 | npc_act_339, 395 | npc_act_340, 396 | npc_act_341, 397 | npc_act_342, 398 | npc_act_343, 399 | npc_act_344, 400 | npc_act_345, 401 | npc_act_346, 402 | npc_act_347, 403 | npc_act_348, 404 | npc_act_349, 405 | npc_act_350, 406 | npc_act_351, 407 | npc_act_352, 408 | npc_act_353, 409 | npc_act_354, 410 | npc_act_355, 411 | npc_act_356, 412 | npc_act_357, 413 | npc_act_358, 414 | npc_act_359, 415 | npc_act_360, 416 | npc_act_null, 417 | }; 418 | 419 | npc_func_t npc_boss_functab[] = { 420 | npc_act_null, 421 | boss_act_omega, 422 | boss_act_balfrog, 423 | boss_act_monster_x, 424 | boss_act_core, 425 | boss_act_ironhead, 426 | boss_act_twins, 427 | boss_act_undead_core, 428 | boss_act_heavy_press, 429 | boss_act_ballos, 430 | }; 431 | -------------------------------------------------------------------------------- /src/game/npctab.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | #define NPC_MAX_ACTFUNC 384 6 | 7 | #pragma pack(push, 1) 8 | 9 | typedef struct { 10 | u8 front; 11 | u8 top; 12 | u8 back; 13 | u8 bottom; 14 | } npc_tab_hitbox_t; 15 | 16 | typedef struct { 17 | u16 bits; 18 | u16 life; 19 | u8 surf_id; 20 | u8 snd_hit; 21 | u8 snd_die; 22 | u8 size; 23 | s32 exp; 24 | s32 damage; 25 | npc_tab_hitbox_t hit; 26 | npc_tab_hitbox_t view; 27 | } npc_class_t; 28 | 29 | #pragma pack(pop) 30 | 31 | struct npc; 32 | typedef void (*npc_func_t)(struct npc *); 33 | 34 | extern npc_class_t *npc_classtab; 35 | extern npc_func_t npc_functab[]; 36 | extern npc_func_t npc_boss_functab[]; 37 | 38 | void npc_load_classtab(const char *tabpath); 39 | -------------------------------------------------------------------------------- /src/game/player.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/graphics.h" 5 | 6 | // also known as MyChar.h 7 | 8 | #define PLR_MAX_ARMS 14 9 | #define PLR_MAX_HELD_ARMS 8 10 | #define PLR_MAX_ITEMS 48 11 | #define PLR_MAX_HELD_ITEMS 32 12 | #define PLR_MAX_LIFE 232 13 | #define PLR_MAX_STAR 3 14 | 15 | enum equip_flags { 16 | EQUIP_BOOSTER_0_8 = 0x01, 17 | EQUIP_MAP = 0x02, 18 | EQUIP_ARMS_BARRIER = 0x04, 19 | EQUIP_TURBOCHARGE = 0x08, 20 | EQUIP_AIR_TANK = 0x10, 21 | EQUIP_BOOSTER_2_0 = 0x20, 22 | EQUIP_MIMIGA_MASK = 0x40, 23 | EQUIP_WHIMSICAL_STAR = 0x80, 24 | EQUIP_NIKUMARU_COUNTER = 0x100 25 | }; 26 | 27 | enum plr_cond_flags { 28 | PLRCOND_USE_BUTTON = 0x01, 29 | PLRCOND_INVISIBLE = 0x02, 30 | PLRCOND_WALKING = 0x04, 31 | PLRCOND_ONGROUND = 0x08, 32 | PLRCOND_IN_WIND = 0x20, 33 | PLRCOND_ALIVE = 0x80, 34 | }; 35 | 36 | typedef struct { 37 | u8 id; 38 | u8 level; 39 | s16 exp; 40 | s16 ammo; 41 | s16 max_ammo; 42 | } plr_arm_data_t; 43 | 44 | // fuckton of fields 45 | // sorted them in order of size/alignment and renamed some 46 | typedef struct { 47 | plr_arm_data_t arms[PLR_MAX_ARMS]; 48 | u8 items[PLR_MAX_ITEMS]; 49 | 50 | gfx_texrect_t rect; 51 | gfx_texrect_t rect_arms; 52 | 53 | hitbox_t hit; 54 | hitbox_t view; 55 | 56 | u32 flags; 57 | u32 equip; 58 | 59 | s32 x; 60 | s32 y; 61 | s32 xvel; 62 | s32 yvel; 63 | s32 tgt_x; 64 | s32 tgt_y; 65 | s32 index_x; 66 | s32 index_y; 67 | s32 anim; 68 | s32 anim_wait; 69 | s32 boost_cnt; 70 | bool up; 71 | bool down; 72 | 73 | s16 life; 74 | s16 max_life; 75 | s16 life_bar; 76 | s16 life_count; 77 | s16 exp_count; 78 | s16 exp_wait; 79 | s16 exp_flash; 80 | s16 air; 81 | s16 air_count; 82 | s16 star; 83 | s16 cooldown; 84 | u16 bubble; 85 | s16 arms_x; 86 | s16 num_arms; 87 | s16 num_items; 88 | 89 | u8 cond; 90 | s8 dir; 91 | u8 shock; 92 | s8 boost_sw; 93 | s8 splash; 94 | s8 question; 95 | u8 unit; 96 | u8 arm; 97 | } player_t; 98 | 99 | extern const s16 plr_arms_exptab[PLR_MAX_ARMS][3]; 100 | 101 | extern player_t player; 102 | 103 | void plr_init(void); 104 | void plr_reset(void); 105 | void plr_draw(int cam_x, int cam_y); 106 | void plr_set_pos(int x, int y); 107 | void plr_animate(const bool input_enabled); 108 | void plr_act(const bool input_enabled); 109 | void plr_damage(int val); 110 | void plr_jump_back(int from); 111 | void plr_face_towards(int what); 112 | void plr_add_life(int val); 113 | void plr_add_max_life(int val); 114 | void plr_add_ammo(int arm_id, int val); 115 | 116 | int plr_item_find(const u32 item); 117 | void plr_item_equip(const u32 item, const bool equip); 118 | void plr_item_give(const u32 item); 119 | void plr_item_take(const u32 item); 120 | 121 | plr_arm_data_t *plr_arm_find(const u8 id); 122 | plr_arm_data_t *plr_arm_find_missile_launcher(void); 123 | void plr_arm_reset_exp(void); 124 | void plr_arm_add_exp(int val); 125 | bool plr_arm_at_max_exp(void); 126 | void plr_arm_reset_spur_charge(void); 127 | bool plr_arm_charge_ammo(const int val); 128 | bool plr_arm_use_ammo(const int val); 129 | void plr_arm_swap_to_first(void); 130 | int plr_arm_swap_to_next(void); 131 | int plr_arm_swap_to_prev(void); 132 | void plr_arm_shoot(void); 133 | bool plr_arm_give(const int id, const int max_ammo); 134 | bool plr_arm_take(const int id); 135 | bool plr_arm_trade(const int id, const int new_id, const int new_max_ammo); 136 | 137 | void plr_arms_refill_all(void); 138 | void plr_arms_empty_all(void); 139 | 140 | void plr_star_reset(void); 141 | 142 | void plr_debug_cheat(void); 143 | -------------------------------------------------------------------------------- /src/game/profile.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "engine/common.h" 4 | #include "engine/timer.h" 5 | #include "engine/org.h" 6 | 7 | #include "game/game.h" 8 | #include "game/player.h" 9 | #include "game/npc.h" 10 | #include "game/camera.h" 11 | #include "game/menu.h" 12 | #include "game/profile.h" 13 | 14 | #define PROFILE_MAGIC "CSP\x02" 15 | 16 | profile_t profile; 17 | int profile_slot = -1; 18 | u32 profile_stopwatch = 0; 19 | 20 | void profile_reset(void) { 21 | memset(&profile, 0, sizeof(profile)); 22 | profile_slot = -1; 23 | profile_stopwatch = 0; 24 | } 25 | 26 | void profile_save(void) { 27 | memcpy(profile.magic, PROFILE_MAGIC, 4); 28 | 29 | // save config 30 | profile.config.vol_sfx = snd_sfx_volume; 31 | profile.config.vol_music = org_get_master_volume(); 32 | memcpy(profile.config.binds, input_binds, sizeof(profile.config.binds)); 33 | 34 | // save globals 35 | profile.save.game_tick = game_tick; 36 | profile.save.clock_tick = profile_stopwatch; 37 | memcpy(profile.save.npc_flags, npc_flags, sizeof(profile.save.npc_flags)); 38 | memcpy(profile.save.map_flags, map_flags, sizeof(profile.save.map_flags)); 39 | memcpy(profile.save.skip_flags, skip_flags, sizeof(profile.save.skip_flags)); 40 | memcpy(profile.save.tele_dest, tele_dest, sizeof(profile.save.tele_dest)); 41 | profile.save.tele_dest_num = tele_dest_num; 42 | profile.save.stage_id = stage_data->id; 43 | profile.save.stage_bank_id = stage_bank_id; 44 | profile.save.music_id = org_get_id(); 45 | 46 | if (profile_stopwatch) { 47 | // this is a post-game save with nikumaru timer on, save the record 48 | const int total_seconds = profile_stopwatch / 50; 49 | const int min = total_seconds / 60; 50 | const int sec = total_seconds % 60; 51 | const int sub = (profile_stopwatch / 5) % 10; 52 | sprintf(profile.save.stage_title, "= %d'%02d\"%d =", min, sec, sub); 53 | } else if (stage_data->id == STAGE_CREDITS_ID) { 54 | // this is a post-game save without nikumaru timer on, mark it as such 55 | strcpy(profile.save.stage_title, "= End ="); 56 | } else { 57 | memcpy(profile.save.stage_title, stage_data->title, sizeof(profile.save.stage_title)); 58 | } 59 | 60 | // save player 61 | memcpy(profile.save.player.arms, player.arms, sizeof(profile.save.player.arms)); 62 | memcpy(profile.save.player.items, player.items, sizeof(profile.save.player.items)); 63 | profile.save.player.x = player.x; 64 | profile.save.player.y = player.y; 65 | profile.save.player.dir = player.dir; 66 | profile.save.player.life = player.life; 67 | profile.save.player.max_life = player.max_life; 68 | profile.save.player.equip = player.equip; 69 | profile.save.player.star = player.star; 70 | profile.save.player.arm = player.arm; 71 | profile.save.player.num_arms = player.num_arms; 72 | profile.save.player.num_items = player.num_items; 73 | profile.save.player.unit = player.unit; 74 | 75 | printf("profile_save(): stage=%02x stage_bank=%02x\n", profile.save.stage_id, profile.save.stage_bank_id); 76 | } 77 | 78 | bool profile_load(const bool load_skipflags) { 79 | if (memcmp(profile.magic, PROFILE_MAGIC, 4)) 80 | return FALSE; 81 | 82 | // don't reset skip flags, they're either going to be overwritten or we need to keep them as is 83 | game_reset(FALSE); 84 | 85 | // make sure camera is 100% faded out while we're loading shit 86 | cam_complete_fade(); 87 | 88 | // load config 89 | memcpy(input_binds, profile.config.binds, sizeof(input_binds)); 90 | snd_set_sfx_volume(profile.config.vol_sfx); 91 | org_set_master_volume(profile.config.vol_music); 92 | 93 | // load globals 94 | game_tick = profile.save.game_tick; 95 | game_stopwatch = profile_stopwatch = profile.save.clock_tick; 96 | tele_dest_num = profile.save.tele_dest_num; 97 | memcpy(npc_flags, profile.save.npc_flags, sizeof(npc_flags)); 98 | memcpy(map_flags, profile.save.map_flags, sizeof(map_flags)); 99 | memcpy(tele_dest, profile.save.tele_dest, sizeof(tele_dest)); 100 | 101 | // only overwrite skip_flags if loading from main menu 102 | if (load_skipflags) 103 | memcpy(skip_flags, profile.save.skip_flags, sizeof(skip_flags)); 104 | 105 | // load stage bank 106 | gfx_draw_loading(); 107 | stage_load_stage_bank(profile.save.stage_bank_id); 108 | 109 | // after loading the stage bank we can get into the actual stage 110 | stage_transition(profile.save.stage_id, 0, 0, 1); 111 | stage_change_music(profile.save.music_id); 112 | 113 | // load player 114 | memcpy(player.arms, profile.save.player.arms, sizeof(player.arms)); 115 | memcpy(player.items, profile.save.player.items, sizeof(player.items)); 116 | player.x = profile.save.player.x; 117 | player.y = profile.save.player.y; 118 | player.dir = profile.save.player.dir; 119 | player.life = profile.save.player.life; 120 | player.max_life = profile.save.player.max_life; 121 | player.equip = profile.save.player.equip; 122 | player.star = profile.save.player.star; 123 | player.arm = profile.save.player.arm; 124 | player.num_arms = profile.save.player.num_arms; 125 | player.num_items = profile.save.player.num_items; 126 | player.unit = profile.save.player.unit; 127 | 128 | // recenter whimsical star projectiles 129 | plr_star_reset(); 130 | 131 | // recenter camera since player position changed 132 | cam_center_on_player(); 133 | 134 | // show camera 135 | cam_clear_fade(); 136 | 137 | return TRUE; 138 | } 139 | -------------------------------------------------------------------------------- /src/game/profile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | #include "engine/input.h" 5 | #include "engine/mcrd.h" 6 | 7 | #include "game/player.h" 8 | #include "game/game.h" 9 | #include "game/npc.h" 10 | #include "game/stage.h" 11 | 12 | #define PROFILE_FILENAME "BIDOUKUTSU" 13 | 14 | #pragma pack(push, 1) 15 | 16 | typedef struct { 17 | plr_arm_data_t arms[PLR_MAX_HELD_ARMS]; 18 | u8 items[PLR_MAX_HELD_ITEMS]; 19 | s32 x; 20 | s32 y; 21 | s16 dir; 22 | s16 life; 23 | s16 max_life; 24 | s16 star; 25 | u16 arm; 26 | u16 equip; 27 | u16 unit; 28 | u8 num_arms; 29 | u8 num_items; 30 | } profile_player_t; 31 | 32 | typedef struct { 33 | u16 binds[IN_NUM_ACTIONS]; 34 | s16 vol_sfx; 35 | s16 vol_music; 36 | u32 reserved[4]; 37 | } profile_config_t; 38 | 39 | typedef struct { 40 | profile_player_t player; 41 | char stage_title[MAX_STAGE_TITLE]; 42 | tele_dest_t tele_dest[GAME_MAX_TELEDEST]; 43 | u8 npc_flags[NPC_MAX_FLAGS]; 44 | u8 map_flags[GAME_MAX_MAPFLAGS]; 45 | u8 skip_flags[GAME_MAX_SKIPFLAGS]; 46 | u32 game_tick; 47 | u32 clock_tick; 48 | u16 stage_id; 49 | u16 stage_bank_id; 50 | u16 music_id; 51 | u16 tele_dest_num; 52 | u32 reserved[4]; 53 | } profile_save_t; 54 | 55 | #define PROFILE_DATA_SIZE (sizeof(profile_save_t) + sizeof(profile_config_t) + 4) 56 | 57 | typedef struct { 58 | char magic[4]; // 'CSP\x02' 59 | profile_save_t save; 60 | profile_config_t config; 61 | // padding to align with memcard sectors 62 | u8 pad[ALIGN(PROFILE_DATA_SIZE, MCRD_SECSIZE) - PROFILE_DATA_SIZE]; 63 | } profile_t; 64 | 65 | #pragma pack(pop) 66 | 67 | extern profile_t profile; 68 | extern int profile_slot; 69 | extern u32 profile_stopwatch; 70 | 71 | void profile_reset(void); 72 | void profile_save(void); 73 | bool profile_load(const bool load_skipflags); 74 | -------------------------------------------------------------------------------- /src/game/stage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | #define STAGE_PATH_FORMAT "0\\STAGE00.STG;1" 6 | #define STAGE_PATH_PREFIX "\\" 7 | #define STAGE_PATH_START 1 8 | 9 | #define STAGE_OPENING_ID 72 10 | #define STAGE_CREDITS_ID 0 11 | 12 | #define MAX_TILESET_SIZE 16 13 | #define MAX_STAGE_LINKS 4 14 | #define MAX_STAGE_TITLE 32 15 | 16 | #define MAX_STAGES 100 17 | 18 | #pragma pack(push, 1) 19 | 20 | typedef struct { 21 | s16 x; 22 | s16 y; 23 | s16 event_flag; 24 | s16 event_num; 25 | s16 class_num; 26 | u16 bits; 27 | } stage_event_t; 28 | 29 | typedef struct { 30 | u32 id; // map identifier (index in the maplist, starting with 0) 31 | s16 width; // width in tiles 32 | s16 height; // height in tiles 33 | s16 bk_type; // background type 34 | s16 boss_type; // boss type 35 | u16 ev_count; // number of "event" structs in pxe contents 36 | u16 ev_offset; // offset from map_data[] to pxe contents 37 | u16 tsc_offset; // offset from map_data[] to decoded tsc data 38 | u16 tsc_size; // size of the tsc data in bytes 39 | char title[MAX_STAGE_TITLE]; // title that displays on the automap and/or when you enter 40 | u8 atrb[MAX_TILESET_SIZE * MAX_TILESET_SIZE]; // pxa contents 41 | u8 map_data[]; // [width * height] pxm data 42 | // [StageEvent[ev_count] follows] 43 | // [decoded tsc data follows] 44 | } stage_t; 45 | 46 | typedef struct { 47 | u32 music_id; // in-game song id (index in musiclist.h) 48 | u32 bank_ofs; // offset to sfx_bank + ORG data from start of stage bank 49 | } stage_song_t; 50 | 51 | typedef struct { 52 | u16 numstages; // number of stages in bank (up to MAX_STAGE_LINKS) 53 | u16 numsongs; // number of sfx_bank + ORG combos (up to MAX_STAGE_LINKS) 54 | s16 bk_width; // background surface width 55 | s16 bk_height; // background surface height 56 | u32 surfofs; // offset to surface bank at the end 57 | u32 stageofs[MAX_STAGE_LINKS]; // offsets to each stage and its data (or 0 if unused) 58 | stage_song_t songs[]; // [numsongs] offsets to each sfx_bank + ORG combo 59 | // [Stage[numstages] follows] 60 | // [sfx_bank_t+orgdata[numsongs] follows] 61 | // [gfx_bank_t follows] 62 | } stage_bank_t; 63 | 64 | #pragma pack(pop) 65 | 66 | enum bktype { 67 | BACKGROUND_TYPE_STATIONARY = 0, // Doesn't move at all 68 | BACKGROUND_TYPE_MOVE_DISTANT = 1, // Moves at half the speed of the foreground 69 | BACKGROUND_TYPE_MOVE_NEAR = 2, // Moves at the same speed as the foreground 70 | BACKGROUND_TYPE_WATER = 3, // No background - draws a water foreground layer instead 71 | BACKGROUND_TYPE_BLACK = 4, // No background - just black 72 | BACKGROUND_TYPE_AUTOSCROLL = 5, // Constantly scrolls to the left (used by Ironhead) 73 | BACKGROUND_TYPE_CLOUDS_WINDY = 6, // Fancy parallax scrolling, items are blown to the left (used by bkMoon) 74 | BACKGROUND_TYPE_CLOUDS = 7 // Fancy parallax scrolling (used by bkFog) 75 | }; 76 | 77 | extern stage_bank_t *stage_bank; 78 | extern u32 stage_bank_id; 79 | 80 | extern stage_t *stage_data; 81 | extern int stage_water_y; 82 | 83 | void stage_init(void); 84 | void stage_reset(void); 85 | void stage_update(void); // actually just updates the bg 86 | 87 | int stage_load_stage_bank(const u32 id); 88 | void stage_free_stage_bank(void); 89 | 90 | void stage_change_music(const u32 id); 91 | void stage_resume_music(void); 92 | 93 | // changes current stage to `id`, executes `event` and puts player at `plr_x`, `plr_y` 94 | // if stage `id` is not in the current stage bank, will change bank to `id` 95 | int stage_transition(const u32 id, const u32 event, int plr_x, int plr_y); 96 | 97 | void stage_draw(int cam_x, int cam_y); 98 | 99 | static inline u8 stage_get_atrb(const int x, const int y) { 100 | if (x < 0 || y < 0 || x >= stage_data->width || y >= stage_data->height) 101 | return 0; 102 | const u8 t = stage_data->map_data[y * stage_data->width + x]; 103 | return stage_data->atrb[t]; 104 | } 105 | 106 | static inline void stage_shift_tile(const int x, const int y) { 107 | --stage_data->map_data[y * stage_data->width + x]; 108 | } 109 | 110 | static inline void stage_delete_tile(const int x, const int y) { 111 | stage_data->map_data[y * stage_data->width + x] = 0; 112 | } 113 | 114 | int stage_set_tile(const int x, const int y, const int t); 115 | -------------------------------------------------------------------------------- /src/game/tsc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine/common.h" 4 | 5 | #define TSC_MAX_SIZE 0x5000 6 | #define TSC_MAX_LINES 4 7 | #define TSC_MAX_NUMBERS 4 8 | #define TSC_MAX_LOADED_SCRIPTS 3 9 | #define TSC_LINE_LEN 35 10 | 11 | #define TEXT_LEFT (VID_WIDTH / 2 - 108) 12 | #define TEXT_BOX_LEFT (VID_WIDTH / 2 - 122) 13 | #define TEXT_TOP (VID_HEIGHT - 48) 14 | 15 | enum tsc_script_id { 16 | TSC_SCRIPT_ARMS_ITEM, 17 | TSC_SCRIPT_STAGE_SELECT, 18 | TSC_SCRIPT_STAGE, 19 | }; 20 | 21 | enum tsc_mode { 22 | TSC_MODE_OFF = 0, 23 | TSC_MODE_PARSE = 1, 24 | TSC_MODE_NOD = 2, 25 | TSC_MODE_NEWLINE = 3, 26 | TSC_MODE_WAIT = 4, 27 | TSC_MODE_FADE = 5, 28 | TSC_MODE_YESNO = 6, 29 | TSC_MODE_WAIT_PLAYER = 7, 30 | }; 31 | 32 | enum tsc_flags { 33 | TSCFLAG_IN_STRING = 2048, 34 | }; 35 | 36 | #pragma pack(push, 1) 37 | 38 | typedef struct { 39 | u16 id; // event number (the 4 digits after the '#') 40 | u16 ofs; // offset to event code from start of tsc_script_t 41 | } tsc_event_t; 42 | 43 | typedef struct { 44 | u32 num_ev; // number of events in script 45 | tsc_event_t ev_map[]; // [num_ev] 46 | // compiled tsc bytecode follows 47 | } tsc_script_t; 48 | 49 | #pragma pack(pop) 50 | 51 | typedef struct { 52 | tsc_script_t *script; // current script buffer 53 | u32 size; // script buffer size 54 | 55 | u8 *readptr; // read position in script 56 | 57 | s16 flags; 58 | s16 writepos; // x position in line 59 | s16 line; // current line 60 | s16 line_y[TSC_MAX_LINES]; 61 | s16 face; 62 | s16 face_x; 63 | s16 item; 64 | s16 item_y; 65 | s16 text_x; 66 | s16 text_y; 67 | s16 num[TSC_MAX_NUMBERS]; 68 | 69 | s16 wait; 70 | s16 wait_next; 71 | s16 next_event; 72 | 73 | s8 mode; // current mode (e.g. NOD, WAI) 74 | s8 yesno; // selection in yes/no prompt 75 | u8 blink; // cursor blink 76 | u8 last_opcode; // last executed opcode 77 | } tsc_state_t; 78 | 79 | extern tsc_state_t tsc_state; 80 | 81 | void tsc_init(void); 82 | void tsc_reset(void); 83 | bool tsc_update(void); 84 | void tsc_draw(void); 85 | void tsc_set_stage_script(tsc_script_t *data, const u32 size); 86 | void tsc_switch_script(const int idx); 87 | void tsc_start_event(const int num); 88 | void tsc_jump_event(const int num); 89 | void tsc_stop_event(void); 90 | void tsc_clear_text(void); -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "engine/common.h" 6 | #include "engine/timer.h" 7 | #include "engine/memory.h" 8 | #include "engine/sound.h" 9 | #include "engine/org.h" 10 | #include "engine/graphics.h" 11 | #include "engine/filesystem.h" 12 | #include "engine/input.h" 13 | #include "engine/mcrd.h" 14 | #include "engine/exception.h" 15 | #include "game/game.h" 16 | 17 | int main(int argc, char **argv) { 18 | gfx_init(); 19 | ex_install_handler(); 20 | cd_init(); 21 | mem_init(); // have to do this AFTER CdInit for some reason 22 | snd_init(SFX_MAIN_BANK); 23 | org_init(); 24 | in_init(); 25 | 26 | // load main graphics bank 27 | gfx_load_gfx_bank(GFX_MAIN_BANK); 28 | gfx_init_fonts(); 29 | 30 | // now we can init game-related stuff 31 | timer_init(); 32 | game_init(); 33 | 34 | // all allocations after game_init() are unloaded every stagebank change 35 | // high mark is changed during credits 36 | mem_set_mark(MEM_MARK_LO); 37 | mem_set_mark(MEM_MARK_HI); 38 | 39 | u32 now = timer_ticks; 40 | u32 next_frame = now; 41 | 42 | game_start_intro(); 43 | 44 | while (1) { 45 | now = timer_ticks; 46 | if (now >= next_frame) { 47 | in_update(); 48 | game_frame(); 49 | gfx_swap_buffers(); 50 | next_frame = now + 2; 51 | } 52 | } 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /system.cnf: -------------------------------------------------------------------------------- 1 | BOOT=cdrom:\doukutsu.exe;1 2 | TCB=4 3 | EVENT=10 4 | STACK=801FFFF0 5 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CXX ?= g++ 3 | CFLAGS := -std=gnu99 -g -Og -Wno-unused-result 4 | CXXFLAGS := -std=gnu++11 -g -Og -Wno-unused-result 5 | LDFLAGS := -static -lm 6 | LIBPSXAV_SRC := $(wildcard src/libpsxav/*.c) 7 | 8 | all: orgconv.exe sfxconv.exe surfpack.exe stagepack.exe creditspack.exe trigcalc.exe tscc.exe 9 | 10 | orgconv.exe: src/orgconv.c $(LIBPSXAV_SRC) 11 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 12 | 13 | sfxconv.exe: src/sfxconv.c $(LIBPSXAV_SRC) 14 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 15 | 16 | surfpack.exe: src/surfpack.c src/common/vram.c src/common/surface.c 17 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 18 | 19 | stagepack.exe: src/stagepack.c src/common/vram.c src/common/surface.c src/common/stage.c src/common/tsc.c 20 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 21 | 22 | creditspack.exe: src/creditspack.c src/common/vram.c src/common/surface.c 23 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 24 | 25 | trigcalc.exe: src/trigcalc.c 26 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 27 | 28 | tscc.exe: src/tscc.c src/common/tsc.c 29 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 30 | 31 | img2h.exe: src/img2h.c 32 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 33 | 34 | # this one will only compile under mingw 35 | fontgen.exe: src/fontgen.cpp 36 | $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lmingw32 -lgdi32 -lddraw 37 | 38 | clean: 39 | rm -f *.exe 40 | 41 | .PHONY: clean 42 | -------------------------------------------------------------------------------- /tools/data/Fade4bpp.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/tools/data/Fade4bpp.pbm -------------------------------------------------------------------------------- /tools/data/FontAscii.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/tools/data/FontAscii.pbm -------------------------------------------------------------------------------- /tools/data/SaveIcon.pbm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/doukutsupsx/513b5122532eee6fd43a42c0493604aeee96c527/tools/data/SaveIcon.pbm -------------------------------------------------------------------------------- /tools/make_banks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -eq 0 ]] ; then 4 | echo 'usage: ./make_banks.sh []' 5 | exit 0 6 | fi 7 | 8 | for fn in `ls "$1"`; do 9 | echo "$1/$fn" "$3/${fn%%.*}.sfx" 10 | ./orgconv.exe "$1/$fn" "$2" "$3/${fn%%.*}.sfx" $4 11 | done 12 | -------------------------------------------------------------------------------- /tools/src/common/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define BMP_MAX_PALETTE 256 12 | 13 | #define NUM_INST 100 14 | #define INST_LEN 256 15 | #define MAX_SFX 160 16 | 17 | #define SPURAM_ALIGN 8 18 | #define SPURAM_START 0x1000 19 | #define SPURAM_SIZE (512 * 1024) 20 | #define SPURAM_AVAIL (SPURAM_SIZE - SPURAM_START) 21 | 22 | #define ALIGN(x, align) (((x) + ((align) - 1)) & ~((align) - 1)) 23 | 24 | #ifdef _WIN32 25 | #define plat_mkdir(x, y) mkdir(x) 26 | #else 27 | #define plat_mkdir(x, y) mkdir(x, y) 28 | #endif 29 | 30 | #pragma pack(push, 1) 31 | 32 | struct bank_hdr { 33 | uint32_t data_size; // size of raw SPU data at the end 34 | uint32_t num_sfx; // number of samples in bank, including #0 (dummy) and all the unused samples 35 | uint32_t sfx_addr[1]; // address in SPU RAM of each sample, first one is always 0, others may be 0 (means it's unused) 36 | // after the last sfx_addr, raw SPU data follows 37 | }; 38 | 39 | struct sfx { 40 | int16_t *data; 41 | uint32_t len; // in samples 42 | uint32_t freq; 43 | uint32_t addr; 44 | }; 45 | 46 | struct bitmap { 47 | uint8_t *data; 48 | uint8_t palette[BMP_MAX_PALETTE * 4]; 49 | int width; 50 | int height; 51 | int numcolors; 52 | int stride; 53 | int leftpad; 54 | }; 55 | 56 | #pragma pack(pop) 57 | 58 | static inline char *skip_whitespace(char *p) { 59 | while (*p && isspace(*p)) ++p; 60 | return p; 61 | } 62 | 63 | static inline char *trim_whitespace(char *p) { 64 | while (*p && isspace(*p)) ++p; 65 | const unsigned len = strlen(p); 66 | for (unsigned i = len; i >= 0 && p[i] && isspace(p[i]); --i) 67 | p[i] = '\0'; 68 | return p; 69 | } 70 | 71 | static inline char *str_tolower(char *p) { 72 | for (; *p; ++p) 73 | *p = tolower((int)*p); 74 | } 75 | 76 | static inline uint8_t *read_file(const char *fname, uint32_t *out_size) { 77 | FILE *f = fopen(fname, "rb"); 78 | if (!f) return NULL; 79 | 80 | fseek(f, 0, SEEK_END); 81 | const uint32_t size = ftell(f); 82 | const uint32_t asize = ALIGN(size, 4); 83 | fseek(f, 0, SEEK_SET); 84 | 85 | uint8_t *buf = calloc(1, asize); 86 | assert(buf); 87 | 88 | fread(buf, size, 1, f); 89 | fclose(f); 90 | 91 | *out_size = asize; 92 | 93 | return buf; 94 | } 95 | 96 | static inline int find_id(const uint32_t what, const uint32_t *list, const int count) { 97 | for (int i = 0; i < count; ++i) { 98 | if (what == list[i]) 99 | return i; 100 | } 101 | return -1; 102 | } 103 | 104 | static inline int find_string(const char *what, const char **list, const int count) { 105 | for (int i = 0; i < count; ++i) { 106 | if (!strcasecmp(what, list[i])) 107 | return i; 108 | } 109 | return -1; 110 | } 111 | -------------------------------------------------------------------------------- /tools/src/common/musiclist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | static const char *music_names[] = { 4 | "", 5 | "WANPAKU", 6 | "ANZEN", 7 | "GAMEOVER", 8 | "GRAVITY", 9 | "WEED", 10 | "MDOWN2", 11 | "FIREEYE", 12 | "VIVI", 13 | "MURA", 14 | "FANFALE1", 15 | "GINSUKE", 16 | "CEMETERY", 17 | "PLANT", 18 | "KODOU", 19 | "FANFALE3", 20 | "FANFALE2", 21 | "DR", 22 | "ESCAPE", 23 | "JENKA", 24 | "MAZE", 25 | "ACCESS", 26 | "IRONH", 27 | "GRAND", 28 | "CURLY", 29 | "OSIDE", 30 | "REQUIEM", 31 | "WANPAK2", 32 | "QUIET", 33 | "LASTCAVE", 34 | "BALCONY", 35 | "LASTBTL", 36 | "LASTBT3", 37 | "ENDING", 38 | "ZONBIE", 39 | "BDOWN", 40 | "HELL", 41 | "JENKA2", 42 | "MARINE", 43 | "BALLOS", 44 | "TOROKO", 45 | "WHITE", 46 | }; 47 | 48 | #define MUSIC_COUNT ((int)(sizeof(music_names) / sizeof(*music_names))) 49 | -------------------------------------------------------------------------------- /tools/src/common/stage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "surface.h" 8 | 9 | #define MAX_STAGES 256 10 | #define MAX_STAGE_TITLE 32 11 | #define MAX_STAGE_LINKS 4 12 | #define MAX_STAGE_SONGS 8 13 | #define MAX_STAGE_SURFACES 8 14 | #define MAX_STAGE_SIZE 1024 15 | #define MAX_STAGELIST_LINKS 32 16 | #define MAX_RES_NAME 16 17 | #define MAX_TILESET_SIZE 16 18 | 19 | #define MIN_STAGE_WIDTH 21 20 | #define MIN_STAGE_HEIGHT 16 21 | 22 | #define START_STAGE_ID 72 23 | 24 | #pragma pack(push, 1) 25 | 26 | typedef struct { 27 | int16_t x; 28 | int16_t y; 29 | int16_t code_flag; 30 | int16_t code_event; 31 | int16_t code_char; 32 | uint16_t bits; 33 | } stage_event_t; 34 | 35 | typedef struct { 36 | uint32_t id; // map identifier (index in the maplist, starting with 0) 37 | int16_t width; // width in tiles 38 | int16_t height; // height in tiles 39 | int16_t bk_type; // background type 40 | int16_t boss_type; // boss type 41 | uint16_t ev_count; // number of "event" structs in pxe contents 42 | uint16_t ev_offset; // offset from map_data[] to pxe contents 43 | uint16_t tsc_offset; // offset from map_data[] to compiled tsc data 44 | uint16_t tsc_size; // size of the tsc data in bytes 45 | char title[MAX_STAGE_TITLE]; // title that displays on the automap and/or when you enter 46 | uint8_t atrb[MAX_TILESET_SIZE][MAX_TILESET_SIZE]; // pxa contents 47 | uint8_t map_data[]; // [width * height] pxm data 48 | // [pxe data follows] 49 | // [compiled tsc data follows] 50 | } stage_t; 51 | 52 | typedef struct { 53 | uint32_t music_id; // in-game song id (index in musiclist.h) 54 | uint32_t bank_ofs; // offset to sfx_bank + ORG data from start of stage bank 55 | } stage_song_t; 56 | 57 | typedef struct { 58 | uint16_t numstages; // number of stages in bank (up to MAX_STAGE_LINKS) 59 | uint16_t numsongs; // number of sfx_bank + ORG combos (up to MAX_STAGE_LINKS) 60 | int16_t bk_width; // background surface width 61 | int16_t bk_height; // background surface height 62 | uint32_t surfofs; // offset to surface bank at the end 63 | uint32_t stageofs[MAX_STAGE_LINKS]; // offsets to each stage_t and its data (or 0 if unused) 64 | stage_song_t songs[]; // [numsongs] offsets to each sfx_bank + ORG combo 65 | // followed by: 66 | // 67 | // stage_t stage 0 68 | // stage 0 data 69 | // ... 70 | // stage_t stage (numstages - 1) 71 | // stage (numstages-1) data 72 | // 73 | // sfx_bank bank 0 74 | // org_file org 0 75 | // ... 76 | // sfx_bank bank (numsongs - 1) 77 | // org_file org (numsongs - 1) 78 | // 79 | // vram_surfbank_t 80 | // VRAM bank data 81 | } stagebank_t; 82 | 83 | #pragma pack(pop) 84 | 85 | typedef struct { 86 | char name[MAX_RES_NAME]; 87 | char title[MAX_STAGE_TITLE]; 88 | char tilesheet[MAX_RES_NAME]; 89 | char npcsheet[MAX_RES_NAME]; 90 | char bosssheet[MAX_RES_NAME]; 91 | char bksheet[MAX_RES_NAME]; 92 | char titlesheet[MAX_RES_NAME]; 93 | uint32_t songs[MAX_STAGE_SONGS]; 94 | uint32_t links[MAX_STAGELIST_LINKS]; 95 | int bktype; 96 | int bossnum; 97 | int numsongs; 98 | int numsurfaces; 99 | int numlinks; 100 | bool packed; 101 | stage_t *stage; 102 | surf_list_t surfaces[MAX_STAGE_SURFACES]; 103 | } stage_list_t; 104 | 105 | int read_stagelist(stage_list_t *list, FILE *f); 106 | 107 | stage_t *stage_load(const uint32_t id, const char *prefix, const char *tileprefix, char **out_tscsrc); 108 | 109 | uint32_t stage_write_bank(const stage_list_t *root, const stage_list_t *stlist, const char *datapath, FILE *f); 110 | 111 | bool stage_load_tsc_head(const char *path); 112 | -------------------------------------------------------------------------------- /tools/src/common/surface.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "common.h" 7 | #include "vram.h" 8 | #include "surface.h" 9 | 10 | #define NUM_LIST_ARGS 10 11 | #define NUM_SURF_ARGS 3 12 | #define NUM_RECT_ARGS 4 13 | 14 | static inline bool parse_vram_surf_rect(char **args) { 15 | if (!isdigit(args[0][7])) { 16 | fprintf(stderr, "warning: SURFSET has to have a page numer at the end\n"); 17 | return false; 18 | } 19 | const int pg = args[0][7] - '0'; 20 | if (pg > VRAM_NUM_PAGES) { 21 | fprintf(stderr, "warning: SURFSET page number too high\n"); 22 | return false; 23 | } 24 | const vram_rect_t r = { atoi(args[1]), atoi(args[2]), atoi(args[3]), atoi(args[4]) }; 25 | vram_restrict_surf_rect(pg, &r); 26 | printf("surf page %d limited by rect: (%d, %d, %d, %d)\n", pg, r.x, r.y, r.w, r.h); 27 | } 28 | 29 | static inline bool parse_vram_clut_rect(char **args) { 30 | if (!isdigit(args[0][7])) { 31 | fprintf(stderr, "warning: CLUTSET has to have a page numer at the end\n"); 32 | return false; 33 | } 34 | const int pg = args[0][7] - '0'; 35 | if (pg > VRAM_NUM_CLUT_PAGES) { 36 | fprintf(stderr, "warning: CLUTSET page number too high\n"); 37 | return false; 38 | } 39 | const vram_rect_t r = { atoi(args[1]), atoi(args[2]), atoi(args[3]), atoi(args[4]) }; 40 | vram_restrict_clut_rect(pg, &r); 41 | printf("clut page %d limited by rect: (%d, %d, %d, %d)\n", pg, r.x, r.y, r.w, r.h); 42 | } 43 | 44 | static inline bool parse_surf_entry(char **args, surf_list_t *list) { 45 | const uint32_t id = atoi(args[0]); 46 | if (id > SURFACE_ID_MAX) { 47 | fprintf(stderr, "warning: invalid surface id %u\n", id); 48 | return false; 49 | } 50 | 51 | const char *path = trim_whitespace(args[1]); 52 | if (!path[0]) { 53 | fprintf(stderr, "warning: empty surface path for surface %u\n", id); 54 | return false; 55 | } 56 | 57 | const int pad = atoi(args[2]); 58 | if (pad < 0 || pad > 256) { 59 | fprintf(stderr, "warning: invalid pad value %d\n", pad); 60 | return false; 61 | } 62 | 63 | strncpy(list->fname, path, sizeof(list->fname) - 1); 64 | list->id = id; 65 | list->img.leftpad = pad; 66 | return true; 67 | } 68 | 69 | int read_surflist(surf_list_t *list, FILE *f) { 70 | char line[2048]; 71 | char *token[1 + NUM_LIST_ARGS] = { NULL }; 72 | char *p; 73 | int num = 0; 74 | 75 | while (true) { 76 | p = fgets(line, sizeof(line) - 1, f); 77 | if (!p) break; 78 | 79 | p = skip_whitespace(p); 80 | if (!*p || *p == '#') continue; 81 | 82 | int i = 0; 83 | token[0] = p = strtok(p, ",\r\n"); 84 | for (i = 1; i < NUM_LIST_ARGS && p; ++i) 85 | token[i] = p = strtok(NULL, ",\r\n"); 86 | if (i < NUM_RECT_ARGS || i > NUM_LIST_ARGS) { 87 | fprintf(stderr, "warning: ignoring malformed directive:\n%s\n", line); 88 | continue; 89 | } 90 | 91 | if (!strncasecmp(token[0], "SURF", 5) && i >= NUM_SURF_ARGS && num < MAX_SURFACES) 92 | num += (int)parse_surf_entry(&token[1], &list[num]); 93 | else if (!strncasecmp(token[0], "SURFSET", 7) && i >= NUM_RECT_ARGS) 94 | parse_vram_surf_rect(token); 95 | else if (!strncasecmp(token[0], "CLUTSET", 7) && i >= NUM_RECT_ARGS) 96 | parse_vram_clut_rect(token); 97 | } 98 | 99 | return num; 100 | } 101 | 102 | int convert_surface(const uint32_t id, const struct bitmap *img, const bool force_align) { 103 | const uint16_t mode = (img->numcolors > 16); 104 | 105 | const uint16_t clut = vram_fit_clut(img->palette, mode); 106 | if (!clut) { 107 | fprintf(stderr, "error: could not fit CLUT for #%u in VRAM!\n", id); 108 | return -1; 109 | } 110 | 111 | // tilesets and fonts need to be aligned to address them more easily 112 | // backgrounds are just sometimes fuckhuge 113 | const bool align_to_page = 114 | (id == SURFACE_ID_LEVEL_TILESET) || 115 | (id == SURFACE_ID_LEVEL_BACKGROUND) || 116 | (id == SURFACE_ID_TEXT_BOX) || 117 | (id == SURFACE_ID_FONT1) || 118 | (id == SURFACE_ID_FONT2) || 119 | (id == SURFACE_ID_FADE) || 120 | force_align; 121 | vram_surf_t *vsurf = vram_fit_surf(img, clut, align_to_page); 122 | if (!vsurf) { 123 | fprintf(stderr, "error: could not fit #%u in VRAM!\n", id); 124 | return -2; 125 | } 126 | 127 | vsurf->id = id; 128 | 129 | return 0; 130 | } 131 | 132 | int read_bmp(struct bitmap *bmp, FILE *f) { 133 | struct { 134 | char id[2]; // BM 135 | uint32_t fsize; 136 | uint16_t reserved[2]; 137 | uint32_t dataofs; 138 | } __attribute__((packed)) header; 139 | 140 | struct { 141 | uint32_t size; 142 | int32_t w; 143 | int32_t h; 144 | uint16_t planes; 145 | uint16_t bpp; 146 | uint32_t compression; 147 | uint32_t datasize; 148 | int32_t ppm_x; 149 | int32_t ppm_y; 150 | uint32_t numcolors; 151 | uint32_t usedcolors; 152 | } __attribute__((packed)) dib; 153 | 154 | fread(&header, sizeof(header), 1, f); 155 | if (header.id[0] != 'B' || header.id[1] != 'M') 156 | return -1; 157 | 158 | fread(&dib, sizeof(dib), 1, f); 159 | if (dib.size != sizeof(dib)) 160 | return -2; 161 | if (dib.bpp != 1 && dib.bpp != 4 && dib.bpp != 8) 162 | return -3; 163 | if ((dib.numcolors < 2 && dib.numcolors) || dib.numcolors > 256) 164 | return -4; 165 | if (dib.compression != 0) 166 | return -5; 167 | 168 | if (dib.bpp) 169 | bmp->numcolors = 1 << dib.bpp; 170 | else 171 | bmp->numcolors = dib.numcolors; 172 | 173 | if (dib.bpp < 4) 174 | dib.bpp = 4; // can't have 1-bit images 175 | 176 | bmp->width = dib.w; 177 | bmp->height = dib.h; 178 | bmp->stride = 4 * ((dib.bpp * dib.w + 31) / 32); 179 | 180 | fread(bmp->palette, bmp->numcolors * 4, 1, f); 181 | 182 | bmp->data = calloc(1, bmp->stride * bmp->height); 183 | if (!bmp->data) return -6; 184 | 185 | if (bmp->numcolors == 2) { 186 | bmp->numcolors = 16; 187 | // assume the image is all black (palette index 0) 188 | // this trips us up on Fade.pbm, but we can just use a copy with 3 colors 189 | // HACK: bkBlack is not actually black, so copy palette index 1 to index 0 190 | if (bmp->palette[4] || bmp->palette[5] || bmp->palette[6]) { 191 | bmp->palette[0] = bmp->palette[4]; 192 | bmp->palette[1] = bmp->palette[5]; 193 | bmp->palette[2] = bmp->palette[6]; 194 | } 195 | return 0; 196 | } 197 | 198 | fseek(f, header.dataofs, SEEK_SET); 199 | 200 | // read in reverse because BMPs are mirrored vertically 201 | uint8_t *dst = bmp->data + bmp->stride * (bmp->height - 1); 202 | uint8_t linebuf[bmp->stride]; 203 | for (int y = 0; y < dib.h; ++y) { 204 | fread(linebuf, bmp->stride, 1, f); 205 | memcpy(dst, linebuf, bmp->stride); 206 | dst -= bmp->stride; 207 | } 208 | 209 | return 0; 210 | } 211 | -------------------------------------------------------------------------------- /tools/src/common/surface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common.h" 6 | 7 | enum surface_id { 8 | SURFACE_ID_TITLE = 0, 9 | SURFACE_ID_PIXEL = 1, 10 | SURFACE_ID_LEVEL_TILESET = 2, 11 | SURFACE_ID_FADE = 6, 12 | SURFACE_ID_ITEM_IMAGE = 8, 13 | SURFACE_ID_MAP = 9, 14 | SURFACE_ID_SCREEN_GRAB = 10, 15 | SURFACE_ID_ARMS = 11, 16 | SURFACE_ID_ARMS_IMAGE = 12, 17 | SURFACE_ID_ROOM_NAME = 13, 18 | SURFACE_ID_STAGE_ITEM = 14, 19 | SURFACE_ID_LOADING = 15, 20 | SURFACE_ID_MY_CHAR = 16, 21 | SURFACE_ID_BULLET = 17, 22 | SURFACE_ID_CARET = 19, 23 | SURFACE_ID_NPC_SYM = 20, 24 | SURFACE_ID_LEVEL_SPRITESET_1 = 21, 25 | SURFACE_ID_LEVEL_SPRITESET_2 = 22, 26 | SURFACE_ID_NPC_REGU = 23, 27 | SURFACE_ID_TEXT_BOX = 26, 28 | SURFACE_ID_FACE = 27, 29 | SURFACE_ID_LEVEL_BACKGROUND = 28, 30 | SURFACE_ID_VALUE_VIEW = 29, 31 | SURFACE_ID_TEXT_LINE1 = 30, 32 | SURFACE_ID_TEXT_LINE2 = 31, 33 | SURFACE_ID_TEXT_LINE3 = 32, 34 | SURFACE_ID_TEXT_LINE4 = 33, 35 | SURFACE_ID_TEXT_LINE5 = 34, 36 | SURFACE_ID_CREDIT_CAST = 35, 37 | SURFACE_ID_CREDITS_IMAGE = 36, 38 | SURFACE_ID_CASTS = 37, 39 | SURFACE_ID_FONT1 = 38, 40 | SURFACE_ID_FONT2 = 39, 41 | SURFACE_ID_MAX = 40 42 | }; 43 | 44 | typedef struct surflist { 45 | char fname[2048]; 46 | uint8_t id; 47 | struct bitmap img; 48 | } surf_list_t; 49 | 50 | int read_surflist(surf_list_t *list, FILE *f); 51 | int read_bmp(struct bitmap *bmp, FILE *f); 52 | int convert_surface(const uint32_t id, const struct bitmap *img, const bool force_align); 53 | -------------------------------------------------------------------------------- /tools/src/common/tsc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define TSC_MAX_SIZE 0x5000 // original game limitation 6 | #define TSC_MAX_EVENTS 1024 // max events per script 7 | 8 | #pragma pack(push, 1) 9 | 10 | typedef struct { 11 | uint16_t id; // event number (the 4 digits after the '#') 12 | uint16_t ofs; // offset to event code from start of tsc_script_t 13 | } tsc_event_t; 14 | 15 | typedef struct { 16 | uint32_t num_ev; // number of events in script 17 | tsc_event_t ev_map[]; // [num_ev] 18 | // compiled tsc bytecode follows 19 | } tsc_script_t; 20 | 21 | #pragma pack(pop) 22 | 23 | // requires a NULL-terminated string 24 | tsc_script_t *tsc_compile(char *src, const int in_size, int *out_size); 25 | 26 | void tsc_decode(uint8_t *data, const int size); 27 | 28 | // these scan in the source text since it's easier this way 29 | 30 | // scan tsc for music changes 31 | int tsc_scan_music(const char *src, uint32_t *songlist, const int limit); 32 | 33 | // scan tsc for potential stage transitions 34 | int tsc_scan_transitions(const char *src, uint32_t *linklist, const uint32_t ignore, const int limit); 35 | -------------------------------------------------------------------------------- /tools/src/common/vram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "common.h" 8 | 9 | #define MAX_SURFACES 40 10 | 11 | // dimensions of the framebuffers 12 | #define VRAM_FB_WIDTH 320 13 | #define VRAM_FB_HEIGHT 240 14 | // dimensions of entire vram 15 | #define VRAM_WIDTH 1024 16 | #define VRAM_HEIGHT 512 17 | // psx texpage dimensions 18 | #define VRAM_TEXPAGE_WIDTH 64 19 | #define VRAM_TEXPAGE_HEIGHT 256 20 | // dimensions of free vram (as in, space to the right of the two framebuffers) 21 | #define VRAM_XSTART VRAM_FB_WIDTH 22 | #define VRAM_NUM_PAGES 2 23 | #define VRAM_PAGE_WIDTH (1024 - VRAM_XSTART) 24 | #define VRAM_PAGE_HEIGHT VRAM_TEXPAGE_HEIGHT 25 | #define VRAM_PAGES_WIDTH VRAM_PAGE_WIDTH 26 | #define VRAM_PAGES_HEIGHT (VRAM_NUM_PAGES * VRAM_PAGE_HEIGHT) 27 | // CLUT pages are in between the two framebuffers 28 | #define VRAM_NUM_CLUT_PAGES 2 29 | #define VRAM_CLUT_PAGE_WIDTH VRAM_FB_WIDTH 30 | #define VRAM_CLUT_PAGE_HEIGHT (VRAM_PAGE_HEIGHT - VRAM_FB_HEIGHT) 31 | #define VRAM_CLUT_PAGE_SIZE (VRAM_CLUT_PAGE_WIDTH * VRAM_CLUT_PAGE_HEIGHT) 32 | #define VRAM_CLUT_PAGE_Y(n) (VRAM_FB_HEIGHT + (n)*VRAM_PAGE_HEIGHT) 33 | 34 | #define PSXRGB(r, g, b) ((((b) >> 3) << 10) | (((g) >> 3) << 5) | ((r) >> 3)) 35 | #define PSXTPAGE(tp, abr, x, y) ((((x)&0x3FF)>>6) | (((y)>>8)<<4) | (((abr)&0x3)<<5) | (((tp)&0x3)<<7)) 36 | #define PSXCLUT(x, y) (((y)<<6) | (((x)>>4)&0x3F)) 37 | 38 | #define PSXRED(c) (((c >> 0) & 0x1F) << 3) 39 | #define PSXGREEN(c) (((c >> 5) & 0x1F) << 3) 40 | #define PSXBLUE(c) (((c >> 10) & 0x1F) << 3) 41 | 42 | #pragma pack(push, 1) 43 | 44 | typedef struct { 45 | int8_t id; // in-game surface id (or -1 if unloaded) 46 | uint8_t mode; // 1 for 256-color and 0 for 16-color 47 | uint16_t clut; // CLUT address as given by vram_fit_clut() 48 | uint16_t tex_x; // absolute X of top left corner in VRAM 49 | uint16_t tex_y; // absolute Y of top left corner in VRAM 50 | } vram_surf_t; 51 | 52 | typedef struct { 53 | int16_t x; 54 | int16_t y; 55 | int16_t w; 56 | int16_t h; 57 | } vram_rect_t; 58 | 59 | typedef struct { 60 | uint16_t numsurf; // total number of surfaces in bank 61 | uint16_t numclut; // total number of CLUTs in bank 62 | vram_rect_t clut_rect[VRAM_NUM_PAGES]; // where to copy the data for each CLUT page 63 | vram_rect_t surf_rect[VRAM_NUM_PAGES]; // where to copy the data for each surface page 64 | vram_surf_t surf[]; // [numsurf] surface headers 65 | // [clutdata_h * VRAM_CLUT_PAGE_WIDTH] words of clut data follows 66 | // [surfdata_h * VRAM_PAGE_WIDTH] words of surface data follows 67 | } vram_surfbank_t; 68 | 69 | typedef struct { 70 | int16_t mode; // 1 for 256-color, 0 for 16-color, -1 for no image 71 | uint16_t w; // width, in words 72 | uint16_t h; // height, in vram lines 73 | uint16_t size; // size, in words 74 | uint32_t ofs; // offset from beginning of ram_surfbank_t 75 | } ram_surf_t; 76 | 77 | typedef struct { 78 | uint32_t numsurf; // total number of surfaces in bank, each surface has its own clut 79 | ram_surf_t surf[]; // [numsurf] surface headers 80 | // surface data follows: 81 | // for each surface: 82 | // some words: image data \ total: `size` words 83 | // 16 or 256 words: clut data / 84 | } ram_surfbank_t; 85 | 86 | #pragma pack(pop) 87 | 88 | // returns CLUT address (result of getClut(clut_x, clut_y)) on success or 0 if failed 89 | // `mode` is 1 for 256-color CLUTs and 0 for 16-color CLUTs 90 | // `clut` is in BGRX8888 format (like in BMPs) 91 | uint16_t vram_fit_clut(const uint8_t *clut, uint16_t mode); 92 | 93 | // returns surface on success or NULL if failed 94 | // `clut` is the CLUT's address in VRAM as returned by vram_fit_clut() 95 | // `mode` is 1 for 256-color CLUTs and 0 for 16-color CLUTs 96 | // `data` is w*h 8-bit pixels 97 | vram_surf_t *vram_fit_surf(const struct bitmap *bmp, const uint16_t clut, const bool align); 98 | 99 | // writes vram_surfbank_t into the file 100 | uint32_t vram_write_surf_bank(FILE *f); 101 | 102 | // writes current VRAM image in PNG format into the file 103 | bool vram_export_png(const char *fname); 104 | 105 | // mark VRAM area as off limits for CLUT data 106 | bool vram_restrict_clut_rect(const int page, const vram_rect_t *rect); 107 | 108 | // mark VRAM area as off limits for surface data 109 | bool vram_restrict_surf_rect(const int page, const vram_rect_t *rect); 110 | 111 | // get filled data area for surface page 112 | const vram_rect_t *vram_get_filled_surf_rect(const int pg); 113 | 114 | // get filled data area for CLUT page 115 | const vram_rect_t *vram_get_filled_clut_rect(const int pg); 116 | 117 | // clear vram and surface list 118 | void vram_reset(void); 119 | 120 | // convert RGBA clut into RGB5551 clut 121 | void vram_transform_clut(uint16_t *dst, const uint8_t *src, const int len); 122 | -------------------------------------------------------------------------------- /tools/src/creditspack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "common/common.h" 8 | #include "common/surface.h" 9 | #include "common/vram.h" 10 | 11 | #define MAX_CREDITS_IMAGES 19 12 | 13 | static struct bitmap bmps[MAX_CREDITS_IMAGES]; 14 | static int num_bmps = 0; 15 | 16 | static int load_image(const char *path) { 17 | bmps[num_bmps].width = 0; 18 | bmps[num_bmps].height = 0; 19 | bmps[num_bmps].numcolors = 0; 20 | 21 | FILE *f = fopen(path, "rb"); 22 | if (!f) { num_bmps++; return -1; } 23 | 24 | const int ret = read_bmp(&bmps[num_bmps++], f); 25 | fclose(f); 26 | if (ret < 0) return -2; 27 | 28 | return 0; 29 | } 30 | 31 | static uint8_t *transform_image(uint8_t *dst, const struct bitmap *bmp) { 32 | const uint8_t *src = bmp->data; 33 | const int linelen = (bmp->numcolors > 16) ? bmp->width : (bmp->width + 1) >> 1; 34 | uint8_t *pdst = dst; 35 | for (int y = 0; y < bmp->height; ++y) { 36 | if (bmp->numcolors > 16) { 37 | memcpy(pdst, src, linelen); 38 | pdst += linelen; 39 | } else { 40 | // in BMP the leftmost pixel is in the topmost nibble because fuck you 41 | const uint8_t *psrc = src; 42 | pdst = &dst[linelen * y]; 43 | for (int i = 0; i < linelen; ++i, ++psrc, ++pdst) 44 | *pdst = ((*psrc & 0xF) << 4) | (*psrc >> 4); 45 | } 46 | src += bmp->stride; 47 | } 48 | return pdst; 49 | } 50 | 51 | int main(int argc, char **argv) { 52 | if (argc < 3) { 53 | printf("usage: creditspack \n"); 54 | return -1; 55 | } 56 | 57 | const char *datapath = argv[1]; 58 | const char *outpath = argv[2]; 59 | char buf[2048]; 60 | 61 | uint32_t total_size = sizeof(ram_surfbank_t); 62 | 63 | snprintf(buf, sizeof(buf), "%s/casts.pbm", datapath); 64 | if (load_image(buf) < 0) { 65 | fprintf(stderr, "error: could not read bmp '%s'\n", buf); 66 | return -1; 67 | } 68 | 69 | int linelen = (bmps[0].numcolors > 16) ? bmps[0].width : (bmps[0].width + 1) >> 1; 70 | total_size += sizeof(ram_surf_t) + (linelen * bmps[0].height) + 71 | ((bmps[0].numcolors > 16) ? 256 : 16) * 2; 72 | 73 | for (int i = 1; i < MAX_CREDITS_IMAGES; ++i) { 74 | snprintf(buf, sizeof(buf), "%s/credit/credit%02d.bmp", datapath, i); 75 | load_image(buf); 76 | linelen = (bmps[i].numcolors > 16) ? bmps[i].width : (bmps[i].width + 1) >> 1; 77 | total_size += sizeof(ram_surf_t) + (linelen * bmps[i].height) + 78 | ((bmps[i].numcolors > 16) ? 256 : 16) * 2; 79 | printf("* (%02d) '%s': %dx%d, %d colors\n", i, buf, bmps[i].width, bmps[i].height, bmps[i].numcolors); 80 | } 81 | 82 | printf("total_size = %u\n", total_size); 83 | 84 | ram_surfbank_t *bank = calloc(1, total_size); 85 | assert(bank); 86 | 87 | uint8_t *surfdata = (uint8_t *)bank + sizeof(*bank) + sizeof(*bank->surf) * num_bmps; 88 | bank->numsurf = num_bmps; 89 | 90 | for (int i = 0; i < num_bmps; ++i) { 91 | if (bmps[i].width == 0) { 92 | bank->surf[i].mode = -1; 93 | } else { 94 | bank->surf[i].mode = (bmps[i].numcolors > 16); 95 | bank->surf[i].w = bmps[i].width; 96 | bank->surf[i].h = bmps[i].height; 97 | bank->surf[i].ofs = surfdata - (uint8_t *)bank; 98 | uint8_t *start = surfdata; 99 | surfdata = transform_image(surfdata, &bmps[i]); 100 | vram_transform_clut((uint16_t *)surfdata, bmps[i].palette, bmps[i].numcolors); 101 | surfdata += 2 * (bank->surf[i].mode ? 256 : 16); 102 | bank->surf[i].size = (surfdata - start) / 2; 103 | printf("* (%02d) %3dx%3d, %3d colors: ofs %06x size %06x words\n", i, bank->surf[i].w, bank->surf[i].h, bmps[i].numcolors, bank->surf[i].ofs, bank->surf[i].size); 104 | } 105 | } 106 | 107 | FILE *f = fopen(outpath, "wb"); 108 | if (!f) { 109 | fprintf(stderr, "error: could not open '%s' for writing\n", outpath); 110 | free(bank); 111 | return -1; 112 | } 113 | 114 | fwrite(bank, surfdata - (uint8_t *)bank, 1, f); 115 | fclose(f); 116 | 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /tools/src/fontgen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define FONT_COLS 16 6 | #define FONT_ROWS_ANSI 6 7 | #define FONT_ROWS_SJIS 6 8 | 9 | #ifndef _WIN32 10 | #error "This program requires GDI for the backwards ass font rendering." 11 | #endif 12 | 13 | #define WIN32_LEAN_AND_MEAN 14 | #include 15 | #include 16 | 17 | static LPDIRECTDRAW ddraw; 18 | static LPDIRECTDRAWSURFACE surf; 19 | 20 | static void cleanup(void) { 21 | if (surf) surf->Release(); 22 | if (ddraw) ddraw->Release(); 23 | } 24 | 25 | int main(int argc, char **argv) { 26 | if (argc < 6) { 27 | printf("usage: fontgen -j|-a \n"); 28 | return -1; 29 | } 30 | 31 | const DWORD charset = !strcmp(argv[1], "-j") ? SHIFTJIS_CHARSET : ANSI_CHARSET; 32 | const char *fntname = argv[2]; 33 | const char *outfile = argv[5]; 34 | int chw = atoi(argv[3]); 35 | int chh = atoi(argv[4]); 36 | const int chrows = (charset == SHIFTJIS_CHARSET) ? FONT_ROWS_SJIS : FONT_ROWS_ANSI; 37 | 38 | if (chw <= 0 || chw > 32) chw = 6; 39 | if (chh <= 0 || chh > 32) chh = 12; 40 | 41 | if (DirectDrawCreate(NULL, &ddraw, NULL) != DD_OK) { 42 | fprintf(stderr, "error: could not create ddraw context\n"); 43 | return -2; 44 | } 45 | 46 | ddraw->SetCooperativeLevel(NULL, DDSCL_NORMAL); 47 | 48 | printf("%s %dx%d\n", fntname, chw, chh); 49 | 50 | atexit(cleanup); 51 | 52 | DDSURFACEDESC desc; 53 | memset(&desc, 0, sizeof(desc)); 54 | desc.dwSize = sizeof(desc); 55 | desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; 56 | desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; 57 | desc.dwWidth = FONT_COLS * chw; 58 | desc.dwHeight = chrows * chh; 59 | const HRESULT res = ddraw->CreateSurface(&desc, &surf, NULL); 60 | if (res != DD_OK) { 61 | fprintf(stderr, "error: could not create ddraw surface: %08x\n", res); 62 | return -3; 63 | } 64 | 65 | DDCOLORKEY ddcolorkey; 66 | ddcolorkey.dwColorSpaceLowValue = 0; 67 | ddcolorkey.dwColorSpaceHighValue = 0; 68 | surf->SetColorKey(DDCKEY_SRCBLT, &ddcolorkey); 69 | 70 | const DWORD quality = NONANTIALIASED_QUALITY; 71 | HFONT font = CreateFontA(chh, chw, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, charset, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, quality, FIXED_PITCH | FF_DONTCARE, fntname); 72 | if (!font) { 73 | fprintf(stderr, "error: could not create font with the specified properties\n"); 74 | return -4; 75 | } 76 | 77 | const DWORD color = 0x00FFFFFF; 78 | const DWORD fillcolor = 0x00000000; 79 | HDC hdc; 80 | 81 | DDBLTFX ddbltfx; 82 | memset(&ddbltfx, 0, sizeof(ddbltfx)); 83 | ddbltfx.dwSize = sizeof(ddbltfx); 84 | ddbltfx.dwFillColor = fillcolor; 85 | RECT rc = { 0, 0, (int)desc.dwWidth, (int)desc.dwHeight }; 86 | surf->Blt(&rc, 0, 0, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx); 87 | 88 | surf->GetDC(&hdc); 89 | { 90 | HGDIOBJ hgdiobj = SelectObject(hdc, font); 91 | SetBkColor(hdc, fillcolor); 92 | SetBkMode(hdc, TRANSPARENT); 93 | SetTextColor(hdc, color); 94 | char text[FONT_COLS]; 95 | for (int row = 0; row < chrows; ++row) { 96 | for (int ch = 0; ch < FONT_COLS; ++ch) 97 | text[ch] = ' ' + FONT_COLS * row + ch; 98 | TextOutA(hdc, 0, row * chh, text, FONT_COLS); 99 | } 100 | SelectObject(hdc, hgdiobj); 101 | } 102 | surf->ReleaseDC(hdc); 103 | 104 | surf->GetDC(&hdc); 105 | { 106 | HDC hmemdc = CreateCompatibleDC(hdc); 107 | memset(&desc, 0, sizeof(desc)); 108 | desc.dwSize = sizeof(desc); 109 | surf->GetSurfaceDesc(&desc); 110 | HBITMAP hbmp = CreateCompatibleBitmap(hdc, desc.dwWidth, desc.dwHeight); 111 | HBITMAP hprevbmp = (HBITMAP)SelectObject(hmemdc, hbmp); 112 | BitBlt(hmemdc, 0, 0, desc.dwWidth, desc.dwHeight, hdc, 0, 0, SRCCOPY); 113 | if (OpenClipboard(NULL)) { 114 | EmptyClipboard(); 115 | SetClipboardData(CF_BITMAP,hbmp); 116 | CloseClipboard(); 117 | printf("font image is now in your clipboard\n"); 118 | } else { 119 | fprintf(stderr, "error: could not open clipboard\n"); 120 | } 121 | SelectObject(hmemdc, hprevbmp); 122 | DeleteDC(hmemdc); 123 | } 124 | surf->ReleaseDC(hdc); 125 | 126 | DeleteObject(font); 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /tools/src/img2h.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define STB_IMAGE_IMPLEMENTATION 6 | #include "common/stb_image.h" 7 | 8 | #define PSXRGB(r, g, b) ((((b) >> 3) << 10) | (((g) >> 3) << 5) | ((r) >> 3)) 9 | 10 | int main(int argc, char **argv) { 11 | if (argc < 2) { 12 | printf("usage: img2h \n"); 13 | return -1; 14 | } 15 | 16 | int w, h; 17 | uint8_t *data = stbi_load(argv[1], &w, &h, NULL, 3); 18 | if (!data) { 19 | fprintf(stderr, "error: could not read image '%s'\n", argv[1]); 20 | return -1; 21 | } 22 | 23 | printf("const int img_width = %d;\n", w); 24 | printf("const int img_height = %d;\n", h); 25 | printf("const unsigned short img_data[%d] = {\n ", w * h); 26 | 27 | const uint8_t *p = data; 28 | for (int i = 0; i < w * h; ++i, p += 3) { 29 | const uint16_t c = PSXRGB(p[0], p[1], p[2]); 30 | printf(" 0x%04X,", c); 31 | if (i != (w * h - 1) && (i & 7) == 7) 32 | printf("\n "); 33 | } 34 | 35 | puts("\n};"); 36 | 37 | free(data); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /tools/src/libpsxav/cdrom.c: -------------------------------------------------------------------------------- 1 | /* 2 | libpsxav: MDEC video + SPU/XA-ADPCM audio library 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | */ 23 | 24 | #include 25 | #include "libpsxav.h" 26 | 27 | static uint32_t psx_cdrom_calculate_edc(uint8_t *sector, uint32_t offset, uint32_t size) 28 | { 29 | uint32_t edc = 0; 30 | for (int i = offset; i < offset+size; i++) { 31 | edc ^= 0xFF&(uint32_t)sector[i]; 32 | for (int ibit = 0; ibit < 8; ibit++) { 33 | edc = (edc>>1)^(0xD8018001*(edc&0x1)); 34 | } 35 | } 36 | return edc; 37 | } 38 | 39 | void psx_cdrom_calculate_checksums(uint8_t *sector, psx_cdrom_sector_type_t type) 40 | { 41 | switch (type) { 42 | case PSX_CDROM_SECTOR_TYPE_MODE1: { 43 | uint32_t edc = psx_cdrom_calculate_edc(sector, 0x0, 0x810); 44 | sector[0x810] = (uint8_t)(edc); 45 | sector[0x811] = (uint8_t)(edc >> 8); 46 | sector[0x812] = (uint8_t)(edc >> 16); 47 | sector[0x813] = (uint8_t)(edc >> 24); 48 | 49 | memset(sector + 0x814, 0, 8); 50 | // TODO: ECC 51 | } break; 52 | case PSX_CDROM_SECTOR_TYPE_MODE2_FORM1: { 53 | uint32_t edc = psx_cdrom_calculate_edc(sector, 0x10, 0x808); 54 | sector[0x818] = (uint8_t)(edc); 55 | sector[0x819] = (uint8_t)(edc >> 8); 56 | sector[0x81A] = (uint8_t)(edc >> 16); 57 | sector[0x81B] = (uint8_t)(edc >> 24); 58 | 59 | // TODO: ECC 60 | } break; 61 | case PSX_CDROM_SECTOR_TYPE_MODE2_FORM2: { 62 | uint32_t edc = psx_cdrom_calculate_edc(sector, 0x10, 0x91C); 63 | sector[0x92C] = (uint8_t)(edc); 64 | sector[0x92D] = (uint8_t)(edc >> 8); 65 | sector[0x92E] = (uint8_t)(edc >> 16); 66 | sector[0x92F] = (uint8_t)(edc >> 24); 67 | } break; 68 | } 69 | } -------------------------------------------------------------------------------- /tools/src/libpsxav/libpsxav.h: -------------------------------------------------------------------------------- 1 | /* 2 | libpsxav: MDEC video + SPU/XA-ADPCM audio library 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | */ 23 | 24 | #ifndef __LIBPSXAV_H__ 25 | #define __LIBPSXAV_H__ 26 | 27 | #include 28 | #include 29 | 30 | // audio.c 31 | 32 | #define PSX_AUDIO_XA_FREQ_SINGLE 18900 33 | #define PSX_AUDIO_XA_FREQ_DOUBLE 37800 34 | 35 | typedef enum { 36 | PSX_AUDIO_XA_FORMAT_XA, // .xa file 37 | PSX_AUDIO_XA_FORMAT_XACD // 2352-byte sector 38 | } psx_audio_xa_format_t; 39 | 40 | typedef struct { 41 | psx_audio_xa_format_t format; 42 | bool stereo; // false or true 43 | int frequency; // 18900 or 37800 Hz 44 | int bits_per_sample; // 4 or 8 45 | int file_number; // 00-FF 46 | int channel_number; // 00-1F 47 | } psx_audio_xa_settings_t; 48 | 49 | typedef struct { 50 | int qerr; // quanitisation error 51 | uint64_t mse; // mean square error 52 | int prev1, prev2; 53 | } psx_audio_encoder_channel_state_t; 54 | 55 | typedef struct { 56 | psx_audio_encoder_channel_state_t left; 57 | psx_audio_encoder_channel_state_t right; 58 | } psx_audio_encoder_state_t; 59 | 60 | #define PSX_AUDIO_SPU_LOOP_END 1 61 | #define PSX_AUDIO_SPU_LOOP_REPEAT 3 62 | #define PSX_AUDIO_SPU_LOOP_START 4 63 | 64 | uint32_t psx_audio_xa_get_buffer_size(psx_audio_xa_settings_t settings, int sample_count); 65 | uint32_t psx_audio_spu_get_buffer_size(int sample_count); 66 | uint32_t psx_audio_xa_get_buffer_size_per_sector(psx_audio_xa_settings_t settings); 67 | uint32_t psx_audio_spu_get_buffer_size_per_block(void); 68 | uint32_t psx_audio_xa_get_samples_per_sector(psx_audio_xa_settings_t settings); 69 | uint32_t psx_audio_spu_get_samples_per_block(void); 70 | int psx_audio_xa_encode(psx_audio_xa_settings_t settings, psx_audio_encoder_state_t *state, int16_t* samples, int sample_count, uint8_t *output); 71 | int psx_audio_xa_encode_simple(psx_audio_xa_settings_t settings, int16_t* samples, int sample_count, uint8_t *output); 72 | int psx_audio_spu_encode(psx_audio_encoder_state_t *state, int16_t* samples, int sample_count, uint8_t *output); 73 | int psx_audio_spu_encode_simple(int16_t* samples, int sample_count, uint8_t *output, int loop_start); 74 | int psx_audio_xa_encode_finalize(psx_audio_xa_settings_t settings, uint8_t *output, int output_length); 75 | void psx_audio_spu_set_flag_at_sample(uint8_t* spu_data, int sample_pos, int flag); 76 | 77 | // cdrom.c 78 | 79 | #define PSX_CDROM_SECTOR_SIZE 2352 80 | 81 | typedef enum { 82 | PSX_CDROM_SECTOR_TYPE_MODE1, 83 | PSX_CDROM_SECTOR_TYPE_MODE2_FORM1, 84 | PSX_CDROM_SECTOR_TYPE_MODE2_FORM2 85 | } psx_cdrom_sector_type_t; 86 | 87 | void psx_cdrom_calculate_checksums(uint8_t *sector, psx_cdrom_sector_type_t type); 88 | 89 | #endif /* __LIBPSXAV_H__ */ -------------------------------------------------------------------------------- /tools/src/sfxconv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define DR_WAV_IMPLEMENTATION 8 | #include "common/dr_wav.h" 9 | #include "libpsxav/libpsxav.h" 10 | #include "common/common.h" 11 | 12 | // output PSX SPURAM 13 | static uint8_t spuram[SPURAM_SIZE + 1024]; // 1kb of grace zone 14 | static int spuram_ptr = SPURAM_START; 15 | 16 | static struct sfx sfx[MAX_SFX]; // 0 is dummy 17 | static int max_sfx = 0; 18 | 19 | static struct bank_hdr bank_hdr; 20 | 21 | static bool load_wav(struct sfx *sfx, const char *fname) { 22 | drwav wav; 23 | if (!drwav_init_file(&wav, fname, NULL)) 24 | return false; 25 | 26 | if (wav.bitsPerSample != 8 || wav.channels != 1 || wav.sampleRate != 22050) { 27 | fprintf(stderr, "error: '%s' is not a mono unsigned 8-bit 22khz PCM WAV file!\n", fname); 28 | drwav_uninit(&wav); 29 | return false; 30 | } 31 | 32 | int16_t *pcm = calloc(1, sizeof(int16_t) * wav.totalPCMFrameCount); 33 | assert(pcm); 34 | 35 | const uint32_t total_read = drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, pcm); 36 | assert(total_read); 37 | 38 | sfx->data = pcm; 39 | sfx->len = total_read; 40 | sfx->freq = wav.sampleRate; 41 | 42 | drwav_uninit(&wav); 43 | 44 | return true; 45 | } 46 | 47 | static bool load_all_sfx(const char *path) { 48 | char fname[2048]; 49 | int i = 1; 50 | for (; i < MAX_SFX; ++i) { 51 | snprintf(fname, sizeof(fname), "%s/%d.wav", path, i); 52 | if (load_wav(&sfx[i], fname)) 53 | max_sfx = i; 54 | } 55 | return (i > 1); 56 | } 57 | 58 | static void cleanup(void) { 59 | for (int i = 0; i < max_sfx; ++i) { 60 | if (sfx[i].data) 61 | free(sfx[i].data); 62 | } 63 | } 64 | 65 | static inline bool is_sfx_looping(const int sfx) { 66 | return (sfx == 40 || sfx == 41 || sfx == 58 || sfx == 7); 67 | } 68 | 69 | int main(int argc, char **argv) { 70 | if (argc != 3) { 71 | printf("usage: sfxconv \n"); 72 | return -1; 73 | } 74 | 75 | atexit(cleanup); 76 | 77 | const char *wavpath = argv[1]; 78 | const char *outfname = argv[2]; 79 | 80 | if (!load_all_sfx(wavpath)) { 81 | fprintf(stderr, "error: could not load samples from '%s'\n", wavpath); 82 | return -2; 83 | } 84 | 85 | printf("%d samples loaded\n", max_sfx); 86 | 87 | // convert samples 88 | 89 | for (int i = 1; i <= max_sfx; ++i) { 90 | if (sfx[i].data == NULL) 91 | continue; 92 | const int loop_start = is_sfx_looping(i) ? 0 : -1; 93 | // 16 NULL bytes of lead-in to avoid pops 94 | const int adpcm_len = psx_audio_spu_encode_simple(sfx[i].data, sfx[i].len, spuram + spuram_ptr + 16, loop_start); 95 | if (adpcm_len <= 0) { 96 | fprintf(stderr, "error: could not encode sfx %d\n", i); 97 | return -3; 98 | } 99 | sfx[i].addr = spuram_ptr; 100 | spuram_ptr += ALIGN(adpcm_len + 16, 8); 101 | if (spuram_ptr >= SPURAM_SIZE) { 102 | fprintf(stderr, "error: ran out of SPU RAM packing sfx %d\n", i); 103 | return -4; 104 | } 105 | } 106 | 107 | bank_hdr.num_sfx = max_sfx + 1; 108 | bank_hdr.data_size = spuram_ptr - SPURAM_START; 109 | bank_hdr.sfx_addr[0] = 0; 110 | 111 | printf("SPU RAM total usage: %u/%u bytes\n", spuram_ptr, SPURAM_SIZE); 112 | printf("SPU RAM start address: %u\n", SPURAM_START); 113 | printf("bank size: %u bytes\n", bank_hdr.data_size); 114 | printf("bank start: %u\n", SPURAM_START); 115 | printf("bank ident: %02x %02x %02x %02x\n", 116 | spuram[SPURAM_START+0], spuram[SPURAM_START+1], spuram[SPURAM_START+2], spuram[SPURAM_START+3]); 117 | 118 | FILE *f = fopen(outfname, "wb"); 119 | if (!f) { 120 | fprintf(stderr, "error: could not open '%s' for writing\n", outfname); 121 | return -5; 122 | } 123 | 124 | // write header 125 | fwrite(&bank_hdr, sizeof(bank_hdr), 1, f); 126 | // write sample addresses 127 | for (int i = 1; i <= max_sfx; ++i) 128 | fwrite(&sfx[i].addr, sizeof(uint32_t), 1, f); 129 | // write sample data 130 | fwrite(spuram + SPURAM_START, spuram_ptr - SPURAM_START, 1, f); 131 | 132 | fclose(f); 133 | 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /tools/src/stagepack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common/common.h" 6 | #include "common/stage.h" 7 | #include "common/tsc.h" 8 | 9 | static stage_list_t stages[MAX_STAGES]; 10 | static int numstages = 0; 11 | 12 | static void cleanup(void) { 13 | for (int i = 0; i < numstages; ++i) 14 | free(stages[i].stage); 15 | } 16 | 17 | int main(int argc, char **argv) { 18 | if (argc < 3) { 19 | printf("usage: stagepack \n"); 20 | return -1; 21 | } 22 | 23 | const char *listname = argv[1]; 24 | const char *datapath = argv[2]; 25 | const char *outpath = argv[3]; 26 | 27 | // init stage list 28 | for (int i = 0; i < MAX_STAGES; ++i) { 29 | stages[i].numlinks = -1; 30 | stages[i].numsongs = -1; 31 | } 32 | 33 | FILE *f = fopen(listname, "r"); 34 | if (!f) { 35 | fprintf(stderr, "error: could not open stage list file '%s'\n", listname); 36 | return -2; 37 | } 38 | 39 | numstages = read_stagelist(stages, f); 40 | printf("\nparsed %d stages from '%s'\n", numstages, listname); 41 | 42 | fclose(f); 43 | 44 | char path[2148]; 45 | char tpath[2048]; 46 | 47 | // load head.tsc 48 | snprintf(path, sizeof(path), "%s/Head.tsc", datapath); 49 | if (!stage_load_tsc_head(path)) { 50 | fprintf(stderr, "error: could not load '%s'\n", path); 51 | return -3; 52 | } 53 | 54 | atexit(cleanup); 55 | 56 | for (int i = 0; i < numstages; ++i) { 57 | // skip NULL stages 58 | if (!stages[i].name[0]) continue; 59 | 60 | snprintf(path, sizeof(path), "%s/Stage/%s", datapath, stages[i].name); 61 | if (stages[i].tilesheet[0]) 62 | snprintf(tpath, sizeof(tpath), "%s/Stage/%s", datapath, stages[i].tilesheet); 63 | else 64 | tpath[0] = '\0'; 65 | 66 | // load the map data and tsc 67 | char *tsc_src = NULL; 68 | stage_t *stage = stage_load(i, path, tpath, &tsc_src); 69 | stages[i].stage = stage; 70 | if (!stages[i].stage) { 71 | fprintf(stderr, "error: could not load stage '%s'\n", path); 72 | return -4; 73 | } 74 | 75 | printf("loaded stage '%8.8s' (id %02x, size %03ux%03u)\n", stages[i].name, stage->id, stage->width, stage->height); 76 | 77 | // fill in the rest of the fields 78 | stage->boss_type = stages[i].bossnum; 79 | stage->bk_type = stages[i].bktype; 80 | strncpy(stage->title, stages[i].title, sizeof(stage->title) - 1); 81 | 82 | // scan for songs used by the stage if they weren't specified manually with a MUSIC directive 83 | if (stages[i].numsongs < 0) 84 | stages[i].numsongs = tsc_scan_music(tsc_src, stages[i].songs, MAX_STAGE_SONGS); 85 | 86 | // scan for transitions if they weren't specified manually with a BANK directive 87 | if (stages[i].numlinks < 0) 88 | stages[i].numlinks = tsc_scan_transitions(tsc_src, stages[i].links, stage->id, MAX_STAGE_LINKS); 89 | 90 | free(tsc_src); 91 | } 92 | 93 | puts(""); 94 | 95 | // save out banks for each stage 96 | // the game will know when to read a new bank from CD 97 | for (int i = 0; i < numstages; ++i) { 98 | // skip NULL stages and stages that are already in a bank 99 | if (!stages[i].name[0] || stages[i].packed) 100 | continue; 101 | 102 | snprintf(tpath, sizeof(tpath), "%s/%01x", outpath, stages[i].stage->id >> 4); 103 | plat_mkdir(tpath, 0755); 104 | 105 | snprintf(path, sizeof(path), "%s/stage%02x.stg", tpath, stages[i].stage->id); 106 | FILE *f = fopen(path, "wb"); 107 | if (!f) { 108 | fprintf(stderr, "error: could not open '%s' for writing\n", path); 109 | return -5; 110 | } 111 | 112 | const uint32_t num = stage_write_bank(&stages[i], stages, datapath, f); 113 | if (!num) { 114 | fclose(f); 115 | fprintf(stderr, "error: could not write bank for stage '%s' (%u)\n", stages[i].name, stages[i].stage->id); 116 | return -6; 117 | } 118 | 119 | fclose(f); 120 | 121 | printf("wrote %u stage(s) into bank '%s'\n", num, path); 122 | } 123 | 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /tools/src/surfpack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "common/common.h" 8 | #include "common/surface.h" 9 | #include "common/vram.h" 10 | 11 | static surf_list_t surflist[MAX_SURFACES]; 12 | static int surfnum; 13 | 14 | static inline int load_surface(int i) { 15 | printf("loading %03d from %s: ", surflist[i].id, surflist[i].fname); 16 | 17 | FILE *f = fopen(surflist[i].fname, "rb"); 18 | if (!f) { 19 | fprintf(stderr, "error: could not open bitmap '%s'\n", surflist[i].fname); 20 | return -4; 21 | } 22 | 23 | const int ret = read_bmp(&surflist[i].img, f); 24 | fclose(f); 25 | if (ret < 0) { 26 | fprintf(stderr, "error: could not read BMP: %d\n", ret); 27 | return -5; 28 | } 29 | 30 | printf("%03dx%03d, %03d colors\n", surflist[i].img.width, surflist[i].img.height, surflist[i].img.numcolors); 31 | 32 | return 0; 33 | } 34 | 35 | int main(int argc, char **argv) { 36 | if (argc < 3) { 37 | printf("usage: surfpack [-e] \n"); 38 | return -1; 39 | } 40 | 41 | bool do_export = false; 42 | const char *infile; 43 | const char *outfile; 44 | if (argc == 4) { 45 | do_export = !strcmp(argv[1], "-e"); 46 | infile = argv[2]; 47 | outfile = argv[3]; 48 | } else { 49 | infile = argv[1]; 50 | outfile = argv[2]; 51 | } 52 | 53 | FILE *f = fopen(infile, "r"); 54 | if (!f) { 55 | fprintf(stderr, "error: could not open '%s' for reading\n", infile); 56 | return -2; 57 | } 58 | 59 | surfnum = read_surflist(surflist, f); 60 | fclose(f); 61 | puts(""); 62 | 63 | if (surfnum <= 0) { 64 | fprintf(stderr, "error: empty or invalid surface list in '%s'\n", infile); 65 | return -3; 66 | } 67 | 68 | for (int i = 0; i < surfnum; ++i) { 69 | if (load_surface(i) < 0) 70 | return -4; 71 | if (convert_surface(surflist[i].id, &surflist[i].img, false) < 0) 72 | return -5; 73 | } 74 | 75 | f = fopen(outfile, "wb"); 76 | if (!f) { 77 | fprintf(stderr, "error: could not open '%s' for writing\n", outfile); 78 | return -6; 79 | } 80 | 81 | vram_write_surf_bank(f); 82 | fclose(f); 83 | 84 | if (do_export) 85 | vram_export_png(outfile); 86 | 87 | const vram_rect_t *r; 88 | for (int i = 0; i < VRAM_NUM_PAGES; ++i) { 89 | r = vram_get_filled_surf_rect(i); 90 | printf("space taken up on surface page %d: (%d, %d, %d, %d)\n", i, r->x, r->y, r->w, r->h); 91 | } 92 | for (int i = 0; i < VRAM_NUM_CLUT_PAGES; ++i) { 93 | r = vram_get_filled_clut_rect(i); 94 | printf("space taken up on CLUT page %d: (%d, %d, %d, %d)\n", i, r->x, r->y, r->w, r->h); 95 | } 96 | 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /tools/src/trigcalc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // generates the tables from Triangle.cpp 7 | 8 | int main(int argc, char **argv) { 9 | // sine 10 | for (int i = 0; i < 0x100; ++i) { 11 | if (i) putchar((i & 7) ? ' ' : '\n'); 12 | const int32_t s = (int32_t)(sin(i * 6.2831998 / 256.0) * 512.0); 13 | printf("%4d,", s); 14 | } 15 | 16 | puts("\n"); 17 | 18 | // tangent 19 | for (int i = 0; i < 0x21; ++i) { 20 | if (i) putchar((i & 7) ? ' ' : '\n'); 21 | const float a = (float)(i * 6.2831855f / 256.0f); 22 | const float b = (float)sin(a) / (float)cos(a); 23 | const int16_t t = (int16_t)(b * 8192.0f); 24 | printf("%4d,", t); 25 | } 26 | 27 | return 0; 28 | } -------------------------------------------------------------------------------- /tools/src/tscc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "common/common.h" 8 | #include "common/tsc.h" 9 | 10 | int main(int argc, char **argv) { 11 | if (argc < 3) { 12 | printf("usage: tscc [-d] []\n"); 13 | return -1; 14 | } 15 | 16 | const char *infile = NULL; 17 | const char *outfile = NULL; 18 | bool decode_only = !strcmp(argv[1], "-d"); 19 | if (!decode_only) { 20 | infile = argv[1]; 21 | outfile = argv[2]; 22 | } else { 23 | infile = argv[2]; 24 | } 25 | 26 | FILE *f = fopen(infile, "rb"); 27 | if (!f) { 28 | fprintf(stderr, "error: could not open '%s' for reading\n", infile); 29 | return -2; 30 | } 31 | 32 | fseek(f, 0, SEEK_END); 33 | const int size = ftell(f); 34 | fseek(f, 0, SEEK_SET); 35 | 36 | char *buf = calloc(1, size + 1); // add NUL 37 | assert(buf); 38 | fread(buf, size, 1, f); 39 | fclose(f); 40 | 41 | tsc_decode((uint8_t *)buf, size); 42 | 43 | if (decode_only) { 44 | printf("%s\n", buf); 45 | free(buf); 46 | return 0; 47 | } 48 | 49 | int tsc_size = -1; 50 | tsc_script_t *tsc = tsc_compile(buf, size, &tsc_size); 51 | free(buf); 52 | if (tsc_size <= 0) 53 | return -3; 54 | 55 | f = fopen(outfile, "wb"); 56 | if (!f) { 57 | fprintf(stderr, "error: could not open '%s' for writing\n", outfile); 58 | free(tsc); 59 | return -2; 60 | } 61 | 62 | fwrite(tsc, tsc_size, 1, f); 63 | fclose(f); 64 | 65 | free(tsc); 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /tools/surflists/surf_main.lst: -------------------------------------------------------------------------------- 1 | # surface bank that's loaded at the start of the game and kept permanently in memory 2 | # surfaces will be packed into VRAM in the order they appear in this list 3 | # `pad` > 0 adds black columns of pixels to the left of the image for alignment 4 | # `tex_x` field of the affected surface points to the beginning of the actual image 5 | 6 | # id path pad comment 7 | 8 | SURF, 23, data/Npc/NpcRegu.pbm, 0, SURFACE_ID_NPC_REGU (8bpp) 9 | SURF, 27, data/Face.pbm, 0, SURFACE_ID_FACE (8bpp) 10 | SURF, 8, data/ItemImage.pbm, 0, SURFACE_ID_ITEM_IMAGE 11 | SURF, 11, data/Arms.pbm, 20, SURFACE_ID_ARMS 12 | SURF, 12, data/ArmsImage.pbm, 0, SURFACE_ID_ARMS_IMAGE 13 | SURF, 14, data/StageImage.pbm, 0, SURFACE_ID_STAGE_ITEM 14 | SURF, 16, data/MyChar.pbm, 0, SURFACE_ID_MY_CHAR 15 | SURF, 17, data/Bullet.pbm, 0, SURFACE_ID_BULLET 16 | SURF, 19, data/Caret.pbm, 16, SURFACE_ID_CARET 17 | SURF, 26, data/TextBox4bpp.pbm, 0, SURFACE_ID_TEXTBOX, updated for PSX 18 | SURF, 20, data/Npc/NpcSym.pbm, 0, SURFACE_ID_NPC_SYM 19 | SURF, 38, data/FontAscii.pbm, 0, SURFACE_ID_FONT1 20 | SURF, 6, data/Fade4bpp.pbm, 0, SURFACE_ID_FADE, but not 2bpp 21 | --------------------------------------------------------------------------------