we can now download and install mods from the back-end

This commit is contained in:
MathieuG-P
2022-10-01 02:56:15 +02:00
parent e75cd44872
commit a43e238905
9 changed files with 102 additions and 12 deletions
+5
View File
@@ -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",
+1
View File
@@ -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 -1
View File
@@ -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"
}
+18 -1
View File
@@ -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 -1
View File
@@ -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; }
+3 -1
View File
@@ -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