+  RHDN Forum Archive
|-+  Romhacking
| |-+  ROM Hacking Discussion
| | |-+  [Pokémon RBY] Picture id to data pointer [SOLVED]
Pages: [1]
Author Topic: [Pokémon RBY] Picture id to data pointer [SOLVED]  (Read 1 times)
ubitux
Guest
« on: March 23, 2010, 09:44:05 am »

Hi,

I'm trying to understand the tie that is made between the picture id and the related data used to display entities in Pokémon Red/Blue/Yellow.

Each map of the game contains an object data which contains the number of warps, signpost and entities (people, trainers, classic objects). There is more information about this stuff here: http://www.datacrystal.org/wiki/Pokemon_Red/Blue:Notes. So what I call entities have a picture id entry, which is a simple char. Also, sprites are stored in the ROM image at addresses 0x10000 and 0x14000. But pictures id are not simple ids to those memory location: indeed, each entity has a different number of sprite, so It does not work as a simple id. There must be a sprite table somewhere.

So here is what I tried:

First, I went to the Map #1, and tried to understand how this entity is displayed:



In the ROM, the picture id (0x2F) is located at 0x0183CC (Pokémon Red US). So the game must read 0x43CC at a time. So I put a breakpoint access on this address.

The game does not break when the sprite is displayed, but it breaks at ROM0:1188 when we enter the map. Then, here is the code:

Code:
ldi a, (hl)      ; hl = 0x43CC, we have a = 0x2F as expected and we lost the hl pointer (increment)
ld (de), a       ; Ok, picture id saved at de = 0xC160
inc d            ; Pointer lost
ld a, 04         ; No more reference to the picture id

So the only way to get the picture id back is to read at 0xC160.

Next step was to put breakpoint access on 0xC160. The only accesses I saw were at ROM1:4CB1 and ROM1:4B28.

But here are the codes:

ROM1:4CB1
Code:
ld a, (de)     ; de = 0xC160, we have a = 0x2F as expected
and a          ; if a...
jp z, 4d69     ; jump to 0x4d69. We have a picture id, so it does not jump
inc e
inc e          ; We've lost our pointer...
ld a, (de)     ; and the value...

ROM1:4B28
Code:
ld a, (de)     ; As before, de = 0xC160 and a = 0x2F
and a          ; if a...
jp z, 4bad     ; jump to 0x4bad. We have a picture id, so it does not jump
inc e
inc e          ; same scheme, we've lost our pointer
ld a, (de)     ; and the value...

So how the !@#$ does the data pointer is determined?! The picture id is never used!

If I changed the written value at ROM0:1188, it changes the picture id, so it's the good value, there is no doubt about that.

It's been days I'm trying to understand how this can work, and I'm really stuck. I decomposed each frame leading to the display of the entity when it enters in the viewable area, but no, nothing more than the codes you see above.

According to the manual, there is an echo of 0xCxxx at 0xExxx, so I also tried to monitor accesses on 0xE160, but nothing.

Does anyone have any hint?
« Last Edit: April 29, 2010, 10:17:00 pm by ubitux »
Tauwasser
Guest
« Reply #1 on: March 23, 2010, 12:09:12 pm »

On Gold/Silver, there is a similar mapping.

It will have the following table in RAM:

[Picture ID][Tile ID]...

So basically, you may be looking at the wrong data. The 2F should only be used to see if the sprite gfx data for sprite 2F is actually in VRAM, then use the tile id (the tile number of the topmost tile) to display the sprite. The rest (viewing directions) are just relative offsets to the base tile id.

There is a table at 0xC20D that will be loaded with the picture IDs. Each entry is 0x10 bytes. The second byte is the offset from 0x8000 in 0x08 tile steps, it seems at first glance. You can basically work from there. One entry per people event. It seems there are other side effects as well.

Here's the commented asm on your examined code. Notice how pointers aren't lost!

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; People collision detection\t\t;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


ld h, $C1
ld a, [$FF00 + $DA]\t; P1's table entry offset
add a, $00\t\t; table base = C100
ld l, a
ld a, [hl]
and a, a
ret z\t\t\t; Stop if P1 not visible

;; Determine P1's collision dimensions (1x1, 1x2, 2x1, 2x2)

ld a, l
add a, $03
ld l, a
ldi a, [hl]\t\t; Vertical collision type
call $4D72
ldi a, [hl]\t\t; Y offset in px [Y|y]
add a, $04\t\t; 4px offset in y direction
add a, b\t\t; + 00 or - 01
and a, $F0
or a, c
ld [$FF00 + $90], a\t; store collision box v start [Y|0], or [Y-1|9], or [Y|7]
                        ;(y should be C for all sprites to work correctly)
