OK. If you're not familiar with C#, you should study it a bit before trying to understand this. Obviously, you will understand what I say because my teaching level is over 9000 but it'll be quite hard for you to make a good program with it, so yeh. Google is your friend here.
I made some functions for you people. The basic functions, writeHex and readHex were made to be in a school project I did but I said meh let's do some more functions and release it here, and also making a better tutorial than my old one.
The functions can be found in the attached files.
Without further ado, let's go!
Requirements:
Basic C# Knoledge
A C# Compiler (I use Visual Studio 2013)
My C# Functions (They're in the attachments)
Before I start explaining how to make the code, I will first explain about every function I did:
WriteHex -> Writes hexadecimal data into a location at the ROM. Can write only one or two bytes.
ReadHex -> Reads hexadecimal data from the ROM, returns a string with the bytes read.
GetGameCode -> Reads the two bytes at 0xAC (default game code location for .GBA Pokémon games), converts it to UTF8 and returns a string with the game code.
IpsPatch -> Patches an .IPS file into a ROM.
WriteHexArray -> Writes hexadecimal data into a location at the ROM. Can write more than one or two bytes; depending on the array length.
Here's the usage manual and examples:
Spoiler:
1) Adding to your project:
Import the .cs file provided in the attachments to your project. In Visual Studio, press Shift+Alt+A.
Go into the top of the form where you want to use the functions. Up, where all the "using" thingys are, add a new line:
Code:
using Gal;
Then, in your code, add
Code:
GBAFunctions gf = new GBAFunctions();
2) Calling the functions is very easy. Here's an explanation on every function:
WriteHex:
Code:
gf.WriteHex(string Byte(s)ToWrite, string Offset, string FileLocation, int AmountToWrite);
If I want to write 0xE0 at 0x800000, in a file opened via openFileDialog1, imma use this:
This function returns a string with the bytes read.
If I want to display in a message box 3 bytes at 0xE1, in a file opened via openFileDialog2 and I want to split them with spaces, imma do this:
Starting the program!
OK. I am going to show you how to make a program that enables/disable the flashback that happens when you load a save file in FireRed.
That is for Visual C#. Console-ish C# is ugly imo, therefore I won't cover it in this tutorial.
Start by making 3 buttons. Name one button "btnOpen", name another "btnEnable" and name another "btnDisable". Now, make a OpenFileDialog and name it openFileDialog1 (the default name).
Go to the code area. Where all the "using [X]" are, paste:
Code:
using Gal;
Now, you are officially using me (lol jk it's just a reference to the namespace of my class).
Look for:
Code:
public [yourprojectname]()
{
InitializeComponent();
}
After that code, paste:
Code:
GBAFunctions gf = new GBAFunctions(); //References to the class I made.
byte[] EnableFlashback = { 0x00, 0x28, 0x0F, 0xD0 }; //Creates a new byte array, that's holding the data to enable the flashback.
byte[] DisableFlashback = { 0x00, 0x1C, 0x0F, 0xE0 }; //Creates a new byte array, that's holding the data to disable the flackback.
Go back on the desinger and double click on btnOpen and paste in the code zone:
Code:
if (openFileDialog1.ShowDialog() == DialogResult.OK) //Opens a file choosing dialog and checks if a file's been chosen.
{
string gameCode = gf.GetGameCode(openFileDialog1.FileName); //Creates a new string that'll hold the game code.
if (gameCode != "BPRE") //If the game code isn't "BPRE", English FireRed's game code.
{
MessageBox.Show("Sorry. Only FireRed Support.", "Sorry");
openFileDialog1.FileName = ""; //Resets the file the open file dialog's holding.
}
else {
MessageBox.Show("FireRed Opened!", "Success!");
}
}
else
{
//Whatever you want it to do if the user didn't choose a file.
}
You made the open file button! Feel free to test it and change the text of the button.
Go back to the designer and double click btnEnable and paste in the code zone:
Code:
gf.WriteHexArray(EnableFlashback, "110F54", openFileDialog1.FileName); //Writes the EnableFlashback array we created before into the offset 0x110F54, where the bytes determine if the flashback system is enabled or not are located.
You made the enable flashback button! Again, feel free to test it and change the text of the button.
Again, go back to the designer and double click btnDiasble and paste in the code zone:
Code:
gf.WriteHexArray(DisableFlashBack, "110F54", openFileDialog1.FileName); //Writes the DisableFlashBack array we created before into the offset 0x110F54, where the bytes determine if the flashback system is enabled or not are located.
Nice nice. You finished coding your program haha. That was quick... Now design it... Make your own unique design that'll attract users. If you don't do it... why would they choose to use your program and not another one's program?
Optional: If you want to avoid error that nobody really understands, you may make a 'try' thingy before any function call. For example:
Code:
try{
gf.ReadHex(2,"800000",openFileDialog1.FileName,false);
}
catch (Exception ex){
MessageBox.Show("Something went wrong with the ReadHex Command","damn"); //Alternatively, you could use MessageBox.Show(ex.ToString(),"damn");
}
Credits (no particular order)
Hopeless Masquerade - Helping me a lot with C# and with the ReadHex function!
Microsoft - The awesome Visual Studio program and C#!
If I forgot someone, please let me know!
The functions should work fine. If you find an error or more, please contact me so I could fix it ASAP. Also, if you find a mistake in the tutorial itself, contact me so I could fix it ASAP. Sorry for not providing pictures in the tutorial. I am a really lazy person. If people find this tutorial REALLY hard to understand, I will provide pictures. I am writing this when I am really tired, so you will probably find something wrong with this tutorial.
I don't want to offend anyone but it does not seem like a well thought-through implementation to use strings in order to specify addresses instead of, well, unsigned integers. You are using objects to define methods that could be static all-through since they are not connected to the instance as far as I can see. As you are not saving the rom in the memory, if you want to execute more than one write operation you would have constant file access which is pretty slow.
Most of what your library offers can also be accomplished using simple streams and a binary reader / writer, I don't see the necesity to wrap those. (Except maybe the IPS functionality which is a bit more complicated)
As said, no offence to anyone, but I think using this library does not enhance your project. The data conversion and file access slows down the execution time.
Frankly the functions you've provided are pointless when the default IO library for .Net does exactly this and more.
What you've written can be done much more easily with existing classes called BinaryReader and BinaryWriter.
Code:
// First, be sure to include "System.IO" at the to with the other included namespaces.
// This code goes within the function called by the button or whatever
using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(openFileDialog1.FileName)))
{
bw.BaseStream.Seek(0x110F54, SeekOrigin.Begin);
bw.Write(EnableFlashback);
}
Code:
// To get the game code
// Encodings may need a reference to System.Text (iirc)
using (BinaryReader br = new BinaryReader(File.OpenRead(openFileDialog1.FileName)))
{
br.BaseStream.Seek(0xAC, SeekOrigin.Begin);
string gameCode = Encoding.UTF8.GetString(br.ReadBytes(4));
}
Much better looking, is it not?
EDIT:
While I'm thinking about it, here's some basic code to apply an .ips patch.
Code:
static void ApplyIPSPatch(string patchFile, string romFile)
{
// First, read the file data for the patch and the ROM
byte[] patchData = File.ReadAllBytes(patchFile);
byte[] romData = File.ReadAllBytes(romFile);
// Now check the patch for the Header and EOF
if (Encoding.UTF8.GetString(patchData, 0, 5) != "PATCH") throw new Exception("Invalid patch header!");
if (Encoding.UTF8.GetString(patchData, patchData.Length - 3, 3) != "EOF") throw new Exception("Invalid patch footer!");
// Since the header/footer checked out, apply the patch data
int i = 5;
while (i < patchData.Length - 3)
{
// So IPS patches store changes in "packets"
// Each packet starts with a 24 bit offset (max value of 0xFFFFFF which is why IPS doesn't work with expanded ROMs)
// By default, BinaryReaders and BinaryWriters cannot work with 24 bit integers which is why arrays are being used here.
int offset = GetInt24(patchData, i);
i += 3;
// Next in the packet is the size of the data to copy from the patch to the ROM
// This is a 16 bit integer
int size = GetInt16(patchData, i);
i += 2;
// However, if the size is zero, we have an "RLE" packet
// Otherwise "size" data is copied from the patch to the ROM
if (size == 0)
{
// A second size is provided for an RLE packet
int rleSize = GetInt16(patchData, i);
i += 2;
byte repeat = patchData[i];
i++;
for (int j = 0; j < rleSize; j++)
{
romData[j + offset] = repeat;
}
}
else
{
for (int j = 0; j < size; j++)
{
romData[j + offset] = patchData[i];
i++;
}
}
}
// Finally, write the changes made to the ROM file
File.WriteAllBytes(romFile, romData);
}
// IPS patch data is provided in big endian (which BinaryReader/Writers also cannot handle by default)
// So these functions help convert arrays of bytes into integers
static int GetInt24(byte[] buffer, int pos)
{
return buffer[pos] | (buffer[pos + 1] << 8) | (buffer[pos + 2] << 16);
}
static int GetInt16(byte[] buffer, int pos)
{
return buffer[pos] | (buffer[pos + 1] << 8);
}
That should about cover it, although I didn't actually test it or anything. I figured it would be nice to share the general idea though.
I don't want to offend anyone but it does not seem like a well thought-through implementation to use strings in order to specify addresses instead of, well, unsigned integers. You are using objects to define methods that could be static all-through since they are not connected to the instance as far as I can see. As you are not saving the rom in the memory, if you want to execute more than one write operation you would have constant file access which is pretty slow.
Most of what your library offers can also be accomplished using simple streams and a binary reader / writer, I don't see the necesity to wrap those. (Except maybe the IPS functionality which is a bit more complicated)
As said, no offence to anyone, but I think using this library does not enhance your project. The data conversion and file access slows down the execution time.
~SBird
Quote:
Originally Posted by Hopeless Masquerade
Frankly the functions you've provided are pointless when the default IO library for .Net does exactly this and more.
What you've written can be done much more easily with existing classes called BinaryReader and BinaryWriter.
Code:
// First, be sure to include "System.IO" at the to with the other included namespaces.
// This code goes within the function called by the button or whatever
using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(openFileDialog1.FileName)))
{
bw.BaseStream.Seek(0x110F54, SeekOrigin.Begin);
bw.Write(EnableFlashback);
}
Code:
// To get the game code
// Encodings may need a reference to System.Text (iirc)
using (BinaryReader br = new BinaryReader(File.OpenRead(openFileDialog1.FileName)))
{
br.BaseStream.Seek(0xAC, SeekOrigin.Begin);
string gameCode = Encoding.UTF8.GetString(br.ReadBytes(4));
}
Much better looking, is it not?
EDIT:
While I'm thinking about it, here's some basic code to apply an .ips patch.
Code:
static void ApplyIPSPatch(string patchFile, string romFile)
{
// First, read the file data for the patch and the ROM
byte[] patchData = File.ReadAllBytes(patchFile);
byte[] romData = File.ReadAllBytes(romFile);
// Now check the patch for the Header and EOF
if (Encoding.UTF8.GetString(patchData, 0, 5) != "PATCH") throw new Exception("Invalid patch header!");
if (Encoding.UTF8.GetString(patchData, patchData.Length - 3, 3) != "EOF") throw new Exception("Invalid patch footer!");
// Since the header/footer checked out, apply the patch data
int i = 5;
while (i < patchData.Length - 3)
{
// So IPS patches store changes in "packets"
// Each packet starts with a 24 bit offset (max value of 0xFFFFFF which is why IPS doesn't work with expanded ROMs)
// By default, BinaryReaders and BinaryWriters cannot work with 24 bit integers which is why arrays are being used here.
int offset = GetInt24(patchData, i);
i += 3;
// Next in the packet is the size of the data to copy from the patch to the ROM
// This is a 16 bit integer
int size = GetInt16(patchData, i);
i += 2;
// However, if the size is zero, we have an "RLE" packet
// Otherwise "size" data is copied from the patch to the ROM
if (size == 0)
{
// A second size is provided for an RLE packet
int rleSize = GetInt16(patchData, i);
i += 2;
byte repeat = patchData[i];
i++;
for (int j = 0; j < rleSize; j++)
{
romData[j + offset] = repeat;
}
}
else
{
for (int j = 0; j < size; j++)
{
romData[j + offset] = patchData[i];
i++;
}
}
}
// Finally, write the changes made to the ROM file
File.WriteAllBytes(romFile, romData);
}
// IPS patch data is provided in big endian (which BinaryReader/Writers also cannot handle by default)
// So these functions help convert arrays of bytes into integers
static int GetInt24(byte[] buffer, int pos)
{
return buffer[pos] | (buffer[pos + 1] << 8) | (buffer[pos + 2] << 16);
}
static int GetInt16(byte[] buffer, int pos)
{
return buffer[pos] | (buffer[pos + 1] << 8);
}
That should about cover it, although I didn't actually test it or anything. I figured it would be nice to share the general idea though.
Thank y'all for the comments. Yes, using the normal .NET functions is fine but I made this thread so people that aren't so-good in C# could make tools too. As a beginner, I had a really hard time understanding the BinaryWriter and the BinaryReader commands, however, Hopelees Masquerade was there to help me. I am pretty sure that you don't have the time and the energy to help everyone as much as you helped me (you helped me a lot, didn't you? haha) so these functions can be used by beginners. Obviously, the beginners using these functions could always hop to the source code after they have more hacking experience and after a while, even making a tool that we all could use for hacking (if you are still hacking, ofcourse). Also, why should you write 4~5 lines when you can write 1 line? C:
Here's a toPointer method. I made this to erase a **** load of stuff from the XML. What this does is create a pointer from a given offset. For example, if you repoint the, let's say, names list offset, you wouldn't have to change anything in the XML or ini because this checks the pointer to the pointer. Feel free to tell me if there's bugs.
A conversion like this eats up performance because you are using many string operations. This is much easier:
public void WritePointer(uint offset)
{
// bw is your binarywriter in a global scope
bw.Write(BitConverter.GetBytes(offset + 0x08000000));
}
This code basically adds 0x8000000 to the offset => 0x 08 AB CD EF.
BitConverter.GetBytes(); reverses this 32-bit value to EF CD AB 08 and stores it as a representation of bytes (Byte[] { EF, CD, AB, 08 }) which you can write to ROM via bw.Write(Byte[] bytes) overload.
A conversion like this eats up performance because you are using many string operations. This is much easier:
public void WritePointer(uint offset)
{
// bw is your binarywriter in a global scope
bw.Write(BitConverter.GetBytes(offset + 0x08000000));
}
This code basically adds 0x8000000 to the offset => 0x 08 AB CD EF.
BitConverter.GetBytes(); reverses this 32-bit value to EF CD AB 08 and stores it as a representation of bytes (Byte[] { EF, CD, AB, 08 }) which you can write to ROM via bw.Write(Byte[] bytes) overload.
Hmm, interesting. Pretty cool, and definitely much more efficient. Thank you.
Why even include the BitConverter stuff? You can just write it as an integer for the same effect, as BinaryWriters and Readers already work in little endian.
I would not rely on the conversion performance-wise. Streaming is terribly slow on MONO, as cross-platform ability has to be assured. But thanks, I didn't know you could just pass any integral type to this function. :-)
I would not rely on the conversion performance-wise. Streaming is terribly slow on MONO, as cross-platform ability has to be assured. But thanks, I didn't know you could just pass any integral type to this function. :-)
You can do more than that! It even accepts a number of other types, including Booleans and Strings. Any Read method that the BinaryReader implements is provided as an overloaded Write function in a BinaryWriter.