Using Short Paths (ASCII-compatible) for Exe Dir and User Dir on Windows (#77)

Co-authored-by: Agent X <44549182+AgentXLP@users.noreply.github.com>
This commit is contained in:
Radek Krzyśków 2024-06-24 00:38:32 +02:00 committed by GitHub
parent f348e03685
commit 227a4bbcf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 191 additions and 228 deletions

View file

@ -785,7 +785,7 @@ ifneq ($(SDL1_USED)$(SDL2_USED),00)
endif
ifeq ($(WINDOWS_BUILD),1)
BACKEND_LDFLAGS += `$(SDLCONFIG) --static-libs` -lsetupapi -luser32 -limm32 -lole32 -loleaut32 -lshell32 -lwinmm -lversion
BACKEND_LDFLAGS += `$(SDLCONFIG) --static-libs` -lsetupapi -luser32 -limm32 -lole32 -loleaut32 -lshell32 -lshlwapi -lwinmm -lversion
else
BACKEND_LDFLAGS += `$(SDLCONFIG) --libs`
endif

View file

@ -3,7 +3,6 @@
#include "pc/ini.h"
#include "pc/mods/mods.h"
#include "pc/mods/mods_utils.h"
#include "pc/os/os.h"
#include "player_palette.h"
const struct PlayerPalette DEFAULT_MARIO_PALETTE =
@ -64,14 +63,14 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) {
}
// open directory
os_dirent* dir = NULL;
struct dirent* dir = NULL;
OS_DIR* d = os_opendir(lpath);
DIR* d = opendir(lpath);
if (!d) { return; }
// iterate
char path[SYS_MAX_PATH] = { 0 };
while ((dir = os_readdir(d)) != NULL) {
while ((dir = readdir(d)) != NULL) {
// sanity check / fill path[]
if (!directory_sanity_check(dir, lpath, path)) { continue; }
snprintf(path, SYS_MAX_PATH, "%s", os_get_dir_name(dir));
@ -113,7 +112,7 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) {
if (gPresetPaletteCount >= MAX_PRESET_PALETTES) { break; }
}
os_closedir(d);
closedir(d);
// this should mean we are in the exe path's palette dir
if (appendPalettes) {
@ -128,7 +127,7 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) {
}
}
}
// copy remaining palettes
for (int i = 0; i < gPresetPaletteCount; i++) {
bool isCharacterPalette = false;

View file

@ -40,7 +40,7 @@ static CrashHandlerText sCrashHandlerText[128 + 256 + 4];
#define PTR long long unsigned int)(uintptr_t
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define MEMNEW(typ, cnt) calloc(sizeof(typ), cnt)
#define MEMNEW(typ, cnt) calloc(cnt, sizeof(typ))
#define STRING(str, size, fmt, ...) char str[size]; snprintf(str, size, fmt, __VA_ARGS__);
#define BACK_TRACE_SIZE 15
@ -457,15 +457,9 @@ static void crash_handler(const int signalNum, siginfo_t *info, UNUSED ucontext_
// Load symbols
char filename[256] = { 0 };
if (GetModuleFileName(NULL, filename, sizeof(filename))) {
int index = strlen(filename);
while (--index > 0) {
if (filename[index] == '\\') {
filename[index] = '\0';
break;
}
}
strncat(filename, "\\coop.map", 255);
const char *exe_path = sys_exe_path();
if (NULL != exe_path) {
snprintf(filename, 256, "%s/%s", exe_path, "coop.map");
} else {
snprintf(filename, 256, "%s", "coop.map");
}

View file

@ -17,12 +17,9 @@ bool djui_language_init(char* lang) {
}
// construct path
char exePath[SYS_MAX_PATH] = "";
path_get_folder((char*)path_to_executable(), exePath);
char path[SYS_MAX_PATH] = "";
if (!lang || lang[0] == '\0') { lang = "English"; }
snprintf(path, SYS_MAX_PATH, "%s/lang/%s.ini", exePath, lang);
snprintf(path, SYS_MAX_PATH, "%s/lang/%s.ini", sys_exe_path(), lang);
// load
sLang = ini_load(path);

View file

@ -8,7 +8,6 @@
#include "pc/debuglog.h"
#include "pc/utils/misc.h"
#include "pc/configfile.h"
#include "pc/os/os.h"
#include "pc/lua/smlua_hooks.h"
#include "game/bettercamera.h"
@ -86,9 +85,9 @@ void djui_panel_language_create(struct DjuiBase* caller) {
snprintf(lpath, SYS_MAX_PATH, "%s/lang", sys_exe_path());
// open directory
os_dirent* dir = NULL;
struct dirent* dir = NULL;
OS_DIR* d = os_opendir(lpath);
DIR* d = opendir(lpath);
if (!d) {
LOG_ERROR("Could not open directory '%s'", lpath);
@ -111,10 +110,10 @@ void djui_panel_language_create(struct DjuiBase* caller) {
// iterate
char path[SYS_MAX_PATH] = { 0 };
while ((dir = os_readdir(d)) != NULL) {
while ((dir = readdir(d)) != NULL) {
// sanity check / fill path[]
//if (!directory_sanity_check(dir, lpath, path)) { continue; }
snprintf(path, SYS_MAX_PATH, "%s", os_get_dir_name(dir));
snprintf(path, SYS_MAX_PATH, "%s", dir->d_name);
// strip the name before the .
char* c = path;
@ -130,7 +129,7 @@ void djui_panel_language_create(struct DjuiBase* caller) {
if (!strcmp(path, "English")) { chkEnglish = checkbox; }
}
os_closedir(d);
closedir(d);
if (!foundMatch && chkEnglish) {
chkEnglish->value = &sTrue;

View file

@ -51,6 +51,11 @@ bool fs_init(const char *writepath) {
printf("FS: writepath set to `%s`\n", fs_writepath);
#endif
// we shall not progress any further if the path is inaccessible
if ('\0' == fs_writepath[0]) {
sys_fatal("Could not access the User Preferences directory.");
}
fs_mount(fs_writepath);
return true;
@ -244,11 +249,13 @@ const char *fs_get_write_path(const char *vpath) {
return path;
}
const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) {
const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) {
if (NULL == path) { return ""; }
// ! means "executable directory"
if (path[0] == '!') {
if (snprintf(buf, bufsiz, "%s%s", sys_exe_path(), path + 1) < 0) {
return NULL;
if (snprintf(buf, bufsiz, "%s%s", sys_exe_path(), path + 1) < 0) {
return "";
}
} else {
strncpy(buf, path, bufsiz);
@ -262,8 +269,33 @@ const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) {
return buf;
}
bool fs_sys_filename_is_portable(char const *filename) {
char c;
while (0 != (c = *(filename++))) {
if (c < ' ' || c > '~') {
// character outside of printable range
return false;
}
switch (c) {
// characters unallowed in filenames
case '/': case '\\': case '<': case '>':
case ':': case '"': case '|': case '?': case '*':
return false;
}
}
return true;
}
/* these operate on the real file system */
bool fs_sys_path_exists(const char *name) {
struct stat st;
return (stat(name, &st) == 0);
}
bool fs_sys_file_exists(const char *name) {
struct stat st;
return (stat(name, &st) == 0 && S_ISREG(st.st_mode));
@ -286,7 +318,7 @@ bool fs_sys_dir_is_empty(const char *name) {
bool ret = true;
while ((ent = readdir(dir)) != NULL) {
// skip "." and ".."
if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' ||
if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' ||
(ent->d_name[1] == '.' && ent->d_name[2] == '\0'))) {
continue;
}
@ -340,36 +372,17 @@ bool fs_sys_walk(const char *base, walk_fn_t walk, void *user, const bool recur)
}
bool fs_sys_mkdir(const char *name) {
#ifdef _WIN32
#ifdef _WIN32
return _mkdir(name) == 0;
#else
#else
return mkdir(name, 0777) == 0;
#endif
#endif
}
bool fs_sys_copy_file(const char *oldname, const char *newname) {
uint8_t buf[2048];
FILE *fin = fopen(oldname, "rb");
if (!fin) return false;
FILE *fout = fopen(newname, "wb");
if (!fout) {
fclose(fin);
return false;
}
bool ret = true;
size_t rx;
while ((rx = fread(buf, 1, sizeof(buf), fin)) > 0) {
if (!fwrite(buf, rx, 1, fout)) {
ret = false;
break;
}
}
fclose(fout);
fclose(fin);
return ret;
bool fs_sys_rmdir(const char *name) {
#ifdef _WIN32
return _rmdir(name) == 0;
#else
return rmdir(name) == 0;
#endif
}

View file

@ -97,13 +97,16 @@ const char *fs_get_write_path(const char *vpath);
// expands special chars in paths and changes backslashes to forward slashes
const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path);
bool fs_sys_filename_is_portable(char const *filename);
/* these operate on the real filesystem and are used by fs_packtype_dir */
bool fs_sys_walk(const char *base, walk_fn_t walk, void *user, const bool recur);
bool fs_sys_path_exists(const char *name);
bool fs_sys_file_exists(const char *name);
bool fs_sys_dir_exists(const char *name);
bool fs_sys_dir_is_empty(const char *name);
bool fs_sys_mkdir(const char *name); // creates with 0777 by default
bool fs_sys_copy_file(const char *oldname, const char *newname);
bool fs_sys_rmdir(const char *name); // removes an empty directory
#endif // _SM64_FS_H_

View file

@ -454,18 +454,18 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) {
return true;
}
bool isDirectory = is_directory(fullPath);
bool isDirectory = fs_sys_dir_exists(fullPath);
// make sure mod is valid
if (str_ends_with(modName, ".lua")) {
valid = true;
} else if (is_directory(fullPath)) {
} else if (fs_sys_dir_exists(fullPath)) {
char tmpPath[SYS_MAX_PATH] = { 0 };
if (!concat_path(tmpPath, fullPath, "main.lua")) {
LOG_ERROR("Failed to concat path '%s' + '%s'", fullPath, "main.lua");
return true;
}
valid = path_exists(tmpPath);
valid = fs_sys_path_exists(tmpPath);
}
if (!valid) {

View file

@ -115,7 +115,7 @@ C_FIELD const char* mod_storage_load(const char* key) {
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!path_exists(filename)) { return NULL; }
if (!fs_sys_path_exists(filename)) { return NULL; }
mINI::INIFile file(filename);
mINI::INIStructure ini;
@ -152,7 +152,7 @@ C_FIELD bool mod_storage_remove(const char* key) {
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!path_exists(filename)) { return false; }
if (!fs_sys_path_exists(filename)) { return false; }
mINI::INIFile file(filename);
mINI::INIStructure ini;
@ -172,7 +172,7 @@ C_FIELD bool mod_storage_clear(void) {
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!path_exists(filename)) { return false; }
if (!fs_sys_path_exists(filename)) { return false; }
mINI::INIFile file(filename);
mINI::INIStructure ini;

View file

@ -218,7 +218,7 @@ static void mods_load(struct Mods* mods, char* modsBasePath, UNUSED bool isUserM
normalize_path(modsBasePath);
// check for existence
if (!is_directory(modsBasePath)) {
if (!fs_sys_dir_exists(modsBasePath)) {
LOG_ERROR("Could not find directory '%s'", modsBasePath);
}
@ -275,10 +275,8 @@ void mods_refresh_local(void) {
// load mods
if (hasUserPath) { mods_load(&gLocalMods, userModPath, true); }
const char* exePath = path_to_executable();
char defaultModsPath[SYS_MAX_PATH] = { 0 };
path_get_folder((char*)exePath, defaultModsPath);
strncat(defaultModsPath, MOD_DIRECTORY, SYS_MAX_PATH-1);
snprintf(defaultModsPath, SYS_MAX_PATH, "%s/%s", sys_exe_path(), MOD_DIRECTORY);
mods_load(&gLocalMods, defaultModsPath, false);
// sort

View file

@ -93,7 +93,7 @@ void mods_delete_folder(char* path) {
if (!strcmp(dir->d_name, "..")) { continue; }
if (!concat_path(fullPath, path, dir->d_name)) { continue; }
if (is_directory(fullPath)) {
if (fs_sys_dir_exists(fullPath)) {
mods_delete_folder(fullPath);
} else if (fs_sys_file_exists(fullPath)) {
if (unlink(fullPath) == -1) {
@ -185,75 +185,6 @@ char* extract_lua_field(char* fieldName, char* buffer) {
//////////////////////////////////////////////////////////////////////////////////////////
const char* path_to_executable(void) {
static char exePath[SYS_MAX_PATH] = { 0 };
if (exePath[0] != '\0') { return exePath; }
#if defined(_WIN32) || defined(_WIN64)
HMODULE hModule = GetModuleHandle(NULL);
if (hModule == NULL) {
LOG_ERROR("unable to retrieve absolute windows path!");
return NULL;
}
GetModuleFileName(hModule, exePath, SYS_MAX_PATH-1);
#elif defined(OSX_BUILD)
u32 bufsize = SYS_MAX_PATH-1;
if (_NSGetExecutablePath(exePath, &bufsize) != 0) {
LOG_ERROR("unable to retrieve absolute mac path!");
return NULL;
}
#else
char procPath[SYS_MAX_PATH] = { 0 };
snprintf(procPath, SYS_MAX_PATH-1, "/proc/%d/exe", getpid());
s32 rc = readlink(procPath, exePath, SYS_MAX_PATH-1);
if (rc <= 0) {
LOG_ERROR("unable to retrieve absolute linux path!");
return NULL;
}
#endif
return exePath;
}
bool path_is_portable_filename(char* string) {
char* s = string;
while (*s != '\0') {
char c = *s;
if (c < ' ' || c > '~') {
// outside of printable range
return false;
}
switch (c) {
// unallowed in filenames
case '/':
case '\\':
case '<':
case '>':
case ':':
case '"':
case '|':
case '?':
case '*':
return false;
}
s++;
}
return true;
}
bool path_exists(char* path) {
struct stat sb = { 0 };
return (stat(path, &sb) == 0);
}
bool is_directory(char* path) {
struct stat sb = { 0 };
return (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode));
}
void normalize_path(char* path) {
// replace slashes
char* p = path;
@ -298,7 +229,7 @@ void path_get_folder(char* path, char* outpath) {
bool directory_sanity_check(struct dirent* dir, char* dirPath, char* outPath) {
// skip non-portable filenames
if (!path_is_portable_filename(dir->d_name)) { return false; }
if (!fs_sys_filename_is_portable(dir->d_name)) { return false; }
// skip anything that contains \ or /
if (strchr(dir->d_name, '/') != NULL) { return false; }
@ -315,7 +246,7 @@ bool directory_sanity_check(struct dirent* dir, char* dirPath, char* outPath) {
normalize_path(outPath);
// sanity check
if (!path_exists(outPath)) {
if (!fs_sys_path_exists(outPath)) {
LOG_ERROR("Path doesn't exist: '%s'", outPath);
return false;
}

View file

@ -16,10 +16,6 @@ bool str_ends_with(const char* string, const char* suffix);
char* extract_lua_field(char* fieldName, char* buffer);
const char* path_to_executable(void);
bool path_is_portable_filename(char* string);
bool path_exists(char* path);
bool is_directory(char* path);
void normalize_path(char* path);
bool concat_path(char* destination, char* path, char* fname);
char* path_basename(char* path);

View file

@ -1,6 +0,0 @@
#pragma once
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64) || defined(WIN64)
#include "os_win.h"
#else
#include "os_other.h"
#endif

View file

@ -1,10 +0,0 @@
#pragma once
#include <dirent.h>
#define os_dirent struct dirent
#define OS_DIR DIR
#define os_opendir(_x) opendir(_x)
#define os_readdir(_x) readdir(_x)
#define os_closedir(_x) closedir(_x)
#define os_get_dir_name(_x) _x->d_name

View file

@ -1,20 +0,0 @@
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64) || defined(WIN64)
#include <stdio.h>
#include <stringapiset.h>
#include "os_win.h"
#include "pc/platform.h"
OS_DIR* os_opendir(const char* path) {
wchar_t wpath[SYS_MAX_PATH] = { 0 };
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, SYS_MAX_PATH);
return _wopendir(wpath);
}
const char* os_get_dir_name(os_dirent* dir) {
static char path[SYS_MAX_PATH] = { 0 };
snprintf(path, SYS_MAX_PATH, "%ls", dir->d_name);
return path;
}
#endif

View file

@ -1,13 +0,0 @@
#pragma once
#include <wchar.h>
#include <dirent.h>
#define os_dirent struct _wdirent
#define OS_DIR _WDIR
#define os_closedir(_x) _wclosedir(_x)
#define os_readdir(_x) _wreaddir(_x)
OS_DIR* os_opendir(const char* path);
const char* os_get_dir_name(os_dirent* dir);

View file

@ -5,6 +5,12 @@
#include <string.h>
#include <ctype.h>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#endif
#include "cliopts.h"
#include "fs/fs.h"
#include "configfile.h"
@ -76,57 +82,133 @@ void sys_fatal(const char *fmt, ...) {
sys_fatal_impl(msg);
}
#ifdef HAVE_SDL2
#if defined(_WIN32) || defined(_WIN64)
static BOOL sys_windows_short_path(LPSTR destPath, SIZE_T destSize, LPWSTR wideLongPath)
{
WCHAR wideShortPath[SYS_MAX_PATH];
// Convert the Long Path in Wide Format to the alternate short form.
// It will still point to already existing directory.
if (0 == GetShortPathNameW(wideLongPath, wideShortPath, SYS_MAX_PATH)) { return FALSE; }
// Short Path can be safely represented by the US-ASCII Charset.
return (WideCharToMultiByte(CP_ACP, 0, wideShortPath, (-1), destPath, destSize, NULL, NULL) > 0);
}
const char *sys_user_path(void)
{
static char shortPath[SYS_MAX_PATH] = { 0 };
if ('\0' != shortPath[0]) { return shortPath; }
WCHAR widePath[SYS_MAX_PATH];
// "%USERPROFILE%\AppData\Roaming"
WCHAR *wcsAppDataPath = NULL;
HRESULT res = SHGetKnownFolderPath(
&(FOLDERID_RoamingAppData),
(KF_FLAG_CREATE | KF_FLAG_DONT_UNEXPAND),
NULL, &(wcsAppDataPath));
if (S_OK != res)
{
if (NULL != wcsAppDataPath) { CoTaskMemFree(wcsAppDataPath); }
return NULL;
}
LPCWSTR subdirs[] = { L"sm64ex-coop", L"sm64coopdx", NULL };
for (int i = 0; NULL != subdirs[i]; i++)
{
if (_snwprintf(widePath, SYS_MAX_PATH, L"%s\\%s", wcsAppDataPath, subdirs[i]) <= 0) { return NULL; }
// Directory already exists.
if (FALSE != PathIsDirectoryW(widePath))
{
// Directory is not empty, so choose this name.
if (FALSE == PathIsDirectoryEmptyW(widePath)) { break; }
}
// 'widePath' will hold the last checked subdir name.
}
// System resource can be safely released now.
if (NULL != wcsAppDataPath) { CoTaskMemFree(wcsAppDataPath); }
// Always try to create the directory pointed to by User Path,
// but ignore errors if the destination already exists.
if (FALSE == CreateDirectoryW(widePath, NULL))
{
if (ERROR_ALREADY_EXISTS != GetLastError()) { return NULL; }
}
return sys_windows_short_path(shortPath, SYS_MAX_PATH, widePath) ? shortPath : NULL;
}
const char *sys_exe_path(void)
{
static char shortPath[SYS_MAX_PATH] = { 0 };
if ('\0' != shortPath[0]) { return shortPath; }
WCHAR widePath[SYS_MAX_PATH];
if (0 == GetModuleFileNameW(NULL, widePath, SYS_MAX_PATH)) { return NULL; }
WCHAR *lastBackslash = wcsrchr(widePath, L'\\');
if (NULL != lastBackslash) { *lastBackslash = L'\0'; }
else { return NULL; }
return sys_windows_short_path(shortPath, SYS_MAX_PATH, widePath) ? shortPath : NULL;
}
static void sys_fatal_impl(const char *msg) {
MessageBoxA(NULL, msg, "Fatal error", MB_ICONERROR);
fprintf(stderr, "FATAL ERROR:\n%s\n", msg);
fflush(stderr);
exit(1);
}
#elif defined(HAVE_SDL2)
// we can just ask SDL for most of this shit if we have it
#include <SDL2/SDL.h>
static const char *sys_old_user_path(void) {
static char path[SYS_MAX_PATH] = { 0 };
// get the new pref path from SDL
char *sdlPath = SDL_GetPrefPath("", "sm64ex-coop");
if (sdlPath) {
const unsigned int len = strlen(sdlPath);
snprintf(path, sizeof(path), "%s", sdlPath);
path[sizeof(path)-1] = 0;
SDL_free(sdlPath);
// strip the trailing separator
if (path[len-1] == '/' || path[len-1] == '\\') { path[len-1] = 0; }
}
return path;
}
const char *sys_user_path(void) {
static char path[SYS_MAX_PATH] = { 0 };
if ('\0' != path[0]) { return path; }
// get the new pref path from SDL
char *sdlPath = SDL_GetPrefPath("", "sm64coopdx");
if (sdlPath) {
// redirect to the old user path if the current one is empty (likely just created from SDL_GetPrefPath)
if (fs_sys_dir_is_empty(sdlPath)) {
char const *subdirs[] = { "sm64ex-coop", "sm64coopdx", NULL };
char *sdlPath = NULL;
for (int i = 0; NULL != subdirs[i]; i++)
{
if (sdlPath) {
// Previous dir likely just created with SDL_GetPrefPath.
fs_sys_rmdir(sdlPath);
SDL_free(sdlPath);
return sys_old_user_path();
}
const unsigned int len = strlen(sdlPath);
snprintf(path, sizeof(path), "%s", sdlPath);
path[sizeof(path)-1] = 0;
sdlPath = SDL_GetPrefPath("", subdirs[i]);
SDL_free(sdlPath);
// strip the trailing separator
if (path[len-1] == '/' || path[len-1] == '\\') { path[len-1] = 0; }
// Choose this directory if it already exists and is not empty.
if (sdlPath && !fs_sys_dir_is_empty(sdlPath)) { break; }
}
if (NULL == sdlPath) { return NULL; }
strncpy(path, sdlPath, SYS_MAX_PATH - 1);
SDL_free(sdlPath);
// strip the trailing separator
const unsigned int len = strlen(path);
if (path[len-1] == '/' || path[len-1] == '\\') { path[len-1] = 0; }
return path;
}
const char *sys_exe_path(void) {
static char path[SYS_MAX_PATH] = { 0 };
if ('\0' != path[0]) { return path; }
char *sdlPath = SDL_GetBasePath();
if (sdlPath && sdlPath[0]) {
// use the SDL path if it exists