diff --git a/Cargo.toml b/Cargo.toml index 0acb50f..7c6f8d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,10 @@ edition = "2021" [dependencies] async-trait = "0.1.82" +byteorder = "1.5.0" +cbc = "0.1.2" chrono = "0.4.38" +des = "0.8.1" kbinxml = { git = "https://github.com/mbilker/kbinxml-rs.git", version = "3.1.1" } lazy_static = "1.5.0" lz77 = "0.1.0" diff --git a/src/handlers/boot/get_services_handler.rs b/src/handlers/boot/get_services_handler.rs index 0f180b8..4149c8a 100644 --- a/src/handlers/boot/get_services_handler.rs +++ b/src/handlers/boot/get_services_handler.rs @@ -176,6 +176,69 @@ impl GetServicesHandler { } services } + + fn add_pan_services<'a>( + mut services: Vec>, + common_url: &'a str, + ) -> Vec> { + let m39_services = vec![ + "local", "local2", "lobby", "slocal", "slocal2", "sglocal", "sglocal2", "lab", + "globby", "slobby", "sglobby", + ]; + + for service in m39_services { + services.push( + XmlEvent::start_element("item") + .attr("name", service) + .attr("url", &common_url) + .into(), + ); + services.push(XmlEvent::end_element().into()); + } + services + } + + fn add_ujk_services<'a>( + mut services: Vec>, + common_url: &'a str, + ) -> Vec> { + let m39_services = vec![ + "local", "local2", "lobby", "slocal", "slocal2", "sglocal", "sglocal2", "lab", + "globby", "slobby", "sglobby", + ]; + + for service in m39_services { + services.push( + XmlEvent::start_element("item") + .attr("name", service) + .attr("url", &common_url) + .into(), + ); + services.push(XmlEvent::end_element().into()); + } + services + } + + fn add_l44_services<'a>( + mut services: Vec>, + common_url: &'a str, + ) -> Vec> { + let m39_services = vec![ + "local", "local2", "lobby", "slocal", "slocal2", "sglocal", "sglocal2", "lab", + "globby", "slobby", "sglobby", + ]; + + for service in m39_services { + services.push( + XmlEvent::start_element("item") + .attr("name", service) + .attr("url", &common_url) + .into(), + ); + services.push(XmlEvent::end_element().into()); + } + services + } } #[async_trait] @@ -201,6 +264,9 @@ impl Handler for GetServicesHandler { Some("MDX") => GetServicesHandler::add_mdx_services(services, &common_url), Some("M39") => GetServicesHandler::add_m39_services(services, &common_url), Some("M32") => GetServicesHandler::add_m32_services(services, &common_url), + Some("PAN") => GetServicesHandler::add_pan_services(services, &common_url), + Some("UJK") => GetServicesHandler::add_ujk_services(services, &common_url), + Some("L44") => GetServicesHandler::add_l44_services(services, &common_url), _ => services, }; diff --git a/src/handlers/common/card/inquire_card_managment_handler.rs b/src/handlers/common/card/inquire_card_managment_handler.rs new file mode 100644 index 0000000..97b7dc1 --- /dev/null +++ b/src/handlers/common/card/inquire_card_managment_handler.rs @@ -0,0 +1,98 @@ +use crate::{handlers::handler::Handler, utils::card_convert::CardConvert}; +use std::io::{BufReader, Cursor}; +use xml::{ + reader::{EventReader, XmlEvent}, + EmitterConfig, +}; + +pub struct InquireCardManagment { + card_id: String, +} + +pub struct InquireCardManagmentHandler { + module: String, + method: String, +} + +impl InquireCardManagmentHandler { + pub fn new() -> Self { + InquireCardManagmentHandler { + module: "cardmng".to_string(), + method: "inquire".to_string(), + } + } + + fn parse_inquire_card_managment(xml_data: &str) -> Option { + let reader = EventReader::new(BufReader::new(xml_data.as_bytes())); + let mut inquire_card_managment = InquireCardManagment { + card_id: String::new(), + }; + + for event in reader { + match event { + Ok(XmlEvent::StartElement { + name, attributes, .. + }) => { + if name.local_name == "cardmng" { + for attr in attributes { + match attr.name.local_name.as_str() { + "cardid" => inquire_card_managment.card_id = attr.value.clone(), + _ => {} + } + } + } + } + Err(e) => { + print!("Error: {}", e); + return None; + } + _ => {} + } + } + + Some(inquire_card_managment) + } +} + +#[async_trait] +impl Handler for InquireCardManagmentHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, body: String) -> String { + let card_managment = Self::parse_inquire_card_managment(body.as_str()).unwrap(); + + let mut buffer = Cursor::new(vec![]); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer + .write(xml::writer::XmlEvent::start_element("response")) + .unwrap(); + + if card_managment.card_id == "" { + writer.write(xml::writer::XmlEvent::start_element("cardmng") + .attr("status", "111")).unwrap(); + + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + return String::from_utf8(buffer.into_inner()).unwrap() + } + + let konami_id = CardConvert::to_konami_id(card_managment.card_id); + + if konami_id.is_some() { + println!("Konami id: {}", konami_id.unwrap()); + } + + writer.write(xml::writer::XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/card/mod.rs b/src/handlers/common/card/mod.rs new file mode 100644 index 0000000..f18170d --- /dev/null +++ b/src/handlers/common/card/mod.rs @@ -0,0 +1 @@ +pub mod inquire_card_managment_handler; \ No newline at end of file diff --git a/src/handlers/common/mod.rs b/src/handlers/common/mod.rs index 1681a77..a9d4845 100644 --- a/src/handlers/common/mod.rs +++ b/src/handlers/common/mod.rs @@ -3,4 +3,5 @@ pub mod alive_pcb_tracker_handler; pub mod get_messages_handler; pub mod put_pcb_event_handler; -pub mod ota; \ No newline at end of file +pub mod ota; +pub mod card; \ No newline at end of file diff --git a/src/handlers/common/put_pcb_event_handler.rs b/src/handlers/common/put_pcb_event_handler.rs index abd52d5..068ee13 100644 --- a/src/handlers/common/put_pcb_event_handler.rs +++ b/src/handlers/common/put_pcb_event_handler.rs @@ -4,8 +4,6 @@ use xml::{reader::{EventReader, XmlEvent}, EmitterConfig}; use crate::handlers::handler::Handler; - - struct PcbEvent { tag: String, src_id: String, diff --git a/src/main.rs b/src/main.rs index 366fb7d..0c760aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,11 @@ mod parsers; mod generators; mod handlers; mod routes; +mod utils; use handlers::boot::get_services_handler::GetServicesHandler; use handlers::common::alive_pcb_tracker_handler::AlivePcbTrackerHandler; +use handlers::common::card::inquire_card_managment_handler::InquireCardManagmentHandler; use handlers::common::get_messages_handler::GetMessageHandler; use handlers::common::ota::list_package_handler::ListPackageHandler; use handlers::common::ota::progress_dl_status_handler::ProgressDLStatusHandler; @@ -35,6 +37,7 @@ fn register_handlers() -> Vec> { let put_pcb_event_handler = Arc::new(PutPcbEventHandler::new()); let list_package_handler = Arc::new(ListPackageHandler::new()); let progress_dl_status_handler = Arc::new(ProgressDLStatusHandler::new()); + let inquire_card_managment_handler = Arc::new(InquireCardManagmentHandler::new()); let handlers: Vec> = vec![ get_services_handler, @@ -42,7 +45,8 @@ fn register_handlers() -> Vec> { get_messages_handler, put_pcb_event_handler, list_package_handler, - progress_dl_status_handler + progress_dl_status_handler, + inquire_card_managment_handler ]; handlers diff --git a/src/utils/card_convert.rs b/src/utils/card_convert.rs new file mode 100644 index 0000000..91c7304 --- /dev/null +++ b/src/utils/card_convert.rs @@ -0,0 +1,128 @@ + +use super::triple_des; + +lazy_static! { + static ref ALPHABET: Vec = "0123456789ABCDEFGHJKLMNPRSTUWXYZ".chars().collect(); +} + +lazy_static! { + static ref RAWKEY: Vec = { + "?I'llB2c.YouXXXeMeHaYpy!" + .chars() + .filter_map(|c| if c.is_ascii() { Some(c as u8) } else { None }) + .collect() + }; +} + +pub struct CardConvert {} + +impl CardConvert { + pub fn to_konami_id(card_id: String) -> Option { + if card_id.len() != 16 { + eprintln!("Invalid UID length must be 16 characters"); + return None; + } + + let card_type: i32 = if card_id.starts_with("E004") { + 1 + } else if card_id.starts_with('0') { + 2 + } else { + -1 + }; + + if card_type == -1 { + eprintln!("Invalid UID prefix"); + return None; + } + + let mut konami_id: Vec = (0..card_id.len() / 2) + .map(|x| u8::from_str_radix(&card_id[x * 2..x * 2 + 2], 16).unwrap()) + .collect(); + + konami_id.reverse(); + + if konami_id.len() != 8 { + eprintln!("Invalid UID length. Must be 8 bytes"); + return None; + } + + let konami_id = triple_des::encrypt( + &konami_id, + &Self::generate_encryption_key(), + konami_id.len(), + ); + + if konami_id.len() != 8 { + eprintln!("Invalid UID length. Must be 8 bytes"); + return None; + } + + let konami_id = Self::unpack_5(&konami_id); + + let mut konami_id = [&konami_id[..13], &[0, 0, 0]].concat(); + + if konami_id.len() != 16 { + eprintln!("Invalid unpacked ID length. Must be 16 bytes"); + return None; + } + + konami_id[0] ^= card_type as u8; + konami_id[13] = 1; + + for i in 1..14 { + konami_id[i] ^= konami_id[i - 1]; + } + + konami_id[14] = card_type as u8; + konami_id[15] = Self::calculate_checksum(konami_id.clone()); + + Some(konami_id.iter().map(|&byte| ALPHABET[byte as usize]).collect()) + } + + fn generate_encryption_key() -> Vec { + RAWKEY.iter().map(|&x| x * 2).collect() + } + + fn unpack_5(data: &[u8]) -> Vec { + // Convert each byte to an 8-bit binary string and concatenate them + let binary_string: String = data.iter().map(|&b| format!("{:08b}", b)).collect(); + + // Ensure the total length is a multiple of 5 by padding with zeroes if necessary + let remainder = binary_string.len() % 5; + let padded_binary_string = if remainder != 0 { + let padding_length = 5 - remainder; + format!("{}{}", binary_string, "0".repeat(padding_length)) + } else { + binary_string + }; + + // Convert the 5-bit chunks back into bytes + let mut result = Vec::new(); + for chunk in padded_binary_string.as_bytes().chunks(5) { + let chunk_str = std::str::from_utf8(chunk).unwrap(); + let byte = u8::from_str_radix(chunk_str, 2).unwrap(); + result.push(byte); + } + + result + } + + fn calculate_checksum(konami_id: Vec) -> u8 { + let mut checksum: u8 = 0; + + for i in 0..15 { + if i >= konami_id.len() { + break; + } + + checksum = checksum.wrapping_add(konami_id[i] * ((i % 3) + 1) as u8); + } + + while checksum > 31 { + checksum = (checksum >> 5) + (checksum & 31); + } + + checksum as u8 + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..54343df --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,3 @@ + +pub mod card_convert; +pub mod triple_des; \ No newline at end of file diff --git a/src/utils/triple_des.rs b/src/utils/triple_des.rs new file mode 100644 index 0000000..f282370 --- /dev/null +++ b/src/utils/triple_des.rs @@ -0,0 +1,46 @@ +use des::TdesEde3; +use cbc::cipher::{KeyIvInit, BlockEncryptMut, BlockDecryptMut}; + +// Alias for Triple DES CBC encryption +type TdesCbc = cbc::Encryptor; + +// Alias for Triple DES CBC decryption +type TdesCbcDecryptor = cbc::Decryptor; + +// Function to encrypt the data using Triple DES in CBC mode with no padding +pub fn encrypt(data: &[u8], key: &[u8], msg_size: usize) -> Vec { + // All-zero IV (8 bytes) + let iv = [0u8; 8]; + + // Create the Triple DES CBC encryptor with the key and IV + let cipher = TdesCbc::new(key.into(), &iv.into()); + + // Since padding is set to None, the input must be a multiple of the block size (8 bytes for DES/3DES) + assert!(data.len() % 8 == 0, "Input data must be a multiple of 8 bytes"); + + // Create a mutable buffer from the input data + let mut buffer = data.to_vec(); + + // Encrypt the data in place + cipher.encrypt_padded_mut::(&mut buffer, msg_size) + .expect("Encryption failed").to_vec() +} + +// Function to decrypt the data using Triple DES in CBC mode with no padding +pub fn decrypt(data: &[u8], key: &[u8]) -> Vec { + // All-zero IV (8 bytes) + let iv = [0u8; 8]; + + // Create the Triple DES CBC decryptor with the key and IV + let mut cipher = TdesCbcDecryptor::new(key.into(), &iv.into()); + + // Since padding is set to None, the input must be a multiple of the block size (8 bytes for DES/3DES) + assert!(data.len() % 8 == 0, "Input data must be a multiple of 8 bytes"); + + // Create a mutable buffer from the input data + let mut buffer = data.to_vec(); + + // Decrypt the data in place + cipher.decrypt_padded_mut::(&mut buffer) + .expect("Decryption failed").to_vec() +} \ No newline at end of file diff --git a/web/vite.config.ts.timestamp-1725920055853-df6655ad6213d.mjs b/web/vite.config.ts.timestamp-1725920055853-df6655ad6213d.mjs new file mode 100644 index 0000000..bf81f49 --- /dev/null +++ b/web/vite.config.ts.timestamp-1725920055853-df6655ad6213d.mjs @@ -0,0 +1,21 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///D:/ProjectsOld/Rust/Medusa/web/node_modules/vite/dist/node/index.js"; +import vue from "file:///D:/ProjectsOld/Rust/Medusa/web/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import vueDevTools from "file:///D:/ProjectsOld/Rust/Medusa/web/node_modules/vite-plugin-vue-devtools/dist/vite.mjs"; +var __vite_injected_original_import_meta_url = "file:///D:/ProjectsOld/Rust/Medusa/web/vite.config.ts"; +var vite_config_default = defineConfig({ + plugins: [ + vue(), + vueDevTools() + ], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", __vite_injected_original_import_meta_url)) + } + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxQcm9qZWN0c09sZFxcXFxSdXN0XFxcXE1lZHVzYVxcXFx3ZWJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXFByb2plY3RzT2xkXFxcXFJ1c3RcXFxcTWVkdXNhXFxcXHdlYlxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovUHJvamVjdHNPbGQvUnVzdC9NZWR1c2Evd2ViL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgICB2dWVEZXZUb29scygpLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9XHJcbn0pXHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBc1IsU0FBUyxlQUFlLFdBQVc7QUFFelQsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8saUJBQWlCO0FBSnFKLElBQU0sMkNBQTJDO0FBTzlOLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxFQUNkO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLGNBQWMsSUFBSSxJQUFJLFNBQVMsd0NBQWUsQ0FBQztBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==