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
)
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
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.
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"
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)
{
#include "custom.h"
#include "inputdevice.h"
#include "gui.h"
+#include "registry.h"
#include "target.h"
#include "target_main.h"
#include "savestate.h"
#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)) {
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);
status->setText(QStringLiteral("Ready"));
}
+ struct HostPathBinding {
+ const char *key;
+ QLineEdit *edit;
+ };
+
+ QVector<HostPathBinding> 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<HostFlagBinding> 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")) {
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<WinUaeQtHardwareBoard> (*boards)(void *context) = nullptr;
#include "zfile.h"
#include "gfxboard.h"
#include "ethernet.h"
+#include "registry.h"
#ifdef PROWIZARD
#include "moduleripper.h"
#endif
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;
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#if defined(UAE_HOST_DARWIN)
+#include <mach-o/dyld.h>
+#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;
+}
--- /dev/null
+#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);
--- /dev/null
+#include "sysconfig.h"
+#include "sysdeps.h"
+
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <unistd.h>
+
+#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;
+}