/* This is a lot like try_load_world but much more thorough, and doesn't * pass data through or leave a file open. This needs to be done before * any data is ever loaded, so that Megazeux can cleanly abort if there * is an issue. */ enum val_result validate_world_file(const char *filename, int savegame, int *end_of_global_offset, int decrypt_attempted) { enum val_result result = VAL_SUCCESS; FILE *f; char magic[15]; int num_boards; int board_name_offset; int v, i; /* TEST 1: Make sure it's a readable file */ if(!(f = val_fopen(filename))) { val_error(FILE_DOES_NOT_EXIST, 0); result = VAL_MISSING; goto err_out; } /* TEST 2: Is it a save file? */ if(savegame) { int screen_mode, num_counters, num_strings, len; if(fread(magic, 5, 1, f) != 1) goto err_invalid; v = save_magic(magic); if(!v) goto err_invalid; else if (v > WORLD_VERSION) { val_error(SAVE_VERSION_TOO_RECENT, v); result = VAL_VERSION; goto err_close; } else if (v < WORLD_VERSION) { val_error(SAVE_VERSION_OLD, v); result = VAL_VERSION; goto err_close; } /* TEST 3: Check for truncation, savegame style, hope this * doesn't explode :erm: */ if( fseek(f, 8, SEEK_SET) || fseek(f, WORLD_BLOCK_1_SIZE, SEEK_CUR) || fseek(f, 71, SEEK_CUR) || fseek(f, (len = fgetw(f)), SEEK_CUR) || (len < 0) || fseek(f, WORLD_BLOCK_2_SIZE, SEEK_CUR) || fseek(f, 24, SEEK_CUR)) { debug("pre-counters\n"); goto err_invalid; } //do counters - vvvvnnnn(name) num_counters = fgetd(f); if(num_counters < 0) { debug("counter num\n"); goto err_invalid; } for(i = 0; i < num_counters; i++) { if( fseek(f, 4, SEEK_CUR) || //value fseek(f, (len = fgetd(f)), SEEK_CUR) || (len < 0)) { debug("counters\n"); goto err_invalid; } } //do strings- nnnnllll(name)(value) num_strings = fgetd(f); if(num_strings < 0) { debug("string num\n"); goto err_invalid; } for(i = 0; i < num_strings; i++) { int name_length = fgetd(f); int value_length = fgetd(f); if( (name_length < 0) || (value_length < 0) || fseek(f, name_length, SEEK_CUR) || fseek(f, value_length, SEEK_CUR)) { debug("strings\n"); goto err_invalid; } } if( fseek(f, 4612, SEEK_CUR) || //sprites fseek(f, 12, SEEK_CUR) || //misc fseek(f, fgetw(f), SEEK_CUR) || //fread_open fseek(f, 4, SEEK_CUR) || //fread_pos fseek(f, fgetw(f), SEEK_CUR) || //fwrite_open fseek(f, 4, SEEK_CUR)) //fwrite_pos { debug("post strings\n"); goto err_invalid; } screen_mode = fgetw(f); if((screen_mode > 3) || (screen_mode > 1 && fseek(f, 768, SEEK_CUR))) //smzx palette { debug("smzx palette\n"); goto err_invalid; } if( fseek(f, 4, SEEK_CUR) || //commands ((len = fgetd(f)) < 0) || //vlayer size fseek(f, 4, SEEK_CUR) || // width & height fseek(f, len, SEEK_CUR) || //chars fseek(f, len, SEEK_CUR)) //colors { debug("vlayer\n"); goto err_invalid; } /* now we should be at the global robot pointer! */ } else /* !savegame */ { int protection_method; /* TEST 3: Check for truncation */ if(fseek(f, WORLD_GLOBAL_OFFSET_OFFSET, SEEK_SET)) goto err_invalid; fseek(f, BOARD_NAME_SIZE, SEEK_SET); /* TEST 4: If we think it's locked, try to decrypt it. */ protection_method = fgetc(f); if(protection_method > 0) { if(protection_method > 3) goto err_invalid; if(decrypt_attempted) // In the unlikely event that this will happen again goto err_invalid; val_error(WORLD_PASSWORD_PROTECTED, 0); if(!confirm(NULL, "Would you like to decrypt it?")) { result = VAL_NEED_UNLOCK; goto err_close; } else { val_error(WORLD_LOCKED, 0); result = VAL_ABORTED; goto err_close; } } /* TEST 5: Make sure the magic is awwrightttt~ */ fread(magic, 1, 3, f); v = world_magic(magic); if(v == 0) goto err_invalid; else if (v < 0x0205) { val_error(WORLD_FILE_VERSION_OLD, v); result = VAL_VERSION; goto err_close; } else if (v > WORLD_VERSION) { val_error(WORLD_FILE_VERSION_TOO_RECENT, v); result = VAL_VERSION; goto err_close; } /* TEST 6: Attempt to eliminate invalid files by * testing the palette for impossible values. */ fseek(f, WORLD_GLOBAL_OFFSET_OFFSET - 48, SEEK_SET); for(i = 0; i<48; i++) { int val = fgetc(f); if((val < 0) || (val > 63)) goto err_invalid; } /* now we should be at the global robot pointer! */ } /* TEST 7: Either branch should be at the global robot pointer now. * Test for valid SFX structure, if applicable, and board information. */ fseek(f, 4, SEEK_CUR); // Do the sfx num_boards = fgetc(f); if(num_boards == 0) { int sfx_size = fgetw(f); int sfx_off = ftell(f); for (i = 0; i < NUM_SFX; i++) { if(fseek(f, fgetc(f), SEEK_CUR)) break; } if((i != NUM_SFX) || ((ftell(f) - sfx_off) != sfx_size)) goto err_invalid; num_boards = fgetc(f); } if(num_boards == 0) goto err_invalid; board_name_offset = ftell(f); //Make sure board name and pointer data exists if( fseek(f, num_boards * BOARD_NAME_SIZE, SEEK_CUR) || fseek(f, num_boards * 8, SEEK_CUR) || ((ftell(f) - board_name_offset) != num_boards * (BOARD_NAME_SIZE + 8))) goto err_invalid; /* If any of the pointers are less than this pos we probably * aren't dealing with a valid world, but it's not our job * to figure that out right now, so we'll pass it back. */ if(end_of_global_offset) *end_of_global_offset = ftell(f); //todo: maybe have a complete fail when N number of pointers fail? goto err_close; err_invalid: result = VAL_INVALID; if(savegame) val_error(SAVE_FILE_INVALID, 0); else val_error(WORLD_FILE_INVALID, 0); err_close: fclose(f); err_out: return result; }
int save_world(struct world *mzx_world, const char *file, int savegame) { int i, num_boards; int gl_rob_position, gl_rob_save_position; int board_offsets_position, board_begin_position, board_end_position; int board_size; unsigned int *size_offset_list; unsigned char *charset_mem; unsigned char r, g, b; struct board *cur_board; FILE *fp; int meter_target = 2 + mzx_world->num_boards, meter_curr = 0; #ifdef CONFIG_DEBYTECODE if(!savegame) { fp = fopen(file, "rb"); if(fp) { if(!fseek(fp, 0x1A, SEEK_SET)) { char tmp[3]; if(fread(tmp, 1, 3, fp) == 3) { // If it's not a 2.90 world, abort the save if(world_magic(tmp) < 0x025A) { error("Save would overwrite older world. Aborted.", 0, 1, 1337); goto exit_close; } } } fclose(fp); } } #endif fp = fopen(file, "wb"); if(!fp) { error("Error saving world", 1, 8, 0x0D01); return -1; } meter_initial_draw(meter_curr, meter_target, "Saving..."); if(savegame) { // Write this MZX's version string fputs("MZS", fp); fputc((WORLD_VERSION >> 8) & 0xff, fp); fputc(WORLD_VERSION & 0xff, fp); // Write the version of the loaded world for this SAV fputw(mzx_world->version, fp); fputc(mzx_world->current_board_id, fp); } else {