Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Settings/Localization/ModSettingsUi/eng.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@
"sidebar.title": "Mods",
"sidebar.subtitle": "Browse registered mods, pages, and sections.",
"sidebar.modMeta": "{0} pages",
"sidebar.modHeader.none": "No mod selected",
"sidebar.modPreview.empty": "No preview",
"sidebar.modPreview.noImage": "No resources",
"button.open": "Open",
"button.back": "Back",
"button.remove": "Remove",
Expand Down
3 changes: 3 additions & 0 deletions Settings/Localization/ModSettingsUi/zhs.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@
"sidebar.title": "模组",
"sidebar.subtitle": "浏览已注册的模组、页面与分区。",
"sidebar.modMeta": "共 {0} 个页面",
"sidebar.modHeader.none": "未选择模组",
"sidebar.modPreview.empty": "无预览",
"sidebar.modPreview.noImage": "无资源",
"button.open": "打开",
"button.back": "返回",
"button.remove": "删除",
Expand Down
11 changes: 8 additions & 3 deletions Settings/ModSettings/ModSettingsLocalization.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Reflection;
using MegaCrit.Sts2.Core.Modding;
using STS2RitsuLib.Compat;
using STS2RitsuLib.Utils;

Expand Down Expand Up @@ -27,9 +28,13 @@ public static string ResolveModName(string modId, string fallback)
if (!string.IsNullOrWhiteSpace(configuredName))
return configuredName;

return Sts2ModManagerCompat.EnumerateModsForManifestLookup().FirstOrDefault(mod =>
string.Equals(mod.manifest?.id, modId, StringComparison.OrdinalIgnoreCase))?.manifest?.name
?? fallback;
var match = Sts2ModManagerCompat.EnumerateModsForManifestLookup()
.FirstOrDefault(mod =>
string.Equals(mod.manifest?.id, modId, StringComparison.OrdinalIgnoreCase));
if (match?.manifest is ModManifest mm && !string.IsNullOrWhiteSpace(mm.name))
return mm.name;

return fallback;
}


Expand Down
182 changes: 182 additions & 0 deletions Settings/ModSettingsUi/ModSettingsModInfoResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using System.IO;
using System.Reflection;
using Godot;
using MegaCrit.Sts2.Core.Assets;
using MegaCrit.Sts2.Core.Modding;
using STS2RitsuLib.Compat;

namespace STS2RitsuLib.Settings
{
/// <summary>
/// Resolves installed mod manifest fields (name, version, icon) for the settings sidebar header.
/// Matches vanilla modding screen: <see cref="ModManifest" /> fields and <c>res://&lt;id&gt;/mod_image.png</c>.
/// </summary>
internal static class ModSettingsModInfoResolver
{
internal static Mod? TryFindMod(string modId)
{
if (string.IsNullOrWhiteSpace(modId))
return null;

foreach (var m in Sts2ModManagerCompat.EnumerateModsForManifestLookup())
{
if (string.Equals(m.manifest?.id, modId, StringComparison.OrdinalIgnoreCase))
return m;
}

foreach (var m in Sts2ModManagerCompat.EnumerateModsForManifestLookup())
{
if (string.IsNullOrWhiteSpace(m.path))
continue;
var trimmed = m.path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var folder = Path.GetFileName(trimmed);
if (string.Equals(folder, modId, StringComparison.OrdinalIgnoreCase))
return m;
}

return null;
}

internal static string ResolveTitle(Mod? mod, string modId)
{
if (mod?.manifest is ModManifest mm && !string.IsNullOrWhiteSpace(mm.name))
return mm.name;

if (mod != null)
{
var n = GetManifestMemberString(mod.manifest, "name", "Name");
if (!string.IsNullOrWhiteSpace(n))
return n;
}

return ModSettingsLocalization.ResolveModName(modId, modId);
}

internal static string? ResolveVersion(Mod? mod)
{
if (mod?.manifest is ModManifest mm && !string.IsNullOrWhiteSpace(mm.version))
return mm.version;
return mod == null ? null : GetManifestMemberString(mod.manifest, "version", "Version");
}

internal static string? ResolveAuthor(Mod? mod)
{
if (mod?.manifest is ModManifest mm && !string.IsNullOrWhiteSpace(mm.author))
return mm.author;
return mod == null ? null : GetManifestMemberString(mod.manifest, "author", "Author");
}

internal static string? ResolveDescription(Mod? mod, int maxLen = 220)
{
string? d;
if (mod?.manifest is ModManifest mm && !string.IsNullOrWhiteSpace(mm.description))
d = mm.description;
else
d = mod == null ? null : GetManifestMemberString(mod.manifest, "description", "Description");
if (string.IsNullOrWhiteSpace(d))
return null;
d = d.Trim().Replace("\r\n", "\n");
return d.Length <= maxLen ? d : d[..maxLen].TrimEnd() + "…";
}

/// <summary>
/// Optional manifest icon paths, then vanilla <c>res://&lt;manifest id&gt;/mod_image.png</c>.
/// </summary>
internal static Texture2D? TryLoadModIcon(Mod? mod, string modId)
{
var fromManifest = TryLoadManifestCustomIcon(mod);
if (fromManifest != null)
return fromManifest;

var id = mod?.manifest is ModManifest mm ? mm.id : null;
foreach (var key in new[] { id, modId })
{
if (string.IsNullOrWhiteSpace(key))
continue;
var tex = TryLoadVanillaModImageRes(key);
if (tex != null)
return tex;
}

return null;
}

private static Texture2D? TryLoadManifestCustomIcon(Mod? mod)
{
if (mod?.manifest == null)
return null;

var path = GetManifestMemberString(mod.manifest, "icon", "Icon", "thumbnail", "Thumbnail", "icon_path",
"iconPath");
if (string.IsNullOrWhiteSpace(path))
return null;

path = path.Trim();
try
{
if (path.StartsWith("res://", StringComparison.Ordinal))
{
if (ResourceLoader.Exists(path))
return PreloadManager.Cache.GetAsset<Texture2D>(path);
return GD.Load<Texture2D>(path);
}

if (File.Exists(path))
{
var img = Image.LoadFromFile(path);
if (img != null)
return ImageTexture.CreateFromImage(img);
}
}
catch
{
// ignored
}

return null;
}

private static Texture2D? TryLoadVanillaModImageRes(string manifestId)
{
var path = $"res://{manifestId}/mod_image.png";
try
{
if (!ResourceLoader.Exists(path))
return null;
return PreloadManager.Cache.GetAsset<Texture2D>(path);
}
catch
{
try
{
return GD.Load<Texture2D>(path);
}
catch
{
return null;
}
}
}

private static string? GetManifestMemberString(object? manifest, params string[] names)
{
if (manifest == null)
return null;

var t = manifest.GetType();
foreach (var name in names)
{
var p = t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p?.GetValue(manifest) is string s && !string.IsNullOrWhiteSpace(s))
return s;

var f = t.GetField(name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (f?.GetValue(manifest) is string s2 && !string.IsNullOrWhiteSpace(s2))
return s2;
}

return null;
}
}
}
Loading