Skip to content

Simple Program

Tom Sherman edited this page Mar 8, 2017 · 26 revisions

Previous Section: Getting Started

Contents

Overview

Example

In this tutorial we will create a simple AI for SSBM. This provides a good example that incorporates most elements of the library and produces something semi-useful in the end.

To start we will create an AI that drops down from the re-spawn platform. This is pretty much the most basic move the AI could do, so it's a good introduction to this tutorial.

Note that this AI will not do anything else. Our AI will completely overwrite the default cpu player, so if no moves were given to the AI nothing would happen at all.

Background Knowledge

It is extremely important to understand variable scope and the static keyword.

Other Important Topics:

Function Pointers

Unions

Basic Structure

First step is to create a file called SimpleProgram.c. This will be the source file that contains _main() (equivalent to main in a normal C program - see FAQ). The basic structure of this file looks like:

static bool init_run = false;

static void init()
{
    ...
}

void _main()
{
    if (!init_run) { init(); }

    ...
}

Most of the mods you ever create will start with this basic structure. Initialization is primarily used for creating the heap, but there could be many other things that need to be done the first time _main is called. In our case we just need to initialize the heap. Our initialization function will be:

#include <mml/system.h>
#include <mml/gctypes.h>

static char heap[2000];
static bool init_run = false;

static void init()
{
    initHeap(heap, heap + sizeof(heap));
    init_run = true;
}

initHeap takes a pointer to the lower and uppper limit of the heap. There are several ways of reserving enough space for your heap, but the simplest is just to declare your own variable in the global scope. In this case, heap is 2000 bytes long and we pass a pointer to the beginning and end of heap to the initHeap function. Now any calls to malloc, calloc or realloc will allocate space in this region.

Even if you never call malloc there are a few functions in the library that call malloc. If you ever call one of these functions you will need to initialize the heap first.

Declaring the AI

We want the AI to live in the global scope, otherwise the AI would not be able to "remember" information from previous frames and would have to make an independent decision every frame. The declaration looks like:

#include <mml/ai.h>

static AI cpuPlayer = INIT_AI(2, FALCO | FOX | MARTH | FALCON);

The INIT_AI macro allows for simple initialization. In this case, our AI will play as port 2 and only use characters Falco, Fox, Marth, and Falcon. If the game is loaded with one of these characters on a legal stage, the AI will take over.

Adding Logic

This is the core mechanism by which the AI makes decisions. By design, the library wraps up all functionality required to make an AI, but the user still needs to provide the rules by which the AI makes decisions. A Logic struct is a simple way to encode if-then logic. Our example looks like:

static Logic respawnLogic = 
{
    {&actionStateEq, .arg1.u = 2, .arg2.u = _AS_RebirthWait},
    {&addMove, .arg1.p = &cpuPlayer, .arg2.p = &_mv_shortHop}
};

In order to understand that, let's break down what exactly is in a Logic struct.

typedef struct
{
    FunctionCall condition;
    FunctionCall action;

} Logic;

This is the definition of the struct. Don't worry about the specifics of what a FunctionCall is yet. Basically, Logic is a struct that encodes if-then logic. condition is a call to a function that returns a bool value. action is a call to a function that happens if condition is true. Basically this is equivalent to the statement:

if (condition())
{
    action()
}

However, encoding that in a struct provides much more flexibility and much smaller code.

The definition of FunctionCall is:

typedef struct
{
    void* function;
    FunctionArg arg1;
    FunctionArg arg2;
    
} FunctionCall;

Basically, it is a function pointer along with 2 arguments. FunctionArg is a union of 32-bit data types that allows for multiple different data types to be the argument of a function.

typedef union
{
    u32 u;
    s32 s;
    f32 f;
    void* p;

} FunctionArg

Now that we know what is in a Logic struct, let's re-examine our example:

static Logic respawnLogic = 
{
    {&actionStateEq, .arg1.u = 2, .arg2.u = _AS_RebirthWait},
    {&addMove, .arg1.p = &cpuPlayer, .arg2.p = &_mv_shortHop}
};

The condition function of respawnLogic checks if the action state of player 2 is equal to RebirthWait (aka waiting on the platform). The action function of respawnLogic adds the move "short hop" to the cpuPlayer.

This means that every frame the following if-then statement is called:

if (actionStateEq(2, _AS_RebirthWait))
{
    addMove(&cpuPlayer, &_mv_shortHop);
}

AI structure

The basic structure of any program that uses an AI is:

static void init()
{
    ...
}

static void loadDefaultLogic()
{
    ...
}

void _main()
{
    if (!init_run) { init(); }

    if (needLogic(&cpuPlayer)) { loadDefaultLogic(); }

    updateAI(&cpuPlayer);
}

