using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("RESTORE.PlayerDataPatcher")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("RESTORE.PlayerDataPatcher")] [assembly: AssemblyTitle("RESTORE.PlayerDataPatcher")] [assembly: AssemblyVersion("1.0.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; } } } namespace RestorePlayerDataPatcher { public static class Patcher { private enum FieldKind { Bool, Int, Float, String } private sealed class FieldSpec { public FieldKind Type; public string Name; public object DefaultValue; public string Source; } private sealed class InjectedField { public FieldSpec Spec; public FieldDefinition Field; } private const string ConfigFileName = "restore_playerdata_fields.txt"; private static readonly ManualLogSource Log = Logger.CreateLogSource("RESTORE.PlayerDataPatcher"); private static readonly List Specs = new List(); private static bool specsLoaded; public static IEnumerable TargetDLLs { get { EnsureSpecsLoaded(); yield return "Assembly-CSharp.dll"; } } public static void Patch(AssemblyDefinition assembly) { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Expected O, but got Unknown EnsureSpecsLoaded(); if (assembly == null || assembly.MainModule == null) { Log.LogError((object)"Assembly-CSharp was null."); return; } TypeDefinition val = FindPlayerData(assembly.MainModule); if (val == null) { Log.LogError((object)"Could not find PlayerData in Assembly-CSharp."); return; } List list = new List(); for (int i = 0; i < Specs.Count; i++) { FieldSpec fieldSpec = Specs[i]; FieldDefinition val2 = FindField(val, fieldSpec.Name); if (val2 != null) { TypeReference typeReference = GetTypeReference(assembly.MainModule, fieldSpec.Type); if (((MemberReference)((FieldReference)val2).FieldType).FullName != ((MemberReference)typeReference).FullName) { Log.LogError((object)("PlayerData field '" + fieldSpec.Name + "' already exists as " + ((MemberReference)((FieldReference)val2).FieldType).FullName + ", not " + ((MemberReference)typeReference).FullName + ".")); } else { Log.LogInfo((object)("PlayerData field already exists: " + ((MemberReference)((FieldReference)val2).FieldType).Name + " " + ((MemberReference)val2).Name + ".")); } } else { TypeReference typeReference2 = GetTypeReference(assembly.MainModule, fieldSpec.Type); FieldDefinition val3 = new FieldDefinition(fieldSpec.Name, (FieldAttributes)6, typeReference2); val.Fields.Add(val3); list.Add(new InjectedField { Spec = fieldSpec, Field = val3 }); Log.LogInfo((object)("Injected real PlayerData field: " + ((MemberReference)typeReference2).Name + " " + fieldSpec.Name + ".")); } } if (list.Count > 0) { InjectDefaults(val, list); } Log.LogInfo((object)("RESTORE PlayerData patch complete. " + list.Count + " field(s) injected.")); } private static void EnsureSpecsLoaded() { if (!specsLoaded) { specsLoaded = true; Specs.Clear(); List list = FindConfigFiles(); for (int i = 0; i < list.Count; i++) { LoadConfigFile(list[i]); } if (Specs.Count == 0) { Log.LogWarning((object)"No restore_playerdata_fields.txt was found. Using the three built-in example fields."); AddOrValidate(new FieldSpec { Type = FieldKind.Bool, Name = "restoreStartedAct4", DefaultValue = false, Source = "" }); AddOrValidate(new FieldSpec { Type = FieldKind.Int, Name = "restoreOldHeartCount", DefaultValue = 0, Source = "" }); AddOrValidate(new FieldSpec { Type = FieldKind.String, Name = "restoreCurrentObjective", DefaultValue = string.Empty, Source = "" }); } Log.LogInfo((object)("Loaded " + Specs.Count + " RESTORE PlayerData field definition(s).")); } } private static List FindConfigFiles() { List list = new List(); AddConfigFilesFromDirectory(Paths.PluginPath, list); AddConfigFilesFromDirectory(Paths.PatcherPluginPath, list); return list; } private static void AddConfigFilesFromDirectory(string directory, List results) { if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) { return; } string[] files; try { files = Directory.GetFiles(directory, "restore_playerdata_fields.txt", SearchOption.AllDirectories); } catch (Exception ex) { Log.LogWarning((object)("Could not search for restore_playerdata_fields.txt under " + directory + ":\n" + ex)); return; } for (int i = 0; i < files.Length; i++) { string fullPath = Path.GetFullPath(files[i]); bool flag = false; for (int j = 0; j < results.Count; j++) { if (string.Equals(results[j], fullPath, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { results.Add(fullPath); } } } private static void LoadConfigFile(string path) { Log.LogInfo((object)("Loading PlayerData fields from: " + path)); string[] array; try { array = File.ReadAllLines(path); } catch (Exception ex) { Log.LogError((object)("Could not read " + path + ":\n" + ex)); return; } for (int i = 0; i < array.Length; i++) { string text = ((array[i] != null) ? array[i].Trim() : string.Empty); if (text.Length != 0 && !text.StartsWith("#") && !text.StartsWith("//")) { FieldSpec spec; try { spec = ParseLine(text, path, i + 1); } catch (Exception ex2) { Log.LogError((object)(path + ":" + (i + 1) + ": " + ex2.Message)); continue; } AddOrValidate(spec); } } } private static FieldSpec ParseLine(string line, string path, int lineNumber) { SplitDefinitionLine(line, out var typeText, out var name, out var defaultText); FieldKind fieldKind = ParseKind(typeText); if (!IsValidIdentifier(name)) { throw new FormatException("'" + name + "' is not a valid C# field name."); } object defaultValue = ParseDefault(fieldKind, defaultText); return new FieldSpec { Type = fieldKind, Name = name, DefaultValue = defaultValue, Source = path + ":" + lineNumber }; } private static void SplitDefinitionLine(string line, out string typeText, out string name, out string defaultText) { int num = SkipWhitespace(line, 0); int num2 = FindWhitespace(line, num); if (num >= line.Length || num2 <= num) { throw new FormatException("Expected: [default]"); } typeText = line.Substring(num, num2 - num); int num3 = SkipWhitespace(line, num2); int num4 = FindWhitespace(line, num3); if (num3 >= line.Length) { throw new FormatException("Expected a field name after the type."); } if (num4 <= num3) { num4 = line.Length; } name = line.Substring(num3, num4 - num3); int num5 = SkipWhitespace(line, num4); defaultText = ((num5 < line.Length) ? line.Substring(num5) : string.Empty); } private static int SkipWhitespace(string text, int index) { while (index < text.Length && char.IsWhiteSpace(text[index])) { index++; } return index; } private static int FindWhitespace(string text, int index) { while (index < text.Length && !char.IsWhiteSpace(text[index])) { index++; } return index; } private static FieldKind ParseKind(string value) { if (string.Equals(value, "bool", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "boolean", StringComparison.OrdinalIgnoreCase)) { return FieldKind.Bool; } if (string.Equals(value, "int", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "int32", StringComparison.OrdinalIgnoreCase)) { return FieldKind.Int; } if (string.Equals(value, "float", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "single", StringComparison.OrdinalIgnoreCase)) { return FieldKind.Float; } if (string.Equals(value, "string", StringComparison.OrdinalIgnoreCase)) { return FieldKind.String; } throw new FormatException("Unsupported type '" + value + "'. Use bool, int, float, or string."); } private static object ParseDefault(FieldKind kind, string value) { switch (kind) { case FieldKind.Bool: { if (string.IsNullOrWhiteSpace(value)) { return false; } if (bool.TryParse(value.Trim(), out var result2)) { return result2; } if (value.Trim() == "1") { return true; } if (value.Trim() == "0") { return false; } throw new FormatException("Invalid bool default '" + value + "'."); } case FieldKind.Int: { if (string.IsNullOrWhiteSpace(value)) { return 0; } if (!int.TryParse(value.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { throw new FormatException("Invalid int default '" + value + "'."); } return result; } case FieldKind.Float: { if (string.IsNullOrWhiteSpace(value)) { return 0f; } if (!float.TryParse(value.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3)) { throw new FormatException("Invalid float default '" + value + "'."); } return result3; } default: { if (string.IsNullOrEmpty(value)) { return string.Empty; } string text = value.Trim(); if (text.Length >= 2 && text[0] == '"' && text[text.Length - 1] == '"') { text = text.Substring(1, text.Length - 2); } return UnescapeString(text); } } } private static string UnescapeString(string value) { return value.Replace("\\\\", "\u0001").Replace("\\n", "\n").Replace("\\r", "\r") .Replace("\\t", "\t") .Replace("\\\"", "\"") .Replace("\u0001", "\\"); } private static bool IsValidIdentifier(string value) { if (string.IsNullOrEmpty(value)) { return false; } char c = value[0]; if (!char.IsLetter(c) && c != '_') { return false; } for (int i = 1; i < value.Length; i++) { char c2 = value[i]; if (!char.IsLetterOrDigit(c2) && c2 != '_') { return false; } } return true; } private static void AddOrValidate(FieldSpec spec) { for (int i = 0; i < Specs.Count; i++) { FieldSpec fieldSpec = Specs[i]; if (string.Equals(fieldSpec.Name, spec.Name, StringComparison.Ordinal)) { if (fieldSpec.Type != spec.Type) { Log.LogError((object)("Conflicting PlayerData definitions for '" + spec.Name + "': " + fieldSpec.Type.ToString() + " from " + fieldSpec.Source + " and " + spec.Type.ToString() + " from " + spec.Source + ".")); } return; } } Specs.Add(spec); } private static TypeDefinition FindPlayerData(ModuleDefinition module) { for (int i = 0; i < module.Types.Count; i++) { TypeDefinition val = module.Types[i]; if (((MemberReference)val).Name == "PlayerData" && string.IsNullOrEmpty(((TypeReference)val).Namespace)) { return val; } } return null; } private static FieldDefinition FindField(TypeDefinition type, string name) { for (int i = 0; i < type.Fields.Count; i++) { FieldDefinition val = type.Fields[i]; if (((MemberReference)val).Name == name) { return val; } } return null; } private static TypeReference GetTypeReference(ModuleDefinition module, FieldKind kind) { return (TypeReference)(kind switch { FieldKind.Bool => module.TypeSystem.Boolean, FieldKind.Int => module.TypeSystem.Int32, FieldKind.Float => module.TypeSystem.Single, _ => module.TypeSystem.String, }); } private static void InjectDefaults(TypeDefinition playerData, List fields) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) MethodDefinition val = FindSetupNewPlayerData(playerData); if (val == null || !val.HasBody) { Log.LogError((object)"Could not find PlayerData.SetupNewPlayerData(bool); fields were injected but custom defaults were not."); return; } ILProcessor iLProcessor = val.Body.GetILProcessor(); Instruction val2 = ((val.Body.Instructions.Count > 0) ? val.Body.Instructions[0] : null); if (val2 == null) { val2 = Instruction.Create(OpCodes.Ret); iLProcessor.Append(val2); } for (int i = 0; i < fields.Count; i++) { InjectedField injectedField = fields[i]; iLProcessor.InsertBefore(val2, Instruction.Create(OpCodes.Ldarg_0)); InsertLoadDefault(iLProcessor, val2, injectedField.Spec); iLProcessor.InsertBefore(val2, Instruction.Create(OpCodes.Stfld, (FieldReference)(object)injectedField.Field)); } Log.LogInfo((object)"Injected default initialization into PlayerData.SetupNewPlayerData(bool)."); } private static MethodDefinition FindSetupNewPlayerData(TypeDefinition playerData) { for (int i = 0; i < playerData.Methods.Count; i++) { MethodDefinition val = playerData.Methods[i]; if (!(((MemberReference)val).Name != "SetupNewPlayerData") && ((MethodReference)val).Parameters.Count == 1 && !(((MemberReference)((ParameterReference)((MethodReference)val).Parameters[0]).ParameterType).FullName != "System.Boolean")) { return val; } } return null; } private static void InsertLoadDefault(ILProcessor il, Instruction before, FieldSpec spec) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) if (spec.Type == FieldKind.Bool) { bool flag = (bool)spec.DefaultValue; il.InsertBefore(before, Instruction.Create(flag ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); } else if (spec.Type == FieldKind.Int) { il.InsertBefore(before, Instruction.Create(OpCodes.Ldc_I4, (int)spec.DefaultValue)); } else if (spec.Type == FieldKind.Float) { il.InsertBefore(before, Instruction.Create(OpCodes.Ldc_R4, (float)spec.DefaultValue)); } else if (!(spec.DefaultValue is string text)) { il.InsertBefore(before, Instruction.Create(OpCodes.Ldnull)); } else { il.InsertBefore(before, Instruction.Create(OpCodes.Ldstr, text)); } } } }