World Driver Championship Car & Track mod – Take 2

Yonks ago (2013 to be precise), I was working on a project which I hoped eventually would enable me to import new car and track models into the N64 game World Driver Championship, I really want to make a Speed Racer mod for the game, it’s slidy handling feels like a good fit to me.
I got as far as partially decoding the track and car data formats, and had a basic C# track viewer up and running (barely, my understanding of OpenGL was… tenuous). Then it fell by the wayside as projects tend to do from time to time and gathered dust…WorldDriverChampionshipuntil now!

Starting from scratch (with the help of my old notes, thank-you past me!) in D, and modern OpenGL the project lives once again.
I’ve started from the other side this time, doing the car model viewer first. So far I have models displaying, and I think I’ve worked out the details for textures. There are some small sections of data in the car blobs that I haven’t figured out yet, but I wonder if they might be to do with in-game things like handling, which I haven’t tested yet.wdc_23_2_16

Nintendo 64 debugger output

A bunch of N64 games have dummied functions left in which used to send strings out to a console on the debugging hardware. Now they typically do nothing, but some games still call them a lot. I hacked together a little something to pull strings out of PJ64 when the function is called. It’s not pretty, I borrowed code from wherever I could find it and it doesn’t even work 100% but it does print out some strings from some games. Some are missed because multiple calls to the function in a row happen too fast to be seen. I started implementing support for a version of the function found in NHL Breakaway ’98 but it’s not in working form in the code here.

PJ64 must be running in Interpreter mode for this to work.
It probably won’t work unmodified on anyone else’s system as it’s using a hardcoded address.

A much better option would be to write this into an emulator 🙂 Maybe some day.

// N64 Debug Console.cpp : main project file.

#include <windows.h>
#include <stdio.h>
#include <Psapi.h>
#include <iostream>
#include <string>
#include <Tlhelp32.h>

using namespace std;
#define PROCESS_NAME "Project64.exe"
#define MAX_PROCESS 100
#define BASE_ADDRESS 0x52ea0000
HANDLE pPJ64Process;
int MAX_MSG_LENGTH = 100;
enum FormatType {typeD,	typeF, typeS};
enum FunctionType {bHarvest, nHL};

void GetProcess( char *pName )
{
		int pCount = 0;
        PROCESSENTRY32 pEntry = { 0 };

        pEntry.dwSize = sizeof(PROCESSENTRY32);

        HANDLE pSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); //Create an 32SnapShot.

        if(Process32First( pSnapShot, &pEntry ))
        {
                while( Process32Next( pSnapShot, &pEntry ) && pCount < MAX_PROCESS-1 ) //Loop through all processes.
                {
                        HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS,FALSE, pEntry.th32ProcessID ); //Open the current Process.

                        if(!strcmpi( pEntry.szExeFile, PROCESS_NAME ) ) //Check if the current process is the PROCESS_NAME
                        {
                                pPJ64Process = hProcess;
								cout << "Found PJ64" << "\n";
								pCount++;
                        }
                }
        }
        CloseHandle( pSnapShot ); //Close the SnapShot.
}

void endianReverse(byte *bytes)
{
    byte temp;
	byte temp2;
	int i = 0;
	while (i <= (MAX_MSG_LENGTH - 4))
	{
		temp = bytes[i];
		temp2 = bytes[i+1];

		bytes[i] = bytes[i+3];
		bytes[i+1] = bytes[i+2];
		bytes[i+2] = temp2;
		bytes[i+3] = temp;
		i+=4;
	}
}

int findFormatters(byte *rawMsg, FormatType *types)
{
	int numFormatters = 0;
	int i = 0;
	while (rawMsg[i] != 0)
	{
		if ((char)rawMsg[i] == '%')
		{
			while ((char)rawMsg[i] != 'd' && (char)rawMsg[i] != 'x'	&& (char)rawMsg[i] != 'f' && (char)rawMsg[i] != 's')
			{
				//if (rawMsg[i] == 0) break;
				i++;
			}
			if ((char)rawMsg[i] == 'd' || (char)rawMsg[i] == 'x') types[numFormatters] = typeD;
			else if ((char)rawMsg[i] == 'f') types[numFormatters] = typeF;
			else if ((char)rawMsg[i] == 's') types[numFormatters] = typeS;
			numFormatters++;
		}
		i++;
	}

	return numFormatters;
}

byte* getSubString(byte* subString, int pointer)
{
	int offset = pointer & 0x00FFFFFF;
	ReadProcessMemory(pPJ64Process,(void*)(BASE_ADDRESS + offset),subString,MAX_MSG_LENGTH,0);
	return subString;
}

