From a5b31dea56e9a966ec44345c08b1478a2867f4f8 Mon Sep 17 00:00:00 2001 From: ShizZy Date: Thu, 19 Sep 2013 23:28:05 -0400 Subject: [PATCH] updated to chunk_file module from ppsspp --- src/common/src/chunk_file.h | 770 +++++++++++++++++++++++++++++------- 1 file changed, 630 insertions(+), 140 deletions(-) diff --git a/src/common/src/chunk_file.h b/src/common/src/chunk_file.h index 0c333b094..c480bc8ea 100644 --- a/src/common/src/chunk_file.h +++ b/src/common/src/chunk_file.h @@ -1,7 +1,19 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. +// Copyright (C) 2003 Dolphin Project. +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official SVN repository and contact information can be found at +// http://code.google.com/p/dolphin-emu/ #ifndef _POINTERWRAP_H_ #define _POINTERWRAP_H_ @@ -17,13 +29,39 @@ #include #include -#include #include #include +#include +#include +#ifndef __SYMBIAN32__ +#if defined(IOS) || defined(MACGNUSTD) +#include +#else #include +#endif +#endif #include "common.h" #include "file_util.h" +//#include "../ext/snappy/snappy-c.h" + +#if defined(IOS) || defined(MACGNUSTD) +namespace std { + using tr1::is_pointer; +} +#endif +#ifdef __SYMBIAN32__ +namespace std { + template + struct bool_constant { + typedef bool_constant type; + static const bool value = bool_value; + }; + template const bool bool_constant::value; + template struct is_pointer : public bool_constant {}; + template struct is_pointer : public bool_constant {}; +} +#endif template struct LinkedListItem : public T @@ -31,118 +69,505 @@ struct LinkedListItem : public T LinkedListItem *next; }; +class PointerWrap; + +class PointerWrapSection +{ +public: + PointerWrapSection(PointerWrap &p, int ver, const char *title) : p_(p), ver_(ver), title_(title) { + } + ~PointerWrapSection(); + + bool operator == (const int &v) const { return ver_ == v; } + bool operator != (const int &v) const { return ver_ != v; } + bool operator <= (const int &v) const { return ver_ <= v; } + bool operator >= (const int &v) const { return ver_ >= v; } + bool operator < (const int &v) const { return ver_ < v; } + bool operator > (const int &v) const { return ver_ > v; } + + operator bool() const { + return ver_ > 0; + } + +private: + PointerWrap &p_; + int ver_; + const char *title_; +}; + // Wrapper class class PointerWrap { -public: - enum Mode + // This makes it a compile error if you forget to define DoState() on non-POD. + // Which also can be a problem, for example struct tm is non-POD on linux, for whatever reason... +#ifdef _MSC_VER + template::value, bool isPointer = std::is_pointer::value> +#else + template::value> +#endif + struct DoHelper { + static void DoArray(PointerWrap *p, T *x, int count) + { + for (int i = 0; i < count; ++i) + p->Do(x[i]); + } + + static void Do(PointerWrap *p, T &x) + { + p->DoClass(x); + } + }; + + template + struct DoHelper + { + static void DoArray(PointerWrap *p, T *x, int count) + { + p->DoVoid((void *)x, sizeof(T) * count); + } + + static void Do(PointerWrap *p, T &x) + { + p->DoVoid((void *)&x, sizeof(x)); + } + }; + +public: + enum Mode { MODE_READ = 1, // load MODE_WRITE, // save MODE_MEASURE, // calculate size MODE_VERIFY, // compare }; + enum Error { + ERROR_NONE = 0, + ERROR_WARNING = 1, + ERROR_FAILURE = 2, + }; + u8 **ptr; Mode mode; + Error error; public: - PointerWrap(u8 **ptr_, Mode mode_) : ptr(ptr_), mode(mode_) {} + PointerWrap(u8 **ptr_, Mode mode_) : ptr(ptr_), mode(mode_), error(ERROR_NONE) {} + PointerWrap(unsigned char **ptr_, int mode_) : ptr((u8**)ptr_), mode((Mode)mode_), error(ERROR_NONE) {} - void SetMode(Mode mode_) { mode = mode_; } - Mode GetMode() const { return mode; } - u8** GetPPtr() { return ptr; } + PointerWrapSection Section(const char *title, int ver) { + return Section(title, ver, ver); + } - template - void Do(std::map& x) - { - u32 count = (u32)x.size(); - Do(count); + // The returned object can be compared against the version that was loaded. + // This can be used to support versions as old as minVer. + // Version = 0 means the section was not found. + PointerWrapSection Section(const char *title, int minVer, int ver) { + char marker[16] = {0}; + int foundVersion = ver; - switch (mode) + strncpy(marker, title, sizeof(marker)); + if (!ExpectVoid(marker, sizeof(marker))) { - case MODE_READ: - for (x.clear(); count != 0; --count) + // Might be before we added name markers for safety. + if (foundVersion == 1 && ExpectVoid(&foundVersion, sizeof(foundVersion))) + DoMarker(title); + // Wasn't found, but maybe we can still load the state. + else + foundVersion = 0; + } + else + Do(foundVersion); + + if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) { + WARN_LOG(COMMON, "Savestate failure: wrong version %d found for %s", foundVersion, title); + SetError(ERROR_FAILURE); + return PointerWrapSection(*this, -1, title); + } + return PointerWrapSection(*this, foundVersion, title); + } + + void SetMode(Mode mode_) {mode = mode_;} + Mode GetMode() const {return mode;} + u8 **GetPPtr() {return ptr;} + void SetError(Error error_) + { + if (error < error_) + error = error_; + if (error > ERROR_WARNING) + mode = PointerWrap::MODE_MEASURE; + } + + bool ExpectVoid(void *data, int size) + { + switch (mode) { + case MODE_READ: if (memcmp(data, *ptr, size) != 0) return false; break; + case MODE_WRITE: memcpy(*ptr, data, size); break; + case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything + case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break; + default: break; // throw an error? + } + (*ptr) += size; + return true; + } + + void DoVoid(void *data, int size) + { + switch (mode) { + case MODE_READ: memcpy(data, *ptr, size); break; + case MODE_WRITE: memcpy(*ptr, data, size); break; + case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything + case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break; + default: break; // throw an error? + } + (*ptr) += size; + } + + template + void Do(std::map &x) + { + if (mode == MODE_READ) + { + for (auto it = x.begin(), end = x.end(); it != end; ++it) { - std::pair pair; - Do(pair.first); - Do(pair.second); - x.insert(pair); + if (it->second != NULL) + delete it->second; + } + } + T *dv = NULL; + DoMap(x, dv); + } + + template + void Do(std::map &x) + { + T dv = T(); + DoMap(x, dv); + } + + template + void DoMap(std::map &x, T &default_val) + { + unsigned int number = (unsigned int)x.size(); + Do(number); + switch (mode) { + case MODE_READ: + { + x.clear(); + while (number > 0) + { + K first = K(); + Do(first); + T second = default_val; + Do(second); + x[first] = second; + --number; + } } break; - case MODE_WRITE: case MODE_MEASURE: case MODE_VERIFY: - for (auto itr = x.begin(); itr != x.end(); ++itr) { - Do(itr->first); - Do(itr->second); + typename std::map::iterator itr = x.begin(); + while (number > 0) + { + K first = itr->first; + Do(first); + Do(itr->second); + --number; + ++itr; + } } break; } } - template - void DoContainer(T& x) + template + void Do(std::multimap &x) { - u32 size = (u32)x.size(); - Do(size); - x.resize(size); - - for (auto itr = x.begin(); itr != x.end(); ++itr) - Do(*itr); + if (mode == MODE_READ) + { + for (auto it = x.begin(), end = x.end(); it != end; ++it) + { + if (it->second != NULL) + delete it->second; + } + } + T *dv = NULL; + DoMultimap(x, dv); } - template - void Do(std::vector& x) + template + void Do(std::multimap &x) { - DoContainer(x); + T dv = T(); + DoMultimap(x, dv); } - template - void Do(std::list& x) + template + void DoMultimap(std::multimap &x, T &default_val) { - DoContainer(x); + unsigned int number = (unsigned int)x.size(); + Do(number); + switch (mode) { + case MODE_READ: + { + x.clear(); + while (number > 0) + { + K first = K(); + Do(first); + T second = default_val; + Do(second); + x.insert(std::make_pair(first, second)); + --number; + } + } + break; + case MODE_WRITE: + case MODE_MEASURE: + case MODE_VERIFY: + { + typename std::multimap::iterator itr = x.begin(); + while (number > 0) + { + Do(itr->first); + Do(itr->second); + --number; + ++itr; + } + } + break; + } } - template - void Do(std::deque& x) + // Store vectors. + template + void Do(std::vector &x) { - DoContainer(x); + T *dv = NULL; + DoVector(x, dv); } - template - void Do(std::basic_string& x) + template + void Do(std::vector &x) { - DoContainer(x); + T dv = T(); + DoVector(x, dv); } - template - void DoArray(T* x, u32 count) + + template + void DoPOD(std::vector &x) { - for (u32 i = 0; i != count; ++i) + T dv = T(); + DoVectorPOD(x, dv); + } + + template + void Do(std::vector &x, T &default_val) + { + DoVector(x, default_val); + } + + template + void DoVector(std::vector &x, T &default_val) + { + u32 vec_size = (u32)x.size(); + Do(vec_size); + x.resize(vec_size, default_val); + if (vec_size > 0) + DoArray(&x[0], vec_size); + } + + template + void DoVectorPOD(std::vector &x, T &default_val) + { + u32 vec_size = (u32)x.size(); + Do(vec_size); + x.resize(vec_size, default_val); + if (vec_size > 0) + DoArray(&x[0], vec_size); + } + + // Store deques. + template + void Do(std::deque &x) + { + T *dv = NULL; + DoDeque(x, dv); + } + + template + void Do(std::deque &x) + { + T dv = T(); + DoDeque(x, dv); + } + + template + void DoDeque(std::deque &x, T &default_val) + { + u32 deq_size = (u32)x.size(); + Do(deq_size); + x.resize(deq_size, default_val); + u32 i; + for(i = 0; i < deq_size; i++) Do(x[i]); } - template - void Do(T& x) + // Store STL lists. + template + void Do(std::list &x) { - // Ideally this would be std::is_trivially_copyable, but not enough support yet - static_assert(std::is_pod::value, "Only sane for POD types"); - - DoVoid((void*)&x, sizeof(x)); - } - - template - void DoPOD(T& x) - { - DoVoid((void*)&x, sizeof(x)); + T *dv = NULL; + Do(x, dv); } - template - void DoPointer(T*& x, T* const base) + template + void Do(std::list &x) { + T dv = T(); + DoList(x, dv); + } + + template + void Do(std::list &x, T &default_val) + { + DoList(x, default_val); + } + + template + void DoList(std::list &x, T &default_val) + { + u32 list_size = (u32)x.size(); + Do(list_size); + x.resize(list_size, default_val); + + typename std::list::iterator itr, end; + for (itr = x.begin(), end = x.end(); itr != end; ++itr) + Do(*itr); + } + + + // Store STL sets. + template + void Do(std::set &x) + { + if (mode == MODE_READ) + { + for (auto it = x.begin(), end = x.end(); it != end; ++it) + { + if (*it != NULL) + delete *it; + } + } + DoSet(x); + } + + template + void Do(std::set &x) + { + DoSet(x); + } + + template + void DoSet(std::set &x) + { + unsigned int number = (unsigned int)x.size(); + Do(number); + + switch (mode) + { + case MODE_READ: + { + x.clear(); + while (number-- > 0) + { + T it = T(); + Do(it); + x.insert(it); + } + } + break; + case MODE_WRITE: + case MODE_MEASURE: + case MODE_VERIFY: + { + typename std::set::iterator itr = x.begin(); + while (number-- > 0) + Do(*itr++); + } + break; + + default: + ERROR_LOG(COMMON, "Savestate error: invalid mode %d.", mode); + } + } + + // Store strings. + void Do(std::string &x) + { + int stringLen = (int)x.length() + 1; + Do(stringLen); + + switch (mode) { + case MODE_READ: x = (char*)*ptr; break; + case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break; + case MODE_MEASURE: break; + case MODE_VERIFY: _dbg_assert_msg_(COMMON, !strcmp(x.c_str(), (char*)*ptr), "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n", x.c_str(), (char*)*ptr, ptr); break; + } + (*ptr) += stringLen; + } + + void Do(std::wstring &x) + { + int stringLen = sizeof(wchar_t)*((int)x.length() + 1); + Do(stringLen); + + switch (mode) { + case MODE_READ: x = (wchar_t*)*ptr; break; + case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break; + case MODE_MEASURE: break; + case MODE_VERIFY: _dbg_assert_msg_(COMMON, x == (wchar_t*)*ptr, "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n", x.c_str(), (wchar_t*)*ptr, ptr); break; + } + (*ptr) += stringLen; + } + + template + void DoClass(T &x) { + x.DoState(*this); + } + + template + void DoClass(T *&x) { + if (mode == MODE_READ) + { + if (x != NULL) + delete x; + x = new T(); + } + x->DoState(*this); + } + + template + void DoArray(T *x, int count) { + DoHelper::DoArray(this, x, count); + } + + template + void Do(T &x) { + DoHelper::Do(this, x); + } + + template + void DoPOD(T &x) { + DoHelper::Do(this, x); + } + + template + void DoPointer(T* &x, T*const base) { // pointers can be more than 2^31 apart, but you're using this function wrong if you need that much range s32 offset = x - base; Do(offset); @@ -150,8 +575,7 @@ public: x = base + offset; } - // Let's pretend std::list doesn't exist! - template * (*TNew)(), void (*TFree)(LinkedListItem*), void (*TDo)(PointerWrap&, T*)> + template* (*TNew)(), void (*TFree)(LinkedListItem*), void (*TDo)(PointerWrap&, T*)> void DoLinkedList(LinkedListItem*& list_start, LinkedListItem** list_end=0) { LinkedListItem* list_cur = list_start; @@ -211,66 +635,48 @@ public: } } - void DoMarker(const char* prevName, u32 arbitraryNumber = 0x42) + void DoMarker(const char* prevName, u32 arbitraryNumber=0x42) { u32 cookie = arbitraryNumber; Do(cookie); - - if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) + if(mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) { - PanicAlertT("Error: After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", - prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); - mode = PointerWrap::MODE_MEASURE; + PanicAlertT("Error: After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); + SetError(ERROR_FAILURE); } } - -private: - __forceinline void DoByte(u8& x) - { - switch (mode) - { - case MODE_READ: - x = **ptr; - break; - - case MODE_WRITE: - **ptr = x; - break; - - case MODE_MEASURE: - break; - - case MODE_VERIFY: - _dbg_assert_msg_(COMMON, (x == **ptr), - "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", - x, x, &x, **ptr, **ptr, *ptr); - break; - - default: - break; - } - - ++(*ptr); - } - - void DoVoid(void *data, u32 size) - { - for(u32 i = 0; i != size; ++i) - DoByte(reinterpret_cast(data)[i]); - } }; +inline PointerWrapSection::~PointerWrapSection() { + if (ver_ > 0) { + p_.DoMarker(title_); + } +} + + class CChunkFileReader { public: + enum Error { + ERROR_NONE, + ERROR_BAD_FILE, + ERROR_BROKEN_STATE, + }; + // Load file template template - static bool Load(const std::string& _rFilename, u32 _Revision, T& _class) + static Error Load(const std::string& _rFilename, int _Revision, const char *_VersionString, T& _class, std::string* _failureReason) { INFO_LOG(COMMON, "ChunkReader: Loading %s" , _rFilename.c_str()); + _failureReason->clear(); + _failureReason->append("LoadStateWrongVersion"); - if (!File::Exists(_rFilename)) - return false; + if (!File::Exists(_rFilename)) { + _failureReason->clear(); + _failureReason->append("LoadStateDoesntExist"); + ERROR_LOG(COMMON, "ChunkReader: File doesn't exist"); + return ERROR_BAD_FILE; + } // Check file size const u64 fileSize = File::GetSize(_rFilename); @@ -278,14 +684,14 @@ public: if (fileSize < headerSize) { ERROR_LOG(COMMON,"ChunkReader: File too small"); - return false; + return ERROR_BAD_FILE; } File::IOFile pFile(_rFilename, "rb"); if (!pFile) { ERROR_LOG(COMMON,"ChunkReader: Can't open file for reading"); - return false; + return ERROR_BAD_FILE; } // read the header @@ -293,91 +699,175 @@ public: if (!pFile.ReadArray(&header, 1)) { ERROR_LOG(COMMON,"ChunkReader: Bad header size"); - return false; + return ERROR_BAD_FILE; } - + // Check revision if (header.Revision != _Revision) { ERROR_LOG(COMMON,"ChunkReader: Wrong file revision, got %d expected %d", header.Revision, _Revision); - return false; + return ERROR_BAD_FILE; + } + + if (strcmp(header.GitVersion, _VersionString) != 0) + { + WARN_LOG(COMMON, "This savestate was generated by a different version of PPSSPP, %s. It may not load properly.", + header.GitVersion); } // get size - const u32 sz = (u32)(fileSize - headerSize); + const int sz = (int)(fileSize - headerSize); if (header.ExpectedSize != sz) { ERROR_LOG(COMMON,"ChunkReader: Bad file size, got %d expected %d", sz, header.ExpectedSize); - return false; + return ERROR_BAD_FILE; } - + // read the state - std::vector buffer(sz); - if (!pFile.ReadArray(&buffer[0], sz)) + u8* buffer = new u8[sz]; + if (!pFile.ReadBytes(buffer, sz)) { ERROR_LOG(COMMON,"ChunkReader: Error reading file"); - return false; + return ERROR_BAD_FILE; + } + + u8 *ptr = buffer; + u8 *buf = buffer; + if (header.Compress) { + u8 *uncomp_buffer = new u8[header.UncompressedSize]; + size_t uncomp_size = header.UncompressedSize; + snappy_uncompress((const char *)buffer, sz, (char *)uncomp_buffer, &uncomp_size); + if ((int)uncomp_size != header.UncompressedSize) { + ERROR_LOG(COMMON,"Size mismatch: file: %i calc: %i", (int)header.UncompressedSize, (int)uncomp_size); + } + ptr = uncomp_buffer; + buf = uncomp_buffer; + delete [] buffer; } - u8* ptr = &buffer[0]; PointerWrap p(&ptr, PointerWrap::MODE_READ); _class.DoState(p); + delete[] buf; INFO_LOG(COMMON, "ChunkReader: Done loading %s" , _rFilename.c_str()); - return true; + if (p.error != p.ERROR_FAILURE) { + return ERROR_NONE; + } else { + return ERROR_BROKEN_STATE; + } } - + // Save file template template - static bool Save(const std::string& _rFilename, u32 _Revision, T& _class) + static Error Save(const std::string& _rFilename, int _Revision, const char *_VersionString, T& _class) { INFO_LOG(COMMON, "ChunkReader: Writing %s" , _rFilename.c_str()); + File::IOFile pFile(_rFilename, "wb"); if (!pFile) { ERROR_LOG(COMMON,"ChunkReader: Error opening file for write"); - return false; + return ERROR_BAD_FILE; } + bool compress = true; + // Get data u8 *ptr = 0; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); _class.DoState(p); size_t const sz = (size_t)ptr; - std::vector buffer(sz); + + u8 * buffer = new u8[sz]; ptr = &buffer[0]; p.SetMode(PointerWrap::MODE_WRITE); _class.DoState(p); // Create header SChunkHeader header; + header.Compress = compress ? 1 : 0; header.Revision = _Revision; - header.ExpectedSize = (u32)sz; + header.ExpectedSize = (int)sz; + header.UncompressedSize = (int)sz; + strncpy(header.GitVersion, _VersionString, 32); + header.GitVersion[31] = '\0'; // Write to file - if (!pFile.WriteArray(&header, 1)) - { - ERROR_LOG(COMMON,"ChunkReader: Failed writing header"); - return false; + if (compress) { + size_t comp_len = snappy_max_compressed_length(sz); + u8 *compressed_buffer = new u8[comp_len]; + snappy_compress((const char *)buffer, sz, (char *)compressed_buffer, &comp_len); + delete [] buffer; + header.ExpectedSize = (int)comp_len; + if (!pFile.WriteArray(&header, 1)) + { + ERROR_LOG(COMMON,"ChunkReader: Failed writing header"); + return ERROR_BAD_FILE; + } + if (!pFile.WriteBytes(&compressed_buffer[0], comp_len)) { + ERROR_LOG(COMMON,"ChunkReader: Failed writing compressed data"); + return ERROR_BAD_FILE; + } else { + INFO_LOG(COMMON, "Savestate: Compressed %i bytes into %i", (int)sz, (int)comp_len); + } + delete [] compressed_buffer; + } else { + if (!pFile.WriteArray(&header, 1)) + { + ERROR_LOG(COMMON,"ChunkReader: Failed writing header"); + return ERROR_BAD_FILE; + } + if (!pFile.WriteBytes(&buffer[0], sz)) + { + ERROR_LOG(COMMON,"ChunkReader: Failed writing data"); + return ERROR_BAD_FILE; + } + delete [] buffer; } - - if (!pFile.WriteArray(&buffer[0], sz)) - { - ERROR_LOG(COMMON,"ChunkReader: Failed writing data"); - return false; + + INFO_LOG(COMMON,"ChunkReader: Done writing %s", + _rFilename.c_str()); + if (p.error != p.ERROR_FAILURE) { + return ERROR_NONE; + } else { + return ERROR_BROKEN_STATE; } + } + + template + static Error Verify(T& _class) + { + u8 *ptr = 0; - INFO_LOG(COMMON,"ChunkReader: Done writing %s", _rFilename.c_str()); - return true; + // Step 1: Measure the space required. + PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); + _class.DoState(p); + size_t const sz = (size_t)ptr; + std::vector buffer(sz); + + // Step 2: Dump the state. + ptr = &buffer[0]; + p.SetMode(PointerWrap::MODE_WRITE); + _class.DoState(p); + + // Step 3: Verify the state. + ptr = &buffer[0]; + p.SetMode(PointerWrap::MODE_VERIFY); + _class.DoState(p); + + return ERROR_NONE; } private: struct SChunkHeader { - u32 Revision; - u32 ExpectedSize; + int Revision; + int Compress; + int ExpectedSize; + int UncompressedSize; + char GitVersion[32]; }; };