Preprocessor purge round 18: difficult parts of WIN, LIN, MACOSX, AND

This commit is contained in:
Tamás Bálint Misius 2023-01-19 18:31:47 +01:00
parent 49102e395c
commit 36d6f1d67e
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
13 changed files with 838 additions and 647 deletions

View File

@ -37,6 +37,9 @@ host_arch = host_machine.cpu_family()
host_platform = host_machine.system()
# educated guesses follow, PRs welcome
if c_compiler.get_id() in [ 'msvc' ]
if host_platform != 'windows'
error('this seems fishy')
endif
host_libc = 'msvc'
elif c_compiler.get_id() in [ 'gcc' ] and host_platform == 'windows'
host_libc = 'mingw'
@ -49,6 +52,11 @@ elif host_platform in [ 'android' ]
host_platform = 'android'
host_libc = 'bionic'
else
if host_platform != 'linux'
# TODO: maybe use 'default' in place of 'linux', or use something other than host_platform where details such as desktop integration are concerned
warning('host platform is not linux but we will pretend that it is')
host_platform = 'linux'
endif
host_libc = 'gnu'
endif
@ -326,10 +334,7 @@ is_beta = get_option('beta')
is_mod = mod_id > 0
update_server = get_option('update_server')
conf_data.set('LIN', host_platform == 'linux')
conf_data.set('AND', host_platform == 'android')
conf_data.set('WIN', host_platform == 'windows')
conf_data.set('MACOSX', host_platform == 'darwin')
conf_data.set('SET_WINDOW_ICON', host_platform == 'linux' ? 'true' : 'false')
conf_data.set('X86', is_x86 ? 'true' : 'false')
conf_data.set('BETA', is_beta ? 'true' : 'false')
conf_data.set('INSTALL_CHECK', install_check ? 'true' : 'false')

View File

@ -1,12 +1,7 @@
#pragma once
#include <cstdint>
// Boolean macros (defined / not defined), would be great to get rid of them all.
#mesondefine LIN
#mesondefine AND
#mesondefine WIN
#mesondefine MACOSX
constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@;
constexpr bool DEBUG = @DEBUG@;
constexpr bool X86 = @X86@;
constexpr bool BETA = @BETA@;

View File

@ -117,9 +117,10 @@ int SDLOpen()
}
}
#ifdef LIN
WindowIcon(sdl_window);
#endif
if constexpr (SET_WINDOW_ICON)
{
WindowIcon(sdl_window);
}
return 0;
}

View File

@ -177,9 +177,10 @@ void SDLOpen()
}
}
#ifdef LIN
WindowIcon(sdl_window);
#endif
if constexpr (SET_WINDOW_ICON)
{
WindowIcon(sdl_window);
}
}
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_)

View File

