Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
301 changes: 258 additions & 43 deletions AquaMai.Mods/GameSystem/SkipBoardNoCheck.cs
Original file line number Diff line number Diff line change
@@ -1,75 +1,290 @@
using System.Collections.Generic;
using System.Linq;
using AMDaemon;
using AquaMai.Config.Attributes;
using AquaMai.Config.Attributes;
using Comio.BD15070_4;
using Mecha;
using HarmonyLib;
using Mecha;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Manager;
using UnityEngine;

namespace AquaMai.Mods.GameSystem;

[ConfigSection(
name: "旧框灯板支持",
en: "Skip BoardNo check to use the old cab light board 837-15070-02",
zh: "跳过 BoardNo 检查以使用 837-15070-02(旧框灯板)")]
en: "Skip BoardNo check to use the old cab light board 837-15070-02, and remapping the Billboard LED to woofer LED, roof LED and center LED",
zh: "跳过 BoardNo 检查以使用 837-15070-02(旧框灯板),并映射新框顶板 LED 至重低音扬声器 LED、顶板 LED 以及中央 LED")]
public class SkipBoardNoCheck
{
[HarmonyTranspiler]
private static readonly FieldInfo _controlField;
private static readonly FieldInfo _boardField;
private static readonly FieldInfo _ctrlField;
private static readonly FieldInfo _ioCtrlField;
private static readonly FieldInfo _setLedGs8BitCommandField;
private static readonly FieldInfo _gsUpdateField;
private static readonly MethodInfo _sendForceCommandMethod;

[HarmonyPatch(typeof(BoardCtrl15070_4), "_md_initBoard_GetBoardInfo")]
static IEnumerable<CodeInstruction> Transpiler1(IEnumerable<CodeInstruction> instructions)
public class BoardCtrl15070_4__md_initBoard_GetBoardInfo_Patch
{
var codes = new List<CodeInstruction>(instructions);
bool patched = false;
for (int i = 0; i < codes.Count; i++)
[HarmonyTranspiler]
static IEnumerable<CodeInstruction> Transpiler1(IEnumerable<CodeInstruction> instructions)
{
if (codes[i].opcode == OpCodes.Callvirt &&
codes[i].operand != null &&
codes[i].operand.ToString().Contains("IsEqual"))
var codes = new List<CodeInstruction>(instructions);
bool patched = false;
for (int i = 0; i < codes.Count; i++)
{
codes[i] = new CodeInstruction(OpCodes.Pop);
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));
if (codes[i].opcode == OpCodes.Callvirt &&
codes[i].operand != null &&
codes[i].operand.ToString().Contains("IsEqual"))
{
codes[i] = new CodeInstruction(OpCodes.Pop);
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));

patched = true;
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 BoardCtrl15070_4._md_initBoard_GetBoardInfo 方法成功");
break;
patched = true;
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 BoardCtrl15070_4._md_initBoard_GetBoardInfo 方法成功");
break;
}
}
if (!patched)
{
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:BoardCtrl15070_4._md_initBoard_GetBoardInfo");
}
return codes;
}
if (!patched)
}

[HarmonyPatch(typeof(Bd15070_4Control), "IsNeedFirmUpdate")]
public class Bd15070_4Control_IsNeedFirmUpdate_Patch
{
[HarmonyTranspiler]
static IEnumerable<CodeInstruction> Transpiler2(IEnumerable<CodeInstruction> instructions)
{
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:BoardCtrl15070_4._md_initBoard_GetBoardInfo");
var codes = new List<CodeInstruction>(instructions);
bool patched = false;
for (int i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == OpCodes.Callvirt &&
codes[i].operand != null &&
codes[i].operand.ToString().Contains("IsEqual"))
{
codes[i] = new CodeInstruction(OpCodes.Pop);
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));

patched = true;
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 Bd15070_4Control.IsNeedFirmUpdate 方法成功");
break;
}
}
if (!patched)
{
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:Bd15070_4Control.IsNeedFirmUpdate");
}
return codes;
}
return codes;
}

[HarmonyTranspiler]
[HarmonyPatch(typeof(Bd15070_4Control), "IsNeedFirmUpdate")]
static IEnumerable<CodeInstruction> Transpiler2(IEnumerable<CodeInstruction> instructions)
static SkipBoardNoCheck()
{
var ledIfType = typeof(Bd15070_4IF);
_controlField = ledIfType.GetField("_control", BindingFlags.NonPublic | BindingFlags.Instance);
_gsUpdateField = ledIfType.GetField("_gsUpdate", BindingFlags.NonPublic | BindingFlags.Instance);

var controlType = typeof(Mecha.Bd15070_4Control);
_boardField = controlType.GetField("_board", BindingFlags.NonPublic | BindingFlags.Instance);

var boardType = typeof(Comio.BD15070_4.Board15070_4);
_ctrlField = boardType.GetField("_ctrl", BindingFlags.NonPublic | BindingFlags.Instance);

var boardCtrlType = typeof(Comio.BD15070_4.BoardCtrl15070_4);
_ioCtrlField = boardCtrlType.GetField("_ioCtrl", BindingFlags.NonPublic | BindingFlags.Instance);
_sendForceCommandMethod = boardCtrlType.GetMethod("SendForceCommand", BindingFlags.Public | BindingFlags.Instance);

var ioCtrlType = typeof(IoCtrl);
_setLedGs8BitCommandField = ioCtrlType.GetField("SetLedGs8BitCommand", BindingFlags.Public | BindingFlags.Instance);

if (_controlField == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Failed to cache _control field");
}
if (_setLedGs8BitCommandField == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Failed to cache SetLedGs8BitCommand field");
}
if (_sendForceCommandMethod == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Failed to cache SendForceCommand method");
}
}

