mirror of
https://git.neonteam.dev/amizing/robinsr.git
synced 2025-03-12 03:28:30 -04:00
feat(sdk): add automatic hotfix downloader
This commit is contained in:
parent
608775b428
commit
6c5ba8b6e4
@ -11,6 +11,7 @@ tokio = { version = "1.36.0", features = ["full"] }
|
|||||||
axum = "0.8.1"
|
axum = "0.8.1"
|
||||||
axum-server = "0.7.1"
|
axum-server = "0.7.1"
|
||||||
tower-http = "0.6.2"
|
tower-http = "0.6.2"
|
||||||
|
reqwest = "0.12.12"
|
||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|||||||
@ -9,6 +9,7 @@ tokio.workspace = true
|
|||||||
tower-http = { workspace = true, features = ["cors"] }
|
tower-http = { workspace = true, features = ["cors"] }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
axum-server.workspace = true
|
axum-server.workspace = true
|
||||||
|
reqwest.workspace = true
|
||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|||||||
@ -1,27 +1,127 @@
|
|||||||
use std::{collections::HashMap, sync::OnceLock};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use anyhow::{Result, anyhow};
|
||||||
|
use prost::Message as _;
|
||||||
|
use proto::Gateserver;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
const DEFAULT_VERSIONS: &str = include_str!("../../../versions.json");
|
const DEFAULT_VERSIONS: &str = include_str!("../../../versions.json");
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct VersionConfig {
|
pub struct VersionConfig {
|
||||||
pub asset_bundle_url: String,
|
pub asset_bundle_url: String,
|
||||||
pub ex_resource_url: String,
|
pub ex_resource_url: String,
|
||||||
pub lua_url: String,
|
pub lua_url: String,
|
||||||
// pub lua_version: String,
|
pub ifix_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn instance() -> &'static HashMap<String, VersionConfig> {
|
impl VersionConfig {
|
||||||
static INSTANCE: OnceLock<HashMap<String, VersionConfig>> = OnceLock::new();
|
const CNPROD_HOST: &str = "prod-gf-cn-dp01.bhsr.com";
|
||||||
INSTANCE.get_or_init(|| {
|
const CNBETA_HOST: &str = "beta-release01-cn.bhsr.com";
|
||||||
|
const OSPROD_HOST: &str = "prod-official-asia-dp01.starrails.com";
|
||||||
|
const OSBETA_HOST: &str = "beta-release01-asia.starrails.com";
|
||||||
|
|
||||||
|
const PROXY_HOST: &str = "proxy1.neonteam.dev"; // we used this because PS users usually have a proxy enabled
|
||||||
|
|
||||||
|
pub async fn load_hotfix() -> HashMap<String, VersionConfig> {
|
||||||
const CONFIG_PATH: &str = "versions.json";
|
const CONFIG_PATH: &str = "versions.json";
|
||||||
|
|
||||||
let data = std::fs::read_to_string(CONFIG_PATH).unwrap_or_else(|_| {
|
let data = match fs::read_to_string(CONFIG_PATH).await {
|
||||||
std::fs::write(CONFIG_PATH, DEFAULT_VERSIONS).expect("Failed to create versions file");
|
Ok(data) => data,
|
||||||
DEFAULT_VERSIONS.to_string()
|
Err(_) => {
|
||||||
});
|
fs::write(CONFIG_PATH, DEFAULT_VERSIONS)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create versions file");
|
||||||
|
DEFAULT_VERSIONS.to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
serde_json::from_str(&data).unwrap_or_else(|e| panic!("Failed to parse versions.json: {e}"))
|
match serde_json::from_str(&data) {
|
||||||
})
|
Ok(data) => data,
|
||||||
|
Err(_) => {
|
||||||
|
tracing::error!("malformed versions.json. replacing it with default one");
|
||||||
|
let _ = fs::write(CONFIG_PATH, DEFAULT_VERSIONS).await;
|
||||||
|
serde_json::from_str(DEFAULT_VERSIONS).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_hotfix(version: &str, dispatch_seed: &str) -> Result<VersionConfig> {
|
||||||
|
let (region, branch, _, _) = Self::parse_version_string(version)?;
|
||||||
|
|
||||||
|
let host = match (region.as_str(), branch.as_str()) {
|
||||||
|
("OS", "BETA") => Self::OSBETA_HOST,
|
||||||
|
("OS", "PROD") => Self::OSPROD_HOST,
|
||||||
|
("CN", "BETA") => Self::CNBETA_HOST,
|
||||||
|
("CN", "PROD") => Self::CNPROD_HOST,
|
||||||
|
// TODO: Support more host, or use query_dispatch result to determine the urls
|
||||||
|
_ => Self::OSBETA_HOST,
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = format!(
|
||||||
|
"https://{}/{host}/query_gateway?version={}&platform_type=1&language_type=3&dispatch_seed={}&channel_id=1&sub_channel_id=1&is_need_url=1",
|
||||||
|
Self::PROXY_HOST,
|
||||||
|
version,
|
||||||
|
dispatch_seed
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("fetching hotfix: {url}");
|
||||||
|
|
||||||
|
let res = reqwest::get(url).await?.text().await?;
|
||||||
|
|
||||||
|
tracing::info!("raw gateway response: {}", res);
|
||||||
|
|
||||||
|
let bytes = rbase64::decode(&res)?;
|
||||||
|
let decoded = Gateserver::decode(bytes.as_slice())?;
|
||||||
|
|
||||||
|
if decoded.retcode != 0 || res.is_empty() {
|
||||||
|
return Err(anyhow::format_err!(
|
||||||
|
"gateway result code: {} message: {}",
|
||||||
|
decoded.retcode,
|
||||||
|
decoded.msg
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("{:#?}", decoded);
|
||||||
|
|
||||||
|
Ok(VersionConfig {
|
||||||
|
asset_bundle_url: decoded.asset_bundle_url,
|
||||||
|
ex_resource_url: decoded.ex_resource_url,
|
||||||
|
lua_url: decoded.lua_url,
|
||||||
|
ifix_url: decoded.ifix_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_version_string(s: &str) -> Result<(String, String, String, String)> {
|
||||||
|
const BRANCHES: [&str; 7] = ["PREbeta", "BETA", "PROD", "DEV", "PRE", "GM", "CECREATION"];
|
||||||
|
const OS: [&str; 3] = ["Android", "Win", "iOS"];
|
||||||
|
|
||||||
|
let region = s
|
||||||
|
.get(0..2)
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid region url: {s}"))?;
|
||||||
|
let after_region = s.get(2..).unwrap_or(s);
|
||||||
|
|
||||||
|
let branch = BRANCHES
|
||||||
|
.iter()
|
||||||
|
.find(|&&b| after_region.starts_with(b))
|
||||||
|
.map(|&b| b.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid branch url: {s}"))?;
|
||||||
|
|
||||||
|
let branch_len = branch.len();
|
||||||
|
let after_branch = after_region.get(branch_len..).unwrap_or(after_region);
|
||||||
|
|
||||||
|
let os = OS
|
||||||
|
.iter()
|
||||||
|
.find(|&&o| after_branch.starts_with(o))
|
||||||
|
.map(|&o| o.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid os url: {s}"))?;
|
||||||
|
|
||||||
|
let os_len = os.len();
|
||||||
|
let version = after_branch
|
||||||
|
.get(os_len..)
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid version url: {s}"))?;
|
||||||
|
|
||||||
|
Ok((region.to_string(), branch, os, version.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::http::Method;
|
use axum::http::Method;
|
||||||
use axum::http::header::CONTENT_TYPE;
|
use axum::http::header::CONTENT_TYPE;
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
|
use config::version_config::VersionConfig;
|
||||||
use services::{auth, dispatch, errors, sr_tools};
|
use services::{auth, dispatch, errors, sr_tools};
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
@ -12,9 +18,51 @@ mod services;
|
|||||||
|
|
||||||
const PORT: u16 = 21000;
|
const PORT: u16 = 21000;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
hotfix_map: HashMap<String, VersionConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
async fn get_or_insert_hotfix(&mut self, version: &str, dispatch_seed: &str) -> &VersionConfig {
|
||||||
|
if self.hotfix_map.contains_key(version) {
|
||||||
|
return &self.hotfix_map[version];
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"trying to fetch hotfix for version {version} with dispatch seed {dispatch_seed}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let hotfix = match VersionConfig::fetch_hotfix(version, dispatch_seed).await {
|
||||||
|
Ok(hotfix) => hotfix,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("failed to fetch hotfix. reason: {err}");
|
||||||
|
VersionConfig::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.hotfix_map.insert(version.to_string(), hotfix);
|
||||||
|
|
||||||
|
if let Ok(serialized) = serde_json::to_string_pretty(&self.hotfix_map) {
|
||||||
|
let _ = fs::write("versions.json", serialized).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
&self.hotfix_map[version]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start_sdkserver() -> Result<()> {
|
pub async fn start_sdkserver() -> Result<()> {
|
||||||
let span = tracing::span!(Level::DEBUG, "main");
|
let span = tracing::span!(Level::DEBUG, "main");
|
||||||
let _ = span.enter();
|
let _ = span.enter();
|
||||||
|
let hotfix_map = VersionConfig::load_hotfix().await;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"loaded {} hotfix versions. supported versions: {:?}",
|
||||||
|
hotfix_map.len(),
|
||||||
|
hotfix_map.keys()
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = Arc::new(RwLock::new(AppState { hotfix_map }));
|
||||||
|
|
||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
.route(
|
.route(
|
||||||
@ -48,6 +96,7 @@ pub async fn start_sdkserver() -> Result<()> {
|
|||||||
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
||||||
.allow_headers([CONTENT_TYPE]),
|
.allow_headers([CONTENT_TYPE]),
|
||||||
)
|
)
|
||||||
|
.with_state(state)
|
||||||
.fallback(errors::not_found);
|
.fallback(errors::not_found);
|
||||||
|
|
||||||
let addr = format!("0.0.0.0:{PORT}");
|
let addr = format!("0.0.0.0:{PORT}");
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
use crate::config::version_config;
|
use std::sync::Arc;
|
||||||
use axum::extract::Query;
|
|
||||||
|
use crate::AppState;
|
||||||
|
use axum::extract::{Query, State};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use proto::{DispatchRegionData, Gateserver, RegionEntry};
|
use proto::{DispatchRegionData, Gateserver, RegionEntry};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch";
|
pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch";
|
||||||
pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway";
|
pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway";
|
||||||
@ -30,37 +34,38 @@ pub async fn query_dispatch() -> String {
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct QueryGatewayParameters {
|
pub struct QueryGatewayParameters {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
pub dispatch_seed: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[instrument(skip(state))]
|
||||||
pub async fn query_gateway(parameters: Query<QueryGatewayParameters>) -> String {
|
pub async fn query_gateway(
|
||||||
let rsp = if let Some(config) = version_config::instance().get(¶meters.version) {
|
State(state): State<Arc<RwLock<AppState>>>,
|
||||||
Gateserver {
|
parameters: Query<QueryGatewayParameters>,
|
||||||
retcode: 0,
|
) -> String {
|
||||||
ip: String::from("127.0.0.1"),
|
let mut lock = state.write().await;
|
||||||
port: 23301,
|
let config = lock
|
||||||
asset_bundle_url: config.asset_bundle_url.clone(),
|
.get_or_insert_hotfix(¶meters.version, ¶meters.dispatch_seed)
|
||||||
ex_resource_url: config.ex_resource_url.clone(),
|
.await;
|
||||||
lua_url: config.lua_url.clone(),
|
|
||||||
ifix_version: String::from("0"),
|
let rsp = Gateserver {
|
||||||
enable_design_data_version_update: true,
|
retcode: 0,
|
||||||
enable_version_update: true,
|
ip: String::from("127.0.0.1"),
|
||||||
enable_upload_battle_log: true,
|
port: 23301,
|
||||||
network_diagnostic: true,
|
asset_bundle_url: config.asset_bundle_url.clone(),
|
||||||
close_redeem_code: true,
|
ex_resource_url: config.ex_resource_url.clone(),
|
||||||
enable_android_middle_package: true,
|
lua_url: config.lua_url.clone(),
|
||||||
enable_watermark: true,
|
ifix_version: String::from("0"),
|
||||||
event_tracking_open: true,
|
enable_design_data_version_update: true,
|
||||||
enable_cdn_ipv6: 1,
|
enable_version_update: true,
|
||||||
enable_save_replay_file: true,
|
enable_upload_battle_log: true,
|
||||||
..Default::default()
|
network_diagnostic: true,
|
||||||
}
|
close_redeem_code: true,
|
||||||
} else {
|
enable_android_middle_package: true,
|
||||||
Gateserver {
|
enable_watermark: true,
|
||||||
retcode: 9,
|
event_tracking_open: true,
|
||||||
login_white_msg: format!("forbidden version: {} or invalid bind", parameters.version),
|
enable_cdn_ipv6: 1,
|
||||||
..Default::default()
|
enable_save_replay_file: true,
|
||||||
}
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buff = Vec::new();
|
let mut buff = Vec::new();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user