61 lines
1.9 KiB
Rust
61 lines
1.9 KiB
Rust
use anyhow::Result;
|
||
use byteorder::{LittleEndian, ReadBytesExt};
|
||
use serde::{Deserialize, Serialize};
|
||
use std::io::{Cursor, Read};
|
||
use std::path::Path;
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct HueEntry {
|
||
pub id: u32,
|
||
pub name: String,
|
||
/// 32 colors as RGBA u32 values.
|
||
pub colors: Vec<u32>,
|
||
}
|
||
|
||
/// Read all hues from hues.mul.
|
||
/// Format: 375 blocks × 8 hues = 3000 hues.
|
||
/// Each hue: 32×u16 colors + u16 tableStart + u16 tableEnd + 20-byte name.
|
||
pub fn read_hues(hues_path: &Path) -> Result<Vec<HueEntry>> {
|
||
let data = std::fs::read(hues_path)?;
|
||
let mut cursor = Cursor::new(&data);
|
||
let mut hues = Vec::new();
|
||
|
||
for block in 0..375u32 {
|
||
// Block header (4 bytes, skip)
|
||
let _header = cursor.read_u32::<LittleEndian>()?;
|
||
|
||
for h in 0..8u32 {
|
||
let id = block * 8 + h;
|
||
let mut raw_colors = [0u16; 32];
|
||
for c in raw_colors.iter_mut() {
|
||
*c = cursor.read_u16::<LittleEndian>()?;
|
||
}
|
||
let _table_start = cursor.read_u16::<LittleEndian>()?;
|
||
let _table_end = cursor.read_u16::<LittleEndian>()?;
|
||
|
||
let mut name_buf = [0u8; 20];
|
||
cursor.read_exact(&mut name_buf)?;
|
||
let name = String::from_utf8_lossy(&name_buf)
|
||
.trim_end_matches('\0')
|
||
.to_string();
|
||
|
||
let colors = raw_colors
|
||
.iter()
|
||
.map(|&c| {
|
||
let r = ((c >> 10) & 0x1F) as u32;
|
||
let g = ((c >> 5) & 0x1F) as u32;
|
||
let b = (c & 0x1F) as u32;
|
||
let r = (r << 3) | (r >> 2);
|
||
let g = (g << 3) | (g >> 2);
|
||
let b = (b << 3) | (b >> 2);
|
||
(r << 24) | (g << 16) | (b << 8) | 0xFF
|
||
})
|
||
.collect();
|
||
|
||
hues.push(HueEntry { id, name, colors });
|
||
}
|
||
}
|
||
|
||
Ok(hues)
|
||
}
|