Whenever _main is called, it first checks if in needs to run the initialization function. Then it checks if the AI needs instructions (happens whenever the AI has no logic or moves queued up). If the AI does need logic, it calls the loadDefaultLogic function that gives the AI its basic instructions. Finally, the AI is updated - all its logic is processed and it's inputs for that frame are written to the controller.

In our case, the default logic looks like:

static Logic respawnLogic = 
{
    {&actionStateEq, .arg1.u = 2, .arg2.u = _AS_RebirthWait},
    {&addMove, .arg1.p = &cpuPlayer, .arg2.p = &_mv_shortHop}
};

static void loadDefaultLogic()
{
    addLogic(&cpuPlayer, &respawnLogic);
}

It is important to declare Logic in the global scope, for more info see Data is Smaller than Code.

Configuration File

Now that our source file is complete (see here for complete file) it is time to create the configuration file for wiimake. This file will basically be the same for each program in this tutorial. First create a file called SimpleProgram.ini.

The first variable we need to define is our available memory regions. This tells wiimake which addresses in can overwrite.

REGIONS =

    8022887c-80228920 ; unused code
    8032c848-8032c87c ; unused code
    8032dcb0-8032ddb8 ; unused code
    8032ed8c-8032ee8c ; unused code
    80393a5c-80393c0c ; unused code
    803fa3e8-803fc2e8 ; debug menu tables/strings
    803fc420-803fdc18 ; debug menu tables/strings
    803001dc-80301e40 ; debug menu functions
    801910e0-8019af4c ; tournament mode
    8040a950-8040bf4c ; unknown

This is a standard list of regions in SSBM v1.02. The debug menu data is overwritten along with tournament mode, but this gives enough room for us to work with.

Next we want to tell wiimake about any addresses we want to overwrite (equivalent to DOL modding).

; debug menu replaces tournament mode

8022d638 = 38000006

; unlock all characters and stages, random stage select

801648c8 = 38a007ff
801644bc = 38a007ff
80173580 = 38a007ff

; default settings (pause on)

803d4a48 = 00340100
803d4a4c = 04000a00
803d4a50 = 08010100
803d4a60 = ff000000
803d4a70 = 00000000
803d4a78 = e70000b0

; spoof controllers

80376bd4 = 38000000
80376bdc = 38000000
80376bf0 = 380000d8
80376c04 = 38000001

; boot to CSS

801bfa20 = 38600002

Note that the debug menu replaces the tournament mode option, so now it is safe to overwrite tournament mode as it is no longer accessible to the user. It is also neccesary to have a "spoofed" controller plugged in to port 2 or else the AI has no way of sending input. Any other DOL mods are just for convenience.

Next we need to tell wiimake how to compile and link our code:

SOURCES = SimpleProgram.c

LIBRARIES = ../../libmml.a

INCLUDE_PATHS = ../mml-1.0.0/include/

COMPILER_FLAGS = -std=c99 -fno-builtin -fmerge-constants -Werror

LINKER_FLAGS =

This says that are only source file is SimpleProgram.c. It also gives wiimake the path to the library for linking, and the path to the include folder of the library for compiling. Finally, we can pass flags to the compiler and linker. -fno-builtin is essential since the library overwrites many built-in functions. -fmerge-constants helps with large constants in the .data section of the executable (especially useful with strings). -Werror is important since warnings are more serious when programming in this kind of environment.

The last piece of the config file is for the fixed symbols. This tells wiimake which functions in our code we want set up branches to from the original melee source code.

FIXED_SYMBOLS =

    _main 80377998 7ee3bb78
    endGame 801b15cc 38800000
    display 801a633c 7c7f1b78

First, _main is injected at 80377998. This address is in between the game reading the controller and processing the input. It allows for controller overwrites to work. 7ee3bb78 is the instruction originally at 80377998.

endGame is a function in the library. This must be placed at a point in the code which is executed when a game ends. It sets important global variables that tell the AI when it is in or out of a game. The instruction at 801b15cc tells Melee to branch to the results screen. We overwrite it with 38800000 which tells Melee to skip the results screen. Thus, we accomplish two things here.

display is the function that loads in our output stream to the address the debug menu looks at. This way we can overwrite the debug menu strings/tables since they will never be called, and it sets up the debug menu (called by clicking tournament mode) to be replaced with our output stream. Any calls to print will put text here.

The final config file is here.

Creating the program

Now that the config file is complete we simply run:

wiimake [MELEE.ISO] SimpleProgram.ini

and we have created out first program! Load up this iso and enter a game on a valid stage and set player 2 to cpu Falco/Fox/Marth/Falcon. This cpu should do nothing except jump down from the re-spawn platform. See iso reference to verify that your iso was built correctly.

Next Section

Now that you've gotten the hang of writing a mod, let's make something useful. The next section creates an AI that can tech.

Next Section: Teching