diff --git a/src/unlocker/spoofer.cpp b/src/unlocker/spoofer.cpp index 6344b73..fe4d4ac 100644 --- a/src/unlocker/spoofer.cpp +++ b/src/unlocker/spoofer.cpp @@ -23,11 +23,16 @@ Spoofer::Spoofer() _log->info("Spoofer init"); loadConfig(); + + _log->info("Starting WebSocket server"); + initServer(); } Spoofer::~Spoofer() { _log->info("Stopping WebSocket server"); + stopServer(); + delete _log; } @@ -113,6 +118,127 @@ void Spoofer::saveConfig() auto errCtx = glz::write_file_json(conf, configPath, buffer); if (errCtx.ec != glz::error_code::none) _log->error("Failed to save config to {}", configPath); } + +/* + websocket +*/ +void Spoofer::initServer() +{ + if (_wsServer) PANIC("Attempted to init websocket server from invalid state"); + _wsServer = new ix::WebSocketServer(WS_PORT, WS_ADDR); + + _wsServer->setOnClientMessageCallback([this](std::shared_ptr connectionState, + ix::WebSocket& webSocket, + const std::unique_ptr& msg) { + this->wsMessageCallback(connectionState, webSocket, msg); + }); + + std::pair res = _wsServer->listen(); + if (!res.first) PANIC("Websocket server failed to start"); + + _log->verbose("WebSocket server running @ {}:{}", WS_ADDR, WS_PORT); + + _wsServer->start(); +} + +void Spoofer::stopServer() +{ + if (_wsServer) + { + _wsServer->stop(); + delete _wsServer; + _wsServer = nullptr; + } +} + +void Spoofer::wsMessageCallback(std::shared_ptr /*connectionState*/, ix::WebSocket& webSocket, + const std::unique_ptr& msg) +{ + std::lock_guard lock(_mutex); + switch (msg->type) + { + case ix::WebSocketMessageType::Open: + { + _log->verbose("Websocket connection open, URI: {}", msg->openInfo.uri); + WSMessages::Init initMsg; + initMsg.profile.camperItems = _camperItems; + initMsg.profile.camperAddons = _camperAddons; + initMsg.profile.slasherAddons = _slasherAddons; + initMsg.profile.camperOfferings = _camperOfferings; + initMsg.profile.slasherOfferings = _slasherOfferings; + initMsg.profile.globalOfferings = _globalOfferings; + initMsg.profile.camperPerks = _camperPerks; + initMsg.profile.slasherPerks = _slasherPerks; + initMsg.profile.catalogItemIds = _catalogItemIds; + initMsg.profile.dlcListGRDK = _dlcListGRDK; + initMsg.profile.dlcListEGS = _dlcListEGS; + initMsg.profile.dlcListSteam = _dlcListSteam; + initMsg.profile.unlockedCharacters = _unlockedCharacters; + initMsg.toggles.spoofItems = _spoofItems; + initMsg.toggles.spoofPerks = _spoofPerks; + initMsg.toggles.spoofCatalog = _spoofCatalog; + initMsg.toggles.spoofDLCs = _spoofDLCs; + std::string out; + auto errCtx = glz::write_json(initMsg, out); + if (errCtx && errCtx.ec != glz::error_code::none) + _log->error("Error occurred while writing init msg"); + else + webSocket.send(out); + break; + } + case ix::WebSocketMessageType::Close: + _log->verbose("Websocket connection close"); + break; + case ix::WebSocketMessageType::Message: + { + WSMessages::Request req; + auto err = glz::read_json(req, msg->str); + if (err.ec != glz::error_code::none) + { + _log->error("Failed to parse websocket message"); + break; + } + switch (req.action) + { + case WSMessages::SYNC_STATE: + { + _camperItems = std::move(req.profile.camperItems); + _camperAddons = std::move(req.profile.camperAddons); + _slasherAddons = std::move(req.profile.slasherAddons); + _camperOfferings = std::move(req.profile.camperOfferings); + _slasherOfferings = std::move(req.profile.slasherOfferings); + _globalOfferings = std::move(req.profile.globalOfferings); + _camperPerks = std::move(req.profile.camperPerks); + _slasherPerks = std::move(req.profile.slasherPerks); + _catalogItemIds = std::move(req.profile.catalogItemIds); + _dlcListGRDK = std::move(req.profile.dlcListGRDK); + _dlcListEGS = std::move(req.profile.dlcListEGS); + _dlcListSteam = std::move(req.profile.dlcListSteam); + _unlockedCharacters = std::move(req.profile.unlockedCharacters); + saveConfig(); + _log->verbose("Profile state synchronized."); + break; + } + case WSMessages::SYNC_TOGGLES: + { + _spoofItems = req.toggles.spoofItems; + _spoofPerks = req.toggles.spoofPerks; + _spoofCatalog = req.toggles.spoofCatalog; + _spoofDLCs = req.toggles.spoofDLCs; + saveConfig(); + _log->verbose("Toggles synchronized."); + break; + } + default: + _log->warning("Unknown action: {}", static_cast(req.action)); + break; + } + break; + } + default: + break; + } +} /* proxy handlers */ diff --git a/src/unlocker/spoofer.h b/src/unlocker/spoofer.h index 57cab96..0da2c11 100644 --- a/src/unlocker/spoofer.h +++ b/src/unlocker/spoofer.h @@ -9,6 +9,20 @@ #include +#define WS_ADDR "0.0.0.0" +#define WS_PORT 4444 + +class TinyMITMProxy; + +namespace ix +{ + class WebSocket; + class WebSocketServer; + struct WebSocketMessage; + + class ConnectionState; +} // namespace ix + namespace seallib { class Logger; @@ -31,6 +45,35 @@ struct SpooferConfig std::unordered_set unlockedCharacters; }; +namespace WSMessages +{ + enum Action : int + { + INIT_CONFIG = 0, + SYNC_STATE = 1, + SYNC_TOGGLES = 2 + }; + struct Toggles + { + bool spoofItems = false; + bool spoofPerks = false; + bool spoofCatalog = false; + bool spoofDLCs = false; + }; + struct Init + { + Action action = INIT_CONFIG; + SpooferConfig profile; + Toggles toggles; + }; + struct Request + { + Action action; + SpooferConfig profile{}; + Toggles toggles{}; + }; +} // namespace WSMessages + class Spoofer { public: @@ -45,6 +88,15 @@ class Spoofer */ void loadConfig(); void saveConfig(); + + /* + websocket server stuff + */ + void initServer(); + void stopServer(); + + void wsMessageCallback(std::shared_ptr connectionState, ix::WebSocket& webSocket, + const std::unique_ptr& msg); /* proxy handlers */ @@ -77,6 +129,11 @@ class Spoofer std::unordered_set _dlcListSteam; std::unordered_set _unlockedCharacters; + + /* + internal variables + */ + ix::WebSocketServer* _wsServer = nullptr; seallib::Logger* _log = nullptr; std::mutex _mutex; };