Skip to content

Commit ea9f672

Browse files
kevin0216ptchen314
andauthored
[+] 新增舊框燈版 837-15070-02 重低音 LED / 頂板 LED / 中央 LED 的支持 (#100)
* [+] Adding support for 837-15070-02 Woofer LED, Roof LED and Center LED Co-authored-by: PT_Chen <[email protected]> * [F] Fixing not consistant module name * [F] Fixed missing command * [F] Optimized with reducing reflection fetching * refactor: Merging into SkipBoardNoCheck --------- Co-authored-by: PT_Chen <[email protected]>
1 parent d2ddf7e commit ea9f672

File tree

1 file changed

+258
-43
lines changed

1 file changed

+258
-43
lines changed
Lines changed: 258 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,290 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
3-
using AMDaemon;
4-
using AquaMai.Config.Attributes;
1+
using AquaMai.Config.Attributes;
52
using Comio.BD15070_4;
6-
using Mecha;
73
using HarmonyLib;
4+
using Mecha;
5+
using System.Collections.Generic;
6+
using System.Reflection;
87
using System.Reflection.Emit;
9-
using Manager;
108
using UnityEngine;
119

1210
namespace AquaMai.Mods.GameSystem;
1311

1412
[ConfigSection(
1513
name: "旧框灯板支持",
16-
en: "Skip BoardNo check to use the old cab light board 837-15070-02",
17-
zh: "跳过 BoardNo 检查以使用 837-15070-02(旧框灯板)")]
14+
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",
15+
zh: "跳过 BoardNo 检查以使用 837-15070-02(旧框灯板),并映射新框顶板 LED 至重低音扬声器 LED、顶板 LED 以及中央 LED")]
1816
public class SkipBoardNoCheck
1917
{
20-
[HarmonyTranspiler]
18+
private static readonly FieldInfo _controlField;
19+
private static readonly FieldInfo _boardField;
20+
private static readonly FieldInfo _ctrlField;
21+
private static readonly FieldInfo _ioCtrlField;
22+
private static readonly FieldInfo _setLedGs8BitCommandField;
23+
private static readonly FieldInfo _gsUpdateField;
24+
private static readonly MethodInfo _sendForceCommandMethod;
25+
2126
[HarmonyPatch(typeof(BoardCtrl15070_4), "_md_initBoard_GetBoardInfo")]
22-
static IEnumerable<CodeInstruction> Transpiler1(IEnumerable<CodeInstruction> instructions)
27+
public class BoardCtrl15070_4__md_initBoard_GetBoardInfo_Patch
2328
{
24-
var codes = new List<CodeInstruction>(instructions);
25-
bool patched = false;
26-
for (int i = 0; i < codes.Count; i++)
29+
[HarmonyTranspiler]
30+
static IEnumerable<CodeInstruction> Transpiler1(IEnumerable<CodeInstruction> instructions)
2731
{
28-
if (codes[i].opcode == OpCodes.Callvirt &&
29-
codes[i].operand != null &&
30-
codes[i].operand.ToString().Contains("IsEqual"))
32+
var codes = new List<CodeInstruction>(instructions);
33+
bool patched = false;
34+
for (int i = 0; i < codes.Count; i++)
3135
{
32-
codes[i] = new CodeInstruction(OpCodes.Pop);
33-
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
34-
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));
36+
if (codes[i].opcode == OpCodes.Callvirt &&
37+
codes[i].operand != null &&
38+
codes[i].operand.ToString().Contains("IsEqual"))
39+
{
40+
codes[i] = new CodeInstruction(OpCodes.Pop);
41+
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
42+
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));
3543

36-
patched = true;
37-
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 BoardCtrl15070_4._md_initBoard_GetBoardInfo 方法成功");
38-
break;
44+
patched = true;
45+
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 BoardCtrl15070_4._md_initBoard_GetBoardInfo 方法成功");
46+
break;
47+
}
3948
}
49+
if (!patched)
50+
{
51+
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:BoardCtrl15070_4._md_initBoard_GetBoardInfo");
52+
}
53+
return codes;
4054
}
41-
if (!patched)
55+
}
56+
57+
[HarmonyPatch(typeof(Bd15070_4Control), "IsNeedFirmUpdate")]
58+
public class Bd15070_4Control_IsNeedFirmUpdate_Patch
59+
{
60+
[HarmonyTranspiler]
61+
static IEnumerable<CodeInstruction> Transpiler2(IEnumerable<CodeInstruction> instructions)
4262
{
43-
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:BoardCtrl15070_4._md_initBoard_GetBoardInfo");
63+
var codes = new List<CodeInstruction>(instructions);
64+
bool patched = false;
65+
for (int i = 0; i < codes.Count; i++)
66+
{
67+
if (codes[i].opcode == OpCodes.Callvirt &&
68+
codes[i].operand != null &&
69+
codes[i].operand.ToString().Contains("IsEqual"))
70+
{
71+
codes[i] = new CodeInstruction(OpCodes.Pop);
72+
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
73+
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));
74+
75+
patched = true;
76+
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 Bd15070_4Control.IsNeedFirmUpdate 方法成功");
77+
break;
78+
}
79+
}
80+
if (!patched)
81+
{
82+
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:Bd15070_4Control.IsNeedFirmUpdate");
83+
}
84+
return codes;
4485
}
45-
return codes;
4686
}
4787