void messageFormat(byte *rawMsg,  byte *param1, byte *param2, byte *param3, int p1, int p2, int p3)
{
	int i = 0;
	byte *subString = new byte[MAX_MSG_LENGTH];
	FormatType *formatTypes = new FormatType[3];
	int numFormatters = findFormatters(rawMsg, formatTypes);

	//Should only pass in paramXs, then create pXs in here

	if (numFormatters == 0)
	{
		printf((char*)rawMsg);
	}
	else if (numFormatters == 1)
	{
		if (formatTypes[0] == typeD)
		{
			printf((char*)rawMsg, p1);
		}
		else if (formatTypes[0] == typeF)
		{
			printf((char*)rawMsg, (float)*param1);
		}
		else if (formatTypes[0] == typeS)
		{
			subString = getSubString(subString, p1);
			endianReverse(subString);
			printf((char*)rawMsg, (char*)subString);
		}
		else
		{
			cout << "unknown % format (1)" << "\n";
		}
	}
	else if (numFormatters == 2)
	{
		if (formatTypes[0] == typeD && formatTypes[1] == typeD)
		{
			printf((char*)rawMsg, p1, p2);
		}
		else if (formatTypes[0] == typeD && formatTypes[1] == typeS)
		{
            subString = getSubString(subString, p2);
            endianReverse(subString);
			printf((char*)rawMsg, p1, (char*)subString);
		}
		else if (formatTypes[0] == typeD && formatTypes[1] == typeF)
		{
			printf((char*)rawMsg, p1, (float)*param2);
		}
		else if (formatTypes[0] == typeF && formatTypes[1] == typeF)
		{
			printf((char*)rawMsg, (float)*param1, (float)*param2);
		}
		else
		{
			cout << "unknown % format (2): " << (char*)rawMsg << "\n";
		}
	}
	else
	{
		if (formatTypes[0] == typeD && formatTypes[1] == typeD && formatTypes[2] == typeD)
		{
			//printf((char*)rawMsg, (int)*param1, (int)*param2, (int)*param3);
			printf((char*)rawMsg, p1, p2, p3);
		}
		else if (formatTypes[0] == typeF && formatTypes[1] == typeF && formatTypes[2] == typeF)
		{
			printf((char*)rawMsg, (float)*param1, (float)*param2, (float)*param3);
		}
		else if (formatTypes[0] == typeD && formatTypes[1] == typeD && formatTypes[2] == typeS)
		{
            subString = getSubString(subString, p3);
            endianReverse(subString);
			printf((char*)rawMsg, p1, p2, (char*)subString);
		}
		else
		{
			cout << "unknown % format (3)" << "\n";
		}
	}
	//cout << endl;
}

DWORD GetModuleBase(HANDLE hProc, string &sModuleName)
{
   HMODULE *hModules;
   char szBuf[50];
   DWORD cModules;
   DWORD dwBase = 0;
   //------

   EnumProcessModules(hProc, hModules, 0, &cModules);
   hModules = new HMODULE[cModules/sizeof(HMODULE)];

   if(EnumProcessModules(hProc, hModules, cModules/sizeof(HMODULE), &cModules)) {
      for(int i = 0; i < cModules/sizeof(HMODULE); i++) {
         if(GetModuleBaseName(hProc, hModules[i], szBuf, sizeof(szBuf))) {
            if(sModuleName.compare(szBuf) == 0) {
               dwBase = (DWORD)hModules[i];
               break;
            }
         }
      }
   }

   delete[] hModules;

   return dwBase;
}