ldi a, [hl]\t\t; Horizontal collision type
call $4D72
ld a, [hl]\t\t; X offset in px [X|x]
add a, b\t\t; + 00 or - 01
and a, $F0
or a, c
ld [$FF00 + $91], a\t; store collision box h start [X|0], or [X-1|9], or [X|7]
                         ;(x should be 0 for all sprites to work correctly)

;; Copy P1 data to data part

ld a, l
add a, $07
ld l, a
xor a, a
ldd [hl], a
ldd [hl], a
ld a, [$FF00 + $91]
ldd [hl], a
ld a, [$FF00 + $90]
ld [hl], a

;; Collision test

xor a, a\t\t; Begin with first (of 0x10 total) people events
@4CA5:
ld [$FF00 + $8F], a\t; spriteCounter * 0x10 is offset
swap a\t\t\t; conclude => struct in RAM 0x10 bytes big
ld e, a
ld a, [$FF00 + $DA]
cp a, e
jp z, @4D69\t\t; skip self (P1)
ld d, h
ld a, [de]
and a, a
jp z, @4D69\t\t; skip empty people events P2

inc e
inc e
ld a, [de]\t\t; Hidden status
inc a
jp z, @4D69\t\t; skip hidden people events (0xFF)

ld a, [$FF00 + $DA]
add a, $0A\t\t; P1's v collision box start
ld l, a

;; vertical collision test

inc e
ld a, [de]\t\t; Current P2's v collision type
call @4D72
inc e
ld a, [de]
add a, $04
add a, b
and a, $F0
or a, c\t\t\t; see above
sub a, [hl]\t\t; compare to P1 v start
jr nc, @4CD4
cpl
inc a\t\t\t; two's complement negative values
@4CD4:
ld [$FF00 + $90], a\t; vertical pixel offset to P1 (always positive) v start
push af
rl c
pop af
ccf
rl c\t\t\t; c = xxxxxxVv; xxxxxx = old c bits 5-0 (unimportant)
\t\t\t;      V: 1 = P1 below or inside P2
\t\t\t;\t  0 = P1 above P2
\t\t\t;      v: complement of V

;; P1 v box size

ld b, $07
ld a, [hl]\t\t; b = 07 for v collision type box or box to below
and a, $0F
jr z, @4CE6
ld b, $09\t\t; b = 09 for box to up

@4CE6:
ld a, [$FF00 + $90]
sub a, b
ld [$FF00 + $92], a\t; v start distance - v box size
ld a, b
ld [$FF00 + $90], a\t; P1's v box size
jr c, @4D01\t\t; if v distance less than box size, we have to check h (definitely v inside)

;; people event v box end correction

ld b, $07
dec e
ld a, [de]
inc e
and a, a
jr z, @4CFA
ld b, $09\t\t; P2's v box size
@4CFA:
ld a, [$FF00 + $92]
sub a, b
jr z, @4D01\t\t; continue testing on exact match or overlap (carry set)
jr nc, @4D69\t\t; skip further test when under people event, but outside of its v box

;; horizontal collision test

@4D01:
inc e
inc l
ld a, [de]\t\t; P2's h collision type
push bc\t\t\t; remember old c value
call @4D72
inc e
ld a, [de]
add a, b
and a, $F0
or a, c\t\t\t; see above
pop bc\t\t\t; restore old c value
sub a, [hl]\t\t; compare to P1 h start
jr nc, @4D14
cpl
inc a
@4D14:
ld [$FF00 + $91], a\t; horizontal pixel offset to P1 (always positive) h start
push af
rl c
pop af
ccf
rl c\t\t\t; c = xxxxVvHh; xxxx = old c bits 3-0 (unimportant)
\t\t\t;      V: 1 = P1 below or inside P2
\t\t\t;\t  0 = P1 above P2
\t\t\t;      H: 1 = P1 to the right or inside P2
\t\t\t;\t  0 = P1 to the left of P2
\t\t\t;      h,v: complement of H resp. V
;; P1 h box size

ld b, $07\t\t; b = 07 for h collision type box or box to left
ld a, [hl]
and a, $0F
jr z, @4D26
ld b, $09\t\t; b = 09 for box to right

@4D26:
ld a, [$FF00 + $91]
sub a, b
ld [$FF00 + $92], a\t; h start distance - h box size
ld a, b
ld [$FF00 + $91], a\t; P1's h box size
jr c, @4D41\t\t; if h distance less than box size, we have a collision (definitely h and v inside)

