遊戲年代 GOAL GOAL

 找回密碼
 立即註冊
搜索
查看: 1244|回復: 28

[PC版模擬器] [PCE模擬器] Geargrafx 1.7.14 繁/簡/英文版

[複製鏈接]

20

主題

192

帖子

3807

積分

銀牌會員

Rank: 5Rank: 5

積分
3807
發表於 2026-7-3 14:48:56 | 顯示全部樓層 |閱讀模式
本帖最後由 good7 於 2026-7-8 10:31 編輯

Geargrafx 1.7.14 繁/簡/英文版

這支程式也可以使用 Reshade 著色器,以後不用怕著色器(效果)太少了。
這支程式是用 Open GL 渲染器,選擇的時候要注意一下。

sshot-10.png

sshot-11.png


這個正式版有三個功能改變,

第一個在主選單的「除錯」選項,這個一般人很少用到,所以有了選擇可以把它隱藏起來了。
第二、三個功能就是「啟動時載入上次的遊戲」,和「啟用程式睡眠」功能。這兩個功能跟 supersnes9x 的功能一樣,不用多說了。


如果在執行檔所在資料夾放上空檔的 portable.ini 文字檔案,則變成 portable 版本(免安裝的綠色版本),所有的操作都會放在程式檔所在資料夾下,如果沒有的話,
就是會放在

C:\Users\你的使用者名稱\AppData\Roaming\Geardome\Geargrafx

sshot-6.png

sshot-8.png

下載:
https://u.pcloud.link/publink/sh ... Auoec4HWfcSyjK80TWy


以下是動過的記錄(應該都有記錄了)
================================================================================
  Geargrafx 問題點與修復記錄(整理後)
  對象專案:z:\g1714(Geargrafx 模擬器 v1.7.14)
  整理日期:2026-07-06
================================================================================

本檔案分兩大部分:
  第一部分:效能最佳化與模擬正確性 Bug 修復(迭代 1-10,2026-07-05)
  第二部分:穩定性/安全性修復(2026-07-06)


================================================================================
                      第一部分
  效能最佳化與模擬正確性 Bug 修復(迭代 1-10)
================================================================================

所有最佳化均嚴守「可數學證明位元等價」原則,不動 SaveState 格式、
ReadVRAM 副作用、latch 時序、SSE2/AVX2 解碼本體。Bug 修復均對照
Mednafen 參考實作確認。

所有 bug 候選經多次迭代驗證,最終判定如下:
  已修(對齊 Mednafen):Timer 0x7F、blur latch、ApplyLowPassFilter
                         round/clamp、MED-2、MED-3
  已撤除(非 bug)      :LENR underflow(回滾)、HIGH-2、HW-2、HW-3、
                         HW-4、MED-1、MED-4、MED-5、MED-6、m_hsync 命名

重要教訓:第 6 迭代曾將 LENR underflow 誤判為 bug 修成 0x0000,經
Mednafen 對照確認 DMA 完成時 LENR 就是 0xFFFF(u16 underflow 作完成
偵測)。結論:判斷疑似 bug 必須對照參考實作,不能只憑直覺。


================================================================================
第一部分 §1  效能最佳化(A 類,6 項全完成)
================================================================================

------------------------------------------------------------------------
[1] RenderBackground() — 像素提取 LUT 最佳化
------------------------------------------------------------------------
檔案:huc6270.cpp RenderBackground(第 699 行起)

