use crate::codec::formats::osu::parser::{
parse_difficulty, parse_event, parse_general, parse_metadata, parse_timing_point,
};
use crate::error::{RoxError, RoxResult};
use super::types::{TaikoBeatmap, TaikoHitObject, TaikoHitsound};
const MAX_FILE_SIZE: usize = 100 * 1024 * 1024;
pub fn parse(data: &[u8]) -> RoxResult<TaikoBeatmap> {
if data.len() > MAX_FILE_SIZE {
return Err(RoxError::InvalidFormat(format!(
"File too large: {} bytes (max {}MB)",
data.len(),
MAX_FILE_SIZE / 1024 / 1024
)));
}
let content = std::str::from_utf8(data)
.map_err(|e| RoxError::InvalidFormat(format!("Invalid UTF-8: {e}")))?;
let mut beatmap = TaikoBeatmap::default();
let mut section = "";
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("//") {
continue;
}
if line.starts_with("osu file format v") {
beatmap.format_version = line
.strip_prefix("osu file format v")
.and_then(|s| s.parse().ok())
.unwrap_or(14);
continue;
}
if line.starts_with('[') && line.ends_with(']') {
section = line;
continue;
}
match section {
"[General]" => parse_general(line, &mut beatmap.general),
"[Metadata]" => parse_metadata(line, &mut beatmap.metadata),
"[Difficulty]" => parse_difficulty(line, &mut beatmap.difficulty),
"[Events]" => parse_event(line, &mut beatmap.background),
"[TimingPoints]" => {
if let Some(tp) = parse_timing_point(line) {
beatmap.timing_points.push(tp);
}
}
"[HitObjects]" => parse_hit_object_line(line, &mut beatmap),
_ => {}
}
}
Ok(beatmap)
}
fn parse_hit_object_line(line: &str, beatmap: &mut TaikoBeatmap) {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() >= 5 {
let time_ms: f64 = match parts[2].parse() {
Ok(v) => v,
Err(_) => {
tracing::warn!("Failed to parse time_ms in taiko object: '{}'", parts[2]);
0.0
}
};
let object_type: u32 = match parts[3].parse() {
Ok(v) => v,
Err(_) => {
tracing::warn!(
"Failed to parse object_type in taiko object: '{}'",
parts[3]
);
0
}
};
let hitsound: u32 = match parts[4].parse() {
Ok(v) => v,
Err(_) => {
tracing::warn!("Failed to parse hitsound in taiko object: '{}'", parts[4]);
0
}
};
beatmap.hit_objects.push(TaikoHitObject {
time_ms,
hitsound: TaikoHitsound::from_bits_truncate(hitsound),
object_type,
});
}
}