This commit is contained in:
Robin Appelman 2022-09-03 17:06:57 +02:00
commit 8de1835d29
10 changed files with 282 additions and 73 deletions

View file

@ -2,9 +2,11 @@ import {Packet} from "./parser";
import React, {useCallback, Component} from 'react'
import {useDropzone} from 'react-dropzone'
import ReactDOM from "react-dom";
import { createRoot } from 'react-dom/client';
import {Header} from "./header";
import {PacketDetails, PacketTable} from "./table";
import {filterPacket, Search, SearchBar} from "./search";
import {filterPacket, isSearchEmpty, SearchBar, SearchFilter} from "./search";
import {ResponseMessageData} from "./rpc";
let _style = require('../styles/style.css');
@ -34,15 +36,11 @@ interface AppState {
class_names: Map<number, string>,
active: Packet | null,
activeIndex: number | null,
search: Search,
search: SearchFilter,
matches: number[],
worker: Worker
}
type MessageData = { type: "progress", progress: number }
| { type: "packet", packet: Packet }
| { type: "done", packets: PacketMeta[], header: Header, prop_names: { identifier: number, table: string, prop: string }[], class_names: { identifier: number, name: string }[] }
| { type: "packet_names", packet: {} };
class App extends Component<{}, AppState> {
state: AppState = {
loading: false,
@ -55,20 +53,27 @@ class App extends Component<{}, AppState> {
activeIndex: null,
search: {
entity: 0,
filter: "",
classIds: [],
propIds: [],
search: "",
class_ids: [],
prop_ids: [],
},
matches: [],
worker: null
}
onSearch = debounce((search: Search) => this.setState({search}), 500)
onSearch = debounce((search: SearchFilter) => {
if (!isSearchEmpty(search)) {
console.log(search);
this.state.worker.postMessage({type: "search", filter: search});
}
this.setState({search});
}, 500)
load(data: ArrayBuffer) {
this.setState({loading: true});
const worker = new Worker('./worker.js');
this.setState({worker});
worker.addEventListener("message", (event: MessageEvent<MessageData>) => {
worker.addEventListener("message", (event: MessageEvent<ResponseMessageData>) => {
const data = event.data;
if (data.type !== "progress") {
console.log(data);
@ -96,7 +101,11 @@ class App extends Component<{}, AppState> {
});
break;
case "packet":
this.setState({active: data.packet})
this.setState({active: data.packet});
break;
case "search_result":
this.setState({matches: data.matches});
break;
}
});
worker.postMessage({
@ -106,12 +115,11 @@ class App extends Component<{}, AppState> {
}
filteredPackets(): PacketMeta[] {
return this.state.packets;
// if (this.state.search.filter || this.state.search.entity) {
// return this.state.packets.filter(packet => filterPacket(packet, this.state.search));
// } else {
// return this.state.packets;
// }
if (isSearchEmpty(this.state.search)) {
return this.state.packets;
} else {
return this.state.matches.map(index => this.state.packets[index]);
}
}
render() {
@ -130,6 +138,7 @@ class App extends Component<{}, AppState> {
)
} else if (this.state.packets.length) {
let active = <></>;
const packets = this.filteredPackets();
if (this.state.active) {
active = <div className="details"><PacketDetails packet={this.state.active}
search={this.state.search}
@ -141,7 +150,7 @@ class App extends Component<{}, AppState> {
<SearchBar onSearch={this.onSearch} class_names={this.state.class_names}
prop_names={this.state.prop_names}/>
<div className="packets">
<PacketTable packets={this.filteredPackets()} class_names={this.state.class_names}
<PacketTable packets={packets} class_names={this.state.class_names}
activeIndex={this.state.activeIndex}
prop_names={this.state.prop_names}
onClick={(index) => {
@ -161,12 +170,9 @@ class App extends Component<{}, AppState> {
}
}
ReactDOM.render(
<App/>
,
document.getElementById("root")
);
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App/>);
function DemoDropzone({onDrop}: { onDrop: (data: ArrayBuffer) => void }) {
const onDropCb = useCallback((acceptedFiles: File[]) => {

View file

@ -1,12 +1,12 @@
import {EventInfo, GameEventDefinition, Message, PacketEntity, SendPropValue} from "../parser";
import React from "react";
import {filterEntity, filterMessage, Search} from "../search";
import {filterEntity, filterMessage, isSearchEmpty, SearchFilter} from "../search";
export interface MessageInfoProps {
msg: Message,
prop_names: Map<number, { table: String, prop: String }>,
class_names: Map<number, String>,
search: Search
search: SearchFilter
}
export function MessageInfo({msg, prop_names, class_names, search}: MessageInfoProps) {
@ -105,22 +105,22 @@ function formatEventDefinition(event: GameEventDefinition): string {
return `${event.event_type}{${values.join(', ')}}`;
}
function filteredEntities(entities: PacketEntity[], search: Search) {
if (search.filter || search.entity) {
function filteredEntities(entities: PacketEntity[], search: SearchFilter) {
if (!isSearchEmpty(search)) {
return entities.filter(entities => {
return (search.entity == 0 || search.entity == entities.entity_index) &&
(search.filter.length < 3 || filterEntity(entities.server_class, entities.baseline_props.concat(entities.props), search))
(search.search.length < 3 || filterEntity(entities.server_class, entities.props, search))
});
} else {
return entities;
}
}
function filteredTempEntities(entities: EventInfo[], search: Search) {
function filteredTempEntities(entities: EventInfo[], search: SearchFilter) {
if (search.entity) {
return [];
}
if (search.filter) {
if (!isSearchEmpty(search)) {
return entities.filter(entities => filterEntity(entities.class_id, entities.props, search));
} else {
return entities;

14
www/src/rpc.ts Normal file
View file

@ -0,0 +1,14 @@
import {SearchFilter} from "./search";
import {Packet} from "./parser";
import {Header} from "./header";
import {PacketMeta} from "./index";
export type RequestMessageData = {type: "data", data: ArrayBuffer}
| {type: "get", packet: number}
| {type: "search", filter: SearchFilter}
export type ResponseMessageData = { type: "progress", progress: number }
| { type: "packet", packet: Packet }
| { type: "done", packets: PacketMeta[], header: Header, prop_names: { identifier: number, table: string, prop: string }[], class_names: { identifier: number, name: string }[] }
| { type: "packet_names", packet: {} }
| { type: "search_result", matches: number[] };

View file

@ -3,15 +3,15 @@ import React, {ChangeEvent, Component} from "react";
import './search.css'
import {Message, Packet, SendProp, StringTable} from "./parser";
export interface Search {
filter: string,
export interface SearchFilter {
entity: number,
propIds: number[],
classIds: number[],
search: string,
prop_ids: number[],
class_ids: number[],
}
export interface SearchBarProps {
onSearch: (search: Search) => void,
onSearch: (search: SearchFilter) => void,
prop_names: Map<number, { table: string, prop: string }>,
class_names: Map<number, string>,
}
@ -27,12 +27,12 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
entity: 0
}
getSearch(): Search {
getSearch(): SearchFilter {
return {
filter: this.state.filter,
search: this.state.filter,
entity: this.state.entity,
propIds: filterPropNames(this.props.prop_names, this.state.filter),
classIds: filterClassNames(this.props.class_names, this.state.filter),
prop_ids: filterPropNames(this.props.prop_names, this.state.filter),
class_ids: filterClassNames(this.props.class_names, this.state.filter),
}
}
@ -70,7 +70,7 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
export function filterPacket(
packet: Packet,
search: Search,
search: SearchFilter,
): boolean {
switch (packet.type) {
case "Signon":
@ -79,7 +79,7 @@ export function filterPacket(
case "SyncTick":
return false;
case "ConsoleCmd":
return search.entity == 0 && packet.command.includes(search.filter);
return search.entity == 0 && packet.command.includes(search.search);
case "UserCmd":
return false;
case "DataTables":
@ -92,6 +92,9 @@ export function filterPacket(
}
function filterPropNames(prop_names: Map<number, { table: string, prop: string }>, filter: string): number[] {
if (filter.length === 0) {
return [];
}
filter = filter.toLowerCase();
let ids = [];
for (let [id, {table, prop}] of prop_names.entries()) {
@ -103,6 +106,9 @@ function filterPropNames(prop_names: Map<number, { table: string, prop: string }
}
function filterClassNames(class_names: Map<number, string>, filter: string): number[] {
if (filter.length === 0) {
return [];
}
filter = filter.toLowerCase();
let ids = [];
for (let [id, name] of class_names.entries()) {
@ -115,54 +121,64 @@ function filterClassNames(class_names: Map<number, string>, filter: string): num
export function filterMessage(
message: Message,
search: Search,
search: SearchFilter,
): boolean {
switch (message.type) {
case "File":
return search.entity == 0 && message.file_name.includes(search.filter);
return search.entity == 0 && message.file_name.includes(search.search);
case "StringCmd":
return search.entity == 0 && message.command.includes(search.filter);
return search.entity == 0 && message.command.includes(search.search);
case "SetConVar":
return search.entity == 0 && message.vars.some(cvar => cvar.value.includes(search.filter) || cvar.key.includes(search.filter));
return search.entity == 0 && message.vars.some(cvar => cvar.value.includes(search.search) || cvar.key.includes(search.search));
case "Print":
return search.entity == 0 && message.value.includes(search.filter);
return search.entity == 0 && message.value.includes(search.search);
case "ClassInfo":
return search.entity == 0 && message.entries.some(entry => entry.class_name.includes(search.filter) || entry.table_name.includes(search.filter));
return search.entity == 0 && message.entries.some(entry => entry.class_name.includes(search.search) || entry.table_name.includes(search.search));
case "CreateStringTable":
return search.entity == 0 && filterStringTable(message.table, search);
case "UpdateStringTable":
return search.entity == 0 && message.entries.some(([_index, entry]) => (entry.text && entry.text.includes(search.filter)));
return search.entity == 0 && message.entries.some(([_index, entry]) => (entry.text && entry.text.includes(search.search)));
case "SetView":
return search.entity == 0 && message.index === search.entity;
case "SayText2":
return search.entity == 0 && ((message.text && message.text.includes(search.filter)) || (message.from && message.from.includes(search.filter)));
return search.entity == 0 && ((message.text && message.text.includes(search.search)) || (message.from && message.from.includes(search.search)));
case "Text":
return search.entity == 0 && message.text.includes(search.filter);
return search.entity == 0 && message.text.includes(search.search);
case "EntityMessage":
return search.entity == 0 && search.classIds.includes(message.class_id)
return search.entity == 0 && search.class_ids.includes(message.class_id)
case "GameEvent":
return search.entity == 0 && message.event.type.includes(search.filter)
return search.entity == 0 && message.event.type.includes(search.search)
case "PacketEntities":
return message.removed_entities.includes(search.entity) || message.entities.some(entity => (search.entity == 0 || entity.entity_index == search.entity) && filterEntity(entity.server_class, entity.baseline_props.concat(entity.props), search))
return message.removed_entities.includes(search.entity)
|| message.entities.some(entity => (search.entity == 0 || entity.entity_index == search.entity)
&& filterEntity(entity.server_class, entity.props, search))
case "TempEntities":
return search.entity == 0 && message.events.some(event => filterEntity(event.class_id, event.props, search))
case "GetCvarValue":
return search.entity == 0 && message.value.includes(search.filter);
return search.entity == 0 && message.value.includes(search.search);
default:
return false;
}
}
export function filterEntity(class_id: number, props: SendProp[], search: Search): boolean {
return search.classIds.includes(class_id) || props.some(prop => search.propIds.includes(prop.identifier))
|| props.some(prop => prop.value == search.filter);
export function filterEntity(class_id: number, props: SendProp[], search: SearchFilter): boolean {
if (search.search.length === 0 && search.class_ids.length === 0 && search.prop_ids.length === 0) {
return true;
}
return search.class_ids.includes(class_id) || props.some(prop => search.prop_ids.includes(prop.identifier))
|| props.some(prop => prop.value == search.search);
}
function filterStringTable(table: StringTable, search: Search): boolean {
if (table.name.includes(search.filter)) {
function filterStringTable(table: StringTable, search: SearchFilter): boolean {
if (table.name.includes(search.search)) {
return true;
} else if (table.entries.some(([_index, entry]) => entry.text.includes(search.filter))) {
} else if (table.entries.some(([_index, entry]) => entry.text.includes(search.search))) {
return true;
}
return false;
}
export function isSearchEmpty(filter: SearchFilter) {
return filter.search.length === 0 && filter.entity === 0 && filter.class_ids.length === 0 && filter.prop_ids.length === 0
}

View file

@ -3,7 +3,7 @@ import {GameEventDefinition, Message, Packet, PacketEntity, SendPropValue, UserC
import {FixedSizeList as List} from 'react-window';
import {MessageInfo} from "./packets/message";
import {UserCmdDetails} from "./packets/usercmd";
import {filterMessage, filterPacket, Search} from "./search";
import {filterMessage, filterPacket, SearchFilter} from "./search";
import {PacketMeta, PacketType} from "./index"
interface TableProps {
@ -81,11 +81,11 @@ interface DetailProps {
packet: Packet,
prop_names: Map<number, { table: string, prop: string }>,
class_names: Map<number, string>,
search: Search,
search: SearchFilter,
}
function filteredMessages(messages: Message[], search: Search) {
if (search.filter || search.entity) {
function filteredMessages(messages: Message[], search: SearchFilter) {
if (search.search || search.entity) {
return messages.filter(message => filterMessage(message, search));
} else {
return messages;

View file

@ -1,12 +1,11 @@
import {Parser} from "demo-inspector";
import {RequestMessageData, ResponseMessageData} from "./rpc";
declare function postMessage(message: any): void;
declare function postMessage(message: ResponseMessageData): void;
let parser: Parser | null = null;
type MessageData = {type: "data", data: ArrayBuffer} | {type: "get", packet: number}
onmessage = function (event: MessageEvent<MessageData>) {
onmessage = function (event: MessageEvent<RequestMessageData>) {
const data = event.data;
switch (data.type) {
case "data":
@ -24,6 +23,12 @@ onmessage = function (event: MessageEvent<MessageData>) {
})
})
break;
case "search":
if (parser) {
const matches = parser.search(data.filter);
postMessage({type: "search_result", matches: Array.prototype.slice.call(matches)})
}
break;
case "get":
if (parser) {
const packet = parser.packet(data.packet);