use crate::error::{Result, LateJavaCoreError};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct MinecraftAssetsOptions {
pub path: String,
pub instance: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionJson {
pub asset_index: Option<AssetIndex>,
pub assets: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetIndex {
pub id: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetItem {
pub r#type: AssetType,
pub path: String,
pub content: Option<String>,
pub sha1: Option<String>,
pub size: Option<u64>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AssetType {
#[serde(rename = "CFILE")]
CFile,
#[serde(rename = "Assets")]
Assets,
}
pub struct MinecraftAssets {
asset_index: Option<AssetIndex>,
options: MinecraftAssetsOptions,
}
impl MinecraftAssets {
pub fn new(options: MinecraftAssetsOptions) -> Self {
Self {
asset_index: None,
options,
}
}
pub async fn get_assets(&mut self, version_json: &VersionJson) -> Result<Vec<AssetItem>> {
self.asset_index = version_json.asset_index.clone();
if self.asset_index.is_none() {
return Ok(Vec::new());
}
let asset_index = self.asset_index.as_ref().unwrap();
let response = reqwest::get(&asset_index.url).await?;
let data: serde_json::Value = response.json().await?;
let mut assets_array = Vec::new();
assets_array.push(AssetItem {
r#type: AssetType::CFile,
path: format!("assets/indexes/{}.json", asset_index.id),
content: Some(serde_json::to_string(&data)?),
sha1: None,
size: None,
url: None,
});
if let Some(objects) = data.get("objects").and_then(|o| o.as_object()) {
for (_, obj) in objects {
if let Some(obj_map) = obj.as_object() {
if let (Some(hash), Some(size)) = (
obj_map.get("hash").and_then(|h| h.as_str()),
obj_map.get("size").and_then(|s| s.as_u64())
) {
assets_array.push(AssetItem {
r#type: AssetType::Assets,
sha1: Some(hash.to_string()),
size: Some(size),
path: format!("assets/objects/{}/{}", &hash[0..2], hash),
url: Some(format!("https://resources.download.minecraft.net/{}/{}", &hash[0..2], hash)),
});
}
}
}
}
Ok(assets_array)
}
pub fn copy_assets(&self, version_json: &VersionJson) -> Result<()> {
let assets = version_json.assets.as_ref()
.ok_or_else(|| LateJavaCoreError::Minecraft("No assets field in version JSON".to_string()))?;
let legacy_directory = if let Some(instance) = &self.options.instance {
format!("{}/instances/{}/resources", self.options.path, instance)
} else {
format!("{}/resources", self.options.path)
};
let path_assets = format!("{}/assets/indexes/{}.json", self.options.path, assets);
if !Path::new(&path_assets).exists() {
return Ok(()); }
let assets_content = std::fs::read_to_string(&path_assets)?;
let assets_data: serde_json::Value = serde_json::from_str(&assets_content)?;
if let Some(objects) = assets_data.get("objects").and_then(|o| o.as_object()) {
for (file_path, hash_data) in objects {
if let Some(hash_obj) = hash_data.as_object() {
if let Some(full_hash) = hash_obj.get("hash").and_then(|h| h.as_str()) {
let sub_hash = &full_hash[0..2];
let sub_asset_dir = format!("{}/assets/objects/{}", self.options.path, sub_hash);
let path_segments: Vec<&str> = file_path.split('/').collect();
if path_segments.len() > 1 {
let dir_path = path_segments[0..path_segments.len()-1].join("/");
let full_dir_path = format!("{}/{}", legacy_directory, dir_path);
std::fs::create_dir_all(&full_dir_path)?;
}
let source_file = format!("{}/{}", sub_asset_dir, full_hash);
let target_file = format!("{}/{}", legacy_directory, file_path);
if !Path::new(&target_file).exists() {
std::fs::copy(&source_file, &target_file)?;
}
}
}
}
}
Ok(())
}
}