Repository: std-microblock/chromatic Branch: chromatic Commit: a5a78a0f0cba Files: 34 Total size: 85.1 KB Directory structure: gitextract_tq7m8ylh/ ├── .clangd ├── .github/ │ └── workflows/ │ └── xmake.yml ├── .gitignore ├── .gitmodules ├── README.md ├── deps/ │ ├── blook.lua │ └── breeze-js.lua ├── ipc/ │ ├── ipc.cc │ ├── ipc.h │ └── shared_memory_ipc.h ├── scripts/ │ ├── bindgen.bat │ └── rebuild.ps1 ├── src/ │ ├── config.cc │ ├── config.h │ ├── context.cc │ ├── context.h │ ├── entry.cc │ ├── hooks/ │ │ ├── blink_parse_html_manipulator.cc │ │ ├── blink_parse_html_manipulator.h │ │ ├── disable-integrity.cc │ │ ├── disable-integrity.h │ │ ├── wait_for_module_load.cc │ │ └── wait_for_module_load.h │ ├── script/ │ │ ├── bindings/ │ │ │ ├── binding_qjs.h │ │ │ ├── binding_types.cc │ │ │ ├── binding_types.d.ts │ │ │ ├── binding_types.h │ │ │ └── quickjspp.hpp │ │ ├── script.cc │ │ └── script.h │ ├── utils.cc │ └── utils.h ├── test/ │ └── ipc_test.cc └── xmake.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clangd ================================================ CompileFlags: Add: ["-std:latest", "/clang:-std=c++23"] ================================================ FILE: .github/workflows/xmake.yml ================================================ name: Build permissions: write-all on: push: branches: [ "chromatic" ] pull_request: branches: [ "chromatic" ] release: types: [created] jobs: build: runs-on: windows-2025 steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: xmake-io/github-action-setup-xmake@v1 with: xmake-version: latest actions-cache-folder: '.xmake-cache' actions-cache-key: 'ci' package-cache: true package-cache-key: windows-2025 # build-cache: true # build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }} - name: Xmake configure run: | xmake config -v --yes --toolchain=clang-cl --mode=releasedbg - name: build-releasedbg run: | xmake b --yes --verbose - name: Upload Artifacts uses: actions/upload-artifact@v4.6.0 with: path: ./build/windows/x64/ name: windows-build - name: Create Archive if: github.event_name == 'release' run: | Compress-Archive -Path ./build/windows/* -DestinationPath windows-build-pdb.zip Remove-Item -Path ./build/windows/x64/releasedbg/*.pdb -Force Remove-Item -Path ./build/windows/x64/releasedbg/*.lib -Force Compress-Archive -Path ./build/windows/* -DestinationPath windows-build.zip - name: Upload Release Assets if: github.event_name == 'release' uses: softprops/action-gh-release@v1 with: files: | windows-build.zip windows-build-pdb.zip token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ build .xmake ================================================ FILE: .gitmodules ================================================ [submodule "deps/blook"] path = deps/blook url = https://github.com/std-microblock/blook [submodule "deps/cpp-ipc"] path = deps/cpp-ipc url = https://github.com/std-microblock/cpp-ipc ================================================ FILE: README.md ================================================

chromatic

