This is a decompilation of Pokémon Emerald.
To set up the repository, see INSTALL.md.
The version of mgba integrated into dolphin does not have a console to read logs. If you want to debug the game by printing logs you will need to use select GBA (TCP) in dolphin (instead of integrated) then open a seperate instance of mgba. Run the Wii Channel, then in mgba go to file > connect to dolphin and hope it connects. This connection has higher latency than the integrated version and if the initial connection to dolphin fails you need to restart both dolphin and mgba and try again. Repeat this until it works.
To enable logging in pokeemerald go to config.h and uncomment the line #define NDEBUG
. You can then print logs like so:
DebugPrintfLevel(MGBA_LOG_DEBUG, "Some Log Message");
This version of pokeemerald has been modified to allow creation of special tasks that connect to the internet via a wii channel.
I've written a small guide bellow, but if you want to jump right into the code the best places to start are:
network.h
(which includes some documentation on how the communication works)net_conn.c
(which has some example tasks. Look at NET_CONN_START_BATTLE_FUNC for a commented example)Lilycove_GTS_2F/scrips.inc
(to see how I start the task from a scrip)
- In
network.h
define a value for your task e.g.#define NET_CONN_START_MART_FUNC 2
- In
net_conn.h
add an entry to thesNetConnFunctions
array e.g[NET_CONN_START_MART_FUNC] = SetupForOnlineMartTask
- In
net_conn.h
define 4 functions for your task (Setup, Cancel, Process, EndConnection). e.g
static void SetupForOnlineMartTask();
static void Task_OnlineMartCancel(u8 taskId);
static void Task_OnlineMartProcess(u8 taskId);
static void Task_EndOnlineMartConnection(u8 taskId);
I'll explain these more in a sec.
- In your script.inc start the network task like this
setvar VAR_0x8004, NET_CONN_START_MART_FUNC
special CallNetworkFunction
waitstate
- Implement the 4 functions.
SetupFunction - This runs before the other tasks and is mostly used to configure the sSendRecvMgr. Copy and modify a existing one.
CancelFunction - This is what runs when a user cancels mid connection (by pressing 'B'). Normally this just calls the EndConnectionFunction.
EndConnectionFunction - This runs after everything. Copy and modify a existing one. Typically it should a) End the serial tranmission b) Destroy the task.
ProcessFunction - This is where you cycle through your tasks states.
While running the network task will loop between your process function and Task_NetworkTaskLoop. Normally each loop your Function will increment it's state, setup the sSendRecvMgr for the next operation or process some data. A simple task would have 5 states (SEND, TRANSMIT, WAIT, RECEIVE, FINISH). Here's a simple example of what a process function might look like:
static void Task_MyProcessFunction(u8 taskId)
{
switch (sSendRecvMgr.nextProcessStep)
{
case MY_SEND_STATE: // Send some data from somewhere in the GBAs memory to the wii
break;
case MY_TRANSMIT_STATE: // Ask the wii to send that data to the server
break;
case MY_WAIT_STATE: // Wait for the server to send the wii data and play some animation
break;
case MY_RECEIVE_STATE: // Read data from the wii to somewhere in the GBA's memory
break;
case MY_FINISH_STATE: // Process the data we got back
sSendRecvMgr.state = NET_CONN_STATE_DONE; // This will cause the task to end
break;
}
gTasks[taskId].func = Task_NetworkTaskLoop;
}
-
Don't directly kill the task inside your Process() method. Set sSendRecvMgr state to done e.g.
sSendRecvMgr.state = NET_CONN_STATE_DONE;
So the connection will finish properly. (Although you will still need to callNetConnDisableSerial();
in your EndConnectionFunction if you want serial transmission to stop) -
ALWAYS validate the data you got back from the server before using it. If the network connection drops at the wrong point the GBA could pull back bad data from the wii. I strongly recommend setting a special var e.g
gSpecialVar_0x8003
to0
during setup and only setting it to1
once you have made sure the data is ok. If it is still when the task ends0
then branch to an error message in your script.inc
Before we get started there are few important things to understand so you know what is going on under the hood.
1.. How is the joybus connection working?
- The wii is master in this connection and is basically polling the gba for data. It does this frequently after it when the gba reports itself when loading the bios (this is why you need to reset to start the connection)
- By configuring REG_RCNT we can make it so that any data written to JOY_TRANS_L or JOY_TRANS_H is sent to the wii. So when talking to the wii we send 4 bytes at a time.
- By reading from JOY_RECV we can get data back (also 4 bytes).
- Read and write opperations cannot happen at the same time.
- Any time we want to send more than 4 bytes at a time we need to be careful. So we try and keep importaint instructions to 4 bytes.
2.. How does the implementation work?
- When the gba is detected the wii allocated a 4kb array in memory for that gba
- The gba can read and write data to that 4kb array using commands
- The gba can request a section of that array be sent to the server using another command
- The server can also write data to that array while the connection is open
- When the gba sends 12 02 00 00 it will start a tcp server using the first 14 bytes of the array as the ip and port
- Messages between the wii and the TCP server are capped at 1kb in size
- If you write/read past the end of the memory the wii may ignore the request completely or return no response
There are a few special commands but the main ones to be aware of are:
Receive data from the wii's memory array:
RECV | 25 YY XX XX | YY * 16 is the starting offset to receive data from in the array | XX XX is the length of data to receive
Example: 0x25F00004 = please send me 4 bytes of data from your array, starting from 3840 (0xf0 * 16). i.e array[3840],array[3841],array[3842] and array[3844]
Send data to the wii's memory array:
SEND | 15 YY XX XX | YY * 16 is the starting offset to write data to in the array | XX XX is the length of data to write
Example: 0x15000008 = the next bytes you read from me should be written to the very start of your array
Ask the wii to send data from its array to the server:
TRAN | 13 YY XX XX | YY * 16 is the starting offset to transmit data from | XX XX is the length of data to send to the server
Example 0x13010010 = please send all the data from index 16 (01 * 16) to index 32 (16 + 0x10) to the server
disableChecks
when using these commands and enable them again after.
For more info on why its setup like this you can see the docs in network.h
Fortunately there are a couple of simple methods you can call to setup the next opperation in your ProcessFunction
void configureSendRecvMgr(u16 cmd, vu32 * dataStart, u16 length, u8 state, u8 nextProcessStep);
void configureSendRecvMgrChunked(u16 cmd, vu32 * dataStart, u16 length, u8 state, u8 nextProcessStep, u8 chunkSize);
These methods both do basically the same thing. The difference being that configureSendRecvMgr
validated the data once at the end whereas configureSendRecvMgrChunked
validates sections of the data as it's sent. This means configureSendRecvMgrChunked
has a larger overhead (as it needs to send a bunch more validation messages) but unlike configureSendRecvMgr
if a transmission fails it won't have to start from scratch.
Basically if you are sending less than 16 bytes use configureSendRecvMgr
if you are sending more then use configureSendRecvMgrChunked
. The minimum and recommended chunk size is 16 because the transfer commands don't allow writing data at a more granular level.
Let's take a look at a simplified example of the ProcessFunction steps to download a custom mart:
Here some chars are written into gStringVar3[0]
0x1502 means we are sending data to position 16*2 in the wii's array.
The data we are sending is gStringVar3 and we are sending 4 bytes
When the task is done the state should update to DOWNLOAD_MART_TRANSMIT_REQUEST
case DOWNLOAD_MART_SEND_REQUEST:
gStringVar3[0] = 'M'; gStringVar3[1] = 'A'; gStringVar3[2] = '_'; gStringVar3[3] = '1';
configureSendRecvMgr(0x1502, (vu32 *) &gStringVar3[0], 4, NET_CONN_STATE_SEND, DOWNLOAD_MART_TRANSMIT_REQUEST);
break;
Note we are setting disableChecks
because the don't work for transmit requests
We then update the retryPoint (so if the connection fails we don't go back to DOWNLOAD_MART_SEND_REQUEST)
0x1302 means we are asking the wii to transmit data starting from 16*2 (where we wrote to before). We are asking transmit bytes
When the task is done it should update the state to DOWNLOAD_MART_WAIT_FOR_SERVER
case DOWNLOAD_MART_TRANSMIT_REQUEST:
sSendRecvMgr.disableChecks = TRUE;
sSendRecvMgr.retryPoint = DOWNLOAD_MART_TRANSMIT_REQUEST;
configureSendRecvMgr(0x1302, 0, 4, NET_CONN_STATE_SEND, DOWNLOAD_MART_WAIT_FOR_SERVER);
break;
The network wait checks don't actually work yet. Currently we just play an animation that lasts a few seconds and hope it's finished by the time we try and read data 🤞
When the wait is done is should move to DOWNLOAD_MART_RECEIVE_DATA
case DOWNLOAD_MART_WAIT_FOR_SERVER:
// Some waiting animation code here
sSendRecvMgr.nextProcessStep = DOWNLOAD_MART_RECEIVE_DATA;
break;
First we need to remember to re-enable check (as we disabled them for the transmit)
Then we clear gStringVar3 because we are setting it as the address to receive data to
0x25F0 means we are asing the wii to send us data in it's array starting from 0xF0 * 16 (the is where the server wrote data to). We are asking for 16 bytes of data
case DOWNLOAD_MART_RECEIVE_DATA:
sSendRecvMgr.disableChecks = FALSE;
CpuFill32(0, &gStringVar3, sizeof(gStringVar3));
configureSendRecvMgrChunked(0x25F0, (vu32 *) &gStringVar3[0], 16, NET_CONN_STATE_RECEIVE, DOWNLOAD_MART_FINISH, MINIMUM_CHUNK_SIZE);
break;
🗒️ I strongly recommend always making chunkSize
16. And alway making sure you are transmitting a multiple of 4 bytes as nothing else has really been tested
By default the rom is configured to try and connect to localhost port 9000. This config is passed from the rom when trying to start a connection to a server (although there is a mechanism to force a different address to be used in the wii channel).
You can configure the game to connect to your server by changing the values of NET_SERVER_ADDR_LENGTH
and sNetServerAddr
in net_conn.c
. Your server must accept TCP connections using an IPV4 address (IPV6 is not supported). However as of channel v0.1.1 it will attempt to resolve domain names to an IPV4 address. Some examples of values that could be used for sNetServerAddr
are:
- 127.0.0.1:9000
- example.com:8089 (> v0.1.1)
🗒️ The maximum address length (including port) is 32 characters. i.e NET_SERVER_ADDR_LENGTH
should never be more than 32. Personally I recommend trying to keep it less than 16 chars. I also think it needs to only be letters with an 8 bit ascii encoding so things like .みんな
probably will not work.
Additionally an override ip address can be configured on the wii using the Netowrk Config
menu. Once a sucessful connection has been made from this menu all NEW gba connections will use the override address. This will not effect old connections (even if they were unsucessful) i.e you can only configure the override address while the Wii still reads 'Waiting'. Once it shows the player name the the network config from the rom will always be used (until the channel is restarted).
Other disassembly and/or decompilation projects:
- Pokémon Red and Blue
- Pokémon Gold and Silver (Space World '97 demo)
- Pokémon Yellow
- Pokémon Trading Card Game
- Pokémon Pinball
- Pokémon Stadium
- Pokémon Gold and Silver
- Pokémon Crystal
- Pokémon Ruby and Sapphire
- Pokémon Pinball: Ruby & Sapphire
- Pokémon FireRed and LeafGreen
- Pokémon Mystery Dungeon: Red Rescue Team