SM64 Hacking - Wie in C Coden?

geschrieben am 11.10.2014 15:05:31
( Link )
Jo, habe vor einer Weile das herausgefunden, dass man in SM64 in C coden kann und dann entsprechende Hacks dafür erstellen kann. Zunächst einmal müsste ich dann aber klären was "Behaviors" und "Behavior Scripts" sind. Also, im game hat jedes Objekt wie z.B der pinke Bob-Omb in Bob-Omb Battlefield eine Model ID (also der "pinke Bob-Omb" an sich) und ein Behavior(Behavior = Verhalten -> Was er machen soll). In dem Behavior werden Animation, Aktionen und Kollision geregelt. Nun hatten aber die Entwickler von SM64 höchstwahrscheinlich einen Skript Interpreter geschrieben, mit dem sie schnell und einfach Behaviors laden konnten. Das sind "Behavior Scripts". Allerdings haben wir natürlich nicht mehr diese in "Sprachform" sondern in Hex-Format. Dennoch wird man merken, dass die Hexformate in der Behaviorliste allgemein gültig sind. Ich hole jetzt mal den Pink-Bob Omb als Beispiel aus der Liste von VL-Tone:

21CFDC/0031DC 00 04 00 00 <- 0x00 markiert meist Start. Bei "Solid Objects" muss 00 04 auf 00 09 gesetzt werden.
21CFE0/0031E0 11 01 24 49 <- Ist ein OR command.
21CFE4/0031E4 27 26 00 00 08 02 39 6C <- Ist die Animation, Animation ist auf adresse 0x0802396C
21CFEC/0031EC 2F 00 00 00 00 80 00 00 <- Interaction, in dem Falle: Solid.
21CFF4/0031F4 1E 00 00 00 <- "DROP TO GROUND" setzt das Objekt genau auf den Boden.
21CFF8/0031F8 23 00 00 00 00 64 00 3C <- Das ist eine sogenannte "Collision Sphere" heißt also, ne kugelartige Collision. X = 64 und Y = 3C.
21D000/003200 28 00 00 00 <- Animiert Objekt (muss mit 0x27 zusammen gemacht werden)
21D004/003204 10 1D 00 00 <- Ein parameter, in dem Falle für das Behavior.
21D008/003208 2D 00 00 00 <- Deklariert Ursprungsposition.
21D00C/00320C 0C 00 00 00 80 2E 76 AC <- Ist eine Funktion, die sich auf 0x802E76AC (RAM offset) befindet. Hier werden die Aktionen vom Pink Bob-Omb geladen. Also das eigentliche "Behavior"
21D014/003214 08 00 00 00 <- 0x08 bedeutet immer: Anfang eines Loopes. Alles was nun folgt wird "IMMER" ausgeführt.
21D018/003218 10 05 00 00 <- Ausnahmefall -> Für 0x2F command.
21D01C/00321C 0C 00 00 00 80 2E 7C 4C <- Nochmal eine Funktion.
21D024/003224 09 00 00 00 <- Loop Ende.

Man sieht also, das sind Behavior scripts. Die Syntax zu verstehen ist recht einfach. Wir basteln mal schnell ein eigenes Behavior, wir machen es auch nicht überaus kompliziert wie oben. Ich benutze gerne für NPCs, die ich in Zukunft machen will immer unused Behaviors, also Behaviors, die im Game nie benutzt wurden sind und bei Verwendung auch so gut wie keine Funktion bieten können.

Dieses Hier:
ROM Addr: 0021ACD0 Hex Behav: 13000ED0
>>>>>>>>>>Unused Behavior?
21ACD0/000ED0 00 04 00 00
21ACD4/000ED4 11 01 00 01
21ACD8/000ED8 30 00 00 00 00 1E FE 70 FF CE 03 E8 03 E8 00 C8 00 00 00 00
21ACEC/000EEC 10 05 00 00
21ACF0/000EF0 23 00 00 00 00 28 00 28
21ACF8/000EF8 08 00 00 00
21ACFC/000EFC 0C 00 00 00 80 2A E8 5C
21AD04/000F04 09 00 00 00

Wir wollen, dass unser NPC eine Collision Sphere besitzt und die Animation von Fly Guy hat (was uns später zwingt, dass Objekt mit Model ID = "Fly Guy" und diesem Behavior zu setzen. Bei anderen Objekten sieht das dann etwas "komisch" aus). Dafür ersetzen wir einfach das riesige 0x30 command mit einem 0x27, 0x28 und 0x2F command:

21ACD0/000ED0 00 04 00 00
21ACD4/000ED4 11 01 00 01
21ACD8/000ED8 27 26 00 00 08 01 1A 64
21ACE0/000EC0 28 00 00 00
21ACE4/000EC4 2F 00 00 00 00 80 00 00
21ACEC/000EEC 10 05 00 00
21ACF0/000EF0 23 00 00 00 00 28 00 28
21ACF8/000EF8 08 00 00 00
21ACFC/000EFC 0C 00 00 00 80 2A E8 5C
21AD04/000F04 09 00 00 00

Nochmal zur Erklärung:
0x27 Command -> Animation.
Syntax:

[27] [26] [00 00] [08] [01 1A 64]

0x27: Animation command
[1] - Befehl
[2]*4+88:= Adresse
object->address:=
(special case: 26 for animation)
[3-4] Ignoriert (Daher immer 00)
[5] Bank Nummer
[6-8] Adresse der Animation in der Bank.

[2F] [00 00 00] [00 80 00 00]

0x2F: Interaction Command (Gibt auch noch kürzeren Command, der genau das Gleiche macht)
[1] - Befehl
[2-4] - Ignoriert
[5-8] - Interaction (in unserem Falle 80 00 00 für "Solid")

[23] [00 00 00] [00 28] [00 28]

0x23: Collision Sphere (Bildet um das Objekt eine "kugelförmige", sphärenartige Kollision)
[1] - Befehl (0x23)
[2-4] - Ignoriert
[5-6] - X und Z Sphäre.
[7-8] - Y Sphäre.

So, unser Behavior ist jetzt Solid und haben diesem jetzt eine Kollision gegeben. Später wenn wir unsere eigene Funktion geschrieben haben, müssen wir auch das 0x0C command ändern.

