Skip to main content

minecraft_java_rs_core/launcher/
game_data.rs

1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    error::LaunchError,
7    models::{
8        java::JavaFileItem,
9        loader::LoaderType,
10        minecraft::{AssetItem, MinecraftVersionJson},
11    },
12};
13
14// ── GameData ──────────────────────────────────────────────────────────────────
15
16#[derive(Debug, Clone, Deserialize, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct GameData {
19    pub minecraft_json: MinecraftVersionJson,
20    /// Loader profile merged on top of the base version JSON (raw JSON value
21    /// to avoid coupling game_data to a specific loader type).
22    #[serde(default)]
23    pub minecraft_loader: Option<serde_json::Value>,
24    pub minecraft_version: String,
25    pub minecraft_java: JavaInfo,
26    /// Extra library JARs added by the mod loader; included in the classpath.
27    #[serde(default)]
28    pub loader_libraries: Vec<AssetItem>,
29    /// Main class override set by the mod loader.
30    #[serde(default)]
31    pub loader_main_class: Option<String>,
32    /// Loader version id, e.g. `"1.20.4-forge-47.4.20"`.
33    #[serde(default)]
34    pub loader_version_id: Option<String>,
35    /// Which loader type was installed (used for Forge-specific JVM flags).
36    #[serde(default)]
37    pub loader_type: Option<LoaderType>,
38    /// Extra plain-string game args contributed by the loader
39    /// (from `minecraftArguments` or `arguments.game` in the loader JSON).
40    #[serde(default)]
41    pub loader_extra_game_args: Vec<String>,
42    /// Extra JVM args from the loader version JSON (`arguments.jvm`), pre-resolved.
43    #[serde(default)]
44    pub loader_extra_jvm_args: Vec<String>,
45}
46
47#[derive(Debug, Clone, Deserialize, Serialize)]
48pub struct JavaInfo {
49    pub files: Vec<JavaFileItem>,
50    pub path: String,
51}
52
53// ── Persistence ───────────────────────────────────────────────────────────────
54
55const GAME_DATA_FILE: &str = "gameData.json";
56
57pub async fn load_game_data(dir: &Path) -> Result<GameData, LaunchError> {
58    let path = dir.join(GAME_DATA_FILE);
59    let raw = tokio::fs::read_to_string(&path).await?;
60    let data = serde_json::from_str(&raw)?;
61    Ok(data)
62}
63
64pub async fn save_game_data(dir: &Path, data: &GameData) -> Result<(), LaunchError> {
65    tokio::fs::create_dir_all(dir).await?;
66    let json = serde_json::to_string_pretty(data)?;
67    tokio::fs::write(dir.join(GAME_DATA_FILE), json).await?;
68    Ok(())
69}
70
71// ── Tests ─────────────────────────────────────────────────────────────────────
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use tempfile::TempDir;
77
78    fn make_game_data() -> GameData {
79        use crate::models::minecraft::{
80            AssetIndexRef, DownloadArtifact, MinecraftVersionJson, VersionDownloads,
81        };
82        GameData {
83            minecraft_version: "1.20.4".into(),
84            minecraft_loader: None,
85            minecraft_java: JavaInfo {
86                files: vec![],
87                path: "/usr/bin/java".into(),
88            },
89            loader_libraries: vec![],
90            loader_main_class: None,
91            loader_version_id: None,
92            loader_type: None,
93            loader_extra_game_args: vec![],
94            loader_extra_jvm_args: vec![],
95            minecraft_json: MinecraftVersionJson {
96                id: "1.20.4".into(),
97                version_type: "release".into(),
98                assets: Some("16".into()),
99                asset_index: Some(AssetIndexRef {
100                    id: "16".into(),
101                    url: "https://example.com/16.json".into(),
102                    sha1: "abc123".into(),
103                    size: 0,
104                    total_size: None,
105                }),
106                downloads: Some(VersionDownloads {
107                    client: DownloadArtifact {
108                        url: "https://example.com/client.jar".into(),
109                        sha1: "def456".into(),
110                        size: 0,
111                    },
112                    server: None,
113                    client_mappings: None,
114                    server_mappings: None,
115                }),
116                libraries: vec![],
117                arguments: None,
118                minecraft_arguments: None,
119                java_version: None,
120                main_class: Some("net.minecraft.client.main.Main".into()),
121                has_natives: false,
122            },
123        }
124    }
125
126    #[tokio::test]
127    async fn round_trip_game_data() {
128        let dir = TempDir::new().unwrap();
129        let original = make_game_data();
130        save_game_data(dir.path(), &original).await.unwrap();
131        let loaded = load_game_data(dir.path()).await.unwrap();
132        assert_eq!(loaded.minecraft_version, "1.20.4");
133        assert_eq!(loaded.minecraft_java.path, "/usr/bin/java");
134        assert!(loaded.minecraft_loader.is_none());
135    }
136
137    #[tokio::test]
138    async fn load_missing_file_returns_error() {
139        let dir = TempDir::new().unwrap();
140        let result = load_game_data(dir.path()).await;
141        assert!(result.is_err());
142    }
143
144    #[tokio::test]
145    async fn save_creates_missing_dir() {
146        let dir = TempDir::new().unwrap();
147        let nested = dir.path().join("a").join("b").join("c");
148        let data = make_game_data();
149        save_game_data(&nested, &data).await.unwrap();
150        assert!(nested.join("gameData.json").exists());
151    }
152}