diff --git a/api/account.go b/api/account.go index 7153778..7872052 100644 --- a/api/account.go +++ b/api/account.go @@ -5,21 +5,22 @@ import ( "crypto/rand" "database/sql" "encoding/base64" - "encoding/hex" "encoding/json" "fmt" "net/http" "os" "regexp" + "strconv" + "time" "github.com/Flashfyre/pokerogue-server/db" "golang.org/x/crypto/argon2" ) const ( - argonTime = 1 - argonMemory = 256*1024 - argonThreads = 4 + argonTime = 1 + argonMemory = 256 * 1024 + argonThreads = 4 argonKeyLength = 32 ) @@ -27,9 +28,9 @@ var isValidUsername = regexp.MustCompile(`^\w{1,16}$`).MatchString // /account/info - get account info -type AccountInfoResponse struct{ - Username string `json:"username"` - HasGameSession bool `json:"hasGameSession"` +type AccountInfoResponse struct { + Username string `json:"username"` + LastSessionSlot int `json:"lastSessionSlot"` } func (s *Server) HandleAccountInfo(w http.ResponseWriter, r *http.Request) { @@ -45,9 +46,26 @@ func (s *Server) HandleAccountInfo(w http.ResponseWriter, r *http.Request) { return } - _, err = os.Stat("userdata/" + hex.EncodeToString(uuid) + "/session.pzs") + var latestSaveTime time.Time + latestSaveId := -1 + for id := range sessionSlotCount { + fileName := "session" + if id != 0 { + fileName += strconv.Itoa(id) + } - response, err := json.Marshal(AccountInfoResponse{Username: username, HasGameSession: err == nil}) + stat, err := os.Stat(fmt.Sprintf("userdata/%x/%s.pzs", uuid, fileName)) + if err != nil { + continue + } + + if stat.ModTime().After(latestSaveTime) { + latestSaveTime = stat.ModTime() + latestSaveId = id + } + } + + response, err := json.Marshal(AccountInfoResponse{Username: username, LastSessionSlot: latestSaveId}) if err != nil { http.Error(w, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError) return diff --git a/api/savedata.go b/api/savedata.go index 2f4abad..62be50d 100644 --- a/api/savedata.go +++ b/api/savedata.go @@ -8,10 +8,13 @@ import ( "fmt" "net/http" "os" + "strconv" "github.com/klauspost/compress/zstd" ) +const sessionSlotCount = 3 + // /savedata/get - get save data func (s *Server) HandleSavedataGet(w http.ResponseWriter, r *http.Request) { @@ -60,7 +63,23 @@ func (s *Server) HandleSavedataGet(w http.ResponseWriter, r *http.Request) { w.Write(saveJson) case "1": // Session - save, err := os.ReadFile("userdata/" + hexUuid + "/session.pzs") + slotId, err := strconv.Atoi(r.URL.Query().Get("slot")) + if err != nil { + http.Error(w, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest) + return + } + + if slotId < 0 || slotId >= sessionSlotCount { + http.Error(w, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest) + return + } + + fileName := "session" + if slotId != 0 { + fileName += strconv.Itoa(slotId) + } + + save, err := os.ReadFile(fmt.Sprintf("userdata/%s/%s.pzs", hexUuid, fileName)) if err != nil { http.Error(w, fmt.Sprintf("failed to read save file: %s", err), http.StatusInternalServerError) return @@ -152,6 +171,22 @@ func (s *Server) HandleSavedataUpdate(w http.ResponseWriter, r *http.Request) { return } case "1": // Session + slotId, err := strconv.Atoi(r.URL.Query().Get("slot")) + if err != nil { + http.Error(w, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest) + return + } + + if slotId < 0 || slotId >= sessionSlotCount { + http.Error(w, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest) + return + } + + fileName := "session" + if slotId != 0 { + fileName += strconv.Itoa(slotId) + } + var session SessionSaveData err = json.NewDecoder(r.Body).Decode(&session) if err != nil { @@ -180,7 +215,7 @@ func (s *Server) HandleSavedataUpdate(w http.ResponseWriter, r *http.Request) { return } - err = os.WriteFile("userdata/"+hexUuid+"/session.pzs", compressed, 0644) + err = os.WriteFile(fmt.Sprintf("userdata/%s/session%s.pzs", hexUuid, fileName), compressed, 0644) if err != nil { http.Error(w, fmt.Sprintf("failed to write save file: %s", err), http.StatusInternalServerError) return @@ -212,7 +247,23 @@ func (s *Server) HandleSavedataDelete(w http.ResponseWriter, r *http.Request) { return } case "1": // Session - err := os.Remove("userdata/" + hexUuid + "/session.pzs") + slotId, err := strconv.Atoi(r.URL.Query().Get("slot")) + if err != nil { + http.Error(w, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest) + return + } + + if slotId < 0 || slotId >= sessionSlotCount { + http.Error(w, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest) + return + } + + fileName := "session" + if slotId != 0 { + fileName += strconv.Itoa(slotId) + } + + err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hexUuid, fileName)) if err != nil && !os.IsNotExist(err) { http.Error(w, fmt.Sprintf("failed to delete save file: %s", err), http.StatusInternalServerError) return