Pages: 1
Posted on 02-10-17, 05:27 am (rev. 2 by shibboleet on 02-10-17, 05:35 am)
Mole
Normal user

Karma: 2013
Posts: 255/359
Since: 07-08-12
So this is a (lame?) attempt and showing the newbies how the actors in NSMBDS really work. Now this tutorial assumes that you've already learned some C, C++, and ARM assembly. If you haven't learned any of these (but really ARM in general if you just want to get a tiny grasp), you should exit this thread and try to learn these languages. Oh, and you also have to have a version of IDA. I use 6.8 (used to use 6.1 but now the idb fails for some reason.)

So to actually figure out how an actor works, you first have to find the pointer to it's profile in the profile pointer list. This is located at address 0x0203997C in the game's code (I think it's US region, correct me if I'm wrong, and to get to this address you hit "g" in the IDA view, and then paste that address). The profile pointer list literally points each entry to the actor's constructor, which is what we'll get into later. Now during the 10 minutes I spent looking at the IDB, I'm not sure how one would calculate how to find an actor. But for the sake of example, we will use the Jumping Cheep Cheep's pointer. This pointer is at 0x0203997C.

main0:0203997C DCD off_2228F10 ; 136


if you're using a outdated IDB, it will appear as this. Highlight off_2228F10 in IDA and hit "n". In the field, simply type in "JumpingCheepCheep__Profile" and hit enter. Then it should show up as this:

main0:0203997C DCD JumpingCheepCheep__Profile; 136


and if it looks like that, highlight it and hit enter OR just double click on the name. After you click on it, you should get something like this, except that JumpingCheepCheep_ctor is labeled something else. Highlight it and name it what I have below.

Now to actually break this down. The first line, DCD, means to pretty much load into memory. So in this example, the constructor is loaded into memory and accessed. The second line just says that to get to this position, you had to come from the profile pointer table.

ov50:02228F10 JumpingCheepCheep__Profile DCD JumpingCheepCheep_ctor ov50:02228F10 ; DATA XREF: main0:profilePointerTableo


After renaming the routine, double click on it. You should have something like this:
ov50:02228DC0 JumpingCheepCheep_ctor ; DATA XREF: ov50:off_2228F10o ov50:02228DC0 STMFD SP!, {R4,LR} ov50:02228DC4 LDR R0, =0x4C8 ov50:02228DC8 BL allocFromGameHeap ov50:02228DCC MOVS R4, R0 ov50:02228DD0 BEQ loc_2228DE8 ov50:02228DD4 BL sub_209DEE0 ov50:02228DD8 LDR R1, =JumpingCheepCheep__vtable ov50:02228DDC ADD R0, R4, #0x3F4 ov50:02228DE0 STR R1, [R4] ov50:02228DE4 BL sub_2019644 ov50:02228DE8 ov50:02228DE8 loc_2228DE8 ; CODE XREF: JumpingCheepCheep_ctor+10j ov50:02228DE8 MOV R0, R4 ov50:02228DEC LDMFD SP!, {R4,PC} ov50:02228DEC ; End of function JumpingCheepCheep_ctor


