Skip to content

Commit eef8a67

Browse files
authored
feat: Display error frame when specific hardware is not available. (#51)
* test: complete all essentials for custom hardware display * feat: complete all types of hardware error checks * fix: add multiple source for TouchSensor check to avoid occational AMDaemon Error missing issue * feat: refactored error frame sections into a global helper tool * fix: resolve potential weakness * feat: add an localization string * fix: camera idx fetch issue
1 parent 2a09d26 commit eef8a67

File tree

4 files changed

+294
-0
lines changed

4 files changed

+294
-0
lines changed

AquaMai.Core/Helpers/ErrorFrame.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using HarmonyLib;
3+
using Manager;
4+
using MelonLoader;
5+
using Monitor.Error;
6+
using Process;
7+
using Process.Error;
8+
using TMPro;
9+
10+
namespace AquaMai.Core.Helpers;
11+
12+
public static class ErrorFrame
13+
{
14+
private static int _customErrCode;
15+
private static string _customErrMsg;
16+
private static DateTime _customErrDate;
17+
18+
public static void Show(ProcessBase process, int errCode, string errMsg)
19+
{
20+
_customErrCode = errCode;
21+
_customErrMsg = errMsg;
22+
_customErrDate = DateTime.Now;
23+
Show(process);
24+
}
25+
26+
// Display the error frame with AMDaemon's original error message.
27+
public static void Show(ProcessBase process)
28+
{
29+
var tv = Traverse.Create(process);
30+
var ctn = tv.Field("container").GetValue<ProcessDataContainer>();
31+
ctn.processManager.AddProcess((ProcessBase) new ErrorProcess(ctn));
32+
ctn.processManager.ReleaseProcess(process);
33+
GameManager.IsErrorMode = true;
34+
}
35+
36+
// patch the error monitor so that it can display custom error codes and messages.
37+
[HarmonyPostfix]
38+
[HarmonyPatch(typeof(ErrorMonitor), "Initialize", typeof(int), typeof(bool))]
39+
public static void PostInitialize(ErrorMonitor __instance)
40+
{
41+
var tv = Traverse.Create(__instance);
42+
if (_customErrCode == 0)
43+
{
44+
MelonLogger.Msg($"Displaying error frame with AMDaemon code {AMDaemon.Error.Number}: {AMDaemon.Error.Message}");
45+
return;
46+
}
47+
MelonLogger.Msg($"Displaying error frame with custom code {_customErrCode}: {_customErrMsg}");
48+
tv.Field("ErrorID").GetValue<TextMeshProUGUI>().text = _customErrCode.ToString().PadLeft(4, '0');
49+
tv.Field("ErrorMessage").GetValue<TextMeshProUGUI>().text = _customErrMsg;
50+
tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString();
51+
}
52+
53+
}

AquaMai.Core/Startup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ public static void Initialize(Assembly modsAssembly, HarmonyLib.Harmony harmony)
164164
CollectWantedPatches(wantedPatches, typeof(KeyListener));
165165
CollectWantedPatches(wantedPatches, typeof(Shim));
166166
CollectWantedPatches(wantedPatches, typeof(NetPacketHook));
167+
CollectWantedPatches(wantedPatches, typeof(ErrorFrame));
167168
// 使用时才 patch!不要添加这个
168169
// CollectWantedPatches(wantedPatches, typeof(GameSettingsManager));
169170

AquaMai.Mods/UX/HardwareAlert.cs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Globalization;
5+
using AquaMai.Config.Attributes;
6+
using AquaMai.Core.Helpers;
7+
using AquaMai.Core.Resources;
8+
using AquaMai.Mods.GameSystem;
9+
using HarmonyLib;
10+
using IO;
11+
using MAI2.Util;
12+
using MAI2System;
13+
using Main;
14+
using Manager;
15+
using MelonLoader;
16+
using Monitor.Error;
17+
using Process;
18+
using Process.Error;
19+
using TMPro;
20+
using UnityEngine;
21+
22+
namespace AquaMai.Mods.UX;
23+
24+
[ConfigSection(
25+
en: "Custom hardware alert, you can configure to display an error frame upon hardware failure. Toggle the switches below to define the required hardware.",
26+
zh: "自定义硬件警告,可配置在指定硬件自检失败时阻止游戏启动并显示报错画面,开启下方的错误类型以配置需要关注的错误。")]
27+
public class HardwareAlert
28+
{
29+
[ConfigEntry(
30+
en: "If enabled, all the in-game hardware warnings will be displayed in game's original language, like Japanese for SDEZ. If you have used any translation pack, you should disable this setting.",
31+
zh: "如果启用,所有硬件警告将会使用游戏原本的语言显示,例如 SDEZ 就会用日文显示报错。如果你安装了任何汉化包,你应该关闭这个选项。")]
32+
private static readonly bool UseOriginalGameLanguage = true;
33+
[ConfigEntry(
34+
en: "1P Touch Sensor",
35+
zh: "1P 触摸屏")]
36+
private static readonly bool TouchSensor_1P = false; // Error 3300, 3301
37+
[ConfigEntry(
38+
en: "2P Touch Sensor",
39+
zh: "2P 触摸屏")]
40+
private static readonly bool TouchSensor_2P = false; // Error 3302, 3303
41+
[ConfigEntry(
42+
en: "1P LED",
43+
zh: "1P LED")]
44+
private static readonly bool LED_1P = false; // custom 3400
45+
[ConfigEntry(
46+
en: "2P LED",
47+
zh: "2P LED")]
48+
private static readonly bool LED_2P = false; // custom 3401
49+
[ConfigEntry(
50+
en: "Player Camera",
51+
zh: "玩家摄像机")]
52+
private static readonly bool PlayerCamera = false; // 3102
53+
[ConfigEntry(
54+
en: "DX Pass 1P",
55+
zh: "DX Pass 1P")]
56+
private static readonly bool CodeReader_1P = false; // 3101
57+
[ConfigEntry(
58+
en: "DX Pass 2P",
59+
zh: "DX Pass 2P")]
60+
private static readonly bool CodeReader_2P = false; // 3101
61+
[ConfigEntry(
62+
en: "WeChat QRCode Camera",
63+
zh: "二维码扫描摄像头")]
64+
public static readonly bool ChimeCamera = false; // 3100
65+
66+
private static readonly List<string> CameraTypeList = ["QRLeft", "QRRight", "Photo", "Chime"];
67+
private static SortedDictionary<CameraTypeEnumInner, int> _cameraIndex = [];
68+
private static bool _isInitialized = false;
69+
70+
private enum CameraTypeEnumInner
71+
{
72+
QRLeft,
73+
QRRight,
74+
Photo,
75+
Chime,
76+
}
77+
78+
[HarmonyPostfix]
79+
[HarmonyPatch(typeof(CameraManager), "CameraInitialize")]
80+
public static void PostCameraInitialize(CameraManager __instance)
81+
{
82+
if (_isInitialized)
83+
{
84+
return;
85+
}
86+
87+
var curCamIdx = 0;
88+
foreach (var cameraTypeName in CameraTypeList)
89+
{
90+
if (Enum.TryParse<CameraTypeEnumInner>(cameraTypeName, out var cameraType))
91+
{
92+
MelonLogger.Msg($"[HardwareAlert] Identified camera type {cameraType} for current game version on idx {curCamIdx}");
93+
_cameraIndex[cameraType] = curCamIdx;
94+
curCamIdx++;
95+
}
96+
}
97+
98+
_isInitialized = true;
99+
}
100+
101+
[HarmonyPostfix]
102+
[HarmonyPatch(typeof(StartupProcess), "OnUpdate")]
103+
public static void OnPostUpdate(StartupProcess __instance)
104+
{
105+
// get current startup state
106+
var tv = Traverse.Create(__instance);
107+
// var state = tv.Field("_state").GetValue<byte>();
108+
var statusSubMsg = tv.Field("_statusSubMsg").GetValue<string[]>();
109+
110+
// Touch sensor check
111+
// The built-in AMDaemon errors are not stable, and cannot be localized.
112+
// So we decided to use another approach to check it.
113+
if (TouchSensor_1P && statusSubMsg[0] == ConstParameter.TestString_Bad)
114+
{
115+
ErrorFrame.Show(__instance, 3300, FaultTouchSensor1P[GetLocale()]);
116+
return;
117+
}
118+
if (TouchSensor_2P && statusSubMsg[1] == ConstParameter.TestString_Bad)
119+
{
120+
ErrorFrame.Show(__instance, 3302, FaultTouchSensor2P[GetLocale()]);
121+
return;
122+
}
123+
124+
// LED check
125+
if (LED_1P && statusSubMsg[2] == ConstParameter.TestString_Bad)
126+
{
127+
ErrorFrame.Show(__instance, 3400, FaultLED1P[GetLocale()]);
128+
return;
129+
}
130+
if (LED_2P && statusSubMsg[3] == ConstParameter.TestString_Bad)
131+
{
132+
ErrorFrame.Show(__instance, 3401, FaultLED2P[GetLocale()]);
133+
return;
134+
}
135+
136+
// Camera Check
137+
if (CameraManager.IsReady)
138+
{
139+
var nCam = CameraManager.IsAvailableCameras.Length;
140+
141+
var pcIdx = _cameraIndex[CameraTypeEnumInner.Photo];
142+
if (PlayerCamera && pcIdx < nCam && !CameraManager.IsAvailableCameras[pcIdx])
143+
{
144+
ErrorFrame.Show(__instance, 3102, FaultPlayerCamera[GetLocale()]);
145+
return;
146+
}
147+
148+
var cr1PIdx = _cameraIndex[CameraTypeEnumInner.QRLeft];
149+
if (CodeReader_1P && cr1PIdx < nCam && !CameraManager.IsAvailableCameras[cr1PIdx])
150+
{
151+
ErrorFrame.Show(__instance, 3101, FaultQR1P[GetLocale()]);
152+
return;
153+
}
154+
155+
var cr2PIdx = _cameraIndex[CameraTypeEnumInner.QRRight];
156+
if (CodeReader_2P && cr2PIdx < nCam && !CameraManager.IsAvailableCameras[cr2PIdx])
157+
{
158+
ErrorFrame.Show(__instance, 3101, FaultQR2P[GetLocale()]);
159+
return;
160+
}
161+
162+
var chimeIdx = _cameraIndex[CameraTypeEnumInner.Chime];
163+
if (ChimeCamera && chimeIdx < nCam && !CameraManager.IsAvailableCameras[chimeIdx])
164+
{
165+
ErrorFrame.Show(__instance, 3100, FaultChime[GetLocale()]);
166+
return;
167+
}
168+
}
169+
}
170+
171+
private static string GetLocale()
172+
{
173+
if (UseOriginalGameLanguage)
174+
{
175+
return GameVersionToLocale[GameInfo.GameId];
176+
}
177+
MelonLogger.Msg($"[HardwareAlert] Using locale '{Locale.Culture.TwoLetterISOLanguageName}'");
178+
// MelonLogger.Msg($"[HardwareAlert] Using locale '{Locale.Culture.Name}'");
179+
return Locale.Culture.TwoLetterISOLanguageName.Equals("zh",
180+
StringComparison.OrdinalIgnoreCase)
181+
? "zh"
182+
: "en";
183+
}
184+
185+
private static readonly Dictionary<string, string> GameVersionToLocale = new()
186+
{
187+
["SDEZ"] = "jp",
188+
["SDGA"] = "en",
189+
["SDGB"] = "zh",
190+
};
191+
private static readonly Dictionary<string, string> FaultTouchSensor1P = new()
192+
{
193+
["jp"] = "タッチセンサ(1P)はご利用いただけません",
194+
["en"] = "Touch Sensor (1P) not available",
195+
["zh"] = "触摸传感器(1P)不可用",
196+
};
197+
private static readonly Dictionary<string, string> FaultTouchSensor2P = new()
198+
{
199+
["jp"] = "タッチセンサ(2P)はご利用いただけません",
200+
["en"] = "Touch Sensor (2P) not available",
201+
["zh"] = "触摸传感器(2P)不可用",
202+
};
203+
private static readonly Dictionary<string, string> FaultLED1P = new()
204+
{
205+
["jp"] = "LED(1P)はご利用いただけません",
206+
["en"] = "LED(1P) not available",
207+
["zh"] = "LED(1P)不可用",
208+
};
209+
private static readonly Dictionary<string, string> FaultLED2P = new()
210+
{
211+
["jp"] = "LED(2P)はご利用いただけません",
212+
["en"] = "LED(2P) not available",
213+
["zh"] = "LED(2P)不可用",
214+
};
215+
private static readonly Dictionary<string, string> FaultQR1P = new()
216+
{
217+
["jp"] = "コードリーダー(1P)はご利用いただけません",
218+
["en"] = "Code Reader (1P) not available",
219+
["zh"] = "DX Pass 二维码相机(1P)不可用", // This thing does not exist...
220+
};
221+
private static readonly Dictionary<string, string> FaultQR2P = new()
222+
{
223+
["jp"] = "コードリーダー(2P)はご利用いただけません",
224+
["en"] = "Code Reader (2P) not available",
225+
["zh"] = "DX Pass 二维码相机(2P)不可用", // This thing does not exist...
226+
};
227+
private static readonly Dictionary<string, string> FaultPlayerCamera = new()
228+
{
229+
["jp"] = "プレイヤーカメラはご利用いただけません",
230+
["en"] = "Player Camera not available",
231+
["zh"] = "玩家相机不可用",
232+
};
233+
private static readonly Dictionary<string, string> FaultChime = new()
234+
{
235+
["jp"] = "コードリーダーは(Chime)ご利用いただけません", // This thing does not exist...
236+
["en"] = "Code Reader (Chime) not available", // This thing does not exist...
237+
["zh"] = "二维码相机不可用",
238+
};
239+
}

AquaMai/configSort.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
- Utils.LogNetworkRequests
7676
- Utils.ShowErrorLog
7777
- UX.NoAmDaemonAlert
78+
- UX.HardwareAlert
7879

7980
键位和灵敏度:
8081
- GameSystem.HidInput

0 commit comments

Comments
 (0)