From 6303b29258b72c19faa72e23d3380220d1d12daa Mon Sep 17 00:00:00 2001 From: Rangi <35663410+Rangi42@users.noreply.github.com> Date: Sat, 20 Mar 2021 10:16:45 -0400 Subject: [PATCH] Implement the newbox PC storage system (#608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Squashed from the `newbox` branch) Many thanks to FIQ for rewriting the backend to (a) navigate all the boxes without saving, and (b) store 16 boxes instead of 14 (320 Pokémon instead of 280)! Also for a Gen 3+-style frontend with colored icons and Menu/Swap/Item modes. And to darsh for writing BSP scripts so that Prism's `bspcomp` can patch old save files as far back as 2019. The `box_struct` is no longer a subset of `party_struct`; instead, `party_struct` is encoded to `savemon_struct` on deposit and decoded on withdraw. (They still begin with mostly the same fields, but `savemon_struct` stores a single PP Up byte and has 7-bit encoded nickname and OT fields with a checksum in the 8th bits. This means that PP as well as HP and status will be restored on deposit.) Long-term plans include using redrawn icons by choosh based on Crystal Clear, with the same palettes as full-size sprites; and rearranging more of the struct fields to allow 10-bit mon, move, and item IDs (up to 510 of each), 20-bit OT ID (6 digits, 000000-999999), or even 5-bit IVs (0-31 instead of 0-15). Fixes #510 Co-authored-by: Fredrik Ljungdahl Co-authored-by: itsdarsh --- .github/workflows/main.yml | 12 + .gitignore | 1 + CREDITS.md | 2 +- Makefile | 16 +- audio/music_player.asm | 5 + bsp/apply_party_patches.txt | 93 + bsp/constants.txt | 90 + bsp/constants_old.txt | 132 + bsp/item_index_offsets.txt | 33 + bsp/item_patches.txt | 166 + bsp/main.txt | 21 + bsp/misc.txt | 148 + bsp/move_patches.txt | 61 + bsp/patch.txt | 52 + bsp/pocket_offsets.txt | 57 + bsp/pokecenter_check.txt | 54 + bsp/pokemon_patches.txt | 63 + bsp/save_patch_list.txt | 46 + bsp/save_patch_utils.txt | 218 ++ bsp/save_patches.txt | 274 ++ bsp/save_patches_old.txt | 607 ++++ bsp/savefile.txt | 206 ++ bsp/sram_offsets.txt | 74 + constants.asm | 1 + constants/menu_constants.asm | 2 - constants/misc_constants.asm | 10 +- constants/pc_constants.asm | 40 + constants/pokemon_data_constants.asm | 58 +- constants/sprite_anim_constants.asm | 19 + constants/text_constants.asm | 4 +- data/events/special_pointers.asm | 1 - data/items/names.asm | 514 +-- data/pc/bad_egg.asm | 21 + data/pc/default_box_themes.asm | 18 + data/pc/theme_names.asm | 62 + data/phone/text/bill.asm | 61 +- data/predef_pointers.asm | 3 +- data/sprite_anims/framesets.asm | 25 + data/sprite_anims/oam.asm | 52 + data/sprite_anims/sequences.asm | 10 + data/text/common.asm | 36 +- engine/battle/core.asm | 9 +- engine/battle/read_trainer_party.asm | 2 +- engine/events/daycare.asm | 135 +- engine/events/judge_machine.asm | 15 +- engine/events/lucky_number.asm | 262 +- engine/events/npc_trade.asm | 2 +- engine/events/pokecenter_pc.asm | 23 +- engine/events/shiny_ditto.asm | 15 +- engine/events/shuckle.asm | 20 +- engine/events/specials.asm | 63 +- engine/events/wonder_trade.asm | 4 +- engine/gfx/cgb_layouts.asm | 97 +- engine/gfx/color.asm | 13 +- engine/gfx/load_pics.asm | 38 +- engine/gfx/mon_icons.asm | 46 +- engine/gfx/palettes.asm | 278 ++ engine/gfx/sprite_anims.asm | 151 + engine/items/item_effects.asm | 97 +- engine/link/link.asm | 4 +- engine/math/print_num.asm | 15 +- engine/menus/intro_menu.asm | 52 +- engine/menus/naming_screen.asm | 3 +- engine/menus/save.asm | 420 +-- engine/movie/bsod.asm | 12 + engine/overworld/variables.asm | 11 +- engine/phone/phone_scripts.asm | 7 +- engine/pokegear/pokegear.asm | 26 +- engine/pokemon/bills_pc.asm | 3478 +++++++-------------- engine/pokemon/bills_pc_top.asm | 215 -- engine/pokemon/bills_pc_ui.asm | 3602 ++++++++++++++++++++++ engine/pokemon/breeding.asm | 24 +- engine/pokemon/breedmon_level_growth.asm | 8 +- engine/pokemon/caught_data.asm | 56 +- engine/pokemon/mail.asm | 2 +- engine/pokemon/mon_menu.asm | 358 ++- engine/pokemon/mon_stats.asm | 49 +- engine/pokemon/move_mon.asm | 613 +--- engine/pokemon/move_mon_wo_mail.asm | 132 - engine/pokemon/party_menu.asm | 9 +- engine/pokemon/search.asm | 235 +- engine/pokemon/stats_screen.asm | 283 +- engine/pokemon/tempmon.asm | 84 +- gfx/pc/bags.png | Bin 0 -> 178 bytes gfx/pc/cursor.png | Bin 0 -> 110 bytes gfx/pc/mail.png | Bin 114 -> 0 bytes gfx/pc/modes.png | Bin 0 -> 167 bytes gfx/pc/pc.png | Bin 115 -> 202 bytes home.asm | 1 + home/copy_rle.asm | 17 + home/header.asm | 3 +- home/init.asm | 4 + home/lcd.asm | 175 +- home/text.asm | 67 +- macros/wram.asm | 68 +- main.asm | 15 +- maps/RadioTower1F.asm | 28 +- ram/hram.asm | 2 +- ram/sram.asm | 58 +- ram/wram0.asm | 73 +- ram/wramx.asm | 115 +- tools/Makefile | 6 +- tools/bsp/LICENSE | 24 + tools/bsp/README.md | 37 + tools/bsp/bspcomp.c | 1026 ++++++ tools/bsp/bsppatch.js | 1260 ++++++++ tools/bsp/compiler.md | 241 ++ tools/bsp/patcher.js | 55 + tools/bsp/patching.md | 87 + tools/bsp/sample.htm | 109 + tools/bsp/specification.md | 1074 +++++++ 111 files changed, 13609 insertions(+), 5272 deletions(-) create mode 100644 bsp/apply_party_patches.txt create mode 100644 bsp/constants.txt create mode 100644 bsp/constants_old.txt create mode 100644 bsp/item_index_offsets.txt create mode 100644 bsp/item_patches.txt create mode 100644 bsp/main.txt create mode 100644 bsp/misc.txt create mode 100644 bsp/move_patches.txt create mode 100644 bsp/patch.txt create mode 100644 bsp/pocket_offsets.txt create mode 100644 bsp/pokecenter_check.txt create mode 100644 bsp/pokemon_patches.txt create mode 100644 bsp/save_patch_list.txt create mode 100644 bsp/save_patch_utils.txt create mode 100644 bsp/save_patches.txt create mode 100644 bsp/save_patches_old.txt create mode 100644 bsp/savefile.txt create mode 100644 bsp/sram_offsets.txt create mode 100644 constants/pc_constants.asm create mode 100644 data/pc/bad_egg.asm create mode 100644 data/pc/default_box_themes.asm create mode 100644 data/pc/theme_names.asm delete mode 100644 engine/pokemon/bills_pc_top.asm create mode 100644 engine/pokemon/bills_pc_ui.asm delete mode 100644 engine/pokemon/move_mon_wo_mail.asm create mode 100644 gfx/pc/bags.png create mode 100644 gfx/pc/cursor.png delete mode 100644 gfx/pc/mail.png create mode 100644 gfx/pc/modes.png create mode 100644 home/copy_rle.asm create mode 100644 tools/bsp/LICENSE create mode 100644 tools/bsp/README.md create mode 100644 tools/bsp/bspcomp.c create mode 100644 tools/bsp/bsppatch.js create mode 100644 tools/bsp/compiler.md create mode 100644 tools/bsp/patcher.js create mode 100644 tools/bsp/patching.md create mode 100644 tools/bsp/sample.htm create mode 100644 tools/bsp/specification.md diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cf8f0ef0f8..a6c9c1fab1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,8 @@ jobs: mv polishedcrystal-3.0.0-beta.gbc build/polishedcrystal-3.0.0-faithful-debug-beta.gbc mv polishedcrystal-3.0.0-beta.sym build/polishedcrystal-3.0.0-faithful-debug-beta.sym make tidy + make bsp + mv polishedcrystal-3.0.0-beta.bsp build/polishedcrystal-3.0.0-beta.bsp popd - name: Delete old release id: delete_release @@ -145,3 +147,13 @@ jobs: asset_path: ./polishedcrystal/build/polishedcrystal-3.0.0-faithful-debug-beta.sym asset_name: polishedcrystal-3.0.0-beta-${{ env.SHORT_SHA }}-faithful-debug.sym asset_content_type: text/plain + - name: Upload BSP + id: upload-bsp + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./polishedcrystal/build/polishedcrystal-3.0.0-beta.bsp + asset_name: polishedcrystal-3.0.0-beta-${{ env.SHORT_SHA }}.bsp + asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore index 958934fe9b..62c92047ee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.map *.sym *.exe +*.bsp *.h.gch *.pyc *$py.class diff --git a/CREDITS.md b/CREDITS.md index bdaef9c28e..08c933a15b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -15,7 +15,7 @@ Pokémon Polished Crystal would never have been finished without the help of man * luckytyphlosion for performance optimizations to the game engine, and a 60FPS overworld. * The TPP Anniversary Crystal 251 dev team for making their code publically usable (specifically: the Move Relearner, automatic box switching, Gen VI money loss, and caught data stats page code). * Sanqui for the music player with piano roll visualization. -* ax6 for porting the xorshift+ PRNG from Prism. +* ax6 for porting the xorshift+ PRNG from Prism as well as developing bsp and related patching functions originally for Prism. * MeroMero for the in-battle color inversion code, Smeargle color code, and some move animations. * kroc for the no-RTC code. * VictoriaLacroix for the Running Shoes routine. diff --git a/Makefile b/Makefile index 9dab062b6f..ee7917cd7a 100755 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ gfx/misc.o .SUFFIXES: -.PHONY: clean tidy crystal faithful nortc debug monochrome freespace tools +.PHONY: clean tidy crystal faithful nortc debug monochrome freespace tools bsp .SECONDEXPANSION: .PRECIOUS: %.2bpp %.1bpp .SECONDARY: @@ -82,15 +82,17 @@ clean: tidy find gfx \( -name '*.[12]bpp' -o -name '*.2bpp.vram[012]' -o -name '*.2bpp.vram[012]p' \) -delete find gfx/pokemon -mindepth 1 \( -name 'bitmask.asm' -o -name 'frames.asm' -o -name 'front.animated.tilemap' -o -name 'front.dimensions' \) -delete find data/tilesets -name '*_collision.bin' -delete + $(MAKE) clean -C tools/ tidy: - rm -f $(crystal_obj) $(wildcard $(NAME)-*.gbc) $(wildcard $(NAME)-*.map) $(wildcard $(NAME)-*.sym) - $(MAKE) clean -C tools/ + rm -f $(crystal_obj) $(wildcard $(NAME)-*.gbc) $(wildcard $(NAME)-*.map) $(wildcard $(NAME)-*.sym) $(wildcard $(NAME)-*.bsp) freespace: ROM_NAME = $(NAME)-$(VERSION) -freespace: crystal +freespace: crystal tools/bankends tools/bankends $(ROM_NAME).map > bank_ends.txt +bsp: $(NAME)-$(VERSION).bsp + define DEP $1: $2 $$(shell tools/scan_includes $2) @@ -109,6 +111,10 @@ endif $(RGBDS_DIR)rgbfix $(RGBFIX_FLAGS) $@ tools/bankends -q $(ROM_NAME).map +.bsp: tools/bspcomp +%.bsp: $(wildcard bsp/*.txt) + cd bsp; ../tools/bspcomp patch.txt ../$@; cd .. + gfx/battle/lyra_back.2bpp: rgbgfx += -h @@ -163,6 +169,8 @@ gfx/pokegear/pokegear_sprites.2bpp: tools/gfx += --trim-whitespace gfx/pokemon/%/back.2bpp: rgbgfx += -h +gfx/pc/obj.2bpp: gfx/pc/modes.2bpp gfx/pc/bags.2bpp ; cat $^ > $@ + gfx/slots/slots_1.2bpp: tools/gfx += --trim-whitespace gfx/slots/slots_2.2bpp: tools/gfx += --interleave --png=$< gfx/slots/slots_3.2bpp: tools/gfx += --interleave --png=$< --remove-duplicates --keep-whitespace --remove-xflip diff --git a/audio/music_player.asm b/audio/music_player.asm index 28242692ce..754eca0b5c 100644 --- a/audio/music_player.asm +++ b/audio/music_player.asm @@ -108,6 +108,11 @@ endc MusicPlayer:: call ClearTileMap + ld a, LOW(LCDMusicPlayer) + ldh [hFunctionTargetLo], a + ld a, HIGH(LCDMusicPlayer) + ldh [hFunctionTargetHi], a + ; Load palette ld hl, rIE set LCD_STAT, [hl] diff --git a/bsp/apply_party_patches.txt b/bsp/apply_party_patches.txt new file mode 100644 index 0000000000..354851c326 --- /dev/null +++ b/bsp/apply_party_patches.txt @@ -0,0 +1,93 @@ +ApplyPartyPatches: + ; #1: callback, will receive party pointer, nickname pointer, OT pointer, location (0 = party, 1 = box, 2 = daycare) + push #e + push #d + push #c + push #b + push #a + set #a, #1 + set #1, 5 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + seek #result + readbyte #temp + add #1, #result, 8 + add #2, #result, 0x16a + add #3, #result, 0x128 + set #4, #zero + callnz #temp, .apply_to_list + set #1, 6 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + seek #result + readbyte #temp + shiftleft #temp, 31 ; nonzero if bit 0 is set + callnz #temp, .daycare_mon + set #1, 7 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + seek #result + readbyte #temp + shiftleft #temp, 31 + ; there are two extra bytes for the daycare lady we need to skip + ; (#1 should end up on sBreedmon2, not sBreedmon2Item) + readhalfword #result ; move file pointer ahead + pos #result + callnz #temp, .daycare_mon + set #b, sBox1_v6 + call .boxes + set #b, sBox8_v6 + call .boxes + pop #a + pop #b + pop #c + pop #d + pop #e + return + +.boxes + set #c, 7 +.box_loop + seek #b + readbyte #temp + add #1, #b, 22 + add #2, #b, 882 + add #3, #b, 662 + set #4, 1 + callnz #temp, .apply_to_list + add #b, BOX_SIZE_v6 + decrement #c + jumpnz #c, .box_loop + return + +.daycare_mon + add #1, #result, 23 + add #2, #result, 1 + add #3, #result, 12 + set #4, 2 + rotateleft #temp, 1 +.apply_to_list + set #d, #temp + set #e, #4 +.loop + push #3 + push #2 + push #1 + set #4, #e + call #a + pop #1 + set #4, PARTYMON_STRUCT_LENGTH + jumpz #e, .length_OK + set #4, BOXMON_STRUCT_LENGTH +.length_OK + add #1, #4 + pop #2 + add #2, NAME_LENGTH + pop #3 + add #3, NAME_LENGTH + decrement #d + jumpnz #d, .loop + return diff --git a/bsp/constants.txt b/bsp/constants.txt new file mode 100644 index 0000000000..db66626640 --- /dev/null +++ b/bsp/constants.txt @@ -0,0 +1,90 @@ +; size constants + define BANK_SIZE, 0x4000 + define BANKS, 0x80 + define ROM_SIZE, 0x200000 + define HASH_SIZE, 20 + define SAVE_SIZE, 0x8000 + +; SRAM pointers + define sPartyMail, 0x0600 + define sMailbox, 0x0835 + define sBackupMailbox, 0x0a0c + define sSaveVersion, 0x0be2 + define sBackupOptions, 0x1200 + define sBackupCheckValue1, 0x1207 + define sBackupGameData, 0x1208 + define sBackupChecksum, 0x1f0d + define sBackupCheckValue2, 0x1f0f + define sOptions, 0x2000 + define sCheckValue1, 0x2007 + define sGameData, 0x2008 + define sChecksum, 0x2d0d + define sCheckValue2, 0x2d0f + define sNewBox1, 0x2d10 + define sBackupNewBox1, 0x2f20 + define sLinkBattleRecord, 0x3266 + define sByteBeforeHallOfFame, 0x32bf + define sBoxMons1, 0x4000 + define sBoxMons1UsedEntries, 0x5fc6 + define sBoxMons2, 0x6000 + define sBoxMons2UsedEntries, 0x7fc6 + +; game data offsets + define PLAYER_NAME_OFFSET, 0x003 + define ITEMS_OFFSET, 0x38d + define MEDICINE_OFFSET, 0x425 + define BALLS_OFFSET, 0x471 + define BERRIES_OFFSET, 0x4a5 + define PC_ITEMS_OFFSET, 0x4e5 + define BOX_NAMES_OFFSET, 0x6e1 + define MAP_GROUP_OFFSET, 0x834 + define PARTY_COUNT_OFFSET, 0x856 + define PARTY_MON_OT_OFFSET, 0x97e + define POKEDEX_CAUGHT_OFFSET, 0xa0b + define DAYCARE_MAN_OFFSET, 0xa6a + define BREED_MON_1_NICK_OFFSET, 0xa6b + define BREED_MON_1_OT_OFFSET, 0xa76 + define DAYCARE_LADY_OFFSET, 0xaa1 + define BREED_MON_2_NICK_OFFSET, 0xaa4 + define BREED_MON_2_OT_OFFSET, 0Xaaf + define EGG_MON_NICK_OFFSET, 0xada + define MAGIKARP_RECORD_HOLDER_NAME_OFFSET, 0xb6b + +; SRAM sizes + define GAME_DATA_SIZE, 0xb7b + define BOX_SIZE, 0x3d4 + +; struct lengths + define PARTYMON_STRUCT_LENGTH, 48 + define BOXMON_STRUCT_LENGTH, 32 + define NAME_LENGTH, 11 + define BOX_NAME_LENGTH, 9 + define PLAYER_NAME_LENGTH, 8 + +; pokemon struct offsets + define MOVES_OFFSET, 2 + define FORM_OFFSET, 21 + define PP_OFFSET, 22 + define CAUGHTBALL_OFFSET, 28 + +; pokemon struct constants + define NUM_MOVES, 4 + define PP_MASK, 0x3f + define PP_UP_MASK, 0xc0 + define FORM_MASK, 0x1f + +; pokemon constants + define GYARADOS, 0x82 + +; move constants + define RAGE, 0x63 + define SELFDESTRUCT, 0x78 ; becomes Trick Room + define FRESH_SNACK, 0x87 + define EXPLOSION, 0x99 + define FACADE, 0xab + define MILK_DRINK, 0xd0 ; becomes Shell Smash + define RETURN, 0xd8 + +; event flags + define EVENT_GOT_SHUCKIE, 74 + define EVENT_MANIA_TOOK_SHUCKIE_OR_LET_YOU_KEEP_HIM, 75 diff --git a/bsp/constants_old.txt b/bsp/constants_old.txt new file mode 100644 index 0000000000..eb88582614 --- /dev/null +++ b/bsp/constants_old.txt @@ -0,0 +1,132 @@ +; SRAM pointers + define sBackupCheckValue1_v0, 0x1208 + define sBackupGameData_v0, 0x1209 + define sBackupPlayerData_v0, 0x1209 + define sBackupMapData_v0, 0x1a15 + define sBackupPokemonData_v0, 0x1a44 + define sBackupGameDataEnd_v0, 0x1d68 + define sBackupChecksum_v0, 0x1ef2 + define sBackupCheckValue2_v0, 0x1ef4 + define sCheckValue1_v0, 0x2008 + define sGameData_v0, 0x2009 + define sPlayerData_v0, 0x2009 + define sChecksum_v0, 0x2cf2 + define sCheckValue2_v0, 0x2cf4 + + define sBackupMapData_v1, 0x19fb + define sBackupPokemonData_v1, 0x1a2a + define sBackupGameDataEnd_v1, 0x1d4f + define sBackupChecksum_v1, 0x1ed9 + define sBackupCheckValue2_v1, 0x1edb + define sMapData_v1, 0x27fb + define sPokemonData_v1, 0x282a + define sGameDataEnd_v1, 0x2b4f + define sChecksum_v1, 0x2cd9 + define sCheckValue2_v1, 0x2cdb + define sBox_v1, 0x2cdc + + define sBackupMapData_v2, 0x1a11 + define sBackupPokemonData_v2, 0x1a40 + define sBackupGameDataEnd_v2, 0x1d65 + define sBackupChecksum_v2, 0x1eef + define sBackupCheckValue2_v2, 0x1ef1 + define sMapData_v2, 0x2811 + define sPokemonData_v2, 0x2840 + define sGameDataEnd_v2, 0x2b65 + define sChecksum_v2, 0x2cef + define sCheckValue2_v2, 0x2cf1 + + define sBackupMapData_v3, 0x1a31 + define sBackupPokemonData_v3, 0x1a60 + define sBackupGameDataEnd_v3, 0x1d85 + define sBackupChecksum_v3, 0x1f0f + define sBackupCheckValue2_v3, 0x1f11 + define sMapData_v3, 0x2831 + define sPokemonData_v3, 0x2860 + define sGameDataEnd_v3, 0x2b85 + define sChecksum_v3, 0x2d0f + define sCheckValue2_v3, 0x2d11 + + define sBackupMapData_v4, 0x1a2f + define sBackupPokemonData_v4, 0x1a5e + define sBackupGameDataEnd_v4, 0x1d83 + define sBackupChecksum_v4, 0x1f0d + define sBackupCheckValue2_v4, 0x1f0f + define sMapData_v4, 0x282f + define sPokemonData_v4, 0x285e + define sGameDataEnd_v4, 0x2b83 + define sChecksum_v4, 0x2d0d + define sCheckValue2_v4, 0x2d0f + + define sBackupChecksum_v5, 0x1f0c + define sBackupCheckValue2_v5, 0x1f0e + define sChecksum_v5, 0x2d0c + define sCheckValue2_v5, 0x2d0e + + define sSaveVersion_v6, 0x0be2 + define sBox_v6, 0x2d10 + define sBox1_v6, 0x4000 + define sBox1MonOT_v6, 0x4296 + define sBox1MonNicknames_v6, 0x4372 + define sBox8_v6, 0x6000 + define sBox8MonOT_v6, 0x6296 + define sBox8MonNicknames_v6, 0x6372 + +; SRAM sizes + define GAME_DATA_SIZE_v0, 0xb5f + define GAME_DATA_SIZE_v1, 0xb46 + define GAME_DATA_SIZE_v2, 0xb5c + define GAME_DATA_SIZE_v3, 0xb7c + define GAME_DATA_SIZE_v4, 0xb7a + +; game data offsets + define NUM_ITEMS_OFFSET_v0, 0x387 + define KEY_ITEMS_OFFSET_v0, 0x4ac + define REG_KEY_ITEMS_v0, 0xb5b + + define ITEMS_OFFSET_v1, 0x38c + define MEDICINE_OFFSET_v1, 0x41a + define BALLS_OFFSET_v1, 0x458 + define BERRIES_OFFSET_v1, 0x486 + define NUM_PC_ITEMS_OFFSET_v1, 0x4af + define PC_ITEMS_OFFSET_v1, 0x4b0 + define REG_KEY_ITEM_FLAGS_v1, 0xb41 + define PARTY_COUNT_OFFSET_v1, 0x821 + define DAYCARE_MAN_OFFSET_v1, 0xa38 + define DAYCARE_LADY_OFFSET_v1, 0xa6f + + define ITEMS_OFFSET_v2, 0x38c + define MEDICINE_OFFSET_v2, 0x41a + define BALLS_OFFSET_v2, 0x466 + define BERRIES_OFFSET_v2, 0x49a + define PC_ITEMS_OFFSET_v2, 0x4c6 + define PARTY_COUNT_OFFSET_v2, 0x837 + define DAYCARE_MAN_OFFSET_v2, 0xa4e + define DAYCARE_LADY_OFFSET_v2, 0xa85 + + define ITEMS_OFFSET_v3, 0x38c + define MEDICINE_OFFSET_v3, 0x42e + define BALLS_OFFSET_v3, 0x47a + define BERRIES_OFFSET_v3, 0x4ae + define PC_ITEMS_OFFSET_v3, 0x4e6 + define PARTY_COUNT_OFFSET_v3, 0x857 + define DAYCARE_MAN_OFFSET_v3, 0xa6e + define DAYCARE_LADY_OFFSET_v3, 0xaa5 + + define ITEMS_OFFSET_v4, 0x38c + define MEDICINE_OFFSET_v4, 0x424 + define BALLS_OFFSET_v4, 0x470 + define BERRIES_OFFSET_v4, 0x4a4 + define PC_ITEMS_OFFSET_v4, 0x4e4 + define EVENT_FLAGS_OFFSET_v4, 0x5bf + define PARTY_COUNT_OFFSET_v4, 0x855 + define DAYCARE_MAN_OFFSET_v4, 0xa6c + define DAYCARE_LADY_OFFSET_v4, 0xaa3 + + define MAP_FROM_CONT_OFFSET_v5, 0x35d + +; removed event flags + define EVENT_NOISY_FOREST_PIKABLU_GUY, 2027 + +; old data sizes + define BOX_SIZE_v6, 0x450 diff --git a/bsp/item_index_offsets.txt b/bsp/item_index_offsets.txt new file mode 100644 index 0000000000..44672601cc --- /dev/null +++ b/bsp/item_index_offsets.txt @@ -0,0 +1,33 @@ +ItemIndexOffsets_Build0ToBuild4: + ; upper bound, offset + db 0x16, 0 + db 0x37, 1 + db 0x52, 2 + db 0x71, 3 + db 0x7d, -26 + db 0x82, -20 + db 0x86, -16 + db 0x87, -78 + db 0xc2, -16 + db 0xf4, -1 + db 0 + +ItemIndexOffsets_Build1ToBuild4: + db 0x53, 0 + db 0x62, 1 + db 0x67, 7 + db 0xa7, 11 + db 0xea, 20 + db 0 + +ItemIndexOffsets_Build2ToBuild4: + db 0x63, 0 + db 0x68, 6 + db 0xa8, 10 + db 0xeb, 19 + db 0 + +ItemIndexOffsets_Build3ToBuild4: + db 0x6e, 0 + db 0xfa, 4 + db 0 diff --git a/bsp/item_patches.txt b/bsp/item_patches.txt new file mode 100644 index 0000000000..c4ac0f80ef --- /dev/null +++ b/bsp/item_patches.txt @@ -0,0 +1,166 @@ +AdjustItemIndexes: + ; #1: offset pointer table + gethalfwordinc #temp, #1 + ifeq #temp, 0xffff, .error_pointer + getwordinc #r0, #1 + iflt #temp, #build, AdjustItemIndexes + + set #1, #zero + call .get_pocket + + set #1, 1 + call .get_pocket + + set #1, 2 + call .get_pocket + + set #1, 3 + call .get_pocket + + set #1, 4 + call .get_pocket + + set #1, .mon_callback + jump ApplyPartyPatches + +.get_pocket + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + seek #result +.pocket_loop + set #address, #r0 + call .index_loop + seekfwd 1 + ifne #result, 0xff, .pocket_loop + return + +.mon_callback + increment #1 + seek #1 + set #address, #r0 +.index_loop + getfilebyte #result + getbyteinc #temp, #address + getbyteinc #offset, #address + jumpz #temp, .done_loop + ifgt #result, #temp, .index_loop + add #result, #offset +.done_loop + writebyte #result + return + +.error_pointer + bufstring .error_string_pointer_1 + bufnumber #build + bufstring .error_string_pointer_2 + printbuf + exit 1 + +.error_string_pointer_1 + string "ERROR: Incoming build number " +.error_string_pointer_2 + string " out of range for GetItemIndexAdjustment" + +ResizePockets: + ; #r0: version number + ; #1: item pocket offset + ; #2: medicine pocket offset + ; #3: balls pocket offset + ; #4: berries pocket offset + ; shift forward if offset is positive, shift backwards if offset is negative + push #curbuild + set #curbuild, #r0 + jumpz #4, .balls + push #1 + set #1, 4 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + decrement #result + set #1, #4 + call ShiftPocketBytes + pop #1 + +.balls + jumpz #3, .medicine + push #1 + set #1, 3 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + decrement #result + set #1, #3 + call ShiftPocketBytes + pop #1 + +.medicine + jumpz #2, .items + push #1 + set #1, 2 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + decrement #result + set #1, #2 + call ShiftPocketBytes + pop #1 + +.items + jumpz #1, .done + push #1 + set #1, 1 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + decrement #result + pop #1 + call ShiftPocketBytes +.done + pop #curbuild + return + +ShiftPocketBytes: + push #1 + push #2 + ifgt #1, 0x7fff, .shift_back +; .shift_forward + push #3 + seek #result + set #3, #result + set #2, #1 + shiftleft #2, 1 + set #1, 0x3fff + ifeq #savefile, 1, .continue_shift_forward + set #1, 0x1fff +.continue_shift_forward + subtract #1, #3 + subtract #1, #2 + call ShiftSaveDataForward + seek #result + writehalfword #zero + pop #3 + jump .done_shift + +.shift_back +; TODO: Warn the player if they're overwriting data + xor #1, 0xffff + increment #1 + set #2, #1 + shiftleft #2, 1 + subtract #result, #2 + decrement #result + seek #result + writebyte 0xff ; ensure that pocket still has a terminator + increment #result + set #1, 0x3fff + ifeq #savefile, 1, .continue_shift_back + set #1, 0x1fff +.continue_shift_back + subtract #1, #result + subtract #1, #2 + call ShiftSaveDataBack +.done_shift + pop #2 + pop #1 + return diff --git a/bsp/main.txt b/bsp/main.txt new file mode 100644 index 0000000000..8d546fa565 --- /dev/null +++ b/bsp/main.txt @@ -0,0 +1,21 @@ +Main: + call PrintInitialMessage + print .savefile_string + menu #result, YesNoMenu + jumpz #result, PatchSavefile + exit 1 + +.savefile_string + string "Do you want to patch a savefile to the latest version?" + +PrintInitialMessage: + bufstring .polished_crystal_string + bufnumber CURRENT_BUILD + bufstring .parenthesis_asterisks_string + printbuf + return + +.polished_crystal_string + string "*** Pokémon Polished Crystal save patch ( build " +.parenthesis_asterisks_string + string " ) ***" diff --git a/bsp/misc.txt b/bsp/misc.txt new file mode 100644 index 0000000000..6f33bec399 --- /dev/null +++ b/bsp/misc.txt @@ -0,0 +1,148 @@ +PrintBuild4VersionCheck: + set #temp, 0x0a ; newline + bufstring .build4_string + bufchar #temp + bufchar #temp + + bufstring .horizontal_tab + bufstring .build4_0_string + bufstring .build4_softboiled_milk_drink + bufstring .build4_national_dex + bufstring .build4_0_dex + bufchar #temp + bufstring .horizontal_tab + bufstring .horizontal_tab + bufstring .build4_0_dex_link + bufchar #temp + bufchar #temp + + bufstring .horizontal_tab + bufstring .build4_1_string + bufstring .build4_softboiled_milk_drink + bufstring .build4_national_dex + bufstring .build4_1_dex + bufchar #temp + bufstring .horizontal_tab + bufstring .horizontal_tab + bufstring .build4_1_dex_link + bufchar #temp + bufchar #temp + + bufstring .horizontal_tab + bufstring .build4_2_string + bufstring .build4_fresh_snack_shell_smash + bufstring .build4_national_dex + bufstring .build4_2_dex + bufchar #temp + bufstring .horizontal_tab + bufstring .horizontal_tab + bufstring .build4_1_dex_link + bufchar #temp + bufchar #temp + + bufstring .horizontal_tab + bufstring .build4_3_string + bufstring .build4_fresh_snack_shell_smash + bufstring .build4_national_dex + bufstring .build4_2_dex + bufchar #temp + bufstring .horizontal_tab + bufstring .horizontal_tab + bufstring .build4_1_dex_link + bufchar #temp + bufchar #temp + + bufstring .horizontal_tab + bufstring .build4_4_string + bufstring .build4_fresh_snack_shell_smash + bufstring .build4_national_dex + bufstring .build4_2_dex + bufchar #temp + bufstring .horizontal_tab + bufstring .horizontal_tab + bufstring .build4_1_dex_link + bufchar #temp + bufchar #temp + + bufstring .horizontal_tab + bufstring .build4_5_string + bufstring .build4_fresh_snack_shell_smash + bufstring .build4_national_dex + bufstring .build4_5_dex + bufchar #temp + bufstring .horizontal_tab + bufstring .horizontal_tab + bufstring .build4_5_dex_link + bufchar #temp + bufchar #temp + + bufstring .build4_instruction_1 + bufchar #temp + bufstring .build4_instruction_2 + printbuf + + menu #result, .subbuilds + return + +.build4_string + string "There are four possible versions of build 4:" +.build4_0_string + string "* 4.0 (8 December 2019): " +.build4_1_string + string "* 4.1 (16 December 2019): " +.build4_2_string + string "* 4.2 (21 December 2019): " +.build4_3_string + string "* 4.3 (24 December 2019): Removed Pikablu guy from Noisy Forest, " +.build4_4_string + string "* 4.4 (4 January 2020): Can now Fly to Yellow Forest and Cerulean Cape, " +.build4_5_string + string "* 4.5 (2 February 2020): " +.build4_instruction_1 + string "Compare your National Dex order to the links provided, and check if Softboiled and Milk Drink are still in game or have been replaced with Fresh Snack (or if Shell Smash is in the game.)" +.build4_instruction_2 + string "Then, select the option that corresponds to the build number based on the info above. If you are unsure, select 4.3, or go to the Polished Crystal Discord server and ask for help in #questions." +.build4_0_dex_link + string "https://github.com/Rangi42/polishedcrystal/blob/c19d51791d045f2508a19c1a2c9b05a2bdf46208/constants/pokemon_constants.asm" +.build4_1_dex_link + string "https://github.com/Rangi42/polishedcrystal/blob/d5d26b4d33438224579bbc0400d7a7dd954b2f19/constants/pokemon_constants.asm" +.build4_5_dex_link + string "https://github.com/Rangi42/polishedcrystal/blob/5325cfb8e352011e20e83f90a439c7d15dc088cf/constants/pokemon_constants.asm" +.build4_softboiled_milk_drink + string "Softboiled and Milk Drink still in game, " +.build4_fresh_snack_shell_smash + string "Softboiled and Milk Drink replaced with Fresh Snack, Shell Smash added, " +.build4_national_dex + string "National Dex matches this list " +.build4_0_dex: + string "(no Shuckle): " +.build4_1_dex: + string "(Shuckle added, Leafeon/Glaceon/Porygon-Z indexes moved): " +.build4_2_dex: + string "(same as 4.1): " +.build4_5_dex: + string "(Rhyperior index moved): " +.horizontal_tab + string "    " ; note that these are hard spaces (0xff) + +.subbuilds + dw .zero + dw .one + dw .two + dw .three + dw .four + dw .five + dw -1 + +.zero + string "4.0" +.one + string "4.1" +.two + string "4.2" +.three + string "4.3" +.four + string "4.4" +.five + string "4.5" diff --git a/bsp/move_patches.txt b/bsp/move_patches.txt new file mode 100644 index 0000000000..6655d7ecea --- /dev/null +++ b/bsp/move_patches.txt @@ -0,0 +1,61 @@ +ReplaceMove: + ; replaces old move with new move that target Pokemon doesn't already know + ; #r0: move replace table + set #1, .mon_callback + jump ApplyPartyPatches + +.mon_callback + push #1 + add #1, MOVES_OFFSET + seek #1 + set #2, #zero + set #address, #r0 + set #length, NUM_MOVES + increment #length ; #length decrements at the start of the loop + getbyteinc #result, #address + call .find_move_loop + pop #1 + ifgt #2, NUM_MOVES, .done + + ; sets PP, if people really wanna use a save patcher as a max ether then so be it + decrement #address ; address points to PP byte + add #1, PP_OFFSET + add #1, #2 + decrement #1 + seek #1 + getfilebyte #1 + and #1, PP_UP_MASK + rotateleft #1, -6 + getbyte #result, #address + divide #temp, #result, 5 + multiply #temp, #1 + add #result, #temp + rotateleft #1, 6 + add #result, #1 + writebyte #result +.done + return + +.find_move_loop + increment #2 + decrement #length + retz #length ; target move not found + readbyte #temp + ifne #result, #temp, .find_move_loop + + seekback 1 + pushpos +.replace_move_loop + seek #1 + set #length, NUM_MOVES + getbyteinc #result, #address + increment #address +.inner_loop + readbyte #temp + ifeq #temp, #result, .replace_move_loop + decrement #length + jumpnz #length, .inner_loop + + poppos + writebyte #result + return diff --git a/bsp/patch.txt b/bsp/patch.txt new file mode 100644 index 0000000000..f680fe6bd1 --- /dev/null +++ b/bsp/patch.txt @@ -0,0 +1,52 @@ +; Many of the included functions were originally developed by the RainbowDevs team at Pokemon Prism. +; A wholehearted thank you also goes out to aaaaaa123456789 (aka ax6) for developing bsp and the Prism patch. + +; version info + define CURRENT_BUILD, 7 + +; variables + define zero, 0 + define r0, 100 + define r1, 101 + define a, 201 + define b, 202 + define c, 203 + define d, 204 + define e, 205 + define patch, 241 + define message, 242 + define savefile, 243 + define build, 244 + define curbuild, 245 + define lowseed, 246 + define highseed, 247 + define source, 248 + define target, 249 + define address, 250 + define banks, 251 + define length, 252 + define offset, 253 + define temp, 254 + define result, 255 + + include "constants.txt" + include "constants_old.txt" + + include "main.txt" + include "savefile.txt" + include "save_patches.txt" + include "save_patches_old.txt" + include "save_patch_list.txt" + include "save_patch_utils.txt" + + include "pokemon_patches.txt" + include "item_patches.txt" + include "move_patches.txt" + include "apply_party_patches.txt" + include "pokecenter_check.txt" + + include "sram_offsets.txt" + include "pocket_offsets.txt" + include "item_index_offsets.txt" + + include "misc.txt" diff --git a/bsp/pocket_offsets.txt b/bsp/pocket_offsets.txt new file mode 100644 index 0000000000..45e3c44210 --- /dev/null +++ b/bsp/pocket_offsets.txt @@ -0,0 +1,57 @@ +PocketOffsetsByBuild: + dh 1 + dh ITEMS_OFFSET_v1 + dh MEDICINE_OFFSET_v1 + dh BALLS_OFFSET_v1 + dh BERRIES_OFFSET_v1 + dh PC_ITEMS_OFFSET_v1 + dh PARTY_COUNT_OFFSET_v1 + dh DAYCARE_MAN_OFFSET_v1 + dh DAYCARE_LADY_OFFSET_v1 + dh 0 + + dh 2 + dh ITEMS_OFFSET_v2 + dh MEDICINE_OFFSET_v2 + dh BALLS_OFFSET_v2 + dh BERRIES_OFFSET_v2 + dh PC_ITEMS_OFFSET_v2 + dh PARTY_COUNT_OFFSET_v2 + dh DAYCARE_MAN_OFFSET_v2 + dh DAYCARE_LADY_OFFSET_v2 + dh 0 + + dh 3 + dh ITEMS_OFFSET_v3 + dh MEDICINE_OFFSET_v3 + dh BALLS_OFFSET_v3 + dh BERRIES_OFFSET_v3 + dh PC_ITEMS_OFFSET_v3 + dh PARTY_COUNT_OFFSET_v3 + dh DAYCARE_MAN_OFFSET_v3 + dh DAYCARE_LADY_OFFSET_v3 + dh 0 + + dh 4 + dh ITEMS_OFFSET_v4 + dh MEDICINE_OFFSET_v4 + dh BALLS_OFFSET_v4 + dh BERRIES_OFFSET_v4 + dh PC_ITEMS_OFFSET_v4 + dh PARTY_COUNT_OFFSET_v4 + dh DAYCARE_MAN_OFFSET_v4 + dh DAYCARE_LADY_OFFSET_v4 + dh 0 + + dh 6 + dh ITEMS_OFFSET + dh MEDICINE_OFFSET + dh BALLS_OFFSET + dh BERRIES_OFFSET + dh PC_ITEMS_OFFSET + dh PARTY_COUNT_OFFSET + dh DAYCARE_MAN_OFFSET + dh DAYCARE_LADY_OFFSET + dh 0 + + dh 0 diff --git a/bsp/pokecenter_check.txt b/bsp/pokecenter_check.txt new file mode 100644 index 0000000000..75a4ffa021 --- /dev/null +++ b/bsp/pokecenter_check.txt @@ -0,0 +1,54 @@ +PokecenterCheck: + set #1, MAP_GROUP_OFFSET + call GetGameDataPointer + seek #result + bufnumber #result + bufchar 0x20 + getfilehalfword #result + bufnumber #result + bufchar 0x20 + set #address, .pokecenters +.loop + gethalfwordinc #temp, #address + bufnumber #temp + bufchar 0x20 + jumpz #temp, .not_in_pokecenter + ifne #temp, #result, .loop + return +.not_in_pokecenter + printbuf + print .not_in_pokecenter_string + menu #result, YesNoMenu + retz #result + exit #result + +.not_in_pokecenter_string + string "You are not currently inside a Pokémon Center. Remember that, in order to ensure patching is successful, you should be inside a Pokémon Center when patching your savefile and exit it immediately when you continue the game. Do you want to continue patching anyway?" + +.pokecenters + ; map group, map number + db 1, 1 ;Olivine + db 2, 3 ;Mahogany + db 4, 3 ;Ecruteak + db 5, 6 ;Blackthorn + db 6, 1 ;Cinnabar + db 7, 4 ;Cerulean + db 7, 8 ;Route 10 + db 8, 1 ;Azalea + db 10, 8 ;Violet + db 10, 11 ;Route 32 + db 11, 24 ;Goldenrod + db 12, 5 ;Vermilion + db 14, 3 ;Route 3 + db 14, 8 ;Pewter + db 16, 3 ;Indigo + db 17, 12 ;Fuchsia + db 18, 6 ;Lavender + db 19, 3 ;Silver + db 21, 20 ;Celadon + db 22, 6 ;Cianwood + db 23, 10 ;Viridian + db 25, 4 ;Saffron + db 26, 6 ;Cherrygrove + db 31, 8 ;Shamouti + dh 0 diff --git a/bsp/pokemon_patches.txt b/bsp/pokemon_patches.txt new file mode 100644 index 0000000000..08be76cbfa --- /dev/null +++ b/bsp/pokemon_patches.txt @@ -0,0 +1,63 @@ +AdjustPokemonIndexes: + set #1, .mon_callback + call ApplyPartyPatches + + ; modify party species + set #1, 5 + call GetGameDataOffsetConstant + set #1, #result + call GetGameDataPointer + increment #result + seek #result +.party_species_loop + call .species_adjust + getfilebyte #temp + ifne #temp, 0xff, .party_species_loop + + ; modify box species + set #1, sBox1_v6 + call .box_species + + set #1, sBox8_v6 + call .box_species + + ; modify pokedex flags + seek 0x2a16 ; TODO: replace literal + set #1, 256 + set #2, #r0 + call ShiftFlags + + seekfwd 32 + set #1, 256 + set #2, #r0 + jump ShiftFlags + +; box species adjustments +.box_species + set #length, 7 + increment #1 ; point to sBoxSpecies +.box_outer_loop + retz #length + seek #1 +.box_inner_loop + call .species_adjust + getfilebyte #temp + ifne #temp, 0xff, .box_inner_loop + decrement #length + add #1, 0x450 + jump .box_outer_loop + +.mon_callback + seek #1 +.species_adjust + getfilebyte #result + set #address, #r0 +.index_loop + gethalfwordinc #temp, #address ; get offset index upper bound + ifeq #temp, 0xffff, .done_loop + gethalfwordinc #offset, #address ; get offset + ifgt #result, #temp, .index_loop + add #result, #offset +.done_loop + writebyte #result + return diff --git a/bsp/save_patch_list.txt b/bsp/save_patch_list.txt new file mode 100644 index 0000000000..b5020b29a5 --- /dev/null +++ b/bsp/save_patch_list.txt @@ -0,0 +1,46 @@ +SavePatches: + ; if build < (2 byte value), apply patches indicated by 4 byte pointer; 0 ends the list + ; savebreak fixes (i.e. game data size changes) should usually be the first functions called per version + dh 1 + dw CreateKeyItemBitflags ; explicit + + dh 4 + dw ResizePocketsToBuild4 ; explicit + + dh 4 + dw AdjustItemIndexesToBuild4 ; implicit + + dh 4 + dw AdjustMonCaughtBallData ; implicit + + dh 4 + dw ReplaceSelfDestruct ; implicit + + dh 5 ; 4.1-4.5 + dw AdjustMonAndMoveIndexesPostBuild4 ; implicit + + dh 5 + dw RemoveExtraOptionsByte ; explicit + + dh 6 ; 5.1 + dw ResetInitialOptions ; implicit + + dh 6 + dw AdjustVariableSprites ; explicit + + dh 6 + dw LegendaryDogStatus ; implicit + + dh 7 ; 6.1 + dw UpdateCharset ; implicit + + dh 7 ; 6.2 + dw ChangeGyaradosForm ; implicit + + dh 7 + dw ConvertToNewbox ; first build with version number + + dh 7 ; 7.1 + dw ChangeCheckValue ; implicit, only to convert check value to 97 + + dh 0 diff --git a/bsp/save_patch_utils.txt b/bsp/save_patch_utils.txt new file mode 100644 index 0000000000..18a110fddb --- /dev/null +++ b/bsp/save_patch_utils.txt @@ -0,0 +1,218 @@ +DoNothing: + return + +GetGameDataPointer: + set #address, GameDataStartPointers +.get_pointer_loop + gethalfwordinc #temp, #address + ifge #temp, #curbuild, .got_pointer + add #address, 8 + jump .get_pointer_loop + +.got_pointer + getwordinc #temp, #address + add #result, #1, #temp + ifeq #savefile, 1, .done + getword #temp, #address + add #result, #1, #temp +.done + return + +GetGameDataOffsetConstant: +; (0 = items, 1 = medicine, 2 = balls, 3 = berries, 4 = PC items, 5 = party count, 6 = daycare man, 7 = daycare lady, error if >7) +; returns targeted constant from OldPocketOffsets + set #address, PocketOffsetsByBuild +.get_offset_loop + gethalfwordinc #temp, #address + jumpz #temp, .error_build + ifge #temp, #curbuild, .get_constant_loop + add #address, 18 + jump .get_offset_loop + +.get_constant_loop + gethalfwordinc #result, #address + jumpz #result, .error_oob + retz #1 + decrement #1 + jump .get_constant_loop + +.error_build + bufstring .error_build_string_1 + bufnumber #curbuild + bufstring .error_build_string_2 + printbuf + exit 1 + +.error_oob + print .error_oob_string + exit 1 + +.error_build_string_1 + string "ERROR: Invalid build " +.error_build_string_2 + string " for GetGameDataOffsetConstant" + +.error_oob_string + string "ERROR: GetGameDataOffsetConstant went out-of-bounds" + +ShiftFlags: + ; thank you kindly to ax6 for creating the base of this function + ; in: #1: flag array size (in bits), #2: pointer to mapping table + ; file pointer must be at the beginning of the array + ; the mapping table is a halfword array of upper bound index (exclusive), offset; end with -1 + pushpos + add #1, 31 + shiftright #1, 5 + shiftleft #3, #1, 2 + add #3, 4 + seekfwd #3 + set #3, #1 +.readloop + seekback 8 + readword #4 + push #4 + decrement #3 + jumpnz #3, .readloop + stackread #address, #1 + seek #address + +.fixloop + ; #3 = current index, #4 = offset, #5 = upper bound index, #6 = stack pos + gethalfwordinc #5, #2 + ifeq #5, 0xffff, .done + gethalfwordinc #4, #2 + jumpz #4, .next +.innerloop + shiftright #source, #3, 5 + shiftleft #source, 2 + add #source, #address + seek #source + getfileword #source ; word to read from + and #temp, #3, 31 + shiftright #source, #temp + or #source, -2 ; get source bit, preserve other target bits + add #6, #3, #4 + and #6, 0xffff + and #temp, #6, 31 + subtract #temp, #zero, #temp + shiftright #6, 5 ; get target stack pos + stackread #target, #6 ; word to write to + rotateleft #target, #temp + or #target, 1 + and #target, #source + subtract #temp, #zero, #temp + rotateleft #target, #temp + stackwrite #6, #target + increment #3 + iflt #3, #5, .innerloop +.next + set #3, #5 + jump .fixloop + +.done + stackread #2, #1 + seek #2 +.writeloop + pop #2 + writeword #2 + decrement #1 + jumpnz #1, .writeloop + poppos + return + +FlagAction: + ; current file pointer: flag array + ; #1: flag number + ; #2: action (0: clear, 1: set, 2: toggle, >2: none) + ; returns old value + pushpos + shiftright #temp, #1, 3 + seekfwd #temp + getfilebyte #temp + and #1, 7 + shiftright #result, #temp, #1 + and #result, 1 + shiftleft #1, 1, #1 + ifgt #2, 2, .done + jumptable #2 + dw .clear + dw .set + dw .toggle + +.clear + jumpz #result, .done +.toggle + xor #temp, #1 + jump .write + +.set + or #temp, #1 +.write + writebyte #temp +.done + poppos + return + +CopyFrom1To2: + ; #1: source, #2: #target, #3: count + retz #3 + seek #1 + readbyte #temp + increment #1 + seek #2 + writebyte #temp + increment #2 + decrement #3 + jump CopyFrom1To2 + +ShiftSaveDataBack: + ; #1: count, #2: amount + retz #1 + seekfwd #2 + getfilebyte #temp + seekback #2 + writebyte #temp + decrement #1 + jump ShiftSaveDataBack + +ShiftSaveDataForward: + ; #1: count, #2: amount, #3: starting offset + add #3, #1 + increment #1 +.loop + seek #3 + getfilebyte #temp + seekfwd #2 + writebyte #temp + decrement #3 + decrement #1 + jumpnz #1, .loop + return + +PrintWithLeadingZeros: + ; prints number #1 in #2 digits, with leading zeros + push -0x30 ;so it will become 0 when 0x30 is added to it + increment #2 + jump .handle_loop +.push_loop + remainder #temp, #1, 10 + divide #1, 10 + push #temp +.handle_loop + decrement #2 + jumpnz #2, .push_loop +.pop_loop + pop #temp + add #temp, 0x30 ;0x30 is ASCII for '0' + retz #temp + bufchar #temp + jump .pop_loop + +YesNoMenu: + dw .yes + dw .no + dw -1 +.yes + string "Yes" +.no + string "No" diff --git a/bsp/save_patches.txt b/bsp/save_patches.txt new file mode 100644 index 0000000000..d8b36a579a --- /dev/null +++ b/bsp/save_patches.txt @@ -0,0 +1,274 @@ +ConvertToNewbox: + set #a, #zero ; [0, 14) + set #temp, 57 + seek sBoxMons1UsedEntries + fillbyte #temp, #zero + seek sBoxMons2UsedEntries + fillbyte #temp, #zero + +.box_outer_loop + set #source, sBox1_v6 + iflt #a, 7, .got_source + set #source, sBox8_v6 +.got_source + remainder #temp, #a, 7 + mulacum #source, #temp, BOX_SIZE_v6 + seek #source ; we are now at sBox#Count + readbyte #b ; box count, we are now at sBox#Species + + set #length, #zero + set #2, sBox_v6 +.copy_temp_loop + ifge #length, #b, .done_temp_loop + add #1, #source, 22 ; we are now at sBox#Mon1 + mulacum #1, #length, 32 + set #3, 22 ; mon struct up to PP + call CopyFrom1To2 + +; condense 4 PP bytes into one byte for PP Ups + seek #1 + readword #temp + pos #1 + and #temp, 0xC0C0C0C0 +.pp_up_loop + shiftright #temp, 6 + or #3, #temp ; #3 is already set to zero thanks to CopyFrom1To2 + ifne #temp, #zero, .pp_up_loop + seek #2 + writebyte #3 + increment #2 + set #3, 6 ; remainder of mon struct + call CopyFrom1To2 + +; set aside hyper training bytes + push #2 + add #2, 3 +; copy nick + add #1, 828 + mulacum #1, #length, -21 ; offset from box mon n + 1 to nickname n + set #3, 10 + call CopyFrom1To2 +; copy OT + subtract #1, 230 ; offset from nickname n+1 to OT n + set #3, 7 + call CopyFrom1To2 + increment #1 ; skip excess terminator byte (always 0x53) + set #4, #2 +; copy hyper training bytes + pop #2 + set #3, 3 + call CopyFrom1To2 + set #2, #4 + increment #length + jump .copy_temp_loop + +.done_temp_loop + seek sBoxMons1UsedEntries + iflt #a, 7, .got_entries + seek sBoxMons2UsedEntries +.got_entries + remainder #1, #a, 7 + multiply #1, 20 + add #length, #1, #b + set #2, 1 +.set_flags_loop + ifge #1, #length, .done_flags_loop + push #1 + call FlagAction + pop #1 + increment #1 + jump .set_flags_loop + +.done_flags_loop + set #1, sBox_v6 + seek #1 + set #length, #b +; begin calculating 16-bit checksum for each box pokemon +.encode_outer_loop + jumpz #length, .done_encode_loop + set #2, #zero + set #result, 127 +.mon_struct_loop + increment #2 + readbyte #3 + mulacum #result, #3, #2 + iflt #2, 32, .mon_struct_loop +; convert nickname/OT chars to 7-bit, accounting for special chars + increment #2 ; one multiplier (33) is skipped upon checksum calculation + pushpos +.nickname_ot_loop + increment #2 + getfilebyte #3 + set #temp, 0xfa + ifeq #3, 0x7f, .replace + increment #temp + ifeq #3, 0x53, .replace + increment #temp + jumpnz #3, .remove_bit +.replace + set #3, #temp +.remove_bit + and #3, 0x7f + writebyte #3 + mulacum #result, #3, #2 + iflt #2, 50, .nickname_ot_loop +; write checksum for each 16 lower bits (starting with MSB) into encoded nickname/OT chars + poppos + rotateleft #result, -15 + set #2, 16 +.write_checksum_loop + getfilebyte #3 + shiftleft #temp, #result, 7 + or #3, #temp + writebyte #3 + rotateleft #result, 1 + decrement #2 + jumpnz #2, .write_checksum_loop + readbyte #temp ; skip the 17th byte in nick+OT + decrement #length + jump .encode_outer_loop + +.done_encode_loop +; copy all of temp back to pokedb (#1 is already set to sBox_v6) + remainder #temp, #a, 7 + multiply #temp, BOX_SIZE + add #2, sBoxMons1, #temp + iflt #a, 7, .got_target + add #2, sBoxMons2, #temp +.got_target + multiply #3, #b, 49 + call CopyFrom1To2 + +; clear out bytes from last pokedb entry to next box (or used entry flags) + increment #a + set #1, sBoxMons2UsedEntries + ifeq #a, 14, .next + set #1, sBoxMons1UsedEntries + ifeq #a, 7, .next + set #1, sBox1_v6 + iflt #a, 7, .got_source_2 + set #1, sBox8_v6 +.got_source_2 + remainder #temp, #a, 7 + mulacum #1, #temp, BOX_SIZE_v6 +.next + subtract #1, #2 + seek #2 + fillbyte #1, #zero + iflt #a, 14, .box_outer_loop + +; we can now fill sBackupNewBox with newbox + set #a, #zero + set #target, sBackupNewBox1 + set #source, sBoxMons1UsedEntries + call .newbox_outer_loop + + ; #a = 7, #target = sBackupNewBox8 + set #source, sBoxMons2UsedEntries + call .newbox_outer_loop + +; add boxes 15 and 16 + seek #target ; sBackupNewBox15 + set #1, 0x1B ; our exit condition, since box 15 has default theme 0x1B and box 16 has default theme 0x1C + set #2, .box15 + set #3, 6 +.add_box_loop + fillbyte 23, #zero ; clear out box 15/16 + writedata #2, #3 + add #2, #3 + fillbyte 3, #zero + writebyte #1 + increment #1 + iflt #1, 0x1D, .add_box_loop + +; shift link battle/HOF/battle tower data to end of box names + set #1, 3499 + set #2, 292 + call ShiftSaveDataBack + decrement #2 + fillbyte #2, #zero + +; copy backup new box into active new box + set #1, sBackupNewBox1 + set #2, sNewBox1 + subtract #3, sBackupNewBox1, sNewBox1 + call CopyFrom1To2 + +; clear old box names + set #1, BOX_NAMES_OFFSET + call GetGameDataPointer + seek #result + fillbyte 126, #zero + +; clear wEggMon (removed as part of newbox) + set #1, EGG_MON_NICK_OFFSET + call GetGameDataPointer + seek #result + fillbyte 54, #zero + return + +.newbox_outer_loop + set #length, #zero +.copy_flags_loop + seek #source + remainder #1, #a, 7 + multiply #1, 20 + add #1, #length + set #2, #source ; we know #source > 2 + push #1 + call FlagAction + pop #1 + increment #1 + seek #target + jumpz #result, .got_value ; write 0 if flag is cleared + set #result, #1 +.got_value + writebyte #result + increment #target + increment #length + iflt #length, 20, .copy_flags_loop + +; set sram bank (0 for box1-7, 1 for box8-14) + set #temp, #zero + iflt #a, 7, .sram_bank_2 + decrement #temp +.sram_bank_2 + fillbyte 3, #temp + add #target, 3 ; target now points at box name + + set #1, BOX_NAMES_OFFSET + call GetGameDataPointer + set #3, 9 + multiply #1, #a, #3 + add #1, #result + set #2, #target + add #target, #3 + call CopyFrom1To2 + + set #1, .default_themes + add #1, #a + getbyte #temp, #1 + writebyte #temp + increment #target + increment #a + remainder #temp, #a, 7 + jumpnz #temp, .newbox_outer_loop + return + +.box15 + db 0x81, 0xae, 0xb7, 0xe1, 0xe5, 0x53 ; "Box15@" + +.box16 + db 0x81, 0xae, 0xb7, 0xe1, 0xe6, 0x53 ; "Box16@" + +.default_themes + db 0x17, 0x15, 0x16, 0x18, 0x0D, 0x19, 0x0C, 0x0E, 0x12, 0x0F, 0x10, 0x11, 0x1A, 0x13 + +ChangeCheckValue: + set #temp, sCheckValue1 + ifeq #savefile, 1, .next + set #temp, sBackupCheckValue1 +.next + seek #temp + writebyte 0x61 + return diff --git a/bsp/save_patches_old.txt b/bsp/save_patches_old.txt new file mode 100644 index 0000000000..bdb7b5631c --- /dev/null +++ b/bsp/save_patches_old.txt @@ -0,0 +1,607 @@ +CreateKeyItemBitflags: + ; create space for key item flags + ; will involve (temporarily) overwriting the first four bytes of key item data + set #1, KEY_ITEMS_OFFSET_v0 + call GetGameDataPointer + seek #result + readword #temp + push #temp + + set #1, NUM_ITEMS_OFFSET_v0 + call GetGameDataPointer + set #3, #result + set #2, 4 + set #1, 291 + call ShiftSaveDataForward + + ; we want to preserve location of NUM_ITEMS_OFFSET_v0 + ; (will be location of key item flags) + seek #result + push #result + writeword #zero + set #1, KEY_ITEMS_OFFSET_v0 + call GetGameDataPointer + seek #result + pop #result + seekfwd 4 + + ; we first need to check the first four key item bytes that were overwritten + pop #temp +.key_items_loop_1 + and #1, #temp, 0xff + call .loop_common_get + jumpz #1, .key_items_loop_1 ; quietly ignore invalid/obsolete indexes + ifeq #1, 0xff, .done_loop + decrement #1 ; loop_common_get returns a valid index from 1-1c, we want 0-1b now + push #temp + call .loop_common_set + pop #temp + shiftright #temp, 8 + jumpnz #temp, .key_items_loop_1 + + ; now we can check the remaining key items +.key_items_loop_2 + readbyte #1 + call .loop_common_get + jumpz #1, .key_items_loop_2 + ifeq #1, 0xff, .done_loop + decrement #1 + call .loop_common_set + jump .key_items_loop_2 + +.done_loop + ; shift data from NumPCItems to before RegisteredItems + set #1, NUM_PC_ITEMS_OFFSET_v1 + call GetGameDataPointer + seek #result + set #2, 26 + set #1, 1682 + call ShiftSaveDataBack + + ; update registered items, quietly clear items that are out of range (i.e. Repel) + set #1, REG_KEY_ITEM_FLAGS_v1 + call GetGameDataPointer + seek #result + writebyte #zero ; clear wRegisteredItemFlags + pushpos + + set #1, REG_KEY_ITEMS_v0 + call GetGameDataPointer + seek #result + + set #temp, 4 +.registered_loop + getfilebyte #1 + call .loop_common_get + jumpz #1, .clear + ifeq #1, 0xff, .clear + decrement #1 +.clear + writebyte #1 + decrement #temp + jumpnz #temp, .registered_loop + + ; shift data from RegisteredItems to end of save bank + ; (obviously needs to check which bank is being affected) + poppos + set #2, 25 + set #1, 5275 + ifeq #savefile, 1, ShiftSaveDataBack + set #1, 667 + call ShiftSaveDataBack + + ; we don't care about overwriting game data with box data + ; if we're loading from a backup anyway + seek sBox_v1 + set #1, 4899 + jump ShiftSaveDataBack + +; ==== common key item loop functions ==== +.loop_common_get +; gets the appropriate adjusted flag value for key item loops +; returns #1 with the result (0xff if terminator is reached) + ifeq #1, 0xff, .continue + ifeq #1, 0x5b, .zero ; ignore EXP_SHARE key item + ifle #1, 0x54, .zero + ifge #1, 0x72, .zero + ifle #1, 0x5a, .sub0x54 + ifge #1, 0x70, .sub0x54 + decrement #1 ; subtract #temp, 0x55 +.sub0x54 + subtract #1, 0x54 + return +.zero + set #1, #zero +.continue + return + +.loop_common_set +; sets the appropriate key item flag + pushpos + seek #result + push #result + set #2, 1 + call FlagAction + pop #result + poppos + return + +; ==== resize pockets ==== +; positive differences shift forward, negative differences shift backwards +; #r0: OLD build number +; #1: item pocket size difference +; #2: medicine pocket size difference +; #3: balls pocket size difference +; #4: berries pocket size difference +; exits with error if build number is out of expected range +ResizePocketsToBuild4: + set #address, .pocket_patch_table +.patch_loop + gethalfwordinc #r0, #address + ifeq #r0, 0xffff, .error + ifge #r0, #build, .got_patch + add #address, 8 + jump .patch_loop +.got_patch + gethalfwordinc #1, #address + gethalfwordinc #2, #address + gethalfwordinc #3, #address + gethalfword #4, #address + jump ResizePockets + +.error + print .error_string + exit 1 + +.pocket_patch_table + ; highest build, items, medicine, balls, berries + dh 1 + dh 5, 7, 3, 11 + + dh 2 + dh 5, 0, 0, 10 + + dh 3 + dh -5, 0, 0, 4 + + dh -1 + +.error_string + string "ERROR: Incoming build number out of range for ResizePocketsByBuild" + +; ==== fix item indexes ==== +; #curbuild used as build number +; #1: offset table +AdjustItemIndexesToBuild4: + ; note: this function can be generalized for any build, + ; but you'd need to modify the ItemIndexOffsets tables for each update + ifne #curbuild, 4, .error ; debug statement + set #1, .pointer_table + jump AdjustItemIndexes + +.error + bufstring .error_string_build_1 + bufnumber #curbuild + bufstring .error_string_build_2 + printbuf + exit 1 + +.pointer_table + ; highest build, index offset table + dh 0 + dw ItemIndexOffsets_Build0ToBuild4 + + dh 1 + dw ItemIndexOffsets_Build1ToBuild4 + + dh 2 + dw ItemIndexOffsets_Build2ToBuild4 + + dh 3 + dw ItemIndexOffsets_Build3ToBuild4 + + dh -1 + +.error_string_build_1 + string "ERROR: Invalid curbuild number " +.error_string_build_2 + string " for AdjustItemIndexesBuild4" + +AdjustMonCaughtBallData: + set #1, .ball_adjust_callback + jump ApplyPartyPatches + +.ball_adjust_callback + add #1, CAUGHTBALL_OFFSET + seek #1 + getfilebyte #temp + and #result, #temp, 0x1f ; CAUGHTBALL_MASK + iflt #result, 0x17, .done + ifgt #result, 0x18, .done + increment #result + and #temp, 0xe0 ; ~CAUGHTBALL_MASK + add #result, #temp + writebyte #result +.done + return + +ReplaceSelfDestruct: + ; priority for replacement moves are Explosion -> Return -> Facade -> Rage (stored little-endian) + set #r0, .move_replace_table + jump ReplaceMove + +.move_replace_table + db SELFDESTRUCT ; move to replace + ; move to replace with, base PP + db EXPLOSION, 5 + db RETURN, 20 + db FACADE, 20 + db RAGE, 20 + +ReplaceMilkDrink: + ; without hacking, no mon should have both Softboiled (Fresh Snack) and Milk Drink + set #r0, .move_replace_table + jump ReplaceMove + +.move_replace_table + db MILK_DRINK ; move to replace + ; move to replace with, base PP + db FRESH_SNACK, 10 + db FRESH_SNACK, 10 + db FRESH_SNACK, 10 + db FRESH_SNACK, 10 + +AdjustMonAndMoveIndexesPostBuild4: + set #curbuild, 4 ; we're technically not at build 5 yet + iflt #build, 4, .build4_0 + call PrintBuild4VersionCheck ; old/misc.txt + jumptable #result + dw .build4_0 + dw .build4_1 + dw .build4_2 + dw .build4_3 + dw AdjustRhyperiorIndex ; build4_4 + dw DoNothing ; build4_5 + +.build4_0 + call AdjustShuckleAndEeveelutionIndexes +.build4_1 + call ReplaceMilkDrink +.build4_2 + call RemovePikabluGuyFlag +.build4_3 + call CeruleanCapeYellowForestSpawnFlags + ; fallthrough +AdjustRhyperiorIndex: + set #r0, .mon_adjust_table + jump AdjustPokemonIndexes + +.mon_adjust_table + ; upper bound, offset + ; terminated by -1 + dh 0xae, 0 ; MAGNEZONE + dh 0xb5, 1 ; BELLOSSOM + dh 0xb6, -7 ; RHYPERIOR + dh -1 + +AdjustShuckleAndEeveelutionIndexes: + set #r0, .mon_adjust_table + call AdjustPokemonIndexes + + seek 0x2a16 ; TODO: replace literal + set #1, 210 + set #2, #zero + call FlagAction + + seekfwd 32 + set #1, 210 + set #2, #zero + jump FlagAction + +.mon_adjust_table + dh 0xc0, 0 ; UMBREON + dh 0xc2, 59 ; LEAFEON/GLACEON + dh 0xd4, -2 ; SCIZOR + dh 0xe9, -1 ; PORYGON2 + dh 0xfb, 0 ; CELEBI + dh 0xfc, 2 ; SYLVEON + dh 0xfd, -20 ; PORYGON_Z + dh -1 + +RemovePikabluGuyFlag: + set #1, EVENT_FLAGS_OFFSET_v4 + call GetGameDataPointer + seek #result + + set #1, 2192 + set #2, .flag_adjust_table + jump ShiftFlags + +.flag_adjust_table + dh 2027, 0 + dh 2191, -1 + dh -1 + +CeruleanCapeYellowForestSpawnFlags: + seek sMapData_v4 ; same as Visited Spawns offset + ifeq #savefile, 1, .done + seek sBackupMapData_v4 +.done + set #1, 29 + set #2, .flag_adjust_table + jump ShiftFlags + +.flag_adjust_table + dh 6, 0 + dh 24, 1 + dh 27, 2 + dh 28, -21 + dh 29, -3 + dh -1 + +RemoveExtraOptionsByte: + seek sCheckValue1 ; new check value location + set #1, 8183 + ifeq #savefile, 1, .done + seek sBackupCheckValue1 + set #1, 3575 +.done + set #2, 1 + jump ShiftSaveDataBack + +ResetInitialOptions: + seek sCheckValue1 + ifeq #savefile, 1, .done + seek sBackupCheckValue1 +.done + seekback 1 ; initial options 2 + writebyte 0x80 ; 0b10000000 + print .reset_options_string + return + +.reset_options_string + string "*** New initial options were added 1 March 2020. You will therefore be asked to reset your in-game options. ***" + +AdjustVariableSprites: + set #curbuild, 5 + set #1, MAP_FROM_CONT_OFFSET_v5 + call GetGameDataPointer + set #3, #result + set #1, 7321 + ifeq #savefile, 1, .done + set #1, 2713 +.done + set #2, 1 + call ShiftSaveDataForward + seek #result + writebyte #zero + return + +LegendaryDogStatus: + set #1, POKEDEX_CAUGHT_OFFSET + call GetGameDataPointer + seek #result + + set #1, 323 + set #2, 3 + call ShiftSaveDataBack + writebyte #zero + + set #1, 10 + decrement #2 + call ShiftSaveDataBack + writebyte #zero + + set #1, 10 + decrement #2 + call ShiftSaveDataBack + writebyte #zero + return + +UpdateCharset: + seek sSaveVersion_v6 + writehalfword 0x0100 + set #length, NAME_LENGTH + set #3, #zero + + ; player name + set #1, PLAYER_NAME_OFFSET + set #2, #length + increment #3 + call .fix_wram + + ; rival name, backup name, trendy phrase + multiply #2, #length, 3 + decrement #3 + call .fix_wram + + ; box names + set #1, BOX_NAMES_OFFSET + multiply #2, BOX_NAME_LENGTH, 14 ; handles box names + call .fix_wram + + ; party mon OT + set #1, PARTY_MON_OT_OFFSET + multiply #2, #length, 6 ; handles party mon OT and nicknames + push #2 + increment #3 + call .fix_wram + + ; party mon nicknames + pop #2 + decrement #3 + call .replace_loop ; we don't need to seek game data + + ; breed mon 1 nick + set #1, BREED_MON_1_NICK_OFFSET + set #2, #length + call .fix_wram + + ; breed mon 1 OT + set #2, #length + increment #3 + call .replace_loop + + ; breed mon 2 nick + set #1, BREED_MON_2_NICK_OFFSET + set #2, #length + decrement #3 + call .fix_wram + + ; breed mon 2 OT + set #2, #length + increment #3 + call .replace_loop + + ; magikarp record holder OT + set #1, MAGIKARP_RECORD_HOLDER_NAME_OFFSET + set #2, #length + decrement #3 + call .fix_wram + + set #a, 41 ; message and author + set #b, 6 ; bytes in mailbox struct that contains no chars + + seek sPartyMail + set #1, 12 ; party mail + backup party mail + set #2, #a + set #4, #b + call .fix_sram_loop + + seek sMailbox + set #1, 10 + set #2, #a + set #4, #b + call .fix_sram_loop + + seek sBackupMailbox + set #1, 10 + set #2, #a + set #4, #b + call .fix_sram_loop + + seek sLinkBattleRecord + set #1, 5 + set #2, #length + decrement #2 ; NAME_LENGTH - 1 + set #4, 8 + call .fix_sram_loop + + ; hall of fame nicknames + seek sByteBeforeHallOfFame ; literally, the byte right before sHallOfFame + set #a, 30 +.hof_loop + seekfwd 8 ; go from end to mon 1 nickname + set #1, 6 + set #2, #length + decrement #2 ; MON_NAME_LENGTH - 1 + set #4, #1 + call .fix_sram_loop + decrement #a + jumpnz #a, .hof_loop + + set #a, 7 + set #b, 220 + set #c, 884 + + increment #3 + seek sBox1MonOT_v6 + set #1, #a + set #2, #b + set #4, #c + call .fix_sram_loop + + seek sBox8MonOT_v6 + set #1, #a + set #2, #b + set #4, #c + call .fix_sram_loop + + decrement #3 + seek sBox1MonNicknames_v6 + set #1, #a + set #2, #b + set #4, #c + call .fix_sram_loop + + seek sBox8MonNicknames_v6 + set #1, #a + set #2, #b + set #4, #c +.fix_sram_loop + ; #1: count + ; #2: # of bytes to search + ; #3: set hyper training bytes? + ; #4: # of bytes to skip + push #2 + call .replace_loop + pop #2 + decrement #1 + retz #1 + seekfwd #4 + jump .fix_sram_loop + +.fix_wram + ; #1: offset + ; #2: # of bytes to search + ; #3: set hyper training bytes? + call GetGameDataPointer + seek #result +.replace_loop + retz #2 + decrement #2 + remainder #temp, #2, #length + set #result, #zero + ifgt #temp, 2, .no_hyper_training + jumpnz #3, .write +.no_hyper_training + getfilebyte #result + set #address, .ngrams + getbyteinc #temp, #address +.ngram_loop + ifeq #result, #temp, .replace_ngram + getbyteinc #temp, #address + jumpnz #temp, .ngram_loop + ifne #result, 0x50, .write + set #result, 0x53 +.write + writebyte #result + jump .replace_loop + +.replace_ngram + set #temp, .Masuda + iflt #result, 0x30, .got_name + jumpz #3, .write ; Kenya nickname is stored as "Kenya@Ry@" + set #temp, .Randy +.got_name + seekback 1 + writedata #temp, 7 + subtract #2, 5 + jump .replace_loop + +.ngrams + db 0x22, 0x2f, 0x37, 0x3f, 0x41, 0 +.Masuda + db 0x8c, 0xa0, 0xb2, 0xb4, 0xa3, 0xa0, 0x53 +.Randy + db 0x91, 0xa0, 0xad, 0xa3, 0xb8, 0x53, 0x53 + +ChangeGyaradosForm: + set #curbuild, 6 + set #1, .mon_callback + call ApplyPartyPatches + +.mon_callback + seek #1 + getfilebyte #temp + ifne #temp, GYARADOS, .done + add #1, FORM_OFFSET + seek #1 + getfilebyte #result + and #temp, #result, FORM_MASK + ifne #temp, 0x0f, .done + increment #result + increment #result ; form increases from 0x0f to 0x11 + writebyte #result +.done + return diff --git a/bsp/savefile.txt b/bsp/savefile.txt new file mode 100644 index 0000000000..5779d66444 --- /dev/null +++ b/bsp/savefile.txt @@ -0,0 +1,206 @@ +PatchSavefile: + call CheckNotEmpty + call CheckSavefileIsValid + call PrintSavefileMessage + set #temp, CURRENT_BUILD + ifgt #build, 9999, .invalid_build + ifgt #build, #temp, .future_build + ifeq #build, #temp, .latest_build + push #temp + bufstring .savefile_build_string_1 + set #1, #build + set #2, 4 + call PrintWithLeadingZeros + bufstring .savefile_build_string_2 + printbuf + menu #result, YesNoMenu + jumpnz #result, .refused + call PokecenterCheck + set #address, SavePatches +.loop + gethalfwordinc #curbuild, #address + jumpz #curbuild, .done + getwordinc #patch, #address + ifle #curbuild, #build, .loop + push #address + call #patch + pop #address + jump .loop +.done + pop #build + set #curbuild, #build + call FixSavefileChecksum + seek sSaveVersion + shiftleft #temp, #build, 8 + shiftright #build, 8 + or #build, #temp + writehalfword #build + print .done_string + exit #zero + +.latest_build + print .latest_build_string + exit 1 + +.future_build + bufstring .future_build_string_1 + set #1, #build + set #2, 4 + call PrintWithLeadingZeros + bufstring .future_build_string_2 + printbuf +.refused + exit 1 + +.invalid_build + print .invalid_build_string + exit 1 + +.latest_build_string + string "The savefile already corresponds to the current build of Polished Crystal. No patching is necessary." +.future_build_string_1 + string "The savefile indicates it corresponds to build " +.future_build_string_2 + string ", which is higher than the current build of Polished Crystal. The savefile cannot be patched." +.invalid_build_string + string "The savefile contains an invalid build number, and thus cannot be patched." +.savefile_build_string_1 + string "The savefile corresponds to build " +.savefile_build_string_2 + string ". Patch to the current build? (WARNING: It is strongly recommended that you save your game on the first floor of a Pokemon Center before patching.)" +.done_string + string "The savefile was patched successfully." + +CheckSavefileIsValid: + ; returns 0 if invalid, 1 if primary is valid, or 2 if backup is valid + set #savefile, 1 + set #2, .backup ; we're gonna be using #1 for checksum calcs and storing check value 1 + set #address, CheckPointers + jump .check_1 + +.backup + set #savefile, 2 + set #2, .invalid + set #address, BackupCheckPointers + ; fallthrough +.check_1 + gethalfwordinc #build, #address + gethalfwordinc #temp, #address + seek #temp + readbyte #1 + ifeq #1, 97, .get_build + ifne #build, 7, .not_build_6 + decrement #build ; special case for v6 since it shares the same check value addresses as v7 except sCheckValue1 == 99 +.not_build_6 + gethalfwordinc #temp, #address ; skip sSaveVersion + ifeq #1, 99, .check_2 +.check_1_cont + ifeq #build, #zero, #2 + getwordinc #temp, #address ; add #address, 4 + jump .check_1 + +.get_build + gethalfwordinc #temp, #address + ifeq #temp, #zero, .check_1_cont ; if this is true, a value was 97 when it shouldn't have been + seek #temp + getfilehalfword #build + shiftright #temp, #build, 8 + shiftleft #build, 8 + or #build, #temp + and #build, 0xffff +.check_2 + gethalfwordinc #temp, #address + seek #temp + readbyte #temp + ifeq #temp, 0x7f, .checksum + ifeq #build, #zero, #2 + add #address, 2 + jump .check_1 + +.checksum + set #curbuild, #build + set #1, #zero + push #address + call GetGameDataPointer + seek #result + call CalculateSavefileChecksum + pop #address + gethalfwordinc #temp, #address + seek #temp + readhalfword #temp + ifeq #temp, #result, .valid + ifeq #build, #zero, #2 + jump .check_1 + +.invalid + set #savefile, #zero +.valid + return + +CalculateSavefileChecksum: + ; returns the checksum; calculates starting at the current address + set #address, GameDataSizes +.loop + gethalfwordinc #temp, #address + gethalfwordinc #length, #address + iflt #temp, #build, .loop +DoSavefileChecksumCalculation: + set #result, #zero +.loop + readbyte #temp + add #result, #temp + decrement #length + jumpnz #length, .loop + and #result, 0xffff + return + +FixSavefileChecksum: + set #1, #zero + call GetGameDataPointer + seek #result + call CalculateSavefileChecksum + seek sChecksum + ifeq #savefile, 1, .ok + seek sBackupChecksum +.ok + writehalfword #result + return + +PrintSavefileMessage: + jumptable #savefile + dw .invalid + dw .ok + dw .backup + +.backup + print .backup_string +.ok + return + +.invalid + print .invalid_string + menu #result, YesNoMenu + set #savefile, 1 + retz #result + exit #result + +.backup_string + string "The main save data in the savefile seems to be invalid, but the game can restore it from the backup. Unexpected errors may occur." +.invalid_string + string "The savefile seems to be invalid or corrupted. Are you sure you want to continue?" + +CheckNotEmpty: + seek #zero + getfilebyte #result + set #length, SAVE_SIZE +.loop + readbyte #temp + subtract #temp, #result + retnz #temp + decrement #length + jumpnz #length, .loop + print .empty_savefile_string + exit 1 + +.empty_savefile_string + string "The savefile seems to be empty. Make sure to save the game from within the game before patching, not using emulator savestates. Also, remember that you only need to patch the savefile after updating to a new version; patching before starting a new game is not necessary." diff --git a/bsp/sram_offsets.txt b/bsp/sram_offsets.txt new file mode 100644 index 0000000000..8260e564cb --- /dev/null +++ b/bsp/sram_offsets.txt @@ -0,0 +1,74 @@ +GameDataStartPointers: +; version number, game data start, backup game data start + dh 4 + dw sGameData_v0, sBackupGameData_v0 + + dh -1 + dw sGameData, sBackupGameData + +GameDataSizes: +; build number, game data size + dh 0 + dh GAME_DATA_SIZE_v0 + + dh 1 + dh GAME_DATA_SIZE_v1 + + dh 2 + dh GAME_DATA_SIZE_v2 + + dh 3 + dh GAME_DATA_SIZE_v3 + + dh 5 + dh GAME_DATA_SIZE_v4 + + dh -1 + dh GAME_DATA_SIZE + +CheckPointers: +; stored in DESCENDING order, build 0 check pointers should be end of list +; build number +; check value 1, build number pointer (zero if skipped), check value 2, checksum + dh 7 + dh sCheckValue1, sSaveVersion, sCheckValue2, sChecksum + + dh 5 + dh sCheckValue1, 0, sCheckValue2_v5, sChecksum_v5 + + dh 4 + dh sCheckValue1_v0, 0, sCheckValue2_v4, sChecksum_v4 + + dh 3 + dh sCheckValue1_v0, 0, sCheckValue2_v3, sChecksum_v3 + + dh 2 + dh sCheckValue1_v0, 0, sCheckValue2_v2, sChecksum_v2 + + dh 1 + dh sCheckValue1_v0, 0, sCheckValue2_v1, sChecksum_v1 + + dh 0 + dh sCheckValue1_v0, 0, sCheckValue2_v0, sChecksum_v0 + +BackupCheckPointers: + dh 7 + dh sBackupCheckValue1, sSaveVersion, sBackupCheckValue2, sBackupChecksum + + dh 5 + dh sBackupCheckValue1, 0, sBackupCheckValue2_v5, sBackupChecksum_v5 + + dh 4 + dh sBackupCheckValue1_v0, 0, sBackupCheckValue2_v4, sBackupChecksum_v4 + + dh 3 + dh sBackupCheckValue1_v0, 0, sBackupCheckValue2_v3, sBackupChecksum_v3 + + dh 2 + dh sBackupCheckValue1_v0, 0, sBackupCheckValue2_v2, sBackupChecksum_v2 + + dh 1 + dh sBackupCheckValue1_v0, 0, sBackupCheckValue2_v1, sBackupChecksum_v1 + + dh 0 + dh sBackupCheckValue1_v0, 0, sBackupCheckValue2_v0, sBackupChecksum_v0 diff --git a/constants.asm b/constants.asm index 2c22ad46a0..827cac1542 100644 --- a/constants.asm +++ b/constants.asm @@ -39,6 +39,7 @@ INCLUDE "constants/music_constants.asm" INCLUDE "constants/music_player_constants.asm" INCLUDE "constants/nature_constants.asm" INCLUDE "constants/npc_trade_constants.asm" +INCLUDE "constants/pc_constants.asm" INCLUDE "constants/phone_constants.asm" INCLUDE "constants/pokemon_constants.asm" INCLUDE "constants/pokemon_data_constants.asm" diff --git a/constants/menu_constants.asm b/constants/menu_constants.asm index 20aa3fcdba..14ca3bebfd 100644 --- a/constants/menu_constants.asm +++ b/constants/menu_constants.asm @@ -69,8 +69,6 @@ MONMENU_MENUOPTION EQU 1 NUM_MONMENU_ITEMS EQU 8 -SAVE_VERSION EQU 1 - ; start/select menu return values HMENURETURN_SCRIPT EQU %10000000 HMENURETURN_ASM EQU %11111111 diff --git a/constants/misc_constants.asm b/constants/misc_constants.asm index e033a04dbd..29bdc9fcb0 100644 --- a/constants/misc_constants.asm +++ b/constants/misc_constants.asm @@ -9,8 +9,12 @@ TRUE EQU 1 const CHECK_FLAG ; save file corruption check values -SAVE_CHECK_VALUE_1 EQU 99 -SAVE_CHECK_VALUE_2 EQU 127 +SAVE_CHECK_VALUE_1 EQU 97 +SAVE_CHECK_VALUE_2 EQU 127 +SAVE_CHECK_VALUE_1_OLD EQU 99 ; check digit before save version 7 + +; save file version +SAVE_VERSION EQU 7 ; time of day boundaries MORN_HOUR EQU 5 ; 5 AM - 9 AM (4 hours) @@ -54,3 +58,5 @@ BUG_CONTESTANT_SIZE EQU 4 const ERR_STACK_UNDERFLOW const ERR_BT_STATE const ERR_VERSION_MISMATCH + const ERR_OLDBOX + const ERR_NEWBOX diff --git a/constants/pc_constants.asm b/constants/pc_constants.asm new file mode 100644 index 0000000000..ad36ffb69b --- /dev/null +++ b/constants/pc_constants.asm @@ -0,0 +1,40 @@ +; pc themes (see data/bills_pc_theme_names.asm) + const_def + const THEME_STANDARD + const THEME_PRO + const THEME_MOBILE + const THEME_CLASSIC + const THEME_BLISS + const THEME_CONTRAST + const THEME_NATURE + const THEME_TRUTH + const THEME_IDEALS + const THEME_LIGHT + const THEME_DARKNESS + const THEME_MATTE + const THEME_NORMAL + const THEME_FIGHTING + const THEME_FLYING + const THEME_POISON + const THEME_GROUND + const THEME_ROCK + const THEME_BUG + const THEME_GHOST + const THEME_STEEL + const THEME_FIRE + const THEME_WATER + const THEME_GRASS + const THEME_ELECTRIC + const THEME_PSYCHIC + const THEME_ICE + const THEME_DRAGON + const THEME_DARK + const THEME_FAIRY +NUM_BILLS_PC_THEMES EQU const_value + +; sprite animation parameters +PCANIM_STATIC EQU 0 ; used when holding stuff, we don't want to bop then +PCANIM_ANIMATE EQU 90 ; baseline +PCANIM_PICKUP EQU 91 ; we're picking up/placing down something +PCANIM_PICKUP_NEXT EQU 98 ; cursor is at the bottom, ready for pickup +PCANIM_QUICKFRAMES EQU 9 diff --git a/constants/pokemon_data_constants.asm b/constants/pokemon_data_constants.asm index 7ade5954bf..71dd1e5f0b 100644 --- a/constants/pokemon_data_constants.asm +++ b/constants/pokemon_data_constants.asm @@ -75,14 +75,13 @@ GENDER_UNKNOWN EQU %1111 const EGG_DRAGON ; e const EGG_NONE ; f (Undiscovered) -; party_struct members (see macros/wram.asm) +; breed_struct and party_struct members (see macros/wram.asm) rsreset MON_SPECIES rb MON_ITEM rb MON_MOVES rb NUM_MOVES MON_ID rw MON_EXP rb 3 - MON_EVS rb NUM_STATS rsset MON_EVS MON_HP_EV rb @@ -115,7 +114,7 @@ MON_CAUGHTBALL EQU MON_CAUGHTGENDER MON_CAUGHTLEVEL rb MON_CAUGHTLOCATION rb MON_LEVEL rb -BOXMON_STRUCT_LENGTH EQU _RS +BREEDMON_STRUCT_LENGTH EQU _RS MON_STATUS rb rb_skip MON_HP rw @@ -129,6 +128,53 @@ MON_SAT rw MON_SDF rw PARTYMON_STRUCT_LENGTH EQU _RS +; savemon_struct members (see macros/wram.asm) +rsreset +SAVEMON_SPECIES rb +SAVEMON_ITEM rb +SAVEMON_MOVES rb NUM_MOVES +SAVEMON_ID rw +SAVEMON_EXP rb 3 +SAVEMON_EVS rb NUM_STATS +rsset SAVEMON_EVS +SAVEMON_HP_EV rb +SAVEMON_ATK_EV rb +SAVEMON_DEF_EV rb +SAVEMON_SPD_EV rb +SAVEMON_SAT_EV rb +SAVEMON_SDF_EV rb +SAVEMON_DVS rb NUM_STATS / 2 +rsset SAVEMON_DVS +SAVEMON_HP_ATK_DV rb +SAVEMON_DEF_SPD_DV rb +SAVEMON_SAT_SDF_DV rb +SAVEMON_PERSONALITY rw +SAVEMON_SHINY EQU SAVEMON_PERSONALITY +SAVEMON_ABILITY EQU SAVEMON_PERSONALITY +SAVEMON_NATURE EQU SAVEMON_PERSONALITY +SAVEMON_GENDER EQU SAVEMON_PERSONALITY + 1 +SAVEMON_IS_EGG EQU SAVEMON_PERSONALITY + 1 +SAVEMON_EXTSPECIES EQU SAVEMON_PERSONALITY + 1 +SAVEMON_FORM EQU SAVEMON_PERSONALITY + 1 +; savemon_struct is identical to party_struct before this point +SAVEMON_PP_UPS rb +; savemon_struct is shifted from party_struct beyond this point +SAVEMON_HAPPINESS rb +SAVEMON_PKRUS rb +SAVEMON_CAUGHTDATA rb 3 +rsset SAVEMON_CAUGHTDATA +SAVEMON_CAUGHTGENDER rb +SAVEMON_CAUGHTTIME EQU SAVEMON_CAUGHTGENDER +SAVEMON_CAUGHTBALL EQU SAVEMON_CAUGHTGENDER +SAVEMON_CAUGHTLEVEL rb +SAVEMON_CAUGHTLOCATION rb +SAVEMON_LEVEL rb +; savemon_struct is different from party_struct beyond this point +SAVEMON_EXTRA rb 3 +SAVEMON_NICKNAME rb MON_NAME_LENGTH - 1 +SAVEMON_OT rb PLAYER_NAME_LENGTH - 1 +SAVEMON_STRUCT_LENGTH EQU _RS + ; personality SHINY_MASK EQU %10000000 @@ -173,8 +219,10 @@ HYPER_TRAINING_MASK EQU %11111100 PARTY_LENGTH EQU 6 ; boxes -MONS_PER_BOX EQU 20 -NUM_BOXES EQU 14 +MONS_PER_BOX EQU 20 +MONDB_ENTRIES EQU 167 +MIN_MONDB_SLACK EQU 10 +NUM_BOXES EQU (MONDB_ENTRIES * 2 - MIN_MONDB_SLACK) / MONS_PER_BOX ; 16 ; hall of fame HOF_MON_LENGTH EQU 1 + 2 + 2 + 1 + (MON_NAME_LENGTH - 1) ; species, id, dvs, level, nick diff --git a/constants/sprite_anim_constants.asm b/constants/sprite_anim_constants.asm index c88e5dd407..48ac63170e 100644 --- a/constants/sprite_anim_constants.asm +++ b/constants/sprite_anim_constants.asm @@ -58,6 +58,11 @@ NUM_SPRITEANIMDICT_ENTRIES EQU 10 const SPRITE_ANIM_INDEX_INTRO_SUICUNE_AWAY const SPRITE_ANIM_INDEX_CELEBI const SPRITE_ANIM_INDEX_MAX_STAT_SPARKLE + const SPRITE_ANIM_INDEX_PC_CURSOR + const SPRITE_ANIM_INDEX_PC_QUICK + const SPRITE_ANIM_INDEX_PC_MODE + const SPRITE_ANIM_INDEX_PC_MODE2 + const SPRITE_ANIM_INDEX_PC_PACK NUM_SPRITE_ANIM_INDEXES EQU const_value ; DoAnimFrame.Jumptable indexes (see engine/gfx/sprite_anims.asm) @@ -90,6 +95,10 @@ NUM_SPRITE_ANIM_INDEXES EQU const_value const SPRITE_ANIM_SEQ_SUICUNE_AWAY const SPRITE_ANIM_SEQ_CELEBI const SPRITE_ANIM_SEQ_MAX_STAT_SPARKLE + const SPRITE_ANIM_SEQ_PC_CURSOR + const SPRITE_ANIM_SEQ_PC_QUICK + const SPRITE_ANIM_SEQ_PC_MODE + const SPRITE_ANIM_SEQ_PC_PACK NUM_SPRITE_ANIM_SEQS EQU const_value ; SpriteAnimFrameData indexes (see data/sprite_anims/framesets.asm) @@ -143,6 +152,11 @@ NUM_SPRITE_ANIM_SEQS EQU const_value const SPRITE_ANIM_FRAMESET_CELEBI_LEFT const SPRITE_ANIM_FRAMESET_CELEBI_RIGHT const SPRITE_ANIM_FRAMESET_MAX_STAT_SPARKLE + const SPRITE_ANIM_FRAMESET_PC_CURSOR + const SPRITE_ANIM_FRAMESET_PC_QUICK + const SPRITE_ANIM_FRAMESET_PC_MODE + const SPRITE_ANIM_FRAMESET_PC_MODE2 + const SPRITE_ANIM_FRAMESET_PC_PACK NUM_SPRITE_ANIM_FRAMESETS EQU const_value ; SpriteAnimOAMData indexes (see data/sprite_anims/oam.asm) @@ -222,4 +236,9 @@ NUM_SPRITE_ANIM_FRAMESETS EQU const_value const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_10 const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11 const SPRITE_ANIM_OAMSET_MAX_STAT_SPARKLE + const SPRITE_ANIM_OAMSET_PC_CURSOR + const SPRITE_ANIM_OAMSET_PC_QUICK + const SPRITE_ANIM_OAMSET_PC_MODE + const SPRITE_ANIM_OAMSET_PC_MODE2 + const SPRITE_ANIM_OAMSET_PC_PACK NUM_SPRITE_ANIM_OAMSETS EQU const_value diff --git a/constants/text_constants.asm b/constants/text_constants.asm index 166b28b5b1..7c0a81579c 100644 --- a/constants/text_constants.asm +++ b/constants/text_constants.asm @@ -33,12 +33,14 @@ TEXTBOX_INNERY EQU TEXTBOX_Y + 2 TEXTBOX_FRAME_TILES EQU 6 ; PrintNum bit flags - const_def 5 + const_def 4 + const PRINTNUM_DELAY_F ; 4 const PRINTNUM_MONEY_F ; 5 const PRINTNUM_LEFTALIGN_F ; 6 const PRINTNUM_LEADINGZEROS_F ; 7 ; PrintNum arguments (see engine/math/print_num.asm) +PRINTNUM_DELAY EQU 1 << PRINTNUM_DELAY_F PRINTNUM_MONEY EQU 1 << PRINTNUM_MONEY_F PRINTNUM_LEFTALIGN EQU 1 << PRINTNUM_LEFTALIGN_F PRINTNUM_LEADINGZEROS EQU 1 << PRINTNUM_LEADINGZEROS_F diff --git a/data/events/special_pointers.asm b/data/events/special_pointers.asm index a559292949..7e27211b7e 100644 --- a/data/events/special_pointers.asm +++ b/data/events/special_pointers.asm @@ -146,7 +146,6 @@ SpecialsPointers:: add_special GiveMystriEgg add_special Special_ReiBlessing add_special BillBoxSwitchCheck - add_special BillBoxSwitch add_special HealPartyEvenForNuzlocke add_special SaveMusic add_special RestoreMusic diff --git a/data/items/names.asm b/data/items/names.asm index 732b39ac20..8c9918b335 100644 --- a/data/items/names.asm +++ b/data/items/names.asm @@ -1,264 +1,264 @@ ItemNames:: - db "Park Ball@" - db "# Ball@" - db "Great Ball@" - db "Ultra Ball@" - db "Master Ball@" - db "Safari Ball@" - db "Level Ball@" - db "Lure Ball@" - db "Moon Ball@" - db "Friend Ball@" - db "Fast Ball@" - db "Heavy Ball@" - db "Love Ball@" - db "AbilityPatch@" - db "Repeat Ball@" - db "Timer Ball@" - db "Nest Ball@" - db "Net Ball@" - db "Dive Ball@" - db "Luxury Ball@" - db "Heal Ball@" - db "Quick Ball@" - db "Dusk Ball@" - db "Dream Ball@" - db "Premier Ball@" - db "Cherish Ball@" - db "Potion@" - db "Super Potion@" - db "Hyper Potion@" - db "Max Potion@" - db "Antidote@" - db "Burn Heal@" - db "ParalyzeHeal@" - db "Awakening@" - db "Ice Heal@" - db "Full Heal@" - db "Full Restore@" - db "Revive@" - db "Max Revive@" - db "Ether@" - db "Max Ether@" - db "Elixir@" - db "Max Elixir@" - db "HP Up@" - db "Protein@" - db "Iron@" - db "Carbos@" - db "Calcium@" - db "Zinc@" - db "Rare Candy@" - db "PP Up@" - db "PP Max@" - db "Fresh Water@" - db "Soda Pop@" - db "Lemonade@" - db "Moomoo Milk@" + rawchar "Park Ball@" + rawchar "Poké Ball@" + rawchar "Great Ball@" + rawchar "Ultra Ball@" + rawchar "Master Ball@" + rawchar "Safari Ball@" + rawchar "Level Ball@" + rawchar "Lure Ball@" + rawchar "Moon Ball@" + rawchar "Friend Ball@" + rawchar "Fast Ball@" + rawchar "Heavy Ball@" + rawchar "Love Ball@" + rawchar "AbilityPatch@" + rawchar "Repeat Ball@" + rawchar "Timer Ball@" + rawchar "Nest Ball@" + rawchar "Net Ball@" + rawchar "Dive Ball@" + rawchar "Luxury Ball@" + rawchar "Heal Ball@" + rawchar "Quick Ball@" + rawchar "Dusk Ball@" + rawchar "Dream Ball@" + rawchar "Premier Ball@" + rawchar "Cherish Ball@" + rawchar "Potion@" + rawchar "Super Potion@" + rawchar "Hyper Potion@" + rawchar "Max Potion@" + rawchar "Antidote@" + rawchar "Burn Heal@" + rawchar "ParalyzeHeal@" + rawchar "Awakening@" + rawchar "Ice Heal@" + rawchar "Full Heal@" + rawchar "Full Restore@" + rawchar "Revive@" + rawchar "Max Revive@" + rawchar "Ether@" + rawchar "Max Ether@" + rawchar "Elixir@" + rawchar "Max Elixir@" + rawchar "HP Up@" + rawchar "Protein@" + rawchar "Iron@" + rawchar "Carbos@" + rawchar "Calcium@" + rawchar "Zinc@" + rawchar "Rare Candy@" + rawchar "PP Up@" + rawchar "PP Max@" + rawchar "Fresh Water@" + rawchar "Soda Pop@" + rawchar "Lemonade@" + rawchar "Moomoo Milk@" if DEF(FAITHFUL) - db "RageCandyBar@" + rawchar "RageCandyBar@" else - db "Cake of Rage@" + rawchar "Cake of Rage@" endc - db "PewterCrunch@" - db "Sacred Ash@" - db "EnergyPowder@" - db "Energy Root@" - db "Heal Powder@" - db "Revival Herb@" - db "X Attack@" - db "X Defend@" - db "X Speed@" - db "X Spcl.Atk@" - db "X Spcl.Def@" - db "X Accuracy@" - db "Dire Hit@" + rawchar "PewterCrunch@" + rawchar "Sacred Ash@" + rawchar "EnergyPowder@" + rawchar "Energy Root@" + rawchar "Heal Powder@" + rawchar "Revival Herb@" + rawchar "X Attack@" + rawchar "X Defend@" + rawchar "X Speed@" + rawchar "X Spcl.Atk@" + rawchar "X Spcl.Def@" + rawchar "X Accuracy@" + rawchar "Dire Hit@" if DEF(FAITHFUL) - db "Guard Spec.@" + rawchar "Guard Spec.@" else - db "Guard Stats@" + rawchar "Guard Stats@" endc - db "Repel@" - db "Super Repel@" - db "Max Repel@" - db "Escape Rope@" - db "# Doll@" - db "Ability Cap@" - db "Leaf Stone@" - db "Fire Stone@" - db "Water Stone@" - db "ThunderStone@" - db "Moon Stone@" - db "Sun Stone@" - db "Dusk Stone@" - db "Dawn Stone@" - db "Shiny Stone@" - db "Ice Stone@" - db "Everstone@" - db "Exp.Share@" - db "Cheri Berry@" - db "Chesto Berry@" - db "Pecha Berry@" - db "Rawst Berry@" - db "Aspear Berry@" - db "Leppa Berry@" - db "Oran Berry@" - db "Persim Berry@" - db "Lum Berry@" - db "Sitrus Berry@" - db "Figy Berry@" - db "Pomeg Berry@" - db "Kelpsy Berry@" - db "Qualot Berry@" - db "Hondew Berry@" - db "Grepa Berry@" - db "Tamato Berry@" - db "Liechi Berry@" - db "Ganlon Berry@" - db "Salac Berry@" - db "Petaya Berry@" - db "Apicot Berry@" - db "Lansat Berry@" - db "Starf Berry@" - db "Enigma Berry@" - db "Custap Berry@" - db "Jaboca Berry@" - db "Rowap Berry@" - db "Kee Berry@" - db "MarangaBerry@" - db "Berry Juice@" - db "Silk Scarf@" - db "Black Belt@" - db "Sharp Beak@" - db "Poison Barb@" - db "Soft Sand@" - db "Hard Stone@" - db "SilverPowder@" - db "Spell Tag@" - db "Metal Coat@" - db "Charcoal@" - db "Mystic Water@" - db "Miracle Seed@" - db "Magnet@" - db "TwistedSpoon@" - db "NeverMeltIce@" - db "Dragon Fang@" - db "BlackGlasses@" - db "Pink Bow@" - db "BrightPowder@" - db "Scope Lens@" - db "Quick Claw@" - db "King's Rock@" - db "Focus Band@" - db "Leftovers@" - db "Lucky Egg@" - db "Amulet Coin@" - db "Cleanse Tag@" - db "Smoke Ball@" - db "Berserk Gene@" - db "Light Ball@" - db "Stick@" - db "Thick Club@" - db "Lucky Punch@" - db "Metal Powder@" - db "Quick Powder@" - db "Armor Suit@" - db "Air Balloon@" - db "Assault Vest@" - db "Big Root@" - db "Binding Band@" - db "Destiny Knot@" - db "Eviolite@" - db "Expert Belt@" - db "Focus Sash@" - db "Grip Claw@" - db "Life Orb@" - db "Light Clay@" - db "Metronome@" - db "Muscle Band@" - db "Protect Pads@" - db "Rocky Helmet@" - db "Safe Goggles@" - db "Shed Shell@" - db "Shell Bell@" - db "Soothe Bell@" - db "Weak Policy@" - db "Wide Lens@" - db "Wise Glasses@" - db "Zoom Lens@" - db "Eject Button@" - db "Lagging Tail@" - db "Iron Ball@" - db "Ring Target@" - db "Red Card@" - db "Absorb Bulb@" - db "Cell Battery@" - db "LuminousMoss@" - db "Snowball@" - db "Eject Pack@" - db "Room Service@" - db "BlundrPolicy@" - db "Throat Spray@" - db "Heavy Boots@" - db "UtilUmbrella@" - db "Mental Herb@" - db "Power Herb@" - db "White Herb@" - db "Damp Rock@" - db "Heat Rock@" - db "Smooth Rock@" - db "Icy Rock@" - db "Choice Band@" - db "Choice Scarf@" - db "Choice Specs@" - db "Flame Orb@" - db "Toxic Orb@" - db "Black Sludge@" - db "Macho Brace@" - db "Power Weight@" - db "Power Bracer@" - db "Power Belt@" - db "Power Lens@" - db "Power Band@" - db "Power Anklet@" - db "Dragon Scale@" - db "Up-Grade@" - db "Dubious Disc@" - db "Protector@" - db "Electirizer@" - db "Magmarizer@" - db "Razor Fang@" - db "Razor Claw@" - db "Odd Souvenir@" - db "Nugget@" - db "Big Nugget@" - db "TinyMushroom@" - db "Big Mushroom@" - db "BalmMushroom@" - db "Pearl@" - db "Big Pearl@" - db "Pearl String@" - db "Stardust@" - db "Star Piece@" - db "Brick Piece@" - db "Rare Bone@" - db "Silver Leaf@" - db "Gold Leaf@" - db "SlowpokeTail@" - db "Bottle Cap@" - db "Helix Fossil@" - db "Dome Fossil@" - db "Old Amber@" - db "Mulch@" - db "Sweet Honey@" - db "Mint Leaf@" - db "Flower Mail@" - db "Surf Mail@" - db "LiteBlueMail@" - db "PortraitMail@" - db "Lovely Mail@" - db "Eon Mail@" - db "Morph Mail@" - db "BlueSky Mail@" - db "Music Mail@" - db "Mirage Mail@" + rawchar "Repel@" + rawchar "Super Repel@" + rawchar "Max Repel@" + rawchar "Escape Rope@" + rawchar "Poké Doll@" + rawchar "Ability Cap@" + rawchar "Leaf Stone@" + rawchar "Fire Stone@" + rawchar "Water Stone@" + rawchar "ThunderStone@" + rawchar "Moon Stone@" + rawchar "Sun Stone@" + rawchar "Dusk Stone@" + rawchar "Dawn Stone@" + rawchar "Shiny Stone@" + rawchar "Ice Stone@" + rawchar "Everstone@" + rawchar "Exp.Share@" + rawchar "Cheri Berry@" + rawchar "Chesto Berry@" + rawchar "Pecha Berry@" + rawchar "Rawst Berry@" + rawchar "Aspear Berry@" + rawchar "Leppa Berry@" + rawchar "Oran Berry@" + rawchar "Persim Berry@" + rawchar "Lum Berry@" + rawchar "Sitrus Berry@" + rawchar "Figy Berry@" + rawchar "Pomeg Berry@" + rawchar "Kelpsy Berry@" + rawchar "Qualot Berry@" + rawchar "Hondew Berry@" + rawchar "Grepa Berry@" + rawchar "Tamato Berry@" + rawchar "Liechi Berry@" + rawchar "Ganlon Berry@" + rawchar "Salac Berry@" + rawchar "Petaya Berry@" + rawchar "Apicot Berry@" + rawchar "Lansat Berry@" + rawchar "Starf Berry@" + rawchar "Enigma Berry@" + rawchar "Custap Berry@" + rawchar "Jaboca Berry@" + rawchar "Rowap Berry@" + rawchar "Kee Berry@" + rawchar "MarangaBerry@" + rawchar "Berry Juice@" + rawchar "Silk Scarf@" + rawchar "Black Belt@" + rawchar "Sharp Beak@" + rawchar "Poison Barb@" + rawchar "Soft Sand@" + rawchar "Hard Stone@" + rawchar "SilverPowder@" + rawchar "Spell Tag@" + rawchar "Metal Coat@" + rawchar "Charcoal@" + rawchar "Mystic Water@" + rawchar "Miracle Seed@" + rawchar "Magnet@" + rawchar "TwistedSpoon@" + rawchar "NeverMeltIce@" + rawchar "Dragon Fang@" + rawchar "BlackGlasses@" + rawchar "Pink Bow@" + rawchar "BrightPowder@" + rawchar "Scope Lens@" + rawchar "Quick Claw@" + rawchar "King's Rock@" + rawchar "Focus Band@" + rawchar "Leftovers@" + rawchar "Lucky Egg@" + rawchar "Amulet Coin@" + rawchar "Cleanse Tag@" + rawchar "Smoke Ball@" + rawchar "Berserk Gene@" + rawchar "Light Ball@" + rawchar "Stick@" + rawchar "Thick Club@" + rawchar "Lucky Punch@" + rawchar "Metal Powder@" + rawchar "Quick Powder@" + rawchar "Armor Suit@" + rawchar "Air Balloon@" + rawchar "Assault Vest@" + rawchar "Big Root@" + rawchar "Binding Band@" + rawchar "Destiny Knot@" + rawchar "Eviolite@" + rawchar "Expert Belt@" + rawchar "Focus Sash@" + rawchar "Grip Claw@" + rawchar "Life Orb@" + rawchar "Light Clay@" + rawchar "Metronome@" + rawchar "Muscle Band@" + rawchar "Protect Pads@" + rawchar "Rocky Helmet@" + rawchar "Safe Goggles@" + rawchar "Shed Shell@" + rawchar "Shell Bell@" + rawchar "Soothe Bell@" + rawchar "Weak Policy@" + rawchar "Wide Lens@" + rawchar "Wise Glasses@" + rawchar "Zoom Lens@" + rawchar "Eject Button@" + rawchar "Lagging Tail@" + rawchar "Iron Ball@" + rawchar "Ring Target@" + rawchar "Red Card@" + rawchar "Absorb Bulb@" + rawchar "Cell Battery@" + rawchar "LuminousMoss@" + rawchar "Snowball@" + rawchar "Eject Pack@" + rawchar "Room Service@" + rawchar "BlundrPolicy@" + rawchar "Throat Spray@" + rawchar "Heavy Boots@" + rawchar "UtilUmbrella@" + rawchar "Mental Herb@" + rawchar "Power Herb@" + rawchar "White Herb@" + rawchar "Damp Rock@" + rawchar "Heat Rock@" + rawchar "Smooth Rock@" + rawchar "Icy Rock@" + rawchar "Choice Band@" + rawchar "Choice Scarf@" + rawchar "Choice Specs@" + rawchar "Flame Orb@" + rawchar "Toxic Orb@" + rawchar "Black Sludge@" + rawchar "Macho Brace@" + rawchar "Power Weight@" + rawchar "Power Bracer@" + rawchar "Power Belt@" + rawchar "Power Lens@" + rawchar "Power Band@" + rawchar "Power Anklet@" + rawchar "Dragon Scale@" + rawchar "Up-Grade@" + rawchar "Dubious Disc@" + rawchar "Protector@" + rawchar "Electirizer@" + rawchar "Magmarizer@" + rawchar "Razor Fang@" + rawchar "Razor Claw@" + rawchar "Odd Souvenir@" + rawchar "Nugget@" + rawchar "Big Nugget@" + rawchar "TinyMushroom@" + rawchar "Big Mushroom@" + rawchar "BalmMushroom@" + rawchar "Pearl@" + rawchar "Big Pearl@" + rawchar "Pearl String@" + rawchar "Stardust@" + rawchar "Star Piece@" + rawchar "Brick Piece@" + rawchar "Rare Bone@" + rawchar "Silver Leaf@" + rawchar "Gold Leaf@" + rawchar "SlowpokeTail@" + rawchar "Bottle Cap@" + rawchar "Helix Fossil@" + rawchar "Dome Fossil@" + rawchar "Old Amber@" + rawchar "Mulch@" + rawchar "Sweet Honey@" + rawchar "Mint@" + rawchar "Flower Mail@" + rawchar "Surf Mail@" + rawchar "LiteBlueMail@" + rawchar "PortraitMail@" + rawchar "Lovely Mail@" + rawchar "Eon Mail@" + rawchar "Morph Mail@" + rawchar "BlueSky Mail@" + rawchar "Music Mail@" + rawchar "Mirage Mail@" diff --git a/data/pc/bad_egg.asm b/data/pc/bad_egg.asm new file mode 100644 index 0000000000..c165b53a74 --- /dev/null +++ b/data/pc/bad_egg.asm @@ -0,0 +1,21 @@ +BadEggRLE: + db UNOWN, 1 ; $c6 + db $00, 1 + db HIDDEN_POWER, 1 ; $ed + db $00, 17 + db ABILITY_1 | QUIRKY, 1 ; $38 + db MALE | IS_EGG_MASK | UNOWN_QUESTION_FORM, 1 ; $68 + db $00, 9 + db EGG_LEVEL, 1 ; $01 + db $00, 16 + db "B", 1 ; $81 + db "a", 1 ; $a0 + db "d", 1 ; $a3 + db " ", 1 ; $7f + db "E", 1 ; $84 + db "g", 2 ; $a6 + db "@", 4 ; $53 + db "?", 1 ; $9e + db "@", 7 ; $53 + db $00, 3 + db $ff ; end diff --git a/data/pc/default_box_themes.asm b/data/pc/default_box_themes.asm new file mode 100644 index 0000000000..53eaecad44 --- /dev/null +++ b/data/pc/default_box_themes.asm @@ -0,0 +1,18 @@ +BillsPC_DefaultBoxThemes: + db THEME_GRASS + db THEME_FIRE + db THEME_WATER + db THEME_ELECTRIC + db THEME_FIGHTING + db THEME_PSYCHIC + db THEME_NORMAL + db THEME_FLYING + db THEME_BUG + db THEME_POISON + db THEME_GROUND + db THEME_ROCK + db THEME_ICE + db THEME_GHOST + db THEME_DRAGON + db THEME_DARK + db -1 ; end diff --git a/data/pc/theme_names.asm b/data/pc/theme_names.asm new file mode 100644 index 0000000000..be611ba3a2 --- /dev/null +++ b/data/pc/theme_names.asm @@ -0,0 +1,62 @@ +BillsPC_ThemeNames: + dw .Standard + dw .Pro + dw .Mobile + dw .Classic + dw .Bliss + dw .Contrast + dw .Nature + dw .Truth + dw .Ideals + dw .Light + dw .Darkness + dw .Matte + dw .Normal + dw .Fighting + dw .Flying + dw .Poison + dw .Ground + dw .Rock + dw .Bug + dw .Ghost + dw .Steel + dw .Fire + dw .Water + dw .Grass + dw .Electric + dw .Psychic + dw .Ice + dw .Dragon + dw .Dark + dw .Fairy + +.Standard: db "Standard@" +.Pro: db "Pro@" +.Mobile: db "Mobile@" +.Classic: db "Classic@" +.Bliss: db "Bliss@" +.Contrast: db "Contrast@" +.Nature: db "Nature@" +.Truth: db "Truth@" +.Ideals: db "Ideals@" +.Light: db "Light@" +.Darkness: db "Darkness@" +.Matte: db "Matte@" +.Normal: db "Normal@" +.Fighting: db "Fighting@" +.Flying: db "Flying@" +.Poison: db "Poison@" +.Ground: db "Ground@" +.Rock: db "Rock@" +.Bug: db "Bug@" +.Ghost: db "Ghost@" +.Steel: db "Steel@" +.Fire: db "Fire@" +.Water: db "Water@" +.Grass: db "Grass@" +.Electric: db "Electric@" +.Psychic: db "Psychic@" +.Ice: db "Ice@" +.Dragon: db "Dragon@" +.Dark: db "Dark@" +.Fairy: db "Fairy@" diff --git a/data/phone/text/bill.asm b/data/phone/text/bill.asm index b9069e5309..b78d8865a2 100644 --- a/data/phone/text/bill.asm +++ b/data/phone/text/bill.asm @@ -42,10 +42,9 @@ BillPhoneNotFullText: text "Thanks for" line "waiting!" - para ", your Box" - line "has room for " - text_ram wStringBuffer3 - cont "more #mon." + para ", your" + line "database has a lot" + cont "of space left." para "Get out there and" line "fill it up!" @@ -55,28 +54,21 @@ BillPhoneNearlyFullText: text "Thanks for" line "waiting!" - para ", your Box" - line "has room for only" - cont "" - text_ram wStringBuffer3 - text " more #mon." + para ", your" + line "database is almost" + cont "overtaxed!" para "Maybe you should" - line "switch your Box." + line "save your game?" done BillPhoneFullText: text "Thanks for" line "waiting!" - para ", your Box" - line "is full!" - - para "You'll have to" - line "switch Boxes if" - - para "you want to catch" - line "more #mon." + para ", your" + line "database is" + cont "overtaxed." prompt BillPhoneNewlyFullText: @@ -87,14 +79,8 @@ BillPhoneNewlyFullText: line "my Storage System." para "That last #mon" - line "you sent filled" - cont "your Box up." - - para "You'll have to" - line "switch Boxes if" - - para "you want to catch" - line "more #mon." + line "overtaxed my" + cont "systems!" prompt BillWholePCFullText: @@ -106,13 +92,18 @@ BillWholePCFullText: cont "to make space." done -BillWantNextBox: - text "I'll swap to the" - line "next Box with" - cont "space for you." +BillFlushBySaving: + text "It needs to run" + line "garbage collection" + cont "before more use." - para "However…" - prompt + para "Save the game to" + line "continue using the" + cont "PC." + + para "Do you want to" + line "save now?" + done BillThankYouText: text "Thank you for" @@ -124,13 +115,9 @@ BillCallMeToSwitch: text "OK, I'll leave it" line "alone." - para "Call me back if" + para "Save the game if" line "you change your" cont "mind." - - para "You can also" - line "change boxes your-" - cont "self via PC." prompt BillPhoneSecondBadgeText: diff --git a/data/predef_pointers.asm b/data/predef_pointers.asm index 442cddc7e5..949bbec4d1 100644 --- a/data/predef_pointers.asm +++ b/data/predef_pointers.asm @@ -6,7 +6,6 @@ PredefPointers:: add_predef ComputeHPBarPixels add_predef FillPP add_predef TryAddMonToParty - add_predef SentGetPkmnIntoFromBox add_predef AnimateHPBar add_predef CalcPkmnStats add_predef CalcPkmnStatC @@ -37,4 +36,4 @@ PredefPointers:: add_predef SubtractHPFromUser add_predef GetUserItemAfterUnnerve add_predef ChangeHappiness - add_predef RemoveMonFromPartyOrBox + add_predef RemoveMonFromParty diff --git a/data/sprite_anims/framesets.asm b/data/sprite_anims/framesets.asm index c4e3b4dd29..9c1c364b63 100755 --- a/data/sprite_anims/framesets.asm +++ b/data/sprite_anims/framesets.asm @@ -48,6 +48,11 @@ SpriteAnimFrameData: dw .Frameset_CelebiLeft dw .Frameset_CelebiRight dw .Frameset_MaxStatSparkle + dw .Frameset_PcCursor + dw .Frameset_PcQuick + dw .Frameset_PcMode + dw .Frameset_PcMode2 + dw .Frameset_PcPack .Frameset_00: frame SPRITE_ANIM_OAMSET_RED_WALK_1, 32 @@ -325,3 +330,23 @@ SpriteAnimFrameData: .Frameset_MaxStatSparkle: frame SPRITE_ANIM_OAMSET_MAX_STAT_SPARKLE, 32 dorestart + +.Frameset_PcCursor: + frame SPRITE_ANIM_OAMSET_PC_CURSOR, 32 + dorestart + +.Frameset_PcQuick: + frame SPRITE_ANIM_OAMSET_PC_QUICK, 8 + delanim + +.Frameset_PcMode: + frame SPRITE_ANIM_OAMSET_PC_MODE, 32 + dorestart + +.Frameset_PcMode2: + frame SPRITE_ANIM_OAMSET_PC_MODE2, 32 + dorestart + +.Frameset_PcPack: + frame SPRITE_ANIM_OAMSET_PC_PACK, 32 + dorestart diff --git a/data/sprite_anims/oam.asm b/data/sprite_anims/oam.asm index a9d1ea3488..08e6d2e7e1 100644 --- a/data/sprite_anims/oam.asm +++ b/data/sprite_anims/oam.asm @@ -75,6 +75,11 @@ SpriteAnimOAMData: dbw $04, .OAMData_GameFreakLogo4_11 ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_10 dbw $00, .OAMData_GameFreakLogo4_11 ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11 dbw $00, .OAMData_MaxStatSparkle ; SPRITE_ANIM_OAMSET_MAX_STAT_SPARKLE + dbw $00, .OAMData_PcCursor ; SPRITE_ANIM_OAMSET_PC_CURSOR + dbw $00, .OAMData_PcQuick ; SPRITE_ANIM_OAMSET_PC_QUICK + dbw $00, .OAMData_PcMode ; SPRITE_ANIM_OAMSET_PC_MODE + dbw $00, .OAMData_PcMode2 ; SPRITE_ANIM_OAMSET_PC_MODE2 + dbw $00, .OAMData_PcPack ; SPRITE_ANIM_OAMSET_PC_PACK .OAMData_1x1_Palette0: db 1 @@ -626,3 +631,50 @@ SpriteAnimOAMData: .OAMData_MaxStatSparkle: db 1 dsprite 2, 0, 2, 0, $00, $0 + +.OAMData_PcCursor: + db 9 + ; Cursor + dsprite 0, 0, 0, 0, $04, $1 | VRAM_BANK_1 + dsprite 0, 0, 1, 0, $04, $2 | VRAM_BANK_1 | X_FLIP + dsprite 1, 0, 0, 0, $05, $1 | VRAM_BANK_1 + dsprite 1, 0, 1, 0, $05, $2 | VRAM_BANK_1 | X_FLIP + + ; Mini + dsprite 1, 2, 0, 0, $08, $3 | VRAM_BANK_1 + dsprite 1, 2, 1, 0, $09, $3 | VRAM_BANK_1 + dsprite 2, 2, 0, 0, $0a, $3 | VRAM_BANK_1 + dsprite 2, 2, 1, 0, $0b, $3 | VRAM_BANK_1 + + ; Mini shadow (TODO) + + ; Item + dsprite 2, 0, 0, 4, $10, $0 | VRAM_BANK_1 + +.OAMData_PcQuick: + db 4 + ; Mini + dsprite 0, 0, 0, 0, $14, $5 | VRAM_BANK_1 + dsprite 0, 0, 1, 0, $15, $5 | VRAM_BANK_1 + dsprite 1, 0, 0, 0, $16, $5 | VRAM_BANK_1 + dsprite 1, 0, 1, 0, $17, $5 | VRAM_BANK_1 + + ; Mini shadow (TODO) + +.OAMData_PcMode: + db 3 + dsprite 0, 0, 2, 0, $26, $2 | VRAM_BANK_1 + dsprite 0, 0, 3, 0, $27, $2 | VRAM_BANK_1 + dsprite 0, 0, 4, 0, $28, $2 | VRAM_BANK_1 + +.OAMData_PcMode2: + db 2 + dsprite 0, 0, 0, 0, $24, $2 | VRAM_BANK_1 + dsprite 0, 0, 1, 0, $25, $2 | VRAM_BANK_1 + +.OAMData_PcPack: + db 4 + dsprite 0, 0, 0, 0, $2f, $4 | VRAM_BANK_1 + dsprite 0, 0, 1, 0, $30, $4 | VRAM_BANK_1 + dsprite 1, 0, 0, 0, $31, $4 | VRAM_BANK_1 + dsprite 1, 0, 1, 0, $32, $4 | VRAM_BANK_1 diff --git a/data/sprite_anims/sequences.asm b/data/sprite_anims/sequences.asm index 378c2de7a2..fbda0e8341 100644 --- a/data/sprite_anims/sequences.asm +++ b/data/sprite_anims/sequences.asm @@ -68,3 +68,13 @@ SpriteAnimSeqData: db SPRITE_ANIM_FRAMESET_CELEBI_LEFT, SPRITE_ANIM_SEQ_NULL, $00 ; SPRITE_ANIM_INDEX_MAX_STAT_SPARKLE db SPRITE_ANIM_FRAMESET_MAX_STAT_SPARKLE, SPRITE_ANIM_SEQ_MAX_STAT_SPARKLE, $00 +; SPRITE_ANIM_INDEX_PC_CURSOR + db SPRITE_ANIM_FRAMESET_PC_CURSOR, SPRITE_ANIM_SEQ_PC_CURSOR, $00 +; SPRITE_ANIM_INDEX_PC_QUICK + db SPRITE_ANIM_FRAMESET_PC_QUICK, SPRITE_ANIM_SEQ_PC_QUICK, $00 +; SPRITE_ANIM_INDEX_PC_MODE + db SPRITE_ANIM_FRAMESET_PC_MODE, SPRITE_ANIM_SEQ_PC_MODE, $00 +; SPRITE_ANIM_INDEX_PC_MODE2 + db SPRITE_ANIM_FRAMESET_PC_MODE2, SPRITE_ANIM_SEQ_NULL, $00 +; SPRITE_ANIM_INDEX_PC_PACK + db SPRITE_ANIM_FRAMESET_PC_PACK, SPRITE_ANIM_SEQ_PC_PACK, $00 diff --git a/data/text/common.asm b/data/text/common.asm index 1527ab49f7..fa35c87b6a 100644 --- a/data/text/common.asm +++ b/data/text/common.asm @@ -2510,7 +2510,7 @@ _LeftWithDayCareLadyText:: SECTION "_LeftWithDayCareManText", ROMX _LeftWithDayCareManText:: text "It's " - text_ram wBreedMon1 + text_ram wBreedMon1Nickname line "that was left with" cont "the Day-Care Man." done @@ -2729,7 +2729,7 @@ _LuckyNumberMatchPartyText:: line "with the ID number" para "of " - text_ram wStringBuffer1 + text_decimal wTempMonID, 2, 5 text " in" line "your party." prompt @@ -2742,9 +2742,11 @@ _LuckyNumberMatchPCText:: line "with the ID number" para "of " - text_ram wStringBuffer1 - text " in" - line "your PC Box." + text_decimal wTempMonID, 2, 5 + text " in Box" + line "“" + text_ram wStringBuffer2 + text "”." prompt SECTION "_CaughtAskNicknameText", ROMX @@ -4168,11 +4170,20 @@ Text_Waitbutton_2:: text_promptbutton text_end +SECTION "_BallCurBoxFullText", ROMX +_BallCurBoxFullText:: + text "" + text_ram wStringBuffer1 + text " is full." + prompt + SECTION "_BallSentToPCText", ROMX _BallSentToPCText:: text_ram wMonOrItemNameBuffer text " was" - line "sent to Bill's PC." + line "sent to " + text_ram wStringBuffer1 + text "." prompt SECTION "_NewDexDataText", ROMX @@ -4313,13 +4324,20 @@ _BallDontBeAThiefText:: text "Don't be a thief!" prompt -SECTION "_BallBoxFullText", ROMX -_BallBoxFullText:: - text "The #mon Box" +SECTION "_BallStorageFullText", ROMX +_BallStorageFullText:: + text "The storage system" line "is full. That" cont "can't be used now." prompt +SECTION "_BallDatabaseFullText", ROMX +_BallDatabaseFullText:: + text "The PC database is" + line "overtaxed. Please" + cont "save the game." + prompt + SECTION "Text_MonIsHiddenFromBall", ROMX Text_MonIsHiddenFromBall:: text "The #mon can't" diff --git a/engine/battle/core.asm b/engine/battle/core.asm index 158826135f..69ff87ddb7 100644 --- a/engine/battle/core.asm +++ b/engine/battle/core.asm @@ -3727,7 +3727,7 @@ endr ld [de], a ld hl, wBattleMonLevel ld de, wTempMonLevel - ld bc, wTempMonStatsEnd - wTempMonLevel + ld bc, wTempMonEnd - wTempMonLevel rst CopyBytes ld a, [wCurBattleMon] ld hl, wPartyMon1Species @@ -4348,12 +4348,7 @@ Battle_StatsScreen: ld bc, $31 tiles rst CopyBytes call EnableLCD - call ClearSprites - call LowVolume - xor a ; PARTYMON - ld [wMonType], a - farcall StatsScreenInit - call MaxVolume + farcall OpenPartyStats call DisableLCD ld hl, vTiles0 ld de, vTiles2 tile $31 diff --git a/engine/battle/read_trainer_party.asm b/engine/battle/read_trainer_party.asm index 52f643e274..b14073d441 100755 --- a/engine/battle/read_trainer_party.asm +++ b/engine/battle/read_trainer_party.asm @@ -14,7 +14,7 @@ ReadTrainerParty: ld [hl], a ld hl, wOTPartyMons - ld bc, wOTPartyMonsEnd - wOTPartyMons + ld bc, PARTYMON_STRUCT_LENGTH * PARTY_LENGTH xor a rst ByteFill diff --git a/engine/events/daycare.asm b/engine/events/daycare.asm index fcba0bb2e2..735580b26b 100755 --- a/engine/events/daycare.asm +++ b/engine/events/daycare.asm @@ -452,7 +452,8 @@ Special_DayCareManOutside: text_end DayCare_GiveEgg: - ld a, [wEggMonLevel] + call DayCare_GenerateEgg + ld a, [wTempMonLevel] ld [wCurPartyLevel], a ld hl, wPartyCount ld a, [hl] @@ -464,7 +465,7 @@ DayCare_GiveEgg: ld c, a ld b, 0 add hl, bc - ld a, [wEggMonSpecies] + ld a, [wTempMonSpecies] ld [hli], a ld [wCurSpecies], a ld [wCurPartySpecies], a @@ -472,64 +473,27 @@ DayCare_GiveEgg: ; Red Gyarados' Eggs should be plain cp MAGIKARP jr nz, .not_red_magikarp - ld a, [wEggMonForm] + ld a, [wTempMonForm] and SPECIESFORM_MASK cp GYARADOS_RED_FORM jr c, .not_red_magikarp - ld a, [wEggMonForm] + ld a, [wTempMonForm] and $ff - SPECIESFORM_MASK or PLAIN_FORM - ld [wEggMonForm], a + ld [wTempMonForm], a .not_red_magikarp ld [hl], -1 - ld hl, wPartyMonNicknames - ld bc, MON_NAME_LENGTH - call DayCare_GetCurrentPartyMember - ld hl, wEggMonNickname - rst CopyBytes - - ld hl, wPartyMonOTs - ld bc, NAME_LENGTH - call DayCare_GetCurrentPartyMember - ld hl, wEggMonOT - rst CopyBytes - - ld hl, wPartyMon1 - ld bc, PARTYMON_STRUCT_LENGTH - call DayCare_GetCurrentPartyMember - ld hl, wEggMon - ld bc, wEggMonEnd - wEggMon - rst CopyBytes - - ld a, [wEggMonForm] - ld [wCurForm], a - call GetBaseData + ; Party count is already increased since before. ld a, [wPartyCount] - dec a - ld hl, wPartyMon1 - ld bc, PARTYMON_STRUCT_LENGTH - rst AddNTimes - ld b, h - ld c, l - ld hl, MON_ID + 1 - add hl, bc - push hl - ld hl, MON_MAXHP - add hl, bc - ld d, h - ld e, l - pop hl - push bc - ld b, FALSE - predef CalcPkmnStats - pop bc - ld hl, MON_HP - add hl, bc + ld [wTempMonSlot], a xor a - ld [hli], a - ld [hl], a + ld [wTempMonBox], a + + ; Recalculates stats and sets other partymon stuff. + farcall SetTempPartyMonData + farcall UpdateStorageBoxMonFromTemp and a ret @@ -537,14 +501,6 @@ DayCare_GiveEgg: scf ret -DayCare_GetCurrentPartyMember: - ld a, [wPartyCount] - dec a - rst AddNTimes - ld d, h - ld e, l - ret - CheckParentItem: ; Returns nz if a parent has given item a. a is set to 1 or 2 depending ; on whether parent 1 or 2 had the item. If both have it, a is set @@ -660,7 +616,7 @@ InheritDV: ld a, e push de push hl - ld de, wEggMonDVs + ld de, wTempMonDVs ; halve A; 0-1: first byte, 2-3: second, 4-5: third srl a ; sets carry if a is odd, maintained thorough the loop inc a @@ -711,16 +667,9 @@ DayCare_InitBreeding: cp 150 jr c, .loop ld [wStepsToEgg], a - xor a - ld hl, wEggMon - ld bc, wEggMonEnd - wEggMon - rst ByteFill - ld hl, wEggMonNickname - ld bc, MON_NAME_LENGTH - rst ByteFill - ld hl, wEggMonOT - ld bc, NAME_LENGTH - rst ByteFill + ret + +DayCare_GenerateEgg: ld a, [wBreedMon1Species] ld [wCurPartySpecies], a ld a, [wBreedMon1Gender] @@ -773,7 +722,15 @@ DayCare_InitBreeding: .GotEggSpecies: ld [wCurPartySpecies], a ld [wCurSpecies], a - ld [wEggMonSpecies], a + + ; Clear tempmon struct + xor a + ld hl, wTempMon + ld bc, PARTYMON_STRUCT_LENGTH + MON_NAME_LENGTH + PLAYER_NAME_LENGTH + 3 + rst ByteFill + + ld a, [wCurPartySpecies] + ld [wTempMonSpecies], a ; Form inheritance: from the mother or non-Ditto. If both ; parents share species, pick at random. @@ -787,43 +744,43 @@ DayCare_InitBreeding: call GetBaseData ; Set name and item - ld hl, wEggMonNickname + ld hl, wTempMonNickname ld de, .String_EGG call CopyName2 ld hl, wPlayerName - ld de, wEggMonOT + ld de, wTempMonOT ld bc, NAME_LENGTH rst CopyBytes xor a - ld [wEggMonItem], a + ld [wTempMonItem], a ; Set moves for the egg call InitEggMoves ; Set OTID to the player - ld hl, wEggMonID + ld hl, wTempMonID ld a, [wPlayerID] ld [hli], a ld a, [wPlayerID + 1] ld [hl], a ; Zero EXP - ld hl, wEggMonExp + ld hl, wTempMonExp xor a ld [hli], a ld [hli], a ld [hl], a ; Zero EVs - ld b, wEggMonDVs - wEggMonEVs - ld hl, wEggMonEVs + ld b, wTempMonDVs - wTempMonEVs + ld hl, wTempMonEVs .loop2 ld [hli], a dec b jr nz, .loop2 ; Set random DVs - ld hl, wEggMonDVs + ld hl, wTempMonDVs call Random ld [hli], a call Random @@ -861,8 +818,8 @@ DayCare_InitBreeding: ; Zero the personality data xor a - ld [wEggMonPersonality], a - ld [wEggMonPersonality + 1], a + ld [wTempMonPersonality], a + ld [wTempMonPersonality + 1], a ; Do Ability ; Ability Capsules greatly boosts HA rate of child: it makes it @@ -925,7 +882,7 @@ DayCare_InitBreeding: .hidden_ability ld a, HIDDEN_ABILITY .got_ability - ld hl, wEggMonAbility + ld hl, wTempMonAbility or [hl] ld [hl], a @@ -943,7 +900,7 @@ DayCare_InitBreeding: ld a, NUM_NATURES call RandomRange .got_nature - ld hl, wEggMonNature + ld hl, wTempMonNature or [hl] ld [hl], a @@ -997,13 +954,13 @@ DayCare_InitBreeding: cp c jr nc, .not_shiny ld a, SHINY_MASK - ld hl, wEggMonShiny + ld hl, wTempMonShiny or [hl] ld [hl], a .not_shiny ; Gender - ld a, [wEggMonSpecies] + ld a, [wTempMonSpecies] ld c, a ld a, [wCurForm] ld b, a @@ -1015,7 +972,7 @@ DayCare_InitBreeding: ; a = carry (rnd(0..7) < c) ? FEMALE : MALE (0) sbc a and FEMALE - ld hl, wEggMonGender + ld hl, wTempMonGender or [hl] ld [hl], a @@ -1032,15 +989,15 @@ DayCare_InitBreeding: ld hl, wBreedMon1CaughtBall call .inherit_mother_unless_samespecies ld a, [hl] - ld [wEggMonCaughtBall], a + ld [wTempMonCaughtBall], a ; PP, egg cycles, level ld hl, wStringBuffer1 ld de, wMonOrItemNameBuffer ld bc, NAME_LENGTH rst CopyBytes - ld hl, wEggMonMoves - ld de, wEggMonPP + ld hl, wTempMonMoves + ld de, wTempMonPP predef FillPP ld hl, wMonOrItemNameBuffer ld de, wStringBuffer1 @@ -1053,14 +1010,14 @@ DayCare_InitBreeding: add a add a add b - ld hl, wEggMonHappiness + ld hl, wTempMonHappiness ld [hli], a xor a ld [hli], a ld [hli], a ld [hl], a ld a, [wCurPartyLevel] - ld [wEggMonLevel], a + ld [wTempMonLevel], a ret .inherit_mother_unless_samespecies diff --git a/engine/events/judge_machine.asm b/engine/events/judge_machine.asm index 192811f4d7..3916a8f001 100644 --- a/engine/events/judge_machine.asm +++ b/engine/events/judge_machine.asm @@ -38,6 +38,13 @@ JudgeMachine: call FadeToMenu call JudgeSystem call ExitAllMenus +; hLCDInterruptFunction gets overridden for drawing the radar chart. +; (It is not called as such because the LCD interrupt is disabled.) +; This restores it to the LCDGeneric function. + ld a, LOW(LCDGeneric) + ldh [hFunctionTargetLo], a + ld a, HIGH(LCDGeneric) + ldh [hFunctionTargetHi], a ld hl, NewsMachineContinueText jr .continue @@ -747,7 +754,7 @@ DrawLowRadarLine: ; For x from b to d, draw a point at (x, c) .loop push de - call hFunction ; FillRadarUp/Down/Left/Right + call hLCDInterruptFunction ; FillRadarUp/Down/Left/Right pop de ; Update D and y @@ -802,7 +809,7 @@ DrawHighRadarLine: ; For y from c to e, draw a point at (b, y) .loop push de - call hFunction ; FillRadarUp/Down/Left/Right + call hLCDInterruptFunction ; FillRadarUp/Down/Left/Right pop de ; Update D and x @@ -842,7 +849,7 @@ DrawHorizontalRadarLine: ; For x from b to d, draw a point at (x, c) .loop push de - call hFunction ; FillRadarUp/Down/Left/Right + call hLCDInterruptFunction ; FillRadarUp/Down/Left/Right pop de inc b ld a, d @@ -864,7 +871,7 @@ DrawVerticalRadarLine: ; For y from c to e, draw a point at (b, y) .loop push de - call hFunction ; FillRadarUp/Down/Left/Right + call hLCDInterruptFunction ; FillRadarUp/Down/Left/Right pop de inc c ld a, e diff --git a/engine/events/lucky_number.asm b/engine/events/lucky_number.asm index 66872bc4f4..fbbcfda8a7 100644 --- a/engine/events/lucky_number.asm +++ b/engine/events/lucky_number.asm @@ -1,218 +1,106 @@ Special_CheckForLuckyNumberWinners: +; Returns number of digits matching xor a ldh [hScriptVar], a ld [wFoundMatchingIDInParty], a - ld a, [wPartyCount] - and a - ret z - ld d, a - ld hl, wPartyMon1ID - ld bc, wPartySpecies -.PartyLoop: - push hl - ld bc, wPartyMon1IsEgg - wPartyMon1ID - add hl, bc - bit MON_IS_EGG_F, [hl] - pop hl - call z, .CompareLuckyNumberToMonID - ld bc, PARTYMON_STRUCT_LENGTH - add hl, bc - dec d - jr nz, .PartyLoop - ld a, BANK(sBox) - call GetSRAMBank - ld a, [sBoxCount] - and a - jr z, .SkipOpenBox - ld d, a - ld hl, sBoxMon1ID - ld bc, sBoxSpecies -.OpenBoxLoop: - push hl - ld bc, wPartyMon1IsEgg - wPartyMon1ID - add hl, bc - bit MON_IS_EGG_F, [hl] - pop hl - jr nz, .SkipOpenBoxMon - call .CompareLuckyNumberToMonID - jr nc, .SkipOpenBoxMon - ld a, 1 - ld [wFoundMatchingIDInParty], a -.SkipOpenBoxMon: - ld bc, BOXMON_STRUCT_LENGTH - add hl, bc - dec d - jr nz, .OpenBoxLoop - -.SkipOpenBox: - call CloseSRAM - ld c, $0 -.BoxesLoop: - ld a, [wCurBox] - and $f - cp c - jr z, .SkipBox - ld hl, .BoxBankAddresses - ld b, 0 - add hl, bc - add hl, bc - add hl, bc - ld a, [hli] - call GetSRAMBank - ld a, [hli] - ld h, [hl] - ld l, a ; hl now contains the address of the loaded box in SRAM - ld a, [hl] - and a - jr z, .SkipBox ; no mons in this box - push bc - ld b, h - ld c, l - inc bc - ld de, sBoxMon1ID - sBox - add hl, de - ld d, a -.BoxNLoop: - push hl - ld bc, wPartyMon1IsEgg - wPartyMon1ID - add hl, bc - bit MON_IS_EGG_F, [hl] - pop hl - jr nz, .SkipBoxMon - - call .CompareLuckyNumberToMonID ; sets hScriptVar and wCurPartySpecies appropriately - jr nc, .SkipBoxMon - ld a, 1 - ld [wFoundMatchingIDInParty], a - -.SkipBoxMon: - ld bc, BOXMON_STRUCT_LENGTH - add hl, bc - dec d - jr nz, .BoxNLoop - pop bc - -.SkipBox: - inc c - ld a, c - cp NUM_BOXES - jr c, .BoxesLoop - - call CloseSRAM - ldh a, [hScriptVar] - and a - ret z ; found nothing - ld a, [wFoundMatchingIDInParty] - and a - push af - ld a, [wCurPartySpecies] - ld [wNamedObjectIndexBuffer], a - call GetPokemonName - ld hl, .FoundPartymonText - pop af - jr z, .print - ld hl, .FoundBoxmonText + ; Prepare lucky number buffer + ld hl, wStringBuffer1 + ld de, wLuckyIDNumber + lb bc, PRINTNUM_LEADINGZEROS | 2, 5 + call PrintNum -.print - jp PrintText + ld b, NUM_BOXES +.outer_loop + inc b + dec b + ld c, PARTY_LENGTH + jr z, .loop + ld c, MONS_PER_BOX +.loop + farcall GetStorageBoxMon + jr z, .next -.CompareLuckyNumberToMonID: + ld hl, wTempMonIsEgg + bit MON_IS_EGG_F, [hl] + jr nz, .next + ld de, wTempMonID push bc - push de - push hl - ld d, h - ld e, l ld hl, wBuffer1 lb bc, PRINTNUM_LEADINGZEROS | 2, 5 call PrintNum - ld hl, wLuckyNumberDigitsBuffer - ld de, wLuckyIDNumber - lb bc, PRINTNUM_LEADINGZEROS | 2, 5 - call PrintNum - lb bc, 5, 0 - ld hl, wLuckyNumberDigitsBuffer + 4 + ld hl, wStringBuffer1 + 4 ld de, wBuffer1 + 4 -.loop + ld b, 0 +.compare_loop ld a, [de] cp [hl] - jr nz, .done + jr nz, .compare_failed + inc b dec de dec hl - inc c - dec b - jr nz, .loop - -.done - pop hl - push hl - ld de, -6 - add hl, de - ld a, [hl] - pop hl - pop de - push af - ld a, c - ld b, 1 + ld a, b cp 5 - jr z, .okay - ld b, 2 - cp 4 - jr z, .okay - ld b, 3 - cp 3 - jr nc, .okay - ld b, 4 - cp 2 - jr nz, .nomatch - -.okay - inc b + jr nz, .compare_loop + ; if we're here, all 5 digits match +.compare_failed ldh a, [hScriptVar] - and a - jr z, .bettermatch + and $f cp b - jr c, .nomatch - -.bettermatch - dec b - ld a, b - ldh [hScriptVar], a - pop bc ld a, b - ld [wCurPartySpecies], a - pop bc - scf - ret - -.nomatch - pop bc pop bc + jr nc, .next + swap a + or b + swap a + ldh [hScriptVar], a + ld hl, wFoundMatchingIDInParty + ld [hl], c + cp 5 + jr z, .done + jr nz, .next +.next + dec c + jr nz, .loop + dec b + bit 7, b ; check for reaching -1 + jr z, .outer_loop +.done + ldh a, [hScriptVar] and a - ret + ret z ; found nothing + + ; Get storage mon nickname + push af + swap a + and $f + ld b, a + ld a, [wFoundMatchingIDInParty] + ld c, a + farcall GetStorageBoxMon + pop af + and $f + ldh [hScriptVar], a -.BoxBankAddresses: - dba sBox1 - dba sBox2 - dba sBox3 - dba sBox4 - dba sBox5 - dba sBox6 - dba sBox7 - dba sBox8 - dba sBox9 - dba sBox10 - dba sBox11 - dba sBox12 - dba sBox13 - dba sBox14 + inc b + dec b + ld hl, .MatchInParty + jr z, .got_text + farcall GetBoxName + ld hl, wStringBuffer1 + ld de, wStringBuffer2 + ld bc, BOX_NAME_LENGTH + rst CopyBytes + ld hl, .MatchInStorage +.got_text + jp PrintText -.FoundPartymonText: +.MatchInParty: ; Congratulations! We have a match with the ID number of @ in your party. text_far _LuckyNumberMatchPartyText text_end -.FoundBoxmonText: +.MatchInStorage: ; Congratulations! We have a match with the ID number of @ in your PC BOX. text_far _LuckyNumberMatchPCText text_end diff --git a/engine/events/npc_trade.asm b/engine/events/npc_trade.asm index 1b3a6ba393..c3c78f2489 100755 --- a/engine/events/npc_trade.asm +++ b/engine/events/npc_trade.asm @@ -146,7 +146,7 @@ DoNPCTrade: xor a ld [wMonType], a ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox + predef RemoveMonFromParty predef TryAddMonToParty ld e, NPCTRADE_DIALOG diff --git a/engine/events/pokecenter_pc.asm b/engine/events/pokecenter_pc.asm index 803a667aac..640cb23f36 100755 --- a/engine/events/pokecenter_pc.asm +++ b/engine/events/pokecenter_pc.asm @@ -286,9 +286,28 @@ PlayersPCAskWhatDoText: text_far _PlayersPCAskWhatDoText text_end +ClearPCItemScreen: + call DisableSpriteUpdates + xor a + ldh [hBGMapMode], a + call ClearBGPalettes + call ClearSprites + hlcoord 0, 0 + ld bc, SCREEN_HEIGHT * SCREEN_WIDTH + ld a, " " + rst ByteFill + hlcoord 0, 0 + lb bc, 10, 18 + call Textbox + hlcoord 0, 12 + lb bc, 4, 18 + call Textbox + call ApplyAttrAndTilemapInVBlank + jp SetPalettes ; load regular palettes? + PlayerWithdrawItemMenu: call LoadStandardMenuHeader - farcall ClearPCItemScreen + call ClearPCItemScreen .loop call PCItemsJoypad jr c, .quit @@ -346,7 +365,7 @@ PlayerWithdrawItemMenu: PlayerTossItemMenu: call LoadStandardMenuHeader - farcall ClearPCItemScreen + call ClearPCItemScreen .loop call PCItemsJoypad jr c, .quit diff --git a/engine/events/shiny_ditto.asm b/engine/events/shiny_ditto.asm index 6e36b0f8dd..ebb3ca2bc1 100644 --- a/engine/events/shiny_ditto.asm +++ b/engine/events/shiny_ditto.asm @@ -62,13 +62,16 @@ endr ld de, .Nickname call CopyName2 -; OT. +; OT and Extra. ld a, [wPartyCount] dec a ld hl, wPartyMonOTs call SkipNames - ld de, .OT - call CopyName2 + ld d, h + ld e, l + ld hl, .OTAndExtra + ld bc, PLAYER_NAME_LENGTH + 3 + rst CopyBytes ld a, TRUE ldh [hScriptVar], a @@ -79,7 +82,9 @@ endr ldh [hScriptVar], a ret -.OT: - rawchar "Mr.@" +.OTAndExtra: + rawchar "Mr.@@@" + db 0, 0, 0 + .Nickname: rawchar "Masuda@" diff --git a/engine/events/shuckle.asm b/engine/events/shuckle.asm index d8544e354f..b2856986f0 100644 --- a/engine/events/shuckle.asm +++ b/engine/events/shuckle.asm @@ -38,13 +38,16 @@ SpecialGiveShuckie: ld de, SpecialShuckleNickname call CopyName2 -; OT. +; OT and Extra. ld a, [wPartyCount] dec a ld hl, wPartyMonOTs call SkipNames - ld de, SpecialShuckleOT - call CopyName2 + ld d, h + ld e, l + ld hl, SpecialShuckleOTAndExtra + ld bc, PLAYER_NAME_LENGTH + 3 + rst CopyBytes ; Engine flag for this event. ld hl, wDailyFlags @@ -90,7 +93,7 @@ ReturnShuckie: ld a, [wCurPartyMon] ld hl, wPartyMonOTs call SkipNames - ld de, SpecialShuckleOT + ld de, SpecialShuckleOTAndExtra .CheckOT: ld a, [de] cp [hl] @@ -112,9 +115,7 @@ ReturnShuckie: cp 150 ld a, $3 jr nc, .HappyToStayWithYou - xor a ; take from pc - ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox + predef RemoveMonFromParty ld a, $2 .HappyToStayWithYou: ldh [hScriptVar], a @@ -135,8 +136,9 @@ ReturnShuckie: ldh [hScriptVar], a ret -SpecialShuckleOT: - rawchar "Kirk@" +SpecialShuckleOTAndExtra: + rawchar "Kirk@@@@" + db 0, 0, 0 SpecialShuckleNickname: rawchar "Shuckie@" diff --git a/engine/events/specials.asm b/engine/events/specials.asm index a03b5c452e..0cb5d8ca3e 100644 --- a/engine/events/specials.asm +++ b/engine/events/specials.asm @@ -474,60 +474,13 @@ RespawnRoamingSuicune: ret BillBoxSwitchCheck: - ld a, [wCurBox] - cp NUM_BOXES - 1 - jr nz, .notbox14 - ld a, -1 -.notbox14 - inc a -.billboxloop - inc a - ld c, a - push af - farcall GetBoxCountWithC - cp MONS_PER_BOX - jr nz, .foundspace - pop af - dec a - cp NUM_BOXES - 1 - jr nz, .notlastbox - ld a, -1 -.notlastbox - inc a - ld c, a - ld a, [wCurBox] - cp c - ld a, c - jr nz, .billboxloop - xor a - ldh [hScriptVar], a - ret - -.foundspace - pop af - dec a +; Returns 0 if our storage system box-wise is completely full, 1 otherwise. + farcall NewStorageBoxPointer + ld b, 1 + jr nc, .ok + jr nz, .ok + dec b +.ok + ld a, b ldh [hScriptVar], a - ld [wTempScriptBuffer], a ret - -BillBoxSwitch: - ; back up wMisc to wDecompressScratch - ld hl, wMisc - ld de, wDecompressScratch - ld bc, (wMiscEnd - wMisc) - ld a, BANK(wDecompressScratch) - call FarCopyWRAM - ; change boxes (overwrites wMisc) - ld a, [wTempScriptBuffer] - ld e, a - farcall ChangeBoxSaveGame - ; a = carry (didn't save) ? FALSE : TRUE - sbc a - inc a - ldh [hScriptVar], a - ; restore wMisc from wDecompressScratch - ld hl, wDecompressScratch - ld de, wMisc - ld bc, (wMiscEnd - wMisc) - ld a, BANK(wDecompressScratch) - jp FarCopyWRAM diff --git a/engine/events/wonder_trade.asm b/engine/events/wonder_trade.asm index 3a8e6e338f..6c876dd477 100644 --- a/engine/events/wonder_trade.asm +++ b/engine/events/wonder_trade.asm @@ -158,7 +158,7 @@ DoWonderTrade: xor a ld [wMonType], a ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox + predef RemoveMonFromParty call GetWonderTradeOTForm ld [wCurForm], a @@ -404,7 +404,7 @@ GetGSBallPichu: xor a ld [wMonType], a ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox + predef RemoveMonFromParty predef TryAddMonToParty ld b, MALE diff --git a/engine/gfx/cgb_layouts.asm b/engine/gfx/cgb_layouts.asm index 286d902c36..7701d36c81 100644 --- a/engine/gfx/cgb_layouts.asm +++ b/engine/gfx/cgb_layouts.asm @@ -969,53 +969,84 @@ _CGB_PokedexUnownMode: jp _CGB_FinishLayout _CGB_BillsPC: + ; Get box theme + ld a, [wCurBox] + ld b, a + inc b + farcall GetBoxTheme +BillsPC_PreviewTheme: + ; hl = BillsPC_ThemePals + a * 6 * 2 + add a + add a + ld e, a + ld d, 0 + ld hl, BillsPC_ThemePals + add hl, de + add hl, de + add hl, de ld de, wBGPals1 - ld hl, .MenuPalette - call LoadHLPaletteIntoDE - - ld a, [wCurPartySpecies] + ld c, 1 * 2 + call LoadCPaletteBytesFromHLIntoDE + push hl + ld hl, GenderAndExpBarPals + ld c, 2 * 2 + call LoadCPaletteBytesFromHLIntoDE + push de + ld hl, PokerusAndShinyPals + ld de, wBillsPC_PokerusShinyPal + ld c, 2 * 2 + call LoadCPaletteBytesFromHLIntoDE + + ; Prevents flickering shiny+pokerus background + ld hl, wBGPals1 palette 0 + ld de, wBGPals1 palette 3 + ld c, 1 * 2 + call LoadCPaletteBytesFromHLIntoDE + pop de + pop hl + ld c, 5 * 2 + call LoadCPaletteBytesFromHLIntoDE + ld a, [wBillsPC_ApplyThemePals] and a - jr nz, .GetMonPalette - ld hl, .OrangePalette + jr nz, .apply_pals + ld de, wOBPals1 palette 1 + ld hl, .CursorPal + push hl call LoadHLPaletteIntoDE - jr .Resume - -.GetMonPalette: - ld bc, wTempMonPersonality - call GetPlayerOrMonPalettePointer - call LoadPalette_White_Col1_Col2_Black - call VaryBGPal1ByTempMonDVs - -.Resume: - call WipeAttrMap - - hlcoord 1, 4, wAttrMap - lb bc, 7, 7 - ld a, $1 - call FillBoxWithByte + pop hl + call LoadHLPaletteIntoDE + ld hl, .PackPal + ld de, wOBPals1 palette 4 + jp LoadHLPaletteIntoDE - call InitPartyMenuOBPals +.apply_pals + farjp BillsPC_SetPals - jp _CGB_FinishLayout - -.MenuPalette: +.CursorPal: +; Coloring is fixed up later. if !DEF(MONOCHROME) RGB 31, 31, 31 - RGB 31, 20, 10 - RGB 26, 10, 06 + RGB 31, 31, 31 + RGB 00, 00, 00 RGB 00, 00, 00 else - MONOCHROME_RGB_FOUR + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_BLACK + RGB_MONOCHROME_BLACK endc -.OrangePalette: +.PackPal: if !DEF(MONOCHROME) - RGB 31, 15, 00 - RGB 23, 12, 00 - RGB 15, 07, 00 + RGB 31, 31, 31 + RGB 31, 31, 31 + RGB 07, 19, 07 RGB 00, 00, 00 else - MONOCHROME_RGB_FOUR + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_DARK + RGB_MONOCHROME_BLACK endc _CGB_UnownPuzzle: diff --git a/engine/gfx/color.asm b/engine/gfx/color.asm index ded2f54de7..3db6a487ab 100644 --- a/engine/gfx/color.asm +++ b/engine/gfx/color.asm @@ -234,11 +234,12 @@ LoadMailPalettes: jp ApplyAttrMap LoadHLPaletteIntoDE: + ld c, $8 +LoadCPaletteBytesFromHLIntoDE: ldh a, [rSVBK] push af ld a, $5 ldh [rSVBK], a - ld c, $8 .loop ld a, [hli] ld [de], a @@ -419,6 +420,16 @@ ApplyPartyMenuHPPals: ld a, e jp FillBoxWithByte +SetPartyMenuPal: +; Writes mon icon color a to palette in de + ld hl, PartyMenuOBPals + ld bc, 1 palettes + push bc + rst AddNTimes + pop bc + ld bc, 1 palettes + jp FarCopyColorWRAM + InitPartyMenuOBPals: ld hl, PartyMenuOBPals ld de, wOBPals1 diff --git a/engine/gfx/load_pics.asm b/engine/gfx/load_pics.asm index 47cfd1d725..59aba96271 100644 --- a/engine/gfx/load_pics.asm +++ b/engine/gfx/load_pics.asm @@ -86,6 +86,24 @@ GetFrontpic: ldh [rSVBK], a jp CloseSRAM +PrepareFrontpic: + ld a, [wCurPartySpecies] + ld [wCurSpecies], a + and a + ret z + ldh a, [rSVBK] + push af + call _PrepareFrontpic + pop af + ldh [rSVBK], a + jp CloseSRAM + +GetPreparedFrontpic: + ld a, BANK(sScratch) + call GetSRAMBank + call _GetPreparedFrontpic + jp CloseSRAM + FrontpicPredef: ld a, [wCurPartySpecies] ld [wCurSpecies], a @@ -106,6 +124,19 @@ FrontpicPredef: jp CloseSRAM _GetFrontpic: + call _PrepareFrontpic + ; fallthrough +_GetPreparedFrontpic: + push hl + ld de, sScratch + 1 tiles + ld c, 7 * 7 + ldh a, [hROMBank] + ld b, a + call Get2bpp + pop hl + ret + +_PrepareFrontpic: ld a, BANK(sScratch) call GetSRAMBank push de @@ -130,13 +161,6 @@ _GetFrontpic: ld de, wDecompressScratch call PadFrontpic pop hl - push hl - ld de, sScratch + 1 tiles - ld c, 7 * 7 - ldh a, [hROMBank] - ld b, a - call Get2bpp - pop hl ret GetFrontpicPointer: diff --git a/engine/gfx/mon_icons.asm b/engine/gfx/mon_icons.asm index cd82304f2c..5160f8378b 100644 --- a/engine/gfx/mon_icons.asm +++ b/engine/gfx/mon_icons.asm @@ -208,11 +208,15 @@ LoadMoveMenuMonIcon: push bc depixel 3, 4, 2, 4 + push de + ld hl, wTempMonForm + jr _InitScreenMonIcon InitScreenMonIcon: push de ld a, MON_FORM ; aka MON_IS_EGG call GetPartyParamLocation +_InitScreenMonIcon: ld a, [hl] and SPECIESFORM_MASK ld [wCurIconForm], a @@ -388,8 +392,11 @@ GetIcon_a: ; Load icon graphics into VRAM starting from tile a. ld l, a ld h, 0 - + ; fallthrough GetIcon: + ld c, 8 + ; fallthrough +DoGetIcon: ; Load icon graphics into VRAM starting from tile hl. ; One tile is 16 bytes long. @@ -402,15 +409,50 @@ endr push hl push hl + ld a, c + push af call LoadOverworldMonIcon + pop af + ld c, a ld h, d ld l, e pop de - call DecompressRequest2bpp pop hl ret +GetStorageIcon_a: +; Load frame 1 icon graphics into VRAM starting from tile a + ld l, a ; no-optimize hl|bc|de = a * 16 (rept) + ld h, 0 +rept 4 + add hl, hl +endr + ld de, vTiles0 + add hl, de + ; fallthrough +GetStorageIcon: + push hl + + push hl + ld a, 4 + push af + call LoadOverworldMonIcon + pop af + ld c, a + ld h, d + ld l, e + pop de + push de + push bc + call FarDecompressWRA6InB + pop bc + pop hl + ld de, wDecompressScratch + farcall BillsPC_SafeRequest2bppInWRA6 + pop hl + ret + FreezeMonIcons: ld hl, wSpriteAnimationStructs ld e, PARTY_LENGTH diff --git a/engine/gfx/palettes.asm b/engine/gfx/palettes.asm index cc7a62ded1..0ccdce4c71 100644 --- a/engine/gfx/palettes.asm +++ b/engine/gfx/palettes.asm @@ -94,6 +94,14 @@ else MONOCHROME_RGB_TWO endc +PokerusAndShinyPals: +if !DEF(MONOCHROME) + RGB 31, 25, 00 + RGB 31, 10, 26 +else + MONOCHROME_RGB_TWO +endc + StatsScreenPals: if !DEF(MONOCHROME) ; pink @@ -1898,3 +1906,273 @@ else RGB_MONOCHROME_DARK RGB_MONOCHROME_WHITE endc + +BillsPC_ThemePals: +if !DEF(MONOCHROME) +; standard + RGB 20, 26, 31 + ; two gender colors go here + RGB 00, 00, 00 + RGB 20, 26, 31 + RGB 05, 06, 18 + RGB 11, 16, 30 + RGB 31, 31, 31 +; pro + RGB 07, 11, 22 + ; two gender colors go here + RGB 31, 31, 31 + RGB 07, 11, 22 + RGB 03, 04, 13 + RGB 13, 24, 29 + RGB 31, 31, 31 +; mobile + RGB 20, 28, 20 + ; two gender colors go here + RGB 00, 00, 00 + RGB 20, 28, 20 + RGB 00, 00, 00 + RGB 10, 18, 15 + RGB 31, 31, 31 +; classic + RGB 00, 16, 16 + ; two gender colors go here + RGB 31, 31, 31 + RGB 00, 16, 16 + RGB 00, 00, 00 + RGB 23, 24, 24 + RGB 31, 31, 31 +; bliss + RGB 07, 20, 07 + ; two gender colors go here + RGB 31, 31, 31 + RGB 07, 20, 07 + RGB 00, 06, 15 + RGB 06, 15, 28 + RGB 31, 31, 31 +; contrast + RGB 06, 17, 24 + ; two gender colors go here + RGB 31, 31, 31 + RGB 06, 17, 24 + RGB 08, 08, 08 + RGB 31, 13, 00 + RGB 31, 31, 31 +; nature + RGB 05, 14, 00 + ; two gender colors go here + RGB 31, 31, 31 + RGB 05, 14, 00 + RGB 03, 06, 05 + RGB 12, 25, 01 + RGB 31, 31, 31 +; truth + RGB 31, 09, 09 + ; two gender colors go here + RGB 31, 31, 31 + RGB 31, 09, 09 + RGB 15, 07, 07 + RGB 31, 16, 08 + RGB 31, 31, 31 +; ideals + RGB 02, 11, 14 + ; two gender colors go here + RGB 31, 31, 31 + RGB 02, 11, 14 + RGB 00, 03, 06 + RGB 00, 26, 29 + RGB 31, 31, 31 +; light + RGB 21, 21, 21 + ; two gender colors go here + RGB 00, 00, 00 + RGB 21, 21, 21 + RGB 00, 00, 00 + RGB 10, 10, 10 + RGB 31, 31, 31 +; darkness + RGB 10, 10, 10 + ; two gender colors go here + RGB 31, 31, 31 + RGB 10, 10, 10 + RGB 00, 00, 00 + RGB 21, 21, 21 + RGB 31, 31, 31 +; matte + RGB 07, 07, 07 + ; two gender colors go here + RGB 31, 31, 31 + RGB 07, 07, 07 + RGB 07, 07, 07 + RGB 07, 07, 07 + RGB 31, 31, 31 +; normal + RGB 14, 11, 09 + ; two gender colors go here + RGB 31, 31, 31 + RGB 14, 11, 09 + RGB 05, 05, 03 + RGB 21, 21, 14 + RGB 31, 31, 31 +; fighting + RGB 09, 08, 07 + ; two gender colors go here + RGB 31, 31, 31 + RGB 09, 08, 07 + RGB 06, 01, 00 + RGB 27, 04, 02 + RGB 31, 31, 31 +; flying + RGB 14, 11, 19 + ; two gender colors go here + RGB 31, 31, 31 + RGB 14, 11, 19 + RGB 05, 04, 07 + RGB 22, 17, 30 + RGB 31, 31, 31 +; poison + RGB 09, 07, 10 + ; two gender colors go here + RGB 31, 31, 31 + RGB 09, 07, 10 + RGB 05, 02, 04 + RGB 22, 07, 19 + RGB 31, 31, 31 +; ground + RGB 17, 13, 06 + ; two gender colors go here + RGB 31, 31, 31 + RGB 17, 13, 06 + RGB 07, 06, 03 + RGB 29, 24, 12 + RGB 31, 31, 31 +; rock + RGB 14, 11, 05 + ; two gender colors go here + RGB 31, 31, 31 + RGB 14, 11, 05 + RGB 06, 05, 02 + RGB 24, 20, 07 + RGB 31, 31, 31 +; bug + RGB 15, 18, 02 + ; two gender colors go here + RGB 31, 31, 31 + RGB 15, 18, 02 + RGB 05, 05, 01 + RGB 21, 23, 06 + RGB 31, 31, 31 +; ghost + RGB 09, 07, 10 + ; two gender colors go here + RGB 31, 31, 31 + RGB 09, 07, 10 + RGB 03, 02, 04 + RGB 15, 11, 18 + RGB 31, 31, 31 +; steel + RGB 16, 15, 14 + ; two gender colors go here + RGB 31, 31, 31 + RGB 16, 15, 14 + RGB 05, 05, 06 + RGB 23, 23, 25 + RGB 31, 31, 31 +; fire + RGB 24, 06, 05 + ; two gender colors go here + RGB 31, 31, 31 + RGB 24, 06, 05 + RGB 07, 04, 01 + RGB 31, 15, 04 + RGB 31, 31, 31 +; water + RGB 09, 11, 15 + ; two gender colors go here + RGB 31, 31, 31 + RGB 09, 11, 15 + RGB 03, 04, 07 + RGB 11, 18, 30 + RGB 31, 31, 31 +; grass + RGB 11, 16, 08 + ; two gender colors go here + RGB 31, 31, 31 + RGB 11, 16, 08 + RGB 03, 06, 03 + RGB 11, 25, 11 + RGB 31, 31, 31 +; electric + RGB 23, 20, 07 + ; two gender colors go here + RGB 31, 31, 31 + RGB 23, 20, 07 + RGB 07, 06, 02 + RGB 31, 24, 06 + RGB 31, 31, 31 +; psychic + RGB 17, 10, 12 + ; two gender colors go here + RGB 31, 31, 31 + RGB 17, 10, 12 + RGB 07, 02, 03 + RGB 31, 09, 15 + RGB 31, 31, 31 +; ice + RGB 18, 18, 20 + ; two gender colors go here + RGB 31, 31, 31 + RGB 18, 18, 20 + RGB 04, 06, 06 + RGB 16, 27, 27 + RGB 31, 31, 31 +; dragon + RGB 09, 07, 18 + ; two gender colors go here + RGB 31, 31, 31 + RGB 09, 07, 18 + RGB 03, 02, 07 + RGB 15, 07, 31 + RGB 31, 31, 31 +; dark + RGB 09, 08, 07 + ; two gender colors go here + RGB 31, 31, 31 + RGB 09, 08, 07 + RGB 03, 02, 02 + RGB 15, 11, 09 + RGB 31, 31, 31 +; fairy + RGB 21, 15, 15 + ; two gender colors go here + RGB 31, 31, 31 + RGB 21, 15, 15 + RGB 07, 05, 07 + RGB 31, 20, 29 + RGB 31, 31, 31 +else +; standard + RGB_MONOCHROME_WHITE + ; two gender colors go here + RGB_MONOCHROME_BLACK + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_DARK + RGB_MONOCHROME_LIGHT + RGB_MONOCHROME_WHITE +; pro + RGB_MONOCHROME_DARK + ; two gender colors go here + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_DARK + RGB_MONOCHROME_BLACK + RGB_MONOCHROME_LIGHT + RGB_MONOCHROME_WHITE +rept NUM_BILLS_PC_THEMES - 2 + RGB_MONOCHROME_DARK + ; two gender colors go here + RGB_MONOCHROME_WHITE + RGB_MONOCHROME_DARK + RGB_MONOCHROME_BLACK + RGB_MONOCHROME_LIGHT + RGB_MONOCHROME_WHITE +endr +endc diff --git a/engine/gfx/sprite_anims.asm b/engine/gfx/sprite_anims.asm index 89f9a7b793..cc159d0a15 100644 --- a/engine/gfx/sprite_anims.asm +++ b/engine/gfx/sprite_anims.asm @@ -33,6 +33,10 @@ DoAnimFrame: dw AnimSeq_IntroSuicuneAway ; SPRITE_ANIM_SEQ_SUICUNE_AWAY dw AnimSeq_Celebi ; SPRITE_ANIM_SEQ_CELEBI dw AnimSeq_MaxStatSparkle ; SPRITE_ANIM_SEQ_MAX_STAT_SPARKLE + dw AnimSeq_PcCursor ; SPRITE_ANIM_SEQ_PC_CURSOR + dw AnimSeq_PcQuick ; SPRITE_ANIM_SEQ_PC_QUICK + dw AnimSeq_PcMode ; SPRITE_ANIM_SEQ_PC_MODE + dw AnimSeq_PcPack ; SPRITE_ANIM_SEQ_PC_PACK AnimSeq_PartyMon: ld a, [wMenuCursorY] @@ -592,6 +596,153 @@ AnimSeq_MaxStatSparkle: ld [hl], a ret +AnimSeq_PcCursor: + push de + push bc + farcall BillsPC_GetCursorSlot + farcall BillsPC_GetXYFromStorageBox + pop bc + ld hl, SPRITEANIMSTRUCT_XOFFSET + add hl, bc + ld [hl], d + ld hl, SPRITEANIMSTRUCT_YOFFSET + add hl, bc + ld [hl], e + pop de + + ; Check for static cursor + ld a, [wBillsPC_CursorAnimFlag] + and a + ret z + + ; If we're picking up, the PC UI handles this flag. + cp PCANIM_PICKUP + jr c, .not_picking + sub PCANIM_PICKUP - 1 + add [hl] + ld [hl], a + ret +.not_picking + cp PCANIM_ANIMATE / 2 + 1 + jr c, .dont_bop + inc [hl] + inc [hl] +.dont_bop + dec a + ld [wBillsPC_CursorAnimFlag], a + ret nz + ld a, PCANIM_ANIMATE + ld [wBillsPC_CursorAnimFlag], a + ret + +AnimSeq_PcQuick: + ; Moves a storage system mini from one destination to another. + push de + + ; Check if the animation has concluded + ld hl, wBillsPC_QuickFrames + inc [hl] + dec [hl] + jr z, .finish_anim + dec [hl] + + ; Handle x movement. + ld a, [wBillsPC_QuickFromX] + ld d, a + ld a, [wBillsPC_QuickToX] + ld e, a + ld hl, SPRITEANIMSTRUCT_XOFFSET + call .ShiftPos + ld a, [wBillsPC_QuickFromY] + ld d, a + ld a, [wBillsPC_QuickToY] + ld e, a + ld hl, SPRITEANIMSTRUCT_YOFFSET + call .ShiftPos + jr .done + +.finish_anim + farcall BillsPC_FinishQuickAnim + ; fallthrough +.done + pop de + ret + +.ShiftPos: + ; Set sprite position depending on movement frame. + push hl + push bc + + ; Compute the difference between the coordinates + ld a, d + sub e + + ; Load the result into bc. This sets b to $ff if we got a negative result. + ld c, a + sbc a + ld b, a + + ; Multiply by the frame number. + xor a + ld h, a + ld l, a + ld a, [wBillsPC_QuickFrames] + inc a +.loop + dec a + jr z, .got_multiplier + add hl, bc + jr .loop +.got_multiplier + ; Divide by 8 and put 8bit result in a. + ld a, l + sra h + rra + sra h + rra + sra h + rra + + ; Get resulting coordinate. + add e + + ; Write to sprite anim coord. + pop bc + pop hl + add hl, bc + ld [hl], a + ret + +AnimSeq_PcMode: + ld a, [wBillsPC_CursorMode] + ld h, a + add h + add h + ld hl, SPRITEANIMSTRUCT_TILE_ID + add hl, bc + ld [hl], a + ret + +AnimSeq_PcPack: + ; Display male or female pack + ld a, [wPlayerGender] + add a + add a + ld hl, SPRITEANIMSTRUCT_TILE_ID + add hl, bc + ld [hl], a + + ; Hide pack outside Item mode + farcall BillsPC_CheckBagDisplay + ld a, $80 ; move it out of view + jr nz, .got_pack_y + xor a +.got_pack_y + ld hl, SPRITEANIMSTRUCT_YOFFSET + add hl, bc + ld [hl], a + ret + AnimSeqs_IncAnonJumptableIndex: ld hl, SPRITEANIMSTRUCT_JUMPTABLE_INDEX add hl, bc diff --git a/engine/items/item_effects.asm b/engine/items/item_effects.asm index a51f505cbf..86e6727e5f 100644 --- a/engine/items/item_effects.asm +++ b/engine/items/item_effects.asm @@ -351,12 +351,8 @@ PokeBallEffect: jr .room_in_party .check_room - ld a, BANK(sBoxCount) - call GetSRAMBank - ld a, [sBoxCount] - cp MONS_PER_BOX - call CloseSRAM - jp z, Ball_BoxIsFullMessage + farcall NewStorageBoxPointer + jp c, Ball_BoxIsFullMessage .room_in_party xor a @@ -626,12 +622,8 @@ PokeBallEffect: farcall SetBoxMonCaughtData - ld a, BANK(sBoxCount) - call GetSRAMBank - - ld a, [sBoxCount] - cp MONS_PER_BOX - jr nz, .BoxNotFullYet + farcall NewStorageBoxPointer + jr nc, .BoxNotFullYet ld hl, wBattleResult set 7, [hl] .BoxNotFullYet: @@ -640,9 +632,8 @@ PokeBallEffect: jr nz, .SkipBoxMonFriendBall ; caught Pokemon become the first Pokemon in the box ld a, FRIEND_BALL_HAPPINESS - ld [sBoxMon1Happiness], a + ld [wTempMonHappiness], a .SkipBoxMonFriendBall: - call CloseSRAM ld a, [wInitialOptions] bit NUZLOCKE_MODE, a @@ -661,37 +652,55 @@ PokeBallEffect: .AlwaysNicknameBox: xor a ld [wCurPartyMon], a - ld a, BOXMON + ld a, TEMPMON ld [wMonType], a ld de, wMonOrItemNameBuffer ld b, $0 ; pokemon farcall NamingScreen - ld a, BANK(sBoxMonNicknames) - call GetSRAMBank - ld hl, wMonOrItemNameBuffer - ld de, sBoxMonNicknames + ld de, wTempMonNickname ld bc, MON_NAME_LENGTH rst CopyBytes - ld hl, sBoxMonNicknames + ld hl, wTempMonNickname ld de, wStringBuffer1 call InitName - call CloseSRAM - .SkipBoxMonNickname: - ld a, BANK(sBoxMonNicknames) - call GetSRAMBank - - ld hl, sBoxMonNicknames + ld hl, wTempMonNickname ld de, wMonOrItemNameBuffer ld bc, MON_NAME_LENGTH rst CopyBytes - call CloseSRAM + farcall UpdateStorageBoxMonFromTemp + + ; Switch current Box if it was full. We can check this by checking if + ; the tempmon's box location matches the current box. + ld a, [wTempMonBox] + ld b, a + ld a, [wCurBox] + inc a + cp b + jr z, .curbox_not_full + + push bc + ld b, a + farcall GetBoxName + ld hl, Text_CurBoxFull + call PrintText + pop bc + ; Switch current box. + ld a, b + dec a + ld [wCurBox], a + +.curbox_not_full + ld a, [wCurBox] + inc a + ld b, a + farcall GetBoxName ld hl, Text_SentToBillsPC call PrintText @@ -832,6 +841,10 @@ TextJump_Waitbutton: text_far Text_Waitbutton_2 text_end +Text_CurBoxFull: + text_far _BallCurBoxFullText + text_end + Text_SentToBillsPC: ; was sent to BILL's PC. text_far _BallSentToPCText @@ -2258,7 +2271,9 @@ WontHaveAnyEffect_NotUsedMessage: jr ItemWasntUsedMessage Ball_BoxIsFullMessage: - ld hl, Ball_BoxIsFullText + ld hl, Ball_StorageFullText + jr z, ItemWasntUsedMessage + ld hl, Ball_DatabaseTaxedText jr ItemWasntUsedMessage Ball_MonIsHiddenMessage: @@ -2383,9 +2398,14 @@ DontBeAThiefText: text_far _BallDontBeAThiefText text_end -Ball_BoxIsFullText: +Ball_StorageFullText: + ; The #MON BOX is full. That can't be used now. + text_far _BallStorageFullText + text_end + +Ball_DatabaseTaxedText: ; The #MON BOX is full. That can't be used now. - text_far _BallBoxFullText + text_far _BallDatabaseFullText text_end Ball_MonIsHiddenText: @@ -2496,6 +2516,18 @@ ComputeMaxPP: pop bc ret +RestoreTempPP: + ld hl, wTempMonMoves + ld de, wTempMonPP + ld a, [wMenuCursorY] + push af + ld a, TEMPMON + ld [wMonType], a + call _RestoreAllPP + pop af + ld [wMenuCursorY], a + ret + RestoreAllPP: ld a, MON_PP call GetPartyParamLocation @@ -2504,8 +2536,11 @@ RestoreAllPP: call GetPartyParamLocation pop de xor a ; PARTYMON - ld [wMenuCursorY], a ld [wMonType], a + ; fallthrough +_RestoreAllPP: + xor a + ld [wMenuCursorY], a ld c, NUM_MOVES .loop ld a, [hli] diff --git a/engine/link/link.asm b/engine/link/link.asm index 47e2dbc2a8..3e1edaa985 100755 --- a/engine/link/link.asm +++ b/engine/link/link.asm @@ -1619,7 +1619,7 @@ LinkTrade: xor a ; REMOVE_PARTY ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox + predef RemoveMonFromParty ld a, [wPartyCount] dec a ld [wCurPartyMon], a @@ -1663,7 +1663,7 @@ LinkTrade: ld de, wTempMonSpecies ld bc, PARTYMON_STRUCT_LENGTH rst CopyBytes - farcall AddTempmonToParty + farcall AddTempMonToParty ld a, [wPartyCount] dec a ld [wCurPartyMon], a diff --git a/engine/math/print_num.asm b/engine/math/print_num.asm index 14b88395d5..2b03b8cc6e 100755 --- a/engine/math/print_num.asm +++ b/engine/math/print_num.asm @@ -3,6 +3,7 @@ _PrintNum:: ; High nibble of c denotes decimal point location. ; Works on up to 1-8 digits and up to 4 bytes (up to 99999999). ; The higher b nibble has some flags: +; Bit 4: For each number printed, add a text delay (used for text_decimal) ; Bit 5: Print a pokedollar sign before the number itself ; Bit 6: Left-aligned number instead of right-aligned ; Bit 7: Print leading zeros @@ -23,7 +24,7 @@ _PrintNum:: push hl ld c, 4 ld a, b - and $1f + and $f ld b, a .loop ld a, b @@ -83,7 +84,7 @@ endr ; Store maximum string length in the lower b nibble instead of c. ; Use c as a loop counter instead. This simplifies code a bit. ld a, b - and $e0 + and $f0 add c ld b, a ld c, 8 @@ -161,12 +162,16 @@ PrintHLNum: push af ld a, "¥" ld [hli], a + bit PRINTNUM_DELAY_F, b + call nz, .printnum_delay pop af .got_number add "0" .got_value ld [hli], a + bit PRINTNUM_DELAY_F, b + call nz, .printnum_delay ldh a, [hPrintNum + 4] and a ret z @@ -176,3 +181,9 @@ PrintHLNum: ld a, "." ld [hli], a ret +.printnum_delay + push hl + push de + push bc + call PrintLetterDelay + jp PopBCDEHL diff --git a/engine/menus/intro_menu.asm b/engine/menus/intro_menu.asm index 15be69fe7a..a30d00a566 100755 --- a/engine/menus/intro_menu.asm +++ b/engine/menus/intro_menu.asm @@ -79,12 +79,7 @@ ResetWRAM_NotPlus: ld [wCurBox], a - call SetDefaultBoxNames - - ld a, BANK(sBoxCount) - call GetSRAMBank - ld hl, sBoxCount - call _ResetWRAM_InitList + farcall InitializeBoxes call CloseSRAM ld hl, wMoney @@ -107,17 +102,13 @@ ResetWRAM: xor a rst ByteFill - ; erase wGameData, but keep Money, wCurBox, wBoxNames, and wBattlePoints + ; erase wGameData, but keep wMoney and wBattlePoints ld hl, wGameData ld bc, wMoney - wGameData xor a rst ByteFill ld hl, wMoneyEnd - ld bc, wCurBox - wMoneyEnd - xor a - rst ByteFill - ld hl, wBoxNamesEnd - ld bc, wBattlePoints - wBoxNamesEnd + ld bc, wBattlePoints - wMoneyEnd xor a rst ByteFill ld hl, wBattlePointsEnd @@ -125,6 +116,12 @@ ResetWRAM: xor a rst ByteFill + ; Fill party species array with terminators. + ld hl, wPartySpecies + ld bc, PARTY_LENGTH + 1 + dec a ; ld a, -1 + rst ByteFill + call Random ldh a, [rLY] ldh [hSecondsBackup], a @@ -238,37 +235,6 @@ _ResetWRAM_InitList: ld [hl], a ret -SetDefaultBoxNames: - ld hl, wBoxNames - ld c, 0 -.loop - push hl - ld de, .Box - call CopyName2 - dec hl - ld a, c - inc a - cp 10 - jr c, .less - sub 10 - ld [hl], "1" ; no-optimize *hl++|*hl-- = N - inc hl -.less - add "0" - ld [hli], a - ld [hl], "@" - pop hl - ld de, 9 - add hl, de - inc c - ld a, c - cp NUM_BOXES - jr c, .loop - ret - -.Box: - db "Box@" - InitializeMagikarpHouse: ld hl, wBestMagikarpLengthMmHi ld a, $3 diff --git a/engine/menus/naming_screen.asm b/engine/menus/naming_screen.asm index abb82991a8..08a64c8a3a 100755 --- a/engine/menus/naming_screen.asm +++ b/engine/menus/naming_screen.asm @@ -197,7 +197,8 @@ NamingScreen: jr .StoreParams .StoreBoxIconParams: - ld a, BOX_NAME_LENGTH - 1 + ; the terminator isn't saved, so no "- 1" is needed. + ld a, BOX_NAME_LENGTH hlcoord 5, 4 .StoreParams: diff --git a/engine/menus/save.asm b/engine/menus/save.asm index 66209bcfbf..a64150302b 100644 --- a/engine/menus/save.asm +++ b/engine/menus/save.asm @@ -1,3 +1,5 @@ +BOXSAVE_USECURRENT EQU 1 + SaveMenu: ld c, 4 call SFXDelayFrames @@ -42,32 +44,6 @@ SaveAfterLinkTrade: call SaveRTC jp ClearWRAMStateAfterSave -ChangeBoxSaveGame: - push de - ld hl, ChangeBoxSaveText - call MenuTextbox - call YesNoBox - call ExitMenu - jr c, .refused - call AskOverwriteSaveFile - jr c, .refused - jr nc, SaveAndChangeBox -.refused - pop de - ret - -SaveAndChangeBox: - call SetWRAMStateForSave - call SaveBox - pop de - ld a, e - ld [wCurBox], a - call LoadBox - call SavedTheGame - call ClearWRAMStateAfterSave - and a - ret - Link_SaveGame: call AskOverwriteSaveFile ret c @@ -78,61 +54,6 @@ ForceGameSave: and a ret -MovePkmnWOMail_SaveGame: - call SetWRAMStateForSave - push de - call SaveBox - pop de - ld a, e - ld [wCurBox], a - call LoadBox - jp ClearWRAMStateAfterSave - -MovePkmnWOMail_InsertMon_SaveGame: - call SetWRAMStateForSave - push de - call SaveBox - pop de - ld a, e - ld [wCurBox], a - ld a, $1 - ld [wSaveFileExists], a - call StageRTCTimeForSave - call ValidateSave - call SaveOptions - call SavePlayerData - call SavePokemonData - call SaveChecksum - call ValidateBackupSave - call SaveBackupOptions - call SaveBackupPlayerData - call SaveBackupPokemonData - call SaveBackupChecksum - farcall BackupPartyMonMail - call SaveRTC - call LoadBox - call ClearWRAMStateAfterSave - ld de, SFX_SAVE - jp PlaySFX - -StartMovePkmnWOMail_SaveGame: - ld hl, MoveMonWOMailSaveText - call MenuTextbox - call YesNoBox - call ExitMenu - jr c, .refused - call AskOverwriteSaveFile - jr c, .refused - call SetWRAMStateForSave - call SavedTheGame - call ClearWRAMStateAfterSave - and a - ret - -.refused - scf - ret - SetWRAMStateForSave: ld a, $1 ld [wGameLogicPaused], a @@ -226,7 +147,6 @@ SaveGameData:: call SaveOptions call SavePlayerData call SavePokemonData - call SaveBox ; This function is never called mid-Battle Tower (only in the beginning). ; So this is always a safe action, and gets rid of potential old BT state @@ -237,21 +157,63 @@ SaveGameData:: xor a ld [sBattleTowerChallengeState], a + ; At this point, there is no longer any harm in setting this. We can't set + ; it earlier, because it might confuse the load routine into using bad + ; box/mail data, and we can't set it later because we need to set it + ; before our main save copy is valid. + ld a, 1 + call SetSavePhase + call SaveChecksum + call WriteBackupSave + farcall SaveRTC ; should we move this? + call CloseSRAM ; just in case + pop af + ldh [hVBlank], a + ret + +WriteBackupSave: +; Runs after saving the main copy. Writes the "pseudo-WRAM" copies of storage +; and mail, then creates the backup save. This process is automatically run +; on game load if we have a valid main save but not a backup save. + ; Save storage and mail to backup + farcall BackupPartyMonMail + call SaveStorageSystem + + ; Save the backup copy of game data. call ValidateBackupSave call SaveBackupOptions call SaveBackupPlayerData call SaveBackupPokemonData call SaveBackupChecksum - farcall BackupPartyMonMail - call SaveRTC - call CloseSRAM ; just in case - pop af - ldh [hVBlank], a - ret + + ; Finished saving. + xor a + call SetSavePhase + jp CloseSRAM + +LoadStorageSystem: +; Copy backup storage system to active. + ld hl, sBackupNewBox1 + ld de, sNewBox1 + call CopyStorageSystem + + ; Initialize allocation information. + farjp FlushStorageSystem + +SaveStorageSystem: +; Copy active storage system to backup. + ld hl, sNewBox1 + ld de, sBackupNewBox1 + ; fallthrough +CopyStorageSystem: + ld a, BANK(sNewBox1) + call GetSRAMBank + ld bc, sNewBoxEnd - sNewBox1 + rst CopyBytes + jp CloseSRAM ErasePreviousSave: - call EraseBoxes call EraseHallOfFame call EraseLinkBattleStats call EraseBattleTowerStatus @@ -335,10 +297,6 @@ SavePokemonData: rst CopyBytes jp CloseSRAM -SaveBox: - call GetBoxAddress - jp SaveBoxAddress - SaveChecksum: ld hl, sGameData ld bc, sGameDataEnd - sGameData @@ -405,19 +363,38 @@ SaveBackupChecksum: ld [sBackupChecksum + 1], a jp CloseSRAM +WasMidSaveAborted: +; Returns z if the system was reset mid-saving. + ld a, BANK(sWritingBackup) + call GetSRAMBank + ld a, [sWritingBackup] + dec a + jp CloseSRAM + +SetSavePhase: +; set current save phase: 1 (saving), 0 (not saving). + push af + ld a, BANK(sWritingBackup) + call GetSRAMBank + pop af + ld [sWritingBackup], a + jp CloseSRAM + TryLoadSaveFile: call VerifyGameVersion call VerifyChecksum jr nz, .backup call LoadPlayerData call LoadPokemonData - call LoadBox + + ; If a mid-save was aborted but main save data is good, finish it. + call WasMidSaveAborted + call z, WriteBackupSave farcall RestorePartyMonMail - call ValidateBackupSave - call SaveBackupOptions - call SaveBackupPlayerData - call SaveBackupPokemonData - call SaveBackupChecksum + call LoadStorageSystem + + ; Just in case + call WriteBackupSave and a ret @@ -426,13 +403,9 @@ TryLoadSaveFile: jr nz, .corrupt call LoadBackupPlayerData call LoadBackupPokemonData - call LoadBox farcall RestorePartyMonMail - call ValidateSave - call SaveOptions - call SavePlayerData - call SavePokemonData - call SaveChecksum + call LoadStorageSystem + call SaveGameData and a ret @@ -502,7 +475,10 @@ CheckPrimarySaveFile: call GetSRAMBank ld a, [sCheckValue1] cp SAVE_CHECK_VALUE_1 + jr z, .yes + cp SAVE_CHECK_VALUE_1_OLD jr nz, .nope +.yes ld a, [sCheckValue2] cp SAVE_CHECK_VALUE_2 jr nz, .nope @@ -524,7 +500,10 @@ CheckBackupSaveFile: call GetSRAMBank ld a, [sBackupCheckValue1] cp SAVE_CHECK_VALUE_1 + jr z, .yes + cp SAVE_CHECK_VALUE_1_OLD jr nz, .nope +.yes ld a, [sBackupCheckValue2] cp SAVE_CHECK_VALUE_2 jr nz, .nope @@ -562,10 +541,6 @@ LoadPokemonData: rst CopyBytes jp CloseSRAM -LoadBox: - call GetBoxAddress - jp LoadBoxAddress - VerifyChecksum: ld hl, sGameData ld bc, sGameDataEnd - sGameData @@ -622,228 +597,6 @@ VerifyBackupChecksum: pop af ret -GetBoxAddress: - ld a, [wCurBox] - cp NUM_BOXES - jr c, .ok - xor a - ld [wCurBox], a - -.ok - ld e, a - ld d, 0 - ld hl, BoxAddresses -rept 5 - add hl, de -endr - ld a, [hli] - push af - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - ld a, [hli] - ld h, [hl] - ld l, a - pop af - ret - -SaveBoxAddress: -; Save box via wMisc. -; We do this in three steps because the size of wMisc is less than -; the size of sBox. - push hl -; Load the first part of the active box. - push af - push de - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBox - ld de, wMisc - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - pop de - pop af -; Save it to the target box. - push af - push de - call GetSRAMBank - ld hl, wMisc - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - -; Load the second part of the active box. - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBox + (wMiscEnd - wMisc) - ld de, wMisc - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - pop de - pop af - - ld hl, (wMiscEnd - wMisc) - add hl, de - ld e, l - ld d, h -; Save it to the next part of the target box. - push af - push de - call GetSRAMBank - ld hl, wMisc - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - -; Load the third and final part of the active box. - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBox + (wMiscEnd - wMisc) * 2 - ld de, wMisc - ld bc, sBoxEnd - (sBox + (wMiscEnd - wMisc) * 2) ; $8e - rst CopyBytes - call CloseSRAM - pop de - pop af - - ld hl, (wMiscEnd - wMisc) - add hl, de - ld e, l - ld d, h -; Save it to the final part of the target box. - call GetSRAMBank - ld hl, wMisc - ld bc, sBoxEnd - (sBox + (wMiscEnd - wMisc) * 2) ; $8e - rst CopyBytes - call CloseSRAM - - pop hl - ret - -LoadBoxAddress: -; Load box via wMisc. -; We do this in three steps because the size of wMisc is less than -; the size of sBox. - push hl - ld l, e - ld h, d -; Load part 1 - push af - push hl - call GetSRAMBank - ld de, wMisc - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - ld a, BANK(sBox) - call GetSRAMBank - ld hl, wMisc - ld de, sBox - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - pop hl - pop af - - ld de, (wMiscEnd - wMisc) - add hl, de -; Load part 2 - push af - push hl - call GetSRAMBank - ld de, wMisc - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - ld a, BANK(sBox) - call GetSRAMBank - ld hl, wMisc - ld de, sBox + (wMiscEnd - wMisc) - ld bc, (wMiscEnd - wMisc) - rst CopyBytes - call CloseSRAM - pop hl - pop af -; Load part 3 - ld de, (wMiscEnd - wMisc) - add hl, de - call GetSRAMBank - ld de, wMisc - ld bc, sBoxEnd - (sBox + (wMiscEnd - wMisc) * 2) ; $8e - rst CopyBytes - call CloseSRAM - ld a, BANK(sBox) - call GetSRAMBank - ld hl, wMisc - ld de, sBox + (wMiscEnd - wMisc) * 2 - ld bc, sBoxEnd - (sBox + (wMiscEnd - wMisc) * 2) ; $8e - rst CopyBytes - call CloseSRAM - - pop hl - ret - -EraseBoxes: - ld hl, BoxAddresses - ld c, NUM_BOXES -.next - push bc - ld a, [hli] - call GetSRAMBank - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - xor a - ld [de], a - inc de - ld a, -1 - ld [de], a - inc de - ld bc, sBoxEnd - (sBox + 2) -.clear - xor a - ld [de], a - inc de - dec bc - ld a, b - or c - jr nz, .clear - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - ld a, -1 - ld [de], a - inc de - xor a - ld [de], a - call CloseSRAM - pop bc - dec c - jr nz, .next - ret - -BoxAddresses: -; dbww bank, address, address - dbww BANK(sBox1), sBox1, sBox1End - dbww BANK(sBox2), sBox2, sBox2End - dbww BANK(sBox3), sBox3, sBox3End - dbww BANK(sBox4), sBox4, sBox4End - dbww BANK(sBox5), sBox5, sBox5End - dbww BANK(sBox6), sBox6, sBox6End - dbww BANK(sBox7), sBox7, sBox7End - dbww BANK(sBox8), sBox8, sBox8End - dbww BANK(sBox9), sBox9, sBox9End - dbww BANK(sBox10), sBox10, sBox10End - dbww BANK(sBox11), sBox11, sBox11End - dbww BANK(sBox12), sBox12, sBox12End - dbww BANK(sBox13), sBox13, sBox13End - dbww BANK(sBox14), sBox14, sBox14End - Checksum: ld de, 0 .loop @@ -885,11 +638,6 @@ SaveFileCorruptedText: text_far _SaveFileCorruptedText text_end -ChangeBoxSaveText: - ; When you change a #MON BOX, data will be saved. OK? - text_far _ChangeBoxSaveText - text_end - MoveMonWOMailSaveText: ; Each time you move a #MON, data will be saved. OK? text_far _MoveMonWOMailSaveText diff --git a/engine/movie/bsod.asm b/engine/movie/bsod.asm index 837b747f21..3e1e4f74ce 100644 --- a/engine/movie/bsod.asm +++ b/engine/movie/bsod.asm @@ -62,6 +62,12 @@ BSOD: dec a ; a == ERR_VERSION_MISMATCH? ld de, BSOD_VersionMismatch jr z, .done + dec a ; a == ERR_OLDBOX? + ld de, BSOD_OldBox + jr z, .done + dec a ; a == ERR_NEWBOX? + ld de, BSOD_NewBox + jr z, .done ld de, BSOD_UnknownError .done hlcoord 1, 14 @@ -144,5 +150,11 @@ BSOD_OldBTState: BSOD_VersionMismatch: db "Version mismatch@" +BSOD_OldBox: + db "Old PC box storage@" + +BSOD_NewBox: + db "Fatal PC box error@" + BSOD_UnknownError: db "Unknown error@" diff --git a/engine/overworld/variables.asm b/engine/overworld/variables.asm index 6d8999397e..2b771b7d7d 100644 --- a/engine/overworld/variables.asm +++ b/engine/overworld/variables.asm @@ -118,15 +118,8 @@ _GetVarAction:: ret .BoxFreeSpace: -; Remaining slots in the current box. - ld a, BANK(sBoxCount) - call GetSRAMBank - ld hl, sBoxCount - ld a, MONS_PER_BOX - sub [hl] - ld b, a - call CloseSRAM - ld a, b +; Remaining database entries + farcall CheckFreeDatabaseEntries jp .loadstringbuffer2 .BattleResult: diff --git a/engine/phone/phone_scripts.asm b/engine/phone/phone_scripts.asm index 9bc8f1045e..7bb8d5e538 100644 --- a/engine/phone/phone_scripts.asm +++ b/engine/phone/phone_scripts.asm @@ -193,9 +193,12 @@ BillPhoneScript2: BillPhoneScriptCheckForBoxes: special BillBoxSwitchCheck ifequal 0, BillPhoneWholePCFull - farwritetext BillWantNextBox - special BillBoxSwitch + farwritetext BillFlushBySaving + yesorno + iffalse .rejected + special Special_TryQuickSave iftrue .hang_up +.rejected farwritetext BillCallMeToSwitch .hang_up farwritetext BillThankYouText diff --git a/engine/pokegear/pokegear.asm b/engine/pokegear/pokegear.asm index e765286a6a..dd0f68cbc2 100755 --- a/engine/pokegear/pokegear.asm +++ b/engine/pokegear/pokegear.asm @@ -260,7 +260,7 @@ InitPokegearTilemap: dw .Radio .Clock: - ld de, ClockTilemapRLE + ld hl, ClockTilemapRLE call Pokegear_LoadTilemapRLE hlcoord 12, 1 ld de, .switch @@ -287,14 +287,14 @@ InitPokegearTilemap: jp PokegearMap_UpdateLandmarkName .Radio: - ld de, RadioTilemapRLE + ld hl, RadioTilemapRLE call Pokegear_LoadTilemapRLE hlcoord 0, 12 lb bc, 4, 18 jp Textbox .Phone: - ld de, PhoneTilemapRLE + ld hl, PhoneTilemapRLE call Pokegear_LoadTilemapRLE hlcoord 0, 12 lb bc, 4, 18 @@ -1286,23 +1286,9 @@ DeleteSpriteAnimStruct2ToEnd: Pokegear_LoadTilemapRLE: ; Format: repeat count, tile ID - ; Terminated with $FF - hlcoord 0, 0 -.loop - ld a, [de] - cp $ff - ret z - ld b, a - inc de - ld a, [de] - ld c, a - inc de - ld a, b -.load - ld [hli], a - dec c - jr nz, .load - jr .loop + ; Terminated with $ff + decoord 0, 0 + jp CopyRLE PokegearText_WhomToCall: ; Whom do you want to call? diff --git a/engine/pokemon/bills_pc.asm b/engine/pokemon/bills_pc.asm index 965ac80701..ca0217de72 100644 --- a/engine/pokemon/bills_pc.asm +++ b/engine/pokemon/bills_pc.asm @@ -1,2511 +1,1377 @@ -_DepositPKMN: - ld hl, wOptions1 - ld a, [hl] - push af - set NO_TEXT_SCROLL, [hl] - ld a, [wVramState] - push af - xor a - ld [wVramState], a - ldh a, [hInMenu] - push af - ld a, $1 - ldh [hInMenu], a - xor a - ldh [hMapAnims], a - call BillsPC_InitRAM - xor a - ld [wBillsPC_LoadedBox], a - call DelayFrame +CheckCurPartyMonFainted: + ld hl, wPartyMon1HP + ld de, PARTYMON_STRUCT_LENGTH + ld b, $0 .loop - call JoyTextDelay - ld a, [wJumptableIndex] - bit 7, a - jr nz, .done - call .RunJumptable - call DelayFrame + ld a, [wCurPartyMon] + cp b + jr z, .skip + ld a, [hli] + or [hl] + jr nz, .notfainted + dec hl + +.skip + inc b + ld a, [wPartyCount] + cp b + jr z, .done + add hl, de jr .loop + .done - call ClearSprites - pop af - ldh [hInMenu], a - pop af - ld [wVramState], a - pop af - ld [wOptions1], a + scf ret -.RunJumptable: - call StandardStackJumpTable - -.Jumptable: - dw .Init - dw .HandleJoypad - dw .WhatsUp - dw .Submenu - dw BillsPC_EndJumptableLoop - -.Init: - xor a - ldh [hBGMapMode], a - call ClearSprites - call CopyBoxmonSpecies - call BillsPC_BoxName - ld de, PCString_ChooseaPKMN - call BillsPC_PlaceString - ld a, $5 - ld [wBillsPC_NumMonsOnScreen], a - call BillsPC_RefreshTextboxes - call PCMonInfo - call BillsPC_PrintBoxCountAndCapacityInsideBox - xor a - ld [wCurPartySpecies], a - ld a, CGB_BILLS_PC - call BillsPC_ApplyPalettes - call ApplyTilemapInVBlank - call BillsPC_UpdateSelectionCursor - jp BillsPC_IncrementJumptableIndex - -.HandleJoypad: - ld hl, hJoyPressed - ld a, [hl] - and B_BUTTON - jr nz, .b_button - ld a, [hl] - and A_BUTTON - jr nz, .a_button - call Withdraw_UpDown - and a - ret z - call BillsPC_UpdateSelectionCursor - xor a - ldh [hBGMapMode], a - call BillsPC_RefreshTextboxes - call PCMonInfo - call BillsPC_PrintBoxCountAndCapacityInsideBox - ld a, $1 - ldh [hBGMapMode], a - call DelayFrame - jp DelayFrame - -.a_button - call BillsPC_GetSelectedPokemonSpecies +.notfainted and a - ret z - cp -1 - jr z, .b_button - ld a, $2 - ld [wJumptableIndex], a - ret - -.go_back - ld hl, wJumptableIndex - dec [hl] - ret - -.b_button - ld a, $4 - ld [wJumptableIndex], a ret -.WhatsUp: - xor a - ldh [hBGMapMode], a - call ClearSprites - call BillsPC_GetSelectedMonPal - ld de, PCString_WhatsUp - call BillsPC_PlaceString - ld a, $1 - ld [wMenuCursorY], a - jp BillsPC_IncrementJumptableIndex - -.Submenu: - ld hl, BillsPCDepositMenuDataHeader - call CopyMenuHeader - ld a, [wMenuCursorY] - ld [wMenuCursorBuffer], a - call VerticalMenu - jp c, BillsPCDepositFuncCancel - ld a, [wMenuCursorY] - dec a - and $3 - call StackJumpTable - -BillsPCDepositJumptable: - dw BillsPCDepositFuncDeposit ; Deposit Pokemon - dw BillsPCDepositFuncStats ; Pokemon Stats - dw BillsPCDepositFuncRelease ; Release Pokemon - dw BillsPCDepositFuncCancel ; Cancel - -BillsPCDepositFuncDeposit: - call BillsPC_CheckMail_PreventBlackout - jp c, BillsPCDepositFuncCancel - call DepositPokemon - jr c, .box_full +SwapStorageBoxSlots: +; Swaps slots from de to bc. Preserves de, while bc is changed to a proper slot +; if c is 0, otherwise preserved. Equivalent to bc->de except c may be 0 to mean +; "put anywhere in the party/box". Returns the following in a: +; 0: Successful swap +; 1: Save is required to perform the swap +; 2: The party is full +; 3: The box is full +; 4: Doing this would remove the last healthy mon in party +; 5: Can't move partymon to Box, because they're holding Mail. + ; Compare source->dest to see if we're "moving" something with itself. + ld h, -1 + ld a, b + cp d + ld a, c + jr nz, .not_equal + ld h, e + cp e + jr nz, .not_equal +.done xor a - ld [wJumptableIndex], a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a ret +.not_equal + ; Convert destination slot 0 to a real destination, if we can. + push de + and a ; a is c from beforehand. + jr nz, .got_dest -.box_full - ld de, PCString_WhatsUp - jp BillsPC_PlaceString - -BillsPCDepositFuncStats: - call LoadStandardMenuHeader - call BillsPC_StatsScreen - call ExitMenu - call PCMonInfo - jp BillsPC_GetSelectedMonPal - -BillsPCDepositFuncRelease: - call BillsPC_CheckMail_PreventBlackout - jr c, BillsPCDepositFuncCancel - call BillsPC_IsMonAnEgg - jr c, BillsPCDepositFuncCancel - ld a, [wMenuCursorY] - push af - ld de, PCString_ReleasePKMN - call BillsPC_PlaceString - call LoadStandardMenuHeader - lb bc, 14, 11 - call PlaceYesNoBox - ld a, [wMenuCursorY] - dec a - call ExitMenu + ld e, PARTY_LENGTH + ld a, b and a - jr nz, .failed_release - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - xor a - ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox - call ReleasePKMN_ByePKMN - xor a - ld [wJumptableIndex], a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a - pop af - ret + jr z, .dest_loop + ld e, MONS_PER_BOX +.dest_loop + inc c + call GetStorageBoxMon + jr z, .got_dest + ld a, c + cp h + jr nz, .dest_next -.failed_release - ld de, PCString_WhatsUp - call BillsPC_PlaceString - pop af - ld [wMenuCursorY], a - ret + ; We encountered our current entry while seeking for blank entries. This + ; basically makes this a no-op (since there's no earlier blank entry), so + ; return early. + pop de + jr .done -BillsPCDepositFuncCancel: - xor a - ld [wJumptableIndex], a - ret +.dest_next + cp e + jr nz, .dest_loop -BillsPCDepositMenuDataHeader: - db $40 ; flags - db 04, 09 ; start coords - db 13, 19 ; end coords - dw .MenuData2 - db 1 ; default option - -.MenuData2: - db $80 ; flags - db 4 ; items - db "Deposit@" - db "Stats@" - db "Release@" - db "Cancel@" - -_WithdrawPKMN: - ld hl, wOptions1 - ld a, [hl] - push af - set NO_TEXT_SCROLL, [hl] - ld a, [wVramState] - push af - xor a - ld [wVramState], a - ldh a, [hInMenu] - push af - ld a, $1 - ldh [hInMenu], a - xor a - ldh [hMapAnims], a - call BillsPC_InitRAM - ld a, NUM_BOXES + 1 - ld [wBillsPC_LoadedBox], a - call DelayFrame -.loop - call JoyTextDelay - ld a, [wJumptableIndex] - bit 7, a - jr nz, .done - call .RunJumptable - call DelayFrame - jr .loop -.done - call ClearSprites - pop af - ldh [hInMenu], a - pop af - ld [wVramState], a - pop af - ld [wOptions1], a + ; Party (or Box) is full + pop de + cp MONS_PER_BOX + ld a, 2 + ret c + inc a ret -.RunJumptable: - call StandardStackJumpTable +.got_dest + pop de -.Jumptable: - dw .Init - dw .Joypad - dw .PrepSubmenu - dw BillsPC_Withdraw - dw BillsPC_EndJumptableLoop + ; Now that we have proper slots, preserve bcde from this point. + push de + push bc + call .do_it + pop bc + pop de + ret -.Init: - ld a, NUM_BOXES + 1 - ld [wBillsPC_LoadedBox], a - xor a - ldh [hBGMapMode], a - call ClearSprites - call CopyBoxmonSpecies - call BillsPC_BoxName - ld de, PCString_ChooseaPKMN - call BillsPC_PlaceString - ld a, $5 - ld [wBillsPC_NumMonsOnScreen], a - call BillsPC_RefreshTextboxes - call PCMonInfo - call BillsPC_PrintBoxCountAndCapacityInsideBox - xor a - ld [wCurPartySpecies], a - ld a, CGB_BILLS_PC - call BillsPC_ApplyPalettes - call ApplyTilemapInVBlank - call BillsPC_UpdateSelectionCursor - jp BillsPC_IncrementJumptableIndex - -.Joypad: - ld hl, hJoyPressed - ld a, [hl] - and B_BUTTON - jr nz, .b_button - ld a, [hl] - and A_BUTTON - jr nz, .a_button - call Withdraw_UpDown +.do_it + ; If dbox movement the same way as box->party. + ld a, d + cp b + jr nc, .dont_swap + push bc + ld b, d + ld c, e + pop de +.dont_swap + ; At this point, b<=d. So if d is party, we're swapping party members, and + ; if b is nonparty (i.e. >0), we're swapping between box slots. + ; Otherwise, we're swapping a party slot bc to box slot de. + ld a, d and a - ret z - call BillsPC_UpdateSelectionCursor - xor a - ldh [hBGMapMode], a - call BillsPC_RefreshTextboxes - call PCMonInfo - call BillsPC_PrintBoxCountAndCapacityInsideBox - ld a, $1 - ldh [hBGMapMode], a - call DelayFrame - jp DelayFrame - -.a_button - call BillsPC_GetSelectedPokemonSpecies + jr z, .party_swap + ld a, b and a - ret z - cp -1 - jr z, .b_button - ld a, $2 - ld [wJumptableIndex], a - ret + jr nz, .box_swap -.b_button - ld a, $4 - ld [wJumptableIndex], a - ret + ; We're swapping a party and box slot. First, verify that we're not losing + ; our last healthy mon and that the partymon isn't holding Mail. + push de + push bc + ld a, c -.PrepSubmenu: - xor a - ldh [hBGMapMode], a - call ClearSprites - call BillsPC_GetSelectedMonPal - ld de, PCString_WhatsUp - call BillsPC_PlaceString - ld a, $1 - ld [wMenuCursorY], a - jp BillsPC_IncrementJumptableIndex - -BillsPC_Withdraw: - ld hl, .MenuDataHeader - call CopyMenuHeader - ld a, [wMenuCursorY] - ld [wMenuCursorBuffer], a - call VerticalMenu - jp c, .cancel - ld a, [wMenuCursorY] - dec a - and 3 - call StackJumpTable - -.Jumptable - dw .withdraw ; Withdraw - dw .stats ; Stats - dw .release ; Release - dw .cancel ; Cancel - -.withdraw - call BillsPC_CheckMail_PreventBlackout - jp c, .cancel - call TryWithdrawPokemon - jr c, .FailedWithdraw - xor a - ld [wJumptableIndex], a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a - ret -.FailedWithdraw: - ld de, PCString_WhatsUp - jp BillsPC_PlaceString - -.stats - call LoadStandardMenuHeader - call BillsPC_StatsScreen - call ExitMenu - call PCMonInfo - jp BillsPC_GetSelectedMonPal - -.release - ld a, [wMenuCursorY] - push af - call BillsPC_IsMonAnEgg - jr c, .FailedRelease - ld de, PCString_ReleasePKMN - call BillsPC_PlaceString - call LoadStandardMenuHeader - lb bc, 14, 11 - call PlaceYesNoBox - ld a, [wMenuCursorY] + ; This is required for CheckCurPartyMonFainted dec a - call ExitMenu - and a - jr nz, .FailedRelease - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] ld [wCurPartyMon], a - ld a, PC_DEPOSIT - ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox - call ReleasePKMN_ByePKMN - xor a - ld [wJumptableIndex], a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a - pop af - ret -.FailedRelease: - ld de, PCString_WhatsUp - call BillsPC_PlaceString - pop af - ld [wMenuCursorY], a - ret - -.cancel - xor a - ld [wJumptableIndex], a - ret - -.MenuDataHeader: - db $40 ; flags - db 04, 09 ; start coords - db 13, 19 ; end coords - dw .MenuData - db 1 ; default option - -.MenuData: - db $80 ; flags - db 4 ; items - db "Withdraw@" - db "Stats@" - db "Release@" - db "Cancel@" - -_MovePKMNWithoutMail: - ld hl, wOptions1 - ld a, [hl] - push af - set NO_TEXT_SCROLL, [hl] - ld a, [wVramState] - push af - xor a - ld [wVramState], a - ldh a, [hInMenu] - push af - ld a, $1 - ldh [hInMenu], a - xor a - ldh [hMapAnims], a - call BillsPC_InitRAM - ld a, [wCurBox] - and $f - inc a - ld [wBillsPC_LoadedBox], a - call DelayFrame -.asm_e2781 - call JoyTextDelay - ld a, [wJumptableIndex] - bit 7, a - jr nz, .asm_e2793 - call .RunJumptable - call DelayFrame - jr .asm_e2781 - -.asm_e2793 - call ClearSprites - pop af - ldh [hInMenu], a - pop af - ld [wVramState], a - pop af - ld [wOptions1], a - ret -.RunJumptable: - call StandardStackJumpTable + ; Is the party slot occupied? Also writes the partymon to wTempMon. + call GetStorageBoxMon + jr z, .not_last_healthy -.Jumptable: - dw .Init - dw .Joypad - dw .PrepSubmenu - dw .MoveMonWOMailSubmenu - dw .PrepInsertCursor - dw .Joypad2 - dw BillsPC_EndJumptableLoop - -.Init: - xor a - ldh [hBGMapMode], a - call ClearSprites - call CopyBoxmonSpecies - ld de, PCString_ChooseaPKMN - call BillsPC_PlaceString + ; Check if the partymon is holding Mail. We can't store Mail in a Box. + ld a, [wTempMonItem] + call ItemIsMail_a ld a, 5 - ld [wBillsPC_NumMonsOnScreen], a - call BillsPC_RefreshTextboxes - call BillsPC_MoveMonWOMail_BoxNameAndArrows - call PCMonInfo - call BillsPC_PrintBoxCountAndCapacityInsideBox - xor a - ld [wCurPartySpecies], a - ld a, CGB_BILLS_PC - call BillsPC_ApplyPalettes - call ApplyTilemapInVBlank - call BillsPC_UpdateSelectionCursor - jp BillsPC_IncrementJumptableIndex - -.Joypad: - ld hl, hJoyPressed - ld a, [hl] - and B_BUTTON - jr nz, .b_button - ld a, [hl] - and A_BUTTON - jr nz, .a_button - call MovePkmnWithoutMail_DPad - jr c, .d_pad - and a - ret z - call BillsPC_UpdateSelectionCursor - xor a - ldh [hBGMapMode], a - call BillsPC_RefreshTextboxes - call PCMonInfo - call BillsPC_PrintBoxCountAndCapacityInsideBox - ld a, $1 - ldh [hBGMapMode], a - call DelayFrame - jp DelayFrame - -.d_pad - xor a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a - ld [wJumptableIndex], a - ret - -.a_button - call BillsPC_GetSelectedPokemonSpecies - and a - ret z - cp -1 - jr z, .b_button - ld a, $2 - ld [wJumptableIndex], a - ret + jr c, .pop_bcde_and_return -.b_button - ld a, $6 - ld [wJumptableIndex], a - ret + ; Otherwise, check if it is our last healthy mon. + call CheckCurPartyMonFainted + jr nc, .not_last_healthy -.PrepSubmenu: - xor a - ldh [hBGMapMode], a - call ClearSprites - call BillsPC_GetSelectedMonPal - ld de, PCString_WhatsUp - call BillsPC_PlaceString - ld a, $1 - ld [wMenuCursorY], a - jp BillsPC_IncrementJumptableIndex - -.MoveMonWOMailSubmenu: - ld hl, .MenuDataHeader - call CopyMenuHeader - ld a, [wMenuCursorY] - ld [wMenuCursorBuffer], a - call VerticalMenu - jp c, .Cancel - ld a, [wMenuCursorY] - dec a - and 3 - call StackJumpTable - -.Jumptable2: - dw .Move - dw .Stats - dw .Cancel - -.Move: - call BillsPC_CheckMail_PreventBlackout - jp c, .Cancel - ld a, [wBillsPC_ScrollPosition] - ld [wBillsPC_BackupScrollPosition], a - ld a, [wBillsPC_CursorPosition] - ld [wBillsPC_BackupCursorPosition], a - ld a, [wBillsPC_LoadedBox] - ld [wBillsPC_BackupLoadedBox], a - ld a, $4 - ld [wJumptableIndex], a + ; Doing this would lose us our last healthy mon, so abort. + ld a, 4 +.pop_bcde_and_return + pop bc + pop de ret -.Stats: - call LoadStandardMenuHeader - call BillsPC_StatsScreen - call ExitMenu - call PCMonInfo - jp BillsPC_GetSelectedMonPal - -.Cancel: - xor a - ld [wJumptableIndex], a - ret +.not_last_healthy + pop bc -.MenuDataHeader: - db $40 ; flags - db 04, 09 ; start coords - db 13, 19 ; end coords - dw .MenuData2 - db 1 ; default option - -.MenuData2: - db $80 ; flags - db 3 ; items - db "Move@" - db "Stats@" - db "Cancel@" - -.PrepInsertCursor: - xor a - ldh [hBGMapMode], a - call CopyBoxmonSpecies - ld de, PCString_MoveToWhere - call BillsPC_PlaceString - ld a, $5 - ld [wBillsPC_NumMonsOnScreen], a - call BillsPC_RefreshTextboxes - call BillsPC_MoveMonWOMail_BoxNameAndArrows - call ClearSprites - call BillsPC_UpdateInsertCursor - call ApplyTilemapInVBlank - jp BillsPC_IncrementJumptableIndex - -.Joypad2: - ld hl, hJoyPressed - ld a, [hl] - and B_BUTTON - jr nz, .b_button_2 - ld a, [hl] - and A_BUTTON - jr nz, .a_button_2 - call MovePkmnWithoutMail_DPad_2 - jr c, .dpad_2 + ; Try to allocate a new pokedb pointer, unless the party slot was empty. + ld de, 0 ; in case we're blanking the box slot + ld a, [wTempMonSlot] and a - ret z - call BillsPC_UpdateInsertCursor - xor a - ldh [hBGMapMode], a - call BillsPC_RefreshTextboxes - ld a, $1 - ldh [hBGMapMode], a - call DelayFrame - jp DelayFrame - -.dpad_2 - xor a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a - ld a, $4 - ld [wJumptableIndex], a - ret + call nz, NewStoragePointer + jr nc, .found_new_pokedb -.a_button_2 - call BillsPC_CheckSpaceInDestination - jr c, .no_space - call MovePKMNWitoutMail_InsertMon - xor a - ld [wJumptableIndex], a + ; The pokedb is full, we need to save first. + pop de + ld a, 1 ret -.no_space - ld hl, wJumptableIndex - dec [hl] - ret +.found_new_pokedb + call AddStorageMon -.b_button_2 - ld a, [wBillsPC_BackupScrollPosition] - ld [wBillsPC_ScrollPosition], a - ld a, [wBillsPC_BackupCursorPosition] - ld [wBillsPC_CursorPosition], a - ld a, [wBillsPC_BackupLoadedBox] - ld [wBillsPC_LoadedBox], a - xor a - ld [wJumptableIndex], a - ret + ; Get the current pokedb pointer in the box slot for writing to party. + pop hl ; Box slot + push bc ; Party slot + ld b, h + ld c, l + push de ; New pokedb pointer + call GetStorageBoxPointer + ld h, d + ld l, e + pop de + push hl ; Previous pokedb pointer + call SetStorageBoxPointer -BillsPC_InitRAM: - call ClearBGPalettes - call ClearSprites - call ClearTileMap - call BillsPC_InitGFX - ld hl, wBillsPCData - ld bc, wBillsPCDataEnd - wBillsPCData - xor a - rst ByteFill + ; Now, write previous pointer to party, then we're done. + pop de + pop bc + call SetStorageBoxPointer xor a - ld [wJumptableIndex], a - ld [wBillsPC_CursorPosition], a - ld [wBillsPC_ScrollPosition], a - ret - -BillsPC_IncrementJumptableIndex: - ld hl, wJumptableIndex - inc [hl] ret -BillsPC_EndJumptableLoop: - ld hl, wJumptableIndex - set 7, [hl] - ret - -_StatsScreenDPad: - ld a, [wBillsPC_NumMonsOnScreen] - ld d, a - ld a, [wBillsPC_NumMonsInBox] - and a - jr z, .empty - dec a - cp $1 - jr z, .empty - ld e, a - ld a, [hl] - and D_UP - jr nz, BillsPC_PressUp - ld a, [hl] - and D_DOWN - jr nz, BillsPC_PressDown -.empty +.party_swap + ; Check if we're placing a mon in a blank party slot. This means we're + ; shifting every other party member upwards, placing the held mon last. + ld a, [wPartyCount] + cp c + jr c, .shift + call SwapPartyMons xor a ret -Withdraw_UpDown: - ld hl, hJoyLast - ld a, [wBillsPC_NumMonsOnScreen] - ld d, a - ld a, [wBillsPC_NumMonsInBox] - ld e, a - and a - jr z, .empty - ld a, [hl] - and D_UP - jr nz, BillsPC_PressUp - ld a, [hl] - and D_DOWN - jr nz, BillsPC_PressDown -.empty +.shift + ; Shift the held mon until it's last in the party. + ld c, e + call ShiftPartySlotToEnd xor a ret -MovePkmnWithoutMail_DPad: - ld hl, hJoyLast - ld a, [wBillsPC_NumMonsOnScreen] - ld d, a - ld a, [wBillsPC_NumMonsInBox] - ld e, a - and a - jr z, .check_left_right - ld a, [hl] - and D_UP - jr nz, BillsPC_PressUp - ld a, [hl] - and D_DOWN - jr nz, BillsPC_PressDown - -.check_left_right - ld a, [hl] - and D_LEFT - jr nz, BillsPC_PressLeft - ld a, [hl] - and D_RIGHT - jr nz, BillsPC_PressRight - xor a - ret +.box_swap + ; Swaps 2 box pointers between box slot A in bc and slot B in de -MovePkmnWithoutMail_DPad_2: - ld hl, hJoyLast - ld a, [wBillsPC_NumMonsOnScreen] - ld d, a - ld a, [wBillsPC_NumMonsInBox] - ld e, a - and a - jr z, .check_left_right + push de ; Slot B + call GetStorageBoxPointer ; de = A's pointer + pop hl ; hl = Slot B - ld a, [hl] - and D_UP - jr nz, BillsPC_PressUp - ld a, [hl] - and D_DOWN - jr nz, BillsPC_PressDown + push bc ; Slot A + ld b, h + ld c, l ; bc = Slot B + push de ; A's pointer + call GetStorageBoxPointer ; de = B's pointer + ld h, d + ld l, e ; hl = B's pointer + pop de ; de = A's pointer + push hl + call SetStorageBoxPointer ; Set Slot B (bc) to have A's pointer (de) + pop de ; de = B's pointer + pop bc ; bc = Slot A + call SetStorageBoxPointer ; Set Slot A (bc) to have B's pointer (de) -.check_left_right - ld a, [hl] - and D_LEFT - jr nz, BillsPC_PressLeft - ld a, [hl] - and D_RIGHT - jr nz, BillsPC_PressRight + ; We're done xor a ret -BillsPC_PressUp: - ld hl, wBillsPC_CursorPosition - ld a, [hl] - and a - jr z, .top - dec [hl] - jr BillsPC_UpDownDidSomething +SwapPartyMonMail: + push hl + push de + push bc + dec c + dec e + ld d, c + jr DoMailSwap + +SwapPartyMons: +; Swap 1-indexed partymon c and e. Preserves bc, de, hl. +; TODO: this is more efficient than SwitchPartyMons, maybe make it use this. + push hl + push de + push bc + dec c + dec e + ld d, c -.top - ld hl, wBillsPC_ScrollPosition - ld a, [hl] - and a - jr z, BillsPC_JoypadDidNothing - dec [hl] - jr BillsPC_UpDownDidSomething + ; Swap species in the species array + ld hl, wPartySpecies + ld bc, 1 + call DoPartySwap -BillsPC_PressDown: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - inc a - cp e - jr nc, BillsPC_JoypadDidNothing + ; Swap partymon struct + ld hl, wPartyMon1 + ld c, PARTYMON_STRUCT_LENGTH + call DoPartySwap - ld hl, wBillsPC_CursorPosition - ld a, [hl] - inc a - cp d - jr nc, .not_bottom - inc [hl] - jr BillsPC_UpDownDidSomething + ; Swap nickname + ld hl, wPartyMonNicknames + ld c, MON_NAME_LENGTH + call DoPartySwap -.not_bottom - ld hl, wBillsPC_ScrollPosition - inc [hl] - jr BillsPC_UpDownDidSomething + ; Swap OT name + ld hl, wPartyMonOTs + ld c, NAME_LENGTH + call DoPartySwap -BillsPC_PressLeft: - ld hl, wBillsPC_LoadedBox - ld a, [hl] - and a - jr z, .wrap_around - dec [hl] - jr BillsPC_LeftRightDidSomething + ; fallthrough +DoMailSwap: + ; Swap Mail + ld a, BANK(sPartyMon1Mail) + call GetSRAMBank + ld hl, sPartyMon1Mail + ld bc, MAIL_STRUCT_LENGTH + call DoPartySwap + call CloseSRAM + jp PopBCDEHL +DoPartySwap: +; Swaps bc bytes between hl+d*bc and hl+e*bc + ; Get pointers to swap + push de + push hl + ld a, d + rst AddNTimes + ld a, e + ld d, h + ld e, l + pop hl + rst AddNTimes + ; Now hl and de points to which bytes to swap -.wrap_around - ld [hl], NUM_BOXES - jr BillsPC_LeftRightDidSomething + push de + ld de, wSwitchMonBuffer + push bc + push hl + rst CopyBytes + pop de + pop bc + pop hl + push hl + push bc + rst CopyBytes + pop bc + pop de + ld hl, wSwitchMonBuffer + rst CopyBytes + pop de + ret -BillsPC_PressRight: - ld hl, wBillsPC_LoadedBox - ld a, [hl] +NewStorageBoxPointer: +; Sets bcde to an unused box storage location. Preserves wTempMon. Returns: +; nc|z: Active box has free space +; nc|nz: Active box full, space found elsewhere +; c|z: Storage System filled completely. +; c|nz: Storage System has space, but the database is full. Save to free space. + ; Figure out if we have space in the storage system. Check active box first, + ; then other boxes in sequence until we loop back to the active box. We loop + ; upwards, despite downwards generally being more efficient for UI benefit, + ; since we want to place mons starting at the beginning of a box, rather + ; than the end). + ld a, [wCurBox] + inc a + ld b, a + ld d, NUM_BOXES +.outer_loop + ld c, 1 +.inner_loop + push de + call GetStorageBoxPointer + ld a, e + pop de + and a + jr z, .found_free_space + ld a, c + inc c + cp MONS_PER_BOX + jr nz, .inner_loop + ld a, b + inc b cp NUM_BOXES - jr z, .wrap_around - inc [hl] - jr BillsPC_LeftRightDidSomething - -.wrap_around - ld [hl], 0 - jr BillsPC_LeftRightDidSomething + jr nz, .dont_wrap_box + ld b, 1 +.dont_wrap_box + dec d + jr nz, .outer_loop -BillsPC_JoypadDidNothing: - xor a + ; Storage system completely filled. + scf ret -BillsPC_UpDownDidSomething: - ld a, TRUE - and a - ret +.found_free_space + ; Check if there's a free database entry. + call NewStoragePointer + jr nc, .storage_ok -BillsPC_LeftRightDidSomething: + ; If we still have no space left, we need to save. + or 1 scf ret -BillsPC_PlaceString: - push de - hlcoord 0, 15 - lb bc, 1, 18 - call Textbox - pop de - hlcoord 1, 16 - rst PlaceString - ret - -BillsPC_MoveMonWOMail_BoxNameAndArrows: - call BillsPC_BoxName - hlcoord 8, 1 - ld [hl], $5f - hlcoord 19, 1 - ld [hl], $5e - ret - -BillsPC_BoxName: - hlcoord 8, 0 - lb bc, 1, 10 - call Textbox - - ld a, [wBillsPC_LoadedBox] - and a - jr z, .party - - cp NUM_BOXES + 1 - jr nz, .gotbox - +.storage_ok + ; Returns z if the new storage was found in our active box, nz otherwise. + ; Always return nc. ld a, [wCurBox] inc a -.gotbox - dec a - ld hl, wBoxNames - ld bc, BOX_NAME_LENGTH - rst AddNTimes - ld e, l - ld d, h - jr .print - -.party - ld de, .PartyPKMN -.print - hlcoord 10, 1 - rst PlaceString + cp b + ret z + or 1 ret -.PartyPKMN: - db "Party @" - -PCMonInfo: -; Display a monster's pic and -; attributes when highlighting -; it in a PC menu. - -; Includes the neat cascading -; effect when showing the pic. - -; Example: Species, level, gender, -; whether it's holding an item. - - hlcoord 0, 0 - lb bc, 15, 8 - call ClearBox - - hlcoord 8, 14 - lb bc, 1, 3 - call ClearBox - - call BillsPC_GetSelectedPokemonSpecies - inc a - jr z, .no_pkmn - dec a - jr nz, .pkmn - -.no_pkmn - ld de, PCString_ChooseaPKMN - jp BillsPC_PlaceString - -.pkmn - ld [wd265], a - hlcoord 1, 4 - farcall PlaceFrontpicAtHL +NewStoragePointer: +; Sets de to an unused pokedb entry. Returns c if none was found. +; Preserves wTempMon. + ; Try twice, flushing the database if the first one failed. + call .GetStorage + ret nc + call FlushStorageSystem + ; fallthrough +.GetStorage: + ld d, 1 +.outer_loop + ld e, 1 +.inner_loop + call IsStorageUsed + jr z, .found_free_space + inc e + ld a, e + cp MONDB_ENTRIES + 1 + jr nz, .inner_loop + inc d + ld a, d + cp 3 + ccf + jr nc, .outer_loop + ret +.found_free_space + xor a + ret - call BillsPC_LoadMonStats - ld a, [wTempMonIsEgg] - bit MON_IS_EGG_F, a - ld a, [wd265] - jr z, .not_egg - ld a, EGG +FlushStorageSystem: +; Frees up orphaned pokedb entries and reallocates used entries. Beware of soft- +; resets and make sure this process completes before loading up a game. + ld a, BANK(wPokeDB1UsedEntries) + call StackCallInWRAMBankA +.Function: + push hl + push de + push bc -.not_egg - ld [wCurPartySpecies], a - ld [wCurSpecies], a - ld hl, wTempMonForm - predef GetVariant - call GetBaseData - ld de, vTiles2 tile $00 - predef GetFrontpic + ; Clear used pokedb entries. xor a - ld [wBillsPC_MonHasMail], a - ld a, [wCurPartySpecies] - ld [wd265], a - ld a, [wTempMonIsEgg] - bit MON_IS_EGG_F, a - ret nz - - call GetBasePokemonName - hlcoord 1, 14 - rst PlaceString - - hlcoord 1, 12 - call PrintLevel - - ld a, $3 - ld [wMonType], a - farcall GetGender - jr c, .skip_gender - ld a, "♂" - jr nz, .printgender - ld a, "♀" -.printgender - hlcoord 5, 12 - ld [hl], a -.skip_gender + ld hl, wPokeDB1UsedEntries + ld bc, wPokeDB2UsedEntriesEnd - wPokeDB1UsedEntries + rst ByteFill - ld a, [wTempMonItem] + ; Now, set flags as per box usage. + ld b, 1 +.outer_loop + ld c, 1 +.inner_loop + call GetStorageBoxPointer + call _AllocateStorageFlag + ld a, c + inc c + cp MONS_PER_BOX + jr nz, .inner_loop + ld a, b + inc b + cp NUM_BOXES * 2 ; current + backup + jr nz, .outer_loop + jp PopBCDEHL + +GetStorageBoxPointer: +; Returns the pokedb bank+entry in de for box b, slot c. + ; Ensure that we're dealing with an actual box and not a partymon. + ld a, b and a - jr nz, .has_item - ld de, PCString_NoHeldItem - jp BillsPC_PlaceString - -.has_item - ld d, a - push de - ld [wNamedObjectIndexBuffer], a - call GetItemName - ld de, wStringBuffer1 - call BillsPC_PlaceString - pop de - call ItemIsMail - jr c, .mail - ld a, $5d ; item icon - jr .printitem -.mail - ld a, $1 - ld [wBillsPC_MonHasMail], a - ld a, $5c ; mail icon -.printitem - hlcoord 7, 12 - ld [hl], a - ret + ld a, ERR_NEWBOX + jp z, Crash -BillsPC_LoadMonStats: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld e, a - ld d, $0 - ld hl, wBillsPCPokemonList + 1 - add hl, de - add hl, de - add hl, de - ld a, [hl] - and a - jr z, .party - cp NUM_BOXES + 1 - jp z, .sBox - ld b, a - call GetBoxPointer - ld a, b + ld a, BANK(sNewBox1) call GetSRAMBank - ; level + push hl - ld bc, sBoxMon1Level - sBox - add hl, bc - ld bc, BOXMON_STRUCT_LENGTH - ld a, e + push bc + ld a, b + ld hl, sNewBox1Entries + ld bc, sNewBox2 - sNewBox1 + dec a rst AddNTimes - ld a, [hl] - ld [wTempMonLevel], a - pop hl - ; item + pop bc + push bc + dec c + ld b, 0 push hl - ld bc, sBoxMon1Item - sBox add hl, bc - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld a, [hl] - ld [wTempMonItem], a + ld e, [hl] pop hl - ; DVs and personality (DVs for color variation) - push hl - ld bc, sBoxMon1DVs - sBox + ld c, sNewBox1Banks - sNewBox1Entries add hl, bc - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld bc, wTempMonDVs -rept 4 - ld a, [hli] - ld [bc], a - inc bc -endr - ld a, [hl] - ld [bc], a + pop bc + push bc + dec c + ld d, 1 ; will cause a useless bankswitch in flag checking, but that's OK + ld b, CHECK_FLAG + predef FlagPredef + pop bc + jr z, .got_bank + inc d +.got_bank pop hl - ; moves (for Pikachu forms) - ld bc, sBoxMon1Moves - sBox - add hl, bc - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld bc, wTempMonMoves -rept NUM_MOVES - 1 - ld a, [hli] - ld [bc], a - inc bc -endr - ld a, [hl] - ld [bc], a - jp CloseSRAM - -.party - ; level - ld hl, wPartyMon1Level - ld bc, PARTYMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld a, [hl] - ld [wTempMonLevel], a - ; item - ld hl, wPartyMon1Item - ld bc, PARTYMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld a, [hl] - ld [wTempMonItem], a - ; DVs and personality (DVs for color variation) - ld hl, wPartyMon1DVs - ld bc, PARTYMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld bc, wTempMonDVs -rept 4 - ld a, [hli] - ld [bc], a - inc bc -endr - ld a, [hl] - ld [bc], a - ; moves (for Pikachu forms) - ld hl, wPartyMon1Item - ld bc, PARTYMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld bc, wTempMonMoves -rept NUM_MOVES - 1 - ld a, [hli] - ld [bc], a - inc bc -endr - ld a, [hl] - ld [bc], a - ret - -.sBox - ld a, BANK(sBox) - call GetSRAMBank - ; level - ld hl, sBoxMon1Level - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld a, [hl] - ld [wTempMonLevel], a - ; item - ld hl, sBoxMon1Item - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld a, [hl] - ld [wTempMonItem], a - ; DVs and personality (DVs for color variation) - ld hl, sBoxMon1DVs - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld bc, wTempMonDVs -rept 4 - ld a, [hli] - ld [bc], a - inc bc -endr - ld a, [hl] - ld [bc], a - ; moves (for Pikachu forms) - ld hl, sBoxMon1Moves - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes - ld bc, wTempMonMoves -rept NUM_MOVES - 1 - ld a, [hli] - ld [bc], a - inc bc -endr - ld a, [hl] - ld [bc], a jp CloseSRAM -BillsPC_RefreshTextboxes: - hlcoord 8, 2 - lb bc, 10, 10 - call Textbox - - hlcoord 8, 2 - ld [hl], "└" - hlcoord 19, 2 - ld [hl], "┘" - - ld a, [wBillsPC_ScrollPosition] - ld e, a - ld d, 0 - ld hl, wBillsPCPokemonList - add hl, de - add hl, de - add hl, de - ld e, l - ld d, h - hlcoord 9, 4 - ld a, [wBillsPC_NumMonsOnScreen] -.loop - push af +UpdateStorageBoxMonFromTemp: +; Updates storage pointed to by wTempMonBox+wTempMonSlot with content in +; wTempMon. If this is part of a Box, this allocates a new entry. +; Returns z if successful. + ; Just run a simple copy if we're updating the party. + ld a, [wTempMonSlot] + ld c, a + ld a, [wTempMonBox] + ld b, a + and a + jp z, CopyBetweenPartyAndTemp + + ; Otherwise, we need to allocate a new box entry. + ; Erase the current entry before trying to find a new one. + ; This code exists to gurantee that should the storage commit work once, + ; it will always continue to work for the same tempmon session without an + ; enforced save inbetween. Without it, the code could write a new 314th + ; entry the first write, then fail to reuse the same entry later. + call GetStorageBoxPointer push de - push hl - call .PlaceNickname - pop hl - ld de, 2 * SCREEN_WIDTH - add hl, de + ld e, 0 + call SetStorageBoxPointer + push bc + call NewStoragePointer + pop bc + jr nc, .found_entry pop de - inc de - inc de - inc de - pop af - dec a - jr nz, .loop + call SetStorageBoxPointer + or 1 ret -.CancelString: - db "Cancel@" - -.PlaceNickname: - ld a, [de] - and a - ret z - cp -1 - jr nz, .get_nickname - ld de, .CancelString - rst PlaceString +.found_entry + call AddStorageMon + call SetStorageBoxPointer + pop de + xor a ret -.get_nickname - inc de - ld a, [de] - ld b, a - inc de - ld a, [de] - ld e, a +RemoveStorageBoxMon: +; Erases box b slot c. Done by simply just setting it to a null entry. + ld e, 0 + ; fallthrough +SetStorageBoxPointer: +; Sets box b slot c to have storage pointer de. If bc is a party slot, will +; fill it with the pokedb entry in de, or empty the slot (potentially shifting +; later party members upwards) if de is a null slot. + push hl + push de + push bc + + ; Are we dealing with a party slot? ld a, b and a jr z, .party - cp NUM_BOXES + 1 - jr z, .sBox - push hl - call GetBoxPointer - ld a, b + + ; We're dealing with a box, so set box pointer appropriately. + ld a, BANK(sNewBox1) call GetSRAMBank - push hl - ld bc, sBoxMons - sBox - add hl, bc - ld bc, BOXMON_STRUCT_LENGTH - ld a, e + + ; Get the correct box. + ld a, b + ld hl, sNewBox1Entries + ld bc, sNewBox2 - sNewBox1 + dec a rst AddNTimes - ld a, [hl] - pop hl - and a - jr z, .boxfail - ld bc, sBoxMonNicknames - sBox + pop bc + push bc + + ; Get the corret slot and write the db entry to it. + dec c + ld b, 0 + push hl add hl, bc - ld bc, MON_NAME_LENGTH - ld a, e - rst AddNTimes - ld de, wStringBuffer1 - ld bc, MON_NAME_LENGTH - rst CopyBytes - call CloseSRAM + ld [hl], e pop hl - ld de, wStringBuffer1 - rst PlaceString - ret -.boxfail - call CloseSRAM - pop hl - jr .placeholder_string + ; Write the db bank. + ld a, c + ld c, sNewBox1Banks - sNewBox1Entries + add hl, bc + ld c, a + ld b, RESET_FLAG + dec d + jr z, .got_flag_action + ld b, SET_FLAG +.got_flag_action + predef FlagPredef + jr .done .party - push hl - ld hl, wPartySpecies - ld d, $0 - add hl, de - ld a, [hl] - and a - jr z, .partyfail - ld hl, wPartyMonNicknames - ld bc, MON_NAME_LENGTH - ld a, e - rst AddNTimes - ld de, wStringBuffer1 - ld bc, MON_NAME_LENGTH - rst CopyBytes - pop hl - ld de, wStringBuffer1 - rst PlaceString - ret - -.partyfail - pop hl - jr .placeholder_string + ; Get the mon from the pokedb for party writing. + call GetStorageMon + jr nz, .not_empty -.sBox - push hl - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBoxSpecies - ld d, $0 - add hl, de - ld a, [hl] - and a - jr z, .sBoxFail - ld hl, sBoxMonNicknames - ld bc, MON_NAME_LENGTH - ld a, e - rst AddNTimes - ld de, wStringBuffer1 - ld bc, MON_NAME_LENGTH - rst CopyBytes - call CloseSRAM - pop hl - ld de, wStringBuffer1 - rst PlaceString - ret + ; First, shift this partymon to the end, effectively shifting everything + ; past it upwards. + call ShiftPartySlotToEnd -.sBoxFail - call CloseSRAM - pop hl -.placeholder_string - ld de, .Placeholder - rst PlaceString - ret - -.Placeholder: - db "-----@" + ; Then delete the partymon. + ld hl, wPartyCount + dec [hl] + ld c, [hl] + ld b, 0 + ld hl, wPartySpecies + add hl, bc + ld [hl], -1 + jr .done -copy_box_data: MACRO -.loop\@ - ld a, [hl] - cp -1 - jr z, .done\@ - and a - jr z, .done\@ - ld [de], a - inc de - ld a, [wBillsPC_LoadedBox] - ld [de], a - inc de - ld a, [wBillsPCTempListIndex] - ld [de], a - inc a - ld [wBillsPCTempListIndex], a - inc de - inc hl - ld a, [wBillsPCTempBoxCount] +.not_empty + ; If this slot was previously empty, we'll append it to the party end. + ld a, [wPartyCount] + cp c + jr nc, .partyslot_not_empty inc a - ld [wBillsPCTempBoxCount], a - jr .loop\@ + ld c, a + ld [wPartyCount], a -.done\@ -IF \1 +.partyslot_not_empty + ; b is 0 from earlier, from referencing the party. + call CopyBetweenPartyAndTemp +.done call CloseSRAM -ENDC - ld a, -1 - ld [de], a - ld a, [wBillsPCTempBoxCount] - inc a - ld [wBillsPC_NumMonsInBox], a -ENDM + jp PopBCDEHL -CopyBoxmonSpecies: - xor a - ld hl, wBillsPCPokemonList - ld bc, 3 * 30 - rst ByteFill - ld de, wBillsPCPokemonList - xor a - ld [wBillsPCTempListIndex], a - ld [wBillsPCTempBoxCount], a - ld a, [wBillsPC_LoadedBox] - and a - jr z, .party - cp NUM_BOXES + 1 - jr z, .sBox - ld b, a - call GetBoxPointer - ld a, b - call GetSRAMBank - inc hl - copy_box_data 1 - ret - -.party +ShiftPartySlotToEnd: +; Shift party slot c until the end. + ld a, [wPartyCount] + cp c + ret z + ld e, c + inc c + call SwapPartyMons + jr ShiftPartySlotToEnd + +CopyBetweenPartyAndTemp: +; Copies between partymon c (1-indexed) and temp. Doesn't preserve registers. +; Note that this will not update the party count if adding a new mon. +; If bit 7 of b is set, copies between wOTPartyMons instead of wPartyMons. +; If bit 0 of b is set, copies from party to temp, otherwise the reverse. + dec c ld hl, wPartySpecies - copy_box_data 0 - ret + ld de, wTempMonSpecies + ld a, 1 + call .Copy -.sBox - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBoxSpecies - copy_box_data 1 - ret + ld hl, wPartyMon1 + ld de, wTempMon + ld a, PARTYMON_STRUCT_LENGTH + call .Copy -BillsPC_GetSelectedMonPal: -; Sets Pokémon palette, even for eggs, and sets target mon (not EGG) to -; wCurPartySpecies. - call BillsPC_GetSelectedPokemonSpecies - push af + ld hl, wPartyMonNicknames + ld de, wTempMonNickname + ld a, MON_NAME_LENGTH + call .Copy - ; Get target species or EGG - ld a, [wTempMonIsEgg] - bit MON_IS_EGG_F, a - ld a, EGG - jr nz, .got_species - pop af + ld hl, wPartyMonOTs + ld de, wTempMonOT + ld a, NAME_LENGTH + call .Copy + +.Copy: +; Copies c bytes from hl+c*a to de if b is 1, otherwise the reverse. + push bc + bit 7, b + jr z, .got_party + + ; Copies from OT party instead. + push bc + ld bc, wOTPartyMons - wPartyMons + add hl, bc + pop bc +.got_party + ld b, 0 push af -.got_species - ld [wCurPartySpecies], a - ld a, CGB_BILLS_PC - call BillsPC_ApplyPalettes + rst AddNTimes pop af - ld [wCurPartySpecies], a - ret - -BillsPC_GetSelectedPokemonSpecies: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld e, a - ld d, $0 - ld hl, wBillsPCPokemonList - add hl, de - add hl, de - add hl, de - ld a, [hl] + pop bc + push bc + ld c, a + bit 0, b + call z, SwapHLDE + ld b, 0 + rst CopyBytes + pop bc ret -BillsPC_UpdateSelectionCursor: - ld a, [wBillsPC_NumMonsInBox] +AddStorageMon: +; Adds wTempMon to storage pointed to with de. Does nothing if e is 0, meaning +; a null entry. Returns a fatal error (crash) if the entry is occupied. + ; Do nothing if we're pointing towards null storage. + ld a, e and a - jp z, ClearSprites - - ld hl, .OAM - ld de, wVirtualOAM -.loop - ld a, [hl] - cp -1 ret z - ld a, [wBillsPC_CursorPosition] - and $7 - swap a - add [hl] - inc hl - ld [de], a - inc de -rept 3 - ld a, [hli] - ld [de], a - inc de -endr - jr .loop -.OAM: - dsprite 4, 6, 10, 0, $00, $0 - dsprite 4, 6, 11, 0, $00, $0 - dsprite 4, 6, 12, 0, $00, $0 - dsprite 4, 6, 13, 0, $00, $0 - dsprite 4, 6, 14, 0, $00, $0 - dsprite 4, 6, 15, 0, $00, $0 - dsprite 4, 6, 16, 0, $00, $0 - dsprite 4, 6, 17, 0, $00, $0 - dsprite 4, 6, 18, 0, $00, $0 - dsprite 4, 6, 18, 7, $00, $0 - dsprite 7, 1, 10, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 11, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 12, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 13, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 14, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 15, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 16, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 17, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 18, 0, $00, $0 | Y_FLIP - dsprite 7, 1, 18, 7, $00, $0 | Y_FLIP - dsprite 5, 6, 9, 6, $01, $0 - dsprite 6, 1, 9, 6, $01, $0 | Y_FLIP - dsprite 5, 6, 19, 1, $01, $0 | X_FLIP - dsprite 6, 1, 19, 1, $01, $0 | X_FLIP | Y_FLIP - db -1 - -BillsPC_UpdateInsertCursor: - ld hl, .OAM - ld de, wVirtualOAM -.loop - ld a, [hl] - cp -1 - ret z - ld a, [wBillsPC_CursorPosition] - and $7 - swap a - add [hl] - inc hl - ld [de], a - inc de -rept 3 - ld a, [hli] - ld [de], a - inc de -endr - jr .loop + ; Allocate the entry. Return a fatal error if the entry was already set. + call AllocateStorageFlag + ld a, ERR_NEWBOX + jp nz, Crash + push hl + push de + push bc -.OAM: - dsprite 4, 7, 10, 0, $06, $0 - dsprite 5, 3, 11, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 12, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 13, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 14, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 15, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 16, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 17, 0, $00, $0 | Y_FLIP - dsprite 5, 3, 18, 0, $00, $0 | Y_FLIP - dsprite 4, 7, 19, 0, $07, $0 - db -1 - -BillsPC_CheckSpaceInDestination: -; If moving within a box, no need to be here. - ld hl, wBillsPC_LoadedBox - ld a, [wBillsPC_BackupLoadedBox] - cp [hl] - jr z, .same_box + ; Encode the tempmon for adding, but decode it afterwards to leave it in + ; the same state. + push de + call EncodeTempMon + pop de + call OpenStorageDB -; Exceeding box or party capacity is a big no-no. - ld a, [wBillsPC_LoadedBox] - and a - ld e, MONS_PER_BOX + 1 - jr nz, .compare - ld e, PARTY_LENGTH + 1 -.compare - ld a, [wBillsPC_NumMonsInBox] - cp e - jr nc, .no_room -.same_box - and a - ret + ld hl, sBoxMons1Mons + ld bc, SAVEMON_STRUCT_LENGTH + ld a, e + dec a + rst AddNTimes + ld d, h + ld e, l + ld hl, wEncodedTempMon + ld bc, SAVEMON_STRUCT_LENGTH + rst CopyBytes -.no_room - ld de, PCString_TheresNoRoom - call BillsPC_PlaceString - ld de, SFX_WRONG - call WaitPlaySFX - call WaitSFX - scf - ret + call DecodeTempMon + call CloseSRAM + jp PopBCDEHL -BillsPC_CheckMail_PreventBlackout: - ld a, [wBillsPC_LoadedBox] - and a - jr nz, .Okay - ld a, [wBillsPC_NumMonsInBox] - cp $3 - jr c, .ItsYourLastPokemon - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - farcall CheckCurPartyMonFainted - jr c, .AllOthersFainted - ld a, [wBillsPC_MonHasMail] - and a - jr nz, .HasMail -.Okay: - and a - ret +OpenStorageDB: +; Opens pokedb bank given by d (1 or 2). Leaves SRAM open, obviously. + ld a, d + dec a + ld a, BANK(sBoxMons1) + jr z, .got_bank + ld a, BANK(sBoxMons2) +.got_bank + jp GetSRAMBank + +EncodeTempMon: +; Encodes party_struct wTempMon in-place to savemon_struct wEncodedTempMon. +; Bytes identical to both structs do not need encoding. + + ; Convert 4 PP bytes to 1 PP Up byte. + ld hl, wTempMonPP + ld b, NUM_MOVES + ld d, h + ld e, l +.loop + ld a, [de] + srl [hl] + srl [hl] + and %11000000 + or [hl] + ld [hl], a + inc de + dec b + jr nz, .loop -.HasMail: - ld de, PCString_RemoveMail - jr .NotOkay - -.AllOthersFainted: - ld de, PCString_NoMoreUsablePKMN - jr .NotOkay - -.ItsYourLastPokemon: - ld de, PCString_ItsYourLastPKMN -.NotOkay: - call BillsPC_PlaceString - ld de, SFX_WRONG - call WaitPlaySFX - call WaitSFX - scf - ret + ; Shift everything after PP Ups backwards. + ld hl, wTempMonHappiness + ld de, wEncodedTempMonHappiness + ld bc, wEncodedTempMonExtra - wEncodedTempMonHappiness + rst CopyBytes -BillsPC_IsMonAnEgg: - call BillsPC_GetSelectedPokemonSpecies ; sets hl and e - inc hl - ld a, [hl] - and a - jr z, .party - cp NUM_BOXES + 1 - jr z, .sBox + ; Move Extra bytes. + ; de == wEncodedTempMonExtra + ld hl, wTempMonExtra + ld bc, 3 + rst CopyBytes - ld b, a - call GetBoxPointer - ld a, b - call GetSRAMBank - ld bc, sBoxMon1IsEgg - sBox - add hl, bc - jr .okay_sBox + ; Move name-related bytes. + ld hl, wTempMonNickname + ld de, wEncodedTempMonNickname + ld bc, MON_NAME_LENGTH - 1 + rst CopyBytes + ld hl, wTempMonOT + ld de, wEncodedTempMonOT + ld bc, PLAYER_NAME_LENGTH - 1 + rst CopyBytes -.party - ld hl, wPartyMon1IsEgg - ld bc, PARTYMON_STRUCT_LENGTH - ld a, e - rst AddNTimes + ; Convert nickname+OT characters into reversible 7bit. + ld hl, wEncodedTempMonNickname + ld b, wEncodedTempMonEnd - wEncodedTempMonNickname +.charmap_loop ld a, [hl] - ld e, a - jr .okay_party - -.sBox - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBoxMon1IsEgg + ; " " -> $7a + ld c, $7a | ~%01111111 + cp " " + jr z, .replace + ; "@" -> $7b + inc c + cp "@" + jr z, .replace + ; "" -> $7c + inc c + and a + jr nz, .removebit +.replace + ld a, c +.removebit + and %01111111 + ld [hli], a + dec b + jr nz, .charmap_loop + ; fallthrough +ChecksumTempMon: +; Calculate and write a checksum and to TempMon. Use a nonzero baseline to +; avoid a complete null content from having 0 as a checksum. +; Returns z if an existing checksum is identical to the written checksum. + ; boxmon struct + 3 extra bytes (normally placed after OT) + ld bc, wEncodedTempMon + ld hl, 127 + lb de, SAVEMON_NICKNAME, 0 + call .DoChecksum + + ; nickname + OT. This skips one CRC multiplier due to a double-increase. + ; Counterintuitive but harmless. + ld bc, wEncodedTempMonNickname + ld d, $80 | (SAVEMON_STRUCT_LENGTH - SAVEMON_NICKNAME) + call .DoChecksum + + ; Compare and write the result + ld d, h + ld e, l -.okay_sBox - ld bc, BOXMON_STRUCT_LENGTH - ld a, e - rst AddNTimes + ; Checksum is 16bit, further ones are padded with zeroes. + ; The padding being nonzero is also counted as invalid. + ld b, 0 ; used for checksum error detection + ld hl, wEncodedTempMonNickname + ld c, SAVEMON_STRUCT_LENGTH - SAVEMON_NICKNAME +.WriteChecksum: ld a, [hl] - ld e, a - call CloseSRAM - ld a, e -.okay_party - bit MON_IS_EGG_F, a - jr nz, .egg + and %01111111 + sla e + rl d + jr nc, .not_set + or %10000000 +.not_set + cp [hl] + ld [hli], a + jr z, .checksum_valid + inc b +.checksum_valid + dec c + jr nz, .WriteChecksum + ld a, b and a ret -.egg - ld de, PCString_NoReleasingEGGS - call BillsPC_PlaceString - ld de, SFX_WRONG - call WaitPlaySFX - call WaitSFX - scf - ret - -BillsPC_StatsScreen: - call LowVolume - call BillsPC_CopyMon - ld a, $3 - ld [wMonType], a - predef StatsScreenInit - call BillsPC_InitGFX - jp MaxVolume - -StatsScreenDPad: - ld hl, hJoyPressed - ld a, [hl] - and A_BUTTON | B_BUTTON | D_RIGHT | D_LEFT - ld [wMenuJoypad], a +.DoChecksum: + inc e + dec d + bit 6, d ret nz + ld a, [bc] + inc bc + bit 7, d + jr z, .not_7bit + and %01111111 +.not_7bit + push bc + ld b, 0 + ld c, a + ld a, e + rst AddNTimes + pop bc + jr .DoChecksum - ld a, [hl] - and D_DOWN | D_UP - ld [wMenuJoypad], a - ret z - - call _StatsScreenDPad - and a - jr z, .did_nothing - call BillsPC_GetSelectedPokemonSpecies - ld [wd265], a - call BillsPC_LoadMonStats - ld a, [wd265] - ld [wCurPartySpecies], a - ld [wCurSpecies], a - ld hl, wTempMonForm - predef GetVariant - call GetBaseData - jp BillsPC_CopyMon - -.did_nothing - xor a - ld [wMenuJoypad], a - ret +DecodeTempMon: +; Decodes TempMon. Returns nz. Sets carry in case of invalid checksum. + ; First, run a checksum check. Don't use the result until we've done + ; character replacements back to their original state + call ChecksumTempMon + push af -BillsPC_CopyMon: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - ld a, [wBillsPC_LoadedBox] - and a - jr z, .party - cp NUM_BOXES + 1 - jr nz, .box - ld a, BANK(sBox) - call GetSRAMBank - ld hl, sBoxSpecies - call CopySpeciesToTemp - ld hl, sBoxMonNicknames - call CopyNicknameToTemp - ld hl, sBoxMonOTs - call CopyOTNameToTemp - ld hl, sBoxMons - ld bc, BOXMON_STRUCT_LENGTH - ld a, [wCurPartyMon] - rst AddNTimes - ld de, wBufferMon - ld bc, PARTYMON_STRUCT_LENGTH + ; Move extra data back + ld hl, wEncodedTempMonExtra + ld de, wTempMonExtra + ld bc, 3 rst CopyBytes - call CloseSRAM - farjp CalcBufferMonStats -.party - ld hl, wPartySpecies - call CopySpeciesToTemp - ld hl, wPartyMonNicknames - call CopyNicknameToTemp - ld hl, wPartyMonOTs - call CopyOTNameToTemp - ld hl, wPartyMons - ld bc, PARTYMON_STRUCT_LENGTH - ld a, [wCurPartyMon] - rst AddNTimes - ld de, wBufferMon - ld bc, PARTYMON_STRUCT_LENGTH - rst CopyBytes - ret + ; Reverse the 7bit character encoding back to its original state. + ld hl, wEncodedTempMonNickname + ld b, wEncodedTempMonEnd - wEncodedTempMonNickname +.charmap_loop + ld a, [hl] + or $80 + sub $fa + ld c, " " + jr z, .replace + dec a + ld c, "@" + jr z, .replace + dec a + ld c, 0 + jr z, .replace -.box - ld b, a - call GetBoxPointer - ld a, b - call GetSRAMBank - push hl + ; Reverse the previous decrements + add $fc + ld c, a +.replace + ld [hl], c inc hl - call CopySpeciesToTemp - pop hl - push hl - ld bc, sBoxMonNicknames - sBox - add hl, bc - call CopyNicknameToTemp - pop hl - push hl - ld bc, sBoxMonOTs - sBox - add hl, bc - call CopyOTNameToTemp - pop hl - ld bc, sBoxMons - sBox - add hl, bc - ld bc, BOXMON_STRUCT_LENGTH - call CopyMonToTemp - call CloseSRAM - farjp CalcBufferMonStats - -DepositPokemon: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - ld hl, wPartyMonNicknames - ld a, [wCurPartyMon] - call GetNickname - ld a, PC_DEPOSIT - ld [wPokemonWithdrawDepositParameter], a - predef SentGetPkmnIntoFromBox - jr c, .asm_boxisfull - xor a - ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox - ld de, PCString_Stored - call BillsPC_PlaceString - ld l, c - ld h, b - ld de, wStringBuffer1 - rst PlaceString - ld a, "!" - ld [bc], a - ld a, [wTempMonIsEgg] - bit MON_IS_EGG_F, a - ld a, EGG - jr nz, .got_species - ld a, [wCurPartySpecies] -.got_species - call PlayCry - hlcoord 0, 0 - lb bc, 15, 8 - call ClearBox - hlcoord 8, 14 - lb bc, 1, 3 - call ClearBox - hlcoord 0, 15 - lb bc, 1, 18 - call Textbox - call ApplyTilemapInVBlank - and a - ret - -.asm_boxisfull - ld de, PCString_BoxFull - call BillsPC_PlaceString - ld de, SFX_WRONG - call WaitPlaySFX - call WaitSFX - scf - ret - -TryWithdrawPokemon: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - ld a, BANK(sBoxMonNicknames) - call GetSRAMBank - ld a, [wCurPartyMon] - ld hl, sBoxMonNicknames - call GetNickname - call CloseSRAM - xor a - ld [wPokemonWithdrawDepositParameter], a - predef SentGetPkmnIntoFromBox - jr c, .PartyFull - ld a, PC_DEPOSIT - ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox - ld de, PCString_Got - call BillsPC_PlaceString - ld l, c - ld h, b - ld de, wStringBuffer1 - rst PlaceString - ld a, "!" - ld [bc], a - ld a, [wTempMonIsEgg] - bit MON_IS_EGG_F, a - ld a, EGG - jr nz, .got_species - ld a, [wCurPartySpecies] -.got_species - call PlayCry - hlcoord 0, 0 - lb bc, 15, 8 - call ClearBox - hlcoord 8, 14 - lb bc, 1, 3 - call ClearBox - hlcoord 0, 15 - lb bc, 1, 18 - call Textbox - call ApplyTilemapInVBlank - and a - ret + dec b + jr nz, .charmap_loop -.PartyFull: - ld de, PCString_PartyFull - call BillsPC_PlaceString - ld de, SFX_WRONG - call WaitPlaySFX - call WaitSFX - scf - ret + ; Copy nickname and OT back to its original place. We need to do this backwards + ; due to overlap between wEncodedTempMon(Nickname|OT) and wTempMon(Nickname|OT). + ld hl, wEncodedTempMonOT + PLAYER_NAME_LENGTH - 2 + ld de, wTempMonOT + PLAYER_NAME_LENGTH - 1 + lb bc, 2, PLAYER_NAME_LENGTH - 1 -ReleasePKMN_ByePKMN: - ld a, [wCurPartySpecies] - ld [wd265], a - call GetPokemonName - - hlcoord 0, 0 - lb bc, 15, 8 - call ClearBox - hlcoord 8, 14 - lb bc, 1, 3 - call ClearBox - ld de, PCString_Bye - call BillsPC_PlaceString - ld l, c - ld h, b - inc hl - ld de, wStringBuffer1 - rst PlaceString - ld l, c - ld h, b - ld [hl], "!" +.outer_loop + ld a, "@" + ld [de], a + dec de +.inner_loop + ld a, [hld] + ld [de], a + dec de + dec c + jr nz, .inner_loop - call ApplyTilemapInVBlank - ld a, [wCurPartySpecies] - push af - call PlayCry - pop af + ; If this is the first time we leave the loop, do mon nickname now. + dec b + ld de, wTempMonNickname + MON_NAME_LENGTH - 1 + ld c, MON_NAME_LENGTH - 1 + jr nz, .outer_loop + + ; Shift data past PP to leave room for PP data. + ld hl, wEncodedTempMonLevel + ld de, wTempMonLevel + lb bc, NUM_MOVES, wEncodedTempMonLevel - wEncodedTempMonPPUps +.reverse_copybytes_loop + ld a, [hld] + ld [de], a + dec de + dec c + jr nz, .reverse_copybytes_loop - cp RAIKOU - jr nz, .not_raikou - farcall RespawnRoamingRaikou - jr .not_suicune -.not_raikou - cp ENTEI - jr nz, .not_entei - farcall RespawnRoamingEntei - jr .not_suicune -.not_entei - cp SUICUNE - jr nz, .not_suicune - farcall RespawnRoamingSuicune -.not_suicune - - ld de, PCString_ReleasedPKMN - jp BillsPC_PlaceString - -MovePKMNWitoutMail_InsertMon: - push hl - push de - push bc + ; Write PP Up data. + ld a, [hl] +.pp_loop push af - ld de, .Saving_LeaveOn - call BillsPC_PlaceString + and %11000000 + ld [de], a pop af - pop bc - pop de - pop hl - ld a, [wCurBox] - push af - ld bc, 0 - ld a, [wBillsPC_BackupLoadedBox] - and a - jr nz, .moving_from_box - set 0, c - -.moving_from_box - ld a, [wBillsPC_LoadedBox] - and a - jr nz, .moving_to_box - set 1, c + add a + add a + dec de + dec b + jr nz, .pp_loop -.moving_to_box - ld hl, .Jumptable - add hl, bc - add hl, bc - ld a, [hli] - ld h, [hl] - ld l, a - call _hl_ pop af - ld e, a - farjp MovePkmnWOMail_InsertMon_SaveGame + jr z, SetTempPartyMonData -.Saving_LeaveOn: - db "Saving… Leave ON!@" + ld hl, BadEggRLE + ld de, wTempMon + call CopyRLE + call SetTempPartyMonData + scf + ret -.Jumptable: +INCLUDE "data/pc/bad_egg.asm" - dw .BoxToBox - dw .PartyToBox - dw .BoxToParty - dw .PartyToParty +SetTempPartyMonData: + ; Calculate stats + ld a, [wTempMonSpecies] + ld [wCurSpecies], a + ld a, [wTempMonForm] + ld [wCurForm], a + call GetBaseData + ld hl, wTempMonOT + PLAYER_NAME_LENGTH + ld a, [hl] + and HYPER_TRAINING_MASK + inc a + ld b, a + ld hl, wTempMonEVs - 1 + ld de, wTempMonMaxHP + ld a, [wTempMonLevel] + ld [wCurPartyLevel], a + predef CalcPkmnStats -.BoxToBox: - ld hl, wBillsPC_BackupLoadedBox - ld a, [wBillsPC_LoadedBox] - cp [hl] - jr z, .same_box - call .CopyFromBox - jp .CopyToBox - -.same_box - call .CopyFromBox - call .CheckTrivialMove - jp .CopyToBox - -.PartyToBox: - call .CopyFromParty - ld a, $1 - ld [wGameLogicPaused], a - farcall SaveGameData - farcall SaveCurrentVersion + ; Reset status condition xor a - ld [wGameLogicPaused], a - jp .CopyToBox - -.BoxToParty: - call .CopyFromBox - jp .CopyToParty - -.PartyToParty: - call .CopyFromParty - call .CheckTrivialMove - jp .CopyToParty - -.CheckTrivialMove: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld e, a - ld a, [wBillsPC_BackupCursorPosition] - ld hl, wBillsPC_BackupScrollPosition - add [hl] - cp e - ret nc - ld hl, wBillsPC_CursorPosition + ld [wTempMonStatus], a + + ; Set HP to full + ld hl, wTempMonMaxHP + ld de, wTempMonHP + ld a, [hli] + ld [de], a + inc de ld a, [hl] - and a - jr z, .top_of_screen - dec [hl] + ld [de], a + + ; Eggs have 0 current HP + ld hl, wTempMonIsEgg + bit MON_IS_EGG_F, [hl] + jr z, .not_egg + xor a + ld [de], a + dec de + ld [de], a + +.not_egg + ld hl, wTempMonMoves + ld de, wTempMonPP + farcall RestoreTempPP + or 1 ret -.top_of_screen - ld hl, wBillsPC_ScrollPosition - ld a, [hl] - and a +EnsureStorageSpace: +; Returns z if we have at least a unallocated pokedb entries left. This exists +; because flushing incurs a significant performance penalty, so this function +; avoids it when checking storage if we can get away with it. + ld b, a + + ; First, check if we have enough entries without flushing. + push bc + call _CheckFreeDatabaseEntries + pop bc + cp b + sbc a ret z - dec [hl] + + ; Try again, this time with flushing. + push bc + call CheckFreeDatabaseEntries + pop bc + cp b + sbc a + ret + +CheckFreeDatabaseEntries: +; Returns amount of unused database entries left, or 255 if 255+. We don't +; really care if we have 255 or 314 entries left, only if we're running low. + ; Flush the storage system of duplicate entries. + call FlushStorageSystem + ; fallthrough +_CheckFreeDatabaseEntries: + ; Now, count used entries. + ld a, BANK(wPokeDB1UsedEntries) + call StackCallInWRAMBankA +.Function: + ld hl, wPokeDB1UsedEntries + call .CountEntries + push bc + ld hl, wPokeDB2UsedEntries + call .CountEntries + pop bc + add c + ret nc + ld a, 255 ret -.CopyFromBox: - ld a, [wBillsPC_BackupLoadedBox] - dec a - ld e, a - farcall MovePkmnWOMail_SaveGame - ld a, [wBillsPC_BackupCursorPosition] - ld hl, wBillsPC_BackupScrollPosition - add [hl] - ld [wCurPartyMon], a - ld a, $1 +.CountEntries: + ld b, (MONDB_ENTRIES + 7) / 8 + call CountSetBits + cpl + add MONDB_ENTRIES + 1 + ld c, a + ret + +InitializeBoxes: +; Initializes the Storage System boxes as empty with default names and themes. + ld a, BANK(sNewBox1) call GetSRAMBank - ld hl, sBoxSpecies - call CopySpeciesToTemp - ld hl, sBoxMonNicknames - call CopyNicknameToTemp - ld hl, sBoxMonOTs - call CopyOTNameToTemp - ld hl, sBoxMons - ld bc, BOXMON_STRUCT_LENGTH - call CopyMonToTemp - call CloseSRAM - farcall CalcBufferMonStats - ld a, PC_DEPOSIT - ld [wPokemonWithdrawDepositParameter], a - predef_jump RemoveMonFromPartyOrBox + ld b, NUM_BOXES + ld hl, sNewBox1 +.name_loop + push bc + ld d, b + ld bc, sNewBox1Name - sNewBox1 + xor a + rst ByteFill + push hl + push de + ld de, .Box + call CopyName2 + dec hl + pop de + ld a, NUM_BOXES + 1 + sub d + sub 10 + add "0" + 10 + jr c, .next + ld [hl], "1" ; no-optimize *hl++|*hl-- = N + inc hl + sub 10 +.next + ld [hli], a + ld [hl], "@" + pop hl + ld c, sNewBox2 - sNewBox1Name + add hl, bc + pop bc + dec b + jr nz, .name_loop + + ; Sanity check backup boxes. We want to leave them alone for the most part + ; in case the player chose to start a new game, at least until they save. + ; However, if this is a brand new game, the SRAM might be filled with + ; garbage data. Ensure that box slots don't reference entries past + ; MONDB_ENTRIES since that would lead to odd behaviour when trying to check + ; flags in FlushStorageSystem (which needs to care about both active and + ; backup boxes). + ld b, NUM_BOXES + ld hl, sBackupNewBox1Entries +.outer_backup_loop + ld c, MONS_PER_BOX +.inner_backup_loop + ld a, [hl] + cp MONDB_ENTRIES + 1 + jr c, .valid_entry + xor a +.valid_entry + ld [hli], a + dec c + jr nz, .inner_backup_loop + ld de, sBackupNewBox2 - sBackupNewBox1 - MONS_PER_BOX + add hl, de + dec b + jr nz, .outer_backup_loop -.CopyToBox: - ld a, [wBillsPC_LoadedBox] + ld de, BillsPC_DefaultBoxThemes + ld hl, sNewBox1Theme +.theme_loop + ld a, [de] + inc de + inc a + jr z, .done dec a - ld e, a - farcall MovePkmnWOMail_SaveGame - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - farjp InsertPokemonIntoBox + ld [hl], a + ld bc, sNewBox2 - sNewBox1 + add hl, bc + jr .theme_loop -.CopyFromParty: - ld a, [wBillsPC_BackupCursorPosition] - ld hl, wBillsPC_BackupScrollPosition - add [hl] - ld [wCurPartyMon], a - ld hl, wPartySpecies - call CopySpeciesToTemp - ld hl, wPartyMonNicknames - call CopyNicknameToTemp - ld hl, wPartyMonOTs - call CopyOTNameToTemp - ld hl, wPartyMon1Species - ld bc, PARTYMON_STRUCT_LENGTH - call CopyMonToTemp - xor a - ld [wPokemonWithdrawDepositParameter], a - predef_jump RemoveMonFromPartyOrBox +.done + call CloseSRAM -.CopyToParty: - ld a, [wBillsPC_CursorPosition] - ld hl, wBillsPC_ScrollPosition - add [hl] - ld [wCurPartyMon], a - farjp InsertPokemonIntoParty + ; In case we reset the game mid-flush and then chose to start a new game, + ; ensure that all entries are allocated properly. + jp FlushStorageSystem -CopySpeciesToTemp: - ld a, [wCurPartyMon] - ld c, a - ld b, $0 - add hl, bc - ld a, [hl] - ld [wCurPartySpecies], a - ret +.Box: + rawchar "Box @" -CopyNicknameToTemp: - ld bc, MON_NAME_LENGTH - ld a, [wCurPartyMon] - rst AddNTimes - ld de, wBufferMonNickname - ld bc, MON_NAME_LENGTH - rst CopyBytes - ret +INCLUDE "data/pc/default_box_themes.asm" -CopyOTNameToTemp: - ld bc, NAME_LENGTH - ld a, [wCurPartyMon] +GetBoxTheme: +; Returns box b's theme in a. + ld c, 0 + jr CopyBoxTheme + +SetBoxTheme: +; Sets box b's theme to a. + ld c, 1 + ; fallthrough +CopyBoxTheme: + push af + ld a, BANK(sNewBox1) + call GetSRAMBank + ld hl, sNewBox1Theme + ld a, b + dec a + push bc + ld bc, sNewBox2 - sNewBox1 rst AddNTimes - ld de, wBufferMonOT - ld bc, NAME_LENGTH - rst CopyBytes - ret + pop bc + pop af + dec c + jr z, .set_theme + ld a, [hl] +.set_theme + ld [hl], a + jp CloseSRAM -CopyMonToTemp: - ld a, [wCurPartyMon] +GetBoxName: +; Writes name of box b to string buffer 1. + ld c, 0 + call CopyBoxName + + ; Ensure that there's a terminator at the end. This isn't included as part + ; of saved box name. + ld a, "@" + ld [wStringBuffer1 + BOX_NAME_LENGTH], a + ret + +SetBoxName: +; Writes name from string buffer 1 to box b. + ld c, 1 + ; fallthrough +CopyBoxName: +; Copies between box b and string buffer 1 depending on value of c. +; c=0: Copy from box b to string buffer 1. +; c=1: Copy from string buffer 1 to box b. + ld a, BANK(sNewBox1) + call GetSRAMBank + ld hl, sNewBox1Name + ld a, b + dec a + push bc + ld bc, sNewBox2 - sNewBox1 rst AddNTimes - ld de, wBufferMon + pop bc + ld de, wStringBuffer1 + dec c + call z, SwapHLDE + ld bc, BOX_NAME_LENGTH rst CopyBytes - ret + jp CloseSRAM -GetBoxPointer: - dec b - ld c, b - ld b, 0 - ld hl, .boxes - add hl, bc - add hl, bc - add hl, bc - ld a, [hli] +PrevStorageBoxMon: +; Reads wTempMonBox+wTempMonSlot and attempts to load a previous mon. +; Returns nz upon success, otherwise z. If there is no previous mon, +; wTempMonBox+wTempMonSlot is unchanged. + push bc + ld a, [wTempMonSlot] ld b, a - ld a, [hli] - ld h, [hl] - ld l, a - ret - -.boxes - ; bank, address - dba sBox1 - dba sBox2 - dba sBox3 - dba sBox4 - dba sBox5 - dba sBox6 - dba sBox7 - dba sBox8 - dba sBox9 - dba sBox10 - dba sBox11 - dba sBox12 - dba sBox13 - dba sBox14 - -BillsPC_ApplyPalettes: - call GetCGBLayout - ld a, %11100100 - call DmgToCgbBGPals - ld a, %11111100 - jp DmgToCgbObjPal0 - -BillsPC_InitGFX: - call DisableLCD - ld hl, vTiles2 tile $00 - ld bc, $31 tiles - xor a - rst ByteFill - call LoadStandardFont - call LoadFontsBattleExtra - ld hl, PCMailGFX - ld de, vTiles2 tile $5c - ld bc, 4 tiles - rst CopyBytes - ld hl, PCSelectLZ - ld de, vTiles0 tile $00 - call Decompress - ld a, 6 - call SkipMusic - jp EnableLCD - -PCSelectLZ: INCBIN "gfx/pc/pc.2bpp.lz" -PCMailGFX: INCBIN "gfx/pc/mail.2bpp" - -PCString_ChooseaPKMN: db "Choose a .@" -PCString_WhatsUp: db "What's up?@" -PCString_ReleasePKMN: db "Release ?@" -PCString_MoveToWhere: db "Move to where?@" -PCString_ItsYourLastPKMN: db "It's your last !@" -PCString_TheresNoRoom: db "There's no room!@" -PCString_NoMoreUsablePKMN: db "No more usable !@" -PCString_RemoveMail: db "Remove Mail.@" -PCString_ReleasedPKMN: db "Released .@" -PCString_Bye: db "Bye,@" -PCString_Stored: db "Stored @" -PCString_Got: db "Got @" -PCString_BoxFull: db "The Box is full.@" -PCString_PartyFull: db "The party's full!@" -PCString_NoReleasingEGGS: db "No releasing Eggs!@" -PCString_NoHeldItem: db "No held item@" - -_ChangeBox: - call LoadStandardMenuHeader - call BillsPC_ClearTilemap + ld c, a .loop - xor a - ldh [hBGMapMode], a - call BillsPC_PrintBoxName - call BillsPC_PlaceChooseABoxString - ld hl, _ChangeBox_menudataheader - call CopyMenuHeader - xor a - ld [wMenuScrollPosition], a - hlcoord 0, 4 - lb bc, 8, 9 - call Textbox - call ScrollingMenu - ld a, [wMenuJoypad] - cp B_BUTTON - jr z, .done - call BillsPC_PlaceWhatsUpString - call BillsPC_ChangeBoxSubmenu + dec c + jr z, .restore_slot + push bc + ld a, [wTempMonBox] + ld b, a + call GetStorageBoxMon + pop bc + jr nz, .done jr .loop +.restore_slot + ld a, b + ld [wTempMonSlot], a .done - jp CloseWindow - -BillsPC_ClearTilemap: - xor a - ldh [hBGMapMode], a - hlcoord 0, 0 - ld bc, SCREEN_WIDTH * SCREEN_HEIGHT - ld a, " " - rst ByteFill + pop bc ret -_ChangeBox_menudataheader: - db $40 ; flags - db 05, 01 ; start coords - db 12, 09 ; end coords - dw .menudata2 - db 1 ; default option - -.menudata2 - db $22 ; flags - db 4, 0 - db 1 - dba .boxes - dba .boxnames - dba NULL - dba BillsPC_PrintBoxCountAndCapacity - -.boxes - db NUM_BOXES -for x, 1, NUM_BOXES + 1 - db x -endr - db -1 - -.boxnames - push de - ld a, [wMenuSelection] - dec a - call GetBoxName - pop hl - rst PlaceString - ret +NextStorageBoxMon: +; Reads wTempMonBox+wTempMonSlot and attempts to load the next mon. +; Returns nz upon success, otherwise z. If there is no next mon, +; wTempMonBox+wTempMonSlot is unchanged. + push bc + ld a, [wTempMonSlot] + ld b, a + ld c, a +.loop + ld a, c + inc c + cp MONS_PER_BOX + jr z, .restore_slot +.get_storage + push bc + ld a, [wTempMonBox] + ld b, a + call GetStorageBoxMon + pop bc + jr nz, .done -GetBoxName: - ld bc, BOX_NAME_LENGTH - ld hl, wBoxNames - rst AddNTimes - ld d, h - ld e, l + ; If we're dealing with a party, we ran past the amount of mons we have. + ld a, [wTempMonBox] + and $7f + jr nz, .loop + ; fallthrough +.restore_slot + ld a, b + ld [wTempMonSlot], a +.done + pop bc ret -BillsPC_PrintBoxCountAndCapacity: - hlcoord 11, 7 - lb bc, 5, 7 - call Textbox - ld a, [wMenuSelection] - cp -1 +GetStorageBoxMon: +; Reads storage bank+entry from box b slot c and put it in wTempMon. +; This function supports handling the OT party by setting b to $80. +; If there is a checksum error, put Bad Egg data in wTempMon instead. +; Returns c in case of a Bad Egg, z if the requested mon doesn't exist, +; nz|nc otherwise. If b==0, read from party list. c is 1-indexed. + xor a + ld [wTempMonSlot], a + + ; Check if we're reading party or box data. + ld a, b + ld [wTempMonBox], a + and $7f + jr z, .read_party + push de + call GetStorageBoxPointer + call GetStorageMon + pop de ret z - hlcoord 12, 9 - ld de, .Pokemon - rst PlaceString - call GetBoxCount - ld [wd265], a - hlcoord 13, 11 - ld de, wd265 - lb bc, 1, 2 - call PrintNum - ld de, .out_of_20 - rst PlaceString + ld a, c + ld [wTempMonSlot], a ret -.Pokemon: - db "#mon@" - -.out_of_20 - ; db "/20@" - db "/" - db "0" + MONS_PER_BOX / 10 ; "2" - db "0" + MONS_PER_BOX % 10 ; "0" - db "@" - -BillsPC_PrintBoxCountAndCapacityInsideBox: - hlcoord 0, 0 - lb bc, 1, 5 - call Textbox - ld a, [wBillsPC_LoadedBox] +.read_party and a - jr z, .party - ld a, [wBillsPC_NumMonsInBox] - dec a - ld [wd265], a - hlcoord 1, 1 - ld de, wd265 - lb bc, 1, 2 - call PrintNum - ld de, .out_of_20 - rst PlaceString - ret - -.party ld a, [wPartyCount] - ld [wd265], a - hlcoord 1, 1 - ld de, wd265 - lb bc, 1, 2 - call PrintNum - ld de, .out_of_6 - rst PlaceString + jr z, .got_partycount + ld a, [wOTPartyCount] +.got_partycount + cp c + jr nc, .party_not_empty + xor a ret - -.out_of_20 - ; db "/20@" - db "/" -if MONS_PER_BOX < 10 - db " " -else - db "0" + MONS_PER_BOX / 10 ; "2" -endc - db "0" + MONS_PER_BOX % 10 ; "0" - db "@" - -.out_of_6 - ; db "/ 6@" - db "/" -if PARTY_LENGTH < 10 - db " " -else - db "0" + PARTY_LENGTH / 10 ; "0" -endc - db "0" + PARTY_LENGTH % 10 ; "6" - db "@" - -GetBoxCountWithC: - ld a, [wCurBox] - ld b, a +.party_not_empty ld a, c - ld c, b - jr BoxSelectionJumpIn + ld [wTempMonSlot], a + push hl + push de + push bc + inc b + call CopyBetweenPartyAndTemp -GetBoxCount: - ld a, [wCurBox] - ld c, a - ld a, [wMenuSelection] -BoxSelectionJumpIn: + ; Calculate stats + ld a, [wTempMonSpecies] + ld [wCurSpecies], a + ld a, [wTempMonForm] + ld [wCurForm], a + call GetBaseData + or 1 + jp PopBCDEHL + +GetStorageMon: +; Reads storage bank d, entry e and put it in wTempMon. +; If there is a checksum error, put Bad Egg data in wTempMon instead. +; Returns c in case of a Bad Egg, z if the requested mon doesn't exist, +; nz|nc otherwise. + push hl + push de + push bc + call IsStorageUsed + jr z, .done ; entry not found + + call OpenStorageDB + + ; Get the correct pointer + ld hl, sBoxMons1Mons + ld bc, SAVEMON_STRUCT_LENGTH + ld a, e dec a - cp c - jr z, .activebox - ld c, a - ld b, 0 - ld hl, .boxbanks - add hl, bc - add hl, bc - add hl, bc - ld a, [hli] - ld b, a - call GetSRAMBank - ld a, [hli] - ld h, [hl] - ld l, a - ld a, [hl] + rst AddNTimes + + ; Write to wEncodedTempMon and then decode it. + ld de, wEncodedTempMon + ld bc, SAVEMON_STRUCT_LENGTH + rst CopyBytes + + ; Decode the result. This also returns a Bad Egg failsafe on a checksum + ; error. + call DecodeTempMon +.done call CloseSRAM - ld c, a - ld a, [wSavedAtLeastOnce] - and a - jr z, .newfile - ld a, c - ret + jp PopBCDEHL -.newfile +AllocateStorageFlag: +; Allocates the given storage flag. Returns nz if storage is already in use. + call IsStorageUsed + ret nz + call _AllocateStorageFlag xor a ret -.activebox - ld a, BANK(sBoxCount) - ld b, a - call GetSRAMBank - ld hl, sBoxCount - ld a, [hl] - jp CloseSRAM - -.boxbanks - dba sBox1 - dba sBox2 - dba sBox3 - dba sBox4 - dba sBox5 - dba sBox6 - dba sBox7 - dba sBox8 - dba sBox9 - dba sBox10 - dba sBox11 - dba sBox12 - dba sBox13 - dba sBox14 - -BillsPC_PrintBoxName: - hlcoord 0, 0 - lb bc, 2, 18 - call Textbox - hlcoord 1, 2 - ld de, .Current - rst PlaceString - ld a, [wCurBox] - and $f - call GetBoxName - hlcoord 11, 2 - rst PlaceString +IsStorageUsed: +; Returns z if the given storage slot is unused. Preserves wTempMon. + ld a, CHECK_FLAG + jr StorageFlagAction +_AllocateStorageFlag: + ld a, SET_FLAG + ; fallthrough +StorageFlagAction: +; Performs flag action a on storage entry in de. + ; If we're dealing with a null entry (e=0), do nothing but pretend the + ; entry is unused if asked. Don't optimize away the xor a, since we + ; want to mimic the normal behaviour of the function. + inc e + dec e + jr nz, .not_null + xor a ret -.Current: - db "Current@" +.not_null + push hl + push de + push bc + ld b, a -BillsPC_ChangeBoxSubmenu: - ld hl, .MenuDataHeader - call LoadMenuHeader - call VerticalMenu - call ExitMenu - ret c - ld a, [wMenuCursorY] - cp $1 - jr z, .Switch - cp $2 - jr z, .Name + call .do_it + ; Stack call doesn't preserve flags. and a - ret + jp PopBCDEHL -.Switch: - ld a, [wMenuSelection] - dec a - ld e, a - ld a, [wCurBox] - cp e - ret z - farjp ChangeBoxSaveGame - -.Name: - ld b, $4 ; box - ld de, wBoxNameBuffer - farcall NamingScreen - call ClearTileMap - call LoadStandardFont - call LoadFontsBattleExtra - ld a, [wMenuSelection] - dec a - call GetBoxName - ld e, l - ld d, h - ld hl, wBoxNameBuffer - ld c, BOX_NAME_LENGTH - 1 - call InitString - ld a, [wMenuSelection] +.do_it + ld a, BANK(wPokeDB1UsedEntries) + call StackCallInWRAMBankA +.Function: + ld a, d dec a - call GetBoxName - ld de, wBoxNameBuffer - jp CopyName2 - -.MenuDataHeader: - db $40 ; flags - db 04, 11 ; start coords - db 11, 19 ; end coords - dw .MenuData2 - db 1 ; default option - -.MenuData2: - db $80 ; flags - db 3 ; items - db "Switch@" - db "Name@" - db "Quit@" - -BillsPC_PlaceChooseABoxString: - ld de, .ChooseABox - jr BillsPC_PlaceChangeBoxString - -.ChooseABox: - db "Choose a Box.@" - -BillsPC_PlaceWhatsUpString: - ld de, .WhatsUp - jr BillsPC_PlaceChangeBoxString - -.WhatsUp: - db "What's up?@" - -BillsPC_PlaceChangeBoxString: - push de - hlcoord 0, 14 - lb bc, 2, 18 - call Textbox - pop de - hlcoord 1, 16 - rst PlaceString - ld a, $1 - ldh [hBGMapMode], a - ret + ld hl, wPokeDB1UsedEntries + jr z, .got_entries + ld hl, wPokeDB2UsedEntries +.got_entries + ld c, e + dec c + ld d, 0 + predef_jump FlagPredef diff --git a/engine/pokemon/bills_pc_top.asm b/engine/pokemon/bills_pc_top.asm deleted file mode 100644 index 627a0fcfa2..0000000000 --- a/engine/pokemon/bills_pc_top.asm +++ /dev/null @@ -1,215 +0,0 @@ -_BillsPC: - call .CheckCanUsePC - ret c - call .LogIn - call .UseBillsPC - jp .LogOut - -.CheckCanUsePC: - ld a, [wPartyCount] - and a - ret nz - ld hl, .Text_GottaHavePokemon - call MenuTextboxBackup - scf - ret - -.Text_GottaHavePokemon: - ; You gotta have #MON to call! - text_far _PCGottaHavePokemonText - text_end - -.LogIn: - xor a - ldh [hBGMapMode], a - call LoadStandardMenuHeader - call ClearPCItemScreen - ld hl, wOptions1 - ld a, [hl] - push af - set NO_TEXT_SCROLL, [hl] - ld hl, .Text_What - call PrintText - pop af - ld [wOptions1], a - jp LoadFontsBattleExtra - -.Text_What: - ; What? - text_far _PCWhatText - text_end - -.LogOut: - jp CloseSubmenu - -.UseBillsPC: - ld hl, .MenuDataHeader - call LoadMenuHeader - ld a, $1 -.loop - ld [wMenuCursorBuffer], a - call SetPalettes - xor a - ld [wWhichIndexSet], a - ldh [hBGMapMode], a - call DoNthMenu - jr c, .cancel - ld a, [wMenuCursorBuffer] - push af - ld a, [wMenuSelection] - ld hl, .Jumptable - call JumpTable - pop bc - ld a, b - jr nc, .loop -.cancel - jp CloseWindow - -.MenuDataHeader: - db $40 ; flags - db 00, 00 ; start coords - db 17, 19 ; end coords - dw .MenuData2 - db 1 ; default option - -.MenuData2: - db $80 ; flags - db 0 ; items - dw .items - dw PlaceMenuStrings - dw .strings - -.strings - db "Withdraw @" - db "Deposit @" - db "Change Box@" - db "Move w/o Mail@" - db "See ya!@" - -.Jumptable: - dw BillsPC_WithdrawMenu - dw BillsPC_DepositMenu - dw BillsPC_ChangeBoxMenu - dw BillsPC_MovePKMNMenu - dw BillsPC_SeeYa - -.items - db 5 - db 0 ; WITHDRAW - db 1; DEPOSIT - db 2 ; CHANGE BOX - db 3 ; MOVE PKMN - db 4 ; SEE YA! - db -1 - -BillsPC_SeeYa: - scf - ret - -BillsPC_MovePKMNMenu: - call LoadStandardMenuHeader - farcall IsAnyMonHoldingMail - jr nc, .no_mail - ld hl, .Text_MonHoldingMail - call PrintText - jr .quit - -.no_mail - farcall StartMovePkmnWOMail_SaveGame - jr c, .quit - farcall _MovePKMNWithoutMail - call ReturnToMapFromSubmenu - call ClearPCItemScreen - -.quit - call CloseWindow - and a - ret - -.Text_MonHoldingMail: - ; There is a #MON holding MAIL. Please remove the MAIL. - text_far _PCMonHoldingMailText - text_end - -BillsPC_DepositMenu: - call LoadStandardMenuHeader - farcall _DepositPKMN - call ReturnToMapFromSubmenu - call ClearPCItemScreen - call CloseWindow - and a - ret - -CheckCurPartyMonFainted: - ld hl, wPartyMon1HP - ld de, PARTYMON_STRUCT_LENGTH - ld b, $0 -.loop - ld a, [wCurPartyMon] - cp b - jr z, .skip - ld a, [hli] - or [hl] - jr nz, .notfainted - dec hl - -.skip - inc b - ld a, [wPartyCount] - cp b - jr z, .done - add hl, de - jr .loop - -.done - scf - ret - -.notfainted - and a - ret - -BillsPC_WithdrawMenu: - call LoadStandardMenuHeader - farcall _WithdrawPKMN - call ReturnToMapFromSubmenu - call ClearPCItemScreen - call CloseWindow - and a - ret - -BillsPC_ChangeBoxMenu: - farcall _ChangeBox - and a - ret - -ClearPCItemScreen: - call DisableSpriteUpdates - xor a - ldh [hBGMapMode], a - call ClearBGPalettes - call ClearSprites - hlcoord 0, 0 - ld bc, SCREEN_HEIGHT * SCREEN_WIDTH - ld a, " " - rst ByteFill - hlcoord 0, 0 - lb bc, 10, 18 - call Textbox - hlcoord 0, 12 - lb bc, 4, 18 - call Textbox - call ApplyAttrAndTilemapInVBlank - jp SetPalettes ; load regular palettes? - -CopyBoxmonToTempMon: - ld a, [wCurPartyMon] - ld hl, sBoxMon1Species - ld bc, BOXMON_STRUCT_LENGTH - rst AddNTimes - ld de, wTempMonSpecies - ld bc, BOXMON_STRUCT_LENGTH - ld a, BANK(sBoxMon1Species) - call GetSRAMBank - rst CopyBytes - jp CloseSRAM diff --git a/engine/pokemon/bills_pc_ui.asm b/engine/pokemon/bills_pc_ui.asm new file mode 100644 index 0000000000..fcabd3d046 --- /dev/null +++ b/engine/pokemon/bills_pc_ui.asm @@ -0,0 +1,3602 @@ +; Object palettes + const_def 1 + const PAL_CURSOR_MODE1 + const PAL_CURSOR_MODE2 + const PAL_MINI_ICON + +; Cursor modes + const_def + const PC_MENU_MODE ; 0 + const PC_SWAP_MODE ; 1 + const PC_ITEM_MODE ; 2 +NUM_PC_MODES EQU const_value + +; BillsPC_MenuStrings indexes +; BillsPC_MenuJumptable indexes + const_def + const BOXMENU_CANCEL + const BOXMENU_WITHDRAW + const BOXMENU_DEPOSIT + const BOXMENU_STATS + const BOXMENU_SWITCH + const BOXMENU_MOVES + const BOXMENU_ITEM + const BOXMENU_RELEASE + const BOXMENU_RENAME + const BOXMENU_THEME + const BOXMENU_RELEASEALL + const BOXMENU_TAKEMAIL + const BOXMENU_READMAIL + const BOXMENU_MOVEITEM + const BOXMENU_BAGITEM + const BOXMENU_GIVEITEM + +_BillsPC: + call .CheckCanUsePC + ret c + ld hl, wOptions1 + ld a, [hl] + push af + set NO_TEXT_SCROLL, [hl] + ld a, 71 + ldh [rLYC], a + call LoadStandardMenuHeader + call UseBillsPC + + ; Disable hblank before restoring blockdata, since blockdata and hblank pals + ; overlap. + ld hl, rIE + res LCD_STAT, [hl] + ld a, LOW(LCDGeneric) + ldh [hFunctionTargetLo], a + ld a, HIGH(LCDGeneric) + ldh [hFunctionTargetHi], a + + call ReturnToMapFromSubmenu + pop af + ld [wOptions1], a + jp CloseSubmenu + +.CheckCanUsePC: + ld a, [wPartyCount] + and a + ret nz + ld hl, .Text_GottaHavePokemon + call MenuTextboxBackup + scf + ret + +.Text_GottaHavePokemon: + ; You gotta have #MON to call! + text_far _PCGottaHavePokemonText + text_end + +BillsPC_LoadUI: + ld a, 1 + ldh [rVBK], a + + ; Reserve 4 blank tiles for empty slots + ld a, 4 + ld hl, vTiles3 +.blank_loop + ld de, vTiles5 tile $7f + push af + ld c, 1 + push hl + push de + call Get2bpp + pop de + pop hl + ld bc, 1 tiles + add hl, bc + pop af + dec a + jr nz, .blank_loop + + ; Load cursor tiles. + ld de, BillsPC_CursorGFX + ld hl, vTiles3 tile $04 + lb bc, BANK(BillsPC_CursorGFX), 3 + call Get2bpp + + ; Blank held cursor mini + item icons. + ld hl, vTiles3 tile $08 + ld a, 7 ; cursor+quick mini/shadow/item + hover item icon + call BillsPC_BlankTiles + + call BillsPC_BlankCursorItem + + ; Held item icon + ld hl, vTiles3 tile $1c + ld de, HeldItemIcons + lb bc, BANK(HeldItemIcons), 2 + call Get2bpp + + ; Cursor mode and Pack sprites + ld hl, BillsPC_ObjGFX + ld de, vTiles3 tile $24 + lb bc, BANK(BillsPC_ObjGFX), 19 + call DecompressRequest2bpp + + xor a + ldh [rVBK], a + + ; Cursor sprite OAM + lb de, -18, 0 ; fixed up by the animseq code + ld a, SPRITE_ANIM_INDEX_PC_CURSOR + call _InitSpriteAnimStruct + ld a, PCANIM_ANIMATE + ld [wBillsPC_CursorAnimFlag], a + + ; Cursor mode icon + lb de, $98, $10 + ld a, SPRITE_ANIM_INDEX_PC_MODE + push de + call _InitSpriteAnimStruct + pop af + ld a, SPRITE_ANIM_INDEX_PC_MODE2 + call _InitSpriteAnimStruct + + ; Pack icon. + ; TODO: Instead of a hack where we prevent the pack from claiming a slot, + ; maybe implement a sprite anim priority system? + ld hl, wSpriteAnim4 ; reserve this for quickanim + inc [hl] + push hl + lb de, $58, $30 + ld a, SPRITE_ANIM_INDEX_PC_PACK + call _InitSpriteAnimStruct + pop hl + dec [hl] + + ; Gender symbols and shiny star + ld hl, BattleExtrasGFX + ld de, vTiles2 tile $40 + lb bc, BANK(BattleExtrasGFX), 4 + call DecompressRequest2bpp + + ; Box frame tiles and Pokérus symbol (overwrites first tile of BattleExtrasGFX) + ld hl, BillsPC_TileGFX + ld de, vTiles2 tile $31 + lb bc, BANK(BillsPC_TileGFX), 16 + call DecompressRequest2bpp + + ; Set up background + outline palettes + xor a + ld [wBillsPC_ApplyThemePals], a + ; fallthrough +_BillsPC_GetCGBLayout: + ld a, CGB_BILLS_PC + jp GetCGBLayout + +BillsPC_RefreshTheme: + ld a, 1 + ld [wBillsPC_ApplyThemePals], a + jr _BillsPC_GetCGBLayout + +UseBillsPC: + call ClearTileMap + call ClearPalettes + farcall WipeAttrMap + call ClearSprites + call ClearSpriteAnims + ld a, [wVramState] + res 0, a + ld [wVramState], a + + call BillsPC_LoadUI + + xor a ; PC_MENU_MODE + call _BillsPC_SetCursorMode + + ; Default cursor data (top left of storage, not holding anything) + ld a, $12 + ld [wBillsPC_CursorPos], a + xor a + ld [wBillsPC_CursorHeldSlot], a + + ld a, 1 + ldh [rVBK], a + + ; Pokepic attributes + hlcoord 0, 0, wAttrMap + lb bc, 7, 7 + ld a, 2 + call FillBoxWithByte + + ; Shiny and PkRs + hlcoord 5, 8, wAttrMap + ld a, $3 + ld [hli], a + ld [hl], a + + ; Item name is in vbk1 + hlcoord 10, 2, wAttrMap ; Cursor's item + ld bc, 10 + ld a, VRAM_BANK_1 + push bc + rst ByteFill + pop bc + hlcoord 10, 3, wAttrMap ; Mon's item + rst ByteFill + + ; Storage box + hlcoord 7, 4 + lb bc, 12, 11 + ld de, .BoxTiles + call .Box + + ; Seperator between box name and content + hlcoord 7, 6 + lb bc, $3e, 11 + call .SpecialRow + + ; set up box title to use vbk0 (previously set to vbk1 by .Box) + hlcoord 8, 5, wAttrMap + ld bc, 11 + ld a, 7 + rst ByteFill + + ; initialize icon graphics + palettes (tilemaps are set up later) + ld a, 1 + ldh [rVBK], a + call SetPartyIcons + call SetBoxIconsAndName + xor a + ldh [rVBK], a + + ; Party box + hlcoord 0, 9 + lb bc, 7, 5 + ld de, .PartyTiles + call .Box + + ; Party label borders + hlcoord 0, 10 + lb bc, $36, 5 + call .SpecialRow + + ; Party label text + hlcoord 2, 9 + ld a, $38 + ld [hli], a + inc a + ld [hli], a + inc a + ld [hli], a + inc a + hlcoord 2, 10 + ld [hli], a + inc a + ld [hli], a + inc a + ld [hli], a + + ; Write icon tilemaps + ; Party + hlcoord 1, 11 + lb bc, 3, 2 + lb de, $80, 2 | VRAM_BANK_1 + call .WriteIconTilemap + + ; Storage + hlcoord 8, 7 + lb bc, 5, 4 + lb de, $98, 4 | VRAM_BANK_1 + call .WriteIconTilemap + + ; Update attribute map data + ld b, 2 + call SafeCopyTilemapAtOnce + + ; Set up for HBlank palette switching + ld a, LOW(LCDBillsPC1) + ldh [hFunctionTargetLo], a + ld a, HIGH(LCDBillsPC1) + ldh [hFunctionTargetHi], a + ld hl, rIE + set LCD_STAT, [hl] + + ; Display data about current Pokémon pointed to by cursor + call GetCursorMon + + ; Begin storage system interaction + call ManageBoxes + + ; Finished with storage system. Cleanup + call ClearTileMap + jp ClearPalettes + +.Box: +; Draws a box with tiles and attributes + push bc + push hl + call CreateBoxBorders + pop hl + ld bc, wAttrMap - wTileMap + add hl, bc + pop bc + ld de, .BoxAttr + jp CreateBoxBorders + +.BoxTiles: + db $33, $32, $33 ; top + db $31, $7f, $31 ; middle + db $33, $32, $33 ; bottom +.PartyTiles: + db $35, $34, $35 ; top + db $31, $7f, $31 ; middle + db $33, $32, $33 ; bottom +.BoxAttr: + db 1, 1, 1 | X_FLIP ; top + db 1, 2 | VRAM_BANK_1, 1 | X_FLIP ; middle + db 1 | Y_FLIP, 1 | Y_FLIP, 1 | X_FLIP | Y_FLIP ; bottom + +.SpecialRow: +; Draws a nonstandard box outline + ld a, b + ld [hli], a + inc a + ld b, 0 + push bc + push hl + rst ByteFill + dec a + ld [hl], a + pop hl + ld bc, wAttrMap - wTileMap + add hl, bc + pop bc + ld a, 1 + rst ByteFill + ret + +.WriteIconTilemap: +; Writes icon tile+attr data for b rows, c cols starting from hlcoord, tile a + ld a, d +.tile_row + push bc + push de + push hl +.tile_col + call .icon + dec c + jr nz, .tile_col + pop hl + ld bc, SCREEN_WIDTH * 2 + add hl, bc + pop de + pop bc + dec b + jr nz, .tile_row + ret + +.icon + push bc + ld [hli], a + inc a + ld [hld], a + inc a + ld bc, SCREEN_WIDTH + add hl, bc + ld [hli], a + inc a + ld [hld], a + inc a + ld bc, -SCREEN_WIDTH + (wAttrMap - wTileMap) + add hl, bc + ld [hl], e + inc hl + ld [hl], e + ld bc, SCREEN_WIDTH - 1 + add hl, bc + ld [hl], e + inc hl + ld [hl], e + inc e + ld bc, -SCREEN_WIDTH + 2 + (wTileMap - wAttrMap) + add hl, bc + pop bc + ret + +BillsPC_CursorGFX: +INCBIN "gfx/pc/cursor.2bpp" + +BillsPC_TileGFX: +INCBIN "gfx/pc/pc.2bpp.lz" + +BillsPC_ObjGFX: +INCBIN "gfx/pc/obj.2bpp.lz" + +BillsPC_BlankTiles: +; Used as input to blank a*4 tiles (mon icons typically use 4 tiles). + ld de, vTiles3 tile $00 ; Reserved blank tiles. + ld bc, 4 tiles +.loop + push hl + push de + push bc + push af + ld c, 4 + call BillsPC_SafeGet2bpp + pop af + pop bc + pop de + pop hl + add hl, bc + dec a + jr nz, .loop + ret + +BillsPC_SetCursorMode: + call _BillsPC_SetCursorMode + jp BillsPC_SetPals + +_BillsPC_SetCursorMode: +; Switches cursor mode and updates the cursor palette. Doesn't write palettes, +; use the non-underscore version of this to do that. Also updates the mode icon. + ld [wBillsPC_CursorMode], a + ld a, BANK("GBC Video") + call StackCallInWRAMBankA +.Function: + ; hl = .CursorPals + [wBillsPC_CursorMode] * 4 + ld a, [wBillsPC_CursorMode] + add a + add a + add LOW(.CursorPals) + ld l, a + adc HIGH(.CursorPals) + sub l + ld h, a + ld de, wOBPals1 palette PAL_CURSOR_MODE1 + 4 + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + ld de, wOBPals1 palette PAL_CURSOR_MODE2 + 4 + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + ret + +.CursorPals: +; PC_MENU_MODE = red +if !DEF(MONOCHROME) + RGB 31, 20, 20 + RGB 31, 10, 06 +else + MONOCHROME_RGB_TWO +endc +; PC_SWAP_MODE = blue +if !DEF(MONOCHROME) + RGB 20, 20, 31 + RGB 06, 10, 31 +else + MONOCHROME_RGB_TWO +endc +; PC_ITEM_MODE = green +if !DEF(MONOCHROME) + RGB 20, 28, 20 + RGB 06, 26, 10 +else + MONOCHROME_RGB_TWO +endc + + +BillsPC_SafeRequest2bppInWRA6:: + ldh a, [hROMBank] + ld b, a + call RunFunctionInWRA6 + +BillsPC_SafeGet2bpp: +; Only copies graphics when doing so wont interfere with hblank palette usage. +; Otherwise, wait until next frame. + ldh a, [rLY] + cp $40 + jp c, Get2bpp + call DelayFrame + jr BillsPC_SafeGet2bpp + +BillsPC_PrintBoxName: +; Writes name of current Box to box name area in storage system + hlcoord 9, 5 + ld a, " " + ld bc, 9 + rst ByteFill + + ; Write new box name + ld a, [wCurBox] + ld b, a + inc b + farcall GetBoxName + ld hl, wStringBuffer1 + ld d, h + ld e, l + + ; Center the name + ld b, 0 +.loop + ld a, [hli] + inc b + cp "@" + jr nz, .loop + srl b + ld a, 5 + sub b + ld c, a + ld b, 0 + hlcoord 9, 5 + add hl, bc + jp PlaceString + +SetPartyIcons: +; Writes party list + ; Blank current list + xor a + ld hl, wBillsPC_PartyList + ld bc, PARTY_LENGTH * 2 + rst ByteFill + + ld hl, vTiles4 tile $00 + ld a, PARTY_LENGTH + call BillsPC_BlankTiles + +_SetPartyIcons: + ; Write party members + lb bc, 0, 1 + ld hl, wBillsPC_PartyList + lb de, PARTY_LENGTH, $80 + jr PCIconLoop + +SetBoxIconsAndName: + ; Blank previous box name + call BillsPC_PrintBoxName + ; fallthrough +SetBoxIcons: + ; Blank current list + xor a + ld hl, wBillsPC_BoxList + ld bc, MONS_PER_BOX * 2 + rst ByteFill + + ld hl, vTiles4 tile $18 + ld a, MONS_PER_BOX + call BillsPC_BlankTiles + +_SetBoxIcons: + ; Write box members + ld a, [wCurBox] + inc a + ld b, a + ld c, 1 + ld hl, wBillsPC_BoxList + lb de, MONS_PER_BOX, $98 + ; fallthrough +PCIconLoop: + ; Don't draw mons we're holding. + ld a, [wBillsPC_CursorHeldBox] + cp b + jr nz, .not_holding + ld a, [wBillsPC_CursorHeldSlot] + cp c + jr z, .blank +.not_holding + call GetStorageBoxMon + jr z, .blank + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld a, [wTempMon] + jr z, .got_curicon + ld a, EGG +.got_curicon + ld [wCurIcon], a + ld [hli], a + ld a, [wTempMonForm] + ld [wCurIconForm], a + ld [hli], a + ld a, e + push hl + push de + push bc + farcall GetStorageIcon_a + pop bc + pop de + pop hl + call WriteIconPaletteData + jr .next + +.blank + ; Fill storage species slot with a blank species. + xor a + ld [hli], a + ld [hli], a + +.next + ld a, e + add 4 + ld e, a + inc c + dec d + jr nz, PCIconLoop + ret + +BillsPC_GetMonIconAddr: + push de + push bc + inc b + dec b + ld hl, wBillsPC_PartyList + jr z, .got_tile_base + ld hl, wBillsPC_BoxList +.got_tile_base + ld a, 2 + ld b, 0 + dec c + rst AddNTimes + pop bc + pop de + ret + +BillsPC_GetMonTileAddr: + push de + push bc + inc b + dec b + ld hl, vTiles4 tile $00 + jr z, .got_tile_base + ld hl, vTiles4 tile $18 +.got_tile_base + ld a, 4 tiles + ld b, 0 + dec c + rst AddNTimes + pop bc + pop de + ret + +BillsPC_GetMonPalAddr: +; Gets mon pal in hl for box b slot c. + push de + push bc + ld a, c + dec a + inc b + dec b + ld bc, wBillsPC_MonPals2 - wBillsPC_MonPals1 + ld d, 2 + ld hl, wBillsPC_PartyPals3 + jr z, .loop + ld d, 4 + ld hl, wBillsPC_MonPals1 +.loop + sub d + jr c, .found_row + add hl, bc + jr .loop +.found_row + add d + add a + add a + ld c, a + add hl, bc + pop bc + pop de + ret + +WriteIconPaletteData: +; Write box slot c's palette data. If b is zero, write party palette instead. +; (This is the same input as various "box mon data" functions). + push hl + push de + push bc + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld a, [wTempMonSpecies] + jr z, .got_species + ld a, EGG +.got_species + ld hl, wTempMonPersonality + farcall _GetMonIconPalette + pop bc + push bc + push af + call BillsPC_GetMonPalAddr + pop af + +if !DEF(MONOCHROME) + ; TODO: per-mon palettes + ; RGB values copied from PartyMenuOBPals + ld [hl], LOW(palred 31 + palgreen 19 + palblue 10) ; no-optimize *hl++|*hl-- = N + inc hl + ld [hl], HIGH(palred 31 + palgreen 19 + palblue 10) ; no-optimize *hl++|*hl-- = N + inc hl + and a ; PAL_OW_RED + ld de, palred 31 + palgreen 07 + palblue 01 + jr z, .got_pal2 + dec a ; PAL_OW_BLUE + ld de, palred 10 + palgreen 09 + palblue 31 + jr z, .got_pal2 + dec a ; PAL_OW_GREEN + ld de, palred 07 + palgreen 23 + palblue 03 + jr z, .got_pal2 + dec a ; PAL_OW_BROWN + ld de, palred 15 + palgreen 10 + palblue 03 + jr z, .got_pal2 + dec a ; PAL_OW_PURPLE + ld de, palred 18 + palgreen 04 + palblue 18 + jr z, .got_pal2 + dec a ; PAL_OW_GRAY + ld de, palred 13 + palgreen 13 + palblue 13 + jr z, .got_pal2 + dec a ; PAL_OW_PINK + ld de, palred 31 + palgreen 10 + palblue 11 + jr z, .got_pal2 + ; PAL_OW_TEAL + ld de, palred 03 + palgreen 23 + palblue 21 +.got_pal2 + ld [hl], e + inc hl + ld [hl], d +else + ld [hl], LOW(PAL_MONOCHROME_WHITE) ; no-optimize *hl++|*hl-- = N + inc hl + ld [hl], HIGH(PAL_MONOCHROME_WHITE) ; no-optimize *hl++|*hl-- = N + inc hl + ld [hl], LOW(PAL_MONOCHROME_LIGHT) ; no-optimize *hl++|*hl-- = N + inc hl + ld [hl], HIGH(PAL_MONOCHROME_LIGHT) +endc + jp PopBCDEHL + +BillsPC_HideCursorAndMode: + call BillsPC_HideCursor + ; fallthrough +BillsPC_HideModeIcon: + ld hl, wVirtualOAMSprite09 + ld bc, 20 + xor a + rst ByteFill + ret + +BillsPC_HideCursor: + ld hl, wVirtualOAM + ld bc, 36 + xor a + rst ByteFill + ret + +BillsPC_UpdateCursorLocation: + push hl + push de + push bc + ldh a, [rLY] + cp $60 + call nc, DelayFrame + ld hl, wVirtualOAMSprite30 + ld de, wStringBuffer3 + ld bc, 8 + rst CopyBytes + farcall PlaySpriteAnimations + ld hl, wStringBuffer3 + ld de, wVirtualOAMSprite30 + ld bc, 8 + rst CopyBytes + jp PopBCDEHL + +BillsPC_GetCursorHeldSlot: +; Returns current held box+slot to slot bc. Returns z if nothing is held. + ld a, [wBillsPC_CursorHeldBox] + ld b, a + ld a, [wBillsPC_CursorHeldSlot] + ld c, a + and a + ret + +BillsPC_GetCursorSlot: +; Converts cursor position to slot bc. Returns c if hovering on box name. +; b is 0 for party, 1-15 for box, c is 1-20 for slot, 0 for boxname. +; If b is 0 and c is -1, the cursor is on the bag. + ld c, 0 + ld a, [wCurBox] + inc a + ld b, a + ld a, [wBillsPC_CursorPos] + sub $10 + ret c + + ld b, a + and $f + ; Column 0-1 is party + cp 2 + jr c, .party + + ; Otherwise we're checking storage + ; With existing $yx row 0-4 col 2-5, we want to get y*4+x-1. + ld c, a + ld a, b + swap a + and $f + add a + add a + add c + dec a + ld c, a + ld a, [wCurBox] + inc a + ld b, a + ret +.party + ; With existing $yx row 2-4 col 0-1, we want to get y*2+x-3. + ld c, a + ld a, b + swap a + and $f + add a + add c + sub 3 + ld c, a + ld b, 0 + ret nz + + ; If the result was c=0, the cursor is on the bag, so return c=-1 instead. + ld c, -1 + ret + +BillsPC_Withdraw: + ld b, 0 + jr MoveCurMonToBox + +BillsPC_Deposit: + ld a, [wCurBox] + inc a + ld b, a + ; fallthrough +MoveCurMonToBox: + push bc + call BillsPC_GetCursorSlot + ld d, b + ld e, c + pop bc + ld c, 0 + call BillsPC_SwapStorage + ret nz ; failed + + ; Perform movement animation. + ld c, a + push de + ld d, b + ld e, c + pop bc + push bc + call BillsPC_PerformQuickAnim + pop bc + ; fallthrough +CheckPartyShift: +; Shifts entries around to ensure there are no blank party entries. +; This is a purely graphical effect, internal PC functions has already +; taken care of any blank spots data-wise. + xor a + ld e, a + ld d, a + ld b, a +.outer_loop + ld a, e + inc e + cp PARTY_LENGTH - 1 + ret z + call .CheckBlankIcon + jr nz, .outer_loop + ld c, e +.inner_loop + ld a, c + inc c + cp PARTY_LENGTH + ret z + call .CheckBlankIcon + jr z, .inner_loop + + ; Found icon to swap + push de + push bc + call BillsPC_PerformQuickAnim + pop bc + pop de + jr .outer_loop + +.CheckBlankIcon: + ; a = [wBillsPC_PartyList + a * 2] + add a + add LOW(wBillsPC_PartyList) + ld l, a + adc HIGH(wBillsPC_PartyList) + sub l + ld h, a + ld a, [hl] + and a + ret + +GetCursorMon: +; Prints data about Pokémon at cursor if nothing is held (underline to force). +; Returns z if cursor is on an empty pkmn slot. + ; Only handle box arrows if we're holding a mon + call BillsPC_GetCursorHeldSlot + bit 7, b + jr nz, _GetCursorMon + inc c + dec c + jr z, _GetCursorMon + + call BillsPC_UpdateCursorLocation + ; fallthrough +BillsPC_SetBoxArrows: + ld a, [wBillsPC_CursorPos] + cp $10 + jr c, .box_cursors + + ; Clear box switch arrows. + ld a, " " + hlcoord 8, 5 + ld [hl], a + hlcoord 18, 5 + ld [hl], a + xor a + ret + +.box_cursors + hlcoord 8, 5 + ld [hl], "◀" + hlcoord 18, 5 + ld [hl], "▶" + ret + +_GetCursorMon: + call BillsPC_UpdateCursorLocation + + ; Check if cursor is currently hovering over a mon. + call BillsPC_GetCursorSlot + jr c, .clear + ld a, c + inc a + or b + jr z, .clear + + call GetStorageBoxMon + jr nz, .not_clear + ld a, -1 + ld [wVirtualOAMSprite30], a + ; fallthrough +.clear + ; Clear existing data + + ; Clear nickname+species+icon. Leave 3rd row (held item) alone. + hlcoord 7, 0 + lb bc, 2, 13 + call ClearBox + hlcoord 7, 3 + lb bc, 1, 13 + call ClearBox + + ; Clear pokepic + level/gender + hlcoord 0, 0 + lb bc, 9, 7 + call ClearBox + call BillsPC_SetBoxArrows + ld a, [wBillsPC_CursorPos] + cp $10 + jr c, .reset_item + cp $21 + jr z, .reset_item + xor a + ret +.reset_item + ld a, -1 + ld [wVirtualOAMSprite30], a + or 1 + ret + +.not_clear + ; Prepare frontpic. Split into decompression + loading to make sure we + ; refresh the pokepic and the palette in a single frame (decompression + ; is unpredictable, but bpp copy can be relied upon). + ld a, [wTempMonSpecies] + ld hl, wTempMonForm + ld de, vTiles2 + push de + push af + predef GetVariant + ld a, [wTempMonIsEgg] + ld d, a + pop af + bit MON_IS_EGG_F, d + jr z, .not_egg + ld a, EGG +.not_egg + ld [wCurPartySpecies], a + ld [wCurSpecies], a + call GetBaseData + pop de + farcall PrepareFrontpic + + push hl + ld a, "@" + ld [wStringBuffer2], a + call GetMonItemUnlessCursor + jr z, .delay_loop + ld [wNamedObjectIndexBuffer], a + call GetItemName + ld hl, wStringBuffer1 + ld de, wStringBuffer2 + ld bc, ITEM_NAME_LENGTH + rst CopyBytes + +.delay_loop + ; Delay first before finishing frontpic. Retry if it puts us too late. + ; If we try to proceed otherwise, we might run past the hblank interrupt + ; window with GetPreparedFrontpic. + call DelayFrame + ldh a, [rLY] + cp $13 + jr nc, .delay_loop + + ld a, [wAttrMap] + and VRAM_BANK_1 + pop hl + push af + ld a, 0 + jr nz, .dont_switch_vbk + ld a, 1 + ldh [rVBK], a +.dont_switch_vbk + farcall GetPreparedFrontpic + xor a + ldh [rVBK], a + ld hl, wBillsPC_ItemVWF + ld bc, 10 tiles + xor a + push hl + rst ByteFill + pop hl + ld de, wStringBuffer1 + call GetMonItemUnlessCursor + push af + call nz, PlaceVWFString + call DelayFrame + + ld a, 1 + ldh [rVBK], a + ld hl, vTiles5 tile $31 + ld de, wBillsPC_ItemVWF + ld c, 10 + call Get2bpp + pop af + and a + ld de, vTiles3 tile $00 + jr z, .got_item_tile + ld d, a + call ItemIsMail + ld de, vTiles3 tile $1c + jr c, .got_item_tile + ld de, vTiles3 tile $1d +.got_item_tile + ld hl, vTiles3 tile $20 + ld c, 1 + call Get2bpp + xor a + ldh [rVBK], a + + pop af + ld a, 2 + jr nz, .got_new_tile_bank + ld a, 2 | VRAM_BANK_1 +.got_new_tile_bank + hlcoord 0, 0, wAttrMap + lb bc, 7, 7 + call FillBoxWithByte + + ; Colors + ld bc, wTempMonPersonality + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld a, EGG + jr nz, .egg + ld a, [wTempMonSpecies] +.egg + farcall GetMonNormalOrShinyPalettePointer + ld de, wBillsPC_PokepicPal + push de + ld b, 4 +.loop + ld a, BANK(PokemonPalettes) + call GetFarByte + inc hl + ld [de], a + inc de + dec b + jr nz, .loop + + pop hl + farcall VaryBGPalByTempMonDVs + + ; Show or hide item icon + ld hl, wVirtualOAMSprite30 + call GetMonItemUnlessCursor + ld [hl], -1 + jr z, .item_icon_done + ld a, 40 + ld [hli], a + ld a, 72 + ld [hli], a + ld a, $20 + ld [hli], a + ld [hl], VRAM_BANK_1 +.item_icon_done + + ld b, 0 + call SafeCopyTilemapAtOnce + + ; Clear text + call .clear + + ; Poképic tilemap + hlcoord 0, 0 + farcall PlaceFrontpicAtHL + + ; Nickname + hlcoord 8, 0 + ld de, wTempMonNickname + call PlaceString + + ; If we're dealing with an egg, we're done now. + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ret nz + + ; Species name + ld a, [wTempMonSpecies] + ld [wNamedObjectIndexBuffer], a + hlcoord 8, 1 + ld a, "/" + ld [hli], a + call GetPokemonName + ld de, wStringBuffer1 + call PlaceString + + ; Level + hlcoord 0, 8 + call PrintLevel + + ; Gender + ld a, TEMPMON + ld [wMonType], a + farcall GetGender + hlcoord 4, 8 + jr c, .genderless + ld a, $41 + jr nz, .male + ; female + inc a +.male + ld [hl], a +.genderless + + ; Shiny + push hl + farcall GetShininess + pop hl + inc hl + jr z, .not_shiny + ld [hl], $43 +.not_shiny + ld a, [wTempMonPokerusStatus] + and a + inc hl + jr z, .did_pokerus + ; TODO: smiley face if cured (use shiny color + custom color 3?) + ld [hl], "." + and $f + jr z, .did_pokerus + ld [hl], $40 ; Rs +.did_pokerus + + ; Item + ld c, 2 + hlcoord 10, 3 + ld a, $31 +.item_loop_begin + ld b, 10 +.item_loop + ld [hli], a + inc a + dec b + jr nz, .item_loop + hlcoord 10, 2 + dec c + jr nz, .item_loop_begin +.ret_nz + or 1 + ret + +BillsPC_CheckBagDisplay: +; Returns z if we should display the bag. + ; Always display it if the cursor is hovering it. + ld a, [wBillsPC_CursorPos] + cp $21 + ret z + ; fallthrough +_BillsPC_CheckBagDisplay: + call BillsPC_IsHoldingItem + jr z, .check_cursor_mode + xor a + ret + +.check_cursor_mode + ; Always display it in Item Mode. + ld a, [wBillsPC_CursorMode] + cp PC_ITEM_MODE + ret + +ManageBoxes: +; Main box management function +.loop + call BillsPC_UpdateCursorLocation + call DelayFrame + call JoyTextDelay +.redo_input + ldh a, [hJoyPressed] + ld hl, wInputFlags + rrca + jr c, .pressed_a + rrca + jp c, .pressed_b + rrca + jp c, .pressed_select + rrca + jp c, .pressed_start + rrca + jp c, .pressed_right + rrca + jp c, .pressed_left + rrca + jp c, .pressed_up + rrca + jp c, .pressed_down + jr .loop +.pressed_a + ; If we're holding a mon, try to place it in the current cursor location. + ld a, [wBillsPC_CursorHeldSlot] + and a + jr z, .nothing_held_a + call BillsPC_PlaceHeldMon ; might not do anything + jr .loop + +.nothing_held_a + ; Check if this slot is empty. + call GetCursorMon + jr z, .loop + + ; In item mode, if we're on a mon, it must be holding an item. + ld a, [wBillsPC_CursorMode] + cp PC_ITEM_MODE + jr nz, .confirm_ok + ld a, [wBillsPC_CursorPos] + cp $10 ; If below this, we're on boxname. + jr c, .confirm_ok + cp $21 ; Bag location. + jr z, .confirm_ok + ld a, [wTempMonItem] + and a + jr z, .loop + +.confirm_ok + ld de, SFX_READ_TEXT_2 + call PlaySFX + + ; check if we're on top row (hovering over box name) + ld a, [wBillsPC_CursorPos] + cp $10 + ld hl, .BoxMenu + jr c, .got_menu + + ; If we're in PC_MENU_MODE, open a menu. + ld a, [wBillsPC_CursorMode] + and a ; PC_MENU_MODE? + jr z, .prepare_menu + + ; Otherwise, either pick the mon up in PC_SWAP_MODE... + dec a + push af + call z, BillsPC_Switch + pop af + + ; ...or pick up the item in PC_ITEM_MODE. + call nz, BillsPC_MoveItem + jr .loop + +.prepare_menu + ; check if we're in party or storage + ld a, [wBillsPC_CursorPos] + and $f + cp $2 + ld hl, .PartyMonMenu + jr c, .got_menu + + ; hide the cursor + call BillsPC_HideCursor + ld hl, .StorageMonMenu +.got_menu + ld b, 1 + call BillsPC_Menu + jp .loop + +.pressed_b + ; If we're holding a mon, abort the selection. + ld a, [wBillsPC_CursorHeldSlot] + and a + jr z, .nothing_held_b + call BillsPC_AbortSelection + jp .loop + +.nothing_held_b + ; Prompt if we want to exit Box operations or not. + call BillsPC_HideCursorAndMode + ld hl, .ContinueBoxUse + call MenuTextbox + call YesNoBox + push af + call BillsPC_UpdateCursorLocation + call CloseWindow + pop af + ret c + jp .loop + +.pressed_select + ; Don't allow modeswitch if cursor is on the pack. + ld a, [wBillsPC_CursorPos] + cp $21 + jp z, .loop + + ; Don't allow modeswitch from/to PC_ITEM_MODE if holding something. + ld a, [wBillsPC_CursorHeldSlot] + and a + ld a, [wBillsPC_CursorMode] + jr z, .not_holding_anything + cp PC_ITEM_MODE + jp z, .loop + xor PC_MENU_MODE ^ PC_SWAP_MODE + jr .got_new_mode +.not_holding_anything + inc a + cp NUM_PC_MODES + jr nz, .got_new_mode + xor a ; PC_MENU_MODE +.got_new_mode + call BillsPC_SetCursorMode + jp .loop + +.pressed_right + ld a, [wBillsPC_CursorPos] + cp $10 + jr nc, .regular_right + ld a, [wCurBox] + inc a + jr .new_box + +.regular_right + ; Move right, wrapping around + inc a ; 6 columns, CursorPosValid fixes up final column 6+ + and LOW(~$8) + jr .new_cursor_pos + +.pressed_left + ld a, [wBillsPC_CursorPos] + cp $10 + jr nc, .regular_left + ld a, [wCurBox] + add NUM_BOXES - 1 + ; fallthrough +.new_box + cp NUM_BOXES + jr c, .valid_box + sub NUM_BOXES +.valid_box + ld [wCurBox], a + call BillsPC_RefreshTheme + call DelayFrame ; Avoid screen tearing + call BillsPC_PrintBoxName + ld b, 0 + call SafeCopyTilemapAtOnce + xor a + ldh [hBGMapMode], a + inc a + ldh [rVBK], a + call SetBoxIcons + xor a + ldh [rVBK], a + inc a + ldh [hBGMapMode], a + jp .loop + +.regular_left + ; Move left, wrapping around + or $8 ; 6 columns, CursorPosValid fixes up final column 6+ + dec a + and LOW(~$8) + jr .new_cursor_pos + +.pressed_start + ; Cursor jumps to the box name + ld a, [wBillsPC_CursorPos] + and $f + ; If we were at the party area, set to first boxmon column. + cp 2 + jr nc, .new_cursor_pos + ld a, 2 + jr .new_cursor_pos + +.pressed_up + ld a, [wBillsPC_CursorPos] + sub $10 + jr .new_cursor_pos +.pressed_down + ld a, [wBillsPC_CursorPos] + add $10 + ; fallthrough +.new_cursor_pos + ld [wBillsPC_CursorPos], a + call BillsPC_CursorPosValid + jp nz, .redo_input + call GetCursorMon + jp .loop + +.ContinueBoxUse: + text "Continue Box" + line "operations?" + done + +.StorageMonMenu: + db $40 ; flags + db 02, 09 ; start coords + db 17, 19 ; end coords + dw .StorageMenuData2 + db 1 ; default option + +.StorageMenuData2: + db $20 ; flags + db 0 ; items + dw .storageitems + dw PlaceMenuStrings + dw BillsPC_MenuStrings + +.PartyMonMenu: + db $40 ; flags + db 02, 10 ; start coords + db 17, 19 ; end coords + dw .PartyMenuData2 + db 1 ; default option + +.PartyMenuData2: + db $20 ; flags + db 0 ; items + dw .partyitems + dw PlaceMenuStrings + dw BillsPC_MenuStrings + +.BoxMenu: + db $40 ; flags + db 08, 10 ; start coords + db 17, 19 ; end coords + dw .BoxMenuData2 + db 1 ; default option + +.BoxMenuData2: + db $20 ; flags + db 0 ; items + dw .boxitems + dw PlaceMenuStrings + dw BillsPC_MenuStrings + +.storageitems + db 7 + db BOXMENU_WITHDRAW + db BOXMENU_STATS + db BOXMENU_SWITCH + db BOXMENU_MOVES + db BOXMENU_ITEM + db BOXMENU_RELEASE + db BOXMENU_CANCEL + db -1 + +.partyitems + db 7 + db BOXMENU_DEPOSIT + db BOXMENU_STATS + db BOXMENU_SWITCH + db BOXMENU_MOVES + db BOXMENU_ITEM + db BOXMENU_RELEASE + db BOXMENU_CANCEL + db -1 + +.boxitems + db 4 + db BOXMENU_RENAME + db BOXMENU_THEME + db BOXMENU_RELEASEALL + db BOXMENU_CANCEL + db -1 + +BillsPC_MenuStrings: + db "Cancel@" + ; pokémon management options + db "Withdraw@" + db "Deposit@" + db "Stats@" + db "Switch@" + db "Moves@" + db "Item@" + db "Release@" + ; box options + db "Rename@" + db "Theme@" + db "Release@" + ; holding a mail + db "Take@" + db "Read@" + ; holding an item + db "Move@" + db "Bag@" + ; doesn't hold an item + db "Give@" + +BillsPC_MenuJumptable: + dw DoNothing + dw BillsPC_Withdraw + dw BillsPC_Deposit + dw BillsPC_Stats + dw BillsPC_Switch + dw BillsPC_Moves + dw BillsPC_Item + dw BillsPC_Release + dw BillsPC_Rename + dw BillsPC_Theme + dw BillsPC_ReleaseAll + dw BillsPC_TakeMail + dw BillsPC_ReadMail + dw BillsPC_MoveItem + dw BillsPC_BagItem + dw BillsPC_GiveItem + +BillsPC_Stats: + call BillsPC_PrepareTransistion + farcall _OpenPartyStats + jp BillsPC_ReturnFromTransistion + +BillsPC_CursorPick1: +; Plays the first part of the cursor pickup animation + ld hl, wBillsPC_CursorAnimFlag + ld a, [hl] + cp PCANIM_ANIMATE / 2 + 1 + ld a, PCANIM_PICKUP + 1 + sbc 0 + ld [hl], a + ld [wBillsPC_CursorAnimFlag], a +.pick_loop + call BillsPC_UpdateCursorLocation + call DelayFrame + ld a, [hl] + cp PCANIM_PICKUP_NEXT + ret z + inc [hl] + jr .pick_loop + +BillsPC_CursorPick2: +; Plays the second part of the cursor pickup animation. Stops at regular bop. +; Just write PCANIM_STATIC to [hl] afterwards if this isn't what you want. + ld hl, wBillsPC_CursorAnimFlag + + ; Skip first delay since we already did one at the end of CursorPick1. + jr .start_loop +.pick_loop2 + call BillsPC_UpdateCursorLocation + call DelayFrame +.start_loop + dec [hl] + ld a, [hl] + cp PCANIM_PICKUP + jr nc, .pick_loop2 + ret ; [hl] is now PCANIM_ANIMATE. + +BillsPC_SetIcon: +; Writes icon tiles to hl depending on species data in de. Assumes vbk1. + ld a, [de] + inc de + ld [wCurIcon], a + ld a, [de] + ld [wCurIconForm], a + push hl + call BillsPC_SetPals + call DelayFrame + pop hl + farjp GetStorageIcon + +BillsPC_MoveIconData: +; Copies icon data from slot bc to slot de, then blanks slot bc. +; Box -1 is a sentinel for held (slot 0) or quick (slot 1). +; TODO: can we make this code (.GetAddr especially) less messy? + ; Copy palette data + ldh a, [rSVBK] + push af + ld a, BANK(wOBPals1) + ldh [rSVBK], a + xor a + ldh [hBGMapMode], a + + ; Copy palette data + call .Copy + pop af + ldh [rSVBK], a + + ld a, 1 + ldh [rVBK], a + + ; Handle held items seperately from this point. + call BillsPC_IsHoldingItem + jr z, .not_holding_item + + ; Check if we're loading or unloading the icon + ld a, [wBillsPC_QuickFrames] + and a + ld de, vTiles3 tile $00 ; Blank. + jr z, .got_item_tile + cp PCANIM_QUICKFRAMES - 1 + jr nz, .quick_ok + ld a, b + inc a + ld de, vTiles3 tile $20 ; Item for mon cursor is hovering + jr nz, .got_item_tile + ld de, vTiles3 tile $10 ; Item cursor is holding. +.got_item_tile + ld hl, vTiles3 tile $14 ; Quick tile. + push bc + ld c, 1 + call BillsPC_SafeGet2bpp + call BillsPC_SetPals + pop bc + +.quick_ok + ; Check if we should blank the cursor tile. + inc b + ld a, c + or b + ld hl, vTiles3 tile $10 + ld a, 1 + call z, BillsPC_BlankTiles + jr .done + +.not_holding_item + ; Copy extspecies data + ld a, 1 + call .Copy + + ; Set new icon data unless we're only blanking. + ld a, d + and e + inc a + jr z, .blank_old + + push bc + ld b, d + ld c, e + ld a, 1 + call .GetAddr + push hl + ld a, 2 + call .GetAddr + pop de + call BillsPC_SetIcon + pop bc + +.blank_old + ; Blank old icon data. + ld a, 1 + call .GetAddr + xor a + ld [hli], a + ld [hli], a + ld a, 2 + call .GetAddr + ld a, 1 + call BillsPC_BlankTiles + +.done + xor a + ldh [rVBK], a + inc a + ldh [hBGMapMode], a + ret + +.Copy: +; Copies from address depending on bc and a to addr depending on de and a. + ; Check if we're blanking. + push af + ld a, d + and e + inc a + jr nz, .not_blanking + pop af + ret + +.not_blanking + pop af + call .GetAddr + push bc + push de + push hl + ld b, d + ld c, e + call .GetAddr + ld d, h + ld e, l + pop hl + and a + ld bc, 2 + jr nz, .got_len + + call BillsPC_IsHoldingItem + ld c, 4 + jr z, .got_len + + ; If holding an item, just copy icon pal to quickmove. + ld hl, wOBPals1 palette $0 + 2 + ld de, wOBPals1 palette $5 + 2 +.got_len + rst CopyBytes + pop de + pop bc + ret + +.GetAddr: +; Depending on a, set hl to an address based on box b slot c. + push bc + push af + inc b + jr z, .held + dec b + jr z, .party + + ; Box + and a + jr z, .box_party_pal + dec a + jr z, .box_extspecies + + ; Boxmon tile + ld hl, vTiles4 tile $18 + jr .get_tile_addr + +.box_extspecies + ld hl, wBillsPC_BoxList + jr .get_ext_addr + +.party + and a + jr z, .box_party_pal + dec a + jr z, .party_extspecies + + ; Party tile + ld hl, vTiles4 tile $00 + ; fallthrough +.get_tile_addr + ld b, 4 tiles + jr .addntimes + +.box_party_pal + call BillsPC_GetMonPalAddr + jr .got_addr + +.party_extspecies + ld hl, wBillsPC_PartyList + ; fallthrough +.get_ext_addr + ld b, 2 + ; fallthrough +.addntimes + ld a, c + ld c, b + ld b, 0 + dec a + rst AddNTimes + jr .got_addr + +.held + and a + jr z, .held_pal + dec a + jr z, .held_extspecies + + ; Held tile + ld hl, vTiles3 tile $14 + dec c + jr z, .got_addr + ld hl, vTiles3 tile $08 + jr .got_addr + +.held_pal + ld hl, wOBPals1 palette $5 + 2 + dec c + jr z, .got_addr + ld hl, wOBPals1 palette PAL_MINI_ICON + 2 + jr .got_addr + +.held_extspecies + ld hl, wBillsPC_QuickIcon + dec c + jr z, .got_addr + ld hl, wBillsPC_HeldIcon + ; fallthrough +.got_addr + pop af + pop bc + ret + +BillsPC_Switch: +; Pick up mon for movement. + ; Mark current cursor slot for movement. + call BillsPC_GetCursorSlot + ld a, b + ld [wBillsPC_CursorHeldBox], a + ld a, c + ld [wBillsPC_CursorHeldSlot], a + + push bc + call BillsPC_CursorPick1 + pop bc + + ; Update pal for the cursor mini, in case we pick it up. + lb de, -1, 0 + call BillsPC_MoveIconData + + call BillsPC_CursorPick2 + ld [hl], PCANIM_STATIC + ret + +BillsPC_PrepareQuickAnim: +; Sets up a quick-move animation from bc to de. + ld hl, wBillsPC_QuickFrom + push bc + push de + call .SetQuickStruct + pop bc + ld hl, wBillsPC_QuickTo + call .SetQuickStruct + ld a, PCANIM_QUICKFRAMES + ld [wBillsPC_QuickFrames], a + + lb de, 0, 0 + ld a, SPRITE_ANIM_INDEX_PC_QUICK + call _InitSpriteAnimStruct + + call BillsPC_UpdateCursorLocation + pop bc + lb de, -1, 1 + jp BillsPC_MoveIconData + +.SetQuickStruct: + ld a, b + ld [hli], a + ld a, c + ld [hli], a + push bc + call BillsPC_GetXYFromStorageBox + pop bc + + ; If we're dealing with a held item, we need to offset XY slightly. + + ; The cursor slot uses a different Y offset. + inc b + ld b, 0 + jr nz, .not_cursor + ld b, 2 +.not_cursor + call BillsPC_IsHoldingItem + ld c, 4 + jr nz, .got_offset + lb bc, 0, 0 +.got_offset + ld a, d + add c + ld [hli], a + ld a, e + add b + add c + ld [hli], a + ret + +BillsPC_GetXYFromStorageBox: +; Returns appropriate icon XY pos in de from storage box b, slot c. Includes +; a +8 pixel offset as per normal sprite positioning. +; Box -1 means held by cursor. + inc b + jr nz, .not_cursor + + ; Cursor held mons are just offset upwards a bit. + dec b + push bc + call BillsPC_GetCursorSlot + call BillsPC_GetXYFromStorageBox + pop bc + ld a, e + sub PCANIM_PICKUP_NEXT - PCANIM_PICKUP + 1 + ld e, a + ret + +.not_cursor + res 7, b + dec b + jr z, .party + ld a, c + and a + jr nz, .not_on_boxname + ; fallthrough +.boxname_pos + lb de, $6c, $38 + ret + +.not_on_boxname + ; We're dealing with a boxmon. If this isn't part of the current Box, return + ; boxname position since we want to simulate moving the mon towards another + ; Box. + ld a, [wCurBox] + inc a + cp b + jr nz, .boxname_pos + + ; The position is within our current Box. + ld a, 4 + lb de, $48, $48 + jr .fix_xy + +.party + ld a, c + inc a + lb de, $30, $58 + ret z + ld a, 2 + lb de, $10, $68 + ; fallthrough +.fix_xy + ; Fix xy depending on slot c. For every a slots, y changes by 16. Then, + ; for the remainder, x changes by 24. + push bc + ld b, a + ld a, c + dec a +.loop + sub b + jr c, .got_y + push af + ld a, e + add 16 + ld e, a + pop af + jr .loop +.got_y + add b + + ; Multiply remainder by 24. + add a ; * 2 + add a ; * 4 + add a ; * 8 + ld b, a + add b ; * 16 + add b ; * 24 + add d + ld d, a + pop bc + ret + +BillsPC_PerformQuickAnim: +; Performs a synchronous quickmove animation. Used when aborting a selection or +; when doing a party shift (otherwise it's asynchronous). + call BillsPC_PrepareQuickAnim +.loop + call BillsPC_UpdateCursorLocation + call DelayFrame + ld a, [wBillsPC_QuickFrames] + and a + jr nz, .loop + jp BillsPC_UpdateCursorLocation + +BillsPC_FinishQuickAnim: +; Called from sprite anim code. + push hl + push de + push bc + + ; Verify that the destination was either the party or the current Box. + ; If the destination is another Box, just vanish the sprite. + ld a, [wBillsPC_QuickToSlot] + ld e, a + ld a, [wBillsPC_QuickToBox] + ld d, a + and a + jr z, .ok + ld a, [wCurBox] + inc a + cp d +.ok + lb bc, -1, 1 + call z, BillsPC_MoveIconData + + ; Blank the icon. MoveIconData might have done this already, but this makes + ; sure it's handled in case we never ran that function. + ldh a, [rVBK] + ld b, a + ldh a, [hBGMapMode] + ld c, a + push bc + xor a + ldh [hBGMapMode], a + inc a + ldh [rVBK], a + ld hl, vTiles3 tile $14 + call BillsPC_BlankTiles + pop bc + ld a, b + ldh [rVBK], a + ld a, c + ldh [hBGMapMode], a + jp PopBCDEHL + +BillsPC_AbortSelection: +; Deselects the mon currently held, moving it to where it was prior. + ld a, 1 + ldh [rVBK], a + + ; If we're dealing with an item, we don't need to reload any tiles. + call BillsPC_GetCursorHeldSlot + bit 7, b + push bc + call nz, BillsPC_BlankCursorItem + pop bc + + ; Ensure that the icon is returned, if in party/current Box. + ld d, b + ld e, c + lb bc, -1, 0 + call BillsPC_PerformQuickAnim + + xor a + ld [wBillsPC_CursorHeldBox], a + ld [wBillsPC_CursorHeldSlot], a + + ; We might need to move the cursor elsewhere if this removes the bag icon. + call BillsPC_MaybeMoveCursor + ld a, PCANIM_ANIMATE + ld [wBillsPC_CursorAnimFlag], a + + xor a + ldh [rVBK], a + jp GetCursorMon + +BillsPC_MaybeMoveCursor: +; If the cursor is on the bag, and the bag is no longer there, move it. +; Returns z if we moved the cursor. + ld a, [wBillsPC_CursorPos] + cp $21 + ret nz + + call _BillsPC_CheckBagDisplay + jr nz, .move_cursor + or 1 + ret + +.move_cursor + ld a, $31 ; Move to right below it. + ld [wBillsPC_CursorPos], a + xor a + ret + +BillsPC_PrepareTransistion: +; Prepares for a screen transistion. + ; Save the content of the current screen. + call LoadStandardMenuHeader + + ; After clearing palettes, we need to busyloop. We can't just DelayFrame, + ; because in case vblank occurs after the ClearPalettes but before + ; DelayFrame, we end up delaying twice, causing hblank to overwrite the + ; palette clear. + call ClearPalettes +.busyloop + ldh a, [hCGBPalUpdate] + and a + jr nz, .busyloop + + ; Disable hblank interrupt. + ld hl, rIE + res LCD_STAT, [hl] + + jp ClearSprites + +BillsPC_ReturnFromTransistion: + call ExitMenu + jp BillsPC_RestoreUI + +BillsPC_Moves: + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld hl, .CantCheckEggMoves + jp nz, BillsPC_PrintText + call BillsPC_PrepareTransistion + farcall _ManagePokemonMoves + jp BillsPC_ReturnFromTransistion + +.CantCheckEggMoves: + text "You can't check" + line "an Egg's moves!" + prompt + +BillsPC_GetStorageSpace: +; Forces game save until we have at least a free pokedb entries left. +; Returns nz if we abort the prompt with insufficient storage space left. + ld b, a +.loop + ld a, b + push bc + farcall EnsureStorageSpace + pop bc + ret z + + push bc + ld hl, BillsPC_MustSaveToContinue + call MenuTextbox + call YesNoBox + push af + jr c, .menutext_abort + farcall ForceGameSave + ld hl, BillsPC_GameSaved + call PrintText + ; fallthrough +.menutext_abort + call BillsPC_UpdateCursorLocation + call CloseWindow + pop af + pop bc + jr nc, .loop + or 1 + ret + +BillsPC_GiveItem: + ; If we're dealing with a Box mon, we must have at least 1 free pokedb + ; entry. + call BillsPC_GetCursorSlot + ld a, b + and a + jr z, .entries_not_full + + ld a, 1 + call BillsPC_GetStorageSpace + ret nz + +.entries_not_full + call BillsPC_PrepareTransistion + farcall PCGiveItem + jp BillsPC_ReturnFromTransistion + +GetMonItemUnlessCursor: +; Returns mon item unless the cursor is holding it. Returns z if cursor held. + push de + push bc + call .do_it + pop bc + pop de + ld a, 0 + ret z + ld a, [wTempMonItem] + and a + ret + +.do_it + ; Figure out if we're drawing to cursor item or general held item. + call BillsPC_GetCursorFromTo + + ; d is $80 | b if it's the same box + cursor is holding an item. + ld a, d + sub b + xor $80 + ret nz + ld a, e + cp c + ret + +BillsPC_BlankCursorItem: +; Blanks cursor item and swap icon. Assumes vbk1. + ; Remove held item icon. + ld a, -1 + ld [wVirtualOAMSprite31], a + + ; Blank cursor item name. Only uses 10 tiles, but this is ok. + ld hl, vTiles5 tile $3b + ld a, 3 + jp BillsPC_BlankTiles + +BillsPC_IsHoldingItem: +; Returns nz if we're holding an item. + push bc + call BillsPC_GetCursorHeldSlot + bit 7, b + pop bc + ret + +BillsPC_TakeMail: +; Returns carry if mail is taken. + ld a, [wTempMonSlot] + dec a + ld [wCurPartyMon], a + call BillsPC_HideCursorAndMode + farcall TakeMail + + ; Preserve return flags. + push af + call GetCursorMon + pop af + ret + +BillsPC_ReadMail: + ld a, [wTempMonSlot] + dec a + ld [wCurPartyMon], a + call BillsPC_PrepareTransistion + farcall ReadPartyMonMail + jp BillsPC_ReturnFromTransistion + +BillsPC_MoveItem: +; Pick up item for movement. + ; Check if the cursor is on the pack. + call BillsPC_GetCursorSlot + ld a, c + inc a + or b + jr nz, .not_on_pack + + call BillsPC_PrepareTransistion + farcall PCPickItem + push af + call BillsPC_ReturnFromTransistion + pop af + ret z + + ; Reload the item VWF string. + ld a, [wCurItem] + ld [wNamedObjectIndexBuffer], a + ld [wBillsPC_CursorItem], a + call GetItemName + + ld hl, wBillsPC_ItemVWF + ld bc, 10 tiles + xor a + push hl + rst ByteFill + pop hl + ld de, wStringBuffer1 + call PlaceVWFString + lb bc, 0, -1 + jr .got_cursor_item + +.not_on_pack + ; Removing items might reallocate a storage mon, so check that we have space + ; for that in the database. + and b + jr z, .entries_not_full + + ld a, 1 + push bc + call BillsPC_GetStorageSpace + pop bc + ret nz + +.entries_not_full + ; Mark current cursor slot for movement. + farcall GetStorageBoxMon + ld a, [wTempMonItem] + ld [wBillsPC_CursorItem], a + ; fallthrough +.got_cursor_item + ld a, b + or $80 ; Mark that we're holding an item rather than a mon. + ld [wBillsPC_CursorHeldBox], a + ld a, c + ld [wBillsPC_CursorHeldSlot], a + + push bc + call BillsPC_CursorPick1 + pop bc + + ld a, 1 + ldh [rVBK], a + dec a + ldh [hBGMapMode], a + + ; Load held item icon + ld hl, wVirtualOAMSprite31 + ld a, 32 + ld [hli], a + ld a, 72 + ld [hli], a + ld a, $06 + ld [hli], a + ld [hl], VRAM_BANK_1 | PAL_CURSOR_MODE2 + + ; Load held item name + ld hl, vTiles5 tile $3b + ld de, wBillsPC_ItemVWF + ld c, 10 + call BillsPC_SafeGet2bpp + + ; Load cursor item icon. + call BillsPC_LoadCursorItemIcon + + xor a + ldh [rVBK], a + inc a + ldh [hBGMapMode], a + + call GetCursorMon + + call BillsPC_CursorPick2 + ld [hl], PCANIM_STATIC + ret + +BillsPC_LoadCursorItemIcon: + ld hl, vTiles3 tile $10 + lb bc, BANK(HeldItemIcons), 1 + + ld a, [wBillsPC_CursorItem] + ld d, a + call ItemIsMail + ld de, HeldItemIcons ; mail icon + jr c, .got_item_tile + ld de, HeldItemIcons tile 1 ; regular item icon +.got_item_tile + jp BillsPC_SafeGet2bpp + +BillsPC_BagItem: + ; If we're dealing with a Box mon, we must have at least 1 free pokedb + ; entry. + ld a, [wTempMonItem] + ld b, a + push bc + call BillsPC_GetCursorSlot + call _BillsPC_BagItem + pop bc + ret nz + ld a, b + ld [wNamedObjectIndexBuffer], a + call GetItemName + ld hl, BillsPC_MovedToPackText + ; fallthrough +BillsPC_PrintText: + push hl + call BillsPC_HideCursorAndMode + pop hl + call MenuTextbox + call BillsPC_UpdateCursorLocation + jp CloseWindow + +_BillsPC_BagItem: +; Returns z on success. + ld a, b + and a + jr z, .entries_not_full + + ld a, 1 + push bc + call BillsPC_GetStorageSpace + pop bc + ret nz + +.entries_not_full + farcall GetStorageBoxMon + call .do_it + ld a, [wTempMonItem] + and a + ret + +.do_it + ld a, [wTempMonItem] + ld [wCurItem], a + + ; Check if this is a Mail (can be invoked when placing using Item Mode). + ld d, a + call ItemIsMail + jr nc, .put_in_pack + + call BillsPC_TakeMail + push af + ld b, 0 + call SafeCopyTilemapAtOnce + pop af + sbc a + inc a + ret nz + + ; This lets the function know that the removal succeeded. + ld [wTempMonItem], a + ret + +.put_in_pack + ld a, 1 + ld [wItemQuantityChangeBuffer], a + ld hl, wNumItems + call ReceiveItem + ld hl, BillsPC_PackFullText + jr nc, BillsPC_PrintText + xor a + ld [wTempMonItem], a + call BillsPC_UpdateStorage_CheckMewtwo + jp GetCursorMon + +BillsPC_UpdateStorage_CheckMewtwo: +; Updates storage and potentially switches Mewtwo form if item changed. + push hl + push de + push bc + ld a, [wTempMonSpecies] + ld [wCurPartySpecies], a + ld de, wTempMonItem + ld hl, wTempMonForm + ld a, [hl] + ld b, a + push bc + farcall _UpdateMewtwoForm + farcall UpdateStorageBoxMonFromTemp + pop bc + ld a, [wTempMonForm] + cp b + jr z, .done + + ; Check if we should reload the icon. If the mon is in another box, don't + ; bother. + ld a, [wCurBox] + inc a + ld b, a + ld a, [wTempMonBox] + and a + jr z, .update + cp b + jr nz, .done + +.update + ; Reload icon + xor a + ldh [hBGMapMode], a + inc a + ldh [rVBK], a + + ld a, [wTempMonBox] + ld b, a + ld a, [wTempMonSlot] + ld c, a + + call BillsPC_GetMonIconAddr + ld a, [wTempMonSpecies] + ld [hli], a + ld [wCurIcon], a + ld a, [wTempMonForm] + ld [hld], a + ld [wCurIconForm], a + call BillsPC_GetMonTileAddr + push bc + farcall GetStorageIcon + pop bc + call WriteIconPaletteData + + xor a + ldh [rVBK], a + inc a + ldh [hBGMapMode], a +.done + jp PopBCDEHL + +BillsPC_CantPutMailIntoPackText: + text "The Mail would" + line "lose its message." + prompt + +BillsPC_PackFullText: + text "The Bag is full…" + prompt + +BillsPC_MovedToPackText: + text "Moved " + text_ram wStringBuffer1 + text "" + line "to Bag." + prompt + + +BillsPC_Menu: +; hl: menu data header, b: amount of menus to close + inc b + push bc + call LoadMenuHeader + xor a + ld [wWhichIndexSet], a + ldh [hBGMapMode], a ; restored to 1 upon CloseWindow + call DoNthMenu + pop bc + push af + push bc + call BillsPC_UpdateCursorLocation +.closemenu_loop + pop bc + dec b + jr z, .menus_closed + push bc + call ExitMenu + jr .closemenu_loop +.menus_closed + call ApplyTilemap + pop af + ret c + ld a, [wMenuSelection] + ld hl, BillsPC_MenuJumptable + jp JumpTable + +BillsPC_Item: + call BillsPC_HideCursorAndMode + + ; Eggs can't be given items. + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld hl, BillsPC_EggsCantHoldItemsText + jp nz, BillsPC_PrintText + + ; Give a slightly different menu depending on whether the mon is holding + ; an item right now or not and whether or not it's Mail. + ld a, [wTempMonItem] + and a + ld hl, .ItCanHoldAnItem + ld de, .NoItemMenu + jr z, .got_menu + ld d, a + call ItemIsMail + ld hl, .ItemIsSelected + ld de, .ItemMenu + jr nc, .got_menu + ld de, .MailMenu +.got_menu + push de + call MenuTextbox + pop hl + ld b, 2 + jp BillsPC_Menu + +.ItemIsSelected: + text_ram wStringBuffer2 + text " is" + line "selected." + done + +.ItCanHoldAnItem: + text_ram wTempMonNickname + text " can" + line "hold an item." + done + +.MailMenu: + db $40 ; flags + db 03, 11 ; start coords + db 12, 19 ; end coords + dw .MailMenuData + db 1 ; default option + +.MailMenuData: + db $20 ; flags + db 4 ; items + dw .mail + dw PlaceMenuStrings + dw BillsPC_MenuStrings + +.ItemMenu: + db $40 ; flags + db 05, 11 ; start coords + db 12, 19 ; end coords + dw .ItemMenuData + db 1 ; default option + +.ItemMenuData: + db $20 ; flags + db 3 ; items + dw .items + dw PlaceMenuStrings + dw BillsPC_MenuStrings + +.NoItemMenu: + db $40 ; flags + db 07, 11 ; start coords + db 12, 19 ; end coords + dw .NoItemMenuData + db 1 ; default option + +.NoItemMenuData: + db $20 ; flags + db 2 ; items + dw .noitems + dw PlaceMenuStrings + dw BillsPC_MenuStrings + +.mail + db 4 + db BOXMENU_MOVEITEM + db BOXMENU_TAKEMAIL + db BOXMENU_READMAIL + db BOXMENU_CANCEL + db -1 + +.items + db 3 + db BOXMENU_MOVEITEM + db BOXMENU_BAGITEM + db BOXMENU_CANCEL + db -1 + +.noitems + db 2 + db BOXMENU_GIVEITEM + db BOXMENU_CANCEL + db -1 + +BillsPC_EggsCantHoldItemsText: + text "Eggs can't hold" + line "items." + prompt + +BillsPC_CanReleaseMon: +; Verifies if the given mon in box b, slot c, can be released. Sets wTempMon. +; Returns the following in a: +; 0: Can release +; 1: Can't release last healthy mon +; 2: Can't release Egg +; 3: Can't release mon knowing HMs +; 4: Empty slot + ; Is there even anything there? + farcall GetStorageBoxMon + ld a, 4 + jr z, .done + + ; If we're dealing with our party, ensure that this isn't our last mon. + ld a, b + and a + jr nz, .not_last_healthy + ld a, c + dec a + ld [wCurPartyMon], a + push hl + push de + push bc + farcall CheckCurPartyMonFainted + pop bc + pop de + pop hl + ld a, 1 + jr c, .done + ; fallthrough +.not_last_healthy + ; Can't release Eggs. + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld a, 2 + ret nz + + ; Ensure that the mon doesn't know any HMs. + push de + push hl + push bc + ld hl, wTempMonMoves + ld b, NUM_MOVES +.loop + ld a, [hli] + and a + jr z, .hm_check_done + push hl + push bc + ld hl, HMMoves + ld de, 1 + call IsInArray + pop bc + pop hl + ld a, 3 + jr c, .hm_check_done + dec b + jr nz, .loop + xor a +.hm_check_done + pop bc + pop hl + ; fallthrough +.pop_de_done + pop de +.done + and a + ret + +BillsPC_MaybeRespawnBeast: +; Respawns a roaming beast if you're releasing your own beast. + push hl + push de + push bc + ; Check if we are in fact the OT. Doesn't care for the "treat as OT" option + ; because that would be a bit silly in this particular case. + ld hl, wTempMonID + ld a, [wPlayerID] + cp [hl] + jr nz, .done + inc hl + ld a, [wPlayerID + 1] + cp [hl] + jr nz, .done + + ld hl, wTempMonOT + ld de, wPlayerName +.loop + ld a, [de] + cp [hl] + inc de + inc hl + jr nz, .done + cp "@" + jr nz, .loop + + ; This is ours. Check which, if any, beast we should respawn. + ld a, [wTempMonSpecies] + cp RAIKOU + jr nz, .not_raikou + farcall RespawnRoamingRaikou + jr .done +.not_raikou + cp ENTEI + jr nz, .not_entei + farcall RespawnRoamingEntei + jr .done +.not_entei + cp SUICUNE + jr nz, .done + farcall RespawnRoamingSuicune +.done + jp PopBCDEHL + +BillsPC_ReleaseAll: + call BillsPC_HideModeIcon + + ; Double confirmation. + ld hl, .ReallyReleaseBox + call MenuTextbox + call NoYesBox + jr c, .done + + ld hl, .CantRecallReleasedMons + call PrintText + call NoYesBox + jr c, .done + + ; We want to give 3 possible messages: + ; * Nothing was released. You can't release Eggs or PKMN knowing HMs. + ; * There's nothing there! + ; * X PKMN released. + lb de, 0, 0 ; Successful and failed releases. + call BillsPC_GetCursorSlot +.loop + ld a, c + inc c + cp MONS_PER_BOX + jr z, .releases_done + + call BillsPC_CanReleaseMon + jr nz, .failed_release + inc d + push de + call BillsPC_MaybeRespawnBeast + farcall RemoveStorageBoxMon + lb de, -1, -1 + push bc + call BillsPC_MoveIconData + pop bc + pop de + jr .loop +.failed_release + ; Check if there was something there. + cp 4 + jr z, .loop + inc e + jr .loop +.releases_done + ld a, d + ld [wd265], a + or e + ld hl, .NothingThere + jr z, .print + and d + ld hl, .NothingReleased + jr z, .print2 + ld hl, .ReleasedXMon +.print + push de + call PrintText + pop de + ld a, e + and a + ld hl, .TheRestWasnt + jr z, .done +.print2 + call PrintText +.done + call BillsPC_UpdateCursorLocation + jp CloseWindow + +.ReallyReleaseBox: + text "Really release the" + line "entire box?" + done + +.CantRecallReleasedMons: + text "You can't recall" + line "released #mon." + cont "Are you sure?" + done + +.NothingThere: + text "This box is empty." + prompt + +.NothingReleased: + text "You can't release" + line "Eggs or #mon" + cont "with HM moves." + prompt + +.ReleasedXMon: + text "Released " + text_decimal wd265, 1, 2 + text "" + line "#mon." + prompt + +.TheRestWasnt: + text "The rest are Eggs" + line "or know HM moves." + prompt + +BillsPC_Release: + call BillsPC_GetCursorSlot + call BillsPC_CanReleaseMon + ld hl, BillsPC_LastPartyMon + dec a + jr z, .print + ld hl, .CantReleaseEgg + dec a + jr z, .print + ld hl, .CantReleaseHMMons + dec a + jr z, .print + + ; We don't need to check for error 4 (empty slot) since we can't get to this + ; menu in that case. + call BillsPC_HideCursorAndMode + ld hl, .ReallyReleaseMon + call MenuTextbox + call NoYesBox + jr c, .done + + ; Copy mon nick to a string buffer, since SetStorageBoxPointer might + ; mangle wTempMon. + ld hl, wTempMonNickname + ld de, wStringBuffer1 + ld bc, MON_NAME_LENGTH + rst CopyBytes + + ; Then release the mon. + call BillsPC_GetCursorSlot + push bc + call BillsPC_MaybeRespawnBeast + farcall RemoveStorageBoxMon + + ; Print message and reload current cursor mon. + ld hl, .WasReleasedOutside + call PrintText + + call .done + pop bc + lb de, -1, -1 + call BillsPC_MoveIconData + call CheckPartyShift + jp GetCursorMon + +.done + call BillsPC_UpdateCursorLocation + jp CloseWindow + +.print + jp BillsPC_PrintText + +.CantReleaseEgg: + text "You can't release" + line "an Egg!" + prompt + +.CantReleaseHMMons: + text "You can't release" + line " with HM moves!" + prompt + +.ReallyReleaseMon: + text "Really release" + line "" + text_ram wTempMonNickname + text "?" + done + +.WasReleasedOutside: + text "" + text_ram wStringBuffer1 + text " was" + line "released outside." + cont "Bye, " + text_ram wStringBuffer1 + text "!" + prompt + +BillsPC_Rename: + call BillsPC_PrepareTransistion + ld b, 4 + ld de, wStringBuffer2 + farcall NamingScreen + ld hl, wStringBuffer2 + + ; Abort if no name was entered. + ld a, "@" + cp [hl] + jr z, .abort + ld de, wStringBuffer1 + ld bc, BOX_NAME_LENGTH + rst CopyBytes + ld a, [wCurBox] + inc a + ld b, a + farcall SetBoxName +.abort + jp BillsPC_ReturnFromTransistion + +BillsPC_Theme: + call BillsPC_HideCursorAndMode + + call LoadStandardMenuHeader + ld hl, .PickAThemeText + call PrintText + + ld hl, .ThemeMenuDataHeader + call CopyMenuHeader + call InitScrollingMenu + xor a + ld [wMenuScrollPosition], a + call ScrollingMenu + + call BillsPC_UpdateCursorLocation + call CloseWindow + + ld a, [wMenuJoypad] + cp B_BUTTON + jr z, .refresh_theme ; revert back to what it used to be + + ; [sNewBox[wCurBox]Theme] = [wScrollingMenuCursorPosition] + ld a, [wCurBox] + ld b, a + inc b + ld a, [wScrollingMenuCursorPosition] + farcall SetBoxTheme + +.refresh_theme + jp BillsPC_RefreshTheme + +.PickAThemeText: + text "Please" + line "pick a theme." + done + +.ThemeMenuDataHeader: + db $40 ; flags + db 01, 08 ; start coords + db 13, 18 ; end coords + dw .ThemeMenuData2 + db 1 ; default option + +.ThemeMenuData2: + db $30 ; flags + db 6, 0 ; rows, columns + db 1 ; horizontal spacing + dba .ThemeList + dba .GetThemeString + dba NULL + dba .PreviewTheme + +.ThemeList: + db NUM_BILLS_PC_THEMES +for x, 1, NUM_BILLS_PC_THEMES + 1 + db x +endr + db -1 + +.GetThemeString: + ld a, [wMenuSelection] + dec a + push de + ld e, a + ld d, 0 + ld hl, BillsPC_ThemeNames + add hl, de + add hl, de + ld a, [hli] + ld d, [hl] + ld e, a + pop hl + rst PlaceString + ret + +.PreviewTheme: + ld a, 1 + ld [wBillsPC_ApplyThemePals], a + ld a, [wMenuSelection] + cp -1 + jr z, .current_theme + dec a + farjp BillsPC_PreviewTheme +.current_theme + farjp _CGB_BillsPC + +INCLUDE "data/pc/theme_names.asm" + +BillsPC_GetCursorFromTo: +; Returns source (held mon) in de and destination (cursor location) in bc. + call BillsPC_GetCursorHeldSlot + ld d, b + ld e, c + jp BillsPC_GetCursorSlot + +BillsPC_SwapStorage: +; Swaps slots bc and de. Returns z on success with effective slot in a. + call BillsPC_UpdateCursorLocation + push de + push bc + + ; Items are handled seperately. + call BillsPC_IsHoldingItem + jp z, .holding_mon + + ; Check if we're on the pack. + ld a, c + inc a + jr nz, .not_on_pack + + ; Otherwise, move a mon's item there. Do nothing if the item originated from + ; the pack (e = -1). + ld b, d + res 7, b + ld c, e + inc e + call nz, _BillsPC_BagItem + pop bc + pop de + ret + +.not_on_pack + ; Don't do anything if we're hovering over an empty slot or boxname. + dec a + jp z, .abort + farcall GetStorageBoxMon + jp z, .abort + + ; If we're moving to a Box, we might need to verify that we have the db + ; space to do so. Box source has already been verified, so box->party is + ; always safe. This also does the right thing if we're moving from the bag, + ; since it shares box identifier with party. + ld a, b + and a + jr z, .entries_not_full + ld a, d + and $7f + ld a, 1 + jr z, .got_space_req + inc a +.got_space_req + call BillsPC_GetStorageSpace + jp nz, .abort + pop bc + pop de + push de + push bc + ; fallthrough +.entries_not_full + ; Don't allow Eggs to hold items. + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + ld a, 7 + jp nz, .failed + + ; Movement from the bag needs special handling. + ld a, e + inc a + jr nz, .moving_between_mon + + ; Don't allow putting Mail into storage. + ld a, b + and a + jr z, .mail_ok + ld a, [wBillsPC_CursorItem] + ld d, a + call ItemIsMail + ld a, 6 + jp c, .failed + +.mail_ok + ; If the mon in question is already holding an item, we need to verify that + ; we have room for this new item in the bag and that it isn't Mail, which + ; we want to prevent users from accidentally erasing. + ld a, [wTempMonItem] + and a + ld [wCurItem], a + jr z, .dest_is_itemless + + ld d, a + call ItemIsMail + ld a, 8 + jp c, .failed + + ; Try to add the user's current item into the bag. + ld a, 1 + ld [wItemQuantityChangeBuffer], a + ld hl, wNumItems + call ReceiveItem + ld a, 9 + jp nc, .failed + ; fallthrough +.dest_is_itemless + ; Check if we want to compose a message. + ld a, [wBillsPC_CursorItem] + ld [wCurItem], a + ld d, a + call ItemIsMail + jr nc, .compose_check_done + + push af + ld a, [wTempMonSlot] + dec a + ld [wCurPartyMon], a + ld a, [wTempMonSpecies] + ld [wCurPartySpecies], a + call BillsPC_PrepareTransistion + farcall ComposeMailMessage + call BillsPC_ReturnFromTransistion + + ; reload cursor item icon + ld a, 1 + ldh [rVBK], a + call BillsPC_LoadCursorItemIcon + xor a + ldh [rVBK], a + pop af + +.compose_check_done + ld [wTempMonItem], a + ld [wCurItem], a + call BillsPC_UpdateStorage_CheckMewtwo + ld a, 1 + ld [wItemQuantityChangeBuffer], a + ld hl, wNumItems + call TossItem + xor a + jp .done + +.moving_between_mon + ; Throw out the "is item" flag. + ld a, d + and $7f + ld d, a + or b + jr nz, .party_check_done + + ; If both mons are in the party, possibly transfer Mail. + push de + push bc + farcall SwapPartyMonMail + pop bc + pop de + +.party_check_done + ; Swap items. + push de + push bc + ld hl, wTempMonItem + ld b, d + ld c, e + farcall GetStorageBoxMon + ld e, [hl] + ld a, b + pop bc + push af + farcall GetStorageBoxMon + pop af + ld d, [hl] + + ; Ensure that we're not trying to move mail into storage. + + ; Check if item d is a mail about to be given to a storage mon. + and a + call nz, ItemIsMail + ld a, 6 + jr c, .item_failed + push de + + ; Check if item e is a mail about to be given to a storage mon. + ld a, b + ld d, e + and a + call nz, ItemIsMail + ld a, 6 + jr c, .pop_de_item_failed + + ; No mail is about to be sent to storage, so proceed with the item move. + ld [hl], e + push hl + call BillsPC_UpdateStorage_CheckMewtwo + pop hl + pop de + pop bc + farcall GetStorageBoxMon + ld [hl], d + call BillsPC_UpdateStorage_CheckMewtwo + xor a + jr .done + +.pop_de_item_failed + pop de + ; fallthrough +.item_failed + pop bc + jr .failed + +.holding_mon + ; Try to swap slots bc and de and interpret result. + call SwapStorageBoxSlots + and a + jr nz, .failed + ld a, c + jr .done + +.failed + push af + push hl + push bc + call BillsPC_HideCursorAndMode + pop bc + pop hl + pop af + sub 2 + ld hl, BillsPC_MustSaveToContinue + jr c, .swap_failed + ld hl, .PartyIsFull + jr z, .swap_failed + ld hl, .BoxIsFull + dec a + jr z, .swap_failed + ld hl, BillsPC_LastPartyMon + dec a + jr z, .swap_failed + ld hl, .IsHoldingMail + dec a + jr z, .swap_failed + + ; Not returned by SwapStorageBoxSlots, but rather if item move failed. + ld hl, .CantStoreMail + dec a + jr z, .swap_failed + ld hl, BillsPC_EggsCantHoldItemsText + dec a + jr z, .swap_failed + ld hl, BillsPC_CantPutMailIntoPackText + dec a + jr z, .swap_failed + ld hl, BillsPC_PackFullText + ; fallthrough +.swap_failed + ; Print error message + push af + call MenuTextbox + pop af + + ; On carry, we got a confirmation prompt which re-runs this on "yes". + jr nc, .menutext_abort + + call YesNoBox + jr c, .menutext_abort + + ; Just re-run this function. + farcall ForceGameSave + ld hl, BillsPC_GameSaved + call PrintText + call BillsPC_UpdateCursorLocation + call CloseWindow + pop bc + pop de + jp BillsPC_SwapStorage +.menutext_abort + call BillsPC_UpdateCursorLocation + call CloseWindow +.abort + or 1 +.done + pop bc + pop de + ret + +.PartyIsFull: + text "The party is full." + prompt + +.BoxIsFull: + text "The box is full." + prompt + +.IsHoldingMail: + text "Held Mail must be" + line "removed first." + prompt + +.CantStoreMail: + text "Can't place Mail in" + line "storage." + prompt + +BillsPC_LastPartyMon: + text "That's your last" + line "healthy #mon!" + prompt + +BillsPC_MustSaveToContinue: + text "Save the game to" + line "do this?" + done + +BillsPC_GameSaved: + text_far _SavedTheGameText + text_end + +BillsPC_PlaceHeldMon: +; Places held mon at the current cursor location. Might perform swaps, or even +; be aborted, depending on circumstances. + ; Get source in de and destination in bc. + call BillsPC_GetCursorFromTo + + ; Try to swap slots bc and de and interpret result. + call BillsPC_SwapStorage + ret nz ; failed + + inc c + jr nz, .not_on_pack + dec c + + ; Avoid Pack icon flickering. + call DelayFrame + + ; Prevents quickanim + xor a + jr .place_icon + +.not_on_pack + dec c + jr nz, .not_on_boxname + + ; If we moved it onto a box, just move the sprite to its location without + ; any placing animation. + push de + ld e, a + ld d, b + lb bc, -1, 0 + call BillsPC_PerformQuickAnim + pop bc + ld a, PCANIM_ANIMATE + ld [wBillsPC_CursorAnimFlag], a + jr .partyshift + +.not_on_boxname + ; Check if the slot is blank. + ld a, c + dec a + add a + inc b + dec b + ld hl, wBillsPC_PartyList + jr z, .got_monlist + ld hl, wBillsPC_BoxList +.got_monlist + add l + ld l, a + adc h + sub l + ld h, a + ld a, [hl] + ; fallthrough + and a +.place_icon + push af + push de + push bc + call nz, BillsPC_PrepareQuickAnim + + call BillsPC_CursorPick1 + pop de + lb bc, -1, 0 + call BillsPC_MoveIconData + call BillsPC_IsHoldingItem + jr z, .holding_mon + + ld a, 1 + ldh [rVBK], a + call BillsPC_BlankCursorItem + xor a + ldh [rVBK], a + + ; Redundant, but fixes display when placing back on the same mon. + call .blankcursor + + call GetCursorMon + +.holding_mon + call BillsPC_CursorPick2 + pop bc + pop af +.partyshift + call CheckPartyShift + call BillsPC_MaybeMoveCursor + call z, GetCursorMon + ; fallthrough +.blankcursor + xor a + ld [wBillsPC_CursorHeldBox], a + ld [wBillsPC_CursorHeldSlot], a + ret + +BillsPC_SetPals: + call BillsPC_ApplyPals + jp SetPalettes + +BillsPC_ApplyPals: +; Sets palettes. This writes palette data for HBlank row1 mons/etc into +; wBGPals1. This avoids wrong palette flickering for a single frame. + ld a, BANK("GBC Video") + call StackCallInWRAMBankA +.Function: + ld de, wBillsPC_PalList + ld hl, wBGPals1 palette 2 + ld c, 6 +.loop + ; Copy white to color 0. +if !DEF(MONOCHROME) + ld a, $ff + ld [hli], a + ld a, $7f + ld [hli], a +else + ld a, LOW(PAL_MONOCHROME_WHITE) + ld [hli], a + ld a, HIGH(PAL_MONOCHROME_WHITE) + ld [hli], a +endc + + ; Copy hblank colors to color 1 and 2. + ld b, 4 +.inner_loop + ld a, [de] + inc de + ld [hli], a + dec b + jr nz, .inner_loop + + ; Copy black to color 3. +if !DEF(MONOCHROME) + xor a + ld [hli], a + ld [hli], a +else + ld a, LOW(PAL_MONOCHROME_BLACK) + ld [hli], a + ld a, HIGH(PAL_MONOCHROME_BLACK) + ld [hli], a +endc + dec c + jr nz, .loop + + ; Fix BG3 color 0, which is shared with the main background. + ld hl, wBGPals1 + ld de, wBGPals1 palette 3 + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + ret + +BillsPC_RestoreUI: + call ClearPalettes + call ClearSprites + call ClearSpriteAnims + + ; This needs to be done in case a frontpic anim overwrote data here. + ld a, 1 + ldh [rVBK], a + + call SetPartyIcons + call SetBoxIconsAndName + + xor a + ldh [rVBK], a + + call BillsPC_LoadUI + + ; Fixes cursor palettes. + ld a, [wBillsPC_CursorMode] + call _BillsPC_SetCursorMode + call BillsPC_ApplyPals + call GetCursorMon + ld b, 2 + call SafeCopyTilemapAtOnce + + ld hl, rIE + set LCD_STAT, [hl] + + ld a, 1 + ldh [hBGMapMode], a + ret + +BillsPC_CursorPosValid: +; Returns z if the cursor position is valid + ; Check for columns beyond 5 + ld b, a + and $f + cp 6 + jr nc, .invalid + + ; Check for party rows less than 2 + cp 2 + jr nc, .not_party + ld a, b + cp $20 + jr c, .invalid + + ; Rows 3-5 are always valid. + cp $30 + jr nc, .not_party + + ; Bag location is only valid sometimes. + cp $21 + jr nz, .invalid + + call _BillsPC_CheckBagDisplay + jr nz, .invalid + +.not_party + ; Check for rows beyond 5 + ld a, b + cp $60 + jr c, .valid +.invalid + or 1 + ld a, b + ret +.valid + xor a + ld a, b + ret diff --git a/engine/pokemon/breeding.asm b/engine/pokemon/breeding.asm index 41dd07989d..fd0a949655 100644 --- a/engine/pokemon/breeding.asm +++ b/engine/pokemon/breeding.asm @@ -488,14 +488,14 @@ InitEggMoves: ; reversed inheritance priority ; Default level 1 moves - ld de, wEggMonMoves + ld de, wTempMonMoves xor a ld [wBuffer1], a ; c = species - ld a, [wEggMonSpecies] + ld a, [wTempMonSpecies] ld c, a ; b = form - ld a, [wEggMonForm] + ld a, [wTempMonForm] and SPECIESFORM_MASK ld b, a predef FillMoves @@ -547,8 +547,8 @@ InitEggMoves: call .GetEggMoves ; Done, fill PP - ld hl, wEggMonMoves - ld de, wEggMonPP + ld hl, wTempMonMoves + ld de, wTempMonPP predef_jump FillPP .GetEggMoves: @@ -570,10 +570,10 @@ InitEggMoves: InheritLevelMove: ; If move d is part of the level up moveset, inherit that move ; c = species - ld a, [wEggMonSpecies] + ld a, [wTempMonSpecies] ld c, a ; b = form - ld a, [wEggMonForm] + ld a, [wTempMonForm] and SPECIESFORM_MASK ld b, a ; bc = index @@ -606,10 +606,10 @@ InheritLevelMove: InheritEggMove: ; If move d is an egg move, inherit that move ; c = species - ld a, [wEggMonSpecies] + ld a, [wTempMonSpecies] ld c, a ; b = form - ld a, [wEggMonForm] + ld a, [wTempMonForm] and SPECIESFORM_MASK ld b, a ; bc = index @@ -632,7 +632,7 @@ InheritEggMove: jr .loop InheritMove: - ld hl, wEggMonMoves + ld hl, wTempMonMoves ld b, NUM_MOVES .loop ld a, [hli] @@ -646,8 +646,8 @@ InheritMove: ; shift moves push de ld bc, 3 - ld hl, wEggMonMoves + 1 - ld de, wEggMonMoves + ld hl, wTempMonMoves + 1 + ld de, wTempMonMoves rst CopyBytes pop de .got_move_byte diff --git a/engine/pokemon/breedmon_level_growth.asm b/engine/pokemon/breedmon_level_growth.asm index 1f678a7c62..249f6481c2 100644 --- a/engine/pokemon/breedmon_level_growth.asm +++ b/engine/pokemon/breedmon_level_growth.asm @@ -1,7 +1,7 @@ GetBreedMon1LevelGrowth: - ld hl, wBreedMon1Stats + ld hl, wBreedMon1 ld de, wTempMon - ld bc, BOXMON_STRUCT_LENGTH + ld bc, BREEDMON_STRUCT_LENGTH rst CopyBytes farcall CalcLevel ld a, [wBreedMon1Level] @@ -13,9 +13,9 @@ GetBreedMon1LevelGrowth: ret GetBreedMon2LevelGrowth: - ld hl, wBreedMon2Stats + ld hl, wBreedMon2 ld de, wTempMon - ld bc, BOXMON_STRUCT_LENGTH + ld bc, BREEDMON_STRUCT_LENGTH rst CopyBytes farcall CalcLevel ld a, [wBreedMon2Level] diff --git a/engine/pokemon/caught_data.asm b/engine/pokemon/caught_data.asm index 0f4bcf61cf..8875c12e43 100644 --- a/engine/pokemon/caught_data.asm +++ b/engine/pokemon/caught_data.asm @@ -85,58 +85,52 @@ CheckPartyFullAfterContest: ret .TryAddToBox: - ld a, BANK(sBoxCount) - call GetSRAMBank - ld hl, sBoxCount - ld a, [hl] - cp MONS_PER_BOX - call CloseSRAM - jr nc, .BoxFull + farcall NewStorageBoxPointer + jr c, .BoxFull + push bc xor a ld [wCurPartyMon], a ld hl, wContestMon - ld de, wBufferMon - ld bc, BOXMON_STRUCT_LENGTH + ld de, wTempMon + ld bc, PARTYMON_STRUCT_LENGTH rst CopyBytes ld hl, wPlayerName - ld de, wBufferMonOT + ld de, wTempMonOT ld bc, NAME_LENGTH rst CopyBytes - farcall InsertPokemonIntoBox + pop bc + ld a, b + ld [wTempMonBox], a + ld a, c + ld [wTempMonSlot], a + farcall UpdateStorageBoxMonFromTemp ld a, [wCurPartySpecies] ld [wd265], a call GetPokemonName call GiveANickname_YesNo ld hl, wStringBuffer1 jr c, .Box_SkipNickname - ld a, BOXMON + ld a, TEMPMON ld [wMonType], a ld de, wMonOrItemNameBuffer farcall InitNickname ld hl, wMonOrItemNameBuffer .Box_SkipNickname: - ld a, BANK(sBoxMonNicknames) - call GetSRAMBank - ld de, sBoxMonNicknames + ld de, wTempMonNickname ld bc, MON_NAME_LENGTH rst CopyBytes - call CloseSRAM + farcall UpdateStorageBoxMonFromTemp .BoxFull: - ld a, BANK(sBoxMon1Level) - call GetSRAMBank - ld a, [sBoxMon1Level] + ld a, [wTempMonLevel] ld [wCurPartyLevel], a - call CloseSRAM xor a ; PARK_BALL ld [wCurItem], a call SetBoxMonCaughtData - ld a, BANK(sBoxMon1CaughtLocation) - call GetSRAMBank - ld hl, sBoxMon1CaughtLocation + ld hl, wTempMonCaughtLocation ld [hl], NATIONAL_PARK - call CloseSRAM + farcall UpdateStorageBoxMonFromTemp xor a ld [wContestMon], a ld a, $1 @@ -203,20 +197,14 @@ SetBoxmonOrEggmonCaughtData: ret SetBoxMonCaughtData: - ld a, BANK(sBoxMon1CaughtData) - call GetSRAMBank - ld hl, sBoxMon1CaughtData + ld hl, wTempMonCaughtData call SetBoxmonOrEggmonCaughtData - jp CloseSRAM + farjp UpdateStorageBoxMonFromTemp SetGiftBoxMonCaughtData: - push bc - ld a, BANK(sBoxMon1CaughtData) - call GetSRAMBank - ld hl, sBoxMon1CaughtData - pop bc + ld hl, wTempMonCaughtData call SetGiftMonCaughtData - jp CloseSRAM + farjp UpdateStorageBoxMonFromTemp SetGiftPartyMonCaughtData: ld a, [wPartyCount] diff --git a/engine/pokemon/mail.asm b/engine/pokemon/mail.asm index 5ec1a0ba8f..0efda3f387 100644 --- a/engine/pokemon/mail.asm +++ b/engine/pokemon/mail.asm @@ -172,7 +172,7 @@ CheckPokeItem:: jr c, .close_sram_return xor a ld [wPokemonWithdrawDepositParameter], a - predef RemoveMonFromPartyOrBox + predef RemoveMonFromParty ld a, $1 .close_sram_return diff --git a/engine/pokemon/mon_menu.asm b/engine/pokemon/mon_menu.asm index e0a9549862..783f5eaac0 100644 --- a/engine/pokemon/mon_menu.asm +++ b/engine/pokemon/mon_menu.asm @@ -235,25 +235,10 @@ GiveTakePartyMonItem: db "No held item@" .GiveItem: - - call DepositSellInitPackBuffers - -.loop - call DepositSellPack - - ld a, [wPackUsedItem] - and a + call GetItemToGive ret z - - call CheckUniqueItemPocket - jr nz, TryGiveItemToPartymon - - ld hl, CantBeHeldText - call MenuTextboxBackup - jr .loop - + ; fallthrough TryGiveItemToPartymon: - call SpeechTextbox call PartyMonItemName call GetPartyItemLocation @@ -268,7 +253,7 @@ TryGiveItemToPartymon: jr .already_holding_item .give_item_to_mon - call GiveItemToPokemon + call TossItemToGive ld hl, MadeHoldText call MenuTextboxBackup jp GivePartyItem @@ -284,7 +269,7 @@ TryGiveItemToPartymon: call StartMenuYesNo ret c - call GiveItemToPokemon + call TossItemToGive ld a, [wd265] push af ld a, [wCurItem] @@ -306,7 +291,6 @@ TryGiveItemToPartymon: ld a, [wd265] ld [wCurItem], a ; fallthrough - GivePartyItem: call GetPartyItemLocation ld a, [wCurItem] @@ -318,6 +302,83 @@ GivePartyItem: ret nc jp ComposeMailMessage +GetItemToGive: + call DepositSellInitPackBuffers + ; fallthrough +_GetItemToGive: +; Returns nz if we got an item to give. + call DepositSellPack + + ld a, [wPackUsedItem] + and a + ret z + + call CheckUniqueItemPocket + ret nz + + ld hl, CantBeHeldText + call MenuTextboxBackup + jr _GetItemToGive + +PCPickItem: +; For preparing an item to give to a mon of choice later on (or swap). +; Returns nz on success. + call DepositSellInitPackBuffers + jr _GetItemToGive + +PCGiveItem: + call DepositSellInitPackBuffers +.loop + call _GetItemToGive + ret z + + ; Ensure that we aren't trying to give Mail to a Pokémon in storage. + ld a, [wCurItem] + ld d, a + call ItemIsMail + jr nc, .item_ok + + ld a, [wTempMonBox] + and a + jr z, .item_ok + + ld hl, CantPlaceMailInStorageText + call MenuTextboxBackup + jr .loop + +.item_ok + call PartyMonItemName + call TossItemToGive + + ld hl, wTempMonNickname + ld de, wMonOrItemNameBuffer + ld bc, MON_NAME_LENGTH + rst CopyBytes + + ld hl, MadeHoldText + call MenuTextboxBackup + + ; Now, actually give the item. + ld a, [wTempMonSpecies] + ld [wCurPartySpecies], a + ld de, wCurItem + ld a, [de] + ld [wTempMonItem], a + ld hl, wTempMonForm + call _UpdateMewtwoForm + farcall UpdateStorageBoxMonFromTemp + + ; We know that if we're dealing with Mail, then we're giving to a partymon. + ; Thus, there's no harm in using party-specific code. + ld a, [wTempMonSlot] + dec a + ld [wCurPartyMon], a + ld a, [wCurItem] + ld d, a + call ItemIsMail + ret nc + jp ComposeMailMessage + ; swap items between two party pokemon SwapPartyItem: ld a, [wPartyCount] @@ -348,7 +409,7 @@ SwapPartyItem: ; wCurPartyMon contains second selected pkmn ; getting pkmn2 item and putting into stack item addr + item id call GetPartyItemLocation - ld a, [hl] ; a pkmn2 contains item + ld a, [hl] ; a pkmn2 contains item push hl push af ; getting pkmn 1 item and putting item id into b @@ -356,12 +417,12 @@ SwapPartyItem: dec a ld [wCurPartyMon], a call GetPartyItemLocation - ld a, [hl] ; a pkmn1 contains item + ld a, [hl] ; a pkmn1 contains item ld b, a ; actual swap - pop af + pop af ld [hl], a ; pkmn1 get pkm2 item - pop hl + pop hl ld a, b ld [hl], a ; pkmn1 get pkm2 item xor a @@ -403,18 +464,21 @@ TakePartyItem: jp MenuTextboxBackup UpdateMewtwoForm: + ld d, h + ld e, l + ld a, MON_FORM + call GetPartyParamLocation +_UpdateMewtwoForm: ld a, [wCurPartySpecies] cp MEWTWO ret nz - ld a, [hl] + ld a, [de] cp ARMOR_SUIT ld a, MEWTWO_ARMORED_FORM jr z, .got_form dec a ; PLAIN_FORM .got_form ld d, a - ld a, MON_FORM - call GetPartyParamLocation ld a, [hl] and $ff - SPECIESFORM_MASK or d @@ -467,6 +531,11 @@ CantBeHeldText: text_far _ItemCantHeldText text_end +CantPlaceMailInStorageText: + text "Can't place Mail in" + line "storage." + prompt + GetPartyItemLocation: push af ld a, MON_ITEM @@ -480,7 +549,7 @@ ReceiveItemFromPokemon: ld hl, wNumItems jp ReceiveItem -GiveItemToPokemon: +TossItemToGive: ld a, $1 ld [wItemQuantityChangeBuffer], a ld hl, wNumItems @@ -532,20 +601,36 @@ MonMailAction: call ExitMenu ; Interpret the menu. - jp c, .done + ld a, $3 + ret c ld a, [wMenuCursorY] cp $1 jr z, .read cp $2 - jr z, .take - jp .done + jr z, TakeMail + ld a, $3 + ret .read farcall ReadPartyMonMail xor a ret -.take +.MenuDataHeader: + db $40 ; flags + db 10, 12 ; start coords + db 17, 19 ; end coords + dw .MenuData2 + db 1 ; default option + +.MenuData2: + db $80 ; flags + db 3 ; items + db "Read@" + db "Take@" + db "Quit@" + +TakeMail: ld hl, .sendmailtopctext call StartMenuYesNo jr c, .RemoveMailToBag @@ -555,17 +640,17 @@ MonMailAction: jr c, .MailboxFull ld hl, .sentmailtopctext call MenuTextboxBackup - jr .done + jr .TookMail .MailboxFull: ld hl, .mailboxfulltext call MenuTextboxBackup - jr .done + jr .KeptMail .RemoveMailToBag: ld hl, .mailwilllosemessagetext call StartMenuYesNo - jr c, .done + jr c, .KeptMail call GetPartyItemLocation ld a, [hl] ld [wCurItem], a @@ -576,31 +661,21 @@ MonMailAction: call GetCurNickname ld hl, .tookmailfrommontext call MenuTextboxBackup + ; fallthrough +.TookMail: + scf jr .done .BagIsFull: ld hl, .bagfulltext call MenuTextboxBackup ; fallthrough - +.KeptMail: + and a .done ld a, $3 ret -.MenuDataHeader: - db $40 ; flags - db 10, 12 ; start coords - db 17, 19 ; end coords - dw .MenuData2 - db 1 ; default option - -.MenuData2: - db $80 ; flags - db 3 ; items - db "Read@" - db "Take@" - db "Quit@" - .mailwilllosemessagetext ; The MAIL will lose its message. OK? text_far _MailLoseMessageText @@ -632,12 +707,16 @@ MonMailAction: text_end OpenPartyStats: +; Stats screen for partymon in wCurPartyMon. + call PreparePartyTempMon + ; fallthrough +_OpenPartyStats: +; Stats screen for any mon, as supplied by wTempMonBox+wTempMonSlot call LoadStandardMenuHeader call ClearSprites -; PartyMon - xor a - ld [wMonType], a call LowVolume + ld a, TEMPMON + ld [wMonType], a predef StatsScreenInit call MaxVolume call ExitMenu @@ -762,7 +841,7 @@ ChooseMoveToDelete: call LoadFontsBattleExtra ld a, MOVESCREEN_DELETER ld [wMoveScreenMode], a - call MoveScreenLoop + call MoveScreen pop bc push af ld a, b @@ -780,7 +859,7 @@ ChooseMoveToForget: call LoadFontsBattleExtra ld a, MOVESCREEN_NEWMOVE ld [wMoveScreenMode], a - call MoveScreenLoop + call MoveScreen pop bc push af ld a, b @@ -824,7 +903,7 @@ ChooseMoveToRelearn: call LoadFontsBattleExtra ld a, MOVESCREEN_REMINDER ld [wMoveScreenMode], a - call MoveScreenLoop + call MoveScreen pop bc push af ld a, b @@ -846,9 +925,25 @@ ChooseMoveToRelearn: pop af ret +PreparePartyTempMon: +; Switches curpartymon to tempmon box+slot + xor a + ld [wTempMonBox], a + ld a, [wCurPartyMon] + inc a + ld [wTempMonSlot], a + ret + ManagePokemonMoves: - ld a, MON_IS_EGG - call GetPartyParamLocation + call PreparePartyTempMon + ; fallthrough +_ManagePokemonMoves: + ld a, [wTempMonBox] + ld b, a + ld a, [wTempMonSlot] + ld c, a + farcall GetStorageBoxMon + ld hl, wTempMonIsEgg bit MON_IS_EGG_F, [hl] jr nz, .egg ld hl, wOptions1 @@ -866,11 +961,23 @@ ManagePokemonMoves: xor a ret +MoveScreen: + call PreparePartyTempMon + ; fallthrough MoveScreenLoop: ; Returns: ; a = >0: f = nc|nz; selected move (index in wMoveScreenSelectedMove) ; a = 0: f = nc|z; user pressed B ; f = c; no options existed, move screen was aborted early + ld a, [wTempMonBox] + ld b, a + ld a, [wTempMonSlot] + ld c, a + + ; Update this in case we switched to a different mon. + dec a + ld [wCurPartyMon], a + farcall GetStorageBoxMon xor a ld [wMoveScreenSelectedMove], a ld [wMoveScreenCursor], a @@ -891,8 +998,7 @@ MoveScreenLoop: ; Copy over moves from the party struct ld bc, NUM_MOVES - ld a, MON_MOVES - call GetPartyParamLocation + ld hl, wTempMonMoves ld de, wMoveScreenMoves .movecopy_loop ld a, [hli] @@ -1026,36 +1132,23 @@ MoveScreenLoop: ld a, 3 jp .update_screen_cursor .species_right - ld a, [wPartyCount] - ld d, a - ld a, [wCurPartyMon] - dec d - cp d - jp z, .loop + ld a, [wTempMonSlot] + ld c, a .loop_right - inc a - ld d, a - ld bc, PARTYMON_STRUCT_LENGTH - ld hl, wPartyMon1IsEgg - rst AddNTimes - ld a, [hl] - and IS_EGG_MASK - ld a, d - jr nz, .loop_right_invalid - ld hl, wPartyMon1Species - rst AddNTimes - ld a, [hl] - call IsAPokemon - ld a, d - jr c, .loop_right_invalid - ld [wCurPartyMon], a - jp MoveScreenLoop -.loop_right_invalid - ld a, [wPartyCount] - dec a - cp d - ld a, d - jp z, .loop + push bc + farcall NextStorageBoxMon + pop bc + jr nz, .check_right + + ; There's no (non-Egg) mons afterwards, so revert wTempMon to what it was. + ld a, [wTempMonBox] + ld b, a + farcall GetStorageBoxMon + jp .loop +.check_right + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + jp z, MoveScreenLoop jr .loop_right .pressed_left ld a, [wMoveScreenMode] @@ -1067,30 +1160,23 @@ MoveScreenLoop: xor a jr .update_screen_cursor .species_left - ld a, [wCurPartyMon] - and a - jp z, .loop + ld a, [wTempMonSlot] + ld c, a .loop_left - dec a - ld d, a - ld bc, PARTYMON_STRUCT_LENGTH - ld hl, wPartyMon1IsEgg - rst AddNTimes - ld a, [hl] - and IS_EGG_MASK - ld a, d - jr nz, .loop_left_invalid - ld hl, wPartyMon1Species - rst AddNTimes - ld a, [hl] - call IsAPokemon - ld a, d - jr c, .loop_left_invalid - ld [wCurPartyMon], a - jp MoveScreenLoop -.loop_left_invalid - and a - jp z, .loop + push bc + farcall PrevStorageBoxMon + pop bc + jr nz, .check_left + + ; There's no previous (non-Egg) mons, so revert wTempMon to what it was. + ld a, [wTempMonBox] + ld b, a + farcall GetStorageBoxMon + jp .loop +.check_left + ld a, [wTempMonIsEgg] + bit MON_IS_EGG_F, a + jp z, MoveScreenLoop jr .loop_left .pressed_up ld a, [wMoveScreenCursor] @@ -1164,12 +1250,24 @@ MoveScreenLoop: jr .finish_swap .regular_swap_move - ld a, MON_MOVES - call GetPartyParamLocation + ld hl, wTempMonMoves call .swap_location - ld a, MON_PP - call GetPartyParamLocation + ld hl, wTempMonPP call .swap_location +.storage_swap_loop + farcall UpdateStorageBoxMonFromTemp + jr z, .finish_swap + + ; undo the swap + ld hl, wTempMonMoves + call .swap_location + ld hl, wTempMonPP + call .swap_location + ld hl, .MustSaveFirst + call PrintTextNoBox + xor a + ld [wMoveSwapBuffer], a + jp .outer_loop .finish_swap ld hl, wMoveScreenMoves @@ -1205,6 +1303,11 @@ MoveScreenLoop: ld [de], a ret +.MustSaveFirst: + text "Please save the" + line "game first." + prompt + GetForgottenMoves:: ; retrieve a list of a mon's forgotten moves, excluding ones beyond level ; and moves the mon already knows @@ -1307,13 +1410,10 @@ SetUpMoveScreenBG: call GetCGBLayout call LoadFontsBattleExtra call ClearSpriteAnims2 - ld a, [wCurPartyMon] - ld e, a - ld d, $0 - ld hl, wPartySpecies - add hl, de - ld a, [hl] + ld a, [wTempMonSpecies] ld [wd265], a + ld a, [wTempMonForm] + ld [wCurForm], a farcall LoadMoveMenuMonIcon hlcoord 0, 1 lb bc, 9, 18 @@ -1324,16 +1424,11 @@ SetUpMoveScreenBG: hlcoord 2, 0 lb bc, 2, 3 call ClearBox - xor a - ld [wMonType], a - ld hl, wPartyMonNicknames - ld a, [wCurPartyMon] - call GetNickname + ld de, wTempMonNickname hlcoord 5, 1 rst PlaceString - push bc - farcall CopyPkmnToTempMon - pop hl + ld h, b + ld l, c call PrintLevel call SetPalettes hlcoord 16, 0 @@ -1362,6 +1457,9 @@ MoveScreen_ListMoves: predef ListMoves ; Get PP -- either current PP, or default PP for the move + ld a, [wMoveScreenMode] + and a + jr z, .got_pp ld hl, wListMoves_MoveIndicesBuffer ld de, wTempMonMoves ld bc, NUM_MOVES diff --git a/engine/pokemon/mon_stats.asm b/engine/pokemon/mon_stats.asm index 10cf37470b..3164372244 100644 --- a/engine/pokemon/mon_stats.asm +++ b/engine/pokemon/mon_stats.asm @@ -329,12 +329,13 @@ GetShininess: dec a jr z, .PartyMon -; 2: sBoxMon - ld hl, sBoxMon1Shiny - ld bc, BOXMON_STRUCT_LENGTH +; 2: encountered oldbox code dec a - jr z, .sBoxMon + jr nz, .other + ld a, ERR_OLDBOX + jp Crash +.other ; 3: Other ld hl, wTempMonShiny dec a @@ -347,31 +348,14 @@ GetShininess: ; Get our place in the party/box. .PartyMon: -.sBoxMon ld a, [wCurPartyMon] rst AddNTimes .Shininess: -; sBoxMon data is read directly from SRAM. - ld a, [wMonType] - cp BOXMON - ld a, 1 - call z, GetSRAMBank - -; Shininess +; Check the shininess value ld a, [hl] and SHINY_MASK - ld b, a - -; Close SRAM if we were dealing with a sBoxMon. - ld a, [wMonType] - cp BOXMON - call z, CloseSRAM - -; Check the shininess value - ld a, b - and a ret GetGender: @@ -400,12 +384,13 @@ GetGender: dec a jr z, .PartyMon -; 2: sBoxMon - ld hl, sBoxMon1Gender - ld bc, BOXMON_STRUCT_LENGTH +; 2: encountered oldbox code dec a - jr z, .sBoxMon + jr nz, .other + ld a, ERR_OLDBOX + jp Crash +.other ; 3: Other (used for breeding, possibly elsewhere) ld hl, wTempMonGender dec a @@ -418,27 +403,15 @@ GetGender: ; Get our place in the party/box. .PartyMon: -.sBoxMon ld a, [wCurPartyMon] rst AddNTimes .Gender: -; sBoxMon data is read directly from SRAM. - ld a, [wMonType] - cp BOXMON - ld a, 1 - call z, GetSRAMBank - ; Gender and form are stored in the same byte ld a, [hl] ld b, a -; Close SRAM if we were dealing with a sBoxMon. - ld a, [wMonType] - cp BOXMON - call z, CloseSRAM - ; We need the gender ratio to do anything with this. ld a, [wCurPartySpecies] ld c, a diff --git a/engine/pokemon/move_mon.asm b/engine/pokemon/move_mon.asm index 8597a43e74..fca5f096eb 100644 --- a/engine/pokemon/move_mon.asm +++ b/engine/pokemon/move_mon.asm @@ -528,7 +528,7 @@ FillPP: pop bc ret -AddTempmonToParty: +AddTempMonToParty: ld hl, wPartyCount ld a, [hl] cp PARTY_LENGTH @@ -635,332 +635,6 @@ AddTempmonToParty: and a ret -SentGetPkmnIntoFromBox: -; Sents/Gets Pkmn into/from Box depending on Parameter -; wPokemonWithdrawDepositParameter == 0: get Pkmn into Party -; wPokemonWithdrawDepositParameter == 1: sent Pkmn into Box -; wPokemonWithdrawDepositParameter == 2: get Pkmn from DayCare -; wPokemonWithdrawDepositParameter == 3: put Pkmn into DayCare - ; Failsafe: never allow writing $ff to species bytes - ld a, [wCurPartySpecies] - inc a ; cp EGG - jr nz, .species_valid - ld a, ERR_EGG_SPECIES - jp Crash - -.species_valid - ld a, BANK(sBoxCount) - call GetSRAMBank - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .check_IfPartyIsFull - cp DAY_CARE_WITHDRAW - jr z, .check_IfPartyIsFull - cp DAY_CARE_DEPOSIT - ld hl, wBreedMon1Species - jr z, .breedmon - - ; we want to sent a Pkmn into the Box - ; so check if there's enough space - ld hl, sBoxCount - ld a, [hl] - cp MONS_PER_BOX - jr nz, .there_is_room - jp CloseSRAM_And_SetCarryFlag - -.check_IfPartyIsFull - ld hl, wPartyCount - ld a, [hl] - cp PARTY_LENGTH - jp z, CloseSRAM_And_SetCarryFlag - -.there_is_room - inc a - ld [hl], a - ld c, a - ld b, 0 - add hl, bc - ld a, [wPokemonWithdrawDepositParameter] - cp DAY_CARE_WITHDRAW - ld a, [wBreedMon1Species] - jr z, .okay1 - ld a, [wCurPartySpecies] - -.okay1 - ld [hli], a - ld [hl], $ff - ld a, [wPokemonWithdrawDepositParameter] - dec a - ld hl, wPartyMon1Species - ld bc, PARTYMON_STRUCT_LENGTH - ld a, [wPartyCount] - jr nz, .okay2 - ld hl, sBoxMon1Species - ld bc, BOXMON_STRUCT_LENGTH - ld a, [sBoxCount] - -.okay2 - dec a ; wPartyCount - 1 - rst AddNTimes - -.breedmon - push hl - ld e, l - ld d, h - ld a, [wPokemonWithdrawDepositParameter] - and a - ld hl, sBoxMon1Species - ld bc, BOXMON_STRUCT_LENGTH - jr z, .okay3 - cp DAY_CARE_WITHDRAW - ld hl, wBreedMon1Species - jr z, .okay4 - ld hl, wPartyMon1Species - ld bc, PARTYMON_STRUCT_LENGTH - -.okay3 - ld a, [wCurPartyMon] - rst AddNTimes - -.okay4 - ld bc, BOXMON_STRUCT_LENGTH - rst CopyBytes - ld a, [wPokemonWithdrawDepositParameter] - cp DAY_CARE_DEPOSIT - ld de, wBreedMon1OT - jr z, .okay5 - dec a - ld hl, wPartyMonOTs - ld a, [wPartyCount] - jr nz, .okay6 - ld hl, sBoxMonOTs - ld a, [sBoxCount] - -.okay6 - dec a - call SkipNames - ld d, h - ld e, l - -.okay5 - ld hl, sBoxMonOTs - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .okay7 - ld hl, wBreedMon1OT - cp DAY_CARE_WITHDRAW - jr z, .okay8 - ld hl, wPartyMonOTs - -.okay7 - ld a, [wCurPartyMon] - call SkipNames - -.okay8 - ld bc, NAME_LENGTH - rst CopyBytes - ld a, [wPokemonWithdrawDepositParameter] - cp DAY_CARE_DEPOSIT - ld de, wBreedMon1Nickname - jr z, .okay9 - dec a - ld hl, wPartyMonNicknames - ld a, [wPartyCount] - jr nz, .okay10 - ld hl, sBoxMonNicknames - ld a, [sBoxCount] - -.okay10 - dec a - call SkipNames - ld d, h - ld e, l - -.okay9 - ld hl, sBoxMonNicknames - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .okay11 - ld hl, wBreedMon1Nickname - cp DAY_CARE_WITHDRAW - jr z, .okay12 - ld hl, wPartyMonNicknames - -.okay11 - ld a, [wCurPartyMon] - call SkipNames - -.okay12 - ld bc, MON_NAME_LENGTH - rst CopyBytes - pop hl - - ld a, [wPokemonWithdrawDepositParameter] - cp PC_DEPOSIT - jr z, .took_out_of_box - cp DAY_CARE_DEPOSIT - jp z, .CloseSRAM_And_ClearCarryFlag - - push hl - srl a - add $2 - ld [wMonType], a - predef CopyPkmnToTempMon - farcall CalcLevel - ld a, d - ld [wCurPartyLevel], a - pop hl - - ld b, h - ld c, l - ld hl, MON_LEVEL - add hl, bc - ld [hl], a - ld hl, MON_MAXHP - add hl, bc - ld d, h - ld e, l - ld hl, MON_EVS - 1 - add hl, bc - - push bc - push hl - ld hl, sBoxMonOTs - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .prepare_hyper_addr - ld hl, wBreedMon1OT - cp DAY_CARE_WITHDRAW - jr z, .got_hyper_addr - ld hl, wPartyMonOTs - -.prepare_hyper_addr - ld a, [wCurPartyMon] - call SkipNames - -.got_hyper_addr - ld bc, PLAYER_NAME_LENGTH - add hl, bc - ld a, [hl] - and HYPER_TRAINING_MASK - pop hl - inc a - ld b, a - call CalcPkmnStats - pop bc - - ld a, [wPokemonWithdrawDepositParameter] - and a - jr nz, .CloseSRAM_And_ClearCarryFlag - ld hl, MON_STATUS - add hl, bc - xor a - ld [hl], a - ld hl, MON_HP - add hl, bc - ld d, h - ld e, l - push hl - ld hl, MON_IS_EGG - add hl, bc - bit MON_IS_EGG_F, [hl] - pop hl - jr nz, .egg - inc hl - inc hl - ld a, [hli] - ld [de], a - ld a, [hl] - inc de - ld [de], a - jr .CloseSRAM_And_ClearCarryFlag - -.egg - xor a - ld [de], a - inc de - ld [de], a - jr .CloseSRAM_And_ClearCarryFlag - -.took_out_of_box - ld a, [sBoxCount] - dec a - ld b, a - call RestorePPofDepositedPokemon -.CloseSRAM_And_ClearCarryFlag: - call CloseSRAM - and a - ret - -CloseSRAM_And_SetCarryFlag: - call CloseSRAM - scf - ret - -RestorePPofDepositedPokemon: - ld a, b - ld hl, sBoxMons - ld bc, BOXMON_STRUCT_LENGTH - rst AddNTimes - ld b, h - ld c, l - ld hl, MON_PP - add hl, bc - push hl - push bc - ld de, wTempMonPP - ld bc, NUM_MOVES - rst CopyBytes - pop bc - ld hl, MON_MOVES - add hl, bc - push hl - ld de, wTempMonMoves - ld bc, NUM_MOVES - rst CopyBytes - pop hl - pop de - - ld a, [wMenuCursorY] - push af - ld a, [wMonType] - push af - ld b, 0 -.loop - ld a, [hli] - and a - jr z, .done - ld [wTempMonMoves], a - ld a, BOXMON - ld [wMonType], a - ld a, b - ld [wMenuCursorY], a - push bc - push hl - push de - call GetMaxPPOfMove - pop de - pop hl - ld a, [wd265] - ld b, a - ld a, [de] - and %11000000 - add b - ld [de], a - pop bc - inc de - inc b - ld a, b - cp NUM_MOVES - jr c, .loop - -.done - pop af - ld [wMonType], a - pop af - ld [wMenuCursorY], a - ret - RetrievePokemonFromDayCareMan: ld a, [wBreedMon1Species] ld [wCurPartySpecies], a @@ -1039,7 +713,7 @@ RetrieveBreedmon: ld a, [hl] ld [wCurForm], a pop hl - ld bc, BOXMON_STRUCT_LENGTH + ld bc, BREEDMON_STRUCT_LENGTH rst CopyBytes call GetBaseData call GetLastPartyMon @@ -1126,44 +800,30 @@ _DepositBreedmon: ld hl, wPartyMon1Species ld bc, PARTYMON_STRUCT_LENGTH rst AddNTimes - ld bc, BOXMON_STRUCT_LENGTH + ld bc, BREEDMON_STRUCT_LENGTH rst CopyBytes xor a ld [wPokemonWithdrawDepositParameter], a - predef_jump RemoveMonFromPartyOrBox + predef_jump RemoveMonFromParty SentPkmnIntoBox: ; Sents the Pkmn into one of Bills Boxes -; the data comes mainly from 'wEnemyMon:' - ld a, BANK(sBoxCount) - call GetSRAMBank - ld de, sBoxCount - ld a, [de] - cp MONS_PER_BOX - jp nc, .full - inc a - ld [de], a +; the data comes mainly from wOTPartyMon1 + farcall NewStorageBoxPointer + jr c, .full + + push bc + lb bc, $81, 1 + farcall CopyBetweenPartyAndTemp ld a, [wCurPartySpecies] ld [wCurSpecies], a - ld c, a -.loop - inc de - ld a, [de] - ld b, a - ld a, c - ld c, b - ld [de], a - inc a - jr nz, .loop - ld hl, wOTPartyMon1Form predef GetVariant call GetBaseData - call ShiftBoxMon ld hl, wPlayerName - ld de, sBoxMonOTs + ld de, wTempMonOT ld bc, NAME_LENGTH rst CopyBytes @@ -1171,16 +831,11 @@ SentPkmnIntoBox: ld [wd265], a call GetPokemonName - ld de, sBoxMonNicknames ld hl, wStringBuffer1 + ld de, wTempMonNickname ld bc, MON_NAME_LENGTH rst CopyBytes - ld hl, wOTPartyMon1 - ld de, sBoxMon1 - ld bc, BOXMON_STRUCT_LENGTH - rst CopyBytes - ld a, [wCurPartySpecies] dec a call SetSeenAndCaughtMon @@ -1190,76 +845,19 @@ SentPkmnIntoBox: jr nz, .not_unown farcall UpdateUnownDex .not_unown - - ld hl, sBoxMon1Moves - ld de, wTempMonMoves - ld bc, NUM_MOVES - rst CopyBytes - - ld hl, sBoxMon1PP - ld de, wTempMonPP - ld bc, NUM_MOVES - rst CopyBytes - - ld b, 0 - call RestorePPofDepositedPokemon - - call CloseSRAM + pop bc + ld a, b + ld [wTempMonBox], a + ld a, c + ld [wTempMonSlot], a + farcall UpdateStorageBoxMonFromTemp scf ret .full - call CloseSRAM and a ret -ShiftBoxMon: - ld hl, sBoxMonOTs - ld bc, NAME_LENGTH - call .shift - - ld hl, sBoxMonNicknames - ld bc, MON_NAME_LENGTH - call .shift - - ld hl, sBoxMons - ld bc, BOXMON_STRUCT_LENGTH - -.shift - ld a, [sBoxCount] - cp 2 - ret c - - push hl - rst AddNTimes - dec hl - ld e, l - ld d, h - pop hl - - ld a, [sBoxCount] - dec a - rst AddNTimes - dec hl - - push hl - ld a, [sBoxCount] - dec a - ld hl, 0 - rst AddNTimes - ld c, l - ld b, h - pop hl -.loop - ld a, [hld] - ld [de], a - dec de - dec bc - ld a, c - or b - jr nz, .loop - ret - GiveEgg:: ld a, [wCurPartySpecies] push af @@ -1375,156 +973,14 @@ GiveEgg:: String_Egg: db "Egg@" -RemoveMonFromPartyOrBox: - ld hl, wPartyCount - - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .okay - - ld a, BANK(sBoxCount) - call GetSRAMBank - ld hl, sBoxCount - -.okay - ld a, [hl] - dec a - ld [hli], a - ld a, [wCurPartyMon] - ld c, a +RemoveMonFromParty: +; Done by writing a null entry to the party slot. ld b, 0 - add hl, bc - ld e, l - ld d, h - inc de -.loop - ld a, [de] - inc de - ld [hli], a - inc a - jr nz, .loop - ld hl, wPartyMonOTs - ld d, PARTY_LENGTH - 1 - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .party - ld hl, sBoxMonOTs - ld d, MONS_PER_BOX - 1 - -.party - ; If this is the last mon in our party (box), - ; shift all the other mons up to close the gap. - ld a, [wCurPartyMon] - call SkipNames - ld a, [wCurPartyMon] - cp d - jr nz, .delete_inside - ld [hl], -1 - jp .finish - -.delete_inside - ; Shift the OT names - ld d, h - ld e, l - ld bc, MON_NAME_LENGTH - add hl, bc - ld bc, wPartyMonNicknames - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .party2 - ld bc, sBoxMonNicknames -.party2 - call CopyDataUntil - ; Shift the struct - ld hl, wPartyMons - ld bc, PARTYMON_STRUCT_LENGTH - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .party4 - ld hl, sBoxMons - ld bc, BOXMON_STRUCT_LENGTH -.party4 - ld a, [wCurPartyMon] - rst AddNTimes - ld d, h - ld e, l - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .party5 - ld bc, BOXMON_STRUCT_LENGTH - add hl, bc - ld bc, sBoxMonOTs - jr .copy - -.party5 - ld bc, PARTYMON_STRUCT_LENGTH - add hl, bc - ld bc, wPartyMonOTs -.copy - call CopyDataUntil - ; Shift the nicknames - ld hl, wPartyMonNicknames - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .party6 - ld hl, sBoxMonNicknames -.party6 - ld bc, MON_NAME_LENGTH - ld a, [wCurPartyMon] - rst AddNTimes - ld d, h - ld e, l - ld bc, MON_NAME_LENGTH - add hl, bc - ld bc, wPartyMonNicknamesEnd - ld a, [wPokemonWithdrawDepositParameter] - and a - jr z, .party7 - ld bc, sBoxMonNicknamesEnd -.party7 - call CopyDataUntil - ; Mail time! -.finish - ld a, [wPokemonWithdrawDepositParameter] - and a - jp nz, CloseSRAM - ld a, [wLinkMode] - and a - ret nz - ; Shift mail - ld a, BANK(sPartyMail) - call GetSRAMBank - ; If this is the last mon in our party, no need to shift mail. - ld hl, wPartyCount ld a, [wCurPartyMon] - cp [hl] - jr z, .close_sram - ; Shift our mail messages up. - ld hl, sPartyMail - ld bc, MAIL_STRUCT_LENGTH - rst AddNTimes - push hl - add hl, bc - pop de - ld a, [wCurPartyMon] - ld b, a -.loop2 - push bc - push hl - ld bc, MAIL_STRUCT_LENGTH - rst CopyBytes - pop hl - push hl - ld bc, MAIL_STRUCT_LENGTH - add hl, bc - pop de - pop bc - inc b - ld a, [wPartyCount] - cp b - jr nz, .loop2 -.close_sram - jp CloseSRAM + inc a + ld c, a + ld e, 0 + farjp SetStorageBoxPointer ComputeNPCTrademonStats: ld a, MON_LEVEL @@ -1655,7 +1111,7 @@ CalcPkmnStats: ; hl is the path to the EVs - 1 ; de is a pointer where the 6 stats are placed - ld c, $0 + ld c, 0 .loop inc c call CalcPkmnStatC @@ -1989,7 +1445,8 @@ GivePoke:: and a jr z, .no_item ld a, [wCurItem] - ld [sBoxMon1Item], a + ld [wTempMonItem], a + farcall UpdateStorageBoxMonFromTemp .no_item ld a, POKE_BALL ld [wCurItem], a @@ -2061,9 +1518,7 @@ GivePoke:: jr .skip_nickname .send_to_box - ld a, BANK(sBoxMonOTs) - call GetSRAMBank - ld de, sBoxMonOTs + ld de, wTempMonOT .loop ld a, [wScriptBank] call GetFarByte @@ -2077,12 +1532,12 @@ GivePoke:: ld b, a ld a, POKE_BALL ld c, a - ld hl, sBoxMon1ID + ld hl, wTempMonID call Random ld [hli], a call Random ld [hl], a - call CloseSRAM + farcall UpdateStorageBoxMonFromTemp farcall SetGiftBoxMonCaughtData jr .skip_nickname @@ -2114,13 +1569,11 @@ GivePoke:: ret z ld hl, TextJump_WasSentToBillsPC call PrintText - ld a, BANK(sBoxMonNicknames) - call GetSRAMBank ld hl, wMonOrItemNameBuffer - ld de, sBoxMonNicknames + ld de, wTempMonNickname ld bc, MON_NAME_LENGTH rst CopyBytes - call CloseSRAM + farcall UpdateStorageBoxMonFromTemp ld b, $1 ret diff --git a/engine/pokemon/move_mon_wo_mail.asm b/engine/pokemon/move_mon_wo_mail.asm deleted file mode 100644 index e6403a9053..0000000000 --- a/engine/pokemon/move_mon_wo_mail.asm +++ /dev/null @@ -1,132 +0,0 @@ -InsertPokemonIntoBox: - ld a, BANK(sBoxCount) - call GetSRAMBank - ld hl, sBoxCount - call InsertSpeciesIntoBoxOrParty - ld a, [sBoxCount] - dec a - ld [wd265], a - ld hl, sBoxMonNicknames - ld bc, MON_NAME_LENGTH - ld de, wBufferMonNickname - call InsertDataIntoBoxOrParty - ld a, [sBoxCount] - dec a - ld [wd265], a - ld hl, sBoxMonOTs - ld bc, NAME_LENGTH - ld de, wBufferMonOT - call InsertDataIntoBoxOrParty - ld a, [sBoxCount] - dec a - ld [wd265], a - ld hl, sBoxMons - ld bc, BOXMON_STRUCT_LENGTH - ld de, wBufferMon - call InsertDataIntoBoxOrParty - ld hl, wBufferMonMoves - ld de, wTempMonMoves - ld bc, NUM_MOVES - rst CopyBytes - ld hl, wBufferMonPP - ld de, wTempMonPP - ld bc, NUM_MOVES - rst CopyBytes - ld a, [wCurPartyMon] - ld b, a - farcall RestorePPofDepositedPokemon - jp CloseSRAM - -InsertPokemonIntoParty: - ld hl, wPartyCount - call InsertSpeciesIntoBoxOrParty - ld a, [wPartyCount] - dec a - ld [wd265], a - ld hl, wPartyMonNicknames - ld bc, MON_NAME_LENGTH - ld de, wBufferMonNickname - call InsertDataIntoBoxOrParty - ld a, [wPartyCount] - dec a - ld [wd265], a - ld hl, wPartyMonOTs - ld bc, NAME_LENGTH - ld de, wBufferMonOT - call InsertDataIntoBoxOrParty - ld a, [wPartyCount] - dec a - ld [wd265], a - ld hl, wPartyMons - ld bc, PARTYMON_STRUCT_LENGTH - ld de, wBufferMon - jp InsertDataIntoBoxOrParty - -InsertSpeciesIntoBoxOrParty: - inc [hl] - inc hl - ld a, [wCurPartyMon] - ld c, a - ld b, 0 - add hl, bc - ld a, [wCurPartySpecies] - ld c, a -.loop - ld a, [hl] - ld [hl], c - inc hl - inc c - ld c, a - jr nz, .loop - ret - -InsertDataIntoBoxOrParty: - push de - push hl - push bc - ld a, [wd265] - dec a - rst AddNTimes - push hl - add hl, bc - ld d, h - ld e, l - pop hl -.loop - push bc - ld a, [wd265] - ld b, a - ld a, [wCurPartyMon] - cp b - pop bc - jr z, .insert - push hl - push de - push bc - rst CopyBytes - pop bc - pop de - pop hl - push hl - ld a, l - sub c - ld l, a - ld a, h - sbc b - ld h, a - pop de - ld a, [wd265] - dec a - ld [wd265], a - jr .loop - -.insert - pop bc - pop hl - ld a, [wCurPartyMon] - rst AddNTimes - ld d, h - ld e, l - pop hl - rst CopyBytes - ret diff --git a/engine/pokemon/party_menu.asm b/engine/pokemon/party_menu.asm index a55dd2dde7..5f9422814b 100644 --- a/engine/pokemon/party_menu.asm +++ b/engine/pokemon/party_menu.asm @@ -166,14 +166,7 @@ BT_PartySelect: prompt .Stats: - call LoadStandardMenuHeader - call ClearSprites - xor a ; PARTYMON - ld [wMonType], a - call LowVolume - predef StatsScreenInit - call MaxVolume - call ExitMenu + farcall OpenPartyStats jp .loop .Moves: diff --git a/engine/pokemon/search.asm b/engine/pokemon/search.asm index aaa44f385b..922fe8391c 100644 --- a/engine/pokemon/search.asm +++ b/engine/pokemon/search.asm @@ -89,211 +89,64 @@ CheckOwnMonAnywhere: ; Check if the player owns any monsters of the species in hScriptVar. ; It must exist in either party or PC, and have the player's OT and ID. - ; If there are no monsters in the party, - ; the player must not own any yet. - ld a, [wPartyCount] - and a - ret z - - ld d, a - ld e, 0 - ld hl, wPartyMon1Species - ld bc, wPartyMonOTs - - ; Run CheckOwnMon on each Pokémon in the party. -.partymon - call CheckOwnMon - ret c ; found! - - push bc - ld bc, PARTYMON_STRUCT_LENGTH - add hl, bc - pop bc - call UpdateOTPointer - dec d - jr nz, .partymon - - ; Run CheckOwnMon on each Pokémon in the PC. - ld a, BANK(sBoxCount) - call GetSRAMBank - ld a, [sBoxCount] - and a - jr z, .boxes - - ld d, a - ld hl, sBoxMon1Species - ld bc, sBoxMonOTs -.openboxmon - call CheckOwnMon - jp c, CloseSRAM ; found! - + ld b, NUM_BOXES +.outer_loop + inc b + dec b + ld c, PARTY_LENGTH + jr z, .loop + ld c, MONS_PER_BOX .loop - push bc - ld bc, BOXMON_STRUCT_LENGTH - add hl, bc - pop bc - call UpdateOTPointer - dec d - jr nz, .openboxmon - - ; Run CheckOwnMon on each monster in the other 13 PC boxes. -.boxes - call CloseSRAM - - ld c, 0 -.box - ; Don't search the current box again. - ld a, [wCurBox] - and $f - cp c - jr z, .loopbox - - ; Load the box. - ld hl, BoxAddressTable1 - ld b, 0 - add hl, bc - add hl, bc - add hl, bc - ld a, [hli] - call GetSRAMBank - ld a, [hli] - ld h, [hl] - ld l, a - - ; Number of monsters in the box - ld a, [hl] - and a - jr z, .loopbox - - push bc - - push hl - ld de, sBoxMons - sBoxCount - add hl, de - ld d, h - ld e, l - pop hl - push de - ld de, sBoxMonOTs - sBoxCount - add hl, de - ld b, h - ld c, l - pop hl - - ld d, a - -.boxmon - call CheckOwnMon - jr nc, .loopboxmon - - ; found! - pop bc - jp CloseSRAM - -.loopboxmon - push bc - ld bc, BOXMON_STRUCT_LENGTH - add hl, bc - pop bc - call UpdateOTPointer - dec d - jr nz, .boxmon - pop bc - -.loopbox - inc c - ld a, c - cp NUM_BOXES - jr c, .box - - ; not found - call CloseSRAM - and a - ret + farcall GetStorageBoxMon + jr z, .next -CheckOwnMon: -; Check if a Pokémon belongs to the player and is of a specific species. - -; inputs: -; hl, pointer to PartyMonNSpecies -; bc, pointer to PartyMonNOT -; hScriptVar should contain the species we're looking for - -; outputs: -; sets carry if monster matches species, ID, and OT name. + ; Check if the species is correct + ld hl, wTempMonSpecies + ldh a, [hScriptVar] + cp [hl] + jr nz, .next - push bc - push hl - push de - ld d, b - ld e, c + ; Eggs don't count + ld hl, wTempMonIsEgg + bit MON_IS_EGG_F, [hl] + jr nz, .next -; check species - ldh a, [hScriptVar] ; species we're looking for - ld b, [hl] ; species we have - cp b - jr nz, .notfound ; species doesn't match + ; If we have the "always OT" initial option on, this is always ours + ld a, [wInitialOptions] + bit TRADED_AS_OT_OPT, a + scf + ret nz -; check ID number - ld bc, MON_ID - add hl, bc ; now hl points to ID number + ; Verify ID + ld hl, wTempMonID ld a, [wPlayerID] cp [hl] - jr nz, .notfound ; ID doesn't match + jr nz, .next inc hl ld a, [wPlayerID + 1] cp [hl] - jr nz, .notfound ; ID doesn't match + jr nz, .next -; check OT - ld hl, wPlayerName - ld b, PLAYER_NAME_LENGTH - 1 -.loop + ; Verify OT + ld hl, wTempMonOT + ld de, wPlayerName +.cmp_ot ld a, [de] cp [hl] - jr nz, .notfound - cp "@" - jr z, .found ; reached end of string - inc hl inc de - dec b - jr nz, .loop - -.found - pop de - pop hl - pop bc + inc hl + jr nz, .next + cp "@" scf - ret - -.notfound - pop de - pop hl - pop bc - and a - ret - -BoxAddressTable1: - dba sBox1 - dba sBox2 - dba sBox3 - dba sBox4 - dba sBox5 - dba sBox6 - dba sBox7 - dba sBox8 - dba sBox9 - dba sBox10 - dba sBox11 - dba sBox12 - dba sBox13 - dba sBox14 + ret z + jr .cmp_ot +.next + dec c + jr nz, .loop + dec b + bit 7, b ; check for reaching -1 + jr z, .outer_loop -UpdateOTPointer: - push hl - ld hl, NAME_LENGTH - add hl, bc - ld b, h - ld c, l - pop hl + ; Failed to find a matching mon + xor a ret diff --git a/engine/pokemon/stats_screen.asm b/engine/pokemon/stats_screen.asm index fc7259f680..a5a405a2ce 100644 --- a/engine/pokemon/stats_screen.asm +++ b/engine/pokemon/stats_screen.asm @@ -19,6 +19,11 @@ StatsScreenInit: ld de, vTiles2 tile $31 lb bc, BANK(GFX_Stats), 41 call DecompressRequest2bpp + ld a, [wTempMonBox] + ld b, a + ld a, [wTempMonSlot] + ld c, a + farcall GetStorageBoxMon call StatsScreenMain call ClearBGPalettes call ClearTileMap @@ -94,7 +99,15 @@ MonStatsInit: call ClearBGPalettes call ClearTileMap farcall HDMATransferTileMapToWRAMBank3 - call StatsScreen_CopyToTempMon + ld a, [wTempMonSlot] + ld [wPartyMenuCursor], a + dec a + ld [wCurPartyMon], a + ld a, [wTempMonSpecies] + ld [wCurSpecies], a + ld [wCurPartySpecies], a + ld a, [wTempMonForm] + ld [wCurForm], a ld a, [wTempMonIsEgg] bit MON_IS_EGG_F, a jr nz, .egg @@ -156,65 +169,12 @@ MonStatsJoypad: and D_DOWN | D_UP | D_LEFT | D_RIGHT | A_BUTTON | B_BUTTON jp StatsScreen_JoypadAction -StatsScreen_CopyToTempMon: - ld a, [wMonType] - cp TEMPMON - jr nz, .breedmon - ld a, [wBufferMon] - ld [wCurSpecies], a - ld a, [wBufferMonForm] - and SPECIESFORM_MASK - ld [wCurForm], a - call GetBaseData - ld hl, wBufferMon - ld de, wTempMon - ld bc, PARTYMON_STRUCT_LENGTH - rst CopyBytes - jr .done - -.breedmon - farcall CopyPkmnOrEggToTempMon - ld a, [wTempMonIsEgg] - bit MON_IS_EGG_F, a - jr nz, .done - ld a, [wMonType] - cp BOXMON - jr c, .done - ld hl, StatsScreen_OTNamePointers - call GetNicknamePointer - farcall CalcTempmonStats -.done - and a - ret - StatsScreen_GetJoypad: call GetJoypad - ld a, [wMonType] - cp TEMPMON - jr nz, .notbreedmon - push hl - push de - push bc - farcall StatsScreenDPad - pop bc - pop de - pop hl - ld a, [wMenuJoypad] - and D_DOWN | D_UP - jr nz, .set_carry - ld a, [wMenuJoypad] - jr .clear_flags - -.notbreedmon ldh a, [hJoyPressed] -.clear_flags and a ret -.set_carry - scf - ret - StatsScreen_JoypadAction: push af ld a, [wStatsScreenFlags] @@ -234,43 +194,16 @@ StatsScreen_JoypadAction: bit D_DOWN_F, a ret z ; d_down - ld a, [wMonType] - cp BOXMON - ret nc - and a - ld a, [wPartyCount] - jr z, .next_mon - ld a, [wOTPartyCount] -.next_mon - ld b, a - ld a, [wCurPartyMon] - inc a - cp b - ret z - ld [wCurPartyMon], a - ld b, a - ld a, [wMonType] - and a - jr nz, .load_mon - ld a, b - inc a - ld [wPartyMenuCursor], a + farcall NextStorageBoxMon jr .load_mon .d_up - ld a, [wCurPartyMon] - and a + farcall PrevStorageBoxMon + ; fallthrough +.load_mon ret z - dec a - ld [wCurPartyMon], a - ld b, a - ld a, [wMonType] - and a - jr nz, .load_mon - ld a, b - inc a - ld [wPartyMenuCursor], a - jr .load_mon + ld h, 0 + jp StatsScreen_SetJumptableIndex .a_button ld a, c @@ -300,10 +233,6 @@ StatsScreen_JoypadAction: ld h, 3 jp StatsScreen_SetJumptableIndex -.load_mon - ld h, 0 - jp StatsScreen_SetJumptableIndex - .b_button ld h, 5 jp StatsScreen_SetJumptableIndex @@ -334,8 +263,7 @@ StatsScreen_InitUpperHalf: call PrintNum hlcoord 14, 0 call PrintLevel - ld hl, .NicknamePointers - call GetNicknamePointer + ld hl, wTempMonNickname call CopyNickname hlcoord 8, 2 rst PlaceString @@ -379,12 +307,6 @@ StatsScreen_InitUpperHalf: ld [hl], a ret -.NicknamePointers: - dw wPartyMonNicknames - dw wOTPartyMonNicknames - dw sBoxMonNicknames - dw wBufferMonNickname - StatsScreen_PlaceHorizontalDivider: hlcoord 0, 7 ld b, SCREEN_WIDTH @@ -523,9 +445,6 @@ StatsScreen_LoadGFX: hlcoord 8, 8 ld [hl], "." .NotImmuneToPkrs: - ld a, [wMonType] - cp BOXMON - jr z, .StatusOK hlcoord 5, 10 push hl ld de, wTempMonStatus @@ -637,8 +556,7 @@ StatsScreen_LoadGFX: lb bc, PRINTNUM_LEADINGZEROS | 2, 5 ld de, wTempMonID call PrintNum - ld hl, StatsScreen_OTNamePointers - call GetNicknamePointer + ld hl, wTempMonOT call CopyNickname hlcoord 1, 15 rst PlaceString @@ -735,10 +653,7 @@ StatsScreen_LoadGFX: farcall PrintTempMonStats ; Print Hyper Training statistics - ld hl, StatsScreen_OTNamePointers - call GetNicknamePointer - ld bc, PLAYER_NAME_LENGTH - add hl, bc + ld hl, wTempMonOT + PLAYER_NAME_LENGTH ld a, [hl] hlcoord 0, 10 ld de, -4 @@ -813,12 +728,6 @@ StatsScreen_LoadGFX: ; $3f = bold H db $3f, "1", "2", $3f -StatsScreen_OTNamePointers: - dw wPartyMonOTs - dw wOTPartyMonOTs - dw sBoxMonOTs - dw wBufferMonOT - TN_PrintToD: ld de, .caughtat hlcoord 1, 8 @@ -981,26 +890,15 @@ StatsScreen_PlaceFrontpic: ld hl, wTempMonForm predef GetVariant call StatsScreen_GetAnimationParam - jr c, .egg - and a - jr z, .no_cry - jr .cry - -.egg - call .AnimateEgg + jr nc, .no_cry + call .Animate jp SetPalettes .no_cry - call .AnimateMon + call .DontAnimate jp SetPalettes -.cry - call SetPalettes - call .AnimateMon - ld a, [wCurPartySpecies] - jp PlayCry2 - -.AnimateMon: +.DontAnimate: ld hl, wStatsScreenFlags set 5, [hl] hlcoord 0, 0 @@ -1009,20 +907,13 @@ StatsScreen_PlaceFrontpic: jp z, PrepMonFrontpicFlipped jp PrepMonFrontpic -.AnimateEgg: +.Animate: ld a, [wCurPartySpecies] - cp UNOWN - jr z, .unownegg + sub UNOWN + jr z, .got_align ld a, TRUE +.got_align ld [wBoxAlignment], a - jr .get_animation - -.unownegg - xor a - ld [wBoxAlignment], a - ; fallthrough - -.get_animation ld a, [wCurPartySpecies] call IsAPokemon ret c @@ -1037,64 +928,28 @@ StatsScreen_PlaceFrontpic: ret StatsScreen_GetAnimationParam: - ld a, [wMonType] - call StackJumpTable - -.Jumptable: - dw .PartyMon - dw .OTPartyMon - dw .BoxMon - dw .Tempmon - dw .Wildmon - -.PartyMon: - ld a, [wCurPartyMon] - ld hl, wPartyMon1Species - ld bc, PARTYMON_STRUCT_LENGTH - rst AddNTimes - ld b, h - ld c, l - jr .CheckEggFaintedFrzSlp - -.OTPartyMon: - xor a - ret - -.BoxMon: - ld hl, sBoxMons - ld bc, PARTYMON_STRUCT_LENGTH - ld a, [wCurPartyMon] - rst AddNTimes - ld b, h - ld c, l - ld a, BANK(sBoxMons) - call GetSRAMBank - call .CheckEggFaintedFrzSlp - push af - call CloseSRAM - pop af - ret - -.Tempmon: ld bc, wTempMon -.CheckEggFaintedFrzSlp: ld a, [wTempMonIsEgg] bit MON_IS_EGG_F, a - jr nz, .egg - call CheckFaintedFrzSlp - jr c, .FaintedFrzSlp -.egg - xor a scf + ret nz + call CheckFaintedFrzSlp + ccf ret -.Wildmon: - ld a, $1 - and a - ret - -.FaintedFrzSlp: - xor a +CheckFaintedFrzSlp: + ld hl, MON_HP + add hl, bc + ld a, [hli] + or [hl] + jr z, .fainted_frz_slp + ld hl, MON_STATUS + add hl, bc + ld a, [hl] + and (1 << FRZ) | SLP + ret z +.fainted_frz_slp + scf ret StatsScreen_LoadTextboxSpaceGFX: @@ -1247,51 +1102,7 @@ StatsScreen_LoadPageIndicators: CopyNickname: ld de, wStringBuffer1 ld bc, MON_NAME_LENGTH - ld a, [wMonType] - cp BOXMON - jr nz, .partymon - ld a, BANK(sBoxMonNicknames) - call GetSRAMBank - push de - rst CopyBytes - pop de - jp CloseSRAM - -.partymon push de rst CopyBytes pop de ret - -GetNicknamePointer: - ld a, [wMonType] - add a - ld c, a - ld b, 0 - add hl, bc - ld a, [hli] - ld h, [hl] - ld l, a - ld a, [wMonType] - cp TEMPMON - ret z - ld a, [wCurPartyMon] - jp SkipNames - -CheckFaintedFrzSlp: - ld hl, MON_HP - add hl, bc - ld a, [hli] - or [hl] - jr z, .fainted_frz_slp - ld hl, MON_STATUS - add hl, bc - ld a, [hl] - and (1 << FRZ) | SLP - jr nz, .fainted_frz_slp - and a - ret - -.fainted_frz_slp - scf - ret diff --git a/engine/pokemon/tempmon.asm b/engine/pokemon/tempmon.asm index 3b7f5d1e5a..87ebd02e61 100644 --- a/engine/pokemon/tempmon.asm +++ b/engine/pokemon/tempmon.asm @@ -7,10 +7,8 @@ CopyPkmnOrEggToTempMon: ld hl, wOTPartyMon1IsEgg cp OTPARTYMON jr z, .got_addr - ld hl, sBoxMon1IsEgg - ld bc, BOXMON_STRUCT_LENGTH - ld a, BANK(sBoxMon1IsEgg) - call GetSRAMBank + ld a, ERR_OLDBOX + jp Crash .got_addr ld a, [wCurPartyMon] rst AddNTimes @@ -39,8 +37,8 @@ _CopyPkmnToTempMon: ld bc, PARTYMON_STRUCT_LENGTH cp OTPARTYMON jr z, .copywholestruct - ld bc, BOXMON_STRUCT_LENGTH - farjp CopyBoxmonToTempMon + ld a, ERR_OLDBOX + jp Crash .copywholestruct ld a, [wCurPartyMon] @@ -50,65 +48,6 @@ _CopyPkmnToTempMon: rst CopyBytes ret -CalcBufferMonStats: - ld bc, wBufferMon - ld hl, wBufferMonOT - jr _TempMonStatsCalculation - -CalcTempmonStats: - ld bc, wTempMon -_TempMonStatsCalculation: - push hl - ld hl, MON_LEVEL - add hl, bc - ld a, [hl] - ld [wCurPartyLevel], a - pop hl - ld de, PLAYER_NAME_LENGTH - add hl, de - ld a, [hl] - and HYPER_TRAINING_MASK - ld hl, MON_MAXHP - add hl, bc - ld d, h - ld e, l - ld hl, MON_EVS - 1 - add hl, bc - push bc - inc a - ld b, a - predef CalcPkmnStats - pop bc - ld hl, MON_HP - add hl, bc - ld d, h - ld e, l - ld hl, MON_IS_EGG - add hl, bc - bit MON_IS_EGG_F, [hl] - jr z, .not_egg - xor a - ld [de], a - inc de - ld [de], a - jr .zero_status - -.not_egg - push bc - ld hl, MON_MAXHP - add hl, bc - ld bc, 2 - rst CopyBytes - pop bc - -.zero_status - ld hl, MON_STATUS - add hl, bc - xor a - ld [hli], a - ld [hl], a - ret - GetPkmnSpecies: ld a, [wMonType] and a ; PARTYMON @@ -139,11 +78,8 @@ GetPkmnSpecies: ret .boxmon - ld a, BANK(sBoxSpecies) - call GetSRAMBank - ld hl, sBoxSpecies - call .done - jp CloseSRAM + ld a, ERR_OLDBOX + jp Crash .breedmon ld a, [wBreedMon1Species] @@ -179,12 +115,8 @@ GetPkmnForm: ret .boxmon - ld a, BANK(sBoxSpecies) - call GetSRAMBank - ld hl, sBoxMon1Form - ld bc, BOXMON_STRUCT_LENGTH - call .getnthmon - jp CloseSRAM + ld a, ERR_OLDBOX + jp Crash .breedmon ld a, [wBreedMon1Form] diff --git a/gfx/pc/bags.png b/gfx/pc/bags.png new file mode 100644 index 0000000000000000000000000000000000000000..78e292e70259d019b6e567a68963bba58a5c59a4 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0zj<51SA+rg7$9&Qk9-Ajv*Dde9sti9X8-;eOT_v z`RJVK61gHN$tSUGTF!kJE|+-Z3-FivYMSK#sJz^6*Y@t-#SSx>rFs@E;%}C!%_wM? zCZ#AQ;U%*0vy8ugQJO^Hfz!1|?^ko%Us!k9!IbZPnSTXWe8SRy0b2`}?b|qqxolC( bvwgJ;Ni)1v#6mRKL5}fs^>bP0l+XkKVR=Bz literal 0 HcmV?d00001 diff --git a/gfx/pc/cursor.png b/gfx/pc/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6c230e069dbeb3f71754129117b97c4aca61b7 GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^96&6=1SA+%F#XO2Ql_3Rjv*Ddl7IZ4&&c~XG2zGN zu7~GC1b(gOa#KI}Liq51d!94@B~9K)UYR8sV(g$Y;VjQ3R)%^NB^|q-_}3tVJYD@< J);T3K0RZwIBzOP- literal 0 HcmV?d00001 diff --git a/gfx/pc/mail.png b/gfx/pc/mail.png deleted file mode 100644 index fd4b2892f337e264b693dd2ac38e42203095fdd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx;Bp3=bcE|uJOHUWakcwN$2@H&DMHeaZ#Bew0 zEah7$#>UIbDApk{gQ5Aq!+{S6|MA-&X8*_U(Daj`hoRU}NI{k1SH0vL-D}7HfDH3= L^>bP0l+XkK)ZrjF diff --git a/gfx/pc/modes.png b/gfx/pc/modes.png new file mode 100644 index 0000000000000000000000000000000000000000..1e8db3f16bf8f4fb56a9bf0500e8df629d974139 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^5kSnr1SA-Q4%F`hQhA;(jv*Dda{D&&9(Lei-m3C- z!t4;9gty9)ml*DyxT#Y5iGPVlqL@y{;l@upd-r@i{doUVD}RmMZ!h%j`&sTTN|-;1ar@JK^`BTbJ&fqK SUZ&~~a)GC-pUXO@geCy(nML{l literal 0 HcmV?d00001 diff --git a/gfx/pc/pc.png b/gfx/pc/pc.png index ce8fed73b3c3fc3ead5feb04f834ec4f1abf39d8..5311ea0cf2549d6161b31d5679e06cfbb1e9e291 100644 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk`Bp75C+I9k|iJmTwAr-em&)f1HHsD}=5Pou} zV~srjH%6US?;=J2Kz04QtF@e+8~>b0{iX8RV1e@)OYHfP2PUv|Vk5q*7N{}NxUHx3vIVCg!0G(J* Az5oCK literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^0zj<51SA+rg7$9&QdXWWjv*DdYEL diff --git a/tools/bsp/README.md b/tools/bsp/README.md new file mode 100644 index 0000000000..e4a1e8f8d8 --- /dev/null +++ b/tools/bsp/README.md @@ -0,0 +1,37 @@ +# bsp — Binary Script Patching + +This project focuses on creating binary patch files; that is, files that encode the conversion between a source and a +target file, both of them being binary files regardless of internal structure. This project also contains a patching +library that can be used to apply said patches once created. + +Instead of following the typical approach of building a file containing the differences as pure data, this project aims +towards building scripted patches — that is, patch files that contain a script that is executed in order to perform the +patching process. While this implies that, unlike in standard approaches, patch files must be built manually; it in +turn allows arbitrarily complex patching and verification processes, including the possibility of handling multiple, +different source and/or target files, or allowing the user of the patch to select which target to produce. + +This software is released to the public domain. For more information, view the (conventionally but inaccurately named) +[LICENSE][license] file. + +For information on the BSP format, defined by this project, read the [specification]. For information about the exact +syntax and details that the compiler uses, read the [compiler documentation][compiler-docs]. For information about the +patching library, read the corresponding [documentation][patcher-docs]; the library itself is available in the +[`bsppatch.js`](bsppatch.js) file. + +A patching program for Node.js using this library is also available here, in the [`patcher.js`](patcher.js) file. This +program uses the `bsppatch.js` library, and requires it to be in the same directory. It needs Node.js 4.x or higher to +run. In order to use it, invoke it with the patch file, the input file and the output filename (in that order) as +command-line arguments, like this: + +``` +node patcher.js patch.bsp input.dat output.dat +``` + +It is also possible to apply patches via a website. A sample patching website is available here, which should work in +any modern browser; it is located in the [`sample.htm`](sample.htm) file, and it requires having the `bsppatch.js` file +in the same directory for it to work. This website can be tested at http://aaaaaa123456789.github.io/bsp. + +[license]: LICENSE +[specification]: specification.md +[compiler-docs]: compiler.md +[patcher-docs]: patching.md diff --git a/tools/bsp/bspcomp.c b/tools/bsp/bspcomp.c new file mode 100644 index 0000000000..ea9c862917 --- /dev/null +++ b/tools/bsp/bspcomp.c @@ -0,0 +1,1026 @@ +#include +#include +#include +#include + +#define LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +#define LETTERS_OR_UNDERSCORE LETTERS "_" +#define DIGITS "0123456789" +#define VALID_ID_CHARACTERS LETTERS_OR_UNDERSCORE DIGITS +#define NUMERIC_CHARACTERS DIGITS "+-" + +struct symbol { + unsigned value; + char name[]; +}; + +struct script_data { + unsigned length; + unsigned symbol_count; + unsigned definition_count; + unsigned reference_count; + struct symbol ** symbols; + struct symbol ** definitions; + struct symbol ** references; + char data[]; +}; + +struct file_stack_entry { + FILE * fp; + char * name; + unsigned line; +}; + +struct command { + const char * name; + int argument; + void (* parser) (int, char **); +}; + +struct argument { + unsigned char kind; // 0: constant, 1: argument, 2: reference + unsigned value; + char reference[]; +}; + +int main(int, char **); +void error_exit(int, const char *, ...); +void initialize_data(void); +void append_data_to_script(const char *, unsigned); +void append_binary_file_to_script(const char *); +void write_halfword_to_buffer(void *, unsigned short); +void write_word_to_buffer(void *, unsigned); +void write_number_to_buffer(unsigned char *, unsigned, unsigned char); +struct symbol * find_symbol(const char *); +struct symbol * find_definition(const char *); +struct symbol * find_identifier_by_name(const char *, struct symbol **, unsigned); +void create_symbol(const char *, unsigned); +void create_definition(const char *, unsigned); +void create_reference(const char *, unsigned); +struct symbol * new_symbol(const char *, unsigned); +void resolve(const struct symbol *); +int find_unquoted_character(const char *, char); +char * trim_string(char *); +char * duplicate_string(const char *); +char ** extract_components_from_line(const char *); +void destroy_component_array(char **); +unsigned count_parameters(char **); +unsigned char get_hex_digit(char); +unsigned convert_string_to_number(const char *); +FILE * open_text_file(const char *); +FILE * open_binary_file(const char *); +FILE * open_binary_file_for_writing(const char *); +FILE * open_file(const char *, const char *, const char *); +char * read_line(FILE *); +char * get_line_from_input(void); +void push_file(const char *); +void pop_file(void); +void write_data_to_file(FILE *, const char *, unsigned); +void process_input_line(const char *); +void declare_label(const char *); +struct command * find_command(const char *); +void flush_locals(void); +void flush_all_symbols(void); +int validate_label(const char *); +struct argument * get_argument(const char *); +char * get_string_argument(const char *); +void no_arguments_command(int, char **); +void one_argument_command(int, char **); +void two_arguments_command(int, char **); +void one_variable_command(int, char **); +void one_variable_one_argument_command(int, char **); +void one_variable_two_arguments_command(int, char **); +void two_variables_command(int, char **); +void two_variables_two_arguments_command(int, char **); +void one_byte_argument_command(int, char **); +void one_halfword_argument_command(int, char **); +void calculation_command(int, char **); +void bit_shift_command(int, char **); +void one_argument_one_byte_argument(int, char **); +void one_argument_one_halfword_argument(int, char **); +void mulacum_command(int, char **); +void standard_command(unsigned char, unsigned char, unsigned char, char **); +void data_command(int, char **); +void hexdata_command(int, char **); +void define_command(int, char **); +void include_command(int, char **); +void string_command(int, char **); +void align_command(int, char **); + +char * current_file = NULL; +unsigned current_line = 0; + +struct file_stack_entry * file_stack = NULL; +unsigned file_stack_length = 0; + +struct script_data * script_data = NULL; + +struct command script_commands[] = { + {"add", 0x20, &calculation_command}, + {"addcarry", 0xb0, &two_variables_two_arguments_command}, + {"align", 0, &align_command}, + {"and", 0x34, &calculation_command}, + {"bsppatch", 0x94, &one_variable_two_arguments_command}, + {"bufchar", 0xa2, &one_argument_command}, + {"bufnumber", 0xa4, &one_argument_command}, + {"bufstring", 0xa0, &one_argument_command}, + {"call", 0x04, &one_argument_command}, + {"callnz", 0x5e, &one_variable_one_argument_command}, + {"callz", 0x5c, &one_variable_one_argument_command}, + {"checksha1", 0x16, &one_variable_one_argument_command}, + {"clearbuf", 0xa7, &no_arguments_command}, + {"db", 1, &data_command}, + {"decrement", 0x9f, &one_variable_command}, + {"define", 0, &define_command}, + {"dh", 2, &data_command}, + {"divide", 0x2c, &calculation_command}, + {"dw", 4, &data_command}, + {"exit", 0x06, &one_argument_command}, + {"fillbyte", 0x70, &one_argument_one_byte_argument}, + {"fillhalfword", 0x74, &one_argument_one_halfword_argument}, + {"fillword", 0x78, &two_arguments_command}, + {"getbyte", 0x10, &one_variable_one_argument_command}, + {"getbytedec", 0x9c, &two_variables_command}, + {"getbyteinc", 0x98, &two_variables_command}, + {"getfilebyte", 0xac, &one_variable_command}, + {"getfilehalfword", 0xad, &one_variable_command}, + {"getfileword", 0xae, &one_variable_command}, + {"gethalfword", 0x12, &one_variable_one_argument_command}, + {"gethalfworddec", 0x9d, &two_variables_command}, + {"gethalfwordinc", 0x99, &two_variables_command}, + {"getstacksize", 0xaa, &one_variable_command}, + {"getvariable", 0xaf, &two_variables_command}, + {"getword", 0x14, &one_variable_one_argument_command}, + {"getworddec", 0x9e, &two_variables_command}, + {"getwordinc", 0x9a, &two_variables_command}, + {"hexdata", 0, &hexdata_command}, + {"ifeq", 0x50, &one_variable_two_arguments_command}, + {"ifge", 0x4c, &one_variable_two_arguments_command}, + {"ifgt", 0x48, &one_variable_two_arguments_command}, + {"ifle", 0x44, &one_variable_two_arguments_command}, + {"iflt", 0x40, &one_variable_two_arguments_command}, + {"ifne", 0x54, &one_variable_two_arguments_command}, + {"incbin", 1, &include_command}, + {"include", 0, &include_command}, + {"increment", 0x9b, &one_variable_command}, + {"ipspatch", 0x86, &one_variable_one_argument_command}, + {"jump", 0x02, &one_argument_command}, + {"jumpnz", 0x5a, &one_variable_one_argument_command}, + {"jumptable", 0x83, &one_variable_command}, + {"jumpz", 0x58, &one_variable_one_argument_command}, + {"length", 0x0b, &one_variable_command}, + {"lockpos", 0x80, &no_arguments_command}, + {"longmul", 0xb8, &two_variables_two_arguments_command}, + {"longmulacum", 0xbc, &two_variables_two_arguments_command}, + {"menu", 0x6a, &one_variable_one_argument_command}, + {"mulacum", 0xbc, &mulacum_command}, + {"multiply", 0x28, &calculation_command}, + {"nop", 0x00, &no_arguments_command}, + {"or", 0x38, &calculation_command}, + {"pop", 0x0a, &one_variable_command}, + {"poppos", 0x93, &no_arguments_command}, + {"pos", 0x0f, &one_variable_command}, + {"print", 0x68, &one_argument_command}, + {"printbuf", 0xa6, &no_arguments_command}, + {"push", 0x08, &one_argument_command}, + {"pushpos", 0x92, &no_arguments_command}, + {"readbyte", 0x0c, &one_variable_command}, + {"readhalfword", 0x0d, &one_variable_command}, + {"readword", 0x0e, &one_variable_command}, + {"remainder", 0x30, &calculation_command}, + {"retnz", 0x91, &one_variable_command}, + {"return", 0x01, &no_arguments_command}, + {"retz", 0x90, &one_variable_command}, + {"rotateleft", 2, &bit_shift_command}, + {"seek", 0x60, &one_argument_command}, + {"seekback", 0x64, &one_argument_command}, + {"seekend", 0x66, &one_argument_command}, + {"seekfwd", 0x62, &one_argument_command}, + {"set", 0x84, &one_variable_one_argument_command}, + {"setstacksize", 0xa8, &one_argument_command}, + {"shiftleft", 0, &bit_shift_command}, + {"shiftright", 1, &bit_shift_command}, + {"shiftrightarith", 3, &bit_shift_command}, + {"stackread", 0x8c, &one_variable_one_argument_command}, + {"stackshift", 0x8e, &one_argument_command}, + {"stackwrite", 0x88, &two_arguments_command}, + {"string", 0, &string_command}, + {"subborrow", 0xb4, &two_variables_two_arguments_command}, + {"subtract", 0x24, &calculation_command}, + {"truncate", 0x1e, &one_argument_command}, + {"truncatepos", 0x82, &no_arguments_command}, + {"unlockpos", 0x81, &no_arguments_command}, + {"writebyte", 0x18, &one_byte_argument_command}, + {"writedata", 0x7c, &two_arguments_command}, + {"writehalfword", 0x1a, &one_halfword_argument_command}, + {"writeword", 0x1c, &one_argument_command}, + {"xor", 0x3c, &calculation_command}, + {"xordata", 0x6c, &two_arguments_command}, + {NULL, 0, NULL} +}; + +#define SET_OPCODE 0x84 +#define SHIFT_OPCODE 0xab + +int main (int argc, char ** argv) { + if (argc != 3) { + fprintf(stderr, "usage: %s \n", *argv); + return 2; + } + initialize_data(); + push_file(argv[1]); + char * line; + int comment_start; + while (file_stack_length) { + current_line ++; + line = get_line_from_input(); + comment_start = find_unquoted_character(line, ';'); + if (comment_start >= 0) line[comment_start] = 0; + process_input_line(line); + free(line); + if (feof(file_stack -> fp)) pop_file(); + } + current_file = NULL; + flush_all_symbols(); + FILE * fp = open_binary_file_for_writing(argv[2]); + write_data_to_file(fp, script_data -> data, script_data -> length); + fclose(fp); + return 0; +} + +void error_exit (int status, const char * fmtstring, ...) { + if (current_file) + fprintf(stderr, "%s:%u: ", current_file, current_line); + else + fprintf(stderr, "error: "); + va_list ap; + va_start(ap, fmtstring); + vfprintf(stderr, fmtstring, ap); + va_end(ap); + putc('\n', stderr); + exit(status); +} + +void initialize_data (void) { + script_data = calloc(1, sizeof(struct script_data)); +} + +void append_data_to_script (const char * data, unsigned length) { + script_data = realloc(script_data, sizeof(struct script_data) + script_data -> length + length); + memcpy(script_data -> data + script_data -> length, data, length); + script_data -> length += length; +} + +void append_binary_file_to_script (const char * file) { + char * prev_current_file = current_file; + current_file = NULL; + FILE * fp = open_binary_file(file); + char * buffer = malloc(65536); + unsigned rv; + while (!feof(fp)) { + rv = fread(buffer, 1, 65536, fp); + if ((!rv) && ferror(fp)) error_exit(1, "could not read data from binary file %s", file); + append_data_to_script(buffer, rv); + } + fclose(fp); + free(buffer); + current_file = prev_current_file; +} + +void write_halfword_to_buffer (void * buffer, unsigned short number) { + write_number_to_buffer(buffer, number, 2); +} + +void write_word_to_buffer (void * buffer, unsigned number) { + write_number_to_buffer(buffer, number, 4); +} + +void write_number_to_buffer (unsigned char * buffer, unsigned number, unsigned char length) { + unsigned char pos; + for (pos = 0; pos < length; pos ++) { + *(buffer ++) = number; + number >>= 8; + } +} + +struct symbol * find_symbol (const char * name) { + return find_identifier_by_name(name, script_data -> symbols, script_data -> symbol_count); +} + +struct symbol * find_definition (const char * name) { + return find_identifier_by_name(name, script_data -> definitions, script_data -> definition_count); +} + +struct symbol * find_identifier_by_name (const char * identifier, struct symbol ** identifiers, unsigned count) { + unsigned pos; + for (pos = 0; pos < count; pos ++) if (!strcmp(identifiers[pos] -> name, identifier)) return identifiers[pos]; + return NULL; +} + +void create_symbol (const char * name, unsigned value) { + script_data -> symbols = realloc(script_data -> symbols, sizeof(struct symbol *) * (script_data -> symbol_count + 1)); + script_data -> symbols[script_data -> symbol_count ++] = new_symbol(name, value); +} + +void create_definition (const char * name, unsigned value) { + script_data -> definitions = realloc(script_data -> definitions, sizeof(struct symbol *) * (script_data -> definition_count + 1)); + script_data -> definitions[script_data -> definition_count ++] = new_symbol(name, value); +} + +void create_reference (const char * target, unsigned value) { + script_data -> references = realloc(script_data -> references, sizeof(struct symbol *) * (script_data -> reference_count + 1)); + script_data -> references[script_data -> reference_count ++] = new_symbol(target, value); +} + +struct symbol * new_symbol (const char * name, unsigned value) { + struct symbol * result = malloc(sizeof(struct symbol) + strlen(name) + 1); + result -> value = value; + strcpy(result -> name, name); + return result; +} + +void resolve (const struct symbol * symbol) { + unsigned pos = 0; + while (pos < script_data -> reference_count) { + if (strcmp(symbol -> name, script_data -> references[pos] -> name)) { + pos ++; + continue; + } + write_word_to_buffer(script_data -> data + script_data -> references[pos] -> value, symbol -> value); + free(script_data -> references[pos]); + script_data -> references[pos] = script_data -> references[-- script_data -> reference_count]; + script_data -> references = realloc(script_data -> references, sizeof(struct symbol *) * script_data -> reference_count); + } +} + +int find_unquoted_character (const char * string, char character) { + unsigned char in_quotes = 0; + unsigned pos; + const char * p; + for (p = string, pos = 0; *p; p ++, pos ++) { + if (*p == '"') { + in_quotes ^= 1; + continue; + } + if (in_quotes) continue; + if (*p == character) return pos; + } + if (in_quotes) error_exit(1, "mismatched quotes in string: %s", string); + return -1; +} + +char * trim_string (char * string) { + while ((*string == ' ') || (*string == '\t')) string ++; + unsigned effective_length = strlen(string); + while (effective_length && ((string[effective_length - 1] == ' ') || (string[effective_length - 1] == '\t'))) effective_length --; + char * result = malloc(effective_length + 1); + memcpy(result, string, effective_length); + result[effective_length] = 0; + return result; +} + +char * duplicate_string (const char * string) { + return strcpy(malloc(strlen(string) + 1), string); +} + +char ** extract_components_from_line (const char * line) { + char * copy = duplicate_string(line); + int pos = find_unquoted_character(copy, ';'); + if (pos >= 0) copy[pos] = 0; + char * current = copy + strspn(copy, " \t"); + if ((!*current)) { + free(copy); + return NULL; + } + pos = strcspn(current, " \t"); + char ** result; + if (!current[pos]) { + result = malloc(sizeof(char *) * 2); + *result = duplicate_string(current); + result[1] = NULL; + return result; + } + result = malloc(sizeof(char *)); + current[pos] = 0; + *result = duplicate_string(current); + current += pos + 1; + unsigned components = 1; + char * component; + while (pos >= 0) { + pos = find_unquoted_character(current, ','); + if (pos >= 0) current[pos] = 0; + component = trim_string(current); + if (pos >= 0) current += pos + 1; + if (*component) { + result = realloc(result, sizeof(char *) * (components + 1)); + result[components ++] = component; + } else + free(component); + } + result = realloc(result, sizeof(char *) * (components + 1)); + result[components] = NULL; + return result; +} + +void destroy_component_array (char ** array) { + char ** current; + for (current = array; *current; current ++) free(*current); + free(array); +} + +unsigned count_parameters (char ** components) { + if (!components) return -1; + unsigned count = 0; + while (components[count ++]); + return count - 1; +} + +unsigned char get_hex_digit (char digit) { + if ((digit >= '0') && (digit <= '9')) return digit - '0'; + if ((digit >= 'A') && (digit <= 'F')) return digit - ('A' - 10); + if ((digit >= 'a') && (digit <= 'f')) return digit - ('a' - 10); + return 0; +} + +unsigned convert_string_to_number (const char * string) { + if (!*string) error_exit(1, "argument is empty"); + char * error_pointer; + unsigned long long value = strtoull(string, &error_pointer, 0); + if (*error_pointer) error_exit(1, "invalid number: %s", string); + return value; +} + +FILE * open_text_file (const char * file) { + return open_file(file, "r", "reading"); +} + +FILE * open_binary_file (const char * file) { + return open_file(file, "rb", "reading"); +} + +FILE * open_binary_file_for_writing (const char * file) { + return open_file(file, "wb", "writing"); +} + +FILE * open_file (const char * file, const char * mode, const char * mode_description) { + FILE * fp = fopen(file, mode); + if (!fp) error_exit(1, "could not open file %s for %s", file, mode_description); + return fp; +} + +char * read_line (FILE * fp) { + char * line = NULL; + unsigned length = 0; + int c; + while (1) { + c = getc(fp); + if ((c == EOF) || (c == '\n')) break; + line = realloc(line, length + 1); + line[length ++] = c; + } + line = realloc(line, length + 1); + line[length] = 0; + return line; +} + +char * get_line_from_input (void) { + return read_line(file_stack -> fp); +} + +void push_file (const char * file) { + current_file = NULL; + FILE * fp = open_text_file(file); + char * filename = duplicate_string(file); + if (file_stack_length) file_stack -> line = current_line; + file_stack = realloc(file_stack, sizeof(struct file_stack_entry) * (file_stack_length + 1)); + memmove(file_stack + 1, file_stack, file_stack_length * sizeof(struct file_stack_entry)); + file_stack_length ++; + *file_stack = (struct file_stack_entry) {.fp = fp, .name = filename, .line = 0}; + current_file = filename; + current_line = 0; +} + +void pop_file (void) { + fclose(file_stack -> fp); + free(file_stack -> name); + file_stack_length --; + memmove(file_stack, file_stack + 1, file_stack_length * sizeof(struct file_stack_entry)); + if (file_stack_length) { + file_stack = realloc(file_stack, sizeof(struct file_stack_entry) * file_stack_length); + current_file = file_stack -> name; + current_line = file_stack -> line; + } else { + free(file_stack); + file_stack = NULL; + current_file = NULL; + current_line = 0; + } +} + +void write_data_to_file (FILE * fp, const char * data, unsigned length) { + unsigned rv; + while (length) { + rv = fwrite(data, 1, (length > 65536) ? 65536 : length, fp); + if (!rv) error_exit(1, "could not write output data"); + data += rv; + length -= rv; + } +} + +void process_input_line (const char * line) { + if (!*line) return; + char * pos; + char * copy; + if (!strchr("\t ", *line)) { + pos = strchr(line, ':'); + if (pos) { + copy = malloc(pos - line + 1); + memcpy(copy, line, pos - line); + copy[pos - line] = 0; + line = pos + 1; + } else { + copy = duplicate_string(line); + line = NULL; + } + declare_label(copy); + free(copy); + if (!(line && *line)) return; + } + char ** components = extract_components_from_line(line); + if (!components) return; + struct command * command = find_command(*components); + if (!command) error_exit(1, "unknown command: %s", *components); + command -> parser(command -> argument, components + 1); + destroy_component_array(components); +} + +void declare_label (const char * label) { + int valid; + if (*label == '.') + valid = validate_label(label + 1); + else { + flush_locals(); + valid = validate_label(label); + } + if (!valid) error_exit(1, "invalid label: %s", label); + if (find_symbol(label)) error_exit(1, "label '%s' already exists", label); + if (find_definition(label)) error_exit(1, "label '%s' overrides a define statement", label); + create_symbol(label, script_data -> length); +} + +struct command * find_command (const char * command_name) { + struct command * result; + for (result = script_commands; result -> name; result ++) if (!strcmp(result -> name, command_name)) return result; + return NULL; +} + +void flush_locals (void) { + unsigned pos = 0; + while (pos < script_data -> symbol_count) { + if (*(script_data -> symbols[pos] -> name) != '.') { + pos ++; + continue; + } + resolve(script_data -> symbols[pos]); + free(script_data -> symbols[pos]); + script_data -> symbols[pos] = script_data -> symbols[-- script_data -> symbol_count]; + script_data -> symbols = realloc(script_data -> symbols, sizeof(struct symbol *) * script_data -> symbol_count); + } + for (pos = 0; pos < script_data -> reference_count; pos ++) if (*(script_data -> references[pos] -> name) == '.') + error_exit(1, "encountered new global label before local '%s' was resolved", script_data -> references[pos] -> name); +} + +void flush_all_symbols (void) { + unsigned pos; + for (pos = 0; pos < script_data -> symbol_count; pos ++) { + resolve(script_data -> symbols[pos]); + free(script_data -> symbols[pos]); + } + free(script_data -> symbols); + script_data -> symbols = NULL; + script_data -> symbol_count = 0; + if (script_data -> reference_count) error_exit(1, "unresolved label: %s", (**(script_data -> references)).name); + free(script_data -> references); + script_data -> references = NULL; +} + +int validate_label (const char * label) { + return (*label) && (strspn(label, VALID_ID_CHARACTERS) == strlen(label)) && strchr(LETTERS_OR_UNDERSCORE, *label); +} + +struct argument * get_argument (const char * string) { + struct argument * result; + if (*string == '#') { + result = get_argument(string + 1); + if (result -> kind) error_exit(1, "invalid variable argument: %s", string); + if (result -> value > 255) error_exit(1, "invalid variable number: %u", result -> value); + result -> kind = 1; + return result; + } + if (strchr(NUMERIC_CHARACTERS, *string)) { + result = malloc(sizeof(struct argument)); + result -> kind = 0; + result -> value = convert_string_to_number(string); + return result; + } + struct symbol * definition = find_definition(string); + if (definition) { + result = malloc(sizeof(struct argument)); + result -> kind = 0; + result -> value = definition -> value; + return result; + } + if (!(validate_label(string) || ((*string == '.') && validate_label(string + 1)))) error_exit(1, "invalid label identifier: %s", string); + result = malloc(sizeof(struct argument) + strlen(string) + 1); + result -> kind = 2; + strcpy(result -> reference, string); + return result; +} + +char * get_string_argument (const char * argument) { + if (*argument != '"') error_exit(1, "unquoted string"); + char * result = duplicate_string(argument + 1); + unsigned length = strlen(result) - 1; + if (result[length] != '"') error_exit(1, "unquoted string"); + result[length] = 0; + char * pos = result; + while ((pos = strstr(pos, "\"\""))) { + pos ++; + memmove(pos, pos + 1, strlen(pos)); + } + result = realloc(result, strlen(result) + 1); + return result; +} + +void no_arguments_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 0, 0, arguments); +} + +void one_argument_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 0, 1, arguments); +} + +void two_arguments_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 0, 2, arguments); +} + +void one_variable_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 1, 0, arguments); +} + +void one_variable_one_argument_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 1, 1, arguments); +} + +void one_variable_two_arguments_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 1, 2, arguments); +} + +void two_variables_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 2, 0, arguments); +} + +void two_variables_two_arguments_command (int opcode_byte, char ** arguments) { + standard_command(opcode_byte, 2, 2, arguments); +} + +void one_byte_argument_command (int opcode_byte, char ** arguments) { + if (count_parameters(arguments) != 1) error_exit(1, "command expects 1 argument(s), got %u", count_parameters(arguments)); + struct argument * argument = get_argument(*arguments); + if (argument -> kind == 2) error_exit(1, "cannot use a reference as a byte-sized argument"); + char buffer[2]; + *buffer = opcode_byte + argument -> kind; + buffer[1] = argument -> value; + free(argument); + append_data_to_script(buffer, 2); +} + +void one_halfword_argument_command (int opcode_byte, char ** arguments) { + if (count_parameters(arguments) != 1) error_exit(1, "command expects 1 argument(s), got %u", count_parameters(arguments)); + struct argument * argument = get_argument(*arguments); + if (argument -> kind == 2) error_exit(1, "cannot use a reference as a halfword-sized argument"); + char buffer[3]; + *buffer = opcode_byte + argument -> kind; + if (argument -> kind) { + buffer[1] = argument -> value; + append_data_to_script(buffer, 2); + } else { + write_halfword_to_buffer(buffer + 1, argument -> value); + append_data_to_script(buffer, 3); + } + free(argument); +} + +void calculation_command (int opcode_byte, char ** arguments) { + if (count_parameters(arguments) != 2) { + standard_command(opcode_byte, 1, 2, arguments); + return; + } + struct argument * argument = get_argument(*arguments); + if (argument -> kind != 1) error_exit(1, "argument must be a variable"); + char buffer[7]; + *buffer = opcode_byte + 2; + buffer[1] = buffer[2] = argument -> value; + free(argument); + argument = get_argument(arguments[1]); + switch (argument -> kind) { + case 0: + write_word_to_buffer(buffer + 3, argument -> value); + append_data_to_script(buffer, 7); + break; + case 1: + buffer[3] = argument -> value; + (*buffer) ++; + append_data_to_script(buffer, 4); + break; + case 2: + memset(buffer + 3, 0, 4); + create_reference(argument -> reference, script_data -> length + 3); + append_data_to_script(buffer, 7); + } + free(argument); +} + +void bit_shift_command (int bit_shift_type, char ** arguments) { + unsigned char shorthand; + switch (count_parameters(arguments)) { + case 2: + shorthand = 1; + break; + case 3: + shorthand = 0; + break; + default: + error_exit(1, "command expects 3 argument(s), got %u", count_parameters(arguments)); + } + struct argument * argument = get_argument(*arguments); + if (argument -> kind != 1) error_exit(1, "argument must be a variable"); + unsigned char variable = argument -> value; + free(argument); + unsigned char shift_type, shift_count; + argument = get_argument(arguments[shorthand ? 1 : 2]); + if (argument -> kind == 2) error_exit(1, "cannot use a reference as a shift count"); + if ((!argument -> kind) && (argument -> value > 31) && (argument -> value < -31u)) error_exit(1, "shift count must be between -31 and 31"); + shift_type = argument -> kind; + shift_count = argument -> value; + free(argument); + if (!shift_type) shift_count &= 31; + argument = shorthand ? NULL : get_argument(arguments[1]); + unsigned char buffer[8]; + unsigned char buffer_length; + if (!(shift_type || shift_count)) { + *buffer = SET_OPCODE; + if (shorthand || (argument -> kind == 1)) (*buffer) ++; + buffer_length = 1; + } else { + *buffer = SHIFT_OPCODE; + bit_shift_type &= 3; + buffer[1] = (shift_type ? 0 : shift_count) | (bit_shift_type << 5); + if (shorthand || (argument -> kind == 1)) buffer[1] |= 0x80; + buffer_length = 2; + } + buffer[buffer_length ++] = variable; + if (shorthand) + buffer[buffer_length ++] = variable; + else + switch (argument -> kind) { + case 0: + write_word_to_buffer(buffer + buffer_length, argument -> value); + buffer_length += 4; + break; + case 1: + buffer[buffer_length ++] = argument -> value; + break; + case 2: + create_reference(argument -> reference, script_data -> length + buffer_length); + memset(buffer + buffer_length, 0, 4); + buffer_length += 4; + } + free(argument); + if (shift_type) buffer[buffer_length ++] = shift_count; + append_data_to_script((char *) buffer, buffer_length); +} + +void one_argument_one_byte_argument (int opcode_byte, char ** arguments) { + if (count_parameters(arguments) != 2) error_exit(1, "command expects 2 argument(s), got %u", count_parameters(arguments)); + struct argument * argument = get_argument(*arguments); + char buffer[6]; + char * current = buffer + 1; + switch (argument -> kind) { + case 0: + write_word_to_buffer(current, argument -> value); + current += 4; + break; + case 1: + *(current ++) = argument -> value; + opcode_byte += 2; + break; + case 2: + create_reference(argument -> reference, script_data -> length + 1); + memset(buffer, 0, 4); + current += 4; + } + free(argument); + argument = get_argument(arguments[1]); + if (argument -> kind == 2) error_exit(1, "cannot use a reference as a byte-sized argument"); + opcode_byte += argument -> kind; + *(current ++) = argument -> value; + free(argument); + *buffer = opcode_byte; + append_data_to_script(buffer, current - buffer); +} + +void one_argument_one_halfword_argument (int opcode_byte, char ** arguments) { + if (count_parameters(arguments) != 2) error_exit(1, "command expects 2 argument(s), got %u", count_parameters(arguments)); + struct argument * argument = get_argument(*arguments); + char buffer[7]; + char * current = buffer + 1; + switch (argument -> kind) { + case 0: + write_word_to_buffer(current, argument -> value); + current += 4; + break; + case 1: + *(current ++) = argument -> value; + opcode_byte += 2; + break; + case 2: + create_reference(argument -> reference, script_data -> length + 1); + memset(buffer, 0, 4); + current += 4; + } + free(argument); + argument = get_argument(arguments[1]); + if (argument -> kind == 2) error_exit(1, "cannot use a reference as a halfword-sized argument"); + opcode_byte += argument -> kind; + if (argument -> kind) + *(current ++) = argument -> value; + else { + write_halfword_to_buffer(current, argument -> value); + current += 2; + } + free(argument); + *buffer = opcode_byte; + append_data_to_script(buffer, current - buffer); +} + +void mulacum_command (int opcode_byte, char ** arguments) { + if (count_parameters(arguments) != 3) error_exit(1, "command expects 3 argument(s), got %u", count_parameters(arguments)); + struct argument * argument = get_argument(*(arguments ++)); + char buffer[11]; + char * current = buffer + 3; + unsigned char arg_number = 2; + *buffer = opcode_byte; + if (argument -> kind != 1) error_exit(1, "argument must be a variable"); + buffer[1] = buffer[2] = argument -> value; + free(argument); + while (arg_number --) { + argument = get_argument(*(arguments ++)); + switch (argument -> kind) { + case 0: + write_word_to_buffer(current, argument -> value); + current += 4; + break; + case 1: + *(current ++) = argument -> value; + *buffer |= 1 << arg_number; + break; + case 2: + create_reference(argument -> reference, script_data -> length + (current - buffer)); + memset(current, 0, 4); + current += 4; + } + free(argument); + } + append_data_to_script(buffer, current - buffer); +} + +void standard_command (unsigned char opcode_byte, unsigned char expected_variable_count, unsigned char expected_argument_count, char ** arguments) { + unsigned temp = count_parameters(arguments); + if (temp != (expected_variable_count + expected_argument_count)) + error_exit(1, "command expects %hhu argument(s), got %u", expected_variable_count + expected_argument_count, temp); + unsigned char * buffer = calloc(1, 1 + expected_variable_count + 4 * expected_argument_count); + unsigned char * current = buffer + 1; + struct argument * argument; + while (expected_variable_count --) { + argument = get_argument(*(arguments ++)); + if (argument -> kind != 1) error_exit(1, "argument must be a variable"); + *(current ++) = argument -> value; + free(argument); + } + while (expected_argument_count --) { + argument = get_argument(*(arguments ++)); + switch (argument -> kind) { + case 0: + write_word_to_buffer(current, argument -> value); + current += 4; + break; + case 1: + *(current ++) = argument -> value; + opcode_byte |= 1 << expected_argument_count; + break; + case 2: + create_reference(argument -> reference, script_data -> length + (current - buffer)); + current += 4; + } + free(argument); + } + *buffer = opcode_byte; + append_data_to_script((char *) buffer, current - buffer); + free(buffer); +} + +void data_command (int width, char ** arguments) { + struct argument * argument; + char buffer[4]; + char * string; + unsigned length; + for (; *arguments; arguments ++) { + if (**arguments == '"') { + string = get_string_argument(*arguments); + memset(buffer, 0, 4); + length = strlen(string); + append_data_to_script(string, length); + free(string); + if (length % width) append_data_to_script(buffer, width - (length % width)); + continue; + } + argument = get_argument(*arguments); + switch (argument -> kind) { + case 0: + write_word_to_buffer(buffer, argument -> value); + append_data_to_script(buffer, width); + break; + case 1: + error_exit(1, "variables are not allowed in data commands"); + // fallthrough + case 2: + if (width != 4) error_exit(1, "references are not allowed in data commands narrower than 32 bits"); + memset(buffer, 0, 4); + create_reference(argument -> reference, script_data -> length); + append_data_to_script(buffer, 4); + } + free(argument); + } +} + +void hexdata_command (__attribute__((unused)) int _, char ** arguments) { + char * buffer; + unsigned argument_number, length, pos; + for (argument_number = 0; arguments[argument_number]; argument_number ++) { + if (strspn(arguments[argument_number], "0123456789abcdefABCDEF") != strlen(arguments[argument_number])) + error_exit(1, "argument %u to hexdata is not a valid hex string", argument_number + 1); + length = (strlen(arguments[argument_number]) + 1) >> 1; + buffer = malloc(length); + for (pos = 0; pos < length; pos ++) + buffer[pos] = (get_hex_digit(arguments[argument_number][pos << 1]) << 4) | get_hex_digit(arguments[argument_number][(pos << 1) + 1]); + append_data_to_script(buffer, length); + free(buffer); + } +} + +void define_command (__attribute__((unused)) int _, char ** arguments) { + if (count_parameters(arguments) != 2) error_exit(1, "command expects 2 argument(s), got %u", count_parameters(arguments)); + if (!validate_label(*arguments)) error_exit(1, "'%s' is not a valid symbol name", *arguments); + struct argument * argument = get_argument(arguments[1]); + if (argument -> kind) error_exit(1, "the second argument to define must be a number (got: %s)", arguments[1]); + unsigned value = argument -> value; + free(argument); + struct symbol * definition = find_definition(*arguments); + if (definition) + definition -> value = value; + else + create_definition(*arguments, value); +} + +void include_command (int is_binary, char ** arguments) { + if (count_parameters(arguments) != 1) error_exit(1, "command expects 1 argument(s), got %u", count_parameters(arguments)); + char * filename = get_string_argument(*arguments); + if (is_binary) + append_binary_file_to_script(filename); + else + push_file(filename); + free(filename); +} + +void string_command (__attribute__((unused)) int _, char ** arguments) { + char * string; + for (; *arguments; arguments ++) { + string = get_string_argument(*arguments); + append_data_to_script(string, strlen(string) + 1); + free(string); + } +} + +void align_command (__attribute__((unused)) int _, char ** arguments) { + if (count_parameters(arguments) != 1) error_exit(1, "command expects 1 argument(s), got %u", count_parameters(arguments)); + struct argument * argument = get_argument(*arguments); + if (argument -> kind || !(argument -> value)) error_exit(1, "the argument to align must be a non-zero number (got: %s)", *arguments); + unsigned alignment = argument -> value; + free(argument); + if (!(script_data -> length % alignment)) return; + alignment -= script_data -> length % alignment; + void * padding = calloc(alignment, 1); + append_data_to_script(padding, alignment); + free(padding); +} diff --git a/tools/bsp/bsppatch.js b/tools/bsp/bsppatch.js new file mode 100644 index 0000000000..d830215cf6 --- /dev/null +++ b/tools/bsp/bsppatch.js @@ -0,0 +1,1260 @@ +function BSPPatcher (bsp, input) { + "use strict"; + + var self = this; + + function ResizableMemoryBlock (initial_size = 0) { + var self = this; + + var current_size; + var parts = []; + + for (current_size = 0; current_size < initial_size; current_size += 8192) parts[current_size >>> 13] = new DataView(new ArrayBuffer(8192)); + current_size = initial_size; + + self.size = function () { + return current_size; + }; + + function shrink (size) { + var new_parts = (size + 8191) >>> 13; + var old_offset = current_size & 8191, new_offset = size & 8191; + if (old_offset === 0) old_offset = 8192; + if (new_offset === 0) new_offset = 8192; + if (new_parts != parts.length) { + while (parts.length > new_parts) parts.pop(); + old_offset = 8192; + } + var offset, part = parts.length - 1; + for (offset = new_offset; offset < old_offset; offset ++) parts[part].setUint8(offset, 0); + current_size = size; + } + + function expand (size) { + var new_parts = (size + 8191) >>> 13; + while (parts.length < new_parts) parts.push(new DataView(new ArrayBuffer(8192))); + current_size = size; + } + + self.resize = function (size) { + if (size == current_size) return; + if (size > current_size) return expand(size); + return shrink(size); + }; + + function break_into_bytes (value, size) { + var bytes = []; + while (bytes.length < size) { + bytes.push(value & 0xff); + value >>>= 8; + } + return bytes; + } + + self.set_byte = function (position, value) { + if (position >= current_size) expand(position + 1); + parts[position >>> 13].setUint8(position & 8191, value & 0xff); + }; + + self.set_halfword = function (position, value) { + if (position > (current_size - 2)) expand(position + 2); + var offset = position & 8191, part = position >>> 13; + value &= 0xffff; + if (offset <= 8190) return parts[part].setUint16(offset, value, true); + var bytes = break_into_bytes(value, 2); + while (bytes.length > 0) { + parts[part].setUint8(offset ++, bytes.shift()); + if (offset == 8192) { + part ++; + offset = 0; + } + } + }; + + self.set_word = function (position, value) { + if (position > (current_size - 4)) expand(position + 4); + var offset = position & 8191, part = position >>> 13; + value >>>= 0; + if (offset <= 8188) return parts[part].setUint32(offset, value, true); + var bytes = break_into_bytes(value, 4); + while (bytes.length > 0) { + parts[part].setUint8(offset ++, bytes.shift()); + if (offset == 8192) { + part ++; + offset = 0; + } + } + }; + + function gather_bytes (bytes) { + var result = 0; + while (bytes.length) result = ((result << 8) >>> 0) + bytes.pop(); + return result; + } + + self.get_byte = function (position) { + if (position >= current_size) throw "attempted to read past the end of the buffer"; + return parts[position >>> 13].getUint8(position & 8191); + }; + + self.get_halfword = function (position) { + if (position > (current_size - 2)) throw "attempted to read past the end of the buffer"; + var offset = position & 8191, part = position >>> 13; + if (offset <= 8190) return parts[part].getUint16(offset, true); + var bytes = []; + var count; + for (count = 0; count < 2; count ++) { + bytes.push(parts[part].getUint8(offset ++)); + if (offset == 8192) { + offset = 0; + part ++; + } + } + return gather_bytes(bytes); + }; + + self.get_word = function (position) { + if (position > (current_size - 4)) throw "attempted to read past the end of the buffer"; + var offset = position & 8191, part = position >>> 13; + if (offset <= 8188) return parts[part].getUint32(offset, true); + var bytes = []; + var count; + for (count = 0; count < 4; count ++) { + bytes.push(parts[part].getUint8(offset ++)); + if (offset == 8192) { + offset = 0; + part ++; + } + } + return gather_bytes(bytes); + }; + + self.generate_array_buffer = function () { + var result = new ArrayBuffer(current_size); + var view = new Uint8Array(result); + var part_count = current_size >>> 13, extra_offset = current_size & 8191; + var pos; + for (pos = 0; pos < part_count; pos ++) view.set(new Uint8Array(parts[pos].buffer), (pos << 13) >>> 0); + for (pos = 0; pos < extra_offset; pos ++) view[pos + ((part_count << 13) >>> 0)] = parts[part_count].getUint8(pos); + return result; + }; + + self.calculate_sha1 = function () { + function rotate (value, count) { + return ((value << count) | (value >>> (32 - count))) >>> 0; + } + + function mixing_function (counter, first, second, third) { + if (counter < 20) + return ((first & second) | (~first & third)) >>> 0; + else if ((counter >= 40) && (counter < 60)) + return ((first & second) | (first & third) | (second & third)) >>> 0; + else + return (first ^ second ^ third) >>> 0; + } + + function hash_constant (counter) { + // constants used by SHA-1; they are actually simply the square roots of 2, 3, 5 and 10 as a fixed-point number (2.30 format) + return ([0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6])[(counter / 20) >>> 0]; + } + + function process_block (state, block) { + var words = new Uint32Array(80); + var pos; + for (pos = 0; pos < 16; pos ++) words[pos] = block.getUint32(pos << 2); + for (; pos < 80; pos ++) words[pos] = rotate((words[pos - 3] ^ words[pos - 8] ^ words[pos - 14] ^ words[pos - 16]) >>> 0, 1); + var temp, a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; + for (pos = 0; pos < 80; pos ++) { + temp = (rotate(a, 5) + mixing_function(pos, b, c, d) + e + words[pos] + hash_constant(pos)) >>> 0; + e = d; + d = c; + c = rotate(b, 30); + b = a; + a = temp; + } + return [(state[0] + a) >>> 0, (state[1] + b) >>> 0, (state[2] + c) >>> 0, (state[3] + d) >>> 0, (state[4] + e) >>> 0]; + } + + var state = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]; + var pos, buf; + for (pos = 0; pos < ((current_size & 0xffffffc0) >>> 0); pos += 64) + state = process_block(state, new DataView(parts[pos >>> 13].buffer.slice(pos & 8191, (pos & 8191) + 64))); + buf = new DataView(new ArrayBuffer(64)); + for (; pos < current_size; pos ++) buf.setUint8(pos & 63, parts[pos >>> 13].getUint8(pos & 8191)); + buf.setUint8(pos & 63, 0x80); + if ((pos & 63) >= 56) { + state = process_block(state, buf); + buf = new DataView(new ArrayBuffer(64)); + } + buf.setUint8(59, current_size >>> 29); + buf.setUint32(60, (current_size << 3) >>> 0); + state = process_block(state, buf); + buf = new Uint8Array(20); + for (pos = 0; pos < 20; pos ++) buf[pos] = (state[pos >> 2] >>> ((3 - (pos & 3)) << 3)) & 0xff; + return buf; + }; + } + + self.success = undefined; + self.failure = undefined; + self.error = undefined; + self.print = undefined; + self.menu = undefined; + + var initialized = false; + var done = false; + var exit_status; + var output; + + var file_buffer; + var current_file_pointer; + var current_file_pointer_locked; + var frames; + var selection_range_check; + var dirty; + var sha1; + + function get_byte (pos) { + if (pos >= frames[0].patch_space.byteLength) throw "attempted to read past the end of the patch space"; + return frames[0].patch_space.getUint8(pos); + } + + function get_halfword (pos) { + if ((pos + 2) > frames[0].patch_space.byteLength) throw "attempted to read past the end of the patch space"; + return frames[0].patch_space.getUint16(pos, true); + } + + function get_word (pos) { + if ((pos + 4) > frames[0].patch_space.byteLength) throw "attempted to read past the end of the patch space"; + return frames[0].patch_space.getUint32(pos, true); + } + + function next_patch_byte () { + if (frames[0].patch_space.byteLength < (frames[0].instruction_pointer + 1)) throw "attempted to read past the end of the patch space"; + return frames[0].patch_space.getUint8(frames[0].instruction_pointer ++); + } + + function next_patch_halfword () { + if (frames[0].patch_space.byteLength < (frames[0].instruction_pointer + 2)) throw "attempted to read past the end of the patch space"; + var result = frames[0].patch_space.getUint16(frames[0].instruction_pointer, true); + frames[0].instruction_pointer += 2; + return result; + } + + function next_patch_word () { + if (frames[0].patch_space.byteLength < (frames[0].instruction_pointer + 4)) throw "attempted to read past the end of the patch space"; + var result = frames[0].patch_space.getUint32(frames[0].instruction_pointer, true); + frames[0].instruction_pointer += 4; + return result; + } + + function next_patch_variable () { + return get_variable(next_patch_byte()); + } + + function update_current_file_pointer (value) { + if (current_file_pointer_locked) return; + current_file_pointer = value >>> 0; + } + + function read_byte (increment_file_pointer) { + if (current_file_pointer >= file_buffer.size()) throw "attempted to read past the end of the file buffer"; + var result = file_buffer.get_byte(current_file_pointer); + if (increment_file_pointer) update_current_file_pointer(current_file_pointer + 1); + return result; + } + + function read_halfword (increment_file_pointer) { + if ((current_file_pointer + 2) > file_buffer.size()) throw "attempted to read past the end of the file buffer"; + var result = file_buffer.get_halfword(current_file_pointer); + if (increment_file_pointer) update_current_file_pointer(current_file_pointer + 2); + return result; + } + + function read_word (increment_file_pointer) { + if ((current_file_pointer + 4) > file_buffer.size()) throw "attempted to read past the end of the file buffer"; + var result = file_buffer.get_word(current_file_pointer); + if (increment_file_pointer) update_current_file_pointer(current_file_pointer + 4); + return result; + } + + function write_byte (value) { + if (current_file_pointer > 0xfffffffe) throw "current file pointer overflow"; + file_buffer.set_byte(current_file_pointer, value); + dirty = true; + update_current_file_pointer(current_file_pointer + 1); + } + + function write_halfword (value) { + if (current_file_pointer > 0xfffffffd) throw "current file pointer overflow"; + file_buffer.set_halfword(current_file_pointer, value); + dirty = true; + update_current_file_pointer(current_file_pointer + 2); + } + + function write_word (value) { + if (current_file_pointer > 0xfffffffb) throw "current file pointer overflow"; + file_buffer.set_word(current_file_pointer, value); + dirty = true; + update_current_file_pointer(current_file_pointer + 4); + } + + function truncate (value) { + if (value > 0xffffffff) throw "file buffer size overflow"; + if (value < 0) throw "file buffer size underflow"; + file_buffer.resize(value); + dirty = true; + } + + function utf8_decode (address) { + var codepoints = []; + var current_codepoint, remaining, next, peek; + while ((next = get_byte(address ++)) !== 0) { + if (remaining > 0) { + if ((next < 0x80) || (next > 0xbf)) throw "invalid UTF-8 string"; + current_codepoint |= (next & 0x3f) << (6 * (-- remaining)); + if (remaining === 0) codepoints.push(current_codepoint); + continue; + } + if (next < 0x80) { + codepoints.push(next); + continue; + } + if ((next < 0xc2) || (next > 0xf4)) throw "invalid UTF-8 string"; + peek = get_byte(address); + if ( + ((next == 0xe0) && (peek < 0xa0)) || + ((next == 0xed) && (peek >= 0xa0)) || + ((next == 0xf0) && (peek < 0x90)) || + ((next == 0xf4) && (peek >= 0x90)) + ) throw "invalid UTF-8 string"; + if (next < 0xe0) { + remaining = 1; + current_codepoint = (next & 0x1f) << 6; + } else if (next < 0xf0) { + remaining = 2; + current_codepoint = (next & 0x0f) << 12; + } else { + remaining = 3; + current_codepoint = (next & 7) << 18; + } + } + if (remaining > 0) throw "invalid UTF-8 string"; + var result = ""; + for (current_codepoint = 0; current_codepoint < codepoints.length; current_codepoint ++) { + if (codepoints[current_codepoint] < 0x10000) { + result += String.fromCharCode(codepoints[current_codepoint]); + continue; + } + result += String.fromCharCode(0xd800 | ((codepoints[current_codepoint] - 0x10000) >> 10)); + result += String.fromCharCode(0xdc00 | (codepoints[current_codepoint] & 0x3ff)); + } + return result; + } + + function update_hashes () { + if (!dirty) return; + sha1 = file_buffer.calculate_sha1(); + dirty = false; + } + + function write_data (position, address, len) { + if (len === 0) return; + while (len --) file_buffer.set_byte(position ++, get_byte(address ++)); + dirty = true; + } + + function xor_data (position, address, len) { + if (len === 0) return; + var value; + while (len --) { + value = (get_byte(address ++) ^ file_buffer.get_byte(position)) & 0xff; + file_buffer.set_byte(position ++, value); + } + dirty = true; + } + + function calculate_real_stack_position (position) { + if (position >= 0x80000000) position = frames[0].stack.length + (position - 0x100000000); + if ((position < 0) || (position >= frames[0].stack.length)) throw "invalid stack position"; + return position; + } + + function array_repeat (value, count) { + var pos, array = []; + for (pos = 31; pos >= 0; pos --) { + array = array.concat(array); + if ((count >> pos) & 1) array.push(value); + } + return array; + } + + function resize_stack (size) { + if (size < frames[0].stack.length) { + frames[0].stack.splice(0, frames[0].stack.length - size); + return; + } + while (frames[0].stack.length < size) push_to_stack(0); + } + + function opcode_parameters (opcode) { + switch (opcode) { + case 0x00: case 0x01: case 0x80: case 0x81: case 0x82: case 0x92: case 0x93: case 0xa6: case 0xa7: + return []; + case 0x02: case 0x04: case 0x06: case 0x08: case 0x1c: case 0x1e: case 0x60: case 0x62: case 0x64: case 0x66: case 0x68: case 0x8e: + case 0xa0: case 0xa2: case 0xa4: case 0xa8: + return [next_patch_word]; + case 0x03: case 0x05: case 0x07: case 0x09: case 0x19: case 0x1b: case 0x1d: case 0x1f: case 0x61: case 0x63: case 0x65: case 0x67: + case 0x69: case 0x83: case 0x8f: case 0x90: case 0x91: case 0xa1: case 0xa3: case 0xa5: case 0xa9: + return [next_patch_variable]; + case 0x10: case 0x12: case 0x14: case 0x16: case 0x6a: case 0x84: case 0x86: case 0x8c: + return [next_patch_byte, next_patch_word]; + case 0x11: case 0x13: case 0x15: case 0x17: case 0x6b: case 0x85: case 0x87: case 0x8d: case 0xaf: + return [next_patch_byte, next_patch_variable]; + case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x18: case 0x9b: case 0x9f: case 0xaa: case 0xac: case 0xad: + case 0xae: + return [next_patch_byte]; + case 0x1a: + return [next_patch_halfword]; + case 0x20: case 0x24: case 0x28: case 0x2c: case 0x30: case 0x34: case 0x38: case 0x3c: case 0x94: + return [next_patch_byte, next_patch_word, next_patch_word]; + case 0x21: case 0x25: case 0x29: case 0x2d: case 0x31: case 0x35: case 0x39: case 0x3d: case 0x95: + return [next_patch_byte, next_patch_word, next_patch_variable]; + case 0x22: case 0x26: case 0x2a: case 0x2e: case 0x32: case 0x36: case 0x3a: case 0x3e: case 0x96: + return [next_patch_byte, next_patch_variable, next_patch_word]; + case 0x23: case 0x27: case 0x2b: case 0x2f: case 0x33: case 0x37: case 0x3b: case 0x3f: case 0x97: + return [next_patch_byte, next_patch_variable, next_patch_variable]; + case 0x40: case 0x44: case 0x48: case 0x4c: case 0x50: case 0x54: + return [next_patch_variable, next_patch_word, next_patch_word]; + case 0x41: case 0x45: case 0x49: case 0x4d: case 0x51: case 0x55: + return [next_patch_variable, next_patch_word, next_patch_variable]; + case 0x42: case 0x46: case 0x4a: case 0x4e: case 0x52: case 0x56: + return [next_patch_variable, next_patch_variable, next_patch_word]; + case 0x43: case 0x47: case 0x4b: case 0x4f: case 0x53: case 0x57: + return [next_patch_variable, next_patch_variable, next_patch_variable]; + case 0x58: case 0x5a: case 0x5c: case 0x5e: case 0x6e: case 0x7a: case 0x7e: case 0x8a: + return [next_patch_variable, next_patch_word]; + case 0x59: case 0x5b: case 0x5d: case 0x5f: case 0x6f: case 0x73: case 0x77: case 0x7b: case 0x7f: case 0x8b: + return [next_patch_variable, next_patch_variable]; + case 0x6c: case 0x78: case 0x7c: case 0x88: + return [next_patch_word, next_patch_word]; + case 0x6d: case 0x71: case 0x75: case 0x79: case 0x7d: case 0x89: + return [next_patch_word, next_patch_variable]; + case 0x70: + return [next_patch_word, next_patch_byte]; + case 0x72: + return [next_patch_variable, next_patch_byte]; + case 0x74: + return [next_patch_word, next_patch_halfword]; + case 0x76: + return [next_patch_variable, next_patch_halfword]; + case 0x98: case 0x99: case 0x9a: case 0x9c: case 0x9d: case 0x9e: case 0xab: + return [next_patch_byte, next_patch_byte]; + case 0xb0: case 0xb4: case 0xb8: case 0xbc: + return [next_patch_byte, next_patch_byte, next_patch_word, next_patch_word]; + case 0xb1: case 0xb5: case 0xb9: case 0xbd: + return [next_patch_byte, next_patch_byte, next_patch_word, next_patch_variable]; + case 0xb2: case 0xb6: case 0xba: case 0xbe: + return [next_patch_byte, next_patch_byte, next_patch_variable, next_patch_word]; + case 0xb3: case 0xb7: case 0xbb: case 0xbf: + return [next_patch_byte, next_patch_byte, next_patch_variable, next_patch_variable]; + default: + throw "undefined opcode"; + } + } + + function return_opcode () { + if (frames[0].stack.length < 1) return 0; + frames[0].instruction_pointer = pop_from_stack(); + return true; + } + + function jump_opcode (target) { + frames[0].instruction_pointer = target; + return true; + } + + function call_opcode (target) { + push_to_stack(frames[0].instruction_pointer); + return jump_opcode(target); + } + + function exit_opcode (value) { + return value; + } + + function push_opcode (value) { + push_to_stack(value); + return true; + } + + function pop_opcode (variable) { + set_variable(variable, pop_from_stack()); + return true; + } + + function length_opcode (variable) { + set_variable(variable, file_buffer.size()); + return true; + } + + function readbyte_opcode (variable) { + set_variable(variable, read_byte(true)); + return true; + } + + function readhalfword_opcode (variable) { + set_variable(variable, read_halfword(true)); + return true; + } + + function readword_opcode (variable) { + set_variable(variable, read_word(true)); + return true; + } + + function pos_opcode (variable) { + set_variable(variable, current_file_pointer); + return true; + } + + function getbyte_opcode (variable, address) { + set_variable(variable, get_byte(address)); + return true; + } + + function gethalfword_opcode (variable, address) { + set_variable(variable, get_halfword(address)); + return true; + } + + function getword_opcode (variable, address) { + set_variable(variable, get_word(address)); + return true; + } + + function checksha1_opcode (variable, address) { + update_hashes(); + var pos, result = 0; + for (pos = 0; pos < 20; pos ++) if (get_byte(address + pos) != sha1[pos]) result |= 1 << pos; + set_variable(variable, result); + return true; + } + + function writebyte_opcode (value) { + write_byte(value & 0xff); + return true; + } + + function writehalfword_opcode (value) { + write_halfword(value & 0xffff); + return true; + } + + function writeword_opcode (value) { + write_word(value); + return true; + } + + function truncate_opcode (value) { + truncate(value); + return true; + } + + function calculation (op) { + return function (variable, first, second) { + var result = eval("first " + op + " second"); + result >>>= 0; + set_variable(variable, result); + return true; + }; + } + + function guarded_calculation (op) { + return function (variable, first, second) { + if (second === 0) throw "division by zero"; + var result = eval("first " + op + " second"); + result >>>= 0; + set_variable(variable, result); + return true; + }; + } + + function multiply (first, second) { + // lots of silliness to ensure 32-bit precision when carrying out the multiplication + var first_lower = first & 0xffff, first_upper = (first >>> 16) & 0xffff; + var second_lower = second & 0xffff, second_upper = (second >>> 16) & 0xffff; + var result_lower = first_lower * second_lower, result_upper = first_lower * second_upper + first_upper * second_lower; + return (((result_upper << 16) + result_lower) & 0xffffffff) >>> 0; + } + + function multiply_opcode (variable, first, second) { + set_variable(variable, multiply(first, second)); + return true; + } + + function conditional_jump_opcode (condition) { + return function (first, second, address) { + var check = eval("first " + condition + " second"); + if (!check) return true; + return jump_opcode(address); + }; + } + + function instruction_if_zero (fn, condition) { + return function (comparand, address = undefined) { + if ((comparand === 0) !== condition) return true; + return fn(address); + }; + } + + function seek_opcode (value) { + update_current_file_pointer(value); + return true; + } + + function seekfwd_opcode (value) { + if (current_file_pointer_locked) return true; + if ((value + current_file_pointer) > 0xffffffff) throw "current file pointer overflow"; + current_file_pointer += value; + return true; + } + + function seekback_opcode (value) { + if (current_file_pointer_locked) return true; + if (value > current_file_pointer) throw "current file pointer overflow"; + current_file_pointer -= value; + return true; + } + + function seekend_opcode (value) { + if (current_file_pointer_locked) return true; + if (value > file_buffer.size()) throw "current file pointer overflow"; + current_file_pointer = file_buffer.size() - value; + return true; + } + + function print_opcode (address) { + if (self.print === undefined) throw "could not display message to user"; + var msg = utf8_decode(address); + queue_function(function () {self.print(msg);}); + return false; + } + + function menu_opcode (variable, address) { + var options = []; + var option; + while (true) { + option = get_word(address); + if (option === 0xffffffff) break; + options.push(option); + address += 4; + } + if (options.length === 0) { + set_variable(variable, 0xffffffff); + return true; + } + if (self.menu === undefined) throw "could not display menu to user"; + options = options.map(utf8_decode); + frames[0].waiting_var = variable; + selection_range_check = options.length; + queue_function(function () {self.menu(options);}); + return false; + } + + function xordata_opcode (start, len) { + if ((current_file_pointer + len) > 0xffffffff) throw "file position overflow"; + if (current_file_pointer >= file_buffer.size()) return writedata_opcode(start, len); + var bytes_to_end = file_buffer.size() - current_file_pointer; + if (bytes_to_end >= len) + xor_data(current_file_pointer, start, len); + else { + xor_data(current_file_pointer, start, bytes_to_end); + write_data(current_file_pointer + bytes_to_end, start + bytes_to_end, len - bytes_to_end); + } + if (!current_file_pointer_locked) current_file_pointer += len; + return true; + } + + function fillbyte_opcode (count, value) { + if (count === 0) return true; + var address = current_file_pointer; + value &= 0xff; + if ((address + count) > 0xffffffff) throw "file position overflow"; + while (count --) file_buffer.set_byte(address ++, value); + if (!current_file_pointer_locked) current_file_pointer = address; + dirty = true; + return true; + } + + function fillhalfword_opcode (count, value) { + if (count === 0) return true; + var address = current_file_pointer; + value &= 0xffff; + if ((address + 2 * count) > 0xffffffff) throw "file position overflow"; + while (count --) { + file_buffer.set_halfword(address, value); + address += 2; + } + if (!current_file_pointer_locked) current_file_pointer = address; + dirty = true; + return true; + } + + function fillword_opcode (count, value) { + if (count === 0) return true; + var address = current_file_pointer; + if ((address + 4 * count) > 0xffffffff) throw "file position overflow"; + while (count --) { + file_buffer.set_word(address, value); + address += 4; + } + if (!current_file_pointer_locked) current_file_pointer = address; + dirty = true; + return true; + } + + function writedata_opcode (start, len) { + if ((current_file_pointer + len) > 0xffffffff) throw "file position overflow"; + write_data(current_file_pointer, start, len); + if (!current_file_pointer_locked) current_file_pointer += len; + return true; + } + + function lockpos_opcode () { + current_file_pointer_locked = true; + return true; + } + + function unlockpos_opcode () { + current_file_pointer_locked = false; + return true; + } + + function truncatepos_opcode () { + truncate(current_file_pointer); + return true; + } + + function jumptable_opcode (value) { + var address = value * 4 + frames[0].instruction_pointer; + if ((address + 4) > frames[0].patch_space.byteLength) throw "attempted to read past the end of the patch space"; + frames[0].instruction_pointer = get_word(address); + return true; + } + + function set_opcode (variable, value) { + set_variable(variable, value); + return true; + } + + function ipspatch_opcode (variable, address) { + var current_address = address; + + function get_next_byte () { + return get_byte(current_address ++); + } + + function get_next_value (bytes) { + var result = 0; + while (bytes > 0) { + result = (result << 8) | get_next_byte(); + bytes --; + } + return result; + } + + var header = [0x50, 0x41, 0x54, 0x43, 0x48]; + while (header.length > 0) if (get_next_byte() !== header.shift()) throw "invalid IPS header"; + var position, count, value; + while (true) { + position = get_next_value(3); + if (position === 0x454f46) break; + position += current_file_pointer; + if (position >= 0xffffffff) throw "file position overflow"; + count = get_next_value(2); + if (count === 0) { + count = get_next_value(2); + value = get_next_byte(); + while (count --) file_buffer.set_byte(position ++, value); + } else + while (count --) file_buffer.set_byte(position ++, get_next_byte()); + } + set_variable(variable, current_address); + dirty = true; + return true; + } + + function stackwrite_opcode (position, value) { + frames[0].stack[calculate_real_stack_position(position)] = value; + return true; + } + + function stackread_opcode (variable, position) { + set_variable(variable, frames[0].stack[calculate_real_stack_position(position)]); + return true; + } + + function stackshift_opcode (amount) { + if (amount >= 0x80000000) amount -= 0x100000000; + if ((amount + frames[0].stack.length) < 0) throw "stack underflow"; + resize_stack(amount + frames[0].stack.length); + return true; + } + + function pushpos_opcode () { + push_to_stack(current_file_pointer); + return true; + } + + function poppos_opcode () { + update_current_file_pointer(pop_from_stack()); + return true; + } + + function bsppatch_opcode (variable, start, len) { + if ((start + len) > frames[0].patch_space.byteLength) throw "attempted to read past the end of the patch space"; + if (len === 0) throw "invalid zero length"; + frames[0].waiting_var = variable; + var new_frame = create_frame(new DataView(frames[0].patch_space.buffer.slice(start, start + len))); + frames.unshift(new_frame); + return true; + } + + function getbyteinc_opcode (value_var, address_var) { + var address = get_variable(address_var); + set_variable(address_var, (address + 1) >>> 0); + set_variable(value_var, get_byte(address)); + return true; + } + + function gethalfwordinc_opcode (value_var, address_var) { + var address = get_variable(address_var); + set_variable(address_var, (address + 2) >>> 0); + set_variable(value_var, get_halfword(address)); + return true; + } + + function getwordinc_opcode (value_var, address_var) { + var address = get_variable(address_var); + set_variable(address_var, (address + 4) >>> 0); + set_variable(value_var, get_word(address)); + return true; + } + + function increment_opcode (variable) { + set_variable(variable, (get_variable(variable) + 1) >>> 0); + return true; + } + + function getbytedec_opcode (value_var, address_var) { + var address = get_variable(address_var); + set_variable(address_var, (address - 1) >>> 0); + set_variable(value_var, get_byte(address)); + return true; + } + + function gethalfworddec_opcode (value_var, address_var) { + var address = get_variable(address_var); + set_variable(address_var, (address - 2) >>> 0); + set_variable(value_var, get_halfword(address)); + return true; + } + + function getworddec_opcode (value_var, address_var) { + var address = get_variable(address_var); + set_variable(address_var, (address - 4) >>> 0); + set_variable(value_var, get_word(address)); + return true; + } + + function decrement_opcode (variable) { + set_variable(variable, (get_variable(variable) - 1) >>> 0); + return true; + } + + function bufstring_opcode (address) { + frames[0].message_buffer += utf8_decode(address); + return true; + } + + function bufchar_opcode (character) { + if ((character > 0x10ffff) || ((character & 0xfffff800) === 0xd800)) throw "invalid Unicode character"; + if (character > 0xffff) + frames[0].message_buffer += String.fromCharCode(0xd800 | ((character - 0x10000) >>> 10)) + String.fromCharCode(0xdc00 | (character & 0x3ff)); + else if (character > 0) + frames[0].message_buffer += String.fromCharCode(character); + return true; + } + + function bufnumber_opcode (value) { + frames[0].message_buffer += value.toString(); + return true; + } + + function printbuf_opcode () { + var msg = frames[0].message_buffer; + frames[0].message_buffer = ""; + queue_function(function () {self.print(msg);}); + return false; + } + + function clearbuf_opcode () { + frames[0].message_buffer = ""; + return true; + } + + function setstacksize_opcode (size) { + resize_stack(size); + return true; + } + + function getstacksize_opcode (variable) { + var size = frames[0].stack.length; + if (size > 0xffffffff) size = 0xffffffff; + set_variable(variable, size); + return true; + } + + function bit_shifting_opcode (bitflags, variable) { + var shift_count = bitflags & 31; + var shift_type = (bitflags >> 5) & 3; + var value; + if (bitflags & 0x80) + value = next_patch_variable(); + else + value = next_patch_word(); + if (shift_count === 0) shift_count = next_patch_variable() & 31; + switch (shift_type) { + case 0: // shiftleft + value <<= shift_count; + value >>>= 0; + break; + case 1: // shiftright + value >>>= shift_count; + break; + case 2: // rotateleft + value = (value << shift_count) | (value >>> (32 - shift_count)); + value >>>= 0; + break; + case 3: // shiftrightarith + value >>= shift_count; + value >>>= 0; + } + set_variable(variable, value); + return true; + } + + function getfilebyte_opcode (variable) { + set_variable(variable, read_byte(false)); + return true; + } + + function getfilehalfword_opcode (variable) { + set_variable(variable, read_halfword(false)); + return true; + } + + function getfileword_opcode (variable) { + set_variable(variable, read_word(false)); + return true; + } + + function getvariable_opcode (variable, num) { + set_variable(variable, get_variable(num)); + return true; + } + + function addcarry_opcode (variable, carry, first, second) { + var result = (first + second) >>> 0; + if (result < first) set_variable(carry, (get_variable(carry) + 1) >>> 0); + if (variable != carry) set_variable(variable, result); + return true; + } + + function subborrow_opcode (variable, borrow, first, second) { + if (first < second) set_variable(borrow, (get_variable(borrow) - 1) >>> 0); + if (variable != borrow) set_variable(variable, (first - second) >>> 0); + return true; + } + + function long_multiply (first, second) { + // ensure 64-bit precision. This is even worse than the 32-bit case + var first_low = first & 0xffff, first_high = first >>> 16, second_low = second & 0xffff, second_high = second >>> 16; + var low = first_low * second_low, mid = first_low * second_high + first_high * second_low, high = first_high * second_high; + if (mid >= 0x100000000) { + high += 0x10000; + mid -= 0x100000000; + } + var carry = low >>> 16; + low &= 0xffff; + mid = (mid + carry) >>> 0; + if (mid < carry) high += 0x10000; + high = (high + (mid >>> 16)) >>> 0; + mid &= 0xffff; + low = (low | (mid << 16)) >>> 0; + return {high: high, low: low}; + } + + function longmul_opcode (low, high, first, second) { + var result = long_multiply(first >>> 0, second >>> 0); + set_variable(high, result.high); + if (low != high) set_variable(low, result.low); + return true; + } + + function longmulacum_opcode (low, high, first, second) { + if (low == high) { + set_variable(low, (multiply(first, second) + get_variable(low)) >>> 0); + return true; + } + var result = long_multiply(first >>> 0, second >>> 0); + result.low += get_variable(low); + if (result.low >= 0x100000000) { + result.high ++; + result.low -= 0x100000000; + } + result.high += get_variable(high); + set_variable(low, result.low >>> 0); + set_variable(high, result.high >>> 0); + return true; + } + + function opcode_function (opcode) { + switch (opcode) { + case 0x00: return function () {return true;}; // nop + case 0x01: return return_opcode; + case 0x02: case 0x03: return jump_opcode; + case 0x04: case 0x05: return call_opcode; + case 0x06: case 0x07: return exit_opcode; + case 0x08: case 0x09: return push_opcode; + case 0x0a: return pop_opcode; + case 0x0b: return length_opcode; + case 0x0c: return readbyte_opcode; + case 0x0d: return readhalfword_opcode; + case 0x0e: return readword_opcode; + case 0x0f: return pos_opcode; + case 0x10: case 0x11: return getbyte_opcode; + case 0x12: case 0x13: return gethalfword_opcode; + case 0x14: case 0x15: return getword_opcode; + case 0x16: case 0x17: return checksha1_opcode; + case 0x18: case 0x19: return writebyte_opcode; + case 0x1a: case 0x1b: return writehalfword_opcode; + case 0x1c: case 0x1d: return writeword_opcode; + case 0x1e: case 0x1f: return truncate_opcode; + case 0x20: case 0x21: case 0x22: case 0x23: return calculation("+"); // add + case 0x24: case 0x25: case 0x26: case 0x27: return calculation("-"); // subtract + case 0x28: case 0x29: case 0x2a: case 0x2b: return multiply_opcode; + case 0x2c: case 0x2d: case 0x2e: case 0x2f: return guarded_calculation("/"); // divide + case 0x30: case 0x31: case 0x32: case 0x33: return guarded_calculation("%"); // remainder + case 0x34: case 0x35: case 0x36: case 0x37: return calculation("&"); // and + case 0x38: case 0x39: case 0x3a: case 0x3b: return calculation("|"); // or + case 0x3c: case 0x3d: case 0x3e: case 0x3f: return calculation("^"); // xor + case 0x40: case 0x41: case 0x42: case 0x43: return conditional_jump_opcode("<"); // iflt + case 0x44: case 0x45: case 0x46: case 0x47: return conditional_jump_opcode("<="); // ifle + case 0x48: case 0x49: case 0x4a: case 0x4b: return conditional_jump_opcode(">"); // ifgt + case 0x4c: case 0x4d: case 0x4e: case 0x4f: return conditional_jump_opcode(">="); // ifge + case 0x50: case 0x51: case 0x52: case 0x53: return conditional_jump_opcode("==="); // ifeq + case 0x54: case 0x55: case 0x56: case 0x57: return conditional_jump_opcode("!=="); // ifne + case 0x58: case 0x59: return instruction_if_zero(jump_opcode, true); // jumpz + case 0x5a: case 0x5b: return instruction_if_zero(jump_opcode, false); // jumpnz + case 0x5c: case 0x5d: return instruction_if_zero(call_opcode, true); // callz + case 0x5e: case 0x5f: return instruction_if_zero(call_opcode, false); // callnz + case 0x60: case 0x61: return seek_opcode; + case 0x62: case 0x63: return seekfwd_opcode; + case 0x64: case 0x65: return seekback_opcode; + case 0x66: case 0x67: return seekend_opcode; + case 0x68: case 0x69: return print_opcode; + case 0x6a: case 0x6b: return menu_opcode; + case 0x6c: case 0x6d: case 0x6e: case 0x6f: return xordata_opcode; + case 0x70: case 0x71: case 0x72: case 0x73: return fillbyte_opcode; + case 0x74: case 0x75: case 0x76: case 0x77: return fillhalfword_opcode; + case 0x78: case 0x79: case 0x7a: case 0x7b: return fillword_opcode; + case 0x7c: case 0x7d: case 0x7e: case 0x7f: return writedata_opcode; + case 0x80: return lockpos_opcode; + case 0x81: return unlockpos_opcode; + case 0x82: return truncatepos_opcode; + case 0x83: return jumptable_opcode; + case 0x84: case 0x85: return set_opcode; + case 0x86: case 0x87: return ipspatch_opcode; + case 0x88: case 0x89: case 0x8a: case 0x8b: return stackwrite_opcode; + case 0x8c: case 0x8d: return stackread_opcode; + case 0x8e: case 0x8f: return stackshift_opcode; + case 0x90: return instruction_if_zero(return_opcode, true); // retz + case 0x91: return instruction_if_zero(return_opcode, false); // retnz + case 0x92: return pushpos_opcode; + case 0x93: return poppos_opcode; + case 0x94: case 0x95: case 0x96: case 0x97: return bsppatch_opcode; + case 0x98: return getbyteinc_opcode; + case 0x99: return gethalfwordinc_opcode; + case 0x9a: return getwordinc_opcode; + case 0x9b: return increment_opcode; + case 0x9c: return getbytedec_opcode; + case 0x9d: return gethalfworddec_opcode; + case 0x9e: return getworddec_opcode; + case 0x9f: return decrement_opcode; + case 0xa0: case 0xa1: return bufstring_opcode; + case 0xa2: case 0xa3: return bufchar_opcode; + case 0xa4: case 0xa5: return bufnumber_opcode; + case 0xa6: return printbuf_opcode; + case 0xa7: return clearbuf_opcode; + case 0xa8: case 0xa9: return setstacksize_opcode; + case 0xaa: return getstacksize_opcode; + case 0xab: return bit_shifting_opcode; + case 0xac: return getfilebyte_opcode; + case 0xad: return getfilehalfword_opcode; + case 0xae: return getfileword_opcode; + case 0xaf: return getvariable_opcode; + case 0xb0: case 0xb1: case 0xb2: case 0xb3: return addcarry_opcode; + case 0xb4: case 0xb5: case 0xb6: case 0xb7: return subborrow_opcode; + case 0xb8: case 0xb9: case 0xba: case 0xbb: return longmul_opcode; + case 0xbc: case 0xbd: case 0xbe: case 0xbf: return longmulacum_opcode; + default: throw "undefined opcode"; + } + } + + function queue_function (fn) { + setTimeout(fn.bind(self), 0); + } + + function create_frame (patch) { + return { + instruction_pointer: 0, + variables: new Uint32Array(256), + patch_space: patch, + stack: [], + waiting_var: undefined, + message_buffer: "" + }; + } + + function initialize () { + file_buffer = new ResizableMemoryBlock(input.byteLength); + current_file_pointer = 0; + current_file_pointer_locked = false; + var file_buffer_reader = new Uint8Array(input); + var pos; + for (pos = 0; pos < input.byteLength; pos ++) file_buffer.set_byte(pos, file_buffer_reader[pos]); + input = undefined; + frames = [create_frame(new DataView(bsp))]; + initialized = true; + dirty = true; + } + + function set_variable (variable, value) { + frames[0].variables[variable & 0xff] = value; + } + + function get_variable (variable) { + return frames[0].variables[variable & 0xff]; + } + + function push_to_stack (value) { + frames[0].stack.unshift(value >>> 0); + } + + function pop_from_stack () { + if (frames[0].stack.length < 1) throw "popped empty stack"; + return frames[0].stack.shift(); + } + + function finish () { + var callback; + switch (get_state()) { + case 2: callback = self.error; break; + case 3: callback = self.failure; break; + case 4: callback = self.success; break; + default: return; + } + frames = undefined; + file_buffer = undefined; + bsp = undefined; + if (callback === undefined) return; + var result = get_result(); + queue_function(function () {callback(result);}); + } + + function step () { + try { + var opcode = next_patch_byte(); + var args = opcode_parameters(opcode); + var pos; + for (pos = 0; pos < args.length; pos ++) args[pos] = args[pos](); // no map because we need this to happen in order + var result = opcode_function(opcode).apply(self, args); + if (typeof result === "boolean") return result; + frames.shift(); + if (frames.length > 0) { + set_variable(frames[0].waiting_var, result); + frames[0].waiting_var = undefined; + return true; + } + done = true; + exit_status = result; + if (result === 0) output = file_buffer.generate_array_buffer(); + finish(); + return false; + } catch (e) { + done = true; + exit_status = false; + output = e; + finish(); + return false; + } + } + + function execute () { + while (step()); + } + + function run (param) { + if (!initialized) return queue_function(function () { + initialize(); + execute(); + }); + if (done) return; + if (frames[0].waiting_var !== undefined) { + if (typeof param !== "number") throw "invalid value selected"; + param >>>= 0; + if ((selection_range_check !== undefined) && (param >= selection_range_check)) throw "selected value out of range"; + selection_range_check = undefined; + set_variable(frames[0].waiting_var, param); + frames[0].waiting_var = undefined; + } + queue_function(execute); + } + + function get_state () { + if (!initialized) return 0; + if (!done) return 1; + if (exit_status === false) return 2; + if (exit_status === 0) return 4; + return 3; + } + + function get_result () { + if (!(initialized && done)) return undefined; + if ((exit_status === false) || (exit_status === 0)) return output; + return exit_status; + } + + Object.defineProperties(self, { + run: { + configurable: false, + enumerable: true, + writable: false, + value: run + }, + state: { + configurable: false, + enumerable: true, + set: undefined, + get: get_state + }, + result: { + configurable: false, + enumerable: true, + set: undefined, + get: get_result + } + }); +} diff --git a/tools/bsp/compiler.md b/tools/bsp/compiler.md new file mode 100644 index 0000000000..4e4ef7efb7 --- /dev/null +++ b/tools/bsp/compiler.md @@ -0,0 +1,241 @@ +# BSP compiler documentation + +This document describes the functioning of the BSP compiler program included in this project, bspcomp, and the version +of the syntax that it uses. This document does _not_ intend to document the BSP format itself; the [specification] does +that, and it also documents the instruction set. Since the instruction set is documented there, duplicating it here +would be redundant; however, this document does describe the overall syntax that the compiler uses, and the various +pseudo-instructions that it supports. + +[specification]: specification.md + +## Invocation + +The script compiler is invoked with two parameters: the source file and the target BSP filename. For instance, the +following invocation: + +``` +./bspcomp patch.txt patch.bsp +``` + +would create a `patch.bsp` file from the source code in `patch.txt`. + +Note that it is impossible to use more than one source file in the invocation. However, this can be remedied by having +the main source file include the rest — this way, multiple source files can freely be combined into a single resulting +BSP file. All inclusion paths are resolved relative to the current working directory (that is, the directory from which +`bspcomp` is invoked). + +## Overall syntax + +This is a sample source file: + +``` + define result, 0xff + +Main: + checksha1 #result, .empty_file_hash + jumpnz #result, Error + seek 0 + subtract #result, MessageEnd, Message + writedata Message, #result + exit 0 +.empty_file_hash + hexdata da39a3ee5e6b4b0d3255bfef95601890afd80709 ;hash for an empty input (0 bytes) + +Error: + print .error + menu #result, .options + jumptable #result + dw .go_ahead + dw .exit +.options + dw .go_ahead_string + dw .exit_string + dw -1 +.error + string "The input file is not empty. Continue?" +.go_ahead_string + string "Yes" +.exit_string + string "No (abort)" + +.go_ahead + truncate 0 + jump Main + +.exit + exit 1 + +Message: + db "Hello world!" +MessageEnd: +``` + +This script would patch an empty file into a file saying "Hello world!". If the source file isn't empty, the script +would give the option to the user of continuing anyway (deleting the data previously in the file in the process) or +aborting the patching process. + +Several features of the syntax can be shown in the above sample script: + +* Indented lines contain instructions (or pseudo-instructions). Non-indented lines contain labels. (Indentation can be + any amount of whitespace; that is, spaces and/or tabs.) A label can only contain letters (uppercase or lowercase; + they are case-sensitive), numbers and underscores; it also must not begin with a number. A label can also be preceded + by a dot, in which case it is a local label. A label can be on its own in a line, or it can be followed by a colon, + and optionally an instruction in the same line. +* A label can be used in an instruction (or pseudo-instruction) as a value. The value of a label is the address at + which the label will be located when the BSP file is compiled and built; the compilation process resolves the labels + into constant values. A local label can only be used in the same scope it is defined, the scope being between one + global (that is, non-local) label and the next; the `.empty_file_hash` label defined in the above sample script can + only be used between the `Main` and `Error` labels. +* Labels must be unique; local labels only need to be unique within their scope. +* Blank lines are ignored. A semicolon marks a comment; anything between a semicolon and the end of the line is ignored + as well. +* Arguments to instructions are given after the instruction itself. The first argument is separated from the name of + the instruction simply by whitespace; further arguments are separated from prior ones by a comma surrounded by + arbitrary amounts of whitespace. +* String arguments to pseudo-instructions that accept them are surrounded by double quotes. A `"` character itself + inside a string argument may be escaped by duplicating it; there are no other escape sequences in strings. (In + particular, the `\` character has no useful effect other than representing itself in a string argument.) +* Numerical constants may be written explicitly (in decimal, in hexadecimal preceded by `0x`, or in octal preceded by + `0`; negative constants will be converted to unsigned constants in the usual two's complement form), or they may + be represented by a label or a definition. A label can only be used where a word constant (or a word-sized immediate + argument) would fit; a definition may be used in any context where a number is required, including a variable number. + Hence, `#result` is simply a clearer way of writing `#255` in the above example, since `result` has been defined to + represent `0xff` (which is 255). +* Variables are written in the form `#`, where `` is either a number or a defined symbol representing a + number. Therefore, `#255` means variable number 255. Note that variable numbers are byte-sized; a number greater than + 255 will be silently truncated to fit. + +## Pseudo-instructions + +Pseudo-instructions are instructions for the compiler. They aren't actually instructions, since an engine won't be able +to execute them (or know they were there in the first place); instead, they are used for various purposes other than +writing code itself, such as adding data to the BSP file, or creating symbolic names for numbers. + +The list of available pseudo-instructions that this compiler supports is the following: + +* [align] +* [db][data] +* [define] +* [dh][data] +* [dw][data] +* [hexdata] +* [incbin][include] +* [include] +* [string] + +[data]: #inserting-data-into-the-bsp-file +[string]: #inserting-null-terminated-strings-into-the-bsp-file +[hexdata]: #inserting-hexadecimal-data-into-the-bsp-file +[align]: #aligning-the-next-instruction-to-a-certain-alignment +[define]: #defining-a-symbolic-name-for-a-value +[include]: #including-other-files + +### Inserting data into the BSP file + +``` +db any[, any[, ...]] +dh any[, any[, ...]] +dw any[, any[, ...]] +``` + +These pseudo-instructions respectively insert bytes, halfwords (16-bit) or words (32-bit) into the BSP directly, given +by value. They can take any number of arguments, and the bytes, halfwords or words are inserted in the order they are +given; halfwords and words are inserted in little-endian format. Note that only `dw` can accept labels as arguments, +since labels are always 32-bit values; also, these pseudo-instructions will silently truncate arguments that are too +large for the data type they accept. + +All of these pseudo-instructions also accept strings as arguments, given between quotes. They will insert the strings +as given, encoded in UTF-8; the `dh` and `dw` pseudo-instructions will zero-pad the strings to make their lengths +respectively multiples of 2 and 4 bytes. Strings and regular values can be mixed in the same line. + +### Inserting null-terminated strings into the BSP file + +``` +string "string"[, "string"[, ...]] +``` + +This pseudo-instruction inserts strings followed by null (`0x00`) bytes. It is provided purely as a convenience for +clearer code; the following: + +``` + string "abc" + string "123", "890" +``` + +is equivalent to: + +``` + db "abc", 0 + db "123", 0, "890", 0 +``` + +Just like the data pseudo-instructions (`db`, `dh` and `dw`), this pseudo-instruction can take any number of arguments. + +### Inserting hexadecimal data into the BSP file + +``` +hexdata hexstring +``` + +This pseudo-instruction inserts raw data given in hexadecimal, of arbitrary length. The arguments must be sequences of +hexadecimal digits of any length (hexadecimal digits being 0-9, A-F, a-f); if any argument has an odd length, it is +padded with an extra 0 digit at the end to make it even. + +This instruction inserts the data in the order it is given. Therefore, this: + +``` + hexdata 0123456789abcdef + hexdata fedc, 3210 +``` + +is equivalent to: + +``` + db 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef + db 0xfe, 0xdc, 0x32, 0x10 +``` + +### Aligning the next instruction to a certain alignment + +``` +align value +``` + +This pseudo-instruction aligns the next line's address to a multiple of the specified value, by padding the BSP file +with zeros. It is an error to do `align 0`. + +### Defining a symbolic name for a value + +``` +define name, value +``` + +This pseudo-instruction defines a symbol to be equal to a certain value. The value must be a 32-bit constant, and the +name must be a valid name (the restrictions are the same as for label names, only that defined names cannot be local). +It is possible to change this value by using another `define` pseudo-instruction, which changes the value for further +lines of code; for instance, the following example: + +``` + define value, 1 + set #1, value + define value, 5 + set #2, value + define value, 3 + set #value, 10 +``` + +would set variable number 1 to 1, variable number 2 to 5 and variable number 3 to 10. + +### Including other files + +``` +include "filename" +incbin "filename" +``` + +These pseudo-instructions include another file into the current one. The filename must be passed as a string; and, if +it is a relative filename, it will be resolved relative to the current working directory of the compiler. + +The `include` pseudo-instruction will include the file as a source file, and therefore compile it as if it had been +transcribed into the original source file in the location the `include` pseudo-instruction is; on the other hand, the +`incbin` pseudo-instruction will simply insert the contents of the binary file as a blob, without any parsing. diff --git a/tools/bsp/patcher.js b/tools/bsp/patcher.js new file mode 100644 index 0000000000..10fc47a0d2 --- /dev/null +++ b/tools/bsp/patcher.js @@ -0,0 +1,55 @@ +var fs = require("fs"); +var rl = require("readline").createInterface({input: process.stdin, output: process.stdout}); + +// using eval() on an external value for a side effect is one of the ugliest programming tricks I've used. I don't feel guilty. +eval(fs.readFileSync("bsppatch.js", "utf-8")); + +process.exitCode = 0; +try { + if (process.argv.length != 5) { + console.log("Error: patch, source and target files must be passed as command-line arguments"); + throw 1; + } + var patch_file = new Uint8Array(fs.readFileSync(process.argv[2])).buffer; + var source_file = new Uint8Array(fs.readFileSync(process.argv[3])).buffer; + var patch = new BSPPatcher(patch_file, source_file); + patch.print = function (string) { + console.log(string); + patch.run(); + } + patch.menu = function (entries) { + var max_entry = entries.length; + var entry; + for (entry = 0; entry < max_entry; entry ++) console.log((entry + 1).toString() + ". " + entries[entry]); + var callback = function (option) { + if (!(/^[0-9]+$/.test(option))) return rl.question("Invalid number. Select an option: ", callback); + var numeric_option = parseInt(option, 10); + if ((option < 1) || (option > max_entry)) return rl.question("Invalid option selected. Select an option: ", callback); + patch.run(numeric_option - 1); + } + rl.question("Select an option: ", callback); + } + patch.error = function (error) { + console.log("Error: " + error.toString()); + process.exitCode = 1; + rl.close(); + } + patch.failure = function (status) { + console.log("Patch exited with exit status " + status.toString()); + process.exitCode = (status > 127) ? 127 : status; + rl.close(); + } + patch.success = function (data) { + rl.close(); + try { + fs.writeFileSync(process.argv[4], Buffer.from(data)); + } catch (e) { + console.log("Error writing output: " + e.toString()); + } + } + patch.run(); +} catch (e) { + if (typeof e !== "number") throw e; + process.exitCode = e; + rl.close(); +} diff --git a/tools/bsp/patching.md b/tools/bsp/patching.md new file mode 100644 index 0000000000..7e994e9257 --- /dev/null +++ b/tools/bsp/patching.md @@ -0,0 +1,87 @@ +# BSP patching library + +The included patching library, [`bsppatch.js`](bsppatch.js), implements the [BSP specification](specification.md) and +therefore allows applying a BSP according to it. While it is not a full application on its own, it can be used by +implementors to create one. + +The library defines a single class, BSPPatcher, which permits applying a BSP. The class is defined in the scope of the +file, that is, this way, without involving any module-like structure: + +```javascript +function BSPPatcher (bsp_array_buffer, input_array_buffer) { + // code here +} +``` + +The library uses mostly ECMAScript 5 features; however, it uses [Typed Arrays][typed-arrays] from ECMAScript 6. The +practical implication of this is that the library will work in any modern browser or Node.js version, but most likely +not in Internet Explorer (although it may work in IE10+ due to some added support for Typed Arrays). + +[typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays + +The constructor, properties and methods of the `BSPPatcher` class are documented here. + +## Constructor + +```javascript +var bsp = new BSPPatcher(bsp_array_buffer, input_array_buffer); +``` + +This creates a new BSPPatcher object, with the corresponding BSP and input file. The BSP and the input are passed to +the constructor as ArrayBuffer objects containing the data of the respective files. + +## Callback properties + +These properties are modifiable by the user of the library, and they are expected to contain callbacks that will be +called in various circumstances. Their initial value is `undefined`, and when any of the properties is set to this +value, the corresponding callback will not be called when the event occurs. These callback properties are: + +* `print`: called whenever a `print` instruction is encountered in the BSP, in order to print a message to the user. It + is passed a single argument, which is the string to be printed. The callback must call the `run()` method of the + BSPPatcher object after it is done printing to continue executing the BSP (which can happen asynchronously, since the + `run()` method is asynchronous anyway). If the `print` property of the BSPPatcher object is set to `undefined` and a + `print` instruction is encountered, an error will occur. +* `menu`: called whenever a `menu` instruction is encountered in the BSP, in order to display a menu for the user to + select an option. It is passed a single argument, which is an array of strings, each string containing one option; + the user will have to select one of these options for the patching process to continue. After the user selects an + option, the `run()` method of the BSPPatcher object must be called, passing the index of the selected option (which + is the index of the corresponding string in the array passed to the `menu`callback) in order for the patching process + to continue. If the `menu` property of the BSPPatcher object is set to `undefined` and a `menu` instruction is + encountered, an error will occur. +* `success`: called when the patching process finishes and exits with an exit status of 0, indicating that the patching + succeeded. It is passed a single argument, which is an ArrayBuffer object containing the output data of the patch. + This callback should be used to store the data in a file or otherwise make it available to the end user. +* `failure`: called when the patching process finishes with a non-zero exit status, indicating failure. It is passed a + single argument, which is the exit status. This callback should be used to report to the end user that the patching + script reported an error. +* `error`: called when the patching process throws an exception. It is passed a single argument, which is the exception + that was thrown; errors in the patching process (such as undefined instructions) throw strings as exceptions. This + callback should also be used to report the error to the end user. + +## Read-only properties + +These properties are used to query the status of the patching process. They can be used instead of (or in addition to) +the `success`, `failure` and `error` callbacks to check the status of the process. + +* `state`: it is an integer showing the current status of the patcher. 0 indicates that the patching process hasn't + been started yet (and can be started with `run()`), 1 indicates that it is on-going (and can thus be resumed with + `run()`), and higher values indicate that the process is done and cannot be restarted or resumed: 2 if an error + occurs, 3 if the script exits with a non-zero exit status (indicating failure) or 4 if the script exits with a zero + exit status (indicating success). +* `result`: it is the result of the patching process, which is the same value that is passed to the `error`, `failure` + and `success` callbacks explained above. This property will be set according to the value of the `state` property, + containing the result value for the corresponding state (i.e., the value that would be passed to the corresponding + callback, since there is a one-to-one mapping between the callbacks and the higher values of the `state` property). + This property is meaningless when `state` is 0 or 1, and therefore will be set to `undefined` in that case. + +## `run()` method + +This class defines only one method, the `run()` method, which is used both to start and to resume the patching process. +The method only takes an argument when the patcher is waiting to be resumed after a `menu` callback, in which case the +argument is the index of the selected option (between 0 and one less than the number of options, as it is usual for +array indices); otherwise, it takes no arguments. + +The `run()` method will always execute asynchronously; it returns immediately and executes the patching asynchronously. +However, this asynchronous call may block the thread for a long time, since it will continue to run uninterrupted until +an I/O instruction (`print` or `menu`) is executed or until the patching process finishes; this must be taken into +consideration when using the library. diff --git a/tools/bsp/sample.htm b/tools/bsp/sample.htm new file mode 100644 index 0000000000..c5a3386319 --- /dev/null +++ b/tools/bsp/sample.htm @@ -0,0 +1,109 @@ + + + + BSP patcher sample + + + + +
+ Patch file:
+ Input file:
+ +
+
+
+
+ + diff --git a/tools/bsp/specification.md b/tools/bsp/specification.md new file mode 100644 index 0000000000..248100cd7d --- /dev/null +++ b/tools/bsp/specification.md @@ -0,0 +1,1074 @@ +# BSP specification + +A binary scripted patch, hereafter a BSP, is a file containing a script indicating how to convert some data, called the +source, into some other data, called the target. In order to achieve this goal, the patcher must execute the script +contained in the BSP. This document formally specifies how that execution must be done, and how the BSP file must be +interpreted. + +The words "byte", "halfword" and "word" throughout this document respectively refer to 8, 16 and 32-bit quantities. All +halfword or word quantities are stored, read and written in little-endian form (that is, least significant byte first) +unless otherwise indicated. + +For clarity of exposition, in some cases, the patch source syntax (as interpreted by this project's compiler) is used. +This is done purely for simplicity; the patch source syntax does not constitute part of this specification, and any +other implementation of a BSP compiler may use any other syntax or even a completely different language compiled to the +BSP format. The BSP format is fully specified by its binary form as documented here. + +## Execution model + +A BSP engine must be capable of executing a BSP file according to this model. The model describes how the engine +behaves and what a BSP can expect of the engine running it. + +The execution model consists of the following elements: + +* A patch space, which contains the contents of the BSP file; this space is initialized when loading the BSP file. The + patch space is immutable (that is, read-only and of fixed size), and it has the same size as the BSP file itself. +* A file buffer, which is the memory space where the operations of the BSP file are carried out. This file buffer is of + variable size, and it is initialized with the data of the source file; it has the same size as the source file upon + initialization. +* 256 word variables, all of which are initialized to zero. These variables are numbered 0 to 255 and can be referenced + by the instructions in the BSP. +* An instruction pointer, which is a word initialized to zero. +* A current file pointer, which is a word initialized to zero and in unlocked state. The current file pointer can be in + one of two states: unlocked, in which case it behaves normally as documented here, and locked, which makes it a + read-only value; writes or updates to the current file pointer must be silently discarded (without causing any + warnings or other kinds of error messages) while in locked state. +* A stack, unbounded in size, that contains word values. It is initialized empty. +* A message buffer, unbounded in size (although engines may limit the size to a reasonable length and discard any data + added to it beyond that limit), that is initialized to an empty string. + +The patch space and the file buffer have a maximum size of 4,294,967,295 bytes. Attempting to write to the file buffer +past its end increases its length (zero-filling any gaps that occur this way); attempting to read from either the file +buffer or the patch space beyond their respective ends is an error. Note that executing code involves reading from the +patch space, and thus this restriction still applies. + +A location in the patch space is referred to as an _address_, while a location in the file buffer is referred to as a +_position_ (therefore, the value of the instruction pointer is an address, and the value of the current file pointer is +a position). Both of them are numbered sequentially from zero in the usual way. + +All arithmetic is carried out in 32-bit words unless otherwise noted. All values are treated as unsigned other than +when indicated; negative values are simply a notational convenience (e.g., -1 is actually `0xffffffff`, that is, the +same as 4,294,967,295). + +All errors should be treated as fatal. If the engine encounters an error condition (such as an invalid opcode, reading +past the end, or a division by zero), it should not attempt to resume execution; instead, it should inform the user of +the error and abort immediately. + +The execution of the patch results in a word value called an exit status, similar in spirit to the exit status of a +program in several operating systems. An exit status of zero indicates success; other values must be treated as failure +and thus handled as errors. When a BSP exits with a status of zero, the contents of the file buffer must be written out +to the target file as the output of the patching process. + +Execution of the BSP proceeds one instruction at a time, in the usual way for this format: the byte pointed by the +instruction pointer is read, which determines the opcode; the operands (if any) for that opcode are read as well; and +the instruction pointer is incremented by the total amount of bytes read; the instruction is then executed. Execution +continues this way until the BSP exits (via the corresponding instruction). Note that some instructions will modify +the instruction pointer upon execution. + +## Opcodes + +The opcode of each instruction is defined by the first byte. This also defines its length, and the kind of operands it +will take. An instruction can be asigned more than one opcode depending on its arguments. For instance, the `set` +instruction has two opcodes: one when called with two variables as its operands, and another one when called with a +variable and an immediate (a constant value encoded in the instruction itself). + +Variables are always encoded as a single byte indicating the variable number. Immediates (that is, constant numerical +operands that are encoded directly in the instruction) are typically words, and they are encoded in little-endian +format; the actual width of immediate operands is indicated in the opcode table. Operands are encoded in the order that +they appear. + +Note that the size of an instruction (in bytes) can be calculated by adding the size of its operands and adding one +byte for the opcode itself. It is shown in the table for simplicity. + +Opcode bytes that don't appear in the following table constitute undefined instructions, and attempting to execute one +of them must be considered a fatal error. + +|Opcode byte|Instruction |Size|Operands | +|:---------:|:---------------------|---:|:---------------------------------------| +|`0x00` |nop | 1|none | +|`0x01` |return | 1|none | +|`0x02` |jump | 5|word | +|`0x03` |jump | 2|variable | +|`0x04` |call | 5|word | +|`0x05` |call | 2|variable | +|`0x06` |exit | 5|word | +|`0x07` |exit | 2|variable | +|`0x08` |push | 5|word | +|`0x09` |push | 2|variable | +|`0x0a` |pop | 2|variable | +|`0x0b` |length | 2|variable | +|`0x0c` |readbyte | 2|variable | +|`0x0d` |readhalfword | 2|variable | +|`0x0e` |readword | 2|variable | +|`0x0f` |pos | 2|variable | +|`0x10` |getbyte | 6|variable, word | +|`0x11` |getbyte | 3|variable, variable | +|`0x12` |gethalfword | 6|variable, word | +|`0x13` |gethalfword | 3|variable, variable | +|`0x14` |getword | 6|variable, word | +|`0x15` |getword | 3|variable, variable | +|`0x16` |checksha1 | 6|variable, word | +|`0x17` |checksha1 | 3|variable, variable | +|`0x18` |writebyte | 2|byte | +|`0x19` |writebyte | 2|variable | +|`0x1a` |writehalfword | 3|halfword | +|`0x1b` |writehalfword | 2|variable | +|`0x1c` |writeword | 5|word | +|`0x1d` |writeword | 2|variable | +|`0x1e` |truncate | 5|word | +|`0x1f` |truncate | 2|variable | +|`0x20` |add | 10|variable, word, word | +|`0x21` |add | 7|variable, word, variable | +|`0x22` |add | 7|variable, variable, word | +|`0x23` |add | 4|variable, variable, variable | +|`0x24` |subtract | 10|variable, word, word | +|`0x25` |subtract | 7|variable, word, variable | +|`0x26` |subtract | 7|variable, variable, word | +|`0x27` |subtract | 4|variable, variable, variable | +|`0x28` |multiply | 10|variable, word, word | +|`0x29` |multiply | 7|variable, word, variable | +|`0x2a` |multiply | 7|variable, variable, word | +|`0x2b` |multiply | 4|variable, variable, variable | +|`0x2c` |divide | 10|variable, word, word | +|`0x2d` |divide | 7|variable, word, variable | +|`0x2e` |divide | 7|variable, variable, word | +|`0x2f` |divide | 4|variable, variable, variable | +|`0x30` |remainder | 10|variable, word, word | +|`0x31` |remainder | 7|variable, word, variable | +|`0x32` |remainder | 7|variable, variable, word | +|`0x33` |remainder | 4|variable, variable, variable | +|`0x34` |and | 10|variable, word, word | +|`0x35` |and | 7|variable, word, variable | +|`0x36` |and | 7|variable, variable, word | +|`0x37` |and | 4|variable, variable, variable | +|`0x38` |or | 10|variable, word, word | +|`0x39` |or | 7|variable, word, variable | +|`0x3a` |or | 7|variable, variable, word | +|`0x3b` |or | 4|variable, variable, variable | +|`0x3c` |xor | 10|variable, word, word | +|`0x3d` |xor | 7|variable, word, variable | +|`0x3e` |xor | 7|variable, variable, word | +|`0x3f` |xor | 4|variable, variable, variable | +|`0x40` |iflt | 10|variable, word, word | +|`0x41` |iflt | 7|variable, word, variable | +|`0x42` |iflt | 7|variable, variable, word | +|`0x43` |iflt | 4|variable, variable, variable | +|`0x44` |ifle | 10|variable, word, word | +|`0x45` |ifle | 7|variable, word, variable | +|`0x46` |ifle | 7|variable, variable, word | +|`0x47` |ifle | 4|variable, variable, variable | +|`0x48` |ifgt | 10|variable, word, word | +|`0x49` |ifgt | 7|variable, word, variable | +|`0x4a` |ifgt | 7|variable, variable, word | +|`0x4b` |ifgt | 4|variable, variable, variable | +|`0x4c` |ifge | 10|variable, word, word | +|`0x4d` |ifge | 7|variable, word, variable | +|`0x4e` |ifge | 7|variable, variable, word | +|`0x4f` |ifge | 4|variable, variable, variable | +|`0x50` |ifeq | 10|variable, word, word | +|`0x51` |ifeq | 7|variable, word, variable | +|`0x52` |ifeq | 7|variable, variable, word | +|`0x53` |ifeq | 4|variable, variable, variable | +|`0x54` |ifne | 10|variable, word, word | +|`0x55` |ifne | 7|variable, word, variable | +|`0x56` |ifne | 7|variable, variable, word | +|`0x57` |ifne | 4|variable, variable, variable | +|`0x58` |jumpz | 6|variable, word | +|`0x59` |jumpz | 3|variable, variable | +|`0x5a` |jumpnz | 6|variable, word | +|`0x5b` |jumpnz | 3|variable, variable | +|`0x5c` |callz | 6|variable, word | +|`0x5d` |callz | 3|variable, variable | +|`0x5e` |callnz | 6|variable, word | +|`0x5f` |callnz | 3|variable, variable | +|`0x60` |seek | 5|word | +|`0x61` |seek | 2|variable | +|`0x62` |seekfwd | 5|word | +|`0x63` |seekfwd | 2|variable | +|`0x64` |seekback | 5|word | +|`0x65` |seekback | 2|variable | +|`0x66` |seekend | 5|word | +|`0x67` |seekend | 2|variable | +|`0x68` |print | 5|word | +|`0x69` |print | 2|variable | +|`0x6a` |menu | 6|variable, word | +|`0x6b` |menu | 3|variable, variable | +|`0x6c` |xordata | 9|word, word | +|`0x6d` |xordata | 6|word, variable | +|`0x6e` |xordata | 6|variable, word | +|`0x6f` |xordata | 3|variable, variable | +|`0x70` |fillbyte | 6|word, byte | +|`0x71` |fillbyte | 6|word, variable | +|`0x72` |fillbyte | 3|variable, byte | +|`0x73` |fillbyte | 3|variable, variable | +|`0x74` |fillhalfword | 7|word, halfword | +|`0x75` |fillhalfword | 6|word, variable | +|`0x76` |fillhalfword | 4|variable, halfword | +|`0x77` |fillhalfword | 3|variable, variable | +|`0x78` |fillword | 9|word, word | +|`0x79` |fillword | 6|word, variable | +|`0x7a` |fillword | 6|variable, word | +|`0x7b` |fillword | 3|variable, variable | +|`0x7c` |writedata | 9|word, word | +|`0x7d` |writedata | 6|word, variable | +|`0x7e` |writedata | 6|variable, word | +|`0x7f` |writedata | 3|variable, variable | +|`0x80` |lockpos | 1|none | +|`0x81` |unlockpos | 1|none | +|`0x82` |truncatepos | 1|none | +|`0x83` |jumptable | 2|variable | +|`0x84` |set | 6|variable, word | +|`0x85` |set | 3|variable, variable | +|`0x86` |ipspatch | 6|variable, word | +|`0x87` |ipspatch | 3|variable, variable | +|`0x88` |stackwrite | 9|word, word | +|`0x89` |stackwrite | 6|word, variable | +|`0x8a` |stackwrite | 6|variable, word | +|`0x8b` |stackwrite | 3|variable, variable | +|`0x8c` |stackread | 6|variable, word | +|`0x8d` |stackread | 3|variable, variable | +|`0x8e` |stackshift | 5|word | +|`0x8f` |stackshift | 2|variable | +|`0x90` |retz | 2|variable | +|`0x91` |retnz | 2|variable | +|`0x92` |pushpos | 1|none | +|`0x93` |poppos | 1|none | +|`0x94` |bsppatch | 10|variable, word, word | +|`0x95` |bsppatch | 7|variable, word, variable | +|`0x96` |bsppatch | 7|variable, variable, word | +|`0x97` |bsppatch | 4|variable, variable, variable | +|`0x98` |getbyteinc | 3|variable, variable | +|`0x99` |gethalfwordinc | 3|variable, variable | +|`0x9a` |getwordinc | 3|variable, variable | +|`0x9b` |increment | 2|variable | +|`0x9c` |getbytedec | 3|variable, variable | +|`0x9d` |gethalfworddec | 3|variable, variable | +|`0x9e` |getworddec | 3|variable, variable | +|`0x9f` |decrement | 2|variable | +|`0xa0` |bufstring | 5|word | +|`0xa1` |bufstring | 2|variable | +|`0xa2` |bufchar | 5|word | +|`0xa3` |bufchar | 2|variable | +|`0xa4` |bufnumber | 5|word | +|`0xa5` |bufnumber | 2|variable | +|`0xa6` |printbuf | 1|none | +|`0xa7` |clearbuf | 1|none | +|`0xa8` |setstacksize | 5|word | +|`0xa9` |setstacksize | 2|variable | +|`0xaa` |getstacksize | 2|variable | +|`0xab` |_(bit shifts)_ | --|_(see below)_ | +|`0xac` |getfilebyte | 2|variable | +|`0xad` |getfilehalfword | 2|variable | +|`0xae` |getfileword | 2|variable | +|`0xaf` |getvariable | 3|variable, variable | +|`0xb0` |addcarry | 11|variable, variable, word, word | +|`0xb1` |addcarry | 8|variable, variable, word, variable | +|`0xb2` |addcarry | 8|variable, variable, variable, word | +|`0xb3` |addcarry | 5|variable, variable, variable, variable | +|`0xb4` |subborrow | 11|variable, variable, word, word | +|`0xb5` |subborrow | 8|variable, variable, word, variable | +|`0xb6` |subborrow | 8|variable, variable, variable, word | +|`0xb7` |subborrow | 5|variable, variable, variable, variable | +|`0xb8` |longmul | 11|variable, variable, word, word | +|`0xb9` |longmul | 8|variable, variable, word, variable | +|`0xba` |longmul | 8|variable, variable, variable, word | +|`0xbb` |longmul | 5|variable, variable, variable, variable | +|`0xbc` |longmulacum | 11|variable, variable, word, word | +|`0xbd` |longmulacum | 8|variable, variable, word, variable | +|`0xbe` |longmulacum | 8|variable, variable, variable, word | +|`0xbf` |longmulacum | 5|variable, variable, variable, variable | + +### Bit shifting opcodes + +Bit shifting opcodes are defined by the byte that follows the opcode byte. The first byte is always `0xab` for these +instructions; the next byte is divided into three bit fields, as follows (where bit 0 is the least significant): + +* Bit 7: variable/immediate flag +* Bits 6-5: shift type +* Bits 4-0: shift count + +The opcode is determined by the shift type, as follows: + +|Shift type|Opcode | +|:--------:|:-------------------| +| 0|shiftleft | +| 1|shiftright | +| 2|rotateleft | +| 3|shiftrightarith | + +If the shift count is 0, the shift count is read from the lower 5 bits of a variable; a byte will be added after the +rest of the operands indicating which variable. Finally, the type of the shift operand is determined by the +variable/immediate flag: if the flag is 0, the operand is an immediate; if it is 1, the operand is a variable. + +This is summarized in the following table (note that the operand list doesn't include the byte that comes after the +opcode byte, with the bitfields as specified above): + +|V/I flag|Shift count|Size|Operands | +|:------:|----------:|---:|:------------------------------| +| 0| 0| 8|variable, word, variable | +| 0| not 0| 7|variable, word | +| 1| 0| 5|variable, variable, variable | +| 1| not 0| 4|variable, variable | + +## Instruction set + +The different instructions are listed here in alphabetical order, for lookup convenience. They are described in +separate sections. + +* [add][calc] +* [addcarry][longcalc] +* [and][calc] +* [bsppatch] +* [bufchar][msgbuffer] +* [bufnumber][msgbuffer] +* [bufstring][msgbuffer] +* [call][flow] +* [callnz][flow] +* [callz][flow] +* [checksha1] +* [clearbuf][msgbuffer] +* [decrement][var-basic] +* [divide][calc] +* [exit] +* [fillbyte][fill] +* [fillhalfword][fill] +* [fillword][fill] +* [getbyte][get] +* [getbytedec][get] +* [getbyteinc][get] +* [getfilebyte][read] +* [getfilehalfword][read] +* [getfileword][read] +* [gethalfword][get] +* [gethalfworddec][get] +* [gethalfwordinc][get] +* [getstacksize][stack-size] +* [getvariable][var-basic] +* [getword][get] +* [getworddec][get] +* [getwordinc][get] +* [ifeq][conditionals] +* [ifge][conditionals] +* [ifgt][conditionals] +* [ifle][conditionals] +* [iflt][conditionals] +* [ifne][conditionals] +* [increment][var-basic] +* [ipspatch] +* [jump][flow] +* [jumpnz][flow] +* [jumptable] +* [jumpz][flow] +* [length] +* [lockpos][pos] +* [longmul][longcalc] +* [longmulacum][longcalc] +* [mulacum][longcalc] (note that this is an alias) +* [menu] +* [multiply][calc] +* [nop] +* [or][calc] +* [pop][stack-basic] +* [poppos][pos] +* [pos] +* [print] +* [printbuf][msgbuffer] +* [push][stack-basic] +* [pushpos][pos] +* [readbyte][read] +* [readhalfword][read] +* [readword][read] +* [remainder][calc] +* [retnz][flow] +* [return][flow] +* [retz][flow] +* [rotateleft][bit-shifts] +* [seek] +* [seekback][seek] +* [seekend][seek] +* [seekfwd][seek] +* [set][var-basic] +* [setstacksize][stack-size] +* [shiftleft][bit-shifts] +* [shiftright][bit-shifts] +* [shiftrightarith][bit-shifts] +* [stackread][stack-adv] +* [stackshift][stack-adv] +* [stackwrite][stack-adv] +* [subborrow][longcalc] +* [subtract][calc] +* [truncate] +* [truncatepos][truncate] +* [unlockpos][pos] +* [writebyte][write] +* [writedata] +* [writehalfword][write] +* [writeword][write] +* [xor][calc] +* [xordata][writedata] + +[nop]: #no-operation +[var-basic]: #basic-variable-operations +[calc]: #arithmetical-and-logical-instructions +[bit-shifts]: #bit-shifting-operations +[longcalc]: #long-arithmetical-instructions +[stack-basic]: #basic-stack-operations +[flow]: #control-flow +[conditionals]: #conditionals +[exit]: #exiting +[print]: #printing-messages +[msgbuffer]: #manipulating-the-message-buffer +[menu]: #option-menus +[jumptable]: #jump-tables +[stack-adv]: #advanced-stack-operations +[stack-size]: #resizing-the-stack +[get]: #reading-values-from-patch-space +[read]: #reading-values-from-the-file-buffer +[write]: #writing-values-to-the-file-buffer +[fill]: #filling-the-file-buffer-with-a-value +[writedata]: #operating-with-data-from-the-bsp-on-the-file-buffer +[length]: #retrieving-the-length-of-the-file-buffer +[truncate]: #resizing-the-file-buffer +[pos]: #operating-with-the-current-file-pointer +[seek]: #modifying-the-current-file-pointer +[checksha1]: #checking-a-sha-1-hash +[ipspatch]: #applying-an-ips-patch +[bsppatch]: #applying-a-bsp-patch-contained-within-the-bsp + +## Instruction description + +Instructions are detailed here, including their operands and semantics. + +For simplicity, the script compiler's syntax is used to show the form of the instruction. Operands that must be +variables are prefixed with `#`; other operands can be either variables or immediates. (In no case an instruction +only accepts an immediate as an operand.) + +### No operation + +``` +nop +``` + +This instruction does nothing at all. It can be used, for instance, as a filler. + +### Basic variable operations + +``` +set #variable, any +increment #variable +decrement #variable +getvariable #variable, #number +``` + +The `set` instruction sets the variable's value to the specified value, which can be an immediate value or another +variable. + +The `increment` and `decrement` instructions respectively add and subtract one from the specified variable. While they +are equivalent to using `add #variable, #variable, 1` or `subtract #variable, #variable, 1`, they are available as +shorter forms (that also use fewer bytes in the BSP file). + +The `getvariable` instruction sets a variable to the value of a variable whose number matches the value of the second +argument. That is, if variable 1 has the value 5, then `getvariable #2, #1` would set variable 2 to the value of +variable 5. (Note that the second argument to `getvariable` can't be an immediate value, because that would be +equivalent to a `set` instruction: `getvariable #2, 5` would be the same as `set #2, #5`. Also note that only the least +significant byte of the second argument's value is used; the upper bytes are ignored.) + +### Arithmetical and logical instructions + +``` +add #variable, any, any +subtract #variable, any, any +multiply #variable, any, any +divide #variable, any, any +remainder #variable, any, any +and #variable, any, any +or #variable, any, any +xor #variable, any, any +``` + +These instructions perform the specified calculation between the two last operands and store the result in the variable +indicated by the first. The operands to the calculation are treated as unsigned values in all cases. + +If the last operand to `divide` or `remainder` is zero, a fatal error occurs. + +Note that the script compiler accepts a two-operand shorthand for these instructions, that is simply expanded to the +full three-operand form by repeating the first operand. (That is, `add #var, 3` is converted to `add #var, #var, 3` +prior to compilation.) This shorthand is a feature of the compiler, not part of the specification for the instructions; +the instructions (in the binary file) can only exist in three-operand form. + +### Bit shifting operations + +``` +shiftleft #variable, any, count +shiftright #variable, any, count +shiftrightarith #variable, any, count +rotateleft #variable, any, count +``` + +These instructions shift the value in the operand by the amount of bits specified in the count; bit counts of 1 to 31 +bits are allowed. (An immediate bit count of 0 is not valid; in the script compiler language, specifying a bit count of +0 will cause the instruction to be encoded as a `set` instruction.) If the bit count is a variable, then only the five +least significant bits of that variable will be used as bit count; the rest are silently truncated. (A bit count of 0 +as a result of using a variable bit count is acceptable.) + +The `shiftleft` and `shiftright` instructions shift the value in the corresponding direction, filling the shifted bits +with zeros. The `shiftrightarith` instruction behaves like `shiftright`, but extends the sign bit (the most significant +bit) instead; if this bit is 1, the shifted bits will be filled with ones. The `rotateleft` instruction shifts the +value to the left and inserts the bits that are dropped from the value on the right: a left rotation of 4 bits on the +value `0x12345678` will generate the value `0x23456781`. + +Note that no `rotateright` instruction exists; the same effect can be achieved by negating the rotation count and using +a `rotateleft` instruction. + +As with the arithmetical and logical instructions, the script compiler accepts a two-operand shorthand for these +instructions, that is expanded to the full three-operand form by repeating the first operand: `shiftleft #var, 2` will +be expanded to `shiftleft #var, #var, 2` prior to compilation. This is a feature of the compiler, not part of the +specification for the instructions; the instructions (in the binary file format) only exist in three-operand form. + +Note on encoding: the last operand, the bit count, is already encoded in the second byte of the instruction (which +selects the opcode and the operand types) when it is an immediate, and therefore will be out of order. (When it is a +variable, it will be encoded explicitly as the last byte of the instruction, as usual.) + +### Long arithmetical instructions + +``` +addcarry #result, #carry, any, any +subborrow #result, #borrow, any, any +longmul #low, #high, any, any +longmulacum #low, #high, any, any +``` + +These instructions perform calculations that are similar to their regular counterparts, but with special care for +results that do not fit in 32 bits. + +The `addcarry` instruction adds the last two operands and stores the result in the variable specified by the first. If +the addition carries (that is, if the result, as a 32-bit unsigned value, is strictly lower than the operands), the +variable specified by the second operand is incremented by one. (Note that the "strictly lower than its operands" +condition can only be fulfilled for both operands or for neither, so testing against any one of the operands is +sufficient.) + +The `subborrow` instruction performs a subtraction in a similar way, storing the result in the variable specified by +the first operand. If the subtraction borrows (that is, if the minuend (the instruction's third operand) as a 32-bit +unsigned value is strictly lower than the subtrahend (the instruction's last operand)), the variable specified by the +second operand is decremented by one. + +The `longmul` instruction performs a 64-bit (unsigned) multiplication between its operands, and stores the lower word +in the variable specified by the first operand and the higher word in the variable specified by the second. For +instance, `longmul #1, #2, 0x12345678, 0x87654321` would set variable 1 to `0x70b88d78` and variable 2 to `0x09a0cd05`. +(Note that `0x12345678` times `0x87654321` is `0x09a0cd0570b88d78`.) + +The `longmulacum` instruction performs a 64-bit multiplication in the same way, but it adds the result to the 64-bit +value contained between the variables in the first two operands. For instance, if variable 1 contains `0xfedcba98` and +variable 2 contains `0x76543210`, then `longmulacum #1, #2, 0x01234567, 0x89abcdef` would set variable 1 to +`0xc82b00c1` and variable 2 to `0x76f0d5ae`. (Note that `0x76543210fedcba98` + (`0x01234567` * `0x89abcdef`) is +`0x76f0d5aec82b00c1`.) + +In case that the first two operands point to the same variable, these instructions behave as follows: + +* The `addcarry` and `subborrow` instructions will increment/decrement the variable in case of carry/borrow, but not + store the actual result of the addition/subtraction anywhere; the result is discarded. +* The `longmul` instruction will store the high word of the result in the variable. The low word of the result is + discarded. +* The `longmulacum` instruction will store the _low_ word of the result in the variable. The high word of the result is + discarded. Note that this intentionally differs from the behavior of the other instructions. + +Due to the special behavior of the `longmulacum` instruction when the first two operands are the same variable, the +compiler has a shorthand for this instruction, called `mulacum`; this instruction takes three operands, and it is +expanded to `longmulacum` by repeating the first operand (which must be a variable). Note that this is a feature of the +compiler, not part of the specification itself; the instruction in its binary form exists in four-operand form only. + +### Basic stack operations + +``` +push any +pop #variable +``` + +These instructions perform basic stack manipulations. The `push` instruction pushes a value into the stack (which can +be either a variable or a word immediate), and the `pop` instruction pops a value from the stack and stores it in the +specified variable. + +If the `pop` instruction is executed with an empty stack, a fatal error occurs. + +### Control flow + +``` +jump address +jumpz #variable, address +jumpnz #variable, address + +call address +callz #variable, address +callnz #variable, address + +return +retz #variable +retnz #variable +``` + +The `jump` instruction updates the instruction pointer, setting it to the value in its operand, which causes control to +jump to the instruction pointed by it. (Note that the address operands in these instructions can be immediate addresses +or variables.) + +The `call` instruction does the same, but it first pushes the current value of the instruction pointer (which points to +the next instruction) to the stack. The `return` instruction pops a value from the stack and sets the instruction +pointer to it, thus returning from a prior call; executing `return` with an empty stack is equivalent to `exit 0`. + +The `z` versions of the instructions above execute conditionally based on the value of a variable: they execute if the +variable is zero, or do nothing otherwise. The `nz` versions invert this condition. + +### Conditionals + +``` +ifeq #variable, any, address +ifne #variable, any, address +iflt #variable, any, address +ifle #variable, any, address +ifgt #variable, any, address +ifge #variable, any, address +``` + +These instructions conditionally jump to the specified address, based on the condition specified by the instruction +itself. Respectively, the conditions are that the variable is equal, not equal, less than, less than or equal, greater +than, and greater than or equal than the second argument to the instruction. + +### Exiting + +``` +exit any +``` + +This instruction terminates the execution of the script. The operand to this instruction is the exit status of the BSP: +zero indicates success, and any other value indicates failure (the engine may use this value in any way it wishes as +long as it follows this convention). The engine must write out the contents of the file buffer to the output (the patch +target) upon exit with a status of zero. + +If the current BSP is being executed as a result of a parent script executing a `bsppatch` instruction, the argument to +exit becomes the exit status that the parent `bsppatch` instruction will store. In this case, execution resumes with +the parent script; the engine must not terminate execution (regardless of exit status) and must not write to the output +(again, regardless of exit status) upon exit of a script invoked by `bsppatch`. + +### Printing messages + +``` +print address +``` + +This instruction prints a message to the user. The address parameter indicates where in the patch space the message +begins; the message must be UTF-8 encoded, and continues until a null byte (a byte with value `0x00`) is found. + +This document does not specify how the engine will display the message; however, in case of a console-based application +(or an environment that behaves in a similar fashion), it is recommended that the engine prints a newline character +after the message. + +If the message is not valid UTF-8, the engine may choose to display the message anyway (handling the invalid characters +in any way it can) or to treat it as a fatal error. + +An engine incapable of handling the full Unicode character set may choose to use a reduced character set and replace +the remaining characters with a suitable substitution character; however, an engine must at least support the Latin +letters (A-Z, a-z), digits (0-9), spaces, and the following punctuation characters: `'-,.;:#%&!?/()[]`. + +### Manipulating the message buffer + +``` +bufstring address +bufchar any +bufnumber any +printbuf +clearbuf +``` + +The first three instructions concatenate data at the end of the message buffer. The last two display it and clear it. + +The `bufstring` instruction concatenates a string (in the same format as for the `print` instruction) at the end of +the message buffer. No separator is inserted before or after the string. + +The `bufchar` instruction appends a single Unicode character to the message buffer. An engine incapable of handling the +full Unicode character set may choose to use a reduced character set and replace the remaining characters with suitable +substitutes; it must however support at least the letters (A-Z, a-z), numbers (0-9), basic punctuation characters +(`'-,.;:#%&!?/()[]`) and the space character. Passing a value that isn't a valid Unicode codepoint (`0x000000` to +`0x00d7ff` and `0x00e000` to `0x10ffff`) is a fatal error. + +The `bufnumber` instruction appends the decimal representation of a number to the message buffer. The number is +treated as a 32-bit unsigned value and converted to decimal, and printed using the regular digit characters (0-9, +`U+0030` to `U+0039`). The shortest representation of the number is used; that is, no leading zeros are printed (other +than a single `0` for the number zero itself). + +The `printbuf` instruction prints the contents of the message buffer as a message (as if it had been passed to the +`print` instruction) and clears the buffer, resetting it to the empty string. The `clearbuf` instruction resets the +message buffer to the empty string without printing it. + +### Option menus + +``` +menu #variable, address +``` + +This instruction displays a menu with options, from which the user has to select one. The selected option number is +written to the indicated variable; the first option is numbered zero. + +The address parameter should point to a list of pointers in patch space, each pointer pointing in turn to the text that +each option will contain (in the same format that the `print` instruction uses); the list is terminated with a +`0xffffffff` value. For instance, this code fragment would display a menu with four options: + +``` + menu #result, Options + ; ... + +Options: + dw .zero + dw .one + dw .two + dw .three + dw -1 +.zero + string "Option 0" +.one + string "Option 1" +.two + string "Option 2" +.three + string "Option 3" +``` + +If the list of pointers is empty (i.e., if the first pointer is `0xffffffff`), no menu is shown to the user, and the +variable is set to `0xffffffff`. + +Note that a menu with just one option must still be shown to the user, as it is possible to use such a menu to give the +user the possibility of aborting the process by stopping the BSP engine. + +### Jump tables + +``` +jumptable #variable +``` + +This instruction expects to be followed by a list of pointers, as many as needed according to the values the variable +may have. The instruction will read the word at `instruction pointer + 4 * #variable` and jump to the address that is +read. Note that no bounds checking is performed on the value of the variable, so the script must ensure that the +instruction will jump to a correct location. + +For instance, the following code will jump to `.zero`, `.one` or `.two` depending on whether the value of `#var` is 0, +1 or 2 respectively (if `#var` is 3 or greater, the result is undefined): + +``` + jumptable #var + dw .zero + dw .one + dw .two + +.zero + ; ... +.one + ; ... +.two + ; ... +``` + +### Advanced stack operations + +``` +stackread #variable, position +stackwrite position, any +stackshift amount +``` + +These instructions operate directly on the values in stack. + +The `stackread` instruction reads a value from the stack into a variable, and the `stackwrite` instruction writes a +value to a position in stack. For both instructions, the position argument is treated as a _signed_ value: if it is +positive, it refers to a position starting from the bottom of the stack (that is, where values would be pushed or +popped next); and if it is negative, it refers to a position starting from the top of the stack (that is, where the +very first value in the stack was pushed). + +Positive positions in stack are numbered from the current stack position upwards: the value that would be popped next +is position 0, the following one in stack is position 1, and so on. Negative positions in stack are numbered downwards +from the stack top: the very first value in the stack is position -1, the one pushed after that one is position -2, and +so on. Attempting to access a position in stack that doesn't exist (that is, a position that is greater or equal than +the number of elements in the stack, or less than minus that amount) is a fatal error. + +The `stackshift` instruction performs a mass push/pop, changing the stack size. The argument to this instruction is +also treated as a _signed_ value: if it is positive, as many zeros as the argument indicates are pushed into the stack, +making it larger; if it is negative, as many elements as the absolute value of the argument indicates are popped and +stored nowhere, making the stack smaller (it is a fatal error to attempt to pop more values than the stack currently +holds). An argument of zero makes the instruction do nothing. + +### Resizing the stack + +``` +getstacksize #variable +setstacksize any +``` + +These instructions manipulate the size of the stack directly. + +The `getstacksize` instruction stores the size of the stack (in number of words) in the specified variable. If the size +of the stack is too large to be stored in a word, then `0xffffffff` is stored. + +The `setstacksize` instruction changes the size of the stack to be the specified value. If this value is larger than +the current size of the stack, enough zeros are pushed to make the stack as large as needed; if this value is smaller +than the current size of the stack, enough values are popped (and stored nowhere) as to reduce the stack to the +specified size. + +### Reading values from patch space + +``` +getbyte #variable, address +getbyteinc #variable, #address +getbytedec #variable, #address + +gethalfword #variable, address +gethalfwordinc #variable, #address +gethalfworddec #variable, #address + +getword #variable, address +getwordinc #variable, #address +getworddec #variable, #address +``` + +These instructions are used to fetch values from the BSP itself. They read a value from patch space and store it in a +variable. The address indicates where the value is located; halfwords and words are read in little-endian form. + +The `inc` and `dec` variants respectively increment and decrement the address variable by the size of the value (1, 2 +or 4) _after_ reading from that address. For these instructions, the address must be a variable; it cannot be an +immediate address. (Note that a reference to a label is compiled as an immediate.) If both arguments to one of these +instructions refer to the same variable, the instruction behaves as its regular counterpart, setting the variable to +the value at the address and disregarding the increment. + +### Reading values from the file buffer + +``` +readbyte #variable +readhalfword #variable +readword #variable + +getfilebyte #variable +getfilehalfword #variable +getfileword #variable +``` + +The first group of instructions reads a value from the file buffer, located at the position given by the current file +pointer. The current file pointer itself is incremented by the size of the value read (1, 2 or 4) after reading the +value, unless it is in locked state. Halfwords and words are read in little-endian form. + +The instructions in the second group perform the same task as their counterparts in the first group, but they don't +update the current file pointer after reading, regardless of whether it is in locked or unlocked state. + +It is a fatal error to attempt to read beyond the end of the file buffer. + +### Writing values to the file buffer + +``` +writebyte any +writehalfword any +writeword any +``` + +These instructions write a value to the file buffer, at the position indicated by the current file pointer. The current +file pointer itself is incremented by the size of the value written (1, 2 or 4) after writing the value, unless it is +in locked state. Halfwords and words are written in little-endian form. + +If the instruction attempts to write past the end of the file buffer, the file buffer is extended to accomodate the new +value. If this results in a gap of uninitialized data, this gap is filled with zeros. + +Note that the `writebyte` and `writehalfword` instructions take respectively a byte and a halfword argument; the upper +bytes of the value are silently ignored. + +### Filling the file buffer with a value + +``` +fillbyte count, any +fillhalfword count, any +fillword count, any +``` + +These instructions repeatedly write the value indicated by their second argument as many times as the first argument +indicates. All considerations about the current file pointer apply as in the `write` instructions; namely, this causes +the current file pointer to be incremented accordingly. + +Attempting to write past the end of the file buffer behaves as with the `write` instructions. + +Note that the `fillbyte` and `fillhalfword` respectively take a byte and a halfword as their second argument; the upper +bytes of the value passed there are silently ignored. + +### Operating with data from the BSP on the file buffer + +``` +writedata address, length +xordata address, length +``` + +The `writedata` instruction writes data (from the patch space, starting at the location indicated by the address +argument) into the file buffer, starting at the current file pointer and going for as many bytes as the length argument +indicates. All considerations about the current file pointer apply as in the other `write` instructions; namely, this +causes the current file pointer to be incremented accordingly. + +The `xordata` instruction applies a XOR operation between the data currently in the file buffer and the data pointed to +by the address in the patch space. + +Attempting to write past the end of the file buffer behaves as with the `write` instructions. Note that `xordata` +behaves identically to `writedata` in this case. + +Also note that, since these instructions read from the patch space, they may attempt to read beyond its end; this is a +fatal error. + +### Retrieving the length of the file buffer + +``` +length #variable +``` + +This instruction stores the current length of the file buffer in the specified variable. + +### Resizing the file buffer + +``` +truncate any +truncatepos +``` + +The `truncate` instruction changes the length of the file buffer to the specified value. If the file buffer is made +shorter this way, the data at the end of it is lost; if it is made longer, the gap at the end is filled with zeros. +Note that this instruction does not update the current file pointer, even if it would point beyond the end of the file +buffer after execution. + +The `truncatepos` instruction changes the length of the file buffer to make it end at the current file pointer; that +is, it sets the length of the file buffer to the value of the current file pointer, causing the current file pointer to +point to the end of the file buffer. + +### Operating with the current file pointer + +``` +pos #variable +lockpos +unlockpos +pushpos +poppos +``` + +The `pos` instruction stores the value of the current file pointer in the specified variable. + +The `lockpos` and `unlockpos` instructions set the current file pointer's state accordingly. + +The `pushpos` instruction pushes the value of the current file pointer into the stack, and the `poppos` instruction +pops a value from the stack and sets the current file pointer to it. Note that it is a fatal error to execute `poppos` +with an empty stack. Also note that `poppos` does not update the current file pointer if it is in locked state +(although it still pops a value, thus behaving as a dummy pop, or `stackshift -1`). + +### Modifying the current file pointer + +``` +seek any +seekfwd any +seekback any +seekend any +``` + +The `seek` instruction sets the current file pointer to the specified value. The `seekfwd` and `seekback` instructions +respectively add and subtract the specified value from the current file pointer. The `seekend` instruction subtracts +the specified value from the file buffer length and sets the current file pointer to the result. + +It is a fatal error to cause the calculations performed by the last three instructions to overflow. + +Note that no bounds checking is performed on the current file pointer: it is allowed to point to a position beyond the +end of the file buffer by any amount. Also note that these instructions do nothing if the current file pointer is in +locked state. + +### Checking a SHA-1 hash + +``` +checksha1 #variable, address +``` + +This instruction calculates the SHA-1 hash of the current contents of the file buffer (the current file pointer is not +read or updated by this instruction), and compares it to the hash stored in patch space at the specified address. The +hash is stored as a 20-byte value, most significant byte first for convenience. The hash is to be calculated as +specified by the formal specification in [RFC 3174][rfc3174]. + +The comparison returns a bit mask, which sets a bit for each byte that fails the comparison: bit 0 is set if the first +byte of the real hash differs from the first byte of the compared hash, and so on. For instance, knowing that the SHA-1 +hash for an empty input is `da39a3ee5e6b4b0d3255bfef95601890afd80709`, the following script: + +``` + checksha1 #result, .hash + ; ... +.hash + hexdata ff39a3ee5eff4b0d3255bfef95601890afd80709 +``` + +when executed with an empty file buffer would set `#result` to `0x00000021`. (Notice that the first and sixth bytes of +the hash have been replaced with `0xff` bytes in the hash provided to `.hash`.) + +Also notice that the instruction will set the variable to zero if (and only if) the hash matches in full. + +If this instruction is executed multiple times (with any arguments), the engine is not required to recalculate the hash +as long as the contents of the file buffer haven't changed since the last time the hash was calculated; since it is +known that the hash will come out to the same value, the engine may simply reuse this value for efficiency. + +[rfc3174]: https://tools.ietf.org/html/rfc3174 + +### Applying an IPS patch + +``` +ipspatch #variable, address +``` + +This instruction applies an IPS patch, embedded in the BSP itself (that is, read from patch space), to the current file +buffer. The current file pointer is taken as an offset to be added to all positions in the IPS patch itself (if this is +undesirable, execute a `seek 0` instruction beforehand), but it is not updated by this instruction. + +The IPS patch is located in patch space, at the address specified by the corresponding argument to the instruction. +After applying the patch, the variable passed to this instruction is set to point to the address where the IPS patch +ends (that is, to the end of the patch, right after the `EOF` marker). + +Note that, as with any other instruction that writes to the file buffer, writing past its end will extend it +accordingly and zero-fill any gaps that arise; also, as with any other instruction that reads from patch space, reading +past its end is a fatal error. + +The IPS patching procedure (as implemented by this specification, in compliance with the IPS specification itself) is +defined as follows: + +1. Read the first five bytes of the patch; they must be equal to the hexadecimal string `50 41 54 43 48` (representing + the ASCII string `PATCH`). If these bytes don't match, a fatal error occurs. +2. Read three bytes and interpret them as a 24-bit big-endian unsigned value. If this value is equal to `0x454f46` + (which happens to be the ASCII string `EOF`), the patch is done (and the variable passed to the `ipspatch` + instruction must be set to the address that would be read next); otherwise, continue with the next step. +3. Add this value to the current file pointer to determine where to begin writing. (Note that this step is specific to + this specification, and not part of the IPS format itself.) +4. Read two bytes and interpret them as a 16-bit big-endian unsigned value; this is the amount of data to write. +5. If the value read in the previous step is not zero, read as many bytes as that value indicates and write those bytes + (in the same order) to the file buffer at the position calculated in step 3; then go back to step 2. +6. Otherwise (that is, if the value read in step 4 was zero), read another 16-bit unsigned value (representing the + count) followed by a single byte; repeatedly write this byte as many times as the count specifies starting at the + position calculated in step 3, and then go back to step 2. + +### Applying a BSP patch contained within the BSP + +``` +bsppatch #variable, address, length +``` + +This instruction executes a BSP contained within the BSP. That is, it is used to create a child BSP that is forked from +the current one and executed separately; the parent is suspended until the child finishes. + +The child BSP has its own variables, stack, instruction pointer, message buffer and patch space, but it shares the file +buffer and current file pointer with the parent; the file buffer and the current file pointer (including its state) are +inherited from the parent script (the one executing the `bsppatch` instruction), and they can be modified freely by the +child BSP (retaining the modifications when it exits). + +The child BSP's patch space is created with the length specified in the `bsppatch` instruction, and filled with data +read from the parent's patch space starting at the specified address; a fatal error occurs if this causes the engine to +read past the end of the parent's patch space. The child BSP's variables and instruction pointer are all initialized to +zero, its message buffer is initialized to the empty string, and its stack is initialized empty, just like when +executing a BSP normally. Execution then resumes with the child, continuing until the child exits; only when the child +exits, the `bsppatch` instruction of the parent can be completed. When the child exits, the variable passed to +`bsppatch` in the parent is set to the child's exit status. + +The engine must not write out to the target file when the child exits, regardless of exit status; it must, instead, +pass the exit status to the parent via the variable passed to `bsppatch`. The file buffer and current file position, +being shared resources between the parent and the child, must also conserve their state when execution returns from the +child to the parent. + +If the child's execution triggers a fatal error, this fatal error must be propagated to the parent; in other words, +a fatal error at any depth must halt the whole engine. Execution of the parent must **not** be resumed after a fatal +error occurs in the child.