From 9ffc04072343a59315decbf59ca9d6307486761d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:16:09 -0600 Subject: [PATCH] feat: implement all methods --- packages/core/src/blitz/blitzVFS.ts | 25 ------ packages/scripts/src/buildAssets/constants.ts | 35 ++++++-- packages/scripts/src/core/vfs/abstract.ts | 85 ++++++++++++++++++- packages/scripts/src/core/vfs/steam.ts | 70 ++------------- packages/scripts/src/core/vfs/zip.ts | 21 ++++- 5 files changed, 134 insertions(+), 102 deletions(-) delete mode 100644 packages/core/src/blitz/blitzVFS.ts diff --git a/packages/core/src/blitz/blitzVFS.ts b/packages/core/src/blitz/blitzVFS.ts deleted file mode 100644 index 776381056..000000000 --- a/packages/core/src/blitz/blitzVFS.ts +++ /dev/null @@ -1,25 +0,0 @@ -export enum BlitzVFSSourceType { - Steam, -} - -type BlitzVFSSource = { - type: BlitzVFSSourceType; - - username: string; - password: string; - - app: number; - os: "windows"; -}; - -export class BlitzVFS { - constructor(private source: BlitzVFSSource) {} - - async init() { - return this; - } - - async file(path: string) { - return new Uint8Array(); - } -} diff --git a/packages/scripts/src/buildAssets/constants.ts b/packages/scripts/src/buildAssets/constants.ts index c7dc2016e..8ac0f3283 100644 --- a/packages/scripts/src/buildAssets/constants.ts +++ b/packages/scripts/src/buildAssets/constants.ts @@ -1,12 +1,29 @@ import { assertSecret } from "@blitzkit/core"; -import { SteamVFS } from "../core/vfs/vfs"; +import { AbstractVFS } from "../core/vfs/abstract"; +import { SteamVFS } from "../core/vfs/steam"; +import { ZipVFS } from "../core/vfs/zip"; -const APP = 444200; -const DEPOT = 444202; +const source = Bun.argv + .find((arg) => arg.startsWith("--source=")) + ?.split("=")[1]; -export const vfs = await new SteamVFS( - assertSecret(import.meta.env.STEAM_USERNAME), - assertSecret(import.meta.env.STEAM_PASSWORD), - APP, - DEPOT -).init(); +let resolved: AbstractVFS; + +if (source === "steam") { + const APP = 444200; + const DEPOT = 444202; + + resolved = await new SteamVFS( + assertSecret(import.meta.env.STEAM_USERNAME), + assertSecret(import.meta.env.STEAM_PASSWORD), + APP, + DEPOT + ).init(); +} else if (source === "zip") { + const url = assertSecret(import.meta.env.ZIP_URL); + resolved = await new ZipVFS(url).init(); +} else { + throw new Error("No valid source specified"); +} + +export const vfs = resolved; diff --git a/packages/scripts/src/core/vfs/abstract.ts b/packages/scripts/src/core/vfs/abstract.ts index 5f32edbcc..a43dd18f5 100644 --- a/packages/scripts/src/core/vfs/abstract.ts +++ b/packages/scripts/src/core/vfs/abstract.ts @@ -1,3 +1,86 @@ +import { readDVPL } from "@blitzkit/core"; +import { XMLParser } from "fast-xml-parser"; +import { normalize } from "path/posix"; +import { parse } from "yaml"; + export abstract class AbstractVFS { - abstract init(): Promise; + textDecoder = new TextDecoder(); + private xmlParser = new XMLParser(); + + abstract init(): Promise; + + abstract dispose(): void; + + abstract has(path: string): boolean; + + abstract raw(path: string): Promise; + + abstract paths(): string[]; + + resolve(path: string) { + const normalized = normalize(path); + if (this.has(normalized)) return normalized; + + const dvplPath = `${path}.dvpl`; + if (this.has(dvplPath)) return dvplPath; + + return null; + } + + assert(path: string) { + const resolved = this.resolve(path); + + if (!resolved) throw new Error(`File not found: ${path}`); + + return resolved; + } + + async file(file: string) { + const resolved = this.assert(file); + const raw = await this.raw(resolved); + let buffer = raw; + + if (resolved.endsWith(".dvpl")) { + buffer = new Uint8Array(readDVPL(Buffer.from(raw))); + } + + return buffer; + } + + dir(path: string) { + const parentSegments = path.split("/").length; + const children: string[] = []; + + for (const [child] of this.paths()) { + const childSegments = child.split("/"); + + if ( + child.startsWith(path) && + parentSegments + 1 === childSegments.length + ) { + children.push(childSegments.at(-1)!); + } + } + + return children; + } + + async text(path: string) { + const file = await this.file(path); + return this.textDecoder.decode(file); + } + + async yaml(path: string) { + const file = await this.text(path); + return parse(file) as Type; + } + + async xml(path: string) { + const file = await this.text(path); + return this.xmlParser.parse(file) as Type; + } + + [Symbol.dispose]() { + this.dispose(); + } } diff --git a/packages/scripts/src/core/vfs/steam.ts b/packages/scripts/src/core/vfs/steam.ts index 013a42b19..a1432d382 100644 --- a/packages/scripts/src/core/vfs/steam.ts +++ b/packages/scripts/src/core/vfs/steam.ts @@ -1,8 +1,4 @@ -import { readDVPL } from "@blitzkit/core/src/blitz"; -import { XMLParser } from "fast-xml-parser"; -import { normalize } from "path/posix"; import SteamUser, { EConnectionProtocol } from "steam-user"; -import { parse } from "yaml"; import { AbstractVFS } from "./abstract"; interface SteamManifestFile { @@ -36,9 +32,6 @@ interface SteamManifest { export class SteamVFS extends AbstractVFS { private steam = new SteamUser({ protocol: EConnectionProtocol.TCP }); - private textDecoder = new TextDecoder(); - private xmlParser = new XMLParser(); - private manifest: Map = new Map(); constructor( @@ -74,10 +67,8 @@ export class SteamVFS extends AbstractVFS { ); }); - const paths: string[] = []; for (const file of manifest.files) { const path = file.filename.replaceAll("\\", "/"); - paths.push(path); this.manifest.set(path, file); } @@ -85,74 +76,23 @@ export class SteamVFS extends AbstractVFS { } has(path: string) { - const normalized = normalize(path); - const dvplPath = `${normalized}.dvpl`; - - return ( - (this.manifest.has(dvplPath) && dvplPath) || - (this.manifest.has(normalized) && normalized) - ); + return this.manifest.has(path); } - assert(requested: string) { - const path = this.has(requested); - - if (!path) throw new Error(`File not found: ${requested}`); - - return path; - } - - dir(path: string) { - const parentSegments = path.split("/").length; - const children: string[] = []; - - for (const child of this.manifest.keys()) { - const childSegments = child.split("/"); - - if ( - child.startsWith(path) && - parentSegments + 1 === childSegments.length - ) { - children.push(childSegments.at(-1)!); - } - } - - return children; - } - - async file(requested: string) { - const path = this.assert(requested); + async raw(path: string) { const fileManifest = this.manifest.get(path)!; const downloaded: { type: "complete"; file: Buffer } = // @ts-expect-error await this.steam.downloadFile(this.app, this.depot, fileManifest); - let buffer = downloaded.file; - if (path.endsWith(".dvpl")) buffer = readDVPL(downloaded.file); - - return new Uint8Array(buffer); + return new Uint8Array(downloaded.file); } - async text(path: string) { - const file = await this.file(path); - return this.textDecoder.decode(file); - } - - async yaml(path: string) { - const file = await this.text(path); - return parse(file) as Type; - } - - async xml(path: string) { - const file = await this.text(path); - return this.xmlParser.parse(file) as Type; + paths() { + return Array.from(this.manifest.keys()); } dispose() { this.steam.logOff(); } - - [Symbol.dispose]() { - this.dispose(); - } } diff --git a/packages/scripts/src/core/vfs/zip.ts b/packages/scripts/src/core/vfs/zip.ts index 083311b22..89758e2a7 100644 --- a/packages/scripts/src/core/vfs/zip.ts +++ b/packages/scripts/src/core/vfs/zip.ts @@ -1,8 +1,9 @@ +import { normalize } from "path/posix"; import { HTTPRangeReader, unzip, ZipEntry } from "unzipit"; import { AbstractVFS } from "./abstract"; export class ZipVFS extends AbstractVFS { - entries: Record = {}; + entries = new Map(); constructor(private url: string, private root = "app/") { super(); @@ -13,9 +14,25 @@ export class ZipVFS extends AbstractVFS { const { entries } = await unzip(reader); for (const entry of Object.values(entries)) { - this.entries[entry.name.slice(this.root.length)] = entry; + const normalized = normalize(entry.name.slice(this.root.length)); + this.entries.set(normalized, entry); } return this; } + + has(path: string) { + return path in this.entries; + } + + async raw(path: string) { + const arrayBuffer = await this.entries.get(path)!.arrayBuffer(); + return new Uint8Array(arrayBuffer); + } + + paths() { + return Array.from(this.entries.keys()); + } + + dispose() {} }