diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt
index 07f8ecc83..841edde2a 100644
--- a/renderdoc/CMakeLists.txt
+++ b/renderdoc/CMakeLists.txt
@@ -132,6 +132,7 @@ set(sources
serialise/streamio.h
serialise/rdcfile.cpp
serialise/rdcfile.h
+ serialise/codecs/xml_codec.cpp
serialise/comp_io_tests.cpp
serialise/streamio_tests.cpp
strings/grisu2.cpp
diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj
index 80a1ca7e3..28cd8a80f 100644
--- a/renderdoc/renderdoc.vcxproj
+++ b/renderdoc/renderdoc.vcxproj
@@ -397,6 +397,7 @@
+
diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters
index 4f5ffb9dc..4ef2d4564 100644
--- a/renderdoc/renderdoc.vcxproj.filters
+++ b/renderdoc/renderdoc.vcxproj.filters
@@ -106,6 +106,9 @@
{3b8694d9-cb25-45e0-a1c2-8cc8cd03df3f}
+
+ {dc39ebda-045d-4d75-8ef9-0c927be3c7ff}
+
@@ -626,6 +629,9 @@
Common\Serialise\Container File
+
+ Common\Serialise\Codecs
+
diff --git a/renderdoc/serialise/codecs/xml_codec.cpp b/renderdoc/serialise/codecs/xml_codec.cpp
new file mode 100644
index 000000000..d518bcec0
--- /dev/null
+++ b/renderdoc/serialise/codecs/xml_codec.cpp
@@ -0,0 +1,765 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2017 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#include "common/common.h"
+#include "serialise/rdcfile.h"
+
+#include "3rdparty/miniz/miniz.h"
+#include "3rdparty/pugixml/pugixml.hpp"
+
+static const char *typeNames[] = {
+ "chunk", "struct", "array", "null", "buffer", "string",
+ "enum", "uint", "int", "float", "bool", "char",
+};
+
+template
+std::string GetBufferName(inttype i)
+{
+ return StringFormat::Fmt("%06u", (uint32_t)i);
+}
+
+struct xml_file_writer : pugi::xml_writer
+{
+ StreamWriter stream;
+
+ xml_file_writer(const char *filename) : stream(FileIO::fopen(filename, "wb"), Ownership::Stream)
+ {
+ }
+
+ void write(const void *data, size_t size) { stream.Write(data, size); }
+};
+
+// avoid &, <, and > since they throw off the ascii alignment
+static constexpr bool IsXMLPrintable(const char c)
+{
+ return (c >= ' ' && c <= '~' && c != '&' && c != '<' && c != '>');
+}
+
+static constexpr bool IsHex(const char c)
+{
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
+}
+
+static constexpr byte FromHex(const char c)
+{
+ return c >= '0' && c <= '9'
+ ? byte(c - '0')
+ : (c >= 'A' && c <= 'F' ? byte(c - 'A') + 10
+ : (c >= 'a' && c <= 'f' ? byte(c - 'a') + 10 : 0));
+}
+
+static void HexEncode(const std::vector &in, std::string &out)
+{
+ const size_t bytesPerLine = 32;
+ const size_t bytesPerGroup = 4;
+
+ const char digit[] = "0123456789ABCDEF";
+ // Reserve rough required size:
+ // - 3 characters per byte (two for hex, 1 for ascii),
+ // - 4 characters per line (3x space between hex and ascii, newline)
+ // - 1 character per group (space)
+ // - 2 characters for leading/trailing newline
+ out.reserve(in.size() * 3 + (in.size() / bytesPerLine) * 4 + (in.size() / bytesPerGroup) + 2);
+
+ // leading newline
+ out = "\n";
+
+ // accumulate ascii representation for each line
+ std::string ascii;
+
+ size_t i = 0;
+ for(byte c : in)
+ {
+ out.push_back(digit[(c & 0xf0) >> 4]);
+ out.push_back(digit[(c & 0x0f) >> 0]);
+
+ if(IsXMLPrintable((char)c))
+ ascii.push_back((char)c);
+ else
+ ascii.push_back('.');
+
+ i++;
+ if((i % bytesPerLine) == 0)
+ {
+ out += StringFormat::Fmt(" %s\n", ascii.c_str());
+ ascii.clear();
+ }
+ else if((i % bytesPerGroup) == 0)
+ {
+ out.push_back(' ');
+ }
+ }
+
+ // add remaining part of a line, if we didn't end by completing one
+ size_t lastLineLength = i % bytesPerLine;
+ if(lastLineLength > 0)
+ {
+ for(i = lastLineLength; i < bytesPerLine; i++)
+ {
+ // print 2 spaces where there would be characters
+ out.push_back(' ');
+ out.push_back(' ');
+
+ // don't print the group space the first time, since it was already printed, but after that
+ // print the group space
+ if((i % bytesPerGroup) == 0 && i > lastLineLength)
+ out.push_back(' ');
+ }
+
+ // add ascii and final newline
+ out += StringFormat::Fmt(" %s\n", ascii.c_str());
+ }
+}
+
+static void HexDecode(const char *str, const char *end, std::vector &out)
+{
+ out.reserve((end - str) / 2);
+
+ if(str[0] == '\n')
+ str++;
+
+ while(str + 1 < end)
+ {
+ if(IsHex(str[0]) && IsHex(str[1]))
+ {
+ out.push_back((FromHex(str[0]) << 4) | FromHex(str[1]));
+
+ str += 2;
+
+ // allow a space after hex, as a byte group
+ if(str[0] == ' ')
+ str++;
+
+ // if we encounter more spaces though, it indicates the end of a line.
+ }
+ else
+ {
+ // on the first non-hex char we encounter, skip to the next newline. This might do nothing if
+ // the char itself was a newline.
+ while(str[0] != '\n' && str < end)
+ str++;
+
+ // the loop above terminates in two ways - when it encounters a newline or when it reaches the
+ // end of the string. We can just assume we encountered a newline and increment past it - if
+ // we're already past the end, going further past will still make us fail the loop condition
+ // and terminate.
+ str++;
+ }
+ }
+}
+
+static void Obj2XML(pugi::xml_node &parent, SDObject &child)
+{
+ pugi::xml_node obj = parent.append_child(typeNames[(uint32_t)child.type.basetype]);
+
+ obj.append_attribute("name") = child.name.c_str();
+
+ if(!child.type.name.empty())
+ obj.append_attribute("typename") = child.type.name.c_str();
+
+ if(child.type.basetype == SDBasic::UnsignedInteger ||
+ child.type.basetype == SDBasic::SignedInteger || child.type.basetype == SDBasic::Float)
+ {
+ obj.append_attribute("width") = child.type.byteSize;
+ }
+
+ if(child.type.flags & SDTypeFlags::Hidden)
+ obj.append_attribute("hidden") = true;
+
+ if(child.type.flags & SDTypeFlags::Nullable)
+ obj.append_attribute("nullable") = true;
+
+ if(child.type.flags & SDTypeFlags::NullString)
+ obj.append_attribute("nullstring") = true;
+
+ if(child.type.basetype == SDBasic::Chunk)
+ {
+ RDCFATAL("Nested chunks!");
+ }
+ else if(child.type.basetype == SDBasic::Null)
+ {
+ // redundant
+ obj.remove_attribute("nullable");
+ }
+ else if(child.type.basetype == SDBasic::Struct || child.type.basetype == SDBasic::Array)
+ {
+ if(child.type.basetype == SDBasic::Array && !child.data.children.empty())
+ obj.remove_attribute("typename");
+
+ for(size_t o = 0; o < child.data.children.size(); o++)
+ {
+ Obj2XML(obj, *child.data.children[o]);
+
+ if(child.type.basetype == SDBasic::Array)
+ obj.last_child().remove_attribute("name");
+ }
+ }
+ else if(child.type.basetype == SDBasic::Buffer)
+ {
+ obj.append_attribute("byteLength") = child.type.byteSize;
+ obj.text() = child.data.basic.u;
+ }
+ else
+ {
+ if(child.type.flags & SDTypeFlags::HasCustomString)
+ {
+ obj.append_attribute("string") = child.data.str.c_str();
+ }
+
+ switch(child.type.basetype)
+ {
+ case SDBasic::Enum:
+ case SDBasic::UnsignedInteger: obj.text() = child.data.basic.u; break;
+ case SDBasic::SignedInteger: obj.text() = child.data.basic.i; break;
+ case SDBasic::String: obj.text() = child.data.str.c_str(); break;
+ case SDBasic::Float: obj.text() = child.data.basic.d; break;
+ case SDBasic::Boolean: obj.text() = child.data.basic.b; break;
+ case SDBasic::Character:
+ {
+ char str[2] = {child.data.basic.c, '\0'};
+ obj.text().set(str);
+ break;
+ }
+ default: RDCERR("Unexpected case");
+ }
+ }
+}
+
+static ReplayStatus Structured2XML(const char *filename, const RDCFile &file, uint64_t version,
+ const StructuredChunkList &chunks)
+{
+ pugi::xml_document doc;
+
+ pugi::xml_node xRoot = doc.append_child("rdc");
+
+ {
+ pugi::xml_node xHeader = xRoot.append_child("header");
+
+ pugi::xml_node xDriver = xHeader.append_child("driver");
+ xDriver.append_attribute("id") = (uint32_t)file.GetDriver();
+ xDriver.text() = file.GetDriverName().c_str();
+
+ pugi::xml_node xIdent = xHeader.append_child("machineIdent");
+
+ xIdent.text().set(file.GetMachineIdent());
+
+ pugi::xml_node xThumbnail = xHeader.append_child("thumbnail");
+
+ const RDCThumb &th = file.GetThumbnail();
+ if(th.pixels && th.len > 0 && th.width > 0 && th.height > 0)
+ {
+ xThumbnail.append_attribute("width") = th.width;
+ xThumbnail.append_attribute("height") = th.height;
+ xThumbnail.text() = "thumb.jpg";
+ }
+ }
+
+ // write all other sections
+ for(int i = 0; i < file.NumSections(); i++)
+ {
+ const SectionProperties &props = file.GetSectionProperties(i);
+
+ if(props.type == SectionType::FrameCapture)
+ continue;
+
+ StreamReader *reader = file.ReadSection(i);
+
+ pugi::xml_node xSection = xRoot.append_child("section");
+
+ if(props.flags & SectionFlags::ASCIIStored)
+ xSection.append_attribute("ascii");
+ if(props.flags & SectionFlags::LZ4Compressed)
+ xSection.append_attribute("lz4");
+ if(props.flags & SectionFlags::ZstdCompressed)
+ xSection.append_attribute("zstd");
+
+ pugi::xml_node name = xSection.append_child("name");
+ name.text() = props.name.c_str();
+
+ pugi::xml_node secVer = xSection.append_child("version");
+ secVer.text() = props.version;
+
+ pugi::xml_node type = xSection.append_child("type");
+ type.text() = (uint32_t)props.type;
+
+ std::vector contents;
+ contents.resize((size_t)reader->GetSize());
+ reader->Read(contents.data(), reader->GetSize());
+
+ pugi::xml_node data = xSection.append_child("data");
+
+ if(props.flags & SectionFlags::ASCIIStored)
+ {
+ // insert the contents literally
+ data.text().set((char *)contents.data());
+ }
+ else
+ {
+ // encode to simple hex. Not efficient, but easy.
+ std::string hexdata;
+ hexdata.reserve(contents.size() * 2);
+ HexEncode(contents, hexdata);
+ data.text().set(hexdata.c_str());
+ }
+
+ delete reader;
+ }
+
+ pugi::xml_node xChunks = xRoot.append_child("chunks");
+
+ xChunks.append_attribute("version") = version;
+
+ for(size_t c = 0; c < chunks.size(); c++)
+ {
+ pugi::xml_node xChunk = xChunks.append_child("chunk");
+ SDChunk *chunk = chunks[c];
+
+ xChunk.append_attribute("id") = chunk->metadata.chunkID;
+ xChunk.append_attribute("name") = chunk->name.c_str();
+ xChunk.append_attribute("length") = chunk->metadata.length;
+ if(chunk->metadata.threadID)
+ xChunk.append_attribute("threadID") = chunk->metadata.threadID;
+ if(chunk->metadata.timestampMicro)
+ xChunk.append_attribute("timestamp") = chunk->metadata.timestampMicro;
+ if(chunk->metadata.durationMicro)
+ xChunk.append_attribute("duration") = chunk->metadata.durationMicro;
+ if(!chunk->metadata.callstack.empty())
+ {
+ pugi::xml_node stack = xChunk.append_child("callstack");
+
+ for(size_t i = 0; i < chunk->metadata.callstack.size(); i++)
+ {
+ stack.append_child("address").text() = chunk->metadata.callstack[i];
+ }
+ }
+
+ if(chunk->metadata.flags & SDChunkFlags::OpaqueChunk)
+ {
+ xChunk.append_attribute("opaque") = true;
+
+ RDCASSERT(!chunk->data.children.empty());
+ pugi::xml_node opaque = xChunk.append_child("buffer");
+ opaque.append_attribute("byteLength") = chunk->data.children[0]->type.byteSize;
+ opaque.text() = chunk->data.children[0]->data.basic.u;
+ }
+ else
+ {
+ for(size_t o = 0; o < chunk->data.children.size(); o++)
+ Obj2XML(xChunk, *chunk->data.children[o]);
+ }
+ }
+
+ xml_file_writer writer(filename);
+ doc.save(writer);
+
+ return writer.stream.IsErrored() ? ReplayStatus::FileIOFailed : ReplayStatus::Succeeded;
+}
+
+static SDObject *XML2Obj(pugi::xml_node &obj)
+{
+ SDObject *ret =
+ new SDObject(obj.attribute("name").as_string(), obj.attribute("typename").as_string());
+
+ std::string name = obj.name();
+
+ for(size_t i = 0; i < ARRAY_COUNT(typeNames); i++)
+ {
+ if(name == typeNames[i])
+ {
+ ret->type.basetype = (SDBasic)i;
+ break;
+ }
+ }
+
+ if(ret->type.basetype == SDBasic::UnsignedInteger ||
+ ret->type.basetype == SDBasic::SignedInteger || ret->type.basetype == SDBasic::Float)
+ {
+ ret->type.byteSize = obj.attribute("width").as_uint();
+ }
+
+ if(obj.attribute("hidden"))
+ ret->type.flags |= SDTypeFlags::Hidden;
+
+ if(obj.attribute("nullable"))
+ ret->type.flags |= SDTypeFlags::Nullable;
+
+ if(obj.attribute("typename"))
+ ret->type.name = obj.attribute("typename").as_string();
+
+ ret->name = obj.attribute("name").as_string();
+
+ if(ret->type.basetype == SDBasic::Chunk)
+ {
+ RDCFATAL("Nested chunks!");
+ }
+ else if(ret->type.basetype == SDBasic::Null)
+ {
+ ret->type.flags |= SDTypeFlags::Nullable;
+ }
+ else if(ret->type.basetype == SDBasic::Struct || ret->type.basetype == SDBasic::Array)
+ {
+ for(pugi::xml_node child = obj.first_child(); child; child = child.next_sibling())
+ {
+ ret->data.children.push_back(XML2Obj(child));
+
+ if(ret->type.basetype == SDBasic::Array)
+ ret->data.children.back()->name = "$el";
+ }
+
+ if(ret->type.basetype == SDBasic::Array && !ret->data.children.empty())
+ ret->type.name = ret->data.children.back()->type.name;
+ }
+ else if(ret->type.basetype == SDBasic::Buffer)
+ {
+ ret->type.byteSize = obj.attribute("byteLength").as_ullong();
+ ret->data.basic.u = obj.text().as_uint();
+ }
+ else
+ {
+ if(obj.attribute("string"))
+ {
+ ret->type.flags |= SDTypeFlags::HasCustomString;
+ ret->data.str = obj.attribute("string").as_string();
+ }
+
+ if(obj.attribute("nullstring"))
+ ret->type.flags |= SDTypeFlags::NullString;
+
+ switch(ret->type.basetype)
+ {
+ case SDBasic::Enum:
+ case SDBasic::UnsignedInteger: ret->data.basic.u = obj.text().as_ullong(); break;
+ case SDBasic::SignedInteger: ret->data.basic.i = obj.text().as_llong(); break;
+ case SDBasic::String: ret->data.str = obj.text().as_string(); break;
+ case SDBasic::Float: ret->data.basic.d = obj.text().as_double(); break;
+ case SDBasic::Boolean: ret->data.basic.b = obj.text().as_bool(); break;
+ case SDBasic::Character: ret->data.basic.c = obj.text().as_string()[0]; break;
+ default: RDCERR("Unexpected case");
+ }
+ }
+
+ return ret;
+}
+
+static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList &buffers,
+ RDCFile *rdc, uint64_t &version, StructuredChunkList &chunks)
+{
+ pugi::xml_document doc;
+ doc.load_string(xml);
+
+ pugi::xml_node root = doc.child("rdc");
+
+ if(!root)
+ {
+ RDCERR("Malformed document, expected rdc node");
+ return ReplayStatus::FileCorrupted;
+ }
+
+ pugi::xml_node xHeader = root.first_child();
+
+ if(strcmp(xHeader.name(), "header"))
+ {
+ RDCERR("Malformed document, expected header node");
+ return ReplayStatus::FileCorrupted;
+ }
+
+ // process the header and push meta-data into RDC
+ {
+ pugi::xml_node xDriver = xHeader.first_child();
+
+ if(strcmp(xDriver.name(), "driver"))
+ {
+ RDCERR("Malformed document, expected driver node");
+ return ReplayStatus::FileCorrupted;
+ }
+
+ RDCDriver driver = (RDCDriver)xDriver.attribute("id").as_uint();
+ std::string driverName = xDriver.text().as_string();
+
+ pugi::xml_node xIdent = xDriver.next_sibling();
+
+ uint64_t machineIdent = xIdent.text().as_ullong();
+
+ pugi::xml_node xThumbnail = xIdent.next_sibling();
+
+ if(strcmp(xThumbnail.name(), "thumbnail"))
+ {
+ RDCERR("Malformed document, expected driver node");
+ return ReplayStatus::FileCorrupted;
+ }
+
+ RDCThumb th;
+ th.width = (uint16_t)xThumbnail.attribute("width").as_uint();
+ th.height = (uint16_t)xThumbnail.attribute("height").as_uint();
+
+ RDCThumb *thumb = NULL;
+
+ if(th.width > 0 && th.height > 0 && !buffers.empty())
+ {
+ th.pixels = buffers.back()->data();
+ th.len = (uint32_t)buffers.back()->size();
+ thumb = &th;
+ }
+
+ rdc->SetData(driver, driverName.c_str(), machineIdent, thumb);
+ }
+
+ // push in other sections
+ pugi::xml_node xSection = xHeader.next_sibling();
+
+ while(!strcmp(xSection.name(), "section"))
+ {
+ SectionProperties props;
+
+ if(xSection.attribute("ascii"))
+ props.flags |= SectionFlags::ASCIIStored;
+ if(xSection.attribute("lz4"))
+ props.flags |= SectionFlags::LZ4Compressed;
+ if(xSection.attribute("zstd"))
+ props.flags |= SectionFlags::ZstdCompressed;
+
+ pugi::xml_node name = xSection.child("name");
+ if(!name)
+ {
+ RDCERR("Malformed section, expected name node");
+ xSection = xSection.next_sibling();
+ continue;
+ }
+ props.name = name.text().as_string();
+
+ pugi::xml_node secVer = xSection.child("version");
+ if(!secVer)
+ {
+ RDCERR("Malformed section, expected version node");
+ xSection = xSection.next_sibling();
+ continue;
+ }
+ props.version = secVer.text().as_ullong();
+
+ pugi::xml_node type = xSection.child("type");
+ if(!type)
+ {
+ RDCERR("Malformed section, expected type node");
+ xSection = xSection.next_sibling();
+ continue;
+ }
+ props.type = (SectionType)type.text().as_uint();
+
+ pugi::xml_node data = xSection.child("data");
+ if(!data)
+ {
+ RDCERR("Malformed section, expected data node");
+ xSection = xSection.next_sibling();
+ continue;
+ }
+
+ const char *str = (const char *)data.text().get();
+ size_t len = strlen(str);
+
+ StreamWriter *writer = rdc->WriteSection(props);
+
+ if(props.flags & SectionFlags::ASCIIStored)
+ {
+ writer->Write(str, len);
+ }
+ else
+ {
+ std::vector decoded;
+ HexDecode(str, str + len, decoded);
+ writer->Write(decoded.data(), decoded.size());
+ }
+
+ writer->Finish();
+ delete writer;
+
+ xSection = xSection.next_sibling();
+ }
+
+ pugi::xml_node xChunks = xSection;
+
+ if(strcmp(xSection.name(), "chunks"))
+ {
+ RDCERR("Malformed document, expected chunks node");
+ return ReplayStatus::FileCorrupted;
+ }
+
+ if(!xChunks.attribute("version"))
+ {
+ RDCERR("Malformed document, expected version attribute");
+ return ReplayStatus::FileCorrupted;
+ }
+
+ version = xChunks.attribute("version").as_ullong();
+
+ for(pugi::xml_node xChunk = xChunks.first_child(); xChunk; xChunk = xChunk.next_sibling())
+ {
+ if(strcmp(xChunk.name(), "chunk"))
+ return ReplayStatus::FileCorrupted;
+
+ SDChunk *chunk = new SDChunk(xChunk.attribute("name").as_string());
+
+ chunk->metadata.chunkID = xChunk.attribute("id").as_uint();
+ chunk->metadata.length = xChunk.attribute("length").as_uint();
+ if(xChunk.attribute("threadID"))
+ chunk->metadata.threadID = xChunk.attribute("threadID").as_uint();
+ if(xChunk.attribute("timestamp"))
+ chunk->metadata.timestampMicro = xChunk.attribute("timestamp").as_ullong();
+ if(xChunk.attribute("duration"))
+ chunk->metadata.durationMicro = xChunk.attribute("duration").as_ullong();
+
+ pugi::xml_node callstack = xChunk.child("callstack");
+ if(callstack)
+ {
+ size_t i = 0;
+ for(pugi::xml_node address = callstack.first_child(); address; address = address.next_sibling())
+ {
+ chunk->metadata.callstack.push_back(address.text().as_ullong());
+ i++;
+ }
+ }
+
+ if(xChunk.attribute("opaque"))
+ {
+ pugi::xml_node opaque = xChunk.child("buffer");
+
+ chunk->metadata.flags |= SDChunkFlags::OpaqueChunk;
+
+ chunk->data.children.push_back(new SDObject("Opaque chunk", "Byte Buffer"));
+ chunk->data.children[0]->type.basetype = SDBasic::Buffer;
+ chunk->data.children[0]->type.byteSize = opaque.attribute("byteLength").as_ullong();
+ chunk->data.children[0]->data.basic.u = opaque.text().as_ullong();
+ }
+ else
+ {
+ for(pugi::xml_node child = xChunk.first_child(); child; child = child.next_sibling())
+ chunk->data.children.push_back(XML2Obj(child));
+ }
+
+ chunks.push_back(chunk);
+ }
+
+ return ReplayStatus::Succeeded;
+}
+
+static ReplayStatus Buffers2ZIP(const std::string &filename, const RDCFile &file,
+ const StructuredBufferList &buffers)
+{
+ std::string zipFile = filename + ".zip";
+
+ mz_zip_archive zip;
+ memset(&zip, 0, sizeof(zip));
+
+ mz_bool b = mz_zip_writer_init_file(&zip, zipFile.c_str(), 0);
+
+ if(!b)
+ {
+ RDCERR("Failed to open .zip file '%s'", zipFile.c_str());
+ return ReplayStatus::FileIOFailed;
+ }
+
+ for(size_t i = 0; i < buffers.size(); i++)
+ mz_zip_writer_add_mem(&zip, GetBufferName(i).c_str(), buffers[i]->data(), buffers[i]->size(),
+ MZ_BEST_COMPRESSION);
+
+ const RDCThumb &th = file.GetThumbnail();
+ if(th.pixels && th.len > 0 && th.width > 0 && th.height > 0)
+ mz_zip_writer_add_mem(&zip, "thumb.jpg", th.pixels, th.len, MZ_BEST_COMPRESSION);
+
+ mz_zip_writer_finalize_archive(&zip);
+ mz_zip_writer_end(&zip);
+
+ return ReplayStatus::Succeeded;
+}
+
+static void ZIP2Buffers(const std::string &filename, StructuredBufferList &buffers)
+{
+ std::string zipFile = filename + ".zip";
+
+ if(!FileIO::exists(zipFile.c_str()))
+ return;
+
+ mz_zip_archive zip;
+ memset(&zip, 0, sizeof(zip));
+
+ mz_bool b = mz_zip_reader_init_file(&zip, zipFile.c_str(), 0);
+
+ if(b)
+ {
+ mz_uint numfiles = mz_zip_reader_get_num_files(&zip);
+
+ buffers.resize(numfiles);
+
+ for(mz_uint i = 0; i < numfiles; i++)
+ {
+ mz_zip_archive_file_stat zstat;
+ mz_zip_reader_file_stat(&zip, i, &zstat);
+
+ size_t sz = 0;
+
+ byte *buf = (byte *)mz_zip_reader_extract_to_heap(&zip, i, &sz, 0);
+
+ if(strcmp(zstat.m_filename, "thumb.jpg"))
+ {
+ int bufname = atoi(zstat.m_filename);
+
+ if(bufname < (int)buffers.size())
+ {
+ buffers[bufname] = new bytebuf;
+ buffers[bufname]->assign(buf, sz);
+ }
+ }
+ else
+ {
+ // we store the thumbnail last to not mess up any buffer indices before then
+ buffers.back() = new bytebuf;
+ buffers.back()->assign(buf, sz);
+ }
+ }
+ }
+
+ mz_zip_reader_end(&zip);
+}
+
+ReplayStatus importXMLZ(const char *filename, StreamReader &reader, RDCFile *rdc, SDFile &structData)
+{
+ if(filename)
+ ZIP2Buffers(filename, structData.buffers);
+
+ uint64_t len = reader.GetSize();
+ char *buf = new char[(size_t)len + 1];
+ reader.Read(buf, (size_t)len);
+ buf[len] = 0;
+
+ return XML2Structured(buf, structData.buffers, rdc, structData.version, structData.chunks);
+}
+
+ReplayStatus exportXMLZ(const char *filename, const RDCFile &rdc, const SDFile &structData)
+{
+ ReplayStatus ret = Buffers2ZIP(filename, rdc, structData.buffers);
+
+ if(ret != ReplayStatus::Succeeded)
+ return ret;
+
+ return Structured2XML(filename, rdc, structData.version, structData.chunks);
+}