Lektion 1 : Wie code ich in C für SM64 Hacking?
Zunächst einmal brauchen wir die entsprechenden Compiler. Ich nehme immer MinGW. Also downloadet euch zuerst MinGW und installiert alles was für C nötig ist. Es gibt einen package manager bei dem ihr auswählen könnt, was installiert werden soll. Dort solltet ihr für C alles auswählen. Wenn ihr euch nicht sicher seid, installiert einfach gleich das ganze Ding.
Als Nächstes müsst ihr euch MSYS 1.0.10 runterladen (nicht MSYS 1.0.11, die funktioniert nämlich nicht richtig!) und installieren. Wir brauchen es später um in den Ordner zu springen und unsere Files zu "maken".

Jetzt fehlt noch das sogenannte "N64Toolchain", welches ihr hier runterladen könnt:
https://gzrt.googlecode.com/files/n64tree-win32-v1.0.7z

Darin befinden sich die BINs zum kompilieren in MIPS Binary, etc.
Macht euch einen schönen Ordner, den ihr, wie auch immer, nach eurem Belieben benennt und extrahiert dort euer Toolchain rein.

Nun schaut in den Ordner "include", dort sehen wir die n64.h die die Datentypen wie u8, u16, u32, usw. definiert. Und wir sehen auch, dass die einzelnen Knöpfe des controllers auch Begriffe haben. Aber wir haben immer noch keine SM64 Header, die Funktionen des Spiels enthält.

Ich habe mir die Header schon viel weiter aufgebaut, deshalb poste ich sie hier. Geht hin und erstellt in include eine "explode.h" Datei und fügt folgendes ein:

Spoiler anzeigen
/* Mario 64 header file rev1 by messiaen.
**
** The information contained in this file is a compilation from many sources,
** especially Cellar Dweller's, Nagra's, Yoshielectron's notes along with
** original research by me. This is to be used with a C compiler targetting MIPS.
**
** You can a find tutorial on setting up a N64 MIPS-GCC Toolchain on Windows
** at http://code.google.com/p/gzrt/wiki/Nint ... chainSetup (ZZT32 we lurve you)
**
** If you are using an *nix environment, check this Wiki for info about the
** appropriate MIPS-R4300i binutils:
**
** http://en.wikibooks.org/wiki/N64_Programming
** 0C 10 14 00 81 CE 0E 94 24 01 00 03 10 00 00 40 00 00 00 00
829D8
** You can contact me at the Jul SM64 Hacking forum @ http://jul.rustedlogic.net/
*/

//#include "math.h"

/* Constants - struct pointers */
#define M64_CURR_OBJ_PTR 0x80361160 /* Pointer to object being currectly processed */
#define M64_FIRST_OBJ_STRUCT 0x8033D488 /* Pointer to the first object (out of 240) in the circular linked list */
#define M64_MARIO_STRUCT 0x8033B170 /* you can read it from 0x8032d93c */
#define M64_MARIO_OBJ_PTR 0x80361158 /* Pointer to Mario OBJ struct in RAM */
#define M64_LEVEL_STRUCT 0x8033B90c
#define M64_OS_MSG_QUEUE 0x8033AF78 /* OS Message Queue pointer */
#define M64_4X4_MATRIX_PTR 0x80220CC0 /* Pointer to 4x4 matrix resulting from calculation */

/* Constants - misc pointers */
#define M64_DISPLAY_STATS_FLAGS 0x8032b26a
#define M64_SEGMENT_TABLE 0x8033b400
#define M64_CURRENT_LEVEL_ID 0x8032ddf8 /* u16 */
#define M64_GEO_LAYOUT_PTR_TABLE 0x8032ddc4 /* Pointer to pointer */
#define DEBUG_FLAG1 0x8032d598

/* graph flags */
#define BILLBOARD 4
#define INVISIBLE 0 /* recheck */
#define VISIBLE 1 /* recheck */

//library function:

extern int sprintf ( char * str, const char * format, ... );

/* Functions */
extern int CreateMessageBox(u16 flags, u16 rotate_to_mario, u16 type_of_dialog, u16 message_id);
/* CreateMessageBox */
/* return value = 0x00 -> dialog is happening */
/* 0x01 -> dialog is over (choice #1) */
/* 0x02 -> dialog is over (choice #2) */
/* 0x03 -> normal dialog is over */
/* type of dialog = 0xA1 -> save related (wing blocks?) */
/* 0xA2 -> regular dialog */
/* 0xA3 -> two choices */
/* You may want to set an wrapper function for */
/* CreateMessageBox (check yoshi.c for an example) */

extern int CreateStar(float x, float y, float z); /* returns pointer for spawned object */
extern void CopyObjParams(u32 *dest, u32 *source); /* copies X,Y,Z + rotation from another object */
extern void CopyObjPosition(u32 *dest, u32 *source);
extern void CopyObjRotation(u32 *dest, u32 *source);
extern void CopyObjScaling(u32 *dest, u32 *source);
extern int DeactivateObject(u32 obj_pointer); /* kills current object */
extern float DistanceFromObject(u32 object1, u32 object2); /* usually object 1 = (*Obj) and object 2 = Mario */
extern void DmaCopy(u32 dst, u32 bottom, u32 top);
extern void Copy32BitTrip(u32 dst, u32 src);
extern void ExplodeObject(u32 obj_ptr);
extern void PlaySound(u32 argument);
extern void HideObject(); /* hides current object by ORing 0x01 at offset 0x02 */
extern void UnHideObject(); /* ORs 0x10*/
extern int RotateTorwardsMario(int current_rotation, int rotation_speed, int arg2);
extern void ScaleObject(float global_scaling_factor);
extern void ScaleXYZ(u32 obj_pointer, float x, float y, float z);
extern void SetModel(u16 model_ID); /* change how the object looks */
extern int SetObjAnimation(u16 animation_index);
extern int ShakeScreen(u16 argument); /* argument = 1 to 4 (?) */
extern int SpawnObj(u32 obj_pointer, u16 model_id, u32 behavior); /* returns pointer for spawned object */
extern int SpawnObjAdv(u32 obj_pointer, float x, float y, float z, u32 obj_parentptr, u16 model_id, u32 behavior);
extern int CheckObjBehavior(u32 behavior_segmented_pointer); /* return 1 if behavior == arg, else 0 */
extern int CheckObjBehavior2(u32 obj_pointer, u32 behavior_segmented_pointer); /* return 1 if behavior == arg, else 0 */
extern void SetObjBehavior(u32 obj_pointer, u32 behavior_segmented_pointer); /* 0x802a14c4 */
extern int IsMarioStepping(); /* returns 1 if Mario is on TOP of a solid object, else 0 */
extern void ProcessCollision(); /* 0x803938cc, usually called from behaviors */
extern int SetMarioAction(u32 mario_struct_pointer, u32 action, u32 unk_arg); /* to Do: check return values */


extern int ProcessGeoLayout(u32 *dest, u32 segmented_address); /* 0x8037e0b4 */
extern void guFrustum(u32 m, float l, float r, float b, float t, float n, float f, float scale); /* Creates a frustum (perspective projection) projection matrix (fixed point) */
extern void guPerspective(u32 m, u16 perspNorm, float fovy, float aspect, float near, float far, float scale);


/* Music Related */
extern int SetMusic(u32 layer, u16 song_index, u32 a2); /* possible layers = 0 (main bgmusic), 1 (other musics) or 2 (sfx) */
extern int SetInstrument(u32 *chan_ptr, u8 instrument_index);

/* these functions need to be tested further */
extern void CreateTextBox(u16 msg_ID);
extern int PrintText(u32 x_pos, char *text, u32 fade); /* used in Credits. a2 = a float value ? */
extern void PrintRegularText(u32 x, u32 y, char *table_text_pointer); /* needs to be tested */
extern int StopMario(u16 arg); /* 1 = stop mario 2 = do nothing??

/* print functions */
extern void PrintInt (u16 x, u16 y, char* text, u32 value);
extern void PrintStr(u16 x, u16 y, char* text);
extern void PrintRegularText(u32 x, u32 y, char *table_text_pointer); /* needs to be tested more, used at credits?*/
extern void PrintXY(u16 x, u16 y, char* text);

/* memory functions */
extern int SegmentedToVirtual(u32 segmented_pointer); /* returns RAM pointer of a segmented address */
extern int GetSegmentBase(int segment);
extern int SetSegmentBase(int segment, void *base); /* sets segment pointer table */
extern u8 *DynamicIndexCopy(u32 index, u32 begin, u32 end, u32 what);
//extern void DecompressMIO0(void *src, void *dst);

/* N64 OS Functions */
extern int osEepromRead(u32 mg, u8 address, u8 *buffer); // ret = 0 -> normal termination, ret = -1 address out of range. Reads data from EEPROM.
extern int osEepromLongRead(u32 mg, u8 address, u8 *buffer, int nbytes);
extern int osEepromWrite(u32 mg, u8 address, u8 *buffer); // ret = 0 -> normal termination, ret = -1 address out of range. Writes data to EEPROM.
extern int osEepromLongWrite(u32 mg, u8 address, u8 *buffer, int nbytes);
extern int osEepromProbe(u32 mq); // 0x00 = no EEPROM loaded. 0x01 = EEPROM_TYPE_4K. 0x02 = EEPROM_TYPE_16K.

// EEPROM Checksum: 0x802792C0. BEQ R0, R0, 0x80279BB4 (80279BAC)

/* math stuff */
extern float sqrtf(float x);
extern float sinf(float x);
extern float cosf(float x);

/* =============================== */

typedef struct anim /* unfinishied */
{
u16 framecount; /* 0x08 */
u32 pointer; /* 0x0c */
u32 pointer2; /* 0x10 */
} Animation;

typedef struct anim2
{
u32 *AnimationDMATable; // 0xd1 items, each item 8 bytes in lenght
u32 Current_DMA; // not sure
u32 TargetAnimationPtr; // 0x80060030 - gets copied to MarioObj->animation (that's where's animation data is DMAed)
u32 padding;
} MarioAnimation;

extern int SetMarioAnimation(MarioAnimation *AnimStruct, u16 index); /* returns 1 if animation has changed, 0 if its the same as before) */



typedef struct Music2
{
u32 _0x00;
u32 _0x04;
u32 _0x08;
u32 _0x0c;
u32 _0x10;
u32 _0x14;
u32 _0x18;
u32 _0x1c;
f32 volume; /* 1 = 0x7f - 0x20 */
f32 _0x024_maybe_pan;
f32 _0x028;
f32 pitch_transposition;
u32 _0x30;
u32 _0x34;
u32 _0x38;
u32 instrument; /* pointer to instrument */
/* stuff missing here */
u32 Vibrato; /* 0x70 */

} ChannelStruct;

typedef struct Music1
{
u32 _0x00;
u32 _0x04;
u16 _0x08;
u16 tempo; /* 0x0a */
u32 _0x0c;
u32 _0x10;
u32 pointer_seq_head;
f32 volume;
u32 _0x1c;
f32 _0x20;
f32 _0x24;
u32 _0x28;
ChannelStruct *Channel[15];
u32 _0x7c_sequence_pointer;
} MusicController; /* Layer 0 = 0x80222618 */

typedef struct collision_triangle
{
u16 collision_type; /* check collision.txt */
u16 _0x02;
u8 flag;
u8 _0x05;
s16 ymin;
s16 ymax;
s16 vertex1_x, vertex1_y, vertex1_z; /* 0x0a */
s16 vertex2_x, vertex2_y, vertex2_z; /* 0x10 */
s16 vertex3_x, vertex3_y, vertex3_z; /* 0x16 */
float normal_x;
float normal_y;
float normal_z;
float negdot;
u32 _0x2c; /* unused? */
} CollisionTriangle;

typedef struct pad_struct /* from nagra */
{
s16 stick_x;
s16 stick_y;
float x;
float y;
float z;
u16 currentButton; /* 0x10 */
u16 previousButton;
u32 *statusData; /* 0x14 */
u32 *controllerData; /* 0x18 */
} Pad;

typedef struct camera_struct /* mario->camera (0x8033C520) */
{
u32 mario_action; /* copied from mario->action */
float x; /* also copied from Mario struct */
float y;
float z;
s16 _0x10_mario_0x2c; /* 0x10 */
u16 rotation; /* again copied from Mario struct */
s16 _0x14_mario_0x30;
s16 _0x16_mario_0x32;
u32 _0x18;
u16 _0x1c;
u16 camera_setting; /* 0x06 = door opening 0x09 = triggers initial peach animation */

/* incomplete, many other members left ?? */
} Camera;

