Files
OpenFUT-Bridge/src/capture.rs
T
funman300 a826e5f7d3 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>
2026-06-25 15:09:47 -07:00

92 lines
2.5 KiB
Rust

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)
}