Skip to main content
Crimsonland stores game configuration in a fixed-size binary blob called crimson.cfg. This is not the save file (that’s game.cfg).

File Properties

Path: <game_dir>\crimson.cfg
Size: 0x480 bytes (1152 bytes)
Endianness: Little-endian
Encryption: None

Binary Structure

typedef struct {
    u8 sound_disabled;              // +0x000
    u8 music_disabled;              // +0x001
    u8 highscore_date_mode;         // +0x002
    u8 highscore_duplicate_mode;    // +0x003
    u8 hud_indicator_toggle[2];     // +0x004
    u8 reserved0_06[8];             // +0x006
    u8 fx_detail_flag0;             // +0x00e
    u8 reserved0_0f;                // +0x00f
    u8 fx_detail_flag1;             // +0x010
    u8 fx_detail_flag2;             // +0x011
    u8 reserved0_12[2];             // +0x012
    i32 player_count;               // +0x014
    i32 game_mode;                  // +0x018
    i32 player_mode_flags;          // +0x01c
    u8 reserved0_20[0x24];          // +0x020
    i32 aim_scheme;                 // +0x044
    u8 reserved0_48[0x28];          // +0x048
    float texture_scale;            // +0x070
    char player_name_buf[12];       // +0x074
    i32 name_slot_selected;         // +0x080
    i32 name_slot_count;            // +0x084
    i32 name_slot_order[8];         // +0x088
    char saved_names[8][27];        // +0x0a8
    char player_name[32];           // +0x180
    i32 player_name_length;         // +0x1a0
    u8 reserved1[0x14];             // +0x1a4
    i32 display_bpp;                // +0x1b8
    i32 screen_width;               // +0x1bc
    i32 screen_height;              // +0x1c0
    i32 windowed;                   // +0x1c4
    i32 keybinds_p1[13];            // +0x1c8
    u8 reserved2[0x0c];             // +0x1fc
    i32 keybinds_p2[13];            // +0x208
    u8 reserved3[0x0c];             // +0x23c
    u8 reserved4[0x200];            // +0x248
    u8 hardcore;                    // +0x448
    u8 full_version;                // +0x449
    u8 reserved5[2];                // +0x44a
    i32 perk_prompt_counter;        // +0x44c
    u8 reserved6[0x14];             // +0x450
    float sfx_volume;               // +0x464
    float music_volume;             // +0x468
    u8 gore_disabled;               // +0x46c
    u8 score_load_gate;             // +0x46d
    u8 reserved7[2];                // +0x46e
    i32 detail_preset;              // +0x470
    float mouse_sensitivity;        // +0x474
    i32 key_pick_perk;              // +0x478
    i32 key_reload;                 // +0x47c
} crimson_cfg_t;  // 0x480 bytes total

Key Fields

Display Settings

OffsetFieldTypeDefaultDescription
0x1b8display_bppi3232Bits per pixel (16/32)
0x1bcscreen_widthi32800Screen width in pixels
0x1c0screen_heighti32600Screen height in pixels
0x1c4windowedi320Windowed mode (0=fullscreen, 1=windowed)

Audio Settings

OffsetFieldTypeDefaultDescription
0x000sound_disabledu80Sound FX disabled flag
0x001music_disabledu80Music disabled flag
0x464sfx_volumefloat1.0SFX volume (0.0-1.0)
0x468music_volumefloat1.0Music volume (0.0-1.0)

Graphics Detail

OffsetFieldTypeDefaultDescription
0x00efx_detail_flag0u81Shadows enabled
0x010fx_detail_flag1u81Extra FX passes
0x011fx_detail_flag2u81Additional effects
0x46cgore_disabledu80Gore disabled flag
0x470detail_preseti325Graphics preset (1-5)
Detail preset mapping:
Presetflag0flag1flag2Description
1000Low
200unchangedMedium
3111High
4111Very High
5111Ultra

Keybinds

