ref: 31c3c94d64e1ac1e40c95acfda7de8b99b4f302b
parent: 775b5d046c7f42a3bd8034b92da92e25969bdbf7
author: vulcandth <[email protected]>
date: Sat Mar 12 12:34:04 EST 2022
Build the Virtual Console patch with `make crystal11_vc` (#882) Fixes #813
--- a/.gitattributes
+++ b/.gitattributes
@@ -28,6 +28,9 @@
*.attrmap binary diff=hex
*.tilemap binary diff=hex
+# Declare files that will always have CRLF line endings on checkout.
+*.patch.template text eol=crlf linguist-language=INI
+
# these are generated but just in case
*.lz binary diff=hex
*.2bpp binary diff=hex
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@
# compiled roms
*.gbc
*.gb
+*.patch
# rgbds extras
*.map
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,9 @@
-roms := pokecrystal.gbc pokecrystal11.gbc pokecrystal_au.gbc pokecrystal_debug.gbc pokecrystal11_debug.gbc
+roms := pokecrystal.gbc \
+ pokecrystal11.gbc \
+ pokecrystal_au.gbc \
+ pokecrystal_debug.gbc \
+ pokecrystal11_debug.gbc
+patches := pokecrystal11.patch
rom_obj := \
audio.o \
@@ -23,6 +28,7 @@
pokecrystal_au_obj := $(rom_obj:.o=_au.o)
pokecrystal_debug_obj := $(rom_obj:.o=_debug.o)
pokecrystal11_debug_obj := $(rom_obj:.o=11_debug.o)
+pokecrystal11_vc_obj := $(rom_obj:.o=11_vc.o)
### Build tools
@@ -54,16 +60,42 @@
crystal_au: pokecrystal_au.gbc
crystal_debug: pokecrystal_debug.gbc
crystal11_debug: pokecrystal11_debug.gbc
+crystal11_vc: pokecrystal11.patch
clean: tidy
- find gfx \( -name "*.[12]bpp" -o -name "*.lz" -o -name "*.gbcpal" -o -name "*.sgb.tilemap" \) -delete
- find gfx/pokemon -mindepth 1 ! -path "gfx/pokemon/unown/*" \( -name "bitmask.asm" -o -name "frames.asm" -o -name "front.animated.tilemap" -o -name "front.dimensions" \) -delete
+ find gfx \
+ \( -name "*.[12]bpp" \
+ -o -name "*.lz" \
+ -o -name "*.gbcpal" \
+ -o -name "*.sgb.tilemap" \) \
+ -delete
+ find gfx/pokemon -mindepth 1 \
+ ! -path "gfx/pokemon/unown/*" \
+ \( -name "bitmask.asm" \
+ -o -name "frames.asm" \
+ -o -name "front.animated.tilemap" \
+ -o -name "front.dimensions" \) \
+ -delete
tidy:
- $(RM) $(roms) $(pokecrystal_obj) $(pokecrystal11_obj) $(pokecrystal_au_obj) $(pokecrystal_debug_obj) $(pokecrystal11_debug_obj) $(roms:.gbc=.map) $(roms:.gbc=.sym) rgbdscheck.o
+ $(RM) $(roms) \
+ $(roms:.gbc=.sym) \
+ $(roms:.gbc=.map) \
+ $(patches) \
+ $(patches:.patch=_vc.gbc) \
+ $(patches:.patch=_vc.sym) \
+ $(patches:.patch=_vc.map) \
+ $(patches:%.patch=vc/%.constants.sym) \
+ $(pokecrystal_obj) \
+ $(pokecrystal11_obj) \
+ $(pokecrystal11_vc_obj) \
+ $(pokecrystal_au_obj) \
+ $(pokecrystal_debug_obj) \
+ $(pokecrystal11_debug_obj) \
+ rgbdscheck.o
$(MAKE) clean -C tools/
-compare: $(roms)
+compare: $(roms) $(patches)
@$(SHA1) -c roms.sha1
tools:
@@ -81,7 +113,13 @@
$(pokecrystal_au_obj): RGBASMFLAGS += -D _CRYSTAL11 -D _CRYSTAL_AU
$(pokecrystal_debug_obj): RGBASMFLAGS += -D _DEBUG
$(pokecrystal11_debug_obj): RGBASMFLAGS += -D _CRYSTAL11 -D _DEBUG
+$(pokecrystal11_vc_obj): RGBASMFLAGS += -D _CRYSTAL11 -D _CRYSTAL11_VC
+%.patch: %_vc.sym vc/%.constants.sym %_vc.gbc %.gbc vc/%.patch.template
+ tools/make_patch $^ $@
+
+%.sym: ;
+
rgbdscheck.o: rgbdscheck.asm
$(RGBASM) -o $@ $<
@@ -105,7 +143,12 @@
$(foreach obj, $(pokecrystal_au_obj), $(eval $(call DEP,$(obj),$(obj:_au.o=.asm))))
$(foreach obj, $(pokecrystal_debug_obj), $(eval $(call DEP,$(obj),$(obj:_debug.o=.asm))))
$(foreach obj, $(pokecrystal11_debug_obj), $(eval $(call DEP,$(obj),$(obj:11_debug.o=.asm))))
+$(foreach obj, $(pokecrystal11_vc_obj), $(eval $(call DEP,$(obj),$(obj:11_vc.o=.asm))))
+# Dependencies for VC files that need to run scan_includes
+%.constants.sym: %.constants.asm $(shell tools/scan_includes %.constants.asm) | rgbdscheck.o
+ $(RGBASM) $< > $@
+
endif
@@ -114,10 +157,12 @@
pokecrystal_au_opt = -Cjv -t PM_CRYSTAL -i BYTU -n 0 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
pokecrystal_debug_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 0 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
pokecrystal11_debug_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 1 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
+pokecrystal11_vc_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 1 -k 01 -l 0x33 -m 0x10 -r 3 -p 0
pokecrystal_base = us
pokecrystal11_base = us
pokecrystal_au_base = us
+pokecrystal11_vc_base = us
pokecrystal_debug_base = dbg
pokecrystal11_debug_base = dbg
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
- Pokemon - Crystal Version (A) [C][!].gbc `sha1: a0fc810f1d4e124434f7be2c989ab5b5892ddf36`
- CRYSTAL_ps3_010328d.bin `sha1: c60d57a24bbe8ecf7cba54ab3f90669f97bd330d`
- CRYSTAL_ps3_us_revise_010710d.bin `sha1: 391ae86b1d5a26db712ffe6c28bbf2a1f804c3c4`
+- CGBBYTE1.784.patch `sha1: a25517f60ca0e887d39ec698aa56a0040532a4b3`
To set up the repository, see [INSTALL.md](INSTALL.md).
--- a/docs/index.md
+++ b/docs/index.md
@@ -21,6 +21,7 @@
- [battle_anim_commands.md](battle_anim_commands.md)
- [move_effect_commands.md](move_effect_commands.md)
- [music_commands.md](music_commands.md)
+- [vc_patch.md](vc_patch.md)
## Other subsystems
--- /dev/null
+++ b/docs/vc_patch.md
@@ -1,0 +1,124 @@
+# Nintendo 2DS/3DS Virtual Console Patch
+
+The Nintendo Virtual Console is an emulator on the 2DS and 3DS consoles. It can emulate the Game Boy Color (among other consoles), while applying enhancements or modifications to some games, such as replacing Link Cable functionality with the DS' Wireless Link capabilities, or disabling Game Boy Printer features.
+
+Game-specific enhancements are determined by a `.patch` file corresponding to the `.gbc` ROM file. These files are bundled together in a `.cia` file; creating such a file is outside the scope of this project.
+
+
+## Build pokecrystal11.patch
+
+To build **pokecrystal11.patch**:
+
+```bash
+make crystal11_vc
+```
+
+This will also create two ROM files, **pokecrystal11.gbc** and **pokecrystal11_vc.gbc**. The pokecrystal11_vc.gbc file has the patches already applied to it; do *not* use this file! The ROM file and patch file must share the same name, so use pokecrystal11.patch together with pokecrystal11.gbc.
+
+
+## Custom files
+
+There are a few files involved with building the `.patch` file, in addition to the ones used for building ROMs.
+
+### vc/pokecrystal11.patch.template
+
+The `.patch.template` file is the basis for the `.patch` file. Many numeric values in the `.patch` file are derived from the values of labels, constants, and ROM content; these values are abstracted into *commands* that get evaluated by `tools/make_patch` to output symbolic names as their actual values, formatted to match the original `.patch` file.
+
+### vc/pokecrystal11.constants.asm
+
+The `.constants.asm` file is used to create a `.constants.sym` file. Typical `.sym` files only list the values of *labels* (ROM banks and addresses); this file is used to list *constants* that are needed by the `.patch.template`. Any constants that the `.patch.template` refers to must be explicitly printed here with the `vc_const` macro.
+
+### tools/make_patch.c
+
+The program used to convert a `.patch.template` into a `.patch` file.
+
+To convert `vc.patch.template` into `vc.patch`:
+
+```bash
+tools/make_patch labels.sym constants.sym patched.gbc original.gbc vc.patch.template vc.patch
+```
+
+For example, this is what `make crystal11_vc` does:
+
+```bash
+tools/make_patch pokecrystal11_vc.sym vc/pokecrystal11.constants.sym pokecrystal11_vc.gbc pokecrystal11.gbc vc/pokecrystal11.patch.template pokecrystal11.patch
+```
+
+## Patch types
+
+**Hooks** do not directly modify the ROM; they just identify locations within the ROM code. When the emulated code execution reaches a hook, the emulator performs an emulation function. For example, the `BiographySave_ret` hook is located after the code to add a new Hall of Fame entry, and causes the emulator to edit the save file to enable the GS Ball event.
+
+Hooks are defined with the `vc_hook` macro, which defines a label starting with "`.VC_`" for the patch template file to use.
+
+**Patches** directly modify the contents of the ROM. This is done before emulation begins. For example, the `print forbid 1` patch modifies an "`and A_BUTTON`" instruction to "`and 0`", so pressing A will not print Unown on the Game Boy Printer.
+
+Patches are defined with the `vc_patch` and `vc_patch_end` macros; `vc_patch` defines a label starting with "`.VC_`", `vc_patch_end` defines a corresponding label with "`_End`" appended. Between these two macros, the code or data is conditionally different depending on whether or not a patch file is being built.
+
+The sole purpose of creating `pokecrystal11_vc.gbc` and `pokecrystal11_vc.sym` is to make these labels and modifications available to `make_patch` for use in the patch template.
+
+
+## Patch template syntax
+
+**Comments** start at a semicolon "`;`" and continue until the end of the line. They are output as-is, without interpreting commands.
+
+**Patch names** are contained in "`[`" brackets "`]`". They are output as-is, without interpreting commands.
+
+Patch names also set the **current patch label**. This is the label starting with "`.VC_`" followed by the patch name, with any invalid characters (not letters "`A-Z`", digits "`0-9`", underscore "`_`", at sign "`@`", or hash "`#`") converted to underscores "`_`". These labels are conditionally defined only when building the patch file with the `vc_hook` and `vc_patch` macros. For example, the patch name "`[fight begin]`" corresponds to the patch label "`.VC_fight_begin`", generated by the "`vc_hook fight_begin`" macro.
+
+**Commands** are contained in "`{`" braces "`}`". They are not output themselves, but may produce their own output when interpreted.
+
+Commands are interpreted with a series of arguments, separated by whitespace (spaces, tabs, or newlines). Leading and trailing whitespace is ignored; for example, "`{ hex @ 4 }`" is interpreted the same as "`{hex @ 4}`".
+
+Some commands may output a **value series**, which is a series of two-digit hexadecimal bytes separated by spaces, preceded by a decimal count: "<code>a*N*: <i>v1</i> <i>v2</i> [...] <i>vN</i></code>".
+
+Some command names have variants to allow reproducing the exact formatting in a `.patch` file. If the command name is all lowercase, the output byte values use lowercase for hexadecimal digits A-F; if it is all uppercase, they use uppercase. For commands which output a value series, if the command name ends in an underscore, a space is output after the colon preceding the values; if not, then it is not.
+
+**Arguments** evaluate to numeric values. They may be any of the following:
+
+- Literal numbers in decimal (base 10, e.g. "`42`"), hexadecimal (base 16, e.g. "`0x2a`"), or octal (base 8, e.g. "`052`"). They may start with a plus sign "`+`". Numbers may not be negative.
+- Comparison operators: "`==`" is 0, "`>`" is 1, "`<`" is 2, "`>=`" is 3, "`<=`" is 4, "`!=`" is 5, and "`||`" is 0x11.
+- Symbol names from the two `.sym` files provided to `make_patch` may evaluate as their bank-relative address, or their absolute offset in the ROM, depending on the command. They may also be followed by a plus sign and a literal number that gets added to the value.
+- "`@`" evaluates as the address or absolute offset of the current patch/hook label.
+
+Any other characters are output as-is.
+
+
+## Patch template commands
+
+
+### <code>{patch[ <i>offset</i>]}</code>
+
+Outputs the bytes of the current patch as a value series, or as a hexadecimal number if there is only one byte. The bytes are found between the current patch label, and the label which is the current patch label plus "`_End`". An optional argument is an *offset* to add to the current patch label before gathering the contents between it and the end label.
+
+For example, if "`{patch}`" outputs "`a3:ab cd ef`", then "`{patch +1}`" outputs "`a2:cd ef`", and "`{patch +2}`" outputs "`0xef`".
+
+Converting the patch template will print a warning if any differences exist between the original and patched ROMs, which are not covered by "`patch`" commands.
+
+
+### <code>{dws <i>args</i>...}</code>
+
+Outputs its arguments as a value series of little-endian 16-bit words.
+
+Symbol names or "`@`" are evaluated as their relative address.
+
+For example, if "`{dws 42 0xabcd wCurSpecies}`" outputs "`a6:2a 00 cd ab 60 cf`", then "`{dws >= wCurSpecies+3}`" outputs "`a4:04 00 63 cf`".
+
+
+### <code>{db <i>arg</i>}</code>
+
+Outputs its argument as a single-byte value series.
+
+Symbol names or "`@`" are evaluated as their relative address.
+
+For example, "`{db 0xEF}`" outputs "`a1:ef`".
+
+
+### <code>{hex <i>arg</i>[ <i>padding</i>]}</code>
+
+Outputs its first argument as a hexadecimal number. An optional second argument is the minimum length in digits; values shorter than it will be padded with leading zeros.
+
+Symbol names or "`@`" are evaluated as their absolute offset.
+
+This command has extra variants to reproduce inconsistent output casing: "`Hex`" prints the last three digits in lowercase and the rest uppercase; "`HEx`" prints the last two digits in lowercase and the rest uppercase; "`hEX`" prints the last three digits in uppercase and the rest lowercase; and "`heX`" prints the last two digits in uppercase and the rest lowercase.
+
+For example, "`{hex 0xabcd 5}`" outputs "`0x0abcd`".
--- a/engine/battle/battle_transition.asm
+++ b/engine/battle/battle_transition.asm
@@ -21,6 +21,7 @@
ld hl, hVBlank
ld a, [hl]
push af
+ vc_hook FPA_link_fight_begin
ld [hl], $1
.loop
@@ -58,6 +59,7 @@
ld a, $1 ; unnecessary bankswitch?
ldh [rSVBK], a
pop af
+ vc_hook FPA_link_fight_End4
ldh [hVBlank], a
call DelayFrame
ret
@@ -310,6 +312,7 @@
dc 0, 0, 0, 1
StartTrainerBattle_SetUpForWavyOutro:
+ vc_hook FPA_link_fight_End0
farcall RespawnPlayerAndOpponent
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
@@ -367,6 +370,7 @@
ret
StartTrainerBattle_SetUpForSpinOutro:
+ vc_hook FPA_link_fight_End1
farcall RespawnPlayerAndOpponent
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
@@ -509,6 +513,7 @@
.wedge5: db 4, 0, 3, 0, 3, 0, 2, 0, 2, 0, 1, 0, 1, 0, 1, -1
StartTrainerBattle_SetUpForRandomScatterOutro:
+ vc_hook FPA_link_fight_End2
farcall RespawnPlayerAndOpponent
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
@@ -763,6 +768,7 @@
calc_sine_wave
StartTrainerBattle_ZoomToBlack:
+ vc_hook FPA_link_fight_End3
farcall RespawnPlayerAndOpponent
ld de, .boxes
--- a/engine/battle/core.asm
+++ b/engine/battle/core.asm
@@ -8940,6 +8940,7 @@
predef PlaceGraphic
xor a
ldh [hWY], a
+ vc_hook fight_begin
ldh [rWY], a
call WaitBGMap
call HideSprites
--- a/engine/battle_anims/anim_commands.asm
+++ b/engine/battle_anims/anim_commands.asm
@@ -58,6 +58,13 @@
farcall CheckBattleScene
jr c, .disabled
+ vc_hook FPA_001_Begin
+ vc_hook FPA_002_Begin
+ vc_hook FPA_003_Begin
+ vc_hook FPA_004_Begin
+ vc_hook FPA_005_Begin
+ vc_hook FPA_006_Begin
+ vc_hook FPA_007_Begin
call BattleAnimClearHud
call RunBattleAnimScript
@@ -64,6 +71,7 @@
call BattleAnimAssignPals
call BattleAnimRequestPals
+ vc_hook FPA_001_End
xor a
ldh [hSCX], a
ldh [hSCY], a
@@ -673,6 +681,7 @@
.loop
ld a, [wBattleAnimGFXTempTileID]
cp (vTiles1 - vTiles0) / LEN_2BPP_TILE - BATTLEANIM_BASE_TILE
+ vc_hook FPA_042801_Begin
ret nc
call GetBattleAnimByte
ld [hli], a
--- a/engine/events/print_unown.asm
+++ b/engine/events/print_unown.asm
@@ -74,7 +74,13 @@
jr nz, .pressed_b
ldh a, [hJoyPressed]
+ vc_patch print_forbid_1
+if DEF(_CRYSTAL11_VC)
+ and 0
+else
and A_BUTTON
+endc
+ vc_patch_end
jr nz, .pressed_a
call .LeftRight
--- a/engine/gfx/color.asm
+++ b/engine/gfx/color.asm
@@ -1033,6 +1033,7 @@
.FinalPush:
ld hl, MltReq1Packet
call _PushSGBPals
+ vc_hook Network_RESET
jp SGBDelayCycles
SGBBorder_PushBGPals:
--- a/engine/link/link.asm
+++ b/engine/link/link.asm
@@ -67,7 +67,13 @@
.player_1
ld de, MUSIC_NONE
call PlayMusic
+ vc_patch NetworkDelay1
+if DEF(_CRYSTAL11_VC)
+ ld c, 26
+else
ld c, 3
+endc
+ vc_patch_end
call DelayFrames
xor a
ldh [rIF], a
@@ -77,6 +83,7 @@
ld hl, wLinkBattleRNPreamble
ld de, wEnemyMon
ld bc, SERIAL_RN_PREAMBLE_LENGTH + SERIAL_RNS_LENGTH
+ vc_hook Network358
call Serial_ExchangeBytes
ld a, SERIAL_NO_DATA_BYTE
ld [de], a
@@ -84,6 +91,7 @@
ld hl, wLinkData
ld de, wOTPartyData
ld bc, SERIAL_PREAMBLE_LENGTH + NAME_LENGTH + 1 + PARTY_LENGTH + 1 + (REDMON_STRUCT_LENGTH + NAME_LENGTH * 2) * PARTY_LENGTH + 3
+ vc_hook Network359
call Serial_ExchangeBytes
ld a, SERIAL_NO_DATA_BYTE
ld [de], a
@@ -91,6 +99,7 @@
ld hl, wPlayerPatchLists
ld de, wOTPatchLists
ld bc, 200
+ vc_hook Network364
call Serial_ExchangeBytes
xor a
@@ -224,7 +233,13 @@
.player_1
ld de, MUSIC_NONE
call PlayMusic
+ vc_patch NetworkDelay4
+if DEF(_CRYSTAL11_VC)
+ ld c, 26
+else
ld c, 3
+endc
+ vc_patch_end
call DelayFrames
xor a
ldh [rIF], a
@@ -234,6 +249,7 @@
ld hl, wLinkBattleRNPreamble
ld de, wEnemyMon
ld bc, SERIAL_RN_PREAMBLE_LENGTH + SERIAL_RNS_LENGTH
+ vc_hook Network360
call Serial_ExchangeBytes
ld a, SERIAL_NO_DATA_BYTE
ld [de], a
@@ -241,6 +257,7 @@
ld hl, wLinkData
ld de, wOTPartyData
ld bc, SERIAL_PREAMBLE_LENGTH + NAME_LENGTH + 1 + PARTY_LENGTH + 1 + 2 + (PARTYMON_STRUCT_LENGTH + NAME_LENGTH * 2) * PARTY_LENGTH + 3
+ vc_hook Network361
call Serial_ExchangeBytes
ld a, SERIAL_NO_DATA_BYTE
ld [de], a
@@ -248,6 +265,7 @@
ld hl, wPlayerPatchLists
ld de, wOTPatchLists
ld bc, 200
+ vc_hook Network362
call Serial_ExchangeBytes
ld a, [wLinkMode]
@@ -256,6 +274,7 @@
ld hl, wLinkPlayerMail
ld de, wLinkOTMail
ld bc, wLinkPlayerMailEnd - wLinkPlayerMail
+ vc_hook Network363
call ExchangeBytes
.not_trading
@@ -1608,6 +1627,7 @@
ldh [rSC], a
ld a, (1 << rSC_ON) | (1 << rSC_CLOCK)
ldh [rSC], a
+ vc_hook ret_heya
ret
GSPlaceTradeScreenFooter: ; unreferenced
@@ -2009,6 +2029,7 @@
ld de, String_TradeCompleted
call PlaceString
farcall Link_WaitBGMap
+ vc_hook save_game_end
ld c, 50
call DelayFrames
ld a, [wLinkMode]
@@ -2161,7 +2182,13 @@
ret
EnterTimeCapsule:
+ vc_patch NetworkDelay2
+if DEF(_CRYSTAL11_VC)
+ ld c, 26
+else
ld c, 10
+endc
+ vc_patch_end
call DelayFrames
ld a, $4
call Link_EnsureSync
@@ -2218,6 +2245,7 @@
ld [hl], a
ldh [hVBlank], a
ld [wLinkMode], a
+ vc_hook term_exit
ret
SetBitsForLinkTradeRequest:
@@ -2282,6 +2310,7 @@
ld a, (0 << rSC_ON) | (0 << rSC_CLOCK)
ldh [rSC], a
ld a, (1 << rSC_ON) | (0 << rSC_CLOCK)
+ vc_hook linkCable_fake_begin
ldh [rSC], a
ld a, [wLinkTimeoutFrames]
dec a
@@ -2374,7 +2403,13 @@
ld a, $6
ld [wPlayerLinkAction], a
ld hl, wLinkTimeoutFrames
+ vc_patch NetworkDelay6
+if DEF(_CRYSTAL11_VC)
+ ld a, $3
+else
ld a, 1
+endc
+ vc_patch_end
ld [hli], a
ld [hl], 50
call Link_CheckCommunicationError
@@ -2395,6 +2430,7 @@
Link_CheckCommunicationError:
xor a
ldh [hSerialReceivedNewData], a
+ vc_hook linkCable_fake_end
ld a, [wLinkTimeoutFrames]
ld h, a
ld a, [wLinkTimeoutFrames + 1]
@@ -2425,6 +2461,7 @@
.CheckConnected:
call WaitLinkTransfer
ld hl, wLinkTimeoutFrames
+ vc_hook Network_RECHECK
ld a, [hli]
inc a
ret nz
@@ -2433,7 +2470,13 @@
ret
.AcknowledgeSerial:
+ vc_patch NetworkDelay3
+if DEF(_CRYSTAL11_VC)
+ ld b, 26
+else
ld b, 10
+endc
+ vc_patch_end
.loop
call DelayFrame
call LinkDataReceived
@@ -2460,8 +2503,10 @@
ld a, [wChosenCableClubRoom]
push af
farcall Link_SaveGame
+ vc_hook linkCable_block_input
ld a, TRUE
jr nc, .return_result
+ vc_hook linkCable_block_input2
xor a ; FALSE
.return_result
ld [wScriptVar], a
@@ -2498,6 +2543,7 @@
ret
TimeCapsule:
+ vc_hook to_play2_mons1
ld a, LINK_TIMECAPSULE
ld [wLinkMode], a
call DisableSpriteUpdates
@@ -2508,6 +2554,7 @@
ret
TradeCenter:
+ vc_hook to_play2_trade
ld a, LINK_TRADECENTER
ld [wLinkMode], a
call DisableSpriteUpdates
@@ -2518,6 +2565,7 @@
ret
Colosseum:
+ vc_hook to_play2_battle
ld a, LINK_COLOSSEUM
ld [wLinkMode], a
call DisableSpriteUpdates
@@ -2532,6 +2580,7 @@
ld [wLinkMode], a
ld c, 3
call DelayFrames
+ vc_hook room_check
jp Link_ResetSerialRegistersAfterLinkClosure
FailedLinkToPast:
--- a/engine/link/mystery_gift.asm
+++ b/engine/link/mystery_gift.asm
@@ -37,14 +37,23 @@
; Prepare the first of two messages for wMysteryGiftPartnerData
farcall StageDataForMysteryGift
call ClearMysteryGiftTrainer
+ vc_patch infrared_fake_0
+if DEF(_CRYSTAL11_VC)
+ farcall StagePartyDataForMysteryGift
+ call ClearMysteryGiftTrainer
+ nop
+else
ld a, 2
ld [wMysteryGiftMessageCount], a
ld a, wMysteryGiftPartnerDataEnd - wMysteryGiftPartnerData
ld [wMysteryGiftStagedDataLength], a
+endc
+ vc_patch_end
ldh a, [rIE]
push af
call ExchangeMysteryGiftData
+ vc_hook infrared_fake_4
ld d, a
xor a
ldh [rIF], a
@@ -260,6 +269,26 @@
jp CloseSRAM
ExchangeMysteryGiftData:
+ vc_hook infrared_fake_2
+ vc_patch infrared_fake_1
+if DEF(_CRYSTAL11_VC)
+ ld d, $ef
+.loop
+ dec d
+ ld a, d
+ or a
+ jr nz, .loop
+ vc_hook infrared_fake_3
+ nop
+ cp MG_CANCELED
+.restart ; same location as unpatched .restart
+ ret z
+ nop
+ nop
+ cp MG_OKAY
+ jr nz, ExchangeMysteryGiftData
+ ret
+else
di
farcall ClearChannels
call InitializeIRCommunicationInterrupts
@@ -268,6 +297,8 @@
call BeginIRCommunication
call InitializeIRCommunicationRoles
ldh a, [hMGStatusFlags]
+endc
+ vc_patch_end
cp MG_CANCELED
jp z, EndOrContinueMysteryGiftIRCommunication
cp MG_OKAY
--- a/engine/menus/menu.asm
+++ b/engine/menus/menu.asm
@@ -362,7 +362,9 @@
call GetMenuJoypad
and a
ret z
+ vc_hook print_forbid_3
scf
+ vc_hook print_forbid_2
ret
_2DMenuInterpretJoypad:
--- a/engine/menus/save.asm
+++ b/engine/menus/save.asm
@@ -161,6 +161,15 @@
ld bc, wHallOfFamePokemonListEnd - wHallOfFamePokemonList + 1
call CopyBytes
call CloseSRAM
+; This vc_hook causes the Virtual Console to set [sMobileEventIndex] and [sMobileEventIndexBackup]
+; to MOBILE_EVENT_OBJECT_GS_BALL ($b), which enables you to get the GS Ball, take it to Kurt, and
+; encounter Celebi. It assumes that sMobileEventIndex and sMobileEventIndexBackup are at their
+; original addresses.
+ vc_hook BiographySave_ret
+ vc_assert BANK(sMobileEventIndex) == $1 && sMobileEventIndex == $be3c, \
+ "sMobileEventIndex is no longer located at 01:be3c."
+ vc_assert BANK(sMobileEventIndexBackup) == $1 && sMobileEventIndexBackup == $be44, \
+ "sMobileEventIndexBackup is no longer located at 01:be44."
ret
SaveGameData:
--- a/engine/overworld/scripting.asm
+++ b/engine/overworld/scripting.asm
@@ -389,6 +389,7 @@
ld a, TRUE
.no
ld [wScriptVar], a
+ vc_hook E_YESNO
ret
Script_loadmenu:
--- a/engine/pokedex/pokedex.asm
+++ b/engine/pokedex/pokedex.asm
@@ -356,6 +356,7 @@
ld a, [hl]
and B_BUTTON
jr nz, .return_to_prev_screen
+ vc_hook print_forbid_5
ld a, [hl]
and A_BUTTON
jr nz, .do_menu_action
--- a/engine/pokemon/mail_2.asm
+++ b/engine/pokemon/mail_2.asm
@@ -67,7 +67,13 @@
ldh a, [hJoyPressed]
and A_BUTTON | B_BUTTON | START
jr z, .loop
+ vc_patch print_forbid_4
+if DEF(_CRYSTAL11_VC)
+ and 0
+else
and START
+endc
+ vc_patch_end
jr nz, .pressed_start
ret
--- a/home/serial.asm
+++ b/home/serial.asm
@@ -290,6 +290,7 @@
jp WaitLinkTransfer ; pointless
WaitLinkTransfer::
+ vc_hook send_send_buf2
ld a, $ff
ld [wOtherPlayerLinkAction], a
.loop
@@ -317,7 +318,13 @@
inc a
jr z, .loop
+ vc_patch Network10
+if DEF(_CRYSTAL11_VC)
+ ld b, 26
+else
ld b, 10
+endc
+ vc_patch_end
.receive
call DelayFrame
call LinkTransfer
@@ -324,7 +331,13 @@
dec b
jr nz, .receive
+ vc_patch Network11
+if DEF(_CRYSTAL11_VC)
+ ld b, 26
+else
ld b, 10
+endc
+ vc_patch_end
.acknowledge
call DelayFrame
call LinkDataReceived
@@ -333,6 +346,7 @@
ld a, [wOtherPlayerLinkAction]
ld [wOtherPlayerLinkMode], a
+ vc_hook send_send_buf2_ret
ret
LinkTransfer::
--- a/macros.asm
+++ b/macros.asm
@@ -6,6 +6,7 @@
INCLUDE "macros/code.asm"
INCLUDE "macros/gfx.asm"
INCLUDE "macros/coords.asm"
+INCLUDE "macros/vc.asm"
INCLUDE "macros/scripts/audio.asm"
INCLUDE "macros/scripts/maps.asm"
--- /dev/null
+++ b/macros/vc.asm
@@ -1,0 +1,27 @@
+vc_hook: MACRO
+ if DEF(_CRYSTAL11_VC)
+ .VC_\1::
+ endc
+ENDM
+
+vc_patch: MACRO
+ if DEF(_CRYSTAL11_VC)
+ assert !DEF(CURRENT_VC_PATCH), "Already started a vc_patch"
+CURRENT_VC_PATCH EQUS "\1"
+ .VC_{CURRENT_VC_PATCH}::
+ endc
+ENDM
+
+vc_patch_end: MACRO
+ if DEF(_CRYSTAL11_VC)
+ assert DEF(CURRENT_VC_PATCH), "No vc_patch started"
+ .VC_{CURRENT_VC_PATCH}_End::
+PURGE CURRENT_VC_PATCH
+ endc
+ENDM
+
+vc_assert: MACRO
+ if DEF(_CRYSTAL11_VC)
+ assert \#
+ endc
+ENDM
--- a/mobile/mobile_40.asm
+++ b/mobile/mobile_40.asm
@@ -1530,6 +1530,7 @@
_LinkBattleSendReceiveAction:
call .StageForSend
ld [wLinkBattleSentAction], a
+ vc_hook send_byt2
farcall PlaceWaitingText
ld a, [wLinkMode]
cp LINK_MOBILE
@@ -1584,7 +1585,14 @@
inc a
jr z, .waiting
+ vc_hook send_byt2_ret
+ vc_patch send_byt2_wait
+if DEF(_CRYSTAL11_VC)
+ ld b, 26
+else
ld b, 10
+endc
+ vc_patch_end
.receive
call DelayFrame
call LinkTransfer
@@ -1591,7 +1599,14 @@
dec b
jr nz, .receive
+ vc_hook send_dummy
+ vc_patch send_dummy_wait
+if DEF(_CRYSTAL11_VC)
+ ld b, 26
+else
ld b, 10
+endc
+ vc_patch_end
.acknowledge
call DelayFrame
call LinkDataReceived
@@ -1598,6 +1613,7 @@
dec b
jr nz, .acknowledge
+ vc_hook send_dummy_end
ld a, [wOtherPlayerLinkAction]
ld [wBattleAction], a
ret
--- a/roms.sha1
+++ b/roms.sha1
@@ -3,3 +3,4 @@
a0fc810f1d4e124434f7be2c989ab5b5892ddf36 *pokecrystal_au.gbc
c60d57a24bbe8ecf7cba54ab3f90669f97bd330d *pokecrystal_debug.gbc
391ae86b1d5a26db712ffe6c28bbf2a1f804c3c4 *pokecrystal11_debug.gbc
+a25517f60ca0e887d39ec698aa56a0040532a4b3 *pokecrystal11.patch
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -1,8 +1,9 @@
+gfx
lzcomp
-png_dimensions
-scan_includes
+make_patch
palette
+png_dimensions
pokemon_animation
pokemon_animation_graphics
-gfx
+scan_includes
stadium
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -6,6 +6,7 @@
tools := \
lzcomp \
gfx \
+ make_patch \
png_dimensions \
pokemon_animation \
pokemon_animation_graphics \
--- /dev/null
+++ b/tools/make_patch.c
@@ -1,0 +1,434 @@
+#define PROGRAM_NAME "make_patch"
+#define USAGE_OPTS "labels.sym constants.sym patched.gbc original.gbc vc.patch.template vc.patch"
+
+#include "common.h"
+
+#include <ctype.h>
+
+struct Buffer {
+ size_t item_size;
+ size_t size;
+ size_t capacity;
+ void *data;
+};
+
+struct Symbol {
+ struct Symbol *next;
+ unsigned int address;
+ unsigned int offset;
+ char name[]; // C99 FAM
+};
+
+struct Patch {
+ unsigned int offset;
+ unsigned int size;
+};
+
+struct Buffer *buffer_create(size_t item_size) {
+ struct Buffer *buffer = xmalloc(sizeof(*buffer));
+ buffer->item_size = item_size;
+ buffer->size = 0;
+ buffer->capacity = 0x10;
+ buffer->data = xmalloc(buffer->capacity * item_size);
+ return buffer;
+}
+
+void buffer_append(struct Buffer *buffer, const void *item) {
+ if (buffer->size >= buffer->capacity) {
+ buffer->capacity = (buffer->capacity + 1) * 2;
+ buffer->data = xrealloc(buffer->data, buffer->capacity * buffer->item_size);
+ }
+ memcpy((char *)buffer->data + (buffer->size++ * buffer->item_size), item, buffer->item_size);
+}
+
+void buffer_free(struct Buffer *buffer) {
+ free(buffer->data);
+ free(buffer);
+}
+
+void symbol_append(struct Symbol **symbols, const char *name, int bank, int address) {
+ size_t name_len = strlen(name) + 1;
+ struct Symbol *symbol = xmalloc(sizeof(*symbol) + name_len);
+ symbol->address = address;
+ symbol->offset = bank > 0 && address < 0x8000 ? address + (bank - 1) * 0x4000 : address;
+ memcpy(symbol->name, name, name_len);
+ symbol->next = *symbols;
+ *symbols = symbol;
+}
+
+void symbol_free(struct Symbol *symbols) {
+ for (struct Symbol *next; symbols; symbols = next) {
+ next = symbols->next;
+ free(symbols);
+ }
+}
+
+const struct Symbol *symbol_find(const struct Symbol *symbols, const char *name) {
+ size_t name_len = strlen(name);
+ for (const struct Symbol *symbol = symbols; symbol; symbol = symbol->next) {
+ size_t sym_name_len = strlen(symbol->name);
+ if (name_len > sym_name_len) {
+ continue;
+ }
+ const char *sym_name = symbol->name;
+ if (name[0] == '.') {
+ // If `name` is a local label, compare it to the local part of `symbol->name`
+ sym_name += sym_name_len - name_len;
+ }
+ if (!strcmp(sym_name, name)) {
+ return symbol;
+ }
+ }
+ error_exit("Error: Unknown symbol: \"%s\"\n", name);
+}
+
+const struct Symbol *symbol_find_cat(const struct Symbol *symbols, const char *prefix, const char *suffix) {
+ char *sym_name = xmalloc(strlen(prefix) + strlen(suffix) + 1);
+ sprintf(sym_name, "%s%s", prefix, suffix);
+ const struct Symbol *symbol = symbol_find(symbols, sym_name);
+ free(sym_name);
+ return symbol;
+}
+
+int parse_number(const char *input, int base) {
+ char *endptr;
+ int n = (int)strtol(input, &endptr, base);
+ if (endptr == input || *endptr || n < 0) {
+ error_exit("Error: Cannot parse number: \"%s\"", input);
+ }
+ return n;
+}
+
+void parse_symbol_value(char *input, int *restrict bank, int *restrict address) {
+ char *colon = strchr(input, ':');
+ if (!colon) {
+ error_exit("Error: Cannot parse bank+address: \"%s\"", input);
+ }
+ *colon++ = '\0';
+ *bank = parse_number(input, 16);
+ *address = parse_number(colon, 16);
+}
+
+void parse_symbols(const char *filename, struct Symbol **symbols) {
+ FILE *file = xfopen(filename, 'r');
+ struct Buffer *buffer = buffer_create(1);
+
+ enum { SYM_PRE, SYM_VALUE, SYM_SPACE, SYM_NAME } state = SYM_PRE;
+ int bank = 0;
+ int address = 0;
+
+ for (;;) {
+ int c = getc(file);
+ if (c == EOF || c == '\n' || c == '\r' || c == ';' || (state == SYM_NAME && (c == ' ' || c == '\t'))) {
+ if (state == SYM_NAME) {
+ // The symbol name has ended; append the buffered symbol
+ buffer_append(buffer, &(char []){'\0'});
+ symbol_append(symbols, buffer->data, bank, address);
+ }
+ // Skip to the next line, ignoring anything after the symbol value and name
+ state = SYM_PRE;
+ while (c != EOF && c != '\n' && c != '\r') {
+ c = getc(file);
+ }
+ if (c == EOF) {
+ break;
+ }
+ } else if (c != ' ' && c != '\t') {
+ if (state == SYM_PRE || state == SYM_SPACE) {
+ // The symbol value or name has started; buffer its contents
+ if (++state == SYM_NAME) {
+ // The symbol name has started; parse the buffered value
+ buffer_append(buffer, &(char []){'\0'});
+ parse_symbol_value(buffer->data, &bank, &address);
+ }
+ buffer->size = 0;
+ }
+ buffer_append(buffer, &c);
+ } else if (state == SYM_VALUE) {
+ // The symbol value has ended; wait to see if a name comes after it
+ state = SYM_SPACE;
+ }
+ }
+
+ fclose(file);
+ buffer_free(buffer);
+}
+
+int parse_arg_value(const char *arg, bool absolute, const struct Symbol *symbols, const char *patch_name) {
+ // Comparison operators for "ConditionValueB" evaluate to their particular values
+ static const char *comparisons[] = {"==", ">", "<", ">=", "<=", "!=", "||"};
+ for (unsigned int i = 0; i < sizeof(comparisons) / sizeof(*comparisons); i++) {
+ if (!strcmp(arg, comparisons[i])) {
+ return i == 6 ? 0x11 : i; // "||" is 0x11
+ }
+ }
+
+ // Literal numbers evaluate to themselves
+ if (isdigit((unsigned)arg[0]) || arg[0] == '+') {
+ return parse_number(arg, 0);
+ }
+
+ // Symbols evaluate to their offset or address, plus an optional offset mod
+ int offset_mod = 0;
+ char *plus = strchr(arg, '+');
+ if (plus) {
+ offset_mod = parse_number(plus, 0);
+ *plus = '\0';
+ }
+ const char *sym_name = !strcmp(arg, "@") ? patch_name : arg; // "@" is the current patch label
+ const struct Symbol *symbol = symbol_find(symbols, sym_name);
+ return (absolute ? symbol->offset : symbol->address) + offset_mod;
+}
+
+void interpret_command(char *command, const struct Symbol *current_hook, const struct Symbol *symbols, struct Buffer *patches, FILE *restrict new_rom, FILE *restrict orig_rom, FILE *restrict output) {
+ // Strip all leading spaces and all but one trailing space
+ int x = 0;
+ for (int i = 0; command[i]; i++) {
+ if (!isspace((unsigned)command[i]) || (i > 0 && !isspace((unsigned)command[i - 1]))) {
+ command[x++] = command[i];
+ }
+ }
+ command[x - (x > 0 && isspace((unsigned)command[x - 1]))] = '\0';
+
+ // Count the arguments
+ int argc = 0;
+ for (const char *c = command; *c; c++) {
+ if (isspace((unsigned)*c)) {
+ argc++;
+ }
+ }
+
+ // Get the arguments
+ char *argv[argc]; // VLA
+ char *arg = command;
+ for (int i = 0; i < argc; i++) {
+ while (*arg && !isspace((unsigned)*arg)) {
+ arg++;
+ }
+ if (!*arg) {
+ break;
+ }
+ *arg++ = '\0';
+ argv[i] = arg;
+ }
+
+ // Use the arguments
+ if (!strcmp(command, "patch") || !strcmp(command, "PATCH") || !strcmp(command, "patch_") || !strcmp(command, "PATCH_")) {
+ if (!current_hook) {
+ error_exit("Error: No current patch for command: \"%s\"", command);
+ }
+ int current_offset = current_hook->offset + (argc > 0 ? parse_number(argv[0], 0) : 0);
+ if (fseek(orig_rom, current_offset, SEEK_SET)) {
+ error_exit("Error: Cannot seek to \"vc_patch %s\" in the original ROM\n", current_hook->name);
+ }
+ if (fseek(new_rom, current_offset, SEEK_SET)) {
+ error_exit("Error: Cannot seek to \"vc_patch %s\" in the new ROM\n", current_hook->name);
+ }
+ const struct Symbol *current_hook_end = symbol_find_cat(symbols, current_hook->name, "_End");
+ int length = current_hook_end->offset - current_offset;
+ buffer_append(patches, &(struct Patch){current_offset, length});
+ bool modified = false;
+ if (length == 1) {
+ int c = getc(new_rom);
+ modified = c != getc(orig_rom);
+ fprintf(output, isupper((unsigned)command[0]) ? "0x%02X" : "0x%02x", c);
+ } else {
+ fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", length);
+ for (int i = 0; i < length; i++) {
+ if (i) {
+ putc(' ', output);
+ }
+ int c = getc(new_rom);
+ modified |= c != getc(orig_rom);
+ fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", c);
+ }
+ }
+ if (!modified) {
+ fprintf(stderr, PROGRAM_NAME ": Warning: \"vc_patch %s\" doesn't alter the ROM\n", current_hook->name);
+ }
+
+ } else if (!strcmp(command, "dws") || !strcmp(command, "DWS") || !strcmp(command, "dws_") || !strcmp(command, "DWS_")) {
+ if (argc < 1) {
+ error_exit("Error: Invalid arguments for command: \"%s\"", command);
+ }
+ fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", argc * 2);
+ for (int i = 0; i < argc; i++) {
+ int value = parse_arg_value(argv[i], false, symbols, current_hook->name);
+ if (value > 0xffff) {
+ error_exit("Error: Invalid value for \"%s\" argument: 0x%x", command, value);
+ }
+ if (i) {
+ putc(' ', output);
+ }
+ fprintf(output, isupper((unsigned)command[0]) ? "%02X %02X": "%02x %02x", value & 0xff, value >> 8);
+ }
+
+ } else if (!strcmp(command, "db") || !strcmp(command, "DB") || !strcmp(command, "db_") || !strcmp(command, "DB_")) {
+ if (argc != 1) {
+ error_exit("Error: Invalid arguments for command: \"%s\"", command);
+ }
+ int value = parse_arg_value(argv[0], false, symbols, current_hook->name);
+ if (value > 0xff) {
+ error_exit("Error: Invalid value for \"%s\" argument: 0x%x", command, value);
+ }
+ fputs(command[strlen(command) - 1] == '_' ? "a1: " : "a1:", output);
+ fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", value);
+
+ } else if (!strcmp(command, "hex") || !strcmp(command, "HEX") || !strcmp(command, "HEx") || !strcmp(command, "Hex") || !strcmp(command, "heX") || !strcmp(command, "hEX")) {
+ if (argc != 1 && argc != 2) {
+ error_exit("Error: Invalid arguments for command: \"%s\"", command);
+ }
+ int value = parse_arg_value(argv[0], true, symbols, current_hook->name);
+ int padding = argc > 1 ? parse_number(argv[1], 0) : 2;
+ if (!strcmp(command, "HEx")) {
+ fprintf(output, "0x%0*X%02x", padding - 2, value >> 8, value & 0xff);
+ } else if (!strcmp(command, "Hex")) {
+ fprintf(output, "0x%0*X%03x", padding - 3, value >> 12, value & 0xfff);
+ } else if (!strcmp(command, "heX")) {
+ fprintf(output, "0x%0*x%02X", padding - 2, value >> 8, value & 0xff);
+ } else if (!strcmp(command, "hEX")) {
+ fprintf(output, "0x%0*x%03X", padding - 3, value >> 12, value & 0xfff);
+ } else {
+ fprintf(output, isupper((unsigned)command[0]) ? "0x%0*X" : "0x%0*x", padding, value);
+ }
+
+ } else {
+ error_exit("Error: Unknown command: \"%s\"\n", command);
+ }
+}
+
+void skip_to_next_line(FILE *restrict input, FILE *restrict output) {
+ for (int c = getc(input); c != EOF; c = getc(input)) {
+ putc(c, output);
+ if (c == '\n' || c == '\r') {
+ break;
+ }
+ }
+}
+
+struct Buffer *process_template(const char *template_filename, const char *patch_filename, FILE *restrict new_rom, FILE *restrict orig_rom, const struct Symbol *symbols) {
+ FILE *input = xfopen(template_filename, 'r');
+ FILE *output = xfopen(patch_filename, 'w');
+
+ struct Buffer *patches = buffer_create(sizeof(struct Patch));
+ struct Buffer *buffer = buffer_create(1);
+
+ // The ROM checksum will always differ
+ buffer_append(patches, &(struct Patch){0x14e, 2});
+ // The Stadium data (see stadium.c) will always differ
+ unsigned int rom_size = (unsigned int)xfsize("", orig_rom);
+ unsigned int stadium_size = 24 + 6 + 2 + (rom_size / 0x2000) * 2;
+ buffer_append(patches, &(struct Patch){rom_size - stadium_size, stadium_size});
+
+ // Fill in the template
+ const struct Symbol *current_hook = NULL;
+ for (int c = getc(input); c != EOF; c = getc(input)) {
+ switch (c) {
+ case ';':
+ // ";" comments until the end of the line
+ putc(c, output);
+ skip_to_next_line(input, output);
+ break;
+
+ case '{':
+ // "{...}" is a template command; buffer its contents
+ buffer->size = 0;
+ for (c = getc(input); c != EOF && c != '}'; c = getc(input)) {
+ buffer_append(buffer, &c);
+ }
+ buffer_append(buffer, &(char []){'\0'});
+ // Interpret the command in the context of the current patch
+ interpret_command(buffer->data, current_hook, symbols, patches, new_rom, orig_rom, output);
+ break;
+
+ case '[':
+ // "[...]" is a patch label; buffer its contents
+ putc(c, output);
+ buffer->size = 0;
+ for (c = getc(input); c != EOF; c = getc(input)) {
+ putc(c, output);
+ if (c == ']') {
+ break;
+ } else if (!isalnum(c) && c != '_' && c != '@' && c != '#') {
+ // Convert non-identifier characters to underscores
+ c = '_';
+ }
+ buffer_append(buffer, &c);
+ }
+ buffer_append(buffer, &(char []){'\0'});
+ // The current patch should have a corresponding ".VC_" label
+ current_hook = symbol_find_cat(symbols, ".VC_", buffer->data);
+ skip_to_next_line(input, output);
+ break;
+
+ default:
+ putc(c, output);
+ }
+ }
+
+ rewind(orig_rom);
+ rewind(new_rom);
+
+ fclose(input);
+ fclose(output);
+ buffer_free(buffer);
+ return patches;
+}
+
+int compare_patch(const void *patch1, const void *patch2) {
+ unsigned int offset1 = ((const struct Patch *)patch1)->offset;
+ unsigned int offset2 = ((const struct Patch *)patch2)->offset;
+ return offset1 > offset2 ? 1 : offset1 < offset2 ? -1 : 0;
+}
+
+bool verify_completeness(FILE *restrict orig_rom, FILE *restrict new_rom, struct Buffer *patches) {
+ qsort(patches->data, patches->size, patches->item_size, compare_patch);
+ for (unsigned int offset = 0, index = 0; ; offset++) {
+ int orig_byte = getc(orig_rom);
+ int new_byte = getc(new_rom);
+ if (orig_byte == EOF || new_byte == EOF) {
+ return orig_byte == new_byte;
+ }
+ struct Patch *patch = &((struct Patch *)patches->data)[index];
+ if (index < patches->size && patch->offset == offset) {
+ if (fseek(orig_rom, patch->size, SEEK_CUR)) {
+ return false;
+ }
+ if (fseek(new_rom, patch->size, SEEK_CUR)) {
+ return false;
+ }
+ offset += patch->size;
+ index++;
+ } else if (orig_byte != new_byte) {
+ fprintf(stderr, PROGRAM_NAME ": Warning: Unpatched difference at offset: 0x%x\n", offset);
+ fprintf(stderr, " Original ROM value: 0x%02x\n", orig_byte);
+ fprintf(stderr, " Patched ROM value: 0x%02x\n", new_byte);
+ fprintf(stderr, " Current patch offset: 0x%06x\n", patch->offset);
+ return false;
+ }
+ }
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 7) {
+ usage_exit(1);
+ }
+
+ struct Symbol *symbols = NULL;
+ parse_symbols(argv[1], &symbols);
+ parse_symbols(argv[2], &symbols);
+
+ FILE *new_rom = xfopen(argv[3], 'r');
+ FILE *orig_rom = xfopen(argv[4], 'r');
+ struct Buffer *patches = process_template(argv[5], argv[6], new_rom, orig_rom, symbols);
+
+ if (!verify_completeness(orig_rom, new_rom, patches)) {
+ fprintf(stderr, PROGRAM_NAME ": Warning: Not all ROM differences are defined by \"%s\"\n", argv[6]);
+ }
+
+ symbol_free(symbols);
+ fclose(new_rom);
+ fclose(orig_rom);
+ buffer_free(patches);
+ return 0;
+}
--- /dev/null
+++ b/vc/pokecrystal11.constants.asm
@@ -1,0 +1,40 @@
+INCLUDE "constants.asm"
+
+; These are all the asm constants needed to make the crystal11_vc patch.
+
+vc_const: MACRO
+ println "00:{04x:\1} \1" ; same format as rgblink's .sym file
+ENDM
+
+; [fight begin]
+ vc_const SCREEN_HEIGHT_PX
+
+; [print forbid 2]
+ vc_const A_BUTTON
+; [print forbid 3]
+ vc_const MAPGROUP_CIANWOOD
+ vc_const MAP_CIANWOOD_PHOTO_STUDIO
+; [print forbid 5]
+ vc_const NO_INPUT
+ vc_const B_BUTTON
+ vc_const D_UP
+ vc_const D_DOWN
+
+; [FPA 001 Begin]
+ vc_const FISSURE
+; [FPA 002 Begin]
+ vc_const SELFDESTRUCT
+; [FPA 003 Begin]
+ vc_const THUNDER
+; [FPA 004 Begin]
+ vc_const FLASH
+; [FPA 005 Begin]
+ vc_const EXPLOSION
+; [FPA 006 Begin]
+ vc_const HORN_DRILL
+; [FPA 007 Begin]
+ vc_const HYPER_BEAM
+
+; [FPA 042801 Begin]
+ vc_const PRESENT
+ vc_const anim_1gfx_command
--- /dev/null
+++ b/vc/pokecrystal11.patch.template
@@ -1,0 +1,698 @@
+;Format Sample
+;[xxxx] ;User-defined Name (Max:31 chars)
+;Mode = 1 ;1:Fixcode; 2:Fixvalue; 3:Mask; 4:Palette; 5:Double Frame Buffer
+;Type = 0 ;0:Begin 1:End
+;Index = 0 ;Index
+;Address = x1F8000 ;ROM Address
+;MemAddress = x2000 ;RAM Address
+;Fixcode = 0 ;Mode1: Fixed Rom Code; Mode2: Fixed Value
+;DelayFrame = 0 ;Delay Frame
+;FadeFrame = 0 ;Fade Frame 0:Off
+;DarkEnable0 = 0 ;0:Off, 1:On (for Normal Mode)
+;ReduceEnable0 = 0 ;0:Off, 1:On (for Normal Mode)
+;MotionBEnable0 = 0 ;0:Off, 1:Black Fade, 2:, 3:Frame Blend (for Normal Mode)
+;Dark0 = 10 ;0~10 (for Normal Mode)
+;ReduceColorR0 = 0 ;0~31 (for Normal Mode)
+;ReduceColorG0 = 0 ;0~31 (for Normal Mode)
+;ReduceColorB0 = 0 ;0~31 (for Normal Mode)
+;MotionBlur0 = 31 ;0~31 (for Normal Mode)
+;DarkEnable1 = 0 ;0:Off, 1:On (for Green Mode)
+;ReduceEnable1 = 0 ;0:Off, 1:On (for Green Mode)
+;MotionBEnable1 = 0 ;0:Off, 1:Black Fade, 2:, 3:Frame Blend (for Green Mode)
+;Dark1 = 10 ;0~10 (for Green Mode)
+;ReduceColorR1 = 0 ;0~31 (for Green Mode)
+;ReduceColorG1 = 0 ;0~31 (for Green Mode)
+;ReduceColorB1 = 0 ;0~31 (for Green Mode)
+;MotionBlur1 = 31 ;0~31 (for Green Mode)
+;PaletteX = c31,31,31 ;X:0~15, cR,G,B (0~31)
+
+[Network10]
+Mode = 1
+Address = {HEX @+1 4}
+Fixcode = {PATCH +1}
+
+[Network11]
+Mode = 1
+Address = {HEX @+1 4}
+Fixcode = {PATCH +1}
+
+[send_send_buf2]
+Mode = 2
+Address = {HEX @ 4}
+Type = 29
+
+[send_send_buf2_ret]
+Mode = 2
+Address = {HEX @ 4}
+Type = 30
+
+[Network358]
+Mode = 2
+Address = {HEX @}
+Type = 4
+
+[Network359]
+Mode = 2
+Address = {HEX @}
+Type = 4
+
+[Network364]
+Mode = 2
+Address = {HEX @}
+;fix pokemon ?? in name
+Type = 26
+
+[Network360]
+Mode = 2
+Address = {HEX @}
+Type = 4
+
+[Network361]
+Mode = 2
+Address = {HEX @}
+Type = 4
+
+[Network362]
+Mode = 2
+Address = {HEX @}
+Type = 4
+
+[Network363]
+Mode = 2
+Address = {HEX @}
+Type = 4
+
+[Network_RECHECK]
+Mode = 2
+Address = {HEX @}
+Type = 7
+
+[send_byt2]
+Mode = 2
+Address = {HEX @+5}
+Type = 31
+
+[send_byt2_ret]
+Mode = 2
+Address = {HEX @}
+Type = 32
+
+[send_byt2_wait]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+[send_dummy]
+Mode = 2
+Address = {HEX @}
+Type = 33
+
+[send_dummy_wait]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+[send_dummy_end]
+Mode = 2
+Address = {HEX @}
+Type = 34
+
+[NetworkDelay1]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+[NetworkDelay2]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+[NetworkDelay3]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+[NetworkDelay4]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+[NetworkDelay6]
+Mode = 1
+Address = {HEX @+1}
+Fixcode = {PATCH +1}
+
+;no use[Network_STOP]
+;Mode = 2
+;Address = 0xF4D34
+;Type = 8
+
+;no use[Network_END]
+;Mode = 2
+;Address = 0xF4D3C
+;Type = 9
+
+[Network_RESET]
+Mode = 2
+Address = {HEX @ 5}
+Type = 10
+
+[E_YESNO]
+Mode = 2
+Address = {HEX @}
+Type = 15
+
+[linkCable fake begin]
+Mode = 2
+Address = {HEX @}
+Type = 16
+
+[linkCable fake end]
+Mode = 2
+Address = {HEX @}
+Type = 17
+
+;MURIYARI
+[linkCable block input]
+Mode = 2
+Address = {HEX @}
+Type = 18
+[linkCable block input2]
+Mode = 2
+Address = {HEX @}
+Type = 24
+[save game end]
+Mode = 2
+Address = {HEX @}
+Type = 20
+[term_exit]
+Mode = 2
+Address = {HEX @}
+Type = 25
+[room_check]
+Mode = 2
+Address = {HEX @}
+Type = 27
+[to_play2_mons1]
+Mode = 2
+Address = {HEX @}
+Type = 11
+[to_play2_trade]
+Mode = 2
+Address = {HEX @}
+Type = 12
+[to_play2_battle]
+Mode = 2
+Address = {HEX @}
+Type = 13
+[ret_heya]
+Mode = 2
+Address = {HEX @}
+Type = 14
+
+
+
+
+
+;ROM:3FBCD ld b, $3E ; '>'
+;ROM:3FBCF inc de
+;ROM:3FBD0 call unk_2D55
+;ROM:3FBD3 xor a
+;ROM:3FBD4 ld [byte_FFD2], a
+;ROM:3FBD6 ld [byte_FF4A], a
+;ROM:3FBD8 call unk_31C2
+;ROM:3FBDB call unk_2FE2
+;
+;ROM:3FBCD: 06 3E 13 CD
+
+;0003fbb5h: 06 3E 13 CD ; .>.?
+
+[fight begin]
+Mode = 11
+Type = 0
+Index = 1
+Address = {HEx @}
+Fixcode={db SCREEN_HEIGHT_PX}
+
+;12 1b 0b 79 b0 find next C9
+[BiographySave_ret]
+Mode = 2
+Address = {HEX @}
+Type = 60
+
+
+; print forbid 1
+;ROM:1758D ld a, [byte_FFA9]
+;ROM:1758F and 2
+;ROM:17591 jr nz, unk_75B4
+;ROM:17593 ld a, [byte_FFA9]
+;ROM:17595 and 1 ;e6 01
+;ROM:17597 jr nz, unk_75A1
+;
+; change "and 1" to "and 0"
+;00017595h: E6 01 20 08 CD BF 75 CD 2E 03 18 E9 FA 57 CE F5 ; ? .涂u?..辁W熙
+;00016c76h: E6 01 20 08 CD A0 6C CD 5A 04 18 E9 FA 63 CF F5 ; ? .蜖l蚙..辁c硝
+[print forbid 1]
+Mode = 1
+Address = {hex @}
+Fixcode={patch}
+
+
+
+[print forbid 2]
+Mode = 6
+Type = 0
+Address = {hex @}
+MemAddress={hex hJoyPressed}
+Fixcode={db NO_INPUT}
+ConditionType = 0
+ConditionValueA = {dws_ wWindowStackPointer wWindowStackPointer+1 wMenuJoypad wMenuSelection wMenuSelection wMenuCursorY hJoyPressed hJoyPressed hJoyPressed hJoyPressed}
+ConditionValueB = {dws_ == == == >= <= == != != != != }
+ConditionValueC = {dws_ 0xdd 0xd3 A_BUTTON 0x00 0x0f 0x03 D_DOWN D_UP B_BUTTON NO_INPUT }
+
+; -----ddddfffffff99999ccccc77777----0xd9c7 no ..............Mem Write: pc32 = 0x230b addr = 0xd9c7 value = 0x8
+; 0xd9c7 is the room number.
+
+
+[print forbid 3]
+Mode = 6
+Type = 0
+Address = {hex @}
+MemAddress={hex hJoyPressed}
+Fixcode={db NO_INPUT}
+ConditionType = 0
+ConditionValueA = {dws_ wWindowStackPointer wWindowStackPointer+1 wMenuJoypad wMenuSelection wMenuCursorY wMapGroup wMapNumber wYCoord wXCoord hJoyPressed hJoyPressed hJoyPressed hJoyPressed}
+ConditionValueB = {dws_ == == == == == == == == == != != != != }
+ConditionValueC = {dws_ 0xaf 0xdf NO_INPUT 0x00 0x01 MAPGROUP_CIANWOOD MAP_CIANWOOD_PHOTO_STUDIO 0x04 0x02 D_DOWN D_UP B_BUTTON NO_INPUT }
+
+
+;ROM:BB29C call unk_934
+;ROM:BB29F ld a, [byte_FFA9]
+;ROM:BB2A1 and $B
+;ROM:BB2A3 jr z, unk_B29C
+;ROM:BB2A5 and 8
+;ROM:BB2A7 jr nz, unk_B2AA
+;ROM:BB2A9 ret
+; 000bb2a5h: E6 08 20 01
+; 000b92a3h: E6 08 20 01 ; ? .
+; change "and 8" to "and 0"
+[print forbid 4]
+Mode = 1
+Address = {hex @}
+Fixcode={patch}
+
+
+;ROM:401D6 call unk_50A5
+;ROM:401D9 ld hl, $FFA9
+;ROM:401DC ld a, [hl]
+;ROM:401DD and 2
+;ROM:401DF jr nz, unk_1F8
+;ROM:401E1 ld a, [hl]
+;ROM:401E2 and 1
+;ROM:401E4 jr nz, unk_1EE
+;ROM:401E6 call unk_4562
+;ROM:401E9 ret nc
+;ROM:401EA call unk_4114
+;ROM:401ED ret
+; -----6666666666ddddddddd88888----0xc6d8 no ..............Mem Write: pc32 = 0x4109b addr = 0xc6d8 value = 0x0
+
+;00040266h: 7E E6 01 20 08 ; ~? .
+[print forbid 5]
+Mode = 6
+Type = 0
+Address = {hex @}
+MemAddress={hex hJoyPressed}
+Fixcode={db NO_INPUT}
+ConditionType = 0
+ConditionValueA = {dws_ wWindowStackPointer wWindowStackPointer+1 wMenuJoypad wMenuSelection wDexArrowCursorPosIndex hJoyPressed hJoyPressed hJoyPressed hJoyPressed}
+ConditionValueB = {dws_ == == == == == != != != != }
+ConditionValueC = {dws_ 0xa1 0xdb A_BUTTON 0x00 0x03 D_DOWN D_UP B_BUTTON NO_INPUT }
+
+
+
+
+;0x29e97
+; call ir_main
+; ld d,a ; IR_STAT
+; xor a
+
+
+; _IRcomm_end 0x2a1b9
+; ld hl,ir_read_buf | 21 50 c7
+; ld de,ir_read_buf_stk | 11 00 c8
+; ld bc,15 |
+; call block_move |
+; |
+;00104bf8h: FE 03 30 24
+;00104bf0h: FE 03 30 24 3E 41 21 0B 51 CF CD FB 50 ; ?0$>A!.Q贤鸓
+;00104bf0h: FE 03 30 24 3E 41 21 0B 51 CF CD FB 50 ; ?0$>A!.Q贤鸓
+;the code below is Set_send_data2
+; 3E 41 21 0A 51 CF CD FA 50
+; 3E 41 21 0B 51 CF CD FB 50
+; ------->
+; BCALL G_BANK0b,set_send_data2
+; call read_buf_clr
+;
+
+;001048dbh: 3E 02 EA 01 CA 3E 14 EA 02 CA F0 FF F5 CD 94 4A
+;001048dbh: 3E 02 EA 01 CA 3E 14 EA 02 CA F0 FF F5 CD 9D 4A
+[infrared fake 0]
+Mode = 1
+Address = {hex @}
+Fixcode={PATCH}
+
+
+;00104c3ch: CD 66 4D CD 9E 4D CD E5 4D F0 BC FE 10 CA 24 4D
+;00104a95h: F3 3E 3A 21 E9 4F CF CD 5E 4D CD 96 4D CD DD 4D ; ?:!镺贤^M蜄M洼M
+;00104a9ch: CD 5E 4D CD 96 4D CD DD 4D F0 BC FE 10 ; 蚟M蜄M洼M鸺?
+
+[infrared fake 1]
+Mode = 1
+Address = {hex @}
+Fixcode={patch}
+
+[infrared fake 2]
+Mode = 2
+Address = {hex @}
+Type = 101
+
+[infrared fake 3]
+Mode = 2
+Address = {hex @}
+Type = 102
+
+[infrared fake 4]
+Mode = 2
+Address = {hex @}
+Type = 103
+
+
+;/////////////
+;////fpa////////
+;/////////rangel zhang ///////////
+
+;PC:51-4118=20 000CC118 LY:012 AF:00A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFC1
+;PC:51-411A=FA 000CC11A LY:012 AF:00A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFC1
+;PC:51-411D=CB 000CC11D LY:012 AF:61A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFC1
+;PC:51-411F=20 000CC11F LY:012 AF:61A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFC1
+;PC:51-4121=CD 000CC121 LY:012 AF:61A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFC1
+;PC:51-417A=CD 000CC17A LY:012 AF:61A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFBF
+;PC:51-41CA=3E 000CC1CA LY:012 AF:61A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFBD
+;PC:51-41CC=EA 000CC1CC LY:012 AF:01A0 BC:E400 DE:E4E4 HL:FFA0 SP:DFBD
+
+;ROM:CC118 jr nz, unk_C14D
+;ROM:CC11A ld a, [byte_D1AB]
+;ROM:CC11D bit 7, a
+;ROM:CC11F jr nz, unk_C138
+;ROM:CC121 call unk_417A
+;ROM:CC124 call unk_415A
+;ROM:CC127 call unk_47F7
+
+;000cc13eh: 6F 26 00 11 ; o&..
+;000cc156h: 6F 26 00 11 ; o&..
+;000cc137h: 38 17 CD 92 41 CD 72 41 CD 95 48 CD D3 41 AF E0 ; 8.蛼A蛂A蜁H陀A
+;000cc128h: 38 17 CD A1 41 CD 63 41 CD A4 48 CD E2 41 AF E0 ; 8.汀A蚦A亭H外A
+
+;the 7th bit of the [byte_D1AB],decide whether the animation should be played.
+;if it's zero , the game code will play fighting animation . otherwise, game code
+; will jump to unk_C138 and avoiding playing animation.
+; so we can begin out FPA patch right at address 0xcc121 .
+
+
+;DarkEnable0 = 0 ;0:Off, 1:On (for Normal Mode)
+;ReduceEnable0 = 0 ;0:Off, 1:On (for Normal Mode)
+;MotionBEnable0 = 0 ;0:Off, 1:Black Fade, 2:, 3:Frame Blend (for Normal Mode)
+;Dark0 = 10 ;0~10 (for Normal Mode)
+;012532
+;
+[FPA 001 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 4
+MotionBEnable0 = 3
+MotionBlur0 = 11
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID}
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ FISSURE }
+
+;ROM:35D09 ld [byte_CFB6], a
+;ROM:35D0C ld a, d
+;ROM:35D0D ld [byte_CFB7], a
+;ROM:35D10 ld c, 3
+;ROM:35D12 call unk_468
+;ROM:35D15 ld hl, $40E5
+;ROM:35D18 ld a, $33 ; '3'
+;ROM:35D1A rst 8
+;ROM:35D1B ret
+; EA B6 CF 7A EA B7 CF 0E 03 CD 68 04 21 E5 40 3E
+;00035d09h: EA C2 CF 7A EA C3 CF 0E 03 CD 68 04 21 D6 40 3E
+;00035d09h: EA C2 CF 7A EA C3 CF 0E 03 CD 68 04 21 D8 40 3E
+
+;******dc7d--------------- Mem Write: pc32 = 0x30a7 addr = 0xd066 value = 0x2c
+;******dc7d--------------- Mem Write: pc32 = 0x30a7 addr = 0xd067 value = 0x3a
+;******dc7d--------------- Mem Write: pc32 = 0x30a7 addr = 0xd068 value = 0xb8
+;******dc7d--------------- Mem Write: pc32 = 0x30a7 addr = 0xd069 value = 0x50
+; ------------ Mem Write: pc32 = 0x35d09 addr = 0xcfb6 value = 0x78
+;s e l d e s c
+;
+
+[FPA 002 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 4
+MotionBEnable0 = 3
+MotionBlur0 = 11
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID }
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ SELFDESTRUCT}
+
+
+; lightening
+; -------------- Mem Write: pc32 = 0x35d09 addr = 0xcfb6 value = 0x57
+[FPA 003 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 4
+MotionBEnable0 = 3
+MotionBlur0 = 15
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID}
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ THUNDER }
+
+
+
+
+;ji wa lei 011800
+
+[FPA 004 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 4
+MotionBEnable0 = 3
+MotionBlur0 = 15
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID}
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ FLASH }
+
+
+;skill name 1 : ..............Mem Write: pc32 = 0x30db addr = 0xcf87 value = 0x2c
+;skill name 2 : ..............Mem Write: pc32 = 0x30db addr = 0xcf88 value = 0x3a
+;skill name 3 : ..............Mem Write: pc32 = 0x30db addr = 0xcf89 value = 0xb8
+;skill name 4 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8a value = 0x50
+;skill name 5 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8b value = 0x8f
+;skill name 6 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8c value = 0x9d
+; include 2 pieces of animationl.
+;ji ba lu 011607
+
+[FPA 005 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 4
+MotionBEnable0 = 3
+MotionBlur0 = 15
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID}
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ EXPLOSION}
+
+
+;skill name 1 : ..............Mem Write: pc32 = 0x30db addr = 0xcf87 value = 0x30
+;skill name 2 : ..............Mem Write: pc32 = 0x30db addr = 0xcf88 value = 0xb2
+;skill name 3 : ..............Mem Write: pc32 = 0x30db addr = 0xcf89 value = 0x3a
+;skill name 4 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8a value = 0xb8
+;skill name 5 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8b value = 0xca
+;skill name 6 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8c value = 0xc2
+; da yi ba ha ku ci 011441
+
+[FPA 006 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 4
+MotionBEnable0 = 3
+MotionBlur0 = 11
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID }
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ HORN_DRILL}
+
+
+
+;skill name 1 : ..............Mem Write: pc32 = 0x30db addr = 0xcf87 value = 0x9b
+;skill name 2 : ..............Mem Write: pc32 = 0x30db addr = 0xcf88 value = 0xa5
+;skill name 3 : ..............Mem Write: pc32 = 0x30db addr = 0xcf89 value = 0xac
+;skill name 4 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8a value = 0x8b
+;skill name 5 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8b value = 0xae
+;skill name 6 : ..............Mem Write: pc32 = 0x30db addr = 0xcf8c value = 0x50
+; 011251
+;
+[FPA 007 Begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 5
+MotionBEnable0 = 3
+MotionBlur0 = 7
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID }
+ConditionValueB = {dws_ == }
+ConditionValueC = {dws_ HYPER_BEAM}
+
+
+
+
+;-----111111111111111144444444444444----0xc902 no ..............Mem Write: pc32 = 0xcc46a addr = 0xc902 value = 0xd
+
+;000cc473h: FE 4F D0 cd ; 﨩?
+;000cc495h: FE 4F D0 CD ; 﨩型
+;000cc497h: FE 4F D0 CD ; 﨩型
+; -------------0xd4170xd4170xd4170xd417--------------- Mem Write: pc32 = 0x3a89 addr = 0xd417 value = 0xd1
+;000cc486h: FE 4F D0 CD 7D 3A 22 FA 19 D4 22 C5 E5 6F 26 00 ; 﨩型}:"??佩o&.
+[FPA 042801 Begin]
+Mode = 3
+Type = 0
+Address = {HEX @}
+DarkEnable0 = 1
+Dark0 = 5
+MotionBEnable0 = 3
+MotionBlur0 = 11
+ConditionType = 0
+ConditionValueA = {dws_ wFXAnimID wBattleAnimByte }
+ConditionValueB = {dws_ == == }
+ConditionValueC = {dws_ PRESENT anim_1gfx_command}
+
+
+
+
+;ROM:CC139 call unk_4192
+;ROM:CC13C call unk_4172
+;ROM:CC13F call unk_4895
+;ROM:CC142 call unk_41D3
+;ROM:CC145 xor a
+
+;ROM:CC154 jr z, unk_C16E
+;ROM:CC156 ld l, a
+;ROM:CC157 ld h, 0
+;ROM:CC159 ld de, $10E
+;ROM:CC15C add hl, de
+;ROM:CC15D ld a, l
+; CC156 6F 26 00 11 0E 01
+
+; 000cc147h: 6F 26 00 11 0E 01 ; o&....
+
+;exit point
+
+[FPA 001 End]
+Mode = 3
+Type = 1
+Address = {hex @}
+
+
+;-----ddddff0xff690xff69fffff----0xffa0 no ....-------------..........Mem Write: pc32 = 0x8c352 addr = 0xffa0 value = 0x1
+;-----ddddff0xff690xff69fffff----0xce57 no ....----5555555577777---------..........Mem Write: pc32 = 0x8c483 addr = 0xce57 value = 0x1a
+;0008c352h: 36 01 FA 57 CE CB 7F 20 08 ; 6.鶺嗡 .
+;0008c229h: 36 01 FA 57 CF CB 7F 20 08 CD 14 43 CD 5A 04 18 ; 6.鶺纤 .?C蚙..
+[FPA link fight begin]
+Mode = 3
+Type = 0
+Address = {hex @}
+DarkEnable0 = 1
+Dark0 = 5
+MotionBEnable0 = 3
+MotionBlur0 = 11
+
+;-----ddddff0xff690xff69fffff----0xffa0 no ....-------------..........Mem Write: pc32 = 0x8c382 addr = 0xffa0 value = 0x0
+;0008c382h: E0 A0 CD 2E 03 C9 ; 酄?.?
+;******ccccccccccceeeeeeeeeee55555555577777777--------------- Mem Write: pc32 = 0x8c483 addr = 0xce57 value = 0x15
+;******ccccccccccceeeeeeeeeee55555555577777777--------------- Mem Write: pc32 = 0x8c483 addr = 0xce57 value = 0x16
+;******ccccccccccceeeeeeeeeee55555555577777777--------------- Mem Write: pc32 = 0x8c483 addr = 0xce57 value = 0x17
+
+;40 90 e4 01 3E at 3E
+;0008c3e4h: 40 90 E4 01 3E at 3E ; @愪.>
+[FPA link fight End0]
+Mode = 3
+Type = 1
+Address = {HEx @}
+
+;3D 20 EF C9 3E 01 at 3E
+; 0008c439h: 3D 20 EF C9 3E at 3e ; = 锷>
+[FPA link fight End1]
+Mode = 3
+Type = 1
+Address = {HEx @}
+
+;01 FF 3E 01 at 3E
+;0008c576h: 01 FF 3E 01 ; .>.
+[FPA link fight End2]
+Mode = 3
+Type = 1
+Address = {HEx @}
+
+;32 00 19 00 3e 01 at 3e
+;0008c764h: 32 00 19 00 3E 01 ; 2...>.
+[FPA link fight End3]
+Mode = 3
+Type = 1
+Address = {HEx @}
+
+;ROM:8C25A ld [byte_FFC6], a
+;ROM:8C25C ld [byte_FFC7], a
+;ROM:8C25E ld [byte_FFC8], a
+;ROM:8C260 ld [byte_FFD0], a
+;ROM:8C262 ld a, 1
+;ROM:8C264 ld [byte_FF70], a
+;ROM:8C266 pop af
+;ROM:8C267 ld [byte_FF9E], a --------------------at here .
+;ROM:8C269 call unk_45A
+;ROM:8C26C ret
+
+
+;ROM:8C298 xor a searching code : AF 22 22 77 CD
+;ROM:8C299 ldi [hl], a
+;ROM:8C29A ldi [hl], a
+;ROM:8C29B ld [hl], a
+;ROM:8C29C call unk_46D8
+;ROM:8C29F ret
+;0008c298h: AF 22 22 77 CD ; ?"w?
+
+
+[FPA link fight End4]
+Mode = 3
+Type = 1
+Address = {hex @}
\ No newline at end of file