Mario Kart Wii Gecko Codes, Cheats, & Hacks

Full Version: Inline ASM HBC Tutorial
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Inline ASM HBC Tutorial



Credits to WiiBrew.org for original basic Inline ASM tutorial. Wiibrew tutorial is HERE

I've made a few apps using Inline PowerPC Assembly for very basic stuff. This isn't really useful for apps in general as you want to learn C and C++, but nonetheless here's a tutorial utilizing Inline ASM for HBC apps.



Chapter 1: Installing DevkitPro PPC

If you don't have DevkitPro PPC, you need to install it. For Windows and Ubuntu linux, there are tutorials literally everywhere on the Web. If you happen to use Debian Linux like me, I wrote a good tutorial for installation HERE.



Chapter 2: Changes to default project

Make a copy of the Hello World Wii Project (examples/Wii/template), and paste it to a new directory. Go ahead and rename the 'template' directory (the one in your new directory) to assembler. Within your /assembler folder, delete the template.pnproj file, you don't need it.

The Makefile at /assembler needs to be modified. Open up the Makefile on your preferred notepad/text-editor and find this...

Code:
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------

CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE)
CXXFLAGS = $(CFLAGS)

LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map

Depending on your devkit version, this contents here may slightly differ. You need to add a comment (hash tag symbol) on the 'CFLAGS' line. like this..

#CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE)

After that edit, add the following line (place it right above the CFLAGS line)...

CFLAGS = -save-temps -Wall $(MACHDEP) $(INCLUDE)

If you did everything correctly, the Makefile (the portion showed earlier), should look like this..

Code:
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------

CFLAGS = -save-temps -Wall $(MACHDEP) $(INCLUDE)
#CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE)
CXXFLAGS = $(CFLAGS)

LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map

Save your edits, close the file.



Chapter 3: Setting up new Default Project

The edits are made. Now compile the project. For linux, it would be something like this...

cd /path/to/the/folder/assembler
make

When the project gets compiled there will be a new directory present... (/assembler/build). Within the build directory, you should see the template.s file, this is our Inline ASM file. Go ahead and delete the following files
  • template.d
  • template.elf.map
  • template.i
  • template.o

And also delete the newly created dol and elf files. Remember that line for CFLAGS you added to the makefile? Go ahead and comment it out. Uncomment the original CFLAGS line. Now you always have a default Inline ASM project. BACK THIS UP somewhere safe.



Chapter 4: Writing some Inline ASM

We are now actually going to do some Inline ASM work to create a very basic new project. Before we do that, let's make a small edit to the project. Open up the /build/template.s file.

Change "Hello World!" to "Hello ASM!!!". And yes, make sure you use 3 total exclamation marks.

Save changes. Open the /source/template.c file and make the same changes to "Hello World!". Save changes.

Now it's time to write some Inline ASM. Open up template.s. Scroll down until you see something like this (yours may slightly differ)...

Code:
bl WPAD_ScanPads
li 3,0
bl WPAD_ButtonsDown
stw 3,8(31)
lwz 9,8(31)
rlwinm 9,9,0,24,24
cmpwi 7,9,0
beq 7,.L3
li 3,0
bl exit

This is the chunk of ASM responsible for constantly loading the Wii Remote button values and seeing if the Home button is pressed. If it is (r9 not equal to 0), the li 3,0 instruction (only argument upcoming function) is executed and then the exit function is called, thus exiting back to HBC.