yeah, that's a lot of information for you at once. But to be pretty blunt, this is what the code does:
dword JumpingCheepCheep_ctor() { dword v0 = (dword)allocFromGameHeap(1224); // allocates from the heap to get memory if (v0) // successfully allocated enough memory { EnemyActor__ctor(); // EnemyActor::ctor, which is something that JumpingCheepCheep inherits *v0 = JumpingCheepCheep::vtable; // pointer to the vtable sub_2019644(v0 + 253); // calls another function } return v0; // returns the results }


So really, the constructor sets up the actor to be able to function, and it also "preserves" memory for it to work. It also has a pointer to the vtable, which contains all of the methods that the class uses. This will also lead us into inheritance and methods. If you see what I see, double click on "JumpingCheepCheep__vtable". You should get something like this:

ov50:02228F24 JumpingCheepCheep__vtable DCD JumpingCheepCheep__onCreate ov50:02228F24 ; DATA XREF: JumpingCheepCheep__dtor+8o ov50:02228F24 ; ov50:off_2227818o ... ov50:02228F28 DCD Actor__beforeCreate ov50:02228F2C DCD Actor__afterCreate ov50:02228F30 DCD Base__onDelete ov50:02228F34 DCD Actor__beforeDelete ov50:02228F38 DCD Actor__afterDelete ov50:02228F3C DCD EnemyActor__onExecute ov50:02228F40 DCD EnemyActor__beforeExecute ov50:02228F44 DCD EnemyActor__afterExecute ov50:02228F48 DCD JumpingCheepCheep__onDraw ov50:02228F4C DCD EnemyActor__beforeDraw ov50:02228F50 DCD Actor__afterDraw ov50:02228F54 DCD Base__willBeDeleted ov50:02228F58 DCD Base__moreHeapShit ov50:02228F5C DCD Base__createHeap ov50:02228F60 DCD JumpingCheepCheep__heapCreated ov50:02228F64 DCD JumpingCheepCheep__dtor ov50:02228F68 DCD JumpingCheepCheep__dtorFree ov50:02228F6C DCD Actor__SetXPosition ov50:02228F70 DCD Actor__IncrementXPosition ov50:02228F74 DCD JumpingCheepCheep__executeState0 ov50:02228F78 DCD EnemyActor__isInvisible ov50:02228F7C DCD EnemyActor__executeState1 ov50:02228F80 DCD EnemyActor__executeState2 ov50:02228F84 DCD EnemyActor__executeState3 ov50:02228F88 DCD EnemyActor__executeState4 ov50:02228F8C DCD EnemyActor__executeState5 ov50:02228F90 DCD EnemyActor__executeState6 ov50:02228F94 DCD EnemyActor__executeState7


yep, even more crap to worry about. However, we can walk through it. This table is a table full of methods that the actor uses. For instance, "Actor__SetXPosition" sets the actor's position. This function is a part of the Actor class, so it inherits that method to use in the Jumping Cheep Cheep class. It saves memory! However, there is something called "Method Overriding", where instead of inheriting a method from a previous class (or even many many classes if the inherited class doesn't use their own method either), the current class overwrites it with something else. This can be seen when the entry starts with "JumpingCheepCheep__", which means that the method is overridden. For example, "JumpingCheepCheep__executeState0". So instead of using something like EnemyActor::executeState0(), it uses JumpingCheepCheep::executeState0().

Since we know how method overrides work, we can now look at important methods that most sprites use.

Method NamePurpose
beforeCreateThis method is executed before the actor is created onto the current scene.
afterCreateThis method is executed after the actor has been created.
beforeDeleteThis method is executed before the actor is deleted.
onDeleteThis method is executed once the actor has been deleted.
afterDeleteThis method is executed after the actor has been deleted.
beforeExecuteThis method is executed before the actor is executed.
onExecuteThis method is executed once the actor has been executed.
afterExecuteThis method is executed after the actor has been executed.
beforeDrawThis method is executed before the actor is drawn onto the current scene.
onDrawThis method is executed once the actor has been drawn.
afterDrawThis method is executed after the actor has been drawn.

Something common would be to click on a function and then see this:

ov0:020A039C EnemyActor__executeState1 ; DATA XREF: ov0:EnemyActor__vtableo ov0:020A039C ; ov10:Class2123dc4__vtableo ... ov0:020A039C MOV R0, #1 ov0:020A03A0 BX LR ov0:020A03A0 ; End of function EnemyActor__executeState1


this pretty much means:

int EnemyActor::executeState1() { return 1; }


which pretty much does nothing. I'm not sure what returns 2 and 3 do, but I think one of them deletes the actor. If not, then whoops, I overlooked something. But that's how NSMBU does it.

That's all I can really provide at the moment. I'll go deeper later, but this is my attempt at partially explaining it.
Posted on 02-10-17, 07:43 am


Karma: 19752
Posts: 918/1100
Since: 04-02-13
Some notes;
dword v0 = (dword)allocFromGameHeap(1224); // allocates from the heap to get memory if (v0) // successfully allocated enough memory


Perhaps it would be so, except that allocFromGameHeap, or, more specifically, allocFromHeap2, jumps to OS_Panic() when it fails to allocate memory. So that defeats that.

For instance, "Actor__SetXPosition" sets the actor's position.


It would, but it doesn't. This function is NEVER run and I can only assume is leftover from zee beta.

Posted on 02-12-17, 06:11 pm
Mole
Normal user

Karma: 2013
Posts: 256/359
Since: 07-08-12
Posted by skawo
Some notes;
dword v0 = (dword)allocFromGameHeap(1224); // allocates from the heap to get memory if (v0) // successfully allocated enough memory


Perhaps it would be so, except that allocFromGameHeap, or, more specifically, allocFromHeap2, jumps to OS_Panic() when it fails to allocate memory. So that defeats that.


wow, that is pathetic.
Posted on 03-07-17, 10:09 am, deleted by shibboleet: please don't
Pages: 1