/* ** serializer.cpp ** Savegame wrapper around RapidJSON ** **--------------------------------------------------------------------------- ** Copyright 2016 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ #define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 // disable this insanity which is bound to make the code break over time. #define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 #define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 #define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseFullPrecisionFlag #include "rapidjson/rapidjson.h" #include "rapidjson/writer.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/document.h" #include "serializer.h" #include "r_data/colormaps.h" #include "r_data/r_interpolate.h" #include "r_defs.h" #include "r_state.h" #include "p_lnspec.h" #include "i_system.h" #include "w_wad.h" #include "p_terrain.h" #include "c_dispatch.h" #include "p_setup.h" #include "p_conversation.h" #include "dsectoreffect.h" #include "d_player.h" #include "r_data/r_interpolate.h" #include "g_shared/a_sharedglobal.h" #include "po_man.h" #include "v_font.h" #include "w_zip.h" #include "doomerrors.h" #include "v_text.h" #include "cmdlib.h" #include "g_levellocals.h" char nulspace[1024 * 1024 * 4]; bool save_full = false; // for testing. Should be removed afterward. int utf8_encode(int32_t codepoint, char *buffer, int *size) { if (codepoint < 0) return -1; else if (codepoint < 0x80) { buffer[0] = (char)codepoint; *size = 1; } else if (codepoint < 0x800) { buffer[0] = 0xC0 + ((codepoint & 0x7C0) >> 6); buffer[1] = 0x80 + ((codepoint & 0x03F)); *size = 2; } else if (codepoint < 0x10000) { buffer[0] = 0xE0 + ((codepoint & 0xF000) >> 12); buffer[1] = 0x80 + ((codepoint & 0x0FC0) >> 6); buffer[2] = 0x80 + ((codepoint & 0x003F)); *size = 3; } else if (codepoint <= 0x10FFFF) { buffer[0] = 0xF0 + ((codepoint & 0x1C0000) >> 18); buffer[1] = 0x80 + ((codepoint & 0x03F000) >> 12); buffer[2] = 0x80 + ((codepoint & 0x000FC0) >> 6); buffer[3] = 0x80 + ((codepoint & 0x00003F)); *size = 4; } else return -1; return 0; } int utf8_decode(const char *src, int *size) { int c = src[0] & 255; int r; *size = 1; if ((c & 0x80) == 0) { return c; } int c1 = src[1] & 255; if ((c & 0xE0) == 0xC0) { r = ((c & 0x1F) << 6) | c1; if (r >= 128) { *size = 2; return r; } return -1; } int c2 = src[2] & 255; if ((c & 0xF0) == 0xE0) { r = ((c & 0x0F) << 12) | (c1 << 6) | c2; if (r >= 2048 && (r < 55296 || r > 57343)) { *size = 3; return r; } return -1; } int c3 = src[3] & 255; if ((c & 0xF8) == 0xF0) { r = ((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3; if (r >= 65536 && r <= 1114111) { *size = 4; return r; } } return -1; } static TArray out; static const char *StringToUnicode(const char *cc, int size = -1) { int ch; const char *c = cc; int count = 0; int count1 = 0; out.Clear(); while ((ch = (*c++) & 255)) { count1++; if (ch >= 128) { if (ch < 0x800) count += 2; else count += 3; // The source cannot contain 4-byte chars. } else count++; if (count1 == size && size > 0) break; } if (count == count1) return cc; // string is pure ASCII. // we need to convert out.Resize(count + 1); out.Last() = 0; c = cc; int i = 0; while ((ch = (*c++) & 255)) { utf8_encode(ch, &out[i], &count1); i += count1; } return &out[0]; } static const char *UnicodeToString(const char *cc) { out.Resize((unsigned)strlen(cc) + 1); int ndx = 0; while (*cc != 0) { int size; int c = utf8_decode(cc, &size); if (c < 0 || c > 255) c = '?'; out[ndx++] = c; cc += size; } out[ndx] = 0; return &out[0]; } //========================================================================== // // // //========================================================================== struct FJSONObject { rapidjson::Value *mObject; rapidjson::Value::MemberIterator mIterator; int mIndex; FJSONObject(rapidjson::Value *v) { mObject = v; if (v->IsObject()) mIterator = v->MemberBegin(); else if (v->IsArray()) { mIndex = 0; } } }; //========================================================================== // // some wrapper stuff to keep the RapidJSON dependencies out of the global headers. // FSerializer should not expose any of this. // //========================================================================== struct FWriter { typedef rapidjson::Writer > Writer; typedef rapidjson::PrettyWriter > PrettyWriter; Writer *mWriter1; PrettyWriter *mWriter2; TArray mInObject; rapidjson::StringBuffer mOutString; TArray mDObjects; TMap mObjectMap; FWriter(bool pretty) { if (!pretty) { mWriter1 = new Writer(mOutString); mWriter2 = nullptr; } else { mWriter1 = nullptr; mWriter2 = new PrettyWriter(mOutString); } } ~FWriter() { if (mWriter1) delete mWriter1; if (mWriter2) delete mWriter2; } bool inObject() const { return mInObject.Size() > 0 && mInObject.Last(); } void StartObject() { if (mWriter1) mWriter1->StartObject(); else if (mWriter2) mWriter2->StartObject(); } void EndObject() { if (mWriter1) mWriter1->EndObject(); else if (mWriter2) mWriter2->EndObject(); } void StartArray() { if (mWriter1) mWriter1->StartArray(); else if (mWriter2) mWriter2->StartArray(); } void EndArray() { if (mWriter1) mWriter1->EndArray(); else if (mWriter2) mWriter2->EndArray(); } void Key(const char *k) { if (mWriter1) mWriter1->Key(k); else if (mWriter2) mWriter2->Key(k); } void Null() { if (mWriter1) mWriter1->Null(); else if (mWriter2) mWriter2->Null(); } void String(const char *k) { k = StringToUnicode(k); if (mWriter1) mWriter1->String(k); else if (mWriter2) mWriter2->String(k); } void String(const char *k, int size) { k = StringToUnicode(k, size); if (mWriter1) mWriter1->String(k); else if (mWriter2) mWriter2->String(k); } void Bool(bool k) { if (mWriter1) mWriter1->Bool(k); else if (mWriter2) mWriter2->Bool(k); } void Int(int32_t k) { if (mWriter1) mWriter1->Int(k); else if (mWriter2) mWriter2->Int(k); } void Int64(int64_t k) { if (mWriter1) mWriter1->Int64(k); else if (mWriter2) mWriter2->Int64(k); } void Uint(uint32_t k) { if (mWriter1) mWriter1->Uint(k); else if (mWriter2) mWriter2->Uint(k); } void Uint64(int64_t k) { if (mWriter1) mWriter1->Uint64(k); else if (mWriter2) mWriter2->Uint64(k); } void Double(double k) { if (mWriter1) { mWriter1->Double(k); } else if (mWriter2) { mWriter2->Double(k); } } }; //========================================================================== // // // //========================================================================== struct FReader { TArray mObjects; rapidjson::Document mDoc; TArray mDObjects; rapidjson::Value *mKeyValue = nullptr; int mPlayers[MAXPLAYERS]; bool mObjectsRead = false; FReader(const char *buffer, size_t length) { rapidjson::Document doc; mDoc.Parse(buffer, length); mObjects.Push(FJSONObject(&mDoc)); memset(mPlayers, -1, sizeof(mPlayers)); } rapidjson::Value *FindKey(const char *key) { FJSONObject &obj = mObjects.Last(); if (obj.mObject->IsObject()) { if (key == nullptr) { // we are performing an iteration of the object through GetKey. auto p = mKeyValue; mKeyValue = nullptr; return p; } else { // Find the given key by name; auto it = obj.mObject->FindMember(key); if (it == obj.mObject->MemberEnd()) return nullptr; return &it->value; } } else if (obj.mObject->IsArray() && (unsigned)obj.mIndex < obj.mObject->Size()) { return &(*obj.mObject)[obj.mIndex++]; } return nullptr; } }; //========================================================================== // // // //========================================================================== bool FSerializer::OpenWriter(bool pretty) { if (w != nullptr || r != nullptr) return false; mErrors = 0; w = new FWriter(pretty); BeginObject(nullptr); return true; } //========================================================================== // // // //========================================================================== bool FSerializer::OpenReader(const char *buffer, size_t length) { if (w != nullptr || r != nullptr) return false; mErrors = 0; r = new FReader(buffer, length); return true; } //========================================================================== // // // //========================================================================== bool FSerializer::OpenReader(FCompressedBuffer *input) { if (input->mSize <= 0 || input->mBuffer == nullptr) return false; if (w != nullptr || r != nullptr) return false; mErrors = 0; if (input->mMethod == METHOD_STORED) { r = new FReader((char*)input->mBuffer, input->mSize); } else { char *unpacked = new char[input->mSize]; input->Decompress(unpacked); r = new FReader(unpacked, input->mSize); delete[] unpacked; } return true; } //========================================================================== // // // //========================================================================== void FSerializer::Close() { if (w == nullptr && r == nullptr) return; // double close? This should skip the I_Error at the bottom. if (w != nullptr) { delete w; w = nullptr; } if (r != nullptr) { // we must explicitly delete all thinkers in the array which did not get linked into the thinker lists. // Otherwise these objects may survive a level deletion and point to incorrect data. for (auto obj : r->mDObjects) { auto think = dyn_cast(obj); if (think != nullptr) { if (think->NextThinker == nullptr || think->PrevThinker == nullptr) { think->Destroy(); } } } delete r; r = nullptr; } if (mErrors > 0) { I_Error("%d errors parsing JSON", mErrors); } } //========================================================================== // // // //========================================================================== unsigned FSerializer::ArraySize() { if (r != nullptr && r->mObjects.Last().mObject->IsArray()) { return r->mObjects.Last().mObject->Size(); } else { return 0; } } //========================================================================== // // // //========================================================================== bool FSerializer::canSkip() const { return isWriting() && w->inObject(); } //========================================================================== // // // //========================================================================== void FSerializer::WriteKey(const char *key) { if (isWriting() && w->inObject()) { assert(key != nullptr); if (key == nullptr) { I_Error("missing element name"); } w->Key(key); } } //========================================================================== // // // //========================================================================== bool FSerializer::BeginObject(const char *name) { if (isWriting()) { WriteKey(name); w->StartObject(); w->mInObject.Push(true); } else { auto val = r->FindKey(name); if (val != nullptr) { assert(val->IsObject()); if (val->IsObject()) { r->mObjects.Push(FJSONObject(val)); } else { Printf(TEXTCOLOR_RED "Object expected for '%s'\n", name); mErrors++; return false; } } else { return false; } } return true; } //========================================================================== // // // //========================================================================== void FSerializer::EndObject() { if (isWriting()) { if (w->inObject()) { w->EndObject(); w->mInObject.Pop(); } else { assert(false && "EndObject call not inside an object"); I_Error("EndObject call not inside an object"); } } else { r->mObjects.Pop(); } } //========================================================================== // // // //========================================================================== bool FSerializer::BeginArray(const char *name) { if (isWriting()) { WriteKey(name); w->StartArray(); w->mInObject.Push(false); } else { auto val = r->FindKey(name); if (val != nullptr) { assert(val->IsArray()); if (val->IsArray()) { r->mObjects.Push(FJSONObject(val)); } else { Printf(TEXTCOLOR_RED "Array expected for '%s'\n", name); mErrors++; return false; } } else { return false; } } return true; } //========================================================================== // // // //========================================================================== void FSerializer::EndArray() { if (isWriting()) { if (!w->inObject()) { w->EndArray(); w->mInObject.Pop(); } else { assert(false && "EndArray call not inside an array"); I_Error("EndArray call not inside an array"); } } else { r->mObjects.Pop(); } } //========================================================================== // // Special handler for args (because ACS specials' arg0 needs special treatment.) // //========================================================================== FSerializer &FSerializer::Args(const char *key, int *args, int *defargs, int special) { if (isWriting()) { if (w->inObject() && defargs != nullptr && !memcmp(args, defargs, 5 * sizeof(int))) { return *this; } WriteKey(key); w->StartArray(); for (int i = 0; i < 5; i++) { if (i == 0 && args[i] < 0 && P_IsACSSpecial(special)) { w->String(FName(ENamedName(-args[i])).GetChars()); } else { w->Int(args[i]); } } w->EndArray(); } else { auto val = r->FindKey(key); if (val != nullptr) { if (val->IsArray()) { unsigned int cnt = MIN(val->Size(), 5); for (unsigned int i = 0; i < cnt; i++) { const rapidjson::Value &aval = (*val)[i]; if (aval.IsInt()) { args[i] = aval.GetInt(); } else if (i == 0 && aval.IsString()) { args[i] = -FName(UnicodeToString(aval.GetString())); } else { assert(false && "Integer expected"); Printf(TEXTCOLOR_RED "Integer expected for '%s[%d]'\n", key, i); mErrors++; } } } else { assert(false && "array expected"); Printf(TEXTCOLOR_RED "array expected for '%s'\n", key); mErrors++; } } } return *this; } //========================================================================== // // Special handler for script numbers // //========================================================================== FSerializer &FSerializer::ScriptNum(const char *key, int &num) { if (isWriting()) { WriteKey(key); if (num < 0) { w->String(FName(ENamedName(-num)).GetChars()); } else { w->Int(num); } } else { auto val = r->FindKey(key); if (val != nullptr) { if (val->IsInt()) { num = val->GetInt(); } else if (val->IsString()) { num = -FName(UnicodeToString(val->GetString())); } else { assert(false && "Integer expected"); Printf(TEXTCOLOR_RED "Integer expected for '%s'\n", key); mErrors++; } } } return *this; } //========================================================================== // // // //========================================================================== FSerializer &FSerializer::Terrain(const char *key, int &terrain, int *def) { if (isWriting() && def != nullptr && terrain == *def) { return *this; } FName terr = P_GetTerrainName(terrain); Serialize(*this, key, terr, nullptr); if (isReading()) { terrain = P_FindTerrain(terr); } return *this; } //========================================================================== // // // //========================================================================== FSerializer &FSerializer::Sprite(const char *key, int32_t &spritenum, int32_t *def) { if (isWriting()) { if (w->inObject() && def != nullptr && *def == spritenum) return *this; WriteKey(key); w->String(sprites[spritenum].name, 4); } else { auto val = r->FindKey(key); if (val != nullptr) { if (val->IsString()) { uint32_t name = *reinterpret_cast(UnicodeToString(val->GetString())); for (auto hint = NumStdSprites; hint-- != 0; ) { if (sprites[hint].dwName == name) { spritenum = hint; break; } } } } } return *this; } //========================================================================== // // // //========================================================================== FSerializer &FSerializer::StringPtr(const char *key, const char *&charptr) { if (isWriting()) { WriteKey(key); if (charptr != nullptr) w->String(charptr); else w->Null(); } else { auto val = r->FindKey(key); if (val != nullptr) { if (val->IsString()) { charptr = UnicodeToString(val->GetString()); } else { charptr = nullptr; } } } return *this; } //========================================================================== // // // //========================================================================== FSerializer &FSerializer::AddString(const char *key, const char *charptr) { if (isWriting()) { WriteKey(key); w->String(charptr); } return *this; } //========================================================================== // // // //========================================================================== unsigned FSerializer::GetSize(const char *group) { if (isWriting()) return -1; // we do not know this when writing. const rapidjson::Value &val = r->mDoc[group]; if (!val.IsArray()) return -1; return val.Size(); } //========================================================================== // // gets the key pointed to by the iterator, caches its value // and returns the key string. // //========================================================================== const char *FSerializer::GetKey() { if (isWriting()) return nullptr; // we do not know this when writing. if (!r->mObjects.Last().mObject->IsObject()) return nullptr; // non-objects do not have keys. auto &it = r->mObjects.Last().mIterator; if (it == r->mObjects.Last().mObject->MemberEnd()) return nullptr; r->mKeyValue = &it->value; return (it++)->name.GetString(); } //========================================================================== // // Writes out all collected objects // //========================================================================== void FSerializer::WriteObjects() { if (isWriting() && w->mDObjects.Size()) { BeginArray("objects"); // we cannot use the C++11 shorthand syntax here because the array may grow while being processed. for (unsigned i = 0; i < w->mDObjects.Size(); i++) { auto obj = w->mDObjects[i]; BeginObject(nullptr); w->Key("classtype"); w->String(obj->GetClass()->TypeName.GetChars()); obj->SerializeUserVars(*this); obj->Serialize(*this); obj->CheckIfSerialized(); EndObject(); } EndArray(); } } //========================================================================== // // Writes out all collected objects // //========================================================================== void FSerializer::ReadObjects(bool hubtravel) { bool founderrors = false; if (isReading() && BeginArray("objects")) { // Do not link any thinker that's being created here. This will be done by deserializing the thinker list later. try { DThinker::bSerialOverride = true; r->mDObjects.Resize(ArraySize()); // First iteration: create all the objects but do nothing with them yet. for (unsigned i = 0; i < r->mDObjects.Size(); i++) { if (BeginObject(nullptr)) { FString clsname; // do not deserialize the class type directly so that we can print appropriate errors. int pindex = -1; Serialize(*this, "classtype", clsname, nullptr); PClass *cls = PClass::FindClass(clsname); if (cls == nullptr) { Printf(TEXTCOLOR_RED "Unknown object class '%s' in savegame\n", clsname.GetChars()); founderrors = true; r->mDObjects[i] = RUNTIME_CLASS(AActor)->CreateNew(); // make sure we got at least a valid pointer for the duration of the loading process. r->mDObjects[i]->Destroy(); // but we do not want to keep this around, so destroy it right away. } else { r->mDObjects[i] = cls->CreateNew(); } EndObject(); } } // Now that everything has been created and we can retrieve the pointers we can deserialize it. r->mObjectsRead = true; if (!founderrors) { // Reset to start; r->mObjects.Last().mIndex = 0; for (unsigned i = 0; i < r->mDObjects.Size(); i++) { auto obj = r->mDObjects[i]; if (BeginObject(nullptr)) { if (obj != nullptr) { int pindex = -1; try { obj->SerializeUserVars(*this); obj->Serialize(*this); } catch (CRecoverableError &err) { // In case something in here throws an error, let's continue and deal with it later. Printf(TEXTCOLOR_RED "'%s'\n while restoring %s\n", err.GetMessage(), obj ? obj->GetClass()->TypeName.GetChars() : "invalid object"); mErrors++; } } EndObject(); } } } EndArray(); DThinker::bSerialOverride = false; assert(!founderrors); if (founderrors) { Printf(TEXTCOLOR_RED "Failed to restore all objects in savegame\n"); mErrors++; } } catch(...) { // nuke all objects we created here. for (auto obj : r->mDObjects) { if (!(obj->ObjectFlags & OF_EuthanizeMe)) obj->Destroy(); } r->mDObjects.Clear(); // make sure this flag gets unset, even if something in here throws an error. DThinker::bSerialOverride = false; throw; } } } //========================================================================== // // // //========================================================================== const char *FSerializer::GetOutput(unsigned *len) { if (isReading()) return nullptr; WriteObjects(); EndObject(); if (len != nullptr) { *len = (unsigned)w->mOutString.GetSize(); } return w->mOutString.GetString(); } //========================================================================== // // // //========================================================================== FCompressedBuffer FSerializer::GetCompressedOutput() { if (isReading()) return{ 0,0,0,0,0,nullptr }; FCompressedBuffer buff; WriteObjects(); EndObject(); buff.mSize = (unsigned)w->mOutString.GetSize(); buff.mZipFlags = 0; buff.mCRC32 = crc32(0, (const Bytef*)w->mOutString.GetString(), buff.mSize); uint8_t *compressbuf = new uint8_t[buff.mSize+1]; z_stream stream; int err; stream.next_in = (Bytef *)w->mOutString.GetString(); stream.avail_in = buff.mSize; stream.next_out = (Bytef*)compressbuf; stream.avail_out = buff.mSize; stream.zalloc = (alloc_func)0; stream.zfree = (free_func)0; stream.opaque = (voidpf)0; // create output in zip-compatible form as required by FCompressedBuffer err = deflateInit2(&stream, 8, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY); if (err != Z_OK) { goto error; } err = deflate(&stream, Z_FINISH); if (err != Z_STREAM_END) { deflateEnd(&stream); goto error; } buff.mCompressedSize = stream.total_out; err = deflateEnd(&stream); if (err == Z_OK) { buff.mBuffer = new char[buff.mCompressedSize]; buff.mMethod = METHOD_DEFLATE; memcpy(buff.mBuffer, compressbuf, buff.mCompressedSize); delete[] compressbuf; return buff; } error: memcpy(compressbuf, w->mOutString.GetString(), buff.mSize + 1); buff.mCompressedSize = buff.mSize; buff.mMethod = METHOD_STORED; return buff; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, bool &value, bool *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->Bool(value); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsBool()); if (val->IsBool()) { value = val->GetBool(); } else { Printf(TEXTCOLOR_RED "boolean type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, int64_t &value, int64_t *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->Int64(value); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsInt64()); if (val->IsInt64()) { value = val->GetInt64(); } else { Printf(TEXTCOLOR_RED "integer type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, uint64_t &value, uint64_t *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->Uint64(value); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsUint64()); if (val->IsUint64()) { value = val->GetUint64(); } else { Printf(TEXTCOLOR_RED "integer type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, int32_t &value, int32_t *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->Int(value); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsInt()); if (val->IsInt()) { value = val->GetInt(); } else { Printf(TEXTCOLOR_RED "integer type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, uint32_t &value, uint32_t *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->Uint(value); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsUint()); if (val->IsUint()) { value = val->GetUint(); } else { Printf(TEXTCOLOR_RED "integer type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, int8_t &value, int8_t *defval) { int32_t vv = value; int32_t vvd = defval? *defval : value-1; Serialize(arc, key, vv, &vvd); value = (int8_t)vv; return arc; } FSerializer &Serialize(FSerializer &arc, const char *key, uint8_t &value, uint8_t *defval) { uint32_t vv = value; uint32_t vvd = defval ? *defval : value - 1; Serialize(arc, key, vv, &vvd); value = (uint8_t)vv; return arc; } FSerializer &Serialize(FSerializer &arc, const char *key, int16_t &value, int16_t *defval) { int32_t vv = value; int32_t vvd = defval ? *defval : value - 1; Serialize(arc, key, vv, &vvd); value = (int16_t)vv; return arc; } FSerializer &Serialize(FSerializer &arc, const char *key, uint16_t &value, uint16_t *defval) { uint32_t vv = value; uint32_t vvd = defval ? *defval : value - 1; Serialize(arc, key, vv, &vvd); value = (uint16_t)vv; return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, double &value, double *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->Double(value); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsDouble()); if (val->IsDouble()) { value = val->GetDouble(); } else { Printf(TEXTCOLOR_RED "float type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, float &value, float *defval) { double vv = value; double vvd = defval ? *defval : value - 1; Serialize(arc, key, vv, &vvd); value = (float)vv; return arc; } //========================================================================== // // // //========================================================================== template FSerializer &SerializePointer(FSerializer &arc, const char *key, T *&value, T **defval, T *base) { assert(base != nullptr); if (arc.isReading() || !arc.w->inObject() || defval == nullptr || value != *defval) { int64_t vv = value == nullptr ? -1 : value - base; Serialize(arc, key, vv, nullptr); value = vv < 0 ? nullptr : base + vv; } return arc; } template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval) { return SerializePointer(arc, key, value, defval, polyobjs); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, const FPolyObj *&value, const FPolyObj **defval) { return SerializePointer(arc, key, value, defval, polyobjs); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, side_t *&value, side_t **defval) { return SerializePointer(arc, key, value, defval, &level.sides[0]); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval) { return SerializePointer(arc, key, value, defval, &level.sectors[0]); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, const sector_t *&value, const sector_t **defval) { return SerializePointer(arc, key, value, defval, &level.sectors[0]); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, player_t *&value, player_t **defval) { return SerializePointer(arc, key, value, defval, players); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, line_t *&value, line_t **defval) { return SerializePointer(arc, key, value, defval, &level.lines[0]); } template<> FSerializer &Serialize(FSerializer &arc, const char *key, vertex_t *&value, vertex_t **defval) { return SerializePointer(arc, key, value, defval, &level.vertexes[0]); } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, FTextureID &value, FTextureID *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { if (!value.Exists()) { arc.WriteKey(key); arc.w->Null(); return arc; } if (value.isNull()) { // save 'no texture' in a more space saving way arc.WriteKey(key); arc.w->Int(0); return arc; } FTextureID chk = value; if (chk.GetIndex() >= TexMan.NumTextures()) chk.SetNull(); FTexture *pic = TexMan[chk]; const char *name; if (Wads.GetLinkedTexture(pic->SourceLump) == pic) { name = Wads.GetLumpFullName(pic->SourceLump); } else { name = pic->Name; } arc.WriteKey(key); arc.w->StartArray(); arc.w->String(name); arc.w->Int(pic->UseType); arc.w->EndArray(); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { if (val->IsArray()) { const rapidjson::Value &nameval = (*val)[0]; const rapidjson::Value &typeval = (*val)[1]; assert(nameval.IsString() && typeval.IsInt()); if (nameval.IsString() && typeval.IsInt()) { value = TexMan.GetTexture(UnicodeToString(nameval.GetString()), typeval.GetInt()); } else { Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'\n", key); value.SetNull(); arc.mErrors++; } } else if (val->IsNull()) { value.SetInvalid(); } else if (val->IsInt() && val->GetInt() == 0) { value.SetNull(); } else { assert(false && "not a texture"); Printf(TEXTCOLOR_RED "object does not represent a texture for '%s'\n", key); value.SetNull(); arc.mErrors++; } } } return arc; } //========================================================================== // // This never uses defval and instead uses 'null' as default // because object pointers cannot be safely defaulted to anything else. // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, DObject *&value, DObject ** /*defval*/, bool *retcode) { if (retcode) *retcode = true; if (arc.isWriting()) { if (value != nullptr) { int ndx; if (value == WP_NOCHANGE) { ndx = -1; } else if (value->ObjectFlags & (OF_EuthanizeMe | OF_Transient)) { return arc; } else { int *pndx = arc.w->mObjectMap.CheckKey(value); if (pndx != nullptr) { ndx = *pndx; } else { ndx = arc.w->mDObjects.Push(value); arc.w->mObjectMap[value] = ndx; } } Serialize(arc, key, ndx, nullptr); } else if (!arc.w->inObject()) { arc.w->Null(); } } else { if (!arc.r->mObjectsRead) { // If you want to read objects, you MUST call ReadObjects first, even if there's only nullptr's. assert(false && "Attempt to read object reference without calling ReadObjects first"); I_Error("Attempt to read object reference without calling ReadObjects first"); } auto val = arc.r->FindKey(key); if (val != nullptr) { if (val->IsNull()) { value = nullptr; return arc; } else if (val->IsInt()) { int index = val->GetInt(); if (index == -1) { value = WP_NOCHANGE; } else { assert(index >= 0 && index < (int)arc.r->mDObjects.Size()); if (index >= 0 && index < (int)arc.r->mDObjects.Size()) { value = arc.r->mDObjects[index]; } else { assert(false && "invalid object reference"); Printf(TEXTCOLOR_RED "Invalid object reference for '%s'\n", key); value = nullptr; arc.mErrors++; if (retcode) *retcode = false; } } return arc; } } if (!retcode) { value = nullptr; } else { *retcode = false; } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); arc.w->String(value.GetChars()); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsString()); if (val->IsString()) { value = UnicodeToString(val->GetString()); } else { Printf(TEXTCOLOR_RED "String expected for '%s'\n", key); arc.mErrors++; value = NAME_None; } } } return arc; } //========================================================================== // // // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, FDynamicColormap *&cm, FDynamicColormap **def) { if (arc.isWriting()) { if (arc.w->inObject() && def != nullptr && cm->Color == (*def)->Color && cm->Fade == (*def)->Fade && cm->Desaturate == (*def)->Desaturate) { return arc; } arc.WriteKey(key); arc.w->StartArray(); arc.w->Uint(cm->Color); arc.w->Uint(cm->Fade); arc.w->Uint(cm->Desaturate); arc.w->EndArray(); } else { auto val = arc.r->FindKey(key); if (val != nullptr) { if (val->IsArray()) { const rapidjson::Value &colorval = (*val)[0]; const rapidjson::Value &fadeval = (*val)[1]; const rapidjson::Value &desatval = (*val)[2]; if (colorval.IsUint() && fadeval.IsUint() && desatval.IsUint()) { cm = GetSpecialLights(colorval.GetUint(), fadeval.GetUint(), desatval.GetUint()); return arc; } } assert(false && "not a colormap"); Printf(TEXTCOLOR_RED "object does not represent a colormap for '%s'\n", key); cm = &NormalLight; } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || sid != *def) { arc.WriteKey(key); const char *sn = (const char*)sid; if (sn != nullptr) arc.w->String(sn); else arc.w->Null(); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsString() || val->IsNull()); if (val->IsString()) { sid = UnicodeToString(val->GetString()); } else if (val->IsNull()) { sid = 0; } else { Printf(TEXTCOLOR_RED "string type expected for '%s'\n", key); sid = 0; arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClassActor *&clst, PClassActor **def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || clst != *def) { arc.WriteKey(key); if (clst == nullptr) { arc.w->Null(); } else { arc.w->String(clst->TypeName.GetChars()); } } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsString() || val->IsNull()); if (val->IsString()) { clst = PClass::FindActor(UnicodeToString(val->GetString())); } else if (val->IsNull()) { clst = nullptr; } else { Printf(TEXTCOLOR_RED "string type expected for '%s'\n", key); clst = nullptr; arc.mErrors++; } } } return arc; } //========================================================================== // // almost, but not quite the same as the above. // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, PClass *&clst, PClass **def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || clst != *def) { arc.WriteKey(key); if (clst == nullptr) { arc.w->Null(); } else { arc.w->String(clst->TypeName.GetChars()); } } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { if (val->IsString()) { clst = PClass::FindClass(UnicodeToString(val->GetString())); } else if (val->IsNull()) { clst = nullptr; } else { Printf(TEXTCOLOR_RED "string type expected for '%s'\n", key); clst = nullptr; arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, FState *&state, FState **def, bool *retcode) { if (retcode) *retcode = false; if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || state != *def) { if (retcode) *retcode = true; arc.WriteKey(key); if (state == nullptr) { arc.w->Null(); } else { PClassActor *info = FState::StaticFindStateOwner(state); if (info != NULL) { arc.w->StartArray(); arc.w->String(info->TypeName.GetChars()); arc.w->Uint((uint32_t)(state - info->OwnedStates)); arc.w->EndArray(); } else { arc.w->Null(); } } } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { if (val->IsNull()) { if (retcode) *retcode = true; state = nullptr; } else if (val->IsArray()) { if (retcode) *retcode = true; const rapidjson::Value &cls = (*val)[0]; const rapidjson::Value &ndx = (*val)[1]; state = nullptr; assert(cls.IsString() && ndx.IsUint()); if (cls.IsString() && ndx.IsUint()) { PClassActor *clas = PClass::FindActor(UnicodeToString(cls.GetString())); if (clas && ndx.GetUint() < (unsigned)clas->NumOwnedStates) { state = clas->OwnedStates + ndx.GetUint(); } else { // this can actually happen by changing the DECORATE so treat it as a warning, not an error. state = nullptr; Printf(TEXTCOLOR_ORANGE "Invalid state '%s+%d' for '%s'\n", cls.GetString(), ndx.GetInt(), key); } } else { assert(false && "not a state"); Printf(TEXTCOLOR_RED "data does not represent a state for '%s'\n", key); arc.mErrors++; } } else if (!retcode) { assert(false && "not an array"); Printf(TEXTCOLOR_RED "array type expected for '%s'\n", key); arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, FStrifeDialogueNode *&node, FStrifeDialogueNode **def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || node != *def) { arc.WriteKey(key); if (node == nullptr) { arc.w->Null(); } else { arc.w->Uint(node->ThisNodeNum); } } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsUint() || val->IsNull()); if (val->IsNull()) { node = nullptr; } else if (val->IsUint()) { if (val->GetUint() >= StrifeDialogues.Size()) { node = nullptr; } else { node = StrifeDialogues[val->GetUint()]; } } else { Printf(TEXTCOLOR_RED "integer expected for '%s'\n", key); arc.mErrors++; node = nullptr; } } } return arc; } //========================================================================== // // // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, FString *&pstr, FString **def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || pstr != *def) { arc.WriteKey(key); if (pstr == nullptr) { arc.w->Null(); } else { arc.w->String(pstr->GetChars()); } } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsNull() || val->IsString()); if (val->IsNull()) { pstr = nullptr; } else if (val->IsString()) { pstr = AActor::mStringPropertyData.Alloc(UnicodeToString(val->GetString())); } else { Printf(TEXTCOLOR_RED "string expected for '%s'\n", key); pstr = nullptr; arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, FString &pstr, FString *def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || pstr.Compare(*def) != 0) { arc.WriteKey(key); arc.w->String(pstr.GetChars()); } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsNull() || val->IsString()); if (val->IsNull()) { pstr = ""; } else if (val->IsString()) { pstr = UnicodeToString(val->GetString()); } else { Printf(TEXTCOLOR_RED "string expected for '%s'\n", key); pstr = ""; arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, char *&pstr, char **def) { if (arc.isWriting()) { if (!arc.w->inObject() || def == nullptr || strcmp(pstr, *def)) { arc.WriteKey(key); if (pstr == nullptr) { arc.w->Null(); } else { arc.w->String(pstr); } } } else { auto val = arc.r->FindKey(key); if (val != nullptr) { assert(val->IsNull() || val->IsString()); if (val->IsNull()) { pstr = nullptr; } else if (val->IsString()) { pstr = copystring(UnicodeToString(val->GetString())); } else { Printf(TEXTCOLOR_RED "string expected for '%s'\n", key); pstr = nullptr; arc.mErrors++; } } } return arc; } //========================================================================== // // // //========================================================================== template<> FSerializer &Serialize(FSerializer &arc, const char *key, FFont *&font, FFont **def) { if (arc.isWriting()) { FName n = font->GetName(); return arc(key, n); } else { FName n; arc(key, n); font = V_GetFont(n); if (font == nullptr) { Printf(TEXTCOLOR_ORANGE "Could not load font %s\n", n.GetChars()); font = SmallFont; } return arc; } } //========================================================================== // // Handler to retrieve a numeric value of any kind. // //========================================================================== FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &value, NumericValue *defval) { if (arc.isWriting()) { if (!arc.w->inObject() || defval == nullptr || value != *defval) { arc.WriteKey(key); switch (value.type) { case NumericValue::NM_signed: arc.w->Int64(value.signedval); break; case NumericValue::NM_unsigned: arc.w->Uint64(value.unsignedval); break; case NumericValue::NM_float: arc.w->Double(value.floatval); break; default: arc.w->Null(); break; } } } else { auto val = arc.r->FindKey(key); value.signedval = 0; value.type = NumericValue::NM_invalid; if (val != nullptr) { if (val->IsUint64()) { value.unsignedval = val->GetUint64(); value.type = NumericValue::NM_unsigned; } else if (val->IsInt64()) { value.signedval = val->GetInt64(); value.type = NumericValue::NM_signed; } else if (val->IsDouble()) { value.floatval = val->GetDouble(); value.type = NumericValue::NM_float; } } } return arc; }