diff --git a/App/WRPGconfig.txt b/App/WRPGconfig.txt new file mode 100644 index 00000000..ae16b272 --- /dev/null +++ b/App/WRPGconfig.txt @@ -0,0 +1,2 @@ +colorblind:false +collect:false diff --git a/Source/Archipelago/APRandomizer.cpp b/Source/Archipelago/APRandomizer.cpp index 754c348d..1e985516 100644 --- a/Source/Archipelago/APRandomizer.cpp +++ b/Source/Archipelago/APRandomizer.cpp @@ -32,7 +32,8 @@ bool APRandomizer::Connect(HWND& messageBoxHandle, std::string& server, std::str } std::vector receivedItems; - std::map counts; + std::map itemCounts; + std::map> itemColors; _memory->WritePanelData(0x00293, BACKGROUND_REGION_COLOR + 12, { mostRecentItemId }); @@ -89,24 +90,27 @@ bool APRandomizer::Connect(HWND& messageBoxHandle, std::string& server, std::str name += " (" + ap->get_item_name(realitem) + ")"; } - if (counts.find(name) == counts.end()) { - counts[name] = 1; + // Track the quantity of this item received in this batch. + if (itemCounts.find(name) == itemCounts.end()) { + itemCounts[name] = 1; receivedItems.emplace_back(name); } - else { - counts[name]++; + itemCounts[name]++; } + + // Assign a color to the item. + itemColors[name] = getColorForItem(item); } for (std::string name : receivedItems) { + // If we received more than one of an item in this batch, add the quantity to the output string. std::string count = ""; - - if (counts[name] > 1) { - count = " (x" + std::to_string(counts[name]) + ")"; + if (itemCounts[name] > 1) { + count = " (x" + std::to_string(itemCounts[name]) + ")"; } - async->queueMessage("Received " + name + count + "."); + async->queueMessage("Received " + name + count + ".", itemColors[name]); } }); @@ -273,12 +277,12 @@ bool APRandomizer::Connect(HWND& messageBoxHandle, std::string& server, std::str int location = item.location; if (panelIdToLocationIdReverse.count(location) && !_memory->ReadPanelData(panelIdToLocationIdReverse[location], SOLVED)) { - async->queueMessage("(Collect) Sent " + itemName + " to " + player + "."); + async->queueMessage("(Collect) Sent " + itemName + " to " + player + ".", getColorForItem(item)); } else { panelLocker->SetItemReward(findResult->first, item); - if(!receiving) async->queueMessage("Sent " + itemName + " to " + player + "."); + if(!receiving) async->queueMessage("Sent " + itemName + " to " + player + ".", getColorForItem(item)); } } }); @@ -463,4 +467,23 @@ void APRandomizer::SeverDoors() { bool APRandomizer::InfiniteChallenge(bool enable) { if(randomizationFinished) async->InfiniteChallenge(enable); return randomizationFinished; +} + +std::array APRandomizer::getColorForItem(const APClient::NetworkItem& item) +{ + // Pick the appropriate color for this item based on its progression flags. Colors are loosely based on AP codes but somewhat + // paler to match the Witness aesthetic. See https://github.com/ArchipelagoMW/Archipelago/blob/main/NetUtils.py for details. + if (item.flags & APClient::ItemFlags::FLAG_ADVANCEMENT) { + return { 0.82f, 0.76f, 0.96f }; + } + else if (item.flags & APClient::ItemFlags::FLAG_NEVER_EXCLUDE) { + // NOTE: "never exclude" here maps onto "useful" in the AP source. + return { 0.68f, 0.75f, 0.94f }; + } + else if (item.flags & APClient::ItemFlags::FLAG_TRAP) { + return { 1.f, 0.7f, 0.67f }; + } + else { + return { 0.81f, 1.f, 1.f }; + } } \ No newline at end of file diff --git a/Source/Archipelago/APRandomizer.h b/Source/Archipelago/APRandomizer.h index 45ce8d48..37ca7df0 100644 --- a/Source/Archipelago/APRandomizer.h +++ b/Source/Archipelago/APRandomizer.h @@ -70,6 +70,8 @@ class APRandomizer { void setPuzzleLocks(HWND loadingHandle); std::string buildUri(std::string& server); + + std::array getColorForItem(const APClient::NetworkItem& item); }; #define DATAPACKAGE_CACHE "ap_datapackage.json" diff --git a/Source/Archipelago/APWatchdog.cpp b/Source/Archipelago/APWatchdog.cpp index e5a1c29e..d9174d2d 100644 --- a/Source/Archipelago/APWatchdog.cpp +++ b/Source/Archipelago/APWatchdog.cpp @@ -691,9 +691,9 @@ void APWatchdog::DisplayMessage() { if (outstandingMessages.empty()) return; - std::string message = outstandingMessages.front(); + HudMessage message = outstandingMessages.front(); outstandingMessages.pop(); - _memory->DisplayHudMessage(message); + _memory->DisplayHudMessage(message.text, message.rgbColor); messageCounter = 8; } diff --git a/Source/Archipelago/APWatchdog.h b/Source/Archipelago/APWatchdog.h index 7c7b99ea..da586cda 100644 --- a/Source/Archipelago/APWatchdog.h +++ b/Source/Archipelago/APWatchdog.h @@ -10,6 +10,11 @@ #include "APState.h" #include "PanelLocker.h" +struct HudMessage { + std::string text; + std::array rgbColor; +}; + struct AudioLogMessage { std::string line1; std::string line2; @@ -59,7 +64,19 @@ class APWatchdog : public Watchdog { void DoubleDoorTargetHack(int id); - void queueMessage(std::string message) { + void queueMessage(std::string text) { + HudMessage message; + message.text = text; + message.rgbColor = { 1.0f, 1.0f, 1.0f }; + + outstandingMessages.push(message); + } + + void queueMessage(std::string text, std::array rgbColor) { + HudMessage message; + message.text = text; + message.rgbColor = rgbColor; + outstandingMessages.push(message); } @@ -90,7 +107,7 @@ class APWatchdog : public Watchdog { std::chrono::system_clock::time_point temporarySpeedModificationTime; bool hasEverModifiedSpeed = false; - std::queue outstandingMessages; + std::queue outstandingMessages; int messageCounter = 0; std::map audioLogMessageBuffer; diff --git a/Source/Memory.cpp b/Source/Memory.cpp index 6754fca7..384fff71 100644 --- a/Source/Memory.cpp +++ b/Source/Memory.cpp @@ -83,6 +83,27 @@ int find(const std::vector &data, const std::vector &search) { return -1; } +std::vector findAll(const std::vector& sourceData, const std::vector& searchSequence) { + std::vector foundIndices; + for (int sourceIndex = 0; sourceIndex < sourceData.size() - searchSequence.size(); sourceIndex++) { + bool foundMatch = true; + + for (int comparisonIndex = 0; comparisonIndex < searchSequence.size(); comparisonIndex++) { + if (sourceData[sourceIndex + comparisonIndex] != searchSequence[comparisonIndex]) + { + foundMatch = false; + break; + } + } + + if (foundMatch) { + foundIndices.push_back(sourceIndex); + } + } + + return foundIndices; +} + int Memory::findGlobals() { const std::vector scanBytes = {0x74, 0x41, 0x48, 0x85, 0xC0, 0x74, 0x04, 0x48, 0x8B, 0x48, 0x10}; #define BUFFER_SIZE 0x100000 // 100 KB @@ -333,6 +354,36 @@ void Memory::findImportantFunctionAddresses(){ return true; }); + // Find hud_draw_headline and its three hardcoded float values for R, G, and B. + executeSigScan({ 0x48, 0x83, 0xEC, 0x68, 0xF2, 0x0F, 0x10, 0x05 }, [this](__int64 offset, int index, const std::vector& data) { + // hud_draw_headline draws text twice: once for black text to use as a drop shadow and once using values that are assigned at runtime from hardcoded values. We need to + // find the addresses of the three hardcoded values (which are all 1.0f) and store them off to be overwritten in the future. + uint64_t functionAddress = _baseAddress + offset + index; + + // Read the function into memory to ensure that we have the entire function call. This is necessary in case the executeSigScan call found the function very, very close + // to the end of its buffer. + const int functionSize = 0x200; + std::vector functionBody; + functionBody.resize(functionSize); + + SIZE_T numBytesWritten; + ReadProcessMemory(_handle, reinterpret_cast(functionAddress), &functionBody[0], functionSize, &numBytesWritten); + + // Find all three instances of 1.0f. (0x3f800000) + std::vector foundIndices = findAll(functionBody, { 0x00, 0x00, 0x80, 0x3f }); + if (foundIndices.size() != 3) { + return false; + } + + // Assign values relative to the base function address. + for (int colorIndex = 0; colorIndex < 3; colorIndex++) + { + hudMessageColorAddresses[colorIndex] = functionAddress + foundIndices[colorIndex]; + } + + return true; + }); + //Boat speed //Find Entity_Boat::set_speed executeSigScan({0x48, 0x89, 0x5C, 0x24, 0x08, 0x57, 0x48, 0x83, 0xEC, 0x40, 0x0F, 0x29, 0x74, 0x24, 0x30, 0x8B, 0xFA }, [this](__int64 offset, int index, const std::vector& data) { @@ -674,7 +725,7 @@ void Memory::CallVoidFunction(int id, uint64_t functionAdress) { WaitForSingleObject(thread, INFINITE); } -void Memory::DisplayHudMessage(std::string message) { +void Memory::DisplayHudMessage(std::string message, std::array rgbColor) { std::lock_guard lock(mtx); char buffer[1024]; @@ -692,8 +743,15 @@ void Memory::DisplayHudMessage(std::string message) { WriteProcessMemory(_handle, _messageAddress, buffer, sizeof(buffer), NULL); + // Write the message's color values to the addresses of the constants we previously found. + const SIZE_T colorSize = sizeof(float); + for (int colorIndex = 0; colorIndex < 3; colorIndex++) + { + void* writeAddress = reinterpret_cast(hudMessageColorAddresses[colorIndex]); + void* readAddress = reinterpret_cast(&rgbColor[colorIndex]); - + WriteProcessMemory(_handle, writeAddress, readAddress, colorSize, NULL); + } __int64 funcAdress = displayHudFunction; __int64 messageAddress = reinterpret_cast<__int64>(_messageAddress); @@ -916,6 +974,7 @@ uint64_t Memory::activateLaserFunction = 0; uint64_t Memory::hudTimePointer = 0; uint64_t Memory::updateEntityPositionFunction = 0; uint64_t Memory::displayHudFunction = 0; +uint64_t Memory::hudMessageColorAddresses[3] = {0,0,0}; uint64_t Memory::setBoatSpeed = 0; uint64_t Memory::boatSpeed4 = 0; uint64_t Memory::boatSpeed3 = 0; diff --git a/Source/Memory.h b/Source/Memory.h index 066bda27..2486d6d1 100644 --- a/Source/Memory.h +++ b/Source/Memory.h @@ -162,7 +162,7 @@ class Memory void RemoveMesh(int id); - void DisplayHudMessage(std::string s); + void DisplayHudMessage(std::string message, std::array rgbColor); void DisplaySubtitles(std::string line1, std::string line2, std::string line3); @@ -180,6 +180,7 @@ class Memory static uint64_t hudTimePointer; static uint64_t relativeAddressOf6; static uint64_t displayHudFunction; + static uint64_t hudMessageColorAddresses[3]; // addresses of constant floats used for HUD message coloring static uint64_t setBoatSpeed; static uint64_t boatSpeed4; static uint64_t boatSpeed3;