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.

Splash Image

LoadingDraw_C_splash_256_128

Here's how it looks in-game

first

The circular throbber does not appear to be embedded.

Tribal Throbber

LoadingDraw_C_throbber_64_64

And in-game

20240205144839_1

And you can see the bug where the throbber is drawn over the cursor. The background image does not appear to be embedded.

Decoding

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()
⚠️ **GitHub.com Fallback** ⚠️