shithub: pokecrystal

Download patch

ref: 31c3c94d64e1ac1e40c95acfda7de8b99b4f302b
parent: 775b5d046c7f42a3bd8034b92da92e25969bdbf7
author: vulcandth <vulcandth@gmail.com>
date: Sat Mar 12 12:34:04 EST 2022

Build the Virtual Console patch with `make crystal11_vc` (#882)

Fixes #813

diff: cannot open b/vc//null: file does not exist: 'b/vc//null'
--- 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