48-
[HarmonyTranspiler]
49-
[HarmonyPatch(typeof(Bd15070_4Control), "IsNeedFirmUpdate")]
50-
static IEnumerable<CodeInstruction> Transpiler2(IEnumerable<CodeInstruction> instructions)
88+
static SkipBoardNoCheck()
89+
{
90+
var ledIfType = typeof(Bd15070_4IF);
91+
_controlField = ledIfType.GetField("_control", BindingFlags.NonPublic | BindingFlags.Instance);
92+
_gsUpdateField = ledIfType.GetField("_gsUpdate", BindingFlags.NonPublic | BindingFlags.Instance);
93+
94+
var controlType = typeof(Mecha.Bd15070_4Control);
95+
_boardField = controlType.GetField("_board", BindingFlags.NonPublic | BindingFlags.Instance);
96+
97+
var boardType = typeof(Comio.BD15070_4.Board15070_4);
98+
_ctrlField = boardType.GetField("_ctrl", BindingFlags.NonPublic | BindingFlags.Instance);
99+
100+
var boardCtrlType = typeof(Comio.BD15070_4.BoardCtrl15070_4);
101+
_ioCtrlField = boardCtrlType.GetField("_ioCtrl", BindingFlags.NonPublic | BindingFlags.Instance);
102+
_sendForceCommandMethod = boardCtrlType.GetMethod("SendForceCommand", BindingFlags.Public | BindingFlags.Instance);
103+
104+
var ioCtrlType = typeof(IoCtrl);
105+
_setLedGs8BitCommandField = ioCtrlType.GetField("SetLedGs8BitCommand", BindingFlags.Public | BindingFlags.Instance);
106+
107+
if (_controlField == null)
108+
{
109+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Failed to cache _control field");
110+
}
111+
if (_setLedGs8BitCommandField == null)
112+
{
113+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Failed to cache SetLedGs8BitCommand field");
114+
}
115+
if (_sendForceCommandMethod == null)
116+
{
117+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Failed to cache SendForceCommand method");
118+
}
119+
}
120+
121+
[HarmonyPatch(typeof(Bd15070_4IF), "_construct")]
122+
public class Bd15070_4IF_Construct_Patch
123+
{
124+
[HarmonyTranspiler]
125+
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
126+
{
127+
var codes = new List<CodeInstruction>(instructions);
128+
int patchedNum = 0;
129+
for (int i = 0; i < codes.Count && patchedNum < 2; i++)
130+
{
131+
if (codes[i].opcode == OpCodes.Ldc_I4_8 &&
132+
codes[i].operand == null)
133+
{
134+
codes[i].opcode = OpCodes.Ldc_I4_S;
135+
codes[i].operand = (sbyte)10;
136+
patchedNum++;
137+
}
138+
}
139+
if (patchedNum == 2)
140+
{
141+
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] Extended Bd15070_4IF._switchParam size and its initialize for loop from 8 to 10!");
142+
}
143+
else
144+
{
145+
MelonLoader.MelonLogger.Warning($"[SkipBoardNoCheck] Bd15070_4IF._switchParam patching failed (patched {patchedNum}/2)");
146+
}
147+
return codes;
148+
}
149+
}
150+
151+
[HarmonyPatch]
152+
public class JvsOutputPwmPatch
153+
{
154+
[HarmonyTargetMethod]
155+
static MethodBase TargetMethod()
156+
{
157+
var type = typeof(IO.Jvs).GetNestedType("JvsOutputPwm", BindingFlags.NonPublic | BindingFlags.Instance);
158+
if (type == null)
159+
{
160+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] JvsOutputPwm type not found");
161+
return null;
162+
}
163+
return type.GetMethod("Set", new[] { typeof(byte), typeof(Color32), typeof(bool) });
164+
}
165+
166+
[HarmonyPrefix]
167+
public static bool Prefix(object __instance, byte index, Color32 color, bool update)
168+
{
169+
RedirectToButtonLedMechanism(index, color);
170+
171+
return false;
172+
}
173+
}
174+
175+
private static void RedirectToButtonLedMechanism(byte playerIndex, Color32 color)
51176
{
52-
var codes = new List<CodeInstruction>(instructions);
53-
bool patched = false;
54-
for (int i = 0; i < codes.Count; i++)
177+
// Check if MechaManager is initialized
178+
if (!IO.MechaManager.IsInitialized)
179+
{
180+
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] MechaManager not initialized, cannot set woofer LED");
181+
return;
182+
}
183+
184+
// Get the LED interface for the player
185+
var ledIf = IO.MechaManager.LedIf;
186+
if (ledIf == null || playerIndex >= ledIf.Length || ledIf[playerIndex] == null)
187+
{
188+
MelonLoader.MelonLogger.Warning($"[SkipBoardNoCheck] LED interface not available for player {playerIndex}");
189+
return;
190+
}
191+
192+
// Use cached reflection FieldInfo and MethodInfo to access the IoCtrl and call SetLedGs8BitCommand[8] & SetLedGs8BitCommand[9] directly
193+
// Then set _gsUpdate flag so PreExecute() sends the update command (just like buttons do)
194+
try
55195
{
56-
if (codes[i].opcode == OpCodes.Callvirt &&
57-
codes[i].operand != null &&
58-
codes[i].operand.ToString().Contains("IsEqual"))
196+
if (_controlField == null)
197+
{
198+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _control field not found in Bd15070_4IF");
199+
return;
200+
}
201+
202+
var control = _controlField.GetValue(ledIf[playerIndex]);
203+
if (control == null)
204+
{
205+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Control object is null");
206+
return;
207+
}
208+
209+
if (_boardField == null)
210+
{
211+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _board field not found in Bd15070_4Control");
212+
return;
213+
}
214+
215+
var board = _boardField.GetValue(control);
216+
if (board == null)
217+
{
218+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] Board object is null");
219+
return;
220+
}
221+
222+
if (_ctrlField == null)
59223
{
60-
codes[i] = new CodeInstruction(OpCodes.Pop);
61-
codes.Insert(i + 1, new CodeInstruction(OpCodes.Pop));
62-
codes.Insert(i + 2, new CodeInstruction(OpCodes.Ldc_I4_1));
224+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _ctrl field not found in Board15070_4");
225+
return;
226+
}
227+
228+
var boardCtrl = _ctrlField.GetValue(board);
229+
if (boardCtrl == null)
230+
{
231+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] BoardCtrl object is null");
232+
return;
233+
}
234+
235+
if (_ioCtrlField == null)
236+
{
237+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] _ioCtrl field not found in BoardCtrl15070_4");
238+
return;
239+
}
240+
241+
var ioCtrl = _ioCtrlField.GetValue(boardCtrl);
242+
if (ioCtrl == null)
243+
{
244+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] IoCtrl object is null");
245+
return;
246+
}
63247

