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