Skip to content

Instantly share code, notes, and snippets.

@SixBeeps
Created December 28, 2020 07:00
Show Gist options
  • Select an option

  • Save SixBeeps/c1745fb7a2c4f5169db7bf2424a9214a to your computer and use it in GitHub Desktop.

Select an option

Save SixBeeps/c1745fb7a2c4f5169db7bf2424a9214a to your computer and use it in GitHub Desktop.
NES ROM Graphics Injector
using System;
using System.IO;
using System.Linq;
namespace NESMixer {
class Program {
public static Random rng = new Random();
static void Main(string[] args) {
if (args.Length == 2) {
ROM rom = new ROM(args[0]);
rom.chrRom = rom.chrRom.OrderByDescending(b => (int)b).ToArray();
rom.SaveToFile(args[1]);
} else if (args.Length == 3) {
ROM r1 = new ROM(args[0]);
ROM r2 = new ROM(args[1]);
r1.chrRom = r2.chrRom;
r1.chrRomLength = r2.chrRomLength;
r1.SaveToFile(args[2]);
} else {
Console.WriteLine("Please use 2 or 3 arguments as the paths to NES Roms");
return;
}
Console.WriteLine("Done!");
}
}
public class ROM {
public long prgRomLength, chrRomLength;
public byte[] prgRom, chrRom, flags, trainer, extraData;
public const int PRG_BLOCK_SIZE = 16384; // PRG block size in bytes
public const int CHR_BLOCK_SIZE = 8192; // Same thing but for CHR
private static byte[] validSignature = { 0x4e, 0x45, 0x53, 0x1a };
public ROM(string filePath) : this(File.Open(filePath, FileMode.Open)) { }
public ROM(FileStream fs) {
if (!fs.CanRead) throw new IOException("Given FileStream cannot be read");
// Check if file has magic bytes
byte[] signature = new byte[4];
fs.Read(signature, 0, 4);
if (!signature.SequenceEqual(validSignature)) throw new FormatException($"Given file does not appear to be in iNES format. Magic bytes are {BitConverter.ToString(validSignature)}, got {BitConverter.ToString(signature)}");
// Calculate how much memory to allocate for PRG ROM and CHR ROM
prgRomLength = fs.ReadByte() * PRG_BLOCK_SIZE;
chrRomLength = fs.ReadByte() * CHR_BLOCK_SIZE;
prgRom = new byte[prgRomLength];
chrRom = new byte[chrRomLength];
// Flags from ROM
flags = new byte[5];
fs.Read(flags, 0, 5);
fs.Seek(5, SeekOrigin.Current); // Should be zero-padded so we can ignore
// Write to trainer if it exists
if (GetBit(flags[0], 2)) {
trainer = new byte[512];
fs.Read(trainer, 0, 512);
}
// Write the PRG and CHR data
fs.Read(prgRom, 0, (int)prgRomLength);
fs.Read(chrRom, 0, (int)chrRomLength);
// Get any remaining data
int exLength = (int)(fs.Length - fs.Position);
if (exLength > 0) {
extraData = new byte[exLength];
fs.Read(extraData, 0, exLength);
Console.WriteLine($"There were {exLength} bytes of information left");
} else {
Console.WriteLine($"No extra info to parse");
}
}
public void SaveToFile(string filePath) {
using (FileStream fs = File.Create(filePath)) {
fs.Write(validSignature);
fs.WriteByte((byte)(prgRomLength / PRG_BLOCK_SIZE));
fs.WriteByte((byte)(chrRomLength / CHR_BLOCK_SIZE));
fs.Write(flags);
for (int i = 0; i < 5; i++)
fs.WriteByte(0x00);
if (trainer != null)
fs.Write(trainer);
fs.Write(prgRom);
fs.Write(chrRom);
}
}
public long TotalSize() {
return prgRomLength + chrRomLength;
}
private static bool GetBit(byte b, int k) {
return (b & (1 << (k - 1))) > 0;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment