News:

Don't forget to visit the main site! There's lots of helpful docs, patches, and more!

Main Menu

Converting SMILE to a modern language

Started by someperson, April 01, 2013, 12:08:23 AM

Previous topic - Next topic

Quietus

I'm guessing he means any / all of the rooms that shake, such as Kraid's room and the Lower Norfair statue room?

someperson

#26
I checked both those rooms just now. Seem fine.

Does anyone know about layer handling in the room state? In SMILE, in the pointers editor, it's called Layer1_2. I found it signals the tile behavior in Ceres Ridley room (that is, if you change it, the tiles mess up). I'm going through the rooms trying to make a list of layer handlers and what they do... has this been done already?

Edit: found slight mention of it here: http://metroidconstruction.com/docs/PhazarSMPointers.txt

P.JBoy

There's GF_Kennon's http://dl.dropbox.com/u/8779072/BankLogs/Bank8FLog.txt
Most Ceres room's Layer1_2 are $C976, except for the elevator shaft during the escape ($C96E) and Ridley's room's Layer1_2: $C97B:

$8FC96E 22 D7 83 84 JSL $8483D7[$84:83D7]   ;\
$8FC972             dx  0F 26 BA48          ;} Turn door to solid blocks


$8FC976 22 C7 DD 88 JSL $88DDC7[$88:DDC7]
$8FC97A 60          RTS


$8FC97B 08          PHP
$8FC97C E2 20       SEP #$20
$8FC97E A9 66       LDA #$66                ;\
$8FC980 85 5D       STA $5D    [$7E:005D]   ;} BG1 char base = BG2 char base = $C000
$8FC982 28          PLP
$8FC983 22 C7 DD 88 JSL $88DDC7[$88:DDC7]
$8FC987 A9 09 00    LDA #$0009              ;} $07EB = 9 (also happens in $88:D916 (Ridley's room again) and $88:D949 (Ceres elevator shaft))
$8FC98A 8D EB 07    STA $07EB  [$7E:07EB]   ;} Seemingly never otherwise written nor ever read)
$8FC98D 60          RTS
...

Crashtour99

Well, there are those falling ceiling tiles in the room right next to the elevator shaft, which iirc are also located near Ridley's sprites.  Dunno if those are loaded for all Ceres rooms during the escape though.  I wouldn't be surprised if they were.

someperson

I'm going to get back to the Layer1_2 stuff later. So how about enemies?

In particular, in a room state there's an enemy population pointer to a list of enemies somewhere in bank 0xA1. According to SMILE, entries in the list go like this:
species, x, y, orientation, special, unknown1, speed, unknown2
each of those things being 2 bytes, and the list ends when a species 0xFFFF occurs. Now, I tried playing with orientation. For the most part it does nothing. I set every enemy's orientation to 2. You know what happened? Some Geemers turned upside down, and the Ripper in the ocean fly room went really slowly. I'm thinking these fields are not consistent from species to species (species, x, and y seem consistent however). Does this sound right? Do you hackers play with these fields, or do you just copy entire entries from the vanilla ROM and not mess with the individual fields?

Same question for PLMs, because they are next on my list and very similar.

Crashtour99

species = 2 byte pointer to enemy header in $A0
x, y, = location within the room

The others are variables used by enemy AI for various purposes.  IIRC special is a flag that tells the main enemy processing routine to use a different kind of processing for that enemy, like for complex hitboxes and gfx (space pirates are a good example of this).  As another example, Zeb and Zebbo (2 bug-like enemies) use the same AI, but by setting a variable you can change one into the other.  It really depends on the enemy how they're used.

Enemies, enemy/room projectiles ($86), and PLMs ($84) are all very similar, though enemy/room projectiles will only be spawned by an enemy or PLM.

I don't really use SMILE (yet) so this may not be entirely accurate, but the room's PLM pointer should go to a list consisting of:
PLM ID = 2 byte pointer to PLM header in $84
x, y, = location in the room
PLM index = varying uses.  For some PLMs it will set a bit in a table somewhere (like item collected or door opened).  For others like scroll PLMs it will serve a different purpose.

P.JBoy

Jathys spent a while figuring out the parameters for each enemy, if you're using SMILE v2.5 you can see them in the enemy editor
...

someperson

#32
So I've been working on figuring out where in the game the Ceres shaft is stated as the first room. This post is to document and publicize what I'm finding before I forget it.

P.JBoy, I didn't have a chance to thank you for helping me out on IRC today. Thanks! And to be clear, to all of you, I don't take for granted the help you've given.

