diff --git a/assets/jsons/bs-versions.json b/assets/jsons/bs-versions.json index 7ef77153..ff01381d 100644 --- a/assets/jsons/bs-versions.json +++ b/assets/jsons/bs-versions.json @@ -840,5 +840,14 @@ "ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/648716b53c17412cd269567ce67d3867514b6acd.png", "ReleaseDate": "1740672033", "year": "2025" + }, + { + "BSVersion": "1.40.4", + "BSManifest": "3323243802033113443", + "OculusBinaryId": "8889412401158460", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510702041542166484", + "ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/c27dac7f96e53672d020d97838cec1d5d28e3180.png", + "ReleaseDate": "1743515622", + "year": "2025" } ] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8ac2062..f1c3bf53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,10 +27,10 @@ "electron-updater": "^6.3.9", "fast-deep-equal": "^3.1.3", "format-duration": "^3.0.2", - "framer-motion": "^12.5.0", + "framer-motion": "^12.6.3", "fs-extra": "^11.3.0", "global-agent": "^3.0.0", - "got": "^14.4.6", + "got": "^14.4.7", "history": "^5.3.0", "is-elevated": "^4.0.0", "md5-file": "^5.0.0", @@ -105,7 +105,7 @@ "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", "detect-port": "^1.5.1", - "electron": "^35.0.1", + "electron": "^35.1.4", "electron-builder": "^25.1.8", "electron-devtools-installer": "^4.0.0", "electronmon": "^2.0.3", @@ -11870,9 +11870,9 @@ } }, "node_modules/electron": { - "version": "35.0.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-35.0.1.tgz", - "integrity": "sha512-iQonj6lnPhqfqha2KXx6LzV1dnu6UPTCWK+b7f9Zvg828umGemi22DKbcJ3/q+Opn7iUVTWyqp9z1JQqkIi6OA==", + "version": "35.1.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz", + "integrity": "sha512-8HjE2wqxY//T09Of8k1eTpK/NeTG2FkTyRD+fyKXmec4wZVscGgZcmWFC0HYN4ktyHAjtplpxdFXjtqRnvzBMg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14433,13 +14433,13 @@ } }, "node_modules/framer-motion": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.5.0.tgz", - "integrity": "sha512-buPlioFbH9/W7rDzYh1C09AuZHAk2D1xTA1BlounJ2Rb9aRg84OXexP0GLd+R83v0khURdMX7b5MKnGTaSg5iA==", + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz", + "integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==", "license": "MIT", "dependencies": { - "motion-dom": "^12.5.0", - "motion-utils": "^12.5.0", + "motion-dom": "^12.6.3", + "motion-utils": "^12.6.3", "tslib": "^2.4.0" }, "peerDependencies": { @@ -14869,9 +14869,9 @@ } }, "node_modules/got": { - "version": "14.4.6", - "resolved": "https://registry.npmjs.org/got/-/got-14.4.6.tgz", - "integrity": "sha512-rnhwfM/PhMNJ1i17k3DuDqgj0cKx3IHxBKVv/WX1uDKqrhi2Gv3l7rhPThR/Cc6uU++dD97W9c8Y0qyw9x0jag==", + "version": "14.4.7", + "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz", + "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==", "license": "MIT", "dependencies": { "@sindresorhus/is": "^7.0.1", @@ -19233,18 +19233,18 @@ } }, "node_modules/motion-dom": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.5.0.tgz", - "integrity": "sha512-uH2PETDh7m+Hjd1UQQ56yHqwn83SAwNjimNPE/kC+Kds0t4Yh7+29rfo5wezVFpPOv57U4IuWved5d1x0kNhbQ==", + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz", + "integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==", "license": "MIT", "dependencies": { - "motion-utils": "^12.5.0" + "motion-utils": "^12.6.3" } }, "node_modules/motion-utils": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.5.0.tgz", - "integrity": "sha512-+hFFzvimn0sBMP9iPxBa9OtRX35ZQ3py0UHnb8U29VD+d8lQ8zH3dTygJWqK7av2v6yhg7scj9iZuvTS0f4+SA==", + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz", + "integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==", "license": "MIT" }, "node_modules/mrmime": { diff --git a/package.json b/package.json index 9659245b..af9a41a9 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", "detect-port": "^1.5.1", - "electron": "^35.0.1", + "electron": "^35.1.4", "electron-builder": "^25.1.8", "electron-devtools-installer": "^4.0.0", "electronmon": "^2.0.3", @@ -170,10 +170,10 @@ "electron-updater": "^6.3.9", "fast-deep-equal": "^3.1.3", "format-duration": "^3.0.2", - "framer-motion": "^12.5.0", + "framer-motion": "^12.6.3", "fs-extra": "^11.3.0", "global-agent": "^3.0.0", - "got": "^14.4.6", + "got": "^14.4.7", "history": "^5.3.0", "is-elevated": "^4.0.0", "md5-file": "^5.0.0", diff --git a/src/main/services/additional-content/maps/local-maps-manager.service.ts b/src/main/services/additional-content/maps/local-maps-manager.service.ts index 168a2f21..4a4f31e9 100644 --- a/src/main/services/additional-content/maps/local-maps-manager.service.ts +++ b/src/main/services/additional-content/maps/local-maps-manager.service.ts @@ -369,10 +369,12 @@ export class LocalMapsManagerService { for (const folder of mapsFolders) { log.info(">", folder); - const regex = new RegExp(`^${escapeRegExp(folder)}\\/`); + const entriesNames = isRoot + ? null + : [ new RegExp(`^${escapeRegExp(folder)}\\/`) ]; const exported = await zip.extract(destination, { - entriesNames: [regex], + entriesNames, abortToken: abortController }); @@ -428,9 +430,10 @@ export class LocalMapsManagerService { throw new Error("Cannot download map, no hash found"); } - log.info("Downloading map", map.name, map.id); - const zipUrl = map.versions.at(0).downloadURL; + + log.info("Downloading map", map.name, map.id, zipUrl); + const mapFolderName = sanitize(`${map.id} (${map.metadata.songName} - ${map.metadata.levelAuthorName})`); const mapsFolder = await this.getMapsFolderPath(version); diff --git a/src/main/services/mods/bs-mods-manager.service.ts b/src/main/services/mods/bs-mods-manager.service.ts index 2314bd3b..089fa2df 100644 --- a/src/main/services/mods/bs-mods-manager.service.ts +++ b/src/main/services/mods/bs-mods-manager.service.ts @@ -117,7 +117,7 @@ export class BsModsManagerService { } private async downloadZip(zipUrl: string): Promise { - zipUrl = path.join(this.beatModsApi.MODS_REPO_URL, zipUrl); + zipUrl = new URL(zipUrl, this.beatModsApi.MODS_REPO_URL).href; log.info("Download mod zip", zipUrl); diff --git a/src/main/services/request.service.ts b/src/main/services/request.service.ts index 6a07a2e7..a8c19887 100644 --- a/src/main/services/request.service.ts +++ b/src/main/services/request.service.ts @@ -9,12 +9,10 @@ import { tryit } from 'shared/helpers/error.helpers'; import path from 'path'; import { pipeline } from 'stream/promises'; import sanitize from 'sanitize-filename'; -import internal from 'stream'; import { app } from 'electron'; export class RequestService { private static instance: RequestService; - private preferredFamily: number | undefined = undefined; private readonly baseHeaders = { 'User-Agent': `BSManager/${app.getVersion()} (Electron/${process.versions.electron} Chrome/${process.versions.chrome} Node/${process.versions.node})`, } @@ -32,23 +30,14 @@ export class RequestService { public async getJSON(url: string): Promise<{ data: T; headers: IncomingHttpHeaders }> { - const familiesToTry = this.preferredFamily ? [this.preferredFamily, this.preferredFamily === 4 ? 6 : 4] : [4, 6]; - - for (const family of familiesToTry) { - try { - - // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) - const res = await got(url, { dnsLookupIpVersion: family, responseType: 'json', headers: this.baseHeaders }); - this.preferredFamily = family; - return { data: res.body as T, headers: res.headers }; - } catch (err) { - log.warn(`IPv${family} request failed, trying next one... URL: ${url}`, err); - } + try { + // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) + const res = await got(url, { responseType: 'json', headers: this.baseHeaders }); + return { data: res.body as T, headers: res.headers }; + } catch (err) { + log.error(`Failed to get JSON from URL: ${url}`, err); + throw err; } - - log.error(`IPv4 and IPv6 requests failed for URL: ${url}`); - this.preferredFamily = undefined; - throw new Error(`IPv4 and IPv6 requests failed for URL: ${url}`); } public downloadFile( @@ -58,63 +47,47 @@ export class RequestService { ): Observable> { return new Observable>((subscriber) => { const progress: Progression = { current: 0, total: 0 }; - const familiesToTry = this.preferredFamily ? [this.preferredFamily, this.preferredFamily === 4 ? 6 : 4] : [4, 6]; - let attempt = 0; - let stream: got.GotEmitter & internal.Duplex; + let file: WriteStream | undefined; - const tryNextFamily = () => { - if (attempt >= familiesToTry.length) { - subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`)); - return; + // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) + const stream = got.stream(url, { headers: this.baseHeaders }); + + stream.on('response', (response) => { + + const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null; + + if (filename) { + dest = path.join(path.dirname(dest), sanitize(filename)); } - const family = familiesToTry[attempt++]; - let file: WriteStream | undefined; + progress.data = dest; + file = createWriteStream(dest); - // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) - stream = got.stream(url, { dnsLookupIpVersion: family, headers: this.baseHeaders }); - - stream.on('response', (response) => { - this.preferredFamily = family; - - const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null; - - if (filename) { - dest = path.join(path.dirname(dest), sanitize(filename)); - } - - progress.data = dest; - file = createWriteStream(dest); - - pipeline(stream, file).catch(err => { - file?.destroy(); - tryit(() => deleteFileSync(dest)); - subscriber.error(err); - }); - }); - - stream.on('downloadProgress', ({ transferred, total }) => { - progress.current = transferred; - progress.total = total; - subscriber.next(progress); - }); - - stream.on('error', err => { - log.warn(`Download failed over IPv${family} for URL: ${url}`, err); - stream.destroy(); + pipeline(stream, file).catch(err => { file?.destroy(); - tryNextFamily(); + tryit(() => deleteFileSync(dest)); + subscriber.error(err); }); + }); - stream.on('end', () => { - file?.end(); - subscriber.next(progress); - subscriber.complete(); - }); - }; + stream.on('downloadProgress', ({ transferred, total }) => { + progress.current = transferred; + progress.total = total; + subscriber.next(progress); + }); - tryNextFamily(); + stream.on('error', err => { + log.error(`Download failed for URL: ${url}`, err); + stream.destroy(); + file?.destroy(); + }); + + stream.on('end', () => { + file?.end(); + subscriber.next(progress); + subscriber.complete(); + }); return () => { stream?.destroy(); @@ -129,6 +102,7 @@ export class RequestService { url: string, options?: got.GotOptions ): Observable> { + return new Observable>((subscriber) => { const progress: Progression = { current: 0, @@ -137,54 +111,38 @@ export class RequestService { }; const headers = { ...this.baseHeaders, ...(options?.headers ?? {}) }; - const familiesToTry = this.preferredFamily ? [this.preferredFamily, this.preferredFamily === 4 ? 6 : 4] : [4, 6]; - let attempt = 0; - let stream: got.GotEmitter & internal.Duplex; + // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) + const stream = got.stream(url, { ...(options ?? {}), headers }); - const tryNextFamily = () => { - if (attempt >= familiesToTry.length) { - subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`)); - return; - } + let data = Buffer.alloc(0); + let response: IncomingMessage; - const family = familiesToTry[attempt++]; - // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) - stream = got.stream(url, { dnsLookupIpVersion: family, ...(options ?? {}), headers }); + stream.once('response', (res) => { + response = res; + }); - let data = Buffer.alloc(0); - let response: IncomingMessage; + stream.on('data', (chunk: Buffer) => { + data = Buffer.concat([data, chunk]); + }); - stream.once('response', (res) => { - this.preferredFamily = family; - response = res; - }); + stream.on('downloadProgress', ({ transferred, total }) => { + progress.current = transferred; + progress.total = total; + subscriber.next(progress); + }); - stream.on('data', (chunk: Buffer) => { - data = Buffer.concat([data, chunk]); - }); + stream.on('error', err => { + log.error(`Download failed for URL: ${url}`, err); + stream.destroy(); + }); - stream.on('downloadProgress', ({ transferred, total }) => { - progress.current = transferred; - progress.total = total; - subscriber.next(progress); - }); - - stream.on('error', err => { - log.warn(`Download failed over IPv${family} for URL: ${url}`, err); - stream.destroy(); - tryNextFamily(); - }); - - stream.on('end', () => { - progress.data = data; - progress.extra = response; - subscriber.next(progress); - subscriber.complete(); - }); - }; - - tryNextFamily(); + stream.on('end', () => { + progress.data = data; + progress.extra = response; + subscriber.next(progress); + subscriber.complete(); + }); return () => { stream?.destroy();