use jzon::{array, object, JsonValue}; use actix_web::{ HttpResponse, http::header::{HeaderValue, HeaderMap} }; use std::time::{SystemTime, UNIX_EPOCH}; use base64::{Engine as _, engine::general_purpose}; use uuid::Uuid; use crate::encryption; use crate::router::{userdata, items}; use crate::database::gree; use crate::runtime::get_easter_mode; struct AssetHashes { version_android: &'static str, version_ios: &'static str, version_windows: &'static str, android: &'static str, ios: &'static str, windows: &'static str, } static ASSET_TABLE: &[(&str, AssetHashes)] = &[ ("JP", AssetHashes { version_android: "4c921d2443335e574a82e04ec9ea243c", version_ios: "4c921d2443335e574a82e04ec9ea243c", version_windows: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", android: "67f8f261c16b3cca63e520a25aad6c1c", ios: "b8975be8300013a168d061d3fdcd4a16", windows: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }), ("GL", AssetHashes { version_android: "5260ff15dff8ba0c00ad91400f515f55", version_ios: "5260ff15dff8ba0c00ad91400f515f55", version_windows: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", android: "d210b28037885f3ef56b8f8aa45ac95b", ios: "dd7175e4bcdab476f38c33c7f34b5e4d", windows: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }), ]; impl AssetHashes { fn resolve(&self, platform: &str, region: &str, easter_hash: Option<&str>) -> Option { let args = crate::get_args(); let (base, override_hash) = match (region, platform) { ("JP", "Android") => (self.android, args.jp_android_asset_hash.as_str()), ("JP", "iOS") => (self.ios, args.jp_ios_asset_hash.as_str()), ("JP", "Windows") => (self.windows, args.windows_asset_hash.as_str()), ("GL", "Android") => (self.android, args.en_android_asset_hash.as_str()), ("GL", "iOS") => (self.ios, args.en_ios_asset_hash.as_str()), _ => return None, }; Some(if !override_hash.is_empty() { override_hash } else if let Some(easter) = easter_hash { easter } else { base }.to_string()) } fn version_for(&self, platform: &str, region: &str) -> Option { let args = crate::get_args(); let (base, override_version) = match (region, platform) { ("JP", "Android") => (self.version_android, ""), ("JP", "iOS") => (self.version_ios, ""), ("JP", "Windows") => (self.version_windows, args.windows_asset_version.as_str()), ("GL", "Android") => (self.version_android, ""), ("GL", "iOS") => (self.version_ios, ""), ("GL", "Windows") => (self.version_windows, ""), _ => return None, }; Some(if !override_version.is_empty() { override_version } else { base }.to_string()) } } static EASTER_HASHES: &[(&str, &str)] = &[ ("JP", "eac0cad61c82bf2e31fc596555747d11"), ("GL", "da7ae831381c3f29337caa9891db7e6a"), ]; pub fn get_player_region(asset_version: &str) -> Option { ASSET_TABLE .iter() .find(|(_, h)| { h.version_android == asset_version || h.version_ios == asset_version || h.version_windows == asset_version }) .map(|(region, _)| region.to_string()) } pub fn parse_platform(header: &str) -> &str { let platform = header.split_whitespace().next().unwrap_or("").to_lowercase(); match platform.as_str() { "android" => "Android", "ios" => "iOS", "windows" => "Windows", "windowsplayer" => "Windows", _ => "Android", } } pub fn get_asset_hash(asset_version: &str, platform: &str) -> Option { let easter = get_easter_mode(); let (region, hashes) = ASSET_TABLE .iter() .find(|(region, h)| h.version_for(platform, region).as_deref() == Some(asset_version))?; let easter_hash = easter .then(|| EASTER_HASHES.iter().find(|(r, _)| r == region).map(|(_, h)| *h)) .flatten(); hashes.resolve(platform, region, easter_hash) } pub fn create_token() -> String { format!("{}", Uuid::now_v7()) } fn get_uuid(input: &str) -> Option { let key = "sk1bdzb310n0s9tl"; let key_index = match input.find(key) { Some(index) => index + key.len(), None => return None, }; let after = &input[key_index..]; let uuid_length = 36; if after.len() >= uuid_length { let uuid = &after[..uuid_length]; return Some(uuid.to_string()); } None } pub fn get_login(headers: &HeaderMap, body: &str) -> String { let blank_header = HeaderValue::from_static(""); let login = headers.get("a6573cbe").unwrap_or(&blank_header).to_str().unwrap_or(""); let decoded = general_purpose::STANDARD.decode(login).unwrap_or_default(); match get_uuid(String::from_utf8_lossy(&decoded).as_ref()) { Some(token) => { token }, None => { let rv = gree::get_uuid(headers, body); assert!(rv != String::new()); rv }, } } pub fn timestamp() -> u64 { let now = SystemTime::now(); let unix_timestamp = now.duration_since(UNIX_EPOCH).unwrap(); unix_timestamp.as_secs() } pub fn timestamp_msec() -> u32 { let now = SystemTime::now(); let unix_timestamp = now.duration_since(UNIX_EPOCH).unwrap(); unix_timestamp.subsec_nanos() } pub fn timestamp_since_midnight() -> u64 { let now = SystemTime::now(); let unix_timestamp = now.duration_since(UNIX_EPOCH).unwrap(); let midnight = unix_timestamp.as_secs() % (24 * 60 * 60); unix_timestamp.as_secs() - midnight } fn init_time(current_time: u64, server_data: &mut JsonValue, token: &str, max_time: u64, max: bool) { let mut edited = false; let default_time = 1709272800; if max_time > 10 && max_time < current_time && server_data["server_time"].as_u64().unwrap_or(0) < max_time && max { server_data["server_time_set"] = timestamp().into(); edited = true; } if server_data["server_time_set"].as_u64().is_none() { server_data["server_time_set"] = timestamp().into(); edited = true; } if server_data["server_time"].as_u64().is_none() { server_data["server_time"] = default_time.into(); edited = true; } if edited && max { userdata::save_server_data(token, server_data.clone()); } } pub fn set_time(current_time: u64, uid: i64, max: bool) -> u64 { let max_time = crate::get_args().max_time; if uid == 0 { if max_time > 10 && max_time < current_time { return max_time; } else { return timestamp(); } } let token = userdata::get_login_token(uid); let mut server_data = userdata::get_server_data(&token); init_time(current_time, &mut server_data, &token, max_time, max); let time_set = server_data["server_time_set"].as_u64().unwrap_or(timestamp()); let server_time = server_data["server_time"].as_u64().unwrap_or(0);//1711741114 if server_time == 0 { return current_time; } let time_since_set = if current_time > time_set { current_time - time_set } else { 0 }; return server_time + time_since_set; } pub fn send(mut data: JsonValue, uid: i64, headers: &HeaderMap) -> HttpResponse { //println!("{}", jzon::stringify(data.clone())); data["server_time"] = set_time(data["server_time"].as_u64().unwrap_or(0), uid, true).into(); if !data["data"]["item_list"].is_empty() || !data["data"]["updated_value_list"]["item_list"].is_empty() { items::check_for_region(&mut data, headers); } let encrypted = encryption::encrypt_packet(&jzon::stringify(data)).unwrap(); let resp = encrypted.into_bytes(); HttpResponse::Ok().body(resp) } pub fn start_login_bonus(id: i64, bonus: &mut JsonValue) -> bool { if crate::router::login::get_login_bonus_info(id).is_empty() { return false; } for dataa in bonus["bonus_list"].members() { if dataa["master_login_bonus_id"].as_i64().unwrap() == id { return false; } } bonus["bonus_list"].push(object!{ master_login_bonus_id: id, day_counts: [], event_bonus_list: [] }).unwrap(); true } pub fn get_card(id: i64, user: &JsonValue) -> JsonValue { if id == 0 { return object!{}; } for data in user["card_list"].members() { if data["master_card_id"].as_i64().unwrap_or(0) == id { return data.clone(); } } object!{} } fn get_cards(arr: JsonValue, user: &JsonValue) -> JsonValue { let mut rv = array![]; for data in arr.members() { let to_push = get_card(data.as_i64().unwrap_or(0), user); if to_push.is_empty() { continue; } rv.push(to_push).unwrap(); } rv } fn get_clear_count(user: &JsonValue, level: i32) -> i64 { let mut rv = 0; for current in user["live_list"].members() { if current["level"] == level { rv += 1; } } rv } fn get_full_combo_count(user: &JsonValue, level: i32) -> i64 { let mut rv = 0; for current in user["live_mission_list"].members() { if current["clear_master_live_mission_ids"].contains(20 + level) { rv += 1; } } rv } fn get_perfect_count(user: &JsonValue, level: i32) -> i64 { let mut rv = 0; for current in user["live_mission_list"].members() { if current["clear_master_live_mission_ids"].contains(40 + level) { rv += 1; } } rv } pub fn get_user(id: i64, friends: &JsonValue, live_data: bool) -> JsonValue { let user = userdata::get_acc_from_uid(id); if !user["error"].is_empty() { return object!{}; } let mut rv = object!{ user: user["user"].clone(), main_deck_detail: { total_power: 0, //how to calculate? deck: user["deck_list"][user["user"]["main_deck_slot"].as_usize().unwrap_or(1) - 1].clone(), card_list: get_cards(user["deck_list"][user["user"]["main_deck_slot"].as_usize().unwrap_or(1) - 1]["main_card_ids"].clone(), &user) }, favorite_card: get_card(user["user"]["favorite_master_card_id"].as_i64().unwrap_or(0), &user), guest_smile_card: get_card(user["user"]["guest_smile_master_card_id"].as_i64().unwrap_or(0), &user), guest_cool_card: get_card(user["user"]["guest_cool_master_card_id"].as_i64().unwrap_or(0), &user), guest_pure_card: get_card(user["user"]["guest_pure_master_card_id"].as_i64().unwrap_or(0), &user), master_title_ids: user["user"]["master_title_ids"].clone() }; if live_data { rv["live_data_summary"] = object!{ clear_count_list: [get_clear_count(&user, 1), get_clear_count(&user, 2), get_clear_count(&user, 3), get_clear_count(&user, 4)], full_combo_list: [get_full_combo_count(&user, 1), get_full_combo_count(&user, 2), get_full_combo_count(&user, 3), get_full_combo_count(&user, 4)], all_perfect_list: [get_perfect_count(&user, 1), get_perfect_count(&user, 2), get_perfect_count(&user, 3), get_perfect_count(&user, 4)], high_score_rate: { rate: 0, detail: [] } }; } rv["user"].remove("sif_user_id"); rv["user"].remove("ss_user_id"); rv["user"].remove("birthday"); if !friends.is_empty() { rv["status"] = if friends["friend_user_id_list"].contains(id) { 3 } else if friends["pending_user_id_list"].contains(id) { 2 } else if friends["request_user_id_list"].contains(id) { 1 } else { 0 }.into(); } rv }