If anyone's wondering why it is important to know this, say you want to wipe out the entire level data and start from scratch. We do not know everything about the data in the ROM. It's moderately safe to guess that anything that is used as level data is probably level data. In reality this isn't true because the space-lacking programmers will reference the same memory for two purposes just because they happen to be the same thing. (I think this is called data mashing, but I don't know how popular that term is). For example, some rooms need very little scroll data (it's 1 byte per screen, a screen being 16x16 tiles -- so a save room only needs 1 byte of scroll data). So it's easy enough to find the correct scroll data hidden in some other data, rather than give it its own space. Luckily we have control over scroll data but it's hard to say what else is using tricks like this. Anyway, you wipe out all the stuff that you think you know what it does and this includes the Ceres shaft. As you are filling it back in, at some point you have to tell the game "this is the first room". One obvious workaround is to leave the Ceres shaft alone. There is a small disadvantage to this, namely that this could produce more fragmentation in your level data. But this is a hacker community, right? Not a "whatever I need to do to make my Super Metroid hack" community.

Anyway, I've found that the same door transition that the 2nd room in the game uses is also used after the cutscene or when you load a game reset after the intro. It is located at 0x83AB58 to the SNES or 0x1AB58 in a hex editor. You'll find 45 DF at that spot, which is a pointer in bank 8F, which means altogether the value is 0x8FDF45 or 0x7DF45, which you might recognize from the SMILE rooms list. If you change it, the game starts at that room instead (expecting a mode 7 room, mind you).

So I suppose the whole thing has moved up a level. The 2nd room's door pointers can be changed, and the game will still somehow know that 0x83AB58 has the transition to the first room.

I've been using Geiger's snes9x debugger, there's something I don't understand: how to get from RAM addresses to hex editor addresses. I'm familiar with the 24 bit address separated into bank and 16 bit offset, but it doesn't seem to be working the same here. Here's an example:


$82/DE74 AE 9B 07    LDX $079B  [$8F:079B]   A:0000 X:AB58 Y:0020 P:eNvmxdIzC
$82/DE77 BD 00 00    LDA $0000,x[$8F:DF45]   A:0000 X:DF45 Y:0020 P:eNvmxdIzC


The first instruction changes X's value to DF45. That value is loaded from [$8F:079B]. The way I would convert this to a hex editor address is to multiply the F by 0x8000 and get 0x78000, then add 0x079B to get 0x7879B. But I go there and can't find 45 DF. I suppose it might have something to do with the 0 being a 0 and not an 8?

Here's the code that seems to load the door transition:

$80/C443 AD 9F 07    LDA $079F  [$80:079F]   A:0001 X:0020 Y:0020 P:envmxdizC
$80/C446 0A          ASL A                   A:0006 X:0020 Y:0020 P:envmxdizC
$80/C447 AA          TAX                     A:000C X:0020 Y:0020 P:envmxdizc
$80/C448 AD 8B 07    LDA $078B  [$80:078B]   A:000C X:000C Y:0020 P:envmxdizc
$80/C44B 0A          ASL A                   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C44C 85 12       STA $12    [$00:0012]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C44E 0A          ASL A                   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C44F 18          CLC                     A:0000 X:000C Y:0020 P:envmxdiZc
$80/C450 65 12       ADC $12    [$00:0012]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C452 6D 8B 07    ADC $078B  [$80:078B]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C455 0A          ASL A                   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C456 18          CLC                     A:0000 X:000C Y:0020 P:envmxdiZc
$80/C457 7D B5 C4    ADC $C4B5,x[$80:C4C1]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C45A AA          TAX                     A:CB2B X:000C Y:0020 P:eNvmxdizc
$80/C45B BD 00 00    LDA $0000,x[$80:CB2B]   A:CB2B X:CB2B Y:0020 P:eNvmxdizc
$80/C45E 8D 9B 07    STA $079B  [$80:079B]   A:DF45 X:CB2B Y:0020 P:eNvmxdizc


Notice that A is DF45 at the end (Ceres shaft), and A and X are clobbered at $80/C443 and $80/C447 respectively. I think I'll delve into the code later. I notice it contains some addresses with that 0 that's bugging me.

Crashtour99

QuoteThe first instruction changes X's value to DF45. That value is loaded from [$8F:079B]. The way I would convert this to a hex editor address is to multiply the F by 0x8000 and get 0x78000, then add 0x079B to get 0x7879B. But I go there and can't find 45 DF. I suppose it might have something to do with the 0 being a 0 and not an 8?
It's loading a value from a RAM address, so you won't find it in the ROM.  As far as Super Metroid goes, any time you see a value loaded from an address below $8000 it's guaranteed to be RAM.  Other RAM addresses above $8000 will be prefaced with the bank location ( $7E or $7F ).  If you see it loading from an address above $8000 and there is no bank byte (long address) then it's loading a value from the current bank in the ROM.

As for that routine, using the RAM map helps a ton.

Commented disassembly:
LDA $079F  [$80:079F]   A:0001 X:0020 Y:0020     Region number
ASL A                               A:0006 X:0020 Y:0020     multiply by 2
TAX                                  A:000C X:0020 Y:0020     transfer to x
LDA $078B  [$80:078B]   A:000C X:000C Y:0020     Which save in the current area to load from? Might alternatively be X/Y of save station on map
ASL A                               A:0000 X:000C Y:0020      multiply by 2
STA $12    [$00:0012]      A:0000 X:000C Y:0020      store to $12 direct page RAM
ASL A                                A:0000 X:000C Y:0020      multiply by 2 again
CLC                                  A:0000 X:000C Y:0020      clear carry
ADC $12    [$00:0012]     A:0000 X:000C Y:0020      add with carry to $12 direct page RAM
ADC $078B  [$80:078B]   A:0000 X:000C Y:0020      then add with carry that to which save in current area to load from
ASL A                                A:0000 X:000C Y:0020      multiply by 2 again
CLC                                   A:0000 X:000C Y:0020      clear carry
ADC $C4B5,x[$80:C4C1]   A:0000 X:000C Y:0020      add with carry to a value from a table @ $80/C4B5 indexed by X  (this seems like it's probably a list of
                                                                                    area load stations grouped by region)
TAX                                   A:CB2B X:000C Y:0020       transfer to X
LDA $0000,x[$80:CB2B]   A:CB2B X:CB2B Y:0020       load a value from $80 with X as index.  $80/CB2B is probably a table of room ID's for said
                                                                                    area load stations grouped by region
STA $079B  [$80:079B]   A:DF45 X:CB2B Y:0020         Current room mdb (Room ID in SMILE)

So, it's basically saying "Hey, you're going into a save room of some sort, this is the Room ID"

someperson

Here's a "decompilation" of that assembly.


//X=region*2
$80/C443 AD 9F 07    LDA $079F  [$80:079F]   A:0001 X:0020 Y:0020 P:envmxdizC
$80/C446 0A          ASL A                   A:0006 X:0020 Y:0020 P:envmxdizC
$80/C447 AA          TAX                     A:000C X:0020 Y:0020 P:envmxdizc

//A=save*14
$80/C448 AD 8B 07    LDA $078B  [$80:078B]   A:000C X:000C Y:0020 P:envmxdizc
$80/C44B 0A          ASL A                   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C44C 85 12       STA $12    [$00:0012]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C44E 0A          ASL A                   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C44F 18          CLC                     A:0000 X:000C Y:0020 P:envmxdiZc
$80/C450 65 12       ADC $12    [$00:0012]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C452 6D 8B 07    ADC $078B  [$80:078B]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C455 0A          ASL A                   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C456 18          CLC                     A:0000 X:000C Y:0020 P:envmxdiZc

//A=saveTable[region][save]
$80/C457 7D B5 C4    ADC $C4B5,x[$80:C4C1]   A:0000 X:000C Y:0020 P:envmxdiZc
$80/C45A AA          TAX                     A:CB2B X:000C Y:0020 P:eNvmxdizc
$80/C45B BD 00 00    LDA $0000,x[$80:CB2B]   A:CB2B X:CB2B Y:0020 P:eNvmxdizc


Indeed, there's a table of 2 byte bank 0x80 pointers at 0x80C4B5/0x0044B5. Starting at 0x80C4C5/0x0044C5 and ending at 0x80CD07/0x004D07 is a bunch of 14 byte save specifications. All these things are contiguous, even though they don't have to be. These save specifications include room header pointers and pointers to what are typically called doors or door out or door data, I can't keep it straight. I think it's better to call these things transitions, because they are used to go from one room to another. A door, an elevator, a save point, and the start of the game could all be using the same transition. For example, there are 2 ways into the Ceres shaft -- from the start of the game down an elevator, and from the 2nd room in the game through a door -- and they both use the same transition.

So anyway, here's how to change the location of the starting room, and I'm going to use only hex editor addresses here. Go to the start of the save table at 0x44B5. Counting in 2 byte spaces, move forward by the Ceres region number, 6. This should land you at 0x44C1. These two bytes, in reverse order, give you 0xCB2B, which you can convert to hex editor address by subtracting 0x8000 to get 0x4B2B. We go there. You can see here the 45 DF that specifies Ceres shaft, and the 58 AB that specifies the transition to it. I tried changing these 4 bytes to F8 91 6A 89, an attempt to load the landing site. I was mildly successful, but I think the game is still expecting a mode 7 room.

I'm happy with this result for now, because I should be able to change where the first room is, and for my own purposes I plan to keep the escape at the beginning. At this point though, I think I'm going to say that if you want this feature, or any other, make it known. I'll edit the first post to reflect this.

someperson

Yup -- I've successfully moved the first room around and overwritten every other room with 0xD0. I've updated the git repo. Here's the non-library code that did it, in case you want to try yourself (I'm not putting this code in the repo because I'm not planning on maintaining it).

#include "sm.hpp"

using namespace sm;

int main(){
//open rom
Rom rom;
if(rom.open("sm.smc")!="") return 1;
if(!rom.indexVanilla()) return 2;
//get the first room
Room room(rom);
if(!room.open(VANILLA_ROOM_OFFSETS[256])) return 5;
//save room
U32 roomOffset;
if(!room.save(roomOffset)) return 3;
//get the first save
Save save(rom);
save.open(save.readRegionTable(CERES));
save.room=roomOffset;
//get the first transition
Transition transition(rom);
transition.open(save.transition);
transition.room=roomOffset;
//get and save the first room mode 7 data
Mode7 mode7(rom);
mode7.open(17);
mode7.save(17);
//save transition
U32 transitionOffset;
if(!transition.save(transitionOffset)) return 4;
//save save
save.transition=transitionOffset;
U32 saveOffset;
if(!save.save(saveOffset)) return 6;
//set the save table accordingly
save.setRegionTable(CERES, saveOffset);
//save rom
rom.dummify();
rom.save("edited.smc");
return 0;
}

Silver Skree

#36
* Silver_Skree sticks his head out from beneath his burrow

This is a very cool project you're doing. I'd like to help you out however I can, but I don't think I'm very capable right now thanks to a certain other pursuit eating up all my free time. Although, my connections might help you more than my time would; I'm the brother of DFPercush, the guy who made SMAll. I saw you mention it earlier in the thread. If it'd help you out, I can get you into contact with him over IRC, or, if you'd prefer, something less time-specific.

Also, a few posts ago you mentioned wondering how to convert between SNES addresses and file offsets. I dunno if you already know of this tool, but if you want to save yourself some head-math at the expense of another open window, check out Lunar Address.


EDIT: Also, I should mention that I have an influence on his motivational direction. If you want SMAll to get some more development, I could nudge him towards that. No guarantees, though.

someperson

Hey,

Thank your brother for releasing the source code publicly. Makes it so much easier; I haven't managed to get the SMILE JX code from JAM even though he said he sent it and moved on. (Not pointing fingers or publicly airing grievances; it's just what happened).

