diff --git a/README.md b/README.md index 18a8fdc9..f0310bef 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ so players of the two versions can play together. says to put the disc image next to the executable, and stops. 3. The game extracts the `maps/` folder. Then the game starts. +The `maps/` folder holds the Xbox maps only. If it holds Halo PC's maps +instead (a Halo PC or Custom Edition install's `maps` folder), the game says +so and stops: Custom Edition maps go in `custom_maps/`, beside `maps/`. + On Linux and Windows, the game puts `maps/` next to the executable. On Android, copy the disc image to the phone first. The app puts `maps/` in its data folder. Refer to [port/android/README.md](port/android/README.md). On diff --git a/port/linux/src/platform.h b/port/linux/src/platform.h index 707dbd72..65360cf8 100644 --- a/port/linux/src/platform.h +++ b/port/linux/src/platform.h @@ -105,6 +105,30 @@ const char *platform_custom_edition_root(void); out of an Xbox disc image into destination (sdl_platform.c), and quits if the player declines; nonzero once destination has one */ BOOL platform_offer_game_data(const char *destination); +/* what the data root's maps folder holds, which should be the Xbox maps +(sdl_platform.c tells the player when it is not, rather than the game +failing on Halo PC's ui.map) */ +enum +{ + _maps_folder_xbox, + /* its ui.map is Halo PC's (retail's, version 7, or Custom Edition's, 609) */ + _maps_folder_halo_pc, + /* it has no ui.map (or one that is not a map) */ + _maps_folder_no_ui, +}; +#define PLATFORM_MAPS_FOLDER_NAMED 4 +struct platform_maps_folder +{ + int state; + /* the data root, whole */ + char root[1024]; + long ui_version; + /* Halo PC maps in maps/ itself, which play only from custom_maps: how + many, and the first PLATFORM_MAPS_FOLDER_NAMED's names */ + long stray_pc_maps; + char stray_names[256]; +}; +void platform_maps_folder_check(struct platform_maps_folder *maps); /* asks the player, in a box of its own that waits for the answer (before the window opens, or between frames), with `count` buttons (3 at most: Android's), `default_index` Enter's and Escape's: the index of the one chosen, or -1 diff --git a/port/linux/src/sdl_platform.c b/port/linux/src/sdl_platform.c index 43acd1dd..d7f089cd 100644 --- a/port/linux/src/sdl_platform.c +++ b/port/linux/src/sdl_platform.c @@ -112,6 +112,61 @@ extern const unsigned long platform_window_icon_size; #endif #endif +#if !defined(HALO_ANDROID) && !defined(HALO_WEB) +/* the maps folder checked to hold the Xbox maps (platform_maps_folder_check). +When it holds Halo PC's instead, or no ui.map, the player is told where each +kind goes and the game quits, rather than failing to load Halo PC's ui.map +as the Xbox's (game_load). Halo PC maps in maps/ itself, which play only from +custom_maps, are warned of */ +static void data_check_maps(void) +{ + struct platform_maps_folder maps; + char message[2048]; + char where[1200]; + BOOL quiet = config_boolean("debug.hidden_window") || config_real("debug.exit_after") > 0.0 || + config_string("debug.network_test")[0]; + + platform_maps_folder_check(&maps); + snprintf(where, sizeof(where), + "Put the Xbox maps from your Halo: Combat Evolved disc image in %s/maps. Halo Custom Edition " + "maps go in a custom_maps folder beside it.", + maps.root); + switch (maps.state) + { + case _maps_folder_xbox: + if (!maps.stray_pc_maps) + return; + snprintf(message, sizeof(message), + "These maps in the maps folder are Halo PC maps, which play only from custom_maps: %s%s.\n\n%s", + maps.stray_names, maps.stray_pc_maps > PLATFORM_MAPS_FOLDER_NAMED ? " and more" : "", where); + platform_log("maps folder: %ld Halo PC maps in %s/maps (%s)", maps.stray_pc_maps, maps.root, + maps.stray_names); + if (!quiet) + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Halo", message, NULL); + return; + case _maps_folder_halo_pc: + snprintf(message, sizeof(message), + "The maps folder holds Halo PC maps, not the Xbox maps: its ui.map is from %s.\n\n%s\n\n" + "To copy the Xbox maps out of your disc image again, move this maps folder aside and " + "start the game.", + maps.ui_version == 609 ? "Halo PC (Custom Edition)" : "Halo PC", where); + break; + default: + snprintf(message, sizeof(message), + "Halo's Xbox maps were not found: %s/maps has no ui.map.\n\n%s\n\n" + "To copy the Xbox maps out of your disc image, move the maps folder aside and start the game.", + maps.root, where); + break; + } + platform_log("maps folder: %s/maps is not the Xbox maps (ui.map version %ld): quitting", maps.root, + maps.ui_version); + platform_log("%s", message); + if (!quiet) + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Halo", message, NULL); + exit(EXIT_FAILURE); +} +#endif + BOOL platform_sdl_initialize(void) { if (platform_sdl_started) @@ -159,8 +214,11 @@ BOOL platform_sdl_initialize(void) platform_sdl_started = TRUE; #ifndef HALO_ANDROID /* found (or offered to the player, platform_offer_game_data) before the - game's window opens */ + game's window opens, and checked to be the Xbox maps */ platform_data_root(); +#ifndef HALO_WEB + data_check_maps(); +#endif /* (a new version looked for meanwhile, updater_poll asking about it) */ updater_start(); #endif diff --git a/port/linux/src/xbox_files.c b/port/linux/src/xbox_files.c index 84241595..a3c31a8f 100644 --- a/port/linux/src/xbox_files.c +++ b/port/linux/src/xbox_files.c @@ -268,6 +268,118 @@ void platform_translate_path(const char *xbox_path, char *host_path, unsigned lo snprintf(host_path, host_path_size, "%s", resolved); } +/* ---------- the maps folder's maps */ + +enum +{ + /* a cache file's header: 'head' and its version */ + MAP_HEADER_SIGNATURE = 0x68656164, + /* (Halo PC's: retail's, and Custom Edition's) */ + HALO_PC_MAP_VERSION = 7, + CUSTOM_EDITION_MAP_VERSION = 609, +}; + +/* a map file's cache version, by its header; 0 if it is not a cache file */ +static long map_file_version(const char *path) +{ + unsigned char header[8]; + FILE *file = fopen(path, "rb"); + long version = 0; + + if (!file) + return 0; + if (fread(header, 1, sizeof(header), file) == sizeof(header) && + (header[0] | header[1] << 8 | header[2] << 16 | (unsigned long)header[3] << 24) == MAP_HEADER_SIGNATURE) + { + version = (long)(header[4] | header[5] << 8 | header[6] << 16 | (unsigned long)header[7] << 24); + } + fclose(file); + return version; +} + +static BOOL map_version_is_pc(long version) +{ + return version == HALO_PC_MAP_VERSION || version == CUSTOM_EDITION_MAP_VERSION; +} + +static BOOL name_ends_with(const char *name, const char *ending) +{ + size_t length = strlen(name); + size_t ending_length = strlen(ending); + size_t index; + + if (length < ending_length) + return FALSE; + for (index = 0; index < ending_length; index++) + { + if (tolower((unsigned char)name[length - ending_length + index]) != tolower((unsigned char)ending[index])) + return FALSE; + } + return TRUE; +} + +/* the Halo PC maps in the maps folder, which play only from custom_maps: +how many, and the first PLATFORM_MAPS_FOLDER_NAMED's names */ +static long pc_maps_in_maps(char *names, unsigned long names_size) +{ + char folder[1024]; + char name[256]; + long count = 0; + void *directory; + + platform_translate_path("d:\\maps", folder, sizeof(folder)); + directory = posix_directory_open(folder); + if (!directory) + return 0; + while (posix_directory_next(directory, name, sizeof(name))) + { + char path[1300]; + + if (!name_ends_with(name, ".map")) + continue; + snprintf(path, sizeof(path), "%s/%s", folder, name); + if (!map_version_is_pc(map_file_version(path))) + continue; + if (count < PLATFORM_MAPS_FOLDER_NAMED) + { + size_t length = strlen(names); + + snprintf(names + length, names_size - length, "%s%s", length ? ", " : "", name); + } + count++; + } + posix_directory_close(directory); + return count; +} + +void platform_maps_folder_check(struct platform_maps_folder *maps) +{ + const char *root = platform_data_root(); + char path[1024]; + + memset(maps, 0, sizeof(*maps)); + /* the data root as the player finds it (whole: Windows' is the + executable's folder's, or paths.data's) */ +#ifndef _WIN32 + if (root[0] != '/' && getcwd(maps->root, sizeof(maps->root))) + { + size_t length = strlen(maps->root); + + if (strcmp(root, ".")) + snprintf(maps->root + length, sizeof(maps->root) - length, "/%s", root); + } + else +#endif + { + snprintf(maps->root, sizeof(maps->root), "%s", root); + } + platform_translate_path("d:\\maps\\ui.map", path, sizeof(path)); + maps->ui_version = map_file_version(path); + maps->state = map_version_is_pc(maps->ui_version) ? _maps_folder_halo_pc : + !maps->ui_version ? _maps_folder_no_ui : _maps_folder_xbox; + maps->stray_pc_maps = pc_maps_in_maps(maps->stray_names, sizeof(maps->stray_names)); +} + /* ---------- file handles */ struct platform_file