@ -6,34 +6,6 @@
#include <fstream>
#include <iostream>
#include <sys/stat.h>
#ifdef WIN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <direct.h>
# include <io.h>
# include <shlobj.h>
# include <shlwapi.h>
# include <shellapi.h>
# include <windows.h>
# include <crtdbg.h>
#else
# include <unistd.h>
# include <ctime>
# include <sys/time.h>
# include <dirent.h>
#endif
#ifdef MACOSX
# include <cstdint>
# include <mach-o/dyld.h>
# include <ApplicationServices/ApplicationServices.h>
#endif
#ifdef LIN
# include "icon_cps.png.h"
# include "icon_exe.png.h"
# include "save.xml.h"
# include "powder.desktop.h"
#endif
namespace Platform
{
@ -41,194 +13,6 @@ namespace Platform
std::string originalCwd;
std::string sharedCwd;
ByteString GetCwd()
{
ByteString cwd;
#if defined(WIN)
wchar_t *cwdPtr = _wgetcwd(NULL, 0);
if (cwdPtr)
{
cwd = WinNarrow(cwdPtr);
}
free(cwdPtr);
#else
char *cwdPtr = getcwd(NULL, 0);
if (cwdPtr)
{
cwd = cwdPtr;
}
free(cwdPtr);
#endif
return cwd;
}
void OpenURI(ByteString uri)
{
#if defined(WIN)
if (int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(uri).c_str(), NULL, NULL, SW_SHOWNORMAL))) <= 32)
{
fprintf(stderr, "cannot open URI: ShellExecute(...) failed\n");
}
#elif defined(MACOSX)
if (system(("open \"" + uri + "\"").c_str()))
{
fprintf(stderr, "cannot open URI: system(...) failed\n");
}
#elif defined(LIN)
if (system(("xdg-open \"" + uri + "\"").c_str()))
{
fprintf(stderr, "cannot open URI: system(...) failed\n");
}
#else
fprintf(stderr, "cannot open URI: not implemented\n");
#endif
}
void Millisleep(long int t)
{
#ifdef WIN
Sleep(t);
#else
struct timespec s;
s.tv_sec = t / 1000;
s.tv_nsec = (t % 1000) * 10000000;
nanosleep(&s, NULL);
#endif
}
long unsigned int GetTime()
{
#ifdef WIN
return GetTickCount();
#elif defined(MACOSX)
struct timeval s;
gettimeofday(&s, NULL);
return (unsigned int)(s.tv_sec * 1000 + s.tv_usec / 1000);
#else
struct timespec s;
clock_gettime(CLOCK_MONOTONIC, &s);
return s.tv_sec * 1000 + s.tv_nsec / 1000000;
#endif
}
bool Stat(ByteString filename)
{
#ifdef WIN
struct _stat s;
if (_stat(filename.c_str(), &s) == 0)
#else
struct stat s;
if (stat(filename.c_str(), &s) == 0)
#endif
{
return true; // Something exists, be it a file, directory, link, etc.
}
else
{
return false; // Doesn't exist
}
}
bool FileExists(ByteString filename)
{
#ifdef WIN
struct _stat s;
if (_stat(filename.c_str(), &s) == 0)
#else
struct stat s;
if (stat(filename.c_str(), &s) == 0)
#endif
{
if(s.st_mode & S_IFREG)
{
return true; // Is file
}
else
{
return false; // Is directory or something else
}
}
else
{
return false; // Doesn't exist
}
}
bool DirectoryExists(ByteString directory)
{
#ifdef WIN
struct _stat s;
if (_stat(directory.c_str(), &s) == 0)
#else
struct stat s;
if (stat(directory.c_str(), &s) == 0)
#endif
{
if(s.st_mode & S_IFDIR)
{
return true; // Is directory
}
else
{
return false; // Is file or something else
}
}
else
{
return false; // Doesn't exist
}
}
bool RemoveFile(ByteString filename)
{
#ifdef WIN
return _wremove(WinWiden(filename).c_str()) == 0;
#else
return remove(filename.c_str()) == 0;
#endif
}
bool RenameFile(ByteString filename, ByteString newFilename, bool replace)
{
#ifdef WIN
if (replace)
{
// TODO: we rely on errno but errors from this are available through GetLastError(); fix
return MoveFileExW(WinWiden(filename).c_str(), WinWiden(newFilename).c_str(), MOVEFILE_REPLACE_EXISTING);
}
return _wrename(WinWiden(filename).c_str(), WinWiden(newFilename).c_str()) == 0;
#else
return rename(filename.c_str(), newFilename.c_str()) == 0;
#endif
}
bool DeleteDirectory(ByteString folder)
{
#ifdef WIN
return _wrmdir(WinWiden(folder).c_str()) == 0;
#else
return rmdir(folder.c_str()) == 0;
#endif
}
bool MakeDirectory(ByteString dir)
{
#ifdef WIN
return _wmkdir(WinWiden(dir).c_str()) == 0;
#else
return mkdir(dir.c_str(), 0755) == 0;
#endif
}
bool ChangeDir(ByteString toDir)
{
#ifdef WIN
return _wchdir(WinWiden(toDir).c_str()) == 0;
#else
return chdir(toDir.c_str()) == 0;
#endif
}
// Returns a list of all files in a directory matching a search
// search - list of search terms. extensions - list of extensions to also match
std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search, std::vector<ByteString> extensions)
@ -237,35 +21,7 @@ std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search,
//Normalise directory string, ensure / or \ is present
if (!directory.size() || (directory.back() != '/' && directory.back() != '\\'))
directory += PATH_SEP;
std::vector<ByteString> directoryList;
#ifdef WIN
struct _wfinddata_t currentFile;
intptr_t findFileHandle;
ByteString fileMatch = directory + "*.*";
findFileHandle = _wfindfirst(Platform::WinWiden(fileMatch).c_str(), &currentFile);
if (findFileHandle == -1L)
{
return std::vector<ByteString>();
}
do
{
directoryList.push_back(Platform::WinNarrow(currentFile.name));
}
while (_wfindnext(findFileHandle, &currentFile) == 0);
_findclose(findFileHandle);
#else
struct dirent * directoryEntry;
DIR *directoryHandle = opendir(directory.c_str());
if (!directoryHandle)
{
return std::vector<ByteString>();
}
while ((directoryEntry = readdir(directoryHandle)))
{
directoryList.push_back(ByteString(directoryEntry->d_name));
}
closedir(directoryHandle);
#endif
auto directoryList = DirectoryList(directory);
search = search.ToLower();
@ -295,38 +51,6 @@ std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search,
return searchResults;
}
#ifdef WIN
ByteString WinNarrow(const std::wstring &source)
{
int buffer_size = WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0, NULL, NULL);
if (!buffer_size)
{
return "";
}
std::string output(buffer_size, 0);
if (!WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size, NULL, NULL))
{
return "";
}
return output;
}
std::wstring WinWiden(const ByteString &source)
{
int buffer_size = MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0);
if (!buffer_size)
{
return L"";
}
std::wstring output(buffer_size, 0);
if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size))
{
return L"";
}
return output;
}
#endif
bool ReadFile(std::vector<char> &fileData, ByteString filename)
{
std::ifstream f(filename, std::ios::binary);
@ -382,362 +106,8 @@ bool WriteFile(const std::vector<char> &fileData, ByteString filename)
return true;
}
ByteString ExecutableName()
{
#ifdef WIN
std::wstring buf(L"?");
while (true)
{
SetLastError(ERROR_SUCCESS);
if (!GetModuleFileNameW(NULL, &buf[0], DWORD(buf.size())))
{
std::cerr << "GetModuleFileNameW: " << GetLastError() << std::endl;
return "";
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
break;
}
buf.resize(buf.size() * 2);
}
return WinNarrow(&buf[0]); // Pass pointer to copy only up to the zero terminator.
#else
# ifdef MACOSX
ByteString firstApproximation("?");
{
auto bufSize = uint32_t(firstApproximation.size());
auto ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize);
if (ret == -1)
{
// Buffer not large enough; likely to happen since it's initially a single byte.
firstApproximation.resize(bufSize);
ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize);
}
if (ret != 0)
{
// Can't even get a first approximation.
std::cerr << "_NSGetExecutablePath: " << ret << std::endl;
return "";
}
}
# else
ByteString firstApproximation("/proc/self/exe");
# endif
auto rp = std::unique_ptr<char, decltype(std::free) *>(realpath(&firstApproximation[0], NULL), std::free);
if (!rp)
{
std::cerr << "realpath: " << errno << std::endl;
return "";
}
return rp.get();
#endif
}
void DoRestart()
{
ByteString exename = ExecutableName();
if (exename.length())
{
#ifdef WIN
int ret = int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(exename).c_str(), NULL, NULL, SW_SHOWNORMAL)));
if (ret <= 32)
{
fprintf(stderr, "cannot restart: ShellExecute(...) failed: code %i\n", ret);
}
else
{
exit(0);
}
#elif defined(LIN) || defined(MACOSX)
execl(exename.c_str(), exename.c_str(), NULL);
int ret = errno;
fprintf(stderr, "cannot restart: execl(...) failed: code %i\n", ret);
#endif
}
else
{
fprintf(stderr, "cannot restart: no executable name???\n");
}
exit(-1);
}
bool CanUpdate()
{
#ifdef MACOSX
return false;
#else
return true;
#endif
}
bool CanInstall()
{
return INSTALL_CHECK;
}
bool Install()
{
bool ok = true;
#if defined(WIN)
auto deleteKey = [](ByteString path) {
RegDeleteKeyW(HKEY_CURRENT_USER, Platform::WinWiden(path).c_str());
};
auto createKey = [](ByteString path, ByteString value, ByteString extraKey = {}, ByteString extraValue = {}) {
auto ok = true;
auto wPath = Platform::WinWiden(path);
auto wValue = Platform::WinWiden(value);
auto wExtraKey = Platform::WinWiden(extraKey);
auto wExtraValue = Platform::WinWiden(extraValue);
HKEY k;
ok = ok && RegCreateKeyExW(HKEY_CURRENT_USER, wPath.c_str(), 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &k, NULL) == ERROR_SUCCESS;
ok = ok && RegSetValueExW(k, NULL, 0, REG_SZ, reinterpret_cast<const BYTE *>(wValue.c_str()), (wValue.size() + 1) * 2) == ERROR_SUCCESS;
if (wExtraKey.size())
{
ok = ok && RegSetValueExW(k, wExtraKey.c_str(), 0, REG_SZ, reinterpret_cast<const BYTE *>(wExtraValue.c_str()), (wExtraValue.size() + 1) * 2) == ERROR_SUCCESS;
}
RegCloseKey(k);
return ok;
};
CoInitializeEx(NULL, COINIT_MULTITHREADED);
auto exe = Platform::ExecutableName();
#ifndef IDI_DOC_ICON
// make this fail so I don't remove #include "resource.h" again and get away with it
# error where muh IDI_DOC_ICON D:
#endif
auto icon = ByteString::Build(exe, ",-", IDI_DOC_ICON);
auto path = Platform::GetCwd();
auto open = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"file://%1\"");
auto ptsave = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"%1\"");
deleteKey("Software\\Classes\\ptsave");
deleteKey("Software\\Classes\\.cps");
deleteKey("Software\\Classes\\.stm");
deleteKey("Software\\Classes\\PowderToySave");
ok = ok && createKey("Software\\Classes\\ptsave", "Powder Toy Save", "URL Protocol", "");
ok = ok && createKey("Software\\Classes\\ptsave\\DefaultIcon", icon);
ok = ok && createKey("Software\\Classes\\ptsave\\shell\\open\\command", ptsave);
ok = ok && createKey("Software\\Classes\\.cps", "PowderToySave");
ok = ok && createKey("Software\\Classes\\.stm", "PowderToySave");
ok = ok && createKey("Software\\Classes\\PowderToySave", "Powder Toy Save");
ok = ok && createKey("Software\\Classes\\PowderToySave\\DefaultIcon", icon);
ok = ok && createKey("Software\\Classes\\PowderToySave\\shell\\open\\command", open);
IShellLinkW *shellLink = NULL;
IPersistFile *shellLinkPersist = NULL;
wchar_t programsPath[MAX_PATH];
ok = ok && SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, SHGFP_TYPE_CURRENT, programsPath) == S_OK;
ok = ok && CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID *)&shellLink) == S_OK;
ok = ok && shellLink->SetPath(Platform::WinWiden(exe).c_str()) == S_OK;
ok = ok && shellLink->SetWorkingDirectory(Platform::WinWiden(path).c_str()) == S_OK;
ok = ok && shellLink->SetDescription(Platform::WinWiden(APPNAME).c_str()) == S_OK;
ok = ok && shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&shellLinkPersist) == S_OK;
ok = ok && shellLinkPersist->Save(Platform::WinWiden(ByteString::Build(Platform::WinNarrow(programsPath), "\\", APPNAME, ".lnk")).c_str(), TRUE) == S_OK;
if (shellLinkPersist)
{
shellLinkPersist->Release();
}
if (shellLink)
{
shellLink->Release();
}
CoUninitialize();
#elif defined(LIN)
auto desktopEscapeString = [](ByteString str) {
ByteString escaped;
for (auto ch : str)
{
auto from = " " "\n" "\t" "\r" "\\";
auto to = "s" "n" "t" "r" "\\";
if (auto off = strchr(from, ch))
{
escaped.append(1, '\\');
escaped.append(1, to[off - from]);
}
else
{
escaped.append(1, ch);
}
}
return escaped;
};
auto desktopEscapeExec = [](ByteString str) {
ByteString escaped;
for (auto ch : str)
{
if (strchr(" \t\n\"\'\\><~|&;$*?#()`", ch))
{
escaped.append(1, '\\');
}
escaped.append(1, ch);
}
return escaped;
};
if (ok)
{
ByteString desktopData(powder_desktop, powder_desktop + powder_desktop_size);
auto exe = Platform::ExecutableName();
auto path = exe.SplitFromEndBy('/').Before();
desktopData = desktopData.Substitute("Exec=" + ByteString(APPEXE), "Exec=" + desktopEscapeString(desktopEscapeExec(exe)));
desktopData += ByteString::Build("Path=", desktopEscapeString(path), "\n");
ByteString file = ByteString::Build(APPVENDOR, "-", APPID, ".desktop");
ok = ok && Platform::WriteFile(std::vector<char>(desktopData.begin(), desktopData.end()), file);
ok = ok && !system(ByteString::Build("xdg-desktop-menu install ", file).c_str());
ok = ok && !system(ByteString::Build("xdg-mime default ", file, " application/vnd.powdertoy.save").c_str());
ok = ok && !system(ByteString::Build("xdg-mime default ", file, " x-scheme-handler/ptsave").c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ByteString file = ByteString(APPVENDOR) + "-save.xml";
ok = ok && Platform::WriteFile(std::vector<char>(save_xml, save_xml + save_xml_size), file);
ok = ok && !system(ByteString::Build("xdg-mime install ", file).c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ByteString file = ByteString(APPVENDOR) + "-cps.png";
ok = ok && Platform::WriteFile(std::vector<char>(icon_cps_png, icon_cps_png + icon_cps_png_size), file);
ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --context mimetypes --size 64 ", file, " application-vnd.powdertoy.save").c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ByteString file = ByteString(APPVENDOR) + "-exe.png";
ok = ok && Platform::WriteFile(std::vector<char>(icon_exe_png, icon_exe_png + icon_exe_png_size), file);
ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --size 64 ", file, " ", APPVENDOR, "-", APPEXE).c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ok = ok && !system("xdg-icon-resource forceupdate");
}
#else
ok = false;
#endif
return ok;
}
bool UpdateStart(const std::vector<char> &data)
{
ByteString exeName = Platform::ExecutableName(), updName;
if (!exeName.length())
return false;
#ifdef WIN
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_upd.exe";
if (!MoveFile(Platform::WinWiden(exeName).c_str(), Platform::WinWiden(updName).c_str()))
return false;
if (!WriteFile(data, exeName))
{
Platform::RemoveFile(exeName);
return false;
}
if ((uintptr_t)ShellExecute(NULL, L"open", Platform::WinWiden(exeName).c_str(), NULL, NULL, SW_SHOWNORMAL) <= 32)
{
Platform::RemoveFile(exeName);
return false;
}
return true;
#else
updName = exeName + "-update";
if (!WriteFile(data, updName))
{
RemoveFile(updName);
return false;
}
if (chmod(updName.c_str(), 0755))
{
RemoveFile(updName);
return false;
}
if (!RenameFile(updName, exeName))
{
RemoveFile(updName);
return false;
}
execl(exeName.c_str(), "powder-update", NULL);
return false; // execl returned, we failed
#endif
}
bool UpdateFinish()
{
#ifdef WIN
ByteString exeName = Platform::ExecutableName(), updName;
int timeout = 5, err;
if constexpr (DEBUG)
{
printf("Update: Current EXE name: %s\n", exeName.c_str());
}
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_upd.exe";
if constexpr (DEBUG)
{
printf("Update: Temp EXE name: %s\n", updName.c_str());
}
while (!Platform::RemoveFile(updName))
{
err = GetLastError();
if (err == ERROR_FILE_NOT_FOUND)
{
if constexpr (DEBUG)
{
printf("Update: Temp file not deleted\n");
}
// Old versions of powder toy name their update files with _update.exe, delete that upgrade file here
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_update.exe";
Platform::RemoveFile(updName);
return true;
}
Sleep(500);
timeout--;
if (timeout <= 0)
{
if constexpr (DEBUG)
{
printf("Update: Delete timeout\n");
}
return false;
}
}
#endif
return true;
}
void UpdateCleanup()
{
#ifdef WIN
UpdateFinish();
#endif
}
void SetupCrt()
{
#ifdef WIN
if constexpr (DEBUG)
{
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
}
#endif
}
}