[HarmonyPatch(typeof(Bd15070_4IF), "_construct")]
public class Bd15070_4IF_Construct_Patch
{
[HarmonyTranspiler]
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
var codes = new List<CodeInstruction>(instructions);
int patchedNum = 0;
for (int i = 0; i < codes.Count && patchedNum < 2; i++)
{
if (codes[i].opcode == OpCodes.Ldc_I4_8 &&
codes[i].operand == null)
{
codes[i].opcode = OpCodes.Ldc_I4_S;
codes[i].operand = (sbyte)10;
patchedNum++;
}
}
if (patchedNum == 2)
{
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] Extended Bd15070_4IF._switchParam size and its initialize for loop from 8 to 10!");
}
else
{
MelonLoader.MelonLogger.Warning($"[SkipBoardNoCheck] Bd15070_4IF._switchParam patching failed (patched {patchedNum}/2)");
}
return codes;
}
}

[HarmonyPatch]
public class JvsOutputPwmPatch
{
[HarmonyTargetMethod]
static MethodBase TargetMethod()
{
var type = typeof(IO.Jvs).GetNestedType("JvsOutputPwm", BindingFlags.NonPublic | BindingFlags.Instance);
if (type == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] JvsOutputPwm type not found");
return null;
}
return type.GetMethod("Set", new[] { typeof(byte), typeof(Color32), typeof(bool) });
}

[HarmonyPrefix]
public static bool Prefix(object __instance, byte index, Color32 color, bool update)
{
RedirectToButtonLedMechanism(index, color);

return false;
}
}

private static void RedirectToButtonLedMechanism(byte playerIndex, Color32 color)
{
var codes = new List<CodeInstruction>(instructions);
bool patched = false;
for (int i = 0; i < codes.Count; i++)
// Check if MechaManager is initialized
if (!IO.MechaManager.IsInitialized)
{
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] MechaManager not initialized, cannot set woofer LED");
return;
}

// Get the LED interface for the player
var ledIf = IO.MechaManager.LedIf;
if (ledIf == null || playerIndex >= ledIf.Length || ledIf[playerIndex] == null)
{
MelonLoader.MelonLogger.Warning($"[SkipBoardNoCheck] LED interface not available for player {playerIndex}");
return;
}

// Use cached reflection FieldInfo and MethodInfo to access the IoCtrl and call SetLedGs8BitCommand[8] & SetLedGs8BitCommand[9] directly
// Then set _gsUpdate flag so PreExecute() sends the update command (just like buttons do)
try
{
if (codes[i].opcode == OpCodes.Callvirt &&
codes[i].operand != null &&
codes[i].operand.ToString().Contains("IsEqual"))
if (_controlField == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _control field not found in Bd15070_4IF");
return;
}

var control = _controlField.GetValue(ledIf[playerIndex]);
if (control == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Control object is null");
return;
}

if (_boardField == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _board field not found in Bd15070_4Control");
return;
}

var board = _boardField.GetValue(control);
if (board == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Board object is null");
return;
}

if (_ctrlField == null)
{
codes[i] = new CodeInstruction(OpCodes.Pop);
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _ctrl field not found in Board15070_4");
return;
}

var boardCtrl = _ctrlField.GetValue(board);
if (boardCtrl == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] BoardCtrl object is null");
return;
}

if (_ioCtrlField == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _ioCtrl field not found in BoardCtrl15070_4");
return;
}

var ioCtrl = _ioCtrlField.GetValue(boardCtrl);
if (ioCtrl == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] IoCtrl object is null");
return;
}

patched = true;
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 Bd15070_4Control.IsNeedFirmUpdate 方法成功");
break;
if (_setLedGs8BitCommandField == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] SetLedGs8BitCommand field not found in IoCtrl");
return;
}

var setLedGs8BitCommandArray = _setLedGs8BitCommandField.GetValue(ioCtrl) as SetLedGs8BitCommand[];
if (setLedGs8BitCommandArray == null || setLedGs8BitCommandArray.Length <= 8)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] SetLedGs8BitCommand array is null or too small");
return;
}

if (_sendForceCommandMethod == null)
{
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] SendForceCommand method not found in BoardCtrl15070_4");
return;
}

// Use SetLedGs8BitCommand[8] and SetLedGs8BitCommand[9] directly (same as buttons 0-7, but for ledPos = 8 and 9)
// This bypasses the FET command path in IoCtrl.SetLedData(), as they are not via FET, they are like buttons
// ledPos = 8 == woofer & roof
// ledPos = 9 == center
setLedGs8BitCommandArray[8].setColor(8, color);
setLedGs8BitCommandArray[9].setColor(9, color);
_sendForceCommandMethod.Invoke(boardCtrl, new object[] { setLedGs8BitCommandArray[8] });
_sendForceCommandMethod.Invoke(boardCtrl, new object[] { setLedGs8BitCommandArray[9] });

if (_gsUpdateField != null)
{
_gsUpdateField.SetValue(ledIf[playerIndex], true);
}
else
{
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] _gsUpdate field not found, LED may not update");
}
}
if (!patched)
catch (System.Exception ex)
{
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:Bd15070_4Control.IsNeedFirmUpdate");
MelonLoader.MelonLogger.Error($"[SkipBoardNoCheck] Failed to set woofer LED: {ex.Message}\n{ex.StackTrace}");
}
return codes;
}
}