Reads the game's process memory read-only (AOB signature scanning, re-resolved every tick — never a cached pointer) and serves a live death counter as a browser-source overlay plus a status panel. Supports co-op: each player runs the program locally, one acts as hub. The peer link is authenticated (HMAC challenge/response, no token on the wire, constant-time compare) but not yet encrypted — see CLAUDE.md's TODO section for the planned TLS+pinning split. Zero external dependencies — including a hand-rolled WebSocket implementation — so the whole thing ships as one .exe.
114 lines
3.2 KiB
Go
114 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// cada test corre en su propio directorio: totals.json y offset.txt
|
|
// se resuelven contra el ejecutable, asi que lo apuntamos al temporal.
|
|
func fresh(t *testing.T, legacy string) {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
testExeDir = dir
|
|
if legacy != "" {
|
|
if err := os.WriteFile(filepath.Join(dir, "offset.txt"), []byte(legacy), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
totals = newTotalsStore()
|
|
st = state{}
|
|
st.init(totals)
|
|
}
|
|
|
|
func total(t *testing.T) int64 { t.Helper(); return st.snapshot().Total }
|
|
|
|
// EL CASO DE EMMA: cambiar de personaje tiene que mover el numero YA,
|
|
// sin esperar a que alguien se muera.
|
|
func TestCambioDePersonajeActualizaAlInstante(t *testing.T) {
|
|
fresh(t, "")
|
|
|
|
st.setCharacter(0, "emma", 40)
|
|
st.setReading(40, false)
|
|
if got := total(t); got != 40 {
|
|
t.Fatalf("emma deberia arrancar con sus 40 muertes, dio %d", got)
|
|
}
|
|
|
|
// se va al menu y carga otro personaje, con otro conteo propio
|
|
st.setPlayerUnloaded("menu")
|
|
st.setCharacter(1, "emmatherock", 7)
|
|
if got := total(t); got != 7 {
|
|
t.Fatalf("al cargar emmatherock el total tenia que saltar a 7 SIN morir, dio %d", got)
|
|
}
|
|
st.setReading(7, false)
|
|
if got := total(t); got != 7 {
|
|
t.Fatalf("tras la lectura deberia seguir en 7, dio %d", got)
|
|
}
|
|
|
|
// y volver muestra lo que el juego lleva para emma
|
|
st.setPlayerUnloaded("menu")
|
|
st.setCharacter(0, "emma", 41)
|
|
st.setReading(41, false)
|
|
if got := total(t); got != 41 {
|
|
t.Fatalf("volviendo a emma deberia dar 41, dio %d", got)
|
|
}
|
|
}
|
|
|
|
// Dos personajes con el MISMO nombre no se tienen que mezclar.
|
|
func TestHomonimosNoSeMezclan(t *testing.T) {
|
|
fresh(t, "")
|
|
st.setCharacter(0, "Tarnished", 100)
|
|
st.setReading(100, false)
|
|
st.setCharacter(3, "Tarnished", 5)
|
|
st.setReading(5, false)
|
|
if got := total(t); got != 5 {
|
|
t.Fatalf("el del slot 3 deberia dar 5, dio %d", got)
|
|
}
|
|
if c, ok := totals.get("slot0"); !ok || c.Total != 100 {
|
|
t.Fatalf("el del slot 0 deberia seguir en 100, dio %+v", c)
|
|
}
|
|
}
|
|
|
|
// El total es el del juego, sin importar que haya un offset.txt viejo
|
|
// dando vueltas al lado del programa.
|
|
func TestIgnoraElOffsetViejo(t *testing.T) {
|
|
fresh(t, "124")
|
|
st.setCharacter(0, "emma", 30)
|
|
st.setReading(30, false)
|
|
if got := total(t); got != 30 {
|
|
t.Fatalf("deberia mostrar las 30 del juego y no las 124 del archivo viejo, dio %d", got)
|
|
}
|
|
}
|
|
|
|
// Sin poder leer el slot, cae al nombre y sigue funcionando.
|
|
func TestSinSlotCaeAlNombre(t *testing.T) {
|
|
fresh(t, "")
|
|
st.setCharacter(-1, "emma", 40)
|
|
st.setReading(40, false)
|
|
if got := total(t); got != 40 {
|
|
t.Fatalf("deberia dar 40, dio %d", got)
|
|
}
|
|
if _, ok := totals.get("name:emma"); !ok {
|
|
t.Fatal("deberia haberlo guardado por nombre")
|
|
}
|
|
}
|
|
|
|
// Al reabrir el programa con el juego cerrado, muestra el ultimo
|
|
// personaje conocido en vez de un cero.
|
|
func TestRecuerdaElUltimoAlReabrir(t *testing.T) {
|
|
fresh(t, "")
|
|
st.setCharacter(2, "emma", 88)
|
|
st.setReading(88, false)
|
|
|
|
totals = newTotalsStore() // simula reabrir el programa
|
|
st = state{}
|
|
st.init(totals)
|
|
if got := total(t); got != 88 {
|
|
t.Fatalf("al reabrir deberia mostrar 88, dio %d", got)
|
|
}
|
|
if st.snapshot().CharName != "emma" {
|
|
t.Fatalf("y recordar el nombre, dio %q", st.snapshot().CharName)
|
|
}
|
|
}
|