pmd_code_table/
lib.rs

1//! A library permitting to decode the code_table.bin file, used in Pokémon Super Mystery Dungeon and Pokémon Rescue Team DX (and maybe Gates to Infinity).
2//!
3//! This file contain a list of unicode character that are used to represent placeholder in the game.
4
5use binread::{BinRead, BinReaderExt, FilePtr32, NullWideString};
6use pmd_sir0::{Sir0, Sir0Error};
7use std::{
8    collections::{BTreeMap, HashMap},
9    io::{self, Read, Seek, SeekFrom},
10};
11use thiserror::Error;
12
13mod code_to_text;
14pub use code_to_text::{CodeToText, CodeToTextError};
15
16mod text_to_code;
17pub use text_to_code::{TextToCode, TextToCodeError};
18
19/// Represent a single entry of a Unicode character <-> placeholder text pair.
20#[derive(BinRead, Debug)]
21#[br(little)]
22pub struct CodeTableEntryFile {
23    #[br(parse_with = FilePtr32::parse, try_map = |x: NullWideString| x.into_string_lossless())]
24    pub string: String,
25    pub value: u16,
26    pub flags: u16,
27    pub lenght: u16,
28    pub unk: u16,
29}
30
31/// Represent a complete [`code_table.bin`] file.
32#[derive(Default)]
33pub struct CodeTable {
34    entries: Vec<CodeTableEntryFile>,
35}
36
37/// An error that may happen while decoding a [`code_table.bin`] file with [`CodeTable`].
38#[derive(Error, Debug)]
39pub enum CodeTableDecodeError {
40    #[error("can't decode the Sir0 file")]
41    CantDecodeSir0(#[from] Sir0Error),
42    #[error("the sir0 container only have {0} pointer, but it should have at least 5 pointer")]
43    NotEnoughtPointer(usize),
44    #[error("The offset of the pointer n°{0} can't be obtained")]
45    CantGetOffsetForPointer(usize),
46    #[error("Can't read (maybe a part) of the Sir0 file. This may be caused by an invalid file")]
47    IOError(#[from] io::Error),
48    #[error("Can't decode/read an entry of the code_table.bin file")]
49    CantReadEntry(#[source] binread::Error),
50}
51
52//TODO: handle flags
53
54impl CodeTable {
55    /// Create a new [`CodeTable`] from a reader open on a ``code_table.bin``
56    pub fn new_from_file<F: Read + Seek>(file: F) -> Result<Self, CodeTableDecodeError> {
57        let mut result = Self::default();
58        let mut sir0 = Sir0::new(file)?;
59        if sir0.offsets_len() < 5 {
60            return Err(CodeTableDecodeError::NotEnoughtPointer(sir0.offsets_len()));
61        };
62
63        for pointer_id in 3..sir0.offsets_len() - 2 {
64            let pointer = *sir0.offsets_get(pointer_id).map_or_else(
65                || Err(CodeTableDecodeError::CantGetOffsetForPointer(pointer_id)),
66                Ok,
67            )?;
68            let sir0_file = sir0.get_file();
69            sir0_file.seek(SeekFrom::Start(pointer))?;
70            let entry: CodeTableEntryFile = sir0_file
71                .read_le()
72                .map_or_else(|err| Err(CodeTableDecodeError::CantReadEntry(err)), Ok)?;
73            result.entries.push(entry);
74        }
75
76        Ok(result)
77    }
78
79    /// Add the three 0xcf00, 0xcf02, 0xcfff code, missing from the standard [`code_table.bin`]
80    /// (control for rubi/overtext)
81    pub fn add_missing(&mut self) {
82        self.entries.push(CodeTableEntryFile {
83            string: "rubi:base".to_string(),
84            value: 0xcf00,
85            flags: 0,
86            lenght: 0,
87            unk: 0,
88        });
89        self.entries.push(CodeTableEntryFile {
90            string: "rubi:over".to_string(),
91            value: 0xcf02,
92            flags: 0,
93            lenght: 0,
94            unk: 0
95        });
96        self.entries.push(CodeTableEntryFile {
97            string: "rubi:end".to_string(),
98            value: 0xcfff,
99            flags: 0,
100            lenght: 0,
101            unk: 0
102        });
103    }
104
105    /// Generate a [`CodeToText`] object, allowing to transform an encoded text to a [`String`] with human readable placeholder.
106    pub fn generate_code_to_text(&self) -> CodeToText {
107        let mut code_to_text = BTreeMap::new();
108        for entry in self.entries.iter() {
109            code_to_text.insert(entry.value, entry);
110        };
111
112        CodeToText {
113            code_to_text
114        }
115    }
116
117    /// Generate a [`TextToCode`] object, allowing to transform a human-readable string (generated by a [`CodeToText`]) into a
118    /// binary text readable by the game (it first need to be put inside a message container (see the ``pmd_message`` crate) then
119    /// into a farc file (see ``pmd_farc`` crate). The ``pmdtranslate`` crate contain command line and a complete exemple for those case.
120    pub fn generate_text_to_code(
121        &self,
122    ) -> TextToCode {
123        let mut text_to_code = HashMap::new();
124        for entry in self.entries.iter() {
125            text_to_code.insert(&entry.string, entry);
126        }
127
128        TextToCode {
129            text_to_code
130        }
131    }
132
133    /// Return a list of all the entries in this file
134    pub fn entries(&self) -> &Vec<CodeTableEntryFile> {
135        &self.entries
136    }
137}