Fix save load/save error

This commit is contained in:
yangzhi 2023-06-29 09:00:23 +08:00
parent bf0903b173
commit 917eaf48c4
7 changed files with 116 additions and 31 deletions

View File

@ -65,8 +65,11 @@ namespace StardewModdingAPI
internal static GameFramework GameFramework { get; } = GameFramework.MonoGame; internal static GameFramework GameFramework { get; } = GameFramework.MonoGame;
/// <summary>The game's assembly name.</summary> /// <summary>The game's assembly name.</summary>
#if SMAPI_FOR_MOBILE
internal static string GameAssemblyName { get; } = "StardewValley";
#else
internal static string GameAssemblyName { get; } = "Stardew Valley"; internal static string GameAssemblyName { get; } = "Stardew Valley";
#endif
/// <summary>The <see cref="Context.ScreenId"/> value which should appear in the SMAPI log, if any.</summary> /// <summary>The <see cref="Context.ScreenId"/> value which should appear in the SMAPI log, if any.</summary>
internal static int? LogScreenId { get; set; } internal static int? LogScreenId { get; set; }
@ -139,11 +142,7 @@ namespace StardewModdingAPI
/// <summary>The directory path in which error logs should be stored.</summary> /// <summary>The directory path in which error logs should be stored.</summary>
public static string LogDir { get; } = Path.Combine(Constants.DataPath, "ErrorLogs"); public static string LogDir { get; } = Path.Combine(Constants.DataPath, "ErrorLogs");
/// <summary>The directory path where all saves are stored.</summary> /// <summary>The directory path where all saves are stored.</summary>
#if SMAPI_FOR_MOBILE
public static string SavesPath { get; } = Constants.DataPath;
#else
public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves"); public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves");
#endif
/// <summary>The name of the current save folder (if save info is available, regardless of whether the save file exists yet).</summary> /// <summary>The name of the current save folder (if save info is available, regardless of whether the save file exists yet).</summary>
public static string? SaveFolderName => Constants.GetSaveFolderName(); public static string? SaveFolderName => Constants.GetSaveFolderName();

View File

@ -178,7 +178,14 @@ namespace StardewModdingAPI.Framework.ModLoading
using MemoryStream outSymbolStream = new(); using MemoryStream outSymbolStream = new();
assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider }); assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider });
byte[] bytes = outAssemblyStream.ToArray(); 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 else
{ {

View File

@ -0,0 +1,12 @@
using System;
namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades;
public static class EnumMethods
{
public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum
{
Type enumType = typeof(TEnum);
return Enum.IsDefined(enumType, value);
}
}

View File

@ -1,6 +1,8 @@
using System; using System;
using HarmonyLib;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using MonoMod.Utils;
using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Rewriters namespace StardewModdingAPI.Framework.ModLoading.Rewriters
@ -31,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/// <param name="toType">The type with methods to map to.</param> /// <param name="toType">The type with methods to map to.</param>
/// <param name="onlyIfPlatformChanged">Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</param> /// <param name="onlyIfPlatformChanged">Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</param>
public MethodToAnotherStaticMethodRewriter(Type fromType, Predicate<MethodReference> fromMethodSelector, Type toType, string toMethod) public MethodToAnotherStaticMethodRewriter(Type fromType, Predicate<MethodReference> fromMethodSelector, Type toType, string toMethod)
: base( $"{fromType.Name} methods") : base($"{fromType.Name} methods")
{ {
this.FromType = fromType; this.FromType = fromType;
this.FromMethodSelector = fromMethodSelector; this.FromMethodSelector = fromMethodSelector;
@ -49,7 +51,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
if (!this.IsMatch(instruction)) if (!this.IsMatch(instruction))
return false; 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(); return this.MarkRewritten();
} }

View File

@ -293,6 +293,7 @@ namespace StardewModdingAPI.Framework
new StringPatcher(this.Reflection), new StringPatcher(this.Reflection),
new ThreadSilenceExitPatch(this.Monitor), new ThreadSilenceExitPatch(this.Monitor),
new UIThreadPatch(this.Monitor), new UIThreadPatch(this.Monitor),
new SaveGamePatch(this.Translator, this.Monitor),
#endif #endif
new TitleMenuPatcher(this.OnLoadStageChanged) new TitleMenuPatcher(this.OnLoadStageChanged)
); );

View File

@ -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.AddCue), typeof(SoundBankMethods), "AddCue");
yield return new MethodToAnotherStaticMethodRewriter(typeof(ISoundBank), (method) => method.Name == nameof(SoundBankMethods.GetCueDefinition), typeof(SoundBankMethods), "GetCueDefinition"); 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 //Constructor Rewrites
yield return new MethodParentRewriter(typeof(MapPage), typeof(MapPageMethods)); yield return new MethodParentRewriter(typeof(MapPage), typeof(MapPageMethods));
yield return new MethodParentRewriter(typeof(ItemGrabMenu), typeof(ItemGrabMenuMethods)); yield return new MethodParentRewriter(typeof(ItemGrabMenu), typeof(ItemGrabMenuMethods));

