owengage / fastnbt

Fast serde serializer and deserializer for Minecraft's NBT and Anvil formats
MIT License
186 stars 34 forks source link

Error Serializing data #64

Closed maddymakesgames closed 2 years ago

maddymakesgames commented 2 years ago

I'm trying to deserialize and then reserialize some player data (specifically this file)

    let mut file = std::fs::File::open("./simple_player.dat").unwrap();
    let mut bytes = Vec::new();
    file.read_to_end(&mut bytes).unwrap();
    let uncompressed = inflate(&bytes);

    let data = from_bytes::<PlayerData>(&uncompressed).unwrap();
    println!("{:?}", data);
    to_bytes(&data).unwrap();

The deserialization works fine but then I get a "no root compound" error when trying to serialize.

Full source of my test: main.rs

owengage commented 2 years ago

Hi Madeline, thanks for the report, especially for including the data and code. I'll have a look at this as soon as I can. Sorry you found a bug!

Cheers

owengage commented 2 years ago

Simple issue on fastnbt's part. I wasn't going to support serializing a bool since NBT does not really have them. But the deserializer happily deserializes 'byte' as a bool, so now it will also serialize it. The no root compound was a red herring error.

This is fixed in fastnbt 2.3.2 and master.

P.S. You might find serde's rename_all useful if you're not already aware of it, you can shorten your main struct to:

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "PascalCase")]
    pub struct PlayerData {
        persistant_id: Option<i32>,
        #[serde(rename = "playerGameType")]
        game_type: i32,
        #[serde(rename = "abilities")]
        abilities: PlayerAbilityData,
        score: Option<i32>,

        dimension: i32,
        on_ground: bool,
        fall_distance: f32,
        motion: Vec<f64>, // [f64; 3]
        #[serde(rename = "Pos")]
        position: Vec<f64>, // [f64; 3]
        rotation: Vec<f32>, // [f32; 2]

        spawn_x: i32,
        spawn_y: i32,
        spawn_z: i32,
        spawn_forced: Option<bool>,

        portal_cooldown: Option<i32>,
        invulnerable: Option<bool>,

        attack_time: Option<i16>,
        hurt_time: i16,
        #[serde(rename = "HurtByTimestamp")]
        hurt_by: Option<i32>,
        death_time: i16,
        sleeping: bool,
        sleep_timer: i16,

        health: i16,
        #[serde(rename = "HealF")]
        heal: Option<f32>,
        #[serde(rename = "foodLevel")]
        food_level: i32,
        #[serde(rename = "foodTickTimer")]
        food_tick_timer: i32,
        #[serde(rename = "foodSaturationLevel")]
        food_saturation_level: f32,
        #[serde(rename = "foodExhaustionLevel")]
        food_exhaustion_level: f32,

        fire: i16,
        air: i16,

        xp_p: f32,
        xp_level: i32,
        xp_total: i32,
        xp_seed: Option<i32>,

        inventory: Vec<InventoryEntry>,
        ender_items: Vec<i8>,

        selected_item_slot: Option<i32>,
        selected_item: Option<InventoryEntry>,
        #[serde(rename = "UUIDLeast")]
        uuid_least: Option<i64>,
        #[serde(rename = "UUIDMost")]
        uuid_most: Option<i64>,
        absorbtion_amount: Option<f32>,
        attributes: Option<Vec<AttributeEntry>>,
        active_effects: Option<Vec<ActiveEffect>>,
    }

But I see you might be well aware of this but just rename each field since Minecraft's way of storing data is so inconsistent...