mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
NX2navi-Setup.exe installs the launcher into %LOCALAPPDATA%\Programs\NX2navi without administrator rights, with shortcuts and an Apps entry. The launcher finds the player's .nup, downloads the build tools itself (the MSYS2 UCRT64 packages toolchain.lock.json pins, checked by sha256 and unpacked without pacman, and zbic from PyPI) and runs tools/make_game.py, whose steps it shows with their progress. Its window follows the game's options menu: colours sampled from it, the menu's filigree, the white glowing cursor, the Triforce in the title band and as the programs' icon, and all text in the game's Cetra Display, decoded from the player's own dump. Mouse, keyboard and an XInput pad drive it. Updates come from GitHub releases (NX2navi-update.json): the new version is downloaded, checked and staged beside the running one, the stub restarts into it and falls back to the previous version when it fails to start, and the game is rebuilt without the dump. make_game.py now keys its exefs and romfs steps on the game files alone, has --rebuild and --events, compiles only the files whose source, headers, flags or compiler changed, and replaces ocarina.exe only once the new one links. tools/make_launcher.py builds the release on Windows or Linux. Tested: tests/launcher_test.py (19 tests), the real toolchain download and install, and under Wine 11 the setup, the stub, the window on the bundled Windows Python, an update from a local feed by clicking Updates, the rollback of a version that fails to start, and the downloaded clang compiling and linking a program against the game's libraries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Merged onto the current port: make_game.py keeps the launcher's step keys, cached link and in-place swap, and its own runtime manifest (windres), AMD's FidelityFX library and the shader preparation, which becomes a step of its own ("shaders") that the launcher shows in its install, rebuild and update jobs. make_launcher.py ships the shader tools and, with --ffx, AMD's library. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
4.9 KiB
C
118 lines
4.9 KiB
C
/* NX2navi.exe: the launcher's own program, in the launcher folder (launcher/paths.py).
|
|
|
|
1. A Python staged by an update (python.next) replaces python\ while nothing runs from it.
|
|
2. The release named by app\current.txt starts: python\pythonw.exe app\<version>\launcher\start.pyw with this
|
|
program's arguments, the launcher folder as working folder and in NX2NAVI_LAUNCHER_ROOT.
|
|
3. When that release fails to start (exit code 86 from start.pyw, or any non-zero exit within six seconds) and
|
|
app\previous.txt names another complete release, current.txt goes back to it and it starts with
|
|
--rolled-back <version>.
|
|
|
|
Its own commands, for the launcher and the setup: --desktop-shortcut on|off, --shortcuts on|off (Desktop and Start
|
|
menu). No console window (-mwindows). */
|
|
#include "common.h"
|
|
|
|
#define STARTUP_FAILED 86
|
|
#define QUICK_MS 6000
|
|
|
|
static void message(const wchar_t *text)
|
|
{
|
|
MessageBoxW(NULL, text, APP_NAME, MB_OK | MB_ICONERROR);
|
|
}
|
|
|
|
static int release_ok(const wchar_t *root, const wchar_t *ver)
|
|
{
|
|
wchar_t p[PATHN];
|
|
swprintf(p, PATHN, L"%ls\\app\\%ls\\launcher\\start.pyw", root, ver);
|
|
return ver[0] && path_exists(p);
|
|
}
|
|
|
|
static void swap_python(const wchar_t *root)
|
|
{
|
|
wchar_t next[PATHN], cur[PATHN], old[PATHN];
|
|
swprintf(next, PATHN, L"%ls\\python.next", root);
|
|
if (!is_dir(next)) return;
|
|
swprintf(cur, PATHN, L"%ls\\python", root);
|
|
swprintf(old, PATHN, L"%ls\\python.old-%lu", root, (unsigned long)GetTickCount());
|
|
if (is_dir(cur) && !MoveFileExW(cur, old, 0)) return; /* in use: try again at the next start */
|
|
if (!MoveFileExW(next, cur, 0)) MoveFileExW(old, cur, 0);
|
|
}
|
|
|
|
/* Start the release; returns its exit code if it ended within QUICK_MS, else -1 (still running). */
|
|
static long start_release(const wchar_t *root, const wchar_t *ver, const wchar_t *args, const wchar_t *extra)
|
|
{
|
|
static wchar_t cmd[32768];
|
|
wchar_t python[PATHN];
|
|
swprintf(python, PATHN, L"%ls\\python\\pythonw.exe", root);
|
|
if (!path_exists(python)) {
|
|
message(L"The launcher's Python is missing. Run NX2navi-Setup.exe again to repair the installation.");
|
|
return 1;
|
|
}
|
|
swprintf(cmd, 32768, L"\"%ls\" \"%ls\\app\\%ls\\launcher\\start.pyw\" %ls %ls", python, root, ver, args, extra);
|
|
SetEnvironmentVariableW(L"NX2NAVI_LAUNCHER_ROOT", root);
|
|
STARTUPINFOW si = {.cb = sizeof si};
|
|
PROCESS_INFORMATION pi;
|
|
if (!CreateProcessW(python, cmd, NULL, NULL, FALSE, 0, NULL, root, &si, &pi)) {
|
|
message(L"The launcher could not be started. Run NX2navi-Setup.exe again to repair the installation.");
|
|
return 1;
|
|
}
|
|
CloseHandle(pi.hThread);
|
|
DWORD code = 0;
|
|
long result = -1;
|
|
if (WaitForSingleObject(pi.hProcess, QUICK_MS) == WAIT_OBJECT_0 && GetExitCodeProcess(pi.hProcess, &code))
|
|
result = (long)code;
|
|
CloseHandle(pi.hProcess);
|
|
return result;
|
|
}
|
|
|
|
static int shortcut_command(const wchar_t *root, int argc, wchar_t **argv)
|
|
{
|
|
for (int i = 1; i + 1 < argc; i++) {
|
|
int on = !wcscmp(argv[i + 1], L"on");
|
|
if (!wcscmp(argv[i], L"--desktop-shortcut")) return set_shortcut(root, 1, on) ? 0 : 1;
|
|
if (!wcscmp(argv[i], L"--shortcuts")) {
|
|
int a = set_shortcut(root, 1, on), b = set_shortcut(root, 0, on);
|
|
return a && b ? 0 : 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, PWSTR args, int show)
|
|
{
|
|
wchar_t root[PATHN], file[PATHN], ver[128], prev[128], extra[160];
|
|
(void)instance; (void)previous; (void)show;
|
|
exe_dir(root, PATHN);
|
|
if (!root[0]) return 1;
|
|
int argc = 0;
|
|
wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
|
if (argv) {
|
|
int r = shortcut_command(root, argc, argv);
|
|
LocalFree(argv);
|
|
if (r >= 0) return r;
|
|
}
|
|
swap_python(root);
|
|
swprintf(file, PATHN, L"%ls\\app\\current.txt", root);
|
|
if (!read_line(file, ver, 128) || !release_ok(root, ver)) {
|
|
swprintf(file, PATHN, L"%ls\\app\\previous.txt", root);
|
|
if (!read_line(file, ver, 128) || !release_ok(root, ver)) {
|
|
message(L"NX2navi's files are incomplete. Run NX2navi-Setup.exe again to repair the installation.");
|
|
return 1;
|
|
}
|
|
}
|
|
long code = start_release(root, ver, args ? args : L"", L"");
|
|
if (code == -1 || code == 0) return 0;
|
|
swprintf(file, PATHN, L"%ls\\app\\previous.txt", root);
|
|
if ((code == STARTUP_FAILED || code > 0) && read_line(file, prev, 128) && wcscmp(prev, ver) &&
|
|
release_ok(root, prev)) {
|
|
swprintf(file, PATHN, L"%ls\\app\\current.txt", root);
|
|
write_line(file, prev);
|
|
swprintf(file, PATHN, L"%ls\\app\\previous.txt", root);
|
|
DeleteFileW(file);
|
|
swprintf(extra, 160, L"--rolled-back %ls", ver);
|
|
start_release(root, prev, args ? args : L"", extra);
|
|
return 0;
|
|
}
|
|
if (code == STARTUP_FAILED) message(L"NX2navi could not start. data\\logs\\crash.log in its folder says why.");
|
|
return (int)code;
|
|
}
|