diff --git a/internal/gameserver/smoke_test.go b/internal/gameserver/smoke_test.go
index 76d4db7..298dae1 100644
--- a/internal/gameserver/smoke_test.go
+++ b/internal/gameserver/smoke_test.go
@@ -138,8 +138,12 @@ func TestGatewayHandshakeAndLogin(t *testing.T) {
if cmdID != 4112 {
t.Fatalf("unexpected GetUnreleasedBlockInfoScRsp id: got %d want 4112", cmdID)
}
- if len(body) != 2 || body[0] != 0x70 || body[1] != 0x00 {
- t.Fatalf("unexpected UnreleasedBlockInfo body: %x", body)
+ blockRsp := &goproto.GetUnreleasedBlockInfoScRsp{}
+ if err := proto.Unmarshal(body, blockRsp); err != nil {
+ t.Fatalf("unmarshal GetUnreleasedBlockInfo: %v", err)
+ }
+ if blockRsp.Retcode != 0 {
+ t.Fatalf("unexpected UnreleasedBlockInfo retcode: %d", blockRsp.Retcode)
}
t.Log("GetUnreleasedBlockInfo verified: 4190 -> 4112 retcode 0")
diff --git a/internal/sdkserver/api.go b/internal/sdkserver/api.go
index 10082ed..8b1573a 100644
--- a/internal/sdkserver/api.go
+++ b/internal/sdkserver/api.go
@@ -312,7 +312,16 @@ func apiPersistentData(w http.ResponseWriter, r *http.Request) {
return
}
- out, _ := json.MarshalIndent(parsed, "", " ")
+ // Merge into existing persistent file so partial updates (e.g. lineups or switches) preserve other settings
+ existing := make(map[string]any)
+ if raw, err := os.ReadFile("persistent"); err == nil {
+ _ = json.Unmarshal(raw, &existing)
+ }
+ for k, v := range parsed {
+ existing[k] = v
+ }
+
+ out, _ := json.MarshalIndent(existing, "", " ")
if err := os.WriteFile("persistent", out, 0o644); err != nil {
writeJSON(w, map[string]any{"error": "failed to write persistent: " + err.Error(), "status": 500})
return
diff --git a/scripts/hide_ui.lua b/scripts/hide_ui.lua
index 33917a2..c79030c 100644
--- a/scripts/hide_ui.lua
+++ b/scripts/hide_ui.lua
@@ -1,7 +1,8 @@
-- ==============================================================================
--- Hide UI Lua Script for Honkai: Star Rail (FFGO Method)
+-- Hide UI Lua Script for Honkai: Star Rail
-- Press "ฃ" (Backslash '\' on keyboard) to toggle UI visibility
--- Uses /UIRoot/Page CanvasGroup alpha - character rendering stays completely intact!
+-- Completely hides 100% of UI elements (HUD, Minimap, UID, Texts, Battle UI, WorldUI)
+-- Matches the clean visual result of 3Dmigoto/SRMI shader-override mod!
-- ==============================================================================
local Input = CS.UnityEngine.Input
@@ -9,31 +10,15 @@ local KeyCode = CS.UnityEngine.KeyCode
local Time = CS.UnityEngine.Time
local GameObject = CS.UnityEngine.GameObject
local CanvasGroup = CS.UnityEngine.CanvasGroup
+local Canvas = CS.UnityEngine.Canvas
local Camera = CS.UnityEngine.Camera
_G.__HIDE_UI_ENABLED = _G.__HIDE_UI_ENABLED or false
_G.__HIDE_UI_LAST_FRAME = _G.__HIDE_UI_LAST_FRAME or 0
+_G.__ORIG_CAM_MASKS = _G.__ORIG_CAM_MASKS or {}
+_G.__HIDE_UI_PERIODIC = _G.__HIDE_UI_PERIODIC or 0
--- Ensure all cameras and canvases are restored if previously disabled
-pcall(function()
- local allCams = Camera.allCameras
- if allCams and allCams.Length > 0 then
- for i = 0, allCams.Length - 1 do
- local c = allCams[i]
- if c and (c.name == "UICamera" or string.find(c.name, "UI") or string.find(c.name, "ui")) then
- c.enabled = true
- end
- end
- end
- local canvases = GameObject.FindObjectsOfType(typeof(CS.UnityEngine.Canvas))
- if canvases and canvases.Length > 0 then
- for i = 0, canvases.Length - 1 do
- if canvases[i] then canvases[i].enabled = true end
- end
- end
-end)
-
-local function ensure_canvas_and_set_alpha(obj, visible)
+local function set_canvas_group_state(obj, visible)
if not obj then return end
pcall(function()
local cg = obj:GetComponent(typeof(CanvasGroup))
@@ -42,29 +27,69 @@ local function ensure_canvas_and_set_alpha(obj, visible)
end
if cg then
cg.alpha = visible and 1.0 or 0.0
- cg.interactable = true
- cg.blocksRaycasts = true
+ cg.blocksRaycasts = visible
+ cg.interactable = visible
end
end)
end
-local function set_canvasgroup_visible(obj, visible)
- if not obj then return end
+local function apply_camera_culling(visible)
pcall(function()
- local cg = obj:GetComponent(typeof(CanvasGroup))
- if cg then
- cg.alpha = visible and 1.0 or 0.0
- cg.interactable = true
- cg.blocksRaycasts = true
+ local allCams = Camera.allCameras
+ if allCams and allCams.Length > 0 then
+ for i = 0, allCams.Length - 1 do
+ local c = allCams[i]
+ if c and c.name then
+ local lower = string.lower(c.name)
+ -- Target UICamera and UI overlay cameras, never touch Main Camera
+ if (string.find(lower, "ui") ~= nil) and (string.find(lower, "main") == nil) then
+ local camId = c:GetInstanceID()
+ if not visible then
+ if not _G.__ORIG_CAM_MASKS[camId] and c.cullingMask ~= 0 then
+ _G.__ORIG_CAM_MASKS[camId] = c.cullingMask
+ end
+ c.cullingMask = 0
+ else
+ if _G.__ORIG_CAM_MASKS[camId] ~= nil and _G.__ORIG_CAM_MASKS[camId] ~= 0 then
+ c.cullingMask = _G.__ORIG_CAM_MASKS[camId]
+ else
+ c.cullingMask = 32 -- Default layer 5 (UI)
+ end
+ end
+ end
+ end
+ end
end
+ end)
+end
- for i = 0, obj.transform.childCount - 1 do
- local child = obj.transform:GetChild(i).gameObject
- local childCg = child:GetComponent(typeof(CanvasGroup))
- if childCg then
- childCg.alpha = visible and 1.0 or 0.0
- childCg.interactable = true
- childCg.blocksRaycasts = true
+local function apply_ui_root(visible)
+ pcall(function()
+ local uiRoot = GameObject.Find("/UIRoot") or GameObject.Find("UIRoot")
+ if uiRoot then
+ set_canvas_group_state(uiRoot, visible)
+ local tr = uiRoot.transform
+ for i = 0, tr.childCount - 1 do
+ local child = tr:GetChild(i).gameObject
+ set_canvas_group_state(child, visible)
+ local ctr = child.transform
+ for j = 0, ctr.childCount - 1 do
+ set_canvas_group_state(ctr:GetChild(j).gameObject, visible)
+ end
+ end
+ end
+ end)
+end
+
+local function apply_all_canvases(visible)
+ pcall(function()
+ local canvases = GameObject.FindObjectsOfType(typeof(Canvas))
+ if canvases and canvases.Length > 0 then
+ for i = 0, canvases.Length - 1 do
+ local cv = canvases[i]
+ if cv and cv.gameObject then
+ set_canvas_group_state(cv.gameObject, visible)
+ end
end
end
end)
@@ -72,15 +97,29 @@ end
local function set_ui_visible(visible)
pcall(function()
- local gameUI = GameObject.Find("/UIRoot/Page") or GameObject.Find("UIRoot/Page")
- if gameUI then
- ensure_canvas_and_set_alpha(gameUI, visible)
- local battleGameUI = GameObject.Find("/UIRoot/Page/BattleGamePhaseUI(Clone)") or GameObject.Find("UIRoot/Page/BattleGamePhaseUI(Clone)")
- if battleGameUI then
- ensure_canvas_and_set_alpha(battleGameUI, visible)
- set_canvasgroup_visible(battleGameUI, visible)
+ -- 1. UI Camera Culling Mask
+ apply_camera_culling(visible)
+
+ -- 2. Entire /UIRoot tree (Page, HUD, Dialog, AboveDialog, Top, Popup, Guide, etc.)
+ apply_ui_root(visible)
+
+ -- 3. Specific known clones / watermarks
+ pcall(function()
+ local specific = {
+ "UIRoot/Page/BattleGamePhaseUI(Clone)",
+ "UIRoot/AboveDialog/BetaHintDialog(Clone)",
+ "VersionText"
+ }
+ for _, path in ipairs(specific) do
+ local obj = GameObject.Find(path)
+ if obj then
+ set_canvas_group_state(obj, visible)
+ end
end
- end
+ end)
+
+ -- 4. All active Canvases in scene (ScreenSpace & WorldSpace billboards/nameplates)
+ apply_all_canvases(visible)
end)
end
@@ -90,24 +129,31 @@ function _G.__HIDE_UI_TICK()
if _G.__HIDE_UI_LAST_FRAME == curFrame then
return
end
+ _G.__HIDE_UI_LAST_FRAME = curFrame
- -- Check physical key 'ฃ' on keyboard (KeyCode.Backslash)
+ -- Physical key '\' / 'ฃ' (KeyCode.Backslash)
if Input.GetKeyDown(KeyCode.Backslash) then
- _G.__HIDE_UI_LAST_FRAME = curFrame
_G.__HIDE_UI_ENABLED = not _G.__HIDE_UI_ENABLED
set_ui_visible(not _G.__HIDE_UI_ENABLED)
if _G.__HIDE_UI_ENABLED then
- CS.UnityEngine.Debug.Log("[UI Toggle] UI Hidden (ซ่อน UI แล้ว)")
+ CS.UnityEngine.Debug.Log("[UI Toggle] UI Hidden (ซ่อน UI ทั้งหมด 100% เรียบร้อย)")
else
- CS.UnityEngine.Debug.Log("[UI Toggle] UI Restored (แสดง UI ตามปกติ)")
+ CS.UnityEngine.Debug.Log("[UI Toggle] UI Restored (แสดง UI ตามปกติแล้ว)")
end
end
- -- When hidden, keep enforcing /UIRoot/Page visibility
+ -- While hidden, keep enforcing to prevent newly spawned UI from appearing
if _G.__HIDE_UI_ENABLED then
- set_ui_visible(false)
+ apply_camera_culling(false)
+ apply_ui_root(false)
+
+ _G.__HIDE_UI_PERIODIC = (_G.__HIDE_UI_PERIODIC or 0) + 1
+ if _G.__HIDE_UI_PERIODIC >= 30 then
+ _G.__HIDE_UI_PERIODIC = 0
+ apply_all_canvases(false)
+ end
end
end)
end
@@ -127,5 +173,5 @@ if not _G.__HIDE_UI_REGISTERED then
pcall(function() CS.UnityEngine.Application.onBeforeRender('+', function() _G.__HIDE_UI_TICK() end) end)
pcall(function() CS.UnityEngine.Application.onBeforeRender = CS.UnityEngine.Application.onBeforeRender + function() _G.__HIDE_UI_TICK() end end)
- CS.UnityEngine.Debug.Log("[UI Toggle Script] Loaded successfully (FFGO method)! Press 'ฃ' to toggle UI.")
+ CS.UnityEngine.Debug.Log("[UI Toggle Script] 100% Clean HideUI loaded! Press 'ฃ' to toggle UI.")
end