1- using System . Collections . Generic ;
2- using System . Linq ;
3- using AMDaemon ;
4- using AquaMai . Config . Attributes ;
1+ using AquaMai . Config . Attributes ;
52using Comio . BD15070_4 ;
6- using Mecha ;
73using HarmonyLib ;
4+ using Mecha ;
5+ using System . Collections . Generic ;
6+ using System . Reflection ;
87using System . Reflection . Emit ;
9- using Manager ;
108using UnityEngine ;
119
1210namespace 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 " ) ]
1816public 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