paavohtl Claude Opus 4.6 (1M context) commited on
Commit
f870fda
·
1 Parent(s): 113d28e

feat: port DDGame__LoadFonts and DDGameWrapper__LoadFontExtension to Rust

Browse files

Replaces the usercall bridge to 0x570F30 with pure Rust that loads 28
bitmap fonts, 23 font extensions, and sets the font palette via typed
DDDisplay vtable calls. Adds three new DDDisplay vtable slots (load_font,
load_font_extension, set_font_palette) and expands gfx_color_table from
9 to 11 entries, absorbing former _unknown_7330 as font palette data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/address.rs CHANGED
@@ -104,7 +104,9 @@ pub mod va {
104
  /// DDGame__InitVersionFlags — stdcall(ddgame_wrapper)
105
  fn/Stdcall DDGAME_INIT_VERSION_FLAGS = 0x0052_5BE0;
106
  /// DDGame__LoadFonts — loads .fnt font resources into the display.
107
- fn DDGAME_LOAD_FONTS = 0x0057_0F30;
 
 
108
  /// DDGame__LoadHudAndWeaponSprites
109
  fn/Thiscall DDGAME_LOAD_HUD_AND_WEAPON_SPRITES = 0x0053_D0E0;
110
  /// DDGame__InitPaletteGradientSprites
 
104
  /// DDGame__InitVersionFlags — stdcall(ddgame_wrapper)
105
  fn/Stdcall DDGAME_INIT_VERSION_FLAGS = 0x0052_5BE0;
106
  /// DDGame__LoadFonts — loads .fnt font resources into the display.
107
+ fn/Usercall DDGAME_LOAD_FONTS = 0x0057_0F30;
108
+ /// DDGameWrapper__LoadFontExtension — loads .fex font extension for a font slot.
109
+ fn/Stdcall DDGAME_WRAPPER_LOAD_FONT_EXTENSION = 0x0057_0E80;
110
  /// DDGame__LoadHudAndWeaponSprites
111
  fn/Thiscall DDGAME_LOAD_HUD_AND_WEAPON_SPRITES = 0x0053_D0E0;
112
  /// DDGame__InitPaletteGradientSprites
crates/openwa-core/src/display/dd_display.rs CHANGED
@@ -29,6 +29,41 @@ pub struct DDDisplayVtable {
29
  /// set layer visibility
30
  #[slot(23)]
31
  pub set_layer_visibility: fn(this: *mut DDDisplay, layer: i32, value: i32),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  /// load sprite with flag (RET 0x14)
33
  #[slot(31)]
34
  pub load_sprite: fn(
 
29
  /// set layer visibility
30
  #[slot(23)]
31
  pub set_layer_visibility: fn(this: *mut DDDisplay, layer: i32, value: i32),
32
+ /// load .fnt bitmap font into a font slot (RET 0x10)
33
+ ///
34
+ /// DDDisplay__LoadFont (0x523560).
35
+ /// mode: font source (1-3, usually 1). font_id: slot index (1-based, max 0x1F).
36
+ /// gfx: GfxDir archive to load from. filename: .fnt resource name.
37
+ #[slot(34)]
38
+ pub load_font: fn(
39
+ this: *mut DDDisplay,
40
+ mode: i32,
41
+ font_id: i32,
42
+ gfx: *mut u8,
43
+ filename: *const core::ffi::c_char,
44
+ ) -> u32,
45
+ /// load .fex font extension (extra glyphs) for a font slot (RET 0x14)
46
+ ///
47
+ /// DDDisplay__LoadFontExtension (0x523620).
48
+ /// font_id: slot index. path: filesystem path to .fex file.
49
+ /// char_map: 61-byte character code table. palette_value: color from GfxColorTable.
50
+ /// flag: unknown (always 0 from DDGameWrapper__LoadFontExtension).
51
+ #[slot(35)]
52
+ pub load_font_extension: fn(
53
+ this: *mut DDDisplay,
54
+ font_id: i32,
55
+ path: *const core::ffi::c_char,
56
+ char_map: *const u8,
57
+ palette_value: u32,
58
+ flag: i32,
59
+ ) -> u32,
60
+ /// set font palette for all loaded fonts (RET 0x8)
61
+ ///
62
+ /// DDDisplay__SetFontPalette (0x523690).
63
+ /// font_count: total number of font slots loaded.
64
+ /// palette_value: palette/color entry from DDGame GfxColorTable.
65
+ #[slot(36)]
66
+ pub set_font_palette: fn(this: *mut DDDisplay, font_count: u32, palette_value: u32),
67
  /// load sprite with flag (RET 0x14)
68
  #[slot(31)]
69
  pub load_sprite: fn(
crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -230,14 +230,13 @@ pub struct DDGame {
230
  pub _field_7304: u32,
231
  /// 0x7308: Sprite/gfx dimension data (passed to GFX_DIR_LOAD_SPRITES).
232
  pub gfx_sprite_data: [u8; 0x730C - 0x7308],
233
- /// 0x730C-0x732F: GfxDir color table (9 entries).
234
  /// Populated from colours.img pixel row: `color_table[i] = get_pixel(sprite, i, 0)`.
235
  /// Known entries:
236
  /// - [6] (0x7324): Crosshair line color (DrawPolygon param_2)
237
  /// - [8] (0x732C): Crosshair line style (DrawPolygon param_1)
238
- pub gfx_color_table: [u32; 9],
239
- /// 0x7330-0x7337: Unknown
240
- pub _unknown_7330: [u8; 8],
241
  /// 0x7338: Fill pixel value
242
  pub fill_pixel: u32,
243
  /// 0x733C-0x733F: Unknown
 
230
  pub _field_7304: u32,
231
  /// 0x7308: Sprite/gfx dimension data (passed to GFX_DIR_LOAD_SPRITES).
232
  pub gfx_sprite_data: [u8; 0x730C - 0x7308],
233
+ /// 0x730C-0x7337: GfxDir color table (11 entries).
234
  /// Populated from colours.img pixel row: `color_table[i] = get_pixel(sprite, i, 0)`.
235
  /// Known entries:
236
  /// - [6] (0x7324): Crosshair line color (DrawPolygon param_2)
237
  /// - [8] (0x732C): Crosshair line style (DrawPolygon param_1)
238
+ /// - [9]-[10] (0x7330-0x7334): Font palette entries (used by LoadFontExtension)
239
+ pub gfx_color_table: [u32; 11],
 
240
  /// 0x7338: Fill pixel value
241
  pub fill_pixel: u32,
242
  /// 0x733C-0x733F: Unknown
crates/openwa-core/src/engine/ddgame_constructor.rs CHANGED
@@ -153,7 +153,7 @@ pub fn init_constructor_addrs() {
153
  unsafe {
154
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
155
  FUN_570A90_ADDR = rb(va::FUN_570A90);
156
- FUN_570F30_ADDR = rb(va::DDGAME_LOAD_FONTS);
157
  LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
158
  LOADING_PROGRESS_TICK_ADDR = rb(va::DDGAME_WRAPPER_LOADING_PROGRESS_TICK);
159
  GFX_LOAD_SPRITES_ADDR = rb(va::GFX_DIR_LOAD_SPRITES);
@@ -627,8 +627,7 @@ unsafe fn init_graphics_and_resources(
627
 
628
  // ── Audio init (non-headless + sound available) ──
629
  if !is_headless {
630
- // FUN_570F30: font loading (usercall ESI=wrapper) — NOT audio related
631
- call_usercall_esi(wrapper, FUN_570F30_ADDR);
632
  if !(*ddgame).sound.is_null() {
633
  load_effect_wavs(wrapper);
634
  // DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
@@ -1067,11 +1066,11 @@ unsafe fn init_graphics_and_resources(
1067
 
1068
  // Statics for usercall bridge addresses
1069
  static mut FUN_570A90_ADDR: u32 = 0;
1070
- static mut FUN_570F30_ADDR: u32 = 0;
1071
  static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
1072
  static mut LOADING_PROGRESS_TICK_ADDR: u32 = 0;
1073
 
1074
- /// Bridge: usercall(ESI=wrapper), plain RET. Used by FUN_570E20, FUN_570F30, LoadSpeechBanks.
1075
  #[cfg(target_arch = "x86")]
1076
  #[unsafe(naked)]
1077
  unsafe extern "C" fn call_usercall_esi(_wrapper: *mut DDGameWrapper, _addr: u32) {
 
153
  unsafe {
154
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
155
  FUN_570A90_ADDR = rb(va::FUN_570A90);
156
+
157
  LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
158
  LOADING_PROGRESS_TICK_ADDR = rb(va::DDGAME_WRAPPER_LOADING_PROGRESS_TICK);
159
  GFX_LOAD_SPRITES_ADDR = rb(va::GFX_DIR_LOAD_SPRITES);
 
627
 
628
  // ── Audio init (non-headless + sound available) ──
629
  if !is_headless {
630
+ crate::engine::ddgame_load_fonts::load_fonts(wrapper);
 
631
  if !(*ddgame).sound.is_null() {
632
  load_effect_wavs(wrapper);
633
  // DSSound_LoadAllSpeechBanks: the original is hooked to our Rust
 
1066
 
1067
  // Statics for usercall bridge addresses
1068
  static mut FUN_570A90_ADDR: u32 = 0;
1069
+
1070
  static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
1071
  static mut LOADING_PROGRESS_TICK_ADDR: u32 = 0;
1072
 
1073
+ /// Bridge: usercall(ESI=wrapper), plain RET. Used by FUN_570E20, LoadSpeechBanks.
1074
  #[cfg(target_arch = "x86")]
1075
  #[unsafe(naked)]
1076
  unsafe extern "C" fn call_usercall_esi(_wrapper: *mut DDGameWrapper, _addr: u32) {
crates/openwa-core/src/engine/ddgame_load_fonts.rs ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! DDGame__LoadFonts (0x570F30) and DDGameWrapper__LoadFontExtension (0x570E80).
2
+ //!
3
+ //! Loads 28 bitmap fonts (.fnt) and 23 font extensions (.fex) into the display,
4
+ //! then sets the font palette. Called from DDGame__Constructor during loading.
5
+
6
+ use core::ffi::c_char;
7
+ use heapless::CString;
8
+
9
+ use crate::display::dd_display::DDDisplay;
10
+ use crate::engine::ddgame_wrapper::DDGameWrapper;
11
+
12
+ /// Total number of font slots loaded (1-based, so slots 1..=28).
13
+ const FONT_COUNT: u32 = 0x1C;
14
+
15
+ /// Number of font slots that get font extensions loaded (1..=23).
16
+ const FONT_EXT_COUNT: u32 = 0x17;
17
+
18
+ /// Font filenames (.fnt), indexed 1..=28 (slot 0 unused).
19
+ const FONT_FILENAMES: [&[u8]; 29] = [
20
+ b"\0", // [0] unused
21
+ b"smlred1.fnt\0", // [1]
22
+ b"smlblu1.fnt\0", // [2]
23
+ b"smlgrn1.fnt\0", // [3]
24
+ b"smlyel1.fnt\0", // [4]
25
+ b"smlppl1.fnt\0", // [5]
26
+ b"smlcyn1.fnt\0", // [6]
27
+ b"smlwht1.fnt\0", // [7]
28
+ b"smlwht2.fnt\0", // [8]
29
+ b"stdred1.fnt\0", // [9]
30
+ b"stdblu1.fnt\0", // [10]
31
+ b"stdgrn1.fnt\0", // [11]
32
+ b"stdyel1.fnt\0", // [12]
33
+ b"stdppl1.fnt\0", // [13]
34
+ b"stdcyn1.fnt\0", // [14]
35
+ b"stdwht1.fnt\0", // [15]
36
+ b"stdwht2.fnt\0", // [16]
37
+ b"medred1.fnt\0", // [17]
38
+ b"medblu1.fnt\0", // [18]
39
+ b"medgrn1.fnt\0", // [19]
40
+ b"medyel1.fnt\0", // [20]
41
+ b"medppl1.fnt\0", // [21]
42
+ b"medcyn1.fnt\0", // [22]
43
+ b"medwht1.fnt\0", // [23]
44
+ b"lnumred1.fnt\0", // [24]
45
+ b"lnumwht1.fnt\0", // [25]
46
+ b"lnumred2.fnt\0", // [26]
47
+ b"lnumwht2.fnt\0", // [27]
48
+ b"digiwht.fnt\0", // [28]
49
+ ];
50
+
51
+ /// Font ID → size category index.
52
+ /// 0 = sml, 1 = std, 2 = med, 3 = bignum, 4 = digi.
53
+ /// Original data at 0x6A90E8.
54
+ const FONT_SIZE_CATEGORY: [u32; 29] = [
55
+ 5, // [0] unused
56
+ 0, 0, 0, 0, 0, 0, 0, 0, // [1-8] sml
57
+ 1, 1, 1, 1, 1, 1, 1, 1, // [9-16] std
58
+ 2, 2, 2, 2, 2, 2, 2, // [17-23] med
59
+ 3, 3, 3, 3, // [24-27] bignum
60
+ 4, // [28] digi
61
+ ];
62
+
63
+ /// Font ID → color index. Original data at 0x6A9160.
64
+ const FONT_COLOR_INDEX: [u32; 29] = [
65
+ 10, // [0]
66
+ 0, 1, 2, 3, 4, 5, 6, 7, // [1-8] red, blu, grn, yel, ppl, cyn, wht1, wht2
67
+ 0, 1, 2, 3, 4, 5, 6, 7, // [9-16]
68
+ 0, 1, 2, 3, 4, 5, 6, // [17-23]
69
+ 9, 6, 8, 7, // [24-27]
70
+ 6, // [28]
71
+ ];
72
+
73
+ /// Color index → gfx_color_table index (0-based).
74
+ /// Original data at 0x6A91D4 (1-based offsets from DDGame+0x7308, converted to 0-based).
75
+ const COLOR_TABLE_INDEX: [usize; 10] = [0, 1, 2, 3, 4, 5, 8, 6, 9, 10];
76
+
77
+ /// Size category → .fex path prefix.
78
+ const SIZE_PREFIXES: [&str; 5] = ["sml", "std", "med", "bignum", ""];
79
+
80
+ /// Extended character map (61 bytes). Original data at 0x66ACD8.
81
+ /// Defines which character codes are present in .fex font extension files.
82
+ const FONT_EXT_CHAR_MAP: [u8; 61] = [
83
+ 0xBF, 0xA1, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D,
84
+ 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E,
85
+ 0xA2, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
86
+ 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0x0A, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0x00,
87
+ ];
88
+
89
+ /// Load a font extension (.fex) for the given font slot.
90
+ ///
91
+ /// Port of DDGameWrapper__LoadFontExtension (0x570E80, stdcall, RET 0x8).
92
+ /// Formats the .fex path from the font's size category, copies the character map,
93
+ /// resolves the palette value, and calls DDDisplay::load_font_extension.
94
+ unsafe fn load_font_extension(wrapper: *mut DDGameWrapper, font_id: u32) {
95
+ let size_category = FONT_SIZE_CATEGORY[font_id as usize];
96
+ let prefix = SIZE_PREFIXES[size_category as usize];
97
+
98
+ // Format path: "Data\Gfx\FontExt\{prefix}2.fex"
99
+ let mut path: CString<64> = CString::new();
100
+ let _ = path.extend_from_bytes(b"Data\\Gfx\\FontExt\\");
101
+ let _ = path.extend_from_bytes(prefix.as_bytes());
102
+ let _ = path.extend_from_bytes(b"2.fex");
103
+
104
+ // Copy character map to stack (original does REP MOVSD + MOVSB)
105
+ let char_map = FONT_EXT_CHAR_MAP;
106
+
107
+ // Resolve palette value: font_id → color_index → gfx_color_table entry
108
+ let color_index = FONT_COLOR_INDEX[font_id as usize];
109
+ let table_index = COLOR_TABLE_INDEX[color_index as usize];
110
+ let ddgame = (*wrapper).ddgame;
111
+ let palette_value = (*ddgame).gfx_color_table[table_index];
112
+
113
+ let display = (*wrapper).display;
114
+ DDDisplay::load_font_extension_raw(
115
+ display,
116
+ font_id as i32,
117
+ path.as_ptr() as *const c_char,
118
+ char_map.as_ptr(),
119
+ palette_value,
120
+ 0,
121
+ );
122
+ }
123
+
124
+ /// Load all bitmap fonts and font extensions.
125
+ ///
126
+ /// Port of DDGame__LoadFonts (0x570F30, usercall ESI=DDGameWrapper).
127
+ ///
128
+ /// Phase 1: Loads 28 .fnt bitmap fonts via DDDisplay::load_font.
129
+ /// Phase 2: Loads .fex font extensions for font slots 1-23.
130
+ /// Phase 3: Sets the font palette via DDDisplay::set_font_palette.
131
+ pub unsafe fn load_fonts(wrapper: *mut DDGameWrapper) {
132
+ let display = (*wrapper).display;
133
+ let gfx_dir = (*wrapper).primary_gfx_dir;
134
+
135
+ // Phase 1: Load all 28 .fnt font files
136
+ for font_id in 1..=FONT_COUNT {
137
+ let filename = FONT_FILENAMES[font_id as usize];
138
+ DDDisplay::load_font_raw(
139
+ display,
140
+ 1,
141
+ font_id as i32,
142
+ gfx_dir,
143
+ filename.as_ptr() as *const c_char,
144
+ );
145
+ }
146
+
147
+ // Phase 2: Load font extensions for slots 1-23
148
+ for font_id in 1..=FONT_EXT_COUNT {
149
+ load_font_extension(wrapper, font_id);
150
+ }
151
+
152
+ // Phase 3: Set font palette — passes font count and gfx_color_table[7].
153
+ let ddgame = (*wrapper).ddgame;
154
+ let palette_value = (*ddgame).gfx_color_table[7];
155
+ DDDisplay::set_font_palette_raw(display, FONT_COUNT, palette_value);
156
+ }
crates/openwa-core/src/engine/mod.rs CHANGED
@@ -1,5 +1,6 @@
1
  pub mod ddgame;
2
  pub mod ddgame_constructor;
 
3
  pub mod ddgame_wrapper;
4
  pub mod game_info;
5
  pub mod game_session;
 
1
  pub mod ddgame;
2
  pub mod ddgame_constructor;
3
+ pub mod ddgame_load_fonts;
4
  pub mod ddgame_wrapper;
5
  pub mod game_info;
6
  pub mod game_session;