mirror of
https://github.com/demostf/cutter.git
synced 2026-06-03 20:04:07 +02:00
wip
This commit is contained in:
parent
df7394a816
commit
1be42764ad
9 changed files with 7252 additions and 44 deletions
12
Cargo.toml
12
Cargo.toml
|
|
@ -7,11 +7,18 @@ edition = "2018"
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cut"
|
||||||
|
path = "src/cut.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["console_error_panic_hook"]
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitbuffer = "0.10"
|
||||||
|
tf-demo-parser = { version = "0.3", path = "../tf-demo-parser" }
|
||||||
wasm-bindgen = "0.2.63"
|
wasm-bindgen = "0.2.63"
|
||||||
|
web-sys = { version = "0.3", features = ["console"] }
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
|
@ -30,5 +37,6 @@ wee_alloc = { version = "0.4.5", optional = true }
|
||||||
wasm-bindgen-test = "0.3.13"
|
wasm-bindgen-test = "0.3.13"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
# Tell `rustc` to optimize for small code size.
|
|
||||||
opt-level = "s"
|
[profile.dev.overrides."*"]
|
||||||
|
opt-level = 3
|
||||||
BIN
out.dem
Normal file
BIN
out.dem
Normal file
Binary file not shown.
15
src/cut.rs
Normal file
15
src/cut.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use democutter::cut;
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<_> = env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("1 argument required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let path = args[1].clone();
|
||||||
|
let file = fs::read(path).unwrap();
|
||||||
|
let output = cut(&file, 30000, 50000);
|
||||||
|
fs::write("out.dem", output).unwrap();
|
||||||
|
}
|
||||||
70
src/entity.rs
Normal file
70
src/entity.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::mem::replace;
|
||||||
|
use tf_demo_parser::demo::message::packetentities::{
|
||||||
|
EntityId, PacketEntitiesMessage, PacketEntity, PVS,
|
||||||
|
};
|
||||||
|
use tf_demo_parser::ParserState;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ActiveEntities {
|
||||||
|
entities: HashMap<EntityId, PacketEntity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveEntities {
|
||||||
|
pub fn handle_message(&mut self, msg: &PacketEntitiesMessage, state: &ParserState) {
|
||||||
|
for entity in &msg.entities {
|
||||||
|
if entity.pvs == PVS::Delete || entity.pvs == PVS::Leave {
|
||||||
|
self.entities.remove(&entity.entity_index);
|
||||||
|
} else {
|
||||||
|
self.handle_entity(entity, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for deleted in msg.removed_entities.iter() {
|
||||||
|
self.entities.remove(deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_entity(&mut self, entity: &PacketEntity, state: &ParserState) {
|
||||||
|
if entity.pvs == PVS::Enter {
|
||||||
|
self.entities.insert(entity.entity_index, entity.clone());
|
||||||
|
} else {
|
||||||
|
self.entities
|
||||||
|
.entry(entity.entity_index)
|
||||||
|
.and_modify(|existing| update_entity(existing, entity, state))
|
||||||
|
.or_insert_with(|| entity.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode(self) -> PacketEntitiesMessage {
|
||||||
|
let max_entries = self.entities.len() as u16;
|
||||||
|
|
||||||
|
let mut entities = self
|
||||||
|
.entities
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_k, v)| v)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
entities.sort_by(|a, b| a.entity_index.cmp(&b.entity_index));
|
||||||
|
|
||||||
|
PacketEntitiesMessage {
|
||||||
|
entities,
|
||||||
|
removed_entities: vec![],
|
||||||
|
max_entries,
|
||||||
|
delta: None,
|
||||||
|
base_line: 0,
|
||||||
|
updated_base_line: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_entity(old: &mut PacketEntity, new: &PacketEntity, _state: &ParserState) {
|
||||||
|
for prop in &new.props {
|
||||||
|
match old
|
||||||
|
.props
|
||||||
|
.iter_mut()
|
||||||
|
.find(|existing| existing.index == prop.index)
|
||||||
|
{
|
||||||
|
Some(existing) => existing.value = prop.value.clone(),
|
||||||
|
None => old.props.push(prop.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
147
src/lib.rs
147
src/lib.rs
|
|
@ -1,19 +1,150 @@
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
mod entity;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
use crate::entity::ActiveEntities;
|
||||||
|
use crate::utils::set_panic_hook;
|
||||||
|
use bitbuffer::{BitRead, BitWrite, BitWriteStream, LittleEndian};
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
use tf_demo_parser::demo::header::Header;
|
||||||
|
use tf_demo_parser::demo::message::{Message, NetTickMessage};
|
||||||
|
use tf_demo_parser::demo::packet::message::{MessagePacket, MessagePacketMeta};
|
||||||
|
use tf_demo_parser::demo::packet::stop::StopPacket;
|
||||||
|
use tf_demo_parser::demo::packet::{Packet, PacketType};
|
||||||
|
use tf_demo_parser::demo::parser::{DemoHandler, Encode, NullHandler, RawPacketStream};
|
||||||
|
use tf_demo_parser::Demo;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::console;
|
||||||
|
|
||||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
|
||||||
// allocator.
|
|
||||||
#[cfg(feature = "wee_alloc")]
|
#[cfg(feature = "wee_alloc")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
const PRESERVE_PACKETS: &[PacketType] = &[
|
||||||
extern {
|
PacketType::Sigon,
|
||||||
fn alert(s: &str);
|
PacketType::DataTables,
|
||||||
}
|
PacketType::StringTables,
|
||||||
|
PacketType::SyncTick,
|
||||||
|
];
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn greet() {
|
pub fn cut(input: &[u8], start_tick: u32, end_tick: u32) -> Vec<u8> {
|
||||||
alert("Hello, democutter!");
|
set_panic_hook();
|
||||||
|
let mut out_buffer = Vec::with_capacity(input.len());
|
||||||
|
{
|
||||||
|
let mut out_stream = BitWriteStream::new(&mut out_buffer, LittleEndian);
|
||||||
|
|
||||||
|
let demo = Demo::new(&input);
|
||||||
|
let mut stream = demo.get_stream();
|
||||||
|
let mut header = Header::read(&mut stream).unwrap();
|
||||||
|
|
||||||
|
let start_tick = min(header.ticks - 10, start_tick);
|
||||||
|
let end_tick = min(header.ticks, end_tick);
|
||||||
|
let duration_per_tick = header.ticks as f32 / header.duration;
|
||||||
|
|
||||||
|
header.ticks = end_tick - start_tick;
|
||||||
|
header.duration = (end_tick - start_tick) as f32 * duration_per_tick;
|
||||||
|
header.write(&mut out_stream).unwrap();
|
||||||
|
|
||||||
|
let mut packets = RawPacketStream::new(stream.clone());
|
||||||
|
let mut start_handler = DemoHandler::default();
|
||||||
|
start_handler.handle_header(&header);
|
||||||
|
|
||||||
|
let mut handler = DemoHandler::default();
|
||||||
|
handler.handle_header(&header);
|
||||||
|
|
||||||
|
let (entities, start_packets, last_server_tick) =
|
||||||
|
skip_start(&mut start_handler, &mut packets, start_tick);
|
||||||
|
|
||||||
|
for packet in start_packets {
|
||||||
|
packet
|
||||||
|
.encode(&mut out_stream, &handler.state_handler)
|
||||||
|
.unwrap();
|
||||||
|
handler.handle_packet(packet).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = entities.encode();
|
||||||
|
let packet = Packet::Message(MessagePacket {
|
||||||
|
tick: 0,
|
||||||
|
messages: vec![
|
||||||
|
Message::NetTick(NetTickMessage {
|
||||||
|
tick: last_server_tick,
|
||||||
|
frame_time: 1881,
|
||||||
|
std_dev: 263,
|
||||||
|
}),
|
||||||
|
Message::PacketEntities(msg),
|
||||||
|
],
|
||||||
|
meta: MessagePacketMeta {
|
||||||
|
flags: 0,
|
||||||
|
view_angles: Default::default(),
|
||||||
|
sequence_in: 0,
|
||||||
|
sequence_out: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
packet
|
||||||
|
.encode(&mut out_stream, &handler.state_handler)
|
||||||
|
.unwrap();
|
||||||
|
handler.handle_packet(packet).unwrap();
|
||||||
|
|
||||||
|
while let Some(mut packet) = packets.next(&handler.state_handler).unwrap() {
|
||||||
|
let ty = packet.packet_type();
|
||||||
|
let original_tick = packet.tick();
|
||||||
|
packet.set_tick(max(original_tick, start_tick) - start_tick);
|
||||||
|
if ty != PacketType::ConsoleCmd {
|
||||||
|
packet
|
||||||
|
.encode(&mut out_stream, &handler.state_handler)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
handler.handle_packet(packet).unwrap();
|
||||||
|
|
||||||
|
if original_tick >= end_tick {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PacketType::Stop.write(&mut out_stream).unwrap();
|
||||||
|
StopPacket { tick: end_tick }
|
||||||
|
.encode(&mut out_stream, &handler.state_handler)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
out_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_start<'a>(
|
||||||
|
handler: &mut DemoHandler<'a, NullHandler>,
|
||||||
|
packets: &mut RawPacketStream<'a>,
|
||||||
|
start_tick: u32,
|
||||||
|
) -> (ActiveEntities, Vec<Packet<'a>>, u32) {
|
||||||
|
let mut entities = ActiveEntities::default();
|
||||||
|
let mut start_packets = Vec::with_capacity(6);
|
||||||
|
let mut server_tick = 0;
|
||||||
|
|
||||||
|
while let Some(packet) = packets.next(&handler.state_handler).unwrap() {
|
||||||
|
if PRESERVE_PACKETS.contains(&packet.packet_type()) {
|
||||||
|
start_packets.push(packet.clone());
|
||||||
|
handler.handle_packet(packet).unwrap();
|
||||||
|
} else {
|
||||||
|
if let Packet::Message(message_packet) = &packet {
|
||||||
|
for msg in &message_packet.messages {
|
||||||
|
match msg {
|
||||||
|
Message::PacketEntities(msg) => {
|
||||||
|
entities.handle_message(msg, &handler.state_handler);
|
||||||
|
}
|
||||||
|
Message::NetTick(NetTickMessage { tick, .. }) => {
|
||||||
|
server_tick = *tick;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let tick = packet.tick();
|
||||||
|
handler.handle_packet(packet).unwrap();
|
||||||
|
|
||||||
|
if tick >= start_tick {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(entities, start_packets, server_tick)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,36 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Hello wasm-pack!</title>
|
<title>POV demo camera unlocker</title>
|
||||||
</head>
|
<style>
|
||||||
<body>
|
input[disabled] {
|
||||||
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
|
opacity: 0.5;
|
||||||
<script src="./bootstrap.js"></script>
|
}
|
||||||
</body>
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
|
||||||
|
<script src="./bootstrap.js"></script>
|
||||||
|
<h1>POV demo camera unlocker</h1>
|
||||||
|
|
||||||
|
Unlocks the camera in POV demos to allow the same controls as when viewing STV demos.
|
||||||
|
|
||||||
|
<p>Select a demo file below to begin processing, once processed the file will be presented as download</p>
|
||||||
|
<p>Processing demo files can take a while</p>
|
||||||
|
<form>
|
||||||
|
<input type="number" id="start" value="30000">
|
||||||
|
<input type="number" id="end" value="50000">
|
||||||
|
<input type="file" id="file">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Note:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>The demo still being recorded as POV demo means that only data around the recording player is in the demo file, players not near the recorder will behave weirdly</li>
|
||||||
|
<li>When the player respawns the freecam camera will be moved</li>
|
||||||
|
<li>The resulting demo might not work or crash tf2 on playback in rare cases</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
33
www/index.js
33
www/index.js
|
|
@ -1,3 +1,32 @@
|
||||||
import * as wasm from "hello-wasm-pack";
|
import {cut} from "democutter";
|
||||||
|
|
||||||
wasm.greet();
|
|
||||||
|
let fileSelect = document.getElementById('file');
|
||||||
|
let startInput = document.getElementById('start');
|
||||||
|
let endInput = document.getElementById('end');
|
||||||
|
fileSelect.addEventListener('change', (event) => {
|
||||||
|
let start = parseInt(startInput.value);
|
||||||
|
let end = parseInt(endInput.value);
|
||||||
|
console.log(start, end);
|
||||||
|
fileSelect.disabled = true;
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(fileSelect.files[0]);
|
||||||
|
reader.addEventListener('load', () => {
|
||||||
|
console.log(reader.result);
|
||||||
|
let result = cut(new Uint8Array(reader.result), start, end);
|
||||||
|
fileSelect.disabled = false;
|
||||||
|
save(result, "cut.dem");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function save(data, fileName) {
|
||||||
|
let a = document.createElement("a");
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.style = "display: none";
|
||||||
|
let blob = new Blob([data], {type: "octet/stream"});
|
||||||
|
let url = window.URL.createObjectURL(blob);
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
6978
www/package-lock.json
generated
6978
www/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,8 +26,10 @@
|
||||||
"url": "https://github.com/rustwasm/create-wasm-app/issues"
|
"url": "https://github.com/rustwasm/create-wasm-app/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/rustwasm/create-wasm-app#readme",
|
"homepage": "https://github.com/rustwasm/create-wasm-app#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"democutter": "file:../pkg"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"hello-wasm-pack": "^0.1.0",
|
|
||||||
"webpack": "^4.29.3",
|
"webpack": "^4.29.3",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.1.0",
|
||||||
"webpack-dev-server": "^3.1.5",
|
"webpack-dev-server": "^3.1.5",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue