use std::{ffi::CStr, iter, ptr::NonNull, slice};
use crate::fd4::FD4ResCapHolder;
use crate::param::ParamDef;
use shared::{OwnedPtr, Subclass};
use super::{FD4ResCap, FD4ResRep};
#[repr(C)]
#[shared::singleton("FD4ParamRepository")]
#[derive(Subclass)]
#[subclass(base = FD4ResRep, base = FD4ResCap)]
pub struct FD4ParamRepository {
pub res_rep: FD4ResRep,
res_cap_holder: FD4ResCapHolder<FD4ParamResCap>,
allocator: usize,
}
impl FD4ParamRepository {
pub unsafe fn res_cap_holder(&self) -> &FD4ResCapHolder<FD4ParamResCap> {
&self.res_cap_holder
}
pub unsafe fn res_cap_holder_mut(&mut self) -> &mut FD4ResCapHolder<FD4ParamResCap> {
&mut self.res_cap_holder
}
pub unsafe fn get<P: ParamDef>(&self, id: u32) -> Option<&P> {
unsafe { self.get_rescap::<P>() }.and_then(|entry| unsafe { entry.get::<P>(id) })
}
pub unsafe fn get_mut<P: ParamDef>(&mut self, id: u32) -> Option<&mut P> {
unsafe { self.get_rescap_mut::<P>() }.and_then(|entry| unsafe { entry.get_mut::<P>(id) })
}
unsafe fn get_rescap<P: ParamDef>(&self) -> Option<&FD4ParamResCap> {
self.res_cap_holder
.entries()
.find(|e| e.struct_name() == P::NAME)
}
unsafe fn get_rescap_mut<P: ParamDef>(&mut self) -> Option<&mut FD4ParamResCap> {
self.res_cap_holder
.entries_mut()
.find(|e| e.struct_name() == P::NAME)
}
}
#[repr(C)]
#[derive(Subclass)]
pub struct FD4ParamResCap {
pub res_cap: FD4ResCap,
pub size: u64,
pub data: OwnedPtr<ParamFile>,
}
impl FD4ParamResCap {
pub fn struct_name(&self) -> &str {
self.data.struct_name()
}
pub unsafe fn get<P: ParamDef>(&self, id: u32) -> Option<&P> {
unsafe { self.data.get_row_by_id(id) }
}
pub unsafe fn get_mut<P: ParamDef>(&mut self, id: u32) -> Option<&mut P> {
unsafe { self.data.get_row_by_id_mut(id) }
}
}
#[repr(C)]
struct ParamFileMetadata {
after_name_offset: u32,
row_count: u32,
_reserved: u64,
}
#[repr(C)]
struct RowLookupEntry {
param_id: u32,
index: u32,
}
#[repr(C)]
struct RowDescriptor<T: Into<u64> + Copy> {
id: u32,
data_offset: T,
name_offset: T,
}
impl<T: Into<u64> + Copy> RowDescriptor<T> {
pub fn data_offset(&self) -> usize {
self.data_offset.into() as usize
}
}
#[repr(C)]
pub struct ParamFile {
strings_offset: u32,
short_data_offset: u16,
_unk06: u16,
paramdef_version: u16,
row_count: u16,
struct_name: ParamStructName,
big_endian: u8,
format_2d: u8,
format_2e: u8,
}
impl ParamFile {
const LOOKUP_TABLE_ALIGNMENT: u32 = 0x10;
pub fn struct_name(&self) -> &str {
if self.has_offset_param_type() {
self.read_offset_struct_name()
} else {
self.read_inline_struct_name()
}
}
pub const fn paramdef_version(&self) -> u16 {
self.paramdef_version
}
pub const fn row_count(&self) -> usize {
self.row_count as usize
}
pub unsafe fn get_row_by_id<P: ParamDef>(&self, id: u32) -> Option<&P> {
let index = self.find_index(id)?;
let (actual_id, data_offset) = self.row_data_offset(index)?;
debug_assert_eq!(id, actual_id, "Unexpected row ID for {}", P::NAME);
Some(unsafe { self.offset::<P>(data_offset).as_ref() })
}
pub unsafe fn get_row_by_id_mut<P: ParamDef>(&mut self, id: u32) -> Option<&mut P> {
let index = self.find_index(id)?;
let (actual_id, data_offset) = self.row_data_offset(index)?;
debug_assert_eq!(id, actual_id, "Unexpected row ID for {}", P::NAME);
Some(unsafe { self.offset::<P>(data_offset).as_mut() })
}
pub unsafe fn get_row_by_index<P: ParamDef>(&self, row_index: usize) -> Option<&P> {
let data_offset = self.row_data_offset(row_index)?.1;
Some(unsafe { self.offset::<P>(data_offset).as_ref() })
}
pub unsafe fn get_row_by_index_mut<P: ParamDef>(&mut self, row_index: usize) -> Option<&mut P> {
let data_offset = self.row_data_offset(row_index)?.1;
Some(unsafe { self.offset::<P>(data_offset).as_mut() })
}
pub unsafe fn rows<'a, P: ParamDef + 'a>(&'a self) -> impl Iterator<Item = (u32, &'a P)> + 'a {
self.lookup_table()
.iter()
.map(|l| unsafe { (l.param_id, self.get_row_by_index(l.index as usize).unwrap()) })
}
pub unsafe fn rows_mut<'a, P: ParamDef + 'a>(
&'a mut self,
) -> impl Iterator<Item = (u32, &'a mut P)> + 'a {
let mut file = NonNull::from_ref(self);
let range = self.lookup_table().as_ptr_range();
let mut ptr = range.start;
let end = range.end;
iter::from_fn(move || {
if ptr == end {
return None;
}
unsafe {
let lookup = ptr.as_ref().unwrap();
let result = file
.as_mut()
.get_row_by_index_mut(lookup.index as usize)
.unwrap();
ptr = ptr.add(1);
Some((lookup.param_id, result))
}
})
}
pub fn find_index(&self, id: u32) -> Option<usize> {
let table = self.lookup_table();
let target_index = self
.lookup_table()
.binary_search_by(|entry| entry.param_id.cmp(&id))
.ok()?;
Some(table[target_index].index as usize)
}
fn lookup_table(&self) -> &[RowLookupEntry] {
let aligned_file_size =
self.metadata()
.after_name_offset
.next_multiple_of(Self::LOOKUP_TABLE_ALIGNMENT) as usize;
unsafe {
slice::from_raw_parts(
self.offset::<RowLookupEntry>(aligned_file_size).as_ptr(),
self.row_count as usize,
)
}
}
const fn metadata(&self) -> &ParamFileMetadata {
unsafe {
let metadata_ptr = (self as *const Self).byte_sub(size_of::<ParamFileMetadata>())
as *const ParamFileMetadata;
&*metadata_ptr
}
}
const unsafe fn offset<T>(&self, offset: usize) -> NonNull<T> {
unsafe { NonNull::from_ref(self).cast::<u8>().add(offset).cast::<T>() }
}
fn read_inline_struct_name(&self) -> &str {
unsafe {
CStr::from_ptr(self.struct_name.inline.as_ptr() as *const i8)
.to_str()
.unwrap_or("")
}
}
fn read_offset_struct_name(&self) -> &str {
unsafe {
let offset = self.struct_name.offset.value;
if offset == 0 {
return "";
}
CStr::from_ptr(self.offset::<i8>(offset as usize).as_ptr())
.to_str()
.unwrap_or("")
}
}
const fn row_descriptors<T: Into<u64> + Copy>(&self) -> &[RowDescriptor<T>] {
let offset = size_of::<ParamFile>() + if self.has_extended_header() { 0x10 } else { 0 };
unsafe {
slice::from_raw_parts(
self.offset::<RowDescriptor<T>>(offset).as_ptr(),
self.row_count as usize,
)
}
}
fn row_data_offset(&self, row_index: usize) -> Option<(u32, usize)> {
if row_index >= self.row_count() {
return None;
}
if self.is_64_bit() {
self.row_descriptors::<u64>()
.get(row_index)
.map(|d| (d.id, d.data_offset()))
} else {
self.row_descriptors::<u32>()
.get(row_index)
.map(|d| (d.id, d.data_offset()))
}
}
const fn has_offset_param_type(&self) -> bool {
(self.format_2d & 0x80) != 0
}
const fn is_64_bit(&self) -> bool {
(self.format_2d & 0x04) != 0
}
const fn has_extended_header(&self) -> bool {
self.is_64_bit() || ((self.format_2d & 0x01) != 0 && (self.format_2d & 0x02) != 0)
}
}
#[repr(C)]
union ParamStructName {
inline: [u8; 0x20],
offset: ParamStructNameOffset,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct ParamStructNameOffset {
_pad: u32,
value: u32,
_reserved: [u32; 6],
}