Skip to content

Commit fda3a38

Browse files
committed
everything i guess
1 parent 4047808 commit fda3a38

21 files changed

+635
-0
lines changed

.github/workflows/main.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on: [ "push", "pull_request" ]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-22.04
8+
9+
steps:
10+
- uses: actions/checkout@v4
11+
with:
12+
submodules: true
13+
14+
- name: Setup .NET
15+
uses: actions/setup-dotnet@v4
16+
with:
17+
dotnet-version: 6.x
18+
19+
- name: Run the Cake script
20+
uses: cake-build/cake-action@v2
21+
with:
22+
cake-version: 4.2.0
23+
verbosity: Diagnostic
24+
25+
- uses: actions/upload-artifact@v4
26+
with:
27+
name: ModExplorer.dll
28+
path: ModExplorer/bin/Release/net6.0/ModExplorer.dll

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
bin/
2+
obj/
3+
/packages/
4+
riderModule.iml
5+
/_ReSharper.Caches/

ModExplorer.sln

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModExplorer", "ModExplorer\ModExplorer.csproj", "{11FBC798-BAF5-4EE5-9511-BE6DB0592F99}"
4+
EndProject
5+
Global
6+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7+
Debug|Any CPU = Debug|Any CPU
8+
Release|Any CPU = Release|Any CPU
9+
EndGlobalSection
10+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
11+
{11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal

ModExplorer/Assets.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using ModExplorer.Utilities;
2+
using Reactor.Utilities;
3+
using UnityEngine;
4+
5+
namespace ModExplorer;
6+
7+
public static class Assets
8+
{
9+
public static AssetBundle MainBundle { get; private set; }
10+
public static BundleAsset<GameObject> MenuPrefab => new(MainBundle, "ModExplorerCanvas");
11+
public static BundleAsset<GameObject> ModListElementPrefab => new(MainBundle, "ModListElement");
12+
13+
public static void Load()
14+
{
15+
MainBundle = AssetBundleManager.Load("modexplorer");
16+
}
17+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using BepInEx;
7+
using BepInEx.Unity.IL2CPP.Utils.Collections;
8+
using HarmonyLib;
9+
using Il2CppInterop.Runtime.InteropTypes.Fields;
10+
using ModExplorer.Data;
11+
using Reactor.Utilities;
12+
using Reactor.Utilities.Attributes;
13+
using TMPro;
14+
using UnityEngine;
15+
using UnityEngine.Events;
16+
using UnityEngine.UI;
17+
18+
namespace ModExplorer.Components;
19+
20+
[RegisterInIl2Cpp]
21+
public class ModExplorerComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
22+
{
23+
public static ModExplorerComponent Instance { get; private set; }
24+
25+
public List<ModListElement> elements;
26+
public Il2CppReferenceField<GameObject> mainContainer;
27+
public Il2CppReferenceField<ScrollRect> scrollRect;
28+
public Il2CppReferenceField<TMP_InputField> searchInputField;
29+
public Il2CppReferenceField<RectTransform> modListContainer;
30+
public Il2CppReferenceField<TextMeshProUGUI> infoMainText;
31+
public Il2CppReferenceField<TextMeshProUGUI> infoBottomText;
32+
public Il2CppReferenceField<TextMeshProUGUI> infoLinkText;
33+
public Il2CppReferenceField<Button> closeButton;
34+
public Il2CppReferenceField<Button> folderButton;
35+
36+
public static ModExplorerComponent Create()
37+
{
38+
var explorer = Instantiate(Assets.MenuPrefab.Get()).GetComponent<ModExplorerComponent>();
39+
return explorer;
40+
}
41+
42+
public void Awake()
43+
{
44+
if (Instance)
45+
{
46+
Logger<ModExplorerPlugin>.Warning($"An instance of the explorer already exists. Destroying the new one.");
47+
Destroy(gameObject);
48+
return;
49+
}
50+
Instance = this;
51+
52+
var transition = mainContainer.Value.AddComponent<TransitionOpen>();
53+
transition.OnClose.AddListener((UnityAction)(() => Destroy(gameObject)));
54+
55+
closeButton.Value.onClick = new Button.ButtonClickedEvent();
56+
closeButton.Value.onClick.AddListener((UnityAction)(() =>
57+
{
58+
transition.Close();
59+
closeButton.Value.gameObject.SetActive(false);
60+
folderButton.Value.gameObject.SetActive(false);
61+
}));
62+
63+
folderButton.Value.onClick = new Button.ButtonClickedEvent();
64+
folderButton.Value.onClick.AddListener((UnityAction)(() =>
65+
{
66+
Application.OpenURL("file:///" + Paths.PluginPath.Replace("\\", "/"));
67+
}));
68+
69+
searchInputField.Value.onValueChanged = new TMP_InputField.OnChangeEvent();
70+
searchInputField.Value.onValueChanged.AddListener((UnityAction<string>)SetSearchResults);
71+
72+
infoMainText.Value.text = string.Empty;
73+
infoBottomText.Value.text = string.Empty;
74+
infoLinkText.Value.text = string.Empty;
75+
76+
modListContainer.Value.DestroyChildren();
77+
elements = new();
78+
foreach (var data in ModDataFinder.Mods)
79+
{
80+
var element = ModListElement.Create(modListContainer);
81+
var btn = element.GetComponent<Button>();
82+
element.SetFromData(data);
83+
btn.onClick = new Button.ButtonClickedEvent();
84+
btn.onClick.AddListener((UnityAction)(() => OpenInfoPageFor(element)));
85+
elements.Add(element);
86+
}
87+
}
88+
89+
public void OpenInfoPageFor(ModListElement element)
90+
{
91+
// do are you have stupid
92+
var colors = element.button.Value.colors;
93+
elements.Do(x =>
94+
{
95+
x.button.Value.colors = colors with { normalColor = Color.white };
96+
});
97+
element.button.Value.colors = colors with { normalColor = Palette.AcceptedGreen };
98+
99+
var data = element.ModData;
100+
StringBuilder mainBuilder = new();
101+
mainBuilder.AppendLine(
102+
$"<size=150>{data.Name}</size> <color=grey><font=\"LiberationSans SDF\">v{data.Version}</font></color>");
103+
if (data.Authors.Length > 0)
104+
mainBuilder.AppendLine($"<font=\"LiberationSans SDF\">by {string.Join(", ", data.Authors)}</font>\n");
105+
if (!data.Description.IsNullOrWhiteSpace())
106+
mainBuilder.AppendLine($"<font=\"LiberationSans SDF\">{data.Description}</font>");
107+
infoMainText.Value.text = mainBuilder.ToString();
108+
109+
StringBuilder bottomBuilder = new();
110+
bottomBuilder.AppendLine($"Mod ID: {data.ID}");
111+
if (!data.License.IsNullOrWhiteSpace())
112+
bottomBuilder.AppendLine($"License: {data.License}");
113+
if (data.Dependencies.Length > 0)
114+
bottomBuilder.AppendLine($"Dependencies: {string.Join(", ", data.Dependencies)}");
115+
infoBottomText.Value.text = bottomBuilder.ToString();
116+
117+
StringBuilder linkBuilder = new();
118+
foreach (var (type, link) in data.Links)
119+
{
120+
linkBuilder.AppendLine($"{type}");
121+
}
122+
infoLinkText.Value.text = linkBuilder.ToString();
123+
}
124+
125+
public void SetSearchResults(string search)
126+
{
127+
if (string.IsNullOrEmpty(search))
128+
{
129+
elements.Do(x => x.gameObject.SetActive(true));
130+
return;
131+
}
132+
133+
var valid = elements
134+
.Where(x => x.ModData.Name.Contains(search, StringComparison.InvariantCultureIgnoreCase) ||
135+
x.ModData.Tags.Any(t => t.Contains(search, StringComparison.InvariantCultureIgnoreCase)))
136+
.OrderBy(x => x.ModData.Name)
137+
.ThenBy(x => x.ModData.Name.Equals(search, StringComparison.OrdinalIgnoreCase))
138+
.ThenBy(x => x.ModData.Name.Contains(search, StringComparison.InvariantCultureIgnoreCase))
139+
.ToList();
140+
141+
elements.Do(x => x.gameObject.SetActive(false));
142+
valid.Do(x => x.gameObject.SetActive(true));
143+
}
144+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using Il2CppInterop.Runtime.Attributes;
3+
using Il2CppInterop.Runtime.InteropTypes.Fields;
4+
using ModExplorer.Data;
5+
using Reactor.Utilities;
6+
using Reactor.Utilities.Attributes;
7+
using TMPro;
8+
using UnityEngine;
9+
using UnityEngine.UI;
10+
11+
namespace ModExplorer.Components;
12+
13+
[RegisterInIl2Cpp]
14+
public class ModListElement(IntPtr cppPtr) : MonoBehaviour(cppPtr)
15+
{
16+
public ModData ModData;
17+
18+
public Il2CppReferenceField<Button> button;
19+
public Il2CppReferenceField<TextMeshProUGUI> text;
20+
public Il2CppReferenceField<Image> icon;
21+
public Il2CppReferenceField<Button> configButton;
22+
23+
public static ModListElement Create(Transform parent)
24+
{
25+
var element = Instantiate(Assets.ModListElementPrefab.Get(), parent);
26+
return element.GetComponent<ModListElement>();
27+
}
28+
29+
[HideFromIl2Cpp]
30+
public void SetFromData(ModData data)
31+
{
32+
ModData = data;
33+
name = data.ID;
34+
text.Value.text =
35+
$"{data.Name}\n<color=grey><font=\"LiberationSans SDF\"><size=24>v{data.Version}</size></font></color>";
36+
if (data.Icon)
37+
icon.Value.sprite = data.Icon;
38+
configButton.Value.gameObject.SetActive(false); // Temporary
39+
}
40+
}

ModExplorer/Data/ModData.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Collections.Generic;
2+
using System.Text.Json.Serialization;
3+
using UnityEngine;
4+
5+
namespace ModExplorer.Data;
6+
7+
public class ModData
8+
{
9+
public string Name { get; set; }
10+
[JsonIgnore]
11+
public string ID { get; set; }
12+
public string Description { get; set; }
13+
public string[] Tags { get; set; } = [];
14+
[JsonIgnore]
15+
public string Version { get; set; }
16+
public string License { get; set; }
17+
public string[] Authors { get; set; } = [];
18+
[JsonIgnore]
19+
public string[] Dependencies { get; set; } = [];
20+
public Dictionary<string, string> Links { get; set; } = [];
21+
22+
[JsonPropertyName("Icon")]
23+
public string IconResource { get; set; }
24+
[JsonIgnore]
25+
public Sprite Icon { get; set; }
26+
}

ModExplorer/Data/ModDataFinder.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text.Json;
7+
using BepInEx;
8+
using BepInEx.Unity.IL2CPP;
9+
using ModExplorer.Utilities;
10+
using Reactor.Utilities;
11+
using Reactor.Utilities.Extensions;
12+
using UnityEngine;
13+
using Object = UnityEngine.Object;
14+
15+
namespace ModExplorer.Data;
16+
17+
public static class ModDataFinder
18+
{
19+
private static List<ModData> _mods { get; set; } = new();
20+
public static IReadOnlyList<ModData> Mods => _mods;
21+
22+
internal static void Initialize()
23+
{
24+
var plugins = IL2CPPChainloader.Instance.Plugins;
25+
foreach (var (id, info) in plugins)
26+
{
27+
try
28+
{
29+
ModData data = null;
30+
var assembly = info.Instance.GetType().Assembly;
31+
foreach (var name in assembly.GetManifestResourceNames())
32+
{
33+
if (name.EndsWith(".modinfo.json"))
34+
{
35+
using var stream = assembly.GetManifestResourceStream(name)!;
36+
using StreamReader sr = new StreamReader(stream);
37+
string json = sr.ReadToEnd();
38+
data = JsonSerializer.Deserialize<ModData>(json);
39+
}
40+
}
41+
42+
data ??= new ModData();
43+
data.ID = id;
44+
data.Name ??= info.Metadata.Name;
45+
data.Version = info.Metadata.Version.WithoutBuild().ToString();
46+
data.Dependencies = info.Dependencies
47+
.Where(x => x.Flags == BepInDependency.DependencyFlags.HardDependency)
48+
.Select(x => x.DependencyGUID)
49+
.ToArray();
50+
51+
if (TryGetIcon(assembly, data, out var icon))
52+
{
53+
data.Icon = icon;
54+
}
55+
56+
_mods.Add(data);
57+
Logger<ModExplorerPlugin>.Info($"ModData for {id} created");
58+
}
59+
catch (Exception e)
60+
{
61+
Logger<ModExplorerPlugin>.Error($"Unable to register ModData for {id}\n{e.ToString()}");
62+
}
63+
}
64+
}
65+
66+
private static bool TryGetIcon(Assembly assembly, ModData data, out Sprite icon)
67+
{
68+
icon = null;
69+
if (data.IconResource.IsNullOrWhiteSpace())
70+
return false;
71+
72+
try
73+
{
74+
icon = SpriteTools.LoadSpriteFromPath(data.IconResource, assembly, 100f);
75+
}
76+
catch (Exception e)
77+
{
78+
Logger<ModExplorerPlugin>.Error($"Unable to get icon for {data.ID}\n{e.ToString()}");
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
}

0 commit comments

Comments
 (0)