mirror of
https://gitea.shironeko-all.duckdns.org/shironeko/Hytale-F2P-2.git
synced 2026-02-26 02:31:46 -03:00
Release v2.1.1: Fix EPERM cross-platform error (#183)
* fix: resolve cross-platform EPERM permissions errors modManager.js: - Switch from hardcoded 'junction' to dynamic symlink type based on OS (fixing Linux EPERM). - Add retry logic for directory removal to handle file locking race conditions. - Improve broken symlink detection during profile sync. gameManager.js: - Implement retry loop (3 attempts) for game directory removal in updateGameFiles to prevent EBUSY/EPERM errors on Windows. paths.js: - Prevent fs.mkdirSync failure in getModsPath by pre-checking for broken symbolic links. * fix: missing pacman builds * prepare release for 2.1.1 minor fix for EPERM error permission * Update README.md Windows Prequisites for ARM64 builds * fix: remove broken symlink after detected * fix: add pathexists for paths.js to check symlink * fix: isbrokenlink should be true to remove the symlink
This commit is contained in:
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -35,6 +35,7 @@ jobs:
|
|||||||
dist/*.AppImage.blockmap
|
dist/*.AppImage.blockmap
|
||||||
dist/*.deb
|
dist/*.deb
|
||||||
dist/*.rpm
|
dist/*.rpm
|
||||||
|
dist/*.pacman
|
||||||
dist/*.pkg.tar.zst
|
dist/*.pkg.tar.zst
|
||||||
dist/latest-linux.yml
|
dist/latest-linux.yml
|
||||||
|
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -1,12 +1,13 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1>🎮 Hytale F2P Launcher | Cross-Platform Multiplayer 🖥️</h1>
|
<h1>🎮 Hytale F2P Launcher 🚀</h1>
|
||||||
|
<h2>💻 Cross-Platform Multiplayer 🖥️</h2>
|
||||||
<h3>Available for Windows 🪟, macOS 🍎, and Linux 🐧</h3>
|
<h3>Available for Windows 🪟, macOS 🍎, and Linux 🐧</h3>
|
||||||
<p><small>An unofficial cross-platform launcher for Hytale with automatic updates and multiplayer support (all OS supported)</small></p>
|
<p><small>An unofficial cross-platform launcher for Hytale with automatic updates and multiplayer support (all OS supported)</small></p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
@@ -17,10 +18,10 @@
|
|||||||
|
|
||||||
### ⚠️ **READ [QUICK START](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-quick-start) before Downloading & Installing the Launcher!** ⚠️
|
### ⚠️ **READ [QUICK START](https://github.com/amiayweb/Hytale-F2P/tree/main?tab=readme-ov-file#-quick-start) before Downloading & Installing the Launcher!** ⚠️
|
||||||
|
|
||||||
🛑 **Found a problem? Join the Discord and Select #Open-A-Ticket!: https://discord.gg/gME8rUy3MB** 🛑
|
#### 🛑 **Found a problem? Join the Discord and Select #Open-A-Ticket!: https://discord.gg/gME8rUy3MB** 🛑
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
If you like the project, <b>feel free to support us via Buy Me a Coffee!</b>
|
👍 If you like the project, <b>feel free to support us via Buy Me a Coffee!</b> ☕<br>
|
||||||
Any support is appreciated and helps keep the project going.
|
Any support is appreciated and helps keep the project going.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -160,9 +161,15 @@
|
|||||||
|
|
||||||
|
|
||||||
### 🪟 Windows Prequisites
|
### 🪟 Windows Prequisites
|
||||||
* **Java JDK 25:** Download via [Adoptium](https://adoptium.net/temurin/releases/?version=25) or [Oracle](https://www.oracle.com/java/technologies/downloads/#jdk25-windows)
|
* **
|
||||||
* **Latest Visual Studio Redist:** Download via [Microsoft Visual C++ Redistributable](https://aka.ms/vc14/vc_redist.x64.exe) or [All-in-One by Techpowerup](https://www.techpowerup.com/download/visual-c-redistributable-runtime-package-all-in-one/)
|
* **Java JDK 25:**
|
||||||
* **ENABLE MULTIPLAYER:** // TODO MULTIPLAYER GUIDE; FIREWALL GUIDE AND SUCH
|
* [Oracle](https://www.oracle.com/java/technologies/downloads/#jdk25-windows), **no** support for Windows ARM64 in both version 25 and 21.
|
||||||
|
* [Adoptium](https://adoptium.net/temurin/releases/?version=25), has Windows ARM64 support in version 21 only.
|
||||||
|
* [Microsoft](https://learn.microsoft.com/en-us/java/openjdk/download), has Windows ARM64 support in version 25.
|
||||||
|
* Download from any vendor if your OS is not Windows with ARM64 architecture.
|
||||||
|
* **Latest Visual Studio Redist:**
|
||||||
|
* Download via [Microsoft Visual C++ Redistributable](https://aka.ms/vc14/vc_redist.x64.exe)
|
||||||
|
* Or [All-in-One by Techpowerup](https://www.techpowerup.com/download/visual-c-redistributable-runtime-package-all-in-one/)
|
||||||
|
|
||||||
### 🐧 Linux Prequisites
|
### 🐧 Linux Prequisites
|
||||||
|
|
||||||
|
|||||||
@@ -179,8 +179,28 @@ async function getModsPath(customInstallPath = null) {
|
|||||||
const profilesPath = path.join(userDataPath, 'Profiles');
|
const profilesPath = path.join(userDataPath, 'Profiles');
|
||||||
|
|
||||||
if (!fs.existsSync(modsPath)) {
|
if (!fs.existsSync(modsPath)) {
|
||||||
// Ensure the Mods directory exists
|
// Check for broken symlink to avoid EEXIST/EPERM on mkdir
|
||||||
fs.mkdirSync(modsPath, { recursive: true });
|
let isBrokenLink = false;
|
||||||
|
let pathExists = false;
|
||||||
|
try {
|
||||||
|
const stats = fs.lstatSync(modsPath);
|
||||||
|
pathExists = true;
|
||||||
|
if (stats.isSymbolicLink()) {
|
||||||
|
// Check if target exists
|
||||||
|
try {
|
||||||
|
fs.statSync(modsPath);
|
||||||
|
} catch {
|
||||||
|
isBrokenLink = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { /* path doesn't exist at all */ }
|
||||||
|
|
||||||
|
if (isBrokenLink) {
|
||||||
|
fs.unlinkSync(modsPath); // Remove broken symlink
|
||||||
|
}
|
||||||
|
if (!pathExists || isBrokenLink) {
|
||||||
|
fs.mkdirSync(modsPath, { recursive: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(disabledModsPath)) {
|
if (!fs.existsSync(disabledModsPath)) {
|
||||||
fs.mkdirSync(disabledModsPath, { recursive: true });
|
fs.mkdirSync(disabledModsPath, { recursive: true });
|
||||||
|
|||||||
@@ -365,7 +365,21 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
|
|||||||
|
|
||||||
if (fs.existsSync(gameDir)) {
|
if (fs.existsSync(gameDir)) {
|
||||||
console.log('Removing old game files...');
|
console.log('Removing old game files...');
|
||||||
fs.rmSync(gameDir, { recursive: true, force: true });
|
let retries = 3;
|
||||||
|
while (retries > 0) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(gameDir, { recursive: true, force: true });
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
if ((err.code === 'EPERM' || err.code === 'EBUSY') && retries > 0) {
|
||||||
|
retries--;
|
||||||
|
console.log(`[UpdateGameFiles] Removal failed with ${err.code}, retrying in 1s... (${retries} retries left)`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.renameSync(tempUpdateDir, gameDir);
|
fs.renameSync(tempUpdateDir, gameDir);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const { getOS } = require('../utils/platformUtils');
|
||||||
const { getModsPath, getProfilesDir } = require('../core/paths');
|
const { getModsPath, getProfilesDir } = require('../core/paths');
|
||||||
const { saveModsToConfig, loadModsFromConfig } = require('../core/config');
|
const { saveModsToConfig, loadModsFromConfig } = require('../core/config');
|
||||||
const profileManager = require('./profileManager');
|
const profileManager = require('./profileManager');
|
||||||
@@ -307,11 +308,16 @@ async function syncModsForCurrentProfile() {
|
|||||||
|
|
||||||
// 2. Symlink / Migration Logic
|
// 2. Symlink / Migration Logic
|
||||||
let needsLink = false;
|
let needsLink = false;
|
||||||
|
let globalStats = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
globalStats = fs.lstatSync(globalModsPath);
|
||||||
|
} catch (e) {
|
||||||
|
// Path doesn't exist
|
||||||
|
}
|
||||||
|
|
||||||
if (fs.existsSync(globalModsPath)) {
|
if (globalStats) {
|
||||||
const stats = fs.lstatSync(globalModsPath);
|
if (globalStats.isSymbolicLink()) {
|
||||||
|
|
||||||
if (stats.isSymbolicLink()) {
|
|
||||||
const linkTarget = fs.readlinkSync(globalModsPath);
|
const linkTarget = fs.readlinkSync(globalModsPath);
|
||||||
// Normalize paths for comparison
|
// Normalize paths for comparison
|
||||||
if (path.resolve(linkTarget) !== path.resolve(profileModsPath)) {
|
if (path.resolve(linkTarget) !== path.resolve(profileModsPath)) {
|
||||||
@@ -319,7 +325,7 @@ async function syncModsForCurrentProfile() {
|
|||||||
fs.unlinkSync(globalModsPath);
|
fs.unlinkSync(globalModsPath);
|
||||||
needsLink = true;
|
needsLink = true;
|
||||||
}
|
}
|
||||||
} else if (stats.isDirectory()) {
|
} else if (globalStats.isDirectory()) {
|
||||||
// MIGRATION: It's a real directory. Move contents to profile.
|
// MIGRATION: It's a real directory. Move contents to profile.
|
||||||
console.log('[ModManager] Migrating global mods folder to profile folder...');
|
console.log('[ModManager] Migrating global mods folder to profile folder...');
|
||||||
const files = fs.readdirSync(globalModsPath);
|
const files = fs.readdirSync(globalModsPath);
|
||||||
@@ -349,7 +355,20 @@ async function syncModsForCurrentProfile() {
|
|||||||
|
|
||||||
// Remove the directory so we can link it
|
// Remove the directory so we can link it
|
||||||
try {
|
try {
|
||||||
fs.rmSync(globalModsPath, { recursive: true, force: true });
|
let retries = 3;
|
||||||
|
while (retries > 0) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(globalModsPath, { recursive: true, force: true });
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
if ((err.code === 'EPERM' || err.code === 'EBUSY') && retries > 0) {
|
||||||
|
retries--;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
needsLink = true;
|
needsLink = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to remove global mods dir:', e);
|
console.error('Failed to remove global mods dir:', e);
|
||||||
@@ -364,8 +383,8 @@ async function syncModsForCurrentProfile() {
|
|||||||
if (needsLink) {
|
if (needsLink) {
|
||||||
console.log(`[ModManager] Creating symlink: ${globalModsPath} -> ${profileModsPath}`);
|
console.log(`[ModManager] Creating symlink: ${globalModsPath} -> ${profileModsPath}`);
|
||||||
try {
|
try {
|
||||||
// 'junction' is key for Windows without admin
|
const symlinkType = getOS() === 'windows' ? 'junction' : 'dir';
|
||||||
fs.symlinkSync(profileModsPath, globalModsPath, 'junction');
|
fs.symlinkSync(profileModsPath, globalModsPath, symlinkType);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If we can't create the symlink, try creating the directory first
|
// If we can't create the symlink, try creating the directory first
|
||||||
console.error('[ModManager] Failed to create symlink. Falling back to direct folder mode.');
|
console.error('[ModManager] Failed to create symlink. Falling back to direct folder mode.');
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||||
"homepage": "https://github.com/amiayweb/Hytale-F2P",
|
"homepage": "https://github.com/amiayweb/Hytale-F2P",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user