diff options
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/nak/src/game_finder/heroic.rs | 23 | ||||
| -rw-r--r-- | libs/nak/src/game_finder/known_games.rs | 159 | ||||
| -rw-r--r-- | libs/nak/src/game_finder/mod.rs | 31 | ||||
| -rw-r--r-- | libs/nak_ffi/include/nak_ffi.h | 1 | ||||
| -rw-r--r-- | libs/nak_ffi/src/lib.rs | 2 | ||||
| -rw-r--r-- | libs/plugin_python/src/runner/pythonrunner.cpp | 102 |
6 files changed, 256 insertions, 62 deletions
diff --git a/libs/nak/src/game_finder/heroic.rs b/libs/nak/src/game_finder/heroic.rs index 7bdda09..e80c1e1 100644 --- a/libs/nak/src/game_finder/heroic.rs +++ b/libs/nak/src/game_finder/heroic.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use serde::Deserialize; -use super::known_games::find_by_gog_id; +use super::known_games::{find_by_epic_id, find_by_gog_id, find_by_title}; use super::{Game, HeroicStore, Launcher}; use crate::logging::{log_info, log_warning}; @@ -202,11 +202,16 @@ fn detect_epic_games(heroic_path: &Path) -> Vec<Game> { } // Get title - let name = game_obj + let title = game_obj .get("title") .and_then(|v| v.as_str()) - .unwrap_or(app_name) - .to_string(); + .unwrap_or(app_name); + + // Look up known game info: try Epic AppName first, then title match + let known_game = find_by_epic_id(app_name) + .or_else(|| find_by_title(title)); + + let name = title.to_string(); // Get Wine prefix let prefix_path = get_heroic_game_prefix(heroic_path, app_name); @@ -219,11 +224,11 @@ fn detect_epic_games(heroic_path: &Path) -> Vec<Game> { launcher: Launcher::Heroic { store: HeroicStore::Epic, }, - my_games_folder: None, - appdata_local_folder: None, - appdata_roaming_folder: None, - registry_path: None, - registry_value: None, + my_games_folder: known_game.and_then(|g| g.my_games_folder.map(String::from)), + appdata_local_folder: known_game.and_then(|g| g.appdata_local_folder.map(String::from)), + appdata_roaming_folder: known_game.and_then(|g| g.appdata_roaming_folder.map(String::from)), + registry_path: known_game.map(|g| g.registry_path.to_string()), + registry_value: known_game.map(|g| g.registry_value.to_string()), }); } } diff --git a/libs/nak/src/game_finder/known_games.rs b/libs/nak/src/game_finder/known_games.rs index 8a30c18..117b0c7 100644 --- a/libs/nak/src/game_finder/known_games.rs +++ b/libs/nak/src/game_finder/known_games.rs @@ -2,6 +2,8 @@ //! //! Contains metadata for games that NaK supports, including: //! - Steam App ID +//! - GOG App ID +//! - Epic Games Store App Name (Legendary/Heroic internal identifier) //! - My Games folder name (Documents/My Games/*) //! - AppData/Local folder name //! - Registry path for game detection @@ -13,8 +15,10 @@ pub struct KnownGame { pub name: &'static str, /// Steam App ID pub steam_app_id: &'static str, - /// GOG App ID (if available) + /// GOG App ID (if available on GOG) pub gog_app_id: Option<&'static str>, + /// Epic Games Store AppName / Legendary internal ID (if available on Epic) + pub epic_app_id: Option<&'static str>, /// Folder name in Documents/My Games (if applicable) pub my_games_folder: Option<&'static str>, /// Folder name in AppData/Local (if applicable) @@ -35,7 +39,8 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ KnownGame { name: "Enderal", steam_app_id: "933480", - gog_app_id: None, + gog_app_id: Some("1708684988"), // Enderal: Forgotten Stories on GOG + epic_app_id: None, my_games_folder: Some("Enderal"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -46,7 +51,8 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ KnownGame { name: "Enderal Special Edition", steam_app_id: "976620", - gog_app_id: None, + gog_app_id: None, // Not on GOG (only original Enderal) + epic_app_id: None, my_games_folder: Some("Enderal Special Edition"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -58,6 +64,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Fallout 3", steam_app_id: "22300", gog_app_id: Some("1454315831"), // Fallout 3 GOTY + epic_app_id: Some("adeae8bbfc94427db57c7dfecce3f1d4"), my_games_folder: Some("Fallout3"), appdata_local_folder: Some("Fallout3"), appdata_roaming_folder: None, @@ -68,7 +75,8 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ KnownGame { name: "Fallout 4", steam_app_id: "377160", - gog_app_id: None, + gog_app_id: Some("1998527297"), // Fallout 4 GOTY on GOG + epic_app_id: Some("61d52ce4d09d41e48800c22784d13ae8"), my_games_folder: Some("Fallout4"), appdata_local_folder: Some("Fallout4"), appdata_roaming_folder: None, @@ -80,6 +88,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Fallout 4 VR", steam_app_id: "611660", gog_app_id: None, + epic_app_id: None, my_games_folder: Some("Fallout4VR"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -91,6 +100,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Fallout New Vegas", steam_app_id: "22380", gog_app_id: Some("1454587428"), // Fallout NV Ultimate + epic_app_id: Some("5daeb974a22a435988892319b3a4f476"), my_games_folder: Some("FalloutNV"), appdata_local_folder: Some("FalloutNV"), appdata_roaming_folder: None, @@ -102,6 +112,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Morrowind", steam_app_id: "22320", gog_app_id: Some("1440163901"), // Morrowind GOTY + epic_app_id: None, // On Epic but internal AppName not yet known my_games_folder: Some("Morrowind"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -113,6 +124,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Oblivion", steam_app_id: "22330", gog_app_id: Some("1458058109"), // Oblivion GOTY Deluxe + epic_app_id: None, // On Epic but internal AppName not yet known my_games_folder: Some("Oblivion"), appdata_local_folder: Some("Oblivion"), appdata_roaming_folder: None, @@ -123,7 +135,8 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ KnownGame { name: "Skyrim", steam_app_id: "72850", - gog_app_id: None, // Not on GOG + gog_app_id: None, // Not on GOG (only SE/AE) + epic_app_id: None, // Not on Epic (only SE/AE) my_games_folder: Some("Skyrim"), appdata_local_folder: Some("Skyrim"), appdata_roaming_folder: None, @@ -134,7 +147,8 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ KnownGame { name: "Skyrim Special Edition", steam_app_id: "489830", - gog_app_id: Some("1711230643"), // Skyrim SE Anniversary Edition + gog_app_id: Some("1711230643"), // Skyrim SE on GOG + epic_app_id: Some("ac82db5035584c7f8a2c548d98c86b2c"), my_games_folder: Some("Skyrim Special Edition"), appdata_local_folder: Some("Skyrim Special Edition"), appdata_roaming_folder: None, @@ -146,6 +160,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Skyrim VR", steam_app_id: "611670", gog_app_id: None, + epic_app_id: None, my_games_folder: Some("Skyrim VR"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -156,7 +171,8 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ KnownGame { name: "Starfield", steam_app_id: "1716740", - gog_app_id: None, + gog_app_id: None, // Xbox/Steam exclusive + epic_app_id: None, my_games_folder: Some("Starfield"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -169,6 +185,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "The Witcher 3", steam_app_id: "292030", gog_app_id: Some("1495134320"), // Witcher 3 GOTY + epic_app_id: None, my_games_folder: Some("The Witcher 3"), appdata_local_folder: None, appdata_roaming_folder: None, @@ -180,6 +197,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Cyberpunk 2077", steam_app_id: "1091500", gog_app_id: Some("1423049311"), + epic_app_id: None, my_games_folder: None, appdata_local_folder: Some("CD Projekt Red/Cyberpunk 2077"), appdata_roaming_folder: None, @@ -192,6 +210,7 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ name: "Baldur's Gate 3", steam_app_id: "1086940", gog_app_id: Some("1456460669"), + epic_app_id: None, my_games_folder: None, appdata_local_folder: Some("Larian Studios/Baldur's Gate 3"), appdata_roaming_folder: None, @@ -201,6 +220,15 @@ pub const KNOWN_GAMES: &[KnownGame] = &[ }, ]; +/// Alternative GOG App IDs that should map to the same game. +/// Some games have multiple GOG product IDs (e.g. different editions). +const GOG_ID_ALIASES: &[(&str, &str)] = &[ + // Morrowind: Vortex uses 1435828767, NaK primary is 1440163901 + ("1435828767", "1440163901"), + // Skyrim Anniversary Edition is a separate GOG product but same game as SE + ("1801825368", "1711230643"), +]; + /// Find a known game by Steam App ID pub fn find_by_steam_id(app_id: &str) -> Option<&'static KnownGame> { let normalized_id = normalize_steam_id(app_id); @@ -209,9 +237,79 @@ pub fn find_by_steam_id(app_id: &str) -> Option<&'static KnownGame> { /// Find a known game by GOG App ID pub fn find_by_gog_id(app_id: &str) -> Option<&'static KnownGame> { + // Direct match first + if let Some(game) = KNOWN_GAMES.iter().find(|g| g.gog_app_id == Some(app_id)) { + return Some(game); + } + // Check aliases: resolve alternate GOG ID to primary, then look up + for &(alias, primary) in GOG_ID_ALIASES { + if app_id == alias { + return KNOWN_GAMES.iter().find(|g| g.gog_app_id == Some(primary)); + } + } + None +} + +/// Find a known game by Epic Games Store AppName (Legendary/Heroic internal ID) +pub fn find_by_epic_id(app_id: &str) -> Option<&'static KnownGame> { KNOWN_GAMES .iter() - .find(|g| g.gog_app_id == Some(app_id)) + .find(|g| g.epic_app_id == Some(app_id)) +} + +/// Strip punctuation and collapse whitespace for fuzzy title matching. +fn normalize_for_matching(s: &str) -> String { + s.chars() + .map(|c| if c.is_alphanumeric() || c == ' ' { c } else { ' ' }) + .collect::<String>() + .split_whitespace() + .collect::<Vec<_>>() + .join(" ") +} + +/// Find a known game by title, using fuzzy matching. +/// Strips common suffixes like "Game of the Year Edition" for comparison. +pub fn find_by_title(title: &str) -> Option<&'static KnownGame> { + let title_lower = title.to_lowercase(); + + // Exact match first + if let Some(game) = KNOWN_GAMES.iter().find(|g| g.name.to_lowercase() == title_lower) { + return Some(game); + } + + // Check if the title starts with or contains a known game name + // Sort by name length descending so "Skyrim Special Edition" matches before "Skyrim" + let mut games_by_name_len: Vec<&KnownGame> = KNOWN_GAMES.iter().collect(); + games_by_name_len.sort_by(|a, b| b.name.len().cmp(&a.name.len())); + + // Normalize both title and game names: strip colons and extra whitespace for comparison + let title_normalized = normalize_for_matching(&title_lower); + + for game in games_by_name_len { + let game_lower = game.name.to_lowercase(); + let game_normalized = normalize_for_matching(&game_lower); + + // Direct containment (e.g. "Fallout 3: Game of the Year Edition" contains "Fallout 3") + if title_lower.contains(&game_lower) { + return Some(game); + } + + // Normalized containment (e.g. "Fallout: New Vegas" normalized to "fallout new vegas" + // matches game name "Fallout New Vegas" normalized to "fallout new vegas") + if title_normalized.contains(&game_normalized) { + return Some(game); + } + + // After-colon match: "The Elder Scrolls III: Morrowind" -> check "Morrowind" + if let Some(after_colon) = title_lower.split(':').nth(1) { + let after_colon = after_colon.trim(); + if after_colon.starts_with(&game_lower) { + return Some(game); + } + } + } + + None } /// Find a known game by name (case-insensitive) @@ -234,7 +332,7 @@ fn normalize_steam_id(app_id: &str) -> &str { #[cfg(test)] mod tests { - use super::find_by_steam_id; + use super::*; #[test] fn fallout_3_goty_alias_maps_to_fallout_3() { @@ -242,4 +340,47 @@ mod tests { assert_eq!(game.name, "Fallout 3"); assert_eq!(game.steam_app_id, "22300"); } + + #[test] + fn find_by_epic_id_works() { + let game = find_by_epic_id("ac82db5035584c7f8a2c548d98c86b2c") + .expect("Should find Skyrim SE by Epic ID"); + assert_eq!(game.name, "Skyrim Special Edition"); + } + + #[test] + fn gog_alias_maps_correctly() { + // Skyrim Anniversary Edition GOG ID maps to Skyrim SE + let game = find_by_gog_id("1801825368") + .expect("Skyrim AE GOG ID should map to Skyrim SE"); + assert_eq!(game.name, "Skyrim Special Edition"); + } + + #[test] + fn find_by_title_matches_full_titles() { + let game = find_by_title("The Elder Scrolls III: Morrowind Game of the Year Edition") + .expect("Should find Morrowind by full Epic title"); + assert_eq!(game.name, "Morrowind"); + } + + #[test] + fn find_by_title_matches_colon_titles() { + let game = find_by_title("The Elder Scrolls IV: Oblivion Game of the Year Edition") + .expect("Should find Oblivion by full title"); + assert_eq!(game.name, "Oblivion"); + } + + #[test] + fn find_by_title_prefers_longer_match() { + let game = find_by_title("Skyrim Special Edition") + .expect("Should find Skyrim SE, not Skyrim"); + assert_eq!(game.name, "Skyrim Special Edition"); + } + + #[test] + fn find_by_title_fallout_nv() { + let game = find_by_title("Fallout: New Vegas Ultimate Edition") + .expect("Should find Fallout NV"); + assert_eq!(game.name, "Fallout New Vegas"); + } } diff --git a/libs/nak/src/game_finder/mod.rs b/libs/nak/src/game_finder/mod.rs index c383c8a..600751a 100644 --- a/libs/nak/src/game_finder/mod.rs +++ b/libs/nak/src/game_finder/mod.rs @@ -20,7 +20,10 @@ use std::path::PathBuf; pub use bottles::detect_bottles_games; pub use heroic::detect_heroic_games; -pub use known_games::{find_by_gog_id, find_by_name, find_by_steam_id, KnownGame, KNOWN_GAMES}; +pub use known_games::{ + find_by_epic_id, find_by_gog_id, find_by_name, find_by_steam_id, find_by_title, KnownGame, + KNOWN_GAMES, +}; pub use registry::{read_registry_value, wine_path_to_linux}; pub use steam::{detect_steam_games, find_game_install_path, find_game_prefix_path, get_known_game}; @@ -161,10 +164,16 @@ impl GameScanResult { // Public API // ============================================================================ -/// Detect all installed games from all supported launchers +/// Detect all installed games from all supported launchers. +/// +/// Games are detected in priority order: Steam -> Heroic (GOG -> Epic) -> Bottles. +/// When the same game is found from multiple launchers, the higher-priority +/// detection is kept and duplicates are skipped. This ensures registry entries +/// (which are shared across storefronts) prefer Steam paths, then GOG, then Epic. pub fn detect_all_games() -> GameScanResult { let mut result = GameScanResult::default(); + // Detect in priority order: Steam first, then GOG (via Heroic), then Epic, then Bottles. let steam_games = detect_steam_games(); result.steam_count = steam_games.len(); result.games.extend(steam_games); @@ -177,9 +186,27 @@ pub fn detect_all_games() -> GameScanResult { result.bottles_count = bottles_games.len(); result.games.extend(bottles_games); + // Deduplicate: keep the first occurrence of each game (by registry_path), + // which respects the detection order (Steam > GOG > Epic > Bottles). + deduplicate_games(&mut result); + result } +/// Remove duplicate game detections, keeping the first (highest-priority) entry. +/// Two games are considered duplicates if they have the same registry_path. +fn deduplicate_games(result: &mut GameScanResult) { + let mut seen_registry_paths = std::collections::HashSet::new(); + result.games.retain(|game| { + if let Some(ref reg_path) = game.registry_path { + seen_registry_paths.insert(reg_path.clone()) + } else { + // Games without registry paths are always kept (no risk of conflict) + true + } + }); +} + /// Detect only Steam games pub fn detect_steam_only() -> GameScanResult { let steam_games = detect_steam_games(); diff --git a/libs/nak_ffi/include/nak_ffi.h b/libs/nak_ffi/include/nak_ffi.h index 8e24041..cc66d9e 100644 --- a/libs/nak_ffi/include/nak_ffi.h +++ b/libs/nak_ffi/include/nak_ffi.h @@ -46,6 +46,7 @@ typedef struct { const char *name;
const char *steam_app_id;
const char *gog_app_id; /* NULL if none */
+ const char *epic_app_id; /* NULL if none */
const char *my_games_folder; /* NULL if not applicable */
const char *appdata_local_folder; /* NULL if not applicable */
const char *appdata_roaming_folder; /* NULL if not applicable */
diff --git a/libs/nak_ffi/src/lib.rs b/libs/nak_ffi/src/lib.rs index fe18cf3..65592fa 100644 --- a/libs/nak_ffi/src/lib.rs +++ b/libs/nak_ffi/src/lib.rs @@ -212,6 +212,7 @@ pub struct NakKnownGame { pub name: *const c_char,
pub steam_app_id: *const c_char,
pub gog_app_id: *const c_char, // null if none
+ pub epic_app_id: *const c_char, // null if none
pub my_games_folder: *const c_char,
pub appdata_local_folder: *const c_char,
pub appdata_roaming_folder: *const c_char,
@@ -236,6 +237,7 @@ static KNOWN_GAMES_FFI: std::sync::LazyLock<KnownGamesVec> = std::sync::LazyLock name: leak_str(kg.name),
steam_app_id: leak_str(kg.steam_app_id),
gog_app_id: leak_str_opt(kg.gog_app_id),
+ epic_app_id: leak_str_opt(kg.epic_app_id),
my_games_folder: leak_str_opt(kg.my_games_folder),
appdata_local_folder: leak_str_opt(kg.appdata_local_folder),
appdata_roaming_folder: leak_str_opt(kg.appdata_roaming_folder),
diff --git a/libs/plugin_python/src/runner/pythonrunner.cpp b/libs/plugin_python/src/runner/pythonrunner.cpp index b0bf48c..3bc62a1 100644 --- a/libs/plugin_python/src/runner/pythonrunner.cpp +++ b/libs/plugin_python/src/runner/pythonrunner.cpp @@ -89,55 +89,63 @@ namespace mo2::python { static const char* argv0 = "ModOrganizer.exe"; #ifndef _WIN32 -#ifdef MO2_PYTHON_SHARED_LIBRARY // Ensure libpython symbols are globally visible for extension modules // loaded later (_struct, PyQt6, etc.). - bool usedNoload = true; - void* pyHandle = - dlopen(MO2_PYTHON_SHARED_LIBRARY, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); - if (pyHandle == nullptr) { - usedNoload = false; - pyHandle = dlopen(MO2_PYTHON_SHARED_LIBRARY, RTLD_NOW | RTLD_GLOBAL); - } - if (pyHandle == nullptr) { - MOBase::log::warn("python: failed to dlopen '{}': {}", - MO2_PYTHON_SHARED_LIBRARY, dlerror()); - } else { - MOBase::log::debug("python: '{}' promoted to RTLD_GLOBAL ({})", - MO2_PYTHON_SHARED_LIBRARY, - usedNoload ? "RTLD_NOLOAD" : "fresh load"); - } - - // Diagnose which DSO the Python init symbols resolve to globally. + // + // We must promote the *already-loaded* libpython to RTLD_GLOBAL. + // Using the compile-time filename (e.g. "libpython3.13.so.1.0") with + // RTLD_NOLOAD can fail when the portable Python's SONAME differs + // (e.g. "libpython3.13.so"), causing a second copy to be loaded and + // making Py_IsInitialized() return false after Py_InitializeFromConfig(). + // + // Instead, find the DSO that provides Py_IsInitialized via dladdr, then + // re-dlopen that exact path with RTLD_GLOBAL. { Dl_info di; - void* sym = dlsym(RTLD_DEFAULT, "Py_InitializeEx"); - if (sym && dladdr(sym, &di)) { - fprintf(stderr, "[py-diag] Py_InitializeEx global from '%s'\n", + void* sym = dlsym(RTLD_DEFAULT, "Py_IsInitialized"); + if (sym && dladdr(sym, &di) && di.dli_fname) { + void* pyHandle = + dlopen(di.dli_fname, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + if (pyHandle) { + MOBase::log::debug( + "python: promoted '{}' to RTLD_GLOBAL via dladdr", di.dli_fname); - MOBase::log::debug("python: Py_InitializeEx (global) from '{}'", - di.dli_fname); - } else { - fprintf(stderr, "[py-diag] Py_InitializeEx NOT in global scope " - "(sym=%p, err=%s)\n", - sym, dlerror()); - MOBase::log::warn("python: Py_InitializeEx NOT in global scope " - "(sym={}, err={})", - sym, dlerror()); - } - sym = dlsym(RTLD_DEFAULT, "Py_IsInitialized"); - if (sym && dladdr(sym, &di)) { - fprintf(stderr, "[py-diag] Py_IsInitialized global from '%s'\n", - di.dli_fname); - MOBase::log::debug("python: Py_IsInitialized (global) from '{}'", - di.dli_fname); + } else { + // Fallback: load by full path (not NOLOAD). + pyHandle = dlopen(di.dli_fname, RTLD_NOW | RTLD_GLOBAL); + if (pyHandle) { + MOBase::log::debug( + "python: loaded '{}' with RTLD_GLOBAL (fresh)", + di.dli_fname); + } else { + MOBase::log::warn( + "python: failed to promote '{}' to RTLD_GLOBAL: {}", + di.dli_fname, dlerror()); + } + } } else { - fprintf(stderr, "[py-diag] Py_IsInitialized NOT in global scope\n"); - MOBase::log::warn("python: Py_IsInitialized NOT in global scope"); + // Py_IsInitialized not yet in scope — libpython may not be loaded + // as a dependency yet. Try the compile-time name. +#ifdef MO2_PYTHON_SHARED_LIBRARY + void* pyHandle = + dlopen(MO2_PYTHON_SHARED_LIBRARY, RTLD_NOW | RTLD_GLOBAL); + if (pyHandle) { + MOBase::log::debug( + "python: loaded '{}' with RTLD_GLOBAL (compile-time name)", + MO2_PYTHON_SHARED_LIBRARY); + } else { + MOBase::log::warn( + "python: failed to dlopen '{}': {}", + MO2_PYTHON_SHARED_LIBRARY, dlerror()); + } +#else + MOBase::log::warn( + "python: Py_IsInitialized not found in global scope and " + "no compile-time library name available"); +#endif } } #endif -#endif // For portable/AppImage builds, set PYTHONHOME so the interpreter // finds the bundled stdlib instead of looking at system paths. @@ -221,8 +229,18 @@ namespace mo2::python { { PyConfig config; PyConfig_InitPythonConfig(&config); - // PYTHONHOME is already set via setenv; PyConfig reads it from env. - PyStatus status = Py_InitializeFromConfig(&config); + // Set config.home directly (more reliable than env for embedded use). + std::wstring wHome = pythonHome.toStdWString(); + PyStatus status = PyConfig_SetString(&config, &config.home, wHome.c_str()); + if (PyStatus_Exception(status)) { + MOBase::log::error( + "python: PyConfig_SetString(home) failed: '{}'", + status.err_msg ? status.err_msg : "(no message)"); + PyConfig_Clear(&config); + restorePythonEnv(); + return false; + } + status = Py_InitializeFromConfig(&config); PyConfig_Clear(&config); if (PyStatus_Exception(status)) { fprintf(stderr, |
