use std::io::{Cursor, Read, Seek, SeekFrom};
use byteorder::{BigEndian, ReadBytesExt};
use crate::error::UffError;
use crate::types::*;
const MAGIC: [u8; 4] = [b'U', b'F', b'F', 0x00];
fn read_str<R: Read>(r: &mut R) -> Result<String, UffError> {
let len = r.read_u16::<BigEndian>()? as usize;
let mut buf = vec![0u8; len];
r.read_exact(&mut buf)?;
Ok(String::from_utf8(buf)?)
}
fn read_hitbox<R: Read>(r: &mut R) -> Result<Hitbox, UffError> {
let name = read_str(r)?;
let raw_type = r.read_u8()?;
let flags = r.read_u8()?;
let x = r.read_f32::<BigEndian>()?;
let y = r.read_f32::<BigEndian>()?;
let width = r.read_f32::<BigEndian>()?;
let height= r.read_f32::<BigEndian>()?;
let damage = r.read_u16::<BigEndian>()?;
let hitstun = r.read_u16::<BigEndian>()?;
let blockstun = r.read_u16::<BigEndian>()?;
let knockback_angle = r.read_f32::<BigEndian>()?;
let knockback_strength = r.read_f32::<BigEndian>()?;
let rotation = r.read_f32::<BigEndian>()?;
Ok(Hitbox {
name,
box_type: BoxType::from_u8(raw_type).unwrap_or(BoxType::Hurt),
enabled: (flags & 0x01) != 0,
knockback: (flags & 0x02) != 0,
x, y, width, height,
damage, hitstun, blockstun,
knockback_angle, knockback_strength, rotation,
})
}
fn read_audio_cue<R: Read>(r: &mut R) -> Result<AudioCue, UffError> {
let clip_id = read_str(r)?;
let volume = r.read_f32::<BigEndian>()?;
let pitch = r.read_f32::<BigEndian>()?;
Ok(AudioCue { clip_id, volume, pitch })
}
fn read_frame_entry<R: Read>(r: &mut R) -> Result<HitboxFrame, UffError> {
let id = r.read_u16::<BigEndian>()?;
let label = read_str(r)?;
let program = read_str(r)?;
let tags = read_str(r)?;
let hitbox_count = r.read_u16::<BigEndian>()? as usize;
let mut hitboxes = Vec::with_capacity(hitbox_count);
for _ in 0..hitbox_count {
hitboxes.push(read_hitbox(r)?);
}
let cue_count = r.read_u16::<BigEndian>()? as usize;
let mut audio_cues = Vec::with_capacity(cue_count);
for _ in 0..cue_count {
audio_cues.push(read_audio_cue(r)?);
}
Ok(HitboxFrame { id, label, program, tags, hitboxes, audio_cues })
}
fn read_sprite_entry<R: Read>(r: &mut R) -> Result<SpriteEntry, UffError> {
let src_x = r.read_u16::<BigEndian>()?;
let src_y = r.read_u16::<BigEndian>()?;
let src_w = r.read_u16::<BigEndian>()?;
let src_h = r.read_u16::<BigEndian>()?;
let pivot_x = r.read_i16::<BigEndian>()?;
let pivot_y = r.read_i16::<BigEndian>()?;
let entry_flags = r.read_u8()?;
let tag = r.read_u8()?;
let duration_ms = r.read_u16::<BigEndian>()?;
Ok(SpriteEntry { src_x, src_y, src_w, src_h, pivot_x, pivot_y, entry_flags, tag, duration_ms })
}
fn read_anim(data: &[u8], offset: u32) -> Result<UffAnimation, UffError> {
let mut r = Cursor::new(data);
r.seek(SeekFrom::Start(offset as u64))?;
let name_len = r.read_u16::<BigEndian>()? as usize;
let frame_count = r.read_u16::<BigEndian>()? as usize;
let default_fps = r.read_u16::<BigEndian>()?;
let loop_mode_raw = r.read_u8()?;
let pixel_flags = r.read_u8()?;
let sheet_w = r.read_u16::<BigEndian>()?;
let sheet_h = r.read_u16::<BigEndian>()?;
let frame_w = r.read_u16::<BigEndian>()?;
let frame_h = r.read_u16::<BigEndian>()?;
let hitbox_size = r.read_u32::<BigEndian>()?;
let pixel_size = r.read_u32::<BigEndian>()?;
let floor_y_override = r.read_u16::<BigEndian>()?;
let mut name_bytes = vec![0u8; name_len];
r.read_exact(&mut name_bytes)?;
let name = String::from_utf8(name_bytes)?;
let mut sprite_table = Vec::with_capacity(frame_count);
for _ in 0..frame_count {
sprite_table.push(read_sprite_entry(&mut r)?);
}
let frames = if hitbox_size > 0 {
let mut frames = Vec::with_capacity(frame_count);
for _ in 0..frame_count {
frames.push(read_frame_entry(&mut r)?);
}
frames
} else {
Vec::new()
};
let pixel_data = if pixel_size > 0 {
let mut buf = vec![0u8; pixel_size as usize];
r.read_exact(&mut buf)?;
Some(buf)
} else {
None
};
Ok(UffAnimation {
name,
default_fps,
loop_mode: LoopMode::from_u8(loop_mode_raw),
pixel_flags,
sheet_w, sheet_h, frame_w, frame_h,
floor_y_override,
sprite_table,
frames,
pixel_data,
})
}
pub fn read_uff(data: &[u8]) -> Result<UffPackage, UffError> {
let mut r = Cursor::new(data);
let mut magic = [0u8; 4];
r.read_exact(&mut magic)?;
if magic != MAGIC {
return Err(UffError::BadMagic(magic));
}
let version = r.read_u8()?;
if version != 1 {
return Err(UffError::UnsupportedVersion(version));
}
let _pkg_flags = r.read_u8()?;
let anim_count = r.read_u16::<BigEndian>()? as usize;
let dir_offset = r.read_u32::<BigEndian>()?;
r.seek(SeekFrom::Current(4))?; let floor_y = r.read_u16::<BigEndian>()?;
r.seek(SeekFrom::Current(6))?;
let char_name = read_str(&mut r)?;
r.seek(SeekFrom::Start(dir_offset as u64))?;
let mut offsets = Vec::with_capacity(anim_count);
for _ in 0..anim_count {
offsets.push(r.read_u32::<BigEndian>()?);
}
let mut animations = Vec::with_capacity(anim_count);
for off in offsets {
animations.push(read_anim(data, off)?);
}
Ok(UffPackage { char_name, floor_y, animations })
}