SDL: Improve RISC OS implementations of SDL_GetBasePath and SDL_GetPrefPath

From 9ee6942e79a0674384d7ad948bbae16c5c16a43c Mon Sep 17 00:00:00 2001
From: Cameron Cawley <[EMAIL REDACTED]>
Date: Wed, 22 Sep 2021 14:01:00 +0100
Subject: [PATCH] Improve RISC OS implementations of SDL_GetBasePath and
 SDL_GetPrefPath

---
 src/filesystem/riscos/SDL_sysfilesystem.c | 170 ++++++++++++++++++----
 1 file changed, 144 insertions(+), 26 deletions(-)

diff --git a/src/filesystem/riscos/SDL_sysfilesystem.c b/src/filesystem/riscos/SDL_sysfilesystem.c
index a16effffe9..125470bb2d 100644
--- a/src/filesystem/riscos/SDL_sysfilesystem.c
+++ b/src/filesystem/riscos/SDL_sysfilesystem.c
@@ -25,28 +25,144 @@
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 /* System dependent filesystem routines                                */
 
-#include <errno.h>
-#include <sys/stat.h>
+#include <kernel.h>
+#include <swis.h>
+#include <unixlib/local.h>
 
 #include "SDL_error.h"
 #include "SDL_stdinc.h"
 #include "SDL_filesystem.h"
-#include "SDL_rwops.h"
+
+/* Wrapper around __unixify_std that uses SDL's memory allocators */
+static char *
+SDL_unixify_std(const char *ro_path, char *buffer, size_t buf_len, int filetype)
+{
+    const char *const in_buf = buffer; /* = NULL if we malloc the buffer.  */
+
+    if (!buffer) {
+        /* This matches the logic in __unixify, with an additional byte for the
+         * extra path separator.
+         */
+        buf_len = SDL_strlen(ro_path) + 14 + 1;
+        buffer = SDL_malloc(buf_len);
+
+        if (!buffer) {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+    }
+
+    if (!__unixify_std(ro_path, buffer, buf_len, filetype)) {
+        if (!in_buf)
+            SDL_free(buffer);
+
+        SDL_SetError("Could not convert '%s' to a Unix-style path", ro_path);
+        return NULL;
+    }
+
+    /* HACK: It's necessary to add an extra path separator here since SDL's API
+     * requires it, however paths with trailing separators aren't normally valid
+     * on RISC OS.
+     */
+    if (__get_riscosify_control() & __RISCOSIFY_NO_PROCESS)
+        SDL_strlcat(buffer, ".", buf_len);
+    else
+        SDL_strlcat(buffer, "/", buf_len);
+
+    return buffer;
+}
+
+static char *
+canonicalisePath(const char *path, const char *pathVar)
+{
+    _kernel_oserror *error;
+    _kernel_swi_regs regs;
+    char *buf;
+
+    regs.r[0] = 37;
+    regs.r[1] = (int)path;
+    regs.r[2] = 0;
+    regs.r[3] = (int)pathVar;
+    regs.r[4] = 0;
+    regs.r[5] = 0;
+    error = _kernel_swi(OS_FSControl, &regs, &regs);
+    if (error) {
+        SDL_SetError("Couldn't canonicalise path: %s", error->errmess);
+        return NULL;
+    }
+
+    regs.r[5] = 1 - regs.r[5];
+    buf = SDL_malloc(regs.r[5]);
+    if (!buf) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+    regs.r[2] = (int)buf;
+    error = _kernel_swi(OS_FSControl, &regs, &regs);
+    if (error) {
+        SDL_SetError("Couldn't canonicalise path: %s", error->errmess);
+        SDL_free(buf);
+        return NULL;
+    }
+
+    return buf;
+}
+
+static _kernel_oserror *
+createDirectoryRecursive(char *path)
+{
+    char *ptr = NULL;
+    _kernel_oserror *error;
+    _kernel_swi_regs regs;
+    regs.r[0] = 8;
+    regs.r[1] = (int)path;
+    regs.r[2] = 0;
+
+    for (ptr = path+1; *ptr; ptr++) {
+        if (*ptr == '.') {
+            *ptr = '\0';
+            error = _kernel_swi(OS_File, &regs, &regs);
+            *ptr = '.';
+            if (error != NULL)
+                return error;
+        }
+    }
+    return _kernel_swi(OS_File, &regs, &regs);
+}
 
 char *
 SDL_GetBasePath(void)
 {
-    SDL_Unsupported();
-    return NULL;
+    _kernel_swi_regs regs;
+    _kernel_oserror *error;
+    char *canon, *ptr, *retval;
+
+    error = _kernel_swi(OS_GetEnv, &regs, &regs);
+    if (error) {
+        return NULL;
+    }
+
+    canon = canonicalisePath((const char *)regs.r[0], "Run$Path");
+    if (!canon) {
+        return NULL;
+    }
+
+    /* chop off filename. */
+    ptr = SDL_strrchr(canon, '.');
+    if (ptr != NULL)
+        *ptr = '\0';
+
+    retval = SDL_unixify_std(canon, NULL, 0, __RISCOSIFY_FILETYPE_NOTSPECIFIED);
+    SDL_free(canon);
+    return retval;
 }
 
 char *
 SDL_GetPrefPath(const char *org, const char *app)
 {
-    const char *prefix = "/<Choices$Write>/";
-    char *retval = NULL;
-    char *ptr = NULL;
-    size_t len = 0;
+    char *canon, *dir, *retval;
+    size_t len;
+    _kernel_oserror *error;
 
     if (!app) {
         SDL_InvalidParamError("app");
@@ -56,34 +172,36 @@ SDL_GetPrefPath(const char *org, const char *app)
         org = "";
     }
 
-    len = SDL_strlen(prefix) + SDL_strlen(org) + SDL_strlen(app) + 3;
-    retval = (char *) SDL_malloc(len);
-    if (!retval) {
+    canon = canonicalisePath("<Choices$Write>", "Run$Path");
+    if (!canon) {
+        return NULL;
+    }
+
+    len = SDL_strlen(canon) + SDL_strlen(org) + SDL_strlen(app) + 4;
+    dir = (char *) SDL_malloc(len);
+    if (!dir) {
         SDL_OutOfMemory();
+        free(canon);
         return NULL;
     }
 
     if (*org) {
-        SDL_snprintf(retval, len, "%s%s/%s/", prefix, org, app);
+        SDL_snprintf(dir, len, "%s.%s.%s", canon, org, app);
     } else {
-        SDL_snprintf(retval, len, "%s%s/", prefix, app);
+        SDL_snprintf(dir, len, "%s.%s", canon, app);
     }
 
-    for (ptr = retval+1; *ptr; ptr++) {
-        if (*ptr == '/') {
-            *ptr = '\0';
-            if (mkdir(retval, 0700) != 0 && errno != EEXIST)
-                goto error;
-            *ptr = '/';
-        }
-    }
-    if (mkdir(retval, 0700) != 0 && errno != EEXIST) {
-error:
-        SDL_SetError("Couldn't create directory '%s': '%s'", retval, strerror(errno));
-        SDL_free(retval);
+    SDL_free(canon);
+
+    error = createDirectoryRecursive(dir);
+    if (error != NULL) {
+        SDL_SetError("Couldn't create directory: %s", error->errmess);
+        SDL_free(dir);
         return NULL;
     }
 
+    retval = SDL_unixify_std(dir, NULL, 0, __RISCOSIFY_FILETYPE_NOTSPECIFIED);
+    SDL_free(dir);
     return retval;
 }