use std::collections::HashSet;
use std::fs::{File, OpenOptions, create_dir_all};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use super::ecc::calculate;
use super::entry::Entry;
use super::error::Ps2mcError;
use super::icon::Icon;
use super::icon_sys::IconSys;
use super::page::Page;
use super::super_block::SuperBlock;
const FAT_ALLOCATED_BIT: u32 = 0x8000_0000;
const FAT_CHAIN_END: u32 = 0x7FFF_FFFF;
const FAT_UNALLOCATED: u32 = 0xFFFF_FFFF;
pub struct MemoryCard {
file: File,
super_block: SuperBlock,
cluster_buffer: Box<[u8]>,
cluster_data_buffer: Box<[u8]>,
page_buffer: Box<[u8]>,
fat_clusters: Vec<u32>,
entries_in_root: Vec<Entry>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SaveIconSet {
pub icon_sys: IconSys,
pub icons: Vec<Icon>,
}
impl MemoryCard {
pub fn open(path: impl AsRef<Path>) -> Result<Self, Ps2mcError> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
Self::from_file(file)
}
pub fn from_file(mut file: File) -> Result<Self, Ps2mcError> {
let mut super_block_bytes = vec![0_u8; 340];
file.read_exact(&mut super_block_bytes)?;
let super_block = SuperBlock::read_from(&super_block_bytes)?;
let raw_page_size = super_block.raw_page_size;
let raw_cluster_size = raw_page_size * super_block.pages_per_cluster;
let cluster_size = super_block.cluster_size;
let mut instance = Self {
file,
super_block,
cluster_buffer: vec![0u8; raw_cluster_size].into_boxed_slice(),
cluster_data_buffer: vec![0u8; cluster_size].into_boxed_slice(),
page_buffer: vec![0u8; raw_page_size].into_boxed_slice(),
fat_clusters: Vec::new(),
entries_in_root: Vec::new(),
};
instance.build_fat()?; let root_entry = instance.root_entry()?;
instance.entries_in_root = instance
.find_sub_entries(root_entry.cluster, root_entry.length)?
.into_iter()
.filter(|e| e.is_exists())
.collect();
Ok(instance)
}
fn build_fat(&mut self) -> Result<(), Ps2mcError> {
let indirect_clusters: Vec<u32> = self
.super_block
.ifc_list
.iter()
.filter(|&&c| c != FAT_UNALLOCATED)
.copied()
.collect();
for &indirect_cluster in &indirect_clusters {
let indirect_entries: Vec<u32> = {
let cluster_data = self.read_cluster(indirect_cluster as usize)?;
cluster_data
.chunks_exact(4)
.map(|chunk| u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
.filter(|v| *v != FAT_UNALLOCATED)
.collect()
};
for &entry in &indirect_entries {
let fat_entries: Vec<u32> = {
let cluster_data = self.read_cluster(entry as usize)?;
cluster_data
.chunks_exact(4)
.map(|chunk| u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
.collect()
};
self.fat_clusters.extend(fat_entries);
}
}
Ok(())
}
fn read_entry_cluster(&mut self, n: usize) -> Result<Vec<Entry>, Ps2mcError> {
let n = n + self.super_block.alloc_offset; Ok(Entry::build(self.read_cluster(n)?))
}
fn read_cluster(&mut self, n: usize) -> Result<&[u8], Ps2mcError> {
let raw_page_size = self.super_block.raw_page_size;
let page_size = self.super_block.page_size;
let pages_per_cluster = self.super_block.pages_per_cluster;
let offset = (n * pages_per_cluster * raw_page_size) as u64;
self.file.seek(SeekFrom::Start(offset))?;
self.file.read_exact(&mut self.cluster_buffer)?;
for i in 0..pages_per_cluster {
let start = i * raw_page_size;
let end = start + raw_page_size;
let raw_slice = &self.cluster_buffer[start..end];
let page = Page::from_raw(raw_slice, self.super_block.page_size, self.super_block.spare_size).ok_or(
Ps2mcError::InvalidPageSize {
page_size: self.super_block.page_size,
},
)?;
let dest_start = i * page_size;
let dest_end = dest_start + page_size;
self.cluster_data_buffer[dest_start..dest_end].copy_from_slice(page.data);
}
Ok(&self.cluster_data_buffer)
}
fn root_entry(&mut self) -> Result<Entry, Ps2mcError> {
self.read_entry_cluster(self.super_block.rootdir_cluster)?
.into_iter()
.next()
.ok_or(Ps2mcError::InvalidRootEntry)
}
fn find_sub_entries(&mut self, cluster: u32, length: usize) -> Result<Vec<Entry>, Ps2mcError> {
let mut chain_start = cluster;
let sub_entries_count = length;
let mut entries = Vec::with_capacity(sub_entries_count);
let mut visited = HashSet::new();
while chain_start != FAT_CHAIN_END && entries.len() < sub_entries_count {
if !visited.insert(chain_start) {
return Err(Ps2mcError::FatCycle { cluster: chain_start });
}
let read_entries = self.read_entry_cluster(chain_start as usize)?;
for entry in read_entries {
if entries.len() < sub_entries_count {
entries.push(entry);
}
}
chain_start = self.fat_value(chain_start as usize); }
entries.retain(|v| v.name_bytes[0] != b'.'); Ok(entries)
}
fn fat_value(&self, n: usize) -> u32 {
let value = self.fat_clusters[n];
if value & FAT_ALLOCATED_BIT != 0 {
value ^ FAT_ALLOCATED_BIT
} else {
value
}
}
pub fn entries(&self) -> &[Entry] {
&self.entries_in_root
}
pub fn lookup_entry(&self, name: &str) -> Option<&Entry> {
self.entries_in_root.iter().find(|e| e.decode_name() == name)
}
pub fn lookup_sub_entries(&mut self, name: &str) -> Result<Vec<Entry>, Ps2mcError> {
let entry = self.lookup_entry(name);
match entry {
Some(entry) => self.find_sub_entries(entry.cluster, entry.length),
None => Err(Ps2mcError::SaveNotFound { name: name.to_string() }),
}
}
pub fn export_file(&mut self, name: &str, output_path: impl AsRef<Path>) -> Result<(), Ps2mcError> {
let entries = self.lookup_sub_entries(name)?;
let dir = output_path.as_ref().join(name);
create_dir_all(&dir)?;
for entry in entries {
if entry.is_file() {
let file_path = dir.join(entry.decode_name());
let mut output_file = File::create(file_path)?;
let mut chain_start = entry.cluster;
let mut bytes_read = 0usize;
let mut visited = HashSet::new();
while chain_start != FAT_CHAIN_END && bytes_read < entry.length {
if !visited.insert(chain_start) {
return Err(Ps2mcError::FatCycle { cluster: chain_start });
}
let cluster_bytes = self.read_cluster(chain_start as usize + self.super_block.alloc_offset)?;
let remaining = entry.length - bytes_read;
let to_copy = remaining.min(cluster_bytes.len());
output_file.write_all(&cluster_bytes[..to_copy])?;
bytes_read += to_copy;
chain_start = self.fat_value(chain_start as usize);
}
}
}
Ok(())
}
pub fn read_file(&mut self, cluster: u32, length: usize) -> Result<Vec<u8>, Ps2mcError> {
let mut chain_start = cluster;
let mut data = Vec::with_capacity(length);
let mut bytes_read = 0usize;
let mut visited = HashSet::new();
while chain_start != FAT_CHAIN_END && bytes_read < length {
if !visited.insert(chain_start) {
return Err(Ps2mcError::FatCycle { cluster: chain_start });
}
let cluster_bytes = self.read_cluster(chain_start as usize + self.super_block.alloc_offset)?;
let remaining = length - bytes_read;
let to_copy = remaining.min(cluster_bytes.len());
data.extend_from_slice(&cluster_bytes[..to_copy]);
bytes_read += to_copy;
chain_start = self.fat_value(chain_start as usize);
}
Ok(data)
}
pub fn write_file(&mut self, cluster: u32, length: usize, data: &[u8]) -> Result<(), Ps2mcError> {
if data.len() != length {
return Err(Ps2mcError::InvalidWriteLength {
expected: length,
actual: data.len(),
});
}
let mut chain_start = cluster;
let mut bytes_written = 0usize;
let mut visited = HashSet::new();
while chain_start != FAT_CHAIN_END && bytes_written < length {
if !visited.insert(chain_start) {
return Err(Ps2mcError::FatCycle { cluster: chain_start });
}
let physical_cluster = chain_start as usize + self.super_block.alloc_offset;
let remaining = length - bytes_written;
let to_write = remaining.min(self.super_block.cluster_size);
let chunk = &data[bytes_written..bytes_written + to_write];
self.write_cluster(physical_cluster, chunk)?;
bytes_written += to_write;
chain_start = self.fat_value(chain_start as usize);
}
if bytes_written != length {
return Err(Ps2mcError::UnexpectedEof {
context: "write file cluster chain",
expected: length,
actual: bytes_written,
});
}
Ok(())
}
fn write_cluster(&mut self, physical_cluster: usize, data: &[u8]) -> Result<(), Ps2mcError> {
let cluster_size = self.super_block.cluster_size;
let write_len = data.len().min(cluster_size);
let _ = self.read_cluster(physical_cluster)?;
self.cluster_data_buffer[..write_len].copy_from_slice(&data[..write_len]);
let buffer = std::mem::take(&mut self.cluster_data_buffer);
let result = self.write_cluster_data(physical_cluster, &buffer);
self.cluster_data_buffer = buffer;
result
}
fn write_cluster_data(&mut self, physical_cluster: usize, data: &[u8]) -> Result<(), Ps2mcError> {
let raw_page_size = self.super_block.raw_page_size;
let page_size = self.super_block.page_size;
let pages_per_cluster = self.super_block.pages_per_cluster;
assert_eq!(data.len(), page_size * pages_per_cluster, "簇数据必须填满整个簇");
let cluster_offset = (physical_cluster * pages_per_cluster * raw_page_size) as u64;
for i in 0..pages_per_cluster {
let page_data = &data[i * page_size..(i + 1) * page_size];
self.page_buffer[..page_size].copy_from_slice(page_data);
let spare_start = page_size;
let ecc_bytes_needed = 3 * (page_size / 128);
for (chunk_idx, chunk) in page_data.chunks_exact(128).enumerate() {
let ecc = calculate(chunk)?;
let offset = spare_start + chunk_idx * 3;
self.page_buffer[offset..offset + 3].copy_from_slice(&ecc);
}
for b in self.page_buffer[spare_start + ecc_bytes_needed..].iter_mut() {
*b = 0;
}
let page_offset = cluster_offset + (i * raw_page_size) as u64;
self.file.seek(SeekFrom::Start(page_offset))?;
self.file.write_all(&self.page_buffer)?;
}
Ok(())
}
pub fn get_icon(&mut self, name: &str) -> Result<SaveIconSet, Ps2mcError> {
let entries = self.lookup_sub_entries(name)?;
let icon_sys_entry =
entries
.iter()
.find(|e| e.decode_name() == "icon.sys")
.ok_or(Ps2mcError::IconFileNotFound {
name: "icon.sys".to_string(),
})?;
let icon_sys_data = self.read_file(icon_sys_entry.cluster, icon_sys_entry.length)?;
let icon_sys = IconSys::read_from(&icon_sys_data)?;
let icon_names = [
icon_sys.icon_file_normal.as_str(),
icon_sys.icon_file_copy.as_str(),
icon_sys.icon_file_delete.as_str(),
];
let mut seen = Vec::with_capacity(3);
let mut icons = Vec::with_capacity(3);
for icon_name in icon_names {
if icon_name.is_empty() || seen.contains(&icon_name) {
continue;
}
seen.push(icon_name);
let icon_entry =
entries
.iter()
.find(|e| e.decode_name() == icon_name)
.ok_or(Ps2mcError::IconFileNotFound {
name: icon_name.to_string(),
})?;
let icon_data = self.read_file(icon_entry.cluster, icon_entry.length)?;
icons.push(Icon::parse(&icon_data)?);
}
Ok(SaveIconSet { icon_sys, icons })
}
pub fn print_info(&self) {
let super_block = &self.super_block;
println!("SuperBlock Info:");
println!("Page Size: {}", super_block.page_size);
println!("Pages per Cluster: {}", super_block.pages_per_cluster);
println!("Clusters per Card: {}", super_block.clusters_per_card);
println!("Allocation Offset: {}", super_block.alloc_offset);
println!("Root Directory Cluster: {}", super_block.rootdir_cluster);
println!("FAT per Cluster: {}", super_block.fat_per_cluster);
println!("IFC List: {:?}", super_block.ifc_list);
}
}