View File

@ -6,6 +6,7 @@
namespace Platform
{
ByteString GetCwd();
ByteString ExecutableNameFirstApprox();
ByteString ExecutableName();
void DoRestart();
@ -32,6 +33,7 @@ namespace Platform
* @return true on success
*/
bool MakeDirectory(ByteString dir);
std::vector<ByteString> DirectoryList(ByteString directory);
std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search, std::vector<ByteString> extensions);
bool ReadFile(std::vector<char> &fileData, ByteString filename);

View File

@ -0,0 +1,32 @@
#include "Platform.h"
#include <ctime>
namespace Platform
{
void OpenURI(ByteString uri)
{
fprintf(stderr, "cannot open URI: not implemented\n");
}
long unsigned int GetTime()
{
struct timespec s;
clock_gettime(CLOCK_MONOTONIC, &s);
return s.tv_sec * 1000 + s.tv_nsec / 1000000;
}
ByteString ExecutableNameFirstApprox()
{
return "/proc/self/exe";
}
bool CanUpdate()
{
return false;
}
bool Install()
{
return false;
}
}

View File

@ -0,0 +1,56 @@
#include "Platform.h"
#include <iostream>
#include <sys/time.h>
#include <cstdint>
#include <mach-o/dyld.h>
namespace Platform
{
void OpenURI(ByteString uri)
{
if (system(("open \"" + uri + "\"").c_str()))
{
fprintf(stderr, "cannot open URI: system(...) failed\n");
}
}
long unsigned int GetTime()
{
struct timeval s;
gettimeofday(&s, NULL);
return (unsigned int)(s.tv_sec * 1000 + s.tv_usec / 1000);
}
ByteString ExecutableNameFirstApprox()
{
ByteString firstApproximation("?");
{
auto bufSize = uint32_t(firstApproximation.size());
auto ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize);
if (ret == -1)
{
// Buffer not large enough; likely to happen since it's initially a single byte.
firstApproximation.resize(bufSize);
ret = _NSGetExecutablePath(&firstApproximation[0], &bufSize);
}
if (ret != 0)
{
// Can't even get a first approximation.
std::cerr << "_NSGetExecutablePath: " << ret << std::endl;
return "";
}
}
return firstApproximation;
}
bool CanUpdate()
{
return false;
}
bool Install()
{
return false;
}
}

