Add the possibility to launch to game with or without bepinex and the ability to backup bepinex and restore this backup

This commit is contained in:
2026-02-02 10:25:47 +01:00
parent d6697b13c6
commit 16fc58e673
4 changed files with 177 additions and 52 deletions

131
main.js
View File

@@ -9,7 +9,18 @@ const extract = require("extract-zip");
const store = new Store(); const store = new Store();
const userSavePath = app.getPath('userData') const userSavePath = app.getPath('userData')
let silksongPath = store.get('silksong-path') let silksongPath = store.get('silksong-path')
let bepinexFolderPath = `${silksongPath}/BepInEx`
let bepinexBackupPath = `${silksongPath}/BepInEx-Backup`
const bepinexFiles = [
".doorstop_version",
"changelog.txt",
"doorstop_config.ini",
"winhttp.dll"
]
let bepinexVersion let bepinexVersion
let bepinexBackupVersion
const createWindow = () => { const createWindow = () => {
const win = new BrowserWindow({ const win = new BrowserWindow({
@@ -41,6 +52,8 @@ app.on('window-all-closed', () => {
ipcMain.handle('save-path', (event, path) => { ipcMain.handle('save-path', (event, path) => {
silksongPath = path; silksongPath = path;
bepinexFolderPath = `${silksongPath}/BepInEx`
bepinexBackupPath = `${silksongPath}/BepInEx-Backup`
store.set('silksong-path', silksongPath); store.set('silksong-path', silksongPath);
}); });
@@ -61,11 +74,25 @@ function saveBepinexVersion(version) {
store.set('bepinex-version', version); store.set('bepinex-version', version);
}; };
function saveBepinexBackupVersion(version) {
bepinexBackupVersion = version;
if (bepinexBackupVersion == undefined) {
store.delete('bepinex-backup-version');
return;
}
store.set('bepinex-backup-version', version);
};
ipcMain.handle('load-bepinex-version', () => { ipcMain.handle('load-bepinex-version', () => {
bepinexVersion = store.get('bepinex-version'); bepinexVersion = store.get('bepinex-version');
return bepinexVersion; return bepinexVersion;
}); });
ipcMain.handle('load-bepinex-backup-version', () => {
bepinexBackupVersion = store.get('bepinex-backup-version');
return bepinexBackupVersion;
});
async function fileExists(filePath) { async function fileExists(filePath) {
try { try {
await fs.access(filePath); await fs.access(filePath);
@@ -124,7 +151,47 @@ ipcMain.handle('open-link', async (event, link) => {
await shell.openExternal(link) await shell.openExternal(link)
}) })
ipcMain.handle('install-bepinex', async () => { ipcMain.handle('launch-game', async (event, mode) => {
const silksongExecutablePath = `${silksongPath}/Hollow Knight Silksong.exe`
if (mode === "modded"){
if (await fileExists(bepinexFolderPath)) {
await shell.openExternal(silksongExecutablePath)
}
else {
await installBepinex()
await shell.openExternal(silksongExecutablePath)
}
}
if (mode === "vanilla"){
if (await fileExists(bepinexFolderPath)) {
await backupBepinex()
await shell.openExternal(silksongExecutablePath)
}
else {
await shell.openExternal(silksongExecutablePath)
}
}
})
async function installBepinex() {
if (await fileExists(bepinexBackupPath)) {
if (await fileExists(`${bepinexBackupPath}/BepInEx`)) {
await fs.cp(`${bepinexBackupPath}/BepInEx`, bepinexFolderPath, { recursive: true })
}
for (const file of bepinexFiles) {
const filePath = `${silksongPath}/${file}`
if (await fileExists(`${bepinexBackupPath}/${file}`)) {
await fs.copyFile(`${bepinexBackupPath}/${file}`, filePath)
}
}
await fs.rm(bepinexBackupPath, { recursive: true })
bepinexBackupVersion = store.get('bepinex-backup-version')
saveBepinexVersion(bepinexBackupVersion)
saveBepinexBackupVersion(undefined)
}
else {
const GITHUB_URL = "https://api.github.com/repos/bepinex/bepinex/releases/latest" const GITHUB_URL = "https://api.github.com/repos/bepinex/bepinex/releases/latest"
const res = await fetch(GITHUB_URL, { const res = await fetch(GITHUB_URL, {
@@ -139,7 +206,6 @@ ipcMain.handle('install-bepinex', async () => {
} }
const release = await res.json(); const release = await res.json();
bepinexVersion = release.tag_name;
const asset = release.assets.find( const asset = release.assets.find(
a => a.name.endsWith(".zip") && a.name.toLowerCase().includes("win_x64") a => a.name.endsWith(".zip") && a.name.toLowerCase().includes("win_x64")
@@ -159,28 +225,59 @@ ipcMain.handle('install-bepinex', async () => {
await extract(filePath, { dir: silksongPath}) await extract(filePath, { dir: silksongPath})
await fs.unlink(filePath) await fs.unlink(filePath)
saveBepinexVersion(bepinexVersion) saveBepinexVersion(release.tag_name)
}
}
ipcMain.handle('install-bepinex', async () => {
await installBepinex()
}) })
ipcMain.handle('uninstall-bepinex', async () => { async function uninstallBepinex() {
const folderPath = `${silksongPath}\\BepInEx` if (await fileExists(bepinexFolderPath)) {
if (await fileExists(folderPath)) { await fs.rm(bepinexFolderPath, { recursive: true })
await fs.rm(folderPath, { recursive: true })
} }
const bepinexFiles = [
".doorstop_version",
"changelog.txt",
"doorstop_config.ini",
"winhttp.dll"
]
for (const file of bepinexFiles) { for (const file of bepinexFiles) {
const filePath = `${silksongPath}\\${file}` const filePath = `${silksongPath}/${file}`
if (await fileExists(filePath)) { if (await fileExists(filePath)) {
await fs.unlink(filePath) await fs.unlink(filePath)
} }
} }
bepinexVersion = undefined saveBepinexVersion(undefined)
saveBepinexVersion(bepinexVersion) }
ipcMain.handle('uninstall-bepinex', async () => {
await uninstallBepinex()
})
async function backupBepinex() {
if (await fileExists(bepinexBackupPath) == false) {
await fs.mkdir(bepinexBackupPath)
}
if (await fileExists(bepinexFolderPath)) {
await fs.cp(bepinexFolderPath, `${bepinexBackupPath}/BepInEx`, { recursive: true })
}
for (const file of bepinexFiles) {
const filePath = `${silksongPath}/${file}`
if (await fileExists(filePath)) {
await fs.copyFile(filePath, `${bepinexBackupPath}/${file}`)
}
}
saveBepinexBackupVersion(bepinexVersion)
await uninstallBepinex()
}
ipcMain.handle('backup-bepinex', async () => {
await backupBepinex()
})
ipcMain.handle('delete-bepinex-backup', async () => {
if (await fileExists(bepinexBackupPath)) {
await fs.rm(bepinexBackupPath, { recursive: true })
saveBepinexBackupVersion(undefined)
}
}) })

View File

@@ -16,14 +16,18 @@ contextBridge.exposeInMainWorld('files', {
saveSilksongPath: (path) => ipcRenderer.invoke('save-path', path), saveSilksongPath: (path) => ipcRenderer.invoke('save-path', path),
loadSilksongPath: () => ipcRenderer.invoke('load-path'), loadSilksongPath: () => ipcRenderer.invoke('load-path'),
saveBepinexVersion: (version) => ipcRenderer.invoke('save-bepinex-version', version), saveBepinexVersion: (version) => ipcRenderer.invoke('save-bepinex-version', version),
loadBepinexVersion: () => ipcRenderer.invoke('load-bepinex-version') loadBepinexVersion: () => ipcRenderer.invoke('load-bepinex-version'),
loadBepinexBackupVersion: () => ipcRenderer.invoke('load-bepinex-backup-version')
}); });
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
openExternalLink: (url) => ipcRenderer.invoke('open-link', url) openExternalLink: (url) => ipcRenderer.invoke('open-link', url),
launchGame: (mode) => ipcRenderer.invoke('launch-game', mode)
}); });
contextBridge.exposeInMainWorld('bepinex', { contextBridge.exposeInMainWorld('bepinex', {
install: () => ipcRenderer.invoke('install-bepinex'), install: () => ipcRenderer.invoke('install-bepinex'),
uninstall: () => ipcRenderer.invoke('uninstall-bepinex') uninstall: () => ipcRenderer.invoke('uninstall-bepinex'),
backup: () => ipcRenderer.invoke('backup-bepinex'),
deleteBackup: () => ipcRenderer.invoke('delete-bepinex-backup')
}) })

View File

@@ -108,6 +108,8 @@
<div class="horizontal-div"> <div class="horizontal-div">
<button class="default-button" onclick="installBepinex()">Install</button> <button class="default-button" onclick="installBepinex()">Install</button>
<button class="important-button" onclick="uninstallBepinex()">Uninstall</button> <button class="important-button" onclick="uninstallBepinex()">Uninstall</button>
<button class="default-button" onclick="backupBepinex()">Backup</button>
<button class="important-button" onclick="deleteBepinexBackup()">Delete Backup</button>
</div> </div>
<br> <br>
<h2>Import/Export</h2> <h2>Import/Export</h2>

View File

@@ -77,8 +77,10 @@ async function navigate(page) {
} }
} }
function launch(mode) { async function launch(mode) {
alert(`Launching the game in ${mode} mode.`); alert(`Launching the game in ${mode} mode.`);
await electronAPI.launchGame(mode);
setBepinexVersion()
} }
async function autoDetectGamePath() { async function autoDetectGamePath() {
@@ -128,12 +130,32 @@ async function uninstallBepinex() {
setBepinexVersion() setBepinexVersion()
} }
async function backupBepinex() {
await bepinex.backup()
setBepinexVersion()
}
async function deleteBepinexBackup() {
await bepinex.deleteBackup()
setBepinexVersion()
}
async function setBepinexVersion() { async function setBepinexVersion() {
const bepinexVersion = await files.loadBepinexVersion()
const bepinexVersionText = document.getElementById("bepinex-version-text") const bepinexVersionText = document.getElementById("bepinex-version-text")
if (bepinexVersionText == undefined) {
return
}
const bepinexVersion = await files.loadBepinexVersion()
const bepinexBackupVersion = await files.loadBepinexBackupVersion()
if(bepinexVersion == undefined) { if(bepinexVersion == undefined) {
if(bepinexBackupVersion == undefined) {
bepinexVersionText.innerText = "BepInEx is not installed" bepinexVersionText.innerText = "BepInEx is not installed"
} }
else {
bepinexVersionText.innerText = `BepInEx ${bepinexBackupVersion} is backuped`
}
}
else { else {
bepinexVersionText.innerText = `BepInEx ${bepinexVersion} is installed` bepinexVersionText.innerText = `BepInEx ${bepinexVersion} is installed`
} }