114 lines
3.1 KiB
TypeScript
Executable file
114 lines
3.1 KiB
TypeScript
Executable file
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<ArrayBuffer>, Uint32Array<ArrayBuffer>] {
|
|
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");
|
|
}
|
|
}
|