diff --git a/src/object.c b/src/object.c index c86dc80c4e..ea4ea68421 100644 --- a/src/object.c +++ b/src/object.c @@ -93,16 +93,17 @@ int object_load_file(int groupIndex, const rct_object_entry *entry, int* chunkSi } SDL_RWclose(rw); - int calculatedChecksum=object_calculate_checksum(&openedEntry, chunk, *chunkSize); + int calculatedChecksum = object_calculate_checksum(&openedEntry, chunk, *chunkSize); // Calculate and check checksum - if (calculatedChecksum != openedEntry.checksum) { + if (calculatedChecksum != openedEntry.checksum && !gConfigGeneral.allow_loading_with_incorrect_checksum) { char buffer[100]; sprintf(buffer, "Object Load failed due to checksum failure: calculated checksum %d, object says %d.", calculatedChecksum, (int)openedEntry.checksum); log_error(buffer); RCT2_GLOBAL(0x00F42BD9, uint8) = 2; free(chunk); return 0; + } objectType = openedEntry.flags & 0x0F; @@ -208,9 +209,50 @@ int write_object_file(SDL_RWops *rw, rct_object_entry* entry) chunkHeader.encoding = object_entry_group_encoding[type]; chunkHeader.length = installed_entry->chunk_size; - size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry), chunk, chunkHeader); - SDL_RWwrite(rw, dst_buffer, 1, size_dst); + //Check if content of object file matches the stored checksum. If it does not, then fix it. + int calculated_checksum = object_calculate_checksum(entry, chunk, installed_entry->chunk_size); + if(entry->checksum != calculated_checksum) { + //Store the current length of the header - it's the offset at which we will write the extra bytes + int salt_offset = chunkHeader.length; + /*Allocate a new chunk 11 bytes longer. + I would just realloc the old one, but realloc can move the data, leaving dangling pointers + into the old buffer. If the chunk is only referenced in one place it would be safe to realloc + it and update that reference, but I don't know the codebase well enough to know if that's the + case, so to be on the safe side I copy it*/ + uint8* new_chunk = malloc(chunkHeader.length + 11); + memcpy(new_chunk,chunk, chunkHeader.length); + //It should be safe to update these in-place because they are local + chunkHeader.length += 11; + + /*Next work out which bits need to be flipped to make the current checksum match the one in the file + The bitwise rotation compensates for the rotation performed during the checksum calculation*/ + int bits_to_flip = entry->checksum ^ ((calculated_checksum << 25) | (calculated_checksum >> 7)); + /*Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is an XOR + of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the bits in the data + that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less than that, you wouldn't + always be able to make the checksum come out to the desired target*/ + new_chunk[salt_offset] = (bits_to_flip & 0x00000001) << 7; + new_chunk[salt_offset + 1] = ((bits_to_flip & 0x00200000) >> 14); + new_chunk[salt_offset + 2] = ((bits_to_flip & 0x000007F8) >> 3); + new_chunk[salt_offset + 3] = ((bits_to_flip & 0xFF000000) >> 24); + new_chunk[salt_offset + 4] = ((bits_to_flip & 0x00100000) >> 13); + new_chunk[salt_offset + 5] = (bits_to_flip & 0x00000004) >> 2; + new_chunk[salt_offset + 6] = 0; + new_chunk[salt_offset + 7] = ((bits_to_flip & 0x000FF000) >> 12); + new_chunk[salt_offset + 8] = (bits_to_flip & 0x00000002) >> 1; + new_chunk[salt_offset + 9] = (bits_to_flip & 0x00C00000) >> 22; + new_chunk[salt_offset + 10] = (bits_to_flip & 0x00000800) >> 11; + + //Write modified chunk data + size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry),new_chunk,chunkHeader); + free(new_chunk); + } else { + //If the checksum matches, write chunk data + size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry), chunk, chunkHeader); + } + + SDL_RWwrite(rw, dst_buffer, 1, size_dst); free(dst_buffer); return 1; } @@ -237,9 +279,14 @@ int object_load_packed(SDL_RWops* rw) } if (object_calculate_checksum(&entry, chunk, chunkSize) != entry.checksum){ - log_error("Checksum mismatch from packed object: %.8s", entry.name); - free(chunk); - return 0; + + if(gConfigGeneral.allow_loading_with_incorrect_checksum) { + log_warning("Checksum mismatch from packed object: %.8s", entry.name); + } else { + log_error("Checksum mismatch from packed object: %.8s", entry.name); + free(chunk); + return 0; + } } int type = entry.flags & 0x0F; @@ -256,6 +303,7 @@ int object_load_packed(SDL_RWops* rw) return 0; } + int entryGroupIndex = 0; for (; entryGroupIndex < object_entry_group_counts[type]; entryGroupIndex++){