diff --git a/PCL.Neo.Core/Models/Minecraft/Game/Data/GameInfo.cs b/PCL.Neo.Core/Models/Minecraft/Game/Data/GameInfo.cs
index d05d75d7..10e4cbba 100644
--- a/PCL.Neo.Core/Models/Minecraft/Game/Data/GameInfo.cs
+++ b/PCL.Neo.Core/Models/Minecraft/Game/Data/GameInfo.cs
@@ -4,6 +4,11 @@ namespace PCL.Neo.Core.Models.Minecraft.Game.Data;
public record GameInfo
{
+ ///
+ /// The name of the game version.
+ ///
+ public required string Name { get; set; }
+
///
/// .minecraft folder path.
///
@@ -15,21 +20,20 @@ public record GameInfo
public required string RootDirectory { get; set; }
///
- /// The name of the game version.
+ /// The loader type.
///
- public required string Name { get; set; }
+ public GameType Type { get; set; } = GameType.Unknown;
///
- /// The loader type.
+ /// The game version.
///
- public GameType Type { get; set; } = GameType.Unknown;
+ public required string Version { get; set; }
///
/// Demonstrate if the version has been loaded (runed).
///
-
[JsonIgnore]
- public bool IsRunning { get; set; } = false;
+ public bool IsRunning { get; set; }
private bool? _isIndie;
@@ -57,6 +61,7 @@ public bool IsIndie
public static GameInfo Factory(
string targetDir, string gameDir,
string versionName,
+ string verison,
bool isIndie,
GameType type)
{
@@ -66,7 +71,8 @@ public static GameInfo Factory(
RootDirectory = targetDir,
GameDirectory = gameDir,
IsIndie = isIndie,
- Type = type
+ Type = type,
+ Version = verison
};
}
}
diff --git a/PCL.Neo.Core/Models/Minecraft/Game/Data/GameVersionId.cs b/PCL.Neo.Core/Models/Minecraft/Game/Data/GameVersionId.cs
deleted file mode 100644
index 17cf88db..00000000
--- a/PCL.Neo.Core/Models/Minecraft/Game/Data/GameVersionId.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-namespace PCL.Neo.Core.Models.Minecraft.Game.Data;
-
-///
-/// 常规游戏版本的版本号,后续可能会拓展到模组版本
-///
-public record GameVersionId(byte Sub, byte? Fix = null) : IComparable
-{
- private readonly (byte Major, byte Sub, byte Fix) _version = (1, Sub, Fix ?? 0);
-
- public byte Major => _version.Major;
- public byte Sub => _version.Sub;
- public byte? Fix => _version.Fix > 0 ? _version.Fix : null;
-
- public int CompareTo(GameVersionId? other)
- {
- return other == null ? 1 : (Major, Sub, Fix ?? 0).CompareTo((other.Major, other.Sub, other.Fix ?? 0));
- }
-
- public override string ToString()
- {
- return Fix.HasValue ? $"{Major}.{Sub}.{Fix}" : $"{Major}.{Sub}";
- }
-
- ///
- public override int GetHashCode()
- {
- return HashCode.Combine(_version.Major, _version.Sub, _version.Fix);
- }
-
- ///
- public virtual bool Equals(GameVersionId? other)
- {
- if (other is null) return false;
- if (ReferenceEquals(this, other)) return true;
-
- return _version.Major == other._version.Major &&
- _version.Sub == other._version.Sub &&
- _version.Fix == other._version.Fix;
- }
-}
diff --git a/PCL.Neo.Core/Models/Minecraft/MetadataFile.cs b/PCL.Neo.Core/Models/Minecraft/MetadataFile.cs
deleted file mode 100644
index d6969377..00000000
--- a/PCL.Neo.Core/Models/Minecraft/MetadataFile.cs
+++ /dev/null
@@ -1,288 +0,0 @@
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-
-namespace PCL.Neo.Core.Models.Minecraft;
-
-public class MetadataFile
-{
- private JsonObject _rawMetadata = new();
-
- #region Model Classes
-
- public class Rule
- {
- [JsonConverter(typeof(JsonStringEnumConverter))]
- public enum ActionEnum
- {
- Unknown,
-
- [JsonStringEnumMemberName("allow")]
- Allow,
-
- [JsonStringEnumMemberName("disallow")]
- Disallow
- }
-
- [JsonPropertyName("action")]
- public ActionEnum Action { get; set; } = ActionEnum.Allow;
-
- [JsonPropertyName("features")]
- public Dictionary? Features { get; set; } = null;
-
- [JsonPropertyName("os")]
- public OsModel? Os { get; set; } = null;
-
- public class OsModel
- {
- [JsonConverter(typeof(JsonStringEnumConverter))]
- public enum ArchEnum
- {
- Unknown,
-
- [JsonStringEnumMemberName("x64")]
- X64,
-
- [JsonStringEnumMemberName("x86")]
- X86
- }
-
- [JsonConverter(typeof(JsonStringEnumConverter))]
- public enum NameEnum
- {
- Unknown,
-
- [JsonStringEnumMemberName("windows")]
- Windows,
-
- [JsonStringEnumMemberName("linux")]
- Linux,
-
- [JsonStringEnumMemberName("osx")]
- Osx
- }
-
- [JsonPropertyName("arch")]
- public ArchEnum? Arch { get; set; } = null;
-
- [JsonPropertyName("name")]
- public NameEnum? Name { get; set; } = null;
-
- [JsonPropertyName("version")]
- public string? Version { get; set; } = null; // regex
- }
- }
-
- public class ConditionalArg
- {
- [JsonPropertyName("rules")]
- public List? Rules { get; set; }
-
- [JsonPropertyName("value")]
- public List Value { get; set; }
- }
-
- public class ArgumentsModel
- {
- public List Game { get; set; } = [];
- public List Jvm { get; set; } = [];
- }
-
- public class JavaVersionModel
- {
- [JsonPropertyName("component")]
- public string Component { get; set; } = string.Empty;
-
- [JsonPropertyName("majorVersion")]
- public int MajorVersion { get; set; }
- }
-
- public class RemoteFileModel
- {
- [JsonPropertyName("id")]
- public string Id { get; set; } = string.Empty;
-
- [JsonPropertyName("path")]
- public string? Path { get; set; } = null;
-
- [JsonPropertyName("sha1")]
- public string Sha1 { get; set; } = string.Empty;
-
- [JsonPropertyName("size")]
- public int Size { get; set; }
-
- [JsonPropertyName("url")]
- public string Url { get; set; } = string.Empty;
- }
-
- public class AssetIndexModel : RemoteFileModel
- {
- [JsonPropertyName("totalSize")]
- public int TotalSize { get; set; }
- }
-
- public class LibraryModel
- {
- [JsonPropertyName("downloads")]
- public DownloadsModel Downloads { get; set; } = new();
-
- [JsonPropertyName("extract")]
- public ExtractModel? Extract { get; set; } = null;
-
- [JsonPropertyName("name")]
- public string Name { get; set; } = string.Empty;
-
- [JsonPropertyName("natives")]
- public Dictionary? Natives { get; set; } = null;
-
- [JsonPropertyName("rules")]
- public List? Rules { get; set; } = null;
-
- public class DownloadsModel
- {
- [JsonPropertyName("artifact")]
- public RemoteFileModel? Artifact { get; set; } = null;
-
- [JsonPropertyName("classifiers")]
- public Dictionary? Classifiers { get; set; } = null;
- }
-
- public class ExtractModel
- {
- [JsonPropertyName("exclude")]
- public List Exclude { get; set; } = [];
- }
- }
-
- public class LoggingModel
- {
- [JsonPropertyName("argument")]
- public string Argument { get; set; } = string.Empty;
-
- [JsonPropertyName("file")]
- public RemoteFileModel File { get; set; } = new();
-
- [JsonPropertyName("type")]
- public string Type { get; set; } = string.Empty;
- }
-
- #endregion
-
- #region Metadata Fields
-
- public ArgumentsModel Arguments { get; set; } = new();
- public AssetIndexModel AssetIndex { get; set; } = new();
- public string Assets { get; set; } = string.Empty;
- public int? ComplianceLevel { get; set; } // field missing in 1.6.4.json
- public Dictionary Downloads { get; set; } = [];
- public string Id { get; set; } = string.Empty;
- public JavaVersionModel? JavaVersion { get; set; } = new(); // field missing in 1.6.1.json
- public List Libraries { get; set; } = [];
- public Dictionary? Logging { get; set; }
- public string MainClass { get; set; } = string.Empty;
- public int MinimumLauncherVersion { get; set; }
- public string ReleaseTime { get; set; } = string.Empty;
- public string Time { get; set; } = string.Empty;
- public ReleaseTypeEnum Type { get; set; }
-
- #endregion
-
- #region Parse Methods
-
- // For simplicity, we assume the metadata files are always valid
-// ReSharper disable PossibleNullReferenceException
-// ReSharper disable AssignNullToNotNullAttribute
-#nullable disable
-#pragma warning disable IL2026
- public static MetadataFile Parse(string json)
- {
- return Parse(JsonNode.Parse(json)!.AsObject());
- }
-
- public static MetadataFile Parse(JsonNode json)
- {
- return Parse(json.AsObject());
- }
-
- public static MetadataFile Parse(JsonObject json)
- {
- var mf = new MetadataFile { _rawMetadata = json };
-
- #region Arguments
-
- if (mf._rawMetadata.ContainsKey("arguments"))
- {
- ParseArguments(mf.Arguments.Game, "game");
- ParseArguments(mf.Arguments.Jvm, "jvm");
-
- // TODO: convert this to json converter
- void ParseArguments(List toBeFilled, string propertyName)
- {
- toBeFilled.Clear();
- foreach (var param in mf._rawMetadata["arguments"][propertyName].AsArray())
- {
- if (param.GetValueKind() == JsonValueKind.String)
- toBeFilled.Add(new ConditionalArg { Value = [param.GetValue()] });
- else if (param.GetValueKind() == JsonValueKind.Object)
- {
- var rules = param["rules"].Deserialize>();
- List value = null;
- if (param["value"].GetValueKind() == JsonValueKind.String)
- value = [param["value"].GetValue()];
- else if (param["value"].GetValueKind() == JsonValueKind.Array)
- value = param["value"].Deserialize>();
- toBeFilled.Add(new ConditionalArg { Rules = rules, Value = value });
- }
- }
- }
- }
- else if (mf._rawMetadata.ContainsKey("minecraftArguments"))
- {
- var argStr = mf._rawMetadata["minecraftArguments"].GetValue();
- mf.Arguments.Game = argStr
- .Split(' ')
- .Select(x => new ConditionalArg { Value = [x] })
- .ToList();
- }
- else
- throw new Exception("Unknown Metadata File version");
-
- #endregion
-
- #region Logging
-
- if (mf._rawMetadata.ContainsKey("logging"))
- mf.Logging = mf._rawMetadata["logging"].Deserialize>();
-
- #endregion
-
- #region Common Fields
-
- mf.AssetIndex = mf._rawMetadata["assetIndex"].Deserialize();
- mf.Assets = mf._rawMetadata["assets"].GetValue();
-
- mf.ComplianceLevel = mf._rawMetadata["complianceLevel"]?.GetValue(); // field missing in 1.6.4.json
-
- mf.Downloads = mf._rawMetadata["downloads"].Deserialize>();
-
- mf.Id = mf._rawMetadata["id"].GetValue();
- mf.JavaVersion = mf._rawMetadata["javaVersion"].Deserialize();
-
- mf.Libraries = mf._rawMetadata["libraries"].Deserialize>();
-
- mf.MainClass = mf._rawMetadata["mainClass"].GetValue();
- mf.MinimumLauncherVersion = mf._rawMetadata["minimumLauncherVersion"].GetValue();
- mf.ReleaseTime = mf._rawMetadata["releaseTime"].GetValue();
- mf.Time = mf._rawMetadata["time"].GetValue();
- mf.Type = mf._rawMetadata["type"].Deserialize();
-
- #endregion
-
- return mf;
- }
-#pragma warning restore IL2026
- // ReSharper restore AssignNullToNotNullAttribute
-// ReSharper restore PossibleNullReferenceException
-
- #endregion
-}
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/Data/FabricModInfo.cs b/PCL.Neo.Core/Models/Minecraft/Mod/Data/FabricModInfo.cs
new file mode 100644
index 00000000..3f636d41
--- /dev/null
+++ b/PCL.Neo.Core/Models/Minecraft/Mod/Data/FabricModInfo.cs
@@ -0,0 +1,45 @@
+using System.Text.Json.Serialization;
+
+namespace PCL.Neo.Core.Models.Minecraft.Mod.Data;
+
+///
+/// 模组信息
+///
+internal record FabricModInfo
+{
+ public record ContactInfo
+ {
+ [JsonPropertyName("email ")]
+ public string Email { get; set; } = string.Empty;
+
+ [JsonPropertyName("irc")]
+ public string Irc { get; set; } = string.Empty;
+
+ [JsonPropertyName("homepage")]
+ public string Homepage { get; set; } = string.Empty;
+
+ [JsonPropertyName("issues")]
+ public string Issues { set; get; } = string.Empty;
+
+ [JsonPropertyName("sources")]
+ public string Sources { get; set; } = string.Empty;
+ }
+
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [JsonPropertyName("version")]
+ public string Version { get; set; } = string.Empty;
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = string.Empty;
+
+ [JsonPropertyName("icon")]
+ public string Icon { get; set; } = string.Empty;
+
+ [JsonPropertyName("contact")]
+ public ContactInfo? Contact { get; set; }
+}
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/Data/MetaModInfo.cs b/PCL.Neo.Core/Models/Minecraft/Mod/Data/MetaModInfo.cs
new file mode 100644
index 00000000..2c3a56d3
--- /dev/null
+++ b/PCL.Neo.Core/Models/Minecraft/Mod/Data/MetaModInfo.cs
@@ -0,0 +1,48 @@
+namespace PCL.Neo.Core.Models.Minecraft.Mod.Data;
+
+internal class MetaModInfo
+{
+ public record ModInfo
+ {
+ ///
+ /// 模组ID,映射到 "modId"。
+ ///
+ public string ModId { get; set; } = string.Empty;
+
+ ///
+ /// 模组版本,映射到 "version"。
+ ///
+ public string Version { get; set; } = string.Empty;
+
+ ///
+ /// 显示名称,映射到 "displayName"。
+ ///
+ public string DisplayName { get; set; } = string.Empty;
+
+ ///
+ /// 主页URL,映射到 "displayURL"。
+ ///
+ public string DisplayUrl { get; set; } = string.Empty;
+
+ ///
+ /// Logo文件名,映射到 "logoFile"。
+ ///
+ public string LogoFile { get; set; } = string.Empty;
+
+ ///
+ /// 制作人员/致谢名单,映射到 "credits"。
+ ///
+ public string Credits { get; set; } = string.Empty;
+
+ ///
+ /// 模组描述,映射到 "description"。
+ ///
+ public string Description { get; set; } = string.Empty;
+ }
+
+ ///
+ /// 映射 TOML 中的 [[mods]] 数组。
+ /// Tomlyn 会自动将 TOML 中的 "mods" 键映射到这个名为 "Mods" 的属性。
+ ///
+ public List Mods { get; set; } = [];
+}
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/Data/ModInfo.cs b/PCL.Neo.Core/Models/Minecraft/Mod/Data/ModInfo.cs
new file mode 100644
index 00000000..df644bee
--- /dev/null
+++ b/PCL.Neo.Core/Models/Minecraft/Mod/Data/ModInfo.cs
@@ -0,0 +1,31 @@
+namespace PCL.Neo.Core.Models.Minecraft.Mod.Data;
+
+public record ModInfo : IDisposable
+{
+ public string Name { get; set; } = string.Empty;
+
+ public string Description { get; set; } = string.Empty;
+
+ public string Version { get; set; } = string.Empty;
+
+ public string Icon { get; set; } = string.Empty;
+ public string Url { get; set; } = string.Empty;
+
+ private bool _disposed;
+
+ ///
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (File.Exists(Icon))
+ {
+ File.Delete(Icon);
+ }
+
+ _disposed = true;
+ }
+}
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/Modpack.cs b/PCL.Neo.Core/Models/Minecraft/Mod/Data/Modpack.cs
similarity index 79%
rename from PCL.Neo.Core/Models/Minecraft/Mod/Modpack.cs
rename to PCL.Neo.Core/Models/Minecraft/Mod/Data/Modpack.cs
index 51ab0002..95b2d486 100644
--- a/PCL.Neo.Core/Models/Minecraft/Mod/Modpack.cs
+++ b/PCL.Neo.Core/Models/Minecraft/Mod/Data/Modpack.cs
@@ -1,13 +1,12 @@
using System.IO.Compression;
-namespace PCL.Neo.Core.Models.Minecraft.Mod;
+namespace PCL.Neo.Core.Models.Minecraft.Mod.Data;
public class ModPack
{
public static void InstallPackModrinth(string mrpack, string directory)
{
- if (!File.Exists(mrpack)) { throw new FileNotFoundException(); }
-
+ if (!File.Exists(mrpack)) throw new FileNotFoundException();
// ZipFile.ExtractToDirectory(mrpack, directory);
using var archive = ZipFile.OpenRead(mrpack);
var modrinthOptions = archive.GetEntry("modrinth.index.json");
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/ModInfo.cs b/PCL.Neo.Core/Models/Minecraft/Mod/ModInfo.cs
deleted file mode 100644
index 94c3491a..00000000
--- a/PCL.Neo.Core/Models/Minecraft/Mod/ModInfo.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace PCL.Neo.Core.Models.Minecraft.Mod;
-
-///
-/// 模组信息
-///
-public record ModInfo
-{
- public required string Id { get; init; }
- public required string Name { get; set; }
- public string Version { get; set; } = string.Empty;
- public bool Enabled { get; set; } = true;
- public string FilePath { get; set; } = string.Empty;
- public string Description { get; set; } = string.Empty;
-}
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/ModInfoReader.cs b/PCL.Neo.Core/Models/Minecraft/Mod/ModInfoReader.cs
new file mode 100644
index 00000000..9a258baa
--- /dev/null
+++ b/PCL.Neo.Core/Models/Minecraft/Mod/ModInfoReader.cs
@@ -0,0 +1,164 @@
+using PCL.Neo.Core.Models.Minecraft.Mod.Data;
+using System.IO.Compression;
+using System.Text.Json;
+using Tomlyn;
+
+namespace PCL.Neo.Core.Models.Minecraft.Mod;
+
+public class ModInfoReader
+{
+ private enum ModInfoType
+ {
+ Unknown,
+ MetaInf,
+ JsonInfo
+ }
+
+ private static (ModInfoType, ZipArchiveEntry?) GetModInfoType(ZipArchive archive)
+ {
+ var entry = archive.GetEntry("META-INF/mods.toml");
+ if (entry != null)
+ {
+ return (ModInfoType.MetaInf, entry);
+ }
+
+ entry = archive.GetEntry("fabric.mod.json");
+ if (entry != null)
+ {
+ return (ModInfoType.JsonInfo, entry);
+ }
+
+ return (ModInfoType.Unknown, null);
+ }
+
+ ///
+ /// Get mod information from the specified mod directory.
+ ///
+ /// The directory that storage mods.
+ ///
+ /// Throw if given directory not found.
+ /// Throw if needed file is not found.
+ public static async Task> GetModInfo(string modDir)
+ {
+ if (!Directory.Exists(modDir))
+ {
+ throw new DirectoryNotFoundException("Mods direcotry not found.");
+ }
+
+ var mods = new List();
+ var modFiles = Directory.GetFiles(modDir, "*.jar");
+
+ foreach (var modFile in modFiles)
+ {
+ using var zipFile = ZipFile.OpenRead(modFile);
+
+ var (type, archiveEntry) = GetModInfoType(zipFile); // get info type and entry
+
+ ArgumentNullException.ThrowIfNull(archiveEntry);
+
+ using var reader = new StreamReader(archiveEntry.Open());
+ var rawContent = await reader.ReadToEndAsync();
+
+ switch (type)
+ {
+ case ModInfoType.JsonInfo:
+ var content = new string(rawContent.Where(it => it != '\n').ToArray());
+ var jsonContent = JsonSerializer.Deserialize(content);
+ ArgumentNullException.ThrowIfNull(jsonContent); // ensure deserialization was successful
+
+ // copy mod icon
+ if (!string.IsNullOrEmpty(jsonContent.Icon))
+ {
+ jsonContent.Icon = await CopyIcon(zipFile, jsonContent.Icon);
+ }
+ else
+ {
+ jsonContent.Icon = "Unknown";
+ }
+
+ // convert to ModInfo
+ string modSource;
+ if (jsonContent.Contact == null)
+ {
+ modSource = string.Empty;
+ }
+ else
+ {
+ modSource = string.IsNullOrEmpty(jsonContent.Contact.Sources)
+ ? jsonContent.Contact.Homepage
+ : jsonContent.Contact.Sources;
+ }
+
+ var modInfo = new ModInfo
+ {
+ Version = jsonContent.Version,
+ Name = string.IsNullOrEmpty(jsonContent.Name) ? jsonContent.Id : jsonContent.Name,
+ Description = jsonContent.Description,
+ Icon = jsonContent.Icon,
+ Url = modSource
+ };
+ mods.Add(modInfo);
+ break;
+ case ModInfoType.MetaInf:
+ var tomlContent = Toml.ToModel(rawContent).Mods.First();
+
+ // copy mod icon
+ if (!string.IsNullOrEmpty(tomlContent.LogoFile))
+ {
+ tomlContent.LogoFile = await CopyIcon(zipFile, tomlContent.LogoFile);
+ }
+ else
+ {
+ tomlContent.LogoFile = "Unknown";
+ }
+
+ // convert to ModInfo
+ var metaInfo = new ModInfo
+ {
+ Name = string.IsNullOrEmpty(tomlContent.DisplayName)
+ ? tomlContent.ModId
+ : tomlContent.DisplayName,
+ Version = tomlContent.Version,
+ Icon = tomlContent.LogoFile,
+ Description = tomlContent.Description,
+ Url = tomlContent.DisplayUrl
+ };
+ mods.Add(metaInfo);
+ break;
+
+ case ModInfoType.Unknown:
+ var unknownInfo = new ModInfo
+ {
+ Name = "Unknown",
+ Description = "Can not read mod information.",
+ Icon = "Unknown",
+ };
+ mods.Add(unknownInfo);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ return mods;
+ }
+
+ private static readonly string ModIconDir = Path.Combine(Const.AppData, "modIcons");
+
+ private static async Task CopyIcon(ZipArchive archive, string iconPath)
+ {
+ var iconEntry = archive.GetEntry(iconPath);
+ if (iconEntry == null)
+ {
+ throw new ArgumentNullException(nameof(iconEntry), "Icon not found.");
+ }
+
+ var tempIconPath = Path.Combine(ModIconDir, $"{Guid.NewGuid().ToString()[..8]}.png");
+ await using var iconStream = iconEntry.Open();
+ await using var fileStream = File.Create(tempIconPath);
+
+ await iconStream.CopyToAsync(fileStream);
+
+ return tempIconPath;
+ }
+}
diff --git a/PCL.Neo.Core/Models/Minecraft/Mod/README.md b/PCL.Neo.Core/Models/Minecraft/Mod/README.md
new file mode 100644
index 00000000..e47de19d
--- /dev/null
+++ b/PCL.Neo.Core/Models/Minecraft/Mod/README.md
@@ -0,0 +1,49 @@
+# ModInfoReader 使用说明
+
+## 简介
+`ModInfoReader` 是一个用于读取 Minecraft 模组(mod)信息的工具类。它支持从指定的模组文件夹中批量解析 `.jar` 文件,自动识别并提取模组的名称、描述、版本、图标等信息,兼容 Fabric 和 Forge 两种主流模组格式。
+
+## 使用方法
+1. 引用命名空间:
+```csharp
+using PCL.Neo.Core.Models.Minecraft.Mod;
+```
+2. 调用静态方法 `GetModInfo`,传入模组文件夹路径:
+```csharp
+var mods = await ModInfoReader.GetModInfo("你的mods文件夹路径");
+```
+3. 遍历返回的模组信息:
+```csharp
+foreach (var modInfo in mods)
+{
+ Console.WriteLine($"名称: {modInfo.Name}");
+ Console.WriteLine($"描述: {modInfo.Description}");
+ Console.WriteLine($"版本: {modInfo.Version}");
+ Console.WriteLine($"图标路径: {modInfo.Icon}");
+ Console.WriteLine($"主页: {modInfo.Url}");
+}
+```
+
+## 示例
+```csharp
+const string modDir = @"C:\你的Minecraft路径\mods";
+var mods = await ModInfoReader.GetModInfo(modDir);
+foreach (var modInfo in mods)
+{
+ Console.WriteLine(modInfo.Name);
+ Console.WriteLine(modInfo.Description);
+ Console.WriteLine(modInfo.Version);
+ Console.WriteLine(modInfo.Icon);
+ Console.WriteLine("--------");
+}
+```
+
+## 注意事项
+- 传入的文件夹路径必须存在且包含 `.jar` 格式的模组文件,否则会抛出异常。
+- 仅支持 Fabric(`fabric.mod.json`)和 Forge(`META-INF/mods.toml`)格式的模组,其他格式会返回默认信息。
+- 解析出的图标会被复制到本地缓存目录(`modIcons`),请注意及时清理无用图标文件。
+- `ModInfo` 实现了 `IDisposable`,如需释放资源(删除图标文件),请在使用完毕后调用 `Dispose()` 方法。
+- 该方法为异步方法,需使用 `await` 调用。
+
+## 联系与反馈
+如有问题或建议,欢迎在项目仓库提交 issue。
diff --git a/PCL.Neo.Core/PCL.Neo.Core.csproj b/PCL.Neo.Core/PCL.Neo.Core.csproj
index c89fda24..ef7d6e9c 100644
--- a/PCL.Neo.Core/PCL.Neo.Core.csproj
+++ b/PCL.Neo.Core/PCL.Neo.Core.csproj
@@ -19,6 +19,7 @@
+
diff --git a/PCL.Neo.Core/Service/Profiles/IProfileService.cs b/PCL.Neo.Core/Service/Profiles/IProfileService.cs
index 2d4ec76e..823445d2 100644
--- a/PCL.Neo.Core/Service/Profiles/IProfileService.cs
+++ b/PCL.Neo.Core/Service/Profiles/IProfileService.cs
@@ -33,7 +33,7 @@ public interface IProfileService
/// The directory where the profile is located.
/// The name of the profile to load.
/// A game profile object.
- Task LoadTargetGameAsync(string targetDir, string gameName);
+ Task GetTargetGameAsync(string targetDir, string gameName);
///
/// Save profile to the specified directory.
diff --git a/PCL.Neo.Core/Service/Profiles/ProfileService.cs b/PCL.Neo.Core/Service/Profiles/ProfileService.cs
index 3c6d448a..88982b2b 100644
--- a/PCL.Neo.Core/Service/Profiles/ProfileService.cs
+++ b/PCL.Neo.Core/Service/Profiles/ProfileService.cs
@@ -63,14 +63,15 @@ public async Task GetProfileAsync(string targetDir, string profileN
var isIndie = Directory.Exists(Path.Combine(version, "saves"));
var gameType = await GetGameType(version, versionName);
- profiles.Games.Add(GameInfo.Factory(targetDir, version, versionName, isIndie, gameType));
+ var cliVer = GetClientVersion(jsonFile);
+ profiles.Games.Add(GameInfo.Factory(targetDir, version, versionName, cliVer, isIndie, gameType));
}
return profiles;
}
///
- public async Task LoadTargetGameAsync(string targetDir, string gameName)
+ public async Task GetTargetGameAsync(string targetDir, string gameName)
{
if (!ValidateDir(targetDir))
{
@@ -96,7 +97,8 @@ public async Task LoadTargetGameAsync(string targetDir, string gameNam
var isIndie = Directory.Exists(Path.Combine(gameDir, "saves"));
var gameType = await GetGameType(gameDir, gameName);
- var gameInfo = GameInfo.Factory(targetDir, gameDir, gameName, isIndie, gameType);
+ var cliVer = GetClientVersion(jsonFile);
+ var gameInfo = GameInfo.Factory(targetDir, gameDir, gameName, cliVer, isIndie, gameType);
return gameInfo;
}
@@ -152,6 +154,18 @@ public bool DeleteGame(GameInfo game, ProfileInfo profile)
return true;
}
+ private static string GetClientVersion(string jsonFilePath)
+ {
+ using var jsonFile = File.OpenRead(jsonFilePath);
+ var jsonDoc = JsonDocument.Parse(jsonFile);
+ var versionElement = jsonDoc.RootElement.GetProperty("clientVersion");
+ var versionStr = versionElement.GetString();
+
+ ArgumentNullException.ThrowIfNull(versionStr);
+
+ return versionStr;
+ }
+
private static bool ValidateDir(string targetDir)
{
return RequiredSubDirectories.All(subDir =>
diff --git a/PCL.Neo.Core/Service/Profiles/README.md b/PCL.Neo.Core/Service/Profiles/README.md
index cf79f94a..f9467db6 100644
--- a/PCL.Neo.Core/Service/Profiles/README.md
+++ b/PCL.Neo.Core/Service/Profiles/README.md
@@ -41,10 +41,10 @@ Task GetProfileAsync(string targetDir, string profileName)
### 2.2 游戏加载方法
-#### LoadTargetGameAsync
+#### GetTargetGameAsync
```csharp
-Task LoadTargetGameAsync(string targetDir, string gameName)
+Task GetTargetGameAsync(string targetDir, string gameName)
```
从指定目录加载特定的游戏版本信息。
@@ -124,7 +124,7 @@ await _profileService.SaveProfilesDefaultAsync(profile);
```csharp
// 加载特定游戏版本
-var game = await _profileService.LoadTargetGameAsync(targetDir, "1.20.6-Fabric");
+var game = await _profileService.GetTargetGameAsync(targetDir, "1.20.6-Fabric");
// 将游戏信息添加到档案
await _profileService.SaveGameInfoToProfileAsync(profile, game, targetDir);
diff --git a/PCL.Neo.Tests/Core/Models/FileHelper/FileTest.cs b/PCL.Neo.Tests/Core/Models/FileHelper/FileTest.cs
index 5f8f811b..3bce3311 100644
--- a/PCL.Neo.Tests/Core/Models/FileHelper/FileTest.cs
+++ b/PCL.Neo.Tests/Core/Models/FileHelper/FileTest.cs
@@ -1,6 +1,6 @@
using PCL.Neo.Core.Download;
using PCL.Neo.Core.Models.Minecraft.Java;
-using PCL.Neo.Core.Models.Minecraft.Mod;
+using PCL.Neo.Core.Models.Minecraft.Mod.Data;
using System;
using System.IO;
using System.Threading.Tasks;
@@ -42,10 +42,4 @@ public void MojangVersionTest()
{
Console.WriteLine(JavaManager.MojangJavaVersion.Δ.Value);
}
-
- [Test]
- public async Task SelectFileTest()
- {
- // await Helpers.FileExtension.SelectFile("Test");
- }
}
diff --git a/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameEntityTest.cs b/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameEntityTest.cs
index ef943a4b..0cf753c5 100644
--- a/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameEntityTest.cs
+++ b/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameEntityTest.cs
@@ -52,7 +52,8 @@ await JavaRuntime.CreateJavaEntityAsync(
GameDirectory =
@"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft\versions\Create",
RootDirectory = @"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft",
- Name = "Create"
+ Name = "Create",
+ Version = "Unknow"
},
launchOptions
);
diff --git a/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameLauncherTest.cs b/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameLauncherTest.cs
index 33e89460..fea36f70 100644
--- a/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameLauncherTest.cs
+++ b/PCL.Neo.Tests/Core/Models/Minecraft/Game/GameLauncherTest.cs
@@ -52,7 +52,8 @@ await JavaRuntime.CreateJavaEntityAsync(
GameDirectory =
@"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft\versions\Create",
RootDirectory = @"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft",
- Name = "Create"
+ Name = "Create",
+ Version = "Unknow"
},
launchOptions
);
diff --git a/PCL.Neo.Tests/Core/Models/Minecraft/Game/IGameLauncherServiceTest.cs b/PCL.Neo.Tests/Core/Models/Minecraft/Game/IGameLauncherServiceTest.cs
index 1e719776..03e2b206 100644
--- a/PCL.Neo.Tests/Core/Models/Minecraft/Game/IGameLauncherServiceTest.cs
+++ b/PCL.Neo.Tests/Core/Models/Minecraft/Game/IGameLauncherServiceTest.cs
@@ -56,7 +56,8 @@ await JavaRuntime.CreateJavaEntityAsync(
GameDirectory =
@"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft\versions\1.20.4-Fabric 0.15.11-[轻量通用]",
RootDirectory = @"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft",
- Name = "1.20.4-Fabric 0.15.11-[轻量通用]"
+ Name = "1.20.4-Fabric 0.15.11-[轻量通用]",
+ Version = "Unknow"
},
launchOptions
);
diff --git a/PCL.Neo.Tests/Core/Models/Minecraft/MetadataFileTest.cs b/PCL.Neo.Tests/Core/Models/Minecraft/MetadataFileTest.cs
deleted file mode 100644
index 31e076d3..00000000
--- a/PCL.Neo.Tests/Core/Models/Minecraft/MetadataFileTest.cs
+++ /dev/null
@@ -1,249 +0,0 @@
-using PCL.Neo.Core.Models.Minecraft;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.Json.Nodes;
-
-namespace PCL.Neo.Tests.Core.Models.Minecraft;
-
-public class MetadataFileTest
-{
- [Test]
- public void Parse()
- {
- foreach (var metadataFilePath in Directory.EnumerateFiles("./MCMetadataFiles"))
- {
- var jsonObj = JsonNode.Parse(File.ReadAllText(metadataFilePath))!.AsObject();
- var meta = MetadataFile.Parse(jsonObj);
- Assert.That(meta.Arguments.Game, Is.Not.Empty);
- if (jsonObj.ContainsKey("arguments"))
- {
- Assert.That(meta.Arguments.Game.Count, Is.EqualTo(jsonObj["arguments"]!["game"]!.AsArray().Count));
- }
-
- Assert.Multiple(() =>
- {
- Assert.That(meta.Assets, Is.Not.Empty);
- Assert.That(meta.AssetIndex.Id, Is.Not.Empty);
- Assert.That(meta.AssetIndex.Path, Is.Null);
- Assert.That(meta.AssetIndex.Sha1, Is.Not.Empty);
- Assert.That(meta.AssetIndex.Size, Is.Not.Zero);
- Assert.That(meta.AssetIndex.TotalSize, Is.Not.Zero);
- });
- Assert.That(meta.Downloads, Is.Not.Empty);
- foreach (var (id, file) in meta.Downloads)
- {
- Assert.Multiple(() =>
- {
- Assert.That(id, Is.Not.Empty);
- Assert.That(file.Path, Is.Null);
- Assert.That(file.Sha1, Is.Not.Empty);
- Assert.That(file.Size, Is.Not.Zero);
- Assert.That(file.Url, Is.Not.Empty);
- });
- }
-
- Assert.That(meta.Id, Is.Not.Empty);
- Assert.Multiple(() =>
- {
- if (meta.JavaVersion is null)
- return;
- Assert.That(meta.JavaVersion.Component, Is.Not.Empty);
- Assert.That(meta.JavaVersion.MajorVersion, Is.Not.Zero);
- });
- Assert.That(meta.Libraries.Count, Is.EqualTo(jsonObj["libraries"]!.AsArray().Count));
- Assert.Multiple(() =>
- {
- if (meta.Logging is null)
- return;
- Assert.That(meta.Logging, Is.Not.Empty);
- foreach (var (id, logging) in meta.Logging)
- {
- Assert.That(id, Is.Not.Empty);
- Assert.That(logging.Argument, Is.Not.Empty);
- Assert.That(logging.File, Is.Not.Null);
- Assert.Multiple(() =>
- {
- Assert.That(logging.File.Id, Is.Not.Empty);
- Assert.That(logging.File.Path, Is.Null);
- Assert.That(logging.File.Sha1, Is.Not.Empty);
- Assert.That(logging.File.Size, Is.Not.Zero);
- Assert.That(logging.File.Url, Is.Not.Empty);
- });
- Assert.That(logging.Type, Is.Not.Empty);
- }
- });
- Assert.That(meta.MainClass, Is.Not.Empty);
- Assert.That(meta.MinimumLauncherVersion, Is.Not.Zero);
- Assert.That(meta.ReleaseTime, Is.Not.Empty);
- Assert.That(meta.Time, Is.Not.Empty);
- Assert.That(meta.Type, Is.Not.EqualTo(ReleaseTypeEnum.Unknown));
- }
- }
-
- [Test]
- public void ArgumentsParsing()
- {
- object[] testGameArgs =
- [
- "--username",
- "${auth_player_name}",
- "--version",
- "${version_name}",
- "--gameDir",
- "${game_directory}",
- "--assetsDir",
- "${assets_root}",
- "--assetIndex",
- "${assets_index_name}",
- "--uuid",
- "${auth_uuid}",
- "--accessToken",
- "${auth_access_token}",
- "--clientId",
- "${clientid}",
- "--xuid",
- "${auth_xuid}",
- "--userType",
- "${user_type}",
- "--versionType",
- "${version_type}",
- new MetadataFile.ConditionalArg
- {
- Rules =
- [
- new MetadataFile.Rule
- {
- Action = MetadataFile.Rule.ActionEnum.Allow,
- Features = new Dictionary { ["is_demo_user"] = true }
- }
- ],
- Value = ["--demo"]
- },
- new MetadataFile.ConditionalArg
- {
- Rules =
- [
- new MetadataFile.Rule
- {
- Action = MetadataFile.Rule.ActionEnum.Allow,
- Features = new Dictionary { ["has_custom_resolution"] = true }
- }
- ],
- Value =
- [
- "--width",
- "${resolution_width}",
- "--height",
- "${resolution_height}"
- ]
- },
- new MetadataFile.ConditionalArg
- {
- Rules =
- [
- new MetadataFile.Rule
- {
- Action = MetadataFile.Rule.ActionEnum.Allow,
- Features = new Dictionary { ["has_quick_plays_support"] = true }
- }
- ],
- Value =
- [
- "--quickPlayPath",
- "${quickPlayPath}"
- ]
- },
- new MetadataFile.ConditionalArg
- {
- Rules =
- [
- new MetadataFile.Rule
- {
- Action = MetadataFile.Rule.ActionEnum.Allow,
- Features = new Dictionary { ["is_quick_play_singleplayer"] = true }
- }
- ],
- Value =
- [
- "--quickPlaySingleplayer",
- "${quickPlaySingleplayer}"
- ]
- },
- new MetadataFile.ConditionalArg
- {
- Rules =
- [
- new MetadataFile.Rule
- {
- Action = MetadataFile.Rule.ActionEnum.Allow,
- Features = new Dictionary { ["is_quick_play_multiplayer"] = true }
- }
- ],
- Value =
- [
- "--quickPlayMultiplayer",
- "${quickPlayMultiplayer}"
- ]
- },
- new MetadataFile.ConditionalArg
- {
- Rules =
- [
- new MetadataFile.Rule
- {
- Action = MetadataFile.Rule.ActionEnum.Allow,
- Features = new Dictionary { ["is_quick_play_realms"] = true }
- }
- ],
- Value =
- [
- "--quickPlayRealms",
- "${quickPlayRealms}"
- ]
- }
- ];
-
- var jsonObj = JsonNode.Parse(File.ReadAllText("./MCMetadataFiles/1.21.5.json"))!.AsObject();
- var meta = MetadataFile.Parse(jsonObj);
- Assert.That(meta.Arguments.Game.Count, Is.EqualTo(testGameArgs.Length));
- for (var i = 0; i < meta.Arguments.Game.Count; i++)
- {
- if (testGameArgs[i] is string)
- {
- Assert.That(meta.Arguments.Game[i].Value.Count, Is.EqualTo(1));
- Assert.That(meta.Arguments.Game[i].Value[0], Is.EqualTo(testGameArgs[i]));
- }
- else
- {
- var arg = meta.Arguments.Game[i];
- var testArg = (MetadataFile.ConditionalArg)testGameArgs[i];
-
- Assert.That(arg.Value.SequenceEqual(testArg.Value), Is.True);
- Assert.That(
- (arg.Rules is null && testArg.Rules is null) ||
- (arg.Rules is not null && testArg.Rules is not null));
- if (arg.Rules is not null && testArg.Rules is not null)
- {
- Assert.That(arg.Rules.Count, Is.EqualTo(testArg.Rules.Count));
- foreach (var (rule, testRule) in arg.Rules.Zip(testArg.Rules))
- {
- Assert.That(rule.Action, Is.EqualTo(testRule.Action));
- Assert.That((rule.Features is null && testRule.Features is null) ||
- (rule.Features is not null && testRule.Features is not null));
- if (rule.Features is not null && testRule.Features is not null)
- Assert.That(rule.Features.SequenceEqual(testRule.Features));
- Assert.That((rule.Os is null && testRule.Os is null) ||
- (rule.Os is not null && testRule.Os is not null));
- if (rule.Os is not null && testRule.Os is not null)
- {
- Assert.That(rule.Os.Arch, Is.EqualTo(testRule.Os.Arch));
- Assert.That(rule.Os.Name, Is.EqualTo(testRule.Os.Name));
- Assert.That(rule.Os.Version, Is.EqualTo(testRule.Os.Version));
- }
- }
- }
- }
- }
- }
-}
diff --git a/PCL.Neo.Tests/Core/Models/Minecraft/Mod/ModInfoReaderTest.cs b/PCL.Neo.Tests/Core/Models/Minecraft/Mod/ModInfoReaderTest.cs
new file mode 100644
index 00000000..1741a8f0
--- /dev/null
+++ b/PCL.Neo.Tests/Core/Models/Minecraft/Mod/ModInfoReaderTest.cs
@@ -0,0 +1,27 @@
+using PCL.Neo.Core.Models.Minecraft.Mod;
+using System;
+using System.Threading.Tasks;
+
+namespace PCL.Neo.Tests.Core.Models.Minecraft.Mod;
+
+[TestFixture]
+[TestOf(typeof(ModInfoReader))]
+public class ModInfoReaderTest
+{
+ [Test]
+ public async Task GetModInfo_ShouldGetModInfos()
+ {
+ const string modDir =
+ @"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft\versions\1.20.4-Fabric 0.15.11-[轻量通用]\mods";
+ var mods = await ModInfoReader.GetModInfo(modDir);
+
+ foreach (var modInfo in mods)
+ {
+ Console.WriteLine(modInfo.Name);
+ Console.WriteLine(modInfo.Description);
+ Console.WriteLine(modInfo.Version);
+ Console.WriteLine(modInfo.Icon);
+ Console.WriteLine("--------");
+ }
+ }
+}
diff --git a/PCL.Neo.Tests/Core/Service/Profiles/ProfileServiceTest.cs b/PCL.Neo.Tests/Core/Service/Profiles/ProfileServiceTest.cs
index 7b644f8c..1a25640d 100644
--- a/PCL.Neo.Tests/Core/Service/Profiles/ProfileServiceTest.cs
+++ b/PCL.Neo.Tests/Core/Service/Profiles/ProfileServiceTest.cs
@@ -4,7 +4,7 @@
using PCL.Neo.Core.Service.Profiles.Data;
using System;
using System.IO;
-using System.Text.Json;
+using System.Linq;
using System.Threading.Tasks;
namespace PCL.Neo.Tests.Core.Service.Profiles;
@@ -27,25 +27,40 @@ public void SetUp()
}
}
+ [TearDown]
+ public void TearDown()
+ {
+ if (Directory.Exists(SaveTempDir))
+ {
+ Directory.Delete(SaveTempDir, true);
+ }
+ }
+
+ [Test]
+ public async Task LoadProfilesDefaultAsync_ShouldLoadProfiles()
+ {
+ var profiles = await _service.LoadProfilesDefaultAsync();
+ var profileInfos = profiles as ProfileInfo[] ?? profiles.ToArray();
+ Assert.That(profileInfos, Is.Not.Empty);
+ foreach (var profile in profileInfos)
+ {
+ Console.WriteLine($"Profile Name: {profile.ProfileName}");
+ Console.WriteLine($"Target Directory: {profile.TargetDir}");
+ Console.WriteLine($"Games Count: {profile.Games.Count}");
+ Console.WriteLine(new string('-', 20));
+ }
+ }
+
[Test]
- public async Task SaveProfilesDefaultAsync_ShouldLoadAllGamesAndSave()
+ public async Task SaveProfilesDefaultAsync_ShouldGetAllGamesAndSave()
{
var profile = await _service.GetProfileAsync(TempDir, "Test_SaveProfiles");
Assert.That(profile, Is.Not.Null);
- Assert.That(profile.Games.Count, Is.EqualTo(33));
+ Assert.That(profile.Games.Count, Is.EqualTo(36));
- await _service.SaveProfilesDefaultAsync(profile);
+ var result = await _service.SaveProfilesDefaultAsync(profile);
- var profiles = await _service.LoadProfilesDefaultAsync();
- foreach (var pro in profiles)
- {
- foreach (var gameInfo in pro.Games)
- {
- Console.WriteLine(gameInfo.Name);
- Console.WriteLine(gameInfo.Type);
- Console.WriteLine(new string('-', 10));
- }
- }
+ Assert.That(result, Is.True);
}
[Test]
@@ -55,13 +70,9 @@ public async Task GetProfileAsync_ShouldSuccess()
await _service.GetProfileAsync(@"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft",
"Test_LoadAndGetProfile");
Assert.That(profiles, Is.Not.Null);
- await _service.SaveProfilesDefaultAsync(profiles);
-
- var secProfiles = await _service.LoadProfilesDefaultAsync();
- Assert.That(secProfiles, Is.Not.Null);
-
- var content = JsonSerializer.Serialize(secProfiles, new JsonSerializerOptions { WriteIndented = true });
+ var result = await _service.SaveProfilesDefaultAsync(profiles);
+ Assert.That(result, Is.True);
}
[Test]
@@ -74,9 +85,9 @@ public void GetProfileAsync_ShouldThrowOnInvalidDir()
}
[Test]
- public async Task LoadTargetGameAsync_ShouldLoadGame()
+ public async Task GetTargetGameAsync_ShouldGetGame()
{
- var game = await _service.LoadTargetGameAsync(TempDir, "1.20.6-Fabric 0.15.11");
+ var game = await _service.GetTargetGameAsync(TempDir, "1.20.6-Fabric 0.15.11");
Assert.That(game, Is.Not.Null);
Assert.That(game.Name, Is.EqualTo("1.20.6-Fabric 0.15.11"));
Assert.That(game.IsIndie, Is.True);
@@ -86,7 +97,7 @@ public async Task LoadTargetGameAsync_ShouldLoadGame()
public void LoadTargetGameAsync_ShouldThrowOnMissingGame()
{
var ex = Assert.ThrowsAsync(async () =>
- await _service.LoadTargetGameAsync(TempDir, "not_exist"));
+ await _service.GetTargetGameAsync(TempDir, "not_exist"));
StringAssert.Contains("Game directory not found", ex!.Message);
}
@@ -121,6 +132,7 @@ public async Task SaveGameInfoToProfileDefaultAsync_AddsGame()
RootDirectory = TempDir,
GameDirectory = "gd",
IsIndie = false,
+ Version = "23w41a",
Type = GameType.Vanilla
};
var result = await _service.SaveGameInfoToProfileDefaultAsync(profile, game);
@@ -140,6 +152,7 @@ public async Task SaveGameInfoToProfileAsync_AddsGame()
Name = "test2",
RootDirectory = TempDir,
GameDirectory = "gd2",
+ Version = "23w41a",
IsIndie = true,
Type = GameType.Vanilla
};
@@ -159,6 +172,7 @@ public void DeleteGame_ShouldDeleteGame()
GameDirectory = string.Empty,
RootDirectory = string.Empty,
IsIndie = true,
+ Version = "23w41a",
Type = GameType.Vanilla,
Name = "None"
};
diff --git a/PCL.Neo/ViewModels/MainWindowViewModel.cs b/PCL.Neo/ViewModels/MainWindowViewModel.cs
index 56e16059..1af536a8 100644
--- a/PCL.Neo/ViewModels/MainWindowViewModel.cs
+++ b/PCL.Neo/ViewModels/MainWindowViewModel.cs
@@ -309,7 +309,8 @@ await JavaRuntime.CreateJavaEntityAsync(
GameDirectory =
@"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft\versions\Create",
RootDirectory = @"C:\Users\WhiteCAT\Desktop\Games\PCL2\.minecraft",
- Name = "Create"
+ Name = "Create",
+ Version = "Unknow"
},
launchOptions
);