Language…
14 users online: anonimzwx,  Atari2.0, Dennsen86, deported, derv82, drkrdnk, ForthRightMC, Fozymandias, hhuxy, kms415,  LouisDoucet, masl, oliver1, RichardDS90 - Guests: 287 - Bots: 259
Users: 64,795 (2,375 active)
Latest user: mathew

SMW/ROMhacking tool related code library

  • Pages:
  • 1
  • 2
I decided to post a couple core functions from Xkas GUI here for good measure.

RemoveCommentsAndTrim

trims up an ASM patch so it can be more easily processed

Code
private string[] removeCommentsAndTrim(string[] input)
        {
            for (int i = 0; i < input.Length; i++)
            {
                if (input[i].Contains(";"))
                {
                    input[i] = input[i].Substring(0, input[i ].IndexOf(";"));
                }
                if (input[i].Contains("//"))
                {
                    input[i] = input[i].Substring(0, input[i].IndexOf("//"));
                }
                input[i] = input[i].Trim();
            }
            return input;
        }


CountBytes

Counts the bytes in an ASM patch, starting at a given line and ending at the next ORG or the end of the file

Code
private int countbytes(string asmpath, int startline)
        {
            int n = 0;
            string[] a = File.ReadAllLines(asmpath);
            a = removeCommentsAndTrim(a);
            for (int i = startline; i < a.Length && !a[ i].ToLowerInvariant().Contains("org"); i++)
                {
                    a[i] = a[i].ToLowerInvariant();
                    if (a[i].Contains(":"))
                        continue;
                    if (a[i].ToLowerInvariant().Contains("incbin"))
                    {
                        n += File.ReadAllBytes(asmpath.Substring(0, asmpath.LastIndexOf("\\") + 1) + a[i].Substring(7)).Length;
                        continue;
                    }
                    if (a[i].ToLowerInvariant().Contains("incsrc"))
                    {
                        n += countbytes(asmpath.Substring (0, asmpath.LastIndexOf("\\") + 1) + a[i].Substring(7), 0);
                        continue;   //                    1                      2   /2   /1
                    }
                    int fair = 0;
                    for (int j = 0; j < a[i].Length; j++)
                    {
                        if (a[i].Substring(j, 1).IndexOfAny(letters) != -1)
                        {
                            fair++;
                            if (fair >= 3)
                            {
                                fair = 0;
                                n++;
                            }
                        }
                        else
                            fair = 0;
                    }

                    fair = 0;
                    for (int j = 0; j < a[i ].Length; j++)
                    {
                        if (a[i].Substring(j, 1).IndexOfAny(hex) != -1)
                        {
                            fair++;
                            if (fair >= 2)
                            {
                                fair = 0;
                                n++;
                            }
                        }
                        else
                            fair = 0;
                    }
                    
                }
            return n;

        }


GetFreeSpace

Searches for a given number of unprotected bytes in a ROM, starting in bank 10. Returns "not" if there isn't a string of free bytes long enough, "exp" if the ROM hasn't been expanded, or returns the hex address of the freespace.

