prtgn_encoding/
lib.rs

1//! # Overview
2//!
3//! `PRTGN_encoding` (listed as `prtgn_encoding` for Cargo.TOML) is a library used by programs compatible with PRTGN files to encode and decode them.
4//!
5//! Any file can be encoded with PRTGN, though for user convenience it is highly recommended to use the .prtgn extension. What is being written isn't a text file, simply add the original file extension to the end. Such as .prtgn_wav does.
6//!
7//! Going along with that, anything can be encoded with PRTGN. As long as what is given to the `write` function as a string.
8//!     An example of this is used by PRTGN for the wav file. [wav_converter.rs | PRTGN version 0.3.1, added in version 0.3.0](https://github.com/PRTGN-Development-Team/.prtgn/blob/83d6a200cdf14e82b84684480198a63ae40c63da/src/command/prtgn_wav/wav_converter.rs).
9//!     `wav_converter.rs` takes a wav file, reads its data, converts the data buffer to a string, and then writes it to a `prtgn_wav` file. The opposite is done for playing the wav file.
10//!
11//! Simply add PRTGN_encoding to your Cargo.TOML and add the following to your file to access commands depending on what you need!
12//!
13//! ## Read Only
14//! ```Rust
15//! use prtgn_encoding::read;
16//! ```
17//!
18//! ## Write Only
19//! ```Rust
20//! use prtgn_encoding::write;
21//! ```
22//!
23//! ## Read and Write
24//! ```Rust
25//! use prtgn_encoding::{read, write};
26//! ```
27//!
28//!
29//! ---
30//! ---
31//!
32//!
33//! # Example File Usage
34//!
35//!
36//! ## Basic Write Example
37//! ```commandline
38//! cargo run --example basic_write
39//! ```
40//!
41//! ## Basic Read Example
42//!
43//! **Run the write example first in order to have a correctly encoded PRTGN file with the right name**
44//!
45//! ```commandline
46//! cargo run --example basic_read
47//! ```
48//!
49//! ## Read Write Combo Example
50//! ```commandline
51//! cargo run --example read_write
52//! ```
53//!
54//!
55//! # Development Note
56//!
57//! To test documentation, run the following.
58//! ```commandline
59//! cargo doc --open
60//! ```
61
62use std::fs::File;
63use std::io::prelude::*;
64use std::io::Result;
65use std::io::{Error, ErrorKind};
66use std::io::SeekFrom;
67use std::string::FromUtf8Error;
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn read_write_test() {
75        println!("Hello from the PRTGN Development Team");
76
77        let text = "Hello from PRTGN Encoding!".to_string();
78        let og_txt = text.clone();
79        write("test.prtgn".to_string(), text).unwrap();
80
81        let decoded = read("test.prtgn".to_string()).unwrap().to_string();
82
83        println!("Original : {:?}", og_txt);
84
85        println!("Decoded : {:?}", decoded);
86        assert_eq!(decoded, og_txt);
87
88        println!("Test passed!");
89
90        std::fs::remove_file("test.prtgn").unwrap();
91    }
92}
93
94const XOR_KEY: u8 = 0x66;
95const FILE_HEADER: &[u8] = b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team | Version 0.2.0 | \x66 ";
96
97
98    /// Writes to a file with PRTGN encoding.
99    ///
100    /// # Examples
101    ///
102    /// ```Rust
103    ///     let filename = "test.prtgn".to_string();
104    ///
105    ///     let text = "Hello world! This is some example text.".to_string();
106    ///
107    ///     write(filename, text).unwrap();
108    /// ```
109    pub fn write(filename: String, text: String) -> Result<()> {
110        {
111            let mut file = File::create(filename)?;
112
113            file.write_all(FILE_HEADER)?;
114
115
116            let encoded_bytes: Vec<u8> = text.as_bytes().iter().map(|byte| byte ^ XOR_KEY).collect();
117
118
119            file.write_all(&encoded_bytes)?;
120        }
121
122
123        Ok(())
124    }
125
126    /// Reads a PRTGN encoded file.
127    ///
128    /// # Examples
129    ///
130    /// ```Rust
131    ///     let filename = "test.prtgn".to_string();
132    ///
133    ///     let read_text = read(filename).unwrap().to_string();
134    ///
135    ///     println!("{}", read_text);
136    /// ```
137    ///
138    /// If the file is using an older encoding standard it is automatically updated to the newest version.
139    pub fn read(filename: String) -> Result<String> {
140        let mut file = File::open(filename.clone())?;
141
142        let mut header_buffer = vec![0u8; FILE_HEADER.len()];
143        file.read_exact(&mut header_buffer)?;
144
145        let older_version = if header_buffer.starts_with(FILE_HEADER) {
146            "newest"
147        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team \x66 ") {
148            file.seek(SeekFrom::Start(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team \x66 ".len() as u64))?;
149            "0.1.3"
150        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00 ") {
151            file.seek(SeekFrom::Start(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00 ".len() as u64))?;
152            "0.1.2"
153        } else if header_buffer.starts_with(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00") {
154            file.seek(SeekFrom::Start(b"Encoded with PRTGN | https://github.com/PRTGN-Development-Team\x01\xFF\x00".len() as u64))?;
155            "0.1.1"
156        } else {
157            return Err(Error::new(ErrorKind::InvalidData, "Not a valid PRTGN encoded file."));
158        };
159
160        if older_version != "newest" {
161
162            println!("The version of encoding is outdated. Automatically rewriting the file after read.");
163            println!("---------------------------------------------");
164            print!("");
165
166        };
167
168        let mut encoded_buffer = Vec::new();
169        file.read_to_end(&mut encoded_buffer)?;
170
171
172        let decoded_byte: Vec<u8> = if older_version == "newest" {
173            encoded_buffer.iter().map(|byte| byte ^ XOR_KEY).collect()
174        } else if older_version == "0.1.3" {
175            encoded_buffer.iter().map(|byte| byte ^ 0x66).collect()
176        } else if older_version == "0.1.2" {
177            encoded_buffer.iter().map(|byte| byte ^ 0xA3).collect()
178        } else if older_version == "0.1.1" {
179            encoded_buffer.iter().map(|byte| byte ^ 0xA3).collect()
180        } else {
181            unreachable!()
182        };
183
184        write(filename, String::from_utf8(decoded_byte.clone()).unwrap())?;
185
186        String::from_utf8(decoded_byte).map_err(|e: FromUtf8Error| Error::new(ErrorKind::InvalidData, e.to_string()))
187    }