paavohtl Claude Opus 4.6 (1M context) commited on
Commit
5d6bf83
·
1 Parent(s): 7cb030a

fix: palette init used DDDisplay pointer arithmetic (sizeof scaling)

Browse files

disp.add(0x3120) with disp: *mut DDDisplay addressed 0x3120 * 4 bytes
(sizeof DDDisplay = 4) instead of 0x3120 bytes. Cast to *mut u8 for
raw byte offsets into the DisplayBase region.

Bug introduced in DDDisplayVtable commit when disp type changed from
*mut u8 to *mut DDDisplay.

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -836,18 +836,19 @@ unsafe fn init_graphics_and_resources(
836
  DDDisplay::load_sprite(disp, 1, 1, 0, gfx_dir, rb(va::STR_CDROM_SPR) as *const u8);
837
  DDDisplay::set_layer_visibility(disp, 1, -100);
838
 
839
- // Palette slot range init
840
- let palette_range_ptr = *(disp.add(0x3120) as *const *mut i16);
841
- if !palette_range_ptr.is_null() && *(disp.add(0x3534) as *const i32) == 0 {
 
842
  let start = *palette_range_ptr as u32;
843
  let end = (*palette_range_ptr.add(1) as u32) + 1;
844
  if start < end {
845
  for i in start..end {
846
- *(disp.add(0x312C + i as usize * 4) as *mut u32) = 1;
847
  }
848
  }
849
- crate::wa_alloc::wa_free(*(disp.add(0x3120) as *const *mut u8));
850
- *(disp.add(0x3120) as *mut u32) = 0;
851
  }
852
  }
853
 
 
836
  DDDisplay::load_sprite(disp, 1, 1, 0, gfx_dir, rb(va::STR_CDROM_SPR) as *const u8);
837
  DDDisplay::set_layer_visibility(disp, 1, -100);
838
 
839
+ // Palette slot range init (raw byte offsets into DisplayBase)
840
+ let disp_raw = disp as *mut u8;
841
+ let palette_range_ptr = *(disp_raw.add(0x3120) as *const *mut i16);
842
+ if !palette_range_ptr.is_null() && *(disp_raw.add(0x3534) as *const i32) == 0 {
843
  let start = *palette_range_ptr as u32;
844
  let end = (*palette_range_ptr.add(1) as u32) + 1;
845
  if start < end {
846
  for i in start..end {
847
+ *(disp_raw.add(0x312C + i as usize * 4) as *mut u32) = 1;
848
  }
849
  }
850
+ crate::wa_alloc::wa_free(*(disp_raw.add(0x3120) as *const *mut u8));
851
+ *(disp_raw.add(0x3120) as *mut u32) = 0;
852
  }
853
  }
854
 