typedef struct level_struct /* 0x8033B90c, from Cellar Dweller's notes */
{
s16 _0x00;
s16 terrain_type;
u32 geo_layout_ptr;
u32 collision_ptr;
u32 _0x0c;
u32 mini_objects_ptr; /* 0x10 pointer to an array of objects defined by command 0x39 */
u32 warp_links_head;
u32 _0x18;
u32 _0x1c;
u32 *objects_head; /* 0x24 objects linked list head */
u32 LevelCameraPointer; /* Level Camera Pointer (generated at run-time) */
u32 _0x28;
u32 _0x2c;
u32 _0x30;
u8 _0x34; /* set by level command 0x30 */
u8 _0x35;
s16 music_param; /* 0x36 */
s16 music_param2; /* 0x38 title screen,etc) */
/* more? */
} Level;

typedef struct object_struct /* Regular objects, Mario also has its own struct like this */
{
u16 graph_node_type; /* 0x00 */
u16 graph_flags;
struct object_struct *prev; /* previous linked list object */
struct object_struct *next; /* next linked list object */
u32 graph_parent;
u32 graph_child; /* 0x10 */
u32 geo_layout_ptr; /* 0x14 */
u32 _0x18;
u32 _0x1c;
float _0x20; /* 0x20 */
float _0x24;
float _0x28;
float x_scaling; /* 0x2c */
float y_scaling; /* 0x30 */
float z_scaling;
u16 _0x38;
u16 _0x3a;
u32 animation; /* 0x3c - current animation */
u16 anim_current_frame; /* 0x40 */
u16 anim_timer; /* timer, animation related? */
u16 anim_current_frame_copy;
u16 _0x46;
u32 _0x48;
u32 _0x4c;
u32 matrix_ptr; /* 0x50 */
float float_0x54;
float float_0x58;
float float_0x5c;
struct object_struct *next_object_ptr; /* 0x60: re-check this */
u32 _0x64;
struct object_struct *next_object_ptr2; /* 0x68: re-check this (child_obj) */
u32 _0x6c;
u32 _0x70; /* 0x70 */
u16 active; /* 0x0000 = inactive, 0x0101 = active */
u16 _0x76; /* collision flag according to YE */
struct object_struct *collided_obj_ptr; /* according to YE, pointer to object collided with */
u32 _0x7c;
u32 _0x80; /* 0x80 */
u32 _0x84;
u32 _0x88;
u32 obj_flags;
u32 _0x90; /* 0x90 */
u32 _0x94;
u32 _0x98;
u32 _0x9c;
float x_pos; /* 0xa0 */
float y_pos;
float z_pos;
float x_speed; /* x increment? */
float y_speed; /* 0xb0 */
float z_speed; /* z_increment? */
float speed;
u32 _0xbc;
u32 _0xc0; /* 0xc0 */
u32 x_rotation; /* 0xc4 - rotation triplet */
u32 y_rotation; /* 0xc8 */
u32 z_rotation;
u32 x_rotation2; /* rotation copy (collision?) 0xd0 */
u32 y_rotation2; /* 0xd4 */
u32 z_rotation2;
u32 _0xd8;
u32 _0xe0; /* 0xe0 */
float _0xe4; /* gravity related? y_speed - 0xe4 ? */
u32 _0xe8;
u32 _0xec;
u32 _0xf0; /* 0xf0 */
u32 _0xf4; /* obj type for some behaviors (ie, ice bully), for AMPS, radius of rotation */
u32 _0xf8;
u32 _0xfc;
u32 _0x100; /* 0x100 */
u32 _0x104;
u32 _0x108;
u32 _0x10c;
u32 _0x110; /* 0x110 */
u32 _0x114;
u32 _0x118;
u32 _0x11c;
u32 animation_ptr; /* 0x120 = (set by 0x27 26 behavior command) entry for animation? */
u32 _0x124; /* in some behaviors, action related? */
float _0x128;
float _0x12c;
u32 interaction; /* 0x130
00 = Something Solid. Can't grab. Mario walks around, Can jump over.
01 = Crashed when jumping at it, Used by Hoot.
02 = Grabbing
04 = Going through door
08 = Knocks mario back and dissappears. No damage.
10 = Something Solid, Can't grab, Mario walks around, Can't jump over, Seems somewhat thin..
40 = Climbing
*/
u32 _0x134;
u32 _0x138;
u32 _0x13c;
u32 _0x140; /* 0x140 */
u32 behav_param; /* behav param */
u32 _0x148;
u32 action;
u32 _0x150; /* 0x150 = also reset when action changes */
u32 timer; /* always incremented. When action changes, it's set to 0 */
float _0x158;
float distance_from_mario;
u32 _0x160; /* 0x160 */
float _0x164_x;
float _0x168_y;
float _0x16c_z;
float _0x170; /* 0x170 */
float _0x174;
u32 _0x178;
u32 transparency;
u32 damage_to_mario; /* According to YE, "How many segments of damage to do to Mario for objects that cause him harm" */
u32 health; /* Health (ie, for King bob-omb and whomp */
u32 behav_param2; /* re-check */
u32 previous_action; /* used to reset the 0x154 timer */
u32 _0x190; /* 0x190 */
float collision_distance; /* NOTE: if collision_distance < disappear_distance then disappear_distance = collision_distance */
u32 _0x198;
float drawing_distance;
u32 _0x1a0; /* 0x1a0 */
u32 _0x1a4;
u32 _0x1a8;
u32 _0x1ac;
u32 _0x1b0; /* 0x1b0 */
u32 _0x1b4;
u32 _0x1b8;
u32 _0x1bc;
u32 _0x1c0; /* 0x1c0 */
u32 _0x1c4;
u32 _0x1c8;
u32 script_ptr;
u32 stack_index; /* 0x1d0 */
u32 stack;
u32 _0x1d8;
u32 _0x1dc;
u32 _0x1e0; /* 0x1e0 */
u32 _0x1e4;
u32 _0x1e8;
u32 _0x1ec;
u32 _0x1f0; /* 0x1f0 */
u16 _0x1f4;
u16 _0x1f6;
float col_sphere_x;
float col_sphere_y;
float _0x200; /* 0x200 */
float _0x204;
float _0x208;
u32 behavior_script_entry;
u32 _0x210; /* 0x210 */
u32 collide_obj_ptr; /* pointer to another object (collision happening)?.
Can be used to detect if Mario is on top of the object by comparing
value with Mario's pointer */
u32 collision_ptr; /* set by behavior script (0x2A command) */
u32 _0x21c;
u32 _0x220; /* 0x220 */
u32 _0x224;
u32 _0x228;
u32 _0x22c;
u32 _0x230; /* 0x230 */
u32 _0x234;
u32 _0x238;
u32 _0x23c;
u32 _0x240; /* 0x240 */
u32 _0x244;
u32 _0x248;
u32 _0x24c;
u32 _0x250; /* 0x250 */
u32 _0x254;
u32 _0x258;
u32 behav_param_copy_ptr;
} Object;

