From c61624bc929ddcb5806915208a253a1496bcb369 Mon Sep 17 00:00:00 2001 From: ktkk Date: Fri, 3 Apr 2026 09:52:05 +0000 Subject: [PATCH] Initial commit --- deno.json | 10 +++++ deno.lock | 42 ++++++++++++++++++++ main.ts | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100755 deno.json create mode 100755 deno.lock create mode 100755 main.ts diff --git a/deno.json b/deno.json new file mode 100755 index 0000000..af121cd --- /dev/null +++ b/deno.json @@ -0,0 +1,10 @@ +{ + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "@std/assert": "jsr:@std/assert@1", + "@std/cli": "jsr:@std/cli@^1.0.28", + "@zip-js/zip-js": "jsr:@zip-js/zip-js@^2.8.26" + } +} diff --git a/deno.lock b/deno.lock new file mode 100755 index 0000000..a131b6b --- /dev/null +++ b/deno.lock @@ -0,0 +1,42 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/assert@1": "1.0.19", + "jsr:@std/cli@*": "1.0.28", + "jsr:@std/cli@^1.0.28": "1.0.28", + "jsr:@std/fmt@^1.0.9": "1.0.9", + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@zip-js/zip-js@^2.8.26": "2.8.26" + }, + "jsr": { + "@std/assert@1.0.19": { + "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/cli@1.0.28": { + "integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a", + "dependencies": [ + "jsr:@std/fmt", + "jsr:@std/internal" + ] + }, + "@std/fmt@1.0.9": { + "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@zip-js/zip-js@2.8.26": { + "integrity": "2b78bfa2c5d43766c9a487fc0274981d45732de41bb5b82433d29d235be67191" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1", + "jsr:@std/cli@^1.0.28", + "jsr:@zip-js/zip-js@^2.8.26" + ] + } +} diff --git a/main.ts b/main.ts new file mode 100755 index 0000000..5af11ef --- /dev/null +++ b/main.ts @@ -0,0 +1,114 @@ +import { parseArgs } from "jsr:@std/cli/parse-args"; +import { + BlobReader, + BlobWriter, + ZipReader, +} from "@zip-js/zip-js"; + +async function* parseResponseChunked(response: Response) { + const lengthHeader = response.headers.get('Content-Length'); + if (!lengthHeader) { + throw Error("Invalid header"); + } + const length = parseInt(lengthHeader); + if (!length) { + throw Error('Content-Length header not found'); + } + const buffer = new ArrayBuffer(length); + const view = new DataView(buffer); + const responseBody = response.body; + if (!responseBody) { + throw Error("Invalid body"); + } + const reader = responseBody.getReader(); + let offset = 0; + let a = 0; + let s = 0; + for (; ;) { + const { done, value } = await reader.read(); + if (done) { + if (offset < length) throw Error('Content-Length mismatch'); + break; + } + + if (!value) { + continue; + } + + new Uint8Array(buffer, offset, value.length).set(value); + offset += value.length; + if (offset > length) { + throw Error('Content-Length mismatch'); + } + for (; offset - a >= 4 && (s = view.getUint32(a, true), !(offset - a < 4 + s));) { + yield buffer.slice(4 + a, 4 + a + s); + a += s + 4; + } + } +} + +function reader(buffer: ArrayBuffer): [Float32Array, Uint32Array] { + const view = new DataView(buffer); + let offset = 0; + const n = view.getUint32(offset, true); + offset += 4; + const i = view.getUint32(offset, true); + offset += 4; + const o = new Float32Array(buffer.slice(offset, offset + 4 * n)); + offset += 4 * n; + const a = new Uint32Array(buffer.slice(offset, offset + 4 * i)); + offset += 4 * i; + return [o, a]; +} + +if (import.meta.main) { + const flags = parseArgs(Deno.args, { + string: ["map"], + }); + + const url = new URL("https://assets.thecdn.io/"); + const mapIds = new Map([ + ["perimeter", "9fdba452-9beb-41a4-9100-19a6948e3587"], + ["outpost", "4a5b068f-2bcc-4c19-ac2c-33f4fadba063"], + ["diremarsh", "8a7afd32-1c1e-4e9e-8649-80a1415d08ba"], + ]); + + let mapId: string | undefined; + if (flags.map && (mapId = mapIds.get(flags.map))) { + url.pathname = `${mapId}.chunks`; + + const response = await fetch(url); + + for await (const chunk of parseResponseChunked(response)) { + const zipFileReader = new BlobReader(new Blob([chunk])); + const zipReader = new ZipReader(zipFileReader); + const firstEntry = (await zipReader.getEntries()).shift(); + if (!firstEntry || firstEntry.directory) { + throw new Error("Invalid zip"); + } + + const dataWriter = new BlobWriter(); + const data = await firstEntry.getData(dataWriter); + + await zipReader.close(); + + const parsedData = reader(await data.arrayBuffer()); + + const [positions, indeces] = parsedData; + + for (let i = 0; i < positions.length; i += 3) { + const chunk = positions.slice(i, i + 3); + const [x, y, z] = chunk; + console.log("v", `${x} ${y} ${z}`); + } + + for (let i = 0; i < indeces.length; i += 2) { + const chunk = indeces.slice(i, i + 2); + const [v1, v2] = chunk; + console.log("l", `${v1} ${v2}`); + } + } + } else { + throw new Error("Unknown map"); + } +}