LoadingDraw_C - widberg/fmtk GitHub Wiki
The class LoadingDraw_C has some bitmap data embedded in the executable. Presumably, this is so the game has something to show before the first BigFile is loaded. These images are RLE-encoded and decode to ARGB. Resources for inspecting the encoded and decoded image data are located at the bottom of this page.
Here's how it looks in-game

The circular throbber does not appear to be embedded.
Tribal Throbber
And in-game

And you can see the bug where the throbber is drawn over the cursor. The background image does not appear to be embedded.
The RLE-encoded data for the splash image can be found at 0x0099673C and is 0x9E48 bytes. The throbber is at 0x009A0584 and is 0xFCC bytes. The bytes can be extracted from the executable using ImHex, and the decoded ARGB data can be viewed with Kuriimu2 Raw Image Viewer. A Python decoding script, input, and output files can be found in LoadingDraw_C.zip if you are interested. The function that does the decoding and a Python script for decoding files are included below.
void __usercall read_data_as_bitmap_data(void *output@<ecx>, int output_size_in_ints@<eax>, unsigned __int8 *input_cursor@<edx>, int bpp)
{
char *output_cursor; // esi
char *output_end; // ebp
int flags_1; // eax
unsigned __int8 *input_cursor_plus_one; // edi
int length_2; // eax
int length_1; // ebx
int flags; // eax
int length; // eax
output_cursor = (char *)output;
output_end = (char *)output + 4 * output_size_in_ints;
if ( (unsigned int)bpp <= 3 )
{
if ( output < output_end )
{
do
{
flags = *input_cursor++;
if ( (flags & 0x80u) == 0 )
{
for ( ; flags; --flags )
{
*output_cursor = input_cursor[2];
output_cursor[1] = input_cursor[1];
output_cursor[2] = *input_cursor;
output_cursor[3] = -1;
output_cursor += 4;
input_cursor += 3;
}
}
else
{
length = flags - 128;
do
{
*output_cursor = input_cursor[2];
output_cursor[1] = input_cursor[1];
output_cursor[2] = *input_cursor;
output_cursor[3] = -1;
output_cursor += 4;
--length;
}
while ( length );
input_cursor += 3;
}
}
while ( output_cursor < output_end );
}
}
else if ( output < output_end )
{
do
{
flags_1 = *input_cursor;
input_cursor_plus_one = input_cursor + 1;
if ( (flags_1 & 0x80u) == 0 )
{
length_1 = 4 * flags_1;
memcpy(output_cursor, input_cursor_plus_one, 4 * flags_1);
output_cursor += length_1;
input_cursor = &input_cursor_plus_one[length_1];
}
else
{
length_2 = flags_1 - 128;
do
{
*(_DWORD *)output_cursor = *(_DWORD *)input_cursor_plus_one;
output_cursor += 4;
--length_2;
}
while ( length_2 );
input_cursor = input_cursor_plus_one + 4;
}
}
while ( output_cursor < output_end );
}
}The Python script from the ZIP is included here for searchability
import argparse
import sys
from pathlib import Path
def read_data_as_bitmap_data(
input_data: bytes, output_size_in_ints: int, bpp: int
) -> bytes:
"""
Decodes RLE-like bitmap data into ARGB output.
:param input_data: compressed input byte stream
:param output_size_in_ints: number of output pixels (32-bit each)
:param bpp: bits per pixel (<=3 uses RGB path, >3 uses ARGB path)
:return: decoded ARGB bytes
"""
output = bytearray(4 * output_size_in_ints)
output_cursor = 0
output_end = 4 * output_size_in_ints
input_cursor = 0
# --- 24-bit input path (RGB -> ARGB) ---
if bpp <= 3:
while output_cursor < output_end:
flags = input_data[input_cursor]
input_cursor += 1
# Literal run
if (flags & 0x80) == 0:
for _ in range(flags):
r = input_data[input_cursor]
g = input_data[input_cursor + 1]
b = input_data[input_cursor + 2]
output[output_cursor : output_cursor + 4] = bytes((b, g, r, 0xFF))
output_cursor += 4
input_cursor += 3
# Repeated run
else:
length = flags - 128
r = input_data[input_cursor]
g = input_data[input_cursor + 1]
b = input_data[input_cursor + 2]
pixel = bytes((b, g, r, 0xFF))
for _ in range(length):
output[output_cursor : output_cursor + 4] = pixel
output_cursor += 4
input_cursor += 3
# --- 32-bit input path (ARGB passthrough) ---
else:
while output_cursor < output_end:
flags = input_data[input_cursor]
input_cursor += 1
# Literal run
if (flags & 0x80) == 0:
length_bytes = 4 * flags
output[output_cursor : output_cursor + length_bytes] = input_data[
input_cursor : input_cursor + length_bytes
]
output_cursor += length_bytes
input_cursor += length_bytes
# Repeated run
else:
length = flags - 128
pixel = input_data[input_cursor : input_cursor + 4]
for _ in range(length):
output[output_cursor : output_cursor + 4] = pixel
output_cursor += 4
input_cursor += 4
return bytes(output)
def main():
parser = argparse.ArgumentParser(
description="Decode RLE-compressed bitmap data into ARGB."
)
parser.add_argument("input_file", type=Path, help="Path to compressed input file")
parser.add_argument(
"output_file", type=Path, help="Path to write decoded ARGB output"
)
parser.add_argument(
"--pixels",
type=lambda x: int(x, 0),
required=True,
help="Number of output pixels (output_size_in_ints)",
)
parser.add_argument(
"--bpp",
type=lambda x: int(x, 0),
required=True,
help="Bits per pixel (<=3 for RGB, >3 for ARGB)",
)
args = parser.parse_args()
try:
input_data = args.input_file.read_bytes()
except OSError as e:
print(f"Failed to read input file: {e}", file=sys.stderr)
sys.exit(1)
output_data = read_data_as_bitmap_data(
input_data=input_data, output_size_in_ints=args.pixels, bpp=args.bpp
)
try:
args.output_file.write_bytes(output_data)
except OSError as e:
print(f"Failed to write output file: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()