From b285e9f1d0feed614da50be1d800aab5c2117fdb Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:21:05 -0500 Subject: [PATCH] discover servers --- packages/game/JSExport/GameInterface.cs | 22 +- packages/scripts/src/discoverServers.ts | 25 +- packages/scripts/src/patchNotes.ts | 319 ------------------- packages/scripts/src/protos.ts | 9 +- packages/scripts/src/setupDepotDownloader.ts | 20 +- packages/website-ue/src/api/server.ts | 10 +- packages/website-ue/src/game/game.ts | 2 +- 7 files changed, 59 insertions(+), 348 deletions(-) delete mode 100644 packages/scripts/src/patchNotes.ts diff --git a/packages/game/JSExport/GameInterface.cs b/packages/game/JSExport/GameInterface.cs index af6e81ece..50414ef94 100644 --- a/packages/game/JSExport/GameInterface.cs +++ b/packages/game/JSExport/GameInterface.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using BlitzKit.Game.Models; using CUE4Parse_Conversion; @@ -40,7 +41,24 @@ public partial class GameInterface } readonly Dictionary texturePaths = []; - readonly string textureCache = "../../temp/textures.json"; + readonly string textureCache = $"{temp}textures.json"; + + [GeneratedRegex(@"\+ClientDiscoveryBindingSources\s*=\s*\(Type=\w+,URL=""(.+)""\)")] + private static partial Regex DiscoveryRegex(); + + public string[] DiscoveryUrls() + { + if (provider.TryGetGameFile("Blitz/Config/DefaultEngine.ini", out var file)) + { + var bytes = file.Read(); + var content = Encoding.UTF8.GetString(bytes); + var matches = DiscoveryRegex().Matches(content); + + return [.. matches.Select(match => match.Groups[1].Value)]; + } + + throw new FileNotFoundException("Engine config not found"); + } public void DiscoverTextures(string[] tankTags) { diff --git a/packages/scripts/src/discoverServers.ts b/packages/scripts/src/discoverServers.ts index 6aa2412bf..a54920538 100644 --- a/packages/scripts/src/discoverServers.ts +++ b/packages/scripts/src/discoverServers.ts @@ -1,9 +1,6 @@ +import { assertSecret } from "@blitzkit/core"; import { GameInterface } from "@blitzkit/game"; -const urls = [ - // -]; - interface Bindings { bindings: Binding[]; forbidden: string[]; @@ -52,12 +49,22 @@ interface Server { addr_dsapp?: string; } -const game = new GameInterface(); +const game = new GameInterface( + assertSecret(import.meta.env.PUBLIC_WOTB_CLIENT), + assertSecret(import.meta.env.WOTB_USMAP), + assertSecret(import.meta.env.TEMP), + Number(assertSecret(import.meta.env.TEXTURE_CHUNKS)), +); -urls.forEach(async (url) => { - const bindings = await fetch(url).then( - (response) => response.json() as Promise, - ); +game.discoveryUrls().forEach(async (url) => { + const response = await fetch(url); + + if (!response.ok) { + console.log(`Failed to fetch ${url}: ${response.statusText}`); + return; + } + + const bindings = (await response.json()) as Bindings; bindings.bindings.forEach(async (binding) => { const discovery = await fetch(binding.discovery).then( diff --git a/packages/scripts/src/patchNotes.ts b/packages/scripts/src/patchNotes.ts deleted file mode 100644 index ba02e8fe1..000000000 --- a/packages/scripts/src/patchNotes.ts +++ /dev/null @@ -1,319 +0,0 @@ -import { - availableProvisions, - createDefaultProvisions, - createDefaultSkills, - fetchConsumableDefinitions, - fetchEquipmentDefinitions, - fetchModelDefinitions, - fetchProvisionDefinitions, - fetchSkillDefinitions, - fetchTankDefinitions, - TankDefinition, - tankIcon, -} from "@blitzkit/core"; -import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity"; -import strings from "@blitzkit/i18n/strings/en.json"; -import { writeFile } from "fs/promises"; -import { isEqual } from "lodash-es"; -import { tankCharacteristics } from "../../website/src/core/blitzkit/tankCharacteristics"; -import { genericDefaultEquipmentMatrix } from "../../website/src/stores/duel/constants"; - -const domain = "https://opentest.blitzkit.app"; - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets"; -const currentTankDefinitions = await fetchTankDefinitions(); -const currentConsumableDefinitions = await fetchConsumableDefinitions(); -const currentProvisionDefinitions = await fetchProvisionDefinitions(); -const currentEquipmentDefinitions = await fetchEquipmentDefinitions(); -const currentSkillDefinitions = await fetchSkillDefinitions(); -const currentModelDefinitions = await fetchModelDefinitions(); - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview"; -const previewTankDefinitions = await fetchTankDefinitions(); -const previewConsumableDefinitions = await fetchConsumableDefinitions(); -const previewProvisionDefinitions = await fetchProvisionDefinitions(); -const previewEquipmentDefinitions = await fetchEquipmentDefinitions(); -const previewSkillDefinitions = await fetchSkillDefinitions(); -const previewModelDefinitions = await fetchModelDefinitions(); - -let notes = "# BlitzKit OpenTest Patch Notes\n\n"; - -const newTanks: TankDefinition[] = []; - -for (const id in previewTankDefinitions.tanks) { - if (!(id in currentTankDefinitions.tanks)) { - newTanks.push(previewTankDefinitions.tanks[id]); - } -} - -newTanks.sort((a, b) => b.tier - a.tier); - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview"; -if (newTanks.length > 0) { - notes += "## New Tanks\n\n"; - - for (const tank of newTanks) { - notes += `### [${tank.name!.locales.en}](${domain}/tanks/${tank.slug})\n`; - notes += `![](${tankIcon(tank.id)})\n\n`; - } - - notes += "\n"; -} - -const changedTanks: [TankDefinition, TankDefinition][] = []; - -for (const stringId in previewTankDefinitions.tanks) { - const id = Number(stringId); - - if (newTanks.some((tank) => tank.id === id)) continue; - - const current = currentTankDefinitions.tanks[id]; - const preview = previewTankDefinitions.tanks[id]; - - if (isEqual(current, preview)) continue; - - changedTanks.push([current, preview]); -} - -changedTanks.sort((a, b) => b[1].tier - a[1].tier); - -import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets"; -if (changedTanks.length > 0) { - notes += "## Changed Tanks\n\n"; - - for (const [current, preview] of changedTanks) { - const currentTurret = current.turrets.at(-1)!; - const previewTurret = preview.turrets.at(-1)!; - const currentGun = currentTurret.guns.at(-1)!; - const previewGun = previewTurret.guns.at(-1)!; - const currentShell = currentGun.shells[0]; - const previewShell = previewGun.shells[0]; - const currentEngine = current.engines.at(-1)!; - const previewEngine = preview.engines.at(-1)!; - const currentTracks = current.tracks.at(-1)!; - const previewTracks = preview.tracks.at(-1)!; - const currentTankModel = currentModelDefinitions.models[current.id]; - const previewTankModel = previewModelDefinitions.models[preview.id]; - - const currentConsumables = Object.values( - currentConsumableDefinitions.consumables, - ).filter((consumable) => - checkConsumableProvisionInclusivity(consumable, current, currentGun), - ); - const previewConsumables = Object.values( - previewConsumableDefinitions.consumables, - ).filter((consumable) => - checkConsumableProvisionInclusivity(consumable, preview, previewGun), - ); - const addedConsumables = previewConsumables.filter( - (previewConsumable) => - !currentConsumables.some( - (currentConsumable) => currentConsumable.id === previewConsumable.id, - ), - ); - const removedConsumables = currentConsumables.filter( - (currentConsumable) => - !previewConsumables.some( - (previewConsumable) => previewConsumable.id === currentConsumable.id, - ), - ); - - const currentProvisions = availableProvisions( - current, - currentGun, - currentProvisionDefinitions, - ); - const previewProvisions = availableProvisions( - preview, - previewGun, - previewProvisionDefinitions, - ); - const addedProvisions = previewProvisions.filter( - (previewProvision) => - !currentProvisions.some( - (currentProvision) => currentProvision.id === previewProvision.id, - ), - ); - const removedProvisions = currentProvisions.filter( - (currentProvision) => - !previewProvisions.some( - (previewProvision) => previewProvision.id === currentProvision.id, - ), - ); - - const currentEquipment = currentEquipmentDefinitions.presets[ - current.equipment_preset - ].slots - .map(({ left, right }) => [left, right]) - .flat(); - const previewEquipment = previewEquipmentDefinitions.presets[ - preview.equipment_preset - ].slots - .map(({ left, right }) => [left, right]) - .flat(); - const addedEquipment = previewEquipment - .filter( - (previewEquipment) => - !currentEquipment.some( - (currentEquipment) => currentEquipment === previewEquipment, - ), - ) - .map((id) => previewEquipmentDefinitions.equipments[id]); - const removedEquipment = currentEquipment - .filter( - (currentEquipment) => - !previewEquipment.some( - (previewEquipment) => previewEquipment === currentEquipment, - ), - ) - .map((id) => currentEquipmentDefinitions.equipments[id]); - - const equalize = false; - const currentCharacteristics = tankCharacteristics( - { - tank: current, - gun: currentGun, - shell: currentShell, - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - assaultDistance: 0, - crewSkills: createDefaultSkills(currentSkillDefinitions), - camouflage: true, - consumables: [], - engine: currentEngine, - provisions: createDefaultProvisions( - current, - currentGun, - currentProvisionDefinitions, - ), - equipmentMatrix: genericDefaultEquipmentMatrix, - stockEngine: current.engines[0], - stockGun: current.turrets[0].guns[0], - stockTrack: current.tracks[0], - stockTurret: current.turrets[0], - track: currentTracks, - turret: currentTurret, - equalize, - }, - { - equipmentDefinitions: currentEquipmentDefinitions, - provisionDefinitions: currentProvisionDefinitions, - tankModelDefinition: currentTankModel, - }, - ); - const previewCharacteristics = tankCharacteristics( - { - tank: preview, - gun: previewGun, - shell: previewShell, - applyDynamicArmor: false, - applyReactiveArmor: false, - applySpallLiner: false, - assaultDistance: 0, - crewSkills: createDefaultSkills(previewSkillDefinitions), - camouflage: true, - consumables: [], - engine: previewEngine, - provisions: createDefaultProvisions( - preview, - previewGun, - previewProvisionDefinitions, - ), - equipmentMatrix: genericDefaultEquipmentMatrix, - stockEngine: preview.engines[0], - stockGun: preview.turrets[0].guns[0], - stockTrack: preview.tracks[0], - stockTurret: preview.turrets[0], - track: previewTracks, - turret: previewTurret, - equalize, - }, - { - equipmentDefinitions: previewEquipmentDefinitions, - provisionDefinitions: previewProvisionDefinitions, - tankModelDefinition: previewTankModel, - }, - ); - const changedCharacteristics: string[] = []; - - for (const key in currentCharacteristics) { - const characteristicKey = key as keyof typeof currentCharacteristics; - if ( - currentCharacteristics[characteristicKey] !== - previewCharacteristics[characteristicKey] - ) { - changedCharacteristics.push(characteristicKey); - } - } - - notes += `### [${preview.name!.locales.en}](${domain}/tanks/${preview.slug})\n`; - notes += `![](${tankIcon(preview.id)})\n\n`; - - if (addedConsumables.length + removedConsumables.length > 0) { - notes += `Consumables: `; - notes += [ - ...addedConsumables.map( - (consumable) => `🟢 ${consumable.name!.locales.en}`, - ), - ...removedConsumables.map( - (consumable) => `🔴 ${consumable.name!.locales.en}`, - ), - ].join(", "); - notes += `\n`; - } - - if (addedProvisions.length + removedProvisions.length > 0) { - notes += `Provisions: `; - notes += [ - ...addedProvisions.map( - (provision) => `🟢 ${provision.name!.locales.en}`, - ), - ...removedProvisions.map( - (provision) => `🔴 ${provision.name!.locales.en}`, - ), - ].join(", "); - notes += `\n`; - } - - if (addedEquipment.length + removedEquipment.length > 0) { - notes += `-Equipment: `; - notes += [ - ...addedEquipment.map( - (equipment) => `🟢 ${equipment.name!.locales.en}`, - ), - ...removedEquipment.map( - (equipment) => `🔴 ${equipment.name!.locales.en}`, - ), - ].join(", "); - notes += `\n`; - } - - if (changedCharacteristics.length > 0) { - notes += `Characteristics:\n`; - - for (const characteristicUntyped of changedCharacteristics) { - const characteristic = - characteristicUntyped as keyof typeof currentCharacteristics; - const name = - strings.website.tools.tankopedia.characteristics.values[ - characteristic - ]; - const before = currentCharacteristics[characteristic]; - const after = previewCharacteristics[characteristic]; - - notes += `- ${ - typeof before === "number" && typeof after === "number" - ? after > before - ? "⬆️" - : "🔻" - : "✏️" - } ${name}: \`${ - typeof before === "number" ? before.toFixed(2) : before - }\` → \`${typeof after === "number" ? after.toFixed(2) : after}\`\n`; - } - } - } -} - -await writeFile("../../temp/patch-notes.md", notes); diff --git a/packages/scripts/src/protos.ts b/packages/scripts/src/protos.ts index 78bafe4a7..19fb70985 100644 --- a/packages/scripts/src/protos.ts +++ b/packages/scripts/src/protos.ts @@ -1,7 +1,10 @@ +import { assertSecret } from "@blitzkit/core"; import { execFile } from "child_process"; import { existsSync } from "fs"; import { mkdir, readdir, rm, writeFile } from "fs/promises"; +const temp = assertSecret(import.meta.env.TEMP); + const TS_PROTO_EXECUTABLE_LOCATIONS = [ "./node_modules/.bin/protoc-gen-ts_proto", "../../node_modules/.bin/protoc-gen-ts_proto", @@ -28,7 +31,7 @@ if (!tsProtoExecutableLocation) { ); } -await mkdir("../../temp", { recursive: true }); +await mkdir(temp, { recursive: true }); let args = ""; @@ -55,9 +58,9 @@ for (const file of filesRaw) { if (file.endsWith(".ts")) await rm(`${ROOT}/${file}`); } -await writeFile("../../temp/protoc.txt", args); +await writeFile(`${temp}/protoc.txt`, args); -execFile("protoc", ["@../../temp/protoc.txt"], (error, stdout, stderr) => { +execFile("protoc", [`@${temp}/protoc.txt`], (error, stdout, stderr) => { if (error) throw new Error(error.message); if (stderr) console.error(stderr); if (stdout) console.log(stdout); diff --git a/packages/scripts/src/setupDepotDownloader.ts b/packages/scripts/src/setupDepotDownloader.ts index 72ef9322b..2225562a0 100644 --- a/packages/scripts/src/setupDepotDownloader.ts +++ b/packages/scripts/src/setupDepotDownloader.ts @@ -1,21 +1,24 @@ +import { assertSecret } from "@blitzkit/core"; import decompress from "decompress"; import { existsSync } from "fs"; import { mkdir, readdir, writeFile } from "fs/promises"; -if (!existsSync("../../temp")) { +const temp = assertSecret(import.meta.env.TEMP); + +if (!existsSync(temp)) { console.log("Creating temp directory..."); - await mkdir("../../temp"); + await mkdir(temp); } console.log("Downloading depot downloader..."); await fetch( - "https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest" + "https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest", ) .then( (response) => response.json() as Promise<{ assets: { browser_download_url: string; name: string }[]; - }> + }>, ) .then(({ assets }) => { const asset = assets.find(({ name }) => name.includes("linux-x64")); @@ -24,16 +27,13 @@ await fetch( }) .then((response) => response.arrayBuffer()) .then((arrayBuffer) => - writeFile("../../temp/depotDownloader.zip", new Uint8Array(arrayBuffer)) + writeFile(`${temp}/depotDownloader.zip`, new Uint8Array(arrayBuffer)), ); console.log("Unzipping..."); -await decompress( - "../../temp/depotDownloader.zip", - "../../temp/depotDownloader" -); +await decompress(`${temp}/depotDownloader.zip`, `${temp}/depotDownloader`); console.log( "Download complete; files:", - await readdir("../../temp/depotDownloader") + await readdir(`${temp}/depotDownloader`), ); diff --git a/packages/website-ue/src/api/server.ts b/packages/website-ue/src/api/server.ts index 8c74032aa..0f868fd5c 100644 --- a/packages/website-ue/src/api/server.ts +++ b/packages/website-ue/src/api/server.ts @@ -22,6 +22,8 @@ import { merge } from "lodash-es"; import { parse } from "yaml"; import { AbstractAPI, Cache } from "./abstract"; +const temp = assertSecret(import.meta.env.TEMP); + if (typeof window !== "undefined") { throw new Error("ServerAPI is being evaluated in the browser"); } @@ -138,7 +140,7 @@ export class ServerAPI extends AbstractAPI { @Cache() async popularTanks() { - if (!existsSync("../../temp/popular.json")) { + if (!existsSync(`${temp}/popular.json`)) { const { tanks } = await this.tanks(); const tanksArray = Object.values(tanks); @@ -212,13 +214,13 @@ export class ServerAPI extends AbstractAPI { .map(([id, views]) => ({ id, views })), } satisfies PopularTanks; - await mkdir("../../temp", { recursive: true }); - await writeFile("../../temp/popular.json", JSON.stringify(popularTanks)); + await mkdir(temp, { recursive: true }); + await writeFile(`${temp}/popular.json`, JSON.stringify(popularTanks)); return popularTanks; } - const data = await readFile("../../temp/popular.json", "utf-8"); + const data = await readFile(`${temp}/popular.json`, "utf-8"); return JSON.parse(data) as PopularTanks; } diff --git a/packages/website-ue/src/game/game.ts b/packages/website-ue/src/game/game.ts index d09e20301..cb9fa8cc7 100644 --- a/packages/website-ue/src/game/game.ts +++ b/packages/website-ue/src/game/game.ts @@ -12,7 +12,7 @@ if (!clientUnmounted && import.meta.env.SSR) { _game = new GameInterface( assertSecret(import.meta.env.PUBLIC_WOTB_CLIENT), assertSecret(import.meta.env.WOTB_USMAP), - "../../temp", + assertSecret(import.meta.env.TEMP), Number(assertSecret(import.meta.env.TEXTURE_CHUNKS)), );