Code
private string getfreespace(string rompath, int bytesneeded)
        {
            byte[] rombyte = File.ReadAllBytes(rompath);
            if (rombyte.Length < 513 * 1024)
                return "exp";
            int header = 0;
            if (rombyte[11] == 0 && rombyte[12] == 0 && rombyte[13] == 0)
                header = 0x200;
            int freebytes = 0;
            int freespace = 0x80000 + header;
            for (int i = freespace; i < rombyte.Length; i++)
            {
                if (rombyte[i] == 0x53)
                {
                    if (rombyte[i + 1] == 0x54)
                    {
                        if (rombyte[i + 2] == 0x41)
                        {
                            if ((rombyte[i + 3] == 0x52) || (((i - header) & 0x77FFF) | 0x8000) == 0x8000)
                            {

                                if (freebytes >= bytesneeded)
                                {
                                    rombyte[freespace] = 0x52;
                                    rombyte[freespace + 1] = 0x41;
                                    rombyte[freespace + 2] = 0x54;
                                    rombyte[freespace + 3] = 0x53;
                                    rombyte[freespace + 4] = (byte)(bytesneeded & 0xFF);
                                    rombyte[freespace + 5] = (byte)(bytesneeded >> 8 & 0xFF);
                                    rombyte[freespace + 6] = (byte)(bytesneeded ^ 0xFFFF & 0xFF);
                                    rombyte[freespace + 7] = (byte)(bytesneeded ^ 0xFFFF >> 8 & 0xFF);
                                    File.WriteAllBytes(rompath, rombyte);
                                    string freestring = String.Format("{0:X}", PCSNES(freespace, header));
                                    while (freestring.Length < 6)
                                    {
                                        freestring = "0" + freestring;
                                    }
                                    return freestring;
                                }
                                freebytes = 0;
                                i += 5 + rombyte[i + 4] + (rombyte[i + 5] * 0x100);
                                freespace = i;
                            }
                        }
                    }
                }
                freebytes++;
            }
            if (freespace >= bytesneeded)
            {
                rombyte[freespace] = 0x52;
                rombyte[freespace + 1] = 0x41;
                rombyte[freespace + 2] = 0x54;
                rombyte[freespace + 3] = 0x53;
                rombyte[freespace + 4] = (byte)(bytesneeded & 0xFF);
                rombyte[freespace + 5] = (byte)(bytesneeded >> 8 & 0xFF);
                rombyte[freespace + 6] = (byte)(bytesneeded ^ 0xFFFF & 0xFF);
                rombyte[freespace + 7] = (byte)(bytesneeded ^ 0xFFFF >> 8 & 0xFF);
                File.WriteAllBytes(rompath, rombyte);
                string freestring = String.Format("{0:X}", PCSNES(freespace, header));
                while (freestring.Length < 6)
                {
                    freestring = "0" + freestring;
                }
                return freestring;
            }
            return "not";
        }


mRecreateResourses

A great code I found somewhere on the internet. It re-creates a given resource in the folder of the tool.

Code
 private void mRecreateResource(string filename)
        {
            // Get Current Assembly refrence
            Assembly currentAssembly = Assembly.GetExecutingAssembly();
            // Get all imbedded resources
            string[] arrResources = currentAssembly.GetManifestResourceNames();

            foreach (string resourceName in arrResources)
            {
                if (resourceName == filename)
                {
                    //Name of the file saved on disk
                    string saveAsName = AppDomain.CurrentDomain.BaseDirectory + filename;
                    FileInfo fileInfoOutputFile = new FileInfo(saveAsName);
                    //CHECK IF FILE EXISTS AND DO SOMETHING DEPENDING ON YOUR NEEDS
                    if (fileInfoOutputFile.Exists)
                    {
                        File.Delete(fileInfoOutputFile.ToString());
                    }
                    //OPEN NEWLY CREATING FILE FOR WRITTING
                    FileStream streamToOutputFile = fileInfoOutputFile.OpenWrite();
                    //GET THE STREAM TO THE RESOURCES
                    Stream streamToResourceFile =
                                        currentAssembly.GetManifestResourceStream(resourceName);

                    //---------------------------------
                    //SAVE TO DISK OPERATION
                    //---------------------------------
                    const int size = 4096;
                    byte[] bytes = new byte[4096];
                    int numBytes;
                    while ((numBytes = streamToResourceFile.Read(bytes, 0, size)) > 0)
                    {
                        streamToOutputFile.Write(bytes, 0, numBytes);
                    }

                    streamToOutputFile.Close();
                    streamToResourceFile.Close();
                }//end_if

            }//end_foreach
        }


...yep, that's about all for now.

One thing I am constantly annoyed at about SMWC is that if I try to index an array with int i, SMWC reads it as an italics tag.

Ersan to the rescue with &#91;!
Here is a small code I wrote in C# for a 'layer 3 palette editor', to get the current palette and color number on a 16x16 color palette grid. (Actually it was meant for a NES palette editor but both of their palettes pretty much behave the same)