View File

@ -0,0 +1,111 @@
#include "Platform.h"
#include <cstring>
#include <ctime>
#include "icon_cps.png.h"
#include "icon_exe.png.h"
#include "save.xml.h"
#include "powder.desktop.h"
namespace Platform
{
void OpenURI(ByteString uri)
{
if (system(("xdg-open \"" + uri + "\"").c_str()))
{
fprintf(stderr, "cannot open URI: system(...) failed\n");
}
}
long unsigned int GetTime()
{
struct timespec s;
clock_gettime(CLOCK_MONOTONIC, &s);
return s.tv_sec * 1000 + s.tv_nsec / 1000000;
}
ByteString ExecutableNameFirstApprox()
{
return "/proc/self/exe";
}
bool CanUpdate()
{
return true;
}
bool Install()
{
bool ok = true;
auto desktopEscapeString = [](ByteString str) {
ByteString escaped;
for (auto ch : str)
{
auto from = " " "\n" "\t" "\r" "\\";
auto to = "s" "n" "t" "r" "\\";
if (auto off = strchr(from, ch))
{
escaped.append(1, '\\');
escaped.append(1, to[off - from]);
}
else
{
escaped.append(1, ch);
}
}
return escaped;
};
auto desktopEscapeExec = [](ByteString str) {
ByteString escaped;
for (auto ch : str)
{
if (strchr(" \t\n\"\'\\><~|&;$*?#()`", ch))
{
escaped.append(1, '\\');
}
escaped.append(1, ch);
}
return escaped;
};
if (ok)
{
ByteString desktopData(powder_desktop, powder_desktop + powder_desktop_size);
auto exe = Platform::ExecutableName();
auto path = exe.SplitFromEndBy('/').Before();
desktopData = desktopData.Substitute("Exec=" + ByteString(APPEXE), "Exec=" + desktopEscapeString(desktopEscapeExec(exe)));
desktopData += ByteString::Build("Path=", desktopEscapeString(path), "\n");
ByteString file = ByteString::Build(APPVENDOR, "-", APPID, ".desktop");
ok = ok && Platform::WriteFile(std::vector<char>(desktopData.begin(), desktopData.end()), file);
ok = ok && !system(ByteString::Build("xdg-desktop-menu install ", file).c_str());
ok = ok && !system(ByteString::Build("xdg-mime default ", file, " application/vnd.powdertoy.save").c_str());
ok = ok && !system(ByteString::Build("xdg-mime default ", file, " x-scheme-handler/ptsave").c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ByteString file = ByteString(APPVENDOR) + "-save.xml";
ok = ok && Platform::WriteFile(std::vector<char>(save_xml, save_xml + save_xml_size), file);
ok = ok && !system(ByteString::Build("xdg-mime install ", file).c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ByteString file = ByteString(APPVENDOR) + "-cps.png";
ok = ok && Platform::WriteFile(std::vector<char>(icon_cps_png, icon_cps_png + icon_cps_png_size), file);
ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --context mimetypes --size 64 ", file, " application-vnd.powdertoy.save").c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ByteString file = ByteString(APPVENDOR) + "-exe.png";
ok = ok && Platform::WriteFile(std::vector<char>(icon_exe_png, icon_exe_png + icon_exe_png_size), file);
ok = ok && !system(ByteString::Build("xdg-icon-resource install --noupdate --size 64 ", file, " ", APPVENDOR, "-", APPEXE).c_str());
Platform::RemoveFile(file);
}
if (ok)
{
ok = ok && !system("xdg-icon-resource forceupdate");
}
return ok;
}
}

View File

@ -0,0 +1,19 @@
#include "Platform.h"
namespace Platform
{
void OpenURI(ByteString uri)
{
fprintf(stderr, "cannot open URI: not implemented\n");
}
bool CanUpdate()
{
return false;
}
bool Install()
{
return false;
}
}

View File

@ -0,0 +1,198 @@
#include "Platform.h"
#include <iostream>
#include <memory>
#include <sys/stat.h>
#include <unistd.h>
#include <ctime>
#include <sys/time.h>
#include <dirent.h>
namespace Platform
{
ByteString GetCwd()
{
ByteString cwd;
char *cwdPtr = getcwd(NULL, 0);
if (cwdPtr)
{
cwd = cwdPtr;
}
free(cwdPtr);
return cwd;
}
void Millisleep(long int t)
{
struct timespec s;
s.tv_sec = t / 1000;
s.tv_nsec = (t % 1000) * 10000000;
nanosleep(&s, NULL);
}
bool Stat(ByteString filename)
{
struct stat s;
if (stat(filename.c_str(), &s) == 0)
{
return true; // Something exists, be it a file, directory, link, etc.
}
else
{
return false; // Doesn't exist
}
}
bool FileExists(ByteString filename)
{
struct stat s;
if (stat(filename.c_str(), &s) == 0)
{
if(s.st_mode & S_IFREG)
{
return true; // Is file
}
else
{
return false; // Is directory or something else
}
}
else
{
return false; // Doesn't exist
}
}
bool DirectoryExists(ByteString directory)
{
struct stat s;
if (stat(directory.c_str(), &s) == 0)
{
if(s.st_mode & S_IFDIR)
{
return true; // Is directory
}
else
{
return false; // Is file or something else
}
}
else
{
return false; // Doesn't exist
}
}
bool RemoveFile(ByteString filename)
{
return remove(filename.c_str()) == 0;
}
bool RenameFile(ByteString filename, ByteString newFilename, bool replace)
{
return rename(filename.c_str(), newFilename.c_str()) == 0;
}
bool DeleteDirectory(ByteString folder)
{
return rmdir(folder.c_str()) == 0;
}
bool MakeDirectory(ByteString dir)
{
return mkdir(dir.c_str(), 0755) == 0;
}
bool ChangeDir(ByteString toDir)
{
return chdir(toDir.c_str()) == 0;
}
std::vector<ByteString> DirectoryList(ByteString directory)
{
std::vector<ByteString> directoryList;
struct dirent * directoryEntry;
DIR *directoryHandle = opendir(directory.c_str());
if (!directoryHandle)
{
return std::vector<ByteString>();
}
while ((directoryEntry = readdir(directoryHandle)))
{
directoryList.push_back(ByteString(directoryEntry->d_name));
}
closedir(directoryHandle);
return directoryList;
}
ByteString ExecutableName()
{
auto firstApproximation = ExecutableNameFirstApprox();
auto rp = std::unique_ptr<char, decltype(std::free) *>(realpath(&firstApproximation[0], NULL), std::free);
if (!rp)
{
std::cerr << "realpath: " << errno << std::endl;
return "";
}
return rp.get();
}
void DoRestart()
{
ByteString exename = ExecutableName();
if (exename.length())
{
execl(exename.c_str(), exename.c_str(), NULL);
int ret = errno;
fprintf(stderr, "cannot restart: execl(...) failed: code %i\n", ret);
}
else
{
fprintf(stderr, "cannot restart: no executable name???\n");
}
exit(-1);
}
bool UpdateStart(const std::vector<char> &data)
{
ByteString exeName = Platform::ExecutableName(), updName;
if (!exeName.length())
return false;
updName = exeName + "-update";
if (!WriteFile(data, updName))
{
RemoveFile(updName);
return false;
}
if (chmod(updName.c_str(), 0755))
{
RemoveFile(updName);
return false;
}
if (!RenameFile(updName, exeName))
{
RemoveFile(updName);
return false;
}
execl(exeName.c_str(), "powder-update", NULL);
return false; // execl returned, we failed
}
bool UpdateFinish()
{
return true;
}
void UpdateCleanup()
{
}
void SetupCrt()
{
}
}

View File

@ -0,0 +1,385 @@
#include "Platform.h"
#include "resource.h"
#include <iostream>
#include <sys/stat.h>
#ifndef NOMINMAX
# define NOMINMAX
#endif
#include <io.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <windows.h>
#include <crtdbg.h>
namespace Platform
{
ByteString GetCwd()
{
ByteString cwd;
wchar_t *cwdPtr = _wgetcwd(NULL, 0);
if (cwdPtr)
{
cwd = WinNarrow(cwdPtr);
}
free(cwdPtr);
return cwd;
}
void OpenURI(ByteString uri)
{
if (int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(uri).c_str(), NULL, NULL, SW_SHOWNORMAL))) <= 32)
{
fprintf(stderr, "cannot open URI: ShellExecute(...) failed\n");
}
}
void Millisleep(long int t)
{
Sleep(t);
}
long unsigned int GetTime()
{
return GetTickCount();
}
bool Stat(ByteString filename)
{
struct _stat s;
if (_stat(filename.c_str(), &s) == 0)
{
return true; // Something exists, be it a file, directory, link, etc.
}
else
{
return false; // Doesn't exist
}
}
bool FileExists(ByteString filename)
{
struct _stat s;
if (_stat(filename.c_str(), &s) == 0)
{
if(s.st_mode & S_IFREG)
{
return true; // Is file
}
else
{
return false; // Is directory or something else
}
}
else
{
return false; // Doesn't exist
}
}
bool DirectoryExists(ByteString directory)
{
struct _stat s;
if (_stat(directory.c_str(), &s) == 0)
{
if(s.st_mode & S_IFDIR)
{
return true; // Is directory
}
else
{
return false; // Is file or something else
}
}
else
{
return false; // Doesn't exist
}
}
bool RemoveFile(ByteString filename)
{
return _wremove(WinWiden(filename).c_str()) == 0;
}
bool RenameFile(ByteString filename, ByteString newFilename, bool replace)
{
if (replace)
{
// TODO: we rely on errno but errors from this are available through GetLastError(); fix
return MoveFileExW(WinWiden(filename).c_str(), WinWiden(newFilename).c_str(), MOVEFILE_REPLACE_EXISTING);
}
return _wrename(WinWiden(filename).c_str(), WinWiden(newFilename).c_str()) == 0;
}
bool DeleteDirectory(ByteString folder)
{
return _wrmdir(WinWiden(folder).c_str()) == 0;
}
bool MakeDirectory(ByteString dir)
{
return _wmkdir(WinWiden(dir).c_str()) == 0;
}
bool ChangeDir(ByteString toDir)
{
return _wchdir(WinWiden(toDir).c_str()) == 0;
}
std::vector<ByteString> DirectoryList(ByteString directory)
{
std::vector<ByteString> directoryList;
struct _wfinddata_t currentFile;
intptr_t findFileHandle;
ByteString fileMatch = directory + "*.*";
findFileHandle = _wfindfirst(Platform::WinWiden(fileMatch).c_str(), &currentFile);
if (findFileHandle == -1L)
{
return std::vector<ByteString>();
}
do
{
directoryList.push_back(Platform::WinNarrow(currentFile.name));
}
while (_wfindnext(findFileHandle, &currentFile) == 0);
_findclose(findFileHandle);
return directoryList;
}
ByteString WinNarrow(const std::wstring &source)
{
int buffer_size = WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0, NULL, NULL);
if (!buffer_size)
{
return "";
}
std::string output(buffer_size, 0);
if (!WideCharToMultiByte(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size, NULL, NULL))
{
return "";
}
return output;
}
std::wstring WinWiden(const ByteString &source)
{
int buffer_size = MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), nullptr, 0);
if (!buffer_size)
{
return L"";
}
std::wstring output(buffer_size, 0);
if (!MultiByteToWideChar(CP_UTF8, 0, source.c_str(), source.size(), &output[0], buffer_size))
{
return L"";
}
return output;
}
ByteString ExecutableName()
{
std::wstring buf(L"?");
while (true)
{
SetLastError(ERROR_SUCCESS);
if (!GetModuleFileNameW(NULL, &buf[0], DWORD(buf.size())))
{
std::cerr << "GetModuleFileNameW: " << GetLastError() << std::endl;
return "";
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
break;
}
buf.resize(buf.size() * 2);
}
return WinNarrow(&buf[0]); // Pass pointer to copy only up to the zero terminator.
}
void DoRestart()
{
ByteString exename = ExecutableName();
if (exename.length())
{
int ret = int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(exename).c_str(), NULL, NULL, SW_SHOWNORMAL)));
if (ret <= 32)
{
fprintf(stderr, "cannot restart: ShellExecute(...) failed: code %i\n", ret);
}
else
{
exit(0);
}
}
else
{
fprintf(stderr, "cannot restart: no executable name???\n");
}
exit(-1);
}
bool CanUpdate()
{
return true;
}
bool Install()
{
bool ok = true;
auto deleteKey = [](ByteString path) {
RegDeleteKeyW(HKEY_CURRENT_USER, Platform::WinWiden(path).c_str());
};
auto createKey = [](ByteString path, ByteString value, ByteString extraKey = {}, ByteString extraValue = {}) {
auto ok = true;
auto wPath = Platform::WinWiden(path);
auto wValue = Platform::WinWiden(value);
auto wExtraKey = Platform::WinWiden(extraKey);
auto wExtraValue = Platform::WinWiden(extraValue);
HKEY k;
ok = ok && RegCreateKeyExW(HKEY_CURRENT_USER, wPath.c_str(), 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &k, NULL) == ERROR_SUCCESS;
ok = ok && RegSetValueExW(k, NULL, 0, REG_SZ, reinterpret_cast<const BYTE *>(wValue.c_str()), (wValue.size() + 1) * 2) == ERROR_SUCCESS;
if (wExtraKey.size())
{
ok = ok && RegSetValueExW(k, wExtraKey.c_str(), 0, REG_SZ, reinterpret_cast<const BYTE *>(wExtraValue.c_str()), (wExtraValue.size() + 1) * 2) == ERROR_SUCCESS;
}
RegCloseKey(k);
return ok;
};
CoInitializeEx(NULL, COINIT_MULTITHREADED);
auto exe = Platform::ExecutableName();
#ifndef IDI_DOC_ICON
// make this fail so I don't remove #include "resource.h" again and get away with it
# error where muh IDI_DOC_ICON D:
#endif
auto icon = ByteString::Build(exe, ",-", IDI_DOC_ICON);
auto path = Platform::GetCwd();
auto open = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"file://%1\"");
auto ptsave = ByteString::Build("\"", exe, "\" ddir \"", path, "\" \"%1\"");
deleteKey("Software\\Classes\\ptsave");
deleteKey("Software\\Classes\\.cps");
deleteKey("Software\\Classes\\.stm");
deleteKey("Software\\Classes\\PowderToySave");
ok = ok && createKey("Software\\Classes\\ptsave", "Powder Toy Save", "URL Protocol", "");
ok = ok && createKey("Software\\Classes\\ptsave\\DefaultIcon", icon);
ok = ok && createKey("Software\\Classes\\ptsave\\shell\\open\\command", ptsave);
ok = ok && createKey("Software\\Classes\\.cps", "PowderToySave");
ok = ok && createKey("Software\\Classes\\.stm", "PowderToySave");
ok = ok && createKey("Software\\Classes\\PowderToySave", "Powder Toy Save");
ok = ok && createKey("Software\\Classes\\PowderToySave\\DefaultIcon", icon);
ok = ok && createKey("Software\\Classes\\PowderToySave\\shell\\open\\command", open);
IShellLinkW *shellLink = NULL;
IPersistFile *shellLinkPersist = NULL;
wchar_t programsPath[MAX_PATH];
ok = ok && SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, SHGFP_TYPE_CURRENT, programsPath) == S_OK;
ok = ok && CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID *)&shellLink) == S_OK;
ok = ok && shellLink->SetPath(Platform::WinWiden(exe).c_str()) == S_OK;
ok = ok && shellLink->SetWorkingDirectory(Platform::WinWiden(path).c_str()) == S_OK;
ok = ok && shellLink->SetDescription(Platform::WinWiden(APPNAME).c_str()) == S_OK;
ok = ok && shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&shellLinkPersist) == S_OK;
ok = ok && shellLinkPersist->Save(Platform::WinWiden(ByteString::Build(Platform::WinNarrow(programsPath), "\\", APPNAME, ".lnk")).c_str(), TRUE) == S_OK;
if (shellLinkPersist)
{
shellLinkPersist->Release();
}
if (shellLink)
{
shellLink->Release();
}
CoUninitialize();
return ok;
}
bool UpdateStart(const std::vector<char> &data)
{
ByteString exeName = Platform::ExecutableName(), updName;
if (!exeName.length())
return false;
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_upd.exe";
if (!MoveFile(Platform::WinWiden(exeName).c_str(), Platform::WinWiden(updName).c_str()))
return false;
if (!WriteFile(data, exeName))
{
Platform::RemoveFile(exeName);
return false;
}
if ((uintptr_t)ShellExecute(NULL, L"open", Platform::WinWiden(exeName).c_str(), NULL, NULL, SW_SHOWNORMAL) <= 32)
{
Platform::RemoveFile(exeName);
return false;
}
return true;
}
bool UpdateFinish()
{
ByteString exeName = Platform::ExecutableName(), updName;
int timeout = 5, err;
if constexpr (DEBUG)
{
printf("Update: Current EXE name: %s\n", exeName.c_str());
}
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_upd.exe";
if constexpr (DEBUG)
{
printf("Update: Temp EXE name: %s\n", updName.c_str());
}
while (!Platform::RemoveFile(updName))
{
err = GetLastError();
if (err == ERROR_FILE_NOT_FOUND)
{
if constexpr (DEBUG)
{
printf("Update: Temp file not deleted\n");
}
// Old versions of powder toy name their update files with _update.exe, delete that upgrade file here
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_update.exe";
Platform::RemoveFile(updName);
return true;
}
Sleep(500);
timeout--;
if (timeout <= 0)
{
if constexpr (DEBUG)
{
printf("Update: Delete timeout\n");
}
return false;
}
}
return true;
}
void UpdateCleanup()
{
UpdateFinish();
}
void SetupCrt()
{
if constexpr (DEBUG)
{
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
}
}
}

View File

@ -4,3 +4,19 @@ common_files += files(
'tpt-rand.cpp',
'tpt-thread-local.cpp',
)
if host_platform == 'windows'
common_files += files('PlatformWindows.cpp')
elif host_platform == 'darwin'
common_files += files('PlatformDarwin.cpp')
common_files += files('PlatformPosix.cpp')
elif host_platform == 'android'
common_files += files('PlatformAndroid.cpp')
common_files += files('PlatformPosix.cpp')
elif host_platform == 'linux'
# TODO: again, this is more like "posix" than "linux"
common_files += files('PlatformLinux.cpp')
common_files += files('PlatformPosix.cpp')
else
common_files += files('PlatformNull.cpp')
common_files += files('PlatformPosix.cpp')
endif