using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using AssetShards;
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Clonesoft.Json;
using CustomPalettes.Core;
using CustomPalettes.Data;
using GameData;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.2.0")]
[assembly: AssemblyInformationalVersion("1.2.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyVersion("1.2.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
internal class ManifestInfo
{
internal const string TSName = "CustomPalettes";
internal const string TSDescription = "Add custom palettes to the game using json files and pngs!";
internal const string TSVersion = "1.2.0";
internal const string TSAuthor = "AuriRex";
internal const string TSWebsite = "https://github.com/AuriRex/GTFO_CustomPalettes/";
}
namespace CustomPalettes
{
internal static class L
{
internal static ManualLogSource Logger { private get; set; }
internal static void Info(string msg)
{
Logger.LogInfo((object)msg);
}
internal static void Msg(string msg)
{
Logger.LogMessage((object)msg);
}
internal static void Debug(string msg)
{
Logger.LogDebug((object)msg);
}
internal static void Warning(string msg)
{
Logger.LogWarning((object)msg);
}
internal static void Error(string msg)
{
Logger.LogError((object)msg);
}
internal static void Exception(Exception ex)
{
Logger.LogError((object)ex.Message);
Logger.LogWarning((object)("StackTrace:\n" + ex.StackTrace));
}
}
public static class Patches
{
[HarmonyPatch(typeof(AssetShardManager), "Setup")]
internal static class AssetShardManager_Setup_Patch
{
[HarmonyPriority(600)]
public static void Postfix()
{
Plugin.OnAssetShardManagerReady();
}
}
[HarmonyPatch(typeof(GameDataInit), "Initialize")]
internal static class GameDataInit_Initialize_Patch
{
[HarmonyPriority(600)]
public static void Postfix()
{
Plugin.OnGameDataInit();
}
}
}
[BepInPlugin("dev.aurirex.gtfo.custompalettes", "Custom Palettes", "1.2.0")]
public class Plugin : BasePlugin
{
public const string GUID = "dev.aurirex.gtfo.custompalettes";
public const string MOD_NAME = "Custom Palettes";
public const string VERSION = "1.2.0";
private static readonly Harmony _harmony = new Harmony("dev.aurirex.gtfo.custompalettes");
private static bool _hasInitedOnce = false;
public override void Load()
{
L.Logger = ((BasePlugin)this).Log;
_harmony.PatchAll(Assembly.GetExecutingAssembly());
PaletteManager.Setup();
PaletteManager.LoadPalettes();
}
internal static void OnAssetShardManagerReady()
{
L.Debug("AssetShardManager ready, Injecting Palettes");
TextureLoader.Setup(PaletteManager.Palettes);
PaletteManager.InjectPalettes();
}
internal static void OnGameDataInit()
{
L.Debug("GameDataInit.Initialize called.");
if (_hasInitedOnce)
{
L.Warning("Reloading Custom Palettes ...");
PaletteManager.LoadPalettes();
TextureLoader.Setup(PaletteManager.Palettes, doCleanup: true);
PaletteManager.InjectPalettes(forceRegeneration: true);
PersistentInventoryManager.m_dirty = true;
}
_hasInitedOnce = true;
}
}
}
namespace CustomPalettes.Data
{
public class CustomPalette
{
public string Name { get; set; } = "Custom Palette";
public string Author { get; set; } = string.Empty;
public string SortingName { get; set; } = "MyCustomPalette";
public uint VanityGroupID { get; set; }
public PaletteData Data { get; set; } = new PaletteData();
[JsonIgnore]
public string FileName { get; internal set; } = string.Empty;
}
public class PaletteData
{
public class Tone
{
public string HexColor { get; set; } = "#FFFFFF";
public string TextureFile { get; set; } = string.Empty;
public int MaterialOverride { get; set; } = -1;
}
[JsonIgnore]
private static readonly Tone _errorTone = new Tone
{
m_color = Color.magenta,
m_texture = Texture2D.whiteTexture,
m_materialOverride = -1
};
public Tone PrimaryTone { get; set; } = new Tone();
public Tone SecondaryTone { get; set; } = new Tone();
public Tone TertiaryTone { get; set; } = new Tone();
public Tone QuaternaryTone { get; set; } = new Tone();
public Tone QuinaryTone { get; set; } = new Tone();
public float TextureTiling { get; set; } = 1f;
[JsonIgnore]
public IEnumerable<Tone> Tones
{
get
{
if (PrimaryTone != null)
{
yield return PrimaryTone;
}
if (SecondaryTone != null)
{
yield return SecondaryTone;
}
if (TertiaryTone != null)
{
yield return TertiaryTone;
}
if (QuaternaryTone != null)
{
yield return QuaternaryTone;
}
if (QuinaryTone != null)
{
yield return QuinaryTone;
}
}
}
internal Tone GetTone(int v)
{
return (Tone)(v switch
{
2 => GetTone(SecondaryTone),
3 => GetTone(TertiaryTone),
4 => GetTone(QuaternaryTone),
5 => GetTone(QuinaryTone),
_ => GetTone(PrimaryTone),
});
}
private static Tone GetTone(Tone tone)
{
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Expected O, but got Unknown
if (tone == null)
{
return _errorTone;
}
Color color = default(Color);
if (!ColorUtility.TryParseHtmlString(tone.HexColor, ref color) && !ColorUtility.TryParseHtmlString("#" + tone.HexColor, ref color))
{
return _errorTone;
}
Texture2D texture = TextureLoader.GetTexture(tone.TextureFile);
return new Tone
{
m_color = color,
m_texture = texture,
m_materialOverride = tone.MaterialOverride
};
}
}
}
namespace CustomPalettes.Core
{
public static class PaletteManager
{
private static string _path;
private static readonly Dictionary<string, CustomPalette> _nameToPalette = new Dictionary<string, CustomPalette>();
private static readonly List<CustomPalette> _palettes = new List<CustomPalette>();
private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
Formatting = (Formatting)1
};
private const string TEMPLATE_FILE_NAME = "_template_palette.json";
public static string CustomPalettesPath => _path ?? (_path = Path.Combine(Paths.BepInExRootPath, "Assets", "CustomPalettes"));
public static string BLOCK_PREFIX => "CustomPalette_".ToUpper();
public static bool DoLoadTemplateFile { get; set; } = false;
public static IEnumerable<CustomPalette> Palettes => _palettes;
public static bool TryGetPaletteFromBlock(VanityItemsTemplateDataBlock block, out CustomPalette palette)
{
return TryGetPaletteFromId(((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).name, out palette);
}
public static bool TryGetPaletteFromId(string identifier, out CustomPalette palette)
{
return _nameToPalette.TryGetValue(identifier, out palette);
}
internal static void Setup()
{
if (!Directory.Exists(CustomPalettesPath))
{
Directory.CreateDirectory(CustomPalettesPath);
}
string path = Path.Combine(CustomPalettesPath, "_template_palette.json");
if (!File.Exists(path))
{
string contents = JsonConvert.SerializeObject((object)GetTemplate(), _jsonSettings);
File.WriteAllText(path, contents);
}
}
internal static void LoadPalettes()
{
_palettes.Clear();
L.Info("Loading Custom Palettes from [" + CustomPalettesPath + "]!");
string[] files = Directory.GetFiles(CustomPalettesPath);
foreach (string path in files)
{
string fileName = Path.GetFileName(path);
if (Path.HasExtension(path) && !(Path.GetExtension(path) != ".json") && (!(fileName == "_template_palette.json") || DoLoadTemplateFile))
{
try
{
CustomPalette customPalette = JsonConvert.DeserializeObject<CustomPalette>(File.ReadAllText(path), _jsonSettings);
L.Info($"Loaded Custom Palette: ({customPalette.SortingName}): {customPalette.Author} | {customPalette.Name}");
customPalette.FileName = fileName;
Sanitize(customPalette);
_palettes.Add(customPalette);
}
catch (Exception ex)
{
L.Exception(ex);
}
}
}
}
internal static void Sanitize(CustomPalette pal)
{
IEnumerable<PaletteData.Tone> enumerable = pal?.Data?.Tones;
if (enumerable == null)
{
return;
}
foreach (PaletteData.Tone item in enumerable)
{
item.TextureFile = SanitizePath(item.TextureFile);
}
}
internal static string SanitizePath(string tex)
{
char[] invalidPathChars = Path.GetInvalidPathChars();
foreach (char oldChar in invalidPathChars)
{
tex = tex.Replace(oldChar, '_');
}
while (tex.Contains(".."))
{
tex = tex.Replace("..", "");
}
return tex.Replace(":", "");
}
private static CustomPalette GetTemplate()
{
return new CustomPalette
{
Author = "YourNameHere",
Name = "Template Palette",
SortingName = "UsedToSort",
Data = new PaletteData
{
PrimaryTone = new PaletteData.Tone
{
HexColor = "#FF0000"
},
SecondaryTone = new PaletteData.Tone
{
HexColor = "#00FF00"
},
TertiaryTone = new PaletteData.Tone
{
HexColor = "#0000FF"
},
QuaternaryTone = new PaletteData.Tone
{
HexColor = "#FFFF00"
},
QuinaryTone = new PaletteData.Tone
{
HexColor = "#00FFFF"
}
}
};
}
private static bool TryGetBlock(string name, out VanityItemsTemplateDataBlock block)
{
block = GameDataBlockBase<VanityItemsTemplateDataBlock>.GetBlock(GameDataBlockBase<VanityItemsTemplateDataBlock>.GetBlockID(name));
return block != null;
}
internal static void InjectPalettes(bool forceRegeneration = false)
{
//IL_0180: Unknown result type (might be due to invalid IL or missing references)
//IL_0187: Expected O, but got Unknown
L.Info($"Injecting {_palettes.Count} Custom Palettes ...");
IOrderedEnumerable<CustomPalette> orderedEnumerable = _palettes.OrderBy((CustomPalette pal) => $"{pal.Author}_{pal.SortingName}_{pal.FileName}");
_nameToPalette.Clear();
foreach (VanityItemsTemplateDataBlock allBlock in GameDataBlockBase<VanityItemsTemplateDataBlock>.GetAllBlocks())
{
if (((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)allBlock).name.StartsWith(BLOCK_PREFIX))
{
((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)allBlock).internalEnabled = false;
}
}
foreach (CustomPalette item in orderedEnumerable)
{
try
{
string text = BLOCK_PREFIX + item.FileName.ToUpper();
if (TryGetBlock(text, out var block))
{
L.Info($"Found existing block for \"{text}\". ({((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).persistentID})");
block.prefab = GeneratePrefab(text, item.Data, forceRegeneration);
block.publicName = item.Name;
((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).internalEnabled = true;
_nameToPalette[text] = item;
continue;
}
block = new VanityItemsTemplateDataBlock();
((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).name = text;
((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).internalEnabled = true;
block.DropWeight = 1f;
block.type = (ClothesType)4;
block.publicName = item.Name;
block.prefab = GeneratePrefab(text, item.Data, forceRegeneration);
_nameToPalette[text] = item;
GameDataBlockBase<VanityItemsTemplateDataBlock>.AddBlock(block, -1);
L.Info($"Injected new palette \"{text}\". ({((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).persistentID})");
uint num = item.VanityGroupID;
if (num == 0)
{
num = 36u;
}
if (!GameDataBlockBase<VanityItemsGroupDataBlock>.HasBlock(num))
{
L.Warning($"Tried to add Palette \"{item?.Name ?? "Unnamed"}\" into group with ID {num} that doesn't exist!");
}
else
{
GameDataBlockBase<VanityItemsGroupDataBlock>.GetBlock(num).Items.Add(((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).persistentID);
L.Info($"Added Palette \"{item?.Name ?? "Unnamed"}\" into group with ID {num}.");
}
}
catch (Exception ex)
{
L.Warning($"Failed to load Custom Palette \"{item?.Name ?? "Unnamed"}\" ({item.FileName}).");
L.Exception(ex);
}
}
}
private static string GeneratePrefab(string identifier, PaletteData data, bool forceRegeneration = false)
{
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Expected O, but got Unknown
if (string.IsNullOrWhiteSpace(identifier))
{
throw new ArgumentException("Identifier may not be null or whitespace.", "identifier");
}
if (data == null)
{
throw new ArgumentNullException("data");
}
if (AssetShardManager.s_loadedAssetsLookup.ContainsKey(identifier))
{
if (!forceRegeneration)
{
return identifier;
}
L.Debug("Regenerating Palette prefab for \"" + identifier + "\" ...");
Object.Destroy(AssetShardManager.s_loadedAssetsLookup[identifier]);
}
GameObject val = new GameObject(identifier);
((Object)val).hideFlags = (HideFlags)61;
Object.DontDestroyOnLoad((Object)(object)val);
ClothesPalette obj = val.AddComponent<ClothesPalette>();
obj.m_textureTiling = data.TextureTiling;
obj.m_primaryTone = data.GetTone(1);
obj.m_secondaryTone = data.GetTone(2);
obj.m_tertiaryTone = data.GetTone(3);
obj.m_quaternaryTone = data.GetTone(4);
obj.m_quinaryTone = data.GetTone(5);
AssetShardManager.s_loadedAssetsLookup[identifier] = (Object)(object)val;
return identifier;
}
}
public static class TextureLoader
{
private static readonly Dictionary<string, TextureData> _textures = new Dictionary<string, TextureData>();
public static void Setup(IEnumerable<CustomPalette> palettes, bool doCleanup = false)
{
foreach (CustomPalette palette in palettes)
{
if (palette != null)
{
ProcessPaletteData(palette);
}
}
if (doCleanup)
{
CleanupUnusedTextures(palettes);
}
}
private static void CleanupUnusedTextures(IEnumerable<CustomPalette> palettes)
{
HashSet<string> hashSet = new HashSet<string>(_textures.Keys);
foreach (CustomPalette palette in palettes)
{
IEnumerable<PaletteData.Tone> enumerable = palette?.Data?.Tones;
if (enumerable == null)
{
continue;
}
foreach (PaletteData.Tone item in enumerable)
{
if (!string.IsNullOrWhiteSpace(item.TextureFile) && hashSet.Contains(item.TextureFile))
{
hashSet.Remove(item.TextureFile);
}
}
}
if (hashSet.Count == 0)
{
return;
}
L.Debug($"Found {hashSet.Count} unused Textures.");
foreach (string item2 in hashSet)
{
if (_textures.TryGetValue(item2, out var value))
{
Object.Destroy((Object)(object)value.Texture);
_textures.Remove(item2);
L.Debug("Unloaded \"" + item2 + "\"!");
}
}
}
private static void ProcessPaletteData(CustomPalette pal)
{
PaletteData data = pal.Data;
if (data == null)
{
return;
}
foreach (PaletteData.Tone tone in data.Tones)
{
string textureFile = tone.TextureFile;
if (!string.IsNullOrWhiteSpace(textureFile) && AttemptLoad(textureFile, pal))
{
L.Msg("Loaded Texture \"" + textureFile + "\" for Palette " + pal.FileName);
}
}
}
private static bool AttemptLoad(string tex, CustomPalette pal)
{
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_00a2: Expected O, but got Unknown
string path = Path.Combine(PaletteManager.CustomPalettesPath, tex);
if (!File.Exists(path))
{
PrintLoadError("File doesn't exist!", tex, pal);
return false;
}
DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(path);
if (_textures.TryGetValue(tex, out var value))
{
if (lastWriteTimeUtc == value.EditedTime)
{
return true;
}
Object.Destroy((Object)(object)value.Texture);
_textures.Remove(tex);
}
string extension = Path.GetExtension(path);
if (extension.ToLower() != ".png" && extension.ToLower() != ".jpg")
{
PrintLoadError("TextureFile is not a valid image file.", tex, pal);
return false;
}
try
{
Texture2D val = new Texture2D(2, 2);
byte[] array = File.ReadAllBytes(path);
if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array), false))
{
PrintLoadError("Image data can't be loaded!", tex, pal);
Object.Destroy((Object)(object)val);
return false;
}
((Object)val).hideFlags = (HideFlags)61;
Object.DontDestroyOnLoad((Object)(object)val);
_textures.Add(tex, new TextureData
{
Texture = val,
EditedTime = lastWriteTimeUtc
});
return true;
}
catch (Exception ex)
{
PrintLoadError(ex.Message, tex, pal);
}
return false;
}
private static void PrintLoadError(string message, string tex, CustomPalette pal)
{
L.Error($"Texture \"{tex}\" could not be loaded for Palette \"{pal?.Name}\" ({pal.FileName}): {message}");
}
public static Texture2D GetTexture(string textureFile)
{
if (string.IsNullOrWhiteSpace(textureFile))
{
return Texture2D.whiteTexture;
}
if (_textures.TryGetValue(textureFile, out var value))
{
return value.Texture;
}
return Texture2D.whiteTexture;
}
}
public class TextureData
{
public Texture2D Texture { get; internal set; }
public DateTime EditedTime { get; internal set; }
}
}