Code
            int paletteNumber = (((e.X >> 4) / 4) + ((e.Y >> 4) * 4));
            int colorNumber = ((e.X >> 4) & 0x03);

This can be used in a mouseover event.

Example values (numbers and borders put by me):

My blog. I could post stuff now and then

My Assembly for the SNES tutorial (it's actually finished now!)
I don't really have anything useful to share, but I do have a (not very efficient, but absolute) algorithm for converting hex to dec for C. This is for a two-character hex number, which you put in the argument.

COPYPASTA!

Code
int hexadecimaltc(char value[2]) //Two-character hexadecimal converter. Converts from hex to dec.
{
	int num; //The number you get after the process is done.
	int temp; //A temporary controller for the number you add to num.
	
	if (value[0] == '0'){
		temp = 0;
	}
	if (value[0] == '1'){
		temp = 1;
	}
	if (value[0] == '2'){
		temp = 2;
	}
	if (value[0] == '3'){
		temp = 3;
	}
	if (value[0] == '4'){
		temp = 4;
	}
	if (value[0] == '5'){
		temp = 5;
	}
	if (value[0] == '6'){
		temp = 6;
	}
	if (value[0] == '7'){
		temp = 7;
	}
	if (value[0] == '8'){
		temp = 8;
	}
	if (value[0] == '9'){
		temp = 9;
	}
	if (value[0] == 'A' || value[0] == 'a'){
		temp = 10;
	}
	if (value[0] == 'B' || value[0] == 'b'){
		temp = 11;
	}
	if (value[0] == 'C' || value[0] == 'c'){
		temp = 12;
	}
	if (value[0] == 'D' || value[0] == 'd'){
		temp = 13;
	}
	if (value[0] == 'E' || value[0] == 'e'){
		temp = 14;
	}
	if (value[0] == 'F' || value[0] == 'f'){
		temp = 15;
	}
	num = 16 * temp;
	
	if (value[1] == '0'){
		temp = 0;
	}
	if (value[1] == '1'){
		temp = 1;
	}
	if (value[1] == '2'){
		temp = 2;
	}
	if (value[1] == '3'){
		temp = 3;
	}
	if (value[1] == '4'){
		temp = 4;
	}
	if (value[1] == '5'){
		temp = 5;
	}
	if (value[1] == '6'){
		temp = 6;
	}
	if (value[1] == '7'){
		temp = 7;
	}
	if (value[1] == '8'){
		temp = 8;
	}
	if (value[1] == '9'){
		temp = 9;
	}
	if (value[1] == 'A' || value[1] == 'a'){
		temp = 10;
	}
	if (value[1] == 'B' || value[1] == 'b'){
		temp = 11;
	}
	if (value[1] == 'C' || value[1] == 'c'){
		temp = 12;
	}
	if (value[1] == 'D' || value[1] == 'd'){
		temp = 13;
	}
	if (value[1] == 'E' || value[1] == 'e'){
		temp = 14;
	}
	if (value[1] == 'F' || value[1] == 'f'){
		temp = 15;
	}
	num = num + temp;
	return num;
}
Still here, I guess. Despite all odds.
Or you could use the strtol() function? (It is located in stdlib.h)

Code
#include <iostream>

[...]

cout << dec << 0xff << endl
Results in 255.


Originally posted by Yakov
Code
#include <iostream>

[...]

cout << dec << 0xff << endl
Results in 255.


strtol is used when the given number is coming from a string. When it comes to inputting number from a programmers perspective like you did will usually result in a decimal output. The only exception to that would be using output modifiers on the iostream(or print style functions of C).
Originally posted by p4plus2
Or you could use the strtol() function? (It is located in stdlib.h)


What's that? I need something to convert decimal to hex anyway, and I don't know how %x works.
Still here, I guess. Despite all odds.
Originally posted by mszegedy
Originally posted by p4plus2
Or you could use the strtol() function? (It is located in stdlib.h)


What's that? I need something to convert decimal to hex anyway, and I don't know how %x works.


It will take a char array input and will return a long int.
Code
long int strtol(const char *start, char **end, int base);


More information can be found here:
http://www.cplusplus.com/reference/clibrary/cstdlib/strtol/

Also, %x works like this:
Code
   printf("hex: %x", 100);
Originally posted by x-treme
Sorry for the bump, but what is the code for patch a ips patch in VB2008?


Code
Dim romstream As New FileStream(romname, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
Dim ipsstream As New FileStream(patchname, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
Dim lint As Integer = CInt(ipsstream.Length)
Dim ipsbyte As Byte() = New Byte(ipsstream.Length - 1) {}
Dim rombyte As Byte() = New Byte(romstream.Length - 1) {}
Dim romresult As IAsyncResult
Dim ipsresult As IAsyncResult = ipsstream.BeginRead(ipsbyte, 0, lint, Nothing, Nothing)
ipsstream.EndRead(ipsresult)
Dim ipson As Integer = 5
Dim totalrepeats As Integer = 0
Dim offset As Integer = 0
Dim keepgoing As Boolean = True
'''///////////////End Init code
'''///////////////Start main code
While keepgoing = True
	offset = ipsbyte(ipson) * &H10000 + ipsbyte(ipson + 1) * &H100 + ipsbyte(ipson + 2)
	ipson += 1
	ipson += 1
	ipson += 1
	'''//////////split between repeating byte mode and standard mode
	If ipsbyte(ipson) * 256 + ipsbyte(ipson + 1) = 0 Then
		'''/////////repeating byte mode
		ipson += 1
		ipson += 1
		totalrepeats = ipsbyte(ipson) * 256 + ipsbyte(ipson + 1)
		ipson += 1
		ipson += 1
		Dim repeatbyte As Byte() = New Byte(totalrepeats - 1) {}
		For ontime As Integer = 0 To totalrepeats - 1
			repeatbyte(ontime) = ipsbyte(ipson)
		Next
		romstream.Seek(offset, SeekOrigin.Begin)
		romresult = romstream.BeginWrite(repeatbyte, 0, totalrepeats, Nothing, Nothing)
		romstream.EndWrite(romresult)
		ipson += 1
	Else
		'''/////////standard mode
		totalrepeats = ipsbyte(ipson) * 256 + ipsbyte(ipson + 1)
		ipson += 1
		ipson += 1
		romstream.Seek(offset, SeekOrigin.Begin)
		romresult = romstream.BeginWrite(ipsbyte, ipson, totalrepeats, Nothing, Nothing)
		romstream.EndWrite(romresult)
		ipson = ipson + totalrepeats
	End If
	'''//////////Test For "EOF"
	If ipsbyte(ipson) = 69 AndAlso ipsbyte(ipson + 1) = 79 AndAlso ipsbyte(ipson + 2) = 70 Then
		keepgoing = False
	End If
End While
romstream.Close()
ipsstream.Close()

Sorry for bumping the tread but since its sticked.....
Some generic snes functions in Python, suggested by imamelia:

Code
def addresstostring(address, endianness = "little"):
    return "".join([hex(ord(x))[2:] if len(hex(ord(x))[2:]) == 2 else "0"+hex(ord(x))[2:] for x in (address[::-1] if endianness == "little" else address)])

Takes in an address in little endian (or big endian if you specify anything else but "little" for the endianness parameter) and returns it as a formatted string (e.g. "\x00\x00\x08" will become "080000")

Code
def LoRomtoPC(address,header=False):
    return (((address & 0x7f0000) >> 1) | (address & 0x7fff)) + header*0x200

def HiRomtoPC(address,header=False):
    return (address & 0x3fffff) + header*0x200

def PCtoLoRom(address,header=False):
    address = address-(header*0x200)
    return ((address & 0x7F8000) << 1) + 0x8000 + (address & 0x7FFF)

def PCtoHiRom(address,header=False):
    return 0xC00000 + ((address - header*0x200)&0x3FFFFF)


Pretty self explanatory, just the snes/PC conversion routines

Code
import re

class FreeSpace(object):
    def __init__(self,start,length):
        self.start = start
        self.LoRom = PCtoLoRom(start)
        self.HiRom = PCtoHiRom(start)
        self.length = length
    def __str__(self):
        return "<FreeSpace object, start= {0} , length= {1} >".format(hex(self.start),hex(self.length))

def FindFreespace(data,size=1,zeros=False):
    matches = re.finditer("STAR(....)",data,re.S)
    count, RATs, addresses, lastRAT = 0,[],[],(0,0)
    for match in matches:
        length = int(addresstostring(match.groups()[0][0:2]),16)
        if length^0xFFFF == int(addresstostring(match.groups()[0][2:]),16) and \
        match.start() + 8 + length >= lastRAT[1]:
            RATs.append((match.start(),match.start()+8+length))
            lastRAT = (match.start(),match.start()+8+length)
    if zeros:
        for RAT in zip([(0,0)]+RATs,RATs+[(len(data),len(data))]):
            if RAT[0][1]+1 < RAT[1][0]:
                matches=re.finditer("".join(["\x00" for x in xrange(size)])+"\x00*",data[RAT[0][1]+1:RAT[1][0]])
                for match in matches:
                    freespace = FreeSpace(RAT[0][1]+1+match.start(),match.end()-match.start())
                    addresses.append(freespace)               
    else:
        for RAT in zip([(0,0)]+RATs,RATs+[(len(data),len(data))]):
            if RAT[0][1]+1 < RAT[1][0]:
                freespace = FreeSpace(RAT[0][1]+1, RAT[1][0]-RAT[0][1])
                if freespace.length >= size:  
                    addresses.append(freespace)
    return addresses

First one is a class, it's not necessary (I could just use a tuple) but it's nicer to be able to call freespace.start than having to do freespace[0].

Following that is a freespace finder. It takes in the ROM data, how many bytes of freespace it should look for, and whether it should look for zeros or just space between RATs, and it returns a list of "FreeSpace" objects that represent areas of freespace in the ROM. And, just for fun, here's the 8 line version (Pastebinned because otherwise it creates massive table stretch) Beat that p4plus2 :P

Code
def FindFreespaceByBanks(data,size=1,zeros=False):
    freespaces = FindFreespace(data,size,zeros)
    bankfreespaces = [[] for x in xrange(len(data)//0x8000)]
    for freespace in freespaces:
        banknum = freespace.start//0x8000
        posinbank = freespace.start%0x8000
        if posinbank + freespace.length > 0x8000:
            tempfreespaces = []
            length = freespace.length
            newfreespace = FreeSpace(freespace.start,0x8000-posinbank)
            bankfreespaces[banknum].append(newfreespace)
            tempfreespaces.append(newfreespace)
            length -= 0x8000-posinbank
            banknum += 1
            while length > 0x8000:
                newfreespace = FreeSpace(0x8000*banknum,0x8000)
                bankfreespaces[banknum].append(newfreespace)
                tempfreespaces.append(newfreespace)
                length -= 0x8000
                banknum += 1 
            newfreespace = FreeSpace(0x8000*banknum,length)
            bankfreespaces[banknum].append(newfreespace)
            tempfreespaces.append(newfreespace)  
        else:
            bankfreespaces[banknum].append(freespace)
    return bankfreespaces 

This is a convenience wrapper around the freespace finding function. Essentially it just takes the freespace and splits it up into banks, it takes the same parameters as the freespace function and returns a list of lists of freepsace objects. Each item in the list corresponds to a bank, so if you wanted, e.g., all freespace within bank ten, you would just grab the tenth item in the list and it would be a list of freespace objects that correspond to the freespace found in bank 10.

These are all available as a module, plus a slogger clone to give an idea of how to use them, from here. Not that really anyone else uses python, but now it's out there if anyone needs it.
Now we just need a convenience wrapper for the convenience wrapper. As much as I appreciate you making these, I still don't see why you had to do the function that way and not just have one that finds the first available area of freespace given a file handle and size (and not letting it cross a bank boundary, of course). That just seems to be needlessly complicating things, if you ask me.

----------------

I'm working on a hack! Check it out here. Progress: 64/95 levels.
Originally posted by imamelia
I still don't see why you had to do the function that way and not just have one that finds the first available area of freespace given a file handle and size (and not letting it cross a bank boundary, of course). That just seems to be needlessly complicating things, if you ask me.

It's slow to look for freespace every single time. This way, it would take a minimal amount of coding on your part to vastly speed up the searching. However, I added a little function anyways that takes what is returned by "FindFreespaceByBanks" function, a length, and whether or not the freespace should be updated (True by default) and returns a freespace object for you, updating the one in the list so that it will take into account the code you are inserting (Eww, side effect). This also included changing the freespace object, but merely for convenience:

Code
class FreeSpace(object):
    def __init__(self,start,length):
        self.start = start
        self.LoRom = PCtoLoRom(start)
        self.HiRom = PCtoHiRom(start)
        self.length = length
    def UpdateAddress(self,length):
        self.length -= length
        self.start += length
        self.LoRom = PCtoLoRom(self.start)
        self.HiRom = PCtoHiRom(self.start)
    def __str__(self):
        return "<FreeSpace object, start= {0} , length= {1} >".format(hex(self.start),hex(self.length))

def GetFirstFreespace(banks,length, update = True):
    for freespaces in banks:
        for freespace in freespaces:
            if freespace.length >= length:
                tmpfreespace = FreeSpace(freespace.start,freespace.length)
                if update:
                    freespace.UpdateAddress(length)
                return tmpfreespace
    return None


I've updated the download for snesproc to add these.

Possible usage would look like this:

Code
import snesproc

file1 = open("ROM.smc",'rb')
data = file1.read()
file1.close()
if len(data)%1024:
    data=data[0x200:]
freespaces = snesproc.FindFreespaceByBanks(data,1,True)
print snesproc.GetFirstFreespace(freespaces[0x10:],0x343)


I don't know if I mentioned, but these all require the ROM to be non headered.
This method of converting between 8-bit colour channels and 5-bit colour channels is far more accurate than simple bit-shifts.

Code
byte Convert8BitTo5Bit(byte value)
{
    return (byte)((value * (31.0 / 255.0) + 0.5);
}

byte Convert5BitTo8Bit(byte value)
{
    return (byte)(((value & 31) * (255.0 / 31.0) + 0.5);
}


Alternatively, if you want to use bit shifts to convert a 5-bit channel value to an 8-bit channel value, the following code shows the most accurate way to do this.

Code
byte Convert5BitTo8Bit(byte value)
{
    return (byte)(((value & 31) << 3) | ((value & 31) >> 2));
}


Of 32 possible values, 4 are off by 1.
GradientToolLevelMusic UtilitySM64 Clean ROM verifierHQX VirtualDub FilterImoSPC2 (Alpha)Music Section SPC PlayerEmbeddable SPC Player for SMWCYouTube EmbedderJSRomcleanJS Address ConverterLazyHDMA
using floating point casting is an amazingly inefficient method.
----------

Interested in MushROMs? View its progress, source code, and make contributions here.

Originally posted by spel werdz rite
using floating point casting is an amazingly inefficient method.

I tried it in .NET before. I did 1 billion 500 million conversions from a 5-bit channel to an 8-bit channel.
e: 1 billion values takes too long for my results; must have been 500 million values instead.

The "bitbashing" method I showed took ~4 ~3.687 seconds to compute.
Using floating point method took ~6 ~5.618 seconds to compute.

If you want to see what's really inefficient, using Math.Round instead of the method I showed, the floating point method took ~12 ~18.339 seconds to compute.
Using Convert.ToByte took ~42 seconds to compute. Holy crap, now THAT's inefficient.
e: Did it again, got ~13.569 seconds with 500 million values. How odd.

If the floating point method taking 50% longer is a problem, just use the bitbashing method I demonstrated. It is far superior to simple bit shifting (AKA "dumb conversion").

e: A precomputed lookup table works fine, too. After all, when converting a 5-bit channel to an 8-bit channel, a lookup table would only be 32 bytes.

Using the lookup table took ~3.390 for the same 500 million values.
GradientToolLevelMusic UtilitySM64 Clean ROM verifierHQX VirtualDub FilterImoSPC2 (Alpha)Music Section SPC PlayerEmbeddable SPC Player for SMWCYouTube EmbedderJSRomcleanJS Address ConverterLazyHDMA
Lua: load ROM into a table

Code
-- Prompt user for ROM name.
local file, fileName, bytes
while not fileOpen do
    io.write("Enter ROM name:\n")
    fileName = io.read()
    file = io.open(fileName, "rb") 
	
    if not file then print("Error opening file\n") else fileOpen = true end
end

-- Now, split file into a table.
local romBuffer = {}
while true do
    local bytes = file:read(1)
    if not bytes then break end
    romBuffer[#romBuffer + 1] = bytes 
end


The ROM is loaded byte by byte into romBuffer table.
Now we can test out this code by printing some bytes from ROM:

Code
for i = 1, 1000 do
    io.write(string.format("%02X ", string.byte( romBuffer[ i ] ))) 
    if i % 8 == 0 then io.write("\n") end
end


I hope this is helpful for those who use Lua. It's very simple and quick, but maybe the code could be optimized more. ;)
Here's a class, written in Java, that converts a 4BPP .bin file back and forth between a more useful format:

Code
public class DataConverter {
    
    /**
     * Converts data from the SNES 4BPP format to a packed array of palette values.
     * 
     * @param data data to convert
     * @return converted array
     */
    public static byte[] fromSNES4BPP(byte[] data) {
        byte[] tileData = new byte[data.length*2];
        
        for (int i = 0; i < tileData.length; i++) {
            int tile = i / 64;
            int row = (i % 64) / 8;
            int col = i % 8;
            
            byte value = 0;
            value |= getBit(data[0 +  tile*32 + row*2], (byte) 7 - col);
            value |= getBit(data[1 +  tile*32 + row*2], (byte) 7 - col) << 1;
            value |= getBit(data[16 + tile*32 + row*2], (byte) 7 - col) << 2;
            value |= getBit(data[17 + tile*32 + row*2], (byte) 7 - col) << 3;
            
            tileData[i] = value;
        }
        
        return tileData;
    }
    
    /**
     * Converts data from a packed array of palette values to the SNES 4BPP format.
     * 
     * @param data data to convert
     * @return converted array
     */
    public static byte[] toSNES4BPP(byte[] tileData) {
        byte[] data = new byte[tileData.length/2];
        
        for (int i = 0; i < tileData.length; i++) {
            int tile = i / 64;
            int row = (i % 64) / 8;
            int col = i % 8;
            
            data[0 +  tile*32 + row*2] |= getBit(tileData[i], 0) << 7 - col;
            data[1 +  tile*32 + row*2] |= getBit(tileData[i], 1) << 7 - col;
            data[16 + tile*32 + row*2] |= getBit(tileData[i], 2) << 7 - col;
            data[17 + tile*32 + row*2] |= getBit(tileData[i], 3) << 7 - col;
        }
        
        return data;
    }
    
    /**
     * Gets the value of a bit in a byte.
     * 
     * @param value byte value to check
     * @param bit which bit to check
     * @return either 0 or 1
     */
    public static byte getBit(byte value, int bit) {
        return (byte) (value >>> bit & 0x1);
    }
    
}


The converted array contains nothing more than a series of palette values, from 0x0 to 0xF. While this is Java code, it could easily be converted to other languages.
Note that this comes from my tile editor, SNESTile, and a prettier version of this class can be found here.
Here is a JavaScript function for ripping BRR sample from a SPC file chunk (can be Buffer from Node.js, Uint8Array or just a regular Array filled with uint8-range numbers).

Works on both Node.js and reasonably-modern broswers.

  • Pages:
  • 1
  • 2