Universal modifier for Chromium/V8 | 广谱注入 Chromium/V8 的通用修改器
> [!NOTE] > 在找 BetterNCM ? > > 由于作者迁移至 QQ 音乐,BetterNCM 疏于维护,以及其年代已久,现将相关代码整体重写并支持极多其它软件,改名为 chromatic。 > > 点击以查看 [BetterNCM 相关代码存档](https://github.com/std-microblock/chromatic/tree/v2) 或 [最后一版 BetterNCM Release](https://github.com/std-microblock/chromatic/releases/tag/1.3.4) > [!WARNING] > This project is still in active development. File a bug report if you meet > any!\ > 此项目仍在开发阶段,如果遇到问题请发送 Issue > > Both English and Chinese issues are accepted. > Issue 中使用中文或英文均可 ## 文档/使用方法 待完善 ## Showcase ```javascript import { chrome } from "chromatic" chrome.blink.add_blink_parse_html_manipulator(html => { if (html.includes('Chromatic #include void test() { auto ctx = std::make_shared(); ctx->reset_runtime(); } ]]}, {configs = {languages = "c++23"}})) end) ================================================ FILE: ipc/ipc.cc ================================================ #include "ipc.h" #include #include #include #include #include "shared_memory_ipc.h" #include "ylt/easylog.hpp" namespace chromatic { void breeze_ipc::connect(std::string_view name) { channel.connect(name.data()); ipc_thread = std::thread([this]() { ELOGFMT(INFO, "IPC thread started, listening for packets..."); while (!exit_signal) { poll(); } }); } breeze_ipc::~breeze_ipc() { exit_signal = true; if (ipc_thread.joinable()) { ipc_thread.join(); } } bool breeze_ipc::poll() { std::string data; channel.try_receive(data); if (!data.empty()) { auto pkt = deserialize(data); if (pkt.has_value()) { // 检查是否是发给自己的包 if (pkt->to_pid != 0 && pkt->to_pid != current_pid_) { return true; // 不是给自己的包,跳过 } if (pkt->is_fragment) { process_fragment(*pkt); } else { process_packet(std::move(*pkt)); } } return true; } return false; } void breeze_ipc::process_packet(packet &&pkt) { if (pkt.return_for_call != 0) { auto it = call_handlers.find(pkt.return_for_call); if (it != call_handlers.end()) { it->second(pkt); call_handlers.erase(it->first); } } else { auto &handler_list = handlers[pkt.name]; for (auto &handler : handler_list) { handler(pkt); } } } void breeze_ipc::process_fragment(const packet &frag) { std::lock_guard lock(fragment_mutex_); auto &cache = fragment_cache_[frag.fragment_id]; if (cache.fragments.empty()) { cache.created_time = std::chrono::steady_clock::now(); cache.base_packet = frag; cache.base_packet.is_fragment = false; cache.base_packet.data.clear(); cache.fragments.resize(frag.fragment_total); } if (frag.fragment_index < frag.fragment_total) { cache.fragments[frag.fragment_index] = frag.data; } bool complete = true; for (const auto &fragment : cache.fragments) { if (fragment.empty()) { complete = false; break; } } if (complete) { reassemble_and_process(frag.fragment_id); } } void breeze_ipc::reassemble_and_process(size_t fragment_id) { auto it = fragment_cache_.find(fragment_id); if (it == fragment_cache_.end()) return; auto &cache = it->second; for (const auto &frag : cache.fragments) { cache.base_packet.data += frag; } process_packet( std::move(deserialize(cache.base_packet.data).value())); fragment_cache_.erase(it); } } // namespace chromatic ================================================ FILE: ipc/ipc.h ================================================ #pragma once #include "./shared_memory_ipc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "rfl.hpp" #include "rfl/json.hpp" #include "ylt/easylog.hpp" #include "ylt/struct_pack.hpp" #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include "Windows.h" namespace chromatic { constexpr static bool use_struct_pack = false; constexpr static bool print_packages = false; constexpr static size_t MAX_PACKET_SIZE = 1024; auto serialize = [](const auto &data) { if constexpr (use_struct_pack) { return struct_pack::serialize(data); } else { return rfl::json::write(data); } }; template auto deserialize(const std::string &data) { if constexpr (use_struct_pack) { return struct_pack::deserialize(data); } else { return rfl::json::read(data); } } template concept StructPackSerializable = requires(T t) { { serialize(t) } -> std::same_as; deserialize(std::declval()); }; struct test_serializable_struct { int a; float b; std::vector c; }; static_assert(StructPackSerializable, "test_struct should be StructPackSerializable"); struct breeze_ipc { struct packet { size_t seq; size_t return_for_call = 0; std::string name; std::string data; DWORD from_pid = 0; // 发送方进程ID DWORD to_pid = 0; // 接收方进程ID bool is_fragment = false; // 是否是分包 size_t fragment_id = 0; // 分包ID size_t fragment_index = 0; // 分包索引 size_t fragment_total = 0; // 总分包数 }; // 分包重组缓存 struct fragment_cache { std::vector fragments; std::chrono::steady_clock::time_point created_time; packet base_packet; }; void connect(std::string_view name); inline size_t inc_seq() { return seq++; } inline size_t next_fragment_id() { return next_fragment_id_++; } // 发送包(可选择目标PID) void send(packet &&pkt, DWORD target_pid = 0) { pkt.to_pid = target_pid; pkt.from_pid = current_pid_; auto serialized = serialize(pkt); // 分包处理 if (serialized.size() > MAX_PACKET_SIZE) { send_fragmented(pkt, serialized); return; } send_impl(serialized); } // 实现分包发送 void send_fragmented(const packet &base_pkt, const std::string &full_data) { const size_t fragment_id = next_fragment_id(); const size_t total_fragments = (full_data.size() + MAX_PACKET_SIZE - 1) / MAX_PACKET_SIZE; for (size_t i = 0; i < total_fragments; ++i) { const size_t start = i * MAX_PACKET_SIZE; const size_t end = std::min(start + MAX_PACKET_SIZE, full_data.size()); packet fragment = base_pkt; fragment.is_fragment = true; fragment.fragment_id = fragment_id; fragment.fragment_index = i; fragment.fragment_total = total_fragments; fragment.data = full_data.substr(start, end - start); auto serialized_fragment = serialize(fragment); send_impl(serialized_fragment); } } // 实际发送实现 void send_impl(const std::string &data) { if (!data.empty()) { if constexpr (print_packages) { if constexpr (use_struct_pack) { for (size_t i = 0; i < data.size(); ++i) { if (i % 16 == 0 && i != 0) { printf("\n"); } char buf[3]; snprintf(buf, sizeof(buf), "%02x ", static_cast(data[i])); WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), buf, 2, nullptr, nullptr); } } else { if (GetConsoleWindow()) WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), data.data(), static_cast(data.size()), nullptr, nullptr); } } channel.send(data); } } bool poll(); // 发送给特定PID template void send(const std::string &name, const T &data, DWORD target_pid = 0) { send( packet{ .seq = inc_seq(), .return_for_call = 0, .name = name, .data = serialize(data), }, target_pid); } template void broadcast(const std::string &name, const T &data) { send(packet{ .seq = inc_seq(), .return_for_call = 0, .name = name, .data = serialize(data), }); } struct listener_remover { breeze_ipc &ipc_instance; std::string name; std::function &handler; void remove() { auto &handlers = ipc_instance.handlers; auto it = handlers.find(name); if (it != handlers.end()) { auto &handler_list = it->second; handler_list.remove_if( [&](const std::function &h) { return &h == &handler; }); } } }; listener_remover add_listener(const std::string &name, std::function &&handler) { auto &h = handlers[name]; h.emplace_back(std::move(handler)); return listener_remover{*this, name, h.back()}; } template listener_remover add_listener(const std::string &name, std::function &&handler) { return add_listener( name, [handler = std::move(handler)](const packet &pkt) { auto data = deserialize(pkt.data); if (data.has_value()) { handler(data.value()); } else { throw std::runtime_error("Failed to deserialize packet data"); } }); } template listener_remover add_call_handler(const std::string &name, std::function &&handler) { return add_listener("call_" + name, [this, handler = std::move(handler), name](const packet &pkt) { auto data = deserialize(pkt.data); if (!data.has_value()) { throw std::runtime_error("Failed to deserialize call data for " + name); } auto result = handler(data.value()); send(packet{.seq = inc_seq(), .return_for_call = pkt.seq, .name = "call_result_" + name, .data = serialize(result), .to_pid = pkt.from_pid}); }); } template listener_remover add_call_handler(const std::string &name, std::function &&handler) { return add_call_handler( name, [handler = std::move(handler)](bool) { return handler(); }); } template std::future call(const std::string &name, const R &data, DWORD target_pid = 0) { auto seq = inc_seq(); auto promise = std::make_shared>(); call_handlers[seq] = [promise](const packet &pkt) { auto result = deserialize(pkt.data); if (result.has_value()) { promise->set_value(std::move(result.value())); } else { promise->set_exception( std::make_exception_ptr(std::runtime_error("Call failed"))); } }; send(packet{ .seq = seq, .return_for_call = 0, .name = "call_" + name, .data = serialize(data), .to_pid = target_pid // 定向发送 }); return promise->get_future(); } template std::future call(const std::string &name, DWORD target_pid = 0) { return call(name, true, target_pid); } template std::optional call_and_poll(const std::string &name, const R &data, DWORD target_pid = 0, std::chrono::milliseconds timeout = std::chrono::seconds(5)) { auto future = call(name, data, target_pid); auto start = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - start < timeout) { if (future.valid() && future.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { return future.get(); } poll(); } return std::nullopt; } template std::optional call_and_poll(const std::string &name, DWORD target_pid = 0, std::chrono::milliseconds timeout = std::chrono::seconds(5)) { return call_and_poll(name, true, target_pid, timeout); } ~breeze_ipc(); private: void process_packet(packet &&pkt); void process_fragment(const packet &frag); void reassemble_and_process(size_t fragment_id); std::unordered_map>> handlers; std::unordered_map> call_handlers; ::ipc::Channel<> channel; std::atomic_size_t seq = 1 + std::chrono::system_clock::now().time_since_epoch().count() / 1000 % 1000000; std::atomic_size_t next_fragment_id_ = 1; std::atomic_bool exit_signal = false; std::thread ipc_thread; DWORD current_pid_ = ::GetCurrentProcessId(); // 分包重组相关 std::mutex fragment_mutex_; std::unordered_map fragment_cache_; }; } // namespace chromatic ================================================ FILE: ipc/shared_memory_ipc.h ================================================ #pragma once #include #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4200) #endif #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ipc { class Exception : public std::runtime_error { public: explicit Exception(const std::string &message) : std::runtime_error(message) {} explicit Exception(const char *message) : std::runtime_error(message) {} }; namespace detail { inline void throw_windows_error(const std::string &message) { DWORD error_code = ::GetLastError(); throw ipc::Exception(message + " (Windows Error: " + std::to_string(error_code) + ")"); } class SidHolder { public: SidHolder() : sid_buffer_() {} ~SidHolder() = default; bool CreateEveryone() { DWORD sid_size = 0; CreateWellKnownSid(WinWorldSid, nullptr, nullptr, &sid_size); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return false; sid_buffer_.resize(sid_size); return !!CreateWellKnownSid(WinWorldSid, nullptr, Get(), &sid_size); } bool CreateUntrusted() { SID_IDENTIFIER_AUTHORITY mandatory_label_authority = SECURITY_MANDATORY_LABEL_AUTHORITY; DWORD sid_size = GetSidLengthRequired(1); sid_buffer_.resize(sid_size); if (!InitializeSid(Get(), &mandatory_label_authority, 1)) return false; *(GetSidSubAuthority(Get(), 0)) = SECURITY_MANDATORY_UNTRUSTED_RID; return true; } PSID Get() { return sid_buffer_.empty() ? nullptr : reinterpret_cast(sid_buffer_.data()); } DWORD GetLength() const { return static_cast(sid_buffer_.size()); } private: std::vector sid_buffer_; SidHolder(const SidHolder &) = delete; SidHolder &operator=(const SidHolder &) = delete; }; inline LPSECURITY_ATTRIBUTES get_sa() { static struct initiator { SECURITY_ATTRIBUTES sa_{}; std::vector sd_buffer_; bool succ_ = false; initiator() { SidHolder everyone_sid; if (!everyone_sid.CreateEveryone()) return; SidHolder untrusted_il_sid; if (!untrusted_il_sid.CreateUntrusted()) return; const DWORD dacl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + everyone_sid.GetLength(); std::vector dacl_buffer(dacl_size); PACL dacl = reinterpret_cast(dacl_buffer.data()); if (!InitializeAcl(dacl, dacl_size, ACL_REVISION)) return; if (!AddAccessAllowedAce(dacl, ACL_REVISION, SYNCHRONIZE | SEMAPHORE_ALL_ACCESS | EVENT_ALL_ACCESS | FILE_MAP_ALL_ACCESS, everyone_sid.Get())) return; const DWORD sacl_size = sizeof(ACL) + sizeof(SYSTEM_MANDATORY_LABEL_ACE) + untrusted_il_sid.GetLength(); std::vector sacl_buffer(sacl_size); PACL sacl = reinterpret_cast(sacl_buffer.data()); if (!InitializeAcl(sacl, sacl_size, ACL_REVISION)) return; if (!AddMandatoryAce(sacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, untrusted_il_sid.Get())) return; SECURITY_DESCRIPTOR sd_absolute = {0}; if (!InitializeSecurityDescriptor(&sd_absolute, SECURITY_DESCRIPTOR_REVISION)) return; if (!SetSecurityDescriptorDacl(&sd_absolute, TRUE, dacl, FALSE)) return; if (!SetSecurityDescriptorSacl(&sd_absolute, TRUE, sacl, FALSE)) return; DWORD sd_buffer_size = 0; MakeSelfRelativeSD(&sd_absolute, nullptr, &sd_buffer_size); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return; sd_buffer_.resize(sd_buffer_size); PSECURITY_DESCRIPTOR sd_relative = reinterpret_cast(sd_buffer_.data()); if (!MakeSelfRelativeSD(&sd_absolute, sd_relative, &sd_buffer_size)) return; sa_.nLength = sizeof(sa_); sa_.lpSecurityDescriptor = sd_relative; sa_.bInheritHandle = FALSE; succ_ = true; } } handle; return handle.succ_ ? &handle.sa_ : nullptr; } struct MessageHeader { std::atomic sequence{0}; uint32_t data_size; bool is_first_fragment; bool is_last_fragment; }; struct MessageSlot { MessageHeader header; char data[0]; }; struct ReaderSlot { std::atomic pid{0}; std::atomic sequence{0}; }; struct SharedMemoryHeader { std::atomic_flag write_lock = ATOMIC_FLAG_INIT; uint32_t capacity; uint32_t max_payload_size; uint32_t max_readers; std::atomic write_index{0}; std::atomic sequence_generator{0}; }; } // namespace detail template class Channel { public: struct Config { size_t capacity = 128; size_t max_message_payload_size = 4096; }; Channel() = default; ~Channel() { disconnect(); } Channel(const Channel &) = delete; Channel &operator=(const Channel &) = delete; Channel(Channel &&) = delete; Channel &operator=(Channel &&) = delete; void connect(const std::string &name, const Config &config = {}) { if (is_connected()) throw Exception("Channel is already connected."); config_ = config; name_ = name; std::wstring shm_name = L"IPC_Channel_SHM_" + std::wstring(name.begin(), name.end()); std::wstring event_name = L"IPC_Channel_EVT_" + std::wstring(name.begin(), name.end()); LPSECURITY_ATTRIBUTES sa = detail::get_sa(); if (!sa) throw Exception("Failed to get security attributes."); const size_t header_size = get_header_size(); const size_t slot_size = sizeof(detail::MessageSlot) + config_.max_message_payload_size; const size_t total_shm_size = header_size + config_.capacity * slot_size; bool is_creator = false; h_map_file_ = ::CreateFileMappingW( INVALID_HANDLE_VALUE, sa, PAGE_READWRITE, static_cast(total_shm_size >> 32), static_cast(total_shm_size & 0xFFFFFFFF), shm_name.c_str()); if (h_map_file_ == NULL) detail::throw_windows_error("CreateFileMappingW failed for " + name); if (::GetLastError() != ERROR_ALREADY_EXISTS) is_creator = true; p_shared_mem_ = ::MapViewOfFile(h_map_file_, FILE_MAP_ALL_ACCESS, 0, 0, total_shm_size); if (p_shared_mem_ == NULL) { disconnect(); detail::throw_windows_error("MapViewOfFile failed for " + name); } p_header_ = static_cast(p_shared_mem_); p_buffer_start_ = reinterpret_cast(p_shared_mem_) + header_size; if (is_creator) { new (p_header_) detail::SharedMemoryHeader(); p_header_->capacity = static_cast(config_.capacity); p_header_->max_payload_size = static_cast(config_.max_message_payload_size); p_header_->max_readers = static_cast(MaxReaders); for (size_t i = 0; i < MaxReaders; ++i) { new (get_reader_slot(i)) detail::ReaderSlot(); } } else { if (p_header_->max_readers != MaxReaders) { disconnect(); throw Exception( "Connection failed: Mismatched MaxReaders configuration."); } if (p_header_->capacity != config_.capacity || p_header_->max_payload_size != config_.max_message_payload_size) { disconnect(); throw Exception("Connection failed: Mismatched capacity or " "max_payload_size configuration."); } } if (is_creator) { h_data_ready_event_ = ::CreateEventW(sa, TRUE, FALSE, event_name.c_str()); if (h_data_ready_event_ == NULL) { disconnect(); detail::throw_windows_error("CreateEventW failed for " + name); } } else { h_data_ready_event_ = ::OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, event_name.c_str()); if (h_data_ready_event_ == NULL) { disconnect(); detail::throw_windows_error("OpenEventW failed for " + name); } } register_reader(); } void disconnect() { if (is_connected()) { deregister_reader(); } if (p_shared_mem_ != nullptr) { ::UnmapViewOfFile(p_shared_mem_); p_shared_mem_ = nullptr; } if (h_map_file_ != NULL) { ::CloseHandle(h_map_file_); h_map_file_ = NULL; } if (h_data_ready_event_ != NULL) { ::CloseHandle(h_data_ready_event_); h_data_ready_event_ = NULL; } p_header_ = nullptr; p_buffer_start_ = nullptr; my_reader_slot_index_ = -1; name_.clear(); } bool is_connected() const { return p_shared_mem_ != nullptr; } void send(const std::string &message) { if (!is_connected()) { throw Exception("Channel is not connected."); } const size_t max_payload = p_header_->max_payload_size; const size_t num_fragments = message.empty() ? 1 : (message.length() + max_payload - 1) / max_payload; const uint32_t capacity = p_header_->capacity; if (num_fragments > capacity) { throw Exception( "Message is too large to fit in the channel buffer capacity."); } std::string_view message_view(message); while (true) { while (p_header_->write_lock.test_and_set(std::memory_order_acquire)) { std::this_thread::yield(); } const uint64_t write_idx = p_header_->write_index.load(std::memory_order_relaxed); uint64_t slowest_reader_seq = get_slowest_reader_sequence(); if ((write_idx - slowest_reader_seq) + num_fragments > capacity) { p_header_->write_lock.clear(std::memory_order_release); std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } try { const uint64_t seq_gen_at_write_time = p_header_->sequence_generator.load(std::memory_order_relaxed); for (size_t i = 0; i < num_fragments; ++i) { uint64_t current_write_slot_idx = (write_idx + i) % capacity; detail::MessageSlot *slot = get_message_slot(current_write_slot_idx); size_t offset = i * max_payload; size_t chunk_size = message.empty() ? 0 : std::min(message_view.length() - offset, max_payload); uint64_t new_sequence = seq_gen_at_write_time + i + 1; slot->header.data_size = static_cast(chunk_size); slot->header.is_first_fragment = (i == 0); slot->header.is_last_fragment = (i == num_fragments - 1); if (chunk_size > 0) { memcpy(&slot->data, message_view.data() + offset, chunk_size); } slot->header.sequence.store(new_sequence, std::memory_order_release); } p_header_->write_index.fetch_add(num_fragments, std::memory_order_relaxed); p_header_->sequence_generator.fetch_add(num_fragments, std::memory_order_relaxed); } catch (...) { p_header_->write_lock.clear(std::memory_order_release); throw; } p_header_->write_lock.clear(std::memory_order_release); ::SetEvent(h_data_ready_event_); return; } } void receive(std::string &message) { if (!internal_try_receive(message, INFINITE)) { throw Exception( "receive failed. The wait handle may be invalid or closed."); } } bool try_receive(std::string &message) { return internal_try_receive(message, 0); } private: bool internal_try_receive(std::string &message, DWORD timeout_ms) { if (!is_connected()) throw Exception("Channel is not connected."); if (my_reader_slot_index_ < 0) throw Exception("Reader is not registered."); std::string reassembly_buffer; bool in_reassembly = false; while (true) { while (true) { uint64_t next_expected_seq = local_read_sequence_ + 1; detail::MessageSlot *found_slot = nullptr; uint64_t probable_idx = (p_header_->write_index.load(std::memory_order_relaxed) - 1) % p_header_->capacity; probable_idx = (local_read_sequence_) % p_header_->capacity; detail::MessageSlot *candidate_slot = get_message_slot(probable_idx); if (candidate_slot->header.sequence.load(std::memory_order_acquire) == next_expected_seq) { found_slot = candidate_slot; } else { for (uint32_t i = 0; i < p_header_->capacity; ++i) { detail::MessageSlot *current_slot = get_message_slot(i); uint64_t slot_sequence = current_slot->header.sequence.load(std::memory_order_relaxed); if (slot_sequence == next_expected_seq) { found_slot = current_slot; break; } } } if (found_slot) { if (found_slot->header.is_first_fragment) { if (in_reassembly) reassembly_buffer.clear(); in_reassembly = true; } else if (!in_reassembly) { local_read_sequence_++; update_reader_progress(); continue; } reassembly_buffer.append(found_slot->data, found_slot->header.data_size); local_read_sequence_++; if (found_slot->header.is_last_fragment) { message = std::move(reassembly_buffer); in_reassembly = false; update_reader_progress(); return true; } } else { break; } } ::ResetEvent(h_data_ready_event_); if (p_header_->sequence_generator.load(std::memory_order_acquire) > local_read_sequence_) { continue; } DWORD wait_result = ::WaitForSingleObject(h_data_ready_event_, timeout_ms); if (wait_result == WAIT_OBJECT_0) { continue; } else if (wait_result == WAIT_TIMEOUT) { return false; } else { detail::throw_windows_error( "WaitForSingleObject failed on ipc::Channel '" + name_ + "'"); } } } static constexpr size_t get_header_size() { return sizeof(detail::SharedMemoryHeader) + MaxReaders * sizeof(detail::ReaderSlot); } detail::ReaderSlot *get_reader_slot(size_t index) const { char *base = static_cast(p_shared_mem_); return reinterpret_cast( base + sizeof(detail::SharedMemoryHeader) + index * sizeof(detail::ReaderSlot)); } detail::MessageSlot *get_message_slot(uint64_t index) const { const size_t slot_size = sizeof(detail::MessageSlot) + config_.max_message_payload_size; return reinterpret_cast(p_buffer_start_ + index * slot_size); } inline static std::atomic_int instance_count; int current_instance_index_ = instance_count++; int current_instance_id_ = (::GetCurrentProcessId() << 16) + current_instance_index_; static bool is_pid_alive(DWORD pid) { HANDLE h_process = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); if (h_process == NULL) { return false; } ::CloseHandle(h_process); return true; } void register_reader() { uint64_t current_global_seq = p_header_->sequence_generator.load(std::memory_order_relaxed); for (size_t i = 0; i < MaxReaders; ++i) { detail::ReaderSlot *slot = get_reader_slot(i); DWORD expected_pid = 0; if (slot->pid.compare_exchange_strong(expected_pid, current_instance_id_, std::memory_order_acq_rel)) { my_reader_slot_index_ = static_cast(i); local_read_sequence_ = current_global_seq; slot->sequence.store(current_global_seq, std::memory_order_release); return; } } disconnect(); throw Exception("Failed to register reader: Maximum number of concurrent " "readers reached for channel '" + name_ + "'."); } void deregister_reader() { if (my_reader_slot_index_ >= 0) { detail::ReaderSlot *slot = get_reader_slot(my_reader_slot_index_); slot->pid.store(0, std::memory_order_release); my_reader_slot_index_ = -1; } } void update_reader_progress() { if (my_reader_slot_index_ >= 0) { get_reader_slot(my_reader_slot_index_) ->sequence.store(local_read_sequence_, std::memory_order_release); } uint64_t now = std::chrono::system_clock::now().time_since_epoch().count(); if (now - last_clean_time_ > clean_interval_ms) { clean_dead_readers(); last_clean_time_ = now; } } uint64_t get_slowest_reader_sequence() const { uint64_t slowest_seq = p_header_->write_index.load(std::memory_order_relaxed); bool reader_found = false; for (size_t i = 0; i < MaxReaders; ++i) { const detail::ReaderSlot *slot = get_reader_slot(i); auto pid = slot->pid.load(std::memory_order_acquire); if (pid != 0 && pid != current_instance_id_) { uint64_t reader_seq = slot->sequence.load(std::memory_order_acquire); if (!reader_found) { slowest_seq = reader_seq; reader_found = true; } else { slowest_seq = std::min(slowest_seq, reader_seq); } } } return slowest_seq; } void clean_dead_readers() { for (size_t i = 0; i < MaxReaders; ++i) { detail::ReaderSlot *slot = get_reader_slot(i); DWORD pid = slot->pid.load(std::memory_order_acquire); if (pid != 0 && !is_pid_alive(pid)) { slot->pid.store(0, std::memory_order_release); slot->sequence.store(0, std::memory_order_release); } } } uint64_t last_clean_time_ = 0; static constexpr uint64_t clean_interval_ms = 100; Config config_; std::string name_; HANDLE h_map_file_ = NULL; HANDLE h_data_ready_event_ = NULL; void *p_shared_mem_ = nullptr; detail::SharedMemoryHeader *p_header_ = nullptr; char *p_buffer_start_ = nullptr; uint64_t local_read_sequence_ = 0; int my_reader_slot_index_ = -1; }; } // namespace ipc #if defined(_MSC_VER) #pragma warning(pop) #endif ================================================ FILE: scripts/bindgen.bat ================================================ npx breeze-bindgen@latest -i src/script/bindings/binding_types.h --nameFilter chromatic::js -o src/script/bindings --tsModuleName chromatic ================================================ FILE: scripts/rebuild.ps1 ================================================ $targetPath = "C:\Program Files\Tencent\QQNT-dev\QQ.exe" # kill the process if it is running foreach ($process in Get-Process -ErrorAction SilentlyContinue) { if ($process.Path -eq $targetPath) { Stop-Process -Id $process.Id -Force || Write-Host "Failed to stop process: $($process.Id) - $($process.Name)" Write-Host "Killed process: $($process.Id) - $($process.Name)" } } xmake b chromatic Start-Process -FilePath $targetPath -WorkingDirectory "C:\Program Files\Tencent\QQNT-dev" -NoNewWindow ================================================ FILE: src/config.cc ================================================ #include "config.h" #include #include #include #include #include #include "rfl.hpp" #include "rfl/DefaultIfMissing.hpp" #include "rfl/json.hpp" #include "ylt/easylog.hpp" #include "utils.h" #include "windows.h" namespace chromatic { std::unique_ptr config::current; std::vector> config::on_reload; void config::write_config() { auto config_file = data_directory() / "config.json"; std::ofstream ofs(config_file); if (!ofs) { std::cerr << "Failed to write config file." << std::endl; return; } ofs << rfl::json::write(*config::current); } void config::read_config() { auto config_file = data_directory() / "config.json"; #ifdef __llvm__ std::ifstream ifs(config_file); if (!std::filesystem::exists(config_file)) { auto config_file = data_directory() / "config.json"; std::ofstream ofs(config_file); if (!ofs) { std::cerr << "Failed to write config file." << std::endl; } ofs << R"({ })"; } if (!ifs) { std::cerr << "Config file could not be opened. Using default config instead." << std::endl; config::current = std::make_unique(); config::current->debug_console = true; } else { std::string json_str; std::copy(std::istreambuf_iterator(ifs), std::istreambuf_iterator(), std::back_inserter(json_str)); if (auto json = rfl::json::read( json_str)) { config::current = std::make_unique(json.value()); } else { std::cerr << "Failed to read config file: " << json.error().what() << "\nUsing default config instead." << std::endl; config::current = std::make_unique(); config::current->debug_console = true; } } for (auto &fn : config::on_reload) { try { fn(); } catch (const std::exception &e) { ELOGFMT(WARN, "Failed to run on_reload function: {}", e.what()); } } #else #pragma message \ "We don't support loading config file on MSVC because of a bug in MSVC." dbgout("We don't support loading config file when compiled with MSVC " "because of a bug in MSVC."); config::current = std::make_unique(); config::current->debug_console = true; #endif if (config::current->debug_console) { ShowWindow(GetConsoleWindow(), SW_SHOW); } else { ShowWindow(GetConsoleWindow(), SW_HIDE); } } std::filesystem::path config::data_directory() { static std::optional path; static std::mutex mtx; std::lock_guard lock(mtx); if (!path) { path = std::filesystem::path(utils::env("USERPROFILE").value()) / ".chromatic" / utils::current_executable_path().filename().string(); } if (!std::filesystem::exists(*path)) { std::filesystem::create_directories(*path); } return path.value(); } void config::run_config_loader() { auto config_path = config::data_directory() / "config.json"; ELOGFMT(INFO, "config file: {}", config_path.string()); config::read_config(); std::thread([config_path]() { auto last_mod = std::filesystem::last_write_time(config_path); while (true) { if (std::filesystem::last_write_time(config_path) != last_mod) { last_mod = std::filesystem::last_write_time(config_path); config::read_config(); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }).detach(); } std::string config::dump_config() { if (!current) { return "{}"; } return rfl::json::write(*current); } } // namespace chromatic ================================================ FILE: src/config.h ================================================ #pragma once #include #include #include #include #include namespace chromatic { struct config { bool debug_console = true; struct detector { struct chrome { bool enable = true; std::string chrome_module_name = ""; } chrome; } detector; std::string $schema; static std::unique_ptr current; static void read_config(); static void write_config(); static void run_config_loader(); static std::string dump_config(); static std::vector> on_reload; static std::filesystem::path data_directory(); }; } // namespace chromatic ================================================ FILE: src/context.cc ================================================ #include "context.h" #include "config.h" #include "cpptrace/basic.hpp" #include "utils.h" #include "ylt/easylog.hpp" #include "cpptrace/cpptrace.hpp" #include "cpptrace/from_current.hpp" #include "Windows.h" #include "blook/blook.h" #include #include #include #include #include "rfl.hpp" #include "rfl/json.hpp" #include "./hooks/blink_parse_html_manipulator.h" #include "./hooks/disable-integrity.h" #include "./hooks/wait_for_module_load.h" namespace chromatic { std::unique_ptr context::current = nullptr; void context::init_singleton() { SetUnhandledExceptionFilter(+[](EXCEPTION_POINTERS *ep) -> long { ELOGFMT(FATAL, "Unhandled exception: {}", cpptrace::stacktrace::current()); Sleep(1000); return EXCEPTION_CONTINUE_SEARCH; }); CPPTRACE_TRY { if (!current) { current = std::make_unique(); current->init_context(); } } CPPTRACE_CATCH(const std::exception &e) { ELOGFMT(ERROR, "Failed to initialize context: {} {}", e.what(), cpptrace::from_current_exception()); Sleep(1000); } catch (...) { ELOGFMT(ERROR, "Failed to initialize context: unknown exception: {}", cpptrace::from_current_exception()); Sleep(1000); } } context::context() {} void context::detect_process_type() { auto cmdline = std::wstring(GetCommandLineW()); auto &cfg = *config::current.get(); if (cfg.detector.chrome.enable) { auto chrome_module = config::current->detector.chrome.chrome_module_name; if (cmdline.find(L"--type=gpu") != std::wstring::npos) { type.chrome_type = process_type::chrome_type::gpu; } else if (cmdline.find(L"--type=renderer") != std::wstring::npos) { type.chrome_type = process_type::chrome_type::renderer; } else if (cmdline.find(L"--type=utility") != std::wstring::npos) { type.chrome_type = process_type::chrome_type::utility; } else if (cmdline.find(L"--type=network") != std::wstring::npos) { type.chrome_type = process_type::chrome_type::network; } else if (cmdline.find(L"--type=") == std::wstring::npos) { type.chrome_type = process_type::chrome_type::main; } if (type.chrome_type) { ELOGFMT(INFO, "Detected Chrome process type: {}", static_cast(type.chrome_type.value())); } else { ELOGFMT(WARN, "Failed to detect Chrome process type."); } auto proc = blook::Process::self(); if (chrome_module == "") { std::shared_ptr chrome_mod; if (auto mod = proc->module("chrome.dll")) { chrome_mod = mod.value(); } else if (auto mod = proc->module("edge.dll")) { chrome_mod = mod.value(); } else if (auto mod = proc->module("libcef.dll")) { chrome_mod = mod.value(); } else if (auto mod = proc->process_module()) { chrome_mod = mod.value(); } // verify if the module is actually chrome constexpr auto chrome_signature = "\\content\\browser\\renderer_host\\debug_urls.cc"; if (chrome_mod && chrome_mod->section(".rdata") && chrome_mod->section(".rdata")->find_one(chrome_signature)) { type.chrome_module = chrome_mod; } else { type.chrome_module = {}; } if (type.chrome_module) { on_before_chrome_startup(); } } else { if (GetModuleHandleW(utils::utf8_to_wstring(chrome_module).c_str())) { type.chrome_module = proc->module( std::filesystem::path(chrome_module).filename().string()) .value(); on_before_chrome_startup(); } else { ELOGFMT(WARN, "Chrome module {} not found, waiting for it to load...", chrome_module); hooks::wait_for_module_load::wait_for_module( chrome_module, [this](void *mod) { if (mod) { ELOGFMT(INFO, "Chrome module {} loaded", mod); type.chrome_module = blook::Process::self() ->module( utils::get_module_path(mod).filename().string()) .value(); auto entry_hook = type.chrome_module->entry_point()->inline_hook(); entry_hook->install( [this, entry_hook](size_t a, size_t b, size_t c) { on_before_chrome_startup(); return entry_hook->call_trampoline(a, b, c); }); } else { ELOGFMT(ERROR, "Failed to load Chrome module"); } }) .wait(); } } if (type.chrome_module) { ELOGFMT( INFO, "Detected Chrome module: {}", utils::get_module_path(type.chrome_module->base().data()).string()); } else { ELOGFMT(WARN, "Chrome module not found, some features may not work."); } } } void context::init_ipc() { // process_ipc.connect(std::format( // "chromatic://process/{}", // std::hash{}(utils::current_executable_path().string()))); process_ipc.connect("chromatic://process/"); } void context::on_before_chrome_startup() { ELOGFMT(INFO, "on_before_chrome_startup called"); blink_parse_html_manipulator::install(); } void context::init_context() { auto cmdline = std::wstring(GetCommandLineW()); ELOGFMT(INFO, "Command line: {}", utils::wstring_to_utf8(cmdline)); bool is_probably_main = cmdline.find(L"--type=") == std::wstring::npos, is_renderer = cmdline.find(L"--type=renderer") != std::wstring::npos; if (!is_probably_main && !is_renderer) { ELOGFMT(INFO, "Chromatic is not running in this process."); return; } init_ipc(); if (is_probably_main) { DWORD mode; AllocConsole(); freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); static HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleMode(h, &mode); SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); static auto log_msg_raw = [](const auto &msg) { std::string msg_formatted = std::format("\033[47;30m chromatic \033[0m {}", msg); msg_formatted.erase( std::find_if(msg_formatted.rbegin(), msg_formatted.rend(), [](unsigned char ch) { return !std::isspace(ch); }) .base(), msg_formatted.end()); msg_formatted += "\n"; WriteConsoleA(h, msg_formatted.c_str(), static_cast(msg_formatted.size()), nullptr, nullptr); }; easylog::add_appender(log_msg_raw); hooks::windows::disable_integrity(); config::run_config_loader(); config::on_reload.push_back([this]() { ELOGFMT(INFO, "Config reloaded, broadcasting to other processes."); process_ipc.send("config_reload", *config::current); }); ELOGFMT(INFO, "Chromatic v0.0.0, initialized as main process."); process_ipc.send("config_reload", *config::current); process_ipc.add_call_handler("get_config", []() { return *config::current; }); process_ipc.add_call_handler( "log", [](const std::string &msg) { log_msg_raw("[other_proc] " + msg); return true; }); std::thread([this]() { detect_process_type(); script = std::make_unique(); static std::unordered_map> symbol_cache; auto symbols_file = config::data_directory() / "symbols.json"; if (std::filesystem::exists(symbols_file)) { try { std::ifstream ifs(symbols_file, std::ios::binary); if (ifs) { symbol_cache = rfl::json::read>>(ifs) .value(); } } catch (const std::exception &e) { ELOGFMT(ERROR, "Failed to read symbols from {}: {}", symbols_file.string(), e.what()); } } process_ipc.add_call_handler< bool, std::pair>>( "set_symbol", [](auto &symbol) { symbol_cache[symbol.first] = symbol.second; std::ofstream ofs(config::data_directory() / "symbols.json", std::ios::binary | std::ios::trunc); if (ofs) { ofs << rfl::json::write(symbol_cache); } return true; }); process_ipc.add_call_handler, std::string>( "get_symbol", [](const std::string &name) { auto it = symbol_cache.find(name); if (it != symbol_cache.end()) { return it->second; } return std::pair{0, 0}; }); blink_parse_html_manipulator::register_js(); }).detach(); } else if (cmdline.find(L"--type=renderer") != std::wstring::npos) { easylog::add_appender([this](std::string_view msg) { process_ipc.call("log", std::string(msg)); }); ELOGFMT(INFO, "requesting config from main process."); process_ipc.add_listener("config_reload", [](const config &cfg) { ELOGFMT(INFO, "Received config_reload"); config::current = std::make_unique(cfg); }); process_ipc.call("get_config"); process_ipc.send(breeze_ipc::packet { .seq = 1, .return_for_call = 0, .name = "call_get_config", .data = "true" }); auto p = process_ipc.call_and_poll("get_config"); if (p) { config::current = std::make_unique(p.value()); } else { ELOGFMT(WARN, "Failed to get config from main process, using default."); config::current = std::make_unique(); } ELOGFMT(INFO, "Chromatic v0.0.0, initialized as renderer process."); ELOGFMT(INFO, "Config loaded: {}", config::current->dump_config()); detect_process_type(); } } } // namespace chromatic ================================================ FILE: src/context.h ================================================ #pragma once #include "blook/module.h" #include "config.h" #include "script/script.h" #include #include #include "ipc.h" namespace chromatic { struct context { static std::unique_ptr current; static void init_singleton(); struct process_type { enum chrome_type { main, renderer, gpu, utility, network }; std::optional chrome_type = {}; std::shared_ptr chrome_module = {}; }; process_type type = {}; breeze_ipc process_ipc; std::unique_ptr script = nullptr; void init_ipc(); void init_context(); context(); void on_before_chrome_startup(); private: void detect_process_type(); }; } // namespace chromatic ================================================ FILE: src/entry.cc ================================================ #include #include #include #include #include #include "blook/blook.h" #include "config.h" #include "context.h" #include "ylt/easylog.hpp" #include "utils.h" #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include "Windows.h" namespace chromatic { int main() { easylog::init_log(easylog::Severity::INFO, "", false, false); easylog::add_appender( [](std::string_view msg) { OutputDebugStringA(msg.data()); }); context::init_singleton(); return 0; } } // namespace chromatic int APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: { auto cmdline = std::string(GetCommandLineA()); if (cmdline.contains("--type=gpu")) { return 1; // Skip if this is a gpu process } chromatic::main(); break; } } return 1; } ================================================ FILE: src/hooks/blink_parse_html_manipulator.cc ================================================ #include "blink_parse_html_manipulator.h" #include "../context.h" #include "../utils.h" #include "blook/function.h" #include "ylt/easylog.hpp" #include #include #include #include #include "blook/blook.h" #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include "Windows.h" #include "../script/bindings/binding_types.h" extern std::list> blink_parse_html_manipulators; namespace chromatic { struct BlinkHTMLData { const std::span &data; std::optional> replacement; }; struct BlinkUtilSpan { char *data; size_t size; }; void blink_parse_html_manipulator::install() { if (context::current->type.chrome_type != context::process_type::renderer || !context::current->type.chrome_module) { return; } ELOGFMT(INFO, "BlinkParseHTMLManipulator: Checking for cached symbol " "HTMLParser__AppendBytes"); auto &chrome = context::current->type.chrome_module; auto rdata = chrome->section(".rdata").value(); auto text = chrome->section(".text").value(); auto crc32 = text.crc32(); static std::optional HTMLParser__AppendBytes = {}; if (auto res = context::current->process_ipc .call_and_poll>( "get_symbol", std::string("HTMLParser__AppendBytes")); res && res->first != 0 && res->second == crc32) { HTMLParser__AppendBytes = text.add(res->first).as_function(); ELOGFMT(INFO, "BlinkParseHTMLManipulator: Using cached symbol " "HTMLParser__AppendBytes at {}", HTMLParser__AppendBytes->data()); } else { auto appendBytesText = rdata.find_one("HTMLDocumentParser::appendBytes"); if (!appendBytesText) { ELOGFMT(WARN, "BlinkParseHTMLManipulator: HTMLDocumentParser::appendBytes not " "found in rdata section"); return; } ELOGFMT(INFO, "BlinkParseHTMLManipulator: Found HTMLDocumentParser::appendBytes " "string at {} in rdata section", appendBytesText.value().data()); auto xref = text.find_xref(appendBytesText.value()); if (!xref) { ELOGFMT(WARN, "BlinkParseHTMLManipulator: HTMLDocumentParser::appendBytes xref " "not found in text section"); return; } ELOGFMT(INFO, "BlinkParseHTMLManipulator: Found HTMLDocumentParser::appendBytes " "function at {} in text section", xref->data()); HTMLParser__AppendBytes = xref->find_upwards({0x56, 0x57}).value().as_function(); context::current->process_ipc .call>>( "set_symbol", std::pair( std::string("HTMLParser__AppendBytes"), std::pair( (size_t)(HTMLParser__AppendBytes->pointer() - text).data(), crc32))); } static auto HTMLParser__AppendBytes_Hook = HTMLParser__AppendBytes->inline_hook(); const auto pFunc = (uint8_t *)HTMLParser__AppendBytes->data(); if (pFunc[0] == 0x56 && pFunc[1] == 0x57 && pFunc[2] == 0x53) { ELOGFMT(INFO, "BlinkParseHTMLManipulator: Using older function signature"); HTMLParser__AppendBytes_Hook->install(+[](void *self, uint8_t *data, size_t size) { ELOGFMT(DEBUG, "BlinkParseHTMLManipulator: Hook called with {} bytes", size); auto span_data = std::span(reinterpret_cast(data), size); std::vector> contexts; BlinkHTMLData html_data{.data = span_data, .replacement = {}}; auto res = context::current->process_ipc.call_and_poll( "on_blink_parse_html_manipulate", std::string(html_data.data.data(), html_data.data.size())); if (res.has_value() && res.value() != std::string_view(html_data.data.data(), html_data.data.size())) { ELOGFMT( DEBUG, "BlinkParseHTMLManipulator: HTML content modified, new size: {}", res.value().size()); html_data.replacement = std::vector(res.value().begin(), res.value().end()); } auto ret = html_data.replacement.has_value() ? HTMLParser__AppendBytes_Hook->call_trampoline( self, html_data.replacement->data(), html_data.replacement->size()) : HTMLParser__AppendBytes_Hook->call_trampoline( self, data, size); return ret; }); } else { ELOGFMT(INFO, "BlinkParseHTMLManipulator: Using newer function signature"); HTMLParser__AppendBytes_Hook->install(+[](void *self, BlinkUtilSpan &data) { ELOGFMT(DEBUG, "BlinkParseHTMLManipulator: Hook called with {} bytes", data.size); auto span_data = std::span(reinterpret_cast(data.data), data.size); std::vector> contexts; BlinkHTMLData html_data{.data = span_data, .replacement = {}}; auto html = std::string(html_data.data.data(), html_data.data.size()); if (auto res = context::current->process_ipc .call( "on_blink_parse_html_manipulate", html) .get(); res != html) { ELOGFMT( DEBUG, "BlinkParseHTMLManipulator: HTML content modified, new size: {}", res.size()); html_data.replacement = std::vector(res.begin(), res.end()); } auto blink_span = html_data.replacement.has_value() ? BlinkUtilSpan{reinterpret_cast( html_data.replacement->data()), html_data.replacement->size()} : BlinkUtilSpan{reinterpret_cast(span_data.data()), span_data.size()}; auto ret = HTMLParser__AppendBytes_Hook->call_trampoline( self, blink_span); return ret; }); } context::current->process_ipc.add_call_handler( "is_blink_parse_html_manipulator_available", []() { return true; }); ELOGFMT(INFO, "BlinkParseHTMLManipulator: Installation completed successfully"); } bool blink_parse_html_manipulator::is_available() { auto result = context::current->process_ipc.call( "is_blink_parse_html_manipulator_available"); if (result.valid() && result.wait_for(std::chrono::milliseconds(20)) == std::future_status::ready) { return true; } else { ELOGFMT( DEBUG, "BlinkParseHTMLManipulator: Availability check failed or timed out"); return false; } } void blink_parse_html_manipulator::register_js() { ELOGFMT(INFO, "BlinkParseHTMLManipulator: Registering JavaScript handlers"); context::current->process_ipc.add_call_handler< std::string, std::string>("on_blink_parse_html_manipulate", [](const std::string &_html) { auto html = _html; size_t manipulator_count = 0; for (auto &manipulator : blink_parse_html_manipulators) { try { if (auto res = manipulator(html); !res.empty()) { html = res; } } catch (const std::exception &e) { ELOGFMT(ERROR, "BlinkParseHTMLManipulator: Exception in manipulator {}: {}", manipulator_count, e.what()); } catch (...) { ELOGFMT( ERROR, "BlinkParseHTMLManipulator: Unknown exception in manipulator {}", manipulator_count); } manipulator_count++; } if (manipulator_count > 0) { ELOGFMT(INFO, "BlinkParseHTMLManipulator: Processed {} manipulators", manipulator_count); } return html; }); context::current->script->ctx.on_bind.push_back([]() { ELOGFMT(DEBUG, "BlinkParseHTMLManipulator: Clearing manipulators on script bind"); blink_parse_html_manipulators.clear(); }); } } // namespace chromatic ================================================ FILE: src/hooks/blink_parse_html_manipulator.h ================================================ #pragma once #include #include #include namespace chromatic { struct blink_parse_html_manipulator { static void install(); static bool is_available(); static void register_js(); }; } ================================================ FILE: src/hooks/disable-integrity.cc ================================================ #include "disable-integrity.h" #include "blook/blook.h" #include "Windows.h" #include #include "processthreadsapi.h" bool chromatic::hooks::windows::disable_integrity() { static std::shared_ptr disable_integrity_hook; auto func = (blook::Pointer)(void *)GetProcAddress( LoadLibraryA("Kernel32.dll"), "UpdateProcThreadAttribute"); if (!func) { return false; } disable_integrity_hook = func.as_function().inline_hook(); disable_integrity_hook->install(( void *)+[](LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, DWORD dwFlags, DWORD_PTR Attribute, PVOID lpValue, SIZE_T cbSize, PVOID lpPreviousValue, PSIZE_T lpReturnSize) { if (Attribute == PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY && cbSize >= sizeof(DWORD64)) { PDWORD64 old_policy = &((PDWORD64)lpValue)[0]; *old_policy &= ~( DWORD64)(PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON); *old_policy &= (DWORD64)(PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON); } return disable_integrity_hook ->trampoline_t()( lpAttributeList, dwFlags, Attribute, lpValue, cbSize, lpPreviousValue, lpReturnSize); }); return true; } ================================================ FILE: src/hooks/disable-integrity.h ================================================ #pragma once namespace chromatic::hooks::windows { bool disable_integrity(); } ================================================ FILE: src/hooks/wait_for_module_load.cc ================================================ #include "wait_for_module_load.h" #include "../utils.h" #include "blook/blook.h" #include #include #include #include #include "Windows.h" #include "blook/process.h" namespace chromatic::hooks::wait_for_module_load { struct WaitTask { std::function verifier; std::promise> promise; WaitTask(std::function v, std::promise> p) : verifier(std::move(v)), promise(std::move(p)) {} }; static std::list> wait_tasks; static void process_module_load(HMODULE mod) { auto module_path = utils::get_module_path(mod); for (auto it = wait_tasks.begin(); it != wait_tasks.end();) { if ((*it)->verifier(module_path)) { (*it)->promise.set_value(blook::Process::self() ->module(module_path.filename().string()) .value()); it = wait_tasks.erase(it); } else { ++it; } } } static void ensure_loadlibrary_hooks() { static std::atomic_bool installed; if (installed.exchange(true)) return; auto self = blook::Process::self(); auto kernel32 = self->module("kernel32.dll").value(); static auto LoadLibraryExAHook = kernel32->exports("LoadLibraryExA")->inline_hook(), LoadLibraryExWHook = kernel32->exports("LoadLibraryExW")->inline_hook(); LoadLibraryExAHook->install(+[]([in] LPCSTR lpLibFileName, HANDLE hFile, [in] DWORD dwFlags) -> HMODULE { auto mod = LoadLibraryExAHook->call_trampoline(lpLibFileName, hFile, dwFlags); process_module_load(mod); return mod; }); LoadLibraryExWHook->install(+[]([in] LPCWSTR lpLibFileName, HANDLE hFile, [in] DWORD dwFlags) -> HMODULE { auto mod = LoadLibraryExWHook->call_trampoline(lpLibFileName, hFile, dwFlags); process_module_load(mod); return mod; }); } std::future> wait_for_module(std::function verifier) { ensure_loadlibrary_hooks(); std::promise> promise; auto future = promise.get_future(); wait_tasks.emplace_back( std::make_unique(std::move(verifier), std::move(promise))); return future; } static std::future> wait_for_module(std::string_view module_name, std::function callback) { if (module_name.empty()) { throw std::invalid_argument("Module name cannot be empty"); } if (GetModuleHandleA(module_name.data()) != nullptr) { return std::async(std::launch::deferred, [&, module_name = std::string( module_name)]() { callback(GetModuleHandleA(module_name.c_str())); return blook::Process::self()->module(std::string(module_name)).value(); }); } return wait_for_module([module_name, callback = std::move(callback)]( std::filesystem::path path) { callback(GetModuleHandleW(path.filename().c_str())); return path.filename() == module_name; }); }; } // namespace chromatic::hooks::wait_for_module_load ================================================ FILE: src/hooks/wait_for_module_load.h ================================================ #pragma once #include "blook/module.h" #include #include namespace chromatic::hooks::wait_for_module_load { std::future> wait_for_module(std::function verifier); std::future> wait_for_module(std::string_view module_name, std::function callback = [](void*) {}); } // namespace chromatic::hooks::wait_for_module_load ================================================ FILE: src/script/bindings/binding_qjs.h ================================================ // This file is generated by Breeze.JS Bindgen (https://github.com/breeze-shell/breeze-js-bindgen) // Do not modify this file manually! #pragma once #include "binding_types.h" #include "quickjspp.hpp" template struct js_bind { static void bind(qjs::Context::Module &mod) {} }; template <> struct qjs::js_traits { static chromatic::js::chrome unwrap(JSContext *ctx, JSValueConst v) { chromatic::js::chrome obj; return obj; } static JSValue wrap(JSContext *ctx, const chromatic::js::chrome &val) noexcept { JSValue obj = JS_NewObject(ctx); return obj; } }; template<> struct js_bind { static void bind(qjs::Context::Module &mod) { mod.class_("chrome") .constructor<>() ; } }; template <> struct qjs::js_traits { static chromatic::js::chrome::blink unwrap(JSContext *ctx, JSValueConst v) { chromatic::js::chrome::blink obj; return obj; } static JSValue wrap(JSContext *ctx, const chromatic::js::chrome::blink &val) noexcept { JSValue obj = JS_NewObject(ctx); return obj; } }; template<> struct js_bind { static void bind(qjs::Context::Module &mod) { mod.class_("chrome::blink") .constructor<>() .static_fun<&chromatic::js::chrome::blink::add_blink_parse_html_manipulator>("add_blink_parse_html_manipulator") .static_fun<&chromatic::js::chrome::blink::is_parse_html_manipulator_available>("is_parse_html_manipulator_available") ; } }; template <> struct qjs::js_traits { static chromatic::js::chrome::blink::blink_parse_manipulate_context unwrap(JSContext *ctx, JSValueConst v) { chromatic::js::chrome::blink::blink_parse_manipulate_context obj; obj.html = js_traits::unwrap(ctx, JS_GetPropertyStr(ctx, v, "html")); obj.url = js_traits::unwrap(ctx, JS_GetPropertyStr(ctx, v, "url")); return obj; } static JSValue wrap(JSContext *ctx, const chromatic::js::chrome::blink::blink_parse_manipulate_context &val) noexcept { JSValue obj = JS_NewObject(ctx); JS_SetPropertyStr(ctx, obj, "html", js_traits::wrap(ctx, val.html)); JS_SetPropertyStr(ctx, obj, "url", js_traits::wrap(ctx, val.url)); return obj; } }; template<> struct js_bind { static void bind(qjs::Context::Module &mod) { mod.class_("chrome::blink::blink_parse_manipulate_context") .constructor<>() .fun<&chromatic::js::chrome::blink::blink_parse_manipulate_context::html>("html") .fun<&chromatic::js::chrome::blink::blink_parse_manipulate_context::url>("url") ; } }; template <> struct qjs::js_traits { static chromatic::js::infra unwrap(JSContext *ctx, JSValueConst v) { chromatic::js::infra obj; return obj; } static JSValue wrap(JSContext *ctx, const chromatic::js::infra &val) noexcept { JSValue obj = JS_NewObject(ctx); return obj; } }; template<> struct js_bind { static void bind(qjs::Context::Module &mod) { mod.class_("infra") .constructor<>() .static_fun<&chromatic::js::infra::log>("log") ; } }; inline void bindAll(qjs::Context::Module &mod) { js_bind::bind(mod); js_bind::bind(mod); js_bind::bind(mod); js_bind::bind(mod); } ================================================ FILE: src/script/bindings/binding_types.cc ================================================ #include "binding_types.h" #include #include "breeze-js/quickjspp.hpp" #include "ylt/easylog.hpp" #include "../../context.h" std::list> blink_parse_html_manipulators; namespace chromatic::js { void chrome::blink::add_blink_parse_html_manipulator( std::function manipulator) { blink_parse_html_manipulators.push_back([manipulator](std::string ctx) { try { return manipulator(ctx); } catch (const std::exception &e) { ELOGFMT(ERROR, "Exception in blink parse HTML manipulator: {}", e.what()); return std::string(); } catch (...) { ELOGFMT(ERROR, "Unknown exception in blink parse HTML manipulator"); return std::string(); } }); } void infra::log(qjs::rest msg) { std::string log_msg; for (const auto &m : msg) { log_msg += m + " "; } log_msg.pop_back(); ELOGFMT(INFO, "{}", log_msg); } } // namespace chromatic::js ================================================ FILE: src/script/bindings/binding_types.d.ts ================================================ // This file is generated by bindgen // Do not modify this file manually! declare module 'chromatic' { export class chrome { } namespace chrome { export class blink { /** * * @param arg0: ((arg1: string) => string) * @returns void */ static add_blink_parse_html_manipulator(arg0: ((arg1: string) => string)): void static is_parse_html_manipulator_available(): boolean } } namespace chrome.blink { export class blink_parse_manipulate_context { html: string url: string } } export class infra { /** * * @param msg: qjs.rest * @returns void */ static log(msg: qjs.rest): void } } ================================================ FILE: src/script/bindings/binding_types.h ================================================ #pragma once #include #include #include #include #include "../../hooks/blink_parse_html_manipulator.h" namespace qjs { template struct rest; } namespace chromatic::js { struct chrome { struct blink { struct blink_parse_manipulate_context { std::string html; std::string url; }; static void add_blink_parse_html_manipulator( std::function); static bool is_parse_html_manipulator_available() { return chromatic::blink_parse_html_manipulator::is_available(); } }; }; struct infra { static void log(qjs::rest msg); }; } // namespace chromatic::js ================================================ FILE: src/script/bindings/quickjspp.hpp ================================================ #pragma once #include "breeze-js/quickjspp.hpp" ================================================ FILE: src/script/script.cc ================================================ #include "script.h" #include "../config.h" #include "bindings/binding_qjs.h" #include "ylt/easylog.hpp" namespace chromatic { script_engine::script_engine() { js_watch_thread = std::thread([this]() { auto script_dir = config::data_directory() / "scripts"; if (!std::filesystem::exists(script_dir)) { std::filesystem::create_directories(script_dir); } ctx.on_bind.push_back([this]() { auto &mod = ctx.js->addModule("chromatic"); bindAll(mod); }); ELOGFMT(INFO, "Script engine initialized."); ctx.watch_folder(script_dir); }); } script_engine::~script_engine() { if (js_watch_thread.joinable()) { ctx.stop_signal = std::make_shared(1); js_watch_thread.join(); } } } // namespace chromatic ================================================ FILE: src/script/script.h ================================================ #pragma once #include "breeze-js/script.h" namespace chromatic { struct script_engine { breeze::script_context ctx; script_engine(); ~script_engine(); private: std::thread js_watch_thread; }; } // namespace chromatic ================================================ FILE: src/utils.cc ================================================ #include "utils.h" #include "Windows.h" namespace chromatic::utils { std::optional env(const std::string &name) { wchar_t buffer[32767]; GetEnvironmentVariableW(utf8_to_wstring(name).c_str(), buffer, 32767); if (buffer[0] == 0) { return std::nullopt; } return wstring_to_utf8(buffer); } std::string wstring_to_utf8(std::wstring const &str) { int size_needed = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0, NULL, NULL); std::string result(size_needed, 0); WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), &result[0], size_needed, NULL, NULL); return result; } std::wstring utf8_to_wstring(std::string const &str) { int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0); std::wstring result(size_needed, 0); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &result[0], size_needed); return result; } std::filesystem::path current_executable_path() { static std::filesystem::path path = chromatic::utils::get_module_path(); return path; } std::filesystem::path get_module_path(void *module_handle) { HMODULE hModule = static_cast(module_handle); wchar_t buffer[MAX_PATH]; if (GetModuleFileNameW(hModule, buffer, MAX_PATH) == 0) { return {}; } return std::filesystem::path(buffer); } task_queue::task_queue() : stop(false) { worker = std::thread(&task_queue::run, this); } task_queue::~task_queue() { { std::lock_guard lock(queue_mutex); stop = true; } condition.notify_all(); if (worker.joinable()) { worker.join(); } } void task_queue::run() { while (true) { std::function task; { std::unique_lock lock(queue_mutex); condition.wait(lock, [this]() { return stop || !tasks.empty(); }); if (stop && tasks.empty()) { return; } task = std::move(tasks.front()); tasks.pop(); } task(); } } } // namespace chromatic::utils ================================================ FILE: src/utils.h ================================================ #pragma once #include #include #include #include #include namespace chromatic { namespace utils { std::optional env(const std::string &name); std::string wstring_to_utf8(std::wstring const &str); std::wstring utf8_to_wstring(std::string const &str); std::filesystem::path current_executable_path(); std::filesystem::path get_module_path(void *module_handle = nullptr); struct task_queue { public: task_queue(); ~task_queue(); template auto add_task(F &&f, Args &&...args) -> std::future> { using return_type = std::invoke_result_t; if (stop) { throw std::runtime_error("add_task called on stopped task_queue"); } auto task = std::make_shared>( std::bind(std::forward(f), std::forward(args)...)); std::future res = task->get_future(); { std::lock_guard lock(queue_mutex); tasks.emplace([task]() { (*task)(); }); } condition.notify_one(); return res; } private: void run(); std::thread worker; std::queue> tasks; std::mutex queue_mutex; std::condition_variable condition; bool stop; }; } // namespace utils } // namespace chromatic ================================================ FILE: test/ipc_test.cc ================================================ #include "ipc.h" #include #include #include #include using namespace chromatic; TEST(IPCTest, BasicMessageSendReceive) { breeze_ipc ipc1, ipc2; ipc1.connect("chromatic://process/1"); ipc2.connect("chromatic://process/1"); bool received = false; auto remover = ipc2.add_listener( "test_msg", [&](const breeze_ipc::packet &pkt) { received = true; }); ipc1.send("test_msg", test_serializable_struct{1, 2.0f, {'a', 'b', 'c'}}); std::this_thread::sleep_for( std::chrono::milliseconds(100)); // Give time for IPC to process EXPECT_TRUE(received); } TEST(IPCTest, RPCCall) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); auto remover = server.add_call_handler("add_one", [](int x) { return x + 1; }); auto future = client.call("add_one", 5); EXPECT_EQ(future.get(), 6); } TEST(IPCTest, StringReturnRPC) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); auto remover = server.add_call_handler( "echo", [](const std::string &s) { return s; }); auto future = client.call("echo", "hello world"); EXPECT_EQ(future.get(), "hello world"); } TEST(IPCTest, PairReturnRPC) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); auto remover = server.add_call_handler, int>( "make_pair", [](int x) { return std::make_pair(x, std::to_string(x)); }); auto future = client.call, int>("make_pair", 42); auto result = future.get(); EXPECT_EQ(result.first, 42); EXPECT_EQ(result.second, "42"); } TEST(IPCTest, StringPairRPC) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); auto remover = server.add_call_handler, std::pair>( "concat_pair", [](const auto &p) { return std::make_pair(p.first + p.second, p.second + p.first); }); auto future = client.call, std::pair>( "concat_pair", std::make_pair("hello", "world")); auto result = future.get(); EXPECT_EQ(result.first, "helloworld"); EXPECT_EQ(result.second, "worldhello"); } struct blink_parse_manipulate_context { std::string html; std::string url; }; TEST(IPCTest, BlinkContextRPC) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); auto remover = server.add_call_handler( "process_html", [](const auto &ctx) { blink_parse_manipulate_context result = ctx; result.html += ""; return result; }); blink_parse_manipulate_context original{.html = "test", .url = "http://example.com"}; auto future = client .call( "process_html", original); auto result = future.get(); EXPECT_EQ(result.html, "test"); EXPECT_EQ(result.url, "http://example.com"); } TEST(IPCTest, Serialization) { test_serializable_struct original{42, 3.14f, {'x', 'y', 'z'}}; auto serialized = struct_pack::serialize(original); auto deserialized = struct_pack::deserialize(serialized); ASSERT_TRUE(deserialized.has_value()); EXPECT_EQ(deserialized->a, 42); EXPECT_FLOAT_EQ(deserialized->b, 3.14f); auto vec = std::vector{'x', 'y', 'z'}; EXPECT_EQ(deserialized->c, vec); } TEST(IPCTest, ALotOfPackets) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); int count = 1000; int received = 0; auto remover = server.add_listener( "test_msg", [&](const breeze_ipc::packet &pkt) { received++; }); for (int i = 0; i < count; ++i) { client.send("test_msg", test_serializable_struct{i, i * 1.1f, {'a', 'b'}}); } std::this_thread::sleep_for( std::chrono::seconds(1)); // Give time for IPC to process EXPECT_EQ(received, count); } TEST(IPCTest, LargePacket) { breeze_ipc server, client; server.connect("chromatic://process/"); client.connect("chromatic://process/"); // ~1MB of data std::string large_data(1024 * 1024, 'x'); auto remover = server.add_call_handler( "echo_large", [](const std::string &data) { std::println("Received large data of size: {}", data.size()); return data; }); auto future = client.call("echo_large", large_data); EXPECT_EQ(future.get(), large_data); } int main(int argc, char **argv) { // auto channel = breeze_ipc{}; // channel.connect("chromatic://process/"); // channel.add_call_handler( // "on_blink_parse_html_manipulate", // [](const std::string &ctx) { // std::println("on_blink_parse_html_manipulate called with: {}", ctx); // return "Processed: " + ctx; // }); // while (1) // ; std::string arg(argc > 1 ? argv[1] : ""); if (arg == "inspect") { ipc::Channel channel; channel.connect("chromatic://process/"); std::cout << "Connected to IPC channel." << std::endl; std::thread input_thread([&channel]() { while (true) { std::string input; std::getline(std::cin, input); channel.send(input); } }); while (true) { std::string data; channel.try_receive(data); if (!data.empty()) { std::cout << "\033[47;30m[" << std::chrono::system_clock::now().time_since_epoch().count() << "] \033[47;0m"; if (*(char *)data.data() == '{') { // Assuming it's a JSON string std::string json_str((char *)data.data(), data.size()); std::println("JSON: {}", json_str); } else { // Print as hex std::cout << "Hex: "; for (size_t i = 0; i < data.size(); ++i) { if (i % 16 == 0 && i != 0) { std::cout << "\n"; } std::cout << std::hex << static_cast(((char *)data.data())[i]) << " "; } std::cout << std::dec << "\n"; } } } } else { try { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } catch (const std::exception &e) { std::cerr << "Exception during test initialization: " << e.what() << std::endl; return 1; } catch (...) { std::cerr << "Unknown exception during test initialization." << std::endl; return 1; } } } ================================================ FILE: xmake.lua ================================================ set_project("chromatic") set_policy("compatibility.version", "3.0") set_languages("c++23") set_warnings("all") add_rules("plugin.compile_commands.autoupdate", {outputdir = "build"}) add_rules("mode.releasedbg") includes("deps/blook.lua") includes("deps/breeze-js.lua") includes("deps/cpp-ipc.lua") add_requires("yalantinglibs 0c98464dd202aaa6275a8da3297719a436b8a51a", { configs = { ssl = true } }) add_requireconfs("**.cinatra", { override = true, version = "e329293f6705649a6f1e8847ec845a7631179bb8" }) add_requireconfs("**.async_simple", { override = true, version = "18f3882be354d407af0f0674121dcddaeff36e26" }) add_requires("blook", "breeze-js", "reflect-cpp", "cpptrace v0.8.3", "gtest") set_runtimes("MT") target("chromatic_ipc") set_kind("static") add_defines("NOMINMAX") add_packages("yalantinglibs", "reflect-cpp", { public = true, }) add_files("ipc/ipc.cc") add_headerfiles("ipc/ipc.h") add_includedirs("ipc", {public = true}) set_encodings("utf-8") target("chromatic") set_kind("shared") add_defines("NOMINMAX") add_packages("blook", "breeze-js", "reflect-cpp", "yalantinglibs", "cpptrace") add_syslinks("oleacc", "ole32", "oleaut32", "uuid", "comctl32", "comdlg32", "gdi32", "user32", "shell32", "kernel32", "advapi32", "psapi") add_files("src/**/*.cc", "src/*.cc") remove_files("src/ipc.cc") add_deps("chromatic_ipc") set_encodings("utf-8") target("chromatic_ipc_test") set_kind("binary") add_deps("chromatic_ipc") add_packages("gtest", "yalantinglibs") add_files("test/ipc_test.cc") add_includedirs("src") set_encodings("utf-8")