diff options
-rw-r--r-- | INSTALL.md | 4 | ||||
-rw-r--r-- | README | 21 | ||||
-rw-r--r-- | README.md | 65 | ||||
-rw-r--r-- | constants.asm | 25 | ||||
-rw-r--r-- | extras/crystal.py | 17 | ||||
-rw-r--r-- | extras/gfx.py | 34 | ||||
-rw-r--r-- | extras/pksv.py | 9 | ||||
-rw-r--r-- | main.asm | 1410 | ||||
-rw-r--r-- | preprocessor.py | 115 | ||||
-rw-r--r-- | wram.asm | 4 |
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? ] @@ -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) @@ -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 |