mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
we can now download and install mods from the back-end
This commit is contained in:
Generated
+5
@@ -9394,6 +9394,11 @@
|
||||
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node-stream-zip": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz",
|
||||
"integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="
|
||||
},
|
||||
"nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
"history": "^5.3.0",
|
||||
"is-online": "^9.0.1",
|
||||
"md5-file": "^5.0.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "BSManager",
|
||||
"name": "bs-manager",
|
||||
"version": "0.1.0",
|
||||
"description": "A foundation for scalable desktop apps",
|
||||
"main": "./dist/main/main.js",
|
||||
|
||||
@@ -90,11 +90,16 @@ export class BSLocalVersionService{
|
||||
return seq.replace( /[<>:"\/\\|?*]+/g, '' );
|
||||
}
|
||||
|
||||
public getVersionFolder(version: BSVersion){
|
||||
return version.name ? `${version.BSVersion}-${version.name}` : version.BSVersion;
|
||||
}
|
||||
public getVersionFolder(version: BSVersion){
|
||||
return version.name ? `${version.BSVersion}-${version.name}` : version.BSVersion;
|
||||
}
|
||||
|
||||
public async getInstalledVersions(): Promise<BSVersion[]>{
|
||||
public getVersionType(version: BSVersion): "steam"|"universal"{
|
||||
if(version.steam){ return "steam"; }
|
||||
return "universal"
|
||||
}
|
||||
|
||||
public async getInstalledVersions(): Promise<BSVersion[]>{
|
||||
const versions: BSVersion[] = [];
|
||||
const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber")
|
||||
if(steamBsFolder && this.utilsService.pathExist(steamBsFolder)){
|
||||
|
||||
@@ -6,12 +6,13 @@ export class BeatModsApiService {
|
||||
|
||||
private static instance: BeatModsApiService;
|
||||
|
||||
private readonly requestService: RequestService
|
||||
private readonly requestService: RequestService;
|
||||
|
||||
private readonly BEAT_MODS_VERSIONS = "https://versions.beatmods.com/versions.json";
|
||||
private readonly BEAT_MODS_ALIAS = "https://alias.beatmods.com/aliases.json";
|
||||
|
||||
private readonly BEAT_MODS_API_URL = "https://beatmods.com/api/v1/";
|
||||
public readonly BEAT_MODS_URL = "https://beatmods.com";
|
||||
|
||||
private readonly aliasesCache = new Map<string, BSVersion[]>();
|
||||
private readonly versionModsCache = new Map<string, Mod[]>();
|
||||
|
||||
@@ -5,14 +5,17 @@ import path from "path";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import md5File from "md5-file";
|
||||
import fs from "fs"
|
||||
import StreamZip from "node-stream-zip";
|
||||
import { RequestService } from "../request.service";
|
||||
|
||||
export class BsModsManagerService {
|
||||
|
||||
private static instance: BsModsManagerService;
|
||||
|
||||
private beatModsApi: BeatModsApiService;
|
||||
private bsLocalService: BSLocalVersionService;
|
||||
private utilsService: UtilsService;
|
||||
private readonly beatModsApi: BeatModsApiService;
|
||||
private readonly bsLocalService: BSLocalVersionService;
|
||||
private readonly utilsService: UtilsService;
|
||||
private readonly requestService: RequestService
|
||||
|
||||
private manifestMatches: Mod[];
|
||||
|
||||
@@ -25,6 +28,7 @@ export class BsModsManagerService {
|
||||
this.beatModsApi = BeatModsApiService.getInstance();
|
||||
this.bsLocalService = BSLocalVersionService.getInstance();
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
this.requestService = RequestService.getInstance();
|
||||
}
|
||||
|
||||
private async getModFromHash(hash: string): Promise<Mod>{
|
||||
@@ -81,6 +85,59 @@ export class BsModsManagerService {
|
||||
return this.getIpaFromHash(injectorMd5);
|
||||
}
|
||||
|
||||
private downloadZip(zipUrl: string): Promise<StreamZip.StreamZipAsync>{
|
||||
zipUrl = path.join(this.beatModsApi.BEAT_MODS_URL, zipUrl);
|
||||
const fileName = path.basename(zipUrl);
|
||||
const tempPath = this.utilsService.getTempPath();
|
||||
this.utilsService.createFolderIfNotExist(this.utilsService.getTempPath());
|
||||
const dest = path.join(tempPath, fileName);
|
||||
return this.requestService.downloadFile(zipUrl, dest).then(zipPath => new StreamZip.async({file : zipPath}));
|
||||
}
|
||||
|
||||
private executeBSIPA(version: BSVersion){
|
||||
|
||||
}
|
||||
|
||||
private installBSIPA(mod: Mod, version: BSVersion){
|
||||
this.installMod(mod, version).then(res => {
|
||||
this.executeBSIPA(version); //TODO
|
||||
})
|
||||
}
|
||||
|
||||
public async installMod(mod: Mod, version: BSVersion): Promise<boolean>{
|
||||
const download = mod.downloads.find(download => {
|
||||
const type = download.type.toLowerCase()
|
||||
return type === "universal" || type === this.bsLocalService.getVersionType(version);
|
||||
});
|
||||
|
||||
if(!download){ return false; }
|
||||
|
||||
const zip = await this.downloadZip(download.url);
|
||||
|
||||
if(!zip){ return false; }
|
||||
|
||||
const crypto = require('crypto');
|
||||
const entries = await zip.entries();
|
||||
|
||||
const checkedEntries = (await Promise.all(Object.values(entries).map(async (entry) => {
|
||||
if(!entry.isFile){ return undefined; }
|
||||
const data = await zip.entryData(entry);
|
||||
const entryMd5 = crypto.createHash('md5').update(data).digest('hex')
|
||||
return download.hashMd5.some(md5 => md5.hash === entryMd5) ? entry : undefined;
|
||||
}))).filter(entry => !!entry);
|
||||
|
||||
if(checkedEntries.length != download.hashMd5.length){ return false; }
|
||||
|
||||
const verionPath = await this.bsLocalService.getVersionPath(version);
|
||||
if(!this.utilsService.pathExist(verionPath)){ return false; }
|
||||
|
||||
const destDir = mod.name.toLowerCase() === "bsipa" ? verionPath : path.join(verionPath, ModsInstallFolder.PENDING);
|
||||
|
||||
await zip.extract(null, destDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public getAvailableMods(version: BSVersion): Promise<Mod[]>{
|
||||
return this.beatModsApi.getVersionMods(version);
|
||||
}
|
||||
@@ -116,6 +173,7 @@ export class BsModsManagerService {
|
||||
const enum ModsInstallFolder {
|
||||
PLUGINS = "Plugins",
|
||||
LIBS = "Libs",
|
||||
PENDING = "IPA/Pending",
|
||||
PLUGINS_PENDING = "IPA/Pending/Plugins",
|
||||
LIBS_PENDING = "IPA/Pending/Libs"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { get } from "https";
|
||||
import { createWriteStream, unlink } from "fs";
|
||||
|
||||
export class RequestService {
|
||||
|
||||
@@ -22,6 +23,22 @@ export class RequestService {
|
||||
res.on('error', (err) => reject(err))
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public downloadFile(url: string, dest: string): Promise<string>{
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = createWriteStream(dest);
|
||||
get(url, res => {
|
||||
res.pipe(file);
|
||||
file.on("close", () => {
|
||||
file.close(() => resolve(dest))
|
||||
}).on("error", err => {
|
||||
unlink(dest, () => reject(err));
|
||||
});
|
||||
}).on("error", err => {
|
||||
unlink(dest, () => reject(err));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFile } from "fs";
|
||||
import { spawnSync } from "child_process";
|
||||
import { homedir } from "os";
|
||||
import path from "path";
|
||||
import { BrowserWindow } from "electron";
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import { rm } from "fs/promises";
|
||||
import { IpcResponse } from "shared/models/ipc";
|
||||
import log from "electron-log";
|
||||
@@ -27,6 +27,7 @@ export class UtilsService{
|
||||
|
||||
public getAssetsScriptsPath(): string { return this.getAssetsPath("scripts") }
|
||||
public getAssestsJsonsPath(): string { return this.getAssetsPath("jsons"); }
|
||||
public getTempPath(): string{ return path.join(app.getPath("temp"), app.getName()) }
|
||||
|
||||
public setMainWindow(win: BrowserWindow){ this.mainWindow = win; }
|
||||
public getMainWindow(){ return this.mainWindow; }
|
||||
|
||||
@@ -24,11 +24,13 @@ export interface ModAuthor {
|
||||
}
|
||||
|
||||
export interface DownloadLink {
|
||||
type: string,
|
||||
type: DownloadLinkType,
|
||||
url: string,
|
||||
hashMd5: FileHashes[]
|
||||
}
|
||||
|
||||
export type DownloadLinkType = "universal"|"steam"|"oculus";
|
||||
|
||||
export interface FileHashes {
|
||||
hash: string,
|
||||
file: string
|
||||
|
||||
Reference in New Issue
Block a user