View File

@ -3,10 +3,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Reflection; using System.Xml.Serialization;
using HarmonyLib; using HarmonyLib;
// using Microsoft.AppCenter.Crashes; // using Microsoft.AppCenter.Crashes;
using Microsoft.Xna.Framework;
using StardewModdingAPI.Framework; using StardewModdingAPI.Framework;
using StardewModdingAPI.Internal.Patching; using StardewModdingAPI.Internal.Patching;
using StardewValley; using StardewValley;
@ -45,13 +44,25 @@ namespace StardewModdingAPI.Patches
/// <param name="harmony">The Harmony instance.</param> /// <param name="harmony">The Harmony instance.</param>
public override void Apply(Harmony harmony, IMonitor monitor) 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( harmony.Patch(
original: AccessTools.Method(typeof(SaveGame), "HandleLoadError"), original: AccessTools.Method(typeof(XmlSerializer).Assembly.GetType("System.Xml.Serialization.XmlSerializationReaderInterpreter"), "GetValueFromXmlString"),
prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.Prefix)) prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.XmlSerializationReaderInterpreter_PrefixGetValueFromXmlString))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(SaveGameMenu), "update"), original: AccessTools.Method(typeof(XmlSerializer).Assembly.GetType("System.Xml.Serialization.XmlSerializationReaderInterpreter"), "AddListValue"),
finalizer: new HarmonyMethod(this.GetType(), nameof(SaveGamePatch.SaveGameMenu_UpdateFinalizer)) 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) else if (SaveGame.newerBackUpExists(fileName) != null)
SaveGame.Load(fileName, false, true); SaveGame.Load(fileName, false, true);
else if (SaveGame.oldBackUpExists(fileName) != null) else if (SaveGame.oldBackUpExists(fileName) != null)
{
SaveGame.Load(fileName, false, true); SaveGame.Load(fileName, false, true);
}
else else
{ {
if (SaveGame.partialOldBackUpExists(fileName) == null) if (SaveGame.partialOldBackUpExists(fileName) == null)
{
failed = true; failed = true;
}
else else
{ {
IEnumerator<int> enumerator1 = SaveGame.getLoadEnumerator(fileName, false, true, true); IEnumerator<int> enumerator1 = SaveGame.getLoadEnumerator(fileName, false, true, true);
while (enumerator1 != null) while (enumerator1 != null)
{
if (!enumerator1.MoveNext()) if (!enumerator1.MoveNext())
enumerator1 = null; enumerator1 = null;
}
IEnumerator<int> enumerator2 = SaveGame.Save(); IEnumerator<int> enumerator2 = SaveGame.Save();
while (enumerator2 != null) while (enumerator2 != null)
{
try try
{ {
if (!enumerator2.MoveNext()) if (!enumerator2.MoveNext())
@ -124,12 +128,10 @@ namespace StardewModdingAPI.Patches
// Crashes.TrackError(ex, null, errorAttachmentLogArray); // Crashes.TrackError(ex, null, errorAttachmentLogArray);
failed = true; failed = true;
} }
}
} }
} }
if (failed) if (failed)
{
SAlertDialogUtil.AlertMessage( SAlertDialogUtil.AlertMessage(
SaveGamePatch.Translator.Get("warn.save-broken"), SaveGamePatch.Translator.Get("warn.save-broken"),
positive: SaveGamePatch.Translator.Get("btn.swap"), positive: SaveGamePatch.Translator.Get("btn.swap"),
@ -138,33 +140,86 @@ namespace StardewModdingAPI.Patches
{ {
if (action == SAlertDialogUtil.ActionType.POSITIVE) if (action == SAlertDialogUtil.ActionType.POSITIVE)
{ {
if (!SaveGame.swapForOldSave()) if (!SaveGame.swapForOldSave()) Game1.ExitToTitle();
{
Game1.ExitToTitle();
}
} }
else else
{
Game1.ExitToTitle(); Game1.ExitToTitle();
}
} }
); );
}
return false; return false;
} }
/// <summary>The method to call instead of <see cref="StardewValley.Menus.SaveGameMenu.update"/>.</summary> /// <summary>The method to call instead of <see cref="StardewValley.Menus.SaveGameMenu.update"/>.</summary>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")]
private static Exception SaveGameMenu_UpdateFinalizer(SaveGameMenu __instance, Exception __exception) 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); SaveGamePatch.Monitor.Log($"Failed during SaveGameMenu.update method :\n{__exception.InnerException ?? __exception}", LogLevel.Error);
__instance.complete(); __instance.complete();
Game1.addHUDMessage(new HUDMessage("An error occurs during save the game.Check the error log for details.", HUDMessage.error_type)); Game1.addHUDMessage(new HUDMessage("An error occurs during save the game.Check the error log for details.", HUDMessage.error_type));
} }
return null; 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 #endif