From 268e2cae0b98779cfb0c590ab9612151c752e868 Mon Sep 17 00:00:00 2001 From: Rangi Date: Tue, 22 Sep 2020 12:04:13 -0400 Subject: Move most files out of the root directory - ram/ froups the ram source files - slack/ is for unused garbage taking up the ROM's free space - gfx.py moved to utils/ --- Makefile | 13 +- bin.asm | 9 - bin/unknown_aebc.bin | Bin 71 -> 0 bytes bin/unknown_bb43.bin | Bin 1213 -> 0 bytes charmap.asm | 296 ---- constants.asm | 16 +- constants/charmap.asm | 296 ++++ constants/text_constants.asm | 2 +- gfx.asm | 646 -------- gfx.py | 171 -- gfx/gfx.asm | 632 ++++++++ gfx/pokemon/annon_pic_ptrs.asm | 55 - gfx/pokemon/annon_pic_ptrs.inc | 55 + gfx/pokemon/annon_pics.asm | 53 - gfx/pokemon/annon_pics.inc | 53 + gfx/pokemon/egg.asm | 2 - gfx/pokemon/egg.inc | 2 + gfx/pokemon/pkmn_pic_banks.asm | 14 - gfx/pokemon/pkmn_pic_banks.inc | 14 + gfx/pokemon/pkmn_pics.asm | 543 ------- gfx/pokemon/pkmn_pics.inc | 543 +++++++ gfx/sgb/corrupted_9e1c.png | Bin 1661 -> 0 bytes gfx/sgb/corrupted_a66c.png | Bin 1230 -> 0 bytes gfx/sgb/corrupted_b1e3.png | Bin 2512 -> 0 bytes gfx/sgb/corrupted_ba93.png | Bin 2188 -> 0 bytes gfx/sgb/sgb_border_gold_corrupted.png | Bin 1410 -> 0 bytes gfx/sgb/sgb_border_silver_corrupted.png | Bin 12388 -> 0 bytes hram.asm | 243 --- layout.link | 6 +- macros.asm | 14 - ram/hram.asm | 243 +++ ram/sram.asm | 14 + ram/vram.asm | 62 + ram/wram.asm | 1282 +++++++++++++++ slack/corrupted_9e1c.png | Bin 0 -> 1661 bytes slack/corrupted_a66c.png | Bin 0 -> 1230 bytes slack/corrupted_b1e3.png | Bin 0 -> 2512 bytes slack/corrupted_ba93.png | Bin 0 -> 2188 bytes slack/sgb_border_gold_corrupted.png | Bin 0 -> 1410 bytes slack/sgb_border_silver_corrupted.png | Bin 0 -> 12388 bytes slack/slack.asm | 23 + slack/unknown_aebc.bin | Bin 0 -> 71 bytes slack/unknown_bb43.bin | Bin 0 -> 1213 bytes sram.asm | 14 - utils/__init__.py | 5 - utils/coverage.py | 2 +- utils/gfx.py | 1114 ++----------- utils/lz.py | 580 ------- utils/png.py | 2650 ------------------------------- utils/pokemontools/__init__.py | 5 + utils/pokemontools/gfx.py | 951 +++++++++++ utils/pokemontools/lz.py | 580 +++++++ utils/pokemontools/png.py | 2650 +++++++++++++++++++++++++++++++ vram.asm | 62 - wram.asm | 1282 --------------- 55 files changed, 7598 insertions(+), 7599 deletions(-) delete mode 100755 bin.asm delete mode 100755 bin/unknown_aebc.bin delete mode 100755 bin/unknown_bb43.bin delete mode 100755 charmap.asm create mode 100755 constants/charmap.asm delete mode 100644 gfx.asm delete mode 100644 gfx.py create mode 100644 gfx/gfx.asm delete mode 100644 gfx/pokemon/annon_pic_ptrs.asm create mode 100644 gfx/pokemon/annon_pic_ptrs.inc delete mode 100644 gfx/pokemon/annon_pics.asm create mode 100644 gfx/pokemon/annon_pics.inc delete mode 100644 gfx/pokemon/egg.asm create mode 100644 gfx/pokemon/egg.inc delete mode 100644 gfx/pokemon/pkmn_pic_banks.asm create mode 100644 gfx/pokemon/pkmn_pic_banks.inc delete mode 100644 gfx/pokemon/pkmn_pics.asm create mode 100644 gfx/pokemon/pkmn_pics.inc delete mode 100755 gfx/sgb/corrupted_9e1c.png delete mode 100755 gfx/sgb/corrupted_a66c.png delete mode 100755 gfx/sgb/corrupted_b1e3.png delete mode 100755 gfx/sgb/corrupted_ba93.png delete mode 100755 gfx/sgb/sgb_border_gold_corrupted.png delete mode 100755 gfx/sgb/sgb_border_silver_corrupted.png delete mode 100644 hram.asm delete mode 100644 macros.asm create mode 100644 ram/hram.asm create mode 100644 ram/sram.asm create mode 100644 ram/vram.asm create mode 100644 ram/wram.asm create mode 100644 slack/corrupted_9e1c.png create mode 100644 slack/corrupted_a66c.png create mode 100644 slack/corrupted_b1e3.png create mode 100644 slack/corrupted_ba93.png create mode 100644 slack/sgb_border_gold_corrupted.png create mode 100644 slack/sgb_border_silver_corrupted.png create mode 100755 slack/slack.asm create mode 100644 slack/unknown_aebc.bin create mode 100644 slack/unknown_bb43.bin delete mode 100644 sram.asm delete mode 100644 utils/__init__.py delete mode 100644 utils/lz.py delete mode 100644 utils/png.py create mode 100644 utils/pokemontools/__init__.py create mode 100644 utils/pokemontools/gfx.py create mode 100644 utils/pokemontools/lz.py create mode 100644 utils/pokemontools/png.py delete mode 100644 vram.asm delete mode 100644 wram.asm diff --git a/Makefile b/Makefile index 4265cfd..1471080 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ ROM := pokegold-spaceworld.gb CORRECTEDROM := $(ROM:%.gb=%-correctheader.gb) BASEROM := baserom.gb -DIRS := home engine data audio maps scripts -FILES := bin.asm gfx.asm vram.asm sram.asm wram.asm hram.asm +DIRS := home engine data gfx audio maps scripts ram slack +FILES := BUILD := build @@ -94,13 +94,14 @@ $(BUILD)/%.d: %.asm | $$(dir $$@) $(SCAN_INCLUDES) ### Misc file-specific graphics rules +$(BUILD)/slack/corrupted_9e1c.2bpp: tools/gfx += --trim-whitespace +$(BUILD)/slack/corrupted_a66c.2bpp: tools/gfx += --trim-whitespace +$(BUILD)/slack/corrupted_b1e3.2bpp: tools/gfx += --trim-whitespace +$(BUILD)/slack/sgb_border_gold_corrupted.2bpp: tools/gfx += --trim-whitespace + $(BUILD)/gfx/sgb/sgb_border_alt.2bpp: tools/gfx += --trim-whitespace $(BUILD)/gfx/sgb/sgb_border_gold.2bpp: tools/gfx += --trim-whitespace -$(BUILD)/gfx/sgb/sgb_border_gold_corrupted.2bpp: tools/gfx += --trim-whitespace $(BUILD)/gfx/sgb/sgb_border_silver.2bpp: tools/gfx += --trim-whitespace -$(BUILD)/gfx/sgb/corrupted_9e1c.2bpp: tools/gfx += --trim-whitespace -$(BUILD)/gfx/sgb/corrupted_a66c.2bpp: tools/gfx += --trim-whitespace -$(BUILD)/gfx/sgb/corrupted_b1e3.2bpp: tools/gfx += --trim-whitespace $(BUILD)/gfx/sgb/sgb_border_silver.2bpp: tools/gfx += --trim-whitespace $(BUILD)/gfx/trainer_card/leaders.2bpp: tools/gfx += --trim-whitespace diff --git a/bin.asm b/bin.asm deleted file mode 100755 index c5ef243..0000000 --- a/bin.asm +++ /dev/null @@ -1,9 +0,0 @@ -SECTION "bin.asm@Unknownaebc", ROMX - -Unknownaebc: -INCBIN "bin/unknown_aebc.bin" - -SECTION "bin.asm@Unknownbb43", ROMX - -Unknownbb43: -INCBIN "bin/unknown_bb43.bin" diff --git a/bin/unknown_aebc.bin b/bin/unknown_aebc.bin deleted file mode 100755 index 531072d..0000000 Binary files a/bin/unknown_aebc.bin and /dev/null differ diff --git a/bin/unknown_bb43.bin b/bin/unknown_bb43.bin deleted file mode 100755 index e21effb..0000000 Binary files a/bin/unknown_bb43.bin and /dev/null differ diff --git a/charmap.asm b/charmap.asm deleted file mode 100755 index be20a37..0000000 --- a/charmap.asm +++ /dev/null @@ -1,296 +0,0 @@ - charmap "", $00 - - charmap "イ゛", $01 - charmap "ヴ", $02 - charmap "エ゛", $03 - charmap "オ゛", $04 - - charmap "ガ", $05 - charmap "ギ", $06 - charmap "グ", $07 - charmap "ゲ", $08 - charmap "ゴ", $09 - charmap "ザ", $0a - charmap "ジ", $0b - charmap "ズ", $0c - charmap "ゼ", $0d - charmap "ゾ", $0e - charmap "ダ", $0f - charmap "ヂ", $10 - charmap "ヅ", $11 - charmap "デ", $12 - charmap "ド", $13 - - charmap "", $14 ; "くん" or "ちゃん" - - charmap "<15>", $15 ; nothing - charmap "<16>", $16 ; nothing - - charmap "ネ゛", $17 - charmap "ノ゛", $18 - - charmap "バ", $19 - charmap "ビ", $1a - charmap "ブ", $1b - charmap "ボ", $1c - - charmap "", $1d ; "に " - charmap "", $1e ; "って" - charmap "", $1f ; "を " - - charmap "ィ゛", $20 - charmap "あ゛", $21 - - charmap "", $22 ; "た!" - charmap "", $23 ; "こうげき" - charmap "", $24 ; "は " - charmap "", $25 ; "の " - - charmap "が", $26 - charmap "ぎ", $27 - charmap "ぐ", $28 - charmap "げ", $29 - charmap "ご", $2a - charmap "ざ", $2b - charmap "じ", $2c - charmap "ず", $2d - charmap "ぜ", $2e - charmap "ぞ", $2f - charmap "だ", $30 - charmap "ぢ", $31 - charmap "づ", $32 - charmap "で", $33 - charmap "ど", $34 - - charmap "", $35 ; "ばん どうろ" - charmap "", $36 ; "わたし" - charmap "", $37 ; "ここは" - charmap "", $38 ; wRedsName - charmap "", $39 ; wGreensName - - charmap "ば", $3a - charmap "び", $3b - charmap "ぶ", $3c - charmap "べ", $3d - charmap "ぼ", $3e - - charmap "", $3f - - charmap "パ", $40 - charmap "ピ", $41 - charmap "プ", $42 - charmap "ポ", $43 - charmap "ぱ", $44 - charmap "ぴ", $45 - charmap "ぷ", $46 - charmap "ぺ", $47 - charmap "ぽ", $48 - - charmap "", $49 ; wMomsName - charmap "", $4a ; "が " - charmap "<_CONT>", $4b ; implements "" - charmap "", $4c - - charmap "も゜", $4d - - charmap "", $4e - charmap "", $4f - charmap "@", $50 ; string terminator - charmap "", $51 - charmap "", $52 ; wPlayerName - charmap "", $53 ; wRivalName - charmap "#", $54 ; "POKé" - charmap "", $55 - charmap "<⋯⋯>", $56 ; "⋯⋯" - charmap "", $57 - charmap "", $58 - charmap "", $59 - charmap "", $5a - charmap "", $5b ; "PC" - charmap "", $5c ; "TM" - charmap "", $5d ; "TRAINER" - charmap "", $5e ; "ROCKET" - charmap "", $5f - - charmap "■", $60 - charmap "▲", $61 - charmap "☎", $62 - - charmap "A", $60 - charmap "B", $61 - charmap "C", $62 - charmap "D", $63 - charmap "E", $64 - charmap "F", $65 - charmap "G", $66 - charmap "H", $67 - charmap "I", $68 - charmap "V", $69 - charmap "S", $6a - charmap "L", $6b - charmap "M", $6c - - charmap ":", $6d - - charmap "ぃ", $6e - charmap "ぅ", $6f - - charmap "「", $70 - charmap "」", $71 - charmap "『", $72 - charmap "』", $73 - charmap "・", $74 - charmap "⋯", $75 - - charmap "ぁ", $76 - charmap "ぇ", $77 - charmap "ぉ", $78 - - charmap "┌", $79 - charmap "─", $7a - charmap "┐", $7b - charmap "│", $7c - charmap "└", $7d - charmap "┘", $7e - - charmap " ", $7f - - charmap "ア", $80 - charmap "イ", $81 - charmap "ウ", $82 - charmap "エ", $83 - charmap "オ", $84 - charmap "カ", $85 - charmap "キ", $86 - charmap "ク", $87 - charmap "ケ", $88 - charmap "コ", $89 - charmap "サ", $8a - charmap "シ", $8b - charmap "ス", $8c - charmap "セ", $8d - charmap "ソ", $8e - charmap "タ", $8f - charmap "チ", $90 - charmap "ツ", $91 - charmap "テ", $92 - charmap "ト", $93 - charmap "ナ", $94 - charmap "ニ", $95 - charmap "ヌ", $96 - charmap "ネ", $97 - charmap "ノ", $98 - charmap "ハ", $99 - charmap "ヒ", $9a - charmap "フ", $9b - charmap "ホ", $9c - charmap "マ", $9d - charmap "ミ", $9e - charmap "ム", $9f - charmap "メ", $a0 - charmap "モ", $a1 - charmap "ヤ", $a2 - charmap "ユ", $a3 - charmap "ヨ", $a4 - charmap "ラ", $a5 - charmap "ル", $a6 - charmap "レ", $a7 - charmap "ロ", $a8 - charmap "ワ", $a9 - charmap "ヲ", $aa - charmap "ン", $ab - - charmap "ッ", $ac - charmap "ャ", $ad - charmap "ュ", $ae - charmap "ョ", $af - charmap "ィ", $b0 - - charmap "あ", $b1 - charmap "い", $b2 - charmap "う", $b3 - charmap "え", $b4 - charmap "お", $b5 - charmap "か", $b6 - charmap "き", $b7 - charmap "く", $b8 - charmap "け", $b9 - charmap "こ", $ba - charmap "さ", $bb - charmap "し", $bc - charmap "す", $bd - charmap "せ", $be - charmap "そ", $bf - charmap "た", $c0 - charmap "ち", $c1 - charmap "つ", $c2 - charmap "て", $c3 - charmap "と", $c4 - charmap "な", $c5 - charmap "に", $c6 - charmap "ぬ", $c7 - charmap "ね", $c8 - charmap "の", $c9 - charmap "は", $ca - charmap "ひ", $cb - charmap "ふ", $cc - charmap "へ", $cd - charmap "ほ", $ce - charmap "ま", $cf - charmap "み", $d0 - charmap "む", $d1 - charmap "め", $d2 - charmap "も", $d3 - charmap "や", $d4 - charmap "ゆ", $d5 - charmap "よ", $d6 - charmap "ら", $d7 - charmap "り", $d8 - charmap "る", $d9 - charmap "れ", $da - charmap "ろ", $db - charmap "わ", $dc - charmap "を", $dd - charmap "ん", $de - - charmap "っ", $df - charmap "ゃ", $e0 - charmap "ゅ", $e1 - charmap "ょ", $e2 - - charmap "ー", $e3 - - charmap "゚", $e4 - charmap "゙", $e5 - - charmap "?", $e6 - charmap "!", $e7 - charmap "。", $e8 - - charmap "ァ", $e9 - charmap "ゥ", $ea - charmap "ェ", $eb - - charmap "▷", $ec - charmap "▶", $ed - charmap "▲", $ed - charmap "▼", $ee - charmap "♂", $ef - charmap "円", $f0 - charmap "×", $f1 - charmap ".", $f2 - charmap "/", $f3 - - charmap "ォ", $f4 - - charmap "♀", $f5 - charmap "0", $f6 - charmap "1", $f7 - charmap "2", $f8 - charmap "3", $f9 - charmap "4", $fa - charmap "5", $fb - charmap "6", $fc - charmap "7", $fd - charmap "8", $fe - charmap "9", $ff diff --git a/constants.asm b/constants.asm index 0d1fdec..d3e99ef 100644 --- a/constants.asm +++ b/constants.asm @@ -1,6 +1,18 @@ -INCLUDE "charmap.asm" +INCLUDE "constants/charmap.asm" -INCLUDE "macros.asm" +INCLUDE "macros/enum.asm" +INCLUDE "macros/predef.asm" +INCLUDE "macros/data.asm" +INCLUDE "macros/code.asm" +INCLUDE "macros/gfx.asm" +INCLUDE "macros/coords.asm" +INCLUDE "macros/farcall.asm" +INCLUDE "macros/text.asm" +INCLUDE "macros/wram.asm" +INCLUDE "macros/audio.asm" +INCLUDE "macros/scripts.asm" +INCLUDE "macros/queue.asm" +INCLUDE "macros/maps.asm" INCLUDE "constants/audio_constants.asm" INCLUDE "constants/gfx_constants.asm" diff --git a/constants/charmap.asm b/constants/charmap.asm new file mode 100755 index 0000000..be20a37 --- /dev/null +++ b/constants/charmap.asm @@ -0,0 +1,296 @@ + charmap "", $00 + + charmap "イ゛", $01 + charmap "ヴ", $02 + charmap "エ゛", $03 + charmap "オ゛", $04 + + charmap "ガ", $05 + charmap "ギ", $06 + charmap "グ", $07 + charmap "ゲ", $08 + charmap "ゴ", $09 + charmap "ザ", $0a + charmap "ジ", $0b + charmap "ズ", $0c + charmap "ゼ", $0d + charmap "ゾ", $0e + charmap "ダ", $0f + charmap "ヂ", $10 + charmap "ヅ", $11 + charmap "デ", $12 + charmap "ド", $13 + + charmap "", $14 ; "くん" or "ちゃん" + + charmap "<15>", $15 ; nothing + charmap "<16>", $16 ; nothing + + charmap "ネ゛", $17 + charmap "ノ゛", $18 + + charmap "バ", $19 + charmap "ビ", $1a + charmap "ブ", $1b + charmap "ボ", $1c + + charmap "", $1d ; "に " + charmap "", $1e ; "って" + charmap "", $1f ; "を " + + charmap "ィ゛", $20 + charmap "あ゛", $21 + + charmap "", $22 ; "た!" + charmap "", $23 ; "こうげき" + charmap "", $24 ; "は " + charmap "", $25 ; "の " + + charmap "が", $26 + charmap "ぎ", $27 + charmap "ぐ", $28 + charmap "げ", $29 + charmap "ご", $2a + charmap "ざ", $2b + charmap "じ", $2c + charmap "ず", $2d + charmap "ぜ", $2e + charmap "ぞ", $2f + charmap "だ", $30 + charmap "ぢ", $31 + charmap "づ", $32 + charmap "で", $33 + charmap "ど", $34 + + charmap "", $35 ; "ばん どうろ" + charmap "", $36 ; "わたし" + charmap "", $37 ; "ここは" + charmap "", $38 ; wRedsName + charmap "", $39 ; wGreensName + + charmap "ば", $3a + charmap "び", $3b + charmap "ぶ", $3c + charmap "べ", $3d + charmap "ぼ", $3e + + charmap "", $3f + + charmap "パ", $40 + charmap "ピ", $41 + charmap "プ", $42 + charmap "ポ", $43 + charmap "ぱ", $44 + charmap "ぴ", $45 + charmap "ぷ", $46 + charmap "ぺ", $47 + charmap "ぽ", $48 + + charmap "", $49 ; wMomsName + charmap "", $4a ; "が " + charmap "<_CONT>", $4b ; implements "" + charmap "", $4c + + charmap "も゜", $4d + + charmap "", $4e + charmap "", $4f + charmap "@", $50 ; string terminator + charmap "", $51 + charmap "", $52 ; wPlayerName + charmap "", $53 ; wRivalName + charmap "#", $54 ; "POKé" + charmap "", $55 + charmap "<⋯⋯>", $56 ; "⋯⋯" + charmap "", $57 + charmap "", $58 + charmap "", $59 + charmap "", $5a + charmap "", $5b ; "PC" + charmap "", $5c ; "TM" + charmap "", $5d ; "TRAINER" + charmap "", $5e ; "ROCKET" + charmap "", $5f + + charmap "■", $60 + charmap "▲", $61 + charmap "☎", $62 + + charmap "A", $60 + charmap "B", $61 + charmap "C", $62 + charmap "D", $63 + charmap "E", $64 + charmap "F", $65 + charmap "G", $66 + charmap "H", $67 + charmap "I", $68 + charmap "V", $69 + charmap "S", $6a + charmap "L", $6b + charmap "M", $6c + + charmap ":", $6d + + charmap "ぃ", $6e + charmap "ぅ", $6f + + charmap "「", $70 + charmap "」", $71 + charmap "『", $72 + charmap "』", $73 + charmap "・", $74 + charmap "⋯", $75 + + charmap "ぁ", $76 + charmap "ぇ", $77 + charmap "ぉ", $78 + + charmap "┌", $79 + charmap "─", $7a + charmap "┐", $7b + charmap "│", $7c + charmap "└", $7d + charmap "┘", $7e + + charmap " ", $7f + + charmap "ア", $80 + charmap "イ", $81 + charmap "ウ", $82 + charmap "エ", $83 + charmap "オ", $84 + charmap "カ", $85 + charmap "キ", $86 + charmap "ク", $87 + charmap "ケ", $88 + charmap "コ", $89 + charmap "サ", $8a + charmap "シ", $8b + charmap "ス", $8c + charmap "セ", $8d + charmap "ソ", $8e + charmap "タ", $8f + charmap "チ", $90 + charmap "ツ", $91 + charmap "テ", $92 + charmap "ト", $93 + charmap "ナ", $94 + charmap "ニ", $95 + charmap "ヌ", $96 + charmap "ネ", $97 + charmap "ノ", $98 + charmap "ハ", $99 + charmap "ヒ", $9a + charmap "フ", $9b + charmap "ホ", $9c + charmap "マ", $9d + charmap "ミ", $9e + charmap "ム", $9f + charmap "メ", $a0 + charmap "モ", $a1 + charmap "ヤ", $a2 + charmap "ユ", $a3 + charmap "ヨ", $a4 + charmap "ラ", $a5 + charmap "ル", $a6 + charmap "レ", $a7 + charmap "ロ", $a8 + charmap "ワ", $a9 + charmap "ヲ", $aa + charmap "ン", $ab + + charmap "ッ", $ac + charmap "ャ", $ad + charmap "ュ", $ae + charmap "ョ", $af + charmap "ィ", $b0 + + charmap "あ", $b1 + charmap "い", $b2 + charmap "う", $b3 + charmap "え", $b4 + charmap "お", $b5 + charmap "か", $b6 + charmap "き", $b7 + charmap "く", $b8 + charmap "け", $b9 + charmap "こ", $ba + charmap "さ", $bb + charmap "し", $bc + charmap "す", $bd + charmap "せ", $be + charmap "そ", $bf + charmap "た", $c0 + charmap "ち", $c1 + charmap "つ", $c2 + charmap "て", $c3 + charmap "と", $c4 + charmap "な", $c5 + charmap "に", $c6 + charmap "ぬ", $c7 + charmap "ね", $c8 + charmap "の", $c9 + charmap "は", $ca + charmap "ひ", $cb + charmap "ふ", $cc + charmap "へ", $cd + charmap "ほ", $ce + charmap "ま", $cf + charmap "み", $d0 + charmap "む", $d1 + charmap "め", $d2 + charmap "も", $d3 + charmap "や", $d4 + charmap "ゆ", $d5 + charmap "よ", $d6 + charmap "ら", $d7 + charmap "り", $d8 + charmap "る", $d9 + charmap "れ", $da + charmap "ろ", $db + charmap "わ", $dc + charmap "を", $dd + charmap "ん", $de + + charmap "っ", $df + charmap "ゃ", $e0 + charmap "ゅ", $e1 + charmap "ょ", $e2 + + charmap "ー", $e3 + + charmap "゚", $e4 + charmap "゙", $e5 + + charmap "?", $e6 + charmap "!", $e7 + charmap "。", $e8 + + charmap "ァ", $e9 + charmap "ゥ", $ea + charmap "ェ", $eb + + charmap "▷", $ec + charmap "▶", $ed + charmap "▲", $ed + charmap "▼", $ee + charmap "♂", $ef + charmap "円", $f0 + charmap "×", $f1 + charmap ".", $f2 + charmap "/", $f3 + + charmap "ォ", $f4 + + charmap "♀", $f5 + charmap "0", $f6 + charmap "1", $f7 + charmap "2", $f8 + charmap "3", $f9 + charmap "4", $fa + charmap "5", $fb + charmap "6", $fc + charmap "7", $fd + charmap "8", $fe + charmap "9", $ff diff --git a/constants/text_constants.asm b/constants/text_constants.asm index e487d4c..b3ed10c 100644 --- a/constants/text_constants.asm +++ b/constants/text_constants.asm @@ -40,6 +40,6 @@ PRINTNUM_MONEY EQU 1 << PRINTNUM_MONEY_F PRINTNUM_RIGHTALIGN EQU 1 << PRINTNUM_RIGHTALIGN_F PRINTNUM_LEADINGZEROS EQU 1 << PRINTNUM_LEADINGZEROS_F -; character sets (see charmap.asm) +; character sets (see constants/charmap.asm) FIRST_REGULAR_TEXT_CHAR EQU $60 FIRST_HIRAGANA_DAKUTEN_CHAR EQU $20 diff --git a/gfx.asm b/gfx.asm deleted file mode 100644 index 1b7aa6e..0000000 --- a/gfx.asm +++ /dev/null @@ -1,646 +0,0 @@ -INCLUDE "constants.asm" - -SECTION "gfx.asm@Mon Nest Icon", ROMX -PokedexNestIconGFX:: -INCBIN "gfx/trainer_gear/dexmap_nest_icon.1bpp" - -SECTION "gfx.asm@Bank 2 Misc GFX", ROMX -UnknownBouncingOrbGFX:: -INCBIN "gfx/overworld/gfx_84bf.2bpp" -JumpShadowGFX:: -INCBIN "gfx/overworld/shadow.2bpp" -ShockEmoteGFX:: -INCBIN "gfx/overworld/shock.2bpp" -QuestionEmoteGFX:: -INCBIN "gfx/overworld/question.2bpp" -HappyEmoteGFX:: -INCBIN "gfx/overworld/happy.2bpp" -UnknownBallGFX:: -INCBIN "gfx/overworld/gfx_85cf.2bpp" - -SECTION "gfx.asm@Trainer Gear GFX", ROMX -TrainerGearGFX:: -INCBIN "gfx/trainer_gear/trainer_gear.2bpp" -RadioGFX:: -INCBIN "gfx/trainer_gear/radio.2bpp" -VerticalPipeGFX:: -INCBIN "gfx/trainer_gear/vertical_pipe.2bpp" - -SECTION "gfx.asm@Title Screen BG Decoration Border", ROMX -TitleBGDecorationBorder:: -INCBIN "gfx/title/titlebgdecoration.2bpp" - -SECTION "gfx.asm@SGB GFX", ROMX - -INCLUDE "data/pokemon/palettes.inc" -INCLUDE "data/super_palettes.inc" - -Corrupted9e1cGFX: -INCBIN "gfx/sgb/corrupted_9e1c.2bpp" - -UnusedSGBBorderGFX:: -INCBIN "gfx/sgb/sgb_border_alt.2bpp" - -Corrupteda66cGFX: -INCBIN "gfx/sgb/corrupted_a66c.2bpp" - -SGBBorderGFX:: -if DEF(GOLD) -INCBIN "gfx/sgb/sgb_border_gold.2bpp" -else -INCBIN "gfx/sgb/sgb_border_silver.2bpp" -endc - -SECTION "gfx.asm@Corrupted SGB GFX", ROMX - -SGBBorderGoldCorruptedGFX: -INCBIN "gfx/sgb/sgb_border_gold_corrupted.2bpp" - -Corruptedb1e3GFX: -INCBIN "gfx/sgb/corrupted_b1e3.2bpp" - -SGBBorderSilverCorruptedGFX: -INCBIN "gfx/sgb/sgb_border_silver_corrupted.2bpp" - -Corruptedba93GFX: -INCBIN "gfx/sgb/corrupted_ba93.2bpp" - -SECTION "gfx.asm@Title Screen GFX", ROMX -if DEF(GOLD) -TitleScreenGFX:: INCBIN "gfx/title/title.2bpp" -TitleScreenVersionGFX:: INCBIN "gfx/title/title_gold_version.2bpp" -TitleScreenHoOhGFX:: INCBIN "gfx/title/title_hooh.2bpp" -TitleScreenLogoGFX:: INCBIN "gfx/title/title_logo.2bpp" -TitleScreenGoldLogoGFX:: INCBIN "gfx/title/title_goldlogo.2bpp" -else -TitleScreenGFX:: INCBIN "gfx/title/title.2bpp" -TitleScreenVersionGFX:: INCBIN "gfx/title/title_silver_version.2bpp" -TitleScreenHoOhGFX:: INCBIN "gfx/title/title_hooh.2bpp" -TitleScreenLogoGFX:: INCBIN "gfx/title/title_logo.2bpp" -TitleScreenGoldLogoGFX:: INCBIN "gfx/title/title_silverlogo.2bpp" -endc - -SECTION "gfx.asm@Name Entry Extra Tiles", ROMX -TextScreenGFX_End:: -INCBIN "gfx/font/text_entry_end.1bpp" -TextScreenGFX_Hyphen:: -INCBIN "gfx/font/text_entry_hyphen.1bpp" -TextScreenGFX_Underscore:: -INCBIN "gfx/font/text_entry_underscore.1bpp" - -SECTION "gfx.asm@Mail Icon GFX", ROMX -MailIconGFX:: -INCBIN "gfx/icons/mail.2bpp" - -SECTION "gfx.asm@Trainer Card GFX", ROMX -TrainerCardBorderGFX:: INCBIN "gfx/trainer_card/border.2bpp" -TrainerCardGFX:: INCBIN "gfx/trainer_card/trainer_card.2bpp" -TrainerCardColonGFX:: INCBIN "gfx/trainer_card/colon.2bpp" -TrainerCardIDNoGFX:: INCBIN "gfx/trainer_card/id_no.2bpp" -.End:: -TrainerCardLeadersGFX:: INCBIN "gfx/trainer_card/leaders.2bpp" - db $18, $00 ; leftover of previous graphics -Unreferenced_UnusedLeaderNameGFX:: INCBIN "gfx/trainer_card/unused_leader_name.2bpp" - -SECTION "gfx.asm@Bank 6 Tilesets 00", ROMX -Tileset_00_GFX: -Tileset_1b_GFX: -INCBIN "gfx/tilesets/tileset_00.common.2bpp" -Tileset_00_Meta: -INCBIN "data/tilesets/tileset_00_metatiles.bin" -Tileset_00_Coll: -INCBIN "data/tilesets/tileset_00_collision.bin" - -SECTION "gfx.asm@Bank 6 Tilesets 1b", ROMX -Tileset_1b_Meta: -INCBIN "data/tilesets/tileset_1b_metatiles.bin" -Tileset_1b_Coll: -INCBIN "data/tilesets/tileset_1b_collision.bin" - -SECTION "gfx.asm@Bank 6 Tilesets 01", ROMX -Tileset_01_GFX: -INCBIN "gfx/tilesets/tileset_01.common.2bpp" -Tileset_01_Meta: -INCBIN "data/tilesets/tileset_01_metatiles.bin" -Tileset_01_Coll: -INCBIN "data/tilesets/tileset_01_collision.bin" - -SECTION "gfx.asm@Bank 6 Tilesets 02", ROMX -Tileset_02_GFX: -INCBIN "gfx/tilesets/tileset_02.common.2bpp" -Tileset_02_Meta: -INCBIN "data/tilesets/tileset_02_metatiles.bin" -Tileset_02_Coll: -INCBIN "data/tilesets/tileset_02_collision.bin" - -SECTION "gfx.asm@Bank 6 Tilesets 09", ROMX -Tileset_09_GFX: -INCBIN "gfx/tilesets/tileset_09.2bpp" -Tileset_09_Meta: -INCBIN "data/tilesets/tileset_09_metatiles.bin" -Tileset_09_Coll: -INCBIN "data/tilesets/tileset_09_collision.bin" - -SECTION "gfx.asm@Bank 7 Tilesets 13", ROMX -Tileset_13_GFX: -INCBIN "gfx/tilesets/tileset_13.2bpp" -Tileset_13_Meta: -INCBIN "data/tilesets/tileset_13_metatiles.bin" -Tileset_13_Coll: -INCBIN "data/tilesets/tileset_13_collision.bin" - -SECTION "gfx.asm@Bank 7 Tilesets 0e", ROMX -Tileset_0e_GFX: -INCBIN "gfx/tilesets/tileset_0e.2bpp" -Tileset_0e_Meta: -INCBIN "data/tilesets/tileset_0e_metatiles.bin" -Tileset_0e_Coll: -INCBIN "data/tilesets/tileset_0e_collision.bin" - -SECTION "gfx.asm@Bank 7 Tilesets 06", ROMX -Tileset_06_GFX: -INCBIN "gfx/tilesets/tileset_06.common.2bpp" -Tileset_06_Meta: -INCBIN "data/tilesets/tileset_06_metatiles.bin" -Tileset_06_Coll: -INCBIN "data/tilesets/tileset_06_collision.bin" - -SECTION "gfx.asm@Bank 7 Tilesets 05", ROMX -Tileset_05_GFX: -INCBIN "gfx/tilesets/tileset_05.common.2bpp" -Tileset_05_Meta: -INCBIN "data/tilesets/tileset_05_metatiles.bin" -Tileset_05_Coll: -INCBIN "data/tilesets/tileset_05_collision.bin" - -SECTION "gfx.asm@Bank 7 Tilesets 03", ROMX -Tileset_03_GFX: -INCBIN "gfx/tilesets/tileset_03.common.2bpp" -Tileset_03_Meta: -INCBIN "data/tilesets/tileset_03_metatiles.bin" -Tileset_03_Coll: -INCBIN "data/tilesets/tileset_03_collision.bin" - -SECTION "gfx.asm@Bank 8 Tilesets 04", ROMX -Tileset_04_GFX: -INCBIN "gfx/tilesets/tileset_04.common.2bpp" -Tileset_04_Meta: -INCBIN "data/tilesets/tileset_04_metatiles.bin" -Tileset_04_Coll: -INCBIN "data/tilesets/tileset_04_collision.bin" - -SECTION "gfx.asm@Bank 8 Tilesets 07", ROMX -Tileset_07_GFX: -INCBIN "gfx/tilesets/tileset_07.common.2bpp" -Tileset_07_Meta: -INCBIN "data/tilesets/tileset_07_metatiles.bin" -Tileset_07_Coll: -INCBIN "data/tilesets/tileset_07_collision.bin" - -SECTION "gfx.asm@Bank 8 Tilesets 08", ROMX -Tileset_08_GFX: -INCBIN "gfx/tilesets/tileset_08.common.2bpp" -Tileset_08_Meta: -INCBIN "data/tilesets/tileset_08_metatiles.bin" -Tileset_08_Coll: -INCBIN "data/tilesets/tileset_08_collision.bin" - -SECTION "gfx.asm@Bank 8 Tilesets 0f", ROMX -Tileset_0f_GFX: -INCBIN "gfx/tilesets/tileset_0f.2bpp" -Tileset_0f_Meta: -INCBIN "data/tilesets/tileset_0f_metatiles.bin" -Tileset_0f_Coll: -INCBIN "data/tilesets/tileset_0f_collision.bin" - -SECTION "gfx.asm@Bank 8 Tilesets 11", ROMX -Tileset_11_GFX: -INCBIN "gfx/tilesets/tileset_11.2bpp" -Tileset_11_Meta: -INCBIN "data/tilesets/tileset_11_metatiles.bin" -Tileset_11_Coll: -INCBIN "data/tilesets/tileset_11_collision.bin" - -SECTION "gfx.asm@Gameboy GFX", ROMX -TradeGameBoyGFX:: -INCBIN "gfx/trade/gameboy.2bpp" - -SECTION "gfx.asm@Bank C Tilesets 12", ROMX -Tileset_12_GFX: -INCBIN "gfx/tilesets/tileset_12.2bpp" -Tileset_12_Meta: -INCBIN "data/tilesets/tileset_12_metatiles.bin" -Tileset_12_Coll: -INCBIN "data/tilesets/tileset_12_collision.bin" - -SECTION "gfx.asm@Bank C Tilesets 0b", ROMX -Tileset_0b_GFX: -INCBIN "gfx/tilesets/tileset_0b.2bpp" -Tileset_0b_Meta: -INCBIN "data/tilesets/tileset_0b_metatiles.bin" -Tileset_0b_Coll: -INCBIN "data/tilesets/tileset_0b_collision.bin" - -SECTION "gfx.asm@Bank C Tilesets 0d", ROMX -Tileset_0d_GFX: -INCBIN "gfx/tilesets/tileset_0d.2bpp" -Tileset_0d_Meta: -INCBIN "data/tilesets/tileset_0d_metatiles.bin" -Tileset_0d_Coll: -INCBIN "data/tilesets/tileset_0d_collision.bin" - -SECTION "gfx.asm@Bank C Tilesets 14", ROMX -Tileset_14_GFX: -INCBIN "gfx/tilesets/tileset_14.2bpp" -Tileset_14_Meta: -INCBIN "data/tilesets/tileset_14_metatiles.bin" -Tileset_14_Coll: -INCBIN "data/tilesets/tileset_14_collision.bin" - -SECTION "gfx.asm@Bank C Tilesets 0c", ROMX -Tileset_0c_GFX: -INCBIN "gfx/tilesets/tileset_0c.2bpp" -Tileset_0c_Meta: -INCBIN "data/tilesets/tileset_0c_metatiles.bin" -Tileset_0c_Coll: -INCBIN "data/tilesets/tileset_0c_collision.bin" - -SECTION "gfx.asm@Bank C Tilesets Common", ROMX -CommonExteriorTilesGFX: -INCBIN "gfx/tilesets/common.2bpp" - -SECTION "gfx.asm@PokeBalls GFX", ROMX - -PokeBallsGFX:: INCBIN "gfx/misc/poke_balls.2bpp" - -SECTION "gfx.asm@Pokedex GFX", ROMX -PokedexButtonsGFX:: -INCBIN "gfx/pokedex/buttons.2bpp" -PokedexPokeBallGFX:: -INCBIN "gfx/pokedex/poke_ball.2bpp" -PokedexCursorGFX:: -INCBIN "gfx/pokedex/cursor.2bpp" -PokedexBorderGFX:: -INCBIN "gfx/pokedex/border.2bpp" -PokedexSearchGFX:: -INCBIN "gfx/pokedex/search.2bpp" - -SECTION "gfx.asm@Trainer Battle Sprites", ROMX -HayatoPic:: INCBIN "gfx/trainer/hayato.pic" -AkanePic:: INCBIN "gfx/trainer/akane.pic" ; Gen 1 Bug Catcher -TsukushiPic:: INCBIN "gfx/trainer/tsukushi.pic" -EnokiPic:: INCBIN "gfx/trainer/enoki.pic" -OkeraPic:: INCBIN "gfx/trainer/okera.pic" ; Gen 1 Police Female -MikanPic:: INCBIN "gfx/trainer/mikan.pic" -BluePic:: INCBIN "gfx/trainer/blue.pic" ; Gen 1 Pokemaniac -GamaPic:: INCBIN "gfx/trainer/gama.pic" ; Gen 1 Super Nerd -RivalPic:: INCBIN "gfx/trainer/rival.pic" -OakPic:: INCBIN "gfx/trainer/oak.pic" -ProtagonistPic:: INCBIN "gfx/trainer/protagonist.pic" -KurtPic:: INCBIN "gfx/trainer/kurt.pic" -YoungsterPic:: INCBIN "gfx/trainer/youngster.pic" -SchoolboyPic:: INCBIN "gfx/trainer/schoolboy.pic" -FledglingPic:: INCBIN "gfx/trainer/fledgling.pic" -LassPic:: INCBIN "gfx/trainer/lass.pic" -ProfessionalMPic:: INCBIN "gfx/trainer/professional_m.pic" -ProfessionalFPic:: INCBIN "gfx/trainer/professional_f.pic" -BeautyPic:: INCBIN "gfx/trainer/beauty.pic" -PokemaniacPic:: INCBIN "gfx/trainer/pokemaniac.pic" -RocketMPic:: INCBIN "gfx/trainer/rocket_m.pic" -TeacherMPic:: INCBIN "gfx/trainer/teacher_m.pic" -TeacherFPic:: INCBIN "gfx/trainer/teacher_f.pic" -BugCatcherBoyPic:: INCBIN "gfx/trainer/bug_catcher_boy.pic" -FisherPic:: INCBIN "gfx/trainer/fisher.pic" -SwimmerMPic:: INCBIN "gfx/trainer/swimmer_m.pic" -SwimmerFPic:: INCBIN "gfx/trainer/swimmer_f.pic" -SuperNerdPic:: INCBIN "gfx/trainer/supernerd.pic" -EngineerPic:: INCBIN "gfx/trainer/engineer.pic" -GreenPic:: INCBIN "gfx/trainer/green.pic" ; Gen 1 Green -BikerPic:: INCBIN "gfx/trainer/biker.pic" -BurglarPic:: INCBIN "gfx/trainer/burglar.pic" -FirebreatherPic:: INCBIN "gfx/trainer/firebreather.pic" -JugglerPic:: INCBIN "gfx/trainer/juggler.pic" -BlackbeltPic:: INCBIN "gfx/trainer/blackbelt.pic" -SportsmanPic:: INCBIN "gfx/trainer/sportsman.pic" -MediumPic:: INCBIN "gfx/trainer/medium.pic" -SoldierPic:: INCBIN "gfx/trainer/soldier.pic" -KimonoGirlPic:: INCBIN "gfx/trainer/kimonogirl.pic" -TwinsPic:: INCBIN "gfx/trainer/twins.pic" - - -SECTION "gfx.asm@Bank 13 Tilesets 0a", ROMX -Tileset_0a_GFX: -INCBIN "gfx/tilesets/tileset_0a.2bpp" -Tileset_0a_Meta: -INCBIN "data/tilesets/tileset_0a_metatiles.bin" -Tileset_0a_Coll: -INCBIN "data/tilesets/tileset_0a_collision.bin" - -SECTION "gfx.asm@Bank 13 Tilesets 16", ROMX -Tileset_16_GFX: -INCBIN "gfx/tilesets/tileset_16.2bpp" -Tileset_16_Meta: -INCBIN "data/tilesets/tileset_16_metatiles.bin" -Tileset_16_Coll: -INCBIN "data/tilesets/tileset_16_collision.bin" - -SECTION "gfx.asm@Bank 13 Tilesets 19", ROMX -Tileset_19_GFX: -INCBIN "gfx/tilesets/tileset_19.2bpp" -Tileset_19_Meta: -INCBIN "data/tilesets/tileset_19_metatiles.bin" -Tileset_19_Coll: -INCBIN "data/tilesets/tileset_19_collision.bin" - -SECTION "gfx.asm@Bank 13 Tilesets 1a", ROMX -Tileset_1a_GFX: -INCBIN "gfx/tilesets/tileset_1a.2bpp" -Tileset_1a_Meta: -INCBIN "data/tilesets/tileset_1a_metatiles.bin" -Tileset_1a_Coll: -INCBIN "data/tilesets/tileset_1a_collision.bin" - -SECTION "gfx.asm@PKMN Sprite Bank List", ROMX -INCLUDE "gfx/pokemon/pkmn_pic_banks.asm" - -INCLUDE "gfx/pokemon/pkmn_pics.asm" - - -SECTION "gfx.asm@Annon Pic Ptrs and Pics", ROMX -INCLUDE "gfx/pokemon/annon_pic_ptrs.asm" -INCLUDE "gfx/pokemon/annon_pics.asm" - -INCLUDE "gfx/pokemon/egg.asm" - -SECTION "gfx.asm@Attack Animation GFX", ROMX - -INCBIN "gfx/battle_anims/attack_animations_1.2bpp" -PointerGFX:: -INCBIN "gfx/battle_anims/pointer.2bpp" -INCBIN "gfx/battle_anims/attack_animations_2.2bpp" - -SECTION "gfx.asm@Pokemon Party Sprites", ROMX -NyoromoIcon:: INCBIN "gfx/icons/nyoromo.2bpp" -PurinIcon:: INCBIN "gfx/icons/purin.2bpp" -DigdaIcon:: INCBIN "gfx/icons/digda.2bpp" -PikachuIcon:: INCBIN "gfx/icons/pikachu.2bpp" -HitodemanIcon:: INCBIN "gfx/icons/hitodeman.2bpp" -KoikingIcon:: INCBIN "gfx/icons/koiking.2bpp" -PoppoIcon:: INCBIN "gfx/icons/poppo.2bpp" -SidonIcon:: INCBIN "gfx/icons/sidon.2bpp" -PippiIcon:: INCBIN "gfx/icons/pippi.2bpp" -NazonokusaIcon:: INCBIN "gfx/icons/nazonokusa.2bpp" -MushiIcon:: INCBIN "gfx/icons/mushi.2bpp" -GangarIcon:: INCBIN "gfx/icons/gangar.2bpp" -LaplaceIcon:: INCBIN "gfx/icons/laplace.2bpp" -BarrierdIcon:: INCBIN "gfx/icons/barrierd.2bpp" -LokonIcon:: INCBIN "gfx/icons/lokon.2bpp" -KentaurosIcon:: INCBIN "gfx/icons/kentauros.2bpp" -ShellderIcon:: INCBIN "gfx/icons/shellder.2bpp" -MetamonIcon:: INCBIN "gfx/icons/metamon.2bpp" -IwarkIcon:: INCBIN "gfx/icons/iwark.2bpp" -BiriridamaIcon:: INCBIN "gfx/icons/biriridama.2bpp" -ZenigameIcon:: INCBIN "gfx/icons/zenigame.2bpp" -FushigidaneIcon:: INCBIN "gfx/icons/fushigidane.2bpp" -HitokageIcon:: INCBIN "gfx/icons/hitokage.2bpp" -BeedleIcon:: INCBIN "gfx/icons/beedle.2bpp" -AnnonIcon:: INCBIN "gfx/icons/annon.2bpp" -IsitsubuteIcon:: INCBIN "gfx/icons/isitsubute.2bpp" -WanrikyIcon:: INCBIN "gfx/icons/wanriky.2bpp" -EggIcon:: INCBIN "gfx/icons/egg.2bpp" -MenokurageIcon:: INCBIN "gfx/icons/menokurage.2bpp" -ButterfreeIcon:: INCBIN "gfx/icons/butterfree.2bpp" -ZubatIcon:: INCBIN "gfx/icons/zubat.2bpp" -KabigonIcon:: INCBIN "gfx/icons/kabigon.2bpp" - -SECTION "gfx.asm@Slot Machine GFX", ROMX -SlotMachineGFX:: -INCBIN "gfx/minigames/slots.2bpp" -SlotMachine2GFX:: -INCBIN "gfx/minigames/slots_2.2bpp" - -SECTION "gfx.asm@Bank 30 Sprites 1", ROMX -GoldSpriteGFX:: INCBIN "gfx/sprites/gold.2bpp" -GoldBikeSpriteGFX:: INCBIN "gfx/sprites/gold_bike.2bpp" -GoldSkateboardSpriteGFX:: INCBIN "gfx/sprites/gold_skateboard.2bpp" -SilverSpriteGFX:: INCBIN "gfx/sprites/silver.2bpp" -OkidoSpriteGFX:: INCBIN "gfx/sprites/okido.2bpp" -RedSpriteGFX:: INCBIN "gfx/sprites/red.2bpp" -BlueSpriteGFX:: INCBIN "gfx/sprites/blue.2bpp" -MasakiSpriteGFX:: INCBIN "gfx/sprites/masaki.2bpp" -ElderSpriteGFX:: INCBIN "gfx/sprites/elder.2bpp" -SakakiSpriteGFX:: INCBIN "gfx/sprites/sakaki.2bpp" -GantetsuSpriteGFX:: INCBIN "gfx/sprites/gantetsu.2bpp" -MomSpriteGFX:: INCBIN "gfx/sprites/mom.2bpp" -SilversMomSpriteGFX:: INCBIN "gfx/sprites/silvers_mom.2bpp" -RedsMomSpriteGFX:: INCBIN "gfx/sprites/reds_mom.2bpp" -NanamiSpriteGFX:: INCBIN "gfx/sprites/nanami.2bpp" -EvilOkidoSpriteGFX:: INCBIN "gfx/sprites/evil_okido.2bpp" -KikukoSpriteGFX:: INCBIN "gfx/sprites/kikuko.2bpp" -HayatoSpriteGFX:: INCBIN "gfx/sprites/hayato.2bpp" -TsukushiSpriteGFX:: INCBIN "gfx/sprites/tsukushi.2bpp" -EnokiSpriteGFX:: INCBIN "gfx/sprites/enoki.2bpp" -MikanSpriteGFX:: INCBIN "gfx/sprites/mikan.2bpp" -CooltrainerMSpriteGFX:: INCBIN "gfx/sprites/cooltrainer_m.2bpp" -CooltrainerFSpriteGFX:: INCBIN "gfx/sprites/cooltrainer_f.2bpp" -BugCatcherBoySpriteGFX:: INCBIN "gfx/sprites/bug_catcher_boy.2bpp" -TwinSpriteGFX:: INCBIN "gfx/sprites/twin.2bpp" -YoungsterSpriteGFX:: INCBIN "gfx/sprites/youngster.2bpp" -LassSpriteGFX:: INCBIN "gfx/sprites/lass.2bpp" -TeacherSpriteGFX:: INCBIN "gfx/sprites/teacher.2bpp" -GirlSpriteGFX:: INCBIN "gfx/sprites/girl.2bpp" -SuperNerdSpriteGFX:: INCBIN "gfx/sprites/super_nerd.2bpp" -RockerSpriteGFX:: INCBIN "gfx/sprites/rocker.2bpp" -PokefanMSpriteGFX:: INCBIN "gfx/sprites/pokefan_m.2bpp" -PokefanFSpriteGFX:: INCBIN "gfx/sprites/pokefan_f.2bpp" -GrampsSpriteGFX:: INCBIN "gfx/sprites/gramps.2bpp" -GrannySpriteGFX:: INCBIN "gfx/sprites/granny.2bpp" -SwimmerMSpriteGFX:: INCBIN "gfx/sprites/swimmer_m.2bpp" -SwimmerFSpriteGFX:: INCBIN "gfx/sprites/swimmer_f.2bpp" -RocketMSpriteGFX:: INCBIN "gfx/sprites/rocket_m.2bpp" -RocketFSpriteGFX:: INCBIN "gfx/sprites/rocket_f.2bpp" -NurseSpriteGFX:: INCBIN "gfx/sprites/nurse.2bpp" -LinkReceptionistSpriteGFX:: INCBIN "gfx/sprites/link_receptionist.2bpp" -ClerkSpriteGFX:: INCBIN "gfx/sprites/clerk.2bpp" -FisherSpriteGFX:: INCBIN "gfx/sprites/fisher.2bpp" -FishingGuruSpriteGFX:: INCBIN "gfx/sprites/fishing_guru.2bpp" - -SECTION "gfx.asm@Bank 31 Sprites 2", ROMX -ScientistSpriteGFX:: INCBIN "gfx/sprites/scientist.2bpp" -MediumSpriteGFX:: INCBIN "gfx/sprites/medium.2bpp" -SageSpriteGFX:: INCBIN "gfx/sprites/sage.2bpp" -FrowningManSpriteGFX:: INCBIN "gfx/sprites/frowning_man.2bpp" -GentlemanSpriteGFX:: INCBIN "gfx/sprites/gentleman.2bpp" -BlackbeltSpriteGFX:: INCBIN "gfx/sprites/blackbelt.2bpp" -ReceptionistSpriteGFX:: INCBIN "gfx/sprites/receptionist.2bpp" -OfficerSpriteGFX:: INCBIN "gfx/sprites/officer.2bpp" -CaptainSpriteGFX:: INCBIN "gfx/sprites/captain.2bpp" -MohawkSpriteGFX:: INCBIN "gfx/sprites/mohawk.2bpp" -GymGuySpriteGFX:: INCBIN "gfx/sprites/gym_guy.2bpp" -SailorSpriteGFX:: INCBIN "gfx/sprites/sailor.2bpp" -HelmetSpriteGFX:: INCBIN "gfx/sprites/helmet.2bpp" -BurglarSpriteGFX:: INCBIN "gfx/sprites/burglar.2bpp" -SidonSpriteGFX:: INCBIN "gfx/sprites/sidon.2bpp" -PippiSpriteGFX:: INCBIN "gfx/sprites/pippi.2bpp" -PoppoSpriteGFX:: INCBIN "gfx/sprites/poppo.2bpp" -LizardonSpriteGFX:: INCBIN "gfx/sprites/lizardon.2bpp" -KabigonSpriteGFX:: INCBIN "gfx/sprites/kabigon.2bpp" -PawouSpriteGFX:: INCBIN "gfx/sprites/pawou.2bpp" -NyorobonSpriteGFX:: INCBIN "gfx/sprites/nyorobon.2bpp" -LaplaceSpriteGFX:: INCBIN "gfx/sprites/laplace.2bpp" -PokeBallSpriteGFX:: INCBIN "gfx/sprites/poke_ball.2bpp" -PokedexSpriteGFX:: INCBIN "gfx/sprites/pokedex.2bpp" -PaperSpriteGFX:: INCBIN "gfx/sprites/paper.2bpp" -OldLinkReceptionistSpriteGFX:: INCBIN "gfx/sprites/old_link_receptionist.2bpp" -EggSpriteGFX:: INCBIN "gfx/sprites/egg.2bpp" -BoulderSpriteGFX:: INCBIN "gfx/sprites/boulder.2bpp" - -SECTION "gfx.asm@Bank 37 Tilesets 10", ROMX -Tileset_10_GFX: -INCBIN "gfx/tilesets/tileset_10.2bpp" -Tileset_10_Meta: -INCBIN "data/tilesets/tileset_10_metatiles.bin" -Tileset_10_Coll: -INCBIN "data/tilesets/tileset_10_collision.bin" - -SECTION "gfx.asm@Bank 37 Tilesets 15", ROMX -Tileset_15_GFX: -INCBIN "gfx/tilesets/tileset_15.2bpp" -Tileset_15_Meta: -INCBIN "data/tilesets/tileset_15_metatiles.bin" -Tileset_15_Coll: -INCBIN "data/tilesets/tileset_15_collision.bin" - -SECTION "gfx.asm@Bank 37 Tilesets 17", ROMX -Tileset_17_GFX: -INCBIN "gfx/tilesets/tileset_17.2bpp" -Tileset_17_Meta: -INCBIN "data/tilesets/tileset_17_metatiles.bin" -Tileset_17_Coll: -INCBIN "data/tilesets/tileset_17_collision.bin" - -SECTION "gfx.asm@Bank 37 Tilesets 18", ROMX -Tileset_18_GFX: -INCBIN "gfx/tilesets/tileset_18.2bpp" -Tileset_18_Meta: -INCBIN "data/tilesets/tileset_18_metatiles.bin" -Tileset_18_Coll: -INCBIN "data/tilesets/tileset_18_collision.bin" - -SECTION "gfx.asm@Poker GFX", ROMX -PokerGFX:: -INCBIN "gfx/minigames/poker.2bpp" - -SECTION "gfx.asm@15 Puzzle GFX", ROMX -FifteenPuzzleGFX:: -INCBIN "gfx/minigames/15_puzzle.2bpp" - -SECTION "gfx.asm@Matches GFX", ROMX -MemoryGameGFX:: -INCBIN "gfx/minigames/matches.2bpp" - -SECTION "gfx.asm@Picross GFX", ROMX -PicrossGFX:: -INCBIN "gfx/minigames/picross.2bpp" -PicrossCursorGFX:: -INCBIN "gfx/minigames/picross_cursor.2bpp" - -SECTION "gfx.asm@Gamefreak Logo GFX", ROMX -GameFreakLogoGFX:: -INCBIN "gfx/splash/game_freak_logo.1bpp" -GameFreakLogoSparkleGFX:: -INCBIN "gfx/splash/game_freak_logo_oam.2bpp" - -SECTION "gfx.asm@Intro Underwater GFX", ROMX -IntroUnderwaterGFX:: -INCBIN "gfx/intro/underwater.2bpp" - -SECTION "gfx.asm@Intro Water Mon and Forest GFX", ROMX -IntroWaterPokemonGFX:: -INCBIN "gfx/intro/water_pokemon.2bpp" -IntroForestGFX:: -INCBIN "gfx/intro/forest.2bpp" - -SECTION "gfx.asm@Intro Mon", ROMX -IntroPurinPikachuGFX:: -INCBIN "gfx/intro/purin_pikachu.2bpp" -IntroLizardon1GFX:: -INCBIN "gfx/intro/lizardon_1.2bpp" -IntroLizardon2GFX:: -INCBIN "gfx/intro/lizardon_2.2bpp" -IntroLizardon3GFX:: -INCBIN "gfx/intro/lizardon_3.2bpp" -IntroLizardonFlamesGFX:: -INCBIN "gfx/intro/lizardon_flames.2bpp" -IntroKamexGFX:: -INCBIN "gfx/intro/kamex.2bpp" -IntroFushigibanaGFX:: -INCBIN "gfx/intro/fushigibana.2bpp" - -SECTION "gfx.asm@Misc GFX", ROMX -FontExtraGFX:: -FontExtraAB_GFX:: INCBIN "gfx/font/font_extra.ab.2bpp" -FontExtraCDEFGHIVSLM_GFX:: INCBIN "gfx/font/font_extra.cdefghivslm.2bpp" -FontSmallKanaPunctuationGFX:: INCBIN "gfx/font/small_kana_punctuation.2bpp" -.End:: -Unreferenced_DefaultFrame0GFX:: INCBIN "gfx/frames/1.2bpp" -FontGFX:: INCBIN "gfx/font/font.1bpp" -.End:: -FontBattleExtraGFX:: -BattleHPBarGFX:: INCBIN "gfx/battle/hp_bar.2bpp" -.End:: -HpExpBarParts0_2bppGFX:: INCBIN "gfx/battle/hp_exp_bar_parts0.2bpp" -BattleMarkersGFX:: INCBIN "gfx/battle/markers.2bpp" -.End:: -LevelUpGFX:: INCBIN "gfx/battle/levelup.2bpp" -.End:: - -Unreferenced_DefaultFrame1:: INCBIN "gfx/frames/1.2bpp" - -FrameGFX:: -INCBIN "gfx/frames/1.1bpp" -.FirstEntryEnd:: -INCBIN "gfx/frames/2.1bpp" -INCBIN "gfx/frames/3.1bpp" -INCBIN "gfx/frames/4.1bpp" -INCBIN "gfx/frames/5.1bpp" -INCBIN "gfx/frames/6.1bpp" -INCBIN "gfx/frames/7.1bpp" -INCBIN "gfx/frames/8.1bpp" -INCBIN "gfx/frames/9.1bpp" - -StatsGFX:: -INCBIN "gfx/stats/separator.2bpp" -INCBIN "gfx/stats/stats.2bpp" -.End:: - -HpExpBarParts0GFX:: INCBIN "gfx/battle/hp_exp_bar_parts0.1bpp" -.End:: -HpExpBarParts1GFX:: INCBIN "gfx/battle/hp_exp_bar_parts1.1bpp" -.End:: -HpExpBarParts2GFX:: INCBIN "gfx/battle/hp_exp_bar_parts2.1bpp" -.End:: -HpExpBarParts3GFX:: INCBIN "gfx/battle/hp_exp_bar_parts3.1bpp" -.End:: -ExpBarGFX:: INCBIN "gfx/battle/exp_bar.2bpp" -.End:: -PokedexGFX:: INCBIN "gfx/pokedex/pokedex.2bpp" -.End:: -PokedexLocationGFX:: INCBIN "gfx/pokedex/locations.2bpp" -.End:: -TownMapGFX:: INCBIN "gfx/trainer_gear/town_map.2bpp" -.End:: -HUD_GFX:: INCBIN "gfx/hud/hud.2bpp" -.End:: -BoldAlphabetGFX:: INCBIN "gfx/font/alphabet.1bpp" -AnnonAlphabetGFX:: INCBIN "gfx/font/annon_alphabet.1bpp" -EmptyTile1bppGFX:: INCBIN "gfx/misc/empty_tile.1bpp" -.End:: -BlackTileAndCursor1bppGFX:: INCBIN "gfx/misc/black_tile_cursor.1bpp" -.End:: -PackIconGFX:: INCBIN "gfx/pack/pack_icons.2bpp" -.End:: - -SECTION "gfx.asm@Town Map Cursor", ROMX -TownMapCursorGFX:: -INCBIN "gfx/trainer_gear/town_map_cursor.2bpp" diff --git a/gfx.py b/gfx.py deleted file mode 100644 index 04e9f7c..0000000 --- a/gfx.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/python - -"""Supplementary scripts for graphics conversion.""" - -import os -import argparse - -from utils import gfx, lz - - -# Graphics with inverted tilemaps that aren't covered by filepath_rules. -pics = [ - 'gfx/shrink1', - 'gfx/shrink2', -] - -def recursive_read(filename): - def recurse(filename_): - lines = [] - for line in open(filename_): - if 'include "' in line.lower(): - lines += recurse(line.split('"')[1]) - else: - lines += [line] - return lines - lines = recurse(filename) - return ''.join(lines) - -base_stats = None -def get_base_stats(): - global base_stats - if not base_stats: - base_stats = recursive_read('data/base_stats.asm') - return base_stats - -def get_pokemon_dimensions(name): - try: - if name == 'egg': - return 5, 5 - if name.startswith('annon_'): - name = 'annon' - base_stats = get_base_stats() - start = base_stats.find('\tdb ' + name.upper()) - start = base_stats.find('\tdn ', start) - end = base_stats.find('\n', start) - line = base_stats[start:end].replace(',', ' ') - w, h = map(int, line.split()[1:3]) - return w, h - except: - return 7, 7 - -def filepath_rules(filepath): - """Infer attributes of certain graphics by their location in the filesystem.""" - args = {} - - filedir, filename = os.path.split(filepath) - if filedir.startswith('./'): - filedir = filedir[2:] - - name, ext = os.path.splitext(filename) - if ext == '.lz': - name, ext = os.path.splitext(name) - - pokemon_name = '' - - if 'gfx/pokemon/' in filedir: - pokemon_name = filedir.split('/')[-1] - if pokemon_name.startswith('annon_'): - index = filedir.find(pokemon_name) - if index != -1: - filedir = filedir[:index + len('annon')] + filedir[index + len('annon_a'):] - if name == 'front': - args['pal_file'] = os.path.join(filedir, 'normal.pal') - args['pic'] = True - args['animate'] = True - elif name == 'back': - args['pal_file'] = os.path.join(filedir, 'shiny.pal') - args['pic'] = True - - elif 'gfx/trainers' in filedir: - args['pic'] = True - - elif os.path.join(filedir, name) in pics: - args['pic'] = True - - if args.get('pal_file'): - if os.path.exists(args['pal_file']): - args['palout'] = args['pal_file'] - else: - del args['pal_file'] - - if args.get('pic'): - if ext == '.png': - w, h = gfx.png.Reader(filepath).asRGBA8()[:2] - w = min(w/8, h/8) - args['pic_dimensions'] = w, w - elif ext == '.2bpp': - if pokemon_name and name == 'front': - w, h = get_pokemon_dimensions(pokemon_name) - args['pic_dimensions'] = w, w - elif pokemon_name and name == 'back': - args['pic_dimensions'] = 6, 6 - else: - args['pic_dimensions'] = 7, 7 - return args - - -def to_1bpp(filename, **kwargs): - name, ext = os.path.splitext(filename) - if ext == '.1bpp': pass - elif ext == '.2bpp': gfx.export_2bpp_to_1bpp(filename, **kwargs) - elif ext == '.png': gfx.export_png_to_1bpp(filename, **kwargs) - elif ext == '.lz': - decompress(filename, **kwargs) - to_1bpp(name, **kwargs) - -def to_2bpp(filename, **kwargs): - name, ext = os.path.splitext(filename) - if ext == '.1bpp': gfx.export_1bpp_to_2bpp(filename, **kwargs) - elif ext == '.2bpp': pass - elif ext == '.png': gfx.export_png_to_2bpp(filename, **kwargs) - elif ext == '.lz': - decompress(filename, **kwargs) - to_2bpp(name, **kwargs) - -def to_png(filename, **kwargs): - name, ext = os.path.splitext(filename) - if ext == '.1bpp': gfx.export_1bpp_to_png(filename, **kwargs) - elif ext == '.2bpp': gfx.export_2bpp_to_png(filename, **kwargs) - elif ext == '.png': pass - elif ext == '.lz': - decompress(filename, **kwargs) - to_png(name, **kwargs) - -def compress(filename, **kwargs): - data = open(filename, 'rb').read() - lz_data = lz.Compressed(data).output - open(filename + '.lz', 'wb').write(bytearray(lz_data)) - -def decompress(filename, **kwargs): - lz_data = open(filename, 'rb').read() - data = lz.Decompressed(lz_data).output - name, ext = os.path.splitext(filename) - open(name, 'wb').write(bytearray(data)) - - -methods = { - '2bpp': to_2bpp, - '1bpp': to_1bpp, - 'png': to_png, - 'lz': compress, - 'unlz': decompress, -} - -def main(method_name, filenames=None): - if filenames is None: filenames = [] - for filename in filenames: - args = filepath_rules(filename) - method = methods.get(method_name) - if method: - method(filename, **args) - -def get_args(): - ap = argparse.ArgumentParser() - ap.add_argument('method_name') - ap.add_argument('filenames', nargs='*') - args = ap.parse_args() - return args - -if __name__ == '__main__': - main(**get_args().__dict__) diff --git a/gfx/gfx.asm b/gfx/gfx.asm new file mode 100644 index 0000000..72d65d1 --- /dev/null +++ b/gfx/gfx.asm @@ -0,0 +1,632 @@ +INCLUDE "constants.asm" + +SECTION "gfx.asm@Mon Nest Icon", ROMX +PokedexNestIconGFX:: +INCBIN "gfx/trainer_gear/dexmap_nest_icon.1bpp" + +SECTION "gfx.asm@Bank 2 Misc GFX", ROMX +UnknownBouncingOrbGFX:: +INCBIN "gfx/overworld/gfx_84bf.2bpp" +JumpShadowGFX:: +INCBIN "gfx/overworld/shadow.2bpp" +ShockEmoteGFX:: +INCBIN "gfx/overworld/shock.2bpp" +QuestionEmoteGFX:: +INCBIN "gfx/overworld/question.2bpp" +HappyEmoteGFX:: +INCBIN "gfx/overworld/happy.2bpp" +UnknownBallGFX:: +INCBIN "gfx/overworld/gfx_85cf.2bpp" + +SECTION "gfx.asm@Trainer Gear GFX", ROMX +TrainerGearGFX:: +INCBIN "gfx/trainer_gear/trainer_gear.2bpp" +RadioGFX:: +INCBIN "gfx/trainer_gear/radio.2bpp" +VerticalPipeGFX:: +INCBIN "gfx/trainer_gear/vertical_pipe.2bpp" + +SECTION "gfx.asm@Title Screen BG Decoration Border", ROMX +TitleBGDecorationBorder:: +INCBIN "gfx/title/titlebgdecoration.2bpp" + +SECTION "gfx.asm@SGB GFX", ROMX + +INCLUDE "data/pokemon/palettes.inc" +INCLUDE "data/super_palettes.inc" + +Corrupted9e1cGFX: +INCBIN "slack/corrupted_9e1c.2bpp" + +UnusedSGBBorderGFX:: +INCBIN "gfx/sgb/sgb_border_alt.2bpp" + +Corrupteda66cGFX: +INCBIN "slack/corrupted_a66c.2bpp" + +SGBBorderGFX:: +if DEF(GOLD) +INCBIN "gfx/sgb/sgb_border_gold.2bpp" +else +INCBIN "gfx/sgb/sgb_border_silver.2bpp" +endc + +SECTION "gfx.asm@Title Screen GFX", ROMX +if DEF(GOLD) +TitleScreenGFX:: INCBIN "gfx/title/title.2bpp" +TitleScreenVersionGFX:: INCBIN "gfx/title/title_gold_version.2bpp" +TitleScreenHoOhGFX:: INCBIN "gfx/title/title_hooh.2bpp" +TitleScreenLogoGFX:: INCBIN "gfx/title/title_logo.2bpp" +TitleScreenGoldLogoGFX:: INCBIN "gfx/title/title_goldlogo.2bpp" +else +TitleScreenGFX:: INCBIN "gfx/title/title.2bpp" +TitleScreenVersionGFX:: INCBIN "gfx/title/title_silver_version.2bpp" +TitleScreenHoOhGFX:: INCBIN "gfx/title/title_hooh.2bpp" +TitleScreenLogoGFX:: INCBIN "gfx/title/title_logo.2bpp" +TitleScreenGoldLogoGFX:: INCBIN "gfx/title/title_silverlogo.2bpp" +endc + +SECTION "gfx.asm@Name Entry Extra Tiles", ROMX +TextScreenGFX_End:: +INCBIN "gfx/font/text_entry_end.1bpp" +TextScreenGFX_Hyphen:: +INCBIN "gfx/font/text_entry_hyphen.1bpp" +TextScreenGFX_Underscore:: +INCBIN "gfx/font/text_entry_underscore.1bpp" + +SECTION "gfx.asm@Mail Icon GFX", ROMX +MailIconGFX:: +INCBIN "gfx/icons/mail.2bpp" + +SECTION "gfx.asm@Trainer Card GFX", ROMX +TrainerCardBorderGFX:: INCBIN "gfx/trainer_card/border.2bpp" +TrainerCardGFX:: INCBIN "gfx/trainer_card/trainer_card.2bpp" +TrainerCardColonGFX:: INCBIN "gfx/trainer_card/colon.2bpp" +TrainerCardIDNoGFX:: INCBIN "gfx/trainer_card/id_no.2bpp" +.End:: +TrainerCardLeadersGFX:: INCBIN "gfx/trainer_card/leaders.2bpp" + db $18, $00 ; leftover of previous graphics +Unreferenced_UnusedLeaderNameGFX:: INCBIN "gfx/trainer_card/unused_leader_name.2bpp" + +SECTION "gfx.asm@Bank 6 Tilesets 00", ROMX +Tileset_00_GFX: +Tileset_1b_GFX: +INCBIN "gfx/tilesets/tileset_00.common.2bpp" +Tileset_00_Meta: +INCBIN "data/tilesets/tileset_00_metatiles.bin" +Tileset_00_Coll: +INCBIN "data/tilesets/tileset_00_collision.bin" + +SECTION "gfx.asm@Bank 6 Tilesets 1b", ROMX +Tileset_1b_Meta: +INCBIN "data/tilesets/tileset_1b_metatiles.bin" +Tileset_1b_Coll: +INCBIN "data/tilesets/tileset_1b_collision.bin" + +SECTION "gfx.asm@Bank 6 Tilesets 01", ROMX +Tileset_01_GFX: +INCBIN "gfx/tilesets/tileset_01.common.2bpp" +Tileset_01_Meta: +INCBIN "data/tilesets/tileset_01_metatiles.bin" +Tileset_01_Coll: +INCBIN "data/tilesets/tileset_01_collision.bin" + +SECTION "gfx.asm@Bank 6 Tilesets 02", ROMX +Tileset_02_GFX: +INCBIN "gfx/tilesets/tileset_02.common.2bpp" +Tileset_02_Meta: +INCBIN "data/tilesets/tileset_02_metatiles.bin" +Tileset_02_Coll: +INCBIN "data/tilesets/tileset_02_collision.bin" + +SECTION "gfx.asm@Bank 6 Tilesets 09", ROMX +Tileset_09_GFX: +INCBIN "gfx/tilesets/tileset_09.2bpp" +Tileset_09_Meta: +INCBIN "data/tilesets/tileset_09_metatiles.bin" +Tileset_09_Coll: +INCBIN "data/tilesets/tileset_09_collision.bin" + +SECTION "gfx.asm@Bank 7 Tilesets 13", ROMX +Tileset_13_GFX: +INCBIN "gfx/tilesets/tileset_13.2bpp" +Tileset_13_Meta: +INCBIN "data/tilesets/tileset_13_metatiles.bin" +Tileset_13_Coll: +INCBIN "data/tilesets/tileset_13_collision.bin" + +SECTION "gfx.asm@Bank 7 Tilesets 0e", ROMX +Tileset_0e_GFX: +INCBIN "gfx/tilesets/tileset_0e.2bpp" +Tileset_0e_Meta: +INCBIN "data/tilesets/tileset_0e_metatiles.bin" +Tileset_0e_Coll: +INCBIN "data/tilesets/tileset_0e_collision.bin" + +SECTION "gfx.asm@Bank 7 Tilesets 06", ROMX +Tileset_06_GFX: +INCBIN "gfx/tilesets/tileset_06.common.2bpp" +Tileset_06_Meta: +INCBIN "data/tilesets/tileset_06_metatiles.bin" +Tileset_06_Coll: +INCBIN "data/tilesets/tileset_06_collision.bin" + +SECTION "gfx.asm@Bank 7 Tilesets 05", ROMX +Tileset_05_GFX: +INCBIN "gfx/tilesets/tileset_05.common.2bpp" +Tileset_05_Meta: +INCBIN "data/tilesets/tileset_05_metatiles.bin" +Tileset_05_Coll: +INCBIN "data/tilesets/tileset_05_collision.bin" + +SECTION "gfx.asm@Bank 7 Tilesets 03", ROMX +Tileset_03_GFX: +INCBIN "gfx/tilesets/tileset_03.common.2bpp" +Tileset_03_Meta: +INCBIN "data/tilesets/tileset_03_metatiles.bin" +Tileset_03_Coll: +INCBIN "data/tilesets/tileset_03_collision.bin" + +SECTION "gfx.asm@Bank 8 Tilesets 04", ROMX +Tileset_04_GFX: +INCBIN "gfx/tilesets/tileset_04.common.2bpp" +Tileset_04_Meta: +INCBIN "data/tilesets/tileset_04_metatiles.bin" +Tileset_04_Coll: +INCBIN "data/tilesets/tileset_04_collision.bin" + +SECTION "gfx.asm@Bank 8 Tilesets 07", ROMX +Tileset_07_GFX: +INCBIN "gfx/tilesets/tileset_07.common.2bpp" +Tileset_07_Meta: +INCBIN "data/tilesets/tileset_07_metatiles.bin" +Tileset_07_Coll: +INCBIN "data/tilesets/tileset_07_collision.bin" + +SECTION "gfx.asm@Bank 8 Tilesets 08", ROMX +Tileset_08_GFX: +INCBIN "gfx/tilesets/tileset_08.common.2bpp" +Tileset_08_Meta: +INCBIN "data/tilesets/tileset_08_metatiles.bin" +Tileset_08_Coll: +INCBIN "data/tilesets/tileset_08_collision.bin" + +SECTION "gfx.asm@Bank 8 Tilesets 0f", ROMX +Tileset_0f_GFX: +INCBIN "gfx/tilesets/tileset_0f.2bpp" +Tileset_0f_Meta: +INCBIN "data/tilesets/tileset_0f_metatiles.bin" +Tileset_0f_Coll: +INCBIN "data/tilesets/tileset_0f_collision.bin" + +SECTION "gfx.asm@Bank 8 Tilesets 11", ROMX +Tileset_11_GFX: +INCBIN "gfx/tilesets/tileset_11.2bpp" +Tileset_11_Meta: +INCBIN "data/tilesets/tileset_11_metatiles.bin" +Tileset_11_Coll: +INCBIN "data/tilesets/tileset_11_collision.bin" + +SECTION "gfx.asm@Gameboy GFX", ROMX +TradeGameBoyGFX:: +INCBIN "gfx/trade/gameboy.2bpp" + +SECTION "gfx.asm@Bank C Tilesets 12", ROMX +Tileset_12_GFX: +INCBIN "gfx/tilesets/tileset_12.2bpp" +Tileset_12_Meta: +INCBIN "data/tilesets/tileset_12_metatiles.bin" +Tileset_12_Coll: +INCBIN "data/tilesets/tileset_12_collision.bin" + +SECTION "gfx.asm@Bank C Tilesets 0b", ROMX +Tileset_0b_GFX: +INCBIN "gfx/tilesets/tileset_0b.2bpp" +Tileset_0b_Meta: +INCBIN "data/tilesets/tileset_0b_metatiles.bin" +Tileset_0b_Coll: +INCBIN "data/tilesets/tileset_0b_collision.bin" + +SECTION "gfx.asm@Bank C Tilesets 0d", ROMX +Tileset_0d_GFX: +INCBIN "gfx/tilesets/tileset_0d.2bpp" +Tileset_0d_Meta: +INCBIN "data/tilesets/tileset_0d_metatiles.bin" +Tileset_0d_Coll: +INCBIN "data/tilesets/tileset_0d_collision.bin" + +SECTION "gfx.asm@Bank C Tilesets 14", ROMX +Tileset_14_GFX: +INCBIN "gfx/tilesets/tileset_14.2bpp" +Tileset_14_Meta: +INCBIN "data/tilesets/tileset_14_metatiles.bin" +Tileset_14_Coll: +INCBIN "data/tilesets/tileset_14_collision.bin" + +SECTION "gfx.asm@Bank C Tilesets 0c", ROMX +Tileset_0c_GFX: +INCBIN "gfx/tilesets/tileset_0c.2bpp" +Tileset_0c_Meta: +INCBIN "data/tilesets/tileset_0c_metatiles.bin" +Tileset_0c_Coll: +INCBIN "data/tilesets/tileset_0c_collision.bin" + +SECTION "gfx.asm@Bank C Tilesets Common", ROMX +CommonExteriorTilesGFX: +INCBIN "gfx/tilesets/common.2bpp" + +SECTION "gfx.asm@PokeBalls GFX", ROMX + +PokeBallsGFX:: INCBIN "gfx/misc/poke_balls.2bpp" + +SECTION "gfx.asm@Pokedex GFX", ROMX +PokedexButtonsGFX:: +INCBIN "gfx/pokedex/buttons.2bpp" +PokedexPokeBallGFX:: +INCBIN "gfx/pokedex/poke_ball.2bpp" +PokedexCursorGFX:: +INCBIN "gfx/pokedex/cursor.2bpp" +PokedexBorderGFX:: +INCBIN "gfx/pokedex/border.2bpp" +PokedexSearchGFX:: +INCBIN "gfx/pokedex/search.2bpp" + +SECTION "gfx.asm@Trainer Battle Sprites", ROMX +HayatoPic:: INCBIN "gfx/trainer/hayato.pic" +AkanePic:: INCBIN "gfx/trainer/akane.pic" ; Gen 1 Bug Catcher +TsukushiPic:: INCBIN "gfx/trainer/tsukushi.pic" +EnokiPic:: INCBIN "gfx/trainer/enoki.pic" +OkeraPic:: INCBIN "gfx/trainer/okera.pic" ; Gen 1 Police Female +MikanPic:: INCBIN "gfx/trainer/mikan.pic" +BluePic:: INCBIN "gfx/trainer/blue.pic" ; Gen 1 Pokemaniac +GamaPic:: INCBIN "gfx/trainer/gama.pic" ; Gen 1 Super Nerd +RivalPic:: INCBIN "gfx/trainer/rival.pic" +OakPic:: INCBIN "gfx/trainer/oak.pic" +ProtagonistPic:: INCBIN "gfx/trainer/protagonist.pic" +KurtPic:: INCBIN "gfx/trainer/kurt.pic" +YoungsterPic:: INCBIN "gfx/trainer/youngster.pic" +SchoolboyPic:: INCBIN "gfx/trainer/schoolboy.pic" +FledglingPic:: INCBIN "gfx/trainer/fledgling.pic" +LassPic:: INCBIN "gfx/trainer/lass.pic" +ProfessionalMPic:: INCBIN "gfx/trainer/professional_m.pic" +ProfessionalFPic:: INCBIN "gfx/trainer/professional_f.pic" +BeautyPic:: INCBIN "gfx/trainer/beauty.pic" +PokemaniacPic:: INCBIN "gfx/trainer/pokemaniac.pic" +RocketMPic:: INCBIN "gfx/trainer/rocket_m.pic" +TeacherMPic:: INCBIN "gfx/trainer/teacher_m.pic" +TeacherFPic:: INCBIN "gfx/trainer/teacher_f.pic" +BugCatcherBoyPic:: INCBIN "gfx/trainer/bug_catcher_boy.pic" +FisherPic:: INCBIN "gfx/trainer/fisher.pic" +SwimmerMPic:: INCBIN "gfx/trainer/swimmer_m.pic" +SwimmerFPic:: INCBIN "gfx/trainer/swimmer_f.pic" +SuperNerdPic:: INCBIN "gfx/trainer/supernerd.pic" +EngineerPic:: INCBIN "gfx/trainer/engineer.pic" +GreenPic:: INCBIN "gfx/trainer/green.pic" ; Gen 1 Green +BikerPic:: INCBIN "gfx/trainer/biker.pic" +BurglarPic:: INCBIN "gfx/trainer/burglar.pic" +FirebreatherPic:: INCBIN "gfx/trainer/firebreather.pic" +JugglerPic:: INCBIN "gfx/trainer/juggler.pic" +BlackbeltPic:: INCBIN "gfx/trainer/blackbelt.pic" +SportsmanPic:: INCBIN "gfx/trainer/sportsman.pic" +MediumPic:: INCBIN "gfx/trainer/medium.pic" +SoldierPic:: INCBIN "gfx/trainer/soldier.pic" +KimonoGirlPic:: INCBIN "gfx/trainer/kimonogirl.pic" +TwinsPic:: INCBIN "gfx/trainer/twins.pic" + + +SECTION "gfx.asm@Bank 13 Tilesets 0a", ROMX +Tileset_0a_GFX: +INCBIN "gfx/tilesets/tileset_0a.2bpp" +Tileset_0a_Meta: +INCBIN "data/tilesets/tileset_0a_metatiles.bin" +Tileset_0a_Coll: +INCBIN "data/tilesets/tileset_0a_collision.bin" + +SECTION "gfx.asm@Bank 13 Tilesets 16", ROMX +Tileset_16_GFX: +INCBIN "gfx/tilesets/tileset_16.2bpp" +Tileset_16_Meta: +INCBIN "data/tilesets/tileset_16_metatiles.bin" +Tileset_16_Coll: +INCBIN "data/tilesets/tileset_16_collision.bin" + +SECTION "gfx.asm@Bank 13 Tilesets 19", ROMX +Tileset_19_GFX: +INCBIN "gfx/tilesets/tileset_19.2bpp" +Tileset_19_Meta: +INCBIN "data/tilesets/tileset_19_metatiles.bin" +Tileset_19_Coll: +INCBIN "data/tilesets/tileset_19_collision.bin" + +SECTION "gfx.asm@Bank 13 Tilesets 1a", ROMX +Tileset_1a_GFX: +INCBIN "gfx/tilesets/tileset_1a.2bpp" +Tileset_1a_Meta: +INCBIN "data/tilesets/tileset_1a_metatiles.bin" +Tileset_1a_Coll: +INCBIN "data/tilesets/tileset_1a_collision.bin" + +SECTION "gfx.asm@PKMN Sprite Bank List", ROMX +INCLUDE "gfx/pokemon/pkmn_pic_banks.inc" + +INCLUDE "gfx/pokemon/pkmn_pics.inc" + + +SECTION "gfx.asm@Annon Pic Ptrs and Pics", ROMX +INCLUDE "gfx/pokemon/annon_pic_ptrs.inc" +INCLUDE "gfx/pokemon/annon_pics.inc" + +INCLUDE "gfx/pokemon/egg.inc" + +SECTION "gfx.asm@Attack Animation GFX", ROMX + +INCBIN "gfx/battle_anims/attack_animations_1.2bpp" +PointerGFX:: +INCBIN "gfx/battle_anims/pointer.2bpp" +INCBIN "gfx/battle_anims/attack_animations_2.2bpp" + +SECTION "gfx.asm@Pokemon Party Sprites", ROMX +NyoromoIcon:: INCBIN "gfx/icons/nyoromo.2bpp" +PurinIcon:: INCBIN "gfx/icons/purin.2bpp" +DigdaIcon:: INCBIN "gfx/icons/digda.2bpp" +PikachuIcon:: INCBIN "gfx/icons/pikachu.2bpp" +HitodemanIcon:: INCBIN "gfx/icons/hitodeman.2bpp" +KoikingIcon:: INCBIN "gfx/icons/koiking.2bpp" +PoppoIcon:: INCBIN "gfx/icons/poppo.2bpp" +SidonIcon:: INCBIN "gfx/icons/sidon.2bpp" +PippiIcon:: INCBIN "gfx/icons/pippi.2bpp" +NazonokusaIcon:: INCBIN "gfx/icons/nazonokusa.2bpp" +MushiIcon:: INCBIN "gfx/icons/mushi.2bpp" +GangarIcon:: INCBIN "gfx/icons/gangar.2bpp" +LaplaceIcon:: INCBIN "gfx/icons/laplace.2bpp" +BarrierdIcon:: INCBIN "gfx/icons/barrierd.2bpp" +LokonIcon:: INCBIN "gfx/icons/lokon.2bpp" +KentaurosIcon:: INCBIN "gfx/icons/kentauros.2bpp" +ShellderIcon:: INCBIN "gfx/icons/shellder.2bpp" +MetamonIcon:: INCBIN "gfx/icons/metamon.2bpp" +IwarkIcon:: INCBIN "gfx/icons/iwark.2bpp" +BiriridamaIcon:: INCBIN "gfx/icons/biriridama.2bpp" +ZenigameIcon:: INCBIN "gfx/icons/zenigame.2bpp" +FushigidaneIcon:: INCBIN "gfx/icons/fushigidane.2bpp" +HitokageIcon:: INCBIN "gfx/icons/hitokage.2bpp" +BeedleIcon:: INCBIN "gfx/icons/beedle.2bpp" +AnnonIcon:: INCBIN "gfx/icons/annon.2bpp" +IsitsubuteIcon:: INCBIN "gfx/icons/isitsubute.2bpp" +WanrikyIcon:: INCBIN "gfx/icons/wanriky.2bpp" +EggIcon:: INCBIN "gfx/icons/egg.2bpp" +MenokurageIcon:: INCBIN "gfx/icons/menokurage.2bpp" +ButterfreeIcon:: INCBIN "gfx/icons/butterfree.2bpp" +ZubatIcon:: INCBIN "gfx/icons/zubat.2bpp" +KabigonIcon:: INCBIN "gfx/icons/kabigon.2bpp" + +SECTION "gfx.asm@Slot Machine GFX", ROMX +SlotMachineGFX:: +INCBIN "gfx/minigames/slots.2bpp" +SlotMachine2GFX:: +INCBIN "gfx/minigames/slots_2.2bpp" + +SECTION "gfx.asm@Bank 30 Sprites 1", ROMX +GoldSpriteGFX:: INCBIN "gfx/sprites/gold.2bpp" +GoldBikeSpriteGFX:: INCBIN "gfx/sprites/gold_bike.2bpp" +GoldSkateboardSpriteGFX:: INCBIN "gfx/sprites/gold_skateboard.2bpp" +SilverSpriteGFX:: INCBIN "gfx/sprites/silver.2bpp" +OkidoSpriteGFX:: INCBIN "gfx/sprites/okido.2bpp" +RedSpriteGFX:: INCBIN "gfx/sprites/red.2bpp" +BlueSpriteGFX:: INCBIN "gfx/sprites/blue.2bpp" +MasakiSpriteGFX:: INCBIN "gfx/sprites/masaki.2bpp" +ElderSpriteGFX:: INCBIN "gfx/sprites/elder.2bpp" +SakakiSpriteGFX:: INCBIN "gfx/sprites/sakaki.2bpp" +GantetsuSpriteGFX:: INCBIN "gfx/sprites/gantetsu.2bpp" +MomSpriteGFX:: INCBIN "gfx/sprites/mom.2bpp" +SilversMomSpriteGFX:: INCBIN "gfx/sprites/silvers_mom.2bpp" +RedsMomSpriteGFX:: INCBIN "gfx/sprites/reds_mom.2bpp" +NanamiSpriteGFX:: INCBIN "gfx/sprites/nanami.2bpp" +EvilOkidoSpriteGFX:: INCBIN "gfx/sprites/evil_okido.2bpp" +KikukoSpriteGFX:: INCBIN "gfx/sprites/kikuko.2bpp" +HayatoSpriteGFX:: INCBIN "gfx/sprites/hayato.2bpp" +TsukushiSpriteGFX:: INCBIN "gfx/sprites/tsukushi.2bpp" +EnokiSpriteGFX:: INCBIN "gfx/sprites/enoki.2bpp" +MikanSpriteGFX:: INCBIN "gfx/sprites/mikan.2bpp" +CooltrainerMSpriteGFX:: INCBIN "gfx/sprites/cooltrainer_m.2bpp" +CooltrainerFSpriteGFX:: INCBIN "gfx/sprites/cooltrainer_f.2bpp" +BugCatcherBoySpriteGFX:: INCBIN "gfx/sprites/bug_catcher_boy.2bpp" +TwinSpriteGFX:: INCBIN "gfx/sprites/twin.2bpp" +YoungsterSpriteGFX:: INCBIN "gfx/sprites/youngster.2bpp" +LassSpriteGFX:: INCBIN "gfx/sprites/lass.2bpp" +TeacherSpriteGFX:: INCBIN "gfx/sprites/teacher.2bpp" +GirlSpriteGFX:: INCBIN "gfx/sprites/girl.2bpp" +SuperNerdSpriteGFX:: INCBIN "gfx/sprites/super_nerd.2bpp" +RockerSpriteGFX:: INCBIN "gfx/sprites/rocker.2bpp" +PokefanMSpriteGFX:: INCBIN "gfx/sprites/pokefan_m.2bpp" +PokefanFSpriteGFX:: INCBIN "gfx/sprites/pokefan_f.2bpp" +GrampsSpriteGFX:: INCBIN "gfx/sprites/gramps.2bpp" +GrannySpriteGFX:: INCBIN "gfx/sprites/granny.2bpp" +SwimmerMSpriteGFX:: INCBIN "gfx/sprites/swimmer_m.2bpp" +SwimmerFSpriteGFX:: INCBIN "gfx/sprites/swimmer_f.2bpp" +RocketMSpriteGFX:: INCBIN "gfx/sprites/rocket_m.2bpp" +RocketFSpriteGFX:: INCBIN "gfx/sprites/rocket_f.2bpp" +NurseSpriteGFX:: INCBIN "gfx/sprites/nurse.2bpp" +LinkReceptionistSpriteGFX:: INCBIN "gfx/sprites/link_receptionist.2bpp" +ClerkSpriteGFX:: INCBIN "gfx/sprites/clerk.2bpp" +FisherSpriteGFX:: INCBIN "gfx/sprites/fisher.2bpp" +FishingGuruSpriteGFX:: INCBIN "gfx/sprites/fishing_guru.2bpp" + +SECTION "gfx.asm@Bank 31 Sprites 2", ROMX +ScientistSpriteGFX:: INCBIN "gfx/sprites/scientist.2bpp" +MediumSpriteGFX:: INCBIN "gfx/sprites/medium.2bpp" +SageSpriteGFX:: INCBIN "gfx/sprites/sage.2bpp" +FrowningManSpriteGFX:: INCBIN "gfx/sprites/frowning_man.2bpp" +GentlemanSpriteGFX:: INCBIN "gfx/sprites/gentleman.2bpp" +BlackbeltSpriteGFX:: INCBIN "gfx/sprites/blackbelt.2bpp" +ReceptionistSpriteGFX:: INCBIN "gfx/sprites/receptionist.2bpp" +OfficerSpriteGFX:: INCBIN "gfx/sprites/officer.2bpp" +CaptainSpriteGFX:: INCBIN "gfx/sprites/captain.2bpp" +MohawkSpriteGFX:: INCBIN "gfx/sprites/mohawk.2bpp" +GymGuySpriteGFX:: INCBIN "gfx/sprites/gym_guy.2bpp" +SailorSpriteGFX:: INCBIN "gfx/sprites/sailor.2bpp" +HelmetSpriteGFX:: INCBIN "gfx/sprites/helmet.2bpp" +BurglarSpriteGFX:: INCBIN "gfx/sprites/burglar.2bpp" +SidonSpriteGFX:: INCBIN "gfx/sprites/sidon.2bpp" +PippiSpriteGFX:: INCBIN "gfx/sprites/pippi.2bpp" +PoppoSpriteGFX:: INCBIN "gfx/sprites/poppo.2bpp" +LizardonSpriteGFX:: INCBIN "gfx/sprites/lizardon.2bpp" +KabigonSpriteGFX:: INCBIN "gfx/sprites/kabigon.2bpp" +PawouSpriteGFX:: INCBIN "gfx/sprites/pawou.2bpp" +NyorobonSpriteGFX:: INCBIN "gfx/sprites/nyorobon.2bpp" +LaplaceSpriteGFX:: INCBIN "gfx/sprites/laplace.2bpp" +PokeBallSpriteGFX:: INCBIN "gfx/sprites/poke_ball.2bpp" +PokedexSpriteGFX:: INCBIN "gfx/sprites/pokedex.2bpp" +PaperSpriteGFX:: INCBIN "gfx/sprites/paper.2bpp" +OldLinkReceptionistSpriteGFX:: INCBIN "gfx/sprites/old_link_receptionist.2bpp" +EggSpriteGFX:: INCBIN "gfx/sprites/egg.2bpp" +BoulderSpriteGFX:: INCBIN "gfx/sprites/boulder.2bpp" + +SECTION "gfx.asm@Bank 37 Tilesets 10", ROMX +Tileset_10_GFX: +INCBIN "gfx/tilesets/tileset_10.2bpp" +Tileset_10_Meta: +INCBIN "data/tilesets/tileset_10_metatiles.bin" +Tileset_10_Coll: +INCBIN "data/tilesets/tileset_10_collision.bin" + +SECTION "gfx.asm@Bank 37 Tilesets 15", ROMX +Tileset_15_GFX: +INCBIN "gfx/tilesets/tileset_15.2bpp" +Tileset_15_Meta: +INCBIN "data/tilesets/tileset_15_metatiles.bin" +Tileset_15_Coll: +INCBIN "data/tilesets/tileset_15_collision.bin" + +SECTION "gfx.asm@Bank 37 Tilesets 17", ROMX +Tileset_17_GFX: +INCBIN "gfx/tilesets/tileset_17.2bpp" +Tileset_17_Meta: +INCBIN "data/tilesets/tileset_17_metatiles.bin" +Tileset_17_Coll: +INCBIN "data/tilesets/tileset_17_collision.bin" + +SECTION "gfx.asm@Bank 37 Tilesets 18", ROMX +Tileset_18_GFX: +INCBIN "gfx/tilesets/tileset_18.2bpp" +Tileset_18_Meta: +INCBIN "data/tilesets/tileset_18_metatiles.bin" +Tileset_18_Coll: +INCBIN "data/tilesets/tileset_18_collision.bin" + +SECTION "gfx.asm@Poker GFX", ROMX +PokerGFX:: +INCBIN "gfx/minigames/poker.2bpp" + +SECTION "gfx.asm@15 Puzzle GFX", ROMX +FifteenPuzzleGFX:: +INCBIN "gfx/minigames/15_puzzle.2bpp" + +SECTION "gfx.asm@Matches GFX", ROMX +MemoryGameGFX:: +INCBIN "gfx/minigames/matches.2bpp" + +SECTION "gfx.asm@Picross GFX", ROMX +PicrossGFX:: +INCBIN "gfx/minigames/picross.2bpp" +PicrossCursorGFX:: +INCBIN "gfx/minigames/picross_cursor.2bpp" + +SECTION "gfx.asm@Gamefreak Logo GFX", ROMX +GameFreakLogoGFX:: +INCBIN "gfx/splash/game_freak_logo.1bpp" +GameFreakLogoSparkleGFX:: +INCBIN "gfx/splash/game_freak_logo_oam.2bpp" + +SECTION "gfx.asm@Intro Underwater GFX", ROMX +IntroUnderwaterGFX:: +INCBIN "gfx/intro/underwater.2bpp" + +SECTION "gfx.asm@Intro Water Mon and Forest GFX", ROMX +IntroWaterPokemonGFX:: +INCBIN "gfx/intro/water_pokemon.2bpp" +IntroForestGFX:: +INCBIN "gfx/intro/forest.2bpp" + +SECTION "gfx.asm@Intro Mon", ROMX +IntroPurinPikachuGFX:: +INCBIN "gfx/intro/purin_pikachu.2bpp" +IntroLizardon1GFX:: +INCBIN "gfx/intro/lizardon_1.2bpp" +IntroLizardon2GFX:: +INCBIN "gfx/intro/lizardon_2.2bpp" +IntroLizardon3GFX:: +INCBIN "gfx/intro/lizardon_3.2bpp" +IntroLizardonFlamesGFX:: +INCBIN "gfx/intro/lizardon_flames.2bpp" +IntroKamexGFX:: +INCBIN "gfx/intro/kamex.2bpp" +IntroFushigibanaGFX:: +INCBIN "gfx/intro/fushigibana.2bpp" + +SECTION "gfx.asm@Misc GFX", ROMX +FontExtraGFX:: +FontExtraAB_GFX:: INCBIN "gfx/font/font_extra.ab.2bpp" +FontExtraCDEFGHIVSLM_GFX:: INCBIN "gfx/font/font_extra.cdefghivslm.2bpp" +FontSmallKanaPunctuationGFX:: INCBIN "gfx/font/small_kana_punctuation.2bpp" +.End:: +Unreferenced_DefaultFrame0GFX:: INCBIN "gfx/frames/1.2bpp" +FontGFX:: INCBIN "gfx/font/font.1bpp" +.End:: +FontBattleExtraGFX:: +BattleHPBarGFX:: INCBIN "gfx/battle/hp_bar.2bpp" +.End:: +HpExpBarParts0_2bppGFX:: INCBIN "gfx/battle/hp_exp_bar_parts0.2bpp" +BattleMarkersGFX:: INCBIN "gfx/battle/markers.2bpp" +.End:: +LevelUpGFX:: INCBIN "gfx/battle/levelup.2bpp" +.End:: + +Unreferenced_DefaultFrame1:: INCBIN "gfx/frames/1.2bpp" + +FrameGFX:: +INCBIN "gfx/frames/1.1bpp" +.FirstEntryEnd:: +INCBIN "gfx/frames/2.1bpp" +INCBIN "gfx/frames/3.1bpp" +INCBIN "gfx/frames/4.1bpp" +INCBIN "gfx/frames/5.1bpp" +INCBIN "gfx/frames/6.1bpp" +INCBIN "gfx/frames/7.1bpp" +INCBIN "gfx/frames/8.1bpp" +INCBIN "gfx/frames/9.1bpp" + +StatsGFX:: +INCBIN "gfx/stats/separator.2bpp" +INCBIN "gfx/stats/stats.2bpp" +.End:: + +HpExpBarParts0GFX:: INCBIN "gfx/battle/hp_exp_bar_parts0.1bpp" +.End:: +HpExpBarParts1GFX:: INCBIN "gfx/battle/hp_exp_bar_parts1.1bpp" +.End:: +HpExpBarParts2GFX:: INCBIN "gfx/battle/hp_exp_bar_parts2.1bpp" +.End:: +HpExpBarParts3GFX:: INCBIN "gfx/battle/hp_exp_bar_parts3.1bpp" +.End:: +ExpBarGFX:: INCBIN "gfx/battle/exp_bar.2bpp" +.End:: +PokedexGFX:: INCBIN "gfx/pokedex/pokedex.2bpp" +.End:: +PokedexLocationGFX:: INCBIN "gfx/pokedex/locations.2bpp" +.End:: +TownMapGFX:: INCBIN "gfx/trainer_gear/town_map.2bpp" +.End:: +HUD_GFX:: INCBIN "gfx/hud/hud.2bpp" +.End:: +BoldAlphabetGFX:: INCBIN "gfx/font/alphabet.1bpp" +AnnonAlphabetGFX:: INCBIN "gfx/font/annon_alphabet.1bpp" +EmptyTile1bppGFX:: INCBIN "gfx/misc/empty_tile.1bpp" +.End:: +BlackTileAndCursor1bppGFX:: INCBIN "gfx/misc/black_tile_cursor.1bpp" +.End:: +PackIconGFX:: INCBIN "gfx/pack/pack_icons.2bpp" +.End:: + +SECTION "gfx.asm@Town Map Cursor", ROMX +TownMapCursorGFX:: +INCBIN "gfx/trainer_gear/town_map_cursor.2bpp" diff --git a/gfx/pokemon/annon_pic_ptrs.asm b/gfx/pokemon/annon_pic_ptrs.asm deleted file mode 100644 index dae3fd0..0000000 --- a/gfx/pokemon/annon_pic_ptrs.asm +++ /dev/null @@ -1,55 +0,0 @@ -AnnonPicPtrs:: -AnnonPicPtrsFront:: - dw AnnonAPicFront -AnnonPicPtrsBack:: - dw AnnonAPicBack - dw AnnonBPicFront - dw AnnonBPicBack - dw AnnonCPicFront - dw AnnonCPicBack - dw AnnonDPicFront - dw AnnonDPicBack - dw AnnonEPicFront - dw AnnonEPicBack - dw AnnonFPicFront - dw AnnonFPicBack - dw AnnonGPicFront - dw AnnonGPicBack - dw AnnonHPicFront - dw AnnonHPicBack - dw AnnonIPicFront - dw AnnonIPicBack - dw AnnonJPicFront - dw AnnonJPicBack - dw AnnonKPicFront - dw AnnonKPicBack - dw AnnonLPicFront - dw AnnonLPicBack - dw AnnonMPicFront - dw AnnonMPicBack - dw AnnonNPicFront - dw AnnonNPicBack - dw AnnonOPicFront - dw AnnonOPicBack - dw AnnonPPicFront - dw AnnonPPicBack - dw AnnonQPicFront - dw AnnonQPicBack - dw AnnonRPicFront - dw AnnonRPicBack - dw AnnonSPicFront - dw AnnonSPicBack - dw AnnonTPicFront - dw AnnonTPicBack - dw AnnonUPicFront - dw AnnonUPicBack - dw AnnonVPicFront - dw AnnonVPicBack - dw AnnonWPicFront - dw AnnonWPicBack - dw AnnonXPicFront - dw AnnonXPicBack - dw AnnonYPicFront - dw AnnonYPicBack - dw AnnonZPicFront - dw AnnonYPicBack ; typo in original list, should be AnnonZPicBack diff --git a/gfx/pokemon/annon_pic_ptrs.inc b/gfx/pokemon/annon_pic_ptrs.inc new file mode 100644 index 0000000..dae3fd0 --- /dev/null +++ b/gfx/pokemon/annon_pic_ptrs.inc @@ -0,0 +1,55 @@ +AnnonPicPtrs:: +AnnonPicPtrsFront:: + dw AnnonAPicFront +AnnonPicPtrsBack:: + dw AnnonAPicBack + dw AnnonBPicFront + dw AnnonBPicBack + dw AnnonCPicFront + dw AnnonCPicBack + dw AnnonDPicFront + dw AnnonDPicBack + dw AnnonEPicFront + dw AnnonEPicBack + dw AnnonFPicFront + dw AnnonFPicBack + dw AnnonGPicFront + dw AnnonGPicBack + dw AnnonHPicFront + dw AnnonHPicBack + dw AnnonIPicFront + dw AnnonIPicBack + dw AnnonJPicFront + dw AnnonJPicBack + dw AnnonKPicFront + dw AnnonKPicBack + dw AnnonLPicFront + dw AnnonLPicBack + dw AnnonMPicFront + dw AnnonMPicBack + dw AnnonNPicFront + dw AnnonNPicBack + dw AnnonOPicFront + dw AnnonOPicBack + dw AnnonPPicFront + dw AnnonPPicBack + dw AnnonQPicFront + dw AnnonQPicBack + dw AnnonRPicFront + dw AnnonRPicBack + dw AnnonSPicFront + dw AnnonSPicBack + dw AnnonTPicFront + dw AnnonTPicBack + dw AnnonUPicFront + dw AnnonUPicBack + dw AnnonVPicFront + dw AnnonVPicBack + dw AnnonWPicFront + dw AnnonWPicBack + dw AnnonXPicFront + dw AnnonXPicBack + dw AnnonYPicFront + dw AnnonYPicBack + dw AnnonZPicFront + dw AnnonYPicBack ; typo in original list, should be AnnonZPicBack diff --git a/gfx/pokemon/annon_pics.asm b/gfx/pokemon/annon_pics.asm deleted file mode 100644 index fa1c25b..0000000 --- a/gfx/pokemon/annon_pics.asm +++ /dev/null @@ -1,53 +0,0 @@ -AnnonPics:: -AnnonAPicFront:: INCBIN "gfx/pokemon/annon_a/front.pic" -AnnonAPicBack:: INCBIN "gfx/pokemon/annon_a/back.pic" -AnnonBPicFront:: INCBIN "gfx/pokemon/annon_b/front.pic" -AnnonBPicBack:: INCBIN "gfx/pokemon/annon_b/back.pic" -AnnonCPicFront:: INCBIN "gfx/pokemon/annon_c/front.pic" -AnnonCPicBack:: INCBIN "gfx/pokemon/annon_c/back.pic" -AnnonDPicFront:: INCBIN "gfx/pokemon/annon_d/front.pic" -AnnonDPicBack:: INCBIN "gfx/pokemon/annon_d/back.pic" -AnnonEPicFront:: INCBIN "gfx/pokemon/annon_e/front.pic" -AnnonEPicBack:: INCBIN "gfx/pokemon/annon_e/back.pic" -AnnonFPicFront:: INCBIN "gfx/pokemon/annon_f/front.pic" -AnnonFPicBack:: INCBIN "gfx/pokemon/annon_f/back.pic" -AnnonGPicFront:: INCBIN "gfx/pokemon/annon_g/front.pic" -AnnonGPicBack:: INCBIN "gfx/pokemon/annon_g/back.pic" -AnnonHPicFront:: INCBIN "gfx/pokemon/annon_h/front.pic" -AnnonHPicBack:: INCBIN "gfx/pokemon/annon_h/back.pic" -AnnonIPicFront:: INCBIN "gfx/pokemon/annon_i/front.pic" -AnnonIPicBack:: INCBIN "gfx/pokemon/annon_i/back.pic" -AnnonJPicFront:: INCBIN "gfx/pokemon/annon_j/front.pic" -AnnonJPicBack:: INCBIN "gfx/pokemon/annon_j/back.pic" -AnnonKPicFront:: INCBIN "gfx/pokemon/annon_k/front.pic" -AnnonKPicBack:: INCBIN "gfx/pokemon/annon_k/back.pic" -AnnonLPicFront:: INCBIN "gfx/pokemon/annon_l/front.pic" -AnnonLPicBack:: INCBIN "gfx/pokemon/annon_l/back.pic" -AnnonMPicFront:: INCBIN "gfx/pokemon/annon_m/front.pic" -AnnonMPicBack:: INCBIN "gfx/pokemon/annon_m/back.pic" -AnnonNPicFront:: INCBIN "gfx/pokemon/annon_n/front.pic" -AnnonNPicBack:: INCBIN "gfx/pokemon/annon_n/back.pic" -AnnonOPicFront:: INCBIN "gfx/pokemon/annon_o/front.pic" -AnnonOPicBack:: INCBIN "gfx/pokemon/annon_o/back.pic" -AnnonPPicFront:: INCBIN "gfx/pokemon/annon_p/front.pic" -AnnonPPicBack:: INCBIN "gfx/pokemon/annon_p/back.pic" -AnnonQPicFront:: INCBIN "gfx/pokemon/annon_q/front.pic" -AnnonQPicBack:: INCBIN "gfx/pokemon/annon_q/back.pic" -AnnonRPicFront:: INCBIN "gfx/pokemon/annon_r/front.pic" -AnnonRPicBack:: INCBIN "gfx/pokemon/annon_r/back.pic" -AnnonSPicFront:: INCBIN "gfx/pokemon/annon_s/front.pic" -AnnonSPicBack:: INCBIN "gfx/pokemon/annon_s/back.pic" -AnnonTPicFront:: INCBIN "gfx/pokemon/annon_t/front.pic" -AnnonTPicBack:: INCBIN "gfx/pokemon/annon_t/back.pic" -AnnonUPicFront:: INCBIN "gfx/pokemon/annon_u/front.pic" -AnnonUPicBack:: INCBIN "gfx/pokemon/annon_u/back.pic" -AnnonVPicFront:: INCBIN "gfx/pokemon/annon_v/front.pic" -AnnonVPicBack:: INCBIN "gfx/pokemon/annon_v/back.pic" -AnnonWPicFront:: INCBIN "gfx/pokemon/annon_w/front.pic" -AnnonWPicBack:: INCBIN "gfx/pokemon/annon_w/back.pic" -AnnonXPicFront:: INCBIN "gfx/pokemon/annon_x/front.pic" -AnnonXPicBack:: INCBIN "gfx/pokemon/annon_x/back.pic" -AnnonYPicFront:: INCBIN "gfx/pokemon/annon_y/front.pic" -AnnonYPicBack:: INCBIN "gfx/pokemon/annon_y/back.pic" -AnnonZPicFront:: INCBIN "gfx/pokemon/annon_z/front.pic" -AnnonZPicBack:: INCBIN "gfx/pokemon/annon_z/back.pic" diff --git a/gfx/pokemon/annon_pics.inc b/gfx/pokemon/annon_pics.inc new file mode 100644 index 0000000..fa1c25b --- /dev/null +++ b/gfx/pokemon/annon_pics.inc @@ -0,0 +1,53 @@ +AnnonPics:: +AnnonAPicFront:: INCBIN "gfx/pokemon/annon_a/front.pic" +AnnonAPicBack:: INCBIN "gfx/pokemon/annon_a/back.pic" +AnnonBPicFront:: INCBIN "gfx/pokemon/annon_b/front.pic" +AnnonBPicBack:: INCBIN "gfx/pokemon/annon_b/back.pic" +AnnonCPicFront:: INCBIN "gfx/pokemon/annon_c/front.pic" +AnnonCPicBack:: INCBIN "gfx/pokemon/annon_c/back.pic" +AnnonDPicFront:: INCBIN "gfx/pokemon/annon_d/front.pic" +AnnonDPicBack:: INCBIN "gfx/pokemon/annon_d/back.pic" +AnnonEPicFront:: INCBIN "gfx/pokemon/annon_e/front.pic" +AnnonEPicBack:: INCBIN "gfx/pokemon/annon_e/back.pic" +AnnonFPicFront:: INCBIN "gfx/pokemon/annon_f/front.pic" +AnnonFPicBack:: INCBIN "gfx/pokemon/annon_f/back.pic" +AnnonGPicFront:: INCBIN "gfx/pokemon/annon_g/front.pic" +AnnonGPicBack:: INCBIN "gfx/pokemon/annon_g/back.pic" +AnnonHPicFront:: INCBIN "gfx/pokemon/annon_h/front.pic" +AnnonHPicBack:: INCBIN "gfx/pokemon/annon_h/back.pic" +AnnonIPicFront:: INCBIN "gfx/pokemon/annon_i/front.pic" +AnnonIPicBack:: INCBIN "gfx/pokemon/annon_i/back.pic" +AnnonJPicFront:: INCBIN "gfx/pokemon/annon_j/front.pic" +AnnonJPicBack:: INCBIN "gfx/pokemon/annon_j/back.pic" +AnnonKPicFront:: INCBIN "gfx/pokemon/annon_k/front.pic" +AnnonKPicBack:: INCBIN "gfx/pokemon/annon_k/back.pic" +AnnonLPicFront:: INCBIN "gfx/pokemon/annon_l/front.pic" +AnnonLPicBack:: INCBIN "gfx/pokemon/annon_l/back.pic" +AnnonMPicFront:: INCBIN "gfx/pokemon/annon_m/front.pic" +AnnonMPicBack:: INCBIN "gfx/pokemon/annon_m/back.pic" +AnnonNPicFront:: INCBIN "gfx/pokemon/annon_n/front.pic" +AnnonNPicBack:: INCBIN "gfx/pokemon/annon_n/back.pic" +AnnonOPicFront:: INCBIN "gfx/pokemon/annon_o/front.pic" +AnnonOPicBack:: INCBIN "gfx/pokemon/annon_o/back.pic" +AnnonPPicFront:: INCBIN "gfx/pokemon/annon_p/front.pic" +AnnonPPicBack:: INCBIN "gfx/pokemon/annon_p/back.pic" +AnnonQPicFront:: INCBIN "gfx/pokemon/annon_q/front.pic" +AnnonQPicBack:: INCBIN "gfx/pokemon/annon_q/back.pic" +AnnonRPicFront:: INCBIN "gfx/pokemon/annon_r/front.pic" +AnnonRPicBack:: INCBIN "gfx/pokemon/annon_r/back.pic" +AnnonSPicFront:: INCBIN "gfx/pokemon/annon_s/front.pic" +AnnonSPicBack:: INCBIN "gfx/pokemon/annon_s/back.pic" +AnnonTPicFront:: INCBIN "gfx/pokemon/annon_t/front.pic" +AnnonTPicBack:: INCBIN "gfx/pokemon/annon_t/back.pic" +AnnonUPicFront:: INCBIN "gfx/pokemon/annon_u/front.pic" +AnnonUPicBack:: INCBIN "gfx/pokemon/annon_u/back.pic" +AnnonVPicFront:: INCBIN "gfx/pokemon/annon_v/front.pic" +AnnonVPicBack:: INCBIN "gfx/pokemon/annon_v/back.pic" +AnnonWPicFront:: INCBIN "gfx/pokemon/annon_w/front.pic" +AnnonWPicBack:: INCBIN "gfx/pokemon/annon_w/back.pic" +AnnonXPicFront:: INCBIN "gfx/pokemon/annon_x/front.pic" +AnnonXPicBack:: INCBIN "gfx/pokemon/annon_x/back.pic" +AnnonYPicFront:: INCBIN "gfx/pokemon/annon_y/front.pic" +AnnonYPicBack:: INCBIN "gfx/pokemon/annon_y/back.pic" +AnnonZPicFront:: INCBIN "gfx/pokemon/annon_z/front.pic" +AnnonZPicBack:: INCBIN "gfx/pokemon/annon_z/back.pic" diff --git a/gfx/pokemon/egg.asm b/gfx/pokemon/egg.asm deleted file mode 100644 index a5a38fb..0000000 --- a/gfx/pokemon/egg.asm +++ /dev/null @@ -1,2 +0,0 @@ -SECTION "gfx/pokemon/egg.asm", ROMX -EggPicFront:: INCBIN "gfx/pokemon/egg/front.pic" diff --git a/gfx/pokemon/egg.inc b/gfx/pokemon/egg.inc new file mode 100644 index 0000000..a5a38fb --- /dev/null +++ b/gfx/pokemon/egg.inc @@ -0,0 +1,2 @@ +SECTION "gfx/pokemon/egg.asm", ROMX +EggPicFront:: INCBIN "gfx/pokemon/egg/front.pic" diff --git a/gfx/pokemon/pkmn_pic_banks.asm b/gfx/pokemon/pkmn_pic_banks.asm deleted file mode 100644 index dd0fc42..0000000 --- a/gfx/pokemon/pkmn_pic_banks.asm +++ /dev/null @@ -1,14 +0,0 @@ -MonSpriteBankList:: - ; last mon in bank, bank # - db DEX_RAICHU, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 0 - db DEX_DUGTRIO, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 1 - db DEX_GOLONE, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 2 - db DEX_CRAB, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 3 - db DEX_STARMIE, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 4 - db DEX_FREEZER, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 5 - db DEX_JARANRA, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 6 - db DEX_KOUNYA, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 7 - db DEX_BOMBSEEKER, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 8 - db DEX_NYULA, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 9 - db $ff, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 10 - db $ff, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 11 diff --git a/gfx/pokemon/pkmn_pic_banks.inc b/gfx/pokemon/pkmn_pic_banks.inc new file mode 100644 index 0000000..dd0fc42 --- /dev/null +++ b/gfx/pokemon/pkmn_pic_banks.inc @@ -0,0 +1,14 @@ +MonSpriteBankList:: + ; last mon in bank, bank # + db DEX_RAICHU, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 0 + db DEX_DUGTRIO, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 1 + db DEX_GOLONE, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 2 + db DEX_CRAB, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 3 + db DEX_STARMIE, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 4 + db DEX_FREEZER, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 5 + db DEX_JARANRA, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 6 + db DEX_KOUNYA, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 7 + db DEX_BOMBSEEKER, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 8 + db DEX_NYULA, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 9 + db $ff, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 10 + db $ff, BANK("gfx/pokemon/pkmn_pics.asm@PKMN Pics 1") + 11 diff --git a/gfx/pokemon/pkmn_pics.asm b/gfx/pokemon/pkmn_pics.asm deleted file mode 100644 index 178b738..0000000 --- a/gfx/pokemon/pkmn_pics.asm +++ /dev/null @@ -1,543 +0,0 @@ -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 1", ROMX - -FushigidanePicFront:: INCBIN "gfx/pokemon/fushigidane/front.pic" -FushigidanePicBack:: INCBIN "gfx/pokemon/fushigidane/back.pic" -FushigisouPicFront:: INCBIN "gfx/pokemon/fushigisou/front.pic" -FushigisouPicBack:: INCBIN "gfx/pokemon/fushigisou/back.pic" -FushigibanaPicFront:: INCBIN "gfx/pokemon/fushigibana/front.pic" -FushigibanaPicBack:: INCBIN "gfx/pokemon/fushigibana/back.pic" -HitokagePicFront:: INCBIN "gfx/pokemon/hitokage/front.pic" -HitokagePicBack:: INCBIN "gfx/pokemon/hitokage/back.pic" -LizardoPicFront:: INCBIN "gfx/pokemon/lizardo/front.pic" -LizardoPicBack:: INCBIN "gfx/pokemon/lizardo/back.pic" -LizardonPicFront:: INCBIN "gfx/pokemon/lizardon/front.pic" -LizardonPicBack:: INCBIN "gfx/pokemon/lizardon/back.pic" -ZenigamePicFront:: INCBIN "gfx/pokemon/zenigame/front.pic" -ZenigamePicBack:: INCBIN "gfx/pokemon/zenigame/back.pic" -KameilPicFront:: INCBIN "gfx/pokemon/kameil/front.pic" -KameilPicBack:: INCBIN "gfx/pokemon/kameil/back.pic" -KamexPicFront:: INCBIN "gfx/pokemon/kamex/front.pic" -KamexPicBack:: INCBIN "gfx/pokemon/kamex/back.pic" -CaterpiePicFront:: INCBIN "gfx/pokemon/caterpie/front.pic" -CaterpiePicBack:: INCBIN "gfx/pokemon/caterpie/back.pic" -TranselPicFront:: INCBIN "gfx/pokemon/transel/front.pic" -TranselPicBack:: INCBIN "gfx/pokemon/transel/back.pic" -ButterfreePicFront:: INCBIN "gfx/pokemon/butterfree/front.pic" -ButterfreePicBack:: INCBIN "gfx/pokemon/butterfree/back.pic" -BeedlePicFront:: INCBIN "gfx/pokemon/beedle/front.pic" -BeedlePicBack:: INCBIN "gfx/pokemon/beedle/back.pic" -CocoonPicFront:: INCBIN "gfx/pokemon/cocoon/front.pic" -CocoonPicBack:: INCBIN "gfx/pokemon/cocoon/back.pic" -SpearPicFront:: INCBIN "gfx/pokemon/spear/front.pic" -SpearPicBack:: INCBIN "gfx/pokemon/spear/back.pic" -PoppoPicFront:: INCBIN "gfx/pokemon/poppo/front.pic" -PoppoPicBack:: INCBIN "gfx/pokemon/poppo/back.pic" -PigeonPicFront:: INCBIN "gfx/pokemon/pigeon/front.pic" -PigeonPicBack:: INCBIN "gfx/pokemon/pigeon/back.pic" -PigeotPicFront:: INCBIN "gfx/pokemon/pigeot/front.pic" -PigeotPicBack:: INCBIN "gfx/pokemon/pigeot/back.pic" -KorattaPicFront:: INCBIN "gfx/pokemon/koratta/front.pic" -KorattaPicBack:: INCBIN "gfx/pokemon/koratta/back.pic" -RattaPicFront:: INCBIN "gfx/pokemon/ratta/front.pic" -RattaPicBack:: INCBIN "gfx/pokemon/ratta/back.pic" -OnisuzumePicFront:: INCBIN "gfx/pokemon/onisuzume/front.pic" -OnisuzumePicBack:: INCBIN "gfx/pokemon/onisuzume/back.pic" -OnidrillPicFront:: INCBIN "gfx/pokemon/onidrill/front.pic" -OnidrillPicBack:: INCBIN "gfx/pokemon/onidrill/back.pic" -ArboPicFront:: INCBIN "gfx/pokemon/arbo/front.pic" -ArboPicBack:: INCBIN "gfx/pokemon/arbo/back.pic" -ArbokPicFront:: INCBIN "gfx/pokemon/arbok/front.pic" -ArbokPicBack:: INCBIN "gfx/pokemon/arbok/back.pic" -PikachuPicFront:: INCBIN "gfx/pokemon/pikachu/front.pic" -PikachuPicBack:: INCBIN "gfx/pokemon/pikachu/back.pic" -RaichuPicFront:: INCBIN "gfx/pokemon/raichu/front.pic" -RaichuPicBack:: INCBIN "gfx/pokemon/raichu/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 2", ROMX - -SandPicFront:: INCBIN "gfx/pokemon/sand/front.pic" -SandPicBack:: INCBIN "gfx/pokemon/sand/back.pic" -SandpanPicFront:: INCBIN "gfx/pokemon/sandpan/front.pic" -SandpanPicBack:: INCBIN "gfx/pokemon/sandpan/back.pic" -Nidoran_FPicFront:: INCBIN "gfx/pokemon/nidoran_f/front.pic" -Nidoran_FPicBack:: INCBIN "gfx/pokemon/nidoran_f/back.pic" -NidorinaPicFront:: INCBIN "gfx/pokemon/nidorina/front.pic" -NidorinaPicBack:: INCBIN "gfx/pokemon/nidorina/back.pic" -NidoqueenPicFront:: INCBIN "gfx/pokemon/nidoqueen/front.pic" -NidoqueenPicBack:: INCBIN "gfx/pokemon/nidoqueen/back.pic" -Nidoran_MPicFront:: INCBIN "gfx/pokemon/nidoran_m/front.pic" -Nidoran_MPicBack:: INCBIN "gfx/pokemon/nidoran_m/back.pic" -NidorinoPicFront:: INCBIN "gfx/pokemon/nidorino/front.pic" -NidorinoPicBack:: INCBIN "gfx/pokemon/nidorino/back.pic" -NidokingPicFront:: INCBIN "gfx/pokemon/nidoking/front.pic" -NidokingPicBack:: INCBIN "gfx/pokemon/nidoking/back.pic" -PippiPicFront:: INCBIN "gfx/pokemon/pippi/front.pic" -PippiPicBack:: INCBIN "gfx/pokemon/pippi/back.pic" -PixyPicFront:: INCBIN "gfx/pokemon/pixy/front.pic" -PixyPicBack:: INCBIN "gfx/pokemon/pixy/back.pic" -RokonPicFront:: INCBIN "gfx/pokemon/rokon/front.pic" -RokonPicBack:: INCBIN "gfx/pokemon/rokon/back.pic" -KyukonPicFront:: INCBIN "gfx/pokemon/kyukon/front.pic" -KyukonPicBack:: INCBIN "gfx/pokemon/kyukon/back.pic" -PurinPicFront:: INCBIN "gfx/pokemon/purin/front.pic" -PurinPicBack:: INCBIN "gfx/pokemon/purin/back.pic" -PukurinPicFront:: INCBIN "gfx/pokemon/pukurin/front.pic" -PukurinPicBack:: INCBIN "gfx/pokemon/pukurin/back.pic" -ZubatPicFront:: INCBIN "gfx/pokemon/zubat/front.pic" -ZubatPicBack:: INCBIN "gfx/pokemon/zubat/back.pic" -GolbatPicFront:: INCBIN "gfx/pokemon/golbat/front.pic" -GolbatPicBack:: INCBIN "gfx/pokemon/golbat/back.pic" -NazonokusaPicFront:: INCBIN "gfx/pokemon/nazonokusa/front.pic" -NazonokusaPicBack:: INCBIN "gfx/pokemon/nazonokusa/back.pic" -KusaihanaPicFront:: INCBIN "gfx/pokemon/kusaihana/front.pic" -KusaihanaPicBack:: INCBIN "gfx/pokemon/kusaihana/back.pic" -RuffresiaPicFront:: INCBIN "gfx/pokemon/ruffresia/front.pic" -RuffresiaPicBack:: INCBIN "gfx/pokemon/ruffresia/back.pic" -ParasPicFront:: INCBIN "gfx/pokemon/paras/front.pic" -ParasPicBack:: INCBIN "gfx/pokemon/paras/back.pic" -ParasectPicFront:: INCBIN "gfx/pokemon/parasect/front.pic" -ParasectPicBack:: INCBIN "gfx/pokemon/parasect/back.pic" -KongpangPicFront:: INCBIN "gfx/pokemon/kongpang/front.pic" -KongpangPicBack:: INCBIN "gfx/pokemon/kongpang/back.pic" -MorphonPicFront:: INCBIN "gfx/pokemon/morphon/front.pic" -MorphonPicBack:: INCBIN "gfx/pokemon/morphon/back.pic" -DigdaPicFront:: INCBIN "gfx/pokemon/digda/front.pic" -DigdaPicBack:: INCBIN "gfx/pokemon/digda/back.pic" -DugtrioPicFront:: INCBIN "gfx/pokemon/dugtrio/front.pic" -DugtrioPicBack:: INCBIN "gfx/pokemon/dugtrio/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 3", ROMX - -NyarthPicFront:: INCBIN "gfx/pokemon/nyarth/front.pic" -NyarthPicBack:: INCBIN "gfx/pokemon/nyarth/back.pic" -PersianPicFront:: INCBIN "gfx/pokemon/persian/front.pic" -PersianPicBack:: INCBIN "gfx/pokemon/persian/back.pic" -KoduckPicFront:: INCBIN "gfx/pokemon/koduck/front.pic" -KoduckPicBack:: INCBIN "gfx/pokemon/koduck/back.pic" -GolduckPicFront:: INCBIN "gfx/pokemon/golduck/front.pic" -GolduckPicBack:: INCBIN "gfx/pokemon/golduck/back.pic" -MankeyPicFront:: INCBIN "gfx/pokemon/mankey/front.pic" -MankeyPicBack:: INCBIN "gfx/pokemon/mankey/back.pic" -OkorizaruPicFront:: INCBIN "gfx/pokemon/okorizaru/front.pic" -OkorizaruPicBack:: INCBIN "gfx/pokemon/okorizaru/back.pic" -GardiePicFront:: INCBIN "gfx/pokemon/gardie/front.pic" -GardiePicBack:: INCBIN "gfx/pokemon/gardie/back.pic" -WindiePicFront:: INCBIN "gfx/pokemon/windie/front.pic" -WindiePicBack:: INCBIN "gfx/pokemon/windie/back.pic" -NyoromoPicFront:: INCBIN "gfx/pokemon/nyoromo/front.pic" -NyoromoPicBack:: INCBIN "gfx/pokemon/nyoromo/back.pic" -NyorozoPicFront:: INCBIN "gfx/pokemon/nyorozo/front.pic" -NyorozoPicBack:: INCBIN "gfx/pokemon/nyorozo/back.pic" -NyorobonPicFront:: INCBIN "gfx/pokemon/nyorobon/front.pic" -NyorobonPicBack:: INCBIN "gfx/pokemon/nyorobon/back.pic" -CaseyPicFront:: INCBIN "gfx/pokemon/casey/front.pic" -CaseyPicBack:: INCBIN "gfx/pokemon/casey/back.pic" -YungererPicFront:: INCBIN "gfx/pokemon/yungerer/front.pic" -YungererPicBack:: INCBIN "gfx/pokemon/yungerer/back.pic" -FoodinPicFront:: INCBIN "gfx/pokemon/foodin/front.pic" -FoodinPicBack:: INCBIN "gfx/pokemon/foodin/back.pic" -WanrikyPicFront:: INCBIN "gfx/pokemon/wanriky/front.pic" -WanrikyPicBack:: INCBIN "gfx/pokemon/wanriky/back.pic" -GorikyPicFront:: INCBIN "gfx/pokemon/goriky/front.pic" -GorikyPicBack:: INCBIN "gfx/pokemon/goriky/back.pic" -KairikyPicFront:: INCBIN "gfx/pokemon/kairiky/front.pic" -KairikyPicBack:: INCBIN "gfx/pokemon/kairiky/back.pic" -MadatsubomiPicFront:: INCBIN "gfx/pokemon/madatsubomi/front.pic" -MadatsubomiPicBack:: INCBIN "gfx/pokemon/madatsubomi/back.pic" -UtsudonPicFront:: INCBIN "gfx/pokemon/utsudon/front.pic" -UtsudonPicBack:: INCBIN "gfx/pokemon/utsudon/back.pic" -UtsubotPicFront:: INCBIN "gfx/pokemon/utsubot/front.pic" -UtsubotPicBack:: INCBIN "gfx/pokemon/utsubot/back.pic" -MenokuragePicFront:: INCBIN "gfx/pokemon/menokurage/front.pic" -MenokuragePicBack:: INCBIN "gfx/pokemon/menokurage/back.pic" -DokukuragePicFront:: INCBIN "gfx/pokemon/dokukurage/front.pic" -DokukuragePicBack:: INCBIN "gfx/pokemon/dokukurage/back.pic" -IsitsubutePicFront:: INCBIN "gfx/pokemon/isitsubute/front.pic" -IsitsubutePicBack:: INCBIN "gfx/pokemon/isitsubute/back.pic" -GolonePicFront:: INCBIN "gfx/pokemon/golone/front.pic" -GolonePicBack:: INCBIN "gfx/pokemon/golone/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 4", ROMX - -GolonyaPicFront:: INCBIN "gfx/pokemon/golonya/front.pic" -GolonyaPicBack:: INCBIN "gfx/pokemon/golonya/back.pic" -PonytaPicFront:: INCBIN "gfx/pokemon/ponyta/front.pic" -PonytaPicBack:: INCBIN "gfx/pokemon/ponyta/back.pic" -GallopPicFront:: INCBIN "gfx/pokemon/gallop/front.pic" -GallopPicBack:: INCBIN "gfx/pokemon/gallop/back.pic" -YadonPicFront:: INCBIN "gfx/pokemon/yadon/front.pic" -YadonPicBack:: INCBIN "gfx/pokemon/yadon/back.pic" -YadoranPicFront:: INCBIN "gfx/pokemon/yadoran/front.pic" -YadoranPicBack:: INCBIN "gfx/pokemon/yadoran/back.pic" -CoilPicFront:: INCBIN "gfx/pokemon/coil/front.pic" -CoilPicBack:: INCBIN "gfx/pokemon/coil/back.pic" -RarecoilPicFront:: INCBIN "gfx/pokemon/rarecoil/front.pic" -RarecoilPicBack:: INCBIN "gfx/pokemon/rarecoil/back.pic" -KamonegiPicFront:: INCBIN "gfx/pokemon/kamonegi/front.pic" -KamonegiPicBack:: INCBIN "gfx/pokemon/kamonegi/back.pic" -DodoPicFront:: INCBIN "gfx/pokemon/dodo/front.pic" -DodoPicBack:: INCBIN "gfx/pokemon/dodo/back.pic" -DodorioPicFront:: INCBIN "gfx/pokemon/dodorio/front.pic" -DodorioPicBack:: INCBIN "gfx/pokemon/dodorio/back.pic" -PawouPicFront:: INCBIN "gfx/pokemon/pawou/front.pic" -PawouPicBack:: INCBIN "gfx/pokemon/pawou/back.pic" -JugonPicFront:: INCBIN "gfx/pokemon/jugon/front.pic" -JugonPicBack:: INCBIN "gfx/pokemon/jugon/back.pic" -BetbeterPicFront:: INCBIN "gfx/pokemon/betbeter/front.pic" -BetbeterPicBack:: INCBIN "gfx/pokemon/betbeter/back.pic" -BetbetonPicFront:: INCBIN "gfx/pokemon/betbeton/front.pic" -BetbetonPicBack:: INCBIN "gfx/pokemon/betbeton/back.pic" -ShellderPicFront:: INCBIN "gfx/pokemon/shellder/front.pic" -ShellderPicBack:: INCBIN "gfx/pokemon/shellder/back.pic" -ParshenPicFront:: INCBIN "gfx/pokemon/parshen/front.pic" -ParshenPicBack:: INCBIN "gfx/pokemon/parshen/back.pic" -GhosPicFront:: INCBIN "gfx/pokemon/ghos/front.pic" -GhosPicBack:: INCBIN "gfx/pokemon/ghos/back.pic" -GhostPicFront:: INCBIN "gfx/pokemon/ghost/front.pic" -GhostPicBack:: INCBIN "gfx/pokemon/ghost/back.pic" -GangarPicFront:: INCBIN "gfx/pokemon/gangar/front.pic" -GangarPicBack:: INCBIN "gfx/pokemon/gangar/back.pic" -IwarkPicFront:: INCBIN "gfx/pokemon/iwark/front.pic" -IwarkPicBack:: INCBIN "gfx/pokemon/iwark/back.pic" -SleepePicFront:: INCBIN "gfx/pokemon/sleepe/front.pic" -SleepePicBack:: INCBIN "gfx/pokemon/sleepe/back.pic" -SleeperPicFront:: INCBIN "gfx/pokemon/sleeper/front.pic" -SleeperPicBack:: INCBIN "gfx/pokemon/sleeper/back.pic" -CrabPicFront:: INCBIN "gfx/pokemon/crab/front.pic" -CrabPicBack:: INCBIN "gfx/pokemon/crab/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 5", ROMX - -KinglerPicFront:: INCBIN "gfx/pokemon/kingler/front.pic" -KinglerPicBack:: INCBIN "gfx/pokemon/kingler/back.pic" -BiriridamaPicFront:: INCBIN "gfx/pokemon/biriridama/front.pic" -BiriridamaPicBack:: INCBIN "gfx/pokemon/biriridama/back.pic" -MaruminePicFront:: INCBIN "gfx/pokemon/marumine/front.pic" -MaruminePicBack:: INCBIN "gfx/pokemon/marumine/back.pic" -TamatamaPicFront:: INCBIN "gfx/pokemon/tamatama/front.pic" -TamatamaPicBack:: INCBIN "gfx/pokemon/tamatama/back.pic" -NassyPicFront:: INCBIN "gfx/pokemon/nassy/front.pic" -NassyPicBack:: INCBIN "gfx/pokemon/nassy/back.pic" -KarakaraPicFront:: INCBIN "gfx/pokemon/karakara/front.pic" -KarakaraPicBack:: INCBIN "gfx/pokemon/karakara/back.pic" -GaragaraPicFront:: INCBIN "gfx/pokemon/garagara/front.pic" -GaragaraPicBack:: INCBIN "gfx/pokemon/garagara/back.pic" -SawamularPicFront:: INCBIN "gfx/pokemon/sawamular/front.pic" -SawamularPicBack:: INCBIN "gfx/pokemon/sawamular/back.pic" -EbiwalarPicFront:: INCBIN "gfx/pokemon/ebiwalar/front.pic" -EbiwalarPicBack:: INCBIN "gfx/pokemon/ebiwalar/back.pic" -BeroringaPicFront:: INCBIN "gfx/pokemon/beroringa/front.pic" -BeroringaPicBack:: INCBIN "gfx/pokemon/beroringa/back.pic" -DogarsPicFront:: INCBIN "gfx/pokemon/dogars/front.pic" -DogarsPicBack:: INCBIN "gfx/pokemon/dogars/back.pic" -MatadogasPicFront:: INCBIN "gfx/pokemon/matadogas/front.pic" -MatadogasPicBack:: INCBIN "gfx/pokemon/matadogas/back.pic" -SihornPicFront:: INCBIN "gfx/pokemon/sihorn/front.pic" -SihornPicBack:: INCBIN "gfx/pokemon/sihorn/back.pic" -SidonPicFront:: INCBIN "gfx/pokemon/sidon/front.pic" -SidonPicBack:: INCBIN "gfx/pokemon/sidon/back.pic" -LuckyPicFront:: INCBIN "gfx/pokemon/lucky/front.pic" -LuckyPicBack:: INCBIN "gfx/pokemon/lucky/back.pic" -MonjaraPicFront:: INCBIN "gfx/pokemon/monjara/front.pic" -MonjaraPicBack:: INCBIN "gfx/pokemon/monjara/back.pic" -GaruraPicFront:: INCBIN "gfx/pokemon/garura/front.pic" -GaruraPicBack:: INCBIN "gfx/pokemon/garura/back.pic" -TattuPicFront:: INCBIN "gfx/pokemon/tattu/front.pic" -TattuPicBack:: INCBIN "gfx/pokemon/tattu/back.pic" -SeadraPicFront:: INCBIN "gfx/pokemon/seadra/front.pic" -SeadraPicBack:: INCBIN "gfx/pokemon/seadra/back.pic" -TosakintoPicFront:: INCBIN "gfx/pokemon/tosakinto/front.pic" -TosakintoPicBack:: INCBIN "gfx/pokemon/tosakinto/back.pic" -AzumaoPicFront:: INCBIN "gfx/pokemon/azumao/front.pic" -AzumaoPicBack:: INCBIN "gfx/pokemon/azumao/back.pic" -HitodemanPicFront:: INCBIN "gfx/pokemon/hitodeman/front.pic" -HitodemanPicBack:: INCBIN "gfx/pokemon/hitodeman/back.pic" -StarmiePicFront:: INCBIN "gfx/pokemon/starmie/front.pic" -StarmiePicBack:: INCBIN "gfx/pokemon/starmie/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 6", ROMX - -BarrierdPicFront:: INCBIN "gfx/pokemon/barrierd/front.pic" -BarrierdPicBack:: INCBIN "gfx/pokemon/barrierd/back.pic" -StrikePicFront:: INCBIN "gfx/pokemon/strike/front.pic" -StrikePicBack:: INCBIN "gfx/pokemon/strike/back.pic" -RougelaPicFront:: INCBIN "gfx/pokemon/rougela/front.pic" -RougelaPicBack:: INCBIN "gfx/pokemon/rougela/back.pic" -ElebooPicFront:: INCBIN "gfx/pokemon/eleboo/front.pic" -ElebooPicBack:: INCBIN "gfx/pokemon/eleboo/back.pic" -BooberPicFront:: INCBIN "gfx/pokemon/boober/front.pic" -BooberPicBack:: INCBIN "gfx/pokemon/boober/back.pic" -KailiosPicFront:: INCBIN "gfx/pokemon/kailios/front.pic" -KailiosPicBack:: INCBIN "gfx/pokemon/kailios/back.pic" -KentaurosPicFront:: INCBIN "gfx/pokemon/kentauros/front.pic" -KentaurosPicBack:: INCBIN "gfx/pokemon/kentauros/back.pic" -KoikingPicFront:: INCBIN "gfx/pokemon/koiking/front.pic" -KoikingPicBack:: INCBIN "gfx/pokemon/koiking/back.pic" -GyaradosPicFront:: INCBIN "gfx/pokemon/gyarados/front.pic" -GyaradosPicBack:: INCBIN "gfx/pokemon/gyarados/back.pic" -LaplacePicFront:: INCBIN "gfx/pokemon/laplace/front.pic" -LaplacePicBack:: INCBIN "gfx/pokemon/laplace/back.pic" -MetamonPicFront:: INCBIN "gfx/pokemon/metamon/front.pic" -MetamonPicBack:: INCBIN "gfx/pokemon/metamon/back.pic" -EievuiPicFront:: INCBIN "gfx/pokemon/eievui/front.pic" -EievuiPicBack:: INCBIN "gfx/pokemon/eievui/back.pic" -ShowersPicFront:: INCBIN "gfx/pokemon/showers/front.pic" -ShowersPicBack:: INCBIN "gfx/pokemon/showers/back.pic" -ThundersPicFront:: INCBIN "gfx/pokemon/thunders/front.pic" -ThundersPicBack:: INCBIN "gfx/pokemon/thunders/back.pic" -BoosterPicFront:: INCBIN "gfx/pokemon/booster/front.pic" -BoosterPicBack:: INCBIN "gfx/pokemon/booster/back.pic" -PorygonPicFront:: INCBIN "gfx/pokemon/porygon/front.pic" -PorygonPicBack:: INCBIN "gfx/pokemon/porygon/back.pic" -OmnitePicFront:: INCBIN "gfx/pokemon/omnite/front.pic" -OmnitePicBack:: INCBIN "gfx/pokemon/omnite/back.pic" -OmstarPicFront:: INCBIN "gfx/pokemon/omstar/front.pic" -OmstarPicBack:: INCBIN "gfx/pokemon/omstar/back.pic" -KabutoPicFront:: INCBIN "gfx/pokemon/kabuto/front.pic" -KabutoPicBack:: INCBIN "gfx/pokemon/kabuto/back.pic" -KabutopsPicFront:: INCBIN "gfx/pokemon/kabutops/front.pic" -KabutopsPicBack:: INCBIN "gfx/pokemon/kabutops/back.pic" -PteraPicFront:: INCBIN "gfx/pokemon/ptera/front.pic" -PteraPicBack:: INCBIN "gfx/pokemon/ptera/back.pic" -KabigonPicFront:: INCBIN "gfx/pokemon/kabigon/front.pic" -KabigonPicBack:: INCBIN "gfx/pokemon/kabigon/back.pic" -FreezerPicFront:: INCBIN "gfx/pokemon/freezer/front.pic" -FreezerPicBack:: INCBIN "gfx/pokemon/freezer/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 7", ROMX - -ThunderPicFront:: INCBIN "gfx/pokemon/thunder/front.pic" -ThunderPicBack:: INCBIN "gfx/pokemon/thunder/back.pic" -FirePicFront:: INCBIN "gfx/pokemon/fire/front.pic" -FirePicBack:: INCBIN "gfx/pokemon/fire/back.pic" -MiniryuPicFront:: INCBIN "gfx/pokemon/miniryu/front.pic" -MiniryuPicBack:: INCBIN "gfx/pokemon/miniryu/back.pic" -HakuryuPicFront:: INCBIN "gfx/pokemon/hakuryu/front.pic" -HakuryuPicBack:: INCBIN "gfx/pokemon/hakuryu/back.pic" -KairyuPicFront:: INCBIN "gfx/pokemon/kairyu/front.pic" -KairyuPicBack:: INCBIN "gfx/pokemon/kairyu/back.pic" -MewtwoPicFront:: INCBIN "gfx/pokemon/mewtwo/front.pic" -MewtwoPicBack:: INCBIN "gfx/pokemon/mewtwo/back.pic" -MewPicFront:: INCBIN "gfx/pokemon/mew/front.pic" -MewPicBack:: INCBIN "gfx/pokemon/mew/back.pic" -HappaPicFront:: INCBIN "gfx/pokemon/happa/front.pic" -HappaPicBack:: INCBIN "gfx/pokemon/happa/back.pic" -HanamoguraPicFront:: INCBIN "gfx/pokemon/hanamogura/front.pic" -HanamoguraPicBack:: INCBIN "gfx/pokemon/hanamogura/back.pic" -HanaryuPicFront:: INCBIN "gfx/pokemon/hanaryu/front.pic" -HanaryuPicBack:: INCBIN "gfx/pokemon/hanaryu/back.pic" -HonogumaPicFront:: INCBIN "gfx/pokemon/honoguma/front.pic" -HonogumaPicBack:: INCBIN "gfx/pokemon/honoguma/back.pic" -VolbearPicFront:: INCBIN "gfx/pokemon/volbear/front.pic" -VolbearPicBack:: INCBIN "gfx/pokemon/volbear/back.pic" -DynabearPicFront:: INCBIN "gfx/pokemon/dynabear/front.pic" -DynabearPicBack:: INCBIN "gfx/pokemon/dynabear/back.pic" -KurusuPicFront:: INCBIN "gfx/pokemon/kurusu/front.pic" -KurusuPicBack:: INCBIN "gfx/pokemon/kurusu/back.pic" -AquaPicFront:: INCBIN "gfx/pokemon/aqua/front.pic" -AquaPicBack:: INCBIN "gfx/pokemon/aqua/back.pic" -AquariaPicFront:: INCBIN "gfx/pokemon/aquaria/front.pic" -AquariaPicBack:: INCBIN "gfx/pokemon/aquaria/back.pic" -HohoPicFront:: INCBIN "gfx/pokemon/hoho/front.pic" -HohoPicBack:: INCBIN "gfx/pokemon/hoho/back.pic" -BoboPicFront:: INCBIN "gfx/pokemon/bobo/front.pic" -BoboPicBack:: INCBIN "gfx/pokemon/bobo/back.pic" -PachimeePicFront:: INCBIN "gfx/pokemon/pachimee/front.pic" -PachimeePicBack:: INCBIN "gfx/pokemon/pachimee/back.pic" -MokokoPicFront:: INCBIN "gfx/pokemon/mokoko/front.pic" -MokokoPicBack:: INCBIN "gfx/pokemon/mokoko/back.pic" -DenryuPicFront:: INCBIN "gfx/pokemon/denryu/front.pic" -DenryuPicBack:: INCBIN "gfx/pokemon/denryu/back.pic" -MikonPicFront:: INCBIN "gfx/pokemon/mikon/front.pic" -MikonPicBack:: INCBIN "gfx/pokemon/mikon/back.pic" -MonjaPicFront:: INCBIN "gfx/pokemon/monja/front.pic" -MonjaPicBack:: INCBIN "gfx/pokemon/monja/back.pic" -JaranraPicFront:: INCBIN "gfx/pokemon/jaranra/front.pic" -JaranraPicBack:: INCBIN "gfx/pokemon/jaranra/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 8", ROMX - -HaneeiPicFront:: INCBIN "gfx/pokemon/haneei/front.pic" -HaneeiPicBack:: INCBIN "gfx/pokemon/haneei/back.pic" -PukuPicFront:: INCBIN "gfx/pokemon/puku/front.pic" -PukuPicBack:: INCBIN "gfx/pokemon/puku/back.pic" -ShibirefuguPicFront:: INCBIN "gfx/pokemon/shibirefugu/front.pic" -ShibirefuguPicBack:: INCBIN "gfx/pokemon/shibirefugu/back.pic" -PichuPicFront:: INCBIN "gfx/pokemon/pichu/front.pic" -PichuPicBack:: INCBIN "gfx/pokemon/pichu/back.pic" -PyPicFront:: INCBIN "gfx/pokemon/py/front.pic" -PyPicBack:: INCBIN "gfx/pokemon/py/back.pic" -PupurinPicFront:: INCBIN "gfx/pokemon/pupurin/front.pic" -PupurinPicBack:: INCBIN "gfx/pokemon/pupurin/back.pic" -MizuuoPicFront:: INCBIN "gfx/pokemon/mizuuo/front.pic" -MizuuoPicBack:: INCBIN "gfx/pokemon/mizuuo/back.pic" -NatyPicFront:: INCBIN "gfx/pokemon/naty/front.pic" -NatyPicBack:: INCBIN "gfx/pokemon/naty/back.pic" -NatioPicFront:: INCBIN "gfx/pokemon/natio/front.pic" -NatioPicBack:: INCBIN "gfx/pokemon/natio/back.pic" -GyopinPicFront:: INCBIN "gfx/pokemon/gyopin/front.pic" -GyopinPicBack:: INCBIN "gfx/pokemon/gyopin/back.pic" -MarilPicFront:: INCBIN "gfx/pokemon/maril/front.pic" -MarilPicBack:: INCBIN "gfx/pokemon/maril/back.pic" -Manbo1PicFront:: INCBIN "gfx/pokemon/manbo1/front.pic" -Manbo1PicBack:: INCBIN "gfx/pokemon/manbo1/back.pic" -IkariPicFront:: INCBIN "gfx/pokemon/ikari/front.pic" -IkariPicBack:: INCBIN "gfx/pokemon/ikari/back.pic" -GrotessPicFront:: INCBIN "gfx/pokemon/grotess/front.pic" -GrotessPicBack:: INCBIN "gfx/pokemon/grotess/back.pic" -EksingPicFront:: INCBIN "gfx/pokemon/eksing/front.pic" -EksingPicBack:: INCBIN "gfx/pokemon/eksing/back.pic" -ParaPicFront:: INCBIN "gfx/pokemon/para/front.pic" -ParaPicBack:: INCBIN "gfx/pokemon/para/back.pic" -KokumoPicFront:: INCBIN "gfx/pokemon/kokumo/front.pic" -KokumoPicBack:: INCBIN "gfx/pokemon/kokumo/back.pic" -TwoheadPicFront:: INCBIN "gfx/pokemon/twohead/front.pic" -TwoheadPicBack:: INCBIN "gfx/pokemon/twohead/back.pic" -YoroidoriPicFront:: INCBIN "gfx/pokemon/yoroidori/front.pic" -YoroidoriPicBack:: INCBIN "gfx/pokemon/yoroidori/back.pic" -AnimonPicFront:: INCBIN "gfx/pokemon/animon/front.pic" -AnimonPicBack:: INCBIN "gfx/pokemon/animon/back.pic" -HinazuPicFront:: INCBIN "gfx/pokemon/hinazu/front.pic" -HinazuPicBack:: INCBIN "gfx/pokemon/hinazu/back.pic" -SunnyPicFront:: INCBIN "gfx/pokemon/sunny/front.pic" -SunnyPicBack:: INCBIN "gfx/pokemon/sunny/back.pic" -PaonPicFront:: INCBIN "gfx/pokemon/paon/front.pic" -PaonPicBack:: INCBIN "gfx/pokemon/paon/back.pic" -DonphanPicFront:: INCBIN "gfx/pokemon/donphan/front.pic" -DonphanPicBack:: INCBIN "gfx/pokemon/donphan/back.pic" -TwinzPicFront:: INCBIN "gfx/pokemon/twinz/front.pic" -TwinzPicBack:: INCBIN "gfx/pokemon/twinz/back.pic" -KirinrikiPicFront:: INCBIN "gfx/pokemon/kirinriki/front.pic" -KirinrikiPicBack:: INCBIN "gfx/pokemon/kirinriki/back.pic" -PainterPicFront:: INCBIN "gfx/pokemon/painter/front.pic" -PainterPicBack:: INCBIN "gfx/pokemon/painter/back.pic" -KounyaPicFront:: INCBIN "gfx/pokemon/kounya/front.pic" -KounyaPicBack:: INCBIN "gfx/pokemon/kounya/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 9", ROMX - -RinrinPicFront:: INCBIN "gfx/pokemon/rinrin/front.pic" -RinrinPicBack:: INCBIN "gfx/pokemon/rinrin/back.pic" -BerurunPicFront:: INCBIN "gfx/pokemon/berurun/front.pic" -BerurunPicBack:: INCBIN "gfx/pokemon/berurun/back.pic" -NyorotonoPicFront:: INCBIN "gfx/pokemon/nyorotono/front.pic" -NyorotonoPicBack:: INCBIN "gfx/pokemon/nyorotono/back.pic" -YadokingPicFront:: INCBIN "gfx/pokemon/yadoking/front.pic" -YadokingPicBack:: INCBIN "gfx/pokemon/yadoking/back.pic" -RedibaPicFront:: INCBIN "gfx/pokemon/rediba/front.pic" -RedibaPicBack:: INCBIN "gfx/pokemon/rediba/back.pic" -MitsuboshiPicFront:: INCBIN "gfx/pokemon/mitsuboshi/front.pic" -MitsuboshiPicBack:: INCBIN "gfx/pokemon/mitsuboshi/back.pic" -PuchicornPicFront:: INCBIN "gfx/pokemon/puchicorn/front.pic" -PuchicornPicBack:: INCBIN "gfx/pokemon/puchicorn/back.pic" -EifiePicFront:: INCBIN "gfx/pokemon/eifie/front.pic" -EifiePicBack:: INCBIN "gfx/pokemon/eifie/back.pic" -BlackyPicFront:: INCBIN "gfx/pokemon/blacky/front.pic" -BlackyPicBack:: INCBIN "gfx/pokemon/blacky/back.pic" -TurbanPicFront:: INCBIN "gfx/pokemon/turban/front.pic" -TurbanPicBack:: INCBIN "gfx/pokemon/turban/back.pic" -BetbabyPicFront:: INCBIN "gfx/pokemon/betbaby/front.pic" -BetbabyPicBack:: INCBIN "gfx/pokemon/betbaby/back.pic" -TeppouoPicFront:: INCBIN "gfx/pokemon/teppouo/front.pic" -TeppouoPicBack:: INCBIN "gfx/pokemon/teppouo/back.pic" -OkutankPicFront:: INCBIN "gfx/pokemon/okutank/front.pic" -OkutankPicBack:: INCBIN "gfx/pokemon/okutank/back.pic" -GonguPicFront:: INCBIN "gfx/pokemon/gongu/front.pic" -GonguPicBack:: INCBIN "gfx/pokemon/gongu/back.pic" -KapoererPicFront:: INCBIN "gfx/pokemon/kapoerer/front.pic" -KapoererPicBack:: INCBIN "gfx/pokemon/kapoerer/back.pic" -PudiePicFront:: INCBIN "gfx/pokemon/pudie/front.pic" -PudiePicBack:: INCBIN "gfx/pokemon/pudie/back.pic" -HanekoPicFront:: INCBIN "gfx/pokemon/haneko/front.pic" -HanekoPicBack:: INCBIN "gfx/pokemon/haneko/back.pic" -PoponekoPicFront:: INCBIN "gfx/pokemon/poponeko/front.pic" -PoponekoPicBack:: INCBIN "gfx/pokemon/poponeko/back.pic" -WatanekoPicFront:: INCBIN "gfx/pokemon/wataneko/front.pic" -WatanekoPicBack:: INCBIN "gfx/pokemon/wataneko/back.pic" -BaririnaPicFront:: INCBIN "gfx/pokemon/baririna/front.pic" -BaririnaPicBack:: INCBIN "gfx/pokemon/baririna/back.pic" -LipPicFront:: INCBIN "gfx/pokemon/lip/front.pic" -LipPicBack:: INCBIN "gfx/pokemon/lip/back.pic" -ElebabyPicFront:: INCBIN "gfx/pokemon/elebaby/front.pic" -ElebabyPicBack:: INCBIN "gfx/pokemon/elebaby/back.pic" -BoobyPicFront:: INCBIN "gfx/pokemon/booby/front.pic" -BoobyPicBack:: INCBIN "gfx/pokemon/booby/back.pic" -KireihanaPicFront:: INCBIN "gfx/pokemon/kireihana/front.pic" -KireihanaPicBack:: INCBIN "gfx/pokemon/kireihana/back.pic" -TsubomittoPicFront:: INCBIN "gfx/pokemon/tsubomitto/front.pic" -TsubomittoPicBack:: INCBIN "gfx/pokemon/tsubomitto/back.pic" -MiltankPicFront:: INCBIN "gfx/pokemon/miltank/front.pic" -MiltankPicBack:: INCBIN "gfx/pokemon/miltank/back.pic" -BombseekerPicFront:: INCBIN "gfx/pokemon/bombseeker/front.pic" -BombseekerPicBack:: INCBIN "gfx/pokemon/bombseeker/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 10", ROMX - -GiftPicFront:: INCBIN "gfx/pokemon/gift/front.pic" -GiftPicBack:: INCBIN "gfx/pokemon/gift/back.pic" -KotoraPicFront:: INCBIN "gfx/pokemon/kotora/front.pic" -KotoraPicBack:: INCBIN "gfx/pokemon/kotora/back.pic" -RaitoraPicFront:: INCBIN "gfx/pokemon/raitora/front.pic" -RaitoraPicBack:: INCBIN "gfx/pokemon/raitora/back.pic" -MadamePicFront:: INCBIN "gfx/pokemon/madame/front.pic" -MadamePicBack:: INCBIN "gfx/pokemon/madame/back.pic" -NorowaraPicFront:: INCBIN "gfx/pokemon/norowara/front.pic" -NorowaraPicBack:: INCBIN "gfx/pokemon/norowara/back.pic" -KyonpanPicFront:: INCBIN "gfx/pokemon/kyonpan/front.pic" -KyonpanPicBack:: INCBIN "gfx/pokemon/kyonpan/back.pic" -YamikarasuPicFront:: INCBIN "gfx/pokemon/yamikarasu/front.pic" -YamikarasuPicBack:: INCBIN "gfx/pokemon/yamikarasu/back.pic" -HappiPicFront:: INCBIN "gfx/pokemon/happi/front.pic" -HappiPicBack:: INCBIN "gfx/pokemon/happi/back.pic" -ScissorsPicFront:: INCBIN "gfx/pokemon/scissors/front.pic" -ScissorsPicBack:: INCBIN "gfx/pokemon/scissors/back.pic" -PurakkusuPicFront:: INCBIN "gfx/pokemon/purakkusu/front.pic" -PurakkusuPicBack:: INCBIN "gfx/pokemon/purakkusu/back.pic" -DevilPicFront:: INCBIN "gfx/pokemon/devil/front.pic" -DevilPicBack:: INCBIN "gfx/pokemon/devil/back.pic" -HelgaaPicFront:: INCBIN "gfx/pokemon/helgaa/front.pic" -HelgaaPicBack:: INCBIN "gfx/pokemon/helgaa/back.pic" -WolfmanPicFront:: INCBIN "gfx/pokemon/wolfman/front.pic" -WolfmanPicBack:: INCBIN "gfx/pokemon/wolfman/back.pic" -WarwolfPicFront:: INCBIN "gfx/pokemon/warwolf/front.pic" -WarwolfPicBack:: INCBIN "gfx/pokemon/warwolf/back.pic" -Porygon2PicFront:: INCBIN "gfx/pokemon/porygon2/front.pic" -Porygon2PicBack:: INCBIN "gfx/pokemon/porygon2/back.pic" -NameilPicFront:: INCBIN "gfx/pokemon/nameil/front.pic" -NameilPicBack:: INCBIN "gfx/pokemon/nameil/back.pic" -HaganeilPicFront:: INCBIN "gfx/pokemon/haganeil/front.pic" -HaganeilPicBack:: INCBIN "gfx/pokemon/haganeil/back.pic" -KingdraPicFront:: INCBIN "gfx/pokemon/kingdra/front.pic" -KingdraPicBack:: INCBIN "gfx/pokemon/kingdra/back.pic" -RaiPicFront:: INCBIN "gfx/pokemon/rai/front.pic" - db 0 ; how did that get here? -RaiPicBack:: INCBIN "gfx/pokemon/rai/back.pic" -EnPicFront:: INCBIN "gfx/pokemon/en/front.pic" -EnPicBack:: INCBIN "gfx/pokemon/en/back.pic" -SuiPicFront:: INCBIN "gfx/pokemon/sui/front.pic" -SuiPicBack:: INCBIN "gfx/pokemon/sui/back.pic" -NyulaPicFront:: INCBIN "gfx/pokemon/nyula/front.pic" -NyulaPicBack:: INCBIN "gfx/pokemon/nyula/back.pic" - - -SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 11", ROMX - -HououPicFront:: INCBIN "gfx/pokemon/houou/front.pic" -HououPicBack:: INCBIN "gfx/pokemon/houou/back.pic" -TogepyPicFront:: INCBIN "gfx/pokemon/togepy/front.pic" -TogepyPicBack:: INCBIN "gfx/pokemon/togepy/back.pic" -BuluPicFront:: INCBIN "gfx/pokemon/bulu/front.pic" -BuluPicBack:: INCBIN "gfx/pokemon/bulu/back.pic" -TailPicFront:: INCBIN "gfx/pokemon/tail/front.pic" -TailPicBack:: INCBIN "gfx/pokemon/tail/back.pic" -LeafyPicFront:: INCBIN "gfx/pokemon/leafy/front.pic" -LeafyPicBack:: INCBIN "gfx/pokemon/leafy/back.pic" diff --git a/gfx/pokemon/pkmn_pics.inc b/gfx/pokemon/pkmn_pics.inc new file mode 100644 index 0000000..178b738 --- /dev/null +++ b/gfx/pokemon/pkmn_pics.inc @@ -0,0 +1,543 @@ +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 1", ROMX + +FushigidanePicFront:: INCBIN "gfx/pokemon/fushigidane/front.pic" +FushigidanePicBack:: INCBIN "gfx/pokemon/fushigidane/back.pic" +FushigisouPicFront:: INCBIN "gfx/pokemon/fushigisou/front.pic" +FushigisouPicBack:: INCBIN "gfx/pokemon/fushigisou/back.pic" +FushigibanaPicFront:: INCBIN "gfx/pokemon/fushigibana/front.pic" +FushigibanaPicBack:: INCBIN "gfx/pokemon/fushigibana/back.pic" +HitokagePicFront:: INCBIN "gfx/pokemon/hitokage/front.pic" +HitokagePicBack:: INCBIN "gfx/pokemon/hitokage/back.pic" +LizardoPicFront:: INCBIN "gfx/pokemon/lizardo/front.pic" +LizardoPicBack:: INCBIN "gfx/pokemon/lizardo/back.pic" +LizardonPicFront:: INCBIN "gfx/pokemon/lizardon/front.pic" +LizardonPicBack:: INCBIN "gfx/pokemon/lizardon/back.pic" +ZenigamePicFront:: INCBIN "gfx/pokemon/zenigame/front.pic" +ZenigamePicBack:: INCBIN "gfx/pokemon/zenigame/back.pic" +KameilPicFront:: INCBIN "gfx/pokemon/kameil/front.pic" +KameilPicBack:: INCBIN "gfx/pokemon/kameil/back.pic" +KamexPicFront:: INCBIN "gfx/pokemon/kamex/front.pic" +KamexPicBack:: INCBIN "gfx/pokemon/kamex/back.pic" +CaterpiePicFront:: INCBIN "gfx/pokemon/caterpie/front.pic" +CaterpiePicBack:: INCBIN "gfx/pokemon/caterpie/back.pic" +TranselPicFront:: INCBIN "gfx/pokemon/transel/front.pic" +TranselPicBack:: INCBIN "gfx/pokemon/transel/back.pic" +ButterfreePicFront:: INCBIN "gfx/pokemon/butterfree/front.pic" +ButterfreePicBack:: INCBIN "gfx/pokemon/butterfree/back.pic" +BeedlePicFront:: INCBIN "gfx/pokemon/beedle/front.pic" +BeedlePicBack:: INCBIN "gfx/pokemon/beedle/back.pic" +CocoonPicFront:: INCBIN "gfx/pokemon/cocoon/front.pic" +CocoonPicBack:: INCBIN "gfx/pokemon/cocoon/back.pic" +SpearPicFront:: INCBIN "gfx/pokemon/spear/front.pic" +SpearPicBack:: INCBIN "gfx/pokemon/spear/back.pic" +PoppoPicFront:: INCBIN "gfx/pokemon/poppo/front.pic" +PoppoPicBack:: INCBIN "gfx/pokemon/poppo/back.pic" +PigeonPicFront:: INCBIN "gfx/pokemon/pigeon/front.pic" +PigeonPicBack:: INCBIN "gfx/pokemon/pigeon/back.pic" +PigeotPicFront:: INCBIN "gfx/pokemon/pigeot/front.pic" +PigeotPicBack:: INCBIN "gfx/pokemon/pigeot/back.pic" +KorattaPicFront:: INCBIN "gfx/pokemon/koratta/front.pic" +KorattaPicBack:: INCBIN "gfx/pokemon/koratta/back.pic" +RattaPicFront:: INCBIN "gfx/pokemon/ratta/front.pic" +RattaPicBack:: INCBIN "gfx/pokemon/ratta/back.pic" +OnisuzumePicFront:: INCBIN "gfx/pokemon/onisuzume/front.pic" +OnisuzumePicBack:: INCBIN "gfx/pokemon/onisuzume/back.pic" +OnidrillPicFront:: INCBIN "gfx/pokemon/onidrill/front.pic" +OnidrillPicBack:: INCBIN "gfx/pokemon/onidrill/back.pic" +ArboPicFront:: INCBIN "gfx/pokemon/arbo/front.pic" +ArboPicBack:: INCBIN "gfx/pokemon/arbo/back.pic" +ArbokPicFront:: INCBIN "gfx/pokemon/arbok/front.pic" +ArbokPicBack:: INCBIN "gfx/pokemon/arbok/back.pic" +PikachuPicFront:: INCBIN "gfx/pokemon/pikachu/front.pic" +PikachuPicBack:: INCBIN "gfx/pokemon/pikachu/back.pic" +RaichuPicFront:: INCBIN "gfx/pokemon/raichu/front.pic" +RaichuPicBack:: INCBIN "gfx/pokemon/raichu/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 2", ROMX + +SandPicFront:: INCBIN "gfx/pokemon/sand/front.pic" +SandPicBack:: INCBIN "gfx/pokemon/sand/back.pic" +SandpanPicFront:: INCBIN "gfx/pokemon/sandpan/front.pic" +SandpanPicBack:: INCBIN "gfx/pokemon/sandpan/back.pic" +Nidoran_FPicFront:: INCBIN "gfx/pokemon/nidoran_f/front.pic" +Nidoran_FPicBack:: INCBIN "gfx/pokemon/nidoran_f/back.pic" +NidorinaPicFront:: INCBIN "gfx/pokemon/nidorina/front.pic" +NidorinaPicBack:: INCBIN "gfx/pokemon/nidorina/back.pic" +NidoqueenPicFront:: INCBIN "gfx/pokemon/nidoqueen/front.pic" +NidoqueenPicBack:: INCBIN "gfx/pokemon/nidoqueen/back.pic" +Nidoran_MPicFront:: INCBIN "gfx/pokemon/nidoran_m/front.pic" +Nidoran_MPicBack:: INCBIN "gfx/pokemon/nidoran_m/back.pic" +NidorinoPicFront:: INCBIN "gfx/pokemon/nidorino/front.pic" +NidorinoPicBack:: INCBIN "gfx/pokemon/nidorino/back.pic" +NidokingPicFront:: INCBIN "gfx/pokemon/nidoking/front.pic" +NidokingPicBack:: INCBIN "gfx/pokemon/nidoking/back.pic" +PippiPicFront:: INCBIN "gfx/pokemon/pippi/front.pic" +PippiPicBack:: INCBIN "gfx/pokemon/pippi/back.pic" +PixyPicFront:: INCBIN "gfx/pokemon/pixy/front.pic" +PixyPicBack:: INCBIN "gfx/pokemon/pixy/back.pic" +RokonPicFront:: INCBIN "gfx/pokemon/rokon/front.pic" +RokonPicBack:: INCBIN "gfx/pokemon/rokon/back.pic" +KyukonPicFront:: INCBIN "gfx/pokemon/kyukon/front.pic" +KyukonPicBack:: INCBIN "gfx/pokemon/kyukon/back.pic" +PurinPicFront:: INCBIN "gfx/pokemon/purin/front.pic" +PurinPicBack:: INCBIN "gfx/pokemon/purin/back.pic" +PukurinPicFront:: INCBIN "gfx/pokemon/pukurin/front.pic" +PukurinPicBack:: INCBIN "gfx/pokemon/pukurin/back.pic" +ZubatPicFront:: INCBIN "gfx/pokemon/zubat/front.pic" +ZubatPicBack:: INCBIN "gfx/pokemon/zubat/back.pic" +GolbatPicFront:: INCBIN "gfx/pokemon/golbat/front.pic" +GolbatPicBack:: INCBIN "gfx/pokemon/golbat/back.pic" +NazonokusaPicFront:: INCBIN "gfx/pokemon/nazonokusa/front.pic" +NazonokusaPicBack:: INCBIN "gfx/pokemon/nazonokusa/back.pic" +KusaihanaPicFront:: INCBIN "gfx/pokemon/kusaihana/front.pic" +KusaihanaPicBack:: INCBIN "gfx/pokemon/kusaihana/back.pic" +RuffresiaPicFront:: INCBIN "gfx/pokemon/ruffresia/front.pic" +RuffresiaPicBack:: INCBIN "gfx/pokemon/ruffresia/back.pic" +ParasPicFront:: INCBIN "gfx/pokemon/paras/front.pic" +ParasPicBack:: INCBIN "gfx/pokemon/paras/back.pic" +ParasectPicFront:: INCBIN "gfx/pokemon/parasect/front.pic" +ParasectPicBack:: INCBIN "gfx/pokemon/parasect/back.pic" +KongpangPicFront:: INCBIN "gfx/pokemon/kongpang/front.pic" +KongpangPicBack:: INCBIN "gfx/pokemon/kongpang/back.pic" +MorphonPicFront:: INCBIN "gfx/pokemon/morphon/front.pic" +MorphonPicBack:: INCBIN "gfx/pokemon/morphon/back.pic" +DigdaPicFront:: INCBIN "gfx/pokemon/digda/front.pic" +DigdaPicBack:: INCBIN "gfx/pokemon/digda/back.pic" +DugtrioPicFront:: INCBIN "gfx/pokemon/dugtrio/front.pic" +DugtrioPicBack:: INCBIN "gfx/pokemon/dugtrio/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 3", ROMX + +NyarthPicFront:: INCBIN "gfx/pokemon/nyarth/front.pic" +NyarthPicBack:: INCBIN "gfx/pokemon/nyarth/back.pic" +PersianPicFront:: INCBIN "gfx/pokemon/persian/front.pic" +PersianPicBack:: INCBIN "gfx/pokemon/persian/back.pic" +KoduckPicFront:: INCBIN "gfx/pokemon/koduck/front.pic" +KoduckPicBack:: INCBIN "gfx/pokemon/koduck/back.pic" +GolduckPicFront:: INCBIN "gfx/pokemon/golduck/front.pic" +GolduckPicBack:: INCBIN "gfx/pokemon/golduck/back.pic" +MankeyPicFront:: INCBIN "gfx/pokemon/mankey/front.pic" +MankeyPicBack:: INCBIN "gfx/pokemon/mankey/back.pic" +OkorizaruPicFront:: INCBIN "gfx/pokemon/okorizaru/front.pic" +OkorizaruPicBack:: INCBIN "gfx/pokemon/okorizaru/back.pic" +GardiePicFront:: INCBIN "gfx/pokemon/gardie/front.pic" +GardiePicBack:: INCBIN "gfx/pokemon/gardie/back.pic" +WindiePicFront:: INCBIN "gfx/pokemon/windie/front.pic" +WindiePicBack:: INCBIN "gfx/pokemon/windie/back.pic" +NyoromoPicFront:: INCBIN "gfx/pokemon/nyoromo/front.pic" +NyoromoPicBack:: INCBIN "gfx/pokemon/nyoromo/back.pic" +NyorozoPicFront:: INCBIN "gfx/pokemon/nyorozo/front.pic" +NyorozoPicBack:: INCBIN "gfx/pokemon/nyorozo/back.pic" +NyorobonPicFront:: INCBIN "gfx/pokemon/nyorobon/front.pic" +NyorobonPicBack:: INCBIN "gfx/pokemon/nyorobon/back.pic" +CaseyPicFront:: INCBIN "gfx/pokemon/casey/front.pic" +CaseyPicBack:: INCBIN "gfx/pokemon/casey/back.pic" +YungererPicFront:: INCBIN "gfx/pokemon/yungerer/front.pic" +YungererPicBack:: INCBIN "gfx/pokemon/yungerer/back.pic" +FoodinPicFront:: INCBIN "gfx/pokemon/foodin/front.pic" +FoodinPicBack:: INCBIN "gfx/pokemon/foodin/back.pic" +WanrikyPicFront:: INCBIN "gfx/pokemon/wanriky/front.pic" +WanrikyPicBack:: INCBIN "gfx/pokemon/wanriky/back.pic" +GorikyPicFront:: INCBIN "gfx/pokemon/goriky/front.pic" +GorikyPicBack:: INCBIN "gfx/pokemon/goriky/back.pic" +KairikyPicFront:: INCBIN "gfx/pokemon/kairiky/front.pic" +KairikyPicBack:: INCBIN "gfx/pokemon/kairiky/back.pic" +MadatsubomiPicFront:: INCBIN "gfx/pokemon/madatsubomi/front.pic" +MadatsubomiPicBack:: INCBIN "gfx/pokemon/madatsubomi/back.pic" +UtsudonPicFront:: INCBIN "gfx/pokemon/utsudon/front.pic" +UtsudonPicBack:: INCBIN "gfx/pokemon/utsudon/back.pic" +UtsubotPicFront:: INCBIN "gfx/pokemon/utsubot/front.pic" +UtsubotPicBack:: INCBIN "gfx/pokemon/utsubot/back.pic" +MenokuragePicFront:: INCBIN "gfx/pokemon/menokurage/front.pic" +MenokuragePicBack:: INCBIN "gfx/pokemon/menokurage/back.pic" +DokukuragePicFront:: INCBIN "gfx/pokemon/dokukurage/front.pic" +DokukuragePicBack:: INCBIN "gfx/pokemon/dokukurage/back.pic" +IsitsubutePicFront:: INCBIN "gfx/pokemon/isitsubute/front.pic" +IsitsubutePicBack:: INCBIN "gfx/pokemon/isitsubute/back.pic" +GolonePicFront:: INCBIN "gfx/pokemon/golone/front.pic" +GolonePicBack:: INCBIN "gfx/pokemon/golone/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 4", ROMX + +GolonyaPicFront:: INCBIN "gfx/pokemon/golonya/front.pic" +GolonyaPicBack:: INCBIN "gfx/pokemon/golonya/back.pic" +PonytaPicFront:: INCBIN "gfx/pokemon/ponyta/front.pic" +PonytaPicBack:: INCBIN "gfx/pokemon/ponyta/back.pic" +GallopPicFront:: INCBIN "gfx/pokemon/gallop/front.pic" +GallopPicBack:: INCBIN "gfx/pokemon/gallop/back.pic" +YadonPicFront:: INCBIN "gfx/pokemon/yadon/front.pic" +YadonPicBack:: INCBIN "gfx/pokemon/yadon/back.pic" +YadoranPicFront:: INCBIN "gfx/pokemon/yadoran/front.pic" +YadoranPicBack:: INCBIN "gfx/pokemon/yadoran/back.pic" +CoilPicFront:: INCBIN "gfx/pokemon/coil/front.pic" +CoilPicBack:: INCBIN "gfx/pokemon/coil/back.pic" +RarecoilPicFront:: INCBIN "gfx/pokemon/rarecoil/front.pic" +RarecoilPicBack:: INCBIN "gfx/pokemon/rarecoil/back.pic" +KamonegiPicFront:: INCBIN "gfx/pokemon/kamonegi/front.pic" +KamonegiPicBack:: INCBIN "gfx/pokemon/kamonegi/back.pic" +DodoPicFront:: INCBIN "gfx/pokemon/dodo/front.pic" +DodoPicBack:: INCBIN "gfx/pokemon/dodo/back.pic" +DodorioPicFront:: INCBIN "gfx/pokemon/dodorio/front.pic" +DodorioPicBack:: INCBIN "gfx/pokemon/dodorio/back.pic" +PawouPicFront:: INCBIN "gfx/pokemon/pawou/front.pic" +PawouPicBack:: INCBIN "gfx/pokemon/pawou/back.pic" +JugonPicFront:: INCBIN "gfx/pokemon/jugon/front.pic" +JugonPicBack:: INCBIN "gfx/pokemon/jugon/back.pic" +BetbeterPicFront:: INCBIN "gfx/pokemon/betbeter/front.pic" +BetbeterPicBack:: INCBIN "gfx/pokemon/betbeter/back.pic" +BetbetonPicFront:: INCBIN "gfx/pokemon/betbeton/front.pic" +BetbetonPicBack:: INCBIN "gfx/pokemon/betbeton/back.pic" +ShellderPicFront:: INCBIN "gfx/pokemon/shellder/front.pic" +ShellderPicBack:: INCBIN "gfx/pokemon/shellder/back.pic" +ParshenPicFront:: INCBIN "gfx/pokemon/parshen/front.pic" +ParshenPicBack:: INCBIN "gfx/pokemon/parshen/back.pic" +GhosPicFront:: INCBIN "gfx/pokemon/ghos/front.pic" +GhosPicBack:: INCBIN "gfx/pokemon/ghos/back.pic" +GhostPicFront:: INCBIN "gfx/pokemon/ghost/front.pic" +GhostPicBack:: INCBIN "gfx/pokemon/ghost/back.pic" +GangarPicFront:: INCBIN "gfx/pokemon/gangar/front.pic" +GangarPicBack:: INCBIN "gfx/pokemon/gangar/back.pic" +IwarkPicFront:: INCBIN "gfx/pokemon/iwark/front.pic" +IwarkPicBack:: INCBIN "gfx/pokemon/iwark/back.pic" +SleepePicFront:: INCBIN "gfx/pokemon/sleepe/front.pic" +SleepePicBack:: INCBIN "gfx/pokemon/sleepe/back.pic" +SleeperPicFront:: INCBIN "gfx/pokemon/sleeper/front.pic" +SleeperPicBack:: INCBIN "gfx/pokemon/sleeper/back.pic" +CrabPicFront:: INCBIN "gfx/pokemon/crab/front.pic" +CrabPicBack:: INCBIN "gfx/pokemon/crab/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 5", ROMX + +KinglerPicFront:: INCBIN "gfx/pokemon/kingler/front.pic" +KinglerPicBack:: INCBIN "gfx/pokemon/kingler/back.pic" +BiriridamaPicFront:: INCBIN "gfx/pokemon/biriridama/front.pic" +BiriridamaPicBack:: INCBIN "gfx/pokemon/biriridama/back.pic" +MaruminePicFront:: INCBIN "gfx/pokemon/marumine/front.pic" +MaruminePicBack:: INCBIN "gfx/pokemon/marumine/back.pic" +TamatamaPicFront:: INCBIN "gfx/pokemon/tamatama/front.pic" +TamatamaPicBack:: INCBIN "gfx/pokemon/tamatama/back.pic" +NassyPicFront:: INCBIN "gfx/pokemon/nassy/front.pic" +NassyPicBack:: INCBIN "gfx/pokemon/nassy/back.pic" +KarakaraPicFront:: INCBIN "gfx/pokemon/karakara/front.pic" +KarakaraPicBack:: INCBIN "gfx/pokemon/karakara/back.pic" +GaragaraPicFront:: INCBIN "gfx/pokemon/garagara/front.pic" +GaragaraPicBack:: INCBIN "gfx/pokemon/garagara/back.pic" +SawamularPicFront:: INCBIN "gfx/pokemon/sawamular/front.pic" +SawamularPicBack:: INCBIN "gfx/pokemon/sawamular/back.pic" +EbiwalarPicFront:: INCBIN "gfx/pokemon/ebiwalar/front.pic" +EbiwalarPicBack:: INCBIN "gfx/pokemon/ebiwalar/back.pic" +BeroringaPicFront:: INCBIN "gfx/pokemon/beroringa/front.pic" +BeroringaPicBack:: INCBIN "gfx/pokemon/beroringa/back.pic" +DogarsPicFront:: INCBIN "gfx/pokemon/dogars/front.pic" +DogarsPicBack:: INCBIN "gfx/pokemon/dogars/back.pic" +MatadogasPicFront:: INCBIN "gfx/pokemon/matadogas/front.pic" +MatadogasPicBack:: INCBIN "gfx/pokemon/matadogas/back.pic" +SihornPicFront:: INCBIN "gfx/pokemon/sihorn/front.pic" +SihornPicBack:: INCBIN "gfx/pokemon/sihorn/back.pic" +SidonPicFront:: INCBIN "gfx/pokemon/sidon/front.pic" +SidonPicBack:: INCBIN "gfx/pokemon/sidon/back.pic" +LuckyPicFront:: INCBIN "gfx/pokemon/lucky/front.pic" +LuckyPicBack:: INCBIN "gfx/pokemon/lucky/back.pic" +MonjaraPicFront:: INCBIN "gfx/pokemon/monjara/front.pic" +MonjaraPicBack:: INCBIN "gfx/pokemon/monjara/back.pic" +GaruraPicFront:: INCBIN "gfx/pokemon/garura/front.pic" +GaruraPicBack:: INCBIN "gfx/pokemon/garura/back.pic" +TattuPicFront:: INCBIN "gfx/pokemon/tattu/front.pic" +TattuPicBack:: INCBIN "gfx/pokemon/tattu/back.pic" +SeadraPicFront:: INCBIN "gfx/pokemon/seadra/front.pic" +SeadraPicBack:: INCBIN "gfx/pokemon/seadra/back.pic" +TosakintoPicFront:: INCBIN "gfx/pokemon/tosakinto/front.pic" +TosakintoPicBack:: INCBIN "gfx/pokemon/tosakinto/back.pic" +AzumaoPicFront:: INCBIN "gfx/pokemon/azumao/front.pic" +AzumaoPicBack:: INCBIN "gfx/pokemon/azumao/back.pic" +HitodemanPicFront:: INCBIN "gfx/pokemon/hitodeman/front.pic" +HitodemanPicBack:: INCBIN "gfx/pokemon/hitodeman/back.pic" +StarmiePicFront:: INCBIN "gfx/pokemon/starmie/front.pic" +StarmiePicBack:: INCBIN "gfx/pokemon/starmie/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 6", ROMX + +BarrierdPicFront:: INCBIN "gfx/pokemon/barrierd/front.pic" +BarrierdPicBack:: INCBIN "gfx/pokemon/barrierd/back.pic" +StrikePicFront:: INCBIN "gfx/pokemon/strike/front.pic" +StrikePicBack:: INCBIN "gfx/pokemon/strike/back.pic" +RougelaPicFront:: INCBIN "gfx/pokemon/rougela/front.pic" +RougelaPicBack:: INCBIN "gfx/pokemon/rougela/back.pic" +ElebooPicFront:: INCBIN "gfx/pokemon/eleboo/front.pic" +ElebooPicBack:: INCBIN "gfx/pokemon/eleboo/back.pic" +BooberPicFront:: INCBIN "gfx/pokemon/boober/front.pic" +BooberPicBack:: INCBIN "gfx/pokemon/boober/back.pic" +KailiosPicFront:: INCBIN "gfx/pokemon/kailios/front.pic" +KailiosPicBack:: INCBIN "gfx/pokemon/kailios/back.pic" +KentaurosPicFront:: INCBIN "gfx/pokemon/kentauros/front.pic" +KentaurosPicBack:: INCBIN "gfx/pokemon/kentauros/back.pic" +KoikingPicFront:: INCBIN "gfx/pokemon/koiking/front.pic" +KoikingPicBack:: INCBIN "gfx/pokemon/koiking/back.pic" +GyaradosPicFront:: INCBIN "gfx/pokemon/gyarados/front.pic" +GyaradosPicBack:: INCBIN "gfx/pokemon/gyarados/back.pic" +LaplacePicFront:: INCBIN "gfx/pokemon/laplace/front.pic" +LaplacePicBack:: INCBIN "gfx/pokemon/laplace/back.pic" +MetamonPicFront:: INCBIN "gfx/pokemon/metamon/front.pic" +MetamonPicBack:: INCBIN "gfx/pokemon/metamon/back.pic" +EievuiPicFront:: INCBIN "gfx/pokemon/eievui/front.pic" +EievuiPicBack:: INCBIN "gfx/pokemon/eievui/back.pic" +ShowersPicFront:: INCBIN "gfx/pokemon/showers/front.pic" +ShowersPicBack:: INCBIN "gfx/pokemon/showers/back.pic" +ThundersPicFront:: INCBIN "gfx/pokemon/thunders/front.pic" +ThundersPicBack:: INCBIN "gfx/pokemon/thunders/back.pic" +BoosterPicFront:: INCBIN "gfx/pokemon/booster/front.pic" +BoosterPicBack:: INCBIN "gfx/pokemon/booster/back.pic" +PorygonPicFront:: INCBIN "gfx/pokemon/porygon/front.pic" +PorygonPicBack:: INCBIN "gfx/pokemon/porygon/back.pic" +OmnitePicFront:: INCBIN "gfx/pokemon/omnite/front.pic" +OmnitePicBack:: INCBIN "gfx/pokemon/omnite/back.pic" +OmstarPicFront:: INCBIN "gfx/pokemon/omstar/front.pic" +OmstarPicBack:: INCBIN "gfx/pokemon/omstar/back.pic" +KabutoPicFront:: INCBIN "gfx/pokemon/kabuto/front.pic" +KabutoPicBack:: INCBIN "gfx/pokemon/kabuto/back.pic" +KabutopsPicFront:: INCBIN "gfx/pokemon/kabutops/front.pic" +KabutopsPicBack:: INCBIN "gfx/pokemon/kabutops/back.pic" +PteraPicFront:: INCBIN "gfx/pokemon/ptera/front.pic" +PteraPicBack:: INCBIN "gfx/pokemon/ptera/back.pic" +KabigonPicFront:: INCBIN "gfx/pokemon/kabigon/front.pic" +KabigonPicBack:: INCBIN "gfx/pokemon/kabigon/back.pic" +FreezerPicFront:: INCBIN "gfx/pokemon/freezer/front.pic" +FreezerPicBack:: INCBIN "gfx/pokemon/freezer/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 7", ROMX + +ThunderPicFront:: INCBIN "gfx/pokemon/thunder/front.pic" +ThunderPicBack:: INCBIN "gfx/pokemon/thunder/back.pic" +FirePicFront:: INCBIN "gfx/pokemon/fire/front.pic" +FirePicBack:: INCBIN "gfx/pokemon/fire/back.pic" +MiniryuPicFront:: INCBIN "gfx/pokemon/miniryu/front.pic" +MiniryuPicBack:: INCBIN "gfx/pokemon/miniryu/back.pic" +HakuryuPicFront:: INCBIN "gfx/pokemon/hakuryu/front.pic" +HakuryuPicBack:: INCBIN "gfx/pokemon/hakuryu/back.pic" +KairyuPicFront:: INCBIN "gfx/pokemon/kairyu/front.pic" +KairyuPicBack:: INCBIN "gfx/pokemon/kairyu/back.pic" +MewtwoPicFront:: INCBIN "gfx/pokemon/mewtwo/front.pic" +MewtwoPicBack:: INCBIN "gfx/pokemon/mewtwo/back.pic" +MewPicFront:: INCBIN "gfx/pokemon/mew/front.pic" +MewPicBack:: INCBIN "gfx/pokemon/mew/back.pic" +HappaPicFront:: INCBIN "gfx/pokemon/happa/front.pic" +HappaPicBack:: INCBIN "gfx/pokemon/happa/back.pic" +HanamoguraPicFront:: INCBIN "gfx/pokemon/hanamogura/front.pic" +HanamoguraPicBack:: INCBIN "gfx/pokemon/hanamogura/back.pic" +HanaryuPicFront:: INCBIN "gfx/pokemon/hanaryu/front.pic" +HanaryuPicBack:: INCBIN "gfx/pokemon/hanaryu/back.pic" +HonogumaPicFront:: INCBIN "gfx/pokemon/honoguma/front.pic" +HonogumaPicBack:: INCBIN "gfx/pokemon/honoguma/back.pic" +VolbearPicFront:: INCBIN "gfx/pokemon/volbear/front.pic" +VolbearPicBack:: INCBIN "gfx/pokemon/volbear/back.pic" +DynabearPicFront:: INCBIN "gfx/pokemon/dynabear/front.pic" +DynabearPicBack:: INCBIN "gfx/pokemon/dynabear/back.pic" +KurusuPicFront:: INCBIN "gfx/pokemon/kurusu/front.pic" +KurusuPicBack:: INCBIN "gfx/pokemon/kurusu/back.pic" +AquaPicFront:: INCBIN "gfx/pokemon/aqua/front.pic" +AquaPicBack:: INCBIN "gfx/pokemon/aqua/back.pic" +AquariaPicFront:: INCBIN "gfx/pokemon/aquaria/front.pic" +AquariaPicBack:: INCBIN "gfx/pokemon/aquaria/back.pic" +HohoPicFront:: INCBIN "gfx/pokemon/hoho/front.pic" +HohoPicBack:: INCBIN "gfx/pokemon/hoho/back.pic" +BoboPicFront:: INCBIN "gfx/pokemon/bobo/front.pic" +BoboPicBack:: INCBIN "gfx/pokemon/bobo/back.pic" +PachimeePicFront:: INCBIN "gfx/pokemon/pachimee/front.pic" +PachimeePicBack:: INCBIN "gfx/pokemon/pachimee/back.pic" +MokokoPicFront:: INCBIN "gfx/pokemon/mokoko/front.pic" +MokokoPicBack:: INCBIN "gfx/pokemon/mokoko/back.pic" +DenryuPicFront:: INCBIN "gfx/pokemon/denryu/front.pic" +DenryuPicBack:: INCBIN "gfx/pokemon/denryu/back.pic" +MikonPicFront:: INCBIN "gfx/pokemon/mikon/front.pic" +MikonPicBack:: INCBIN "gfx/pokemon/mikon/back.pic" +MonjaPicFront:: INCBIN "gfx/pokemon/monja/front.pic" +MonjaPicBack:: INCBIN "gfx/pokemon/monja/back.pic" +JaranraPicFront:: INCBIN "gfx/pokemon/jaranra/front.pic" +JaranraPicBack:: INCBIN "gfx/pokemon/jaranra/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 8", ROMX + +HaneeiPicFront:: INCBIN "gfx/pokemon/haneei/front.pic" +HaneeiPicBack:: INCBIN "gfx/pokemon/haneei/back.pic" +PukuPicFront:: INCBIN "gfx/pokemon/puku/front.pic" +PukuPicBack:: INCBIN "gfx/pokemon/puku/back.pic" +ShibirefuguPicFront:: INCBIN "gfx/pokemon/shibirefugu/front.pic" +ShibirefuguPicBack:: INCBIN "gfx/pokemon/shibirefugu/back.pic" +PichuPicFront:: INCBIN "gfx/pokemon/pichu/front.pic" +PichuPicBack:: INCBIN "gfx/pokemon/pichu/back.pic" +PyPicFront:: INCBIN "gfx/pokemon/py/front.pic" +PyPicBack:: INCBIN "gfx/pokemon/py/back.pic" +PupurinPicFront:: INCBIN "gfx/pokemon/pupurin/front.pic" +PupurinPicBack:: INCBIN "gfx/pokemon/pupurin/back.pic" +MizuuoPicFront:: INCBIN "gfx/pokemon/mizuuo/front.pic" +MizuuoPicBack:: INCBIN "gfx/pokemon/mizuuo/back.pic" +NatyPicFront:: INCBIN "gfx/pokemon/naty/front.pic" +NatyPicBack:: INCBIN "gfx/pokemon/naty/back.pic" +NatioPicFront:: INCBIN "gfx/pokemon/natio/front.pic" +NatioPicBack:: INCBIN "gfx/pokemon/natio/back.pic" +GyopinPicFront:: INCBIN "gfx/pokemon/gyopin/front.pic" +GyopinPicBack:: INCBIN "gfx/pokemon/gyopin/back.pic" +MarilPicFront:: INCBIN "gfx/pokemon/maril/front.pic" +MarilPicBack:: INCBIN "gfx/pokemon/maril/back.pic" +Manbo1PicFront:: INCBIN "gfx/pokemon/manbo1/front.pic" +Manbo1PicBack:: INCBIN "gfx/pokemon/manbo1/back.pic" +IkariPicFront:: INCBIN "gfx/pokemon/ikari/front.pic" +IkariPicBack:: INCBIN "gfx/pokemon/ikari/back.pic" +GrotessPicFront:: INCBIN "gfx/pokemon/grotess/front.pic" +GrotessPicBack:: INCBIN "gfx/pokemon/grotess/back.pic" +EksingPicFront:: INCBIN "gfx/pokemon/eksing/front.pic" +EksingPicBack:: INCBIN "gfx/pokemon/eksing/back.pic" +ParaPicFront:: INCBIN "gfx/pokemon/para/front.pic" +ParaPicBack:: INCBIN "gfx/pokemon/para/back.pic" +KokumoPicFront:: INCBIN "gfx/pokemon/kokumo/front.pic" +KokumoPicBack:: INCBIN "gfx/pokemon/kokumo/back.pic" +TwoheadPicFront:: INCBIN "gfx/pokemon/twohead/front.pic" +TwoheadPicBack:: INCBIN "gfx/pokemon/twohead/back.pic" +YoroidoriPicFront:: INCBIN "gfx/pokemon/yoroidori/front.pic" +YoroidoriPicBack:: INCBIN "gfx/pokemon/yoroidori/back.pic" +AnimonPicFront:: INCBIN "gfx/pokemon/animon/front.pic" +AnimonPicBack:: INCBIN "gfx/pokemon/animon/back.pic" +HinazuPicFront:: INCBIN "gfx/pokemon/hinazu/front.pic" +HinazuPicBack:: INCBIN "gfx/pokemon/hinazu/back.pic" +SunnyPicFront:: INCBIN "gfx/pokemon/sunny/front.pic" +SunnyPicBack:: INCBIN "gfx/pokemon/sunny/back.pic" +PaonPicFront:: INCBIN "gfx/pokemon/paon/front.pic" +PaonPicBack:: INCBIN "gfx/pokemon/paon/back.pic" +DonphanPicFront:: INCBIN "gfx/pokemon/donphan/front.pic" +DonphanPicBack:: INCBIN "gfx/pokemon/donphan/back.pic" +TwinzPicFront:: INCBIN "gfx/pokemon/twinz/front.pic" +TwinzPicBack:: INCBIN "gfx/pokemon/twinz/back.pic" +KirinrikiPicFront:: INCBIN "gfx/pokemon/kirinriki/front.pic" +KirinrikiPicBack:: INCBIN "gfx/pokemon/kirinriki/back.pic" +PainterPicFront:: INCBIN "gfx/pokemon/painter/front.pic" +PainterPicBack:: INCBIN "gfx/pokemon/painter/back.pic" +KounyaPicFront:: INCBIN "gfx/pokemon/kounya/front.pic" +KounyaPicBack:: INCBIN "gfx/pokemon/kounya/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 9", ROMX + +RinrinPicFront:: INCBIN "gfx/pokemon/rinrin/front.pic" +RinrinPicBack:: INCBIN "gfx/pokemon/rinrin/back.pic" +BerurunPicFront:: INCBIN "gfx/pokemon/berurun/front.pic" +BerurunPicBack:: INCBIN "gfx/pokemon/berurun/back.pic" +NyorotonoPicFront:: INCBIN "gfx/pokemon/nyorotono/front.pic" +NyorotonoPicBack:: INCBIN "gfx/pokemon/nyorotono/back.pic" +YadokingPicFront:: INCBIN "gfx/pokemon/yadoking/front.pic" +YadokingPicBack:: INCBIN "gfx/pokemon/yadoking/back.pic" +RedibaPicFront:: INCBIN "gfx/pokemon/rediba/front.pic" +RedibaPicBack:: INCBIN "gfx/pokemon/rediba/back.pic" +MitsuboshiPicFront:: INCBIN "gfx/pokemon/mitsuboshi/front.pic" +MitsuboshiPicBack:: INCBIN "gfx/pokemon/mitsuboshi/back.pic" +PuchicornPicFront:: INCBIN "gfx/pokemon/puchicorn/front.pic" +PuchicornPicBack:: INCBIN "gfx/pokemon/puchicorn/back.pic" +EifiePicFront:: INCBIN "gfx/pokemon/eifie/front.pic" +EifiePicBack:: INCBIN "gfx/pokemon/eifie/back.pic" +BlackyPicFront:: INCBIN "gfx/pokemon/blacky/front.pic" +BlackyPicBack:: INCBIN "gfx/pokemon/blacky/back.pic" +TurbanPicFront:: INCBIN "gfx/pokemon/turban/front.pic" +TurbanPicBack:: INCBIN "gfx/pokemon/turban/back.pic" +BetbabyPicFront:: INCBIN "gfx/pokemon/betbaby/front.pic" +BetbabyPicBack:: INCBIN "gfx/pokemon/betbaby/back.pic" +TeppouoPicFront:: INCBIN "gfx/pokemon/teppouo/front.pic" +TeppouoPicBack:: INCBIN "gfx/pokemon/teppouo/back.pic" +OkutankPicFront:: INCBIN "gfx/pokemon/okutank/front.pic" +OkutankPicBack:: INCBIN "gfx/pokemon/okutank/back.pic" +GonguPicFront:: INCBIN "gfx/pokemon/gongu/front.pic" +GonguPicBack:: INCBIN "gfx/pokemon/gongu/back.pic" +KapoererPicFront:: INCBIN "gfx/pokemon/kapoerer/front.pic" +KapoererPicBack:: INCBIN "gfx/pokemon/kapoerer/back.pic" +PudiePicFront:: INCBIN "gfx/pokemon/pudie/front.pic" +PudiePicBack:: INCBIN "gfx/pokemon/pudie/back.pic" +HanekoPicFront:: INCBIN "gfx/pokemon/haneko/front.pic" +HanekoPicBack:: INCBIN "gfx/pokemon/haneko/back.pic" +PoponekoPicFront:: INCBIN "gfx/pokemon/poponeko/front.pic" +PoponekoPicBack:: INCBIN "gfx/pokemon/poponeko/back.pic" +WatanekoPicFront:: INCBIN "gfx/pokemon/wataneko/front.pic" +WatanekoPicBack:: INCBIN "gfx/pokemon/wataneko/back.pic" +BaririnaPicFront:: INCBIN "gfx/pokemon/baririna/front.pic" +BaririnaPicBack:: INCBIN "gfx/pokemon/baririna/back.pic" +LipPicFront:: INCBIN "gfx/pokemon/lip/front.pic" +LipPicBack:: INCBIN "gfx/pokemon/lip/back.pic" +ElebabyPicFront:: INCBIN "gfx/pokemon/elebaby/front.pic" +ElebabyPicBack:: INCBIN "gfx/pokemon/elebaby/back.pic" +BoobyPicFront:: INCBIN "gfx/pokemon/booby/front.pic" +BoobyPicBack:: INCBIN "gfx/pokemon/booby/back.pic" +KireihanaPicFront:: INCBIN "gfx/pokemon/kireihana/front.pic" +KireihanaPicBack:: INCBIN "gfx/pokemon/kireihana/back.pic" +TsubomittoPicFront:: INCBIN "gfx/pokemon/tsubomitto/front.pic" +TsubomittoPicBack:: INCBIN "gfx/pokemon/tsubomitto/back.pic" +MiltankPicFront:: INCBIN "gfx/pokemon/miltank/front.pic" +MiltankPicBack:: INCBIN "gfx/pokemon/miltank/back.pic" +BombseekerPicFront:: INCBIN "gfx/pokemon/bombseeker/front.pic" +BombseekerPicBack:: INCBIN "gfx/pokemon/bombseeker/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 10", ROMX + +GiftPicFront:: INCBIN "gfx/pokemon/gift/front.pic" +GiftPicBack:: INCBIN "gfx/pokemon/gift/back.pic" +KotoraPicFront:: INCBIN "gfx/pokemon/kotora/front.pic" +KotoraPicBack:: INCBIN "gfx/pokemon/kotora/back.pic" +RaitoraPicFront:: INCBIN "gfx/pokemon/raitora/front.pic" +RaitoraPicBack:: INCBIN "gfx/pokemon/raitora/back.pic" +MadamePicFront:: INCBIN "gfx/pokemon/madame/front.pic" +MadamePicBack:: INCBIN "gfx/pokemon/madame/back.pic" +NorowaraPicFront:: INCBIN "gfx/pokemon/norowara/front.pic" +NorowaraPicBack:: INCBIN "gfx/pokemon/norowara/back.pic" +KyonpanPicFront:: INCBIN "gfx/pokemon/kyonpan/front.pic" +KyonpanPicBack:: INCBIN "gfx/pokemon/kyonpan/back.pic" +YamikarasuPicFront:: INCBIN "gfx/pokemon/yamikarasu/front.pic" +YamikarasuPicBack:: INCBIN "gfx/pokemon/yamikarasu/back.pic" +HappiPicFront:: INCBIN "gfx/pokemon/happi/front.pic" +HappiPicBack:: INCBIN "gfx/pokemon/happi/back.pic" +ScissorsPicFront:: INCBIN "gfx/pokemon/scissors/front.pic" +ScissorsPicBack:: INCBIN "gfx/pokemon/scissors/back.pic" +PurakkusuPicFront:: INCBIN "gfx/pokemon/purakkusu/front.pic" +PurakkusuPicBack:: INCBIN "gfx/pokemon/purakkusu/back.pic" +DevilPicFront:: INCBIN "gfx/pokemon/devil/front.pic" +DevilPicBack:: INCBIN "gfx/pokemon/devil/back.pic" +HelgaaPicFront:: INCBIN "gfx/pokemon/helgaa/front.pic" +HelgaaPicBack:: INCBIN "gfx/pokemon/helgaa/back.pic" +WolfmanPicFront:: INCBIN "gfx/pokemon/wolfman/front.pic" +WolfmanPicBack:: INCBIN "gfx/pokemon/wolfman/back.pic" +WarwolfPicFront:: INCBIN "gfx/pokemon/warwolf/front.pic" +WarwolfPicBack:: INCBIN "gfx/pokemon/warwolf/back.pic" +Porygon2PicFront:: INCBIN "gfx/pokemon/porygon2/front.pic" +Porygon2PicBack:: INCBIN "gfx/pokemon/porygon2/back.pic" +NameilPicFront:: INCBIN "gfx/pokemon/nameil/front.pic" +NameilPicBack:: INCBIN "gfx/pokemon/nameil/back.pic" +HaganeilPicFront:: INCBIN "gfx/pokemon/haganeil/front.pic" +HaganeilPicBack:: INCBIN "gfx/pokemon/haganeil/back.pic" +KingdraPicFront:: INCBIN "gfx/pokemon/kingdra/front.pic" +KingdraPicBack:: INCBIN "gfx/pokemon/kingdra/back.pic" +RaiPicFront:: INCBIN "gfx/pokemon/rai/front.pic" + db 0 ; how did that get here? +RaiPicBack:: INCBIN "gfx/pokemon/rai/back.pic" +EnPicFront:: INCBIN "gfx/pokemon/en/front.pic" +EnPicBack:: INCBIN "gfx/pokemon/en/back.pic" +SuiPicFront:: INCBIN "gfx/pokemon/sui/front.pic" +SuiPicBack:: INCBIN "gfx/pokemon/sui/back.pic" +NyulaPicFront:: INCBIN "gfx/pokemon/nyula/front.pic" +NyulaPicBack:: INCBIN "gfx/pokemon/nyula/back.pic" + + +SECTION "gfx/pokemon/pkmn_pics.asm@PKMN Pics 11", ROMX + +HououPicFront:: INCBIN "gfx/pokemon/houou/front.pic" +HououPicBack:: INCBIN "gfx/pokemon/houou/back.pic" +TogepyPicFront:: INCBIN "gfx/pokemon/togepy/front.pic" +TogepyPicBack:: INCBIN "gfx/pokemon/togepy/back.pic" +BuluPicFront:: INCBIN "gfx/pokemon/bulu/front.pic" +BuluPicBack:: INCBIN "gfx/pokemon/bulu/back.pic" +TailPicFront:: INCBIN "gfx/pokemon/tail/front.pic" +TailPicBack:: INCBIN "gfx/pokemon/tail/back.pic" +LeafyPicFront:: INCBIN "gfx/pokemon/leafy/front.pic" +LeafyPicBack:: INCBIN "gfx/pokemon/leafy/back.pic" diff --git a/gfx/sgb/corrupted_9e1c.png b/gfx/sgb/corrupted_9e1c.png deleted file mode 100755 index f124031..0000000 Binary files a/gfx/sgb/corrupted_9e1c.png and /dev/null differ diff --git a/gfx/sgb/corrupted_a66c.png b/gfx/sgb/corrupted_a66c.png deleted file mode 100755 index a8bab25..0000000 Binary files a/gfx/sgb/corrupted_a66c.png and /dev/null differ diff --git a/gfx/sgb/corrupted_b1e3.png b/gfx/sgb/corrupted_b1e3.png deleted file mode 100755 index 0ee2cb5..0000000 Binary files a/gfx/sgb/corrupted_b1e3.png and /dev/null differ diff --git a/gfx/sgb/corrupted_ba93.png b/gfx/sgb/corrupted_ba93.png deleted file mode 100755 index dfd88be..0000000 Binary files a/gfx/sgb/corrupted_ba93.png and /dev/null differ diff --git a/gfx/sgb/sgb_border_gold_corrupted.png b/gfx/sgb/sgb_border_gold_corrupted.png deleted file mode 100755 index 740bbd1..0000000 Binary files a/gfx/sgb/sgb_border_gold_corrupted.png and /dev/null differ diff --git a/gfx/sgb/sgb_border_silver_corrupted.png b/gfx/sgb/sgb_border_silver_corrupted.png deleted file mode 100755 index 5b32a8e..0000000 Binary files a/gfx/sgb/sgb_border_silver_corrupted.png and /dev/null differ diff --git a/hram.asm b/hram.asm deleted file mode 100644 index 4acfbfa..0000000 --- a/hram.asm +++ /dev/null @@ -1,243 +0,0 @@ -SECTION "HRAM", HRAM - -hOAMDMA:: - ds 10 - - ds 3 ; TODO - -hRTCHours:: db -hRTCMinutes:: db -hRTCSeconds:: db -hRTCDays:: db - ds 2 ; TODO -hRTCStatusFlags:: db - ds 3 ; TODO - -hVBlankCounter:: - db - -hROMBank:: - db - - -hVBlank:: - db - -hMapEntryMethod:: - db - -hStartmenuCloseAndSelectHookEnable:: db - -hStartmenuCloseAndSelectHookTemp:: db - -hJoypadUp:: db -; Raw Joypad Up Event -; A pressed key was released -hJoypadDown:: db -; Raw Joypad Down Event -; An unpressed key was pressed -hJoypadState:: db -; Raw Joypad State -; State of all keys during current frame -hJoypadSum:: db -; Raw Joypad State Sum -; Sum of all keys that were pressed -; since hJoypadSum was last cleared - - ds 1; TODO -hJoyDown:: db -hJoyState:: db -hJoySum:: db - -hJoyDebounceSrc:: db -; hJoySum will be updated from -; 00 - hJoyDown -; <> - hJoyState -; See GetJoypadDebounced - -hJoypadState2:: db - - ds 6 ; TODO -hGraphicStartTile:: db -hMoveMon:: db - -UNION - -hTextBoxCursorBlinkInterval:: ds 2 - -NEXTU -hEventCollisionException:: db -hEventID:: db - -NEXTU - -hSpriteWidth:: -hSpriteInterlaceCounter:: - db -hSpriteHeight:: - db - -NEXTU - -hConnectionStripLength:: db -hConnectedMapWidth:: db - -NEXTU - -hMapObjectIndexBuffer:: db -hObjectStructIndexBuffer:: db - -ENDU - -hSpriteOffset:: - db - - db ; TODO - -UNION - -hProduct:: -hDividend:: - ; ds 4 - db - -hMultiplicand:: -hQuotient:: - ds 3 - -hMultiplier:: -hDivisor:: -hRemainder:: - db - -hMathBuffer:: - ds 5 - -NEXTU - -hPrintNumLeadingDigit:: db ; digit one place-value up -hPrintNumDividend:: ds 3 ; big-endian -hPrintNumDivisor:: ds 3 ; big-endian -hPrintNumTemp:: ds 3 ; big-endian - -ENDU - - ds 3 ; TODO - -hFFC0:: ds 1 - - ds 6 - -hFFC7:: db -hFFC8:: db -hFFC9:: db -hFFCA:: db -hFFCB:: db -hFFCC:: db - - ds 3 ; TODO - -hLCDCPointer:: - db - -hLYOverrideStart:: db -hLYOverrideEnd:: db - ds 1 ; TODO - - -hSerialReceived:: - db - -hLinkPlayerNumber:: - db - -hSerialIgnoringInitialData:: - db - - -hSerialSend:: - db -hSerialReceive:: - db - - -hSCX:: db -hSCY:: db -hWX:: db -hWY:: db - -hOverworldFlashlightEffect:: db -; Influences draw distance of map around HIRO -; Meant to go from 0 --> to desired distance -; or else graphical errors will occur. -; 0 - regular distance -; 1 - 14x14 tile block -; 2 - 10x10 tile block -; 3 - 6x 6 tile block -; 4 - 2x 2 tile block - -hBGMapMode:: - db - -hBGMapTransferPosition:: - db - -hBGMapAddress:: - dw - db ; TODO - -hSPTemp:: - dw - -hRedrawRowOrColumnMode:: db -; Used for redrawing BG in small updates -; instead of once completely for faster -; scrolling on overworld etc. -; Valid values: -; $00 - no redraw -; $01 - column redraw (move horizontally) -; $02 - row redraw (move vertically) -; $03 - flashlight row redraw 0 (move up) -; $04 - flashlight row redraw 0 (move down) -; $05 - flashlight column redraw 0 (move left) -; $06 - flashlight column redraw 0 (move right) -; $07 - flashlight row redraw 1 (move up) -; $08 - flashlight row redraw 1 (move down) -; $09 - flashlight column redraw 1 (move left) -; $0A - flashlight column redraw 1 (move right) -; $0B - flashlight row redraw 2 (move up) -; $0C - flashlight row redraw 2 (move down) -; $0D - flashlight column redraw 2 (move left) -; $0E - flashlight column redraw 2 (move right) -; $0F - flashlight row redraw 3 (move up) -; $10 - flashlight row redraw 3 (move down) -; $11 - flashlight column redraw 3 (move left) -; $12 - flashlight column redraw 3 (move right) - -hRedrawRowOrColumnDest:: ds 2 - -hMapAnims:: - db - -hTileAnimFrame:: - db - -hFFEA:: - db - -hFFEB:: db -hFFEC:: db -hFFED:: db - -hFFEE:: - db - -hRandomAdd:: db -hRandomSub:: db -hRTCRandom:: db - -hBattleTurn:: db - -hCurMapTextSubroutinePtr:: dw - - ; TODO diff --git a/layout.link b/layout.link index bdb22e0..f84ba9e 100644 --- a/layout.link +++ b/layout.link @@ -129,9 +129,9 @@ ROMX $02 "gfx.asm@Title Screen BG Decoration Border" "engine/dumps/bank02.asm@Function928b" "gfx.asm@SGB GFX" - "bin.asm@Unknownaebc" - "gfx.asm@Corrupted SGB GFX" - "bin.asm@Unknownbb43" + "slack.asm@Unknownaebc" + "slack.asm@Corrupted SGB GFX" + "slack.asm@Unknownbb43" ROMX $03 org $4000 diff --git a/macros.asm b/macros.asm deleted file mode 100644 index c18995a..0000000 --- a/macros.asm +++ /dev/null @@ -1,14 +0,0 @@ -INCLUDE "macros/enum.asm" -INCLUDE "macros/predef.asm" -;INCLUDE "macros/rst.asm" -INCLUDE "macros/data.asm" -INCLUDE "macros/code.asm" -INCLUDE "macros/gfx.asm" -INCLUDE "macros/coords.asm" -INCLUDE "macros/farcall.asm" -INCLUDE "macros/text.asm" -INCLUDE "macros/wram.asm" -INCLUDE "macros/audio.asm" -INCLUDE "macros/scripts.asm" -INCLUDE "macros/queue.asm" -INCLUDE "macros/maps.asm" diff --git a/ram/hram.asm b/ram/hram.asm new file mode 100644 index 0000000..4acfbfa --- /dev/null +++ b/ram/hram.asm @@ -0,0 +1,243 @@ +SECTION "HRAM", HRAM + +hOAMDMA:: + ds 10 + + ds 3 ; TODO + +hRTCHours:: db +hRTCMinutes:: db +hRTCSeconds:: db +hRTCDays:: db + ds 2 ; TODO +hRTCStatusFlags:: db + ds 3 ; TODO + +hVBlankCounter:: + db + +hROMBank:: + db + + +hVBlank:: + db + +hMapEntryMethod:: + db + +hStartmenuCloseAndSelectHookEnable:: db + +hStartmenuCloseAndSelectHookTemp:: db + +hJoypadUp:: db +; Raw Joypad Up Event +; A pressed key was released +hJoypadDown:: db +; Raw Joypad Down Event +; An unpressed key was pressed +hJoypadState:: db +; Raw Joypad State +; State of all keys during current frame +hJoypadSum:: db +; Raw Joypad State Sum +; Sum of all keys that were pressed +; since hJoypadSum was last cleared + + ds 1; TODO +hJoyDown:: db +hJoyState:: db +hJoySum:: db + +hJoyDebounceSrc:: db +; hJoySum will be updated from +; 00 - hJoyDown +; <> - hJoyState +; See GetJoypadDebounced + +hJoypadState2:: db + + ds 6 ; TODO +hGraphicStartTile:: db +hMoveMon:: db + +UNION + +hTextBoxCursorBlinkInterval:: ds 2 + +NEXTU +hEventCollisionException:: db +hEventID:: db + +NEXTU + +hSpriteWidth:: +hSpriteInterlaceCounter:: + db +hSpriteHeight:: + db + +NEXTU + +hConnectionStripLength:: db +hConnectedMapWidth:: db + +NEXTU + +hMapObjectIndexBuffer:: db +hObjectStructIndexBuffer:: db + +ENDU + +hSpriteOffset:: + db + + db ; TODO + +UNION + +hProduct:: +hDividend:: + ; ds 4 + db + +hMultiplicand:: +hQuotient:: + ds 3 + +hMultiplier:: +hDivisor:: +hRemainder:: + db + +hMathBuffer:: + ds 5 + +NEXTU + +hPrintNumLeadingDigit:: db ; digit one place-value up +hPrintNumDividend:: ds 3 ; big-endian +hPrintNumDivisor:: ds 3 ; big-endian +hPrintNumTemp:: ds 3 ; big-endian + +ENDU + + ds 3 ; TODO + +hFFC0:: ds 1 + + ds 6 + +hFFC7:: db +hFFC8:: db +hFFC9:: db +hFFCA:: db +hFFCB:: db +hFFCC:: db + + ds 3 ; TODO + +hLCDCPointer:: + db + +hLYOverrideStart:: db +hLYOverrideEnd:: db + ds 1 ; TODO + + +hSerialReceived:: + db + +hLinkPlayerNumber:: + db + +hSerialIgnoringInitialData:: + db + + +hSerialSend:: + db +hSerialReceive:: + db + + +hSCX:: db +hSCY:: db +hWX:: db +hWY:: db + +hOverworldFlashlightEffect:: db +; Influences draw distance of map around HIRO +; Meant to go from 0 --> to desired distance +; or else graphical errors will occur. +; 0 - regular distance +; 1 - 14x14 tile block +; 2 - 10x10 tile block +; 3 - 6x 6 tile block +; 4 - 2x 2 tile block + +hBGMapMode:: + db + +hBGMapTransferPosition:: + db + +hBGMapAddress:: + dw + db ; TODO + +hSPTemp:: + dw + +hRedrawRowOrColumnMode:: db +; Used for redrawing BG in small updates +; instead of once completely for faster +; scrolling on overworld etc. +; Valid values: +; $00 - no redraw +; $01 - column redraw (move horizontally) +; $02 - row redraw (move vertically) +; $03 - flashlight row redraw 0 (move up) +; $04 - flashlight row redraw 0 (move down) +; $05 - flashlight column redraw 0 (move left) +; $06 - flashlight column redraw 0 (move right) +; $07 - flashlight row redraw 1 (move up) +; $08 - flashlight row redraw 1 (move down) +; $09 - flashlight column redraw 1 (move left) +; $0A - flashlight column redraw 1 (move right) +; $0B - flashlight row redraw 2 (move up) +; $0C - flashlight row redraw 2 (move down) +; $0D - flashlight column redraw 2 (move left) +; $0E - flashlight column redraw 2 (move right) +; $0F - flashlight row redraw 3 (move up) +; $10 - flashlight row redraw 3 (move down) +; $11 - flashlight column redraw 3 (move left) +; $12 - flashlight column redraw 3 (move right) + +hRedrawRowOrColumnDest:: ds 2 + +hMapAnims:: + db + +hTileAnimFrame:: + db + +hFFEA:: + db + +hFFEB:: db +hFFEC:: db +hFFED:: db + +hFFEE:: + db + +hRandomAdd:: db +hRandomSub:: db +hRTCRandom:: db + +hBattleTurn:: db + +hCurMapTextSubroutinePtr:: dw + + ; TODO diff --git a/ram/sram.asm b/ram/sram.asm new file mode 100644 index 0000000..5200979 --- /dev/null +++ b/ram/sram.asm @@ -0,0 +1,14 @@ +INCLUDE "constants.asm" + +SECTION "Sprite Buffers", SRAM + +sSpriteBuffer0:: ds SPRITEBUFFERSIZE +sSpriteBuffer1:: ds SPRITEBUFFERSIZE +sSpriteBuffer2:: ds SPRITEBUFFERSIZE + +SECTION "Unknown, bank 0", SRAM + +s0_a600:: ds 7 ; TODO: properly label this and figure out exact size + +SECTION "Window Stack Top", SRAM +sWindowStackTop:: dw diff --git a/ram/vram.asm b/ram/vram.asm new file mode 100644 index 0000000..bf10e4d --- /dev/null +++ b/ram/vram.asm @@ -0,0 +1,62 @@ +INCLUDE "constants.asm" + +SECTION "VRAM", VRAM + +UNION + +vChars0:: + ds $80 tiles + +vChars1:: + ds $80 tiles + +vChars2:: + ds $80 tiles + +NEXTU + +; Battle/menu +vSprites:: + ds $80 tiles + +vFont:: + ds $80 tiles + +vFrontPic:: + ds 7 * 7 tiles + +NEXTU + +vNPCSprites:: + ds $80 tiles + +vNPCSprites2:: + ds $80 tiles + +vTileset:: + ds $20 tiles +vExteriorTileset:: + ds $40 tiles +vTilesetEnd:: + +NEXTU + + ds $80 tiles + +vTitleLogo:: + ds $80 tiles + +;vFrontPic:: + ds 7 * 7 tiles + +vTitleLogo2:: + ; TODO: what size? + +ENDU + + +vBGMap0:: + ds BG_MAP_WIDTH * BG_MAP_HEIGHT + +vBGMap1:: + ds BG_MAP_WIDTH * BG_MAP_HEIGHT diff --git a/ram/wram.asm b/ram/wram.asm new file mode 100644 index 0000000..9690975 --- /dev/null +++ b/ram/wram.asm @@ -0,0 +1,1282 @@ +INCLUDE "constants.asm" + + +SECTION "Music engine RAM", WRAM0 + +wMusic:: + +wChannels:: +wChannel1:: channel_struct wChannel1 +wChannel2:: channel_struct wChannel2 +wChannel3:: channel_struct wChannel3 +wChannel4:: channel_struct wChannel4 + +wSFXChannels:: +wChannel5:: channel_struct wChannel5 +wChannel6:: channel_struct wChannel6 +wChannel7:: channel_struct wChannel7 +wChannel8:: channel_struct wChannel8 + + ds 1 + +wCurTrackDuty:: db +wCurTrackIntensity:: db +wCurTrackFrequency:: dw +wc195:: db + + ds 2 ; TODO + +wCurChannel:: db +wVolume:: db +wSoundOutput:: +; corresponds to $ff25 +; bit 4-7: ch1-4 so2 on/off +; bit 0-3: ch1-4 so1 on/off + db + + ds 1 ; TODO + +wMusicID:: dw +wMusicBank:: db + + ds 5 ; TODO + +wLowHealthAlarm:: +; bit 7: on/off +; bit 4: pitch +; bit 0-3: counter + db + +wMusicFade:: +; fades volume over x frames +; bit 7: fade in/out +; bit 0-6: number of frames for each volume level +; $00 = none (default) + db +wMusicFadeCount:: db +wMusicFadeID:: +wMusicFadeIDLow:: db +wMusicFadeIDHigh:: db + + ds 2 ; TODO + +wIncrementTempo: dw +wMapMusic:: db +wCryPitch:: dw +wCryLength:: dw + ds 7 ; TODO + +wc1b9:: db +wc1ba:: db +; either wChannelsEnd or wMusicEnd, unsure + + ds 1 ; TODO + +wMusicInitEnd:: + + +SECTION "OAM Buffer", WRAM0 + +wVirtualOAM:: + ds SPRITEOAMSTRUCT_LENGTH * NUM_SPRITE_OAM_STRUCTS +wVirtualOAMEnd:: + +wTileMap:: + ds SCREEN_HEIGHT * SCREEN_WIDTH + +UNION + +wTileMapBackup:: + ds SCREEN_HEIGHT * SCREEN_WIDTH + +NEXTU + + ds 1 + +wc409:: ds 1 +wc40a:: ds 1 + +; Monster or Trainer test? +wWhichPicTest:: + db + + +wc40c:: ds 1 +wc40d:: ds 1 +wc40e:: ds 1 + + ds 11 + +wc41a:: db + + ds 165 + +wSpriteAnimIDBuffer:: db + + ds 6 + +wc4c7:: db +wc4c8:: db + + ds 7 + +wNamingScreenDestinationPointer:: dw +wNamingScreenCurNameLength:: db +wNamingScreenMaxNameLength:: db +wNamingScreenType:: db +wNamingScreenCursorObjectPointer:: dw +wNamingScreenLastCharacter:: db +wNamingScreenStringEntryCoordY:: db +wNamingScreenStringEntryCoordX:: db + + ds 64 + +wc51a:: ds 1 + +ENDU + + +SECTION "Map Buffer", WRAM0 + +wMapBuffer:: +wMapScriptNumber:: db +wMapScriptNumberLocation:: dw +wUnknownMapPointer:: dw ; TODO +wc5ed:: db + ds 18 +wMapBufferEnd:: + + +UNION + +wOverworldMapBlocks:: ds 1300 +wOverworldMapBlocksEnd:: + +NEXTU + +wLYOverrides:: + ds SCREEN_HEIGHT_PX + ds $10 +wLYOverrides2:: + +NEXTU +; Battle-related + + ds $1ea + +wActiveBGEffects:: +wBGEffect1:: battle_bg_effect wBGEffect1 +wBGEffect2:: battle_bg_effect wBGEffect2 +wBGEffect3:: battle_bg_effect wBGEffect3 +wBGEffect4:: battle_bg_effect wBGEffect4 +wBGEffect5:: battle_bg_effect wBGEffect5 +wActiveBGEffectsEnd:: + +wNumActiveBattleAnims:: db + +wBattleAnimFlags:: db +wBattleAnimAddress:: dw +wBattleAnimDuration:: db +wBattleAnimParent:: dw +wBattleAnimLoops:: db +wBattleAnimVar:: db +wBattleAnimByte:: db +wBattleAnimOAMPointerLo:: db + db + +UNION +; unidentified +wBattleAnimTemp0:: db +wBattleAnimTemp1:: db +wBattleAnimTemp2:: db +wBattleAnimTemp3:: db + +NEXTU +wBattleAnimTempOAMFlags:: db +wBattleAnimTempField02:: db +wBattleAnimTempTileID:: db +wBattleAnimTempXCoord:: db +wBattleAnimTempYCoord:: db +wBattleAnimTempXOffset:: db +wBattleAnimTempYOffset:: db +wBattleAnimTempAddSubFlags:: db +wBattleAnimTempPalette:: db +ENDU + + ds $32 +wBattleAnimEnd:: + + ds $1aa ; TODO + + +wc9ef:: ds 1 + + ds 6 + +wBattleMonNickname:: ds 6 +wEnemyMonNickname:: ds 6 + +wca02:: ds 1 +wca03:: ds 1 +wca04:: ds 1 + + ds 3 + +wca08:: ds 1 +wca09:: ds 1 +wca0a:: ds 1 + + ds 5 ; TODO + +wca10:: ds 1 +wca11:: ds 1 +wca12:: ds 1 +wca13:: ds 1 +wca14:: ds 1 + + ds $22 ; TODO + +wca37:: ds 1 +wca38:: ds 1 +wca39:: ds 1 +wca3a:: ds 1 +wca3b:: ds 1 +wca3c:: ds 1 +wPlayerSubStatus3:: db + +wca3e:: ds 1 +wca3f:: ds 1 +wca40:: ds 1 +wca41:: ds 1 + +wEnemySubStatus3:: db + +wca43:: db + +wca44:: db + + ds $12 +wTrainerClass:: + db + +wca58:: ds 1 +wca59:: ds 1 +wca5a:: ds 1 +wca5b:: ds 1 +wca5c:: ds 1 + + ds $5c + +wcab9:: ds 1 + + ds 6 + +wcac0:: ds 1 +wcac1:: ds 1 +wcac2:: ds 1 + +wLinkBattleRNCount:: db + + ds 12 + +wcad0:: ds 1 + + ds 9 + +wcada:: ds 1 + + ds 6 + +wcae1:: ds 1 + +ENDU + + + + +SECTION "CB14", WRAM0[$CB14] + +UNION +wRedrawRowOrColumnSrcTiles:: +; the tiles of the row or column to be redrawn by RedrawRowOrColumn + ds SCREEN_WIDTH * 2 +NEXTU +wRedrawFlashlightDst0:: dw +wRedrawFlashlightSrc0:: dw +wRedrawFlashlightBlackDst0:: dw +wRedrawFlashlightDst1:: dw +wRedrawFlashlightSrc1:: dw +wRedrawFlashlightBlackDst1:: dw +wRedrawFlashlightWidthHeight:: db +; width or height of flashlight redraw region +; in units of two tiles (people event meta tile) +ENDU + +SECTION "CB56", WRAM0[$CB4C] +wOtherPlayerLinkMode:: db +wOtherPlayerLinkAction:: db + ds 3 ; TODO + +wPlayerLinkAction:: db + ds 4 ; TODO + +wLinkTimeoutFrames:: dw +wcb58:: ds 2 +wMonType:: db +wCurSpecies:: db +wNamedObjectTypeBuffer:: db + +SECTION "CB5E", WRAM0[$CB5E] +wJumptableIndex:: db +wFlyDestination:: db + +wcb60:: ds 1 +wcb61:: ds 1 + +wVBCopySize:: ds 1 +wVBCopySrc:: ds 2 +wVBCopyDst:: ds 2 +wVBCopyDoubleSize:: ds 1 +wVBCopyDoubleSrc:: ds 2 +wVBCopyDoubleDst:: ds 2 +wcb6c:: db +wcb6d:: db +wcb6e:: db +wPlayerStepDirection:: db + +SECTION "CB71", WRAM0[$CB70] + +wcb70:: db + +wVBCopyFarSize:: ds 1 +wVBCopyFarSrc:: ds 2 +wVBCopyFarDst:: ds 2 +wVBCopyFarSrcBank:: ds 1 +wPlayerMovement:: db +wMovementObject:: db + ptrba wMovementData + +wcb7c:: ds 1 + +SECTION "Collision buffer", WRAM0[$CB90] + +wTileDown:: db +wTileUp:: db +wTileLeft:: db +wTileRight:: db + +wScreenSave:: + ds 6 * 5 + +SECTION "CBB2", WRAM0[$CBB2] +wToolgearBuffer:: + ds $40 + +SECTION "CBF2", WRAM0[$CBF2] + +wWindowData:: +wWindowStackPointer:: dw +wMenuJoypad:: db +wMenuSelection:: db +wMenuSelectionQuantity:: db +wWhichIndexSet:: +wActiveBackpackPocket:: db +wScrollingMenuCursorPosition:: db +wWindowStackSize:: db + +SECTION "CC09", WRAM0[$CC02] + +wMenuDataHeader:: + db +wMenuBorderTopCoord:: db +wMenuBorderLeftCoord:: db +wMenuBorderBottomCoord:: db +wMenuBorderRightCoord:: db +wMenuDataPointer:: dw +wMenuCursorBuffer:: db + ds 8 ; TODO +wMenuDataHeaderEnd:: + +wMenuData2:: +wMenuDataFlags:: db +wMenuDataItems:: db +wMenuDataIndicesPointer:: dw +wMenuDataDisplayFunctionPointer:: dw +wMenuDataPointerTableAddr:: dw + +SECTION "MenuData3", WRAM0[$CC22] +wMenuData3:: + +w2DMenuCursorInitY:: db +w2DMenuCursorInitX:: db +w2DMenuNumRows:: db +w2DMenuNumCols:: db +w2DMenuFlags:: dw +w2DMenuCursorOffsets:: db +wMenuJoypadFilter:: db +w2DMenuDataEnd:: + +wMenuCursorY:: db +wMenuCursorX:: db +wCursorOffCharacter:: db +wCursorCurrentTile:: dw + +SECTION "CC32", WRAM0[$CC32] ; Please merge when more is disassembled +wVBlankJoyFrameCounter: db + +wVBlankOccurred: db +wLastSpawnMapGroup: db +wLastSpawnMapNumber: db + + ds 2 + +;Controls what type of opening (fire/notes) you get. +wcc38:: +wTitleSequenceOpeningType:: + db + +wDefaultSpawnPoint:: + db + +wMovementBufferCount:: db +wMovementBufferObject:: db + ptrba wMovementBufferPointer +wMovementBuffer:: + ds 55 + +SECTION "CC9A", WRAM0[$CC9A] + +wSkatingDirection:: db +wCompanionCollisionFrameCounter:: db + +wUnknownWordcc9c:: + dw + +wUnknownBuffercc9e:: + ds 14 + + +wSpriteCurPosX : ds 1 +wSpriteCurPosY : ds 1 +wSpriteWidth : ds 1 +wSpriteHeight : ds 1 +wSpriteInputCurByte : ds 1 +wSpriteInputBitCounter : ds 1 +wSpriteOutputBitOffset : ds 1 +wSpriteLoadFlags : ds 1 +wSpriteUnpackMode : ds 1 +wSpriteFlipped : ds 1 +wSpriteInputPtr : ds 2 +wSpriteOutputPtr : ds 2 +wSpriteOutputPtrCached : ds 2 +wSpriteDecodeTable0Ptr : ds 2 +wSpriteDecodeTable1Ptr : ds 2 + +wccc0:: ds 1 +wccc1:: ds 1 +wccc2:: ds 1 +wccc3:: ds 1 +wccc4:: ds 1 + +SECTION "CCC7", WRAM0[$CCC7] + +wDisableVBlankOAMUpdate:: db + +SECTION "CCCA", WRAM0[$CCCA] + +wBGP:: db +wOBP0:: db +wOBP1:: db + +wcccd:: ds 1 + +wDisableVBlankWYUpdate:: db +wSGB:: db + +SECTION "CCD0", WRAM0[$CCD0] + +wccd0:: ds 1 +wccd1:: ds 1 +wccd2:: ds 1 +wccd3:: ds 1 + + ds 5 + +wccd9:: ds 1 + +SECTION "CCE1", WRAM0[$CCE1] + +wcce1:: ds 1 +wcce2:: ds 1 +wcce3:: ds 1 +wcce4:: ds 1 + + ds 6 + +wcceb:: ds 1 + + ds 5 + +wccf1:: ds 1 +wccf2:: ds 1 +wccf3:: ds 1 +wccf4:: ds 1 + +SECTION "CD11", WRAM0[$CD11] + +wcd11:: ds 1 + + ds 20 + +wStringBuffer1:: ds 1 ; How long is this? +wcd27:: ds 1 +SECTION "CD31", WRAM0[$CD31] + +UNION +wStartDay:: db +wStartHour:: db +wStartMinute:: db + +NEXTU +wHPBarTempHP:: dw + +NEXTU +wStringBuffer2:: db ; How long is this? + +ENDU + + +SECTION "CD3C", WRAM0[$CD3C] + +wcd3c:: db +wRegularItemsCursor:: db +wBackpackAndKeyItemsCursor:: db +wStartmenuCursor:: db +wcd40:: db +wcd41:: db +wcd42:: db +wcd43:: db +wRegularItemsScrollPosition:: db +wBackpackAndKeyItemsScrollPosition:: db +wcd46:: ds 1 +wcd47:: ds 1 +wSelectedSwapPosition:: db +wMenuScrollPosition:: db + +wTextDest:: ds 2 + +wQueuedScriptBank:: db +wQueuedScriptAddr:: dw + +wPredefID:: + db + +wPredefHL:: + dw +wPredefDE:: + dw +wPredefBC:: + +wFarCallBCBuffer:: + dw + +wcd56:: ds 1 +wcd57:: ds 1 +wFieldMoveSucceeded:: db +wVramState:: db + + ds 3 ; TODO +wcd5d:: db + db +wChosenStarter:: db + +SECTION "CD70", WRAM0[$CD70] +wcd70:: ds 1 +wcd71:: ds 1 +wcd72:: dw +wcd74:: db +wcd75:: db + +wCurItem:: db +wItemIndex:: db +wMonDexIndex: db +wWhichPokemon: db + +SECTION "CD7B", WRAM0[$CD7B] + +wHPBarType:: db +wcd7c:: ds 1 + +wItemQuantity:: db +wItemQuantityBuffer:: db +wcd7f:: db +wcd80:: db +wcd81:: db + +SECTION "CD9E", WRAM0 [$CD9E] +wLoadedMonLevel:: db + +SECTION "CDAF", WRAM0 [$CDAF] +wcdaf:: db + +SECTION "CDB0", WRAM0 [$CDB0] +wTalkingTargetType:: db +;bit 0 = has engaged NPC in dialogue +;bit 1 = has engaged sign in dialogue + +wcdb1:: ds 1 +wcdb2:: ds 1 + +SECTION "CDB9", WRAM0[$CDB9] + +wcdb9:: ds 1 + +wItemAttributeParamBuffer:: db +wCurPartyLevel:: db + +SECTION "CDBD", WRAM0[$CDBD] + +wLinkMode:: db +; 00 - +; 01 - +; 02 - +; 03 - + +wNextWarp:: db +wNextMapGroup:: db +wNextMapId:: db +wPrevWarp:: db + + ds 1 + +UNION +wFieldMoveScriptID:: db +wMapBlocksAddress:: dw +wReplacementBlock:: db + +NEXTU + +wHPBarMaxHP:: dw +wHPBarOldHP:: dw + +ENDU + +wHPBarNewHP:: dw +wHPBarDelta:: db +wcdca:: db +wHPBarHPDifference:: dw + +wLinkBattleRNs:: ds 10 + +wcdd7:: ds 1 +wcdd8:: ds 1 +wcdd9:: ds 1 +wcdda:: ds 1 +wcddb:: ds 1 +wcddc:: ds 1 +wcddd:: ds 1 +wcdde:: ds 1 +wcddf:: ds 1 +wcde0:: ds 1 +wcde1:: ds 1 +wcde2:: ds 1 +wcde3:: ds 1 +wcde4:: ds 1 +wcde5:: ds 1 +wcde6:: ds 1 +wcde7:: ds 1 +wcde8:: ds 1 +wcde9:: ds 1 +wcdea:: ds 1 +wcdeb:: ds 1 + + +SECTION "CDFE", WRAM0[$CDFE] + +wcdfe:: ds 1 +wcdff:: ds 1 +wBattleMode:: db +wce01:: ds 1 +wce02:: ds 1 +wce03:: ds 1 +wce04:: ds 1 +wce05:: ds 1 +wce06:: ds 1 + +wMonHeader:: + +wMonHIndex:: +; In the ROM base stats data structure, this is the dex number, but it is +; overwritten with the dex number after the header is copied to WRAM. + ds 1 + +wMonHBaseStats:: +wMonHBaseHP:: + ds 1 +wMonHBaseAttack:: + ds 1 +wMonHBaseDefense:: + ds 1 +wMonHBaseSpeed:: + ds 1 +wMonHBaseSpecialAtt:: + ds 1 +wMonHBaseSpecialDef:: + ds 1 + +wMonHTypes:: +wMonHType1:: + ds 1 +wMonHType2:: + ds 1 + +wMonHCatchRate:: + ds 1 +wMonHBaseEXP:: + ds 1 + +wMonHItems:: +wMonHItem1:: + ds 1 +wMonHItem2:: + ds 1 + +wMonHGenderRatio:: + ds 1 + +wMonHUnk0:: + ds 1 +wMonHUnk1:: + ds 1 +wMonHUnk2:: + ds 1 + +wMonHSpriteDim:: + ds 1 +wMonHFrontSprite:: + ds 2 +wMonHBackSprite:: + ds 2 + +wMonHGrowthRate:: + ds 1 + +wMonHLearnset:: +; bit field + flag_array 50 + 5 ; size = 7 + ds 1 + +SECTION "CE2D", WRAM0[$CE2D] +wce2d:: ds 1 +wce2e:: ds 1 +wce2f:: ds 1 +wce30:: ds 1 +wce31:: ds 1 +wce32:: ds 1 +wce33:: ds 1 +wce34:: ds 1 +wce35:: ds 1 +wce36:: ds 1 + +wNamedObjectIndexBuffer:: +wCountSetBitsResult:: +wce37:: + db + +SECTION "CE3A", WRAM0[$CE3A] + +wce3a:: ds 1 + +wVBlankSavedROMBank:: + db + +wBuffer:: + db + +wTimeOfDay:: db +; based on RTC +; Time of Day Regular Debug +; 00 - Day 09--15h 00--30s +; 01 - Night 15--06h 30--35s +; 02 - Cave 35--50s +; 03 - Morning 06--09h 50--59s + +wcd3f: ds 1 + +SECTION "CE5F", WRAM0[$CE5F] + +wce5f:: ; debug menu writes $41 to it + db + +wce60:: + db ; main menu checks this, maybe states if there's a save present? + +wActiveFrame:: db + +wTextBoxFlags:: db + +wDebugFlags:: db +; Bit 0: Debug battle indicator +; Bit 1: Debug field indicator +; Bit 2-3: Game is continued (set when selecting continue on the main menu) + +wce64:: ds 1 +wce65:: ds 1 +wce66:: ds 1 + +wPlayerName:: ds 6 + +wMomsName:: ds 6 + +SECTION "CE73", WRAM0[$CE73] + +wce73: ds 1 +wce74: ds 1 +wce75: ds 1 + +wObjectFollow_Leader:: + db +wObjectFollow_Follower:: + db +wCenteredObject:: + db +wFollowerMovementQueueLength:: + db +wFollowMovementQueue:: + ds 5 + +wObjectStructs:: +; Note: this might actually not be an object. TODO: Investigate (if indexing starts at 1, then this isn't an object) +; It might just be unused/a leftover. +wUnkObjectStruct:: object_struct wUnkObject +wPlayerStruct:: object_struct wPlayer +wObject1Struct:: object_struct wObject1 +wObject2Struct:: object_struct wObject2 +wObject3Struct:: object_struct wObject3 +wObject4Struct:: object_struct wObject4 +wObject5Struct:: object_struct wObject5 +wObject6Struct:: object_struct wObject6 +wObject7Struct:: object_struct wObject7 +wObject8Struct:: object_struct wObject8 +wObjectStructsEnd:: + +wCmdQueue:: +wCmdQueueEntry1:: ds 16 +wCmdQueueEntry2:: ds 16 +wCmdQueueEntry3:: ds 16 +wCmdQueueEntry4:: ds 16 + +wMapObjects:: +wPlayerObject:: map_object wPlayer +wMap1Object:: map_object wMap1 +wMap2Object:: map_object wMap2 +wMap3Object:: map_object wMap3 +wMap4Object:: map_object wMap4 +wMap5Object:: map_object wMap5 +wMap6Object:: map_object wMap6 +wMap7Object:: map_object wMap7 +wMap8Object:: map_object wMap8 +wMap9Object:: map_object wMap9 +wMap10Object:: map_object wMap10 +wMap11Object:: map_object wMap11 +wMap12Object:: map_object wMap12 +wMap13Object:: map_object wMap13 +wMap14Object:: map_object wMap14 +wMap15Object:: map_object wMap15 +wMapObjectsEnd:: + +wToolgearFlags:: db +; 76543210 +; | | \- show toolgear +; | | +; | \--- transfer toolgear to window +; \-------- hide toolgear + + ds 2 ; TODO + +wTimeOfDayPal:: db +; Applied according to wCurTimeOfDay from wTimeOfDayPalset + +wd153:: db +; 76543210 +; | \- show player coords in toolgear instead of time +; \-------- switch overworld palettes according to seconds not hours + + ds 3 ; TODO +wTimeOfDayPalFlags:: db +; 76543210 +; \-------- disable overworld palette switch + +wTimeOfDayPalset:: db +; 76543210 +; \/\/\/\/ +; | | | \- Map Palette for TimeOfDay $00 (MORN) +; | | \--- Map Palette for TimeOfDay $01 (DAY) +; | \----- Map Palette for TimeOfDay $02 (NITE) +; \------- Map Palette for TimeOfDay $03 (DARK) + +wCurTimeOfDay:: db + +SECTION "D15B", WRAM0[$D15B] + +wd15b:: db + +wd15c:: db + +wd15d:: db + +wd15e:: db + +wd15f:: db + +SECTION "D163", WRAM0[$D163] + +wd163:: db + +wd164:: db + +wTMsHMs:: db + +SECTION "D19E", WRAM0[$D19E] + +wItems:: +wNumBagItems:: db + +SECTION "D1C8", WRAM0[$D1C8] + +wNumKeyItems:: db +wKeyItems:: db + +SECTION "D1DE", WRAM0[$D1DE] + +wNumBallItems:: db +wBallQuantities:: db + + ds 10 + +wUnknownListLengthd1ea:: db +wUnknownListd1eb:: db + +SECTION "Rival's Name", WRAM0[$D256] +wRegisteredItem:: db +wRegisteredItemQuantity:: db +wRivalName:: ds 6 + ds 6 + +wPlayerState:: db +; 00 - walking +; 01 - bicycle +; 02 - skateboard +; 04 - surfing + +wd265:: db +wd266:: db + +;The starting house's map script number is stored at d29a. Others are probably nearby. +SECTION "D29A", WRAM0[$D29A] +wd29a:: db +wd29b:: db +wd29c:: db +wd29d:: db +wd29e:: db + db +wd2a0:: db + +SECTION "D35F", WRAM0[$D35F] +wOptions:: db + +SECTION "D39D", WRAM0[$D39D] +wd39d:: db + +SECTION "D3A5", WRAM0[$D3A5] +wd3a5:: db + +SECTION "Game Event Flags", WRAM0[$D41A] +wd41a:: db +; 76543210 +; | \- read email? +; \-------- talked to Blue, triggers Oak +wd41b:: db +; 76543210 +; |\-- followed Oak to his back room +; \--- chose a starter +wd41c:: db +; 76543210 +; \----- recieved pokedexes +wd41d:: db +; 76543210 +; \--- beat rival in the lab +wd41e:: db + +SECTION "D4A9", WRAM0[$D4A9] + +wd4a9:: db + ds 1 ; TODO +wJoypadFlags:: db +; 76543210 +; ||||\__/ +; |||| \-- unkn +; |||\----- set for rival intro textbox +; ||\------ don't wait for keypress to close text box +; |\------- joypad sync mtx +; \-------- joypad disabled + +SECTION "wDigWarpNumber", WRAM0[$D4B2] + +wDigWarpNumber:: db +wd4b3:: ds 1 +wd4b4:: ds 1 +wd4b5:: ds 1 +wd4b6:: ds 1 +wd4b7:: ds 1 +wd4b8:: ds 1 +wd4b9:: ds 1 + + +SECTION "Warp data", WRAM0[$D513] + +wWarpNumber:: db + +wCurrMapWarpCount:: + db + +wCurrMapWarps:: +REPT 32 ; TODO: confirm this + ds 5 +ENDR + + +wCurrMapSignCount:: + db + +wCurrMapSigns:: +REPT 16 ; TODO: confirm this + ds 4 +ENDR + +wCurrMapObjectCount:: + db + +wCurrMapInlineTrainers:: +REPT 32 ; TODO: confirm this + ds 2 ; inline trainers. each pair of bytes is direction, distance +ENDR + +SECTION "D637", WRAM0[$D637] +wd637:: db ;OW battle state? $3 wild battle, $8 is trainer battle $4 is left battle, $B is load overworld? $0 is in overworld +wd638:: db ;wd637's last written-to value + +SECTION "Used sprites", WRAM0[$D643] + +wBGMapAnchor:: + dw + +wUsedSprites:: + dw ; This is for the player + +wUsedNPCSprites:: + ds 2 * 5 ; This is for the NPCs + +wUsedSpritesEnd:: + + +SECTION "Map header", WRAM0[$D656] + +wMapGroup:: db +wMapId:: db + +wOverworldMapAnchor:: + dw + +wYCoord:: db +wXCoord:: db + +wMetatileNextY:: db +wMetatileNextX:: db + +wd65e:: + db + +wMapPartial:: +wMapAttributesBank:: + db +wMapTileset:: + db +wMapPermissions:: + db +wMapAttributesPtr:: + dw +wMapPartialEnd:: + +wMapAttributes:: +wMapHeight:: + db +wMapWidth:: + db +wMapBlocksPointer:: + dw +wMapTextPtr:: + dw +wMapScriptPtr:: + dw +wMapObjectsPtr:: + dw +wMapConnections:: + db +wMapAttributesEnd:: + +wNorthMapConnection:: map_connection_struct wNorth +wSouthMapConnection:: map_connection_struct wSouth +wWestMapConnection:: map_connection_struct wWest +wEastMapConnection:: map_connection_struct wEast + + +wTileset:: +wTilesetBank:: + db +wTilesetBlocksAddress:: + dw +wTilesetTilesAddress:: + dw +wTilesetCollisionAddress:: + dw + ds 4 ; TODO +wTilesetEnd:: + +wPartyCount:: db +wPartySpecies:: ds PARTY_LENGTH +wPartyEnd:: db + +wPartyMons:: +wPartyMon1:: party_struct wPartyMon1 +wPartyMon2:: party_struct wPartyMon2 +wPartyMon3:: party_struct wPartyMon3 +wPartyMon4:: party_struct wPartyMon4 +wPartyMon5:: party_struct wPartyMon5 +wPartyMon6:: party_struct wPartyMon6 +wPlayerPartyEnd:: + +wPartyMonOT:: + ds PARTY_LENGTH * 6 +wPartyMonOTEnd:: + +wPartyMonNicknames:: + ds PARTY_LENGTH * MON_NAME_LENGTH ; = $24 +wPartyMonNicknamesEnd:: + +wPokedexOwned:: + flag_array NUM_POKEMON +wPokedexOwnedEnd:: + +wPokedexSeen:: + flag_array NUM_POKEMON +wPokedexSeenEnd:: + +wAnnonDex:: ds 26 + +wAnnonID:: ds 1 + +wd875:: ds 1 +wd876:: ds 1 + + ds 5 + +wd87c:: ds 1 + + ds 5 + +wd882:: ds 1 +wd883:: ds 1 +wd884:: ds 1 + +SECTION "D8A2", WRAM0[$D8A2] + +wd8a2:: ds 1 +wd8a3:: ds 1 +wd8a4:: ds 1 +wd8a5:: ds 1 + + ds 5 + +wd8ab:: ds 1 + +SECTION "wd8b1", WRAM0[$D8B1] + +wd8b1:: ds 1 + + ds 5 + +wd8b7:: ds 1 +wd8b8:: ds 1 + +SECTION "D8D1", WRAM0[$D8D1] + +wd8d1:: ds 1 + + ds 5 + +wd8d7:: ds 1 + + ds 5 + +wd8dd:: ds 1 + +SECTION "D8E3", WRAM0[$D8E3] + +wd8e3:: ds 1 +wd8e4:: ds 1 + +SECTION "D8FD", WRAM0[$D8FD] + +wd8fd:: ds 1 + +SECTION "D913", WRAM0[$D913] + +wd913:: ds 1 + +SECTION "Wild mon buffer", WRAM0[$D91B] + +UNION +wWildMons:: + ds 41 +NEXTU + ds 2 +wd91d:: ds 1 + ds 29 +wd93b:: ds 1 +ENDU + +SECTION "DA3B", WRAM0[$DA3B] + +wOTPartyMonOT:: db + +SECTION "DA5F", WRAM0[$DA5F] + +wda5f:: db + +SECTION "DA83", WRAM0[$DA83] + +wBoxListLength:: db +wBoxList:: ds MONS_PER_BOX + +SECTION "DAA3", WRAM0[$DAA3] + +wdaa3:: db +wdaa4:: db +wdaa5:: db + +SECTION "DE63", WRAM0[$DE63] + +wde63:: db + +SECTION "DF17", WRAM0[$DF17] +wdf17:: ds 1 + +SECTION "DFCB", WRAM0[$DFCB] +wdfcb:: ds 1 + +SECTION "Stack Bottom", WRAM0 + +; Where SP is set at game init +wStackBottom:: +; Due to the way the stack works (`push` first decrements, then writes), the byte at $DFFF is actually wasted diff --git a/slack/corrupted_9e1c.png b/slack/corrupted_9e1c.png new file mode 100644 index 0000000..f124031 Binary files /dev/null and b/slack/corrupted_9e1c.png differ diff --git a/slack/corrupted_a66c.png b/slack/corrupted_a66c.png new file mode 100644 index 0000000..a8bab25 Binary files /dev/null and b/slack/corrupted_a66c.png differ diff --git a/slack/corrupted_b1e3.png b/slack/corrupted_b1e3.png new file mode 100644 index 0000000..0ee2cb5 Binary files /dev/null and b/slack/corrupted_b1e3.png differ diff --git a/slack/corrupted_ba93.png b/slack/corrupted_ba93.png new file mode 100644 index 0000000..dfd88be Binary files /dev/null and b/slack/corrupted_ba93.png differ diff --git a/slack/sgb_border_gold_corrupted.png b/slack/sgb_border_gold_corrupted.png new file mode 100644 index 0000000..740bbd1 Binary files /dev/null and b/slack/sgb_border_gold_corrupted.png differ diff --git a/slack/sgb_border_silver_corrupted.png b/slack/sgb_border_silver_corrupted.png new file mode 100644 index 0000000..5b32a8e Binary files /dev/null and b/slack/sgb_border_silver_corrupted.png differ diff --git a/slack/slack.asm b/slack/slack.asm new file mode 100755 index 0000000..85c76d9 --- /dev/null +++ b/slack/slack.asm @@ -0,0 +1,23 @@ +SECTION "slack.asm@Unknownaebc", ROMX + +Unknownaebc: +INCBIN "slack/unknown_aebc.bin" + +SECTION "slack.asm@Unknownbb43", ROMX + +Unknownbb43: +INCBIN "slack/unknown_bb43.bin" + +SECTION "slack.asm@Corrupted SGB GFX", ROMX + +SGBBorderGoldCorruptedGFX: +INCBIN "slack/sgb_border_gold_corrupted.2bpp" + +Corruptedb1e3GFX: +INCBIN "slack/corrupted_b1e3.2bpp" + +SGBBorderSilverCorruptedGFX: +INCBIN "slack/sgb_border_silver_corrupted.2bpp" + +Corruptedba93GFX: +INCBIN "slack/corrupted_ba93.2bpp" diff --git a/slack/unknown_aebc.bin b/slack/unknown_aebc.bin new file mode 100644 index 0000000..531072d Binary files /dev/null and b/slack/unknown_aebc.bin differ diff --git a/slack/unknown_bb43.bin b/slack/unknown_bb43.bin new file mode 100644 index 0000000..e21effb Binary files /dev/null and b/slack/unknown_bb43.bin differ diff --git a/sram.asm b/sram.asm deleted file mode 100644 index 5200979..0000000 --- a/sram.asm +++ /dev/null @@ -1,14 +0,0 @@ -INCLUDE "constants.asm" - -SECTION "Sprite Buffers", SRAM - -sSpriteBuffer0:: ds SPRITEBUFFERSIZE -sSpriteBuffer1:: ds SPRITEBUFFERSIZE -sSpriteBuffer2:: ds SPRITEBUFFERSIZE - -SECTION "Unknown, bank 0", SRAM - -s0_a600:: ds 7 ; TODO: properly label this and figure out exact size - -SECTION "Window Stack Top", SRAM -sWindowStackTop:: dw diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index b64ec3b..0000000 --- a/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# A subset of pret/pokemon-reverse-engineering-tools -# Only needed for decompressing 1bpp and 2bpp graphics -# https://github.com/pret/pokemon-reverse-engineering-tools - -__version__ = '1.6.0' diff --git a/utils/coverage.py b/utils/coverage.py index 1cd3f4c..2f54e03 100755 --- a/utils/coverage.py +++ b/utils/coverage.py @@ -8,7 +8,7 @@ Generate a PNG visualizing the space used by each bank in the ROM. """ import sys -import png +from pokemontools import png from colorsys import hls_to_rgb from mapreader import MapReader diff --git a/utils/gfx.py b/utils/gfx.py index 80c84d3..a9e56d4 100644 --- a/utils/gfx.py +++ b/utils/gfx.py @@ -1,951 +1,171 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- +#!/usr/bin/python + +"""Supplementary scripts for graphics conversion.""" import os -import sys -import png -from math import sqrt, floor, ceil import argparse -import operator - -from lz import Compressed, Decompressed - - -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 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 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 decompress_file(filein, fileout=None): - image = bytearray(open(filein).read()) - de = Decompressed(image) - - if fileout == None: - fileout = os.path.splitext(filein)[0] - to_file(fileout, de.output) - - -def compress_file(filein, fileout=None): - image = bytearray(open(filein).read()) - lz = Compressed(image) - - if fileout == None: - fileout = filein + '.lz' - to_file(fileout, lz.output) - - -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' - - arguments['is_tileset'] = 'tilesets' in filein - arguments['is_overworld'] = 'sprites' in filein - 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)) - # correct tileset dimensions - if kwargs.get('is_tileset', False) and not (width * height // 8) % 128: - area = width * height - width = 128 - height = area // width - # correct overworld dimensions - elif kwargs.get('is_overworld', False) and not (width * height // 8) % 16: - area = width * height - width = 16 - height = area // width - - # 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: - filename, name, extension = try_decompress(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: - filename, name, extension = try_decompress(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: - filename, name, extension = try_decompress(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 compress(filenames=[]): - for filename in filenames: - data = open(filename, 'rb').read() - lz_data = Compressed(data).output - to_file(filename + '.lz', lz_data) - -def decompress(filenames=[]): - for filename in filenames: - name, extension = os.path.splitext(filename) - lz_data = open(filename, 'rb').read() - data = Decompressed(lz_data).output - to_file(name, data) - -def try_decompress(filename): - """ - Try to decompress a graphic when determining the filetype. - This skips the manual unlz step when attempting - to convert lz-compressed graphics to png. - """ - name, extension = os.path.splitext(filename) - if extension == '.lz': - decompress([filename]) - filename = name - name, extension = os.path.splitext(filename) - return filename, name, extension - - -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, - 'lz': compress, - 'unlz': decompress, - }.get(args.mode, None) - - if method == None: - raise Exception, "Unknown conversion method!" - - method(args.filenames) -if __name__ == "__main__": - main() +from pokemontools import gfx, lz + + +# Graphics with inverted tilemaps that aren't covered by filepath_rules. +pics = [ + 'gfx/shrink1', + 'gfx/shrink2', +] + +def recursive_read(filename): + def recurse(filename_): + lines = [] + for line in open(filename_): + if 'include "' in line.lower(): + lines += recurse(line.split('"')[1]) + else: + lines += [line] + return lines + lines = recurse(filename) + return ''.join(lines) + +base_stats = None +def get_base_stats(): + global base_stats + if not base_stats: + base_stats = recursive_read('data/base_stats.asm') + return base_stats + +def get_pokemon_dimensions(name): + try: + if name == 'egg': + return 5, 5 + if name.startswith('annon_'): + name = 'annon' + base_stats = get_base_stats() + start = base_stats.find('\tdb ' + name.upper()) + start = base_stats.find('\tdn ', start) + end = base_stats.find('\n', start) + line = base_stats[start:end].replace(',', ' ') + w, h = map(int, line.split()[1:3]) + return w, h + except: + return 7, 7 + +def filepath_rules(filepath): + """Infer attributes of certain graphics by their location in the filesystem.""" + args = {} + + filedir, filename = os.path.split(filepath) + if filedir.startswith('./'): + filedir = filedir[2:] + + name, ext = os.path.splitext(filename) + if ext == '.lz': + name, ext = os.path.splitext(name) + + pokemon_name = '' + + if 'gfx/pokemon/' in filedir: + pokemon_name = filedir.split('/')[-1] + if pokemon_name.startswith('annon_'): + index = filedir.find(pokemon_name) + if index != -1: + filedir = filedir[:index + len('annon')] + filedir[index + len('annon_a'):] + if name == 'front': + args['pal_file'] = os.path.join(filedir, 'normal.pal') + args['pic'] = True + args['animate'] = True + elif name == 'back': + args['pal_file'] = os.path.join(filedir, 'shiny.pal') + args['pic'] = True + + elif 'gfx/trainers' in filedir: + args['pic'] = True + + elif os.path.join(filedir, name) in pics: + args['pic'] = True + + if args.get('pal_file'): + if os.path.exists(args['pal_file']): + args['palout'] = args['pal_file'] + else: + del args['pal_file'] + + if args.get('pic'): + if ext == '.png': + w, h = gfx.png.Reader(filepath).asRGBA8()[:2] + w = min(w/8, h/8) + args['pic_dimensions'] = w, w + elif ext == '.2bpp': + if pokemon_name and name == 'front': + w, h = get_pokemon_dimensions(pokemon_name) + args['pic_dimensions'] = w, w + elif pokemon_name and name == 'back': + args['pic_dimensions'] = 6, 6 + else: + args['pic_dimensions'] = 7, 7 + return args + + +def to_1bpp(filename, **kwargs): + name, ext = os.path.splitext(filename) + if ext == '.1bpp': pass + elif ext == '.2bpp': gfx.export_2bpp_to_1bpp(filename, **kwargs) + elif ext == '.png': gfx.export_png_to_1bpp(filename, **kwargs) + elif ext == '.lz': + decompress(filename, **kwargs) + to_1bpp(name, **kwargs) + +def to_2bpp(filename, **kwargs): + name, ext = os.path.splitext(filename) + if ext == '.1bpp': gfx.export_1bpp_to_2bpp(filename, **kwargs) + elif ext == '.2bpp': pass + elif ext == '.png': gfx.export_png_to_2bpp(filename, **kwargs) + elif ext == '.lz': + decompress(filename, **kwargs) + to_2bpp(name, **kwargs) + +def to_png(filename, **kwargs): + name, ext = os.path.splitext(filename) + if ext == '.1bpp': gfx.export_1bpp_to_png(filename, **kwargs) + elif ext == '.2bpp': gfx.export_2bpp_to_png(filename, **kwargs) + elif ext == '.png': pass + elif ext == '.lz': + decompress(filename, **kwargs) + to_png(name, **kwargs) + +def compress(filename, **kwargs): + data = open(filename, 'rb').read() + lz_data = lz.Compressed(data).output + open(filename + '.lz', 'wb').write(bytearray(lz_data)) + +def decompress(filename, **kwargs): + lz_data = open(filename, 'rb').read() + data = lz.Decompressed(lz_data).output + name, ext = os.path.splitext(filename) + open(name, 'wb').write(bytearray(data)) + + +methods = { + '2bpp': to_2bpp, + '1bpp': to_1bpp, + 'png': to_png, + 'lz': compress, + 'unlz': decompress, +} + +def main(method_name, filenames=None): + if filenames is None: filenames = [] + for filename in filenames: + args = filepath_rules(filename) + method = methods.get(method_name) + if method: + method(filename, **args) + +def get_args(): + ap = argparse.ArgumentParser() + ap.add_argument('method_name') + ap.add_argument('filenames', nargs='*') + args = ap.parse_args() + return args + +if __name__ == '__main__': + main(**get_args().__dict__) diff --git a/utils/lz.py b/utils/lz.py deleted file mode 100644 index aef5c64..0000000 --- a/utils/lz.py +++ /dev/null @@ -1,580 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Pokemon Crystal data de/compression. -""" - -""" -A rundown of Pokemon Crystal's compression scheme: - -Control commands occupy bits 5-7. -Bits 0-4 serve as the first parameter for each command. -""" -lz_commands = { - 'literal': 0, # n values for n bytes - 'iterate': 1, # one value for n bytes - 'alternate': 2, # alternate two values for n bytes - 'blank': 3, # zero for n bytes -} - -""" -Repeater commands repeat any data that was just decompressed. -They take an additional signed parameter to mark a relative starting point. -These wrap around (positive from the start, negative from the current position). -""" -lz_commands.update({ - 'repeat': 4, # n bytes starting from s - 'flip': 5, # n bytes in reverse bit order starting from s - 'reverse': 6, # n bytes backwards starting from s -}) - -""" -The long command is used when 5 bits aren't enough. Bits 2-4 contain a new control code. -Bits 0-1 are appended to a new byte as 8-9, allowing a 10-bit parameter. -""" -lz_commands.update({ - 'long': 7, # n is now 10 bits for a new control code -}) -max_length = 1 << 10 # can't go higher than 10 bits -lowmax = 1 << 5 # standard 5-bit param - -""" -If 0xff is encountered instead of a command, decompression ends. -""" -lz_end = 0xff - - -bit_flipped = [ - sum(((byte >> i) & 1) << (7 - i) for i in xrange(8)) - for byte in xrange(0x100) -] - - -class Compressed: - - """ - Usage: - lz = Compressed(data).output - or - lz = Compressed().compress(data) - or - c = Compressed() - c.data = data - lz = c.compress() - - There are some issues with reproducing the target compressor. - Some notes are listed here: - - the criteria for detecting a lookback is inconsistent - - sometimes lookbacks that are mostly 0s are pruned, sometimes not - - target appears to skip ahead if it can use a lookback soon, stopping the current command short or in some cases truncating it with literals. - - this has been implemented, but the specifics are unknown - - self.min_scores: It's unknown if blank's minimum score should be 1 or 2. Most likely it's 1, with some other hack to account for edge cases. - - may be related to the above - - target does not appear to compress backwards - """ - - def __init__(self, *args, **kwargs): - - self.min_scores = { - 'blank': 1, - 'iterate': 2, - 'alternate': 3, - 'repeat': 3, - 'reverse': 3, - 'flip': 3, - } - - self.preference = [ - 'repeat', - 'blank', - 'flip', - 'reverse', - 'iterate', - 'alternate', - #'literal', - ] - - self.lookback_methods = 'repeat', 'reverse', 'flip' - - self.__dict__.update({ - 'data': None, - 'commands': lz_commands, - 'debug': False, - 'literal_only': False, - }) - - self.arg_names = 'data', 'commands', 'debug', 'literal_only' - - self.__dict__.update(kwargs) - self.__dict__.update(dict(zip(self.arg_names, args))) - - if self.data is not None: - self.compress() - - def compress(self, data=None): - if data is not None: - self.data = data - - self.data = list(bytearray(self.data)) - - self.indexes = {} - self.lookbacks = {} - for method in self.lookback_methods: - self.lookbacks[method] = {} - - self.address = 0 - self.end = len(self.data) - self.output = [] - self.literal = None - - while self.address < self.end: - - if self.score(): - self.do_literal() - self.do_winner() - - else: - if self.literal == None: - self.literal = self.address - self.address += 1 - - self.do_literal() - - self.output += [lz_end] - return self.output - - def reset_scores(self): - self.scores = {} - self.offsets = {} - self.helpers = {} - for method in self.min_scores.iterkeys(): - self.scores[method] = 0 - - def bit_flip(self, byte): - return bit_flipped[byte] - - def do_literal(self): - if self.literal != None: - length = abs(self.address - self.literal) - start = min(self.literal, self.address + 1) - self.helpers['literal'] = self.data[start:start+length] - self.do_cmd('literal', length) - self.literal = None - - def score(self): - self.reset_scores() - - map(self.score_literal, ['iterate', 'alternate', 'blank']) - - for method in self.lookback_methods: - self.scores[method], self.offsets[method] = self.find_lookback(method, self.address) - - self.stop_short() - - return any( - score - > self.min_scores[method] + int(score > lowmax) - for method, score in self.scores.iteritems() - ) - - def stop_short(self): - """ - If a lookback is close, reduce the scores of other commands. - """ - best_method, best_score = max( - self.scores.items(), - key = lambda x: ( - x[1], - -self.preference.index(x[0]) - ) - ) - for method in self.lookback_methods: - min_score = self.min_scores[method] - for address in xrange(self.address+1, self.address+best_score): - length, index = self.find_lookback(method, address) - if length > max(min_score, best_score): - # BUG: lookbacks can reduce themselves. This appears to be a bug in the target also. - for m, score in self.scores.items(): - self.scores[m] = min(score, address - self.address) - - - def read(self, address=None): - if address is None: - address = self.address - if 0 <= address < len(self.data): - return self.data[address] - return None - - def find_all_lookbacks(self): - for method in self.lookback_methods: - for address, byte in enumerate(self.data): - self.find_lookback(method, address) - - def find_lookback(self, method, address=None): - """Temporarily stubbed, because the real function doesn't run in polynomial time.""" - return 0, None - - def broken_find_lookback(self, method, address=None): - if address is None: - address = self.address - - existing = self.lookbacks.get(method, {}).get(address) - if existing != None: - return existing - - lookback = 0, None - - # Better to not carelessly optimize at the moment. - """ - if address < 2: - return lookback - """ - - byte = self.read(address) - if byte is None: - return lookback - - direction, mutate = { - 'repeat': ( 1, int), - 'reverse': (-1, int), - 'flip': ( 1, self.bit_flip), - }[method] - - # Doesn't seem to help - """ - if mutate == self.bit_flip: - if byte == 0: - self.lookbacks[method][address] = lookback - return lookback - """ - - data_len = len(self.data) - is_two_byte_index = lambda index: int(index < address - 0x7f) - - for index in self.get_indexes(mutate(byte)): - - if index >= address: - break - - old_length, old_index = lookback - if direction == 1: - if old_length > data_len - index: break - else: - if old_length > index: continue - - if self.read(index) in [None]: continue - - length = 1 # we know there's at least one match, or we wouldn't be checking this index - while 1: - this_byte = self.read(address + length) - that_byte = self.read(index + length * direction) - if that_byte == None or this_byte != mutate(that_byte): - break - length += 1 - - score = length - is_two_byte_index(index) - old_score = old_length - is_two_byte_index(old_index) - if score >= old_score or (score == old_score and length > old_length): - # XXX maybe avoid two-byte indexes when possible - if score >= lookback[0] - is_two_byte_index(lookback[1]): - lookback = length, index - - self.lookbacks[method][address] = lookback - return lookback - - def get_indexes(self, byte): - if not self.indexes.has_key(byte): - self.indexes[byte] = [] - index = -1 - while 1: - try: - index = self.data.index(byte, index + 1) - except ValueError: - break - self.indexes[byte].append(index) - return self.indexes[byte] - - def score_literal(self, method): - address = self.address - - compare = { - 'blank': [0], - 'iterate': [self.read(address)], - 'alternate': [self.read(address), self.read(address + 1)], - }[method] - - # XXX may or may not be correct - if method == 'alternate' and compare[0] == 0: - return - - length = 0 - while self.read(address + length) == compare[length % len(compare)]: - length += 1 - - self.scores[method] = length - self.helpers[method] = compare - - def do_winner(self): - winners = filter( - lambda (method, score): - score - > self.min_scores[method] + int(score > lowmax), - self.scores.iteritems() - ) - winners.sort( - key = lambda (method, score): ( - -(score - self.min_scores[method] - int(score > lowmax)), - self.preference.index(method) - ) - ) - winner, score = winners[0] - - length = min(score, max_length) - self.do_cmd(winner, length) - self.address += length - - def do_cmd(self, cmd, length): - start_address = self.address - - cmd_length = length - 1 - - output = [] - - if length > lowmax: - output.append( - (self.commands['long'] << 5) - + (self.commands[cmd] << 2) - + (cmd_length >> 8) - ) - output.append( - cmd_length & 0xff - ) - else: - output.append( - (self.commands[cmd] << 5) - + cmd_length - ) - - self.helpers['blank'] = [] # quick hack - output += self.helpers.get(cmd, []) - - if cmd in self.lookback_methods: - offset = self.offsets[cmd] - # Negative offsets are one byte. - # Positive offsets are two. - if 0 < start_address - offset - 1 <= 0x7f: - offset = (start_address - offset - 1) | 0x80 - output += [offset] - else: - output += [offset / 0x100, offset % 0x100] # big endian - - if self.debug: - print ' '.join(map(str, [ - cmd, length, '\t', - ' '.join(map('{:02x}'.format, output)), - self.data[start_address:start_address+length] if cmd in self.lookback_methods else '', - ])) - - self.output += output - - - -class Decompressed: - """ - Interpret and decompress lz-compressed data, usually 2bpp. - """ - - """ - Usage: - data = Decompressed(lz).output - or - data = Decompressed().decompress(lz) - or - d = Decompressed() - d.lz = lz - data = d.decompress() - - To decompress from offset 0x80000 in a rom: - data = Decompressed(rom, start=0x80000).output - """ - - lz = None - start = 0 - commands = lz_commands - debug = False - - arg_names = 'lz', 'start', 'commands', 'debug' - - def __init__(self, *args, **kwargs): - self.__dict__.update(dict(zip(self.arg_names, args))) - self.__dict__.update(kwargs) - - self.command_names = dict(map(reversed, self.commands.items())) - self.address = self.start - - if self.lz is not None: - self.decompress() - - if self.debug: print self.command_list() - - - def command_list(self): - """ - Print a list of commands that were used. Useful for debugging. - """ - - text = '' - - output_address = 0 - for name, attrs in self.used_commands: - length = attrs['length'] - address = attrs['address'] - offset = attrs['offset'] - direction = attrs['direction'] - - text += '{2:03x} {0}: {1}'.format(name, length, output_address) - text += '\t' + ' '.join( - '{:02x}'.format(int(byte)) - for byte in self.lz[ address : address + attrs['cmd_length'] ] - ) - - if offset is not None: - repeated_data = self.output[ offset : offset + length * direction : direction ] - if name == 'flip': - repeated_data = map(bit_flipped.__getitem__, repeated_data) - text += ' [' + ' '.join(map('{:02x}'.format, repeated_data)) + ']' - - text += '\n' - output_address += length - - return text - - - def decompress(self, lz=None): - - if lz is not None: - self.lz = lz - - self.lz = bytearray(self.lz) - - self.used_commands = [] - self.output = [] - - while 1: - - cmd_address = self.address - self.offset = None - self.direction = None - - if (self.byte == lz_end): - self.next() - break - - self.cmd = (self.byte & 0b11100000) >> 5 - - if self.cmd_name == 'long': - # 10-bit length - self.cmd = (self.byte & 0b00011100) >> 2 - self.length = (self.next() & 0b00000011) * 0x100 - self.length += self.next() + 1 - else: - # 5-bit length - self.length = (self.next() & 0b00011111) + 1 - - self.__class__.__dict__[self.cmd_name](self) - - self.used_commands += [( - self.cmd_name, - { - 'length': self.length, - 'address': cmd_address, - 'offset': self.offset, - 'cmd_length': self.address - cmd_address, - 'direction': self.direction, - } - )] - - # Keep track of the data we just decompressed. - self.compressed_data = self.lz[self.start : self.address] - - - @property - def byte(self): - return self.lz[ self.address ] - - def next(self): - byte = self.byte - self.address += 1 - return byte - - @property - def cmd_name(self): - return self.command_names.get(self.cmd) - - - def get_offset(self): - - if self.byte >= 0x80: # negative - # negative - offset = self.next() & 0x7f - offset = len(self.output) - offset - 1 - else: - # positive - offset = self.next() * 0x100 - offset += self.next() - - self.offset = offset - - - def literal(self): - """ - Copy data directly. - """ - self.output += self.lz[ self.address : self.address + self.length ] - self.address += self.length - - def iterate(self): - """ - Write one byte repeatedly. - """ - self.output += [self.next()] * self.length - - def alternate(self): - """ - Write alternating bytes. - """ - alts = [self.next(), self.next()] - self.output += [ alts[x & 1] for x in xrange(self.length) ] - - def blank(self): - """ - Write zeros. - """ - self.output += [0] * self.length - - def flip(self): - """ - Repeat flipped bytes from output. - - Example: 11100100 -> 00100111 - """ - self._repeat(table=bit_flipped) - - def reverse(self): - """ - Repeat reversed bytes from output. - """ - self._repeat(direction=-1) - - def repeat(self): - """ - Repeat bytes from output. - """ - self._repeat() - - def _repeat(self, direction=1, table=None): - self.get_offset() - self.direction = direction - # Note: appends must be one at a time (this way, repeats can draw from themselves if required) - for i in xrange(self.length): - byte = self.output[ self.offset + i * direction ] - self.output.append( table[byte] if table else byte ) diff --git a/utils/png.py b/utils/png.py deleted file mode 100644 index db6da12..0000000 --- a/utils/png.py +++ /dev/null @@ -1,2650 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -# png.py - PNG encoder/decoder in pure Python -# -# Copyright (C) 2006 Johann C. Rocholl -# Portions Copyright (C) 2009 David Jones -# And probably portions Copyright (C) 2006 Nicko van Someren -# -# 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 `_ 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/utils/pokemontools/__init__.py b/utils/pokemontools/__init__.py new file mode 100644 index 0000000..b64ec3b --- /dev/null +++ b/utils/pokemontools/__init__.py @@ -0,0 +1,5 @@ +# A subset of pret/pokemon-reverse-engineering-tools +# Only needed for decompressing 1bpp and 2bpp graphics +# https://github.com/pret/pokemon-reverse-engineering-tools + +__version__ = '1.6.0' diff --git a/utils/pokemontools/gfx.py b/utils/pokemontools/gfx.py new file mode 100644 index 0000000..80c84d3 --- /dev/null +++ b/utils/pokemontools/gfx.py @@ -0,0 +1,951 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +import os +import sys +import png +from math import sqrt, floor, ceil +import argparse +import operator + +from lz import Compressed, Decompressed + + +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 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 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 decompress_file(filein, fileout=None): + image = bytearray(open(filein).read()) + de = Decompressed(image) + + if fileout == None: + fileout = os.path.splitext(filein)[0] + to_file(fileout, de.output) + + +def compress_file(filein, fileout=None): + image = bytearray(open(filein).read()) + lz = Compressed(image) + + if fileout == None: + fileout = filein + '.lz' + to_file(fileout, lz.output) + + +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' + + arguments['is_tileset'] = 'tilesets' in filein + arguments['is_overworld'] = 'sprites' in filein + 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)) + # correct tileset dimensions + if kwargs.get('is_tileset', False) and not (width * height // 8) % 128: + area = width * height + width = 128 + height = area // width + # correct overworld dimensions + elif kwargs.get('is_overworld', False) and not (width * height // 8) % 16: + area = width * height + width = 16 + height = area // width + + # 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: + filename, name, extension = try_decompress(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: + filename, name, extension = try_decompress(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: + filename, name, extension = try_decompress(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 compress(filenames=[]): + for filename in filenames: + data = open(filename, 'rb').read() + lz_data = Compressed(data).output + to_file(filename + '.lz', lz_data) + +def decompress(filenames=[]): + for filename in filenames: + name, extension = os.path.splitext(filename) + lz_data = open(filename, 'rb').read() + data = Decompressed(lz_data).output + to_file(name, data) + +def try_decompress(filename): + """ + Try to decompress a graphic when determining the filetype. + This skips the manual unlz step when attempting + to convert lz-compressed graphics to png. + """ + name, extension = os.path.splitext(filename) + if extension == '.lz': + decompress([filename]) + filename = name + name, extension = os.path.splitext(filename) + return filename, name, extension + + +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, + 'lz': compress, + 'unlz': decompress, + }.get(args.mode, None) + + if method == None: + raise Exception, "Unknown conversion method!" + + method(args.filenames) + +if __name__ == "__main__": + main() diff --git a/utils/pokemontools/lz.py b/utils/pokemontools/lz.py new file mode 100644 index 0000000..aef5c64 --- /dev/null +++ b/utils/pokemontools/lz.py @@ -0,0 +1,580 @@ +# -*- coding: utf-8 -*- +""" +Pokemon Crystal data de/compression. +""" + +""" +A rundown of Pokemon Crystal's compression scheme: + +Control commands occupy bits 5-7. +Bits 0-4 serve as the first parameter for each command. +""" +lz_commands = { + 'literal': 0, # n values for n bytes + 'iterate': 1, # one value for n bytes + 'alternate': 2, # alternate two values for n bytes + 'blank': 3, # zero for n bytes +} + +""" +Repeater commands repeat any data that was just decompressed. +They take an additional signed parameter to mark a relative starting point. +These wrap around (positive from the start, negative from the current position). +""" +lz_commands.update({ + 'repeat': 4, # n bytes starting from s + 'flip': 5, # n bytes in reverse bit order starting from s + 'reverse': 6, # n bytes backwards starting from s +}) + +""" +The long command is used when 5 bits aren't enough. Bits 2-4 contain a new control code. +Bits 0-1 are appended to a new byte as 8-9, allowing a 10-bit parameter. +""" +lz_commands.update({ + 'long': 7, # n is now 10 bits for a new control code +}) +max_length = 1 << 10 # can't go higher than 10 bits +lowmax = 1 << 5 # standard 5-bit param + +""" +If 0xff is encountered instead of a command, decompression ends. +""" +lz_end = 0xff + + +bit_flipped = [ + sum(((byte >> i) & 1) << (7 - i) for i in xrange(8)) + for byte in xrange(0x100) +] + + +class Compressed: + + """ + Usage: + lz = Compressed(data).output + or + lz = Compressed().compress(data) + or + c = Compressed() + c.data = data + lz = c.compress() + + There are some issues with reproducing the target compressor. + Some notes are listed here: + - the criteria for detecting a lookback is inconsistent + - sometimes lookbacks that are mostly 0s are pruned, sometimes not + - target appears to skip ahead if it can use a lookback soon, stopping the current command short or in some cases truncating it with literals. + - this has been implemented, but the specifics are unknown + - self.min_scores: It's unknown if blank's minimum score should be 1 or 2. Most likely it's 1, with some other hack to account for edge cases. + - may be related to the above + - target does not appear to compress backwards + """ + + def __init__(self, *args, **kwargs): + + self.min_scores = { + 'blank': 1, + 'iterate': 2, + 'alternate': 3, + 'repeat': 3, + 'reverse': 3, + 'flip': 3, + } + + self.preference = [ + 'repeat', + 'blank', + 'flip', + 'reverse', + 'iterate', + 'alternate', + #'literal', + ] + + self.lookback_methods = 'repeat', 'reverse', 'flip' + + self.__dict__.update({ + 'data': None, + 'commands': lz_commands, + 'debug': False, + 'literal_only': False, + }) + + self.arg_names = 'data', 'commands', 'debug', 'literal_only' + + self.__dict__.update(kwargs) + self.__dict__.update(dict(zip(self.arg_names, args))) + + if self.data is not None: + self.compress() + + def compress(self, data=None): + if data is not None: + self.data = data + + self.data = list(bytearray(self.data)) + + self.indexes = {} + self.lookbacks = {} + for method in self.lookback_methods: + self.lookbacks[method] = {} + + self.address = 0 + self.end = len(self.data) + self.output = [] + self.literal = None + + while self.address < self.end: + + if self.score(): + self.do_literal() + self.do_winner() + + else: + if self.literal == None: + self.literal = self.address + self.address += 1 + + self.do_literal() + + self.output += [lz_end] + return self.output + + def reset_scores(self): + self.scores = {} + self.offsets = {} + self.helpers = {} + for method in self.min_scores.iterkeys(): + self.scores[method] = 0 + + def bit_flip(self, byte): + return bit_flipped[byte] + + def do_literal(self): + if self.literal != None: + length = abs(self.address - self.literal) + start = min(self.literal, self.address + 1) + self.helpers['literal'] = self.data[start:start+length] + self.do_cmd('literal', length) + self.literal = None + + def score(self): + self.reset_scores() + + map(self.score_literal, ['iterate', 'alternate', 'blank']) + + for method in self.lookback_methods: + self.scores[method], self.offsets[method] = self.find_lookback(method, self.address) + + self.stop_short() + + return any( + score + > self.min_scores[method] + int(score > lowmax) + for method, score in self.scores.iteritems() + ) + + def stop_short(self): + """ + If a lookback is close, reduce the scores of other commands. + """ + best_method, best_score = max( + self.scores.items(), + key = lambda x: ( + x[1], + -self.preference.index(x[0]) + ) + ) + for method in self.lookback_methods: + min_score = self.min_scores[method] + for address in xrange(self.address+1, self.address+best_score): + length, index = self.find_lookback(method, address) + if length > max(min_score, best_score): + # BUG: lookbacks can reduce themselves. This appears to be a bug in the target also. + for m, score in self.scores.items(): + self.scores[m] = min(score, address - self.address) + + + def read(self, address=None): + if address is None: + address = self.address + if 0 <= address < len(self.data): + return self.data[address] + return None + + def find_all_lookbacks(self): + for method in self.lookback_methods: + for address, byte in enumerate(self.data): + self.find_lookback(method, address) + + def find_lookback(self, method, address=None): + """Temporarily stubbed, because the real function doesn't run in polynomial time.""" + return 0, None + + def broken_find_lookback(self, method, address=None): + if address is None: + address = self.address + + existing = self.lookbacks.get(method, {}).get(address) + if existing != None: + return existing + + lookback = 0, None + + # Better to not carelessly optimize at the moment. + """ + if address < 2: + return lookback + """ + + byte = self.read(address) + if byte is None: + return lookback + + direction, mutate = { + 'repeat': ( 1, int), + 'reverse': (-1, int), + 'flip': ( 1, self.bit_flip), + }[method] + + # Doesn't seem to help + """ + if mutate == self.bit_flip: + if byte == 0: + self.lookbacks[method][address] = lookback + return lookback + """ + + data_len = len(self.data) + is_two_byte_index = lambda index: int(index < address - 0x7f) + + for index in self.get_indexes(mutate(byte)): + + if index >= address: + break + + old_length, old_index = lookback + if direction == 1: + if old_length > data_len - index: break + else: + if old_length > index: continue + + if self.read(index) in [None]: continue + + length = 1 # we know there's at least one match, or we wouldn't be checking this index + while 1: + this_byte = self.read(address + length) + that_byte = self.read(index + length * direction) + if that_byte == None or this_byte != mutate(that_byte): + break + length += 1 + + score = length - is_two_byte_index(index) + old_score = old_length - is_two_byte_index(old_index) + if score >= old_score or (score == old_score and length > old_length): + # XXX maybe avoid two-byte indexes when possible + if score >= lookback[0] - is_two_byte_index(lookback[1]): + lookback = length, index + + self.lookbacks[method][address] = lookback + return lookback + + def get_indexes(self, byte): + if not self.indexes.has_key(byte): + self.indexes[byte] = [] + index = -1 + while 1: + try: + index = self.data.index(byte, index + 1) + except ValueError: + break + self.indexes[byte].append(index) + return self.indexes[byte] + + def score_literal(self, method): + address = self.address + + compare = { + 'blank': [0], + 'iterate': [self.read(address)], + 'alternate': [self.read(address), self.read(address + 1)], + }[method] + + # XXX may or may not be correct + if method == 'alternate' and compare[0] == 0: + return + + length = 0 + while self.read(address + length) == compare[length % len(compare)]: + length += 1 + + self.scores[method] = length + self.helpers[method] = compare + + def do_winner(self): + winners = filter( + lambda (method, score): + score + > self.min_scores[method] + int(score > lowmax), + self.scores.iteritems() + ) + winners.sort( + key = lambda (method, score): ( + -(score - self.min_scores[method] - int(score > lowmax)), + self.preference.index(method) + ) + ) + winner, score = winners[0] + + length = min(score, max_length) + self.do_cmd(winner, length) + self.address += length + + def do_cmd(self, cmd, length): + start_address = self.address + + cmd_length = length - 1 + + output = [] + + if length > lowmax: + output.append( + (self.commands['long'] << 5) + + (self.commands[cmd] << 2) + + (cmd_length >> 8) + ) + output.append( + cmd_length & 0xff + ) + else: + output.append( + (self.commands[cmd] << 5) + + cmd_length + ) + + self.helpers['blank'] = [] # quick hack + output += self.helpers.get(cmd, []) + + if cmd in self.lookback_methods: + offset = self.offsets[cmd] + # Negative offsets are one byte. + # Positive offsets are two. + if 0 < start_address - offset - 1 <= 0x7f: + offset = (start_address - offset - 1) | 0x80 + output += [offset] + else: + output += [offset / 0x100, offset % 0x100] # big endian + + if self.debug: + print ' '.join(map(str, [ + cmd, length, '\t', + ' '.join(map('{:02x}'.format, output)), + self.data[start_address:start_address+length] if cmd in self.lookback_methods else '', + ])) + + self.output += output + + + +class Decompressed: + """ + Interpret and decompress lz-compressed data, usually 2bpp. + """ + + """ + Usage: + data = Decompressed(lz).output + or + data = Decompressed().decompress(lz) + or + d = Decompressed() + d.lz = lz + data = d.decompress() + + To decompress from offset 0x80000 in a rom: + data = Decompressed(rom, start=0x80000).output + """ + + lz = None + start = 0 + commands = lz_commands + debug = False + + arg_names = 'lz', 'start', 'commands', 'debug' + + def __init__(self, *args, **kwargs): + self.__dict__.update(dict(zip(self.arg_names, args))) + self.__dict__.update(kwargs) + + self.command_names = dict(map(reversed, self.commands.items())) + self.address = self.start + + if self.lz is not None: + self.decompress() + + if self.debug: print self.command_list() + + + def command_list(self): + """ + Print a list of commands that were used. Useful for debugging. + """ + + text = '' + + output_address = 0 + for name, attrs in self.used_commands: + length = attrs['length'] + address = attrs['address'] + offset = attrs['offset'] + direction = attrs['direction'] + + text += '{2:03x} {0}: {1}'.format(name, length, output_address) + text += '\t' + ' '.join( + '{:02x}'.format(int(byte)) + for byte in self.lz[ address : address + attrs['cmd_length'] ] + ) + + if offset is not None: + repeated_data = self.output[ offset : offset + length * direction : direction ] + if name == 'flip': + repeated_data = map(bit_flipped.__getitem__, repeated_data) + text += ' [' + ' '.join(map('{:02x}'.format, repeated_data)) + ']' + + text += '\n' + output_address += length + + return text + + + def decompress(self, lz=None): + + if lz is not None: + self.lz = lz + + self.lz = bytearray(self.lz) + + self.used_commands = [] + self.output = [] + + while 1: + + cmd_address = self.address + self.offset = None + self.direction = None + + if (self.byte == lz_end): + self.next() + break + + self.cmd = (self.byte & 0b11100000) >> 5 + + if self.cmd_name == 'long': + # 10-bit length + self.cmd = (self.byte & 0b00011100) >> 2 + self.length = (self.next() & 0b00000011) * 0x100 + self.length += self.next() + 1 + else: + # 5-bit length + self.length = (self.next() & 0b00011111) + 1 + + self.__class__.__dict__[self.cmd_name](self) + + self.used_commands += [( + self.cmd_name, + { + 'length': self.length, + 'address': cmd_address, + 'offset': self.offset, + 'cmd_length': self.address - cmd_address, + 'direction': self.direction, + } + )] + + # Keep track of the data we just decompressed. + self.compressed_data = self.lz[self.start : self.address] + + + @property + def byte(self): + return self.lz[ self.address ] + + def next(self): + byte = self.byte + self.address += 1 + return byte + + @property + def cmd_name(self): + return self.command_names.get(self.cmd) + + + def get_offset(self): + + if self.byte >= 0x80: # negative + # negative + offset = self.next() & 0x7f + offset = len(self.output) - offset - 1 + else: + # positive + offset = self.next() * 0x100 + offset += self.next() + + self.offset = offset + + + def literal(self): + """ + Copy data directly. + """ + self.output += self.lz[ self.address : self.address + self.length ] + self.address += self.length + + def iterate(self): + """ + Write one byte repeatedly. + """ + self.output += [self.next()] * self.length + + def alternate(self): + """ + Write alternating bytes. + """ + alts = [self.next(), self.next()] + self.output += [ alts[x & 1] for x in xrange(self.length) ] + + def blank(self): + """ + Write zeros. + """ + self.output += [0] * self.length + + def flip(self): + """ + Repeat flipped bytes from output. + + Example: 11100100 -> 00100111 + """ + self._repeat(table=bit_flipped) + + def reverse(self): + """ + Repeat reversed bytes from output. + """ + self._repeat(direction=-1) + + def repeat(self): + """ + Repeat bytes from output. + """ + self._repeat() + + def _repeat(self, direction=1, table=None): + self.get_offset() + self.direction = direction + # Note: appends must be one at a time (this way, repeats can draw from themselves if required) + for i in xrange(self.length): + byte = self.output[ self.offset + i * direction ] + self.output.append( table[byte] if table else byte ) diff --git a/utils/pokemontools/png.py b/utils/pokemontools/png.py new file mode 100644 index 0000000..db6da12 --- /dev/null +++ b/utils/pokemontools/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 +# Portions Copyright (C) 2009 David Jones +# And probably portions Copyright (C) 2006 Nicko van Someren +# +# 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 `_ 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/vram.asm b/vram.asm deleted file mode 100644 index bf10e4d..0000000 --- a/vram.asm +++ /dev/null @@ -1,62 +0,0 @@ -INCLUDE "constants.asm" - -SECTION "VRAM", VRAM - -UNION - -vChars0:: - ds $80 tiles - -vChars1:: - ds $80 tiles - -vChars2:: - ds $80 tiles - -NEXTU - -; Battle/menu -vSprites:: - ds $80 tiles - -vFont:: - ds $80 tiles - -vFrontPic:: - ds 7 * 7 tiles - -NEXTU - -vNPCSprites:: - ds $80 tiles - -vNPCSprites2:: - ds $80 tiles - -vTileset:: - ds $20 tiles -vExteriorTileset:: - ds $40 tiles -vTilesetEnd:: - -NEXTU - - ds $80 tiles - -vTitleLogo:: - ds $80 tiles - -;vFrontPic:: - ds 7 * 7 tiles - -vTitleLogo2:: - ; TODO: what size? - -ENDU - - -vBGMap0:: - ds BG_MAP_WIDTH * BG_MAP_HEIGHT - -vBGMap1:: - ds BG_MAP_WIDTH * BG_MAP_HEIGHT diff --git a/wram.asm b/wram.asm deleted file mode 100644 index 9690975..0000000 --- a/wram.asm +++ /dev/null @@ -1,1282 +0,0 @@ -INCLUDE "constants.asm" - - -SECTION "Music engine RAM", WRAM0 - -wMusic:: - -wChannels:: -wChannel1:: channel_struct wChannel1 -wChannel2:: channel_struct wChannel2 -wChannel3:: channel_struct wChannel3 -wChannel4:: channel_struct wChannel4 - -wSFXChannels:: -wChannel5:: channel_struct wChannel5 -wChannel6:: channel_struct wChannel6 -wChannel7:: channel_struct wChannel7 -wChannel8:: channel_struct wChannel8 - - ds 1 - -wCurTrackDuty:: db -wCurTrackIntensity:: db -wCurTrackFrequency:: dw -wc195:: db - - ds 2 ; TODO - -wCurChannel:: db -wVolume:: db -wSoundOutput:: -; corresponds to $ff25 -; bit 4-7: ch1-4 so2 on/off -; bit 0-3: ch1-4 so1 on/off - db - - ds 1 ; TODO - -wMusicID:: dw -wMusicBank:: db - - ds 5 ; TODO - -wLowHealthAlarm:: -; bit 7: on/off -; bit 4: pitch -; bit 0-3: counter - db - -wMusicFade:: -; fades volume over x frames -; bit 7: fade in/out -; bit 0-6: number of frames for each volume level -; $00 = none (default) - db -wMusicFadeCount:: db -wMusicFadeID:: -wMusicFadeIDLow:: db -wMusicFadeIDHigh:: db - - ds 2 ; TODO - -wIncrementTempo: dw -wMapMusic:: db -wCryPitch:: dw -wCryLength:: dw - ds 7 ; TODO - -wc1b9:: db -wc1ba:: db -; either wChannelsEnd or wMusicEnd, unsure - - ds 1 ; TODO - -wMusicInitEnd:: - - -SECTION "OAM Buffer", WRAM0 - -wVirtualOAM:: - ds SPRITEOAMSTRUCT_LENGTH * NUM_SPRITE_OAM_STRUCTS -wVirtualOAMEnd:: - -wTileMap:: - ds SCREEN_HEIGHT * SCREEN_WIDTH - -UNION - -wTileMapBackup:: - ds SCREEN_HEIGHT * SCREEN_WIDTH - -NEXTU - - ds 1 - -wc409:: ds 1 -wc40a:: ds 1 - -; Monster or Trainer test? -wWhichPicTest:: - db - - -wc40c:: ds 1 -wc40d:: ds 1 -wc40e:: ds 1 - - ds 11 - -wc41a:: db - - ds 165 - -wSpriteAnimIDBuffer:: db - - ds 6 - -wc4c7:: db -wc4c8:: db - - ds 7 - -wNamingScreenDestinationPointer:: dw -wNamingScreenCurNameLength:: db -wNamingScreenMaxNameLength:: db -wNamingScreenType:: db -wNamingScreenCursorObjectPointer:: dw -wNamingScreenLastCharacter:: db -wNamingScreenStringEntryCoordY:: db -wNamingScreenStringEntryCoordX:: db - - ds 64 - -wc51a:: ds 1 - -ENDU - - -SECTION "Map Buffer", WRAM0 - -wMapBuffer:: -wMapScriptNumber:: db -wMapScriptNumberLocation:: dw -wUnknownMapPointer:: dw ; TODO -wc5ed:: db - ds 18 -wMapBufferEnd:: - - -UNION - -wOverworldMapBlocks:: ds 1300 -wOverworldMapBlocksEnd:: - -NEXTU - -wLYOverrides:: - ds SCREEN_HEIGHT_PX - ds $10 -wLYOverrides2:: - -NEXTU -; Battle-related - - ds $1ea - -wActiveBGEffects:: -wBGEffect1:: battle_bg_effect wBGEffect1 -wBGEffect2:: battle_bg_effect wBGEffect2 -wBGEffect3:: battle_bg_effect wBGEffect3 -wBGEffect4:: battle_bg_effect wBGEffect4 -wBGEffect5:: battle_bg_effect wBGEffect5 -wActiveBGEffectsEnd:: - -wNumActiveBattleAnims:: db - -wBattleAnimFlags:: db -wBattleAnimAddress:: dw -wBattleAnimDuration:: db -wBattleAnimParent:: dw -wBattleAnimLoops:: db -wBattleAnimVar:: db -wBattleAnimByte:: db -wBattleAnimOAMPointerLo:: db - db - -UNION -; unidentified -wBattleAnimTemp0:: db -wBattleAnimTemp1:: db -wBattleAnimTemp2:: db -wBattleAnimTemp3:: db - -NEXTU -wBattleAnimTempOAMFlags:: db -wBattleAnimTempField02:: db -wBattleAnimTempTileID:: db -wBattleAnimTempXCoord:: db -wBattleAnimTempYCoord:: db -wBattleAnimTempXOffset:: db -wBattleAnimTempYOffset:: db -wBattleAnimTempAddSubFlags:: db -wBattleAnimTempPalette:: db -ENDU - - ds $32 -wBattleAnimEnd:: - - ds $1aa ; TODO - - -wc9ef:: ds 1 - - ds 6 - -wBattleMonNickname:: ds 6 -wEnemyMonNickname:: ds 6 - -wca02:: ds 1 -wca03:: ds 1 -wca04:: ds 1 - - ds 3 - -wca08:: ds 1 -wca09:: ds 1 -wca0a:: ds 1 - - ds 5 ; TODO - -wca10:: ds 1 -wca11:: ds 1 -wca12:: ds 1 -wca13:: ds 1 -wca14:: ds 1 - - ds $22 ; TODO - -wca37:: ds 1 -wca38:: ds 1 -wca39:: ds 1 -wca3a:: ds 1 -wca3b:: ds 1 -wca3c:: ds 1 -wPlayerSubStatus3:: db - -wca3e:: ds 1 -wca3f:: ds 1 -wca40:: ds 1 -wca41:: ds 1 - -wEnemySubStatus3:: db - -wca43:: db - -wca44:: db - - ds $12 -wTrainerClass:: - db - -wca58:: ds 1 -wca59:: ds 1 -wca5a:: ds 1 -wca5b:: ds 1 -wca5c:: ds 1 - - ds $5c - -wcab9:: ds 1 - - ds 6 - -wcac0:: ds 1 -wcac1:: ds 1 -wcac2:: ds 1 - -wLinkBattleRNCount:: db - - ds 12 - -wcad0:: ds 1 - - ds 9 - -wcada:: ds 1 - - ds 6 - -wcae1:: ds 1 - -ENDU - - - - -SECTION "CB14", WRAM0[$CB14] - -UNION -wRedrawRowOrColumnSrcTiles:: -; the tiles of the row or column to be redrawn by RedrawRowOrColumn - ds SCREEN_WIDTH * 2 -NEXTU -wRedrawFlashlightDst0:: dw -wRedrawFlashlightSrc0:: dw -wRedrawFlashlightBlackDst0:: dw -wRedrawFlashlightDst1:: dw -wRedrawFlashlightSrc1:: dw -wRedrawFlashlightBlackDst1:: dw -wRedrawFlashlightWidthHeight:: db -; width or height of flashlight redraw region -; in units of two tiles (people event meta tile) -ENDU - -SECTION "CB56", WRAM0[$CB4C] -wOtherPlayerLinkMode:: db -wOtherPlayerLinkAction:: db - ds 3 ; TODO - -wPlayerLinkAction:: db - ds 4 ; TODO - -wLinkTimeoutFrames:: dw -wcb58:: ds 2 -wMonType:: db -wCurSpecies:: db -wNamedObjectTypeBuffer:: db - -SECTION "CB5E", WRAM0[$CB5E] -wJumptableIndex:: db -wFlyDestination:: db - -wcb60:: ds 1 -wcb61:: ds 1 - -wVBCopySize:: ds 1 -wVBCopySrc:: ds 2 -wVBCopyDst:: ds 2 -wVBCopyDoubleSize:: ds 1 -wVBCopyDoubleSrc:: ds 2 -wVBCopyDoubleDst:: ds 2 -wcb6c:: db -wcb6d:: db -wcb6e:: db -wPlayerStepDirection:: db - -SECTION "CB71", WRAM0[$CB70] - -wcb70:: db - -wVBCopyFarSize:: ds 1 -wVBCopyFarSrc:: ds 2 -wVBCopyFarDst:: ds 2 -wVBCopyFarSrcBank:: ds 1 -wPlayerMovement:: db -wMovementObject:: db - ptrba wMovementData - -wcb7c:: ds 1 - -SECTION "Collision buffer", WRAM0[$CB90] - -wTileDown:: db -wTileUp:: db -wTileLeft:: db -wTileRight:: db - -wScreenSave:: - ds 6 * 5 - -SECTION "CBB2", WRAM0[$CBB2] -wToolgearBuffer:: - ds $40 - -SECTION "CBF2", WRAM0[$CBF2] - -wWindowData:: -wWindowStackPointer:: dw -wMenuJoypad:: db -wMenuSelection:: db -wMenuSelectionQuantity:: db -wWhichIndexSet:: -wActiveBackpackPocket:: db -wScrollingMenuCursorPosition:: db -wWindowStackSize:: db - -SECTION "CC09", WRAM0[$CC02] - -wMenuDataHeader:: - db -wMenuBorderTopCoord:: db -wMenuBorderLeftCoord:: db -wMenuBorderBottomCoord:: db -wMenuBorderRightCoord:: db -wMenuDataPointer:: dw -wMenuCursorBuffer:: db - ds 8 ; TODO -wMenuDataHeaderEnd:: - -wMenuData2:: -wMenuDataFlags:: db -wMenuDataItems:: db -wMenuDataIndicesPointer:: dw -wMenuDataDisplayFunctionPointer:: dw -wMenuDataPointerTableAddr:: dw - -SECTION "MenuData3", WRAM0[$CC22] -wMenuData3:: - -w2DMenuCursorInitY:: db -w2DMenuCursorInitX:: db -w2DMenuNumRows:: db -w2DMenuNumCols:: db -w2DMenuFlags:: dw -w2DMenuCursorOffsets:: db -wMenuJoypadFilter:: db -w2DMenuDataEnd:: - -wMenuCursorY:: db -wMenuCursorX:: db -wCursorOffCharacter:: db -wCursorCurrentTile:: dw - -SECTION "CC32", WRAM0[$CC32] ; Please merge when more is disassembled -wVBlankJoyFrameCounter: db - -wVBlankOccurred: db -wLastSpawnMapGroup: db -wLastSpawnMapNumber: db - - ds 2 - -;Controls what type of opening (fire/notes) you get. -wcc38:: -wTitleSequenceOpeningType:: - db - -wDefaultSpawnPoint:: - db - -wMovementBufferCount:: db -wMovementBufferObject:: db - ptrba wMovementBufferPointer -wMovementBuffer:: - ds 55 - -SECTION "CC9A", WRAM0[$CC9A] - -wSkatingDirection:: db -wCompanionCollisionFrameCounter:: db - -wUnknownWordcc9c:: - dw - -wUnknownBuffercc9e:: - ds 14 - - -wSpriteCurPosX : ds 1 -wSpriteCurPosY : ds 1 -wSpriteWidth : ds 1 -wSpriteHeight : ds 1 -wSpriteInputCurByte : ds 1 -wSpriteInputBitCounter : ds 1 -wSpriteOutputBitOffset : ds 1 -wSpriteLoadFlags : ds 1 -wSpriteUnpackMode : ds 1 -wSpriteFlipped : ds 1 -wSpriteInputPtr : ds 2 -wSpriteOutputPtr : ds 2 -wSpriteOutputPtrCached : ds 2 -wSpriteDecodeTable0Ptr : ds 2 -wSpriteDecodeTable1Ptr : ds 2 - -wccc0:: ds 1 -wccc1:: ds 1 -wccc2:: ds 1 -wccc3:: ds 1 -wccc4:: ds 1 - -SECTION "CCC7", WRAM0[$CCC7] - -wDisableVBlankOAMUpdate:: db - -SECTION "CCCA", WRAM0[$CCCA] - -wBGP:: db -wOBP0:: db -wOBP1:: db - -wcccd:: ds 1 - -wDisableVBlankWYUpdate:: db -wSGB:: db - -SECTION "CCD0", WRAM0[$CCD0] - -wccd0:: ds 1 -wccd1:: ds 1 -wccd2:: ds 1 -wccd3:: ds 1 - - ds 5 - -wccd9:: ds 1 - -SECTION "CCE1", WRAM0[$CCE1] - -wcce1:: ds 1 -wcce2:: ds 1 -wcce3:: ds 1 -wcce4:: ds 1 - - ds 6 - -wcceb:: ds 1 - - ds 5 - -wccf1:: ds 1 -wccf2:: ds 1 -wccf3:: ds 1 -wccf4:: ds 1 - -SECTION "CD11", WRAM0[$CD11] - -wcd11:: ds 1 - - ds 20 - -wStringBuffer1:: ds 1 ; How long is this? -wcd27:: ds 1 -SECTION "CD31", WRAM0[$CD31] - -UNION -wStartDay:: db -wStartHour:: db -wStartMinute:: db - -NEXTU -wHPBarTempHP:: dw - -NEXTU -wStringBuffer2:: db ; How long is this? - -ENDU - - -SECTION "CD3C", WRAM0[$CD3C] - -wcd3c:: db -wRegularItemsCursor:: db -wBackpackAndKeyItemsCursor:: db -wStartmenuCursor:: db -wcd40:: db -wcd41:: db -wcd42:: db -wcd43:: db -wRegularItemsScrollPosition:: db -wBackpackAndKeyItemsScrollPosition:: db -wcd46:: ds 1 -wcd47:: ds 1 -wSelectedSwapPosition:: db -wMenuScrollPosition:: db - -wTextDest:: ds 2 - -wQueuedScriptBank:: db -wQueuedScriptAddr:: dw - -wPredefID:: - db - -wPredefHL:: - dw -wPredefDE:: - dw -wPredefBC:: - -wFarCallBCBuffer:: - dw - -wcd56:: ds 1 -wcd57:: ds 1 -wFieldMoveSucceeded:: db -wVramState:: db - - ds 3 ; TODO -wcd5d:: db - db -wChosenStarter:: db - -SECTION "CD70", WRAM0[$CD70] -wcd70:: ds 1 -wcd71:: ds 1 -wcd72:: dw -wcd74:: db -wcd75:: db - -wCurItem:: db -wItemIndex:: db -wMonDexIndex: db -wWhichPokemon: db - -SECTION "CD7B", WRAM0[$CD7B] - -wHPBarType:: db -wcd7c:: ds 1 - -wItemQuantity:: db -wItemQuantityBuffer:: db -wcd7f:: db -wcd80:: db -wcd81:: db - -SECTION "CD9E", WRAM0 [$CD9E] -wLoadedMonLevel:: db - -SECTION "CDAF", WRAM0 [$CDAF] -wcdaf:: db - -SECTION "CDB0", WRAM0 [$CDB0] -wTalkingTargetType:: db -;bit 0 = has engaged NPC in dialogue -;bit 1 = has engaged sign in dialogue - -wcdb1:: ds 1 -wcdb2:: ds 1 - -SECTION "CDB9", WRAM0[$CDB9] - -wcdb9:: ds 1 - -wItemAttributeParamBuffer:: db -wCurPartyLevel:: db - -SECTION "CDBD", WRAM0[$CDBD] - -wLinkMode:: db -; 00 - -; 01 - -; 02 - -; 03 - - -wNextWarp:: db -wNextMapGroup:: db -wNextMapId:: db -wPrevWarp:: db - - ds 1 - -UNION -wFieldMoveScriptID:: db -wMapBlocksAddress:: dw -wReplacementBlock:: db - -NEXTU - -wHPBarMaxHP:: dw -wHPBarOldHP:: dw - -ENDU - -wHPBarNewHP:: dw -wHPBarDelta:: db -wcdca:: db -wHPBarHPDifference:: dw - -wLinkBattleRNs:: ds 10 - -wcdd7:: ds 1 -wcdd8:: ds 1 -wcdd9:: ds 1 -wcdda:: ds 1 -wcddb:: ds 1 -wcddc:: ds 1 -wcddd:: ds 1 -wcdde:: ds 1 -wcddf:: ds 1 -wcde0:: ds 1 -wcde1:: ds 1 -wcde2:: ds 1 -wcde3:: ds 1 -wcde4:: ds 1 -wcde5:: ds 1 -wcde6:: ds 1 -wcde7:: ds 1 -wcde8:: ds 1 -wcde9:: ds 1 -wcdea:: ds 1 -wcdeb:: ds 1 - - -SECTION "CDFE", WRAM0[$CDFE] - -wcdfe:: ds 1 -wcdff:: ds 1 -wBattleMode:: db -wce01:: ds 1 -wce02:: ds 1 -wce03:: ds 1 -wce04:: ds 1 -wce05:: ds 1 -wce06:: ds 1 - -wMonHeader:: - -wMonHIndex:: -; In the ROM base stats data structure, this is the dex number, but it is -; overwritten with the dex number after the header is copied to WRAM. - ds 1 - -wMonHBaseStats:: -wMonHBaseHP:: - ds 1 -wMonHBaseAttack:: - ds 1 -wMonHBaseDefense:: - ds 1 -wMonHBaseSpeed:: - ds 1 -wMonHBaseSpecialAtt:: - ds 1 -wMonHBaseSpecialDef:: - ds 1 - -wMonHTypes:: -wMonHType1:: - ds 1 -wMonHType2:: - ds 1 - -wMonHCatchRate:: - ds 1 -wMonHBaseEXP:: - ds 1 - -wMonHItems:: -wMonHItem1:: - ds 1 -wMonHItem2:: - ds 1 - -wMonHGenderRatio:: - ds 1 - -wMonHUnk0:: - ds 1 -wMonHUnk1:: - ds 1 -wMonHUnk2:: - ds 1 - -wMonHSpriteDim:: - ds 1 -wMonHFrontSprite:: - ds 2 -wMonHBackSprite:: - ds 2 - -wMonHGrowthRate:: - ds 1 - -wMonHLearnset:: -; bit field - flag_array 50 + 5 ; size = 7 - ds 1 - -SECTION "CE2D", WRAM0[$CE2D] -wce2d:: ds 1 -wce2e:: ds 1 -wce2f:: ds 1 -wce30:: ds 1 -wce31:: ds 1 -wce32:: ds 1 -wce33:: ds 1 -wce34:: ds 1 -wce35:: ds 1 -wce36:: ds 1 - -wNamedObjectIndexBuffer:: -wCountSetBitsResult:: -wce37:: - db - -SECTION "CE3A", WRAM0[$CE3A] - -wce3a:: ds 1 - -wVBlankSavedROMBank:: - db - -wBuffer:: - db - -wTimeOfDay:: db -; based on RTC -; Time of Day Regular Debug -; 00 - Day 09--15h 00--30s -; 01 - Night 15--06h 30--35s -; 02 - Cave 35--50s -; 03 - Morning 06--09h 50--59s - -wcd3f: ds 1 - -SECTION "CE5F", WRAM0[$CE5F] - -wce5f:: ; debug menu writes $41 to it - db - -wce60:: - db ; main menu checks this, maybe states if there's a save present? - -wActiveFrame:: db - -wTextBoxFlags:: db - -wDebugFlags:: db -; Bit 0: Debug battle indicator -; Bit 1: Debug field indicator -; Bit 2-3: Game is continued (set when selecting continue on the main menu) - -wce64:: ds 1 -wce65:: ds 1 -wce66:: ds 1 - -wPlayerName:: ds 6 - -wMomsName:: ds 6 - -SECTION "CE73", WRAM0[$CE73] - -wce73: ds 1 -wce74: ds 1 -wce75: ds 1 - -wObjectFollow_Leader:: - db -wObjectFollow_Follower:: - db -wCenteredObject:: - db -wFollowerMovementQueueLength:: - db -wFollowMovementQueue:: - ds 5 - -wObjectStructs:: -; Note: this might actually not be an object. TODO: Investigate (if indexing starts at 1, then this isn't an object) -; It might just be unused/a leftover. -wUnkObjectStruct:: object_struct wUnkObject -wPlayerStruct:: object_struct wPlayer -wObject1Struct:: object_struct wObject1 -wObject2Struct:: object_struct wObject2 -wObject3Struct:: object_struct wObject3 -wObject4Struct:: object_struct wObject4 -wObject5Struct:: object_struct wObject5 -wObject6Struct:: object_struct wObject6 -wObject7Struct:: object_struct wObject7 -wObject8Struct:: object_struct wObject8 -wObjectStructsEnd:: - -wCmdQueue:: -wCmdQueueEntry1:: ds 16 -wCmdQueueEntry2:: ds 16 -wCmdQueueEntry3:: ds 16 -wCmdQueueEntry4:: ds 16 - -wMapObjects:: -wPlayerObject:: map_object wPlayer -wMap1Object:: map_object wMap1 -wMap2Object:: map_object wMap2 -wMap3Object:: map_object wMap3 -wMap4Object:: map_object wMap4 -wMap5Object:: map_object wMap5 -wMap6Object:: map_object wMap6 -wMap7Object:: map_object wMap7 -wMap8Object:: map_object wMap8 -wMap9Object:: map_object wMap9 -wMap10Object:: map_object wMap10 -wMap11Object:: map_object wMap11 -wMap12Object:: map_object wMap12 -wMap13Object:: map_object wMap13 -wMap14Object:: map_object wMap14 -wMap15Object:: map_object wMap15 -wMapObjectsEnd:: - -wToolgearFlags:: db -; 76543210 -; | | \- show toolgear -; | | -; | \--- transfer toolgear to window -; \-------- hide toolgear - - ds 2 ; TODO - -wTimeOfDayPal:: db -; Applied according to wCurTimeOfDay from wTimeOfDayPalset - -wd153:: db -; 76543210 -; | \- show player coords in toolgear instead of time -; \-------- switch overworld palettes according to seconds not hours - - ds 3 ; TODO -wTimeOfDayPalFlags:: db -; 76543210 -; \-------- disable overworld palette switch - -wTimeOfDayPalset:: db -; 76543210 -; \/\/\/\/ -; | | | \- Map Palette for TimeOfDay $00 (MORN) -; | | \--- Map Palette for TimeOfDay $01 (DAY) -; | \----- Map Palette for TimeOfDay $02 (NITE) -; \------- Map Palette for TimeOfDay $03 (DARK) - -wCurTimeOfDay:: db - -SECTION "D15B", WRAM0[$D15B] - -wd15b:: db - -wd15c:: db - -wd15d:: db - -wd15e:: db - -wd15f:: db - -SECTION "D163", WRAM0[$D163] - -wd163:: db - -wd164:: db - -wTMsHMs:: db - -SECTION "D19E", WRAM0[$D19E] - -wItems:: -wNumBagItems:: db - -SECTION "D1C8", WRAM0[$D1C8] - -wNumKeyItems:: db -wKeyItems:: db - -SECTION "D1DE", WRAM0[$D1DE] - -wNumBallItems:: db -wBallQuantities:: db - - ds 10 - -wUnknownListLengthd1ea:: db -wUnknownListd1eb:: db - -SECTION "Rival's Name", WRAM0[$D256] -wRegisteredItem:: db -wRegisteredItemQuantity:: db -wRivalName:: ds 6 - ds 6 - -wPlayerState:: db -; 00 - walking -; 01 - bicycle -; 02 - skateboard -; 04 - surfing - -wd265:: db -wd266:: db - -;The starting house's map script number is stored at d29a. Others are probably nearby. -SECTION "D29A", WRAM0[$D29A] -wd29a:: db -wd29b:: db -wd29c:: db -wd29d:: db -wd29e:: db - db -wd2a0:: db - -SECTION "D35F", WRAM0[$D35F] -wOptions:: db - -SECTION "D39D", WRAM0[$D39D] -wd39d:: db - -SECTION "D3A5", WRAM0[$D3A5] -wd3a5:: db - -SECTION "Game Event Flags", WRAM0[$D41A] -wd41a:: db -; 76543210 -; | \- read email? -; \-------- talked to Blue, triggers Oak -wd41b:: db -; 76543210 -; |\-- followed Oak to his back room -; \--- chose a starter -wd41c:: db -; 76543210 -; \----- recieved pokedexes -wd41d:: db -; 76543210 -; \--- beat rival in the lab -wd41e:: db - -SECTION "D4A9", WRAM0[$D4A9] - -wd4a9:: db - ds 1 ; TODO -wJoypadFlags:: db -; 76543210 -; ||||\__/ -; |||| \-- unkn -; |||\----- set for rival intro textbox -; ||\------ don't wait for keypress to close text box -; |\------- joypad sync mtx -; \-------- joypad disabled - -SECTION "wDigWarpNumber", WRAM0[$D4B2] - -wDigWarpNumber:: db -wd4b3:: ds 1 -wd4b4:: ds 1 -wd4b5:: ds 1 -wd4b6:: ds 1 -wd4b7:: ds 1 -wd4b8:: ds 1 -wd4b9:: ds 1 - - -SECTION "Warp data", WRAM0[$D513] - -wWarpNumber:: db - -wCurrMapWarpCount:: - db - -wCurrMapWarps:: -REPT 32 ; TODO: confirm this - ds 5 -ENDR - - -wCurrMapSignCount:: - db - -wCurrMapSigns:: -REPT 16 ; TODO: confirm this - ds 4 -ENDR - -wCurrMapObjectCount:: - db - -wCurrMapInlineTrainers:: -REPT 32 ; TODO: confirm this - ds 2 ; inline trainers. each pair of bytes is direction, distance -ENDR - -SECTION "D637", WRAM0[$D637] -wd637:: db ;OW battle state? $3 wild battle, $8 is trainer battle $4 is left battle, $B is load overworld? $0 is in overworld -wd638:: db ;wd637's last written-to value - -SECTION "Used sprites", WRAM0[$D643] - -wBGMapAnchor:: - dw - -wUsedSprites:: - dw ; This is for the player - -wUsedNPCSprites:: - ds 2 * 5 ; This is for the NPCs - -wUsedSpritesEnd:: - - -SECTION "Map header", WRAM0[$D656] - -wMapGroup:: db -wMapId:: db - -wOverworldMapAnchor:: - dw - -wYCoord:: db -wXCoord:: db - -wMetatileNextY:: db -wMetatileNextX:: db - -wd65e:: - db - -wMapPartial:: -wMapAttributesBank:: - db -wMapTileset:: - db -wMapPermissions:: - db -wMapAttributesPtr:: - dw -wMapPartialEnd:: - -wMapAttributes:: -wMapHeight:: - db -wMapWidth:: - db -wMapBlocksPointer:: - dw -wMapTextPtr:: - dw -wMapScriptPtr:: - dw -wMapObjectsPtr:: - dw -wMapConnections:: - db -wMapAttributesEnd:: - -wNorthMapConnection:: map_connection_struct wNorth -wSouthMapConnection:: map_connection_struct wSouth -wWestMapConnection:: map_connection_struct wWest -wEastMapConnection:: map_connection_struct wEast - - -wTileset:: -wTilesetBank:: - db -wTilesetBlocksAddress:: - dw -wTilesetTilesAddress:: - dw -wTilesetCollisionAddress:: - dw - ds 4 ; TODO -wTilesetEnd:: - -wPartyCount:: db -wPartySpecies:: ds PARTY_LENGTH -wPartyEnd:: db - -wPartyMons:: -wPartyMon1:: party_struct wPartyMon1 -wPartyMon2:: party_struct wPartyMon2 -wPartyMon3:: party_struct wPartyMon3 -wPartyMon4:: party_struct wPartyMon4 -wPartyMon5:: party_struct wPartyMon5 -wPartyMon6:: party_struct wPartyMon6 -wPlayerPartyEnd:: - -wPartyMonOT:: - ds PARTY_LENGTH * 6 -wPartyMonOTEnd:: - -wPartyMonNicknames:: - ds PARTY_LENGTH * MON_NAME_LENGTH ; = $24 -wPartyMonNicknamesEnd:: - -wPokedexOwned:: - flag_array NUM_POKEMON -wPokedexOwnedEnd:: - -wPokedexSeen:: - flag_array NUM_POKEMON -wPokedexSeenEnd:: - -wAnnonDex:: ds 26 - -wAnnonID:: ds 1 - -wd875:: ds 1 -wd876:: ds 1 - - ds 5 - -wd87c:: ds 1 - - ds 5 - -wd882:: ds 1 -wd883:: ds 1 -wd884:: ds 1 - -SECTION "D8A2", WRAM0[$D8A2] - -wd8a2:: ds 1 -wd8a3:: ds 1 -wd8a4:: ds 1 -wd8a5:: ds 1 - - ds 5 - -wd8ab:: ds 1 - -SECTION "wd8b1", WRAM0[$D8B1] - -wd8b1:: ds 1 - - ds 5 - -wd8b7:: ds 1 -wd8b8:: ds 1 - -SECTION "D8D1", WRAM0[$D8D1] - -wd8d1:: ds 1 - - ds 5 - -wd8d7:: ds 1 - - ds 5 - -wd8dd:: ds 1 - -SECTION "D8E3", WRAM0[$D8E3] - -wd8e3:: ds 1 -wd8e4:: ds 1 - -SECTION "D8FD", WRAM0[$D8FD] - -wd8fd:: ds 1 - -SECTION "D913", WRAM0[$D913] - -wd913:: ds 1 - -SECTION "Wild mon buffer", WRAM0[$D91B] - -UNION -wWildMons:: - ds 41 -NEXTU - ds 2 -wd91d:: ds 1 - ds 29 -wd93b:: ds 1 -ENDU - -SECTION "DA3B", WRAM0[$DA3B] - -wOTPartyMonOT:: db - -SECTION "DA5F", WRAM0[$DA5F] - -wda5f:: db - -SECTION "DA83", WRAM0[$DA83] - -wBoxListLength:: db -wBoxList:: ds MONS_PER_BOX - -SECTION "DAA3", WRAM0[$DAA3] - -wdaa3:: db -wdaa4:: db -wdaa5:: db - -SECTION "DE63", WRAM0[$DE63] - -wde63:: db - -SECTION "DF17", WRAM0[$DF17] -wdf17:: ds 1 - -SECTION "DFCB", WRAM0[$DFCB] -wdfcb:: ds 1 - -SECTION "Stack Bottom", WRAM0 - -; Where SP is set at game init -wStackBottom:: -; Due to the way the stack works (`push` first decrements, then writes), the byte at $DFFF is actually wasted -- cgit v1.2.3