From 9093993f132eacf076986e48a99c9b435b1f83a7 Mon Sep 17 00:00:00 2001 From: TotallyNotElite <1yourexperiment@protonmail.com> Date: Sat, 6 Oct 2018 14:42:38 +0200 Subject: [PATCH 1/3] IRCLib V1 --- .gitmodules | 3 + external/CMakeLists.txt | 2 +- external/chirc | 1 + include/irc.hpp | 2 +- src/hooks/CreateMove.cpp | 2 +- src/irc.cpp | 339 ++++----------------------------------- 6 files changed, 42 insertions(+), 307 deletions(-) create mode 160000 external/chirc diff --git a/.gitmodules b/.gitmodules index c4972bc0..3f60a66c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "external/IRCClient"] path = external/IRCClient url = https://github.com/nullworks/IRCClient +[submodule "external/chirc"] + path = external/chirc + url = https://gitlab.com/TotallyNotElite/chirc.git diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a49690db..d435f101 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -5,4 +5,4 @@ target_include_directories(cathook PRIVATE "${CMAKE_CURRENT_LIST_DIR}/MicroPathe target_include_directories(cathook PRIVATE "${CMAKE_CURRENT_LIST_DIR}/ucccccp") target_include_directories(cathook PRIVATE "${CMAKE_CURRENT_LIST_DIR}/TF2_NavFile_Reader") -add_subdirectory(IRCClient) +add_subdirectory(chirc) diff --git a/external/chirc b/external/chirc new file mode 160000 index 00000000..7799d931 --- /dev/null +++ b/external/chirc @@ -0,0 +1 @@ +Subproject commit 7799d931b9c555a54b61025df6cb25f602bd3f39 diff --git a/include/irc.hpp b/include/irc.hpp index 59dbc458..568960c4 100644 --- a/include/irc.hpp +++ b/include/irc.hpp @@ -3,5 +3,5 @@ namespace IRC { bool sendmsg(std::string &msg, bool loopback = false); -void auth(bool reply = false); +//void auth(bool reply = false); } // namespace IRC diff --git a/src/hooks/CreateMove.cpp b/src/hooks/CreateMove.cpp index 6a3e2082..3d14bc77 100644 --- a/src/hooks/CreateMove.cpp +++ b/src/hooks/CreateMove.cpp @@ -259,7 +259,7 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, DelayTimer.update(); hacks::tf2::NavBot::Init(); hacks::tf2::NavBot::initonce(); - IRC::auth(); + //IRC::auth(); firstcm = false; } g_Settings.bInvalid = false; diff --git a/src/irc.cpp b/src/irc.cpp index 30d04a9a..22759abd 100644 --- a/src/irc.cpp +++ b/src/irc.cpp @@ -2,328 +2,59 @@ #include #include "IRCClient.h" #include -#include "boost/random.hpp" -#include "boost/generator_iterator.hpp" #include "irc.hpp" #include "ucccccp.hpp" #include "Settings.hpp" +#include "ChIRC.hpp" +#include namespace IRC { -static std::atomic thread_shouldrun; -static std::atomic thread_running; static settings::Bool enabled("irc.enabled", "true"); static settings::Bool anon("irc.anon", "true"); static settings::Bool authenticate("irc.auth", "true"); -// To prevent possible crashes when IRC shuts down while sending msg -static std::mutex IRCConnection_mutex; -bool shouldauth; +static settings::String channel("irc.channel", "#cat_comms"); +static settings::String address("irc.address", "cathook.irc.inkcat.net"); +static settings::Int port("irc.port", "8080"); -struct IRCData +static ChIRC::ChIRC irc; +void updateData(std::string commandandcontrol) { - std::string address = "cathook.irc.inkcat.net"; - int port = 8080; - std::string channel = "#cat_comms"; - std::string username = "You"; - std::string nickname = "You"; - std::unique_ptr IRCConnection = nullptr; -}; - -static IRCData IRCData; - -void printmsg(std::string &usr, std::string &msg) -{ - if (msg.size() > 256 || usr.size() > 256) - { - logging::Info("IRC: Message too large."); - return; - } - if (g_Settings.bInvalid) - logging::Info("[IRC] %s: %s", usr.c_str(), msg.c_str()); - else - PrintChat("\x07%06X[IRC] %s\x01: %s", 0xe05938, usr.c_str(), - msg.c_str()); + std::string nick = g_ISteamFriends->GetPersonaName(); + irc.UpdateData(nick, nick, *channel, commandandcontrol, *address, *port); } -void printmsgcopy(std::string usr, std::string msg) +static HookedFunction paint(HookedFunctions_types::HF_Paint, "IRC", 16, + []() { irc.Update(); }); + +template +void rvarCallback(settings::VariableBase &var, T after) { - if (msg.size() > 256 || usr.size() > 256) - { - logging::Info("IRC: Message too large."); - return; - } - if (g_Settings.bInvalid) - logging::Info("[IRC] %s: %s", usr.c_str(), msg.c_str()); - else - PrintChat("\x07%06X[IRC] %s\x01: %s", 0xe05938, usr.c_str(), - msg.c_str()); + irc.Disconnect(); + updateData(""); + if (enabled) + irc.Connect(); } -void handleMessage(IRCMessage message, IRCClient *client) +void rvarCallback_enabled(settings::VariableBase &var, bool after) { - std::string &cmd = message.command; - std::string &channel = message.parameters.at(0); - std::string &rawmsg = message.parameters.at(1); - std::string &usr = message.prefix.nick; - - if (!ucccccp::validate(rawmsg)) - return; - std::string msg(ucccccp::decrypt(rawmsg)); - if (msg == "Attempt at ucccccping and failing" || - msg == "Unsupported version") - return; - - // Handle privmsg (Message to #channel) - if (cmd == "PRIVMSG") - { - if (msg.empty() || usr.empty()) - return; - // Handle messages - if (msg.find("msg") == 0) - { - std::string toprint = msg.substr(3); - if (toprint.empty()) - return; - printmsg(usr, toprint); - return; - } - - // Handle auth requests - if (msg.find("auth") == 0) - { - // Check if we are in a game - if (g_Settings.bInvalid) - return; - bool isreply = false; - std::string steamidhash; - if (msg.find("authrep") == 0) - isreply = true; - // Get steamid hash from string - if (isreply) - steamidhash = msg.substr(7); - else - steamidhash = msg.substr(4); - - for (int i = 0; i < g_IEngine->GetMaxClients(); i++) - { - if (i == g_pLocalPlayer->entity_idx) - continue; - player_info_s pinfo; - // Get playerinfo and check if player on server - if (!g_IEngine->GetPlayerInfo(i, &pinfo)) - continue; - auto tarsteamid = pinfo.friendsID; - MD5Value_t result; - // Hash steamid - MD5_ProcessSingleBuffer(&tarsteamid, sizeof(tarsteamid), - result); - // Get bits of hash and store in string - std::string tarhash; - for (auto i : result.bits) - { - for (int j = 0; j < 8; j++) - tarhash.append(std::to_string((i >> j) & 1)); - } - // Check if steamid of sender == steamid we currently check - // (using hashes) - if (tarhash == steamidhash) - { - // Use actual steamid to set cat status - auto &playerlistdata = playerlist::AccessData(tarsteamid); - if (playerlistdata.state == playerlist::k_EState::DEFAULT) - { - playerlistdata.state = playerlist::k_EState::CAT; - } - // Avoid replying to a reply - if (isreply) - // We are done here. Steamid duplicates don't exist. - return; - // If message is not a reply, reply. - auth(true); - // We are done here. Steamid duplicates don't exist. - return; - } - } - } - } -} - -int randomint() -{ - boost::mt19937 rng{ static_cast(std::time(nullptr)) }; - boost::uniform_int<> oneto10000(1, 10000); - boost::variate_generator > dice( - rng, oneto10000); - return dice(); -} - -void IRCThread() -{ - if (!*anon) - { - IRCData.username = g_ISteamFriends->GetPersonaName(); - IRCData.nickname = g_ISteamFriends->GetPersonaName(); - IRCData.nickname.append(format("-", randomint())); - } - else - { - IRCData.username = "Anon"; - IRCData.nickname = "Anon"; - IRCData.nickname.append(format("-", randomint())); - } - std::replace(IRCData.nickname.begin(), IRCData.nickname.end(), ' ', '_'); - std::replace(IRCData.username.begin(), IRCData.username.end(), ' ', '_'); - - IRCConnection_mutex.lock(); - IRCData.IRCConnection = std::make_unique(); - IRCClient &client = *IRCData.IRCConnection; - client.HookIRCCommand("PRIVMSG", handleMessage); - - logging::Info("IRC: Initializing IRC"); - if (client.InitSocket()) - { - logging::Info("IRC: Socket Initialized. Connecting to cathook IRC..."); - if (client.Connect(IRCData.address.c_str(), IRCData.port)) - { - logging::Info("IRC: Connected. Logging in..."); - if (client.Login(IRCData.nickname, IRCData.username)) - { - logging::Info("IRC: Logged in. Joining channel."); - std::thread joinChannel([=]() { - std::this_thread::sleep_for( - std::chrono_literals::operator""s(3)); - if (IRCData.IRCConnection && - IRCData.IRCConnection->Connected()) - IRCData.IRCConnection->SendIRC( - format("JOIN ", IRCData.channel)); - logging::Info("IRC: Init complete."); - }); - joinChannel.detach(); - IRCConnection_mutex.unlock(); - while (client.Connected() && thread_shouldrun.load()) - client.ReceiveData(); - } - else - IRCConnection_mutex.unlock(); - } - else - IRCConnection_mutex.unlock(); - } - else - IRCConnection_mutex.unlock(); - logging::Info("Exiting IRC thread..."); - // Lock mutex twice twice tf - IRCConnection_mutex.lock(); - IRCData.IRCConnection->SendIRC("QUIT"); - IRCData.IRCConnection->Disconnect(); - IRCData.IRCConnection.reset(); - thread_running.store(false); - IRCConnection_mutex.unlock(); -} - -static Timer IRCRetry{}; - -static HookedFunction pt(HF_Paint, "IRC", 16, []() { - if (!*enabled) - { - thread_shouldrun.store(false); - return; - } - if (!thread_running.load() && IRCRetry.test_and_set(10000)) - { - thread_shouldrun.store(true); - thread_running.store(true); - std::thread ircthread(IRCThread); - ircthread.detach(); - logging::Info("IRC: Running."); - } -}); - -bool sendraw(std::string &msg) -{ - std::lock_guard lock(IRCConnection_mutex); - if (IRCData.IRCConnection && IRCData.IRCConnection->Connected()) - { - if (IRCData.IRCConnection->SendIRC(format( - "PRIVMSG ", IRCData.channel, " :", ucccccp::encrypt(msg, 'B')))) - return true; - } - return false; -} - -bool sendmsg(std::string &msg, bool loopback) -{ - std::string raw = "msg" + msg; - if (sendraw(raw)) - { - if (loopback) - { - printmsg(IRCData.nickname, msg); - } - return true; - } - if (loopback) - printmsgcopy("Cathook", "Error! Couldn't send message."); - return false; -} - -void auth(bool reply) -{ - if (g_Settings.bInvalid && !g_Settings.is_create_move) - return; - if (!*authenticate) - return; - MD5Value_t result; - MD5_ProcessSingleBuffer(&LOCAL_E->player_info.friendsID, sizeof(uint32), - result); - std::string msg = "auth"; - if (reply) - msg.append("rep"); - for (auto i : result.bits) - { - for (int j = 0; j < 8; j++) - msg.append(std::to_string((i >> j) & 1)); - } - sendraw(msg); -} - -static CatCommand irc_send_cmd("irc_send_cmd", "Send cmd to IRC", - [](const CCommand &args) { - IRCData.IRCConnection->SendIRC(args.ArgS()); - }); - -static CatCommand irc_send("irc_send", "Send message to IRC", - [](const CCommand &args) { - std::string msg(args.ArgS()); - sendmsg(msg, true); - }); - -static CatCommand irc_auth("irc_auth", - "Auth via IRC (Find users on same server)", - [](const CCommand &args) { auth(); }); -static CatCommand irc_disconnect( - "irc_disconnect", - "Disconnect from IRC (Warning you might automatically reconnect)", - [](const CCommand &args) { - std::lock_guard lock(IRCConnection_mutex); - if (IRCData.IRCConnection) - { - // IRCData.IRCConnection->SendIRC("QUIT"); - IRCData.IRCConnection->Disconnect(); - } - }); - -void rvarCallback(settings::VariableBase &var, bool after) -{ - std::lock_guard lock(IRCConnection_mutex); - if (IRCData.IRCConnection) - { - IRCData.IRCConnection->SendIRC("QUIT"); - IRCData.IRCConnection->Disconnect(); - } + irc.Disconnect(); + if (after) + irc.Connect(); } static InitRoutine init([]() { - enabled.installChangeCallback(rvarCallback); - anon.installChangeCallback(rvarCallback); + updateData(""); + enabled.installChangeCallback(rvarCallback_enabled); + anon.installChangeCallback(rvarCallback); + authenticate.installChangeCallback(rvarCallback); + channel.installChangeCallback(rvarCallback); + address.installChangeCallback(rvarCallback); + port.installChangeCallback(rvarCallback); + + irc.Connect(); }); + +static CatCommand send("irc_send", "Send message to IRC", + [](const CCommand &args) { irc.privmsg(args.ArgS()); }); + } // namespace IRC From 5ccd8dffdb9f9ee6801f01e527efd392769cc91d Mon Sep 17 00:00:00 2001 From: TotallyNotElite <1yourexperiment@protonmail.com> Date: Sat, 6 Oct 2018 21:00:11 +0200 Subject: [PATCH 2/3] IRCLib --- .gitmodules | 5 +- external/IRCClient | 1 - external/chirc | 2 +- include/irc.hpp | 2 +- src/hooks/CreateMove.cpp | 2 +- src/irc.cpp | 206 ++++++++++++++++-- .../menu/menu/special/VariableListEntry.cpp | 2 +- 7 files changed, 195 insertions(+), 25 deletions(-) delete mode 160000 external/IRCClient diff --git a/.gitmodules b/.gitmodules index 3f60a66c..239f0730 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,9 +22,6 @@ [submodule "external/TF2_NavFile_Reader"] path = external/TF2_NavFile_Reader url = https://github.com/nullworks/TF2_NavFile_Reader -[submodule "external/IRCClient"] - path = external/IRCClient - url = https://github.com/nullworks/IRCClient [submodule "external/chirc"] path = external/chirc - url = https://gitlab.com/TotallyNotElite/chirc.git + url = https://gitlab.com/TotallyNotElite/chirc.git \ No newline at end of file diff --git a/external/IRCClient b/external/IRCClient deleted file mode 160000 index 8c07a4a1..00000000 --- a/external/IRCClient +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8c07a4a1d9d1ed104ec24b921f0734a59d5a1d0e diff --git a/external/chirc b/external/chirc index 7799d931..a25ff4c3 160000 --- a/external/chirc +++ b/external/chirc @@ -1 +1 @@ -Subproject commit 7799d931b9c555a54b61025df6cb25f602bd3f39 +Subproject commit a25ff4c3d00135b6bca709acc8421f39b1116df0 diff --git a/include/irc.hpp b/include/irc.hpp index 568960c4..59dbc458 100644 --- a/include/irc.hpp +++ b/include/irc.hpp @@ -3,5 +3,5 @@ namespace IRC { bool sendmsg(std::string &msg, bool loopback = false); -//void auth(bool reply = false); +void auth(bool reply = false); } // namespace IRC diff --git a/src/hooks/CreateMove.cpp b/src/hooks/CreateMove.cpp index 3d14bc77..6a3e2082 100644 --- a/src/hooks/CreateMove.cpp +++ b/src/hooks/CreateMove.cpp @@ -259,7 +259,7 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, DelayTimer.update(); hacks::tf2::NavBot::Init(); hacks::tf2::NavBot::initonce(); - //IRC::auth(); + IRC::auth(); firstcm = false; } g_Settings.bInvalid = false; diff --git a/src/irc.cpp b/src/irc.cpp index 22759abd..38607c56 100644 --- a/src/irc.cpp +++ b/src/irc.cpp @@ -18,43 +18,217 @@ static settings::String address("irc.address", "cathook.irc.inkcat.net"); static settings::Int port("irc.port", "8080"); static ChIRC::ChIRC irc; + +void printmsg(std::string &usr, std::string &msg) +{ + if (msg.size() > 256 || usr.size() > 256) + { + logging::Info("IRC: Message too large."); + return; + } + if (g_Settings.bInvalid) + logging::Info("[IRC] %s: %s", usr.c_str(), msg.c_str()); + else + PrintChat("\x07%06X[IRC] %s\x01: %s", 0xe05938, usr.c_str(), + msg.c_str()); +} +void printmsgcopy(std::string usr, std::string msg) +{ + if (msg.size() > 256 || usr.size() > 256) + { + logging::Info("IRC: Message too large."); + return; + } + if (g_Settings.bInvalid) + logging::Info("[IRC] %s: %s", usr.c_str(), msg.c_str()); + else + PrintChat("\x07%06X[IRC] %s\x01: %s", 0xe05938, usr.c_str(), + msg.c_str()); +} + +void handleMessage(IRCMessage message, IRCClient *client) +{ + std::string &cmd = message.command; + std::string &channel = message.parameters.at(0); + std::string &rawmsg = message.parameters.at(1); + std::string &usr = message.prefix.nick; + if (!ucccccp::validate(rawmsg)) + return; + std::string msg(ucccccp::decrypt(rawmsg)); + if (msg == "Attempt at ucccccping and failing" || + msg == "Unsupported version") + return; + + // Handle privmsg (Message to #channel) + if (cmd == "PRIVMSG") + { + if (msg.empty() || usr.empty()) + return; + // Handle messages + if (msg.find("msg") == 0) + { + std::string toprint = msg.substr(3); + if (toprint.empty()) + return; + printmsg(usr, toprint); + return; + } + + // Handle auth requests + if (msg.find("auth") == 0) + { + // Check if we are in a game + if (g_Settings.bInvalid) + return; + bool isreply = false; + std::string steamidhash; + if (msg.find("authrep") == 0) + isreply = true; + // Get steamid hash from string + if (isreply) + steamidhash = msg.substr(7); + else + steamidhash = msg.substr(4); + + for (int i = 0; i < g_IEngine->GetMaxClients(); i++) + { + if (i == g_pLocalPlayer->entity_idx) + continue; + player_info_s pinfo; + // Get playerinfo and check if player on server + if (!g_IEngine->GetPlayerInfo(i, &pinfo)) + continue; + auto tarsteamid = pinfo.friendsID; + MD5Value_t result; + // Hash steamid + MD5_ProcessSingleBuffer(&tarsteamid, sizeof(tarsteamid), + result); + // Get bits of hash and store in string + std::string tarhash; + for (auto i : result.bits) + { + for (int j = 0; j < 8; j++) + tarhash.append(std::to_string((i >> j) & 1)); + } + // Check if steamid of sender == steamid we currently check + // (using hashes) + if (tarhash == steamidhash) + { + // Use actual steamid to set cat status + auto &playerlistdata = playerlist::AccessData(tarsteamid); + if (playerlistdata.state == playerlist::k_EState::DEFAULT) + { + playerlistdata.state = playerlist::k_EState::CAT; + } + // Avoid replying to a reply + if (isreply) + // We are done here. Steamid duplicates don't exist. + return; + // If message is not a reply, reply. + auth(true); + // We are done here. Steamid duplicates don't exist. + return; + } + } + } + } +} + void updateData(std::string commandandcontrol) { - std::string nick = g_ISteamFriends->GetPersonaName(); + std::string nick("Anon"); + if (!*anon) + nick = g_ISteamFriends->GetPersonaName(); irc.UpdateData(nick, nick, *channel, commandandcontrol, *address, *port); } -static HookedFunction paint(HookedFunctions_types::HF_Paint, "IRC", 16, - []() { irc.Update(); }); -template -void rvarCallback(settings::VariableBase &var, T after) +bool sendmsg(std::string &msg, bool loopback) { - irc.Disconnect(); - updateData(""); - if (enabled) - irc.Connect(); + std::string raw = "msg" + msg; + if (irc.privmsg(raw)) + { + if (loopback) + { + printmsgcopy(irc.getData().nick, msg); + } + return true; + } + if (loopback) + printmsgcopy("Cathook", "Error! Couldn't send message."); + return false; } -void rvarCallback_enabled(settings::VariableBase &var, bool after) +void auth(bool reply) { - irc.Disconnect(); - if (after) - irc.Connect(); + if (g_Settings.bInvalid && !g_Settings.is_create_move) + return; + if (!*authenticate) + return; + MD5Value_t result; + MD5_ProcessSingleBuffer(&LOCAL_E->player_info.friendsID, sizeof(uint32), + result); + std::string msg = "auth"; + if (reply) + msg.append("rep"); + for (auto i : result.bits) + { + for (int j = 0; j < 8; j++) + msg.append(std::to_string((i >> j) & 1)); + } + irc.privmsg(msg); +} + +static bool restarting{ false }; + +static HookedFunction paint(HookedFunctions_types::HF_Paint, "IRC", 16, []() { + if (!restarting) + irc.Update(); +}); + +template void rvarCallback(settings::VariableBase &var, T after) +{ + if (!restarting) + { + restarting = true; + std::thread reload([]() { + std::this_thread::sleep_for( + std::chrono_literals::operator""ms(500)); + irc.Disconnect(); + updateData(""); + if (enabled) + irc.Connect(); + restarting = false; + }); + reload.detach(); + } } static InitRoutine init([]() { updateData(""); - enabled.installChangeCallback(rvarCallback_enabled); + enabled.installChangeCallback(rvarCallback); anon.installChangeCallback(rvarCallback); authenticate.installChangeCallback(rvarCallback); channel.installChangeCallback(rvarCallback); address.installChangeCallback(rvarCallback); port.installChangeCallback(rvarCallback); + irc.installCallback("PRIVMSG", handleMessage); irc.Connect(); }); -static CatCommand send("irc_send", "Send message to IRC", - [](const CCommand &args) { irc.privmsg(args.ArgS()); }); +static CatCommand irc_send_cmd("irc_send_cmd", "Send cmd to IRC", + [](const CCommand &args) { + irc.sendraw(args.ArgS()); + }); + +static CatCommand irc_send("irc_send", "Send message to IRC", + [](const CCommand &args) { + std::string msg(args.ArgS()); + sendmsg(msg, true); + }); + +static CatCommand irc_auth("irc_auth", + "Auth via IRC (Find users on same server)", + []() { auth(); }); } // namespace IRC diff --git a/src/visual/menu/menu/special/VariableListEntry.cpp b/src/visual/menu/menu/special/VariableListEntry.cpp index e258fde8..c378bd2d 100755 --- a/src/visual/menu/menu/special/VariableListEntry.cpp +++ b/src/visual/menu/menu/special/VariableListEntry.cpp @@ -182,7 +182,7 @@ void zerokernel::VariableListEntry::onMove() void zerokernel::VariableListEntry::recalculateSize() { - printf("VLE::recalculateSize()\n"); + //printf("VLE::recalculateSize()\n"); bb.updateFillSize(); bb.shrinkContent(); bb.extend(label.getBoundingBox()); From 9c2a43004e3dc5d81167030a2c874785be8aee3a Mon Sep 17 00:00:00 2001 From: TotallyNotElite <1yourexperiment@protonmail.com> Date: Sat, 6 Oct 2018 21:36:18 +0200 Subject: [PATCH 3/3] make nullworks --- .gitmodules | 2 +- external/chirc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 239f0730..9e2d2b18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,4 +24,4 @@ url = https://github.com/nullworks/TF2_NavFile_Reader [submodule "external/chirc"] path = external/chirc - url = https://gitlab.com/TotallyNotElite/chirc.git \ No newline at end of file + url = https://github.com/nullworks/ChIRC diff --git a/external/chirc b/external/chirc index a25ff4c3..6f330a57 160000 --- a/external/chirc +++ b/external/chirc @@ -1 +1 @@ -Subproject commit a25ff4c3d00135b6bca709acc8421f39b1116df0 +Subproject commit 6f330a571bf5dd7e570c27f4b8852f832c434dbe