Skip to content

Commit

Permalink
Merge branch 'main' of github.com:SteamClientHomebrew/Millennium
Browse files Browse the repository at this point in the history
  • Loading branch information
shdwmtr committed Nov 27, 2024
2 parents cbfd653 + 2eb7a94 commit b299c8c
Show file tree
Hide file tree
Showing 20 changed files with 358 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/artifact.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ jobs:
mkdir D:/a/Millennium/Millennium/build/artifacts
cmake --build build --config Release
cp D:/a/Millennium/Millennium/Python-3.11.8/PCbuild/win32/python311.dll D:/a/env/python311.dll
cp /d/a/Millennium/Millennium/build/user32.dll D:/a/env/user32.dll
cp /d/a/Millennium/Millennium/build/win32/user32.dll D:/a/env/user32.dll
cp /d/a/Millennium/Millennium/build/millennium.dll D:/a/env/millennium.dll
mkdir D:/a/env/ext/bin
# Disable Millennium CLI for now, as it keeps get false positive detections for no apparent reason
# cp /d/a/Millennium/Millennium/build/cli/millennium.exe D:/a/env/ext/bin/millennium.exe
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ jobs:
mkdir D:/a/Millennium/Millennium/build/artifacts
cmake --build build --config Release
cp D:/a/Millennium/Millennium/Python-3.11.8/PCbuild/win32/python311.dll D:/a/env/python311.dll
cp /d/a/Millennium/Millennium/build/user32.dll D:/a/env/user32.dll
cp /d/a/Millennium/Millennium/build/win32/user32.dll D:/a/env/user32.dll
cp /d/a/Millennium/Millennium/build/millennium.dll D:/a/env/millennium.dll
mkdir D:/a/env/ext/bin
# Disable Millennium CLI for now, as it keeps get false positive detections for no apparent reason
# cp /d/a/Millennium/Millennium/build/cli/millennium.exe D:/a/env/ext/bin/millennium.exe
Expand Down
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ if (NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")
endif()

if (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -s")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)
endif()

project(Millennium LANGUAGES CXX)

if (WIN32 AND NOT GITHUB_ACTION_BUILD)
Expand Down Expand Up @@ -64,6 +69,10 @@ add_compile_definitions(
_CRT_SECURE_NO_WARNINGS
)

if (WIN32)
add_subdirectory(win32)
endif()

add_subdirectory(cli)

find_package(CURL REQUIRED) # used for web requests.
Expand Down Expand Up @@ -120,7 +129,7 @@ if (NOT APPLE)
endif()

if (WIN32)
set_target_properties(Millennium PROPERTIES OUTPUT_NAME "user32")
set_target_properties(Millennium PROPERTIES OUTPUT_NAME "millennium")
set_target_properties(Millennium PROPERTIES PREFIX "")
set_target_properties(Millennium PROPERTIES NO_EXPORT TRUE)
endif()
Expand Down
2 changes: 1 addition & 1 deletion assets/core/util/theme_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def __init__(self, host="localhost", port=9123):
self.thread = None
self.stop_event = asyncio.Event()

async def handler(self, websocket, path):
async def handler(self, websocket):

logger.log("Client connected")
try:
Expand Down
7 changes: 4 additions & 3 deletions assets/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ export default async function PluginMain() {
const startTime = performance.now();

pluginSelf.WatchDog = WatchDog // Expose WatchDog to the global scope
Settings.FetchAllSettings().then((result: SettingsProps) => InitializePatcher(startTime, result))
Settings.FetchAllSettings().then((result: SettingsProps) => {
InitializePatcher(startTime, result)
Millennium.AddWindowCreateHook(windowCreated)
})

// @todo: fix notificaitons modal
// Millennium.callServerMethod("updater.get_update_list")
Expand All @@ -151,6 +154,4 @@ export default async function PluginMain() {
// console.error("Failed to fetch updates")
// pluginSelf.connectionFailed = true
// })

Millennium.AddWindowCreateHook(windowCreated)
}
2 changes: 1 addition & 1 deletion assets/src/tabs/Themes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ const ThemeViewModal: React.FC = () => {

<Field
label={locale.themePanelInjectJavascript}
description={locale.themePanelInjectCSSToolTip}
description={locale.themePanelInjectJavascriptToolTip}
>
{jsState !== undefined && (
<Toggle value={jsState} onChange={onScriptToggle} />
Expand Down
16 changes: 16 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# [2.15.0](https://github.com/shdwmtr/millennium/compare/v2.14.2...v2.15.0) (2024-11-12)


### Features

* Binary stripping on release modules to drastically reduce binary size ([45734f2](https://github.com/shdwmtr/millennium/commit/45734f2be936a2ed58072f96d52c5d68945d6e71))

## [2.14.2](https://github.com/shdwmtr/millennium/compare/v2.14.1...v2.14.2) (2024-11-12)


### Bug Fixes

* Fix the CI ([7c29d23](https://github.com/shdwmtr/millennium/commit/7c29d23fecaf2f3f6b760bf95f5d368db87e2a3f))
* Fix theme installer not working ([559ae66](https://github.com/shdwmtr/millennium/commit/559ae6617d79f2772bace63d3df1b4f049b764c2))
* Hopefully help prevent false positives ([d448ab1](https://github.com/shdwmtr/millennium/commit/d448ab12ed0b8bda10a17ced92123b91a2ef06ad))

## [2.14.1](https://github.com/shdwmtr/millennium/compare/v2.14.0...v2.14.1) (2024-11-09)


Expand Down
7 changes: 7 additions & 0 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,13 @@ while ($pipeServer.IsConnected) {
if ($bytesRead -gt 0) {
$message = [System.Text.Encoding]::UTF8.GetString($buffer, 0, $bytesRead)
[Console]::Write($message)

# Check if the message contains "SteamUI successfully loaded"
if ($message -match "SteamUI successfully loaded!") {
Write-Host "`n${BoldGreen}++${ResetColor} Millennium has successfully loaded. Installation complete!"
Start-Sleep -Seconds 2
exit
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion scripts/uninstall.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ function PrettyPrintSizeOnDisk {
}

$assets = @(
"Millennium", @("user32.dll", "python311.dll")
"Millennium", @("user32.dll", "python311.dll", "millennium.dll")
"Core Modules", "ext/data/assets"
"Python Cache", "ext/data/cache"
"User Plugins", "plugins"
Expand Down
4 changes: 2 additions & 2 deletions scripts/version.rc
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

#define VER_COMPANYNAME_STR "Steam Homebrew\0"
#define VER_FILEDESCRIPTION_STR "A plugin loader for the modern Steam Client\0"
#define VER_INTERNALNAME_STR "user32.dll\0"
#define VER_ORIGINALFILENAME_STR "user32.dll\0"
#define VER_INTERNALNAME_STR "millennium.dll\0"
#define VER_ORIGINALFILENAME_STR "millennium.dll\0"
#define VER_LEGALCOPYRIGHT_STR "Steam Homebrew 2024\0"
#define VER_LEGALTRADEMARKS1_STR "All Rights Reserved\0"
#define VER_LEGALTRADEMARKS2_STR "\0"
Expand Down
191 changes: 187 additions & 4 deletions src/core/hooks/web_load.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <core/loader.h>
#include <core/ffi/ffi.h>
#include <sys/encoding.h>
#include <sys/http.h>
#include <unordered_set>
// #include <boxer/boxer.h>

WebkitHandler WebkitHandler::get()
Expand All @@ -19,6 +21,8 @@ void WebkitHandler::SetupGlobalHooks()
{ "patterns", {
{ { "urlPattern", "https://*.*.com/public/shared/css/buttons.css*" }, { "resourceType", "Stylesheet" }, { "requestStage", "Response" } },
{ { "urlPattern", "https://*.*.com/public/shared/javascript/shared_global.js*" }, { "resourceType", "Script" }, { "requestStage", "Response" } },
{ { "urlPattern", "*" }, { "resourceType", "Document" }, { "requestStage", "Request" } },


{ { "urlPattern", fmt::format("{}*", this->m_javaScriptVirtualUrl) }, { "requestStage", "Request" } }
}
Expand All @@ -34,6 +38,127 @@ bool WebkitHandler::IsGetBodyCall(nlohmann::basic_json<> message)

std::string WebkitHandler::HandleJsHook(std::string body)
{
const static std::string API = R"(
if (typeof window.MILLENNIUM_BACKEND_IPC === 'undefined')
{
const IPCMain = {
postMessage: function(messageId, contents) {
return new Promise(function(resolve) {
const message = { id: messageId, iteration: window.CURRENT_IPC_CALL_COUNT++, data: contents };
const messageHandler = function(data) {
const json = JSON.parse(data.data);
// Wait to receive the correct message id from the backend
if (json.id !== message.iteration) return;
resolve(json);
window.MILLENNIUM_IPC_SOCKET.removeEventListener('message', messageHandler);
};
window.MILLENNIUM_IPC_SOCKET.addEventListener('message', messageHandler);
window.MILLENNIUM_IPC_SOCKET.send(JSON.stringify(message));
});
}
};
window.MILLENNIUM_BACKEND_IPC = IPCMain;
window.Millennium = {
callServerMethod: function(pluginName, methodName, kwargs) {
return new Promise(function(resolve, reject) {
const query = {
pluginName: pluginName,
methodName: methodName,
...(kwargs && { argumentList: kwargs })
};
// Call handled from "src\core\ipc\pipe.cpp @ L:67"
window.MILLENNIUM_BACKEND_IPC.postMessage(0, query).then(function(response) {
if (response && response.failedRequest) {
reject(`IPC call from [name: ${pluginName}, method: ${methodName}] failed on exception -> ${response.failMessage}`);
}
const responseStream = response.returnValue;
// FFI backend encodes string responses in base64 to avoid encoding issues
resolve(typeof responseStream === 'string' ? atob(responseStream) : responseStream);
});
});
},
findElement: function(privateDocument, querySelector, timeout) {
return new Promise(function(resolve, reject) {
const matchedElements = privateDocument.querySelectorAll(querySelector);
// Node is already in DOM and doesn't require watchdog
if (matchedElements.length) {
resolve(matchedElements);
return;
}
let timer = null;
const observer = new MutationObserver(function() {
const matchedElements = privateDocument.querySelectorAll(querySelector);
if (matchedElements.length) {
if (timer) clearTimeout(timer);
observer.disconnect();
resolve(matchedElements);
}
});
// Observe the document body for item changes, assuming we are waiting for target element
observer.observe(privateDocument.body, {
childList: true,
subtree: true
});
if (timeout) {
timer = setTimeout(function() {
observer.disconnect();
reject();
}, timeout);
}
});
}
};
function createWebSocket(url) {
return new Promise((resolve, reject) => {
const startTime = Date.now(); // Record the start time
try {
let socket = new WebSocket(url);
socket.addEventListener('open', () => {
const endTime = Date.now(); // Record the end time
const connectionTime = endTime - startTime; // Calculate the connection time
console.log('%c Millennium ', 'background: black; color: white',
`Successfully connected to IPC server. Connection time: ${connectionTime}ms.`);
resolve(socket);
});
socket.addEventListener('close', () => {
setTimeout(() => {
createWebSocket(url).then(resolve).catch(reject);
}, 100);
});
}
catch (error) {
console.warn('Failed to connect to IPC server:', error);
}
});
}
createWebSocket('ws://localhost:)" + std::to_string(m_ipcPort) + R"(').then((socket) => {
window.MILLENNIUM_IPC_SOCKET = socket;
window.CURRENT_IPC_CALL_COUNT = 0;
})
.catch((error) => console.error('Initial WebSocket connection failed:', error));
}
)";

std::string scriptTagInject;

for (auto& hookItem : *m_hookListPtr)
Expand All @@ -46,8 +171,8 @@ std::string WebkitHandler::HandleJsHook(std::string body)
std::filesystem::path relativePath = std::filesystem::relative(hookItem.path, SystemIO::GetSteamPath());

scriptTagInject.append(fmt::format(
"document.head.appendChild(Object.assign(document.createElement('script'), {{ src: '{}{}', type: 'module', id: 'millennium-injected' }}));\n",
this->m_javaScriptVirtualUrl, relativePath.generic_string()
"{}\ndocument.head.appendChild(Object.assign(document.createElement('script'), {{ src: '{}{}', type: 'module', id: 'millennium-injected' }}));\n",
API, this->m_javaScriptVirtualUrl, relativePath.generic_string()
));
}
return scriptTagInject + body;
Expand Down Expand Up @@ -139,7 +264,7 @@ void WebkitHandler::HandleHooks(nlohmann::basic_json<> message)
{
auto [id, request_id, type] = (*requestIterator);

if (message["id"] != id || !message["result"]["base64Encoded"])
if (type == "Document" || message["id"] != id || !message["result"]["base64Encoded"])
{
requestIterator++;
continue;
Expand Down Expand Up @@ -181,11 +306,69 @@ void WebkitHandler::HandleHooks(nlohmann::basic_json<> message)

void WebkitHandler::DispatchSocketMessage(nlohmann::basic_json<> message)
{
// if (message.value("method", "").find("Debugger.") == std::string::npos)
// {
// Logger.Log(message.dump(4));
// }

try
{
if (message["method"] == "Fetch.requestPaused")
{
switch (this->IsGetBodyCall(message))
if (message["params"]["resourceType"] == "Document")
{

std::string requestId = message["params"]["requestId"].get<std::string>();
std::string requestUrl = message["params"]["request"]["url"].get<std::string>();
nlohmann::json requestHeaders = message["params"]["request"]["headers"];

nlohmann::json responseHeaders = nlohmann::json::array({
{
{ "name", "Content-Security-Policy" },
{ "value", "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; script-src * 'unsafe-inline' 'unsafe-eval' data: blob:;" }
},
{
{ "name", "Access-Control-Allow-Origin" },
{ "value", "*" }
}
});

for (auto& [key, value] : requestHeaders.items())
{
responseHeaders.push_back({ { "name", key }, { "value", value } });
}

nlohmann::json responseHeadersJson;

const auto [originalContent, statusCode] = Http::GetWithHeaders(requestUrl.c_str(), requestHeaders, requestHeaders.value("User-Agent", ""), responseHeadersJson);

for (const auto& item : responseHeadersJson)
{
bool keyExists = std::any_of(responseHeaders.begin(), responseHeaders.end(),
[&](const nlohmann::json& existingHeader) {
return existingHeader.at("name") == item.at("name");
});

if (!keyExists) {
responseHeaders.push_back(item);
}
}

nlohmann::json message = {
{ "id", 63453 },
{ "method", "Fetch.fulfillRequest" },
{ "params", {
{ "requestId", requestId },
{ "responseCode", statusCode },
{ "responseHeaders", responseHeaders },
{ "body", Base64Encode(originalContent) }
}}
};

Sockets::PostGlobal(message);

}
else switch (this->IsGetBodyCall(message))
{
case true:
{
Expand Down
3 changes: 3 additions & 0 deletions src/core/hooks/web_load.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ class WebkitHandler
void DispatchSocketMessage(nlohmann::basic_json<> message);
void SetupGlobalHooks();

void SetIPCPort(uint16_t ipcPort) { m_ipcPort = ipcPort; }

private:
uint16_t m_ipcPort;
long long hookMessageId = -69;

// must share the same base url, or be whitelisted.
Expand Down
Loading

0 comments on commit b299c8c

Please sign in to comment.