int main()
{
    string sConsoleMessage = "";

	//DWORD baseAddress = (DWORD)keke;
	DWORD baseAddress = 0x52ea0000;
	DWORD address;
	//DWORD address = baseAddress + 0x6f620; // World Driver
	DWORD readAddress;
	byte *rawMsg = new byte[MAX_MSG_LENGTH];
	byte *param1 = new byte[4];
	byte *param2 = new byte[4];
	byte *param3 = new byte[4];
	byte newData[] = {0x00,0x80,0x0c,0x3c,
	0x2c,0xf6,0x87,0xad,
	0x28,0xf6,0x86,0xad,
	0x20,0xf6,0x84,0xad,
	0x24,0xf6,0x85,0xad,
	0x08,0x00,0xe0,0x03,
	0x08,0x00,0xbd,0x27};
	byte newData2[] = {0x08,0x00,0xe0,0x03,0x00,0x00,0x00,0x00};
	int p1;
	int p2;
	int p3;
	int msgPointer = 0;
	int oldMsgPointer = 1;
	bool foundFunction = false;
	FunctionType funcType = bHarvest;

	int *memBlock = new int[16];

    GetProcess( PROCESS_NAME );
    //DWORD jiggle = GetModuleBase(pPJ64Process, string("heheh"));
	if(!pPJ64Process)
	{
		cout <<"Could not get handle!\n"; 		cin.get(); 	} 	else 	{ 		// Find print function 		int matching = 0; 		int x = 0; 		while (!foundFunction) 		{         	ReadProcessMemory(pPJ64Process,(void*)(baseAddress + x),memBlock,(sizeof(int) * 16),0);         	if (memBlock[0] == 0x27bdfff8) matching++;         	if (memBlock[1] == 0xafa40008) matching++;         	if (memBlock[2] == 0xafa5000c) matching++;         	if (memBlock[3] == 0xafa60010) matching++;         	if (memBlock[4] == 0xafa70014) matching++;         	if (memBlock[5] == 0x03e00008) matching++;         	if (memBlock[6] == 0x27bd0008) matching++;         	if (memBlock[7] == 0x27bdfff8) matching++;         	if (memBlock[8] == 0xafa40008) matching++;         	if (memBlock[9] == 0xafa5000c) matching++;         	if (memBlock[10] == 0xafa60010) matching++;         	if (memBlock[11] == 0xafa70014) matching++;         	if (memBlock[12] == 0x03e00008) matching++;         	if (memBlock[13] == 0x27bd0008) matching++;         	if (memBlock[14] == 0x00000000) matching++;         	if (memBlock[15] == 0x00000000) matching++;         	         	if (matching == 16)         	{ 				foundFunction = true; 			} 			else 			{ 				matching = 0; 				x += 4; 			} 			if (x == 0x00400000) break; 		} 		 		if (!foundFunction) 		{ 			matching = 0; 			x = 0; 		} 		while (!foundFunction) // Look for NHL type function 		{             ReadProcessMemory(pPJ64Process,(void*)(baseAddress + x),memBlock,(sizeof(int) * 16),0);         	if (memBlock[0] == 0x27bdff98) matching++;         	if (memBlock[1] == 0x27bd0068) matching++;         	if (memBlock[2] == 0x03e00008) matching++;         	if (memBlock[3] == 0x00000000) matching++;         	if (matching == 4)         	{ 				foundFunction = true; 				funcType = nHL; 			} 			else 			{ 				matching = 0; 				x += 4; 			} 			if (x == 0x00400000) break; 		} 		 		if (foundFunction) 		{ 			int y = x; 			if (funcType == nHL) 			{ 				y -= 0x1c; 			} 			if (y & 0x00008000) y+=0x10000; 			y += 8; 			newData[0] = (y >> 16) & 0xFF;
			newData[0xd] = (y >> 8) & 0xFF;
			newData[0xc] = y & 0xFF;
			newData[0x11] = ((y + 4) >> 8) & 0xFF;
			newData[0x10] = (y + 4) & 0xFF;
			newData[0x9] = ((y + 8) >> 8) & 0xFF;
			newData[0x8] = (y + 8) & 0xFF;
			newData[0x5] = ((y + 12) >> 8) & 0xFF;
			newData[0x4] = (y + 12) & 0xFF;
			cout << "Function found at: " << hex << x << endl;
			cout << "Function type: " << funcType << endl;
			if (funcType == bHarvest)
			{
				WriteProcessMemory(pPJ64Process,(void*)(baseAddress + x + 0x1c),&newData,28,NULL);
				WriteProcessMemory(pPJ64Process,(void*)(baseAddress + x),&newData2,8,NULL);
				WriteProcessMemory(pPJ64Process,(void*)(baseAddress + x + 0x30),&newData2,8,NULL);
			}
			else if (funcType == nHL)
			{
				WriteProcessMemory(pPJ64Process,(void*)(baseAddress + x),&newData,28,NULL);
			}

			address = baseAddress + x + 0x8;
		}
		else
		{
			cout << "Didn't find function to hook";
		}

		while(1)
		{
			if (funcType == bHarvest)
			{
				ReadProcessMemory(pPJ64Process,(void*)address,&msgPointer,sizeof(msgPointer),0);
			}
			else if (funcType == nHL)
			{
                ReadProcessMemory(pPJ64Process,(void*)(address + 4),&msgPointer,sizeof(msgPointer),0);
			}

			if ((msgPointer & 0xff000000) == 0x80000000)
			{
				msgPointer = msgPointer & 0x0FFFFFFF;

				if (msgPointer != oldMsgPointer) // New Message
				{
					//cout << hex << msgPointer << "\n";
                    readAddress = msgPointer + baseAddress;
					ReadProcessMemory(pPJ64Process,(void*)readAddress,rawMsg,MAX_MSG_LENGTH,0);

					if (funcType == bHarvest)
					{
						ReadProcessMemory(pPJ64Process,(void*)(address + 4),param1,4,0);
						ReadProcessMemory(pPJ64Process,(void*)(address + 4),&p1,sizeof(int),0);
					}
					else if (funcType == nHL)
					{
                        ReadProcessMemory(pPJ64Process,(void*)address,param1,4,0);
						ReadProcessMemory(pPJ64Process,(void*)address,&p1,sizeof(int),0);
					}
					ReadProcessMemory(pPJ64Process,(void*)(address + 8),param2,4,0);
					ReadProcessMemory(pPJ64Process,(void*)(address + 12),param3,4,0);
					ReadProcessMemory(pPJ64Process,(void*)(address + 4),&p1,sizeof(int),0);
					ReadProcessMemory(pPJ64Process,(void*)(address + 8),&p2,sizeof(int),0);
					ReadProcessMemory(pPJ64Process,(void*)(address + 12),&p3,sizeof(int),0);

					endianReverse(rawMsg);
					messageFormat(rawMsg, param1, param2, param3, p1, p2, p3);
					oldMsgPointer = msgPointer;
				}
			}
		//Sleep(100);
		}
		return 0;
	}
}