問題(熱點排行 #1):
  內層迴圈每 pixel 執行 4 次 variable-SHR(無法常數折疊),同一個 tile
  內的 byte1~byte4 固定不變卻被重複解算 8 次。每 pixel 約 15 ops。

對策:像素 LUT 預解
  在 tile 邊界(if tile_col != prev_tile_col)內,預先解算 8 個像素值
  存入 u8 pixels[8]:
    pixels[bit] = ((byte1 >> (7-bit)) & 1) | (((byte2 >> (7-bit)) & 1) << 1)
                | (((byte3 >> (7-bit)) & 1) << 2) | (((byte4 >> (7-bit)) & 1) << 3);
  內層迴圈改為:m_line_buffer = color_shifted | pixels[bg_x & 7];

等價性驗證:
  令 k = bg_x & 7,舊 tile_x = 7 - k,pixel = pixels[k] = pixels[bg_x&7],
  代入後與舊算式位元級恆等。

效果:內層迴圈從約 15 ops/pixel 降為約 3 ops/pixel,變數移位完全消除。


------------------------------------------------------------------------
[2] RenderBackground() — 像素預解 SSE2 SIMD 化
------------------------------------------------------------------------
檔案:huc6270.cpp RenderBackground(像素預解迴圈,第 740-744 行)

問題(熱點排行 #2):
  像素預解迴圈每次 tile 邊界執行,每條掃描線約 32~128 次,8 次反覆
  共 96 ops/tile,每秒約 464640 次 tile-change,總計約 44M ops/sec(純量)。

對策:SSE2 PMOVMSKB 位元矩陣轉置
  原理:PC Engine 的 4 個 bit-plane byte 視為 4×8 位元矩陣,每條 column
  對應一個 pixel(4-bit)。使用 _mm_movemask_epi8 提取每 byte 的 MSB,
  配合 _mm_slli_epi32 將目標位元移至 MSB 位置。

  演算法:
    packed = (byte4<<24) | (byte3<<16) | (byte2<<8) | byte1
    v = _mm_cvtsi32_si128(packed)   // XMM bytes 0-3 = [byte1..byte4]
    pixels[7-N] = _mm_movemask_epi8(v << N) & 0x0F
    // = (byte1>>(7-N)&1) | ((byte2>>(7-N)&1)<<1) | ...

  平台:SSE2 以上(x86/x64),使用檔案既有 GG_USE_SSE2 / GG_USE_AVX2
  編譯分支。非 x86 平台自動降級為純量 for 迴圈(#else 路徑)。

公式推導驗證:
  令 XMM bytes 0-3 為 [b1][b2][b3][b4],左移 N 位後新 byte B 的 MSB
  = 原 byte B 的 bit (7-N)(0 ≤ N ≤ 7 範圍內不跨 byte)。
  movemask 取 byte 0..3 的 MSB:
    bit 0 = b1 MSB, bit 1 = b2 MSB, bit 2 = b3 MSB, bit 3 = b4 MSB
  ∴ movemask(v << N) & 0x0F = pixels[7-N],與純量算式位元級相符。

位元級隨機驗證(byte1=0xAA, byte2=0xCC, byte3=0xF0, byte4=0xFF):
  N=0..2 → 15, N=3 → 13, N=4..7 → 5,與純量 pixels[7-N] 全部相符。
  邊界:全 0 → 全 0,全 0xFF → 全 15,均相符。

效果:8 次純量反覆 → 8 次 SIMD 指令。

注意事項:
  - _mm_slli_epi32(v, N) 僅用於 N=0..7
  - movemask 結果僅取低 4 bits(& 0x0F)
  - byte1~byte4 為 u8 型別,強轉 u32 避免移位溢位
  - 位元順序:pixels[0] = 最左像素(bit 7 優先),與 PC Engine tile 編碼一致


------------------------------------------------------------------------
[3] RenderSprites() — 16-pixel LUT 預解 + SSE2
------------------------------------------------------------------------
檔案:huc6270.cpp RenderSprites(第 767 行起,熱點排行 #5)

問題:
  內層 sprite pixel 迴圈每 pixel 算 pixel_x(含 x-flip)+ 4 plane 移位,
  同 RenderBackground 的 planar-to-packed 模式(4 個 u16 bit-plane → 16
  個 4-bit pixel)。

對策:16-pixel LUT 預解 + SSE2 PMOVMSKB(lo/hi byte 分開處理)
  將 16-bit plane 拆為 lo_byte(bits 7-0)和 hi_byte(bits 15-8),
  各別套用 PMOVMSKB 技巧。依 x-flip 旗標排列最終 pixel 順序。

  非 x-flip(0x0800 SET):sprite_pixels[0..7]=lo[7..0], [8..15]=hi[7..0]
   x-flip(0x0800 CLEAR):sprite_pixels[0..7]=hi[0..7](reverse),
                          [8..15]=lo[0..7](reverse)

  內層迴圈改為:pixel = sprite_pixels[x & 0xF](1 table lookup)

等價性驗證:
  非 x-flip:x=0→bit0→lo bit0, x=7→bit7→lo bit7, x=8→bit8→hi bit0,
             x=15→bit15→hi bit7,mapping 恆等。
  x-flip:pixel_x = 15-(x&0xF),反向 mapping 同樣恆等。

效果:內層變數移位 → table lookup。

注意事項:
  - 其他熱點(#2 ReadVRAM 純量、#3 m_vram[] 陣列、#4 FetchSprites 僅傳遞、
    #6 SATTransfer cycle-accurate)不適合最佳化。


------------------------------------------------------------------------
[4] RenderBackground — tile 為單位迴圈重構(Pass 4-A)
------------------------------------------------------------------------
檔案:huc6270.cpp RenderBackground(lines 699-765)

問題:
  原結構每像素算 tile_col = bg_x>>3 並比較 prev_tile_col,第一像素靠
  prev_tile_col=-1 強制載入。

對策:
  tile 為單位外迴圈(while i<width),每 tile 開頭無條件載入一次,
  內 do-while 跑該 tile 像素(pixel_idx 0..7)。

等價性驗證(5 點):
  ① tile 邊界:pixel_idx 由 7→8 ⟺ bg_x>>3 改變(low 3 bits 全 1)
  ② wrap 安全:x_mask∈{255,511,1023} 低 3 位全 1,wrap 不擾亂計數
  ③ VRAM 讀取:每 tile 開頭 BAT+2 tile-data,順序/次數/位址一致,
     m_vram_openbus 終值一致(openbus 副作用保留)
  ④ pixels 索引:pixel_idx 從 bg_x&7 起始遞增,等同 pixels[bg_x&7]
  ⑤ 邊界:width=0 不進入;起始非對齊;部分 tile 由 do-while (i<width) 處理

效果:移除每像素 shift+比較,分支預測友善。


------------------------------------------------------------------------
[5] RenderSprites — 內迴圈不變量外提(Pass 4-B)
------------------------------------------------------------------------
檔案:huc6270.cpp RenderSprites(內迴圈 lines 786-862)

問題:
  sprite 迴圈級有兩個不變量在 16-像素內迴圈被重複讀取。

對策:
  外提兩個不變量至 sprite 迴圈級:
    bool is_index_zero = (m_sprites.index == 0);
    u16  palette_rendered = m_sprites.palette | 0x100 | sprite_rendered_flag;
  內 16-像素迴圈改用 local 變數。

等價性:m_sprites.index / palette 在 16-像素內迴圈為常數(sprite 迴圈
  內無寫入),外提後內迴圈讀取值位元一致,純讀取無副作用差異。

效果:移除每像素成員解址 ×2。


------------------------------------------------------------------------
[6] HuC6260::Clock — 刪除 dead cycles_to_hsync 分支(A1)
------------------------------------------------------------------------
檔案:huc6260_inline.h(lines 47-61)

問題(每 cycle 熱點):
  原 Clock() 每 cycle 計算 cycles_to_hsync 的 ternary 並做第二次 min。
  該 ternary 在 HUC6260_HSYNC_END_HPOS==0 且 m_hpos>=0 時永遠走 else,
  結果 = LINE_LENGTH - m_hpos = cycles_to_line_end,第二次 min 為 no-op。

對策:刪除 ternary + clamp + min(7 行),加註解。

等價性驗證(5 點):
  ① HUC6260_HSYNC_END_HPOS == 0(huc6260.h:31 常數)
  ② m_hpos 恆 >= 0:Reset 寫 0;Clock 內 step <= cycles_to_line_end;
     LoadState 後 SanitizeState clamp 到 [0, LINE_LENGTH-1]
  ③ ternary 永遠走 else → cycles_to_hsync = cycles_to_line_end
  ④ min(step, cycles_to_hsync) 已在 line 58-59 做過 → 第二次 min 為 no-op
  ⑤ hsync 觸發點(m_hpos == 0)由 line 115 歸零獨立觸發,不依賴 cycles_to_hsync

編譯器角度:ternary 中 m_hpos 是 s32 member,可能 alias,編譯器無法自消
→ 手動有效。

效果:每 cycle 省 1 次 ternary + 1 次 min 比較。


------------------------------------------------------------------------
[7] RenderLine BG-disable SSE2 零填(A12)
------------------------------------------------------------------------
檔案:huc6270.cpp RenderLine(lines 685-687)

問題:
  原版 BG 關閉時用純量 for 寫 0x100 到 m_line_buffer[0..width-1]。

對策:
  改用 SSE2:_mm_set1_epi16(0x0100) + _mm_store_si128,8 個 u16 一批,
  剩餘純量 tail。純量 fallback 保留。

等價性驗證(3 點):
  ① m_line_buffer 為 alignas(16) u16[](huc6270.h:162)→ _mm_store_si128 對齊安全
  ② 寫入值位元 pattern:SSE2 寫 0x0100 per u16,純量寫 0x100,完全相同
  ③ 寬度處理:SSE2 處理 i+7<width 的前 N*8 個,剩餘 width%8 個純量,
     寫入範圍、值與原版全純量完全一致

效果:BG 關閉場景,寬度 1024 時 1024 次寫入 → 128 次 SSE store。


------------------------------------------------------------------------
[8] ApplyLowPassFilter 外提 inv_alpha(A13)
------------------------------------------------------------------------
檔案:huc6260.cpp ApplyLowPassFilter(lines 437-439)

問題:
  原版內迴圈(每 pixel)計算 (1.0f - alpha) 三次(r/g/b 各一)。alpha
  在 line 390 計算後整個函式內不再改變。

對策:預算 inv_alpha = 1.0f - alpha 一次,內迴圈用 local。

等價性驗證(2 點):
  ① alpha 在 line 390 後無其他寫入點 → 函式內常數
  ② inv_alpha = 1.0f - alpha 為純 IEEE 754 減法,預算一次與每次重算位元
     結果完全相同(同一運算元、同一運算 → 同一位元 pattern)
  浮點運算順序未改變,僅消除重複計算 → 無 ULP 差異風險。

效果:lowpass 啟用時,每 pixel 省 2 次 float 減法
(242 線 × 約 512 pixel = 約 124k 次/frame)。


------------------------------------------------------------------------
[9] HuC6270 transfer gate bool — m_any_transfer_pending(A3)
------------------------------------------------------------------------
檔案:huc6270.h, huc6270.cpp, huc6270_inline.h

問題(每 cycle 熱點):
  原 Clock() 每 cycle 測 if (sat>0) else if (vram>0)(兩次 load + 兩次
  branch)。99% cycle 兩者皆 0。

對策:
  新增 m_any_transfer_pending bool 為嚴格超集閘極,false 時跳過整段。
  同步點(全部已實作):
    - 設 true:VBlankIRQ(sat=1024)、WriteRegister LENR(vram)
    - 清 false:SATTransfer 完成(sat==0 且 vram==0)、VRAMTransfer 完成
    - Reset:false
    - LoadState:重算 = (sat>0 || vram>0)
    - Clock():if (m_any_transfer_pending) { 原 if/else if }

等價性:
  bool 為嚴格超集:false ⟺ sat==0 且 vram==0 ⟺ 原兩條件都失敗 → 跳過
  不改行為。true 時跑原邏輯。所有排程/清除點已涵蓋。m_any_transfer_pending
  為衍生量,不入序列化,LoadState 後重算,不破壞 SaveState。

效果:每 cycle 省 2 次 load + 2 次 branch(99% cycle)。


------------------------------------------------------------------------
[10] HuC6260 Clock member 外提 local(A5/A6)
------------------------------------------------------------------------
檔案:huc6260_inline.h HuC6260::Clock

問題(每 cycle 熱點):
  Clock() while 迴圈內 3 個使用點重複載入 member。

對策:
  Clock() while 迴圈前外提三個 member 為 local:
    const int full_line_width = k_huc6260_full_line_width[m_speed];
    const int screen_start_x = m_screen_start_x;
    const int screen_end_x = m_screen_end_x;
  迴圈內 3 個使用點改用 local。

等價性:
  這些 member 在整個 Clock() 呼叫期間不變(CPU 無法插隊寫 VCE 暫存器,
  RunInstruction 與 ClockHardware 交替執行)。外提至 local 值不變。
  SaveState 載入後,下次 Clock() 開頭讀 local 仍從 member 讀。
  編譯器角度:member 可能被 alias,編譯器無法自消 → 手動有效。

效果:每 cycle 省 member 重複載入。


------------------------------------------------------------------------
[11] IsCpuVramSlotAvailable 結果快取(A8)
------------------------------------------------------------------------
檔案:huc6270_inline.h ProcessCpuVramAccesses

問題(每 3-cycle slot,CPU VRAM DMA 期間非常熱):
  原 while 迴圈每圈呼叫 IsCpuVramSlotAvailable(3)。該函式讀取
  hpos/v_state/burst_mode/latched_cr/sprite_count/load_bg_*/latched_mwr/
  divider,這些在 ProcessCpuVramAccesses 內全不變。

對策:
  while 前呼叫一次,結果存 slot_available,每圈用 local。

等價性(親自驗證):
  ProcessCpuVramAccesses 內部不推進任何硬體時脈(hpos 等不變)。
  IsCpuVramSlotAvailable(3) 的 elapsed_cycles=3 固定 → 結果為常數。
  m_allow_vram_access 副作用:首次呼叫寫入,之後不呼叫 → 保留首次值
  (與原版每圈重寫相同值等價,因輸入相同)。
  delay 邏輯(m_transfer_delay)每圈仍跑,不受影響。

效果:CPU VRAM DMA 期間,每 slot 省一次完整 IsCpuVramSlotAvailable 呼叫
(含 CurrentHClock、多重條件分支、dots_per_sprite switch)。


------------------------------------------------------------------------
[12] m_in_display_window bool 快取(B2)
------------------------------------------------------------------------
檔案:huc6270.h, huc6270.cpp, huc6270_inline.h

問題(每 cycle 熱點):
  Clock() 每 cycle 測 m_active_line && (m_h_state == HDW)。

對策:
  新增 m_in_display_window bool,在 5 個轉換點更新,Clock() 改讀 bool。
  5 個更新點:
    1. Reset:init false
    2. SetHSyncHigh:強制 HSW 後 false(非 NextHorizontalState 路徑,易漏)
    3. EndOfLine:m_active_line 設定後重算
    4. NextHorizontalState switch 後:反映 post-transition m_h_state
       (含 HSW case 內 HSyncStart→EndOfLine 變動)
    5. LoadState 結尾:m_active_line + m_h_state 讀回後重算
  唯一讀者:Clock()(inline.h:53)→ if (m_in_display_window)

等價性:
  m_in_display_window = m_active_line && (m_h_state == HDW)。所有寫入點
  涵蓋。Clock 內 m_h_state 只在 NextHorizontalState 改,m_active_line 只
  在 EndOfLine 改 → 掃描線內無 drift。bool 為衍生量,不入序列化,
  LoadState 後重算 → 不破壞 SaveState。

效果:每 cycle 省 bool load + enum compare。


------------------------------------------------------------------------
[13] m_bg_enabled / m_sprites_enabled 預解碼(B5)
------------------------------------------------------------------------
檔案:huc6270.h, huc6270.cpp, huc6270_inline.h

問題(每 scanline + 每 CPU VRAM cycle):
  RenderLine 與 IsCpuVramSlotAvailable 每次測 m_latched_cr & 0x80/0x40。

對策:
  新增 m_bg_enabled/m_sprites_enabled bool,在 4 個 decode 點更新,讀者
  改用 bool。sprites 有第二讀者(IsCpuVramSlotAvailable),雙重收益。
  4 個 decode 點:
    1. Reset:m_latched_cr = HUC6270_VAR_CR 後
    2. EndOfLine wrap:部分 mask 重寫 bits 6,7 後(剛好是 BG/sprites bit)
    3. NextHorizontalState HDS:在 !m_burst_mode guard 內
       (burst mode 不 latch,bool 保持 frame wrap 值;burst 下 bits 6,7=0
        → bools=false,正確)
    4. LoadState 結尾
  4 個讀者替換:
    - RenderLine:697 → if(!m_bg_enabled)
    - RenderLine:715 → if(m_bg_enabled)
    - RenderLine:718 → if(m_sprites_enabled)
    - IsCpuVramSlotAvailable:281 → bool sprites_enabled = m_sprites_enabled;

等價性:
  m_bg_enabled = (m_latched_cr & 0x80) != 0
  m_sprites_enabled = (m_latched_cr & 0x40) != 0
  EndOfLine wrap 只重寫 bits 6,7 → decode 在重寫後反映新值。
  HDS guard 內 decode → 只在非 burst 追蹤 latch。
  兩 bool 為衍生量,不入序列化,LoadState 後重算。

效果:每 scanline + 每 CPU VRAM cycle 省解碼。


================================================================================
第一部分 §2  模擬正確性 Bug 修復(5 項,對齊 Mednafen)
================================================================================

------------------------------------------------------------------------
[Bug-1] ReadTimerRegister 0x7F → m_timer_reload(HIGH-1)
------------------------------------------------------------------------
檔案:huc6280_inline.h ReadTimerRegister(line 272-278)

問題:
  reload 視窗(m_timer_counter==0 && m_timer_cycles<=15)回傳硬編碼 0x7F,
  而非 m_timer_reload。當 reload != 0x7F 時讀值錯誤。

Mednafen 對照(pce/huc6280.cpp):
  TimerRead(line 709-718):先 TimerSync() 再讀 timer_value,
    return(timer_value | (IODataBuffer & 0x80))
  TimerSync(line 527-560):underflow 後 timer_inreload,
    下次同步 timer_value = timer_load(line 539)
  → reload 視窗讀到的是 timer_load,不是常數 0x7F

修復:
  reload 視窗回傳 m_timer_reload(對應 Mednafen 的 timer_load)。
  非 reload 視窗仍回 m_timer_counter(與原版相同)。

注意:
  Geargrafx 的 5*3=15 cycles 視窗是其自己設計,無直接 Mednafen 對應
  (Mednafen 用 timer_inreload 狀態機)。本修復保留視窗,只改值。


------------------------------------------------------------------------
[Bug-2] VCE blur/frame-length latch(HW-1)
------------------------------------------------------------------------
檔案:huc6260.h, huc6260.cpp, huc6260_inline.h

問題:
  VSync start/end/wrap(inline.h:122,139,161)用 live m_blur 計算
  k_huc6260_total_lines[m_blur]。mid-frame 改 blur 會讓 frame 長度不可
  預測地變動,三個比較點可能用不同 blur 值。

Mednafen 對照(pce/vce.cpp):
  line 546:vblank_counter = vblank ? 4095 :
            ((lc263 ? 358995 : 357630) - 4095)
  → frame 長度在 vblank 邊界 latch 進 vblank_counter,下個 frame 期間
    不再讀 lc263 決定長度。

修復:
  新增 m_latched_total_lines member,在 frame wrap 時 latch:
    - huc6260.h:新增 s32 m_latched_total_lines(line 139)
    - huc6260.cpp Reset:m_latched_total_lines = k_...[m_blur]
    - huc6260_inline.h SanitizeState:同上(LoadState 後重算)
    - inline.h:122,139,161 三比較點改用 m_latched_total_lines
    - inline.h:161 frame wrap 後 latch:
        m_latched_total_lines = k_huc6260_total_lines[m_blur]
    - inline.h GetTotalLines():回傳 m_latched_total_lines
      (huc6270.cpp:348 EndOfLine 的 frame wrap 一致)

等價性:
  - blur 不變時:latched 值 = live 值,行為與原版完全相同
  - mid-frame 改 blur:整個 frame 用 latched 值,與 Mednafen 一致
  SaveState 相容:m_latched_total_lines 為衍生量,不入序列化。
  LoadState 後 SanitizeState 重算(line 396),不破壞 SaveState 格式。

實際影響:極小。遊戲鮮少 mid-frame 切 blur bit(lc263 主要用於 262 vs
263 line 模式選擇,是配置而非動態切換)。


------------------------------------------------------------------------
[Bug-3] ApplyLowPassFilter RGBA8888 round/clamp(CONFIRMED-2)
------------------------------------------------------------------------
檔案:huc6260.cpp ApplyLowPassFilter(line 452-454)

問題:
  RGB565 路徑(line 444-446):RoundToByte(r) = clamp(0,255) + 四捨五入
  RGBA8888 路徑(line 452-454):(u8)r = 純截斷,無 clamp
  兩個差異:
    (1) 截斷 vs 四捨五入
    (2) 無 clamp:r > 255.0f(IIR 浮點誤差累積)時 (u8)r 會 wrap

後果:
  (1) 同一 frame,RGBA8888 輸出比 RGB565 系統性偏暗(截斷累積)
  (2) r > 255.0f 時 wrap 成小值 → 亮點/壞點

修復:
  RGBA8888 路徑改用 RoundToByte(r/g/b),與 RGB565 對稱。
  RoundToByte(huc6260_inline.h:525-534):
    value <= 0 → 0; value >= 255 → 255; else (u8)(value + 0.5f)

等價性:
  RGB565 路徑完全不變。RGBA8888 路徑行為改變(截斷→四捨五入+clamp),
  此為 bug 修復。alpha 通道未動(由 RenderFrameTemplate 寫 255)。

影響評估:
  正常情形(r 在 [0,255]):四捨五入 vs 截斷差最多 1 LSB,視覺無感。
  異常情形(r > 255.0f):原版 wrap 壞點 → 修復後 clamp 到 255。


------------------------------------------------------------------------
[Bug-4] AdjustForMultipleDividers hardcode 242 → end_line(MED-2)
------------------------------------------------------------------------
檔案:huc6260.cpp AdjustForMultipleDividers(line 348)

問題:
  最終 memcpy 用 hardcode 242,但迴圈(line 298)只寫入
  end_line = GetCurrentHeight() 行。scanline cropping 時 end_line < 242,
  memcpy 會 copy 到 m_scale_buffer 中 stale 資料。

修復:242 → end_line,與迴圈寫入範圍一致。

等價性:
  - end_line == 242(無 cropping):與原版完全相同
  - end_line < 242(有 cropping):只 copy 寫入的行數,不 copy stale
  消費端(emu.cpp screenshot、OpenGL 上傳)用 GetCurrentHeight() 決定高度,一致。


------------------------------------------------------------------------
[Bug-5] ClockTimer if → while hardening(MED-3)
------------------------------------------------------------------------
檔案:huc6280_inline.h ClockTimer(line 248)

問題(latent):
  原 if (m_timer_cycles <= 0) 只處理一次 underflow。
  若 cycles > divisor(=1024*3=3072),m_timer_cycles -= cycles 後可能
  遠小於 0,單次 if 只能處理一個 period,會丟掉多次 timer IRQ。
  正常 instruction(cycles < 100)不會觸發,為 hardening。

Mednafen 對照(pce/huc6280.cpp TimerSync line 533):
  while (timer_div <= 0) { ... timer_div += reload_div; }
  → 用 while 處理多次 underflow

修復:
  if → while;m_timer_cycles = divisor + m_timer_cycles → m_timer_cycles
  += divisor(數學等價,放進 while)。

等價性:
  - 正常(cycles < divisor):while 至多跑一次,與 if 相同
  - 長 stall(cycles >= divisor):while 處理多次 underflow
    (hardening,對齊 Mednafen)
  timer reload/IRQ 邏輯未動,只改迴圈結構。

================================================================================
                      第二部分
            穩定性/安全性修復(2026-07-06)
================================================================================

經三輪系統性掃描與驗證(廣度掃描 → 逐點判讀 → 完整攻擊鏈 + 啟用條件
+ 反向質疑),找出可能破壞程式運行穩定性的問題,分四波修復。
全部使用專案既有慣例(CLAMP/MIN/MAX/IsValidPointer/Error/TraceProblem/
std::atomic/lock_guard/CLAMP 後置範本)。

共通性質:
  - 未改變任何公開 API 介面或 save-state 格式(向後相容)
  - 第三方依賴(libchdr/miniz 等)未被修改


================================================================================
第二部分 §1  CD-ROM 子系統(嚴重等級,惡意檔案即可觸發)
================================================================================

根因:libchdr 的 metadata 路徑完全無完整性驗證(VERIFY_BLOCK_CRC、
rawsha1、sha1 皆不含 metadata),chd_get_metadata 直接 core_fread 攻擊者
可控的位元組。

------------------------------------------------------------------------
[C1] CHD u32 下溢污染 TOC(嚴重)
------------------------------------------------------------------------
檔案:cdrom_chd_image.cpp ReadTOC(line 392)

問題:
  data_frames = frames - pregap_dv(int - u32 混型),pregap_dv > frames
  時下溢成約 UINT32_MAX,污染 sector_count/end_lba/m_toc.sector_count。
  CHD metadata 無完整性驗證,攻擊者可控。

修復:
  1. frames/pregap/postgap 負值檢查:
     if (frames < 0 || pregap < 0 || postgap < 0) { Error; return false; }
  2. 下溢保護:
     if (pregap_dv > (u32)frames) { Error; return false; }
  3. data_frames 改 (u32)frames - pregap_dv
  4. L3 對齊計算改 u32 運算:((u32)frames + 3u) & ~3u

注意:用 return false(非 clamp)是因為 CHD 損壞/惡意構造,載入失敗比
載入 TOC 污染的 CD 更安全。99 軌上限(for i<99)只限軌數,不限
frames/pregap 數值,本修復補此缺口。


------------------------------------------------------------------------
[C2] CUE 最後一軌 u32 下溢污染 TOC(嚴重)
------------------------------------------------------------------------
檔案:cdrom_cuebin_image.cpp ParseCueFile TOC 組裝(line 978)

問題:
  last_size = file_size - last.file_offset,file_offset 由 CUE INDEX 時間
  戳累加(可達 m=99 → 約 1.05GB),可超過小 BIN 的 file_size,u32 下溢。

修復:
  if (last.file_offset > f.img_file->file_size) { Error; return false; }

注意:INDEX m<=99 範圍檢查不足以擋 file_offset 累加。


------------------------------------------------------------------------
[C3] ZipSlip 路徑穿越 — 可持久化 RCE(嚴重)
------------------------------------------------------------------------
檔案:common.h extract_zip_to_folder(line 389);新增 is_path_safe helper

問題:
  out_path = out_dir + "/" + file_stat.m_filename,條目名未淨化 .. 或絕對
  路徑。寫入 %APPDATA%\Geardome\tmp\,../ 可逃逸覆寫 DLL。

修復:
  新增 is_path_safe(const char* path):
    - 拒絕 NULL/空字串
    - 拒絕絕對路徑(/、\、X: 磁碟代號)
    - 拒絕含 .. 完整 segment(檢查 segment 邊界,不誤殺 a..b/foo..)
  extract 前檢查:if (!is_path_safe(...)) { Error; continue; }
  (continue 非 return false,讓其他合法條目仍可解壓)


------------------------------------------------------------------------
[H5] CHD sscanf 棧緩衝區溢位 — RCE 潛力(嚴重)
------------------------------------------------------------------------
檔案:cdrom_chd_image.cpp ReadTOC sscanf(line 343, 355)

問題:
  sscanf 用第三方巨集 CDROM_TRACK_METADATA*_FORMAT,%s 無寬度限制,
  目標 type[64]/subtype[32] 棧溢位。metadata 無 hash 驗證,sscanf 仍返回 8。

修復:
  呼叫端改用本地帶寬度格式字串(不使用第三方巨集):
    sscanf(metadata, "TRACK:%d TYPE:%63s SUBTYPE:%31s FRAMES:%d ...", ...)
  寬度對應:%63s → type[64]、%31s → subtype[32]/pgtype[32]/pgsub[32]

注意:第三方巨集在 libchdr 標頭,不改(避免污染依賴、升級衝突)。


------------------------------------------------------------------------
[M9] CUE FILE 相對路徑穿越讀任意檔(中)
------------------------------------------------------------------------
檔案:cdrom_cuebin_image.cpp ParseCueFile FILE 處理(line 745)

問題:
  ../sensitive.bin 不被過濾,可讀 CUE 目錄外檔案。

修復:路徑拼接前用 is_path_safe 檢查(共用 C3 helper),
  不通過 return false。

注意:成員名是 m_file_directory(snake_case)。


------------------------------------------------------------------------
[M10] CUE 軌數無上限 — DoS(中)
------------------------------------------------------------------------
檔案:cdrom_cuebin_image.cpp ParseCueFile 主迴圈(line 702)

問題:while(getline) 無軌數上限,百萬 TRACK 行可耗盡記憶體。

修復:加 track_count 計數,超過 k_max_cue_tracks(99) 即 return false
(對齊 CHD 的 99 上限)。


------------------------------------------------------------------------
[M11] WAV data_size 覆寫 file_size — 巨量配置(中)
------------------------------------------------------------------------
檔案:cdrom_cuebin_image.cpp FindWavDataChunk(line 637)

問題:data_size(不可信)直接覆寫 file_size,無交叉驗證。

修復:用 file.GetSize() 取實際大小,data_size > available 時 clamp。


------------------------------------------------------------------------
[M8] SetupToc index0>index1 下溢(低)
------------------------------------------------------------------------
檔案:cdrom_cuebin_image.cpp ParseCueFile TOC 組裝(line 971)

問題:(start_lba - lead_in_lba) 在 INDEX 00 > INDEX 01 時下溢。

修復:if (track.start_lba < track.lead_in_lba) { Error; return false; }


------------------------------------------------------------------------
[M2] PreloadTrackChunks u32 乘法溢位(低)
------------------------------------------------------------------------
檔案:cdrom_cuebin_image.cpp PreloadTrackChunks(line 366)

問題:total_bytes = sector_count * sector_size(u32*u32),獨立不觸發,
需 C1/C2 先污染。

修復:改 u64 計算,> 0xFFFFFFFF 則 return false。


================================================================================
第二部分 §2  save-state 加固(同源缺陷)
================================================================================

根因:save-state 的 payload 完全無 checksum/CRC(types.h:113-126 無欄位),
唯一 rom_crc 是公開值(Redump + Log 印出)。攻擊者改 payload 任意欄位 +
補正確 header 即可 100% 通過。

------------------------------------------------------------------------
[H3] m_step_index 未 clamp → OOB 讀 LUT(高)
------------------------------------------------------------------------
檔案:adpcm.cpp LoadState(line 224)

問題:
  LoadState 直接 read(&m_step_index)(u8,可達 255),首解
  m_step_delta[(m_step_index<<3)+value] 索引可達 2047,遠超陣列 392 項。
  CD 遊戲續播時下一 sample 即觸發。

修復:讀入後 m_step_index = CLAMP(m_step_index, 0, 48)
  (魔數 48 與 adpcm_inline.h:286 一致)。

注意:clamp 放在 read 之後立即做,不依賴 version 分支。載入後到首次解碼
之間無其他重設路徑,故必須在 LoadState 內完成。


------------------------------------------------------------------------
[H4] m_sprite_count 未 clamp → 防禦縱深(低)
------------------------------------------------------------------------
檔案:huc6270.cpp LoadState(line 1149)

問題(第三輪確認鏈條中斷):
  m_sprite_count 每條 active scanline 由 FetchSprites 清零重建,RCR event
  先於 RenderLine,save-state 注入值在第一幀就被覆蓋。故嚴重度從高降為低。
  仍加 clamp 是防禦縱深(同源缺陷,未來重構改變時序時可兜底)。

修復:m_sprite_count = CLAMP(m_sprite_count, 0, HUC6270_SPRITES * 2)


------------------------------------------------------------------------
[M1] LFO 負移位 UB(中)
------------------------------------------------------------------------
檔案:huc6280_psg.cpp(line 325)

問題:
  ((lfo_data-16) << (((m_lfo_control&3)-1)<<1)),control&3==0 時移位量
  為 -2(負數左移 UB)。LoadState 將 lfo_enabled 與 lfo_control 獨立讀入
  可解耦。

修復:if (m_lfo_control & 3) 守衛,control&3 為 0 時跳過移位。

注意:control&3==0 表示 LFO 頻率為 0,本就不該有 shift 作用。


================================================================================
第二部分 §3  MCP 並發子系統
================================================================================

重要前提:MCP 預設完全停用(mcp_mode=-1,需 --mcp-stdio/--mcp-http CLI
旗標,無 config/選單開關)。一般玩家不碰,僅影響主動啟用的 AI 除錯使用者。
HTTP 預設綁 127.0.0.1(loopback)。

------------------------------------------------------------------------
[C4] MCP Server detach 後 use-after-free(中)
------------------------------------------------------------------------
檔案:mcp_server.h, mcp_server.cpp, mcp_manager.h

問題:
  Stop() 將 m_thread detach(非 join),被 detach 線程仍在 recv()/send()
  存取 transport;同時 McpServer 被刪,transport 也被 SafeDelete。UAF。
  stdio getline 無法被 close 中斷,不能直接改 join。

修復(shared_ptr 徹底方案):
  1. McpServer 繼承 enable_shared_from_this<McpServer>
  2. Start() 內 m_thread 用 shared_from_this() 捕獲
  3. reader_thread 也透過 shared_from_this() 捕獲
  4. McpManager m_server 改 shared_ptr,Stop 用 reset() 取代 SafeDelete
  效果:detached 線程持有引用,McpServer 存活到所有線程結束,
  ~McpServer 的 SafeDelete(m_transport) 此時無人存取。

設計理由(為何共享整個 McpServer 而非只共享 transport):
  reader/Run 線程跑的是 McpServer 成員函式,深度依賴 this。只共享 transport
  不夠(成員函式透過 this 存取成員,this 失效仍 UAF)。共享整個 McpServer
  才能確保所有被存取的成員都存活。

注意事項:
  - shared_from_this() 前提:物件必須已由 shared_ptr 管理。McpManager::Start()
    用 shared_ptr(new ...) 包裹後才呼叫 ->Start()。未來維護時 McpServer
    必須由 shared_ptr 管理,不能直接 new 後呼叫 Start()。
  - 既有非本次引入的注意點:StdioTransport::close() 只設 m_closed,不解阻塞
    getline。stdio 模式 reader 可能滯留(McpServer 存活更久)。根治需讓
    close 真正關閉 stdin fd(未處理,屬獨立工程)。


------------------------------------------------------------------------
[H1/M7] 媒體載入中 MCP 讀寫核心裸指標(中/低)
------------------------------------------------------------------------
檔案:mcp_manager.h PumpCommands(line 189)

問題:
  emu_update 先 pump commands 再檢查 loading_state,載入執行緒重設
  RAM/ROM/VRAM 時 MCP 工具碰裸指標。MCP 自帶 m_pending_media_load guard
  不涵蓋 GUI 載入。

修復:PumpCommands 開頭加 if (emu_is_media_loading()) return;

效果:涵蓋所有 core 操作(read_memory、controller input、input macro、
delayed releases),一次擋住 H1 與 M7。載入期間所有 MCP 工具暫停,
載入完成後恢復。


------------------------------------------------------------------------
[M3] HTTP Content-Length 無上限 — DoS(低)
------------------------------------------------------------------------
檔案:mcp_transport.h recv()(line 575)

問題:atoi 無上限,單請求可觸發 GB 級分配。

修復:加 kMaxContentLength(16MB) 上限,超範圍 connection_closed + break。


------------------------------------------------------------------------
[M4] HttpTransport::close() 對 socket 操作未持鎖(低)
------------------------------------------------------------------------
檔案:mcp_transport.h HttpTransport::close()(line 714-731)

問題:close 對 socket 無 lock_guard,與持鎖的 send/recv 競爭
(Windows 雙重 close)。

修復:socket 操作用 std::lock_guard<std::mutex> lock(m_mutex) 包住
(對齊 close_current_client 既有正確寫法)。

注意:m_closed 與 notify_all 在鎖外(m_closed 是 atomic,安全;
notify_all 鎖外合法)。


------------------------------------------------------------------------
[M5] m_closed 為非原子 bool(低)
------------------------------------------------------------------------
檔案:mcp_transport.h StdioTransport(line 97)+ HttpTransport(line 736)

問題:bool m_closed 被 recv(reader 線程)讀、close(主線程)寫,無同步。

修復:兩處改 std::atomic<bool>(對齊 mcp_server.h m_running 先例)。
建構函式皆 m_closed = false 初始化。


================================================================================
第二部分 §4  低風險加固
================================================================================

------------------------------------------------------------------------
[H2] HuCard 載入 int 溢位 → heap overflow(低)
------------------------------------------------------------------------
檔案:media.cpp LoadHuCardFromBuffer(line 241)

問題:(size + 0x1FFF) & ~0x1FFF,size 接近 INT_MAX 時有號溢位。

修復:if (size > 0x40000000) { Error; return false; }(1GB 上限)。


------------------------------------------------------------------------
[H6] AudioLBA 未知模式 assert(false)(低)
------------------------------------------------------------------------
檔案:scsi_controller.cpp AudioLBA default(line 750)

問題:mode==0xC0 時 assert(false),Debug build 終止行程。

修復:移除 assert(false),保留 TraceProblem + return 0
(符合 SCSI 主慣例:TraceProblem 為主,assert 為特例)。


------------------------------------------------------------------------
[H7] BIOS tellg 失敗未檢查(低)
------------------------------------------------------------------------
檔案:media.cpp LoadBios(line 355)

問題:int size = (int)tellg(),失敗回 -1,原僅 Log 不拒絕。

修復:加 if (!file || size < 0) { Error; return false; }


------------------------------------------------------------------------
[H8] loading_thread_active/result 非原子(低)
------------------------------------------------------------------------
檔案:emu.cpp(line 64-65)

問題:兩變數非原子跨執行緒(join + atomic loading_state 已建立
happen-before,無實際並發,但型別不符最佳實踐)。

修復:改 std::atomic<bool>。


------------------------------------------------------------------------
[L1] 記憶體編輯器最後一行越界讀寫(低)
------------------------------------------------------------------------
檔案:gui_debug_memeditor.cpp 渲染迴圈(line 227)

問題:total_rows 無條件進位,最後列 byte_address 可 >= m_mem_size,
存取 m_mem_data[byte_address] 無守衛。僅 debug 工具手動觸發。

修復:if (byte_address >= m_mem_size) continue
(保持表格對齊:skip 分支也呼叫 TableNextColumn + separator)。


------------------------------------------------------------------------
[L2] strncpy_fit 對 src==NULL 無檢查(低)
------------------------------------------------------------------------
檔案:common.h strncpy_fit(line 186)

問題:strlen(src) 對 NULL 為 UB。

修復:加 if (!IsValidPointer(dest) || !IsValidPointer(src) || dest_size==0)
  守衛(dest 有效但 src NULL 時 dest[0]='\0',dest NULL 時不寫入)。


------------------------------------------------------------------------
[L5] parse_int long→int 截斷(低)
------------------------------------------------------------------------
檔案:shader_preset.cpp parse_int(line 537)

問題:(int)result 無範圍檢查,LP64 平台(long 64-bit)截斷。

修復:加 <climits>,if (result < INT_MIN || result > INT_MAX)
  return default_value。

================================================================================
                          記錄結束
================================================================================










評分

參與人數 4威望 +2 金錢 +85 貢獻 +2 收起 理由
ogawa61 + 10 精品文章
nnm11111 + 1 + 30 + 1 感謝分享
風間涼 + 15 這個模擬器好用..感謝優化..
ili789 + 1 + 30 + 1 感謝分享

查看全部評分

回復

使用道具 舉報

5

主題

76

帖子

582

積分

高級會員

Rank: 4

積分
582
發表於 2026-7-3 16:01:50 | 顯示全部樓層
感谢楼主大大的分享
回復

使用道具 舉報

0

主題

541

帖子

569

積分

高級會員

Rank: 4

積分
569
發表於 2026-7-3 16:57:09 | 顯示全部樓層
感謝樓主分享
回復

使用道具 舉報

0

主題

551

帖子

1273

積分

高級會員

Rank: 4

積分
1273
發表於 2026-7-3 19:20:16 | 顯示全部樓層
        感謝分享
回復

使用道具 舉報

8

主題

6006

帖子

5807

積分

金牌會員

Rank: 6Rank: 6

積分
5807
發表於 2026-7-4 00:54:22 | 顯示全部樓層
本帖最後由 syz2424 於 2026-7-4 01:06 編輯

感謝大量無修改私熱心時刻提供的分享遊戲資訊 [PCE模擬器] Geargrafx 1.7.14 繁/簡/中文版《測試版》
為什麼我的繁體跟簡體都是亂碼.英文版本沒有問題
回復

使用道具 舉報

0

主題

372

帖子

1092

積分

高級會員

Rank: 4

積分
1092
發表於 2026-7-4 06:15:50 | 顯示全部樓層
感谢楼主分享
回復

使用道具 舉報

0

主題

5384

帖子

4895

積分

金牌會員

Rank: 6Rank: 6

積分
4895
發表於 2026-7-4 06:31:28 | 顯示全部樓層
感谢大大的分享
回復

使用道具 舉報

20

主題

192

帖子

3807

積分

銀牌會員

Rank: 5Rank: 5

積分
3807
 樓主| 發表於 2026-7-4 06:38:56 | 顯示全部樓層
本帖最後由 good7 於 2026-7-5 12:47 編輯
syz2424 發表於 2026-7-4 00:54
感謝大量無修改私熱心時刻提供的分享遊戲資訊  Geargrafx 1.7.14 繁/簡/中文版《測試版》
為什麼我的繁體跟 ...


你的現象就應該如同下面的狀況,

更正:
將 simhei.ttf 複製到 C:\Users\你的使用者名稱\AppData\Roaming\Geardome\Geargrafx 下就可以了。

sshot-32.png


評分

參與人數 1金錢 +15 收起 理由
風間涼 + 15 實用解決問題資訊與方法..

查看全部評分

回復

使用道具 舉報

0

主題

539

帖子

930

積分

高級會員

Rank: 4

積分
930
發表於 2026-7-4 07:50:28 | 顯示全部樓層
謝謝分享
回復

使用道具 舉報

0

主題

88

帖子

225

積分

中級會員

Rank: 3Rank: 3

積分
225
發表於 2026-7-4 08:15:44 | 顯示全部樓層
謝謝大大的無私分享
回復

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 立即註冊

本版積分規則

Archiver|小黑屋|遊戲年代 GOAL GOAL

GMT+8, 2026-10-14 01:50 , Processed in 0.024908 second(s), 23 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回復 返回頂部 返回列表