Skip to content

Commit 1bdd736

Browse files
authored
Merge pull request #19 from TheSHEEEP/master
Minimal Linux Proton Support
2 parents 7119735 + 7222ed1 commit 1bdd736

File tree

2 files changed

+59
-40
lines changed

2 files changed

+59
-40
lines changed

src/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -761,15 +761,16 @@ ipcMain.on("exportModsToClipboard", async (event, mods: Mod[]) => {
761761

762762
ipcMain.on("startGame", async (event, mods: Mod[], startGameOptions: StartGameOptions, saveName?: string) => {
763763
const appDataPath = app.getPath("userData");
764-
const userScriptPath = `${appData.gamePath}\\my_mods.txt`;
764+
const userScriptPath = nodePath.join(appData.gamePath, "my_mods.txt");
765765

766766
const sortedMods = sortByNameAndLoadOrder(mods);
767767
const enabledMods = sortedMods.filter((mod) => mod.isEnabled);
768768

769+
const linuxBit = process.platform === "linux" ? "Z:" : "";
769770
const dataMod: Mod = {
770771
humanName: "",
771772
name: "data.pack",
772-
path: `${appData.dataFolder}\\data.pack`,
773+
path: nodePath.join(appData.dataFolder as string, "data.pack"),
773774
imgPath: "",
774775
workshopId: "",
775776
isEnabled: true,
@@ -789,14 +790,13 @@ ipcMain.on("startGame", async (event, mods: Mod[], startGameOptions: StartGameOp
789790
startGameOptions.isScriptLoggingEnabled ||
790791
startGameOptions.isSkipIntroMoviesEnabled
791792
) {
792-
await fs.mkdir(`${appDataPath}\\tempPacks`, { recursive: true });
793+
await fs.mkdir(nodePath.join(appDataPath, "tempPacks"), { recursive: true });
793794

794795
// const data = await readPacks(enabledMods.map((mod) => mod.path));
795796
const tempPackName = "!!!!out.pack";
796-
const tempPackPath = `${appDataPath}\\tempPacks\\${tempPackName}`;
797+
const tempPackPath = nodePath.join(appDataPath, "tempPacks", tempPackName);
797798
await writePack(appData.packsData, tempPackPath, enabledMods.concat(dataMod), startGameOptions);
798-
799-
extraEnabledMods = `\nadd_working_directory "${appDataPath}\\tempPacks";` + `\nmod "${tempPackName}";`;
799+
extraEnabledMods = `\nadd_working_directory "${linuxBit + nodePath.join(appDataPath, "tempPacks")}";` + `\nmod "${tempPackName}";`;
800800
}
801801

802802
const modPathsInsideMergedMods = enabledMods
@@ -811,12 +811,12 @@ ipcMain.on("startGame", async (event, mods: Mod[], startGameOptions: StartGameOp
811811
const text =
812812
enabledModsWithoutMergedInMods
813813
.filter((mod) => nodePath.relative(appData.dataFolder as string, mod.modDirectory) != "")
814-
.map((mod) => `add_working_directory "${mod.modDirectory}";`)
814+
.map((mod) => `add_working_directory "${linuxBit + mod.modDirectory}";`)
815815
.concat(enabledModsWithoutMergedInMods.map((mod) => `mod "${mod.name}";`))
816816
.join("\n") + extraEnabledMods;
817817

818818
await fs.writeFile(userScriptPath, text);
819-
const batPath = `${appDataPath}\\game.bat`;
819+
const batPath = nodePath.join(appDataPath, "game.bat");
820820
let batData = `start /d "${appData.gamePath}" Warhammer3.exe`;
821821
if (saveName) {
822822
batData += ` game_startup_mode campaign_load "${saveName}" ;`;

src/modFunctions.ts

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { parse, getTime } from "date-fns";
22
import Registry from "winreg";
33
import * as VDF from "@node-steam/vdf";
4-
import * as fs from "fs/promises";
4+
import * as fsPromises from "fs/promises";
55
import * as dumbfs from "fs";
66
import appData from "./appData";
77
import fetch from "electron-fetch";
88
import { zonedTimeToUtc } from "date-fns-tz";
99
import * as nodePath from "path";
1010
import * as fsExtra from "fs-extra";
11+
import * as os from "os";
1112

1213
export function fetchModData(ids: string[], cb: (modData: ModData) => void, log: (msg: string) => void) {
1314
ids.forEach(async (workshopId) => {
@@ -157,17 +158,17 @@ export async function getDataMod(filePath: string, log: (msg: string) => void):
157158
let lastChangedLocal = undefined;
158159
let size = -1;
159160
try {
160-
[lastChangedLocal, size] = await fs.stat(filePath).then((stats) => {
161+
[lastChangedLocal, size] = await fsPromises.stat(filePath).then((stats) => {
161162
return [stats.mtimeMs, stats.size];
162163
});
163164
} catch (err) {
164165
log(`ERROR: ${err}`);
165166
}
166167

167168
let doesThumbnailExist = false;
168-
const thumbnailPath = `${dataPath}\\${fileName.replace(/\.pack$/, ".png")}`;
169+
const thumbnailPath = nodePath.join(dataPath, fileName.replace(/\.pack$/, ".png"));
169170
try {
170-
await fs.access(thumbnailPath, dumbfs.constants.R_OK);
171+
await fsPromises.access(thumbnailPath, dumbfs.constants.R_OK);
171172
doesThumbnailExist = true;
172173
// eslint-disable-next-line no-empty
173174
} catch {}
@@ -181,11 +182,13 @@ export async function getDataMod(filePath: string, log: (msg: string) => void):
181182
// eslint-disable-next-line no-empty
182183
} catch {}
183184

185+
186+
const linuxBit = process.platform === "linux" ? "Z:" : "";
184187
const mod: Mod = {
185188
humanName: "",
186189
name: fileName,
187190
path: filePath,
188-
modDirectory: nodePath.dirname(filePath),
191+
modDirectory: linuxBit + nodePath.dirname(filePath),
189192
imgPath: doesThumbnailExist ? thumbnailPath : "",
190193
workshopId: fileName,
191194
isEnabled: false,
@@ -206,7 +209,7 @@ const getDataMods = async (gameDir: string, log: (msg: string) => void): Promise
206209
if (!dataPath) throw new Error("Data folder not found");
207210

208211
const vanillaPacks: string[] = [];
209-
return fs.readFile(`${gameDir}\\data\\manifest.txt`, "utf8").then(async (data) => {
212+
return fsPromises.readFile(nodePath.join(gameDir, "data", "manifest.txt"), "utf8").then(async (data) => {
210213
const re = /([^\s]+)/;
211214
data.split("\n").map((line) => {
212215
const found = line.match(re);
@@ -215,7 +218,7 @@ const getDataMods = async (gameDir: string, log: (msg: string) => void): Promise
215218
}
216219
});
217220

218-
const files = await fs.readdir(dataPath, { withFileTypes: true });
221+
const files = await fsPromises.readdir(dataPath, { withFileTypes: true });
219222

220223
const dataModsPromises = files
221224
.filter(
@@ -238,36 +241,52 @@ const getDataMods = async (gameDir: string, log: (msg: string) => void): Promise
238241
};
239242

240243
const getFolderPaths = async (log: (msg: string) => void) => {
241-
const regKey = new Registry({
242-
hive: Registry.HKLM,
243-
key: "\\SOFTWARE\\Wow6432Node\\Valve\\Steam",
244-
});
244+
let installPath = "";
245+
if (process.platform === "win32") {
246+
const regKey = new Registry({
247+
hive: Registry.HKLM,
248+
key: "\\SOFTWARE\\Wow6432Node\\Valve\\Steam",
249+
});
245250

246-
const items = await regKeyValuesAsPromise(regKey);
247-
const installPathObj = items.find((x) => x.name === "InstallPath");
248-
if (!installPathObj) return;
251+
const items = await regKeyValuesAsPromise(regKey);
252+
const installPathObj = items.find((x) => x.name === "InstallPath");
253+
if (!installPathObj) {
254+
log("Unable to find InstallPath in Windows registry");
255+
return;
256+
}
257+
installPath = installPathObj.value;
258+
}
259+
else if (process.platform === "linux") {
260+
const steamPath = os.homedir() + "/.steam/steam";
261+
if (!dumbfs.existsSync(steamPath)) {
262+
log("Unable to find steam directory at " + steamPath);
263+
return;
264+
}
265+
installPath = steamPath;
266+
}
249267

250-
const installPath = installPathObj.value;
251-
const libFoldersPath = `${installPath}\\steamapps\\libraryfolders.vdf`;
268+
const libFoldersPath = nodePath.join(installPath, "steamapps", "libraryfolders.vdf");
269+
log(`Check lib vdf at ${libFoldersPath}`);
270+
if (!dumbfs.existsSync(libFoldersPath)) return;
252271
log(`Found libraryfolders.vdf at ${libFoldersPath}`);
253272

254-
const data = await fs.readFile(libFoldersPath, "utf8");
273+
const data = await fsPromises.readFile(libFoldersPath, "utf8");
255274
const object = VDF.parse(data).libraryfolders;
256275
const paths = [];
257276
for (const property in object) {
258277
paths.push(object[property].path);
259278
}
260279

261-
for (const path of paths) {
262-
const worshopFilePath = `${path}\\steamapps\\appmanifest_1142710.acf`;
280+
for (const basepath of paths) {
281+
const path = basepath.replaceAll("\\\\", "\\").replaceAll("//", "/");
282+
const worshopFilePath = nodePath.join(path, "steamapps", "appmanifest_1142710.acf");
263283
try {
264-
await fs.readFile(worshopFilePath);
284+
await fsPromises.readFile(worshopFilePath);
265285
log(`Found appmanifest_1142710.acf at ${worshopFilePath}`);
266-
// log(worshopFilePath);
267-
const contentFolder = `${path}\\steamapps\\workshop\\content\\1142710`;
268-
appData.contentFolder = contentFolder.replaceAll("\\\\", "\\");
269-
appData.gamePath = `${path}\\steamapps\\common\\Total War WARHAMMER III`.replaceAll("\\\\", "\\");
270-
appData.dataFolder = `${appData.gamePath}\\data`.replaceAll("\\\\", "\\");
286+
const contentFolder = nodePath.join(path, "steamapps", "workshop", "content", "1142710");
287+
appData.contentFolder = contentFolder;
288+
appData.gamePath = nodePath.join(path, "steamapps", "common", "Total War WARHAMMER III");
289+
appData.dataFolder = nodePath.join(appData.gamePath, "data");
271290

272291
log(`Content folder is at ${appData.contentFolder}`);
273292
log(`Game path is at ${appData.gamePath}`);
@@ -295,7 +314,7 @@ export async function getContentModInFolder(
295314
if (!appData.contentFolder) throw new Error("Content folder not found");
296315
const contentFolder = appData.contentFolder;
297316

298-
const files = await fs.readdir(`${contentFolder}\\${contentSubFolderName}`, { withFileTypes: true });
317+
const files = await fsPromises.readdir(nodePath.join(contentFolder, contentSubFolderName), { withFileTypes: true });
299318

300319
const pack = files.find((file) => file.name.endsWith(".pack"));
301320
const img = files.find((file) => file.name.endsWith(".png"));
@@ -305,8 +324,8 @@ export async function getContentModInFolder(
305324
let lastChangedLocal = undefined;
306325
let size = -1;
307326
try {
308-
[lastChangedLocal, size] = await fs
309-
.stat(`${contentFolder}\\${contentSubFolderName}\\${pack.name}`)
327+
[lastChangedLocal, size] = await fsPromises
328+
.stat(nodePath.join(contentFolder, contentSubFolderName, pack.name))
310329
.then((stats) => {
311330
return [stats.mtimeMs, stats.size];
312331
});
@@ -315,14 +334,14 @@ export async function getContentModInFolder(
315334
}
316335

317336
// log(`Reading pack file ${contentFolder}\\${file.name}\\${pack.name}`);
318-
const packPath = `${contentFolder}\\${contentSubFolderName}\\${pack.name}`;
319-
const imgPath = (img && `${contentFolder}\\${contentSubFolderName}\\${img.name}`) || "";
337+
const packPath = nodePath.join(contentFolder, contentSubFolderName, pack.name);
338+
const imgPath = (img && nodePath.join(contentFolder, contentSubFolderName, img.name) || "");
320339
const mod: Mod = {
321340
author: "",
322341
humanName: "",
323342
name: pack.name,
324343
path: packPath,
325-
modDirectory: `${contentFolder}\\${contentSubFolderName}`,
344+
modDirectory: nodePath.join(contentFolder, contentSubFolderName),
326345
imgPath: imgPath,
327346
workshopId: contentSubFolderName,
328347
isEnabled: false,
@@ -348,7 +367,7 @@ export async function getMods(log: (msg: string) => void): Promise<Mod[]> {
348367
const dataMods = await getDataMods(appData.gamePath, log);
349368
mods.push(...dataMods);
350369

351-
const files = await fs.readdir(contentFolder, { withFileTypes: true });
370+
const files = await fsPromises.readdir(contentFolder, { withFileTypes: true });
352371
const newMods = files
353372
.filter((file) => file.isDirectory())
354373
.map(async (contentSubFolder) => {

0 commit comments

Comments
 (0)