mirror of
https://codeberg.org/demostf/inspector.git
synced 2026-06-03 10:04:09 +02:00
search
This commit is contained in:
parent
6dfc8bc4fb
commit
8de1835d29
10 changed files with 282 additions and 73 deletions
|
|
@ -37,3 +37,6 @@ wasm-bindgen-test = "0.3.13"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
|
[package.metadata.wasm-pack.profile.release]
|
||||||
|
wasm-opt = true
|
||||||
14
src/lib.rs
14
src/lib.rs
|
|
@ -1,14 +1,15 @@
|
||||||
|
mod search;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use crate::utils::set_panic_hook;
|
use crate::search::{packet_matches, SearchFilter};
|
||||||
use bitbuffer::{BitRead, BitReadBuffer, BitReadStream, LittleEndian};
|
use bitbuffer::{BitRead, BitReadBuffer, BitReadStream, LittleEndian};
|
||||||
use js_sys::Function;
|
use js_sys::Function;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tf_demo_parser::demo::header::Header;
|
use tf_demo_parser::demo::header::Header;
|
||||||
use tf_demo_parser::demo::packet::datatable::{DataTablePacket, SendTableName, ServerClassName};
|
use tf_demo_parser::demo::packet::datatable::{DataTablePacket, SendTableName, ServerClassName};
|
||||||
use tf_demo_parser::demo::packet::Packet;
|
use tf_demo_parser::demo::packet::Packet;
|
||||||
|
use tf_demo_parser::demo::parser::DemoHandler;
|
||||||
use tf_demo_parser::demo::parser::RawPacketStream;
|
use tf_demo_parser::demo::parser::RawPacketStream;
|
||||||
use tf_demo_parser::demo::parser::{DemoHandler, NullHandler};
|
|
||||||
use tf_demo_parser::demo::sendprop::SendPropName;
|
use tf_demo_parser::demo::sendprop::SendPropName;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
|
@ -144,6 +145,15 @@ impl Parser {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn search(&self, filter: JsValue) -> Vec<usize> {
|
||||||
|
let filter: SearchFilter = filter.into_serde().unwrap();
|
||||||
|
self.packets
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, packet)| packet_matches(packet, &filter).then_some(index))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
|
|
||||||
154
src/search.rs
Normal file
154
src/search.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tf_demo_parser::demo::message::gameevent::GameEventMessage;
|
||||||
|
use tf_demo_parser::demo::message::packetentities::{PacketEntitiesMessage, PacketEntity};
|
||||||
|
use tf_demo_parser::demo::message::setconvar::SetConVarMessage;
|
||||||
|
use tf_demo_parser::demo::message::stringtable::{
|
||||||
|
CreateStringTableMessage, UpdateStringTableMessage,
|
||||||
|
};
|
||||||
|
use tf_demo_parser::demo::message::tempentities::TempEntitiesMessage;
|
||||||
|
use tf_demo_parser::demo::message::{
|
||||||
|
EntityMessage, FileMessage, GetCvarValueMessage, Message, PrintMessage, SetViewMessage,
|
||||||
|
StringCmdMessage,
|
||||||
|
};
|
||||||
|
use tf_demo_parser::demo::packet::consolecmd::ConsoleCmdPacket;
|
||||||
|
use tf_demo_parser::demo::packet::message::MessagePacket;
|
||||||
|
use tf_demo_parser::demo::packet::stringtable::{StringTableEntry, StringTablePacket};
|
||||||
|
use tf_demo_parser::demo::packet::Packet;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SearchFilter {
|
||||||
|
pub entity: u32,
|
||||||
|
pub search: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub prop_ids: Vec<u64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub class_ids: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchFilter {
|
||||||
|
pub fn has_entity_filter(&self) -> bool {
|
||||||
|
!self.search.is_empty() || !self.prop_ids.is_empty() || !self.class_ids.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn packet_matches(packet: &Packet, filter: &SearchFilter) -> bool {
|
||||||
|
// return false;
|
||||||
|
// if packet
|
||||||
|
// .packet_type()
|
||||||
|
// .as_lowercase_str()
|
||||||
|
// .contains(&filter.search)
|
||||||
|
// {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
match packet {
|
||||||
|
Packet::Signon(MessagePacket { messages, .. })
|
||||||
|
| Packet::Message(MessagePacket { messages, .. }) => messages
|
||||||
|
.iter()
|
||||||
|
.any(|message| message_matches(message, filter)),
|
||||||
|
Packet::SyncTick(_) => false,
|
||||||
|
Packet::ConsoleCmd(ConsoleCmdPacket { command, .. }) => command.contains(&filter.search),
|
||||||
|
Packet::UserCmd(_) => false,
|
||||||
|
Packet::DataTables(_) => false,
|
||||||
|
Packet::Stop(_) => false,
|
||||||
|
Packet::StringTables(StringTablePacket { tables, .. }) => tables.iter().any(|table| {
|
||||||
|
table.name.contains(&filter.search)
|
||||||
|
|| table.entries.iter().any(|(_, entry)| {
|
||||||
|
entry
|
||||||
|
.text
|
||||||
|
.as_deref()
|
||||||
|
.map(|text| text.contains(&filter.search))
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message_matches(message: &Message, filter: &SearchFilter) -> bool {
|
||||||
|
let has_search = !filter.search.is_empty();
|
||||||
|
match message {
|
||||||
|
Message::File(FileMessage { file_name, .. }) => {
|
||||||
|
has_search && file_name.contains(&filter.search)
|
||||||
|
}
|
||||||
|
Message::StringCmd(StringCmdMessage { command, .. }) => {
|
||||||
|
has_search && command.contains(&filter.search)
|
||||||
|
}
|
||||||
|
Message::SetConVar(SetConVarMessage { vars, .. }) => {
|
||||||
|
has_search
|
||||||
|
&& vars.iter().any(|var| {
|
||||||
|
var.key.contains(&filter.search) || var.value.contains(&filter.search)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Message::Print(PrintMessage { value }) => {
|
||||||
|
has_search && value.as_ref().contains(&filter.search)
|
||||||
|
}
|
||||||
|
Message::CreateStringTable(CreateStringTableMessage { table, .. }) => {
|
||||||
|
has_search && table.name.contains(&filter.search)
|
||||||
|
|| table
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.any(|(_, entry)| string_entry_matches(entry, filter))
|
||||||
|
}
|
||||||
|
Message::UpdateStringTable(UpdateStringTableMessage { entries, .. }) => {
|
||||||
|
has_search
|
||||||
|
&& entries
|
||||||
|
.iter()
|
||||||
|
.any(|(_, entry)| string_entry_matches(entry, filter))
|
||||||
|
}
|
||||||
|
Message::SetView(SetViewMessage { index }) => (*index as u32) == filter.entity,
|
||||||
|
Message::UserMessage(_) => false,
|
||||||
|
Message::EntityMessage(EntityMessage { class_id, .. }) => {
|
||||||
|
filter.class_ids.contains(&(*class_id as u32))
|
||||||
|
}
|
||||||
|
Message::GameEvent(GameEventMessage { event, .. }) => {
|
||||||
|
has_search && event.event_type().as_str().contains(&filter.search)
|
||||||
|
}
|
||||||
|
Message::PacketEntities(PacketEntitiesMessage {
|
||||||
|
entities,
|
||||||
|
removed_entities,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
(removed_entities.contains(&filter.entity.into()) && !filter.has_entity_filter())
|
||||||
|
|| entities.iter().any(|entity| entity_matches(entity, filter))
|
||||||
|
}
|
||||||
|
Message::TempEntities(TempEntitiesMessage { events }) => events.iter().any(|event| {
|
||||||
|
filter
|
||||||
|
.class_ids
|
||||||
|
.contains(&(u16::from(event.class_id).into()))
|
||||||
|
}),
|
||||||
|
Message::GetCvarValue(GetCvarValueMessage { value, .. }) => {
|
||||||
|
has_search && value.contains(&filter.search)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_entry_matches(entry: &StringTableEntry, filter: &SearchFilter) -> bool {
|
||||||
|
(!filter.search.is_empty())
|
||||||
|
&& entry
|
||||||
|
.text
|
||||||
|
.as_deref()
|
||||||
|
.map(|text| text.contains(&filter.search))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entity_matches(entity: &PacketEntity, filter: &SearchFilter) -> bool {
|
||||||
|
if entity.entity_index != filter.entity && filter.entity != 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filter.has_entity_filter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter
|
||||||
|
.class_ids
|
||||||
|
.contains(&u16::from(entity.server_class).into())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity
|
||||||
|
.props
|
||||||
|
.iter()
|
||||||
|
.any(|prop| filter.prop_ids.contains(&prop.identifier.into()))
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_panic_hook() {
|
pub fn set_panic_hook() {
|
||||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
// `set_panic_hook` function at least once during initialization, and then
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ import {Packet} from "./parser";
|
||||||
import React, {useCallback, Component} from 'react'
|
import React, {useCallback, Component} from 'react'
|
||||||
import {useDropzone} from 'react-dropzone'
|
import {useDropzone} from 'react-dropzone'
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
import {Header} from "./header";
|
import {Header} from "./header";
|
||||||
import {PacketDetails, PacketTable} from "./table";
|
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');
|
let _style = require('../styles/style.css');
|
||||||
|
|
||||||
|
|
@ -34,15 +36,11 @@ interface AppState {
|
||||||
class_names: Map<number, string>,
|
class_names: Map<number, string>,
|
||||||
active: Packet | null,
|
active: Packet | null,
|
||||||
activeIndex: number | null,
|
activeIndex: number | null,
|
||||||
search: Search,
|
search: SearchFilter,
|
||||||
|
matches: number[],
|
||||||
worker: Worker
|
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> {
|
class App extends Component<{}, AppState> {
|
||||||
state: AppState = {
|
state: AppState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
@ -55,20 +53,27 @@ class App extends Component<{}, AppState> {
|
||||||
activeIndex: null,
|
activeIndex: null,
|
||||||
search: {
|
search: {
|
||||||
entity: 0,
|
entity: 0,
|
||||||
filter: "",
|
search: "",
|
||||||
classIds: [],
|
class_ids: [],
|
||||||
propIds: [],
|
prop_ids: [],
|
||||||
},
|
},
|
||||||
|
matches: [],
|
||||||
worker: null
|
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) {
|
load(data: ArrayBuffer) {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
const worker = new Worker('./worker.js');
|
const worker = new Worker('./worker.js');
|
||||||
this.setState({worker});
|
this.setState({worker});
|
||||||
worker.addEventListener("message", (event: MessageEvent<MessageData>) => {
|
worker.addEventListener("message", (event: MessageEvent<ResponseMessageData>) => {
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
if (data.type !== "progress") {
|
if (data.type !== "progress") {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
@ -96,7 +101,11 @@ class App extends Component<{}, AppState> {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "packet":
|
case "packet":
|
||||||
this.setState({active: data.packet})
|
this.setState({active: data.packet});
|
||||||
|
break;
|
||||||
|
case "search_result":
|
||||||
|
this.setState({matches: data.matches});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
|
|
@ -106,12 +115,11 @@ class App extends Component<{}, AppState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredPackets(): PacketMeta[] {
|
filteredPackets(): PacketMeta[] {
|
||||||
return this.state.packets;
|
if (isSearchEmpty(this.state.search)) {
|
||||||
// if (this.state.search.filter || this.state.search.entity) {
|
return this.state.packets;
|
||||||
// return this.state.packets.filter(packet => filterPacket(packet, this.state.search));
|
} else {
|
||||||
// } else {
|
return this.state.matches.map(index => this.state.packets[index]);
|
||||||
// return this.state.packets;
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
@ -130,6 +138,7 @@ class App extends Component<{}, AppState> {
|
||||||
)
|
)
|
||||||
} else if (this.state.packets.length) {
|
} else if (this.state.packets.length) {
|
||||||
let active = <></>;
|
let active = <></>;
|
||||||
|
const packets = this.filteredPackets();
|
||||||
if (this.state.active) {
|
if (this.state.active) {
|
||||||
active = <div className="details"><PacketDetails packet={this.state.active}
|
active = <div className="details"><PacketDetails packet={this.state.active}
|
||||||
search={this.state.search}
|
search={this.state.search}
|
||||||
|
|
@ -141,7 +150,7 @@ class App extends Component<{}, AppState> {
|
||||||
<SearchBar onSearch={this.onSearch} class_names={this.state.class_names}
|
<SearchBar onSearch={this.onSearch} class_names={this.state.class_names}
|
||||||
prop_names={this.state.prop_names}/>
|
prop_names={this.state.prop_names}/>
|
||||||
<div className="packets">
|
<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}
|
activeIndex={this.state.activeIndex}
|
||||||
prop_names={this.state.prop_names}
|
prop_names={this.state.prop_names}
|
||||||
onClick={(index) => {
|
onClick={(index) => {
|
||||||
|
|
@ -161,12 +170,9 @@ class App extends Component<{}, AppState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
const container = document.getElementById('root');
|
||||||
<App/>
|
const root = createRoot(container);
|
||||||
,
|
root.render(<App/>);
|
||||||
document.getElementById("root")
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
function DemoDropzone({onDrop}: { onDrop: (data: ArrayBuffer) => void }) {
|
function DemoDropzone({onDrop}: { onDrop: (data: ArrayBuffer) => void }) {
|
||||||
const onDropCb = useCallback((acceptedFiles: File[]) => {
|
const onDropCb = useCallback((acceptedFiles: File[]) => {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import {EventInfo, GameEventDefinition, Message, PacketEntity, SendPropValue} from "../parser";
|
import {EventInfo, GameEventDefinition, Message, PacketEntity, SendPropValue} from "../parser";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {filterEntity, filterMessage, Search} from "../search";
|
import {filterEntity, filterMessage, isSearchEmpty, SearchFilter} from "../search";
|
||||||
|
|
||||||
export interface MessageInfoProps {
|
export interface MessageInfoProps {
|
||||||
msg: Message,
|
msg: Message,
|
||||||
prop_names: Map<number, { table: String, prop: String }>,
|
prop_names: Map<number, { table: String, prop: String }>,
|
||||||
class_names: Map<number, String>,
|
class_names: Map<number, String>,
|
||||||
search: Search
|
search: SearchFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessageInfo({msg, prop_names, class_names, search}: MessageInfoProps) {
|
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(', ')}}`;
|
return `${event.event_type}{${values.join(', ')}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filteredEntities(entities: PacketEntity[], search: Search) {
|
function filteredEntities(entities: PacketEntity[], search: SearchFilter) {
|
||||||
if (search.filter || search.entity) {
|
if (!isSearchEmpty(search)) {
|
||||||
return entities.filter(entities => {
|
return entities.filter(entities => {
|
||||||
return (search.entity == 0 || search.entity == entities.entity_index) &&
|
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 {
|
} else {
|
||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function filteredTempEntities(entities: EventInfo[], search: Search) {
|
function filteredTempEntities(entities: EventInfo[], search: SearchFilter) {
|
||||||
if (search.entity) {
|
if (search.entity) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
if (search.filter) {
|
if (!isSearchEmpty(search)) {
|
||||||
return entities.filter(entities => filterEntity(entities.class_id, entities.props, search));
|
return entities.filter(entities => filterEntity(entities.class_id, entities.props, search));
|
||||||
} else {
|
} else {
|
||||||
return entities;
|
return entities;
|
||||||
|
|
|
||||||
14
www/src/rpc.ts
Normal file
14
www/src/rpc.ts
Normal 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[] };
|
||||||
|
|
@ -3,15 +3,15 @@ import React, {ChangeEvent, Component} from "react";
|
||||||
import './search.css'
|
import './search.css'
|
||||||
import {Message, Packet, SendProp, StringTable} from "./parser";
|
import {Message, Packet, SendProp, StringTable} from "./parser";
|
||||||
|
|
||||||
export interface Search {
|
export interface SearchFilter {
|
||||||
filter: string,
|
|
||||||
entity: number,
|
entity: number,
|
||||||
propIds: number[],
|
search: string,
|
||||||
classIds: number[],
|
prop_ids: number[],
|
||||||
|
class_ids: number[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchBarProps {
|
export interface SearchBarProps {
|
||||||
onSearch: (search: Search) => void,
|
onSearch: (search: SearchFilter) => void,
|
||||||
prop_names: Map<number, { table: string, prop: string }>,
|
prop_names: Map<number, { table: string, prop: string }>,
|
||||||
class_names: Map<number, string>,
|
class_names: Map<number, string>,
|
||||||
}
|
}
|
||||||
|
|
@ -27,12 +27,12 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
|
||||||
entity: 0
|
entity: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
getSearch(): Search {
|
getSearch(): SearchFilter {
|
||||||
return {
|
return {
|
||||||
filter: this.state.filter,
|
search: this.state.filter,
|
||||||
entity: this.state.entity,
|
entity: this.state.entity,
|
||||||
propIds: filterPropNames(this.props.prop_names, this.state.filter),
|
prop_ids: filterPropNames(this.props.prop_names, this.state.filter),
|
||||||
classIds: filterClassNames(this.props.class_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(
|
export function filterPacket(
|
||||||
packet: Packet,
|
packet: Packet,
|
||||||
search: Search,
|
search: SearchFilter,
|
||||||
): boolean {
|
): boolean {
|
||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case "Signon":
|
case "Signon":
|
||||||
|
|
@ -79,7 +79,7 @@ export function filterPacket(
|
||||||
case "SyncTick":
|
case "SyncTick":
|
||||||
return false;
|
return false;
|
||||||
case "ConsoleCmd":
|
case "ConsoleCmd":
|
||||||
return search.entity == 0 && packet.command.includes(search.filter);
|
return search.entity == 0 && packet.command.includes(search.search);
|
||||||
case "UserCmd":
|
case "UserCmd":
|
||||||
return false;
|
return false;
|
||||||
case "DataTables":
|
case "DataTables":
|
||||||
|
|
@ -92,6 +92,9 @@ export function filterPacket(
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterPropNames(prop_names: Map<number, { table: string, prop: string }>, filter: string): number[] {
|
function filterPropNames(prop_names: Map<number, { table: string, prop: string }>, filter: string): number[] {
|
||||||
|
if (filter.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
filter = filter.toLowerCase();
|
filter = filter.toLowerCase();
|
||||||
let ids = [];
|
let ids = [];
|
||||||
for (let [id, {table, prop}] of prop_names.entries()) {
|
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[] {
|
function filterClassNames(class_names: Map<number, string>, filter: string): number[] {
|
||||||
|
if (filter.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
filter = filter.toLowerCase();
|
filter = filter.toLowerCase();
|
||||||
let ids = [];
|
let ids = [];
|
||||||
for (let [id, name] of class_names.entries()) {
|
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(
|
export function filterMessage(
|
||||||
message: Message,
|
message: Message,
|
||||||
search: Search,
|
search: SearchFilter,
|
||||||
): boolean {
|
): boolean {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "File":
|
case "File":
|
||||||
return search.entity == 0 && message.file_name.includes(search.filter);
|
return search.entity == 0 && message.file_name.includes(search.search);
|
||||||
case "StringCmd":
|
case "StringCmd":
|
||||||
return search.entity == 0 && message.command.includes(search.filter);
|
return search.entity == 0 && message.command.includes(search.search);
|
||||||
case "SetConVar":
|
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":
|
case "Print":
|
||||||
return search.entity == 0 && message.value.includes(search.filter);
|
return search.entity == 0 && message.value.includes(search.search);
|
||||||
case "ClassInfo":
|
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":
|
case "CreateStringTable":
|
||||||
return search.entity == 0 && filterStringTable(message.table, search);
|
return search.entity == 0 && filterStringTable(message.table, search);
|
||||||
case "UpdateStringTable":
|
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":
|
case "SetView":
|
||||||
return search.entity == 0 && message.index === search.entity;
|
return search.entity == 0 && message.index === search.entity;
|
||||||
case "SayText2":
|
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":
|
case "Text":
|
||||||
return search.entity == 0 && message.text.includes(search.filter);
|
return search.entity == 0 && message.text.includes(search.search);
|
||||||
case "EntityMessage":
|
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":
|
case "GameEvent":
|
||||||
return search.entity == 0 && message.event.type.includes(search.filter)
|
return search.entity == 0 && message.event.type.includes(search.search)
|
||||||
case "PacketEntities":
|
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":
|
case "TempEntities":
|
||||||
return search.entity == 0 && message.events.some(event => filterEntity(event.class_id, event.props, search))
|
return search.entity == 0 && message.events.some(event => filterEntity(event.class_id, event.props, search))
|
||||||
case "GetCvarValue":
|
case "GetCvarValue":
|
||||||
return search.entity == 0 && message.value.includes(search.filter);
|
return search.entity == 0 && message.value.includes(search.search);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterEntity(class_id: number, props: SendProp[], search: Search): boolean {
|
export function filterEntity(class_id: number, props: SendProp[], search: SearchFilter): boolean {
|
||||||
return search.classIds.includes(class_id) || props.some(prop => search.propIds.includes(prop.identifier))
|
if (search.search.length === 0 && search.class_ids.length === 0 && search.prop_ids.length === 0) {
|
||||||
|| props.some(prop => prop.value == search.filter);
|
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 {
|
function filterStringTable(table: StringTable, search: SearchFilter): boolean {
|
||||||
if (table.name.includes(search.filter)) {
|
if (table.name.includes(search.search)) {
|
||||||
return true;
|
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 true;
|
||||||
}
|
}
|
||||||
return false;
|
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
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import {GameEventDefinition, Message, Packet, PacketEntity, SendPropValue, UserC
|
||||||
import {FixedSizeList as List} from 'react-window';
|
import {FixedSizeList as List} from 'react-window';
|
||||||
import {MessageInfo} from "./packets/message";
|
import {MessageInfo} from "./packets/message";
|
||||||
import {UserCmdDetails} from "./packets/usercmd";
|
import {UserCmdDetails} from "./packets/usercmd";
|
||||||
import {filterMessage, filterPacket, Search} from "./search";
|
import {filterMessage, filterPacket, SearchFilter} from "./search";
|
||||||
import {PacketMeta, PacketType} from "./index"
|
import {PacketMeta, PacketType} from "./index"
|
||||||
|
|
||||||
interface TableProps {
|
interface TableProps {
|
||||||
|
|
@ -81,11 +81,11 @@ interface DetailProps {
|
||||||
packet: Packet,
|
packet: Packet,
|
||||||
prop_names: Map<number, { table: string, prop: string }>,
|
prop_names: Map<number, { table: string, prop: string }>,
|
||||||
class_names: Map<number, string>,
|
class_names: Map<number, string>,
|
||||||
search: Search,
|
search: SearchFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
function filteredMessages(messages: Message[], search: Search) {
|
function filteredMessages(messages: Message[], search: SearchFilter) {
|
||||||
if (search.filter || search.entity) {
|
if (search.search || search.entity) {
|
||||||
return messages.filter(message => filterMessage(message, search));
|
return messages.filter(message => filterMessage(message, search));
|
||||||
} else {
|
} else {
|
||||||
return messages;
|
return messages;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import {Parser} from "demo-inspector";
|
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;
|
let parser: Parser | null = null;
|
||||||
|
|
||||||
type MessageData = {type: "data", data: ArrayBuffer} | {type: "get", packet: number}
|
onmessage = function (event: MessageEvent<RequestMessageData>) {
|
||||||
|
|
||||||
onmessage = function (event: MessageEvent<MessageData>) {
|
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "data":
|
case "data":
|
||||||
|
|
@ -24,6 +23,12 @@ onmessage = function (event: MessageEvent<MessageData>) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
case "search":
|
||||||
|
if (parser) {
|
||||||
|
const matches = parser.search(data.filter);
|
||||||
|
postMessage({type: "search_result", matches: Array.prototype.slice.call(matches)})
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "get":
|
case "get":
|
||||||
if (parser) {
|
if (parser) {
|
||||||
const packet = parser.packet(data.packet);
|
const packet = parser.packet(data.packet);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue