Ramdump

From en64 wiki
Jump to navigation Jump to search

When working with a piece of software and stepping through its code with a debugger it's very helpful to observe things like memory and register's while you do so. Luckily if you are working with a good emulator like Nemu, you have access to a wide array of debugging tools. In Nemu you can observe and change memory quite quickly but you are still limited to doing one thing at a time and you can only view variables in hex. Let's say you want to breakpoint at a start of the function and have an in depth look at the emulator's memory at that particular point. There are two ways to do this, by scanning the emulator process's memory space or using a ramdump. Scanning the emulator's memory provides a few problems, you need to know the start address of the game's RAM, the emulator's memory would also have each 4 bytes of the game's RAM endian swapped if you are on a little endian system.

Taking a ramdump in Nemu

The memory dump debugging feature can be found under the plugin header in Nemu. You can save the ramdump as little endian to avoid having to byteswap it yourself.

Ramdump.png

How to use the ramdump

You are probably wondering what to do with this big blob of memory, you can use a hex editor on it if you want, but the sane thing to do is to load it in your own program so you can write code that reads the RAM as the game would. We are going to use C++ and Visual Studio on Windows for this tutorial. Create a new empty console project without precompiled header's and paste in the following code:

#include <tchar.h>
#include <vector>
#include <memory>
#include <fstream>
#include <iterator>

#ifdef WIN32
#define RAMFETCH(x) (RAMBuffer->data() + ((x) & 0x00FFFFFF))
#else
#define RAMFETCH(x) (x)
#endif

std::unique_ptr<std::vector<char>> RAMBuffer;

bool FileToByteVec(const char * Filename, std::vector<char> * Vec)
{
    if(!Filename || !Vec)return false;

    std::ifstream F(Filename, std::ios::binary);
    if(!F.is_open())return false;

    F.unsetf(std::ios::skipws);

    std::streampos FileSize;

    F.seekg(0, std::ios::end);
    FileSize = F.tellg();
    F.seekg(0, std::ios::beg);

    Vec->reserve(FileSize);

    Vec->insert(Vec->begin(), std::istream_iterator<char>(F), std::istream_iterator<char>());

    F.close();

    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    RAMBuffer = std::make_unique<std::vector<char>>();
    if(!FileToByteVec("C:/Users/ZE/Desktop/Ramdump", RAMBuffer.get()))return -1;

    //Your code
    float ActorY = *(float *)RAMFETCH(0x801DAA30 + 0x28); //Read link's Y
    //

    return 0;
}

Now you can freely work with with the RAMBuffer and treat it as if it was the game's RAM at the point of the breakpoint triggering. You can also use the RAMFETCH macro in C code that you will compile for the N64 using the GCC toolchain, you can create code that you debug with your IDE of choice and will also compile correctly for MIPS. One thing to note is that because the N64 is big endian and you are probably on a little endian machine, non 4 byte variables will require special conversion (Tutorial coming later).