Files
jak-project/goal_src/jak1/engine/game/game-save.gc
T
Matt Dallmeyer 697337166d [jak1] Patch autosave/progress lock in speedrun mode (#4340)
We already skip the "first autosave warning" prompt in speedrunner mode,
this just moves the check up a bit earlier, where it feels more
appropriate imo.

This also prevents a potential softlock from occurring in speedrunner
mode only, if you manage to bring up the progress menu while the first
autosave occurs/finishes (e.g. by pause buffering 7th scout fly, or one
of the citadel sage cells).

In that scenario, the `auto-save` process gets stuck in `done` state in
this loop because `progress-allowed?` is false due to the pending
powercell cutscene, and so we keep resetting `state-time`:

https://github.com/open-goal/jak-project/blob/d8710bb2f645528e598e2bd799708d9d945f76c6/goal_src/jak1/engine/game/game-save.gc#L1172-L1174
(in normal circumstances, this prevents the autosave prompt from popping
up during and shortly after the cell cutscene)

The problem is that the `progress` code has a sort of mirrored check,
where it will only `enter!` the next state if there is no `auto-save`
process:

https://github.com/open-goal/jak-project/blob/d8710bb2f645528e598e2bd799708d9d945f76c6/goal_src/jak1/pc/progress-pc.gc#L3600-L3609

Here's an example of the locked progress screen, I run some code in the
REPL to show the game is still running, and then use a breakpoint and
continue to artificially get past the `time-elapsed?` check in the
`auto-save` code

https://youtu.be/4eZlQbRq6l8
2026-07-10 22:49:52 -04:00

1190 lines
68 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "engine/game/game-info.gc")
(require "engine/ui/text-h.gc")
(require "engine/gfx/sprite/sparticle/sparticle-launcher.gc")
(require "engine/ps2/memcard-h.gc")
(require "engine/load/loader.gc")
;; The game-save system implements the "background autosave" feature.
;; The process works like this:
;; - The useful parts of game-info are packed into a game-save (all at once)
;; - The auto-save process runs a state machine to write this to memory card.
;; - Additionally, the main loop calls mc-run to run the C++ memory card state machine.
;; Having two state machines, one in C++ and one in GOAL is kind of a questionable and confusing design.
(define-extern mark-speedrun-started (function none))
;; version identifier
(defconstant SAVE_VERSION 1)
;; the C++ memory card functions also use these codes.
(defenum mc-status-code
:type uint32
(busy 0)
(ok 1)
(bad-handle 2)
(format-failed 3)
(internal-error 4)
(write-error 5)
(read-error 6)
(new-game 7)
(no-memory 8)
(no-card 9)
(no-last 10)
(no-format 11)
(no-file 12)
(no-save 13)
(no-space 14)
(bad-version 15)
(no-process 16)
(no-auto-save 17))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; state serialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; identifier for game save fields
(defenum game-save-elt
:type uint16
(name 100)
(base-time 101)
(real-time 102)
(game-time 103)
(integral-time 104)
(continue 200)
(life 201)
(money 202)
(money-total 203)
(money-per-level 204)
(buzzer-total 205)
(fuel-cell 206)
(death-movie-tick 207)
(task-list 300)
(perm-list 301)
(hint-list 303)
(text-list 304)
(level-open-list 305)
(total-deaths 400)
(continue-deaths 401)
(fuel-cell-deaths 402)
(game-start-time 403)
(continue-timke 404) ;; typo in game
(death-time 405)
(hit-time 406)
(fuel-cell-pickup-time 407)
(continue-time 408)
(fuel-cell-time 409)
(enter-level-time 410)
(deaths-per-level 411)
(death-pos 412)
(auto-save-count 413)
(in-level-time 414)
(sfx-volume 500)
(music-volume 501)
(dialog-volume 502)
(language 503)
(screenx 504)
(screeny 505)
(vibration 506)
(play-hints 507)
(video-mode 508)
(aspect-ratio 509))
;; DECOMP BEGINS
;; the game save is a bunch of "game-save-tag"s
;; the elt-type field identifies what it is.
;; the data may be stored in one of the user fields, or
;; may be stored after the tag itself.
;; the count/size fields determine how big it is.
(deftype game-save-tag (structure)
((user-object object 2)
(user-uint64 uint64 :overlay-at (-> user-object 0))
(user-float0 float :overlay-at (-> user-object 0))
(user-float float 2 :overlay-at (-> user-object 0))
(user-int32 int32 2 :overlay-at (-> user-object 0))
(user-uint32 uint32 2 :overlay-at (-> user-object 0))
(user-int16 int16 4 :overlay-at (-> user-object 0))
(user-uint16 uint16 4 :overlay-at (-> user-object 0))
(user-int8 int8 8 :overlay-at (-> user-object 0))
(user-int80 int8 :overlay-at (-> user-object 0))
(user-int81 int8 :overlay-at (-> user-int8 1))
(user-uint8 uint8 8 :overlay-at (-> user-object 0))
(elt-count int32)
(elt-size uint16)
(elt-type game-save-elt)))
;; A game-save is a dynamic type that contains the full save.
;; it contains common metadata plus all the tags
;; the common metadata is used to display info about a save, without needing to
;; fully unpack the data stored in the tags.
(deftype game-save (basic)
((version int32)
(allocated-length int32)
(length int32)
(info-int32 int32 16)
(info-int8 int8 64 :overlay-at (-> info-int32 0))
(level-index int32 :overlay-at (-> info-int32 0))
(fuel-cell-count float :overlay-at (-> info-int32 1))
(money-count float :overlay-at (-> info-int32 2))
(buzzer-count float :overlay-at (-> info-int32 3))
(completion-percentage float :overlay-at (-> info-int32 4))
(minute uint8 :overlay-at (-> info-int32 5))
(hour uint8 :overlay-at (-> info-int8 21))
(week uint8 :overlay-at (-> info-int8 22))
(day uint8 :overlay-at (-> info-int8 23))
(month uint8 :overlay-at (-> info-int32 6))
(year uint8 :overlay-at (-> info-int8 25))
(new-game int32 :overlay-at (-> info-int32 7))
(tag game-save-tag :inline :dynamic))
(:methods
(new (symbol type int) _type_)
(save-to-file (_type_ string) _type_)
(load-from-file! (_type_ string) _type_)
(debug-print (_type_ symbol) _type_)))
(defmethod asize-of ((this game-save))
"Get the size in memory of the save"
(the-as int (+ (-> game-save size) (the-as uint (-> this allocated-length)))))
(defmethod new game-save ((allocation symbol) (type-to-make type) (arg0 int))
"Allocate a game save. arg0 is the number of bytes for tags."
(let ((v0-0 (object-new allocation type-to-make (the-as int (+ (-> type-to-make size) (the-as uint arg0))))))
(set! (-> v0-0 version) SAVE_VERSION)
(set! (-> v0-0 allocated-length) arg0)
v0-0))
(defun-debug game-save-elt->string ((arg0 game-save-elt))
(enum->string game-save-elt arg0))
(defun progress-level-index->string ((arg0 int))
"Convert an index for a level in the progress menu (not actual data levels)
to a string (translated)."
(if (< arg0 (-> *level-task-data* length))
(lookup-text! *common-text* (-> *level-task-data* arg0 level-name-id) #f)
(the-as string #f)))
(defmethod debug-print ((this game-save) (detail symbol))
"Print a save to #t"
(format #t "[~8x] ~A~%" this (-> this type))
(format #t "~Tversion: ~D~%" (-> this version))
(format #t "~Tallocated-length: ~D~%" (-> this allocated-length))
(format #t "~Tlength: ~D~%" (-> this length))
(format #t "~Tlevel-index: ~D~%" (-> this level-index))
(format #t "~Tfuel-cell-count: ~f~%" (-> this fuel-cell-count))
(format #t "~Tmoney-count: ~f~%" (-> this money-count))
(format #t "~Tbuzzer-count: ~f~%" (-> this buzzer-count))
(format #t "~Tcompletion-percentage: ~f~%" (-> this completion-percentage))
(format #t "~Tsave-time: ~x:~x ~x/~x/~x~%" (-> this hour) (-> this minute) (-> this day) (-> this month) (-> this year))
(format #t "~Ttag[]: @ #x~X~%" (-> this tag))
;; loop through tags
(let ((tag (the-as game-save-tag (-> this tag)))
(tag-idx 0))
(while (< (the-as int tag) (the-as int (&-> this tag 0 user-int8 (-> this length))))
(format #t
"~T [~3D] ~-32S [~3D/~3D] ~12D ~8f "
tag-idx
(game-save-elt->string (-> tag elt-type))
(-> tag elt-count)
(-> tag elt-size)
(-> tag user-uint64)
(-> tag user-float0))
(let ((v1-0 (-> tag elt-type)))
(if (or (= v1-0 (game-save-elt name)) (= v1-0 (game-save-elt continue)))
(format #t "= \"~G\"~%" (&+ (the-as pointer tag) 16))
(format #t "~%")))
(when detail
(case (-> tag elt-type)
(((game-save-elt money-per-level)
(game-save-elt deaths-per-level)
;; added in pc port
(game-save-elt level-open-list))
(dotimes (prog-lev-idx (-> tag elt-count))
(let ((lev-name (progress-level-index->string prog-lev-idx)))
(if lev-name
(format #t
" ~-32S: ~D~%"
lev-name
(-> (the-as (pointer uint8) (&+ (the-as pointer (&-> (the-as (pointer uint8) tag) 16)) prog-lev-idx))))))))
(((game-save-elt enter-level-time))
(dotimes (s2-2 (-> tag elt-count))
(let ((a2-14 (progress-level-index->string s2-2)))
(if a2-14
(format #t
" ~-32S: ~D~%"
a2-14
(-> (the-as (pointer uint64) (&+ (&+ (the-as pointer tag) 16) (* s2-2 8)))))))))
(((game-save-elt in-level-time))
(dotimes (s2-3 (-> tag elt-count))
(let ((a2-15 (progress-level-index->string s2-3)))
(if a2-15
(format #t
" ~-32S: ~D~%"
a2-15
(-> (the-as (pointer uint64) (&+ (&+ (the-as pointer tag) 16) (* s2-3 8)))))))))
(((game-save-elt fuel-cell-time))
(dotimes (s2-4 (-> tag elt-count))
(let ((a2-16 (game-task->string (the-as game-task s2-4))))
(if a2-16
(format #t
" ~-32S: ~D~%"
a2-16
(-> (the-as (pointer uint64) (&+ (&+ (the-as pointer tag) 16) (* s2-4 8)))))))))
;; below here were added for pc port
(((game-save-elt hint-list))
(dotimes (i (-> tag elt-count))
(awhen (text-id->string (-> *game-info* hint-control i id))
(format #t " ~-32S [~D]~%" it i)
(format #t
" ~-32S: ~D~%"
"start-time"
(-> (the-as (pointer uint64) (&+ (&+ (the-as pointer tag) 16) (* i (-> tag elt-size)))) 0))
(format #t
" ~-32S: ~D~%"
"last-time-called"
(-> (the-as (pointer uint64) (&+ (&+ (the-as pointer tag) 16) (* i (-> tag elt-size)))) 1))
(format #t
" ~-32S: ~D~%"
"num-attempts"
(-> (the-as (pointer uint8) (&+ (&+ (the-as pointer tag) 16) (* i (-> tag elt-size)))) 16))
(format #t
" ~-32S: ~D~%"
"num-success"
(-> (the-as (pointer uint8) (&+ (&+ (the-as pointer tag) 16) (* i (-> tag elt-size)))) 17)))))
(((game-save-elt text-list))
(dotimes (i (* 8 (-> tag elt-count)))
(awhen (text-id->string (the text-id i))
(unless (string= it "*unknown*")
(format #t
" ~-32S: ~A~%"
it
(logtest? (-> (the-as (pointer uint8) (&+ (the-as pointer tag) 16)) (/ i 8)) (ash 1 (mod i 8))))))))
(((game-save-elt task-list) (game-save-elt perm-list))
(dotimes (i (-> tag elt-count))
(let ((perm (the-as entity-perm (&+ (the-as pointer tag) 16 (* i 16)))))
(format #t
"~T ~-4D: ~20D/#x~16x[~8f/~8f]"
i
(-> perm user-uint64)
(-> perm user-uint64)
(-> perm user-float 0)
(-> perm user-float 1))
(format #t " task: ~-32S #x~4x~%" (game-task->string (-> perm task)) (-> perm aid))
(format #t "~T~T (entity-perm-status ")
(bit-enum->string entity-perm-status (-> perm status) #t)
(format #t ")~%"))))))
(set! tag
(the-as game-save-tag
(&+ (the-as pointer tag) (logand -16 (+ (* (the-as int (-> tag elt-size)) (-> tag elt-count)) 31)))))
(+! tag-idx 1)))
this)
(defmethod inspect ((this game-save))
(debug-print this #f))
(defmethod save-game! ((this game-info) (arg0 game-save) (arg1 string))
"Update the game-save to have the info from the current game state"
;; some stuff lives in the levels and needs to be copied into game-info.
(dotimes (s3-0 (-> *level* length))
(let ((a1-1 (-> *level* level s3-0))) (if (= (-> a1-1 status) 'active) (copy-perms-from-level! this a1-1))))
;; set common data
(set! (-> arg0 length) 0)
(set! (-> arg0 version) 1)
(set! (-> arg0 level-index) (-> (lookup-level-info (-> this current-continue level)) index))
(set! (-> arg0 fuel-cell-count) (-> this fuel))
(set! (-> arg0 money-count) (-> this money-total))
(set! (-> arg0 buzzer-count) (-> this buzzer-total))
(set! (-> arg0 completion-percentage) (calculate-completion (the-as progress #f)))
(when (string= (-> this current-continue name) "title-start")
(set! (-> arg0 new-game) 1)
(set! (-> arg0 level-index) (-> (lookup-level-info 'training) index))
(set! (-> arg0 fuel-cell-count) 0.0)
(set! (-> arg0 money-count) 0.0)
(set! (-> arg0 buzzer-count) 0.0)
(set! (-> arg0 completion-percentage) 0.0))
(let ((s3-1 (new 'stack 'scf-time)))
(scf-get-time s3-1)
(when (zero? (-> s3-1 stat))
(set! (-> arg0 minute) (-> s3-1 minute))
(set! (-> arg0 hour) (-> s3-1 hour))
(set! (-> arg0 day) (-> s3-1 day))
(set! (-> arg0 week) (-> s3-1 week))
(set! (-> arg0 month) (-> s3-1 month))
(set! (-> arg0 year) (-> s3-1 year))))
(let ((s3-2 (the-as object (-> arg0 tag))))
(let ((s2-0 (-> (the-as (inline-array game-save-tag) s3-2) 0)))
(set! (-> s2-0 elt-type) (game-save-elt name))
(set! (-> s2-0 elt-count) (+ (length arg1) 1))
(set! (-> s2-0 elt-size) (the-as uint 1)))
(copy-charp<-charp (-> (the-as (inline-array game-save-tag) s3-2) 1 user-uint8) (-> arg1 data))
(let ((v1-37 (&+ (the-as pointer s3-2) (+ (logand -16 (+ (-> (the-as (inline-array game-save-tag) s3-2) 0 elt-count) 15)) 16))))
(let ((a0-15 (the-as game-save-tag (&+ v1-37 0))))
(set! (-> a0-15 elt-type) (game-save-elt base-time))
(set! (-> a0-15 elt-count) 0)
(set! (-> a0-15 user-uint64) (the-as uint (current-time))))
(let ((v1-38 (&+ v1-37 16)))
(let ((a0-16 (the-as game-save-tag (&+ v1-38 0))))
(set! (-> a0-16 elt-type) (game-save-elt real-time))
(set! (-> a0-16 elt-count) 0)
(set! (-> a0-16 user-uint64) (the-as uint (-> *display* real-frame-counter))))
(let ((v1-39 (&+ v1-38 16)))
(let ((a0-17 (the-as game-save-tag (&+ v1-39 0))))
(set! (-> a0-17 elt-type) (game-save-elt game-time))
(set! (-> a0-17 elt-count) 0)
(set! (-> a0-17 user-uint64) (the-as uint (-> *display* game-frame-counter))))
(let ((v1-40 (&+ v1-39 16)))
(let ((a0-18 (the-as game-save-tag (&+ v1-40 0))))
(set! (-> a0-18 elt-type) (game-save-elt integral-time))
(set! (-> a0-18 elt-count) 0)
(set! (-> a0-18 user-uint64) (the-as uint (-> *display* integral-frame-counter))))
(let ((s4-1 (the-as object (&+ v1-40 16))))
(let ((s3-3 (-> this current-continue name)))
(let ((s2-1 (the-as game-save-tag (-> (the-as game-save-tag s4-1) user-object))))
(set! (-> s2-1 elt-type) (game-save-elt continue))
(set! (-> s2-1 elt-count) (+ ((method-of-type string length) s3-3) 1))
(set! (-> s2-1 elt-size) (the-as uint 1)))
(copy-charp<-charp (-> (the-as game-save-tag (&+ (the-as game-save-tag s4-1) 16)) user-uint8) (-> s3-3 data)))
(let ((v1-50 (&+ (the-as pointer s4-1) (+ (logand -16 (+ (-> (the-as game-save-tag s4-1) elt-count) 15)) 16))))
(let ((a0-24 (the-as game-save-tag (&+ v1-50 0))))
(set! (-> a0-24 elt-type) (game-save-elt life))
(set! (-> a0-24 elt-count) 0)
(set! (-> a0-24 user-float0) (-> this life)))
(let ((v1-51 (&+ v1-50 16)))
(let ((a0-25 (the-as game-save-tag (&+ v1-51 0))))
(set! (-> a0-25 elt-type) (game-save-elt buzzer-total))
(set! (-> a0-25 elt-count) 0)
(set! (-> a0-25 user-float0) (-> this buzzer-total)))
(let ((v1-52 (&+ v1-51 16)))
(let ((a0-26 (the-as game-save-tag (&+ v1-52 0))))
(set! (-> a0-26 elt-type) (game-save-elt fuel-cell))
(set! (-> a0-26 elt-count) 0)
(set! (-> a0-26 user-float0) (-> this fuel)))
(let ((v1-53 (&+ v1-52 16)))
(let ((a0-27 (the-as game-save-tag (&+ v1-53 0))))
(set! (-> a0-27 elt-type) (game-save-elt death-movie-tick))
(set! (-> a0-27 elt-count) 0)
(set! (-> a0-27 user-uint64) (the-as uint (-> this death-movie-tick))))
(let ((v1-54 (&+ v1-53 16)))
(let ((a0-28 (the-as game-save-tag (&+ v1-54 0))))
(set! (-> a0-28 elt-type) (game-save-elt money))
(set! (-> a0-28 elt-count) 0)
(set! (-> a0-28 user-float0) (-> this money)))
(let ((v1-55 (&+ v1-54 16)))
(let ((a0-29 (the-as game-save-tag (&+ v1-55 0))))
(set! (-> a0-29 elt-type) (game-save-elt money-total))
(set! (-> a0-29 elt-count) 0)
(set! (-> a0-29 user-float0) (-> this money-total)))
(let ((v1-56 (&+ v1-55 16)))
(let ((a0-30 (the-as game-save-tag (&+ v1-56 0))))
(set! (-> a0-30 elt-type) (game-save-elt money-per-level))
(set! (-> a0-30 elt-count) 32)
(set! (-> a0-30 elt-size) (the-as uint 1)))
(let ((v1-57 (&+ v1-56 16)))
(dotimes (a0-31 32)
(set! (-> (the-as (pointer uint8) (&+ v1-57 a0-31))) (-> this money-per-level a0-31)))
(let ((v1-58 (&+ v1-57 32)))
(let ((a0-34 (the-as object (&+ v1-58 0))))
(set! (-> (the-as game-save-tag a0-34) elt-type) (game-save-elt level-open-list))
(set! (-> (the-as game-save-tag a0-34) elt-count) 32)
(set! (-> (the-as game-save-tag a0-34) elt-size) (the-as uint 1)))
(let ((v1-59 (&+ v1-58 16)))
(dotimes (a0-35 32)
(set! (-> (the-as (pointer uint8) (&+ v1-59 a0-35))) (-> this level-opened a0-35)))
(let ((v1-60 (&+ v1-59 32))
(s4-2 (-> (the-as (pointer int32) (-> this perm-list)) 0)))
(let ((a0-39 (the-as game-save-tag (&+ v1-60 0))))
(set! (-> a0-39 elt-type) (game-save-elt perm-list))
(set! (-> a0-39 elt-count) s4-2)
(set! (-> a0-39 elt-size) (the-as uint 16)))
(let ((s3-4 (&+ v1-60 16)))
(dotimes (s2-2 s4-2)
(mem-copy! (the-as pointer (the-as game-save-tag (&+ s3-4 (* s2-2 16))))
(the-as pointer (-> this perm-list data s2-2))
16))
(let ((v1-68 (&+ s3-4 (logand -16 (+ (* s4-2 16) 15))))
(s4-3 (-> this task-perm-list length)))
(let ((a0-45 (the-as game-save-tag (&+ v1-68 0))))
(set! (-> a0-45 elt-type) (game-save-elt task-list))
(set! (-> a0-45 elt-count) s4-3)
(set! (-> a0-45 elt-size) (the-as uint 16)))
(let ((s3-5 (&+ v1-68 16)))
(dotimes (s2-3 s4-3)
(mem-copy! (the-as pointer (the-as game-save-tag (&+ s3-5 (* s2-3 16))))
(the-as pointer (-> this task-perm-list data s2-3))
16))
(let ((a0-49 (&+ s3-5 (logand -16 (+ (* s4-3 16) 15))))
(v1-79 (/ (logand -8 (+ (-> this text-ids-seen allocated-length) 7)) 8)))
(let ((a1-46 (the-as object (&+ a0-49 0))))
(set! (-> (the-as game-save-tag a1-46) elt-type) (game-save-elt text-list))
(set! (-> (the-as game-save-tag a1-46) elt-count) v1-79)
(set! (-> (the-as game-save-tag a1-46) elt-size) (the-as uint 1)))
(let ((a0-50 (&+ a0-49 16)))
(dotimes (a1-47 v1-79)
(set! (-> (the-as (pointer uint8) (&+ a0-50 a1-47))) (-> this text-ids-seen bytes a1-47)))
(let ((a0-51 (&+ a0-50 (logand -16 (+ v1-79 15))))
(v1-84 (-> this hint-control length)))
(let ((a1-51 (the-as game-save-tag (&+ a0-51 0))))
(set! (-> a1-51 elt-type) (game-save-elt hint-list))
(set! (-> a1-51 elt-count) v1-84)
(set! (-> a1-51 elt-size) (the-as uint 32)))
(let ((a0-52 (&+ a0-51 16)))
(dotimes (a1-52 v1-84)
(set! (-> (the-as (pointer int64) (&+ a0-52 (* (* a1-52 4) 8)))) (-> this hint-control a1-52 start-time))
(set! (-> (the-as (pointer int64) (&+ a0-52 (* (+ (* a1-52 4) 1) 8)))) (-> this hint-control a1-52 last-time-called))
(set! (-> (the-as (pointer int8) (&+ a0-52 (+ (* a1-52 32) 16)))) (-> this hint-control a1-52 num-attempts))
(set! (-> (the-as (pointer int8) (&+ a0-52 (+ (* a1-52 32) 17)))) (-> this hint-control a1-52 num-success)))
(let ((v1-86 (&+ a0-52 (* v1-84 32))))
(let ((a0-54 (the-as game-save-tag (&+ v1-86 0))))
(set! (-> a0-54 elt-type) (game-save-elt auto-save-count))
(set! (-> a0-54 elt-count) 0)
(set! (-> a0-54 user-uint64) (the-as uint (-> this auto-save-count))))
(let ((v1-87 (&+ v1-86 16)))
(let ((a0-55 (the-as game-save-tag (&+ v1-87 0))))
(set! (-> a0-55 elt-type) (game-save-elt total-deaths))
(set! (-> a0-55 elt-count) 0)
(set! (-> a0-55 user-uint64) (the-as uint (-> this total-deaths))))
(let ((v1-88 (&+ v1-87 16)))
(let ((a0-56 (the-as game-save-tag (&+ v1-88 0))))
(set! (-> a0-56 elt-type) (game-save-elt continue-deaths))
(set! (-> a0-56 elt-count) 0)
(set! (-> a0-56 user-uint64) (the-as uint (-> this continue-deaths))))
(let ((v1-89 (&+ v1-88 16)))
(let ((a0-57 (the-as game-save-tag (&+ v1-89 0))))
(set! (-> a0-57 elt-type) (game-save-elt fuel-cell-deaths))
(set! (-> a0-57 elt-count) 0)
(set! (-> a0-57 user-uint64) (the-as uint (-> this fuel-cell-deaths))))
(let ((v1-90 (&+ v1-89 16)))
(let ((a0-58 (the-as game-save-tag (&+ v1-90 0))))
(set! (-> a0-58 elt-type) (game-save-elt game-start-time))
(set! (-> a0-58 elt-count) 0)
(set! (-> a0-58 user-uint64) (the-as uint (-> this game-start-time))))
(let ((v1-91 (&+ v1-90 16)))
(let ((a0-59 (the-as game-save-tag (&+ v1-91 0))))
(set! (-> a0-59 elt-type) (game-save-elt continue-time))
(set! (-> a0-59 elt-count) 0)
(set! (-> a0-59 user-uint64) (the-as uint (-> this continue-time))))
(let ((v1-92 (&+ v1-91 16)))
(let ((a0-60 (the-as game-save-tag (&+ v1-92 0))))
(set! (-> a0-60 elt-type) (game-save-elt death-time))
(set! (-> a0-60 elt-count) 0)
(set! (-> a0-60 user-uint64) (the-as uint (-> this death-time))))
(let ((v1-93 (&+ v1-92 16)))
(let ((a0-61 (the-as game-save-tag (&+ v1-93 0))))
(set! (-> a0-61 elt-type) (game-save-elt hit-time))
(set! (-> a0-61 elt-count) 0)
(set! (-> a0-61 user-uint64) (the-as uint (-> this hit-time))))
(let ((v1-94 (&+ v1-93 16)))
(let ((a0-62 (the-as game-save-tag (&+ v1-94 0))))
(set! (-> a0-62 elt-type) (game-save-elt fuel-cell-pickup-time))
(set! (-> a0-62 elt-count) 0)
(set! (-> a0-62 user-uint64) (the-as uint (-> this fuel-cell-pickup-time))))
(let ((v1-95 (&+ v1-94 16)))
(let ((a0-63 (the-as game-save-tag (&+ v1-95 0))))
(set! (-> a0-63 elt-type) (game-save-elt fuel-cell-time))
(set! (-> a0-63 elt-count) 116)
(set! (-> a0-63 elt-size) (the-as uint 8)))
(let ((v1-96 (&+ v1-95 16)))
(let ((a0-64 (the-as object 0)))
(while (< (the-as int a0-64) 116)
(set! (-> (the-as (pointer int64) (&+ v1-96 (* (the-as int a0-64) 8)))) (-> this fuel-cell-time (the-as int a0-64)))
(set! a0-64 (+ (the-as int a0-64) 1))))
(let ((v1-97 (&+ v1-96 928)))
(let ((a0-67 (the-as game-save-tag (&+ v1-97 0))))
(set! (-> a0-67 elt-type) (game-save-elt deaths-per-level))
(set! (-> a0-67 elt-count) 32)
(set! (-> a0-67 elt-size) (the-as uint 1)))
(let ((v1-98 (&+ v1-97 16)))
(dotimes (a0-68 32)
(set! (-> (the-as (pointer uint8) (&+ v1-98 a0-68))) (-> this deaths-per-level a0-68)))
(let ((v1-99 (&+ v1-98 32)))
(let ((a0-71 (the-as game-save-tag (&+ v1-99 0))))
(set! (-> a0-71 elt-type) (game-save-elt enter-level-time))
(set! (-> a0-71 elt-count) 32)
(set! (-> a0-71 elt-size) (the-as uint 8)))
(let ((v1-100 (&+ v1-99 16)))
(dotimes (a0-72 32)
(set! (-> (the-as (pointer int64) (&+ v1-100 (* a0-72 8)))) (-> this enter-level-time a0-72)))
(let ((v1-101 (&+ v1-100 256)))
(let ((a0-75 (the-as game-save-tag (&+ v1-101 0))))
(set! (-> a0-75 elt-type) (game-save-elt in-level-time))
(set! (-> a0-75 elt-count) 32)
(set! (-> a0-75 elt-size) (the-as uint 8)))
(let ((v1-102 (&+ v1-101 16)))
(dotimes (a0-76 32)
(set! (-> (the-as (pointer int64) (&+ v1-102 (* a0-76 8)))) (-> this in-level-time a0-76)))
(let ((v1-103 (&+ v1-102 256)))
(let ((a0-79 (the-as game-save-tag (&+ v1-103 0))))
(set! (-> a0-79 elt-type) (game-save-elt sfx-volume))
(set! (-> a0-79 elt-count) 0)
(set! (-> a0-79 user-float0) (-> *setting-control* default sfx-volume)))
(let ((v1-104 (&+ v1-103 16)))
(let ((a0-80 (the-as game-save-tag (&+ v1-104 0))))
(set! (-> a0-80 elt-type) (game-save-elt music-volume))
(set! (-> a0-80 elt-count) 0)
(set! (-> a0-80 user-float0) (-> *setting-control* default music-volume)))
(let ((v1-105 (&+ v1-104 16)))
(let ((a0-81 (the-as game-save-tag (&+ v1-105 0))))
(set! (-> a0-81 elt-type) (game-save-elt dialog-volume))
(set! (-> a0-81 elt-count) 0)
(set! (-> a0-81 user-float0) (-> *setting-control* default dialog-volume)))
(let ((v1-106 (&+ v1-105 16)))
(let ((a0-82 (the-as game-save-tag (&+ v1-106 0))))
(set! (-> a0-82 elt-type) (game-save-elt language))
(set! (-> a0-82 elt-count) 0)
(set! (-> a0-82 user-uint64) (the-as uint (-> *setting-control* default language))))
(let ((v1-107 (&+ v1-106 16)))
(let ((a0-83 (the-as game-save-tag (&+ v1-107 0))))
(set! (-> a0-83 elt-type) (game-save-elt screenx))
(set! (-> a0-83 elt-count) 0)
(set! (-> a0-83 user-float0) (the float (-> *setting-control* default screenx))))
(let ((v1-108 (&+ v1-107 16)))
(let ((a0-84 (the-as game-save-tag (&+ v1-108 0))))
(set! (-> a0-84 elt-type) (game-save-elt screeny))
(set! (-> a0-84 elt-count) 0)
(set! (-> a0-84 user-float0) (the float (-> *setting-control* default screeny))))
(let ((v1-109 (&+ v1-108 16)))
(let ((a0-85 (the-as game-save-tag (&+ v1-109 0))))
(set! (-> a0-85 elt-type) (game-save-elt vibration))
(set! (-> a0-85 elt-count) 0)
(set! (-> a0-85 user-uint64) (the-as uint (if (-> *setting-control* default vibration) 1 0))))
(let ((v1-110 (&+ v1-109 16)))
(let ((a0-86 (the-as game-save-tag (&+ v1-110 0))))
(set! (-> a0-86 elt-type) (game-save-elt play-hints))
(set! (-> a0-86 elt-count) 0)
(set! (-> a0-86 user-uint64) (the-as uint (if (-> *setting-control* default play-hints) 1 0))))
(let ((v1-111 (&+ v1-110 16)))
(let ((a0-87 (the-as game-save-tag (&+ v1-111 0))))
(set! (-> a0-87 elt-type) (game-save-elt video-mode))
(set! (-> a0-87 elt-count) 0)
(let ((a1-121 (-> *setting-control* default video-mode)))
(set! (-> a0-87 user-uint64)
(the-as uint
(cond
((= a1-121 'ntsc) 1)
((= a1-121 'pal) 2)
(else 0))))))
(let ((v1-112 (&+ v1-111 16)))
(let ((a0-88 (the-as object (&+ v1-112 0))))
(set! (-> (the-as game-save-tag a0-88) elt-type) (game-save-elt aspect-ratio))
(set! (-> (the-as game-save-tag a0-88) elt-count) 0)
(let ((a1-125 (-> *setting-control* default aspect-ratio)))
(set! (-> (the-as (pointer int64) a0-88))
(cond
((= a1-125 'aspect4x3) 1)
((= a1-125 'aspect16x9) 2)
(else 0)))))
(set! (-> arg0 length) (&- (&+ v1-112 16) (the-as uint (the-as pointer (-> arg0 tag))))))))))))))))))))))))))))))))))))))))))))))))))))))))
(if (< (-> arg0 allocated-length) (-> arg0 length))
(format 0 "ERROR: SAVEGAME: fatal error, save is using ~D of ~D bytes." (-> arg0 length) (-> arg0 allocated-length)))
(none))
(defmethod load-game! ((this game-info) (save game-save))
"Copy save data from a game-save to a game-info"
(let ((save-data (the-as game-save-tag (-> save tag))))
;; loop over all tags
(while (< (the-as int save-data) (the-as int (+ (+ (-> save length) 76) (the-as int save))))
(let ((a0-1 (-> save-data elt-type)))
;; og:preserve-this Moved these settings to pc-settings
(cond
;; ((= a0-1 (game-save-elt sfx-volume)) (set! (-> *setting-control* default sfx-volume) (-> save-data user-float0)))
;; ((= a0-1 (game-save-elt music-volume)) (set! (-> *setting-control* default music-volume) (-> save-data user-float0)))
;; ((= a0-1 (game-save-elt dialog-volume)) (set! (-> *setting-control* default dialog-volume) (-> save-data user-float0)))
((= a0-1 (game-save-elt language))
(set! (-> *setting-control* default language) (the-as language-enum (-> save-data user-uint64))))
;; ((= a0-1 (game-save-elt vibration)) (set! (-> *setting-control* default vibration) (= (-> save-data user-uint64) 1)))
;; ((= a0-1 (game-save-elt play-hints)) (set! (-> *setting-control* default play-hints) (= (-> save-data user-uint64) 1)))
(else (format 0 "PC PORT: Skipping setting from game save, its stored in the pc-settings now~%"))))
(set! save-data
(the-as game-save-tag
(&+ (the-as pointer save-data) (logand -16 (+ (* (the-as int (-> save-data elt-size)) (-> save-data elt-count)) 31)))))))
;; og:preserve-this make sure loading a save doesn't reset speedrun state
(when (and (-> *pc-settings* speedrunner-mode?) (zero? (-> save new-game)))
(mark-speedrun-started))
;; if we're a new game, set our checkpoint.
(when (nonzero? (-> save new-game))
(set-continue! this "game-start")
(set! save save)
(goto cfg-134))
;; loop over all tags
(let ((data (the-as game-save-tag (-> save tag))))
(while (< (the-as int data) (the-as int (&-> save tag 0 user-int8 (-> save length))))
(case (-> data elt-type)
(((game-save-elt base-time))
(let ((old-base-frame (current-time)))
(set! (current-time) (the-as time-frame (-> data user-uint64)))
(set! (-> *display* old-base-frame-counter) (+ (current-time) -1))
(let ((frame-counter-diff (- (current-time) old-base-frame)))
(if (nonzero? (-> this blackout-time)) (+! (-> this blackout-time) frame-counter-diff))
(if (nonzero? (-> this letterbox-time)) (+! (-> this letterbox-time) frame-counter-diff))
(if (nonzero? (-> this hint-play-time)) (+! (-> this hint-play-time) frame-counter-diff))
(if (nonzero? (-> this display-text-time)) (+! (-> this display-text-time) frame-counter-diff))))
(buzz-stop! 0))
(((game-save-elt game-time))
(set! (-> *display* game-frame-counter) (the-as time-frame (-> data user-uint64)))
(set! (-> *display* old-game-frame-counter) (+ (-> *display* game-frame-counter) -1)))
(((game-save-elt real-time))
(set! (-> *display* real-frame-counter) (the-as time-frame (-> data user-uint64)))
(set! (-> *display* old-real-frame-counter) (+ (-> *display* real-frame-counter) -1)))
(((game-save-elt integral-time))
(set! (-> *display* integral-frame-counter) (the-as time-frame (-> data user-uint64)))
(set! (-> *display* old-integral-frame-counter) (+ (-> *display* integral-frame-counter) -1)))
(((game-save-elt continue))
(format (clear *temp-string*) "~G" (&+ (the-as pointer data) 16))
(set-continue! this *temp-string*))
(((game-save-elt life)) (set! (-> this life) (-> data user-float0)))
(((game-save-elt buzzer-total)) (set! (-> this buzzer-total) (-> data user-float0)))
(((game-save-elt fuel-cell)) (set! (-> this fuel) (-> data user-float0)))
(((game-save-elt death-movie-tick)) (set! (-> this death-movie-tick) (the-as int (-> data user-uint64))))
(((game-save-elt money)) (set! (-> this money) (-> data user-float0)))
(((game-save-elt money-total)) (set! (-> this money-total) (-> data user-float0)))
(((game-save-elt money-per-level))
(let ((v1-34 (min 32 (-> data elt-count))))
(dotimes (a0-76 v1-34)
(set! (-> this money-per-level a0-76) (-> (the-as (pointer uint8) (&+ (the-as pointer data) 16)) a0-76)))))
(((game-save-elt level-open-list))
(let ((v1-38 (min 32 (-> data elt-count))))
(dotimes (a0-80 v1-38)
(set! (-> this level-opened a0-80) (-> (the-as (pointer uint8) (&+ (the-as pointer data) 16)) a0-80)))))
(((game-save-elt perm-list))
(let ((s3-2 (min (-> data elt-count) (-> this perm-list allocated-length))))
(set! (-> this perm-list length) s3-2)
(dotimes (s2-0 s3-2)
(mem-copy! (the-as pointer (-> this perm-list data s2-0)) (&+ (&+ (the-as pointer data) 16) (* s2-0 16)) 16))))
(((game-save-elt task-list))
(let ((s3-4 (min (-> data elt-count) (-> this task-perm-list allocated-length))))
(set! (-> this task-perm-list length) s3-4)
(dotimes (s2-1 s3-4)
(mem-copy! (the-as pointer (-> this task-perm-list data s2-1)) (&+ (&+ (the-as pointer data) 16) (* s2-1 16)) 16))))
(((game-save-elt text-list))
(let ((v1-61 (/ (logand -8 (+ (-> this text-ids-seen allocated-length) 7)) 8))
(a0-94 (-> data elt-count)))
(dotimes (a1-35 v1-61)
(cond
((>= a1-35 a0-94) (set! (-> this text-ids-seen bytes a1-35) (the-as uint 0)) 0)
(else (set! (-> this text-ids-seen bytes a1-35) (-> (the-as (pointer uint8) (&+ (the-as pointer data) 16)) a1-35)))))))
(((game-save-elt hint-list))
(cond
((= (-> this hint-control length) (-> data elt-count))
(let ((v1-65 (&+ (the-as pointer data) 16)))
(dotimes (a0-99 (-> data elt-count))
(set! (-> this hint-control a0-99 start-time)
(the-as time-frame (-> (the-as (pointer uint64) (&+ v1-65 (* (* a0-99 4) 8))))))
(set! (-> this hint-control a0-99 last-time-called)
(the-as time-frame (-> (the-as (pointer uint64) (&+ v1-65 (* (+ (* a0-99 4) 1) 8))))))
(set! (-> this hint-control a0-99 num-attempts)
(-> (the-as (pointer int8) (&+ (the-as (pointer uint8) v1-65) (+ (* a0-99 32) 16)))))
(set! (-> this hint-control a0-99 num-success)
(-> (the-as (pointer int8) (&+ (the-as (pointer uint8) v1-65) (+ (* a0-99 32) 17))))))))
(else (format 0 "WARNING: SAVEGAME: hint control list did not match current, ignoring~%"))))
(((game-save-elt auto-save-count)) (set! (-> this auto-save-count) (the-as int (-> data user-uint64))))
(((game-save-elt total-deaths)) (set! (-> this total-deaths) (the-as int (-> data user-uint64))))
(((game-save-elt continue-deaths)) (set! (-> this continue-deaths) (the-as int (-> data user-uint64))))
(((game-save-elt fuel-cell-deaths)) (set! (-> this fuel-cell-deaths) (the-as int (-> data user-uint64))))
(((game-save-elt game-start-time)) (set! (-> this game-start-time) (the-as time-frame (-> data user-uint64))))
(((game-save-elt continue-time)) (set! (-> this continue-time) (the-as time-frame (-> data user-uint64))))
(((game-save-elt death-time)) (set! (-> this death-time) (the-as time-frame (-> data user-uint64))))
(((game-save-elt hit-time)) (set! (-> this hit-time) (the-as time-frame (-> data user-uint64))))
(((game-save-elt fuel-cell-pickup-time))
(set! (-> this fuel-cell-pickup-time) (the-as time-frame (-> data user-uint64))))
(((game-save-elt deaths-per-level))
(let ((v1-79 (min 32 (-> data elt-count))))
(dotimes (a0-122 v1-79)
(set! (-> this deaths-per-level a0-122) (-> (the-as (pointer uint8) (&+ (the-as pointer data) 16)) a0-122)))))
(((game-save-elt enter-level-time))
(let ((v1-83 (min 32 (-> data elt-count))))
(dotimes (a0-126 v1-83)
(set! (-> this enter-level-time a0-126)
(the-as time-frame (-> (the-as (pointer uint64) (&+ (&+ (the-as pointer data) 16) (* a0-126 8)))))))))
(((game-save-elt in-level-time))
(let ((v1-87 (min 32 (-> data elt-count))))
(dotimes (a0-130 v1-87)
(set! (-> this in-level-time a0-130)
(the-as time-frame (-> (the-as (pointer uint64) (&+ (&+ (the-as pointer data) 16) (* a0-130 8)))))))))
(((game-save-elt fuel-cell-time))
(let ((v1-92 (min 32 (-> data elt-count))))
(dotimes (a0-133 v1-92)
(set! (-> this fuel-cell-time a0-133)
(the-as time-frame (-> (the-as (pointer uint64) (&+ (&+ (the-as pointer data) 16) (* a0-133 8))))))))))
(set! data
(the-as game-save-tag
(&+ (the-as pointer data) (logand -16 (+ (* (the-as int (-> data elt-size)) (-> data elt-count)) 31)))))))
;; update entity stuff in levels that are active
(dotimes (s4-1 (-> *level* length))
(let ((a1-68 (-> *level* level s4-1))) (if (= (-> a1-68 status) 'active) (copy-perms-to-level! this a1-68))))
;; update tasks
(let ((s5-1 2) ;; jungle eggtop
(s4-2 115) ;; max - 1
)
(while (>= (the-as uint s4-2) (the-as uint s5-1))
;; calling this will run the function to see if the task is done or not
(get-task-status (the-as game-task s5-1))
(+! s5-1 1)))
(label cfg-134)
save)
(defmethod save-to-file ((this game-save) (arg0 string))
"Write a game save to a file for debugging"
(let ((s5-0 (new 'stack 'file-stream arg0 'write)))
(file-stream-write s5-0 (&-> this type) (+ (-> this type size) (the-as uint (-> this length))))
(file-stream-close s5-0))
this)
(defmethod load-from-file! ((this game-save) (filename string))
"Load a game save from a file for debugging"
(let ((stream (new 'stack 'file-stream filename 'read)))
(let ((in-size (file-stream-length stream))
(my-size (-> this allocated-length)))
(cond
((>= (asize-of this) in-size)
;; thing in file is not bigger than we are, safe to read.
(cond
((= (file-stream-read stream (&-> this type) in-size) in-size)
;; read success! set the type tag
(set! (-> this type) game-save))
(else
;; fail.
(format 0 "ERROR: SAVEGAME: save file ~A did not read correctly.~%" stream)
(set! (-> this length) 0)
0)))
(else
;; file is bigger than we are, just give up because we don't have
;; enough room to put the save
(format 0 "ERROR: SAVEGAME: save file ~A is too big~%" stream)))
(set! (-> this allocated-length) my-size))
(when (!= (-> this version) SAVE_VERSION)
;; uh-oh, the version is wrong
(format 0
"ERROR: SAVEGAME: save file ~A was version ~d, but only ~d is supported.~%"
stream
(-> this version)
SAVE_VERSION)
(set! (-> this length) 0))
(file-stream-close stream))
this)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; particles
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; used for the flashing auto-save icon.
(defpartgroup group-part-save-icon
:id 656
:flags (screen-space)
:bounds (static-bspherem 0 0 0 100)
:parts ((sp-item 2662)))
(defpart 2662
:init-specs
((:texture (checkpoint common))
(:num 1.0)
(:scale-x (meters 1.5))
(:scale-y :copy scale-x)
(:r 128.0)
(:g 128.0)
(:b 128.0)
(:a 128.0)
(:timer (seconds 0.017))
(:flags (bit2 bit9 bit13))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; auto-save process
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; the status of the memory cards.
(define *auto-save-info* (new 'global 'mc-slot-info))
(deftype auto-save (process)
((card int32)
(slot int32)
(which int32)
(buffer kheap)
(mode basic)
(result mc-status-code)
(save game-save)
(info mc-slot-info :inline)
(notify handle)
(state-time time-frame)
(part sparticle-launch-control))
(:state-methods
get-heap
get-card
format-card
create-file
save
restore
(error mc-status-code)
done
unformat-card))
(defmethod deactivate ((this auto-save))
"Deactivate the auto-save process."
;; kill the particles
(if (nonzero? (-> this part)) (kill-and-free-particles (-> this part)))
;; and do a normal deactivate.
((method-of-type process deactivate) this)
(none))
(defmethod relocate ((this auto-save) (offset int))
"Relocate an auto-save process by arg0 bytes."
;; update our reference particle launch control, which is allocated on our process heap
(if (nonzero? (-> this part)) (&+! (-> this part) offset))
;; then relocate the process. This will relocate the heap (memory, and basics on it).
(the-as auto-save ((method-of-type process relocate) this offset)))
(defbehavior auto-save-post auto-save ()
(when (and (= *cheat-mode* 'debug) (cpad-hold? 0 l3))
(let ((gp-0 (new 'stack 'font-context *font-default-matrix* 32 160 0.0 (font-color default) (font-flags shadow kerning))))
(set-width! gp-0 440)
(set-height! gp-0 80)
(set! (-> gp-0 flags) (font-flags shadow kerning))
(format (clear *temp-string*) "~S / ~S ~D~%" (-> self mode) (-> self state name) (-> self which))
(print-game-text *temp-string* gp-0 #f 128 22)))
;; auto-save drawing
(when (and (= (-> self mode) 'auto-save) (!= (-> self next-state name) 'done))
(let ((gp-1 (new 'stack 'font-context *font-default-matrix* 20 40 0.0 (font-color default) (font-flags shadow kerning))))
(set-scale! gp-1 0.8)
(set-width! gp-1 472)
(set-height! gp-1 20)
(set! (-> gp-1 flags) (font-flags shadow kerning middle middle-vert large))
;; if this is the first time saving, display a warning.
(when (zero? (-> *game-info* auto-save-count))
(print-game-text (lookup-text! *common-text* (text-id saving-data) #f) gp-1 #f 128 22)
(set! (-> gp-1 origin x) 20.0)
(set! (-> gp-1 origin y) 130.0)
(set-scale! gp-1 0.7)
(set-height! gp-1 40)
(let ((s5-2 print-game-text))
((the-as (function object string object none) format)
(clear *temp-string*)
(lookup-text! *common-text* (text-id memcard-do-not-remove) #f)
1)
(s5-2 *temp-string* gp-1 #f 128 22))))
;; flash the icon.
(when (< (mod (-> *display* real-frame-counter) 300) 270)
(if (> (-> self part matrix) 0)
(set-vector! (sprite-get-user-hvdf (-> self part matrix))
1842.0
(the float
(+ (the int
(* 0.5
(- (* (if (= (get-aspect-ratio) 'aspect16x9) 370.0 360.0) (-> *video-parms* relative-y-scale))
(the float (-> *video-parms* screen-sy)))))
2048))
(+ -1024.0 (-> *math-camera* hvdf-off z))
(-> *math-camera* hvdf-off w)))
(spawn (-> self part) *zero-vector*)))
(none))
(defstatehandler auto-save :post auto-save-post)
(defbehavior auto-save-init-by-other auto-save ((desired-mode symbol) (notify-proc process-tree) (card-idx int) (file-idx int))
;; trying to create multiple auto save procs, bad idea.
(when (handle->process (-> *game-info* auto-save-proc))
(send-event notify-proc 'notify 'error 16)
(return #f))
;; set us as the auto save proc
(set! (-> *game-info* auto-save-proc) (process->handle self))
(set! (-> *game-info* auto-save-status) (mc-status-code ok))
(stack-size-set! (-> self main-thread) 512)
(logclear! (-> self mask) (process-mask pause menu progress))
;; setup ourself
(set! (-> self card) card-idx)
(set! (-> self which) file-idx)
(set! (-> self buffer) #f)
(set! (-> self mode) desired-mode)
(set! (-> self result) (mc-status-code ok))
(set! (-> self save) #f)
(set! (-> self notify) (process->handle notify-proc))
(set! (-> self part) (create-launch-control group-part-save-icon self))
(set! (-> self part matrix) (sprite-allocate-user-hvdf))
(cond
((= desired-mode 'auto-save)
;; also save pc settings.
(with-pc
(commit-to-file *pc-settings*))
(if (not (-> *setting-control* current auto-save)) (go-virtual error (mc-status-code no-auto-save)))
(when (and (zero? (-> self card)) (-> *setting-control* current auto-save))
(set! (-> self card) (-> *game-info* auto-save-card))
(set! (-> self which) (-> *game-info* auto-save-which))))
((= desired-mode 'error) (set! (-> *setting-control* default auto-save) #f) (go-virtual error (mc-status-code no-card))))
(set! (-> *setting-control* default auto-save) #f)
(go-virtual get-heap)
(none))
;; Get heap memory.
(defstate get-heap (auto-save)
:virtual #t
:code
(behavior ()
(set! (-> self state-time) (-> *display* real-frame-counter))
(let ((a0-1 (reserve-alloc *art-control*)))
(while (not a0-1)
(if (>= (- (-> *display* real-frame-counter) (-> self state-time)) (seconds 60))
(go-virtual error (mc-status-code no-memory)))
(suspend)
(set! a0-1 (reserve-alloc *art-control*)))
(set! (-> self buffer) a0-1))
(go-virtual get-card)))
(defstate get-card (auto-save)
:virtual #t
:code
(behavior ()
(label cfg-0)
(mc-get-slot-info (-> self slot) (-> self info))
(when (zero? (-> self info known))
(suspend)
(goto cfg-0))
(cond
((zero? (-> self info handle)) (go-virtual error (mc-status-code no-card)))
((zero? (-> self card)) (set! (-> self card) (-> self info handle)))
((!= (-> self info handle) (-> self card)) (go-virtual error (mc-status-code bad-handle))))
(case (-> self mode)
(('save 'auto-save) (go-virtual save))
(('save-last)
(set! (-> self which) (-> self info last-file))
(if (= (-> self which) -1) (go-virtual error (mc-status-code no-last)) (go-virtual save)))
(('restore) (go-virtual restore))
(('format-card) (go-virtual format-card))
(('unformat-card) (go-virtual unformat-card))
(('create-file) (go-virtual create-file))
(else (go-virtual done)))))
(defstate format-card (auto-save)
:virtual #t
:code
(behavior ()
(when (zero? (-> self info formatted))
(label cfg-1)
(set! (-> self result) (mc-format (-> self card)))
(when (!= (-> self result) (mc-status-code ok))
(suspend)
(goto cfg-1))
(label cfg-3)
(set! (-> self result) (the-as mc-status-code (mc-check-result)))
(let ((v1-4 (-> self result)))
(b! (nonzero? v1-4) cfg-5 :delay (nop!))
(b! #t cfg-10 :delay (nop!))
(label cfg-5)
(b! (= v1-4 (mc-status-code format-failed)) cfg-1 :delay (nop!))
(nop!)
(b! (!= v1-4 (mc-status-code ok)) cfg-9 :delay (nop!)))
(b! #t cfg-12 :delay (nop!))
(the-as none 0)
(b! #t cfg-10 :delay (nop!))
(label cfg-9)
(go-virtual error (-> self result))
(label cfg-10)
(suspend)
(b! #t cfg-3 :delay (nop!)))
(label cfg-12)
(case (-> self mode)
(('create-file 'save 'save-last 'auto-save 'restore) (go-virtual create-file)))
(go-virtual done)))
(defstate unformat-card (auto-save)
:virtual #t
:code
(behavior ()
(when (nonzero? (-> self info formatted))
(label cfg-1)
(set! (-> self result) (mc-unformat (-> self card)))
(when (!= (-> self result) (mc-status-code ok))
(suspend)
(goto cfg-1))
(loop
(set! (-> self result) (the-as mc-status-code (mc-check-result)))
(case (-> self result)
(((mc-status-code busy)))
(((mc-status-code ok)) (goto cfg-11))
(else (go-virtual error (-> self result))))
(suspend)))
(label cfg-11)
(go-virtual done)))
(defstate create-file (auto-save)
:virtual #t
:code
(behavior ()
(cond
((zero? (-> self info formatted)) (go-virtual error (mc-status-code no-format)))
((zero? (-> self info inited))
(if (< (-> self info mem-actual) (-> self info mem-required)) (go-virtual error (mc-status-code no-space)))
(let ((v1-12 (-> self buffer))) (set! (-> v1-12 current) (-> v1-12 base)))
(label cfg-6)
(set! (-> self result) (mc-create-file (-> self card) (the-as uint (-> self buffer base))))
(when (!= (-> self result) (mc-status-code ok))
(suspend)
(goto cfg-6))
(loop
(set! (-> self result) (the-as mc-status-code (mc-check-result)))
(case (-> self result)
(((mc-status-code busy)))
(((mc-status-code ok)) (goto cfg-16))
(else (go-virtual error (-> self result))))
(suspend))))
(label cfg-16)
(case (-> self mode)
(('restore) (go-virtual restore))
(('save 'save-last 'auto-save) (go-virtual save)))
(go-virtual done)))
(defstate save (auto-save)
:virtual #t
:code
(behavior ()
(cond
((zero? (-> self info formatted)) (go-virtual error (mc-status-code no-format)))
((zero? (-> self info inited)) (go-virtual error (mc-status-code no-file))))
(case (-> self mode)
(('auto-save) (+! (-> *game-info* auto-save-count) 1)))
(let ((v1-14 (-> self buffer))) (set! (-> v1-14 current) (-> v1-14 base)))
(let ((gp-0 (the-as object loading-level)))
(set! loading-level (-> self buffer))
;; og:preserve-this
;; pc port note : the original game used 64K here's which is slightly too small for the save file.
;; the buffer being used here is one of the spool buffers which are much larger
;; this means that the original game only cleared the first 64K of that buffer when allocating
;; a new save file which would leave the last parts to be filled with garbage, which may or may not cause issues.
;; for the port we clear the entire spool buffer instead, entirely bypassing this potential issue.
(set! (-> self save) (new 'loading-level 'game-save (- SPOOL_HEAP_SIZE (psize-of game-save))))
(save-game! *game-info* (-> self save) "save")
(set! loading-level (the-as kheap gp-0))
;0
(when *debug-segment*
(debug-print (-> self save) (user? dass)))
(label cfg-7)
(set! (-> self result)
(mc-save (-> self card) (-> self which) (&-> self save type) (the-as int (-> self save info-int32))))
(when (!= (-> self result) (mc-status-code ok))
(suspend)
(goto cfg-7))
(loop
(set! (-> self result) (the-as mc-status-code (mc-check-result)))
(let ((v1-24 (-> self result)))
(set! gp-0
(cond
((= v1-24 (mc-status-code busy)) #f)
((= v1-24 (mc-status-code ok)) (goto cfg-21) gp-0)
((= v1-24 (mc-status-code write-error)) (suspend) gp-0)
(else
(case (-> self mode)
(('auto-save) (seekl! (-> *game-info* auto-save-count) 0 1)))
(go-virtual error (-> self result))))))
(suspend)))
(label cfg-21)
(go-virtual done)))
(defstate restore (auto-save)
:virtual #t
:code
(behavior ()
(local-vars (gp-0 object))
(cond
((zero? (-> self info formatted)) (go-virtual error (mc-status-code no-format)))
((zero? (-> self info inited)) (go-virtual error (mc-status-code no-file))))
(let ((v1-10 (-> self buffer))) (set! (-> v1-10 current) (-> v1-10 base)))
(if (zero? (-> self info file (-> self which) present)) (go-virtual error (mc-status-code no-save)))
(label cfg-6)
(set! (-> self result) (mc-load (-> self card) (-> self which) (-> self buffer base)))
(when (!= (-> self result) (mc-status-code ok))
(suspend)
(goto cfg-6))
(loop
(set! (-> self result) (the-as mc-status-code (mc-check-result)))
(let ((v1-22 (-> self result)))
(set! gp-0
(cond
((= v1-22 (mc-status-code busy)) #f)
((= v1-22 (mc-status-code ok)) (goto cfg-20) gp-0)
((= v1-22 (mc-status-code read-error)) (suspend) gp-0)
((= v1-22 (mc-status-code new-game)) (go-virtual error (mc-status-code no-save)))
(else (go-virtual error (-> self result))))))
(suspend))
(label cfg-20)
(set! (-> self save) (the-as game-save (&+ (-> self buffer base) 4)))
(let ((v1-34 (-> self save)))
(set! (-> v1-34 type) game-save)
(if (!= (-> v1-34 version) 1) (go-virtual error (mc-status-code bad-version))))
(set-setting! 'music-volume 'abs 0.0 0)
(set-setting! 'sfx-volume 'abs 0.0 0)
(set! (-> *game-info* mode) 'play)
(initialize! *game-info* 'game (-> self save) (the-as string #f))
(set-master-mode 'game)
(add-setting! 'process-mask 'set 0.0 (process-mask progress))
(apply-settings *setting-control*)
(dotimes (gp-1 15)
(suspend))
(go-virtual done)))
(defstate error (auto-save)
:virtual #t
:event
(behavior ((proc process) (argc int) (message symbol) (block event-message-block))
(case message
(('progress-allowed?) #t)
(('die) (deactivate self))))
:code
(behavior ((arg0 mc-status-code))
(if (-> self buffer) (reserve-free *art-control* (-> self buffer)))
(set! (-> self result) arg0)
(let ((s5-0 *auto-save-info*))
(mem-copy! (the-as pointer s5-0) (the-as pointer (-> self info)) 300)
(send-event (handle->process (-> self notify)) 'notify 'error (-> self result) s5-0))
(format #t "SAVE ERROR: ~A~%" (enum->string mc-status-code (-> self result)))
(if (= (-> self result) (mc-status-code no-auto-save)) (return #f))
(case (-> self mode)
(('auto-save 'error)
(set! (-> self state-time) (-> *display* real-frame-counter))
(set! (-> *game-info* auto-save-status) arg0)
(while (< (- (-> *display* real-frame-counter) (-> self state-time)) (seconds 0.2))
(if (not (progress-allowed?)) (set! (-> self state-time) (-> *display* real-frame-counter)))
(suspend))
(if (= arg0 (mc-status-code no-card))
(activate-progress *dproc* (progress-screen memcard-removed))
(activate-progress *dproc* (progress-screen memcard-auto-save-error)))))))
(defstate done (auto-save)
:virtual #t
:code
(behavior ()
(if (and (-> self buffer) (-> *art-control* reserve-buffer)) (reserve-free *art-control* (-> self buffer)))
(set! (-> *game-info* auto-save-status) (mc-status-code ok))
(case (-> self mode)
(('save 'save-last 'auto-save 'restore)
(set! (-> *setting-control* default auto-save) #t)
(set! (-> *game-info* auto-save-card) (-> self card))
(set! (-> *game-info* auto-save-which) (-> self which))))
(let ((gp-0 *auto-save-info*))
(mem-copy! (the-as pointer gp-0) (the-as pointer (-> self info)) 300)
(send-event (handle->process (-> self notify)) 'notify 'done 1 gp-0))
(case (-> self mode)
(('auto-save)
;; og:preserve-this Don't display the first auto-save warning prompt if speedrunning
(when (and (= (-> *game-info* auto-save-count) 1) (not (-> *pc-settings* speedrunner-mode?)))
(set! (-> self event-hook) (-> (method-of-object self error) event))
(set! (-> self state-time) (-> *display* real-frame-counter))
(while (< (- (-> *display* real-frame-counter) (-> self state-time)) (seconds 0.2))
(if (not (progress-allowed?)) (set! (-> self state-time) (-> *display* real-frame-counter)))
(suspend))
(activate-progress *dproc* (progress-screen auto-save)))))))
(defun auto-save-command ((arg0 symbol) (arg1 int) (arg2 int) (arg3 process-tree))
(process-spawn auto-save arg0 arg3 arg1 arg2 :stack *kernel-dram-stack*)
(none))
(defun auto-save-check ()
(when (and (-> *setting-control* current auto-save) (not (handle->process (-> *game-info* auto-save-proc))))
(mc-get-slot-info 0 *auto-save-info*)
(if (and (nonzero? (-> *auto-save-info* known))
(or (zero? (-> *auto-save-info* handle)) (!= (-> *auto-save-info* handle) (-> *game-info* auto-save-card))))
(auto-save-command 'error 0 0 *default-pool*)))
0
(none))