64-
patched = true;
65-
MelonLoader.MelonLogger.Msg("[SkipBoardNoCheck] 修补 Bd15070_4Control.IsNeedFirmUpdate 方法成功");
66-
break;
248+
if (_setLedGs8BitCommandField == null)
249+
{
250+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] SetLedGs8BitCommand field not found in IoCtrl");
251+
return;
252+
}
253+
254+
var setLedGs8BitCommandArray = _setLedGs8BitCommandField.GetValue(ioCtrl) as SetLedGs8BitCommand[];
255+
if (setLedGs8BitCommandArray == null || setLedGs8BitCommandArray.Length <= 8)
256+
{
257+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] SetLedGs8BitCommand array is null or too small");
258+
return;
259+
}
260+
261+
if (_sendForceCommandMethod == null)
262+
{
263+
MelonLoader.MelonLogger.Error("[SkipBoardNoCheck] SendForceCommand method not found in BoardCtrl15070_4");
264+
return;
265+
}
266+
267+
// Use SetLedGs8BitCommand[8] and SetLedGs8BitCommand[9] directly (same as buttons 0-7, but for ledPos = 8 and 9)
268+
// This bypasses the FET command path in IoCtrl.SetLedData(), as they are not via FET, they are like buttons
269+
// ledPos = 8 == woofer & roof
270+
// ledPos = 9 == center
271+
setLedGs8BitCommandArray[8].setColor(8, color);
272+
setLedGs8BitCommandArray[9].setColor(9, color);
273+
_sendForceCommandMethod.Invoke(boardCtrl, new object[] { setLedGs8BitCommandArray[8] });
274+
_sendForceCommandMethod.Invoke(boardCtrl, new object[] { setLedGs8BitCommandArray[9] });
275+
276+
if (_gsUpdateField != null)
277+
{
278+
_gsUpdateField.SetValue(ledIf[playerIndex], true);
279+
}
280+
else
281+
{
282+
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] _gsUpdate field not found, LED may not update");
67283
}
68284
}
69-
if (!patched)
285+
catch (System.Exception ex)
70286
{
71-
MelonLoader.MelonLogger.Warning("[SkipBoardNoCheck] 未找到需要修补的代码位置:Bd15070_4Control.IsNeedFirmUpdate");
287+
MelonLoader.MelonLogger.Error($"[SkipBoardNoCheck] Failed to set woofer LED: {ex.Message}\n{ex.StackTrace}");
72288
}
73-
return codes;
74289
}
75290
}

0 commit comments

Comments
 (0)