fix(launcher): Gitea http URL; Wine Z: path + Wow.exe case check
- baked-gitea-channel: http:// for brassnet mirror. - win-game-dir: map Unix /home/... to Z:\ under win32 (Wine folder picker). - resolveGameDir + saveGameDir + patch paths use it; Wow.exe resolved case-insensitively. - Version 1.0.6; README checklist for Wine. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -125,7 +125,8 @@ CI workflow **Sync release to Gitea** (`.github/workflows/gitea-release-sync.yml
|
|||||||
6. **Secrets** — **`GITEA_BASE_URL`**, **`GITEA_TOKEN`**, **`GITEA_OWNER`**, **`GITEA_REPO`** must be set under **Settings → Secrets and variables → Actions**. A failed “Upload to Gitea” step usually prints which is missing.
|
6. **Secrets** — **`GITEA_BASE_URL`**, **`GITEA_TOKEN`**, **`GITEA_OWNER`**, **`GITEA_REPO`** must be set under **Settings → Secrets and variables → Actions**. A failed “Upload to Gitea” step usually prints which is missing.
|
||||||
7. **Actions tab** — Open the latest **Sync release to Gitea** run; a red **build-electron** (old tag without `package-lock.json`, etc.) or **Upload to Gitea** step shows the real error.
|
7. **Actions tab** — Open the latest **Sync release to Gitea** run; a red **build-electron** (old tag without `package-lock.json`, etc.) or **Upload to Gitea** step shows the real error.
|
||||||
8. **HTTP 422 `repo is empty`** — The Gitea repo has **no commits** yet. Push any initial commit (e.g. **Add README** in the Gitea web UI, or `git push` to **`main`**). Optionally set **`GITEA_TARGET_REF`** to match your real default branch if it is not **`main`**. From this repo you can run **`scripts/bootstrap-gitea-repo.sh`** (see script header for `GITEA_*` env or pass the HTTPS/SSH clone URL as the first argument).
|
8. **HTTP 422 `repo is empty`** — The Gitea repo has **no commits** yet. Push any initial commit (e.g. **Add README** in the Gitea web UI, or `git push` to **`main`**). Optionally set **`GITEA_TARGET_REF`** to match your real default branch if it is not **`main`**. From this repo you can run **`scripts/bootstrap-gitea-repo.sh`** (see script header for `GITEA_*` env or pass the HTTPS/SSH clone URL as the first argument).
|
||||||
9. **`sync Wow.exe: fetch failed`** (Linux/AppImage especially) — Usually **TLS** (self-signed Gitea cert) or **cannot reach** the host. Use a **trusted certificate** on Gitea, or put your CA in the system trust store, or set **`NODE_EXTRA_CA_CERTS=/path/to/ca.pem`** when launching the AppImage if you must use self-signed HTTPS. If the mirror is LAN-only HTTP, set **`base_url`** in **`lib/baked-gitea-channel.js`** to **`http://…`** (only if acceptable). Ensure **`Wow-patched.exe`** exists on the **same Gitea release** you configured (**`release_tag`**: `latest` vs pinned tag). Newer builds show the failing URL and TLS hints in the error text.
|
9. **`sync Wow.exe: fetch failed`** — Often **HTTPS/TLS** to Gitea; use **`http://…`** in **`lib/baked-gitea-channel.js`** if you only serve plain HTTP, or fix certs / **`NODE_EXTRA_CA_CERTS`**. Ensure **`Wow-patched.exe`** exists on the release (**`release_tag`**: `latest` vs pinned). Errors include the failing URL when possible.
|
||||||
|
10. **Wine + Windows portable** — If the folder picker returns **`/home/...`**, the launcher maps it to **`Z:\home\...`** (Wine’s Unix root). **`Wow.exe`** is matched case-insensitively for Linux-backed folders. Re-save the WoW folder after upgrading if validation still fails.
|
||||||
|
|
||||||
### Private Gitea token for players
|
### Private Gitea token for players
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
* Token stays in env: GITEA_TOKEN or launcher.json → gitea.token_env.
|
* Token stays in env: GITEA_TOKEN or launcher.json → gitea.token_env.
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Scheme optional — gitea-release normalizes to https:// if missing.
|
// http:// kept as-is; bare host gets https in gitea-release.js
|
||||||
base_url: 'https://brassnet.ddns.net:33983',
|
base_url: 'http://brassnet.ddns.net:33983',
|
||||||
owner: 'Dawnsorrow',
|
owner: 'Dawnsorrow',
|
||||||
repo: 'Fractured-Distro',
|
repo: 'Fractured-Distro',
|
||||||
release_tag: 'latest',
|
release_tag: 'latest',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
|
const { normalizeWinGameDir } = require('./win-game-dir');
|
||||||
|
|
||||||
/** Sources no longer shipped; drop from merged files so old launcher.json does not keep fetching them. */
|
/** Sources no longer shipped; drop from merged files so old launcher.json does not keep fetching them. */
|
||||||
const DEPRECATED_FILE_SOURCES = new Set(['patch-Z.MPQ']);
|
const DEPRECATED_FILE_SOURCES = new Set(['patch-Z.MPQ']);
|
||||||
@@ -105,8 +106,9 @@ async function saveGameDir(configPath, gameDir) {
|
|||||||
function resolveGameDir(cfg, configPath) {
|
function resolveGameDir(cfg, configPath) {
|
||||||
const gd = cfg.game_dir;
|
const gd = cfg.game_dir;
|
||||||
if (!gd) return '';
|
if (!gd) return '';
|
||||||
if (path.isAbsolute(gd)) return path.normalize(gd);
|
const abs = path.isAbsolute(gd) ? path.normalize(gd) : path.normalize(path.join(path.dirname(configPath), gd));
|
||||||
return path.normalize(path.join(path.dirname(configPath), gd));
|
if (process.platform === 'win32') return normalizeWinGameDir(abs);
|
||||||
|
return abs;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getConfigPath, loadConfig, saveGameDir, resolveGameDir, mergeConfig, applyBakedGitea };
|
module.exports = { getConfigPath, loadConfig, saveGameDir, resolveGameDir, mergeConfig, applyBakedGitea };
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
|
const fsSync = require('fs');
|
||||||
const { downloadGitHubRepoFile, downloadReleaseAsset } = require('./github');
|
const { downloadGitHubRepoFile, downloadReleaseAsset } = require('./github');
|
||||||
|
const { normalizeWinGameDir } = require('./win-game-dir');
|
||||||
|
|
||||||
function pad2(n) {
|
function pad2(n) {
|
||||||
return String(n).padStart(2, '0');
|
return String(n).padStart(2, '0');
|
||||||
@@ -13,19 +15,38 @@ function backupSuffix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function wowExePath(cfg) {
|
function wowExePath(cfg) {
|
||||||
|
const gd = normalizeWinGameDir(cfg.game_dir || '');
|
||||||
const exe = (cfg.launch && cfg.launch.exe) || 'Wow.exe';
|
const exe = (cfg.launch && cfg.launch.exe) || 'Wow.exe';
|
||||||
const parts = exe.replace(/\\/g, '/').split('/').filter(Boolean);
|
const parts = exe.replace(/\\/g, '/').split('/').filter(Boolean);
|
||||||
return path.join(cfg.game_dir, ...parts);
|
const primary = path.join(gd, ...parts);
|
||||||
|
if (process.platform === 'win32' && gd && fsSync.existsSync(primary)) return primary;
|
||||||
|
if (process.platform === 'win32' && gd) {
|
||||||
|
try {
|
||||||
|
const base = path.basename(primary);
|
||||||
|
const dir = path.dirname(primary);
|
||||||
|
const names = fsSync.readdirSync(dir);
|
||||||
|
const hit = names.find((n) => n.toLowerCase() === base.toLowerCase());
|
||||||
|
if (hit) {
|
||||||
|
const alt = path.join(dir, hit);
|
||||||
|
if (fsSync.statSync(alt).isFile()) return alt;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wowInstallValid(cfg) {
|
function wowInstallValid(cfg) {
|
||||||
if (!cfg.game_dir) return false;
|
if (!cfg.game_dir) return false;
|
||||||
return require('fs').existsSync(wowExePath(cfg));
|
const p = wowExePath(cfg);
|
||||||
|
return fsSync.existsSync(p) && fsSync.statSync(p).isFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installFile(cfg, entry) {
|
async function installFile(cfg, entry) {
|
||||||
const parts = String(entry.dest).replace(/\\/g, '/').split('/').filter(Boolean);
|
const parts = String(entry.dest).replace(/\\/g, '/').split('/').filter(Boolean);
|
||||||
const destAbs = path.join(cfg.game_dir, ...parts);
|
const root = normalizeWinGameDir(cfg.game_dir || '');
|
||||||
|
const destAbs = path.join(root, ...parts);
|
||||||
if (entry.backup) {
|
if (entry.backup) {
|
||||||
try {
|
try {
|
||||||
const st = await fs.stat(destAbs);
|
const st = await fs.stat(destAbs);
|
||||||
@@ -66,7 +87,7 @@ async function applyRealmlist(cfg) {
|
|||||||
const r = String(rel).trim().replace(/\\/g, '/');
|
const r = String(rel).trim().replace(/\\/g, '/');
|
||||||
if (!r) continue;
|
if (!r) continue;
|
||||||
const segs = r.split('/').filter(Boolean);
|
const segs = r.split('/').filter(Boolean);
|
||||||
const abs = path.join(cfg.game_dir, ...segs);
|
const abs = path.join(normalizeWinGameDir(cfg.game_dir || ''), ...segs);
|
||||||
await fs.mkdir(path.dirname(abs), { recursive: true });
|
await fs.mkdir(path.dirname(abs), { recursive: true });
|
||||||
await fs.writeFile(abs, content, 'utf8');
|
await fs.writeFile(abs, content, 'utf8');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Under Wine, the folder picker often returns a Unix absolute path (/home/...).
|
||||||
|
* Windows Node does not resolve that to the WoW install; map to Wine's Z: drive
|
||||||
|
* (Z: == / on typical Wine prefixes).
|
||||||
|
*/
|
||||||
|
function normalizeWinGameDir(gameDir) {
|
||||||
|
if (process.platform !== 'win32') return String(gameDir || '').trim();
|
||||||
|
let s = String(gameDir || '').trim();
|
||||||
|
if (!s) return s;
|
||||||
|
s = s.replace(/\//g, path.win32.sep);
|
||||||
|
if (s.startsWith('\\\\')) return path.normalize(s);
|
||||||
|
if (/^[A-Za-z]:/.test(s)) return path.normalize(s);
|
||||||
|
if (s.startsWith(path.win32.sep)) return path.win32.normalize(`Z:${s}`);
|
||||||
|
return path.normalize(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { normalizeWinGameDir };
|
||||||
@@ -4,6 +4,7 @@ const { app, BrowserWindow, ipcMain, dialog, Menu } = require('electron');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const { loadConfig, saveGameDir, resolveGameDir } = require('./lib/config-store');
|
const { loadConfig, saveGameDir, resolveGameDir } = require('./lib/config-store');
|
||||||
|
const { normalizeWinGameDir } = require('./lib/win-game-dir');
|
||||||
const { applyPatches, wowExePath, wowInstallValid, doAuth } = require('./lib/patch');
|
const { applyPatches, wowExePath, wowInstallValid, doAuth } = require('./lib/patch');
|
||||||
const { readPatchState } = require('./lib/patch-manifest');
|
const { readPatchState } = require('./lib/patch-manifest');
|
||||||
const { setupAutoUpdater } = require('./lib/auto-update');
|
const { setupAutoUpdater } = require('./lib/auto-update');
|
||||||
@@ -95,7 +96,8 @@ ipcMain.handle('launcher:saveGameDir', async (_e, dir) => {
|
|||||||
const trimmed = String(dir || '').trim();
|
const trimmed = String(dir || '').trim();
|
||||||
if (!trimmed) throw new Error('folder path is empty');
|
if (!trimmed) throw new Error('folder path is empty');
|
||||||
const { configPath } = await loadConfig(app);
|
const { configPath } = await loadConfig(app);
|
||||||
const norm = path.normalize(trimmed);
|
const norm =
|
||||||
|
process.platform === 'win32' ? normalizeWinGameDir(path.normalize(trimmed)) : path.normalize(trimmed);
|
||||||
const probe = { ...(await readMergedConfig()).config, game_dir: norm };
|
const probe = { ...(await readMergedConfig()).config, game_dir: norm };
|
||||||
if (!wowInstallValid(probe)) {
|
if (!wowInstallValid(probe)) {
|
||||||
throw new Error(`That folder does not contain ${(probe.launch && probe.launch.exe) || 'Wow.exe'}`);
|
throw new Error(`That folder does not contain ${(probe.launch && probe.launch.exe) || 'Wow.exe'}`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fractured-launcher-electron",
|
"name": "fractured-launcher-electron",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"description": "Fractured WoW launcher (Electron) — no console window, native folder picker, auto-update",
|
"description": "Fractured WoW launcher (Electron) — no console window, native folder picker, auto-update",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
"renderer.js",
|
"renderer.js",
|
||||||
"styles.css",
|
"styles.css",
|
||||||
"default-launcher.json",
|
"default-launcher.json",
|
||||||
|
"lib/win-game-dir.js",
|
||||||
"lib/baked-gitea-channel.js",
|
"lib/baked-gitea-channel.js",
|
||||||
"lib/gitea-release.js",
|
"lib/gitea-release.js",
|
||||||
"lib/patch-manifest.js",
|
"lib/patch-manifest.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user