1
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
13
.idea/.idea.Plugin.ClientModLoader/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.Plugin.ClientModLoader/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider ignored files
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/.idea.Plugin.ClientModLoader.iml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
4
.idea/.idea.Plugin.ClientModLoader/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.Plugin.ClientModLoader/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
6
.idea/.idea.Plugin.ClientModLoader/.idea/git_toolbox_blame.xml
generated
Normal file
6
.idea/.idea.Plugin.ClientModLoader/.idea/git_toolbox_blame.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxBlameSettings">
|
||||
<option name="version" value="2" />
|
||||
</component>
|
||||
</project>
|
8
.idea/.idea.Plugin.ClientModLoader/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.Plugin.ClientModLoader/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
6
.idea/.idea.Plugin.ClientModLoader/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.Plugin.ClientModLoader/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
13
Plugin.ClientModLoader.sln.DotSettings.user
Normal file
13
Plugin.ClientModLoader.sln.DotSettings.user
Normal file
@@ -0,0 +1,13 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImGui_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea9f3616f41243ab9c7f97ef274feb9c4d400_003F9d_003Fc14ad916_003FImGui_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMemoryExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F76803a04e31b4fee99d90bcbfc5a6bdde8e930_003Fdd_003Fd6f3a4b9_003FMemoryExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMyDefinitionManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F34f70babce284fbd8bd6b4a545e86609a17e00_003F15_003Fdda8a4a9_003FMyDefinitionManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMyObjectBuilder_005FCheckpoint_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5fc89e31eedc4d00a398eb23fa21b4c13ffe00_003Fc4_003F46a79233_003FMyObjectBuilder_005FCheckpoint_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMyWorkshop_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F34f70babce284fbd8bd6b4a545e86609a17e00_003F27_003F40d62d28_003FMyWorkshop_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARange_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F36b5151d8b20b97ce4d68ff12cc15eb23cd5e58474e6282da7a2fa67363512_003FRange_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASteamUGCQueryCompleted_005Ft_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0057baf19dee4a4b8d37fd0c46f1b3325ba00_003Fb4_003F9a9c7cd1_003FSteamUGCQueryCompleted_005Ft_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMyLanguagesEnum_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1eb8878a2e6343dc94c75733597801dea0400_003Fdf_003Fd3272324_003FMyLanguagesEnum_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACallResult_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0057baf19dee4a4b8d37fd0c46f1b3325ba00_003F90_003Faeefa61e_003FCallResult_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASteamUGCDetails_005Ft_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0057baf19dee4a4b8d37fd0c46f1b3325ba00_003F2a_003F258d424e_003FSteamUGCDetails_005Ft_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMyTexts_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1eb8878a2e6343dc94c75733597801dea0400_003Fa1_003F7f551730_003FMyTexts_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7102cd0ffb8973777e61b1942c3fffac7e14016a511d055c3adf73ff91748_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
3
Plugin.ClientModLoader.slnx
Normal file
3
Plugin.ClientModLoader.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="Plugin.ClientModLoader/Plugin.ClientModLoader.csproj" />
|
||||
</Solution>
|
87
Plugin.ClientModLoader/ModInjector.cs
Normal file
87
Plugin.ClientModLoader/ModInjector.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Definitions;
|
||||
using Sandbox.Engine.Networking;
|
||||
using Sandbox.Game.World;
|
||||
using VRage.Game;
|
||||
|
||||
namespace Plugin.ClientModLoader;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class ModInjector
|
||||
{
|
||||
public static HashSet<ulong> Mods = [];
|
||||
private static readonly List<MyObjectBuilder_Checkpoint.ModItem> AdditionalFilledModItems = [];
|
||||
|
||||
[HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadWorldModsBlockingInternal))]
|
||||
[HarmonyPrefix]
|
||||
private static void DownloadModsBlockingPrefix(ref List<MyObjectBuilder_Checkpoint.ModItem> mods, ref List<MyObjectBuilder_Checkpoint.ModItem> __state)
|
||||
{
|
||||
AdditionalFilledModItems.Clear();
|
||||
__state = mods;
|
||||
AppendToList(ref mods);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadWorldModsBlockingInternal))]
|
||||
[HarmonyPostfix]
|
||||
private static void DownloadModsBlockingPostfix(List<MyObjectBuilder_Checkpoint.ModItem> mods, List<MyObjectBuilder_Checkpoint.ModItem> __state)
|
||||
{
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
var index = __state.FindIndex(b =>
|
||||
b.PublishedFileId == mod.PublishedFileId && b.PublishedServiceName == mod.PublishedServiceName);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
var stateMod = __state[index];
|
||||
stateMod.SetModData(mod.GetModData());
|
||||
__state[index] = stateMod;
|
||||
}
|
||||
else if (Mods.Contains(mod.PublishedFileId))
|
||||
AdditionalFilledModItems.Add(mod);
|
||||
else
|
||||
__state.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MyDefinitionManager), nameof(MyDefinitionManager.LoadData))]
|
||||
[HarmonyPrefix]
|
||||
private static void LoadDefinitionsPrefix(ref List<MyObjectBuilder_Checkpoint.ModItem> mods) => AppendToList(ref mods);
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptManager), nameof(MyScriptManager.LoadData))]
|
||||
[HarmonyTranspiler]
|
||||
private static IEnumerable<CodeInstruction> LoadScriptsTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
|
||||
{
|
||||
var staticGetter = AccessTools.PropertyGetter(typeof(MySession), nameof(MySession.Static));
|
||||
var modsGetter = AccessTools.PropertyGetter(typeof(MySession), nameof(MySession.Mods));
|
||||
return new CodeMatcher(instructions, generator)
|
||||
.Start()
|
||||
.DeclareLocal(typeof(List<MyObjectBuilder_Checkpoint.ModItem>), out var modsLocal)
|
||||
.CreateLabel(out var start)
|
||||
.InsertAndAdvance(
|
||||
new(OpCodes.Call, staticGetter),
|
||||
new(OpCodes.Call, modsGetter),
|
||||
new(OpCodes.Stloc, modsLocal),
|
||||
new(OpCodes.Ldloc, modsLocal),
|
||||
new(OpCodes.Brfalse, start),
|
||||
new(OpCodes.Ldloca, modsLocal),
|
||||
CodeInstruction.Call(typeof(ModInjector), nameof(AppendToList))
|
||||
)
|
||||
.MatchStartForward(CodeMatch.Calls(staticGetter), CodeMatch.Calls(modsGetter))
|
||||
.Repeat(a => a
|
||||
.SetAndAdvance(OpCodes.Ldloc, modsLocal)
|
||||
.SetAndAdvance(OpCodes.Nop, null))
|
||||
.Instructions();
|
||||
}
|
||||
|
||||
private static void AppendToList(ref List<MyObjectBuilder_Checkpoint.ModItem> mods)
|
||||
{
|
||||
// copy
|
||||
mods = mods.ToList();
|
||||
|
||||
mods.AddRange(AdditionalFilledModItems.Count > 0
|
||||
? AdditionalFilledModItems
|
||||
: Mods.Select(mod => new MyObjectBuilder_Checkpoint.ModItem(mod, "Steam")));
|
||||
}
|
||||
}
|
321
Plugin.ClientModLoader/ModListComponent.cs
Normal file
321
Plugin.ClientModLoader/ModListComponent.cs
Normal file
@@ -0,0 +1,321 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Net.Http.Json;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using CringePlugins.Abstractions;
|
||||
using ImGuiNET;
|
||||
using Plugin.ClientModLoader.Utils;
|
||||
using Steamworks;
|
||||
using VRage;
|
||||
using static ImGuiNET.ImGui;
|
||||
|
||||
namespace Plugin.ClientModLoader;
|
||||
|
||||
public class ModListComponent : IRenderComponent
|
||||
{
|
||||
private readonly string _configPath;
|
||||
private readonly JsonSerializerOptions _options = new(JsonSerializerDefaults.Web);
|
||||
|
||||
private bool _visible;
|
||||
private string _searchQuery = string.Empty;
|
||||
private Task? _searchTask;
|
||||
|
||||
private PublishedFileId_t[] _mods = [];
|
||||
private ImmutableDictionary<ulong, WhitelistItem> _whitelist = ImmutableDictionary<ulong, WhitelistItem>.Empty;
|
||||
|
||||
private List<ModItem> _modList = [];
|
||||
private ModItem? _selected;
|
||||
public HashSet<ulong> Installed { get; }
|
||||
|
||||
public bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set => _visible = value;
|
||||
}
|
||||
|
||||
public ModListComponent(string configPath)
|
||||
{
|
||||
_configPath = configPath;
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
using var stream = File.OpenRead(configPath);
|
||||
Installed = JsonSerializer.Deserialize<HashSet<ulong>>(stream, _options)!;
|
||||
}
|
||||
else Installed = [];
|
||||
|
||||
_searchTask = DownloadWhitelist();
|
||||
}
|
||||
|
||||
private async Task DownloadWhitelist()
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
var response =
|
||||
await client.GetFromJsonAsync<Whitelist>(
|
||||
"https://github.com/sepluginloader/sepluginloader.github.io/raw/refs/heads/main/plugins.json");
|
||||
|
||||
if (response is null) return;
|
||||
|
||||
var mods = new List<PublishedFileId_t>();
|
||||
var builder = ImmutableDictionary.CreateBuilder<ulong, WhitelistItem>();
|
||||
|
||||
foreach (var item in response.Mods)
|
||||
{
|
||||
if (!ulong.TryParse(item.Id, out var id)) continue;
|
||||
|
||||
mods.Add(new PublishedFileId_t(id));
|
||||
builder.Add(id, item);
|
||||
}
|
||||
|
||||
_mods = mods.ToArray();
|
||||
_whitelist = builder.ToImmutable();
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
public void OnFrame()
|
||||
{
|
||||
if (!Visible) return;
|
||||
SetNextWindowSize(new(700, 500), ImGuiCond.FirstUseEver);
|
||||
if (!Begin("Mod List", ref _visible)) return;
|
||||
|
||||
try
|
||||
{
|
||||
InputText("##searchbox", ref _searchQuery, 256);
|
||||
|
||||
SameLine();
|
||||
|
||||
if (Button("Search"))
|
||||
{
|
||||
_searchTask = RefreshAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
Spacing();
|
||||
|
||||
switch (_searchTask)
|
||||
{
|
||||
case { IsCompleted: false }:
|
||||
TextDisabled("Loading...");
|
||||
return;
|
||||
case { IsCompletedSuccessfully: false }:
|
||||
{
|
||||
TextDisabled("Failed to load plugins list");
|
||||
if (_searchTask.Exception is null) return;
|
||||
|
||||
foreach (var exception in _searchTask.Exception.InnerExceptions)
|
||||
{
|
||||
TextWrapped($"{exception.GetType()}: {exception.Message}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_modList.Count == 0)
|
||||
{
|
||||
TextDisabled("Nothing found");
|
||||
return;
|
||||
}
|
||||
|
||||
BeginChild("List", new(400, 0), ImGuiChildFlags.Border | ImGuiChildFlags.ResizeX);
|
||||
{
|
||||
if (BeginTable("AvailableModsTable", 3,
|
||||
ImGuiTableFlags.ScrollY | ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
TableSetupColumn("Name", ImGuiTableColumnFlags.None, .75f);
|
||||
TableSetupColumn("Installed", ImGuiTableColumnFlags.None, .25f);
|
||||
TableHeadersRow();
|
||||
|
||||
foreach (var mod in _modList)
|
||||
{
|
||||
TableNextRow();
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
var selected = _selected == mod;
|
||||
|
||||
if (Selectable(mod.Name, ref selected, ImGuiSelectableFlags.SpanAllColumns))
|
||||
{
|
||||
_selected = selected ? mod : null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(mod.Summary) && IsItemHovered(ImGuiHoveredFlags.ForTooltip))
|
||||
{
|
||||
SetTooltip(mod.Summary);
|
||||
}
|
||||
|
||||
TableNextColumn();
|
||||
|
||||
var installed = Installed.Contains(mod.Id);
|
||||
TextColored(installed ? new(0f, 1f, 0f, 1f) : new(1f, 0f, 0f, 1f),
|
||||
installed ? "Installed" : "Not Installed");
|
||||
}
|
||||
|
||||
EndTable();
|
||||
}
|
||||
|
||||
EndChild();
|
||||
}
|
||||
|
||||
SameLine();
|
||||
|
||||
BeginGroup();
|
||||
|
||||
BeginChild("Mod View", new(0, -GetFrameHeightWithSpacing())); // Leave room for 1 line below us
|
||||
|
||||
if (_selected is not null)
|
||||
{
|
||||
Text(_selected.Name);
|
||||
Separator();
|
||||
if (BeginTabBar("##ModViewTabs"))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_selected.Description) && BeginTabItem("Description"))
|
||||
{
|
||||
TextWrapped(_selected.Description);
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem("Mod Description"))
|
||||
{
|
||||
TextWrapped(string.IsNullOrEmpty(_selected.ModDescription) ? "Nothing." : _selected.ModDescription);
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
if (BeginTabItem("Details"))
|
||||
{
|
||||
TextLinkOpenURL("Mod URL", _selected.Url);
|
||||
|
||||
Text("Author:");
|
||||
SameLine();
|
||||
TextWrapped(_selected.Author);
|
||||
|
||||
Text("Created:");
|
||||
SameLine();
|
||||
TextWrapped(DateTimeOffset.FromUnixTimeSeconds(_selected.Created).ToString("g"));
|
||||
|
||||
Text("Updated:");
|
||||
SameLine();
|
||||
TextWrapped(DateTimeOffset.FromUnixTimeSeconds(_selected.Updated).ToString("g"));
|
||||
|
||||
EndTabItem();
|
||||
}
|
||||
|
||||
EndTabBar();
|
||||
}
|
||||
}
|
||||
|
||||
EndChild();
|
||||
|
||||
if (_selected is not null)
|
||||
{
|
||||
var installed = Installed.Contains(_selected.Id);
|
||||
if (Button(installed ? "Uninstall" : "Install"))
|
||||
{
|
||||
if (installed)
|
||||
Installed.Remove(_selected.Id);
|
||||
else
|
||||
Installed.Add(_selected.Id);
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
finally
|
||||
{
|
||||
End();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Save()
|
||||
{
|
||||
using var stream = File.Create(_configPath);
|
||||
|
||||
JsonSerializer.Serialize(stream, Installed, _options);
|
||||
}
|
||||
|
||||
private async Task RefreshAsync()
|
||||
{
|
||||
var handle = SteamUGC.CreateQueryUGCDetailsRequest(_mods, (uint)_mods.Length);
|
||||
|
||||
if (handle.m_UGCQueryHandle == ulong.MaxValue)
|
||||
throw new Exception("Failed to create UGC request");
|
||||
|
||||
try
|
||||
{
|
||||
SteamUGC.SetSearchText(handle, _searchQuery);
|
||||
SteamUGC.SetReturnLongDescription(handle, true);
|
||||
// TODO font support for other languages
|
||||
// SteamUGC.SetLanguage(handle, MapLanguage(MySandboxGame.Config.Language));
|
||||
|
||||
var call = SteamUGC.SendQueryUGCRequest(handle);
|
||||
|
||||
var result = await CallResultAsync<SteamUGCQueryCompleted_t>.Create(call);
|
||||
|
||||
var list = new List<ModItem>((int)result.m_unNumResultsReturned);
|
||||
|
||||
for (uint i = 0; i < result.m_unNumResultsReturned; i++)
|
||||
{
|
||||
if (!SteamUGC.GetQueryUGCResult(handle, i, out var details))
|
||||
break;
|
||||
if (details.m_eResult != EResult.k_EResultOK)
|
||||
continue;
|
||||
|
||||
var title = details.m_rgchTitle;
|
||||
|
||||
if (!string.IsNullOrEmpty(_searchQuery) && !title.Contains(_searchQuery, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var item = _whitelist[details.m_nPublishedFileId.m_PublishedFileId];
|
||||
|
||||
var description = details.m_rgchDescription;
|
||||
list.Add(new(details.m_nPublishedFileId.m_PublishedFileId, title,
|
||||
string.IsNullOrEmpty(item.Tooltip)
|
||||
? description[..Math.Min(description.Length - 1, 255)]
|
||||
: item.Tooltip,
|
||||
item.Description,
|
||||
description, $"https://steamcommunity.com/sharedfiles/filedetails/?id={details.m_nPublishedFileId.m_PublishedFileId}",
|
||||
details.m_rtimeCreated, details.m_rtimeUpdated, item.Author));
|
||||
}
|
||||
|
||||
_modList = list;
|
||||
}
|
||||
finally
|
||||
{
|
||||
SteamUGC.ReleaseQueryUGCRequest(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private static string MapLanguage(MyLanguagesEnum lang) =>
|
||||
lang switch
|
||||
{
|
||||
MyLanguagesEnum.Czech => "czech",
|
||||
MyLanguagesEnum.German => "german",
|
||||
MyLanguagesEnum.Russian => "russian",
|
||||
MyLanguagesEnum.Spanish_Spain => "spanish",
|
||||
MyLanguagesEnum.French => "french",
|
||||
MyLanguagesEnum.Italian => "italian",
|
||||
MyLanguagesEnum.Danish => "danish",
|
||||
MyLanguagesEnum.Dutch => "dutch",
|
||||
MyLanguagesEnum.Polish => "polish",
|
||||
MyLanguagesEnum.Finnish => "finnish",
|
||||
MyLanguagesEnum.Hungarian => "hungarian",
|
||||
MyLanguagesEnum.Portuguese_Brazil => "brazilian",
|
||||
MyLanguagesEnum.Norwegian => "norwegian",
|
||||
MyLanguagesEnum.Spanish_HispanicAmerica => "latam",
|
||||
MyLanguagesEnum.Swedish => "swedish",
|
||||
MyLanguagesEnum.Romanian => "romanian",
|
||||
MyLanguagesEnum.Ukrainian => "ukrainian",
|
||||
MyLanguagesEnum.Turkish => "turkish",
|
||||
MyLanguagesEnum.ChineseChina => "schinese",
|
||||
MyLanguagesEnum.Japanese => "japanese",
|
||||
_ => "english",
|
||||
};
|
||||
|
||||
private record ModItem(ulong Id, string Name, string Summary, string? Description, string ModDescription, string Url, long Created, long Updated, string Author);
|
||||
|
||||
private record WhitelistItem(string Id, string? Tooltip, string Author, string? Description);
|
||||
|
||||
private record Whitelist(ImmutableArray<WhitelistItem> Mods);
|
||||
}
|
21
Plugin.ClientModLoader/Plugin.ClientModLoader.csproj
Normal file
21
Plugin.ClientModLoader/Plugin.ClientModLoader.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<RestoreAdditionalProjectSources>https://ng.zznty.ru/v3/index.json</RestoreAdditionalProjectSources>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CringePlugins" Version="*" ExcludeAssets="runtime; native"/>
|
||||
<PackageReference Include="Steamworks.NET" Version="20.1.0" ExcludeAssets="runtime; native" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
34
Plugin.ClientModLoader/Plugin.cs
Normal file
34
Plugin.ClientModLoader/Plugin.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using CringePlugins.Render;
|
||||
using HarmonyLib;
|
||||
using VRage.Plugins;
|
||||
|
||||
namespace Plugin.ClientModLoader;
|
||||
|
||||
public class Plugin : IPlugin
|
||||
{
|
||||
private ModListComponent? _component;
|
||||
|
||||
private readonly DirectoryInfo _dir =
|
||||
new(Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"CringeLauncher"));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Init(object gameInstance)
|
||||
{
|
||||
new Harmony("Plugin.ClientModLoader").PatchAll(typeof(Plugin).Assembly);
|
||||
RenderHandler.Current.RegisterComponent(_component = new ModListComponent(Path.Join(_dir.FullName, "mods.json")));
|
||||
ModInjector.Mods = _component.Installed;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public void OpenConfigDialog()
|
||||
{
|
||||
if (_component != null) _component.Visible = true;
|
||||
}
|
||||
}
|
32
Plugin.ClientModLoader/Utils/CallResultAsync.cs
Normal file
32
Plugin.ClientModLoader/Utils/CallResultAsync.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Steamworks;
|
||||
|
||||
namespace Plugin.ClientModLoader.Utils;
|
||||
|
||||
public static class CallResultAsync<T> where T : struct
|
||||
{
|
||||
public static async Task<T> Create(SteamAPICall_t call)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
using var callResult = CallResult<T>.Create((args, failed) =>
|
||||
{
|
||||
if (failed)
|
||||
{
|
||||
ref var result = ref Unsafe.As<T, GenericCallResult>(ref args);
|
||||
tcs.TrySetException(new Exception($"CallResult failed: {result.m_eResult}"));
|
||||
}
|
||||
else tcs.TrySetResult(args);
|
||||
});
|
||||
callResult.Set(call);
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
private struct GenericCallResult
|
||||
{
|
||||
public UGCQueryHandle_t m_handle;
|
||||
public EResult m_eResult;
|
||||
}
|
||||
}
|
201
Plugin.ClientModLoader/packages.lock.json
Normal file
201
Plugin.ClientModLoader/packages.lock.json
Normal file
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net9.0-windows7.0": {
|
||||
"CringePlugins": {
|
||||
"type": "Direct",
|
||||
"requested": "[*, )",
|
||||
"resolved": "0.1.51",
|
||||
"contentHash": "6v6FfWlrLge3E67R75P/svFtvbAodzffwMyISUYFrZK6XTYsLPLExegnHswPNMC1PFbwD5ukhXYF9eUgIluubQ==",
|
||||
"dependencies": {
|
||||
"ImGui.NET.DirectX": "1.91.0.1",
|
||||
"Lib.Harmony.Thin": "2.3.4-torch",
|
||||
"NLog": "5.4.0",
|
||||
"NuGet": "0.1.51",
|
||||
"SharedCringe": "0.1.51",
|
||||
"SpaceEngineersDedicated.ReferenceAssemblies": "1.206.30",
|
||||
"dnlib": "4.4.0"
|
||||
}
|
||||
},
|
||||
"Steamworks.NET": {
|
||||
"type": "Direct",
|
||||
"requested": "[20.1.0, )",
|
||||
"resolved": "20.1.0",
|
||||
"contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ=="
|
||||
},
|
||||
"CringeBootstrap.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.51",
|
||||
"contentHash": "dpA0Tr7Ir+6WzlVq98on0kxcEwpUr/arl0+8EX3QPsO2tXYTuRJRDMqy4SlMjN5t+xDfT/iILzkcUldIfogE8A=="
|
||||
},
|
||||
"dnlib": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "cKHI720q+zfEEvzklWVGt6B0TH3AibAyJbpUJl4U6KvTP13tycfnqJpkGHRZ/oQ45BTIoIxIwltHIJVDN+iCqQ=="
|
||||
},
|
||||
"ImGui.NET.DirectX": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.91.0.1",
|
||||
"contentHash": "PpW1gQ9g97h6Hm/h/tkSBOmsBYgGwN8wKNmlJomcQFD/zRY1HPkJZz18XRSfRLHPmH2eeh4hhhZv1KHug7dF9g==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.5.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Lib.Harmony.Thin": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.3.4-torch",
|
||||
"contentHash": "UnLUnLLiXfHZdKa1zhi6w8cl8tJTrpVixLtvjFEVtlDA6Rkf06OcZ2gSidcbcgKjTcR+fk5Qsdos3mU5oohzfg==",
|
||||
"dependencies": {
|
||||
"MonoMod.Core": "1.2.2",
|
||||
"System.Text.Json": "9.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
|
||||
},
|
||||
"Mono.Cecil": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.11.5",
|
||||
"contentHash": "fxfX+0JGTZ8YQeu1MYjbBiK2CYTSzDyEeIixt+yqKKTn7FW8rv7JMY70qevup4ZJfD7Kk/VG/jDzQQTpfch87g=="
|
||||
},
|
||||
"MonoMod.Backports": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.2",
|
||||
"contentHash": "baYlNy8n8kmaNhNvqmZ/dIPOeO1r9//dG1i2WbunMWtWZ2EKtIgmXaS+ZzphzTsikkGnoD4Jwr5g0TVdpDjgpw==",
|
||||
"dependencies": {
|
||||
"MonoMod.ILHelpers": "1.1.0"
|
||||
}
|
||||
},
|
||||
"MonoMod.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.2.2",
|
||||
"contentHash": "3R9mQ7TxeQQBFLtGTR2VIDSBGx/I7d8RYpxvW1Mot0/PB6+UOnHr+lkw7dj8cus8YSnLdCFT+CG4JW89daEvjg==",
|
||||
"dependencies": {
|
||||
"Mono.Cecil": "0.11.5",
|
||||
"MonoMod.Backports": "1.1.2",
|
||||
"MonoMod.ILHelpers": "1.1.0",
|
||||
"MonoMod.Utils": "25.0.8"
|
||||
}
|
||||
},
|
||||
"MonoMod.ILHelpers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.1.0",
|
||||
"contentHash": "L2FWjhTrv7tcIxshfZ+M3OcaNr4cNw0IwiVZEgwqRnZ5QAN3+RrNJ8ZwCzwXUWyPDqooJxMcjjg8PsSYUiNBjQ=="
|
||||
},
|
||||
"MonoMod.Utils": {
|
||||
"type": "Transitive",
|
||||
"resolved": "25.0.8",
|
||||
"contentHash": "8wfyawgvGqUvfchAj7/gPazAjXO8CWNyXH+C0az+hgJH/80zVL5LxDhQMz0NQTyoCEJWU6gNBGc7CL+G7vCbaQ==",
|
||||
"dependencies": {
|
||||
"Mono.Cecil": "0.11.5",
|
||||
"MonoMod.Backports": "1.1.2",
|
||||
"MonoMod.ILHelpers": "1.1.0"
|
||||
}
|
||||
},
|
||||
"NLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.4.0",
|
||||
"contentHash": "LwMcGSW3soF3/SL68rlJN3Eh3ktrAPycC3zZR/07OYBPraZUu0bygEC7kIN10lUQgMXT4s84Fi1chglGdGrQEg=="
|
||||
},
|
||||
"NuGet": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.51",
|
||||
"contentHash": "fwUsNKy/EQB26JocU3suqYqFqbQ8mb63Ri5NGf7OPy7zLooxHNUYaN+MuBhbshveP+8IKdlPfc8UkrFyzKK8PA==",
|
||||
"dependencies": {
|
||||
"NuGet.Frameworks": "6.13.2",
|
||||
"NuGet.Versioning": "6.13.2",
|
||||
"System.Linq.Async": "6.0.1"
|
||||
}
|
||||
},
|
||||
"NuGet.Frameworks": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.13.2",
|
||||
"contentHash": "SQKUf56j7IdJL//fU7fcmn234awjRA2qLvgbfs/TxmY7oJrNOxaaDxASyCbS4eJW3LCOd6ONHMZ/cPlbQh4xVg=="
|
||||
},
|
||||
"NuGet.Versioning": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.13.2",
|
||||
"contentHash": "pGYNyvCVM+Z9jITTiJiuxFC8oJXFdh2k25ZDV4tSAOSuKyAWvh1VcfJy0WZGWdI6J7Avkbl0qra7XENYFSy4Ng=="
|
||||
},
|
||||
"protobuf-net": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.0",
|
||||
"contentHash": "kTGOK0E87473sOImOjgZOnz3kTC2aMLffoRWQLYNuBLJnwNNmjanF9IkevZ9Q7yYLeABQfcF3BpeepuMntMVNw=="
|
||||
},
|
||||
"SharedCringe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.51",
|
||||
"contentHash": "9QargvU/hAVUXt1QZaeCHExmoYfrtHP9Vq9X5oMbIX8lOCmjulZx0rSuwoDR46/I1eu4+xVvM78/u+F4UiP6uw==",
|
||||
"dependencies": {
|
||||
"CringeBootstrap.Abstractions": "0.1.51",
|
||||
"NLog": "5.4.0",
|
||||
"SpaceEngineersDedicated.ReferenceAssemblies": "1.206.30"
|
||||
}
|
||||
},
|
||||
"SharpDX": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.2.0-keen-cringe",
|
||||
"contentHash": "LaJN3h1Gi1FWVdef2I5WtOH9gwzKCBniH0CragarbkN2QheYY6Lqm+91PcOfp1w/4wdVb+k8Kjv3sO393Tphtw=="
|
||||
},
|
||||
"SpaceEngineersDedicated.ReferenceAssemblies": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.206.30",
|
||||
"contentHash": "xk/EgMhbG7oT4fPzW1DcFT8tYkxJFPK3+j+t4vms9a/wz8cCmszbilA2Y+JWIpmauUDcfovX8eqAOKlgz3dpcg==",
|
||||
"dependencies": {
|
||||
"SharpDX": "4.2.0-keen-cringe",
|
||||
"protobuf-net": "1.0.0"
|
||||
}
|
||||
},
|
||||
"System.Buffers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.1",
|
||||
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
|
||||
},
|
||||
"System.Linq.Async": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.1",
|
||||
"contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Bcl.AsyncInterfaces": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Numerics.Vectors": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.0",
|
||||
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"System.Text.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "9.0.0",
|
||||
"contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A=="
|
||||
}
|
||||
},
|
||||
"net9.0-windows7.0/win-x64": {
|
||||
"Steamworks.NET": {
|
||||
"type": "Direct",
|
||||
"requested": "[20.1.0, )",
|
||||
"resolved": "20.1.0",
|
||||
"contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ=="
|
||||
},
|
||||
"ImGui.NET.DirectX": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.91.0.1",
|
||||
"contentHash": "PpW1gQ9g97h6Hm/h/tkSBOmsBYgGwN8wKNmlJomcQFD/zRY1HPkJZz18XRSfRLHPmH2eeh4hhhZv1KHug7dF9g==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.5.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user