Merge branch 'main' into dev

This commit is contained in:
TrèsAbhi committed 2025-11-27 17:16:12 -06:00
commit 54b73d11ce
27 files changed
+203 -199

No files matched your search

+2
View File
@@ -14,6 +14,7 @@
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
"@types/progress": "^2.0.7",
"@types/steam-user": "^5.1.0",
"@types/three": "^0.180.0",
"crc": "^4.3.2",
"csv-parse": "^6.1.0",
@@ -27,6 +28,7 @@
"progress": "^2.0.3",
"puppeteer": "^24.22.3",
"sharp": "^0.34.4",
"steam-user": "^5.2.3",
"three": "^0.180.0",
"ts-proto": "^2.7.7",
"yaml": "^2.8.1"
@@ -1,4 +1,5 @@
import { assertSecret, SteamVFS } from "@blitzkit/core";
import { assertSecret } from "@blitzkit/core";
import { SteamVFS } from "../core/steam/vfs";
const APP = 444200;
const DEPOT = 444202;
@@ -2,11 +2,11 @@ import {
Hierarchy,
Sc2ReadStream,
ScgReadStream,
SteamVFS,
VertexAttribute,
} from "@blitzkit/core";
import { Accessor, Document, Node, Scene } from "@gltf-transform/core";
import { times } from "lodash-es";
import { SteamVFS } from "../steam/vfs";
import {
vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes,
@@ -2,7 +2,6 @@ import {
Hierarchy,
Sc2ReadStream,
ScgReadStream,
SteamVFS,
Textures,
VertexAttribute,
} from "@blitzkit/core";
@@ -10,6 +9,7 @@ import { Document, Material, Node, Scene } from "@gltf-transform/core";
import { dedup, prune } from "@gltf-transform/functions";
import { times } from "lodash-es";
import { dirname } from "path";
import { SteamVFS } from "../../steam/vfs";
import { readBaseColor } from "../readBaseColor";
import { readNormal } from "../readNormal";
import { readRoughnessMetallic } from "../readRoughnessMetallic";
+155
View File
@@ -0,0 +1,155 @@
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";
interface SteamManifestFile {
chunks: {
sha: string;
crc: number;
offset: string;
cb_original: number;
cb_compressed: number;
}[];
filename: string;
size: string;
flags: number;
sha_filename: string;
sha_content: string;
linktarget: unknown;
}
interface SteamManifest {
files: SteamManifestFile[];
depot_id: number;
gid_manifest: string;
creation_time: number;
filenames_encrypted: boolean;
cb_disk_original: string;
cb_disk_compressed: string;
unique_chunks: number;
crc_encrypted: number;
crc_clear: number;
}
export class SteamVFS {
private steam = new SteamUser({ protocol: EConnectionProtocol.TCP });
private textDecoder = new TextDecoder();
private xmlParser = new XMLParser();
private manifest: Map<string, SteamManifestFile> = new Map();
constructor(
private username: string,
private password: string,
private app: number,
private depot: number
) {}
async init() {
this.steam.logOn({
accountName: this.username,
password: this.password,
});
await new Promise((resolve) => this.steam.once("loggedOn", resolve));
const productInfo = await this.steam.getProductInfo([this.app], []);
const manifest: SteamManifest = await new Promise((resolve) => {
// @ts-expect-error
this.steam.getManifest(
this.app,
this.depot,
productInfo.apps[this.app].appinfo.depots[this.depot].manifests.public
.gid,
"public",
// @ts-expect-error
(_, response) => {
resolve(response);
}
);
});
const paths: string[] = [];
for (const file of manifest.files) {
const path = file.filename.replaceAll("\\", "/");
paths.push(path);
this.manifest.set(path, file);
}
return this;
}
has(path: string) {
const normalized = normalize(path);
const dvplPath = `${normalized}.dvpl`;
return (
(this.manifest.has(dvplPath) && dvplPath) ||
(this.manifest.has(normalized) && normalized)
);
}
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);
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);
}
async text(path: string) {
const file = await this.file(path);
return this.textDecoder.decode(file);
}
async yaml<Type>(path: string) {
const file = await this.text(path);
return parse(file) as Type;
}
async xml<Type>(path: string) {
const file = await this.text(path);
return this.xmlParser.parse(file) as Type;
}
dispose() {
this.steam.logOff();
}
[Symbol.dispose]() {
this.dispose();
}
}