diff --git a/Cargo.lock b/Cargo.lock index 5165892..82d150f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,12 +342,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.6.7" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "winapi", + "windows-targets", ] [[package]] @@ -419,9 +419,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "pcap" -version = "1.3.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e935fc73d54a89fff576526c2ccd42bbf8247aae05b358693475b14fd4ff79" +checksum = "45f1686828a29fd8002fbf9c01506b4b2dd575c2305e1b884da3731abae8b9e0" dependencies = [ "bitflags", "errno", diff --git a/Cargo.toml b/Cargo.toml index e928e38..cd6b3eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ homepage = "https://github.com/IceDynamix/reliquary-archiver" base64 = "0.22.0" clap = { version = "4.5.4", features = ["derive"] } color-eyre = "0.6.3" -pcap = "1.3.0" +pcap = "2.0.0" serde = { version = "1.0.198", features = ["derive"] } serde_json = "1.0.116" tracing = "0.1.40" diff --git a/src/export/fribbels.rs b/src/export/fribbels.rs index f49d838..7be569e 100644 --- a/src/export/fribbels.rs +++ b/src/export/fribbels.rs @@ -46,12 +46,14 @@ pub struct Export { pub struct Metadata { pub uid: Option, pub trailblazer: Option<&'static str>, + pub current_trailblazer_path: Option<&'static str>, } pub struct OptimizerExporter { database: Database, uid: Option, trailblazer: Option<&'static str>, + current_trailblazer_path: Option<&'static str>, light_cones: Vec, relics: Vec, characters: Vec, @@ -64,6 +66,7 @@ impl OptimizerExporter { database, uid: None, trailblazer: None, + current_trailblazer_path: None, light_cones: vec![], relics: vec![], characters: vec![], @@ -89,6 +92,13 @@ impl OptimizerExporter { .filter_map(|b| export_proto_hero(&self.database, &b)) .collect(); + self.current_trailblazer_path = trailblazer_id_to_path(hero.cur_basic_type.value() as u32); + if let Some(path) = self.current_trailblazer_path { + info!(path, "found current trailblazer path"); + } else { + warn!("unknown path for current trailblazer"); + } + info!(num=builds.len(), "found trailblazer builds"); self.trailblazer_characters.append(&mut builds); } @@ -223,6 +233,7 @@ impl Exporter for OptimizerExporter { metadata: Metadata { uid: self.uid, trailblazer: self.trailblazer, + current_trailblazer_path: self.current_trailblazer_path, }, light_cones: self.light_cones, relics: self.relics, diff --git a/src/main.rs b/src/main.rs index b12105d..9de09ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::sync::{mpsc, Mutex}; use std::time::Duration; use clap::Parser; -use pcap::{ConnectionStatus, Device}; +use pcap::{ConnectionStatus, Device, Error}; use reliquary::network::{ConnectionPacket, GamePacket, GameSniffer}; use reliquary::network::gen::command_id::{PlayerLoginFinishScRsp, PlayerLoginScRsp}; use tracing::{debug, error, info, instrument, trace, warn}; @@ -48,7 +48,7 @@ fn main() { let export = match args.pcap { Some(_) => file_capture(&args, exporter, sniffer), - None => live_capture(&args, exporter, sniffer) + None => live_capture(&args, exporter, sniffer), }; if let Some(export) = export { @@ -74,12 +74,15 @@ fn tracing_init(args: &Args) { 0 => "reliquary_archiver=info", 1 => "info", 2 => "debug", - _ => "trace" - }.parse().unwrap() + _ => "trace", + } + .parse() + .unwrap(), ) .from_env_lossy(); let stdout_log = tracing_subscriber::fmt::layer() + .with_ansi(false) .with_filter(env_filter); let subscriber = Registry::default().with(stdout_log); @@ -97,16 +100,16 @@ fn tracing_init(args: &Args) { let subscriber = subscriber.with(file_log); - tracing::subscriber::set_global_default(subscriber) - .expect("unable to set up logging"); + tracing::subscriber::set_global_default(subscriber).expect("unable to set up logging"); } #[instrument(skip_all)] fn file_capture(args: &Args, mut exporter: E, mut sniffer: GameSniffer) -> Option - where E: Exporter +where + E: Exporter, { - let mut capture = pcap::Capture::from_file(args.pcap.as_ref().unwrap()) - .expect("could not read pcap file"); + let mut capture = + pcap::Capture::from_file(args.pcap.as_ref().unwrap()).expect("could not read pcap file"); capture.filter(PACKET_FILTER, false).unwrap(); @@ -143,11 +146,15 @@ fn file_capture(args: &Args, mut exporter: E, mut sniffer: GameSniffer) -> Op #[instrument(skip_all)] fn live_capture(args: &Args, mut exporter: E, mut sniffer: GameSniffer) -> Option - where E: Exporter +where + E: Exporter, { let (tx, rx) = mpsc::channel(); let mut join_handles = Vec::new(); + // we need to specify a specific network device when using pcap to capture network packets. + // to lessen the burden on the user, we instead just capture *all* valid network devices + // by capturing each on a different thread and sending the captured packets to a mpsc channel for device in Device::list() .unwrap() .into_iter() @@ -160,6 +167,12 @@ fn live_capture(args: &Args, mut exporter: E, mut sniffer: GameSniffer) -> Op join_handles.push(handle); } + // we clone tx into every thread, but at the end the original tx still remains. + // rx.recv will continue to listen while at least one tx is still alive. + // we drop the original tx to make sure that there are no tx alive after all threads + // have dropped theirs + drop(tx); + let mut invalid = 0; let mut warning_sent = false; @@ -187,8 +200,12 @@ fn live_capture(args: &Args, mut exporter: E, mut sniffer: GameSniffer) -> Op invalid += 1; if invalid >= 25 && !warning_sent { - error!("received a large number of packets that could not be parsed"); - warn!("you probably started capturing when you were already in-game"); + error!( + "received a large number of packets that could not be parsed" + ); + warn!( + "you probably started capturing when you were already in-game" + ); warn!("please log out and log back in"); warning_sent = true; } @@ -227,24 +244,56 @@ fn live_capture(args: &Args, mut exporter: E, mut sniffer: GameSniffer) -> Op Some(exporter.export()) } -#[instrument("thread", skip_all, fields(device = device.name))] +#[instrument(skip_all, fields(device = device.desc))] fn capture_device(device: Device, tx: mpsc::Sender>) { let mut capture = pcap::Capture::from_device(device) .unwrap() .immediate_mode(true) + .promisc(true) + .timeout(0) // explicitly disable timeout?? .open() .unwrap(); - capture.filter(PACKET_FILTER, false).unwrap(); + capture.filter(PACKET_FILTER, true).unwrap(); debug!("listening"); - while let Ok(packet) = capture.next_packet() { - trace!("captured packet"); - if let Err(e) = tx.send(packet.data.to_vec()) { - debug!("channel closed: {e}"); - return; + let mut has_captured = false; + + loop { + match capture.next_packet() { + Ok(packet) => { + trace!("captured packet"); + if let Err(e) = tx.send(packet.data.to_vec()) { + debug!("channel closed: {e}"); + break; + } + + has_captured = true; + } + Err(e) => { + // we only really care about capture errors on devices that we already know + // are relevant (have sent packets before) and send those errors on warn level. + // + // if a capture errors right after initialization or on a device that did + // not receive any relevant packets, error is less useful to the user, + // so we lower the logging level + + if !has_captured { + debug!(?e); + break; + } else if matches!(e, Error::TimeoutExpired) { + // somehow a timeout error can still happen even if i explicitly + // disable the timeout?? why :sob: + debug!(?e); + continue; + } else { + warn!(?e); + break; + } + } } } -} + debug!("stop listening"); +}