We will modify this chunk of ASM to shutdown the Wii instead of exiting to HBC. The Wii has some GPIO lines which you can read about HERE, which we can write basic ASM for to execute a shutdown (NOTE: This will only work on HBC versions 1.1.0 or later or else there may be a permissions error and the shutdown won't be executed).

Here's a small snippet of ASM to shutdown the Wii....

Code:
lis r3, 0xCD80 #Set GPIO_OUT upper 16 bits
lwz r4, 0x00E0 (r3) #Load Pin Connection word value from GPIO_OUT
ori r4, r4, 0x0002 #Toggle the SHUTDOWN Pin
stw r4, 0x00E0 (r3) #Update the GPIO_OUT Pins

Now add this snippet of ASM right underneath the 'beq' instruction (the branch taken if the Home button is NOT pressed). The chunk of ASM should now look like this...

Code:
bl WPAD_ScanPads
li 3,0
bl WPAD_ButtonsDown
stw 3,8(31)
lwz 9,8(31)
rlwinm 9,9,0,24,24
cmpwi 7,9,0
beq 7,.L3

    lis r3, 0xCD80 #Set GPIO_OUT upper 16 bits
    lwz r4, 0x00E0 (r3) #Load Pin Connection word value from GPIO_OUT
    ori r4, r4, 0x0002 #Toggle the SHUTDOWN Pin
    stw r4, 0x00E0 (r3) #Update the GPIO_OUT Pins

li 3,0
bl exit

Onto compiling...



Chapter 5: Compiling the Inline ASM Project

We simply cannot go ahead run make on the Makefile to have this project compiled. We need do some extra steps, which is creating an object file (.o file) from our Inline ASM file. This is done using devkit's binutils. For linux, something like this would be executed in your terminal..

cd /path/to/your/binutils #(devkitpro/devkitPPC/bin)

./powerpc-eabi-as -mregnames -mbroadway /path/to/assembler/build/template.s -o /path/to/assembler/build/template.o

cd /path/to/assembler

make

The project will now compile. Rename the dol file to boot.dol.



Chapter 6: Testing the HBC App

After renaming the dol file. Throw in any xml and png file to make a quick app out of the project. Place the project in the apps folder of your SD/USB device. Launch the app.

Side note: Make sure your XML file has the tag <ahb_access/> tag(right before the final tag of </app>). These is needed to allow Broadway to have full permissions or else the Wii shutdown function won't work.

If you are too lazy to find or make an xml, here's a template you can use, you will still need to get an icon.png btw...

Code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<app version="1.1">
<name>NAME OF PROGRAM</name>
<version>0.0</version>
<release_date>20200101</release_date>
<coder>AUTHOR</coder>
<short_description>STUFF</short_description>
<long_description>MORE STUFF</long_description>
<ahb_access/>
</app>

You will see the Hello ASM!!! message. Now press the Home button on your Wii Remote. The Wii will shutdown. Congratz! And there ya go, a quick example of how to run very basic Inline ASM instructions on a HBC project.



Chapter 7: Controller and Console Font Stuff

In this snippet of code, this is the button check for the Wii Remote to see if HOME button was pressed to exit to HBC

Code:
bl WPAD_ScanPads
li 3,0 #Arg for WPAD_ButtonsDown
bl WPAD_ButtonsDown
stw 3,8(31) #Buttons Down returned in r3, Store it
lwz 9,8(31) #Load Buttons-Down into r9
rlwinm 9,9,0,24,24 #Clear all bits except HOME button bit
cmpwi 7,9,0 #Check r9 vs 0, use cr7
beq 7,.L3 #If HOME button bit is 0 (NOT pressed), continue to .L3 (video syncing and keeping app running)

The Rotation instruction shows bit 24 is for the HOME button. Here is all the Wii Remote button bits if you ever need them...
  • Bits 16 thru 18 = unused
  • Plus = Bit 19 (0x00001000)
  • Up = Bit 20 (0x00000800)
  • Down = Bit 21 (0x00000400)
  • Right = Bit 22 (0x00000200)
  • Left = Bit 23 (0x00000100)
  • Home = Bit 24 (0x00000080)
  • Bits 25 & 26 = unused
  • Minus = Bit 27 (0x00000010)
  • A = Bit 28 (0x00000008)
  • B = Bit 29 (0x00000004)
  • 1 = Bit 30 (0x00000002)
  • 2 = Bit 31 (0x00000001)

And if a nunchuck is attached, here's its extra bits
  • C = Bit 14 (0x00020000)
  • Z = Bit 15 (0x00010000)

Fyi, for anything Classic Controller related, you don't need to add any extra functions to call/check, Wii Remote attachments are configured automatically, simply just use these bits. Keep in mind that the nunchuck attachment bits (14 and 15) are also used by the Classic Controller)
  • Right = Bit 0 (0x80000000)
  • Down = Bit 1 (0x40000000)
  • L = Bit 2 (0x20000000)
  • Minus = Bit 3 (0x10000000)
  • Home = Bit 4 (0x08000000)
  • Plus = Bit 5 (0x04000000)
  • R = Bit 6 (0x02000000)
  • Bit 7 = Unused
  • ZL = bit 8 (0x00800000)
  • B = Bit 9 (0x00400000)
  • Y = Bit 10 (0x00200000)
  • A = Bit 11 (0x00100000)
  • X = bit 12 (0x00080000)
  • ZR = bit 13 (0x00040000)
  • Left = bit 14 (0x00020000)
  • Up = bit 15 (0x00010000)


If desired you can easily add GCN compatibility. You can do this via adding the basic functions in C then modifying via Inline or do everything from scratch in the inline file.

Via C file:
Add this right underneath WPAD_Init
Code:
// Initialise GCN
    PAD_Init();

And in the part of the C file that deals with the WPAD scan and exiting to HBC via HOME button, modify it something like this...
Code:
// Call WPAD_ScanPads each loop, this reads the latest controller states
        WPAD_ScanPads();

        // WPAD_ButtonsDown tells us which buttons were pressed in this loop
        // this is a "one shot" state which will not fire again until the button has been released
        u32 pressed = WPAD_ButtonsDown(0);
       
        // Call PAD_ScanPads each loop, this reads the latest GCN controller states
        PAD_ScanPads();
       
        u32 GCNpressed = PAD_ButtonsDown(0);

        // We return to the launcher application via exit
        if ( pressed & WPAD_BUTTON_HOME ) exit(0);
       
        if ( GCNpressed & PAD_TRIGGER_START ) exit(0);

        // Wait for the next frame
        VIDEO_WaitVSync();

Afterwards, just compile with the makefile modified to allow you to edit the .s inline file to what you need.

Via Inline:
If you don't want to modify the C file, just do everything via Inline. In your Inline file, add a "bl PAD_Init" right underneath the "bl WPAD_Init". Then edit the button scanning portion to something like this...


Code:
bl WPAD_ScanPads
li 3,0
bl WPAD_ButtonsDown
stw 3,8(31)
bl PAD_ScanPads #scan the GCN
li 3,0
bl PAD_ButtonsDown #get what buttons are pressed in the GCN
stw 3,12(31) #safe to do. Store GCN buttons


Then do what you need with the WPAD and PAD button values that are stored in reference to r31.

And here are all the GCN button bit values...
  • Start = bit 19 (0x00001000)
  • Y button = bit 20 (0x00000800)
  • X button = bit 21 (0x00000400)
  • B button = bit 22 (0x00000200)
  • A button = bit 23 (0x00000100)
  • bit 24 unused
  • L = bit 25 (0x00000040)
  • R = bit 26 (0x00000020)
  • Z = bit 27 (0x00000010)
  • Up = bit 28 (0x00000008)
  • Down = bit 29 (0x00000004)
  • Right = bit 30 (0x00000002)
  • Left = bit 31 (0x00000001)

Here are some tips/tricks to make printf not so boring...

Start Console (always included in the source by default, simply posted here for completeness)
\033[2;0H

Enter into New Line
\n

Clear Console, Move cursor back to very top left (note: this goes up to being partially out of screen so further messages afterwards start with a double enter \n\n)
\x1b[2J

Set Font Color (XX = color)
\x1b[XXm

30 = Black
31 = Red
32 = Green
33 = Yellow
34 = Blue (not useful as it's very faded/dull)
35 = Purple
36 = Cyan
37 = White/Default

You can also set the Fill Color. Just add 10 to font color XX value, string used is same as font color.




Chapter 8: Intro to Utilizing the SD/USD device

One of the great things about HBC apps is the 'universal' access to working with the SD device. Many Wii games do not include Symbols for the SD card slot, which means you would have to code everything out manually (including the FAT file system).

The NAND file system is used in a lot of Gecko Codes, but it's inferior to operating on files present on the SD device. End users that will use your HBC apps will almost always prefer SD over NAND.

NOTE: I've personally never tested any of this with your files being on a USB stick. May not work.



Chapter 9: Re-doing the default Inline ASM project

You don't need to delete the original saved project you did from the earlier chapters. However we will re-write it since we need extra headers and libraries to interact with the SD/USB device.

We will repeat Chapters 2 and 3 but with some extra steps. When doing the very first edits on the makefile in Chapter 2, some extra changes will need to be made.

Find the line that looks like this...

Code:
LIBS    :=    -lwiiuse -lbte -logc -lm

You will need to add in the -lfat library. Add in the library to update the makefile to look like this...

Code:
LIBS    :=    -lwiiuse -lbte -lfat -logc -lm

Save and close. Now go to the template.c file, we need to add some headers. At the very top of the .c file, you will see this...

Code:
#include <stdio.h>
#include <stdlib.h>
#include <gccore.h>
#include <wiiuse/wpad.h>

Change that portion of the file to be this....

Code:
#include <gccore.h>
#include <wiiuse/wpad.h>

#include <fat.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

Save and close. Complete Chapter 2 (remember to add in the new CFLAGs line in the Makefile). After that, complete Chapter 3 for this project.

Now backup this project somewhere safe, you can name it to something like "SD-Template-Base".



Chapter 10: Initializing the FAT File System

To even begin opening/reading/writing/etc files on the SD/USB device, you need to init the FAT system. If the device isn't in FAT16 and FAT32, this will fail. You can init the FAT system like this...

Code:
bl fatInitDefault
cmpwi r3, 0
beq- error

fatInitDefault should never return null. If so, something went wrong. Be sure to add in some code to handle errors, as you don't want the App to continue with its task when an error occurs or you might be greeted with an Exception Screen output.



Chapter 11: Opening, Creating, & Closing Files on SD

Opening and closing is quite different when compared to how its done on NAND files. Instead of having a file descriptor (fd), you instead have a file pointer (fp).

Opening a file requires the fopen function and it requires 2 args

r3 = Pointer to File Name (must end in null)
r4 = Pointer to Permissions

Regarding the r4 arg, you need a pointer that points to a standard ASCII string (that ends in null). This string (depending on its contents) decides what permissions will be used when opening the file.

Permission Ascii String List:
"rb" = Read permissions
"wb" = Write permssions
"ab" = Write perms & append

The 'b' part of the string stands for binary. Since you are working on this via assembly, you need to include this 'b'.

So for example, let's say you wanted to open up a file called "test.txt" with write permissions. You could do it like this....

Code:
#Establish ASCII strings for file name and perms
file_name:
.string "test.txt"
perms:
.string "rb"

#Other code here

#Open test.txt with read perms
lis r3, file_name@ha
la r3, file_name@l (r3)
lis r4, perms@ha
la r4, perms@l (r4)
bl fopen

fopen is also different than something such as isfs_open (NAND work) when it comes to return values. If fopen is successful, you are returned a memory pointer. This is your File Pointer, aka fp. If r3 = 0, then an error has occurred.

Code:
#Pretend we've called fopen, now we will check r3
cmpwi r3, 0
beq- error

Creating files is very different that ISFS. There is no designated create function. Instead to create a file, you need to open a non-existing file with write perms ("wb").

You can open a file that you intend to append data to the end of it via opening with using an r4 arg with a pointer that points to "ab". When opening a file in this method, any writes you do will start immediately after the end of the file and get appended. Please note that opening a file in this manner renders the fseek and rewind functions as useless (more on that in Chapter 13)

Closing a file is simple, just place the fp into r3, and call fclose. r3 will return as 0 if everything went OK.

Code:
#Pretend fp is in r31
mr r3, r31
fclose
cmpwi r3, 0
bne- error

Unlike utilizing the NAND file system, with SD/USB, you can reopen files with different perms without having to close them beforehand.

The function for that is freopen. There are 3 args to freopen~

r3 = pointer to filename (same as fopen)
r4 = pointer to perms (same as fopen)
r5 = fp from the original use of fopen on the file

If an error occurs on freopen, a null value is returned in r3.



Chapter 12: Reading and Writing Files on SD

For reading a file (dumping it contents to memory), you must have opened the file earlier with read permissions. The function to read a file is called fread. It has 4 arguments.

r3 = Pointer to dump file's contents to (aka called the Buffer)
r4 = size
r5 = count
r6 = fp (file pointer you were given from fopen)

r4 (size) and r5 (count) can be deceptive... to say the least. Since you are working on files via a 'binary' manner, the terms of these are backwards...

For the type of work we do, r4 will always be 1. This is because the file (as being read/written in binary form) only has 1 type of data, therefore the size is 1.

r5 (count) is what you will use for the *real* size of the file.

Example~

Code:
#Dump 100 bytes of a file, pretend we have already opended if with read perms
#Pretend fp is in r31 and our r3 arg will be the EVA spot of 0x80001500
lis r3, 0x8000
ori r3, r3, 0x1500
li r4, 1
li r5, 100
mr r3, r31
bl fread

When the function returns, r3's value should match what was originally used as the r5 arg. If not, then an error occurred.

Writing (function name: fwrite) is the same as Reading except the r3 arg is a pointer to the contents that will be used to write on the file. Return (r3) value works in the same manner as fread. Remember to open the file with write perms before hand.



Chapter 13: Getting the size of a file on the SD

A file may have a dynamic or unknown size. Therefore, if you are needing to allocate memory later for fread or fwrite, you will need a way to know the file's size to know how much memory to allocate. Memory allocation will be discussed in the next Chapter.

fseek, ftell, and rewind are the 3 functions you will need to do this. Please note that fseek and rewind do not work on files that were opened with an r4 arg of "ab" (write perms & append).

When working on a file, you are by default, at the very beginning of said file. You can actually "move" where you are "located" on the file.

fseek allows you to do this. It has 3 args.

r3 = fp
r4 = offset from origin of file
r5 = type of origin

Before you can set r4, you need to know what r5 value you will be using. r5 comes with 3 options.

0 = Beginning of file aka SEEK_SET
1 = Current position of file aka SEEK_CUR
2 = End of file aka SEEK_END

Once you have r5 set, you can then apply an offset value in r4.

For getting the size of the file, we need to use an r4 arg of 0 and an r5 arg of 2 (SEEK_END)

Code:
#pretend fp is in r31
mr r3, r31
li r4, 0
li r5, 2
bl fseek

fseek will return 0 if everything went OK.

After you have executed fseek (r4 as 0, r5 as 2), you will be "located" at the end of the file. At this point, we can use the ftell function. ftell tells us our "location value" of the file. Since we have 'seeked' to the very end, the "location value" after calling ftell will give us our file size.

ftell has only one arg. r3 = fp

Code:
#pretend fp is in r31
mr r3, r31
bl ftell

After ftell has been called, r3 will now contain the file's size (in bytes). You do NOT need to check for errors on ftell. As long as the correct r3 arg was supplied, ftell will NEVER output an unexpected error code.

We're almost done! Be sure to have some code to backup the file's size somewhere safe (such as an unused Global Variable Register). All we have do is call the function rewind (to "move" back to the start of the file) and we're good. The function rewind requires 1 arg (r3 = fp)

Code:
#pretend fp is in r31
mr r3, r31
bl rewind

Similar to ftell, you don't need to check for any errors in rewind.



Chapter 14: Allocating Memory

Sometimes the EVA isn't enough space for reading/writing files. You will need to call a function to give you some free memory to use.

Not only that, since we are working on what is known as input/output streams, we need the memory to be 32-byte aligned (meaning the address to start of the memory block needs to be divisible by 32)

The function we need is called memalign. memalign takes 2 args

r3 = Alignment (based on byte)
r4 = Size

The great thing about memalign, is that you need not need to worry about "aligning" the size so its divisible by the r3 arg. Since we need a 32-byte alignment, our r3 arg is 32. r4 arg will be what you received back from ftell.

Code:
#pretend size we got from ftell was previously saved in r30
li r3, 32
mr r4, r30
bl memalign

memalign will return you a pointer to the allocated memory block if it was successful. r3 will be 0 if an error occurred.



Chapter 15: Renaming and Deleting Files on the SD

The function name for deleting a file is remove. Deleting a file is easy. You just need one arg (r3) and it has the be the file name (must end in null). The file cannot be opened when you are deleting it. If deleting the file was successful, a 0 will be returned.

Renaming a file is simple too. Make sure the file is NOT opened when renaming. Function name for renaming is simply rename. rename requires 2 args.

r3 = pointer to old name (must end in null)
r4 = pointer to new name (must end in null)

r3 will return a 0 if everything went OK.



Chapter 16: Cache Considerations

Unlike ISFS functions, SD functions do not manage Cache for you.

If Cache isn't properly managed, inaccurate data and/or instructions may be present before, after, and/or during a read and/or write. For reading data, the space of memory where you are dumping to, may need to be invalidated (dcbi) BEFORE you call fread. This is because there are may be data sitting in virtual cached memory that hasn't been pushed out to physical memory yet. If you call fread without data invalidation beforehand, the data in virtual cached memory may get pushed onto physical memory AFTER fread has already dumped SD contents to memory.

By invalidating the memory beforehand, it ensures that any data residing in virtual cached memory will be purged (never make it to physical). A data flush or clean for this scenario makes zero sense as any data pushed to physical memory will be replaced by fread's dumped contents anyway.

Example:
Code:
#Pretend r29 points to memory region where fread will dump contents to
#Pretend r30 contains the byte size of this region
mr r3, r29
mr r4, r30
bl DCInvalidateRange
.. .. #Instructions here to setup fread args
bl fread

If you are using fread to dump instructions to memory that will executed after dumping, then the region of memory in question will need to be instruction-invalidated (icbi) AFTER the fread. Data invalidation before the fread still needs to be included as well.

Example:
Code:
#Pretend r29 points to memory region where fread will dump instructions to
#Pretend r30 contains the byte size of this region
mr r3, r29
mr r4, r30
bl DCInvalidateRange
.. .. #Instructions here to setup fread args
bl fread
.. .. #Instructions here to verify fread r3 return value
mr r3, r29
mr r4, r30
bl ICInvalidateRange

For writing data, the space of memory that contains the content should be flushed sometime BEFORE you call fwrite. This is because fwrite (or the SD functions in general) can only interact with physical memory. If you have data in virtual cached memory that hasn't been pushed to physical memory yet, then fwrite will use data in physical memory that is "stale". Therefore you have ended up writing to the SD file with stale content.

Example:
Code:
#Pretend r29 points to memory region where fread will dump contents to
#Pretend r30 contains the byte size of this region
mr r3, r29
mr r4, r30
bl DCFlushRange
.. .. #Instructions here to setup fread args
bl fwrite



Chapter 17: Conclusion; Final Tip

Welp that's it. You now have the knowledge to make/work on HBC apps in an "Inline" manner via Assembly. Also, you have the knowledge to work with the SD device.

Final Tip: To test if a SD file already exists, you will open it using read permissions. Simply check if a valid fp was returned. If so, then you know the file does indeed already exists.