Skip to content

Commit 02fb8ce

Browse files
committed
feat: initial commit
0 parents  commit 02fb8ce

11 files changed

Lines changed: 3524 additions & 0 deletions

.github/funding.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
custom: https://buymeacoffee.com/JustPyrrha

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea/
2+
bin/
3+
obj/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Pyrrha Wills
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Py.LibLocalization.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}") = "Py.LibLocalization", "Py.LibLocalization\Py.LibLocalization.csproj", "{62D60641-FE53-4154-90AC-504AB9A38CEB}"
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+
{62D60641-FE53-4154-90AC-504AB9A38CEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{62D60641-FE53-4154-90AC-504AB9A38CEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{62D60641-FE53-4154-90AC-504AB9A38CEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{62D60641-FE53-4154-90AC-504AB9A38CEB}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using Boardgame.Modding;
3+
using Fidelity.Singleton;
4+
using Py.LibLocalization.Utils;
5+
6+
namespace Py.LibLocalization
7+
{
8+
// ReSharper disable once UnusedType.Global
9+
public class LibLocalization : DemeoMod
10+
{
11+
public override void OnEarlyInit()
12+
{
13+
if (HarmonyLoader.LoadHarmony())
14+
{
15+
ModPatches.Patch();
16+
}
17+
else
18+
{
19+
Console.WriteLine("[Py.LibLocalization] ERROR | Failed to load Harmony, Py.LibLocalization WILL NOT WORK.");
20+
}
21+
}
22+
23+
public override void Load()
24+
{
25+
Singleton<LocaleRegistry>.Instance.Refresh();
26+
}
27+
28+
public override ModdingAPI.ModInformation ModInformation { get; } = new()
29+
{
30+
name = "Py.LibLocalization",
31+
author = "JustPyrrha",
32+
version = "1.0.0",
33+
description = "Mod localization library.",
34+
isNetworkCompatible = true
35+
};
36+
}
37+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Collections.Generic;
2+
using Fidelity.Localization;
3+
using Fidelity.Singleton;
4+
5+
namespace Py.LibLocalization
6+
{
7+
/// <summary>
8+
/// Registry for modded localizations.
9+
/// </summary>
10+
public class LocaleRegistry : Singleton<LocaleRegistry>
11+
{
12+
// <locale, <id, localization>>
13+
private readonly Dictionary<string, Dictionary<string, string>> _internalRegistry = new();
14+
15+
/// <summary>
16+
/// Get all registered localizations matching a locale.
17+
/// </summary>
18+
/// <param name="locale">locale to get localizations for</param>
19+
/// <returns>custom localizations matching the provided locale</returns>
20+
public Dictionary<string, string> GetLocalizationsForLocale(string locale) =>
21+
!_internalRegistry.ContainsKey(locale.ToLower())
22+
? new Dictionary<string, string>()
23+
: _internalRegistry[locale.ToLower()];
24+
25+
/// <summary>
26+
/// Register a mod's localizations.
27+
/// </summary>
28+
/// <param name="locale">locale key (e.g. en-US)</param>
29+
/// <param name="localizations">A dictionary of stringId, localization for locale.</param>
30+
public void AddLocalizations(string locale, Dictionary<string, string> localizations)
31+
{
32+
foreach (var (id, localization) in localizations)
33+
{
34+
AddLocalization(locale, id, localization);
35+
}
36+
}
37+
38+
/// <summary>
39+
/// Register a single localization.
40+
/// </summary>
41+
/// <param name="locale">locale key (e.g. en-US)</param>
42+
/// <param name="id">stringId for the localization</param>
43+
/// <param name="localization">localization matching the stringId and locale.</param>
44+
public void AddLocalization(string locale, string id, string localization)
45+
{
46+
if (!_internalRegistry.ContainsKey(locale.ToLower()))
47+
{
48+
_internalRegistry[locale.ToLower()] = new Dictionary<string, string>();
49+
}
50+
51+
_internalRegistry[locale.ToLower()].Add(id, localization);
52+
}
53+
54+
/// <summary>
55+
/// Refreshes Demeo's locale registry.
56+
/// Note: Py.Localizations will call this during DemeoMod.Load so you shouldn't have to.
57+
/// </summary>
58+
public void Refresh()
59+
{
60+
Singleton<Locale>.Instance.Refresh();
61+
}
62+
}
63+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netstandard2.1</TargetFramework>
4+
<ImplicitUsings>disable</ImplicitUsings>
5+
<Nullable>disable</Nullable>
6+
<LangVersion>10</LangVersion>
7+
<DemeoDir Condition=" '$(DemeoDir)' == '' ">C:\Program Files (x86)\Steam\steamapps\common\Demeo - PC Edition</DemeoDir>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Reference Include="Assembly-CSharp">
12+
<HintPath>$(DemeoDir)\demeo_Data\Managed\Assembly-CSharp.dll</HintPath>
13+
</Reference>
14+
<Reference Include="UnityEngine.CoreModule">
15+
<HintPath>$(DemeoDir)\demeo_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
16+
</Reference>
17+
<Reference Include="ResolutionGames.Localization">
18+
<HintPath>$(DemeoDir)\demeo_Data\Managed\ResolutionGames.Localization.dll</HintPath>
19+
</Reference>
20+
<Reference Include="ResolutionGames.Singleton">
21+
<HintPath>$(DemeoDir)\demeo_Data\Managed\ResolutionGames.Singleton.dll</HintPath>
22+
</Reference>
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<PackageReference Include="Lib.Harmony" Version="2.2.2" />
27+
</ItemGroup>
28+
29+
<Target Name="CopyOutputFileToDemeoDir" AfterTargets="Build">
30+
<Copy SourceFiles="$(OutputPath)\$(AssemblyName).dll" DestinationFolder="$(DemeoDir)\DemeoMods\Py.Localization" />
31+
</Target>
32+
</Project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Reflection;
5+
using UnityEngine;
6+
7+
namespace Py.LibLocalization.Utils
8+
{
9+
public static class HarmonyLoader
10+
{
11+
public static bool LoadHarmony() =>
12+
LoadHarmony(
13+
Path.Combine(
14+
Path.GetDirectoryName(Application.dataPath)!,
15+
"DemeoMods",
16+
"Py.Localization"
17+
)
18+
);
19+
20+
public static bool LoadHarmony(string modDir)
21+
{
22+
if (CheckAlreadyLoaded()) return true;
23+
if (!Directory.Exists(modDir)) return false;
24+
25+
foreach (var harmonyFile in Directory.GetFiles(modDir, "0Harmony.dll"))
26+
{
27+
try
28+
{
29+
Assembly.LoadFile(harmonyFile);
30+
return true;
31+
}
32+
catch (Exception)
33+
{
34+
return false;
35+
}
36+
}
37+
38+
return false;
39+
}
40+
41+
private static bool CheckAlreadyLoaded()
42+
{
43+
var harmonyType = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
44+
from type in assembly.GetTypes()
45+
where type.Name == "Harmony" && type.Namespace == "HarmonyLib"
46+
select type).FirstOrDefault();
47+
48+
return harmonyType != null;
49+
}
50+
}
51+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Fidelity.Localization;
4+
using Fidelity.Singleton;
5+
using HarmonyLib;
6+
7+
namespace Py.LibLocalization.Utils
8+
{
9+
public static class ModPatches
10+
{
11+
public static void Patch()
12+
{
13+
var harmony = new Harmony("Py.Localization");
14+
harmony.Patch(
15+
original: AccessTools.Method(typeof(Locale), "TryLoadStrings"),
16+
postfix: new HarmonyMethod(typeof(ModPatches), nameof(Locale_TryLoadStrings_Postfix))
17+
);
18+
}
19+
20+
private static void Locale_TryLoadStrings_Postfix(
21+
// ReSharper disable InconsistentNaming
22+
string ___currentLocale,
23+
ref Dictionary<string, string> ___strings
24+
// ReSharper restore InconsistentNaming
25+
)
26+
{
27+
var modStrings = Singleton<LocaleRegistry>.Instance.GetLocalizationsForLocale(___currentLocale);
28+
foreach (var (id, localization) in modStrings)
29+
{
30+
___strings[id] = localization;
31+
}
32+
Console.WriteLine($"[Py.LibLocalization] Loaded {modStrings.Count} custom localizations for {___currentLocale}.");
33+
}
34+
}
35+
}

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Py.LibLocalization
2+
3+
A simple library for adding custom localizations to [Demeo](https://www.resolutiongames.com/demeo).
4+
5+
## Installation
6+
Download the [latest stable release](https://github.com/orendain/DemeoMods/releases/latest)'s `Py.LibLocalization-version.zip` file
7+
and extract it into your Demeo game folder.
8+
9+
You should have a folder structure that looks like this:
10+
```
11+
Demeo (or Demeo - PC Edition)/
12+
├─ DemeoMods/
13+
│ ├─ Py.LibLocalization/
14+
│ │ ├─ 0Harmony.dll
15+
│ │ ├─ Py.LibLocalization.dll
16+
```
17+
18+
## Developer Guide
19+
### Setup
20+
Add dependencies for `ResolutionGames.Singleton.dll` (which can be found in Demeo's `demeo_Data/Managed` folder)
21+
and `Py.LibLocalization.dll`
22+
23+
### Add a localization
24+
> [!IMPORTANT]
25+
> You should add your localizations before the built-in mod loader (Boardgame.Modding.ModLoader) calls `DemeoMod.Load` since that's when LibLocalization refreshes Demeo's localization list.\
26+
> It's recommended to load them during `DemeoMod.OnEarlyInit` if you're using the built-in mod loader.\
27+
> You can still add localizations after `DemeoMod.Load` has been called you'll just need to call `registry.Refresh()` but this is resource intensive and should generally be avoided since it reloads all localizations, vanilla (from Unity Resources) and modded (from LocaleRegistry).
28+
29+
1. Start by getting the singleton instance of LocalRegistry.
30+
```csharp
31+
var registry = Fedility.Singlton.Singleton<Py.LibLocalization.LocaleRegistry>.Instance;
32+
```
33+
2. Add localization(s)
34+
> [!WARNING]
35+
> Be careful when adding localizations since you can override vanilla localizations if you use an id already defined by Demeo.
36+
37+
```csharp
38+
// Add many
39+
registry.AddLocalizations(
40+
"en-US",
41+
new Dictonary<string, string>
42+
{
43+
{ "Card/mycard/title", "My Custom Card" },
44+
{ "Card/mycard/description", "Look at this card I made!" }
45+
}
46+
);
47+
48+
// Add single
49+
registry.AddLocalization("en-US", "Card/mycard/title", "My Custom Card");
50+
```
51+
If you're not sure what to use for the localization id, have a look at [default-localization-keys.txt](default-localization-keys.txt). It contains all the localization ids included in Demeo by default and should give you a general idea on how to format your ids.
52+
53+
54+
### Frequently Asked Questions
55+
- Q: Why does the download include `0Harmony.dll`?\
56+
A: Since we use the built-in mod loader we need to load Lib.Harmony ourselves. If it's already loaded by a third-party mod loader the dll will be ignored.

0 commit comments

Comments
 (0)