mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: dev tools
This commit is contained in:
1 parent
74b821ac13
commit
edce64c1bd
24 files changed
+698
-93
No files matched your search
@@ -34,7 +34,6 @@ target
|
|||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
*.userprefs
|
*.userprefs
|
||||||
mono_crash.*
|
mono_crash.*
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
[Dd]ebugPublic/
|
||||||
[Rr]elease/
|
[Rr]elease/
|
||||||
[Rr]eleases/
|
[Rr]eleases/
|
||||||
|
|||||||
Vendored
+1
@@ -69,6 +69,7 @@
|
|||||||
"DIDS",
|
"DIDS",
|
||||||
"donotremove",
|
"donotremove",
|
||||||
"drei",
|
"drei",
|
||||||
|
"dsapp",
|
||||||
"dvpd",
|
"dvpd",
|
||||||
"dvpl",
|
"dvpl",
|
||||||
"DVPLCRC",
|
"DVPLCRC",
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { CatalogItem } from '@protos/blitz_items';
|
import { CatalogItem } from '@protos/blitz_items';
|
||||||
import { Any } from '@protos/google/protobuf/any';
|
import { Any } from '@protos/google/protobuf/any';
|
||||||
import { lowerFirst } from 'lodash-es';
|
import { lowerFirst } from 'lodash-es';
|
||||||
|
import { compareUint8Arrays } from '../../../scripts/src/core/github/compareUint8Arrays';
|
||||||
import { MessageFns } from '../protos';
|
import { MessageFns } from '../protos';
|
||||||
|
|
||||||
export class CatalogItemAccessor {
|
export class CatalogItemAccessor {
|
||||||
public id: string;
|
public id: string;
|
||||||
components: Record<string, Any> = {};
|
components: Record<string, Any> = {};
|
||||||
|
count: number;
|
||||||
|
|
||||||
constructor(item: CatalogItem) {
|
constructor(private item: CatalogItem) {
|
||||||
this.id = item.catalog_id;
|
this.id = item.catalog_id;
|
||||||
|
this.count = item.components.length;
|
||||||
|
|
||||||
for (const component of item.components) {
|
for (const component of item.components) {
|
||||||
if (
|
if (
|
||||||
@@ -77,4 +80,20 @@ export class CatalogItemAccessor {
|
|||||||
encode() {
|
encode() {
|
||||||
return CatalogItem.encode(this.pack()).finish();
|
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,
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -118,4 +118,14 @@ export class MetadataAccessor {
|
|||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toArray() {
|
||||||
|
return Object.values(this.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
toSortedArray() {
|
||||||
|
return this.toArray().sort((a, b) =>
|
||||||
|
a.catalog_id.localeCompare(b.catalog_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"@react-three/fiber": "^8.17.10",
|
"@react-three/fiber": "^8.17.10",
|
||||||
"@types/ffi-napi": "^4.0.10",
|
"@types/ffi-napi": "^4.0.10",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/pako": "^2.0.3",
|
||||||
"@types/react": "^19.1.5",
|
"@types/react": "^19.1.5",
|
||||||
"@types/react-dom": "^19.1.5",
|
"@types/react-dom": "^19.1.5",
|
||||||
"@types/ref-napi": "^3.0.12",
|
"@types/ref-napi": "^3.0.12",
|
||||||
@@ -33,6 +34,8 @@
|
|||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
|
"minizlib": "^3.0.2",
|
||||||
|
"pako": "^2.1.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"ref-napi": "^3.0.3",
|
"ref-napi": "^3.0.3",
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Flex, type FlexProps, Theme } from '@radix-ui/themes';
|
import { Flex, type FlexProps, Theme } from '@radix-ui/themes';
|
||||||
import type { ComponentProps } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
import type { RadixSize } from '../stores/embedState';
|
|
||||||
import { BlitzKitTheme } from './BlitzKitTheme';
|
import { BlitzKitTheme } from './BlitzKitTheme';
|
||||||
|
|
||||||
type PageWrapperProps = FlexProps & {
|
type PageWrapperProps = FlexProps & {
|
||||||
color?: ComponentProps<typeof Theme>['accentColor'];
|
color?: ComponentProps<typeof Theme>['accentColor'];
|
||||||
containerProps?: ComponentProps<typeof Theme>;
|
containerProps?: ComponentProps<typeof Theme>;
|
||||||
noFlex1?: boolean;
|
noFlex1?: boolean;
|
||||||
padding?: RadixSize;
|
|
||||||
noMinHeight?: boolean;
|
noMinHeight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,7 +16,6 @@ export function PageWrapper({
|
|||||||
noFlex1 = false,
|
noFlex1 = false,
|
||||||
containerProps,
|
containerProps,
|
||||||
noMinHeight = false,
|
noMinHeight = false,
|
||||||
padding = '4',
|
|
||||||
...props
|
...props
|
||||||
}: PageWrapperProps) {
|
}: PageWrapperProps) {
|
||||||
return (
|
return (
|
||||||
@@ -35,8 +32,9 @@ export function PageWrapper({
|
|||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
gap="4"
|
gap="8"
|
||||||
p={padding}
|
px="4"
|
||||||
|
py="8"
|
||||||
width="100%"
|
width="100%"
|
||||||
maxWidth="80rem"
|
maxWidth="80rem"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { fetchMetadata } from '@blitzkit/closed';
|
import { fetchMetadata } from '@blitzkit/closed';
|
||||||
import { BlitzkitServerAPI, MetadataAccessor } from '@blitzkit/core';
|
import {
|
||||||
|
assertSecret,
|
||||||
|
BlitzkitServerAPI,
|
||||||
|
MetadataAccessor,
|
||||||
|
} from '@blitzkit/core';
|
||||||
|
|
||||||
const metadata = await fetchMetadata();
|
const metadata = await fetchMetadata(
|
||||||
|
assertSecret(import.meta.env.AUTO_PROXY_ORIGIN),
|
||||||
|
);
|
||||||
const accessor = new MetadataAccessor(metadata);
|
const accessor = new MetadataAccessor(metadata);
|
||||||
export const api = new BlitzkitServerAPI(accessor);
|
export const api = new BlitzkitServerAPI(accessor);
|
||||||
@@ -8,6 +8,8 @@ export const ffi = Library(FFI_PATH, {
|
|||||||
debug_echo: [types.CString, [types.CString]],
|
debug_echo: [types.CString, [types.CString]],
|
||||||
mount_game: ['void', [types.CString]],
|
mount_game: ['void', [types.CString]],
|
||||||
debug_get_file_count: [types.int, []],
|
debug_get_file_count: [types.int, []],
|
||||||
|
get_bindings_url_production: [types.CString, []],
|
||||||
|
get_bindings_url_dev: [types.CString, []],
|
||||||
});
|
});
|
||||||
|
|
||||||
ffi.mount_game(assertSecret(import.meta.env.GAME_MOUNT_POINT));
|
ffi.mount_game(assertSecret(import.meta.env.GAME_MOUNT_POINT));
|
||||||
@@ -1,14 +1,49 @@
|
|||||||
import { Heading } from '@radix-ui/themes';
|
import { Link, Table } from '@radix-ui/themes';
|
||||||
import { FFIEcho } from '../../components/Internal/FFIEcho';
|
|
||||||
import { FFIFileCount } from '../../components/Internal/FFIFileCount';
|
|
||||||
import { PageWrapper } from '../../components/PageWrapper';
|
import { PageWrapper } from '../../components/PageWrapper';
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
return (
|
return (
|
||||||
<PageWrapper color="amber">
|
<PageWrapper align="center">
|
||||||
<Heading>BlitzKit FFI</Heading>
|
<Table.Root>
|
||||||
<FFIEcho />
|
<Table.Header>
|
||||||
<FFIFileCount />
|
<Table.Row>
|
||||||
|
<Table.ColumnHeaderCell>Path</Table.ColumnHeaderCell>
|
||||||
|
<Table.ColumnHeaderCell>Memo</Table.ColumnHeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
|
||||||
|
<Table.Body>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>
|
||||||
|
<Link href="/internal/debug">Debug</Link>
|
||||||
|
</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>
|
||||||
|
Some debug information to check if everything is up
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>
|
||||||
|
<Link href="/internal/bindings">Bindings</Link>
|
||||||
|
</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>List of all active bindings</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>
|
||||||
|
<Link href="/internal/discovery">Discovery</Link>
|
||||||
|
</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>List of all active discovery lists</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
|
||||||
|
<Table.Row>
|
||||||
|
<Table.RowHeaderCell>
|
||||||
|
<Link href="/internal/differ">Differ</Link>
|
||||||
|
</Table.RowHeaderCell>
|
||||||
|
<Table.Cell>Differentiate two branches</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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 (
|
||||||
|
<PageWrapper>
|
||||||
|
<Bindings url={JSON.parse(ffi.get_bindings_url_dev()!)} dev />
|
||||||
|
<Separator size="4" />
|
||||||
|
<Bindings url={JSON.parse(ffi.get_bindings_url_production()!)} />
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function Bindings({ url, dev }: { url: string; dev?: boolean }) {
|
||||||
|
const data = await fetch(url).then(
|
||||||
|
(response) => response.json() as Promise<Bindings>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text>
|
||||||
|
Bindings URL{' '}
|
||||||
|
<Link href={url} target="_blank">
|
||||||
|
{url}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{data.bindings.map((binding) => (
|
||||||
|
<Flex direction="column" gap="2" key={binding.name}>
|
||||||
|
<Heading>
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
{binding.env}
|
||||||
|
|
||||||
|
{!dev && (
|
||||||
|
<Badge variant="solid" color="blue">
|
||||||
|
Production
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{dev && (
|
||||||
|
<Badge variant="solid" color="orange">
|
||||||
|
Dev
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{binding.show_server_selection && (
|
||||||
|
<Badge color="green">Public</Badge>
|
||||||
|
)}
|
||||||
|
{!binding.show_server_selection && (
|
||||||
|
<Badge color="red">Private</Badge>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<Text>{binding.name}</Text>
|
||||||
|
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text>Discovery:</Text>
|
||||||
|
<Link href={binding.discovery} target="_blank">
|
||||||
|
{binding.discovery}
|
||||||
|
</Link>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex gap="2">
|
||||||
|
<Text>Versions:</Text>
|
||||||
|
|
||||||
|
{binding.versions.map((version) => (
|
||||||
|
<Badge variant="outline" color="gray" key={version}>
|
||||||
|
{version}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
import InternalBaseLayout from 'packages/website-ue/src/layouts/InternalBaseLayout.astro';
|
||||||
|
import { Page } from './_index';
|
||||||
|
---
|
||||||
|
|
||||||
|
<InternalBaseLayout>
|
||||||
|
<Page />
|
||||||
|
</InternalBaseLayout>
|
||||||
@@ -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 (
|
||||||
|
<PageWrapper color="amber">
|
||||||
|
<Heading>BlitzKit FFI</Heading>
|
||||||
|
<FFIEcho />
|
||||||
|
<FFIFileCount />
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
import InternalBaseLayout from 'packages/website-ue/src/layouts/InternalBaseLayout.astro';
|
||||||
|
import { Page } from './_index';
|
||||||
|
---
|
||||||
|
|
||||||
|
<InternalBaseLayout>
|
||||||
|
<Page />
|
||||||
|
</InternalBaseLayout>
|
||||||
@@ -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<DiscoveryServer | undefined>(undefined);
|
||||||
|
const [serverB, setSeverB] = useState<DiscoveryServer | undefined>(undefined);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [diff, setDiff] = useState<CatalogDiffEntry[] | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper justify="center">
|
||||||
|
<Flex justify="center" align="center" gap="2">
|
||||||
|
<Select.Root
|
||||||
|
size="3"
|
||||||
|
defaultValue="undefined"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSeverA(servers.find((server) => server.name === value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger style={{ flex: 1, maxWidth: '16rem' }} />
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Item value="undefined">Select base server</Select.Item>
|
||||||
|
|
||||||
|
{servers.map((server) => (
|
||||||
|
<Select.Item key={server.name} value={server.name}>
|
||||||
|
{server.name}
|
||||||
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
size="3"
|
||||||
|
disabled={!serverA || !serverB}
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const diff = await diffServers(serverA!.addr, serverB!.addr);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
setDiff(diff);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? <Spinner /> : <CommitIcon />}
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Select.Root
|
||||||
|
size="3"
|
||||||
|
defaultValue="undefined"
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSeverB(servers.find((server) => server.name === value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Trigger style={{ flex: 1, maxWidth: '16rem' }} />
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Item value="undefined">Select base server</Select.Item>
|
||||||
|
|
||||||
|
{servers.map((server) => (
|
||||||
|
<Select.Item key={server.name} value={server.name}>
|
||||||
|
{server.name}
|
||||||
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{diff !== null && (
|
||||||
|
<DiffDisplay
|
||||||
|
diff={diff.filter(
|
||||||
|
(entry) => entry.type !== CatalogItemDiffType.Unmodified,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DiffDisplay({ diff }: { diff: CatalogDiffEntry[] }) {
|
||||||
|
return (
|
||||||
|
<Flex direction="column" gap="4">
|
||||||
|
{diff.length === 0 && (
|
||||||
|
<Text align="center" color="gray">
|
||||||
|
Branches are identical
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{diff.map((entry) => (
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
gap="2"
|
||||||
|
style={{
|
||||||
|
backgroundColor: Var('gray-3'),
|
||||||
|
boxShadow: Var('shadow-3'),
|
||||||
|
borderRadius: Var('radius-3'),
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box p="3">
|
||||||
|
<Text>
|
||||||
|
{diffEmojis[entry.type]} {entry.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Flex gap="1">
|
||||||
|
{(entry.type === CatalogItemDiffType.Removed ||
|
||||||
|
entry.type === CatalogItemDiffType.Unmodified) && (
|
||||||
|
<ItemDisplay item={entry.item} />
|
||||||
|
)}
|
||||||
|
{entry.type === CatalogItemDiffType.Modified && (
|
||||||
|
<ItemDisplay item={entry.a} />
|
||||||
|
)}
|
||||||
|
{(entry.type === CatalogItemDiffType.Added ||
|
||||||
|
entry.type === CatalogItemDiffType.Unmodified) && (
|
||||||
|
<ItemDisplay item={entry.item} />
|
||||||
|
)}
|
||||||
|
{entry.type === CatalogItemDiffType.Modified && (
|
||||||
|
<ItemDisplay item={entry.b} />
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemDisplay({ item }: { item: CatalogItemAccessor }) {
|
||||||
|
const entries = Object.entries(item.components);
|
||||||
|
|
||||||
|
protos;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
direction="column"
|
||||||
|
gap="4"
|
||||||
|
flexGrow="1"
|
||||||
|
p="3"
|
||||||
|
style={{
|
||||||
|
backgroundColor: Var('gray-2'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{entries.length === 0 && <Text color="gray">No components</Text>}
|
||||||
|
|
||||||
|
{entries.map(([key, value]) => (
|
||||||
|
<ComponentDisplay key={key} name={key} value={value} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ComponentDisplay({ name, value }: { name: string; value: Any }) {
|
||||||
|
const [code, setCode] = useState<string | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction="column" gap="1">
|
||||||
|
<Flex gap="2" align="center">
|
||||||
|
<IconButton
|
||||||
|
size="1"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
const messageName = value.type_url
|
||||||
|
.slice(2)
|
||||||
|
.split('.')
|
||||||
|
.slice(1)
|
||||||
|
.join('.');
|
||||||
|
const json = (
|
||||||
|
protos[messageName as keyof typeof protos] as MessageFns<any>
|
||||||
|
).decode(value.value);
|
||||||
|
|
||||||
|
setCode(JSON.stringify(json, null, 2));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ReaderIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Code color="gray" highContrast variant="ghost">
|
||||||
|
{name}
|
||||||
|
</Code>
|
||||||
|
<Badge>{value.value.length.toLocaleString()}B</Badge>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{code !== null && (
|
||||||
|
<Code
|
||||||
|
ml="4"
|
||||||
|
size="1"
|
||||||
|
color="gray"
|
||||||
|
highContrast
|
||||||
|
style={{ whiteSpace: 'pre' }}
|
||||||
|
>
|
||||||
|
{code}
|
||||||
|
</Code>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CatalogItemDiffType {
|
||||||
|
Unmodified,
|
||||||
|
Modified,
|
||||||
|
Added,
|
||||||
|
Removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
type CatalogDiffEntry = { name: string } & (
|
||||||
|
| {
|
||||||
|
type: CatalogItemDiffType.Modified;
|
||||||
|
a: CatalogItemAccessor;
|
||||||
|
b: CatalogItemAccessor;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: Exclude<CatalogItemDiffType, CatalogItemDiffType.Modified>;
|
||||||
|
item: CatalogItemAccessor;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const diffEmojis: Record<CatalogItemDiffType, string> = {
|
||||||
|
[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<string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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<string, DiscoveryServer>();
|
||||||
|
|
||||||
|
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<Bindings>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
bindings.map(async (binding) => {
|
||||||
|
const discovery = await fetch(binding.discovery).then(
|
||||||
|
(response) => response.json() as Promise<Discovery>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
---
|
||||||
|
|
||||||
|
<InternalBaseLayout>
|
||||||
|
<Page {servers} client:load />
|
||||||
|
</InternalBaseLayout>
|
||||||
@@ -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<string, DiscoveryServer>;
|
||||||
|
icmp_servers: ICMPServers;
|
||||||
|
clusters_versions: Record<string, Empty[]>;
|
||||||
|
countries_by_clusters: CountriesByClusters;
|
||||||
|
extensions: Extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Empty {
|
||||||
|
version: string;
|
||||||
|
server: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CountriesByClusters = Record<string, { countries: string[] }>;
|
||||||
|
|
||||||
|
interface Extensions {
|
||||||
|
endpoints: Record<string, Endpoint>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Endpoint {
|
||||||
|
playability_config_url: string;
|
||||||
|
fullscreen_notifications_url: string;
|
||||||
|
mgg_frontier_url: string;
|
||||||
|
mgg_pay_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICMPServers {
|
||||||
|
default: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Bindings>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
bindings.map(async (binding) => {
|
||||||
|
const discovery = await fetch(binding.discovery).then(
|
||||||
|
(response) => response.json() as Promise<Discovery>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<PageWrapper>
|
||||||
|
{servers.map((server) => (
|
||||||
|
<Flex direction="column" key={server.addr}>
|
||||||
|
<Heading>{server.name}</Heading>
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
Address:{' '}
|
||||||
|
<Link href={server.addr} target="_blank">
|
||||||
|
{server.addr}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{server.addr_dsapp && (
|
||||||
|
<Text>
|
||||||
|
DSApp:{' '}
|
||||||
|
<Link href={server.addr_dsapp} target="_blank">
|
||||||
|
{server.addr_dsapp}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
import InternalBaseLayout from 'packages/website-ue/src/layouts/InternalBaseLayout.astro';
|
||||||
|
import { Page } from './_index';
|
||||||
|
---
|
||||||
|
|
||||||
|
<InternalBaseLayout>
|
||||||
|
<Page />
|
||||||
|
</InternalBaseLayout>
|
||||||
@@ -7,8 +7,10 @@
|
|||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../submodules/CUE4Parse/CUE4Parse/CUE4Parse.csproj" />
|
<ProjectReference Include="../../submodules/CUE4Parse/CUE4Parse/CUE4Parse.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ini-parser" Version="2.5.2" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
+14
-1
@@ -2,7 +2,6 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using BlitzKit.FFI.Models;
|
using BlitzKit.FFI.Models;
|
||||||
using BlitzKit.FFI.Utils;
|
using BlitzKit.FFI.Utils;
|
||||||
using CUE4Parse.Compression;
|
|
||||||
|
|
||||||
namespace BlitzKit.FFI;
|
namespace BlitzKit.FFI;
|
||||||
|
|
||||||
@@ -32,4 +31,18 @@ public static class FFI
|
|||||||
{
|
{
|
||||||
return provider.Files.Count;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using BlitzKit.FFI.Utils;
|
||||||
using CUE4Parse.FileProvider;
|
using CUE4Parse.FileProvider;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
|
|
||||||
@@ -18,24 +18,14 @@ namespace BlitzKit.FFI.Models
|
|||||||
{
|
{
|
||||||
Initialize();
|
Initialize();
|
||||||
Mount();
|
Mount();
|
||||||
|
|
||||||
Console.WriteLine(DiscoveryURL());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DiscoveryURL()
|
public INIAccessor GetDefaultEngine() => new(this["Blitz/Config/DefaultEngine.ini"]);
|
||||||
{
|
|
||||||
if (TryGetGameFile("Blitz/Config/DefaultEngine.ini", out var defaultEngine))
|
|
||||||
{
|
|
||||||
INIAccessor accessor = new(Encoding.UTF8.GetString(defaultEngine.Read()));
|
|
||||||
|
|
||||||
var url = (string)accessor.Get("DiscoveryURLDedicProduction");
|
public string GetBindingsURLProduction() =>
|
||||||
|
GetDefaultEngine()["/Script/MintSDK.MintSDKSettingsObject"]["DiscoveryBindingsURLProduction"];
|
||||||
|
|
||||||
return url;
|
public string GetBindingsURLDev() =>
|
||||||
}
|
GetDefaultEngine()["/Script/MintSDK.MintSDKSettingsObject"]["DiscoveryBindingsURLDev"];
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException("Could not find DefaultEngine.ini");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace BlitzKit.FFI.Models
|
|
||||||
{
|
|
||||||
public class INIAccessor
|
|
||||||
{
|
|
||||||
private Dictionary<string, object> 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<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = (List<string>)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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
Submodule submodules/blitzkit-closed updated: 89dc337d27...69df3b07a2.
Reference in new issue
Block a user