Skip to content

Commit 6c092b8

Browse files
Polyscript config (#110)
* Add Config * Actually load the mod # Conflicts: # src/Loader.cs * Slight refactor and fix of warning * Use System.Text.json instead of newtonsoft * add missing using statement * Fix 2 warnings * Fix bug of game crashing on assembly load... * other languages in localization * rename polyscriptmod to polymod * Add gld config * Actually load config * Revert "rename polyscriptmod to polymod" This reverts commit c5154ec. * Fix bug related to game crashing on launch * On error, set mod to errrror instead of crashing * Change naming for @JKDev * change name of polyScriptMod to PolyScript * fix * minor fix
1 parent dfa7f64 commit 6c092b8

8 files changed

Lines changed: 335 additions & 50 deletions

File tree

FodyWeavers.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
3+
<Costura>
4+
<IncludeAssemblies>
5+
Scriban
6+
</IncludeAssemblies>
7+
</Costura>
8+
</Weavers>

PolyMod.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFramework>net6.0</TargetFramework>
44
<ImplicitUsings>enable</ImplicitUsings>
@@ -20,6 +20,10 @@
2020

2121
<ItemGroup>
2222
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.738" />
23+
<PackageReference Include="Costura.Fody" Version="6.0.0">
24+
<PrivateAssets>all</PrivateAssets>
25+
</PackageReference>
26+
<PackageReference Include="Scriban" Version="6.2.1" />
2327
<PackageReference Include="TheBattleOfPolytopia" Version="$(PolytopiaVersion)-738" />
2428
<EmbeddedResource Include="resources\*.*" />
2529
</ItemGroup>

resources/localization.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
"German (Germany)": "UNSER DISCORD"
2323
},
2424
"polymod_hub_footer": {
25-
"English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
26-
"Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
27-
"Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
28-
"Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
29-
"French (France)": "Rejoignez notre discord! N'hésitez pas à discuter des mods, à en créer et à demander de l'aide!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
30-
"Polish": "Dołącz do naszego discorda! Zachęcamy do omawiania modów, tworzenia ich lub proszenia o pomoc!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
31-
"Portuguese (Brazil)": "Entre no nosso Discord! Sinta-se à vontade para discutir sobre os mods, criar novos mods e pedir ajuda!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
32-
"Elyrion": "§ii∫ Δi^#ȱrΔ! Δi^#₺^^ mȱΔ#, ȱrrȱ ỹ a^š ỹȱπ!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
33-
"German (Germany)": "Tritt unserem Discord bei, um Hilfe zu bekommen, Mods zu diskutieren oder sogar selbst zu erstellen!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon"
25+
"English": "Join our discord! Feel free to discuss mods, create them and ask for help!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
26+
"Russian": "Присоединяйтесь к нашему дискорду! Не стесняйтесь обсуждать моды, создавать их и просить о помощи!\n\n{0}Особая благодарность{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
27+
"Turkish": "Discord sunucumuza katıl! Orada modlar oluşturabilir, tartışabilir ve yardım isteyebilirsin!\n\n{0}Hepinize çok teşekkür ederim:{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
28+
"Spanish (Mexico)": "Unete a nuestro discord! Aqui se puede discutir sobre la modificacion del juego, guias para crear su propio, preguntar a los creadores, y mas!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
29+
"French (France)": "Rejoignez notre discord! N'hésitez pas à discuter des mods, à en créer et à demander de l'aide!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
30+
"Polish": "Dołącz do naszego discorda! Zachęcamy do omawiania modów, tworzenia ich lub proszenia o pomoc!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
31+
"Portuguese (Brazil)": "Entre no nosso Discord! Sinta-se à vontade para discutir sobre os mods, criar novos mods e pedir ajuda!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
32+
"Elyrion": "§ii∫ Δi^#ȱrΔ! Δi^#₺^^ mȱΔ#, ȱrrȱ ỹ a^š ỹȱπ!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon",
33+
"German (Germany)": "Tritt unserem Discord bei, um Hilfe zu bekommen, Mods zu diskutieren oder sogar selbst zu erstellen!\n\n{0}Special thanks{1}\n___exploit___\njohnklipi\nhighflyer\nMRB\nincomplete_tree\nArtemis\nParanoia\nNyrrv\nCitillan\nLukasAyas\nVaM\nWhail\nBrober\nMaradon"
3434
},
3535
"polymod_hub_header": {
3636
"English": "{0}Welcome!{1}\nHere you can see the list of all currently loaded mods:",

src/Loader.cs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Globalization;
1313
using System.IO.Compression;
1414
using System.Reflection;
15+
using System.Text;
1516
using System.Text.Json;
1617
using System.Text.RegularExpressions;
1718
using UnityEngine;
@@ -230,7 +231,7 @@ public static void AddPatchDataType(string typeId, Type type, bool shouldCreateC
230231
/// Loads all mods from the mods directory.
231232
/// </summary>
232233
/// <param name="mods">A dictionary to populate with the loaded mods.</param>
233-
internal static void LoadMods(Dictionary<string, Mod> mods)
234+
internal static void RegisterMods(Dictionary<string, Mod> mods)
234235
{
235236
Directory.CreateDirectory(Plugin.MODS_PATH);
236237
string[] modContainers = Directory.GetDirectories(Plugin.MODS_PATH)
@@ -279,8 +280,7 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
279280
files.Add(new(entry.FullName, entry.ReadBytes()));
280281
}
281282
}
282-
283-
// Validate manifest
283+
#region ValidateManifest()
284284
if (manifest == null)
285285
{
286286
Plugin.logger.LogError($"Mod manifest not found in {modContainer}");
@@ -311,6 +311,7 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
311311
Plugin.logger.LogError($"Mod {manifest.id} already exists");
312312
continue;
313313
}
314+
#endregion
314315
mods.Add(manifest.id, new(
315316
manifest,
316317
Mod.Status.Success,
@@ -319,7 +320,41 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
319320
Plugin.logger.LogInfo($"Registered mod {manifest.id}");
320321
}
321322

322-
// Check dependencies
323+
CheckDependencies(mods);
324+
}
325+
326+
internal static void LoadMods(Dictionary<string, Mod> mods, out bool dependencyCycle)
327+
{
328+
dependencyCycle = !SortMods(Registry.mods);
329+
if (dependencyCycle) return;
330+
331+
StringBuilder checksumString = new();
332+
foreach (var (id, mod) in Registry.mods)
333+
{
334+
if (mod.status != Mod.Status.Success) continue;
335+
foreach (var file in mod.files)
336+
{
337+
checksumString.Append(JsonSerializer.Serialize(file));
338+
if (Path.GetExtension(file.name) == ".dll")
339+
{
340+
LoadAssemblyFile(mod, file);
341+
}
342+
if (Path.GetFileName(file.name) == "sprites.json")
343+
{
344+
LoadSpriteInfoFile(mod, file);
345+
}
346+
}
347+
if (!mod.client && id != "polytopia")
348+
{
349+
checksumString.Append(id);
350+
checksumString.Append(mod.version.ToString());
351+
}
352+
}
353+
Compatibility.HashSignatures(checksumString);
354+
355+
}
356+
private static void CheckDependencies(Dictionary<string, Mod> mods)
357+
{
323358
foreach (var (id, mod) in mods)
324359
{
325360
foreach (var dependency in mod.dependencies ?? Array.Empty<Mod.Dependency>())
@@ -362,7 +397,7 @@ internal static void LoadMods(Dictionary<string, Mod> mods)
362397
/// </summary>
363398
/// <param name="mods">The dictionary of mods to sort.</param>
364399
/// <returns>True if the mods could be sorted (no circular dependencies), false otherwise.</returns>
365-
internal static bool SortMods(Dictionary<string, Mod> mods)
400+
private static bool SortMods(Dictionary<string, Mod> mods)
366401
{
367402
Stopwatch s = new();
368403
Dictionary<string, List<string>> graph = new();
@@ -431,6 +466,16 @@ public static void LoadAssemblyFile(Mod mod, Mod.File file)
431466
try
432467
{
433468
Assembly assembly = Assembly.Load(file.bytes);
469+
if (assembly
470+
.GetTypes()
471+
.FirstOrDefault(t => t.IsSubclassOf(typeof(PolyScriptBase)))
472+
is { } modType)
473+
{
474+
var modInstance = (PolyScriptBase) Activator.CreateInstance(modType)!;
475+
modInstance.Initialize(mod.id, BepInEx.Logging.Logger.CreateLogSource($"PolyMod] [{mod.id}"));
476+
modInstance.Load();
477+
return;
478+
}
434479
foreach (Type type in assembly.GetTypes())
435480
{
436481
MethodInfo? loadWithLogger = type.GetMethod("Load", new Type[] { typeof(ManualLogSource) });

src/Managers/Config.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Nodes;
3+
4+
namespace PolyMod.Managers;
5+
6+
/// <summary>
7+
/// Allows mods to save config.
8+
/// </summary>
9+
public class Config<T> where T : class
10+
{
11+
private T? currentConfig;
12+
private readonly string modName;
13+
private readonly ConfigTypes configType;
14+
private static readonly string ExposedConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json");
15+
private readonly string perModConfigPath;
16+
private T? defaultConfig;
17+
public Config(string modName, ConfigTypes configType)
18+
{
19+
this.modName = modName;
20+
this.configType = configType;
21+
perModConfigPath = Path.Combine(Plugin.MODS_PATH, $"{modName}.json");
22+
Load();
23+
}
24+
25+
internal void Load() // can be called internally if config changes; gui config not implemented yet
26+
{
27+
switch (configType)
28+
{
29+
case ConfigTypes.PerMod:
30+
{
31+
if (!File.Exists(perModConfigPath))
32+
{
33+
return;
34+
}
35+
var jsonText = File.ReadAllText(perModConfigPath);
36+
currentConfig = JsonSerializer.Deserialize<T>(jsonText);
37+
break;
38+
}
39+
case ConfigTypes.Exposed:
40+
{
41+
if (!File.Exists(ExposedConfigPath))
42+
{
43+
return;
44+
}
45+
var jsonText = File.ReadAllText(ExposedConfigPath);
46+
currentConfig = JsonNode.Parse(jsonText)![modName]?.Deserialize<T>();
47+
break;
48+
}
49+
default:
50+
throw new ArgumentOutOfRangeException();
51+
}
52+
}
53+
/// <summary>
54+
/// Sets the default if the config does not exist yet. Always call this before reading from the config.
55+
/// </summary>
56+
public void SetDefaultConfig(T defaultValue)
57+
{
58+
defaultConfig = defaultValue;
59+
if (currentConfig is not null) return;
60+
Write(defaultConfig);
61+
SaveChanges();
62+
}
63+
64+
/// <summary>
65+
/// Writes the **entire** config. Usage not recommended, use Edit() instead
66+
/// </summary>
67+
public void Write(T config)
68+
{
69+
currentConfig = config;
70+
}
71+
/// <summary>
72+
/// Gets the config. Should only be called after setting a default.
73+
/// </summary>
74+
public T Get()
75+
{
76+
return currentConfig ?? throw new InvalidOperationException("Must set default before reading config.");
77+
}
78+
/// <summary>
79+
/// Edits the config. Should only be called after setting a default.
80+
/// </summary>
81+
/// <remarks>Call SaveChanges after editing</remarks>
82+
public void Edit(Action<T> editor)
83+
{
84+
editor(currentConfig ?? throw new InvalidOperationException("Must set default before reading config."));
85+
}
86+
/// <summary>
87+
/// Gets part of the config. Should only be called after setting a default
88+
/// </summary>
89+
public TResult Get<TResult>(Func<T, TResult> getter)
90+
{
91+
return getter(currentConfig ?? throw new InvalidOperationException("Must set default before reading config."));
92+
}
93+
/// <summary>
94+
/// Writes the config to disk
95+
/// </summary>
96+
public void SaveChanges()
97+
{
98+
switch (configType)
99+
{
100+
case ConfigTypes.PerMod:
101+
var perModJson = JsonSerializer.Serialize(currentConfig, new JsonSerializerOptions { WriteIndented = true });
102+
File.WriteAllText(perModConfigPath, perModJson);
103+
break;
104+
case ConfigTypes.Exposed:
105+
var modsConfigText = File.ReadAllText(ExposedConfigPath);
106+
var modsConfigJson = JsonNode.Parse(modsConfigText)!.AsObject();
107+
modsConfigJson[modName] = JsonSerializer.SerializeToNode(currentConfig!);
108+
File.WriteAllText(ExposedConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
109+
break;
110+
default:
111+
throw new ArgumentOutOfRangeException();
112+
}
113+
}
114+
115+
public enum ConfigTypes
116+
{
117+
PerMod,
118+
Exposed
119+
}
120+
}

src/Managers/GLDConfig.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Nodes;
3+
using Scriban;
4+
using Scriban.Runtime;
5+
6+
namespace PolyMod.Managers;
7+
8+
public class GldConfigTemplate
9+
{
10+
private static readonly string ConfigPath = Path.Combine(Plugin.BASE_PATH, "mods.json");
11+
12+
private readonly string templateText;
13+
private JsonObject currentConfig = new();
14+
private string modName;
15+
16+
public GldConfigTemplate(string templateText, string modName)
17+
{
18+
this.templateText = templateText;
19+
this.modName = modName;
20+
Load();
21+
}
22+
private void Load()
23+
{
24+
if (File.Exists(ConfigPath))
25+
{
26+
var json = File.ReadAllText(ConfigPath);
27+
if (JsonNode.Parse(json) is JsonObject modsConfig
28+
&& modsConfig.TryGetPropertyValue(modName, out var modConfigNode)
29+
&& modConfigNode is JsonObject modConfig)
30+
{
31+
currentConfig = modConfig;
32+
return;
33+
}
34+
}
35+
currentConfig = new JsonObject();
36+
}
37+
38+
public string? Render()
39+
{
40+
if (!templateText.Contains("{{")) return templateText;
41+
var template = Template.Parse(templateText);
42+
var context = new TemplateContext();
43+
var scriptObject = new ScriptObject();
44+
45+
bool changedConfig = false;
46+
scriptObject.Import("config",
47+
new Func<string, string, string>((key, defaultValue) =>
48+
{
49+
if (currentConfig.TryGetPropertyValue(key, out var token) && token != null)
50+
{
51+
return token.ToString();
52+
}
53+
54+
changedConfig = true;
55+
currentConfig[key] = defaultValue;
56+
57+
return defaultValue;
58+
})
59+
);
60+
context.PushGlobal(scriptObject);
61+
string? result;
62+
try
63+
{
64+
result = template.Render(context);
65+
}
66+
catch (Exception e)
67+
{
68+
Plugin.logger.LogError("error during parse of gld patch template: " + e.ToString());
69+
result = null;
70+
}
71+
if (changedConfig)
72+
{
73+
SaveChanges();
74+
}
75+
return result;
76+
}
77+
78+
public void SaveChanges()
79+
{
80+
JsonObject modsConfigJson;
81+
if (File.Exists(ConfigPath))
82+
{
83+
var modsConfigText = File.ReadAllText(ConfigPath);
84+
modsConfigJson = (JsonNode.Parse(modsConfigText) as JsonObject) ?? new JsonObject();
85+
}
86+
else
87+
{
88+
modsConfigJson = new JsonObject();
89+
}
90+
91+
modsConfigJson[modName] = currentConfig;
92+
File.WriteAllText(ConfigPath, modsConfigJson.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
93+
}
94+
}

0 commit comments

Comments
 (0)