diff --git a/pkstrings/src/lib.rs b/pkstrings/src/lib.rs index 0d0906e..8d69eb8 100644 --- a/pkstrings/src/lib.rs +++ b/pkstrings/src/lib.rs @@ -13,218 +13,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::convert::TryFrom; -use std::fmt; -use std::ops::Deref; - -const fn strtohex(chr: &char) -> Option { - Some(match chr { - cap @ 'A'..='Z' => 0x80 - ('A' as u8) + (*cap as u8), - '(' => 0x9a, - ')' => 0x9b, - ':' => 0x9c, - ';' => 0x9d, - '[' => 0x9e, - ']' => 0x9f, - low @ 'a'..='z' => 0xa0 - ('a' as u8) + (*low as u8), - '\'' => 0xe0, - '-' => 0xe3, - '?' => 0xe6, - '!' => 0xe7, - '.' => 0xe8, - '▷' => 0xec, - '▶' => 0xed, - '▼' => 0xee, - '♂' => 0xef, - '×' => 0xf1, - // TODO: Pattern currently unreachable. Figure something out. - // In the Japanese games (as can be seen below), 0xF2 is distinguishable from 0xE8, with - // the former meant as a decimal point while the latter is punctuation. Presumably this - // intention was largely inherited when the English games were made, as most of the game's - // script uses 0xE8 exclusively; however, 0xF2 appears in the character table for user - // input, meaning it may appear in user-input names (and, conversely, 0xE8 never should). - // Source: https://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_I - // '.' => 0xf2, - '/' => 0xf3, - ',' => 0xf4, - '♀' => 0xf5, - num @ '0'..='9' => 0xf6 - ('0' as u8) + (*num as u8), - ' ' => 0x7f, - '@' => 0x50, - _ => return None, - }) -} - -const fn hextostr(hex: u8) -> Option { - Some(match hex { - cap @ 0x80..=0x99 => (('A' as u8) + (cap - 0x80)) as char, - 0x9a => '(', - 0x9b => ')', - 0x9c => ':', - 0x9d => ';', - 0x9e => '[', - 0x9f => ']', - low @ 0xa0..=0xb9 => (('a' as u8) + (low - 0xa0)) as char, - 0xe0 => '\'', - 0xe3 => '-', - 0xe6 => '?', - 0xe7 => '!', - 0xe8 => '.', - 0xec => '▷', - 0xed => '▶', - 0xee => '▼', - 0xef => '♂', - 0xf1 => '×', - 0xf2 => '.', - 0xf3 => '/', - 0xf4 => ',', - 0xf5 => '♀', - num @ 0xf6..=0xff => (('0' as u8) + (num - 0xf6)) as char, - 0x7f => ' ', - 0x50 => '@', - _ => return None, - }) -} - -#[derive(Debug, Eq, PartialEq)] -pub enum Error { - InvalidCharacter, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self { - Error::InvalidCharacter => write!(f, "Invalid character"), - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct PKString(String); - -impl PKString { - pub fn into_string(self) -> String { - self.0 - } -} - -impl Deref for PKString { - type Target = String; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl fmt::Display for PKString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("{}", &self.0)) - } -} - -impl TryFrom for PKString { - type Error = Error; - - fn try_from(ord: u8) -> Result { - let mut buf = String::with_capacity(1); - - match hextostr(ord) { - Some(chr) => buf.push(chr.clone()), - None => return Err(Error::InvalidCharacter), - } - - Ok(PKString(buf)) - } -} - -impl TryFrom<&[u8]> for PKString { - type Error = Error; - - fn try_from(data: &[u8]) -> Result { - let mut buf = String::with_capacity(data.len()); - let placeholder = '_'; - - for ord in data { - if let Some(chr) = hextostr(*ord) { - buf.push(chr.clone()); - } else { - buf.push(placeholder); - } - } - - Ok(PKString(buf)) - } -} - -impl Into> for PKString { - fn into(self) -> Vec { - let mut vec = Vec::with_capacity(self.len()); - - for chr in self.0.chars() { - if let Some(ord) = strtohex(&chr) { - vec.push(ord); - } else { - // TODO: Change this. - panic!(); - } - } - - vec - } -} - -impl TryFrom<&str> for PKString { - type Error = Error; - - fn try_from(data: &str) -> Result { - PKString::try_from(String::from(data)) - } -} - -impl TryFrom for PKString { - type Error = Error; - - fn try_from(data: String) -> Result { - for chr in data.chars() { - if strtohex(&chr).is_none() { - return Err(Error::InvalidCharacter); - } - } - - Ok(PKString(data)) - } -} +pub mod pkstring; #[cfg(test)] -mod tests { - use crate::PKString; - use std::convert::TryFrom; +mod tests; - #[test] - fn test_from_ord() { - let gary: &[u8] = &[0x86, 0x80, 0x91, 0x98]; - assert_eq!(PKString::try_from(gary), Ok(PKString(String::from("GARY")))); - - let party_nicks: &[u8] = &[ - 0x8a, 0x80, 0x83, 0x80, 0x81, 0x91, 0x80, 0x50, 0x50, 0x50, 0x50, 0x8d, 0x88, 0x83, - 0x8e, 0x8a, 0x88, 0x8d, 0x86, 0x50, 0x50, 0x50, 0x81, 0x8b, 0x80, 0x92, 0x93, 0x8e, - 0x88, 0x92, 0x84, 0x50, 0x50, 0x8e, 0x83, 0x83, 0x88, 0x92, 0x87, 0x50, 0x50, 0x50, - 0x50, 0x50, 0x8f, 0x88, 0x83, 0x86, 0x84, 0x98, 0x50, 0x50, 0x50, 0x50, 0x50, 0x82, - 0x87, 0x80, 0x91, 0x8c, 0x80, 0x8d, 0x83, 0x84, 0x91, 0x50, - ]; - let result = - String::from("KADABRA@@@@NIDOKING@@@BLASTOISE@@ODDISH@@@@@PIDGEY@@@@@CHARMANDER@"); - assert_eq!(PKString::try_from(party_nicks), Ok(PKString(result))); - } - - #[test] - fn test_from_chr() { - let gary: Vec = vec![0x86, 0x80, 0x91, 0x98]; - let result: Vec = PKString(String::from("GARY")).into(); - assert_eq!(result, gary); - - let nidoranf: Vec = vec![0x8d, 0x88, 0x83, 0x8e, 0x91, 0x80, 0x8d, 0xef]; - let result: Vec = PKString(String::from("NIDORAN♂")).into(); - assert_eq!(result, nidoranf); - } -} +pub use pkstring::PKString; diff --git a/pkstrings/src/pkstring.rs b/pkstrings/src/pkstring.rs new file mode 100644 index 0000000..82b6a48 --- /dev/null +++ b/pkstrings/src/pkstring.rs @@ -0,0 +1,208 @@ +// Copyright (C) 2021 "Maxime “pep” Buquet " +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::convert::TryFrom; +use std::fmt; +use std::ops::Deref; + +const fn strtohex(chr: &char) -> Option { + Some(match chr { + cap @ 'A'..='Z' => 0x80 - ('A' as u8) + (*cap as u8), + '(' => 0x9a, + ')' => 0x9b, + ':' => 0x9c, + ';' => 0x9d, + '[' => 0x9e, + ']' => 0x9f, + low @ 'a'..='z' => 0xa0 - ('a' as u8) + (*low as u8), + '\'' => 0xe0, + '-' => 0xe3, + '?' => 0xe6, + '!' => 0xe7, + '.' => 0xe8, + '▷' => 0xec, + '▶' => 0xed, + '▼' => 0xee, + '♂' => 0xef, + '×' => 0xf1, + // TODO: Pattern currently unreachable. Figure something out. + // In the Japanese games (as can be seen below), 0xF2 is distinguishable from 0xE8, with + // the former meant as a decimal point while the latter is punctuation. Presumably this + // intention was largely inherited when the English games were made, as most of the game's + // script uses 0xE8 exclusively; however, 0xF2 appears in the character table for user + // input, meaning it may appear in user-input names (and, conversely, 0xE8 never should). + // Source: https://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_I + // '.' => 0xf2, + '/' => 0xf3, + ',' => 0xf4, + '♀' => 0xf5, + num @ '0'..='9' => 0xf6 - ('0' as u8) + (*num as u8), + ' ' => 0x7f, + '@' => 0x50, + _ => return None, + }) +} + +const fn hextostr(hex: u8) -> Option { + Some(match hex { + cap @ 0x80..=0x99 => (('A' as u8) + (cap - 0x80)) as char, + 0x9a => '(', + 0x9b => ')', + 0x9c => ':', + 0x9d => ';', + 0x9e => '[', + 0x9f => ']', + low @ 0xa0..=0xb9 => (('a' as u8) + (low - 0xa0)) as char, + 0xe0 => '\'', + 0xe3 => '-', + 0xe6 => '?', + 0xe7 => '!', + 0xe8 => '.', + 0xec => '▷', + 0xed => '▶', + 0xee => '▼', + 0xef => '♂', + 0xf1 => '×', + 0xf2 => '.', + 0xf3 => '/', + 0xf4 => ',', + 0xf5 => '♀', + num @ 0xf6..=0xff => (('0' as u8) + (num - 0xf6)) as char, + 0x7f => ' ', + 0x50 => '@', + _ => return None, + }) +} + +#[derive(Debug, Eq, PartialEq)] +pub enum Error { + InvalidCharacter, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Error::InvalidCharacter => write!(f, "Invalid character"), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PKString(String); + +impl Deref for PKString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for PKString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}", &self.0)) + } +} + +impl TryFrom for PKString { + type Error = Error; + + fn try_from(ord: u8) -> Result { + let mut buf = String::with_capacity(1); + + match hextostr(ord) { + Some(chr) => buf.push(chr.clone()), + None => return Err(Error::InvalidCharacter), + } + + Ok(PKString(buf)) + } +} + +impl TryFrom<&[u8]> for PKString { + type Error = Error; + + fn try_from(data: &[u8]) -> Result { + let mut buf = String::with_capacity(data.len()); + + for ord in data { + match hextostr(*ord) { + Some(chr) => buf.push(chr.clone()), + None => return Err(Error::InvalidCharacter), + } + } + + Ok(PKString(buf)) + } +} + +impl TryFrom> for PKString { + type Error = Error; + + fn try_from(data: Vec) -> Result { + PKString::try_from(data.as_slice()) + } +} + +impl TryFrom<&str> for PKString { + type Error = Error; + + fn try_from(data: &str) -> Result { + PKString::try_from(String::from(data)) + } +} + +impl TryFrom for PKString { + type Error = Error; + + fn try_from(data: String) -> Result { + for chr in data.chars() { + if strtohex(&chr).is_none() { + return Err(Error::InvalidCharacter); + } + } + + Ok(PKString(data)) + } +} + +impl From for Vec { + fn from(pkstr: PKString) -> Vec { + let mut vec = Vec::with_capacity(pkstr.len()); + + for chr in pkstr.0.chars() { + if let Some(ord) = strtohex(&chr) { + vec.push(ord); + } else { + // TODO: Change this. + panic!(); + } + } + + vec + } +} + +impl From for String { + fn from(pkstr: PKString) -> String { + pkstr.0 + } +} + +// impl PKString { +// fn as_slice(&self) -> &[u8] { +// self.0.as_slice() +// } +// } diff --git a/pkstrings/src/tests.rs b/pkstrings/src/tests.rs new file mode 100644 index 0000000..4664808 --- /dev/null +++ b/pkstrings/src/tests.rs @@ -0,0 +1,120 @@ +// Copyright (C) 2021 "Maxime “pep” Buquet " +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::PKString; +use std::convert::TryFrom; + +const GARY_SLICE_U8: &[u8] = &[0x86, 0x80, 0x91, 0x98]; +const GARY_STR: &str = "GARY"; + +#[test] +fn test_try_from_u8() { + match PKString::try_from(0x80) { // 'A' + Ok(pkstr) => assert_eq!(pkstr, PKString::try_from("A").unwrap()), + Err(_) => panic!(), + } + + match PKString::try_from(0x79) { // Invalid + Ok(_) => panic!(), + Err(_) => (), + } +} + +#[test] +fn test_try_from_slice_u8() { + match PKString::try_from(GARY_SLICE_U8) { + Ok(pkstr) => assert_eq!(pkstr, PKString::try_from(GARY_STR).unwrap()), + Err(_) => panic!(), + } + + let invalid: &[u8] = &[ + 0x86, + 0x80, + 0x79, // Invalid + 0x98, + ]; + match PKString::try_from(invalid) { + Ok(_) => panic!(), + Err(_) => (), + } +} + +#[test] +fn test_try_from_vec_u8() { + let gary: Vec = GARY_SLICE_U8.to_vec(); + match PKString::try_from(gary) { + Ok(pkstr) => assert_eq!(pkstr, PKString::try_from(GARY_STR).unwrap()), + Err(_) => panic!(), + } + + let invalid: Vec = vec![ + 0x86, + 0x80, + 0x79, // Invalid + 0x98, + ]; + match PKString::try_from(invalid) { + Ok(_) => panic!(), + Err(_) => (), + } +} + +#[test] +fn test_from_ord() { + assert_eq!(PKString::try_from(GARY_SLICE_U8), Ok(PKString::try_from(GARY_STR).unwrap())); + + let party_nicks: &[u8] = &[ + 0x8a, 0x80, 0x83, 0x80, 0x81, 0x91, 0x80, 0x50, 0x50, 0x50, 0x50, 0x8d, 0x88, 0x83, + 0x8e, 0x8a, 0x88, 0x8d, 0x86, 0x50, 0x50, 0x50, 0x81, 0x8b, 0x80, 0x92, 0x93, 0x8e, + 0x88, 0x92, 0x84, 0x50, 0x50, 0x8e, 0x83, 0x83, 0x88, 0x92, 0x87, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x8f, 0x88, 0x83, 0x86, 0x84, 0x98, 0x50, 0x50, 0x50, 0x50, 0x50, 0x82, + 0x87, 0x80, 0x91, 0x8c, 0x80, 0x8d, 0x83, 0x84, 0x91, 0x50, + ]; + let result = "KADABRA@@@@NIDOKING@@@BLASTOISE@@ODDISH@@@@@PIDGEY@@@@@CHARMANDER@"; + assert_eq!(PKString::try_from(party_nicks), Ok(PKString::try_from(result).unwrap())); +} + +#[test] +fn test_from_chr() { + let result: Vec = PKString::try_from(GARY_STR).unwrap().into(); + assert_eq!(result, GARY_SLICE_U8); + + let nidoranf: Vec = vec![0x8d, 0x88, 0x83, 0x8e, 0x91, 0x80, 0x8d, 0xef]; + let result: Vec = PKString::try_from("NIDORAN♂").unwrap().into(); + assert_eq!(result, nidoranf); +} + +#[test] +fn test_into_vec_u8() { + let pkstr: PKString = PKString::try_from(GARY_SLICE_U8).unwrap(); + let res: Vec = pkstr.into(); + assert_eq!(res, GARY_SLICE_U8); +} + +#[test] +fn test_from_string() { + match PKString::try_from(String::from(GARY_STR)) { + Ok(pkstr) => assert_eq!(pkstr, PKString::try_from(GARY_SLICE_U8).unwrap()), + Err(_) => panic!(), + } +} + +// #[test] +// fn test_as_slice_u8() { +// let gary: Vec = vec![0x86, 0x80, 0x91, 0x98]; +// let pkstr: PKString = PKString::try_from(gary.clone()).unwrap(); +// let res: &[u8] = pkstr.as_slice(); +// assert_eq!(res, gary.as_slice()); +// }