typedef struct mario_struct /* 8033b170 */
{
u32 status;
u32 flags; /* cap & other flags */
u32 _0x08;
u32 action; /* see Romanian Girl list */
u32 previous_action; /* 0x10 */
u32 _0x14;
u16 _0x18;
u16 _0x1a;
u32 _0x1c;
float _0x20; /* 0x20 */
u16 _0x24; /* rotation related, if bit 1 of status is set, 0x24 is copied to 0x2e */
s16 hitstun; /* hitstun counter (how long Mario stays invencible after getting hit */
u32 _0x28;
s16 _0x2c;
u16 rotation; /* divide it by 180 to get the angle? */
s16 _0x30; /* 0x30 */
s16 _0x32;
u32 _0x34;
u32 _0x38;
float x_pos; /* 0x3c */
float y_pos; /* 0x40 */
float z_pos;
float x_speed;
float y_speed;
float z_speed; /* 0x50. The next four floats are related to speed/acelleration */
float speed;
float _0x58;
float _0x5c;
u32 _0x60; /* 0x60 */
u32 _0x64;
CollisionTriangle *curr_collision_triangle; /* current triangle mario is stepping in */
float _0x6c;
float ground_y; /* 0x70 - ground Y */
u32 _0x74;
u32 _0x78;
u32 _0x7c;
u32 _0x80; /* 0x80 */
u32 _0x84;
Object *MarioObj;
u32 _0x8c_ptr;
u32 Mario_level_command; /* 0x90 = 8033b4b0 = Information read from the Level command that sets Mario*/
Camera *camera;
u32 _0x98_ptr; /* 0x8033B3B0 */
Pad *pad; /* pointer to controller struct controller 1 = 8033AF90 controller2 = 8033AFAC*/
MarioAnimation *MarioAnimationStruct; /* 0x8033B080 */
u32 _0xa4;
s16 coins; /* 0xa8 */
s16 stars; /* 0xaa */
s16 lifes; /* 0xac */
s16 power; /* 0xae */
u16 constant_ground_distance; /* usually 0xBD */
u16 misc_timer; /* on any value other than zero it will decrease until zero (also, drains mario energy?) */
u32 cap_timer;
u32 _0xb8;
float _0xbc;
float _0xc0; /* 0xc0 */
} MarioStruct;


Darin sind alle Funktionsprototypen enthalten, die wir später mit dem Linker an die entsprechenden Adressen des Spiels "verknüpfen". Auch sehen wir dort Structs, wie "MarioStruct" oder "ObjectStruct". Auch diese können wir später im Code ansprechen wie Mario->Coins, wodurch wir auf Mario's derzeitige Münzenanzahl zugreifen können.

Ich stelle euch dann auch gleich noch die levels.h und animations.h zur Verfügung (auch in include reinmachen):

levels.h:
Spoiler anzeigen
#define HAUNTED_HOUSE 0x0004
#define COOL_COOL_MOUNTAIN 0x0005
#define INSIDE_CASTLE 0x0006
#define HAZY_MAZE 0x0007
#define SHIFTING_SAND_LAND 0x0008
#define BOB_OMB_BATTLEFIELD 0x0009
#define SNOWMANS_LAND 0x000A
#define WET_DRY_WORLD 0x000B
#define JOLLY_ROGER_BAY 0x000C
#define TINY_HUGE_ISLAND 0x000D
#define TICK_TOCK_CLOCK 0x000E
#define RAINBOW_RIDE 0x000F
#define CASTLE_GROUNDS 0x0010
#define BOWSER_FIRST_COURSE 0x0011
#define VANISH_CAP 0x0012
#define BOWSERS_FIRE_SEA 0x0013
#define SECRET_AQUARIUM 0x0014
#define BOWSER_THIRD_COURSE 0x0015
#define LETHAL_LAVA_LAND 0x0016
#define DIRE_DIRE_DOCKS 0x0017
#define WHOMPS_FORTRESS 0x0018
#define PICTURE_AT_END 0x0019
#define CASTLE_COURTYARD 0x001A
#define PEACH_SECRET_SLIDE 0x001B
#define METAL_CAP_COURSE 0x001C
#define WING_CAP 0x001D
#define BOWSER_FIRST_BATTLE 0x001E
#define RAINBOW_CLOUDS 0x001F
#define BOWSER_SECOND_BATTLE 0x0021
#define BOWSER_THIRD_BATTLE 0x0022
#define TALL_TALL_MOUNTAIN 0x0024


und animations.h:
Spoiler anzeigen
/* Yoshi animation indexes */
#define YOSHI_STANDING 0x00
#define YOSHI_WALKING 0x01
#define YOSHI_JUMPING 0x02

/* Koopa-The-Quick */

#define KOOPA_FALLING 0x00
#define KOOPA_ANIM1 0x01
#define KOOPA_ANIM2 0x02
#define KOOPA_RUNNING_FAST 0x03
#define KOOPA_RUNNING_MODERATELY 0x04
#define KOOPA_ANIM5 0x05
#define KOOPA_GETTING_UP 0x06
#define KOOPA_LOOKING_AROUND 0x07
#define KOOPA_GETTING_UP_AND_STANDING 0x08
#define KOOPA_ANIM9 0x09
#define KOOPA_ANIM10 0x0A
#define KOOPA_ANIM11 0x0B
#define KOOPA_JUMPING_FROM_GROUND 0x0C
#define KOOPA_JUMPING_WHILE_STANDING 0x0D

/* Baby Penguin */
#define PENGUIN_WALKING 0x00
#define PENGUIN_SLIDING 0x01
#define PENGUIN_LOOKING_AROUND 0x02
#define PENGUIN_STAND_UP 0x03
#define PENGUIN_WALKING_2 0x04


Nun gehen wir wieder aus DeinOrdner/include wieder raus und nach DeinOrdner. Nun erstellen wir wieder einen neuen Ordner, der unseren ersten C code enthält. Ich nenne ihn hier: "SM64Test". In diesem Ordner erstellen wir eine neue Datei, diesmal mit der Endung .c. Ich nenne unseren Testcode: "HelloWorld.c" Als Nächstes müssen wir in diesem Ordner den Linker und die Makefile einfügen. Zunächst hier den Linker:

Spoiler anzeigen
/* ========================================================================
*
* n64ld.x
*
* GNU Linker script for building an image that is set up for the N64
* but still has the data factored into sections. It is not directly
* runnable, and it contains the debug info if available. It will need
* a 'loader' to perform the final stage of transformation to produce
* a raw image.
*
* Copyright (c) 1999 Ground Zero Development, All rights reserved.
* Developed by Frank Somers
* Modifications by hcs (halleyscometsoftware@hotmail.com)
*
* $Header: /cvsroot/n64dev/n64dev/lib/alt-libn64/n64ld.x,v 1.2 2006/08/11 15:54:11 halleyscometsw Exp $
*
* ========================================================================
*/

OUTPUT_FORMAT ("elf32-bigmips", "elf32-bigmips", "elf32-littlemips")
OUTPUT_ARCH (mips)
EXTERN (_start)
ENTRY (_start)

SECTIONS {
/* Start address of code is 1K up in uncached, unmapped RAM. We have
* to be at least this far up in order to not interfere with the cart
* boot code which is copying it down from the cart
*/

. = 0x80405000 ;

/* Library Functions */
sprintf = 0x8032255c ;

/* Game Functions */
CreateMessageBox = 0x802A4BE4 ;
CreateTextBox = 0x802d8d08 ;
CreateStar = 0x802F2B88 ;
CheckObjBehavior = 0x802a14fc;
CheckObjBehavior2 = 0x802A1554;
SetObjBehavior = 0x802a14c4;
CopyObjParams = 0x8029F0E0 ;
CopyObjPosition = 0x8029F120 ;
CopyObjRotation = 0x8029F148 ;
CopyObjScaling = 0x8029F3A4 ;
DistanceFromObject = 0x8029e27c ;
DeactivateObject = 0x802a0568 ;
DmaCopy = 0x80278504 ;
Copy32BitTrip = 0x80378800 ;
ExplodeObject = 0x802e6af8 ;
GetSegmentBase = 0x80277f20 ;
HideObject = 0x8029F620 ;
UnHideObject = 0x8029f6bc ;
PlaySound = 0x802ca190;
PrintInt = 0x802d62d8 ;
PrintXY = 0x802d66c0 ;
RotateTorwardsMario = 0x8029e530 ;
ScaleObject = 0x8029F430;
ScaleXYZ = 0x8029F3D0;
SegmentedToVirtual = 0x80277f50;
SetModel = 0x802a04c0;
SetSegmentBase = 0x80277ee0 ;
SetObjAnimation = 0x8029f464;
ShakeScreen = 0x802a50fc ;
SpawnObj = 0x8029edcc ;
SpawnObjAdv = 0x8029EF64 ;

SetMarioAction = 0x80252cf4;
SetMarioAnimation = 0x80279084;

IsMarioStepping = 0x802A3CFC ;

DynamicIndexCopy = 0x8027868c ;

ProcessGeoLayout = 0x8037e0b4;


MoveRelated = 0x802a2320 ;

PreMoveObj = 0x802a1308;
MoveObj = 0x802A2348 ;


MoveObj2 = 0x802A2644 ;
UnknownMove = 0x8029E714 ;

_8037a9a8 = 0x8037a9a8;


StopMario = 0x8028bd34 ;
PrintText = 0x802d8844 ;
PrintStr = 0x802d6554 ;
PrintRegularText = 0x802d7e88;

sinf = 0x80325480;
cosf = 0x80325310;
sqrtf = 0x80323a50;

ProcessCollision = 0x803839cc;

/* Music Related */
SetMusic = 0x80320544;
SetInstrument = 0x8031cfd4;

/* OS Functions */
osEepromRead = 0x80329150 ;
osEepromWrite = 0x80328AF0 ;
osEepromLongRead = 0x80324690 ;
osEepromLongWrite = 0x803247D0 ;
osEepromProbe = 0x80324080 ;

guFrustum = 0x80324D74 ;
guPerspective = 0x80325010 ;

//DecompressMIO0 = 0x8027F4E0 ;

/* The text section carries the app code and its relocation addr is
* the first byte of the cart domain in cached, unmapped memory
*/

.text : {
FILL (0)

__text_start = . ;
*(.init)
*(.text)
*(.ctors)
*(.dtors)
*(.rodata)
*(.fini)
__text_end = . ;
}


/* Data section has relocation address at start of RAM in cached,
* unmapped memory, but is loaded just at the end of the text segment,
* and must be copied to the correct location at startup
*/

.data : {
/* Gather all initialised data together. The memory layout
* will place the global initialised data at the lowest addrs.
* The lit8, lit4, sdata and sbss sections have to be placed
* together in that order from low to high addrs with the _gp symbol
* positioned (aligned) at the start of the sdata section.
* We then finish off with the standard bss section
*/

FILL (0xaa)

__data_start = . ;
*(.data)
*(.lit8)
*(.lit4) ;
/* _gp = ALIGN(16) + 0x7ff0 ;*/
/* _gp = . + 0x7ff0; */
. = ALIGN(16);
_gp = . ;
*(.sdata)
__data_end = . ;
/*
__bss_start = . ;
*(.scommon)
*(.sbss)
*(COMMON)
*(.bss)
/* XXX Force 8-byte end alignment and update startup code */

__bss_end = . ;
*/
}

.bss (NOLOAD) : {
__bss_start = . ;
*(.scommon)
*(.sbss)
*(COMMON)
*(.bss)
__bss_end = . ;
end = . ;
}

}


