- name = linenoise("\nFile name: ");
- if (name == NULL)
- return GO_TOP;
- fp = fopen(name,(resume ? READ_MODE : WRITE_MODE));
- if (fp == NULL)
- printf("Can't open file %s, try again.\n", name);
- }
- linenoiseFree(name);
-
- DATIME(&i,&k);
- k=i+650*k;
- if (!resume)
- {
- save.savetime = k;
- save.mode = -1;
- save.version = VRSION;
- memcpy(&save.game, &game, sizeof(struct game_t));
- save.bird = OBJSND[BIRD];
- save.bivalve = OBJTXT[OYSTER];
- IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp));
- fclose(fp);
- RSPEAK(266);
- exit(0);
- } else {
- IGNORE(fread(&save, sizeof(struct save_t), 1, fp));
- fclose(fp);
- if (save.version != VRSION) {
- SETPRM(1,k/10,MOD(k,10));
- SETPRM(3,VRSION/10,MOD(VRSION,10));
- RSPEAK(269);
- } else {
- memcpy(&game, &save.game, sizeof(struct game_t));
- OBJSND[BIRD] = save.bird;
- OBJTXT[OYSTER] = save.bivalve;
- game.zzword=RNDVOC(3,game.zzword);
- }
- return GO_TOP;
+ char* name = readline("\nFile name: ");
+ if (name == NULL)
+ return GO_TOP;
+ fp = fopen(name, READ_MODE);
+ if (fp == NULL)
+ printf("Can't open file %s, try again.\n", name);
+ free(name);
+ }
+
+ return restore(fp);
+}
+
+int restore(FILE* fp)
+{
+ /* Read and restore game state from file, assuming
+ * sane initial state.
+ * If ADVENT_NOSAVE is defined, do nothing instead. */
+#ifdef ADVENT_NOSAVE
+ return GO_UNKNOWN;
+#endif
+
+ IGNORE(fread(&save, sizeof(struct save_t), 1, fp));
+ fclose(fp);
+ if (save.version != VRSION) {
+ rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), VRSION / 10, MOD(VRSION, 10));
+ } else if (is_valid(save.game)) {
+ game = save.game;
+ }
+ return GO_TOP;
+}
+
+bool is_valid(struct game_t valgame)
+{
+ /* Save files can be roughly grouped into three groups:
+ * With valid, reaceable state, with valid, but unreachable
+ * state and with invaild state. We check that state is
+ * valid: no states are outside minimal or maximal value
+ */
+
+ /* Prevent division by zero */
+ if (valgame.abbnum == 0) {
+ return false; // LCOV_EXCL_LINE
+ }
+
+ /* Check for RNG overflow. Truncate */
+ if (valgame.lcg_x >= LCG_M) {
+ valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE
+ }
+
+ /* Check for RNG underflow. Transpose */
+ if (valgame.lcg_x < LCG_M) {
+ valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M);
+ }
+
+ /* Bounds check for locations */
+ if ( valgame.chloc < -1 || valgame.chloc > NLOCATIONS ||
+ valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS ||
+ valgame.loc < 0 || valgame.loc > NLOCATIONS ||
+ valgame.newloc < 0 || valgame.newloc > NLOCATIONS ||
+ valgame.oldloc < 0 || valgame.oldloc > NLOCATIONS ||
+ valgame.oldlc2 < 0 || valgame.oldlc2 > NLOCATIONS) {
+ return false; // LCOV_EXCL_LINE
+ }
+ /* Bounds check for location arrays */
+ for (int i = 0; i <= NDWARVES; i++) {
+ if (valgame.dloc[i] < -1 || valgame.dloc[i] > NLOCATIONS ||
+ valgame.odloc[i] < -1 || valgame.odloc[i] > NLOCATIONS) {
+ return false; // LCOV_EXCL_LINE
+ }