use std::{error::Error, fs::File, io::{Cursor, Read, Seek, Write}, path::{Path, PathBuf}}; use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter}; fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Vec { haystack.windows(needle.len()) .enumerate() .filter_map(|(i, arr)| if arr == needle { Some(i) } else { None }) .collect() } fn recursively_find_classes(path: PathBuf) -> Vec { let e = std::fs::read_dir(path).unwrap(); let mut classes = Vec::new(); for entry in e { if let Ok(entry) = entry { match entry.metadata().unwrap().is_dir() { true => classes.append(&mut recursively_find_classes(entry.path())), false => { if entry.file_name().to_str().unwrap().ends_with(".class") { classes.push(entry.path()); } } } } } classes } // patch_jar(r#"D:\Documents\RustroverProjects\XCraft\xcraft\libraries\com\mojang\authlib\1.5.22\authlib-1.5.22__.jar"#, "authlib_patched.jar", "http://localhost:8999/api/") pub fn patch_jar(input_jar: &str, output_jar: &str, endpoint: &str) -> Result<(), Box> { let mut target_dir = PathBuf::new(); target_dir.push("out"); let archive = std::fs::read(input_jar)?; zip_extract::extract(Cursor::new(archive), &target_dir, true)?; let needle = b"https://sessionserver.mojang.com/session/minecraft/"; let replacement = endpoint.as_bytes(); for path in recursively_find_classes(PathBuf::from(".\\out")) { let mut haystack = std::fs::read(&path).unwrap(); let mut v = find_subsequence(&haystack, needle); if v.is_empty() { continue; } while let Some(g) = v.first() { let (a,b) = haystack.split_at(*g); let l = a[a.len()-1]; let mut a = a[..a.len()-1].to_vec(); a.push( if l as usize > needle.len() { (replacement.len() + (l as usize - needle.len())) as u8 } else { replacement.len() as u8 }); a.append(&mut replacement.to_vec()); a.append(&mut b[needle.len()..].to_vec()); haystack = a; v = find_subsequence(&haystack, needle); } std::fs::write(path, haystack)?; } let jar = File::create(output_jar)?; zip_dir(&target_dir, &target_dir, jar)?; std::fs::remove_dir_all(&target_dir)?; Ok(()) } fn zip_dir( src_dir: &Path, prefix: &Path, writer: T, ) -> zip::result::ZipResult<()> { let mut zip = ZipWriter::new(writer); let file_options = SimpleFileOptions::default() .compression_method(CompressionMethod::Deflated) .unix_permissions(0o644); // Simulating a DOS file attribute let dir_options = SimpleFileOptions::default() .compression_method(CompressionMethod::Deflated) .unix_permissions(0o755); let mut buffer = Vec::new(); for entry in walkdir::WalkDir::new(src_dir).same_file_system(true).min_depth(1) { let entry = entry.unwrap(); let path = entry.path(); let name = path.strip_prefix(prefix).unwrap(); let dos_path = name.to_string_lossy().replace("/", "\\"); if path.is_file() { let mut f = File::open(path)?; f.read_to_end(&mut buffer)?; zip.start_file(dos_path, file_options)?; zip.write_all(&buffer)?; buffer.clear(); } else if path.is_dir() { zip.add_directory(dos_path, dir_options)?; } } zip.finish()?; Ok(()) }