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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
//! 所有的启动器元数据结构都在这里

use std::{
    collections::BTreeMap as Map,
    fmt,
    marker::PhantomData,
    path::{Path, PathBuf},
};

use inner_future::stream::StreamExt;
use serde::{
    de::{self, SeqAccess, Visitor},
    Deserialize, Deserializer, Serialize,
};

use super::VersionType;
use crate::{
    package::PackageName,
    prelude::*,
    semver::MinecraftVersion,
    utils::{get_full_path, NATIVE_ARCH_LAZY},
};

/// 一个针对系统的规则
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct OSRule {
    /// 系统名称,通常是直接和 [`crate::utils::TARGET_OS`] 比对即可
    #[serde(default)]
    pub name: String,
    /// 系统版本号,依照平台而定
    #[serde(default)]
    pub version: String,
    /// 系统架构,通常是直接和 [`crate::utils::NATIVE_ARCH_LAZY`] 比对即可
    #[serde(default)]
    pub arch: String,
}

/// 一个规则
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct ApplyRule {
    /// 规则的类型,通常是 `allow` 或者 `disallow`
    pub action: String,
    /// 规则需要满足的操作系统类型
    #[serde(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub os: Option<OSRule>,
    /// 规则需要满足的一些特殊情况
    ///
    /// 但是 SCL 并没有使用这部分来做判断
    #[serde(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub features: Option<Map<String, bool>>,
}

/// 一个用于检查规则是否满足条件的特质
///
/// [`ApplyRule`] 实现了这个特质
pub trait Allowed {
    /// 判断当前情况是否满足该规则
    fn is_allowed(&self) -> bool;
}

impl Allowed for [ApplyRule] {
    fn is_allowed(&self) -> bool {
        if self.is_empty() {
            true
        } else {
            let mut should_push = false;
            for rule in self {
                if rule.action == "disallow" {
                    if let Some(os) = &rule.os {
                        if !os.name.is_empty()
                            && os.name != crate::utils::TARGET_OS
                            && !os.arch.is_empty()
                            && os.arch != NATIVE_ARCH_LAZY.as_ref()
                        {
                            continue;
                        } else {
                            break;
                        }
                    } else {
                        continue;
                    }
                } else if rule.action == "allow" {
                    if let Some(os) = &rule.os {
                        if (!os.name.is_empty() && os.name != crate::utils::TARGET_OS)
                            || (!os.arch.is_empty() && os.arch != NATIVE_ARCH_LAZY.as_ref())
                        {
                            continue;
                        } else {
                            should_push = true;
                            break;
                        }
                    } else {
                        should_push = true; // 可能会有不允许的情况,继续寻找
                        continue;
                    }
                }
            }
            should_push
        }
    }
}

/// 特殊指派的参数
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct SpecificalArgument {
    /// 添加此参数需要满足的条件
    #[serde(default)]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub rules: Vec<ApplyRule>,
    /// 需要添加的参数
    #[serde(skip_serializing_if = "Vec::is_empty")]
    #[serde(deserialize_with = "string_or_seq")]
    pub value: Vec<String>,
}

/// 游戏启动的一个参数
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(untagged)]
pub enum Argument {
    /// 无条件的参数,必须附加
    Common(String),
    /// 有条件的参数,需要根据条件附加
    Specify(SpecificalArgument),
}

