A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 3211 lines 99 kB view raw
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}