I'll probably be peaking into SMAll every once in a while, so thanks for the networking opportunity with your bro.

Regarding contributing to the library: as of now, the library is "theoretically usable". I'll be actually using it when I feel like making the randomizer idea I had. As long as you understand the usefulness of separating library functions from randomizer/editor/glitcherator/viewer functionality, I think it'll be pretty easy to contribute. You can create your own randomizer/editor/glitcherator/viewer that uses the library and request functionality/documentation from me when it lacks, or make it yourself. Or you can directly create, say, the music part of the library without actually using it because, hey, maybe you're looking into how music works in SM right now because you're curious. If so, writing C++ code is one way to keep track of everything you're learning. It's OK if that stuff is only "theoretically usable" -- it'll get tested and corrected eventually.

Antidote

hey some person, nice library, it works pretty well. I had to make some modifications to get it compiling on linux but it was rather painless (a few odd issues with min and max o.o)

I do have one gripe however, I understand you're still kinda new to C++, so you probably don't know this, but "using namespace" is very bad practice and should be avoided at all costs.

Musaran

Anyone can provide a build ?


I tried to do it myself, but as usual with dev stuff it spiraled in an incomprehensible maƫlstrom. :neutral:

Musaran

My DansSmv build (or at least attempt)

All I get is this on the command line :
QuoteCouldn't open ROM.


I stop here ATM.