Im Linker sieht man die selben Funktionen aus explode.h, nur das sie dieses Mal Adressen zugewiesen werden, also den Adressen des Spiels. Denn diese Funktionen existieren im Spiel und so können wir sie verknüpfen. Wir brauchen also nur die richtigen Funktionsprototypen zu schreiben (mit den richtigen parametern und den richtigen Rückgabewert (falls es einen gibt)) und diese müssen wir dann über den Linker mit der entsprechenden Adresse verknüpfen. Im Output wird dann einfach über JAL (was Jump and Link bedeutet, und generell benutzt wird um auf Subroutinen zu springen) geregelt. Wenn die Parameter stimmen, dann werden die Argumentregister (bei MIPS ASM) A0-A3 (oder falls mehr vorhanden, dann über den Stack) mit den richtigen Längen geladen. Nun zu dem:

Code
OUTPUT_FORMAT ("elf32-bigmips", "elf32-bigmips", "elf32-littlemips")
OUTPUT_ARCH (mips)
EXTERN (_start)
ENTRY (_start)


OUTPUT_FORMAT und OUTPUT_ARCH sind einfach nur da um auszusagen, dass wir unseren Code in MIPS Binary kompilieren. Bei EXTERN(_start) und ENTRY(_start) wird ein entsprechendes "Main" festgelegt. Deshalb müssen wir später in unserem C code unbedingt "_start" schreiben, ansonsten kriegen wir einen Fehler vom Compiler.

Nun folgt zuletzt die Makefile, die wir benötigen, damit unser Code überhaupt kompiliert wird. Wir müssen dem Compiler ja ebenfalls ein paar Commands geben, was er machen soll.

Spoiler anzeigen
#
# Standard N64 Makefile
#

# Name!
PRJNAME = debug
PARTS = $(PRJNAME).o

# Vars
INC_FLAGS = -I. -I../include
LIB_FLAGS = -L. -T mario64.x
ROOT = ..
N64ROOT = ../root
N64PREFIX = mips64-
N64BIN = ${N64ROOT}/bin

# Flags
NOBUILTIN=-fno-builtin
CFLAGS=-std=gnu99 -nodefaultlibs -march=vr4300 -mtune=vr4300 -mabi=32 ${INC_FLAGS} $(NOBUILTIN) -o3
ASFLAGS = -march=vr4300 -mabi=32
LINK_FLAGS=${LIB_FLAGS} ${LIBS}

# Programs
CC = $(N64BIN)/mips64-gcc
LD = $(N64BIN)/mips64-ld
OBJCOPY = $(N64BIN)/mips64-objcopy
CHEAT = $(N64BIN)/nemucheat
BINCODE = $(N64BIN)/bin2code

# Awwright
$(PRJNAME).bin: $(PRJNAME).elf
$(OBJCOPY) $(PRJNAME).elf $(PRJNAME).bin -O binary

$(PRJNAME).elf: $(PARTS)
$(LD) -o $(PRJNAME).elf $(PARTS) $(LINK_FLAGS)

clean:
rm *.o *.elf *~ *.bin final.txt codes.txt -vf


Wichtig ist jetzt, dass wir bei PRJNAME (ganz am Anfang) den Namen ändern auf HelloWorld, da meine C Datei auch "HelloWorld.c" heißt. Ansonsten wird der Compiler die C Datei nicht finden und ebenfalls einen Fehler ausgeben. Ich möchte nicht unbedingt jetzt auf die einzelnen Flags eingehen, da das einige wahrscheinlich nur eher verwirrt. Aber -march=vr4300 bedeutet, dass unser Code in MIPS R4300 Binary kompiliert werden soll und -mabi=32 bedeutet, dass unsere Register (MIPS Register) die Länge von 32-bit haben.

Nun wo wir fertig sind, schreiben wir nun unseren ersten C code.



Zuerst müssen wir immer sicherstellen, dass wir die n64.h und die explode.h inkludieren, denn sie tragen immerhin die vordefinierten Datentypen und die explode.h die entsprechenden Funktionen. Ohne diese beiden Headers, wäre Code schreiben unmöglich. Dann müssen wir, wie ich bereits sagte, darauf achten, dass unsere Funktion _start heißt und sie sollte generell immer void sein. Ihr müsst in die Klammern nichts reinschreiben, (wird automatisch dann als void gewertet) ich habe es aber nur zu Übersichtszwecken gemacht und um Konfusion zu vermeiden.

Dann müssen wir als Nächstes ein inline ASM Befehl reinschreiben, denn wir müssen dafür sorgen, dass die Pointer (des Global Pointers, kurz GP) richtig geladen werden. Ansonsten crasht der Code das game.

Nun schreiben wir unsere eigentliche erste Funktion,
Code
extern void PrintXY(u16 x, u16 y, char* text);
(Funktionsprototyp in explode.h)

Grundsätzlich ist das dritte Argument immer ein Pointer und in ASM hätte man den Text manuell irgendwo eintragen müssen und anschließend die Pointeradresse in A2 laden müssen. In unserem Falle aber regelt der Compiler das ebenfalls selber und schreibt den Text am Ende des Codes und der Compiler trägt die Adresse automatisch ein. Jetzt, nachdem wir unseren Code geschrieben haben, müssen wir ihn nur noch kompilieren. Allerdings müssen wir noch eine winzige Kleinigkeit im Linker regeln, nämlich unseren Entry-Point im RAM von SM64. Im Linker sollte man unter SECTIONS direkt am Anfang folgendes sehen:

Code
. = 0x80405000 ;


Dies ist unser Entry-Point im RAM. Wir lassen es mal so stehen, weil das gerade so passt.

Nun kompilieren wir unseren Code. Dafür öffnen wir jetzt MSYS und machen ein "cd" zu unseren HelloWorld.c Code. Ich habe mein Zeugs immer direkt auf C:\ gespeichert, weil mir das lange cd'en auf die Nerven geht. Der Vorgang sieht dann wie folgt aus:



Kriegt ihr sowas raus, dann sollte alles funktioniert haben. Wenn ihr jetzt nochmal in euren Ordner schaut, seht ihr das auch neue Dateien entstanden sind:


In diesem Falle interessiert uns aber die HelloWorld.bin Datei (nee, ist keine Snes9x ROM), denn da steckt unser MIPS Binary drin. Wir wollen nun unseren C code im Spiel testen, dafür müssen wir aber jetzt die normale SM64 ROM (die 8MB groß ist) extenden auf 24MB und anschließend noch auf 65MB expanden.

