The current URL is datacrystal.tcrf.net.
Phantasy Star II/Notes
The following article is a Notes Page for Phantasy Star II.
The notes are for the american version. Offsets are different in the japanese version (checked).
Graphics Storage
Basic Compression
The game uses one basic compression scheme, for storing some of the arts, as well as other data (such maps). Here's its description :
First, the data is split in 32 bytes chunks. Then, for each chunk:
- N (byte) : it's a counter. If $FF, the compression is over.
- repeat N times:
- V (byte) : the value that will be decoded
- M (long) : a mask indicating the locations in the current 32 byte chunk where to put this value
- At the end of the loop, some locations in the chunks may not have got a value. If so, they are given in raster order after at this point.
- Go to first stage.
An example from the game : the font is located at 0x29EB8
01 : N = 1
we repeat 1 time :
BB : the value
FF88B888 : the mask (=0b11111111100010001011100010001000). The current chunk is :
BBBBBBBB BBBBBBBB BB?????? BB?????? BB??BBBB BB?????? BB?????? BB??????
Since the loop is over, all subsequent bytes are values for each ?? in the preceding (there are 16 of them) :
11 11 11 16 66 66 16 16 BA AA 16 BA AA 16 BA AB : we get for the 1st chunk :
BBBBBBBB BBBBBBBB BB111111 BB166666 BB16BBBB BB16BAAA BB16BAAA BB16BAAB
02 : N = 2, so
BB, EE008000 : that gives
BBBBBB?? BBBBBB?? ???????? ???????? BB?????? ???????? ???????? ????????
55, 00102277 : now we have
BBBBBB?? BBBBBB?? ??????55 ???????? BB??55?? ????55?? ??555555 ??555555
16 values are left unknown :
B1 15 11 11 B1 66 6B 16 15 B1 61 AB 15 56 B1 15 : so the final value is :
BBBBBBB1 BBBBBB15 1111B155 666B1615 BBB15561 AB155556 B1555555 15555555
Some example of such graphisms (to be completed) :
29EB8 : Font
2AC66 : SEGA logo
6FF22 : intro art (2)
Nemesis compressed graphisms
Some art uses the well-known Nemesis compression scheme. You can use The Sega Compressor to decompress / recompress them.
Some example of such graphisms : see maps storage.
Font storage
Font patterns
The font is made of 192 8x8 patterns (I use the term 'pattern' to denote 8x8 objects stored in VRAM ; a "tile" is made of several patterns, see later), including :
- icons for battle windows, status, check boxes
- two complete alphabet sets, uppercase and lowercase
- some remaining of the japanese version, unused
Encoding
The encoding doesn't follow the pattern ordering. An actual character is in fact a 8x16 tile (2 patterns heigh) ; the upper pattern is always a space and the bottom one is the actual glyph (in order to allow interline space).
The correspondance is stored in a table starting at 0x12BC8. Each entry is 2 bytes, the first being always 0x26 (rank of the space in the font patterns ; this was used in the Japanese font to store the dakuten).
For example, the table starts with :
26 26 : so the character with code 0 is made of 2 stacked spaces,
26 97 : one space on top of the 0x97th font pattern, which represents the digit "0"
etc.
Special codes
- 0xBB : [character1] this code is replaced by the name of a character
- 0xBC : [character2] same thing (allows to have 2 characters names in the same sentence, for texts like "XXX heals YYY"
- 0xBD : [monster] replaced by a monster name
- 0xBE : [technique] replaced by a technique
- 0xBF : [item] replaced by an item name
- 0xC0 : [value] replaced by a numeric value
- 0xC1 : carriage return
- 0xC2 : [clear] clear current window
- 0xC3 : [wait] wait for a button to be pressed
- 0xC4 : [end] end of text
Texts
Towns
Towns' names can be found at address 0x10FAE, though I can't confirm it's effectively read by the game. Each name is 5 bytes long.
Note : some names were truncated to fit in 5 bytes. For example, Arima's real name is Arimaya, Oputa is in fact Oputano, Kueri is Kueris.
Items
Items' names are storen in a table starting at 0x12D68.
Each entry in the table is 16 bytes long, the ten first bytes are the item's name.
Then comes :
- (w) price
- (b) type : still unclear
- (b) who can equip it ? it's a record of eight bit-flags. Bit 0 is Rolf, up to bit 7 which is Shir.
- (b) attack modifier
- (b) defense modifier
Throughout the game, items are identified by their index in this table.
Techniques
It's the same scheme. The table is located at 0x13568.
Each entry is 8 bytes long, the 5 first code for the name of the technique, then :
- (b) tp cost
- (b) type (to be studied)
- (b) power (to be studied)
For example, FOI, GIFOI and NAFOI share the same type (0xF1), but their power go increasing (0x0F, 0x28, 0x82).
Ennemies
Same scheme. Table begins at 0x1779E, each entry is 10 bytes long and only consist in the ennemy name.
Musics
Likely only used when visiting Ustvestia. Located at 0x18ADA, 12 bytes per track.
Dialogs
There's a table of adresses (longs) at 0x18BE2. Each address point to a sub-table of inremental offsets (one byte each) : the first offset must be added to the subtable address to get the first dialog, the second offset must be added to the first dialog address to get the second dialog, etc.
Other said, n-th value in the subtable is the length of the (n - 1)-th dialog.
In the game, each dialogue is identified by a word : aabb, where aa is the index of the address in the table (starting at 0), and bb the index in the sub-table (starting at 1). It can help for locating some pieces of code.
Example : dialog with id = 0x1502 : The address of the subtable is at 0x18BE2 + 0x15*4 = 0x18C36, we find 0x1DB5A
At this address we read 04 28, so the address of the 1st dialog is at 0x1DB5A + 0x04 = 0x1DB5E and the address of the 2nd is at 0x1DB5E + 0x28 = 0x1DB86.
We find the data :
0B 00 3D 33 ... C4 (up to 0x1DC18)
that translates to :
A young girl is battling a giant demon. I am close by, but can't move or speak! All I can do[wait] is watch while the demon keeps striking at the girl.
The first table allows to move the dialog texts in the ROM quite easily.
Due to the system of incremental pointers, a text is limited to 255 characters maximum. To overcome this limitation, the game engine allows to load up to 4 texts one after another, but it has to be hardcoded.
I implemented a quick hack that allows, inside the dialog text, to give the index of the next dialog, so it's not hardcoded anymore. It seems to work.
End of text is generally given by a 0xC4 or a 0xC5 value. But if not present, the dialog decrypting routine stops by itself once the 255-th character reached.
Interface
The basic interface system (blue windows and checkboxes with a red flashing cursor) is quite annoying to modify (for example with the aim to enlarge or rearrange windows, to allow longer words).
Windows
The windows information is stored as a table located at 0x13760.
Each entry is 8 bytes long, under the form :
- a word, of the form 0x4000 + y*128 + 2*x, where x and y are the coordinates (in 8x8 units) of the top-left corner of the window
- a long, which is an offset to the representation of the content of the window (see later)
- a byte, which is the width (in 8 units) of the window - 1 (including borders)
- a byte, which is the height (in 8 units) of the window - 1 (including borders)
The first entry of the table has no use as far as I can tell.
The offset to the representation of the window can be located in ROM, for windows whose content is always the same, or in RAM, for windows whose content varies (for example, character selection windows, item or technique selection).
Example : the main menu window has id 1. Its description starts at 0x13768 :
4082 00013B08 08 0A
0x4082 = 0x4000 + 1*128 + 1*2, so the window is located at (1, 1) with 8x8 units, that is to say at (8, 8) in pixels units. Its width is 9 x 8 = 72 pixels, and its height is 11 x 8 = 88 pixels, and the content of the window is located at ROM 0x13B08.
At 0x13B08, we find :
B9 B9 B9 B9 B9 B9 B9 ======= (upper border) B4 B5 2F 3A 2B 33 26 []ITEM ([] = checkbox) 26 26 26 26 26 26 26 (spaces to separate lines) B4 B5 39 3A 27 3A 2B []STATE 26 26 26 26 26 26 26 B4 B5 3A 2B 29 2E 26 []TECH 26 26 26 26 26 26 26 B4 B5 39 3A 38 34 2D []STRNG 26 26 26 26 26 26 26 B4 B5 2B 37 36 26 26 []EQP BE BE BE BE BE BE BE ======= (lower border)
Notes on RAM window : basic layout is stored in ROM (often, the checkboxes). Finding the address in ROM requires looking into the asm.
Menu
Editing a menu not only requires to edit the window attributes described above, but also the location of the flashing red rectangle (cursor). Sadly, these locations are hardcoded, that means you have to look into the code.
For example, the location of the cursor for the main menu described above is hardcoded at address 0xF7E0 by the lines:
move.w #$90,d1 move.w #$90,d2 bra.w setup_checkboxes
the position is given by $80 + actual position, here the actual position is (16, 16).
Some windows with their ids and notes
when followed by a *, the window content is read from the RAM (see above)
- 01 : main menu
- 02*: main menu character selection for items ; basic layout is at 0x15650
- 03*: main menu item selection 1
- 04*: main menu item selection 2
- 05 : use/give/tos item
- 06*: give item, character selection
- 07 : ?
- 08 : dialog window
- 09 : Yes / No
- 10 : State / Order
- 11 : 1st character state
- 12 : 2nd character state
- 13 : 3rd character state
- 14 : 4th character state
- 15 : meseta in state screen
- 16 : new order
- 17 : old order
Maps
Not everything is understood about map.
Let's start with vocabulary : I call a 'pattern' a 8x8 bitmap, a 'tile' is a 32x32 bitmap, made of 16 patterns. A 'layer' is an assembling of several tiles.
A map consists of several things :
- a set of pattern
- a set of tiles, made with those patterns
- the map width and the map height
- 2 layers, made with those tiles
- some palettes infos
- infos about NPC (patterns, sprites infos, events related to them)
- infos about exits (what do they connect to ?)
- maybe more
The pattern have already been covered in section Graphism ; they are Nemesis compressed.
Patterns and Tiles sets
The tileset_id refers to a table located at 0x9342. Each entry consists of two long pointers, the first is the patterns data (Nemesis compressed), the second if the tile set by itself.
The tiles sets are compressed using the basic scheme described in section I.1.
The data obtained must be read as sequences of 16 words, one per pattern, in the classical Genesis plane data format : each word is of the form
pcchvaaa aaaaaaaa
where aaaaaaaaaaa is the index of the pattern (map tiles patterns are loaded starting from VRAM 0x0), p is the priority, c the palette, h = 1 if the pattern must be horizontally flipped, v = 1 if vertically flipped.
Layer Data
One byte per tile ; total size of buffer is w*h for layer0, w*h/4 for layer1 if bit7 of byte12 in the Map Table (see below) is set, w*h otherwise.
Collision Data
Palettes
The palettes are loaded in RAM (in Genesis format : 16 words of the form 0RGB) from adress 0xFFFB00. So first palette is at 0xFFFB00, second at 0xFFFB20, etc.
The routine that loads them works as follow : it take an palette id as parameter, then looks into a table that starts at 0x1197C, and whose entries are 8 bytes long :
- 0 (l) : the source address
- 4 (w) : the dest address in RAM
- 6 (w) : the number of longs to copy - 1
For example, if the palette id is 0x4C, then the corresponding entry in the table is at 0x1197C + 0x4C*8 = 0x11BDC ; we find :
000123C4 FB40 000F
So we copy 0xF + 1 = 16 longs (that is, 64 bytes, or 2 palettes) from ROM address 0x123C4 to RAM adress 0xFB40 (address of palette 2). So we load palettes 2 and 3, that are exactly the palettes used to render the patterns for the maps (palettes 0 and 1 are used for sprites).
Shop Events
Contained in a table whose entries are 6 bytes long, ended by FF FF.
Description of an entry :
- 00 (b) : y position (in tiles)
- 01 (b) : x position (in tiles)
- to be completed
Map Table
The map table is located at 0x27C0A ; each entry is 20 bytes long :
- 00 (b) : tileset_id ; determines which patterns and set of tiles are loaded (see above) ; all dezorian maps have the MSB of this byte set. From what I see, it only results in the shopkeepers speaking dezorian.
- 01 (b) : dimensions ; this byte is of the form hw (h and w are nybbles) and the actual size of the map is then (in pixels) : 256h x 256w
- 02 (l) : pointer to layer 0 ; the first byte of this long is the battle_id of the map in the first part of the game (before Nei's death)
- 06 (l) : pointer to layer 1 ; the first byte of this long is the battle id of the map in the second part of the game (after Nei's death)
- 0A (l) : pointer to collision data ; the first byte codes for miscellaneous infos : only the 5 lsb are signifient. What follows is not sure, feel free to complete or fix :
- if bit0 = 0, the map is toric (top = bottom and left = right) ; only Motavia map uses that
- usage of bit1 is unknown
- if bit2 = 1, the bottom layer doesn't move (used for spatial levels, Gaira and Noah)
- bit3 = 1 for levels whose top layer is made of pipes
- bit4 seems to select which layer contains the tiles that must be used for checking collisions
- 0E (l) : pointer to "shop events" ; the first byte is a palette id. See section palette for more details.
- 12 (b) : the MSB makes the layer1 moves twice as fast as the layer0 (pipes in dungeons for example) ; the lower nybble is the set of NPC to load
- 13 (b) : music id ; let the MSB clear cancel the music change from the previous scene.
Exits
Exiting from a map can be done three ways :
- it can be coded as a shop event
- it can occur when you walk on special tiles : tiles 0x53 (Motavian towns) or 0x72 (Dezorian towns)
- it can occur when the collision info attached to the tile you walk on is equal to some values
This part is still under researches.