rlm@87: (ns com.aurellem.gb-driver rlm@87: (:import com.aurellem.gb.Gb) rlm@87: (:import java.io.File) rlm@87: (:import org.apache.commons.io.FileUtils) rlm@87: (:import (java.nio IntBuffer ByteOrder))) rlm@87: rlm@87: (Gb/loadVBA) rlm@87: rlm@87: (def ^:dynamic *max-history* 2e4) rlm@87: rlm@87: (def ^:dynamic *backup-saves-to-disk* true) rlm@87: rlm@87: (def ^:dynamic *save-history* true) rlm@87: rlm@87: (def ^:dynamic *save-state-cache* rlm@87: (File. "/home/r/proj/pokemon-escape/save-states/")) rlm@87: rlm@87: (def yellow-rom-image rlm@87: (File. "/home/r/proj/pokemon-escape/roms/yellow.gbc")) rlm@87: rlm@87: (def yellow-save-file rlm@87: (File. "/home/r/proj/pokemon-escape/roms/yellow.sav")) rlm@87: rlm@87: (def current-frame (atom 0)) rlm@87: rlm@87: (defn vba-init [] rlm@87: (reset! current-frame 0) rlm@87: (.delete yellow-save-file) rlm@87: (Gb/startEmulator (.getCanonicalPath yellow-rom-image))) rlm@87: rlm@87: (defn shutdown [] (Gb/shutdown)) rlm@87: rlm@87: (defn reset [] (shutdown) (vba-init)) rlm@87: rlm@87: (defn cpu-data [size arr-fn] rlm@87: (let [store (int-array size)] rlm@87: (fn [] (arr-fn store) store))) rlm@87: rlm@87: (def ram rlm@87: (cpu-data (Gb/getRAMSize) #(Gb/getRAM %))) rlm@87: rlm@87: (def rom rlm@87: (cpu-data (Gb/getROMSize) #(Gb/getROM %))) rlm@87: rlm@87: (def working-ram rlm@87: (cpu-data Gb/WRAM_SIZE #(Gb/getWRAM %))) rlm@87: rlm@87: (def video-ram rlm@87: (cpu-data Gb/VRAM_SIZE #(Gb/getVRAM %))) rlm@87: rlm@87: (def registers rlm@87: (cpu-data Gb/NUM_REGISTERS #(Gb/getRegisters %))) rlm@87: rlm@87: (def button-code rlm@87: {;; main buttons rlm@87: :a 0x0001 rlm@87: :b 0x0002 rlm@87: rlm@87: ;; directional pad rlm@87: :r 0x0010 rlm@87: :l 0x0020 rlm@87: :u 0x0040 rlm@87: :d 0x0080 rlm@87: rlm@87: ;; meta buttons rlm@87: :select 0x0004 rlm@87: :start 0x0008 rlm@87: rlm@87: ;; hard reset -- not really a button rlm@87: :reset 0x0800}) rlm@87: rlm@87: (defn button-mask [buttons] rlm@87: (reduce bit-or 0x0000 (map button-code buttons))) rlm@87: rlm@87: (defn buttons [mask] rlm@87: (loop [buttons [] rlm@87: masks (seq button-code)] rlm@87: (if (empty? masks) buttons rlm@87: (let [[button value] (first masks)] rlm@87: (if (not= 0x0000 (bit-and value mask)) rlm@87: (recur (conj buttons button) (rest masks)) rlm@87: (recur buttons (rest masks))))))) rlm@87: rlm@87: (defrecord SaveState [frame save-data]) rlm@87: rlm@87: (defn frame [] @current-frame) rlm@87: rlm@87: (defn save-state [] rlm@87: (SaveState. (frame) (Gb/saveState))) rlm@87: rlm@87: (defn load-state [#^SaveState save] rlm@87: (reset! current-frame (:frame save)) rlm@87: (Gb/loadState (:save-data save))) rlm@87: rlm@87: (def empty-history (sorted-map)) rlm@87: rlm@87: (def history (atom empty-history)) rlm@87: rlm@87: (defn frame->disk-save [frame] rlm@87: (File. *save-state-cache* rlm@87: (format "%07d.sav" frame))) rlm@87: rlm@87: (defn get-save-from-disk [frame] rlm@87: (let [save (frame->disk-save frame)] rlm@87: (if (.exists save) rlm@87: (let [buf (Gb/saveBuffer) rlm@87: bytes (FileUtils/readFileToByteArray save)] rlm@87: (.put buf bytes) rlm@87: (.flip buf) rlm@87: (SaveState. frame buf))))) rlm@87: rlm@87: (defn store-save-to-disk [^SaveState save] rlm@87: (let [buf (:save-data save) rlm@87: bytes (byte-array (.limit buf)) rlm@87: dest (frame->disk-save (:frame save))] rlm@87: (.get buf bytes) rlm@87: (FileUtils/writeByteArrayToFile dest bytes) rlm@87: (.rewind buf) dest)) rlm@87: rlm@87: (defn find-save-state [frame] rlm@87: (let [save (@history frame)] rlm@87: (if (not (nil? save)) save rlm@87: (get-save-from-disk frame)))) rlm@87: rlm@87: (defn goto [frame] rlm@87: (let [save (find-save-state frame)] rlm@87: (if (nil? save) rlm@87: (println frame "is not in history") rlm@87: (do rlm@87: (reset! current-frame frame) rlm@87: (load-state save))))) rlm@87: rlm@87: (defn clear-history [] (reset! history empty-history)) rlm@87: rlm@87: (defn rewind rlm@87: ([] (rewind 1)) rlm@87: ([n] (goto (- @current-frame n)))) rlm@87: rlm@87: (defn backup-state rlm@87: ([] (backup-state (frame))) rlm@87: ([frame] rlm@87: (let [save (save-state)] rlm@87: (swap! history #(assoc % frame save)) rlm@87: ;;(store-save-to-disk save) rlm@87: (if (> (count @history) *max-history*) rlm@87: (swap! history #(dissoc % (first (first %)))))))) rlm@87: rlm@87: (defn advance [] rlm@87: (if *save-history* rlm@87: (backup-state @current-frame)) rlm@87: (swap! current-frame inc)) rlm@87: rlm@87: (defn step rlm@87: ([] (advance) (Gb/step)) rlm@87: ([mask-or-buttons] rlm@87: (advance) rlm@87: (if (number? mask-or-buttons) rlm@87: (Gb/step mask-or-buttons) rlm@87: (Gb/step (button-mask mask-or-buttons))))) rlm@87: rlm@87: (defn play-moves rlm@87: ([start moves] rlm@87: (goto start) rlm@87: (dorun (map step moves)) rlm@87: (backup-state) rlm@87: (frame)) rlm@87: ([moves] rlm@87: (dorun (map step moves)) rlm@87: (backup-state) rlm@87: (frame))) rlm@87: rlm@87: (defn play rlm@87: ([] (play Integer/MAX_VALUE)) rlm@87: ([n] (dorun (dotimes [_ n] (step))))) rlm@87: rlm@87: (defmacro without-saves [& forms] rlm@87: `(binding [*save-history* false] rlm@87: ~@forms)) rlm@87: rlm@87: rlm@87: (require '(clojure [zip :as zip])) rlm@87: rlm@87: rlm@87: rlm@87: rlm@87: (defn tree->str [original] rlm@87: (loop [s ".\n" loc (zip/down (zip/seq-zip (seq original)))] rlm@87: (if (zip/end? loc) s rlm@87: (let [d (count (zip/path loc)) rlm@87: rep rlm@87: (str rlm@87: s rlm@87: (if (and (zip/up loc) rlm@87: (> (count (-> loc zip/up zip/rights)) 0)) rlm@87: "|" "") rlm@87: (apply str (repeat (dec d) " ")) rlm@87: (if (= (count (zip/rights loc)) 0) rlm@87: "`-- " rlm@87: "|-- ") rlm@87: (zip/node loc) rlm@87: "\n")] rlm@87: (recur rep (zip/next loc)))))) rlm@87: rlm@87: rlm@87: rlm@87: