From 16f2b954fa5b0996b07a37e36cf75c6003f1cc5f Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 22 Aug 2026 21:42:43 -0600 Subject: [PATCH] Add full color extension to messages --- include/d/d_msg_class.h | 33 ++++++++++++ mods/flow_demo/src/mod.cpp | 14 ++--- sdk/include/mods/svc/flow.hpp | 15 +++++- sdk/include/mods/svc/message.h | 2 +- src/d/d_msg_class.cpp | 96 ++++++++++++++++++++++++++++++++-- src/dusk/mods/svc/flow.cpp | 15 ++++-- 6 files changed, 161 insertions(+), 14 deletions(-) diff --git a/include/d/d_msg_class.h b/include/d/d_msg_class.h index 2735561c05..c70345ca92 100644 --- a/include/d/d_msg_class.h +++ b/include/d/d_msg_class.h @@ -121,6 +121,25 @@ struct jmessage_tReference : public JMessage::TReference { void setDemoFrame(u32 i_frame) { mDemoFrame = i_frame; } void setTopColorType(u8 i_colorType) { mTopColorType = i_colorType; } void setNowColorType(u8 i_colorType) { mNowColorType = i_colorType; } +#if TARGET_PC + void resetColors() { + mNowCCColor = 0xFFFFFFFF; + mNowGCColor = 0xFFFFFFFF; + mTopCCColor = 0xFFFFFFFF; + mTopGCColor = 0xFFFFFFFF; + mNowFullColor = false; + mTopFullColor = false; + } + void setFullColor(u32 ccColor, u32 gcColor) { + mNowCCColor = ccColor; + mNowGCColor = gcColor; + mNowFullColor = true; + } + void clearNowFullColor() { mNowFullColor = false; } + bool hasTopFullColor() { return mTopFullColor; } + u32 getTopCCColor() { return mTopCCColor; } + u32 getTopGCColor() { return mTopGCColor; } +#endif void setTopTagScale(u16 i_tagScale) { mTopTagScale = i_tagScale; } void setNowTagScale(u16 i_tagScale) { mNowTagScale = i_tagScale; } void setRevoMessageID(u32 i_msgID) { mRevoMessageID = i_msgID; } @@ -365,6 +384,14 @@ struct jmessage_tReference : public JMessage::TReference { /* 0x1274 */ bool mSelectSetCancelFlag; /* 0x1275 */ bool mBombNameUseFlag; /* 0x1276 */ u8 mBatchColorFlag; +#if TARGET_PC + u32 mNowCCColor; + u32 mNowGCColor; + u32 mTopCCColor; + u32 mTopGCColor; + bool mNowFullColor; + bool mTopFullColor; +#endif }; // Size: 0x1278 struct jmessage_tMeasureProcessor : public JMessage::TRenderingProcessor { @@ -462,6 +489,9 @@ struct jmessage_tRenderingProcessor : public JMessage::TRenderingProcessor { void do_selwidthcenter(int); void do_heightcenter(); void do_color(u8); +#if TARGET_PC + void do_fullcolor(u32 ccColor, u32 gcColor); +#endif void do_scale(f32); void do_linedown(s16); void do_transY(s16, bool); @@ -640,6 +670,9 @@ struct jmessage_string_tRenderingProcessor : public JMessage::TRenderingProcesso void do_rubystrcat(char*); void do_outfont(u8); void do_color(u8); +#if TARGET_PC + void do_fullcolor(u32 ccColor, u32 gcColor); +#endif void do_scale(f32); void do_linedown(s16); void do_numset(s16); diff --git a/mods/flow_demo/src/mod.cpp b/mods/flow_demo/src/mod.cpp index e4d18a90f0..a05150458b 100644 --- a/mods/flow_demo/src/mod.cpp +++ b/mods/flow_demo/src/mod.cpp @@ -177,18 +177,20 @@ MOD_EXPORT ModResult mod_initialize(ModError* outError) { if (result == MOD_OK) { result = add_message(mods::flow::MessageBuilder{kResponseStyle} .text_color(MESSAGE_COLOR_DEFAULT) - .text("Hello from ") - .text_color(MESSAGE_COLOR_RED) - .text("FlowService") + .text("Custom ") + .text_color(0xFF0000FF) + .text("colors") + .text_color(MESSAGE_COLOR_DEFAULT) + .text(" and even ") + .text_color(0x00FF00FF, 0x00FFFFFF) + .text("gradients") .text_color(MESSAGE_COLOR_DEFAULT) - .text(", ") - .player_name() .text("!\n") .character_delay(3) .text("This text is slow. ") .character_delay(0) .pause(12) - .text_scale(125) + .text_scale(150) .text("Big") .text_scale(100) .text(" text too.") diff --git a/sdk/include/mods/svc/flow.hpp b/sdk/include/mods/svc/flow.hpp index 2b1cb1b594..9687a6eebb 100644 --- a/sdk/include/mods/svc/flow.hpp +++ b/sdk/include/mods/svc/flow.hpp @@ -459,7 +459,20 @@ public: } MessageBuilder& text_color(MessageTextColor color) { const auto index = static_cast(color); - return raw_tag(255, 0, {&index, 1}); + return raw_tag(0xFF, 0, {&index, 1}); + } + /* Full 32-bit text color (Dusklight extension) */ + MessageBuilder& text_color(uint32_t rgba) { + std::array arguments{}; + write_bits(arguments.data(), rgba); + return raw_tag(0xFF, 0, arguments); + } + /* Full 32-bit text vertical gradient colors (Dusklight extension) */ + MessageBuilder& text_color(uint32_t upperRgba, uint32_t lowerRgba) { + std::array arguments{}; + write_bits(arguments.data(), upperRgba); + write_bits(arguments.data() + sizeof(upperRgba), lowerRgba); + return raw_tag(0xFF, 0, arguments); } MessageBuilder& text_scale(uint16_t percent) { return timed_tag(255, 1, percent); } MessageBuilder& character_delay(uint16_t frames) { return timed_tag(0, 6, frames); } diff --git a/sdk/include/mods/svc/message.h b/sdk/include/mods/svc/message.h index 172dce4895..b170e6f09b 100644 --- a/sdk/include/mods/svc/message.h +++ b/sdk/include/mods/svc/message.h @@ -8,7 +8,7 @@ #define MESSAGE_SERVICE_ID "dev.twilitrealm.dusklight.message" #define MESSAGE_SERVICE_MAJOR 1u -#define MESSAGE_SERVICE_MINOR 0u +#define MESSAGE_SERVICE_MINOR 1u typedef uint16_t MessageId; typedef uint64_t MessageHandle; diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 46b246264a..4358d6d5d2 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -16,6 +16,21 @@ #if TARGET_PC #include "dusk/menu_pointer.h" #include "dusk/scope_guard.hpp" +#include "helpers/bits.hpp" + +static bool read_full_color(const void* data, u32 size, u32& ccColor, u32& gcColor) { + if (size == 4) { + ccColor = dusk::read_bits(data); + gcColor = ccColor; + return true; + } + if (size == 8) { + ccColor = dusk::read_bits(data); + gcColor = dusk::read_bits(static_cast(data) + sizeof(ccColor)); + return true; + } + return false; +} #endif #if TARGET_PC @@ -400,6 +415,9 @@ jmessage_tReference::jmessage_tReference() { resetCharCountBuffer(); mNowColorType = 0; mTopColorType = 0; +#if TARGET_PC + resetColors(); +#endif mButtonTagStopFlag = 0; mPageEndCount = 0; mSelectNum = 0; @@ -594,6 +612,13 @@ void jmessage_tReference::pageSend() { if (mNowColorType != mTopColorType) { mTopColorType = mNowColorType; } +#if TARGET_PC + mTopFullColor = mNowFullColor; + if (mNowFullColor) { + mTopCCColor = mNowCCColor; + mTopGCColor = mNowGCColor; + } +#endif mTopWordCount = mNowWordCount; mCharAlpha = 0.0f; @@ -1917,6 +1942,9 @@ void jmessage_tSequenceProcessor::do_begin(void const* pEntry, char const* pszTe pReference->resetCharCnt(); pReference->setNowColorType(0); pReference->setTopColorType(0); +#if TARGET_PC + pReference->resetColors(); +#endif pReference->setNowWordCount(0); pReference->setTopWordCount(0); pReference->setBatchColorFlag(0); @@ -2280,9 +2308,21 @@ bool jmessage_tSequenceProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_si return true; case MSGTAG_GROUP(255): switch (i_tag) { - case MSGTAG_COLOR: + case MSGTAG_COLOR: { +#if TARGET_PC + u32 ccColor; + u32 gcColor; + if (read_full_color(i_data, i_size, ccColor, gcColor)) { + pReference->setFullColor(ccColor, gcColor); + } else if (i_size == 1) { + pReference->clearNowFullColor(); + pReference->setNowColorType(*(u8*)i_data & 0xFF); + } +#else pReference->setNowColorType(*(u8*)i_data & 0xFF); +#endif return true; + } case MSGTAG_SCALE: pReference->setNowTagScale(*(BE(u16)*)i_data & 0xFFFF); return true; @@ -2863,7 +2903,15 @@ void jmessage_tRenderingProcessor::do_begin(void const* pEntry, char const* pszT do_scale(field_0x44); } +#if TARGET_PC + if (pReference->hasTopFullColor()) { + do_fullcolor(pReference->getTopCCColor(), pReference->getTopGCColor()); + } else { + do_color(pReference->getTopColorType()); + } +#else do_color(pReference->getTopColorType()); +#endif pReference->resetDrawLightCount(); do_widthcenter(); @@ -3017,9 +3065,20 @@ bool jmessage_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_s return 1; case MSGTAG_GROUP(255): switch (i_tag) { - case MSGTAG_COLOR: + case MSGTAG_COLOR: { +#if TARGET_PC + u32 ccColor; + u32 gcColor; + if (read_full_color(i_data, i_size, ccColor, gcColor)) { + do_fullcolor(ccColor, gcColor); + } else if (i_size == 1) { + do_color(*(u8*)i_data & 0xFF); + } +#else do_color(*(u8*)i_data & 0xFF); +#endif return 1; + } case MSGTAG_SCALE: field_0x13c = *(BE(u16)*)i_data & 0xFFFF; do_scale(field_0x13c / 100.0f); @@ -3630,6 +3689,18 @@ void jmessage_tRenderingProcessor::do_color(u8 i_colorNo) { do_strcat(buffer, false, false, false); } +#if TARGET_PC +void jmessage_tRenderingProcessor::do_fullcolor(u32 ccColor, u32 gcColor) { + mColorNo = 0xFF; + mCCColor = ccColor; + mGCColor = gcColor; + + char buffer[40]; + SAFE_SPRINTF(buffer, "\x1B" "CC[%08x]" "\x1B" "GC[%08x]", mCCColor, mGCColor); + do_strcat(buffer, false, false, false); +} +#endif + void jmessage_tRenderingProcessor::do_scale(f32 param_1) { jmessage_tReference* pReference = (jmessage_tReference*)getReference(); @@ -4684,9 +4755,20 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, switch(i_tag & 0xFF0000) { case MSGTAG_GROUP(255): switch(i_tag) { - case MSGTAG_COLOR: + case MSGTAG_COLOR: { +#if TARGET_PC + u32 ccColor; + u32 gcColor; + if (read_full_color(i_data, i_size, ccColor, gcColor)) { + do_fullcolor(ccColor, gcColor); + } else if (i_size == 1) { + do_color(*(u8*)i_data & 0xFF); + } +#else do_color(*(u8*)i_data & 0xFF); +#endif break; + } case MSGTAG_SCALE: do_scale(*(BE(u16)*)i_data / 100.0f); break; @@ -5336,6 +5418,14 @@ void jmessage_string_tRenderingProcessor::do_color(u8 i_colorNo) { do_strcat(buffer); } +#if TARGET_PC +void jmessage_string_tRenderingProcessor::do_fullcolor(u32 ccColor, u32 gcColor) { + char buffer[32]; + SAFE_SPRINTF(buffer, "\x1b" "CC[%08x]" "\x1b" "GC[%08x]", ccColor, gcColor); + do_strcat(buffer); +} +#endif + void jmessage_string_tRenderingProcessor::do_scale(f32 i_scale) { J2DTextBox::TFontSize fontSize; mpReference->getPanePtr()->getFontSize(fontSize); diff --git a/src/dusk/mods/svc/flow.cpp b/src/dusk/mods/svc/flow.cpp index b55006c3b0..9c5af25e4c 100644 --- a/src/dusk/mods/svc/flow.cpp +++ b/src/dusk/mods/svc/flow.cpp @@ -11,6 +11,7 @@ #include "JSystem/JMessage/control.h" #include "JSystem/JMessage/processor.h" #include "JSystem/JMessage/resource.h" +#include "d/d_msg_class.h" #include #include @@ -467,11 +468,19 @@ bool valid_encoded_text(const uint8_t* text, size_t size) { if (text[offset] == 0) { return offset + 1 == size; } - if (text[offset] == 0x1a) { + if (text[offset] == JMessage::data::gcTagBegin) { if (offset + 2 > size || text[offset + 1] < 5 || text[offset + 1] > size - offset) { return false; } - offset += text[offset + 1]; + const size_t tagSize = text[offset + 1]; + const uint32_t tag = + MSGTAG_GROUP(text[offset + 2]) | read_bits(text + offset + 3); + const size_t argumentSize = tagSize - 5; + if (tag == MSGTAG_COLOR && argumentSize != 1 && argumentSize != 4 && argumentSize != 8) + { + return false; + } + offset += tagSize; } else { ++offset; } @@ -490,7 +499,7 @@ size_t encoded_text_size(const ResourceInfo& resource, const char* text) { if (bytes[offset] == 0) { return offset + 1; } - if (bytes[offset] == 0x1a) { + if (bytes[offset] == JMessage::data::gcTagBegin) { if (offset + 2 > maximum || bytes[offset + 1] < 5 || bytes[offset + 1] > maximum - offset) {