Initial commit
This commit is contained in:
242
license-system-launcher/src/bin/launcher_gui.rs
Normal file
242
license-system-launcher/src/bin/launcher_gui.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use eframe::egui;
|
||||
|
||||
const MAGIC: &[u8] = b"LSWRAP1";
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"授权打包器",
|
||||
options,
|
||||
Box::new(|_| Box::new(LauncherGui::new())),
|
||||
)
|
||||
}
|
||||
|
||||
struct LauncherGui {
|
||||
api_base: String,
|
||||
integration_code: String,
|
||||
input_path: String,
|
||||
output_path: String,
|
||||
extract_to_self: bool,
|
||||
status: String,
|
||||
}
|
||||
|
||||
impl LauncherGui {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
api_base: String::new(),
|
||||
integration_code: String::new(),
|
||||
input_path: String::new(),
|
||||
output_path: String::new(),
|
||||
extract_to_self: false,
|
||||
status: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pack(&mut self) -> Result<(), String> {
|
||||
let api_base = normalize_api_base(&self.api_base)?;
|
||||
let (project_id, project_key) = decode_integration_code(&self.integration_code)?;
|
||||
|
||||
let input = PathBuf::from(self.input_path.trim());
|
||||
if !input.exists() {
|
||||
return Err("未选择可执行文件".to_string());
|
||||
}
|
||||
|
||||
let output = if self.output_path.trim().is_empty() {
|
||||
derive_output_path(&input)?
|
||||
} else {
|
||||
PathBuf::from(self.output_path.trim())
|
||||
};
|
||||
|
||||
if input == output {
|
||||
return Err("输出文件不能与输入文件相同".to_string());
|
||||
}
|
||||
|
||||
let exe_dir = env::current_exe()
|
||||
.map_err(|e| format!("无法定位程序目录: {e}"))?
|
||||
.parent()
|
||||
.unwrap_or(Path::new("."))
|
||||
.to_path_buf();
|
||||
let stub_path = exe_dir.join("launcher_stub.exe");
|
||||
if !stub_path.exists() {
|
||||
return Err("未找到 launcher_stub.exe,请放在同目录".to_string());
|
||||
}
|
||||
|
||||
let payload_name = input
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("payload.exe")
|
||||
.to_string();
|
||||
|
||||
let extract_to = if self.extract_to_self { "self" } else { "temp" };
|
||||
|
||||
let config = serde_json::json!({
|
||||
"api_base": api_base,
|
||||
"project_id": project_id,
|
||||
"project_secret": project_key,
|
||||
"license_file": "license.key",
|
||||
"request_timeout_sec": 10,
|
||||
"heartbeat_retries": 2,
|
||||
"heartbeat_retry_delay_sec": 5,
|
||||
"extract_to": extract_to,
|
||||
"keep_payload": false,
|
||||
"payload_name": payload_name
|
||||
});
|
||||
let config_bytes = serde_json::to_vec(&config)
|
||||
.map_err(|e| format!("配置序列化失败: {e}"))?;
|
||||
|
||||
pack_files(&stub_path, &input, &output, &config_bytes)?;
|
||||
self.output_path = output.to_string_lossy().to_string();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for LauncherGui {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
handle_drop(ctx, self);
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("授权打包器");
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.label("授权系统地址");
|
||||
ui.add(egui::TextEdit::singleline(&mut self.api_base).hint_text("http://118.145.218.2:39256"));
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.label("项目对接码");
|
||||
ui.add(egui::TextEdit::singleline(&mut self.integration_code).hint_text("LSC1.xxxxx"));
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.label("选择要打包的 EXE");
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::TextEdit::singleline(&mut self.input_path).desired_width(360.0));
|
||||
if ui.button("浏览").clicked() {
|
||||
if let Some(file) = rfd::FileDialog::new().add_filter("EXE", &["exe"]).pick_file() {
|
||||
self.input_path = file.to_string_lossy().to_string();
|
||||
if self.output_path.trim().is_empty() {
|
||||
if let Ok(path) = derive_output_path(&file) {
|
||||
self.output_path = path.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.label("输出文件");
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::TextEdit::singleline(&mut self.output_path).desired_width(360.0));
|
||||
if ui.button("另存为").clicked() {
|
||||
if let Some(file) = rfd::FileDialog::new().add_filter("EXE", &["exe"]).set_file_name("packed.exe").save_file() {
|
||||
self.output_path = file.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.checkbox(&mut self.extract_to_self, "解压到程序目录(需要本地资源时勾选)");
|
||||
|
||||
ui.add_space(10.0);
|
||||
if ui.button("开始打包").clicked() {
|
||||
match self.pack() {
|
||||
Ok(_) => self.status = "打包完成".to_string(),
|
||||
Err(err) => self.status = format!("打包失败: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.status.is_empty() {
|
||||
ui.add_space(8.0);
|
||||
ui.label(&self.status);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_drop(ctx: &egui::Context, app: &mut LauncherGui) {
|
||||
let dropped = ctx.input(|i| i.raw.dropped_files.clone());
|
||||
for file in dropped {
|
||||
if let Some(path) = file.path {
|
||||
if path.extension().and_then(|s| s.to_str()).map(|s| s.eq_ignore_ascii_case("exe")).unwrap_or(false) {
|
||||
app.input_path = path.to_string_lossy().to_string();
|
||||
if app.output_path.trim().is_empty() {
|
||||
if let Ok(out) = derive_output_path(&path) {
|
||||
app.output_path = out.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pack_files(stub_path: &Path, payload_path: &Path, output_path: &Path, config: &[u8]) -> Result<(), String> {
|
||||
let stub = fs::read(stub_path).map_err(|e| format!("读取 stub 失败: {e}"))?;
|
||||
let payload = fs::read(payload_path).map_err(|e| format!("读取 EXE 失败: {e}"))?;
|
||||
|
||||
let mut out = fs::File::create(output_path).map_err(|e| format!("创建输出失败: {e}"))?;
|
||||
out.write_all(&stub).map_err(|e| format!("写入 stub 失败: {e}"))?;
|
||||
out.write_all(&payload).map_err(|e| format!("写入 EXE 失败: {e}"))?;
|
||||
out.write_all(config).map_err(|e| format!("写入配置失败: {e}"))?;
|
||||
out.write_all(MAGIC).map_err(|e| format!("写入标识失败: {e}"))?;
|
||||
out.write_all(&(payload.len() as u64).to_le_bytes())
|
||||
.map_err(|e| format!("写入长度失败: {e}"))?;
|
||||
out.write_all(&(config.len() as u64).to_le_bytes())
|
||||
.map_err(|e| format!("写入长度失败: {e}"))?;
|
||||
out.flush().map_err(|e| format!("写入失败: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn normalize_api_base(value: &str) -> Result<String, String> {
|
||||
let trimmed = value.trim().trim_end_matches('/');
|
||||
if trimmed.is_empty() {
|
||||
return Err("请输入授权系统地址".to_string());
|
||||
}
|
||||
Ok(trimmed.to_string())
|
||||
}
|
||||
|
||||
fn derive_output_path(input: &Path) -> Result<PathBuf, String> {
|
||||
let parent = input.parent().unwrap_or(Path::new("."));
|
||||
let stem = input
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("app");
|
||||
let ext = input
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("exe");
|
||||
let name = format!("{stem}_packed.{ext}");
|
||||
Ok(parent.join(name))
|
||||
}
|
||||
|
||||
fn decode_integration_code(code: &str) -> Result<(String, String), String> {
|
||||
let trimmed = code.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("请输入对接码".to_string());
|
||||
}
|
||||
|
||||
if trimmed.contains('|') {
|
||||
return split_code(trimmed);
|
||||
}
|
||||
|
||||
let raw = trimmed.strip_prefix("LSC1.").unwrap_or(trimmed);
|
||||
let decoded = STANDARD.decode(raw).map_err(|_| "对接码格式错误".to_string())?;
|
||||
let decoded_str = String::from_utf8(decoded).map_err(|_| "对接码解析失败".to_string())?;
|
||||
split_code(&decoded_str)
|
||||
}
|
||||
|
||||
fn split_code(value: &str) -> Result<(String, String), String> {
|
||||
let mut parts = value.split('|');
|
||||
let project_id = parts.next().unwrap_or("").trim();
|
||||
let project_key = parts.next().unwrap_or("").trim();
|
||||
if project_id.is_empty() || project_key.is_empty() {
|
||||
return Err("对接码内容不完整".to_string());
|
||||
}
|
||||
Ok((project_id.to_string(), project_key.to_string()))
|
||||
}
|
||||
Reference in New Issue
Block a user