summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--Makefile2
m---------extras0
-rw-r--r--src/constants/card_data_constants.asm2
-rw-r--r--src/constants/hardware_constants.asm25
-rw-r--r--src/constants/text_constants.asm90
-rw-r--r--src/data/cards.asm384
-rw-r--r--src/data/effect_commands.asm2
-rw-r--r--src/engine/bank01.asm94
-rw-r--r--src/engine/bank04.asm2
-rw-r--r--src/engine/bank06.asm6
-rw-r--r--src/engine/home.asm210
-rw-r--r--src/macros/code.asm4
-rw-r--r--src/macros/data.asm5
-rw-r--r--src/macros/wram.asm2
-rw-r--r--src/text/text10.asm76
-rw-r--r--src/text/text11.asm54
-rw-r--r--src/text/text12.asm52
-rw-r--r--src/text/text9.asm10
-rw-r--r--src/text/text_offsets.asm192
-rw-r--r--src/wram.asm16
-rw-r--r--tools/configuration.py57
-rw-r--r--tools/gfx.py893
-rw-r--r--tools/png.py2650
-rw-r--r--tools/scan_includes.py43
-rw-r--r--tools/tcgdisasm.py947
-rw-r--r--tools/wram.py322
27 files changed, 5600 insertions, 543 deletions
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index b024897..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "extras"]
- path = extras
- url = https://github.com/pret/pokemon-reverse-engineering-tools/
diff --git a/Makefile b/Makefile
index 09db89b..34fbbaa 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@
.SECONDEXPANSION:
OBJS = src/main.o src/gfx.o src/text.o src/audio.o src/wram.o src/hram.o
-EXTRAS = extras/pokemontools
+EXTRAS = tools
$(foreach obj, $(OBJS), \
$(eval $(obj:.o=)_dep = $(shell python $(EXTRAS)/scan_includes.py $(obj:.o=.asm))) \
diff --git a/extras b/extras
deleted file mode 160000
-Subproject f286f17bde37d0469b1e0203cc45cc69cea4a4c
diff --git a/src/constants/card_data_constants.asm b/src/constants/card_data_constants.asm
index e857aa6..c110a69 100644
--- a/src/constants/card_data_constants.asm
+++ b/src/constants/card_data_constants.asm
@@ -62,7 +62,7 @@ CARD_DATA_MOVE2_ANIMATION EQU $31
CARD_DATA_RETREAT_COST EQU $32
CARD_DATA_WEAKNESS EQU $33
CARD_DATA_RESISTANCE EQU $34
-CARD_DATA_KIND EQU $35
+CARD_DATA_CATEGORY EQU $35
CARD_DATA_POKEDEX_NUMBER EQU $37
CARD_DATA_UNKNOWN1 EQU $38
CARD_DATA_LEVEL EQU $39
diff --git a/src/constants/hardware_constants.asm b/src/constants/hardware_constants.asm
index 098b569..a93e250 100644
--- a/src/constants/hardware_constants.asm
+++ b/src/constants/hardware_constants.asm
@@ -1,7 +1,9 @@
-; From http://nocash.emubase.de/pandocs.htm.
+; From http://bgb.bircd.org/pandocs.htm
GBC EQU $11
+LY_VBLANK EQU 145
+
; MBC3
MBC3SRamEnable EQU $0000
MBC3RomBank EQU $2000
@@ -30,8 +32,6 @@ TIMER EQU 2
SERIAL EQU 3
JOYPAD EQU 4
-LY_VBLANK EQU 145
-
; OAM attribute flags
OAM_PALETTE EQU %111
OAM_TILE_BANK EQU 3
@@ -44,18 +44,18 @@ OAM_PRIORITY EQU 7 ; 0: OBJ above BG, 1: OBJ behind BG (colors 1-3)
rJOYP EQU $ff00 ; Joypad (R/W)
rSB EQU $ff01 ; Serial transfer data (R/W)
rSC EQU $ff02 ; Serial Transfer Control (R/W)
-rSC_ON EQU 7
-rSC_CGB EQU 1
-rSC_CLOCK EQU 0
+SC_ON EQU 7
+SC_CGB EQU 1
+SC_CLOCK EQU 0
rDIV EQU $ff04 ; Divider Register (R/W)
rTIMA EQU $ff05 ; Timer counter (R/W)
rTMA EQU $ff06 ; Timer Modulo (R/W)
rTAC EQU $ff07 ; Timer Control (R/W)
-rTAC_ON EQU 2
-rTAC_4096_HZ EQU 0
-rTAC_262144_HZ EQU 1
-rTAC_65536_HZ EQU 2
-rTAC_16384_HZ EQU 3
+TAC_ON EQU 2
+TAC_4096_HZ EQU 0
+TAC_262144_HZ EQU 1
+TAC_65536_HZ EQU 2
+TAC_16384_HZ EQU 3
rIF EQU $ff0f ; Interrupt Flag (R/W)
rNR10 EQU $ff10 ; Channel 1 Sweep register (R/W)
rNR11 EQU $ff11 ; Channel 1 Sound length/Wave pattern duty (R/W)
@@ -79,8 +79,7 @@ rNR50 EQU $ff24 ; Channel control / ON-OFF / Volume (R/W)
rNR51 EQU $ff25 ; Selection of Sound output terminal (R/W)
rNR52 EQU $ff26 ; Sound on/off
rLCDC EQU $ff40 ; LCD Control (R/W)
-rLCDC_ENABLE EQU 7
-rLCDC_ENABLE_MASK EQU 1 << rLCDC_ENABLE
+LCDC_ON EQU 7
rSTAT EQU $ff41 ; LCDC Status (R/W)
rSCY EQU $ff42 ; Scroll Y (R/W)
rSCX EQU $ff43 ; Scroll X (R/W)
diff --git a/src/constants/text_constants.asm b/src/constants/text_constants.asm
index 3c93cdf..92554af 100644
--- a/src/constants/text_constants.asm
+++ b/src/constants/text_constants.asm
@@ -15,38 +15,58 @@ done EQUS "db TX_END"
charmap "♀", "%"
charmap "”", "\""
-; TX_SYMBOL (full-tile symbols loaded into v0Tiles2)
- charmap "<", TX_SYMBOL
- charmap " >", $00
- charmap "FIRE>", $01
- charmap "GRASS>", $02
- charmap "LIGHTNING>", $03
- charmap "WATER>", $04
- charmap "FIGHTING>", $05
- charmap "PSYCHIC>", $06
- charmap "COLORLESS>", $07
- charmap "POISONED>", $08
- charmap "ASLEEP>", $09
- charmap "CONFUSED>", $0a
- charmap "PARALYZED>", $0b
- charmap "PKMN_ICON>", $0d ; icon displayed along with no. of Pkmn in duel screen
- charmap "HP>", $10
- charmap "Lv>", $11
- charmap "E>", $12
- charmap "No>", $13
- charmap "PLUSPOWER>", $14
- charmap "DEFENDER>", $15
- charmap "🌕>", $16 ; HP tile
- charmap "🌑>", $17 ; HP tile with damage counter
- charmap "0>", $20
- charmap "1>", $21
- charmap "2>", $22
- charmap "3>", $23
- charmap "4>", $24
- charmap "5>", $25
- charmap "6>", $26
- charmap "7>", $27
- charmap "8>", $28
- charmap "9>", $29
- charmap "+>", $2b
- charmap "PRIZE_ICON>", $30 ; icon displayed along with no. of prizes in duel screen
+; TX_SYMBOL (full-tile icons/symbols loaded at the beginning of v0Tiles2)
+; TODO: Use symbols in menus (cursor tile number, tile behind cursor), draw text boxes, WriteByteToBGMap0, etc
+; If user-defined functions ever become a thing a symbol(*) syntax would probably be preferred over SYM_*
+
+ charmap "<", TX_SYMBOL
+ const_def
+ txsymbol SPACE
+ txsymbol FIRE
+ txsymbol GRASS
+ txsymbol LIGHTNING
+ txsymbol WATER
+ txsymbol FIGHTING
+ txsymbol PSYCHIC
+ txsymbol COLORLESS
+ txsymbol POISONED
+ txsymbol ASLEEP
+ txsymbol CONFUSED
+ txsymbol PARALYZED
+ txsymbol CURSOR_U
+ txsymbol POKEMON
+ txsymbol UNKNOWN_0E
+ txsymbol CURSOR_R
+ txsymbol HP
+ txsymbol Lv
+ txsymbol E
+ txsymbol No
+ txsymbol PLUSPOWER
+ txsymbol DEFENDER
+ txsymbol HP_OK
+ txsymbol HP_NOK
+ txsymbol BOX_TOP_L
+ txsymbol BOX_TOP_R
+ txsymbol BOX_BTM_L
+ txsymbol BOX_BTM_R
+ txsymbol BOX_TOP
+ txsymbol BOX_BOTTOM
+ txsymbol BOX_LEFT
+ txsymbol BOX_RIGHT
+ txsymbol 0
+ txsymbol 1
+ txsymbol 2
+ txsymbol 3
+ txsymbol 4
+ txsymbol 5
+ txsymbol 6
+ txsymbol 7
+ txsymbol 8
+ txsymbol 9
+ txsymbol DOT
+ txsymbol PLUS
+ txsymbol MINUS
+ txsymbol x
+ txsymbol SLASH
+ txsymbol CURSOR_D
+ txsymbol PRIZE
diff --git a/src/data/cards.asm b/src/data/cards.asm
index 480607e..db2a9ec 100644
--- a/src/data/cards.asm
+++ b/src/data/cards.asm
@@ -275,7 +275,7 @@ BulbasaurCard: ; 30e28 (c:4e28)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx BulbasaurKind ; kind
+ tx SeedName ; category
db 1 ; Pokedex number
db 0
db 13 ; level
@@ -326,7 +326,7 @@ IvysaurCard: ; 30e69 (c:4e69)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx BulbasaurKind ; kind
+ tx SeedName ; category
db 2 ; Pokedex number
db 0
db 20 ; level
@@ -377,7 +377,7 @@ Venusaur1Card: ; 30eaa (c:4eaa)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx BulbasaurKind ; kind
+ tx SeedName ; category
db 3 ; Pokedex number
db 0
db 64 ; level
@@ -428,7 +428,7 @@ Venusaur2Card: ; 30eeb (c:4eeb)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx BulbasaurKind ; kind
+ tx SeedName ; category
db 3 ; Pokedex number
db 0
db 67 ; level
@@ -479,7 +479,7 @@ CaterpieCard: ; 30f2c (c:4f2c)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx CaterpieKind ; kind
+ tx WormName ; category
db 10 ; Pokedex number
db 0
db 13 ; level
@@ -530,7 +530,7 @@ MetapodCard: ; 30f6d (c:4f6d)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx MetapodKind ; kind
+ tx CocoonName ; category
db 11 ; Pokedex number
db 0
db 21 ; level
@@ -581,7 +581,7 @@ ButterfreeCard: ; 30fae (c:4fae)
db 0 ; retreat cost
db WR_FIRE ; weakness
db WR_FIGHTING ; resistance
- tx ButterfreeKind ; kind
+ tx ButterflyName ; category
db 12 ; Pokedex number
db 0
db 28 ; level
@@ -632,7 +632,7 @@ WeedleCard: ; 30fef (c:4fef)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx WeedleKind ; kind
+ tx HairyBugName ; category
db 13 ; Pokedex number
db 0
db 12 ; level
@@ -683,7 +683,7 @@ KakunaCard: ; 31030 (c:5030)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx MetapodKind ; kind
+ tx CocoonName ; category
db 14 ; Pokedex number
db 0
db 23 ; level
@@ -734,7 +734,7 @@ BeedrillCard: ; 31071 (c:5071)
db 0 ; retreat cost
db WR_FIRE ; weakness
db WR_FIGHTING ; resistance
- tx BeedrillKind ; kind
+ tx PoisonBeeName ; category
db 15 ; Pokedex number
db 0
db 32 ; level
@@ -785,7 +785,7 @@ EkansCard: ; 310b2 (c:50b2)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx EkansKind ; kind
+ tx SnakeName ; category
db 23 ; Pokedex number
db 0
db 10 ; level
@@ -836,7 +836,7 @@ ArbokCard: ; 310f3 (c:50f3)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx ArbokKind ; kind
+ tx CobraName ; category
db 24 ; Pokedex number
db 0
db 27 ; level
@@ -887,7 +887,7 @@ NidoranFCard: ; 31134 (c:5134)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx NidoranFKind ; kind
+ tx PoisonPinName ; category
db 29 ; Pokedex number
db 0
db 13 ; level
@@ -938,7 +938,7 @@ NidorinaCard: ; 31175 (c:5175)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx NidoranFKind ; kind
+ tx PoisonPinName ; category
db 30 ; Pokedex number
db 0
db 24 ; level
@@ -989,7 +989,7 @@ NidoqueenCard: ; 311b6 (c:51b6)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx NidoqueenKind ; kind
+ tx DrillName ; category
db 31 ; Pokedex number
db 0
db 43 ; level
@@ -1040,7 +1040,7 @@ NidoranMCard: ; 311f7 (c:51f7)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx NidoranFKind ; kind
+ tx PoisonPinName ; category
db 32 ; Pokedex number
db 0
db 20 ; level
@@ -1091,7 +1091,7 @@ NidorinoCard: ; 31238 (c:5238)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx NidoranFKind ; kind
+ tx PoisonPinName ; category
db 33 ; Pokedex number
db 0
db 25 ; level
@@ -1142,7 +1142,7 @@ NidokingCard: ; 31279 (c:5279)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx NidoqueenKind ; kind
+ tx DrillName ; category
db 34 ; Pokedex number
db 0
db 48 ; level
@@ -1193,7 +1193,7 @@ ZubatCard: ; 312ba (c:52ba)
db 0 ; retreat cost
db WR_PSYCHIC ; weakness
db WR_FIGHTING ; resistance
- tx ZubatKind ; kind
+ tx BatName ; category
db 41 ; Pokedex number
db 0
db 10 ; level
@@ -1244,7 +1244,7 @@ GolbatCard: ; 312fb (c:52fb)
db 0 ; retreat cost
db WR_PSYCHIC ; weakness
db WR_FIGHTING ; resistance
- tx ZubatKind ; kind
+ tx BatName ; category
db 42 ; Pokedex number
db 0
db 29 ; level
@@ -1295,7 +1295,7 @@ OddishCard: ; 3133c (c:533c)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx OddishKind ; kind
+ tx WeedName ; category
db 43 ; Pokedex number
db 0
db 8 ; level
@@ -1346,7 +1346,7 @@ GloomCard: ; 3137d (c:537d)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx OddishKind ; kind
+ tx WeedName ; category
db 44 ; Pokedex number
db 0
db 22 ; level
@@ -1397,7 +1397,7 @@ VileplumeCard: ; 313be (c:53be)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx VileplumeKind ; kind
+ tx FlowerName ; category
db 45 ; Pokedex number
db 0
db 35 ; level
@@ -1448,7 +1448,7 @@ ParasCard: ; 313ff (c:53ff)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx ParasKind ; kind
+ tx MushroomName ; category
db 46 ; Pokedex number
db 0
db 8 ; level
@@ -1499,7 +1499,7 @@ ParasectCard: ; 31440 (c:5440)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx ParasKind ; kind
+ tx MushroomName ; category
db 47 ; Pokedex number
db 0
db 28 ; level
@@ -1550,7 +1550,7 @@ VenonatCard: ; 31481 (c:5481)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx VenonatKind ; kind
+ tx InsectName ; category
db 48 ; Pokedex number
db 0
db 12 ; level
@@ -1601,7 +1601,7 @@ VenomothCard: ; 314c2 (c:54c2)
db 0 ; retreat cost
db WR_FIRE ; weakness
db WR_FIGHTING ; resistance
- tx VenomothKind ; kind
+ tx PoisonmothName ; category
db 49 ; Pokedex number
db 0
db 28 ; level
@@ -1652,7 +1652,7 @@ BellsproutCard: ; 31503 (c:5503)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx VileplumeKind ; kind
+ tx FlowerName ; category
db 69 ; Pokedex number
db 0
db 11 ; level
@@ -1703,7 +1703,7 @@ WeepinbellCard: ; 31544 (c:5544)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx WeepinbellKind ; kind
+ tx FlycatcherName ; category
db 70 ; Pokedex number
db 0
db 28 ; level
@@ -1754,7 +1754,7 @@ VictreebelCard: ; 31585 (c:5585)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx WeepinbellKind ; kind
+ tx FlycatcherName ; category
db 71 ; Pokedex number
db 0
db 42 ; level
@@ -1805,7 +1805,7 @@ GrimerCard: ; 315c6 (c:55c6)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx GrimerKindOrSludgeName ; kind
+ tx SludgeName ; category
db 88 ; Pokedex number
db 0
db 17 ; level
@@ -1841,7 +1841,7 @@ MukCard: ; 31607 (c:5607)
; move 2
energy GRASS, 3 ; energies
- tx GrimerKindOrSludgeName ; name
+ tx SludgeName ; name
tx MayInflictPoisonDescription ; description
dw NONE ; description (cont)
db 30 ; damage
@@ -1856,7 +1856,7 @@ MukCard: ; 31607 (c:5607)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx GrimerKindOrSludgeName ; kind
+ tx SludgeName ; category
db 89 ; Pokedex number
db 0
db 34 ; level
@@ -1878,7 +1878,7 @@ ExeggcuteCard: ; 31648 (c:5648)
; move 1
energy PSYCHIC, 1 ; energies
- tx DrowzeeKindOrHypnosisName ; name
+ tx HypnosisName ; name
tx InflictSleepDescription ; description
dw NONE ; description (cont)
db 0 ; damage
@@ -1907,7 +1907,7 @@ ExeggcuteCard: ; 31648 (c:5648)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx ExeggcuteKind ; kind
+ tx EggName ; category
db 102 ; Pokedex number
db 0
db 14 ; level
@@ -1958,7 +1958,7 @@ ExeggutorCard: ; 31689 (c:5689)
db 3 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx ExeggutorKind ; kind
+ tx CoconutName ; category
db 103 ; Pokedex number
db 0
db 35 ; level
@@ -2009,7 +2009,7 @@ KoffingCard: ; 316ca (c:56ca)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx KoffingKind ; kind
+ tx PoisonGasName ; category
db 109 ; Pokedex number
db 0
db 13 ; level
@@ -2060,7 +2060,7 @@ WeezingCard: ; 3170b (c:570b)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx KoffingKind ; kind
+ tx PoisonGasName ; category
db 110 ; Pokedex number
db 0
db 27 ; level
@@ -2111,7 +2111,7 @@ Tangela1Card: ; 3174c (c:574c)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx TangelaKind ; kind
+ tx VineName ; category
db 114 ; Pokedex number
db 0
db 8 ; level
@@ -2162,7 +2162,7 @@ Tangela2Card: ; 3178d (c:578d)
db 2 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx TangelaKind ; kind
+ tx VineName ; category
db 114 ; Pokedex number
db 0
db 12 ; level
@@ -2213,7 +2213,7 @@ ScytherCard: ; 317ce (c:57ce)
db 0 ; retreat cost
db WR_FIRE ; weakness
db WR_FIGHTING ; resistance
- tx ScytherKind ; kind
+ tx MantisName ; category
db 123 ; Pokedex number
db 0
db 25 ; level
@@ -2264,7 +2264,7 @@ PinsirCard: ; 3180f (c:580f)
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
- tx PinsirKind ; kind
+ tx StagbeetleName ; category
db 127 ; Pokedex number
db 0
db 24 ; level
@@ -2315,7 +2315,7 @@ CharmanderCard: ; 31850 (c:5850)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx CharmanderKind ; kind
+ tx LizardName ; category
db 4 ; Pokedex number
db 0
db 10 ; level
@@ -2366,7 +2366,7 @@ CharmeleonCard: ; 31891 (c:5891)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx CharmeleonKind ; kind
+ tx FlameName ; category
db 5 ; Pokedex number
db 0
db 32 ; level
@@ -2417,7 +2417,7 @@ CharizardCard: ; 318d2 (c:58d2)
db 3 ; retreat cost
db WR_WATER ; weakness
db WR_FIGHTING ; resistance
- tx CharmeleonKind ; kind
+ tx FlameName ; category
db 6 ; Pokedex number
db 0
db 76 ; level
@@ -2468,7 +2468,7 @@ VulpixCard: ; 31913 (c:5913)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx VulpixKind ; kind
+ tx FoxName ; category
db 37 ; Pokedex number
db 0
db 11 ; level
@@ -2519,7 +2519,7 @@ Ninetails1Card: ; 31954 (c:5954)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx VulpixKind ; kind
+ tx FoxName ; category
db 38 ; Pokedex number
db 0
db 32 ; level
@@ -2570,7 +2570,7 @@ Ninetails2Card: ; 31995 (c:5995)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx VulpixKind ; kind
+ tx FoxName ; category
db 38 ; Pokedex number
db 0
db 35 ; level
@@ -2621,7 +2621,7 @@ GrowlitheCard: ; 319d6 (c:59d6)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx GrowlitheKind ; kind
+ tx PuppyName ; category
db 58 ; Pokedex number
db 0
db 18 ; level
@@ -2672,7 +2672,7 @@ Arcanine1Card: ; 31a17 (c:5a17)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx ArcanineKind ; kind
+ tx LegendaryName ; category
db 59 ; Pokedex number
db 0
db 34 ; level
@@ -2723,7 +2723,7 @@ Arcanine2Card: ; 31a58 (c:5a58)
db 3 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx ArcanineKind ; kind
+ tx LegendaryName ; category
db 59 ; Pokedex number
db 0
db 45 ; level
@@ -2774,7 +2774,7 @@ PonytaCard: ; 31a99 (c:5a99)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx PonytaKind ; kind
+ tx FireHorseName ; category
db 77 ; Pokedex number
db 0
db 10 ; level
@@ -2825,7 +2825,7 @@ RapidashCard: ; 31ada (c:5ada)
db 0 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx PonytaKind ; kind
+ tx FireHorseName ; category
db 78 ; Pokedex number
db 0
db 33 ; level
@@ -2876,7 +2876,7 @@ Magmar1Card: ; 31b1b (c:5b1b)
db 2 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx MagmarKind ; kind
+ tx SpitfireName ; category
db 126 ; Pokedex number
db 0
db 24 ; level
@@ -2927,7 +2927,7 @@ Magmar2Card: ; 31b5c (c:5b5c)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx MagmarKind ; kind
+ tx SpitfireName ; category
db 126 ; Pokedex number
db 0
db 31 ; level
@@ -2978,7 +2978,7 @@ Flareon1Card: ; 31b9d (c:5b9d)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx CharmeleonKind ; kind
+ tx FlameName ; category
db 136 ; Pokedex number
db 0
db 22 ; level
@@ -3029,7 +3029,7 @@ Flareon2Card: ; 31bde (c:5bde)
db 1 ; retreat cost
db WR_WATER ; weakness
db NONE ; resistance
- tx CharmeleonKind ; kind
+ tx FlameName ; category
db 136 ; Pokedex number
db 0
db 28 ; level
@@ -3080,7 +3080,7 @@ Moltres1Card: ; 31c1f (c:5c1f)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx CharmeleonKind ; kind
+ tx FlameName ; category
db 146 ; Pokedex number
db 0
db 35 ; level
@@ -3131,7 +3131,7 @@ Moltres2Card: ; 31c60 (c:5c60)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx CharmeleonKind ; kind
+ tx FlameName ; category
db 146 ; Pokedex number
db 0
db 37 ; level
@@ -3182,7 +3182,7 @@ SquirtleCard: ; 31ca1 (c:5ca1)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx SquirtleKind ; kind
+ tx TinyTurtleName ; category
db 7 ; Pokedex number
db 0
db 8 ; level
@@ -3233,7 +3233,7 @@ WartortleCard: ; 31ce2 (c:5ce2)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx WartortleKind ; kind
+ tx TurtleName ; category
db 8 ; Pokedex number
db 0
db 22 ; level
@@ -3284,7 +3284,7 @@ BlastoiseCard: ; 31d23 (c:5d23)
db 3 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx BlastoiseKind ; kind
+ tx ShellfishName ; category
db 9 ; Pokedex number
db 0
db 52 ; level
@@ -3335,7 +3335,7 @@ PsyduckCard: ; 31d64 (c:5d64)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx PsyduckKind ; kind
+ tx DuckName ; category
db 54 ; Pokedex number
db 0
db 15 ; level
@@ -3386,7 +3386,7 @@ GolduckCard: ; 31da5 (c:5da5)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx PsyduckKind ; kind
+ tx DuckName ; category
db 55 ; Pokedex number
db 0
db 27 ; level
@@ -3437,7 +3437,7 @@ PoliwagCard: ; 31de6 (c:5de6)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx PoliwagKind ; kind
+ tx TadpoleName ; category
db 60 ; Pokedex number
db 0
db 13 ; level
@@ -3488,7 +3488,7 @@ PoliwhirlCard: ; 31e27 (c:5e27)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx PoliwagKind ; kind
+ tx TadpoleName ; category
db 61 ; Pokedex number
db 0
db 28 ; level
@@ -3539,7 +3539,7 @@ PoliwrathCard: ; 31e68 (c:5e68)
db 3 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx PoliwagKind ; kind
+ tx TadpoleName ; category
db 62 ; Pokedex number
db 0
db 48 ; level
@@ -3590,7 +3590,7 @@ TentacoolCard: ; 31ea9 (c:5ea9)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx TentacoolKind ; kind
+ tx JellyfishName ; category
db 72 ; Pokedex number
db 0
db 10 ; level
@@ -3641,7 +3641,7 @@ TentacruelCard: ; 31eea (c:5eea)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx TentacoolKind ; kind
+ tx JellyfishName ; category
db 73 ; Pokedex number
db 0
db 21 ; level
@@ -3692,7 +3692,7 @@ SeelCard: ; 31f2b (c:5f2b)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx SeelKind ; kind
+ tx SeaLionName ; category
db 86 ; Pokedex number
db 0
db 12 ; level
@@ -3743,7 +3743,7 @@ DewgongCard: ; 31f6c (c:5f6c)
db 3 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx SeelKind ; kind
+ tx SeaLionName ; category
db 87 ; Pokedex number
db 0
db 42 ; level
@@ -3794,7 +3794,7 @@ ShellderCard: ; 31fad (c:5fad)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx ShellderKind ; kind
+ tx BivalveName ; category
db 90 ; Pokedex number
db 0
db 8 ; level
@@ -3845,7 +3845,7 @@ CloysterCard: ; 31fee (c:5fee)
db 2 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx ShellderKind ; kind
+ tx BivalveName ; category
db 91 ; Pokedex number
db 0
db 25 ; level
@@ -3896,7 +3896,7 @@ KrabbyCard: ; 3202f (c:602f)
db 2 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx KrabbyKind ; kind
+ tx RiverCrabName ; category
db 98 ; Pokedex number
db 0
db 20 ; level
@@ -3947,7 +3947,7 @@ KinglerCard: ; 32070 (c:6070)
db 3 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx KinglerKind ; kind
+ tx PincerName ; category
db 99 ; Pokedex number
db 0
db 27 ; level
@@ -3998,7 +3998,7 @@ HorseaCard: ; 320b1 (c:60b1)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx HorseaKind ; kind
+ tx DragonName ; category
db 116 ; Pokedex number
db 0
db 19 ; level
@@ -4049,7 +4049,7 @@ SeadraCard: ; 320f2 (c:60f2)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx HorseaKind ; kind
+ tx DragonName ; category
db 117 ; Pokedex number
db 0
db 23 ; level
@@ -4100,7 +4100,7 @@ GoldeenCard: ; 32133 (c:6133)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx GoldeenKind ; kind
+ tx GoldfishName ; category
db 118 ; Pokedex number
db 0
db 12 ; level
@@ -4151,7 +4151,7 @@ SeakingCard: ; 32174 (c:6174)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx GoldeenKind ; kind
+ tx GoldfishName ; category
db 119 ; Pokedex number
db 0
db 28 ; level
@@ -4202,7 +4202,7 @@ StaryuCard: ; 321b5 (c:61b5)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx StaryuKind ; kind
+ tx StarshapeName ; category
db 120 ; Pokedex number
db 0
db 15 ; level
@@ -4253,7 +4253,7 @@ StarmieCard: ; 321f6 (c:61f6)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx StarmieKind ; kind
+ tx MysteriousName ; category
db 121 ; Pokedex number
db 0
db 28 ; level
@@ -4304,7 +4304,7 @@ MagikarpCard: ; 32237 (c:6237)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx MagikarpKind ; kind
+ tx FishName ; category
db 129 ; Pokedex number
db 0
db 8 ; level
@@ -4355,7 +4355,7 @@ GyaradosCard: ; 32278 (c:6278)
db 3 ; retreat cost
db WR_GRASS ; weakness
db WR_FIGHTING ; resistance
- tx GyaradosKind ; kind
+ tx AtrociousName ; category
db 130 ; Pokedex number
db 0
db 41 ; level
@@ -4406,7 +4406,7 @@ LaprasCard: ; 322b9 (c:62b9)
db 2 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx LaprasKind ; kind
+ tx TransportName ; category
db 131 ; Pokedex number
db 0
db 31 ; level
@@ -4457,7 +4457,7 @@ Vaporeon1Card: ; 322fa (c:62fa)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx VaporeonKind ; kind
+ tx BubbleJetName ; category
db 134 ; Pokedex number
db 0
db 29 ; level
@@ -4508,7 +4508,7 @@ Vaporeon2Card: ; 3233b (c:633b)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db NONE ; resistance
- tx VaporeonKind ; kind
+ tx BubbleJetName ; category
db 134 ; Pokedex number
db 0
db 42 ; level
@@ -4559,7 +4559,7 @@ OmanyteCard: ; 3237c (c:637c)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx OmanyteKind ; kind
+ tx SpiralName ; category
db 138 ; Pokedex number
db 0
db 19 ; level
@@ -4610,7 +4610,7 @@ OmastarCard: ; 323bd (c:63bd)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx OmanyteKind ; kind
+ tx SpiralName ; category
db 139 ; Pokedex number
db 0
db 32 ; level
@@ -4661,7 +4661,7 @@ Articuno1Card: ; 323fe (c:63fe)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx ArticunoKind ; kind
+ tx FreezeName ; category
db 144 ; Pokedex number
db 0
db 35 ; level
@@ -4712,7 +4712,7 @@ Articuno2Card: ; 3243f (c:643f)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx ArticunoKind ; kind
+ tx FreezeName ; category
db 144 ; Pokedex number
db 0
db 37 ; level
@@ -4763,7 +4763,7 @@ Pikachu1Card: ; 32480 (c:6480)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 12 ; level
@@ -4814,7 +4814,7 @@ Pikachu2Card: ; 324c1 (c:64c1)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 14 ; level
@@ -4865,7 +4865,7 @@ Pikachu3Card: ; 32502 (c:6502)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 16 ; level
@@ -4916,7 +4916,7 @@ Pikachu4Card: ; 32543 (c:6543)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 16 ; level
@@ -4967,7 +4967,7 @@ FlyingPikachuCard: ; 32584 (c:6584)
db 1 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 12 ; level
@@ -5018,7 +5018,7 @@ SurfingPikachu1Card: ; 325c5 (c:65c5)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 13 ; level
@@ -5069,7 +5069,7 @@ SurfingPikachu2Card: ; 32606 (c:6606)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 25 ; Pokedex number
db 0
db 13 ; level
@@ -5120,7 +5120,7 @@ Raichu1Card: ; 32647 (c:6647)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 26 ; Pokedex number
db 0
db 40 ; level
@@ -5171,7 +5171,7 @@ Raichu2Card: ; 32688 (c:6688)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 26 ; Pokedex number
db 0
db 45 ; level
@@ -5222,7 +5222,7 @@ Magnemite1Card: ; 326c9 (c:66c9)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx MagnemiteKind ; kind
+ tx MagnetName ; category
db 81 ; Pokedex number
db 0
db 13 ; level
@@ -5273,7 +5273,7 @@ Magnemite2Card: ; 3270a (c:670a)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx MagnemiteKind ; kind
+ tx MagnetName ; category
db 81 ; Pokedex number
db 0
db 15 ; level
@@ -5324,7 +5324,7 @@ Magneton1Card: ; 3274b (c:674b)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx MagnemiteKind ; kind
+ tx MagnetName ; category
db 82 ; Pokedex number
db 0
db 28 ; level
@@ -5375,7 +5375,7 @@ Magneton2Card: ; 3278c (c:678c)
db 2 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx MagnemiteKind ; kind
+ tx MagnetName ; category
db 82 ; Pokedex number
db 0
db 35 ; level
@@ -5426,7 +5426,7 @@ VoltorbCard: ; 327cd (c:67cd)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx VoltorbKind ; kind
+ tx BallName ; category
db 100 ; Pokedex number
db 0
db 10 ; level
@@ -5477,7 +5477,7 @@ Electrode1Card: ; 3280e (c:680e)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx VoltorbKind ; kind
+ tx BallName ; category
db 101 ; Pokedex number
db 0
db 35 ; level
@@ -5528,7 +5528,7 @@ Electrode2Card: ; 3284f (c:684f)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx VoltorbKind ; kind
+ tx BallName ; category
db 101 ; Pokedex number
db 0
db 42 ; level
@@ -5579,7 +5579,7 @@ Electabuzz1Card: ; 32890 (c:6890)
db 2 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx ElectabuzzKind ; kind
+ tx ElectricName ; category
db 125 ; Pokedex number
db 0
db 20 ; level
@@ -5630,7 +5630,7 @@ Electabuzz2Card: ; 328d1 (c:68d1)
db 2 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx ElectabuzzKind ; kind
+ tx ElectricName ; category
db 125 ; Pokedex number
db 0
db 35 ; level
@@ -5681,7 +5681,7 @@ Jolteon1Card: ; 32912 (c:6912)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx JolteonKind ; kind
+ tx LightningName ; category
db 135 ; Pokedex number
db 0
db 24 ; level
@@ -5732,7 +5732,7 @@ Jolteon2Card: ; 32953 (c:6953)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db NONE ; resistance
- tx JolteonKind ; kind
+ tx LightningName ; category
db 135 ; Pokedex number
db 0
db 29 ; level
@@ -5783,7 +5783,7 @@ Zapdos1Card: ; 32994 (c:6994)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx ElectabuzzKind ; kind
+ tx ElectricName ; category
db 145 ; Pokedex number
db 0
db 40 ; level
@@ -5834,7 +5834,7 @@ Zapdos2Card: ; 329d5 (c:69d5)
db 3 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx ElectabuzzKind ; kind
+ tx ElectricName ; category
db 145 ; Pokedex number
db 0
db 64 ; level
@@ -5885,7 +5885,7 @@ Zapdos3Card: ; 32a16 (c:6a16)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx ElectabuzzKind ; kind
+ tx ElectricName ; category
db 145 ; Pokedex number
db 0
db 68 ; level
@@ -5936,7 +5936,7 @@ SandshrewCard: ; 32a57 (c:6a57)
db 1 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 27 ; Pokedex number
db 0
db 12 ; level
@@ -5987,7 +5987,7 @@ SandslashCard: ; 32a98 (c:6a98)
db 1 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx PikachuKind ; kind
+ tx MouseName ; category
db 28 ; Pokedex number
db 0
db 33 ; level
@@ -6038,7 +6038,7 @@ DiglettCard: ; 32ad9 (c:6ad9)
db 0 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx DiglettKind ; kind
+ tx MoleName ; category
db 50 ; Pokedex number
db 0
db 8 ; level
@@ -6089,7 +6089,7 @@ DugtrioCard: ; 32b1a (c:6b1a)
db 2 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx DiglettKind ; kind
+ tx MoleName ; category
db 51 ; Pokedex number
db 0
db 36 ; level
@@ -6140,7 +6140,7 @@ MankeyCard: ; 32b5b (c:6b5b)
db 0 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MankeyKind ; kind
+ tx PigMonkeyName ; category
db 56 ; Pokedex number
db 0
db 7 ; level
@@ -6191,7 +6191,7 @@ PrimeapeCard: ; 32b9c (c:6b9c)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MankeyKind ; kind
+ tx PigMonkeyName ; category
db 57 ; Pokedex number
db 0
db 35 ; level
@@ -6242,7 +6242,7 @@ MachopCard: ; 32bdd (c:6bdd)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MachopKindOrSuperpowerName ; kind
+ tx SuperpowerName ; category
db 66 ; Pokedex number
db 0
db 20 ; level
@@ -6293,7 +6293,7 @@ MachokeCard: ; 32c1e (c:6c1e)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MachopKindOrSuperpowerName ; kind
+ tx SuperpowerName ; category
db 67 ; Pokedex number
db 0
db 40 ; level
@@ -6344,7 +6344,7 @@ MachampCard: ; 32c5f (c:6c5f)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MachopKindOrSuperpowerName ; kind
+ tx SuperpowerName ; category
db 68 ; Pokedex number
db 0
db 67 ; level
@@ -6395,7 +6395,7 @@ GeodudeCard: ; 32ca0 (c:6ca0)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx GeodudeKind ; kind
+ tx RockName ; category
db 74 ; Pokedex number
db 0
db 16 ; level
@@ -6446,7 +6446,7 @@ GravelerCard: ; 32ce1 (c:6ce1)
db 2 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx GeodudeKind ; kind
+ tx RockName ; category
db 75 ; Pokedex number
db 0
db 29 ; level
@@ -6497,7 +6497,7 @@ GolemCard: ; 32d22 (c:6d22)
db 4 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx GolemKind ; kind
+ tx MegatonName ; category
db 76 ; Pokedex number
db 0
db 36 ; level
@@ -6548,7 +6548,7 @@ OnixCard: ; 32d63 (c:6d63)
db 3 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx OnixKind ; kind
+ tx RockSnakeName ; category
db 95 ; Pokedex number
db 0
db 12 ; level
@@ -6599,7 +6599,7 @@ CuboneCard: ; 32da4 (c:6da4)
db 1 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx CuboneKind ; kind
+ tx LonelyName ; category
db 104 ; Pokedex number
db 0
db 13 ; level
@@ -6650,7 +6650,7 @@ Marowak1Card: ; 32de5 (c:6de5)
db 1 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx MarowakKind ; kind
+ tx BonekeeperName ; category
db 105 ; Pokedex number
db 0
db 26 ; level
@@ -6701,7 +6701,7 @@ Marowak2Card: ; 32e26 (c:6e26)
db 2 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx MarowakKind ; kind
+ tx BonekeeperName ; category
db 105 ; Pokedex number
db 0
db 32 ; level
@@ -6752,7 +6752,7 @@ HitmonleeCard: ; 32e67 (c:6e67)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx HitmonleeKind ; kind
+ tx KickingName ; category
db 106 ; Pokedex number
db 0
db 30 ; level
@@ -6803,7 +6803,7 @@ HitmonchanCard: ; 32ea8 (c:6ea8)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx HitmonchanKind ; kind
+ tx PunchingName ; category
db 107 ; Pokedex number
db 0
db 33 ; level
@@ -6854,7 +6854,7 @@ RhyhornCard: ; 32ee9 (c:6ee9)
db 3 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx RhyhornKind ; kind
+ tx SpikeName ; category
db 111 ; Pokedex number
db 0
db 18 ; level
@@ -6905,7 +6905,7 @@ RhydonCard: ; 32f2a (c:6f2a)
db 3 ; retreat cost
db WR_GRASS ; weakness
db WR_LIGHTNING ; resistance
- tx NidoqueenKind ; kind
+ tx DrillName ; category
db 112 ; Pokedex number
db 0
db 48 ; level
@@ -6956,7 +6956,7 @@ KabutoCard: ; 32f6b (c:6f6b)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx BlastoiseKind ; kind
+ tx ShellfishName ; category
db 140 ; Pokedex number
db 0
db 9 ; level
@@ -7007,7 +7007,7 @@ KabutopsCard: ; 32fac (c:6fac)
db 1 ; retreat cost
db WR_GRASS ; weakness
db NONE ; resistance
- tx BlastoiseKind ; kind
+ tx ShellfishName ; category
db 141 ; Pokedex number
db 0
db 30 ; level
@@ -7058,7 +7058,7 @@ AerodactylCard: ; 32fed (c:6fed)
db 2 ; retreat cost
db WR_GRASS ; weakness
db WR_FIGHTING ; resistance
- tx AerodactylKind ; kind
+ tx FossilName ; category
db 142 ; Pokedex number
db 0
db 28 ; level
@@ -7109,7 +7109,7 @@ AbraCard: ; 3302e (c:702e)
db 0 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx AbraKind ; kind
+ tx PsiName ; category
db 63 ; Pokedex number
db 0
db 10 ; level
@@ -7160,7 +7160,7 @@ KadabraCard: ; 3306f (c:706f)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx AbraKind ; kind
+ tx PsiName ; category
db 64 ; Pokedex number
db 0
db 38 ; level
@@ -7211,7 +7211,7 @@ AlakazamCard: ; 330b0 (c:70b0)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx AbraKind ; kind
+ tx PsiName ; category
db 65 ; Pokedex number
db 0
db 42 ; level
@@ -7262,7 +7262,7 @@ Slowpoke1Card: ; 330f1 (c:70f1)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx SlowpokeKind ; kind
+ tx DopeyName ; category
db 79 ; Pokedex number
db 0
db 9 ; level
@@ -7313,7 +7313,7 @@ Slowpoke2Card: ; 33132 (c:7132)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx SlowpokeKind ; kind
+ tx DopeyName ; category
db 79 ; Pokedex number
db 0
db 18 ; level
@@ -7364,7 +7364,7 @@ SlowbroCard: ; 33173 (c:7173)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx SlowbroKind ; kind
+ tx HermitcrabName ; category
db 80 ; Pokedex number
db 0
db 26 ; level
@@ -7415,7 +7415,7 @@ Gastly1Card: ; 331b4 (c:71b4)
db 0 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx GastlyKind ; kind
+ tx GasName ; category
db 92 ; Pokedex number
db 0
db 8 ; level
@@ -7466,7 +7466,7 @@ Gastly2Card: ; 331f5 (c:71f5)
db 0 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx GastlyKind ; kind
+ tx GasName ; category
db 92 ; Pokedex number
db 0
db 17 ; level
@@ -7517,7 +7517,7 @@ Haunter1Card: ; 33236 (c:7236)
db 0 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx GastlyKind ; kind
+ tx GasName ; category
db 93 ; Pokedex number
db 0
db 17 ; level
@@ -7539,7 +7539,7 @@ Haunter2Card: ; 33277 (c:7277)
; move 1
energy PSYCHIC, 1 ; energies
- tx DrowzeeKindOrHypnosisName ; name
+ tx HypnosisName ; name
tx InflictSleepDescription ; description
dw NONE ; description (cont)
db 0 ; damage
@@ -7568,7 +7568,7 @@ Haunter2Card: ; 33277 (c:7277)
db 1 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx GastlyKind ; kind
+ tx GasName ; category
db 93 ; Pokedex number
db 0
db 22 ; level
@@ -7619,7 +7619,7 @@ GengarCard: ; 332b8 (c:72b8)
db 1 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx GengarKind ; kind
+ tx ShadowName ; category
db 94 ; Pokedex number
db 0
db 38 ; level
@@ -7670,7 +7670,7 @@ DrowzeeCard: ; 332f9 (c:72f9)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx DrowzeeKindOrHypnosisName ; kind
+ tx HypnosisName ; category
db 96 ; Pokedex number
db 0
db 12 ; level
@@ -7721,7 +7721,7 @@ HypnoCard: ; 3333a (c:733a)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx DrowzeeKindOrHypnosisName ; kind
+ tx HypnosisName ; category
db 97 ; Pokedex number
db 0
db 36 ; level
@@ -7772,7 +7772,7 @@ MrMimeCard: ; 3337b (c:737b)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MrMimeKindOrBarrierName ; kind
+ tx BarrierName ; category
db 122 ; Pokedex number
db 0
db 28 ; level
@@ -7823,7 +7823,7 @@ JynxCard: ; 333bc (c:73bc)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx JynxKind ; kind
+ tx HumanShapeName ; category
db 124 ; Pokedex number
db 0
db 23 ; level
@@ -7859,12 +7859,12 @@ Mewtwo1Card: ; 333fd (c:73fd)
; move 2
energy PSYCHIC, 2 ; energies
- tx MrMimeKindOrBarrierName ; name
+ tx BarrierName ; name
tx BarrierDescription ; description
dw NONE ; description (cont)
db 0 ; damage
db RESIDUAL ; category
- dw MewtwoMrMimeKindAndBarrierEffectCommands ; effect commands
+ dw MewtwoBarrierEffectCommands ; effect commands
db NONE ; flags 1
db NULLIFY_OR_WEAKEN_ATTACK + DISCARD_ENERGY ; flags 2
db NONE ; flags 3
@@ -7874,7 +7874,7 @@ Mewtwo1Card: ; 333fd (c:73fd)
db 3 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MewtwoKind ; kind
+ tx GeneticName ; category
db 150 ; Pokedex number
db 0
db 53 ; level
@@ -7925,7 +7925,7 @@ Mewtwo2Card: ; 3343e (c:743e)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MewtwoKind ; kind
+ tx GeneticName ; category
db 150 ; Pokedex number
db 0
db 60 ; level
@@ -7976,7 +7976,7 @@ Mewtwo3Card: ; 3347f (c:747f)
db 2 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MewtwoKind ; kind
+ tx GeneticName ; category
db 150 ; Pokedex number
db 0
db 60 ; level
@@ -8027,7 +8027,7 @@ Mew1Card: ; 334c0 (c:74c0)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MewKind ; kind
+ tx NewSpeciesName ; category
db 151 ; Pokedex number
db 0
db 8 ; level
@@ -8078,7 +8078,7 @@ Mew2Card: ; 33501 (c:7501)
db 0 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MewKind ; kind
+ tx NewSpeciesName ; category
db 151 ; Pokedex number
db 0
db 15 ; level
@@ -8129,7 +8129,7 @@ Mew3Card: ; 33542 (c:7542)
db 1 ; retreat cost
db WR_PSYCHIC ; weakness
db NONE ; resistance
- tx MewKind ; kind
+ tx NewSpeciesName ; category
db 151 ; Pokedex number
db 0
db 23 ; level
@@ -8180,7 +8180,7 @@ PidgeyCard: ; 33583 (c:7583)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx PidgeyKind ; kind
+ tx TinyBirdName ; category
db 16 ; Pokedex number
db 0
db 8 ; level
@@ -8231,7 +8231,7 @@ PidgeottoCard: ; 335c4 (c:75c4)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx PidgeottoKind ; kind
+ tx BirdName ; category
db 17 ; Pokedex number
db 0
db 36 ; level
@@ -8282,7 +8282,7 @@ Pidgeot1Card: ; 33605 (c:7605)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx PidgeottoKind ; kind
+ tx BirdName ; category
db 18 ; Pokedex number
db 0
db 38 ; level
@@ -8333,7 +8333,7 @@ Pidgeot2Card: ; 33646 (c:7646)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx PidgeottoKind ; kind
+ tx BirdName ; category
db 18 ; Pokedex number
db 0
db 40 ; level
@@ -8384,7 +8384,7 @@ RattataCard: ; 33687 (c:7687)
db 0 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx RattataKind ; kind
+ tx RatName ; category
db 19 ; Pokedex number
db 0
db 9 ; level
@@ -8435,7 +8435,7 @@ RaticateCard: ; 336c8 (c:76c8)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx RattataKind ; kind
+ tx RatName ; category
db 20 ; Pokedex number
db 0
db 41 ; level
@@ -8486,7 +8486,7 @@ SpearowCard: ; 33709 (c:7709)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx PidgeyKind ; kind
+ tx TinyBirdName ; category
db 21 ; Pokedex number
db 0
db 13 ; level
@@ -8537,7 +8537,7 @@ FearowCard: ; 3374a (c:774a)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx FearowKind ; kind
+ tx BeakName ; category
db 22 ; Pokedex number
db 0
db 27 ; level
@@ -8588,7 +8588,7 @@ ClefairyCard: ; 3378b (c:778b)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx ClefairyKind ; kind
+ tx FairyName ; category
db 35 ; Pokedex number
db 0
db 14 ; level
@@ -8639,7 +8639,7 @@ ClefableCard: ; 337cc (c:77cc)
db 2 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx ClefairyKind ; kind
+ tx FairyName ; category
db 36 ; Pokedex number
db 0
db 34 ; level
@@ -8690,7 +8690,7 @@ Jigglypuff1Card: ; 3380d (c:780d)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx JigglypuffKind ; kind
+ tx BalloonName ; category
db 39 ; Pokedex number
db 0
db 12 ; level
@@ -8741,7 +8741,7 @@ Jigglypuff2Card: ; 3384e (c:784e)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx JigglypuffKind ; kind
+ tx BalloonName ; category
db 39 ; Pokedex number
db 0
db 13 ; level
@@ -8792,7 +8792,7 @@ Jigglypuff3Card: ; 3388f (c:788f)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx JigglypuffKind ; kind
+ tx BalloonName ; category
db 39 ; Pokedex number
db 0
db 14 ; level
@@ -8843,7 +8843,7 @@ WigglytuffCard: ; 338d0 (c:78d0)
db 2 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx JigglypuffKind ; kind
+ tx BalloonName ; category
db 40 ; Pokedex number
db 0
db 36 ; level
@@ -8894,7 +8894,7 @@ Meowth1Card: ; 33911 (c:7911)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx MeowthKind ; kind
+ tx ScratchCatName ; category
db 52 ; Pokedex number
db 0
db 14 ; level
@@ -8945,7 +8945,7 @@ Meowth2Card: ; 33952 (c:7952)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx MeowthKind ; kind
+ tx ScratchCatName ; category
db 52 ; Pokedex number
db 0
db 15 ; level
@@ -8996,7 +8996,7 @@ PersianCard: ; 33993 (c:7993)
db 0 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx PersianKind ; kind
+ tx ClassyCatName ; category
db 53 ; Pokedex number
db 0
db 25 ; level
@@ -9047,7 +9047,7 @@ FarfetchdCard: ; 339d4 (c:79d4)
db 1 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx FarfetchdKind ; kind
+ tx WildDuckName ; category
db 83 ; Pokedex number
db 0
db 20 ; level
@@ -9098,7 +9098,7 @@ DoduoCard: ; 33a15 (c:7a15)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx DoduoKind ; kind
+ tx TwinBirdName ; category
db 84 ; Pokedex number
db 0
db 10 ; level
@@ -9149,7 +9149,7 @@ DodrioCard: ; 33a56 (c:7a56)
db 0 ; retreat cost
db WR_LIGHTNING ; weakness
db WR_FIGHTING ; resistance
- tx DodrioKind ; kind
+ tx TriplebirdName ; category
db 85 ; Pokedex number
db 0
db 28 ; level
@@ -9200,7 +9200,7 @@ LickitungCard: ; 33a97 (c:7a97)
db 3 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx LickitungKind ; kind
+ tx LickingName ; category
db 108 ; Pokedex number
db 0
db 26 ; level
@@ -9251,7 +9251,7 @@ ChanseyCard: ; 33ad8 (c:7ad8)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx ExeggcuteKind ; kind
+ tx EggName ; category
db 113 ; Pokedex number
db 0
db 55 ; level
@@ -9302,7 +9302,7 @@ KangaskhanCard: ; 33b19 (c:7b19)
db 3 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx KangaskhanKind ; kind
+ tx ParentName ; category
db 115 ; Pokedex number
db 0
db 40 ; level
@@ -9353,7 +9353,7 @@ TaurosCard: ; 33b5a (c:7b5a)
db 2 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx TaurosKind ; kind
+ tx WildBullName ; category
db 128 ; Pokedex number
db 0
db 32 ; level
@@ -9404,7 +9404,7 @@ DittoCard: ; 33b9b (c:7b9b)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx DittoKind ; kind
+ tx TransformName ; category
db 132 ; Pokedex number
db 0
db 19 ; level
@@ -9455,7 +9455,7 @@ EeveeCard: ; 33bdc (c:7bdc)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx EeveeKind ; kind
+ tx EvolutionName ; category
db 133 ; Pokedex number
db 0
db 12 ; level
@@ -9506,7 +9506,7 @@ PorygonCard: ; 33c1d (c:7c1d)
db 1 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx PorygonKind ; kind
+ tx VirtualName ; category
db 137 ; Pokedex number
db 0
db 12 ; level
@@ -9557,7 +9557,7 @@ SnorlaxCard: ; 33c5e (c:7c5e)
db 4 ; retreat cost
db WR_FIGHTING ; weakness
db WR_PSYCHIC ; resistance
- tx SnorlaxKind ; kind
+ tx SleepingName ; category
db 143 ; Pokedex number
db 0
db 20 ; level
@@ -9608,7 +9608,7 @@ DratiniCard: ; 33c9f (c:7c9f)
db 1 ; retreat cost
db NONE ; weakness
db WR_PSYCHIC ; resistance
- tx HorseaKind ; kind
+ tx DragonName ; category
db 147 ; Pokedex number
db 0
db 10 ; level
@@ -9659,7 +9659,7 @@ DragonairCard: ; 33ce0 (c:7ce0)
db 2 ; retreat cost
db NONE ; weakness
db WR_PSYCHIC ; resistance
- tx HorseaKind ; kind
+ tx DragonName ; category
db 148 ; Pokedex number
db 0
db 33 ; level
@@ -9710,7 +9710,7 @@ Dragonite1Card: ; 33d21 (c:7d21)
db 2 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx HorseaKind ; kind
+ tx DragonName ; category
db 149 ; Pokedex number
db 0
db 41 ; level
@@ -9761,7 +9761,7 @@ Dragonite2Card: ; 33d62 (c:7d62)
db 1 ; retreat cost
db NONE ; weakness
db WR_FIGHTING ; resistance
- tx HorseaKind ; kind
+ tx DragonName ; category
db 149 ; Pokedex number
db 0
db 45 ; level
diff --git a/src/data/effect_commands.asm b/src/data/effect_commands.asm
index 1df3d0d..6dc74ee 100644
--- a/src/data/effect_commands.asm
+++ b/src/data/effect_commands.asm
@@ -797,7 +797,7 @@ MewtwoPsychicEffectCommands:
dbw $09, $5d7b
db $00
-MewtwoMrMimeKindAndBarrierEffectCommands:
+MewtwoBarrierEffectCommands:
dbw $01, $5d8e
dbw $02, $5d9c
dbw $03, $5dbf
diff --git a/src/engine/bank01.asm b/src/engine/bank01.asm
index 6a21178..42719fe 100644
--- a/src/engine/bank01.asm
+++ b/src/engine/bank01.asm
@@ -12,7 +12,7 @@ GameLoop: ; 4000 (1:4000)
ld a, [sa009]
ld [wccf2], a
call DisableSRAM
- ld a, $1
+ ld a, 1
ld [wUppercaseFlag], a
ei
farcall CommentedOut_1a6cc
@@ -37,7 +37,7 @@ GameLoop: ; 4000 (1:4000)
Func_4050: ; 4050 (1:4050)
farcall Func_1996e
- ld a, $1
+ ld a, 1
ld [wUppercaseFlag], a
ret
@@ -76,7 +76,7 @@ ContinueDuel: ; 407a (1:407a)
xor a
ld [wDuelFinished], a
call DuelMainInterface
- jp StartDuel.begin_turn
+ jp MainDuelLoop.begin_turn
; 0x4097
FailedToContinueDuel: ; 4097 (1:4097)
@@ -125,9 +125,10 @@ StartDuel: ; 409f (1:409f)
call PlaySong
call Func_4b60
ret c
+; fallthrough
; the loop returns here after every turn switch
-.main_duel_loop ; 40ee (1:40ee)
+MainDuelLoop ; 40ee (1:40ee)
xor a
ld [wCurrentDuelMenuItem], a
call UpdateSubstatusConditions_StartOfTurn
@@ -154,7 +155,7 @@ StartDuel: ; 409f (1:409f)
.next_turn
call SwapTurn
- jr .main_duel_loop
+ jr MainDuelLoop
.practice_duel
ld a, [wIsPracticeDuel]
@@ -257,7 +258,7 @@ StartDuel: ; 409f (1:409f)
ld a, PLAYER_TURN
ldh [hWhoseTurn], a
call Func_4b60
- jp .main_duel_loop
+ jp MainDuelLoop
.link_duel
call Func_0f58
@@ -271,7 +272,7 @@ StartDuel: ; 409f (1:409f)
ld a, h
ldh [hWhoseTurn], a
call Func_4b60
- jp nc, .main_duel_loop
+ jp nc, MainDuelLoop
ret
; 0x420b
@@ -365,9 +366,8 @@ PrintDuelMenu: ; 4295 (1:4295)
ret nz
ld a, [wCurrentDuelMenuItem]
call SetMenuItem
-; fallthrough
-HandleDuelMenuInputAndShortcuts:
+.handle_input
call DoFrame
ldh a, [hButtonsHeld]
and B_BUTTON
@@ -393,11 +393,11 @@ HandleDuelMenuInputAndShortcuts:
jp nz, DuelMenuShortcut_BothActivePokemon
ld a, [wcbe7]
or a
- jr nz, HandleDuelMenuInputAndShortcuts
+ jr nz, .handle_input
call HandleDuelMenuInput
ld a, e
ld [wCurrentDuelMenuItem], a
- jr nc, HandleDuelMenuInputAndShortcuts
+ jr nc, .handle_input
ldh a, [hCurrentMenuItem]
ld hl, DuelMenuFunctionTable
jp JumpToFunctionInTable
@@ -608,7 +608,7 @@ OpenPlayerHandScreen: ; 4436 (1:4436)
.handle_input
call Func_55f0
push af
- ld a, [wcbdf]
+ ld a, [wSortCardListByID]
or a
call nz, SortHandCardsByID
pop af
@@ -646,7 +646,7 @@ UseEnergyCard: ; 4477 (1:4477)
call OpenPlayAreaScreenForSelection ; choose card to play energy card on
jp c, DuelMainInterface ; exit if no card was chosen
.play_energy_set_played
- ld a, $1
+ ld a, 1
ld [wAlreadyPlayedEnergy], a
.play_energy
ldh a, [hTempPlayAreaLocationOffset_ff9d]
@@ -1858,22 +1858,22 @@ DrawDuelHUD: ; 5093 (1:5093)
push de
pop bc
- ; print the Pkmn icon along with the no. of play area Pokemon
- ld a, LOW("<PKMN_ICON>")
+ ; print the Pokemon icon along with the no. of play area Pokemon
+ ld a, SYM_POKEMON
call WriteByteToBGMap0
inc b
ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
call GetTurnDuelistVariable
- add LOW("<0>") - 1
+ add SYM_0 - 1
call WriteByteToBGMap0
inc b
; print the Prize icon along with the no. of prizes yet to draw
- ld a, LOW("<PRIZE_ICON>")
+ ld a, SYM_PRIZE
call WriteByteToBGMap0
inc b
call CountPrizes
- add LOW("<0>")
+ add SYM_0
call WriteByteToBGMap0
; print the arena Pokemon card name and level text
@@ -1956,11 +1956,11 @@ DrawDuelHUD: ; 5093 (1:5093)
call GetTurnDuelistVariable
or a
jr z, .check_defender
- ld a, LOW("<PLUSPOWER>")
+ ld a, SYM_PLUSPOWER
call WriteByteToBGMap0
inc b
ld a, [hl] ; number of attached Pluspower
- add LOW("<0>")
+ add SYM_0
call WriteByteToBGMap0
dec b
.check_defender
@@ -1969,11 +1969,11 @@ DrawDuelHUD: ; 5093 (1:5093)
or a
jr z, .done
inc c
- ld a, LOW("<DEFENDER>")
+ ld a, SYM_DEFENDER
call WriteByteToBGMap0
inc b
ld a, [hl] ; number of attached Defender
- add LOW("<0>")
+ add SYM_0
call WriteByteToBGMap0
.done
ret
@@ -1996,10 +1996,10 @@ DrawDuelHorizontalSeparator: ; 516f (1:516f)
DuelEAndHPTileData: ; 5188 (1:5188)
; x, y, tiles[], 0
- db 1, 1, LOW("<E>"), 0
- db 1, 2, LOW("<HP>"), 0
- db 9, 8, LOW("<E>"), 0
- db 9, 9, LOW("<HP>"), 0
+ db 1, 1, SYM_E, 0
+ db 1, 2, SYM_HP, 0
+ db 9, 8, SYM_E, 0
+ db 9, 9, SYM_HP, 0
db $ff
; 0x5199
@@ -2221,7 +2221,7 @@ DrawCardListScreenLayout: ; 559a (1:559a)
ld hl, wSelectedDuelSubMenuItem
ld [hli], a
ld [hl], a
- ld [wcbdf], a
+ ld [wSortCardListByID], a
ld hl, wcbd8
ld [hli], a
ld [hl], a
@@ -2229,13 +2229,13 @@ DrawCardListScreenLayout: ; 559a (1:559a)
ld a, START
ld [wcbd6], a
ld hl, wCardListInfoBoxText
- ld [hl], LOW(PleaseSelectHandText_)
+ ldtx [hl], PleaseSelectHandText, & $ff
inc hl
- ld [hl], HIGH(PleaseSelectHandText_)
+ ldtx [hl], PleaseSelectHandText, >> 8
inc hl ; wCardListHeaderText
- ld [hl], LOW(DuelistHandText_)
+ ldtx [hl], DuelistHandText, & $ff
inc hl
- ld [hl], HIGH(DuelistHandText_)
+ ldtx [hl], DuelistHandText, >> 8
.draw
call ZeroObjectPositionsAndToggleOAMCopy
call EmptyScreen
@@ -2300,7 +2300,7 @@ Func_55f0: ; 55f0 (1:55f0)
or a
ret
.asm_563b
- ld a, [wcbdf]
+ ld a, [wSortCardListByID]
or a
jr nz, .asm_560b
call SortCardsInDuelTempListByID
@@ -2308,8 +2308,8 @@ Func_55f0: ; 55f0 (1:55f0)
ld hl, wSelectedDuelSubMenuItem
ld [hli], a
ld [hl], a
- ld a, $01
- ld [wcbdf], a
+ ld a, 1
+ ld [wSortCardListByID], a
call EraseCursor
jr .asm_55f6
.asm_5654
@@ -2386,7 +2386,7 @@ Func_56a0: ; 56a0 (1:56a0)
CardListParameters: ; 5710 (1;5710)
db 1, 3 ; cursor x, cursor y
db 4 ; item x
- db $0e
+ db 14 ; maximum length, in tiles, occupied by the name and level string of each card in the list
db 5 ; number of items selectable without scrolling
db $0f ; cursor tile number
db $00 ; tile behind cursor
@@ -3190,7 +3190,7 @@ CheckPrintPoisoned: ; 63bb (1:63bb)
and POISONED
jr z, .print
.poison
- ld a, LOW("<POISONED>")
+ ld a, SYM_POISONED
.print
call WriteByteToBGMap0
pop af
@@ -3224,8 +3224,8 @@ CheckPrintCnfSlpPrz: ; 63ce (1:63ce)
ret
.status_symbols
- ; NO_STATUS, CONFUSED, ASLEEP, PARALYZED
- db LOW("< >"), LOW("<CONFUSED>"), LOW("<ASLEEP>"), LOW("<PARALYZED>")
+ ; NO_STATUS, CONFUSED, ASLEEP, PARALYZED
+ db SYM_SPACE, SYM_CONFUSED, SYM_ASLEEP, SYM_PARALYZED
; 0x63e6
; print the symbols of the attached energies of a turn holder's play area card
@@ -3246,7 +3246,7 @@ PrintPlayAreaCardAttachedEnergies: ; 63e6 (1:63e6)
jr nz, .empty_loop
pop hl
ld de, wAttachedEnergies
- lb bc, LOW("<FIRE>"), NUM_TYPES - 1
+ lb bc, SYM_FIRE, NUM_TYPES - 1
.next_color
ld a, [de] ; energy count of current color
inc de
@@ -3264,7 +3264,7 @@ PrintPlayAreaCardAttachedEnergies: ; 63e6 (1:63e6)
ld a, [wTotalAttachedEnergies]
cp 9
jr c, .place_tiles
- ld a, LOW("<+>")
+ ld a, SYM_PLUS
ld [wDefaultText + 7], a
.place_tiles
pop bc
@@ -3280,14 +3280,14 @@ PrintPlayAreaCardAttachedEnergies: ; 63e6 (1:63e6)
; input d, e: max. HP, current HP
DrawHPBar: ; 6614 (1:6614)
ld a, MAX_HP
- ld c, LOW("< >")
+ ld c, SYM_SPACE
call .fill_hp_bar ; empty bar
ld a, d
- ld c, LOW("<🌕>")
+ ld c, SYM_HP_OK
call .fill_hp_bar ; fill (max. HP) with HP counters
ld a, d
sub e
- ld c, LOW("<🌑>")
+ ld c, SYM_HP_NOK
; fill (max. HP - current HP) with damaged HP counters
.fill_hp_bar
or a
@@ -3428,7 +3428,7 @@ AIUseEnergyCard: ; 69a5 (1:69a5)
call LoadCardDataToBuffer1_FromDeckIndex
call DrawLargePictureOfCard
call $68e4
- ld a, $1
+ ld a, 1
ld [wAlreadyPlayedEnergy], a
call DrawDuelMainScene
ret
@@ -3762,7 +3762,7 @@ _TossCoin: ; 71ad (1:71ad)
INCROM $72ff, $7354
BuildVersion: ; 7354 (1:7354)
- db "VER 12/20 09:36",TX_END
+ db "VER 12/20 09:36", TX_END
INCROM $7364, $7571
@@ -3770,8 +3770,8 @@ Func_7571: ; 7571 (1:7571)
INCROM $7571, $7576
Func_7576: ; 7576 (1:7576)
- farcall $6, $591f
- ret
+ farcall $6, $591f
+ ret
; 0x757b
INCROM $757b, $758f
diff --git a/src/engine/bank04.asm b/src/engine/bank04.asm
index 9bf84f3..3656e52 100644
--- a/src/engine/bank04.asm
+++ b/src/engine/bank04.asm
@@ -10,7 +10,7 @@ Func_10000: ; 10000 (4:4000)
ldh [hSCX], a
ldh [hSCY], a
ld a, [wLCDC]
- bit 7, a
+ bit LCDC_ON, a
jr nz, .asm_10025
xor a
ld [rSCX], a
diff --git a/src/engine/bank06.asm b/src/engine/bank06.asm
index 1a85a24..7c19b74 100644
--- a/src/engine/bank06.asm
+++ b/src/engine/bank06.asm
@@ -58,14 +58,14 @@ _CopyCardNameAndLevel: ; 18000 (6:4000)
jr z, .done
ld a, TX_SYMBOL
ld [hli], a
- ld [hl], LOW("<Lv>")
+ ld [hl], SYM_Lv
inc hl
ld a, [wLoadedCard1Level]
cp 10
jr c, .one_digit
ld [hl], TX_SYMBOL
inc hl
- ld b, LOW("<0>") - 1
+ ld b, SYM_0 - 1
.first_digit_loop
inc b
sub 10
@@ -76,7 +76,7 @@ _CopyCardNameAndLevel: ; 18000 (6:4000)
.one_digit
ld [hl], TX_SYMBOL
inc hl
- add LOW("<0>")
+ add SYM_0
ld [hl], a ; last (or only) digit
inc hl
.done
diff --git a/src/engine/home.asm b/src/engine/home.asm
index f3b5baf..14308b4 100644
--- a/src/engine/home.asm
+++ b/src/engine/home.asm
@@ -191,7 +191,7 @@ SetupTimer: ; 0241 (0:0241)
.set_timer
ld a, b
ld [rTMA], a
- ld a, rTAC_16384_HZ
+ ld a, TAC_16384_HZ
ld [rTAC], a
ld a, $7
ld [rTAC], a
@@ -209,7 +209,7 @@ CheckForCGB: ; 025c (0:025c)
WaitForVBlank: ; 0264 (0:0264)
push hl
ld a, [wLCDC]
- bit rLCDC_ENABLE, a
+ bit LCDC_ON, a
jr z, .asm_275
ld hl, wVBlankCtr
ld a, [hl]
@@ -224,12 +224,12 @@ WaitForVBlank: ; 0264 (0:0264)
; turn LCD on
EnableLCD: ; 0277 (0:0277)
- ld a, [wLCDC] ;
- bit rLCDC_ENABLE, a ;
- ret nz ; assert that LCD is off
- or rLCDC_ENABLE_MASK ;
- ld [wLCDC], a ;
- ld [rLCDC], a ; turn LCD on
+ ld a, [wLCDC] ;
+ bit LCDC_ON, a ;
+ ret nz ; assert that LCD is off
+ or 1 << LCDC_ON ;
+ ld [wLCDC], a ;
+ ld [rLCDC], a ; turn LCD on
ld a, FLUSH_ALL
ld [wFlushPaletteFlags], a
ret
@@ -237,7 +237,7 @@ EnableLCD: ; 0277 (0:0277)
; wait for vblank, then turn LCD off
DisableLCD: ; 028a (0:028a)
ld a, [rLCDC] ;
- bit rLCDC_ENABLE, a ;
+ bit LCDC_ON, a ;
ret z ; assert that LCD is on
ld a, [rIE]
ld [wIE], a
@@ -841,7 +841,9 @@ CallHL: ; 05c1 (0:05c1)
jp hl
; 0x5c2
-Func_05c2: ; 5c2 (0:5c2)
+; converts two one-digit numbers provided in a to text (ascii) format,
+; writes them to [wcaa0] and [wcaa0 + 1], and to the BGMap0 address at bc
+WriteTwoOneDigitNumbers: ; 05c2 (0:05c2)
push hl
push bc
push de
@@ -852,7 +854,7 @@ Func_05c2: ; 5c2 (0:5c2)
pop bc
call BCCoordToBGMap0Address
pop hl
- ld b, $02
+ ld b, 2
call JPHblankCopyDataHLtoDE
pop de
pop bc
@@ -860,7 +862,9 @@ Func_05c2: ; 5c2 (0:5c2)
ret
; 0x5db
-Func_05db: ; 5db (0:5db)
+; converts a one-digit number provided in the lower nybble of a to text
+; (ascii) format, and writes it to [wcaa0] and to the BGMap0 address at bc
+WriteOneDigitNumber: ; 05db (0:05db)
push hl
push bc
push de
@@ -871,7 +875,7 @@ Func_05db: ; 5db (0:5db)
pop bc
call BCCoordToBGMap0Address
pop hl
- ld b, $01
+ ld b, 1
call JPHblankCopyDataHLtoDE
pop de
pop bc
@@ -879,7 +883,9 @@ Func_05db: ; 5db (0:5db)
ret
; 0x5f4
-Func_05f4: ; 5f4 (0:5f4)
+; converts four one-digit numbers provided in h and l to text (ascii) format,
+; writes them to [wcaa0] through [wcaa0 + 3], and to the BGMap0 address at bc
+WriteFourOneDigitNumbers: ; 05f4 (0:05f4)
push hl
push bc
push de
@@ -895,7 +901,7 @@ Func_05f4: ; 5f4 (0:5f4)
pop bc
call BCCoordToBGMap0Address
pop hl
- ld b, $04
+ ld b, 4
call JPHblankCopyDataHLtoDE
pop de
pop bc
@@ -903,16 +909,19 @@ Func_05f4: ; 5f4 (0:5f4)
ret
; 0x614
-; given two numbers in the two nybbles of register a, write them
-; in text format to hl (most significant nybble first)
-WriteNumbersInTextFormat: ; 614 (0:614)
+; given two one-digit numbers in the two nybbles of register a,
+; write them in text (ascii) format to hl (most significant nybble first).
+; numbers above 9 are converted to VWF tiles.
+WriteNumbersInTextFormat: ; 0614 (0:0614)
push af
swap a
call WriteNumberInTextFormat
pop af
; fallthrough
-; given a number in the (bottom nybble) of register a, write it in text format to hl
+; given a one-digit number in the (lower nybble) of register a,
+; write it in text (ascii) format to hl.
+; numbers above 9 are converted to VWF tiles.
WriteNumberInTextFormat:
and $0f
add "0"
@@ -924,7 +933,46 @@ WriteNumberInTextFormat:
ret
; 0x627
- INCROM $0627, $0663
+; converts the one-byte number at a to text (ascii) format,
+; and writes it to [wcaa0] and the BGMap0 address at bc
+WriteOneByteNumber: ; 0627 (0:0627)
+ push bc
+ push hl
+ ld l, a
+ ld h, $00
+ ld de, wcaa0
+ push de
+ push bc
+ ld bc, -100
+ call TwoByteNumberToText.get_digit
+ ld bc, -10
+ call TwoByteNumberToText.get_digit
+ ld bc, -1
+ call TwoByteNumberToText.get_digit
+ pop bc
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 3
+ call JPHblankCopyDataHLtoDE
+ pop hl
+ pop bc
+ ret
+; 0x650
+
+; converts the two-byte number at hl to text (ascii) format,
+; and writes it to [wcaa0] and the BGMap0 address at bc
+WriteTwoByteNumber: ; 0650 (0:0650)
+ push bc
+ ld de, wcaa0
+ push de
+ call TwoByteNumberToText
+ call BCCoordToBGMap0Address
+ pop hl
+ ld b, 5
+ call JPHblankCopyDataHLtoDE
+ pop bc
+ ret
+; 0x663
; convert the number at hl to text (ascii) format and write it to de
TwoByteNumberToText: ; 0663 (0:0663)
@@ -1040,7 +1088,7 @@ WriteByteToBGMap0: ; 06c3 (0:06c3)
ld [hl], a
call BCCoordToBGMap0Address
pop hl
- ld b, $1
+ ld b, 1
call HblankCopyDataHLtoDE
pop bc
pop de
@@ -2083,7 +2131,67 @@ Func_0c53: ; 0c53 (0:0c53)
ret
; 0xc5f
- INCROM $0c5f, $0c91
+; returns a /= 10
+; returns carry if a % 10 >= 5
+Func_0c5f: ; 0c5f (0:0c5f)
+ push de
+ ld e, -1
+.asm_c62
+ inc e
+ sub 10
+ jr nc, .asm_c62
+ add 5
+ ld a, e
+ pop de
+ ret
+; 0xc6c
+
+; Save a pointer to a list, given at de, to wListPointer
+SetListPointer: ; 0c6c (0:0c6c)
+ push hl
+ ld hl, wListPointer
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ pop hl
+ ret
+; 0xc75
+
+; Return the current element of the list at wListPointer,
+; and advance the list to the next element
+GetNextElementOfList: ; 0c75 (0:0c75)
+ push hl
+ push de
+ ld hl, wListPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld a, [de]
+ inc de
+; fallthrough
+
+SetListToNextPosition: ; 0c7f (0:0c7f)
+ ld [hl], d
+ dec hl
+ ld [hl], e
+ pop de
+ pop hl
+ ret
+; 0xc85
+
+; Set the current element of the list at wListPointer to a,
+; and advance the list to the next element
+SetNextElementOfList: ; 0c85 (0:0c85)
+ push hl
+ push de
+ ld hl, wListPointer
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ ld [de], a
+ inc de
+ jr SetListToNextPosition
+; 0xc91
; called at roughly 240Hz by TimerHandler
SerialTimerHandler: ; 0c91 (0:0c91)
@@ -4333,7 +4441,7 @@ Func_1823: ; 1823 (0:1823)
DealConfusionDamageToSelf: ; 1828 (0:1828)
bank1call DrawDuelMainScene
- ld a, $1
+ ld a, 1
ld [wDamageToSelfMode], a
ldtx hl, DamageToSelfDueToConfusionText
call DrawWideTextBox_PrintText
@@ -4437,7 +4545,7 @@ CheckSelfConfusionDamage: ; 18d7 (0:18d7)
ldtx de, ConfusionCheckDamageText
call TossCoin
jr c, .no_confusion_damage
- ld a, $1
+ ld a, 1
ld [wGotHeadsFromConfusionCheck], a
scf
ret
@@ -5309,7 +5417,7 @@ GetCardAlbumProgress: ; 1da4 (0:1da4)
; if LCD on, copy during h-blank only
SafeCopyDataDEtoHL: ; 1dca (0:1dca)
ld a, [wLCDC] ;
- bit rLCDC_ENABLE, a ;
+ bit LCDC_ON, a ;
jr nz, .lcd_on ; assert that LCD is on
.lcd_off_loop
ld a, [de]
@@ -5384,9 +5492,9 @@ DrawLabeledTextBox: ; 1e00 (0:1e00)
push hl
; top left tile of the box
ld hl, wc000
- ld a, $5
+ ld a, TX_SYMBOL
ld [hli], a
- ld a, $18
+ ld a, SYM_BOX_TOP_L
ld [hli], a
; white tile before the text
ld a, $70
@@ -5396,7 +5504,7 @@ DrawLabeledTextBox: ; 1e00 (0:1e00)
ld d, h
pop hl
call CopyText
- ld hl, $c003
+ ld hl, wc000 + 3
call Func_23c1
ld l, e
ld h, d
@@ -5413,17 +5521,17 @@ DrawLabeledTextBox: ; 1e00 (0:1e00)
jr z, .draw_top_border_right_tile
ld b, a
.draw_top_border_line_loop
- ld a, $5
+ ld a, TX_SYMBOL
ld [hli], a
- ld a, $1c
+ ld a, SYM_BOX_TOP
ld [hli], a
dec b
jr nz, .draw_top_border_line_loop
.draw_top_border_right_tile
- ld a, $5
+ ld a, TX_SYMBOL
ld [hli], a
- ld a, $19
+ ld a, SYM_BOX_TOP_R
ld [hli], a
ld [hl], $0
pop bc
@@ -6140,7 +6248,7 @@ Func_2325: ; 2325 (0:2325)
ret
; search linked-list for letters e/d (regisers), if found hoist the result to
-; head of list and return it. carry flag denotes success.
+; head of list and return it. carry flag denotes success.
Func_235e: ; 235e (0:235e)
ld a, [wcd0a] ;
or a ;
@@ -6171,7 +6279,7 @@ Func_235e: ; 235e (0:235e)
ld a, [hl] ; if key1[l] == e and ;
cp d ; key2[l] == d: ;
jr z, .asm_238f ; break ;
-.asm_238a ;
+.asm_238a
ld h, $c8 ; ;
ld l, [hl] ; l ← next[l] ;
jr .asm_237d
@@ -6269,7 +6377,7 @@ Func_23d3: ; 23d3 (0:23d3)
; convert the number at hl to TX_SYMBOL text format and write it to wcaa0
; replace leading zeros with $00
-TwoByteNumberToLargeText_TrimLeadingZeros: ; 245d (0:245d)
+TwoByteNumberToTxSymbol_TrimLeadingZeros: ; 245d (0:245d)
push de
push bc
ld de, wcaa0
@@ -6291,14 +6399,14 @@ TwoByteNumberToLargeText_TrimLeadingZeros: ; 245d (0:245d)
.digit_loop
inc hl
ld a, [hl]
- cp LOW("<0>")
+ cp SYM_0
jr nz, .done ; jump if not zero
- ld [hl], LOW("< >") ; trim leading zero
+ ld [hl], SYM_SPACE ; trim leading zero
inc hl
dec e
jr nz, .digit_loop
dec hl
- ld [hl], LOW("<0>")
+ ld [hl], SYM_0
.done
dec hl
pop bc
@@ -6309,7 +6417,7 @@ TwoByteNumberToLargeText_TrimLeadingZeros: ; 245d (0:245d)
ld a, TX_SYMBOL
ld [de], a
inc de
- ld a, LOW("<0>") - 1
+ ld a, SYM_0 - 1
.substract_loop
inc a
add hl, bc
@@ -6507,7 +6615,7 @@ InitializeCardListParameters: ; 25ea (0:25ea)
ld a, [hli]
ld [wListItemXPosition], a
ld a, [hli]
- ld [wcd1c], a
+ ld [wListItemNameMaxLength], a
ld a, [hli]
ld [wNumMenuItems], a
ld a, [hli]
@@ -6852,7 +6960,7 @@ PrintCardListItems: ; 2799 (0:2799)
call LoadCardDataToBuffer1_FromDeckIndex
call DrawCardSymbol
call Func_22ae
- ld a, [wcd1c]
+ ld a, [wListItemNameMaxLength]
call CopyCardNameAndLevel
ld hl, wDefaultText
call Func_21c5
@@ -7648,12 +7756,16 @@ ReadTextOffset: ; 2ded (0:2ded)
pop de
ret
-; convert the number at hl to text (ascii) format and write it to wcaa0
-; return c = 4 - leading_zeros
+; if [wcd0a] != 0:
+; convert the number at hl to text (ascii) format and write it to wcaa0
+; return c = 4 - leading_zeros
+; if [wcd0a] == 0:
+; convert the number at hl to TX_SYMBOL text format and write it to wcaa0
+; replace leading zeros with $00
TwoByteNumberToText_CountLeadingZeros: ; 2e12 (0:2e12)
ld a, [wcd0a]
or a
- jp z, TwoByteNumberToLargeText_TrimLeadingZeros
+ jp z, TwoByteNumberToTxSymbol_TrimLeadingZeros
ld de, wcaa0
push de
call TwoByteNumberToText
@@ -7683,7 +7795,7 @@ CopyTurnDuelistName: ; 2e2c (0:2e2c)
pop hl
ret
-; prints text with id at hl with letter delay in a textbox area
+; prints text with id at hl, with letter delay, in a textbox area
PrintText: ; 2e41 (0:2e41)
ld a, l
or h
@@ -7721,7 +7833,7 @@ PrintText: ; 2e41 (0:2e41)
jr nc, .next_tile_loop
ret
-; prints text with id at hl without letter delay in a textbox area
+; prints text with id at hl, without letter delay, in a textbox area
PrintTextNoDelay: ; 2e76 (0:2e76)
ldh a, [hBankROM]
push af
@@ -9436,7 +9548,7 @@ Func_380e: ; 380e (0:380e)
; enable the play time counter and execute the game event at [wGameEvent].
; then return to the overworld, or restart the game (only after Credits).
ExecuteGameEvent: ; 383d (0:383d)
- ld a, $1
+ ld a, 1
ld [wPlayTimeCounterEnable], a
ldh a, [hBankROM]
push af
@@ -9967,7 +10079,7 @@ Func_3b31: ; 3b31 (0:3b31)
ld [wcad4], a
.asm_3b45
call ZeroObjectPositions
- ld a, $1
+ ld a, 1
ld [wVBlankOAMCopyToggle], a
pop af
call BankswitchHome
@@ -10085,7 +10197,7 @@ Func_3c46: ; 3c46 (0:3c46)
DoFrameIfLCDEnabled: ; 3c48 (0:3c48)
push af
ld a, [rLCDC]
- bit rLCDC_ENABLE, a
+ bit LCDC_ON, a
jr z, .done
push bc
push de
diff --git a/src/macros/code.asm b/src/macros/code.asm
index 9e8a4ce..55f4b63 100644
--- a/src/macros/code.asm
+++ b/src/macros/code.asm
@@ -3,7 +3,11 @@ lb: MACRO ; r, hi, lo
ENDM
ldtx: MACRO
+if _NARG == 2
ld \1, \2_
+else
+ ld \1, \2_ \3
+endc
ENDM
bank1call: MACRO
diff --git a/src/macros/data.asm b/src/macros/data.asm
index 80978b7..7028636 100644
--- a/src/macros/data.asm
+++ b/src/macros/data.asm
@@ -84,3 +84,8 @@ ENDM
tx: MACRO
dw \1_
ENDM
+
+txsymbol: MACRO
+ const SYM_\1
+ charmap "\1>", const_value + -1
+ENDM
diff --git a/src/macros/wram.asm b/src/macros/wram.asm
index e692a92..cedb0dd 100644
--- a/src/macros/wram.asm
+++ b/src/macros/wram.asm
@@ -15,7 +15,7 @@ card_data_struct: MACRO
\1RetreatCost:: ds 1
\1Weakness:: ds 1
\1Resistance:: ds 1
-\1Kind:: ds 2
+\1Category:: ds 2
\1PokedexNumber:: ds 1
\1Unknown1:: ds 1
\1Level:: ds 1
diff --git a/src/text/text10.asm b/src/text/text10.asm
index 013e5da..316397e 100644
--- a/src/text/text10.asm
+++ b/src/text/text10.asm
@@ -17,7 +17,7 @@ DoubleAttackX30Description: ; 5807b (16:407b)
line "damage times the number of heads."
done
-BeedrillKind: ; 580c0 (16:40c0)
+PoisonBeeName: ; 580c0 (16:40c0)
text "Poison Bee"
done
@@ -39,7 +39,7 @@ WrapName: ; 5813e (16:413e)
text "Wrap"
done
-EkansKind: ; 58144 (16:4144)
+SnakeName: ; 58144 (16:4144)
text "Snake"
done
@@ -71,7 +71,7 @@ PoisonFangName: ; 58277 (16:4277)
text "Poison Fang"
done
-ArbokKind: ; 58284 (16:4284)
+CobraName: ; 58284 (16:4284)
text "Cobra"
done
@@ -106,7 +106,7 @@ NidoranFsCallForFamilyDescription: ; 58352 (16:4352)
line "attack if your Bench is full.)"
done
-NidoranFKind: ; 583ff (16:43ff)
+PoisonPinName: ; 583ff (16:43ff)
text "Poison Pin"
done
@@ -156,7 +156,7 @@ MegaPunchName: ; 58584 (16:4584)
text "Mega Punch"
done
-NidoqueenKind: ; 58590 (16:4590)
+DrillName: ; 58590 (16:4590)
text "Drill"
done
@@ -250,7 +250,7 @@ ZubatsLeechLifeDescription: ; 588b3 (16:48b3)
line "than that, remove all of them."
done
-ZubatKind: ; 58980 (16:4980)
+BatName: ; 58980 (16:4980)
text "Bat"
done
@@ -299,7 +299,7 @@ SproutDescription: ; 58b42 (16:4b42)
line "Bench is full.)"
done
-OddishKind: ; 58be1 (16:4be1)
+WeedName: ; 58be1 (16:4be1)
text "Weed"
done
@@ -357,7 +357,7 @@ PetalDanceDescription: ; 58def (16:4def)
line "doing damage)."
done
-VileplumeKind: ; 58e64 (16:4e64)
+FlowerName: ; 58e64 (16:4e64)
text "Flower"
done
@@ -383,7 +383,7 @@ InflictSleepDescription: ; 58ee9 (16:4ee9)
text "The Defending Pokémon is now Asleep."
done
-ParasKind: ; 58f0f (16:4f0f)
+MushroomName: ; 58f0f (16:4f0f)
text "Mushroom"
done
@@ -420,7 +420,7 @@ VenonatLeechLifeDescription: ; 58ffe (16:4ffe)
line "than that, remove all of them."
done
-VenonatKind: ; 590cf (16:50cf)
+InsectName: ; 590cf (16:50cf)
text "Insect"
done
@@ -458,7 +458,7 @@ VenomPowderDescription: ; 5922c (16:522c)
line "Poisoned."
done
-VenomothKind: ; 59278 (16:5278)
+PoisonmothName: ; 59278 (16:5278)
text "Poisonmoth"
done
@@ -494,7 +494,7 @@ RazorLeafName: ; 59403 (16:5403)
text "Razor Leaf"
done
-WeepinbellKind: ; 5940f (16:540f)
+FlycatcherName: ; 5940f (16:540f)
text "Flycatcher"
done
@@ -554,7 +554,7 @@ GrimersMinimizeDescription: ; 595dc (16:55dc)
line "Weakness and Resistance)."
done
-GrimerKindOrSludgeName: ; 5965e (16:565e)
+SludgeName: ; 5965e (16:565e)
text "Sludge"
done
@@ -589,7 +589,7 @@ ExeggcuteName: ; 597b1 (16:57b1)
text "Exeggcute"
done
-DrowzeeKindOrHypnosisName: ; 597bc (16:57bc)
+HypnosisName: ; 597bc (16:57bc)
text "Hypnosis"
done
@@ -599,7 +599,7 @@ ExeggcutesLeechSeedDescription: ; 597c6 (16:57c6)
line "damage counter from Exeggcute."
done
-ExeggcuteKind: ; 59828 (16:5828)
+EggName: ; 59828 (16:5828)
text "Egg"
done
@@ -633,7 +633,7 @@ BigEggsplosionDescription: ; 598d9 (16:58d9)
line "damage times the number of heads."
done
-ExeggutorKind: ; 5995c (16:595c)
+CoconutName: ; 5995c (16:595c)
text "Coconut"
done
@@ -658,7 +658,7 @@ FoulGasDescription: ; 599df (16:59df)
line "it is now Confused."
done
-KoffingKind: ; 59a3c (16:5a3c)
+PoisonGasName: ; 59a3c (16:5a3c)
text "Poison Gas"
done
@@ -702,7 +702,7 @@ BindName: ; 59bc7 (16:5bc7)
text "Bind"
done
-TangelaKind: ; 59bcd (16:5bcd)
+VineName: ; 59bcd (16:5bcd)
text "Vine"
done
@@ -736,7 +736,7 @@ SwordsDanceDescription: ; 59cc2 (16:5cc2)
line "doubled."
done
-ScytherKind: ; 59d0b (16:5d0b)
+MantisName: ; 59d0b (16:5d0b)
text "Mantis"
done
@@ -758,7 +758,7 @@ GuillotineName: ; 59d87 (16:5d87)
text "Guillotine"
done
-PinsirKind: ; 59d93 (16:5d93)
+StagbeetleName: ; 59d93 (16:5d93)
text "Stagbeetle"
done
@@ -782,7 +782,7 @@ EmberDescription: ; 59e15 (16:5e15)
line "attack."
done
-CharmanderKind: ; 59e63 (16:5e63)
+LizardName: ; 59e63 (16:5e63)
text "Lizard"
done
@@ -807,7 +807,7 @@ CharmeleonsFlamethrowerDescription: ; 59ef4 (16:5ef4)
line "attack."
done
-CharmeleonKind: ; 59f42 (16:5f42)
+FlameName: ; 59f42 (16:5f42)
text "Flame"
done
@@ -859,7 +859,7 @@ ConfuseRayName: ; 5a151 (16:6151)
text "Confuse Ray"
done
-VulpixKind: ; 5a15e (16:615e)
+FoxName: ; 5a15e (16:615e)
text "Fox"
done
@@ -938,7 +938,7 @@ FlareName: ; 5a4e8 (16:64e8)
text "Flare"
done
-GrowlitheKind: ; 5a4ef (16:64ef)
+PuppyName: ; 5a4ef (16:64ef)
text "Puppy"
done
@@ -975,7 +975,7 @@ FlamesOfRageDescription: ; 5a5e8 (16:65e8)
line "counter on Arcanine."
done
-ArcanineKind: ; 5a689 (16:6689)
+LegendaryName: ; 5a689 (16:6689)
text "Legendary"
done
@@ -1017,7 +1017,7 @@ FlameTailName: ; 5a7d6 (16:67d6)
text "Flame Tail"
done
-PonytaKind: ; 5a7e2 (16:67e2)
+FireHorseName: ; 5a7e2 (16:67e2)
text "Fire Horse"
done
@@ -1072,7 +1072,7 @@ FirePunchDescription: ; 5a9bf (16:69bf)
line "Magmar in order to use this attack."
done
-MagmarKind: ; 5aa09 (16:6a09)
+SpitfireName: ; 5aa09 (16:6a09)
text "Spitfire"
done
@@ -1202,7 +1202,7 @@ SquirtlesWithdrawDescription: ; 5af4b (16:6f4b)
line "effects of attacks still happen.)"
done
-SquirtleKind: ; 5afd6 (16:6fd6)
+TinyTurtleName: ; 5afd6 (16:6fd6)
text "Tiny Turtle"
done
@@ -1223,7 +1223,7 @@ WartortlesWithdrawDescription: ; 5b050 (16:7050)
line "effects of attacks still happen.)"
done
-WartortleKind: ; 5b0dc (16:70dc)
+TurtleName: ; 5b0dc (16:70dc)
text "Turtle"
done
@@ -1268,7 +1268,7 @@ HydroPumpDescription: ; 5b273 (16:7273)
line "add more than 20 damage in this way."
done
-BlastoiseKind: ; 5b322 (16:7322)
+ShellfishName: ; 5b322 (16:7322)
text "Shellfish"
done
@@ -1291,7 +1291,7 @@ HeadacheDescription: ; 5b3a2 (16:73a2)
line "cards during his or her next turn."
done
-PsyduckKind: ; 5b3e7 (16:73e7)
+DuckName: ; 5b3e7 (16:73e7)
text "Duck"
done
@@ -1341,7 +1341,7 @@ PoliwagsWaterGunDescription: ; 5b547 (16:7547)
line "add more than 20 damage in this way."
done
-PoliwagKind: ; 5b5f4 (16:75f4)
+TadpoleName: ; 5b5f4 (16:75f4)
text "Tadpole"
done
@@ -1417,7 +1417,7 @@ CowardiceDescription: ; 5b890 (16:7890)
line "is Asleep, Confused, or Paralyzed."
done
-TentacoolKind: ; 5b987 (16:7987)
+JellyfishName: ; 5b987 (16:7987)
text "Jellyfish"
done
@@ -1449,7 +1449,7 @@ HeadbuttName: ; 5ba7d (16:7a7d)
text "Headbutt"
done
-SeelKind: ; 5ba87 (16:7a87)
+SeaLionName: ; 5ba87 (16:7a87)
text "Sea Lion"
done
@@ -1492,7 +1492,7 @@ HideInShellDescription: ; 5bb87 (16:7b87)
line "effects of attacks still happen.)"
done
-ShellderKind: ; 5bc12 (16:7c12)
+BivalveName: ; 5bc12 (16:7c12)
text "Bivalve"
done
@@ -1539,7 +1539,7 @@ KrabbysCallForFamilyDescription: ; 5bd6b (16:7d6b)
line "Bench is full.)"
done
-KrabbyKind: ; 5be0a (16:7e0a)
+RiverCrabName: ; 5be0a (16:7e0a)
text "River Crab"
done
@@ -1566,7 +1566,7 @@ CrabhammerName: ; 5bec3 (16:7ec3)
text "Crabhammer"
done
-KinglerKind: ; 5becf (16:7ecf)
+PincerName: ; 5becf (16:7ecf)
text "Pincer"
done
@@ -1588,6 +1588,6 @@ OpponentAttackMayDoNothingDescription: ; 5bf4b (16:7f4b)
line "If tails, that attack does nothing."
done
-HorseaKind: ; 5bfd7 (16:7fd7)
+DragonName: ; 5bfd7 (16:7fd7)
text "Dragon"
done
diff --git a/src/text/text11.asm b/src/text/text11.asm
index 673150d..5304dfa 100644
--- a/src/text/text11.asm
+++ b/src/text/text11.asm
@@ -37,7 +37,7 @@ HornAttackName: ; 5c1f5 (17:41f5)
text "Horn Attack"
done
-GoldeenKind: ; 5c202 (17:4202)
+GoldfishName: ; 5c202 (17:4202)
text "Goldfish"
done
@@ -69,7 +69,7 @@ SlapName: ; 5c2df (17:42df)
text "Slap"
done
-StaryuKind: ; 5c2e5 (17:42e5)
+StarshapeName: ; 5c2e5 (17:42e5)
text "Starshape"
done
@@ -98,7 +98,7 @@ StarFreezeName: ; 5c3cf (17:43cf)
text "Star Freeze"
done
-StarmieKind: ; 5c3dc (17:43dc)
+MysteriousName: ; 5c3dc (17:43dc)
text "Mysterious"
done
@@ -121,7 +121,7 @@ MagikarpsFlailDescription: ; 5c45e (17:445e)
line "damage counters on Magikarp."
done
-MagikarpKind: ; 5c49f (17:449f)
+FishName: ; 5c49f (17:449f)
text "Fish"
done
@@ -143,7 +143,7 @@ BubblebeamName: ; 5c517 (17:4517)
text "Bubblebeam"
done
-GyaradosKind: ; 5c523 (17:4523)
+AtrociousName: ; 5c523 (17:4523)
text "Atrocious"
done
@@ -165,7 +165,7 @@ LaprasWaterGunDescription: ; 5c597 (17:4597)
line "more than 20 damage in this way."
done
-LaprasKind: ; 5c643 (17:4643)
+TransportName: ; 5c643 (17:4643)
text "Transport"
done
@@ -189,7 +189,7 @@ FocusEnergyDescription: ; 5c6c2 (17:46c2)
line "doubled."
done
-VaporeonKind: ; 5c70b (17:470b)
+BubbleJetName: ; 5c70b (17:470b)
text "Bubble Jet"
done
@@ -240,7 +240,7 @@ OmanytesWaterGunDescription: ; 5c932 (17:4932)
line "more than 20 damage in this way."
done
-OmanyteKind: ; 5c9df (17:49df)
+SpiralName: ; 5c9df (17:49df)
text "Spiral"
done
@@ -290,7 +290,7 @@ BlizzardDescription: ; 5cb77 (17:4b77)
line "for Benched Pokémon.)"
done
-ArticunoKind: ; 5cc5b (17:4c5b)
+FreezeName: ; 5cc5b (17:4c5b)
text "Freeze"
done
@@ -348,7 +348,7 @@ ThunderJoltDescription: ; 5ceb1 (17:4eb1)
line "10 damage to itself."
done
-PikachuKind: ; 5ceeb (17:4eeb)
+MouseName: ; 5ceeb (17:4eeb)
text "Mouse"
done
@@ -499,7 +499,7 @@ MagnemitesSelfdestructDescription: ; 5d59f (17:559f)
line "to itself."
done
-MagnemiteKind: ; 5d636 (17:5636)
+MagnetName: ; 5d636 (17:5636)
text "Magnet"
done
@@ -573,7 +573,7 @@ VoltorbName: ; 5d9fc (17:59fc)
text "Voltorb"
done
-VoltorbKind: ; 5da05 (17:5a05)
+BallName: ; 5da05 (17:5a05)
text "Ball"
done
@@ -653,7 +653,7 @@ ElectabuzzsQuickAttackDescription: ; 5dd7d (17:5d7d)
line "10 damage."
done
-ElectabuzzKind: ; 5ddec (17:5dec)
+ElectricName: ; 5ddec (17:5dec)
text "Electric"
done
@@ -694,7 +694,7 @@ StunNeedleName: ; 5dfa2 (17:5fa2)
text "Stun Needle"
done
-JolteonKind: ; 5dfaf (17:5faf)
+LightningName: ; 5dfaf (17:5faf)
text "Lightning"
done
@@ -837,7 +837,7 @@ MudSlapName: ; 5e68d (17:668d)
text "Mud Slap"
done
-DiglettKind: ; 5e697 (17:6697)
+MoleName: ; 5e697 (17:6697)
text "Mole"
done
@@ -890,7 +890,7 @@ PeekDescriptionCont: ; 5e8b8 (17:68b8)
line "is Asleep, Confused, or Paralyzed."
done
-MankeyKind: ; 5e8ff (17:68ff)
+PigMonkeyName: ; 5e8ff (17:68ff)
text "Pig Monkey"
done
@@ -928,7 +928,7 @@ LowKickName: ; 5ea2f (17:6a2f)
text "Low Kick"
done
-MachopKindOrSuperpowerName: ; 5ea39 (17:6a39)
+SuperpowerName: ; 5ea39 (17:6a39)
text "Superpower"
done
@@ -1013,7 +1013,7 @@ StoneBarrageDescription: ; 5ed50 (17:6d50)
line "the number of heads."
done
-GeodudeKind: ; 5eda8 (17:6da8)
+RockName: ; 5eda8 (17:6da8)
text "Rock"
done
@@ -1066,7 +1066,7 @@ GolemsSelfdestructDescription: ; 5ef5e (17:6f5e)
line "itself."
done
-GolemKind: ; 5eff2 (17:6ff2)
+MegatonName: ; 5eff2 (17:6ff2)
text "Megaton"
done
@@ -1089,7 +1089,7 @@ OnixsHardenDescription: ; 5f063 (17:7063)
line "happen.)"
done
-OnixKind: ; 5f11d (17:711d)
+RockSnakeName: ; 5f11d (17:711d)
text "Rock Snake"
done
@@ -1122,7 +1122,7 @@ CubonesRageDescription: ; 5f27f (17:727f)
line "for each damage counter on Cubone."
done
-CuboneKind: ; 5f2c6 (17:72c6)
+LonelyName: ; 5f2c6 (17:72c6)
text "Lonely"
done
@@ -1152,7 +1152,7 @@ CallforFriendDescription: ; 5f34f (17:734f)
line "Bench is full.)"
done
-MarowakKind: ; 5f3e9 (17:73e9)
+BonekeeperName: ; 5f3e9 (17:73e9)
text "Bonekeeper"
done
@@ -1212,7 +1212,7 @@ HighJumpKickName: ; 5f6d0 (17:76d0)
text "High Jump Kick"
done
-HitmonleeKind: ; 5f6e0 (17:76e0)
+KickingName: ; 5f6e0 (17:76e0)
text "Kicking"
done
@@ -1234,7 +1234,7 @@ SpecialPunch: ; 5f75f (17:775f)
text "Special Punch"
done
-HitmonchanKind: ; 5f76e (17:776e)
+PunchingName: ; 5f76e (17:776e)
text "Punching"
done
@@ -1260,7 +1260,7 @@ LeerDescription: ; 5f7ec (17:77ec)
line "ends this effect.)"
done
-RhyhornKind: ; 5f889 (17:7889)
+SpikeName: ; 5f889 (17:7889)
text "Spike"
done
@@ -1376,7 +1376,7 @@ PrehistoricPowerDescription: ; 5fd8b (17:7d8b)
line "Confused, or Paralyzed."
done
-AerodactylKind: ; 5fe00 (17:7e00)
+FossilName: ; 5fe00 (17:7e00)
text "Fossil"
done
@@ -1390,7 +1390,7 @@ AbraName: ; 5fe6c (17:7e6c)
text "Abra"
done
-AbraKind: ; 5fe72 (17:7e72)
+PsiName: ; 5fe72 (17:7e72)
text "Psi"
done
diff --git a/src/text/text12.asm b/src/text/text12.asm
index 80b3530..e3e7e12 100644
--- a/src/text/text12.asm
+++ b/src/text/text12.asm
@@ -25,7 +25,7 @@ SlowpokesAmnesiaDescription: ; 60155 (18:4155)
line "next turn."
done
-SlowpokeKind: ; 601c8 (18:41c8)
+DopeyName: ; 601c8 (18:41c8)
text "Dopey"
done
@@ -75,7 +75,7 @@ StrangeBehaviorDescription: ; 60360 (18:4360)
line "Confused, or Paralyzed."
done
-SlowbroKind: ; 6044c (18:444c)
+HermitcrabName: ; 6044c (18:444c)
text "Hermitcrab"
done
@@ -110,7 +110,7 @@ DestinyBondDescription: ; 6051c (18:451c)
line "Knock Out that Pokémon."
done
-GastlyKind: ; 605bf (18:45bf)
+GasName: ; 605bf (18:45bf)
text "Gas"
done
@@ -208,7 +208,7 @@ DarkMindDescription: ; 609c2 (18:49c2)
line "for Benched Pokémon.)"
done
-GengarKind: ; 60a5f (18:4a5f)
+ShadowName: ; 60a5f (18:4a5f)
text "Shadow"
done
@@ -284,7 +284,7 @@ MrMimesMeditateDescription: ; 60d2b (18:4d2b)
line "Defending Pokémon."
done
-MrMimeKindOrBarrierName: ; 60d81 (18:4d81)
+BarrierName: ; 60d81 (18:4d81)
text "Barrier"
done
@@ -309,7 +309,7 @@ JynxsMeditateDescription: ; 60e27 (18:4e27)
line "Defending Pokémon."
done
-JynxKind: ; 60e7d (18:4e7d)
+HumanShapeName: ; 60e7d (18:4e7d)
text "Human Shape"
done
@@ -341,7 +341,7 @@ BarrierDescription: ; 60f45 (18:4f45)
line "including damage, done to Mewtwo."
done
-MewtwoKind: ; 60ff3 (18:4ff3)
+GeneticName: ; 60ff3 (18:4ff3)
text "Genetic"
done
@@ -389,7 +389,7 @@ NeutralizingShieldDescription: ; 6114c (18:514c)
line "Paralyzed."
done
-MewKind: ; 611fa (18:51fa)
+NewSpeciesName: ; 611fa (18:51fa)
text "New Species"
done
@@ -442,7 +442,7 @@ PidgeyName: ; 61442 (18:5442)
text "Pidgey"
done
-PidgeyKind: ; 6144a (18:544a)
+TinyBirdName: ; 6144a (18:544a)
text "Tiny Bird"
done
@@ -467,7 +467,7 @@ PidgeottosMirrorMoveDescription: ; 614d0 (18:54d0)
line "Pokémon."
done
-PidgeottoKind: ; 61540 (18:5540)
+BirdName: ; 61540 (18:5540)
text "Bird"
done
@@ -537,7 +537,7 @@ RattataName: ; 618c5 (18:58c5)
text "Rattata"
done
-RattataKind: ; 618ce (18:58ce)
+RatName: ; 618ce (18:58ce)
text "Rat"
done
@@ -603,7 +603,7 @@ DrillPeckName: ; 61b72 (18:5b72)
text "Drill Peck"
done
-FearowKind: ; 61b7e (18:5b7e)
+BeakName: ; 61b7e (18:5b7e)
text "Beak"
done
@@ -634,7 +634,7 @@ ClefairysMetronomeDescription: ; 61bfd (18:5bfd)
line "still Colorless.)"
done
-ClefairyKind: ; 61cb9 (18:5cb9)
+FairyName: ; 61cb9 (18:5cb9)
text "Fairy"
done
@@ -691,7 +691,7 @@ JigglypuffsDoubleEdgeDescription: ; 61f12 (18:5f12)
text "Jigglypuff does 20 damage to itself."
done
-JigglypuffKind: ; 61f38 (18:5f38)
+BalloonName: ; 61f38 (18:5f38)
text "Balloon"
done
@@ -778,7 +778,7 @@ CatPunchDescription: ; 62287 (18:6287)
line "happen.)"
done
-MeowthKind: ; 62359 (18:6359)
+ScratchCatName: ; 62359 (18:6359)
text "Scratch Cat"
done
@@ -820,7 +820,7 @@ PounceDescription: ; 62475 (18:6475)
line "effect.)"
done
-PersianKind: ; 62552 (18:6552)
+ClassyCatName: ; 62552 (18:6552)
text "Classy Cat"
done
@@ -851,7 +851,7 @@ PotSmashName: ; 6269d (18:669d)
text "Pot Smash"
done
-FarfetchdKind: ; 626a8 (18:66a8)
+WildDuckName: ; 626a8 (18:66a8)
text "Wild Duck"
done
@@ -869,7 +869,7 @@ FuryAttackName: ; 62719 (18:6719)
text "Fury Attack"
done
-DoduoKind: ; 62726 (18:6726)
+TwinBirdName: ; 62726 (18:6726)
text "Twin Bird"
done
@@ -898,7 +898,7 @@ DodriosRageDescription: ; 627ee (18:67ee)
line "for each damage counter on Dodrio."
done
-DodrioKind: ; 62835 (18:6835)
+TriplebirdName: ; 62835 (18:6835)
text "Triplebird"
done
@@ -916,7 +916,7 @@ TongueWrapName: ; 628aa (18:68aa)
text "Tongue Wrap"
done
-LickitungKind: ; 628b7 (18:68b7)
+LickingName: ; 628b7 (18:68b7)
text "Licking"
done
@@ -967,7 +967,7 @@ CometPunchName: ; 62a63 (18:6a63)
text "Comet Punch"
done
-KangaskhanKind: ; 62a70 (18:6a70)
+ParentName: ; 62a70 (18:6a70)
text "Parent"
done
@@ -992,7 +992,7 @@ RampageDescription: ; 62ae7 (18:6ae7)
line "now Confused (after doing damage)."
done
-TaurosKind: ; 62b72 (18:6b72)
+WildBullName: ; 62b72 (18:6b72)
text "Wild Bull"
done
@@ -1026,7 +1026,7 @@ MorphDescriptionCont: ; 62c90 (18:6c90)
line "it)."
done
-DittoKind: ; 62d23 (18:6d23)
+TransformName: ; 62d23 (18:6d23)
text "Transform"
done
@@ -1048,7 +1048,7 @@ TailWagDescription: ; 62d94 (18:6d94)
line "effect.)"
done
-EeveeKind: ; 62e2f (18:6e2f)
+EvolutionName: ; 62e2f (18:6e2f)
text "Evolution"
done
@@ -1083,7 +1083,7 @@ Conversion2Description: ; 62f2d (18:6f2d)
line "Colorless."
done
-PorygonKind: ; 62f79 (18:6f79)
+VirtualName: ; 62f79 (18:6f79)
text "Virtual"
done
@@ -1113,7 +1113,7 @@ BodySlamName: ; 63088 (18:7088)
text "Body Slam"
done
-SnorlaxKind: ; 63093 (18:7093)
+SleepingName: ; 63093 (18:7093)
text "Sleeping"
done
diff --git a/src/text/text9.asm b/src/text/text9.asm
index 9beb35d..b329238 100644
--- a/src/text/text9.asm
+++ b/src/text/text9.asm
@@ -1251,7 +1251,7 @@ BulbasaursLeechSeedDescription: ; 57569 (15:7569)
line "damage counter from Bulbasaur."
done
-BulbasaurKind: ; 575cb (15:75cb)
+SeedName: ; 575cb (15:75cb)
text "Seed"
done
@@ -1370,7 +1370,7 @@ MayInflictParalysisDescription: ; 57ad0 (15:7ad0)
line "Pokémon is now Paralyzed."
done
-CaterpieKind: ; 57b10 (15:7b10)
+WormName: ; 57b10 (15:7b10)
text "Worm"
done
@@ -1399,7 +1399,7 @@ StunSporeName: ; 57c14 (15:7c14)
text "Stun Spore"
done
-MetapodKind: ; 57c20 (15:7c20)
+CocoonName: ; 57c20 (15:7c20)
text "Cocoon"
done
@@ -1440,7 +1440,7 @@ ButterfreesMegaDrainDescriptionCont: ; 57dee (15:7dee)
line "them."
done
-ButterfreeKind: ; 57e36 (15:7e36)
+ButterflyName: ; 57e36 (15:7e36)
text "Butterfly"
done
@@ -1463,7 +1463,7 @@ MayInflictPoisonDescription: ; 57eaf (15:7eaf)
line "Pokémon is now Poisoned."
done
-WeedleKind: ; 57eee (15:7eee)
+HairyBugName: ; 57eee (15:7eee)
text "Hairy Bug"
done
diff --git a/src/text/text_offsets.asm b/src/text/text_offsets.asm
index 7a77e79..568653d 100644
--- a/src/text/text_offsets.asm
+++ b/src/text/text_offsets.asm
@@ -2062,7 +2062,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer BulbasaurName ; 0x080a
textpointer LeechSeedName ; 0x080b
textpointer BulbasaursLeechSeedDescription ; 0x080c
- textpointer BulbasaurKind ; 0x080d
+ textpointer SeedName ; 0x080d
textpointer BulbasaurDescription ; 0x080e
textpointer IvysaurName ; 0x080f
textpointer VineWhipName ; 0x0810
@@ -2084,25 +2084,25 @@ TextOffsets:: ; 34000 (d:4000)
textpointer CaterpieName ; 0x0820
textpointer StringShotName ; 0x0821
textpointer MayInflictParalysisDescription ; 0x0822
- textpointer CaterpieKind ; 0x0823
+ textpointer WormName ; 0x0823
textpointer CaterpieDescription ; 0x0824
textpointer MetapodName ; 0x0825
textpointer StiffenName ; 0x0826
textpointer MetapodsStiffenDescription ; 0x0827
textpointer StunSporeName ; 0x0828
- textpointer MetapodKind ; 0x0829
+ textpointer CocoonName ; 0x0829
textpointer MetapodDescription ; 0x082a
textpointer ButterfreeName ; 0x082b
textpointer WhirlwindName ; 0x082c
textpointer WhirlwindDescription ; 0x082d
textpointer ButterfreesMegaDrainDescription ; 0x082e
textpointer ButterfreesMegaDrainDescriptionCont ; 0x082f
- textpointer ButterfreeKind ; 0x0830
+ textpointer ButterflyName ; 0x0830
textpointer ButterfreeDescription ; 0x0831
textpointer WeedleName ; 0x0832
textpointer PoisonStingName ; 0x0833
textpointer MayInflictPoisonDescription ; 0x0834
- textpointer WeedleKind ; 0x0835
+ textpointer HairyBugName ; 0x0835
textpointer WeedleDescription ; 0x0836
textpointer KakunaName ; 0x0837
textpointer KakunasStiffenDescription ; 0x0838
@@ -2110,25 +2110,25 @@ TextOffsets:: ; 34000 (d:4000)
textpointer BeedrillName ; 0x083a
textpointer TwineedleName ; 0x083b
textpointer DoubleAttackX30Description ; 0x083c
- textpointer BeedrillKind ; 0x083d
+ textpointer PoisonBeeName ; 0x083d
textpointer BeedrillDescription ; 0x083e
textpointer EkansName ; 0x083f
textpointer SpitPoisonName ; 0x0840
textpointer WrapName ; 0x0841
- textpointer EkansKind ; 0x0842
+ textpointer SnakeName ; 0x0842
textpointer EkansDescription ; 0x0843
textpointer ArbokName ; 0x0844
textpointer TerrorStrikeName ; 0x0845
textpointer TerrorStrikeDescription ; 0x0846
textpointer PoisonFangName ; 0x0847
- textpointer ArbokKind ; 0x0848
+ textpointer CobraName ; 0x0848
textpointer ArbokDescription ; 0x0849
textpointer NidoranFName ; 0x084a
textpointer FurySweepesName ; 0x084b
textpointer TripleAttackX10Description ; 0x084c
textpointer CallForFamilyName ; 0x084d
textpointer NidoranFsCallForFamilyDescription ; 0x084e
- textpointer NidoranFKind ; 0x084f
+ textpointer PoisonPinName ; 0x084f
textpointer NidoranFDescription ; 0x0850
textpointer NidorinaName ; 0x0851
textpointer SupersonicName ; 0x0852
@@ -2139,7 +2139,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer BoyfriendsName ; 0x0857
textpointer BoyfriendsDescription ; 0x0858
textpointer MegaPunchName ; 0x0859
- textpointer NidoqueenKind ; 0x085a
+ textpointer DrillName ; 0x085a
textpointer NidoqueenDescription ; 0x085b
textpointer NidoranMName ; 0x085c
textpointer HornHazardName ; 0x085d
@@ -2157,7 +2157,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer ZubatName ; 0x0869
textpointer LeechLifeName ; 0x086a
textpointer ZubatsLeechLifeDescription ; 0x086b
- textpointer ZubatKind ; 0x086c
+ textpointer BatName ; 0x086c
textpointer ZubatDescription ; 0x086d
textpointer GolbatName ; 0x086e
textpointer WingAttackName ; 0x086f
@@ -2166,7 +2166,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer OddishName ; 0x0872
textpointer SproutName ; 0x0873
textpointer SproutDescription ; 0x0874
- textpointer OddishKind ; 0x0875
+ textpointer WeedName ; 0x0875
textpointer OddishDescription ; 0x0876
textpointer GloomName ; 0x0877
textpointer FoulOdorName ; 0x0878
@@ -2177,34 +2177,34 @@ TextOffsets:: ; 34000 (d:4000)
textpointer HealDescription ; 0x087d
textpointer PetalDanceName ; 0x087e
textpointer PetalDanceDescription ; 0x087f
- textpointer VileplumeKind ; 0x0880
+ textpointer FlowerName ; 0x0880
textpointer VileplumeDescription ; 0x0881
textpointer ParasName ; 0x0882
textpointer ScratchName ; 0x0883
textpointer SporeName ; 0x0884
textpointer InflictSleepDescription ; 0x0885
- textpointer ParasKind ; 0x0886
+ textpointer MushroomName ; 0x0886
textpointer ParasDescription ; 0x0887
textpointer ParasectName ; 0x0888
textpointer SlashName ; 0x0889
textpointer ParasectDescription ; 0x088a
textpointer VenonatName ; 0x088b
textpointer VenonatLeechLifeDescription ; 0x088c
- textpointer VenonatKind ; 0x088d
+ textpointer InsectName ; 0x088d
textpointer VenonatDescription ; 0x088e
textpointer VenomothName ; 0x088f
textpointer ShiftName ; 0x0890
textpointer ShiftDescription ; 0x0891
textpointer VenomPowderName ; 0x0892
textpointer VenomPowderDescription ; 0x0893
- textpointer VenomothKind ; 0x0894
+ textpointer PoisonmothName ; 0x0894
textpointer VenomothDescription ; 0x0895
textpointer BellsproutName ; 0x0896
textpointer BellsproutsCallForFamilyDescription ; 0x0897
textpointer BellsproutDescription ; 0x0898
textpointer WeepinbellName ; 0x0899
textpointer RazorLeafName ; 0x089a
- textpointer WeepinbellKind ; 0x089b
+ textpointer FlycatcherName ; 0x089b
textpointer WeepinbellDescription ; 0x089c
textpointer VictreebelName ; 0x089d
textpointer LureName ; 0x089e
@@ -2216,28 +2216,28 @@ TextOffsets:: ; 34000 (d:4000)
textpointer NastyGooName ; 0x08a4
textpointer MinimizeName ; 0x08a5
textpointer GrimersMinimizeDescription ; 0x08a6
- textpointer GrimerKindOrSludgeName ; 0x08a7
+ textpointer SludgeName ; 0x08a7
textpointer GrimerDescription ; 0x08a8
textpointer MukName ; 0x08a9
textpointer ToxicGasName ; 0x08aa
textpointer ToxicGasDescription ; 0x08ab
textpointer MukDescription ; 0x08ac
textpointer ExeggcuteName ; 0x08ad
- textpointer DrowzeeKindOrHypnosisName ; 0x08ae
+ textpointer HypnosisName ; 0x08ae
textpointer ExeggcutesLeechSeedDescription ; 0x08af
- textpointer ExeggcuteKind ; 0x08b0
+ textpointer EggName ; 0x08b0
textpointer ExeggcuteDescription ; 0x08b1
textpointer ExeggutorName ; 0x08b2
textpointer TeleportName ; 0x08b3
textpointer TeleportDescription ; 0x08b4
textpointer BigEggsplosionName ; 0x08b5
textpointer BigEggsplosionDescription ; 0x08b6
- textpointer ExeggutorKind ; 0x08b7
+ textpointer CoconutName ; 0x08b7
textpointer ExeggutorDescription ; 0x08b8
textpointer KoffingName ; 0x08b9
textpointer FoulGasName ; 0x08ba
textpointer FoulGasDescription ; 0x08bb
- textpointer KoffingKind ; 0x08bc
+ textpointer PoisonGasName ; 0x08bc
textpointer KoffingDescription ; 0x08bd
textpointer WeezingName ; 0x08be
textpointer SmogName ; 0x08bf
@@ -2246,29 +2246,29 @@ TextOffsets:: ; 34000 (d:4000)
textpointer WeezingDescription ; 0x08c2
textpointer TangelaName ; 0x08c3
textpointer BindName ; 0x08c4
- textpointer TangelaKind ; 0x08c5
+ textpointer VineName ; 0x08c5
textpointer Tangela1Description ; 0x08c6
textpointer PoisonWhipName ; 0x08c7
textpointer Tangela2Description ; 0x08c8
textpointer ScytherName ; 0x08c9
textpointer SwordsDanceName ; 0x08ca
textpointer SwordsDanceDescription ; 0x08cb
- textpointer ScytherKind ; 0x08cc
+ textpointer MantisName ; 0x08cc
textpointer ScytherDescription ; 0x08cd
textpointer PinsirName ; 0x08ce
textpointer IronGripName ; 0x08cf
textpointer GuillotineName ; 0x08d0
- textpointer PinsirKind ; 0x08d1
+ textpointer StagbeetleName ; 0x08d1
textpointer PinsirDescription ; 0x08d2
textpointer CharmanderName ; 0x08d3
textpointer EmberName ; 0x08d4
textpointer EmberDescription ; 0x08d5
- textpointer CharmanderKind ; 0x08d6
+ textpointer LizardName ; 0x08d6
textpointer CharmanderDescription ; 0x08d7
textpointer CharmeleonName ; 0x08d8
textpointer FlamethrowerName ; 0x08d9
textpointer CharmeleonsFlamethrowerDescription ; 0x08da
- textpointer CharmeleonKind ; 0x08db
+ textpointer FlameName ; 0x08db
textpointer CharmeleonDescription ; 0x08dc
textpointer CharizardName ; 0x08dd
textpointer EnergyBurnName ; 0x08de
@@ -2278,7 +2278,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer CharizardDescription ; 0x08e2
textpointer VulpixName ; 0x08e3
textpointer ConfuseRayName ; 0x08e4
- textpointer VulpixKind ; 0x08e5
+ textpointer FoxName ; 0x08e5
textpointer VulpixDescription ; 0x08e6
textpointer NinetailsName ; 0x08e7
textpointer NinetailsLureDescription ; 0x08e8
@@ -2293,14 +2293,14 @@ TextOffsets:: ; 34000 (d:4000)
textpointer Ninetails2Description ; 0x08f1
textpointer GrowlitheName ; 0x08f2
textpointer FlareName ; 0x08f3
- textpointer GrowlitheKind ; 0x08f4
+ textpointer PuppyName ; 0x08f4
textpointer GrowlitheDescription ; 0x08f5
textpointer ArcanineName ; 0x08f6
textpointer QuickAttackName ; 0x08f7
textpointer QuickAttackDescription ; 0x08f8
textpointer FlamesOfRageName ; 0x08f9
textpointer FlamesOfRageDescription ; 0x08fa
- textpointer ArcanineKind ; 0x08fb
+ textpointer LegendaryName ; 0x08fb
textpointer Arcanine1Description ; 0x08fc
textpointer ArcaninesFlamethrowerDescription ; 0x08fd
textpointer TakeDownName ; 0x08fe
@@ -2309,7 +2309,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer PonytaName ; 0x0901
textpointer SmashKickName ; 0x0902
textpointer FlameTailName ; 0x0903
- textpointer PonytaKind ; 0x0904
+ textpointer FireHorseName ; 0x0904
textpointer PonytaDescription ; 0x0905
textpointer RapidashName ; 0x0906
textpointer StompName ; 0x0907
@@ -2320,7 +2320,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer MagmarName ; 0x090c
textpointer FirePunchName ; 0x090d
textpointer FirePunchDescription ; 0x090e
- textpointer MagmarKind ; 0x090f
+ textpointer SpitfireName ; 0x090f
textpointer Magmar1Description ; 0x0910
textpointer SmokescreenName ; 0x0911
textpointer MagmarsSmokescreenDescription ; 0x0912
@@ -2345,11 +2345,11 @@ TextOffsets:: ; 34000 (d:4000)
textpointer BubbleName ; 0x0925
textpointer WithdrawName ; 0x0926
textpointer SquirtlesWithdrawDescription ; 0x0927
- textpointer SquirtleKind ; 0x0928
+ textpointer TinyTurtleName ; 0x0928
textpointer SquirtleDescription ; 0x0929
textpointer WartortleName ; 0x092a
textpointer WartortlesWithdrawDescription ; 0x092b
- textpointer WartortleKind ; 0x092c
+ textpointer TurtleName ; 0x092c
textpointer WartortleDescription ; 0x092d
textpointer BlastoiseName ; 0x092e
textpointer RainDanceName ; 0x092f
@@ -2357,12 +2357,12 @@ TextOffsets:: ; 34000 (d:4000)
textpointer RainDanceDescriptionCont ; 0x0931
textpointer HydroPumpName ; 0x0932
textpointer HydroPumpDescription ; 0x0933
- textpointer BlastoiseKind ; 0x0934
+ textpointer ShellfishName ; 0x0934
textpointer BlastoiseDescription ; 0x0935
textpointer PsyduckName ; 0x0936
textpointer HeadacheName ; 0x0937
textpointer HeadacheDescription ; 0x0938
- textpointer PsyduckKind ; 0x0939
+ textpointer DuckName ; 0x0939
textpointer PsyduckDescription ; 0x093a
textpointer GolduckName ; 0x093b
textpointer PsyshockName ; 0x093c
@@ -2372,7 +2372,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer PoliwagName ; 0x0940
textpointer WaterGunName ; 0x0941
textpointer PoliwagsWaterGunDescription ; 0x0942
- textpointer PoliwagKind ; 0x0943
+ textpointer TadpoleName ; 0x0943
textpointer PoliwagDescription ; 0x0944
textpointer PoliwhirlName ; 0x0945
textpointer AmnesiaName ; 0x0946
@@ -2386,14 +2386,14 @@ TextOffsets:: ; 34000 (d:4000)
textpointer TentacoolName ; 0x094e
textpointer CowardiceName ; 0x094f
textpointer CowardiceDescription ; 0x0950
- textpointer TentacoolKind ; 0x0951
+ textpointer JellyfishName ; 0x0951
textpointer TentacoolDescription ; 0x0952
textpointer TentacruelName ; 0x0953
textpointer JellyfishStingName ; 0x0954
textpointer TentacruelDescription ; 0x0955
textpointer SeelName ; 0x0956
textpointer HeadbuttName ; 0x0957
- textpointer SeelKind ; 0x0958
+ textpointer SeaLionName ; 0x0958
textpointer SeelDescription ; 0x0959
textpointer DewgongName ; 0x095a
textpointer AuroraBeamName ; 0x095b
@@ -2402,7 +2402,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer ShellderName ; 0x095e
textpointer HideInShellName ; 0x095f
textpointer HideInShellDescription ; 0x0960
- textpointer ShellderKind ; 0x0961
+ textpointer BivalveName ; 0x0961
textpointer ShellderDescription ; 0x0962
textpointer CloysterName ; 0x0963
textpointer ClampName ; 0x0964
@@ -2411,17 +2411,17 @@ TextOffsets:: ; 34000 (d:4000)
textpointer CloysterDescription ; 0x0967
textpointer KrabbyName ; 0x0968
textpointer KrabbysCallForFamilyDescription ; 0x0969
- textpointer KrabbyKind ; 0x096a
+ textpointer RiverCrabName ; 0x096a
textpointer KrabbyDescription ; 0x096b
textpointer KinglerName ; 0x096c
textpointer FlailName ; 0x096d
textpointer KinglersFlailDescription ; 0x096e
textpointer CrabhammerName ; 0x096f
- textpointer KinglerKind ; 0x0970
+ textpointer PincerName ; 0x0970
textpointer KinglerDescription ; 0x0971
textpointer HorseaName ; 0x0972
textpointer OpponentAttackMayDoNothingDescription ; 0x0973
- textpointer HorseaKind ; 0x0974
+ textpointer DragonName ; 0x0974
textpointer HorseaDescription ; 0x0975
textpointer SeadraName ; 0x0976
textpointer SeadrasWaterGunDescription ; 0x0977
@@ -2429,39 +2429,39 @@ TextOffsets:: ; 34000 (d:4000)
textpointer SeadraDescription ; 0x0979
textpointer GoldeenName ; 0x097a
textpointer HornAttackName ; 0x097b
- textpointer GoldeenKind ; 0x097c
+ textpointer GoldfishName ; 0x097c
textpointer GoldeenDescription ; 0x097d
textpointer SeakingName ; 0x097e
textpointer WaterfallName ; 0x097f
textpointer SeakingDescription ; 0x0980
textpointer StaryuName ; 0x0981
textpointer SlapName ; 0x0982
- textpointer StaryuKind ; 0x0983
+ textpointer StarshapeName ; 0x0983
textpointer StaryuDescription ; 0x0984
textpointer StarmieName ; 0x0985
textpointer RecoverName ; 0x0986
textpointer StarmiesRecoverDescription ; 0x0987
textpointer StarFreezeName ; 0x0988
- textpointer StarmieKind ; 0x0989
+ textpointer MysteriousName ; 0x0989
textpointer StarmieDescription ; 0x098a
textpointer MagikarpName ; 0x098b
textpointer TackleName ; 0x098c
textpointer MagikarpsFlailDescription ; 0x098d
- textpointer MagikarpKind ; 0x098e
+ textpointer FishName ; 0x098e
textpointer MagikarpDescription ; 0x098f
textpointer GyaradosName ; 0x0990
textpointer DragonRageName ; 0x0991
textpointer BubblebeamName ; 0x0992
- textpointer GyaradosKind ; 0x0993
+ textpointer AtrociousName ; 0x0993
textpointer GyaradosDescription ; 0x0994
textpointer LaprasName ; 0x0995
textpointer LaprasWaterGunDescription ; 0x0996
- textpointer LaprasKind ; 0x0997
+ textpointer TransportName ; 0x0997
textpointer LaprasDescription ; 0x0998
textpointer VaporeonName ; 0x0999
textpointer FocusEnergyName ; 0x099a
textpointer FocusEnergyDescription ; 0x099b
- textpointer VaporeonKind ; 0x099c
+ textpointer BubbleJetName ; 0x099c
textpointer Vaporeon1Description ; 0x099d
textpointer VaporeonsWaterGunDescription ; 0x099e
textpointer Vaporeon2Description ; 0x099f
@@ -2470,7 +2470,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer ClairvoyanceName ; 0x09a2
textpointer ClairvoyanceDescription ; 0x09a3
textpointer OmanytesWaterGunDescription ; 0x09a4
- textpointer OmanyteKind ; 0x09a5
+ textpointer SpiralName ; 0x09a5
textpointer OmanyteDescription ; 0x09a6
textpointer OmastarName ; 0x09a7
textpointer OmastarsWaterGunDescription ; 0x09a8
@@ -2479,7 +2479,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer FreezeDryName ; 0x09ab
textpointer BlizzardName ; 0x09ac
textpointer BlizzardDescription ; 0x09ad
- textpointer ArticunoKind ; 0x09ae
+ textpointer FreezeName ; 0x09ae
textpointer Articuno1Description ; 0x09af
textpointer QuickfreezeName ; 0x09b0
textpointer QuickfreezeDescription ; 0x09b1
@@ -2490,7 +2490,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer GnawName ; 0x09b6
textpointer ThunderJoltName ; 0x09b7
textpointer ThunderJoltDescription ; 0x09b8
- textpointer PikachuKind ; 0x09b9
+ textpointer MouseName ; 0x09b9
textpointer Pikachu1Description ; 0x09ba
textpointer SparkName ; 0x09bb
textpointer SparkDescription ; 0x09bc
@@ -2517,7 +2517,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer MagnemiteName ; 0x09d1
textpointer ThunderWaveName ; 0x09d2
textpointer MagnemitesSelfdestructDescription ; 0x09d3
- textpointer MagnemiteKind ; 0x09d4
+ textpointer MagnetName ; 0x09d4
textpointer Magnemite1Description ; 0x09d5
textpointer MagneticStormName ; 0x09d6
textpointer MagneticStormDescription ; 0x09d7
@@ -2530,7 +2530,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer Magneton2sSelfdestructDescription ; 0x09de
textpointer Magneton2Description ; 0x09df
textpointer VoltorbName ; 0x09e0
- textpointer VoltorbKind ; 0x09e1
+ textpointer BallName ; 0x09e1
textpointer VoltorbDescription ; 0x09e2
textpointer ElectrodeName ; 0x09e3
textpointer EnergySpikeName ; 0x09e4
@@ -2544,7 +2544,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer LightScreenDescription ; 0x09ec
textpointer LightScreenDescriptionCont ; 0x09ed
textpointer ElectabuzzsQuickAttackDescription ; 0x09ee
- textpointer ElectabuzzKind ; 0x09ef
+ textpointer ElectricName ; 0x09ef
textpointer Electabuzz1Description ; 0x09f0
textpointer ThunderpunchName ; 0x09f1
textpointer ThunderpunchDescription ; 0x09f2
@@ -2552,7 +2552,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer JolteonName ; 0x09f4
textpointer DoubleAttackX20Description ; 0x09f5
textpointer StunNeedleName ; 0x09f6
- textpointer JolteonKind ; 0x09f7
+ textpointer LightningName ; 0x09f7
textpointer Jolteon1Description ; 0x09f8
textpointer PinMissileName ; 0x09f9
textpointer QuadrupleAttackX20Description ; 0x09fa
@@ -2579,7 +2579,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer DiglettName ; 0x0a0f
textpointer DigName ; 0x0a10
textpointer MudSlapName ; 0x0a11
- textpointer DiglettKind ; 0x0a12
+ textpointer MoleName ; 0x0a12
textpointer DiglettDescription ; 0x0a13
textpointer DugtrioName ; 0x0a14
textpointer EarthquakeName ; 0x0a15
@@ -2589,7 +2589,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer PeekName ; 0x0a19
textpointer PeekDescription ; 0x0a1a
textpointer PeekDescriptionCont ; 0x0a1b
- textpointer MankeyKind ; 0x0a1c
+ textpointer PigMonkeyName ; 0x0a1c
textpointer MankeyDescription ; 0x0a1d
textpointer PrimeapeName ; 0x0a1e
textpointer TantrumName ; 0x0a1f
@@ -2597,7 +2597,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer PrimeapeDescription ; 0x0a21
textpointer MachopName ; 0x0a22
textpointer LowKickName ; 0x0a23
- textpointer MachopKindOrSuperpowerName ; 0x0a24
+ textpointer SuperpowerName ; 0x0a24
textpointer MachopDescription ; 0x0a25
textpointer MachokeName ; 0x0a26
textpointer KarateChopName ; 0x0a27
@@ -2614,7 +2614,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer GeodudeName ; 0x0a32
textpointer StoneBarrageName ; 0x0a33
textpointer StoneBarrageDescription ; 0x0a34
- textpointer GeodudeKind ; 0x0a35
+ textpointer RockName ; 0x0a35
textpointer GeodudeDescription ; 0x0a36
textpointer GravelerName ; 0x0a37
textpointer HardenName ; 0x0a38
@@ -2624,23 +2624,23 @@ TextOffsets:: ; 34000 (d:4000)
textpointer GolemName ; 0x0a3c
textpointer AvalancheName ; 0x0a3d
textpointer GolemsSelfdestructDescription ; 0x0a3e
- textpointer GolemKind ; 0x0a3f
+ textpointer MegatonName ; 0x0a3f
textpointer GolemDescription ; 0x0a40
textpointer OnixName ; 0x0a41
textpointer OnixsHardenDescription ; 0x0a42
- textpointer OnixKind ; 0x0a43
+ textpointer RockSnakeName ; 0x0a43
textpointer OnixDescription ; 0x0a44
textpointer CuboneName ; 0x0a45
textpointer SnivelName ; 0x0a46
textpointer SnivelDescription ; 0x0a47
textpointer CubonesRageDescription ; 0x0a48
- textpointer CuboneKind ; 0x0a49
+ textpointer LonelyName ; 0x0a49
textpointer CuboneDescription ; 0x0a4a
textpointer MarowakName ; 0x0a4b
textpointer BonemerangName ; 0x0a4c
textpointer CallforFriendName ; 0x0a4d
textpointer CallforFriendDescription ; 0x0a4e
- textpointer MarowakKind ; 0x0a4f
+ textpointer BonekeeperName ; 0x0a4f
textpointer Marowak1Description ; 0x0a50
textpointer BoneAttackName ; 0x0a51
textpointer BoneAttackDescription ; 0x0a52
@@ -2651,17 +2651,17 @@ TextOffsets:: ; 34000 (d:4000)
textpointer StretchKickName ; 0x0a57
textpointer StretchKickDescription ; 0x0a58
textpointer HighJumpKickName ; 0x0a59
- textpointer HitmonleeKind ; 0x0a5a
+ textpointer KickingName ; 0x0a5a
textpointer HitmonleeDescription ; 0x0a5b
textpointer HitmonchanName ; 0x0a5c
textpointer JabName ; 0x0a5d
textpointer SpecialPunch ; 0x0a5e
- textpointer HitmonchanKind ; 0x0a5f
+ textpointer PunchingName ; 0x0a5f
textpointer HitmonchanDescription ; 0x0a60
textpointer RhyhornName ; 0x0a61
textpointer LeerName ; 0x0a62
textpointer LeerDescription ; 0x0a63
- textpointer RhyhornKind ; 0x0a64
+ textpointer SpikeName ; 0x0a64
textpointer RhyhornDescription ; 0x0a65
textpointer RhydonName ; 0x0a66
textpointer RamName ; 0x0a67
@@ -2682,10 +2682,10 @@ TextOffsets:: ; 34000 (d:4000)
textpointer AerodactylName ; 0x0a76
textpointer PrehistoricPowerName ; 0x0a77
textpointer PrehistoricPowerDescription ; 0x0a78
- textpointer AerodactylKind ; 0x0a79
+ textpointer FossilName ; 0x0a79
textpointer AerodactylDescription ; 0x0a7a
textpointer AbraName ; 0x0a7b
- textpointer AbraKind ; 0x0a7c
+ textpointer PsiName ; 0x0a7c
textpointer AbraDescription ; 0x0a7d
textpointer KadabraName ; 0x0a7e
textpointer KadabrasRecoverDescription ; 0x0a7f
@@ -2697,7 +2697,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer AlakazamDescription ; 0x0a85
textpointer SlowpokeName ; 0x0a86
textpointer SlowpokesAmnesiaDescription ; 0x0a87
- textpointer SlowpokeKind ; 0x0a88
+ textpointer DopeyName ; 0x0a88
textpointer Slowpoke1Description ; 0x0a89
textpointer SpacingOutName ; 0x0a8a
textpointer SpacingOutDescription ; 0x0a8b
@@ -2706,14 +2706,14 @@ TextOffsets:: ; 34000 (d:4000)
textpointer SlowbroName ; 0x0a8e
textpointer StrangeBehaviorName ; 0x0a8f
textpointer StrangeBehaviorDescription ; 0x0a90
- textpointer SlowbroKind ; 0x0a91
+ textpointer HermitcrabName ; 0x0a91
textpointer SlowbroDescription ; 0x0a92
textpointer GastlyName ; 0x0a93
textpointer SleepingGasName ; 0x0a94
textpointer MayInflictSleepDescription ; 0x0a95
textpointer DestinyBondName ; 0x0a96
textpointer DestinyBondDescription ; 0x0a97
- textpointer GastlyKind ; 0x0a98
+ textpointer GasName ; 0x0a98
textpointer Gastly1Description ; 0x0a99
textpointer LickName ; 0x0a9a
textpointer EnergyConversionName ; 0x0a9b
@@ -2731,7 +2731,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer CurseDescription ; 0x0aa7
textpointer DarkMindName ; 0x0aa8
textpointer DarkMindDescription ; 0x0aa9
- textpointer GengarKind ; 0x0aaa
+ textpointer ShadowName ; 0x0aaa
textpointer GengarDescription ; 0x0aab
textpointer DrowzeeName ; 0x0aac
textpointer PoundName ; 0x0aad
@@ -2746,18 +2746,18 @@ TextOffsets:: ; 34000 (d:4000)
textpointer InvisibleWallDescriptionCont ; 0x0ab6
textpointer MeditateName ; 0x0ab7
textpointer MrMimesMeditateDescription ; 0x0ab8
- textpointer MrMimeKindOrBarrierName ; 0x0ab9
+ textpointer BarrierName ; 0x0ab9
textpointer MrMimeDescription ; 0x0aba
textpointer JynxName ; 0x0abb
textpointer DoubleAttackX10Description ; 0x0abc
textpointer JynxsMeditateDescription ; 0x0abd
- textpointer JynxKind ; 0x0abe
+ textpointer HumanShapeName ; 0x0abe
textpointer JynxDescription ; 0x0abf
textpointer MewtwoName ; 0x0ac0
textpointer PsychicName ; 0x0ac1
textpointer PsychicDescription ; 0x0ac2
textpointer BarrierDescription ; 0x0ac3
- textpointer MewtwoKind ; 0x0ac4
+ textpointer GeneticName ; 0x0ac4
textpointer Mewtwo1Description ; 0x0ac5
textpointer EnergyAbsorptionName ; 0x0ac6
textpointer EnergyAbsorptionDescription ; 0x0ac7
@@ -2766,7 +2766,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer MewName ; 0x0aca
textpointer NeutralizingShieldName ; 0x0acb
textpointer NeutralizingShieldDescription ; 0x0acc
- textpointer MewKind ; 0x0acd
+ textpointer NewSpeciesName ; 0x0acd
textpointer Mew1Description ; 0x0ace
textpointer MysteryAttackName ; 0x0acf
textpointer MysteryAttackDescription ; 0x0ad0
@@ -2776,12 +2776,12 @@ TextOffsets:: ; 34000 (d:4000)
textpointer DevolutionBeamName ; 0x0ad4
textpointer DevolutionBeamDescription ; 0x0ad5
textpointer PidgeyName ; 0x0ad6
- textpointer PidgeyKind ; 0x0ad7
+ textpointer TinyBirdName ; 0x0ad7
textpointer PidgeyDescription ; 0x0ad8
textpointer PidgeottoName ; 0x0ad9
textpointer MirrorMoveName ; 0x0ada
textpointer PidgeottosMirrorMoveDescription ; 0x0adb
- textpointer PidgeottoKind ; 0x0adc
+ textpointer BirdName ; 0x0adc
textpointer PidgeottoDescription ; 0x0add
textpointer PidgeotName ; 0x0ade
textpointer SlicingWindName ; 0x0adf
@@ -2793,7 +2793,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer HurricaneDescription ; 0x0ae5
textpointer Pidgeot2Description ; 0x0ae6
textpointer RattataName ; 0x0ae7
- textpointer RattataKind ; 0x0ae8
+ textpointer RatName ; 0x0ae8
textpointer RattataDescription ; 0x0ae9
textpointer RaticateName ; 0x0aea
textpointer SuperFangName ; 0x0aeb
@@ -2806,13 +2806,13 @@ TextOffsets:: ; 34000 (d:4000)
textpointer FearowName ; 0x0af2
textpointer FearowsAgilityDescription ; 0x0af3
textpointer DrillPeckName ; 0x0af4
- textpointer FearowKind ; 0x0af5
+ textpointer BeakName ; 0x0af5
textpointer FearowDescription ; 0x0af6
textpointer ClefairyName ; 0x0af7
textpointer SingName ; 0x0af8
textpointer MetronomeName ; 0x0af9
textpointer ClefairysMetronomeDescription ; 0x0afa
- textpointer ClefairyKind ; 0x0afb
+ textpointer FairyName ; 0x0afb
textpointer ClefairyDescription ; 0x0afc
textpointer ClefableName ; 0x0afd
textpointer ClefablesMetronomeDescription ; 0x0afe
@@ -2823,7 +2823,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer FirstAidDescription ; 0x0b03
textpointer DoubleEdgeName ; 0x0b04
textpointer JigglypuffsDoubleEdgeDescription ; 0x0b05
- textpointer JigglypuffKind ; 0x0b06
+ textpointer BalloonName ; 0x0b06
textpointer Jigglypuff1Description ; 0x0b07
textpointer FriendshipSongName ; 0x0b08
textpointer FriendshipSongDescription ; 0x0b09
@@ -2839,7 +2839,7 @@ TextOffsets:: ; 34000 (d:4000)
textpointer MeowthName ; 0x0b13
textpointer CatPunchName ; 0x0b14
textpointer CatPunchDescription ; 0x0b15
- textpointer MeowthKind ; 0x0b16
+ textpointer ScratchCatName ; 0x0b16
textpointer Meowth1Description ; 0x0b17
textpointer PayDayName ; 0x0b18
textpointer PayDayDescription ; 0x0b19
@@ -2847,27 +2847,27 @@ TextOffsets:: ; 34000 (d:4000)
textpointer PersianName ; 0x0b1b
textpointer PounceName ; 0x0b1c
textpointer PounceDescription ; 0x0b1d
- textpointer PersianKind ; 0x0b1e
+ textpointer ClassyCatName ; 0x0b1e
textpointer PersianDescription ; 0x0b1f
textpointer FarfetchdName ; 0x0b20
textpointer LeekSlapName ; 0x0b21
textpointer LeekSlapDescription ; 0x0b22
textpointer PotSmashName ; 0x0b23
- textpointer FarfetchdKind ; 0x0b24
+ textpointer WildDuckName ; 0x0b24
textpointer FarfetchdDescription ; 0x0b25
textpointer DoduoName ; 0x0b26
textpointer FuryAttackName ; 0x0b27
- textpointer DoduoKind ; 0x0b28
+ textpointer TwinBirdName ; 0x0b28
textpointer DoduoDescription ; 0x0b29
textpointer DodrioName ; 0x0b2a
textpointer RetreatAidName ; 0x0b2b
textpointer RetreatAidDescription ; 0x0b2c
textpointer DodriosRageDescription ; 0x0b2d
- textpointer DodrioKind ; 0x0b2e
+ textpointer TriplebirdName ; 0x0b2e
textpointer DodrioDescription ; 0x0b2f
textpointer LickitungName ; 0x0b30
textpointer TongueWrapName ; 0x0b31
- textpointer LickitungKind ; 0x0b32
+ textpointer LickingName ; 0x0b32
textpointer LickitungDescription ; 0x0b33
textpointer ChanseyName ; 0x0b34
textpointer ScrunchName ; 0x0b35
@@ -2878,35 +2878,35 @@ TextOffsets:: ; 34000 (d:4000)
textpointer FetchName ; 0x0b3a
textpointer FetchDescription ; 0x0b3b
textpointer CometPunchName ; 0x0b3c
- textpointer KangaskhanKind ; 0x0b3d
+ textpointer ParentName ; 0x0b3d
textpointer KangaskhanDescription ; 0x0b3e
textpointer TaurosName ; 0x0b3f
textpointer RampageName ; 0x0b40
textpointer RampageDescription ; 0x0b41
- textpointer TaurosKind ; 0x0b42
+ textpointer WildBullName ; 0x0b42
textpointer TaurosDescription ; 0x0b43
textpointer DittoName ; 0x0b44
textpointer MorphName ; 0x0b45
textpointer MorphDescription ; 0x0b46
textpointer MorphDescriptionCont ; 0x0b47
- textpointer DittoKind ; 0x0b48
+ textpointer TransformName ; 0x0b48
textpointer DittoDescription ; 0x0b49
textpointer TailWagName ; 0x0b4a
textpointer TailWagDescription ; 0x0b4b
- textpointer EeveeKind ; 0x0b4c
+ textpointer EvolutionName ; 0x0b4c
textpointer EeveeDescription ; 0x0b4d
textpointer PorygonName ; 0x0b4e
textpointer Conversion1Name ; 0x0b4f
textpointer Conversion1Description ; 0x0b50
textpointer Conversion2Name ; 0x0b51
textpointer Conversion2Description ; 0x0b52
- textpointer PorygonKind ; 0x0b53
+ textpointer VirtualName ; 0x0b53
textpointer PorygonDescription ; 0x0b54
textpointer SnorlaxName ; 0x0b55
textpointer ThickSkinnedName ; 0x0b56
textpointer ThickSkinnedDescription ; 0x0b57
textpointer BodySlamName ; 0x0b58
- textpointer SnorlaxKind ; 0x0b59
+ textpointer SleepingName ; 0x0b59
textpointer SnorlaxDescription ; 0x0b5a
textpointer DratiniName ; 0x0b5b
textpointer DratiniDescription ; 0x0b5c
diff --git a/src/wram.asm b/src/wram.asm
index 84cd374..835c29a 100644
--- a/src/wram.asm
+++ b/src/wram.asm
@@ -512,7 +512,12 @@ wBackgroundPalettesCGB:: ; caf0
wObjectPalettesCGB:: ; cb30
ds 8 palettes
- ds $4
+ ds $2
+
+; stores a pointer to a temporary list of elements (e.g. pointer to wDuelTempList)
+; to be read or written sequentially
+wListPointer:: ; cb72
+ ds $2
SECTION "WRAM Serial Transfer", WRAM0
@@ -642,7 +647,8 @@ wCardListHeaderText:: ; cbdc
wcbde:: ; cbde
ds $1
-wcbdf:: ; cbdf
+; flag indicating whether a list of cards should be sorted by ID
+wSortCardListByID:: ; cbdf
ds $1
wcbe0:: ; cbe0
@@ -958,7 +964,7 @@ wListItemXPosition:: ; cd1a
wNumListItems:: ; cd1b
ds $1
-wcd1c:: ; cd1c
+wListItemNameMaxLength:: ; cd1c
ds $1
wListFunctionPointer:: ; cd1d
@@ -1063,7 +1069,7 @@ wce2b:: ; ce2b
wTxRam2:: ; cd3f
ds $2
-; text pointer for the second TX_RAM2 on a text
+; text pointer for the second TX_RAM2 of a text
wTxRam2_b:: ; ce41
ds $2
@@ -1298,6 +1304,8 @@ wd0aa:: ; d0aa
wd0b4:: ; d0b4
ds $1
+; a GAME_EVENT_* constant corresponding to an entry in GameEventPointerTable
+; overworld, duel, credits...
wGameEvent:: ; d0b5
ds $1
diff --git a/tools/configuration.py b/tools/configuration.py
new file mode 100644
index 0000000..6bdb7e8
--- /dev/null
+++ b/tools/configuration.py
@@ -0,0 +1,57 @@
+"""
+Configuration
+"""
+
+import os
+
+class ConfigException(Exception):
+ """
+ Configuration error. Maybe a missing config variable.
+ """
+
+class Config(object):
+ """
+ The Config class handles all configuration for pokemontools. Other classes
+ and functions use a Config object to determine where expected files can be
+ located.
+ """
+
+ def __init__(self, **kwargs):
+ """
+ Store all parameters.
+ """
+ self._config = {}
+
+ for (key, value) in kwargs.items():
+ if key not in self.__dict__:
+ self._config[key] = value
+ else:
+ raise ConfigException(
+ "Can't store \"{0}\" in configuration because the key conflicts with an existing property."
+ .format(key)
+ )
+
+ if "path" not in self._config:
+ self._config["path"] = os.getcwd()
+
+ # vba save states go into ./save-states/
+ if "save_state_path" not in self._config:
+ self._config["save_state_path"] = os.path.join(self._config["path"], "save-states/")
+
+ # assume rom is at ./baserom.gbc
+ if "rom" not in self._config:
+ self._config["rom_path"] = os.path.join(self._config["path"], "baserom.gbc")
+
+ def __getattr__(self, key):
+ """
+ Grab the value from the class properties, then check the configuration,
+ and raise an exception if nothing works.
+ """
+ if key in self.__dict__:
+ return self.__dict__[key]
+ elif key in self._config:
+ return self._config[key]
+ else:
+ raise ConfigException(
+ "no config found for \"{0}\"".format(key)
+ )
diff --git a/tools/gfx.py b/tools/gfx.py
new file mode 100644
index 0000000..3bdc748
--- /dev/null
+++ b/tools/gfx.py
@@ -0,0 +1,893 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import png
+from math import sqrt, floor, ceil
+import argparse
+import operator
+
+import configuration
+config = configuration.Config()
+
+
+def split(list_, interval):
+ """
+ Split a list by length.
+ """
+ for i in xrange(0, len(list_), interval):
+ j = min(i + interval, len(list_))
+ yield list_[i:j]
+
+
+def hex_dump(data, length=0x10):
+ """
+ just use hexdump -C
+ """
+ margin = len('%x' % len(data))
+ output = []
+ address = 0
+ for line in split(data, length):
+ output += [
+ hex(address)[2:].zfill(margin) +
+ ' | ' +
+ ' '.join('%.2x' % byte for byte in line)
+ ]
+ address += length
+ return '\n'.join(output)
+
+
+def get_tiles(image):
+ """
+ Split a 2bpp image into 8x8 tiles.
+ """
+ return list(split(image, 0x10))
+
+def connect(tiles):
+ """
+ Combine 8x8 tiles into a 2bpp image.
+ """
+ return [byte for tile in tiles for byte in tile]
+
+def transpose(tiles, width=None):
+ """
+ Transpose a tile arrangement along line y=-x.
+
+ 00 01 02 03 04 05 00 06 0c 12 18 1e
+ 06 07 08 09 0a 0b 01 07 0d 13 19 1f
+ 0c 0d 0e 0f 10 11 <-> 02 08 0e 14 1a 20
+ 12 13 14 15 16 17 03 09 0f 15 1b 21
+ 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22
+ 1e 1f 20 21 22 23 05 0b 11 17 1d 23
+
+ 00 01 02 03 00 04 08
+ 04 05 06 07 <-> 01 05 09
+ 08 09 0a 0b 02 06 0a
+ 03 07 0b
+ """
+ if width == None:
+ width = int(sqrt(len(tiles))) # assume square image
+ tiles = sorted(enumerate(tiles), key= lambda (i, tile): i % width)
+ return [tile for i, tile in tiles]
+
+def transpose_tiles(image, width=None):
+ return connect(transpose(get_tiles(image), width))
+
+def interleave(tiles, width):
+ """
+ 00 01 02 03 04 05 00 02 04 06 08 0a
+ 06 07 08 09 0a 0b 01 03 05 07 09 0b
+ 0c 0d 0e 0f 10 11 --> 0c 0e 10 12 14 16
+ 12 13 14 15 16 17 0d 0f 11 13 15 17
+ 18 19 1a 1b 1c 1d 18 1a 1c 1e 20 22
+ 1e 1f 20 21 22 23 19 1b 1d 1f 21 23
+ """
+ interleaved = []
+ left, right = split(tiles[::2], width), split(tiles[1::2], width)
+ for l, r in zip(left, right):
+ interleaved += l + r
+ return interleaved
+
+def deinterleave(tiles, width):
+ """
+ 00 02 04 06 08 0a 00 01 02 03 04 05
+ 01 03 05 07 09 0b 06 07 08 09 0a 0b
+ 0c 0e 10 12 14 16 --> 0c 0d 0e 0f 10 11
+ 0d 0f 11 13 15 17 12 13 14 15 16 17
+ 18 1a 1c 1e 20 22 18 19 1a 1b 1c 1d
+ 19 1b 1d 1f 21 23 1e 1f 20 21 22 23
+ """
+ deinterleaved = []
+ rows = list(split(tiles, width))
+ for left, right in zip(rows[::2], rows[1::2]):
+ for l, r in zip(left, right):
+ deinterleaved += [l, r]
+ return deinterleaved
+
+def interleave_tiles(image, width):
+ return connect(interleave(get_tiles(image), width))
+
+def deinterleave_tiles(image, width):
+ return connect(deinterleave(get_tiles(image), width))
+
+
+def condense_image_to_map(image, pic=0):
+ """
+ Reduce an image of adjacent frames to an image containing a base frame and any unrepeated tiles.
+ Returns the new image and the corresponding tilemap used to reconstruct the input image.
+
+ If <pic> is 0, ignore the concept of frames. This behavior might be better off as another function.
+ """
+ tiles = get_tiles(image)
+ new_tiles, tilemap = condense_tiles_to_map(tiles, pic)
+ new_image = connect(new_tiles)
+ return new_image, tilemap
+
+def condense_tiles_to_map(tiles, pic=0):
+ """
+ Reduce a sequence of tiles representing adjacent frames to a base frame and any unrepeated tiles.
+ Returns the new tiles and the corresponding tilemap used to reconstruct the input tile sequence.
+
+ If <pic> is 0, ignore the concept of frames. This behavior might be better off as another function.
+ """
+
+ # Leave the first frame intact for pics.
+ new_tiles = tiles[:pic]
+ tilemap = range(pic)
+
+ for i, tile in enumerate(tiles[pic:]):
+ if tile not in new_tiles:
+ new_tiles.append(tile)
+
+ if pic:
+ # Match the first frame exactly where possible.
+ # This reduces the space needed to replace tiles in pic animations.
+ # For example, if a tile is repeated twice in the first frame,
+ # but at the same relative index as the second tile, use the second index.
+ # When creating a bitmask later, the second index would not require a replacement, but the first index would have.
+ pic_i = i % pic
+ if tile == new_tiles[pic_i]:
+ tilemap.append(pic_i)
+ else:
+ tilemap.append(new_tiles.index(tile))
+ else:
+ tilemap.append(new_tiles.index(tile))
+ return new_tiles, tilemap
+
+def test_condense_tiles_to_map():
+ test = condense_tiles_to_map(list('abcadbae'))
+ if test != (list('abcde'), [0, 1, 2, 0, 3, 1, 0, 4]):
+ raise Exception(test)
+ test = condense_tiles_to_map(list('abcadbae'), 2)
+ if test != (list('abcde'), [0, 1, 2, 0, 3, 1, 0, 4]):
+ raise Exception(test)
+ test = condense_tiles_to_map(list('abcadbae'), 4)
+ if test != (list('abcade'), [0, 1, 2, 3, 4, 1, 0, 5]):
+ raise Exception(test)
+ test = condense_tiles_to_map(list('abcadbea'), 4)
+ if test != (list('abcade'), [0, 1, 2, 3, 4, 1, 5, 3]):
+ raise Exception(test)
+
+
+def to_file(filename, data):
+ """
+ Apparently open(filename, 'wb').write(bytearray(data)) won't work.
+ """
+ file = open(filename, 'wb')
+ for byte in data:
+ file.write('%c' % byte)
+ file.close()
+
+
+def bin_to_rgb(word):
+ red = word & 0b11111
+ word >>= 5
+ green = word & 0b11111
+ word >>= 5
+ blue = word & 0b11111
+ return (red, green, blue)
+
+def convert_binary_pal_to_text_by_filename(filename):
+ pal = bytearray(open(filename).read())
+ return convert_binary_pal_to_text(pal)
+
+def convert_binary_pal_to_text(pal):
+ output = ''
+ words = [hi * 0x100 + lo for lo, hi in zip(pal[::2], pal[1::2])]
+ for word in words:
+ red, green, blue = ['%.2d' % c for c in bin_to_rgb(word)]
+ output += '\tRGB ' + ', '.join((red, green, blue))
+ output += '\n'
+ return output
+
+def read_rgb_macros(lines):
+ colors = []
+ for line in lines:
+ macro = line.split(" ")[0].strip()
+ if macro == 'RGB':
+ params = ' '.join(line.split(" ")[1:]).split(',')
+ red, green, blue = [int(v) for v in params]
+ colors += [[red, green, blue]]
+ return colors
+
+
+def rewrite_binary_pals_to_text(filenames):
+ for filename in filenames:
+ pal_text = convert_binary_pal_to_text_by_filename(filename)
+ with open(filename, 'w') as out:
+ out.write(pal_text)
+
+
+def flatten(planar):
+ """
+ Flatten planar 2bpp image data into a quaternary pixel map.
+ """
+ strips = []
+ for bottom, top in split(planar, 2):
+ bottom = bottom
+ top = top
+ strip = []
+ for i in xrange(7,-1,-1):
+ color = (
+ (bottom >> i & 1) +
+ (top *2 >> i & 2)
+ )
+ strip += [color]
+ strips += strip
+ return strips
+
+def to_lines(image, width):
+ """
+ Convert a tiled quaternary pixel map to lines of quaternary pixels.
+ """
+ tile_width = 8
+ tile_height = 8
+ num_columns = width / tile_width
+ height = len(image) / width
+
+ lines = []
+ for cur_line in xrange(height):
+ tile_row = cur_line / tile_height
+ line = []
+ for column in xrange(num_columns):
+ anchor = (
+ num_columns * tile_row * tile_width * tile_height +
+ column * tile_width * tile_height +
+ cur_line % tile_height * tile_width
+ )
+ line += image[anchor : anchor + tile_width]
+ lines += [line]
+ return lines
+
+
+def dmg2rgb(word):
+ """
+ For PNGs.
+ """
+ def shift(value):
+ while True:
+ yield value & (2**5 - 1)
+ value >>= 5
+ word = shift(word)
+ # distribution is less even w/ << 3
+ red, green, blue = [int(color * 8.25) for color in [word.next() for _ in xrange(3)]]
+ alpha = 255
+ return (red, green, blue, alpha)
+
+
+def rgb_to_dmg(color):
+ """
+ For PNGs.
+ """
+ word = (color['r'] / 8)
+ word += (color['g'] / 8) << 5
+ word += (color['b'] / 8) << 10
+ return word
+
+
+def pal_to_png(filename):
+ """
+ Interpret a .pal file as a png palette.
+ """
+ with open(filename) as rgbs:
+ colors = read_rgb_macros(rgbs.readlines())
+ a = 255
+ palette = []
+ for color in colors:
+ # even distribution over 000-255
+ r, g, b = [int(hue * 8.25) for hue in color]
+ palette += [(r, g, b, a)]
+ white = (255,255,255,255)
+ black = (000,000,000,255)
+ if white not in palette and len(palette) < 4:
+ palette = [white] + palette
+ if black not in palette and len(palette) < 4:
+ palette = palette + [black]
+ return palette
+
+
+def png_to_rgb(palette):
+ """
+ Convert a png palette to rgb macros.
+ """
+ output = ''
+ for color in palette:
+ r, g, b = [color[c] / 8 for c in 'rgb']
+ output += '\tRGB ' + ', '.join(['%.2d' % hue for hue in (r, g, b)])
+ output += '\n'
+ return output
+
+
+def read_filename_arguments(filename):
+ """
+ Infer graphics conversion arguments given a filename.
+
+ Arguments are separated with '.'.
+ """
+ parsed_arguments = {}
+
+ int_arguments = {
+ 'w': 'width',
+ 'h': 'height',
+ 't': 'tile_padding',
+ }
+ arguments = os.path.splitext(filename)[0].lstrip('.').split('.')[1:]
+ for argument in arguments:
+
+ # Check for integer arguments first (i.e. "w128").
+ arg = argument[0]
+ param = argument[1:]
+ if param.isdigit():
+ arg = int_arguments.get(arg, False)
+ if arg:
+ parsed_arguments[arg] = int(param)
+
+ elif argument == 'arrange':
+ parsed_arguments['norepeat'] = True
+ parsed_arguments['tilemap'] = True
+
+ # Pic dimensions (i.e. "6x6").
+ elif 'x' in argument and any(map(str.isdigit, argument)):
+ w, h = argument.split('x')
+ if w.isdigit() and h.isdigit():
+ parsed_arguments['pic_dimensions'] = (int(w), int(h))
+
+ else:
+ parsed_arguments[argument] = True
+
+ return parsed_arguments
+
+
+def export_2bpp_to_png(filein, fileout=None, pal_file=None, height=0, width=0, tile_padding=0, pic_dimensions=None, **kwargs):
+
+ if fileout == None:
+ fileout = os.path.splitext(filein)[0] + '.png'
+
+ image = open(filein, 'rb').read()
+
+ arguments = {
+ 'width': width,
+ 'height': height,
+ 'pal_file': pal_file,
+ 'tile_padding': tile_padding,
+ 'pic_dimensions': pic_dimensions,
+ }
+ arguments.update(read_filename_arguments(filein))
+
+ if pal_file == None:
+ if os.path.exists(os.path.splitext(fileout)[0]+'.pal'):
+ arguments['pal_file'] = os.path.splitext(fileout)[0]+'.pal'
+
+ result = convert_2bpp_to_png(image, **arguments)
+ width, height, palette, greyscale, bitdepth, px_map = result
+
+ w = png.Writer(
+ width,
+ height,
+ palette=palette,
+ compression=9,
+ greyscale=greyscale,
+ bitdepth=bitdepth
+ )
+ with open(fileout, 'wb') as f:
+ w.write(f, px_map)
+
+
+def convert_2bpp_to_png(image, **kwargs):
+ """
+ Convert a planar 2bpp graphic to png.
+ """
+
+ image = bytearray(image)
+
+ pad_color = bytearray([0])
+
+ width = kwargs.get('width', 0)
+ height = kwargs.get('height', 0)
+ tile_padding = kwargs.get('tile_padding', 0)
+ pic_dimensions = kwargs.get('pic_dimensions', None)
+ pal_file = kwargs.get('pal_file', None)
+ interleave = kwargs.get('interleave', False)
+
+ # Width must be specified to interleave.
+ if interleave and width:
+ image = interleave_tiles(image, width / 8)
+
+ # Pad the image by a given number of tiles if asked.
+ image += pad_color * 0x10 * tile_padding
+
+ # Some images are transposed in blocks.
+ if pic_dimensions:
+ w, h = pic_dimensions
+ if not width: width = w * 8
+
+ pic_length = w * h * 0x10
+
+ trailing = len(image) % pic_length
+
+ pic = []
+ for i in xrange(0, len(image) - trailing, pic_length):
+ pic += transpose_tiles(image[i:i+pic_length], h)
+ image = bytearray(pic) + image[len(image) - trailing:]
+
+ # Pad out trailing lines.
+ image += pad_color * 0x10 * ((w - (len(image) / 0x10) % h) % w)
+
+ def px_length(img):
+ return len(img) * 4
+ def tile_length(img):
+ return len(img) * 4 / (8*8)
+
+ if width and height:
+ tile_width = width / 8
+ more_tile_padding = (tile_width - (tile_length(image) % tile_width or tile_width))
+ image += pad_color * 0x10 * more_tile_padding
+
+ elif width and not height:
+ tile_width = width / 8
+ more_tile_padding = (tile_width - (tile_length(image) % tile_width or tile_width))
+ image += pad_color * 0x10 * more_tile_padding
+ height = px_length(image) / width
+
+ elif height and not width:
+ tile_height = height / 8
+ more_tile_padding = (tile_height - (tile_length(image) % tile_height or tile_height))
+ image += pad_color * 0x10 * more_tile_padding
+ width = px_length(image) / height
+
+ # at least one dimension should be given
+ if width * height != px_length(image):
+ # look for possible combos of width/height that would form a rectangle
+ matches = []
+ # Height need not be divisible by 8, but width must.
+ # See pokered gfx/minimize_pic.1bpp.
+ for w in range(8, px_length(image) / 2 + 1, 8):
+ h = px_length(image) / w
+ if w * h == px_length(image):
+ matches += [(w, h)]
+ # go for the most square image
+ if len(matches):
+ width, height = sorted(matches, key= lambda (w, h): (h % 8 != 0, w + h))[0] # favor height
+ else:
+ raise Exception, 'Image can\'t be divided into tiles (%d px)!' % (px_length(image))
+
+ # convert tiles to lines
+ lines = to_lines(flatten(image), width)
+
+ if pal_file == None:
+ palette = None
+ greyscale = True
+ bitdepth = 2
+ px_map = [[3 - pixel for pixel in line] for line in lines]
+
+ else: # gbc color
+ palette = pal_to_png(pal_file)
+ greyscale = False
+ bitdepth = 8
+ px_map = [[pixel for pixel in line] for line in lines]
+
+ return width, height, palette, greyscale, bitdepth, px_map
+
+
+def get_pic_animation(tmap, w, h):
+ """
+ Generate pic animation data from a combined tilemap of each frame.
+ """
+ frame_text = ''
+ bitmask_text = ''
+
+ frames = list(split(tmap, w * h))
+ base = frames.pop(0)
+ bitmasks = []
+
+ for i in xrange(len(frames)):
+ frame_text += '\tdw .frame{}\n'.format(i + 1)
+
+ for i, frame in enumerate(frames):
+ bitmask = map(operator.ne, frame, base)
+ if bitmask not in bitmasks:
+ bitmasks.append(bitmask)
+ which_bitmask = bitmasks.index(bitmask)
+
+ mask = iter(bitmask)
+ masked_frame = filter(lambda _: mask.next(), frame)
+
+ frame_text += '.frame{}\n'.format(i + 1)
+ frame_text += '\tdb ${:02x} ; bitmask\n'.format(which_bitmask)
+ if masked_frame:
+ frame_text += '\tdb {}\n'.format(', '.join(
+ map('${:02x}'.format, masked_frame)
+ ))
+
+ for i, bitmask in enumerate(bitmasks):
+ bitmask_text += '; {}\n'.format(i)
+ for byte in split(bitmask, 8):
+ byte = int(''.join(map(int.__repr__, reversed(byte))), 2)
+ bitmask_text += '\tdb %{:08b}\n'.format(byte)
+
+ return frame_text, bitmask_text
+
+
+def export_png_to_2bpp(filein, fileout=None, palout=None, **kwargs):
+
+ arguments = {
+ 'tile_padding': 0,
+ 'pic_dimensions': None,
+ 'animate': False,
+ 'stupid_bitmask_hack': [],
+ }
+ arguments.update(kwargs)
+ arguments.update(read_filename_arguments(filein))
+
+ image, arguments = png_to_2bpp(filein, **arguments)
+
+ if fileout == None:
+ fileout = os.path.splitext(filein)[0] + '.2bpp'
+ to_file(fileout, image)
+
+ tmap = arguments.get('tmap')
+
+ if tmap != None and arguments['animate'] and arguments['pic_dimensions']:
+ # Generate pic animation data.
+ frame_text, bitmask_text = get_pic_animation(tmap, *arguments['pic_dimensions'])
+
+ frames_path = os.path.join(os.path.split(fileout)[0], 'frames.asm')
+ with open(frames_path, 'w') as out:
+ out.write(frame_text)
+
+ bitmask_path = os.path.join(os.path.split(fileout)[0], 'bitmask.asm')
+
+ # The following Pokemon have a bitmask dummied out.
+ for exception in arguments['stupid_bitmask_hack']:
+ if exception in bitmask_path:
+ bitmasks = bitmask_text.split(';')
+ bitmasks[-1] = bitmasks[-1].replace('1', '0')
+ bitmask_text = ';'.join(bitmasks)
+
+ with open(bitmask_path, 'w') as out:
+ out.write(bitmask_text)
+
+ elif tmap != None and arguments.get('tilemap', False):
+ tilemap_path = os.path.splitext(fileout)[0] + '.tilemap'
+ to_file(tilemap_path, tmap)
+
+ palette = arguments.get('palette')
+ if palout == None:
+ palout = os.path.splitext(fileout)[0] + '.pal'
+ export_palette(palette, palout)
+
+
+def get_image_padding(width, height, wstep=8, hstep=8):
+
+ padding = {
+ 'left': 0,
+ 'right': 0,
+ 'top': 0,
+ 'bottom': 0,
+ }
+
+ if width % wstep and width >= wstep:
+ pad = float(width % wstep) / 2
+ padding['left'] = int(ceil(pad))
+ padding['right'] = int(floor(pad))
+
+ if height % hstep and height >= hstep:
+ pad = float(height % hstep) / 2
+ padding['top'] = int(ceil(pad))
+ padding['bottom'] = int(floor(pad))
+
+ return padding
+
+
+def png_to_2bpp(filein, **kwargs):
+ """
+ Convert a png image to planar 2bpp.
+ """
+
+ arguments = {
+ 'tile_padding': 0,
+ 'pic_dimensions': False,
+ 'interleave': False,
+ 'norepeat': False,
+ 'tilemap': False,
+ }
+ arguments.update(kwargs)
+
+ if type(filein) is str:
+ filein = open(filein, 'rb')
+
+ assert type(filein) is file
+
+ width, height, rgba, info = png.Reader(filein).asRGBA8()
+
+ # png.Reader returns flat pixel data. Nested is easier to work with
+ len_px = len('rgba')
+ image = []
+ palette = []
+ for line in rgba:
+ newline = []
+ for px in xrange(0, len(line), len_px):
+ color = dict(zip('rgba', line[px:px+len_px]))
+ if color not in palette:
+ if len(palette) < 4:
+ palette += [color]
+ else:
+ # TODO Find the nearest match
+ print 'WARNING: %s: Color %s truncated to' % (filein, color),
+ color = sorted(palette, key=lambda x: sum(x.values()))[0]
+ print color
+ newline += [color]
+ image += [newline]
+
+ assert len(palette) <= 4, '%s: palette should be 4 colors, is really %d (%s)' % (filein, len(palette), palette)
+
+ # Pad out smaller palettes with greyscale colors
+ greyscale = {
+ 'black': { 'r': 0x00, 'g': 0x00, 'b': 0x00, 'a': 0xff },
+ 'grey': { 'r': 0x55, 'g': 0x55, 'b': 0x55, 'a': 0xff },
+ 'gray': { 'r': 0xaa, 'g': 0xaa, 'b': 0xaa, 'a': 0xff },
+ 'white': { 'r': 0xff, 'g': 0xff, 'b': 0xff, 'a': 0xff },
+ }
+ preference = 'white', 'black', 'grey', 'gray'
+ for hue in map(greyscale.get, preference):
+ if len(palette) >= 4:
+ break
+ if hue not in palette:
+ palette += [hue]
+
+ palette.sort(key=lambda x: sum(x.values()))
+
+ # Game Boy palette order
+ palette.reverse()
+
+ # Map pixels to quaternary color ids
+ padding = get_image_padding(width, height)
+ width += padding['left'] + padding['right']
+ height += padding['top'] + padding['bottom']
+ pad = bytearray([0])
+
+ qmap = []
+ qmap += pad * width * padding['top']
+ for line in image:
+ qmap += pad * padding['left']
+ for color in line:
+ qmap += [palette.index(color)]
+ qmap += pad * padding['right']
+ qmap += pad * width * padding['bottom']
+
+ # Graphics are stored in tiles instead of lines
+ tile_width = 8
+ tile_height = 8
+ num_columns = max(width, tile_width) / tile_width
+ num_rows = max(height, tile_height) / tile_height
+ image = []
+
+ for row in xrange(num_rows):
+ for column in xrange(num_columns):
+
+ # Split it up into strips to convert to planar data
+ for strip in xrange(min(tile_height, height)):
+ anchor = (
+ row * num_columns * tile_width * tile_height +
+ column * tile_width +
+ strip * width
+ )
+ line = qmap[anchor : anchor + tile_width]
+ bottom, top = 0, 0
+ for bit, quad in enumerate(line):
+ bottom += (quad & 1) << (7 - bit)
+ top += (quad /2 & 1) << (7 - bit)
+ image += [bottom, top]
+
+ dim = arguments['pic_dimensions']
+ if dim:
+ if type(dim) in (tuple, list):
+ w, h = dim
+ else:
+ # infer dimensions based on width.
+ w = width / tile_width
+ h = height / tile_height
+ if h % w == 0:
+ h = w
+
+ tiles = get_tiles(image)
+ pic_length = w * h
+ tile_width = width / 8
+ trailing = len(tiles) % pic_length
+ new_image = []
+ for block in xrange(len(tiles) / pic_length):
+ offset = (h * tile_width) * ((block * w) / tile_width) + ((block * w) % tile_width)
+ pic = []
+ for row in xrange(h):
+ index = offset + (row * tile_width)
+ pic += tiles[index:index + w]
+ new_image += transpose(pic, w)
+ new_image += tiles[len(tiles) - trailing:]
+ image = connect(new_image)
+
+ # Remove any tile padding used to make the png rectangular.
+ image = image[:len(image) - arguments['tile_padding'] * 0x10]
+
+ tmap = None
+
+ if arguments['interleave']:
+ image = deinterleave_tiles(image, num_columns)
+
+ if arguments['pic_dimensions']:
+ image, tmap = condense_image_to_map(image, w * h)
+ elif arguments['norepeat']:
+ image, tmap = condense_image_to_map(image)
+ if not arguments['tilemap']:
+ tmap = None
+
+ arguments.update({ 'palette': palette, 'tmap': tmap, })
+
+ return image, arguments
+
+
+def export_palette(palette, filename):
+ """
+ Export a palette from png to rgb macros in a .pal file.
+ """
+
+ if os.path.exists(filename):
+
+ # Pic palettes are 2 colors (black/white are added later).
+ with open(filename) as rgbs:
+ colors = read_rgb_macros(rgbs.readlines())
+
+ if len(colors) == 2:
+ palette = palette[1:3]
+
+ text = png_to_rgb(palette)
+ with open(filename, 'w') as out:
+ out.write(text)
+
+
+def png_to_lz(filein):
+
+ name = os.path.splitext(filein)[0]
+
+ export_png_to_2bpp(filein)
+ image = open(name+'.2bpp', 'rb').read()
+ to_file(name+'.2bpp'+'.lz', Compressed(image).output)
+
+
+def convert_2bpp_to_1bpp(data):
+ """
+ Convert planar 2bpp image data to 1bpp. Assume images are two colors.
+ """
+ return data[::2]
+
+def convert_1bpp_to_2bpp(data):
+ """
+ Convert 1bpp image data to planar 2bpp (black/white).
+ """
+ output = []
+ for i in data:
+ output += [i, i]
+ return output
+
+
+def export_2bpp_to_1bpp(filename):
+ name, extension = os.path.splitext(filename)
+ image = open(filename, 'rb').read()
+ image = convert_2bpp_to_1bpp(image)
+ to_file(name + '.1bpp', image)
+
+def export_1bpp_to_2bpp(filename):
+ name, extension = os.path.splitext(filename)
+ image = open(filename, 'rb').read()
+ image = convert_1bpp_to_2bpp(image)
+ to_file(name + '.2bpp', image)
+
+
+def export_1bpp_to_png(filename, fileout=None):
+
+ if fileout == None:
+ fileout = os.path.splitext(filename)[0] + '.png'
+
+ arguments = read_filename_arguments(filename)
+
+ image = open(filename, 'rb').read()
+ image = convert_1bpp_to_2bpp(image)
+
+ result = convert_2bpp_to_png(image, **arguments)
+ width, height, palette, greyscale, bitdepth, px_map = result
+
+ w = png.Writer(width, height, palette=palette, compression=9, greyscale=greyscale, bitdepth=bitdepth)
+ with open(fileout, 'wb') as f:
+ w.write(f, px_map)
+
+
+def export_png_to_1bpp(filename, fileout=None):
+
+ if fileout == None:
+ fileout = os.path.splitext(filename)[0] + '.1bpp'
+
+ arguments = read_filename_arguments(filename)
+ image = png_to_1bpp(filename, **arguments)
+
+ to_file(fileout, image)
+
+def png_to_1bpp(filename, **kwargs):
+ image, kwargs = png_to_2bpp(filename, **kwargs)
+ return convert_2bpp_to_1bpp(image)
+
+
+def convert_to_2bpp(filenames=[]):
+ for filename in filenames:
+ name, extension = os.path.splitext(filename)
+ if extension == '.1bpp':
+ export_1bpp_to_2bpp(filename)
+ elif extension == '.2bpp':
+ pass
+ elif extension == '.png':
+ export_png_to_2bpp(filename)
+ else:
+ raise Exception, "Don't know how to convert {} to 2bpp!".format(filename)
+
+def convert_to_1bpp(filenames=[]):
+ for filename in filenames:
+ name, extension = os.path.splitext(filename)
+ if extension == '.1bpp':
+ pass
+ elif extension == '.2bpp':
+ export_2bpp_to_1bpp(filename)
+ elif extension == '.png':
+ export_png_to_1bpp(filename)
+ else:
+ raise Exception, "Don't know how to convert {} to 1bpp!".format(filename)
+
+def convert_to_png(filenames=[]):
+ for filename in filenames:
+ name, extension = os.path.splitext(filename)
+ if extension == '.1bpp':
+ export_1bpp_to_png(filename)
+ elif extension == '.2bpp':
+ export_2bpp_to_png(filename)
+ elif extension == '.png':
+ pass
+ else:
+ raise Exception, "Don't know how to convert {} to png!".format(filename)
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('mode')
+ ap.add_argument('filenames', nargs='*')
+ args = ap.parse_args()
+
+ method = {
+ '2bpp': convert_to_2bpp,
+ '1bpp': convert_to_1bpp,
+ 'png': convert_to_png,
+ }.get(args.mode, None)
+
+ if method == None:
+ raise Exception, "Unknown conversion method!"
+
+ method(args.filenames)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/png.py b/tools/png.py
new file mode 100644
index 0000000..20a7f79
--- /dev/null
+++ b/tools/png.py
@@ -0,0 +1,2650 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+# png.py - PNG encoder/decoder in pure Python
+#
+# Copyright (C) 2006 Johann C. Rocholl <johann@browsershots.org>
+# Portions Copyright (C) 2009 David Jones <drj@pobox.com>
+# And probably portions Copyright (C) 2006 Nicko van Someren <nicko@nicko.org>
+#
+# Original concept by Johann C. Rocholl.
+#
+# LICENCE (MIT)
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""
+Pure Python PNG Reader/Writer
+
+This Python module implements support for PNG images (see PNG
+specification at http://www.w3.org/TR/2003/REC-PNG-20031110/ ). It reads
+and writes PNG files with all allowable bit depths
+(1/2/4/8/16/24/32/48/64 bits per pixel) and colour combinations:
+greyscale (1/2/4/8/16 bit); RGB, RGBA, LA (greyscale with alpha) with
+8/16 bits per channel; colour mapped images (1/2/4/8 bit).
+Adam7 interlacing is supported for reading and
+writing. A number of optional chunks can be specified (when writing)
+and understood (when reading): ``tRNS``, ``bKGD``, ``gAMA``.
+
+For help, type ``import png; help(png)`` in your python interpreter.
+
+A good place to start is the :class:`Reader` and :class:`Writer`
+classes.
+
+Requires Python 2.3. Limited support is available for Python 2.2, but
+not everything works. Best with Python 2.4 and higher. Installation is
+trivial, but see the ``README.txt`` file (with the source distribution)
+for details.
+
+This file can also be used as a command-line utility to convert
+`Netpbm <http://netpbm.sourceforge.net/>`_ PNM files to PNG, and the
+reverse conversion from PNG to PNM. The interface is similar to that
+of the ``pnmtopng`` program from Netpbm. Type ``python png.py --help``
+at the shell prompt for usage and a list of options.
+
+A note on spelling and terminology
+----------------------------------
+
+Generally British English spelling is used in the documentation. So
+that's "greyscale" and "colour". This not only matches the author's
+native language, it's also used by the PNG specification.
+
+The major colour models supported by PNG (and hence by PyPNG) are:
+greyscale, RGB, greyscale--alpha, RGB--alpha. These are sometimes
+referred to using the abbreviations: L, RGB, LA, RGBA. In this case
+each letter abbreviates a single channel: *L* is for Luminance or Luma
+or Lightness which is the channel used in greyscale images; *R*, *G*,
+*B* stand for Red, Green, Blue, the components of a colour image; *A*
+stands for Alpha, the opacity channel (used for transparency effects,
+but higher values are more opaque, so it makes sense to call it
+opacity).
+
+A note on formats
+-----------------
+
+When getting pixel data out of this module (reading) and presenting
+data to this module (writing) there are a number of ways the data could
+be represented as a Python value. Generally this module uses one of
+three formats called "flat row flat pixel", "boxed row flat pixel", and
+"boxed row boxed pixel". Basically the concern is whether each pixel
+and each row comes in its own little tuple (box), or not.
+
+Consider an image that is 3 pixels wide by 2 pixels high, and each pixel
+has RGB components:
+
+Boxed row flat pixel::
+
+ list([R,G,B, R,G,B, R,G,B],
+ [R,G,B, R,G,B, R,G,B])
+
+Each row appears as its own list, but the pixels are flattened so
+that three values for one pixel simply follow the three values for
+the previous pixel. This is the most common format used, because it
+provides a good compromise between space and convenience. PyPNG regards
+itself as at liberty to replace any sequence type with any sufficiently
+compatible other sequence type; in practice each row is an array (from
+the array module), and the outer list is sometimes an iterator rather
+than an explicit list (so that streaming is possible).
+
+Flat row flat pixel::
+
+ [R,G,B, R,G,B, R,G,B,
+ R,G,B, R,G,B, R,G,B]
+
+The entire image is one single giant sequence of colour values.
+Generally an array will be used (to save space), not a list.
+
+Boxed row boxed pixel::
+
+ list([ (R,G,B), (R,G,B), (R,G,B) ],
+ [ (R,G,B), (R,G,B), (R,G,B) ])
+
+Each row appears in its own list, but each pixel also appears in its own
+tuple. A serious memory burn in Python.
+
+In all cases the top row comes first, and for each row the pixels are
+ordered from left-to-right. Within a pixel the values appear in the
+order, R-G-B-A (or L-A for greyscale--alpha).
+
+There is a fourth format, mentioned because it is used internally,
+is close to what lies inside a PNG file itself, and has some support
+from the public API. This format is called packed. When packed,
+each row is a sequence of bytes (integers from 0 to 255), just as
+it is before PNG scanline filtering is applied. When the bit depth
+is 8 this is essentially the same as boxed row flat pixel; when the
+bit depth is less than 8, several pixels are packed into each byte;
+when the bit depth is 16 (the only value more than 8 that is supported
+by the PNG image format) each pixel value is decomposed into 2 bytes
+(and `packed` is a misnomer). This format is used by the
+:meth:`Writer.write_packed` method. It isn't usually a convenient
+format, but may be just right if the source data for the PNG image
+comes from something that uses a similar format (for example, 1-bit
+BMPs, or another PNG file).
+
+And now, my famous members
+--------------------------
+"""
+
+__version__ = "0.0.18"
+
+import itertools
+import math
+# http://www.python.org/doc/2.4.4/lib/module-operator.html
+import operator
+import struct
+import sys
+# http://www.python.org/doc/2.4.4/lib/module-warnings.html
+import warnings
+import zlib
+
+from array import array
+from functools import reduce
+
+try:
+ # `cpngfilters` is a Cython module: it must be compiled by
+ # Cython for this import to work.
+ # If this import does work, then it overrides pure-python
+ # filtering functions defined later in this file (see `class
+ # pngfilters`).
+ import cpngfilters as pngfilters
+except ImportError:
+ pass
+
+
+__all__ = ['Image', 'Reader', 'Writer', 'write_chunks', 'from_array']
+
+
+# The PNG signature.
+# http://www.w3.org/TR/PNG/#5PNG-file-signature
+_signature = struct.pack('8B', 137, 80, 78, 71, 13, 10, 26, 10)
+
+_adam7 = ((0, 0, 8, 8),
+ (4, 0, 8, 8),
+ (0, 4, 4, 8),
+ (2, 0, 4, 4),
+ (0, 2, 2, 4),
+ (1, 0, 2, 2),
+ (0, 1, 1, 2))
+
+def group(s, n):
+ # See http://www.python.org/doc/2.6/library/functions.html#zip
+ return list(zip(*[iter(s)]*n))
+
+def isarray(x):
+ return isinstance(x, array)
+
+def tostring(row):
+ return row.tostring()
+
+def interleave_planes(ipixels, apixels, ipsize, apsize):
+ """
+ Interleave (colour) planes, e.g. RGB + A = RGBA.
+
+ Return an array of pixels consisting of the `ipsize` elements of
+ data from each pixel in `ipixels` followed by the `apsize` elements
+ of data from each pixel in `apixels`. Conventionally `ipixels`
+ and `apixels` are byte arrays so the sizes are bytes, but it
+ actually works with any arrays of the same type. The returned
+ array is the same type as the input arrays which should be the
+ same type as each other.
+ """
+
+ itotal = len(ipixels)
+ atotal = len(apixels)
+ newtotal = itotal + atotal
+ newpsize = ipsize + apsize
+ # Set up the output buffer
+ # See http://www.python.org/doc/2.4.4/lib/module-array.html#l2h-1356
+ out = array(ipixels.typecode)
+ # It's annoying that there is no cheap way to set the array size :-(
+ out.extend(ipixels)
+ out.extend(apixels)
+ # Interleave in the pixel data
+ for i in range(ipsize):
+ out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize]
+ for i in range(apsize):
+ out[i+ipsize:newtotal:newpsize] = apixels[i:atotal:apsize]
+ return out
+
+def check_palette(palette):
+ """Check a palette argument (to the :class:`Writer` class)
+ for validity. Returns the palette as a list if okay; raises an
+ exception otherwise.
+ """
+
+ # None is the default and is allowed.
+ if palette is None:
+ return None
+
+ p = list(palette)
+ if not (0 < len(p) <= 256):
+ raise ValueError("a palette must have between 1 and 256 entries")
+ seen_triple = False
+ for i,t in enumerate(p):
+ if len(t) not in (3,4):
+ raise ValueError(
+ "palette entry %d: entries must be 3- or 4-tuples." % i)
+ if len(t) == 3:
+ seen_triple = True
+ if seen_triple and len(t) == 4:
+ raise ValueError(
+ "palette entry %d: all 4-tuples must precede all 3-tuples" % i)
+ for x in t:
+ if int(x) != x or not(0 <= x <= 255):
+ raise ValueError(
+ "palette entry %d: values must be integer: 0 <= x <= 255" % i)
+ return p
+
+def check_sizes(size, width, height):
+ """Check that these arguments, in supplied, are consistent.
+ Return a (width, height) pair.
+ """
+
+ if not size:
+ return width, height
+
+ if len(size) != 2:
+ raise ValueError(
+ "size argument should be a pair (width, height)")
+ if width is not None and width != size[0]:
+ raise ValueError(
+ "size[0] (%r) and width (%r) should match when both are used."
+ % (size[0], width))
+ if height is not None and height != size[1]:
+ raise ValueError(
+ "size[1] (%r) and height (%r) should match when both are used."
+ % (size[1], height))
+ return size
+
+def check_color(c, greyscale, which):
+ """Checks that a colour argument for transparent or
+ background options is the right form. Returns the colour
+ (which, if it's a bar integer, is "corrected" to a 1-tuple).
+ """
+
+ if c is None:
+ return c
+ if greyscale:
+ try:
+ len(c)
+ except TypeError:
+ c = (c,)
+ if len(c) != 1:
+ raise ValueError("%s for greyscale must be 1-tuple" %
+ which)
+ if not isinteger(c[0]):
+ raise ValueError(
+ "%s colour for greyscale must be integer" % which)
+ else:
+ if not (len(c) == 3 and
+ isinteger(c[0]) and
+ isinteger(c[1]) and
+ isinteger(c[2])):
+ raise ValueError(
+ "%s colour must be a triple of integers" % which)
+ return c
+
+class Error(Exception):
+ def __str__(self):
+ return self.__class__.__name__ + ': ' + ' '.join(self.args)
+
+class FormatError(Error):
+ """Problem with input file format. In other words, PNG file does
+ not conform to the specification in some way and is invalid.
+ """
+
+class ChunkError(FormatError):
+ pass
+
+
+class Writer:
+ """
+ PNG encoder in pure Python.
+ """
+
+ def __init__(self, width=None, height=None,
+ size=None,
+ greyscale=False,
+ alpha=False,
+ bitdepth=8,
+ palette=None,
+ transparent=None,
+ background=None,
+ gamma=None,
+ compression=None,
+ interlace=False,
+ bytes_per_sample=None, # deprecated
+ planes=None,
+ colormap=None,
+ maxval=None,
+ chunk_limit=2**20,
+ x_pixels_per_unit = None,
+ y_pixels_per_unit = None,
+ unit_is_meter = False):
+ """
+ Create a PNG encoder object.
+
+ Arguments:
+
+ width, height
+ Image size in pixels, as two separate arguments.
+ size
+ Image size (w,h) in pixels, as single argument.
+ greyscale
+ Input data is greyscale, not RGB.
+ alpha
+ Input data has alpha channel (RGBA or LA).
+ bitdepth
+ Bit depth: from 1 to 16.
+ palette
+ Create a palette for a colour mapped image (colour type 3).
+ transparent
+ Specify a transparent colour (create a ``tRNS`` chunk).
+ background
+ Specify a default background colour (create a ``bKGD`` chunk).
+ gamma
+ Specify a gamma value (create a ``gAMA`` chunk).
+ compression
+ zlib compression level: 0 (none) to 9 (more compressed);
+ default: -1 or None.
+ interlace
+ Create an interlaced image.
+ chunk_limit
+ Write multiple ``IDAT`` chunks to save memory.
+ x_pixels_per_unit
+ Number of pixels a unit along the x axis (write a
+ `pHYs` chunk).
+ y_pixels_per_unit
+ Number of pixels a unit along the y axis (write a
+ `pHYs` chunk). Along with `x_pixel_unit`, this gives
+ the pixel size ratio.
+ unit_is_meter
+ `True` to indicate that the unit (for the `pHYs`
+ chunk) is metre.
+
+ The image size (in pixels) can be specified either by using the
+ `width` and `height` arguments, or with the single `size`
+ argument. If `size` is used it should be a pair (*width*,
+ *height*).
+
+ `greyscale` and `alpha` are booleans that specify whether
+ an image is greyscale (or colour), and whether it has an
+ alpha channel (or not).
+
+ `bitdepth` specifies the bit depth of the source pixel values.
+ Each source pixel value must be an integer between 0 and
+ ``2**bitdepth-1``. For example, 8-bit images have values
+ between 0 and 255. PNG only stores images with bit depths of
+ 1,2,4,8, or 16. When `bitdepth` is not one of these values,
+ the next highest valid bit depth is selected, and an ``sBIT``
+ (significant bits) chunk is generated that specifies the
+ original precision of the source image. In this case the
+ supplied pixel values will be rescaled to fit the range of
+ the selected bit depth.
+
+ The details of which bit depth / colour model combinations the
+ PNG file format supports directly, are somewhat arcane
+ (refer to the PNG specification for full details). Briefly:
+ "small" bit depths (1,2,4) are only allowed with greyscale and
+ colour mapped images; colour mapped images cannot have bit depth
+ 16.
+
+ For colour mapped images (in other words, when the `palette`
+ argument is specified) the `bitdepth` argument must match one of
+ the valid PNG bit depths: 1, 2, 4, or 8. (It is valid to have a
+ PNG image with a palette and an ``sBIT`` chunk, but the meaning
+ is slightly different; it would be awkward to press the
+ `bitdepth` argument into service for this.)
+
+ The `palette` option, when specified, causes a colour
+ mapped image to be created: the PNG colour type is set to 3;
+ `greyscale` must not be set; `alpha` must not be set;
+ `transparent` must not be set; the bit depth must be 1,2,4,
+ or 8. When a colour mapped image is created, the pixel values
+ are palette indexes and the `bitdepth` argument specifies the
+ size of these indexes (not the size of the colour values in
+ the palette).
+
+ The palette argument value should be a sequence of 3- or
+ 4-tuples. 3-tuples specify RGB palette entries; 4-tuples
+ specify RGBA palette entries. If both 4-tuples and 3-tuples
+ appear in the sequence then all the 4-tuples must come
+ before all the 3-tuples. A ``PLTE`` chunk is created; if there
+ are 4-tuples then a ``tRNS`` chunk is created as well. The
+ ``PLTE`` chunk will contain all the RGB triples in the same
+ sequence; the ``tRNS`` chunk will contain the alpha channel for
+ all the 4-tuples, in the same sequence. Palette entries
+ are always 8-bit.
+
+ If specified, the `transparent` and `background` parameters must
+ be a tuple with three integer values for red, green, blue, or
+ a simple integer (or singleton tuple) for a greyscale image.
+
+ If specified, the `gamma` parameter must be a positive number
+ (generally, a `float`). A ``gAMA`` chunk will be created.
+ Note that this will not change the values of the pixels as
+ they appear in the PNG file, they are assumed to have already
+ been converted appropriately for the gamma specified.
+
+ The `compression` argument specifies the compression level to
+ be used by the ``zlib`` module. Values from 1 to 9 specify
+ compression, with 9 being "more compressed" (usually smaller
+ and slower, but it doesn't always work out that way). 0 means
+ no compression. -1 and ``None`` both mean that the default
+ level of compession will be picked by the ``zlib`` module
+ (which is generally acceptable).
+
+ If `interlace` is true then an interlaced image is created
+ (using PNG's so far only interace method, *Adam7*). This does
+ not affect how the pixels should be presented to the encoder,
+ rather it changes how they are arranged into the PNG file.
+ On slow connexions interlaced images can be partially decoded
+ by the browser to give a rough view of the image that is
+ successively refined as more image data appears.
+
+ .. note ::
+
+ Enabling the `interlace` option requires the entire image
+ to be processed in working memory.
+
+ `chunk_limit` is used to limit the amount of memory used whilst
+ compressing the image. In order to avoid using large amounts of
+ memory, multiple ``IDAT`` chunks may be created.
+ """
+
+ # At the moment the `planes` argument is ignored;
+ # its purpose is to act as a dummy so that
+ # ``Writer(x, y, **info)`` works, where `info` is a dictionary
+ # returned by Reader.read and friends.
+ # Ditto for `colormap`.
+
+ width, height = check_sizes(size, width, height)
+ del size
+
+ if width <= 0 or height <= 0:
+ raise ValueError("width and height must be greater than zero")
+ if not isinteger(width) or not isinteger(height):
+ raise ValueError("width and height must be integers")
+ # http://www.w3.org/TR/PNG/#7Integers-and-byte-order
+ if width > 2**32-1 or height > 2**32-1:
+ raise ValueError("width and height cannot exceed 2**32-1")
+
+ if alpha and transparent is not None:
+ raise ValueError(
+ "transparent colour not allowed with alpha channel")
+
+ if bytes_per_sample is not None:
+ warnings.warn('please use bitdepth instead of bytes_per_sample',
+ DeprecationWarning)
+ if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2):
+ raise ValueError(
+ "bytes per sample must be .125, .25, .5, 1, or 2")
+ bitdepth = int(8*bytes_per_sample)
+ del bytes_per_sample
+ if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth:
+ raise ValueError("bitdepth (%r) must be a positive integer <= 16" %
+ bitdepth)
+
+ self.rescale = None
+ palette = check_palette(palette)
+ if palette:
+ if bitdepth not in (1,2,4,8):
+ raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8")
+ if transparent is not None:
+ raise ValueError("transparent and palette not compatible")
+ if alpha:
+ raise ValueError("alpha and palette not compatible")
+ if greyscale:
+ raise ValueError("greyscale and palette not compatible")
+ else:
+ # No palette, check for sBIT chunk generation.
+ if alpha or not greyscale:
+ if bitdepth not in (8,16):
+ targetbitdepth = (8,16)[bitdepth > 8]
+ self.rescale = (bitdepth, targetbitdepth)
+ bitdepth = targetbitdepth
+ del targetbitdepth
+ else:
+ assert greyscale
+ assert not alpha
+ if bitdepth not in (1,2,4,8,16):
+ if bitdepth > 8:
+ targetbitdepth = 16
+ elif bitdepth == 3:
+ targetbitdepth = 4
+ else:
+ assert bitdepth in (5,6,7)
+ targetbitdepth = 8
+ self.rescale = (bitdepth, targetbitdepth)
+ bitdepth = targetbitdepth
+ del targetbitdepth
+
+ if bitdepth < 8 and (alpha or not greyscale and not palette):
+ raise ValueError(
+ "bitdepth < 8 only permitted with greyscale or palette")
+ if bitdepth > 8 and palette:
+ raise ValueError(
+ "bit depth must be 8 or less for images with palette")
+
+ transparent = check_color(transparent, greyscale, 'transparent')
+ background = check_color(background, greyscale, 'background')
+
+ # It's important that the true boolean values (greyscale, alpha,
+ # colormap, interlace) are converted to bool because Iverson's
+ # convention is relied upon later on.
+ self.width = width
+ self.height = height
+ self.transparent = transparent
+ self.background = background
+ self.gamma = gamma
+ self.greyscale = bool(greyscale)
+ self.alpha = bool(alpha)
+ self.colormap = bool(palette)
+ self.bitdepth = int(bitdepth)
+ self.compression = compression
+ self.chunk_limit = chunk_limit
+ self.interlace = bool(interlace)
+ self.palette = palette
+ self.x_pixels_per_unit = x_pixels_per_unit
+ self.y_pixels_per_unit = y_pixels_per_unit
+ self.unit_is_meter = bool(unit_is_meter)
+
+ self.color_type = 4*self.alpha + 2*(not greyscale) + 1*self.colormap
+ assert self.color_type in (0,2,3,4,6)
+
+ self.color_planes = (3,1)[self.greyscale or self.colormap]
+ self.planes = self.color_planes + self.alpha
+ # :todo: fix for bitdepth < 8
+ self.psize = (self.bitdepth/8) * self.planes
+
+ def make_palette(self):
+ """Create the byte sequences for a ``PLTE`` and if necessary a
+ ``tRNS`` chunk. Returned as a pair (*p*, *t*). *t* will be
+ ``None`` if no ``tRNS`` chunk is necessary.
+ """
+
+ p = array('B')
+ t = array('B')
+
+ for x in self.palette:
+ p.extend(x[0:3])
+ if len(x) > 3:
+ t.append(x[3])
+ p = tostring(p)
+ t = tostring(t)
+ if t:
+ return p,t
+ return p,None
+
+ def write(self, outfile, rows):
+ """Write a PNG image to the output file. `rows` should be
+ an iterable that yields each row in boxed row flat pixel
+ format. The rows should be the rows of the original image,
+ so there should be ``self.height`` rows of ``self.width *
+ self.planes`` values. If `interlace` is specified (when
+ creating the instance), then an interlaced PNG file will
+ be written. Supply the rows in the normal image order;
+ the interlacing is carried out internally.
+
+ .. note ::
+
+ Interlacing will require the entire image to be in working
+ memory.
+ """
+
+ if self.interlace:
+ fmt = 'BH'[self.bitdepth > 8]
+ a = array(fmt, itertools.chain(*rows))
+ return self.write_array(outfile, a)
+
+ nrows = self.write_passes(outfile, rows)
+ if nrows != self.height:
+ raise ValueError(
+ "rows supplied (%d) does not match height (%d)" %
+ (nrows, self.height))
+
+ def write_passes(self, outfile, rows, packed=False):
+ """
+ Write a PNG image to the output file.
+
+ Most users are expected to find the :meth:`write` or
+ :meth:`write_array` method more convenient.
+
+ The rows should be given to this method in the order that
+ they appear in the output file. For straightlaced images,
+ this is the usual top to bottom ordering, but for interlaced
+ images the rows should have already been interlaced before
+ passing them to this function.
+
+ `rows` should be an iterable that yields each row. When
+ `packed` is ``False`` the rows should be in boxed row flat pixel
+ format; when `packed` is ``True`` each row should be a packed
+ sequence of bytes.
+ """
+
+ # http://www.w3.org/TR/PNG/#5PNG-file-signature
+ outfile.write(_signature)
+
+ # http://www.w3.org/TR/PNG/#11IHDR
+ write_chunk(outfile, b'IHDR',
+ struct.pack("!2I5B", self.width, self.height,
+ self.bitdepth, self.color_type,
+ 0, 0, self.interlace))
+
+ # See :chunk:order
+ # http://www.w3.org/TR/PNG/#11gAMA
+ if self.gamma is not None:
+ write_chunk(outfile, b'gAMA',
+ struct.pack("!L", int(round(self.gamma*1e5))))
+
+ # See :chunk:order
+ # http://www.w3.org/TR/PNG/#11sBIT
+ if self.rescale:
+ write_chunk(outfile, b'sBIT',
+ struct.pack('%dB' % self.planes,
+ *[self.rescale[0]]*self.planes))
+
+ # :chunk:order: Without a palette (PLTE chunk), ordering is
+ # relatively relaxed. With one, gAMA chunk must precede PLTE
+ # chunk which must precede tRNS and bKGD.
+ # See http://www.w3.org/TR/PNG/#5ChunkOrdering
+ if self.palette:
+ p,t = self.make_palette()
+ write_chunk(outfile, b'PLTE', p)
+ if t:
+ # tRNS chunk is optional. Only needed if palette entries
+ # have alpha.
+ write_chunk(outfile, b'tRNS', t)
+
+ # http://www.w3.org/TR/PNG/#11tRNS
+ if self.transparent is not None:
+ if self.greyscale:
+ write_chunk(outfile, b'tRNS',
+ struct.pack("!1H", *self.transparent))
+ else:
+ write_chunk(outfile, b'tRNS',
+ struct.pack("!3H", *self.transparent))
+
+ # http://www.w3.org/TR/PNG/#11bKGD
+ if self.background is not None:
+ if self.greyscale:
+ write_chunk(outfile, b'bKGD',
+ struct.pack("!1H", *self.background))
+ else:
+ write_chunk(outfile, b'bKGD',
+ struct.pack("!3H", *self.background))
+
+ # http://www.w3.org/TR/PNG/#11pHYs
+ if self.x_pixels_per_unit is not None and self.y_pixels_per_unit is not None:
+ tup = (self.x_pixels_per_unit, self.y_pixels_per_unit, int(self.unit_is_meter))
+ write_chunk(outfile, b'pHYs', struct.pack("!LLB",*tup))
+
+ # http://www.w3.org/TR/PNG/#11IDAT
+ if self.compression is not None:
+ compressor = zlib.compressobj(self.compression)
+ else:
+ compressor = zlib.compressobj()
+
+ # Choose an extend function based on the bitdepth. The extend
+ # function packs/decomposes the pixel values into bytes and
+ # stuffs them onto the data array.
+ data = array('B')
+ if self.bitdepth == 8 or packed:
+ extend = data.extend
+ elif self.bitdepth == 16:
+ # Decompose into bytes
+ def extend(sl):
+ fmt = '!%dH' % len(sl)
+ data.extend(array('B', struct.pack(fmt, *sl)))
+ else:
+ # Pack into bytes
+ assert self.bitdepth < 8
+ # samples per byte
+ spb = int(8/self.bitdepth)
+ def extend(sl):
+ a = array('B', sl)
+ # Adding padding bytes so we can group into a whole
+ # number of spb-tuples.
+ l = float(len(a))
+ extra = math.ceil(l / float(spb))*spb - l
+ a.extend([0]*int(extra))
+ # Pack into bytes
+ l = group(a, spb)
+ l = [reduce(lambda x,y:
+ (x << self.bitdepth) + y, e) for e in l]
+ data.extend(l)
+ if self.rescale:
+ oldextend = extend
+ factor = \
+ float(2**self.rescale[1]-1) / float(2**self.rescale[0]-1)
+ def extend(sl):
+ oldextend([int(round(factor*x)) for x in sl])
+
+ # Build the first row, testing mostly to see if we need to
+ # changed the extend function to cope with NumPy integer types
+ # (they cause our ordinary definition of extend to fail, so we
+ # wrap it). See
+ # http://code.google.com/p/pypng/issues/detail?id=44
+ enumrows = enumerate(rows)
+ del rows
+
+ # First row's filter type.
+ data.append(0)
+ # :todo: Certain exceptions in the call to ``.next()`` or the
+ # following try would indicate no row data supplied.
+ # Should catch.
+ i,row = next(enumrows)
+ try:
+ # If this fails...
+ extend(row)
+ except:
+ # ... try a version that converts the values to int first.
+ # Not only does this work for the (slightly broken) NumPy
+ # types, there are probably lots of other, unknown, "nearly"
+ # int types it works for.
+ def wrapmapint(f):
+ return lambda sl: f([int(x) for x in sl])
+ extend = wrapmapint(extend)
+ del wrapmapint
+ extend(row)
+
+ for i,row in enumrows:
+ # Add "None" filter type. Currently, it's essential that
+ # this filter type be used for every scanline as we do not
+ # mark the first row of a reduced pass image; that means we
+ # could accidentally compute the wrong filtered scanline if
+ # we used "up", "average", or "paeth" on such a line.
+ data.append(0)
+ extend(row)
+ if len(data) > self.chunk_limit:
+ compressed = compressor.compress(tostring(data))
+ if len(compressed):
+ write_chunk(outfile, b'IDAT', compressed)
+ # Because of our very witty definition of ``extend``,
+ # above, we must re-use the same ``data`` object. Hence
+ # we use ``del`` to empty this one, rather than create a
+ # fresh one (which would be my natural FP instinct).
+ del data[:]
+ if len(data):
+ compressed = compressor.compress(tostring(data))
+ else:
+ compressed = b''
+ flushed = compressor.flush()
+ if len(compressed) or len(flushed):
+ write_chunk(outfile, b'IDAT', compressed + flushed)
+ # http://www.w3.org/TR/PNG/#11IEND
+ write_chunk(outfile, b'IEND')
+ return i+1
+
+ def write_array(self, outfile, pixels):
+ """
+ Write an array in flat row flat pixel format as a PNG file on
+ the output file. See also :meth:`write` method.
+ """
+
+ if self.interlace:
+ self.write_passes(outfile, self.array_scanlines_interlace(pixels))
+ else:
+ self.write_passes(outfile, self.array_scanlines(pixels))
+
+ def write_packed(self, outfile, rows):
+ """
+ Write PNG file to `outfile`. The pixel data comes from `rows`
+ which should be in boxed row packed format. Each row should be
+ a sequence of packed bytes.
+
+ Technically, this method does work for interlaced images but it
+ is best avoided. For interlaced images, the rows should be
+ presented in the order that they appear in the file.
+
+ This method should not be used when the source image bit depth
+ is not one naturally supported by PNG; the bit depth should be
+ 1, 2, 4, 8, or 16.
+ """
+
+ if self.rescale:
+ raise Error("write_packed method not suitable for bit depth %d" %
+ self.rescale[0])
+ return self.write_passes(outfile, rows, packed=True)
+
+ def convert_pnm(self, infile, outfile):
+ """
+ Convert a PNM file containing raw pixel data into a PNG file
+ with the parameters set in the writer object. Works for
+ (binary) PGM, PPM, and PAM formats.
+ """
+
+ if self.interlace:
+ pixels = array('B')
+ pixels.fromfile(infile,
+ (self.bitdepth/8) * self.color_planes *
+ self.width * self.height)
+ self.write_passes(outfile, self.array_scanlines_interlace(pixels))
+ else:
+ self.write_passes(outfile, self.file_scanlines(infile))
+
+ def convert_ppm_and_pgm(self, ppmfile, pgmfile, outfile):
+ """
+ Convert a PPM and PGM file containing raw pixel data into a
+ PNG outfile with the parameters set in the writer object.
+ """
+ pixels = array('B')
+ pixels.fromfile(ppmfile,
+ (self.bitdepth/8) * self.color_planes *
+ self.width * self.height)
+ apixels = array('B')
+ apixels.fromfile(pgmfile,
+ (self.bitdepth/8) *
+ self.width * self.height)
+ pixels = interleave_planes(pixels, apixels,
+ (self.bitdepth/8) * self.color_planes,
+ (self.bitdepth/8))
+ if self.interlace:
+ self.write_passes(outfile, self.array_scanlines_interlace(pixels))
+ else:
+ self.write_passes(outfile, self.array_scanlines(pixels))
+
+ def file_scanlines(self, infile):
+ """
+ Generates boxed rows in flat pixel format, from the input file
+ `infile`. It assumes that the input file is in a "Netpbm-like"
+ binary format, and is positioned at the beginning of the first
+ pixel. The number of pixels to read is taken from the image
+ dimensions (`width`, `height`, `planes`) and the number of bytes
+ per value is implied by the image `bitdepth`.
+ """
+
+ # Values per row
+ vpr = self.width * self.planes
+ row_bytes = vpr
+ if self.bitdepth > 8:
+ assert self.bitdepth == 16
+ row_bytes *= 2
+ fmt = '>%dH' % vpr
+ def line():
+ return array('H', struct.unpack(fmt, infile.read(row_bytes)))
+ else:
+ def line():
+ scanline = array('B', infile.read(row_bytes))
+ return scanline
+ for y in range(self.height):
+ yield line()
+
+ def array_scanlines(self, pixels):
+ """
+ Generates boxed rows (flat pixels) from flat rows (flat pixels)
+ in an array.
+ """
+
+ # Values per row
+ vpr = self.width * self.planes
+ stop = 0
+ for y in range(self.height):
+ start = stop
+ stop = start + vpr
+ yield pixels[start:stop]
+
+ def array_scanlines_interlace(self, pixels):
+ """
+ Generator for interlaced scanlines from an array. `pixels` is
+ the full source image in flat row flat pixel format. The
+ generator yields each scanline of the reduced passes in turn, in
+ boxed row flat pixel format.
+ """
+
+ # http://www.w3.org/TR/PNG/#8InterlaceMethods
+ # Array type.
+ fmt = 'BH'[self.bitdepth > 8]
+ # Value per row
+ vpr = self.width * self.planes
+ for xstart, ystart, xstep, ystep in _adam7:
+ if xstart >= self.width:
+ continue
+ # Pixels per row (of reduced image)
+ ppr = int(math.ceil((self.width-xstart)/float(xstep)))
+ # number of values in reduced image row.
+ row_len = ppr*self.planes
+ for y in range(ystart, self.height, ystep):
+ if xstep == 1:
+ offset = y * vpr
+ yield pixels[offset:offset+vpr]
+ else:
+ row = array(fmt)
+ # There's no easier way to set the length of an array
+ row.extend(pixels[0:row_len])
+ offset = y * vpr + xstart * self.planes
+ end_offset = (y+1) * vpr
+ skip = self.planes * xstep
+ for i in range(self.planes):
+ row[i::self.planes] = \
+ pixels[offset+i:end_offset:skip]
+ yield row
+
+def write_chunk(outfile, tag, data=b''):
+ """
+ Write a PNG chunk to the output file, including length and
+ checksum.
+ """
+
+ # http://www.w3.org/TR/PNG/#5Chunk-layout
+ outfile.write(struct.pack("!I", len(data)))
+ outfile.write(tag)
+ outfile.write(data)
+ checksum = zlib.crc32(tag)
+ checksum = zlib.crc32(data, checksum)
+ checksum &= 2**32-1
+ outfile.write(struct.pack("!I", checksum))
+
+def write_chunks(out, chunks):
+ """Create a PNG file by writing out the chunks."""
+
+ out.write(_signature)
+ for chunk in chunks:
+ write_chunk(out, *chunk)
+
+def filter_scanline(type, line, fo, prev=None):
+ """Apply a scanline filter to a scanline. `type` specifies the
+ filter type (0 to 4); `line` specifies the current (unfiltered)
+ scanline as a sequence of bytes; `prev` specifies the previous
+ (unfiltered) scanline as a sequence of bytes. `fo` specifies the
+ filter offset; normally this is size of a pixel in bytes (the number
+ of bytes per sample times the number of channels), but when this is
+ < 1 (for bit depths < 8) then the filter offset is 1.
+ """
+
+ assert 0 <= type < 5
+
+ # The output array. Which, pathetically, we extend one-byte at a
+ # time (fortunately this is linear).
+ out = array('B', [type])
+
+ def sub():
+ ai = -fo
+ for x in line:
+ if ai >= 0:
+ x = (x - line[ai]) & 0xff
+ out.append(x)
+ ai += 1
+ def up():
+ for i,x in enumerate(line):
+ x = (x - prev[i]) & 0xff
+ out.append(x)
+ def average():
+ ai = -fo
+ for i,x in enumerate(line):
+ if ai >= 0:
+ x = (x - ((line[ai] + prev[i]) >> 1)) & 0xff
+ else:
+ x = (x - (prev[i] >> 1)) & 0xff
+ out.append(x)
+ ai += 1
+ def paeth():
+ # http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
+ ai = -fo # also used for ci
+ for i,x in enumerate(line):
+ a = 0
+ b = prev[i]
+ c = 0
+
+ if ai >= 0:
+ a = line[ai]
+ c = prev[ai]
+ p = a + b - c
+ pa = abs(p - a)
+ pb = abs(p - b)
+ pc = abs(p - c)
+ if pa <= pb and pa <= pc:
+ Pr = a
+ elif pb <= pc:
+ Pr = b
+ else:
+ Pr = c
+
+ x = (x - Pr) & 0xff
+ out.append(x)
+ ai += 1
+
+ if not prev:
+ # We're on the first line. Some of the filters can be reduced
+ # to simpler cases which makes handling the line "off the top"
+ # of the image simpler. "up" becomes "none"; "paeth" becomes
+ # "left" (non-trivial, but true). "average" needs to be handled
+ # specially.
+ if type == 2: # "up"
+ type = 0
+ elif type == 3:
+ prev = [0]*len(line)
+ elif type == 4: # "paeth"
+ type = 1
+ if type == 0:
+ out.extend(line)
+ elif type == 1:
+ sub()
+ elif type == 2:
+ up()
+ elif type == 3:
+ average()
+ else: # type == 4
+ paeth()
+ return out
+
+
+def from_array(a, mode=None, info={}):
+ """Create a PNG :class:`Image` object from a 2- or 3-dimensional
+ array. One application of this function is easy PIL-style saving:
+ ``png.from_array(pixels, 'L').save('foo.png')``.
+
+ Unless they are specified using the *info* parameter, the PNG's
+ height and width are taken from the array size. For a 3 dimensional
+ array the first axis is the height; the second axis is the width;
+ and the third axis is the channel number. Thus an RGB image that is
+ 16 pixels high and 8 wide will use an array that is 16x8x3. For 2
+ dimensional arrays the first axis is the height, but the second axis
+ is ``width*channels``, so an RGB image that is 16 pixels high and 8
+ wide will use a 2-dimensional array that is 16x24 (each row will be
+ 8*3 = 24 sample values).
+
+ *mode* is a string that specifies the image colour format in a
+ PIL-style mode. It can be:
+
+ ``'L'``
+ greyscale (1 channel)
+ ``'LA'``
+ greyscale with alpha (2 channel)
+ ``'RGB'``
+ colour image (3 channel)
+ ``'RGBA'``
+ colour image with alpha (4 channel)
+
+ The mode string can also specify the bit depth (overriding how this
+ function normally derives the bit depth, see below). Appending
+ ``';16'`` to the mode will cause the PNG to be 16 bits per channel;
+ any decimal from 1 to 16 can be used to specify the bit depth.
+
+ When a 2-dimensional array is used *mode* determines how many
+ channels the image has, and so allows the width to be derived from
+ the second array dimension.
+
+ The array is expected to be a ``numpy`` array, but it can be any
+ suitable Python sequence. For example, a list of lists can be used:
+ ``png.from_array([[0, 255, 0], [255, 0, 255]], 'L')``. The exact
+ rules are: ``len(a)`` gives the first dimension, height;
+ ``len(a[0])`` gives the second dimension; ``len(a[0][0])`` gives the
+ third dimension, unless an exception is raised in which case a
+ 2-dimensional array is assumed. It's slightly more complicated than
+ that because an iterator of rows can be used, and it all still
+ works. Using an iterator allows data to be streamed efficiently.
+
+ The bit depth of the PNG is normally taken from the array element's
+ datatype (but if *mode* specifies a bitdepth then that is used
+ instead). The array element's datatype is determined in a way which
+ is supposed to work both for ``numpy`` arrays and for Python
+ ``array.array`` objects. A 1 byte datatype will give a bit depth of
+ 8, a 2 byte datatype will give a bit depth of 16. If the datatype
+ does not have an implicit size, for example it is a plain Python
+ list of lists, as above, then a default of 8 is used.
+
+ The *info* parameter is a dictionary that can be used to specify
+ metadata (in the same style as the arguments to the
+ :class:`png.Writer` class). For this function the keys that are
+ useful are:
+
+ height
+ overrides the height derived from the array dimensions and allows
+ *a* to be an iterable.
+ width
+ overrides the width derived from the array dimensions.
+ bitdepth
+ overrides the bit depth derived from the element datatype (but
+ must match *mode* if that also specifies a bit depth).
+
+ Generally anything specified in the
+ *info* dictionary will override any implicit choices that this
+ function would otherwise make, but must match any explicit ones.
+ For example, if the *info* dictionary has a ``greyscale`` key then
+ this must be true when mode is ``'L'`` or ``'LA'`` and false when
+ mode is ``'RGB'`` or ``'RGBA'``.
+ """
+
+ # We abuse the *info* parameter by modifying it. Take a copy here.
+ # (Also typechecks *info* to some extent).
+ info = dict(info)
+
+ # Syntax check mode string.
+ bitdepth = None
+ try:
+ # Assign the 'L' or 'RGBA' part to `gotmode`.
+ if mode.startswith('L'):
+ gotmode = 'L'
+ mode = mode[1:]
+ elif mode.startswith('RGB'):
+ gotmode = 'RGB'
+ mode = mode[3:]
+ else:
+ raise Error()
+ if mode.startswith('A'):
+ gotmode += 'A'
+ mode = mode[1:]
+
+ # Skip any optional ';'
+ while mode.startswith(';'):
+ mode = mode[1:]
+
+ # Parse optional bitdepth
+ if mode:
+ try:
+ bitdepth = int(mode)
+ except (TypeError, ValueError):
+ raise Error()
+ except Error:
+ raise Error("mode string should be 'RGB' or 'L;16' or similar.")
+ mode = gotmode
+
+ # Get bitdepth from *mode* if possible.
+ if bitdepth:
+ if info.get('bitdepth') and bitdepth != info['bitdepth']:
+ raise Error("mode bitdepth (%d) should match info bitdepth (%d)." %
+ (bitdepth, info['bitdepth']))
+ info['bitdepth'] = bitdepth
+
+ # Fill in and/or check entries in *info*.
+ # Dimensions.
+ if 'size' in info:
+ # Check width, height, size all match where used.
+ for dimension,axis in [('width', 0), ('height', 1)]:
+ if dimension in info:
+ if info[dimension] != info['size'][axis]:
+ raise Error(
+ "info[%r] should match info['size'][%r]." %
+ (dimension, axis))
+ info['width'],info['height'] = info['size']
+ if 'height' not in info:
+ try:
+ l = len(a)
+ except TypeError:
+ raise Error(
+ "len(a) does not work, supply info['height'] instead.")
+ info['height'] = l
+ # Colour format.
+ if 'greyscale' in info:
+ if bool(info['greyscale']) != ('L' in mode):
+ raise Error("info['greyscale'] should match mode.")
+ info['greyscale'] = 'L' in mode
+ if 'alpha' in info:
+ if bool(info['alpha']) != ('A' in mode):
+ raise Error("info['alpha'] should match mode.")
+ info['alpha'] = 'A' in mode
+
+ planes = len(mode)
+ if 'planes' in info:
+ if info['planes'] != planes:
+ raise Error("info['planes'] should match mode.")
+
+ # In order to work out whether we the array is 2D or 3D we need its
+ # first row, which requires that we take a copy of its iterator.
+ # We may also need the first row to derive width and bitdepth.
+ a,t = itertools.tee(a)
+ row = next(t)
+ del t
+ try:
+ row[0][0]
+ threed = True
+ testelement = row[0]
+ except (IndexError, TypeError):
+ threed = False
+ testelement = row
+ if 'width' not in info:
+ if threed:
+ width = len(row)
+ else:
+ width = len(row) // planes
+ info['width'] = width
+
+ if threed:
+ # Flatten the threed rows
+ a = (itertools.chain.from_iterable(x) for x in a)
+
+ if 'bitdepth' not in info:
+ try:
+ dtype = testelement.dtype
+ # goto the "else:" clause. Sorry.
+ except AttributeError:
+ try:
+ # Try a Python array.array.
+ bitdepth = 8 * testelement.itemsize
+ except AttributeError:
+ # We can't determine it from the array element's
+ # datatype, use a default of 8.
+ bitdepth = 8
+ else:
+ # If we got here without exception, we now assume that
+ # the array is a numpy array.
+ if dtype.kind == 'b':
+ bitdepth = 1
+ else:
+ bitdepth = 8 * dtype.itemsize
+ info['bitdepth'] = bitdepth
+
+ for thing in 'width height bitdepth greyscale alpha'.split():
+ assert thing in info
+ return Image(a, info)
+
+# So that refugee's from PIL feel more at home. Not documented.
+fromarray = from_array
+
+class Image:
+ """A PNG image. You can create an :class:`Image` object from
+ an array of pixels by calling :meth:`png.from_array`. It can be
+ saved to disk with the :meth:`save` method.
+ """
+
+ def __init__(self, rows, info):
+ """
+ .. note ::
+
+ The constructor is not public. Please do not call it.
+ """
+
+ self.rows = rows
+ self.info = info
+
+ def save(self, file):
+ """Save the image to *file*. If *file* looks like an open file
+ descriptor then it is used, otherwise it is treated as a
+ filename and a fresh file is opened.
+
+ In general, you can only call this method once; after it has
+ been called the first time and the PNG image has been saved, the
+ source data will have been streamed, and cannot be streamed
+ again.
+ """
+
+ w = Writer(**self.info)
+
+ try:
+ file.write
+ def close(): pass
+ except AttributeError:
+ file = open(file, 'wb')
+ def close(): file.close()
+
+ try:
+ w.write(file, self.rows)
+ finally:
+ close()
+
+class _readable:
+ """
+ A simple file-like interface for strings and arrays.
+ """
+
+ def __init__(self, buf):
+ self.buf = buf
+ self.offset = 0
+
+ def read(self, n):
+ r = self.buf[self.offset:self.offset+n]
+ if isarray(r):
+ r = r.tostring()
+ self.offset += n
+ return r
+
+try:
+ str(b'dummy', 'ascii')
+except TypeError:
+ as_str = str
+else:
+ def as_str(x):
+ return str(x, 'ascii')
+
+class Reader:
+ """
+ PNG decoder in pure Python.
+ """
+
+ def __init__(self, _guess=None, **kw):
+ """
+ Create a PNG decoder object.
+
+ The constructor expects exactly one keyword argument. If you
+ supply a positional argument instead, it will guess the input
+ type. You can choose among the following keyword arguments:
+
+ filename
+ Name of input file (a PNG file).
+ file
+ A file-like object (object with a read() method).
+ bytes
+ ``array`` or ``string`` with PNG data.
+
+ """
+ if ((_guess is not None and len(kw) != 0) or
+ (_guess is None and len(kw) != 1)):
+ raise TypeError("Reader() takes exactly 1 argument")
+
+ # Will be the first 8 bytes, later on. See validate_signature.
+ self.signature = None
+ self.transparent = None
+ # A pair of (len,type) if a chunk has been read but its data and
+ # checksum have not (in other words the file position is just
+ # past the 4 bytes that specify the chunk type). See preamble
+ # method for how this is used.
+ self.atchunk = None
+
+ if _guess is not None:
+ if isarray(_guess):
+ kw["bytes"] = _guess
+ elif isinstance(_guess, str):
+ kw["filename"] = _guess
+ elif hasattr(_guess, 'read'):
+ kw["file"] = _guess
+
+ if "filename" in kw:
+ self.file = open(kw["filename"], "rb")
+ elif "file" in kw:
+ self.file = kw["file"]
+ elif "bytes" in kw:
+ self.file = _readable(kw["bytes"])
+ else:
+ raise TypeError("expecting filename, file or bytes array")
+
+
+ def chunk(self, seek=None, lenient=False):
+ """
+ Read the next PNG chunk from the input file; returns a
+ (*type*, *data*) tuple. *type* is the chunk's type as a
+ byte string (all PNG chunk types are 4 bytes long).
+ *data* is the chunk's data content, as a byte string.
+
+ If the optional `seek` argument is
+ specified then it will keep reading chunks until it either runs
+ out of file or finds the type specified by the argument. Note
+ that in general the order of chunks in PNGs is unspecified, so
+ using `seek` can cause you to miss chunks.
+
+ If the optional `lenient` argument evaluates to `True`,
+ checksum failures will raise warnings rather than exceptions.
+ """
+
+ self.validate_signature()
+
+ while True:
+ # http://www.w3.org/TR/PNG/#5Chunk-layout
+ if not self.atchunk:
+ self.atchunk = self.chunklentype()
+ length, type = self.atchunk
+ self.atchunk = None
+ data = self.file.read(length)
+ if len(data) != length:
+ raise ChunkError('Chunk %s too short for required %i octets.'
+ % (type, length))
+ checksum = self.file.read(4)
+ if len(checksum) != 4:
+ raise ChunkError('Chunk %s too short for checksum.' % type)
+ if seek and type != seek:
+ continue
+ verify = zlib.crc32(type)
+ verify = zlib.crc32(data, verify)
+ # Whether the output from zlib.crc32 is signed or not varies
+ # according to hideous implementation details, see
+ # http://bugs.python.org/issue1202 .
+ # We coerce it to be positive here (in a way which works on
+ # Python 2.3 and older).
+ verify &= 2**32 - 1
+ verify = struct.pack('!I', verify)
+ if checksum != verify:
+ (a, ) = struct.unpack('!I', checksum)
+ (b, ) = struct.unpack('!I', verify)
+ message = "Checksum error in %s chunk: 0x%08X != 0x%08X." % (type, a, b)
+ if lenient:
+ warnings.warn(message, RuntimeWarning)
+ else:
+ raise ChunkError(message)
+ return type, data
+
+ def chunks(self):
+ """Return an iterator that will yield each chunk as a
+ (*chunktype*, *content*) pair.
+ """
+
+ while True:
+ t,v = self.chunk()
+ yield t,v
+ if t == b'IEND':
+ break
+
+ def undo_filter(self, filter_type, scanline, previous):
+ """Undo the filter for a scanline. `scanline` is a sequence of
+ bytes that does not include the initial filter type byte.
+ `previous` is decoded previous scanline (for straightlaced
+ images this is the previous pixel row, but for interlaced
+ images, it is the previous scanline in the reduced image, which
+ in general is not the previous pixel row in the final image).
+ When there is no previous scanline (the first row of a
+ straightlaced image, or the first row in one of the passes in an
+ interlaced image), then this argument should be ``None``.
+
+ The scanline will have the effects of filtering removed, and the
+ result will be returned as a fresh sequence of bytes.
+ """
+
+ # :todo: Would it be better to update scanline in place?
+ # Yes, with the Cython extension making the undo_filter fast,
+ # updating scanline inplace makes the code 3 times faster
+ # (reading 50 images of 800x800 went from 40s to 16s)
+ result = scanline
+
+ if filter_type == 0:
+ return result
+
+ if filter_type not in (1,2,3,4):
+ raise FormatError('Invalid PNG Filter Type.'
+ ' See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .')
+
+ # Filter unit. The stride from one pixel to the corresponding
+ # byte from the previous pixel. Normally this is the pixel
+ # size in bytes, but when this is smaller than 1, the previous
+ # byte is used instead.
+ fu = max(1, self.psize)
+
+ # For the first line of a pass, synthesize a dummy previous
+ # line. An alternative approach would be to observe that on the
+ # first line 'up' is the same as 'null', 'paeth' is the same
+ # as 'sub', with only 'average' requiring any special case.
+ if not previous:
+ previous = array('B', [0]*len(scanline))
+
+ def sub():
+ """Undo sub filter."""
+
+ ai = 0
+ # Loop starts at index fu. Observe that the initial part
+ # of the result is already filled in correctly with
+ # scanline.
+ for i in range(fu, len(result)):
+ x = scanline[i]
+ a = result[ai]
+ result[i] = (x + a) & 0xff
+ ai += 1
+
+ def up():
+ """Undo up filter."""
+
+ for i in range(len(result)):
+ x = scanline[i]
+ b = previous[i]
+ result[i] = (x + b) & 0xff
+
+ def average():
+ """Undo average filter."""
+
+ ai = -fu
+ for i in range(len(result)):
+ x = scanline[i]
+ if ai < 0:
+ a = 0
+ else:
+ a = result[ai]
+ b = previous[i]
+ result[i] = (x + ((a + b) >> 1)) & 0xff
+ ai += 1
+
+ def paeth():
+ """Undo Paeth filter."""
+
+ # Also used for ci.
+ ai = -fu
+ for i in range(len(result)):
+ x = scanline[i]
+ if ai < 0:
+ a = c = 0
+ else:
+ a = result[ai]
+ c = previous[ai]
+ b = previous[i]
+ p = a + b - c
+ pa = abs(p - a)
+ pb = abs(p - b)
+ pc = abs(p - c)
+ if pa <= pb and pa <= pc:
+ pr = a
+ elif pb <= pc:
+ pr = b
+ else:
+ pr = c
+ result[i] = (x + pr) & 0xff
+ ai += 1
+
+ # Call appropriate filter algorithm. Note that 0 has already
+ # been dealt with.
+ (None,
+ pngfilters.undo_filter_sub,
+ pngfilters.undo_filter_up,
+ pngfilters.undo_filter_average,
+ pngfilters.undo_filter_paeth)[filter_type](fu, scanline, previous, result)
+ return result
+
+ def deinterlace(self, raw):
+ """
+ Read raw pixel data, undo filters, deinterlace, and flatten.
+ Return in flat row flat pixel format.
+ """
+
+ # Values per row (of the target image)
+ vpr = self.width * self.planes
+
+ # Make a result array, and make it big enough. Interleaving
+ # writes to the output array randomly (well, not quite), so the
+ # entire output array must be in memory.
+ fmt = 'BH'[self.bitdepth > 8]
+ a = array(fmt, [0]*vpr*self.height)
+ source_offset = 0
+
+ for xstart, ystart, xstep, ystep in _adam7:
+ if xstart >= self.width:
+ continue
+ # The previous (reconstructed) scanline. None at the
+ # beginning of a pass to indicate that there is no previous
+ # line.
+ recon = None
+ # Pixels per row (reduced pass image)
+ ppr = int(math.ceil((self.width-xstart)/float(xstep)))
+ # Row size in bytes for this pass.
+ row_size = int(math.ceil(self.psize * ppr))
+ for y in range(ystart, self.height, ystep):
+ filter_type = raw[source_offset]
+ source_offset += 1
+ scanline = raw[source_offset:source_offset+row_size]
+ source_offset += row_size
+ recon = self.undo_filter(filter_type, scanline, recon)
+ # Convert so that there is one element per pixel value
+ flat = self.serialtoflat(recon, ppr)
+ if xstep == 1:
+ assert xstart == 0
+ offset = y * vpr
+ a[offset:offset+vpr] = flat
+ else:
+ offset = y * vpr + xstart * self.planes
+ end_offset = (y+1) * vpr
+ skip = self.planes * xstep
+ for i in range(self.planes):
+ a[offset+i:end_offset:skip] = \
+ flat[i::self.planes]
+ return a
+
+ def iterboxed(self, rows):
+ """Iterator that yields each scanline in boxed row flat pixel
+ format. `rows` should be an iterator that yields the bytes of
+ each row in turn.
+ """
+
+ def asvalues(raw):
+ """Convert a row of raw bytes into a flat row. Result will
+ be a freshly allocated object, not shared with
+ argument.
+ """
+
+ if self.bitdepth == 8:
+ return array('B', raw)
+ if self.bitdepth == 16:
+ raw = tostring(raw)
+ return array('H', struct.unpack('!%dH' % (len(raw)//2), raw))
+ assert self.bitdepth < 8
+ width = self.width
+ # Samples per byte
+ spb = 8//self.bitdepth
+ out = array('B')
+ mask = 2**self.bitdepth - 1
+ shifts = [self.bitdepth * i
+ for i in reversed(list(range(spb)))]
+ for o in raw:
+ out.extend([mask&(o>>i) for i in shifts])
+ return out[:width]
+
+ return map(asvalues, rows)
+
+ def serialtoflat(self, bytes, width=None):
+ """Convert serial format (byte stream) pixel data to flat row
+ flat pixel.
+ """
+
+ if self.bitdepth == 8:
+ return bytes
+ if self.bitdepth == 16:
+ bytes = tostring(bytes)
+ return array('H',
+ struct.unpack('!%dH' % (len(bytes)//2), bytes))
+ assert self.bitdepth < 8
+ if width is None:
+ width = self.width
+ # Samples per byte
+ spb = 8//self.bitdepth
+ out = array('B')
+ mask = 2**self.bitdepth - 1
+ shifts = list(map(self.bitdepth.__mul__, reversed(list(range(spb)))))
+ l = width
+ for o in bytes:
+ out.extend([(mask&(o>>s)) for s in shifts][:l])
+ l -= spb
+ if l <= 0:
+ l = width
+ return out
+
+ def iterstraight(self, raw):
+ """Iterator that undoes the effect of filtering, and yields
+ each row in serialised format (as a sequence of bytes).
+ Assumes input is straightlaced. `raw` should be an iterable
+ that yields the raw bytes in chunks of arbitrary size.
+ """
+
+ # length of row, in bytes
+ rb = self.row_bytes
+ a = array('B')
+ # The previous (reconstructed) scanline. None indicates first
+ # line of image.
+ recon = None
+ for some in raw:
+ a.extend(some)
+ while len(a) >= rb + 1:
+ filter_type = a[0]
+ scanline = a[1:rb+1]
+ del a[:rb+1]
+ recon = self.undo_filter(filter_type, scanline, recon)
+ yield recon
+ if len(a) != 0:
+ # :file:format We get here with a file format error:
+ # when the available bytes (after decompressing) do not
+ # pack into exact rows.
+ raise FormatError(
+ 'Wrong size for decompressed IDAT chunk.')
+ assert len(a) == 0
+
+ def validate_signature(self):
+ """If signature (header) has not been read then read and
+ validate it; otherwise do nothing.
+ """
+
+ if self.signature:
+ return
+ self.signature = self.file.read(8)
+ if self.signature != _signature:
+ raise FormatError("PNG file has invalid signature.")
+
+ def preamble(self, lenient=False):
+ """
+ Extract the image metadata by reading the initial part of
+ the PNG file up to the start of the ``IDAT`` chunk. All the
+ chunks that precede the ``IDAT`` chunk are read and either
+ processed for metadata or discarded.
+
+ If the optional `lenient` argument evaluates to `True`, checksum
+ failures will raise warnings rather than exceptions.
+ """
+
+ self.validate_signature()
+
+ while True:
+ if not self.atchunk:
+ self.atchunk = self.chunklentype()
+ if self.atchunk is None:
+ raise FormatError(
+ 'This PNG file has no IDAT chunks.')
+ if self.atchunk[1] == b'IDAT':
+ return
+ self.process_chunk(lenient=lenient)
+
+ def chunklentype(self):
+ """Reads just enough of the input to determine the next
+ chunk's length and type, returned as a (*length*, *type*) pair
+ where *type* is a string. If there are no more chunks, ``None``
+ is returned.
+ """
+
+ x = self.file.read(8)
+ if not x:
+ return None
+ if len(x) != 8:
+ raise FormatError(
+ 'End of file whilst reading chunk length and type.')
+ length,type = struct.unpack('!I4s', x)
+ if length > 2**31-1:
+ raise FormatError('Chunk %s is too large: %d.' % (type,length))
+ return length,type
+
+ def process_chunk(self, lenient=False):
+ """Process the next chunk and its data. This only processes the
+ following chunk types, all others are ignored: ``IHDR``,
+ ``PLTE``, ``bKGD``, ``tRNS``, ``gAMA``, ``sBIT``, ``pHYs``.
+
+ If the optional `lenient` argument evaluates to `True`,
+ checksum failures will raise warnings rather than exceptions.
+ """
+
+ type, data = self.chunk(lenient=lenient)
+ method = '_process_' + as_str(type)
+ m = getattr(self, method, None)
+ if m:
+ m(data)
+
+ def _process_IHDR(self, data):
+ # http://www.w3.org/TR/PNG/#11IHDR
+ if len(data) != 13:
+ raise FormatError('IHDR chunk has incorrect length.')
+ (self.width, self.height, self.bitdepth, self.color_type,
+ self.compression, self.filter,
+ self.interlace) = struct.unpack("!2I5B", data)
+
+ check_bitdepth_colortype(self.bitdepth, self.color_type)
+
+ if self.compression != 0:
+ raise Error("unknown compression method %d" % self.compression)
+ if self.filter != 0:
+ raise FormatError("Unknown filter method %d,"
+ " see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ."
+ % self.filter)
+ if self.interlace not in (0,1):
+ raise FormatError("Unknown interlace method %d,"
+ " see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ."
+ % self.interlace)
+
+ # Derived values
+ # http://www.w3.org/TR/PNG/#6Colour-values
+ colormap = bool(self.color_type & 1)
+ greyscale = not (self.color_type & 2)
+ alpha = bool(self.color_type & 4)
+ color_planes = (3,1)[greyscale or colormap]
+ planes = color_planes + alpha
+
+ self.colormap = colormap
+ self.greyscale = greyscale
+ self.alpha = alpha
+ self.color_planes = color_planes
+ self.planes = planes
+ self.psize = float(self.bitdepth)/float(8) * planes
+ if int(self.psize) == self.psize:
+ self.psize = int(self.psize)
+ self.row_bytes = int(math.ceil(self.width * self.psize))
+ # Stores PLTE chunk if present, and is used to check
+ # chunk ordering constraints.
+ self.plte = None
+ # Stores tRNS chunk if present, and is used to check chunk
+ # ordering constraints.
+ self.trns = None
+ # Stores sbit chunk if present.
+ self.sbit = None
+
+ def _process_PLTE(self, data):
+ # http://www.w3.org/TR/PNG/#11PLTE
+ if self.plte:
+ warnings.warn("Multiple PLTE chunks present.")
+ self.plte = data
+ if len(data) % 3 != 0:
+ raise FormatError(
+ "PLTE chunk's length should be a multiple of 3.")
+ if len(data) > (2**self.bitdepth)*3:
+ raise FormatError("PLTE chunk is too long.")
+ if len(data) == 0:
+ raise FormatError("Empty PLTE is not allowed.")
+
+ def _process_bKGD(self, data):
+ try:
+ if self.colormap:
+ if not self.plte:
+ warnings.warn(
+ "PLTE chunk is required before bKGD chunk.")
+ self.background = struct.unpack('B', data)
+ else:
+ self.background = struct.unpack("!%dH" % self.color_planes,
+ data)
+ except struct.error:
+ raise FormatError("bKGD chunk has incorrect length.")
+
+ def _process_tRNS(self, data):
+ # http://www.w3.org/TR/PNG/#11tRNS
+ self.trns = data
+ if self.colormap:
+ if not self.plte:
+ warnings.warn("PLTE chunk is required before tRNS chunk.")
+ else:
+ if len(data) > len(self.plte)/3:
+ # Was warning, but promoted to Error as it
+ # would otherwise cause pain later on.
+ raise FormatError("tRNS chunk is too long.")
+ else:
+ if self.alpha:
+ raise FormatError(
+ "tRNS chunk is not valid with colour type %d." %
+ self.color_type)
+ try:
+ self.transparent = \
+ struct.unpack("!%dH" % self.color_planes, data)
+ except struct.error:
+ raise FormatError("tRNS chunk has incorrect length.")
+
+ def _process_gAMA(self, data):
+ try:
+ self.gamma = struct.unpack("!L", data)[0] / 100000.0
+ except struct.error:
+ raise FormatError("gAMA chunk has incorrect length.")
+
+ def _process_sBIT(self, data):
+ self.sbit = data
+ if (self.colormap and len(data) != 3 or
+ not self.colormap and len(data) != self.planes):
+ raise FormatError("sBIT chunk has incorrect length.")
+
+ def _process_pHYs(self, data):
+ # http://www.w3.org/TR/PNG/#11pHYs
+ self.phys = data
+ fmt = "!LLB"
+ if len(data) != struct.calcsize(fmt):
+ raise FormatError("pHYs chunk has incorrect length.")
+ self.x_pixels_per_unit, self.y_pixels_per_unit, unit = struct.unpack(fmt,data)
+ self.unit_is_meter = bool(unit)
+
+ def read(self, lenient=False):
+ """
+ Read the PNG file and decode it. Returns (`width`, `height`,
+ `pixels`, `metadata`).
+
+ May use excessive memory.
+
+ `pixels` are returned in boxed row flat pixel format.
+
+ If the optional `lenient` argument evaluates to True,
+ checksum failures will raise warnings rather than exceptions.
+ """
+
+ def iteridat():
+ """Iterator that yields all the ``IDAT`` chunks as strings."""
+ while True:
+ try:
+ type, data = self.chunk(lenient=lenient)
+ except ValueError as e:
+ raise ChunkError(e.args[0])
+ if type == b'IEND':
+ # http://www.w3.org/TR/PNG/#11IEND
+ break
+ if type != b'IDAT':
+ continue
+ # type == b'IDAT'
+ # http://www.w3.org/TR/PNG/#11IDAT
+ if self.colormap and not self.plte:
+ warnings.warn("PLTE chunk is required before IDAT chunk")
+ yield data
+
+ def iterdecomp(idat):
+ """Iterator that yields decompressed strings. `idat` should
+ be an iterator that yields the ``IDAT`` chunk data.
+ """
+
+ # Currently, with no max_length parameter to decompress,
+ # this routine will do one yield per IDAT chunk: Not very
+ # incremental.
+ d = zlib.decompressobj()
+ # Each IDAT chunk is passed to the decompressor, then any
+ # remaining state is decompressed out.
+ for data in idat:
+ # :todo: add a max_length argument here to limit output
+ # size.
+ yield array('B', d.decompress(data))
+ yield array('B', d.flush())
+
+ self.preamble(lenient=lenient)
+ raw = iterdecomp(iteridat())
+
+ if self.interlace:
+ raw = array('B', itertools.chain(*raw))
+ arraycode = 'BH'[self.bitdepth>8]
+ # Like :meth:`group` but producing an array.array object for
+ # each row.
+ pixels = map(lambda *row: array(arraycode, row),
+ *[iter(self.deinterlace(raw))]*self.width*self.planes)
+ else:
+ pixels = self.iterboxed(self.iterstraight(raw))
+ meta = dict()
+ for attr in 'greyscale alpha planes bitdepth interlace'.split():
+ meta[attr] = getattr(self, attr)
+ meta['size'] = (self.width, self.height)
+ for attr in 'gamma transparent background'.split():
+ a = getattr(self, attr, None)
+ if a is not None:
+ meta[attr] = a
+ if self.plte:
+ meta['palette'] = self.palette()
+ return self.width, self.height, pixels, meta
+
+
+ def read_flat(self):
+ """
+ Read a PNG file and decode it into flat row flat pixel format.
+ Returns (*width*, *height*, *pixels*, *metadata*).
+
+ May use excessive memory.
+
+ `pixels` are returned in flat row flat pixel format.
+
+ See also the :meth:`read` method which returns pixels in the
+ more stream-friendly boxed row flat pixel format.
+ """
+
+ x, y, pixel, meta = self.read()
+ arraycode = 'BH'[meta['bitdepth']>8]
+ pixel = array(arraycode, itertools.chain(*pixel))
+ return x, y, pixel, meta
+
+ def palette(self, alpha='natural'):
+ """Returns a palette that is a sequence of 3-tuples or 4-tuples,
+ synthesizing it from the ``PLTE`` and ``tRNS`` chunks. These
+ chunks should have already been processed (for example, by
+ calling the :meth:`preamble` method). All the tuples are the
+ same size: 3-tuples if there is no ``tRNS`` chunk, 4-tuples when
+ there is a ``tRNS`` chunk. Assumes that the image is colour type
+ 3 and therefore a ``PLTE`` chunk is required.
+
+ If the `alpha` argument is ``'force'`` then an alpha channel is
+ always added, forcing the result to be a sequence of 4-tuples.
+ """
+
+ if not self.plte:
+ raise FormatError(
+ "Required PLTE chunk is missing in colour type 3 image.")
+ plte = group(array('B', self.plte), 3)
+ if self.trns or alpha == 'force':
+ trns = array('B', self.trns or '')
+ trns.extend([255]*(len(plte)-len(trns)))
+ plte = list(map(operator.add, plte, group(trns, 1)))
+ return plte
+
+ def asDirect(self):
+ """Returns the image data as a direct representation of an
+ ``x * y * planes`` array. This method is intended to remove the
+ need for callers to deal with palettes and transparency
+ themselves. Images with a palette (colour type 3)
+ are converted to RGB or RGBA; images with transparency (a
+ ``tRNS`` chunk) are converted to LA or RGBA as appropriate.
+ When returned in this format the pixel values represent the
+ colour value directly without needing to refer to palettes or
+ transparency information.
+
+ Like the :meth:`read` method this method returns a 4-tuple:
+
+ (*width*, *height*, *pixels*, *meta*)
+
+ This method normally returns pixel values with the bit depth
+ they have in the source image, but when the source PNG has an
+ ``sBIT`` chunk it is inspected and can reduce the bit depth of
+ the result pixels; pixel values will be reduced according to
+ the bit depth specified in the ``sBIT`` chunk (PNG nerds should
+ note a single result bit depth is used for all channels; the
+ maximum of the ones specified in the ``sBIT`` chunk. An RGB565
+ image will be rescaled to 6-bit RGB666).
+
+ The *meta* dictionary that is returned reflects the `direct`
+ format and not the original source image. For example, an RGB
+ source image with a ``tRNS`` chunk to represent a transparent
+ colour, will have ``planes=3`` and ``alpha=False`` for the
+ source image, but the *meta* dictionary returned by this method
+ will have ``planes=4`` and ``alpha=True`` because an alpha
+ channel is synthesized and added.
+
+ *pixels* is the pixel data in boxed row flat pixel format (just
+ like the :meth:`read` method).
+
+ All the other aspects of the image data are not changed.
+ """
+
+ self.preamble()
+
+ # Simple case, no conversion necessary.
+ if not self.colormap and not self.trns and not self.sbit:
+ return self.read()
+
+ x,y,pixels,meta = self.read()
+
+ if self.colormap:
+ meta['colormap'] = False
+ meta['alpha'] = bool(self.trns)
+ meta['bitdepth'] = 8
+ meta['planes'] = 3 + bool(self.trns)
+ plte = self.palette()
+ def iterpal(pixels):
+ for row in pixels:
+ row = [plte[x] for x in row]
+ yield array('B', itertools.chain(*row))
+ pixels = iterpal(pixels)
+ elif self.trns:
+ # It would be nice if there was some reasonable way
+ # of doing this without generating a whole load of
+ # intermediate tuples. But tuples does seem like the
+ # easiest way, with no other way clearly much simpler or
+ # much faster. (Actually, the L to LA conversion could
+ # perhaps go faster (all those 1-tuples!), but I still
+ # wonder whether the code proliferation is worth it)
+ it = self.transparent
+ maxval = 2**meta['bitdepth']-1
+ planes = meta['planes']
+ meta['alpha'] = True
+ meta['planes'] += 1
+ typecode = 'BH'[meta['bitdepth']>8]
+ def itertrns(pixels):
+ for row in pixels:
+ # For each row we group it into pixels, then form a
+ # characterisation vector that says whether each
+ # pixel is opaque or not. Then we convert
+ # True/False to 0/maxval (by multiplication),
+ # and add it as the extra channel.
+ row = group(row, planes)
+ opa = map(it.__ne__, row)
+ opa = map(maxval.__mul__, opa)
+ opa = list(zip(opa)) # convert to 1-tuples
+ yield array(typecode,
+ itertools.chain(*map(operator.add, row, opa)))
+ pixels = itertrns(pixels)
+ targetbitdepth = None
+ if self.sbit:
+ sbit = struct.unpack('%dB' % len(self.sbit), self.sbit)
+ targetbitdepth = max(sbit)
+ if targetbitdepth > meta['bitdepth']:
+ raise Error('sBIT chunk %r exceeds bitdepth %d' %
+ (sbit,self.bitdepth))
+ if min(sbit) <= 0:
+ raise Error('sBIT chunk %r has a 0-entry' % sbit)
+ if targetbitdepth == meta['bitdepth']:
+ targetbitdepth = None
+ if targetbitdepth:
+ shift = meta['bitdepth'] - targetbitdepth
+ meta['bitdepth'] = targetbitdepth
+ def itershift(pixels):
+ for row in pixels:
+ yield [p >> shift for p in row]
+ pixels = itershift(pixels)
+ return x,y,pixels,meta
+
+ def asFloat(self, maxval=1.0):
+ """Return image pixels as per :meth:`asDirect` method, but scale
+ all pixel values to be floating point values between 0.0 and
+ *maxval*.
+ """
+
+ x,y,pixels,info = self.asDirect()
+ sourcemaxval = 2**info['bitdepth']-1
+ del info['bitdepth']
+ info['maxval'] = float(maxval)
+ factor = float(maxval)/float(sourcemaxval)
+ def iterfloat():
+ for row in pixels:
+ yield [factor * p for p in row]
+ return x,y,iterfloat(),info
+
+ def _as_rescale(self, get, targetbitdepth):
+ """Helper used by :meth:`asRGB8` and :meth:`asRGBA8`."""
+
+ width,height,pixels,meta = get()
+ maxval = 2**meta['bitdepth'] - 1
+ targetmaxval = 2**targetbitdepth - 1
+ factor = float(targetmaxval) / float(maxval)
+ meta['bitdepth'] = targetbitdepth
+ def iterscale():
+ for row in pixels:
+ yield [int(round(x*factor)) for x in row]
+ if maxval == targetmaxval:
+ return width, height, pixels, meta
+ else:
+ return width, height, iterscale(), meta
+
+ def asRGB8(self):
+ """Return the image data as an RGB pixels with 8-bits per
+ sample. This is like the :meth:`asRGB` method except that
+ this method additionally rescales the values so that they
+ are all between 0 and 255 (8-bit). In the case where the
+ source image has a bit depth < 8 the transformation preserves
+ all the information; where the source image has bit depth
+ > 8, then rescaling to 8-bit values loses precision. No
+ dithering is performed. Like :meth:`asRGB`, an alpha channel
+ in the source image will raise an exception.
+
+ This function returns a 4-tuple:
+ (*width*, *height*, *pixels*, *metadata*).
+ *width*, *height*, *metadata* are as per the
+ :meth:`read` method.
+
+ *pixels* is the pixel data in boxed row flat pixel format.
+ """
+
+ return self._as_rescale(self.asRGB, 8)
+
+ def asRGBA8(self):
+ """Return the image data as RGBA pixels with 8-bits per
+ sample. This method is similar to :meth:`asRGB8` and
+ :meth:`asRGBA`: The result pixels have an alpha channel, *and*
+ values are rescaled to the range 0 to 255. The alpha channel is
+ synthesized if necessary (with a small speed penalty).
+ """
+
+ return self._as_rescale(self.asRGBA, 8)
+
+ def asRGB(self):
+ """Return image as RGB pixels. RGB colour images are passed
+ through unchanged; greyscales are expanded into RGB
+ triplets (there is a small speed overhead for doing this).
+
+ An alpha channel in the source image will raise an
+ exception.
+
+ The return values are as for the :meth:`read` method
+ except that the *metadata* reflect the returned pixels, not the
+ source image. In particular, for this method
+ ``metadata['greyscale']`` will be ``False``.
+ """
+
+ width,height,pixels,meta = self.asDirect()
+ if meta['alpha']:
+ raise Error("will not convert image with alpha channel to RGB")
+ if not meta['greyscale']:
+ return width,height,pixels,meta
+ meta['greyscale'] = False
+ typecode = 'BH'[meta['bitdepth'] > 8]
+ def iterrgb():
+ for row in pixels:
+ a = array(typecode, [0]) * 3 * width
+ for i in range(3):
+ a[i::3] = row
+ yield a
+ return width,height,iterrgb(),meta
+
+ def asRGBA(self):
+ """Return image as RGBA pixels. Greyscales are expanded into
+ RGB triplets; an alpha channel is synthesized if necessary.
+ The return values are as for the :meth:`read` method
+ except that the *metadata* reflect the returned pixels, not the
+ source image. In particular, for this method
+ ``metadata['greyscale']`` will be ``False``, and
+ ``metadata['alpha']`` will be ``True``.
+ """
+
+ width,height,pixels,meta = self.asDirect()
+ if meta['alpha'] and not meta['greyscale']:
+ return width,height,pixels,meta
+ typecode = 'BH'[meta['bitdepth'] > 8]
+ maxval = 2**meta['bitdepth'] - 1
+ maxbuffer = struct.pack('=' + typecode, maxval) * 4 * width
+ def newarray():
+ return array(typecode, maxbuffer)
+
+ if meta['alpha'] and meta['greyscale']:
+ # LA to RGBA
+ def convert():
+ for row in pixels:
+ # Create a fresh target row, then copy L channel
+ # into first three target channels, and A channel
+ # into fourth channel.
+ a = newarray()
+ pngfilters.convert_la_to_rgba(row, a)
+ yield a
+ elif meta['greyscale']:
+ # L to RGBA
+ def convert():
+ for row in pixels:
+ a = newarray()
+ pngfilters.convert_l_to_rgba(row, a)
+ yield a
+ else:
+ assert not meta['alpha'] and not meta['greyscale']
+ # RGB to RGBA
+ def convert():
+ for row in pixels:
+ a = newarray()
+ pngfilters.convert_rgb_to_rgba(row, a)
+ yield a
+ meta['alpha'] = True
+ meta['greyscale'] = False
+ return width,height,convert(),meta
+
+def check_bitdepth_colortype(bitdepth, colortype):
+ """Check that `bitdepth` and `colortype` are both valid,
+ and specified in a valid combination. Returns if valid,
+ raise an Exception if not valid.
+ """
+
+ if bitdepth not in (1,2,4,8,16):
+ raise FormatError("invalid bit depth %d" % bitdepth)
+ if colortype not in (0,2,3,4,6):
+ raise FormatError("invalid colour type %d" % colortype)
+ # Check indexed (palettized) images have 8 or fewer bits
+ # per pixel; check only indexed or greyscale images have
+ # fewer than 8 bits per pixel.
+ if colortype & 1 and bitdepth > 8:
+ raise FormatError(
+ "Indexed images (colour type %d) cannot"
+ " have bitdepth > 8 (bit depth %d)."
+ " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
+ % (bitdepth, colortype))
+ if bitdepth < 8 and colortype not in (0,3):
+ raise FormatError("Illegal combination of bit depth (%d)"
+ " and colour type (%d)."
+ " See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
+ % (bitdepth, colortype))
+
+def isinteger(x):
+ try:
+ return int(x) == x
+ except (TypeError, ValueError):
+ return False
+
+
+# === Support for users without Cython ===
+
+try:
+ pngfilters
+except NameError:
+ class pngfilters(object):
+ def undo_filter_sub(filter_unit, scanline, previous, result):
+ """Undo sub filter."""
+
+ ai = 0
+ # Loops starts at index fu. Observe that the initial part
+ # of the result is already filled in correctly with
+ # scanline.
+ for i in range(filter_unit, len(result)):
+ x = scanline[i]
+ a = result[ai]
+ result[i] = (x + a) & 0xff
+ ai += 1
+ undo_filter_sub = staticmethod(undo_filter_sub)
+
+ def undo_filter_up(filter_unit, scanline, previous, result):
+ """Undo up filter."""
+
+ for i in range(len(result)):
+ x = scanline[i]
+ b = previous[i]
+ result[i] = (x + b) & 0xff
+ undo_filter_up = staticmethod(undo_filter_up)
+
+ def undo_filter_average(filter_unit, scanline, previous, result):
+ """Undo up filter."""
+
+ ai = -filter_unit
+ for i in range(len(result)):
+ x = scanline[i]
+ if ai < 0:
+ a = 0
+ else:
+ a = result[ai]
+ b = previous[i]
+ result[i] = (x + ((a + b) >> 1)) & 0xff
+ ai += 1
+ undo_filter_average = staticmethod(undo_filter_average)
+
+ def undo_filter_paeth(filter_unit, scanline, previous, result):
+ """Undo Paeth filter."""
+
+ # Also used for ci.
+ ai = -filter_unit
+ for i in range(len(result)):
+ x = scanline[i]
+ if ai < 0:
+ a = c = 0
+ else:
+ a = result[ai]
+ c = previous[ai]
+ b = previous[i]
+ p = a + b - c
+ pa = abs(p - a)
+ pb = abs(p - b)
+ pc = abs(p - c)
+ if pa <= pb and pa <= pc:
+ pr = a
+ elif pb <= pc:
+ pr = b
+ else:
+ pr = c
+ result[i] = (x + pr) & 0xff
+ ai += 1
+ undo_filter_paeth = staticmethod(undo_filter_paeth)
+
+ def convert_la_to_rgba(row, result):
+ for i in range(3):
+ result[i::4] = row[0::2]
+ result[3::4] = row[1::2]
+ convert_la_to_rgba = staticmethod(convert_la_to_rgba)
+
+ def convert_l_to_rgba(row, result):
+ """Convert a grayscale image to RGBA. This method assumes
+ the alpha channel in result is already correctly
+ initialized.
+ """
+ for i in range(3):
+ result[i::4] = row
+ convert_l_to_rgba = staticmethod(convert_l_to_rgba)
+
+ def convert_rgb_to_rgba(row, result):
+ """Convert an RGB image to RGBA. This method assumes the
+ alpha channel in result is already correctly initialized.
+ """
+ for i in range(3):
+ result[i::4] = row[i::3]
+ convert_rgb_to_rgba = staticmethod(convert_rgb_to_rgba)
+
+
+# === Command Line Support ===
+
+def read_pam_header(infile):
+ """
+ Read (the rest of a) PAM header. `infile` should be positioned
+ immediately after the initial 'P7' line (at the beginning of the
+ second line). Returns are as for `read_pnm_header`.
+ """
+
+ # Unlike PBM, PGM, and PPM, we can read the header a line at a time.
+ header = dict()
+ while True:
+ l = infile.readline().strip()
+ if l == b'ENDHDR':
+ break
+ if not l:
+ raise EOFError('PAM ended prematurely')
+ if l[0] == b'#':
+ continue
+ l = l.split(None, 1)
+ if l[0] not in header:
+ header[l[0]] = l[1]
+ else:
+ header[l[0]] += b' ' + l[1]
+
+ required = [b'WIDTH', b'HEIGHT', b'DEPTH', b'MAXVAL']
+ WIDTH,HEIGHT,DEPTH,MAXVAL = required
+ present = [x for x in required if x in header]
+ if len(present) != len(required):
+ raise Error('PAM file must specify WIDTH, HEIGHT, DEPTH, and MAXVAL')
+ width = int(header[WIDTH])
+ height = int(header[HEIGHT])
+ depth = int(header[DEPTH])
+ maxval = int(header[MAXVAL])
+ if (width <= 0 or
+ height <= 0 or
+ depth <= 0 or
+ maxval <= 0):
+ raise Error(
+ 'WIDTH, HEIGHT, DEPTH, MAXVAL must all be positive integers')
+ return 'P7', width, height, depth, maxval
+
+def read_pnm_header(infile, supported=(b'P5', b'P6')):
+ """
+ Read a PNM header, returning (format,width,height,depth,maxval).
+ `width` and `height` are in pixels. `depth` is the number of
+ channels in the image; for PBM and PGM it is synthesized as 1, for
+ PPM as 3; for PAM images it is read from the header. `maxval` is
+ synthesized (as 1) for PBM images.
+ """
+
+ # Generally, see http://netpbm.sourceforge.net/doc/ppm.html
+ # and http://netpbm.sourceforge.net/doc/pam.html
+
+ # Technically 'P7' must be followed by a newline, so by using
+ # rstrip() we are being liberal in what we accept. I think this
+ # is acceptable.
+ type = infile.read(3).rstrip()
+ if type not in supported:
+ raise NotImplementedError('file format %s not supported' % type)
+ if type == b'P7':
+ # PAM header parsing is completely different.
+ return read_pam_header(infile)
+ # Expected number of tokens in header (3 for P4, 4 for P6)
+ expected = 4
+ pbm = (b'P1', b'P4')
+ if type in pbm:
+ expected = 3
+ header = [type]
+
+ # We have to read the rest of the header byte by byte because the
+ # final whitespace character (immediately following the MAXVAL in
+ # the case of P6) may not be a newline. Of course all PNM files in
+ # the wild use a newline at this point, so it's tempting to use
+ # readline; but it would be wrong.
+ def getc():
+ c = infile.read(1)
+ if not c:
+ raise Error('premature EOF reading PNM header')
+ return c
+
+ c = getc()
+ while True:
+ # Skip whitespace that precedes a token.
+ while c.isspace():
+ c = getc()
+ # Skip comments.
+ while c == '#':
+ while c not in b'\n\r':
+ c = getc()
+ if not c.isdigit():
+ raise Error('unexpected character %s found in header' % c)
+ # According to the specification it is legal to have comments
+ # that appear in the middle of a token.
+ # This is bonkers; I've never seen it; and it's a bit awkward to
+ # code good lexers in Python (no goto). So we break on such
+ # cases.
+ token = b''
+ while c.isdigit():
+ token += c
+ c = getc()
+ # Slight hack. All "tokens" are decimal integers, so convert
+ # them here.
+ header.append(int(token))
+ if len(header) == expected:
+ break
+ # Skip comments (again)
+ while c == '#':
+ while c not in '\n\r':
+ c = getc()
+ if not c.isspace():
+ raise Error('expected header to end with whitespace, not %s' % c)
+
+ if type in pbm:
+ # synthesize a MAXVAL
+ header.append(1)
+ depth = (1,3)[type == b'P6']
+ return header[0], header[1], header[2], depth, header[3]
+
+def write_pnm(file, width, height, pixels, meta):
+ """Write a Netpbm PNM/PAM file.
+ """
+
+ bitdepth = meta['bitdepth']
+ maxval = 2**bitdepth - 1
+ # Rudely, the number of image planes can be used to determine
+ # whether we are L (PGM), LA (PAM), RGB (PPM), or RGBA (PAM).
+ planes = meta['planes']
+ # Can be an assert as long as we assume that pixels and meta came
+ # from a PNG file.
+ assert planes in (1,2,3,4)
+ if planes in (1,3):
+ if 1 == planes:
+ # PGM
+ # Could generate PBM if maxval is 1, but we don't (for one
+ # thing, we'd have to convert the data, not just blat it
+ # out).
+ fmt = 'P5'
+ else:
+ # PPM
+ fmt = 'P6'
+ header = '%s %d %d %d\n' % (fmt, width, height, maxval)
+ if planes in (2,4):
+ # PAM
+ # See http://netpbm.sourceforge.net/doc/pam.html
+ if 2 == planes:
+ tupltype = 'GRAYSCALE_ALPHA'
+ else:
+ tupltype = 'RGB_ALPHA'
+ header = ('P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\n'
+ 'TUPLTYPE %s\nENDHDR\n' %
+ (width, height, planes, maxval, tupltype))
+ file.write(header.encode('ascii'))
+ # Values per row
+ vpr = planes * width
+ # struct format
+ fmt = '>%d' % vpr
+ if maxval > 0xff:
+ fmt = fmt + 'H'
+ else:
+ fmt = fmt + 'B'
+ for row in pixels:
+ file.write(struct.pack(fmt, *row))
+ file.flush()
+
+def color_triple(color):
+ """
+ Convert a command line colour value to a RGB triple of integers.
+ FIXME: Somewhere we need support for greyscale backgrounds etc.
+ """
+ if color.startswith('#') and len(color) == 4:
+ return (int(color[1], 16),
+ int(color[2], 16),
+ int(color[3], 16))
+ if color.startswith('#') and len(color) == 7:
+ return (int(color[1:3], 16),
+ int(color[3:5], 16),
+ int(color[5:7], 16))
+ elif color.startswith('#') and len(color) == 13:
+ return (int(color[1:5], 16),
+ int(color[5:9], 16),
+ int(color[9:13], 16))
+
+def _add_common_options(parser):
+ """Call *parser.add_option* for each of the options that are
+ common between this PNG--PNM conversion tool and the gen
+ tool.
+ """
+ parser.add_option("-i", "--interlace",
+ default=False, action="store_true",
+ help="create an interlaced PNG file (Adam7)")
+ parser.add_option("-t", "--transparent",
+ action="store", type="string", metavar="#RRGGBB",
+ help="mark the specified colour as transparent")
+ parser.add_option("-b", "--background",
+ action="store", type="string", metavar="#RRGGBB",
+ help="save the specified background colour")
+ parser.add_option("-g", "--gamma",
+ action="store", type="float", metavar="value",
+ help="save the specified gamma value")
+ parser.add_option("-c", "--compression",
+ action="store", type="int", metavar="level",
+ help="zlib compression level (0-9)")
+ return parser
+
+def _main(argv):
+ """
+ Run the PNG encoder with options from the command line.
+ """
+
+ # Parse command line arguments
+ from optparse import OptionParser
+ version = '%prog ' + __version__
+ parser = OptionParser(version=version)
+ parser.set_usage("%prog [options] [imagefile]")
+ parser.add_option('-r', '--read-png', default=False,
+ action='store_true',
+ help='Read PNG, write PNM')
+ parser.add_option("-a", "--alpha",
+ action="store", type="string", metavar="pgmfile",
+ help="alpha channel transparency (RGBA)")
+ _add_common_options(parser)
+
+ (options, args) = parser.parse_args(args=argv[1:])
+
+ # Convert options
+ if options.transparent is not None:
+ options.transparent = color_triple(options.transparent)
+ if options.background is not None:
+ options.background = color_triple(options.background)
+
+ # Prepare input and output files
+ if len(args) == 0:
+ infilename = '-'
+ infile = sys.stdin
+ elif len(args) == 1:
+ infilename = args[0]
+ infile = open(infilename, 'rb')
+ else:
+ parser.error("more than one input file")
+ outfile = sys.stdout
+ if sys.platform == "win32":
+ import msvcrt, os
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+
+ if options.read_png:
+ # Encode PNG to PPM
+ png = Reader(file=infile)
+ width,height,pixels,meta = png.asDirect()
+ write_pnm(outfile, width, height, pixels, meta)
+ else:
+ # Encode PNM to PNG
+ format, width, height, depth, maxval = \
+ read_pnm_header(infile, (b'P5',b'P6',b'P7'))
+ # When it comes to the variety of input formats, we do something
+ # rather rude. Observe that L, LA, RGB, RGBA are the 4 colour
+ # types supported by PNG and that they correspond to 1, 2, 3, 4
+ # channels respectively. So we use the number of channels in
+ # the source image to determine which one we have. We do not
+ # care about TUPLTYPE.
+ greyscale = depth <= 2
+ pamalpha = depth in (2,4)
+ supported = [2**x-1 for x in range(1,17)]
+ try:
+ mi = supported.index(maxval)
+ except ValueError:
+ raise NotImplementedError(
+ 'your maxval (%s) not in supported list %s' %
+ (maxval, str(supported)))
+ bitdepth = mi+1
+ writer = Writer(width, height,
+ greyscale=greyscale,
+ bitdepth=bitdepth,
+ interlace=options.interlace,
+ transparent=options.transparent,
+ background=options.background,
+ alpha=bool(pamalpha or options.alpha),
+ gamma=options.gamma,
+ compression=options.compression)
+ if options.alpha:
+ pgmfile = open(options.alpha, 'rb')
+ format, awidth, aheight, adepth, amaxval = \
+ read_pnm_header(pgmfile, 'P5')
+ if amaxval != '255':
+ raise NotImplementedError(
+ 'maxval %s not supported for alpha channel' % amaxval)
+ if (awidth, aheight) != (width, height):
+ raise ValueError("alpha channel image size mismatch"
+ " (%s has %sx%s but %s has %sx%s)"
+ % (infilename, width, height,
+ options.alpha, awidth, aheight))
+ writer.convert_ppm_and_pgm(infile, pgmfile, outfile)
+ else:
+ writer.convert_pnm(infile, outfile)
+
+
+if __name__ == '__main__':
+ try:
+ _main(sys.argv)
+ except Error as e:
+ print(e, file=sys.stderr)
diff --git a/tools/scan_includes.py b/tools/scan_includes.py
new file mode 100644
index 0000000..53ff091
--- /dev/null
+++ b/tools/scan_includes.py
@@ -0,0 +1,43 @@
+#!/bin/python
+# coding: utf-8
+
+"""
+Recursively scan an asm file for dependencies.
+"""
+
+import sys
+import argparse
+import os.path
+
+includes = set()
+
+def scan_file(filename):
+ for line in open(filename):
+ if 'INC' not in line:
+ continue
+ line = line.split(';')[0]
+ if 'INCLUDE' in line:
+ include = line.split('"')[1]
+ if os.path.exists("src/"):
+ includes.add("src/" + include)
+ scan_file("src/" + include)
+ else:
+ includes.add(include)
+ scan_file(include)
+ elif 'INCBIN' in line:
+ include = line.split('"')[1]
+ if 'baserom.gbc' not in line and os.path.exists("src/"):
+ includes.add("src/" + include)
+ else:
+ includes.add(include)
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument('filenames', nargs='*')
+ args = ap.parse_args()
+ for filename in set(args.filenames):
+ scan_file(filename)
+ sys.stdout.write(' '.join(includes))
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/tcgdisasm.py b/tools/tcgdisasm.py
new file mode 100644
index 0000000..75d1257
--- /dev/null
+++ b/tools/tcgdisasm.py
@@ -0,0 +1,947 @@
+# -*- coding: utf-8 -*-
+"""
+GBC disassembler
+"""
+
+import os
+import argparse
+from ctypes import c_int8
+
+import configuration
+from wram import read_constants
+
+z80_table = [
+ ('nop', 0), # 00
+ ('ld bc, {}', 2), # 01
+ ('ld [bc], a', 0), # 02
+ ('inc bc', 0), # 03
+ ('inc b', 0), # 04
+ ('dec b', 0), # 05
+ ('ld b, ${:02x}', 1), # 06
+ ('rlca', 0), # 07
+ ('ld [{}], sp', 2), # 08
+ ('add hl, bc', 0), # 09
+ ('ld a, [bc]', 0), # 0a
+ ('dec bc', 0), # 0b
+ ('inc c', 0), # 0c
+ ('dec c', 0), # 0d
+ ('ld c, ${:02x}', 1), # 0e
+ ('rrca', 0), # 0f
+ ('db $10', 0), # 10
+ ('ld de, {}', 2), # 11
+ ('ld [de], a', 0), # 12
+ ('inc de', 0), # 13
+ ('inc d', 0), # 14
+ ('dec d', 0), # 15
+ ('ld d, ${:02x}', 1), # 16
+ ('rla', 0), # 17
+ ('jr {}', 1), # 18
+ ('add hl, de', 0), # 19
+ ('ld a, [de]', 0), # 1a
+ ('dec de', 0), # 1b
+ ('inc e', 0), # 1c
+ ('dec e', 0), # 1d
+ ('ld e, ${:02x}', 1), # 1e
+ ('rra', 0), # 1f
+ ('jr nz, {}', 1), # 20
+ ('ld hl, {}', 2), # 21
+ ('ld [hli], a', 0), # 22
+ ('inc hl', 0), # 23
+ ('inc h', 0), # 24
+ ('dec h', 0), # 25
+ ('ld h, ${:02x}', 1), # 26
+ ('daa', 0), # 27
+ ('jr z, {}', 1), # 28
+ ('add hl, hl', 0), # 29
+ ('ld a, [hli]', 0), # 2a
+ ('dec hl', 0), # 2b
+ ('inc l', 0), # 2c
+ ('dec l', 0), # 2d
+ ('ld l, ${:02x}', 1), # 2e
+ ('cpl', 0), # 2f
+ ('jr nc, {}', 1), # 30
+ ('ld sp, {}', 2), # 31
+ ('ld [hld], a', 0), # 32
+ ('inc sp', 0), # 33
+ ('inc [hl]', 0), # 34
+ ('dec [hl]', 0), # 35
+ ('ld [hl], ${:02x}', 1), # 36
+ ('scf', 0), # 37
+ ('jr c, {}', 1), # 38
+ ('add hl, sp', 0), # 39
+ ('ld a, [hld]', 0), # 3a
+ ('dec sp', 0), # 3b
+ ('inc a', 0), # 3c
+ ('dec a', 0), # 3d
+ ('ld a, ${:02x}', 1), # 3e
+ ('ccf', 0), # 3f
+ ('ld b, b', 0), # 40
+ ('ld b, c', 0), # 41
+ ('ld b, d', 0), # 42
+ ('ld b, e', 0), # 43
+ ('ld b, h', 0), # 44
+ ('ld b, l', 0), # 45
+ ('ld b, [hl]', 0), # 46
+ ('ld b, a', 0), # 47
+ ('ld c, b', 0), # 48
+ ('ld c, c', 0), # 49
+ ('ld c, d', 0), # 4a
+ ('ld c, e', 0), # 4b
+ ('ld c, h', 0), # 4c
+ ('ld c, l', 0), # 4d
+ ('ld c, [hl]', 0), # 4e
+ ('ld c, a', 0), # 4f
+ ('ld d, b', 0), # 50
+ ('ld d, c', 0), # 51
+ ('ld d, d', 0), # 52
+ ('ld d, e', 0), # 53
+ ('ld d, h', 0), # 54
+ ('ld d, l', 0), # 55
+ ('ld d, [hl]', 0), # 56
+ ('ld d, a', 0), # 57
+ ('ld e, b', 0), # 58
+ ('ld e, c', 0), # 59
+ ('ld e, d', 0), # 5a
+ ('ld e, e', 0), # 5b
+ ('ld e, h', 0), # 5c
+ ('ld e, l', 0), # 5d
+ ('ld e, [hl]', 0), # 5e
+ ('ld e, a', 0), # 5f
+ ('ld h, b', 0), # 60
+ ('ld h, c', 0), # 61
+ ('ld h, d', 0), # 62
+ ('ld h, e', 0), # 63
+ ('ld h, h', 0), # 64
+ ('ld h, l', 0), # 65
+ ('ld h, [hl]', 0), # 66
+ ('ld h, a', 0), # 67
+ ('ld l, b', 0), # 68
+ ('ld l, c', 0), # 69
+ ('ld l, d', 0), # 6a
+ ('ld l, e', 0), # 6b
+ ('ld l, h', 0), # 6c
+ ('ld l, l', 0), # 6d
+ ('ld l, [hl]', 0), # 6e
+ ('ld l, a', 0), # 6f
+ ('ld [hl], b', 0), # 70
+ ('ld [hl], c', 0), # 71
+ ('ld [hl], d', 0), # 72
+ ('ld [hl], e', 0), # 73
+ ('ld [hl], h', 0), # 74
+ ('ld [hl], l', 0), # 75
+ ('halt', 0), # 76
+ ('ld [hl], a', 0), # 77
+ ('ld a, b', 0), # 78
+ ('ld a, c', 0), # 79
+ ('ld a, d', 0), # 7a
+ ('ld a, e', 0), # 7b
+ ('ld a, h', 0), # 7c
+ ('ld a, l', 0), # 7d
+ ('ld a, [hl]', 0), # 7e
+ ('ld a, a', 0), # 7f
+ ('add b', 0), # 80
+ ('add c', 0), # 81
+ ('add d', 0), # 82
+ ('add e', 0), # 83
+ ('add h', 0), # 84
+ ('add l', 0), # 85
+ ('add [hl]', 0), # 86
+ ('add a', 0), # 87
+ ('adc b', 0), # 88
+ ('adc c', 0), # 89
+ ('adc d', 0), # 8a
+ ('adc e', 0), # 8b
+ ('adc h', 0), # 8c
+ ('adc l', 0), # 8d
+ ('adc [hl]', 0), # 8e
+ ('adc a', 0), # 8f
+ ('sub b', 0), # 90
+ ('sub c', 0), # 91
+ ('sub d', 0), # 92
+ ('sub e', 0), # 93
+ ('sub h', 0), # 94
+ ('sub l', 0), # 95
+ ('sub [hl]', 0), # 96
+ ('sub a', 0), # 97
+ ('sbc b', 0), # 98
+ ('sbc c', 0), # 99
+ ('sbc d', 0), # 9a
+ ('sbc e', 0), # 9b
+ ('sbc h', 0), # 9c
+ ('sbc l', 0), # 9d
+ ('sbc [hl]', 0), # 9e
+ ('sbc a', 0), # 9f
+ ('and b', 0), # a0
+ ('and c', 0), # a1
+ ('and d', 0), # a2
+ ('and e', 0), # a3
+ ('and h', 0), # a4
+ ('and l', 0), # a5
+ ('and [hl]', 0), # a6
+ ('and a', 0), # a7
+ ('xor b', 0), # a8
+ ('xor c', 0), # a9
+ ('xor d', 0), # aa
+ ('xor e', 0), # ab
+ ('xor h', 0), # ac
+ ('xor l', 0), # ad
+ ('xor [hl]', 0), # ae
+ ('xor a', 0), # af
+ ('or b', 0), # b0
+ ('or c', 0), # b1
+ ('or d', 0), # b2
+ ('or e', 0), # b3
+ ('or h', 0), # b4
+ ('or l', 0), # b5
+ ('or [hl]', 0), # b6
+ ('or a', 0), # b7
+ ('cp b', 0), # b8
+ ('cp c', 0), # b9
+ ('cp d', 0), # ba
+ ('cp e', 0), # bb
+ ('cp h', 0), # bc
+ ('cp l', 0), # bd
+ ('cp [hl]', 0), # be
+ ('cp a', 0), # bf
+ ('ret nz', 0), # c0
+ ('pop bc', 0), # c1
+ ('jp nz, {}', 2), # c2
+ ('jp {}', 2), # c3
+ ('call nz, {}', 2), # c4
+ ('push bc', 0), # c5
+ ('add ${:02x}', 1), # c6
+ ('rst $0', 0), # c7
+ ('ret z', 0), # c8
+ ('ret', 0), # c9
+ ('jp z, {}', 2), # ca
+ ('bitops', 1), # cb
+ ('call z, {}', 2), # cc
+ ('call {}', 2), # cd
+ ('adc ${:02x}', 1), # ce
+ ('rst $8', 0), # cf
+ ('ret nc', 0), # d0
+ ('pop de', 0), # d1
+ ('jp nc, {}', 2), # d2
+ ('db $d3', 0), # d3
+ ('call nc, {}', 2), # d4
+ ('push de', 0), # d5
+ ('sub ${:02x}', 1), # d6
+ ('rst $10', 0), # d7
+ ('ret c', 0), # d8
+ ('reti', 0), # d9
+ ('jp c, {}', 2), # da
+ ('db $db', 0), # db
+ ('call c, {}', 2), # dc
+ ('db $dd', 2), # dd
+ ('sbc ${:02x}', 1), # de
+ ('bank1call {}', 2), # df
+ ('ldh [{}], a', 1), # e0
+ ('pop hl', 0), # e1
+ ('ld [$ff00+c], a', 0), # e2
+ ('db $e3', 0), # e3
+ ('db $e4', 0), # e4
+ ('push hl', 0), # e5
+ ('and ${:02x}', 1), # e6
+ ('rst $20', 0), # e7
+ ('add sp, ${:02x}', 1), # e8
+ ('jp [hl]', 0), # e9
+ ('ld [{}], a', 2), # ea
+ ('db $eb', 0), # eb
+ ('db $ec', 2), # ec
+ ('db $ed', 2), # ed
+ ('xor ${:02x}', 1), # ee
+ ('farcall {}', 3), # ef
+ ('ldh a, [{}]', 1), # f0
+ ('pop af', 0), # f1
+ ('db $f2', 0), # f2
+ ('di', 0), # f3
+ ('db $f4', 0), # f4
+ ('push af', 0), # f5
+ ('or ${:02x}', 1), # f6
+ ('rst $30', 0), # f7
+ ('ld hl, sp+${:02x}', 1), # f8
+ ('ld sp, [hl]', 0), # f9
+ ('ld a, [{}]', 2), # fa
+ ('ei', 0), # fb
+ ('db $fc', 2), # fc
+ ('db $fd', 2), # fd
+ ('cp ${:02x}', 1), # fe
+ ('debug_ret', 0), # ff
+]
+
+bit_ops_table = [
+ "rlc b", "rlc c", "rlc d", "rlc e", "rlc h", "rlc l", "rlc [hl]", "rlc a", # $00 - $07
+ "rrc b", "rrc c", "rrc d", "rrc e", "rrc h", "rrc l", "rrc [hl]", "rrc a", # $08 - $0f
+ "rl b", "rl c", "rl d", "rl e", "rl h", "rl l", "rl [hl]", "rl a", # $10 - $17
+ "rr b", "rr c", "rr d", "rr e", "rr h", "rr l", "rr [hl]", "rr a", # $18 - $1f
+ "sla b", "sla c", "sla d", "sla e", "sla h", "sla l", "sla [hl]", "sla a", # $20 - $27
+ "sra b", "sra c", "sra d", "sra e", "sra h", "sra l", "sra [hl]", "sra a", # $28 - $2f
+ "swap b", "swap c", "swap d", "swap e", "swap h", "swap l", "swap [hl]", "swap a", # $30 - $37
+ "srl b", "srl c", "srl d", "srl e", "srl h", "srl l", "srl [hl]", "srl a", # $38 - $3f
+ "bit 0, b", "bit 0, c", "bit 0, d", "bit 0, e", "bit 0, h", "bit 0, l", "bit 0, [hl]", "bit 0, a", # $40 - $47
+ "bit 1, b", "bit 1, c", "bit 1, d", "bit 1, e", "bit 1, h", "bit 1, l", "bit 1, [hl]", "bit 1, a", # $48 - $4f
+ "bit 2, b", "bit 2, c", "bit 2, d", "bit 2, e", "bit 2, h", "bit 2, l", "bit 2, [hl]", "bit 2, a", # $50 - $57
+ "bit 3, b", "bit 3, c", "bit 3, d", "bit 3, e", "bit 3, h", "bit 3, l", "bit 3, [hl]", "bit 3, a", # $58 - $5f
+ "bit 4, b", "bit 4, c", "bit 4, d", "bit 4, e", "bit 4, h", "bit 4, l", "bit 4, [hl]", "bit 4, a", # $60 - $67
+ "bit 5, b", "bit 5, c", "bit 5, d", "bit 5, e", "bit 5, h", "bit 5, l", "bit 5, [hl]", "bit 5, a", # $68 - $6f
+ "bit 6, b", "bit 6, c", "bit 6, d", "bit 6, e", "bit 6, h", "bit 6, l", "bit 6, [hl]", "bit 6, a", # $70 - $77
+ "bit 7, b", "bit 7, c", "bit 7, d", "bit 7, e", "bit 7, h", "bit 7, l", "bit 7, [hl]", "bit 7, a", # $78 - $7f
+ "res 0, b", "res 0, c", "res 0, d", "res 0, e", "res 0, h", "res 0, l", "res 0, [hl]", "res 0, a", # $80 - $87
+ "res 1, b", "res 1, c", "res 1, d", "res 1, e", "res 1, h", "res 1, l", "res 1, [hl]", "res 1, a", # $88 - $8f
+ "res 2, b", "res 2, c", "res 2, d", "res 2, e", "res 2, h", "res 2, l", "res 2, [hl]", "res 2, a", # $90 - $97
+ "res 3, b", "res 3, c", "res 3, d", "res 3, e", "res 3, h", "res 3, l", "res 3, [hl]", "res 3, a", # $98 - $9f
+ "res 4, b", "res 4, c", "res 4, d", "res 4, e", "res 4, h", "res 4, l", "res 4, [hl]", "res 4, a", # $a0 - $a7
+ "res 5, b", "res 5, c", "res 5, d", "res 5, e", "res 5, h", "res 5, l", "res 5, [hl]", "res 5, a", # $a8 - $af
+ "res 6, b", "res 6, c", "res 6, d", "res 6, e", "res 6, h", "res 6, l", "res 6, [hl]", "res 6, a", # $b0 - $b7
+ "res 7, b", "res 7, c", "res 7, d", "res 7, e", "res 7, h", "res 7, l", "res 7, [hl]", "res 7, a", # $b8 - $bf
+ "set 0, b", "set 0, c", "set 0, d", "set 0, e", "set 0, h", "set 0, l", "set 0, [hl]", "set 0, a", # $c0 - $c7
+ "set 1, b", "set 1, c", "set 1, d", "set 1, e", "set 1, h", "set 1, l", "set 1, [hl]", "set 1, a", # $c8 - $cf
+ "set 2, b", "set 2, c", "set 2, d", "set 2, e", "set 2, h", "set 2, l", "set 2, [hl]", "set 2, a", # $d0 - $d7
+ "set 3, b", "set 3, c", "set 3, d", "set 3, e", "set 3, h", "set 3, l", "set 3, [hl]", "set 3, a", # $d8 - $df
+ "set 4, b", "set 4, c", "set 4, d", "set 4, e", "set 4, h", "set 4, l", "set 4, [hl]", "set 4, a", # $e0 - $e7
+ "set 5, b", "set 5, c", "set 5, d", "set 5, e", "set 5, h", "set 5, l", "set 5, [hl]", "set 5, a", # $e8 - $ef
+ "set 6, b", "set 6, c", "set 6, d", "set 6, e", "set 6, h", "set 6, l", "set 6, [hl]", "set 6, a", # $f0 - $f7
+ "set 7, b", "set 7, c", "set 7, d", "set 7, e", "set 7, h", "set 7, l", "set 7, [hl]", "set 7, a" # $f8 - $ff
+]
+
+unconditional_returns = [0xc9, 0xd9]
+absolute_jumps = [0xc3, 0xc2, 0xca, 0xd2, 0xda]
+call_commands = [0xcd, 0xc4, 0xcc, 0xd4, 0xdc, 0xdf, 0xef]
+relative_jumps = [0x18, 0x20, 0x28, 0x30, 0x38]
+unconditional_jumps = [0xc3, 0x18]
+
+
+def asm_label(address):
+ """
+ Return a local label name for asm at <address>.
+ """
+ return '.asm_%x' % address
+
+def data_label(address):
+ """
+ Return a local label name for data at <address>.
+ """
+ return '.data_%x' % address
+
+def get_local_address(address):
+ """
+ Return the local address of a rom address.
+ """
+ bank = address / 0x4000
+ address &= 0x3fff
+ if bank:
+ return address + 0x4000
+ return address
+
+def get_global_address(address, bank):
+ """
+ Return the rom address of a local address and bank.
+
+ This accounts for a quirk in mbc3 where 0:4000-7fff resolves to 1:4000-7fff.
+ """
+ if address < 0x8000:
+ if address >= 0x4000 and bank > 0:
+ return address + (bank - 1) * 0x4000
+
+ return address
+
+def created_but_unused_labels_exist(byte_labels):
+ """
+ Check whether a label has been created but not used.
+
+ If so, then that means it has to be called or specified later.
+ """
+ return (False in [label["definition"] for label in byte_labels.values()])
+
+def all_byte_labels_are_defined(byte_labels):
+ """
+ Check whether all labels have already been defined.
+ """
+ return (False not in [label["definition"] for label in byte_labels.values()])
+
+def load_rom(path='baserom.gbc'):
+ return bytearray(open(path, 'rb').read())
+
+def read_symfile(path='baserom.sym'):
+ """
+ Return a list of dicts of label data from an rgbds .sym file.
+ """
+ symbols = []
+ for line in open(path):
+ line = line.strip().split(';')[0]
+ if line:
+ bank_address, label = line.split(' ')[:2]
+ bank, address = bank_address.split(':')
+ symbols += [{
+ 'label': label,
+ 'bank': int(bank, 16),
+ 'address': int(address, 16),
+ }]
+ return symbols
+
+def load_symbols(path):
+ sym = {}
+ reverse_sym = {}
+ wram_sym = {}
+ sram_sym = {}
+ vram_sym = {}
+ hram_sym = {}
+
+ symbols = read_symfile(path)
+ for symbol in symbols:
+ bank = symbol['bank']
+ address = symbol['address']
+ label = symbol['label']
+
+ if 0x0000 <= address < 0x8000:
+ if not sym.has_key(bank):
+ sym[bank] = {}
+
+ sym[bank][address] = label
+ reverse_sym[label] = get_global_address(address, bank)
+
+ elif 0x8000 <= address < 0xa000:
+ if not vram_sym.has_key(bank):
+ vram_sym[bank] = {}
+
+ vram_sym[bank][address] = label
+
+ elif 0xa000 <= address < 0xc000:
+ if not sram_sym.has_key(bank):
+ sram_sym[bank] = {}
+
+ sram_sym[bank][address] = label
+
+ elif 0xc000 <= address < 0xe000:
+ if not wram_sym.has_key(bank):
+ wram_sym[bank] = {}
+
+ wram_sym[bank][address] = label
+
+ elif 0xff80 <= address < 0xfffe:
+ if not hram_sym.has_key(bank):
+ hram_sym[bank] = {}
+
+ hram_sym[bank][address] = label
+
+ else:
+ raise ValueError("Unsupported symfile label type.")
+
+ return sym, reverse_sym, wram_sym, sram_sym, vram_sym, hram_sym
+
+def get_symbol(sym, address, bank=0):
+ if sym:
+ if 0x0000 <= address < 0x4000:
+ return sym.get(0, {}).get(address)
+ else:
+ return sym.get(bank, {}).get(address)
+
+ return None
+
+def get_banked_ram_sym(sym, address):
+ #if sym:
+ # if 0xc000 <= address < 0xd000:
+ # return sym.get(0, {}).get(address)
+ # else:
+ # return sym.get(bank, {}).get(address)
+ if sym:
+ for bank in sym.keys():
+ temp_sym = sym.get(bank, {}).get(address)
+ if temp_sym:
+ return temp_sym
+
+ return None
+
+def create_address_comment(offset):
+ comment_bank = offset / 0x4000
+ if comment_bank != 0:
+ comment_bank_addr = (offset % 0x4000) + 0x4000
+ else:
+ comment_bank_addr = offset
+
+ return " ; %x (%x:%x)" % (offset, comment_bank, comment_bank_addr)
+
+def offset_is_used(labels, offset):
+ if offset in labels.keys():
+ return 0 < labels[offset]["usage"]
+
+class Disassembler(object):
+ """
+ GBC disassembler
+ """
+
+ def __init__(self, config):
+ """
+ Setup the class instance.
+ """
+ self.config = config
+ self.spacing = '\t'
+ self.rom = None
+ self.sym = None
+ self.rsym = None
+ self.gbhw = None
+ self.vram = None
+ self.sram = None
+ self.hram = None
+ self.wram = None
+
+ def initialize(self, rom, symfile):
+ """
+ Setup the disassembler.
+ """
+ path = os.path.join(self.config.path, rom)
+ self.rom = load_rom(path)
+
+ # load ram symbols
+ path = os.path.join(self.config.path, symfile)
+ if os.path.exists(path):
+ self.sym, self.rsym, self.wram, self.sram, self.vram, self.hram = load_symbols(path)
+
+ # load hardware constants
+ path = os.path.join(self.config.path, 'src/constants/hardware_constants.asm')
+ if os.path.exists(path):
+ self.gbhw = read_constants(path)
+
+ def find_label(self, address, bank=0):
+ if type(address) is str:
+ address = int(address.replace('$', '0x'), 16)
+ elif address is None:
+ return address
+
+ if 0x0000 <= address < 0x8000:
+ label = self.get_symbol(address, bank)
+ elif address < 0xa000 and self.vram:
+ label = self.get_vram(address)
+ elif address < 0xc000:
+ label = self.get_sram(address)
+ elif address < 0xe000:
+ label = self.get_wram(address)
+ elif ((0xff00 <= address < 0xff80) or (address == 0xffff)) and self.gbhw:
+ label = self.gbhw.get(address)
+ elif (0xff80 <= address < 0xffff) and self.hram:
+ label = self.get_hram(address)
+ else:
+ label = None
+
+ return label
+
+ def get_symbol(self, address, bank):
+ symbol = get_symbol(self.sym, address, bank)
+ if symbol == 'NULL' and address == 0 and bank == 0:
+ return None
+ return symbol
+
+ def get_wram(self, address):
+ symbol = get_banked_ram_sym(self.wram, address)
+ if symbol == 'NULL' and address == 0:
+ return None
+ return symbol
+
+ def get_sram(self, address):
+ symbol = get_banked_ram_sym(self.sram, address)
+ if symbol == 'NULL' and address == 0:
+ return None
+ return symbol
+
+ def get_vram(self, address):
+ symbol = get_banked_ram_sym(self.vram, address)
+ if symbol == 'NULL' and address == 0:
+ return None
+ return symbol
+
+ def get_hram(self, address):
+ symbol = get_banked_ram_sym(self.hram, address)
+ if symbol == 'NULL' and address == 0:
+ return None
+ return symbol
+
+ def find_address_from_label(self, label):
+ if self.rsym:
+ return self.rsym.get(label)
+
+ return None
+
+ def output_bank_opcodes(self, start_offset, stop_offset, hard_stop=False, parse_data=False, include_last_address=True):
+ """
+ Output bank opcodes.
+
+ fs = current_address
+ b = bank_byte
+ in = input_data -- rom
+ bank_size = byte_count
+ i = offset
+ ad = end_address
+ a, oa = current_byte_number
+
+ stop_at can be used to supply a list of addresses to not disassemble
+ over. This is useful if you know in advance that there are a lot of
+ fall-throughs.
+ """
+
+ debug = False
+
+ bank_id = start_offset / 0x4000
+
+ stop_offset_undefined = False
+
+ # check if stop_offset isn't defined
+ if stop_offset is None:
+ stop_offset_undefined = True
+ # stop at the end of the current bank if stop_offset is not defined
+ stop_offset = (bank_id + 1) * 0x4000 - 1
+
+ if debug:
+ print "bank id is: " + str(bank_id)
+
+ rom = self.rom
+
+ offset = start_offset
+ current_byte_number = 0 #start from the beginning
+
+ byte_labels = {}
+ data_tables = {}
+
+ output = "Func_%x:%s\n" % (start_offset,create_address_comment(start_offset))
+ is_data = False
+
+ while True:
+ #first check if this byte already has a label
+ #if it does, use the label
+ #if not, generate a new label
+
+ local_offset = get_local_address(offset)
+
+ data_label_used = offset_is_used(data_tables, local_offset)
+ byte_label_used = offset_is_used(byte_labels, local_offset)
+ data_label_created = local_offset in data_tables.keys()
+ byte_label_created = local_offset in byte_labels.keys()
+
+ if byte_label_created:
+ # if a byte label exists, remove any significance if there is a data label that exists
+ if data_label_created:
+ data_line_label = data_tables[local_offset]["name"]
+ data_tables[local_offset]["usage"] = 0
+ else:
+ data_line_label = data_label(offset)
+ data_tables[local_offset] = {}
+ data_tables[local_offset]["name"] = data_line_label
+ data_tables[local_offset]["usage"] = 0
+
+ line_label = byte_labels[local_offset]["name"]
+ byte_labels[local_offset]["usage"] += 1
+ output += "\n"
+ elif data_label_created and parse_data:
+ # go add usage to a data label if it exists
+ data_line_label = data_tables[local_offset]["name"]
+ data_tables[local_offset]["usage"] += 1
+
+ line_label = asm_label(offset)
+ byte_labels[local_offset] = {}
+ byte_labels[local_offset]["name"] = line_label
+ byte_labels[local_offset]["usage"] = 0
+ output += "\n"
+ else:
+ # create both a data and byte label if neither exist
+ data_line_label = data_label(offset)
+ data_tables[local_offset] = {}
+ data_tables[local_offset]["name"] = data_line_label
+ data_tables[local_offset]["usage"] = 0
+
+ line_label = asm_label(offset)
+ byte_labels[local_offset] = {}
+ byte_labels[local_offset]["name"] = line_label
+ byte_labels[local_offset]["usage"] = 0
+
+ # any labels created not above are now used, so mark them as "defined"
+ byte_labels[local_offset]["definition"] = True
+ data_tables[local_offset]["definition"] = True
+
+ # for now, output the byte and data labels (unused labels will be removed later)
+ output += line_label + "\n" + data_line_label + "\n"
+
+ # get the current byte
+ opcode_byte = rom[offset]
+
+ # process the current byte if this is code or parse data has not been set
+ if not is_data or not parse_data:
+ # fetch the opcode string from a predefined table
+ opcode_str = z80_table[opcode_byte][0]
+ # fetch the number of arguments
+ opcode_nargs = z80_table[opcode_byte][1]
+ # get opcode arguments in advance (may not be used)
+ opcode_arg_1 = rom[offset+1]
+ opcode_arg_2 = rom[offset+2]
+ opcode_arg_3 = rom[offset+3]
+
+ if opcode_nargs == 0:
+ # set output string simply as the opcode
+ opcode_output_str = opcode_str
+
+ elif opcode_nargs == 1:
+ # opcodes with 1 argument
+ if opcode_byte != 0xcb: # bit opcodes are handled separately
+
+ if opcode_byte in relative_jumps:
+ # if the current opcode is a relative jump, generate a label for the address we're jumping to
+ # get the address of the location to jump to
+ target_address = offset + 2 + c_int8(opcode_arg_1).value
+ # get the local address to use as a key for byte_labels and data_tables
+ local_target_address = get_local_address(target_address)
+
+ if local_target_address in byte_labels.keys():
+ # if the label has already been created, increase the usage and set output to the already created label
+ byte_labels[local_target_address]["usage"] += 1
+ opcode_output_str = byte_labels[local_target_address]["name"]
+ elif target_address < start_offset:
+ # if we're jumping to an address that is located before the start offset, assume it is a function
+ opcode_output_str = "Func_%x" % target_address
+ else:
+ # create a new label
+ opcode_output_str = asm_label(target_address)
+ byte_labels[local_target_address] = {}
+ byte_labels[local_target_address]["name"] = opcode_output_str
+ # we know the label is used once, so set the usage to 1
+ byte_labels[local_target_address]["usage"] = 1
+ # since the label has not been output yet, mark it as "not defined"
+ byte_labels[local_target_address]["definition"] = False
+
+ # check if the target address conflicts with any data labels
+ if local_target_address in data_tables.keys():
+ # if so, remove any instances of it being used and set it as defined
+ data_tables[local_target_address]["usage"] = 0
+ data_tables[local_target_address]["definition"] = True
+
+ # format the resulting argument into the output string
+ opcode_output_str = opcode_str.format(opcode_output_str)
+
+ # debug function
+ if created_but_unused_labels_exist(byte_labels) and debug:
+ output += create_address_comment(offset)
+
+ elif opcode_byte == 0xe0 or opcode_byte == 0xf0:
+ # handle gameboy hram read/write opcodes
+ # create the address
+ high_ram_address = 0xff00 + opcode_arg_1
+ # search for an hram constant if possible
+ high_ram_label = self.find_label(high_ram_address, bank_id)
+ # if we couldn't find one, default to the address
+ if high_ram_label is None:
+ high_ram_label = "$%x" % high_ram_address
+
+ # format the resulting argument into the output string
+ opcode_output_str = opcode_str.format(high_ram_label)
+
+ else:
+ # if this isn't a relative jump or hram read/write, just format the byte into the opcode string
+ opcode_output_str = opcode_str.format(opcode_arg_1)
+
+ else:
+ # handle bit opcodes by fetching the opcode from a separate table
+ opcode_output_str = bit_ops_table[opcode_arg_1]
+
+ elif opcode_nargs == 2:
+ # opcodes with a pointer as an argument
+ # format the two arguments into a little endian 16-bit pointer
+ local_target_offset = opcode_arg_2 << 8 | opcode_arg_1
+ # get the global offset of the pointer
+ target_offset = get_global_address(local_target_offset, bank_id)
+ # attempt to look for a matching label
+ if opcode_byte == 0xdf:
+ # bank1call
+ target_label = self.find_label(local_target_offset, 1)
+ else:
+ # regular call or jump instructions
+ target_label = self.find_label(local_target_offset, bank_id)
+
+ if opcode_byte in call_commands + absolute_jumps:
+ if target_label is None:
+ # if this is a call or jump opcode and the target label is not defined, create an undocumented label descriptor
+ target_label = "Func_%x" % target_offset
+
+ else:
+ # anything that isn't a call or jump is a load-based command
+ if target_label is None:
+ # handle the case of a label for the current address not existing
+
+ # first, check if this is a byte label
+ if offset_is_used(byte_labels, local_target_offset):
+ # fetch the already created byte label
+ target_label = byte_labels[local_target_offset]["name"]
+ # prevent this address from being treated as a data label
+ if local_target_offset in data_tables.keys():
+ data_tables[local_target_offset]["usage"] = 0
+ else:
+ data_tables[local_target_offset] = {}
+ data_tables[local_target_offset]["name"] = target_label
+ data_tables[local_target_offset]["usage"] = 0
+ data_tables[local_target_offset]["definition"] = True
+
+ elif local_target_offset >= 0x8000 or not parse_data:
+ # do not create a label if this is a wram label or parse_data is not set
+ target_label = "$%x" % local_target_offset
+
+ elif local_target_offset in data_tables.keys():
+ # if the target offset has been created as a data label, increase usage and use the already defined name
+ data_tables[local_target_offset]["usage"] += 1
+ target_label = data_tables[local_target_offset]["name"]
+ else:
+ # for now, treat this as a data label, but do not set it as used (will be replaced later if unused)
+ target_label = data_label(target_offset)
+ data_tables[local_target_offset] = {}
+ data_tables[local_target_offset]["name"] = target_label
+ data_tables[local_target_offset]["usage"] = 0
+ data_tables[local_target_offset]["definition"] = False
+
+ # format the label that was created into the opcode string
+ opcode_output_str = opcode_str.format(target_label)
+
+ elif opcode_nargs == 3:
+ # macros with bank and pointer as an argument
+ # format the three arguments into a three-byte pointer
+ local_target_offset = opcode_arg_3 << 8 | opcode_arg_2
+ # get the global offset of the pointer
+ target_offset = get_global_address(local_target_offset, opcode_arg_1)
+ # attempt to look for a matching label
+ target_label = self.find_label(local_target_offset, opcode_arg_1)
+
+ if opcode_byte in call_commands + absolute_jumps:
+ if target_label is None:
+ # if this is a call or jump opcode and the target label is not defined, create an undocumented label descriptor
+ target_label = "Func_%x" % target_offset
+
+ # format the label that was created into the opcode string
+ opcode_output_str = opcode_str.format(target_label)
+
+ else:
+ # error checking
+ raise ValueError("Invalid amount of args.")
+
+ # append the formatted opcode output string to the output
+ output += self.spacing + opcode_output_str + "\n" #+ " ; " + hex(offset)
+ # increase the current byte number and offset by the amount of arguments plus 1 (opcode itself)
+ current_byte_number += opcode_nargs + 1
+ offset += opcode_nargs + 1
+
+ else:
+ # output a single lined db, using the current byte
+ output += self.spacing + "db ${:02x}\n".format(opcode_byte) #+ " ; " + hex(offset)
+ # manually increment offset and current byte number
+ offset += 1
+ current_byte_number += 1
+ # stop treating the current code as data if we're parsing over a byte label
+ if get_local_address(offset) in byte_labels.keys():
+ is_data = False
+
+ # update the local offset
+ local_offset = get_local_address(offset)
+
+ # stop processing regardless of function end if we've passed the stop offset and the hard stop (dry run) flag is set
+ if hard_stop and offset >= stop_offset:
+ break
+ # check if this is the end of the function, or we're processing data
+ elif (opcode_byte in unconditional_jumps + unconditional_returns) or is_data:
+ # define data if it is located at the current offset
+ if local_offset not in byte_labels.keys() and local_offset in data_tables.keys() and created_but_unused_labels_exist(data_tables) and parse_data:
+ is_data = True
+ #stop reading at a jump, relative jump or return
+ elif all_byte_labels_are_defined(byte_labels) and (offset >= stop_offset or stop_offset_undefined):
+ break
+ # otherwise, add some spacing
+ output += "\n"
+
+ # before returning output, we need to clean up some things
+
+ # first, clean up on unused byte labels
+ for label_line in byte_labels.values():
+ if label_line["usage"] == 0:
+ output = output.replace((label_line["name"] + "\n"), "")
+
+ # clean up on unused data labels
+ # this is slightly trickier to do as arguments for two byte variables use data labels
+
+ # create a list of the output lines including the newlines
+ output_lines = [e+"\n" for e in output.split("\n") if e != ""]
+
+ # go through each label
+ for label_addr in data_tables.keys():
+ # get the label dict
+ label_line = data_tables[label_addr]
+ # check if this label is unused
+ if label_line["usage"] == 0:
+ # get label name
+ label_name = label_line["name"]
+ # loop over all output lines
+ for i, line in enumerate(output_lines):
+ if line.startswith(label_name):
+ # remove line if it starts with the current label
+ output_lines.pop(i)
+ elif label_name in line:
+ # if the label is used in a load-based opcode, replace it with the raw hex reference
+ output_lines[i] = output_lines[i].replace(label_name, "$%x" % get_local_address(label_addr))
+
+ # convert the modified list of lines into a string
+ output = "".join(output_lines)
+
+ # tone down excessive spacing
+ output = output.replace("\n\n\n","\n\n")
+
+ # add the offset of the final location
+ if include_last_address:
+ output += "; " + hex(offset)
+
+ return [output, offset, stop_offset, byte_labels, data_tables]
+
+def get_raw_addr(addr):
+ if addr:
+ if ":" in addr:
+ addr = addr.split(":")
+ addr = int(addr[0], 16)*0x4000+(int(addr[1], 16)%0x4000)
+ else:
+ label_addr = disasm.find_address_from_label(addr)
+ if label_addr:
+ addr = label_addr
+ else:
+ addr = int(addr, 16)
+
+ return addr
+
+if __name__ == "__main__":
+ # argument parser
+ ap = argparse.ArgumentParser()
+ ap.add_argument("-r", dest="rom", default="baserom.gbc")
+ ap.add_argument("-o", dest="filename", default="tcgdisasm_output.asm")
+ ap.add_argument("-s", dest="symfile", default="tcg.sym")
+ ap.add_argument("-q", "--quiet", dest="quiet", action="store_true")
+ ap.add_argument("-a", "--append", dest="append", action="store_true")
+ ap.add_argument("-nw", "--no-write", dest="no_write", action="store_true")
+ ap.add_argument("-d", "--dry-run", dest="dry_run", action="store_true")
+ ap.add_argument("-pd", "--parse_data", dest="parse_data", action="store_true")
+ ap.add_argument('offset')
+ ap.add_argument('end', nargs='?')
+
+ args = ap.parse_args()
+ conf = configuration.Config()
+
+ # initialize disassembler
+ disasm = Disassembler(conf)
+ disasm.initialize(args.rom, args.symfile)
+
+ # get global address of the start and stop offsets
+ start_addr = get_raw_addr(args.offset)
+ stop_addr = get_raw_addr(args.end)
+
+ # run the disassembler and return the output
+ output = disasm.output_bank_opcodes(start_addr,stop_addr,hard_stop=args.dry_run,parse_data=args.parse_data)[0]
+
+ # suppress output if quiet flag is set
+ if not args.quiet:
+ print output
+
+ # only write to the output file if the no write flag is unset
+ if not args.no_write:
+ if args.append:
+ with open(args.filename, "a") as f:
+ f.write("\n\n" + output)
+ else:
+ with open(args.filename, "w") as f:
+ f.write(output)
diff --git a/tools/wram.py b/tools/wram.py
new file mode 100644
index 0000000..8270566
--- /dev/null
+++ b/tools/wram.py
@@ -0,0 +1,322 @@
+# coding: utf-8
+"""
+RGBDS BSS section and constant parsing.
+"""
+
+import os
+import os.path
+
+
+def separate_comment(line):
+ if ';' in line:
+ i = line.find(';')
+ return line[:i], line[i:]
+ return line, None
+
+
+def rgbasm_to_py(text):
+ return text.replace('$', '0x').replace('%', '0b')
+
+
+def make_wram_labels(wram_sections):
+ wram_labels = {}
+ for section in wram_sections:
+ for label in section['labels']:
+ if label['address'] not in wram_labels.keys():
+ wram_labels[label['address']] = []
+ wram_labels[label['address']] += [label['label']]
+ return wram_labels
+
+def bracket_value(string, i=0):
+ return string.split('[')[1 + i*2].split(']')[0]
+
+class BSSReader:
+ """
+ Read rgbasm BSS/WRAM sections and associate labels with addresses.
+ Also reads constants/variables, even in macros.
+ """
+ sections = []
+ section = None
+ address = None
+ macros = {}
+ constants = {}
+
+ section_types = {
+ 'VRAM': 0x8000,
+ 'SRAM': 0xa000,
+ 'WRAM0': 0xc000,
+ 'WRAMX': 0xd000,
+ 'HRAM': 0xff80,
+ }
+
+ def __init__(self, *args, **kwargs):
+ self.__dict__.update(kwargs)
+
+ def read_bss_line(self, l):
+ parts = l.strip().split(' ')
+ token = parts[0].strip()
+ params = ' '.join(parts[1:]).split(',')
+
+ if token in ['ds', 'db', 'dw']:
+ if any(params):
+ length = eval(rgbasm_to_py(params[0]), self.constants.copy())
+ else:
+ length = {'ds': 1, 'db': 1, 'dw': 2}[token]
+ self.address += length
+ # assume adjacent labels to use the same space
+ for label in self.section['labels'][::-1]:
+ if label['length'] == 0:
+ label['length'] = length
+ else:
+ break
+
+ elif token in self.macros.keys():
+ macro_text = '\n'.join(self.macros[token]) + '\n'
+ for i, p in enumerate(params):
+ macro_text = macro_text.replace('\\'+str(i+1),p)
+ macro_text = macro_text.split('\n')
+ macro_reader = BSSReader(
+ sections = list(self.sections),
+ section = dict(self.section),
+ address = self.address,
+ constants = self.constants,
+ )
+ macro_sections = macro_reader.read_bss_sections(macro_text)
+ self.section = macro_sections[-1]
+ if self.section['labels']:
+ self.address = self.section['labels'][-1]['address'] + self.section['labels'][-1]['length']
+
+
+ def read_bss_sections(self, bss):
+
+ if self.section is None:
+ self.section = {
+ "labels": [],
+ }
+
+ if type(bss) is str:
+ bss = bss.split('\n')
+
+ macro = False
+ macro_name = None
+ for line in bss:
+ line = line.lstrip()
+ line, comment = separate_comment(line)
+ line = line.strip()
+ split_line = line.split()
+ split_line_upper = map(str.upper, split_line)
+
+ if not line:
+ pass
+
+ elif line[-4:].upper() == 'ENDM':
+ macro = False
+ macro_name = None
+
+ elif macro:
+ self.macros[macro_name] += [line]
+
+ elif line[-5:].upper() == 'MACRO':
+ macro_name = line.split(':')[0]
+ macro = True
+ self.macros[macro_name] = []
+
+ elif 'INCLUDE' == line[:7].upper():
+ filename = line.split('"')[1]
+ if os.path.exists("src/"):
+ self.read_bss_sections(open("src/" + filename).readlines())
+ else:
+ self.read_bss_sections(open(filename).readlines())
+
+ elif 'SECTION' == line[:7].upper():
+ if self.section: # previous
+ self.sections += [self.section]
+
+ section_def = line.split(',')
+ name = section_def[0].split('"')[1]
+ type_ = section_def[1].strip()
+ if len(section_def) > 2:
+ bank = bracket_value(section_def[2])
+ else:
+ bank = None
+
+ if '[' in type_:
+ self.address = int(rgbasm_to_py(bracket_value(type_)), 16)
+ else:
+ if self.address == None or bank != self.section['bank'] or self.section['type'] != type_:
+ self.address = self.section_types.get(type_, self.address)
+ # else: keep going from this address
+
+ self.section = {
+ 'name': name,
+ 'type': type_,
+ 'bank': bank,
+ 'start': self.address,
+ 'labels': [],
+ }
+
+ elif ':' in line:
+ # rgbasm allows labels without :, but prefer convention
+ label = line[:line.find(':')]
+ if '\\' in label:
+ raise Exception, line + ' ' + label
+ if ';' not in label:
+ section_label = {
+ 'label': label,
+ 'address': self.address,
+ 'length': 0,
+ }
+ self.section['labels'] += [section_label]
+ self.read_bss_line(line.split(':')[-1])
+
+ elif any(x in split_line_upper for x in ['EQU', '=', 'SET']): # TODO: refactor
+ for x in ['EQU', '=', 'SET']:
+ if x in split_line_upper:
+ index = split_line_upper.index(x)
+ real = split_line[index]
+ name, value = map(' '.join, [split_line[:index], split_line[index+1:]])
+ value = rgbasm_to_py(value)
+ self.constants[name] = eval(value, self.constants.copy())
+
+ else:
+ self.read_bss_line(line)
+
+ self.sections += [self.section]
+ return self.sections
+
+def read_bss_sections(bss):
+ reader = BSSReader()
+ return reader.read_bss_sections(bss)
+
+
+def constants_to_dict(constants):
+ """Deprecated. Use BSSReader."""
+ return dict((eval(rgbasm_to_py(constant[constant.find('EQU')+3:constant.find(';')])), constant[:constant.find('EQU')].strip()) for constant in constants)
+
+def scrape_constants(text):
+ if type(text) is not list:
+ text = text.split('\n')
+ bss = BSSReader()
+ bss.read_bss_sections(text)
+ constants = bss.constants
+ return {v: k for k, v in constants.items()}
+
+def read_constants(filepath):
+ """
+ Load lines from a file and grab any constants using BSSReader.
+ """
+ lines = []
+ if os.path.exists(filepath):
+ with open(filepath, "r") as file_handler:
+ lines = file_handler.readlines()
+ return scrape_constants(lines)
+
+class WRAMProcessor(object):
+ """
+ RGBDS BSS section and constant parsing.
+ """
+
+ def __init__(self, config):
+ """
+ Setup for WRAM parsing.
+ """
+ self.config = config
+
+ self.paths = {}
+
+ if os.path.exists("src/"):
+ path = "src/"
+ else:
+ path = ""
+
+ if hasattr(self.config, "wram"):
+ self.paths["wram"] = self.config.wram
+ else:
+ self.paths["wram"] = os.path.join(self.config.path, path + "wram.asm")
+
+ if hasattr(self.config, "hram"):
+ self.paths["hram"] = self.config.hram
+ else:
+ self.paths["hram"] = os.path.join(self.config.path, path + "hram.asm")
+
+ if hasattr(self.config, "gbhw"):
+ self.paths["gbhw"] = self.config.gbhw
+ else:
+ self.paths["gbhw"] = os.path.join(self.config.path, path + "gbhw.asm")
+
+ def initialize(self):
+ """
+ Read constants.
+ """
+ self.setup_wram_sections()
+ self.setup_wram_labels()
+ self.setup_hram_constants()
+ self.setup_gbhw_constants()
+
+ self.reformat_wram_labels()
+
+ def read_wram_sections(self):
+ """
+ Opens the wram file and calls read_bss_sections.
+ """
+ wram_content = None
+ wram_file_path = self.paths["wram"]
+
+ with open(wram_file_path, "r") as wram:
+ wram_content = wram.readlines()
+
+ wram_sections = read_bss_sections(wram_content)
+ return wram_sections
+
+ def setup_wram_sections(self):
+ """
+ Call read_wram_sections and set a variable.
+ """
+ self.wram_sections = self.read_wram_sections()
+ return self.wram_sections
+
+ def setup_wram_labels(self):
+ """
+ Make wram labels based on self.wram_sections as input.
+ """
+ self.wram_labels = make_wram_labels(self.wram_sections)
+ return self.wram_labels
+
+ def read_hram_constants(self):
+ """
+ Read constants from hram.asm using read_constants.
+ """
+ hram_constants = read_constants(self.paths["hram"])
+ return hram_constants
+
+ def setup_hram_constants(self):
+ """
+ Call read_hram_constants and set a variable.
+ """
+ self.hram_constants = self.read_hram_constants()
+ return self.hram_constants
+
+ def read_gbhw_constants(self):
+ """
+ Read constants from gbhw.asm using read_constants.
+ """
+ gbhw_constants = read_constants(self.paths["gbhw"])
+ return gbhw_constants
+
+ def setup_gbhw_constants(self):
+ """
+ Call read_gbhw_constants and set a variable.
+ """
+ self.gbhw_constants = self.read_gbhw_constants()
+ return self.gbhw_constants
+
+ def reformat_wram_labels(self):
+ """
+ Flips the wram_labels dictionary the other way around to access
+ addresses by label.
+ """
+ self.wram = {}
+
+ for (address, labels) in self.wram_labels.iteritems():
+ for label in labels:
+ self.wram[label] = address