diff --git a/src/game.c b/src/game.c index e3b6682b0e..d30caa3185 100644 --- a/src/game.c +++ b/src/game.c @@ -619,9 +619,13 @@ int game_load_save(const char *path) FILE *file; int i, j; + log_verbose("loading saved game, %s", path); + strcpy((char*)0x0141EF68, path); file = fopen(path, "rb"); if (file == NULL) { + log_error("unable to open %s", path); + RCT2_GLOBAL(0x009AC31B, uint8) = 255; RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_STRING_ID, uint16) = STR_FILE_CONTAINS_INVALID_DATA; return 0; @@ -629,6 +633,9 @@ int game_load_save(const char *path) if (!sawyercoding_validate_checksum(file)) { fclose(file); + + log_error("invalid checksum, %s", path); + RCT2_GLOBAL(0x009AC31B, uint8) = 255; RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_STRING_ID, uint16) = STR_FILE_CONTAINS_INVALID_DATA; return 0; diff --git a/src/object.h b/src/object.h index 1df0fd3f2a..e33e85f331 100644 --- a/src/object.h +++ b/src/object.h @@ -61,5 +61,6 @@ int object_get_length(rct_object_entry *entry); rct_object_entry *object_get_next(rct_object_entry *entry); int object_calculate_checksum(rct_object_entry *entry, char *data, int dataLength); int object_paint(int type, int eax, int ebx, int ecx, int edx, int esi, int edi, int ebp); +int sub_6A9F42(FILE *file, rct_object_entry* entry); #endif diff --git a/src/scenario.c b/src/scenario.c index 7a2066bb28..72e6e49c27 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -33,6 +33,7 @@ #include "ride/ride.h" #include "scenario.h" #include "util/sawyercoding.h" +#include "util/util.h" #include "world/map.h" #include "world/park.h" #include "world/sprite.h" @@ -740,6 +741,87 @@ int scenario_prepare_for_save() return 1; } +/** + * + * rct2: 0x006AA244 + */ +int scenario_get_num_packed_objects_to_write() +{ + int i, count = 0; + rct_object_entry_extended *entry = (rct_object_entry_extended*)0x00F3F03C; + + for (i = 0; i < 721; i++, entry++) { + if (RCT2_ADDRESS(0x009ACFA4, uint32)[i] == 0xFFFFFFFF || (entry->flags & 0xF0)) + continue; + + count++; + } + + return count; +} + +/** + * + * rct2: 0x006AA26E + */ +int scenario_write_packed_objects(FILE *file) +{ + int i; + rct_object_entry_extended *entry = (rct_object_entry_extended*)0x00F3F03C; + for (i = 0; i < 721; i++, entry++) { + if (RCT2_ADDRESS(0x009ACFA4, uint32)[i] == 0xFFFFFFFF || (entry->flags & 0xF0)) + continue; + + if (!sub_6A9F42(file, (rct_object_entry*)entry)) + return 0; + } + + return 1; +} + +/** + * + * rct2: 0x006AA039 + */ +int scenario_write_available_objects(FILE *file) +{ + char *buffer, *dstBuffer; + int i, encodedLength; + sawyercoding_chunk_header chunkHeader; + + const int totalEntries = 721; + const int bufferLength = totalEntries * sizeof(rct_object_entry); + + // Initialise buffers + buffer = malloc(bufferLength); + dstBuffer = malloc(bufferLength + sizeof(sawyercoding_chunk_header)); + if (buffer == NULL || dstBuffer == NULL) + return 0; + + // Write entries + rct_object_entry_extended *srcEntry = (rct_object_entry_extended*)0x00F3F03C; + rct_object_entry *dstEntry = (rct_object_entry*)buffer; + for (i = 0; i < 721; i++) { + if (RCT2_ADDRESS(0x009ACFA4, uint32)[i] == 0xFFFFFFFF) + memset(dstEntry, 0xFF, sizeof(rct_object_entry)); + else + *dstEntry = *((rct_object_entry*)srcEntry); + + srcEntry++; + dstEntry++; + } + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_ROTATE; + chunkHeader.length = bufferLength; + encodedLength = sawyercoding_write_chunk_buffer(dstBuffer, buffer, chunkHeader); + fwrite(dstBuffer, encodedLength, 1, file); + + // Free buffers + free(dstBuffer); + free(buffer); +} + /** * * rct2: 0x006754F5 @@ -747,11 +829,193 @@ int scenario_prepare_for_save() */ int scenario_save(char *path, int flags) { + rct_s6_header *s6Header = (rct_s6_header*)0x009E34E4; + rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; + + FILE *file; + char *buffer; + sawyercoding_chunk_header chunkHeader; + int encodedLength; + long fileSize; + uint32 checksum; + + rct_window *w; + rct_viewport *viewport; + int viewX, viewY, viewZoom, viewRotation; + if (flags & 2) log_verbose("saving scenario, %s", path); else log_verbose("saving game, %s", path); - strcpy((char*)0x0141EF68, path); - return !(RCT2_CALLPROC_X(0x006754F5, flags, 0, 0, 0, 0, 0, 0) & 0x100); + + if (!(flags & 0x80000000)) + window_close_construction_windows(); + + RCT2_CALLPROC_EBPSAFE(0x0068B111); + RCT2_CALLPROC_EBPSAFE(0x0069EBE4); + RCT2_CALLPROC_EBPSAFE(0x0069EBA4); + RCT2_CALLPROC_EBPSAFE(0x00677552); + RCT2_CALLPROC_EBPSAFE(0x00674BCF); + + // Set saved view + w = window_get_main(); + if (w != NULL) { + viewport = w->viewport; + + viewX = viewport->view_width / 2 + viewport->view_x; + viewY = viewport->view_height / 2 + viewport->view_y; + viewZoom = viewport->zoom; + viewRotation = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_ROTATION, uint8); + } else { + viewX = 0; + viewY = 0; + viewZoom = 0; + viewRotation = 0; + } + + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_X, uint16) = viewX; + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_Y, uint16) = viewY; + RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, uint16) = viewZoom | (viewRotation << 8); + + // + memset(s6Header, 0, sizeof(rct_s6_header)); + s6Header->type = flags & 2 ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME; + s6Header->num_packed_objects = flags & 1 ? scenario_get_num_packed_objects_to_write() : 0; + s6Header->version = S6_RCT2_VERSION; + s6Header->magic_number = S6_MAGIC_NUMBER; + + file = fopen(path, "wb"); + if (file == NULL) { + log_error("Unable to write to %s", path); + return 0; + } + + buffer = malloc(0x600000); + if (buffer == NULL) { + log_error("Unable to allocate enough space for a write buffer."); + fclose(file); + return 0; + } + + // Write header chunk + chunkHeader.encoding = CHUNK_ENCODING_ROTATE; + chunkHeader.length = sizeof(rct_s6_header); + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)s6Header, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write scenario info chunk + if (flags & 2) { + chunkHeader.encoding = CHUNK_ENCODING_ROTATE; + chunkHeader.length = sizeof(rct_s6_info); + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)s6Info, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + } + + // Write packed objects + if (s6Header->num_packed_objects > 0) { + if (!scenario_write_packed_objects(file)) { + free(buffer); + fclose(file); + return 0; + } + } + + // Write available objects chunk + scenario_write_available_objects(file); + + // Write date etc. chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 16; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x00F663A8, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write map elements + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x4A85EC; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x00F663B8, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + if (flags & 2) { + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x27104C; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x010E63B8, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 4; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x01357844, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 8; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x01357BC8, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 2; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x01357CB0, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 1082; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x01357CF2, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 16; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x0135832C, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 4; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x0135853C, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x761E8; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x01358740, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + } else { + // Write chunk + chunkHeader.encoding = CHUNK_ENCODING_RLECOMPRESSED; + chunkHeader.length = 0x2E8570; + encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)0x010E63B8, chunkHeader); + fwrite(buffer, encodedLength, 1, file); + } + + free(buffer); + + // Due to the buffered writing and the writing of packed objects, there is no single buffer to calculate the checksum. + // Therefore the file is closed, opened, closed and opened again to save the checksum. Inefficient, but will do until + // the chunk writing code is better. + fclose(file); + + file = fopen(path, "rb"); + fileSize = fsize(file); + buffer = malloc(fileSize); + fread(buffer, fileSize, 1, file); + fclose(file); + + checksum = sawyercoding_calculate_checksum(buffer, fileSize); + + fopen(path, "wb"); + fwrite(buffer, fileSize, 1, file); + fwrite(&checksum, sizeof(int), 1, file); + fclose(file); + + if (!(flags & 0x80000000)) + RCT2_CALLPROC_EBPSAFE(0x006A9FC0); + + gfx_invalidate_screen(); + RCT2_GLOBAL(0x009DEA66, uint16) = 0; + return 1; } \ No newline at end of file diff --git a/src/scenario.h b/src/scenario.h index b3b3a7ac54..dd877ad453 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -372,6 +372,9 @@ enum { S6_TYPE_SCENARIO }; +#define S6_RCT2_VERSION 120001 +#define S6_MAGIC_NUMBER 0x00031144 + enum { SCENARIO_CATEGORY_BEGINNER, SCENARIO_CATEGORY_CHALLENGING, diff --git a/src/util/sawyercoding.c b/src/util/sawyercoding.c index d21ff1f51c..dd94a64786 100644 --- a/src/util/sawyercoding.c +++ b/src/util/sawyercoding.c @@ -30,15 +30,11 @@ static void decode_chunk_rotate(char *buffer, int length); int encode_chunk_rle(char *src_buffer, char *dst_buffer, int length); void encode_chunk_rotate(char *buffer, int length); -int sawyercoding_calculate_checksum(uint8* buffer, uint32 length){ - int checksum = 0; - do { - int bufferSize = min(length , 1024); - - for (int i = 0; i < bufferSize; i++) - checksum += buffer[i]; - length -= bufferSize; - } while (length != 0); +uint32 sawyercoding_calculate_checksum(uint8* buffer, uint32 length) +{ + uint32 i, checksum = 0; + for (i = 0; i < length; i++) + checksum += buffer[i]; return checksum; } @@ -230,10 +226,9 @@ int sawyercoding_write_chunk_buffer(uint8 *dst_file, uint8* buffer, sawyercoding free(encode_buffer); break; case CHUNK_ENCODING_RLECOMPRESSED: - RCT2_ERROR("This has not been implemented"); - return -1; - //chunkHeader.length = decode_chunk_rle(src_buffer, buffer, chunkHeader.length); - //chunkHeader.length = decode_chunk_repeat(buffer, chunkHeader.length); + log_warning("RLECOMPRESSED encoding has not been implemented, using CHUNK_ENCODING_RLE instead."); + chunkHeader.encoding = CHUNK_ENCODING_RLE; + return sawyercoding_write_chunk_buffer(dst_file, buffer, chunkHeader); break; case CHUNK_ENCODING_ROTATE: encode_chunk_rotate(buffer, chunkHeader.length); diff --git a/src/util/sawyercoding.h b/src/util/sawyercoding.h index 19fe468bd5..2845e7368f 100644 --- a/src/util/sawyercoding.h +++ b/src/util/sawyercoding.h @@ -37,7 +37,7 @@ enum { }; int sawyercoding_validate_checksum(FILE *file); -int sawyercoding_calculate_checksum(uint8* buffer, uint32 length); +uint32 sawyercoding_calculate_checksum(uint8* buffer, uint32 length); int sawyercoding_read_chunk(FILE *file, uint8 *buffer); int sawyercoding_write_chunk_buffer(uint8 *dst_file, uint8* buffer, sawyercoding_chunk_header chunkHeader); int sawyercoding_decode_sv4(char *src, char *dst, int length);