psxmem/
lib.rs

1//! # PSXmem
2//!
3//! `psxmem` is a library that can be used to read in and parse raw PSX/PS1 memory card dumps
4//! including raw *.mcr formats that some emulators use.
5
6use std::fs::File;
7use std::io::{BufWriter, Read, Write};
8use std::{fmt, str};
9
10use byteorder::{LittleEndian, ReadBytesExt};
11use deku::prelude::*;
12use gif::{Encoder as GifEncoder, Frame as GifFrame, Repeat};
13use png::Encoder;
14
15mod errors;
16pub use crate::errors::MCError;
17
18const BLOCK: usize = 0x2000;
19const FRAME: usize = 0x80;
20
21#[derive(Clone, Copy, Debug, DekuRead, DekuWrite, PartialEq, Eq)]
22#[deku(endian = "little")]
23pub struct Header {
24    id: [u8; 2],
25    pad: [u8; 125],
26    checksum: u8,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30#[repr(u32)]
31pub enum BAState {
32    AllocFirst = 0x51,
33    AllocMid = 0x52,
34    AllocLast = 0x53,
35    Free = 0xa0,
36    FreeFirst = 0xa1,
37    FreeMid = 0xa2,
38    FreeLast = 0xa3,
39    UNKNOWN,
40}
41
42#[derive(Clone, Copy, Debug, DekuRead, DekuWrite, PartialEq, Eq)]
43#[deku(endian = "little")]
44pub struct DirectoryFrame {
45    pub state: u32,
46    pub filesize: u32,
47    pub next_block: u16,
48    pub filename: [u8; 21],
49    pub pad: [u8; 96],
50    pub checksum: u8,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum Region {
55    Japan,
56    America,
57    Europe,
58    UNKNOWN,
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum License {
63    Sony,
64    Licensed,
65    UNKNOWN,
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct RegionInfo {
70    pub region: Region,
71    pub license: License,
72    pub name: String,
73}
74
75impl DirectoryFrame {
76    fn load(input: &[u8], n: usize) -> Result<Vec<Self>, MCError> {
77        let mut frame = Vec::<Self>::new();
78        validate_checksum(input)?;
79        let (mut next, mut df) = Self::from_bytes((input, 0))?;
80        frame.push(df);
81        loop {
82            if frame.len() == n {
83                break;
84            }
85            let (input, _) = next;
86            validate_checksum(input)?;
87            (next, df) = Self::from_bytes(next)?;
88            frame.push(df);
89        }
90        Ok(frame)
91    }
92
93    fn get_alloc_state(&self) -> BAState {
94        match self.state {
95            0x51 => BAState::AllocFirst,
96            0x52 => BAState::AllocMid,
97            0x53 => BAState::AllocLast,
98            0xa0 => BAState::Free,
99            0xa1 => BAState::FreeFirst,
100            0xa2 => BAState::FreeMid,
101            0xa3 => BAState::FreeLast,
102            _ => BAState::UNKNOWN,
103        }
104    }
105
106    fn get_region_info(&self) -> Result<RegionInfo, MCError> {
107        let region = match self.filename[1] {
108            b'I' => Region::Japan,
109            b'A' => Region::America,
110            b'E' => Region::Europe,
111            _ => Region::UNKNOWN,
112        };
113
114        let license = match self.filename[3] {
115            b'C' => License::Sony,
116            b'L' => License::Licensed,
117            _ => License::UNKNOWN,
118        };
119
120        let name = str::from_utf8(&self.filename[12..])?.to_string();
121
122        Ok(RegionInfo {
123            region,
124            license,
125            name,
126        })
127    }
128}
129
130impl fmt::Display for DirectoryFrame {
131    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132        write!(
133            f,
134            "\n State: {:?}\n Filesize: {}\n Next block: {}\n Region Info: {:?}\n Checksum: {}",
135            self.get_alloc_state(),
136            self.filesize,
137            self.next_block,
138            self.get_region_info(),
139            self.checksum
140        )
141    }
142}
143
144#[derive(Clone, Copy, Debug, DekuRead, DekuWrite, PartialEq, Eq)]
145#[deku(endian = "little")]
146pub struct BrokenFrame {
147    broken_frame: u32,
148    pad: [u8; 123],
149    checksum: u8,
150}
151
152impl BrokenFrame {
153    fn load(input: &[u8], n: usize) -> Result<Vec<Self>, MCError> {
154        let mut frame = Vec::<Self>::new();
155        validate_checksum(input)?;
156        let (mut next, mut df) = Self::from_bytes((input, 0))?;
157        frame.push(df);
158        loop {
159            if frame.len() == n {
160                break;
161            }
162            let (input, _) = next;
163            validate_checksum(input)?;
164            (next, df) = Self::from_bytes(next)?;
165            frame.push(df);
166        }
167        Ok(frame)
168    }
169}
170
171/// Frame
172///
173/// A `Frame` is 128 bytes of data. Typically the final byte of data is a checksum, but several
174/// `Frame` types do not follow that convention.
175#[derive(Clone, Copy, Debug, DekuRead, DekuWrite, PartialEq, Eq)]
176#[deku(endian = "little")]
177pub struct Frame {
178    /// The data contained in the `Frame`.
179    pub data: [u8; FRAME],
180}
181
182impl Frame {
183    /// `load` will read in `n` x `Frame`s worth of data and return a `Result` of a `Vec<Frame>`
184    /// and will also validate the checksum of the frames.
185    pub fn load(input: &[u8], n: usize) -> Result<Vec<Self>, MCError> {
186        let mut frame = Vec::<Self>::new();
187        validate_checksum(input)?;
188        let (mut next, mut df) = Self::from_bytes((input, 0))?;
189        frame.push(df);
190        loop {
191            if frame.len() == n {
192                break;
193            }
194            let (input, _) = next;
195            validate_checksum(input)?;
196            (next, df) = Self::from_bytes(next)?;
197            frame.push(df);
198        }
199        Ok(frame)
200    }
201
202    pub fn print_strings(f: &Frame) {
203        let mut s = String::new();
204
205        for i in f.data {
206            if i.is_ascii_graphic() || i == b' ' {
207                s.push(i as char);
208                continue;
209            } else if i == 0 && s.len() > 1 {
210                println!("{}", s.trim_start());
211            }
212            s.clear();
213        }
214        if !s.is_empty() {
215            println!("{}", s.trim_start());
216        }
217    }
218
219    pub fn print_values(f: &Frame) {
220        let mut buf: &[u8] = &f.data;
221        let mut count = 0;
222        while let Ok(v) = buf.read_u16::<LittleEndian>() {
223            if v != 0 {
224                println!("{:02}) Hex: {:04x} Dec: {}", count, v, v);
225            }
226            count += 1;
227        };
228    }
229
230    pub fn print_hex(f: &Frame) {
231        let mut s = String::new();
232        for (e, i) in f.data.iter().enumerate() {
233            if e > 0 {
234                if e % 16 == 0 {
235                    println!(" {}", s);
236                    s.clear();
237                } else if e % 2 == 0 {
238                    print!(" |");
239                }
240            }
241            print!(" {:02x}", i);
242            if i.is_ascii_graphic() {
243                s.push(*i as char);
244            } else {
245                s.push('.');
246            }
247        }
248        println!(" {}", s);
249    }
250
251    pub fn set_u32_at(f: &mut Frame, v: u32, ofs: usize) -> Result<(), MCError> {
252        let mut idx = &mut f.data[ofs..];
253        idx.write_all(v.to_le_bytes().as_ref())?;
254
255        Ok(())
256    }
257
258    pub fn set_u16_at(f: &mut Frame, v: u16, ofs: usize) -> Result<(), MCError> {
259        let mut idx = &mut f.data[ofs..];
260        idx.write_all(v.to_le_bytes().as_ref())?;
261
262        Ok(())
263    }
264}
265
266/// Block
267///
268/// A `Block` is 8KB of data, or 64 `Frame`s.
269#[derive(Clone, Copy, Debug, DekuRead, DekuWrite, PartialEq, Eq)]
270#[deku(endian = "little")]
271pub struct Block {
272    /// The data contained in the `Block`.
273    pub data: [u8; BLOCK],
274}
275
276/// DataBlock
277///
278/// A `DataBlock` is a `Block` that is a game save block.
279#[derive(Clone, Debug, PartialEq, Eq)]
280pub struct DataBlock {
281    /// The frame that contains the Title information.
282    pub title_frame: TitleFrame,
283    /// The frame(s) that contain the Icon information. This is the static or animated
284    /// image that is displayed when viewing the memory card management. There can be
285    /// 1 to 3 frames per save file.
286    pub icon_frames: Vec<Frame>,
287
288    /// The actual save data is stored here.
289    pub data_frames: Vec<Frame>,
290}
291
292impl DataBlock {
293    /// Parse a raw `Block` into a `DataBlock`.
294    pub fn load_data_block(b: Block) -> Result<Self, MCError> {
295        // Read title frame
296        let (_, title_frame) = TitleFrame::from_bytes((&b.data, 0))?;
297
298        // Read icon frame(s)
299        let num_frames = title_frame.display as usize & 0x03;
300        let icon_frames = DataBlock::read_n_frames(&b.data[FRAME..], num_frames)?;
301
302        // Read data frame
303        // title_frame len + (icon_frame len * num icon_frames)
304        let next = FRAME + (FRAME * icon_frames.len());
305        let num_frames = b.data[next..].len() / FRAME;
306        let data_frames = DataBlock::read_n_frames(&b.data[next..], num_frames)?;
307
308        Ok(DataBlock {
309            title_frame,
310            icon_frames,
311            data_frames,
312        })
313    }
314
315    /// Parse all `Block`s into `DataBlock`s.
316    pub fn load_all_data_blocks(v: &[Block]) -> Result<Vec<Self>, MCError> {
317        let mut out = Vec::<Self>::new();
318        for i in v {
319            out.push(Self::load_data_block(*i)?);
320        }
321
322        Ok(out)
323    }
324
325    fn read_n_frames(input: &[u8], num_frames: usize) -> Result<Vec<Frame>, MCError> {
326        let mut frame = Vec::<Frame>::new();
327        let (mut next, mut f) = Frame::from_bytes((input, 0))?;
328        frame.push(f);
329        loop {
330            if frame.len() == num_frames {
331                break;
332            }
333            (next, f) = Frame::from_bytes(next)?;
334            frame.push(f);
335        }
336        Ok(frame)
337    }
338
339    /// Write all `DataBlock` data to `out`.
340    pub fn write<T: std::io::Write>(&self, out: &mut T) -> Result<(), MCError> {
341        let t = self.title_frame.to_bytes()?;
342        out.write_all(&t)?;
343
344        for ic in &self.icon_frames {
345            let i = ic.to_bytes()?;
346            out.write_all(&i)?;
347        }
348
349        for df in &self.data_frames {
350            let d = df.to_bytes()?;
351            out.write_all(&d)?;
352        }
353
354        Ok(())
355    }
356
357    /// Export all image frames to separate `.png` image files. If there are more than 1 frames,
358    /// then also export them as a combined `.gif`.
359    pub fn export_all_images(&self) -> Result<(), MCError> {
360        // Extract out individual frames
361        for (n, i) in self.icon_frames.iter().enumerate() {
362            let filename = format!("{}_frame{}.png", self.title_frame.decode_title()?, n);
363            let file = File::create(filename)?;
364            let mut w = BufWriter::new(file);
365            let mut enc = Encoder::new(&mut w, 16, 16);
366            enc.set_color(png::ColorType::Rgba);
367            enc.set_depth(png::BitDepth::Eight);
368
369            let mut writer = enc.write_header()?;
370
371            let pixel_data = self.translate_bmp_to_rgba(i)?;
372
373            writer.write_image_data(&pixel_data)?;
374        }
375
376        // If > 1 frame, extract it out as a gif too
377        if self.icon_frames.len() > 1 {
378            self.export_gif()?;
379        }
380
381        Ok(())
382    }
383
384    fn export_gif(&self) -> Result<(), MCError> {
385        let w = 16;
386        let h = 16;
387        let filename = format!("{}.gif", self.title_frame.decode_title()?);
388        let mut file = File::create(filename)?;
389        let mut enc = GifEncoder::new(&mut file, w, h, &[])?;
390        enc.set_repeat(Repeat::Infinite)?;
391        for i in self.icon_frames.iter() {
392            let mut pixels = self.translate_bmp_to_rgba(i)?;
393            let gifframe = GifFrame::from_rgba(w, h, &mut *pixels);
394            enc.write_frame(&gifframe)?;
395        }
396
397        Ok(())
398    }
399
400    fn translate_bmp_to_rgba(&self, f: &Frame) -> Result<Vec<u8>, MCError> {
401        let mut rgba = Vec::<u8>::new();
402
403        // Each byte in the data array is 2x 4bit addresses into the 16x u16 array palette
404        for v in f.data {
405            for s in 0..2 {
406                let index = (v >> (4 * s as u8)) & 0x0f;
407                let pixel: u16 = self.title_frame.icon_palette[index as usize];
408                // format is abgr, needs to be pushed rgba
409                //
410                // push red
411                rgba.push(((pixel & 0x001f) as u16) as u8 * 8);
412                // push green
413                rgba.push(((pixel & (0x001f << 5)) as u16 >> 5) as u8 * 8);
414                // push blue
415                rgba.push(((pixel & (0x001f << 10)) as u16 >> 10) as u8 * 8);
416                // push alpha alpha is either 1 or 0, best results are simply ignored, lol
417                rgba.push(255);
418            }
419        }
420
421        Ok(rgba)
422    }
423}
424
425#[derive(Clone, Copy, Debug, PartialEq, Eq)]
426pub enum IconDisplay {
427    OneFrame,
428    TwoFrames,
429    ThreeFrames,
430    UNKNOWNFrames,
431}
432
433/// TitleFrame
434///
435/// The `TitleFrame` contains the Title of the game save file, as well as other info on
436/// how many frames are in the image, as well as block number and the icon palette.
437#[derive(Clone, Copy, Debug, DekuRead, DekuWrite, PartialEq, Eq)]
438#[deku(endian = "little")]
439pub struct TitleFrame {
440    pub id: [u8; 2],
441    pub display: u8,
442    pub block_num: u8,
443    pub title: [u8; 64],
444    pub reserved: [u8; 28],
445    pub icon_palette: [u16; 16],
446}
447
448impl TitleFrame {
449    /// Decode the Title from Shift-JIS into ASCII
450    pub fn decode_title(self) -> Result<String, MCError> {
451        // Shift JIS decode the Title
452        let mut s = String::new();
453
454        let mut p = 0;
455        loop {
456            match self.title[p] {
457                // TODO: This does not match punctuation marks [0x81, 0x43..0x97]
458                0x81 => {
459                    if self.title[p + 1] == 0x40 {
460                        s.push(' ');
461                    }
462                }
463                0x82 => {
464                    if (self.title[p + 1] >= 0x4f && self.title[p + 1] <= 0x58)
465                        || (self.title[p + 1] >= 0x60 && self.title[p + 1] <= 0x79)
466                    {
467                        // Translate 0..9 and A..Z
468                        s.push((self.title[p + 1] - 0x1f) as char);
469                    } else if self.title[p + 1] >= 0x81 && self.title[p + 1] <= 0x9a {
470                        // Translate a..z
471                        s.push((self.title[p + 1] - 0x20) as char);
472                    }
473                }
474                0x00 => break,
475                _ => (),
476            }
477            p += 2;
478            if p >= self.title.len() {
479                break;
480            }
481        }
482
483        Ok(s)
484    }
485
486    fn get_icon_display(&self) -> IconDisplay {
487        match self.display {
488            0x11 => IconDisplay::OneFrame,
489            0x12 => IconDisplay::TwoFrames,
490            0x13 => IconDisplay::ThreeFrames,
491            _ => IconDisplay::UNKNOWNFrames,
492        }
493    }
494}
495
496impl fmt::Display for TitleFrame {
497    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
498        let name = match self.decode_title() {
499            Ok(s) => s,
500            Err(_) => "Unknown".to_string(),
501        };
502        write!(
503            f,
504            "\n Filename: {}\n Icon: {:?}\n Block Number: {}",
505            name,
506            self.get_icon_display(),
507            self.block_num
508        )
509    }
510}
511
512/// InfoBlock
513///
514/// The `InfoBlock` is the first block in the memory card and contains the directory info
515/// for the locations of all the data / save file blocks, as well as any broken frame info.
516#[derive(Clone, Debug, PartialEq, Eq)]
517pub struct InfoBlock {
518    /// The header info that identifies this as PSX/PS1 memory card data.
519    pub header: Header,
520
521    /// The directory `Frame`s that detail the save file info and `Block` locations. There are
522    /// 15 `dir_frames`.
523    pub dir_frames: Vec<DirectoryFrame>,
524
525    /// The broken frames identify bad `Frame`s in the memory card. There are 20 `broken_frames`.
526    pub broken_frames: Vec<BrokenFrame>,
527
528    unused_frames: Vec<Frame>,
529    wr_test_frame: Header,
530}
531
532impl InfoBlock {
533    /// Open and parse the first block of the memory card.
534    pub fn open(b: Block) -> Result<Self, MCError> {
535        // Validate and load header
536        validate_checksum(&b.data)?;
537        let (_, header) = Header::from_bytes((&b.data, 0))?;
538
539        // Read directory frames
540        let dir_frames = DirectoryFrame::load(&b.data[FRAME..], 15)?;
541
542        // Read broken frames
543        let mut offset = (dir_frames.len() * FRAME) + FRAME;
544        let broken_frames = BrokenFrame::load(&b.data[offset..], 20)?;
545
546        offset += broken_frames.len() * FRAME;
547        let unused_frames = Frame::load(&b.data[offset..], 27)?;
548
549        offset += unused_frames.len() * FRAME;
550        validate_checksum(&b.data[offset..])?;
551        let (_, wr_test_frame) = Header::from_bytes((&b.data[offset..], 0))?;
552
553        Ok(InfoBlock {
554            header,
555            dir_frames,
556            broken_frames,
557            unused_frames,
558            wr_test_frame,
559        })
560    }
561
562    /// Write the contents of the `InfoBlock` to `out`.
563    pub fn write<T: std::io::Write>(&self, out: &mut T) -> Result<(), MCError> {
564        let mut h = self.header.to_bytes()?;
565        out.write_all(update_checksum(&mut h)?)?;
566
567        for df in &self.dir_frames {
568            let mut d = df.to_bytes()?;
569            out.write_all(update_checksum(&mut d)?)?;
570        }
571
572        for bf in &self.broken_frames {
573            let mut b = bf.to_bytes()?;
574            out.write_all(update_checksum(&mut b)?)?;
575        }
576
577        for uf in &self.unused_frames {
578            let mut f = uf.to_bytes()?;
579            out.write_all(update_checksum(&mut f)?)?;
580        }
581
582        let mut wrt = self.wr_test_frame.to_bytes()?;
583        out.write_all(update_checksum(&mut wrt)?)?;
584
585        Ok(())
586    }
587}
588
589/// #MemCard
590///
591/// The entire contents of the memory card are loaded into a `MemCard` struct. From here
592/// the data can be manipulated and written back out.
593#[derive(Clone, Debug, PartialEq, Eq)]
594pub struct MemCard {
595    /// The initial block of data on the memory card.
596    pub info: InfoBlock,
597
598    /// The save data blocks on the memory card.
599    pub data: Vec<DataBlock>,
600}
601
602impl MemCard {
603    /// Open and parse the memory card file from a filename. Load the data into a `MemCard`
604    /// structure.
605    pub fn open(filename: &str) -> Result<Self, MCError> {
606        let mut file = File::open(&filename)?;
607
608        // Load Info Block
609        let mut block0 = Block { data: [0u8; BLOCK] };
610        file.read_exact(&mut block0.data)?;
611        let info = InfoBlock::open(block0)?;
612
613        // Read Data Blocks
614        let mut blocks = Vec::<Block>::new();
615        loop {
616            let mut block = Block { data: [0u8; BLOCK] };
617            file.read_exact(&mut block.data)?;
618            blocks.push(block);
619            if blocks.len() == 15 {
620                break;
621            }
622        }
623
624        // Load Data Blocks
625        let data = DataBlock::load_all_data_blocks(&blocks)?;
626
627        Ok(MemCard { info, data })
628    }
629
630    /// Write out the `MemCard` data to a file.
631    pub fn write(&self, filename: &str) -> Result<(), MCError> {
632        let mut file = File::create(&filename)?;
633
634        self.info.write(&mut file)?;
635        for d in &self.data {
636            d.write(&mut file)?;
637        }
638
639        Ok(())
640    }
641
642    /// Search for a game save block that matches the `search` term. The search is case
643    /// insensitive.
644    pub fn find_game(&self, search: &str) -> Result<Vec<DataBlock>, MCError> {
645        let mut found = Vec::<DataBlock>::new();
646        let mut needle = String::from(search);
647        needle.make_ascii_lowercase();
648
649        // Find names that match in the data blocks
650        for info in &self.data {
651            let mut haystack = info.title_frame.decode_title()?;
652            haystack.make_ascii_lowercase();
653
654            if haystack.contains(&needle) {
655                found.push(info.clone());
656            }
657        }
658
659        Ok(found)
660    }
661}
662
663/// Calculate the `Frame` checksum.
664pub fn calc_checksum(d: &[u8]) -> u8 {
665    let mut c = 0;
666    for i in d.iter().take(FRAME - 1) {
667        c ^= *i;
668    }
669    c
670}
671
672/// Calculate the `Frame` checksum and validate that it matches the expected value.
673pub fn validate_checksum(d: &[u8]) -> Result<(), MCError> {
674    let c = calc_checksum(d);
675    if c != d[FRAME - 1] {
676        return Err(MCError::BadChecksum);
677    }
678
679    Ok(())
680}
681
682/// Update the `Frame` checksum after making edits.
683pub fn update_checksum(d: &mut [u8]) -> Result<&[u8], MCError> {
684    let c = calc_checksum(d);
685    d[FRAME - 1] = c;
686
687    validate_checksum(d)?;
688
689    Ok(d)
690}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695
696    #[test]
697    fn memcard_open() {
698        let _ = MemCard::open("epsxe000.mcr").unwrap();
699
700        /*
701        // Export images
702        for d in m.data {
703            d.export_all_images().unwrap();
704        }
705        */
706    }
707
708    #[test]
709    fn memcard_write() {
710        let m = MemCard::open("epsxe000.mcr").unwrap();
711
712        let w = m.find_game("WILD").unwrap();
713        for i in w {
714            println!("{}", i.title_frame);
715        }
716
717        m.write("test.mcr").unwrap();
718    }
719
720    #[test]
721    fn memcard_modify() {
722        let mut a = MemCard::open("epsxe000.mcr").unwrap();
723        a.info.header.id = [0x11, 0x22];
724        a.write("test.mcr").unwrap();
725
726        let mut b = MemCard::open("test.mcr").unwrap();
727        b.info.dir_frames[0].filesize = 4000000;
728        b.write("test.mcr").unwrap();
729
730        let mut c = MemCard::open("test.mcr").unwrap();
731        c.info.broken_frames[0].broken_frame = 12345;
732        c.write("test.mcr").unwrap();
733    }
734}