;; people event h box end correction

ld b, $07
dec e
ld a, [de]
inc e
and a, a
jr z, @4D3A
ld b, $09\t\t; P2's h box size
@4D3A:
ld a, [$FF00 + $92]
sub a, b
jr z, @4D41\t\t; collision on exact match or overlap (carry set)
jr nc, @4D69\t\t; skip further test when under people event, but outside of its h box



@4D41:
ld a, [$FF00 + $91]
ld b, a
ld a, [$FF00 + $90]
inc l\t\t\t; write to P1's byte 0x0C (blocked direction)
cp a, b
jr c, @4D4E\t\t; if h box size >= v box size
ld b, $0C\t\t; check vertical box
jr @4D50
@4D4E:
ld b, $03\t\t; else check horizontal box



@4D50:
ld a, c\t\t\t; get box matches
and a, b
or a, [hl]\t\t; last box matches
ld [hl], a
ld a, c\t\t\t; get box matches
inc l
inc l\t\t\t; write to P1's bytes 0x0E/0x0F (blocking person bit)

;; Transform table person no --> bit no

ld a, [$FF00 + $8F]
ld de, $4D85
add a, a
add a, e
ld e, a\t
jr nc, @4D62
inc d\t\t\t; spriteCounter * 2 + 01:4D85
@4D62:
ld a, [de]
or a, [hl]
ldi [hl], a
inc de
ld a, [de]
or a, [hl]
ld [hl], a\t\t; data is OR'd => more than one people event can block P1

@4D69:
ld a, [$FF00 + $8F]
inc a
cp a, $10\t\t; is max 0x10 people events reached?
jp nz, @4CA5
ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Collision Type rules\t\t\t;
;\t\t\t\t\t;
; a:\t00 - c = 00, b = 00\t\t;
;\tFF - c = 09, b = FF\t\t;
;\t<> - c = 07, a = 00, b = 00\t;
;\t\t\t\t\t;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

@4D72:
and a, a
ld b, $00
ld c, $00
jr z, @4D84
ld c, $09
cp a, $FF
jr z, @4D83
ld c, $07
ld a, $00
@4D83:
ld b, a
@4D84:
ret

RAM table:

0x10 bytes per entry:

[PictureID][Animation][Hidden (FF = yes, else no)][V box type][V offset in px]
[H box type][H offset in px][?][?][Facing][Corrected V offset][Corrected H offset]
[blocked directions][?][Blocking people event (2b)]

Basically, code like this is why everything is so slow to begin with. Sprites are only adjusted after all these collision tests and possibly other laggy code. That's why you can watch the sprites update two pixels late while walking. Mind-boggling solution... Instead of having x/y pairs on the map, like in G/S, they use this o.o!

cYa,

Tauwasser

MOD EDIT: Fixed some board-stretching formatting in the code section.
« Last Edit: March 24, 2010, 07:35:34 pm by KingMike »
ubitux
Guest
« Reply #2 on: March 24, 2010, 05:32:57 pm »

Quote from: Tauwasser on March 23, 2010, 12:09:12 pm
So basically, you may be looking at the wrong data. The 2F should only be used to see if the sprite gfx data for sprite 2F is actually in VRAM, then use the tile id (the tile number of the topmost tile) to display the sprite. The rest (viewing directions) are just relative offsets to the base tile id.

Can you be more explicit? I didn't understand. I mean, the entity picture is only identified by this 0x2F value, what else can be used? I'm lost with what you said.

Quote from: Tauwasser on March 23, 2010, 12:09:12 pm
There is a table at 0xC20D that will be loaded with the picture IDs. Each entry is 0x10 bytes. The second byte is the offset from 0x8000 in 0x08 tile steps, it seems at first glance. You can basically work from there. One entry per people event. It seems there are other side effects as well.

I didn't see any access to this address; how did you get it?

Quote from: Tauwasser on March 23, 2010, 12:09:12 pm
Here's the commented asm on your examined code. Notice how pointers aren't lost!

We agreed they are lost in the code I showed no? Where/how did you locate this people collision code? I didn't found it.

Anyway, thanks again for you help, but I'm a bit more confused this time.
Tauwasser
Guest
« Reply #3 on: March 24, 2010, 06:05:15 pm »

