|
|
================================================================================
Flycast 專案最佳化、安全性漏洞及其它記錄
================================================================================
一、安全性修復
================================================================================
------------------------------------------------------------------------------
1.1 Critical(即時危及安全性/可利用性)
------------------------------------------------------------------------------
[C-01] CHD sscanf 堆疊緩衝區溢位
檔案:core/imgread/chd.cpp:198,203,218
修改:三處 sscanf 的格式字串中 %s → %15s,限制寫入寬度
說明:type[16] 等四個 16 byte 緩衝區不再被超長元資料溢位
狀態:已修復
[C-02] ISO9660 目錄記錄越界讀取
檔案:core/imgread/isofs.cpp:76
修改:if (index >= data.size()) → if (index + 34 > data.size())
說明:確保剩餘空間足夠容納完整的目錄記錄結構(至少 34 byte)
狀態:已修復
[C-03] RetroArch 存檔 MEM 區段大小未驗證
檔案:core/serialize.cpp:117-122
修改:加入 if (sectionSize > limit) throw Exception(...)
說明:MEM 區段的 sectionSize 現在會與剩餘資料長度比較
狀態:已修復
[C-05/06] JIT 代碼緩衝區 RWX(W^X 違規)+ jit_set_exec 空操作
檔案:core/windows/win_vmem.cpp:188,254-256
修改:mem_alloc 初始分配 PAGE_EXECUTE_READWRITE → PAGE_READWRITE;
實作 jit_set_exec(VirtualProtect 切換 PAGE_READWRITE / PAGE_EXECUTE_READ)
說明:初始分配改為 RW,編譯完成後 jit_set_exec(true) 切為 RX,實作 W^X 防護
狀態:已修復
[C-08] Lua 無沙箱 — 任意程式碼執行
檔案:core/lua/lua.cpp:622-623
修改:luaL_openlibs(L) 替換為白名單載入(base/table/string/math/utf8)
說明:移除 os/io/package/debug 函式庫,消除任意命令執行能力
狀態:已修復
[C-10] getSubPath 路徑遍歷漏洞
檔案:core/oslib/storage.cpp:151-155
修改:加入 .. 和絕對路徑檢查,不合法時 throw StorageException
說明:所有 36+ 處呼叫端現在都受到路徑遍歷保護
狀態:已修復
[C-1] SH4 動態重編譯器記憶體洩漏
問題:block cache 累積 ~165 MB 未釋放的 oplist 記憶體
原因:std::vector 每次 compile 完不釋放 capacity
影響:boot/gameplay 期間記憶體持續上升
修復:rdv_CompilePC 回傳後插入 std::vector<shil_opcode>().swap(rbi->oplist)
狀態:已修復
[C-2] 垂直同步堵塞(lost-wakeup 死結)
問題:renderEnd.Wait() 和 frame_finished.Wait() lost-wakeup 設計缺陷
影響:Emu thread 永久阻塞,模擬器死結
修復:cResetEvent::Wait() → predicate 版 + render() 提早返回時補 renderEnd.Set()
狀態:已修復
------------------------------------------------------------------------------
1.2 High(越界/溢位/注入等可利用性高)
------------------------------------------------------------------------------
[C1] rec-x86\x86_ops.cpp:326-327 邏輯錯位(stack push 順序顛倒)
說明:interpreter_fallback(Sh4Context*,u16,OpCallFP*,u32) x86 __fastcall
stack push 順序反了:oph 與 pc 對調。函式從 stack 讀到錯誤的
函式指標→呼叫非法位址→崩潰。影響所有 x86 MMU 啟用時的路徑。
修復:交換兩個 push 指令的順序(__fastcall 參數由右至左 push:先 pc 再 oph)
狀態:已修復
[C6] pvr_sb_regs.cpp:67-76 陣列越界(無邊界檢查)
說明:calculate_start_link_addr() 用 SB_SDDIV 直接索引
((u16*)base)[SB_SDDIV] / ((u32*)base)[SB_SDDIV],無 bounds check,
guest 可設 SB_SDDIV 任意值→OOB read。
修復:拆分 offset/base 變數,計算 elemSize(2 或 4),
加入 SB_SDDIV >= (RAM_SIZE - offset) / elemSize 溢位安全檢查,
越界時回傳 2(sort DMA 迴圈終止值)
狀態:已修復
[C11] TexCache.cpp:1183-1184 Unsigned wraparound → 緩衝區溢寫
說明:padding + (width - xclip.max - 1) * bpp
當 xclip.max >= width 時,width - xclip.max - 1 作為 u32 wrap
成巨大值→pixWriter.advance() 指標跳躍→VRAM 寫越界。
修復:(width - xclip.max - 1) 改為 (width - clipWidth);
clipWidth = min(width, xclip.max+1),保證不 wraparound。
正常路徑數學等價,越界時為 0
狀態:已修復
[C12] sorter.cpp:67-73 陣列越界(無 primitive restart 檢查)
說明:sortedTriangles path 直接 rendContext.idx[tri.first + i],
未檢查 (u32)-1 primitive restart marker(非 sorted path 有檢查)。
若 index buffer 含 -1,存取 verts[0xFFFFFFFF]。
修復:提取 idx0/idx2 變數,檢查 (u32)-1 後才 dereference,
與非 sorted path 邏輯一致
狀態:已修復
[H-01] CDI fread 回傳值未檢查
檔案:core/deps/chdpsr/cdipsr.cpp:47-103
修改:CDI_read_track 中 13 處 fread 全部加入 != 1 return false 檢查
狀態:已修復
[H-02] GDI 軌道計數無上限 → OOM DoS
檔案:core/imgread/gdi.cpp:34
修改:iso_tc == 0 → iso_tc == 0 || iso_tc > 100
狀態:已修復
[H-03] GDI EndFAD 整數下溢
檔案:core/imgread/gdi.cpp:99-100
修改:加入 OFFSET 範圍檢查(< 0 或 > fileInfo.size 時 throw)
狀態:已修復
[H-04] CDI fseek 偏移量整數溢位
檔案:core/imgread/cdi.cpp:183
修改:track.total_length * track.sector_size → (long long)... * ...
狀態:已修復
[H-05] CHD 數值欄位無範圍驗證
檔案:core/imgread/chd.cpp:222
修改:加入 frames/padframes/pregap/postgap 範圍檢查
狀態:已修復
[H-06] DNS 回應解析無邊界 strlen
檔案:core/network/dns.cpp:130-137
修改:加入最小長度檢查;strlen → strnlen;加 reader > end 檢查
狀態:已修復
[H-07] DHCP 選項越界讀取
檔案:core/network/dcnet.cpp:709
修改:while 條件 options - frame < len → options - frame + 2 < len
狀態:已修復
[H-08] GGPO VF4Card 缺少長度驗證
檔案:core/network/ggpo.cpp:468
修改:加入 len >= sizeof(type) + sizeof(card) 檢查
狀態:已修復
[H-09] registerHandler 使用 assert(Release 被移除)
檔案:core/hw/mem/addrspace.cpp:236
修改:assert(rv < HANDLER_COUNT) → verify(rv < HANDLER_COUNT)
狀態:已修復
[H-10] 處理器 ID 索引無邊界檢查
檔案:core/hw/mem/addrspace.cpp:123,168
修改:readt/writet 中加入 verify(id < HANDLER_COUNT)
狀態:已修復
[H-11] init() 中分配無錯誤檢查
檔案:core/windows/win_vmem.cpp:67,72
修改:加入 verify(mem_handle != NULL) 和 verify(base_alloc != NULL)
狀態:已修復
[H-13] UAF 風險:Stale Block 與代碼快取重用
檔案:core/hw/sh4/dyna/blockmanager.cpp:316
修改:bm_ResetCache 中加入 del_blocks.clear()
狀態:已修復
[H-14] cheats 計數無上限 → DoS/OOM
檔案:core/cheats.cpp:401
修改:加入 count 範圍檢查 (0-10000)
狀態:已修復
[H-15] cheat.size 位元移位 UB
檔案:core/cheats.cpp:417
修改:1 << → 1u <<,限制 memSize 範圍 [0,5]
狀態:已修復
[H-16] apply() 位址遞增無邊界
檔案:core/cheats.cpp:765,785
修改:copy 迴圈加邊界條件;setValue 迴圈加 address >= RAM_SIZE break
狀態:已修復
[H-17] INI 格式注入
檔案:core/cfg/ini.cpp:282-296
修改:加入 sanitizeIniValue() 過濾 \n \r;save() 兩處 fprintf 套用
狀態:已修復
[H-20] GDB stack 命令堆疊緩衝區溢位
檔案:core/debug/gdb_server.cpp:587-605
修改:固定/VLA 緩衝區改為 std::string 動態分配
狀態:已修復
[H-22] ELF 載入器越界讀取 + 整數下溢
檔案:core/reios/reios_elf.cpp:55-66
修改:加入 offset+len <= filesize 和 memSize >= len 檢查
狀態:已修復
[H-23] readdir 非執行緒安全
檔案:core/oslib/directory.cpp:52
修改:static dirent d → thread_local dirent d
狀態:已修復
[H-24] UDP AP 發現越界讀取
檔案:core/network/dcnet.cpp:470
修改:while 條件改為 p + 5 <= recvbuf + len
狀態:已修復
[H-25] GDB 繫結所有介面
檔案:core/debug/gdb_server.cpp:140
修改:tcp::v4() → address_v4::loopback()
狀態:已修復
[H-26] getArcadeFlashPath 路徑遍歷
檔案:core/oslib/oslib.cpp:103
修改:加入 fileName 淨化(拒絕 .. / \)
狀態:已修復
[H15] maple_jvs.cpp:1831-1837 邊界缺失
說明:handle_86_subcommand case 0x27 用 dma_buffer_in[idx] 無檢查
cmd_count 與 len 是否超出剩餘資料。
修復:迴圈內加入兩個 break 條件:
if (idx + 2 >= dma_count_in) break;
if (idx + 2 + len > dma_count_in) break;
狀態:已修復
[H17] TexCache.cpp:1228 Null pointer dereference
說明:invalidate() 直接 libCore_vramlock_Unlock_block_wb(lock_block)
未檢查 lock_block 是否已經為 nullptr。
修復:加入 if (lock_block != nullptr) 檢查後才呼叫並設為 nullptr
狀態:已修復
[H18] texconv.cpp:34-43(oslib.h) UB(__builtin_clz(0))
說明:bitscanrev(0) 在 GCC/Clang 呼叫 __builtin_clz(0) 是 UB,
若 texture width/height=0 時觸發。
修復:bitscanrev() 函式開頭加入 if (v == 0) return 0;
狀態:已修復
[H19] texconv.cpp:391-392,496-497 OOB 存取
說明:detwiddle[2][11][1024] 用 bitscanrev(width/height) 索引,
若值 >10 時越界,未驗證 texture dimension。
修復:4 處 detwiddle 存取改為 std::min(bitscanrev(...), 10u)
狀態:已修復
------------------------------------------------------------------------------
1.3 Medium(中等風險/需條件觸發)
------------------------------------------------------------------------------
[M-12] GGPO Deserializer 限制不一致
檔案:core/network/ggpo.cpp:448
修改:1_MB → settings.platform.isNaomi() ? 20_MB : 10_MB
狀態:已修復
[M-13] CDI 負軌道長度僅警告
檔案:core/imgread/cdi.cpp:166
修改:track.length < 0 WARN → track.length <= 0 throw
狀態:已修復
[M-14b] repeatCount 無上限
檔案:core/cheats.cpp:419
修改:限制 repeatCount ≤ 65536
狀態:已修復
[M-15] INI get_int/64 strstr("0x") 邏輯缺陷
檔案:core/cfg/ini.cpp:15-25, 27-33
修改:strstr → 字首檢查 value[0]=='0' && value[1]=='x'/'X'
狀態:已修復
[M-16] option.h 路徑列表越界存取
檔案:core/cfg/option.h:212,220
修改:加入 if (start >= paths.size()) break 邊界檢查
狀態:已修復
[M-24] saveScreenshot 路徑遍歷
檔案:core/oslib/oslib.cpp:301
修改:加入 name 淨化(拒絕 .. / \)
狀態:已修復
[M-25] descrambl 非執行緒安全全域狀態
檔案:core/reios/descrambl.cpp:16,33
修改:static → thread_local(seed 和 idx[])
狀態:已修復
[M-27] stdclass 路徑拼接無驗證
檔案:core/stdclass.cpp:67-81,100-114
修改:get_writable_config_path/data_path 加入 filename 淨化
狀態:已修復
[M-29] HTTP 回應無大小限制
檔案:core/oslib/http_client.cpp:62,233
修改:加入 10MB 上限檢查(Windows + curl 兩處)
狀態:已修復
[M-31] GDB unpack 無輸入驗證
檔案:core/debug/gdb_server.cpp:38-46
修改:加入嚴格 hex 字元範圍驗證,非 hex 回傳 0
狀態:已修復
================================================================================
二、系統級防護強化(CMAKE/建置層,3 項)
================================================================================
[CMAKE-01] Stack Canary 恢復
檔案:CMakeLists.txt:102
修改前:target_compile_options(${PROJECT_NAME} PRIVATE /GR /GS-)
修改後:target_compile_options(${PROJECT_NAME} PRIVATE /GR /GS)
說明:/GS- 明確關閉 stack canary,改為 /GS 恢復保護
狀態:已修復
[CMAKE-02] GCC Stack Protector 啟用
檔案:CMakeLists.txt:106
修改:加入 -fstack-protector-strong 編譯選項
說明:GCC build 加入 stack protector,與 MSVC 的 /GS 對等
狀態:已修復
[CMAKE-03] 移除 _CRT_SECURE_NO_WARNINGS
檔案:CMakeLists.txt:112
修改:移除 $<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS> 定義
說明:恢復 MSVC 對不安全 CRT 函數的編譯警告
狀態:已修復
================================================================================
三、穩定性/正確性修復
================================================================================
------------------------------------------------------------------------------
3.1 執行緒安全/競態條件
------------------------------------------------------------------------------
[C7/H2] emulator.h:207 + 多處 資料競賽(state 非 atomic)
說明:State state = Uninitialized 非 std::atomic<>,跨 thread 無同步存取:
read(795,942,961,965,1061)、write(514,683,757,788,954,969);
[H2] state = Error 在 run() catch block 中未受 mutex 保護,由本項一併解決。
修復:宣告為 std::atomic<State>
實際修改:
emulator.h:207 State state → std::atomic<State> state{Uninitialized}
emulator.h:217 getState() 改用 state.load()(移除 mutex)
emulator.h:218 setState() 改用 state.store(s)(移除 mutex)
emulator.cpp:966 WARN_LOG 中 state 改為 state.load()
狀態:已修復
[#4] ctx_list 跨執行緒無鎖存取
目標:core\hw\pvr\ta_ctx.cpp
問題:ctx_list 在 Emu 執行緒與主執行緒之間無鎖存取,可能導致迭代器失效
對策:新增 static std::recursive_mutex mtx_list;在 5 個存取點加入 lock_guard
狀態:已修復
[#6] FrameCount 非原子跨執行緒讀寫
目標:core\hw\pvr\Renderer_if.h:7, Renderer_if.cpp:22, ta_ctx.cpp:87, spg.cpp:184
問題:FrameCount 為普通 u32,多執行緒讀寫 → C++ data race (UB)
對策:將 u32 FrameCount 改為 std::atomic<u32> FrameCount
狀態:已修復
[M-10] NaomiNetwork 競態條件(未修復,需架構調整)
檔案:core/network/naomi_network.h
說明:_startNow 需改為 atomic
狀態:未修復(需外部配合)
[H22] CustomTexture.cpp:141-160 Memory ordering
說明:custom_image_data 非 atomic 寫在 atomic decrement 之後,
缺 release/acquire ordering,其他 thread 可能看到 decrement=0
但 custom_image_data 尚未 visible。
修復:custom_load_in_progress-- 前加入
std::atomic_thread_fence(std::memory_order_release)
狀態:已修復
[#21] dc_reset 軟重設與 unloadGame 可能並行
目標:core\emulator.cpp:720-726
問題:dc_reset(false) 不在 Emulator::mutex 內,可能與 unloadGame 並行修改
對策:在 dc_reset 呼叫外包裹 std::lock_guard<std::mutex>
狀態:已修復
[H10] decoder.cpp:565-615 邊界缺失
說明:MatchDiv32() 在迴圈中 IReadMem16(v_pc) 最多 32 次(128 bytes)
超出目前 block 範圍無檢查。
修復:迴圈開頭加入 if ((v_pc >> 24) != (pc >> 24)) break;
確保 v_pc 不跨越 16MB 區域邊界
狀態:已修復
------------------------------------------------------------------------------
3.2 死結/掛起
------------------------------------------------------------------------------
[#2] Renderer 例外導致死結
目標:core\hw\pvr\Renderer_if.cpp:194-215
問題:renderer->Process/Render 拋出例外時,FinishRender 不執行 → rqueue 不清空
→ frame_finished 不 Set → 下一幀 QueueRender 永久阻塞 → 模擬器死結
對策:用 try/catch 包裝 Process+Render+present 區段;catch 中執行
renderEnd.Set() + rend_allow_rollback() 確保渲染狀態一致
狀態:已修復
[C-2] 垂直同步堵塞(lost-wakeup)— 詳見「一、安全性 1.1 [C-2]」
狀態:已修復
------------------------------------------------------------------------------
3.3 邏輯錯誤/狀態不一致
------------------------------------------------------------------------------
[C3] nullDC.cpp:468 狀態不一致(跳過關鍵重設步驟)
說明:dc_bookmarkLoad() 直接呼叫 dc_deserialize() 而非 emu.loadstate(),
跳過:custom_texture preload、aica::arm::recompiler::flush()、
mmu_flush_table()、bm_Reset()、memwatch::unprotect/reset、
mmu_set_state()、getSh4Executor()->ResetCache() 等。
修復:dc_deserialize(deser) → emu.loadstate(deser)
(loadstate 內部呼叫 dc_deserialize 並執行前後 9 個關鍵步驟)
狀態:已修復
[C8] cfg/option.h:134-139 邏輯錯誤(per-game 設定被靜默刪除)
說明:save() 呼叫 doLoad(section,name) 傳入 value 當預設值,
若 global config 無該選項,doLoad() 回傳 value,
value==doLoad() 永遠 true→cfgDeleteEntry 刪除 per-game 條目,
使用者對預設值的修改遺失。
修復:新增 3 參數 doLoad(section, name, defValue) 重載(if constexpr),
save() 改為 doLoad(section, name, defaultValue)
狀態:已修復
[C9] maple_devs.cpp:601 資料錯位(錯誤的資料來源)
說明:VMU LCD block read 使用 wptr(flash_data,192),
應使用 wptr(lcd_data,192) 回傳 LCD 顯示內容而非 flash 儲存資料。
修復:flash_data → lcd_data(與 write path line 694 rptr(lcd_data,192) 一致)
狀態:已修復
[H1] emulator.cpp:489-493 狀態不一致
說明:init() 在 Error 狀態時 verify(state==Init) assert→crash,
無從 Error 恢復到 Init 的路徑。
修復:init() 加入 Error 狀態恢復:if (state == Error) { term(); state = Uninitialized; }
term() 條件改為 if (state == Init || state == Error)
狀態:已修復
[H8] serialize.cpp:139,151-154 狀態不一致
說明:ram_size 僅在 isConsole() 時序列化。若 save 在 console 建立、
load 在非 console 平台(或反之),後續所有欄位偏移→stream desync。
修復:Deserializer 與 Serializer 皆改為無條件 serialize(ram_size)
(if(_version >= V42)),確保所有平台 stream 佈局一致
狀態:已修復
[H12] ta_vtx.cpp:956-965 邏輯錯位
說明:CurrentPPlist==nullptr(wldkickspw hack)時 return,
但 CurrentPP 在此之前已指向可能失效的 context。
修復:將 if (CurrentPPlist == nullptr) return 移至函式開頭
狀態:已修復
[H13] maple_jvs.cpp:1044-1053,1123-1133 順序相依(side effect coupling)
說明:read_joystick_x() 修改成員 axis_y,read_joystick_y() 依賴此值,
若呼叫順序變更→Y axis 回傳 0。
修復:read_joystick_y() 改為獨立讀取 mapleInputState,
呼叫 limit_joystick_magnitude 後回傳,不依賴 read_joystick_x() 副作用
狀態:已修復
[H14] gdromv3.cpp:1309 狀態不一致
說明:SB_GDLEN==0 視為 0x02000000(line 1234),
SB_GDLEND 用 & 0x01ffffe0 mask wrap 在 0x01ffffe0,
與 SB_GDLEN(0) 永遠不相等→DMA 永不完成。
修復:mask 改為 0x01ffffff,允許 SB_GDLEND 達到 0x01ffffff
狀態:已修復
[H26] nullDC.cpp:134 狀態不一致
說明:dc_savestate() 在 save 成功前就 clear lastStateFile,
若 save 失敗,lastStateFile 已清空但檔案可能不完整。
修復:移除 line 134 的 lastStateFile.clear(),
改在 save 成功路徑(free(data) 後)加入
狀態:已修復
[H3] stdclass.cpp:27 邏輯錯誤
說明:file_exists() 使用 access(filename, R_OK) 而非 F_OK,
存在但不可讀時回傳 false。
修復:R_OK → F_OK;oslib/directory.h 新增 #define F_OK 0
狀態:已修復
[M1] sh4_cycles.cpp:443,479 邏輯錯誤(log 誤植)
說明:write function 內 log 寫 "Invalid read size" / "Invalid read to area 2",
應為 "Invalid write"。
修復:line 443 → "aica: Invalid write size";line 479 → "Invalid write to area 2"
狀態:已修復
[H21] vulkan_renderer.cpp:119-123 順序顛倒
說明:framebufferTexIndex %= GetSwapChainSize() 在 vector resize 前計算,
若 swap chain 變小,index 可能超出 vector 大小。
修復:將 framebufferTexIndex 計算移至 resize 之後
狀態:已修復
[H24] holly_intc.cpp:268,294 不一致
說明:Write_SB_ISTNRM<true> 無 &,Write_SB_ISTNRM<false> 有 &,
template argument deduction 可能選擇不同 overload。
修復:line 294 &Write_SB_ISTNRM<false> → Write_SB_ISTNRM<false>(統一無 &)
狀態:已修復
[M6] spg.cpp:20 初始化
說明:prv_cur_scanline = -1 (0xFFFFFFFF) sentinel 值,
首次 getNextSpgInterrupt() 計算差 1 scanline。
修復:prv_cur_scanline = -1 → prv_cur_scanline = 0
狀態:已修復
[H11] elan.cpp:1614,1627 未初始化
說明:schedId=-1(default),若 executeCommand() 在 init() 前被呼叫
(via write_elancmd memory write handler),sh4_sched_request(-1)。
修復:兩處 sh4_sched_request(schedId, 512) 前加入 if (schedId != -1)
狀態:已修復
[C2] emulator.cpp:842 UB(字串自我賦值 via c_str())
說明:settings.content.gameId = settings.content.gameId.c_str()
C++17 operator=(const char*) 可能在 realloc 時 invalidate 來源
指標→編譯器優化後產出錯誤結果。
修復:改為 std::string(settings.content.gameId.c_str()),
建立獨立暫存物件消除 aliasing UB,語意保持(截斷至首個 null)
狀態:已修復
[M5] ta_vtx.cpp:1041-1062 除零
說明:CaclulateSpritePlane 中 k3 = (AC_x * AB_y - AC_y * AB_x),
若 k3==0 或 AB_x==0 && AB_y==0 時除零→NaN。
修復:k3==0 時 k1/k2 設為 0;AB_x==0 && AB_y==0 時跳過(退化邊)
狀態:已修復
[M7] spg.cpp:105 除零
說明:(prv_cur_scanline + 1) % pvr_numscanlines,若 pvr_numscanlines==0 時除零。
修復:加入 if (pvr_numscanlines > 0) 保護
狀態:已修復
[M8] maple_devs.cpp:1075-1078 符號混淆
說明:s16 INC = (VIBSET>>24) & 0xFF,0-255 存入 s16 128-255 變負。
修復:u8 inc_raw = (VIBSET>>24) & 0xFF;INH 時 INC = -(s16)inc_raw;
非 INH 且非 EXH 時 INC = inc_raw
狀態:已修復
[H20] gl4/gles.cpp:326-333 缺少功能
說明:Two-volume mode area 1 的 palette texture 跳過 palettePixel 解碼
(只在 area1=false 分支呼叫),直接取 texcol。
修復:area1 分支改為依 pp_Palette 值選擇 texture/palettePixel/palettePixelBilinear,
使用 tex1 + vec3(vtx_uv1, 1.0) 或 vec3(vtx_uv1.xy, vtx_uv.z)
狀態:已修復
------------------------------------------------------------------------------
3.4 資源管理/錯誤處理
------------------------------------------------------------------------------
[H4] stdclass.cpp:241-247 邏輯錯誤
說明:RamRegion::free() 未重置 ownsMemory=false,重複呼叫時
freeAligned(nullptr)(若 data=nullptr)。
修復:free() 函式中 data=nullptr 後加入 ownsMemory = false
狀態:已修復
[H5] stdclass.cpp:234-239 邏輯錯誤
說明:RamRegion::alloc() 未檢查 allocAligned() 回傳值是否為 nullptr,
失敗時 data=nullptr 但 ownsMemory=true。
修復:allocAligned() 後加入 if (data == nullptr) throw std::bad_alloc()
狀態:已修復
[H7] nullDC.cpp:280 錯誤處理(catch 太窄)
說明:dc_loadstate() 只 catch Deserializer::Exception,
其他例外(bad_alloc、runtime_error)未處理→free(data) 不執行。
修復:改為 catch(const std::exception&)
(Deserializer::Exception 繼承 std::runtime_error,涵蓋於 std::exception)
狀態:已修復
[H25] nullDC.cpp:327-333 資源洩漏
說明:pngData.resize(header.pngSize) 若 throw bad_alloc,
fclose(f) 不執行→FILE* leak。
修復:fread + resize 區塊包入 try-catch,catch 中 fclose(f) 後 re-throw
狀態:已修復
[M10] emulator.cpp:945-946 錯誤處理
說明:getSh4Executor()->Start() 不在 try-catch 內,若 throw 則 state 停留在 Running。
修復:Start() 呼叫移入 try block 內,在 runInternal() 之前
狀態:已修復
------------------------------------------------------------------------------
3.5 型別/運算安全
------------------------------------------------------------------------------
[M3] ta_vtx.cpp:1235-1237 窄化轉換
說明:(int)vd_rc.render_passes.back().op_count 從 u32 轉 int,
若 >INT_MAX 變負數,與 size()(size_t)比較時轉為巨大值。
修復:移除 3 處 (int) cast,直接比較 u32 與 size()
狀態:已修復
[M2] ta_vtx.cpp:31 型別混用
說明:constexpr u32 ListType_None = -1,
-1 作為 u32 為 0xFFFFFFFF,比較語意不明確。
修復:改為 constexpr u32 ListType_None = 0xFFFFFFFF 並註明意圖
狀態:已修復
[H16] TexCache.cpp:876 UB(strict aliasing)
說明:(u8 *&)dest 從 u16* 轉 u8*& 違反 strict aliasing。
修復:內部存 u8*,讀取時 cast 為 u16*:
(u8 *&)dest += bytes → dest = reinterpret_cast<u16*>(reinterpret_cast<u8*>(dest) + bytes)
狀態:已修復
[M21] rec-x64\rec_x64.cpp:338-341 語意錯誤
說明:shop_mul_s64(有號 64-bit 乘法)使用 mul(無號)非 imul(有號),
目前因取 lower 64 bits 結果相同而正確,語意錯誤。
修復:mul(rcx) → imul(rcx)(lower 64 bits 結果相同,語意正確)
狀態:已修復
[M12] pvr_regs.cpp:91-107 執行緒安全
說明:regName() 回傳 static char[32] 指標,非 thread-safe。
修復:static char regName[32] → static thread_local char regName[32]
狀態:已修復
================================================================================
四、效能最佳化
================================================================================
------------------------------------------------------------------------------
4.1 檔案讀取/載入(I/O 路徑)
------------------------------------------------------------------------------
[M-4] 檔案讀取系統呼叫過多
問題:GDROM DMA 讀取 16 磁區產生 16 次 fread 系統呼叫
影響:16 次 syscall 替代 1 次,提高 I/O 開銷
修復:RawTrackFile::Read 新增 batch 版本,一次 fread(32KB)
狀態:已修復
[M-5] 檔案讀取中繼緩衝區
問題:原始格式 == 目標格式時,temp buffer memcpy 完全多餘
影響:每 DMA 額外 3-5μs
修復:Disc::ReadSectors fast path,skip temp buffer
狀態:已修復
[L-1] elan.cpp cache 污染
問題:Naomi2 紋理 DMA 使用普通 memcpy 污染 L2/L3 cache
影響:SH4 執行緒 cache 效率大幅下降
修復:改用 non-temporal store (_mm_stream_si128)
狀態:已修復
------------------------------------------------------------------------------
4.2 渲染/TA 管線
------------------------------------------------------------------------------
【最佳化 1】ta_thd_data32_i() — TA 資料接收瓶頸
目標:core/hw/pvr/ta.cpp:545-588
方法:非時態寫入 + 預取 + 批次展開 + PCW 讀取合併
修改:
1. 新增 simd256_t::stream_copy() 方法(非時態寫入)
2. 新增 #include <emmintrin.h>(x86 SSE2 標頭)
3. ta_thd_data32_i():使用 dst->stream_copy(data)、__builtin_prefetch、PCW 讀取合併
4. ta_vtx_data():批次展開從 4 次增加到 8 次
狀態:已修復
【最佳化 2】ReadFramebuffer() — 像素格式轉換 SIMD 化
目標:core/rend/TexCache.cpp:634-757
方法:SSE2 SIMD 加速 fbde_0555 和 fbde_565 格式的像素轉換
修改:
1. 新增 #include <emmintrin.h>、#include <type_traits>
2. fbde_0555 格式 SIMD 路徑:每次處理 4 個 16 位元像素
3. fbde_565 格式 SIMD 路徑:每次處理 4 個 16 位元像素
4. 標量回退路徑:保留原始程式碼處理剩餘像素
5. 條件編譯:#if HOST_CPU == CPU_X86
狀態:已修復
【最佳化 3】GLES Shader Uniform 迴圈 — 版本計數器延遲更新
目標:core/rend/gles/gles.cpp:1185-1190
方法:版本計數器機制,消除每幀 N 次 UseProgram + Set
修改:
1. PipelineShader 新增 u32 uniformVersion = 0
2. ShaderUniforms_t 新增 u32 version = 0
3. renderFrame 中 6 行迴圈取代為 ShaderUniforms.version++
4. SetGPState 中 UseProgram 後加延遲檢查
狀態:已修復
【最佳化 4】makeIndex / makePrimRestartIndex — reserve + is_vertex_inf 位元級最佳化
目標:core/hw/pvr/ta_util.cpp:520, 438, 29
方法:is_vertex_inf 位元級檢查 + idx 向量 reserve
修改:
1. is_vertex_inf:使用 float_bits() 輔助函式,6 浮點運算 → 2 整數 AND+比較
2. makeIndex/makePrimRestartIndex:函式開頭加 reserve(size + totalVerts*2)
狀態:已修復
【最佳化 5】AppendPolyVertex — verts/idx 初始 reserve 提升
目標:core/hw/pvr/ta_ctx.h:359-360
修改:rend.verts.reserve(32768) → 65536、rend.idx.reserve(32768) → 65536
理由:遊戲實測 vtx 最大 34,046、idx 最大 47,178,原始 32768 不足
狀態:已修復
【最佳化 6】Provoking Vertex 修正 — memcpy 合併
目標:core/rend/sorter.cpp:23
方法:將 4 次 4B memcpy 合併為 2 次 8B memcpy
修改:sizeof(vertex.col) + sizeof(vertex.spc) 合併複製
狀態:已修復
[#6] GLES u32→u16 索引轉換 — resize + 自動向量化
目標:core/rend/gles/gles.cpp:1090(upload_vertex_indices)
修改:
1. resize 替代 clear+reserve:單一呼叫設定大小
2. 直接賦值替代 push_back:消除每元素容量檢查分支
3. 緊密迴圈 + const 指標:GCC 13 可自動向量化(SSE2 pack 指令)
狀態:已修復
------------------------------------------------------------------------------
4.3 GPU/SwapChain/Vulkan
------------------------------------------------------------------------------
[M-1] vulkan buffer 膨脹
問題:imageCount 因 swapInterval 高乘數而過度配置 (240Hz: 12張)
影響:GPU 視訊記憶體浪費,延遲增加
修復:固定 imageCount = max(3, minImageCount) (120Hz: 3張, 240Hz: 3張)
狀態:已修復
[M-2] vblank polling 堵塞
問題:vblank 內 network IO polling loop 堵塞 100ms
影響:幀率崩跌至 ~10fps
修復:移除 100ms polling loop,使用單次 non-blocking 呼叫
狀態:已修復
[M-3] swapchain 資源重複配置
問題:CreateSwapChain 每次重建都從頭配置,oldSwapchain 未傳入
影響:重建延遲 + 記憶體峰值
修復:修改 createInfo.oldSwapchain = swapChain.get()
狀態:已修復
[L-2] sortTriangles 可能觸發 realloc
問題:static vector 在 trigCount 波動時觸發 realloc
影響:僅場景切換時有分配,但可能影響
修復:添加 capacity 檢查後再 resize
狀態:已修復
[L-3] n2uniforms 每幀 resize
問題:Naomi2 每幀 uniform buffer 分配
影響:uniform 記憶體頻繁分配
修復:static vector + reserve
狀態:已修復
================================================================================
五、程式碼品質/可維護性
================================================================================
[C-3] 紋理濾波重複邏輯
問題:5 處後端重複的采樣器濾波 if-else 邏輯
影響:程式碼冗餘,單點維護困難
修復:提取為 static inline isNearestFilter() 共用函式
狀態:已修復
[M14] TexCache.cpp:97-103 Dead code
說明:invalidate() 將 lock(auto&)設為 nullptr(via vramlock_list_remove),
if(lock!=nullptr) 永遠 false。
修復:移除 invalidate() 後的 if(lock!=nullptr) + ERROR_LOG + die 區塊
狀態:已修復
[M1] sh4_cycles.cpp:443,479 log 誤植 — 詳見「三、穩定性 3.3」
狀態:已修復
================================================================================
六、載入檔案相關.實質對策整理
================================================================================
------------------------------------------------------------------------------
6.1 高優先(掛起/錯誤資料/溢位)
------------------------------------------------------------------------------
[1] 引號解析無限迴圈 → 程式掛起 gdi.cpp / cue.cpp
位置:gdi.cpp L68-70、cue.cpp L148-150
問題:引號內遇到 EOF 時 for(;;) 永遠不結束,程式整個掛起。
對策:加入 EOF 檢查 `if (!(gdi >> last))
throw FlycastException(...)`,改為拋出異常。
狀態:已修復
[2] fread 截斷仍繼續解析 gdi.cpp / cue.cpp
位置:gdi.cpp L26-30、cue.cpp L70-74
問題:讀檔不完整時僅發 WARNING,卻繼續用截斷資料解析,造成後續錯誤。
對策:改為 throw FlycastException,不再沿用不完整資料。
狀態:已修復
[3] RawTrackFile 整數溢位(u32*u32) common.h
位置:common.h L242-251
問題:`first_fad * secfmt` 用 u32*u32 可能溢出,產生錯誤偏移量。
對策:改用 u64 運算後再轉 s32,並加 `verify(product <= (u64)UINT32_MAX)`。
狀態:已修復
[4] CDITrackFile 整數溢位 cdi.cpp
位置:cdi.cpp L16-23
問題:`first_fad * secfmt` u32*u32 可能溢出。
對策:改用 u64 運算後轉 s32,加 verify 哨兵檢查。
狀態:已修復
[5] Texture DMA base+size 越界 elan.cpp
位置:elan.cpp L1628 與 L1644
問題:原僅檢查 `link->size > VRAM_SIZE` 限總量,未檢查 `base+size` 越界;
寫入 VRAM 與 ELAN_RAM 可能超出容量。
對策:L1628 新增 `(vramAddress & VRAM_MASK) + size > VRAM_SIZE`;
L1644 再增 `(offset & ELAN_RAM_MASK) + size > ERAM_SIZE_MAX`。
狀態:已修復
[8] strict aliasing violation(未定義行為) common.cpp
位置:common.cpp L200-203
問題:`return *(u32 *)p;` 把 u8[4] 強轉為 u32*,違反 C++ 標準。
對策:改用 memcpy 避免 strict aliasing violation。
狀態:已修復
[10] DMA 32-bit path len 非 4 倍數造成無限迴圈 dmac.cpp(hw/sh4)
位置:dmac.cpp L88
問題:32-bit DMA 路徑 `while(len>0){ len-=4 }` 若 SB_C2DLEN 非 4 倍數
會無限迴圈(或大量反覆執行)。
對策:進入迴圈前 `len &= ~3u;` 向下對齊 4 的倍數,等同真實硬體忽略低位。
狀態:已修復
------------------------------------------------------------------------------
6.2 中優先(越界/邊界/型別安全性)
------------------------------------------------------------------------------
[6] 整數 <= 0 檢查位置錯誤 cdi.cpp
位置:cdi.cpp L173-178
問題:track.length <= 0 檢查在 Track 已建立並 push_back 之後才做,
不完整 track 會被加入磁碟結構。
對策:將檢查移到 push_back 之前。
狀態:已修復
[7] truncated track 處理邏輯錯誤 cdi.cpp
位置:cdi.cpp L180-189
問題:truncated track 時 SEEK_CUR 加上 track.total_length 卻沒乘
track.sector_size(非 truncated 時有乘),導致檔案定位錯誤。
對策:truncated 分支同樣乘以 sector_size。
狀態:已修復
[9] GDI SSIZE=0 產 null track gdi.cpp
位置:gdi.cpp L91-95
問題:SSIZE==0 時 t.file 為 nullptr 仍被 push_back,讀取時可能
解引用錯誤。
對策:SSIZE==0 時 throw FlycastException。
狀態:已修復
[11] DreamPicoPort words OOB 上限 dreampicoport.cpp
位置:dreampicoport.cpp L481-485
問題:msg.data 固定 1024byte、僅容 256 u32;原迴圈無上限保護,
第 257 個 word 寫入 dst[1024] 越界 1byte。
對策:`u32 dataWords = std::min(words.size()-1, sizeof(msg.data)/4);` 鎖住上限。
狀態:已修復
[12] isofs offset >= len 下溢 offset isofs.cpp
位置:isofs.cpp L131
問題:caller 以 offset > len 呼叫時 `len - offset` 下運算成 OOB 值。
對策:min() 前先 `if (offset >= len) return 0;`
狀態:已修復
[13] m1cartridge buffer_actual_size < 2 m1cartridge.cpp
位置:m1cartridge.cpp L37-38
問題:直接 `hist[0] = buffer[buffer_actual_size - 2];`
buffer_actual_size < 2 時負索引 / 越界。
對策:改為 `if (buffer_actual_size >= 2)` 先檢查。
狀態:已修復
[14] CHD cached_hunk 初值少一個 0?(哨兵差異) chd.cpp
位置:chd.cpp L23
問題:`u32 cached_hunk[2] = { 0xFFFFFFF, 0xFFFFFFF }` 少一個 F,
可能造成極端 false hit。
對策:改為 { 0xFFFFFFFF, 0xFFFFFFFF }。
狀態:已修復
[15] CHD C 式向下轉型 chd.cpp
位置:chd.cpp L276
問題:`((CHDTrack*)tracks.back().file)` 用 C 風格轉型,型別不安全。
對策:改用 static_cast<CHDTrack*>。
狀態:已修復
[16] GDI OFFSET 驗證順序 gdi.cpp
位置:gdi.cpp L106-112
問題:RawTrackFile 在 OFFSET 驗證之前建立,物件生命週期管理不佳。
對策:先驗證 OFFSET 再建立 RawTrackFile。
狀態:已修復
================================================================================
七、後端移植/平台建置
================================================================================
------------------------------------------------------------------------------
7.1 GLES/GL4 後端移植至新 core API
------------------------------------------------------------------------------
對策日期:2026-07-22
目標:core/rend/gles/ + core/rend/gl4/(整個 OpenGL/GLES 後端)
修改檔案(10 個):
CMakeLists.txt: USE_OPENGL 區塊加入 gles/gl4 原始碼 + 編譯定義 + opengl32
11.bat: USE_OPENGL=OFF → ON
wsi/context.h: 加 virtual void swap() {}
wsi/sdl.h: swap() → swap() override
gles/gles.h: TransformMatrix → TransformMatrix<COORD_OPENGL>
gles/gles.cpp: FlycastException×3, GLGraphicsContext cast, 移除swapInterval
gles/gldraw.cpp: setTileClip用GetTileClip, Rect→int[4], writeFramebufferToVRAM重寫
gles/gltex.cpp: fbClip→fb_X/Y_CLIP, WriteTextureToVRam去除clip參數
gl4/gldraw.cpp: FlycastException, setTileClip新API, Rect→int[4]
gl4/gles.cpp: getBaseScissor新API
解決的 12 個不相容點:
1. TransformMatrix 模板化
2. Rect 型別消除(改用 int clip_rect[4])
3. getTileClip → GetTileClip 自由函式
4. getBaseScissor → GetScissorMatrix + fb_X/Y_CLIP 計算
5. GraphicsContext 版本方法
6. swap() → GraphicsContext 加虛擬方法
7. setSwapInterval → 移除
8. globClip → ta_GLOB_TILE_CLIP 計算
9. fbClip → fb_X_CLIP/fb_Y_CLIP
10. swapInterval → 移除
11. RendererException → FlycastException
12. getWriteFBToVramParams/writeFramebufferToVRAM → 重寫
------------------------------------------------------------------------------
7.2 UploadToGPUGl4 immutable storage 尺寸/格式變化檢查缺失修復
------------------------------------------------------------------------------
目標:core/rend/gles/gltex.cpp:97-105
問題:UploadToGPUGl4 使用 glTexStorage2D 建立不可變紋理,但只在 texID == 0 時建立,
後續紋理更新時不檢查尺寸/格式/mipmapLevels 是否一致
修改:
1. gles.h — TextureCacheData 加入 4 個 storage 追蹤成員變數
2. gltex.cpp — UploadToGPUGl4 加入屬性變化檢查 + 刪舊建新邏輯
================================================================================
八、未修復項目(需外部配合)
================================================================================
[M-08] 未加密 WebSocket (ice.cpp:234, ws:// → wss://)
原因:需伺服器端(lobby.flyca.st)支援 wss://
[M-09] 硬編碼 TURN 憑證 (ice.cpp:436)
原因:需伺服器端 REST API 發放短期憑證
[M-10] NaomiNetwork 競態條件 (naomi_network.h)
原因:需架構調整(_startNow 改為 atomic)
================================================================================
附註
================================================================================
- 原始記錄修復日期:2026-07-24、2026-07-28、2026-07-29
- 已修復項目標記「狀態:已修復」;未修復者明確標示原因。
- 總計整理約 100 餘項。
================================================================================
|
|