diff --git a/.gitmodules b/.gitmodules index c9383a3..7ec5876 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "extern/CommonLibSSE"] path = external/CommonLibSSE url = https://github.com/Ryan-rsm-McKenzie/CommonLibSSE -[submodule "external/FlashLibSkyrim"] - path = external/FlashLibSkyrim - url = https://github.com/Exit-9B/FlashLibSkyrim [submodule "tools/SKSE-CMakeModules"] path = tools/SKSE-CMakeModules url = https://github.com/Exit-9B/SKSE-CMakeModules.git diff --git a/CMakeLists.txt b/CMakeLists.txt index ffcf403..84162c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.24) project( MapMarkerFramework - VERSION 2.2.0 + VERSION 2.2.1 LANGUAGES CXX ) @@ -30,15 +30,12 @@ SKSEPlugin_Add( src/PCH/PCH.h ) -add_subdirectory("external/FlashLibSkyrim" FlashLibSkyrim EXCLUDE_FROM_ALL) - find_package(jsoncpp REQUIRED) find_package(tsl-ordered-map CONFIG REQUIRED) target_link_libraries( ${PROJECT_NAME} PRIVATE - FlashLibSkyrim jsoncpp_static tsl::ordered_map ) diff --git a/external/FlashLibSkyrim b/external/FlashLibSkyrim deleted file mode 160000 index 945ec88..0000000 --- a/external/FlashLibSkyrim +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 945ec880e927547a755f45797c68508820eea3dc diff --git a/src/PCH/PCH.h b/src/PCH/PCH.h index 0eef552..e4bbc36 100644 --- a/src/PCH/PCH.h +++ b/src/PCH/PCH.h @@ -7,7 +7,6 @@ #include #include "RE/Offset.Ext.h" -#include "REL/Pattern.h" #ifdef NDEBUG #include diff --git a/src/RE/Offset.Ext.h b/src/RE/Offset.Ext.h index f4f9ffb..d304b57 100644 --- a/src/RE/Offset.Ext.h +++ b/src/RE/Offset.Ext.h @@ -2,12 +2,47 @@ namespace Offset { + namespace GASActionBufferData + { + inline constexpr REL::ID Vtbl(242366); + } + + namespace GASDoAction + { + inline constexpr REL::ID Vtbl(242413); + } + + namespace GFxInitImportActions + { + inline constexpr REL::ID Vtbl(244866); + } + namespace GFxMovieDefImpl { // SkyrimSE 1.6.318.0: 0x18D0260 inline constexpr REL::ID Vtbl(243274); } + namespace GFxPlaceObject2 + { + inline constexpr REL::ID Vtbl(242592); + } + + namespace GFxPlaceObject3 + { + inline constexpr REL::ID Vtbl(242593); + } + + namespace GFxRemoveObject + { + inline constexpr REL::ID Vtbl(244863); + } + + namespace GFxRemoveObject2 + { + inline constexpr REL::ID Vtbl(244864); + } + namespace HUDMenu { // SkyrimSE 1.6.318.0: 0x8AC4F0 diff --git a/src/REL/Pattern.h b/src/REL/Pattern.h deleted file mode 100644 index 9671c30..0000000 --- a/src/REL/Pattern.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -namespace REL -{ - namespace detail - { - struct optional_byte - { - constexpr optional_byte() {} - constexpr optional_byte(std::nullopt_t) {} - constexpr optional_byte(std::uint8_t a_value) : has_value(true), value(a_value) {} - - optional_byte& operator=(std::nullopt_t) - { - has_value = false; - return *this; - } - - optional_byte& operator=(std::uint8_t a_value) - { - has_value = true; - value = a_value; - return *this; - } - - bool has_value = false; - std::uint8_t value = 0; - }; - - template requires (N % 3 == 0) - struct pattern_impl - { - static constexpr auto Size = N / 3; - - constexpr pattern_impl(const char(&a_str)[N]) - { - for (std::size_t i = 0; i < Size; i++) { - data[i] = parse_byte(a_str[i * 3], a_str[i * 3 + 1]); - } - } - - [[nodiscard]] bool match(const void* a_address) const - { - for (std::size_t i = 0; i < Size; i++) { - if (!data[i].has_value) { - continue; - } - - if (data[i].value != static_cast(a_address)[i]) { - return false; - } - } - - return true; - } - - [[nodiscard]] static constexpr optional_byte parse_byte(char a_char1, char a_char2) - { - if (a_char1 == '?' && a_char2 == '?') { - return std::nullopt; - } - - return parse_hex(a_char1) * 0x10 + parse_hex(a_char2); - } - - [[nodiscard]] static constexpr std::uint8_t parse_hex(char a_char) - { - if (a_char >= '0' && a_char <= '9') { - return a_char - '0'; - } - else if (a_char >= 'A' && a_char <= 'F') { - return a_char + 0xA - 'A'; - } - else if (a_char >= 'a' && a_char <= 'f') { - return a_char + 0xA - 'a'; - } - } - - optional_byte data[Size]; - }; - } - - template - struct Pattern - { - [[nodiscard]] bool match(std::uintptr_t a_address) const - { - return Impl.match(reinterpret_cast(a_address)); - } - }; -} diff --git a/src/SWF/ActionGenerator.cpp b/src/SWF/ActionGenerator.cpp new file mode 100644 index 0000000..7c78f2f --- /dev/null +++ b/src/SWF/ActionGenerator.cpp @@ -0,0 +1,287 @@ +#include "SWF/ActionGenerator.h" + +namespace SWF +{ + void ActionGenerator::Ready() + { + _undefinedLabels.clear(); + + std::int16_t constantPoolSize = _constantPoolSize; + FlushConstantPool(); + + _committed.Write(0); + + auto view = _committed.Get(); + auto code = InitBuffer(view.size()); + std::memcpy(code, view.data(), view.size()); + + for (auto& [writePos, offset] : _definedLabels) { + std::int16_t pos = writePos + constantPoolSize; + code[pos] = offset & 0xFF; + code[pos + 1] = (offset >> 8) & 0xFF; + } + } + + auto ActionGenerator::GetCode() -> RE::GASActionBufferData* + { + return _bufferData; + } + + void ActionGenerator::FlushConstantPool() + { + if (!_constantPool.empty()) { + + std::uint16_t size = _constantPoolSize - 3; + std::uint16_t count = static_cast(_constantPool.size()); + + _committed.WriteUI8(0x88); + _committed.WriteUI16(size); + _committed.WriteUI16(count); + + for (auto& [value, _] : _constantPool) { + _committed.WriteSTRING(value.data()); + } + + _constantPool.clear(); + _constantPoolSize = 0; + } + + _committed.Write(_temporary.Get()); + _temporary.Clear(); + } + + // Stack operations + + void ActionGenerator::Push([[maybe_unused]] std::nullptr_t a_value) + { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(1); + _temporary.WriteUI8(2); + } + + void ActionGenerator::Push(std::int32_t a_value) + { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(5); + _temporary.WriteUI8(7); + _temporary.WriteSI32(a_value); + } + + void ActionGenerator::Push(float a_value) + { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(5); + _temporary.WriteUI8(1); + _temporary.WriteFLOAT(a_value); + } + + void ActionGenerator::Push(double a_value) + { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(9); + _temporary.WriteUI8(6); + _temporary.WriteDOUBLE(a_value); + } + + void ActionGenerator::Push(const std::string& a_value, bool a_useConstantPool) + { + if (a_useConstantPool) { + std::uint16_t registerNum; + + auto item = _constantPool.find(a_value); + if (item != _constantPool.end()) { + registerNum = item->second; + } + else { + if (_constantPool.empty()) { + _constantPoolSize += 5; + } + + registerNum = static_cast(_constantPool.size()); + _constantPool[a_value] = registerNum; + _constantPoolSize += static_cast(a_value.length() + 1); + } + + if (registerNum < 0x100) { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(2); + _temporary.WriteUI8(8); + _temporary.WriteUI8(registerNum & 0xFF); + } + else { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(2); + _temporary.WriteUI8(9); + _temporary.WriteUI16(registerNum); + } + } + else { + _temporary.WriteUI8(0x96); + _temporary.WriteUI16(2); + _temporary.WriteUI8(0); + _temporary.WriteSTRING(a_value.data()); + } + } + + // Arithmetic operators + + void ActionGenerator::Add() + { + _temporary.WriteUI8(0x0A); + } + + void ActionGenerator::Subtract() + { + _temporary.WriteUI8(0x0B); + } + + void ActionGenerator::Multiply() + { + _temporary.WriteUI8(0x0C); + } + + void ActionGenerator::Divide() + { + _temporary.WriteUI8(0x0D); + } + + // Numerical comparison + + void ActionGenerator::Equals2() + { + _temporary.WriteUI8(0x49); + } + + // Logical operators + + void ActionGenerator::Not() + { + _temporary.WriteUI8(0x12); + } + + // Control flow + + void ActionGenerator::Jump(Label& a_label) + { + std::int16_t programCounter = GetPos() + 5; + + _temporary.WriteUI8(0x99); + _temporary.WriteUI16(2); + + if (!a_label.defined) { + std::int16_t writePos = GetPos(); + AddUndefinedLabel(a_label, writePos, programCounter); + } + + _temporary.WriteSI16(a_label.loc - programCounter); + } + + void ActionGenerator::If(Label& a_label) + { + std::int16_t programCounter = GetPos() + 5; + + _temporary.WriteUI8(0x9D); + _temporary.WriteUI16(2); + + if (!a_label.defined) { + std::int16_t writePos = GetPos(); + AddUndefinedLabel(a_label, writePos, programCounter); + } + + _temporary.WriteSI16(a_label.loc - programCounter); + } + + void ActionGenerator::L([[maybe_unused]] Label& a_label) + { + a_label.defined = true; + a_label.loc = GetPos(); + + auto [begin, end] = _undefinedLabels.equal_range(std::addressof(a_label)); + for (auto it = begin; it != end; ++it) { + auto& labelRef = it->second; + std::int16_t offset = a_label.loc - labelRef.programCounter; + _definedLabels[labelRef.writePos] = offset; + } + } + + // Variables + + void ActionGenerator::GetVariable() + { + _temporary.WriteUI8(0x1C); + } + + void ActionGenerator::SetVariable() + { + _temporary.WriteUI8(0x1D); + } + + // Script Object actions + + void ActionGenerator::DefineLocal() + { + _temporary.WriteUI8(0x3C); + } + + void ActionGenerator::GetMember() + { + _temporary.WriteUI8(0x4E); + } + + void ActionGenerator::SetMember() + { + _temporary.WriteUI8(0x4F); + } + + // Other + + void ActionGenerator::InstanceOf() + { + _temporary.WriteUI8(0x54); + } + + auto ActionGenerator::GetPos() -> std::int16_t + { + std::int16_t committedPos = static_cast(_committed.GetPos()); + std::int16_t tempPos = static_cast(_temporary.GetPos()); + + return committedPos + tempPos; + } + + void ActionGenerator::AddUndefinedLabel( + Label& a_label, + std::int16_t a_writePos, + std::int16_t a_programPos) + { + _undefinedLabels.insert( + { + std::addressof(a_label), + LabelRef{ + a_writePos, + a_programPos, + } + }); + } + + auto ActionGenerator::InitBuffer(std::size_t a_size) -> std::uint8_t* + { + static REL::Relocation GASActionBufferData_vtbl{ + Offset::GASActionBufferData::Vtbl + }; + + _bufferData = static_cast( + RE::GMemory::Alloc(sizeof(RE::GASActionBufferData))); + + std::memset(_bufferData, 0, sizeof(RE::GASActionBufferData)); + + *reinterpret_cast(_bufferData) = GASActionBufferData_vtbl.get(); + + auto buffer = RE::GMemory::AllocAutoHeap(_bufferData, a_size); + + _bufferData->buffer = buffer; + _bufferData->size = a_size; + _bufferData->unk20 = 0; + + return static_cast(buffer); + } +} diff --git a/src/SWF/ActionGenerator.h b/src/SWF/ActionGenerator.h new file mode 100644 index 0000000..dacffca --- /dev/null +++ b/src/SWF/ActionGenerator.h @@ -0,0 +1,90 @@ +#pragma once + +#include "SWFOutputStream.h" + +namespace SWF +{ + class ActionGenerator + { + public: + void Ready(); + auto GetCode() -> RE::GASActionBufferData*; + + protected: + struct Label + { + bool defined = false; + std::int16_t loc = 0; + }; + + // Stack operations + + void Push(std::nullptr_t a_value); + void Push(std::int32_t a_value); + void Push(float a_value); + void Push(double a_value); + void Push(const std::string& a_value, bool a_useConstantPool = true); + + // Arithmetic operators + + void Add(); + void Subtract(); + void Multiply(); + void Divide(); + + // Numerical comparison + + void Equals2(); + + // Logical operators + + void Not(); + + // Control flow + + void Jump(Label& a_label); + void If(Label& a_label); + void L(Label& a_label); + + // Variables + + void GetVariable(); + void SetVariable(); + + // ScriptObject actions + + void DefineLocal(); + void GetMember(); + void SetMember(); + + // Other + + void InstanceOf(); + + private: + struct LabelRef + { + std::int16_t writePos; + std::int16_t programCounter; + }; + + // Note: currently private because it could break labels + void FlushConstantPool(); + + auto GetPos() -> std::int16_t; + + void AddUndefinedLabel(Label& a_label, std::int16_t a_writePos, std::int16_t a_programPos); + + auto InitBuffer(std::size_t a_size) -> std::uint8_t*; + + RE::GASActionBufferData* _bufferData; + SWFOutputStream _committed; + SWFOutputStream _temporary; + + tsl::ordered_map _constantPool; + std::int16_t _constantPoolSize = 0; + + std::unordered_multimap _undefinedLabels; + std::unordered_map _definedLabels; + }; +} diff --git a/src/SWF/SWFOutputStream.cpp b/src/SWF/SWFOutputStream.cpp new file mode 100644 index 0000000..3acae72 --- /dev/null +++ b/src/SWF/SWFOutputStream.cpp @@ -0,0 +1,374 @@ +#include "SWF/SWFOutputStream.h" + +namespace SWF +{ + void SWFOutputStream::Write(std::uint8_t a_byte) + { + AlignByte(); + _os << static_cast(a_byte); + _pos++; + } + + void SWFOutputStream::Write(std::string_view a_data) + { + AlignByte(); + _os << a_data; + _pos += a_data.length() + 1; + } + + void SWFOutputStream::WriteUB(std::int32_t a_nBits, std::int64_t a_value) + { + for (std::int32_t bit = 0; bit < a_nBits; bit++) { + std::int8_t nb = static_cast((a_value >> (a_nBits - 1 - bit)) & 1); + _tempByte += nb * (1 << (7 - _bitPos)); + _bitPos++; + if (_bitPos == 8) { + _bitPos = 0; + Write(_tempByte); + _tempByte = 0; + } + } + } + + void SWFOutputStream::WriteSB(std::int32_t a_nBits, std::int64_t a_value) + { + WriteUB(a_nBits, a_value); + } + + void SWFOutputStream::WriteFB(std::int32_t a_nBits, float a_value) + { + WriteSB(a_nBits, static_cast(a_value * (1 << 16))); + } + + void SWFOutputStream::WriteSI16(std::int16_t a_value) + { + WriteUI16(static_cast(a_value)); + } + + void SWFOutputStream::WriteSI32(std::int32_t a_value) + { + WriteUI32(static_cast(a_value)); + } + + void SWFOutputStream::WriteUI8(std::uint8_t a_value) + { + Write(a_value); + } + + void SWFOutputStream::WriteUI16(std::uint16_t a_value) + { + Write(a_value & 0xFF); + Write(((a_value) >> 8) & 0xFF); + } + + void SWFOutputStream::WriteUI32(std::uint32_t a_value) + { + Write(a_value & 0xFF); + Write((a_value >> 8) & 0xFF); + Write((a_value >> 16) & 0xFF); + Write((a_value >> 24) & 0xFF); + } + + void SWFOutputStream::WriteLong(std::uint64_t a_value) + { + Write((a_value >> 32) & 0xFF); + Write((a_value >> 40) & 0xFF); + Write((a_value >> 48) & 0xFF); + Write((a_value >> 56) & 0xFF); + Write(a_value & 0xFF); + Write((a_value >> 8) & 0xFF); + Write((a_value >> 16) & 0xFF); + Write((a_value >> 24) & 0xFF); + } + + void SWFOutputStream::WriteFIXED(double a_value) + { + WriteUI32(static_cast(a_value * (1 << 16))); + } + + void SWFOutputStream::WriteFIXED8(float a_value) + { + WriteUI16(static_cast(a_value * (1 << 8))); + } + + void SWFOutputStream::WriteFLOAT(float a_value) + { + union FloatToInt + { + float value; + std::uint32_t intBits; + char bytes[sizeof(double)]; + }; + + FloatToInt cv{ .value = a_value }; + + WriteUI32(cv.intBits); + } + + void SWFOutputStream::WriteDOUBLE(double a_value) + { + union DoubleToInt + { + double value; + std::uint64_t intBits; + }; + + DoubleToInt cv{ .value = a_value }; + + WriteLong(cv.intBits); + } + + void SWFOutputStream::WriteSTRING(const char* a_value) + { + if (!a_value) { + return; + } + + Write(std::string(a_value)); + Write(0); + } + + void SWFOutputStream::WriteRGBA(RE::GColor a_value) + { + WriteUI8(a_value.colorData.channels.red); + WriteUI8(a_value.colorData.channels.green); + WriteUI8(a_value.colorData.channels.blue); + WriteUI8(a_value.colorData.channels.alpha); + } + + void SWFOutputStream::WriteMATRIX(const RE::GMatrix2D& a_value) + { + float sx = a_value.data[0][0]; + float sy = a_value.data[1][1]; + float skx = a_value.data[0][1]; + float sky = a_value.data[1][0]; + float tx = a_value.data[0][2]; + float ty = a_value.data[1][2]; + + bool hasScale = sx != 1.0f || sy != 1.0f; + WriteUB(1, hasScale ? 1 : 0); + if (hasScale) { + std::int32_t nBits = GetNeededBitsF(sx); + nBits = (std::max)(nBits, GetNeededBitsF(sy)); + + WriteUB(5, nBits); + WriteFB(nBits, sx); + WriteFB(nBits, sy); + } + + bool hasRotate = skx != 0.0f || sky != 0.0f; + WriteUB(1, hasRotate ? 1 : 0); + if (hasRotate) { + std::int32_t nBits = GetNeededBitsF(skx); + nBits = (std::max)(nBits, GetNeededBitsF(sky)); + + WriteUB(5, nBits); + WriteFB(nBits, skx); + WriteFB(nBits, sky); + } + + std::int32_t nTranslateBits = GetNeededBitsF(tx); + nTranslateBits = (std::max)(nTranslateBits, GetNeededBitsF(ty)); + + WriteUB(5, nTranslateBits); + WriteFB(nTranslateBits, tx); + WriteFB(nTranslateBits, ty); + + AlignByte(); + } + + void SWFOutputStream::WriteCXFORMWITHALPHA(const RE::GRenderer::Cxform& a_value) + { + enum : std::size_t + { + kR, + kG, + kB, + kA, + kRGBA + }; + + enum + { + kMult, + kAdd, + kMultAdd + }; + + // clang-format off + bool hasAddTerms = + a_value.matrix[kR][kAdd] != 0.0f || + a_value.matrix[kG][kAdd] != 0.0f || + a_value.matrix[kB][kAdd] != 0.0f || + a_value.matrix[kA][kAdd] != 0.0f; + + WriteUB(1, hasAddTerms ? 1 : 0); + + bool hasMultTerms = + a_value.matrix[kR][kMult] != 1.0f || + a_value.matrix[kG][kMult] != 1.0f || + a_value.matrix[kB][kMult] != 1.0f || + a_value.matrix[kA][kMult] != 1.0f; + + WriteUB(1, hasMultTerms ? 1 : 0); + // clang-format on + + std::int32_t nBits = 1; + if (hasMultTerms) { + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kR][kMult])); + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kG][kMult])); + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kB][kMult])); + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kA][kMult])); + } + if (hasAddTerms) { + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kR][kAdd])); + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kG][kAdd])); + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kB][kAdd])); + nBits = (std::max)(nBits, GetNeededBitsF(a_value.matrix[kA][kAdd])); + } + + WriteUB(4, nBits); + if (hasMultTerms) { + WriteFB(nBits, a_value.matrix[kR][kMult]); + WriteFB(nBits, a_value.matrix[kG][kMult]); + WriteFB(nBits, a_value.matrix[kB][kMult]); + WriteFB(nBits, a_value.matrix[kA][kMult]); + } + if (hasAddTerms) { + WriteFB(nBits, a_value.matrix[kR][kAdd]); + WriteFB(nBits, a_value.matrix[kG][kAdd]); + WriteFB(nBits, a_value.matrix[kB][kAdd]); + WriteFB(nBits, a_value.matrix[kA][kAdd]); + } + + AlignByte(); + } + + void SWFOutputStream::WriteFILTERLIST(const RE::GArray& a_value) + { + assert(a_value.GetSize() <= 255); + WriteUI8(static_cast(a_value.GetSize())); + for (auto& filter : a_value) { + WriteFILTER(filter); + } + } + + void SWFOutputStream::WriteFILTER(const Filter& a_value) + { + using FilterMode = RE::GRenderer::FilterModes; + FilterType filterType = static_cast(a_value.filterType.underlying() & 0xF); + + RE::stl::enumeration filterMode{ static_cast( + a_value.blurFilterParams.mode) }; + + bool knockOut = a_value.filterType.all(FilterType::kFlag_KnockOut) || + filterMode.all(FilterMode::Filter_Knockout); + + bool compositeSource = a_value.filterType.none(FilterType::kFlag_HideObject) && + filterMode.none(FilterMode::Filter_HideObject); + + switch (filterType) { + case FilterType::kDropShadow: + WriteRGBA(a_value.blurFilterParams.color); + WriteFIXED(a_value.blurFilterParams.blurX); + WriteFIXED(a_value.blurFilterParams.blurY); + WriteFIXED(a_value.angle); + WriteFIXED(a_value.distance); + WriteFIXED8(a_value.blurFilterParams.strength); + WriteUB(1, filterMode.all(FilterMode::Filter_Inner)); + WriteUB(1, knockOut); + WriteUB(1, compositeSource); + WriteUB(5, a_value.blurFilterParams.passes); + break; + case FilterType::kBlur: + WriteFIXED(a_value.blurFilterParams.blurX); + WriteFIXED(a_value.blurFilterParams.blurY); + WriteUB(5, a_value.blurFilterParams.passes); + WriteUB(3, 0); + break; + case FilterType::kGlow: + WriteRGBA(a_value.blurFilterParams.color); + WriteFIXED(a_value.blurFilterParams.blurX); + WriteFIXED(a_value.blurFilterParams.blurY); + WriteFIXED8(a_value.blurFilterParams.strength); + WriteUB(1, filterMode.all(FilterMode::Filter_Inner)); + WriteUB(1, knockOut); + WriteUB(1, compositeSource); + WriteUB(5, a_value.blurFilterParams.passes); + break; + case FilterType::kBevel: + WriteRGBA(a_value.blurFilterParams.color); + WriteRGBA(a_value.blurFilterParams.color2); + WriteFIXED(a_value.blurFilterParams.blurX); + WriteFIXED(a_value.blurFilterParams.blurY); + WriteFIXED(a_value.angle); + WriteFIXED(a_value.distance); + WriteFIXED8(a_value.blurFilterParams.strength); + WriteUB(1, filterMode.all(FilterMode::Filter_Inner)); + WriteUB(1, knockOut); + WriteUB(1, compositeSource); + WriteUB(1, 0); + WriteUB(4, a_value.blurFilterParams.passes); + break; + case FilterType::kGradientGlow: + // not supported + break; + case FilterType::kConvolution: + // not supported + break; + case FilterType::kAdjustColor: + for (std::int32_t i = 0; i < 20; i++) { + WriteFLOAT(a_value.colorMatrix[i]); + } + break; + case FilterType::kGradientBevel: + // not supported + break; + } + } + + auto SWFOutputStream::Get() -> std::string_view + { + _os.flush(); + return _os.view(); + } + + auto SWFOutputStream::GetPos() -> std::int64_t + { + return _pos; + } + + void SWFOutputStream::Clear() + { + _os.str(""s); + _pos = 0; + _bitPos = 0; + _tempByte = 0; + } + + void SWFOutputStream::AlignByte() + { + if (_bitPos > 0) { + _bitPos = 0; + Write(_tempByte); + _tempByte = 0; + } + } + + auto SWFOutputStream::GetNeededBitsS(std::int32_t a_value) -> std::int32_t + { + std::int32_t counter = 32; + std::uint32_t mask = 0x80000000; + std::int32_t val = std::abs(a_value); + while (((val & mask) == 0) && (counter > 0)) { + mask >>= 1; + counter -= 1; + } + return counter + 1; + } + + auto SWFOutputStream::GetNeededBitsF(float a_value) -> std::int32_t + { + return GetNeededBitsS(static_cast(a_value)) + 16; + } +} diff --git a/src/SWF/SWFOutputStream.h b/src/SWF/SWFOutputStream.h new file mode 100644 index 0000000..fcd185e --- /dev/null +++ b/src/SWF/SWFOutputStream.h @@ -0,0 +1,49 @@ +#pragma once + +namespace SWF +{ + class SWFOutputStream + { + public: + using Filter = RE::GFxPlaceObjectUnpackedData::Filter; + using FilterType = RE::GFxPlaceObjectUnpackedData::FilterType; + + void Write(std::uint8_t a_value); + void Write(std::string_view a_data); + + void WriteUB(std::int32_t a_nBits, std::int64_t a_value); + void WriteSB(std::int32_t a_nBits, std::int64_t a_value); + void WriteFB(std::int32_t a_nBits, float a_value); + void WriteSI16(std::int16_t a_value); + void WriteSI32(std::int32_t a_value); + void WriteUI8(std::uint8_t a_value); + void WriteUI16(std::uint16_t a_value); + void WriteUI32(std::uint32_t a_value); + void WriteLong(std::uint64_t a_value); + void WriteFIXED(double a_value); + void WriteFIXED8(float a_value); + void WriteFLOAT(float a_value); + void WriteDOUBLE(double a_value); + void WriteSTRING(const char* a_value); + void WriteRGBA(RE::GColor a_value); + void WriteMATRIX(const RE::GMatrix2D& a_value); + void WriteCXFORMWITHALPHA(const RE::GRenderer::Cxform& a_value); + void WriteFILTERLIST(const RE::GArray& a_value); + void WriteFILTER(const Filter& a_value); + + auto Get() -> std::string_view; + auto GetPos() -> std::int64_t; + void Clear(); + + private: + void AlignByte(); + + static auto GetNeededBitsS(std::int32_t a_value) -> std::int32_t; + static auto GetNeededBitsF(float a_value) -> std::int32_t; + + std::ostringstream _os; + std::int64_t _pos = 0; + std::uint8_t _bitPos = 0; + std::uint8_t _tempByte = 0; + }; +} diff --git a/src/SWF/TagFactory.cpp b/src/SWF/TagFactory.cpp new file mode 100644 index 0000000..836b0db --- /dev/null +++ b/src/SWF/TagFactory.cpp @@ -0,0 +1,209 @@ +#include "SWF/TagFactory.h" +#include "SWF/SWFOutputStream.h" + +namespace SWF +{ + auto TagFactory::MakePlaceObject( + RE::GFxMovieDataDef* a_movieData, + const RE::GFxPlaceObjectData& a_data) -> RE::GFxPlaceObjectBase* + { + enum class PlaceFlags2 : std::uint8_t + { + kNone = 0, + kMove = 1 << 0, + kHasCharacter = 1 << 1, + kHasMatrix = 1 << 2, + kHasColorTransform = 1 << 3, + kHasRatio = 1 << 4, + kHasName = 1 << 5, + kHasClipDepth = 1 << 6, + kHasClipActions = 1 << 7, + }; + + enum class PlaceFlags3 : std::uint8_t + { + kNone = 0, + kHasFilterList = 1 << 0, + kHasBlendMode = 1 << 1, + kHasCacheAsBitmap = 1 << 2, + kHasClassName = 1 << 3, + kHasImage = 1 << 4, + kHasVisible = 1 << 5, + kOpaqueBackground = 1 << 6, + }; + + static REL::Relocation GFxPlaceObject2_vtbl{ + Offset::GFxPlaceObject2::Vtbl + }; + static REL::Relocation GFxPlaceObject3_vtbl{ + Offset::GFxPlaceObject2::Vtbl + }; + + SWFOutputStream sos; + RE::stl::enumeration placeFlags2(PlaceFlags2::kNone); + RE::stl::enumeration placeFlags3(PlaceFlags3::kNone); + + placeFlags2 |= static_cast(a_data.placeFlags.underlying() & 0x5F); + if (a_data.name) { + placeFlags2 |= PlaceFlags2::kHasName; + } + if (a_data.clipActions) { + placeFlags2 |= PlaceFlags2::kHasClipActions; + } + if (a_data.placeFlags.all(RE::GFxPlaceFlags::kHasFilterList)) { + placeFlags3 |= PlaceFlags3::kHasFilterList; + } + if (a_data.placeFlags.all(RE::GFxPlaceFlags::kHasBlendMode)) { + placeFlags3 |= PlaceFlags3::kHasBlendMode; + } + + sos.WriteUI8(placeFlags2.underlying()); + if (placeFlags3 != PlaceFlags3::kNone) { + sos.WriteUI8(placeFlags3.underlying()); + } + + sos.WriteUI16(static_cast(a_data.depth)); + + if (placeFlags2.all(PlaceFlags2::kHasCharacter)) { + sos.WriteUI16(a_data.characterId.GetIDIndex() & 0xFFFF); + } + if (placeFlags2.all(PlaceFlags2::kHasMatrix)) { + sos.WriteMATRIX(a_data.matrix); + } + if (placeFlags2.all(PlaceFlags2::kHasColorTransform)) { + sos.WriteCXFORMWITHALPHA(a_data.colorTransform); + } + if (placeFlags2.all(PlaceFlags2::kHasRatio)) { + sos.WriteFIXED8(a_data.ratio); + } + if (placeFlags2.all(PlaceFlags2::kHasName)) { + sos.WriteSTRING(a_data.name); + } + if (placeFlags2.all(PlaceFlags2::kHasClipDepth)) { + sos.WriteUI16(a_data.clipDepth); + } + if (placeFlags3.all(PlaceFlags3::kHasFilterList)) { + sos.WriteFILTERLIST(a_data.filterList); + } + if (placeFlags3.all(PlaceFlags3::kHasBlendMode)) { + sos.WriteUI8(a_data.blendMode.underlying()); + } + if (placeFlags2.all(PlaceFlags2::kHasClipActions)) { + // todo + } + + auto data = sos.Get(); + if (data.empty()) { + return nullptr; + } + + if (placeFlags3 == PlaceFlags3::kNone) { + auto size = ((sizeof(RE::GFxPlaceObject2) - 1 + data.size()) + 7) & -0x8; + auto placeObject = static_cast( + a_movieData->loadTaskData->allocator.Alloc(size)); + + if (placeObject) { + *reinterpret_cast(placeObject) = GFxPlaceObject2_vtbl.get(); + memcpy(placeObject->data, data.data(), data.size()); + } + return placeObject; + } + else { + auto size = ((sizeof(RE::GFxPlaceObject3) - 1 + data.size()) + 7) & -0x8; + auto placeObject = static_cast( + a_movieData->loadTaskData->allocator.Alloc(size)); + + if (placeObject) { + *reinterpret_cast(placeObject) = GFxPlaceObject3_vtbl.get(); + memcpy(placeObject->data, data.data(), data.size()); + } + return placeObject; + } + } + + auto TagFactory::MakeRemoveObject(RE::GFxMovieDataDef* a_movieData, std::uint16_t a_depth) + -> RE::GFxRemoveObject2* + { + static REL::Relocation GFxRemoveObject2_vtbl{ + Offset::GFxRemoveObject2::Vtbl + }; + + auto removeObject = static_cast( + a_movieData->loadTaskData->allocator.Alloc(sizeof(RE::GFxRemoveObject2))); + + std::memset(removeObject, 0, sizeof(RE::GFxRemoveObject2)); + + if (removeObject) { + *reinterpret_cast(removeObject) = GFxRemoveObject2_vtbl.get(); + + removeObject->depth = a_depth; + } + + return removeObject; + } + + auto TagFactory::MakeRemoveObject( + RE::GFxMovieDataDef* a_movieData, + std::uint16_t a_characterId, + std::uint16_t a_depth) -> RE::GFxRemoveObject* + { + static REL::Relocation GFxRemoveObject_vtbl{ + Offset::GFxRemoveObject::Vtbl + }; + + auto removeObject = static_cast( + a_movieData->loadTaskData->allocator.Alloc(sizeof(RE::GFxRemoveObject))); + + std::memset(removeObject, 0, sizeof(RE::GFxRemoveObject)); + + if (removeObject) { + *reinterpret_cast(removeObject) = GFxRemoveObject_vtbl.get(); + + removeObject->characterId = a_characterId; + removeObject->depth = a_depth; + } + + return removeObject; + } + + auto TagFactory::MakeInitImportActions(RE::GFxMovieDataDef* a_movieData, std::uint32_t a_movieIndex) + -> RE::GFxInitImportActions* + { + static REL::Relocation GFxInitImportActions_vtbl{ + Offset::GFxInitImportActions::Vtbl + }; + + auto initImportActions = static_cast( + a_movieData->loadTaskData->allocator.Alloc(sizeof(RE::GFxInitImportActions))); + + std::memset(initImportActions, 0, sizeof(RE::GFxInitImportActions)); + + if (initImportActions) { + *reinterpret_cast( + initImportActions) = GFxInitImportActions_vtbl.get(); + + initImportActions->movieIndex = a_movieIndex; + } + + return initImportActions; + } + + auto TagFactory::MakeDoAction(RE::GFxMovieDataDef* a_movieData, RE::GASActionBufferData* a_data) + -> RE::GASDoAction* + { + static REL::Relocation GASDoAction_vtbl{ Offset::GASDoAction::Vtbl }; + + auto doAction = static_cast( + a_movieData->loadTaskData->allocator.Alloc(sizeof(RE::GASDoAction))); + std::memset(doAction, 0, sizeof(RE::GASDoAction)); + assert(!doAction->data); + + if (doAction) { + *reinterpret_cast(doAction) = GASDoAction_vtbl.get(); + + doAction->data = RE::GPtr(a_data); + } + + return doAction; + } +} diff --git a/src/SWF/TagFactory.h b/src/SWF/TagFactory.h new file mode 100644 index 0000000..b21abee --- /dev/null +++ b/src/SWF/TagFactory.h @@ -0,0 +1,29 @@ +#pragma once + +namespace SWF +{ + class TagFactory + { + public: + TagFactory() = delete; + + static auto MakePlaceObject( + RE::GFxMovieDataDef* a_movieData, + const RE::GFxPlaceObjectData& a_data) -> RE::GFxPlaceObjectBase*; + + static auto MakeRemoveObject(RE::GFxMovieDataDef* a_movieData, std::uint16_t a_depth) + -> RE::GFxRemoveObject2*; + + static auto MakeRemoveObject( + RE::GFxMovieDataDef* a_movieData, + std::uint16_t a_characterId, + std::uint16_t a_depth) -> RE::GFxRemoveObject*; + + static auto MakeInitImportActions( + RE::GFxMovieDataDef* a_movieData, + std::uint32_t a_movieIndex) -> RE::GFxInitImportActions*; + + static auto MakeDoAction(RE::GFxMovieDataDef* a_movieData, RE::GASActionBufferData* a_data) + -> RE::GASDoAction*; + }; +} diff --git a/src/main/Patch.cpp b/src/main/Patch.cpp index dfc8a41..6af2cc8 100644 --- a/src/main/Patch.cpp +++ b/src/main/Patch.cpp @@ -47,7 +47,7 @@ bool Patch::WriteLoadHUDPatch(LoadMovieFunc* a_newCall, REL::Relocation{ Offset::HUDMenu::Ctor, 0xFF }; - constexpr auto relcall = REL::Pattern<"E8 ?? ?? ?? ??">{}; + constexpr auto relcall = REL::make_pattern<"E8">(); if (relcall.match(hud_hook.address())) { logger::trace("Using normal address for HUD hook"sv); @@ -71,7 +71,7 @@ bool Patch::WriteLoadMapPatch(LoadMovieFunc* a_newCall, REL::Relocation{ Offset::MapMenu::Ctor, 0x1D1 }; - constexpr auto relcall = REL::Pattern<"E8 ?? ?? ?? ??">{}; + constexpr auto relcall = REL::make_pattern<"E8">(); if (relcall.match(map_hook.address())) { a_origCall = trampoline.write_call<5>(map_hook.address(), a_newCall); @@ -91,7 +91,7 @@ bool Patch::WriteLocalMapPatch( auto door_hook = REL::Relocation{ Offset::LocalMapMenu::PopulateData, 0x941 }; - constexpr auto relcall = REL::Pattern<"E8 ?? ?? ?? ??">{}; + constexpr auto relcall = REL::make_pattern<"E8">(); if (relcall.match(door_hook.address())) { a_origCall = trampoline.write_call<5>(door_hook.address(), a_newCall);