From edce64c1bd7f9af32e6b7053081de8807fd779b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:48:43 -0500 Subject: [PATCH] feat: dev tools --- .gitignore | 1 - .vscode/settings.json | 1 + bun.lockb | Bin 321118 -> 322512 bytes .../core/src/blitz/catalogItemAccessor.ts | 21 +- packages/core/src/blitz/metadata.ts | 10 + packages/website-ue/package.json | 3 + .../website-ue/src/components/PageWrapper.tsx | 8 +- .../src/core/blitzkit/api/server.ts | 10 +- packages/website-ue/src/core/blitzkit/ffi.ts | 2 + .../website-ue/src/pages/internal/_index.tsx | 49 ++- .../src/pages/internal/bindings/_index.tsx | 89 ++++++ .../src/pages/internal/bindings/index.astro | 8 + .../src/pages/internal/debug/_index.tsx | 14 + .../src/pages/internal/debug/index.astro | 8 + .../src/pages/internal/differ/_index.tsx | 289 ++++++++++++++++++ .../src/pages/internal/differ/index.astro | 40 +++ .../src/pages/internal/discovery/_index.tsx | 99 ++++++ .../src/pages/internal/discovery/index.astro | 8 + src/BlitzKit.FFI/BlitzKit.FFI.csproj | 4 +- src/BlitzKit.FFI/FFI.cs | 15 +- src/BlitzKit.FFI/Models/BlitzProvider.cs | 22 +- src/BlitzKit.FFI/Models/INIAccessor.cs | 58 ---- src/BlitzKit.FFI/Utils/INIAccessor.cs | 30 ++ submodules/blitzkit-closed | 2 +- 24 files changed, 698 insertions(+), 93 deletions(-) create mode 100644 packages/website-ue/src/pages/internal/bindings/_index.tsx create mode 100644 packages/website-ue/src/pages/internal/bindings/index.astro create mode 100644 packages/website-ue/src/pages/internal/debug/_index.tsx create mode 100644 packages/website-ue/src/pages/internal/debug/index.astro create mode 100644 packages/website-ue/src/pages/internal/differ/_index.tsx create mode 100644 packages/website-ue/src/pages/internal/differ/index.astro create mode 100644 packages/website-ue/src/pages/internal/discovery/_index.tsx create mode 100644 packages/website-ue/src/pages/internal/discovery/index.astro delete mode 100644 src/BlitzKit.FFI/Models/INIAccessor.cs create mode 100644 src/BlitzKit.FFI/Utils/INIAccessor.cs diff --git a/.gitignore b/.gitignore index 5db5c98ec..a1e6cbbce 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ target *.sln.docstates *.userprefs mono_crash.* -[Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ diff --git a/.vscode/settings.json b/.vscode/settings.json index f5a086df5..11a6322cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -69,6 +69,7 @@ "DIDS", "donotremove", "drei", + "dsapp", "dvpd", "dvpl", "DVPLCRC", diff --git a/bun.lockb b/bun.lockb index ce79beddc9bc7aab3971e7c16411c07029629205..85b4fad8f7db30fdd94c7e1b92d328f9fe690cbf 100644 GIT binary patch delta 61779 zcmeFadz?*G{QrN>8HaP+C7Bt-kRrKXW|(mXx#kk(HdAOY7>wH(k(f~_LTIOjlBOss zMG`8ZQWTX+8Ie@Vy;O=y{hqIV8I!O2e!kzw^Sox>x_A9^qU@xw>kmHOs|bu|Z^jJqzdq(isx&a}p5-idB?yogUnsrh}X z22zU5pV-m=()_Jeb50cX`7ZbQazBGBz+2%f;MH(BxIxJ0D+77ZNb#Koa>GK`ME|1<1mWKytrH`kdzQH3WhSm0}88X^8nF8dJIyD@Zjv1Gcl{q}iHxMxfeZr88k?CY;ag|fSOjt9f9;_Kqo_xx` zsHPKN9HT7uIp(RRZ1$*(AsOj|eKW~#)u3m z`n{IZ5DV6nnhdLg6R^hq3paintOjm?HO9NHbNxIxydar z;#81Z3s!}L293@h#UMOJgXNiHjnpCLz>STao-K2ET>8-TiFhIzTdw>94{40IG;#Ra zrcU}RVO2OTeLUXu`Oac1{Rvl}*Ua(YPp~|d53Axg;vJ9errc6F2-^s#pals|&*nDw z`54yRELh7+5UvIvY2h@W4R%B9=CFp&Bsm4Qht<<5u6-*Uhg~0*hpvEQ-~q`_xe+)= z397q730Mh#jyU`otjXq`zr`s)4XxV7skkN$U<`9#r2`tWQr9~k7y_#yU%)yGK7>od zzmQMm-_y?LV;<%XYKQ;Tfc6NgC=O;K=lWswIKQpW$8hByA)^ZTwxg5oBUlx$>*VM! zV^_vr3af%#xGG$uvonVtg_ZtU8mb}kChLScIZi~L+k>iIow0csR)u5I#$}~5p6R2; zXN=EC&$=63JsJg9fdA~~r283`2WH>s4B;%wlLv-p)t!`{8gBdpeV@9=3+6 z23!K3kTE`eNVd->Ryjkm(kIZVtejt|NG(4FtD+%;GsY>wxX~j=W{>gtdZaimZwkxB zm0-={Kl?iRVK@f+9az`CCt=p6+`C{6?LfF19C7t(u+p9HV^8v&+|LQn>D<>~joA{H zb6_PL1gqkA9Ye=4kHRcOo6)+7}x)HF_^>OW1a7FBEUA-c# z{84WF>48rE1Mua{zub2SsDPJXC0q)tqFG7+XS?xfu6-k{2E;}?Lzc~TM91hM>Gsn5 z;0R}O4Nm7ei#`)uLp!Xooy?a#YS6ezW5$mjmo_+~=0wB_qnycB%;mxMRN*Q$Zgf`G z?ODD*t+?V5(!k-1~)j?GTbPWK%g?=<=&*dE@xBQr8H z#`_jCiJ8!W-1n^0#Vh7`qy5MPr<&VtcYG6pt5aMJShM5-;#JIMs!?;wOmcLu`i1E7 z^=vnO$mmhy(*~vc#3QKok2{=vU%2dUm12Fz5HuJ??sVGwF}6;)x2(JpRdY7D$;ZR; z-H?%K<5@?1W2r{X@h0y;>@wJcGlmS&APr8RkTw!8bVAqJH83qJy>7E6J|=j27V~J( z=+Pr6#@Cd1HG9y=tcl7uaP-7<2GMuTG`lr9x!DAC7QE_mFChza=r4l-{VYruRNz-&MvRwYiOxFPz6@Tv_YfCsinoS zHR(N%{KlD}CHe>~zwW=!X;{q}PQDQ?uYnameq57g6ytlJbSnQd@@Zx57DY#OcE#N9 zxV9v0yBwAWhG;BsVQUgfZv<-ys>5o)6)FJ6<>NJ@OJK`GzXzQ7t+SncC$N?8hB=N$ z_hGAHXxbz}1S9(uqj=m%IXy$Yt&x#eNCGzwOQ-Y)zM1(n7=0;^#e z)dNgab1MXVhiu+If+qKSTyO=3ZF+ zndtHZi=76^LF4OYWSz%WKSvNR9stYnV@8i3lQw9CZ}2$gO5?%4xJT9Sw%nExP=UQj zsGfCpxwXsRFL5ePz?Q>beasoTB1;`TWvMm1Oz;2Y?&q52j$`AWa+cqxVP${J?K935@maXK9Cm*o8LCSC!6)>A~|5)kplvPf@(_y9W>T&{H0lPY^^glfBOsK9eI0KUeYoZ;aoXg

>r_<9wF56X@!z3q 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 Y0AdfyZE4bgbk$8vcO4pMk}~@L0NwsQ^Z)<= delta 61095 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 diff --git a/packages/core/src/blitz/catalogItemAccessor.ts b/packages/core/src/blitz/catalogItemAccessor.ts index 0dca3cf8f..fbe032a4e 100644 --- a/packages/core/src/blitz/catalogItemAccessor.ts +++ b/packages/core/src/blitz/catalogItemAccessor.ts @@ -1,14 +1,17 @@ import { CatalogItem } from '@protos/blitz_items'; import { Any } from '@protos/google/protobuf/any'; import { lowerFirst } from 'lodash-es'; +import { compareUint8Arrays } from '../../../scripts/src/core/github/compareUint8Arrays'; import { MessageFns } from '../protos'; export class CatalogItemAccessor { public id: string; components: Record = {}; + count: number; - constructor(item: CatalogItem) { + constructor(private item: CatalogItem) { this.id = item.catalog_id; + this.count = item.components.length; for (const component of item.components) { if ( @@ -77,4 +80,20 @@ export class CatalogItemAccessor { encode() { return CatalogItem.encode(this.pack()).finish(); } + + equals(other: CatalogItemAccessor) { + return ( + this.count === other.count && + this.item.components.every( + (component) => + component.value === undefined || + (component.key in other.components && + (component.value.component === undefined || + compareUint8Arrays( + other.components[component.key].value, + component.value.component.value, + ))), + ) + ); + } } diff --git a/packages/core/src/blitz/metadata.ts b/packages/core/src/blitz/metadata.ts index 3826893d5..ead38e30c 100644 --- a/packages/core/src/blitz/metadata.ts +++ b/packages/core/src/blitz/metadata.ts @@ -118,4 +118,14 @@ export class MetadataAccessor { return filtered; } + + toArray() { + return Object.values(this.items); + } + + toSortedArray() { + return this.toArray().sort((a, b) => + a.catalog_id.localeCompare(b.catalog_id), + ); + } } diff --git a/packages/website-ue/package.json b/packages/website-ue/package.json index 1994f4136..d1dd4b8f1 100644 --- a/packages/website-ue/package.json +++ b/packages/website-ue/package.json @@ -22,6 +22,7 @@ "@react-three/fiber": "^8.17.10", "@types/ffi-napi": "^4.0.10", "@types/lodash-es": "^4.17.12", + "@types/pako": "^2.0.3", "@types/react": "^19.1.5", "@types/react-dom": "^19.1.5", "@types/ref-napi": "^3.0.12", @@ -33,6 +34,8 @@ "immer": "^10.1.1", "lodash-es": "^4.17.21", "minimatch": "^10.0.1", + "minizlib": "^3.0.2", + "pako": "^2.1.0", "react": "^19.1.0", "react-dom": "^19.1.0", "ref-napi": "^3.0.3", diff --git a/packages/website-ue/src/components/PageWrapper.tsx b/packages/website-ue/src/components/PageWrapper.tsx index 7ec4e7101..665ccb174 100644 --- a/packages/website-ue/src/components/PageWrapper.tsx +++ b/packages/website-ue/src/components/PageWrapper.tsx @@ -1,13 +1,11 @@ import { Flex, type FlexProps, Theme } from '@radix-ui/themes'; import type { ComponentProps } from 'react'; -import type { RadixSize } from '../stores/embedState'; import { BlitzKitTheme } from './BlitzKitTheme'; type PageWrapperProps = FlexProps & { color?: ComponentProps['accentColor']; containerProps?: ComponentProps; noFlex1?: boolean; - padding?: RadixSize; noMinHeight?: boolean; }; @@ -18,7 +16,6 @@ export function PageWrapper({ noFlex1 = false, containerProps, noMinHeight = false, - padding = '4', ...props }: PageWrapperProps) { return ( @@ -35,8 +32,9 @@ export function PageWrapper({ > - BlitzKit FFI - - + + + + + Path + Memo + + + + + + + Debug + + + Some debug information to check if everything is up + + + + + + Bindings + + List of all active bindings + + + + + Discovery + + List of all active discovery lists + + + + + Differ + + Differentiate two branches + + + ); } diff --git a/packages/website-ue/src/pages/internal/bindings/_index.tsx b/packages/website-ue/src/pages/internal/bindings/_index.tsx new file mode 100644 index 000000000..57ba9a7e5 --- /dev/null +++ b/packages/website-ue/src/pages/internal/bindings/_index.tsx @@ -0,0 +1,89 @@ +import { Badge, Flex, Heading, Link, Separator, Text } from '@radix-ui/themes'; +import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper'; +import { ffi } from 'packages/website-ue/src/core/blitzkit/ffi'; + +export interface Bindings { + bindings: Binding[]; + forbidden: string[]; +} + +interface Binding { + name: string; + versions: string[]; + discovery: string; + env: string; + show_server_selection: boolean; +} + +export async function Page() { + return ( + + + + + + ); +} + +async function Bindings({ url, dev }: { url: string; dev?: boolean }) { + const data = await fetch(url).then( + (response) => response.json() as Promise, + ); + + return ( + <> + + Bindings URL{' '} + + {url} + + + + {data.bindings.map((binding) => ( + + + + {binding.env} + + {!dev && ( + + Production + + )} + {dev && ( + + Dev + + )} + {binding.show_server_selection && ( + Public + )} + {!binding.show_server_selection && ( + Private + )} + + + + {binding.name} + + + Discovery: + + {binding.discovery} + + + + + Versions: + + {binding.versions.map((version) => ( + + {version} + + ))} + + + ))} + + ); +} diff --git a/packages/website-ue/src/pages/internal/bindings/index.astro b/packages/website-ue/src/pages/internal/bindings/index.astro new file mode 100644 index 000000000..dc56143d6 --- /dev/null +++ b/packages/website-ue/src/pages/internal/bindings/index.astro @@ -0,0 +1,8 @@ +--- +import InternalBaseLayout from 'packages/website-ue/src/layouts/InternalBaseLayout.astro'; +import { Page } from './_index'; +--- + + + + diff --git a/packages/website-ue/src/pages/internal/debug/_index.tsx b/packages/website-ue/src/pages/internal/debug/_index.tsx new file mode 100644 index 000000000..7fd8e5130 --- /dev/null +++ b/packages/website-ue/src/pages/internal/debug/_index.tsx @@ -0,0 +1,14 @@ +import { Heading } from '@radix-ui/themes'; +import { FFIEcho } from '../../../components/Internal/FFIEcho'; +import { FFIFileCount } from '../../../components/Internal/FFIFileCount'; +import { PageWrapper } from '../../../components/PageWrapper'; + +export function Page() { + return ( + + BlitzKit FFI + + + + ); +} diff --git a/packages/website-ue/src/pages/internal/debug/index.astro b/packages/website-ue/src/pages/internal/debug/index.astro new file mode 100644 index 000000000..dc56143d6 --- /dev/null +++ b/packages/website-ue/src/pages/internal/debug/index.astro @@ -0,0 +1,8 @@ +--- +import InternalBaseLayout from 'packages/website-ue/src/layouts/InternalBaseLayout.astro'; +import { Page } from './_index'; +--- + + + + diff --git a/packages/website-ue/src/pages/internal/differ/_index.tsx b/packages/website-ue/src/pages/internal/differ/_index.tsx new file mode 100644 index 000000000..d28f4ef61 --- /dev/null +++ b/packages/website-ue/src/pages/internal/differ/_index.tsx @@ -0,0 +1,289 @@ +import type { MessageFns } from '@protos/blitz_static_profile_avatar_component'; +import type { Any } from '@protos/google/protobuf/any'; +import * as protos from '@protos/index'; +import { CommitIcon, ReaderIcon } from '@radix-ui/react-icons'; +import { + Badge, + Box, + Code, + Flex, + IconButton, + Select, + Spinner, + Text, +} from '@radix-ui/themes'; +import { MetadataAccessor } from 'packages/core/src'; +import type { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor'; +import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper'; +import { Var } from 'packages/website-ue/src/core/radix/var'; +import { useState } from 'react'; +import { fetchMetadata } from 'submodules/blitzkit-closed/src'; +import type { DiscoveryServer } from '../discovery/_index'; + +export function Page({ servers }: { servers: DiscoveryServer[] }) { + const [serverA, setSeverA] = useState(undefined); + const [serverB, setSeverB] = useState(undefined); + const [loading, setLoading] = useState(false); + const [diff, setDiff] = useState(null); + + return ( + + + { + setSeverA(servers.find((server) => server.name === value)); + }} + > + + + Select base server + + {servers.map((server) => ( + + {server.name} + + ))} + + + + { + setLoading(true); + + const diff = await diffServers(serverA!.addr, serverB!.addr); + + setLoading(false); + setDiff(diff); + }} + > + {loading ? : } + + + { + setSeverB(servers.find((server) => server.name === value)); + }} + > + + + Select base server + + {servers.map((server) => ( + + {server.name} + + ))} + + + + + {diff !== null && ( + entry.type !== CatalogItemDiffType.Unmodified, + )} + /> + )} + + ); +} + +function DiffDisplay({ diff }: { diff: CatalogDiffEntry[] }) { + return ( + + {diff.length === 0 && ( + + Branches are identical + + )} + + {diff.map((entry) => ( + + + + {diffEmojis[entry.type]} {entry.name} + + + + + {(entry.type === CatalogItemDiffType.Removed || + entry.type === CatalogItemDiffType.Unmodified) && ( + + )} + {entry.type === CatalogItemDiffType.Modified && ( + + )} + {(entry.type === CatalogItemDiffType.Added || + entry.type === CatalogItemDiffType.Unmodified) && ( + + )} + {entry.type === CatalogItemDiffType.Modified && ( + + )} + + + ))} + + ); +} + +function ItemDisplay({ item }: { item: CatalogItemAccessor }) { + const entries = Object.entries(item.components); + + protos; + + return ( + + {entries.length === 0 && No components} + + {entries.map(([key, value]) => ( + + ))} + + ); +} + +function ComponentDisplay({ name, value }: { name: string; value: Any }) { + const [code, setCode] = useState(null); + + return ( + + + { + const messageName = value.type_url + .slice(2) + .split('.') + .slice(1) + .join('.'); + const json = ( + protos[messageName as keyof typeof protos] as MessageFns + ).decode(value.value); + + setCode(JSON.stringify(json, null, 2)); + }} + > + + + + + {name} + + {value.value.length.toLocaleString()}B + + + {code !== null && ( + + {code} + + )} + + ); +} + +enum CatalogItemDiffType { + Unmodified, + Modified, + Added, + Removed, +} + +type CatalogDiffEntry = { name: string } & ( + | { + type: CatalogItemDiffType.Modified; + a: CatalogItemAccessor; + b: CatalogItemAccessor; + } + | { + type: Exclude; + item: CatalogItemAccessor; + } +); + +const diffEmojis: Record = { + [CatalogItemDiffType.Unmodified]: '🩶', + [CatalogItemDiffType.Modified]: '🟡', + [CatalogItemDiffType.Added]: '🟢', + [CatalogItemDiffType.Removed]: '🔴', +}; + +async function diffServers(urlA: string, urlB: string) { + const [metadataA, metadataB] = await Promise.all([ + fetchMetadata(urlA), + fetchMetadata(urlB), + ]).then((members) => + members.map((metadata) => new MetadataAccessor(metadata)), + ); + const itemNameSet = new Set(); + + for (const item in metadataA.items) itemNameSet.add(item); + for (const item in metadataB.items) itemNameSet.add(item); + + const itemNames = [...itemNameSet].sort(); + const diff: CatalogDiffEntry[] = []; + + for (const name of itemNames) { + if (name in metadataA.items) { + if (name in metadataB.items) { + const a = metadataA.get(name); + const b = metadataB.get(name); + + if (a.equals(b)) { + diff.push({ name, type: CatalogItemDiffType.Unmodified, item: a }); + } else { + diff.push({ + name, + type: CatalogItemDiffType.Modified, + a: a, + b: b, + }); + } + } else { + diff.push({ + name, + type: CatalogItemDiffType.Removed, + item: metadataA.get(name), + }); + } + } else if (name in metadataB.items) { + diff.push({ + name, + type: CatalogItemDiffType.Added, + item: metadataB.get(name), + }); + } + } + + return diff; +} diff --git a/packages/website-ue/src/pages/internal/differ/index.astro b/packages/website-ue/src/pages/internal/differ/index.astro new file mode 100644 index 000000000..57059f4b3 --- /dev/null +++ b/packages/website-ue/src/pages/internal/differ/index.astro @@ -0,0 +1,40 @@ +--- +import { ffi } from '../../../core/blitzkit/ffi'; +import InternalBaseLayout from '../../../layouts/InternalBaseLayout.astro'; +import { Bindings } from '../bindings/_index'; +import { Discovery, DiscoveryServer } from '../discovery/_index'; +import { Page } from './_index'; + +const serversMap = new Map(); + +await Promise.all( + [ffi.get_bindings_url_dev()!, ffi.get_bindings_url_production()!] + .map((raw) => JSON.parse(raw) as string) + .map(async (url) => { + const { bindings } = await fetch(url).then( + (response) => response.json() as Promise, + ); + + await Promise.all( + bindings.map(async (binding) => { + const discovery = await fetch(binding.discovery).then( + (response) => response.json() as Promise, + ); + + Object.values(discovery.servers).map((server) => { + if (serversMap.has(server.name)) return; + serversMap.set(server.name, server); + }); + }), + ); + }), +); + +const servers = [...serversMap.values()].sort((a, b) => + a.name.localeCompare(b.name), +); +--- + + + + diff --git a/packages/website-ue/src/pages/internal/discovery/_index.tsx b/packages/website-ue/src/pages/internal/discovery/_index.tsx new file mode 100644 index 000000000..2398de59c --- /dev/null +++ b/packages/website-ue/src/pages/internal/discovery/_index.tsx @@ -0,0 +1,99 @@ +import { Flex, Heading, Link, Text } from '@radix-ui/themes'; +import { PageWrapper } from 'packages/website-ue/src/components/PageWrapper'; +import { ffi } from 'packages/website-ue/src/core/blitzkit/ffi'; +import type { Bindings } from '../bindings/_index'; + +export interface Discovery { + version: string; + clusters: string[]; + default_server: string; + servers: Record; + icmp_servers: ICMPServers; + clusters_versions: Record; + countries_by_clusters: CountriesByClusters; + extensions: Extensions; +} + +interface Empty { + version: string; + server: string; +} + +type CountriesByClusters = Record; + +interface Extensions { + endpoints: Record; +} + +interface Endpoint { + playability_config_url: string; + fullscreen_notifications_url: string; + mgg_frontier_url: string; + mgg_pay_url: string; +} + +interface ICMPServers { + default: Record; +} + +export interface DiscoveryServer { + name: string; + addr: string; + addr_dsapp?: string; +} + +const servers: DiscoveryServer[] = []; + +async function populate(url: string) { + const { bindings } = await fetch(url).then( + (response) => response.json() as Promise, + ); + + await Promise.all( + bindings.map(async (binding) => { + const discovery = await fetch(binding.discovery).then( + (response) => response.json() as Promise, + ); + + Object.values(discovery.servers).map((server) => { + const doesInclude = servers.some((s) => s.addr === server.addr); + if (!doesInclude) servers.push(server); + }); + }), + ); +} + +await Promise.all([ + populate(JSON.parse(ffi.get_bindings_url_dev()!)), + populate(JSON.parse(ffi.get_bindings_url_production()!)), +]); + +servers.sort((a, b) => a.name.localeCompare(b.name)); + +export function Page() { + return ( + + {servers.map((server) => ( + + {server.name} + + + Address:{' '} + + {server.addr} + + + + {server.addr_dsapp && ( + + DSApp:{' '} + + {server.addr_dsapp} + + + )} + + ))} + + ); +} diff --git a/packages/website-ue/src/pages/internal/discovery/index.astro b/packages/website-ue/src/pages/internal/discovery/index.astro new file mode 100644 index 000000000..dc56143d6 --- /dev/null +++ b/packages/website-ue/src/pages/internal/discovery/index.astro @@ -0,0 +1,8 @@ +--- +import InternalBaseLayout from 'packages/website-ue/src/layouts/InternalBaseLayout.astro'; +import { Page } from './_index'; +--- + + + + diff --git a/src/BlitzKit.FFI/BlitzKit.FFI.csproj b/src/BlitzKit.FFI/BlitzKit.FFI.csproj index d393bab71..e4384e248 100644 --- a/src/BlitzKit.FFI/BlitzKit.FFI.csproj +++ b/src/BlitzKit.FFI/BlitzKit.FFI.csproj @@ -7,8 +7,10 @@ win-x64 true - + + + diff --git a/src/BlitzKit.FFI/FFI.cs b/src/BlitzKit.FFI/FFI.cs index 5a07f912b..d5f9ff321 100644 --- a/src/BlitzKit.FFI/FFI.cs +++ b/src/BlitzKit.FFI/FFI.cs @@ -2,7 +2,6 @@ using System.Runtime.InteropServices; using BlitzKit.FFI.Models; using BlitzKit.FFI.Utils; -using CUE4Parse.Compression; namespace BlitzKit.FFI; @@ -32,4 +31,18 @@ public static class FFI { return provider.Files.Count; } + + [UnmanagedCallersOnly(EntryPoint = "get_bindings_url_production")] + public static IntPtr GetBindingsURLProduction() + { + var url = provider.GetBindingsURLProduction(); + return Marshal.StringToHGlobalAnsi(url); + } + + [UnmanagedCallersOnly(EntryPoint = "get_bindings_url_dev")] + public static IntPtr GetBindingsURLDev() + { + var url = provider.GetBindingsURLDev(); + return Marshal.StringToHGlobalAnsi(url); + } } diff --git a/src/BlitzKit.FFI/Models/BlitzProvider.cs b/src/BlitzKit.FFI/Models/BlitzProvider.cs index 268720e24..2cb9d929a 100644 --- a/src/BlitzKit.FFI/Models/BlitzProvider.cs +++ b/src/BlitzKit.FFI/Models/BlitzProvider.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using System.Text; +using BlitzKit.FFI.Utils; using CUE4Parse.FileProvider; using CUE4Parse.UE4.Versions; @@ -18,24 +18,14 @@ namespace BlitzKit.FFI.Models { Initialize(); Mount(); - - Console.WriteLine(DiscoveryURL()); } - public string DiscoveryURL() - { - if (TryGetGameFile("Blitz/Config/DefaultEngine.ini", out var defaultEngine)) - { - INIAccessor accessor = new(Encoding.UTF8.GetString(defaultEngine.Read())); + public INIAccessor GetDefaultEngine() => new(this["Blitz/Config/DefaultEngine.ini"]); - var url = (string)accessor.Get("DiscoveryURLDedicProduction"); + public string GetBindingsURLProduction() => + GetDefaultEngine()["/Script/MintSDK.MintSDKSettingsObject"]["DiscoveryBindingsURLProduction"]; - return url; - } - else - { - throw new FileNotFoundException("Could not find DefaultEngine.ini"); - } - } + public string GetBindingsURLDev() => + GetDefaultEngine()["/Script/MintSDK.MintSDKSettingsObject"]["DiscoveryBindingsURLDev"]; } } diff --git a/src/BlitzKit.FFI/Models/INIAccessor.cs b/src/BlitzKit.FFI/Models/INIAccessor.cs deleted file mode 100644 index 56b536320..000000000 --- a/src/BlitzKit.FFI/Models/INIAccessor.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace BlitzKit.FFI.Models -{ - public class INIAccessor - { - private Dictionary data = []; - - public INIAccessor(string content) - { - var lines = content.Split(['\n', '\r'], System.StringSplitOptions.RemoveEmptyEntries); - - foreach (var line in lines) - { - var trimmed = line.Trim(); - - if (string.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith(';')) - continue; - - var prefix = trimmed[0]; - string key, - value; - - if (prefix == '+' || prefix == '-') - { - var x = trimmed.IndexOf('='); - key = trimmed[1..x]; - value = trimmed[(x + 1)..]; - - if (!data.TryGetValue(key, out var existing)) - { - data[key] = existing = new List(); - } - - var list = (List)existing; - - if (prefix == '+') - list.Add(value); - else - list.Remove(value); - } - else - { - var x = trimmed.IndexOf('='); - key = trimmed[..x]; - value = trimmed[(x + 1)..]; - - data[key] = value; - } - } - } - - public bool TryGet(string key, out object value) => data.TryGetValue(key, out value); - - public object Get(string key) => data[key]; - } -} diff --git a/src/BlitzKit.FFI/Utils/INIAccessor.cs b/src/BlitzKit.FFI/Utils/INIAccessor.cs new file mode 100644 index 000000000..288af988d --- /dev/null +++ b/src/BlitzKit.FFI/Utils/INIAccessor.cs @@ -0,0 +1,30 @@ +using System.Text; +using CUE4Parse.FileProvider.Objects; +using IniParser.Model; +using IniParser.Model.Configuration; +using IniParser.Parser; + +namespace BlitzKit.FFI.Utils +{ + public class INIAccessor + { + private static readonly IniParserConfiguration configuration = new() + { + AllowDuplicateSections = true, + AllowDuplicateKeys = true, + ConcatenateDuplicateKeys = true, + }; + + private readonly IniData data; + + public INIAccessor(GameFile file) + { + var bytes = file.Read(); + var str = Encoding.UTF8.GetString(bytes); + IniDataParser parser = new(configuration); + data = parser.Parse(str); + } + + public KeyDataCollection this[string sectionName] => data[sectionName]; + } +} diff --git a/submodules/blitzkit-closed b/submodules/blitzkit-closed index 89dc337d2..69df3b07a 160000 --- a/submodules/blitzkit-closed +++ b/submodules/blitzkit-closed @@ -1 +1 @@ -Subproject commit 89dc337d2713dd4aaa8319d01c544f9e08756127 +Subproject commit 69df3b07a2e8cf52ca0235c180393e4315d6f940