├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── battle ├── Makefile ├── ai.asm ├── ai_cond.asm ├── attack.asm ├── battle.asm ├── battle_data.asm ├── char.asm ├── cmd.asm ├── damage.asm ├── equip.asm ├── fight.asm ├── init.asm ├── magic.asm ├── retal.asm ├── timer.asm ├── timer_dur.asm └── win.asm ├── btlgfx ├── Makefile ├── btlgfx.asm ├── btlgfx_data.asm ├── cmd_anim.asm ├── cursor.asm ├── magic.asm ├── math.asm ├── menu.asm ├── monster_death.asm ├── ppu.asm ├── special_anim.asm ├── sprite.asm ├── summon.asm └── weapon.asm ├── cutscene ├── Makefile ├── cutscene.asm └── cutscene_data.asm ├── ff4-en.lnk ├── ff4-jp.lnk ├── field ├── Makefile ├── bg_anim.asm ├── bg_gfx.asm ├── event.asm ├── field.asm ├── field_data.asm ├── hardware.asm ├── header.asm ├── move.asm ├── npc.asm ├── pal_anim.asm ├── player.asm ├── rand_battle.asm ├── special.asm ├── sprite.asm ├── tilemap.asm ├── title_en.asm ├── title_jp.asm ├── trigger.asm ├── vehicle.asm └── window.asm ├── include ├── const.inc ├── hardware.inc ├── macros.inc ├── menu_text_en.json └── menu_text_jp.json ├── menu ├── Makefile ├── config.asm ├── equip.asm ├── fat_choco.asm ├── item.asm ├── load.asm ├── magic.asm ├── menu.asm ├── menu_data.asm ├── name.asm ├── namingway.asm ├── order.asm ├── save.asm ├── shop.asm ├── sort.asm ├── status.asm ├── system.asm └── treasure.asm ├── notes ├── ff4-spc-ram-map.txt ├── ff4-spc.asm ├── ff4j-sfc-ram-map.txt ├── ff4j-sfc-rom-map.txt └── ff4j-sfc.asm ├── package-lock.json ├── package.json ├── sound ├── Makefile ├── sound.asm └── sound_data.asm ├── tools ├── calc-checksum.js ├── decode-ff4.js ├── encode-ff4.js ├── encode_menu_text.js └── romtools │ ├── data-codec.js │ ├── data-manager.js │ ├── gfx.js │ ├── hex-string.js │ ├── memory-map.js │ ├── range.js │ ├── rom-decoder.js │ ├── rom-encoder.js │ └── text-codec.js └── vanilla ├── README.md ├── ff4-en-rip.json └── ff4-jp-rip.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sfc 3 | *.o 4 | *.lst 5 | *.map 6 | node_modules/* 7 | ff4-*-data.json 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # exported variables used by module makefiles 3 | export ASM = ca65 4 | export ASMFLAGS = 5 | export VERSION_EXT 6 | 7 | # the linker 8 | LINK = ld65 9 | LINKFLAGS = 10 | 11 | # list of ROM versions 12 | VERSIONS = ff4-jp ff4-jp1 ff4-en ff4-en1 #ff4-jpez 13 | ROM_DIR = rom 14 | ROMS = $(foreach V, $(VERSIONS), $(ROM_DIR)/$(V).sfc) 15 | 16 | # list of modules 17 | MODULES = field menu btlgfx battle sound cutscene 18 | 19 | .PHONY: all rip encode-jp encode-en clean $(VERSIONS) $(MODULES) 20 | 21 | # disable default suffix rules 22 | .SUFFIXES: 23 | 24 | # make all versions 25 | all: $(VERSIONS) 26 | 27 | # rip data from ROMs 28 | rip: 29 | node tools/decode-ff4.js 30 | 31 | encode-jp: ff4-jp-data.json 32 | node tools/encode-ff4.js ff4-jp-data.json 33 | 34 | encode-en: ff4-en-data.json 35 | node tools/encode-ff4.js ff4-en-data.json 36 | 37 | # clean module subdirectories 38 | MODULES_CLEAN = $(foreach M, $(MODULES), $(M)_clean) 39 | 40 | %_clean: 41 | $(MAKE) -C $* clean 42 | 43 | clean: $(MODULES_CLEAN) 44 | $(RM) -r $(ROM_DIR) 45 | 46 | # ROM filenames 47 | FF4_JP_PATH = $(ROM_DIR)/ff4-jp.sfc 48 | FF4_JP1_PATH = $(ROM_DIR)/ff4-jp1.sfc 49 | FF4_EN_PATH = $(ROM_DIR)/ff4-en.sfc 50 | FF4_EN1_PATH = $(ROM_DIR)/ff4-en1.sfc 51 | FF4_JPEZ_PATH = $(ROM_DIR)/ff4-jpez.sfc 52 | 53 | ff4-jp: $(FF4_JP_PATH) 54 | ff4-jp1: $(FF4_JP1_PATH) 55 | ff4-en: $(FF4_EN_PATH) 56 | ff4-en1: $(FF4_EN1_PATH) 57 | ff4-jpez: $(FF4_JPEZ_PATH) 58 | 59 | # set up target-specific variables 60 | ff4-jp: VERSION_EXT = jp 61 | ff4-jp: ASMFLAGS += -D ROM_VERSION=0 62 | 63 | ff4-jp1: VERSION_EXT = jp1 64 | ff4-jp1: ASMFLAGS += -D BUGFIX_WORLD_BATTLE=1 -D ROM_VERSION=1 65 | 66 | ff4-en: VERSION_EXT = en 67 | ff4-en: ASMFLAGS += -D LANG_EN=1 -D ROM_VERSION=0 -D SIMPLE_CONFIG=1 \ 68 | -D BUGFIX_WORLD_BATTLE=1 -D BUGFIX_SYLPH_EFFECT=1 69 | 70 | ff4-en1: VERSION_EXT = en1 71 | ff4-en1: ASMFLAGS += -D LANG_EN=1 -D BUGFIX_REV1=1 -D ROM_VERSION=1 \ 72 | -D SIMPLE_CONFIG=1 -D BUGFIX_WORLD_BATTLE=1 -D BUGFIX_SYLPH_EFFECT=1 73 | 74 | ff4-jpez: VERSION_EXT = jpez 75 | ff4-jpez: ASMFLAGS += -D ROM_VERSION=0 -D BUGFIX_WORLD_BATTLE=1 \ 76 | -D EASY_VERSION=1 -D BUGFIX_REV1=1 77 | 78 | # target-specific object filenames 79 | OBJ_FILES_JP = $(foreach M, $(MODULES), $(M)/obj/$(M)_jp.o) 80 | OBJ_FILES_JP1 = $(foreach M, $(MODULES), $(M)/obj/$(M)_jp1.o) 81 | OBJ_FILES_EN = $(foreach M, $(MODULES), $(M)/obj/$(M)_en.o) 82 | OBJ_FILES_EN1 = $(foreach M, $(MODULES), $(M)/obj/$(M)_en1.o) 83 | OBJ_FILES_JPEZ = $(foreach M, $(MODULES), $(M)/obj/$(M)_jpez.o) 84 | 85 | # rules for making ROM files 86 | $(FF4_JP_PATH): ff4-jp.lnk encode-jp $(OBJ_FILES_JP) 87 | @mkdir -p rom 88 | $(LINK) $(LINKFLAGS) -m $(@:sfc=map) -o $@ -C $< $(OBJ_FILES_JP) 89 | node tools/calc-checksum.js $@ 90 | 91 | $(FF4_JP1_PATH): ff4-jp.lnk encode-jp $(OBJ_FILES_JP1) 92 | @mkdir -p rom 93 | $(LINK) $(LINKFLAGS) -m $(@:sfc=map) -o $@ -C $< $(OBJ_FILES_JP1) 94 | node tools/calc-checksum.js $@ 95 | 96 | $(FF4_EN_PATH): ff4-en.lnk encode-en $(OBJ_FILES_EN) 97 | @mkdir -p rom 98 | $(LINK) $(LINKFLAGS) -m $(@:sfc=map) -o $@ -C $< $(OBJ_FILES_EN) 99 | node tools/calc-checksum.js $@ 100 | 101 | $(FF4_EN1_PATH): ff4-en.lnk encode-en $(OBJ_FILES_EN1) 102 | @mkdir -p rom 103 | $(LINK) $(LINKFLAGS) -m $(@:sfc=map) -o $@ -C $< $(OBJ_FILES_EN1) 104 | node tools/calc-checksum.js $@ 105 | 106 | $(FF4_JPEZ_PATH): ff4-jp.lnk encode-jp $(OBJ_FILES_JPEZ) 107 | @mkdir -p rom 108 | $(LINK) $(LINKFLAGS) -m $(@:sfc=map) -o $@ -C $< $(OBJ_FILES_JPEZ) 109 | node tools/calc-checksum.js $@ 110 | 111 | # run sub-make to create object files for each module 112 | $(OBJ_FILES_JP): $(MODULES) 113 | $(OBJ_FILES_JP1): $(MODULES) 114 | $(OBJ_FILES_EN): $(MODULES) 115 | $(OBJ_FILES_EN1): $(MODULES) 116 | $(OBJ_FILES_JPEZ): $(MODULES) 117 | 118 | # rules for making modules in subdirectories 119 | define MAKE_MODULE 120 | $1/obj/$1_%.o: 121 | $$(MAKE) -C $1 122 | endef 123 | 124 | $(foreach M, $(MODULES), $(eval $(call MAKE_MODULE,$(M)))) 125 | -------------------------------------------------------------------------------- /battle/Makefile: -------------------------------------------------------------------------------- 1 | 2 | NAME = battle 3 | OBJ_DIR = obj 4 | TARGET = $(OBJ_DIR)/$(NAME)_$(VERSION_EXT).o 5 | 6 | SRC_MAIN = $(NAME).asm 7 | SRC_FILES = $(wildcard *.asm) 8 | INC_DIR = ../include 9 | INC_FILES = $(wildcard $(INC_DIR)/*.inc) 10 | DATA_FILES = $(wildcard data/*) 11 | GFX_FILES = $(wildcard gfx/*) 12 | TEXT_FILES = $(wildcard text/*) 13 | 14 | .PHONY: all clean 15 | 16 | # disable default suffix rules 17 | .SUFFIXES: 18 | 19 | all: $(TARGET) 20 | 21 | clean: 22 | $(RM) -r $(OBJ_DIR) data gfx text 23 | 24 | $(TARGET): $(SRC_FILES) $(INC_FILES) $(DATA_FILES) $(GFX_FILES) $(TEXT_FILES) 25 | @mkdir -p $(OBJ_DIR) 26 | $(ASM) $(ASMFLAGS) -I $(INC_DIR) -l $(@:o=lst) $(SRC_MAIN) -o $@ 27 | -------------------------------------------------------------------------------- /battle/damage.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: damage.asm | 8 | ; | | 9 | ; | description: damage calculation | 10 | ; | | 11 | ; | created: 4/21/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ get number of hits ] 15 | 16 | CalcHits: 17 | @c987: stz $38fd ; clear number of hits 18 | lda $38fb 19 | beq @c99e ; return if no base hits 20 | tay 21 | @c990: jsr Rand99 22 | cmp $38fa ; check vs. hit rate 23 | bcs @c99b 24 | inc $38fd ; increment number of hits 25 | @c99b: dey 26 | bne @c990 27 | @c99e: rts 28 | 29 | ; ------------------------------------------------------------------------------ 30 | 31 | ; [ calculate damage ] 32 | 33 | CalcDmg: 34 | @c99f: ldx $3902 ; base attack 35 | stx $3956 36 | stx $3958 37 | lsr $3957 38 | ror $3956 39 | lda $3957 40 | beq @c9b9 41 | ldx #$00ff 42 | stx $3956 43 | @c9b9: clr_ax 44 | lda $3956 45 | jsr RandXA 46 | tax 47 | stx $3956 48 | jsr Add16 49 | ldx $395a 50 | stx $a4 51 | lda $38fe ; elemental multiplier 52 | jsr ApplyDmgMult 53 | lda $38ff ; creature type multiplier 54 | jsr ApplyDmgMult 55 | lda $3900 56 | beq @c9ec ; branch if not a crit 57 | clc 58 | lda $a4 59 | adc $3901 ; add crit bonus 60 | sta $a4 61 | lda $a5 62 | adc #0 63 | sta $a5 64 | @c9ec: sec 65 | lda $a4 66 | sbc $3904 67 | sta $a4 68 | lda $a5 69 | sbc $3905 70 | sta $a5 71 | bcs @ca01 72 | clr_ax 73 | stx $a4 74 | @ca01: ldx $a4 75 | stx $393d 76 | lda $38fc 77 | tax 78 | stx $393f 79 | jsr Mult16 80 | ldx $3943 81 | beq @ca1b 82 | ldx #$ffff 83 | stx $3941 84 | @ca1b: ldx $3941 85 | stx $3945 86 | lda $3906 87 | tax 88 | stx $3947 89 | jsr Div16 90 | ldx $3949 91 | stx $a4 92 | cpx #$270f 93 | bcc @ca3a 94 | ldx #$270f 95 | stx $a4 96 | @ca3a: ldx $a4 97 | bne @ca40 98 | inc $a4 99 | @ca40: rts 100 | 101 | ; ------------------------------------------------------------------------------ 102 | 103 | ; [ apply damage multiplier ] 104 | 105 | ApplyDmgMult: 106 | @ca41: bne @ca48 107 | stz $a4 108 | stz $a5 109 | rts 110 | @ca48: lsr 111 | bne @ca50 112 | lsr $a5 113 | ror $a4 114 | rts 115 | @ca50: tax 116 | stx $393d 117 | ldx $a4 118 | stx $393f 119 | jsr Mult16 120 | ldx $3941 121 | stx $a4 122 | rts 123 | 124 | ; ------------------------------------------------------------------------------ 125 | 126 | ; [ get pointer to damage buffer ] 127 | 128 | GetDmgPtr: 129 | @ca62: sta $a9 130 | bpl @ca6b ; branch if a character 131 | and #$7f 132 | clc 133 | adc #$05 134 | @ca6b: asl 135 | tax 136 | rts 137 | 138 | ; ------------------------------------------------------------------------------ 139 | 140 | ; [ update hp after damage ] 141 | 142 | ApplyDmg: 143 | @ca6e: longa 144 | clr_ax 145 | stx $a9 146 | tay 147 | ; start of loop 148 | @ca75: tya 149 | lsr 150 | tax 151 | lda $3540,x 152 | and #$00ff 153 | beq @ca83 ; branch if target is present 154 | jmp @cb22 155 | @ca83: ldx $a9 156 | lda $34d4,y 157 | and #$4000 158 | beq @ca9e ; branch if attack didn't miss 159 | lda $355b 160 | beq @ca9e ; branch if attack can show zero damage 161 | lda $34d4,y 162 | and #$bfff ; clear miss flag 163 | sta $34d4,y 164 | jmp @cb22 165 | @ca9e: lda $34d4,y 166 | bpl @cab4 ; branch if attack did damage 167 | ; restore hp 168 | clc 169 | lda $34d4,y 170 | and #$3fff 171 | adc $2007,x ; add to target hp 172 | bcc @cae6 173 | lda #$ffff ; max $ffff 174 | bra @cae6 175 | ; damage hp 176 | @cab4: lda $34d4,y 177 | and #$bfff ; clear miss flag 178 | sta $2898 179 | sec 180 | lda $2007,x 181 | beq @cad1 182 | sbc $2898 ; subtract from target hp 183 | beq @caca 184 | bcs @cae6 185 | ; hp reached zero 186 | @caca: shorta 187 | inc $3907 ; increment number of targets that died 188 | longa 189 | @cad1: tya 190 | asl 191 | tax 192 | shorta0 193 | lda $338e,x ; set dead status 194 | and #$fe 195 | ora #$80 196 | sta $338e,x 197 | longa 198 | clr_a 199 | ldx $a9 200 | @cae6: sta $2007,x ; cap at max hp 201 | cmp $2009,x 202 | bcc @caf6 203 | lda $2009,x 204 | sta $2007,x 205 | bra @cb13 206 | @caf6: lda $2009,x 207 | jsr Lsr_2 208 | cmp $2007,x 209 | bcc @cb13 ; branch if not at critical hp 210 | tya 211 | asl 212 | tax 213 | shorta0 214 | lda $3391,x ; set critical status 215 | ora #$01 216 | sta $3391,x 217 | longa 218 | bra @cb22 219 | @cb13: shorta0 220 | ldx $a9 221 | lda $2006,x ; clear critical status 222 | and #$fe 223 | sta $2006,x 224 | longa 225 | @cb22: ldx $a9 ; next target 226 | txa 227 | clc 228 | adc #$0080 229 | tax 230 | stx $a9 231 | iny2 232 | cpy #$001a 233 | beq @cb36 234 | jmp @ca75 235 | @cb36: shorta0 236 | rts 237 | 238 | ; ------------------------------------------------------------------------------ 239 | 240 | ; [ apply attack status ] 241 | 242 | ApplyAttackStatus: 243 | @cb3a: lda $3908 244 | bpl @cb4d 245 | lda $2740 246 | and #$82 247 | beq @cb4d 248 | stz $3908 249 | stz $3909 250 | rts 251 | @cb4d: longa 252 | lda $2703 253 | and #$bfff 254 | cmp $3908 255 | bcc @cb60 256 | stz $3908 257 | jmp @cc32 258 | @cb60: ldx $c7 259 | lda $2703 260 | ora $3908 261 | sta $338e,x 262 | lda $3908 263 | and #$3b01 264 | bne @cb76 265 | jmp @cc32 266 | @cb76: shorta0 267 | lda $3908 268 | and #$01 269 | beq @cba9 270 | lda #$06 271 | sta $d6 272 | lda $cf 273 | jsr CalcTimerDur 274 | lda #$09 ; poison timer 275 | jsr SetTimer 276 | lda #$40 277 | sta $2a06,x 278 | lda $cf 279 | asl 280 | tax 281 | lda $29eb,x ; enable poison timer 282 | ora #$10 283 | sta $29eb,x 284 | lda $d4 285 | sta $2b2a,x 286 | lda $d5 287 | sta $2b2b,x 288 | @cba9: lda $3909 289 | and #$30 290 | beq @cbc4 291 | lda #$04 292 | sta $d6 293 | lda $cf 294 | jsr CalcTimerDur 295 | lda #$03 ; action timer 296 | jsr SetTimer 297 | lda #$40 298 | sta $2a06,x 299 | rts 300 | @cbc4: lda $3909 301 | and #$03 302 | beq @cc31 303 | ldx $c7 304 | lda $2704 305 | and #$fc 306 | sta $a9 307 | lda $2704 308 | and #$03 309 | beq @cc07 310 | clc 311 | adc $269f 312 | sta $aa 313 | and #$04 314 | beq @cbff 315 | lda $a9 316 | sta $338f,x 317 | lda $2703 318 | ora #$40 319 | sta $338e,x 320 | lda $cf 321 | asl 322 | tax 323 | lda $29eb,x ; disable petrify timer 324 | and #$f7 325 | sta $29eb,x 326 | rts 327 | @cbff: lda $a9 328 | ora $aa 329 | sta $338f,x 330 | rts 331 | @cc07: lda #$04 332 | sta $d6 333 | lda $cf 334 | jsr CalcTimerDur 335 | lda #$0c ; petrify timer 336 | jsr SetTimer 337 | lda #$40 338 | sta $2a06,x 339 | lda $cf 340 | asl 341 | tax 342 | lda $29eb,x ; enable petrify timer 343 | ora #$08 344 | sta $29eb,x 345 | lda $d4 346 | sta $2b44,x 347 | lda $d5 348 | sta $2b45,x 349 | rts 350 | @cc31: rts 351 | @cc32: shorta0 352 | rts 353 | 354 | ; ------------------------------------------------------------------------------ 355 | -------------------------------------------------------------------------------- /battle/retal.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: retal.asm | 8 | ; | | 9 | ; | description: monster retaliation | 10 | ; | | 11 | ; | created: 4/21/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ do retaliation ] 15 | 16 | DoRetal: 17 | @c16c: lda $34c4 18 | bmi @c172 19 | rts 20 | @c172: lda #$02 ; battle graphics $02: wait one frame 21 | jsr ExecBtlGfx 22 | lda $34c5 23 | sta $38b2 24 | @c17d: lda $3877 25 | bne @c187 26 | lda $38b2 27 | bne @c18a 28 | @c187: jmp @c401 29 | @c18a: jsr FindValidChar 30 | lda $a9 31 | bne @c187 32 | lda #$01 33 | sta $390a 34 | sta $38b3 35 | ldx #5 36 | lda $38b2 37 | @c19f: asl 38 | bcs @c1a5 39 | inx 40 | bra @c19f 41 | @c1a5: txa 42 | sta $d2 43 | sec 44 | sbc #$05 45 | tax 46 | lda $38b2 47 | jsr ClearBit 48 | sta $38b2 49 | lda $38aa,x 50 | beq @c1d8 51 | lda $d2 52 | jsr SelectObj 53 | clr_ay 54 | ldx $a6 55 | lda $2003,x 56 | and #$c0 57 | bne @c1d8 58 | lda $2004,x 59 | and #$3c 60 | bne @c1d8 61 | lda $2005,x 62 | and #$40 63 | beq @c1e0 64 | @c1d8: stz $390a 65 | stz $38b3 66 | bra @c17d 67 | @c1e0: lda $2050,x 68 | sta $38b4,y 69 | inx 70 | iny 71 | cpy #7 72 | bne @c1e0 73 | clr_ay 74 | ldx $a6 75 | @c1f1: stz $2050,x 76 | inx 77 | iny 78 | cpy #7 79 | bne @c1f1 80 | sec 81 | lda $d2 82 | sbc #$05 83 | sta $361c 84 | sta $df 85 | lda #$3c 86 | sta $e1 87 | jsr Mult8 88 | ldx $e3 89 | clr_ay 90 | @c210: lda $3659,x 91 | sta $3839,y 92 | inx 93 | iny 94 | cpy #$003c 95 | bne @c210 96 | jsr InitAIGfxScript 97 | stz $35be 98 | lda $361c 99 | sta $df 100 | lda #$14 101 | sta $e1 102 | jsr Mult8 103 | ldx $e3 104 | stx $35c3 105 | stx $35c5 106 | lda $361c 107 | tax 108 | stx $393d 109 | ldx #$0258 110 | stx $393f 111 | jsr Mult16 112 | ldx $3941 113 | stx $35bf 114 | stx $35c1 115 | lda $361c 116 | sta $df 117 | lda #$28 118 | sta $e1 119 | jsr Mult8 120 | ldx $e3 121 | stx $35c8 122 | lda $361c 123 | tax 124 | stx $393d 125 | ldx #$00a0 126 | stx $393f 127 | jsr Mult16 128 | ldx $3941 129 | stx $35cc 130 | @c277: lda $35be 131 | jsr Asl_2 132 | clc 133 | adc $35c8 134 | sta $35ca 135 | lda $35c9 136 | adc #$00 137 | sta $35cb 138 | lda $35be 139 | tax 140 | longa 141 | txa 142 | jsr Asl_4 143 | clc 144 | adc $35cc 145 | sta $35ce 146 | shorta0 147 | ldx $35c5 148 | lda $531f,x 149 | cmp #$ff 150 | beq @c2f0 151 | stz $35c7 152 | @c2ad: ldx $35ca 153 | lda $53bf,x 154 | cmp #$ff 155 | beq @c2e0 156 | clr_ay 157 | ldx $35ce 158 | @c2bc: lda $54ff,x 159 | sta $289c,y 160 | inx 161 | iny 162 | cpy #4 163 | bne @c2bc 164 | stx $35ce 165 | jsr CheckAICond 166 | lda $de 167 | beq @c2e5 168 | inc $35ca 169 | inc $35c7 170 | lda $35c7 171 | cmp #$04 172 | bne @c2ad 173 | @c2e0: inc $35be 174 | bra @c2f0 175 | @c2e5: inc $35c5 176 | inc $35c5 177 | inc $35be 178 | bra @c277 179 | @c2f0: lda $de 180 | beq @c2fd 181 | lda $35be 182 | dec 183 | sta $35be 184 | bra @c300 185 | @c2fd: jmp @c3d6 186 | @c300: lda $361c 187 | tax 188 | stx $393d 189 | ldx #$0258 190 | stx $393f 191 | jsr Mult16 192 | lda $35be 193 | sta $df 194 | lda #$3c 195 | sta $e1 196 | jsr Mult8 197 | clc 198 | lda $3941 199 | adc $e3 200 | sta $e3 201 | lda $3942 202 | adc $e4 203 | sta $e4 204 | ldx $e3 205 | clr_ay 206 | @c32f: lda $7e59ff,x 207 | sta $289c,y 208 | cmp #$ff 209 | beq @c33e 210 | inx 211 | iny 212 | bra @c32f 213 | @c33e: clr_ax 214 | ldy $38bb 215 | @c343: lda $289c,x 216 | sta $3659,y 217 | cmp #$ff 218 | beq @c37e 219 | cmp #$c0 220 | bcs @c361 221 | sta $365a,y 222 | phx 223 | ldx $a6 224 | sta $2052,x 225 | plx 226 | lda #$c2 227 | sta $3659,y 228 | iny 229 | @c361: cmp #$e8 230 | bcc @c373 231 | cmp #$fa 232 | bcs @c37a 233 | inx 234 | iny 235 | lda $289c,x 236 | sta $3659,y 237 | bra @c37a 238 | @c373: phx 239 | ldx $a6 240 | sta $2051,x 241 | plx 242 | @c37a: inx 243 | iny 244 | bra @c343 245 | @c37e: ldx $38bb 246 | @c381: lda $3659,x 247 | cmp #$ff 248 | beq @c3c9 249 | cmp #$f9 250 | beq @c3ba 251 | cmp #$e8 252 | bcc @c3c6 253 | cmp #$f0 254 | bcc @c3ab 255 | cmp #$f4 256 | bcc @c3c5 257 | cmp #$f8 258 | bcs @c3c6 259 | phx 260 | pha 261 | inx 262 | lda $3659,x 263 | sta $a9 264 | pla 265 | jsr ChangeBattleVar 266 | plx 267 | bra @c3c5 268 | @c3ab: phx 269 | pha 270 | inx 271 | lda $3659,x 272 | sta $a9 273 | pla 274 | jsr ChangeMonsterStat 275 | plx 276 | bra @c3c5 277 | @c3ba: phx 278 | inx 279 | lda $3659,x 280 | sta $a9 281 | jsr GetAITarget 282 | plx 283 | @c3c5: inx 284 | @c3c6: inx 285 | bra @c381 286 | @c3c9: jsr SetMonsterTarget 287 | lda $361c 288 | tax 289 | stz $3879,x 290 | jsr _ad78 291 | @c3d6: lda $d2 292 | jsr SelectObj 293 | clr_ay 294 | ldx $a6 295 | @c3df: lda $38b4,y 296 | sta $2050,x 297 | inx 298 | iny 299 | cpy #7 300 | bne @c3df 301 | ldx $38bb 302 | clr_ay 303 | @c3f1: lda $3839,y 304 | sta $3659,x 305 | inx 306 | iny 307 | cpy #$003c 308 | bne @c3f1 309 | jmp @c17d 310 | @c401: stz $390a 311 | stz $38b3 312 | rts 313 | 314 | ; ------------------------------------------------------------------------------ 315 | 316 | ; [ find valid character target ] 317 | 318 | ; $a9: set to 1 if no valid targets found 319 | 320 | FindValidChar: 321 | @c408: clr_axy 322 | sty $a9 323 | @c40d: lda $3540,y 324 | bne @c425 ; branch if not present 325 | lda $2003,x 326 | and #$c0 327 | bne @c425 ; branch if dead 328 | lda $2005,x 329 | and #$82 330 | bne @c425 ; branch if magnetized or jumping 331 | lda $2006,x 332 | bpl @c430 ; branch if not hiding 333 | @c425: jsr NextObj 334 | iny 335 | cpy #5 336 | bne @c40d 337 | inc $a9 338 | @c430: rts 339 | 340 | ; ------------------------------------------------------------------------------ 341 | 342 | ; [ skip a.i. multi-action (normal) ] 343 | 344 | SkipMultiAttack: 345 | @c431: clr_ay 346 | sty $a9 347 | ldx $38bb 348 | @c438: lda $3659,x 349 | cmp #$ff 350 | beq @c45d 351 | cmp #$fc 352 | beq @c446 353 | inx 354 | bra @c438 355 | @c446: inc $a9 356 | ldx $38bb 357 | lda #$e1 358 | sta $3659,x 359 | clr_a 360 | sta $365a,x 361 | dec 362 | sta $365b,x 363 | lda #$fc 364 | sta $365b,x 365 | @c45d: rts 366 | 367 | ; ------------------------------------------------------------------------------ 368 | 369 | ; [ skip a.i. multi-action (retaliation) ] 370 | 371 | SkipMultiAttackRetal: 372 | @c45e: clr_ay 373 | sty $a9 374 | phx 375 | @c463: lda $3839,x 376 | cmp #$ff 377 | beq @c487 378 | cmp #$fc 379 | beq @c471 380 | inx 381 | bra @c463 382 | @c471: inc $a9 383 | plx 384 | lda #$e1 385 | sta $3659,x 386 | clr_a 387 | sta $365a,x 388 | dec 389 | sta $365c,x 390 | lda #$fc 391 | sta $365b,x 392 | rts 393 | @c487: plx 394 | rts 395 | 396 | ; ------------------------------------------------------------------------------ 397 | -------------------------------------------------------------------------------- /battle/timer_dur.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: timer_dur.asm | 8 | ; | | 9 | ; | description: calculate timer durations | 10 | ; | | 11 | ; | created: 4/21/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ calculate timer duration ] 15 | 16 | CalcTimerDur: 17 | @9e2c: stz $3558 18 | cmp #$05 19 | bcc @9e36 20 | inc $3558 21 | @9e36: jsr SelectObj 22 | ldx $a6 23 | lda $2060,x ; base timer duration 24 | sta $a9 25 | lda $2061,x 26 | sta $aa 27 | lda $203b,x ; speed modifier 28 | tay 29 | sty $3979 30 | phx 31 | lda $d6 ; timer duration function 32 | asl 33 | tax 34 | lda f:TimerDurTbl,x 35 | sta $80 36 | lda f:TimerDurTbl+1,x 37 | sta $81 38 | lda #$03 39 | sta $82 40 | plx 41 | jml [$0080] 42 | 43 | ; ------------------------------------------------------------------------------ 44 | 45 | ; [ timer duration $00: action ] 46 | 47 | TimerDur_00: 48 | @9e65: jsr ApplySpeedMod 49 | ldy $ab 50 | bne @9e6e 51 | inc $ab ; min 1 52 | @9e6e: jmp SetTimerDur 53 | 54 | ; ------------------------------------------------------------------------------ 55 | 56 | ; [ timer duration $01/$02: immediate ] 57 | 58 | TimerDur_01: 59 | TimerDur_02: 60 | @9e71: lda $3558 61 | bne @9e7a 62 | clr_ax 63 | bra @9e7d 64 | @9e7a: ldx #1 65 | @9e7d: stx $a9 66 | jsr ApplySpeedMod 67 | jmp SetTimerDur 68 | 69 | ; ------------------------------------------------------------------------------ 70 | 71 | ; [ timer duration $0b: item ] 72 | 73 | TimerDur_0b: 74 | @9e85: lda $397b 75 | sta $df 76 | lda #$06 77 | sta $e1 78 | jsr Mult8 79 | ldx $e3 80 | lda f:ItemProp,x ; item action delay (all are zero in vanilla) 81 | bra _9eab 82 | 83 | ; ------------------------------------------------------------------------------ 84 | 85 | ; [ timer duration $03: magic ] 86 | 87 | TimerDur_03: 88 | @9e99: lda $397b 89 | sta $df 90 | lda #$06 91 | sta $e1 92 | jsr Mult8 93 | ldx $e3 94 | lda f:AttackProp,x ; magic action delay 95 | _9eab: and #$1f 96 | tax 97 | stx $a9 98 | asl $a9 ; multiply by 2 99 | rol $aa 100 | lda $388b 101 | beq @9ebd ; branch if not auto-battle 102 | clr_ax 103 | stx $a9 104 | @9ebd: jsr ApplySpeedMod 105 | jmp SetTimerDur 106 | 107 | ; ------------------------------------------------------------------------------ 108 | 109 | ; [ timer duration $04/$05: sleep/paralyze ] 110 | 111 | TimerDur_04: 112 | TimerDur_05: 113 | @9ec3: lda $3558 114 | beq @9ecd ; branch if a character 115 | lda $202f,x ; monster level + 10 116 | bra @9ed0 117 | @9ecd: lda $2018,x ; mod. spirit 118 | @9ed0: sta $ad 119 | asl $ad 120 | asl $ad 121 | sec 122 | lda #$2c ; 300 - 4 * (mod. spirit) 123 | sbc $ad 124 | sta $a9 125 | lda #$01 126 | sbc #$00 127 | sta $aa 128 | bcs @9eea 129 | ldx #1 ; min 1 130 | stx $a9 131 | @9eea: jsr ApplySpeedMod 132 | ldx $ab 133 | stx $3945 134 | ldx #6 ; divide by 6 135 | stx $3947 136 | jsr Div16 137 | ldx $3949 138 | stx $ab 139 | jmp SetTimerDur 140 | 141 | ; ------------------------------------------------------------------------------ 142 | 143 | ; [ timer duration $06/$07: poison/petrify ] 144 | 145 | TimerDur_06: 146 | TimerDur_07: 147 | @9f03: lda $3558 148 | beq @9f0d 149 | lda $202f,x 150 | bra @9f10 151 | @9f0d: lda $2016,x 152 | @9f10: clc 153 | adc #$14 154 | tax 155 | stx $a9 156 | jsr ApplySpeedMod 157 | jmp SetTimerDur 158 | 159 | ; ------------------------------------------------------------------------------ 160 | 161 | ; [ timer duration $08: reflect ] 162 | 163 | TimerDur_08: 164 | @9f1c: lda $397b 165 | sta $ad 166 | stz $ae 167 | asl $ad 168 | rol $ae 169 | clc 170 | lda $ad 171 | adc #$1e 172 | sta $a9 173 | lda $ae 174 | adc #$00 175 | sta $aa 176 | jsr ApplySpeedMod 177 | jmp SetTimerDur 178 | 179 | ; ------------------------------------------------------------------------------ 180 | 181 | ; [ timer duration $09: sap ] 182 | 183 | TimerDur_09: 184 | @9f3a: lda $3558 185 | beq @9f4a 186 | lda $202f,x 187 | sta $ad 188 | lda #$04 189 | sta $ae 190 | bra @9f57 191 | @9f4a: clc 192 | lda $2017,x 193 | adc $2018,x 194 | sta $ad 195 | lda #$02 196 | sta $ae 197 | @9f57: lda $ad 198 | sta $df 199 | lda $ae 200 | sta $e2 201 | jsr Mult8 202 | clc 203 | lda $e3 204 | adc #$1e 205 | sta $a9 206 | lda $e4 207 | adc #$00 208 | sta $aa 209 | jsr ApplySpeedMod 210 | jmp SetTimerDur 211 | 212 | ; ------------------------------------------------------------------------------ 213 | 214 | ; [ timer duration $0a: stop ] 215 | 216 | TimerDur_0a: 217 | @9f75: lda $397b 218 | sta $df 219 | lda #$03 220 | sta $e1 221 | jsr Mult8 222 | ldx $e3 223 | stx $a9 224 | jsr ApplySpeedMod 225 | jmp SetTimerDur 226 | 227 | ; ------------------------------------------------------------------------------ 228 | 229 | ; [ timer duration $0c: action delay ] 230 | 231 | TimerDur_0c: 232 | @9f8b: lda $397b ; battle command id 233 | cmp #$06 234 | bne @9f97 ; branch if not jump 1 235 | ldx #4 236 | bra @9fca 237 | @9f97: tax 238 | lda f:CmdDelayTbl,x ; action delay for each command 239 | bne @9fa1 240 | jmp TimerDur_01 241 | @9fa1: bpl @9fb8 242 | 243 | ; msb set: delay is multiple of a turn 244 | and #$7f 245 | tax 246 | stx $393f 247 | ldx $a9 248 | stx $393d 249 | jsr Mult16 250 | ldx $3941 251 | stx $a9 252 | bra @9fcc 253 | 254 | ; msb clear: delay is fraction of a turn 255 | @9fb8: tax 256 | stx $3947 257 | ldx $a9 258 | stx $3945 259 | jsr Div16 260 | ldx $3949 261 | bne @9fca 262 | inx 263 | @9fca: stx $a9 264 | @9fcc: jsr ApplySpeedMod 265 | ; fallthrough 266 | 267 | ; ------------------------------------------------------------------------------ 268 | 269 | ; [ set timer duration ] 270 | 271 | SetTimerDur: 272 | @9fcf: ldy $ab 273 | bpl @9fd5 ; min zero 274 | clr_ay 275 | @9fd5: sty $d4 276 | rts 277 | 278 | ; ------------------------------------------------------------------------------ 279 | 280 | ; [ apply speed multiplier ] 281 | 282 | ApplySpeedMod: 283 | @9fd8: ldx $a9 ; base duration 284 | stx $393d 285 | ldx $3979 ; multiplier 286 | stx $393f 287 | jsr Mult16 288 | ldx $3941 289 | stx $3945 290 | ldx #$0010 291 | stx $3947 292 | jsr Div16 293 | ldx $3949 294 | stx $ab ; mod. duration 295 | rts 296 | 297 | ; ------------------------------------------------------------------------------ 298 | 299 | ; timer duration jump table 300 | TimerDurTbl: 301 | @9ffb: .addr TimerDur_00 302 | .addr TimerDur_01 303 | .addr TimerDur_02 304 | .addr TimerDur_03 305 | .addr TimerDur_04 306 | .addr TimerDur_05 307 | .addr TimerDur_06 308 | .addr TimerDur_07 309 | .addr TimerDur_08 310 | .addr TimerDur_09 311 | .addr TimerDur_0a 312 | .addr TimerDur_0b 313 | .addr TimerDur_0c 314 | 315 | ; ------------------------------------------------------------------------------ 316 | -------------------------------------------------------------------------------- /btlgfx/Makefile: -------------------------------------------------------------------------------- 1 | 2 | NAME = btlgfx 3 | OBJ_DIR = obj 4 | TARGET = $(OBJ_DIR)/$(NAME)_$(VERSION_EXT).o 5 | 6 | SRC_MAIN = $(NAME).asm 7 | SRC_FILES = $(wildcard *.asm) 8 | INC_DIR = ../include 9 | INC_FILES = $(wildcard $(INC_DIR)/*.inc) 10 | DATA_FILES = $(wildcard data/*) 11 | GFX_FILES = $(wildcard gfx/*) 12 | TEXT_FILES = $(wildcard text/*) 13 | 14 | .PHONY: all clean 15 | 16 | # disable default suffix rules 17 | .SUFFIXES: 18 | 19 | all: $(TARGET) 20 | 21 | clean: 22 | $(RM) -r $(OBJ_DIR) data gfx text 23 | 24 | $(TARGET): $(SRC_FILES) $(INC_FILES) $(DATA_FILES) $(GFX_FILES) $(TEXT_FILES) 25 | @mkdir -p $(OBJ_DIR) 26 | $(ASM) $(ASMFLAGS) -I $(INC_DIR) -l $(@:o=lst) $(SRC_MAIN) -o $@ 27 | -------------------------------------------------------------------------------- /btlgfx/math.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: math.asm | 8 | ; | | 9 | ; | description: math routines for battle animations | 10 | ; | | 11 | ; | created: 4/21/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ increase polar angle ] 15 | 16 | IncPolarAngle: 17 | @e732: pha 18 | clc 19 | adc $f133,x 20 | sta $f133,x 21 | pla 22 | clc 23 | adc $f173,x 24 | sta $f173,x 25 | rts 26 | 27 | ; ------------------------------------------------------------------------------ 28 | 29 | ; [ init polar angle (far) ] 30 | 31 | InitPolarAngle_far: 32 | @e743: jsr InitPolarAngle 33 | rtl 34 | 35 | ; ------------------------------------------------------------------------------ 36 | 37 | ; [ init polar angle ] 38 | 39 | InitPolarAngle: 40 | @e747: ldx #0 41 | lda #$40 42 | @e74c: stz $f133,x ; angle for sine 43 | sta $f173,x ; angle for cosine 44 | inx 45 | cpx #8 46 | bne @e74c 47 | rts 48 | 49 | ; ------------------------------------------------------------------------------ 50 | 51 | ; [ init polar radius (far) ] 52 | 53 | SetPolarRadius_far: 54 | @e759: jsr SetPolarRadius 55 | rtl 56 | 57 | ; ------------------------------------------------------------------------------ 58 | 59 | ; [ set polar radius ] 60 | 61 | SetPolarRadius: 62 | @e75d: ldx #0 63 | @e760: sta $f1b3,x ; angle for y-direction 64 | sta $f1f3,x ; angle for x-direction 65 | inx 66 | cpx #8 67 | bne @e760 68 | rts 69 | 70 | ; ------------------------------------------------------------------------------ 71 | 72 | ; [ increase polar angle ] 73 | 74 | ; unused 75 | 76 | IncPolarRadius: 77 | @e76d: pha 78 | clc 79 | adc $f1b3,x 80 | sta $f1b3,x 81 | pla 82 | clc 83 | adc $f1f3,x 84 | sta $f1f3,x 85 | rts 86 | 87 | ; ------------------------------------------------------------------------------ 88 | 89 | ; [ calculate polar y coordinate ] 90 | 91 | CalcPolarY: 92 | @e77e: lda $f1b3,x 93 | asl 94 | sta $28 95 | lda $f133,x 96 | jmp CalcSine 97 | 98 | ; ------------------------------------------------------------------------------ 99 | 100 | ; [ calculate polar x coordinate ] 101 | 102 | ; unused 103 | 104 | CalcPolarX: 105 | @e78a: lda $f1f3,x 106 | asl 107 | sta $28 108 | lda $f173,x 109 | jmp CalcSine 110 | 111 | ; ------------------------------------------------------------------------------ 112 | 113 | ; [ calculate $28 * sin(A) (far) ] 114 | 115 | CalcSine_far: 116 | @e796: jsr CalcSine 117 | rtl 118 | 119 | ; ------------------------------------------------------------------------------ 120 | 121 | ; [ calculate $28 * sin(A) ] 122 | 123 | CalcSine: 124 | @e79a: tax 125 | lda f:AnimSineTbl,x 126 | bpl @e7b1 127 | eor #$ff 128 | sta $26 129 | jsr Mult8 130 | lda $2b 131 | eor #$ff 132 | inc 133 | bpl @e7ba 134 | @e7af: sec 135 | rts 136 | @e7b1: sta $26 137 | jsr Mult8 138 | lda $2b 139 | bmi @e7af 140 | @e7ba: clc 141 | rts 142 | 143 | ; ------------------------------------------------------------------------------ 144 | 145 | ; [ calculate trajectory (far) ] 146 | 147 | CalcTrajectory_far: 148 | @e7bc: jsr CalcTrajectory 149 | rtl 150 | 151 | ; ------------------------------------------------------------------------------ 152 | 153 | ; [ calculate trajectory ] 154 | 155 | ; calculates a vector trajectory from one point to another 156 | 157 | CalcTrajectory: 158 | @e7c0: stz $f120 159 | ldx $f111 ; initial x position 160 | stx $f118 161 | stz $f116 162 | stz $f117 163 | stz $f11b 164 | stz $f11c 165 | stz $f11a 166 | lda $f112 ; initial y position 167 | cmp $f114 ; final y position 168 | beq @e810 169 | bcc @e7f9 170 | ; move in -y direction 171 | dec $f117 172 | lda $f112 173 | sec 174 | sbc $f114 175 | sta $f11b 176 | lsr 177 | clc 178 | adc $f114 179 | sta $f11f 180 | bra @e810 181 | ; move in +y direction 182 | @e7f9: inc $f117 183 | lda $f114 184 | sec 185 | sbc $f112 186 | sta $f11b 187 | lsr 188 | clc 189 | adc $f112 190 | sta $f11f 191 | bra @e810 192 | @e810: lda $f111 193 | cmp $f113 194 | beq @e846 195 | bcc @e831 196 | ; move in -x direction 197 | dec $f116 198 | lda $f111 199 | sec 200 | sbc $f113 201 | sta $f11c 202 | lsr 203 | clc 204 | adc $f113 205 | sta $f11e 206 | bra @e846 207 | ; move in +x direction 208 | @e831: inc $f116 209 | lda $f113 210 | sec 211 | sbc $f111 212 | sta $f11c 213 | lsr 214 | clc 215 | adc $f111 216 | sta $f11e 217 | @e846: lda $f11c 218 | tax 219 | stx $26 220 | lda $f115 ; divide by trajectory speed 221 | tax 222 | stx $28 223 | jsr Div16 224 | lda $2a 225 | inc 226 | sta $f11d 227 | rts 228 | 229 | ; ------------------------------------------------------------------------------ 230 | 231 | ; [ update vector trajectory (far) ] 232 | 233 | UpdateTrajectory_far: 234 | @e85c: jsr UpdateTrajectory 235 | rtl 236 | 237 | ; ------------------------------------------------------------------------------ 238 | 239 | ; [ update vector trajectory ] 240 | 241 | UpdateTrajectory: 242 | @e860: stz $f121 243 | stz $f122 244 | lda $f115 245 | sta $00 246 | @e86b: lda $f118 247 | clc 248 | adc $f116 249 | sta $f118 250 | lda $f11a 251 | clc 252 | adc $f11b 253 | sta $f11a 254 | @e87f: cmp $f11c 255 | bcc @e8cb 256 | lda $f117 257 | bmi @e898 258 | longa 259 | lda $00 260 | pha 261 | lda $f117 262 | and #$00ff 263 | sta $00 264 | bra @e8a5 265 | @e898: longa 266 | lda $00 267 | pha 268 | lda $f117 269 | ora #$ff00 270 | sta $00 271 | @e8a5: lda $f121 272 | clc 273 | adc $00 274 | sta $f121 275 | pla 276 | sta $00 277 | shorta0 278 | lda $f119 279 | clc 280 | adc $f117 281 | sta $f119 282 | lda $f11a 283 | sec 284 | sbc $f11c 285 | sta $f11a 286 | jmp @e87f 287 | @e8cb: dec $00 288 | bne @e86b 289 | dec $f11d 290 | bne @e8d6 291 | sec 292 | rts 293 | @e8d6: clc 294 | rts 295 | 296 | ; ------------------------------------------------------------------------------ 297 | -------------------------------------------------------------------------------- /btlgfx/monster_death.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: monster_death.asm | 8 | ; | | 9 | ; | description: monster death routines | 10 | ; | | 11 | ; | created: 4/21/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ load monster death pal (near) ] 15 | 16 | LoadMonsterDeathPal_near: 17 | @e8d8: jsl LoadMonsterDeathPal 18 | rts 19 | 20 | ; ------------------------------------------------------------------------------ 21 | 22 | ; [ ] 23 | 24 | _02e8dd: 25 | @e8dd: jsl _03f555 26 | rts 27 | 28 | ; ------------------------------------------------------------------------------ 29 | 30 | ; [ ] 31 | 32 | _02e8e2: 33 | @e8e2: jsl _03f56a 34 | rts 35 | 36 | ; ------------------------------------------------------------------------------ 37 | 38 | ; [ ] 39 | 40 | _02e8e7: 41 | @e8e7: tax 42 | lda #$ff 43 | sta $f123,x 44 | jsr _02e8dd 45 | jsr LoadMonsterTiles 46 | jsr _02e8e2 47 | jmp TfrRightMonsterTiles 48 | 49 | ; ------------------------------------------------------------------------------ 50 | 51 | ; [ battle graphics $10: monster death animations ] 52 | 53 | MonsterDeath: 54 | @e8f9: lda $f49a 55 | cmp #$8c 56 | bne @e906 57 | sta $f483 58 | stz $f49a 59 | @e906: lda $f473 60 | bne @e91e 61 | clr_ax 62 | @e90d: lda $29b5,x 63 | cmp $f123,x 64 | bne @e91e 65 | inx 66 | cpx #8 67 | bne @e90d 68 | jmp @e9a5 ; return if no monsters died 69 | @e91e: lda $38e6 70 | cmp #$03 71 | bne @e928 72 | jmp @e9a5 73 | @e928: lda $29a4 74 | and #$10 75 | beq @e940 76 | lda $38e6 77 | bne @e940 78 | jsr _02c46d 79 | jsr _02e8dd 80 | jsl _03f418 81 | bra @e960 82 | @e940: jsr _02e8dd 83 | jsr UpdateBG1Tiles 84 | ldy $1800 85 | cpy #$01b7 86 | bne @e969 87 | lda $38e6 88 | bne @e969 89 | inc $f4a1 90 | jsr LoadMonsterTiles 91 | jsr TfrRightMonsterTiles 92 | jsl _03f591 93 | @e960: jsr _02e8e2 94 | clr_a 95 | jsr PlaySfx 96 | bra @e9a5 97 | @e969: clr_ax 98 | @e96b: lda $29b5,x 99 | cmp $f12b,x 100 | beq @e981 101 | phx 102 | txa 103 | sta $f109 104 | lda #$07 105 | sta $f10a 106 | jsr _028a89 107 | plx 108 | @e981: inx 109 | cpx #8 110 | bne @e96b 111 | jsr ModifyBG1Tiles_near 112 | jsr _02e8e2 113 | jsr TfrLeftMonsterTiles 114 | jsr LoadMonsterDeathPal_near 115 | lda #1 116 | jsr SwapMonsterScreen 117 | jsr LoadMonsterTiles 118 | jsr TfrRightMonsterTiles 119 | jsr MonsterDeathAnim 120 | clr_a 121 | jsr SwapMonsterScreen 122 | @e9a5: stz $f483 123 | rts 124 | 125 | ; ------------------------------------------------------------------------------ 126 | 127 | ; [ monster death animation ] 128 | 129 | MonsterDeathAnim: 130 | @e9a9: lda $f483 131 | bne @e9be 132 | lda #$80 133 | sta $f485 134 | lda $38e6 135 | tax 136 | lda f:MonsterDeathSfxTbl,x 137 | jsr PlaySfx 138 | @e9be: stz $f483 139 | jsr WaitFrame 140 | clr_ax 141 | @e9c6: lda $ee30,x 142 | sta $00 143 | lda $ee31,x 144 | sta $01 145 | lda #1 146 | jsr DecColor 147 | lda $00 148 | sta $ee30,x 149 | lda $01 150 | sta $ee31,x 151 | inx2 152 | cpx #$0020 153 | bne @e9c6 154 | jsl UpdateMonsterDeathAnim 155 | inc $4e 156 | lda $4e 157 | cmp #$30 158 | bne @e9be 159 | rts 160 | 161 | ; ------------------------------------------------------------------------------ 162 | -------------------------------------------------------------------------------- /cutscene/Makefile: -------------------------------------------------------------------------------- 1 | 2 | NAME = cutscene 3 | OBJ_DIR = obj 4 | TARGET = $(OBJ_DIR)/$(NAME)_$(VERSION_EXT).o 5 | 6 | SRC_MAIN = $(NAME).asm 7 | SRC_FILES = $(wildcard *.asm) 8 | INC_DIR = ../include 9 | INC_FILES = $(wildcard $(INC_DIR)/*.inc) 10 | DATA_FILES = $(wildcard data/*) 11 | GFX_FILES = $(wildcard gfx/*) 12 | TEXT_FILES = $(wildcard text/*) 13 | 14 | .PHONY: all clean 15 | 16 | # disable default suffix rules 17 | .SUFFIXES: 18 | 19 | all: $(TARGET) 20 | 21 | clean: 22 | $(RM) -r $(OBJ_DIR) data gfx text 23 | 24 | $(TARGET): $(SRC_FILES) $(INC_FILES) $(DATA_FILES) $(GFX_FILES) $(TEXT_FILES) 25 | @mkdir -p $(OBJ_DIR) 26 | $(ASM) $(ASMFLAGS) -I $(INC_DIR) -l $(@:o=lst) $(SRC_MAIN) -o $@ 27 | -------------------------------------------------------------------------------- /cutscene/cutscene_data.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: cutscene_data.asm | 8 | ; | | 9 | ; | description: data for cutscene module | 10 | ; | | 11 | ; | created: 4/21/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | .import RNGTbl, WindowGfx, DakutenTbl 15 | 16 | ; ------------------------------------------------------------------------------ 17 | 18 | .segment "solar_system_sprite" 19 | 20 | ; 12/f660 21 | .include "data/solar_system_sprite.asm" 22 | 23 | ; ------------------------------------------------------------------------------ 24 | 25 | .segment "cutscene_gfx" 26 | 27 | ; 13/d200 28 | .include "gfx/solar_system_pal.asm" 29 | 30 | ; 13/d300 31 | .include "gfx/credits_stars_gfx.asm" 32 | 33 | ; 13/d510 34 | .include "gfx/credits_pal.asm" 35 | 36 | ; ------------------------------------------------------------------------------ 37 | 38 | .segment "solar_system_gfx" 39 | 40 | ; 15/cc00 41 | .include "gfx/solar_system_gfx.asm" 42 | 43 | ; 15/d840 44 | .include "gfx/big_moon_gfx.asm" 45 | 46 | ; ------------------------------------------------------------------------------ 47 | -------------------------------------------------------------------------------- /ff4-en.lnk: -------------------------------------------------------------------------------- 1 | memory { 2 | ram: start = $000000, size = $2000, type = rw; 3 | wram: start = $7e2000, size = $01e000, type = rw; 4 | sram: start = $700000, size = $2000, type = rw; 5 | bank_00: start = $008000, size = $8000, type = ro, fill = yes, fillval = $ff; 6 | bank_01: start = $018000, size = $8000, type = ro, fill = yes, fillval = $ff; 7 | bank_02: start = $028000, size = $8000, type = ro, fill = yes, fillval = $ff; 8 | bank_03: start = $038000, size = $8000, type = ro, fill = yes, fillval = $ff; 9 | bank_04: start = $048000, size = $24000, type = ro, fill = yes, fillval = $ff; 10 | bank_08: start = $08c000, size = $4000, type = ro, fill = yes, fillval = $ff; 11 | bank_09: start = $098000, size = $8000, type = ro, fill = yes, fillval = $ff; 12 | bank_0a: start = $0a8000, size = $8000, type = ro, fill = yes, fillval = $ff; 13 | bank_0b: start = $0b8000, size = $8000, type = ro, fill = yes, fillval = $ff; 14 | bank_0c: start = $0c8000, size = $8000, type = ro, fill = yes, fillval = $ff; 15 | bank_0d: start = $0d8000, size = $8000, type = ro, fill = yes, fillval = $ff; 16 | bank_0e: start = $0e8000, size = $8000, type = ro, fill = yes, fillval = $ff; 17 | bank_0f: start = $0f8000, size = $8000, type = ro, fill = yes, fillval = $ff; 18 | bank_10: start = $108000, size = $8000, type = ro, fill = yes, fillval = $ff; 19 | bank_11: start = $118000, size = $8000, type = ro, fill = yes, fillval = $ff; 20 | bank_12: start = $128000, size = $8000, type = ro, fill = yes, fillval = $ff; 21 | bank_13: start = $138000, size = $8000, type = ro, fill = yes, fillval = $ff; 22 | bank_14: start = $148000, size = $8000, type = ro, fill = yes, fillval = $ff; 23 | bank_15: start = $158000, size = $8000, type = ro, fill = yes, fillval = $ff; 24 | bank_16: start = $168000, size = $8000, type = ro, fill = yes, fillval = $ff; 25 | bank_17: start = $178000, size = $17e00, type = ro, fill = yes, fillval = $ff; 26 | bank_19: start = $19fe00, size = $0200, type = ro, fill = yes, fillval = $ff; 27 | bank_1a: start = $1a8000, size = $8000, type = ro, fill = yes, fillval = $ff; 28 | bank_1b: start = $1b8000, size = $8000, type = ro, fill = yes, fillval = $00; 29 | bank_1c: start = $1c8000, size = $8000, type = ro, fill = yes, fillval = $ff; 30 | bank_1d: start = $1d8000, size = $8000, type = ro, fill = yes, fillval = $ff; 31 | bank_1e: start = $1e8000, size = $8000, type = ro, fill = yes, fillval = $ff; 32 | bank_1f: start = $1f8000, size = $8000, type = ro, fill = yes, fillval = $ff; 33 | } 34 | 35 | segments { 36 | # battle_dp: load = ram, type = bss, start = $0000; 37 | menu_dp: load = ram, type = bss, start = $0100; 38 | stack: load = ram, type = bss, start = $0200; 39 | sprite_ram: load = ram, type = bss, start = $0300; 40 | field_dp: load = ram, type = bss, start = $0600; 41 | # sound_dp: load = ram, type = bss, start = $1e00; 42 | # wram: load = wram, type = bss; 43 | # sram: load = sram, type = bss; 44 | 45 | field_code: load = bank_00, type = ro; 46 | field_code_ext: load = bank_00, type = ro, start = $00ffbc; 47 | snes_header: load = bank_00, type = ro, start = $00ffc0; 48 | vectors: load = bank_00, type = ro, start = $00ffe0; 49 | menu_code: load = bank_01, type = ro; 50 | menu_data: load = bank_01, type = ro; 51 | btlgfx_code3: load = bank_01, type = ro, start = $01e300; 52 | btlgfx_code: load = bank_02, type = ro, align = $80; 53 | battle_code: load = bank_03, type = ro, align = $80; 54 | btlgfx_code2: load = bank_03, type = ro, start = $03f280; 55 | sound_code: load = bank_04, type = ro; 56 | sound_data: load = bank_04, type = ro, start = $04c000; 57 | title_gfx: load = bank_08, type = ro, start = $08c000; 58 | prologue_gfx: load = bank_08, type = ro, start = $08ec80; 59 | monster_gfx1: load = bank_09, type = ro; 60 | monster_gfx2: load = bank_0a, type = ro; 61 | window_gfx: load = bank_0a, type = ro, start = $0af000; 62 | monster_gfx3: load = bank_0b, type = ro; 63 | monster_gfx4: load = bank_0c, type = ro; 64 | battle_anim_gfx: load = bank_0c, type = ro, start = $0cb6c0; 65 | misc_battle_gfx: load = bank_0c, type = ro; 66 | map_sprite_pal: load = bank_0d, type = ro; 67 | unused: load = bank_0d, type = ro; 68 | menu_pal: load = bank_0d, type = ro, start = $0d86d0; 69 | summon_gfx: load = bank_0d, type = ro, start = $0d87f0; 70 | summon_tilemap: load = bank_0d, type = ro, start = $0df260; 71 | summon_frame: load = bank_0d, type = ro, start = $0df660; 72 | btlgfx_data1: load = bank_0d, type = ro, start = $0df800; 73 | battle_prop: load = bank_0e, type = ro, align = $0100; 74 | monster_prop: load = bank_0e, type = ro, align = $0100; 75 | rand_battle: load = bank_0e, type = ro, align = $0100; 76 | battle_anim_pal: load = bank_0e, type = ro, align = $0100; 77 | boss_tilemap: load = bank_0e, type = ro, align = $0100; 78 | ai_data: load = bank_0e, type = ro, start = $0ee000; # align = $0100; 79 | battle_dlg: load = bank_0e, type = ro, align = $0200; 80 | attack_name: load = bank_0f, type = ro, start = $0f8000; 81 | attack_prop: load = bank_0f, type = ro, start = $0f9070; 82 | attack_anim: load = bank_0f, type = ro, start = $0f9e10; 83 | item_misc: load = bank_0f, type = ro, start = $0fa450; 84 | char_name: load = bank_0f, type = ro, start = $0fa710; 85 | char_prop: load = bank_0f, type = ro, start = $0fa900; 86 | item_desc: load = bank_0f, type = ro, start = $0fae00; 87 | battle_msg: load = bank_0f, type = ro, start = $0fb000; 88 | status_name: load = bank_0f, type = ro, start = $0fb400; 89 | level_up: load = bank_0f, type = ro, start = $0fb500; 90 | menu_code2: load = bank_0f, type = ro, start = $0fc600; 91 | spell_list: load = bank_0f, type = ro, start = $0fc700; 92 | monster_gfx_prop: load = bank_0f, type = ro, start = $0fca00; 93 | battle_bg_pal: load = bank_0f, type = ro, start = $0fd200; 94 | battle_anim: load = bank_0f, type = ro, start = $0fd4e0; 95 | event_dlg1: load = bank_10, type = ro; 96 | map_dlg: load = bank_11, type = ro; 97 | event_data1: load = bank_12, type = ro; 98 | event_data2: load = bank_12, type = ro, start = $12f000; 99 | solar_system_sprite: load = bank_12, type = ro, start = $12f660; 100 | npc_data: load = bank_13, type = ro, align = $0100; 101 | shop_prop: load = bank_13, type = ro, align = $0100; 102 | event_dlg2: load = bank_13, type = ro, start = $13a500; 103 | cutscene_gfx: load = bank_13, type = ro, start = $13d200; 104 | cutscene_code: load = bank_13, type = ro, start = $13d610; 105 | btlgfx_data2: load = bank_13, type = ro, start = $13f900; 106 | battle_data: load = bank_13, type = ro, start = $13fd00; 107 | battle_code2: load = bank_13, type = ro, start = $13ff12; 108 | cutscene_code_ext: load = bank_13, type = ro, start = $13fff7; 109 | world_data: load = bank_14, type = ro; 110 | field_data: load = bank_14, type = ro, align = $0100; 111 | menu_code3: load = bank_14, type = ro, align = $0100; 112 | triggers: load = bank_15, type = ro, align = $80; 113 | map_title: load = bank_15, type = ro, start = $159620; 114 | map_prop: load = bank_15, type = ro, start = $159c80; 115 | field_code2: load = bank_15, type = ro, start = $15b000; 116 | solar_system_gfx: load = bank_15, type = ro, align = $0100; 117 | map_gfx4: load = bank_15, type = ro, start = $15dc00; 118 | world_tilemap: load = bank_16, type = ro; 119 | telescope_window: load = bank_16, type = ro, align = $80; 120 | btlgfx_data3: load = bank_16, type = ro, start = $16ed80; 121 | sub_tilemap: load = bank_17, type = ro; 122 | world_triggers: load = bank_19, type = ro, start = $19fe60; 123 | battle_char_gfx: load = bank_1a, type = ro; 124 | prologue_moon: load = bank_1a, type = ro, start = $1afcf0; 125 | map_sprite_gfx: load = bank_1b, type = ro; 126 | world_sprite_gfx: load = bank_1c, type = ro; 127 | battle_bg_gfx: load = bank_1c, type = ro, start = $1ca800; 128 | weapon_gfx: load = bank_1c, type = ro, start = $1cd900; 129 | monster_pal: load = bank_1c, type = ro; 130 | battle_char_pal: load = bank_1c, type = ro; 131 | world_gfx: load = bank_1d, type = ro; 132 | portrait_gfx: load = bank_1d, type = ro; 133 | map_gfx3: load = bank_1d, type = ro; 134 | map_gfx1: load = bank_1e, type = ro; 135 | menu_code4: load = bank_1e, type = ro; 136 | earth_moon: load = bank_1e, type = ro, align = $20; 137 | map_gfx2: load = bank_1f, type = ro; 138 | } 139 | -------------------------------------------------------------------------------- /ff4-jp.lnk: -------------------------------------------------------------------------------- 1 | memory { 2 | ram: start = $000000, size = $2000, type = rw; 3 | wram: start = $7e2000, size = $01e000, type = rw; 4 | sram: start = $700000, size = $2000, type = rw; 5 | bank_00: start = $008000, size = $8000, type = ro, fill = yes, fillval = $ff; 6 | bank_01: start = $018000, size = $8000, type = ro, fill = yes, fillval = $ff; 7 | bank_02: start = $028000, size = $8000, type = ro, fill = yes, fillval = $ff; 8 | bank_03: start = $038000, size = $8000, type = ro, fill = yes, fillval = $ff; 9 | bank_04: start = $048000, size = $24000, type = ro, fill = yes, fillval = $ff; 10 | bank_08: start = $08c000, size = $4000, type = ro, fill = yes, fillval = $ff; 11 | bank_09: start = $098000, size = $8000, type = ro, fill = yes, fillval = $ff; 12 | bank_0a: start = $0a8000, size = $8000, type = ro, fill = yes, fillval = $ff; 13 | bank_0b: start = $0b8000, size = $8000, type = ro, fill = yes, fillval = $ff; 14 | bank_0c: start = $0c8000, size = $8000, type = ro, fill = yes, fillval = $ff; 15 | bank_0d: start = $0d8000, size = $8000, type = ro, fill = yes, fillval = $ff; 16 | bank_0e: start = $0e8000, size = $8000, type = ro, fill = yes, fillval = $ff; 17 | bank_0f: start = $0f8000, size = $8000, type = ro, fill = yes, fillval = $ff; 18 | bank_10: start = $108000, size = $8000, type = ro, fill = yes, fillval = $ff; 19 | bank_11: start = $118000, size = $8000, type = ro, fill = yes, fillval = $ff; 20 | bank_12: start = $128000, size = $8000, type = ro, fill = yes, fillval = $ff; 21 | bank_13: start = $138000, size = $8000, type = ro, fill = yes, fillval = $ff; 22 | bank_14: start = $148000, size = $8000, type = ro, fill = yes, fillval = $ff; 23 | bank_15: start = $158000, size = $8000, type = ro, fill = yes, fillval = $ff; 24 | bank_16: start = $168000, size = $8000, type = ro, fill = yes, fillval = $ff; 25 | bank_17: start = $178000, size = $18000, type = ro, fill = yes, fillval = $ff; 26 | bank_1a: start = $1a8000, size = $8000, type = ro, fill = yes, fillval = $ff; 27 | bank_1b: start = $1b8000, size = $8000, type = ro, fill = yes, fillval = $00; 28 | bank_1c: start = $1c8000, size = $8000, type = ro, fill = yes, fillval = $ff; 29 | bank_1d: start = $1d8000, size = $8000, type = ro, fill = yes, fillval = $ff; 30 | bank_1e: start = $1e8000, size = $8000, type = ro, fill = yes, fillval = $ff; 31 | bank_1f: start = $1f8000, size = $8000, type = ro, fill = yes, fillval = $ff; 32 | } 33 | 34 | segments { 35 | # battle_dp: load = ram, type = bss, start = $0000; 36 | menu_dp: load = ram, type = bss, start = $0100; 37 | stack: load = ram, type = bss, start = $0200; 38 | sprite_ram: load = ram, type = bss, start = $0300; 39 | field_dp: load = ram, type = bss, start = $0600; 40 | # sound_dp: load = ram, type = bss, start = $1e00; 41 | # wram: load = wram, type = bss; 42 | # sram: load = sram, type = bss; 43 | 44 | field_code: load = bank_00, type = ro; 45 | field_code_ext: load = bank_00, type = ro, start = $00ffbc; 46 | snes_header: load = bank_00, type = ro, start = $00ffc0; 47 | vectors: load = bank_00, type = ro, start = $00ffe0; 48 | menu_code: load = bank_01, type = ro; 49 | menu_data: load = bank_01, type = ro; 50 | btlgfx_code3: load = bank_01, type = ro, start = $01e300; 51 | btlgfx_code: load = bank_02, type = ro, align = $80; 52 | battle_code: load = bank_03, type = ro, align = $80; 53 | btlgfx_code2: load = bank_03, type = ro, start = $03f280; 54 | sound_code: load = bank_04, type = ro; 55 | sound_data: load = bank_04, type = ro, start = $04c000; 56 | title_gfx: load = bank_08, type = ro; 57 | prologue_gfx: load = bank_08, type = ro; 58 | monster_gfx1: load = bank_09, type = ro; 59 | monster_gfx2: load = bank_0a, type = ro; 60 | window_gfx: load = bank_0a, type = ro, start = $0af000; 61 | monster_gfx3: load = bank_0b, type = ro; 62 | monster_gfx4: load = bank_0c, type = ro; 63 | battle_anim_gfx: load = bank_0c, type = ro, start = $0cb6c0; 64 | misc_battle_gfx: load = bank_0c, type = ro; 65 | map_sprite_pal: load = bank_0d, type = ro; 66 | unused: load = bank_0d, type = ro; 67 | menu_pal: load = bank_0d, type = ro, start = $0d86d0; 68 | summon_gfx: load = bank_0d, type = ro, start = $0d87f0; 69 | summon_tilemap: load = bank_0d, type = ro, start = $0df260; 70 | summon_frame: load = bank_0d, type = ro, start = $0df660; 71 | btlgfx_data1: load = bank_0d, type = ro, start = $0df800; 72 | battle_prop: load = bank_0e, type = ro, align = $0100; 73 | monster_prop: load = bank_0e, type = ro, align = $0100; 74 | rand_battle: load = bank_0e, type = ro, align = $0100; 75 | battle_anim_pal: load = bank_0e, type = ro, align = $0100; 76 | boss_tilemap: load = bank_0e, type = ro, align = $0100; 77 | ai_data: load = bank_0e, type = ro, start = $0ee000; # align = $0100; 78 | battle_dlg: load = bank_0e, type = ro, align = $0200; 79 | attack_name: load = bank_0f, type = ro, start = $0f8000; 80 | attack_prop: load = bank_0f, type = ro, start = $0f9070; 81 | attack_anim: load = bank_0f, type = ro, start = $0f9e10; 82 | item_misc: load = bank_0f, type = ro, start = $0fa450; 83 | char_name: load = bank_0f, type = ro, start = $0fa710; 84 | status_name: load = bank_0f, type = ro; 85 | char_prop: load = bank_0f, type = ro, start = $0fa900; 86 | item_desc: load = bank_0f, type = ro, start = $0fae00; 87 | battle_msg: load = bank_0f, type = ro, start = $0fb200; 88 | level_up: load = bank_0f, type = ro, start = $0fb500; 89 | menu_code2: load = bank_0f, type = ro, start = $0fc600; 90 | spell_list: load = bank_0f, type = ro, start = $0fc700; 91 | monster_gfx_prop: load = bank_0f, type = ro, start = $0fca00; 92 | battle_bg_pal: load = bank_0f, type = ro, start = $0fd200; 93 | battle_anim: load = bank_0f, type = ro, start = $0fd4e0; 94 | event_dlg1: load = bank_10, type = ro; 95 | map_dlg: load = bank_11, type = ro; 96 | event_data1: load = bank_12, type = ro; 97 | event_data2: load = bank_12, type = ro, start = $12f000; 98 | solar_system_sprite: load = bank_12, type = ro, start = $12f660; 99 | npc_data: load = bank_13, type = ro, align = $0100; 100 | shop_prop: load = bank_13, type = ro, align = $0100; 101 | event_dlg2: load = bank_13, type = ro, start = $13a500; 102 | cutscene_gfx: load = bank_13, type = ro, start = $13d200; 103 | cutscene_code: load = bank_13, type = ro, start = $13d610; 104 | btlgfx_data2: load = bank_13, type = ro, start = $13f900; 105 | battle_data: load = bank_13, type = ro, start = $13fd00; 106 | battle_code2: load = bank_13, type = ro, start = $13ff12; 107 | cutscene_code_ext: load = bank_13, type = ro, start = $13fff7; 108 | world_data: load = bank_14, type = ro; 109 | field_data: load = bank_14, type = ro, align = $0100; 110 | menu_code3: load = bank_14, type = ro, align = $0100; 111 | triggers: load = bank_15, type = ro, align = $80; 112 | map_title: load = bank_15, type = ro, start = $159880; 113 | map_prop: load = bank_15, type = ro, align = $80; 114 | field_code2: load = bank_15, type = ro, start = $15b000; 115 | solar_system_gfx: load = bank_15, type = ro, align = $0100; 116 | map_gfx4: load = bank_15, type = ro, start = $15dc00; 117 | world_tilemap: load = bank_16, type = ro; 118 | telescope_window: load = bank_16, type = ro, align = $80; 119 | btlgfx_data3: load = bank_16, type = ro, start = $16ed80; 120 | sub_tilemap: load = bank_17, type = ro; 121 | battle_char_gfx: load = bank_1a, type = ro; 122 | prologue_moon: load = bank_1a, type = ro, start = $1afcf0; 123 | map_sprite_gfx: load = bank_1b, type = ro; 124 | world_sprite_gfx: load = bank_1c, type = ro; 125 | battle_bg_gfx: load = bank_1c, type = ro, start = $1ca800; 126 | weapon_gfx: load = bank_1c, type = ro, start = $1cd900; 127 | monster_pal: load = bank_1c, type = ro; 128 | battle_char_pal: load = bank_1c, type = ro; 129 | world_gfx: load = bank_1d, type = ro; 130 | portrait_gfx: load = bank_1d, type = ro; 131 | map_gfx3: load = bank_1d, type = ro; 132 | map_gfx1: load = bank_1e, type = ro; 133 | menu_code4: load = bank_1e, type = ro; 134 | earth_moon: load = bank_1e, type = ro, align = $20; 135 | map_gfx2: load = bank_1f, type = ro; 136 | } 137 | -------------------------------------------------------------------------------- /field/Makefile: -------------------------------------------------------------------------------- 1 | 2 | NAME = field 3 | OBJ_DIR = obj 4 | TARGET = $(OBJ_DIR)/$(NAME)_$(VERSION_EXT).o 5 | 6 | SRC_MAIN = $(NAME).asm 7 | SRC_FILES = $(wildcard *.asm) 8 | INC_DIR = ../include 9 | INC_FILES = $(wildcard $(INC_DIR)/*.inc) 10 | DATA_FILES = $(wildcard data/*) 11 | GFX_FILES = $(wildcard gfx/*) 12 | TEXT_FILES = $(wildcard text/*) 13 | 14 | .PHONY: all clean 15 | 16 | # disable default suffix rules 17 | .SUFFIXES: 18 | 19 | all: $(TARGET) 20 | 21 | clean: 22 | $(RM) -r $(OBJ_DIR) data gfx text 23 | 24 | $(TARGET): $(SRC_FILES) $(INC_FILES) $(DATA_FILES) $(GFX_FILES) $(TEXT_FILES) 25 | @mkdir -p $(OBJ_DIR) 26 | $(ASM) $(ASMFLAGS) -I $(INC_DIR) -l $(@:o=lst) $(SRC_MAIN) -o $@ 27 | -------------------------------------------------------------------------------- /field/bg_anim.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: bg_anim.asm | 8 | ; | | 9 | ; | description: background animation routines | 10 | ; | | 11 | ; | created: 3/27/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ load bg animation graphics ] 15 | 16 | LoadBGAnimGfx: 17 | @cb01: lda $0fdd 18 | tax 19 | lda f:BGAnimTbl,x 20 | sta $3e 21 | stz $3d 22 | lsr $3e 23 | ror $3d 24 | lda $3e 25 | clc 26 | adc f:BGAnimTbl,x 27 | sta $3e 28 | ldx $3d 29 | ldy #0 30 | lda #$7f 31 | pha 32 | plb 33 | @cb23: lda f:MapAnimGfx,x 34 | sta $5000,y 35 | inx 36 | iny 37 | tya 38 | and #$0f 39 | bne @cb23 40 | @cb31: lda f:MapAnimGfx,x 41 | sta $5000,y 42 | inx 43 | iny 44 | lda #$00 45 | sta $5000,y 46 | iny 47 | tya 48 | and #$0f 49 | bne @cb31 50 | cpy #$0800 51 | bne @cb23 52 | lda #0 53 | pha 54 | plb 55 | rtl 56 | 57 | ; ------------------------------------------------------------------------------ 58 | 59 | ; bg animation graphics for each map tileset 60 | BGAnimTbl: 61 | @cb4f: .byte $00,$00,$00,$02,$03,$06,$07,$0a,$0a,$0a,$0a,$0a,$0d,$0d,$0d,$10 62 | 63 | ; ------------------------------------------------------------------------------ 64 | 65 | ; [ transfer bg animation graphics to vram ] 66 | 67 | TfrBGAnimGfx: 68 | @cb5f: lda $1700 69 | cmp #3 70 | bne @cb6c 71 | lda $7a 72 | and #$06 73 | beq @cb6d 74 | @cb6c: rtl 75 | @cb6d: lda $7a 76 | and #$18 77 | sta $12 78 | stz $13 79 | longa 80 | asl $12 81 | asl $12 82 | asl $12 83 | asl $12 84 | lda $12 85 | clc 86 | adc #$5000 87 | sta $12 88 | lda #0 89 | shorta 90 | lda #$80 91 | sta $2115 92 | stz $420b 93 | lda #$01 94 | sta $4300 95 | lda #$18 96 | sta $4301 97 | ldx #$1200 98 | stx $2116 99 | lda #$7f 100 | sta $4304 101 | ldx #$1200 102 | stx $2116 103 | ldy #4 104 | @cbb2: ldx $12 105 | stx $4302 106 | ldx #$0080 107 | stx $4305 108 | lda #$01 109 | sta $420b 110 | lda $13 111 | clc 112 | adc #$02 113 | sta $13 114 | dey 115 | bne @cbb2 116 | rtl 117 | 118 | ; ------------------------------------------------------------------------------ 119 | -------------------------------------------------------------------------------- /field/bg_gfx.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: bg_gfx.asm | 8 | ; | | 9 | ; | description: background graphics routines | 10 | ; | | 11 | ; | created: 3/26/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | .pushseg 15 | 16 | ; 1d/8000 17 | .segment "world_gfx" 18 | .include "gfx/world_bg_gfx.asm" 19 | 20 | .segment "map_gfx1" 21 | 22 | MapGfxPtrs: 23 | make_ptr_tbl_abs MapGfx, 16 24 | 25 | MapGfx_0002: 26 | .include .sprintf("gfx/cave1_gfx_%s.asm", LANG_SUFFIX) 27 | 28 | MapGfx_0004: 29 | .include .sprintf("gfx/town_ex_gfx_%s.asm", LANG_SUFFIX) 30 | 31 | MapGfx_0005: 32 | .include .sprintf("gfx/town_in_gfx_%s.asm", LANG_SUFFIX) 33 | 34 | MapGfx_0006: 35 | .include .sprintf("gfx/castle_in_gfx_%s.asm", LANG_SUFFIX) 36 | 37 | MapGfx_0007: 38 | .include .sprintf("gfx/crystal_gfx_%s.asm", LANG_SUFFIX) 39 | 40 | MapGfx_0001: 41 | MapGfx_000c: 42 | .include .sprintf("gfx/lunar_gfx_%s.asm", LANG_SUFFIX) 43 | 44 | .segment "map_gfx2" 45 | 46 | MapGfx_0009: 47 | .include .sprintf("gfx/underground_gfx_%s.asm", LANG_SUFFIX) 48 | 49 | MapGfx_0008: 50 | MapGfx_000a: 51 | MapGfx_000b: 52 | .include .sprintf("gfx/tower_gfx_%s.asm", LANG_SUFFIX) 53 | 54 | .include "gfx/map_anim_gfx.asm" 55 | 56 | MapGfx_000d: 57 | .include .sprintf("gfx/mountain_gfx_%s.asm", LANG_SUFFIX) 58 | 59 | MapGfx_000e: 60 | .include .sprintf("gfx/cave2_gfx_%s.asm", LANG_SUFFIX) 61 | 62 | .segment "map_gfx3" 63 | 64 | MapGfx_0003: 65 | .include .sprintf("gfx/castle_ex_gfx_%s.asm", LANG_SUFFIX) 66 | 67 | .segment "map_gfx4" 68 | 69 | MapGfx_0000: 70 | MapGfx_000f: 71 | .include .sprintf("gfx/ship_gfx_%s.asm", LANG_SUFFIX) 72 | 73 | .popseg 74 | 75 | ; ------------------------------------------------------------------------------ 76 | 77 | ; [ transfer 3bpp graphics to vram ] 78 | 79 | ; $49: source bank 80 | ; +$4a: source address 81 | ; +$4c: destination address (vram) 82 | ; +$4e: size 83 | 84 | Tfr3bppGfx: 85 | @b000: stz $420b 86 | lda #$80 87 | sta $2115 88 | lda #$08 89 | sta $4300 90 | lda #$19 91 | sta $4301 92 | stz $4304 93 | ldx $4c 94 | stx $2116 95 | stz $10 96 | ldx #$0610 97 | stx $4302 98 | ldx $4e 99 | stx $4305 100 | lda #$01 101 | sta $420b 102 | stz $420b 103 | lsr $4f ; size / 16 104 | ror $4e 105 | lsr $4f 106 | ror $4e 107 | lsr $4f 108 | ror $4e 109 | lsr $4f 110 | ror $4e 111 | lda #$18 112 | sta $4301 113 | ldx $4c 114 | stx $2116 115 | ldx $4a 116 | stx $4302 117 | lda $49 118 | sta $4304 119 | ldy #0 120 | @b056: stz $420b 121 | lda #$80 122 | sta $2115 123 | lda #$01 124 | sta $4300 125 | ldx #$0010 ; transfer 16 bytes 126 | stx $4305 127 | lda #$01 128 | sta $420b 129 | stz $420b 130 | stz $2115 131 | stz $4300 132 | ldx #8 ; transfer 8 bytes 133 | stx $4305 134 | lda #$01 135 | sta $420b 136 | iny 137 | cpy $4e 138 | bne @b056 139 | rtl 140 | 141 | ; ------------------------------------------------------------------------------ 142 | 143 | ; [ load map bg graphics ] 144 | 145 | LoadBGGfx: 146 | @b088: lda $0fdd 147 | beq @b091 ; airship and ship graphics are 4bpp 148 | cmp #$0f 149 | bne @b094 150 | @b091: jmp @b0be 151 | 152 | ; 3bpp 153 | @b094: jsl ClearBGGfx 154 | stz $420b 155 | lda $0fdd 156 | asl 157 | tax 158 | lda f:MapGfxPtrs,x ; map graphics pointers 159 | sta $4302 160 | lda f:MapGfxPtrs+1,x 161 | sta $4303 162 | lda $0fdd 163 | tax 164 | lda f:MapGfxBankTbl,x ; bank byte for map graphics pointers 165 | sta $4304 166 | jsl TfrBGGfx 167 | rtl 168 | 169 | ; 4bpp 170 | @b0be: ldx #0 171 | stx $47 172 | ldx #$2400 ; copy $2400 bytes -> vram $0000-$11ff 173 | stx $45 174 | lda #.bankbyte(MapGfx_0000) 175 | sta $3c 176 | lda f:MapGfxPtrs 177 | sta $3d 178 | lda f:MapGfxPtrs+1 179 | sta $3e 180 | lda #$80 181 | sta $2115 182 | stz hMDMAEN 183 | lda #$01 184 | sta $4300 185 | lda #$18 186 | sta $4301 187 | lda $3c 188 | sta $4304 189 | ldx $47 190 | stx $2116 191 | ldx $3d 192 | stx $4302 193 | ldx $45 194 | stx $4305 195 | lda #1 196 | sta hMDMAEN 197 | rtl 198 | 199 | ; ------------------------------------------------------------------------------ 200 | 201 | ; bank byte for map graphics pointers (at 1e/8000) 202 | MapGfxBankTbl: 203 | @b104: 204 | .repeat 16, i 205 | .bankbytes .ident(.sprintf("MapGfx_%04x", i)) 206 | .endrep 207 | 208 | ; ------------------------------------------------------------------------------ 209 | 210 | ; [ clear bg1/bg2 graphics in vram ] 211 | 212 | ; this only clears the high byte of each vram address to prepare to 213 | ; load 3bpp graphics 214 | 215 | ClearBGGfx: 216 | @b114: stz hMDMAEN 217 | lda #$80 218 | sta hVMAINC 219 | lda #$08 220 | sta $4300 221 | lda #$19 222 | sta $4301 223 | ldx #$0000 224 | stx hVMADDL 225 | stz $06 ; fixed value for clearing vram 226 | ldx #$0606 227 | stx $4302 228 | stz $4304 229 | ldx #$1800 ; clear $3000 bytes (vram $0000-$17ff) 230 | stx $4305 231 | lda #1 232 | sta hMDMAEN 233 | rtl 234 | 235 | ; ------------------------------------------------------------------------------ 236 | 237 | ; [ transfer bg1/bg2 graphics to vram (3bpp) ] 238 | 239 | ; this does not affect the bytes which determine the 4th bitplane, so 240 | ; they must be cleared first by calling ClearBGGfx 241 | 242 | TfrBGGfx: 243 | @b143: lda #$18 244 | sta $4301 245 | ldx #$0000 246 | stx hVMADDL 247 | ldy #0 248 | @b151: lda #$80 249 | sta hVMAINC 250 | lda #$01 251 | sta $4300 252 | ldx #$0010 253 | stx $4305 254 | lda #$01 255 | sta hMDMAEN 256 | stz hMDMAEN 257 | stz hVMAINC 258 | stz $4300 259 | ldx #8 260 | stx $4305 261 | lda #$01 262 | sta hMDMAEN 263 | iny 264 | cpy #$0180 ; copy 384 tiles (vram $0000-$17ff) 265 | bne @b151 266 | rtl 267 | 268 | ; ------------------------------------------------------------------------------ 269 | 270 | ; [ transfer world map graphics to vram ] 271 | 272 | TfrWorldGfx: 273 | @b181: lda #$80 274 | sta hVMAINC 275 | ldx #$0000 276 | stx hVMADDL 277 | lda $1700 278 | sta $3e 279 | stz $3d 280 | ldx $3d 281 | ldy #0 282 | @b198: lda f:WorldTilePal,x 283 | sta $0bdb,y 284 | inx 285 | iny 286 | cpy #$0100 287 | bne @b198 288 | lda $1700 289 | asl5 290 | sta $3e 291 | stz $3d 292 | ldx $3d 293 | ldy #0 294 | @b1b7: lda f:WorldBGGfx,x 295 | sta $08 296 | inx 297 | and #$0f 298 | clc 299 | adc $0bdb,y 300 | sta hVMDATAH 301 | lda $08 302 | lsr4 303 | clc 304 | adc $0bdb,y 305 | sta hVMDATAH 306 | txa 307 | and #$1f 308 | bne @b1b7 309 | iny 310 | cpy #$0100 311 | bne @b1b7 312 | rtl 313 | 314 | ; -------------------------------------------------------------------------- 315 | -------------------------------------------------------------------------------- /field/hardware.asm: -------------------------------------------------------------------------------- 1 | 2 | ; +----------------------------------------------------------------------------+ 3 | ; | | 4 | ; | FINAL FANTASY IV | 5 | ; | | 6 | ; +----------------------------------------------------------------------------+ 7 | ; | file: hardware.asm | 8 | ; | | 9 | ; | description: system hardware routines | 10 | ; | | 11 | ; | created: 3/27/2022 | 12 | ; +----------------------------------------------------------------------------+ 13 | 14 | ; [ init hardware registers ] 15 | 16 | InitHWRegs: 17 | @c8df: lda #$80 18 | sta hINIDISP 19 | lda #0 20 | sta hNMITIMEN 21 | lda #2 22 | sta hOBJSEL 23 | stz hOAMADDL 24 | stz hOAMADDH 25 | stz hMOSAIC 26 | lda #$19 27 | sta hBG1SC 28 | lda #$33 29 | sta hBG2SC 30 | lda #$29 31 | sta hBG3SC 32 | lda #$00 33 | sta hBG12NBA 34 | lda #$02 35 | sta hBG34NBA 36 | stz hBG3HOFS 37 | stz hBG3HOFS 38 | stz hBG3VOFS 39 | stz hBG3VOFS 40 | lda #$80 41 | sta hVMAINC 42 | stz hM7SEL 43 | stz hM7A 44 | lda #$04 45 | sta hM7A 46 | stz hM7B 47 | stz hM7B 48 | stz hM7C 49 | stz hM7C 50 | stz hM7D 51 | lda #$04 52 | sta hM7D 53 | lda #$80 54 | sta hM7X 55 | sta hM7X 56 | sta hM7Y 57 | sta hM7Y 58 | sta hCGADD 59 | lda #$33 60 | sta hW12SEL 61 | lda #$00 62 | sta hW34SEL 63 | lda #$f3 64 | sta hWOBJSEL 65 | lda #$01 66 | sta hWH0 67 | lda #$fe 68 | sta hWH1 69 | stz hWH2 70 | lda #$ff 71 | sta hWH3 72 | stz hWBGLOG 73 | stz hWOBJLOG 74 | lda #$17 75 | sta hTM 76 | lda #$11 77 | sta hTS 78 | lda #$17 79 | sta hTMW 80 | stz hTSW 81 | lda #$e0 82 | sta hCOLDATA 83 | stz hSETINI 84 | lda #$ff 85 | sta hWRIO 86 | stz hHTIMEL 87 | stz hHTIMEH 88 | stz hVTIMEL 89 | stz hVTIMEH 90 | stz hMDMAEN 91 | stz hHDMAEN 92 | rtl 93 | 94 | ; ------------------------------------------------------------------------------ 95 | 96 | ; [ clear ram ] 97 | 98 | ClearRAM: 99 | @c9aa: ldx #0 100 | @c9ad: lda $1900,x 101 | cmp f:RNGTbl,x ; rng table 102 | bne @c9bf 103 | inx 104 | cpx #$0100 105 | beq @c9cb 106 | jmp @c9ad 107 | ; rng table not loaded (hard reset) 108 | @c9bf: ldx #$1a00 109 | @c9c2: stz a:$0000,x ; clear menu ram ($1a00-$1a64) 110 | inx 111 | cpx #$1a65 112 | bne @c9c2 113 | @c9cb: ldx #0 114 | @c9ce: stz a:$0000,x ; clear battle and menu dp ($0000-$01ff) 115 | inx 116 | cpx #$0200 117 | bne @c9ce 118 | ldx #sprite_ram 119 | @c9da: stz a:$0000,x ; clear ram ($0300-$19ff) 120 | inx 121 | cpx #$0fff ; skip frame counter ($0fff) 122 | bne @c9da 123 | inx 124 | @c9e4: stz a:$0000,x 125 | inx 126 | cpx #$1a00 127 | bne @c9e4 128 | ldx #$1a65 129 | @c9f0: stz a:$0000,x ; clear more ram ($1a65-$1dff) 130 | inx 131 | cpx #$1e00 132 | bne @c9f0 133 | ldx #$2000 134 | lda #0 135 | @c9fe: sta $7e0000,x ; clear work ram ($7e2000-$7fffff) 136 | inx 137 | bne @c9fe 138 | @ca05: sta $7f0000,x 139 | inx 140 | bne @ca05 141 | ldx #0 142 | @ca0f: lda f:RNGTbl,x ; copy rng table to buffer 143 | sta $1900,x 144 | inx 145 | cpx #$0100 146 | bne @ca0f 147 | rtl 148 | 149 | ; ------------------------------------------------------------------------------ 150 | 151 | ; [ reset controller inputs ] 152 | 153 | ; clears flags that ensure that each input is only processed once 154 | 155 | ResetButtons: 156 | @ca1d: lda $02 ; A button 157 | and #JOY_A 158 | bne @ca25 159 | stz $54 160 | @ca25: lda $02 ; X button 161 | and #JOY_X 162 | bne @ca2d 163 | stz $50 164 | @ca2d: lda $02 ; top left button 165 | and #JOY_L 166 | bne @ca35 167 | stz $52 168 | @ca35: lda $02 ; top right button 169 | and #JOY_R 170 | bne @ca3d 171 | stz $53 172 | @ca3d: lda $03 ; B button 173 | and #JOY_B 174 | bne @ca45 175 | stz $55 176 | @ca45: lda $03 ; Y button 177 | and #JOY_Y 178 | bne @ca4d 179 | stz $51 180 | @ca4d: lda $03 ; select button 181 | and #JOY_SELECT 182 | bne @ca55 183 | stz $56 184 | @ca55: lda $03 ; start button 185 | and #JOY_START 186 | bne @ca5d 187 | stz $57 188 | @ca5d: rtl 189 | 190 | ; ------------------------------------------------------------------------------ 191 | 192 | ; [ transfer palettes to ppu ] 193 | 194 | TfrPal: 195 | @ca5e: stz hMDMAEN 196 | stz hCGADD 197 | lda #$02 198 | sta hDMAP0 199 | lda #=0.8" 23 | } 24 | }, 25 | "node_modules/has-symbols": { 26 | "version": "1.0.3", 27 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 28 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 29 | "engines": { 30 | "node": ">= 0.4" 31 | }, 32 | "funding": { 33 | "url": "https://github.com/sponsors/ljharb" 34 | } 35 | }, 36 | "node_modules/has-tostringtag": { 37 | "version": "1.0.0", 38 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 39 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", 40 | "dependencies": { 41 | "has-symbols": "^1.0.2" 42 | }, 43 | "engines": { 44 | "node": ">= 0.4" 45 | }, 46 | "funding": { 47 | "url": "https://github.com/sponsors/ljharb" 48 | } 49 | }, 50 | "node_modules/is-number": { 51 | "version": "7.0.0", 52 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 53 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 54 | "engines": { 55 | "node": ">=0.12.0" 56 | } 57 | }, 58 | "node_modules/is-string": { 59 | "version": "1.0.7", 60 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 61 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", 62 | "dependencies": { 63 | "has-tostringtag": "^1.0.0" 64 | }, 65 | "engines": { 66 | "node": ">= 0.4" 67 | }, 68 | "funding": { 69 | "url": "https://github.com/sponsors/ljharb" 70 | } 71 | }, 72 | "node_modules/isarray": { 73 | "version": "2.0.5", 74 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 75 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" 76 | } 77 | }, 78 | "dependencies": { 79 | "crc-32": { 80 | "version": "1.2.2", 81 | "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", 82 | "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" 83 | }, 84 | "has-symbols": { 85 | "version": "1.0.3", 86 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 87 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 88 | }, 89 | "has-tostringtag": { 90 | "version": "1.0.0", 91 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 92 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", 93 | "requires": { 94 | "has-symbols": "^1.0.2" 95 | } 96 | }, 97 | "is-number": { 98 | "version": "7.0.0", 99 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 100 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 101 | }, 102 | "is-string": { 103 | "version": "1.0.7", 104 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 105 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", 106 | "requires": { 107 | "has-tostringtag": "^1.0.0" 108 | } 109 | }, 110 | "isarray": { 111 | "version": "2.0.5", 112 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 113 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "crc-32": "^1.2.2", 4 | "is-number": "^7.0.0", 5 | "is-string": "^1.0.7", 6 | "isarray": "^2.0.5" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sound/Makefile: -------------------------------------------------------------------------------- 1 | 2 | NAME = sound 3 | OBJ_DIR = obj 4 | TARGET = $(OBJ_DIR)/$(NAME)_$(VERSION_EXT).o 5 | 6 | SRC_MAIN = $(NAME).asm 7 | SRC_FILES = $(wildcard *.asm) 8 | INC_DIR = ../include 9 | INC_FILES = $(wildcard $(INC_DIR)/*.inc) 10 | DATA_FILES = $(wildcard data/*) 11 | GFX_FILES = $(wildcard gfx/*) 12 | TEXT_FILES = $(wildcard text/*) 13 | 14 | .PHONY: all clean 15 | 16 | # disable default suffix rules 17 | .SUFFIXES: 18 | 19 | all: $(TARGET) 20 | 21 | clean: 22 | $(RM) -r $(OBJ_DIR) data gfx text 23 | 24 | $(TARGET): $(SRC_FILES) $(INC_FILES) $(DATA_FILES) $(GFX_FILES) $(TEXT_FILES) 25 | @mkdir -p $(OBJ_DIR) 26 | $(ASM) $(ASMFLAGS) -I $(INC_DIR) -l $(@:o=lst) $(SRC_MAIN) -o $@ 27 | -------------------------------------------------------------------------------- /sound/sound_data.asm: -------------------------------------------------------------------------------- 1 | 2 | .segment "sound_data" 3 | 4 | ; 04/c000 5 | SoundData: 6 | 7 | SongScriptPtrsOffset: 8 | .faraddr SongScriptPtrs - SoundData 9 | 10 | BRRSamplePtrsOffset: 11 | .faraddr BRRSamplePtrs - SoundData 12 | 13 | SongSamplesOffset: 14 | .faraddr SongSamples - SoundData 15 | 16 | SampleLoopStartOffset: 17 | .faraddr SampleLoopStart - SoundData 18 | 19 | SampleFreqMultOffset: 20 | .faraddr SampleFreqMult - SoundData 21 | 22 | ; 04/c00f 23 | .include "data/song_sample.asm" 24 | 25 | ; 04/c8cf 26 | .include "data/sample_loop_start.asm" 27 | 28 | ; 04/c92b 29 | .include "data/sample_freq_mult.asm" 30 | 31 | ; 04/c942 32 | BRRSamplePtrs: 33 | .faraddr 0 34 | make_ptr_tbl_far BRRSample, 22, SoundData 35 | .faraddr 0 36 | 37 | ; 04/c98a 38 | .include "data/brr_sample.asm" 39 | 40 | ; 06/f21d 41 | SongScriptPtrs: 42 | make_ptr_tbl_far SongScript, 70, SoundData 43 | 44 | ; 06/f2ef 45 | .include "data/song_script.asm" 46 | -------------------------------------------------------------------------------- /tools/calc-checksum.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const hexString = require('./romtools/hex-string'); 5 | 6 | function calcSum(data) { 7 | let sum = 0; 8 | for (let i = 0; i < data.length; i++) sum += data[i]; 9 | return sum & 0xFFFF; 10 | } 11 | 12 | function mirrorSum(data, mask) { 13 | mask = mask || 0x800000; 14 | while (!(data.length & mask)) mask >>= 1; 15 | 16 | const part1 = calcSum(data.slice(0, mask)); 17 | let part2 = 0; 18 | 19 | let nextLength = data.length - mask; 20 | if (nextLength) { 21 | part2 = mirrorSum(data.slice(mask), nextLength, mask >> 1); 22 | 23 | while (nextLength < mask) { 24 | nextLength += nextLength; 25 | part2 += part2; 26 | } 27 | } 28 | return (part1 + part2) & 0xFFFF; 29 | } 30 | 31 | // load the ROM file 32 | const romPath = process.argv[2]; 33 | const romBuffer = fs.readFileSync(romPath); 34 | const romData = new Uint8Array(romBuffer); 35 | 36 | // calculate the SNES checksum 37 | const checksum = mirrorSum(romData); 38 | const checksumInverse = checksum ^ 0xFFFF; 39 | 40 | console.log(`SNES Checksum: ${hexString(checksum, 4)}`); 41 | 42 | // set the checksum in the SNES header and write to the ROM file 43 | romData[0x7FDC] = checksumInverse & 0xFF; 44 | romData[0x7FDD] = checksumInverse >> 8; 45 | romData[0x7FDE] = checksum & 0xFF; 46 | romData[0x7FDF] = checksum >> 8; 47 | 48 | fs.writeFileSync(romPath, romData); 49 | 50 | process.exit(0); 51 | -------------------------------------------------------------------------------- /tools/decode-ff4.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const CRC32 = require('crc-32'); 5 | const ROMDataCodec = require('./romtools/data-codec'); 6 | const ROMDecoder = require('./romtools/rom-decoder'); 7 | const ROMRange = require('./romtools/range'); 8 | const hexString = require('./romtools/hex-string'); 9 | 10 | const romInfoListFF4 = { 11 | 0x21027C5D: { 12 | name: 'Final Fantasy IV 1.0 (J)', 13 | ripPath: 'vanilla/ff4-jp-rip.json', 14 | dataPath: 'ff4-jp-data.json' 15 | }, 16 | 0xCAA15E97: { 17 | name: 'Final Fantasy IV 1.1 (J)', 18 | ripPath: 'vanilla/ff4-jp-rip.json', 19 | dataPath: 'ff4-jp-data.json' 20 | }, 21 | 0x65D0A825: { 22 | name: 'Final Fantasy II 1.0 (U)', 23 | ripPath: 'vanilla/ff4-en-rip.json', 24 | dataPath: 'ff4-en-data.json' 25 | }, 26 | 0x23084FCD: { 27 | name: 'Final Fantasy II 1.1 (U)', 28 | ripPath: 'vanilla/ff4-en-rip.json', 29 | dataPath: 'ff4-en-data.json' 30 | } 31 | } 32 | 33 | function ripMonsterGfx(romObj, ripDefinition, decoder) { 34 | // determine the graphics index for each monster 35 | const is3bppList = []; 36 | const gfxOffsetList = []; 37 | for (let bank = 0; bank < 5; bank++) { 38 | const gfxPath = `monsterGraphics${bank + 1}`; 39 | const gfxRange = new ROMRange(ripDefinition.assembly[gfxPath].range); 40 | const gfxOffset = decoder.memoryMap.mapAddress(gfxRange.begin & 0xFF0000); 41 | gfxOffsetList.push(gfxOffset); 42 | const gfxCount = ripDefinition.assembly[gfxPath].arrayLength; 43 | is3bppList.push(new Array(gfxCount).fill(true)); // default is 3bpp 44 | } 45 | const gfxPropArray = romObj.obj.monsterGraphicsProperties; 46 | const gfxPtrs = []; 47 | for (let gfxProp of gfxPropArray) { 48 | 49 | // get the graphics pointer and bank 50 | const bank = gfxProp.graphicsBank; 51 | const ptr = gfxProp[`graphicsPointer`] * 8; 52 | 53 | // set all of the graphics pointers to zero 54 | gfxProp.graphicsIndex1 = 0; 55 | gfxProp.graphicsIndex2 = 0; 56 | gfxProp.graphicsIndex3 = 0; 57 | gfxProp.graphicsIndex4 = 0; 58 | gfxProp.graphicsIndex5 = 0; 59 | 60 | // get the unmapped graphics pointer 61 | const gfxOffset = gfxOffsetList[bank]; 62 | const unmappedPtr = decoder.memoryMap.unmapAddress(gfxOffset + ptr); 63 | 64 | // find the pointer in the list of item ranges for this bank 65 | const gfxPath = `monsterGraphics${bank + 1}`; 66 | const rangeList = ripDefinition.assembly[gfxPath].itemRanges; 67 | for (let r = 0; r < rangeList.length; r++) { 68 | const gfxRange = new ROMRange(rangeList[r]); 69 | if (gfxRange.begin !== unmappedPtr) continue; 70 | gfxProp[`graphicsIndex${bank + 1}`] = r; 71 | is3bppList[bank][r] = gfxProp.is3bpp; 72 | break; 73 | } 74 | } 75 | 76 | // enable the graphics index properties in the definition 77 | const gfxPropDef = romObj.assembly.monsterGraphicsProperties.assembly.assembly; 78 | gfxPropDef.graphicsIndex1.invalid = 'obj.graphicsBank !== 0'; 79 | gfxPropDef.graphicsIndex2.invalid = 'obj.graphicsBank !== 1'; 80 | gfxPropDef.graphicsIndex3.invalid = 'obj.graphicsBank !== 2'; 81 | gfxPropDef.graphicsIndex4.invalid = 'obj.graphicsBank !== 3'; 82 | gfxPropDef.graphicsIndex5.invalid = 'obj.graphicsBank !== 4'; 83 | 84 | // decode 3bpp and 4bpp monster graphics 85 | const dataCodec3bpp = new ROMDataCodec('snes3bpp'); 86 | const dataCodec4bpp = new ROMDataCodec('snes4bpp'); 87 | for (let bank = 0; bank < 5; bank++) { 88 | const gfxPath = `monsterGraphics${bank + 1}`; 89 | const gfxCount = ripDefinition.assembly[gfxPath].arrayLength; 90 | for (let r = 0; r < gfxCount; r++) { 91 | const rawGfxBase64 = Buffer.from(romObj.obj[gfxPath][r], 'base64'); 92 | const rawGfx = new Uint8Array(rawGfxBase64); 93 | let decodedData; 94 | if (is3bppList[bank][r]) { 95 | decodedGfx = dataCodec3bpp.decode(rawGfx); 96 | } else { 97 | decodedGfx = dataCodec4bpp.decode(rawGfx); 98 | } 99 | romObj.obj[gfxPath][r] = Buffer.from(decodedGfx).toString('base64'); 100 | } 101 | } 102 | } 103 | 104 | function ripLevelProg(romObj, ripDefinition, decoder) { 105 | // decode character level progression 106 | const rawLevelArray = romObj.obj.LevelUpProp; 107 | const levelUpPropLow = ripDefinition.obj.LevelUpPropLow; 108 | const levelUpPropHigh = ripDefinition.obj.LevelUpPropHigh; 109 | const levelUpDefLow = ripDefinition.assembly.LevelUpPropLow.assembly; 110 | const levelUpDefHigh = ripDefinition.assembly.LevelUpPropHigh.assembly; 111 | let decodedLowLevelArray = []; 112 | let decodedHighLevelArray = []; 113 | for (let levelDataBase64 of rawLevelArray) { 114 | const rawLevelData = new Uint8Array(Buffer.from(levelDataBase64, 'base64')); 115 | 116 | // decode low level data and high level data separately 117 | const lowLevelData = rawLevelData.slice(0, -8); 118 | const lowLevelObj = decoder.decodeArray(lowLevelData, levelUpDefLow); 119 | decodedLowLevelArray.push(lowLevelObj); 120 | 121 | const highLevelData = rawLevelData.slice(-8); 122 | const highLevelObj = decoder.decodeArray(highLevelData, levelUpDefHigh); 123 | decodedHighLevelArray.push(highLevelObj); 124 | } 125 | romObj.obj.LevelUpPropLow = decodedLowLevelArray; 126 | romObj.obj.LevelUpPropHigh = decodedHighLevelArray; 127 | } 128 | 129 | // search the vanilla directory for valid ROM files 130 | const files = fs.readdirSync('vanilla'); 131 | 132 | let foundOneROM = false; 133 | for (let filename of files) { 134 | const filePath = `vanilla/${filename}`; 135 | if (fs.statSync(filePath).isDirectory()) continue; 136 | 137 | const fileBuf = fs.readFileSync(filePath); 138 | const crc32 = CRC32.buf(fileBuf) >>> 0; 139 | const romInfo = romInfoListFF4[crc32]; 140 | if (!romInfo) continue; 141 | 142 | console.log(`Found ROM: ${romInfo.name}`); 143 | console.log(`File: ${filePath}`); 144 | 145 | // load the definition file 146 | const ripDefinitionFile = fs.readFileSync(romInfo.ripPath); 147 | const ripDefinition = JSON.parse(ripDefinitionFile); 148 | 149 | const decoder = new ROMDecoder(ripDefinition); 150 | 151 | // load the ROM file 152 | const romData = new Uint8Array(fs.readFileSync(filePath)); 153 | const romObj = decoder.decodeROM(romData, ripDefinition); 154 | romObj.crc32 = hexString(crc32, 8); 155 | 156 | ripMonsterGfx(romObj, ripDefinition, decoder); 157 | ripLevelProg(romObj, ripDefinition, decoder); 158 | 159 | fs.writeFileSync(romInfo.dataPath, JSON.stringify(romObj, null, 2)); 160 | foundOneROM = true; 161 | } 162 | 163 | if (!foundOneROM) { 164 | console.log('No valid ROM files found!\nPlease copy your valid FF4 ROM ' + 165 | 'file(s) into the "vanilla" directory.\nIf your ROM has a header, ' + 166 | 'please remove it first.'); 167 | } 168 | 169 | process.exit(0); 170 | -------------------------------------------------------------------------------- /tools/encode-ff4.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const ROMEncoder = require('./romtools/rom-encoder'); 5 | const ROMMemoryMap = require('./romtools/memory-map'); 6 | const ROMRange = require('./romtools/range'); 7 | const ROMDataCodec = require('./romtools/data-codec'); 8 | 9 | function encodeLevelProg(obj, definition) { 10 | // decode low level data and high level data separately 11 | const lowLevelObj = obj.slice(0, -8); 12 | const lowLevelData = encoder.encodeArray(lowLevelObj, definition.lowLevel); 13 | 14 | const highLevelObj = obj.slice(-8); 15 | const highLevelData = encoder.encodeArray(highLevelObj, definition.highLevel); 16 | 17 | const combinedData = new Uint8Array(lowLevelData.length + 8); 18 | combinedData.set(lowLevelData); 19 | combinedData.set(highLevelData, lowLevelData.length); 20 | return Buffer.from(combinedData).toString('base64'); 21 | } 22 | 23 | function encodeMonsterGfx(definition) { 24 | 25 | let isDirty = false; 26 | isDirty |= definition.assembly.monsterGraphicsProperties.isDirty; 27 | isDirty |= definition.assembly.monsterGraphics1.isDirty; 28 | isDirty |= definition.assembly.monsterGraphics1.isDirty; 29 | isDirty |= definition.assembly.monsterGraphics1.isDirty; 30 | isDirty |= definition.assembly.monsterGraphics1.isDirty; 31 | isDirty |= definition.assembly.monsterGraphics1.isDirty; 32 | if (!isDirty) return; 33 | 34 | const memoryMap = new ROMMemoryMap(definition.mode); 35 | 36 | // need to choose 3bpp or 4bpp for each monster graphics 37 | const is3bppList = []; 38 | const gfxOffsetList = []; 39 | for (let bank = 0; bank < 5; bank++) { 40 | const gfxPath = `monsterGraphics${bank + 1}`; 41 | const gfxRange = new ROMRange(definition.assembly[gfxPath].range); 42 | const gfxOffset = memoryMap.mapAddress(gfxRange.begin & 0xFF0000); 43 | gfxOffsetList.push(gfxOffset); 44 | const gfxCount = definition.assembly[gfxPath].arrayLength; 45 | is3bppList.push(new Array(gfxCount).fill(true)); // default is 3bpp 46 | } 47 | 48 | // set the graphics pointer for each monster 49 | const gfxPropArray = definition.obj.monsterGraphicsProperties; 50 | for (let gfxProp of gfxPropArray) { 51 | 52 | // get the graphics pointer and bank 53 | const bank = gfxProp.graphicsBank; 54 | const gfxIndex = gfxProp[`graphicsIndex${bank + 1}`]; 55 | 56 | // get the unmapped graphics pointer 57 | const gfxPath = `monsterGraphics${bank + 1}`; 58 | const gfxRangeString = definition.assembly[gfxPath].itemRanges[gfxIndex]; 59 | const gfxRange = new ROMRange(gfxRangeString); 60 | const mappedPtr = memoryMap.mapAddress(gfxRange.begin); 61 | gfxProp.graphicsPointer = (mappedPtr - gfxOffsetList[bank]) >> 3; 62 | 63 | is3bppList[bank][gfxIndex] = gfxProp.is3bpp; 64 | } 65 | 66 | // don't assembly the graphics index 67 | const gfxPropDef = definition.assembly.monsterGraphicsProperties.assembly.assembly; 68 | gfxPropDef.graphicsIndex1.invalid = true; 69 | gfxPropDef.graphicsIndex2.invalid = true; 70 | gfxPropDef.graphicsIndex3.invalid = true; 71 | gfxPropDef.graphicsIndex4.invalid = true; 72 | gfxPropDef.graphicsIndex5.invalid = true; 73 | 74 | // encode 3bpp and 4bpp monster graphics 75 | const dataCodec3bpp = new ROMDataCodec('snes3bpp'); 76 | const dataCodec4bpp = new ROMDataCodec('snes4bpp'); 77 | for (let bank = 0; bank < 5; bank++) { 78 | const gfxPath = `monsterGraphics${bank + 1}`; 79 | const gfxCount = definition.assembly[gfxPath].arrayLength; 80 | for (let r = 0; r < gfxCount; r++) { 81 | const rawGfxBase64 = Buffer.from(definition.obj[gfxPath][r], 'base64'); 82 | const rawGfx = new Uint8Array(rawGfxBase64); 83 | let decodedData; 84 | if (is3bppList[bank][r]) { 85 | decodedGfx = dataCodec3bpp.encode(rawGfx); 86 | } else { 87 | decodedGfx = dataCodec4bpp.encode(rawGfx); 88 | } 89 | definition.obj[gfxPath][r] = Buffer.from(decodedGfx).toString('base64'); 90 | } 91 | } 92 | } 93 | 94 | function encodeTreasureIndex(definition) { 95 | let isDirty = definition.assembly.mapTriggers1.isDirty; 96 | isDirty |= definition.assembly.mapTriggers2.isDirty; 97 | if (!isDirty) return 98 | 99 | let t = 0; 100 | for (let m = 0; m < definition.obj.mapProperties.length; m++) { 101 | 102 | // reset to zero for underground/moon treasures 103 | if (m === 256) t = 0; 104 | definition.obj.mapProperties[m].treasure = t; 105 | 106 | let triggerArray; 107 | if (m < 256) { 108 | triggerArray = definition.obj.mapTriggers1[m]; 109 | } else { 110 | triggerArray = definition.obj.mapTriggers2[m - 256]; 111 | } 112 | for (const trigger of triggerArray) { 113 | if (trigger.map === 0xFE) t++; 114 | } 115 | 116 | if (t > 256) { 117 | throw 'Treasure overflow: data contains more than 256 treasures'; 118 | } 119 | } 120 | } 121 | 122 | // load the data file 123 | const dataPath = process.argv[2]; 124 | const romDefinitionFile = fs.readFileSync(dataPath); 125 | const romDefinition = JSON.parse(romDefinitionFile); 126 | 127 | const encoder = new ROMEncoder(romDefinition); 128 | 129 | try { 130 | encodeMonsterGfx(romDefinition); 131 | encodeTreasureIndex(romDefinition); 132 | 133 | // encode the level progression data 134 | const levelUpPropLow = romDefinition.obj.LevelUpPropLow; 135 | const levelUpPropHigh = romDefinition.obj.LevelUpPropHigh; 136 | const levelUpDefLow = romDefinition.assembly.LevelUpPropLow.assembly; 137 | const levelUpDefHigh = romDefinition.assembly.LevelUpPropHigh.assembly; 138 | 139 | if (levelUpDefLow.isDirty || levelUpDefHigh.isDirty) { 140 | let encodedLevelArray = []; 141 | for (let i = 0; i < levelUpPropLow.length; i++) { 142 | // encode low level data and high level data separately 143 | const lowLevelObj = levelUpPropLow[i]; 144 | const lowLevelData = encoder.encodeArray(lowLevelObj, levelUpDefLow); 145 | 146 | const highLevelObj = levelUpPropHigh[i]; 147 | const highLevelData = encoder.encodeArray(highLevelObj, levelUpDefHigh); 148 | 149 | const combinedData = new Uint8Array(lowLevelData.length + 8); 150 | combinedData.set(lowLevelData); 151 | combinedData.set(highLevelData, lowLevelData.length); 152 | 153 | const encodedLevelData = Buffer.from(combinedData).toString('base64'); 154 | encodedLevelArray.push(encodedLevelData); 155 | 156 | } 157 | romDefinition.obj.LevelUpProp = encodedLevelArray; 158 | romDefinition.assembly.LevelUpProp.isDirty = true; 159 | } 160 | 161 | } catch(e) { 162 | console.log(e); 163 | process.exit(1); 164 | } 165 | 166 | encoder.encodeROM(romDefinition); 167 | 168 | process.exit(0); 169 | -------------------------------------------------------------------------------- /tools/encode_menu_text.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const isString = require('is-string'); 5 | const isNumber = require('is-number'); 6 | const isArray = require('isarray'); 7 | const hexString = require('./romtools/hex-string'); 8 | const ROMTextCodec = require('./romtools/text-codec'); 9 | 10 | const langSuffix = process.argv[2] || 'jp'; 11 | 12 | const menuStrPath = `../include/menu_text_${langSuffix}.json`; 13 | const outputPath = `text/menu_text_${langSuffix}.inc`; 14 | const definitionPath = `../vanilla/ff4-${langSuffix}-rip.json`; 15 | 16 | const menuStrFile = fs.readFileSync(menuStrPath); 17 | const menuStr = JSON.parse(menuStrFile); 18 | 19 | const romDefinitionFile = fs.readFileSync(definitionPath); 20 | const romDefinition = JSON.parse(romDefinitionFile); 21 | 22 | const encodingDefinition = romDefinition.textEncoding.menuText; 23 | const charTables = romDefinition.charTable; 24 | const codec = new ROMTextCodec(encodingDefinition, charTables); 25 | 26 | let bigString = ''; 27 | for (let label in menuStr) { 28 | const str = menuStr[label]; 29 | const strData = codec.encode(str); 30 | bigString += `.define ${label} `; 31 | for (let i = 0; i < strData.length; i++) { 32 | if (i) bigString += ','; 33 | bigString += hexString(strData[i], 2, '$'); 34 | } 35 | bigString += '\n'; 36 | } 37 | 38 | fs.writeFileSync(outputPath, bigString); 39 | 40 | process.exit(0); 41 | -------------------------------------------------------------------------------- /tools/romtools/data-manager.js: -------------------------------------------------------------------------------- 1 | // data-manager.js 2 | 3 | const ROMTextCodec = require('./text-codec'); 4 | const ROMMemoryMap = require('./memory-map'); 5 | 6 | class ROMDataManager { 7 | constructor(definition) { 8 | 9 | this.definition = definition; 10 | 11 | // create the memory mapper 12 | const mapMode = definition.mode || ROMMemoryMap.MapMode.none; 13 | this.memoryMap = new ROMMemoryMap(mapMode); 14 | 15 | // create text codecs 16 | this.textCodec = {}; 17 | for (let key in definition.textEncoding) { 18 | const encodingDef = definition.textEncoding[key]; 19 | this.textCodec[key] = new ROMTextCodec(encodingDef, definition.charTable); 20 | } 21 | 22 | // create string tables 23 | 24 | } 25 | 26 | getDefinition(key) { 27 | return this.definition.assembly[key]; 28 | } 29 | 30 | getObject(key) { 31 | return this.definition.obj[key]; 32 | } 33 | 34 | } 35 | 36 | module.exports = ROMDataManager; 37 | -------------------------------------------------------------------------------- /tools/romtools/hex-string.js: -------------------------------------------------------------------------------- 1 | 2 | const isNumber = require('is-number'); 3 | 4 | function hexString(num, pad, prefix) { 5 | if (prefix === undefined) prefix = '0x'; 6 | if (num < 0) num = 0xFFFFFFFF + num + 1; 7 | let hex = num.toString(16).toUpperCase(); 8 | if (isNumber(pad)) { 9 | hex = hex.padStart(pad, '0'); 10 | } else if (num < 0x0100) { 11 | hex = hex.padStart(2, '0'); 12 | } else if (num < 0x010000) { 13 | hex = hex.padStart(4, '0'); 14 | } else if (num < 0x01000000) { 15 | hex = hex.padStart(6, '0'); 16 | } else { 17 | hex = hex.padStart(8, '0'); 18 | } 19 | return (prefix + hex); 20 | } 21 | 22 | module.exports = hexString; 23 | -------------------------------------------------------------------------------- /tools/romtools/memory-map.js: -------------------------------------------------------------------------------- 1 | 2 | const ROMRange = require('./range'); 3 | 4 | class ROMMemoryMap { 5 | constructor(mode) { 6 | this.mode = mode; 7 | } 8 | 9 | bankSize() { 10 | switch (this.mode) { 11 | case ROMMemoryMap.MapMode.mmc1: return 0x4000; 12 | case ROMMemoryMap.MapMode.mmc3: return 0x2000; 13 | case ROMMemoryMap.MapMode.loROM: return 0x8000; 14 | case ROMMemoryMap.MapMode.hiROM: return 0x10000; 15 | case ROMMemoryMap.MapMode.x16: return 0x2000; 16 | case ROMMemoryMap.MapMode.wsc: return 0x10000; 17 | default: return 0x10000; 18 | } 19 | } 20 | 21 | // convert CPU address to ROM file address 22 | mapAddress(address) { 23 | switch (this.mode) { 24 | case ROMMemoryMap.MapMode.mmc1: 25 | var bank = address & 0xFF0000; 26 | return (bank >> 2) + (address & 0x3FFF) + 0x10; 27 | 28 | case ROMMemoryMap.MapMode.mmc3: 29 | var bank = address & 0xFF0000; 30 | return (bank >> 3) + (address & 0x1FFF) + 0x10; 31 | 32 | case ROMMemoryMap.MapMode.loROM: 33 | var bank = address & 0xFF0000; 34 | return (bank >> 1) + (address & 0x7FFF); 35 | 36 | case ROMMemoryMap.MapMode.hiROM: 37 | if (address >= 0xC00000) { 38 | return address - 0xC00000; 39 | } else if (address >= 0x800000) { 40 | return address - 0x800000; 41 | } else { 42 | return address; 43 | } 44 | 45 | case ROMMemoryMap.MapMode.gba: 46 | if (address >= 0x08000000) { 47 | return address - 0x08000000; 48 | } else { 49 | return address; 50 | } 51 | 52 | case ROMMemoryMap.MapMode.x16: 53 | var bank = address & 0xFF0000; 54 | bank -= 0x010000; 55 | return (bank >> 3) + (address & 0x1FFF) + 2; 56 | 57 | case ROMMemoryMap.MapMode.wsc: 58 | if (address < 0x10000000) return address; // raw address 59 | var bank = (address >> 12) & 0xFF0000; 60 | return bank + (address & 0xFFFF); 61 | 62 | case ROMMemoryMap.MapMode.None: 63 | default: 64 | return address; 65 | } 66 | } 67 | 68 | // convert ROM file address to CPU address 69 | unmapAddress(address) { 70 | switch (this.mode) { 71 | case ROMMemoryMap.MapMode.mmc1: 72 | address -= 0x10; // iNES header 73 | var bank = (address << 2) & 0xFF0000; 74 | return bank | (address & 0x3FFF) | 0x8000; 75 | 76 | case ROMMemoryMap.MapMode.mmc3: 77 | address -= 0x10; // iNES header 78 | var bank = (address << 3) & 0xFF0000; 79 | if (bank & 0x010000) { 80 | return bank | (address & 0x1FFF) | 0xA000; 81 | } else { 82 | return bank | (address & 0x1FFF) | 0x8000; 83 | } 84 | 85 | case ROMMemoryMap.MapMode.loROM: 86 | var bank = (address << 1) & 0xFF0000; 87 | return bank | (address & 0x7FFF) | 0x8000; 88 | 89 | case ROMMemoryMap.MapMode.hiROM: 90 | return address + 0xC00000; 91 | 92 | case ROMMemoryMap.MapMode.gba: 93 | return address | 0x08000000; 94 | 95 | case ROMMemoryMap.MapMode.x16: 96 | address -= 2; // header 97 | var bank = (address << 3) & 0xFF0000; 98 | bank += 0x010000; 99 | return bank | (address & 0x1FFF) | 0xA000; 100 | 101 | case ROMMemoryMap.MapMode.wsc: 102 | if (address > 0x0F0000) return address; // raw address 103 | var bank = (address & 0x0F0000) << 12; 104 | return bank | (address & 0xFFFF); 105 | 106 | case ROMMemoryMap.MapMode.None: 107 | default: 108 | return address; 109 | } 110 | } 111 | 112 | // convert CPU address (exclusive) to ROM file address (inclusive) 113 | mapRange(range) { 114 | const begin = this.mapAddress(range.begin); 115 | const end = this.mapAddress(range.end); 116 | return new ROMRange(begin, end); 117 | } 118 | 119 | unmapRange(range) { 120 | const begin = this.unmapAddress(range.begin); 121 | const end = this.unmapAddress(range.end); 122 | return new ROMRange(begin, end); 123 | } 124 | } 125 | 126 | ROMMemoryMap.MapMode = { 127 | none: "none", 128 | mmc1: "mmc1", 129 | mmc3: "mmc3", 130 | loROM: "loROM", 131 | hiROM: "hiROM", 132 | gba: "gba", 133 | x16: "x16", 134 | psx: "psx", 135 | wsc: "wsc" 136 | } 137 | 138 | module.exports = ROMMemoryMap; 139 | -------------------------------------------------------------------------------- /tools/romtools/range.js: -------------------------------------------------------------------------------- 1 | 2 | const hexString = require('./hex-string'); 3 | const isNumber = require('is-number'); 4 | const isString = require('is-string'); 5 | 6 | class ROMRange { 7 | 8 | constructor(begin, end) { 9 | 10 | // beginning of range (must be positive) 11 | this.begin = 0; 12 | // end of range (included in range) 13 | this.end = -1; 14 | 15 | if (isNumber(begin)) { 16 | // assume single value for now 17 | this.begin = begin; 18 | this.end = begin; 19 | } else if (isString(begin)) { 20 | const bounds = begin.split('-'); 21 | if (bounds.length == 1) { 22 | // single value 23 | this.begin = Number(begin); 24 | if (!isNumber(this.begin)) { 25 | console.log(`Invalid range: ${begin}`); 26 | this.begin = 0; 27 | } 28 | this.end = this.begin; 29 | } else if (bounds.length == 2) { 30 | // multiple values 31 | this.begin = Number(bounds[0]); 32 | this.end = Number(bounds[1]); 33 | if (!isNumber(this.begin) || !isNumber(this.end)) { 34 | console.log(`Invalid range: ${begin}`); 35 | this.begin = 0; 36 | this.end = -1; 37 | } 38 | } else { 39 | console.log(`Invalid range: ${begin}`); 40 | } 41 | } 42 | 43 | // specified end value supercedes 44 | if (isNumber(end)) { 45 | this.end = end; 46 | } else if (isString(end)) { 47 | this.end = Number(end); 48 | if (!isNumber(this.end)) { 49 | console.log(`Invalid range: ${end}`); 50 | this.end = this.begin - 1; 51 | } 52 | } 53 | 54 | // validate range 55 | // if (this.begin < 0) { 56 | // console.log(`Invalid range begin: ${this.begin}`); 57 | // this.begin = 0; 58 | // } 59 | // if (this.end < this.begin) { 60 | // console.log(`Invalid range: ${this.begin}-${this.end}`); 61 | // this.end = this.begin; 62 | // } 63 | } 64 | 65 | toString(pad) { 66 | 67 | if (pad) { 68 | pad = Number(pad); 69 | } else if (this.end < 0x0100) { 70 | return `${this.begin}-${this.end}`; 71 | } else if (this.end < 0x010000) { 72 | pad = 4; 73 | } else if (this.end < 0x01000000) { 74 | pad = 6; 75 | } else { 76 | pad = 8; 77 | } 78 | return `${hexString(this.begin, pad)}-${hexString(this.end, pad)}`; 79 | } 80 | 81 | contains(i) { 82 | // true if this range includes i 83 | return (i >= this.begin && i <= this.end); 84 | } 85 | 86 | offset(offset) { 87 | // return a new range offset from this range 88 | return new ROMRange(this.begin + offset, this.end + offset); 89 | } 90 | 91 | intersection(range) { 92 | // return a new range that includes the intersection of this range 93 | // with another range (can be empty) 94 | const begin = Math.max(range.begin, this.begin); 95 | const end = Math.min(range.end, this.end); 96 | return new ROMRange(begin, end); 97 | } 98 | 99 | isEmpty() { 100 | // true if the array is empty 101 | return (this.end < this.begin); 102 | } 103 | 104 | get length() { 105 | // return the length of the range 106 | return (this.end - this.begin + 1); 107 | } 108 | 109 | set length(newLength) { 110 | // set the length of the range by changing end 111 | this.end = this.begin + newLength - 1; 112 | } 113 | } 114 | 115 | module.exports = ROMRange; 116 | -------------------------------------------------------------------------------- /tools/romtools/text-codec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const isString = require('is-string'); 3 | const isNumber = require('is-number'); 4 | const isArray = require('isarray'); 5 | const hexString = require('./hex-string'); 6 | 7 | class ROMTextCodec { 8 | constructor(definition, charTables) { 9 | 10 | this.encodingTable = {}; 11 | this.decodingTable = []; 12 | 13 | if (!isArray(definition.charTable)) { 14 | console.log('Invalid text encoding'); 15 | return; 16 | } 17 | for (let charTableKey of definition.charTable) { 18 | const charTable = charTables[charTableKey]; 19 | if (!charTable) { 20 | console.log('Character table not found: ${charTableKey}'); 21 | continue; 22 | } 23 | const keys = Object.keys(charTable.char); 24 | for (let c = 0; c < keys.length; c++) { 25 | const key = keys[c]; 26 | const value = charTable.char[key]; 27 | this.decodingTable[Number(key)] = value; 28 | this.encodingTable[value] = Number(key); 29 | } 30 | } 31 | } 32 | 33 | decode(data) { 34 | let text = ''; 35 | let i = 0; 36 | let b1, b2, c; 37 | 38 | while (i < data.length) { 39 | c = null; 40 | b1 = data[i++]; 41 | b2 = data[i++]; 42 | if (b1) c = this.decodingTable[(b1 << 8) | b2]; 43 | if (!c) { 44 | c = this.decodingTable[b1]; 45 | i--; 46 | } 47 | 48 | if (!c) { 49 | // unknown character 50 | text += hexString(b1, 2, '\\x'); 51 | } else if (c == '\\0') { 52 | // string terminator 53 | break; 54 | } else if (c.endsWith('[[')) { 55 | // 2-byte parameter 56 | text += c; 57 | b1 = data[i++] || 0; 58 | b2 = data[i++] || 0; 59 | text += hexString(b1 | (b2 << 8), 4); 60 | text += ']]'; 61 | } else if (c.endsWith('[')) { 62 | // 1-byte parameter 63 | text += c; 64 | b1 = data[i++] || 0; 65 | text += hexString(b1, 2); 66 | text += ']'; 67 | } else { 68 | // no parameter 69 | text += c; 70 | } 71 | } 72 | 73 | // remove padding from the end of the string 74 | while (text.endsWith('\\pad')) { 75 | text = text.substring(0, text.length - 4); 76 | } 77 | 78 | return text; 79 | } 80 | 81 | encode(text) { 82 | let data = []; 83 | let i = 0; 84 | const keys = Object.keys(this.encodingTable); 85 | 86 | while (i < text.length) { 87 | var remainingText = text.substring(i); 88 | var matches = keys.filter(function(s) { 89 | return remainingText.startsWith(s); 90 | }); 91 | 92 | if (!matches.length && remainingText.startsWith('\\x')) { 93 | const parameter = `0${remainingText.substring(1, 4)}`; 94 | i += 4; 95 | const n = Number(parameter); 96 | if (!isNumber(n)) { 97 | console.log(`Invalid value: ${parameter}`); 98 | } else { 99 | data.push(n); 100 | } 101 | continue; 102 | 103 | } else if (!matches.length) { 104 | console.log(`Invalid character: ${remainingText[0]}`); 105 | i++; 106 | continue; 107 | } 108 | 109 | const match = matches.reduce(function (a, b) { 110 | return a.length > b.length ? a : b; 111 | }); 112 | 113 | // end of string 114 | if (match === '\\0') break; 115 | 116 | let value = this.encodingTable[match]; 117 | i += match.length; 118 | 119 | if (match.endsWith('[[')) { 120 | // 2-byte parameter 121 | let end = text.indexOf(']]', i); 122 | const parameter = text.substring(i, end); 123 | let n = Number(parameter); 124 | if (!isNumber(n) || n > 0xFFFF) { 125 | console.log(`Invalid parameter: ${parameter}`); 126 | n = 0; 127 | end = i; 128 | } 129 | i = end + 2; 130 | value <<= 16; 131 | value |= n; 132 | } else if (match.endsWith('[')) { 133 | // 1-byte parameter 134 | let end = text.indexOf(']', i); 135 | const parameter = text.substring(i, end); 136 | let n = Number(parameter); 137 | if (!isNumber(n) || n > 0xFF) { 138 | console.log(`Invalid parameter: ${parameter}`); 139 | n = 0; 140 | end = i; 141 | } 142 | i = end + 1; 143 | value <<= 8; 144 | value |= n; 145 | } 146 | 147 | if (value > 0xFF) { 148 | data.push(value >> 8); 149 | data.push(value & 0xFF); 150 | } else { 151 | data.push(value); 152 | } 153 | } 154 | 155 | const terminator = this.encodingTable['\\0']; 156 | if (isNumber(terminator) && data[data.length - 1] !== terminator) { 157 | data.push(terminator); 158 | } 159 | 160 | return Uint8Array.from(data); 161 | } 162 | 163 | textLength(data) { 164 | var i = 0; 165 | var b1, b2, c; 166 | 167 | while (i < data.length) { 168 | c = null; 169 | b1 = data[i++]; 170 | b2 = data[i++]; 171 | if (b1) c = this.decodingTable[(b1 << 8) | b2]; 172 | if (!c) { 173 | c = this.decodingTable[b1]; 174 | i--; 175 | } 176 | 177 | if (!c) { 178 | continue; 179 | } else if (c === '\\0') { 180 | break; // string terminator 181 | } else if (c.endsWith('[[')) { 182 | i += 2; 183 | } else if (c.endsWith('[')) { 184 | i++; 185 | } 186 | } 187 | 188 | return Math.min(i, data.length); 189 | } 190 | 191 | getPadValue() { 192 | return this.encodingTable['\\pad'] || 0; 193 | } 194 | } 195 | 196 | module.exports = ROMTextCodec; 197 | -------------------------------------------------------------------------------- /vanilla/README.md: -------------------------------------------------------------------------------- 1 | Copy your vanilla ROM files into this directory. 2 | --------------------------------------------------------------------------------