scl_core/version/
mod.rs

1//! 游戏版本的解析
2
3use std::path::Path;
4
5pub mod mods;
6pub mod structs;
7
8use inner_future::stream::StreamExt;
9
10use self::structs::VersionInfo;
11use crate::prelude::*;
12
13/// 一个游戏版本的信息
14pub struct Version {
15    /// 版本名称,通常和其文件夹同名
16    pub name: String,
17    /// 版本类型,一般通过 [`crate::version::structs::VersionInfo::guess_version_type`] 猜测出,用于展示版本类型和判断是否需要版本独立
18    pub version_type: VersionType,
19    /// 版本的创建日期,实际是该文件夹的创建日期
20    pub created_date: std::time::SystemTime,
21    /// 版本的上一次游玩日期,实际是该文件夹的上一次访问日期
22    pub access_date: std::time::SystemTime,
23}
24
25impl Default for Version {
26    fn default() -> Self {
27        Self {
28            name: String::new(),
29            version_type: VersionType::Unknown,
30            created_date: std::time::SystemTime::UNIX_EPOCH,
31            access_date: std::time::SystemTime::UNIX_EPOCH,
32        }
33    }
34}
35
36/// 通过指定的版本文件夹,搜索所有可启动的游戏版本
37pub async fn get_avaliable_versions(
38    version_directory_path: impl AsRef<Path>,
39) -> DynResult<Vec<Version>> {
40    let mut version_info = VersionInfo {
41        version_base: version_directory_path
42            .as_ref()
43            .to_string_lossy()
44            .to_string(),
45        ..Default::default()
46    };
47    if version_directory_path.as_ref().is_dir() {
48        let mut entries = inner_future::fs::read_dir(version_directory_path.as_ref()).await?;
49        let mut result = Vec::with_capacity(32);
50        while let Some(entry) = entries.try_next().await? {
51            let (created_date, access_date) = if let Ok(metadata) = entry.metadata().await {
52                (
53                    metadata
54                        .created()
55                        .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
56                    metadata
57                        .accessed()
58                        .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
59                )
60            } else {
61                (
62                    std::time::SystemTime::UNIX_EPOCH,
63                    std::time::SystemTime::UNIX_EPOCH,
64                )
65            };
66            if entry.file_type().await?.is_dir() {
67                let entry = entry
68                    .path()
69                    .file_name()
70                    .unwrap()
71                    .to_str()
72                    .unwrap()
73                    .to_string();
74                version_info.version = entry.to_owned();
75                if version_info.load().await.is_ok() {
76                    result.push(Version {
77                        name: entry,
78                        version_type: version_info.guess_version_type(),
79                        created_date,
80                        access_date,
81                    });
82                } else {
83                    result.push(Version {
84                        name: entry,
85                        version_type: VersionType::Unknown,
86                        created_date,
87                        access_date,
88                    });
89                }
90            }
91        }
92        Ok(result)
93    } else {
94        Ok(vec![])
95    }
96}
97
98/// 版本类型
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum VersionType {
101    /// 纯净版本
102    Vanilla,
103    /// Forge 版本
104    Forge,
105    /// Fabric 版本
106    Fabric,
107    /// QuiltMC 版本
108    QuiltMC,
109    /// Optifine 画质增强版本
110    ///
111    /// 如果其是通过其它模组加载器加载的(Forge 或 Fabric),则优先为模组加载器版本
112    Optifine,
113    /// 未知版本
114    Unknown,
115}
116
117impl Default for VersionType {
118    fn default() -> Self {
119        Self::Unknown
120    }
121}
122
123/// 当解析出错时,此处为错误枚举值
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum LoadVersionInfoError {
126    /// 没有提供游戏版本文件夹路径或不存在
127    VersionBaseMissing,
128    /// 没有提供游戏本体文件路径或不存在
129    MainJarFileMissing,
130    /// 没有提供版本元数据文件路径或不存在
131    VersionFileMissing,
132    /// 无法打开版本元数据文件
133    VersionFileOpenFail,
134    /// 无法打开启动器配置文件
135    SCLConfigFileOpenFail,
136    /// 元数据解析错误,内容有误
137    ParseError(String),
138}