Quote from: ubitux on March 24, 2010, 05:32:57 pm
Can you be more explicit? I didn't understand. I mean, the entity picture is only identified by this 0x2F value, what else can be used? I'm lost with what you said.
While they are loaded, usually the route/city tileset maps differ greatly from inside maps (in G/S!). Usually, it will load 0x10 people events max. While loading, it will most likely build a table of sprite tiles. So for instance, in Hero's house, hero is located in Tiles 0x00 to 0x07. At 0x08, hero's mom starts. Hero's mom's sprite number is 0x33. It will build a table with 0x33 and the corresponding starting value of the tiles (0x08) somewhere.
There is a table constructed at 0xC20D, which I found using breakpoints while loading the map (it's right after the code you posted first IIRC, too!). It seemingly corresponds to people events and what sprite they use and maybe more.
You will actually get the to the right data by watching where the sprite is actually loaded from (via breakpoints on vram write to 0x8080, tile 0x08). This might be different for cities/routes than for inside maps. In G/S, this is mainly because routes and cities can be connected via warpless connections, therefore requiring the same "sprite set" be used (because they cannot be reloaded while changing maps without serious lags, hence route houses for some places are used). Therefore, you should best examine inside maps and cities/routes separately.

Quote from: ubitux on March 24, 2010, 05:32:57 pm
We agreed they are lost in the code I showed no? Where/how did you locate this people collision code? I didn't found it.
They aren't lost. I didn't find the routine, I simply looked at the offsets you gave in your post, particularly 01:4CB1.
It's not that hard to understand, I suggest you go over it -- it's a good learning experience I think. Also, people events will usually increase/decrease the h and v box collision types while walking, hence rendering the "safe" values to be either 0x00, 0x01, 0xFF.

cYa,

Tauwasser
ubitux
Guest
« Reply #4 on: April 10, 2010, 04:20:27 pm »

Sorry for the late response.

So the main reason I did not find the code using the picture id was simply that the table was not as you said re-written when walking through connections. Indeed, when trying to break while passing from a house to the outside, I got something new!

At ROM5:79F4, the picture ids are read and written in a some kind of table starting at 0xC21E with some 0x10 padding.

Later, this picture id is read again at ROM1:4C3F and written at 0xFF00+93 (dunno what's this). I'm actually stuck here since a while, but I'd really appreciate your help which unlocked me. :)
« Last Edit: April 10, 2010, 04:28:59 pm by ubitux »
KingMike
Guest
« Reply #5 on: April 10, 2010, 08:12:49 pm »

0xFF00+93 means CPU address $FF93. ($FF80-FFFE is 127 bytes of usable RAM. I believe it's known as HighRAM, though I suspect there's a technical reason to use it over the standard RAM ($C000-DFFF).)
Tauwasser
Guest
« Reply #6 on: April 10, 2010, 10:59:27 pm »

Quote from: KingMike on April 10, 2010, 08:12:49 pm
0xFF00+93 means CPU address $FF93. ($FF80-FFFE is 127 bytes of usable RAM. I believe it's known as HighRAM, though I suspect there's a technical reason to use it over the standard RAM ($C000-DFFF).)

Opcode size and access speed. HighRAM is pretty good for consecutive writes, where you would have to change the destination somewhat more often. You usually see this used in loops that evaluate some expression (like determine what map type some map is or something) and use it in a loop where c becomes the destination, like so:

Code:
xor a,a
ld bc,$1090
.loop:
ld [$FF00 + c], a
inc c
dec b
jnz .loop

(poor example, it's 5:00 AM..., but I've seen it used Cheesy)

Quote from: ubitux on April 10, 2010, 04:20:27 pm
At ROM5:79F4, the picture ids are read and written in a some kind of table starting at 0xC21E with some 0x10 padding.

I think that's the table I was talking about a bit earlier Wink

cYa,

Tauwasser
ubitux
Guest
« Reply #7 on: April 29, 2010, 10:16:38 pm »

Al'Right, I finally got it!

So, here is the method to get the rom address according to the picture id:

First focus on the info relative to the entity: 5 * 0x4000 + (0x7b27 + 4 * (pic_id - 1)) % 0x4000 (5 stands for the bank and 0x7b27 + 4 * (pic_id - 1) is the formula you can read around ROM5:78CD in the code.)

This will focus on a table with successive entries which look like [ADDR][BYTE][BYTE]

The ADDR is the tile addr, the first byte is some kind of counter you should not need, and the last byte is the bank. Then, the tile address in ROM is bank_id * 0x4000 + ADDR % 0x4000.

Here is a teaser of what I could get with this:



I just now to handle the default orientation, shouldn't be a big deal though.
Pages: [1]  


Powered by SMF 1.1.4 | SMF © 2006-2007, Simple Machines LLC