1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! 游戏版本的解析

use std::path::Path;

pub mod mods;
pub mod structs;

use inner_future::stream::StreamExt;

use self::structs::VersionInfo;
use crate::prelude::*;

/// 一个游戏版本的信息
pub struct Version {
    /// 版本名称,通常和其文件夹同名
    pub name: String,
    /// 版本类型,一般通过 [`crate::version::structs::VersionInfo::guess_version_type`] 猜测出,用于展示版本类型和判断是否需要版本独立
    pub version_type: VersionType,
    /// 版本的创建日期,实际是该文件夹的创建日期
    pub created_date: std::time::SystemTime,
    /// 版本的上一次游玩日期,实际是该文件夹的上一次访问日期
    pub access_date: std::time::SystemTime,
}

impl Default for Version {
    fn default() -> Self {
        Self {
            name: String::new(),
            version_type: VersionType::Unknown,
            created_date: std::time::SystemTime::UNIX_EPOCH,
            access_date: std::time::SystemTime::UNIX_EPOCH,
        }
    }
}

/// 通过指定的版本文件夹,搜索所有可启动的游戏版本
pub async fn get_avaliable_versions(
    version_directory_path: impl AsRef<Path>,
) -> DynResult<Vec<Version>> {
    let mut version_info = VersionInfo {
        version_base: version_directory_path
            .as_ref()
            .to_string_lossy()
            .to_string(),
        ..Default::default()
    };
    if version_directory_path.as_ref().is_dir() {
        let mut entries = inner_future::fs::read_dir(version_directory_path.as_ref()).await?;
        let mut result = Vec::with_capacity(32);
        while let Some(entry) = entries.try_next().await? {
            let (created_date, access_date) = if let Ok(metadata) = entry.metadata().await {
                (
                    metadata
                        .created()
                        .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
                    metadata
                        .accessed()
                        .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
                )
            } else {
                (
                    std::time::SystemTime::UNIX_EPOCH,
                    std::time::SystemTime::UNIX_EPOCH,
                )
            };
            if entry.file_type().await?.is_dir() {
                let entry = entry
                    .path()
                    .file_name()
                    .unwrap()
                    .to_str()
                    .unwrap()
                    .to_string();
                version_info.version = entry.to_owned();
                if version_info.load().await.is_ok() {
                    result.push(Version {
                        name: entry,
                        version_type: version_info.guess_version_type(),
                        created_date,
                        access_date,
                    });
                } else {
                    result.push(Version {
                        name: entry,
                        version_type: VersionType::Unknown,
                        created_date,
                        access_date,
                    });
                }
            }
        }
        Ok(result)
    } else {
        Ok(vec![])
    }
}

/// 版本类型
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionType {
    /// 纯净版本
    Vanilla,
    /// Forge 版本
    Forge,
    /// Fabric 版本
    Fabric,
    /// QuiltMC 版本
    QuiltMC,
    /// Optifine 画质增强版本
    ///
    /// 如果其是通过其它模组加载器加载的(Forge 或 Fabric),则优先为模组加载器版本
    Optifine,
    /// 未知版本
    Unknown,
}

impl Default for VersionType {
    fn default() -> Self {
        Self::Unknown
    }
}

/// 当解析出错时,此处为错误枚举值
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadVersionInfoError {
    /// 没有提供游戏版本文件夹路径或不存在
    VersionBaseMissing,
    /// 没有提供游戏本体文件路径或不存在
    MainJarFileMissing,
    /// 没有提供版本元数据文件路径或不存在
    VersionFileMissing,
    /// 无法打开版本元数据文件
    VersionFileOpenFail,
    /// 无法打开启动器配置文件
    SCLConfigFileOpenFail,
    /// 元数据解析错误,内容有误
    ParseError(String),
}