commit 3039b0fa121d4a432ef9d3991bbc82c29892b053 Author: = <=> Date: Fri Sep 6 13:31:50 2024 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30cb2d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,174 @@ +# ---> VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# ---> Rust +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# ---> Node +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# ---> Vue +# gitignore template for Vue.js projects +# +# Recommended template: Node.gitignore + +# TODO: where does this rule come from? +docs/_book + +# TODO: where does this rule come from? +test/ + +static/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0acb50f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "medusa" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.82" +chrono = "0.4.38" +kbinxml = { git = "https://github.com/mbilker/kbinxml-rs.git", version = "3.1.1" } +lazy_static = "1.5.0" +lz77 = "0.1.0" +md5 = "0.7.0" +miniz_oxide = "0.8.0" +psmap = { git = "https://github.com/mbilker/kbinxml-rs.git", version = "2.0.0" } +psmap_derive = { git = "https://github.com/mbilker/kbinxml-rs.git", version = "2.0.0" } +quick-xml = "0.36.1" +rc4 = "0.1.0" +rocket = "0.5.1" +xml-rs = "0.8.21" + +[build-dependencies] +fs_extra = "1.3.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..61f6f8c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Medusa + +E-Amusement server written in rust \ No newline at end of file diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..706f12e --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,2 @@ +[default] +address = "0.0.0.0" \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3c8c9b7 --- /dev/null +++ b/build.rs @@ -0,0 +1,86 @@ +use std::env; +use std::fs; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + // Get the current build profile (debug or release) + let profile = env::var("PROFILE").expect("PROFILE environment variable not set"); + + // Determine the base directory for the build artifacts (target/debug or target/release) + let target_base_dir = PathBuf::from("target"); + + // Construct the target path based on the build profile + let target_profile_dir = target_base_dir.join(profile); + + // Path to the Rocket.toml file you want to copy + let rocket_config_src = PathBuf::from("Rocket.toml"); + + // Path where the Rocket.toml file will be copied + let rocket_config_dest = target_profile_dir.join("Rocket.toml"); + + // Create the target profile directory if it does not exist + fs::create_dir_all(&target_profile_dir).expect("Failed to create directory"); + + // Copy the Rocket.toml configuration file to the target directory + fs::copy(rocket_config_src, rocket_config_dest).expect("Failed to copy Rocket.toml"); + + let install_cmd = "cd web && bun install"; + + // Execute the web build command and wait for it to finish + let _status = if cfg!(target_os = "windows") { + Command::new("cmd") + .arg("/C") + .arg(install_cmd) + .status() + .expect("Failed to execute web build command") + } else { + Command::new("sh") + .arg("-c") + .arg(install_cmd) + .status() + .expect("Failed to execute web build command") + }; + + let web_build_cmd = "cd web && bun run build"; + + // Execute the web build command and wait for it to finish + let status = if cfg!(target_os = "windows") { + Command::new("cmd") + .arg("/C") + .arg(web_build_cmd) + .status() + .expect("Failed to execute web build command") + } else { + Command::new("sh") + .arg("-c") + .arg(web_build_cmd) + .status() + .expect("Failed to execute web build command") + }; + + // Check if the build command was successful + if !status.success() { + panic!("Web build command failed with status: {:?}", status); + } + + // Check if the build command was successful + if status.success() { + // Copy everything from web/dist to static + let web_dist_dir = PathBuf::from("web/dist"); + let static_dir = PathBuf::from("static"); + + // Copy the contents of the web/dist directory to the static directory + let _ = fs_extra::dir::copy( + &web_dist_dir, + &static_dir, + &fs_extra::dir::CopyOptions { + overwrite: true, + content_only: true, + ..Default::default() + }, + ); + } else { + eprintln!("Web build command failed"); + } +} diff --git a/src/generators/mod.rs b/src/generators/mod.rs new file mode 100644 index 0000000..d6aa7ac --- /dev/null +++ b/src/generators/mod.rs @@ -0,0 +1 @@ +pub mod response_generator; \ No newline at end of file diff --git a/src/generators/response_generator.rs b/src/generators/response_generator.rs new file mode 100644 index 0000000..a4836d0 --- /dev/null +++ b/src/generators/response_generator.rs @@ -0,0 +1,76 @@ +use kbinxml::Options; +use rc4::cipher::generic_array::GenericArray; +use rc4::{consts::*, KeyInit, StreamCipher}; +use rc4::Rc4; +use miniz_oxide::deflate::compress_to_vec; + +lazy_static! { + static ref KEY: Vec = { + "00000000000069D74627D985EE2187161570D08D93B12455035B6DF0D8205DF5" + .chars() + .collect::>() + .chunks(2) + .map(|chunk| u8::from_str_radix(&chunk.iter().collect::(), 16).unwrap()) + .collect() + }; +} + +pub struct ResponseGenerator; + +impl ResponseGenerator { + pub fn generate_response(mut binary_data: Vec, compress: bool, info: String) -> Vec { + + let result = kbinxml::from_text_xml(&binary_data); + + let (collection, _encoding) = match result { + Ok(result) => result, + Err(e) => { + eprintln!("Error parsing response xml: {}", e); + return Vec::new() + }, + }; + + let options = Options::with_encoding(kbinxml::EncodingType::SHIFT_JIS); + let result = kbinxml::to_binary_with_options(options, &collection); + + binary_data = match result { + Ok(result) => result, + Err(e) => { + eprintln!("Error encoding response xml: {}", e); + return Vec::new() + } + }; + + if compress { + binary_data = compress_to_vec(&binary_data, 6); + } + + if info != "" { + let mut key = KEY.clone(); + + let info_parts = info.split('-').collect::>(); + + if info_parts.len() < 2 { + eprintln!("Wrong eamuse info",); + return Vec::new(); + } + + let combined = format!("{}{}", info_parts[1], info_parts[2]); + for i in 0..6 { + // Extract a 2-character substring from the combined string + let hex_str = &combined[i * 2..i * 2 + 2]; + + // Convert the 2-character hexadecimal substring to a byte + key[i] = u8::from_str_radix(hex_str, 16).expect("Invalid hex string"); + } + + let md5_hash = md5::compute(key).0.to_vec(); + let rc4_key: GenericArray = GenericArray::clone_from_slice(&md5_hash); + + let mut rc4 = Rc4::new(&rc4_key); + rc4.apply_keystream(&mut binary_data); + } + + binary_data + } +} \ No newline at end of file diff --git a/src/handlers/boot/get_services_handler.rs b/src/handlers/boot/get_services_handler.rs new file mode 100644 index 0000000..a16a784 --- /dev/null +++ b/src/handlers/boot/get_services_handler.rs @@ -0,0 +1,219 @@ +use std::io::Cursor; + +use xml::writer::{EmitterConfig, XmlEvent}; + +use crate::handlers::handler::Handler; + +pub struct GetServicesHandler { + module: String, + method: String, +} + +impl GetServicesHandler { + pub fn new() -> Self { + GetServicesHandler { + module: "services".to_string(), + method: "get".to_string(), + } + } + + pub fn create_core_services_element<'a>( + common_url: String, + listening_address: String, + ) -> Vec> { + let common_url = common_url.clone(); + + let core_services = [ + "cardmng", + "facility", + "message", + "numbering", + "package", + "pcbevent", + "pcbtracker", + "pkglist", + "posevent", + "userdata", + "userid", + "eacoin", + "dlstatus", + "netlog", + "info", + "reference", + "sidmgr", + ]; + + let mut services = Vec::new(); + + services.push( + XmlEvent::start_element("services") + .attr("expire", "3600") + .attr("method", "get") + .attr("mode", "operation") + .attr("status", "0") + .into(), + ); + + for service in core_services.iter() { + let service_url = format!("{}/{}", common_url, service); + + // Push the start element with the service URL, without a temporary reference + services.push( + XmlEvent::start_element("item") + .attr("name", service) + .attr("url", Box::leak(service_url.into_boxed_str())) // Convert to &str at this point + .into(), + ); + services.push(XmlEvent::end_element().into()); + } + + services.push( + XmlEvent::start_element("item") + .attr("name", "ntp") + .attr("url", "ntp://pool.ntp.org/") + .into(), + ); + services.push(XmlEvent::end_element().into()); + + let keep_alive_url = format!( + "{}/keepalive?pa=127.0.0.1&ia=127.0.0.1&ga=127.0.0.1&ma=127.0.0.1&t1=2&t2=10", + listening_address + ); + + services.push( + XmlEvent::start_element("item") + .attr("name", "keepalive") + .attr("url", Box::leak(keep_alive_url.into_boxed_str())) + .into(), + ); + services.push(XmlEvent::end_element().into()); + + services.push(XmlEvent::end_element().into()); // Close "services" element + + services + } + + fn add_kfc_services<'a>( + mut services: Vec>, + common_url: &'a str, + ) -> Vec> { + let kfc_services = vec![ + "local", "local2", "lobby", "slocal", "slocal2", "sglocal", "sglocal2", "lab", + "globby", "slobby", "sglobby", + ]; + + for service in kfc_services { + services.push( + XmlEvent::start_element("item") + .attr("name", service) + .attr("url", common_url) + .into(), + ); + services.push(XmlEvent::end_element().into()); + } + services + } + + fn add_mdx_services<'a>( + mut services: Vec>, + common_url: &'a str, + ) -> Vec> { + let mdx_services = vec![ + "local", "local2", "lobby", "slocal", "slocal2", "sglocal", "sglocal2", "lab", + "globby", "slobby", "sglobby", + ]; + + for service in mdx_services { + services.push( + XmlEvent::start_element("item") + .attr("name", service) + .attr("url", common_url) + .into(), + ); + services.push(XmlEvent::end_element().into()); + } + services + } + + fn add_m39_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_m32_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] +impl Handler for GetServicesHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, model: String, _body: String) -> String { + let common_url = "http://127.0.0.1:8000"; + let listening_address = "http://127.0.0.1:8000"; + + let services = GetServicesHandler::create_core_services_element( + common_url.to_string(), + listening_address.to_string(), + ); + let response = match model.split(':').next() { + Some("KFC") => GetServicesHandler::add_kfc_services(services, &common_url), + 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), + _ => services, + }; + + // Create final XML response + let mut buffer = Cursor::new(Vec::new()); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + writer.write(XmlEvent::start_element("response")).unwrap(); + for element in response { + writer.write(element).unwrap(); + } + writer.write(XmlEvent::end_element()).unwrap(); + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/boot/mod.rs b/src/handlers/boot/mod.rs new file mode 100644 index 0000000..162f79e --- /dev/null +++ b/src/handlers/boot/mod.rs @@ -0,0 +1,2 @@ + +pub mod get_services_handler; \ No newline at end of file diff --git a/src/handlers/common/alive_pcb_tracker_handler.rs b/src/handlers/common/alive_pcb_tracker_handler.rs new file mode 100644 index 0000000..cc47116 --- /dev/null +++ b/src/handlers/common/alive_pcb_tracker_handler.rs @@ -0,0 +1,56 @@ +use std::io::Cursor; + +use crate::handlers::handler::Handler; +use chrono::Utc; +use xml::writer::{EmitterConfig, XmlEvent}; + +pub struct AlivePcbTrackerHandler { + module: String, + method: String, +} + +impl AlivePcbTrackerHandler { + pub fn new() -> Self { + AlivePcbTrackerHandler { + module: "pcbtracker".to_string(), + method: "alive".to_string(), + } + } +} + +#[async_trait] +impl Handler for AlivePcbTrackerHandler { + fn module(&self) -> &str { + &self.module + } + + fn method(&self) -> &str { + &self.method + } + + async fn handle(&self, _model: String, _body: String) -> String { + let mut buffer = Cursor::new(Vec::new()); + let mut writer = EmitterConfig::new() + .perform_indent(true) + .create_writer(&mut buffer); + + writer.write(XmlEvent::start_element("response")).unwrap(); + + let utc_now = Utc::now().timestamp().to_string(); + + let pcb_tracker = XmlEvent::start_element("pcbtracker") + .attr("status", "0") + .attr("expire", "1200") + .attr("ecenable", "0") + .attr("eclimit", "0") + .attr("limit", "0") + .attr("time", &utc_now); + + writer.write(pcb_tracker).unwrap(); + + writer.write(XmlEvent::end_element()).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + + String::from_utf8(buffer.into_inner()).unwrap() + } +} diff --git a/src/handlers/common/mod.rs b/src/handlers/common/mod.rs new file mode 100644 index 0000000..dd43bc2 --- /dev/null +++ b/src/handlers/common/mod.rs @@ -0,0 +1,2 @@ + +pub mod alive_pcb_tracker_handler; \ No newline at end of file diff --git a/src/handlers/core_e_amuse_request_handler.rs b/src/handlers/core_e_amuse_request_handler.rs new file mode 100644 index 0000000..7dbdf84 --- /dev/null +++ b/src/handlers/core_e_amuse_request_handler.rs @@ -0,0 +1,95 @@ +use std::io::Cursor; +use std::sync::Arc; +use rocket::http::{ContentType, Header}; +use rocket::tokio::io::AsyncReadExt; +use rocket::{Data, Request, Response}; +use rocket::response::Responder; +use rocket::response::status::BadRequest; +use rocket::data::ToByteUnit; + +use crate::generators::response_generator::ResponseGenerator; +use crate::parsers::request_parser::RequestParser; + +use super::handler::Handler; + +pub struct BinaryResponse { + body: Vec, + is_compressed: bool, + info: Option, +} + +impl<'r> Responder<'r, 'static> for BinaryResponse { + fn respond_to(self, _: &'r Request<'_>) -> rocket::response::Result<'static> { + let mut binding = Response::build(); + let mut response = binding + .header(ContentType::new("application", "octet-stream")) + .sized_body(self.body.len(), Cursor::new(self.body)); + + if self.is_compressed { + response = response.header(Header::new("x-compress", "lz77")); + } + + if let Some(info) = self.info { + response = response.header(Header::new("x-eamuse-info", info)); + } + + response.ok() + } +} + +pub async fn handle_e_amuse_request(model: &str, module: &str, method: &str, data: Data<'_>, + is_compressed: bool, info: String, handlers: Vec>) -> Result> { + + let mut buffer = Vec::new(); + + if let Err(e) = data.open(256.kibibytes()).read_to_end(&mut buffer).await { + eprintln!("Failed to read data: {}", e); + + return Err(BadRequest(e.to_string())); + } + + let body_xml = RequestParser::parse_request(buffer, is_compressed, info.clone()); + + if body_xml.0.is_none() { + return Err(BadRequest("Could not parse body".to_string())); + } + + let nodes = body_xml.0.unwrap(); + + let body_bytes = match kbinxml::to_text_xml(&nodes) { + Ok(result) => result, + Err(e) => { + eprint!("Could not convert to string {}", e); + return Err(BadRequest("Could not parse body".to_string())); + }, + }; + + let xml_string = match String::from_utf8(body_bytes) { + Ok(result) => result, + Err(e) => { + eprint!("Could not convert to string {}", e); + return Err(BadRequest("Could not parse body".to_string())); + } + }; + + // Print the values of model, module, and method + println!("Model: {}, Module: {}, Method: {}", model, module, method); + + let mut response_xml = String::new(); + + for handler in handlers.iter() { + if handler.module() == module && handler.method() == method { + let model = model.to_string(); + let xml_string = xml_string.clone(); + response_xml = handler.handle(model, xml_string).await; + } + } + + let response = ResponseGenerator::generate_response(response_xml.into(), is_compressed, info.clone()); + // Return the binary data wrapped in BinaryResponse + Ok(BinaryResponse { + body: response, + is_compressed, + info: Some(info) + }) +} \ No newline at end of file diff --git a/src/handlers/handler.rs b/src/handlers/handler.rs new file mode 100644 index 0000000..64b138d --- /dev/null +++ b/src/handlers/handler.rs @@ -0,0 +1,8 @@ +use std::{future::Future, sync::Arc}; + +#[async_trait] +pub trait Handler: Send + Sync { + fn module(&self) -> &str; + fn method(&self) -> &str; + async fn handle(&self,model: String, body: String) -> String; +} \ No newline at end of file diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..5adc3fe --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,4 @@ +pub mod handler; +pub mod boot; +pub mod common; +pub mod core_e_amuse_request_handler; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..17c09aa --- /dev/null +++ b/src/main.rs @@ -0,0 +1,45 @@ + + +use std::sync::Arc; + +mod parsers; +mod generators; +mod handlers; +mod routes; + +use handlers::boot::get_services_handler::GetServicesHandler; +use handlers::common::alive_pcb_tracker_handler::AlivePcbTrackerHandler; +use handlers::handler::Handler; +use rocket::fs::{relative, FileServer, NamedFile}; +use routes::ea::core_route::handle_nostalgia_request; +use routes::ea::core_route::handle_query_request; +use routes::ea::core_route::handle_slash_request; + +#[macro_use] extern crate lazy_static; +#[macro_use] extern crate rocket; + +#[get("/")] +async fn index() -> Option { + NamedFile::open("static/index.html").await.ok() +} + +fn register_handlers() -> Vec> { + let mut handlers: Vec> = Vec::new(); + + let get_services_handler = Arc::new(GetServicesHandler::new()); + let alive_pcb_tracker_handler = Arc::new(AlivePcbTrackerHandler::new()); + handlers.push(get_services_handler); + handlers.push(alive_pcb_tracker_handler); + + handlers +} + +#[launch] +fn rocket() -> _ { + let handlers = register_handlers(); + + rocket::build() + .manage(handlers) + .mount("/", routes![index, handle_query_request, handle_slash_request, handle_slash_request, handle_nostalgia_request]) + .mount("/", FileServer::from(relative!("static"))) +} diff --git a/src/parsers/header_parser.rs b/src/parsers/header_parser.rs new file mode 100644 index 0000000..52ffb50 --- /dev/null +++ b/src/parsers/header_parser.rs @@ -0,0 +1,47 @@ +use std::convert::Infallible; + +use rocket::{request::{FromRequest, Outcome}, Request}; + + +pub struct EAMuseHeaders { + pub is_compressed: bool, + pub info: String +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for EAMuseHeaders { + type Error = Infallible; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + // Extract headers from the request + let headers = request.headers(); + + for header in headers.iter() { + let key = header.name(); + let value = header.value().to_string(); + println!("Header: {} = {}", key, value); + } + + // Example header keys + let is_compressed_header = headers.get_one("x-compress"); + let info_header = headers.get_one("x-eamuse-info"); + + println!("x-compress header: {:?}", is_compressed_header); + println!("x-eamuse-info header: {:?}", info_header); + + // Parse the `is_compressed` header (if it exists and is "true") + let is_compressed = matches!(is_compressed_header, Some(value) if value == "true"); + + // Get the `info` header, or set a default if it's not found + let info = info_header.unwrap_or("default_info").to_string(); + + // Construct the EAMuseHeaders struct + let eamuse_headers = EAMuseHeaders { + is_compressed, + info, + }; + + // Return the constructed struct wrapped in Outcome::Success + Outcome::Success(eamuse_headers) + } +} \ No newline at end of file diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs new file mode 100644 index 0000000..429e1a7 --- /dev/null +++ b/src/parsers/mod.rs @@ -0,0 +1,3 @@ + +pub mod request_parser; +pub mod header_parser; \ No newline at end of file diff --git a/src/parsers/request_parser.rs b/src/parsers/request_parser.rs new file mode 100644 index 0000000..4ffb7e8 --- /dev/null +++ b/src/parsers/request_parser.rs @@ -0,0 +1,74 @@ +use std::io::Cursor; + +use kbinxml::{EncodingType, NodeCollection}; +use rc4::cipher::generic_array::GenericArray; +use rc4::{consts::*, KeyInit, StreamCipher}; +use rc4::Rc4; + +lazy_static! { + static ref KEY: Vec = { + "00000000000069D74627D985EE2187161570D08D93B12455035B6DF0D8205DF5" + .chars() + .collect::>() + .chunks(2) + .map(|chunk| u8::from_str_radix(&chunk.iter().collect::(), 16).unwrap()) + .collect() + }; +} + +pub struct RequestParser { +} + +impl RequestParser { + pub fn parse_request(mut buffer: Vec, is_compressed: bool, info: String) -> (Option, EncodingType) { + + if info != "" { + let mut key = KEY.clone(); + + let info_parts = info.split('-').collect::>(); + + if info_parts.len() < 2 { + eprintln!("Wrong eamuse info",); + return (None, EncodingType::None); + } + + let combined = format!("{}{}", info_parts[1], info_parts[2]); + for i in 0..6 { + // Extract a 2-character substring from the combined string + let hex_str = &combined[i * 2..i * 2 + 2]; + + // Convert the 2-character hexadecimal substring to a byte + key[i] = u8::from_str_radix(hex_str, 16).expect("Invalid hex string"); + } + + let md5_hash = md5::compute(key).0.to_vec(); + let rc4_key: GenericArray = GenericArray::clone_from_slice(&md5_hash); + + let mut rc4 = Rc4::new(&rc4_key); + rc4.apply_keystream(&mut buffer) + } + + if is_compressed { + let uncompress_result = lz77::decompress(Cursor::new(buffer)); + + match uncompress_result { + Ok(result) => + buffer = result, + Err(e) => { + eprintln!("Error decompressing body {}", e); + return (None, EncodingType::None); + }, + } + } + + let result = kbinxml::from_binary(buffer.into()); + + match result { + Ok(result) => (Some(result.0), result.1), + Err(e) => { + eprintln!("Error parsing xml {}", e); + (None, EncodingType::None) + }, + } + } +} \ No newline at end of file diff --git a/src/routes/ea/core_route.rs b/src/routes/ea/core_route.rs new file mode 100644 index 0000000..50d2295 --- /dev/null +++ b/src/routes/ea/core_route.rs @@ -0,0 +1,38 @@ +use std::sync::Arc; +use rocket::response::status::BadRequest; +use rocket::data::Data; +use rocket::State; + +use crate::{handlers::{core_e_amuse_request_handler::{handle_e_amuse_request, BinaryResponse}, handler::Handler}, parsers::header_parser::EAMuseHeaders}; + +#[post("///", data = "")] +pub async fn handle_slash_request(model: &str, module: &str, method: &str, data: Data<'_>, +e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { + let is_compressed = e_amuse_headers.is_compressed; + let info = e_amuse_headers.info; + + handle_e_amuse_request(model, module, method, data, is_compressed, info, handlers.to_vec()).await +} + +#[post("/?&&", data = "")] +pub async fn handle_query_request(model: &str, module: &str, method: &str, data: Data<'_>, +e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { + let is_compressed = e_amuse_headers.is_compressed; + let info = e_amuse_headers.info; + + handle_e_amuse_request(model, module, method, data, is_compressed, info, handlers.to_vec()).await +} + +// TODO weird nostalgia request url +#[post("/?", data = "")] +pub async fn handle_nostalgia_request(model: &str, f: &str, data: Data<'_>, +e_amuse_headers: EAMuseHeaders, handlers: &State>>) -> Result> { + let is_compressed = e_amuse_headers.is_compressed; + let info = e_amuse_headers.info; + + let f_splt = f.split('.').collect::>(); + let module = f_splt[0]; + let service = f_splt[1]; + + handle_e_amuse_request(model, module, service, data, is_compressed, info, handlers.to_vec()).await +} \ No newline at end of file diff --git a/src/routes/ea/mod.rs b/src/routes/ea/mod.rs new file mode 100644 index 0000000..8f4ef2c --- /dev/null +++ b/src/routes/ea/mod.rs @@ -0,0 +1,2 @@ + +pub mod core_route; \ No newline at end of file diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..eb5f011 --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,2 @@ + +pub mod ea; \ No newline at end of file diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs new file mode 100644 index 0000000..6f40582 --- /dev/null +++ b/web/.eslintrc.cjs @@ -0,0 +1,15 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + '@vue/eslint-config-prettier/skip-formatting' + ], + parserOptions: { + ecmaVersion: 'latest' + } +} diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/web/.prettierrc.json b/web/.prettierrc.json new file mode 100644 index 0000000..66e2335 --- /dev/null +++ b/web/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/web/.vscode/extensions.json b/web/.vscode/extensions.json new file mode 100644 index 0000000..93ea3e7 --- /dev/null +++ b/web/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "Vue.volar", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..ba5f187 --- /dev/null +++ b/web/README.md @@ -0,0 +1,39 @@ +# medusa-web + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +bun install +``` + +### Compile and Hot-Reload for Development + +```sh +bun dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +bun build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +bun lint +``` diff --git a/web/bun.lockb b/web/bun.lockb new file mode 100644 index 0000000..c66bc68 Binary files /dev/null and b/web/bun.lockb differ diff --git a/web/env.d.ts b/web/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/web/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..a888544 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..2ac35e4 --- /dev/null +++ b/web/package.json @@ -0,0 +1,37 @@ +{ + "name": "medusa-web", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "pinia": "^2.1.7", + "vue": "^3.4.29", + "vue-router": "^4.3.3" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.8.0", + "@tsconfig/node20": "^20.1.4", + "@types/node": "^20.14.5", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^13.0.0", + "@vue/tsconfig": "^0.5.1", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.23.0", + "npm-run-all2": "^6.2.0", + "prettier": "^3.2.5", + "typescript": "~5.4.0", + "vite": "^5.3.1", + "vite-plugin-vue-devtools": "^7.3.1", + "vue-tsc": "^2.0.21" + } +} diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..7905b05 --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/web/src/assets/base.css b/web/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/web/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/web/src/assets/logo.svg b/web/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/web/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/web/src/assets/main.css b/web/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/web/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/web/src/components/HelloWorld.vue b/web/src/components/HelloWorld.vue new file mode 100644 index 0000000..38d821e --- /dev/null +++ b/web/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/web/src/components/TheWelcome.vue b/web/src/components/TheWelcome.vue new file mode 100644 index 0000000..49d8f73 --- /dev/null +++ b/web/src/components/TheWelcome.vue @@ -0,0 +1,88 @@ + + + diff --git a/web/src/components/WelcomeItem.vue b/web/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/web/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/web/src/components/icons/IconCommunity.vue b/web/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/web/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/web/src/components/icons/IconDocumentation.vue b/web/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/web/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/web/src/components/icons/IconEcosystem.vue b/web/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/web/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/web/src/components/icons/IconSupport.vue b/web/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/web/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/web/src/components/icons/IconTooling.vue b/web/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/web/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..5dcad83 --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,14 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/web/src/router/index.ts b/web/src/router/index.ts new file mode 100644 index 0000000..a49ae50 --- /dev/null +++ b/web/src/router/index.ts @@ -0,0 +1,23 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/about', + name: 'about', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import('../views/AboutView.vue') + } + ] +}) + +export default router diff --git a/web/src/stores/counter.ts b/web/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/web/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/web/src/views/AboutView.vue b/web/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/web/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/web/src/views/HomeView.vue b/web/src/views/HomeView.vue new file mode 100644 index 0000000..d5c0217 --- /dev/null +++ b/web/src/views/HomeView.vue @@ -0,0 +1,9 @@ + + + diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json new file mode 100644 index 0000000..e14c754 --- /dev/null +++ b/web/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/web/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..26a21a9 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) diff --git a/web/vite.config.ts.timestamp-1725557239767-9dda8c8f827e5.mjs b/web/vite.config.ts.timestamp-1725557239767-9dda8c8f827e5.mjs new file mode 100644 index 0000000..e72053b --- /dev/null +++ b/web/vite.config.ts.timestamp-1725557239767-9dda8c8f827e5.mjs @@ -0,0 +1,21 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///C:/Projects/rust/medusa/web/node_modules/vite/dist/node/index.js"; +import vue from "file:///C:/Projects/rust/medusa/web/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import vueDevTools from "file:///C:/Projects/rust/medusa/web/node_modules/vite-plugin-vue-devtools/dist/vite.mjs"; +var __vite_injected_original_import_meta_url = "file:///C:/Projects/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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxQcm9qZWN0c1xcXFxydXN0XFxcXG1lZHVzYVxcXFx3ZWJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFByb2plY3RzXFxcXHJ1c3RcXFxcbWVkdXNhXFxcXHdlYlxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovUHJvamVjdHMvcnVzdC9tZWR1c2Evd2ViL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgICB2dWVEZXZUb29scygpLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9XHJcbn0pXHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBNlEsU0FBUyxlQUFlLFdBQVc7QUFFaFQsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8saUJBQWlCO0FBSitJLElBQU0sMkNBQTJDO0FBT3hOLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxFQUNkO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLGNBQWMsSUFBSSxJQUFJLFNBQVMsd0NBQWUsQ0FBQztBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/web/vite.config.ts.timestamp-1725611137987-504512dd3fecb.mjs b/web/vite.config.ts.timestamp-1725611137987-504512dd3fecb.mjs new file mode 100644 index 0000000..e72053b --- /dev/null +++ b/web/vite.config.ts.timestamp-1725611137987-504512dd3fecb.mjs @@ -0,0 +1,21 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///C:/Projects/rust/medusa/web/node_modules/vite/dist/node/index.js"; +import vue from "file:///C:/Projects/rust/medusa/web/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import vueDevTools from "file:///C:/Projects/rust/medusa/web/node_modules/vite-plugin-vue-devtools/dist/vite.mjs"; +var __vite_injected_original_import_meta_url = "file:///C:/Projects/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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxQcm9qZWN0c1xcXFxydXN0XFxcXG1lZHVzYVxcXFx3ZWJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFByb2plY3RzXFxcXHJ1c3RcXFxcbWVkdXNhXFxcXHdlYlxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovUHJvamVjdHMvcnVzdC9tZWR1c2Evd2ViL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgICB2dWVEZXZUb29scygpLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9XHJcbn0pXHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBNlEsU0FBUyxlQUFlLFdBQVc7QUFFaFQsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8saUJBQWlCO0FBSitJLElBQU0sMkNBQTJDO0FBT3hOLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxFQUNkO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLGNBQWMsSUFBSSxJQUFJLFNBQVMsd0NBQWUsQ0FBQztBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/web/vite.config.ts.timestamp-1725611236802-6f31184b59262.mjs b/web/vite.config.ts.timestamp-1725611236802-6f31184b59262.mjs new file mode 100644 index 0000000..e72053b --- /dev/null +++ b/web/vite.config.ts.timestamp-1725611236802-6f31184b59262.mjs @@ -0,0 +1,21 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///C:/Projects/rust/medusa/web/node_modules/vite/dist/node/index.js"; +import vue from "file:///C:/Projects/rust/medusa/web/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import vueDevTools from "file:///C:/Projects/rust/medusa/web/node_modules/vite-plugin-vue-devtools/dist/vite.mjs"; +var __vite_injected_original_import_meta_url = "file:///C:/Projects/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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxQcm9qZWN0c1xcXFxydXN0XFxcXG1lZHVzYVxcXFx3ZWJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFByb2plY3RzXFxcXHJ1c3RcXFxcbWVkdXNhXFxcXHdlYlxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovUHJvamVjdHMvcnVzdC9tZWR1c2Evd2ViL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgICB2dWVEZXZUb29scygpLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9XHJcbn0pXHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBNlEsU0FBUyxlQUFlLFdBQVc7QUFFaFQsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8saUJBQWlCO0FBSitJLElBQU0sMkNBQTJDO0FBT3hOLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxFQUNkO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLGNBQWMsSUFBSSxJQUFJLFNBQVMsd0NBQWUsQ0FBQztBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/web/vite.config.ts.timestamp-1725611959441-fab86b25a0ef7.mjs b/web/vite.config.ts.timestamp-1725611959441-fab86b25a0ef7.mjs new file mode 100644 index 0000000..e72053b --- /dev/null +++ b/web/vite.config.ts.timestamp-1725611959441-fab86b25a0ef7.mjs @@ -0,0 +1,21 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///C:/Projects/rust/medusa/web/node_modules/vite/dist/node/index.js"; +import vue from "file:///C:/Projects/rust/medusa/web/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import vueDevTools from "file:///C:/Projects/rust/medusa/web/node_modules/vite-plugin-vue-devtools/dist/vite.mjs"; +var __vite_injected_original_import_meta_url = "file:///C:/Projects/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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxQcm9qZWN0c1xcXFxydXN0XFxcXG1lZHVzYVxcXFx3ZWJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFByb2plY3RzXFxcXHJ1c3RcXFxcbWVkdXNhXFxcXHdlYlxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovUHJvamVjdHMvcnVzdC9tZWR1c2Evd2ViL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgICB2dWVEZXZUb29scygpLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9XHJcbn0pXHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBNlEsU0FBUyxlQUFlLFdBQVc7QUFFaFQsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8saUJBQWlCO0FBSitJLElBQU0sMkNBQTJDO0FBT3hOLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxFQUNkO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLGNBQWMsSUFBSSxJQUFJLFNBQVMsd0NBQWUsQ0FBQztBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/web/vite.config.ts.timestamp-1725621790222-41fa60a8534ba.mjs b/web/vite.config.ts.timestamp-1725621790222-41fa60a8534ba.mjs new file mode 100644 index 0000000..e72053b --- /dev/null +++ b/web/vite.config.ts.timestamp-1725621790222-41fa60a8534ba.mjs @@ -0,0 +1,21 @@ +// vite.config.ts +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "file:///C:/Projects/rust/medusa/web/node_modules/vite/dist/node/index.js"; +import vue from "file:///C:/Projects/rust/medusa/web/node_modules/@vitejs/plugin-vue/dist/index.mjs"; +import vueDevTools from "file:///C:/Projects/rust/medusa/web/node_modules/vite-plugin-vue-devtools/dist/vite.mjs"; +var __vite_injected_original_import_meta_url = "file:///C:/Projects/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,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxQcm9qZWN0c1xcXFxydXN0XFxcXG1lZHVzYVxcXFx3ZWJcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFByb2plY3RzXFxcXHJ1c3RcXFxcbWVkdXNhXFxcXHdlYlxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovUHJvamVjdHMvcnVzdC9tZWR1c2Evd2ViL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSAnbm9kZTp1cmwnXHJcblxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZURldlRvb2xzIGZyb20gJ3ZpdGUtcGx1Z2luLXZ1ZS1kZXZ0b29scydcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW1xyXG4gICAgdnVlKCksXHJcbiAgICB2dWVEZXZUb29scygpLFxyXG4gIF0sXHJcbiAgcmVzb2x2ZToge1xyXG4gICAgYWxpYXM6IHtcclxuICAgICAgJ0AnOiBmaWxlVVJMVG9QYXRoKG5ldyBVUkwoJy4vc3JjJywgaW1wb3J0Lm1ldGEudXJsKSlcclxuICAgIH1cclxuICB9XHJcbn0pXHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBNlEsU0FBUyxlQUFlLFdBQVc7QUFFaFQsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxTQUFTO0FBQ2hCLE9BQU8saUJBQWlCO0FBSitJLElBQU0sMkNBQTJDO0FBT3hOLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLElBQUk7QUFBQSxJQUNKLFlBQVk7QUFBQSxFQUNkO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxLQUFLLGNBQWMsSUFBSSxJQUFJLFNBQVMsd0NBQWUsQ0FBQztBQUFBLElBQ3REO0FBQUEsRUFDRjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==