{"id":4,"date":"2013-05-05T00:52:38","date_gmt":"2013-05-05T00:52:38","guid":{"rendered":"http:\/\/jaytheham.com\/tink\/?p=4"},"modified":"2013-05-05T00:52:38","modified_gmt":"2013-05-05T00:52:38","slug":"nintendo-64-debugger-output","status":"publish","type":"post","link":"http:\/\/jaytheham.com\/tink\/?p=4","title":{"rendered":"Nintendo 64 debugger output"},"content":{"rendered":"<p>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&#8217;s not pretty, I borrowed code from wherever I could find it and it doesn&#8217;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 &#8217;98 but it&#8217;s not in working form in the code here.<\/p>\n<p>PJ64 must be running in Interpreter mode for this to work.<br \/>\nIt probably won&#8217;t work unmodified on anyone else&#8217;s system as it&#8217;s using a hardcoded address.<\/p>\n<p>A much better option would be to write this into an emulator \ud83d\ude42 Maybe some day.<\/p>\n<p><iframe loading=\"lazy\" width=\"625\" height=\"352\" src=\"http:\/\/www.youtube.com\/embed\/txGVc99QcaM?feature=oembed\" frameborder=\"0\" allowfullscreen><\/iframe><\/p>\n<p><iframe loading=\"lazy\" width=\"625\" height=\"352\" src=\"http:\/\/www.youtube.com\/embed\/QtNYIRLzceE?feature=oembed\" frameborder=\"0\" allowfullscreen><\/iframe><\/p>\n<pre>\/\/ N64 Debug Console.cpp : main project file.\r\n\r\n#include &lt;windows.h&gt;\r\n#include &lt;stdio.h&gt;\r\n#include &lt;Psapi.h&gt;\r\n#include &lt;iostream&gt;\r\n#include &lt;string&gt;\r\n#include &lt;Tlhelp32.h&gt;\r\n\r\nusing namespace std;\r\n#define PROCESS_NAME \"Project64.exe\"\r\n#define MAX_PROCESS 100\r\n#define BASE_ADDRESS 0x52ea0000\r\nHANDLE pPJ64Process;\r\nint MAX_MSG_LENGTH = 100;\r\nenum FormatType {typeD,\ttypeF, typeS};\r\nenum FunctionType {bHarvest, nHL};\r\n\r\nvoid GetProcess( char *pName )\r\n{\r\n\t\tint pCount = 0;\r\n        PROCESSENTRY32 pEntry = { 0 };\r\n\r\n        pEntry.dwSize = sizeof(PROCESSENTRY32);\r\n\r\n        HANDLE pSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); \/\/Create an 32SnapShot.\r\n\r\n        if(Process32First( pSnapShot, &amp;pEntry ))\r\n        {\r\n                while( Process32Next( pSnapShot, &amp;pEntry ) &amp;&amp; pCount &lt; MAX_PROCESS-1 ) \/\/Loop through all processes.\r\n                {\r\n                        HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS,FALSE, pEntry.th32ProcessID ); \/\/Open the current Process.\r\n\r\n                        if(!strcmpi( pEntry.szExeFile, PROCESS_NAME ) ) \/\/Check if the current process is the PROCESS_NAME\r\n                        {\r\n                                pPJ64Process = hProcess;\r\n\t\t\t\t\t\t\t\tcout &lt;&lt; \"Found PJ64\" &lt;&lt; \"\\n\";\r\n\t\t\t\t\t\t\t\tpCount++;\r\n                        }\r\n                }\r\n        }\r\n        CloseHandle( pSnapShot ); \/\/Close the SnapShot.\r\n}\r\n\r\nvoid endianReverse(byte *bytes)\r\n{\r\n    byte temp;\r\n\tbyte temp2;\r\n\tint i = 0;\r\n\twhile (i &lt;= (MAX_MSG_LENGTH - 4))\r\n\t{\r\n\t\ttemp = bytes[i];\r\n\t\ttemp2 = bytes[i+1];\r\n\r\n\t\tbytes[i] = bytes[i+3];\r\n\t\tbytes[i+1] = bytes[i+2];\r\n\t\tbytes[i+2] = temp2;\r\n\t\tbytes[i+3] = temp;\r\n\t\ti+=4;\r\n\t}\r\n}\r\n\r\nint findFormatters(byte *rawMsg, FormatType *types)\r\n{\r\n\tint numFormatters = 0;\r\n\tint i = 0;\r\n\twhile (rawMsg[i] != 0)\r\n\t{\r\n\t\tif ((char)rawMsg[i] == '%')\r\n\t\t{\r\n\t\t\twhile ((char)rawMsg[i] != 'd' &amp;&amp; (char)rawMsg[i] != 'x'\t&amp;&amp; (char)rawMsg[i] != 'f' &amp;&amp; (char)rawMsg[i] != 's')\r\n\t\t\t{\r\n\t\t\t\t\/\/if (rawMsg[i] == 0) break;\r\n\t\t\t\ti++;\r\n\t\t\t}\r\n\t\t\tif ((char)rawMsg[i] == 'd' || (char)rawMsg[i] == 'x') types[numFormatters] = typeD;\r\n\t\t\telse if ((char)rawMsg[i] == 'f') types[numFormatters] = typeF;\r\n\t\t\telse if ((char)rawMsg[i] == 's') types[numFormatters] = typeS;\r\n\t\t\tnumFormatters++;\r\n\t\t}\r\n\t\ti++;\r\n\t}\r\n\r\n\treturn numFormatters;\r\n}\r\n\r\nbyte* getSubString(byte* subString, int pointer)\r\n{\r\n\tint offset = pointer &amp; 0x00FFFFFF;\r\n\tReadProcessMemory(pPJ64Process,(void*)(BASE_ADDRESS + offset),subString,MAX_MSG_LENGTH,0);\r\n\treturn subString;\r\n}\r\n\r\nvoid messageFormat(byte *rawMsg,  byte *param1, byte *param2, byte *param3, int p1, int p2, int p3)\r\n{\r\n\tint i = 0;\r\n\tbyte *subString = new byte[MAX_MSG_LENGTH];\r\n\tFormatType *formatTypes = new FormatType[3];\r\n\tint numFormatters = findFormatters(rawMsg, formatTypes);\r\n\r\n\t\/\/Should only pass in paramXs, then create pXs in here\r\n\r\n\tif (numFormatters == 0)\r\n\t{\r\n\t\tprintf((char*)rawMsg);\r\n\t}\r\n\telse if (numFormatters == 1)\r\n\t{\r\n\t\tif (formatTypes[0] == typeD)\r\n\t\t{\r\n\t\t\tprintf((char*)rawMsg, p1);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeF)\r\n\t\t{\r\n\t\t\tprintf((char*)rawMsg, (float)*param1);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeS)\r\n\t\t{\r\n\t\t\tsubString = getSubString(subString, p1);\r\n\t\t\tendianReverse(subString);\r\n\t\t\tprintf((char*)rawMsg, (char*)subString);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tcout &lt;&lt; \"unknown % format (1)\" &lt;&lt; \"\\n\";\r\n\t\t}\r\n\t}\r\n\telse if (numFormatters == 2)\r\n\t{\r\n\t\tif (formatTypes[0] == typeD &amp;&amp; formatTypes[1] == typeD)\r\n\t\t{\r\n\t\t\tprintf((char*)rawMsg, p1, p2);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeD &amp;&amp; formatTypes[1] == typeS)\r\n\t\t{\r\n            subString = getSubString(subString, p2);\r\n            endianReverse(subString);\r\n\t\t\tprintf((char*)rawMsg, p1, (char*)subString);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeD &amp;&amp; formatTypes[1] == typeF)\r\n\t\t{\r\n\t\t\tprintf((char*)rawMsg, p1, (float)*param2);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeF &amp;&amp; formatTypes[1] == typeF)\r\n\t\t{\r\n\t\t\tprintf((char*)rawMsg, (float)*param1, (float)*param2);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tcout &lt;&lt; \"unknown % format (2): \" &lt;&lt; (char*)rawMsg &lt;&lt; \"\\n\";\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif (formatTypes[0] == typeD &amp;&amp; formatTypes[1] == typeD &amp;&amp; formatTypes[2] == typeD)\r\n\t\t{\r\n\t\t\t\/\/printf((char*)rawMsg, (int)*param1, (int)*param2, (int)*param3);\r\n\t\t\tprintf((char*)rawMsg, p1, p2, p3);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeF &amp;&amp; formatTypes[1] == typeF &amp;&amp; formatTypes[2] == typeF)\r\n\t\t{\r\n\t\t\tprintf((char*)rawMsg, (float)*param1, (float)*param2, (float)*param3);\r\n\t\t}\r\n\t\telse if (formatTypes[0] == typeD &amp;&amp; formatTypes[1] == typeD &amp;&amp; formatTypes[2] == typeS)\r\n\t\t{\r\n            subString = getSubString(subString, p3);\r\n            endianReverse(subString);\r\n\t\t\tprintf((char*)rawMsg, p1, p2, (char*)subString);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tcout &lt;&lt; \"unknown % format (3)\" &lt;&lt; \"\\n\";\r\n\t\t}\r\n\t}\r\n\t\/\/cout &lt;&lt; endl;\r\n}\r\n\r\nDWORD GetModuleBase(HANDLE hProc, string &amp;sModuleName)\r\n{\r\n   HMODULE *hModules;\r\n   char szBuf[50];\r\n   DWORD cModules;\r\n   DWORD dwBase = 0;\r\n   \/\/------\r\n\r\n   EnumProcessModules(hProc, hModules, 0, &amp;cModules);\r\n   hModules = new HMODULE[cModules\/sizeof(HMODULE)];\r\n\r\n   if(EnumProcessModules(hProc, hModules, cModules\/sizeof(HMODULE), &amp;cModules)) {\r\n      for(int i = 0; i &lt; cModules\/sizeof(HMODULE); i++) {\r\n         if(GetModuleBaseName(hProc, hModules[i], szBuf, sizeof(szBuf))) {\r\n            if(sModuleName.compare(szBuf) == 0) {\r\n               dwBase = (DWORD)hModules[i];\r\n               break;\r\n            }\r\n         }\r\n      }\r\n   }\r\n\r\n   delete[] hModules;\r\n\r\n   return dwBase;\r\n}\r\n\r\nint main()\r\n{\r\n    string sConsoleMessage = \"\";\r\n\r\n\t\/\/DWORD baseAddress = (DWORD)keke;\r\n\tDWORD baseAddress = 0x52ea0000;\r\n\tDWORD address;\r\n\t\/\/DWORD address = baseAddress + 0x6f620; \/\/ World Driver\r\n\tDWORD readAddress;\r\n\tbyte *rawMsg = new byte[MAX_MSG_LENGTH];\r\n\tbyte *param1 = new byte[4];\r\n\tbyte *param2 = new byte[4];\r\n\tbyte *param3 = new byte[4];\r\n\tbyte newData[] = {0x00,0x80,0x0c,0x3c,\r\n\t0x2c,0xf6,0x87,0xad,\r\n\t0x28,0xf6,0x86,0xad,\r\n\t0x20,0xf6,0x84,0xad,\r\n\t0x24,0xf6,0x85,0xad,\r\n\t0x08,0x00,0xe0,0x03,\r\n\t0x08,0x00,0xbd,0x27};\r\n\tbyte newData2[] = {0x08,0x00,0xe0,0x03,0x00,0x00,0x00,0x00};\r\n\tint p1;\r\n\tint p2;\r\n\tint p3;\r\n\tint msgPointer = 0;\r\n\tint oldMsgPointer = 1;\r\n\tbool foundFunction = false;\r\n\tFunctionType funcType = bHarvest;\r\n\r\n\tint *memBlock = new int[16];\r\n\r\n    GetProcess( PROCESS_NAME );\r\n    \/\/DWORD jiggle = GetModuleBase(pPJ64Process, string(\"heheh\"));\r\n\tif(!pPJ64Process)\r\n\t{\r\n\t\tcout &lt;&lt;\"Could not get handle!\\n\"; \t\tcin.get(); \t} \telse \t{ \t\t\/\/ Find print function \t\tint matching = 0; \t\tint x = 0; \t\twhile (!foundFunction) \t\t{         \tReadProcessMemory(pPJ64Process,(void*)(baseAddress + x),memBlock,(sizeof(int) * 16),0);         \tif (memBlock[0] == 0x27bdfff8) matching++;         \tif (memBlock[1] == 0xafa40008) matching++;         \tif (memBlock[2] == 0xafa5000c) matching++;         \tif (memBlock[3] == 0xafa60010) matching++;         \tif (memBlock[4] == 0xafa70014) matching++;         \tif (memBlock[5] == 0x03e00008) matching++;         \tif (memBlock[6] == 0x27bd0008) matching++;         \tif (memBlock[7] == 0x27bdfff8) matching++;         \tif (memBlock[8] == 0xafa40008) matching++;         \tif (memBlock[9] == 0xafa5000c) matching++;         \tif (memBlock[10] == 0xafa60010) matching++;         \tif (memBlock[11] == 0xafa70014) matching++;         \tif (memBlock[12] == 0x03e00008) matching++;         \tif (memBlock[13] == 0x27bd0008) matching++;         \tif (memBlock[14] == 0x00000000) matching++;         \tif (memBlock[15] == 0x00000000) matching++;         \t         \tif (matching == 16)         \t{ \t\t\t\tfoundFunction = true; \t\t\t} \t\t\telse \t\t\t{ \t\t\t\tmatching = 0; \t\t\t\tx += 4; \t\t\t} \t\t\tif (x == 0x00400000) break; \t\t} \t\t \t\tif (!foundFunction) \t\t{ \t\t\tmatching = 0; \t\t\tx = 0; \t\t} \t\twhile (!foundFunction) \/\/ Look for NHL type function \t\t{             ReadProcessMemory(pPJ64Process,(void*)(baseAddress + x),memBlock,(sizeof(int) * 16),0);         \tif (memBlock[0] == 0x27bdff98) matching++;         \tif (memBlock[1] == 0x27bd0068) matching++;         \tif (memBlock[2] == 0x03e00008) matching++;         \tif (memBlock[3] == 0x00000000) matching++;         \tif (matching == 4)         \t{ \t\t\t\tfoundFunction = true; \t\t\t\tfuncType = nHL; \t\t\t} \t\t\telse \t\t\t{ \t\t\t\tmatching = 0; \t\t\t\tx += 4; \t\t\t} \t\t\tif (x == 0x00400000) break; \t\t} \t\t \t\tif (foundFunction) \t\t{ \t\t\tint y = x; \t\t\tif (funcType == nHL) \t\t\t{ \t\t\t\ty -= 0x1c; \t\t\t} \t\t\tif (y &amp; 0x00008000) y+=0x10000; \t\t\ty += 8; \t\t\tnewData[0] = (y &gt;&gt; 16) &amp; 0xFF;\r\n\t\t\tnewData[0xd] = (y &gt;&gt; 8) &amp; 0xFF;\r\n\t\t\tnewData[0xc] = y &amp; 0xFF;\r\n\t\t\tnewData[0x11] = ((y + 4) &gt;&gt; 8) &amp; 0xFF;\r\n\t\t\tnewData[0x10] = (y + 4) &amp; 0xFF;\r\n\t\t\tnewData[0x9] = ((y + 8) &gt;&gt; 8) &amp; 0xFF;\r\n\t\t\tnewData[0x8] = (y + 8) &amp; 0xFF;\r\n\t\t\tnewData[0x5] = ((y + 12) &gt;&gt; 8) &amp; 0xFF;\r\n\t\t\tnewData[0x4] = (y + 12) &amp; 0xFF;\r\n\t\t\tcout &lt;&lt; \"Function found at: \" &lt;&lt; hex &lt;&lt; x &lt;&lt; endl;\r\n\t\t\tcout &lt;&lt; \"Function type: \" &lt;&lt; funcType &lt;&lt; endl;\r\n\t\t\tif (funcType == bHarvest)\r\n\t\t\t{\r\n\t\t\t\tWriteProcessMemory(pPJ64Process,(void*)(baseAddress + x + 0x1c),&amp;newData,28,NULL);\r\n\t\t\t\tWriteProcessMemory(pPJ64Process,(void*)(baseAddress + x),&amp;newData2,8,NULL);\r\n\t\t\t\tWriteProcessMemory(pPJ64Process,(void*)(baseAddress + x + 0x30),&amp;newData2,8,NULL);\r\n\t\t\t}\r\n\t\t\telse if (funcType == nHL)\r\n\t\t\t{\r\n\t\t\t\tWriteProcessMemory(pPJ64Process,(void*)(baseAddress + x),&amp;newData,28,NULL);\r\n\t\t\t}\r\n\r\n\t\t\taddress = baseAddress + x + 0x8;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tcout &lt;&lt; \"Didn't find function to hook\";\r\n\t\t}\r\n\r\n\t\twhile(1)\r\n\t\t{\r\n\t\t\tif (funcType == bHarvest)\r\n\t\t\t{\r\n\t\t\t\tReadProcessMemory(pPJ64Process,(void*)address,&amp;msgPointer,sizeof(msgPointer),0);\r\n\t\t\t}\r\n\t\t\telse if (funcType == nHL)\r\n\t\t\t{\r\n                ReadProcessMemory(pPJ64Process,(void*)(address + 4),&amp;msgPointer,sizeof(msgPointer),0);\r\n\t\t\t}\r\n\r\n\t\t\tif ((msgPointer &amp; 0xff000000) == 0x80000000)\r\n\t\t\t{\r\n\t\t\t\tmsgPointer = msgPointer &amp; 0x0FFFFFFF;\r\n\r\n\t\t\t\tif (msgPointer != oldMsgPointer) \/\/ New Message\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/cout &lt;&lt; hex &lt;&lt; msgPointer &lt;&lt; \"\\n\";\r\n                    readAddress = msgPointer + baseAddress;\r\n\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)readAddress,rawMsg,MAX_MSG_LENGTH,0);\r\n\r\n\t\t\t\t\tif (funcType == bHarvest)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 4),param1,4,0);\r\n\t\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 4),&amp;p1,sizeof(int),0);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (funcType == nHL)\r\n\t\t\t\t\t{\r\n                        ReadProcessMemory(pPJ64Process,(void*)address,param1,4,0);\r\n\t\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)address,&amp;p1,sizeof(int),0);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 8),param2,4,0);\r\n\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 12),param3,4,0);\r\n\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 4),&amp;p1,sizeof(int),0);\r\n\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 8),&amp;p2,sizeof(int),0);\r\n\t\t\t\t\tReadProcessMemory(pPJ64Process,(void*)(address + 12),&amp;p3,sizeof(int),0);\r\n\r\n\t\t\t\t\tendianReverse(rawMsg);\r\n\t\t\t\t\tmessageFormat(rawMsg, param1, param2, param3, p1, p2, p3);\r\n\t\t\t\t\toldMsgPointer = msgPointer;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\/\/Sleep(100);\r\n\t\t}\r\n\t\treturn 0;\r\n\t}\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;s not [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[3,2,5,4],"_links":{"self":[{"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=\/wp\/v2\/posts\/4"}],"collection":[{"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4"}],"version-history":[{"count":2,"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=\/wp\/v2\/posts\/4\/revisions"}],"predecessor-version":[{"id":6,"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=\/wp\/v2\/posts\/4\/revisions\/6"}],"wp:attachment":[{"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/jaytheham.com\/tink\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}