Savestate
Smash Cadet
Hey SmashBoards!
edit: Some rewording as suggested by a friend!
As most of you know, writing any code for melee other than a simple memory replace normally involves coding in assembly, which can be incredibly tedious for more complex functions. I've been thinking of ways to open up another option for people who are more comfortable coding in C, or want to quickly make some less-efficient prototypes for hacks before committing to writing them in assembly.
Given you're running Windows, grab a copy of a gamecube-targeted toolchain from the great guys at devkitPro (automated installer). When installing, you'll want to check the PowerPC toolchain "devkitPPC". If you don't plan on using the other two (I didn't), feel free to uncheck them.
If you're running Linux/OSX, they provide an experimental perl script to put the compiler on your computer. If that doesn't end up working, you may need to build a cross compiler. There's various guides out there to help with that, but I wouldn't be opposed to providing guidance on the issue.
Once the toolchain is installed, you'll need to add the binaries' location to the System Environment Variable "Path". In the Start Menu search for "edit the system environment variables", click "Environment Variables..." button, scrolling down to "Path" in the bottom half of the window, choose "Edit" and add on "C:\devkitPro\devkitPPC\bin" to the end of the large string of text. Make sure there's a semicolon between it and the entry before it. e.g. "...path\directory\location;C:\devkitPro\devkitPPC\bin"
Now open up Command Prompt and type in "powerpc-eabi-gcc". If you see...
...that means you're ready to start programming! If not, make sure you set your Path correctly, and that the binaries are in the folder "C:\devkitPro\devkitPPC\bin" to begin with.
In the subject of programming, I believe it's best to teach with examples, so I'm going to step through the process of writing a code for Melee in C.
First of all, you need to know what you're going to program, so for demo's sake, I'll do something that looks pretty in C, but isn't really all that useful.. Let's write a code that clears out the stale moves table if it's filled up with copies of the same move, and then play the "Menu Forward" sound to indicate that it's been done. Most, if not all, of the info required for this demo can be found on the SSBM v1.2 Datasheet (hosted by Dan Salvato).
: : : : : Step 2. Resources
Alright, so we know what we're gonna do, now let's gather everything we'll need to make it happen. Some functions we'll need are the "Count players in match" function, "Get character data pointer" function, and "Play menu sound: 'Forward'" function. We'll put references to these in the header of our C program as #defines so we can reference them in our code without making it look too confusing.
We'll also need the addresses of the "Stale Moves" table for each player. Now, the stale moves tables are in a fixed place in RAM, spaced equally. Knowing this, we can write down the location of the first one and access the rest by changing the offset by a multiple of the known distance.
Our header will look something like this.
: : : : : Step 3. Additional Preparation
To make accessing the stale moves tables easier, I went ahead and defined some structs that match the format of the tables. This way, I can create new pointers to the structs in the RAM and use them just like how I would any other variable! Neat stuff!
: : : : : Step 4a. Let's Code! (creating pointers to functions in the game)
Ok, so we're ready to start coding! To begin, we'll start with a main function and create new function pointers to the previously defined functions in Melee's RAM.
Side note: Do not call your main function main! Call it _main, calling it main will make the assembler insert a call to
__eabi which breaks things.
In case you're wondering, uint32_t is a way of saying "32-bit unsigned integer". There's also other ones like uint8_t, uint16_t, etc You'll need to add "#include <stdint.h>" to the top of your C file to use 'em.
: : : : : Step 4b. Let's Code! (variable setup)
Now that we have our Melee functions declared, we should set up our Stale Moves tables. We're going to create an array of pointers to each stale-moves-table currently active in the game. First, we need to know how big our array should be! We can figure this out by calling the player_count() function. It returns an integer that indicates how many players are in the current match. Once we have that, we can set our array of pointers to be the size of the number of characters playing.
Once your array of pointers has been initialized, we need to populate it with the pointers to the stale moves tables for each of the currently active players. We'll also need to create an index variable to keep track of which slot we're putting the pointer into.
A neat thing about the pointer_to_character(int) function is that it will return a 0 if the player isn't playing this match. With this in mind, we can make a for loop that goes from zero to four and feeds the loop variable into the pointer_to_character(int) function. If it returns a zero, skip the iteration, if it returns a pointer to a character, we'll use the iteration we're on to determine the character number and multiply the stale table offset by it to get the offset of the stale table for the current character (current in regards to the loop variable). We then put this final calculated offset into the array of pointers to stale tables and increase the current index by one. Rinse and repeat all 4 times.
: : : : : Step 4c. Let's Code! (using our variables)
By this point, we know exactly where all the info we need to access is... we just need to use it now! Given our original plan, we'll need to make a loop that iterates through all the known stale moves tables in our array of pointers to them and check if all of the moves in the stale moves table are the same. If they are, we'll clear the table and play the menu forward sound.
That sounds like a lot to put in a for loop though ... so let's make functions that'll help split the job up!
Essentially, we are going to create functions that this following block of code can reference.
So our two functions will have to follow the format of ...
A moderately easy of implementing all_the_same_move is by saving the first move_id in the table to a local variable and looping through the array of staleness slots, comparing their move_id to the one saved in the beginning. We should also keep track of whether or not the entire slot list is zeros, because if it is, then technically the function would return true even though there's nothing in the list.
clear_table is going to be much easier to implement. We just need to loop through the staleness table slots and set them all to zero (both move_id and action_state_num)
: : : : : Step 5. Compiling your Code to Assembly
Our finished C program:
Time to make some assembly! Go ahead and open up the command prompt and navigate to your code's directory. Run the command powerpc-eabi-gcc -S unstaler.c to generate an assembly file called unstaler.s. To prepare this assembly code for use with ASM <> WiiRD, we need to clean out a few lines of code and add a branch to the _main function.
Remove all the lines of code that begin with a period, except for the loop labels.
They're usually located at the beginning and ends of functions.
Once you finish that, add some code to the beginning of the assembly that backs up r3 to the stack, branches and link to _main, restores r3, and branches to the bottom of the assembly to exit.
Lastly, replace opcode-to-replace with the opcode that's going to be overwritten. To determine that though, we gotta find a good place to inject the code into. After some digging around, I believe I found a function that deals with calculating how much damage a hitbox makes (it's executed once per new hitbox) so we'll just inject it into that function. The address in question is 0x8008923C and the opcode we're replacing is addi r31, r5, 0.
Run your assembly through ASM <> WiiRD to get your Gecko code!
edit: Some rewording as suggested by a friend!
As most of you know, writing any code for melee other than a simple memory replace normally involves coding in assembly, which can be incredibly tedious for more complex functions. I've been thinking of ways to open up another option for people who are more comfortable coding in C, or want to quickly make some less-efficient prototypes for hacks before committing to writing them in assembly.
Given you're running Windows, grab a copy of a gamecube-targeted toolchain from the great guys at devkitPro (automated installer). When installing, you'll want to check the PowerPC toolchain "devkitPPC". If you don't plan on using the other two (I didn't), feel free to uncheck them.
If you're running Linux/OSX, they provide an experimental perl script to put the compiler on your computer. If that doesn't end up working, you may need to build a cross compiler. There's various guides out there to help with that, but I wouldn't be opposed to providing guidance on the issue.
Once the toolchain is installed, you'll need to add the binaries' location to the System Environment Variable "Path". In the Start Menu search for "edit the system environment variables", click "Environment Variables..." button, scrolling down to "Path" in the bottom half of the window, choose "Edit" and add on "C:\devkitPro\devkitPPC\bin" to the end of the large string of text. Make sure there's a semicolon between it and the entry before it. e.g. "...path\directory\location;C:\devkitPro\devkitPPC\bin"
Now open up Command Prompt and type in "powerpc-eabi-gcc". If you see...
Code:
powerpc-eabi-gcc: fatal error: no input files
compilation terminated.
In the subject of programming, I believe it's best to teach with examples, so I'm going to step through the process of writing a code for Melee in C.
______________________________________________________
Writing a Program in C for Super Smash Bros. Melee
______________________________________________________
: : : : : Step 1. The ConceptWriting a Program in C for Super Smash Bros. Melee
______________________________________________________
First of all, you need to know what you're going to program, so for demo's sake, I'll do something that looks pretty in C, but isn't really all that useful.. Let's write a code that clears out the stale moves table if it's filled up with copies of the same move, and then play the "Menu Forward" sound to indicate that it's been done. Most, if not all, of the info required for this demo can be found on the SSBM v1.2 Datasheet (hosted by Dan Salvato).
: : : : : Step 2. Resources
Alright, so we know what we're gonna do, now let's gather everything we'll need to make it happen. Some functions we'll need are the "Count players in match" function, "Get character data pointer" function, and "Play menu sound: 'Forward'" function. We'll put references to these in the header of our C program as #defines so we can reference them in our code without making it look too confusing.
We'll also need the addresses of the "Stale Moves" table for each player. Now, the stale moves tables are in a fixed place in RAM, spaced equally. Knowing this, we can write down the location of the first one and access the rest by changing the offset by a multiple of the known distance.
Our header will look something like this.
Code:
// - Play Sound Effect
#define SSBM_FUNC_PLAYMENUFORWARD 0x80174338
// - Get Pointer to Character Data
#define SSBM_FUNC_CHARPOINTER 0x80034110
// r3 = Player Number
// - Count players in match
#define SSBM_FUNC_COUNTPLAYERS 0x8016B558
// returns r3, number of characters in match
// ==========================
// Staleness Tables
#define SSBM_STALETABLE_P1 0x8045313C
#define SSBM_STALETABLE_DIST 0xE90
#define SSBM_STALETABLE_SIZE 10
To make accessing the stale moves tables easier, I went ahead and defined some structs that match the format of the tables. This way, I can create new pointers to the structs in the RAM and use them just like how I would any other variable! Neat stuff!
Code:
typedef struct SSBM_staleness_slot {
uint16_t move_id; // The action state number
uint16_t action_state_num; // The number of action states
// since the beginning of the match
} SSBM_staleness_slot;
typedef struct SSBM_staleness_table {
uint32_t write_next; // The next slot to write to in the table
SSBM_staleness_slot slots[SSBM_STALETABLE_SIZE]; // The 10 staleness slots
} SSBM_staleness_table;
Ok, so we're ready to start coding! To begin, we'll start with a main function and create new function pointers to the previously defined functions in Melee's RAM.
Side note: Do not call your main function main! Call it _main, calling it main will make the assembler insert a call to
__eabi which breaks things.
Code:
int _main() {
// built in functions setup
void * (*play_menu_forward)() = SSBM_FUNC_PLAYMENUFORWARD;
uint32_t * (*pointer_to_character)(int) = SSBM_FUNC_CHARPOINTER;
uint32_t * (*player_count)() = SSBM_FUNC_COUNTPLAYERS;
// to-do ... !
}
: : : : : Step 4b. Let's Code! (variable setup)
Now that we have our Melee functions declared, we should set up our Stale Moves tables. We're going to create an array of pointers to each stale-moves-table currently active in the game. First, we need to know how big our array should be! We can figure this out by calling the player_count() function. It returns an integer that indicates how many players are in the current match. Once we have that, we can set our array of pointers to be the size of the number of characters playing.
Once your array of pointers has been initialized, we need to populate it with the pointers to the stale moves tables for each of the currently active players. We'll also need to create an index variable to keep track of which slot we're putting the pointer into.
A neat thing about the pointer_to_character(int) function is that it will return a 0 if the player isn't playing this match. With this in mind, we can make a for loop that goes from zero to four and feeds the loop variable into the pointer_to_character(int) function. If it returns a zero, skip the iteration, if it returns a pointer to a character, we'll use the iteration we're on to determine the character number and multiply the stale table offset by it to get the offset of the stale table for the current character (current in regards to the loop variable). We then put this final calculated offset into the array of pointers to stale tables and increase the current index by one. Rinse and repeat all 4 times.
Code:
// populate the array of stale table pointers
int i;
int index = 0;
for (i=0; i<4; i++) {
if (pointer_to_character(i) != 0)
stale_tables[index++] = SSBM_STALETABLE_P1 + (i*SSBM_STALETABLE_DIST);
}
By this point, we know exactly where all the info we need to access is... we just need to use it now! Given our original plan, we'll need to make a loop that iterates through all the known stale moves tables in our array of pointers to them and check if all of the moves in the stale moves table are the same. If they are, we'll clear the table and play the menu forward sound.
That sounds like a lot to put in a for loop though ... so let's make functions that'll help split the job up!
Essentially, we are going to create functions that this following block of code can reference.
Code:
for(i=0; i<num_players; i++) {
// if all slots in a stale table are the same
// move, then clear out the table and menu
// forward noise.
if (all_the_same_move(stale_tables[i])) {
clear_table(stale_tables[i]);
play_menu_forward();
}
}
Code:
int all_the_same_move(SSBM_staleness_table *table)
void clear_table(SSBM_staleness_table *table)
Code:
// returns whether or not a staleness table is filled all with the same move
// 0 = not all the same move (or filled completely with zeros)
// 1 = all the same move
int all_the_same_move(SSBM_staleness_table *table) {
uint16_t action = table->slots[0].move_id;
int i;
int all_zeros = 1;
for (i=0; i<SSBM_STALETABLE_SIZE; i++) {
uint16_t move_in_slot = table->slots[i].move_id;
if (action != move_in_slot)
return 0;
if (move_in_slot != 0)
all_zeros = 0;
}
if (all_zeros == 1)
return 0;
return 1;
}
clear_table is going to be much easier to implement. We just need to loop through the staleness table slots and set them all to zero (both move_id and action_state_num)
Code:
// zeros out a staleness table's slots
void clear_table(SSBM_staleness_table *table) {
int i;
for (i=0; i<SSBM_STALETABLE_SIZE; i++) {
table->slots[i].move_id = 0x0000;
table->slots[i].action_state_num = 0x0000;
}
}
Our finished C program:
wow !
Code:
#include <stdint.h>
// - Play Sound Effect
#define SSBM_FUNC_PLAYMENUFORWARD 0x80174338
// - Get Pointer to Character Data
#define SSBM_FUNC_CHARPOINTER 0x80034110
// r3 = Player Number
// - Count players in match
#define SSBM_FUNC_COUNTPLAYERS 0x8016B558
// returns r3, number of characters in match
// ==========================
// Staleness Tables
#define SSBM_STALETABLE_P1 0x8045313C
#define SSBM_STALETABLE_DIST 0xE90
#define SSBM_STALETABLE_SIZE 10
typedef struct SSBM_staleness_slot {
uint16_t move_id; // The action state number
uint16_t action_state_num; // The number of action states
// since the beginning of the match
} SSBM_staleness_slot;
typedef struct SSBM_staleness_table {
uint32_t write_next; // The next slot to write to in the table
SSBM_staleness_slot slots[SSBM_STALETABLE_SIZE]; // The 10 staleness slots
} SSBM_staleness_table;
// returns whether or not a staleness table is filled all with the same move
// 0 = not all the same move (or filled completely with zeros)
// 1 = all the same move
int all_the_same_move(SSBM_staleness_table *table) {
uint16_t action = table->slots[0].move_id;
int i;
int all_zeros = 1;
for (i=0; i<SSBM_STALETABLE_SIZE; i++) {
uint16_t move_in_slot = table->slots[i].move_id;
if (action != move_in_slot)
return 0;
if (move_in_slot != 0)
all_zeros = 0;
}
if (all_zeros == 1)
return 0;
return 1;
}
// zeros out a staleness table's slots
void clear_table(SSBM_staleness_table *table) {
int i;
for (i=0; i<SSBM_STALETABLE_SIZE; i++) {
table->slots[i].move_id = 0x0000;
table->slots[i].action_state_num = 0x0000;
}
}
int _main() {
// built in functions setup
void * (*play_menu_forward)() = SSBM_FUNC_PLAYMENUFORWARD;
uint32_t * (*pointer_to_character)(int) = SSBM_FUNC_CHARPOINTER;
uint32_t * (*player_count)() = SSBM_FUNC_COUNTPLAYERS;
int num_players = player_count();
SSBM_staleness_table *stale_tables[num_players];
// populate the array of stale table pointers
int i;
int index = 0;
for (i=0; i<4; i++) {
if (pointer_to_character(i) != 0)
stale_tables[index++] = SSBM_STALETABLE_P1 + (i*SSBM_STALETABLE_DIST);
}
for(i=0; i<num_players; i++) {
// if all slots in a stale table are the same
// move, then clear out the table and play_sfx
// trophy get noise.
if (all_the_same_move(stale_tables[i])) {
clear_table(stale_tables[i]);
play_menu_forward();
}
}
}
Time to make some assembly! Go ahead and open up the command prompt and navigate to your code's directory. Run the command powerpc-eabi-gcc -S unstaler.c to generate an assembly file called unstaler.s. To prepare this assembly code for use with ASM <> WiiRD, we need to clean out a few lines of code and add a branch to the _main function.
Remove all the lines of code that begin with a period, except for the loop labels.
They're usually located at the beginning and ends of functions.
Code:
# Get rid of all the junk that looks like this!
.file "unstaler.c"
.section ".text"
.align 2
.globl all_the_same_move
.type all_the_same_move, @function
Code:
begin:
subi sp,sp,4
stw r3,0(sp)
bl _main
cleanup:
lwz r3,0(sp)
addi sp,sp,4
b exit
all_the_same_move:
stwu 1,-40(1)
stw 31,36(1)
mr 31,1
stw 3,24(31)
lwz 9,24(31)
lhz 9,4(9)
sth 9,16(31)
li 9,1
stw 9,12(31)
li 9,0
stw 9,8(31)
b .L2
.L6:
lwz 10,24(31)
lwz 9,8(31)
slwi 9,9,2
add 9,10,9
lhz 9,4(9)
sth 9,18(31)
lhz 9,16(31)
rlwinm 10,9,0,0xffff
lhz 9,18(31)
rlwinm 9,9,0,0xffff
cmpw 7,10,9
beq 7,.L3
li 9,0
b .L4
.L3:
lhz 9,18(31)
rlwinm 9,9,0,0xffff
cmpwi 7,9,0
beq 7,.L5
li 9,0
stw 9,12(31)
.L5:
lwz 9,8(31)
addi 9,9,1
stw 9,8(31)
.L2:
lwz 9,8(31)
cmpwi 7,9,9
ble 7,.L6
lwz 9,12(31)
cmpwi 7,9,1
bne 7,.L7
li 9,0
b .L4
.L7:
li 9,1
.L4:
mr 3,9
addi 11,31,40
lwz 31,-4(11)
mr 1,11
blr
clear_table:
stwu 1,-40(1)
stw 31,36(1)
mr 31,1
stw 3,24(31)
li 9,0
stw 9,8(31)
b .L9
.L10:
lwz 10,24(31)
lwz 9,8(31)
slwi 9,9,2
add 9,10,9
li 10,0
sth 10,4(9)
lwz 10,24(31)
lwz 9,8(31)
slwi 9,9,2
add 9,10,9
li 10,0
sth 10,6(9)
lwz 9,8(31)
addi 9,9,1
stw 9,8(31)
.L9:
lwz 9,8(31)
cmpwi 7,9,9
ble 7,.L10
addi 11,31,40
lwz 31,-4(11)
mr 1,11
blr
_main:
stwu 1,-80(1)
mflr 0
stw 0,84(1)
stw 22,40(1)
stw 23,44(1)
stw 24,48(1)
stw 25,52(1)
stw 26,56(1)
stw 27,60(1)
stw 28,64(1)
stw 29,68(1)
stw 30,72(1)
stw 31,76(1)
mr 31,1
mr 9,1
mr 22,9
lis 9,0x8017
ori 9,9,17208
stw 9,16(31)
lis 9,0x8003
ori 9,9,16656
stw 9,20(31)
lis 9,0x8016
ori 9,9,46424
stw 9,24(31)
lwz 9,24(31)
mtctr 9
bctrl
mr 9,3
stw 9,28(31)
lwz 9,28(31)
addi 10,9,-1
stw 10,32(31)
mr 10,9
mr 28,10
li 27,0
srwi 10,28,27
slwi 23,27,5
or 23,10,23
slwi 24,28,5
mr 10,9
mr 30,10
li 29,0
srwi 10,30,27
slwi 25,29,5
or 25,10,25
slwi 26,30,5
slwi 9,9,2
addi 9,9,3
addi 9,9,15
srwi 9,9,4
slwi 9,9,4
lwz 10,0(1)
neg 9,9
stwux 10,1,9
addi 9,1,8
addi 9,9,3
srwi 9,9,2
slwi 9,9,2
stw 9,36(31)
li 9,0
stw 9,12(31)
li 9,0
stw 9,8(31)
b .L12
.L14:
lwz 9,20(31)
lwz 3,8(31)
mtctr 9
bctrl
mr 9,3
cmpwi 7,9,0
beq 7,.L13
lwz 9,12(31)
addi 10,9,1
stw 10,12(31)
lwz 10,8(31)
mulli 10,10,3728
addis 10,10,0x8045
addi 10,10,12604
lwz 8,36(31)
slwi 9,9,2
add 9,8,9
stw 10,0(9)
.L13:
lwz 9,8(31)
addi 9,9,1
stw 9,8(31)
.L12:
lwz 9,8(31)
cmpwi 7,9,3
ble 7,.L14
li 9,0
stw 9,8(31)
b .L15
.L17:
lwz 10,36(31)
lwz 9,8(31)
slwi 9,9,2
add 9,10,9
lwz 9,0(9)
mr 3,9
bl all_the_same_move
mr 9,3
cmpwi 7,9,0
beq 7,.L16
lwz 10,36(31)
lwz 9,8(31)
slwi 9,9,2
add 9,10,9
lwz 9,0(9)
mr 3,9
bl clear_table
lwz 9,16(31)
mtctr 9
bctrl
.L16:
lwz 9,8(31)
addi 9,9,1
stw 9,8(31)
.L15:
lwz 10,8(31)
lwz 9,28(31)
cmpw 7,10,9
blt 7,.L17
lwz 9,0(1)
stw 9,0(22)
mr 1,22
mr 3,9
addi 11,31,80
lwz 0,4(11)
mtlr 0
lwz 22,-40(11)
lwz 23,-36(11)
lwz 24,-32(11)
lwz 25,-28(11)
lwz 26,-24(11)
lwz 27,-20(11)
lwz 28,-16(11)
lwz 29,-12(11)
lwz 30,-8(11)
lwz 31,-4(11)
mr 1,11
blr
exit:
opcode-to-replace
Run your assembly through ASM <> WiiRD to get your Gecko code!
Code:
C208923C 00000070
3821FFFC 90610000
48000149 80610000
38210004 48000364
9421FFD8 93E10024
7C3F0B78 907F0018
813F0018 A1290004
B13F0010 39200001
913F000C 39200000
913F0008 48000060
815F0018 813F0008
5529103A 7D2A4A14
A1290004 B13F0012
A13F0010 552A043E
A13F0012 5529043E
7F8A4800 419E000C
39200000 4800004C
A13F0012 5529043E
2F890000 419E000C
39200000 913F000C
813F0008 39290001
913F0008 813F0008
2F890009 409DFF9C
813F000C 2F890001
409E000C 39200000
48000008 39200001
7D234B78 397F0028
83EBFFFC 7D615B78
4E800020 9421FFD8
93E10024 7C3F0B78
907F0018 39200000
913F0008 48000040
815F0018 813F0008
5529103A 7D2A4A14
39400000 B1490004
815F0018 813F0008
5529103A 7D2A4A14
39400000 B1490006
813F0008 39290001
913F0008 813F0008
2F890009 409DFFBC
397F0028 83EBFFFC
7D615B78 4E800020
9421FFB0 7C0802A6
90010054 92C10028
92E1002C 93010030
93210034 93410038
9361003C 93810040
93A10044 93C10048
93E1004C 7C3F0B78
7C290B78 7D364B78
3D208017 61294338
913F0010 3D208003
61294110 913F0014
3D208016 6129B558
913F0018 813F0018
7D2903A6 4E800421
7C691B78 913F001C
813F001C 3949FFFF
915F0020 7D2A4B78
7D5C5378 3B600000
578A2EFE 57772834
7D57BB78 57982834
7D2A4B78 7D5E5378
3BA00000 57CA2EFE
57B92834 7D59CB78
57DA2834 5529103A
39290003 3929000F
5529E13E 55292036
81410000 7D2900D0
7D41496E 39210008
39290003 5529F0BE
5529103A 913F0024
39200000 913F000C
39200000 913F0008
48000058 813F0014
807F0008 7D2903A6
4E800421 7C691B78
2F890000 419E0030
813F000C 39490001
915F000C 815F0008
1D4A0E90 3D4A8045
394A313C 811F0024
5529103A 7D284A14
91490000 813F0008
39290001 913F0008
813F0008 2F890003
409DFFA4 39200000
913F0008 48000060
815F0024 813F0008
5529103A 7D2A4A14
81290000 7D234B78
4BFFFD41 7C691B78
2F890000 419E002C
815F0024 813F0008
5529103A 7D2A4A14
81290000 7D234B78
4BFFFDDD 813F0010
7D2903A6 4E800421
813F0008 39290001
913F0008 815F0008
813F001C 7F8A4800
419CFF98 81210000
91360000 7EC1B378
7D234B78 397F0050
800B0004 7C0803A6
82CBFFD8 82EBFFDC
830BFFE0 832BFFE4
834BFFE8 836BFFEC
838BFFF0 83ABFFF4
83CBFFF8 83EBFFFC
7D615B78 4E800020
3BE50000 00000000
________________________________________________________________________
Video
Video
________________________________________________________________________
That's it! A crash course for programming in C for Melee. Feel free to ask any questions, I'll try my best to answer them.
Last edited: