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 System.Xml.Linq; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using FFT.Config; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json.Linq; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("FFTweaks")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1501d0e07f267e955fac1f737c813cb6a3472e53")] [assembly: AssemblyProduct("FFTweaks")] [assembly: AssemblyTitle("FFTweaks")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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; } } } namespace FFT.XMLEditor { [BepInPlugin("fierrof.fft.xmleditor", "FFT.XMLEditor", "1.0.0")] public class XMLEditorPlugin : BaseUnityPlugin { private void Awake() { ReplacementControl.Initialize(((BaseUnityPlugin)this).Info.Location, ((BaseUnityPlugin)this).Logger); ReplacementControl.RefreshRequested += Run; Run(); } private void Run() { try { string path = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty; string text = Path.Combine(path, "data"); if (!Directory.Exists(text)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("data folder not found: " + text)); return; } string[] files = Directory.GetFiles(text, "*.json", SearchOption.AllDirectories); foreach (string path2 in files) { JObject val = JObject.Parse(File.ReadAllText(path2)); JToken obj = val["xmls"]; JArray val2 = (JArray)(object)((obj is JArray) ? obj : null); if (val2 == null) { continue; } foreach (JToken item in val2) { JObject val3 = (JObject)(object)((item is JObject) ? item : null); bool isFlagged = ReplacementControl.IsFlagEnabled((val3 != null) ? val3["once"] : null); if (!ReplacementControl.ShouldApplyFlaggedReplacement(isFlagged)) { continue; } string text2 = (string)((val3 != null) ? val3["targetPath"] : null); if (string.IsNullOrWhiteSpace(text2)) { continue; } string target = text2.Replace('/', Path.DirectorySeparatorChar); string text3 = ResolveTargetPath(target); if (!File.Exists(text3)) { continue; } XDocument val4 = XDocument.Load(text3, (LoadOptions)1); bool flag = false; JToken obj2 = val3["replacements"]; JArray val5 = (JArray)(object)((obj2 is JArray) ? obj2 : null); if (val5 != null) { foreach (JToken item2 in val5) { JArray val6 = (JArray)(object)((item2 is JArray) ? item2 : null); if (val6 == null || ((JContainer)val6).Count < 2) { continue; } string text4 = (string)val6[0]; string text5 = (string)val6[1]; if (string.IsNullOrWhiteSpace(text4) || text5 == null) { continue; } foreach (XElement item3 in ((XContainer)val4).Descendants()) { if (!string.Equals((string)item3.Attribute(XName.op_Implicit("key")), text4, StringComparison.OrdinalIgnoreCase) && !string.Equals((string)item3.Attribute(XName.op_Implicit("name")), text4, StringComparison.OrdinalIgnoreCase) && !string.Equals(item3.Name.LocalName, text4, StringComparison.OrdinalIgnoreCase)) { continue; } XAttribute val7 = item3.Attribute(XName.op_Implicit("value")); if (val7 != null) { val7.Value = text5; flag = true; continue; } bool flag2 = false; string[] array = text5.Split(new char[1] { ',' }); foreach (string text6 in array) { string[] array2 = text6.Split(new char[1] { '=' }); if (array2.Length == 2) { XElement val8 = ((XContainer)item3).Element(XName.op_Implicit(array2[0].Trim())); if (val8 != null) { val8.Value = array2[1].Trim(); flag2 = true; flag = true; } } } if (!flag2 && !item3.HasElements) { item3.Value = text5; flag = true; } } } } JToken obj3 = val3["additions"]; JArray val9 = (JArray)(object)((obj3 is JArray) ? obj3 : null); if (val9 != null) { foreach (JToken item4 in val9) { JArray val10 = (JArray)(object)((item4 is JArray) ? item4 : null); if (val10 == null || ((JContainer)val10).Count < 2) { continue; } string parentName = (string)val10[0]; string text7 = (string)val10[1]; if (string.IsNullOrWhiteSpace(parentName) || string.IsNullOrWhiteSpace(text7)) { continue; } XElement parsed = XElement.Parse(text7); foreach (XElement item5 in from e in ((XContainer)val4).Descendants() where string.Equals(e.Name.LocalName, parentName, StringComparison.OrdinalIgnoreCase) select e) { if (!((XContainer)item5).Elements().Any((XElement e) => XNode.DeepEquals((XNode)(object)e, (XNode)(object)parsed))) { ((XContainer)item5).Add((object)parsed); flag = true; } } } } if (flag) { val4.Save(text3); } } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private static string ResolveTargetPath(string target) { if (Path.IsPathRooted(target)) { return target; } string text = target.Replace('/', Path.DirectorySeparatorChar); string path = Directory.GetParent(Paths.PluginPath)?.FullName ?? Paths.PluginPath; char directorySeparatorChar = Path.DirectorySeparatorChar; if (text.StartsWith("plugins" + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { return Path.Combine(path, text); } return Path.Combine(path, "plugins", text); } } } namespace FFT.RecipeUnlocker { [BepInPlugin("fierrof.fft.recipeunlocker", "FFT.RecipeUnlocker", "1.0.0")] public class RecipeUnlockerPlugin : BaseUnityPlugin { private static RecipeUnlockerPlugin Instance; private readonly Dictionary recipeByItemId = new Dictionary(); private void Awake() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown Instance = this; LoadRecipes(); PatchEquipItem(new Harmony("fierrof.fft.recipeunlocker")); } private void PatchEquipItem(Harmony harmony) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown Type type = AccessTools.TypeByName("CharacterEquipment"); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"CharacterEquipment type not found; equip patch skipped."); return; } MethodInfo[] source = (from method in AccessTools.GetDeclaredMethods(type) where method.Name == "EquipItem" select method).ToArray(); MethodInfo methodInfo = source.FirstOrDefault(delegate(MethodInfo method) { ParameterInfo[] parameters2 = method.GetParameters(); return parameters2.Length == 2 && parameters2[1].ParameterType == typeof(bool) && string.Equals(parameters2[0].ParameterType.Name, "Equipment", StringComparison.Ordinal); }); MethodInfo methodInfo2 = methodInfo ?? source.FirstOrDefault(delegate(MethodInfo method) { ParameterInfo[] parameters = method.GetParameters(); return parameters.Length == 2 && parameters[1].ParameterType == typeof(bool); }) ?? source.FirstOrDefault(); if (methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"EquipItem method not found; equip patch skipped."); } else { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RecipeUnlockerPlugin), "EquipPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void EquipPostfix(object __instance, object __0) { Instance?.HandleEquip(__instance, __0); } private void LoadRecipes() { string path = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty; string text = Path.Combine(path, "data"); if (!Directory.Exists(text)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("data folder not found: " + text)); return; } string[] files = Directory.GetFiles(text, "*.json", SearchOption.AllDirectories); foreach (string path2 in files) { try { JObject val = JObject.Parse(File.ReadAllText(path2)); JToken obj = val["recipes"]; JArray val2 = (JArray)(object)((obj is JArray) ? obj : null); if (val2 == null) { continue; } foreach (JToken item in val2) { JArray val3 = (JArray)(object)((item is JArray) ? item : null); if (val3 != null && ((JContainer)val3).Count >= 2) { int num = ParseInt(val3[0]); string value = (string)val3[1]; if (num != int.MinValue && !string.IsNullOrWhiteSpace(value)) { recipeByItemId[num] = value; } } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } } private void HandleEquip(object characterEquipment, object equippedItem) { if (characterEquipment == null || equippedItem == null) { return; } object obj = Read(characterEquipment, "m_character", "Character", "OwnerCharacter"); object obj2 = Read(obj, "IsLocalPlayer"); if (obj2 is bool && (bool)obj2) { int key = ParseInt(Read(equippedItem, "ItemID", "m_itemID", "ItemId")); if (recipeByItemId.TryGetValue(key, out var value)) { TryLearnRecipeByUid(obj, value); } } } private static void TryLearnRecipeByUid(object character, string recipeUid) { object instance = Read(character, "Inventory"); object obj = Read(instance, "RecipeKnowledge"); if (obj == null) { return; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "IsRecipeLearned", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo != null) { object obj2 = methodInfo.Invoke(obj, new object[1] { recipeUid }); bool flag = default(bool); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return; } } Type type = AccessTools.TypeByName("Recipe"); MethodInfo methodInfo2 = ((type != null) ? AccessTools.Method(obj.GetType(), "LearnRecipe", new Type[1] { type }, (Type[])null) : null); object obj3 = FindRecipeByUid(recipeUid, type); if (methodInfo2 != null && obj3 != null) { methodInfo2.Invoke(obj, new object[1] { obj3 }); return; } MethodInfo methodInfo3 = AccessTools.Method(obj.GetType(), "LearnRecipe", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo3 != null) { methodInfo3.Invoke(obj, new object[1] { recipeUid }); } } private static object FindRecipeByUid(string recipeUid, Type recipeType) { if (string.IsNullOrWhiteSpace(recipeUid) || recipeType == null) { return null; } Object[] array = Resources.FindObjectsOfTypeAll(recipeType); foreach (object obj in array) { string a = Read(obj, "UID", "RecipeID", "m_recipeID")?.ToString(); if (string.Equals(a, recipeUid, StringComparison.OrdinalIgnoreCase)) { return obj; } } return null; } private static object Read(object instance, params string[] memberNames) { if (instance == null) { return null; } Traverse val = Traverse.Create(instance); foreach (string text in memberNames) { Traverse val2 = val.Field(text); if (val2.FieldExists()) { return val2.GetValue(); } Traverse val3 = val.Property(text, (object[])null); if (val3.PropertyExists()) { return val3.GetValue(); } } return null; } private static int ParseInt(object value) { int result; return (value != null && int.TryParse(value.ToString(), out result)) ? result : int.MinValue; } } } namespace FFT.TrueHardcore { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("fierrof.fft.truehardcore", "FFT.TrueHardcore", "1.0.0")] public class TrueHardcoreLootAnimPatch : BaseUnityPlugin { private const string TrueHardcoreHarmonyId = "com.iggy.hardcorere"; private const string TargetPrefixType = "HardcoreRebalance.CombatAnims+InteractionTriggerBase_TryActivateBasicAction"; private Harmony _harmony; private void Awake() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown _harmony = new Harmony("fierrof.fft.truehardcore"); Type type = AccessTools.TypeByName("InteractionTriggerBase"); Type type2 = AccessTools.TypeByName("Character"); if (type == null || type2 == null) { ((BaseUnityPlugin)this).Logger.LogError((object)"Failed to resolve InteractionTriggerBase or Character type."); return; } MethodInfo methodInfo = AccessTools.Method(type, "TryActivateBasicAction", new Type[2] { type2, typeof(int) }, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogError((object)"Failed to find InteractionTriggerBase.TryActivateBasicAction(Character,int) target method."); return; } Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo); if (patchInfo?.Prefixes == null || patchInfo.Prefixes.Count == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"No prefixes found on TryActivateBasicAction; nothing to disable."); return; } int num = 0; foreach (Patch prefix in patchInfo.Prefixes) { if (IsTargetPrefix(prefix)) { _harmony.Unpatch((MethodBase)methodInfo, prefix.PatchMethod); num++; } } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Disabled TrueHardcore loot interaction prefix patches: {num}"); } private static bool IsTargetPrefix(Patch prefix) { if (((prefix != null) ? prefix.PatchMethod : null) == null) { return false; } return string.Equals(prefix.owner, "com.iggy.hardcorere", StringComparison.Ordinal) && string.Equals(prefix.PatchMethod.DeclaringType?.FullName, "HardcoreRebalance.CombatAnims+InteractionTriggerBase_TryActivateBasicAction", StringComparison.Ordinal); } } } namespace FFT.FolderReplacer { [BepInPlugin("fierrof.fft.folderreplacer", "FFT.FolderReplacer", "1.0.0")] public class FolderReplacerPlugin : BaseUnityPlugin { private void Awake() { ReplacementControl.Initialize(((BaseUnityPlugin)this).Info.Location, ((BaseUnityPlugin)this).Logger); ReplacementControl.RefreshRequested += Run; Run(); } private void Run() { try { string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty; string text2 = Path.Combine(text, "data"); if (!Directory.Exists(text2)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("data folder not found: " + text2)); return; } string[] files = Directory.GetFiles(text2, "*.json", SearchOption.AllDirectories); foreach (string path in files) { string jsonDir = Path.GetDirectoryName(path) ?? text2; JObject val = JObject.Parse(File.ReadAllText(path)); JToken obj = val["folders"]; JArray val2 = (JArray)(object)((obj is JArray) ? obj : null); if (val2 == null) { continue; } foreach (JToken item in val2) { JArray val3 = (JArray)(object)((item is JArray) ? item : null); if (val3 == null || ((JContainer)val3).Count < 2) { continue; } bool isFlagged = ReplacementControl.IsFlagEnabled(val3); if (!ReplacementControl.ShouldApplyFlaggedReplacement(isFlagged)) { continue; } string text3 = (((string)val3[0]) ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); string text4 = (((string)val3[1]) ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); if (!string.IsNullOrWhiteSpace(text3) && !string.IsNullOrWhiteSpace(text4)) { string text5 = ResolveSourceFolderPath(text3, text2, text, jsonDir); if (Directory.Exists(text5)) { string[] blacklistEntries = GetBlacklistEntries(val3, text3); string targetFolder = ResolveTargetFolderPath(text4); CopyFolderContents(text5, targetFolder, blacklistEntries); } } } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private void CopyFolderContents(string sourceFolder, string targetFolder, string[] blacklist) { string[] files; try { files = Directory.GetFiles(sourceFolder, "*", SearchOption.AllDirectories); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to enumerate source folder '" + sourceFolder + "': " + ex.Message)); return; } string[] array = files; foreach (string text in array) { try { if (!File.Exists(text)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped missing source file during folder replace: " + text)); continue; } string text2 = text.Substring(sourceFolder.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (!IsBlacklisted(text, text2, blacklist)) { string text3 = Path.Combine(targetFolder, text2); string directoryName = Path.GetDirectoryName(text3); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } File.Copy(text, text3, overwrite: true); } } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to copy '" + text + "' into '" + targetFolder + "': " + ex2.Message)); } } } private static string[] GetBlacklistEntries(JArray pair, string source) { List list = new List(); string text = NormalizeRelativePath(source).TrimEnd(new char[1] { Path.DirectorySeparatorChar }); foreach (JToken item in pair) { JObject val = (JObject)(object)((item is JObject) ? item : null); if (val == null) { continue; } JToken obj = val["blacklist"]; JArray val2 = (JArray)(object)((obj is JArray) ? obj : null); if (val2 == null) { continue; } foreach (JToken item2 in val2) { string text2 = (string)item2; if (string.IsNullOrWhiteSpace(text2)) { continue; } string text3 = NormalizeRelativePath(text2); if (Path.IsPathRooted(text3)) { list.Add("abs:" + text3.TrimEnd(new char[1] { Path.DirectorySeparatorChar })); continue; } string text4 = text3; if (!string.IsNullOrEmpty(text)) { char directorySeparatorChar = Path.DirectorySeparatorChar; if (text3.StartsWith(text + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { text4 = text3.Substring(text.Length).TrimStart(new char[1] { Path.DirectorySeparatorChar }); goto IL_0164; } } if (!string.IsNullOrEmpty(text) && text3.Equals(text, StringComparison.OrdinalIgnoreCase)) { text4 = string.Empty; } goto IL_0164; IL_0164: list.Add(text4.TrimEnd(new char[1] { Path.DirectorySeparatorChar })); } } return list.ToArray(); } private static bool IsBlacklisted(string sourceFile, string relativePath, string[] blacklist) { if (blacklist == null || blacklist.Length == 0) { return false; } string text = NormalizeRelativePath(sourceFile).TrimEnd(new char[1] { Path.DirectorySeparatorChar }); string text2 = NormalizeRelativePath(relativePath).TrimEnd(new char[1] { Path.DirectorySeparatorChar }); foreach (string text3 in blacklist) { if (string.IsNullOrWhiteSpace(text3)) { return true; } if (text3.StartsWith("abs:", StringComparison.Ordinal)) { string text4 = text3.Substring(4).TrimEnd(new char[1] { Path.DirectorySeparatorChar }); if (!text.Equals(text4, StringComparison.OrdinalIgnoreCase)) { char directorySeparatorChar = Path.DirectorySeparatorChar; if (!text.StartsWith(text4 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { continue; } } return true; } string text5 = text3.TrimEnd(new char[1] { Path.DirectorySeparatorChar }); if (!text2.Equals(text5, StringComparison.OrdinalIgnoreCase)) { char directorySeparatorChar = Path.DirectorySeparatorChar; if (!text2.StartsWith(text5 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { continue; } } return true; } return false; } private static string NormalizeRelativePath(string path) { return (path ?? string.Empty).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).Trim(); } private static string ResolveSourceFolderPath(string source, string dataRoot, string pluginDir, string jsonDir) { if (Path.IsPathRooted(source)) { return source; } string text = source.Replace('/', Path.DirectorySeparatorChar); char directorySeparatorChar = Path.DirectorySeparatorChar; if (text.StartsWith("data" + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("data".Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string text3 = Path.Combine(dataRoot, text2); if (Directory.Exists(text3)) { return text3; } string name = new DirectoryInfo(jsonDir).Name; directorySeparatorChar = Path.DirectorySeparatorChar; if (text2.StartsWith(name + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { string path = text2.Substring(name.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string text4 = Path.Combine(jsonDir, path); if (Directory.Exists(text4)) { return text4; } } } string text5 = Path.Combine(pluginDir, text); if (Directory.Exists(text5)) { return text5; } return Path.Combine(Paths.PluginPath, text); } private static string ResolveTargetFolderPath(string target) { if (Path.IsPathRooted(target)) { return target; } string path = target.Replace('/', Path.DirectorySeparatorChar); string path2 = Directory.GetParent(Paths.PluginPath)?.FullName ?? Paths.PluginPath; return Path.Combine(path2, path); } } } namespace FFT.FileReplacer { [BepInPlugin("fierrof.fft.filereplacer", "FFT.FileReplacer", "1.0.0")] public class FileReplacerPlugin : BaseUnityPlugin { private void Awake() { ReplacementControl.Initialize(((BaseUnityPlugin)this).Info.Location, ((BaseUnityPlugin)this).Logger); ReplacementControl.RefreshRequested += Run; Run(); } private void Run() { try { string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty; string text2 = Path.Combine(text, "data"); if (!Directory.Exists(text2)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("data folder not found: " + text2)); return; } string[] files = Directory.GetFiles(text2, "*.json", SearchOption.AllDirectories); foreach (string path in files) { string jsonDir = Path.GetDirectoryName(path) ?? text2; JObject val = JObject.Parse(File.ReadAllText(path)); JToken obj = val["files"]; JArray val2 = (JArray)(object)((obj is JArray) ? obj : null); if (val2 == null) { continue; } foreach (JToken item in val2) { JArray val3 = (JArray)(object)((item is JArray) ? item : null); if (val3 == null || ((JContainer)val3).Count < 2) { continue; } bool isFlagged = ReplacementControl.IsFlagEnabled(val3); if (!ReplacementControl.ShouldApplyFlaggedReplacement(isFlagged)) { continue; } string source = (((string)val3[0]) ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); string target = (((string)val3[1]) ?? string.Empty).Replace('/', Path.DirectorySeparatorChar); string text3 = ResolveSourcePath(source, text2, text, jsonDir); string text4 = ResolveTargetPath(target); if (File.Exists(text3)) { string directoryName = Path.GetDirectoryName(text4); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } File.Copy(text3, text4, overwrite: true); } } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private static string ResolveSourcePath(string source, string dataRoot, string pluginDir, string jsonDir) { if (Path.IsPathRooted(source)) { return source; } string text = source.Replace('/', Path.DirectorySeparatorChar); char directorySeparatorChar = Path.DirectorySeparatorChar; if (text.StartsWith("data" + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("data".Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string text3 = Path.Combine(dataRoot, text2); if (File.Exists(text3)) { return text3; } string name = new DirectoryInfo(jsonDir).Name; directorySeparatorChar = Path.DirectorySeparatorChar; if (text2.StartsWith(name + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { string path = text2.Substring(name.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string text4 = Path.Combine(jsonDir, path); if (File.Exists(text4)) { return text4; } } } directorySeparatorChar = Path.DirectorySeparatorChar; if (text.StartsWith("_overrides" + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { string text5 = Path.Combine(dataRoot, text); if (File.Exists(text5)) { return text5; } } if (text.StartsWith("overrides\\", StringComparison.OrdinalIgnoreCase) || text.StartsWith("overrides/", StringComparison.OrdinalIgnoreCase)) { string path2 = text.Substring("overrides".Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string text6 = Path.Combine(dataRoot, "_overrides", path2); if (File.Exists(text6)) { return text6; } } string text7 = Path.Combine(pluginDir, text); if (File.Exists(text7)) { return text7; } return Path.Combine(Paths.PluginPath, text); } private static string ResolveTargetPath(string target) { if (Path.IsPathRooted(target)) { return target; } string text = target.Replace('/', Path.DirectorySeparatorChar); string path = Directory.GetParent(Paths.PluginPath)?.FullName ?? Paths.PluginPath; char directorySeparatorChar = Path.DirectorySeparatorChar; if (text.StartsWith("plugins" + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { return Path.Combine(path, text); } return Path.Combine(path, "plugins", text); } } } namespace FFT.Config { [BepInPlugin("fierrof.fft.config", "FFT.Config", "1.0.0")] public class ConfigPlugin : BaseUnityPlugin { private ConfigEntry overwriteFirstInstallOnly; private ConfigEntry refreshNow; private void Awake() { ReplacementControl.Initialize(((BaseUnityPlugin)this).Info.Location, ((BaseUnityPlugin)this).Logger); overwriteFirstInstallOnly = ((BaseUnityPlugin)this).Config.Bind("Replacements", "OverwriteFirstInstallOnly", false, "When true, replacements flagged as first-install-only will overwrite on startup and refresh."); refreshNow = ((BaseUnityPlugin)this).Config.Bind("Replacements", "RefreshNow", false, "Set true to run all replacement plugins immediately. It resets to false automatically."); ReplacementControl.SetOverwriteFirstInstallOnly(overwriteFirstInstallOnly.Value); overwriteFirstInstallOnly.SettingChanged += delegate { ReplacementControl.SetOverwriteFirstInstallOnly(overwriteFirstInstallOnly.Value); }; refreshNow.SettingChanged += delegate { if (refreshNow.Value) { ReplacementControl.RequestRefresh(); refreshNow.Value = false; } }; } private void Start() { ReplacementControl.MarkInstallComplete(); } } internal static class ReplacementControl { private static readonly object Sync = new object(); private static bool initialized; private static bool firstInstall = true; private static bool overwriteFirstInstallOnly; private static string markerPath; private static ManualLogSource logger; internal static bool IsFirstInstall { get { lock (Sync) { return firstInstall; } } } internal static bool OverwriteFirstInstallOnly { get { lock (Sync) { return overwriteFirstInstallOnly; } } } internal static event Action RefreshRequested; internal static void Initialize(string pluginLocation, ManualLogSource sourceLogger) { lock (Sync) { if (!initialized) { logger = sourceLogger; string path = Path.GetDirectoryName(pluginLocation) ?? string.Empty; markerPath = Path.Combine(path, ".fftweaks.install.complete"); firstInstall = !File.Exists(markerPath); initialized = true; } } } internal static void SetOverwriteFirstInstallOnly(bool value) { lock (Sync) { overwriteFirstInstallOnly = value; } } internal static void RequestRefresh() { ReplacementControl.RefreshRequested?.Invoke(); } internal static void MarkInstallComplete() { lock (Sync) { if (!initialized || string.IsNullOrWhiteSpace(markerPath)) { return; } if (!File.Exists(markerPath)) { File.WriteAllText(markerPath, DateTime.UtcNow.ToString("o")); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"First-install marker created."); } } firstInstall = false; } } internal static bool ShouldApplyFlaggedReplacement(bool isFlagged) { if (!isFlagged) { return true; } return IsFirstInstall || OverwriteFirstInstallOnly; } internal static bool IsFlagEnabled(JArray tuple) { if (tuple == null || ((JContainer)tuple).Count < 3) { return false; } return IsFlagEnabled(tuple[2]); } internal static bool IsFlagEnabled(JToken token) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if (token == null || (int)token.Type == 10) { return false; } if ((int)token.Type == 9) { return Extensions.Value((IEnumerable)token); } string text = ((object)token).ToString().Trim(); if (string.IsNullOrWhiteSpace(text)) { return false; } return string.Equals(text, "once", StringComparison.OrdinalIgnoreCase); } } }