# HG changeset patch # User Dylan Holmes # Date 1334398828 18000 # Node ID 4901ba2d3860622ee99a0b3c97c6cb9683ee826f # Parent 21b8b3350b2055dee8b4636710507d71d0011d3a Figured out how NPC Pokemon teams are encoded. diff -r 21b8b3350b20 -r 4901ba2d3860 org/rom.org --- a/org/rom.org Sat Apr 14 05:41:55 2012 -0500 +++ b/org/rom.org Sat Apr 14 05:20:28 2012 -0500 @@ -1,8 +1,8 @@ #+title: Notes on Deconstructing Pokemon Yellow #+author: Dylan Holmes #+email: rlm@mit.edu -#+description: -#+keywords: +#+description: A detailed explication of Pok\eacute{}mon Yellow, helped by Clojure. +#+keywords: pokemon, pokemon yellow, rom, gameboy, assembly, hex, pointers, clojure #+SETUPFILE: ../../aurellem/org/setup.org #+INCLUDE: ../../aurellem/org/level-0.org #+BABEL: :exports both :noweb yes :cache no :mkdirp yes @@ -12,7 +12,6 @@ # pokedollar: U+20B1 * Introduction - ** COMMENT Getting linguistic data: names, words, etc. Some of the simplest data @@ -1011,6 +1010,83 @@ ** COMMENT Status ailments + +* NPC Trainers + +** Trainer Pok\eacute{}mon +# http://hax.iimarck.us/topic/103/ +There are two formats for specifying lists of NPC PPok\eacute{}mon: +- If all the Pok\eacute{}mon will have the same level, the format is + - Level (used for all the Pok\eacute{}mon) + - Any number of Pok\eacute{}mon internal ids. + - 0x00, to indicate end-of-list. +- Otherwise, all the Pok\eacute{}mon will have their level + specified. The format is + - 0xFF, to indicate that we will be specifying the levels individually[fn::Because 0xFF is a + forbidden level within the usual gameplay discipline, the game + makers could safely use 0xFF as a mode indicator.]. + - Any number of alternating Level/Pokemon pairs + - 0x00, to indicate end-of-list. + +*** Get the pointers +*** See the data +#+begin_src clojure :exports both :results output +(ns com.aurellem.gb.hxc + (:use (com.aurellem.gb assembly characters gb-driver util mem-util + constants)) + (:import [com.aurellem.gb.gb_driver SaveState])) + +(->> + (rom) + (drop 0x39E2F) + (take 21) + (println)) + + +(->> + (rom) + (drop 0x39E2F) + (take 21) + (partition-by zero?) + (take-nth 2) + (println)) + + + +(let + [pokenames + (zipmap + (rest (range)) + (hxc-pokenames-raw))] + + (->> + (rom) + (drop 0x39E2F) + (take 21) ;; (1922 in all) + (partition-by zero?) + (take-nth 2) + (map + (fn parse-team [[mode & team]] + (if (not= 0xFF mode) + (mapv + #(hash-map :level mode :species (pokenames %)) + team) + + (mapv + (fn [[lvl id]] (hash-map :level lvl :species (pokenames id))) + (partition 2 team))))) + + (println))) + + + +#+end_src + +#+results: +: (11 165 108 0 14 5 0 10 165 165 107 0 14 165 108 107 0 15 165 5 0) +: ((11 165 108) (14 5) (10 165 165 107) (14 165 108 107) (15 165 5)) +: ([{:species RATTATA, :level 11} {:species EKANS, :level 11}] [{:species SPEAROW, :level 14}] [{:species RATTATA, :level 10} {:species RATTATA, :level 10} {:species ZUBAT, :level 10}] [{:species RATTATA, :level 14} {:species EKANS, :level 14} {:species ZUBAT, :level 14}] [{:species RATTATA, :level 15} {:species SPEAROW, :level 15}]) + * Places ** Names of places @@ -1018,11 +1094,18 @@ #+begin_src clojure (def hxc-places "The hardcoded place names in memory. List begins at -ROM@71500. [Cinnabar] Mansion seems to be dynamically calculated." +ROM@71500. [Cinnabar/Cerulean] Mansion seems to be dynamically calculated." (hxc-thunk-words 0x71500 560)) +#+end_src +*** See it work +#+begin_src clojure :exports both :results output +(println (hxc-places)) #+end_src +#+results: +: (PALLET TOWN VIRIDIAN CITY PEWTER CITY CERULEAN CITY LAVENDER TOWN VERMILION CITY CELADON CITY FUCHSIA CITY CINNABAR ISLAND INDIGO PLATEAU SAFFRON CITY ROUTE 1 ROUTE 2 ROUTE 3 ROUTE 4 ROUTE 5 ROUTE 6 ROUTE 7 ROUTE 8 ROUTE 9 ROUTE 10 ROUTE 11 ROUTE 12 ROUTE 13 ROUTE 14 ROUTE 15 ROUTE 16 ROUTE 17 ROUTE 18 SEA ROUTE 19 SEA ROUTE 20 SEA ROUTE 21 ROUTE 22 ROUTE 23 ROUTE 24 ROUTE 25 VIRIDIAN FOREST MT.MOON ROCK TUNNEL SEA COTTAGE S.S.ANNE [POKE]MON LEAGUE UNDERGROUND PATH [POKE]MON TOWER SEAFOAM ISLANDS VICTORY ROAD DIGLETT's CAVE ROCKET HQ SILPH CO. [0x4A] MANSION SAFARI ZONE) + ** Wild Pok\eacute{}mon demographics #+name: wilds #+begin_src clojure @@ -1067,9 +1150,6 @@ * Appendices - - - ** Mapping the ROM | ROM address (hex) | Description | Format | Example | @@ -1118,8 +1198,8 @@ |-----------------------+-----------------+-----------------+-----------------| | 3997D-39B05 | Trainer titles (extended; see 27E77). This list includes strictly more trainers, seemingly at random inserted into the list from 27E77.[fn::The names added are in bold: YOUNGSTER, BUG CATCHER, LASS, *SAILOR*, JR TRAINER(m), JR TRAINER(f), POK\eacute{}MANIAC, SUPER NERD, *HIKER*, *BIKER*, BURGLAR, ENGINEER, JUGGLER, *FISHERMAN*, SWIMMER, *CUE BALL*, *GAMBLER*, BEAUTY, *PSYCHIC*, ROCKER, JUGGLER (again), *TAMER*, *BIRDKEEPER*, BLACKBELT, *RIVAL1*, PROF OAK, CHIEF, SCIENTIST, *GIOVANNI*, ROCKET, COOLTRAINER(m), COOLTRAINER(f), *BRUNO*, *BROCK*, *MISTY*, *LT. SURGE*, *ERIKA*, *KOGA*, *BLAINE*, *SABRINA*, *GENTLEMAN*, *RIVAL2*, *RIVAL3*, *LORELEI*, *CHANNELER*, *AGATHA*, *LANCE*.] | | | | 39B05-39DD0. | unknown | | | -| 39DD1- | (?) Pointers to trainer Pok\eacute{}mon | | | -| 3A289-3A540 (approx) | Trainer Pok\eacute{}mon | Consecutive level/internal-id pairs (as with wild Pok\eacute{}mon; see 04D89) with irregular spacing \mdash{} the separators seem to have some metadata purpose. | The first entry is 0x05 0x66, representing level 5 Eevee (from your first battle in the game[fn::Incidentally, to change your rival's starter Pok\eacute{}mon, it's enough just to change its species in all of your battles with him.].) | +| 39DD1-39E2E | Pointers to trainer Pok\eacute{}mon | Pairs of low-high bits. | The first pair is 0x2F 0x5E, which corresponds to memory location 5E2F relative to this 38000-3C000 bank, i.e.[fn::For details about how relative bank pointers work, see the relevant Appendix.] position 39E2F overall. | +| 39E2F-3A5B2 | Trainer Pok\eacute{}mon | Specially-formatted lists of various length, separated by 0x00. If the list starts with 0xFF, the rest of the list will alternate between levels and internal-ids. Otherwise, start of the list is the level of the whole team, and the rest of the list is internal-ids. | The first entry is (11 165 108 0), which means a level 11 team consisting of Rattata and Ekans. The entry for MISTY is (255 18 27 21 152 0), which means a team of various levels consisting of level 18 Staryu and level 21 Starmie. [fn::Incidentally, if you want to change your rival's starter Pok\eacute{}mon, it's enough just to change its species in all of your battles with him.].) | | 3B1E5-3B361 | Pointers to evolution/learnset data. | One high-low byte pair for each of the 190 Pok\eacute{}mon in internal order. | | |-----------------------+-----------------+-----------------+-----------------| | 3B361-3BBAA | Evolution and learnset data. [fn::Evolution data consists of how to make Pok\eacute{}mon evolve, and what they evolve into. Learnset data consists of the moves that Pok\eacute{}mon learn as they level up.] | Variable-length evolution information (see below), followed by a list of level/move-id learnset pairs. | | @@ -1143,15 +1223,61 @@ | 71C1E-71CAA (approx.) | Tradeable NPC Pok\eacute{}mon. | Internal ID, followed by nickname (11 chars; extra space padded by 0x50). Some of the Pokemon have unknown extra data around the id. | The first entry is [0x76] "GURIO######", corresponding to a Dugtrio named "GURIO". | | 7C249-7C2?? | Pointers to background music, pt II. | | | |-----------------------+-----------------+-----------------+-----------------| -| 98000-B8000 | Dialogue and other messsages. | Variable-length strings. | | +| 98000-B7190 | Dialogue and other messsages. | Variable-length strings. | | +| B7190-B8000 | (empty space) | | 0 0 0 0 0 ... | | B8000-BC000 | The text of each Pok\eacute{}mon's Pok\eacute{}dex entry. | Variable-length descriptions (strings) in Pok\eacute{}dex order, separated by 0x50. These entries use the special characters *0x49* (new page), *0x4E* (new line), and *0x5F* (end entry). | The first entry (Bulbasaur's) is: "It can go for days [0x4E] without eating a [0x4E] single morsel. [0x49] In the bulb on [0x4E] its back, it [0x4E] stores energy [0x5F] [0x50]." | -| BC000-BC60E | Move names. | Variable-length move names, separated by 0x50. The moves are in internal order. | POUND#KARATE CHOP#DOUBLESLAP#COMET PUNCH#... | +| BC000-BC60F | Move names. | Variable-length move names, separated by 0x50. The moves are in internal order. | POUND#KARATE CHOP#DOUBLESLAP#COMET PUNCH#... | +| BC610-BD000 | (empty space) | | 0 0 0 0 0 ... | | E8000-E876C | Names of the \ldquo{}190\rdquo{} species of Pok\eacute{}mon in memory. | Fixed length (10-letter) Pok\eacute{}mon names. Any extra space is padded with the character 0x50. The names are in \ldquo{}internal order\rdquo{}. | RHYDON####KANGASKHANNIDORAN♂#... | |-----------------------+-----------------+-----------------+-----------------| | E9BD5- | The text PLAY TIME (see above, 70442) | | | #+TBLFM: +** Understanding memory banks and pointers +#+begin_src clojure + +(defn endian-flip + "Flip the bytes of the two-byte number." + [n] + (assert (< n 0xFFFF)) + (+ (* 0x100 (rem n 0x100)) + (int (/ n 0x100)))) + + +(defn offset->ptr + "Convert an offset into a little-endian pointer." + [n] + (-> + n + (rem 0x10000) ;; take last four bytes + (rem 0x4000) ;; get relative offset from the start of the bank + (+ 0x4000) + endian-flip)) + +(defn offset->bank + "Get the bank of the offset." + [n] + (int (/ n 0x4000))) + +(defn ptr->offset + "Convert a two-byte little-endian pointer into an offset." + [bank ptr] + (-> + ptr + endian-flip + (- 0x4000) + (+ (* 0x4000 bank)) + )) + +(defn same-bank-offset + "Convert a ptr into an absolute offset by using the bank of the reference." + [reference ptr] + (ptr->offset + (offset->bank reference) + ptr)) +#+end_src + ** Internal Pok\eacute{}mon IDs ** Type IDs @@ -1663,30 +1789,32 @@ (take 0xFFF (drop 0x8800 (memory state)))))) -(defn test-2 [] - (loop [n 0 - pc-1 (pc-trail (-> state-defend (tick) (step [:a]) (step [:a]) (step []) (nstep 100)) 100000) - pc-2 (pc-trail (-> state-speed (tick) (step [:a]) (step [:a]) - (step []) (nstep 100)) 100000)] - (cond (empty? (drop n pc-1)) [pc-1 n] - (not= (take 10 (drop n pc-1)) (take 10 pc-2)) - (recur pc-1 pc-2 (inc n)) - :else - [(take 1000 pc-2) n]))) +;; (defn test-2 [] +;; (loop [n 0 +;; pc-1 (pc-trail (-> state-defend (tick) (step [:a]) (step [:a]) (step []) (nstep 100)) 100000) +;; pc-2 (pc-trail (-> state-speed (tick) (step [:a]) (step [:a]) +;; (step []) (nstep 100)) 100000)] +;; (cond (empty? (drop n pc-1)) [pc-1 n] +;; (not= (take 10 (drop n pc-1)) (take 10 pc-2)) +;; (recur pc-1 pc-2 (inc n)) +;; :else +;; [(take 1000 pc-2) n]))) (defn test-3 "Explore trainer data" - [] + ([] (test-3 0x3A289)) + ([start] (let [pokenames (vec(hxc-pokenames-raw))] (println (reduce str (map - (fn [[lvl pkmn]] - (str (format "%-11s %4d %02X %02X" + (fn [[adr lvl pkmn]] + (str (format "%-11s %4d %02X %02X \t %05X\n" + (cond (zero? lvl) "+" (nil? (get pokenames (dec pkmn))) @@ -1696,14 +1824,16 @@ lvl pkmn lvl - ) "\n")) - - (partition 2 - (take 100;;703 - (drop - 0x3A281 - ;; 0x3A75D - (rom))))))))) + adr + ))) + (map cons + (take-nth 2 (drop start (range))) + (partition 2 + (take 400;;703 + (drop + start + ;; 0x3A75D + (rom))))))))))) (defn search-memory* [mem codes k] (loop [index 0