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 GameData;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CustomPalettes")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyProduct("CustomPalettes")]
[assembly: AssemblyTitle("CustomPalettes")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace CustomPalettes
{
[BepInPlugin("dev.aurirex.gtfo.custompalettes", "Custom Palettes", "1.0.0")]
public class EntryPoint : BasePlugin
{
public const string GUID = "dev.aurirex.gtfo.custompalettes";
public const string NAME = "Custom Palettes";
public const string VERSION = "1.0.0";
private Harmony _harmonyInstance;
private static bool _hasInitedOnce;
public override void Load()
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Expected O, but got Unknown
L.Logger = ((BasePlugin)this).Log;
_harmonyInstance = new Harmony("dev.aurirex.gtfo.custompalettes");
_harmonyInstance.PatchAll(Assembly.GetExecutingAssembly());
PaletteManager.Setup();
PaletteManager.LoadPalettes();
}
internal static void OnAssetShardManagerReady()
{
L.Debug("AssetShardManager ready, Injecting Palettes");
TextureLoader.Setup(PaletteManager.Palletes);
PaletteManager.InjectPalettes();
}
internal static void OnGameDataInit()
{
L.Debug("GameDataInit.Initialize called.");
if (_hasInitedOnce)
{
L.Warning("Reloading Custom Palettes ...");
PaletteManager.LoadPalettes();
TextureLoader.Setup(PaletteManager.Palletes, doCleanup: true);
PaletteManager.InjectPalettes(forceRegeneration: true);
PersistentInventoryManager.m_dirty = true;
}
_hasInitedOnce = true;
}
}
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
{
[HarmonyPriority(600)]
[HarmonyPatch(typeof(AssetShardManager), "Setup")]
internal static class AssetShardManager_Setup_Patch
{
public static void Postfix()
{
EntryPoint.OnAssetShardManagerReady();
}
}
[HarmonyPriority(600)]
[HarmonyPatch(typeof(GameDataInit), "Initialize")]
internal static class GameDataInit_Initialize_Patch
{
public static void Postfix()
{
EntryPoint.OnGameDataInit();
}
}
}
}
namespace CustomPalettes.Core
{
public class CustomPalette
{
public string Name { get; set; } = "Custom Palette";
public string Author { get; set; } = string.Empty;
public string SortingName { get; set; } = "MyCustomPalette";
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
};
}
}
public class PaletteManager
{
private static string _path;
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 => "dev.aurirex.gtfo.custompalettes_CustomPalette_".ToUpper();
public static bool DoLoadTemplateFile { get; set; } = false;
public static IEnumerable<CustomPalette> Palletes => _palettes;
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"
}
}
};
}
internal static void InjectPalettes(bool forceRegeneration = false)
{
//IL_0115: Unknown result type (might be due to invalid IL or missing references)
//IL_011a: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Expected O, but got Unknown
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
//IL_0129: Expected O, but got Unknown
//IL_0129: Unknown result type (might be due to invalid IL or missing references)
//IL_0134: Unknown result type (might be due to invalid IL or missing references)
//IL_013b: Unknown result type (might be due to invalid IL or missing references)
//IL_0148: Unknown result type (might be due to invalid IL or missing references)
//IL_0163: 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}");
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 (GameDataBlockBase<VanityItemsTemplateDataBlock>.HasBlock(text))
{
VanityItemsTemplateDataBlock block = GameDataBlockBase<VanityItemsTemplateDataBlock>.GetBlock(text);
block.prefab = GeneratePrefab(text, item.Data, forceRegeneration);
block.publicName = item.Name;
((GameDataBlockBase<VanityItemsTemplateDataBlock>)(object)block).internalEnabled = true;
continue;
}
VanityItemsTemplateDataBlock val = new VanityItemsTemplateDataBlock();
((GameDataBlockBase<VanityItemsTemplateDataBlock>)val).name = text;
((GameDataBlockBase<VanityItemsTemplateDataBlock>)val).internalEnabled = true;
val.DropWeight = 1f;
val.type = (ClothesType)4;
val.publicName = item.Name;
val.prefab = GeneratePrefab(text, item.Data, forceRegeneration);
GameDataBlockBase<VanityItemsTemplateDataBlock>.AddBlock(val, -1);
}
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 class TextureLoader
{
private static readonly Dictionary<string, TextureData> _textures = new Dictionary<string, TextureData>();
public static void Setup(IEnumerable<CustomPalette> palletes, bool doCleanup = false)
{
foreach (CustomPalette pallete in palletes)
{
if (pallete != null)
{
ProcessPaletteData(pallete);
}
}
if (doCleanup)
{
CleanupUnusedTextures(palletes);
}
}
private static void CleanupUnusedTextures(IEnumerable<CustomPalette> palletes)
{
HashSet<string> hashSet = new HashSet<string>(_textures.Keys);
foreach (CustomPalette pallete in palletes)
{
IEnumerable<PaletteData.Tone> enumerable = pallete?.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; }
}
}