diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 04eabb7..d643760 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,6 +35,7 @@ jobs:
dist/*.AppImage.blockmap
dist/*.deb
dist/*.rpm
+ dist/*.pacman
dist/*.pkg.tar.zst
dist/latest-linux.yml
diff --git a/README.md b/README.md
index 3a7bcb2..ac43863 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
-
+


@@ -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!** ⚠️
-🛑 **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** 🛑
- If you like the project, feel free to support us via Buy Me a Coffee!
+ 👍 If you like the project, feel free to support us via Buy Me a Coffee! ☕
Any support is appreciated and helps keep the project going.
@@ -160,9 +161,15 @@
### 🪟 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/)
-* **ENABLE MULTIPLAYER:** // TODO MULTIPLAYER GUIDE; FIREWALL GUIDE AND SUCH
+* **
+* **Java JDK 25:**
+ * [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
diff --git a/backend/core/paths.js b/backend/core/paths.js
index 17a7b92..78a5289 100644
--- a/backend/core/paths.js
+++ b/backend/core/paths.js
@@ -179,8 +179,28 @@ async function getModsPath(customInstallPath = null) {
const profilesPath = path.join(userDataPath, 'Profiles');
if (!fs.existsSync(modsPath)) {
- // Ensure the Mods directory exists
- fs.mkdirSync(modsPath, { recursive: true });
+ // Check for broken symlink to avoid EEXIST/EPERM on mkdir
+ 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)) {
fs.mkdirSync(disabledModsPath, { recursive: true });
diff --git a/backend/managers/gameManager.js b/backend/managers/gameManager.js
index 2fb8b62..963bbdd 100644
--- a/backend/managers/gameManager.js
+++ b/backend/managers/gameManager.js
@@ -365,7 +365,21 @@ async function updateGameFiles(newVersion, progressCallback, gameDir = GAME_DIR,
if (fs.existsSync(gameDir)) {
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);
diff --git a/backend/managers/modManager.js b/backend/managers/modManager.js
index 7929e8a..631db7f 100644
--- a/backend/managers/modManager.js
+++ b/backend/managers/modManager.js
@@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const axios = require('axios');
+const { getOS } = require('../utils/platformUtils');
const { getModsPath, getProfilesDir } = require('../core/paths');
const { saveModsToConfig, loadModsFromConfig } = require('../core/config');
const profileManager = require('./profileManager');
@@ -307,11 +308,16 @@ async function syncModsForCurrentProfile() {
// 2. Symlink / Migration Logic
let needsLink = false;
+ let globalStats = null;
+
+ try {
+ globalStats = fs.lstatSync(globalModsPath);
+ } catch (e) {
+ // Path doesn't exist
+ }
- if (fs.existsSync(globalModsPath)) {
- const stats = fs.lstatSync(globalModsPath);
-
- if (stats.isSymbolicLink()) {
+ if (globalStats) {
+ if (globalStats.isSymbolicLink()) {
const linkTarget = fs.readlinkSync(globalModsPath);
// Normalize paths for comparison
if (path.resolve(linkTarget) !== path.resolve(profileModsPath)) {
@@ -319,7 +325,7 @@ async function syncModsForCurrentProfile() {
fs.unlinkSync(globalModsPath);
needsLink = true;
}
- } else if (stats.isDirectory()) {
+ } else if (globalStats.isDirectory()) {
// MIGRATION: It's a real directory. Move contents to profile.
console.log('[ModManager] Migrating global mods folder to profile folder...');
const files = fs.readdirSync(globalModsPath);
@@ -349,7 +355,20 @@ async function syncModsForCurrentProfile() {
// Remove the directory so we can link it
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;
} catch (e) {
console.error('Failed to remove global mods dir:', e);
@@ -364,8 +383,8 @@ async function syncModsForCurrentProfile() {
if (needsLink) {
console.log(`[ModManager] Creating symlink: ${globalModsPath} -> ${profileModsPath}`);
try {
- // 'junction' is key for Windows without admin
- fs.symlinkSync(profileModsPath, globalModsPath, 'junction');
+ const symlinkType = getOS() === 'windows' ? 'junction' : 'dir';
+ fs.symlinkSync(profileModsPath, globalModsPath, symlinkType);
} catch (err) {
// 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.');
diff --git a/package-lock.json b/package-lock.json
index 12d6ac3..3a3df94 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "hytale-f2p-launcher",
- "version": "2.1.0",
+ "version": "2.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hytale-f2p-launcher",
- "version": "2.1.0",
+ "version": "2.1.1",
"license": "MIT",
"dependencies": {
"adm-zip": "^0.5.10",
diff --git a/package.json b/package.json
index 1b1fcf7..5937ba6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"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",
"homepage": "https://github.com/amiayweb/Hytale-F2P",
"main": "main.js",