mirror of
https://codeberg.org/demostf/inspector.git
synced 2026-06-04 02:24:09 +02:00
flake packaging
This commit is contained in:
parent
ba6380c558
commit
31d19cdcdd
12 changed files with 1252 additions and 433 deletions
170
wasm/src/lib.rs
Normal file
170
wasm/src/lib.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
mod search;
|
||||
mod utils;
|
||||
|
||||
use crate::search::{packet_matches, SearchFilter};
|
||||
use bitbuffer::{BitRead, BitReadBuffer, BitReadStream, LittleEndian};
|
||||
use js_sys::Function;
|
||||
use serde::Serialize;
|
||||
use tf_demo_parser::demo::header::Header;
|
||||
use tf_demo_parser::demo::packet::datatable::{DataTablePacket, SendTableName, ServerClassName};
|
||||
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::sendprop::SendPropName;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PacketMeta {
|
||||
index: usize,
|
||||
tick: u32,
|
||||
ty: u8,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Parser {
|
||||
header: Header,
|
||||
packets: Vec<Packet<'static>>,
|
||||
prop_names: Vec<(u64, SendTableName, SendPropName)>,
|
||||
class_names: Vec<(u16, ServerClassName)>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Parser {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(input: Vec<u8>, progress: Function) -> Self {
|
||||
let buffer = BitReadBuffer::new_owned(input, LittleEndian);
|
||||
let mut stream = BitReadStream::new(buffer);
|
||||
let header = Header::read(&mut stream).unwrap();
|
||||
|
||||
let mut packet_stream = RawPacketStream::new(stream);
|
||||
let mut handler = DemoHandler::default();
|
||||
handler.handle_header(&header);
|
||||
|
||||
let mut packets = Vec::new();
|
||||
|
||||
let mut last_progress = 0.0;
|
||||
|
||||
let mut prop_names = Vec::new();
|
||||
let mut class_names = Vec::new();
|
||||
|
||||
while let Some(packet) = packet_stream.next(handler.get_parser_state()).unwrap() {
|
||||
let tick = packet.tick();
|
||||
packets.push(packet.clone());
|
||||
|
||||
if let Packet::DataTables(DataTablePacket {
|
||||
tables,
|
||||
server_classes,
|
||||
..
|
||||
}) = &packet
|
||||
{
|
||||
for table in tables {
|
||||
for prop in &table.props {
|
||||
prop_names.push((
|
||||
prop.identifier.into(),
|
||||
prop.identifier
|
||||
.table_name()
|
||||
.unwrap_or_else(|| table.name.clone()),
|
||||
prop.identifier
|
||||
.prop_name()
|
||||
.unwrap_or_else(|| prop.name.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
for class in server_classes {
|
||||
class_names.push((class.id.into(), class.name.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
handler.handle_packet(packet).unwrap();
|
||||
|
||||
let new_progress = ((tick as f32 / header.ticks as f32) * 100.0).floor();
|
||||
if new_progress > last_progress {
|
||||
last_progress = new_progress;
|
||||
progress
|
||||
.call1(&JsValue::NULL, &JsValue::from(new_progress))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Parser {
|
||||
header,
|
||||
packets,
|
||||
prop_names,
|
||||
class_names,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header(&self) -> JsValue {
|
||||
JsValue::from_serde(&self.header).unwrap()
|
||||
}
|
||||
|
||||
pub fn packets(&self) -> Vec<JsValue> {
|
||||
self.packets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, packet)| PacketMeta {
|
||||
index,
|
||||
tick: packet.tick(),
|
||||
ty: packet.packet_type() as u8,
|
||||
})
|
||||
.map(|meta| JsValue::from_serde(&meta).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn packet(&self, index: usize) -> JsValue {
|
||||
JsValue::from_serde(&self.packets[index]).unwrap()
|
||||
}
|
||||
|
||||
pub fn prop_names(&self) -> Vec<JsValue> {
|
||||
self.prop_names
|
||||
.iter()
|
||||
.map(|(identifier, table, prop)| {
|
||||
JsValue::from_serde(&PropName {
|
||||
identifier: identifier.to_string(),
|
||||
table: table.to_string(),
|
||||
prop: prop.to_string(),
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn class_names(&self) -> Vec<JsValue> {
|
||||
self.class_names
|
||||
.iter()
|
||||
.map(|(identifier, name)| {
|
||||
JsValue::from_serde(&ClassName {
|
||||
identifier: *identifier,
|
||||
name: name.to_string(),
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn search(&self, filter: JsValue) -> Vec<usize> {
|
||||
let filter: SearchFilter = filter.into_serde().expect("failed to parse search filter");
|
||||
self.packets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, packet)| packet_matches(packet, &filter).then_some(index))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PropName {
|
||||
pub identifier: String,
|
||||
pub table: String,
|
||||
pub prop: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ClassName {
|
||||
pub identifier: u16,
|
||||
pub name: String,
|
||||
}
|
||||
154
wasm/src/search.rs
Normal file
154
wasm/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::datatable::ClassId;
|
||||
use tf_demo_parser::demo::packet::message::MessagePacket;
|
||||
use tf_demo_parser::demo::packet::stringtable::{StringTableEntry, StringTablePacket};
|
||||
use tf_demo_parser::demo::packet::Packet;
|
||||
use tf_demo_parser::demo::sendprop::SendPropIdentifier;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SearchFilter {
|
||||
pub entity: u32,
|
||||
pub search: String,
|
||||
pub prop_ids: Vec<SendPropIdentifier>,
|
||||
pub class_ids: Vec<ClassId>,
|
||||
}
|
||||
|
||||
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).into()))
|
||||
}
|
||||
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()))
|
||||
}
|
||||
11
wasm/src/utils.rs
Normal file
11
wasm/src/utils.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#[allow(dead_code)]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue