Initial commit: OpenFUT Bridge
FIFA 23 reverse-engineering proxy and integration scaffold. - Catch-all HTTP proxy that captures all incoming FIFA 23 traffic - Known-route mapper (speculative FUT paths → Core API calls) - Placeholder JSON responses for unmapped endpoints - Admin endpoints: GET /_bridge/captures, GET /_bridge/unknown - Capture persistence to captures/*.json for RE analysis - 4 unit tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A captured HTTP request from the FIFA 23 client.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CapturedRequest {
|
||||
pub id: String,
|
||||
pub timestamp: String,
|
||||
pub method: String,
|
||||
pub path: String,
|
||||
pub query: Option<String>,
|
||||
pub headers: Vec<(String, String)>,
|
||||
pub body: Option<String>,
|
||||
pub response_status: Option<u16>,
|
||||
pub response_body: Option<String>,
|
||||
pub mapped_to_core: Option<String>,
|
||||
}
|
||||
|
||||
impl CapturedRequest {
|
||||
pub fn new(
|
||||
method: &str,
|
||||
path: &str,
|
||||
query: Option<&str>,
|
||||
headers: Vec<(String, String)>,
|
||||
body: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
timestamp: Utc::now().to_rfc3339(),
|
||||
method: method.to_string(),
|
||||
path: path.to_string(),
|
||||
query: query.map(String::from),
|
||||
headers,
|
||||
body,
|
||||
response_status: None,
|
||||
response_body: None,
|
||||
mapped_to_core: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_response(mut self, status: u16, body: Option<String>) -> Self {
|
||||
self.response_status = Some(status);
|
||||
self.response_body = body;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Persist a capture to disk as JSON.
|
||||
pub fn save_capture(captures_dir: &str, capture: &CapturedRequest) -> anyhow::Result<()> {
|
||||
let dir = Path::new(captures_dir);
|
||||
std::fs::create_dir_all(dir)?;
|
||||
|
||||
let filename = format!(
|
||||
"{}_{}_{}.json",
|
||||
capture.timestamp.replace(':', "-"),
|
||||
capture.method,
|
||||
capture.id
|
||||
);
|
||||
let path = dir.join(filename);
|
||||
|
||||
let json = serde_json::to_string_pretty(capture)?;
|
||||
std::fs::write(&path, json)?;
|
||||
|
||||
tracing::debug!("Capture saved: {:?}", path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load all captures from the captures directory.
|
||||
pub fn load_all_captures(captures_dir: &str) -> anyhow::Result<Vec<CapturedRequest>> {
|
||||
let dir = Path::new(captures_dir);
|
||||
if !dir.exists() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut captures = Vec::new();
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.extension().map(|e| e == "json").unwrap_or(false) {
|
||||
let content = std::fs::read_to_string(&path)?;
|
||||
if let Ok(capture) = serde_json::from_str::<CapturedRequest>(&content) {
|
||||
captures.push(capture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
captures.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
||||
Ok(captures)
|
||||
}
|
||||
Reference in New Issue
Block a user