Skip to main content

siglus_scene_vm/
display_ffi.rs

1//! C ABI helpers for bundle/mobile launchers that need Siglus display metadata.
2
3use std::ffi::{CStr, CString, c_char};
4use std::path::PathBuf;
5
6use crate::runtime::game_display_info::{
7    resolve_game_cover_from_project_dir, resolve_game_name_from_project_dir,
8};
9
10unsafe fn path_from_cstr(ptr: *const c_char) -> Option<PathBuf> {
11    if ptr.is_null() {
12        return None;
13    }
14    let s = unsafe { CStr::from_ptr(ptr) }.to_string_lossy().to_string();
15    if s.is_empty() {
16        None
17    } else {
18        Some(PathBuf::from(s))
19    }
20}
21
22fn into_c_string_ptr(s: String) -> *mut c_char {
23    CString::new(s)
24        .unwrap_or_else(|_| CString::new("Siglus").unwrap())
25        .into_raw()
26}
27
28/// Free a string returned by the display metadata functions. A null pointer is ignored.
29///
30/// # Safety
31///
32/// If non-null, `ptr` must be an allocation returned by one of this module's
33/// metadata functions, with its original NUL terminator and length preserved.
34/// It must not have been freed. No other access may overlap this call, and the
35/// pointer must not be used after this call.
36#[unsafe(no_mangle)]
37pub unsafe extern "C" fn siglus_string_free(ptr: *mut c_char) {
38    if ptr.is_null() {
39        return;
40    }
41    drop(unsafe { CString::from_raw(ptr) });
42}
43
44/// Return the game name, or the default name if the path is null or unavailable.
45/// Free a non-null result with `siglus_string_free`.
46///
47/// # Safety
48///
49/// If non-null, `game_root_utf8` must point to a NUL-terminated string in a
50/// single readable allocation of at most `isize::MAX` bytes, including the
51/// terminator. The bytes must remain valid and unchanged for this call.
52#[unsafe(no_mangle)]
53pub unsafe extern "C" fn siglus_game_name_from_dir(game_root_utf8: *const c_char) -> *mut c_char {
54    let Some(path) = (unsafe { path_from_cstr(game_root_utf8) }) else {
55        return into_c_string_ptr("Siglus".to_string());
56    };
57    into_c_string_ptr(resolve_game_name_from_project_dir(path))
58}
59
60/// Return the game cover path, or null if no cover is available.
61/// Free a non-null result with `siglus_string_free`.
62///
63/// # Safety
64///
65/// If non-null, `game_root_utf8` must point to a NUL-terminated string in a
66/// single readable allocation of at most `isize::MAX` bytes, including the
67/// terminator. The bytes must remain valid and unchanged for this call.
68#[unsafe(no_mangle)]
69pub unsafe extern "C" fn siglus_game_cover_path_from_dir(
70    game_root_utf8: *const c_char,
71) -> *mut c_char {
72    let Some(path) = (unsafe { path_from_cstr(game_root_utf8) }) else {
73        return std::ptr::null_mut();
74    };
75    let Some(cover) = resolve_game_cover_from_project_dir(path) else {
76        return std::ptr::null_mut();
77    };
78    into_c_string_ptr(cover.source_path.to_string_lossy().to_string())
79}
80
81/// Return the game cover MIME type, or null if no cover is available.
82/// Free a non-null result with `siglus_string_free`.
83///
84/// # Safety
85///
86/// If non-null, `game_root_utf8` must point to a NUL-terminated string in a
87/// single readable allocation of at most `isize::MAX` bytes, including the
88/// terminator. The bytes must remain valid and unchanged for this call.
89#[unsafe(no_mangle)]
90pub unsafe extern "C" fn siglus_game_cover_mime_from_dir(
91    game_root_utf8: *const c_char,
92) -> *mut c_char {
93    let Some(path) = (unsafe { path_from_cstr(game_root_utf8) }) else {
94        return std::ptr::null_mut();
95    };
96    let Some(cover) = resolve_game_cover_from_project_dir(path) else {
97        return std::ptr::null_mut();
98    };
99    into_c_string_ptr(cover.mime)
100}