Add fake-memory-reader tests for the scanner/poller
process.go's boundary functions (readMemory, findModuleBase, productVersion, findProcessID, openProcess, closeProcessHandle) become swappable package variables, same pattern as testExeDir in totals.go. process_test.go builds a fakeProcess (an in-memory buffer addressed like real process memory) to exercise signature scanning, scanModule's chunk-overlap logic, PlayerIns confirmation by memory read (the path Linux always takes), the save-slot read, the double-read name confirmation rule, and rejection of raw-pointer-as-UTF16 garbage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ba312a833
commit
66aba2fff2
3 files changed
+321
-25
No files matched your search
+28
-15
@@ -31,6 +31,19 @@ const processName = "eldenring.exe"
|
||||
// process_linux.go).
|
||||
type procHandle uintptr
|
||||
|
||||
// Boundary functions as package variables, defaulting to the platform
|
||||
// implementation compiled into process_windows.go/process_linux.go.
|
||||
// Tests (process_test.go) swap these for a fake in-memory reader — same
|
||||
// pattern as testExeDir in totals.go, not a new interface.
|
||||
var (
|
||||
findProcessIDFn = findProcessID
|
||||
openProcessFn = openProcess
|
||||
closeProcessHandleFn = closeProcessHandle
|
||||
readMemoryFn = readMemory
|
||||
findModuleBaseFn = findModuleBase
|
||||
productVersionFn = productVersion
|
||||
)
|
||||
|
||||
// ------------------------- signature scans -------------------------
|
||||
//
|
||||
// Two signatures, both the same shape: a 7-byte "mov reg,[rip+disp32]"
|
||||
@@ -111,7 +124,7 @@ func scanModule(h procHandle, base uintptr, size uint32, patterns [][]patByte) [
|
||||
if rem := int(size - pos); readSize > rem {
|
||||
readSize = rem
|
||||
}
|
||||
buf, ok := readMemory(h, base+uintptr(pos), readSize)
|
||||
buf, ok := readMemoryFn(h, base+uintptr(pos), readSize)
|
||||
if ok {
|
||||
for i := 0; i < len(buf); i++ {
|
||||
for p := range patterns {
|
||||
@@ -140,7 +153,7 @@ func ripSlot(h procHandle, matchAddr uintptr) (uintptr, error) {
|
||||
return 0, fmt.Errorf("pattern not found")
|
||||
}
|
||||
codeLocation := matchAddr + 3 // the first 3 bytes are the opcode
|
||||
dispBytes, ok := readMemory(h, codeLocation, 4)
|
||||
dispBytes, ok := readMemoryFn(h, codeLocation, 4)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("couldn't read the RIP-relative displacement")
|
||||
}
|
||||
@@ -178,12 +191,12 @@ type gamePointers struct {
|
||||
func resolvePointers(h procHandle, pid uint32) (gamePointers, error) {
|
||||
var gp gamePointers
|
||||
|
||||
base, size, exePath, err := findModuleBase(pid, processName)
|
||||
base, size, exePath, err := findModuleBaseFn(pid, processName)
|
||||
if err != nil {
|
||||
return gp, err
|
||||
}
|
||||
|
||||
major, minor, label, okVer := productVersion(exePath)
|
||||
major, minor, label, okVer := productVersionFn(exePath)
|
||||
gp.playerInsTried = playerInsCandidates(major, minor, okVer)
|
||||
gp.playerInsOffset = gp.playerInsTried[0]
|
||||
gp.versionLabel = label
|
||||
@@ -216,7 +229,7 @@ func derefPointer(h procHandle, slot uintptr) (uintptr, bool) {
|
||||
if slot == 0 {
|
||||
return 0, false
|
||||
}
|
||||
buf, ok := readMemory(h, slot, 8)
|
||||
buf, ok := readMemoryFn(h, slot, 8)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
@@ -257,7 +270,7 @@ func readCharName(h procHandle, gameDataMan uintptr, c nameCandidate) (string, b
|
||||
}
|
||||
base = p
|
||||
}
|
||||
buf, ok := readMemory(h, base+c.nameOffset, charNameMaxChars*2)
|
||||
buf, ok := readMemoryFn(h, base+c.nameOffset, charNameMaxChars*2)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
@@ -316,7 +329,7 @@ func readSaveSlot(h procHandle, gp gamePointers) int {
|
||||
if !ok || gameMan == 0 {
|
||||
return -1
|
||||
}
|
||||
buf, ok := readMemory(h, gameMan+saveSlotOffset, 1)
|
||||
buf, ok := readMemoryFn(h, gameMan+saveSlotOffset, 1)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
@@ -365,7 +378,7 @@ func isPlayerLoaded(h procHandle, gp *gamePointers) (loaded bool, known bool) {
|
||||
}
|
||||
// A real pointer points at mapped memory; a garbage one almost
|
||||
// never survives this read.
|
||||
if _, ok := readMemory(h, playerIns, 8); !ok {
|
||||
if _, ok := readMemoryFn(h, playerIns, 8); !ok {
|
||||
continue
|
||||
}
|
||||
gp.playerInsOffset = cand
|
||||
@@ -436,7 +449,7 @@ func pollLoop() {
|
||||
|
||||
closeHandle := func() {
|
||||
if handle != 0 {
|
||||
closeProcessHandle(handle)
|
||||
closeProcessHandleFn(handle)
|
||||
handle = 0
|
||||
}
|
||||
pid = 0
|
||||
@@ -449,13 +462,13 @@ func pollLoop() {
|
||||
|
||||
for {
|
||||
if handle == 0 {
|
||||
newPid, err := findProcessID(processName)
|
||||
newPid, err := findProcessIDFn(processName)
|
||||
if err != nil {
|
||||
st.setDisconnected("waiting for eldenring.exe")
|
||||
time.Sleep(3 * time.Second)
|
||||
continue
|
||||
}
|
||||
h, err := openProcess(newPid)
|
||||
h, err := openProcessFn(newPid)
|
||||
if err != nil {
|
||||
st.setDisconnected("couldn't open the process (permissions?): " + err.Error())
|
||||
time.Sleep(3 * time.Second)
|
||||
@@ -472,7 +485,7 @@ func pollLoop() {
|
||||
st.setDisconnected(err.Error())
|
||||
time.Sleep(2 * time.Second)
|
||||
// if the process died, release the handle to retry from scratch
|
||||
if _, ferr := findProcessID(processName); ferr != nil {
|
||||
if _, ferr := findProcessIDFn(processName); ferr != nil {
|
||||
closeHandle()
|
||||
}
|
||||
continue
|
||||
@@ -510,7 +523,7 @@ func pollLoop() {
|
||||
// and our detection was lying. Unlike a timeout, this can't
|
||||
// fire just from leaving the game sitting at the menu a while.
|
||||
if gdm, ok := derefPointer(handle, gp.gameDataManSlot); ok && gdm != 0 {
|
||||
if buf, ok := readMemory(handle, gdm+0x94, 4); ok {
|
||||
if buf, ok := readMemoryFn(handle, gdm+0x94, 4); ok {
|
||||
raw := int32(binary.LittleEndian.Uint32(buf))
|
||||
if raw >= 0 && raw < 1_000_000 {
|
||||
if haveUnloadedRaw {
|
||||
@@ -562,8 +575,8 @@ func pollLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
deathsBuf, ok1 := readMemory(handle, gameDataMan+0x94, 4)
|
||||
bossBuf, ok2 := readMemory(handle, gameDataMan+0xC0, 1)
|
||||
deathsBuf, ok1 := readMemoryFn(handle, gameDataMan+0x94, 4)
|
||||
bossBuf, ok2 := readMemoryFn(handle, gameDataMan+0xC0, 1)
|
||||
if !ok1 {
|
||||
st.setDisconnected("lost the memory reading (the game closed or restarted)")
|
||||
closeHandle()
|
||||
|
||||
Reference in new issue
Block a user