-
Notifications
You must be signed in to change notification settings - Fork 3
Format: Rct image
Type | Value | Description |
---|---|---|
char[8] |
"六丁TC00" (\x98\x5a\x92\x9a TC00 )"六丁TS00" ( \x98\x5a\x92\x9a TS00 )"六丁TC01" ( \x98\x5a\x92\x9a TC01 )"六丁TS01" ( \x98\x5a\x92\x9a TS01 ) |
File Signature and VersionC = decryptedS = encrypted |
uint32 |
Width | Image width in pixels |
uint32 |
Height | Image height in pixels |
uint32 |
DataSize | Size of image data buffer |
Type | Value | Description |
---|---|---|
byte [Size] |
DataBuffer | Image data buffer |
Type | Value | Description |
---|---|---|
uint16 |
BaseNameLengh | Size of Base name string + null terminator |
char [Lengh] |
BaseName | Filename of base layer this image is overlaid on top of |
byte [Size] |
DataBuffer | Image data buffer |
When using RCT version V01, and the BaseNameLength field is non-zero, this image will be treated as a layer to place on top of the specified image file name.
Because RCT has no alpha channel, one color is reserved as the transparent color. In this case, #FF0000
. Whenever pure Red is used, that pixel is replaced with the base image's pixel.
Base image layering can be used recursively, and supports using other base image types besides RCT. The only limitation is the base image cannot be an RC8 (or 8-bit indexed pixel format) image
See here for information on image data encryption.
Rct and Rc8 images are stored in a form of LZ77 coding. The image data buffer contains both raw data and length-position pair commands that determine how the next span of data is decoded.
TODO: Add note in Format: Rc8 image that palette is stored in RGB24 order (rather than BGR24).
Constant | Rc8 | Rct | Description |
---|---|---|---|
ByteDepth | 1 |
3 |
Bytes per pixel |
BaseTable | Rc8[16] | Rct[32] | Shift table |
CmdShiftStart | 3 |
2 |
Bit offset of ShiftIndex |
CmdShiftMask | 0xf |
0x1f |
Bitmask of ShiftIndex |
CmdCountMask | 0x7 |
0x3 |
Bitmask (max) of Count |
CmdCountBase | 3 |
1 |
Base number of pixels added to Count |
Note that all values besides ByteDepth and CmdCountBase are derived from the length of BaseTable.
CmdCountBase is the minimum number of pixels copied, and is added to the Count value after unpacking it from the command and adding the optimal extra count.
static sbyte[] Rc8ShiftTable = {
-16, -32, -48, -64,
49, 33, 17, 1, -15, -31, -47,
34, 18, 2, -14, -30,
};
static sbyte[] RctShiftTable = {
-16, -32, -48, -64, -80, -96,
49, 33, 17, 1, -15, -31, -47,
50, 34, 18, 2, -14, -30, -46,
51, 35, 19, 3, -13, -29, -45,
36, 20, 4, -12, -28
};
static int CalculateShift(int width, sbyte shift) {
int shiftRow = (shift & 0xf);
return (shift >> 4) - (shiftRow * width);
}
static int[] InitShiftTable(int width, sbyte[] baseTable) {
return baseTable.Select(s => CalculateShift(width, s))
.ToArray();
}
Command (byte) | Extra (uint16) | |||
---|---|---|---|---|
Read | 0 |
nnnnnnn |
eeeeeeee eeeeeeee |
|
Copy8 | 1 |
ssss |
nnn |
eeeeeeee eeeeeeee |
CopyT | 1 |
sssss |
nn |
eeeeeeee eeeeeeee |
Flg | Shift | Num | Extra num |
static void CopyOverlapped(byte[] data, int src, int dst, int count) {
while(count > 0) {
int preceding = Math.Min(dst - src, count);
System.Buffer.BlockCopy(data, src, data, dst, preceding);
dst += preceding;
count -= preceding;
}
}
// Unpack and return pixel array:
// * 8-bit indexed | BGR24 pixel format
// * No stride padding (4-byte alignment)
byte[] Unpack(BinaryReader reader) {
int[] shiftTable = InitShiftTable(Width, BaseTable);
byte[] pixels = new byte[Width * Height * ByteDepth];
// Start by reading 1 pixel
reader.Read(pixels, 0, ByteDepth);
int pixelsLeft = pixels.Length - ByteDepth; // Number of bytes left in pixels buffer
int pos = ByteDepth; // Byte position in pixels buffer
while(pixelsLeft > 0) {
byte cmd = reader.ReadByte();
int count;
if((cmd & 0x80) == 0) { // READ RUN MODE:
count = cmd;
if(count == 0x7f) // Max value: Add extra count
count += reader.ReadUInt16();
count = (count + 1) * ByteDepth; // Min: 1 pixel
Debug.Assert(count <= pixelsLeft); // Bounds check: end of pixels[]
reader.Read(pixels, pos, count);
}
else { // COPY RUN MODE:
count = (cmd & CmdCountMask);
if(count == CmdCountMask) // Max value: Add extra count
count += reader.ReadUInt16();
//count = (count * ByteDepth) + 3; // Min: 3 bytes
count = (count + CmdCountBase) * ByteDepth; // Min: 3 bytes
// Shift determines where data is copied from,
// relative to the current buffer position. (always negative)
// ( & 0x7f to mask shift index, regardless of format)
int shiftIdx = (cmd & 0x7f) >> CmdShiftStart;
int shift = shiftTable[shiftIdx] * ByteDepth;
Debug.Assert(count <= pixelsLeft); // Bounds check: end of pixels[]
Debug.Assert(shift < 0 && pos+shift >= 0); // Bounds check: initialized data,
// start of pixels[]
CopyOverlapped(pixels, pos + shift, pos, count);
}
pixelsLeft -= count;
pos += count;
}
return pixels;
}