From: Stefan Reinauer Date: Fri, 12 Jun 2026 07:03:14 +0000 (-0700) Subject: od-unix: persist host settings in winuae.ini X-Git-Url: https://git.unchartedbackwaters.co.uk/w/?a=commitdiff_plain;h=38a54f7dc4ded94f09ad25de4fb214927d973d97;p=francis%2Fwinuae.git od-unix: persist host settings in winuae.ini The Unix port had no equivalent of the Windows registry/winuae.ini store: the Qt Paths page reverted to hardcoded defaults on every start (GitHub issue #2). Port the ini-file mode of the od-win32 registry abstraction into od-unix/registry.cpp on top of the shared ini.cpp, with the same registry.h API. The store resolves to winuae.ini next to the executable when present (portable mode, matching Windows), otherwise $XDG_CONFIG_HOME/winuae/winuae.ini on Linux and ~/Library/Application Support/WinUAE/winuae.ini on macOS; the WINUAE_INI environment variable overrides the location. The Qt launcher loads the Paths page directories and flags from the store and saves them when the dialog closes, through new host-setting hooks on the provider struct. The core path fetchers consult the stored values between per-config unix.*_path overrides and the built-in defaults, so the configured directories also apply outside the GUI. Settings flush on dialog close and at gui_exit. Covered by winuae_unix_registry_test (value types, subtrees, enumeration, deletion, and persistence across reopen). --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ed0420d..e2b3c4c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1389,6 +1389,7 @@ set(WINUAE_CORE_SOURCES od-unix/mman.cpp od-unix/path_expand.cpp od-unix/parallel.cpp + od-unix/registry.cpp od-unix/romscan.cpp od-unix/rtg.cpp od-unix/screenshot.cpp @@ -1467,6 +1468,18 @@ target_include_directories(winuae_unix_path_expand_test PRIVATE ) winuae_unix_register_test(winuae_unix_path_expand_test) +add_executable(winuae_unix_registry_test EXCLUDE_FROM_ALL + od-unix/registry.cpp + od-unix/registry_test.cpp + ini.cpp + missing.cpp +) +target_include_directories(winuae_unix_registry_test PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/od-unix" + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) +winuae_unix_register_test(winuae_unix_registry_test) + add_executable(winuae_unix_threading_test EXCLUDE_FROM_ALL od-unix/threading.cpp od-unix/threading_test.cpp diff --git a/README_unix.md b/README_unix.md index c2933d0a..5db82e12 100644 --- a/README_unix.md +++ b/README_unix.md @@ -334,6 +334,15 @@ When the integrated Qt UI is built, `winuae_unix` opens the configuration UI by The executable also tries to load `default.uae` from the configuration path by default, and the Qt configuration UI starts with it loaded when it exists — save a configuration named `default` to set your startup defaults, like on Windows. Configs given on the command line take precedence. A missing default config is ignored silently; explicit `-config` / `-f` load failures are still reported. +Host settings (the Paths page directories and flags) persist in `winuae.ini`: +a `winuae.ini` next to the executable is used when present (portable mode, +matching Windows); otherwise `$XDG_CONFIG_HOME/winuae/winuae.ini` on Linux +(default `~/.config/winuae/winuae.ini`) and +`~/Library/Application Support/WinUAE/winuae.ini` on macOS. The `WINUAE_INI` +environment variable overrides the location. Configs loaded per session can +still override individual paths through the `unix.*_path` settings, which win +over the stored host defaults. + Unix path expansion is supported for `~/`, `$VAR`, and `${VAR}` in core config paths and Qt file/config boundaries. Relative config and media paths are resolved against the process working directory, matching the non-relative Windows save mode; the Windows-style relative-path save option is not enabled on Unix yet. The Qt Paths page saves runtime-visible target path settings such as `unix.screenshot_path`, `unix.rip_path`, `unix.video_path`, and `statefile_path`; legacy `unix.ui.*` path settings are still accepted when loading older configs. `~user` expansion is not implemented; use an absolute path for another user's home directory. For SANA-II `uaenet.device` startup testing, add `-s sana2=true` or use the smoke target below. Guest TCP/IP stack validation is still pending. diff --git a/od-unix/config.cpp b/od-unix/config.cpp index 40d51ca8..b8e52969 100644 --- a/od-unix/config.cpp +++ b/od-unix/config.cpp @@ -13,6 +13,7 @@ extern int video_recording_active; static int unix_avi_audio_codec = AVIAUDIO_AVI; #endif #include "path_expand.h" +#include "registry.h" #include "romscan.h" #include "savestate.h" #include "sound_unix.h" @@ -770,27 +771,38 @@ static void fetch_user_data_path(TCHAR *out, int size, const char *subdir) fixtrailing(out); } -static void fetch_user_data_path_override(TCHAR *out, int size, const TCHAR *override_path, const char *subdir) +static void fetch_user_data_path_override(TCHAR *out, int size, const TCHAR *override_path, const char *regkey, const char *subdir) { if (override_path && override_path[0]) { uae_tcslcpy(out, override_path, size); fixtrailing(out); - } else { - fetch_user_data_path(out, size, subdir); + return; + } + /* Per-config overrides win; otherwise consult the persistent host + * settings (winuae.ini) before falling back to built-in defaults. */ + if (regkey) { + TCHAR stored[MAX_DPATH]; + int stored_size = MAX_DPATH; + if (regquerystr(NULL, regkey, stored, &stored_size) && stored[0]) { + target_expand_environment(stored, out, size); + fixtrailing(out); + return; + } } + fetch_user_data_path(out, size, subdir); } -void fetch_saveimagepath(TCHAR *out, int size, int) { fetch_user_data_path_override(out, size, path_saveimage, "SaveImages"); } -void fetch_configurationpath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_configuration, "Configuration"); } -void fetch_nvrampath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_nvram, "NVRAMs"); } +void fetch_saveimagepath(TCHAR *out, int size, int) { fetch_user_data_path_override(out, size, path_saveimage, "SaveimagePath", "SaveImages"); } +void fetch_configurationpath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_configuration, "ConfigurationPath", "Configuration"); } +void fetch_nvrampath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_nvram, "NVRAMPath", "NVRAMs"); } void fetch_luapath(TCHAR *out, int size) { fetch_home_path(out, size); } -void fetch_screenshotpath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_screenshot, "Screenshots"); } -void fetch_ripperpath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_ripper, "Rips"); } -void fetch_statefilepath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_statefile, "Save States"); } +void fetch_screenshotpath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_screenshot, "ScreenshotPath", "Screenshots"); } +void fetch_ripperpath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_ripper, "RipperPath", "Rips"); } +void fetch_statefilepath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_statefile, "StatefilePath", "Save States"); } void fetch_inputfilepath(TCHAR *out, int size) { fetch_home_path(out, size); } -void fetch_datapath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_data, NULL); } -void fetch_rompath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_rom, "Kickstarts"); } -void fetch_videopath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_video, "Videos"); } +void fetch_datapath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_data, "DataPath", NULL); } +void fetch_rompath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_rom, "KickstartPath", "Kickstarts"); } +void fetch_videopath(TCHAR *out, int size) { fetch_user_data_path_override(out, size, path_video, "VideoPath", "Videos"); } void target_getdate(int *y, int *m, int *d) { diff --git a/od-unix/gui.cpp b/od-unix/gui.cpp index aef839ee..3a197558 100644 --- a/od-unix/gui.cpp +++ b/od-unix/gui.cpp @@ -11,6 +11,7 @@ #include "custom.h" #include "inputdevice.h" #include "gui.h" +#include "registry.h" #include "target.h" #include "target_main.h" #include "savestate.h" @@ -69,7 +70,10 @@ int gui_init(void) #endif } int gui_update(void) { return 1; } -void gui_exit(void) {} +void gui_exit(void) +{ + registry_flush(); +} void gui_led(int led, int on, int brightness) { if (on >= 0 && led >= 0 && led < int(sizeof(gui_ledstate) * 8)) { diff --git a/od-unix/qt/launcher.cpp b/od-unix/qt/launcher.cpp index 79b79e12..602e34a1 100644 --- a/od-unix/qt/launcher.cpp +++ b/od-unix/qt/launcher.cpp @@ -13336,6 +13336,9 @@ private: relativePaths->setChecked(false); portableMode->setChecked(false); pathDefaultType->setCurrentText(QStringLiteral("User data directory")); + /* Paths and path flags are host settings: restore the persisted + * values instead of the factory defaults. */ + loadHostSettings(); logSelect->setCurrentText(QStringLiteral("winuaebootlog.txt")); fullLogging->setChecked(false); logWindow->setChecked(false); @@ -13352,6 +13355,85 @@ private: status->setText(QStringLiteral("Ready")); } + struct HostPathBinding { + const char *key; + QLineEdit *edit; + }; + + QVector hostPathBindings() const + { + return { + { "KickstartPath", romsPath }, + { "ConfigurationPath", configsPath }, + { "NVRAMPath", nvramPath }, + { "ScreenshotPath", screenshotsPath }, + { "StatefilePath", stateFilesPath }, + { "VideoPath", videosPath }, + { "SaveimagePath", saveImagesPath }, + { "RipperPath", ripsPath }, + { "DataPath", dataPath }, + }; + } + + struct HostFlagBinding { + const char *key; + QCheckBox *box; + }; + + QVector hostFlagBindings() const + { + return { + { "RecursiveROMScan", recursiveRoms }, + { "ConfigurationCache", cacheConfigurations }, + { "ArtCache", cacheBoxArt }, + { "SaveImageOriginalPath", saveImageOriginalPath }, + }; + } + + void loadHostSettings() + { + if (!hardwareProvider.hostSettingGet) { + return; + } + char value[4096]; + for (const HostPathBinding &binding : hostPathBindings()) { + if (binding.edit && hardwareProvider.hostSettingGet(hardwareProvider.context, binding.key, value, sizeof value)) { + binding.edit->setText(QString::fromLocal8Bit(value)); + } + } + for (const HostFlagBinding &binding : hostFlagBindings()) { + if (binding.box && hardwareProvider.hostSettingGet(hardwareProvider.context, binding.key, value, sizeof value)) { + binding.box->setChecked(QString::fromLocal8Bit(value).toInt() != 0); + } + } + } + + void saveHostSettings() + { + if (!hardwareProvider.hostSettingSet) { + return; + } + for (const HostPathBinding &binding : hostPathBindings()) { + if (binding.edit) { + hardwareProvider.hostSettingSet(hardwareProvider.context, binding.key, binding.edit->text().toLocal8Bit().constData()); + } + } + for (const HostFlagBinding &binding : hostFlagBindings()) { + if (binding.box) { + hardwareProvider.hostSettingSet(hardwareProvider.context, binding.key, binding.box->isChecked() ? "1" : "0"); + } + } + if (hardwareProvider.hostSettingsFlush) { + hardwareProvider.hostSettingsFlush(hardwareProvider.context); + } + } + + void done(int result) override + { + saveHostSettings(); + QDialog::done(result); + } + void applyModelPreset(const QString &model) { if (model == QStringLiteral("A1200") || model == QStringLiteral("CD32")) { diff --git a/od-unix/qt/launcher.h b/od-unix/qt/launcher.h index 2ceaae8e..381b304e 100644 --- a/od-unix/qt/launcher.h +++ b/od-unix/qt/launcher.h @@ -116,6 +116,9 @@ struct WinUaeQtBoardCatalog { struct WinUaeQtHardwareInfoProvider { void *context = nullptr; + bool (*hostSettingGet)(void *context, const char *key, char *out, int outLen) = nullptr; + void (*hostSettingSet)(void *context, const char *key, const char *value) = nullptr; + void (*hostSettingsFlush)(void *context) = nullptr; WinUaeQtBoardCatalog (*boardCatalog)(void *context) = nullptr; bool (*applyConfig)(void *context, const WinUaeQtConfig &config) = nullptr; QVector (*boards)(void *context) = nullptr; diff --git a/od-unix/qt/launcher_bridge.cpp b/od-unix/qt/launcher_bridge.cpp index 92844d30..3a18df98 100644 --- a/od-unix/qt/launcher_bridge.cpp +++ b/od-unix/qt/launcher_bridge.cpp @@ -29,6 +29,7 @@ #include "zfile.h" #include "gfxboard.h" #include "ethernet.h" +#include "registry.h" #ifdef PROWIZARD #include "moduleripper.h" #endif @@ -480,10 +481,30 @@ static void bridgePollHostWindowEvents(void *) unix_host_check_quit(); } +static bool bridgeHostSettingGet(void *, const char *key, char *out, int outLen) +{ + int size = outLen; + out[0] = 0; + return regquerystr(nullptr, key, out, &size) != 0; +} + +static void bridgeHostSettingSet(void *, const char *key, const char *value) +{ + regsetstr(nullptr, key, value); +} + +static void bridgeHostSettingsFlush(void *) +{ + registry_flush(); +} + static WinUaeQtHardwareInfoProvider bridgeHardwareProvider(struct uae_prefs *prefs, bool runtimeActions) { WinUaeQtHardwareInfoProvider provider; provider.context = prefs; + provider.hostSettingGet = bridgeHostSettingGet; + provider.hostSettingSet = bridgeHostSettingSet; + provider.hostSettingsFlush = bridgeHostSettingsFlush; provider.boardCatalog = bridgeBoardCatalog; provider.applyConfig = bridgeApplyHardwareConfig; provider.boards = bridgeHardwareBoards; diff --git a/od-unix/registry.cpp b/od-unix/registry.cpp new file mode 100644 index 00000000..e8a7ba6f --- /dev/null +++ b/od-unix/registry.cpp @@ -0,0 +1,400 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include +#include +#if defined(UAE_HOST_DARWIN) +#include +#endif + +#include "registry.h" +#include "ini.h" +#include "uae/string.h" + +/* Unix port of the od-win32 registry abstraction. Only the ini-file mode + * exists here; trees map to slash-joined ini section names below the + * WinUAE root section, exactly like winuae.ini on Windows. */ + +#define ROOT_TREE _T("WinUAE") + +static int initialized; +static TCHAR inipath[MAX_DPATH]; +static TCHAR explicit_inipath[MAX_DPATH]; +static struct ini_data *inidata; + +static bool registry_executable_dir(TCHAR *out, size_t out_size) +{ +#if defined(UAE_HOST_DARWIN) + uint32_t size = out_size; + if (_NSGetExecutablePath(out, &size) != 0) { + return false; + } +#elif defined(UAE_HOST_LINUX) + ssize_t len = readlink("/proc/self/exe", out, out_size - 1); + if (len <= 0 || (size_t)len >= out_size) { + return false; + } + out[len] = 0; +#else + return false; +#endif + TCHAR *slash = _tcsrchr(out, '/'); + if (!slash) { + return false; + } + *slash = 0; + return true; +} + +static void registry_user_ini_path(TCHAR *out, size_t out_size) +{ + const char *home = getenv("HOME"); + if (!home || !home[0]) { + home = "/tmp"; + } +#if defined(UAE_HOST_DARWIN) + snprintf(out, out_size, "%s/Library/Application Support/WinUAE/winuae.ini", home); +#else + const char *config_home = getenv("XDG_CONFIG_HOME"); + if (config_home && config_home[0]) { + snprintf(out, out_size, "%s/winuae/winuae.ini", config_home); + } else { + snprintf(out, out_size, "%s/.config/winuae/winuae.ini", home); + } +#endif +} + +static void registry_resolve_path(void) +{ + if (explicit_inipath[0]) { + uae_tcslcpy(inipath, explicit_inipath, MAX_DPATH); + return; + } + const char *env = getenv("WINUAE_INI"); + if (env && env[0]) { + uae_tcslcpy(inipath, env, MAX_DPATH); + return; + } + TCHAR exedir[MAX_DPATH]; + if (registry_executable_dir(exedir, MAX_DPATH) + && _tcslen(exedir) + 12 < MAX_DPATH) { + _stprintf(inipath, _T("%s/winuae.ini"), exedir); + if (access(inipath, F_OK) == 0) { + /* Portable mode, matching winuae.ini next to winuae.exe. */ + return; + } + } + registry_user_ini_path(inipath, MAX_DPATH); +} + +static void registry_mkdirs(const TCHAR *path) +{ + TCHAR tmp[MAX_DPATH]; + uae_tcslcpy(tmp, path, MAX_DPATH); + TCHAR *slash = _tcsrchr(tmp, '/'); + if (!slash || slash == tmp) { + return; + } + *slash = 0; + for (TCHAR *p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = 0; + mkdir(tmp, 0700); + *p = '/'; + } + } + mkdir(tmp, 0700); +} + +static void registry_init(void) +{ + if (initialized) { + return; + } + initialized = 1; + registry_resolve_path(); + inidata = ini_load(inipath, true); + if (!inidata) { + inidata = ini_new(); + } +} + +static const TCHAR *gs (UAEREG *root) +{ + if (!root) + return ROOT_TREE; + return root->inipath; +} + +static TCHAR *gsn (UAEREG *root, const TCHAR *name) +{ + const TCHAR *r; + TCHAR *s; + if (!root) + return my_strdup (name); + r = gs (root); + s = xmalloc (TCHAR, _tcslen (r) + 1 + _tcslen (name) + 1); + _stprintf (s, _T("%s/%s"), r, name); + return s; +} + +void registry_set_ini_path (const TCHAR *path) +{ + if (path && path[0]) { + uae_tcslcpy(explicit_inipath, path, MAX_DPATH); + } else { + explicit_inipath[0] = 0; + } + if (initialized) { + registry_flush(); + ini_free(inidata); + inidata = NULL; + initialized = 0; + } +} + +void registry_flush (void) +{ + if (!initialized || !inidata || !inidata->modified) { + return; + } + registry_mkdirs(inipath); + if (ini_save(inidata, inipath)) { + inidata->modified = false; + } else { + write_log(_T("Failed to save host settings to '%s'\n"), inipath); + } +} + +int regsetstr (UAEREG *root, const TCHAR *name, const TCHAR *str) +{ + registry_init(); + return ini_addstring(inidata, gs(root), name, str); +} + +int regsetint (UAEREG *root, const TCHAR *name, int val) +{ + TCHAR tmp[100]; + _stprintf (tmp, _T("%d"), val); + return regsetstr(root, name, tmp); +} + +int regqueryint (UAEREG *root, const TCHAR *name, int *val) +{ + int ret = 0; + TCHAR *tmp = NULL; + registry_init(); + if (ini_getstring(inidata, gs(root), name, &tmp)) { + *val = _tstol (tmp); + ret = 1; + } + xfree(tmp); + return ret; +} + +int regsetlonglong (UAEREG *root, const TCHAR *name, unsigned long long val) +{ + TCHAR tmp[100]; + _stprintf (tmp, _T("%lld"), (long long)val); + return regsetstr(root, name, tmp); +} + +int regquerylonglong (UAEREG *root, const TCHAR *name, unsigned long long *val) +{ + int ret = 0; + TCHAR *tmp = NULL; + *val = 0; + registry_init(); + if (ini_getstring(inidata, gs(root), name, &tmp)) { + *val = strtoll (tmp, NULL, 10); + ret = 1; + } + xfree(tmp); + return ret; +} + +int regquerystr (UAEREG *root, const TCHAR *name, TCHAR *str, int *size) +{ + int ret = 0; + TCHAR *tmp = NULL; + registry_init(); + if (ini_getstring(inidata, gs(root), name, &tmp)) { + if (_tcslen(tmp) >= (size_t)*size) + tmp[(*size) - 1] = 0; + _tcscpy (str, tmp); + *size = (int)_tcslen(str); + ret = 1; + } + xfree (tmp); + return ret; +} + +int regenumstr (UAEREG *root, int idx, TCHAR *name, int *nsize, TCHAR *str, int *size) +{ + TCHAR *name2 = NULL; + TCHAR *str2 = NULL; + name[0] = 0; + str[0] = 0; + registry_init(); + int ret = ini_getsectionstring(inidata, gs(root), idx, &name2, &str2); + if (ret) { + if (_tcslen(name2) >= (size_t)*nsize) { + name2[(*nsize) - 1] = 0; + } + if (_tcslen(str2) >= (size_t)*size) { + str2[(*size) - 1] = 0; + } + _tcscpy(name, name2); + _tcscpy(str, str2); + } + xfree(str2); + xfree(name2); + return ret; +} + +int regquerydatasize (UAEREG *root, const TCHAR *name, int *size) +{ + int ret = 0; + int csize = 65536; + TCHAR *tmp = xmalloc (TCHAR, csize); + if (regquerystr (root, name, tmp, &csize)) { + *size = (int)_tcslen(tmp) / 2; + ret = 1; + } + xfree (tmp); + return ret; +} + +int regsetdata (UAEREG *root, const TCHAR *name, const void *str, int size) +{ + uae_u8 *in = (uae_u8*)str; + int ret; + TCHAR *tmp = xmalloc (TCHAR, size * 2 + 1); + for (int i = 0; i < size; i++) + _stprintf (tmp + i * 2, _T("%02X"), in[i]); + registry_init(); + ret = ini_addstring(inidata, gs(root), name, tmp); + xfree (tmp); + return ret; +} + +int regquerydata (UAEREG *root, const TCHAR *name, void *str, int *size) +{ + int csize = (*size) * 2 + 1; + int i, j; + int ret = 0; + TCHAR *tmp = xmalloc (TCHAR, csize); + uae_u8 *out = (uae_u8*)str; + + if (!regquerystr (root, name, tmp, &csize)) + goto err; + j = 0; + for (i = 0; i < (int)_tcslen (tmp); i += 2) { + TCHAR c1 = _totupper(tmp[i + 0]); + TCHAR c2 = _totupper(tmp[i + 1]); + if (c1 >= 'A') + c1 -= 'A' - 10; + else if (c1 >= '0') + c1 -= '0'; + if (c1 > 15) + goto err; + if (c2 >= 'A') + c2 -= 'A' - 10; + else if (c2 >= '0') + c2 -= '0'; + if (c2 > 15) + goto err; + out[j++] = c1 * 16 + c2; + } + ret = 1; +err: + xfree (tmp); + return ret; +} + +int regdelete (UAEREG *root, const TCHAR *name) +{ + registry_init(); + ini_delete(inidata, gs(root), name); + return 1; +} + +int regexists (UAEREG *root, const TCHAR *name) +{ + registry_init(); + if (!inidata) + return 0; + return ini_getstring(inidata, gs(root), name, NULL); +} + +void regdeletetree (UAEREG *root, const TCHAR *name) +{ + TCHAR *s = gsn (root, name); + if (!s) + return; + registry_init(); + ini_delete(inidata, s, NULL); + xfree (s); +} + +int regexiststree (UAEREG *root, const TCHAR *name) +{ + int ret = 0; + TCHAR *s = gsn (root, name); + if (!s) + return 0; + registry_init(); + ret = ini_getstring(inidata, s, NULL, NULL); + xfree (s); + return ret; +} + +UAEREG *regcreatetree (UAEREG *root, const TCHAR *name) +{ + UAEREG *fkey; + TCHAR *ininame; + + registry_init(); + if (!root) { + if (!name) + ininame = my_strdup (gs (NULL)); + else + ininame = my_strdup (name); + } else { + ininame = xmalloc (TCHAR, _tcslen (root->inipath) + 1 + _tcslen (name) + 1); + _stprintf (ininame, _T("%s/%s"), root->inipath, name); + } + fkey = xcalloc (UAEREG, 1); + fkey->inipath = ininame; + return fkey; +} + +void regclosetree (UAEREG *key) +{ + registry_flush(); + if (!key) + return; + xfree (key->inipath); + xfree (key); +} + +int reginitializeinit (TCHAR **pppath) +{ + if (pppath && *pppath) { + registry_set_ini_path(*pppath); + } + registry_init(); + return 1; +} + +void regstatus (void) +{ + registry_init(); + write_log (_T("Host settings: '%s'\n"), inipath); +} + +const TCHAR *getregmode (void) +{ + registry_init(); + return inipath; +} diff --git a/od-unix/registry.h b/od-unix/registry.h new file mode 100644 index 00000000..9483093a --- /dev/null +++ b/od-unix/registry.h @@ -0,0 +1,39 @@ +#pragma once + +/* Unix host settings store with the Windows registry.cpp API, always in + * ini-file mode. Values persist in winuae.ini next to the executable + * (portable mode) or in the per-user configuration directory. */ + +typedef struct UAEREG { + TCHAR *inipath; +} UAEREG; + +extern const TCHAR *getregmode (void); +extern int reginitializeinit (TCHAR **path); +extern void regstatus (void); + +extern int regsetstr (UAEREG*, const TCHAR *name, const TCHAR *str); +extern int regsetint (UAEREG*, const TCHAR *name, int val); +extern int regqueryint (UAEREG*, const TCHAR *name, int *val); +extern int regquerystr (UAEREG*, const TCHAR *name, TCHAR *str, int *size); +extern int regsetlonglong (UAEREG *root, const TCHAR *name, unsigned long long val); +extern int regquerylonglong (UAEREG *root, const TCHAR *name, unsigned long long *val); + +extern int regdelete (UAEREG*, const TCHAR *name); +extern void regdeletetree (UAEREG*, const TCHAR *name); + +extern int regexists (UAEREG*, const TCHAR *name); +extern int regexiststree (UAEREG *, const TCHAR *name); + +extern int regquerydatasize (UAEREG *root, const TCHAR *name, int *size); +extern int regsetdata (UAEREG*, const TCHAR *name, const void *str, int size); +extern int regquerydata (UAEREG *root, const TCHAR *name, void *str, int *size); + +extern int regenumstr (UAEREG*, int idx, TCHAR *name, int *nsize, TCHAR *str, int *size); + +extern UAEREG *regcreatetree (UAEREG*, const TCHAR *name); +extern void regclosetree (UAEREG *key); + +/* Unix additions. */ +extern void registry_set_ini_path (const TCHAR *path); +extern void registry_flush (void); diff --git a/od-unix/registry_test.cpp b/od-unix/registry_test.cpp new file mode 100644 index 00000000..07220f67 --- /dev/null +++ b/od-unix/registry_test.cpp @@ -0,0 +1,109 @@ +#include "sysconfig.h" +#include "sysdeps.h" + +#include +#include +#include +#include +#include + +#include "registry.h" + +void write_log(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +int uaetcslen(const TCHAR *s) +{ + return (int)strlen(s); +} + +static int failures; + +static void check(bool ok, const char *what) +{ + if (!ok) { + fprintf(stderr, "FAIL: %s\n", what); + failures++; + } +} + +int main(void) +{ + char path[256]; + snprintf(path, sizeof path, "/tmp/winuae_registry_test_%ld/sub/winuae.ini", (long)getpid()); + registry_set_ini_path(path); + + /* Root values. */ + check(regsetstr(NULL, "TestString", "hello world") == 1, "regsetstr"); + check(regsetint(NULL, "TestInt", -42) == 1, "regsetint"); + check(regsetlonglong(NULL, "TestLong", 123456789012345ULL) == 1, "regsetlonglong"); + + TCHAR buf[256]; + int size = sizeof buf; + check(regquerystr(NULL, "TestString", buf, &size) == 1 && !strcmp(buf, "hello world"), "regquerystr"); + int v = 0; + check(regqueryint(NULL, "TestInt", &v) == 1 && v == -42, "regqueryint"); + unsigned long long lv = 0; + check(regquerylonglong(NULL, "TestLong", &lv) == 1 && lv == 123456789012345ULL, "regquerylonglong"); + check(regexists(NULL, "TestString") == 1, "regexists"); + check(regexists(NULL, "Missing") == 0, "regexists missing"); + + /* Subtrees. */ + UAEREG *tree = regcreatetree(NULL, "DetectedROMs"); + check(tree != NULL, "regcreatetree"); + check(regsetstr(tree, "ROM_1", "kick13.rom") == 1, "tree regsetstr"); + size = sizeof buf; + check(regquerystr(tree, "ROM_1", buf, &size) == 1 && !strcmp(buf, "kick13.rom"), "tree regquerystr"); + check(regexiststree(NULL, "DetectedROMs") == 1, "regexiststree"); + + /* Enumeration. */ + TCHAR name[64]; + int nsize = sizeof name; + size = sizeof buf; + check(regenumstr(tree, 0, name, &nsize, buf, &size) == 1 && !strcmp(name, "ROM_1"), "regenumstr"); + regclosetree(tree); + + /* Binary data. */ + const unsigned char data[4] = { 0xde, 0xad, 0xbe, 0xef }; + check(regsetdata(NULL, "TestData", data, sizeof data) == 1, "regsetdata"); + unsigned char out[4] = { 0 }; + size = sizeof out; + check(regquerydata(NULL, "TestData", out, &size) == 1 && !memcmp(out, data, sizeof data), "regquerydata"); + size = 0; + check(regquerydatasize(NULL, "TestData", &size) == 1 && size == 4, "regquerydatasize"); + + /* Delete. */ + check(regdelete(NULL, "TestInt") == 1, "regdelete"); + check(regexists(NULL, "TestInt") == 0, "deleted value gone"); + + /* Persistence: flush, drop state, re-open the same file. */ + registry_flush(); + registry_set_ini_path(path); + size = sizeof buf; + check(regquerystr(NULL, "TestString", buf, &size) == 1 && !strcmp(buf, "hello world"), "persisted string"); + UAEREG *tree2 = regcreatetree(NULL, "DetectedROMs"); + size = sizeof buf; + check(regquerystr(tree2, "ROM_1", buf, &size) == 1 && !strcmp(buf, "kick13.rom"), "persisted tree value"); + regclosetree(tree2); + check(regexists(NULL, "TestInt") == 0, "deleted value stays gone"); + + /* Tree deletion. */ + regdeletetree(NULL, "DetectedROMs"); + check(regexiststree(NULL, "DetectedROMs") == 0, "regdeletetree"); + + char cmd[300]; + snprintf(cmd, sizeof cmd, "rm -rf /tmp/winuae_registry_test_%ld", (long)getpid()); + system(cmd); + + if (failures) { + fprintf(stderr, "%d failure(s)\n", failures); + return 1; + } + printf("winuae_unix_registry_test: all checks passed\n"); + return 0; +}