From f889a47e297c7e1c0786c95c98e7aed164f176d9 Mon Sep 17 00:00:00 2001 From: yotti Date: Thu, 7 May 2026 21:34:00 +0900 Subject: [PATCH] Add configurable freeze interval The freeze write interval was hardcoded at 100ms. This adds the ability to configure it via CLI (17i), REST API (POST /api/memory/freeze-interval), and Web UI. New addresses frozen after the change use the new interval. --- internal/memory/modify/freeze.go | 31 +++++++++++++++++++++++++------ internal/server/handlers.go | 17 +++++++++++++++++ internal/server/server.go | 1 + internal/server/static/index.html | 10 ++++++++++ main.go | 17 +++++++++++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/internal/memory/modify/freeze.go b/internal/memory/modify/freeze.go index 1dbe85e..b8bc463 100644 --- a/internal/memory/modify/freeze.go +++ b/internal/memory/modify/freeze.go @@ -9,7 +9,7 @@ import ( "memodroid/internal/memory/search" ) -const freezeInterval = 100 * time.Millisecond +const defaultFreezeInterval = 100 * time.Millisecond type freezeEntry struct { drv driver.Driver @@ -21,15 +21,33 @@ type freezeEntry struct { // Freezer manages a set of frozen addresses. type Freezer struct { - mu sync.Mutex - entries map[uintptr]*freezeEntry + mu sync.Mutex + entries map[uintptr]*freezeEntry + interval time.Duration } func NewFreezer() *Freezer { - return &Freezer{entries: make(map[uintptr]*freezeEntry)} + return &Freezer{ + entries: make(map[uintptr]*freezeEntry), + interval: defaultFreezeInterval, + } +} + +// SetInterval changes the freeze write interval for newly frozen addresses. +func (f *Freezer) SetInterval(d time.Duration) { + f.mu.Lock() + defer f.mu.Unlock() + f.interval = d +} + +// GetInterval returns the current freeze interval. +func (f *Freezer) GetInterval() time.Duration { + f.mu.Lock() + defer f.mu.Unlock() + return f.interval } -// Freeze starts a goroutine that repeatedly writes value to addr every 100ms. +// Freeze starts a goroutine that repeatedly writes value to addr at the configured interval. // Returns an error if addr is already frozen. func (f *Freezer) Freeze(drv driver.Driver, pid int, addr uintptr, value []byte) error { f.mu.Lock() @@ -42,11 +60,12 @@ func (f *Freezer) Freeze(drv driver.Driver, pid int, addr uintptr, value []byte) val := make([]byte, len(value)) copy(val, value) + iv := f.interval e := &freezeEntry{drv: drv, pid: pid, addr: addr, value: val, stop: make(chan struct{})} f.entries[addr] = e go func() { - ticker := time.NewTicker(freezeInterval) + ticker := time.NewTicker(iv) defer ticker.Stop() for { select { diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 0fe6492..b65c5f5 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strconv" + "time" "memodroid/internal/app" "memodroid/internal/driver/adb" @@ -522,6 +523,22 @@ func (h *handler) memoryFreeze(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"ok": true}) } +func (h *handler) freezeSetInterval(w http.ResponseWriter, r *http.Request) { + var req struct { + IntervalMs int `json:"interval_ms"` + } + if err := decode(r, &req); err != nil { + writeError(w, 400, err.Error()) + return + } + if req.IntervalMs <= 0 { + writeError(w, 400, "interval_ms must be positive") + return + } + h.state.Freezer.SetInterval(time.Duration(req.IntervalMs) * time.Millisecond) + writeJSON(w, map[string]any{"ok": true, "interval_ms": req.IntervalMs}) +} + func (h *handler) memoryFreezeAll(w http.ResponseWriter, _ *http.Request) { sess := h.state.GetSession() if sess == nil || !sess.HasCandidates() { diff --git a/internal/server/server.go b/internal/server/server.go index a35a594..6415d98 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -76,6 +76,7 @@ func Start(addr string, state *app.State, d *adb.ADB) error { mux.HandleFunc("/api/memory/modify", h.memoryModify) mux.HandleFunc("/api/memory/undo", h.memoryUndo) mux.HandleFunc("/api/memory/freeze", h.memoryFreeze) + mux.HandleFunc("/api/memory/freeze-interval", h.freezeSetInterval) mux.HandleFunc("/api/memory/freeze-all", h.memoryFreezeAll) mux.HandleFunc("/api/memory/unfreeze", h.memoryUnfreeze) mux.HandleFunc("/api/memory/frozen", h.memoryFrozen) diff --git a/internal/server/static/index.html b/internal/server/static/index.html index 0e10775..ec71cd5 100644 --- a/internal/server/static/index.html +++ b/internal/server/static/index.html @@ -391,6 +391,10 @@ +
+ + +
@@ -793,6 +797,12 @@ log(r.error||'Unfrozen', !r.error); loadFrozen(); } +async function setFreezeInterval() { + const ms = parseInt(document.getElementById('freeze-interval').value); + if (!ms || ms <= 0) { log('Invalid interval', false); return; } + const r = await api('/memory/freeze-interval', {interval_ms: ms}); + log(r.error || `Freeze interval: ${ms}ms`, !r.error); +} // ── Maps ───────────────────────────────────────────── let allMaps = []; diff --git a/main.go b/main.go index bee0e7c..3e49292 100644 --- a/main.go +++ b/main.go @@ -595,6 +595,22 @@ func main() { } else { fmt.Printf("Freezing 0x%x\n", addr) } + case "17i": + s := prompt(fmt.Sprintf("Freeze interval [current: %v]: ", st.Freezer.GetInterval())) + if s == "" { + continue + } + d, err := time.ParseDuration(s) + if err != nil { + fmt.Println("Invalid duration (e.g. 50ms, 200ms, 1s)") + continue + } + if d <= 0 { + fmt.Println("Interval must be positive") + continue + } + st.Freezer.SetInterval(d) + fmt.Printf("Freeze interval set to %v\n", d) case "17a": if requireSession(sess) { count := st.Freezer.FreezeAllCandidates(drv, sess) @@ -782,6 +798,7 @@ func printMenu(st *app.State, d *adb.ADB) { fmt.Println(" 15. Modify Address") fmt.Println(" 16. Undo Last Modify") fmt.Println(" 17. Freeze Address") + fmt.Println("17i. Set Freeze Interval") fmt.Println("17a. Freeze All Candidates") fmt.Println(" 18. Unfreeze Address") fmt.Println(" 19. List Frozen")