Ich denke den Extensionprozess kennt der eine oder andere schon. Man downloade sich von VL-Tones Seite seinen Extender:
http://qubedstudios.rustedlogic.net/M64ROMExtender1.3b.zip

Und extende seine "Super Mario 64 (U) [!].z64" Datei, die 8MB groß sein sollte. Der Output wird dann im selben Ordner sein und "Super Mario 64 (U) [!].ext.z64" heißen. Wenn ihr fertig seid, müsst ihr anschließend die ROM noch expanden auf 65MB. Besser ihr tut es gleich mit Skelux's Level Importer:

http://www.mediafire.com/download/3o83fyel15hkbas/SM64+Level+Importer+1.9.1S.zip

Runterladen, und ROM damit öffnen. Das Tool fragt dann automatisch, ob du die ROM auf 65MB expanden willst. Anschließend kannst du auch dann gleich den PPF Patch patchen lassen, der für den Obj-import notwendig ist. Ist alles fertig, kannst du das Tool wieder schließen. Deine "Super Mario 64 (U) [!].ext.z64" sollte jetzt von 24MB auf 65MB angestiegen sein. Wir haben es geschafft. Um den Code nun ins Game zu patchen, müssen wir erstmal errechnen wo in der ROM sich die entsprechende RAM-Stelle befindet, denn 0x80405000 ist eindeutig eine RAM-Adresse und wir müssen erst errechnen, wo die entsprechende ROM Adresse ist. Die Lösung habe ich bereits:

Man nehme das Präfix "80" weg und man hat 0x405000 übrig. Man addiere 0xE00000 und kriegt 0x1205000 raus. Dort muss unser Code also rein. Öffnet also eure 65MB ROM und springt an das 0x1205000 offset. Ihr solltet dann überall 01 sehen:



Nun müsst ihr eure HelloWorld.bin im HexEditor öffnen und alles von der HelloWorld.bin kopieren.


Jetzt müsst ihr bei der ROM einfach nur an Stelle 0x1205000 alles reinpasten. Allerdings achtet beim (falls jemand den gleichen Hex-Editor benutzt wie ich) HxD Editor, dass ihr NICHT Ctrl+V drückt. Denn dies "pastet" wortwörtlich den .bin rein und vergrößert die ROM bzw. verschiebt dann andere wichtige Stellen. Bei HxD solltet ihr also beim "überschreiben" immer Ctrl+B drücken. Am Schluss sieht das dann so aus:



Euer Code ist nun drin. Speichert ab. ABER! Wir sind noch nicht fertig. Wir müssen jetzt nochmal an unser Behavior (an Adresse: 0x21ACD0) zu unserem 0x0C command, was sich bei Adresse: 0x21ACFC befindet. Dort ändert ihr:

0C 00 00 00 80 2A E8 5C um in: 0C 00 00 00 80 40 50 00

Na, fällt euch was auf? Genau, im behavior command müssen wir die RAM-Adresse eintragen, an dem sich unser Code (während der Laufzeit) befinden wird. Damit der Code natürlich im Game ausgeführt wird, müssen wir erstmal einen Fly Guy mit dem Custom Behavior setzen. Das geht natürlich mit dem allbekannten Toad's Tool 64, den SM64 Level Editor. Auch hier nehmen wir wieder Skelux's Version (er hatte von VL-Tone den Source bekommen und Skelux hat ein bisschen was verbessert): http://www.mediafire.com/download/9712q0cms901nvr/Toad%27s+Tool+64+v0.6.2S.zip Falls jemand einen Mac besitzt, dann das runterladen: http://www.mediafire.com/download/9rdnblj57opbl43/Toad%27s+Tool+64+v0.6.2S+%28Mac%29.zip

Also öffnet euer Toad's Tool 64 und ladet eure ROM. Ich ersetze den Schmetterling jetzt durch "Fly Guy". Falls das Objekt nicht vorhanden sein sollte in dem Level deiner Wahl, nicht schlimm. Dann müsst ihr halt nur ein Objekt wählen und Model ID auf Fly Guy und das entsprechende Behavior setzen. So, ich habe jetzt den Schmetterling in Fly Guy umgewandelt:



Aber (wieder einmal) sind wir noch nicht fertig. Wir müssen jetzt nur noch das Behavior ändern. In der unteren Leiste solltet ihr "Behav" sehen, was für Behavior steht. Dort kommt unser Behavior rein. Da unser custom Behavior nicht in dieser Liste steht, müssen wir es manuell eintippen. Dabei haltet ihr Alt gedrückt und drückt mit der Maus dann in die Behav Textbox rein. Wir tragen nicht 21ACD0 ein, sondern die segmented Adresse, in unseren Falle: "000ED0" solltet ihr keine Hexadezimalansicht haben (es sollte vor jeder Zahl "h" stehen) dann klickt einfach die "Hexadecimal" Checkbox an. Nun Alt+Klicken und dann eintragen:



Jetzt sind wir fertig und speichern ab. Nun öffnet eure ROM in PJ64. Das Resultat sollte dann so aussehen:



Der Code wird, wie gesagt, nur dann ausgeführt wenn das entsprechende Objekt mit dem Custom Behavior geplaced wird. Logisch, das wenn man es doppelt placed, das der code dann auch doppelt ausgeführt wird. Allerdings lässt sich das mit ein paar C Tricks leicht umgehen, bspw. das der Code erst dann ausgeführt wird, wenn man Taste B in der Nähe vom Fly Guy drückt, etc.

Nun wisst ihr, wie man Behaviors zusammenbastelt und C code schreibt und anschließend in die ROM patcht. Natürlich könntet ihr auch euren Code direkt durch Mario's behavior ausführen lassen, allerdings wird der Code (logischerweise) in jedem Level und überall ausgeführt. Daher nutze ich Mario's behavior eher dann, wenn ich was RPG mäßiges mache oder irgendeine Anzeige für Mario mache. Auch sollte man hier Mario's Behavior nicht überschreiben, sondern stattdessen eine Hook in einer bereits vorhandenen Funktion schreiben, sodass Mario's normale Routine ausgeführt wird zusammen mit deinem Code von Adresse 0x80405000. Ich werde auch noch ein paar C tutorials machen, was man nun mit C coden so alles machen kann.