Player 1: Offset 0x1c8 (13 dwords)
Player 2: Offset 0x208 (13 dwords)
Each block:
IndexActionP1 DefaultP2 Default
0Move Forward0x11 (W)0xc8 (Up)
1Move Backward0x1f (S)0xd0 (Down)
2Turn Left0x1e (A)0xcb (Left)
3Turn Right0x20 (D)0xcd (Right)
4Fire0x100 (LMB)0x9d (RCtrl)
5-6Reserved0x17e0x17e
7Aim Left0x10 (Q)0xd3 (Del)
8Aim Right0x12 (E)0xd1 (PgDn)
9Axis Aim Y0x140 (Mouse Y)0x17e
10Axis Aim X0x13f (Mouse X)0x17e
11Axis Move Y0x1530x17e
12Axis Move X0x17e0x17e
Note: Values are DirectInput scan codes.

Player Names

OffsetFieldTypeDescription
0x0a8saved_nameschar[8][27]8 saved player names (27 bytes each)
0x180player_namechar[32]Current player name
0x1a0player_name_lengthi32Length of player_name

Example Config

Hex dump of a typical crimson.cfg:
Offset  Hex                                           ASCII
------  -----------------------------------------    -----
0x000   00 00 00 00 00 00 00 00 00 00 00 00 00 00   ..............  (audio/flags)
0x00e   01 00 01 01 00 00                           ......          (fx flags)
0x1b8   20 00 00 00                                   ...            (bpp=32)
0x1bc   00 04 00 00                                  ....            (width=1024)
0x1c0   00 03 00 00                                  ....            (height=768)
0x1c4   01 00 00 00                                  ....            (windowed=1)
0x1c8   11 00 00 00 1f 00 00 00 ...                 ....            (p1 keybinds)
0x464   00 00 80 3f                                  ...?            (sfx_vol=1.0)
0x468   00 00 80 3f                                  ...?            (music_vol=1.0)
0x470   05 00 00 00                                  ....            (preset=5)

Python Decoder

import struct
from pathlib import Path

class CrimsonConfig:
    def __init__(self, data: bytes):
        if len(data) != 0x480:
            raise ValueError(f"Invalid config size: {len(data)}")
        
        self.data = data
    
    def get_u8(self, offset: int) -> int:
        return self.data[offset]
    
    def get_i32(self, offset: int) -> int:
        return struct.unpack_from("<i", self.data, offset)[0]
    
    def get_float(self, offset: int) -> float:
        return struct.unpack_from("<f", self.data, offset)[0]
    
    def get_string(self, offset: int, length: int) -> str:
        raw = self.data[offset:offset+length]
        return raw.split(b'\0')[0].decode('utf-8', errors='replace')
    
    @property
    def screen_width(self) -> int:
        return self.get_i32(0x1bc)
    
    @property
    def screen_height(self) -> int:
        return self.get_i32(0x1c0)
    
    @property
    def windowed(self) -> bool:
        return self.get_i32(0x1c4) != 0
    
    @property
    def sfx_volume(self) -> float:
        return self.get_float(0x464)
    
    @property
    def music_volume(self) -> float:
        return self.get_float(0x468)
    
    @property
    def player_name(self) -> str:
        return self.get_string(0x180, 32)
    
    def get_keybind(self, player: int, action: int) -> int:
        """Get keybind for player (0 or 1) and action index (0-12)."""
        base = 0x1c8 if player == 0 else 0x208
        return self.get_i32(base + action * 4)

# Usage:
config = CrimsonConfig(Path("crimson.cfg").read_bytes())
print(f"Resolution: {config.screen_width}x{config.screen_height}")
print(f"Windowed: {config.windowed}")
print(f"Player: {config.player_name}")
print(f"P1 fire key: 0x{config.get_keybind(0, 4):x}")

Save Files

Save game format (game.cfg)

PAQ Archives

Asset archive format

Build docs developers (and LLMs) love