diff --git a/CMakeLists.txt b/CMakeLists.txt index 633d57455..2b4093cdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,6 +314,9 @@ endif() # Build Thyme add_subdirectory(src) +# Build Game Model Converter +add_subdirectory(src/tools/gamemodelconverter) + if(STANDALONE) list(APPEND INSTALL_TARGETS thyme) endif() diff --git a/src/platform/standardfile.cpp b/src/platform/standardfile.cpp index 07523eb47..f425fa079 100644 --- a/src/platform/standardfile.cpp +++ b/src/platform/standardfile.cpp @@ -152,6 +152,11 @@ int StandardFile::Seek(int offset, File::SeekMode mode) return fseek(m_file, (long)offset, fmode); } +int StandardFile::Position() +{ + return ftell(m_file); +} + void StandardFile::Next_Line(char *dst, int bytes) { captainslog_trace("Seeking getting next line from StandardFile %s.", m_name.Str()); diff --git a/src/platform/standardfile.h b/src/platform/standardfile.h index ccf36cd0f..67ff7398f 100644 --- a/src/platform/standardfile.h +++ b/src/platform/standardfile.h @@ -42,6 +42,7 @@ class StandardFile : public LocalFile virtual int Read(void *dst, int bytes) override; virtual int Write(void const *src, int bytes) override; virtual int Seek(int offset, File::SeekMode mode) override; + virtual int Position() override; virtual void Next_Line(char *dst, int bytes) override; virtual bool Scan_Int(int &integer) override; virtual bool Scan_Real(float &real) override; diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 9e1531eab..a33a2c89f 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -12,3 +12,4 @@ if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Windows") endif() add_subdirectory(gametextcompiler) +add_subdirectory(gamemodelconverter) diff --git a/src/tools/gamemodelconverter/CMakeLists.txt b/src/tools/gamemodelconverter/CMakeLists.txt new file mode 100644 index 000000000..8b148099f --- /dev/null +++ b/src/tools/gamemodelconverter/CMakeLists.txt @@ -0,0 +1,22 @@ +set(CMAKE_CXX_STANDARD 17) + +set(GAMEMODELCONVERTER_SOURCES + src/chunksmanager.cpp + src/chunksmanager.h + src/chunkfactory.cpp + src/gamemodelcommon.h + src/gamemodelfile.cpp + src/gamemodelfile.h + src/commands.cpp + src/commands.h + src/log.cpp + src/log.h + src/main.cpp + src/processor.cpp + src/processor.h +) + +set(GAMEMODELCONVERTER_INCLUDES src) +add_executable(GameModelConverter ${GAMEMODELCONVERTER_SOURCES}) +target_include_directories(GameModelConverter PRIVATE ${GAMEMODELCONVERTER_INCLUDES}) +target_link_libraries(GameModelConverter PRIVATE thyme_lib) diff --git a/src/tools/gamemodelconverter/src/chunkfactory.cpp b/src/tools/gamemodelconverter/src/chunkfactory.cpp new file mode 100644 index 000000000..41d67bb7d --- /dev/null +++ b/src/tools/gamemodelconverter/src/chunkfactory.cpp @@ -0,0 +1,1837 @@ +#pragma clang diagnostic push +#pragma ide diagnostic ignored "modernize-use-auto" +#include "chunksmanager.h" +#include +#include + +// Helper Functions + +char *ReadChunkRaw(ChunkLoadClass &cload) +{ + if (!cload.Cur_Chunk_Length()) { + char *c = new char[1]; + c[0] = 0; + return c; + } + char *c = new char[cload.Cur_Chunk_Length()]; + size_t bytes = cload.Read(c, cload.Cur_Chunk_Length()); + captainslog_dbgassert(cload.Cur_Chunk_Length() == bytes, "Read chunk size does not match data size"); + return c; +} + +void WriteChunkRaw(ChunkSaveClass &csave, unsigned id, const void *data, unsigned dataSize) +{ + csave.Begin_Chunk(id); + size_t bytes = csave.Write(data, dataSize); + captainslog_dbgassert(dataSize == bytes, "Write chunk size does not match data size"); + csave.End_Chunk(); +} + +void AddData(ChunkTreePtr &data, const char *name, const StringClass &type, const StringClass &formattedValue, void *value) +{ + if (data->data->info == nullptr) { + data->data->info = std::move(ChunkManager::createChunkInfo(name, type, formattedValue, value)); + } else { + captainslog_error("ChunkInfo %s already exists, cannot set %s", (const char *)data->data->info->name, name); + } +} + +void AddString(ChunkTreePtr &data, const char *name, const StringClass &string, const StringClass &type) +{ + AddData(data, name, type, string, (void *)&string); +} + +void AddInt32(ChunkTreePtr &data, const char *name, uint32_t value) +{ + char c[256]; + sprintf(c, "%u", value); + AddData(data, name, "uint32_t", c, (void *)&value); +} + +void AddInt8(ChunkTreePtr &data, const char *name, uint8_t value) +{ + char c[256]; + sprintf(c, "%hhu", value); + AddData(data, name, "uint8_t", c, (void *)&value); +} + +void AddFloat(ChunkTreePtr &data, const char *name, float value) +{ + char c[256]; + sprintf(c, "%f", value); + AddData(data, name, "float", c, (void *)&value); +} + +void AddVector(ChunkTreePtr &data, const char *name, const W3dVectorStruct *value) +{ + char c[256]; + sprintf(c, "%f %f %f", value->x, value->y, value->z); + AddData(data, name, "W3dVectorStruct", c, (void *)value); +} + +// Macro for Defining Functions +// READ_WRITE_CHUNK_STRING +template void Read_Chunk_String(ChunkLoadClass &cload, ChunkTreePtr &data, const char *id) +{ + char* rawData = ReadChunkRaw(cload); + if (strlen(rawData) == 0) { // case of null terminator + for (unsigned i = 0; i < data->data->chunkSize; i++) { + captainslog_dbgassert(rawData[i] == '\0', "Chunk %s size does not match data size", id); + } + } else { + captainslog_dbgassert(data->data->chunkSize == strlen(rawData) + 1, "Chunk %s size does not match data size", id); + } + AddData(data, id, "string", rawData, (void *)rawData); +} + +template void Write_Chunk_String(ChunkSaveClass &csave, ChunkTreePtr &data, unsigned id) +{ + char* rawData = (char*)data->data->info->value; + if (strlen(rawData) == 0) { // case of null terminator + for (unsigned i = 0; i < data->data->chunkSize; i++) { + captainslog_dbgassert(rawData[i] == '\0', "Chunk %s size does not match data size", id); + } + } else { + captainslog_dbgassert(data->data->chunkSize == strlen(rawData) + 1, "Chunk %s size does not match data size", id); + } + WriteChunkRaw(csave, id, rawData, data->data->chunkSize); +} +#define READ_WRITE_CHUNK_STRING(id) \ + void Read_##id(ChunkLoadClass &cload, ChunkTreePtr &data) { Read_Chunk_String(cload, data, #id); } \ + void Write_##id(ChunkSaveClass &csave, ChunkTreePtr &data) { Write_Chunk_String(csave, data, id); } \ + +// READ_WRITE_CHUNK +template void Read_Chunk_Struct(ChunkLoadClass &cload, ChunkTreePtr &data, const char *id) +{ + char* rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(T), "Chunk %s size does not match data size", id); + AddData(data, id, typeid(T).name(), "", (void *)rawData); +} + +template void Write_Chunk_Struct(ChunkSaveClass &csave, ChunkTreePtr &data, unsigned id) +{ + char *rawData = (char *)data->data->info->value; + captainslog_dbgassert(data->data->chunkSize == sizeof(T), "Chunk %X size does not match data size", id); + WriteChunkRaw(csave, id, rawData, sizeof(T)); +} +#define READ_WRITE_CHUNK(id, type) \ + void Read_##id(ChunkLoadClass &cload, ChunkTreePtr &data) { Read_Chunk_Struct(cload, data, #id); } \ + void Write_##id(ChunkSaveClass &csave, ChunkTreePtr &data) { Write_Chunk_Struct(csave, data, id); } \ + +// READ_WRITE_CHUNK_ARRAY +template void Read_Chunk_Array(ChunkLoadClass &cload, ChunkTreePtr &data, const char *id) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize % sizeof(T) == 0, "Chunk %s size does not match data size", id); + std::vector *arrayData = new std::vector(); + unsigned numElements = data->data->chunkSize / sizeof(T); + + for (unsigned i = 0; i < numElements; i++) { + arrayData->push_back(*reinterpret_cast(rawData + i * sizeof(T))); + } + + AddData(data, id, (std::string(typeid(T).name()) + "[]").c_str(), "", (void *)arrayData); + delete [] rawData; +} + +template void Write_Chunk_Array(ChunkSaveClass &csave, ChunkTreePtr &data, unsigned id) +{ + std::vector* arrayData = reinterpret_cast*>(data->data->info->value); + captainslog_dbgassert(data->data->chunkSize % sizeof(T) == 0, "Chunk %X size does not match data size", id); + unsigned numElements = data->data->chunkSize / sizeof(T); + char *chunkData = new char[numElements * sizeof(T)]; + for (unsigned i = 0; i < numElements; i++) { + memcpy(chunkData + i * sizeof(T), &(*arrayData)[i], sizeof(T)); + } + WriteChunkRaw(csave, id, chunkData, numElements * sizeof(T)); + + delete[] chunkData; +} +#define READ_WRITE_CHUNK_ARRAY(id, type) \ + void Read_##id(ChunkLoadClass &cload, ChunkTreePtr &data) { Read_Chunk_Array(cload, data, #id); } \ + void Write_##id(ChunkSaveClass &csave, ChunkTreePtr &data) { Write_Chunk_Array(csave, data, id); } \ + +// READ_WRITE_SUBCHUNKS +template void Read_Sub_Chunks(ChunkLoadClass &cload, ChunkTreePtr &data, const char *id) +{ + AddData(data, id, "StringClass", id, nullptr); + ChunkManager::ReadSubChunks(cload, data); +} + +template void Write_Sub_Chunks(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + ChunkManager::WriteSubChunks(csave, data); +} +#define READ_WRITE_SUBCHUNKS(id) \ + void Read_##id(ChunkLoadClass &cload, ChunkTreePtr &data) { Read_Sub_Chunks(cload, data, #id); } \ + void Write_##id(ChunkSaveClass &csave, ChunkTreePtr &data) { Write_Sub_Chunks(csave, data); } \ + +// Read and Write Functions +READ_WRITE_CHUNK_ARRAY(O_W3D_CHUNK_MATERIALS, W3dMaterialStruct) + +READ_WRITE_CHUNK_ARRAY(O_W3D_CHUNK_MATERIALS2, W3dMaterial2Struct) + +void Read_O_W3D_CHUNK_POV_QUADRANGLES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_POV_QUADRANGLES is unsupported"); + char *rawData = ReadChunkRaw(cload); + AddData(data, "O_W3D_CHUNK_POV_QUADRANGLES", "char", "string", (void *)rawData); +} + +void Write_O_W3D_CHUNK_POV_QUADRANGLES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_POV_QUADRANGLES is unsupported"); + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, O_W3D_CHUNK_POV_QUADRANGLES, rawData, data->data->chunkSize); +} + +void Read_O_W3D_CHUNK_POV_TRIANGLES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_POV_TRIANGLES is unsupported"); + char *rawData = ReadChunkRaw(cload); + AddData(data, "O_W3D_CHUNK_POV_TRIANGLES", "char", "string", (void *)rawData); +} + +void Write_O_W3D_CHUNK_POV_TRIANGLES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_POV_TRIANGLES is unsupported"); + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, O_W3D_CHUNK_POV_TRIANGLES, rawData, data->data->chunkSize); +} + +void Read_O_W3D_CHUNK_QUADRANGLES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_QUADRANGLES is outdated"); + char *rawData = ReadChunkRaw(cload); + AddData(data, "O_W3D_CHUNK_QUADRANGLES", "char", "string", (void *)rawData); +} + +void Write_O_W3D_CHUNK_QUADRANGLES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_QUADRANGLES is outdated"); + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, O_W3D_CHUNK_QUADRANGLES, rawData, data->data->chunkSize); +} + +READ_WRITE_CHUNK_ARRAY(O_W3D_CHUNK_SURRENDER_TRIANGLES, W3dSurrenderTriangleStruct) + +void Read_O_W3D_CHUNK_TRIANGLES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_TRIANGLES is obsoleted"); + char *rawData = ReadChunkRaw(cload); + AddData(data, "O_W3D_CHUNK_TRIANGLES", "char", "string", (void *)rawData); +} + +void Write_O_W3D_CHUNK_TRIANGLES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + captainslog_warn("O_W3D_CHUNK_TRIANGLES is obsoleted"); + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, O_W3D_CHUNK_TRIANGLES, rawData, data->data->chunkSize); +} + +READ_WRITE_CHUNK(OBSOLETE_W3D_CHUNK_EMITTER_COLOR_KEYFRAME, W3dEmitterColorKeyframeStruct) + +READ_WRITE_CHUNK(OBSOLETE_W3D_CHUNK_EMITTER_OPACITY_KEYFRAME, W3dEmitterOpacityKeyframeStruct) + +READ_WRITE_CHUNK(OBSOLETE_W3D_CHUNK_EMITTER_SIZE_KEYFRAME, W3dEmitterSizeKeyframeStruct) + +READ_WRITE_CHUNK(OBSOLETE_W3D_CHUNK_SHADOW_NODE, W3dHModelNodeStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_AABTREE) + +READ_WRITE_CHUNK(W3D_CHUNK_AABTREE_HEADER, W3dMeshAABTreeHeader) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_AABTREE_NODES, W3dMeshAABTreeNode) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_AABTREE_POLYINDICES, uint32_t) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_AGGREGATE) + +READ_WRITE_CHUNK(W3D_CHUNK_AGGREGATE_CLASS_INFO, W3dAggregateMiscInfo) + +READ_WRITE_CHUNK(W3D_CHUNK_AGGREGATE_HEADER, W3dAggregateHeaderStruct) + +void Read_W3D_CHUNK_AGGREGATE_INFO(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dAggregateInfoStruct *info = (W3dAggregateInfoStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dAggregateInfoStruct) + info->SubobjectCount * sizeof(W3dAggregateSubobjectStruct), + "Chunk W3D_CHUNK_AGGREGATE_INFO size does not match expected size for AggregateInfo structure"); + AddData(data, "W3D_CHUNK_AGGREGATE_INFO", "W3dAggregateInfoStruct[][]", "Obsolete", (void *)rawData); +} + +void Write_W3D_CHUNK_AGGREGATE_INFO(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_AGGREGATE_INFO, rawData, data->data->chunkSize); +} + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_ANIMATION) + +void Read_W3D_CHUNK_ANIMATION_CHANNEL(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dAnimChannelStruct *channel = (W3dAnimChannelStruct *)rawData; + // TODO padding at the end of the struct if the length of data array int not fully used + // captainslog_dbgassert(data->data->chunkSize == sizeof(W3dAnimChannelStruct) + + // (channel->LastFrame - channel->FirstFrame) * channel->VectorLen * sizeof(float), + // "Chunk W3D_CHUNK_ANIMATION_CHANNEL size does not match data size"); + if (channel->Flags > ANIM_CHANNEL_VIS) { + StringClass str; + str.Format("W3D_CHUNK_ANIMATION_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_ANIMATION_CHANNEL", "W3dAnimChannelStruct", "", (void *)rawData); +} + +void Write_W3D_CHUNK_ANIMATION_CHANNEL(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dAnimChannelStruct *channel = (W3dAnimChannelStruct *)data->data->info->value; + if (channel->Flags > ANIM_CHANNEL_VIS) { + StringClass str; + str.Format("W3D_CHUNK_ANIMATION_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + WriteChunkRaw(csave, W3D_CHUNK_ANIMATION_CHANNEL, channel, data->data->chunkSize); +} + +READ_WRITE_CHUNK(W3D_CHUNK_ANIMATION_HEADER, W3dAnimHeaderStruct) + +void Read_W3D_CHUNK_BIT_CHANNEL(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dBitChannelStruct *channel = (W3dBitChannelStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dBitChannelStruct) + + ((channel->LastFrame - channel->FirstFrame) / 8) * sizeof(uint8_t), // 8 is the number of bits in a byte + "Chunk W3D_CHUNK_BIT_CHANNEL size does not match data size"); + if (channel->Flags > BIT_CHANNEL_TIMECODED_VIS) { + StringClass str; + str.Format("W3D_CHUNK_BIT_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_BIT_CHANNEL", "W3dBitChannelStruct[]", "", (void *)channel); +} +void Write_W3D_CHUNK_BIT_CHANNEL(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dBitChannelStruct *channel = (W3dBitChannelStruct *)data->data->info->value; + if (channel->Flags > BIT_CHANNEL_TIMECODED_VIS) { + StringClass str; + str.Format("W3D_CHUNK_BIT_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + WriteChunkRaw(csave, W3D_CHUNK_BIT_CHANNEL, channel, data->data->chunkSize); +} + +void Read_W3D_CHUNK_BOX(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dBoxStruct), "Chunk W3D_CHUNK_BOX size does not match data size"); + W3dBoxStruct *box = (W3dBoxStruct *)rawData; + + if (box->Attributes & 4) { + StringClass str; + str.Format("W3D_CHUNK_BOX Unknown Attribute 0x00000004"); + captainslog_warn((const char *)str); + } + + if (box->Attributes & 8) { + StringClass str; + str.Format("W3D_CHUNK_BOX Unknown Attribute 0x00000008"); + captainslog_warn((const char *)str); + } + + if (box->Attributes & 0xFFFFFE00) { + StringClass str; + str.Format("W3D_CHUNK_BOX Unknown Attributes %x", box->Attributes & 0xFFFFFE00); + captainslog_warn((const char *)str); + } + + AddData(data, "W3D_CHUNK_BOX", "W3dBoxStruct", "", (void *)rawData); +} + +void Write_W3D_CHUNK_BOX(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dBoxStruct *box = (W3dBoxStruct *)data->data->info->value; + if (box->Attributes & 4) { + StringClass str; + str.Format("W3D_CHUNK_BOX Unknown Attribute 0x00000004"); + captainslog_warn((const char *)str); + } + + if (box->Attributes & 8) { + StringClass str; + str.Format("W3D_CHUNK_BOX Unknown Attribute 0x00000008"); + captainslog_warn((const char *)str); + } + + if (box->Attributes & 0xFFFFFE00) { + StringClass str; + str.Format("W3D_CHUNK_BOX Unknown Attributes %x", box->Attributes & 0xFFFFFE00); + captainslog_warn((const char *)str); + } + + WriteChunkRaw(csave, W3D_CHUNK_BOX, box, sizeof(W3dBoxStruct)); +} + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_COLLECTION) + +READ_WRITE_CHUNK(W3D_CHUNK_COLLECTION_HEADER, W3dCollectionHeaderStruct) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_COLLECTION_OBJ_NAME) + +READ_WRITE_CHUNK(W3D_CHUNK_COLLISION_NODE, W3dHModelNodeStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_DAMAGE) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_DAMAGE_COLORS, W3dDamageColorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_DAMAGE_HEADER, W3dDamageStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_DAMAGE_VERTICES, W3dDamageVertexStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_DAZZLE) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_DAZZLE_NAME) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_DAZZLE_TYPENAME) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_DCG, W3dRGBAStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_DIG, W3dRGBStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_EMITTER) + +void Read_W3D_CHUNK_EMITTER_BLUR_TIME_KEYFRAMES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dEmitterBlurTimeHeaderStruct *header = (W3dEmitterBlurTimeHeaderStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dEmitterBlurTimeHeaderStruct) + + (header->KeyframeCount + 1) * sizeof(W3dEmitterBlurTimeKeyframeStruct), + "Chunk W3D_CHUNK_EMITTER_BLUR_TIME_KEYFRAMES size does not match data size"); + + AddData(data, "W3D_CHUNK_EMITTER_BLUR_TIME_KEYFRAMES", "W3dEmitterBlurTimeHeaderStruct[][]", "", (void *)rawData); +} + +void Write_W3D_CHUNK_EMITTER_BLUR_TIME_KEYFRAMES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_EMITTER_BLUR_TIME_KEYFRAMES, rawData, data->data->chunkSize); +} + +void Read_W3D_CHUNK_EMITTER_FRAME_KEYFRAMES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dEmitterFrameHeaderStruct *header = (W3dEmitterFrameHeaderStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dEmitterFrameHeaderStruct) + + (header->KeyframeCount + 1) * sizeof(W3dEmitterFrameKeyframeStruct), + "Chunk W3D_CHUNK_EMITTER_FRAME_KEYFRAMES size does not match data size"); + + AddData(data, "W3D_CHUNK_EMITTER_FRAME_KEYFRAMES", "W3dEmitterFrameHeaderStruct[][]", "", (void *)rawData); +} + +void Write_W3D_CHUNK_EMITTER_FRAME_KEYFRAMES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_EMITTER_FRAME_KEYFRAMES, rawData, data->data->chunkSize); +} + +READ_WRITE_CHUNK(W3D_CHUNK_EMITTER_HEADER, W3dEmitterHeaderStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_EMITTER_INFO, W3dEmitterInfoStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_EMITTER_INFOV2, W3dEmitterInfoStructV2) + +void Read_W3D_CHUNK_EMITTER_PROPS(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dEmitterPropertyStruct *props = (W3dEmitterPropertyStruct *)rawData; + + W3dEmitterColorKeyframeStruct *color = (W3dEmitterColorKeyframeStruct *)(rawData + sizeof(W3dEmitterPropertyStruct)); + + W3dEmitterOpacityKeyframeStruct *opacity = (W3dEmitterOpacityKeyframeStruct *)(rawData + + sizeof(W3dEmitterPropertyStruct) + (props->ColorKeyframes * sizeof(W3dEmitterColorKeyframeStruct))); + + W3dEmitterSizeKeyframeStruct *size = (W3dEmitterSizeKeyframeStruct *)(rawData + sizeof(W3dEmitterPropertyStruct) + + (props->ColorKeyframes * sizeof(W3dEmitterColorKeyframeStruct)) + + (props->OpacityKeyframes * sizeof(W3dEmitterOpacityKeyframeStruct))); + captainslog_dbgassert(data->data->chunkSize == + sizeof(W3dEmitterPropertyStruct) + + (props->ColorKeyframes * sizeof(W3dEmitterColorKeyframeStruct)) + + (props->OpacityKeyframes * sizeof(W3dEmitterOpacityKeyframeStruct)) + + (props->SizeKeyframes * sizeof(W3dEmitterSizeKeyframeStruct)), + "Chunk W3D_CHUNK_EMITTER_PROPS size does not match data size"); + + + AddData(data, "W3D_CHUNK_EMITTER_PROPS", "W3dEmitterPropertyStruct[][][][]", "", (void *)rawData); +} + +void Write_W3D_CHUNK_EMITTER_PROPS(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_EMITTER_PROPS, rawData, data->data->chunkSize); +} + +void Read_W3D_CHUNK_EMITTER_ROTATION_KEYFRAMES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dEmitterRotationHeaderStruct *header = (W3dEmitterRotationHeaderStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dEmitterRotationHeaderStruct) + + (header->KeyframeCount + 1) * sizeof(W3dEmitterRotationKeyframeStruct), + "Chunk W3D_CHUNK_EMITTER_ROTATION_KEYFRAMES size does not match data size"); + + AddData(data, "W3D_CHUNK_EMITTER_ROTATION_KEYFRAMES", "W3dEmitterRotationHeaderStruct[][]", "", (void *)rawData); +} + +void Write_W3D_CHUNK_EMITTER_ROTATION_KEYFRAMES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_EMITTER_ROTATION_KEYFRAMES, rawData, data->data->chunkSize); +} + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_EMITTER_USER_DATA) + +READ_WRITE_CHUNK(W3D_CHUNK_FAR_ATTENUATION, W3dLightAttenuationStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_HIERARCHY) + +READ_WRITE_CHUNK(W3D_CHUNK_HIERARCHY_HEADER, W3dHierarchyStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_HLOD) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_HLOD_AGGREGATE_ARRAY) + +READ_WRITE_CHUNK(W3D_CHUNK_HLOD_HEADER, W3dHLodHeaderStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_HLOD_LOD_ARRAY) + +READ_WRITE_CHUNK(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, W3dHLodArrayHeaderStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_HLOD_PROXY_ARRAY) + +READ_WRITE_CHUNK(W3D_CHUNK_HLOD_SUB_OBJECT, W3dHLodSubObjectStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_HMODEL) + +READ_WRITE_CHUNK(OBSOLETE_W3D_CHUNK_HMODEL_AUX_DATA, W3dHModelAuxDataStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_HMODEL_HEADER, W3dHModelHeaderStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_LIGHT) + +void Read_W3D_CHUNK_LIGHT_INFO(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dLightStruct), "Chunk W3D_CHUNK_LIGHT_INFO size does not match data size"); + W3dLightStruct *light = (W3dLightStruct *)rawData; + + int type = light->Attributes & W3D_LIGHT_ATTRIBUTE_TYPE_MASK; + if (type != W3D_LIGHT_ATTRIBUTE_POINT && type != W3D_LIGHT_ATTRIBUTE_SPOT && type != W3D_LIGHT_ATTRIBUTE_DIRECTIONAL) { + StringClass str; + str.Format("W3D_CHUNK_LIGHT_INFO Unknown Light Type %x", type); + captainslog_warn((const char *)str); + } + + if (light->Attributes & 0xFFFFFE00) { + StringClass str; + str.Format("W3D_CHUNK_LIGHT_INFO Unknown Light Flags %x", light->Attributes & 0xFFFFFE00); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_LIGHT_INFO", "W3dLightStruct", "", (void *)rawData); +} + +void Write_W3D_CHUNK_LIGHT_INFO(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dLightStruct *light = (W3dLightStruct *)data->data->info->value; + int type = light->Attributes & W3D_LIGHT_ATTRIBUTE_TYPE_MASK; + if (type != W3D_LIGHT_ATTRIBUTE_POINT && type != W3D_LIGHT_ATTRIBUTE_SPOT && type != W3D_LIGHT_ATTRIBUTE_DIRECTIONAL) { + StringClass str; + str.Format("W3D_CHUNK_LIGHT_INFO Unknown Light Type %x", type); + captainslog_warn((const char *)str); + } + + if (light->Attributes & 0xFFFFFE00) { + StringClass str; + str.Format("W3D_CHUNK_LIGHT_INFO Unknown Light Flags %x", light->Attributes & 0xFFFFFE00); + captainslog_warn((const char *)str); + } + + WriteChunkRaw(csave, W3D_CHUNK_LIGHT_INFO, light, sizeof(W3dLightStruct)); +} + +READ_WRITE_CHUNK(W3D_CHUNK_LIGHT_TRANSFORM, W3dLightTransformStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_LIGHTSCAPE) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_LIGHTSCAPE_LIGHT) + +READ_WRITE_CHUNK(W3D_CHUNK_LOD, W3dLODStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_LODMODEL) + +READ_WRITE_CHUNK(W3D_CHUNK_LODMODEL_HEADER, W3dLODModelHeaderStruct) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_MAP3_FILENAME) + +READ_WRITE_CHUNK(W3D_CHUNK_MAP3_INFO, W3dMap3Struct) + +READ_WRITE_CHUNK(W3D_CHUNK_MATERIAL_INFO, W3dMaterialInfoStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIAL_PASS) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIAL3) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIAL3_DC_MAP) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIAL3_DI_MAP) + +READ_WRITE_CHUNK(W3D_CHUNK_MATERIAL3_INFO, W3dMaterial3Struct) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_MATERIAL3_NAME) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIAL3_SC_MAP) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIAL3_SI_MAP) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MATERIALS3) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MESH) + +READ_WRITE_CHUNK(W3D_CHUNK_MESH_HEADER, W3dMeshHeaderStruct) + +void Read_W3D_CHUNK_MESH_HEADER3(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dMeshHeader3Struct), "Chunk W3D_CHUNK_MESH_HEADER3 size does not match data size"); + W3dMeshHeader3Struct *header = (W3dMeshHeader3Struct *)rawData; + int type = header->Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK; + if (type != W3D_MESH_FLAG_GEOMETRY_TYPE_NORMAL && type != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ALIGNED + && type != W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN && type != OBSOLETE_W3D_MESH_FLAG_GEOMETRY_TYPE_SHADOW + && type != W3D_MESH_FLAG_GEOMETRY_TYPE_AABOX && type != W3D_MESH_FLAG_GEOMETRY_TYPE_OBBOX + && type != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ORIENTED && type != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_Z_ORIENTED) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Mesh Type %x", type); + captainslog_warn((const char *)str); + } + + if (header->Attributes & 0x00000800) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Attribute 0x00000800"); + captainslog_warn((const char *)str); + } + + if (header->Attributes & W3D_MESH_FLAG_PRELIT_MASK) { + if (!header->PrelitVersion) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown value of Attribute PrelitVersion: %x", header->PrelitVersion); + captainslog_warn((const char *)str); + } + } else { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 N/A PrelitVersion"); +// captainslog_warn((const char *)str); TODO is needed? + } + + if (header->VertexChannels & 0xFFFFFF00) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Vertex Channels %x", header->VertexChannels); + captainslog_warn((const char *)str); + } + + if (header->FaceChannels & 0xFFFFFFFE) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Face Channels %x", header->FaceChannels); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_MESH_HEADER3", "W3dMeshHeader3Struct", "", (void *)header); +} + +void Write_W3D_CHUNK_MESH_HEADER3(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dMeshHeader3Struct *header = (W3dMeshHeader3Struct *)data->data->info->value; + int type = header->Attributes & W3D_MESH_FLAG_GEOMETRY_TYPE_MASK; + + if (type != W3D_MESH_FLAG_GEOMETRY_TYPE_NORMAL && type != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ALIGNED + && type != W3D_MESH_FLAG_GEOMETRY_TYPE_SKIN && type != OBSOLETE_W3D_MESH_FLAG_GEOMETRY_TYPE_SHADOW + && type != W3D_MESH_FLAG_GEOMETRY_TYPE_AABOX && type != W3D_MESH_FLAG_GEOMETRY_TYPE_OBBOX + && type != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_ORIENTED && type != W3D_MESH_FLAG_GEOMETRY_TYPE_CAMERA_Z_ORIENTED) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Mesh Type %x", type); + captainslog_warn((const char *)str); + } + + if (header->Attributes & 0x00000800) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Attribute 0x00000800"); + captainslog_warn((const char *)str); + } + + if (header->Attributes & W3D_MESH_FLAG_PRELIT_MASK) { + if (!header->PrelitVersion) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown value of Attribute PrelitVersion: %x", header->PrelitVersion); + captainslog_warn((const char *)str); + } + } else { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 N/A PrelitVersion"); +// captainslog_warn((const char *)str); TODO is needed? + } + + if (header->VertexChannels & 0xFFFFFF00) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Vertex Channels %x", header->VertexChannels); + captainslog_warn((const char *)str); + } + + if (header->FaceChannels & 0xFFFFFFFE) { + StringClass str; + str.Format("W3D_CHUNK_MESH_HEADER3 Unknown Face Channels %x", header->FaceChannels); + captainslog_warn((const char *)str); + } + + WriteChunkRaw(csave, W3D_CHUNK_MESH_HEADER3, header, sizeof(W3dMeshHeader3Struct)); +} + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_MESH_USER_TEXT) + +READ_WRITE_CHUNK(W3D_CHUNK_NEAR_ATTENUATION, W3dLightAttenuationStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_NODE, W3dHModelNodeStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_NULL_OBJECT, W3dNullObjectStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_PER_FACE_TEXCOORD_IDS, Vector3i) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_PER_TRI_MATERIALS, uint16_t) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_PIVOT_FIXUPS, W3dPivotFixupStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_PIVOTS, W3dPivotStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_PLACEHOLDER, W3dPlaceholderStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_POINTS, W3dVectorStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_PRELIT_LIGHTMAP_MULTI_PASS) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_PRELIT_LIGHTMAP_MULTI_TEXTURE) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_PRELIT_UNLIT) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_PRELIT_VERTEX) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_PS2_SHADERS, W3dPS2ShaderStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SCG, W3dRGBStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHADER_IDS, uint32_t) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHADERS, W3dShaderStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_SKIN_NODE, W3dHModelNodeStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_SPOT_LIGHT_INFO, W3dSpotLightStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_SPOT_LIGHT_INFO_5_0, W3dSpotLightStruct_v5_0) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_STAGE_TEXCOORDS, W3dTexCoordStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SURRENDER_NORMALS, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_TEXCOORDS, W3dTexCoordStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_TEXTURE) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_TEXTURE_IDS, uint32_t) + +void Read_W3D_CHUNK_TEXTURE_INFO(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dTextureInfoStruct), "Chunk W3D_CHUNK_TEXTURE_INFO size does not match data size"); + W3dTextureInfoStruct *info = (W3dTextureInfoStruct *)rawData; + + int hint = info->Attributes & 0xF00; + + if (hint != W3DTEXTURE_HINT_BASE && hint != W3DTEXTURE_HINT_EMISSIVE && hint != W3DTEXTURE_HINT_ENVIRONMENT + && hint != W3DTEXTURE_HINT_SHINY_MASK) { + StringClass str; + str.Format("W3D_CHUNK_TEXTURE_INFO Unknown Hints %x", hint); + captainslog_warn((const char *)str); + } + + if (info->Attributes & 0xE000) { + StringClass str; + str.Format("W3D_CHUNK_TEXTURE_INFO Unknown Flags %x", info->Attributes & 0xE000); + captainslog_warn((const char *)str); + } + + if (info->AnimType != W3DTEXTURE_ANIM_LOOP && info->AnimType != W3DTEXTURE_ANIM_PINGPONG + && info->AnimType != W3DTEXTURE_ANIM_ONCE && info->AnimType != W3DTEXTURE_ANIM_MANUAL) { + StringClass str; + str.Format("W3D_CHUNK_TEXTURE_INFO Unknown Anim Type %x", info->AnimType); + captainslog_warn((const char *)str); + } + + AddData(data, "W3D_CHUNK_TEXTURE_INFO", "W3dTextureInfoStruct", "", (void *)info); +} + +void Write_W3D_CHUNK_TEXTURE_INFO(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dTextureInfoStruct *info = (W3dTextureInfoStruct *)data->data->info->value; + int hint = info->Attributes & 0xF00; + + if (hint != W3DTEXTURE_HINT_BASE && hint != W3DTEXTURE_HINT_EMISSIVE && hint != W3DTEXTURE_HINT_ENVIRONMENT + && hint != W3DTEXTURE_HINT_SHINY_MASK) { + StringClass str; + str.Format("W3D_CHUNK_TEXTURE_INFO Unknown Hints %x", hint); + captainslog_warn((const char *)str); + } + + if (info->Attributes & 0xE000) { + StringClass str; + str.Format("W3D_CHUNK_TEXTURE_INFO Unknown Flags %x", info->Attributes & 0xE000); + captainslog_warn((const char *)str); + } + + if (info->AnimType != W3DTEXTURE_ANIM_LOOP && info->AnimType != W3DTEXTURE_ANIM_PINGPONG + && info->AnimType != W3DTEXTURE_ANIM_ONCE && info->AnimType != W3DTEXTURE_ANIM_MANUAL) { + StringClass str; + str.Format("W3D_CHUNK_TEXTURE_INFO Unknown Anim Type %x", info->AnimType); + captainslog_warn((const char *)str); + } + + WriteChunkRaw(csave, W3D_CHUNK_TEXTURE_INFO, info, sizeof(W3dTextureInfoStruct)); +} + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_TEXTURE_NAME) + +void Read_W3D_CHUNK_TEXTURE_REPLACER_INFO(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dTextureReplacerHeaderStruct *header = (W3dTextureReplacerHeaderStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dTextureReplacerHeaderStruct) + + header->ReplacedTexturesCount * sizeof(W3dTextureReplacerStruct), + "Chunk W3D_CHUNK_TEXTURE_REPLACER_INFO size does not match data size"); + + AddData(data, "W3D_CHUNK_TEXTURE_REPLACER_INFO", "W3dTextureReplacerHeaderStruct[][]", "", (void *)rawData); +} + +void Write_W3D_CHUNK_TEXTURE_REPLACER_INFO(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_TEXTURE_REPLACER_INFO, rawData, data->data->chunkSize); +} + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_TEXTURE_STAGE) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_TEXTURES) + +READ_WRITE_CHUNK(W3D_CHUNK_TRANSFORM_NODE, W3dPlaceholderStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_TRIANGLES, W3dTriStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_VERTEX_COLORS, W3dRGBStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_VERTEX_INFLUENCES, W3dVertInfStruct) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_VERTEX_MAPPER_ARGS0) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_VERTEX_MAPPER_ARGS1) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_VERTEX_MATERIAL) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_VERTEX_MATERIAL_IDS, uint32_t) + +void Read_W3D_CHUNK_VERTEX_MATERIAL_INFO(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dVertexMaterialStruct), + "Chunk W3D_CHUNK_VERTEX_MATERIAL_INFO size does not match data size"); + W3dVertexMaterialStruct *material = (W3dVertexMaterialStruct *)rawData; + + if (material->Attributes & 0x00000010) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000010"); + captainslog_warn((const char *)str); + } + + if (material->Attributes & 0x00000020) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000020"); + captainslog_warn((const char *)str); + } + + if (material->Attributes & 0x00000040) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000040"); + captainslog_warn((const char *)str); + } + + if (material->Attributes & 0x00000080) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000080"); + captainslog_warn((const char *)str); + } + + if ((material->Attributes & W3DVERTMAT_STAGE0_MAPPING_MASK) > W3DVERTMAT_STAGE0_MAPPING_GRID_WS_ENVIRONMENT) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Stage 0 Mapper %x", + material->Attributes & W3DVERTMAT_STAGE0_MAPPING_MASK); + captainslog_warn((const char *)str); + } + + if ((material->Attributes & W3DVERTMAT_STAGE1_MAPPING_MASK) > W3DVERTMAT_STAGE1_MAPPING_GRID_WS_ENVIRONMENT) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Stage 1 Mapper %x", + material->Attributes & W3DVERTMAT_STAGE1_MAPPING_MASK); + captainslog_warn((const char *)str); + } + + if (material->Attributes & W3DVERTMAT_PSX_MASK) { + if (material->Attributes & 0xF0000000) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown PSX material flag %x", material->Attributes & 0xF0000000); + captainslog_warn((const char *)str); + } + } + AddData(data, "W3D_CHUNK_VERTEX_MATERIAL_INFO", "W3dVertexMaterialStruct", "", (void *)material); +} + +void Write_W3D_CHUNK_VERTEX_MATERIAL_INFO(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dVertexMaterialStruct *material = (W3dVertexMaterialStruct *)data->data->info->value; + if (material->Attributes & 0x00000010) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000010"); + captainslog_warn((const char *)str); + } + + if (material->Attributes & 0x00000020) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000020"); + captainslog_warn((const char *)str); + } + + if (material->Attributes & 0x00000040) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000040"); + captainslog_warn((const char *)str); + } + + if (material->Attributes & 0x00000080) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Attribute 0x00000080"); + captainslog_warn((const char *)str); + } + + if ((material->Attributes & W3DVERTMAT_STAGE0_MAPPING_MASK) > W3DVERTMAT_STAGE0_MAPPING_GRID_WS_ENVIRONMENT) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Stage 0 Mapper %x", + material->Attributes & W3DVERTMAT_STAGE0_MAPPING_MASK); + captainslog_warn((const char *)str); + } + + if ((material->Attributes & W3DVERTMAT_STAGE1_MAPPING_MASK) > W3DVERTMAT_STAGE1_MAPPING_GRID_WS_ENVIRONMENT) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown Stage 1 Mapper %x", + material->Attributes & W3DVERTMAT_STAGE1_MAPPING_MASK); + captainslog_warn((const char *)str); + } + + if (material->Attributes & W3DVERTMAT_PSX_MASK) { + if (material->Attributes & 0xF0000000) { + StringClass str; + str.Format("W3D_CHUNK_VERTEX_MATERIAL_INFO Unknown PSX material flag %x", material->Attributes & 0xF0000000); + captainslog_warn((const char *)str); + } + } + + WriteChunkRaw(csave, W3D_CHUNK_VERTEX_MATERIAL_INFO, material, sizeof(W3dVertexMaterialStruct)); +} + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_VERTEX_MATERIAL_NAME) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_VERTEX_MATERIALS) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_VERTEX_NORMALS, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_VERTEX_SHADE_INDICES, uint32_t) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_VERTICES, W3dVectorStruct) + +void Read_W3D_CHUNK_EMITTER_LINE_PROPERTIES(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dEmitterLinePropertiesStruct), "Chunk W3D_CHUNK_EMITTER_LINE_PROPERTIES size does not match data size"); + W3dEmitterLinePropertiesStruct *props = (W3dEmitterLinePropertiesStruct *)rawData; + + if (props->Flags & 0x00FFFFF0) { + StringClass str; + str.Format("W3D_CHUNK_EMITTER_LINE_PROPERTIES Unknown Emitter Line Properties flags %x", props->Flags & 0x00FFFFF0); + captainslog_warn((const char *)str); + } + + size_t mapmode = props->Flags >> W3D_ELINE_TEXTURE_MAP_MODE_OFFSET; + if (mapmode != W3D_ELINE_UNIFORM_WIDTH_TEXTURE_MAP && mapmode != W3D_ELINE_UNIFORM_LENGTH_TEXTURE_MAP + && mapmode != W3D_ELINE_TILED_TEXTURE_MAP) { + StringClass str; + str.Format("W3D_CHUNK_EMITTER_LINE_PROPERTIES Unknown Emitter Mapping Mode %x", mapmode); + captainslog_warn((const char *)str); + } + + AddData(data, "W3D_CHUNK_EMITTER_LINE_PROPERTIES", "W3dEmitterLinePropertiesStruct", "", (void *)props); +} + +void Write_W3D_CHUNK_EMITTER_LINE_PROPERTIES(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dEmitterLinePropertiesStruct *props = (W3dEmitterLinePropertiesStruct *)data->data->info->value; + if (props->Flags & 0x00FFFFF0) { + StringClass str; + str.Format("W3D_CHUNK_EMITTER_LINE_PROPERTIES Unknown Emitter Line Properties flags %x", props->Flags & 0x00FFFFF0); + captainslog_warn((const char *)str); + } + + size_t mapmode = props->Flags >> W3D_ELINE_TEXTURE_MAP_MODE_OFFSET; + if (mapmode != W3D_ELINE_UNIFORM_WIDTH_TEXTURE_MAP && mapmode != W3D_ELINE_UNIFORM_LENGTH_TEXTURE_MAP + && mapmode != W3D_ELINE_TILED_TEXTURE_MAP) { + StringClass str; + str.Format("W3D_CHUNK_EMITTER_LINE_PROPERTIES Unknown Emitter Mapping Mode %x", mapmode); + captainslog_warn((const char *)str); + } + + WriteChunkRaw(csave, W3D_CHUNK_EMITTER_LINE_PROPERTIES, props, sizeof(W3dEmitterLinePropertiesStruct)); +} + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SECONDARY_VERTICES, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SECONDARY_VERTEX_NORMALS, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_TANGENTS, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_BINORMALS, W3dVectorStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_COMPRESSED_ANIMATION) + +int flavor = ANIM_FLAVOR_TIMECODED; + +void Read_W3D_CHUNK_COMPRESSED_ANIMATION_HEADER(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dCompressedAnimHeaderStruct), "Chunk W3D_CHUNK_COMPRESSED_ANIMATION_HEADER size does not match data size"); + W3dCompressedAnimHeaderStruct *header = (W3dCompressedAnimHeaderStruct *)rawData; + + if (header->Flavor >= ANIM_FLAVOR_VALID) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_HEADER Unknown Flavor Type %x", header->Flavor); + captainslog_warn((const char *)str); + } + + flavor = header->Flavor; + AddData(data, "W3D_CHUNK_COMPRESSED_ANIMATION_HEADER", "W3dCompressedAnimHeaderStruct", "", (void *)header); +} + +void Write_W3D_CHUNK_COMPRESSED_ANIMATION_HEADER(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dCompressedAnimHeaderStruct *header = (W3dCompressedAnimHeaderStruct *)data->data->info->value; + + if (header->Flavor >= ANIM_FLAVOR_VALID) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_HEADER Unknown Flavor Type %x", header->Flavor); + captainslog_warn((const char *)str); + } + + flavor = header->Flavor; + WriteChunkRaw(csave, W3D_CHUNK_COMPRESSED_ANIMATION_HEADER, header, sizeof(W3dCompressedAnimHeaderStruct)); +} + +void Read_W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + + if (flavor == ANIM_FLAVOR_TIMECODED) { + W3dTimeCodedAnimChannelStruct *channel = (W3dTimeCodedAnimChannelStruct *)rawData; + // TODO need check if need add '1' to the size + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dTimeCodedAnimChannelStruct) + + (((cload.Cur_Chunk_Length() - sizeof(W3dTimeCodedAnimChannelStruct)) >> 2) /*+ 1*/) * sizeof(int), + "Chunk W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL size does not match data size"); + + if (channel->Flags <= ANIM_CHANNEL_VIS) { + } else { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL", "W3dTimeCodedAnimChannelStruct", "", (void *)channel); + } else { + W3dAdaptiveDeltaAnimChannelStruct *channel = (W3dAdaptiveDeltaAnimChannelStruct *)rawData; + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dAdaptiveDeltaAnimChannelStruct) + + (((cload.Cur_Chunk_Length() - sizeof(W3dAdaptiveDeltaAnimChannelStruct)) >> 2) /*+ 1*/) * sizeof(int), + "Chunk W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL size does not match data size"); + + if (channel->Flags <= ANIM_CHANNEL_VIS) { + } else { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + + AddData(data, "W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL", "W3dAdaptiveDeltaAnimChannelStruct", "", (void *)channel); + } +} + +void Write_W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL, rawData, data->data->chunkSize); +} + +void Read_W3D_CHUNK_COMPRESSED_BIT_CHANNEL(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + W3dTimeCodedBitChannelStruct *channel = (W3dTimeCodedBitChannelStruct *)rawData; + // TODO need fix size check +// captainslog_dbgassert(data->data->chunkSize == sizeof(W3dTimeCodedBitChannelStruct) + channel->NumTimeCodes * sizeof(int), +// "Chunk W3D_CHUNK_COMPRESSED_BIT_CHANNEL size does not match data size"); + + if (channel->Flags > BIT_CHANNEL_TIMECODED_VIS) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_BIT_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_COMPRESSED_BIT_CHANNEL", "W3dTimeCodedBitChannelStruct", "", (void *)channel); + +} + +void Write_W3D_CHUNK_COMPRESSED_BIT_CHANNEL(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dTimeCodedBitChannelStruct *channel = (W3dTimeCodedBitChannelStruct *)data->data->info->value; + if (channel->Flags > BIT_CHANNEL_TIMECODED_VIS) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_BIT_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + WriteChunkRaw(csave, W3D_CHUNK_COMPRESSED_BIT_CHANNEL, channel, data->data->chunkSize); +} + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MORPH_ANIMATION) + +READ_WRITE_CHUNK(W3D_CHUNK_MORPHANIM_HEADER, W3dMorphAnimHeaderStruct) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_MORPHANIM_CHANNEL) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_MORPHANIM_POSENAME) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_MORPHANIM_KEYDATA, W3dMorphAnimKeyStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_MORPHANIM_PIVOTCHANNELDATA, uint32_t) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_SOUNDROBJ) + +READ_WRITE_CHUNK(W3D_CHUNK_SOUNDROBJ_HEADER, W3dSoundRObjHeaderStruct) + +#define READ_FLOAT(id, name) \ + case id: { \ + float f; \ + cload.Read(&f, sizeof(f)); \ + AddFloat(data, #name, f); \ + break; \ + } + +#define READ_INT(id, name) \ + case id: { \ + int i; \ + cload.Read(&i, sizeof(i)); \ + AddInt32(data, #name, i); \ + break; \ + } + +#define READ_BOOL(id, name) \ + case id: { \ + bool b; \ + cload.Read(&b, sizeof(b)); \ + AddInt8(data, #name, b); \ + break; \ + } + +#define READ_VECTOR(id, name) \ + case id: { \ + Vector3 v; \ + cload.Read(&v, sizeof(v)); \ + W3dVectorStruct v2; \ + v2.x = v.X; \ + v2.y = v.Y; \ + v2.z = v.Z; \ + AddVector(data, #name, &v2); \ + break; \ + } + +#define READ_STRING(id, name) \ + case id: { \ + StringClass str; \ + cload.Read(str.Get_Buffer(cload.Cur_Micro_Chunk_Length()), cload.Cur_Micro_Chunk_Length()); \ + AddString(data, #name, str, "String"); \ + break; \ + } + +void Read_W3D_CHUNK_SOUNDROBJ_DEFINITION(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + // TODO: Implement Read_W3D_CHUNK_SOUNDROBJ_DEFINITION + captainslog_warn("Read_W3D_CHUNK_SOUNDROBJ_DEFINITION not implemented"); + while (cload.Open_Chunk()) { + switch (cload.Cur_Chunk_ID()) { + case 0x100: + while (cload.Open_Micro_Chunk()) { + switch (cload.Cur_Micro_Chunk_ID()) { + READ_FLOAT(3, m_Priority); + READ_FLOAT(4, m_Volume); + READ_FLOAT(5, m_Pan); + READ_INT(6, m_LoopCount); + READ_FLOAT(7, m_DropoffRadius); + READ_FLOAT(8, m_MaxVolRadius); + READ_INT(9, m_Type); + READ_BOOL(10, m_Is3DSound); + READ_STRING(11, m_Filename); + READ_STRING(12, m_DisplayText); + READ_FLOAT(18, m_StartOffset); + READ_FLOAT(19, m_PitchFactor); + READ_FLOAT(20, m_PitchFactorRandomizer); + READ_FLOAT(21, m_VolumeRandomizer); + READ_INT(22, m_VirtualChannel); + READ_INT(13, m_LogicalType); + READ_FLOAT(14, m_LogicalNotifDelay); + READ_BOOL(15, m_CreateLogicalSound); + READ_FLOAT(16, m_LogicalDropoffRadius); + READ_VECTOR(17, m_SphereColor); + } + cload.Close_Micro_Chunk(); + } + break; + case 0x200: + while (cload.Open_Chunk()) { + if (cload.Cur_Chunk_ID() == 0x100) { + while (cload.Open_Micro_Chunk()) { + switch (cload.Cur_Micro_Chunk_ID()) { + READ_INT(1, m_ID); + READ_STRING(3, m_Name); + } + cload.Close_Micro_Chunk(); + } + } + cload.Close_Chunk(); + } + break; + } + cload.Close_Chunk(); + } +} + +void Write_W3D_CHUNK_SOUNDROBJ_DEFINITION(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + // TODO: Implement Write_W3D_CHUNK_SOUNDROBJ_DEFINITION + captainslog_warn("Write_W3D_CHUNK_SOUNDROBJ_DEFINITION not implemented"); +} + +void DoVector3Channel(ChunkLoadClass &cload, ChunkTreePtr &data, const char *name) +{ + int i = 0; + + for (; cload.Open_Chunk(); cload.Close_Chunk()) { + if (cload.Cur_Chunk_ID() == 51709961) { // PrimitiveAnimationChannelClass + for (; cload.Open_Micro_Chunk(); cload.Close_Micro_Chunk()) { + if (cload.Cur_Micro_Chunk_ID() == 1) { + Vector3 value; + float time; + cload.Read(&value, sizeof(value)); + cload.Read(&time, sizeof(time)); + StringClass str; + str.Format("%s[%d].Value.X", name, i); + AddFloat(data, str, value.X); + str.Format("%s[%d].Value.Y", name, i); + AddFloat(data, str, value.Y); + str.Format("%s[%d].Value.Z", name, i); + AddFloat(data, str, value.Z); + str.Format("%s[%d].time", name, i); + AddFloat(data, str, time); + i++; + } + } + } + } +} + +void DoVector2Channel(ChunkLoadClass &cload, ChunkTreePtr &data, const char *name) +{ + int i = 0; + + for (; cload.Open_Chunk(); cload.Close_Chunk()) { + if (cload.Cur_Chunk_ID() == 51709961) { // PrimitiveAnimationChannelClass + for (; cload.Open_Micro_Chunk(); cload.Close_Micro_Chunk()) { + if (cload.Cur_Micro_Chunk_ID() == 1) { + Vector2 value; + float time; + cload.Read(&value, sizeof(value)); + cload.Read(&time, sizeof(time)); + StringClass str; + str.Format("%s[%d].Value.X", name, i); + AddFloat(data, str, value.X); + str.Format("%s[%d].Value.Y", name, i); + AddFloat(data, str, value.Y); + str.Format("%s[%d].time", name, i); + AddFloat(data, str, time); + i++; + } + } + } + } +} + +void DofloatChannel(ChunkLoadClass &cload, ChunkTreePtr &data, const char *name) +{ + int i = 0; + + for (; cload.Open_Chunk(); cload.Close_Chunk()) { + if (cload.Cur_Chunk_ID() == 51709961) { // PrimitiveAnimationChannelClass + for (; cload.Open_Micro_Chunk(); cload.Close_Micro_Chunk()) { + if (cload.Cur_Micro_Chunk_ID() == 1) { + float value; + float time; + cload.Read(&value, sizeof(value)); + cload.Read(&time, sizeof(time)); + StringClass str; + str.Format("%s[%d].Value", name, i); + AddFloat(data, str, value); + str.Format("%s[%d].time", name, i); + AddFloat(data, str, time); + i++; + } + } + } + } +} + +void DoAlphaVectorStructChannel(ChunkLoadClass &cload, ChunkTreePtr &data, const char *name) +{ + int i = 0; + + for (; cload.Open_Chunk(); cload.Close_Chunk()) { + if (cload.Cur_Chunk_ID() == 51709961) { // PrimitiveAnimationChannelClass + for (; cload.Open_Micro_Chunk(); cload.Close_Micro_Chunk()) { + if (cload.Cur_Micro_Chunk_ID() == 1) { + AlphaVectorStruct value; + float time; + cload.Read(&value, sizeof(value)); + cload.Read(&time, sizeof(time)); + StringClass str; + str.Format("%s[%d].Value.Quat.X", name, i); + AddFloat(data, str, value.Quat.X); + str.Format("%s[%d].Value.Quat.Y", name, i); + AddFloat(data, str, value.Quat.Y); + str.Format("%s[%d].Value.Quat.Z", name, i); + AddFloat(data, str, value.Quat.Z); + str.Format("%s[%d].Value.Quat.W", name, i); + AddFloat(data, str, value.Quat.W); + str.Format("%s[%d].Value.Magnitude", name, i); + AddFloat(data, str, value.Magnitude); + str.Format("%s[%d].time", name, i); + AddFloat(data, str, time); + i++; + } + } + } + } +} + +void Read_W3D_CHUNK_RING(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + // TODO: Implement Read_W3D_CHUNK_RING + captainslog_warn("Read_W3D_CHUNK_RING not implemented"); + while (cload.Open_Chunk()) { + switch (cload.Cur_Chunk_ID()) { + case 1: { + W3dRingStruct *RingStruct; + cload.Read(RingStruct, sizeof(RingStruct)); + + if (RingStruct->Flags & 0xFFFFFFFC) { + StringClass str; + str.Format("W3D_CHUNK_RING Unknown Ring Flags %x", RingStruct->Flags & 0xFFFFFFFC); + captainslog_warn((const char *)str); + } + + AddData(data, "W3D_CHUNK_RING", "W3dRingStruct", "", (void *)RingStruct); + break; + } + case 2: + DoVector3Channel(cload, data, "ColorChannel"); + break; + case 3: + DofloatChannel(cload, data, "AlphaChannel"); + break; + case 4: + DoVector2Channel(cload, data, "InnerScaleChannel"); + break; + case 5: + DoVector2Channel(cload, data, "OuterScaleChannel"); + break; + } + cload.Close_Chunk(); + } +} + +void Write_W3D_CHUNK_RING(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + // TODO: Implement Write_W3D_CHUNK_RING + captainslog_warn("Write_W3D_CHUNK_RING not implemented"); +} + +void Read_W3D_CHUNK_SPHERE(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + // TODO: Implement Read_W3D_CHUNK_SPHERE + captainslog_warn("Read_W3D_CHUNK_SPHERE not implemented"); + while (cload.Open_Chunk()) { + switch (cload.Cur_Chunk_ID()) { + case 1: { + W3dSphereStruct *SphereStruct; + cload.Read(SphereStruct, sizeof(SphereStruct)); + + if (SphereStruct->Flags & 0xFFFFFFF0) { + StringClass str; + str.Format("W3D_CHUNK_SPHERE Unknown Sphere Flags %x", SphereStruct->Flags & 0xFFFFFFFC); + captainslog_warn((const char *)str); + } + AddData(data, "W3D_CHUNK_SPHERE", "W3dSphereStruct", "", (void *)SphereStruct); + + } break; + case 2: + DoVector3Channel(cload, data, "ColorChannel"); + break; + case 3: + DofloatChannel(cload, data, "AlphaChannel"); + break; + case 4: + DoVector3Channel(cload, data, "ScaleChannel"); + break; + case 5: + DoAlphaVectorStructChannel(cload, data, "VectorChannel"); + break; + } + cload.Close_Chunk(); + } +} + +void Write_W3D_CHUNK_SPHERE(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + // TODO: Implement Write_W3D_CHUNK_SPHERE + captainslog_warn("Write_W3D_CHUNK_SPHERE not implemented"); +} + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_SHDMESH) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_SHDMESH_NAME) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_SHDSUBMESH) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_SHDSUBMESH_SHADER) + +READ_WRITE_CHUNK(W3D_CHUNK_SHDSUBMESH_SHADER_TYPE, uint32_t) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_VERTICES, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_VERTEX_NORMALS, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_TRIANGLES, Vector3i16) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_VERTEX_SHADE_INDICES, uint32_t) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_UV0, W3dTexCoordStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_UV1, W3dTexCoordStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_TANGENT_BASIS_S, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_TANGENT_BASIS_T, W3dVectorStruct) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_SHDSUBMESH_TANGENT_BASIS_SXT, W3dVectorStruct) + +READ_WRITE_CHUNK(W3D_CHUNK_EMITTER_EXTRA_INFO, W3dEmitterExtraInfoStruct) + +READ_WRITE_CHUNK_STRING(W3D_CHUNK_SHDMESH_USER_TEXT) + +READ_WRITE_CHUNK_ARRAY(W3D_CHUNK_FXSHADER_IDS, uint32_t) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_FX_SHADERS) + +READ_WRITE_SUBCHUNKS(W3D_CHUNK_FX_SHADER) + +void Read_W3D_CHUNK_FX_SHADER_INFO(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dFXShaderStruct) + 1, "Chunk W3D_CHUNK_FX_SHADER_INFO size does not match data size"); + // TODO Fix W3dFXShaderStruct + // uint8_t *version = (uint8_t *)rawData; + // W3dFXShaderStruct *shader = (W3dFXShaderStruct *)(rawData + 1); + AddData(data, "W3D_CHUNK_FX_SHADER_INFO", "W3dFXShaderStruct", "", (void *)rawData); + +} + +void Write_W3D_CHUNK_FX_SHADER_INFO(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + captainslog_warn("Write_W3D_CHUNK_FX_SHADER_INFO not implemented"); + char *rawData = (char *)data->data->info->value; + WriteChunkRaw(csave, W3D_CHUNK_FX_SHADER_INFO, rawData, data->data->chunkSize); +} + +struct W3dFXShaderConstantStruct +{ + uint32_t Type; + uint32_t Length; + std::string ConstantName; + union + { + std::string Texture; + std::vector Floats; + uint32_t Int; + bool Bool; + } Value; + + W3dFXShaderConstantStruct &operator=(const char *rawData) + { + Type = *(uint32_t *)rawData; + Length = *(uint32_t *)(rawData + 4); + ConstantName = std::string(rawData + 8, Length); + + if (Type == CONSTANT_TYPE_TEXTURE) { + Value.Texture = std::string(rawData + 8 + Length); + } else if (Type >= CONSTANT_TYPE_FLOAT1 && Type <= CONSTANT_TYPE_FLOAT4) { + int count = Type - 1; + float *floats = (float *)(rawData + 8 + Length); + Value.Floats.assign(floats, floats + count); + } else if (Type == CONSTANT_TYPE_INT) { + Value.Int = *(uint32_t *)(rawData + 8 + Length); + } else if (Type == CONSTANT_TYPE_BOOL) { + Value.Bool = *(uint8_t *)(rawData + 8 + Length) != 0; + } else { + // Handle unknown types + Value.Texture = "Unknown"; + } + + return *this; + } +}; + +void Read_W3D_CHUNK_FX_SHADER_CONSTANT(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + // TODO check if this correct + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dFXShaderConstantStruct), "Chunk W3D_CHUNK_FX_SHADER_CONSTANT size does not match data size"); + W3dFXShaderConstantStruct *constant = (W3dFXShaderConstantStruct *)rawData; + uint32_t type = *(uint32_t *)rawData; + uint32_t length = *(uint32_t *)(rawData + 4); + uint32_t expectedSize = 4 + 4 + length; // 4 bytes for 'type' and 'length' plus length for constant name + if (type == CONSTANT_TYPE_TEXTURE) { + expectedSize += 4 + strlen((char *)(rawData + expectedSize)); // add texture size + } else if (type >= CONSTANT_TYPE_FLOAT1 && type <= CONSTANT_TYPE_FLOAT4) { + expectedSize += sizeof(float) * (type - 1); // add float array size + } else if (type == CONSTANT_TYPE_INT) { + expectedSize += sizeof(uint32_t); // add int size + } else if (type == CONSTANT_TYPE_BOOL) { + expectedSize += sizeof(uint8_t); // add bool size + } else { + StringClass str; + str.Format("W3D_CHUNK_FX_SHADER_CONSTANT Unknown Constant Type %x", constant->Type); + captainslog_warn((const char *)str); + } + + captainslog_dbgassert(data->data->chunkSize == expectedSize, + "Chunk W3D_CHUNK_FX_SHADER_CONSTANT size does not match expected size"); + + + if (constant->Value.Texture == "Unknown") { + } + AddData(data, "W3D_CHUNK_FX_SHADER_CONSTANT", "W3dFXShaderConstantStruct", "", (void *)constant); +} + +void Write_W3D_CHUNK_FX_SHADER_CONSTANT(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dFXShaderConstantStruct *constant = (W3dFXShaderConstantStruct *)data->data->info->value; + if (constant->Value.Texture == "Unknown") { + StringClass str; + str.Format("W3D_CHUNK_FX_SHADER_CONSTANT Unknown Constant Type %x", constant->Type); + captainslog_warn((const char *)str); + } + WriteChunkRaw(csave, W3D_CHUNK_FX_SHADER_CONSTANT, constant, sizeof(W3dFXShaderConstantStruct)); +} + +struct W3dCompressedMotionChannelStructNew +{ + uint8_t Zero; + uint8_t Flavor; + uint8_t VectorLen; + uint8_t Flags; + uint16_t NumTimeCodes; + uint16_t Pivot; + + std::vector KeyFrames; // ANIM_FLAVOR_NEW_TIMECODED + float Scale; // עבור Scale + std::vector Initial; + std::vector Data; + + W3dCompressedMotionChannelStructNew &operator=(const char *rawData) + { + Zero = *(uint8_t *)rawData; + Flavor = *(uint8_t *)(rawData + 1); + VectorLen = *(uint8_t *)(rawData + 2); + Flags = *(uint8_t *)(rawData + 3); + NumTimeCodes = *(uint16_t *)(rawData + 4); + Pivot = *(uint16_t *)(rawData + 6); + + size_t offset = sizeof(W3dCompressedMotionChannelStructNew); + + if (Flavor == ANIM_FLAVOR_NEW_TIMECODED) { + KeyFrames.assign( + (uint16_t *)(rawData + offset), (uint16_t *)(rawData + offset + NumTimeCodes * sizeof(uint16_t))); + offset += NumTimeCodes * sizeof(uint16_t); + + int datalen = VectorLen * NumTimeCodes; + if (NumTimeCodes & 1) { + offset += 2; // Padding for odd NumTimeCodes + } + Data.assign((uint32_t *)(rawData + offset), (uint32_t *)(rawData + offset + datalen * sizeof(uint32_t))); + } else { + Scale = *(float *)(rawData + offset); + offset += sizeof(float); + + Initial.assign((float *)(rawData + offset), (float *)(rawData + offset + VectorLen * sizeof(float))); + offset += VectorLen * sizeof(float); + + int count = (NumTimeCodes * VectorLen - sizeof(W3dCompressedMotionChannelStructNew) - sizeof(float) + - sizeof(float) * VectorLen) / sizeof(uint32_t); + Data.assign((uint32_t *)(rawData + offset), (uint32_t *)(rawData + offset + count * sizeof(uint32_t))); + } + return *this; + } +}; + +void Read_W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL(ChunkLoadClass &cload, ChunkTreePtr &data) +{ + char *rawData = ReadChunkRaw(cload); + // TODO check if this correct + captainslog_dbgassert(data->data->chunkSize == sizeof(W3dCompressedMotionChannelStructNew), "Chunk W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL size does not match data size"); + W3dCompressedMotionChannelStructNew *channel = (W3dCompressedMotionChannelStructNew *)rawData; + if (channel->Flavor >= ANIM_FLAVOR_NEW_VALID) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL Unknown Flavor Type %x", channel->Flavor); + captainslog_warn((const char *)str); + } + + if (channel->Flags > ANIM_CHANNEL_VIS) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + + AddData( + data, "W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL", "W3dCompressedMotionChannelStructNew", "", (void *)channel); +} + +void Write_W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL(ChunkSaveClass &csave, ChunkTreePtr &data) +{ + W3dCompressedMotionChannelStructNew *channel = (W3dCompressedMotionChannelStructNew *)data->data->info->value; + if (channel->Flavor >= ANIM_FLAVOR_NEW_VALID) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL Unknown Flavor Type %x", channel->Flavor); + captainslog_warn((const char *)str); + } + + if (channel->Flags > ANIM_CHANNEL_VIS) { + StringClass str; + str.Format("W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL Unknown Animation Channel Type %x", channel->Flags); + captainslog_warn((const char *)str); + } + WriteChunkRaw( + csave, W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL, channel, sizeof(W3dCompressedMotionChannelStructNew)); +} + +std::map chunkFuncMap; + +#define CHUNK(id) \ + { \ + ChunkIOFuncs c; \ + c.name = #id; \ + c.ReadChunk = Read_##id; \ + c.WriteChunk = Write_##id; \ + chunkFuncMap[id] = c; \ + } + +void ChunkManager::InitiateChunkFuncMap() +{ + CHUNK(O_W3D_CHUNK_MATERIALS) + CHUNK(O_W3D_CHUNK_MATERIALS2) + CHUNK(O_W3D_CHUNK_POV_QUADRANGLES) + CHUNK(O_W3D_CHUNK_POV_TRIANGLES) + CHUNK(O_W3D_CHUNK_QUADRANGLES) + CHUNK(O_W3D_CHUNK_SURRENDER_TRIANGLES) + CHUNK(O_W3D_CHUNK_TRIANGLES) + CHUNK(OBSOLETE_W3D_CHUNK_EMITTER_COLOR_KEYFRAME) + CHUNK(OBSOLETE_W3D_CHUNK_EMITTER_OPACITY_KEYFRAME) + CHUNK(OBSOLETE_W3D_CHUNK_EMITTER_SIZE_KEYFRAME) + CHUNK(OBSOLETE_W3D_CHUNK_SHADOW_NODE) + CHUNK(W3D_CHUNK_AABTREE) + CHUNK(W3D_CHUNK_AABTREE_HEADER) + CHUNK(W3D_CHUNK_AABTREE_NODES) + CHUNK(W3D_CHUNK_AABTREE_POLYINDICES) + CHUNK(W3D_CHUNK_AGGREGATE) + CHUNK(W3D_CHUNK_AGGREGATE_CLASS_INFO) + CHUNK(W3D_CHUNK_AGGREGATE_HEADER) + CHUNK(W3D_CHUNK_AGGREGATE_INFO) + CHUNK(W3D_CHUNK_ANIMATION) + CHUNK(W3D_CHUNK_ANIMATION_CHANNEL) + CHUNK(W3D_CHUNK_ANIMATION_HEADER) + CHUNK(W3D_CHUNK_BIT_CHANNEL) + CHUNK(W3D_CHUNK_BOX) + CHUNK(W3D_CHUNK_COLLECTION) + CHUNK(W3D_CHUNK_COLLECTION_HEADER) + CHUNK(W3D_CHUNK_COLLECTION_OBJ_NAME) + CHUNK(W3D_CHUNK_COLLISION_NODE) + CHUNK(W3D_CHUNK_DAMAGE) + CHUNK(W3D_CHUNK_DAMAGE_COLORS) + CHUNK(W3D_CHUNK_DAMAGE_HEADER) + CHUNK(W3D_CHUNK_DAMAGE_VERTICES) + CHUNK(W3D_CHUNK_DAZZLE) + CHUNK(W3D_CHUNK_DAZZLE_NAME) + CHUNK(W3D_CHUNK_DAZZLE_TYPENAME) + CHUNK(W3D_CHUNK_DCG) + CHUNK(W3D_CHUNK_DIG) + CHUNK(W3D_CHUNK_EMITTER) + CHUNK(W3D_CHUNK_EMITTER_BLUR_TIME_KEYFRAMES) + CHUNK(W3D_CHUNK_EMITTER_FRAME_KEYFRAMES) + CHUNK(W3D_CHUNK_EMITTER_HEADER) + CHUNK(W3D_CHUNK_EMITTER_INFO) + CHUNK(W3D_CHUNK_EMITTER_INFOV2) + CHUNK(W3D_CHUNK_EMITTER_PROPS) + CHUNK(W3D_CHUNK_EMITTER_ROTATION_KEYFRAMES) + CHUNK(W3D_CHUNK_EMITTER_USER_DATA) + CHUNK(W3D_CHUNK_FAR_ATTENUATION) + CHUNK(W3D_CHUNK_HIERARCHY) + CHUNK(W3D_CHUNK_HIERARCHY_HEADER) + CHUNK(W3D_CHUNK_HLOD) + CHUNK(W3D_CHUNK_HLOD_AGGREGATE_ARRAY) + CHUNK(W3D_CHUNK_HLOD_HEADER) + CHUNK(W3D_CHUNK_HLOD_LOD_ARRAY) + CHUNK(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) + CHUNK(W3D_CHUNK_HLOD_PROXY_ARRAY) + CHUNK(W3D_CHUNK_HLOD_SUB_OBJECT) + CHUNK(W3D_CHUNK_HMODEL) + CHUNK(OBSOLETE_W3D_CHUNK_HMODEL_AUX_DATA) + CHUNK(W3D_CHUNK_HMODEL_HEADER) + CHUNK(W3D_CHUNK_LIGHT) + CHUNK(W3D_CHUNK_LIGHT_INFO) + CHUNK(W3D_CHUNK_LIGHT_TRANSFORM) + CHUNK(W3D_CHUNK_LIGHTSCAPE) + CHUNK(W3D_CHUNK_LIGHTSCAPE_LIGHT) + CHUNK(W3D_CHUNK_LOD) + CHUNK(W3D_CHUNK_LODMODEL) + CHUNK(W3D_CHUNK_LODMODEL_HEADER) + CHUNK(W3D_CHUNK_MAP3_FILENAME) + CHUNK(W3D_CHUNK_MAP3_INFO) + CHUNK(W3D_CHUNK_MATERIAL_INFO) + CHUNK(W3D_CHUNK_MATERIAL_PASS) + CHUNK(W3D_CHUNK_MATERIAL3) + CHUNK(W3D_CHUNK_MATERIAL3_DC_MAP) + CHUNK(W3D_CHUNK_MATERIAL3_DI_MAP) + CHUNK(W3D_CHUNK_MATERIAL3_INFO) + CHUNK(W3D_CHUNK_MATERIAL3_NAME) + CHUNK(W3D_CHUNK_MATERIAL3_SC_MAP) + CHUNK(W3D_CHUNK_MATERIAL3_SI_MAP) + CHUNK(W3D_CHUNK_MATERIALS3) + CHUNK(W3D_CHUNK_MESH) + CHUNK(W3D_CHUNK_MESH_HEADER) + CHUNK(W3D_CHUNK_MESH_HEADER3) + CHUNK(W3D_CHUNK_MESH_USER_TEXT) + CHUNK(W3D_CHUNK_NEAR_ATTENUATION) + CHUNK(W3D_CHUNK_NODE) + CHUNK(W3D_CHUNK_NULL_OBJECT) + CHUNK(W3D_CHUNK_PER_FACE_TEXCOORD_IDS) + CHUNK(W3D_CHUNK_PER_TRI_MATERIALS) + CHUNK(W3D_CHUNK_PIVOT_FIXUPS) + CHUNK(W3D_CHUNK_PIVOTS) + CHUNK(W3D_CHUNK_PLACEHOLDER) + CHUNK(W3D_CHUNK_POINTS) + CHUNK(W3D_CHUNK_PRELIT_LIGHTMAP_MULTI_PASS) + CHUNK(W3D_CHUNK_PRELIT_LIGHTMAP_MULTI_TEXTURE) + CHUNK(W3D_CHUNK_PRELIT_UNLIT) + CHUNK(W3D_CHUNK_PRELIT_VERTEX) + CHUNK(W3D_CHUNK_PS2_SHADERS) + CHUNK(W3D_CHUNK_SCG) + CHUNK(W3D_CHUNK_SHADER_IDS) + CHUNK(W3D_CHUNK_SHADERS) + CHUNK(W3D_CHUNK_SKIN_NODE) + CHUNK(W3D_CHUNK_SPOT_LIGHT_INFO) + CHUNK(W3D_CHUNK_SPOT_LIGHT_INFO_5_0) + CHUNK(W3D_CHUNK_STAGE_TEXCOORDS) + CHUNK(W3D_CHUNK_SURRENDER_NORMALS) + CHUNK(W3D_CHUNK_TEXCOORDS) + CHUNK(W3D_CHUNK_TEXTURE) + CHUNK(W3D_CHUNK_TEXTURE_IDS) + CHUNK(W3D_CHUNK_TEXTURE_INFO) + CHUNK(W3D_CHUNK_TEXTURE_NAME) + CHUNK(W3D_CHUNK_TEXTURE_REPLACER_INFO) + CHUNK(W3D_CHUNK_TEXTURE_STAGE) + CHUNK(W3D_CHUNK_TEXTURES) + CHUNK(W3D_CHUNK_TRANSFORM_NODE) + CHUNK(W3D_CHUNK_TRIANGLES) + CHUNK(W3D_CHUNK_VERTEX_COLORS) + CHUNK(W3D_CHUNK_VERTEX_INFLUENCES) + CHUNK(W3D_CHUNK_VERTEX_MAPPER_ARGS0) + CHUNK(W3D_CHUNK_VERTEX_MAPPER_ARGS1) + CHUNK(W3D_CHUNK_VERTEX_MATERIAL) + CHUNK(W3D_CHUNK_VERTEX_MATERIAL_IDS) + CHUNK(W3D_CHUNK_VERTEX_MATERIAL_INFO) + CHUNK(W3D_CHUNK_VERTEX_MATERIAL_NAME) + CHUNK(W3D_CHUNK_VERTEX_MATERIALS) + CHUNK(W3D_CHUNK_VERTEX_NORMALS) + CHUNK(W3D_CHUNK_VERTEX_SHADE_INDICES) + CHUNK(W3D_CHUNK_VERTICES) + CHUNK(W3D_CHUNK_EMITTER_LINE_PROPERTIES) + CHUNK(W3D_CHUNK_SECONDARY_VERTICES) + CHUNK(W3D_CHUNK_SECONDARY_VERTEX_NORMALS) + CHUNK(W3D_CHUNK_TANGENTS) + CHUNK(W3D_CHUNK_BINORMALS) + CHUNK(W3D_CHUNK_COMPRESSED_ANIMATION) + CHUNK(W3D_CHUNK_COMPRESSED_ANIMATION_HEADER) + CHUNK(W3D_CHUNK_COMPRESSED_ANIMATION_CHANNEL) + CHUNK(W3D_CHUNK_COMPRESSED_BIT_CHANNEL) + CHUNK(W3D_CHUNK_MORPH_ANIMATION) + CHUNK(W3D_CHUNK_MORPHANIM_HEADER) + CHUNK(W3D_CHUNK_MORPHANIM_CHANNEL) + CHUNK(W3D_CHUNK_MORPHANIM_POSENAME) + CHUNK(W3D_CHUNK_MORPHANIM_KEYDATA) + CHUNK(W3D_CHUNK_MORPHANIM_PIVOTCHANNELDATA) + CHUNK(W3D_CHUNK_SOUNDROBJ) + CHUNK(W3D_CHUNK_SOUNDROBJ_HEADER) + CHUNK(W3D_CHUNK_SOUNDROBJ_DEFINITION) + CHUNK(W3D_CHUNK_RING) + CHUNK(W3D_CHUNK_SPHERE) + CHUNK(W3D_CHUNK_SHDMESH) + CHUNK(W3D_CHUNK_SHDMESH_NAME) + CHUNK(W3D_CHUNK_SHDSUBMESH) + CHUNK(W3D_CHUNK_SHDSUBMESH_SHADER) + CHUNK(W3D_CHUNK_SHDSUBMESH_SHADER_TYPE) + CHUNK(W3D_CHUNK_SHDSUBMESH_VERTICES) + CHUNK(W3D_CHUNK_SHDSUBMESH_VERTEX_NORMALS) + CHUNK(W3D_CHUNK_SHDSUBMESH_TRIANGLES) + CHUNK(W3D_CHUNK_SHDSUBMESH_VERTEX_SHADE_INDICES) + CHUNK(W3D_CHUNK_SHDSUBMESH_UV0) + CHUNK(W3D_CHUNK_SHDSUBMESH_UV1) + CHUNK(W3D_CHUNK_SHDSUBMESH_TANGENT_BASIS_S) + CHUNK(W3D_CHUNK_SHDSUBMESH_TANGENT_BASIS_T) + CHUNK(W3D_CHUNK_SHDSUBMESH_TANGENT_BASIS_SXT) + CHUNK(W3D_CHUNK_EMITTER_EXTRA_INFO) + CHUNK(W3D_CHUNK_SHDMESH_USER_TEXT) + CHUNK(W3D_CHUNK_FXSHADER_IDS) + CHUNK(W3D_CHUNK_FX_SHADERS) + CHUNK(W3D_CHUNK_FX_SHADER) + CHUNK(W3D_CHUNK_FX_SHADER_INFO) + CHUNK(W3D_CHUNK_FX_SHADER_CONSTANT) + CHUNK(W3D_CHUNK_COMPRESSED_ANIMATION_MOTION_CHANNEL) +} +#pragma clang diagnostic pop \ No newline at end of file diff --git a/src/tools/gamemodelconverter/src/chunksmanager.cpp b/src/tools/gamemodelconverter/src/chunksmanager.cpp new file mode 100644 index 000000000..87f8ef244 --- /dev/null +++ b/src/tools/gamemodelconverter/src/chunksmanager.cpp @@ -0,0 +1,106 @@ +#include "chunksmanager.h" + +ChunkManager::ChunkManager(const char *filePath, int flag) +{ + // m_rootChunks.push_back(std::make_unique()); + InitiateChunkFuncMap(); +} + +ChunkManager::~ChunkManager() +{ + if (m_file) { + m_file->Close(); + } +} + +void ChunkManager::ReadChunks(ChunkLoadClass &chunkLoader) +{ + while (chunkLoader.Open_Chunk()) { + ChunkTreePtr root = std::make_unique(); + ReadSubChunks(chunkLoader, root); + m_rootChunks.push_back(std::move(root)); + chunkLoader.Close_Chunk(); + } +} + +void ChunkManager::ReadSubChunks(ChunkLoadClass &chunkLoader, ChunkTreePtr &parentChunk) +{ + if (parentChunk == nullptr) { + parentChunk = std::make_unique(); + } + + if (!parentChunk->data) { + ReadChunkInfo(chunkLoader, parentChunk); + } + while (chunkLoader.Open_Chunk()) { + ChunkTreePtr subchunk = std::make_unique(); + ReadChunkInfo(chunkLoader, subchunk); + parentChunk->subchunks.push_back(std::move(subchunk)); + chunkLoader.Close_Chunk(); + } +} + +void ChunkManager::ReadChunkInfo(ChunkLoadClass &chunkLoader, ChunkTreePtr &data) +{ + if (data == nullptr) { + captainslog_error("Chunk is null"); + return; + } + if (!data->data) { + ChunkPtr chunk = std::make_unique(); + chunk->chunkType = chunkLoader.Cur_Chunk_ID(); + chunk->chunkSize = chunkLoader.Cur_Chunk_Length(); + data->data = std::move(chunk); + } + + auto it = chunkFuncMap.find(data->data->chunkType); + if (it != chunkFuncMap.end()) { + it->second.ReadChunk(chunkLoader, data); + } else { + StringClass str; + str.Format("Unknown Chunk 0x%X", data->data->chunkType); + captainslog_warn((const char *)str); + data->data->info = nullptr; + } +} + +void ChunkManager::WriteChunks(ChunkSaveClass &chunkSaver) +{ + for (auto &root : m_rootChunks) { + if (root->data->info->value != nullptr) { + WriteChunkInfo(chunkSaver, root); + } else { + WriteSubChunks(chunkSaver, root); + } + } +} + +void ChunkManager::WriteSubChunks(ChunkSaveClass &chunkSaver, ChunkTreePtr &parentChunk) +{ + if (parentChunk->data) { + if (!chunkSaver.Begin_Chunk(parentChunk->data->chunkType)) { + captainslog_error("Failed to begin chunk"); + return; + } + for (auto &subchunk : parentChunk->subchunks) { + WriteChunkInfo(chunkSaver, subchunk); + } + // Parent chunk data, no data to write, continue to subchunks + if (!chunkSaver.End_Chunk()) { + captainslog_error("Failed to end chunk"); + return; + } + } +} + +void ChunkManager::WriteChunkInfo(ChunkSaveClass &chunkSaver, ChunkTreePtr &data) +{ + auto it = chunkFuncMap.find(data->data->chunkType); + if (it != chunkFuncMap.end()) { + it->second.WriteChunk(chunkSaver, data); + } else { + StringClass str; + str.Format("Unknown Chunk 0x%X", data->data->chunkType); + captainslog_warn((const char *)str); + } +} diff --git a/src/tools/gamemodelconverter/src/chunksmanager.h b/src/tools/gamemodelconverter/src/chunksmanager.h new file mode 100644 index 000000000..38921c9ce --- /dev/null +++ b/src/tools/gamemodelconverter/src/chunksmanager.h @@ -0,0 +1,99 @@ +#pragma once + +#include "chunkio.h" +#include "ffactory.h" +#include "vector2.h" +#include "vector3.h" +#include "vector3i.h" +#include "w3d_file.h" +#include "w3d_obsolete.h" +#include +#include +#include + +struct ChunkInfo +{ + StringClass name; // The chunk name (e.g. "W3D_CHUNK_VERTEX_MATERIALS") + StringClass type; // The chunk type (e.g., "W3dVectorStruct," "W3dColorStruct") + StringClass data; // Formatted data (e.g. "Version 4.2", "X: 1, Y: 3, Z: 8"), will W3X support. + void *value; // The Raw type data (e.g. address of W3dVectorStruct content) + + ChunkInfo(const char *name, const StringClass &type, const StringClass &data, void *value) : + name(name), type(type), data(data), value(value) {} + + ~ChunkInfo() + { + delete [] value; + } +}; + + +using ChunkInfoPtr = std::unique_ptr; + +struct Chunk +{ + uint32_t chunkType; + uint32_t chunkSize; + ChunkInfoPtr info; +}; + +using ChunkPtr = std::unique_ptr; +using ChunkList = std::vector; + +struct ChunkTree +{ + ChunkPtr data; + std::vector> subchunks; +}; + +using ChunkTreePtr = std::unique_ptr; + +struct ChunkIOFuncs +{ + const char *name; + void (*ReadChunk)(ChunkLoadClass &chunkLoader, ChunkTreePtr &data); + void (*WriteChunk)(ChunkSaveClass &chunkSaver, ChunkTreePtr &data); +}; + +extern std::map chunkFuncMap; + +class ChunkManager +{ +public: + // flags for the chunk manager + enum ChunkManagerType + { + LOAD = 0, + SAVE = 1, + }; + + ChunkManager(const char *filePath, int flag); + ~ChunkManager(); + + + // Utility functions + void InitiateChunkFuncMap(); + static ChunkInfoPtr createChunkInfo( + const char *name = "Unknown", + const StringClass &type = "Unknown", + const StringClass &data = "Unknown", + void *value = nullptr) + { + return std::make_unique(name, type, data, value); + } + void ReadChunks(ChunkLoadClass &chunkLoader); + static void ReadSubChunks(ChunkLoadClass &chunkLoader, ChunkTreePtr &parentChunk); + static void ReadChunkInfo(ChunkLoadClass &chunkLoader, ChunkTreePtr &chunk); + + void WriteChunks(ChunkSaveClass &chunkSaver); + static void WriteSubChunks(ChunkSaveClass &chunkSaver, ChunkTreePtr &parentChunk); + static void WriteChunkInfo(ChunkSaveClass &chunkSaver, ChunkTreePtr &chunk); + + ChunkTreePtr & GetRootChunk() { return m_rootChunks.front(); } + void SetRootChunk(ChunkTreePtr root) { m_rootChunks.push_back(std::move(root)); } + +private: + std::vector m_rootChunks; + FileClass *m_file; +}; + diff --git a/src/tools/gamemodelconverter/src/commands.cpp b/src/tools/gamemodelconverter/src/commands.cpp new file mode 100644 index 000000000..e0530c653 --- /dev/null +++ b/src/tools/gamemodelconverter/src/commands.cpp @@ -0,0 +1,139 @@ +/** + * @file + * + * @author xezon, DevGeniusCode + * + * @brief Model Converter Commands. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "commands.h" + +namespace Thyme +{ +namespace +{ +const char *const s_command_action_names[] = { + "LOAD_W3D", + "LOAD_W3X", + "LOAD_BLEND", + "LOAD_MAX", + "SAVE_W3D", + "SAVE_W3X", + "SAVE_BLEND", + "SAVE_MAX", + "RESET", + "SET_OPTIONS", +}; + +const char *const s_command_argument_names[] = { + "FILE_ID", + "FILE_PATH", + "OPTION", +}; + +const char *const s_simple_action_names[] = { + "OPTIONS", + "LOAD_W3D", + "LOAD_W3X", + "LOAD_BLEND", + "LOAD_MAX", + "SAVE_W3D", + "SAVE_W3X", + "SAVE_BLEND", + "SAVE_MAX", + +}; + +static_assert(ARRAY_SIZE(s_command_action_names) == g_commandActionCount); +static_assert(ARRAY_SIZE(s_command_argument_names) == g_commandArgumentCount); +static_assert(ARRAY_SIZE(s_simple_action_names) == g_simpleActionCount); + +template +bool String_To_Enum_Id(const char *str, EnumType &id, const char *const (&search_names)[Size]) +{ + for (size_t index = 0; index < ARRAY_SIZE(search_names); ++index) { + if (strcasecmp(str, search_names[index]) == 0) { + id = static_cast(index); + return true; + } + } + return false; +} +} // namespace + +bool String_To_Command_Action_Id(const char *str, CommandActionId &action_id) +{ + return String_To_Enum_Id(str, action_id, s_command_action_names); +} + +bool String_To_Command_Argument_Id(const char *str, CommandArgumentId &argument_id) +{ + return String_To_Enum_Id(str, argument_id, s_command_argument_names); +} + +bool String_To_Simple_Action_Id(const char *str, SimpleActionId &action_id) +{ + return String_To_Enum_Id(str, action_id, s_simple_action_names); +} + +CommandId Command::s_id = 1000000000; + +bool LoadW3DCommand::Execute() const +{ + return m_filePtr->Load_W3D(m_filePath.c_str()); +} + +bool LoadW3XCommand::Execute() const +{ + return m_filePtr->Load_W3X(m_filePath.c_str()); +} + +bool LoadBlendCommand::Execute() const +{ + return m_filePtr->Load_BLEND(m_filePath.c_str()); +} + +bool LoadMaxCommand::Execute() const +{ + return m_filePtr->Load_MAX(m_filePath.c_str()); +} + +bool SaveW3DCommand::Execute() const +{ + return m_filePtr->Save_W3D(m_filePath.c_str()); +} + +bool SaveW3XCommand::Execute() const +{ + return m_filePtr->Save_W3X(m_filePath.c_str()); +} + +bool SaveBlendCommand::Execute() const +{ + return m_filePtr->Save_BLEND(m_filePath.c_str()); +} + +bool SaveMaxCommand::Execute() const +{ + return m_filePtr->Save_MAX(m_filePath.c_str()); +} + +bool ResetCommand::Execute() const +{ + m_filePtr->Reset(); + return true; +} + +bool SetOptionsCommand::Execute() const +{ + m_filePtr->Set_Options(m_options); + return true; +} + +} // namespace Thyme diff --git a/src/tools/gamemodelconverter/src/commands.h b/src/tools/gamemodelconverter/src/commands.h new file mode 100644 index 000000000..6ad86f2d5 --- /dev/null +++ b/src/tools/gamemodelconverter/src/commands.h @@ -0,0 +1,226 @@ +/** + * @file + * + * @author xezon, DevGeniusCode + * + * @brief GameModel Converter Commands. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once + +#include +#include +#include + +namespace Thyme +{ +using CommandId = size_t; +using GameModelOption = GameModelOption; +using GameModelOptions = GameModelFile::Options; +using GameModelFile = GameModelFile; +using GameModelFilePtr = std::shared_ptr; + +enum class CommandActionId + +{ + INVALID = -1, + LOAD_W3D, + LOAD_W3X, + LOAD_BLEND, + LOAD_MAX, + SAVE_W3D, + SAVE_W3X, + SAVE_BLEND, + SAVE_MAX, + RESET, + SET_OPTIONS, + + COUNT +}; + +enum class CommandArgumentId +{ + INVALID = -1, + FILE_ID, + FILE_PATH, + OPTIONS, + + COUNT +}; + +enum class SimpleActionId +{ + OPTIONS, + LOAD_W3D_FILE, + LOAD_W3X_FILE, + LOAD_BLEND_FILE, + LOAD_MAX_FILE, + SAVE_W3D, + SAVE_W3X, + SAVE_BLEND, + SAVE_MAX, + + COUNT +}; + +constexpr size_t g_commandActionCount = size_t(CommandActionId::COUNT); +constexpr size_t g_commandArgumentCount = size_t(CommandArgumentId::COUNT); +constexpr size_t g_simpleActionCount = size_t(SimpleActionId::COUNT); + +bool String_To_Command_Action_Id(const char *str, CommandActionId &action_id); +bool String_To_Command_Argument_Id(const char *str, CommandArgumentId &argument_id); +bool String_To_Simple_Action_Id(const char *str, SimpleActionId &action_id); + +class Command +{ +public: + Command() : m_id(s_id++) {} + virtual ~Command() {} + + CommandId Id() const { return m_id; } + void Set_Id(CommandId id) { m_id = id; } + + virtual CommandActionId Type() const = 0; + virtual bool Execute() const = 0; + +private: + CommandId m_id; + static CommandId s_id; +}; + +class LoadW3DCommand : public Command +{ +public: + LoadW3DCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::LOAD_W3D; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class LoadW3XCommand : public Command +{ +public: + LoadW3XCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::LOAD_W3X; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class LoadBlendCommand : public Command +{ +public: + LoadBlendCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::LOAD_BLEND; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class LoadMaxCommand : public Command +{ +public: + LoadMaxCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::LOAD_MAX; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class SaveW3DCommand : public Command +{ +public: + SaveW3DCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::SAVE_W3D; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class SaveW3XCommand : public Command +{ +public: + SaveW3XCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::SAVE_W3X; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class SaveBlendCommand : public Command +{ +public: + SaveBlendCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::SAVE_BLEND; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class SaveMaxCommand : public Command +{ +public: + SaveMaxCommand(const GameModelFilePtr &file_ptr, const char *path) : m_filePtr(file_ptr), m_filePath(path) {} + + virtual CommandActionId Type() const override { return CommandActionId::SAVE_MAX; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + std::string m_filePath; +}; + +class ResetCommand : public Command +{ +public: + ResetCommand(const GameModelFilePtr &file_ptr) : m_filePtr(file_ptr) {} + + virtual CommandActionId Type() const override { return CommandActionId::RESET; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; +}; + +class SetOptionsCommand : public Command +{ +public: + SetOptionsCommand(const GameModelFilePtr &file_ptr, GameModelOptions options) : m_filePtr(file_ptr), m_options(options) {} + + virtual CommandActionId Type() const override { return CommandActionId::SET_OPTIONS; } + virtual bool Execute() const override; + +private: + GameModelFilePtr m_filePtr; + GameModelOptions m_options; +}; + +} // namespace Thyme diff --git a/src/tools/gamemodelconverter/src/gamemodelcommon.h b/src/tools/gamemodelconverter/src/gamemodelcommon.h new file mode 100644 index 000000000..2442387d8 --- /dev/null +++ b/src/tools/gamemodelconverter/src/gamemodelcommon.h @@ -0,0 +1,111 @@ +/** + * @file gamemodelcommon.h + * + * @author DevGeniusCode + * + * @brief Common structures for Game Model Files. + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once + +#include "asciistring.h" +#include "bittype.h" +#include "unicodestring.h" +#include "utility/array.h" +#include "utility/arrayview.h" +#include "w3d_file.h" +#include +#include +#include + +// TODO size/count of chunks +constexpr size_t g_chunkCount = 77; + +namespace Thyme +{ + +/* +// Type aliases to avoid duplication +using W3dVector = W3dVectorStruct; +using W3dQuaternion = W3dQuaternionStruct; +using W3dRGB = W3dRGBStruct; +using W3dRGBA = W3dRGBAStruct; +using W3dMaterial3 = W3dMaterial3Struct; +using W3dVertexMaterial = W3dVertexMaterialStruct; +using W3dTri = W3dTriStruct; +using W3dVertInf = W3dVertInfStruct; +using W3dTexCoord = W3dTexCoordStruct; +using W3dMaterialInfo = W3dMaterialInfoStruct; +using W3dShader = W3dShaderStruct; +using W3dTextureInfo = W3dTextureInfoStruct; +using W3dMeshHeader3 = W3dMeshHeader3Struct; +using W3dMeshAABTreeHeader = W3dMeshAABTreeHeader; +using W3dMeshAABTreeNode = W3dMeshAABTreeNode; +using W3dEmitterHeader = W3dEmitterHeaderStruct; +using W3dEmitterInfo = W3dEmitterInfoStruct; +using W3dEmitterInfoV2 = W3dEmitterInfoStructV2; +using W3dEmitterProperty = W3dEmitterPropertyStruct; +// ...add other type aliases as needed... + + */ + +struct ChunkInfo { + uint32_t chunkType; // Chunk ID (from w3d_file.h) + uint32_t chunkSize; // Size of chunk data (including subchunk flag) Note: MSB indicates presence of subchunks. + std::vector data; // Raw chunk data + std::vector subChunks; // Vector to store sub-chunks +}; + + +using ChunkInfos = std::vector; +using ChunkInfosArray = rts::array; +using ConstChunkInfosPtrArray = rts::array; +using ChunkInfosPtrArray = rts::array; +using Utf8Array = std::vector; +using utf8view = rts::array_view; + +struct ParsedMeshData { + std::vector vertices; + std::vector normals; + std::vector triangles; + // ... other mesh data ... + W3dMeshHeader3Struct header; +}; + +struct ParsedEmitterData { + W3dEmitterHeaderStruct header; + W3dEmitterInfoStruct info; + W3dEmitterInfoStructV2 infoV2; + std::vector properties; +}; + +//Helper functions for reading data from chunk +template +T ReadFromChunk(const Thyme::ChunkInfo& chunk, size_t offset) { + if (offset + sizeof(T) > chunk.data.size()) { +// std::cerr << "Error: Trying to read past the end of the chunk data!" << std::endl; + T temp{}; + return temp; + } + return *reinterpret_cast(&chunk.data[offset]); +} + +template +std::vector ReadArrayFromChunk(const Thyme::ChunkInfo& chunk) { + size_t numElements = chunk.data.size() / sizeof(T); + std::vector array(numElements); + if (chunk.data.size() % sizeof(T) != 0) { +// std::cerr << "Error: Chunk data size is not a multiple of element size!" << std::endl; + return {}; //Return empty vector in case of error + } + memcpy(array.data(), chunk.data.data(), chunk.data.size()); + return array; +} + +} // namespace Thyme \ No newline at end of file diff --git a/src/tools/gamemodelconverter/src/gamemodelfile.cpp b/src/tools/gamemodelconverter/src/gamemodelfile.cpp new file mode 100644 index 000000000..a513f9656 --- /dev/null +++ b/src/tools/gamemodelconverter/src/gamemodelfile.cpp @@ -0,0 +1,329 @@ +/** + * @file gamemodelfile.cpp + * + * @author DevGeniusCode + * + * @brief Game Model File. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "gamemodelfile.h" + +#define GAMEMODELLOG_TRACE(fmt, ...) \ + captainslog_trace(fmt, ##__VA_ARGS__); \ + GameModelFile::Log_Line("TRACE : ", fmt, ##__VA_ARGS__) + +#define GAMEMODELLOG_DEBUG(fmt, ...) \ + captainslog_debug(fmt, ##__VA_ARGS__); \ + GameModelFile::Log_Line("DEBUG : ", fmt, ##__VA_ARGS__) + +#define GAMEMODELLOG_INFO(fmt, ...) \ + captainslog_info(fmt, ##__VA_ARGS__); \ + GameModelFile::Log_Line("INFO : ", fmt, ##__VA_ARGS__) + +#define GAMEMODELLOG_WARN(fmt, ...) \ + captainslog_warn(fmt, ##__VA_ARGS__); \ + GameModelFile::Log_Line("WARNING : ", fmt, ##__VA_ARGS__) + +#define GAMEMODELLOG_ERROR(fmt, ...) \ + captainslog_error(fmt, ##__VA_ARGS__); \ + GameModelFile::Log_Line("ERROR : ", fmt, ##__VA_ARGS__) + +#define GAMEMODELLOG_FATAL(fmt, ...) \ + captainslog_fatal(fmt, ##__VA_ARGS__); \ + GameModelFile::Log_Line("FATAL : ", fmt, ##__VA_ARGS__) + +namespace Thyme +{ + +namespace +{ +// TODO delete this function +template constexpr size_t Bit_To_Index(IntegerType integer) +{ + using UnsignedInt = UnsignedIntegerT; + size_t n = 0; + for (; n < sizeof(IntegerType) * 8; ++n) { + if (UnsignedInt(integer) & (UnsignedInt(1) << n)) { + return n; + } + } + return n; +} + +constexpr const char *const s_option_0 = "None"; + +constexpr const char *const s_options[] = { + s_option_0, +}; + +static_assert(s_option_0 == s_options[size_t(GameModelOption::NONE)]); +} // namespace + +bool Name_To_Game_Model_Option(const char *name, GameModelOption &option) +{ + size_t index = 0; + for (const char *option_name : s_options) { + if (strcasecmp(option_name, name) == 0) { + option = (index == 0) ? GameModelOption::NONE : static_cast(1 << (index - 1)); + return true; + } + ++index; + } + return false; +} + +FILE *GameModelFile::s_logfile = nullptr; +GameModelFile::GameModelFile() : // Initialize with default options + m_options(GameModelOption::NONE), m_rootChunk(nullptr), m_chunkLoader(nullptr) {}; + +bool GameModelFile::Is_Loaded() const +{ + return false; +} + +GameModelFile::FileType GameModelFile::Get_File_Type(const char *filename, FileType filetype) +{ + if (filetype == FileType::AUTO) { + std::string fn(filename); + if (fn.find(".w3d") != std::string::npos) + return FileType::W3D; + // Add checks for .w3x, .blend, .max here + if (fn.find(".w3x") != std::string::npos) + return FileType::W3X; + if (fn.find(".blend") != std::string::npos) + return FileType::BLEND; + if (fn.find(".max") != std::string::npos) + return FileType::MAX; + } + return filetype; +} + +bool GameModelFile::Load(const char *filename) +{ + const FileType filetype = Get_File_Type(filename, FileType::AUTO); + + return Load(filename, filetype); +} + +bool GameModelFile::Load_W3D(const char *filename) +{ + return Load(filename, FileType::W3D); +} + +bool GameModelFile::Load_W3X(const char *filename) +{ + return Load(filename, FileType::W3X); +} + +bool GameModelFile::Load_BLEND(const char *filename) +{ + return Load(filename, FileType::BLEND); +} + +bool GameModelFile::Load_MAX(const char *filename) +{ + return Load(filename, FileType::MAX); +} + +bool GameModelFile::Save(const char *filename) +{ + FileType filetype = Get_File_Type(filename, FileType::AUTO); + return Save(filename, filetype); +} + +bool GameModelFile::Save_W3D(const char *filename) +{ + return Save(filename, FileType::W3D); +} + +bool GameModelFile::Save_W3X(const char *filename) +{ + return Save(filename, FileType::W3X); +} + +bool GameModelFile::Save_BLEND(const char *filename) +{ + return Save(filename, FileType::BLEND); +} + +bool GameModelFile::Save_MAX(const char *filename) +{ + return Save(filename, FileType::MAX); +} + +void GameModelFile::Set_Options(Options options) +{ + m_options = options; +} + +GameModelFile::Options GameModelFile::Get_Options() const +{ + return m_options; +} + +void GameModelFile::Set_Log_File(FILE *log) +{ + s_logfile = log; +} + +// Private Methods + +bool GameModelFile::Load(const char *filename, FileType filetype) +{ + Reset(); + + captainslog_assert(filetype != FileType::AUTO); + + if (!filename || !filename[0]) { + GAMEMODELLOG_ERROR("File without name cannot be loaded"); + return false; + } + + bool success = false; + + switch (filetype) { + + case FileType::W3D: { + success = Read_W3D_File(filename, m_options); + break; + } + case FileType::W3X: + case FileType::BLEND: + case FileType::MAX: { + GAMEMODELLOG_WARN("Loading for file type '%s' not yet implemented.", filetype); + break; + } + default: + GAMEMODELLOG_ERROR("Unknown file type: %d", filetype); + break; + } + + if (success) { + GAMEMODELLOG_INFO("File '%s' loaded successfully", filename); + } else { + GAMEMODELLOG_ERROR("File '%s' failed to load", filename); + } + + return success; +} + +bool GameModelFile::Save(const char *filename, FileType filetype) +{ + captainslog_assert(filetype != FileType::AUTO); + + if (!filename || !filename[0]) { + GAMEMODELLOG_ERROR("File without name cannot be saved"); + return false; + } + + // TODO Is_Any_Loaded for chunks + // if (!Is_Any_Loaded()) { + // GAMEMODELLOG_ERROR("File without chunk data cannot be saved"); + // return false; + // } + + bool success = false; + + switch (filetype) { + + case FileType::W3D: { + success = Write_W3D_File(filename, m_options); + break; + } + case FileType::W3X: + case FileType::BLEND: + case FileType::MAX: { + GAMEMODELLOG_WARN("Saving for file type '%s' not yet implemented.", filetype); + success = false; + break; + } + default: + GAMEMODELLOG_ERROR("Unknown file type: %d", filetype); + success = false; + break; + } + + if (success) { + GAMEMODELLOG_INFO("File '%s' saved successfully", filename); + } else { + GAMEMODELLOG_ERROR("File '%s' failed to save", filename); + } + + return success; +} + +void GameModelFile::Reset() +{ + m_options = Options::Value::NONE; + // TODO good free memory? + // if (m_chunkManager) { + // delete m_chunkManager; + // m_chunkManager = nullptr; + // } +} + +bool GameModelFile::Read_W3D_File(const char *filename, const Options &options) +{ + GAMEMODELLOG_INFO("Reading model file '%s' in W3D format", filename); + + auto_file_ptr file(g_theFileFactory, filename); + if (!file->Open(FileOpenType::FM_READ)) { + GAMEMODELLOG_ERROR("Could not open file: %s", filename); + return false; + } + + m_chunkManager = new ChunkManager(filename, ChunkManager::ChunkManagerType::LOAD); + m_chunkLoader = ChunkLoadClass(file); + m_chunkManager->ReadChunks(m_chunkLoader); + + // TODO unknown chunks + GAMEMODELLOG_INFO("Successfully parsed W3D file: %s", filename); + + return true; +} + +bool GameModelFile::Write_W3D_File(const char *filename, const Options &options) +{ + GAMEMODELLOG_INFO("Writing model file '%s' in W3D format", filename); + + auto_file_ptr file(g_theFileFactory, filename); + if (!file->Open(FileOpenType::FM_WRITE)) { + GAMEMODELLOG_ERROR("Could not open file: %s", filename); + return false; + } + + ChunkSaveClass chunkSaver(file); + m_chunkManager->WriteChunks(chunkSaver); // השתמש בפונקציה החדשה לכתיבה + + return true; +} + +void GameModelFile::Log_Line(const char *prefix, const char *format, ...) +{ + FILE *file = s_logfile; + + if (file != nullptr) { + va_list args; + va_start(args, format); + fputs(prefix, file); + vfprintf(file, format, args); + fputs("\n", file); + va_end(args); + fflush(file); + } +} + +} // namespace Thyme + +#undef GAMEMODELLOG_TRACE +#undef GAMEMODELLOG_DEBUG +#undef GAMEMODELLOG_INFO +#undef GAMEMODELLOG_WARN +#undef GAMEMODELLOG_ERROR +#undef GAMEMODELLOG_FATAL diff --git a/src/tools/gamemodelconverter/src/gamemodelfile.h b/src/tools/gamemodelconverter/src/gamemodelfile.h new file mode 100644 index 000000000..ae7dfc748 --- /dev/null +++ b/src/tools/gamemodelconverter/src/gamemodelfile.h @@ -0,0 +1,112 @@ +/** + * @file gamemodelfile.h + * + * @author DevGeniusCode + * + * @brief Game Model File. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once + +#include "utility/ebitflags.h" +#include "chunksmanager.h" +#include "filesystem.h" +#include + + +namespace Thyme +{ + +enum class GameModelOption : uint32_t +{ + NONE = 0, +}; + +bool Name_To_Game_Model_Option(const char *name, GameModelOption &option); + +} // namespace Thyme + +DEFINE_RTS_UNDERLYING_TYPE(Thyme::GameModelOption, uint32_t); + +namespace Thyme +{ + +// #TODO Return error codes for load and save? + +class GameModelFile +{ +public: + using Options = rts::ebitflags; + +public: + GameModelFile(); + ~GameModelFile(){ + Reset(); + } + + // TODO Checks whether chunks is loaded. + bool Is_Loaded() const; + + // Loads models file from disk. Does not unload previous data on failure. + bool Load(const char *filename); + bool Load_W3D(const char *filename); + bool Load_W3X(const char *filename); + bool Load_BLEND(const char *filename); + bool Load_MAX(const char *filename); + + // Saves models file to disk. Will write over any existing file. + bool Save(const char *filename); + bool Save_W3D(const char *filename); + bool Save_W3X(const char *filename); + bool Save_BLEND(const char *filename); + bool Save_MAX(const char *filename); + + // Unloads all data and resets all settings. + void Reset(); + + // Sets options for loading and saving files. + void Set_Options(Options options); + Options Get_Options() const; + + // Optional logging stream. Works besides captains log. + static void Set_Log_File(FILE *log); + +private: + enum class FileType + { + AUTO, + W3D, + W3X, + BLEND, + MAX, + + COUNT, + }; + + +private: + bool Load(const char *filename, FileType filetype); + bool Save(const char *filename, FileType filetype); + + static FileType Get_File_Type(const char *filename, FileType filetype); + + bool Read_W3D_File(const char *filename, const Options& options); + bool Write_W3D_File(const char *filename, const Options& options); + + static void Log_Line(const char *prefix, const char *format, ...); + +private: + Options m_options; + static FILE *s_logfile; + ChunkTreePtr m_rootChunk; + ChunkManager * m_chunkManager; + ChunkLoadClass m_chunkLoader; +}; + +} // namespace Thyme diff --git a/src/tools/gamemodelconverter/src/log.cpp b/src/tools/gamemodelconverter/src/log.cpp new file mode 100644 index 000000000..bde4e3400 --- /dev/null +++ b/src/tools/gamemodelconverter/src/log.cpp @@ -0,0 +1,27 @@ +/** + * @file + * + * @author xezon + * + * @brief Game Text Compiler Log. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "log.h" +#include +#include + +void Print_Line(const char *message, ...) +{ + va_list args; + va_start(args, message); + vfprintf(stderr, message, args); + fprintf(stderr, "\n"); + va_end(args); + fflush(stderr); +} diff --git a/src/tools/gamemodelconverter/src/log.h b/src/tools/gamemodelconverter/src/log.h new file mode 100644 index 000000000..55b52f169 --- /dev/null +++ b/src/tools/gamemodelconverter/src/log.h @@ -0,0 +1,17 @@ +/** + * @file + * + * @author xezon + * + * @brief Game Text Compiler Log. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once + +void Print_Line(const char *message, ...); diff --git a/src/tools/gamemodelconverter/src/main.cpp b/src/tools/gamemodelconverter/src/main.cpp new file mode 100644 index 000000000..d132b3a81 --- /dev/null +++ b/src/tools/gamemodelconverter/src/main.cpp @@ -0,0 +1,244 @@ +/** + * @file + * + * @author xezon, DevGeniusCode + * + * @brief Model Converter. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "log.h" +#include "processor.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined PLATFORM_WINDOWS +HWND g_applicationHWnd; +#endif + +using namespace Thyme; + +// clang-format off +void Print_Help() +{ +// 1 2 3 4 5 6 7 8 9 10 11 12 +//3456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Print_Line( +R"#(Function Command List ... + +Syntax: COMMAND_NAME(ARGUMENT_NAME_A:value,ARGUMENT_NAME_B:value) +All capital words are interpreted keywords and must not be omitted. +All symbols of ( : , ) are part of the syntax and must not be omitted. +'mandatory' and 'optional' words show whether or not argument is mandatory. +[1] and [n] words show that argument takes one or multiple values. +Commands and command arguments are not case sensitive. +Space character will end current Command and begin new Command in command line. +Commands are executed in the order they are written in the command line. + +LOAD_W3D(FILE_ID:optional, FILE_PATH:mandatory) + > Loads a W3D file from FILE_PATH into the FILE_ID slot. + The W3D file is considered the authoritative format. + +LOAD_W3X(FILE_ID:optional, FILE_PATH:mandatory) + > Loads a W3X file from FILE_PATH into the FILE_ID slot. + W3X is an XML format representation of W3D. + +LOAD_BLEND(FILE_ID:optional, FILE_PATH:mandatory) + > Loads a Blender (.blend) file from FILE_PATH into the FILE_ID slot. + Used for non-destructive editing of W3D data. + +LOAD_MAX(FILE_ID:optional, FILE_PATH:mandatory) + > Loads a 3ds Max (.max) file from FILE_PATH into the FILE_ID slot. + Used for non-destructive editing of W3D data. + +SAVE_W3D(FILE_ID:optional, FILE_PATH:mandatory) + > Saves the W3D file from FILE_ID slot to FILE_PATH. + +SAVE_W3X(FILE_ID:optional, FILE_PATH:mandatory) + > Saves the W3X file from FILE_ID slot to FILE_PATH. + +SAVE_BLEND(FILE_ID:optional, FILE_PATH:mandatory) + > Saves the Blender (.blend) file from FILE_ID slot to FILE_PATH. + +SAVE_MAX(FILE_ID:optional, FILE_PATH:mandatory) + > Saves the 3ds Max (.max) file from FILE_ID slot to FILE_PATH. + +RESET(FILE_ID:optional) + > Resets all loaded data in the specified FILE_ID slot. + +SET_OPTIONS(FILE_ID:optional,OPTION:[n]optional) + > Sets options of OPTION in FILE_ID. +)#"); +// 1 2 3 4 5 6 7 8 9 10 11 12 +//3456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Print_Line( +R"#(Command Argument List ... + +FILE_ID:number +FILE_ID takes number and allows to manage multiple files in compiler. Default is 0. + +FILE_PATH:path +FILE_PATH takes any relative or absolute path. + +OPTION:enum +OPTION takes one [1] or multiple [n] options, separated by pipe: +None +)#"); +// 1 2 3 4 5 6 7 8 9 10 11 12 +//3456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Print_Line( +R"#(Simplified Command List ... + +Commands are executed in the order they are listed here. +All capital words are NOT interpreted keywords and are substituted by the command argument(s) of choice. +[1] and [n] words show that argument takes one or multiple values. +Commands and command arguments are not case sensitive. + +WARNING: Commands that save files will overwrite existing files without confirmation. + +-load_w3d filepath.w3d + > Loads a W3D file from the specified file path. + +-load_w3x filepath.w3x + > Loads a W3X file from the specified file path. + +-load_blend filepath.blend + > Loads a Blender file from the specified file path. + +-load_max filepath.max + > Loads a 3ds Max file from the specified file path. + +-save_w3d filepath.w3d + > Saves the loaded data as a W3D file to the given file path. + +-save_w3x filepath.w3x + > Saves the loaded data as a W3X file to the given file path. + +-save_blend filepath.blend + > Saves the loaded data as a Blender file to the given file path. + +-save_max filepath.max + > Saves the loaded data as a 3ds Max file to the given file path. + +Example 1: Convert a W3D file to W3X format + > GameModelConverter.exe -load_w3d D:\models\model.w3d -save_w3x D:\models\model.w3x + +Example 2: Convert a W3X file to W3D format + > GameModelConverter.exe -load_w3x D:\models\model.w3x -save_w3d D:\models\model.w3d + +Example 3: Load a Blender file and save as W3D + > GameModelConverter.exe -load_blend D:\models\model.blend -save_w3d D:\models\model.w3d + +Example 4: Load a W3D file and save as Max format + > GameModelConverter.exe -load_w3d D:\models\model.w3d -save_max D:\models\model.max +)#"); +} +// clang-format on + +void Print_Error(const Processor::Result &result, const Processor::CommandTexts &command_texts) +{ + const size_t command_index = result.error_command_index; + const char *result_name = Processor::Get_Result_Name(result.id); + const char *command_name = (command_index < command_texts.size()) ? command_texts[command_index] : ""; + const std::string error_str(result.error_text.begin(), result.error_text.end()); + + Print_Line("Execution stopped with error '%s' at command '%s' (%zu) and error string '%s'", + result_name, + command_name, + command_index, + error_str.c_str()); +} + + +namespace +{ +constexpr int NoError = 0; +constexpr int MissingArgumentsError = 1; +constexpr int ProcessorParseError = 2; +constexpr int ProcessorExecuteError = 3; +} // namespace + +LocalFileSystem *Create_Local_File_System() +{ + return new Win32LocalFileSystem; +} + +struct CaptainsLogCreator +{ + CaptainsLogCreator() + { + captains_settings_t captains_settings = { 0 }; + captains_settings.level = LOGLEVEL_DEBUG; + captains_settings.console = true; + captains_settings.print_file = true; + captainslog_init(&captains_settings); + }; + ~CaptainsLogCreator() { captainslog_deinit(); } +}; + +struct EngineSystemsCreator +{ + EngineSystemsCreator() + { + g_theSubsystemList = new SubsystemInterfaceList; + g_theFileSystem = new FileSystem; + Init_Subsystem(g_theLocalFileSystem, "TheLocalFileSystem", Create_Local_File_System()); + g_theLocalFileSystem->Init(); + } + ~EngineSystemsCreator() + { + delete g_theArchiveFileSystem; + delete g_theLocalFileSystem; + delete g_theFileSystem; + delete g_theSubsystemList; + g_theArchiveFileSystem = nullptr; + g_theLocalFileSystem = nullptr; + g_theFileSystem = nullptr; + g_theSubsystemList = nullptr; + } +}; + +int main(int argc, const char *argv[]) +{ + Print_Line("Game Model Converter v1.0 By The Assembly Armada"); + + if (argc < 2) { + Print_Help(); + return MissingArgumentsError; + } + GameModelFile::Set_Log_File(stderr); + CaptainsLogCreator captains_log_creator; + EngineSystemsCreator engine_systems_creator; + + const auto command_texts = rts::Make_Array_View(argv + 1, argc - 1); + Processor processor; + Processor::Result result = processor.Parse_Commands(command_texts); + + if (result.id != Processor::ResultId::SUCCESS) { + Print_Line("ERROR : Model Converter failed to parse commands"); + Print_Error(result, command_texts); + return ProcessorParseError; + } + + result = processor.Execute_Commands(); + + if (result.id != Processor::ResultId::SUCCESS) { + Print_Line("ERROR : Model Converter failed to execute commands"); + Print_Error(result, command_texts); + return ProcessorExecuteError; + } + + Print_Line("Model Converter completed successfully"); + return NoError; +} diff --git a/src/tools/gamemodelconverter/src/processor.cpp b/src/tools/gamemodelconverter/src/processor.cpp new file mode 100644 index 000000000..9debd4144 --- /dev/null +++ b/src/tools/gamemodelconverter/src/processor.cpp @@ -0,0 +1,618 @@ +/** + * @file + * + * @author xezon, DevGeniusCode + * + * @brief Model Converter Processor. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "processor.h" +#include "commands.h" +#include +#include + +namespace Thyme +{ +namespace +{ +constexpr const char *const s_result_names[] = { + "SUCCESS", + "INVALID_COMMAND_ACTION", + "INVALID_COMMAND_ARGUMENT", + "INVALID_OPTION_VALUE", + "INVALID_FILE_ID_ARGUMENT", + "MISSING_FILE_PATH_ARGUMENT", + "EXECUTION_ERROR", +}; + +static_assert(ARRAY_SIZE(s_result_names) == size_t(Processor::ResultId::COUNT)); +} // namespace + +const char *Processor::Get_Result_Name(ResultId id) +{ + return s_result_names[size_t(id)]; +} + +const Processor::FileId Processor::s_defaultFileId = { 0 }; + +Processor::Processor() : m_fileMap(), m_commands() {} + +Processor::Result Processor::Parse_Commands(const CommandTexts &command_texts) +{ + if (Has_Simple_Command(command_texts)) { + return Parse_Simple_Commands(command_texts); + } else { + return Parse_Function_Commands(command_texts); + } +} + +Processor::Result Processor::Execute_Commands() const +{ + Result result(ResultId::SUCCESS); + + for (const CommandPtr &command : m_commands) { + if (!command->Execute()) { + result.id = ResultId::EXECUTION_ERROR; + result.error_command_index = command->Id(); + break; + } + } + return result; +} + +Processor::Result Processor::Parse_Function_Commands(const CommandTexts &command_texts) +{ + Result result(ResultId::SUCCESS); + CommandPtrs commands; + + for (size_t index = 0; index < command_texts.size(); ++index) { + + CommandAction action; + result = Parse_Function_Command(action, command_texts[index], index); + + if (result.id != ResultId::SUCCESS) { + result.error_command_index = index; + break; + } + + result = Add_New_Command(commands, m_fileMap, action); + + if (result.id != ResultId::SUCCESS) { + result.error_command_index = index; + break; + } + } + + if (result.id == ResultId::SUCCESS) { + m_commands.swap(commands); + } + + return result; +} + +Processor::Result Processor::Parse_Simple_Commands(const CommandTexts &command_texts) +{ + Result result(ResultId::SUCCESS); + CommandActionSequence actions; + CommandPtrs commands; + + if (!command_texts.empty()) { + + const size_t size = command_texts.size(); + + for (size_t index = 0; index < size - 1; ++index) { + + if (!Is_Simple_Command(command_texts[index])) { + continue; + } + + const char *command_name = command_texts[index] + 1; + const char *command_value = command_texts[index + 1]; + + result = Parse_Simple_Command(actions, command_name, command_value, index); + + if (result.id != ResultId::SUCCESS) { + result.error_command_index = index; + break; + } + + ++index; + } + } + + if (result.id == ResultId::SUCCESS) { + + for (const CommandAction &action : actions) { + if (action.action_id != CommandActionId::INVALID) { + result = Add_New_Command(commands, m_fileMap, action); + + if (result.id != ResultId::SUCCESS) { + result.error_command_index = action.command_index; + break; + } + } + } + } + + if (result.id == ResultId::SUCCESS) { + m_commands.swap(commands); + } + + return result; +} + +bool Processor::Has_Simple_Command(const CommandTexts &command_texts) +{ + for (const char *command_text : command_texts) { + if (Is_Simple_Command(command_text)) { + return true; + } + } + return false; +} + +bool Processor::Is_Simple_Command(const char *command_text) +{ + return command_text != nullptr && *command_text == '-'; +} + +template bool Processor::Parse_Next_Word(std::string &word, const char *&str, const char (&separators)[Size]) +{ + const char *reader = str; + + do { + for (const char separator : separators) { + if (*reader == separator && reader > str) { + word.assign(str, reader); + str = reader; + if (*str != '\0') { + str += 1; + } + return true; + } + } + } while (*reader++ != '\0'); + + return false; +} + +Processor::Result Processor::Parse_Function_Command(CommandAction &action, const char *command_text, size_t command_index) +{ + Result result(ResultId::SUCCESS); + CommandActionId action_id = CommandActionId::INVALID; + CommandArgumentId argument_id = CommandArgumentId::INVALID; + CommandArguments arguments; + const char *reader = command_text; + std::string word; + + while (true) { + const char *word_begin = reader; + + if (reader == command_text && Parse_Next_Word(word, reader, { '(' })) { + if (!String_To_Command_Action_Id(word.c_str(), action_id)) { + result.id = ResultId::INVALID_COMMAND_ACTION; + result.error_text = ErrorText(word_begin, reader); + break; + } + + } else if (argument_id == CommandArgumentId::INVALID && Parse_Next_Word(word, reader, { ':' })) { + if (!String_To_Command_Argument_Id(word.c_str(), argument_id)) { + result.id = ResultId::INVALID_COMMAND_ARGUMENT; + result.error_text = ErrorText(word_begin, reader); + break; + } + arguments.emplace_back(); + + } else if (argument_id != CommandArgumentId::INVALID && Parse_Next_Word(word, reader, { ',', '|', ')' })) { + result = Parse_Command_Argument(arguments.back(), word, argument_id); + if (result.id != ResultId::SUCCESS) { + result.error_text = ErrorText(word_begin, reader); + break; + } + if (*(reader - 1) == ',') { + argument_id = CommandArgumentId::INVALID; + } + + } else { + break; + } + } + + if (action_id == CommandActionId::INVALID) { + result.id = ResultId::INVALID_COMMAND_ACTION; + } + + if (result.id == ResultId::SUCCESS) { + action.action_id = action_id; + action.arguments.swap(arguments); + action.command_index = command_index; + } + + return result; +} + +Processor::Result Processor::Parse_Simple_Command( + CommandActionSequence &actions, const char *command_name, const char *command_value, size_t command_index) +{ + Result result(ResultId::SUCCESS); + SimpleActionId simple_action_id; + + if (!String_To_Simple_Action_Id(command_name, simple_action_id)) { + result.id = ResultId::INVALID_COMMAND_ACTION; + result.error_text = ErrorText(command_name, command_name + strlen(command_name)); + return result; + } + + bool overwrite_action_id = false; + SequenceId sequence_id = SequenceId::INVALID; + CommandActionId action_id = CommandActionId::INVALID; + CommandArgumentId argument_id = CommandArgumentId::INVALID; + + switch (simple_action_id) { + case SimpleActionId::OPTIONS: + sequence_id = SequenceId::SET_OPTIONS; + action_id = CommandActionId::SET_OPTIONS; + argument_id = CommandArgumentId::OPTIONS; + break; + case SimpleActionId::LOAD_W3D_FILE: + sequence_id = SequenceId::LOAD; + action_id = CommandActionId::LOAD_W3D; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::LOAD_W3X_FILE: + sequence_id = SequenceId::LOAD; + action_id = CommandActionId::LOAD_W3X; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::LOAD_BLEND_FILE: + sequence_id = SequenceId::LOAD; + action_id = CommandActionId::LOAD_BLEND; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::LOAD_MAX_FILE: + sequence_id = SequenceId::LOAD; + action_id = CommandActionId::LOAD_MAX; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::SAVE_W3D: + sequence_id = SequenceId::SAVE; + action_id = CommandActionId::SAVE_W3D; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::SAVE_W3X: + sequence_id = SequenceId::SAVE; + action_id = CommandActionId::SAVE_W3X; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::SAVE_BLEND: + sequence_id = SequenceId::SAVE; + action_id = CommandActionId::SAVE_BLEND; + argument_id = CommandArgumentId::FILE_PATH; + break; + case SimpleActionId::SAVE_MAX: + sequence_id = SequenceId::SAVE; + action_id = CommandActionId::SAVE_MAX; + argument_id = CommandArgumentId::FILE_PATH; + break; + } + + static_assert(g_simpleActionCount == 9, "SimpleAction is missing"); + + captainslog_assert(sequence_id != SequenceId::INVALID); + + if (sequence_id != SequenceId::INVALID) { + CommandArgument argument; + + { + const char *reader = command_value; + const char *word_begin = reader; + std::string word; + + while (Parse_Next_Word(word, reader, { '|', '\0' })) { + result = Parse_Command_Argument(argument, word, argument_id); + if (result.id != ResultId::SUCCESS) { + result.error_text = ErrorText(word_begin, reader); + break; + } + word_begin = reader; + } + } + + if (result.id == ResultId::SUCCESS) { + CommandAction &action = actions[size_t(sequence_id)]; + + if (action.action_id == CommandActionId::INVALID || overwrite_action_id) { + action.action_id = action_id; + action.command_index = command_index; + } + action.arguments.emplace_back(std::move(argument)); + } + } + + return result; +} + +Processor::Result Processor::Parse_Command_Argument( + CommandArgument &argument, std::string &str, CommandArgumentId argument_id) +{ + Result result(ResultId::SUCCESS); + + switch (argument_id) { + case CommandArgumentId::FILE_ID: { + FileId file_id; + file_id.value = std::stoi(str); + argument.value.emplace(std::move(file_id)); + break; + } + case CommandArgumentId::FILE_PATH: { + FilePath file_path; + file_path.value = str.c_str(); + argument.value.emplace(std::move(file_path)); + break; + } + case CommandArgumentId::OPTIONS: { + GameModelOption option; + + if (Name_To_Game_Model_Option(str.c_str(), option)) { + GameModelOptions options; + const GameModelOptions *options_ptr = std::get_if(&argument.value); + if (options_ptr != nullptr) { + options = *options_ptr; + } + options |= option; + argument.value.emplace(std::move(options)); + + } else { + result.id = ResultId::INVALID_OPTION_VALUE; + } + break; + } + } + + static_assert(g_commandArgumentCount == 3, "CommandArgument is missing"); + + return result; +} + +Processor::Result Processor::Add_New_Command(CommandPtrs &commands, FileMap &file_map, const CommandAction &action) +{ + Result result(ResultId::SUCCESS); + + Populate_File_Map(file_map, action); + + switch (action.action_id) { + case CommandActionId::LOAD_W3D: + result = Add_Load_W3D_Command(commands, file_map, action); + break; + case CommandActionId::LOAD_W3X: + result = Add_Load_W3X_Command(commands, file_map, action); + break; + case CommandActionId::LOAD_BLEND: + result = Add_Load_BLEND_Command(commands, file_map, action); + break; + case CommandActionId::LOAD_MAX: + result = Add_Load_MAX_Command(commands, file_map, action); + break; + case CommandActionId::SAVE_W3D: + result = Add_Save_W3D_Command(commands, file_map, action); + break; + case CommandActionId::SAVE_W3X: + result = Add_Save_W3X_Command(commands, file_map, action); + break; + case CommandActionId::SAVE_BLEND: + result = Add_Save_BLEND_Command(commands, file_map, action); + break; + case CommandActionId::SAVE_MAX: + result = Add_Save_MAX_Command(commands, file_map, action); + break; + case CommandActionId::RESET: + result = Add_Reset_Command(commands, file_map, action); + break; + case CommandActionId::SET_OPTIONS: + result = Add_Set_Options_Command(commands, file_map, action); + break; + } + + static_assert(g_commandActionCount == 10, "CommandAction is missing"); + + if (result.id == ResultId::SUCCESS) { + commands.back()->Set_Id(action.command_index); + } + + return result; +} + +Processor::Result Processor::Add_Load_W3D_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new LoadW3DCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Load_W3X_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new LoadW3XCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Load_BLEND_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new LoadBlendCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Load_MAX_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new LoadMaxCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Save_W3D_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new SaveW3DCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Save_W3X_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new SaveW3XCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Save_BLEND_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new SaveBlendCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Save_MAX_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto file_path = Get_File_Path(action.arguments); + + if (file_path == nullptr) { + return Result(ResultId::MISSING_FILE_PATH_ARGUMENT); + } + + commands.emplace_back(new SaveMaxCommand(file_ptr, file_path)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Reset_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + + commands.emplace_back(new ResetCommand(file_ptr)); + return Result(ResultId::SUCCESS); +} + +Processor::Result Processor::Add_Set_Options_Command( + CommandPtrs &commands, const FileMap &file_map, const CommandAction &action) +{ + const auto file_ptr = Get_File_Ptr(action.arguments, file_map); + const auto options = Get_Options(action.arguments); + + commands.emplace_back(new SetOptionsCommand(file_ptr, options)); + return Result(ResultId::SUCCESS); +} + +void Processor::Populate_File_Map(FileMap &file_map, const CommandAction &action) +{ + const FileId *file_id_ptr = Find_Ptr(action.arguments); + const FileId file_id = (file_id_ptr == nullptr) ? s_defaultFileId : *file_id_ptr; + Populate_File_Map(file_map, file_id); +} + +void Processor::Populate_File_Map(FileMap &file_map, FileId file_id) +{ + const FileMap::iterator it = file_map.find(file_id); + if (it == file_map.end()) { + auto *file = new GameModelFile(); + file->Set_Options(GameModelOptions::Value::NONE); + file_map.emplace(file_id, file); + } +} + +GameModelFilePtr Processor::Get_File_Ptr(const FileMap &file_map, FileId file_id) +{ + const FileMap::const_iterator it = file_map.find(file_id); + captainslog_assert(it != file_map.end()); + return it->second; +} + +template const Type *Processor::Find_Ptr(const CommandArguments &arguments, size_t occurence) +{ + size_t num = 0; + for (const CommandArgument &argument : arguments) { + const Type *type_ptr = std::get_if(&argument.value); + if (type_ptr != nullptr) { + if (occurence == num++) { + return type_ptr; + } + } + } + return nullptr; +} + +GameModelFilePtr Processor::Get_File_Ptr(const CommandArguments &arguments, const FileMap &file_map, size_t occurence) +{ + const FileId *ptr = Find_Ptr(arguments, occurence); + const FileId file_id = (ptr == nullptr) ? s_defaultFileId : *ptr; + return Get_File_Ptr(file_map, file_id); +} + +const char *Processor::Get_File_Path(const CommandArguments &arguments, size_t occurence) +{ + const FilePath *ptr = Find_Ptr(arguments, occurence); + return (ptr == nullptr) ? nullptr : ptr->value.c_str(); +} + +GameModelOptions Processor::Get_Options(const CommandArguments &arguments, size_t occurence) +{ + const GameModelOptions *ptr = Find_Ptr(arguments, occurence); + return (ptr == nullptr) ? GameModelOptions() : *ptr; +} + +} // namespace Thyme diff --git a/src/tools/gamemodelconverter/src/processor.h b/src/tools/gamemodelconverter/src/processor.h new file mode 100644 index 000000000..2ab4f62f0 --- /dev/null +++ b/src/tools/gamemodelconverter/src/processor.h @@ -0,0 +1,153 @@ +/** + * @file + * + * @author xezon, DevGeniusCode + * + * @brief Model Converter Processor. (Thyme Feature) + * + * @copyright Thyme 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. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once + +#include "commands.h" +#include +#include +#include +#include +#include + +namespace Thyme +{ +class Processor +{ +public: + enum class ResultId + { + SUCCESS, + INVALID_COMMAND_ACTION, + INVALID_COMMAND_ARGUMENT, + INVALID_OPTION_VALUE, + INVALID_FILE_ID_ARGUMENT, + MISSING_FILE_PATH_ARGUMENT, + EXECUTION_ERROR, + + // #TODO Add more detailed execution errors once GameModelFile supports it + + COUNT + }; + + static const char *Get_Result_Name(ResultId id); + + using ErrorText = rts::array_view; + using CommandTexts = rts::array_view; + + struct Result + { + explicit Result(ResultId id) : id(id), error_command_index(0), error_text() {} + + ResultId id; + size_t error_command_index; + ErrorText error_text; + }; + +public: + Processor(); + + Result Parse_Commands(const CommandTexts &command_texts); + Result Execute_Commands() const; + +private: + using CommandPtr = std::shared_ptr; + using CommandPtrs = std::vector; + + struct FileId + { + bool operator==(const FileId &other) const { return value == other.value; } + int value; + }; + + struct FileIdHash + { + std::size_t operator()(const FileId &key) const { return std::hash{}(key.value); } + }; + using FileMap = std::unordered_map; + + struct FilePath + { + std::string value; + }; + + struct CommandArgument + { + std::variant value; + }; + + using CommandArguments = std::vector; + + struct CommandAction + { + CommandActionId action_id = CommandActionId::INVALID; + CommandArguments arguments; + size_t command_index = 0; + }; + + enum class SequenceId + { + INVALID = -1, + SET_OPTIONS, + LOAD, + SAVE, + + COUNT + }; + + using CommandActionSequence = std::array; + +private: + Result Parse_Function_Commands(const CommandTexts &command_texts); + Result Parse_Simple_Commands(const CommandTexts &command_texts); + + static bool Has_Simple_Command(const CommandTexts &command_texts); + static bool Is_Simple_Command(const char *command_text); + + template bool static Parse_Next_Word(std::string &word, const char *&str, const char (&separators)[Size]); + + static Result Parse_Function_Command(CommandAction &action, const char *command_text, size_t command_index); + static Result Parse_Simple_Command( + CommandActionSequence &actions, const char *command_name, const char *command_value, size_t command_index); + static Result Parse_Command_Argument(CommandArgument &argument, std::string &str, CommandArgumentId argument_id); + + static Result Add_New_Command(CommandPtrs &commands, FileMap &file_map, const CommandAction &action); + static Result Add_Load_W3D_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Load_W3X_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Load_BLEND_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Load_MAX_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Save_W3D_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Save_W3X_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Save_BLEND_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Save_MAX_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Reset_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + static Result Add_Set_Options_Command(CommandPtrs &commands, const FileMap &file_map, const CommandAction &action); + + static void Populate_File_Map(FileMap &file_map, const CommandAction &action); + static void Populate_File_Map(FileMap &file_map, FileId file_id); + static GameModelFilePtr Get_File_Ptr(const FileMap &file_map, FileId file_id); + + template static const Type *Find_Ptr(const CommandArguments &arguments, size_t occurence = 0); + static GameModelFilePtr Get_File_Ptr(const CommandArguments &arguments, const FileMap &file_map, size_t occurence = 0); + static const char *Get_File_Path(const CommandArguments &arguments, size_t occurence = 0); + static GameModelOptions Get_Options(const CommandArguments &arguments, size_t occurence = 0); + +private: + FileMap m_fileMap; + CommandPtrs m_commands; + + static const FileId s_defaultFileId; +}; + +} // namespace Thyme diff --git a/src/tools/gamemodelconverter/src/readme.md b/src/tools/gamemodelconverter/src/readme.md new file mode 100644 index 000000000..c78a4503a --- /dev/null +++ b/src/tools/gamemodelconverter/src/readme.md @@ -0,0 +1,24 @@ +The main goal of the project is to create a set of tools that allow the conversion between different file formats used +in 3D modeling for game development. Specifically, the tools will focus on converting **W3D** files (binary format) into +**W3X** (XML format), and also facilitate conversion between W3D and other formats like **Blend** (Blender files) and * +*Max** (3ds Max files). Here's a breakdown of the key points: + +1. **W3D to W3X Conversion**: The first task is to convert binary W3D files into a text-based W3X format. This would + allow better compatibility and transparency because W3X is an XML format, which is easier to parse and edit compared + to the binary W3D format. + +2. **Thyme as the Starting Point**: The development of the **W3D to W3X** conversion tool will begin by using **existing + code in Thyme**, which will help maintain consistency and compatibility across tools and workflows. + +3. **Maintaining Consistency with Mod Builder**: Once W3X files are created, the Mod Builder tool will allow conversion + between W3D, Blend, and Max files. The goal is to ensure that these conversions are **non-destructive**, meaning that + converting a Blend or Max file back to W3D should not introduce any errors or issues. The **Blend to W3D** conversion + is already supported by Mod Builder, but there are concerns about potential issues with the Blender plugin causing + data loss on import. + +4. **W3D as the "File of Truth"**: Throughout this process, the W3D file is considered the authoritative format. W3X is + a **faithful representation** of the W3D file, while Blend and Max files are **non-destructive** representations, + meaning they can be used for editing and modeling without corrupting or altering the original W3D file. + +In summary, the project involves creating a reliable pipeline that can convert between W3D, W3X, Blend, and Max files, +using W3D as the reference format and leveraging existing tools like Thyme and Mod Builder. \ No newline at end of file