crates/openwa-core/src/render/gfx_handler.rs ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! GfxHandler/GfxDir resource loading subsystem.
2
+
3
+ use crate::address::va;
4
+ use crate::rebase::rb;
5
+ use crate::wa_alloc::wa_malloc;
6
+
7
+ /// Entry in a GfxDir hash bucket linked list.
8
+ /// Each entry maps a name string to a cached resource value.
9
+ #[repr(C)]
10
+ pub struct GfxDirEntry {
11
+ pub next: *mut GfxDirEntry,
12
+ /// Passed to GfxHandler vtable[2] for cached resource lookup.
13
+ pub value: u32,
14
+ pub _unknown_08: u32,
15
+ // +0x0C: null-terminated name (variable-length, not in struct)
16
+ }
17
+
18
+ impl GfxDirEntry {
19
+ /// Name string at +0x0C (immediately after the fixed fields).
20
+ pub unsafe fn name_ptr(&self) -> *const u8 {
21
+ (self as *const Self as *const u8).add(0x0C)
22
+ }
23
+ }
24
+
25
+ /// Hash function for GfxDir entry lookup (FUN_566390).
26
+ ///
27
+ /// 10-bit CRC-like hash over the global name buffer at 0x8ACF58.
28
+ /// Operates on the already-lowercased name.
29
+ ///
30
+ /// # Safety
31
+ /// `name` must be a valid null-terminated C string pointer.
32
+ unsafe fn gfx_dir_hash(name: *const u8) -> u32 {
33
+ let mut hash: u32 = 0;
34
+ let mut p = name;
35
+ while *p != 0 {
36
+ let bit9 = (hash >> 9) & 1;
37
+ hash = ((hash << 1) | bit9) & 0x3FF;
38
+ hash = hash.wrapping_add(*p as u32) & 0x3FF;
39
+ p = p.add(1);
40
+ }
41
+ hash
42
+ }
43
+
44
+ /// Pure Rust implementation of GfxDir__FindEntry (0x566520).
45
+ ///
46
+ /// Convention: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4.
47
+ ///
48
+ /// Looks up a name in the GfxHandler's hash table. Names are case-insensitive.
49
+ /// Supports `|`-separated fallback names (e.g. "path\\file.img|fallback.img").
50
+ ///
51
+ /// Returns entry pointer or null. Entry layout:
52
+ /// - entry+0x00: next pointer (linked list)
53
+ /// - entry+0x04: value (passed to vtable[2] for cached load)
54
+ /// - entry+0x08: unknown
55
+ /// - entry+0x0C: name string (null-terminated, lowercase)
56
+ ///
57
+ /// # Safety
58
+ /// `gfx_handler` must be a valid GfxHandler with initialized hash table at +0x04.
59
+ /// `name` must be a valid null-terminated C string.
60
+ #[cfg(target_arch = "x86")]
61
+ pub unsafe fn gfx_dir_find_entry(name: *const u8, gfx_handler: *mut u8) -> *mut u8 {
62
+ let mut current_name = name;
63
+
64
+ loop {
65
+ // Copy name to stack buffer (max 0xFF chars + null)
66
+ let mut buf = [0u8; 0x100];
67
+ let mut i = 0usize;
68
+ let mut src = current_name;
69
+ while *src != 0 && i < 0xFF {
70
+ buf[i] = *src;
71
+ src = src.add(1);
72
+ i += 1;
73
+ }
74
+ buf[i] = 0;
75
+
76
+ // Find '|' separator in buffer
77
+ let mut pipe_pos: Option<usize> = None;
78
+ for j in 0..i {
79
+ if buf[j] == b'|' {
80
+ buf[j] = 0; // truncate at pipe
81
+ pipe_pos = Some(j);
82
+ break;
83
+ }
84
+ }
85
+
86
+ // Compute next_name: pointer into original string after '|'
87
+ let next_name: *const u8 = if let Some(pos) = pipe_pos {
88
+ // Offset of '|' in buffer = pos
89
+ // Same offset in original string → current_name + pos + 1
90
+ current_name.add(pos + 1)
91
+ } else {
92
+ core::ptr::null()
93
+ };
94
+
95
+ // Lowercase A-Z in buffer
96
+ for j in 0..buf.len() {
97
+ if buf[j] == 0 {
98
+ break;
99
+ }
100
+ if buf[j] >= b'A' && buf[j] <= b'Z' {
101
+ buf[j] += 0x20;
102
+ }
103
+ }
104
+
105
+ // Hash the lowercased name
106
+ let bucket = gfx_dir_hash(buf.as_ptr());
107
+
108
+ // Walk linked list in hash bucket
109
+ // gfx_handler+4 = pointer to bucket array (1024 entries)
110
+ let bucket_array = *(gfx_handler.add(4) as *const *const u32);
111
+ let mut entry = *bucket_array.add(bucket as usize) as *mut GfxDirEntry;
112
+
113
+ while !entry.is_null() {
114
+ let entry_name = (*entry).name_ptr();
115
+ let mut match_found = true;
116
+ let mut k = 0usize;
117
+ loop {
118
+ let a = *entry_name.add(k);
119
+ let b = buf[k];
120
+ if a != b {
121
+ match_found = false;
122
+ break;
123
+ }
124
+ if a == 0 {
125
+ break;
126
+ }
127
+ k += 1;
128
+ }
129
+
130
+ if match_found {
131
+ return entry as *mut u8;
132
+ }
133
+
134
+ entry = (*entry).next;
135
+ }
136
+
137
+ // Not found — try fallback name after '|'
138
+ if next_name.is_null() {
139
+ return core::ptr::null_mut();
140
+ }
141
+ current_name = next_name;
142
+ }
143
+ }
144
+
145
+ /// Pure Rust implementation of FUN_5665F0 (GfxHandler reset).
146
+ ///
147
+ /// Convention: usercall(ESI=handler), plain RET. Called at start of LoadDir.
148
+ unsafe fn gfx_handler_reset(handler: *mut u8) {
149
+ *(handler.add(0x04) as *mut u32) = 0;
150
+ *(handler.add(0x08) as *mut u32) = 0;
151
+ *(handler.add(0x194) as *mut u32) = 0;
152
+
153
+ for i in 0..16u32 {
154
+ // handler+0x0C + i*0x10 = 0 (16 entries at stride 0x10)
155
+ *(handler.add(0x0C + i as usize * 0x10) as *mut u32) = 0;
156
+ // handler+0x10C + i*4 = i (index array)
157
+ *(handler.add(0x10C + i as usize * 4) as *mut u32) = i;
158
+ }
159
+
160
+ *(handler.add(400) as *mut u32) = 0; // 0x190
161
+ *(handler.add(0x18C) as *mut u32) = 0x10;
162
+ }
163
+
164
+ /// Pure Rust implementation of GfxHandler__LoadDir (0x5663E0).
165
+ ///
166
+ /// Convention: usercall(EAX=handler), plain RET. Returns 1 on success, 0 on failure.
167
+ ///
168
+ /// Reads a .dir file through the handler's vtable I/O methods:
169
+ /// - vtable[0]: read(buf, size) → thiscall, returns bytes read
170
+ /// - vtable[1]: seek/reposition(size) → thiscall
171
+ /// - vtable[2]: allocate(size) → thiscall, returns buffer or null
172
+ ///
173
+ /// .dir file format:
174
+ /// - 4 bytes: magic "DIR\x1A" (0x1A524944)
175
+ /// - 4 bytes: total_file_size
176
+ /// - 4 bytes: data_size (hash table + entries)
177
+ /// - data: 1024-bucket hash table followed by linked list nodes
178
+ /// All pointers are relative offsets from (data_start + 4)
179
+ ///
180
+ /// # Safety
181
+ /// `handler` must be a valid GfxHandler with file handle at +0x198.
182
+ #[cfg(target_arch = "x86")]
183
+ pub unsafe fn gfx_handler_load_dir(handler: *mut u8) -> i32 {
184
+ gfx_handler_reset(handler);
185
+
186
+ let vt = *(handler as *const *const u32);
187
+ // vtable[0] = read: thiscall(handler, buf, size) -> bytes_read
188
+ let vt_read: unsafe extern "thiscall" fn(*mut u8, *mut u8, u32) -> u32 =
189
+ core::mem::transmute(*vt);
190
+ // vtable[1] = seek: thiscall(handler, size)
191
+ let vt_seek: unsafe extern "thiscall" fn(*mut u8, u32) = core::mem::transmute(*vt.add(1));
192
+ // vtable[2] = allocate: thiscall(handler, size) -> *mut u8
193
+ let vt_alloc: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
194
+ core::mem::transmute(*vt.add(2));
195
+
196
+ // Read and validate magic
197
+ let mut magic: u32 = 0;
198
+ if vt_read(handler, &mut magic as *mut u32 as *mut u8, 4) != 4 {
199
+ return 0;
200
+ }
201
+ if magic != 0x1A524944 {
202
+ // "DIR\x1A"
203
+ return 0;
204
+ }
205
+
206
+ // Read total_file_size and data_size
207
+ let mut total_file_size: u32 = 0;
208
+ if vt_read(handler, &mut total_file_size as *mut u32 as *mut u8, 4) != 4 {
209
+ return 0;
210
+ }
211
+ let mut data_size: u32 = 0;
212
+ if vt_read(handler, &mut data_size as *mut u32 as *mut u8, 4) != 4 {
213
+ return 0;
214
+ }
215
+
216
+ let alloc_size = data_size + 4;
217
+
218
+ // Try fast path: vtable[2] allocate (memory-maps the data)
219
+ let data = vt_alloc(handler, alloc_size);
220
+ *(handler.add(4) as *mut *mut u8) = data;
221
+
222
+ if data.is_null() {
223
+ // Fallback: seek past header, then malloc + read entire data block.
224
+ // Original calls vt_seek(alloc_size) then WA_MallocMemset(EDI=read_size).
225
+ // WA_MallocMemset allocates (size+3)&~3 + 0x20 and memsets size bytes.
226
+ vt_seek(handler, alloc_size);
227
+
228
+ let read_size = total_file_size - data_size - 4;
229
+ let malloc_size = ((read_size + 3) & !3) + 0x20;
230
+ let buf = wa_malloc(malloc_size);
231
+ if buf.is_null() {
232
+ return 0;
233
+ }
234
+ core::ptr::write_bytes(buf, 0, read_size as usize);
235
+ *(handler.add(4) as *mut *mut u8) = buf;
236
+
237
+ let bytes_read = vt_read(handler, buf, read_size);
238
+ if bytes_read != read_size {
239
+ crate::wa_alloc::wa_free(buf);
240
+ return 0;
241
+ }
242
+
243
+ // Mark as "loaded from fallback" at handler+8
244
+ *(handler.add(8) as *mut u32) = 1;
245
+ }
246
+
247
+ // Fix up relative pointers in the hash table
248
+ // 1024 buckets at data[0..0x1000], each is a pointer to a linked list node
249
+ let data = *(handler.add(4) as *const *mut u8);
250
+ let base = data as u32;
251
+
252
+ for bucket in 0..1024u32 {
253
+ let bucket_ptr = data.add(bucket as usize * 4) as *mut u32;
254
+ let entry_offset = *bucket_ptr;
255
+ if entry_offset == 0 {
256
+ continue;
257
+ }
258
+
259
+ // Convert relative offset to absolute: offset + base - 4
260
+ let entry_addr = entry_offset.wrapping_add(base).wrapping_sub(4);
261
+ *bucket_ptr = entry_addr;
262
+
263
+ // Walk linked list, fix up each next pointer
264
+ let mut node = entry_addr as *mut u32;
265
+ loop {
266
+ if node.is_null() {
267
+ break;
268
+ }
269
+ let next_offset = *node;
270
+ if next_offset == 0 {
271
+ break;
272
+ }
273
+ let next_addr = next_offset.wrapping_add(base).wrapping_sub(4);
274
+ *node = next_addr;
275
+ node = next_addr as *mut u32;
276
+ }
277
+ }
278
+
279
+ *(handler.add(0x194) as *mut u32) = 1;
280
+
281
+ 1 // success
282
+ }
283
+
284
+ /// Pure Rust implementation of GfxResource__Create_Maybe (0x4F6300).
285
+ ///
286
+ /// Convention: usercall(ECX=gfx_handler, EAX=name) + 1 stack(output), RET 0x4.
287
+ ///
288
+ /// Looks up `name` in the GfxHandler's directory, tries cached load, wraps
289
+ /// as DisplayGfx. Falls back to loading the raw image and decoding it.
290
+ ///
291
+ /// # Safety
292
+ /// All pointers must be valid WA objects.
293
+ #[cfg(target_arch = "x86")]
294
+ pub unsafe fn gfx_resource_create(
295
+ gfx_handler: *mut u8,
296
+ name: *const u8,
297
+ output: *mut u8,
298
+ ) -> *mut u8 {
299
+ // 1. Try FindEntry → cached load → DisplayGfx wrap
300
+ let entry = gfx_dir_find_entry(name, gfx_handler);
301
+ if !entry.is_null() {
302
+ // gfx_handler->vtable[2](entry->field_4) — cached load
303
+ let vt = *(gfx_handler as *const *const u32);
304
+ let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
305
+ core::mem::transmute(*vt.add(2));
306
+ let entry_val = (*(entry as *const GfxDirEntry)).value;
307
+ let cached = load_cached(gfx_handler, entry_val);
308
+ if !cached.is_null() {
309
+ // DisplayGfx__Constructor_Maybe: stdcall(raw_image), RET 0x4
310
+ let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
311
+ core::mem::transmute(rb(va::DISPLAYGFX_CONSTRUCTOR) as usize);
312
+ return ctor(cached);
313
+ }
314
+ }
315
+
316
+ // 2. Fallback: LoadImage → IMG_Decode
317
+ let raw_image = call_gfx_load_image(gfx_handler, name);
318
+ if raw_image.is_null() {
319
+ return core::ptr::null_mut();
320
+ }
321
+
322
+ // IMG_Decode: stdcall(output, raw_image, 1), RET 0xC
323
+ let decode: unsafe extern "stdcall" fn(*mut u8, *mut u8, i32) -> *mut u8 =
324
+ core::mem::transmute(rb(va::IMG_DECODE) as usize);
325
+ let result = decode(output, raw_image, 1);
326
+
327
+ // Release raw image: raw_image->vtable[0](1)
328
+ let img_vt = *(raw_image as *const *const u32);
329
+ let fn_ptr = *img_vt;
330
+ let release: unsafe extern "thiscall" fn(*mut u8, i32) = core::mem::transmute(fn_ptr);
331
+ release(raw_image, 1);
332
+
333
+ result
334
+ }
335
+
336
+ /// Helper: find entry in GfxDir and load image, or load directly.
337
+ /// Returns a DisplayGfx/sprite pointer or null.
338
+ #[cfg(target_arch = "x86")]
339
+ pub(crate) unsafe fn call_gfx_find_and_load(
340
+ gfx_dir: *mut u8,
341
+ name: *const u8,
342
+ display_ctx: *mut u8,
343
+ ) -> *mut u8 {
344
+ let entry = gfx_dir_find_entry(name, gfx_dir);
345
+
346
+ if !entry.is_null() {
347
+ // Try cached load: gfx_dir->vtable[2](entry->field_4)
348
+ let dir_vt = *(gfx_dir as *const *const u32);
349
+ let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
350
+ core::mem::transmute(*dir_vt.add(2));
351
+ let cached = load_cached(gfx_dir, (*(entry as *const GfxDirEntry)).value);
352
+ if !cached.is_null() {
353
+ // Wrap with DisplayGfx__Constructor_Maybe (0x4F5E80)
354
+ // This is stdcall(1 param), RET 0x4
355
+ let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
356
+ core::mem::transmute(rb(va::DISPLAYGFX_CONSTRUCTOR) as usize);
357
+ return ctor(cached);
358
+ }
359
+ }
360
+
361
+ // Fallback: load image directly
362
+ call_gfx_load_and_wrap(gfx_dir, name, display_ctx)
363
+ }
364
+
365
+ /// Helper: load image via GfxDir__LoadImage + wrap as DisplayGfx.
366
+ /// Used by arrow sprite loop when GfxDir__FindEntry returns null.
367
+ #[cfg(target_arch = "x86")]
368
+ pub(crate) unsafe fn call_gfx_load_and_wrap(
369
+ gfx_handler: *mut u8,
370
+ name: *const u8,
371
+ display_ctx: *mut u8,
372
+ ) -> *mut u8 {
373
+ let image = call_gfx_load_image(gfx_handler, name);
374
+ if image.is_null() {
375
+ return core::ptr::null_mut();
376
+ }
377
+ // FUN_004F5F80(display_ctx, image, 1) — stdcall, RET 0xC (3 params)
378
+ let f: unsafe extern "stdcall" fn(*mut u8, *mut u8, u32) -> *mut u8 =
379
+ core::mem::transmute(rb(va::IMG_DECODE) as usize);
380
+ let result = f(display_ctx, image, 1);
381
+ // Release the raw image
382
+ let image_vt = *(image as *const *const u32);
383
+ let destroy: unsafe extern "thiscall" fn(*mut u8, u32) = core::mem::transmute(*image_vt);
384
+ destroy(image, 1);
385
+ result
386
+ }
387
+
388
+ static mut GFX_LOAD_DIR_ADDR: u32 = 0;
389
+
390
+ // GfxDir__LoadImage is usercall(ESI=gfx_handler) + 1 stack(name), RET 0x4.
391
+ // Returns raw image pointer or null.
392
+ static mut GFX_LOAD_IMAGE_ADDR: u32 = 0;
393
+
394
+ /// Bridge to GfxHandler__LoadDir (0x5663E0).
395
+ /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
396
+ #[cfg(target_arch = "x86")]
397
+ pub(crate) unsafe fn call_gfx_load_dir(handler: *mut u8, addr: u32) -> i32 {
398
+ let result: i32;
399
+ core::arch::asm!(
400
+ "call {addr}",
401
+ addr = in(reg) addr,
402
+ inlateout("eax") handler => result,
403
+ out("ecx") _,
404
+ out("edx") _,
405
+ clobber_abi("C"),
406
+ );
407
+ result
408
+ }
409
+
410
+ #[cfg(target_arch = "x86")]
411
+ #[unsafe(naked)]
412
+ unsafe extern "C" fn call_gfx_load_image(_gfx_handler: *mut u8, _name: *const u8) -> *mut u8 {
413
+ core::arch::naked_asm!(
414
+ "pushl %esi",
415
+ "movl 8(%esp), %esi", // ESI = gfx_handler
416
+ "pushl 12(%esp)", // push name (callee cleans via RET 0x4)
417
+ "calll *({addr})",
418
+ "popl %esi",
419
+ "retl",
420
+ addr = sym GFX_LOAD_IMAGE_ADDR,
421
+ options(att_syntax),
422
+ );
423
+ }
424
+
425
+ /// Initialize runtime addresses for GfxHandler bridges.
426
+ /// Must be called once at DLL startup.
427
+ pub fn init_addrs() {
428
+ unsafe {
429
+ GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
430
+ GFX_LOAD_DIR_ADDR = rb(va::GFX_HANDLER_LOAD_DIR);
431
+ }
432
+ }
433
+
434
+ /// Returns the rebased runtime address for GfxHandler__LoadDir.
435
+ /// Used by callers that pass the address to `call_gfx_load_dir`.
436
+ pub fn gfx_load_dir_addr() -> u32 {
437
+ unsafe { GFX_LOAD_DIR_ADDR }
438
+ }