summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL.md4
-rw-r--r--README21
-rw-r--r--README.md65
-rw-r--r--constants.asm25
-rw-r--r--extras/crystal.py17
-rw-r--r--extras/gfx.py34
-rw-r--r--extras/pksv.py9
-rw-r--r--main.asm1410
-rw-r--r--preprocessor.py115
-rw-r--r--wram.asm4
10 files changed, 1380 insertions, 324 deletions
diff --git a/INSTALL.md b/INSTALL.md
index f41cbf42e..9f45ce52a 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -21,7 +21,7 @@ which rgbasm
git clone https://github.com/kanzure/pokecrystal.git
cd pokecrystal
-make clean; make
+make clean && make
```
# Windows
@@ -158,7 +158,7 @@ to.
To build again, you should use the following command:
```bash
-make clean; make
+make clean && make
```
Feel free to ask us on nucleus.kafuka.org #skeetendo if something goes wrong
diff --git a/README b/README
deleted file mode 100644
index d95036dce..000000000
--- a/README
+++ /dev/null
@@ -1,21 +0,0 @@
-This is a disassembly of Pokémon Crystal.
-
-It uses the following ROM as a base:
- Pokemon - Crystal Version (UE) (V1.0) [C][!].gbc
- md5: 9f2922b235a5eeb78d65594e82ef5dde
-
-To assemble, first install RGBDS and put it in your path.
-
-The version of RGBDS needed is rgbds-linux:
- https://github.com/bentley/rgbds/
- git://github.com/bentley/rgbds.git
-
-Then copy the Pokémon ROM to this directory as "baserom.gbc".
-Then run "make" in your shell.
-
-This will output a file named "pokecrystal.gbc".
-
-See also the disassembly of Pokémon Red:
- http://bitbucket.org/iimarckus/pokered
-
-nucleus.kafuka.org #skeetendo
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..04196c452
--- /dev/null
+++ b/README.md
@@ -0,0 +1,65 @@
+# Pokémon Crystal
+
+This is a hand-crafted disassembly of Pokémon Crystal.
+
+The source code in this project successfully converts back into a ROM image. All source code is meticulously commented.
+
+## Base ROM
+
+The following ROM is required for compiling:
+
+Pokemon - Crystal Version (UE) (V1.0) [C][!].gbc
+
+md5: 9f2922b235a5eeb78d65594e82ef5dde
+
+Eventually this will not be necessary.
+
+## Installing
+
+Simple.
+
+``` bash
+sudo apt-get install make gcc bison git python python-setuptools
+
+# unittest2 is required if using python2.6
+sudo easy_install unittest2
+
+# download rgbds source code
+git clone git://github.com/bentley/rgbds.git
+
+# compile rgbds
+cd rgbds
+make
+sudo make install
+
+# check if rgbasm is installed now
+which rgbasm
+
+# download pokecrystal
+git clone https://github.com/kanzure/pokecrystal.git
+cd pokecrystal
+
+make clean && make
+```
+
+Also, there are [Windows installation instructions](https://github.com/kanzure/pokecrystal/blob/master/INSTALL.md).
+
+## Assembling
+
+* To assemble, first install RGBDS and put it in your path. The version of RGBDS needed is [rgbds-linux](https://github.com/bentley/rgbds/).
+
+* Next, copy the Pokémon ROM to this directory as "baserom.gbc".
+
+* Then run "make" in your shell.
+
+* This will output a file named "pokecrystal.gbc".
+
+## See also
+
+* disassembly of [Pokémon Red](http://bitbucket.org/iimarckus/pokered).
+
+## Contributing
+
+* Hang out with us on IRC, nucleus.kafuka.org #skeetendo ([or use mibbit](http://chat.mibbit.com/?server=nucleus.kafuka.org&channel=#skeetendo))
+
+* Tackle some [issues](https://github.com/kanzure/pokecrystal/issues)!
diff --git a/constants.asm b/constants.asm
index ed3ca4f93..7d73f4232 100644
--- a/constants.asm
+++ b/constants.asm
@@ -36,6 +36,10 @@ TX_FAR: MACRO
db BANK(\1)
ENDM
+RGB: MACRO
+ dw ((\3 << 10) | (\2 << 5) | (\1))
+ ENDM
+
; eventually replace with python macro
note: MACRO
db \1
@@ -1604,6 +1608,12 @@ THURSDAY EQU $04
FRIDAY EQU $05
SATURDAY EQU $06
+; times of day
+MORN EQU 0
+DAY EQU 1
+NITE EQU 2
+DARKNESS EQU 3
+
; trainer groups
FALKNER EQU $01
WHITNEY EQU $02
@@ -3252,17 +3262,15 @@ SPECIAL_DRATINI EQU $0094
SPECIAL_BEASTSCHECK EQU $0096
SPECIAL_MONCHECK EQU $0097
-; battle scripts
-BATTLE_FILLPP EQU $05
-BATTLE_FILLSTATS EQU $0C
+; predefs
+PREDEF_FILLPP EQU $05
+PREDEF_FILLSTATS EQU $0C
+PREDEF_FILLMOVES EQU $1B
+PREDEF_GETUNOWNLETTER EQU $2D
-BATTLE_FILLMOVES EQU $1B
-BATTLE_GETUNOWNLETTER EQU $2D
-
-
-; vars
+; script vars
NUM_VARS EQU $1b
VAR_MOVEMENT EQU $08
@@ -3288,6 +3296,7 @@ MOVE_ACC EQU 4
MOVE_PP EQU 5
MOVE_CHANCE EQU 6
+
; stat constants
NUM_STATS EQU 6
STAT_HP EQU 1
diff --git a/extras/crystal.py b/extras/crystal.py
index ee7b94ae8..8a2b337f6 100644
--- a/extras/crystal.py
+++ b/extras/crystal.py
@@ -1549,12 +1549,12 @@ class ScriptPointerLabelBeforeBank(PointerLabelBeforeBank): pass
class ScriptPointerLabelAfterBank(PointerLabelAfterBank): pass
-def _parse_script_pointer_bytes(self):
+def _parse_script_pointer_bytes(self, debug = False):
PointerLabelParam.parse(self)
- print "_parse_script_pointer_bytes - calculating the pointer located at " + hex(self.address)
+ if debug: print "_parse_script_pointer_bytes - calculating the pointer located at " + hex(self.address)
address = calculate_pointer_from_bytes_at(self.address, bank=self.bank)
if address != None and address > 0x4000:
- print "_parse_script_pointer_bytes - the pointer is: " + hex(address)
+ if debug: print "_parse_script_pointer_bytes - the pointer is: " + hex(address)
self.script = parse_script_engine_script_at(address, debug=self.debug, force=self.force, map_group=self.map_group, map_id=self.map_id)
ScriptPointerLabelParam.parse = _parse_script_pointer_bytes
ScriptPointerLabelBeforeBank.parse = _parse_script_pointer_bytes
@@ -2817,6 +2817,7 @@ pksv_crystal_more = {
0x4F: ["loadmenudata", ["data", MenuDataPointerParam]],
0x50: ["writebackup"],
0x51: ["jumptextfaceplayer", ["text_pointer", RawTextPointerLabelParam]],
+ 0x52: ["3jumptext", ["text_pointer", PointerLabelBeforeBank]],
0x53: ["jumptext", ["text_pointer", RawTextPointerLabelParam]],
0x54: ["closetext"],
0x55: ["keeptextopen"],
@@ -3160,12 +3161,12 @@ class Script:
"""
global command_classes, rom, script_parse_table
current_address = start_address
- print "Script.parse address="+hex(self.address) +" map_group="+str(map_group)+" map_id="+str(map_id)
+ if debug: print "Script.parse address="+hex(self.address) +" map_group="+str(map_group)+" map_id="+str(map_id)
if start_address in stop_points and force == False:
- print "script parsing is stopping at stop_point=" + hex(start_address) + " at map_group="+str(map_group)+" map_id="+str(map_id)
+ if debug: print "script parsing is stopping at stop_point=" + hex(start_address) + " at map_group="+str(map_group)+" map_id="+str(map_id)
return None
if start_address < 0x4000 and start_address not in [0x26ef, 0x114, 0x1108]:
- print "address is less than 0x4000.. address is: " + hex(start_address)
+ if debug: print "address is less than 0x4000.. address is: " + hex(start_address)
sys.exit(1)
if is_script_already_parsed_at(start_address) and not force and not force_top:
raise Exception, "this script has already been parsed before, please use that instance ("+hex(start_address)+")"
@@ -3198,7 +3199,7 @@ class Script:
# no matching command found (not implemented yet)- just end this script
# NOTE: might be better to raise an exception and end the program?
if scripting_command_class == None:
- print "parsing script; current_address is: " + hex(current_address)
+ if debug: print "parsing script; current_address is: " + hex(current_address)
current_address += 1
asm_output = "\n".join([command.to_asm() for command in commands])
end = True
@@ -3231,7 +3232,7 @@ class Script:
script_parse_table[start_address:current_address] = self
asm_output = "\n".join([command.to_asm() for command in commands])
- print "--------------\n"+asm_output
+ if debug: print "--------------\n"+asm_output
# store the script
self.commands = commands
diff --git a/extras/gfx.py b/extras/gfx.py
index f36b944d7..70c657c15 100644
--- a/extras/gfx.py
+++ b/extras/gfx.py
@@ -1283,6 +1283,28 @@ def get_uncompressed_gfx(start, num_tiles, filename):
+def hex_to_rgb(word):
+ red = word & 0b11111
+ word >>= 5
+ green = word & 0b11111
+ word >>= 5
+ blue = word & 0b11111
+ return (red, green, blue)
+
+def grab_palettes(address, length = 0x80):
+ output = ''
+ for word in range(length/2):
+ color = ord(rom[address+1])*0x100 + ord(rom[address])
+ address += 2
+ color = hex_to_rgb(color)
+ red = str(color[0]).zfill(2)
+ green = str(color[1]).zfill(2)
+ blue = str(color[2]).zfill(2)
+ output += '\tRGB '+red+', '+green+', '+blue
+ output += '\n'
+ return output
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('cmd', nargs='?', metavar='cmd', type=str)
@@ -1317,7 +1339,11 @@ if __name__ == "__main__":
# python gfx.py un [address] [num_tiles] [filename]
get_uncompressed_gfx(int(args.arg1,16), int(args.arg2), args.arg3)
- else:
- # python gfx.py
- decompress_all()
- if debug: print 'decompressed known gfx to ../gfx/!'
+ elif args.cmd == 'pal':
+ # python gfx.py pal [address] [length]
+ print grab_palettes(int(args.arg1,16), int(args.arg2))
+
+ #else:
+ ## python gfx.py
+ #decompress_all()
+ #if debug: print 'decompressed known gfx to ../gfx/!'
diff --git a/extras/pksv.py b/extras/pksv.py
index 8f4bafeeb..a73e16db7 100644
--- a/extras/pksv.py
+++ b/extras/pksv.py
@@ -34,7 +34,7 @@ pksv_gs = {
0x21: "checkitem",
0x22: "givemoney",
0x23: "takemoney",
- 0x24: "checkmonkey",
+ 0x24: "checkmoney",
0x25: "givecoins",
0x26: "takecoins",
0x27: "checkcoins",
@@ -179,7 +179,7 @@ pksv_crystal = {
0x21: "checkitem",
0x22: "givemoney",
0x23: "takemoney",
- 0x24: "checkmonkey",
+ 0x24: "checkmoney",
0x25: "givecoins",
0x26: "takecoins",
0x27: "checkcoins",
@@ -292,8 +292,9 @@ pksv_crystal = {
}
#these cause the script to end; used in create_command_classes
-pksv_crystal_more_enders = [0x03, 0x04, 0x05, 0x0C, 0x51, 0x53,
- 0x8D, 0x8F, 0x90, 0x91, 0x92, 0x9B,
+pksv_crystal_more_enders = [0x03, 0x04, 0x05, 0x0C, 0x51, 0x52,
+ 0x53, 0x8D, 0x8F, 0x90, 0x91, 0x92,
+ 0x9B,
0xB2, #maybe?
0xCC, #maybe?
]
diff --git a/main.asm b/main.asm
index 038d0564b..d29d86ccc 100644
--- a/main.asm
+++ b/main.asm
@@ -140,6 +140,9 @@ IncGradGBPalTable_01: ; 52f
INCBIN "baserom.gbc",$547,$568 - $547
DisableLCD: ; 568
+; Turn the LCD off
+; Most of this is just going through the motions
+
; don't need to do anything if lcd is already off
ld a, [$ff40] ; LCDC
bit 7, a ; lcd enable
@@ -782,7 +785,23 @@ FarCopyBytesDouble: ; e9b
; 0xeba
-INCBIN "baserom.gbc",$eba,$ff1 - $eba
+INCBIN "baserom.gbc",$eba,$fc8 - $eba
+
+ClearTileMap: ; fc8
+; Fill the tile map with blank tiles
+ ld hl, TileMap
+ ld a, $7f ; blank tile
+ ld bc, 360 ; length of TileMap
+ call ByteFill
+
+; We aren't done if the LCD is on
+ ld a, [$ff40] ; LCDC
+ bit 7, a
+ ret z
+ jp WaitBGMap
+; fdb
+
+INCBIN "baserom.gbc",$fdb,$ff1 - $fdb
TextBoxBorder: ; ff1
; draw a text box
@@ -1364,7 +1383,59 @@ BitTableFunc: ; 0x2e76
ret
; 0x2ead
-INCBIN "baserom.gbc",$2ead,$2fb1-$2ead
+INCBIN "baserom.gbc", $2ead, $2f8c - $2ead
+
+RNG: ; 2f8c
+; Two random numbers are generated by adding and subtracting
+; the divider to the respective values every time it's called.
+
+; The divider is a value that increments at a rate of 16384Hz.
+; For comparison, the Game Boy operates at a clock speed of 4.2MHz.
+
+; Additionally, an equivalent function is called every frame.
+
+; output:
+; a: rand2
+; ffe1: rand1
+; ffe2: rand2
+
+ push bc
+; Added value
+ ld a, [$ff04] ; divider
+ ld b, a
+ ld a, [$ffe1]
+ adc b
+ ld [$ffe1], a
+; Subtracted value
+ ld a, [$ff04] ; divider
+ ld b, a
+ ld a, [$ffe2]
+ sbc b
+ ld [$ffe2], a
+ pop bc
+ ret
+; 2f9f
+
+FarBattleRNG: ; 2f9f
+; BattleRNG lives in another bank.
+; It handles all RNG calls in the battle engine,
+; allowing link battles to remain in sync using a shared PRNG.
+
+; Save bank
+ ld a, [$ff9d] ; bank
+ push af
+; Bankswitch
+ ld a, BANK(BattleRNG)
+ rst $10
+ call BattleRNG
+; Restore bank
+ ld [$cfb6], a
+ pop af
+ rst $10
+ ld a, [$cfb6]
+ ret
+; 2fb1
+
Function2fb1: ; 2fb1
push bc
@@ -1423,9 +1494,34 @@ CloseSRAM: ; 2fe1
ld [$0000], a
pop af
ret
-; 2fef
+; 2fec
-INCBIN "baserom.gbc",$2fec,$3026-$2fec
+INCBIN "baserom.gbc",$2fec,$300b-$2fec
+
+ClearSprites: ; 300b
+ ld hl, Sprites
+ ld b, TileMap - Sprites
+ xor a
+.loop
+ ld [hli], a
+ dec b
+ jr nz, .loop
+ ret
+; 3016
+
+HideSprites: ; 3016
+; Set all OBJ y-positions to 160 to hide them offscreen
+ ld hl, Sprites
+ ld de, $0004 ; length of an OBJ struct
+ ld b, $28 ; number of OBJ structs
+ ld a, 160 ; y-position
+.loop
+ ld [hl], a
+ add hl, de
+ dec b
+ jr nz, .loop
+ ret
+; 3026
CopyBytes: ; 0x3026
; copy bc bytes from hl to de
@@ -1727,10 +1823,62 @@ StringCmp: ; 31db
ret
; 0x31e4
-INCBIN "baserom.gbc",$31e4,$3340 - $31e4
+INCBIN "baserom.gbc",$31e4,$31f3 - $31e4
+WhiteBGMap: ; 31f3
+ call ClearPalettes
+WaitBGMap: ; 31f6
+; Tell VBlank to update BG Map
+ ld a, 1 ; BG Map 0 tiles
+ ld [$ffd4], a
+; Wait for it to do its magic
+ ld c, 4
+ call DelayFrames
+ ret
+; 3200
+
+INCBIN "baserom.gbc",$3200,$3317 - $3200
+
+ClearPalettes: ; 3317
+; Make all palettes white
+
+; For CGB we make all the palette colors white
+ ld a, [$ffe6]
+ and a
+ jr nz, .cgb
+
+; In DMG mode, we can just change palettes to 0 (white)
+ xor a
+ ld [$ff47], a ; BGP
+ ld [$ff48], a ; OBP0
+ ld [$ff49], a ; OBP1
+ ret
+
+.cgb
+; Save WRAM bank
+ ld a, [$ff70]
+ push af
+; WRAM bank 5
+ ld a, 5
+ ld [$ff70], a
+; Fill BGPals and OBPals with $ffff (white)
+ ld hl, BGPals
+ ld bc, $0080
+ ld a, $ff
+ call ByteFill
+; Restore WRAM bank
+ pop af
+ ld [$ff70], a
+; Request palette update
+ ld a, 1
+ ld [$ffe5], a
+ ret
+; 333e
+
+ClearSGB: ; 333e
+ ld b, $ff
GetSGBLayout: ; 3340
-; load sgb packets unless gb
+; load sgb packets unless dmg
; check cgb
ld a, [$ffe6]
@@ -1823,7 +1971,7 @@ GetName: ; 33c3
call GetNthString
ld de, $d073
ld bc, $000d
- call $3026
+ call CopyBytes
.asm_3403
ld a, e
ld [$d102], a
@@ -1878,7 +2026,71 @@ GetItemName: ; 3468
ret
; 0x3487
-INCBIN "baserom.gbc",$3487,$38a2 - $3487
+INCBIN "baserom.gbc",$3487,$3856 - $3487
+
+GetBaseStats: ; 3856
+ push bc
+ push de
+ push hl
+
+; Save bank
+ ld a, [$ff9d]
+ push af
+; Bankswitch
+ ld a, BANK(BaseStats)
+ rst $10
+
+; Egg doesn't have base stats
+ ld a, [CurSpecies]
+ cp EGG
+ jr z, .egg
+
+; Get base stats
+ dec a
+ ld bc, BaseStatsStructEnd - BaseStats
+ ld hl, BaseStats
+ call AddNTimes
+ ld de, CurBaseStats
+ ld bc, BaseStatsStructEnd - BaseStats
+ call CopyBytes
+ jr .end
+
+.egg
+; ????
+ ld de, $7d9c
+
+; Sprite dimensions
+ ld b, $55
+ ld hl, $d247
+ ld [hl], b
+
+; ????
+ ld hl, $d248
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ jr .end
+
+.end
+; Replace Pokedex # with species
+ ld a, [CurSpecies]
+ ld [CurBaseStats], a
+
+; Restore bank
+ pop af
+ rst $10
+
+ pop hl
+ pop de
+ pop bc
+ ret
+; 389c
+
+INCBIN "baserom.gbc",$389c,$38a2 - $389c
GetNick: ; 38a2
; get the nickname of a partymon
@@ -1985,21 +2197,25 @@ PrintBCDDigit: ; 38f2
ret
; 0x3917
-Function3917: ; 3917
+GetPartyParamLocation: ; 3917
+; Get the location of parameter a from CurPartyMon in hl
push bc
- ld hl, $dcdf
+ ld hl, PartyMons
ld c, a
ld b, $00
add hl, bc
- ld a, [$d109]
- call Function3927
+ ld a, [CurPartyMon]
+ call GetPartyLocation
pop bc
ret
; 3927
-Function3927: ; 3927
-; a is typically [$d109]
- ld bc, $0030
+GetPartyLocation: ; 3927
+; Add the length of a PartyMon struct to hl a times
+; input:
+; a: partymon #
+; hl: partymon struct
+ ld bc, $0030 ; PARTYMON_LENGTH
jp AddNTimes
; 392d
@@ -2159,7 +2375,31 @@ CheckSFX: ; 3dde
ret
; 3dfe
-INCBIN "baserom.gbc",$3dfe,$4000 - $3dfe
+INCBIN "baserom.gbc",$3dfe,$3e10 - $3dfe
+
+ChannelsOff: ; 3e10
+; Quickly turn off music channels
+ xor a
+ ld [$c104], a
+ ld [$c136], a
+ ld [$c168], a
+ ld [$c19a], a
+ ld [$c29c], a
+ ret
+; 3e21
+
+SFXChannelsOff: ; 3e21
+; Quickly turn off sound effect channels
+ xor a
+ ld [$c1cc], a
+ ld [$c1fe], a
+ ld [$c230], a
+ ld [$c262], a
+ ld [$c29c], a
+ ret
+; 3e32
+
+INCBIN "baserom.gbc",$3e32,$4000 - $3e32
SECTION "bank1",DATA,BANK[$1]
@@ -2254,7 +2494,35 @@ CheckNickErrors: ; 669f
db $ff ; end
; 66de
-INCBIN "baserom.gbc",$66de,$8000 - $66de
+INCBIN "baserom.gbc",$66de,$6eef - $66de
+
+DrawGraphic: ; 6eef
+; input:
+; hl: draw location
+; b: height
+; c: width
+; d: tile to start drawing from
+; e: number of tiles to advance for each row
+ call $7009
+ pop bc
+ pop hl
+ ret c
+ bit 5, [hl]
+ jr nz, .asm_6f05
+ push hl
+ call $70a4
+ pop hl
+ ret c
+ push hl
+ call $70ed
+ pop hl
+ ret c
+.asm_6f05
+ and a
+ ret
+; 6f07
+
+INCBIN "baserom.gbc",$6f07,$8000 - $6f07
SECTION "bank2",DATA,BANK[$2]
@@ -2503,7 +2771,7 @@ SpecialsPointers: ; 0xc029
dbw $01,$7305
dbw $01,$737e
dbw $01,$73f7
- dbw $03,$4419
+ dbw BANK(SpecialCheckPokerus),SpecialCheckPokerus
dbw $09,$4b25
dbw $09,$4b4e
dbw $09,$4ae8
@@ -2595,7 +2863,28 @@ SpecialsPointers: ; 0xc029
dbw $24,$4a88
dbw $03,$4224
-INCBIN "baserom.gbc",$c224,$c43d - $c224
+INCBIN "baserom.gbc",$c224,$c3e2 - $c224
+
+ScriptReturnCarry: ; c3e2
+ jr c, .carry
+ xor a
+ ld [ScriptVar], a
+ ret
+.carry
+ ld a, 1
+ ld [ScriptVar], a
+ ret
+; c3ef
+
+INCBIN "baserom.gbc",$c3ef,$c419 - $c3ef
+
+SpecialCheckPokerus: ; c419
+; Check if a monster in your party has Pokerus
+ callba CheckPokerus
+ jp ScriptReturnCarry
+; c422
+
+INCBIN "baserom.gbc",$c422,$c43d - $c422
SpecialSnorlaxAwake: ; 0xc43d
; Check if the Poké Flute channel is playing, and if the player is standing
@@ -12501,53 +12790,100 @@ INCBIN "baserom.gbc",$3d14e,$3ddc2 - $3d14e
INCBIN "baserom.gbc",$3ddc8,$3e8eb - $3ddc8
-Function3e8eb: ; 3e8eb
-;part of battle init
+LoadEnemyMon: ; 3e8eb
+; Initialize enemy monster parameters
+; To do this we pull the species from TempEnemyMonSpecies
+
+; Notes:
+; FarBattleRNG is used to ensure sync between Game Boys
+
+; Clear the whole EnemyMon struct
xor a
ld hl, EnemyMonSpecies
ld bc, $0027
call ByteFill
+
+; We don't need to be here if we're in a link battle
ld a, [InLinkBattle]
and a
jp nz, $5abd
- ld a, [$cfc0]
+
+ ld a, [$cfc0] ; ????
bit 0, a
jp nz, $5abd
+
+; Make sure everything knows what species we're working with
ld a, [TempEnemyMonSpecies]
ld [EnemyMonSpecies], a
- ld [$cf60], a
- ld [$d108], a
- call $3856
- ld a, [$d22d]
+ ld [CurSpecies], a
+ ld [CurPartySpecies], a
+
+; Grab the base stats for this species
+ call GetBaseStats
+
+
+; Let's get the item:
+
+; Is the item predetermined?
+ ld a, [IsInBattle]
dec a
- jr z, .asm_3e925
- ld a, [$d109]
- ld hl, $d289
- call Function3927
+ jr z, .WildItem
+
+; If we're in a trainer battle, the item is in the party struct
+ ld a, [CurPartyMon]
+ ld hl, OTPartyMon1Item
+ call GetPartyLocation ; bc = PartyMon[CurPartyMon] - PartyMons
ld a, [hl]
- jr .asm_3e945
-.asm_3e925
+ jr .UpdateItem
+
+
+.WildItem
+; In a wild battle, we pull from the item slots in base stats
+
+; Force Item1
+; Used for Ho-Oh, Lugia and Snorlax encounters
ld a, [BattleType]
- cp a, $0a
- ld a, [$d241]
- jr z, .asm_3e945
- call $2f9f
- cp a, $c0
- ld a, $00
- jr c, .asm_3e945
- call $2f9f
- cp a, $14
- ld a, [$d241]
- jr nc, .asm_3e945
- ld a, [$d242]
-.asm_3e945
+ cp BATTLETYPE_FORCEITEM
+ ld a, [$d241] ; BufferMonItem1
+ jr z, .UpdateItem
+
+; Failing that, it's all up to chance
+; Effective chances:
+; 75% None
+; 23% Item1
+; 2% Item2
+
+; 25% chance of getting an item
+ call FarBattleRNG
+ cp a, $c0 ; $c0/$100 = 75%
+ ld a, NO_ITEM
+ jr c, .UpdateItem
+
+; From there, an 8% chance for Item2
+ call FarBattleRNG
+ cp a, $14 ; 8% of 25% = 2% Item2
+ ld a, [$d241] ; BaseStatsItem1
+ jr nc, .UpdateItem
+ ld a, [$d242] ; BaseStatsItem2
+
+
+.UpdateItem
ld [EnemyMonItem], a
- ld a, [$d22d]
+
+
+; Initialize DVs
+
+; If we're in a trainer battle, DVs are predetermined
+ ld a, [IsInBattle]
and a
- jr z, .asm_3e963
+ jr z, .InitDVs
+
+; ????
ld a, [$c671]
bit 3, a
- jr z, .asm_3e963
+ jr z, .InitDVs
+
+; Unknown
ld hl, $c6f2
ld de, EnemyMonDVs
ld a, [hli]
@@ -12555,161 +12891,276 @@ Function3e8eb: ; 3e8eb
inc de
ld a, [hl]
ld [de], a
- jp .asm_3ea1a
-.asm_3e963
- ld a, $09
- ld hl, $70c4
- rst $08
- ld a, [$d22d]
+ jp .Happiness
+
+
+.InitDVs
+
+; Trainer DVs
+
+; All trainers have preset DVs, determined by class
+; See GetTrainerDVs for more on that
+ callba GetTrainerDVs
+; These are the DVs we'll use if we're actually in a trainer battle
+ ld a, [IsInBattle]
dec a
- jr nz, .asm_3e9a8
+ jr nz, .UpdateDVs
+
+
+; Wild DVs
+; Here's where the fun starts
+
+; Roaming monsters (Entei, Raikou) work differently
+; They have their own structs, which are shorter than normal
ld a, [BattleType]
- cp a, $05
- jr nz, .asm_3e996
- call $7a01
+ cp a, BATTLETYPE_ROAMING
+ jr nz, .NotRoaming
+
+; Grab HP
+ call GetRoamMonHP
ld a, [hl]
+; Check if the HP has been initialized
and a
+; We'll do something with the result in a minute
push af
- call $7a19
+
+; Grab DVs
+ call GetRoamMonDVs
inc hl
ld a, [hld]
ld c, a
ld b, [hl]
+
+; Get back the result of our check
pop af
- jr nz, .asm_3e9a8
- call $7a19
+; If the RoamMon struct has already been initialized, we're done
+ jr nz, .UpdateDVs
+
+; If it hasn't, we need to initialize the DVs
+; (HP is initialized at the end of the battle)
+ call GetRoamMonDVs
inc hl
- call $2f9f
+ call FarBattleRNG
ld [hld], a
ld c, a
- call $2f9f
+ call FarBattleRNG
ld [hl], a
ld b, a
- jr .asm_3e9a8
-.asm_3e996
- cp a, $07
- jr nz, .asm_3e9a0
- ld b, $ea
- ld c, $aa
- jr .asm_3e9a8
-.asm_3e9a0
- call $2f9f
+; We're done with DVs
+ jr .UpdateDVs
+
+
+.NotRoaming
+; Register a contains BattleType
+
+; Forced shiny battle type
+; Used by Red Gyarados at Lake of Rage
+ cp a, BATTLETYPE_SHINY
+ jr nz, .GenerateDVs
+
+ ld b, ATKDEFDV_SHINY ; $ea
+ ld c, SPDSPCDV_SHINY ; $aa
+ jr .UpdateDVs
+
+.GenerateDVs
+; Generate new random DVs
+ call FarBattleRNG
ld b, a
- call $2f9f
+ call FarBattleRNG
ld c, a
-.asm_3e9a8
+
+.UpdateDVs
+; Input DVs in register bc
ld hl, EnemyMonDVs
ld a, b
ld [hli], a
ld [hl], c
- ld a, [$d22d]
+
+
+; We've still got more to do if we're dealing with a wild monster
+ ld a, [IsInBattle]
dec a
- jr nz, .asm_3ea1a
+ jr nz, .Happiness
+
+
+; Species-specfic:
+
+
+; Unown
ld a, [TempEnemyMonSpecies]
cp a, UNOWN
- jr nz, .notunown
+ jr nz, .Magikarp
+
+; Get letter based on DVs
ld hl, EnemyMonDVs
- ld a, $2d
- call $2d83
+ ld a, PREDEF_GETUNOWNLETTER
+ call Predef
+; Can't use any letters that haven't been unlocked
+; If combined with forced shiny battletype, causes an infinite loop
call CheckUnownLetter
- jr c, .asm_3e9a0
-.notunown
+ jr c, .GenerateDVs ; try again
+
+
+.Magikarp
+; Skimming this part recommended
+
ld a, [TempEnemyMonSpecies]
cp a, MAGIKARP
- jr nz, .asm_3ea1a
+ jr nz, .Happiness
+
+; Get Magikarp's length
ld de, EnemyMonDVs
ld bc, PlayerID
- ld hl, CalcMagikarpLength
- ld a, BANK(CalcMagikarpLength)
- rst $08
- ld a, [$d1ea] ; Magikarp's length
- cp a, $06
- jr nz, .asm_3e9fe
- call $2f8c
- cp a, $0c
- jr c, .asm_3e9fe
- ld a, [$d1eb]
+ callab CalcMagikarpLength
+
+; We're clear if the length is < 1536
+ ld a, [MagikarpLengthHi]
+ cp a, $06 ; $600 = 1536
+ jr nz, .CheckMagikarpArea
+
+; 5% chance of skipping size checks
+ call RNG
+ cp a, $0c ; / $100
+ jr c, .CheckMagikarpArea
+; Try again if > 1614
+ ld a, [MagikarpLengthLo]
cp a, $50
- jr nc, .asm_3e9a0
- call $2f8c
- cp a, $32
- jr c, .asm_3e9fe
- ld a, [$d1eb]
+ jr nc, .GenerateDVs
+
+; 20% chance of skipping this check
+ call RNG
+ cp a, $32 ; / $100
+ jr c, .CheckMagikarpArea
+; Try again if > 1598
+ ld a, [MagikarpLengthLo]
cp a, $40
- jr nc, .asm_3e9a0
-.asm_3e9fe
- ld a, [$dcb5]
- cp a, $09
- jr z, .asm_3ea1a
- ld a, [$dcb6]
- cp a, $06
- jr z, .asm_3ea1a
- call $2f8c
- cp a, $64
- jr c, .asm_3ea1a
- ld a, [$d1ea]
- cp a, $04
- jr c, .asm_3e9a0
-.asm_3ea1a
- ld a, $46
+ jr nc, .GenerateDVs
+
+.CheckMagikarpArea
+; The z checks are supposed to be nz
+; Instead, all maps in GROUP_LAKE_OF_RAGE (mahogany area)
+; and routes 20 and 44 are treated as Lake of Rage
+
+; This also means Lake of Rage Magikarp can be smaller than ones
+; caught elsewhere rather than the other way around
+
+; Intended behavior enforces a minimum size at Lake of Rage
+; The real behavior prevents size flooring in the Lake of Rage area
+ ld a, [MapGroup]
+ cp a, GROUP_LAKE_OF_RAGE
+ jr z, .Happiness
+ ld a, [MapNumber]
+ cp a, MAP_LAKE_OF_RAGE
+ jr z, .Happiness
+; 40% chance of not flooring
+ call RNG
+ cp a, $64 ; / $100
+ jr c, .Happiness
+; Floor at length 1024
+ ld a, [MagikarpLengthHi]
+ cp a, $04 ; $400 = 1024
+ jr c, .GenerateDVs ; try again
+
+
+; Finally done with DVs
+
+.Happiness
+; Set happiness
+ ld a, 70 ; BASE_HAPPINESS
ld [EnemyMonHappiness], a
- ld a, [$d143]
+; Set level
+ ld a, [CurPartyLevel]
ld [EnemyMonLevel], a
+; Fill stats
ld de, EnemyMonMaxHP
ld b, $00
- ld hl, $d201
- ld a, $0c
- call $2d83
- ld a, [$d22d]
- cp a, $02
- jr z, .asm_3ea74
+ ld hl, $d201 ; ?
+ ld a, PREDEF_FILLSTATS
+ call Predef
+
+; If we're in a trainer battle,
+; get the rest of the parameters from the party struct
+ ld a, [IsInBattle]
+ cp a, TRAINER_BATTLE
+ jr z, .OpponentParty
+
+; If we're in a wild battle, check wild-specific stuff
and a
- jr z, .asm_3ea44
+ jr z, .TreeMon
+
+; ????
ld a, [$c671]
bit 3, a
- jp nz, .asm_3ea90
-.asm_3ea44
+ jp nz, .Moves
+
+.TreeMon
+; If we're headbutting trees, some monsters enter battle asleep
call CheckSleepingTreeMon
- ld a, $07
- jr c, .asm_3ea4c
+ ld a, 7 ; Asleep for 7 turns
+ jr c, .UpdateStatus
+; Otherwise, no status
xor a
-.asm_3ea4c
+
+.UpdateStatus
ld hl, EnemyMonStatus
ld [hli], a
+
+; Unused byte
xor a
ld [hli], a
+
+; Full HP...
ld a, [EnemyMonMaxHPHi]
ld [hli], a
ld a, [EnemyMonMaxHPLo]
ld [hl], a
+
+; ...unless it's a RoamMon
ld a, [BattleType]
- cp a, $05
- jr nz, .asm_3ea90
- call $7a01
+ cp a, BATTLETYPE_ROAMING
+ jr nz, .Moves
+
+; Grab HP
+ call GetRoamMonHP
ld a, [hl]
+; Check if it's been initialized again
and a
- jr z, .asm_3ea6e
+ jr z, .InitRoamHP
+; Update from the struct if it has
ld a, [hl]
ld [EnemyMonHPLo], a
- jr .asm_3ea90
-.asm_3ea6e
+ jr .Moves
+
+.InitRoamHP
+; HP only uses the lo byte in the RoamMon struct since
+; Raikou/Entei/Suicune will have < 256 hp at level 40
ld a, [EnemyMonHPLo]
ld [hl], a
- jr .asm_3ea90
-.asm_3ea74
- ld hl, $d2ab
- ld a, [$d109]
- call Function3927
+ jr .Moves
+
+
+.OpponentParty
+; Get HP from the party struct
+ ld hl, (PartyMon1CurHP + 1) - PartyMon1 + OTPartyMon1
+ ld a, [CurPartyMon]
+ call GetPartyLocation
ld a, [hld]
ld [EnemyMonHPLo], a
ld a, [hld]
ld [EnemyMonHPHi], a
- ld a, [$d109]
- ld [$c663], a
+
+; Make sure everything knows which monster the opponent is using
+ ld a, [CurPartyMon]
+ ld [CurOTMon], a
+
+; Get status from the party struct
dec hl
- ld a, [hl]
+ ld a, [hl] ; OTPartyMonStatus
ld [EnemyMonStatus], a
-.asm_3ea90
+
+
+.Moves
+; ????
ld hl, $d23d
ld de, $d224
ld a, [hli]
@@ -12717,17 +13168,23 @@ Function3e8eb: ; 3e8eb
inc de
ld a, [hl]
ld [de], a
+
+; Get moves
ld de, EnemyMonMoves
- ld a, [$d22d]
- cp a, $02
- jr nz, .asm_3eab6
+; Are we in a trainer battle?
+ ld a, [IsInBattle]
+ cp a, TRAINER_BATTLE
+ jr nz, .WildMoves
+; Then copy moves from the party struct
ld hl, OTPartyMon1Moves
- ld a, [$d109]
- call Function3927
- ld bc, $0004
+ ld a, [CurPartyMon]
+ call GetPartyLocation
+ ld bc, NUM_MOVES
call CopyBytes
- jr .asm_3eac5
-.asm_3eab6
+ jr .PP
+
+.WildMoves
+; Clear EnemyMonMoves
xor a
ld h, d
ld l, e
@@ -12735,86 +13192,115 @@ Function3e8eb: ; 3e8eb
ld [hli], a
ld [hli], a
ld [hl], a
+; Make sure the predef knows this isn't a partymon
ld [$d1ea], a
- ld a, $1b
- call $2d83
-.asm_3eac5
- ld a, [$d22d]
- cp a, $02
- jr z, .asm_3ead9
+; Fill moves based on level
+ ld a, PREDEF_FILLMOVES
+ call Predef
+
+.PP
+; Trainer battle?
+ ld a, [IsInBattle]
+ cp a, TRAINER_BATTLE
+ jr z, .TrainerPP
+
+; Fill wild PP
ld hl, EnemyMonMoves
ld de, EnemyMonPP
- ld a, $05
- call $2d83
- jr .asm_3eaeb
-.asm_3ead9
- ld hl, $d29f
- ld a, [$d109]
- call Function3927
+ ld a, PREDEF_FILLPP
+ call Predef
+ jr .Finish
+
+.TrainerPP
+; Copy PP from the party struct
+ ld hl, OTPartyMon1PP
+ ld a, [CurPartyMon]
+ call GetPartyLocation
ld de, EnemyMonPP
- ld bc, $0004
+ ld bc, NUM_MOVES
call CopyBytes
-.asm_3eaeb
+
+.Finish
+; ????
ld hl, $d237
ld de, $d226
- ld b, $05
-.asm_3eaf3
+ ld b, 5 ; # bytes to copy
+; Copy $d237-a to $d226-9
+.loop
ld a, [hli]
ld [de], a
inc de
dec b
- jr nz, .asm_3eaf3
+ jr nz, .loop
+; Copy $d23f to $d22a
ld a, [$d23f]
ld [de], a
inc de
+; Copy $d240 to $d22b
ld a, [$d240]
ld [de], a
+; copy TempEnemyMonSpecies to $d265
ld a, [TempEnemyMonSpecies]
ld [$d265], a
+; ????
call $343b
- ld a, [$d22d]
+; If wild, we're done
+ ld a, [IsInBattle]
and a
ret z
- ld hl, $d073
- ld de, $c616
- ld bc, $000b
+; Update enemy nick
+ ld hl, StringBuffer1
+ ld de, EnemyMonNick
+ ld bc, PKMN_NAME_LENGTH
call CopyBytes
+; ????
ld a, [TempEnemyMonSpecies]
dec a
ld c, a
ld b, $01
ld hl, $deb9
- ld a, $03
- call $2d83
+ ld a, $03 ; PREDEF_
+ call Predef
+; Fill EnemyMon stats
ld hl, EnemyMonAtk
ld de, $c6c1
- ld bc, $000a
+ ld bc, 2*(NUM_STATS-1) ; 2 bytes for each non-HP stat
call CopyBytes
+; We're done
ret
; 3eb38
+
CheckSleepingTreeMon: ; 3eb38
+; Return carry if species is in the list
+; for the current time of day
+
+; Don't do anything if this isn't a tree encounter
ld a, [BattleType]
- cp a, $08 ; headbutt
- jr nz, .notsleeping
- ld hl, SleepingTreeMonMornTable
+ cp a, BATTLETYPE_TREE
+ jr nz, .NotSleeping
+
+; Get list for the time of day
+ ld hl, .Morn
ld a, [TimeOfDay]
- cp a, $01 ; day
- jr c, .check
- ld hl, SleepingTreeMonDayTable
- jr z, .check
- ld hl, SleepingTreeMonNiteTable
-.check
+ cp a, DAY
+ jr c, .Check
+ ld hl, .Day
+ jr z, .Check
+ ld hl, .Nite
+
+.Check
ld a, [TempEnemyMonSpecies]
- ld de, $0001
+ ld de, 1 ; length of species id
call IsInArray
+; If it's a match, the opponent is asleep
ret c
-.notsleeping
+
+.NotSleeping
and a
ret
-; 3eb5d
-SleepingTreeMonNiteTable: ; 3eb5d
+.Nite
db CATERPIE
db METAPOD
db BUTTERFREE
@@ -12827,18 +13313,16 @@ SleepingTreeMonNiteTable: ; 3eb5d
db LEDYBA
db AIPOM
db $ff ; end
-; 3eb69
-SleepingTreeMonDayTable: ; 3eb69
+.Day
db VENONAT
db HOOTHOOT
db NOCTOWL
db SPINARAK
db HERACROSS
db $ff ; end
-; 3eb6f
-SleepingTreeMonMornTable ; 3eb6f
+.Morn
db VENONAT
db HOOTHOOT
db NOCTOWL
@@ -12847,19 +13331,23 @@ SleepingTreeMonMornTable ; 3eb6f
db $ff ; end
; 3eb75
+
CheckUnownLetter: ; 3eb75
-; returns carry if not a valid letter
- ld a, [$def3]
+; Return carry if the Unown letter hasn't been unlocked yet
+ ld a, [$def3] ; UnownLetter
ld c, a
ld de, $0000
-.asm_3eb7c
- srl c ; bit 0 off?
- jr nc, .asm_3eb96
- ld hl, UnownLetterPointerTable
+.loop
+; Has this set been unlocked?
+ srl c
+ jr nc, .next
+; Check out the set
+ ld hl, .LetterSets
add hl, de
ld a, [hli]
ld h, [hl]
ld l, a
+; Is our letter in the set?
push de
ld a, [$d234]
ld de, $0001
@@ -12867,51 +13355,164 @@ CheckUnownLetter: ; 3eb75
call IsInArray
pop bc
pop de
- jr c, .end
-.asm_3eb96
+ jr c, .Match
+.next
+; Next set
inc e
inc e
ld a, e
- cp a, $08 ; has the end of the table been reached?
- jr c, .asm_3eb7c
+; Gone past the end of the table?
+ cp a, 4*2 ; 4 sets with 2-byte pointers
+ jr c, .loop
+
+; Didn't find the letter (not unlocked)
scf
ret
-.end
+
+.Match
+; Valid letter
and a
ret
-UnownLetterPointerTable: ; 3eba1
- dw UnownLetterTable
- dw UnownLetterTable2
- dw UnownLetterTable3
- dw UnownLetterTable4
-; 3eba9
+.LetterSets
+ dw .Set1
+ dw .Set2
+ dw .Set3
+ dw .Set4
-UnownLetterTable: ; 3eba9
+.Set1
; A B C D E F G H I J K
db $01, $02, $03, $04, $05, $06, $07, $08, $09, $0a, $0b
- db $ff
-; 3ebb5
+ db $ff ; end
-UnownLetterTable2: ; 3ebb5
+.Set2
; L M N O P Q R
db $0c, $0d, $0e, $0f, $10, $11, $12
- db $ff
-; 3ebbd
+ db $ff ; end
-UnownLetterTable3: ; 3ebbd
+.Set3
; S T U V W
db $13, $14, $15, $16, $17
- db $ff
-; 3ebc3
+ db $ff ; end
-UnownLetterTable4: ; 3ebc3
+.Set4
; X Y Z
db $18, $19, $1a
- db $ff
-;3ebc7
+ db $ff ; end
+; 3ebc7
+
+INCBIN "baserom.gbc", $3ebc7, $3edd8 - $3ebc7
+
+BattleRNG: ; 3edd8
+; If the normal RNG is used in a link battle it'll desync.
+; To circumvent this a shared PRNG is used instead.
+
+; But if we're in a non-link battle we're safe to use it
+ ld a, [InLinkBattle]
+ and a
+ jp z, RNG
+
+; The PRNG operates in streams of 8 values
+; The reasons for this are unknown
+
+; Which value are we trying to pull?
+ push hl
+ push bc
+ ld a, [LinkBattleRNCount]
+ ld c, a
+ ld b, $0
+ ld hl, LinkBattleRNs
+ add hl, bc
+ inc a
+ ld [LinkBattleRNCount], a
+
+; If we haven't hit the end yet, we're good
+ cp 9 ; Exclude last value. See the closing comment
+ ld a, [hl]
+ pop bc
+ pop hl
+ ret c
+
+
+; If we have, we have to generate new pseudorandom data
+; Instead of having multiple PRNGs, ten seeds are used
+ push hl
+ push bc
+ push af
+
+; Reset count to 0
+ xor a
+ ld [LinkBattleRNCount], a
+ ld hl, LinkBattleRNs
+ ld b, 10 ; number of seeds
+
+; Generate next number in the sequence for each seed
+; The algorithm takes the form *5 + 1 % 256
+.loop
+ ; get last #
+ ld a, [hl]
+
+ ; a * 5 + 1
+ ld c, a
+ add a
+ add a
+ add c
+ inc a
+
+ ; update #
+ ld [hli], a
+ dec b
+ jr nz, .loop
+
+; This has the side effect of pulling the last value first,
+; then wrapping around. As a result, when we check to see if
+; we've reached the end, we have to take this into account.
+ pop af
+ pop bc
+ pop hl
+ ret
+; 3ee0f
-INCBIN "baserom.gbc",$3ebc7,$3fc8b - $3ebc7
+INCBIN "baserom.gbc", $3ee0f, $3fa01 - $3ee0f
+
+GetRoamMonHP: ; 3fa01
+; output: hl = RoamMonCurHP
+ ld a, [TempEnemyMonSpecies]
+ ld b, a
+ ld a, [RoamMon1Species]
+ cp b
+ ld hl, RoamMon1CurHP
+ ret z
+ ld a, [RoamMon2Species]
+ cp b
+ ld hl, RoamMon2CurHP
+ ret z
+; remnant of the GS function
+; we know this will be $00 because it's never initialized
+ ld hl, RoamMon3CurHP
+ ret
+; 3fa19
+
+GetRoamMonDVs: ; 3fa19
+; output: hl = RoamMonDVs
+ ld a, [TempEnemyMonSpecies]
+ ld b, a
+ ld a, [RoamMon1Species]
+ cp b
+ ld hl, RoamMon1DVs
+ ret z
+ ld a, [RoamMon2Species]
+ cp b
+ ld hl, RoamMon2DVs
+ ret z
+; remnant of the GS function
+; we know this will be $0000 because it's never initialized
+ ld hl, RoamMon3DVs
+ ret
+; 3fa31
+
+
+INCBIN "baserom.gbc", $3fa31, $3fc8b - $3fa31
; I have no clue what most of this does
@@ -18073,7 +18674,36 @@ TileTypeTable: ; 4ce1f
db $00, $00, $00, $00, $00, $00, $00, $0f
; 4cf1f
-INCBIN "baserom.gbc",$4cf1f,$50000 - $4cf1f
+INCBIN "baserom.gbc",$4cf1f,$4d860 - $4cf1f
+
+CheckPokerus: ; 4d860
+; Return carry if a monster in your party has Pokerus
+
+; Get number of monsters to iterate over
+ ld a, [PartyCount]
+ and a
+ jr z, .NoPokerus
+ ld b, a
+; Check each monster in the party for Pokerus
+ ld hl, PartyMon1PokerusStatus
+ ld de, PartyMon2 - PartyMon1
+.Check
+ ld a, [hl]
+ and $0f ; only the bottom nybble is used
+ jr nz, .HasPokerus
+; Next PartyMon
+ add hl, de
+ dec b
+ jr nz, .Check
+.NoPokerus
+ and a
+ ret
+.HasPokerus
+ scf
+ ret
+; 4d87a
+
+INCBIN "baserom.gbc",$4d87a,$50000 - $4d87a
SECTION "bank14",DATA,BANK[$14]
@@ -18124,6 +18754,9 @@ Dark:
INCBIN "baserom.gbc",$50A28, $51424 - $50A28
+
+BaseStats:
+
BulbasaurBaseStats: ; 0x51424
db BULBASAUR ; 001
@@ -18154,6 +18787,7 @@ BulbasaurBaseStats: ; 0x51424
db %01000101
db %00000000
; end
+BaseStatsStructEnd:
IvysaurBaseStats: ; 0x51444
db IVYSAUR ; 002
@@ -88337,7 +88971,7 @@ SFX: ; e927c
dbw $3c, $4a22 ; tap
dbw $3c, $4a25 ; tap
dbw $3c, $4a28 ; burn ; that is not a burn
- dbw $3c, $4a2b ;
+ dbw $3c, $4a2b ; title screen sound
dbw $3c, $4a2e ; similar to $60
dbw $3c, $4a31 ; get coin from slots
dbw $3c, $4a34 ; pay day
@@ -89093,7 +89727,245 @@ INCBIN "baserom.gbc", $10983f, $10c000 - $10983f
SECTION "bank43",DATA,BANK[$43]
-INCBIN "baserom.gbc", $10c000, $10ef46 - $10c000
+INCBIN "baserom.gbc", $10c000, $10ed67 - $10c000
+
+TitleScreen: ; 10ed67
+
+ call WhiteBGMap
+ call ClearSprites
+ call ClearTileMap
+
+; Turn BG Map update off
+ xor a
+ ld [$ffd4], a
+
+; Reset timing variables
+ ld hl, $cf63
+ ld [hli], a ; cf63 ; Scene?
+ ld [hli], a ; cf64
+ ld [hli], a ; cf65 ; Timer lo
+ ld [hl], a ; cf66 ; Timer hi
+
+; Turn LCD off
+ call DisableLCD
+
+
+; VRAM bank 1
+ ld a, 1
+ ld [$ff4f], a
+
+
+; Decompress running Suicune gfx
+ ld hl, TitleSuicuneGFX
+ ld de, $8800
+ call $0b50
+
+
+; Clear screen palettes
+ ld hl, $9800
+ ld bc, $0280
+ xor a
+ call ByteFill
+
+
+; Fill tile palettes:
+
+; BG Map 1:
+
+; line 0 (copyright)
+ ld hl, $9c00
+ ld bc, $0020 ; one row
+ ld a, 7 ; palette
+ call ByteFill
+
+
+; BG Map 0:
+
+; Apply logo gradient:
+
+; lines 3-4
+ ld hl, $9860 ; (0,3)
+ ld bc, $0040 ; 2 rows
+ ld a, 2
+ call ByteFill
+; line 5
+ ld hl, $98a0 ; (0,5)
+ ld bc, $0020 ; 1 row
+ ld a, 3
+ call ByteFill
+; line 6
+ ld hl, $98c0 ; (0,6)
+ ld bc, $0020 ; 1 row
+ ld a, 4
+ call ByteFill
+; line 7
+ ld hl, $98e0 ; (0,7)
+ ld bc, $0020 ; 1 row
+ ld a, 5
+ call ByteFill
+; lines 8-9
+ ld hl, $9900 ; (0,8)
+ ld bc, $0040 ; 2 rows
+ ld a, 6
+ call ByteFill
+
+
+; 'CRYSTAL VERSION'
+ ld hl, $9925 ; (5,9)
+ ld bc, $000b ; length of version text
+ ld a, 1
+ call ByteFill
+
+; Suicune gfx
+ ld hl, $9980 ; (0,12)
+ ld bc, $00c0 ; the rest of the screen
+ ld a, 8
+ call ByteFill
+
+
+; Back to VRAM bank 0
+ ld a, $0
+ ld [$ff4f], a
+
+
+; Decompress logo
+ ld hl, TitleLogoGFX
+ ld de, $8800
+ call $0b50
+
+; Decompress background crystal
+ ld hl, TitleCrystalGFX
+ ld de, $8000
+ call $0b50
+
+
+; Clear screen tiles
+ ld hl, $9800
+ ld bc, $0800
+ ld a, $7f
+ call ByteFill
+
+; Draw Pokemon logo
+ ld hl, $c4dc ; TileMap(0,3)
+ ld bc, $0714 ; 20x7
+ ld d, $80
+ ld e, $14
+ call DrawGraphic
+
+; Draw copyright text
+ ld hl, $9c03 ; BG Map 1 (3,0)
+ ld bc, $010d ; 13x1
+ ld d, $c
+ ld e, $10
+ call DrawGraphic
+
+; Initialize running Suicune?
+ ld d, $0
+ call $6ed2
+
+; Initialize background crystal
+ call $6f06
+
+; Save WRAM bank
+ ld a, [$ff70]
+ push af
+; WRAM bank 5
+ ld a, 5
+ ld [$ff70], a
+
+; Update palette colors
+ ld hl, TitleScreenPalettes
+ ld de, $d000
+ ld bc, $0080
+ call CopyBytes
+
+ ld hl, TitleScreenPalettes
+ ld de, $d080
+ ld bc, $0080
+ call CopyBytes
+
+; Restore WRAM bank
+ pop af
+ ld [$ff70], a
+
+
+; LY/SCX trickery starts here
+
+; Save WRAM bank
+ ld a, [$ff70]
+ push af
+; WRAM bank 5
+ ld a, 5
+ ld [$ff70], a
+
+; Make alternating lines come in from opposite sides
+
+; ( This part is actually totally pointless, you can't
+; see anything until these values are overwritten! )
+
+ ld b, 40 ; alternate for 80 lines
+ ld hl, $d100 ; LY buffer
+.loop
+; $00 is the middle position
+ ld [hl], $70 ; coming from the left
+ inc hl
+ ld [hl], $90 ; coming from the right
+ inc hl
+ dec b
+ jr nz, .loop
+
+; Make sure the rest of the buffer is empty
+ ld hl, $d150
+ xor a
+ ld bc, $0040
+ call ByteFill
+
+; Let LCD Stat know we're messing around with SCX
+ ld a, $43 ; ff43 ; SCX
+ ld [$ffc6], a
+
+; Restore WRAM bank
+ pop af
+ ld [$ff70], a
+
+
+; Reset audio
+ call ChannelsOff
+ call $058a
+
+; Set sprite size to 8x16
+ ld a, [$ff40] ; LCDC
+ set 2, a
+ ld [$ff40], a ; LCDC
+
+;
+ ld a, $70
+ ld [$ffcf], a
+ ld a, $8
+ ld [$ffd0], a
+ ld a, $7
+ ld [$ffd1], a
+ ld a, $90
+ ld [$ffd2], a
+
+ ld a, $1
+ ld [$ffe5], a
+
+; Update BG Map 0 (bank 0)
+ ld [$ffd4], a
+
+ xor a
+ ld [$d002], a
+
+; Play starting sound effect
+ call SFXChannelsOff
+ ld de, $0065
+ call StartSFX
+
+ ret
+; 10eea7
+
+INCBIN "baserom.gbc", $10eea7, $10ef46 - $10eea7
TitleSuicuneGFX: ; 10ef46
INCBIN "gfx/title/lz/suicune.lz"
@@ -89111,7 +89983,93 @@ TitleCrystalGFX: ; 10fcee
INCBIN "gfx/title/lz/crystal.lz"
; 10fed7
-INCBIN "baserom.gbc", $10fed7, $110000 - $10fed7
+INCBIN "baserom.gbc", $10fed7, $10fede - $10fed7
+
+TitleScreenPalettes:
+; BG
+ RGB 00, 00, 00
+ RGB 19, 00, 00
+ RGB 15, 08, 31
+ RGB 15, 08, 31
+
+ RGB 00, 00, 00
+ RGB 31, 31, 31
+ RGB 15, 16, 31
+ RGB 31, 01, 13
+
+ RGB 00, 00, 00
+ RGB 07, 07, 07
+ RGB 31, 31, 31
+ RGB 02, 03, 30
+
+ RGB 00, 00, 00
+ RGB 13, 13, 13
+ RGB 31, 31, 18
+ RGB 02, 03, 30
+
+ RGB 00, 00, 00
+ RGB 19, 19, 19
+ RGB 29, 28, 12
+ RGB 02, 03, 30
+
+ RGB 00, 00, 00
+ RGB 25, 25, 25
+ RGB 28, 25, 06
+ RGB 02, 03, 30
+
+ RGB 00, 00, 00
+ RGB 31, 31, 31
+ RGB 26, 21, 00
+ RGB 02, 03, 30
+
+ RGB 00, 00, 00
+ RGB 11, 11, 19
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+
+; OBJ
+ RGB 00, 00, 00
+ RGB 10, 00, 15
+ RGB 17, 05, 22
+ RGB 19, 09, 31
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+ RGB 31, 31, 31
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+ RGB 00, 00, 00
+
+
+INCBIN "baserom.gbc", $10ff5e, $110000 - $10ff5e
SECTION "bank44",DATA,BANK[$44]
@@ -89376,7 +90334,7 @@ Function117bb6:
ld hl, $d002
ld de, $b000
ld bc, $1000
- call $3026
+ call CopyBytes
call CloseSRAM
pop af
ld [$ff70], a
diff --git a/preprocessor.py b/preprocessor.py
index abb672822..9b748dbd6 100644
--- a/preprocessor.py
+++ b/preprocessor.py
@@ -32,6 +32,12 @@ macros = command_classes + \
movement_command_classes + \
music_classes
+# show lines before preprocessing in stdout
+show_original_lines = False
+
+# helpful for debugging macros
+do_macro_sanity_check = False
+
chars = {
"ガ": 0x05,
"ギ": 0x06,
@@ -338,6 +344,7 @@ def quote_translator(asm):
sys.stdout.write(asm)
return
+ output = ""
even = False
i = 0
for token in asms:
@@ -378,15 +385,18 @@ def quote_translator(asm):
char = char + token[0]
token = token[1:]
- sys.stdout.write("${0:02X}".format(chars[char]))
+ output += ("${0:02X}".format(chars[char]))
if len(token):
- sys.stdout.write(", ")
+ output += (", ")
# if not even
else:
- sys.stdout.write(token)
+ output += (token)
even = not even
+
+ sys.stdout.write(output)
+
return
def extract_token(asm):
@@ -405,9 +415,9 @@ def macro_test(asm):
token = extract_token(asm)
# check against all names
- try:
+ if token in macro_table:
return (macro_table[token], token)
- except:
+ else:
return (None, None)
def macro_translator(macro, token, line):
@@ -445,7 +455,8 @@ def macro_translator(macro, token, line):
raise Exception, "macro has no params?"
# write out a comment showing the original line
- sys.stdout.write("; original_line: " + original_line)
+ if show_original_lines:
+ sys.stdout.write("; original_line: " + original_line)
# "db" is a macro because of TextEndingCommand
# rgbasm can handle "db" so no preprocessing is required
@@ -458,51 +469,55 @@ def macro_translator(macro, token, line):
# do: all scripting macros
# don't: signpost, warp_def, person_event, xy_trigger
if not macro.override_byte_check:
- sys.stdout.write("db $%.2x\n" % (macro.id))
+ sys.stdout.write("db ${0:02X}\n".format(macro.id))
# --- long-winded sanity check goes here ---
- # sanity check... this won't work because PointerLabelBeforeBank shows
- # up as two params, so these two lengths will always be different.
- #assert len(params) == len(macro.param_types), \
- # "mismatched number of parameters on this line: " + \
- # original_line
-
- # v2 sanity check :) although it sorta sucks that this loop happens twice?
- allowed_length = 0
- for (index, param_type) in macro.param_types.items():
- param_klass = param_type["class"]
-
- if param_klass.byte_type == "db":
- allowed_length += 1 # just one value
- elif param_klass.byte_type == "dw":
- if param_klass.size == 2:
- allowed_length += 1 # just label
- elif param_klass == MoneyByteParam:
- allowed_length += 1
- elif param_klass.size == 3:
- allowed_length += 2 # bank and label
+ if do_macro_sanity_check:
+
+ # sanity check... this won't work because PointerLabelBeforeBank shows
+ # up as two params, so these two lengths will always be different.
+ #assert len(params) == len(macro.param_types), \
+ # "mismatched number of parameters on this line: " + \
+ # original_line
+
+ # v2 sanity check :) although it sorta sucks that this loop happens twice?
+ allowed_length = 0
+ for (index, param_type) in macro.param_types.items():
+ param_klass = param_type["class"]
+
+ if param_klass.byte_type == "db":
+ allowed_length += 1 # just one value
+ elif param_klass.byte_type == "dw":
+ if param_klass.size == 2:
+ allowed_length += 1 # just label
+ elif param_klass == MoneyByteParam:
+ allowed_length += 1
+ elif param_klass.size == 3:
+ allowed_length += 2 # bank and label
+ else:
+ raise Exception, "dunno what to do with a macro param with a size > 3"
else:
- raise Exception, "dunno what to do with a macro param with a size > 3"
- else:
- raise Exception, "dunno what to do with this non db/dw macro param: " + \
- str(param_klass) + " in line: " + original_line
+ raise Exception, "dunno what to do with this non db/dw macro param: " + \
+ str(param_klass) + " in line: " + original_line
- # sometimes the allowed length can vary
- if hasattr(macro, "allowed_lengths"):
- allowed_lengths = macro.allowed_lengths + [allowed_length]
- else:
- allowed_lengths = [allowed_length]
+ # sometimes the allowed length can vary
+ if hasattr(macro, "allowed_lengths"):
+ allowed_lengths = macro.allowed_lengths + [allowed_length]
+ else:
+ allowed_lengths = [allowed_length]
- assert len(params) in allowed_lengths, \
- "mismatched number of parameters on this line: " + \
- original_line
+ assert len(params) in allowed_lengths, \
+ "mismatched number of parameters on this line: " + \
+ original_line
# --- end of ridiculously long sanity check ---
# used for storetext
correction = 0
+ output = ""
+
index = 0
while index < len(params):
param_type = macro.param_types[index - correction]
@@ -519,24 +534,24 @@ def macro_translator(macro, token, line):
if (byte_type == "dw" and size != 2) or \
(byte_type == "db" and size != 1):
- sys.stdout.write("; " + description + "\n")
+ output += ("; " + description + "\n")
if size == 3 and issubclass(param_klass, PointerLabelBeforeBank):
# write the bank first
- sys.stdout.write("db " + params[index].strip() + "\n")
+ output += ("db " + param + "\n")
# write the pointer second
- sys.stdout.write("dw " + params[index+1].strip() + "\n")
+ output += ("dw " + params[index+1].strip() + "\n")
index += 2
correction += 1
elif size == 3 and issubclass(param_klass, PointerLabelAfterBank):
# write the pointer first
- sys.stdout.write("dw " + params[index].strip() + "\n")
+ output += ("dw " + param + "\n")
# write the bank second
- sys.stdout.write("db " + params[index+1].strip() + "\n")
+ output += ("db " + params[index+1].strip() + "\n")
index += 2
correction += 1
elif size == 3 and issubclass(param_klass, MoneyByteParam):
- sys.stdout.write("db " + MoneyByteParam.from_asm(params[index]) + "\n")
+ output += ("db " + MoneyByteParam.from_asm(param) + "\n")
index += 1
else:
raise Exception, "dunno what to do with this macro " + \
@@ -545,16 +560,18 @@ def macro_translator(macro, token, line):
# or just print out the byte
else:
- sys.stdout.write(byte_type + " " + param + " ; " + description + "\n")
+ output += (byte_type + " " + param + " ; " + description + "\n")
index += 1
+ sys.stdout.write(output)
+
def include_file(asm):
"""This is more reliable than rgbasm/rgbds including files on its own."""
- filename = asm.split("\"")
- filename = filename[1].replace("\"","").replace("\n","")
- lines = open(filename, 'r').readlines()
+ filename = asm.split("\"")[1]
+
+ lines = open(filename, "r").readlines()
for line in lines:
read_line(line)
diff --git a/wram.asm b/wram.asm
index 6f8364a23..18abf545c 100644
--- a/wram.asm
+++ b/wram.asm
@@ -539,7 +539,7 @@ BattleScriptBufferLocLo: ; c6b2
BattleScriptBufferLocHi: ; c6b3
ds 1
- ds 24
+ ds 25
PlayerStatLevels: ; c6cc
; 07 neutral
@@ -658,7 +658,7 @@ MonType: ; cf5f
; 4 wildmon
ds 1
-CurBattleSpecies: ; cf60
+CurSpecies: ; cf60
ds 1
ds 33