use std::{
fs::File,
io::{Read, Write},
num::Wrapping,
};
pub mod mapping;
use mapping::*;
#[doc(hidden)]
pub mod attach;
pub use attach::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ClasspadChar {
SingleByte(u8),
TwoByte(u8, u8),
}
impl ClasspadChar {
pub fn from_classpad_char(code: usize) -> Option<Self> {
if code < 32 {
println!("invalid character code {}", code);
return None;
}
if (32..=126).contains(&code) {
Some(Self::SingleByte(code as u8))
} else if (127..=256).contains(&code) {
println!("invalid character code {}", code);
None
} else if (257..=486).contains(&code) {
Some(Self::TwoByte(0xEC, (code - 256) as u8))
} else if (513..=766).contains(&code) {
Some(Self::TwoByte(0xED, (code - 512) as u8))
} else if (769..=943).contains(&code) {
Some(Self::TwoByte(0xEE, (code - 768) as u8))
} else {
println!("invalid character code {}", code);
None
}
}
pub fn to_classpad_char(&self) -> usize {
match self {
Self::SingleByte(v) => *v as usize,
Self::TwoByte(a, b) => match a {
0xEC => *b as usize + 256,
0xED => *b as usize + 512,
0xEE => *b as usize + 768,
_ => panic!("invalid representation"),
},
}
}
pub fn from_unicode_char(ch: char) -> Option<Self> {
if (32..=126).contains(&(ch as u32)) {
return Some(Self::SingleByte(ch as u8));
}
if let Some(classpad_char_code) = UNICODE_MAP.get_by_second(&ch) {
return Self::from_classpad_char(*classpad_char_code);
}
None
}
pub fn to_unicode_char(&self) -> Option<char> {
match self {
Self::SingleByte(ch) => Some(*ch as char),
Self::TwoByte(_, _) => UNICODE_MAP.get_by_first(&self.to_classpad_char()).copied(),
}
}
}
impl PartialOrd for ClasspadChar {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.to_classpad_char().partial_cmp(&other.to_classpad_char())
}
}
impl Ord for ClasspadChar {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
#[repr(transparent)]
pub struct ClasspadString(Vec<ClasspadChar>);
impl ClasspadString {
pub(crate) fn from_xcp_data(bytes: &[u8]) -> Self {
let mut chars: Vec<ClasspadChar> = vec![];
let mut first_byte: Option<u8> = None;
for byte in bytes {
if let Some(first_byte_content) = first_byte {
chars.push(ClasspadChar::TwoByte(first_byte_content, *byte));
first_byte = None;
continue;
}
if *byte == 0xEC_u8 {
first_byte = Some(*byte);
continue;
}
if *byte == 0xED_u8 {
first_byte = Some(*byte);
continue;
}
if *byte == 0xEE_u8 {
first_byte = Some(*byte);
continue;
}
chars.push(ClasspadChar::SingleByte(*byte));
}
Self(chars)
}
pub fn from_classpad_chars(codes: &[usize]) -> Self {
let a: Vec<ClasspadChar> = codes
.iter()
.filter_map(|code| ClasspadChar::from_classpad_char(*code))
.collect();
Self(a)
}
pub(crate) fn to_bytes_vec(&self) -> Vec<u8> {
let mut v: Vec<u8> = vec![];
for char in &self.0 {
match char {
ClasspadChar::SingleByte(c) => v.push(*c),
ClasspadChar::TwoByte(a, b) => {
v.push(*a);
v.push(*b);
}
}
}
v
}
pub fn to_unicode_str(&self) -> String {
self.0.iter().filter_map(|c| c.to_unicode_char()).collect()
}
}
impl std::str::FromStr for ClasspadString {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut c: Vec<ClasspadChar> = vec![];
for char in s.replace("\r\n", "\r").chars() {
if let Some(ch) = ClasspadChar::from_unicode_char(char) {
c.push(ch);
}
}
Ok(Self(c))
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum XCPFileType {
Text,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct XCPFile {
pub file_type: XCPFileType,
pub data: ClasspadString,
pub folder_name: String,
pub var_name: String,
}
impl XCPFile {
pub fn write_to_vec(&self) -> Vec<u8> {
let mut content: Vec<u8> = vec![];
let mut checksum: Wrapping<u8> = Wrapping(0x00); fn append_bytes(content: &mut Vec<u8>, bytes: Vec<u8>) -> Wrapping<u8> {
content.append(&mut bytes.clone());
bytes
.iter()
.map(|b| Wrapping(*b))
.sum::<Wrapping<u8>>()
}
fn push_byte(content: &mut Vec<u8>, byte: u8) -> Wrapping<u8> {
content.push(byte);
Wrapping(byte)
}
fn push_ascii_hex_num(content: &mut Vec<u8>, num: u8) -> Wrapping<u8> {
let s = format!("{:02x}", num);
content.append(&mut s.as_bytes().to_vec());
Wrapping(num)
}
let vcp_bytes = "VCP.XDATA".as_bytes().to_vec();
checksum -= append_bytes(&mut content, vcp_bytes);
checksum -= push_byte(&mut content, 0x00);
content.append(&mut "5f4d4353".as_bytes().to_vec());
checksum -= Wrapping(0x5f_u8) + Wrapping(0x4d_u8) + Wrapping(0x43_u8) + Wrapping(0x53_u8);
checksum -= push_ascii_hex_num(&mut content, (self.folder_name.len() + 1) as u8);
checksum -= append_bytes(&mut content, self.folder_name.as_bytes().to_vec());
checksum -= push_byte(&mut content, 0x00);
checksum -= push_ascii_hex_num(&mut content, (self.var_name.len() + 1) as u8);
checksum -= append_bytes(&mut content, self.var_name.as_bytes().to_vec());
checksum -= push_byte(&mut content, 0x00);
content.append(&mut "00000031".as_bytes().to_vec());
checksum -= Wrapping(0x31_u8);
let mut folname2 = self.folder_name.as_bytes().to_vec();
let diff = 16 - folname2.len();
folname2.extend(std::iter::repeat(0xff).take(diff));
checksum -= append_bytes(&mut content, folname2);
let mut varname2 = self.var_name.as_bytes().to_vec();
let diff = 16 - varname2.len();
varname2.extend(std::iter::repeat(0xff).take(diff));
checksum -= append_bytes(&mut content, varname2);
println!("checksum after varname2 {}", checksum);
let data = self.data.to_bytes_vec();
let len1_no_pad = 4 + 9 + data.len() + 2;
let padding_len =
(Wrapping(0_i32) - Wrapping(15_i32 + data.len() as i32)) & Wrapping(0x03_i32);
let len1 = len1_no_pad + padding_len.0 as usize;
let len2 = data.len() + 3;
let len1_bytes = (len1 as u32).to_be_bytes(); checksum -= append_bytes(&mut content, len1_bytes.to_vec());
println!("len1 checksum {}", checksum);
let guq_bytes = "GUQ".as_bytes().to_vec();
checksum -= append_bytes(&mut content, guq_bytes);
let guq_padding_bytes = std::iter::repeat(0xff).take(10);
checksum -= append_bytes(&mut content, guq_padding_bytes.collect());
println!("block guq checksum {}", checksum);
let len1asc = format!("{:X}", len1).to_lowercase();
let len1asc_bytes = format!("{:0>8}", len1asc).as_bytes().to_vec();
content.append(&mut len1asc_bytes.clone());
checksum -= Wrapping(len1 as u8); let len2_bytes = (len2 as u32).to_le_bytes();
checksum -= append_bytes(&mut content, len2_bytes.to_vec());
content.extend(std::iter::repeat(0x00).take(9));
println!("after block0 {}", checksum);
checksum -= append_bytes(&mut content, data);
checksum -= push_byte(&mut content, 0x00);
checksum -= push_byte(&mut content, 0xff);
checksum -= append_bytes(
&mut content,
std::iter::repeat(0x00)
.take(padding_len.0 as usize)
.collect(),
);
let checksum_string = format!("{:02x}", checksum);
content.append(&mut checksum_string.as_bytes().to_vec());
content
}
pub fn write_to_file(&self, file: &mut File) {
let contents = self.write_to_vec();
file.write_all(&contents).unwrap();
}
pub fn read_from_vec(content: Vec<u8>) -> Self {
fn ascii_assert(input: &[u8], s: &str) {
let read: String = input.iter().map(|by| *by as char).collect();
assert_eq!(read, s);
}
assert_eq!(&content[0..9], "VCP.XDATA".as_bytes());
assert_eq!(&content[9], &(0x00_u8.to_le_bytes()[0]));
ascii_assert(&content[10..18], "5f4d4353");
let folder_name_digits: String = content[18..20]
.iter()
.map(|by| by.to_ascii_lowercase() as char)
.collect();
let folder_name_len: usize = folder_name_digits.as_str().parse::<usize>().unwrap() - 1;
let mut active_byte = 20; let folder_name: String = content[active_byte..(active_byte + folder_name_len)]
.iter()
.map(|by| by.to_ascii_lowercase() as char)
.collect();
active_byte += folder_name_len;
active_byte += 1; let var_name_digits: String = content[active_byte..(active_byte + 2)]
.iter()
.map(|by| by.to_ascii_lowercase() as char)
.collect();
let var_name_len: usize = var_name_digits.as_str().parse::<usize>().unwrap() - 1;
active_byte += 2;
let var_name: String = content[active_byte..(active_byte + var_name_len)]
.iter()
.map(|by| by.to_ascii_lowercase() as char)
.collect();
active_byte += var_name_len;
active_byte += 1; ascii_assert(&content[active_byte..(active_byte + 8)], "00000031");
active_byte += 8;
active_byte += 32;
let len1 = u32::from_be_bytes(content[active_byte..(active_byte + 4)].try_into().unwrap());
assert_eq!(len1 % 4, 0); active_byte += 4;
ascii_assert(&content[active_byte..(active_byte + 3)], "GUQ");
active_byte += 13; active_byte += 8;
let len2 = (u32::from_le_bytes(content[active_byte..(active_byte + 4)].try_into().unwrap())
- 3) as usize;
active_byte += 4;
active_byte += 9;
let data = ClasspadString::from_xcp_data(&content[active_byte..(active_byte + len2)]);
Self {
file_type: XCPFileType::Text,
data,
folder_name,
var_name,
}
}
pub fn read_from_file(file: &mut File) -> Self {
let mut content: Vec<u8> = vec![];
let read_result = file.read_to_end(&mut content);
let Ok(_) = read_result else {
panic!("failed to read file.");
};
Self::read_from_vec(content)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn read_xcp() {
let mut file = File::open("./src/v-(Program).xcp").unwrap();
let xcp = XCPFile::read_from_file(&mut file);
let mut outfile = File::create("./src/v-output.xcp").unwrap();
xcp.write_to_file(&mut outfile);
}
#[test]
fn generate_xcp() {
let file = XCPFile {
file_type: XCPFileType::Text,
data: ClasspadString::from_str("abc").unwrap(),
folder_name: "main".to_string(),
var_name: "testvar".to_string(),
};
let mut opened_file = File::create("./src/abc.xcp").unwrap();
file.write_to_file(&mut opened_file);
}
#[test]
fn write_integral() {
let mut s = ClasspadString::from_str("").unwrap();
s.attach_integral().attach_str("x");
let file = XCPFile {
file_type: XCPFileType::Text,
data: s,
folder_name: "main".to_string(),
var_name: "integ".to_string(),
};
let mut opened_file = File::create("./src/integ.xcp").unwrap();
file.write_to_file(&mut opened_file);
}
}