-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #129 from Kylemc1413/feature/error-messages
Show error and catch exceptions when failing to load beatmap
- Loading branch information
Showing
5 changed files
with
180 additions
and
15 deletions.
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
source/SongCore/HarmonyPatches/HarmonyTranspilersFixPatch.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
using System.Reflection.Emit; | ||
using HarmonyLib; | ||
using SongCore.Utilities; | ||
|
||
namespace SongCore.HarmonyPatches | ||
{ | ||
/// <summary> | ||
/// This patch fixes an <a href="https://github.com/BepInEx/HarmonyX/issues/65">issue</a> with HarmonyX that causes it | ||
/// to remove certain instructions when patching a method with a transpiler. It removes the condition in | ||
/// <see cref="HarmonyLib.Internal.Patching.ILManipulator.WriteTo" /> that deletes existing <c>Leave</c>, <c>Endfinally</c> | ||
/// and <c>Endfilter</c> instructions from the patched method when they are followed by an exception block. | ||
/// </summary> | ||
// TODO: Remove this once fixed. | ||
internal class HarmonyTranspilersFixPatch | ||
{ | ||
public static MethodBase TargetMethod() => AccessTools.Method("HarmonyLib.Internal.Patching.ILManipulator:WriteTo"); | ||
|
||
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) | ||
{ | ||
return new CodeMatcher(instructions) | ||
.MatchStartForward( | ||
new CodeMatch(OpCodes.Ldloc_3), | ||
new CodeMatch(OpCodes.Ldfld), | ||
new CodeMatch(i => i.opcode == OpCodes.Ldsfld && ((FieldInfo)i.operand).Name == nameof(OpCodes.Leave))) | ||
.ThrowIfInvalid() | ||
.RemoveInstructionsInRange(76, 112) | ||
.InstructionEnumeration(); | ||
} | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
source/SongCore/HarmonyPatches/StandardLevelDetailViewControllerPatch.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Reflection.Emit; | ||
using HarmonyLib; | ||
using MonoMod.Utils; | ||
using Polyglot; | ||
using SongCore.Utilities; | ||
|
||
namespace SongCore.HarmonyPatches | ||
{ | ||
/// <summary> | ||
/// This patch catches all exceptions and displays an error message to the user | ||
/// in the <see cref="StandardLevelDetailView"/> when the game is loading beatmap levels. | ||
/// </summary> | ||
// TODO: Make this use MethodType.Async once supported. | ||
[HarmonyPatch] | ||
internal class StandardLevelDetailViewControllerPatch | ||
{ | ||
private static MethodBase TargetMethod() => AccessTools.Method(typeof(StandardLevelDetailViewController), nameof(StandardLevelDetailViewController.ShowLoadingAndDoSomething)).GetStateMachineTarget(); | ||
|
||
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) | ||
{ | ||
var codeMatcher = new CodeMatcher(instructions) | ||
.MatchStartForward(new CodeMatch(i => i.blocks.FirstOrDefault()?.blockType == ExceptionBlockType.BeginCatchBlock)) | ||
.ThrowIfInvalid(); | ||
codeMatcher.Instruction.blocks[0].catchType = typeof(Exception); | ||
return codeMatcher | ||
.SetOpcodeAndAdvance(OpCodes.Stloc_3) | ||
.Insert( | ||
new CodeInstruction(OpCodes.Ldloc_1), | ||
new CodeInstruction(OpCodes.Ldloc_3), | ||
Transpilers.EmitDelegate<Action<StandardLevelDetailViewController, Exception>>((standardLevelDetailViewController, ex) => | ||
{ | ||
var handled = false; | ||
switch (ex) | ||
{ | ||
case OperationCanceledException: | ||
// Base game skips those. | ||
return; | ||
case ArgumentOutOfRangeException: | ||
{ | ||
if (ex.StackTrace.Contains(nameof(BeatmapCharacteristicSegmentedControlController))) | ||
{ | ||
const string errorText = "Error loading beatmap. Missing or unknown characteristic."; | ||
standardLevelDetailViewController.ShowContent(StandardLevelDetailViewController.ContentType.Error, errorText); | ||
Logging.Logger.Error(errorText); | ||
handled = true; | ||
} | ||
|
||
break; | ||
} | ||
case ArgumentNullException: | ||
{ | ||
if (ex.StackTrace.Contains(nameof(BeatmapSaveDataHelpers.GetVersion))) | ||
{ | ||
const string errorText = "Error loading beatmap version."; | ||
standardLevelDetailViewController.ShowContent(StandardLevelDetailViewController.ContentType.Error, errorText); | ||
Logging.Logger.Error(errorText); | ||
handled = true; | ||
} | ||
|
||
break; | ||
} | ||
} | ||
|
||
if (!handled) | ||
{ | ||
standardLevelDetailViewController.ShowContent(StandardLevelDetailViewController.ContentType.Error, Localization.Get(StandardLevelDetailViewController.kLoadingDataErrorLocalizationKey)); | ||
} | ||
|
||
Logging.Logger.Error(ex); | ||
})) | ||
.InstructionEnumeration(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using System; | ||
using HarmonyLib; | ||
using IPA.Utilities; | ||
|
||
namespace SongCore.Utilities | ||
{ | ||
public static class CodeMatcherExtensions | ||
{ | ||
private static readonly FieldAccessor<CodeMatcher, string>.Accessor LastErrorAccessor = | ||
FieldAccessor<CodeMatcher, string>.GetAccessor("lastError"); | ||
|
||
/// <summary>Prints the list of instructions of this code matcher instance.</summary> | ||
/// <param name="codeMatcher">The code matcher instance.</param> | ||
/// <returns>The code matcher instance.</returns> | ||
public static CodeMatcher PrintInstructions(this CodeMatcher codeMatcher) | ||
{ | ||
var instructions = codeMatcher.Instructions(); | ||
for (var i = 0; i < instructions.Count; i++) | ||
{ | ||
Logging.Logger.Info($"\t {i} {instructions[i]}"); | ||
} | ||
|
||
return codeMatcher; | ||
} | ||
|
||
/// <summary>Throws an exception if current state is invalid (position out of bounds/last match failed).</summary> | ||
/// <param name="codeMatcher">The code matcher instance.</param> | ||
/// <param name="explanation">Optional explanation of where/why the exception was thrown that will be added to the exception message.</param> | ||
/// <exception cref="InvalidOperationException">Current state is invalid.</exception> | ||
/// <returns>The code matcher instance.</returns> | ||
public static CodeMatcher ThrowIfInvalid(this CodeMatcher codeMatcher, string? explanation = null) | ||
{ | ||
if (codeMatcher.IsInvalid) | ||
{ | ||
var lastError = LastErrorAccessor(ref codeMatcher); | ||
var errMsg = lastError; | ||
if (!string.IsNullOrWhiteSpace(explanation)) | ||
{ | ||
errMsg = $"{explanation} - Current state is invalid. Details: {lastError}"; | ||
} | ||
|
||
throw new InvalidOperationException(errMsg); | ||
} | ||
|
||
return codeMatcher; | ||
} | ||
} | ||
} |