A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/*
2 * midend.c: general middle fragment sitting between the
3 * platform-specific front end and game-specific back end.
4 * Maintains a move list, takes care of Undo and Redo commands, and
5 * processes standard keystrokes for undo/redo/new/quit.
6 */
7
8#include <stdio.h>
9#include <string.h>
10#include <assert.h>
11#include <stdlib.h>
12#include <ctype.h>
13
14#include "puzzles.h"
15
16enum { DEF_PARAMS, DEF_SEED, DEF_DESC }; /* for midend_game_id_int */
17
18enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */
19
20#define special(type) ( (type) != MOVE )
21
22struct midend_state_entry {
23 game_state *state;
24 char *movestr;
25 int movetype;
26};
27
28struct midend_serialise_buf {
29 char *buf;
30 int len, size;
31};
32
33struct midend_serialise_buf_read_ctx {
34 struct midend_serialise_buf *ser;
35 int len, pos;
36};
37
38struct midend {
39 frontend *frontend;
40 random_state *random;
41 const game *ourgame;
42
43 struct preset_menu *preset_menu;
44 char **encoded_presets; /* for midend_which_preset to check against */
45 int n_encoded_presets;
46
47 /*
48 * `desc' and `privdesc' deserve a comment.
49 *
50 * `desc' is the game description as presented to the user when
51 * they ask for Game -> Specific. `privdesc', if non-NULL, is a
52 * different game description used to reconstruct the initial
53 * game_state when de-serialising. If privdesc is NULL, `desc'
54 * is used for both.
55 *
56 * For almost all games, `privdesc' is NULL and never used. The
57 * exception (as usual) is Mines: the initial game state has no
58 * squares open at all, but after the first click `desc' is
59 * rewritten to describe a game state with an initial click and
60 * thus a bunch of squares open. If we used that desc to
61 * serialise and deserialise, then the initial game state after
62 * deserialisation would look unlike the initial game state
63 * beforehand, and worse still execute_move() might fail on the
64 * attempted first click. So `privdesc' is also used in this
65 * case, to provide a game description describing the same
66 * fixed mine layout _but_ no initial click. (These game IDs
67 * may also be typed directly into Mines if you like.)
68 */
69 char *desc, *privdesc, *seedstr;
70 char *aux_info;
71 enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
72
73 int nstates, statesize, statepos;
74 struct midend_state_entry *states;
75
76 struct midend_serialise_buf newgame_undo, newgame_redo;
77 bool newgame_can_store_undo;
78
79 game_params *params, *curparams;
80 game_drawstate *drawstate;
81 bool first_draw;
82 game_ui *ui;
83
84 game_state *oldstate;
85 float anim_time, anim_pos;
86 float flash_time, flash_pos;
87 int dir;
88
89 bool timing;
90 float elapsed;
91 char *laststatus;
92
93 drawing *drawing;
94
95 int pressed_mouse_button;
96
97 struct midend_serialise_buf be_prefs;
98
99 int preferred_tilesize, preferred_tilesize_dpr, tilesize;
100 int winwidth, winheight;
101
102 void (*game_id_change_notify_function)(void *);
103 void *game_id_change_notify_ctx;
104
105 bool one_key_shortcuts;
106};
107
108#define ensure(me) do { \
109 if ((me)->nstates >= (me)->statesize) { \
110 (me)->statesize = (me)->nstates + 128; \
111 (me)->states = sresize((me)->states, (me)->statesize, \
112 struct midend_state_entry); \
113 } \
114} while (0)
115
116/*
117 * Structure storing all the decoded data from reading a serialised
118 * game. We keep it in one of these while we check its sanity, and
119 * only once we're completely satisfied do we install it all in the
120 * midend structure proper.
121 */
122struct deserialise_data {
123 char *seed, *parstr, *desc, *privdesc;
124 char *auxinfo, *uistr, *cparstr;
125 float elapsed;
126 game_params *params, *cparams;
127 game_ui *ui;
128 struct midend_state_entry *states;
129 int nstates, statepos;
130};
131
132/*
133 * Forward references.
134 */
135static const char *midend_deserialise_internal(
136 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx,
137 const char *(*check)(void *ctx, midend *, const struct deserialise_data *),
138 void *cctx);
139static void midend_serialise_prefs(
140 midend *me, game_ui *ui,
141 void (*write)(void *ctx, const void *buf, int len), void *wctx);
142static const char *midend_deserialise_prefs(
143 midend *me, game_ui *ui,
144 bool (*read)(void *ctx, void *buf, int len), void *rctx);
145static config_item *midend_get_prefs(midend *me, game_ui *ui);
146static void midend_set_prefs(midend *me, game_ui *ui, config_item *all_prefs);
147static void midend_apply_prefs(midend *me, game_ui *ui);
148
149void midend_reset_tilesize(midend *me)
150{
151 me->preferred_tilesize = me->ourgame->preferred_tilesize;
152 me->preferred_tilesize_dpr = 1.0;
153 {
154 /*
155 * Allow an environment-based override for the default tile
156 * size by defining a variable along the lines of
157 * `NET_TILESIZE=15'.
158 *
159 * XXX How should this interact with DPR?
160 */
161
162 char buf[80], *e;
163 int j, k, ts;
164
165 sprintf(buf, "%s_TILESIZE", me->ourgame->name);
166 for (j = k = 0; buf[j]; j++)
167 if (!isspace((unsigned char)buf[j]))
168 buf[k++] = toupper((unsigned char)buf[j]);
169 buf[k] = '\0';
170 if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0)
171 me->preferred_tilesize = ts;
172 }
173}
174
175midend *midend_new(frontend *fe, const game *ourgame,
176 const drawing_api *drapi, void *drhandle)
177{
178 midend *me = snew(midend);
179 void *randseed;
180 int randseedsize;
181
182 get_random_seed(&randseed, &randseedsize);
183
184 me->frontend = fe;
185 me->ourgame = ourgame;
186 me->random = random_new(randseed, randseedsize);
187 me->nstates = me->statesize = me->statepos = 0;
188 me->states = NULL;
189 me->newgame_undo.buf = NULL;
190 me->newgame_undo.size = me->newgame_undo.len = 0;
191 me->newgame_redo.buf = NULL;
192 me->newgame_redo.size = me->newgame_redo.len = 0;
193 me->newgame_can_store_undo = false;
194 me->params = ourgame->default_params();
195 me->game_id_change_notify_function = NULL;
196 me->game_id_change_notify_ctx = NULL;
197 me->encoded_presets = NULL;
198 me->n_encoded_presets = 0;
199
200 /*
201 * Allow environment-based changing of the default settings by
202 * defining a variable along the lines of `NET_DEFAULT=25x25w'
203 * in which the value is an encoded parameter string.
204 */
205 {
206 char buf[80], *e;
207 int j, k;
208 sprintf(buf, "%s_DEFAULT", me->ourgame->name);
209 for (j = k = 0; buf[j]; j++)
210 if (!isspace((unsigned char)buf[j]))
211 buf[k++] = toupper((unsigned char)buf[j]);
212 buf[k] = '\0';
213 if ((e = getenv(buf)) != NULL)
214 me->ourgame->decode_params(me->params, e);
215 }
216 me->curparams = NULL;
217 me->desc = me->privdesc = NULL;
218 me->seedstr = NULL;
219 me->aux_info = NULL;
220 me->genmode = GOT_NOTHING;
221 me->drawstate = NULL;
222 me->first_draw = true;
223 me->oldstate = NULL;
224 me->preset_menu = NULL;
225 me->anim_time = me->anim_pos = 0.0F;
226 me->flash_time = me->flash_pos = 0.0F;
227 me->dir = 0;
228 me->ui = NULL;
229 me->pressed_mouse_button = 0;
230 me->laststatus = NULL;
231 me->timing = false;
232 me->elapsed = 0.0F;
233 me->tilesize = me->winwidth = me->winheight = 0;
234 if (drapi)
235 me->drawing = drawing_new(drapi, me, drhandle);
236 else
237 me->drawing = NULL;
238
239 me->be_prefs.buf = NULL;
240 me->be_prefs.size = me->be_prefs.len = 0;
241
242 me->one_key_shortcuts = true;
243
244 midend_reset_tilesize(me);
245
246 sfree(randseed);
247
248 return me;
249}
250
251const game *midend_which_game(midend *me)
252{
253 return me->ourgame;
254}
255
256static void midend_purge_states(midend *me)
257{
258 while (me->nstates > me->statepos) {
259 me->ourgame->free_game(me->states[--me->nstates].state);
260 if (me->states[me->nstates].movestr)
261 sfree(me->states[me->nstates].movestr);
262 }
263 me->newgame_redo.len = 0;
264}
265
266static void midend_free_game(midend *me)
267{
268 while (me->nstates > 0) {
269 me->nstates--;
270 me->ourgame->free_game(me->states[me->nstates].state);
271 sfree(me->states[me->nstates].movestr);
272 }
273
274 if (me->drawstate)
275 me->ourgame->free_drawstate(me->drawing, me->drawstate);
276}
277
278static void midend_free_preset_menu(midend *me, struct preset_menu *menu)
279{
280 if (menu) {
281 int i;
282 for (i = 0; i < menu->n_entries; i++) {
283 sfree(menu->entries[i].title);
284 if (menu->entries[i].params)
285 me->ourgame->free_params(menu->entries[i].params);
286 midend_free_preset_menu(me, menu->entries[i].submenu);
287 }
288 sfree(menu->entries);
289 sfree(menu);
290 }
291}
292
293void midend_free(midend *me)
294{
295 int i;
296
297 midend_free_game(me);
298
299 for (i = 0; i < me->n_encoded_presets; i++)
300 sfree(me->encoded_presets[i]);
301 sfree(me->encoded_presets);
302 if (me->drawing)
303 drawing_free(me->drawing);
304 random_free(me->random);
305 sfree(me->newgame_undo.buf);
306 sfree(me->newgame_redo.buf);
307 sfree(me->states);
308 sfree(me->desc);
309 sfree(me->privdesc);
310 sfree(me->seedstr);
311 sfree(me->aux_info);
312 sfree(me->be_prefs.buf);
313 me->ourgame->free_params(me->params);
314 midend_free_preset_menu(me, me->preset_menu);
315 if (me->ui)
316 me->ourgame->free_ui(me->ui);
317 if (me->curparams)
318 me->ourgame->free_params(me->curparams);
319 sfree(me->laststatus);
320 sfree(me);
321}
322
323static void midend_size_new_drawstate(midend *me)
324{
325 /*
326 * Don't even bother, if we haven't worked out our tile size
327 * anyway yet.
328 */
329 if (me->tilesize > 0) {
330 me->ourgame->compute_size(me->params, me->tilesize, me->ui,
331 &me->winwidth, &me->winheight);
332 me->ourgame->set_size(me->drawing, me->drawstate,
333 me->params, me->tilesize);
334 }
335}
336
337/*
338 * There is no one correct way to convert tilesizes between device
339 * pixel ratios, because there's only a loosely-defined relationship
340 * between tilesize and the actual size of a puzzle. We define this
341 * function as the canonical conversion function so everything in the
342 * midend will be consistent.
343 */
344static int convert_tilesize(midend *me, int old_tilesize,
345 double old_dpr, double new_dpr)
346{
347 int x, y, rx, ry, min, max;
348 game_params *defaults;
349
350 if (new_dpr == old_dpr)
351 return old_tilesize;
352
353 defaults = me->ourgame->default_params();
354
355 me->ourgame->compute_size(defaults, old_tilesize, me->ui, &x, &y);
356 x *= new_dpr / old_dpr;
357 y *= new_dpr / old_dpr;
358
359 min = max = 1;
360 do {
361 max *= 2;
362 me->ourgame->compute_size(defaults, max, me->ui, &rx, &ry);
363 } while (rx <= x && ry <= y);
364
365 while (max - min > 1) {
366 int mid = (max + min) / 2;
367 me->ourgame->compute_size(defaults, mid, me->ui, &rx, &ry);
368 if (rx <= x && ry <= y)
369 min = mid;
370 else
371 max = mid;
372 }
373
374 me->ourgame->free_params(defaults);
375 return min;
376}
377
378void midend_size(midend *me, int *x, int *y, bool user_size,
379 double device_pixel_ratio)
380{
381 int min, max;
382 int rx, ry;
383
384 /*
385 * We can't set the size on the same drawstate twice. So if
386 * we've already sized one drawstate, we must throw it away and
387 * create a new one.
388 */
389 if (me->drawstate && me->tilesize > 0) {
390 me->ourgame->free_drawstate(me->drawing, me->drawstate);
391 me->drawstate = me->ourgame->new_drawstate(me->drawing,
392 me->states[0].state);
393 me->first_draw = true;
394 }
395
396 /*
397 * Find the tile size that best fits within the given space. If
398 * `user_size' is true, we must actually find the _largest_ such
399 * tile size, in order to get as close to the user's explicit
400 * request as possible; otherwise, we bound above at the game's
401 * preferred tile size, so that the game gets what it wants
402 * provided that this doesn't break the constraint from the
403 * front-end (which is likely to be a screen size or similar).
404 */
405 if (user_size) {
406 max = 1;
407 do {
408 max *= 2;
409 me->ourgame->compute_size(me->params, max, me->ui, &rx, &ry);
410 } while (rx <= *x && ry <= *y);
411 } else
412 max = convert_tilesize(me, me->preferred_tilesize,
413 me->preferred_tilesize_dpr,
414 device_pixel_ratio) + 1;
415 min = 1;
416
417 /*
418 * Now binary-search between min and max. We're looking for a
419 * boundary rather than a value: the point at which tile sizes
420 * stop fitting within the given dimensions. Thus, we stop when
421 * max and min differ by exactly 1.
422 */
423 while (max - min > 1) {
424 int mid = (max + min) / 2;
425 me->ourgame->compute_size(me->params, mid, me->ui, &rx, &ry);
426 if (rx <= *x && ry <= *y)
427 min = mid;
428 else
429 max = mid;
430 }
431
432 /*
433 * Now `min' is a valid size, and `max' isn't. So use `min'.
434 */
435
436 me->tilesize = min;
437 if (user_size) {
438 /* If the user requested a change in size, make it permanent. */
439 me->preferred_tilesize = me->tilesize;
440 me->preferred_tilesize_dpr = device_pixel_ratio;
441 }
442 midend_size_new_drawstate(me);
443 *x = me->winwidth;
444 *y = me->winheight;
445}
446
447int midend_tilesize(midend *me) { return me->tilesize; }
448
449void midend_set_params(midend *me, game_params *params)
450{
451 me->ourgame->free_params(me->params);
452 me->params = me->ourgame->dup_params(params);
453}
454
455game_params *midend_get_params(midend *me)
456{
457 return me->ourgame->dup_params(me->params);
458}
459
460static char *encode_params(midend *me, const game_params *params, bool full)
461{
462 char *encoded = me->ourgame->encode_params(params, full);
463 int i;
464
465 /* Assert that the params consist of printable ASCII containing
466 * neither '#' nor ':'. */
467 for (i = 0; encoded[i]; i++)
468 assert(encoded[i] >= 32 && encoded[i] < 127 &&
469 encoded[i] != '#' && encoded[i] != ':');
470 return encoded;
471}
472
473static void assert_printable_ascii(char const *s)
474{
475 /* Assert that s is entirely printable ASCII, and hence safe for
476 * writing in a save file. */
477 int i;
478 for (i = 0; s[i]; i++)
479 assert(s[i] >= 32 && s[i] < 127);
480}
481
482static void midend_set_timer(midend *me)
483{
484 me->timing = (me->ourgame->is_timed &&
485 me->ourgame->timing_state(me->states[me->statepos-1].state,
486 me->ui));
487 if (me->timing || me->flash_time || me->anim_time)
488 activate_timer(me->frontend);
489 else
490 deactivate_timer(me->frontend);
491}
492
493void midend_force_redraw(midend *me)
494{
495 if (me->drawstate)
496 me->ourgame->free_drawstate(me->drawing, me->drawstate);
497 me->drawstate = me->ourgame->new_drawstate(me->drawing,
498 me->states[0].state);
499 me->first_draw = true;
500 midend_size_new_drawstate(me);
501 midend_redraw(me);
502}
503
504static void midend_serialise_buf_write(void *ctx, const void *buf, int len)
505{
506 struct midend_serialise_buf *ser = (struct midend_serialise_buf *)ctx;
507 int new_len;
508
509 assert(len < INT_MAX - ser->len);
510 new_len = ser->len + len;
511 if (new_len > ser->size) {
512 ser->size = new_len + new_len / 4 + 1024;
513 ser->buf = sresize(ser->buf, ser->size, char);
514 }
515 memcpy(ser->buf + ser->len, buf, len);
516 ser->len = new_len;
517}
518
519static bool midend_serialise_buf_read(void *ctx, void *buf, int len)
520{
521 struct midend_serialise_buf_read_ctx *const rctx = ctx;
522
523 if (len > rctx->len - rctx->pos)
524 return false;
525
526 memcpy(buf, rctx->ser->buf + rctx->pos, len);
527 rctx->pos += len;
528 return true;
529}
530
531void midend_new_game(midend *me)
532{
533 me->newgame_undo.len = 0;
534 if (me->newgame_can_store_undo) {
535 /*
536 * Serialise the whole of the game that we're about to
537 * supersede, so that the 'New Game' action can be undone
538 * later.
539 *
540 * We omit this in various situations, such as if there
541 * _isn't_ a current game (not even a starting position)
542 * because this is the initial call to midend_new_game when
543 * the midend is first set up, or if the midend state has
544 * already begun to be overwritten by midend_set_config. In
545 * those situations, we want to avoid writing out any
546 * serialisation, because they will be either invalid, or
547 * worse, valid but wrong.
548 */
549 midend_purge_states(me);
550 midend_serialise(me, midend_serialise_buf_write, &me->newgame_undo);
551 }
552
553 midend_stop_anim(me);
554 midend_free_game(me);
555
556 assert(me->nstates == 0);
557
558 if (me->genmode == GOT_DESC) {
559 me->genmode = GOT_NOTHING;
560 } else {
561 random_state *rs;
562
563 if (me->genmode == GOT_SEED) {
564 me->genmode = GOT_NOTHING;
565 } else {
566 /*
567 * Generate a new random seed. 15 digits comes to about
568 * 48 bits, which should be more than enough.
569 *
570 * I'll avoid putting a leading zero on the number,
571 * just in case it confuses anybody who thinks it's
572 * processed as an integer rather than a string.
573 */
574 char newseed[16];
575 int i;
576 newseed[15] = '\0';
577 newseed[0] = '1' + (char)random_upto(me->random, 9);
578 for (i = 1; i < 15; i++)
579 newseed[i] = '0' + (char)random_upto(me->random, 10);
580 sfree(me->seedstr);
581 me->seedstr = dupstr(newseed);
582
583 if (me->curparams)
584 me->ourgame->free_params(me->curparams);
585 me->curparams = me->ourgame->dup_params(me->params);
586 }
587
588 sfree(me->desc);
589 sfree(me->privdesc);
590 sfree(me->aux_info);
591 me->aux_info = NULL;
592
593 rs = random_new(me->seedstr, strlen(me->seedstr));
594 /*
595 * If this midend has been instantiated without providing a
596 * drawing API, it is non-interactive. This means that it's
597 * being used for bulk game generation, and hence we should
598 * pass the non-interactive flag to new_desc.
599 */
600 me->desc = me->ourgame->new_desc(me->curparams, rs,
601 &me->aux_info, (me->drawing != NULL));
602 assert_printable_ascii(me->desc);
603 me->privdesc = NULL;
604 random_free(rs);
605 }
606
607 ensure(me);
608
609 /*
610 * It might seem a bit odd that we're using me->params to
611 * create the initial game state, rather than me->curparams
612 * which is better tailored to this specific game and which we
613 * always know.
614 *
615 * It's supposed to be an invariant in the midend that
616 * me->params and me->curparams differ in no aspect that is
617 * important after generation (i.e. after new_desc()). By
618 * deliberately passing the _less_ specific of these two
619 * parameter sets, we provoke play-time misbehaviour in the
620 * case where a game has failed to encode a play-time parameter
621 * in the non-full version of encode_params().
622 */
623 me->states[me->nstates].state =
624 me->ourgame->new_game(me, me->params, me->desc);
625
626 /*
627 * As part of our commitment to self-testing, test the aux
628 * string to make sure nothing ghastly went wrong.
629 */
630 if (me->ourgame->can_solve && me->aux_info) {
631 game_state *s;
632 const char *msg;
633 char *movestr;
634
635 msg = NULL;
636 movestr = me->ourgame->solve(me->states[0].state,
637 me->states[0].state,
638 me->aux_info, &msg);
639 assert(movestr && !msg);
640 s = me->ourgame->execute_move(me->states[0].state, movestr);
641 assert(s);
642 me->ourgame->free_game(s);
643 sfree(movestr);
644 }
645
646 me->states[me->nstates].movestr = NULL;
647 me->states[me->nstates].movetype = NEWGAME;
648 me->nstates++;
649 me->statepos = 1;
650 me->drawstate = me->ourgame->new_drawstate(me->drawing,
651 me->states[0].state);
652 me->first_draw = true;
653 midend_size_new_drawstate(me);
654 me->elapsed = 0.0F;
655 me->flash_pos = me->flash_time = 0.0F;
656 me->anim_pos = me->anim_time = 0.0F;
657 if (me->ui)
658 me->ourgame->free_ui(me->ui);
659 me->ui = me->ourgame->new_ui(me->states[0].state);
660 midend_apply_prefs(me, me->ui);
661 midend_set_timer(me);
662 me->pressed_mouse_button = 0;
663
664 if (me->game_id_change_notify_function)
665 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
666
667 me->newgame_can_store_undo = true;
668}
669
670const char *midend_load_prefs(
671 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx)
672{
673 const char *err = midend_deserialise_prefs(me, NULL, read, rctx);
674 return err;
675}
676
677void midend_save_prefs(midend *me,
678 void (*write)(void *ctx, const void *buf, int len),
679 void *wctx)
680{
681 midend_serialise_prefs(me, NULL, write, wctx);
682}
683
684bool midend_can_undo(midend *me)
685{
686 return (me->statepos > 1 || me->newgame_undo.len);
687}
688
689bool midend_can_redo(midend *me)
690{
691 return (me->statepos < me->nstates || me->newgame_redo.len);
692}
693
694struct newgame_undo_deserialise_check_ctx {
695 bool refused;
696};
697
698static const char *newgame_undo_deserialise_check(
699 void *vctx, midend *me, const struct deserialise_data *data)
700{
701 struct newgame_undo_deserialise_check_ctx *ctx =
702 (struct newgame_undo_deserialise_check_ctx *)vctx;
703 char *old, *new;
704
705 /*
706 * Undoing a New Game operation is only permitted if it doesn't
707 * change the game parameters. The point of having the ability at
708 * all is to recover from the momentary finger error of having hit
709 * the 'n' key (perhaps in place of some other nearby key), or hit
710 * the New Game menu item by mistake when aiming for the adjacent
711 * Restart; in both those situations, the game params are the same
712 * before and after the new-game operation.
713 *
714 * In principle, we could generalise this so that _any_ call to
715 * midend_new_game could be undone, but that would need all front
716 * ends to be alert to the possibility that any keystroke passed
717 * to midend_process_key might (if it turns out to have been one
718 * of the synonyms for undo, which the frontend doesn't
719 * necessarily check for) have various knock-on effects like
720 * needing to select a different preset in the game type menu, or
721 * even resizing the window. At least for the moment, it's easier
722 * not to do that, and to simply disallow any newgame-undo that is
723 * disruptive in either of those ways.
724 *
725 * We check both params and cparams, to be as safe as possible.
726 */
727
728 old = encode_params(me, me->params, true);
729 new = encode_params(me, data->params, true);
730 if (strcmp(old, new)) {
731 /* Set a flag to distinguish this deserialise failure
732 * from one due to faulty decoding */
733 ctx->refused = true;
734 return "Undoing this new-game operation would change params";
735 }
736
737 old = encode_params(me, me->curparams, true);
738 new = encode_params(me, data->cparams, true);
739 if (strcmp(old, new)) {
740 ctx->refused = true;
741 return "Undoing this new-game operation would change params";
742 }
743
744 /*
745 * Otherwise, fine, go ahead.
746 */
747 return NULL;
748}
749
750static bool midend_undo(midend *me)
751{
752 const char *deserialise_error;
753
754 if (me->statepos > 1) {
755 if (me->ui)
756 me->ourgame->changed_state(me->ui,
757 me->states[me->statepos-1].state,
758 me->states[me->statepos-2].state);
759 me->statepos--;
760 me->dir = -1;
761 return true;
762 } else if (me->newgame_undo.len) {
763 struct midend_serialise_buf_read_ctx rctx;
764 struct newgame_undo_deserialise_check_ctx cctx;
765 struct midend_serialise_buf serbuf;
766
767 /*
768 * Serialise the current game so that you can later redo past
769 * this undo. Once we're committed to the undo actually
770 * happening, we'll copy this data into place.
771 */
772 serbuf.buf = NULL;
773 serbuf.len = serbuf.size = 0;
774 midend_serialise(me, midend_serialise_buf_write, &serbuf);
775
776 rctx.ser = &me->newgame_undo;
777 rctx.len = me->newgame_undo.len; /* copy for reentrancy safety */
778 rctx.pos = 0;
779 cctx.refused = false;
780 deserialise_error = midend_deserialise_internal(
781 me, midend_serialise_buf_read, &rctx,
782 newgame_undo_deserialise_check, &cctx);
783 if (cctx.refused) {
784 /*
785 * Our post-deserialisation check shows that we can't use
786 * this saved game after all. (deserialise_error will
787 * contain the dummy error message generated by our check
788 * function, which we ignore.)
789 */
790 sfree(serbuf.buf);
791 return false;
792 } else {
793 /*
794 * There should never be any _other_ deserialisation
795 * error, because this serialised data has been held in
796 * our memory since it was created, and hasn't had any
797 * opportunity to be corrupted on disk, accidentally
798 * replaced by the wrong file, etc., by user error.
799 */
800 assert(!deserialise_error);
801
802 /*
803 * Clear the old newgame_undo serialisation, so that we
804 * don't try to undo past the beginning of the game we've
805 * just gone back to and end up at the front of it again.
806 */
807 me->newgame_undo.len = 0;
808
809 /*
810 * Copy the serialisation of the game we've just left into
811 * the midend so that we can redo back into it later.
812 */
813 me->newgame_redo.len = 0;
814 midend_serialise_buf_write(&me->newgame_redo,
815 serbuf.buf, serbuf.len);
816
817 sfree(serbuf.buf);
818 return true;
819 }
820 } else
821 return false;
822}
823
824static bool midend_redo(midend *me)
825{
826 const char *deserialise_error;
827
828 if (me->statepos < me->nstates) {
829 if (me->ui)
830 me->ourgame->changed_state(me->ui,
831 me->states[me->statepos-1].state,
832 me->states[me->statepos].state);
833 me->statepos++;
834 me->dir = +1;
835 return true;
836 } else if (me->newgame_redo.len) {
837 struct midend_serialise_buf_read_ctx rctx;
838 struct newgame_undo_deserialise_check_ctx cctx;
839 struct midend_serialise_buf serbuf;
840
841 /*
842 * Serialise the current game so that you can later undo past
843 * this redo. Once we're committed to the undo actually
844 * happening, we'll copy this data into place.
845 */
846 serbuf.buf = NULL;
847 serbuf.len = serbuf.size = 0;
848 midend_serialise(me, midend_serialise_buf_write, &serbuf);
849
850 rctx.ser = &me->newgame_redo;
851 rctx.len = me->newgame_redo.len; /* copy for reentrancy safety */
852 rctx.pos = 0;
853 cctx.refused = false;
854 deserialise_error = midend_deserialise_internal(
855 me, midend_serialise_buf_read, &rctx,
856 newgame_undo_deserialise_check, &cctx);
857 if (cctx.refused) {
858 /*
859 * Our post-deserialisation check shows that we can't use
860 * this saved game after all. (deserialise_error will
861 * contain the dummy error message generated by our check
862 * function, which we ignore.)
863 */
864 sfree(serbuf.buf);
865 return false;
866 } else {
867 /*
868 * There should never be any _other_ deserialisation
869 * error, because this serialised data has been held in
870 * our memory since it was created, and hasn't had any
871 * opportunity to be corrupted on disk, accidentally
872 * replaced by the wrong file, etc., by user error.
873 */
874 assert(!deserialise_error);
875
876 /*
877 * Clear the old newgame_redo serialisation, so that we
878 * don't try to redo past the end of the game we've just
879 * come into and end up at the back of it again.
880 */
881 me->newgame_redo.len = 0;
882
883 /*
884 * Copy the serialisation of the game we've just left into
885 * the midend so that we can undo back into it later.
886 */
887 me->newgame_undo.len = 0;
888 midend_serialise_buf_write(&me->newgame_undo,
889 serbuf.buf, serbuf.len);
890
891 sfree(serbuf.buf);
892 return true;
893 }
894 } else
895 return false;
896}
897
898static void midend_finish_move(midend *me)
899{
900 float flashtime;
901
902 /*
903 * We do not flash if the later of the two states is special.
904 * This covers both forward Solve moves and backward (undone)
905 * Restart moves.
906 */
907 if ((me->oldstate || me->statepos > 1) &&
908 ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) ||
909 (me->dir < 0 && me->statepos < me->nstates &&
910 !special(me->states[me->statepos].movetype)))) {
911 flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
912 me->states[me->statepos-2].state,
913 me->states[me->statepos-1].state,
914 me->oldstate ? me->dir : +1,
915 me->ui);
916 if (flashtime > 0) {
917 me->flash_pos = 0.0F;
918 me->flash_time = flashtime;
919 }
920 }
921
922 if (me->oldstate)
923 me->ourgame->free_game(me->oldstate);
924 me->oldstate = NULL;
925 me->anim_pos = me->anim_time = 0;
926 me->dir = 0;
927
928 midend_set_timer(me);
929}
930
931void midend_stop_anim(midend *me)
932{
933 if (me->oldstate || me->anim_time != 0) {
934 midend_finish_move(me);
935 midend_redraw(me);
936 }
937}
938
939void midend_restart_game(midend *me)
940{
941 game_state *s;
942
943 assert(me->statepos >= 1);
944 if (me->statepos == 1)
945 return; /* no point doing anything at all! */
946
947 /*
948 * During restart, we reconstruct the game from the (public)
949 * game description rather than from states[0], because that
950 * way Mines gets slightly more sensible behaviour (restart
951 * goes to _after_ the first click so you don't have to
952 * remember where you clicked).
953 */
954 s = me->ourgame->new_game(me, me->params, me->desc);
955
956 /*
957 * Now enter the restarted state as the next move.
958 */
959 midend_stop_anim(me);
960 midend_purge_states(me);
961 ensure(me);
962 me->states[me->nstates].state = s;
963 me->states[me->nstates].movestr = dupstr(me->desc);
964 me->states[me->nstates].movetype = RESTART;
965 me->statepos = ++me->nstates;
966 if (me->ui)
967 me->ourgame->changed_state(me->ui,
968 me->states[me->statepos-2].state,
969 me->states[me->statepos-1].state);
970 me->flash_pos = me->flash_time = 0.0F;
971 midend_finish_move(me);
972 midend_redraw(me);
973 midend_set_timer(me);
974}
975
976static int midend_really_process_key(midend *me, int x, int y, int button)
977{
978 game_state *oldstate =
979 me->ourgame->dup_game(me->states[me->statepos - 1].state);
980 int type = MOVE;
981 bool gottype = false;
982 int ret = PKR_NO_EFFECT;
983 float anim_time;
984 game_state *s;
985 char *movestr = NULL;
986
987 if (!IS_UI_FAKE_KEY(button)) {
988 movestr = me->ourgame->interpret_move(
989 me->states[me->statepos-1].state,
990 me->ui, me->drawstate, x, y, button);
991 }
992
993 if (movestr == NULL || movestr == MOVE_UNUSED) {
994 if ((me->one_key_shortcuts && (button == 'n' || button == 'N')) ||
995 button == '\x0E' || button == UI_NEWGAME) {
996 midend_new_game(me);
997 midend_redraw(me);
998 ret = PKR_SOME_EFFECT;
999 goto done; /* never animate */
1000 } else if ((me->one_key_shortcuts && (button=='u' || button=='U')) ||
1001 button == '*' || button == '\x1A' || button == '\x1F' ||
1002 button == UI_UNDO) {
1003 midend_stop_anim(me);
1004 type = me->states[me->statepos-1].movetype;
1005 gottype = true;
1006 if (!midend_undo(me))
1007 goto done;
1008 ret = PKR_SOME_EFFECT;
1009 } else if ((me->one_key_shortcuts && (button=='r' || button=='R')) ||
1010 button == '#' || button == '\x12' || button == '\x19' ||
1011 button == UI_REDO) {
1012 midend_stop_anim(me);
1013 if (!midend_redo(me))
1014 goto done;
1015 ret = PKR_SOME_EFFECT;
1016 } else if ((button == '\x13' || button == UI_SOLVE) &&
1017 me->ourgame->can_solve) {
1018 ret = PKR_SOME_EFFECT;
1019 if (midend_solve(me))
1020 goto done;
1021 } else if ((me->one_key_shortcuts && (button=='q' || button=='Q')) ||
1022 button == '\x11' || button == UI_QUIT) {
1023 ret = PKR_QUIT;
1024 goto done;
1025 } else {
1026 ret = PKR_UNUSED;
1027 goto done;
1028 }
1029 } else if (movestr == MOVE_NO_EFFECT) {
1030 ret = PKR_NO_EFFECT;
1031 goto done;
1032 } else {
1033 ret = PKR_SOME_EFFECT;
1034 if (movestr == MOVE_UI_UPDATE)
1035 s = me->states[me->statepos-1].state;
1036 else {
1037 assert_printable_ascii(movestr);
1038 s = me->ourgame->execute_move(me->states[me->statepos-1].state,
1039 movestr);
1040 assert(s != NULL);
1041 }
1042
1043 if (s == me->states[me->statepos-1].state) {
1044 /*
1045 * make_move() is allowed to return its input state to
1046 * indicate that although no move has been made, the UI
1047 * state has been updated and a redraw is called for.
1048 */
1049 midend_redraw(me);
1050 midend_set_timer(me);
1051 goto done;
1052 } else if (s) {
1053 midend_stop_anim(me);
1054 midend_purge_states(me);
1055 ensure(me);
1056 assert(movestr != NULL);
1057 me->states[me->nstates].state = s;
1058 me->states[me->nstates].movestr = movestr;
1059 me->states[me->nstates].movetype = MOVE;
1060 me->statepos = ++me->nstates;
1061 me->dir = +1;
1062 if (me->ui)
1063 me->ourgame->changed_state(me->ui,
1064 me->states[me->statepos-2].state,
1065 me->states[me->statepos-1].state);
1066 } else {
1067 goto done;
1068 }
1069 }
1070
1071 if (!gottype)
1072 type = me->states[me->statepos-1].movetype;
1073
1074 /*
1075 * See if this move requires an animation.
1076 */
1077 if (special(type) && !(type == SOLVE &&
1078 (me->ourgame->flags & SOLVE_ANIMATES))) {
1079 anim_time = 0;
1080 } else {
1081 anim_time = me->ourgame->anim_length(oldstate,
1082 me->states[me->statepos-1].state,
1083 me->dir, me->ui);
1084 }
1085
1086 me->oldstate = oldstate; oldstate = NULL;
1087 if (anim_time > 0) {
1088 me->anim_time = anim_time;
1089 } else {
1090 me->anim_time = 0.0;
1091 midend_finish_move(me);
1092 }
1093 me->anim_pos = 0.0;
1094
1095 midend_redraw(me);
1096
1097 midend_set_timer(me);
1098
1099 done:
1100 if (oldstate) me->ourgame->free_game(oldstate);
1101 return ret;
1102}
1103
1104int midend_process_key(midend *me, int x, int y, int button)
1105{
1106 int ret = PKR_UNUSED, ret2;
1107
1108 /*
1109 * Harmonise mouse drag and release messages.
1110 *
1111 * Some front ends might accidentally switch from sending, say,
1112 * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
1113 * drag. (This can happen on the Mac, for example, since
1114 * RIGHT_DRAG is usually done using Command+drag, and if the
1115 * user accidentally releases Command half way through the drag
1116 * then there will be trouble.)
1117 *
1118 * It would be an O(number of front ends) annoyance to fix this
1119 * in the front ends, but an O(number of back ends) annoyance
1120 * to have each game capable of dealing with it. Therefore, we
1121 * fix it _here_ in the common midend code so that it only has
1122 * to be done once.
1123 *
1124 * The possible ways in which things can go screwy in the front
1125 * end are:
1126 *
1127 * - in a system containing multiple physical buttons button
1128 * presses can inadvertently overlap. We can see ABab (caps
1129 * meaning button-down and lowercase meaning button-up) when
1130 * the user had semantically intended AaBb.
1131 *
1132 * - in a system where one button is simulated by means of a
1133 * modifier key and another button, buttons can mutate
1134 * between press and release (possibly during drag). So we
1135 * can see Ab instead of Aa.
1136 *
1137 * Definite requirements are:
1138 *
1139 * - button _presses_ must never be invented or destroyed. If
1140 * the user presses two buttons in succession, the button
1141 * presses must be transferred to the backend unchanged. So
1142 * if we see AaBb , that's fine; if we see ABab (the button
1143 * presses inadvertently overlapped) we must somehow
1144 * `correct' it to AaBb.
1145 *
1146 * - every mouse action must end up looking like a press, zero
1147 * or more drags, then a release. This allows back ends to
1148 * make the _assumption_ that incoming mouse data will be
1149 * sane in this regard, and not worry about the details.
1150 *
1151 * So my policy will be:
1152 *
1153 * - treat any button-up as a button-up for the currently
1154 * pressed button, or ignore it if there is no currently
1155 * pressed button.
1156 *
1157 * - treat any drag as a drag for the currently pressed
1158 * button, or ignore it if there is no currently pressed
1159 * button.
1160 *
1161 * - if we see a button-down while another button is currently
1162 * pressed, invent a button-up for the first one and then
1163 * pass the button-down through as before.
1164 *
1165 * 2005-05-31: An addendum to the above. Some games might want
1166 * a `priority order' among buttons, such that if one button is
1167 * pressed while another is down then a fixed one of the
1168 * buttons takes priority no matter what order they're pressed
1169 * in. Mines, in particular, wants to treat a left+right click
1170 * like a left click for the benefit of users of other
1171 * implementations. So the last of the above points is modified
1172 * in the presence of an (optional) button priority order.
1173 *
1174 * A further addition: we translate certain keyboard presses to
1175 * cursor key 'select' buttons, so that a) frontends don't have
1176 * to translate these themselves (like they do for CURSOR_UP etc),
1177 * and b) individual games don't have to hard-code button presses
1178 * of '\n' etc for keyboard-based cursors. The choice of buttons
1179 * here could eventually be controlled by a runtime configuration
1180 * option.
1181 *
1182 * We also handle converting MOD_CTRL|'a' etc into '\x01' etc,
1183 * specially recognising Ctrl+Shift+Z, and stripping modifier
1184 * flags off keys that aren't meant to have them.
1185 */
1186 if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
1187 if (me->pressed_mouse_button) {
1188 if (IS_MOUSE_DRAG(button)) {
1189 button = me->pressed_mouse_button +
1190 (LEFT_DRAG - LEFT_BUTTON);
1191 } else {
1192 button = me->pressed_mouse_button +
1193 (LEFT_RELEASE - LEFT_BUTTON);
1194 }
1195 } else
1196 return ret; /* ignore it */
1197 } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
1198 /*
1199 * If the new button has lower priority than the old one,
1200 * don't bother doing this.
1201 */
1202 if (me->ourgame->flags &
1203 BUTTON_BEATS(me->pressed_mouse_button, button))
1204 return ret; /* just ignore it */
1205
1206 /*
1207 * Fabricate a button-up for the previously pressed button.
1208 */
1209 ret2 = midend_really_process_key
1210 (me, x, y, (me->pressed_mouse_button +
1211 (LEFT_RELEASE - LEFT_BUTTON)));
1212 ret = min(ret, ret2);
1213 }
1214
1215 /* Canonicalise CTRL+ASCII. */
1216 if ((button & MOD_CTRL) &&
1217 STRIP_BUTTON_MODIFIERS(button) >= 0x40 &&
1218 STRIP_BUTTON_MODIFIERS(button) < 0x80)
1219 button = button & (0x1f | (MOD_MASK & ~MOD_CTRL));
1220 /* Special handling to make CTRL+SHFT+Z into REDO. */
1221 if ((button & (~MOD_MASK | MOD_SHFT)) == (MOD_SHFT | '\x1A'))
1222 button = UI_REDO;
1223 /* interpret_move() expects CTRL and SHFT only on cursor keys, and
1224 * TAB (added as of 7/2024 to support Untangle). */
1225 if (!IS_CURSOR_MOVE(STRIP_BUTTON_MODIFIERS(button))) {
1226 /* reject CTRL+anything odd */
1227 if ((button & MOD_CTRL) && STRIP_BUTTON_MODIFIERS(button) >= 0x20)
1228 return PKR_UNUSED;
1229 /* otherwise strip them, except for tab */
1230 if (STRIP_BUTTON_MODIFIERS(button) != '\t')
1231 button &= ~(MOD_CTRL | MOD_SHFT);
1232 }
1233 /* interpret_move() expects NUM_KEYPAD only on numbers. */
1234 if (STRIP_BUTTON_MODIFIERS(button) < '0' ||
1235 STRIP_BUTTON_MODIFIERS(button) > '9')
1236 button &= ~MOD_NUM_KEYPAD;
1237 /*
1238 * Translate keyboard presses to cursor selection.
1239 */
1240 if (button == '\n' || button == '\r')
1241 button = CURSOR_SELECT;
1242 if (button == ' ')
1243 button = CURSOR_SELECT2;
1244
1245 /*
1246 * Normalise both backspace characters (8 and 127) to \b. Easier
1247 * to do this once, here, than to require all front ends to
1248 * carefully generate the same one - now each front end can
1249 * generate whichever is easiest.
1250 */
1251 if (button == '\177')
1252 button = '\b';
1253
1254 /*
1255 * Now send on the event we originally received.
1256 */
1257 ret2 = midend_really_process_key(me, x, y, button);
1258 ret = min(ret, ret2);
1259
1260 /*
1261 * And update the currently pressed button.
1262 */
1263 if (IS_MOUSE_RELEASE(button))
1264 me->pressed_mouse_button = 0;
1265 else if (IS_MOUSE_DOWN(button))
1266 me->pressed_mouse_button = button;
1267
1268 return ret;
1269}
1270
1271key_label *midend_request_keys(midend *me, int *n)
1272{
1273 key_label *keys = NULL;
1274 int nkeys = 0, i;
1275
1276 if(me->ourgame->request_keys)
1277 {
1278 keys = me->ourgame->request_keys(me->params, &nkeys);
1279 for(i = 0; i < nkeys; ++i)
1280 {
1281 if(!keys[i].label)
1282 keys[i].label = button2label(keys[i].button);
1283 }
1284 }
1285
1286 if(n)
1287 *n = nkeys;
1288
1289 return keys;
1290}
1291
1292/* Return a good label to show next to a key right now. */
1293const char *midend_current_key_label(midend *me, int button)
1294{
1295 assert(IS_CURSOR_SELECT(button));
1296 if (!me->ourgame->current_key_label) return "";
1297 return me->ourgame->current_key_label(
1298 me->ui, me->states[me->statepos-1].state, button);
1299}
1300
1301void midend_redraw(midend *me)
1302{
1303 assert(me->drawing);
1304
1305 if (me->statepos > 0 && me->drawstate) {
1306 bool first_draw = me->first_draw;
1307 me->first_draw = false;
1308
1309 start_draw(me->drawing);
1310
1311 if (first_draw) {
1312 /*
1313 * The initial contents of the window are not guaranteed
1314 * by the front end. But we also don't want to require
1315 * every single game to go to the effort of clearing the
1316 * window on setup. So we centralise here the operation of
1317 * covering the whole window with colour 0 (assumed to be
1318 * the puzzle's background colour) the first time we do a
1319 * redraw operation with a new drawstate.
1320 */
1321 draw_rect(me->drawing, 0, 0, me->winwidth, me->winheight, 0);
1322 }
1323
1324 if (me->oldstate && me->anim_time > 0 &&
1325 me->anim_pos < me->anim_time) {
1326 assert(me->dir != 0);
1327 me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
1328 me->states[me->statepos-1].state, me->dir,
1329 me->ui, me->anim_pos, me->flash_pos);
1330 } else {
1331 me->ourgame->redraw(me->drawing, me->drawstate, NULL,
1332 me->states[me->statepos-1].state, +1 /*shrug*/,
1333 me->ui, 0.0, me->flash_pos);
1334 }
1335
1336 if (first_draw) {
1337 /*
1338 * Call a big draw_update on the whole window, in case the
1339 * game backend didn't.
1340 */
1341 draw_update(me->drawing, 0, 0, me->winwidth, me->winheight);
1342 }
1343
1344 end_draw(me->drawing);
1345 }
1346}
1347
1348/*
1349 * Nasty hacky function used to implement the --redo option in
1350 * gtk.c. Only used for generating the puzzles' icons.
1351 */
1352void midend_freeze_timer(midend *me, float tprop)
1353{
1354 me->anim_pos = me->anim_time * tprop;
1355 midend_redraw(me);
1356 deactivate_timer(me->frontend);
1357}
1358
1359void midend_timer(midend *me, float tplus)
1360{
1361 bool need_redraw = (me->anim_time > 0 || me->flash_time > 0);
1362
1363 me->anim_pos += tplus;
1364 if (me->anim_pos >= me->anim_time ||
1365 me->anim_time == 0 || !me->oldstate) {
1366 if (me->anim_time > 0)
1367 midend_finish_move(me);
1368 }
1369
1370 me->flash_pos += tplus;
1371 if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
1372 me->flash_pos = me->flash_time = 0;
1373 }
1374
1375 if (need_redraw)
1376 midend_redraw(me);
1377
1378 if (me->timing) {
1379 float oldelapsed = me->elapsed;
1380 me->elapsed += tplus;
1381 if ((int)oldelapsed != (int)me->elapsed)
1382 status_bar(me->drawing, me->laststatus ? me->laststatus : "");
1383 }
1384
1385 midend_set_timer(me);
1386}
1387
1388float *midend_colours(midend *me, int *ncolours)
1389{
1390 float *ret;
1391
1392 ret = me->ourgame->colours(me->frontend, ncolours);
1393 assert(*ncolours >= 1);
1394
1395 {
1396 int i;
1397
1398 /*
1399 * Allow environment-based overrides for the standard
1400 * colours by defining variables along the lines of
1401 * `NET_COLOUR_4=6000c0'.
1402 */
1403
1404 for (i = 0; i < *ncolours; i++) {
1405 char buf[80], *e;
1406 unsigned int r, g, b;
1407 int j, k;
1408
1409 sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
1410 for (j = k = 0; buf[j]; j++)
1411 if (!isspace((unsigned char)buf[j]))
1412 buf[k++] = toupper((unsigned char)buf[j]);
1413 buf[k] = '\0';
1414 if ((e = getenv(buf)) != NULL &&
1415 sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
1416 ret[i*3 + 0] = r / 255.0F;
1417 ret[i*3 + 1] = g / 255.0F;
1418 ret[i*3 + 2] = b / 255.0F;
1419 }
1420 assert(0.0F <= ret[i*3 + 0] && ret[i*3 + 0] <= 1.0F);
1421 assert(0.0F <= ret[i*3 + 1] && ret[i*3 + 1] <= 1.0F);
1422 assert(0.0F <= ret[i*3 + 2] && ret[i*3 + 2] <= 1.0F);
1423 }
1424 }
1425
1426 return ret;
1427}
1428
1429struct preset_menu *preset_menu_new(void)
1430{
1431 struct preset_menu *menu = snew(struct preset_menu);
1432 menu->n_entries = 0;
1433 menu->entries_size = 0;
1434 menu->entries = NULL;
1435 return menu;
1436}
1437
1438static struct preset_menu_entry *preset_menu_add(struct preset_menu *menu,
1439 char *title)
1440{
1441 struct preset_menu_entry *toret;
1442 if (menu->n_entries >= menu->entries_size) {
1443 menu->entries_size = menu->n_entries * 5 / 4 + 10;
1444 menu->entries = sresize(menu->entries, menu->entries_size,
1445 struct preset_menu_entry);
1446 }
1447 toret = &menu->entries[menu->n_entries++];
1448 toret->title = title;
1449 toret->params = NULL;
1450 toret->submenu = NULL;
1451 return toret;
1452}
1453
1454struct preset_menu *preset_menu_add_submenu(struct preset_menu *parent,
1455 char *title)
1456{
1457 struct preset_menu_entry *entry = preset_menu_add(parent, title);
1458 entry->submenu = preset_menu_new();
1459 return entry->submenu;
1460}
1461
1462void preset_menu_add_preset(struct preset_menu *parent,
1463 char *title, game_params *params)
1464{
1465 struct preset_menu_entry *entry = preset_menu_add(parent, title);
1466 entry->params = params;
1467}
1468
1469game_params *preset_menu_lookup_by_id(struct preset_menu *menu, int id)
1470{
1471 int i;
1472 game_params *retd;
1473
1474 for (i = 0; i < menu->n_entries; i++) {
1475 if (id == menu->entries[i].id)
1476 return menu->entries[i].params;
1477 if (menu->entries[i].submenu &&
1478 (retd = preset_menu_lookup_by_id(
1479 menu->entries[i].submenu, id)) != NULL)
1480 return retd;
1481 }
1482
1483 return NULL;
1484}
1485
1486static char *preset_menu_add_from_user_env(
1487 midend *me, struct preset_menu *menu, char *p, bool top_level)
1488{
1489 while (*p) {
1490 char *name, *val;
1491 game_params *preset;
1492
1493 name = p;
1494 while (*p && *p != ':') p++;
1495 if (*p) *p++ = '\0';
1496 val = p;
1497 while (*p && *p != ':') p++;
1498 if (*p) *p++ = '\0';
1499
1500 if (!strcmp(val, "#")) {
1501 /*
1502 * Special case: either open a new submenu with the given
1503 * title, or terminate the current submenu.
1504 */
1505 if (*name) {
1506 struct preset_menu *submenu =
1507 preset_menu_add_submenu(menu, dupstr(name));
1508 p = preset_menu_add_from_user_env(me, submenu, p, false);
1509 } else {
1510 /*
1511 * If we get a 'close submenu' indication at the top
1512 * level, there's not much we can do but quietly
1513 * ignore it.
1514 */
1515 if (!top_level)
1516 return p;
1517 }
1518 continue;
1519 }
1520
1521 preset = me->ourgame->default_params();
1522 me->ourgame->decode_params(preset, val);
1523
1524 if (me->ourgame->validate_params(preset, true)) {
1525 /* Drop this one from the list. */
1526 me->ourgame->free_params(preset);
1527 continue;
1528 }
1529
1530 preset_menu_add_preset(menu, dupstr(name), preset);
1531 }
1532
1533 return p;
1534}
1535
1536static void preset_menu_alloc_ids(midend *me, struct preset_menu *menu)
1537{
1538 int i;
1539
1540 for (i = 0; i < menu->n_entries; i++)
1541 menu->entries[i].id = me->n_encoded_presets++;
1542
1543 for (i = 0; i < menu->n_entries; i++)
1544 if (menu->entries[i].submenu)
1545 preset_menu_alloc_ids(me, menu->entries[i].submenu);
1546}
1547
1548static void preset_menu_encode_params(midend *me, struct preset_menu *menu)
1549{
1550 int i;
1551
1552 for (i = 0; i < menu->n_entries; i++) {
1553 if (menu->entries[i].params) {
1554 me->encoded_presets[menu->entries[i].id] =
1555 encode_params(me, menu->entries[i].params, true);
1556 } else {
1557 preset_menu_encode_params(me, menu->entries[i].submenu);
1558 }
1559 }
1560}
1561
1562struct preset_menu *midend_get_presets(midend *me, int *id_limit)
1563{
1564 int i;
1565
1566 if (me->preset_menu)
1567 return me->preset_menu;
1568
1569#if 0
1570 /* Expect the game to implement exactly one of the two preset APIs */
1571 assert(me->ourgame->fetch_preset || me->ourgame->preset_menu);
1572 assert(!(me->ourgame->fetch_preset && me->ourgame->preset_menu));
1573#endif
1574
1575 if (me->ourgame->fetch_preset) {
1576 char *name;
1577 game_params *preset;
1578
1579 /* Simple one-level menu */
1580 assert(!me->ourgame->preset_menu);
1581 me->preset_menu = preset_menu_new();
1582 for (i = 0; me->ourgame->fetch_preset(i, &name, &preset); i++)
1583 preset_menu_add_preset(me->preset_menu, name, preset);
1584
1585 } else {
1586 /* Hierarchical menu provided by the game backend */
1587 me->preset_menu = me->ourgame->preset_menu();
1588 }
1589
1590 {
1591 /*
1592 * Allow user extensions to the preset list by defining an
1593 * environment variable <gamename>_PRESETS whose value is a
1594 * colon-separated list of items, alternating between textual
1595 * titles in the menu and encoded parameter strings. For
1596 * example, "SOLO_PRESETS=2x3 Advanced:2x3da" would define
1597 * just one additional preset for Solo.
1598 */
1599 char buf[80], *e;
1600 int j, k;
1601
1602 sprintf(buf, "%s_PRESETS", me->ourgame->name);
1603 for (j = k = 0; buf[j]; j++)
1604 if (!isspace((unsigned char)buf[j]))
1605 buf[k++] = toupper((unsigned char)buf[j]);
1606 buf[k] = '\0';
1607
1608 if ((e = getenv(buf)) != NULL) {
1609 e = dupstr(e);
1610 preset_menu_add_from_user_env(me, me->preset_menu, e, true);
1611 sfree(e);
1612 }
1613 }
1614
1615 /*
1616 * Finalise the menu: allocate an integer id to each entry, and
1617 * store string encodings of the presets' parameters in
1618 * me->encoded_presets.
1619 */
1620 me->n_encoded_presets = 0;
1621 preset_menu_alloc_ids(me, me->preset_menu);
1622 me->encoded_presets = snewn(me->n_encoded_presets, char *);
1623 for (i = 0; i < me->n_encoded_presets; i++)
1624 me->encoded_presets[i] = NULL;
1625 preset_menu_encode_params(me, me->preset_menu);
1626
1627 if (id_limit)
1628 *id_limit = me->n_encoded_presets;
1629 return me->preset_menu;
1630}
1631
1632int midend_which_preset(midend *me)
1633{
1634 char *encoding = encode_params(me, me->params, true);
1635 int i, ret;
1636
1637 ret = -1;
1638 for (i = 0; i < me->n_encoded_presets; i++)
1639 if (me->encoded_presets[i] &&
1640 !strcmp(encoding, me->encoded_presets[i])) {
1641 ret = i;
1642 break;
1643 }
1644
1645 sfree(encoding);
1646 return ret;
1647}
1648
1649bool midend_wants_statusbar(midend *me)
1650{
1651 return me->ourgame->wants_statusbar;
1652}
1653
1654void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx)
1655{
1656 me->game_id_change_notify_function = notify;
1657 me->game_id_change_notify_ctx = ctx;
1658}
1659
1660bool midend_get_cursor_location(midend *me,
1661 int *x_out, int *y_out,
1662 int *w_out, int *h_out)
1663{
1664 int x, y, w, h;
1665 x = y = -1;
1666 w = h = 1;
1667
1668 if(me->ourgame->get_cursor_location)
1669 me->ourgame->get_cursor_location(me->ui,
1670 me->drawstate,
1671 me->states[me->statepos-1].state,
1672 me->params,
1673 &x, &y, &w, &h);
1674
1675 if(x == -1 && y == -1)
1676 return false;
1677
1678 if(x_out)
1679 *x_out = x;
1680 if(y_out)
1681 *y_out = y;
1682 if(w_out)
1683 *w_out = w;
1684 if(h_out)
1685 *h_out = h;
1686 return true;
1687}
1688
1689void midend_supersede_game_desc(midend *me, const char *desc,
1690 const char *privdesc)
1691{
1692 /* Assert that the descriptions consists only of printable ASCII. */
1693 assert_printable_ascii(desc);
1694 if (privdesc)
1695 assert_printable_ascii(privdesc);
1696 sfree(me->desc);
1697 sfree(me->privdesc);
1698 me->desc = dupstr(desc);
1699 me->privdesc = privdesc ? dupstr(privdesc) : NULL;
1700 if (me->game_id_change_notify_function)
1701 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
1702}
1703
1704config_item *midend_get_config(midend *me, int which, char **wintitle)
1705{
1706 char *titlebuf, *parstr;
1707 const char *rest;
1708 config_item *ret;
1709 char sep;
1710
1711 assert(wintitle);
1712 titlebuf = snewn(40 + strlen(me->ourgame->name), char);
1713
1714 switch (which) {
1715 case CFG_SETTINGS:
1716 sprintf(titlebuf, "%s configuration", me->ourgame->name);
1717 *wintitle = titlebuf;
1718 return me->ourgame->configure(me->params);
1719 case CFG_SEED:
1720 case CFG_DESC:
1721 if (!me->curparams) {
1722 sfree(titlebuf);
1723 return NULL;
1724 }
1725 sprintf(titlebuf, "%s %s selection", me->ourgame->name,
1726 which == CFG_SEED ? "random" : "game");
1727 *wintitle = titlebuf;
1728
1729 ret = snewn(2, config_item);
1730
1731 ret[0].type = C_STRING;
1732 if (which == CFG_SEED)
1733 ret[0].name = "Game random seed";
1734 else
1735 ret[0].name = "Game ID";
1736 /*
1737 * For CFG_DESC the text going in here will be a string
1738 * encoding of the restricted parameters, plus a colon,
1739 * plus the game description. For CFG_SEED it will be the
1740 * full parameters, plus a hash, plus the random seed data.
1741 * Either of these is a valid full game ID (although only
1742 * the former is likely to persist across many code
1743 * changes).
1744 */
1745 parstr = encode_params(me, me->curparams, which == CFG_SEED);
1746 assert(parstr);
1747 if (which == CFG_DESC) {
1748 rest = me->desc ? me->desc : "";
1749 sep = ':';
1750 } else {
1751 rest = me->seedstr ? me->seedstr : "";
1752 sep = '#';
1753 }
1754 ret[0].u.string.sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
1755 sprintf(ret[0].u.string.sval, "%s%c%s", parstr, sep, rest);
1756 sfree(parstr);
1757
1758 ret[1].type = C_END;
1759 ret[1].name = NULL;
1760
1761 return ret;
1762 case CFG_PREFS:
1763 sprintf(titlebuf, "%s preferences", me->ourgame->name);
1764 *wintitle = titlebuf;
1765 return midend_get_prefs(me, NULL);
1766 }
1767
1768 assert(!"We shouldn't be here");
1769 return NULL;
1770}
1771
1772static const char *midend_game_id_int(midend *me, const char *id, int defmode)
1773{
1774 const char *error;
1775 char *par = NULL;
1776 const char *desc, *seed;
1777 game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
1778 bool free_params;
1779
1780 seed = strchr(id, '#');
1781 desc = strchr(id, ':');
1782
1783 if (desc && (!seed || desc < seed)) {
1784 /*
1785 * We have a colon separating parameters from game
1786 * description. So `par' now points to the parameters
1787 * string, and `desc' to the description string.
1788 */
1789 par = snewn(desc-id + 1, char);
1790 strncpy(par, id, desc-id);
1791 par[desc-id] = '\0';
1792 desc++;
1793 seed = NULL;
1794 } else if (seed && (!desc || seed < desc)) {
1795 /*
1796 * We have a hash separating parameters from random seed.
1797 * So `par' now points to the parameters string, and `seed'
1798 * to the seed string.
1799 */
1800 par = snewn(seed-id + 1, char);
1801 strncpy(par, id, seed-id);
1802 par[seed-id] = '\0';
1803 seed++;
1804 desc = NULL;
1805 } else {
1806 /*
1807 * We only have one string. Depending on `defmode', we take
1808 * it to be either parameters, seed or description.
1809 */
1810 if (defmode == DEF_SEED) {
1811 seed = id;
1812 par = NULL;
1813 desc = NULL;
1814 } else if (defmode == DEF_DESC) {
1815 desc = id;
1816 par = NULL;
1817 seed = NULL;
1818 } else {
1819 par = dupstr(id);
1820 seed = desc = NULL;
1821 }
1822 }
1823
1824 /*
1825 * We must be reasonably careful here not to modify anything in
1826 * `me' until we have finished validating things. This function
1827 * must either return an error and do nothing to the midend, or
1828 * return success and do everything; nothing in between is
1829 * acceptable.
1830 */
1831 newcurparams = newparams = oldparams1 = oldparams2 = NULL;
1832
1833 if (par) {
1834 /*
1835 * The params string may underspecify the game parameters, so
1836 * we must first initialise newcurparams with a full set of
1837 * params from somewhere else before we decode_params the
1838 * input string over the top.
1839 *
1840 * But which set? It depends on what other data we have.
1841 *
1842 * If we've been given a _descriptive_ game id, then that may
1843 * well underspecify by design, e.g. Solo game descriptions
1844 * often start just '3x3:' without specifying one of Solo's
1845 * difficulty settings, because it isn't necessary once a game
1846 * has been generated (and you might not even know it, if
1847 * you're manually transcribing a game description). In that
1848 * situation, I've always felt that the best thing to set the
1849 * difficulty to (for use if the user hits 'New Game' after
1850 * pasting in that game id) is whatever it was previously set
1851 * to. That is, we use whatever is already in me->params as
1852 * the basis for our decoding of this input string.
1853 *
1854 * A random-seed based game id, however, should use the real,
1855 * built-in default params, and not even check the
1856 * <game>_DEFAULT environment setting, because when people
1857 * paste each other random seeds - whether it's two users
1858 * arranging to generate the same game at the same time to
1859 * race solving them, or a user sending a bug report upstream
1860 * - the whole point is for the random game id to always be
1861 * interpreted the same way, even if it does underspecify.
1862 *
1863 * A parameter string typed in on its own, with no seed _or_
1864 * description, gets treated the same way as a random seed,
1865 * because again I think the most likely reason for doing that
1866 * is to have a portable representation of a set of params.
1867 */
1868 if (desc) {
1869 newcurparams = me->ourgame->dup_params(me->params);
1870 } else {
1871 newcurparams = me->ourgame->default_params();
1872 }
1873 me->ourgame->decode_params(newcurparams, par);
1874 sfree(par);
1875 error = me->ourgame->validate_params(newcurparams, desc == NULL);
1876 if (error) {
1877 me->ourgame->free_params(newcurparams);
1878 return error;
1879 }
1880 oldparams1 = me->curparams;
1881
1882 /*
1883 * Now filter only the persistent parts of this state into
1884 * the long-term params structure, unless we've _only_
1885 * received a params string in which case the whole lot is
1886 * persistent.
1887 */
1888 oldparams2 = me->params;
1889 if (seed || desc) {
1890 char *tmpstr;
1891
1892 newparams = me->ourgame->dup_params(me->params);
1893
1894 tmpstr = encode_params(me, newcurparams, false);
1895 me->ourgame->decode_params(newparams, tmpstr);
1896
1897 sfree(tmpstr);
1898 } else {
1899 newparams = me->ourgame->dup_params(newcurparams);
1900 }
1901 free_params = true;
1902 } else {
1903 newcurparams = me->curparams;
1904 newparams = me->params;
1905 free_params = false;
1906 }
1907
1908 if (desc) {
1909 error = me->ourgame->validate_desc(newparams, desc);
1910 if (error) {
1911 if (free_params) {
1912 if (newcurparams)
1913 me->ourgame->free_params(newcurparams);
1914 if (newparams)
1915 me->ourgame->free_params(newparams);
1916 }
1917 return error;
1918 }
1919 }
1920
1921 /*
1922 * Now we've got past all possible error points. Update the
1923 * midend itself.
1924 */
1925 me->params = newparams;
1926 me->curparams = newcurparams;
1927 if (oldparams1)
1928 me->ourgame->free_params(oldparams1);
1929 if (oldparams2)
1930 me->ourgame->free_params(oldparams2);
1931
1932 sfree(me->desc);
1933 sfree(me->privdesc);
1934 me->desc = me->privdesc = NULL;
1935 sfree(me->seedstr);
1936 me->seedstr = NULL;
1937
1938 if (desc) {
1939 me->desc = dupstr(desc);
1940 me->genmode = GOT_DESC;
1941 sfree(me->aux_info);
1942 me->aux_info = NULL;
1943 }
1944
1945 if (seed) {
1946 me->seedstr = dupstr(seed);
1947 me->genmode = GOT_SEED;
1948 }
1949
1950 me->newgame_can_store_undo = false;
1951
1952 return NULL;
1953}
1954
1955const char *midend_game_id(midend *me, const char *id)
1956{
1957 return midend_game_id_int(me, id, DEF_PARAMS);
1958}
1959
1960char *midend_get_game_id(midend *me)
1961{
1962 char *parstr, *ret;
1963
1964 parstr = encode_params(me, me->curparams, false);
1965 assert(parstr);
1966 assert(me->desc);
1967 ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
1968 sprintf(ret, "%s:%s", parstr, me->desc);
1969 sfree(parstr);
1970 return ret;
1971}
1972
1973char *midend_get_random_seed(midend *me)
1974{
1975 char *parstr, *ret;
1976
1977 if (!me->seedstr)
1978 return NULL;
1979
1980 parstr = encode_params(me, me->curparams, true);
1981 assert(parstr);
1982 ret = snewn(strlen(parstr) + strlen(me->seedstr) + 2, char);
1983 sprintf(ret, "%s#%s", parstr, me->seedstr);
1984 sfree(parstr);
1985 return ret;
1986}
1987
1988const char *midend_set_config(midend *me, int which, config_item *cfg)
1989{
1990 const char *error;
1991 game_params *params;
1992
1993 switch (which) {
1994 case CFG_SETTINGS:
1995 params = me->ourgame->custom_params(cfg);
1996 error = me->ourgame->validate_params(params, true);
1997
1998 if (error) {
1999 me->ourgame->free_params(params);
2000 return error;
2001 }
2002
2003 me->ourgame->free_params(me->params);
2004 me->params = params;
2005 break;
2006
2007 case CFG_SEED:
2008 case CFG_DESC:
2009 error = midend_game_id_int(me, cfg[0].u.string.sval,
2010 (which == CFG_SEED ? DEF_SEED : DEF_DESC));
2011 if (error)
2012 return error;
2013 break;
2014
2015 case CFG_PREFS:
2016 midend_set_prefs(me, me->ui, cfg);
2017 break;
2018 }
2019
2020 return NULL;
2021}
2022
2023bool midend_can_format_as_text_now(midend *me)
2024{
2025 if (me->ourgame->can_format_as_text_ever)
2026 return me->ourgame->can_format_as_text_now(me->params);
2027 else
2028 return false;
2029}
2030
2031char *midend_text_format(midend *me)
2032{
2033 if (me->ourgame->can_format_as_text_ever && me->statepos > 0 &&
2034 me->ourgame->can_format_as_text_now(me->params))
2035 return me->ourgame->text_format(me->states[me->statepos-1].state);
2036 else
2037 return NULL;
2038}
2039
2040const char *midend_solve(midend *me)
2041{
2042 game_state *s;
2043 const char *msg;
2044 char *movestr;
2045
2046 if (!me->ourgame->can_solve)
2047 return "This game does not support the Solve operation";
2048
2049 if (me->statepos < 1)
2050 return "No game set up to solve"; /* _shouldn't_ happen! */
2051
2052 msg = NULL;
2053 movestr = me->ourgame->solve(me->states[0].state,
2054 me->states[me->statepos-1].state,
2055 me->aux_info, &msg);
2056 assert(movestr != MOVE_UI_UPDATE);
2057 if (!movestr) {
2058 if (!msg)
2059 msg = "Solve operation failed"; /* _shouldn't_ happen, but can */
2060 return msg;
2061 }
2062 assert_printable_ascii(movestr);
2063 s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
2064 assert(s);
2065
2066 /*
2067 * Now enter the solved state as the next move.
2068 */
2069 midend_stop_anim(me);
2070 midend_purge_states(me);
2071 ensure(me);
2072 me->states[me->nstates].state = s;
2073 me->states[me->nstates].movestr = movestr;
2074 me->states[me->nstates].movetype = SOLVE;
2075 me->statepos = ++me->nstates;
2076 if (me->ui)
2077 me->ourgame->changed_state(me->ui,
2078 me->states[me->statepos-2].state,
2079 me->states[me->statepos-1].state);
2080 me->dir = +1;
2081 if (me->ourgame->flags & SOLVE_ANIMATES) {
2082 me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state);
2083 me->anim_time =
2084 me->ourgame->anim_length(me->states[me->statepos-2].state,
2085 me->states[me->statepos-1].state,
2086 +1, me->ui);
2087 me->anim_pos = 0.0;
2088 } else {
2089 me->anim_time = 0.0;
2090 midend_finish_move(me);
2091 }
2092 if (me->drawing)
2093 midend_redraw(me);
2094 midend_set_timer(me);
2095 return NULL;
2096}
2097
2098int midend_status(midend *me)
2099{
2100 /*
2101 * We should probably never be called when the state stack has no
2102 * states on it at all - ideally, midends should never be left in
2103 * that state for long enough to get put down and forgotten about.
2104 * But if we are, I think we return _true_ - pedantically speaking
2105 * a midend in that state is 'vacuously solved', and more
2106 * practically, a user whose midend has been left in that state
2107 * probably _does_ want the 'new game' option to be prominent.
2108 */
2109 if (me->statepos == 0)
2110 return +1;
2111
2112 return me->ourgame->status(me->states[me->statepos-1].state);
2113}
2114
2115char *midend_rewrite_statusbar(midend *me, const char *text)
2116{
2117 /*
2118 * An important special case is that we are occasionally called
2119 * with our own laststatus, to update the timer.
2120 */
2121 if (me->laststatus != text) {
2122 sfree(me->laststatus);
2123 me->laststatus = dupstr(text);
2124 }
2125
2126 if (me->ourgame->is_timed) {
2127 char timebuf[100], *ret;
2128 int min, sec;
2129
2130 sec = (int)me->elapsed;
2131 min = sec / 60;
2132 sec %= 60;
2133 sprintf(timebuf, "[%d:%02d] ", min, sec);
2134
2135 ret = snewn(strlen(timebuf) + strlen(text) + 1, char);
2136 strcpy(ret, timebuf);
2137 strcat(ret, text);
2138 return ret;
2139
2140 } else {
2141 return dupstr(text);
2142 }
2143}
2144
2145#define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
2146#define SERIALISE_VERSION "1"
2147
2148void midend_serialise(midend *me,
2149 void (*write)(void *ctx, const void *buf, int len),
2150 void *wctx)
2151{
2152 int i;
2153
2154 /*
2155 * Each line of the save file contains three components. First
2156 * exactly 8 characters of header word indicating what type of
2157 * data is contained on the line; then a colon followed by a
2158 * decimal integer giving the length of the main string on the
2159 * line; then a colon followed by the string itself (exactly as
2160 * many bytes as previously specified, no matter what they
2161 * contain). Then a newline (of reasonably flexible form).
2162 */
2163#define wr(h,s) do { \
2164 char hbuf[80]; \
2165 const char *str = (s); \
2166 char lbuf[9]; \
2167 copy_left_justified(lbuf, sizeof(lbuf), h); \
2168 sprintf(hbuf, "%s:%d:", lbuf, (int)strlen(str)); \
2169 assert_printable_ascii(hbuf); \
2170 write(wctx, hbuf, strlen(hbuf)); \
2171 assert_printable_ascii(str); \
2172 write(wctx, str, strlen(str)); \
2173 write(wctx, "\n", 1); \
2174} while (0)
2175
2176 /*
2177 * Magic string identifying the file, and version number of the
2178 * file format.
2179 */
2180 wr("SAVEFILE", SERIALISE_MAGIC);
2181 wr("VERSION", SERIALISE_VERSION);
2182
2183 /*
2184 * The game name. (Copied locally to avoid const annoyance.)
2185 */
2186 {
2187 char *s = dupstr(me->ourgame->name);
2188 wr("GAME", s);
2189 sfree(s);
2190 }
2191
2192 /*
2193 * The current long-term parameters structure, in full.
2194 */
2195 if (me->params) {
2196 char *s = encode_params(me, me->params, true);
2197 wr("PARAMS", s);
2198 sfree(s);
2199 }
2200
2201 /*
2202 * The current short-term parameters structure, in full.
2203 */
2204 if (me->curparams) {
2205 char *s = encode_params(me, me->curparams, true);
2206 wr("CPARAMS", s);
2207 sfree(s);
2208 }
2209
2210 /*
2211 * The current game description, the privdesc, and the random seed.
2212 */
2213 if (me->seedstr) {
2214 /*
2215 * Random seeds are not necessarily printable ASCII.
2216 * Hex-encode the seed if necessary. Printable ASCII seeds
2217 * are emitted unencoded for compatibility with older
2218 * versions.
2219 */
2220 int i;
2221
2222 for (i = 0; me->seedstr[i]; i++)
2223 if (me->seedstr[i] < 32 || me->seedstr[i] >= 127)
2224 break;
2225 if (me->seedstr[i]) {
2226 char *hexseed = bin2hex((unsigned char *)me->seedstr,
2227 strlen(me->seedstr));
2228
2229 wr("HEXSEED", hexseed);
2230 sfree(hexseed);
2231 } else
2232 wr("SEED", me->seedstr);
2233 }
2234 if (me->desc)
2235 wr("DESC", me->desc);
2236 if (me->privdesc)
2237 wr("PRIVDESC", me->privdesc);
2238
2239 /*
2240 * The game's aux_info. We obfuscate this to prevent spoilers
2241 * (people are likely to run `head' or similar on a saved game
2242 * file simply to find out what it is, and don't necessarily
2243 * want to be told the answer to the puzzle!)
2244 */
2245 if (me->aux_info) {
2246 unsigned char *s1;
2247 char *s2;
2248 int len;
2249
2250 len = strlen(me->aux_info);
2251 s1 = snewn(len, unsigned char);
2252 memcpy(s1, me->aux_info, len);
2253 obfuscate_bitmap(s1, len*8, false);
2254 s2 = bin2hex(s1, len);
2255
2256 wr("AUXINFO", s2);
2257
2258 sfree(s2);
2259 sfree(s1);
2260 }
2261
2262 /*
2263 * Any required serialisation of the game_ui.
2264 */
2265 if (me->ui && me->ourgame->encode_ui) {
2266 char *s = me->ourgame->encode_ui(me->ui);
2267 if (s) {
2268 wr("UI", s);
2269 sfree(s);
2270 }
2271 }
2272
2273 /*
2274 * The game time, if it's a timed game.
2275 */
2276 if (me->ourgame->is_timed) {
2277 char buf[80];
2278 sprintf(buf, "%g", me->elapsed);
2279 wr("TIME", buf);
2280 }
2281
2282 /*
2283 * The length of, and position in, the states list.
2284 */
2285 {
2286 char buf[80];
2287 sprintf(buf, "%d", me->nstates);
2288 wr("NSTATES", buf);
2289 assert(me->statepos >= 1 && me->statepos <= me->nstates);
2290 sprintf(buf, "%d", me->statepos);
2291 wr("STATEPOS", buf);
2292 }
2293
2294 /*
2295 * For each state after the initial one (which we know is
2296 * constructed from either privdesc or desc), enough
2297 * information for execute_move() to reconstruct it from the
2298 * previous one.
2299 */
2300 for (i = 1; i < me->nstates; i++) {
2301 assert(me->states[i].movetype != NEWGAME); /* only state 0 */
2302 switch (me->states[i].movetype) {
2303 case MOVE:
2304 wr("MOVE", me->states[i].movestr);
2305 break;
2306 case SOLVE:
2307 wr("SOLVE", me->states[i].movestr);
2308 break;
2309 case RESTART:
2310 wr("RESTART", me->states[i].movestr);
2311 break;
2312 }
2313 }
2314
2315#undef wr
2316}
2317
2318/*
2319 * Internal version of midend_deserialise, taking an extra check
2320 * function to be called just before beginning to install things in
2321 * the midend.
2322 *
2323 * Like midend_deserialise proper, this function returns NULL on
2324 * success, or an error message.
2325 */
2326static const char *midend_deserialise_internal(
2327 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx,
2328 const char *(*check)(void *ctx, midend *, const struct deserialise_data *),
2329 void *cctx)
2330{
2331 struct deserialise_data data;
2332 int gotstates = 0;
2333 bool started = false;
2334 int i;
2335
2336 char *val = NULL;
2337 /* Initially all errors give the same report */
2338 const char *ret = "Data does not appear to be a saved game file";
2339
2340 data.seed = data.parstr = data.desc = data.privdesc = NULL;
2341 data.auxinfo = data.uistr = data.cparstr = NULL;
2342 data.elapsed = 0.0F;
2343 data.params = data.cparams = NULL;
2344 data.ui = NULL;
2345 data.states = NULL;
2346 data.nstates = 0;
2347 data.statepos = -1;
2348
2349 /*
2350 * Loop round and round reading one key/value pair at a time
2351 * from the serialised stream, until we have enough game states
2352 * to finish.
2353 */
2354 while (data.nstates <= 0 || data.statepos < 0 ||
2355 gotstates < data.nstates-1) {
2356 char key[9], c;
2357 int len;
2358
2359 do {
2360 if (!read(rctx, key, 1)) {
2361 /* unexpected EOF */
2362 goto cleanup;
2363 }
2364 } while (key[0] == '\r' || key[0] == '\n');
2365
2366 if (!read(rctx, key+1, 8)) {
2367 /* unexpected EOF */
2368 goto cleanup;
2369 }
2370
2371 if (key[8] != ':') {
2372 if (started)
2373 ret = "Data was incorrectly formatted for a saved game file";
2374 goto cleanup;
2375 }
2376 len = strcspn(key, ": ");
2377 assert(len <= 8);
2378 key[len] = '\0';
2379
2380 len = 0;
2381 while (1) {
2382 if (!read(rctx, &c, 1)) {
2383 /* unexpected EOF */
2384 goto cleanup;
2385 }
2386
2387 if (c == ':') {
2388 break;
2389 } else if (c >= '0' && c <= '9' && len < (INT_MAX - 10) / 10) {
2390 len = (len * 10) + (c - '0');
2391 } else {
2392 if (started)
2393 ret = "Data was incorrectly formatted for a"
2394 " saved game file";
2395 goto cleanup;
2396 }
2397 }
2398
2399 val = snewn(len+1, char);
2400 if (!read(rctx, val, len)) {
2401 /* unexpected EOF */
2402 goto cleanup;
2403 }
2404 val[len] = '\0';
2405 /* Validate that all values (apart from SEED) are printable ASCII. */
2406 if (strcmp(key, "SEED"))
2407 for (i = 0; val[i]; i++)
2408 if (val[i] < 32 || val[i] >= 127) {
2409 ret = "Forbidden characters in saved game file";
2410 goto cleanup;
2411 }
2412
2413 if (!started) {
2414 if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
2415 /* ret already has the right message in it */
2416 goto cleanup;
2417 }
2418 /* Now most errors are this one, unless otherwise specified */
2419 ret = "Saved data ended unexpectedly";
2420 started = true;
2421 } else {
2422 if (!strcmp(key, "VERSION")) {
2423 if (strcmp(val, SERIALISE_VERSION)) {
2424 ret = "Cannot handle this version of the saved game"
2425 " file format";
2426 goto cleanup;
2427 }
2428 } else if (!strcmp(key, "GAME")) {
2429 if (strcmp(val, me->ourgame->name)) {
2430 ret = "Save file is from a different game";
2431 goto cleanup;
2432 }
2433 } else if (!strcmp(key, "PARAMS")) {
2434 sfree(data.parstr);
2435 data.parstr = val;
2436 val = NULL;
2437 } else if (!strcmp(key, "CPARAMS")) {
2438 sfree(data.cparstr);
2439 data.cparstr = val;
2440 val = NULL;
2441 } else if (!strcmp(key, "HEXSEED")) {
2442 unsigned char *tmp;
2443 int len = strlen(val) / 2; /* length in bytes */
2444 tmp = hex2bin(val, len);
2445 sfree(data.seed);
2446 data.seed = snewn(len + 1, char);
2447 memcpy(data.seed, tmp, len);
2448 data.seed[len] = '\0';
2449 sfree(tmp);
2450 } else if (!strcmp(key, "SEED")) {
2451 sfree(data.seed);
2452 data.seed = val;
2453 val = NULL;
2454 } else if (!strcmp(key, "DESC")) {
2455 sfree(data.desc);
2456 data.desc = val;
2457 val = NULL;
2458 } else if (!strcmp(key, "PRIVDESC")) {
2459 sfree(data.privdesc);
2460 data.privdesc = val;
2461 val = NULL;
2462 } else if (!strcmp(key, "AUXINFO")) {
2463 unsigned char *tmp;
2464 int len = strlen(val) / 2; /* length in bytes */
2465 tmp = hex2bin(val, len);
2466 obfuscate_bitmap(tmp, len*8, true);
2467
2468 sfree(data.auxinfo);
2469 data.auxinfo = snewn(len + 1, char);
2470 memcpy(data.auxinfo, tmp, len);
2471 data.auxinfo[len] = '\0';
2472 sfree(tmp);
2473 } else if (!strcmp(key, "UI")) {
2474 sfree(data.uistr);
2475 data.uistr = val;
2476 val = NULL;
2477 } else if (!strcmp(key, "TIME")) {
2478 data.elapsed = (float)atof(val);
2479 } else if (!strcmp(key, "NSTATES")) {
2480 if (data.states) {
2481 ret = "Two state counts provided in save file";
2482 goto cleanup;
2483 }
2484 data.nstates = atoi(val);
2485 if (data.nstates <= 0) {
2486 ret = "Number of states in save file was negative";
2487 goto cleanup;
2488 }
2489 data.states = snewn(data.nstates, struct midend_state_entry);
2490 for (i = 0; i < data.nstates; i++) {
2491 data.states[i].state = NULL;
2492 data.states[i].movestr = NULL;
2493 data.states[i].movetype = NEWGAME;
2494 }
2495 } else if (!strcmp(key, "STATEPOS")) {
2496 data.statepos = atoi(val);
2497 } else if (!strcmp(key, "MOVE") ||
2498 !strcmp(key, "SOLVE") ||
2499 !strcmp(key, "RESTART")) {
2500 if (!data.states) {
2501 ret = "No state count provided in save file";
2502 goto cleanup;
2503 }
2504 if (data.statepos < 0) {
2505 ret = "No game position provided in save file";
2506 goto cleanup;
2507 }
2508 gotstates++;
2509 assert(gotstates < data.nstates);
2510 if (!strcmp(key, "MOVE"))
2511 data.states[gotstates].movetype = MOVE;
2512 else if (!strcmp(key, "SOLVE"))
2513 data.states[gotstates].movetype = SOLVE;
2514 else
2515 data.states[gotstates].movetype = RESTART;
2516 data.states[gotstates].movestr = val;
2517 val = NULL;
2518 }
2519 }
2520
2521 sfree(val);
2522 val = NULL;
2523 }
2524
2525 data.params = me->ourgame->default_params();
2526 if (!data.parstr) {
2527 ret = "Long-term parameters in save file are missing";
2528 goto cleanup;
2529 }
2530 me->ourgame->decode_params(data.params, data.parstr);
2531 if (me->ourgame->validate_params(data.params, true)) {
2532 ret = "Long-term parameters in save file are invalid";
2533 goto cleanup;
2534 }
2535 data.cparams = me->ourgame->default_params();
2536 if (!data.cparstr) {
2537 ret = "Short-term parameters in save file are missing";
2538 goto cleanup;
2539 }
2540 me->ourgame->decode_params(data.cparams, data.cparstr);
2541 if (me->ourgame->validate_params(data.cparams, false)) {
2542 ret = "Short-term parameters in save file are invalid";
2543 goto cleanup;
2544 }
2545 if (data.seed && me->ourgame->validate_params(data.cparams, true)) {
2546 /*
2547 * The seed's no use with this version, but we can perfectly
2548 * well use the rest of the data.
2549 */
2550 sfree(data.seed);
2551 data.seed = NULL;
2552 }
2553 if (!data.desc) {
2554 ret = "Game description in save file is missing";
2555 goto cleanup;
2556 } else if (me->ourgame->validate_desc(data.cparams, data.desc)) {
2557 ret = "Game description in save file is invalid";
2558 goto cleanup;
2559 }
2560 if (data.privdesc &&
2561 me->ourgame->validate_desc(data.cparams, data.privdesc)) {
2562 ret = "Game private description in save file is invalid";
2563 goto cleanup;
2564 }
2565 if (data.statepos < 1 || data.statepos > data.nstates) {
2566 ret = "Game position in save file is out of range";
2567 goto cleanup;
2568 }
2569
2570 if (!data.states) {
2571 ret = "No state count provided in save file";
2572 goto cleanup;
2573 }
2574 data.states[0].state = me->ourgame->new_game(
2575 me, data.cparams, data.privdesc ? data.privdesc : data.desc);
2576
2577 for (i = 1; i < data.nstates; i++) {
2578 assert(data.states[i].movetype != NEWGAME);
2579 switch (data.states[i].movetype) {
2580 case MOVE:
2581 case SOLVE:
2582 data.states[i].state = me->ourgame->execute_move(
2583 data.states[i-1].state, data.states[i].movestr);
2584 if (data.states[i].state == NULL) {
2585 ret = "Save file contained an invalid move";
2586 goto cleanup;
2587 }
2588 break;
2589 case RESTART:
2590 if (me->ourgame->validate_desc(
2591 data.cparams, data.states[i].movestr)) {
2592 ret = "Save file contained an invalid restart move";
2593 goto cleanup;
2594 }
2595 data.states[i].state = me->ourgame->new_game(
2596 me, data.cparams, data.states[i].movestr);
2597 break;
2598 }
2599 }
2600
2601 data.ui = me->ourgame->new_ui(data.states[0].state);
2602 midend_apply_prefs(me, data.ui);
2603 if (data.uistr && me->ourgame->decode_ui)
2604 me->ourgame->decode_ui(data.ui, data.uistr,
2605 data.states[data.statepos-1].state);
2606
2607 /*
2608 * Run the externally provided check function, and abort if it
2609 * returns an error message.
2610 */
2611 if (check && (ret = check(cctx, me, &data)) != NULL)
2612 goto cleanup; /* error message is already in ret */
2613
2614 /*
2615 * Now we've run out of possible error conditions, so we're
2616 * ready to start overwriting the real data in the current
2617 * midend. We'll do this by swapping things with the local
2618 * variables, so that the same cleanup code will free the old
2619 * stuff.
2620 */
2621 {
2622 char *tmp;
2623
2624 tmp = me->desc;
2625 me->desc = data.desc;
2626 data.desc = tmp;
2627
2628 tmp = me->privdesc;
2629 me->privdesc = data.privdesc;
2630 data.privdesc = tmp;
2631
2632 tmp = me->seedstr;
2633 me->seedstr = data.seed;
2634 data.seed = tmp;
2635
2636 tmp = me->aux_info;
2637 me->aux_info = data.auxinfo;
2638 data.auxinfo = tmp;
2639 }
2640
2641 me->genmode = GOT_NOTHING;
2642
2643 me->statesize = data.nstates;
2644 data.nstates = me->nstates;
2645 me->nstates = me->statesize;
2646 {
2647 struct midend_state_entry *tmp;
2648 tmp = me->states;
2649 me->states = data.states;
2650 data.states = tmp;
2651 }
2652 me->statepos = data.statepos;
2653
2654 /*
2655 * Don't save the "new game undo/redo" state. So "new game" twice or
2656 * (in some environments) switching away and back, will make a
2657 * "new game" irreversible. Maybe in the future we will have a
2658 * more sophisticated way to decide when to discard the previous
2659 * game state.
2660 */
2661 me->newgame_undo.len = 0;
2662 me->newgame_redo.len = 0;
2663
2664 {
2665 game_params *tmp;
2666
2667 tmp = me->params;
2668 me->params = data.params;
2669 data.params = tmp;
2670
2671 tmp = me->curparams;
2672 me->curparams = data.cparams;
2673 data.cparams = tmp;
2674 }
2675
2676 me->oldstate = NULL;
2677 me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F;
2678 me->dir = 0;
2679
2680 {
2681 game_ui *tmp;
2682
2683 tmp = me->ui;
2684 me->ui = data.ui;
2685 data.ui = tmp;
2686 }
2687
2688 me->elapsed = data.elapsed;
2689 me->pressed_mouse_button = 0;
2690
2691 midend_set_timer(me);
2692
2693 if (me->drawstate)
2694 me->ourgame->free_drawstate(me->drawing, me->drawstate);
2695 me->drawstate =
2696 me->ourgame->new_drawstate(me->drawing,
2697 me->states[me->statepos-1].state);
2698 me->first_draw = true;
2699 midend_size_new_drawstate(me);
2700 if (me->game_id_change_notify_function)
2701 me->game_id_change_notify_function(me->game_id_change_notify_ctx);
2702
2703 ret = NULL; /* success! */
2704
2705 cleanup:
2706 sfree(val);
2707 sfree(data.seed);
2708 sfree(data.parstr);
2709 sfree(data.cparstr);
2710 sfree(data.desc);
2711 sfree(data.privdesc);
2712 sfree(data.auxinfo);
2713 sfree(data.uistr);
2714 if (data.params)
2715 me->ourgame->free_params(data.params);
2716 if (data.cparams)
2717 me->ourgame->free_params(data.cparams);
2718 if (data.ui)
2719 me->ourgame->free_ui(data.ui);
2720 if (data.states) {
2721 int i;
2722
2723 for (i = 0; i < data.nstates; i++) {
2724 if (data.states[i].state)
2725 me->ourgame->free_game(data.states[i].state);
2726 sfree(data.states[i].movestr);
2727 }
2728 sfree(data.states);
2729 }
2730
2731 return ret;
2732}
2733
2734const char *midend_deserialise(
2735 midend *me, bool (*read)(void *ctx, void *buf, int len), void *rctx)
2736{
2737 return midend_deserialise_internal(me, read, rctx, NULL, NULL);
2738}
2739
2740/*
2741 * This function examines a saved game file just far enough to
2742 * determine which game type it contains. It returns NULL on success
2743 * and the game name string in 'name' (which will be dynamically
2744 * allocated and should be caller-freed), or an error message on
2745 * failure.
2746 */
2747const char *identify_game(char **name,
2748 bool (*read)(void *ctx, void *buf, int len),
2749 void *rctx)
2750{
2751 int nstates = 0, statepos = -1, gotstates = 0;
2752 bool started = false;
2753
2754 char *val = NULL;
2755 /* Initially all errors give the same report */
2756 const char *ret = "Data does not appear to be a saved game file";
2757
2758 *name = NULL;
2759
2760 /*
2761 * Loop round and round reading one key/value pair at a time from
2762 * the serialised stream, until we've found the game name.
2763 */
2764 while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) {
2765 char key[9], c;
2766 int len;
2767
2768 do {
2769 if (!read(rctx, key, 1)) {
2770 /* unexpected EOF */
2771 goto cleanup;
2772 }
2773 } while (key[0] == '\r' || key[0] == '\n');
2774
2775 if (!read(rctx, key+1, 8)) {
2776 /* unexpected EOF */
2777 goto cleanup;
2778 }
2779
2780 if (key[8] != ':') {
2781 if (started)
2782 ret = "Data was incorrectly formatted for a saved game file";
2783 goto cleanup;
2784 }
2785 len = strcspn(key, ": ");
2786 assert(len <= 8);
2787 key[len] = '\0';
2788
2789 len = 0;
2790 while (1) {
2791 if (!read(rctx, &c, 1)) {
2792 /* unexpected EOF */
2793 goto cleanup;
2794 }
2795
2796 if (c == ':') {
2797 break;
2798 } else if (c >= '0' && c <= '9' && len < (INT_MAX - 10) / 10) {
2799 len = (len * 10) + (c - '0');
2800 } else {
2801 if (started)
2802 ret = "Data was incorrectly formatted for a"
2803 " saved game file";
2804 goto cleanup;
2805 }
2806 }
2807
2808 val = snewn(len+1, char);
2809 if (!read(rctx, val, len)) {
2810 /* unexpected EOF */
2811 goto cleanup;
2812 }
2813 val[len] = '\0';
2814
2815 if (!started) {
2816 if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
2817 /* ret already has the right message in it */
2818 goto cleanup;
2819 }
2820 /* Now most errors are this one, unless otherwise specified */
2821 ret = "Saved data ended unexpectedly";
2822 started = true;
2823 } else {
2824 if (!strcmp(key, "VERSION")) {
2825 if (strcmp(val, SERIALISE_VERSION)) {
2826 ret = "Cannot handle this version of the saved game"
2827 " file format";
2828 goto cleanup;
2829 }
2830 } else if (!strcmp(key, "GAME")) {
2831 *name = dupstr(val);
2832 ret = NULL;
2833 goto cleanup;
2834 }
2835 }
2836
2837 sfree(val);
2838 val = NULL;
2839 }
2840
2841 cleanup:
2842 sfree(val);
2843 return ret;
2844}
2845
2846const char *midend_print_puzzle(midend *me, document *doc, bool with_soln)
2847{
2848 game_state *soln = NULL;
2849
2850 if (me->statepos < 1)
2851 return "No game set up to print";/* _shouldn't_ happen! */
2852
2853 if (with_soln) {
2854 const char *msg;
2855 char *movestr;
2856
2857 if (!me->ourgame->can_solve)
2858 return "This game does not support the Solve operation";
2859
2860 msg = "Solve operation failed";/* game _should_ overwrite on error */
2861 movestr = me->ourgame->solve(me->states[0].state,
2862 me->states[me->statepos-1].state,
2863 me->aux_info, &msg);
2864 if (!movestr)
2865 return msg;
2866 soln = me->ourgame->execute_move(me->states[me->statepos-1].state,
2867 movestr);
2868 assert(soln);
2869
2870 sfree(movestr);
2871 } else
2872 soln = NULL;
2873
2874 /*
2875 * This call passes over ownership of the two game_states, the
2876 * game_params and the game_ui. Hence we duplicate the ones we
2877 * want to keep, and we don't have to bother freeing soln if it
2878 * was non-NULL.
2879 */
2880 game_ui *ui = me->ourgame->new_ui(me->states[0].state);
2881 midend_apply_prefs(me, ui);
2882 document_add_puzzle(doc, me->ourgame,
2883 me->ourgame->dup_params(me->curparams), ui,
2884 me->ourgame->dup_game(me->states[0].state), soln);
2885
2886 return NULL;
2887}
2888
2889static void midend_apply_prefs(midend *me, game_ui *ui)
2890{
2891 struct midend_serialise_buf_read_ctx rctx[1];
2892 rctx->ser = &me->be_prefs;
2893 rctx->len = me->be_prefs.len;
2894 rctx->pos = 0;
2895 const char *err = midend_deserialise_prefs(
2896 me, ui, midend_serialise_buf_read, rctx);
2897 /* This should have come from our own serialise function, so
2898 * it should never be invalid. */
2899 assert(!err && "Bad internal serialisation of preferences");
2900}
2901
2902static config_item *midend_get_prefs(midend *me, game_ui *ui)
2903{
2904 int n_be_prefs, n_me_prefs, pos, i;
2905 config_item *all_prefs, *be_prefs;
2906
2907 be_prefs = NULL;
2908 n_be_prefs = 0;
2909 if (me->ourgame->get_prefs) {
2910 if (ui) {
2911 be_prefs = me->ourgame->get_prefs(ui);
2912 } else if (me->ui) {
2913 be_prefs = me->ourgame->get_prefs(me->ui);
2914 } else {
2915 game_ui *tmp_ui = me->ourgame->new_ui(NULL);
2916 be_prefs = me->ourgame->get_prefs(tmp_ui);
2917 me->ourgame->free_ui(tmp_ui);
2918 }
2919 while (be_prefs[n_be_prefs].type != C_END)
2920 n_be_prefs++;
2921 }
2922
2923 n_me_prefs = 1;
2924 all_prefs = snewn(n_me_prefs + n_be_prefs + 1, config_item);
2925
2926 pos = 0;
2927
2928 assert(pos < n_me_prefs);
2929 all_prefs[pos].name = "Keyboard shortcuts without Ctrl";
2930 all_prefs[pos].kw = "one-key-shortcuts";
2931 all_prefs[pos].type = C_BOOLEAN;
2932 all_prefs[pos].u.boolean.bval = me->one_key_shortcuts;
2933 pos++;
2934
2935 for (i = 0; i < n_be_prefs; i++) {
2936 all_prefs[pos] = be_prefs[i]; /* structure copy */
2937 pos++;
2938 }
2939
2940 all_prefs[pos].name = NULL;
2941 all_prefs[pos].type = C_END;
2942
2943 if (be_prefs)
2944 /* We already copied each element, so don't free those with
2945 free_cfg(). */
2946 sfree(be_prefs);
2947
2948 return all_prefs;
2949}
2950
2951static void midend_set_prefs(midend *me, game_ui *ui, config_item *all_prefs)
2952{
2953 int pos = 0;
2954 game_ui *tmpui = NULL;
2955
2956 me->one_key_shortcuts = all_prefs[pos].u.boolean.bval;
2957 pos++;
2958
2959 if (me->ourgame->get_prefs) {
2960 if (!ui)
2961 ui = tmpui = me->ourgame->new_ui(NULL);
2962 me->ourgame->set_prefs(ui, all_prefs + pos);
2963 }
2964
2965 me->be_prefs.len = 0;
2966 midend_serialise_prefs(me, ui, midend_serialise_buf_write, &me->be_prefs);
2967
2968 if (tmpui)
2969 me->ourgame->free_ui(tmpui);
2970}
2971
2972static void midend_serialise_prefs(
2973 midend *me, game_ui *ui,
2974 void (*write)(void *ctx, const void *buf, int len), void *wctx)
2975{
2976 config_item *cfg;
2977 int i;
2978
2979 cfg = midend_get_prefs(me, ui);
2980
2981 assert(cfg);
2982
2983 for (i = 0; cfg[i].type != C_END; i++) {
2984 config_item *it = &cfg[i];
2985
2986 /* Expect keywords to be made up only of simple characters */
2987 assert(it->kw[strspn(it->kw, "abcdefghijklmnopqrstuvwxyz-")] == '\0');
2988
2989 write(wctx, it->kw, strlen(it->kw));
2990 write(wctx, "=", 1);
2991
2992 switch (it->type) {
2993 case C_BOOLEAN:
2994 if (it->u.boolean.bval)
2995 write(wctx, "true", 4);
2996 else
2997 write(wctx, "false", 5);
2998 break;
2999 case C_STRING: {
3000 const char *p = it->u.string.sval;
3001 while (*p) {
3002 char c = *p++;
3003 write(wctx, &c, 1);
3004 if (c == '\n')
3005 write(wctx, " ", 1);
3006 }
3007 break;
3008 }
3009 case C_CHOICES: {
3010 int n = it->u.choices.selected;
3011 const char *p = it->u.choices.choicekws;
3012 char sepstr[2];
3013
3014 sepstr[0] = *p++;
3015 sepstr[1] = '\0';
3016
3017 while (n > 0) {
3018 const char *q = strchr(p, sepstr[0]);
3019 assert(q != NULL && "Value out of range in C_CHOICES");
3020 p = q+1;
3021 n--;
3022 }
3023
3024 write(wctx, p, strcspn(p, sepstr));
3025 break;
3026 }
3027 }
3028
3029 write(wctx, "\n", 1);
3030 }
3031
3032 free_cfg(cfg);
3033}
3034
3035struct buffer {
3036 char *data;
3037 size_t len, size;
3038};
3039
3040static void buffer_append(struct buffer *buf, char c)
3041{
3042 if (buf->len + 2 > buf->size) {
3043 size_t new_size = buf->size + buf->size / 4 + 128;
3044 assert(new_size > buf->size);
3045 buf->data = sresize(buf->data, new_size, char);
3046 buf->size = new_size;
3047 assert(buf->len < buf->size);
3048 }
3049 buf->data[buf->len++] = c;
3050 assert(buf->len < buf->size);
3051 buf->data[buf->len] = '\0';
3052}
3053
3054static const char *midend_deserialise_prefs(
3055 midend *me, game_ui *ui,
3056 bool (*read)(void *ctx, void *buf, int len), void *rctx)
3057{
3058 config_item *cfg, *it;
3059 int i;
3060 struct buffer buf[1] = {{ NULL, 0, 0 }};
3061 const char *errmsg = NULL;
3062 char read_char;
3063 char ungot_char = '\0';
3064 bool have_ungot_a_char = false, eof = false;
3065
3066 cfg = midend_get_prefs(me, ui);
3067
3068 while (!eof) {
3069 if (have_ungot_a_char) {
3070 read_char = ungot_char;
3071 have_ungot_a_char = false;
3072 } else {
3073 if (!read(rctx, &read_char, 1))
3074 goto out; /* EOF at line start == success */
3075 }
3076
3077 if (read_char == '#' || read_char == '\n') {
3078 /* Skip comment or blank line */
3079 while (read_char != '\n') {
3080 if (!read(rctx, &read_char, 1))
3081 goto out; /* EOF during boring line == success */
3082 }
3083 continue;
3084 }
3085
3086 buf->len = 0;
3087 while (true) {
3088 buffer_append(buf, read_char);
3089 if (!read(rctx, &read_char, 1)) {
3090 errmsg = "Partial line at end of preferences file";
3091 goto out;
3092 }
3093 if (read_char == '\n') {
3094 errmsg = "Expected '=' after keyword";
3095 goto out;
3096 }
3097 if (read_char == '=')
3098 break;
3099 }
3100
3101 it = NULL;
3102 for (i = 0; cfg[i].type != C_END; i++)
3103 if (!strcmp(buf->data, cfg[i].kw))
3104 it = &cfg[i];
3105
3106 buf->len = 0;
3107 while (true) {
3108 if (!read(rctx, &read_char, 1)) {
3109 /* We tolerate missing \n at the end of the file, so
3110 * this is taken to mean we've got a complete config
3111 * directive. But set the eof flag so that we stop
3112 * after processing it. */
3113 eof = true;
3114 break;
3115 } else if (read_char == '\n') {
3116 /* Newline _might_ be the end of this config
3117 * directive, unless it's followed by a space, in
3118 * which case it's a space-stuffed line
3119 * continuation. */
3120 if (read(rctx, &read_char, 1)) {
3121 if (read_char == ' ') {
3122 buffer_append(buf, '\n');
3123 continue;
3124 } else {
3125 /* But if the next character wasn't a space,
3126 * then we must unget it so that it'll be
3127 * available to the next iteration of our
3128 * outer loop as the first character of the
3129 * next keyword. */
3130 ungot_char = read_char;
3131 have_ungot_a_char = true;
3132 break;
3133 }
3134 } else {
3135 /* And if the newline was followed by EOF, then we
3136 * should finish this iteration of the outer
3137 * loop normally, and then not go round again. */
3138 eof = true;
3139 break;
3140 }
3141 } else {
3142 /* Any other character is just added to the buffer. */
3143 buffer_append(buf, read_char);
3144 }
3145 }
3146
3147 if (!it) {
3148 /*
3149 * Tolerate unknown keywords in a preferences file, on the
3150 * assumption that they're from a different (probably
3151 * later) version of the game.
3152 */
3153 continue;
3154 }
3155
3156 switch (it->type) {
3157 case C_BOOLEAN:
3158 if (!strcmp(buf->data, "true"))
3159 it->u.boolean.bval = true;
3160 else if (!strcmp(buf->data, "false"))
3161 it->u.boolean.bval = false;
3162 else {
3163 errmsg = "Value for boolean was not 'true' or 'false'";
3164 goto out;
3165 }
3166 break;
3167 case C_STRING:
3168 sfree(it->u.string.sval);
3169 it->u.string.sval = buf->data;
3170 buf->data = NULL;
3171 buf->len = buf->size = 0;
3172 break;
3173 case C_CHOICES: {
3174 int n = 0;
3175 bool found = false;
3176 const char *p = it->u.choices.choicekws;
3177 char sepstr[2];
3178
3179 sepstr[0] = *p;
3180 sepstr[1] = '\0';
3181
3182 while (*p++) {
3183 int len = strcspn(p, sepstr);
3184 if (buf->len == len && !memcmp(p, buf->data, len)) {
3185 it->u.choices.selected = n;
3186 found = true;
3187 break;
3188 }
3189 p += len;
3190 n++;
3191 }
3192
3193 if (!found) {
3194 errmsg = "Invalid value for enumeration";
3195 goto out;
3196 }
3197
3198 break;
3199 }
3200 }
3201 }
3202
3203 out:
3204
3205 if (!errmsg)
3206 midend_set_prefs(me, ui, cfg);
3207
3208 free_cfg(cfg);
3209 sfree(buf->data);
3210 return errmsg;
3211}