diff --git a/src/RePak.vcxproj b/src/RePak.vcxproj
index 0d301f90..e600bc7f 100644
--- a/src/RePak.vcxproj
+++ b/src/RePak.vcxproj
@@ -21,6 +21,8 @@
+
+
@@ -161,6 +163,8 @@
NotUsing
+
+
@@ -174,6 +178,7 @@
+
@@ -186,14 +191,19 @@
+
+
+
+
+
@@ -285,6 +295,7 @@
+
diff --git a/src/RePak.vcxproj.filters b/src/RePak.vcxproj.filters
index d068895d..b7b27f67 100644
--- a/src/RePak.vcxproj.filters
+++ b/src/RePak.vcxproj.filters
@@ -250,6 +250,18 @@
assets
+
+ utils
+
+
+ assets
+
+
+ utils
+
+
+ assets
+
@@ -609,6 +621,27 @@
public
+
+ utils
+
+
+ public
+
+
+ public
+
+
+ public
+
+
+ public
+
+
+ math
+
+
+ public
+
diff --git a/src/application/repak.cpp b/src/application/repak.cpp
index 61db1590..f2854a46 100644
--- a/src/application/repak.cpp
+++ b/src/application/repak.cpp
@@ -347,6 +347,7 @@ int main(int argc, char** argv)
extern bool Console_ColorInit();
Console_ColorInit();
+ g_iosmErrorCallback = Error;
g_jsonErrorCallback = Error;
RePak_HandleCommandLine(argc, argv);
diff --git a/src/assets/anim_recording.cpp b/src/assets/anim_recording.cpp
index 174b51f1..2d2a4011 100644
--- a/src/assets/anim_recording.cpp
+++ b/src/assets/anim_recording.cpp
@@ -101,7 +101,8 @@ static void AnimRecording_InternalAddAnimRecording(CPakFileBuilder* const pak, c
for (int i = 0; i < fileHdr.numElements; i++)
{
- bio.Read(pHdr->poseParamValues[i]);
+ if (!bio.Read(pHdr->poseParamValues[i]))
+ Error("Failed to read animation pose parameter value #%i\n", i);
}
for (int i = 0; i < fileHdr.numSequences; i++)
diff --git a/src/assets/animseq.cpp b/src/assets/animseq.cpp
index 37295b2f..b9c96262 100644
--- a/src/assets/animseq.cpp
+++ b/src/assets/animseq.cpp
@@ -57,7 +57,10 @@ static void AnimSeq_ParseDependenciesFromData(const uint8_t* const data, std::se
const char* end = strchr(start, ' ');
if (!end)
- end = event->options + strlen(event->options);
+ {
+ const size_t maxLen = sizeof(event->options) - (start - event->options);
+ end = start + strnlen(start, maxLen);
+ }
const size_t nameLen = (end - start);
@@ -198,15 +201,13 @@ static void AnimSeq_InternalAddAnimSeq(CPakFileBuilder* const pak, const PakGuid
hdr->settingsCount = (uint32_t)set.size();
}
- for (size_t j = 0; j < set.size(); j++)
- {
- std::set::iterator it = set.begin();
- std::advance(it, j);
+ size_t j = 0;
- const PakGuid_t guidToCopy = *it;
+ for (const PakGuid_t& guidToCopy : set)
+ {
reinterpret_cast(&dataLump.data[bufferBase])[j] = guidToCopy;
-
Pak_RegisterGuidRefAtOffset(guidToCopy, bufferBase + (j * sizeof(PakGuid_t)), dataLump, asset);
+ ++j;
}
bufferBase += set.size() * sizeof(PakGuid_t);
diff --git a/src/assets/assets.h b/src/assets/assets.h
index 093200ca..23d4cfaa 100644
--- a/src/assets/assets.h
+++ b/src/assets/assets.h
@@ -9,6 +9,7 @@
#define TXLS_VERSION 1
#define UIMG_VERSION 10
#define RLCD_VERSION 0
+#define EFCT_VERSION 2
//#define DTBL_VERSION 1
#define STLT_VERSION 0
#define STGS_VERSION 1
@@ -36,6 +37,8 @@ namespace Assets
void AddUIImageAsset_v10(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& mapEntry);
void AddLcdScreenEffect_v0(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& mapEntry);
+ void AddParticleEffect_v2(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& /*mapEntry*/);
+
void AddDataTableAsset(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& mapEntry);
void AddSettingsLayout_v0(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& mapEntry);
void AddSettingsAsset_v1(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& mapEntry);
diff --git a/src/assets/datatable.cpp b/src/assets/datatable.cpp
index 44520d56..db7ad1fb 100644
--- a/src/assets/datatable.cpp
+++ b/src/assets/datatable.cpp
@@ -20,6 +20,18 @@ static void DataTable_ReportInvalidDataTypeError(const char* const type, const u
Error("Invalid data type \"%s\" at cell [%u,%u].\n", type, rowIdx, colIdx);
}
+template
+static T DataTable_ParseCellFromDocument(const rapidcsv::Document& doc, const uint32_t colIdx, const uint32_t rowIdx, const dtblcoltype_t type)
+{
+ try {
+ return doc.GetCell(colIdx, rowIdx);
+ }
+ catch (const std::exception& ex) {
+ Error("Exception while parsing %s value from cell [%u,%u]: %s.\n", DataTable_GetStringFromType(type), rowIdx, colIdx, ex.what());
+ return T{};
+ }
+}
+
template
static size_t DataTable_SetupRows(const rapidcsv::Document& doc, datatable_t* const dtblHdr, datatable_asset_t& tmp, std::vector& outTypeRow)
{
@@ -29,7 +41,7 @@ static size_t DataTable_SetupRows(const rapidcsv::Document& doc, datatable_t* co
// typically happens when there's an empty line in the csv file.
if (numTypeNames != dtblHdr->numColumns)
- Error("Expected %u columns for type name row, found %u.\n", dtblHdr->numRows, numTypeNames);
+ Error("Expected %u columns for type name row, found %u.\n", dtblHdr->numColumns, numTypeNames);
// Make sure every row (including rows we don't end up storing in the pak),
// have the same number of columns as the type row. The column count in the
@@ -65,8 +77,8 @@ static size_t DataTable_SetupRows(const rapidcsv::Document& doc, datatable_t* co
for (uint32_t j = 0; j < dtblHdr->numRows; ++j)
{
// this can be std::string since we only deal with the string types here
- std::vector row = doc.GetRow(j);
- const size_t strLen = row[i].length();
+ const std::string cellValue = DataTable_ParseCellFromDocument(doc, i, j, type);
+ const size_t strLen = cellValue.length();
if (isPrecachedAsset && strLen > 0)
tmp.guidRefBufSize += sizeof(PakGuid_t);
@@ -116,18 +128,6 @@ static void DataTable_SetupColumns(CPakFileBuilder* const pak, PakPageLump_s& da
}
}
-template
-static T DataTable_ParseCellFromDocument(rapidcsv::Document& doc, const uint32_t colIdx, const uint32_t rowIdx, const dtblcoltype_t type)
-{
- try {
- return doc.GetCell(colIdx, rowIdx);
- }
- catch (const std::exception& ex) {
- Error("Exception while parsing %s value from cell [%u,%u]: %s.\n", DataTable_GetStringFromType(type), rowIdx, colIdx, ex.what());
- return T{};
- }
-}
-
static void DataTable_ReportInvalidValueError(const dtblcoltype_t type, const uint32_t rowIdx, const uint32_t colIdx)
{
Error("Invalid %s value at cell [%u,%u].\n", DataTable_GetStringFromType(type), rowIdx, colIdx);
@@ -184,25 +184,12 @@ static void DataTable_SetupValues(CPakFileBuilder* const pak, PakAsset_t& asset,
}
case dtblcoltype_t::Vector:
{
- std::string val = DataTable_ParseCellFromDocument(doc, colIdx, rowIdx, col.type);
- std::smatch sm;
+ const std::string val = DataTable_ParseCellFromDocument(doc, colIdx, rowIdx, col.type);
+ Vector3 vec;
// get values from format ""
- const bool result = std::regex_search(val, sm, std::regex("<(.*),(.*),(.*)>"));
-
- // 0 - all
- // 1 - x
- // 2 - y
- // 3 - z
- if (result && (sm.size() == 4))
- {
- const Vector3 vec(
- static_cast(atof(sm[1].str().c_str())),
- static_cast(atof(sm[2].str().c_str())),
- static_cast(atof(sm[3].str().c_str())));
-
+ if (sscanf_s(val.c_str(), "<%f,%f,%f>", &vec.x, &vec.y, &vec.z) == 3)
valbuf.write(vec);
- }
else
DataTable_ReportInvalidValueError(col.type, rowIdx, colIdx);
break;
diff --git a/src/assets/particle_effect.cpp b/src/assets/particle_effect.cpp
new file mode 100644
index 00000000..c0e5f7da
--- /dev/null
+++ b/src/assets/particle_effect.cpp
@@ -0,0 +1,98 @@
+#include "pch.h"
+#include "assets.h"
+#include "particle_operators_params.h"
+#include "public/particle_effect.h"
+#include "utils/DmxTools.h"
+
+#define PARTICLE_DEFINITION_ENC "binary"
+#define PARTICLE_DEFINITION_ENC_VER 5
+#define PARTICLE_DEFINITION_FMT "pcf"
+#define PARTICLE_DEFINITION_FMT_VER 2
+
+static void ParticleEffect_ValidateHeader(const DmxHeader_s& hdr)
+{
+ if (hdr.formatVersion != PARTICLE_DEFINITION_FMT_VER)
+ Error("Particle effect is version %d, but %d was expected!", hdr.formatVersion, PARTICLE_DEFINITION_FMT_VER);
+}
+
+/*static*/ void ParticleEffect_BakeElement(DmSymbolTable& /*symbolTable*/, DmElement_s& /*elem*/)
+{
+ //inheritEntityScale = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "Inherit entity scale", true);
+ //ParticleEffect_BakeParams();
+}
+
+struct ParticlePageMemory_s
+{
+ size_t stringPoolStart;
+ size_t stringDictStart;
+ size_t elementsStart;
+};
+
+/*static*/ void ParticleEffect_CreatePageLayoutAndAlloc(const DmContext_s& ctx, ParticlePageMemory_s& outMem, const char* const particleName)
+{
+ const size_t nameBufLen = strlen(particleName) +1;
+
+ outMem.stringPoolStart = sizeof(EffectAssetData_s) + nameBufLen;
+ outMem.stringDictStart = IALIGN8(outMem.stringPoolStart + ctx.symbolTable.StringBytesRetained());
+ outMem.elementsStart = outMem.stringDictStart + ctx.symbolTable.NumStringsRetained() * sizeof(PagePtr_t);
+}
+
+static void ParticleEffect_ProcessSymbolTable(DmContext_s& ctx)
+{
+ const DmSymbolTable::SymbolId_t target = ctx.symbolTable.Find("DmeParticleOperator");
+
+ if (target == DmSymbolTable::npos)
+ return; // Nothing to process, no string dictionary to build.
+
+ // note(kawe): since baked particle effects must store and expose all fields
+ // including default fields, we don't need to store the field
+ // names inside the baked dictionary. Mark everything that we
+ // plan to discard later so the symbol table is aware and knows
+ // the total pool size we need for the page buffer.
+ for (const DmElement_s& elem : ctx.elementList)
+ {
+ if (elem.type.s != target)
+ continue;
+
+ ctx.symbolTable.MarkAsDiscarded(elem.name.s);
+
+ for (const DmAttribute_s& at : elem.attr)
+ {
+ if (at.type == AT_STRING)
+ ctx.symbolTable.MarkAsDiscarded(at.value.stringSym.s);
+ }
+ }
+}
+
+static void ParticleEffect_InternalBake(CPakFileBuilder* const pak, /*PakAsset_t& asset,*/ const PakGuid_t /*assetGuid*/, const char* const assetPath)
+{
+ BinaryIO input;
+
+ if (!input.Open(pak->GetAssetPath() + assetPath, BinaryIO::Mode_e::Read))
+ Error("Failed to open particle effect asset \"%s\".\n", assetPath);
+
+ DmxHeader_s header;
+
+ if (!Dmx_ParseHdr(input, header))
+ return;
+
+ ParticleEffect_ValidateHeader(header);
+ DmContext_s ctx;
+
+ if (!Dmx_DeserializeBinary(ctx, input))
+ return;
+
+ ParticleEffect_ProcessSymbolTable(ctx);
+
+ std::vector list;
+ for (auto& elem : ctx.elementList)
+ {
+ ParticleDefintionParams_s& params = list.emplace_back();
+ ParticleEffect_BakeParams(ctx.symbolTable, elem, params);
+ }
+}
+
+void Assets::AddParticleEffect_v2(CPakFileBuilder* const pak, const PakGuid_t assetGuid, const char* const assetPath, const rapidjson::Value& /*mapEntry*/)
+{
+ ParticleEffect_InternalBake(pak, assetGuid, assetPath);
+}
diff --git a/src/assets/particle_operator_params.cpp b/src/assets/particle_operator_params.cpp
new file mode 100644
index 00000000..d9b292c9
--- /dev/null
+++ b/src/assets/particle_operator_params.cpp
@@ -0,0 +1,153 @@
+#include "pch.h"
+#include "particle_operators_params.h"
+#include
+#include
+
+
+template
+T ParticleEffect_FindAndSetField_OrDefault(DmSymbolTable& symbolTable, const DmElement_s& elem, const std::string_view name, const T defaultValue)
+{
+ const DmSymbolTable::SymbolId_t sym = symbolTable.Find(name);
+
+ if (sym == DmSymbolTable::npos)
+ return defaultValue;
+
+ const DmAttribute_s* const at = Dme_FindAttribute(elem, DmeSymbol_t(sym));
+
+ if (!at)
+ return defaultValue;
+
+ const DmAttributeType_e expectedType = Dma_GetTypeForPod();
+
+ if (at->type != expectedType)
+ return defaultValue;
+
+ return Dma_GetValue(*at);
+}
+
+template
+T ParticleEffect_FindAndSetField_OrDefault_Clamped(DmSymbolTable& symbolTable, const DmElement_s& elem, const std::string_view name, const T defaultValue, const T minValue, const T maxValue)
+{
+ const T result = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, name, defaultValue);
+
+ if (result < minValue || result > maxValue)
+ {
+ // todo: warning.
+ return defaultValue;
+ }
+
+ return result;
+}
+
+/*static*/ void ParticleEffect_BakeParams(DmSymbolTable& symbolTable, const DmElement_s& elem, ParticleDefintionParams_s& params)
+{
+ params.maxParticles = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "max_particles", 1000);
+ params.initialParticles = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "initial_particles", 0);
+
+ // todo: materialName strings
+
+ params.boundingBoxMin = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "bounding_box_min", Vector3(-10, -10, -10));
+ params.boundingBoxMax = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "bounding_box_max", Vector3(10, 10, 10));
+
+ params.cullRadius = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "cull_radius", 0.0f);
+ params.cullControlPoint = ParticleEffect_FindAndSetField_OrDefault_Clamped(symbolTable, elem, "cull_control_point", 0, 0, 63);
+
+ // todo: fallbackReplacementName strings
+
+ params.fallbackThreshold = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "fallback threshold", 1); // todo: is this a float? most likely not, check s0 pak
+
+ params.constantRadius = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "radius", 5.0f);
+ params.constantColor = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "color", ColorB(255, 255, 255, 255));
+ params.constantColorScale = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "color HDR scale", 1.0f);
+ params.constantRotation = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "rotation", 0.0f);
+ params.constantRotationSpeed = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "rotation_speed", 0.0f);
+ params.constantNormal = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "normal", Vector3(0, 0, 1));
+ params.constantNormalSpinDegrees = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "normal spin degrees", 0.0f);
+ // todo: figure out sequence numbers (user data???)
+
+ params.groupId = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "group id", 0);
+ params.maximumTimeStep = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "maximum time step", 0.1f);
+ params.maximumSimTime = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "maximum sim tick rate", 0.0f);
+ params.minimumSimTime = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum sim tick rate", 0.0f);
+ params.minimumFrames = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum rendered frames", 0);
+
+ params.skipRenderControlPoint = ParticleEffect_FindAndSetField_OrDefault_Clamped(symbolTable, elem, "control point to disable rendering if it is the camera", -1, -1, 63);
+ params.allowRenderControlPoint = ParticleEffect_FindAndSetField_OrDefault_Clamped(symbolTable, elem, "control point to only enable rendering if it is the camera", -1, -1, 63);
+
+ params.maxDrawDistance = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "maximum draw distance", 100000.0f);
+ params.minDrawDistance = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum draw distance", 0.0f);
+
+ params.applyDrawDistanceWhenChild = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "apply draw distance when child", false);
+ params.noDrawTimeToGoToSleep = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "time to sleep when not drawn", 8.0f);
+
+ params.shouldSort = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "Sort particles", true);
+ params.shouldBatch = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "batch particle systems", false);
+
+ params.viewModelEffect = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "view model effect", false);
+ params.screenSpaceEffect = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "screen space effect", false);
+ params.drawWithScreenSpace = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "draw with screen space", false);
+ params.drawThroughLeafSystem = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "draw through leafsystem", true);
+ params.checkOwnerDormant = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "check owner dormant", true);
+ params.maxRecursionDepth = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "maximum portal recursion depth", 8);
+ params.aggregateRadius = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "aggregation radius", 0.0f);
+ params.aggregationMinAvailableParticles = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum free particles to aggregate", 0);
+ params.minimumTimeStep = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum simulation time step", 0.0f);
+ params.minCpuLevel = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum CPU level", 0);
+ params.minGpuLevel = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "minimum GPU level", 0);
+
+ params.stopSimulationAfterTime = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "freeze simulation after time", 1000000000.0f);
+ params.warmUpTime = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "warm-up time", 0.0f);
+ params.pauseAfterWarmUp = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "warm-up and pause", false);
+
+ params.killIfOverLimit = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "kill if over-limit", true);
+ params.useHeightInYaw = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "use height in yaw", false);
+ params.doDrawDuringPhaseShift = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "phase shift - allowed", false);
+ params.doOnlyDrawDuringPhaseShift = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "phase shift - only", false);
+ params.inheritsAlphaVisibility = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "inherit alpha visibility", false);
+ params.inheritsRawVisibility = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "inherit proxy visibility", false);
+ params.randomSeed = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "random seed override", 0);
+
+ params.renderShadows = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "shadows", false);
+ params.isScripted = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "scripted", false);
+ params.reserved = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "reserved", false);
+ params.sortFromOrigin = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "sort from origin", false);
+
+ params.lowResDrawEnable = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "low res draw enable", false);
+ params.lowResDist = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "low res draw dist", 800.0f);
+
+ params.inheritEntityScale = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "Inherit entity scale", true);
+}
+
+/*static*/ void ParticleEffect_BakeParams_C_OP_GraphVector_Params(DmSymbolTable& symbolTable, const DmElement_s& elem, C_OP_GraphVector_Params& params)
+{
+ // TODO: graph array
+ /*
+ graph: "0 0 0 0 0.5 1 1 1 1 0 0 0"
+ */
+ params.m_isLooping = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "graph loop", false);
+ params.m_graphTime = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "graph time", 1.0f);
+ params.m_graphTimeIsInLifespans = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "graph time is in lifespans", true);
+ params.m_bUseLocalSpace = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "use local space", false);
+ params.m_nFieldOutput = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "output field", 0);
+ params.m_outputScaleType = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "output op", 0);
+ params.m_LocalCtrlPoint = ParticleEffect_FindAndSetField_OrDefault_Clamped(symbolTable, elem, "local control point", 0, 0, 63);
+ params.m_inputTimeCtrlPoint = ParticleEffect_FindAndSetField_OrDefault_Clamped(symbolTable, elem, "input time control point", -1, -1, 63);
+ params.m_vecOutputMin = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "output minimum", Vector3(-1.0f, -1.0f, -1.0f));
+ params.m_vecOutputMax = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "output maximum", Vector3(1.0f, 1.0f, 1.0f));
+}
+
+/*static*/ void ParticleEffect_BakeParams_C_OP_RenderLightSource_Params(DmSymbolTable& symbolTable, const DmElement_s& elem, C_OP_RenderLightSource_Params& params)
+{
+ params.m_flColorScale = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "color scale", 1.0f);
+ params.m_bColorScaleByAlpha = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "color scale by alpha", false);
+ params.m_flRadiusScale = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "radius scale", 1.0f);
+ params.m_bIsCockpit = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "is cockpit light", false);
+ params.m_flPriorityMultiplier = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "priority multiplier", 1.0f);
+ params.m_flFalloffHalfwayFrac = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "falloff halfway fraction", 0.3f);
+}
+
+/*static*/ void ParticleEffect_BakeParams_C_OP_RenderScreenVelocityRotate_Params(DmSymbolTable& symbolTable, const DmElement_s& elem, C_OP_RenderScreenVelocityRotate_Params& params)
+{
+ params.m_flRotateRateDegrees = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "rotate_rate(dps)", 0.0f);
+ params.m_flForwardDegrees = ParticleEffect_FindAndSetField_OrDefault(symbolTable, elem, "forward_angle", -90.0f);
+}
diff --git a/src/assets/particle_operators_params.h b/src/assets/particle_operators_params.h
new file mode 100644
index 00000000..f516f860
--- /dev/null
+++ b/src/assets/particle_operators_params.h
@@ -0,0 +1,42 @@
+#pragma once
+#include "math/vector.h"
+#include "math/color.h"
+#include "utils/DmxTools.h"
+#include "public/particle_effect.h"
+
+struct C_OP_GraphVector_Params
+{
+ Vector4 m_graph[16];
+ bool m_isLooping;
+ float m_graphTime;
+ bool m_graphTimeIsInLifespans;
+ bool m_bUseLocalSpace;
+ int m_nFieldOutput;
+ int m_outputScaleType;
+ int m_LocalCtrlPoint;
+ int m_inputTimeCtrlPoint;
+ Vector3 m_vecOutputMin;
+ Vector3 m_vecOutputMax;
+};
+
+struct C_OP_RenderPoints_Params
+{
+};
+
+struct C_OP_RenderLightSource_Params
+{
+ float m_flColorScale;
+ bool m_bColorScaleByAlpha;
+ float m_flRadiusScale;
+ bool m_bIsCockpit;
+ float m_flPriorityMultiplier;
+ float m_flFalloffHalfwayFrac;
+};
+
+struct C_OP_RenderScreenVelocityRotate_Params
+{
+ float m_flRotateRateDegrees;
+ float m_flForwardDegrees;
+};
+
+extern void ParticleEffect_BakeParams(DmSymbolTable& symbolTable, const DmElement_s& elem, ParticleDefintionParams_s& params);
diff --git a/src/assets/settings.cpp b/src/assets/settings.cpp
index 86466718..ceba9740 100644
--- a/src/assets/settings.cpp
+++ b/src/assets/settings.cpp
@@ -273,8 +273,8 @@ static void SettingsAsset_CalculateModValuesBuffers(const rapidjson::Value& modV
item.nameIndex = static_cast(nameIndex);
- if (item.nameIndex > modNamesCount)
- Error("Settings mod value #%zu indexes outside mod names array (%hu > %hu).\n", elemIndex, item.nameIndex, static_cast(modNamesCount));
+ if (item.nameIndex >= modNamesCount)
+ Error("Settings mod value #%zu indexes outside mod names array (%hu > %hu).\n", elemIndex, item.nameIndex, static_cast(modNamesCount-1));
const char* const typeName = JSON_GetValueRequired(elem, "type");
@@ -443,23 +443,16 @@ static void SettingsAsset_WriteAssetValue(const char* const string, const size_t
static void SettingsAsset_WriteVectorValue(const char* const value, const char* const fieldName,
const size_t valueOffset, const bool isVec2, PakPageLump_s& dataLump)
{
- std::cmatch sm; // get values from format "", or "".
- const bool result = std::regex_search(value, sm, std::regex(isVec2 ? "<(.*),(.*)>" : "<(.*),(.*),(.*)>"));
-
- // 0 - all
- // 1 - x
- // 2 - y
- // 3 - z (if !isVec2)
- if (result && (sm.size() == (isVec2 ? 3 : 4)))
- {
- Vector3* const vec = reinterpret_cast(&dataLump.data[valueOffset]);
- vec->x = static_cast(atof(sm[1].str().c_str()));
- vec->y = static_cast(atof(sm[2].str().c_str()));
+ Vector3* const vec = reinterpret_cast(&dataLump.data[valueOffset]);
+ bool success = false;
- if (!isVec2)
- vec->z = static_cast(atof(sm[3].str().c_str()));
- }
+ // get values from format "", or "".
+ if (isVec2)
+ success = sscanf_s(value, "<%f,%f>", &vec->x, &vec->y) == 2;
else
+ success = sscanf_s(value, "<%f,%f,%f>", &vec->x, &vec->y, &vec->z) == 3;
+
+ if (!success)
{
const char* const targetType = isVec2 ? "float2" : "float3";
Error("Field \"%s\" has value '%s' that cannot be parsed as %s.\n", fieldName, value, targetType);
diff --git a/src/assets/settings_layout.cpp b/src/assets/settings_layout.cpp
index d1086dae..ae7f6034 100644
--- a/src/assets/settings_layout.cpp
+++ b/src/assets/settings_layout.cpp
@@ -69,20 +69,19 @@ bool SettingsFieldFinder_FindFieldByAbsoluteOffset(const SettingsLayoutAsset_s&
for (size_t i = 0; i < layout.rootLayout.typeMap.size(); i++)
{
const uint32_t totalValueBufSizeAligned = IALIGN(layout.rootLayout.totalValueBufferSize, layout.rootLayout.alignment);
+ const uint32_t originalBaseCurrentDepth = result.currentBase;
- if (targetOffset > result.currentBase + (layout.rootLayout.arrayElemCount * totalValueBufSizeAligned))
+ if (targetOffset >= originalBaseCurrentDepth + (layout.rootLayout.arrayElemCount * totalValueBufSizeAligned))
return false; // Beyond this layout.
const uint32_t fieldOffset = layout.rootLayout.offsetMap[i];
- if (targetOffset < fieldOffset)
+ if (targetOffset < (originalBaseCurrentDepth + fieldOffset))
return false; // Invalid offset (i.e. we have 2 ints at 4 and 8, but target was 5).
- const uint32_t originalBase = result.currentBase;
-
for (int currArrayIdx = 0; currArrayIdx < layout.rootLayout.arrayElemCount; currArrayIdx++)
{
- const uint32_t elementBase = result.currentBase + (currArrayIdx * totalValueBufSizeAligned);
+ const uint32_t elementBase = originalBaseCurrentDepth + (currArrayIdx * totalValueBufSizeAligned);
const uint32_t absoluteFieldOffset = elementBase + fieldOffset;
const SettingsFieldType_e fieldType = layout.rootLayout.typeMap[i];
@@ -124,7 +123,7 @@ bool SettingsFieldFinder_FindFieldByAbsoluteOffset(const SettingsLayoutAsset_s&
return true;
}
- result.currentBase = originalBase;
+ result.currentBase = originalBaseCurrentDepth;
}
}
}
@@ -230,7 +229,7 @@ bool SettingsFieldFinder_FindFieldByAbsoluteName(const SettingsLayoutAsset_s& la
if (lookupLayoutIndex < 0 || lookupLayoutIndex >= arrayElemCount)
{
Error("%s: field \"%s\" is an array with range interval [0,%d), but provided array subscript was %d.\n",
- __FUNCTION__, lookupName, arrayElemCount-1, lookupLayoutIndex);
+ __FUNCTION__, lookupName, arrayElemCount, lookupLayoutIndex);
return false;
}
diff --git a/src/assets/ui_image_atlas.cpp b/src/assets/ui_image_atlas.cpp
index 8a825f8f..22325eb0 100644
--- a/src/assets/ui_image_atlas.cpp
+++ b/src/assets/ui_image_atlas.cpp
@@ -54,6 +54,9 @@ void Assets::AddUIImageAsset_v10(CPakFileBuilder* const pak, const PakGuid_t ass
UIImageAtlasHeader_t* const pHdr = reinterpret_cast(hdrLump.data);
const TextureAssetHeader_t* const atlasHdr = reinterpret_cast(atlasAsset->header);
+ if (atlasHdr->width == 0 || atlasHdr->height == 0)
+ Error("UI image atlas has invalid dimensions (%hux%hu); width and height cannot be zero!\n", atlasHdr->width, atlasHdr->height);
+
pHdr->width = atlasHdr->width;
pHdr->height = atlasHdr->height;
diff --git a/src/logic/pakfile.cpp b/src/logic/pakfile.cpp
index 3f84d814..39847fba 100644
--- a/src/logic/pakfile.cpp
+++ b/src/logic/pakfile.cpp
@@ -22,6 +22,7 @@ static std::unordered_set s_pakAssetHandler
{"txan", PakAssetScope_e::kClientOnly, nullptr, Assets::AddTextureAnimAsset_v1},
{"uimg", PakAssetScope_e::kClientOnly, Assets::AddUIImageAsset_v10, Assets::AddUIImageAsset_v10},
{"rlcd", PakAssetScope_e::kClientOnly, Assets::AddLcdScreenEffect_v0, Assets::AddLcdScreenEffect_v0},
+ {"efct", PakAssetScope_e::kClientOnly, nullptr, Assets::AddParticleEffect_v2},
{"matl", PakAssetScope_e::kClientOnly, Assets::AddMaterialAsset_v12, Assets::AddMaterialAsset_v15},
{"mt4a", PakAssetScope_e::kClientOnly, nullptr, Assets::AddMaterialForAspectAsset_v3},
{"shdr", PakAssetScope_e::kClientOnly, Assets::AddShaderAsset_v8, Assets::AddShaderAsset_v12},
diff --git a/src/math/color.h b/src/math/color.h
index b5b28a19..666f7786 100644
--- a/src/math/color.h
+++ b/src/math/color.h
@@ -1,13 +1,36 @@
#pragma once
-struct Color
+struct ColorB
{
- Color()
+ ColorB()
+ {
+ this->Set(0, 0, 0, 0);
+ };
+
+ ColorB(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+ {
+ this->Set(r, g, b, a);
+ }
+
+ void Set(uint8_t newR, uint8_t newG, uint8_t newB, uint8_t newA)
+ {
+ this->r = newR;
+ this->g = newG;
+ this->b = newB;
+ this->a = newA;
+ }
+
+ uint8_t r, g, b, a;
+};
+
+struct ColorF
+{
+ ColorF()
{
this->Set(0.0f, 0.0f, 0.0f, 0.0f);
};
- Color(float r, float g, float b, float a)
+ ColorF(float r, float g, float b, float a)
{
this->Set(r, g, b, a);
}
diff --git a/src/math/vector.h b/src/math/vector.h
index 15dbb5dc..1b498ab2 100644
--- a/src/math/vector.h
+++ b/src/math/vector.h
@@ -1,5 +1,28 @@
#pragma once
+struct Vector4
+{
+ Vector4()
+ {
+ this->Set(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+
+ Vector4(float x, float y, float z, float w)
+ {
+ this->Set(x, y, z, w);
+ }
+
+ void Set(float newX, float newY, float newZ, float newW)
+ {
+ this->x = newX;
+ this->y = newY;
+ this->z = newZ;
+ this->w = newW;
+ }
+
+ float x, y, z, w;
+};
+
struct Vector3
{
Vector3()
@@ -64,3 +87,26 @@ struct QAngle
float x, y, z;
};
+
+struct Quaternion
+{
+ Quaternion()
+ {
+ this->Set(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+
+ Quaternion(float x, float y, float z, float w)
+ {
+ this->Set(x, y, z, w);
+ }
+
+ void Set(float newX, float newY, float newZ, float newW)
+ {
+ this->x = newX;
+ this->y = newY;
+ this->z = newZ;
+ this->w = newW;
+ }
+
+ float x, y, z, w;
+};
diff --git a/src/math/vmatrix.h b/src/math/vmatrix.h
new file mode 100644
index 00000000..5bf0df1f
--- /dev/null
+++ b/src/math/vmatrix.h
@@ -0,0 +1,6 @@
+#pragma once
+
+struct VMatrix
+{
+ float m[4][4];
+};
diff --git a/src/pch.h b/src/pch.h
index 46b274ad..1546a91d 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -10,6 +10,7 @@
#include
//#include
#include
+#include
#include
#include
#include
diff --git a/src/public/dmarray.h b/src/public/dmarray.h
new file mode 100644
index 00000000..90348bed
--- /dev/null
+++ b/src/public/dmarray.h
@@ -0,0 +1,49 @@
+#pragma once
+
+struct DmAttribute_s;
+
+//template
+//struct DmArrayValue_s
+//{
+// std::vector data;
+//};
+//
+//template
+//class DmArray
+//{
+//public:
+// DmArray(DmAttribute_s* attr)
+// : m_attr(attr)
+// {
+// assert(attr != nullptr);
+// //assert(attr->type == GetExpectedType());
+// if (!attr->value.ptr)
+// {
+// // Allocate.
+// attr->value.ptr = new DmArrayValue_s();
+// }
+// m_vec = static_cast*>(attr->value.ptr);
+// }
+//
+// void AddToTail(const T& val) { m_vec->data.push_back(val); }
+// void RemoveAll() { m_vec->data.clear(); }
+// void EnsureCapacity(const size_t count) { m_vec->data.reserve(count); }
+// size_t Count() const { return m_vec->data.size(); }
+//
+// T& operator[](size_t i) { return m_vec->data[i]; }
+// const T& operator[](size_t i) const { return m_vec->data[i]; }
+//
+// auto begin() { return m_vec->data.begin(); }
+// auto end() { return m_vec->data.end(); }
+//
+//private:
+// DmAttribute_s* m_attr;
+// DmArrayValue_s* m_vec;
+//
+// ///static DmAttributeType_e GetExpectedType();
+//};
+
+//template<> inline DmAttributeType_e DmArray::GetExpectedType() { return AT_INT_ARRAY; }
+//template<> inline DmAttributeType_e DmArray::GetExpectedType() { return AT_FLOAT_ARRAY; }
+//template<> inline DmAttributeType_e DmArray::GetExpectedType() { return AT_STRING_ARRAY; }
+
diff --git a/src/public/dmattribute.h b/src/public/dmattribute.h
new file mode 100644
index 00000000..55b92fa0
--- /dev/null
+++ b/src/public/dmattribute.h
@@ -0,0 +1,347 @@
+#pragma once
+#include "math/vector.h"
+#include "math/color.h"
+#include "math/vmatrix.h"
+#include "dmarray.h"
+
+// Structs in order for compile-time classification
+// to work inside Dma_GetTypeForPod().
+struct DmeHandle_t { int h; };
+struct DmeSymbol_t { int s; };
+
+struct DmeTime_s
+{
+ DmeTime_s() : tms(INT_MIN) {}
+ explicit DmeTime_s(int tms) : tms(tms) {}
+ explicit DmeTime_s(float sec) : tms(RoundSecondsToTMS(sec)) {}
+ explicit DmeTime_s(double sec) : tms(RoundSecondsToTMS(sec)) {}
+
+ static int RoundSecondsToTMS(float sec) { return (int)floor(10000.0f * sec + 0.5f); };
+ static int RoundSecondsToTMS(double sec) { return (int)floor(10000.0f * sec + 0.5f); };
+
+ int tms;
+};
+
+union DmAttributeValue_u
+{
+ DmeHandle_t element;
+ int intVal;
+ float floatVal;
+ bool boolVal;
+ DmeSymbol_t stringSym;
+ void* ptr;
+ DmeTime_s timeVal;
+ ColorB color;
+ Vector2 vec2;
+ Vector3 vec3;
+ Vector4 vec4;
+ QAngle ang;
+ Quaternion quat;
+ VMatrix mat;
+
+ DmAttributeValue_u() { memset(this, 0, sizeof(*this)); }
+};
+
+enum DmAttributeType_e : int8_t
+{
+ AT_UNKNOWN = 0,
+
+ AT_FIRST_VALUE_TYPE,
+
+ AT_ELEMENT = AT_FIRST_VALUE_TYPE,
+ AT_INT,
+ AT_FLOAT,
+ AT_BOOL,
+ AT_STRING,
+ AT_VOID,
+ AT_TIME,
+ AT_COLOR,
+ AT_VECTOR2,
+ AT_VECTOR3,
+ AT_VECTOR4,
+ AT_QANGLE,
+ AT_QUATERNION,
+ AT_VMATRIX,
+
+ AT_FIRST_ARRAY_TYPE,
+
+ AT_ELEMENT_ARRAY = AT_FIRST_ARRAY_TYPE,
+ AT_INT_ARRAY,
+ AT_FLOAT_ARRAY,
+ AT_BOOL_ARRAY,
+ AT_STRING_ARRAY,
+ AT_VOID_ARRAY,
+ AT_TIME_ARRAY,
+ AT_COLOR_ARRAY,
+ AT_VECTOR2_ARRAY,
+ AT_VECTOR3_ARRAY,
+ AT_VECTOR4_ARRAY,
+ AT_QANGLE_ARRAY,
+ AT_QUATERNION_ARRAY,
+ AT_VMATRIX_ARRAY,
+
+ AT_TYPE_COUNT,
+ AT_TYPE_INVALID,
+};
+
+inline const char* Dma_TypeToString(const DmAttributeType_e type)
+{
+ switch (type)
+ {
+ case AT_UNKNOWN: return "unknown";
+ case AT_ELEMENT: return "element";
+ case AT_INT: return "int";
+ case AT_FLOAT: return "float";
+ case AT_BOOL: return "bool";
+ case AT_STRING: return "string";
+ case AT_VOID: return "void";
+ case AT_TIME: return "time";
+ case AT_COLOR: return "color";
+ case AT_VECTOR2: return "vector2";
+ case AT_VECTOR3: return "vector3";
+ case AT_VECTOR4: return "vector4";
+ case AT_QANGLE: return "qangle";
+ case AT_QUATERNION: return "quaternion";
+ case AT_VMATRIX: return "vmatrix";
+ case AT_ELEMENT_ARRAY: return "element array";
+ case AT_INT_ARRAY: return "int array";
+ case AT_FLOAT_ARRAY: return "float array";
+ case AT_BOOL_ARRAY: return "bool array";
+ case AT_STRING_ARRAY: return "string array";
+ case AT_VOID_ARRAY: return "void array";
+ case AT_TIME_ARRAY: return "time array";
+ case AT_COLOR_ARRAY: return "color array";
+ case AT_VECTOR2_ARRAY: return "vector2 array";
+ case AT_VECTOR3_ARRAY: return "vector3 array";
+ case AT_VECTOR4_ARRAY: return "vector4 array";
+ case AT_QANGLE_ARRAY: return "qangle array";
+ case AT_QUATERNION_ARRAY: return "quaternion array";
+ case AT_VMATRIX_ARRAY: return "vmatrix array";
+ default: return "invalid";
+ }
+}
+
+template
+constexpr DmAttributeType_e Dma_GetTypeForPod()
+{
+ if constexpr (std::is_same_v)
+ return AT_ELEMENT;
+ else if constexpr (std::is_same_v)
+ return AT_ELEMENT_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_INT;
+ else if constexpr (std::is_same_v)
+ return AT_INT_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_FLOAT;
+ else if constexpr (std::is_same_v)
+ return AT_FLOAT_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_BOOL;
+ else if constexpr (std::is_same_v)
+ return AT_BOOL_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_VOID;
+ else if constexpr (std::is_same_v)
+ return AT_VOID_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_STRING;
+ else if constexpr (std::is_same_v)
+ return AT_STRING_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_TIME;
+ else if constexpr (std::is_same_v)
+ return AT_TIME_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_COLOR;
+ else if constexpr (std::is_same_v)
+ return AT_COLOR_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_VECTOR2;
+ else if constexpr (std::is_same_v)
+ return AT_VECTOR2_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_VECTOR3;
+ else if constexpr (std::is_same_v)
+ return AT_VECTOR3_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_VECTOR4;
+ else if constexpr (std::is_same_v)
+ return AT_VECTOR4_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_QANGLE;
+ else if constexpr (std::is_same_v)
+ return AT_QANGLE_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_QUATERNION;
+ else if constexpr (std::is_same_v)
+ return AT_QUATERNION_ARRAY;
+ else if constexpr (std::is_same_v)
+ return AT_VMATRIX;
+ else if constexpr (std::is_same_v)
+ return AT_VMATRIX_ARRAY;
+ else
+ static_assert(std::is_same_v, "Cannot classify data type; unsupported.");
+}
+
+struct DmAttribute_s
+{
+ DmAttributeValue_u value;
+ DmAttributeType_e type = AT_UNKNOWN;
+ DmeSymbol_t name{ -1 };
+};
+
+template
+T Dma_GetValue(const DmAttribute_s& attrib)
+{
+ constexpr DmAttributeType_e t = Dma_GetTypeForPod();
+
+#ifndef NDEBUG
+ if (attrib.type != t)
+ Error("%s: requested type does not match stored type", __FUNCTION__);
+#endif
+ if constexpr (t == AT_ELEMENT)
+ return attrib.value.element;
+ else if constexpr (t == AT_INT)
+ return attrib.value.intVal;
+ else if constexpr (t == AT_FLOAT)
+ return attrib.value.floatVal;
+ else if constexpr (t == AT_BOOL)
+ return attrib.value.boolVal;
+ else if constexpr (t == AT_STRING)
+ return attrib.value.stringSym;
+ else if constexpr (t == AT_VOID)
+ return attrib.value.ptr;
+ else if constexpr (t == AT_TIME)
+ return attrib.value.timeVal;
+ else if constexpr (t == AT_COLOR)
+ return attrib.value.color;
+ else if constexpr (t == AT_VECTOR2)
+ return attrib.value.vec2;
+ else if constexpr (t == AT_VECTOR3)
+ return attrib.value.vec3;
+ else if constexpr (t == AT_VECTOR4)
+ return attrib.value.vec4;
+ else if constexpr (t == AT_QANGLE)
+ return attrib.value.ang;
+ else if constexpr (t == AT_QUATERNION)
+ return attrib.value.quat;
+ else if constexpr (t == AT_VMATRIX)
+ return attrib.value.mat;
+ else
+ static_assert(std::is_same_v, "Unsupported attribute type");
+}
+
+template
+void Dma_SetValue(DmAttribute_s& attrib, const T& v)
+{
+ constexpr DmAttributeType_e t = Dma_GetTypeForPod();
+ attrib.type = t;
+
+ if constexpr (t == AT_ELEMENT)
+ attrib.value.element = v;
+ else if constexpr (t == AT_INT)
+ attrib.value.intVal = v;
+ else if constexpr (t == AT_FLOAT)
+ attrib.value.floatVal = v;
+ else if constexpr (t == AT_BOOL)
+ attrib.value.boolVal = v;
+ else if constexpr (t == AT_STRING)
+ attrib.value.stringSym = v;
+ else if constexpr (t == AT_VOID)
+ attrib.value.ptr = v;
+ else if constexpr (t == AT_TIME)
+ attrib.value.timeVal = v;
+ else if constexpr (t == AT_COLOR)
+ attrib.value.color = v;
+ else if constexpr (t == AT_VECTOR2)
+ attrib.value.vec2 = v;
+ else if constexpr (t == AT_VECTOR3)
+ attrib.value.vec3 = v;
+ else if constexpr (t == AT_VECTOR4)
+ attrib.value.vec4 = v;
+ else if constexpr (t == AT_QANGLE)
+ attrib.value.ang = v;
+ else if constexpr (t == AT_QUATERNION)
+ attrib.value.quat = v;
+ else if constexpr (t == AT_VMATRIX)
+ attrib.value.mat = v;
+ else
+ printf("Unsupported attribute type!\n");
+ //static_assert(std::is_same_v, "Unsupported attribute type!");
+}
+
+inline int Dma_NumComponents(const DmAttributeType_e type)
+{
+ switch (type)
+ {
+ case AT_BOOL:
+ case AT_INT:
+ case AT_FLOAT:
+ case AT_TIME:
+ return 1;
+
+ case AT_VECTOR2:
+ return 2;
+
+ case AT_VECTOR3:
+ case AT_QANGLE:
+ return 3;
+
+ case AT_COLOR:
+ case AT_VECTOR4:
+ case AT_QUATERNION:
+ return 4;
+
+ case AT_VMATRIX:
+ return 16;
+
+ case AT_ELEMENT:
+ case AT_STRING:
+ case AT_VOID:
+ default:
+ return 0;
+ }
+}
+
+template
+struct DmArrayValue_s
+{
+ std::vector data;
+};
+
+template
+class DmArray
+{
+public:
+ DmArray(DmAttribute_s* attr)
+ : m_attr(attr)
+ {
+ assert(attr != nullptr);
+ //assert(attr->type == GetExpectedType());
+ if (!attr->value.ptr)
+ {
+ // Allocate.
+ attr->value.ptr = new DmArrayValue_s();
+ }
+ attr->type = Dma_GetTypeForPod();
+ m_vec = static_cast*>(attr->value.ptr);
+ }
+
+ void AddToTail(const T& val) { m_vec->data.push_back(val); }
+ void RemoveAll() { m_vec->data.clear(); }
+ void EnsureCapacity(const size_t count) { m_vec->data.reserve(count); }
+ size_t Count() const { return m_vec->data.size(); }
+
+ T& operator[](size_t i) { return m_vec->data[i]; }
+ const T& operator[](size_t i) const { return m_vec->data[i]; }
+
+ auto begin() { return m_vec->data.begin(); }
+ auto end() { return m_vec->data.end(); }
+
+private:
+ DmAttribute_s* m_attr;
+ DmArrayValue_s* m_vec;
+
+ ///static DmAttributeType_e GetExpectedType();
+};
diff --git a/src/public/dmelement.h b/src/public/dmelement.h
new file mode 100644
index 00000000..1d46eb98
--- /dev/null
+++ b/src/public/dmelement.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "dmattribute.h"
+
+#define DM_ELEMENT_NULL -1
+#define DM_ELEMENT_EXTERNAL -2
+
+struct DmObjectId_s
+{
+ unsigned char value[16];
+};
+
+using DmObjectIdString_s = char[37]; // 37 is max a string representation of the guid will utilize.
+
+struct DmElement_s
+{
+ DmObjectId_s id;
+ DmeSymbol_t name;
+ DmeSymbol_t type;
+ std::vector attr;
+};
+
+inline const DmAttribute_s* Dme_FindAttribute(const DmElement_s& elem, const DmeSymbol_t name)
+{
+ for (const DmAttribute_s& at : elem.attr)
+ {
+ if (at.name.s == name.s)
+ return &at;
+ }
+
+ return nullptr;
+}
diff --git a/src/public/dmexchange.h b/src/public/dmexchange.h
new file mode 100644
index 00000000..8cfa49e2
--- /dev/null
+++ b/src/public/dmexchange.h
@@ -0,0 +1,43 @@
+#pragma once
+#include "dmelement.h"
+#include "symboltable.h"
+
+#define DMX_VERSION_STARTING_TOKEN ""
+#define DMX_HEADER_COMPONENT_COUNT 4 //
+
+struct DmxHeader_s
+{
+ enum
+ {
+ MAX_FORMAT_NAME_LENGTH = 64,
+ MAX_HEADER_LENGTH = 40 + 2 * MAX_FORMAT_NAME_LENGTH,
+ };
+
+ DmxHeader_s()
+ : encodingVersion(-1)
+ , formatVersion(-1)
+ {
+ encodingName[0] = '\0';
+ formatName[0] = '\0';
+ }
+
+ char encodingName[MAX_FORMAT_NAME_LENGTH];
+ int encodingVersion;
+ char formatName[MAX_FORMAT_NAME_LENGTH];
+ int formatVersion;
+};
+
+enum DmFileId_e
+{
+ DMFILEID_INVALID = 0xFFFFFFFF
+};
+
+typedef CSymbolTable DmSymbolTable;
+typedef std::vector DmElementList;
+
+struct DmContext_s
+{
+ DmSymbolTable symbolTable;
+ DmElementList elementList;
+};
diff --git a/src/public/particle_effect.h b/src/public/particle_effect.h
new file mode 100644
index 00000000..4c618200
--- /dev/null
+++ b/src/public/particle_effect.h
@@ -0,0 +1,155 @@
+#pragma once
+#include "math/vector.h"
+#include "math/color.h"
+#include "dmelement.h"
+
+struct UtlStringDisk_s // CUtlString encoded on the disk.
+{
+ const char* memory;
+ int64_t allocationCount;
+ int64_t growSize;
+ int64_t actualLength;
+};
+
+struct ParticleDefintionParams_s
+{
+ int maxParticles;
+ int initialParticles;
+ UtlStringDisk_s materialName;
+ Vector3 boundingBoxMin;
+ Vector3 boundingBoxMax;
+ float cullRadius;
+ int cullControlPoint;
+ UtlStringDisk_s fallbackReplacementName;
+ int fallbackThreshold;
+ float constantRadius;
+ ColorB constantColor;
+ float constantColorScale;
+ float constantRotation;
+ float constantRotationSpeed;
+ Vector3 constantNormal;
+ float constantNormalSpinDegrees;
+ int constantSequenceNumber;
+ int constantSequenceNumber1;
+ int groupId;
+ float maximumTimeStep;
+ float maximumSimTime;
+ float minimumSimTime;
+ int minimumFrames;
+ int skipRenderControlPoint;
+ int allowRenderControlPoint;
+ float maxDrawDistance;
+ float minDrawDistance;
+ bool applyDrawDistanceWhenChild;
+ char gapBD[3]; // padding.
+ float noDrawTimeToGoToSleep;
+ bool shouldSort;
+ bool shouldBatch;
+ bool viewModelEffect;
+ bool screenSpaceEffect;
+ bool drawWithScreenSpace;
+ bool drawThroughLeafSystem;
+ bool checkOwnerDormant;
+ char gapCB; // padding.
+ int maxRecursionDepth;
+ float aggregateRadius;
+ int aggregationMinAvailableParticles;
+ float minimumTimeStep;
+ int minCpuLevel;
+ int minGpuLevel;
+ float stopSimulationAfterTime;
+ float warmUpTime;
+ bool pauseAfterWarmUp;
+ bool killIfOverLimit;
+ bool useHeightInYaw;
+ bool doDrawDuringPhaseShift;
+ bool doOnlyDrawDuringPhaseShift;
+ bool inheritsAlphaVisibility;
+ bool inheritsRawVisibility;
+ char gapF3; // padding.
+ int randomSeed;
+ bool renderShadows;
+ bool isScripted;
+ bool reserved;
+ bool sortFromOrigin;
+ bool lowResDrawEnable;
+ char gapFD[3]; // padding.
+ float lowResDist;
+ bool inheritEntityScale;
+ char gap105[3]; // padding.
+};
+
+struct ParticleOperatorCommonParams_s
+{
+ float ppStartFadeInTime;
+ float opEndFadeInTime;
+ float opStartFadeOutTime;
+ float opEndFadeOutTime;
+ float opFadeOscillatePeriod;
+ int opTimeOffsetSeed;
+ float opTimeOffsetMin;
+ float opTimeOffsetMax;
+ int opTimeScaleSeed;
+ float opTimeScaleMin;
+ float opTimeScaleMax;
+ int opStrengthScaleSeed;
+ float opStrengthMinScale;
+ float opStrengthMaxScale;
+ int opEndCapState;
+ bool mute;
+};
+
+struct ParticleOperatorBaked_s
+{
+ uint16_t opTypeIndex; // indexes into dictionary, gets the operator type
+ uint16_t opSpecificParamsDataSize; // struct size of operator (matches with ctor for each operator, so its most likely jsut that)
+ uint16_t visibilityInputsIndex; // same stuff as source engine but baked (CParticleVisibilityInputs)
+ uint16_t initializerParamsIndex; // same stuff as source engine but baked
+ uint16_t opSpecificParamsIndex; // points to struct that is specific to this operator
+ char pad[10]; // id must be aligned to 16 bytes.
+ DmObjectId_s id;
+ ParticleOperatorCommonParams_s common; // similar to source, but also baked
+};
+
+struct ParticleOperatorList_s
+{
+ ParticleOperatorBaked_s** operators;
+ uint64_t count;
+};
+
+struct ParticleChildParams_s
+{
+ float delay;
+ bool endCap;
+ bool mute;
+ int elemIdx;
+};
+
+struct ParticleElementDisk_s
+{
+ const char* name;
+ DmObjectId_s id;
+ ParticleChildParams_s* childRefs;
+ const char** scriptRefs;
+ int numChildRefs;
+ int numScriptRefs;
+ bool preventNameBasedLookup;
+ char pad[4]; // params must be aligned to 8 bytes.
+ ParticleDefintionParams_s params;
+ ParticleOperatorList_s operators[6];
+};
+
+struct EffectAssetData_s
+{
+ const char* fileName;
+ ParticleElementDisk_s* elements;
+ size_t numElements;
+ const char** stringDict;
+ size_t numStrings;
+};
+
+struct EffectAssetHdr_s
+{
+ EffectAssetData_s* pcfPtr;
+ size_t pcfCount;
+};
diff --git a/src/public/rpak.h b/src/public/rpak.h
index d0e27fb9..c7a259be 100644
--- a/src/public/rpak.h
+++ b/src/public/rpak.h
@@ -31,6 +31,7 @@
#define TYPE_RMDL MAKE_FOURCC('m', 'd', 'l', '_') // mdl_
#define TYPE_UIMG MAKE_FOURCC('u', 'i', 'm', 'g') // uimg
#define TYPE_RLCD MAKE_FOURCC('r', 'l', 'c', 'd') // rlcd
+#define TYPE_EFCT MAKE_FOURCC('e', 'f', 'c', 't') // efct
#define TYPE_PTCH MAKE_FOURCC('P', 't', 'c', 'h') // Ptch
#define TYPE_DTBL MAKE_FOURCC('d', 't', 'b', 'l') // dtbl
#define TYPE_STLT MAKE_FOURCC('s', 't', 'l', 't') // stlt
@@ -53,6 +54,7 @@ enum class AssetType : uint32_t
RMDL = TYPE_RMDL, // model
UIMG = TYPE_UIMG, // ui image atlas
RLCD = TYPE_RLCD, // lcd screen effect
+ EFCT = TYPE_EFCT, // particle effect
PTCH = TYPE_PTCH, // patch
DTBL = TYPE_DTBL, // datatable
STLT = TYPE_STLT, // settings layout
diff --git a/src/public/symboltable.h b/src/public/symboltable.h
new file mode 100644
index 00000000..5c813036
--- /dev/null
+++ b/src/public/symboltable.h
@@ -0,0 +1,179 @@
+#pragma once
+#include "utils/logger.h"
+
+class CStringPool
+{
+public:
+ CStringPool() = default;
+
+ const char* Add(std::string_view str)
+ {
+ if (str.empty())
+ return "";
+
+ m_pool.emplace_back(str);
+ m_totalStringBytes += str.size() +1; // +1 for null terminator.
+
+ return m_pool.back().c_str();
+ }
+
+ void AppendRetainedStringSize(const size_t len)
+ {
+ m_numUnstaleStrings++;
+ assert(m_numUnstaleStrings <= m_pool.size());
+ m_retainedStringBytes += len +1;
+ assert(m_retainedStringBytes <= m_totalStringBytes);
+ }
+
+ inline size_t StringBytesTotal() const noexcept { return m_totalStringBytes; }
+ inline size_t StringBytesDiscarded() const noexcept { return m_totalStringBytes - m_retainedStringBytes; }
+ inline size_t StringBytesRetained() const noexcept { return m_retainedStringBytes; }
+
+ inline size_t NumStringsTotal() const noexcept { return m_pool.size(); }
+ inline size_t NumStringsDiscarded() const noexcept { return m_pool.size() - m_retainedStringBytes; }
+ inline size_t NumStringsRetained() const noexcept { return m_retainedStringBytes; }
+
+private:
+ std::deque m_pool;
+ size_t m_numUnstaleStrings = 0;
+ size_t m_totalStringBytes = 0;
+ size_t m_retainedStringBytes = 0;
+};
+
+template
+class CSymbolTable
+{
+public:
+ using SymbolId_t = uint32_t;
+ static constexpr SymbolId_t npos = (std::numeric_limits::max)();
+
+ explicit CSymbolTable(const size_t poolReserve = 1024)
+ {
+ m_symbols.reserve(poolReserve);
+ if constexpr (!CaseInsensitive)
+ m_lookupSensitive.reserve(poolReserve);
+ else
+ m_lookupInsensitive.reserve(poolReserve);
+ }
+
+ SymbolId_t AddString(const std::string_view& str)
+ {
+ auto& lookup = getLookup();
+ if (auto it = lookup.find(str); it != lookup.end())
+ return it->second;
+
+ if (m_symbols.size() >= static_cast(npos))
+ Error("CSymbolTable: SymbolId overflow");
+
+ const char* stored = m_pool.Add(str);
+ SymbolId_t id = static_cast(m_symbols.size());
+ m_symbols.emplace_back(stored, str.size());
+ lookup.emplace(m_symbols.back(), id);
+ return id;
+ }
+
+ std::string_view GetString(const SymbolId_t id) const
+ {
+ if (id == npos || static_cast(id) >= m_symbols.size())
+ Error("CSymbolTable::GetString: invalid ID");
+
+ return m_symbols[id];
+ }
+
+ SymbolId_t Find(const std::string_view& str) const noexcept
+ {
+ const auto& lookup = getLookup();
+ if (auto it = lookup.find(str); it != lookup.end())
+ return it->second;
+ return npos;
+ }
+
+ void MarkAsDiscarded(const SymbolId_t id) noexcept
+ {
+ if (id == npos || static_cast(id) >= m_symbols.size())
+ return;
+
+ if (m_discardSet.find(id) != m_discardSet.end())
+ return;
+
+ std::string_view& str = m_symbols[id];
+
+ if (!str.empty())
+ m_pool.AppendRetainedStringSize(str.size());
+
+ m_discardSet.insert(id);
+ }
+
+ bool IsDiscarded(const SymbolId_t id) const noexcept
+ {
+ return m_discardSet.find(id) != m_discardSet.end();
+ }
+
+ inline size_t StringBytesTotal() const noexcept { return m_pool.StringBytesTotal(); }
+ inline size_t StringBytesDiscarded() const noexcept { return m_pool.StringBytesDiscarded(); }
+ inline size_t StringBytesRetained() const noexcept { return m_pool.StringBytesRetained(); }
+
+ inline size_t NumStringsTotal() const noexcept { return m_pool.NumStringsTotal(); }
+ inline size_t NumStringsDiscarded() const noexcept { return m_pool.NumStringsDiscarded(); }
+ inline size_t NumStringsRetained() const noexcept { return m_pool.NumStringsRetained(); }
+
+private:
+ struct CaseInsensitiveHash_s
+ {
+ using is_transparent = void;
+ size_t operator()(std::string_view s) const noexcept
+ {
+ uint64_t hash = 1469598103934665603ULL;
+ for (unsigned char c : s)
+ {
+ hash ^= static_cast(std::tolower(c));
+ hash *= 1099511628211ULL;
+ }
+ return static_cast(hash);
+ }
+ };
+
+ struct CaseInsensitiveEqual_s
+ {
+ using is_transparent = void;
+ bool operator()(std::string_view a, std::string_view b) const noexcept
+ {
+ if (a.size() != b.size())
+ return false;
+
+ for (size_t i = 0; i < a.size(); ++i)
+ {
+ if (std::tolower(static_cast(a[i])) !=
+ std::tolower(static_cast(b[i])))
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ using LookupSensitive = std::unordered_map;
+ using LookupInsensitive = std::unordered_map;
+
+ auto& GetLookup()
+ {
+ if constexpr (CaseInsensitive)
+ return m_lookupInsensitive;
+ else
+ return m_lookupSensitive;
+ }
+ const auto& GetLookup() const
+ {
+ if constexpr (CaseInsensitive)
+ return m_lookupInsensitive;
+ else
+ return m_lookupSensitive;
+ }
+
+ CStringPool m_pool;
+ std::vector m_symbols;
+ std::unordered_set m_discardSet;
+ LookupSensitive m_lookupSensitive;
+ LookupInsensitive m_lookupInsensitive;
+};
diff --git a/src/utils/DmxTools.cpp b/src/utils/DmxTools.cpp
new file mode 100644
index 00000000..c6be4ce9
--- /dev/null
+++ b/src/utils/DmxTools.cpp
@@ -0,0 +1,551 @@
+#include "pch.h"
+#include "DmxTools.h"
+#include "public/dmelement.h"
+#include "public/dmattribute.h"
+#include "public/symboltable.h"
+
+bool Dmx_ParseHdr(BinaryIO& bio, DmxHeader_s& hdr)
+{
+ char hdrStr[DmxHeader_s::MAX_HEADER_LENGTH];
+
+ if (!bio.ParseToken(hdrStr, DMX_VERSION_STARTING_TOKEN, DMX_VERSION_ENDING_TOKEN, sizeof(hdrStr)))
+ {
+ Error("%s: failed to tokenize DMX header! (format should start with \"" DMX_VERSION_STARTING_TOKEN "\" and end with \"" DMX_VERSION_ENDING_TOKEN "\")", __FUNCTION__);
+ return false;
+ }
+
+ const int scanCount = sscanf_s(hdrStr, "encoding %s %d format %s %d\n",
+ hdr.encodingName, (uint32_t)sizeof(hdr.encodingName), &hdr.encodingVersion,
+ hdr.formatName, (uint32_t)sizeof(hdr.formatName), &hdr.formatVersion);
+
+ if (scanCount != DMX_HEADER_COMPONENT_COUNT)
+ {
+ Error("%s: failed to parse DMX header! (expected %d components, got %d)", __FUNCTION__, DMX_HEADER_COMPONENT_COUNT, scanCount);
+ return false;
+ }
+
+ char c; // Consume the rest of the text so the cursor points past the header.
+ while (bio.Get(c) && c != '\0');
+
+ return true;
+}
+
+static bool Dmx_GetStringTable(DmContext_s& ctx, BinaryIO& bio, const int numStrings)
+{
+ char stack[2048];
+ int iter;
+
+ for (iter = 0; iter < numStrings; iter++)
+ {
+ size_t strLen;
+
+ if (!bio.ReadString(stack, sizeof(stack), &strLen))
+ break;
+
+ ctx.symbolTable.AddString({ stack, strLen });
+ }
+
+ if (iter != numStrings)
+ {
+ Error("%s: failed to parse DMX string table! (expected %d strings, got %d)", __FUNCTION__, numStrings, iter);
+ return false;
+ }
+
+ return true;
+}
+
+//static const char* Dme_GetString(BinaryIO& bio, const DmSymbolTable& table)
+//{
+// const int symHnd = bio.Read();
+// return table.GetString(symHnd).data();
+//}
+
+static bool Dme_ReadElem(BinaryIO& bio, DmElement_s& elem)
+{
+ elem.name = bio.Read();
+ elem.type = bio.Read();
+
+ return bio.Read(elem.id);
+}
+
+char* Dme_GuidToString(const DmObjectId_s& guid, DmObjectIdString_s out)
+{
+ static constexpr char hexDigits[] = "0123456789abcdef";
+
+ const uint8_t* v = guid.value;
+ int pos = 0;
+
+ for (int i = 0; i < 16; ++i)
+ {
+ out[pos++] = hexDigits[v[i] >> 4];
+ out[pos++] = hexDigits[v[i] & 0xF];
+
+ if (i == 3 || i == 5 || i == 7 || i == 9)
+ out[pos++] = '-';
+ }
+
+ out[pos] = '\0';
+ return out;
+}
+
+static bool Dmx_DeserializeElements(DmContext_s& ctx, BinaryIO& bio)
+{
+ const int numElems = bio.Read();
+
+ if (!numElems)
+ return true; // valve does this too.
+
+ ctx.elementList.resize(numElems);
+
+ for (int iter = 0; iter < numElems; iter++)
+ {
+ if (!Dme_ReadElem(bio, ctx.elementList[iter]))
+ return false;
+ }
+
+ //printf("num elems: %d\n", numElems);
+ return true;
+}
+
+static DmeSymbol_t Dma_ReadSymHnd(BinaryIO& bio)
+{
+ return bio.Read();
+}
+
+static DmeHandle_t Dma_ReadElemIdx(BinaryIO& bio)
+{
+ const DmeHandle_t hnd = bio.Read();
+
+ if (hnd.h == DM_ELEMENT_EXTERNAL)
+ Error("%s: external element references are not supported!\n", __FUNCTION__);
+
+ return hnd;
+}
+
+template
+static inline T Dma_DoRead(BinaryIO& bio)
+{
+ return bio.Read();
+}
+
+template <>
+inline DmeTime_s Dma_DoRead(BinaryIO& bio)
+{
+ return DmeTime_s(Dma_DoRead(bio));
+}
+
+template
+static void Dma_Deserialize(BinaryIO& bio, DmAttribute_s& attrib)
+{
+ Dma_SetValue(attrib, Dma_DoRead(bio));
+}
+
+static DmAttributeType_e s_lastType;
+
+template
+static void Dma_ReadArray(BinaryIO& bio, DmAttribute_s& attrib)
+{
+ DmArray arr(&attrib);
+ const int numElems = bio.Read();
+
+ if (!numElems)
+ return;
+
+ arr.RemoveAll();
+ arr.EnsureCapacity(numElems);
+
+ for (int i = 0; i < numElems; i++)
+ {
+ const T rd = Dma_DoRead(bio);
+ arr.AddToTail(rd);
+ }
+}
+
+static void Dma_DeserializeAny(BinaryIO& bio, DmAttribute_s& attrib, const DmAttributeType_e type)
+{
+ switch (type)
+ {
+ case AT_INT:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_FLOAT:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_BOOL:
+ Dma_Deserialize(bio, attrib);
+ break;
+// case AT_STRING:
+ //Dma_Deserialize<>(bio, attrib); // todo
+// break;
+// case AT_VOID:
+ //Dma_Deserialize<>(bio, attrib); // todo
+// break;
+ case AT_TIME:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_COLOR:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_VECTOR2:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_VECTOR3:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_VECTOR4:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_QANGLE:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_QUATERNION:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_VMATRIX:
+ Dma_Deserialize(bio, attrib);
+ break;
+ case AT_INT_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_FLOAT_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_BOOL_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+// case AT_STRING_ARRAY:
+ //Unserialize(buf, this->m_pData);
+// break;
+// case AT_VOID_ARRAY:
+ //Unserialize(buf, this->m_pData);
+// break;
+ case AT_TIME_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_COLOR_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_VECTOR2_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_VECTOR3_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_VECTOR4_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_QANGLE_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_QUATERNION_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_VMATRIX_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+
+ default:
+ Error("%s: unhandled attribute type %d @ %zd!\n", __FUNCTION__, type, bio.TellGet());
+ }
+
+ s_lastType = type;
+}
+
+static bool Dma_DeserializeAttribute(const DmContext_s& /*ctx*/, BinaryIO& bio, DmAttribute_s& attrib, const DmAttributeType_e type)
+{
+ switch (type)
+ {
+ case AT_ELEMENT:
+ Dma_SetValue(attrib, Dma_ReadElemIdx(bio));
+ break;
+ case AT_ELEMENT_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+ case AT_STRING:
+ Dma_SetValue(attrib, Dma_ReadSymHnd(bio));
+ break;
+ case AT_STRING_ARRAY:
+ Dma_ReadArray(bio, attrib);
+ break;
+
+ default:
+ Dma_DeserializeAny(bio, attrib, type);
+ }
+
+ return true;
+}
+
+static bool Dmx_DeserializeAttributes(const DmContext_s& ctx, DmElement_s& elem, BinaryIO& bio)
+{
+ const int numAttribs = bio.Read();
+ elem.attr.resize(numAttribs);
+
+ for (int i = 0; i < numAttribs; i++)
+ {
+ DmAttribute_s& attrib = elem.attr[i];
+ attrib.name = bio.Read();
+
+ //printf("%s: %s\n",__FUNCTION__, ctx.symbolTable.GetString(attrib.name.s).data());
+ const DmAttributeType_e type = bio.Read();
+ Dma_DeserializeAttribute(ctx, bio, attrib, type);
+ }
+
+ //printf("numAttribs: %d\n", numAttribs);
+ return true;
+}
+
+#define PRINT_ARRAY(fmt, accessor) \
+ { \
+ auto* arr = static_cast*>(attr.value.ptr); \
+ printf("{ "); \
+ for (size_t i = 0; i < arr->data.size(); ++i) \
+ { \
+ printf(fmt, arr->data[i]); \
+ if (i + 1 < arr->data.size()) printf(", "); \
+ } \
+ printf(" }"); \
+ }
+
+static void Dma_Print(const DmContext_s& ctx, const DmAttribute_s& attr)
+{
+ printf("\t%s(%s) = ", ctx.symbolTable.GetString(attr.name.s).data(), Dma_TypeToString(attr.type));
+
+ switch (attr.type)
+ {
+ case AT_ELEMENT:
+ printf("#%d (--> %s)", attr.value.element.h, ctx.symbolTable.GetString(ctx.elementList[attr.value.element.h].name.s).data());
+ break;
+ case AT_INT:
+ printf("%d", attr.value.intVal);
+ break;
+ case AT_FLOAT:
+ printf("%f", attr.value.floatVal);
+ break;
+ case AT_BOOL:
+ printf(attr.value.boolVal ? "true" : "false");
+ break;
+ case AT_STRING:
+ printf("%s", ctx.symbolTable.GetString(attr.value.stringSym.s).data());
+ break;
+ case AT_VOID:
+ printf("ptr: %p", attr.value.ptr);
+ break;
+ case AT_TIME:
+ printf("%d tms (%.4f sec)", attr.value.timeVal.tms, attr.value.timeVal.tms / 10000.0f);
+ break;
+ case AT_COLOR:
+ printf("Color(%u, %u, %u, %u)", attr.value.color.r, attr.value.color.g,
+ attr.value.color.b, attr.value.color.a);
+ break;
+ case AT_VECTOR2:
+ printf("Vector2(%.3f, %.3f)", attr.value.vec2.x, attr.value.vec2.y);
+ break;
+ case AT_VECTOR3:
+ printf("Vector3(%.3f, %.3f, %.3f)",
+ attr.value.vec3.x, attr.value.vec3.y, attr.value.vec3.z);
+ break;
+ case AT_VECTOR4:
+ printf("Vector4(%.3f, %.3f, %.3f, %.3f)",
+ attr.value.vec4.x, attr.value.vec4.y, attr.value.vec4.z, attr.value.vec4.w);
+ break;
+ case AT_QANGLE:
+ printf("QAngle(%.3f, %.3f, %.3f)",
+ attr.value.ang.x, attr.value.ang.y, attr.value.ang.z);
+ break;
+ case AT_QUATERNION:
+ printf("Quaternion(%.3f, %.3f, %.3f, %.3f)",
+ attr.value.quat.x, attr.value.quat.y, attr.value.quat.z, attr.value.quat.w);
+ break;
+ case AT_VMATRIX:
+ printf("VMatrix[\n");
+ for (int i = 0; i < 4; ++i)
+ {
+ printf(" %.3f %.3f %.3f %.3f\n",
+ attr.value.mat.m[i][0],
+ attr.value.mat.m[i][1],
+ attr.value.mat.m[i][2],
+ attr.value.mat.m[i][3]);
+ }
+ printf("]");
+ break;
+ case AT_ELEMENT_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ const auto& v = arr->data[i];
+ printf("#%d (--> %s)", v.h, ctx.symbolTable.GetString(ctx.elementList[v.h].name.s).data());
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_INT_ARRAY:
+ if (attr.value.ptr) { PRINT_ARRAY("%d", int()); }
+ else printf("{}");
+ break;
+ case AT_FLOAT_ARRAY:
+ if (attr.value.ptr) { PRINT_ARRAY("%f", float()); }
+ else printf("{}");
+ break;
+ case AT_BOOL_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ printf(arr->data[i] ? "true" : "false");
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_VECTOR2_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ const auto& v = arr->data[i];
+ printf("(%.2f, %.2f)", v.x, v.y);
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_VECTOR3_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ const auto& v = arr->data[i];
+ printf("(%.2f, %.2f, %.2f)", v.x, v.y, v.z);
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_VECTOR4_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ const auto& v = arr->data[i];
+ printf("(%.2f, %.2f, %.2f, %.2f)", v.x, v.y, v.z, v.w);
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_QANGLE_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ const auto& v = arr->data[i];
+ printf("(%.2f, %.2f, %.2f)", v.x, v.y, v.z);
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_QUATERNION_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ const auto& v = arr->data[i];
+ printf("(%.2f, %.2f, %.2f, %.2f)", v.x, v.y, v.z, v.w);
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+ case AT_STRING_ARRAY:
+ if (attr.value.ptr)
+ {
+ auto* arr = static_cast*>(attr.value.ptr);
+ printf("{ ");
+ for (size_t i = 0; i < arr->data.size(); ++i)
+ {
+ printf("%s", ctx.symbolTable.GetString(arr->data[i].s).data());
+ if (i + 1 < arr->data.size()) printf(", ");
+ }
+ printf(" }");
+ }
+ else printf("{}");
+ break;
+
+ default:
+ printf("");
+ break;
+ }
+
+ printf("\n");
+}
+
+#undef PRINT_ARRAY
+
+bool Dmx_DeserializeBinary(DmContext_s& ctx, BinaryIO& bio)
+{
+ const int numStrings = bio.Read();
+
+ if (!Dmx_GetStringTable(ctx, bio, numStrings))
+ return false;
+
+ if (!Dmx_DeserializeElements(ctx, bio))
+ return false;
+
+ //size_t j = 0;
+ //for (auto& t : ctx.elementList)
+ //{
+ // printf("----------------------- dmElem #%zd\n", j++);
+ // printf("%s(%s)\n", ctx.symbolTable.GetString(t.type.s).data(), ctx.symbolTable.GetString(t.name.s).data());
+
+ // DmObjectIdString_s idStr;
+ // Dme_GuidToString(t.id, idStr);
+
+ // printf("{%s}\n", idStr);
+ //}
+
+ for (size_t i = 0; i < ctx.elementList.size(); i++)
+ {
+ if (!Dmx_DeserializeAttributes(ctx, ctx.elementList[i], bio))
+ return false;
+ }
+
+ size_t elemIdx = 0;
+ for (auto& t : ctx.elementList)
+ {
+ printf("%s(%s) #%zu\n", ctx.symbolTable.GetString(t.type.s).data(), ctx.symbolTable.GetString(t.name.s).data(), elemIdx++);
+ printf("{\n");
+
+ for (auto& a : t.attr)
+ {
+ Dma_Print(ctx, a);
+ }
+
+ printf("}\n");
+ }
+
+ return true;
+}
diff --git a/src/utils/DmxTools.h b/src/utils/DmxTools.h
new file mode 100644
index 00000000..e6af9f79
--- /dev/null
+++ b/src/utils/DmxTools.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "binaryio.h"
+#include "public/dmexchange.h"
+
+extern bool Dmx_ParseHdr(BinaryIO& bio, DmxHeader_s& hdr);
+extern bool Dmx_DeserializeBinary(DmContext_s& ctx, BinaryIO& bio);
diff --git a/src/utils/binaryio.cpp b/src/utils/binaryio.cpp
index 32c0800a..08fa9858 100644
--- a/src/utils/binaryio.cpp
+++ b/src/utils/binaryio.cpp
@@ -3,23 +3,19 @@
#include
//-----------------------------------------------------------------------------
-// Purpose: CIOStream constructors
+// constructors/destructors
//-----------------------------------------------------------------------------
BinaryIO::BinaryIO()
{
Reset();
}
-
-//-----------------------------------------------------------------------------
-// Purpose: CIOStream destructor
-//-----------------------------------------------------------------------------
BinaryIO::~BinaryIO()
{
- Close();
+ Reset();
}
//-----------------------------------------------------------------------------
-// Purpose: get internal stream mode from selected mode
+// Get internal stream mode from selected mode
//-----------------------------------------------------------------------------
static std::ios_base::openmode GetInternalStreamMode(const BinaryIO::Mode_e mode)
{
@@ -40,22 +36,22 @@ static std::ios_base::openmode GetInternalStreamMode(const BinaryIO::Mode_e mode
}
//-----------------------------------------------------------------------------
-// Purpose: opens the file in specified mode
-// Input : *filePath -
-// mode -
-// Output : true if operation is successful
+// Opens the file in specified mode
//-----------------------------------------------------------------------------
-bool BinaryIO::Open(const char* const filePath, const Mode_e mode)
+bool BinaryIO::OpenEx(const char* const filePath, const Mode_e mode, const size_t pathLen)
{
+ assert(filePath && pathLen > 0);
+
m_flags = GetInternalStreamMode(mode);
m_mode = mode;
+ m_name.assign(filePath, pathLen);
if (m_stream.is_open())
{
m_stream.close();
}
- m_stream.open(filePath, m_flags);
+ m_stream.open(m_name, m_flags);
if (!m_stream.is_open() || !m_stream.good())
{
@@ -64,40 +60,39 @@ bool BinaryIO::Open(const char* const filePath, const Mode_e mode)
if (IsReadMode())
{
- struct _stat64 status;
- if (_stat64(filePath, &status) != NULL)
- {
- return false;
- }
-
- m_size = status.st_size;
+ // Calculate the initial size.
+ m_stream.seekg(0, std::ios::end);
+ m_state.totalSize = m_stream.tellg();
+ m_stream.seekg(0, std::ios::beg);
}
return true;
}
//-----------------------------------------------------------------------------
-// Purpose: resets the state
+// Resets the internal state
//-----------------------------------------------------------------------------
void BinaryIO::Reset()
{
- m_size = 0;
- m_skip = 0;
- m_mode = Mode_e::None;
+ m_state.totalSize = 0;
+ m_state.skipCount = 0;
+ m_state.seekGap = 0;
m_flags = 0;
+ m_mode = Mode_e::None;
}
//-----------------------------------------------------------------------------
-// Purpose: closes the stream
+// Closes the stream
//-----------------------------------------------------------------------------
void BinaryIO::Close()
{
m_stream.close();
Reset();
+ m_name.clear();
}
//-----------------------------------------------------------------------------
-// Purpose: flushes the ofstream
+// Flushes the ofstream
//-----------------------------------------------------------------------------
void BinaryIO::Flush()
{
@@ -106,8 +101,7 @@ void BinaryIO::Flush()
}
//-----------------------------------------------------------------------------
-// Purpose: gets the position of the current character in the stream
-// Output : std::streampos
+// Gets the position of the current character in the stream
//-----------------------------------------------------------------------------
std::streamoff BinaryIO::TellGet()
{
@@ -121,9 +115,7 @@ std::streamoff BinaryIO::TellPut()
}
//-----------------------------------------------------------------------------
-// Purpose: sets the position of the current character in the stream
-// Input : offset -
-// way -
+// Sets the position of the current character in the stream
//-----------------------------------------------------------------------------
void BinaryIO::SeekGet(const std::streamoff offset, const std::ios_base::seekdir way)
{
@@ -138,7 +130,7 @@ void BinaryIO::SeekPut(const std::streamoff offset, const std::ios_base::seekdir
{
assert(IsWriteMode());
- CalcSkipDelta(offset, way);
+ UpdateSeekState(offset, way);
m_stream.seekp(offset, way);
}
void BinaryIO::Seek(const std::streamoff offset, const std::ios_base::seekdir way)
@@ -150,8 +142,7 @@ void BinaryIO::Seek(const std::streamoff offset, const std::ios_base::seekdir wa
}
//-----------------------------------------------------------------------------
-// Purpose: returns the data
-// Output : std::filebuf*
+// Returns the stream buffer
//-----------------------------------------------------------------------------
const std::filebuf* BinaryIO::GetData() const
{
@@ -159,12 +150,11 @@ const std::filebuf* BinaryIO::GetData() const
}
//-----------------------------------------------------------------------------
-// Purpose: returns the data size
-// Output : std::streampos
+// Returns the data size
//-----------------------------------------------------------------------------
const std::streamoff BinaryIO::GetSize() const
{
- return m_size;
+ return m_state.totalSize;
}
bool BinaryIO::IsReadMode() const
@@ -178,32 +168,23 @@ bool BinaryIO::IsWriteMode() const
}
//-----------------------------------------------------------------------------
-// Purpose: checks if we are able to read the file
-// Output : true on success, false otherwise
+// Checks if we are able to read the file
//-----------------------------------------------------------------------------
bool BinaryIO::IsReadable() const
{
- if (!IsReadMode() || !m_stream || m_stream.eof())
- return false;
-
- return true;
+ return IsReadMode() && m_stream.good();
}
//-----------------------------------------------------------------------------
-// Purpose: checks if we are able to write to file
-// Output : true on success, false otherwise
+// Checks if we are able to write to file
//-----------------------------------------------------------------------------
bool BinaryIO::IsWritable() const
{
- if (!IsWriteMode() || !m_stream)
- return false;
-
- return true;
+ return IsWriteMode() && m_stream.good();
}
//-----------------------------------------------------------------------------
-// Purpose: checks if we hit the end of file
-// Output : true on success, false otherwise
+// Checks if we hit the end of file
//-----------------------------------------------------------------------------
bool BinaryIO::IsEof() const
{
@@ -211,83 +192,182 @@ bool BinaryIO::IsEof() const
}
//-----------------------------------------------------------------------------
-// Purpose: reads a string from the file
-// Input : &svOut -
-// Output : true on success, false otherwise
+// Reads a string from the file
//-----------------------------------------------------------------------------
bool BinaryIO::ReadString(std::string& out)
{
if (!IsReadable())
return false;
- while (!m_stream.eof())
- {
- const char c = Read();
+ char c;
+ while (m_stream.get(c))
+ {
if (c == '\0')
- break;
+ return true;
out += c;
}
- return true;
+ return false; // EOF or error, result is truncated.
}
//-----------------------------------------------------------------------------
-// Purpose: reads a string from the file into a fixed size buffer
-// Input : *buf -
-// len -
-// Output : true on success, false otherwise
+// Reads a string from the file into a fixed size buffer
//-----------------------------------------------------------------------------
-bool BinaryIO::ReadString(char* const buf, const size_t len)
+bool BinaryIO::ReadString(char* const buf, const size_t len, size_t* const outWriteCount)
{
+ assert(buf && len > 0);
+
if (!IsReadable())
return false;
size_t i = 0;
+ char c;
+ bool fullRead = false;
- while (i < len && !m_stream.eof())
+ while (i < (len - 1) && m_stream.get(c))
{
- const char c = Read();
-
if (c == '\0')
+ {
+ buf[i] = '\0';
+ fullRead = true;
+
break;
+ }
buf[i++] = c;
}
- return true;
+ if (!fullRead)
+ buf[i] = '\0';
+
+ if (outWriteCount)
+ *outWriteCount = i;
+
+ return fullRead; // if false, string too long and result is truncated.
}
//-----------------------------------------------------------------------------
-// Purpose: writes a string to the file
-// Input : &input -
-// Output : true on success, false otherwise
+// Eat first occurring white space from current position
//-----------------------------------------------------------------------------
-bool BinaryIO::WriteString(const std::string& input, const bool nullterminate)
+void BinaryIO::EatWhiteSpace()
{
- if (!IsWritable())
+ char c;
+ while (Get(c))
+ {
+ if (!isspace((unsigned char)c))
+ {
+ SeekGet(TellGet() - 1);
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parses a token between 2 delimiters into provided scratch buffer
+//-----------------------------------------------------------------------------
+bool BinaryIO::ParseToken(char* scratch, const char* startDelim, const char* endDelim, size_t maxLen)
+{
+ if (!endDelim || !*endDelim || maxLen == 0)
return false;
- const char* const text = input.c_str();
- const size_t len = input.length() + nullterminate;
+ char emptyBuf = '\0';
+ if (!startDelim)
+ startDelim = &emptyBuf;
+
+ const size_t endLen = strlen(endDelim);
+ const size_t startPos = TellGet();
+
+ EatWhiteSpace(); // Leading whitespace.
+
+ for (size_t i = 0; startDelim[i]; ++i)
+ {
+ char c = startDelim[i];
+ if (!isspace((unsigned char)c))
+ {
+ char got;
+ if (!Get(got))
+ {
+ SeekGet(startPos);
+ scratch[0] = '\0';
+ return false;
+ }
+ if (tolower(got) != tolower(c))
+ {
+ SeekGet(startPos);
+ scratch[0] = '\0';
+ return false;
+ }
+ }
+ else
+ {
+ EatWhiteSpace();
+ }
+ }
+
+ EatWhiteSpace(); // After delim.
+ const size_t tokenStart = TellGet();
+
+ size_t endMatchPos = 0;
+ while (true)
+ {
+ char c;
+ if (!Get(c))
+ {
+ SeekGet(startPos);
+ scratch[0] = '\0';
+ return false;
+ }
+
+ if (c == endDelim[endMatchPos])
+ {
+ ++endMatchPos;
+ if (endMatchPos == endLen)
+ break;
+ }
+ else
+ {
+ endMatchPos = 0;
+ }
+ }
- m_stream.write(text, len);
- CalcAddDelta(len);
+ const size_t tokenEnd = TellGet() - endLen;
+ size_t tokenLen = tokenEnd - tokenStart;
+ if (tokenLen >= maxLen)
+ tokenLen = maxLen - 1;
+
+ // Copy token.
+ SeekGet(tokenStart);
+ for (size_t i = 0; i < tokenLen; ++i)
+ {
+ if (!Get(scratch[i]))
+ {
+ SeekGet(startPos);
+ scratch[0] = '\0';
+ return false;
+ }
+ }
+
+ while (tokenLen > 0 && isspace((unsigned char)scratch[tokenLen - 1]))
+ --tokenLen;
+
+ scratch[tokenLen] = '\0';
+
+ SeekGet(tokenEnd + endLen);
return true;
}
// limit number of io calls and allocations by just using this static buffer
// for padding out the stream.
static constexpr size_t PAD_BUF_SIZE = 4096;
-const static char s_padBuf[PAD_BUF_SIZE];
+const static char s_padBuf[PAD_BUF_SIZE] = {};
//-----------------------------------------------------------------------------
-// Purpose: pads the out stream up to count bytes
-// Input : count -
+// Pads the out stream up to count bytes
//-----------------------------------------------------------------------------
-void BinaryIO::Pad(const size_t count)
+bool BinaryIO::Pad(const size_t count, size_t* const outPadCount)
{
assert(count > 0);
size_t remainder = count;
@@ -295,88 +375,136 @@ void BinaryIO::Pad(const size_t count)
while (remainder)
{
const size_t writeCount = (std::min)(remainder, PAD_BUF_SIZE);
- Write(s_padBuf, writeCount);
+
+ if (!Write(s_padBuf, writeCount))
+ break;
remainder -= writeCount;
}
+
+ if (outPadCount)
+ *outPadCount = count - remainder;
+
+ return remainder == 0;
}
//-----------------------------------------------------------------------------
-// Purpose: makes sure that the size gets incremented if we exceeded the end of
-// the stream with the delta amount
+// Internal read function.
//-----------------------------------------------------------------------------
-void BinaryIO::CalcAddDelta(const size_t count)
+bool BinaryIO::DoRead(char* const outBuf, const size_t readCount, size_t* const outReadCount)
{
- if (m_skip > 0)
- {
- m_skip -= count;
+ if (!IsReadable())
+ return false;
+
+ const std::streamoff signedReadCount = static_cast(readCount);
+ const std::streamoff actualReadCount = m_stream.rdbuf()->sgetn(outBuf, readCount);
+
+ if (outReadCount)
+ *outReadCount = static_cast(actualReadCount);
+
+ const bool wasSuccesful = actualReadCount == signedReadCount;
- if (m_skip < 0)
+ if (!wasSuccesful)
+ {
+ if (g_iosmErrorCallback)
{
- m_size += -m_skip; // Add the overshoot to the file size.
- m_skip = 0;
+ g_iosmErrorCallback("%s: short read on file \"%s\"; actualReadCount( %zd ) != signedReadCount( %zd ) @ TellGet( %zi )!\n",
+ __FUNCTION__, m_name.c_str(), actualReadCount, signedReadCount, TellGet());
}
}
- else
- m_size += count;
+
+ return wasSuccesful;
}
//-----------------------------------------------------------------------------
-// Purpose: if we seek backwards, and then write new data, we should not add
-// this to the total output size of the stream as we modify and not
-// add. we have to keep by how much we shifted backwards and advanced
-// forward until we can start adding again.
+// Internal write function.
//-----------------------------------------------------------------------------
-void BinaryIO::CalcSkipDelta(const std::streamoff offset, const std::ios_base::seekdir way)
+bool BinaryIO::DoWrite(const char* const inBuf, const size_t writeCount, size_t* const outWriteCount)
{
- switch (way)
- {
- case std::ios_base::beg:
+ if (!IsWritable())
+ return false;
+
+ const std::streamoff signedWriteCount = static_cast(writeCount);
+ const std::streamoff actualWriteCount = m_stream.rdbuf()->sputn(inBuf, signedWriteCount);
+
+ PostWriteUpdate(actualWriteCount);
+
+ if (outWriteCount)
+ *outWriteCount = actualWriteCount;
+
+ const bool wasSuccesful = actualWriteCount == signedWriteCount;
+
+ if (!wasSuccesful)
{
- if (offset < 0)
+ if (g_iosmErrorCallback)
{
- assert(false && "Negative offset in std::ios_base::beg is invalid.");
- return;
+ g_iosmErrorCallback("%s: short write on file \"%s\"; actualWriteCount( %zd ) != signedWriteCount( %zd ) @ TellPut( %zi )!\n",
+ __FUNCTION__, m_name.c_str(), actualWriteCount, signedWriteCount, TellPut());
}
+ }
- if (offset > m_size)
- {
- m_size = offset;
- m_skip = 0;
- }
- else
- m_skip = m_size - offset;
+ return wasSuccesful;
+}
+
+//-----------------------------------------------------------------------------
+// If we seek backwards, and then write new data, we should not add
+// this to the total output size of the stream as we modify and not
+// add. we have to keep by how much we shifted backwards and advanced
+// forward until we can start adding again.
+//-----------------------------------------------------------------------------
+void BinaryIO::UpdateSeekState(const std::streamoff offset, const std::ios_base::seekdir way)
+{
+ std::streamoff targetPos = 0;
+
+ switch (way) // determine the abs target pos of the seek.
+ {
+ case std::ios_base::beg:
+ targetPos = offset;
break;
- }
case std::ios_base::cur:
- {
- if (offset > 0)
- CalcAddDelta(offset);
- else
- m_skip += -offset;
+ targetPos = (m_state.totalSize - m_state.skipCount) + offset;
break;
- }
case std::ios_base::end:
- {
- if (offset >= 0)
- {
- m_size += offset;
- m_skip = 0;
- }
- else
- m_skip += -offset;
+ targetPos = m_state.totalSize + offset;
break;
}
- default:
- assert(false && "Unsupported seek direction.");
- break;
+
+ if (targetPos > m_state.totalSize)
+ {
+ m_state.seekGap = targetPos - m_state.totalSize;
+ m_state.skipCount = 0; // cannot have skip and gap.
+ }
+ else // seeking within logical bounds.
+ {
+ m_state.skipCount = m_state.totalSize - targetPos;
+ m_state.seekGap = 0; // cannot have skip and gap.
}
+}
- // Ensure m_skip is non-negative, this can happen if you call this method
- // with cur or end, and a negative value who's absolute value is greater
- // than the total stream size. If you hit this, you have a bug somewhere.
- assert(m_skip >= 0);
+//-----------------------------------------------------------------------------
+// Updates the logical file size after a write of `writeCount` bytes.
+// This handles padding from gaps and appending new data.
+//-----------------------------------------------------------------------------
+void BinaryIO::PostWriteUpdate(const std::streamoff writeCount)
+{
+ // if we had a gap, append it to the total size as it has been padded out.
+ if (m_state.seekGap > 0)
+ {
+ m_state.totalSize += m_state.seekGap;
+ m_state.seekGap = 0;
+ }
- if (m_skip < 0)
- m_skip = 0;
+ // account for overwriting/appending.
+ if (m_state.skipCount > 0)
+ {
+ if (writeCount >= m_state.skipCount)
+ {
+ m_state.totalSize += (writeCount - m_state.skipCount);
+ m_state.skipCount = 0;
+ }
+ else // Writing within the logical bounds.
+ m_state.skipCount -= writeCount;
+ }
+ else
+ m_state.totalSize += writeCount;
}
diff --git a/src/utils/binaryio.h b/src/utils/binaryio.h
index 1c1091df..402c24d6 100644
--- a/src/utils/binaryio.h
+++ b/src/utils/binaryio.h
@@ -1,5 +1,8 @@
#pragma once
+typedef void(*BinaryIOLogger_fn)(const char* fmt, ...);
+inline BinaryIOLogger_fn g_iosmErrorCallback = nullptr;
+
class BinaryIO
{
public:
@@ -15,8 +18,8 @@ class BinaryIO
BinaryIO();
~BinaryIO();
- bool Open(const char* const filePath, const Mode_e mode);
- inline bool Open(const std::string& filePath, const Mode_e mode) { return Open(filePath.c_str(), mode); };
+ inline bool Open(const char* const filePath, const Mode_e mode) { return OpenEx(filePath, mode, strlen(filePath)); }
+ inline bool Open(const std::string& filePath, const Mode_e mode) { return OpenEx(filePath.c_str(), mode, filePath.length()); };
void Close();
void Reset();
@@ -41,85 +44,95 @@ class BinaryIO
bool IsEof() const;
//-----------------------------------------------------------------------------
- // Purpose: reads any value from the file
+ // Reads any value from the file with specified size
//-----------------------------------------------------------------------------
template
- inline void Read(T& value)
+ inline bool Read(T* const outBuf, const size_t readCount, size_t* const outReadCount = nullptr)
{
- if (IsReadable())
- m_stream.read(reinterpret_cast(&value), sizeof(value));
+ return DoRead(reinterpret_cast(outBuf), readCount, outReadCount);
}
//-----------------------------------------------------------------------------
- // Purpose: reads any value from the file with specified size
+ // Reads any value from the file
//-----------------------------------------------------------------------------
template
- inline void Read(T* const value, const size_t size)
- {
- if (IsReadable())
- m_stream.read(reinterpret_cast(value), size);
- }
- template
- inline void Read(T& value, const size_t size)
+ inline bool Read(T& value, size_t* const outReadCount = nullptr)
{
- if (IsReadable())
- m_stream.read(reinterpret_cast(&value), size);
+ return Read(&value, sizeof(T), outReadCount);
}
//-----------------------------------------------------------------------------
- // Purpose: reads any value from the file and returns it
+ // Reads any value from the file and returns it
//-----------------------------------------------------------------------------
template
- inline T Read()
+ inline T Read(size_t* const outReadCount = nullptr)
{
T value{};
- if (!IsReadable())
- return value;
+ Read(&value, sizeof(T), outReadCount);
- m_stream.read(reinterpret_cast(&value), sizeof(value));
return value;
}
bool ReadString(std::string& svOut);
- bool ReadString(char* const pBuf, const size_t nLen);
+ bool ReadString(char* const pBuf, const size_t nLen, size_t* const outWriteCount = nullptr);
- //-----------------------------------------------------------------------------
- // Purpose: writes any value to the file
- //-----------------------------------------------------------------------------
template
- inline void Write(const T& value)
+ inline bool Get(T& out)
{
- if (!IsWritable())
- return;
+ m_stream.get(out);
+ return m_stream.good();
+ }
- const size_t count = sizeof(value);
+ void EatWhiteSpace();
+ bool ParseToken(char* const scratch, const char* const startDelim, const char* const endDelim, const size_t maxLen);
- m_stream.write(reinterpret_cast(&value), count);
- CalcAddDelta(count);
+ //-----------------------------------------------------------------------------
+ // Writes any value to the file with specified size
+ //-----------------------------------------------------------------------------
+ template
+ inline bool Write(const T* const inBuf, const size_t writeCount, size_t* const outWriteCount = nullptr)
+ {
+ return DoWrite(reinterpret_cast(inBuf), writeCount, outWriteCount);
}
//-----------------------------------------------------------------------------
- // Purpose: writes any value to the file with specified size
+ // Writes any value to the file
//-----------------------------------------------------------------------------
template
- inline void Write(const T* const value, const size_t size)
+ inline bool Write(const T& value, size_t* const outWriteCount = nullptr)
{
- if (!IsWritable())
- return;
+ return Write(&value, sizeof(T), outWriteCount);
+ }
- m_stream.write(reinterpret_cast(value), size);
- CalcAddDelta(size);
+ //-----------------------------------------------------------------------------
+ // Writes a string to the file
+ //-----------------------------------------------------------------------------
+ inline bool WriteString(const std::string& input, const bool nullTerminate, size_t* const outWriteCount = nullptr)
+ {
+ return Write(input.c_str(), input.length() + nullTerminate, outWriteCount);
}
- bool WriteString(const std::string& svInput, const bool nullterminate);
- void Pad(const size_t count);
+
+ bool Pad(const size_t count, size_t* const outPadCount = nullptr);
protected:
- void CalcAddDelta(const size_t count);
- void CalcSkipDelta(const std::streamoff offset, const std::ios_base::seekdir way);
+ bool OpenEx(const char* const filePath, const Mode_e mode, const size_t pathLen);
+
+ void UpdateSeekState(const std::streamoff offset, const std::ios_base::seekdir way);
+ void PostWriteUpdate(const std::streamoff writeCount);
+
+ bool DoRead(char* const outBuf, const size_t readCount, size_t* const outReadCount);
+ bool DoWrite(const char* const inBuf, const size_t writeCount, size_t* const outWriteCount);
private:
+ struct CursorState_s
+ {
+ std::streamoff totalSize; // Total stream output size.
+ std::streamoff skipCount; // Amount seeked into logical bounds.
+ std::streamoff seekGap; // Amount seeked beyond logical bounds.
+ };
+
std::fstream m_stream; // I/O stream.
- std::streamoff m_size; // File size.
- std::streamoff m_skip; // Amount skipped back.
+ CursorState_s m_state; // Stream state.
std::ios_base::openmode m_flags; // Stream flags.
Mode_e m_mode; // Stream mode.
+ std::string m_name; // Stream name.
};
diff --git a/src/utils/dmarray.cpp b/src/utils/dmarray.cpp
new file mode 100644
index 00000000..9cc859ab
--- /dev/null
+++ b/src/utils/dmarray.cpp
@@ -0,0 +1,3 @@
+#include "pch.h"
+#include "public/dmarray.h"
+
diff --git a/src/utils/jsonutils.cpp b/src/utils/jsonutils.cpp
index 14fac940..516f5998 100644
--- a/src/utils/jsonutils.cpp
+++ b/src/utils/jsonutils.cpp
@@ -19,7 +19,10 @@ bool JSON_ParseFromFile(const char* const assetPath, const char* const debugName
// if there are parsing or validation problems, we will still error as
// these are considered unintentional problems.
if (mandatory)
- g_jsonErrorCallback("%s: couldn't open %s file.\n", __FUNCTION__, debugName);
+ {
+ if (g_jsonErrorCallback)
+ g_jsonErrorCallback("%s: couldn't open %s file.\n", __FUNCTION__, debugName);
+ }
return false;
}
@@ -28,15 +31,20 @@ bool JSON_ParseFromFile(const char* const assetPath, const char* const debugName
if (document.ParseStream(jsonStreamWrapper).HasParseError())
{
- g_jsonErrorCallback("%s: %s parse error at position %zu: [%s].\n", __FUNCTION__, debugName,
- document.GetErrorOffset(), rapidjson::GetParseError_En(document.GetParseError()));
+ if (g_jsonErrorCallback)
+ {
+ g_jsonErrorCallback("%s: %s parse error at position %zu: [%s].\n", __FUNCTION__, debugName,
+ document.GetErrorOffset(), rapidjson::GetParseError_En(document.GetParseError()));
+ }
return false;
}
if (!document.IsObject())
{
- g_jsonErrorCallback("%s: %s root was not an object.\n", __FUNCTION__, debugName);
+ if (g_jsonErrorCallback)
+ g_jsonErrorCallback("%s: %s root was not an object.\n", __FUNCTION__, debugName);
+
return false;
}
diff --git a/src/utils/jsonutils.h b/src/utils/jsonutils.h
index a4d00998..b2bd965d 100644
--- a/src/utils/jsonutils.h
+++ b/src/utils/jsonutils.h
@@ -257,7 +257,9 @@ inline bool JSON_GetRequired(const T& data, typename T::StringRefType member, ty
if (JSON_GetIterator(data, member, out))
return true;
- g_jsonErrorCallback("%s: unable to find field \"%s\".\n", __FUNCTION__, member.s);
+ if (g_jsonErrorCallback)
+ g_jsonErrorCallback("%s: unable to find field \"%s\".\n", __FUNCTION__, member.s);
+
return false;
}
@@ -275,8 +277,10 @@ inline bool JSON_GetRequired(const T& data, typename T::StringRefType member,
return true;
}
- g_jsonErrorCallback("%s: field \"%s\" is of type %s, but accessor expected type %s.\n",
- __FUNCTION__, member.s, JSON_TypeToString(it->value), JSON_TypeToString(type));
+ if (g_jsonErrorCallback)
+ g_jsonErrorCallback("%s: field \"%s\" is of type %s, but accessor expected type %s.\n",
+ __FUNCTION__, member.s, JSON_TypeToString(it->value), JSON_TypeToString(type));
+
return false;
}
@@ -462,7 +466,9 @@ inline bool JSON_ParseNumberRequired(const T& data, typename T::StringRefType me
return true;
}
- g_jsonErrorCallback("%s: unable to parse field \"%s\".\n", __FUNCTION__, member.s);
+ if (g_jsonErrorCallback)
+ g_jsonErrorCallback("%s: unable to parse field \"%s\".\n", __FUNCTION__, member.s);
+
return false;
}