#[test]
fn argument_test() {
    let text = serde_json::from_str::<Argument>(r#""test""#).unwrap();
    assert_eq!(text, Argument::Common("test".into()));
    let specify = serde_json::from_str::<Argument>(r#"{"rules":[],"value":"test"}"#).unwrap();
    assert_eq!(
        specify,
        Argument::Specify(SpecificalArgument {
            rules: vec![],
            value: vec!["test".into()]
        })
    );
}

/// 游戏启动参数
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
#[serde(default)]
pub struct Arguments {
    /// 用于添加到主类参数后面的游戏参数
    pub game: Vec<Argument>,
    /// 用于添加到 Class Path 前面的 JVM 参数
    pub jvm: Vec<Argument>,
}

/// 素材索引信息
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct AssetIndex {
    /// 素材的索引文件 ID,通常是当前的游戏版本号
    pub id: String,
    /// 素材的索引文件 SHA1 摘要
    pub sha1: String,
    /// 素材的索引文件大小,以字节为单位
    pub size: u32,
    /// 所有素材的总计大小,以字节为单位
    #[serde(rename = "totalSize")]
    pub total_size: u32,
    /// 素材的索引文件的下载链接
    pub url: String,
}

/// 一个下载项目结构
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct DownloadItem {
    /// 如果提供,则是下载的安装路径
    #[serde(default)]
    pub path: String,
    /// 该下载项目的文件 SHA1 摘要值
    pub sha1: String,
    /// 该下载项目的文件大小,以字节为单位
    pub size: usize,
    /// 该下载项目的下载链接,如果不提供则应该都是 Maven 的仓库路径,开头加上镜像源链接下载即可
    pub url: String,
}

/// 依赖库的下载信息结构
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct LibraryDownload {
    /// 需要下载的包,通常是 JAR 文件
    #[serde(skip_serializing_if = "Option::is_none")]
    pub artifact: Option<DownloadItem>,
    /// 旧版原生库的分类下载信息
    #[serde(skip_serializing_if = "Option::is_none")]
    pub classifiers: Option<Map<String, DownloadItem>>,
}

/// 一个依赖库结构
///
/// 通过访问 [`Library::rules`] 并调用 [`Allowed::is_allowed`] 来确认此依赖是否需要被下载/添加
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Library {
    /// 添加这个依赖库需要满足的规则
    #[serde(default)]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub rules: Vec<ApplyRule>,
    /// 需要下载的依赖库项目
    #[serde(skip_serializing_if = "Option::is_none")]
    pub downloads: Option<LibraryDownload>,
    /// 旧版本的依赖下载链接,常见于一些模组加载器里
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    /// 旧版本的依赖原生库项目,新版本将直接把原生库放在了 [`Library::downloads`] 项目里直接作为 Class Path 的一部分导入了。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub natives: Option<Map<String, String>>,
    /// 这个依赖库的名称,通常是包名和版本号
    pub name: String,
}

/// 日志配置文件的下载信息
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct LoggingFile {
    /// 日志配置的 ID
    pub id: String,
    /// 日志配置文件的 SHA1 摘要
    pub sha1: String,
    /// 日志配置文件的大小,以字节为单位
    pub size: u32,
    /// 日志配置文件的下载链接
    pub url: String,
}

/// 日志处理方式,通常是 Log4J 的相关配置
///
/// 不过 SCL 并没有用这里的东西
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct LoggingConfig {
    /// 日志相关参数
    pub argument: String,
    /// 日志输出类型
    #[serde(rename = "type")]
    pub logger_type: String,
    /// 日志配置文件的下载结构
    pub file: LoggingFile,
}

/// SCL 的独立版本设置,此处的信息会
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
#[serde(default)]
pub struct SCLLaunchConfig {
    /// 最大内存,单位 MB,如不提供则为自动
    pub max_mem: Option<usize>,
    /// Java 运行时路径
    pub java_path: String,
    /// 是否使用版本独立
    pub game_independent: bool,
    /// 设定游戏窗口标题
    pub window_title: String,
    /// 额外的 JVM 参数,将会附加到 Class Path 前面
    pub jvm_args: String,
    /// 额外的游戏参数,将会附加到参数末尾
    pub game_args: String,
    /// 包装器执行文件路径,对于某些 Linux 用户有用,用于指定 Java 前的执行文件
    pub wrapper_path: String,
    /// 包装器执行文件参数,将会附加到包装器执行文件后
    pub wrapper_args: String,
}

/// 版本元数据里的日志方式
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Logging {
    /// 针对客户端的日志处理方式
    pub client: Option<LoggingConfig>,
}

/// 新版本的 Java 版本元数据
///
/// 如果存在则可以根据此数据选择对应的 Java 运行时
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct JavaVersion {
    /// 该 Java 运行时版本在官方启动器中的代号
    ///
    /// 通过该代号可以找到官方使用的 JVM 运行时
    pub component: String,
    /// 该 Java 运行时的主要版本号
    #[serde(rename = "majorVersion")]
    pub major_version: u8,
}

/// 版本元数据
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct VersionMeta {
    /// 继承某个版本
    #[serde(default)]
    #[serde(rename = "inheritsFrom")]
    pub inherits_from: String,
    /// 1.12 以前的继承版本
    #[serde(default)]
    #[serde(rename = "clientVersion")]
    pub client_version: String,
    #[serde(default)]
    #[serde(rename = "javaVersion")]
    /// 新版本的 Java 版本元数据
    ///
    /// 如果存在则可以根据此数据选择对应的 Java 运行时
    pub java_version: Option<JavaVersion>,
    /// 游戏启动参数
    #[serde(skip_serializing_if = "Option::is_none")]
    pub arguments: Option<Arguments>,
    /// 游戏启动参数 旧版本
    #[serde(default)]
    #[serde(rename = "minecraftArguments")]
    pub minecraft_arguments: String,
    /// 资源索引元数据
    #[serde(rename = "assetIndex")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub asset_index: Option<AssetIndex>,
    /// 该版本需要下载的主要游戏文件信息
    #[serde(skip_serializing_if = "Option::is_none")]
    pub downloads: Option<Map<String, DownloadItem>>,
    /// 该版本所需的依赖库列表
    #[serde(default)]
    pub libraries: Vec<Library>,
    /// 该版本元数据的日志输出配置
    #[serde(skip_serializing_if = "Option::is_none")]
    pub logging: Option<Logging>,
    /// 该版本元数据的主类
    #[serde(rename = "mainClass")]
    pub main_class: String,
    /// 该版本元数据的主类列表,因为有可能存在多个主类 JAR
    #[serde(skip)]
    pub main_jars: Vec<String>,
}

impl VersionMeta {
    pub(crate) fn fix_libraries(&mut self) {
        for library in &mut self.libraries {
            if library.rules.is_allowed()
                && library.downloads.is_none()
                && library.natives.is_none()
            {
                if let Ok(p) = library.name.parse::<PackageName>() {
                    let p = p.to_maven_jar_path("");
                    library.downloads = Some(LibraryDownload {
                        artifact: Some(DownloadItem {
                            path: p,
                            sha1: "".into(),
                            size: 0,
                            url: "".into(),
                        }),
                        classifiers: None,
                    })
                }
            }
        }
    }

    /// 根据元数据判断需要的最低 Java 运行时版本
    pub fn required_java_version(&self) -> u8 {
        if let Some(java_version) = &self.java_version {
            java_version.major_version
        } else if let Some(assets) = &self.asset_index {
            if let Ok((_, ver)) = crate::semver::parse_version(&assets.id) {
                ver.required_java_version()
            } else {
                8
            }
        } else if !self.inherits_from.is_empty() {
            if let Ok((_, ver)) = crate::semver::parse_version(&self.inherits_from) {
                ver.required_java_version()
            } else {
                8
            }
        } else {
            8
        }
    }
}

impl std::ops::AddAssign for VersionMeta {
    fn add_assign(&mut self, data: VersionMeta) {
        self.main_class = data.main_class.to_owned();
        self.minecraft_arguments = data.minecraft_arguments;
        self.libraries.extend_from_slice(&data.libraries);
        self.main_jars.extend_from_slice(&data.main_jars);
        if let Some(downloads) = &mut data.downloads.to_owned() {
            if let Some(self_downloads) = &mut self.downloads {
                self_downloads.append(downloads);
            } else {
                self.downloads = Some(downloads.to_owned());
            }
        }
        if let Some(arguments) = &data.arguments {
            if let Some(self_arguments) = &mut self.arguments {
                for a in arguments.jvm.iter() {
                    self_arguments.jvm.push(a.to_owned());
                }
                for a in arguments.game.iter() {
                    self_arguments.game.push(a.to_owned());
                }
            } else {
                self.arguments = Some(arguments.to_owned())
            }
        }
        if let Some(logging) = &data.logging {
            self.logging = Some(logging.to_owned());
        }
    }
}

/// 版本元数据结构
///
/// 当提供了 [`VersionInfo::version_base`] 和 [`VersionInfo::version`]
/// 两个字段的信息后可使用 [`VersionInfo::load`] 来读取其他数据
#[derive(Debug, Clone, Default)]
pub struct VersionInfo {
    /// 版本文件夹主目录
    pub version_base: String,
    /// 版本名称
    pub version: String,
    /// 读取成功之后的元数据
    pub meta: Option<VersionMeta>,
    /// 启动器配置
    pub scl_launch_config: Option<SCLLaunchConfig>,
    /// 猜测的版本类型
    pub version_type: VersionType,
    /// 猜测的原始 Minecraft 版本类型
    pub minecraft_version: MinecraftVersion,
    /// 猜测的所需最低 Java 版本,1.17 之后一般为 16+,其余的为 8+
    pub required_java: u8,
}

impl VersionInfo {
    /// 根据 [`VersionInfo::version_base`] 和 [`VersionInfo::version`] 读取版本元数据信息
    pub async fn load(&mut self) -> DynResult {
        let version_base_path = Path::new(&self.version_base);
        if version_base_path.is_dir() {
            let jar_path = version_base_path
                .join(&self.version)
                .join(format!("{}.jar", &self.version));
            let meta_path = version_base_path
                .join(&self.version)
                .join(format!("{}.json", &self.version));
            let scl_config_path = version_base_path.join(&self.version).join(".scl.json");
            if !meta_path.is_file() {
                anyhow::bail!(
                    "该版本 {} (游戏文件夹:{}) 缺失元数据文件",
                    &self.version,
                    &self.version_base
                )
            } else {
                if scl_config_path.is_file() {
                    // 加载启动器设置
                    let data = inner_future::fs::read_to_string(scl_config_path).await?;
                    let scl_config = serde_json::from_str(data.trim_start_matches('\u{feff}'))?; // 去掉可能存在的 BOM
                    self.scl_launch_config = Some(scl_config);
                }
                // 解析元文件,提取数据
                let data = inner_future::fs::read_to_string(meta_path).await?;
                let mut meta: VersionMeta =
                    serde_json::from_str(data.trim_start_matches('\u{feff}'))?; // 去掉可能存在的 BOM
                if jar_path.is_file() {
                    meta.main_jars.push(get_full_path(jar_path));
                }
                self.required_java = meta.required_java_version();
                if let Some(assets) = &meta.asset_index {
                    if let Ok((_, ver)) = crate::semver::parse_version(&assets.id) {
                        self.minecraft_version = ver;
                    }
                } else if !meta.inherits_from.is_empty() {
                    if let Ok((_, ver)) = crate::semver::parse_version(&meta.inherits_from) {
                        self.minecraft_version = ver;
                    }
                }
                self.meta = Some(meta);
                self.version_type = self.guess_version_type();
                Ok(())
            }
        } else {
            anyhow::bail!("游戏文件夹 {} 不是正确的文件夹", &self.version_base)
        }
    }

    /// 删除版本文件夹,约等于删除整个版本
    ///
    /// 但是注意本操作不会清理 assets 文件夹和 libraries 文件夹的内容
    pub async fn delete(self) {
        let version_base_path = Path::new(&self.version_base);
        if version_base_path.is_dir() {
            let version_path = version_base_path.join(&self.version);
            let _ = inner_future::fs::remove_dir_all(version_path).await;
        }
    }

    /// 重命名版本,如果目标版本名没有已有版本则会尝试重命名版本文件夹到该名称下
    pub async fn rename_version(&mut self, new_version_name: &str) -> DynResult {
        let version_base_path = Path::new(&self.version_base);
        if version_base_path.is_dir() {
            let version_path = version_base_path.join(&self.version);
            let version_jar_path = version_path.join(format!("{}.jar", self.version));
            let version_json_path = version_path.join(format!("{}.json", self.version));
            let new_version_path = version_base_path.join(new_version_name);
            let new_version_jar_path = version_path.join(format!("{new_version_name}.jar"));
            let new_version_json_path = version_path.join(format!("{new_version_name}.json"));
            if new_version_path.is_dir() {
                anyhow::bail!("目标版本名称已存在")
            } else {
                if version_jar_path.is_file() {
                    inner_future::fs::rename(version_jar_path, new_version_jar_path).await?;
                }
                if version_json_path.is_file() {
                    inner_future::fs::rename(version_json_path, new_version_json_path).await?
                };
                inner_future::fs::rename(version_path, new_version_path).await?;
                self.version = new_version_name.to_owned();
                Ok(())
            }
        } else {
            anyhow::bail!("文件夹不存在")
        }
    }

    /// 保存元数据和独立版本设置
    pub async fn save(&self) -> DynResult {
        let version_base_path = Path::new(&self.version_base);
        if version_base_path.is_dir() {
            let meta_path = version_base_path
                .join(&self.version)
                .join(format!("{}.json", &self.version));
            let scl_config_path = version_base_path.join(&self.version).join(".scl.json");
            // 这个不是刚需了
            // if !jar_path.is_file() {
            //     anyhow::bail!("版本 Jar 文件缺失")
            // }
            if !meta_path.is_file() {
                anyhow::bail!("版本 JSON 元数据文件缺失")
            } else {
                // 解析元文件,提取数据
                if let Some(meta) = &self.meta {
                    let file = std::fs::OpenOptions::new()
                        .truncate(true)
                        .write(true)
                        .open(meta_path);
                    if let Ok(file) = file {
                        match serde_json::to_writer(file, meta) {
                            Ok(_) => {}
                            Err(err) => {
                                anyhow::bail!("元数据解析失败:{}", err)
                            }
                        }
                    } else {
                        anyhow::bail!("无法打开版本元数据文件")
                    }
                }
                if let Some(scl_config) = &self.scl_launch_config {
                    let file = std::fs::OpenOptions::new()
                        .truncate(true)
                        .create(true)
                        .write(true)
                        .open(scl_config_path);
                    if let Ok(file) = file {
                        match serde_json::to_writer(file, scl_config) {
                            Ok(_) => {}
                            Err(err) => {
                                anyhow::bail!("SCL 配置文件写入失败:{}", err)
                            }
                        }
                    } else {
                        anyhow::bail!("无法打开 SCL 配置文件")
                    }
                } else if scl_config_path.is_file() {
                    inner_future::fs::remove_file(scl_config_path).await?;
                }
                Ok(())
            }
        } else {
            anyhow::bail!("版本文件夹未找到")
        }
    }

    /// 根据元数据猜测版本的种类
    pub fn guess_version_type(&self) -> VersionType {
        let mut has_optifine = false;
        let mut has_fabric = false;
        if let Some(meta) = &self.meta {
            for lib in &meta.libraries {
                if lib.name.starts_with("net.fabricmc:") {
                    // 也有可能是 QuiltMC
                    has_fabric = true;
                } else if lib.name.starts_with("net.minecraftforge:") {
                    return VersionType::Forge;
                } else if lib.name.starts_with("org.quiltmc:") {
                    return VersionType::QuiltMC;
                } else if lib.name.starts_with("optifine:") {
                    // 我们要优先 Forge 和 Fabric
                    has_optifine = true;
                }
            }
            if has_fabric {
                VersionType::Fabric
            } else if has_optifine {
                VersionType::Optifine
            } else {
                VersionType::Vanilla
            }
        } else {
            VersionType::Unknown
        }
    }

    /// 一个指向版本主目录的位置
    ///
    /// 如果没有开启版本独立,则会返回类似 `.minecraft` 的文件夹路径
    ///
    /// 如果开启版本独立,则会返回类似 `.minecraft/versions/版本名称` 的文件夹路径
    pub fn version_path(&self) -> PathBuf {
        if self
            .scl_launch_config
            .as_ref()
            .map(|x| x.game_independent)
            .unwrap_or(false)
        {
            // 版本独立
            let mut result = PathBuf::new();
            result.push(&self.version_base);
            result.push(&self.version);
            result
        } else {
            let mut result = PathBuf::new();
            result.push(&self.version_base);
            result.pop();
            result
        }
    }

    /// 读取该版本下的所有模组信息
    pub async fn get_mods(&self) -> DynResult<Vec<super::mods::Mod>> {
        let mods_path = self.version_path().join("mods");
        if !mods_path.is_dir() {
            return Ok(vec![]);
        }
        let mut files = inner_future::fs::read_dir(mods_path).await?;
        let mut results = vec![];
        while let Some(file) = files.try_next().await? {
            if file.path().is_file()
                && file
                    .path()
                    .file_name()
                    .map(|x| x.to_string_lossy().ends_with(".jar"))
                    == Some(true)
                || file
                    .path()
                    .file_name()
                    .map(|x| x.to_string_lossy().ends_with(".jar.disabled"))
                    == Some(true)
            {
                results.push(super::mods::Mod::from_path(file.path()).await?);
            }
        }
        Ok(results)
    }

    /// 根据版本实际情况获取最佳的最大内存用量,单位为 MB
    ///
    /// 代码参考自 <https://github.com/Hex-Dragon/PCL2/blob/f1310f18fda13b79b7a6189f02df15cd8300b28d/Plain%20Craft%20Launcher%202/Pages/PageSetup/PageSetupLaunch.xaml.vb#L327>
    pub async fn get_automated_maxium_memory(&self) -> u64 {
        let mem_status = crate::utils::get_mem_status();
        let mut free = mem_status.free as i64;
        let mods = self.get_mods().await.unwrap_or_default();
        let (mem_min, mem_t1, mem_t2, mem_t3) = if !mods.is_empty() {
            (
                400 + mods.len() as i64 * 7,
                1500 + mods.len() as i64 * 10,
                3000 + mods.len() as i64 * 17,
                6000 + mods.len() as i64 * 34,
            )
        } else {
            (300, 1500, 2500, 4000)
        };
        // 预分配内存,阶段一,0 ~ T1,100%
        let mut result = 0;
        let mem_delta = mem_t1;
        free = (free - 100).max(0);
        result += free.min(mem_delta);
        free -= mem_delta + 100;
        if free < 100 {
            return result.max(mem_min) as _;
        }
        // 预分配内存,阶段二,T1 ~ T2,80%
        let mem_delta = mem_t2 - mem_t1;
        free = (free - 100).max(0);
        result += ((free as f64 * 0.8) as i64).min(mem_delta);
        free -= ((mem_delta as f64 / 0.8) as i64) + 100;
        if free < 100 {
            return result.max(mem_min) as _;
        }
        // 预分配内存,阶段三,T2 ~ T3,60%
        let mem_delta = mem_t2 - mem_t1;
        free = (free - 200).max(0);
        result += ((free as f64 * 0.6) as i64).min(mem_delta);
        free -= ((mem_delta as f64 / 0.6) as i64) + 200;
        if free < 100 {
            return result.max(mem_min) as _;
        }
        // 预分配内存,阶段四,T3 ~ T3 * 2,40%
        let mem_delta = mem_t3;
        free = (free - 300).max(0);
        result += ((free as f64 * 0.4) as i64).min(mem_delta);
        free -= ((mem_delta as f64 / 0.4) as i64) + 300;
        if free < 100 {
            return result.max(mem_min) as _;
        }
        result.max(mem_min) as _
    }

    /// 读取该版本下的所有世界存档信息
    ///
    /// 目前只是简单的扫一遍文件夹而已
    ///
    /// TODO: 做准确的解析过滤
    pub async fn get_saves(&self) -> DynResult<Vec<WorldSave>> {
        let saves_path = self.version_path().join("saves");
        let mut result = Vec::new();
        let mut files = inner_future::fs::read_dir(&saves_path).await?;
        while let Some(_file) = files.try_next().await? {
            result.push(WorldSave {})
        }
        Ok(result)
    }

    /// 读取该版本下的所有资源包信息
    ///
    /// 目前只是简单的扫一遍文件夹而已
    ///
    /// TODO: 做准确的解析过滤
    pub async fn get_resources_packs(&self) -> DynResult<Vec<ResourcesPack>> {
        let resourcepacks_path = self.version_path().join("resourcepacks");
        let texturepacks_path = self.version_path().join("texturepacks");
        let mut result = Vec::new();
        if resourcepacks_path.is_dir() {
            let mut files = inner_future::fs::read_dir(&resourcepacks_path).await?;
            while let Some(_file) = files.try_next().await? {
                result.push(ResourcesPack {})
            }
        }
        if texturepacks_path.is_dir() {
            let mut files = inner_future::fs::read_dir(&texturepacks_path).await?;
            while let Some(_file) = files.try_next().await? {
                result.push(ResourcesPack {})
            }
        }
        Ok(result)
    }
}

/// 一个世界存档的信息结构
#[derive(Debug)]
pub struct WorldSave {
    // TODO
}

/// 一个资源包的信息结构
#[derive(Debug)]
pub struct ResourcesPack {
    // TODO
}

fn string_or_seq<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
    D: Deserializer<'de>,
{
    struct StringOrVec(PhantomData<Vec<String>>);

    impl<'de> Visitor<'de> for StringOrVec {
        type Value = Vec<String>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("string or list of strings")
        }

        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(vec![value.into()])
        }

        fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
        where
            S: SeqAccess<'de>,
        {
            Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
        }
    }

    deserializer.deserialize_any(StringOrVec(PhantomData))
}