diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 557c6832..c3bdbff5 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -65,8 +65,11 @@ namespace StardewModdingAPI internal static GameFramework GameFramework { get; } = GameFramework.MonoGame; /// The game's assembly name. +#if SMAPI_FOR_MOBILE + internal static string GameAssemblyName { get; } = "StardewValley"; +#else internal static string GameAssemblyName { get; } = "Stardew Valley"; - +#endif /// The value which should appear in the SMAPI log, if any. internal static int? LogScreenId { get; set; } @@ -139,11 +142,7 @@ namespace StardewModdingAPI /// The directory path in which error logs should be stored. public static string LogDir { get; } = Path.Combine(Constants.DataPath, "ErrorLogs"); /// The directory path where all saves are stored. -#if SMAPI_FOR_MOBILE - public static string SavesPath { get; } = Constants.DataPath; -#else public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves"); -#endif /// The name of the current save folder (if save info is available, regardless of whether the save file exists yet). public static string? SaveFolderName => Constants.GetSaveFolderName(); diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index f0ad5600..51200573 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -178,7 +178,14 @@ namespace StardewModdingAPI.Framework.ModLoading using MemoryStream outSymbolStream = new(); assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider }); byte[] bytes = outAssemblyStream.ToArray(); - lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray()); + if (assembly.File.Name != "MoonSharp.Interpreter.dll" && assembly.File.Name != "PyTK.dll" ) + { + lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray()); + } + else + { + lastAssembly = Assembly.Load(bytes); + } } else { diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/EnumMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/EnumMethods.cs new file mode 100644 index 00000000..cd03229b --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/EnumMethods.cs @@ -0,0 +1,12 @@ +using System; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades; + +public static class EnumMethods +{ + public static bool IsDefined(TEnum value) where TEnum : struct, Enum + { + Type enumType = typeof(TEnum); + return Enum.IsDefined(enumType, value); + } +} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodToAnotherStaticMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodToAnotherStaticMethodRewriter.cs index 66fa094b..cc78b5c0 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodToAnotherStaticMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodToAnotherStaticMethodRewriter.cs @@ -1,6 +1,8 @@ using System; +using HarmonyLib; using Mono.Cecil; using Mono.Cecil.Cil; +using MonoMod.Utils; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters @@ -31,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The type with methods to map to. /// Whether to only rewrite references if loading the assembly on a different platform than it was compiled on. public MethodToAnotherStaticMethodRewriter(Type fromType, Predicate fromMethodSelector, Type toType, string toMethod) - : base( $"{fromType.Name} methods") + : base($"{fromType.Name} methods") { this.FromType = fromType; this.FromMethodSelector = fromMethodSelector; @@ -49,7 +51,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters if (!this.IsMatch(instruction)) return false; - instruction.Operand = module.ImportReference(this.ToType.GetMethod(this.ToMethod)); + MethodReference newReference = module.ImportReference(this.ToType.GetMethod(this.ToMethod)); + if (instruction.Operand is GenericInstanceMethod instructionOperand) + { + GenericInstanceMethod genericInstance = new(newReference); + genericInstance.GenericArguments.AddRange(instructionOperand.GenericArguments); + newReference = genericInstance; + } + instruction.Operand = newReference; return this.MarkRewritten(); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 4e559a47..e0c08a09 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -293,6 +293,7 @@ namespace StardewModdingAPI.Framework new StringPatcher(this.Reflection), new ThreadSilenceExitPatch(this.Monitor), new UIThreadPatch(this.Monitor), + new SaveGamePatch(this.Translator, this.Monitor), #endif new TitleMenuPatcher(this.OnLoadStageChanged) ); diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index aaabf6d1..b545a421 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -98,6 +98,8 @@ namespace StardewModdingAPI.Metadata yield return new MethodToAnotherStaticMethodRewriter(typeof(ISoundBank), (method) => method.Name == nameof(SoundBankMethods.AddCue), typeof(SoundBankMethods), "AddCue"); yield return new MethodToAnotherStaticMethodRewriter(typeof(ISoundBank), (method) => method.Name == nameof(SoundBankMethods.GetCueDefinition), typeof(SoundBankMethods), "GetCueDefinition"); + yield return new MethodToAnotherStaticMethodRewriter(typeof(Enum), (method) => method.Name == nameof(EnumMethods.IsDefined) && method.Parameters.Count == 1, typeof(EnumMethods), "IsDefined"); + //Constructor Rewrites yield return new MethodParentRewriter(typeof(MapPage), typeof(MapPageMethods)); yield return new MethodParentRewriter(typeof(ItemGrabMenu), typeof(ItemGrabMenuMethods)); diff --git a/src/SMAPI/Patches/SaveGamePatch.cs b/src/SMAPI/Patches/SaveGamePatch.cs index 77a2b74a..1afb6cbd 100644 --- a/src/SMAPI/Patches/SaveGamePatch.cs +++ b/src/SMAPI/Patches/SaveGamePatch.cs @@ -3,10 +3,9 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Reflection; +using System.Xml.Serialization; using HarmonyLib; // using Microsoft.AppCenter.Crashes; -using Microsoft.Xna.Framework; using StardewModdingAPI.Framework; using StardewModdingAPI.Internal.Patching; using StardewValley; @@ -45,13 +44,25 @@ namespace StardewModdingAPI.Patches /// The Harmony instance. public override void Apply(Harmony harmony, IMonitor monitor) { + // harmony.Patch( + // original: AccessTools.Method(typeof(SaveGame), "HandleLoadError"), + // prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.Prefix)) + // ); + // harmony.Patch( + // original: AccessTools.Method(typeof(SaveGameMenu), "update"), + // finalizer: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.SaveGameMenu_UpdateFinalizer)) + // ); harmony.Patch( - original: AccessTools.Method(typeof(SaveGame), "HandleLoadError"), - prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.Prefix)) + original: AccessTools.Method(typeof(XmlSerializer).Assembly.GetType("System.Xml.Serialization.XmlSerializationReaderInterpreter"), "GetValueFromXmlString"), + prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.XmlSerializationReaderInterpreter_PrefixGetValueFromXmlString)) ); harmony.Patch( - original: AccessTools.Method(typeof(SaveGameMenu), "update"), - finalizer: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.SaveGameMenu_UpdateFinalizer)) + original: AccessTools.Method(typeof(XmlSerializer).Assembly.GetType("System.Xml.Serialization.XmlSerializationReaderInterpreter"), "AddListValue"), + prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.XmlSerializationReaderInterpreter_PrefixAddListValue)) + ); + harmony.Patch( + original: AccessTools.Method(typeof(XmlSerializer).Assembly.GetType("System.Xml.Serialization.XmlSerializationWriterInterpreter"), "GetEnumXmlValue"), + prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.XmlSerializationWriterInterpreter_PrefixGetEnumXmlValue)) ); } @@ -92,27 +103,20 @@ namespace StardewModdingAPI.Patches else if (SaveGame.newerBackUpExists(fileName) != null) SaveGame.Load(fileName, false, true); else if (SaveGame.oldBackUpExists(fileName) != null) - { SaveGame.Load(fileName, false, true); - } else { if (SaveGame.partialOldBackUpExists(fileName) == null) - { failed = true; - } else { IEnumerator enumerator1 = SaveGame.getLoadEnumerator(fileName, false, true, true); while (enumerator1 != null) - { if (!enumerator1.MoveNext()) enumerator1 = null; - } IEnumerator enumerator2 = SaveGame.Save(); while (enumerator2 != null) - { try { if (!enumerator2.MoveNext()) @@ -124,12 +128,10 @@ namespace StardewModdingAPI.Patches // Crashes.TrackError(ex, null, errorAttachmentLogArray); failed = true; } - } } } if (failed) - { SAlertDialogUtil.AlertMessage( SaveGamePatch.Translator.Get("warn.save-broken"), positive: SaveGamePatch.Translator.Get("btn.swap"), @@ -138,33 +140,86 @@ namespace StardewModdingAPI.Patches { if (action == SAlertDialogUtil.ActionType.POSITIVE) { - if (!SaveGame.swapForOldSave()) - { - Game1.ExitToTitle(); - } + if (!SaveGame.swapForOldSave()) Game1.ExitToTitle(); } else - { Game1.ExitToTitle(); - } } ); - } return false; } + /// The method to call instead of . /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static Exception SaveGameMenu_UpdateFinalizer(SaveGameMenu __instance, Exception __exception) { - if(__exception != null) { + if (__exception != null) + { SaveGamePatch.Monitor.Log($"Failed during SaveGameMenu.update method :\n{__exception.InnerException ?? __exception}", LogLevel.Error); __instance.complete(); Game1.addHUDMessage(new HUDMessage("An error occurs during save the game.Check the error log for details.", HUDMessage.error_type)); } + return null; } + + private static bool XmlSerializationReaderInterpreter_PrefixGetValueFromXmlString(string value, ref object __result) + { + if (value?.Length > 0) return true; + __result = null; + return false; + } + + private static bool XmlSerializationReaderInterpreter_PrefixAddListValue(object listType, ref object list, int index, object value, bool canCreateInstance) + { + Type type = (Type)AccessTools.Property(listType.GetType(), "Type").GetValue(listType); + if (type.IsArray) return true; + + if (list == null) + { + if (!canCreateInstance) throw new InvalidOperationException(string.Format("Could not serialize {0}. Default constructors are required for collections and enumerators.", type.FullName)); + + list = Activator.CreateInstance(type, true); + } + + Type listItemType = (Type)AccessTools.Property(listType.GetType(), "ListItemType").GetValue(listType); + if (listItemType.IsEnum && value != null) + type.GetMethod("Add", new[] { typeof(string) }).Invoke(list, new[] { value.ToString() }); + else + type.GetMethod("Add", new[] { listItemType }).Invoke(list, new[] { value }); + + return false; + } + + private static bool XmlSerializationWriterInterpreter_PrefixGetEnumXmlValue(XmlTypeMapping typeMap, object ob, ref string __result) + { + if (ob == null) + { + __result = null; + return false; + } + + object objectMap = AccessTools.Property(typeMap.GetType(), "ObjectMap").GetValue(typeMap); + if (ob is string enumString) + { + string[] enumNames = (string[])AccessTools.Property(objectMap.GetType(), "EnumNames").GetValue(objectMap); + string[] xmlNames = (string[])AccessTools.Property(objectMap.GetType(), "XmlNames").GetValue(objectMap); + for (int i = 0; i < enumNames.Length; i++) + if (enumString == enumNames[i]) + { + __result = xmlNames[i]; + return false; + } + + __result = null; + } + else + __result = (string)AccessTools.Method(objectMap.GetType(), "GetXmlName").Invoke(objectMap, new[] { typeMap.TypeFullName, ob }); + + return false; + } } } #endif