/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "DebugSerializer.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptRequest.h" #include "scriptinterface/JSON.h" #include "lib/secure_crt.h" #include "lib/utf8.h" #include "ps/CStr.h" #include #include /* * The output format here is intended to be compatible with YAML, * so it is human readable and usable in diff and can also be parsed with * external tools. */ // MSVC and GCC give slightly different serializations of floats // (e.g. "1e+010" vs "1e+10"). To make the debug serialization easily comparable // across platforms, we want to convert to a canonical form. // TODO: we just do e+0xx now; ought to handle varying precisions and inf and nan etc too template std::string canonfloat(T value, int prec) { std::stringstream str; str << std::setprecision(prec) << value; std::string r = str.str(); size_t e = r.find('e'); if (e == r.npos) // no "e" return r; if (e == r.length() - 5 && r[e + 2] == '0') // e.g. "1e+010" r.erase(e + 2, 1); return r; } CDebugSerializer::CDebugSerializer(const ScriptInterface& scriptInterface, std::ostream& stream, bool includeDebugInfo) : m_ScriptInterface(scriptInterface), m_Stream(stream), m_IsDebug(includeDebugInfo), m_Indent(0) { } void CDebugSerializer::Indent(int spaces) { m_Indent += spaces; } void CDebugSerializer::Dedent(int spaces) { ENSURE(spaces <= m_Indent); m_Indent -= spaces; } #define INDENT std::string(m_Indent, ' ') void CDebugSerializer::Comment(const std::string& comment) { m_Stream << INDENT << "# " << comment << "\n"; } void CDebugSerializer::TextLine(const std::string& text) { m_Stream << INDENT << text << "\n"; } void CDebugSerializer::PutNumber(const char* name, uint8_t value) { m_Stream << INDENT << name << ": " << (int)value << "\n"; } void CDebugSerializer::PutNumber(const char* name, int8_t value) { m_Stream << INDENT << name << ": " << (int)value << "\n"; } void CDebugSerializer::PutNumber(const char* name, uint16_t value) { m_Stream << INDENT << name << ": " << value << "\n"; } void CDebugSerializer::PutNumber(const char* name, int16_t value) { m_Stream << INDENT << name << ": " << value << "\n"; } void CDebugSerializer::PutNumber(const char* name, uint32_t value) { m_Stream << INDENT << name << ": " << value << "\n"; } void CDebugSerializer::PutNumber(const char* name, int32_t value) { m_Stream << INDENT << name << ": " << value << "\n"; } void CDebugSerializer::PutNumber(const char* name, float value) { m_Stream << INDENT << name << ": " << canonfloat(value, 8) << "\n"; } void CDebugSerializer::PutNumber(const char* name, double value) { m_Stream << INDENT << name << ": " << canonfloat(value, 17) << "\n"; } void CDebugSerializer::PutNumber(const char* name, fixed value) { m_Stream << INDENT << name << ": " << value.ToString() << "\n"; } void CDebugSerializer::PutBool(const char* name, bool value) { m_Stream << INDENT << name << ": " << (value ? "true" : "false") << "\n"; } void CDebugSerializer::PutString(const char* name, const std::string& value) { std::string escaped; escaped.reserve(value.size()); for (size_t i = 0; i < value.size(); ++i) if (value[i] == '"') escaped += "\\\""; else if (value[i] == '\\') escaped += "\\\\"; else if (value[i] == '\n') escaped += "\\n"; else escaped += value[i]; m_Stream << INDENT << name << ": " << "\"" << escaped << "\"\n"; } void CDebugSerializer::PutScriptVal(const char* name, JS::MutableHandleValue value) { ScriptRequest rq(m_ScriptInterface); JS::RootedValue serialize(rq.cx); if (Script::GetProperty(rq, value, "Serialize", &serialize) && !serialize.isNullOrUndefined()) { // If the value has a Serialize property, pretty-parse that instead. // (this gives more accurate OOS reports). ScriptFunction::Call(rq, value, "Serialize", &serialize); std::string serialized_source = Script::ToString(rq, &serialize, true); m_Stream << INDENT << name << ": " << serialized_source << "\n"; } else { std::string source = Script::ToString(rq, value, true); m_Stream << INDENT << name << ": " << source << "\n"; } } void CDebugSerializer::PutRaw(const char* name, const u8* data, size_t len) { m_Stream << INDENT << name << ": (" << len << " bytes)"; char buf[4]; for (size_t i = 0; i < len; ++i) { sprintf_s(buf, ARRAY_SIZE(buf), " %02x", (unsigned int)data[i]); m_Stream << buf; } m_Stream << "\n"; } bool CDebugSerializer::IsDebug() const { return m_IsDebug; } std::ostream& CDebugSerializer::GetStream() { return m_Stream; }