Rupas1k / source2-demo

Dota 2 / Deadlock replay parser written in Rust
Apache License 2.0
19 stars 3 forks source link

[Deadlock] Issue with entity.get_property_by_name() appending "m_flSimulationTime" and causing invalid property name #3

Open IOExceptional opened 1 month ago

IOExceptional commented 1 month ago
use source2_demo::prelude::*;

#[derive(Default)]
struct HeroUpgradeBits { }

#[observer]
impl HeroUpgradeBits {
    #[on_entity]
    fn handle_entities(&mut self, ctx: &Context, entity: &Entity) -> ObserverResult {
        if entity.class().name() != "CCitadelPlayerController" {
            return Ok(());
        }
        let hero_upgrade_bits = entity.get_property_by_name("m_nHeroAbilityUpgradeBits");
        println!("Hero upgrade bits: {:?}", hero_upgrade_bits);

        Ok(())
    }
}

fn main() -> anyhow::Result<()> {
    let args = std::env::args().collect::<Vec<_>>();
    let Some(filepath) = args.get(1) else {
        eprintln!("Usage: {} <demofile>", args[0]);
        return Ok(());
    };

    let replay = unsafe { memmap2::Mmap::map(&std::fs::File::open(filepath)?)? };
    let mut parser = Parser::new(&replay)?;

    parser.register_observer::<HeroUpgradeBits>();

    let start = std::time::Instant::now();
    parser.run_to_end()?;
    println!("Elapsed: {:?}", start.elapsed());

    Ok(())
}

Added the above as a new example in the dl-examples and it prints out;

Hero upgrade bits: Err(PropertyNameNotFound("m_nHeroAbilityUpgradeBitsm_flSimulationTime", "CCitadelPlayerController", "74"))

Rupas1k commented 1 month ago

Hello @IOExceptional, thanks for your report! I fixed field name in the error, however you still can't access arrays and vectors directly, only their elements.

What you can do now is:

for i in 0..4 {
    let bits: u8 = entity
        .get_property_by_name(&format!("m_nHeroAbilityUpgradeBits.{:04}", i))?
        .try_into()?;
    ...
}

Or use macro:

for i in 0..4 {
    let bits: u8 = property!(entity, "m_nHeroAbilityUpgradeBits.{:04}", i);
    ...
}

I don't really like it, so I think I'll open a PR and try to make a more flexible way to access fields, something like that:

for x in entity.get_field("m_nHeroAbilityUpgradeBits")?.iter() {
    let bits: u8 = x.value()?.try_into()?;
    ...
}

and

let bits: u8 = entity.get_field("m_nHeroAbilityUpgradeBits.0000")?.value()?.try_into()?;
IOExceptional commented 1 month ago

That's super helpful, thanks a lot!

I actually hadn't even realised it was an array value, but that makes sense why it worked with a lot of other properties