rlm@145: (ns com.aurellem.gb.gb-driver rlm@145: (:import com.aurellem.gb.Gb) rlm@145: (:import java.io.File) rlm@526: (:import javax.sound.sampled.AudioFormat) rlm@145: (:import org.apache.commons.io.FileUtils) rlm@145: (:import (java.nio IntBuffer ByteOrder))) rlm@145: rlm@145: ;; Savestates ocsenave@227: rlm@145: (defrecord SaveState [data]) rlm@145: rlm@145: (def user-home (File. (System/getProperty "user.home"))) rlm@145: rlm@145: (def ^:dynamic *save-state-cache* rlm@145: (File. user-home "proj/vba-clojure/save-states/")) rlm@145: rlm@145: (def current-state (atom nil)) rlm@145: rlm@145: (defn state-cache-file [name] rlm@145: (File. *save-state-cache* (str name ".sav"))) rlm@145: rlm@145: (defn write-state! rlm@145: ([^SaveState name] rlm@145: (write-state! @current-state name)) rlm@145: ([^SaveState save ^String name] rlm@145: (let [buffer (:data save) rlm@145: bytes (byte-array (.limit buffer)) rlm@145: dest (state-cache-file name)] rlm@145: (.get buffer bytes) rlm@145: (FileUtils/writeByteArrayToFile dest bytes) rlm@145: (.rewind buffer) rlm@145: dest))) rlm@145: rlm@145: (defn read-state [name] rlm@145: (let [save (state-cache-file name)] rlm@154: (assert (.exists save)) rlm@154: (let [buffer (Gb/saveBuffer) rlm@154: bytes (FileUtils/readFileToByteArray save)] rlm@154: (.put buffer bytes) rlm@154: (.flip buffer) rlm@154: (SaveState. buffer)))) rlm@145: ;;;;;;;;;;;;;;;; rlm@145: rlm@496: rlm@145: ;; Gameboy management rlm@145: (Gb/loadVBA) rlm@145: rlm@145: (def yellow-rom-image rlm@145: (File. user-home "proj/pokemon-escape/roms/yellow.gbc")) rlm@145: rlm@145: (def yellow-save-file rlm@145: (File. user-home "proj/pokemon-escape/roms/yellow.sav")) rlm@145: rlm@145: (def on? (atom nil)) rlm@145: rlm@145: (defn shutdown! [] (Gb/shutdown) (reset! on? false)) rlm@145: rlm@145: (defn restart! [] rlm@145: (shutdown!) rlm@145: (.delete yellow-save-file) rlm@145: (Gb/startEmulator (.getCanonicalPath yellow-rom-image)) rlm@145: (reset! on? true)) rlm@145: rlm@145: ;;; The first state! rlm@145: (defn gen-root! [] rlm@145: (restart!) rlm@145: (let [state (SaveState. (Gb/saveState))] rlm@145: (write-state! state "root" ) state)) rlm@145: rlm@145: (defn root [] rlm@145: (if (.exists (state-cache-file "root")) rlm@145: (read-state "root") rlm@145: (gen-root!))) rlm@145: rlm@145: ;;;; Press Buttons rlm@145: rlm@145: (def button-code rlm@145: {;; main buttons rlm@145: :a 0x0001 rlm@145: :b 0x0002 rlm@145: rlm@145: ;; directional pad rlm@145: :r 0x0010 rlm@145: :l 0x0020 rlm@145: :u 0x0040 rlm@145: :d 0x0080 rlm@145: rlm@145: ;; meta buttons rlm@145: :select 0x0004 rlm@145: :start 0x0008 rlm@145: rlm@145: ;; pseudo-buttons rlm@145: :restart 0x0800 ; hard reset rlm@145: :listen -1 ; listen for user input rlm@145: }) rlm@145: rlm@145: (defn button-mask [buttons] rlm@145: (reduce bit-or 0x0000 (map button-code buttons))) rlm@145: rlm@145: (defn set-state! [^SaveState state] rlm@145: (assert (:data state) "Not a valid state!") rlm@145: (if (not @on?) (restart!)) rlm@145: (if (not= state @current-state) rlm@145: (do rlm@145: (Gb/loadState (:data state)) rlm@145: (reset! current-state state)))) rlm@145: rlm@145: (defn update-state [] rlm@145: (reset! current-state rlm@145: (SaveState. (Gb/saveState)))) rlm@145: rlm@145: (defn step rlm@145: ([^SaveState state buttons] rlm@145: (set-state! state) rlm@145: (Gb/step (button-mask buttons)) rlm@145: (reset! current-state rlm@145: (SaveState. (Gb/saveState)))) rlm@145: ([^SaveState state] rlm@145: (step state [:listen])) rlm@145: ([] (step (if @current-state @current-state (root))))) rlm@145: rlm@145: (defn tick rlm@145: ([] (tick @current-state)) rlm@145: ([^SaveState state] rlm@145: (set-state! state) rlm@145: (Gb/tick) rlm@145: (update-state))) rlm@145: rlm@145: (defn play rlm@145: ([^SaveState state n] rlm@145: (try rlm@145: (set-state! state) rlm@145: (dorun (dotimes [_ n] rlm@145: (Thread/sleep 1) rlm@145: (Gb/step))) rlm@145: (finally rlm@145: (update-state)))) rlm@145: ([n] rlm@145: (play @current-state n))) rlm@145: rlm@145: (defn continue! rlm@145: ([state] rlm@145: (play state Integer/MAX_VALUE)) rlm@145: ([] rlm@145: (continue! @current-state))) rlm@145: rlm@326: (defn run-moves [state moves] rlm@154: (set-state! state) rlm@154: (dorun (map #(Gb/step (button-mask %)) rlm@154: moves)) rlm@154: (update-state)) rlm@154: rlm@145: (defn play-moves rlm@145: ([moves [prev state]] rlm@145: (set-state! state) rlm@145: (dorun (map (fn [move] (step @current-state move)) moves)) rlm@145: [(concat prev moves) @current-state])) rlm@522: rlm@522: (defn accurate-memory rlm@522: ([^SaveState state address] rlm@522: (set-state! state) rlm@522: (Gb/readMemory address)) rlm@522: ([address] rlm@522: (accurate-memory @current-state address))) rlm@145: rlm@145: ;;;;;;;;;;; rlm@145: rlm@496: ;; Get Screen Pixels, Save Screenshot rlm@496: rlm@496: (defn write-png! rlm@496: ([^SaveState state ^File target] rlm@496: (set-state! state) rlm@496: (Gb/nwritePNG (.getCanonicalPath target))) rlm@496: ([^File target] rlm@496: (write-png! @current-state target))) rlm@496: rlm@145: ;;;;;;;;;;;;;;; CPU data rlm@145: rlm@145: (defn cpu-data [size arr-fn] rlm@145: (let [store (int-array size)] rlm@145: (fn get-data rlm@145: ([] (get-data @current-state)) rlm@145: ([state] rlm@145: (set-state! state) (arr-fn store) store)))) rlm@145: rlm@145: (defn write-cpu-data [size store-fn] rlm@145: (fn store-data rlm@145: ([state new-data] rlm@145: (set-state! state) rlm@145: (let [store (int-array new-data)] rlm@145: (assert (= size (count new-data))) rlm@145: (store-fn store) rlm@145: (update-state))) rlm@145: ([new-data] rlm@145: (store-data @current-state new-data)))) rlm@145: rlm@526: (def gb-sound-format rlm@526: "44100 hertz, linear PCM, 2 channels with 16 bits per sample." rlm@526: (AudioFormat. 44100 16 2 true false)) rlm@526: rlm@526: (let [store (byte-array Gb/MAX_SOUND_BYTES)] rlm@526: (defn sound-bytes rlm@526: "Returns a byte array containting the sound samples rlm@526: generated this step." rlm@526: ([](sound-bytes @current-state)) rlm@524: ([state] rlm@524: (set-state! state) rlm@524: (Gb/getFrameSound store) rlm@526: (let [actual-bytes (* 2 (Gb/getSoundFrameWritten))] rlm@526: (Gb/setSoundFrameWritten 0) rlm@526: (byte-array (take actual-bytes store)))))) rlm@525: rlm@145: (def memory rlm@145: (cpu-data Gb/GB_MEMORY #(Gb/getMemory %))) rlm@145: rlm@145: (def ram rlm@145: (cpu-data Gb/RAM_SIZE #(Gb/getRAM %))) rlm@145: rlm@145: (def rom rlm@145: (cpu-data Gb/ROM_SIZE #(Gb/getROM %))) rlm@145: rlm@145: (def working-ram rlm@145: (cpu-data Gb/WRAM_SIZE #(Gb/getWRAM %))) rlm@145: rlm@145: (def video-ram rlm@145: (cpu-data Gb/VRAM_SIZE #(Gb/getVRAM %))) rlm@145: rlm@145: (def registers rlm@145: (cpu-data Gb/NUM_REGISTERS #(Gb/getRegisters %))) rlm@145: rlm@496: (def pixels rlm@496: (cpu-data (* Gb/DISPLAY_WIDTH Gb/DISPLAY_HEIGHT) rlm@496: #(Gb/getPixels %))) rlm@496: rlm@145: (def write-memory! rlm@145: (write-cpu-data Gb/GB_MEMORY #(Gb/writeMemory %))) rlm@145: rlm@145: (def write-registers! rlm@145: (write-cpu-data Gb/NUM_REGISTERS #(Gb/writeRegisters %))) rlm@145: rlm@191: (def write-rom! rlm@191: (write-cpu-data Gb/ROM_SIZE #(Gb/writeROM %))) rlm@191: rlm@145: ;;;;; Registers ;;;;;;;;;;;;;;;;;;;;;;;;;;; rlm@145: rlm@145: (defmacro gen-get-set-register [name index] rlm@145: (let [name-bang (symbol (str name "!"))] rlm@145: `(do rlm@145: (defn ~name rlm@145: ~(str "Retrieve the " name " register from state, or " rlm@145: "from @current-state if state is absent.") rlm@145: ([state#] rlm@145: (nth (registers state#) ~index)) rlm@145: ([] rlm@145: (~name @current-state))) rlm@145: (defn ~name-bang rlm@145: ~(str "Set the " name " register for state, or " rlm@145: "for @current-state if state is absent.") rlm@145: ([state# new-register#] rlm@145: (set-state! state#) rlm@145: (let [registers# (registers state#)] rlm@145: (aset registers# ~index new-register#) rlm@145: (Gb/writeRegisters registers#) rlm@145: (update-state))) rlm@145: ([new-register#] rlm@145: (~name-bang @current-state new-register#)))))) rlm@145: rlm@145: ;; 16 bit registers rlm@145: (gen-get-set-register PC 0) rlm@145: (gen-get-set-register SP 1) rlm@145: (gen-get-set-register AF 2) rlm@145: (gen-get-set-register BC 3) rlm@145: (gen-get-set-register DE 4) rlm@145: (gen-get-set-register HL 5) rlm@145: (gen-get-set-register IFF 6) rlm@145: rlm@145: ;; 8 bit registers rlm@145: (gen-get-set-register DIV 7) rlm@145: (gen-get-set-register TIMA 8) rlm@145: (gen-get-set-register TMA 9) rlm@145: (gen-get-set-register IF 11) rlm@145: (gen-get-set-register LCDC 12) rlm@145: (gen-get-set-register STAT 13) rlm@145: (gen-get-set-register SCY 14) rlm@145: (gen-get-set-register SCX 15) rlm@145: (gen-get-set-register LY 16) rlm@145: (gen-get-set-register DMA 18) rlm@145: (gen-get-set-register WY 19) rlm@145: (gen-get-set-register WX 20) rlm@145: (gen-get-set-register VBK 21) rlm@145: (gen-get-set-register HDMA1 22) rlm@145: (gen-get-set-register HDMA2 23) rlm@145: (gen-get-set-register HDMA3 24) rlm@145: (gen-get-set-register HDMA4 25) rlm@145: (gen-get-set-register HDMA5 26) rlm@145: (gen-get-set-register SVBK 27) rlm@145: (gen-get-set-register IE 28) rlm@145: rlm@145: ;;;;;;;;;;;;;;; rlm@145: rlm@145: (defmacro defn-memo rlm@145: [& forms] rlm@145: (let [fun-name (first forms)] rlm@145: `(do rlm@145: (defn ~@forms) rlm@145: (alter-var-root (var ~fun-name) memoize)))) rlm@145: rlm@488: (def original-rom (rom (root))) ocsenave@227: rlm@489: rlm@489: ;; RGB colors in an image are not the same as those in a GameBoy, so I rlm@489: ;; need to convert them. Fortunately, this code is already written rlm@489: ;; for me in this C-code from the public domain hi-color converter by rlm@489: ;; Glen Cook, Jeff Frohwein, and Rob Jones. rlm@488: (defn rgb->gb-rb [[r g b :as color]] rlm@488: (let [store (int-array 3)] rlm@488: (Gb/translateRGB (int-array color) store) rlm@524: (vec store))) rlm@524: