struct board *load_board_allocate(FILE *fp, int savegame, int file_version, int world_version) { struct board *cur_board = cmalloc(sizeof(struct board)); int board_size, board_location, last_location; enum val_result result; board_size = fgetd(fp); // Skip deleted boards if(!board_size) { fseek(fp, 4, SEEK_CUR); goto err_out; } board_location = fgetd(fp); last_location = ftell(fp); if(fseek(fp, board_location, SEEK_SET)) { val_error(WORLD_BOARD_MISSING, board_location); goto err_out; } cur_board->world_version = world_version; result = load_board_direct(cur_board, fp, board_size, savegame, file_version); if(result != VAL_SUCCESS) create_blank_board(cur_board); fseek(fp, last_location, SEEK_SET); return cur_board; err_out: free(cur_board); return NULL; }
__editor_maybe_static int load_board_direct(struct board *cur_board, FILE *fp, int data_size, int savegame, int version) { int num_robots, num_scrolls, num_sensors, num_robots_active; int overlay_mode, size, board_width, board_height, i; int viewport_x, viewport_y, viewport_width, viewport_height; int truncated = 0; struct robot *cur_robot; struct scroll *cur_scroll; struct sensor *cur_sensor; char *test_buffer; int board_location = ftell(fp); cur_board->num_robots = 0; cur_board->num_robots_allocated = 0; cur_board->num_robots_active = 0; cur_board->num_scrolls = 0; cur_board->num_scrolls_allocated = 0; cur_board->num_sensors = 0; cur_board->num_sensors_allocated = 0; cur_board->robot_list = NULL; cur_board->robot_list_name_sorted = NULL; cur_board->sensor_list = NULL; cur_board->scroll_list = NULL; // Initialize some fields that may no longer be loaded // from the board file itself.. cur_board->last_key = '?'; cur_board->num_input = 0; cur_board->input_size = 0; cur_board->input_string[0] = 0; cur_board->player_last_dir = 0x10; cur_board->bottom_mesg[0] = 0; cur_board->b_mesg_timer = 0; cur_board->lazwall_start = 7; cur_board->b_mesg_row = 24; cur_board->b_mesg_col = -1; cur_board->scroll_x = 0; cur_board->scroll_y = 0; cur_board->locked_x = -1; cur_board->locked_y = -1; cur_board->volume = 255; cur_board->volume_inc = 0; cur_board->volume_target = 255; // board_mode, unused if(fgetc(fp) == EOF) { val_error(WORLD_BOARD_MISSING, board_location); return VAL_MISSING; } overlay_mode = fgetc(fp); if(!overlay_mode) { int overlay_width; int overlay_height; overlay_mode = fgetc(fp); overlay_width = fgetw(fp); overlay_height = fgetw(fp); size = overlay_width * overlay_height; if((size < 1) || (size > MAX_BOARD_SIZE)) goto err_invalid; cur_board->overlay = cmalloc(size); cur_board->overlay_color = cmalloc(size); if(load_RLE2_plane(cur_board->overlay, fp, size)) goto err_freeoverlay; test_buffer = cmalloc(1024); free(test_buffer); // Skip sizes if(fseek(fp, 4, SEEK_CUR) || load_RLE2_plane(cur_board->overlay_color, fp, size)) goto err_freeoverlay; test_buffer = cmalloc(1024); free(test_buffer); } else { overlay_mode = 0; // Undo that last get fseek(fp, -1, SEEK_CUR); } cur_board->overlay_mode = overlay_mode; board_width = fgetw(fp); board_height = fgetw(fp); cur_board->board_width = board_width; cur_board->board_height = board_height; size = board_width * board_height; if((size < 1) || (size > MAX_BOARD_SIZE)) goto err_freeoverlay; cur_board->level_id = cmalloc(size); cur_board->level_color = cmalloc(size); cur_board->level_param = cmalloc(size); cur_board->level_under_id = cmalloc(size); cur_board->level_under_color = cmalloc(size); cur_board->level_under_param = cmalloc(size); if(load_RLE2_plane(cur_board->level_id, fp, size)) goto err_freeboard; if(fseek(fp, 4, SEEK_CUR) || load_RLE2_plane(cur_board->level_color, fp, size)) goto err_freeboard; if(fseek(fp, 4, SEEK_CUR) || load_RLE2_plane(cur_board->level_param, fp, size)) goto err_freeboard; if(fseek(fp, 4, SEEK_CUR) || load_RLE2_plane(cur_board->level_under_id, fp, size)) goto err_freeboard; if(fseek(fp, 4, SEEK_CUR) || load_RLE2_plane(cur_board->level_under_color, fp, size)) goto err_freeboard; if(fseek(fp, 4, SEEK_CUR) || load_RLE2_plane(cur_board->level_under_param, fp, size)) goto err_freeboard; // Load board parameters if(version < 0x0253) { fread(cur_board->mod_playing, LEGACY_MOD_FILENAME_MAX, 1, fp); cur_board->mod_playing[LEGACY_MOD_FILENAME_MAX] = 0; } else { size_t len = fgetw(fp); if(len >= MAX_PATH) len = MAX_PATH - 1; fread(cur_board->mod_playing, len, 1, fp); cur_board->mod_playing[len] = 0; } viewport_x = fgetc(fp); viewport_y = fgetc(fp); viewport_width = fgetc(fp); viewport_height = fgetc(fp); if( (viewport_x < 0) || (viewport_x > 79) || (viewport_y < 0) || (viewport_y > 24) || (viewport_width < 1) || (viewport_width > 80) || (viewport_height < 1) || (viewport_height > 25)) goto err_invalid; cur_board->viewport_x = viewport_x; cur_board->viewport_y = viewport_y; cur_board->viewport_width = viewport_width; cur_board->viewport_height = viewport_height; cur_board->can_shoot = fgetc(fp); cur_board->can_bomb = fgetc(fp); cur_board->fire_burn_brown = fgetc(fp); cur_board->fire_burn_space = fgetc(fp); cur_board->fire_burn_fakes = fgetc(fp); cur_board->fire_burn_trees = fgetc(fp); cur_board->explosions_leave = fgetc(fp); cur_board->save_mode = fgetc(fp); cur_board->forest_becomes = fgetc(fp); cur_board->collect_bombs = fgetc(fp); cur_board->fire_burns = fgetc(fp); for(i = 0; i < 4; i++) { cur_board->board_dir[i] = fgetc(fp); } cur_board->restart_if_zapped = fgetc(fp); cur_board->time_limit = fgetw(fp); if(version < 0x0253) { cur_board->last_key = fgetc(fp); cur_board->num_input = fgetw(fp); cur_board->input_size = fgetc(fp); fread(cur_board->input_string, LEGACY_INPUT_STRING_MAX + 1, 1, fp); cur_board->input_string[LEGACY_INPUT_STRING_MAX] = 0; cur_board->player_last_dir = fgetc(fp); fread(cur_board->bottom_mesg, LEGACY_BOTTOM_MESG_MAX + 1, 1, fp); cur_board->bottom_mesg[LEGACY_BOTTOM_MESG_MAX] = 0; cur_board->b_mesg_timer = fgetc(fp); cur_board->lazwall_start = fgetc(fp); cur_board->b_mesg_row = fgetc(fp); cur_board->b_mesg_col = (signed char)fgetc(fp); cur_board->scroll_x = (signed short)fgetw(fp); cur_board->scroll_y = (signed short)fgetw(fp); cur_board->locked_x = (signed short)fgetw(fp); cur_board->locked_y = (signed short)fgetw(fp); } else if(savegame) { size_t len; cur_board->last_key = fgetc(fp); cur_board->num_input = fgetw(fp); cur_board->input_size = fgetw(fp); len = fgetw(fp); if(len >= ROBOT_MAX_TR) len = ROBOT_MAX_TR - 1; fread(cur_board->input_string, len, 1, fp); cur_board->input_string[len] = 0; cur_board->player_last_dir = fgetc(fp); len = fgetw(fp); if(len >= ROBOT_MAX_TR) len = ROBOT_MAX_TR - 1; fread(cur_board->bottom_mesg, len, 1, fp); cur_board->bottom_mesg[len] = 0; cur_board->b_mesg_timer = fgetc(fp); cur_board->lazwall_start = fgetc(fp); cur_board->b_mesg_row = fgetc(fp); cur_board->b_mesg_col = (signed char)fgetc(fp); cur_board->scroll_x = (signed short)fgetw(fp); cur_board->scroll_y = (signed short)fgetw(fp); cur_board->locked_x = (signed short)fgetw(fp); cur_board->locked_y = (signed short)fgetw(fp); } cur_board->player_ns_locked = fgetc(fp); cur_board->player_ew_locked = fgetc(fp); cur_board->player_attack_locked = fgetc(fp); if(version < 0x0253 || savegame) { cur_board->volume = fgetc(fp); cur_board->volume_inc = fgetc(fp); cur_board->volume_target = fgetc(fp); } /***************/ /* Load robots */ /***************/ num_robots = fgetc(fp); num_robots_active = 0; if(num_robots == EOF) truncated = 1; // EOF/crazy value check if((num_robots < 0) || (num_robots > 255) || (num_robots > size)) goto board_scan; cur_board->robot_list = ccalloc(num_robots + 1, sizeof(struct robot *)); // Also allocate for name sorted list cur_board->robot_list_name_sorted = ccalloc(num_robots, sizeof(struct robot *)); // Any null objects being placed will later be optimized out if(num_robots) { for(i = 1; i <= num_robots; i++) { // Make sure there's robots to load here int length_check = fgetw(fp); fseek(fp, -2, SEEK_CUR); if(length_check < 0) { // Send off the error and then tell validation to shut up for now val_error(WORLD_ROBOT_MISSING, ftell(fp)); set_validation_suppression(1); truncated = 1; } cur_robot = load_robot_allocate(fp, savegame, version); if(cur_robot->used) { cur_board->robot_list[i] = cur_robot; cur_board->robot_list_name_sorted[num_robots_active] = cur_robot; num_robots_active++; } else { // We don't need no null robot clear_robot(cur_robot); cur_board->robot_list[i] = NULL; } } } set_validation_suppression(-1); if(num_robots_active > 0) { if(num_robots_active != num_robots) { cur_board->robot_list_name_sorted = crealloc(cur_board->robot_list_name_sorted, sizeof(struct robot *) * num_robots_active); } qsort(cur_board->robot_list_name_sorted, num_robots_active, sizeof(struct robot *), cmp_robots); } else { free(cur_board->robot_list_name_sorted); cur_board->robot_list_name_sorted = NULL; } cur_board->num_robots = num_robots; cur_board->num_robots_allocated = num_robots; cur_board->num_robots_active = num_robots_active; /****************/ /* Load scrolls */ /****************/ num_scrolls = fgetc(fp); if(num_scrolls == EOF) truncated = 1; if((num_scrolls < 0) || (num_scrolls > 255) || (num_robots + num_scrolls > size)) goto board_scan; cur_board->scroll_list = ccalloc(num_scrolls + 1, sizeof(struct scroll *)); if(num_scrolls) { for(i = 1; i <= num_scrolls; i++) { cur_scroll = load_scroll_allocate(fp); if(cur_scroll->used) cur_board->scroll_list[i] = cur_scroll; else clear_scroll(cur_scroll); } } cur_board->num_scrolls = num_scrolls; cur_board->num_scrolls_allocated = num_scrolls; /****************/ /* Load sensors */ /****************/ num_sensors = fgetc(fp); if(num_sensors == EOF) truncated = 1; if((num_sensors < 0) || (num_sensors > 255) || (num_scrolls + num_sensors + num_robots > size)) goto board_scan; cur_board->sensor_list = ccalloc(num_sensors + 1, sizeof(struct sensor *)); if(num_sensors) { for(i = 1; i <= num_sensors; i++) { cur_sensor = load_sensor_allocate(fp); if(cur_sensor->used) cur_board->sensor_list[i] = cur_sensor; else clear_sensor(cur_sensor); } } cur_board->num_sensors = num_sensors; cur_board->num_sensors_allocated = num_sensors; board_scan: // Now do a board scan to make sure there aren't more than the data told us. { int robot_count = 0, scroll_count = 0, sensor_count = 0; char err_mesg[80] = { 0 }; for(i = 0; i < (board_width * board_height); i++) { if(cur_board->level_id[i] > 127) cur_board->level_id[i] = CUSTOM_BLOCK; if(cur_board->level_under_id[i] > 127) cur_board->level_under_id[i] = CUSTOM_FLOOR; switch(cur_board->level_id[i]) { case ROBOT: case ROBOT_PUSHABLE: { robot_count++; if(robot_count > cur_board->num_robots) { cur_board->level_id[i] = CUSTOM_BLOCK; cur_board->level_param[i] = 'R'; cur_board->level_color[i] = 0xCF; } break; } case SIGN: case SCROLL: { scroll_count++; if(scroll_count > cur_board->num_scrolls) { cur_board->level_id[i] = CUSTOM_BLOCK; cur_board->level_param[i] = 'S'; cur_board->level_color[i] = 0xCF; } } case SENSOR: { // Wait, I forgot. Nobody cares about sensors. //sensor_count++; if(sensor_count > cur_board->num_sensors) { cur_board->level_id[i] = CUSTOM_FLOOR; cur_board->level_param[i] = 'S'; cur_board->level_color[i] = 0xDF; } } } } if(robot_count > cur_board->num_robots) { snprintf(err_mesg, 80, "Board @ %Xh: found %i robots; expected %i", board_location, robot_count, cur_board->num_robots); error(err_mesg, 1, 8, 0); } if(scroll_count > cur_board->num_scrolls) { snprintf(err_mesg, 80, "Board @ %Xh: found %i scrolls/signs; expected %i", board_location, scroll_count, cur_board->num_scrolls); error(err_mesg, 1, 8, 0); } // This won't be reached but I'll leave it anyway. if(sensor_count > cur_board->num_sensors) { snprintf(err_mesg, 80, "Board @ %Xh: found %i sensors; expected %i", board_location, sensor_count, cur_board->num_sensors); error(err_mesg, 1, 8, 0); } if(err_mesg[0]) error("Any extra robots/scrolls/signs were replaced", 1, 8, 0); } if(truncated == 1) val_error(WORLD_BOARD_TRUNCATED_SAFE, board_location); return VAL_SUCCESS; err_freeboard: free(cur_board->level_id); free(cur_board->level_color); free(cur_board->level_param); free(cur_board->level_under_id); free(cur_board->level_under_color); free(cur_board->level_under_param); err_freeoverlay: if(overlay_mode) { free(cur_board->overlay); free(cur_board->overlay_color); } err_invalid: val_error(WORLD_BOARD_CORRUPT, board_location); return VAL_INVALID; }
/* 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; }