From 6c4c24e72705d48caab46a3399d4a8fef49285c9 Mon Sep 17 00:00:00 2001 From: ComputerElite <71177995+ComputerElite@users.noreply.github.com> Date: Sat, 2 Apr 2022 11:19:41 +0200 Subject: [PATCH] fix crash on song start --- mod.json | 2 +- qpm.json | 2 +- src/STmanager.cpp | 302 ++++---- src/Server/Server.cpp | 172 ++--- src/Server/ServerHTTP.cpp | 968 ++++++++++++------------ src/Server/ServerHeaders.hpp | 18 +- src/Server/ServerMulticast.cpp | 284 ++++---- src/SettingsViewController.cpp | 306 ++++---- src/main.cpp | 1256 ++++++++++++++++---------------- 9 files changed, 1655 insertions(+), 1655 deletions(-) diff --git a/mod.json b/mod.json index 1919f4f..35c43b6 100644 --- a/mod.json +++ b/mod.json @@ -3,7 +3,7 @@ "name": "Streamer Tools", "id": "Streamer-Tools", "author": "EnderdracheLP, ComputerElite, Lauriethefish, Phaze", - "version": "0.5.0", + "version": "0.5.1", "packageId": "com.beatgames.beatsaber", "packageVersion": "1.21.0", "description": "Allows applications to get information from the game like, currently played song.", diff --git a/qpm.json b/qpm.json index 8c282e4..87bedbd 100644 --- a/qpm.json +++ b/qpm.json @@ -4,7 +4,7 @@ "info": { "name": "Streamer Tools", "id": "Streamer-Tools", - "version": "0.5.0", + "version": "0.5.1", "url": null, "additionalData": { "overrideSoName": "libStreamer-Tools.so" diff --git a/src/STmanager.cpp b/src/STmanager.cpp index 0ca996a..d3cc601 100644 --- a/src/STmanager.cpp +++ b/src/STmanager.cpp @@ -1,152 +1,152 @@ -#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string -#define NO_CODEGEN_USE -#include -#include - -#include "STmanager.hpp" -#include "Config.hpp" -#include "Server/ServerHeaders.hpp" - -STManager::STManager(Logger& logger) : logger(logger) { - logger.info("Starting network thread . . ."); - networkThread = std::thread(&STManager::runServer, this); // Run the thread - networkThreadHTTP = std::thread(&STManager::runServerHTTP, this); // Run the thread - networkThreadMulticast = std::thread(&STManager::MulticastServer, this); // Run the thread -} - -// Creates the JSON to send back to the Streamer Tools application -std::string STManager::constructResponse() { - - statusLock.lock(); // Lock the mutex so that stuff doesn't get overwritten while we're reading from it - - rapidjson::Document doc; - auto& alloc = doc.GetAllocator(); - doc.SetObject(); - - doc.AddMember("location", STManager::location, alloc); - doc.AddMember("isPractice", STManager::isPractice, alloc); - doc.AddMember("paused", STManager::paused, alloc); - doc.AddMember("time", STManager::time, alloc); - doc.AddMember("endTime", STManager::endTime, alloc); - doc.AddMember("score", STManager::score, alloc); - doc.AddMember("rank", STManager::rank, alloc); - doc.AddMember("combo", STManager::combo, alloc); - doc.AddMember("energy", STManager::energy, alloc); - doc.AddMember("accuracy", STManager::accuracy, alloc); - doc.AddMember("levelName", STManager::levelName, alloc); - doc.AddMember("levelSubName", STManager::levelSubName, alloc); - doc.AddMember("levelAuthor", STManager::levelAuthor, alloc); - doc.AddMember("songAuthor", STManager::songAuthor, alloc); - doc.AddMember("id", STManager::id, alloc); - doc.AddMember("difficulty", STManager::difficulty, alloc); - doc.AddMember("bpm", STManager::bpm, alloc); - doc.AddMember("njs", STManager::njs, alloc); - doc.AddMember("players", STManager::players, alloc); - doc.AddMember("maxPlayers", STManager::maxPlayers, alloc); - doc.AddMember("mpGameId", STManager::mpGameId, alloc); - doc.AddMember("mpGameIdShown", STManager::mpGameIdShown, alloc); - doc.AddMember("goodCuts", STManager::goodCuts, alloc); - doc.AddMember("badCuts", STManager::badCuts, alloc); - doc.AddMember("missedNotes", STManager::missedNotes, alloc); - doc.AddMember("fps", STManager::fps, alloc); - doc.AddMember("configFetched", configFetched, alloc); - doc.AddMember("coverFetchable", STManager::coverFetchable, alloc); - statusLock.unlock(); - - // Convert the document into a string - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - return buffer.GetString(); -} - -std::string STManager::multicastResponse() { - rapidjson::Document doc; - auto& alloc = doc.GetAllocator(); - doc.SetObject(); - - std::string http = localIP + ":" + std::to_string(PORT_HTTP); - std::string httpv6 = "[" + localIPv6 + "]:" + std::to_string(PORT_HTTP); - std::string socket = localIP + ":" + std::to_string(PORT); - std::string socketv6 = "[" + localIPv6 + "]:" + std::to_string(PORT); - - doc.AddMember("ModID", STModInfo.id, alloc); - doc.AddMember("ModVersion", STModInfo.version, alloc); - doc.AddMember("Socket", socket, alloc); - doc.AddMember("HTTP", http, alloc); - doc.AddMember("Socketv6", socketv6, alloc); - doc.AddMember("HTTPv6", httpv6, alloc); - doc.AddMember("GameVersion", gameVersion, alloc); - - // Convert the document into a string - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - return buffer.GetString(); -} - -std::string STManager::constructConfigResponse() { - rapidjson::Document doc; - auto& alloc = doc.GetAllocator(); - doc.SetObject(); - - doc.AddMember("lastChanged", getModConfig().LastChanged.GetValue(), alloc); - doc.AddMember("decimals", getModConfig().DecimalsForNumbers.GetValue(), alloc); - doc.AddMember("dontenergy", getModConfig().DontEnergy.GetValue(), alloc); - doc.AddMember("dontmpcode", getModConfig().DontMpCode.GetValue(), alloc); - doc.AddMember("alwaysmpcode", getModConfig().AlwaysMpCode.GetValue(), alloc); - doc.AddMember("alwaysupdate", getModConfig().AlwaysUpdate.GetValue(), alloc); - - // Convert the document into a string - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - return buffer.GetString(); -} - -std::string STManager::constructPositionResponse() { - rapidjson::Document doc; - auto& alloc = doc.GetAllocator(); - doc.SetObject(); - if (Head) { - UnityEngine::Vector3 HeadVectPos = Head->get_position(); - rapidjson::Value HeadPos(rapidjson::kArrayType); - HeadPos.PushBack(HeadVectPos.x, alloc).PushBack(HeadVectPos.y, alloc).PushBack(HeadVectPos.z, alloc); - doc.AddMember("HeadPos", HeadPos, alloc); - - UnityEngine::Vector3 HeadVectRot = Head->get_eulerAngles(); - rapidjson::Value HeadRot(rapidjson::kArrayType); - HeadRot.PushBack(HeadVectRot.x, alloc).PushBack(HeadVectRot.y, alloc).PushBack(HeadVectRot.z, alloc); - doc.AddMember("HeadRot", HeadRot, alloc); - } - - if (VR_Right) { - UnityEngine::Vector3 VRC_RightVectPos = VR_Right->get_position(); - rapidjson::Value VRC_RightPos(rapidjson::kArrayType); - VRC_RightPos.PushBack(VRC_RightVectPos.x, alloc).PushBack(VRC_RightVectPos.y, alloc).PushBack(VRC_RightVectPos.z, alloc); - doc.AddMember("VR_RightPos", VRC_RightPos, alloc); - - UnityEngine::Vector3 VRC_RightVectRot = VR_Right->get_eulerAngles(); - rapidjson::Value VRC_RightRot(rapidjson::kArrayType); - VRC_RightRot.PushBack(VRC_RightVectRot.x, alloc).PushBack(VRC_RightVectRot.y, alloc).PushBack(VRC_RightVectRot.z, alloc); - doc.AddMember("VR_RightRot", VRC_RightRot, alloc); - } - - if (VR_Left) { - UnityEngine::Vector3 VRC_LeftVectPos = VR_Left->get_position(); - rapidjson::Value VRC_LeftPos(rapidjson::kArrayType); - VRC_LeftPos.PushBack(VRC_LeftVectPos.x, alloc).PushBack(VRC_LeftVectPos.y, alloc).PushBack(VRC_LeftVectPos.z, alloc); - doc.AddMember("VR_LeftPos", VRC_LeftPos, alloc); - - UnityEngine::Vector3 VRC_LeftVectRot = VR_Left->get_eulerAngles(); - rapidjson::Value VRC_LeftRot(rapidjson::kArrayType); - VRC_LeftRot.PushBack(VRC_LeftVectRot.x, alloc).PushBack(VRC_LeftVectRot.y, alloc).PushBack(VRC_LeftVectRot.z, alloc); - doc.AddMember("VR_LeftRot", VRC_LeftRot, alloc); - } - - // Convert the document into a string - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - doc.Accept(writer); - return buffer.GetString(); +#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string +#define NO_CODEGEN_USE +#include +#include + +#include "STmanager.hpp" +#include "Config.hpp" +#include "Server/ServerHeaders.hpp" + +STManager::STManager(Logger& logger) : logger(logger) { + logger.info("Starting network thread . . ."); + networkThread = std::thread(&STManager::runServer, this); // Run the thread + networkThreadHTTP = std::thread(&STManager::runServerHTTP, this); // Run the thread + networkThreadMulticast = std::thread(&STManager::MulticastServer, this); // Run the thread +} + +// Creates the JSON to send back to the Streamer Tools application +std::string STManager::constructResponse() { + + statusLock.lock(); // Lock the mutex so that stuff doesn't get overwritten while we're reading from it + + rapidjson::Document doc; + auto& alloc = doc.GetAllocator(); + doc.SetObject(); + + doc.AddMember("location", STManager::location, alloc); + doc.AddMember("isPractice", STManager::isPractice, alloc); + doc.AddMember("paused", STManager::paused, alloc); + doc.AddMember("time", STManager::time, alloc); + doc.AddMember("endTime", STManager::endTime, alloc); + doc.AddMember("score", STManager::score, alloc); + doc.AddMember("rank", STManager::rank, alloc); + doc.AddMember("combo", STManager::combo, alloc); + doc.AddMember("energy", STManager::energy, alloc); + doc.AddMember("accuracy", STManager::accuracy, alloc); + doc.AddMember("levelName", STManager::levelName, alloc); + doc.AddMember("levelSubName", STManager::levelSubName, alloc); + doc.AddMember("levelAuthor", STManager::levelAuthor, alloc); + doc.AddMember("songAuthor", STManager::songAuthor, alloc); + doc.AddMember("id", STManager::id, alloc); + doc.AddMember("difficulty", STManager::difficulty, alloc); + doc.AddMember("bpm", STManager::bpm, alloc); + doc.AddMember("njs", STManager::njs, alloc); + doc.AddMember("players", STManager::players, alloc); + doc.AddMember("maxPlayers", STManager::maxPlayers, alloc); + doc.AddMember("mpGameId", STManager::mpGameId, alloc); + doc.AddMember("mpGameIdShown", STManager::mpGameIdShown, alloc); + doc.AddMember("goodCuts", STManager::goodCuts, alloc); + doc.AddMember("badCuts", STManager::badCuts, alloc); + doc.AddMember("missedNotes", STManager::missedNotes, alloc); + doc.AddMember("fps", STManager::fps, alloc); + doc.AddMember("configFetched", configFetched, alloc); + doc.AddMember("coverFetchable", STManager::coverFetchable, alloc); + statusLock.unlock(); + + // Convert the document into a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +std::string STManager::multicastResponse() { + rapidjson::Document doc; + auto& alloc = doc.GetAllocator(); + doc.SetObject(); + + std::string http = localIP + ":" + std::to_string(PORT_HTTP); + std::string httpv6 = "[" + localIPv6 + "]:" + std::to_string(PORT_HTTP); + std::string socket = localIP + ":" + std::to_string(PORT); + std::string socketv6 = "[" + localIPv6 + "]:" + std::to_string(PORT); + + doc.AddMember("ModID", STModInfo.id, alloc); + doc.AddMember("ModVersion", STModInfo.version, alloc); + doc.AddMember("Socket", socket, alloc); + doc.AddMember("HTTP", http, alloc); + doc.AddMember("Socketv6", socketv6, alloc); + doc.AddMember("HTTPv6", httpv6, alloc); + doc.AddMember("GameVersion", gameVersion, alloc); + + // Convert the document into a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +std::string STManager::constructConfigResponse() { + rapidjson::Document doc; + auto& alloc = doc.GetAllocator(); + doc.SetObject(); + + doc.AddMember("lastChanged", getModConfig().LastChanged.GetValue(), alloc); + doc.AddMember("decimals", getModConfig().DecimalsForNumbers.GetValue(), alloc); + doc.AddMember("dontenergy", getModConfig().DontEnergy.GetValue(), alloc); + doc.AddMember("dontmpcode", getModConfig().DontMpCode.GetValue(), alloc); + doc.AddMember("alwaysmpcode", getModConfig().AlwaysMpCode.GetValue(), alloc); + doc.AddMember("alwaysupdate", getModConfig().AlwaysUpdate.GetValue(), alloc); + + // Convert the document into a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); +} + +std::string STManager::constructPositionResponse() { + rapidjson::Document doc; + auto& alloc = doc.GetAllocator(); + doc.SetObject(); + if (Head) { + UnityEngine::Vector3 HeadVectPos = Head->get_position(); + rapidjson::Value HeadPos(rapidjson::kArrayType); + HeadPos.PushBack(HeadVectPos.x, alloc).PushBack(HeadVectPos.y, alloc).PushBack(HeadVectPos.z, alloc); + doc.AddMember("HeadPos", HeadPos, alloc); + + UnityEngine::Vector3 HeadVectRot = Head->get_eulerAngles(); + rapidjson::Value HeadRot(rapidjson::kArrayType); + HeadRot.PushBack(HeadVectRot.x, alloc).PushBack(HeadVectRot.y, alloc).PushBack(HeadVectRot.z, alloc); + doc.AddMember("HeadRot", HeadRot, alloc); + } + + if (VR_Right) { + UnityEngine::Vector3 VRC_RightVectPos = VR_Right->get_position(); + rapidjson::Value VRC_RightPos(rapidjson::kArrayType); + VRC_RightPos.PushBack(VRC_RightVectPos.x, alloc).PushBack(VRC_RightVectPos.y, alloc).PushBack(VRC_RightVectPos.z, alloc); + doc.AddMember("VR_RightPos", VRC_RightPos, alloc); + + UnityEngine::Vector3 VRC_RightVectRot = VR_Right->get_eulerAngles(); + rapidjson::Value VRC_RightRot(rapidjson::kArrayType); + VRC_RightRot.PushBack(VRC_RightVectRot.x, alloc).PushBack(VRC_RightVectRot.y, alloc).PushBack(VRC_RightVectRot.z, alloc); + doc.AddMember("VR_RightRot", VRC_RightRot, alloc); + } + + if (VR_Left) { + UnityEngine::Vector3 VRC_LeftVectPos = VR_Left->get_position(); + rapidjson::Value VRC_LeftPos(rapidjson::kArrayType); + VRC_LeftPos.PushBack(VRC_LeftVectPos.x, alloc).PushBack(VRC_LeftVectPos.y, alloc).PushBack(VRC_LeftVectPos.z, alloc); + doc.AddMember("VR_LeftPos", VRC_LeftPos, alloc); + + UnityEngine::Vector3 VRC_LeftVectRot = VR_Left->get_eulerAngles(); + rapidjson::Value VRC_LeftRot(rapidjson::kArrayType); + VRC_LeftRot.PushBack(VRC_LeftVectRot.x, alloc).PushBack(VRC_LeftVectRot.y, alloc).PushBack(VRC_LeftVectRot.z, alloc); + doc.AddMember("VR_LeftRot", VRC_LeftRot, alloc); + } + + // Convert the document into a string + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + doc.Accept(writer); + return buffer.GetString(); } \ No newline at end of file diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 4a3c0c8..09cc108 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -1,87 +1,87 @@ -#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string -#define NO_CODEGEN_USE - -#include "ServerHeaders.hpp" -#include "STmanager.hpp" - -bool STManager::runServer() { - // Make our IPv6 endpoint - sockaddr_in6 addr; - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(PORT); - addr.sin6_addr = in6addr_any; - int v6OnlyEnabled = 0; - char numeric_addr[INET6_ADDRSTRLEN]; - - int sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); // Create the socket - // Prevents the socket taking a while to close from causing a crash - int iSetOption = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&iSetOption, sizeof(iSetOption)); - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6OnlyEnabled, sizeof(v6OnlyEnabled)); // Disable v6 Only restriction to allow v4 connections - if (sock == -1) { - logger.error("Error creating socket: %s", strerror(errno)); - return false; - } - - // Attempt to bind to our port - if (bind(sock, (struct sockaddr*)&addr, sizeof(addr))) { - logger.error("Error binding to port %d: %s", PORT, strerror(errno)); - close(sock); - return false; - } - - logger.info("Listening on port %d", PORT); - while (true) { - if (listen(sock, CONNECTION_QUEUE_LENGTH) == -1) { // Return if an error occurs listening for a request - logger.error("Error listening for request"); - ConnectedSocket = false; - continue; - } - - socklen_t socklen = sizeof addr; - int client_sock = accept(sock, (struct sockaddr*)&addr, &socklen); // Accept the next request - if (client_sock == -1) { - logger.error("Error accepting request"); - ConnectedSocket = false; - continue; - } - std::string ClientIP(inet_ntop(AF_INET6, (struct sockaddr*)&addr.sin6_addr, numeric_addr, sizeof numeric_addr)); - // If ClientIP starts with ffff it's an IPv4 - if (ClientIP.starts_with("::ffff:")) { - ClientIP = ClientIP.substr(7, ClientIP.length()); - } - logger.info("Client connected with address: %s", ClientIP.c_str()); - ConnectedSocket = true; // Set this to true here so it no longer sends out Multicast while a connection has been established. - - // Pass the socket handle over and start a seperate thread for sending back the reply - std::thread RequestThread([this, client_sock]() { return STManager::sendResponse(client_sock); }); // Use threads for the response - RequestThread.detach(); // Detach the thread from this thread - } - - close(sock); - return true; -} - -void STManager::sendResponse(int client_sock) { - while (client_sock != -1) { - std::string response = constructResponse(); - LOG_DEBUG_SOCKET("Response: %s", response.c_str()); - - int convertedLength = htonl(response.length()); - if (write(client_sock, &convertedLength, 4) == -1) { // First send the length of the data - logger.error("Error sending length prefix: %s", strerror(errno)); - ConnectedSocket = false; - close(client_sock); return; - } - if (write(client_sock, response.c_str(), response.length()) == -1) { // Then send the string - logger.error("Error sending JSON: %s", strerror(errno)); - ConnectedSocket = false; - close(client_sock); return; - } - std::chrono::milliseconds timespan(50); - std::this_thread::sleep_for(timespan); - } - close(client_sock); // Close the client's socket to avoid leaking resources - ConnectedSocket = false; - return; +#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string +#define NO_CODEGEN_USE + +#include "ServerHeaders.hpp" +#include "STmanager.hpp" + +bool STManager::runServer() { + // Make our IPv6 endpoint + sockaddr_in6 addr; + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(PORT); + addr.sin6_addr = in6addr_any; + int v6OnlyEnabled = 0; + char numeric_addr[INET6_ADDRSTRLEN]; + + int sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); // Create the socket + // Prevents the socket taking a while to close from causing a crash + int iSetOption = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&iSetOption, sizeof(iSetOption)); + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6OnlyEnabled, sizeof(v6OnlyEnabled)); // Disable v6 Only restriction to allow v4 connections + if (sock == -1) { + logger.error("Error creating socket: %s", strerror(errno)); + return false; + } + + // Attempt to bind to our port + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr))) { + logger.error("Error binding to port %d: %s", PORT, strerror(errno)); + close(sock); + return false; + } + + logger.info("Listening on port %d", PORT); + while (true) { + if (listen(sock, CONNECTION_QUEUE_LENGTH) == -1) { // Return if an error occurs listening for a request + logger.error("Error listening for request"); + ConnectedSocket = false; + continue; + } + + socklen_t socklen = sizeof addr; + int client_sock = accept(sock, (struct sockaddr*)&addr, &socklen); // Accept the next request + if (client_sock == -1) { + logger.error("Error accepting request"); + ConnectedSocket = false; + continue; + } + std::string ClientIP(inet_ntop(AF_INET6, (struct sockaddr*)&addr.sin6_addr, numeric_addr, sizeof numeric_addr)); + // If ClientIP starts with ffff it's an IPv4 + if (ClientIP.starts_with("::ffff:")) { + ClientIP = ClientIP.substr(7, ClientIP.length()); + } + logger.info("Client connected with address: %s", ClientIP.c_str()); + ConnectedSocket = true; // Set this to true here so it no longer sends out Multicast while a connection has been established. + + // Pass the socket handle over and start a seperate thread for sending back the reply + std::thread RequestThread([this, client_sock]() { return STManager::sendResponse(client_sock); }); // Use threads for the response + RequestThread.detach(); // Detach the thread from this thread + } + + close(sock); + return true; +} + +void STManager::sendResponse(int client_sock) { + while (client_sock != -1) { + std::string response = constructResponse(); + LOG_DEBUG_SOCKET("Response: %s", response.c_str()); + + int convertedLength = htonl(response.length()); + if (write(client_sock, &convertedLength, 4) == -1) { // First send the length of the data + logger.error("Error sending length prefix: %s", strerror(errno)); + ConnectedSocket = false; + close(client_sock); return; + } + if (write(client_sock, response.c_str(), response.length()) == -1) { // Then send the string + logger.error("Error sending JSON: %s", strerror(errno)); + ConnectedSocket = false; + close(client_sock); return; + } + std::chrono::milliseconds timespan(50); + std::this_thread::sleep_for(timespan); + } + close(client_sock); // Close the client's socket to avoid leaking resources + ConnectedSocket = false; + return; } \ No newline at end of file diff --git a/src/Server/ServerHTTP.cpp b/src/Server/ServerHTTP.cpp index 35821ed..b4d6d70 100644 --- a/src/Server/ServerHTTP.cpp +++ b/src/Server/ServerHTTP.cpp @@ -1,484 +1,484 @@ -#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string -#define NO_CODEGEN_USE -#include "ServerHeaders.hpp" -#include "STmanager.hpp" -#include "Config.hpp" - -#include "UnityEngine/ImageConversion.hpp" -#include "UnityEngine/Texture2D.hpp" -#include "Unity/Collections/NativeArray_1.hpp" -#include "System/Convert.hpp" - -//LoggerContextObject HTTPLogger; - -std::string lastClientIP; - -bool STManager::runServerHTTP() { - // Make our IPv6 endpoint - sockaddr_in6 ServerHTTP; - ServerHTTP.sin6_family = AF_INET6; - ServerHTTP.sin6_port = htons(PORT_HTTP); - ServerHTTP.sin6_addr = in6addr_any; - int v6OnlyEnabled = 0; - char numeric_addr[INET6_ADDRSTRLEN]; - - int sockHTTPv6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); // Create the socket - // Prevents the socket taking a while to close from causing a crash - int iSetOption = 1; - setsockopt(sockHTTPv6, SOL_SOCKET, SO_REUSEADDR, (char*)&iSetOption, sizeof(iSetOption)); - setsockopt(sockHTTPv6, IPPROTO_IPV6, IPV6_V6ONLY, &v6OnlyEnabled, sizeof(v6OnlyEnabled)); // Disable v6 Only restriction to allow v4 connections - if (sockHTTPv6 == -1) { - logger.error("HTTP: Error creating socket: %s", strerror(errno)); - return false; - } - - // Attempt to bind to our port - if (bind(sockHTTPv6, (struct sockaddr*)&ServerHTTP, sizeof(ServerHTTP))) { - logger.error("HTTP: Error binding to port %d: %s", PORT_HTTP, strerror(errno)); - close(sockHTTPv6); - return false; - } - - logger.info("HTTP: Listening on port %d", PORT_HTTP); - while (true) { - if (listen(sockHTTPv6, CONNECTION_QUEUE_LENGTH) == -1) { // Return if an error occurs listening for a request - logger.error("HTTP: Error listening for request"); - continue; - } - - socklen_t socklenHTTP = sizeof ServerHTTP; - int client_sock = accept(sockHTTPv6, (struct sockaddr*)&ServerHTTP, &socklenHTTP); // Accept the next request - if (client_sock == -1) { - logger.error("HTTP: Error accepting request %s", strerror(errno)); - continue; - } - // Getting the client_ip using inet_ntop - std::string ClientIP(inet_ntop(AF_INET6, (struct sockaddr*)&ServerHTTP.sin6_addr, numeric_addr, sizeof numeric_addr)); - - // If ClientIP starts with ::ffff: it's an IPv4, format IPv6 mapping to IPv4 address mapping - if (ClientIP.starts_with("::ffff:")) { - ClientIP = ClientIP.substr(7, ClientIP.length()); - } - // For normal builds we only want to log Client Connected if the IP is different from the Client that connected on the last Request -#ifdef DEBUG_BUILD -#if HTTP_LOGGING == 1 - logger.info("HTTP: Client connected with address: %s", ClientIP.c_str()); -#endif -#else - if (lastClientIP != ClientIP) logger.info("HTTP: Client connected with address: %s", ClientIP.c_str()); - lastClientIP = ClientIP; -#endif - ConnectedHTTP = true; // Set this to true here so it no longer sends out Multicast after a connection has been established. - - // Pass the socket handle over and start a seperate thread for sending back the reply - std::thread RequestHTTPThread([this, client_sock]() { return STManager::HandleRequestHTTP(client_sock); }); // Use threads for the response - - RequestHTTPThread.detach(); // Detach the thread from this thread - } - close(sockHTTPv6); - return true; -} - -// This assumes buffer is at least x bytes long, -// and that the socket is blocking. -void STManager::ReadRequest(int socket, unsigned int x, char* buffer) -{ - std::string BufferStr(buffer); - int bytesRead = 0; - int result; - bool loaded = false; - - while (bytesRead < x) - { - result = read(socket, buffer + bytesRead, x - bytesRead); - if (result < 1) - { - logger.error("HTTP: Error receiving request: %s", strerror(errno)); - ConnectedHTTP = false; - break; - } - bytesRead += result; - LOG_DEBUG_HTTP("Received bytes: %d", bytesRead); - LOG_DEBUG_HTTP("Received Request: \n%s", buffer); - - BufferStr = buffer; - if ((BufferStr.find("Content-Length:") != std::string::npos)) { - size_t start = BufferStr.find("Content-Length: "); - //LOG_DEBUG_HTTP("index of Content-Length: " + std::to_string(start)); - size_t end = BufferStr.find("\r\n", start); - //LOG_DEBUG_HTTP("index of end: " + std::to_string(end)); - std::string Length = BufferStr.substr(start+16, end - start + 1); - //LOG_DEBUG_HTTP("Content-Length: " + Length); - //LOG_DEBUG_HTTP("Received-Length: " + std::to_string((BufferStr.substr(BufferStr.find("\r\n\r\n"))).length() - 4)); - if (std::stoi(Length) == (BufferStr.substr(BufferStr.find("\r\n\r\n"))).length() - 4) ConnectedHTTP = false; break; - } - else if (((BufferStr.find("\r\n\r\n") != std::string::npos) && (BufferStr.find("GET") != std::string::npos))) { - ConnectedHTTP = false; - break; - } - } -} -// WARNING: Unfinished function not working, can be used for file downloads -// Function for generating response, fourth parameter is optional, fourth parameter will be "multipart/mixed" if not specified -//bool STManager::MultipartResponseGen(int client_sock, std::string TypeOfMessage, std::string HTTPCode, std::string ContentType = "multipart/mixed") { -// if (ContentType.find("multipart/") == std::string::npos) { -// getLogger().error("ContentType not known"); return false; -// } -// std::string result; -// result = -// "HTTP/1.1 " + HTTPCode + "\r\n" \ -// "Content-Type: " + ContentType + "; boundary=CoolBoundaryName\r\n" \ -// "Access-Control-Allow-Origin: *\r\n" \ -// "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n\r\n" \ -// "--CoolBoundaryName"; -// -// if (write(client_sock, result.c_str(), result.length()) == -1) { // Then send the string -// getLogger().error("HTTP: Error sending Response: \n%s", strerror(errno)); -// return false; -// } -// LOG_DEBUG_HTTP("HTTP Response: \n%s", result.c_str()); -// bool ClientConnected = true; -// std::string Message; -// while (ClientConnected) { -// if (TypeOfMessage == "StandardResponse") Message = STManager::constructResponse(); -// result = -// "Content-Length: " + std::to_string(Message.length()) + "\r\n" \ -// "Content-Type: application/json\r\n\r\n" + \ -// Message + "\r\n" + \ -// "--CoolBoundaryName"; -// SendResponse: // Just incase we ever need to use goto SendResponse to skip over code -// if (write(client_sock, result.c_str(), result.length()) == -1) { // Then send the string -// getLogger().error("HTTP: Error sending Response: \n%s", strerror(errno)); -// ClientConnected = false; -// return false; -// } -// LOG_DEBUG_HTTP("HTTP Response: \n%s", result.c_str()); -// } -// return true; -//} - -// Function for generating response, second and last parameter is optional, second parameter will be "text/plain" if not specified -std::string ResponseGen(std::string HTTPCode, std::string ContentType = "text/plain", std::string Message = "", std::string AllowedMethods = "") { - std::string result; - if ((HTTPCode.find("204") != std::string::npos) || Message.empty()) { - result = - "HTTP/1.1 204 No Content\r\n"\ - "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n" \ - "Access-Control-Allow-Origin: *\r\n\r\n"; - return result; - } - if (!AllowedMethods.empty()) { - result = - "HTTP/1.1 " + HTTPCode + "\r\n" \ - "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n" \ - "Content-Length: " + std::to_string(Message.length()) + "\r\n" \ - "Content-Type: " + ContentType + "\r\n" \ - "Allow: " + AllowedMethods + "\r\n" \ - "Accept-Patch: " + ContentType + "\r\n" \ - "Access-Control-Allow-Origin: *\r\n\r\n" + \ - Message; - return result; - } - else { - result = - "HTTP/1.1 " + HTTPCode + "\r\n" \ - "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n" \ - "Content-Length: " + std::to_string(Message.length()) + "\r\n" \ - "Content-Type: " + ContentType + "\r\n" \ - "Access-Control-Allow-Origin: *\r\n\r\n" + \ - Message; - return result; - } -} - - -std::string STManager::GetCoverImage(std::string ImageFormat = "jpg", bool Base64 = true) { - std::string result; - ArrayW RawCoverbytesArray; - LOG_DEBUG_HTTP("Ptr CoverTexture is: %p", coverTexture); - if (ImageFormat == "jpg") { - RawCoverbytesArray = UnityEngine::ImageConversion::EncodeToJPG(coverTexture); - LOG_DEBUG_HTTP("Ptr RawCoverbytesArray is: %p", RawCoverbytesArray); - } - else if (ImageFormat == "png") { - RawCoverbytesArray = UnityEngine::ImageConversion::EncodeToPNG(coverTexture); - LOG_DEBUG_HTTP("Ptr RawCoverbytesArray is: %p", RawCoverbytesArray); - } - if (!RawCoverbytesArray) return result = ""; - if (Base64) { - return result = "data:image/" + ImageFormat + ";base64," + std::string(System::Convert::ToBase64String(RawCoverbytesArray)); - } - else { - std::string data(reinterpret_cast(RawCoverbytesArray.begin()), RawCoverbytesArray.Length()); - return result = data; - } - return result = ""; -} - -void STManager::SendResponseHTTP(int client_sock, std::string response) { - if (!response.empty()) { - SendResponse: // Just incase we ever need to use goto SendResponse to skip over code - if (write(client_sock, response.c_str(), response.length()) == -1) { // Then send the string - logger.error("HTTP: Error sending Response: \n%s", strerror(errno)); - ConnectedHTTP = false; - close(client_sock); return; - } - LOG_DEBUG_HTTP("HTTP Response: \n%s", response.c_str()); - } - ConnectedHTTP = false; - close(client_sock); // Close the client's socket to avoid leaking resources - return; -} - -bool STManager::HandleConfigChange(int client_sock, std::string BufferStr) { - size_t start = BufferStr.find("{"); - LOG_DEBUG_HTTP("index of {: %s", std::to_string(start).c_str()); - size_t end = BufferStr.find("}"); - LOG_DEBUG_HTTP("index of }: %s", std::to_string(end).c_str()); - std::string json = BufferStr.substr(start, end - start + 1); - LOG_DEBUG_HTTP("json: %s", json.c_str()); - rapidjson::Document document; - if (document.Parse(json).HasParseError()) { - SendResponseHTTP(client_sock, ResponseGen("400 Bad Request", "text/html", - " "\ - " "\ - " streamer-tools - 400 Bad Request "\ - " "\ - " "\ - " "\ - "
Streamer-tools - 400 Bad Request
"\ - "
The request the Server received was invalid.
"\ - "
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ - " ")); // Yes this is long but page is pretty-ish)); - return true; - } - else { - // TODO: Maybe use this in the future for Parsing - //static const char* kTypeNames[] = - //{ "Null", "False", "True", "Object", "Array", "String", "Number" }; - - //for (auto& m : document.GetObject()) { - // logger.debug("Type of member %s is %s\n", - // m.name.GetString(), kTypeNames[m.value.GetType()]); - // if (document.FindMember) - //} - if (document.FindMember("lastChanged") != document.MemberEnd() && document["lastChanged"].IsInt() && document["lastChanged"].GetInt() > getModConfig().LastChanged.GetValue()) { - bool didValueChange = false; - if (document.FindMember("decimals") != document.MemberEnd() && document["decimals"].IsInt()) { - getModConfig().DecimalsForNumbers.SetValue(document["decimals"].GetInt()); - didValueChange = true; - } - static const char* BoolModConfigValueNames[] = - { "dontenergy", "dontmpcode", "alwaysmpcode", "alwaysupdate" }; - ConfigUtils::ConfigValue* CUBoolModConfigValueNames[] = - { &getModConfig().DontEnergy, &getModConfig().DontMpCode, &getModConfig().AlwaysMpCode, &getModConfig().AlwaysUpdate }; - for (int i = 0; i < sizeof(BoolModConfigValueNames)/sizeof(char*); ++i) { - if (document.FindMember(BoolModConfigValueNames[i]) != document.MemberEnd() && document[BoolModConfigValueNames[i]].IsBool()) { - CUBoolModConfigValueNames[i]->SetValue(document[BoolModConfigValueNames[i]].GetBool()); - didValueChange = true; - } - } - if (didValueChange) { - getModConfig().LastChanged.SetValue(document["lastChanged"].GetInt()); - MakeConfigUI(true); - - SendResponseHTTP(client_sock, ResponseGen("200 OK", "application/json", constructConfigResponse(), "GET, PATCH")); - return true; - } - else return false; - } - else { - SendResponseHTTP(client_sock, ResponseGen("422 Unprocessable Entity", "text/html", - " "\ - " "\ - " streamer-tools - 422 Unprocessable Entity "\ - " "\ - " "\ - " "\ - "
Streamer-tools - 422 Unprocessable Entity
"\ - "
The request the Server received contained an entity that could not be processed.
"\ - "
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ - " ")); // Yes this is long but page is pretty-ish)); - return true; - } - return false; - } - static const char* kTypeNames[] = - { "Null", "False", "True", "Object", "Array", "String", "Number" }; - - for (auto& m : document.GetObject()) { - logger.debug("Type of member %s is %s\n", - m.name.GetString(), kTypeNames[m.value.GetType()]); - } - return false; -} - -#define ROUTE_START() if ((BufferStr.find("GET / ") != std::string::npos) || (BufferStr.find("GET /index") != std::string::npos)) -#define ROUTE(METHOD,URI) if ((BufferStr.find(METHOD " " URI " ") != std::string::npos) || (BufferStr.find(METHOD " " URI "/ ") != std::string::npos) || (BufferStr.find(METHOD " " URI "?") != std::string::npos)) -#define ROUTE_PATH(PATH) else if (BufferStr.find(PATH" ") == std::string::npos && BufferStr.find(PATH) != std::string::npos) -#define ROUTE_GET(URI) ROUTE("GET", URI) -#define ROUTE_POST(URI) ROUTE("POST", URI) -#define ROUTE_PUT(URI) ROUTE("PUT", URI) -#define ROUTE_PATCH(URI) ROUTE("PATCH", URI) - - -void STManager::HandleRequestHTTP(int client_sock) { - unsigned int length = 4096+1; - char* buffer = 0; - std::string response; - std::string messageStr; - buffer = new char[length]; - ReadRequest(client_sock, length, buffer); - std::string BufferStr(buffer); - delete[] buffer; - ROUTE_START() { - ROUTE_GET("/index.json") goto NormalResponse; - //if (BufferStr); - response = ResponseGen("200 OK", "text/html", "\n\n \n streamer-tools\n \n \n \n \n
Streamer-tools
\n
\n Enhance your Beat Saber stream with overlays and more. This mod provides all data necessary for the client on your PC to work.\n
\n Get the client by ComputerElite or Open the overlays in your browser (not every overlay may work) to start your Beat Saber streamer life\n
\n
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
\n \n"); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/data") { - NormalResponse: - messageStr = constructResponse(); - response = ResponseGen("200 OK", "application/json", messageStr); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_PATH("/cover/") { - if (CoverStatus == Running) { - std::unique_lock lk(STManager::CoverLock); - STManager::cv.wait_for(lk, std::chrono::seconds(2)); - } - std::string tempStr; - ROUTE_GET("/cover/base64") { - COVER_B64_JPG: - if (!coverTexture) goto NotFound; - else if (CoverChanged[0]) { - LOG_DEBUG_HTTP("%d", CoverChanged[0]); - tempStr = GetCoverImage(); - if (!tempStr.empty()) coverImageBase64 = tempStr; - CoverChanged[0] = false; - } - else LOG_DEBUG_HTTP("CoverImageUnchanged"); - - response = ResponseGen("200 OK", "text/plain", coverImageBase64); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/cover/base64/png") { - COVER_B64_PNG: - if (!coverTexture) goto NotFound; - else if (CoverChanged[1]) { - tempStr = GetCoverImage("png"); - if (!tempStr.empty()) coverImageBase64PNG = tempStr; - CoverChanged[1] = false; - } - else LOG_DEBUG_HTTP("CoverImageUnchanged"); - - response = ResponseGen("200 OK", "text/plain", coverImageBase64PNG); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/cover/cover.jpg") { - if (!coverTexture) goto NotFound; - else if (CoverChanged[2]) { - - tempStr = GetCoverImage("jpg", false); - if (!tempStr.empty()) coverImageJPG = tempStr; - CoverChanged[2] = false; - } - else LOG_DEBUG_HTTP("CoverImageUnchanged"); - - response = ResponseGen("200 OK", "image/jpg", /*stats*/ coverImageJPG); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/cover/cover.png") { - if (!coverTexture) goto NotFound; - else if (CoverChanged[3]) { - tempStr = GetCoverImage("png", false); - if (!tempStr.empty()) coverImagePNG = tempStr; - CoverChanged[3] = false; - } - else LOG_DEBUG_HTTP("CoverImageUnchanged"); - - response = ResponseGen("200 OK", "image/png", /*stats*/ coverImagePNG); - SendResponseHTTP(client_sock, response); - return; - } - } - ROUTE_GET("/config") { - CONFIG: - configFetched = true; - response = ResponseGen("200 OK", "application/json", constructConfigResponse(), "GET, PATCH"); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_PATCH("/config") { - PCONFIG: - if (!HandleConfigChange(client_sock, BufferStr)) SendResponseHTTP(client_sock, ResponseGen("204")); - return; - } - ROUTE_GET("/positions") { - if (STManager::Head && STManager::VR_Right && STManager::VR_Left && !(location == 0 || location >= 4 )) { - SendResponseHTTP(client_sock, ResponseGen("200 OK", "application/json", constructPositionResponse())); - return; - } - else { - SendResponseHTTP(client_sock, ResponseGen("204")); - return; - } - } - ROUTE_GET("/info") { - ModInfoResponse: - messageStr = multicastResponse(); - response = ResponseGen("200 OK", "application/json", messageStr); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/loader.html") { - response = ResponseGen("200 OK", "text/html", "\n\n \n test\n \n \n Yeah so it load Something\n \n \n"); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/snake") { - response = ResponseGen("200 OK", "text/html", "\n\n \n \n \n \n \n"); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/overlays.html") { - response = ResponseGen("200 OK", "text/html", "\n\n \n streamer-tools\n \n \n \n \n
Streamer-tools - Overlays
\n
\n Here are all currently available overlays (not every overlay may work):\n
\n
\n\n
\n
\n
" + STModInfo.id + "/" + STModInfo.version + " ("+ headsetType +") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
\n \n \n"); - SendResponseHTTP(client_sock, response); - return; - } - ROUTE_GET("/teapot") { - response = ResponseGen("418 I'm a teapot", "text/html", - " "\ - " "\ - " streamer-tools - 418 I'm a teapot "\ - " "\ - " "\ - " "\ - "
Streamer-tools - 418 I'm a teapot
"\ - "
The requested entity body is short and stout.
"\ - "
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ - " "); // Yes this is long but page is pretty-ish - SendResponseHTTP(client_sock, response); - return; - } - // 404 or invalid - NotFound: - response = ResponseGen("404 Not Found", "text/html", - " "\ - " "\ - " streamer-tools - 404 Not found "\ - " "\ - " "\ - " "\ - "
Streamer-tools - 404 Not found
"\ - "
The endpoint you were looking for could not be found.
"\ - "
" + STModInfo.id + "/" + STModInfo.version + " ("+ headsetType +") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ - " "); // Yes this is long but page is pretty-ish - SendResponseHTTP(client_sock, response); -} +#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string +#define NO_CODEGEN_USE +#include "ServerHeaders.hpp" +#include "STmanager.hpp" +#include "Config.hpp" + +#include "UnityEngine/ImageConversion.hpp" +#include "UnityEngine/Texture2D.hpp" +#include "Unity/Collections/NativeArray_1.hpp" +#include "System/Convert.hpp" + +//LoggerContextObject HTTPLogger; + +std::string lastClientIP; + +bool STManager::runServerHTTP() { + // Make our IPv6 endpoint + sockaddr_in6 ServerHTTP; + ServerHTTP.sin6_family = AF_INET6; + ServerHTTP.sin6_port = htons(PORT_HTTP); + ServerHTTP.sin6_addr = in6addr_any; + int v6OnlyEnabled = 0; + char numeric_addr[INET6_ADDRSTRLEN]; + + int sockHTTPv6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); // Create the socket + // Prevents the socket taking a while to close from causing a crash + int iSetOption = 1; + setsockopt(sockHTTPv6, SOL_SOCKET, SO_REUSEADDR, (char*)&iSetOption, sizeof(iSetOption)); + setsockopt(sockHTTPv6, IPPROTO_IPV6, IPV6_V6ONLY, &v6OnlyEnabled, sizeof(v6OnlyEnabled)); // Disable v6 Only restriction to allow v4 connections + if (sockHTTPv6 == -1) { + logger.error("HTTP: Error creating socket: %s", strerror(errno)); + return false; + } + + // Attempt to bind to our port + if (bind(sockHTTPv6, (struct sockaddr*)&ServerHTTP, sizeof(ServerHTTP))) { + logger.error("HTTP: Error binding to port %d: %s", PORT_HTTP, strerror(errno)); + close(sockHTTPv6); + return false; + } + + logger.info("HTTP: Listening on port %d", PORT_HTTP); + while (true) { + if (listen(sockHTTPv6, CONNECTION_QUEUE_LENGTH) == -1) { // Return if an error occurs listening for a request + logger.error("HTTP: Error listening for request"); + continue; + } + + socklen_t socklenHTTP = sizeof ServerHTTP; + int client_sock = accept(sockHTTPv6, (struct sockaddr*)&ServerHTTP, &socklenHTTP); // Accept the next request + if (client_sock == -1) { + logger.error("HTTP: Error accepting request %s", strerror(errno)); + continue; + } + // Getting the client_ip using inet_ntop + std::string ClientIP(inet_ntop(AF_INET6, (struct sockaddr*)&ServerHTTP.sin6_addr, numeric_addr, sizeof numeric_addr)); + + // If ClientIP starts with ::ffff: it's an IPv4, format IPv6 mapping to IPv4 address mapping + if (ClientIP.starts_with("::ffff:")) { + ClientIP = ClientIP.substr(7, ClientIP.length()); + } + // For normal builds we only want to log Client Connected if the IP is different from the Client that connected on the last Request +#ifdef DEBUG_BUILD +#if HTTP_LOGGING == 1 + logger.info("HTTP: Client connected with address: %s", ClientIP.c_str()); +#endif +#else + if (lastClientIP != ClientIP) logger.info("HTTP: Client connected with address: %s", ClientIP.c_str()); + lastClientIP = ClientIP; +#endif + ConnectedHTTP = true; // Set this to true here so it no longer sends out Multicast after a connection has been established. + + // Pass the socket handle over and start a seperate thread for sending back the reply + std::thread RequestHTTPThread([this, client_sock]() { return STManager::HandleRequestHTTP(client_sock); }); // Use threads for the response + + RequestHTTPThread.detach(); // Detach the thread from this thread + } + close(sockHTTPv6); + return true; +} + +// This assumes buffer is at least x bytes long, +// and that the socket is blocking. +void STManager::ReadRequest(int socket, unsigned int x, char* buffer) +{ + std::string BufferStr(buffer); + int bytesRead = 0; + int result; + bool loaded = false; + + while (bytesRead < x) + { + result = read(socket, buffer + bytesRead, x - bytesRead); + if (result < 1) + { + logger.error("HTTP: Error receiving request: %s", strerror(errno)); + ConnectedHTTP = false; + break; + } + bytesRead += result; + LOG_DEBUG_HTTP("Received bytes: %d", bytesRead); + LOG_DEBUG_HTTP("Received Request: \n%s", buffer); + + BufferStr = buffer; + if ((BufferStr.find("Content-Length:") != std::string::npos)) { + size_t start = BufferStr.find("Content-Length: "); + //LOG_DEBUG_HTTP("index of Content-Length: " + std::to_string(start)); + size_t end = BufferStr.find("\r\n", start); + //LOG_DEBUG_HTTP("index of end: " + std::to_string(end)); + std::string Length = BufferStr.substr(start+16, end - start + 1); + //LOG_DEBUG_HTTP("Content-Length: " + Length); + //LOG_DEBUG_HTTP("Received-Length: " + std::to_string((BufferStr.substr(BufferStr.find("\r\n\r\n"))).length() - 4)); + if (std::stoi(Length) == (BufferStr.substr(BufferStr.find("\r\n\r\n"))).length() - 4) ConnectedHTTP = false; break; + } + else if (((BufferStr.find("\r\n\r\n") != std::string::npos) && (BufferStr.find("GET") != std::string::npos))) { + ConnectedHTTP = false; + break; + } + } +} +// WARNING: Unfinished function not working, can be used for file downloads +// Function for generating response, fourth parameter is optional, fourth parameter will be "multipart/mixed" if not specified +//bool STManager::MultipartResponseGen(int client_sock, std::string TypeOfMessage, std::string HTTPCode, std::string ContentType = "multipart/mixed") { +// if (ContentType.find("multipart/") == std::string::npos) { +// getLogger().error("ContentType not known"); return false; +// } +// std::string result; +// result = +// "HTTP/1.1 " + HTTPCode + "\r\n" \ +// "Content-Type: " + ContentType + "; boundary=CoolBoundaryName\r\n" \ +// "Access-Control-Allow-Origin: *\r\n" \ +// "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n\r\n" \ +// "--CoolBoundaryName"; +// +// if (write(client_sock, result.c_str(), result.length()) == -1) { // Then send the string +// getLogger().error("HTTP: Error sending Response: \n%s", strerror(errno)); +// return false; +// } +// LOG_DEBUG_HTTP("HTTP Response: \n%s", result.c_str()); +// bool ClientConnected = true; +// std::string Message; +// while (ClientConnected) { +// if (TypeOfMessage == "StandardResponse") Message = STManager::constructResponse(); +// result = +// "Content-Length: " + std::to_string(Message.length()) + "\r\n" \ +// "Content-Type: application/json\r\n\r\n" + \ +// Message + "\r\n" + \ +// "--CoolBoundaryName"; +// SendResponse: // Just incase we ever need to use goto SendResponse to skip over code +// if (write(client_sock, result.c_str(), result.length()) == -1) { // Then send the string +// getLogger().error("HTTP: Error sending Response: \n%s", strerror(errno)); +// ClientConnected = false; +// return false; +// } +// LOG_DEBUG_HTTP("HTTP Response: \n%s", result.c_str()); +// } +// return true; +//} + +// Function for generating response, second and last parameter is optional, second parameter will be "text/plain" if not specified +std::string ResponseGen(std::string HTTPCode, std::string ContentType = "text/plain", std::string Message = "", std::string AllowedMethods = "") { + std::string result; + if ((HTTPCode.find("204") != std::string::npos) || Message.empty()) { + result = + "HTTP/1.1 204 No Content\r\n"\ + "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n" \ + "Access-Control-Allow-Origin: *\r\n\r\n"; + return result; + } + if (!AllowedMethods.empty()) { + result = + "HTTP/1.1 " + HTTPCode + "\r\n" \ + "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n" \ + "Content-Length: " + std::to_string(Message.length()) + "\r\n" \ + "Content-Type: " + ContentType + "\r\n" \ + "Allow: " + AllowedMethods + "\r\n" \ + "Accept-Patch: " + ContentType + "\r\n" \ + "Access-Control-Allow-Origin: *\r\n\r\n" + \ + Message; + return result; + } + else { + result = + "HTTP/1.1 " + HTTPCode + "\r\n" \ + "Server: " + STModInfo.id + "/" + STModInfo.version + "\r\n" \ + "Content-Length: " + std::to_string(Message.length()) + "\r\n" \ + "Content-Type: " + ContentType + "\r\n" \ + "Access-Control-Allow-Origin: *\r\n\r\n" + \ + Message; + return result; + } +} + + +std::string STManager::GetCoverImage(std::string ImageFormat = "jpg", bool Base64 = true) { + std::string result; + ArrayW RawCoverbytesArray; + LOG_DEBUG_HTTP("Ptr CoverTexture is: %p", coverTexture); + if (ImageFormat == "jpg") { + RawCoverbytesArray = UnityEngine::ImageConversion::EncodeToJPG(coverTexture); + LOG_DEBUG_HTTP("Ptr RawCoverbytesArray is: %p", RawCoverbytesArray); + } + else if (ImageFormat == "png") { + RawCoverbytesArray = UnityEngine::ImageConversion::EncodeToPNG(coverTexture); + LOG_DEBUG_HTTP("Ptr RawCoverbytesArray is: %p", RawCoverbytesArray); + } + if (!RawCoverbytesArray) return result = ""; + if (Base64) { + return result = "data:image/" + ImageFormat + ";base64," + std::string(System::Convert::ToBase64String(RawCoverbytesArray)); + } + else { + std::string data(reinterpret_cast(RawCoverbytesArray.begin()), RawCoverbytesArray.Length()); + return result = data; + } + return result = ""; +} + +void STManager::SendResponseHTTP(int client_sock, std::string response) { + if (!response.empty()) { + SendResponse: // Just incase we ever need to use goto SendResponse to skip over code + if (write(client_sock, response.c_str(), response.length()) == -1) { // Then send the string + logger.error("HTTP: Error sending Response: \n%s", strerror(errno)); + ConnectedHTTP = false; + close(client_sock); return; + } + LOG_DEBUG_HTTP("HTTP Response: \n%s", response.c_str()); + } + ConnectedHTTP = false; + close(client_sock); // Close the client's socket to avoid leaking resources + return; +} + +bool STManager::HandleConfigChange(int client_sock, std::string BufferStr) { + size_t start = BufferStr.find("{"); + LOG_DEBUG_HTTP("index of {: %s", std::to_string(start).c_str()); + size_t end = BufferStr.find("}"); + LOG_DEBUG_HTTP("index of }: %s", std::to_string(end).c_str()); + std::string json = BufferStr.substr(start, end - start + 1); + LOG_DEBUG_HTTP("json: %s", json.c_str()); + rapidjson::Document document; + if (document.Parse(json).HasParseError()) { + SendResponseHTTP(client_sock, ResponseGen("400 Bad Request", "text/html", + " "\ + " "\ + " streamer-tools - 400 Bad Request "\ + " "\ + " "\ + " "\ + "
Streamer-tools - 400 Bad Request
"\ + "
The request the Server received was invalid.
"\ + "
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ + " ")); // Yes this is long but page is pretty-ish)); + return true; + } + else { + // TODO: Maybe use this in the future for Parsing + //static const char* kTypeNames[] = + //{ "Null", "False", "True", "Object", "Array", "String", "Number" }; + + //for (auto& m : document.GetObject()) { + // logger.debug("Type of member %s is %s\n", + // m.name.GetString(), kTypeNames[m.value.GetType()]); + // if (document.FindMember) + //} + if (document.FindMember("lastChanged") != document.MemberEnd() && document["lastChanged"].IsInt() && document["lastChanged"].GetInt() > getModConfig().LastChanged.GetValue()) { + bool didValueChange = false; + if (document.FindMember("decimals") != document.MemberEnd() && document["decimals"].IsInt()) { + getModConfig().DecimalsForNumbers.SetValue(document["decimals"].GetInt()); + didValueChange = true; + } + static const char* BoolModConfigValueNames[] = + { "dontenergy", "dontmpcode", "alwaysmpcode", "alwaysupdate" }; + ConfigUtils::ConfigValue* CUBoolModConfigValueNames[] = + { &getModConfig().DontEnergy, &getModConfig().DontMpCode, &getModConfig().AlwaysMpCode, &getModConfig().AlwaysUpdate }; + for (int i = 0; i < sizeof(BoolModConfigValueNames)/sizeof(char*); ++i) { + if (document.FindMember(BoolModConfigValueNames[i]) != document.MemberEnd() && document[BoolModConfigValueNames[i]].IsBool()) { + CUBoolModConfigValueNames[i]->SetValue(document[BoolModConfigValueNames[i]].GetBool()); + didValueChange = true; + } + } + if (didValueChange) { + getModConfig().LastChanged.SetValue(document["lastChanged"].GetInt()); + MakeConfigUI(true); + + SendResponseHTTP(client_sock, ResponseGen("200 OK", "application/json", constructConfigResponse(), "GET, PATCH")); + return true; + } + else return false; + } + else { + SendResponseHTTP(client_sock, ResponseGen("422 Unprocessable Entity", "text/html", + " "\ + " "\ + " streamer-tools - 422 Unprocessable Entity "\ + " "\ + " "\ + " "\ + "
Streamer-tools - 422 Unprocessable Entity
"\ + "
The request the Server received contained an entity that could not be processed.
"\ + "
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ + " ")); // Yes this is long but page is pretty-ish)); + return true; + } + return false; + } + static const char* kTypeNames[] = + { "Null", "False", "True", "Object", "Array", "String", "Number" }; + + for (auto& m : document.GetObject()) { + logger.debug("Type of member %s is %s\n", + m.name.GetString(), kTypeNames[m.value.GetType()]); + } + return false; +} + +#define ROUTE_START() if ((BufferStr.find("GET / ") != std::string::npos) || (BufferStr.find("GET /index") != std::string::npos)) +#define ROUTE(METHOD,URI) if ((BufferStr.find(METHOD " " URI " ") != std::string::npos) || (BufferStr.find(METHOD " " URI "/ ") != std::string::npos) || (BufferStr.find(METHOD " " URI "?") != std::string::npos)) +#define ROUTE_PATH(PATH) else if (BufferStr.find(PATH" ") == std::string::npos && BufferStr.find(PATH) != std::string::npos) +#define ROUTE_GET(URI) ROUTE("GET", URI) +#define ROUTE_POST(URI) ROUTE("POST", URI) +#define ROUTE_PUT(URI) ROUTE("PUT", URI) +#define ROUTE_PATCH(URI) ROUTE("PATCH", URI) + + +void STManager::HandleRequestHTTP(int client_sock) { + unsigned int length = 4096+1; + char* buffer = 0; + std::string response; + std::string messageStr; + buffer = new char[length]; + ReadRequest(client_sock, length, buffer); + std::string BufferStr(buffer); + delete[] buffer; + ROUTE_START() { + ROUTE_GET("/index.json") goto NormalResponse; + //if (BufferStr); + response = ResponseGen("200 OK", "text/html", "\n\n \n streamer-tools\n \n \n \n \n
Streamer-tools
\n
\n Enhance your Beat Saber stream with overlays and more. This mod provides all data necessary for the client on your PC to work.\n
\n Get the client by ComputerElite or Open the overlays in your browser (not every overlay may work) to start your Beat Saber streamer life\n
\n
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
\n \n"); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/data") { + NormalResponse: + messageStr = constructResponse(); + response = ResponseGen("200 OK", "application/json", messageStr); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_PATH("/cover/") { + if (CoverStatus == Running) { + std::unique_lock lk(STManager::CoverLock); + STManager::cv.wait_for(lk, std::chrono::seconds(2)); + } + std::string tempStr; + ROUTE_GET("/cover/base64") { + COVER_B64_JPG: + if (!coverTexture) goto NotFound; + else if (CoverChanged[0]) { + LOG_DEBUG_HTTP("%d", CoverChanged[0]); + tempStr = GetCoverImage(); + if (!tempStr.empty()) coverImageBase64 = tempStr; + CoverChanged[0] = false; + } + else LOG_DEBUG_HTTP("CoverImageUnchanged"); + + response = ResponseGen("200 OK", "text/plain", coverImageBase64); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/cover/base64/png") { + COVER_B64_PNG: + if (!coverTexture) goto NotFound; + else if (CoverChanged[1]) { + tempStr = GetCoverImage("png"); + if (!tempStr.empty()) coverImageBase64PNG = tempStr; + CoverChanged[1] = false; + } + else LOG_DEBUG_HTTP("CoverImageUnchanged"); + + response = ResponseGen("200 OK", "text/plain", coverImageBase64PNG); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/cover/cover.jpg") { + if (!coverTexture) goto NotFound; + else if (CoverChanged[2]) { + + tempStr = GetCoverImage("jpg", false); + if (!tempStr.empty()) coverImageJPG = tempStr; + CoverChanged[2] = false; + } + else LOG_DEBUG_HTTP("CoverImageUnchanged"); + + response = ResponseGen("200 OK", "image/jpg", /*stats*/ coverImageJPG); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/cover/cover.png") { + if (!coverTexture) goto NotFound; + else if (CoverChanged[3]) { + tempStr = GetCoverImage("png", false); + if (!tempStr.empty()) coverImagePNG = tempStr; + CoverChanged[3] = false; + } + else LOG_DEBUG_HTTP("CoverImageUnchanged"); + + response = ResponseGen("200 OK", "image/png", /*stats*/ coverImagePNG); + SendResponseHTTP(client_sock, response); + return; + } + } + ROUTE_GET("/config") { + CONFIG: + configFetched = true; + response = ResponseGen("200 OK", "application/json", constructConfigResponse(), "GET, PATCH"); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_PATCH("/config") { + PCONFIG: + if (!HandleConfigChange(client_sock, BufferStr)) SendResponseHTTP(client_sock, ResponseGen("204")); + return; + } + ROUTE_GET("/positions") { + if (STManager::Head && STManager::VR_Right && STManager::VR_Left && !(location == 0 || location >= 4 )) { + SendResponseHTTP(client_sock, ResponseGen("200 OK", "application/json", constructPositionResponse())); + return; + } + else { + SendResponseHTTP(client_sock, ResponseGen("204")); + return; + } + } + ROUTE_GET("/info") { + ModInfoResponse: + messageStr = multicastResponse(); + response = ResponseGen("200 OK", "application/json", messageStr); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/loader.html") { + response = ResponseGen("200 OK", "text/html", "\n\n \n test\n \n \n Yeah so it load Something\n \n \n"); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/snake") { + response = ResponseGen("200 OK", "text/html", "\n\n \n \n \n \n \n"); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/overlays.html") { + response = ResponseGen("200 OK", "text/html", "\n\n \n streamer-tools\n \n \n \n \n
Streamer-tools - Overlays
\n
\n Here are all currently available overlays (not every overlay may work):\n
\n
\n\n
\n
\n
" + STModInfo.id + "/" + STModInfo.version + " ("+ headsetType +") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
\n \n \n"); + SendResponseHTTP(client_sock, response); + return; + } + ROUTE_GET("/teapot") { + response = ResponseGen("418 I'm a teapot", "text/html", + " "\ + " "\ + " streamer-tools - 418 I'm a teapot "\ + " "\ + " "\ + " "\ + "
Streamer-tools - 418 I'm a teapot
"\ + "
The requested entity body is short and stout.
"\ + "
" + STModInfo.id + "/" + STModInfo.version + " (" + headsetType + ") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ + " "); // Yes this is long but page is pretty-ish + SendResponseHTTP(client_sock, response); + return; + } + // 404 or invalid + NotFound: + response = ResponseGen("404 Not Found", "text/html", + " "\ + " "\ + " streamer-tools - 404 Not found "\ + " "\ + " "\ + " "\ + "
Streamer-tools - 404 Not found
"\ + "
The endpoint you were looking for could not be found.
"\ + "
" + STModInfo.id + "/" + STModInfo.version + " ("+ headsetType +") server at " + STManager::localIP + ":" + std::to_string(PORT_HTTP) + "
"\ + " "); // Yes this is long but page is pretty-ish + SendResponseHTTP(client_sock, response); +} diff --git a/src/Server/ServerHeaders.hpp b/src/Server/ServerHeaders.hpp index 3c6b42b..f3e76b8 100644 --- a/src/Server/ServerHeaders.hpp +++ b/src/Server/ServerHeaders.hpp @@ -1,10 +1,10 @@ -#pragma once -#include -#include -#include - -#define ADDRESS_MULTI "232.0.53.5" // Testing setting was "225.1.1.1" and "224.0.0.1" which sends to all hosts on the network -#define PORT_MULTI 53500 -#define PORT 53501 -#define PORT_HTTP 53502 +#pragma once +#include +#include +#include + +#define ADDRESS_MULTI "232.0.53.5" // Testing setting was "225.1.1.1" and "224.0.0.1" which sends to all hosts on the network +#define PORT_MULTI 53500 +#define PORT 53501 +#define PORT_HTTP 53502 #define CONNECTION_QUEUE_LENGTH 2 // How many connections to store to process \ No newline at end of file diff --git a/src/Server/ServerMulticast.cpp b/src/Server/ServerMulticast.cpp index a8af13b..807276e 100644 --- a/src/Server/ServerMulticast.cpp +++ b/src/Server/ServerMulticast.cpp @@ -1,142 +1,142 @@ -#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string -#define NO_CODEGEN_USE - -#include "ServerHeaders.hpp" -#include "STmanager.hpp" -#include -#include - -//LoggerContextObject MulticastLogger; - -bool STManager::MulticastServer() { - MulticastRunning = true; - struct in_addr localInterface; - struct sockaddr_in groupSock; - int datalen; - char databuf[1024]; - - /* - * Create a datagram socket on which to send. - */ - int sd = socket(AF_INET, SOCK_DGRAM, 0); - if (sd < 0) { - logger.error("Error opening datagram socket"); - MulticastRunning = false; - return false; - } - - /* - * Initialize the group sockaddr structure - */ - memset((char*)&groupSock, 0, sizeof(groupSock)); - groupSock.sin_family = AF_INET; - groupSock.sin_addr.s_addr = inet_addr(ADDRESS_MULTI); - groupSock.sin_port = htons(PORT_MULTI); - logger.info("Initializing Multicast on %s:%d", ADDRESS_MULTI, PORT_MULTI); - - /* - * Disable loopback so you do not receive your own datagrams. - */ - { - char loopch = 0; - - if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, - (char*)&loopch, sizeof(loopch)) < 0) { - logger.error("Error setting IP_MULTICAST_LOOP: %s", strerror(errno)); - close(sd); - MulticastRunning = false; - return false; - } - } - - /* - * Set local interface for outbound multicast datagrams. - * The IP address specified must be associated with a local, - * multicast-capable interface. - */ - localInterface.s_addr = inet_addr("0.0.0.0"); - if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, - (char*)&localInterface, - sizeof(localInterface)) < 0) { - logger.error("Error Multicast: setting local interface: %s", strerror(errno)); - MulticastRunning = false; - return false; - } - - struct ifreq ifr; - - struct ifaddrs* ifap, * ifa; - struct sockaddr_in6* sa; - char addrIPv6[INET6_ADDRSTRLEN]; - - std::string addressIPv6Check; - std::string addressIPv6; - - if (getifaddrs(&ifap) == -1) { - logger.error("getifaddrs"); - } - - for (ifa = ifap; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6) { - sa = (struct sockaddr_in6*)ifa->ifa_addr; - getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addrIPv6, - sizeof(addrIPv6), NULL, 0, NI_NUMERICHOST); - //logger.debug("Interface: %s\tAddress: %s\n", ifa->ifa_name, addrIPv6); - // This will get the first IPv6 address on the wlan0 interface that is not link-local - if (strcmp(ifa->ifa_name, "wlan0") == 0) { - addressIPv6Check = addrIPv6; - if (!addressIPv6Check.starts_with("fe80") && !addressIPv6Check.empty()) { - addressIPv6 = addressIPv6Check; - break; - } - } - } - } - - freeifaddrs(ifap); - - /* I want to get an IPv4 IP address */ - ifr.ifr_addr.sa_family = AF_INET; - - /* I want IP address attached to "wlan0" */ - strncpy(ifr.ifr_name, "wlan0", IFNAMSIZ - 1); - - ioctl(sd, SIOCGIFADDR, &ifr); - - char numeric_addr_v4[INET_ADDRSTRLEN]; - - STManager::localIP = inet_ntop(AF_INET, &((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr, numeric_addr_v4, sizeof numeric_addr_v4); - STManager::localIPv6 = addressIPv6; - - while (true) { - std::chrono::minutes MulticastSleep(1); - while (!ConnectedHTTP && !ConnectedSocket) { - std::chrono::seconds timespan(15); - - /* - * Create message to be sent - */ - - std::string message = multicastResponse(); - - /* - * Send a message to the multicast group specified by the - * groupSock sockaddr structure. - */ - - LOG_DEBUG_MULTICAST("Multicast sent: \n%s", message.c_str()); - strcpy(databuf, message.c_str()); - datalen = message.length(); - if (sendto(sd, databuf, datalen, 0, - (struct sockaddr*)&groupSock, - sizeof(groupSock)) < 0) - { - logger.error("Multicast: error sending datagram message: %s", strerror(errno)); - } - std::this_thread::sleep_for(timespan); - } - std::this_thread::sleep_for(MulticastSleep); - } - MulticastRunning = false; - return true; -} +#define RAPIDJSON_HAS_STDSTRING 1 // Enable rapidjson's support for std::string +#define NO_CODEGEN_USE + +#include "ServerHeaders.hpp" +#include "STmanager.hpp" +#include +#include + +//LoggerContextObject MulticastLogger; + +bool STManager::MulticastServer() { + MulticastRunning = true; + struct in_addr localInterface; + struct sockaddr_in groupSock; + int datalen; + char databuf[1024]; + + /* + * Create a datagram socket on which to send. + */ + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + logger.error("Error opening datagram socket"); + MulticastRunning = false; + return false; + } + + /* + * Initialize the group sockaddr structure + */ + memset((char*)&groupSock, 0, sizeof(groupSock)); + groupSock.sin_family = AF_INET; + groupSock.sin_addr.s_addr = inet_addr(ADDRESS_MULTI); + groupSock.sin_port = htons(PORT_MULTI); + logger.info("Initializing Multicast on %s:%d", ADDRESS_MULTI, PORT_MULTI); + + /* + * Disable loopback so you do not receive your own datagrams. + */ + { + char loopch = 0; + + if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, + (char*)&loopch, sizeof(loopch)) < 0) { + logger.error("Error setting IP_MULTICAST_LOOP: %s", strerror(errno)); + close(sd); + MulticastRunning = false; + return false; + } + } + + /* + * Set local interface for outbound multicast datagrams. + * The IP address specified must be associated with a local, + * multicast-capable interface. + */ + localInterface.s_addr = inet_addr("0.0.0.0"); + if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, + (char*)&localInterface, + sizeof(localInterface)) < 0) { + logger.error("Error Multicast: setting local interface: %s", strerror(errno)); + MulticastRunning = false; + return false; + } + + struct ifreq ifr; + + struct ifaddrs* ifap, * ifa; + struct sockaddr_in6* sa; + char addrIPv6[INET6_ADDRSTRLEN]; + + std::string addressIPv6Check; + std::string addressIPv6; + + if (getifaddrs(&ifap) == -1) { + logger.error("getifaddrs"); + } + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6) { + sa = (struct sockaddr_in6*)ifa->ifa_addr; + getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), addrIPv6, + sizeof(addrIPv6), NULL, 0, NI_NUMERICHOST); + //logger.debug("Interface: %s\tAddress: %s\n", ifa->ifa_name, addrIPv6); + // This will get the first IPv6 address on the wlan0 interface that is not link-local + if (strcmp(ifa->ifa_name, "wlan0") == 0) { + addressIPv6Check = addrIPv6; + if (!addressIPv6Check.starts_with("fe80") && !addressIPv6Check.empty()) { + addressIPv6 = addressIPv6Check; + break; + } + } + } + } + + freeifaddrs(ifap); + + /* I want to get an IPv4 IP address */ + ifr.ifr_addr.sa_family = AF_INET; + + /* I want IP address attached to "wlan0" */ + strncpy(ifr.ifr_name, "wlan0", IFNAMSIZ - 1); + + ioctl(sd, SIOCGIFADDR, &ifr); + + char numeric_addr_v4[INET_ADDRSTRLEN]; + + STManager::localIP = inet_ntop(AF_INET, &((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr, numeric_addr_v4, sizeof numeric_addr_v4); + STManager::localIPv6 = addressIPv6; + + while (true) { + std::chrono::minutes MulticastSleep(1); + while (!ConnectedHTTP && !ConnectedSocket) { + std::chrono::seconds timespan(15); + + /* + * Create message to be sent + */ + + std::string message = multicastResponse(); + + /* + * Send a message to the multicast group specified by the + * groupSock sockaddr structure. + */ + + LOG_DEBUG_MULTICAST("Multicast sent: \n%s", message.c_str()); + strcpy(databuf, message.c_str()); + datalen = message.length(); + if (sendto(sd, databuf, datalen, 0, + (struct sockaddr*)&groupSock, + sizeof(groupSock)) < 0) + { + logger.error("Multicast: error sending datagram message: %s", strerror(errno)); + } + std::this_thread::sleep_for(timespan); + } + std::this_thread::sleep_for(MulticastSleep); + } + MulticastRunning = false; + return true; +} diff --git a/src/SettingsViewController.cpp b/src/SettingsViewController.cpp index af519d7..f1eb62a 100644 --- a/src/SettingsViewController.cpp +++ b/src/SettingsViewController.cpp @@ -1,154 +1,154 @@ -#include "STmanager.hpp" -#include "SettingsViewController.hpp" -#include "Config.hpp" - -#include "UnityEngine/RectOffset.hpp" -#include "UnityEngine/RectTransform.hpp" -#include "UnityEngine/Vector2.hpp" -#include "UnityEngine/UI/Image.hpp" -#include "UnityEngine/UI/Toggle.hpp" -#include "UnityEngine/UI/Toggle_ToggleEvent.hpp" -#include "UnityEngine/UI/LayoutElement.hpp" -#include "UnityEngine/Events/UnityAction.hpp" -#include "UnityEngine/Events/UnityAction_1.hpp" -#include "HMUI/ScrollView.hpp" -#include "HMUI/ModalView.hpp" -#include "HMUI/Touchable.hpp" -#include "UnityEngine/Resources.hpp" -#include "GlobalNamespace/ColorPickerButtonController.hpp" -#include "GlobalNamespace/ColorChangeUIEventType.hpp" -#include "System/Action_2.hpp" - -#include "questui/shared/QuestUI.hpp" -#include "questui/shared/BeatSaberUI.hpp" - -using namespace QuestUI; -using namespace UnityEngine; -using namespace UnityEngine::UI; -using namespace UnityEngine::Events; -using namespace HMUI; - -#ifndef REGISTER_FUNCTION -DEFINE_TYPE(StreamerTools, stSettingViewController); -#elif defined(DEFINE_TYPE) -DEFINE_TYPE(StreamerTools::stSettingViewController); -#elif defined(DEFINE_CLASS) -DEFINE_CLASS(StreamerTools::stSettingViewController); -#endif - -bool configFetched = false; - -Transform* parent; - -enum class SettingsState { - NotInit, Updating, Ready -}; - -SettingsState state = SettingsState::NotInit; - -//bool SettingsInit = false; - -//bool NotUpdating = true; - -// Defined here, and defined as extern in STmanager.hpp -GameObject* Decimals; -GameObject* DEnergy; -GameObject* D_MP_Code; -GameObject* A_MP_Code; -GameObject* A_Update; - -void MakeConfigUI(bool UpdateOnly) { - // Prevents a crash from the client trying to set the config before the config ViewController has first activated - // This will update the Config Values while the config is open - if (UpdateOnly && state != SettingsState::Ready) return; - else if (UpdateOnly) { - state = SettingsState::Updating; - Decimals->GetComponent()->CurrentValue = (float)getModConfig().DecimalsForNumbers.GetValue(); - Decimals->GetComponent()->UpdateValue(); - DEnergy->GetComponent()->set_isOn(getModConfig().DontEnergy.GetValue()); - D_MP_Code->GetComponent()->set_isOn(getModConfig().DontMpCode.GetValue()); - A_MP_Code->GetComponent()->set_isOn(getModConfig().AlwaysMpCode.GetValue()); - A_Update->GetComponent()->set_isOn(getModConfig().AlwaysUpdate.GetValue()); - state = SettingsState::Ready; - } - // Creates the Config UI - else { - // Decimals for numbers - getModConfig().DecimalsForNumbers.AddChangeEvent( - [&](int value) { - getModConfig().LastChanged.SetValue(static_cast(time(0))); - configFetched = false; - } - ); - - Decimals = AddConfigValueIncrementInt(parent, getModConfig().DecimalsForNumbers, 1, 0, 20)->get_gameObject(); - BeatSaberUI::AddHoverHint(Decimals, "How many decimal places to show"); - - - //Decimals = STAddConfigValueIncrementInt(parent, getModConfig().DecimalsForNumbers, 1, 0, 20)->get_gameObject(); - //BeatSaberUI::AddHoverHint(Decimals, "How many decimal places to show"); - - // DontEnergy - getModConfig().DontEnergy.AddChangeEvent( - [&](bool value) { - getModConfig().LastChanged.SetValue(static_cast(time(0))); - configFetched = false; - } - ); - - DEnergy = AddConfigValueToggle(parent, getModConfig().DontEnergy)->get_gameObject(); - BeatSaberUI::AddHoverHint(DEnergy, "Dont show energy bar"); - - // DontMpCode - getModConfig().DontMpCode.AddChangeEvent( - [&](bool value) { - getModConfig().LastChanged.SetValue(static_cast(time(0))); - configFetched = false; - } - ); - - D_MP_Code = AddConfigValueToggle(parent, getModConfig().DontMpCode)->get_gameObject(); - BeatSaberUI::AddHoverHint(D_MP_Code, "Don't show multiplayer code"); - - // AlwaysMpCode - getModConfig().AlwaysMpCode.AddChangeEvent( - [&](bool value) { - getModConfig().LastChanged.SetValue(static_cast(time(0))); - configFetched = false; - } - ); - - A_MP_Code = AddConfigValueToggle(parent, getModConfig().AlwaysMpCode)->get_gameObject(); - BeatSaberUI::AddHoverHint(A_MP_Code, "Always show multiplayer code. default: if no shown in game as *****"); - - // AlwaysUpdate - getModConfig().AlwaysUpdate.AddChangeEvent( - [&](bool value) { - getModConfig().LastChanged.SetValue(static_cast(time(0))); - configFetched = false; - } - ); - - A_Update = AddConfigValueToggle(parent, getModConfig().AlwaysUpdate)->get_gameObject(); - BeatSaberUI::AddHoverHint(A_Update, "Update the overlay on song select an not just on song start"); - state = SettingsState::Ready; - } -} - -//bool InSettings = true; - -void StreamerTools::stSettingViewController::DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { - //InSettings = true; - if(firstActivation && state == SettingsState::NotInit) { - get_gameObject()->AddComponent(); - GameObject* container = BeatSaberUI::CreateScrollableSettingsContainer(get_transform()); - parent = container->get_transform(); - QuestUI::BeatSaberUI::CreateText(parent, "Client configuration"); - MakeConfigUI(false); - } else MakeConfigUI(true); -} - -//void StreamerTools::stSettingViewController::DidDeactivate(bool removedFromHierarchy, bool systemScreenDisabling) -//{ -// InSettings = false; +#include "STmanager.hpp" +#include "SettingsViewController.hpp" +#include "Config.hpp" + +#include "UnityEngine/RectOffset.hpp" +#include "UnityEngine/RectTransform.hpp" +#include "UnityEngine/Vector2.hpp" +#include "UnityEngine/UI/Image.hpp" +#include "UnityEngine/UI/Toggle.hpp" +#include "UnityEngine/UI/Toggle_ToggleEvent.hpp" +#include "UnityEngine/UI/LayoutElement.hpp" +#include "UnityEngine/Events/UnityAction.hpp" +#include "UnityEngine/Events/UnityAction_1.hpp" +#include "HMUI/ScrollView.hpp" +#include "HMUI/ModalView.hpp" +#include "HMUI/Touchable.hpp" +#include "UnityEngine/Resources.hpp" +#include "GlobalNamespace/ColorPickerButtonController.hpp" +#include "GlobalNamespace/ColorChangeUIEventType.hpp" +#include "System/Action_2.hpp" + +#include "questui/shared/QuestUI.hpp" +#include "questui/shared/BeatSaberUI.hpp" + +using namespace QuestUI; +using namespace UnityEngine; +using namespace UnityEngine::UI; +using namespace UnityEngine::Events; +using namespace HMUI; + +#ifndef REGISTER_FUNCTION +DEFINE_TYPE(StreamerTools, stSettingViewController); +#elif defined(DEFINE_TYPE) +DEFINE_TYPE(StreamerTools::stSettingViewController); +#elif defined(DEFINE_CLASS) +DEFINE_CLASS(StreamerTools::stSettingViewController); +#endif + +bool configFetched = false; + +Transform* parent; + +enum class SettingsState { + NotInit, Updating, Ready +}; + +SettingsState state = SettingsState::NotInit; + +//bool SettingsInit = false; + +//bool NotUpdating = true; + +// Defined here, and defined as extern in STmanager.hpp +GameObject* Decimals; +GameObject* DEnergy; +GameObject* D_MP_Code; +GameObject* A_MP_Code; +GameObject* A_Update; + +void MakeConfigUI(bool UpdateOnly) { + // Prevents a crash from the client trying to set the config before the config ViewController has first activated + // This will update the Config Values while the config is open + if (UpdateOnly && state != SettingsState::Ready) return; + else if (UpdateOnly) { + state = SettingsState::Updating; + Decimals->GetComponent()->CurrentValue = (float)getModConfig().DecimalsForNumbers.GetValue(); + Decimals->GetComponent()->UpdateValue(); + DEnergy->GetComponent()->set_isOn(getModConfig().DontEnergy.GetValue()); + D_MP_Code->GetComponent()->set_isOn(getModConfig().DontMpCode.GetValue()); + A_MP_Code->GetComponent()->set_isOn(getModConfig().AlwaysMpCode.GetValue()); + A_Update->GetComponent()->set_isOn(getModConfig().AlwaysUpdate.GetValue()); + state = SettingsState::Ready; + } + // Creates the Config UI + else { + // Decimals for numbers + getModConfig().DecimalsForNumbers.AddChangeEvent( + [&](int value) { + getModConfig().LastChanged.SetValue(static_cast(time(0))); + configFetched = false; + } + ); + + Decimals = AddConfigValueIncrementInt(parent, getModConfig().DecimalsForNumbers, 1, 0, 20)->get_gameObject(); + BeatSaberUI::AddHoverHint(Decimals, "How many decimal places to show"); + + + //Decimals = STAddConfigValueIncrementInt(parent, getModConfig().DecimalsForNumbers, 1, 0, 20)->get_gameObject(); + //BeatSaberUI::AddHoverHint(Decimals, "How many decimal places to show"); + + // DontEnergy + getModConfig().DontEnergy.AddChangeEvent( + [&](bool value) { + getModConfig().LastChanged.SetValue(static_cast(time(0))); + configFetched = false; + } + ); + + DEnergy = AddConfigValueToggle(parent, getModConfig().DontEnergy)->get_gameObject(); + BeatSaberUI::AddHoverHint(DEnergy, "Dont show energy bar"); + + // DontMpCode + getModConfig().DontMpCode.AddChangeEvent( + [&](bool value) { + getModConfig().LastChanged.SetValue(static_cast(time(0))); + configFetched = false; + } + ); + + D_MP_Code = AddConfigValueToggle(parent, getModConfig().DontMpCode)->get_gameObject(); + BeatSaberUI::AddHoverHint(D_MP_Code, "Don't show multiplayer code"); + + // AlwaysMpCode + getModConfig().AlwaysMpCode.AddChangeEvent( + [&](bool value) { + getModConfig().LastChanged.SetValue(static_cast(time(0))); + configFetched = false; + } + ); + + A_MP_Code = AddConfigValueToggle(parent, getModConfig().AlwaysMpCode)->get_gameObject(); + BeatSaberUI::AddHoverHint(A_MP_Code, "Always show multiplayer code. default: if no shown in game as *****"); + + // AlwaysUpdate + getModConfig().AlwaysUpdate.AddChangeEvent( + [&](bool value) { + getModConfig().LastChanged.SetValue(static_cast(time(0))); + configFetched = false; + } + ); + + A_Update = AddConfigValueToggle(parent, getModConfig().AlwaysUpdate)->get_gameObject(); + BeatSaberUI::AddHoverHint(A_Update, "Update the overlay on song select an not just on song start"); + state = SettingsState::Ready; + } +} + +//bool InSettings = true; + +void StreamerTools::stSettingViewController::DidActivate(bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { + //InSettings = true; + if(firstActivation && state == SettingsState::NotInit) { + get_gameObject()->AddComponent(); + GameObject* container = BeatSaberUI::CreateScrollableSettingsContainer(get_transform()); + parent = container->get_transform(); + QuestUI::BeatSaberUI::CreateText(parent, "Client configuration"); + MakeConfigUI(false); + } else MakeConfigUI(true); +} + +//void StreamerTools::stSettingViewController::DidDeactivate(bool removedFromHierarchy, bool systemScreenDisabling) +//{ +// InSettings = false; //} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 68ca68f..48588a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,629 +1,629 @@ -#include "modloader/shared/modloader.hpp" -#include "STmanager.hpp" -#include "SettingsViewController.hpp" -#include "Config.hpp" - -#include "custom-types/shared/register.hpp" - -#include "beatsaber-hook/shared/utils/typedefs.h" -#include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" -#include "beatsaber-hook/shared/utils/utils.h" -#include "beatsaber-hook/shared/utils/logging.hpp" -#include "beatsaber-hook/shared/utils/il2cpp-utils.hpp" -#include "beatsaber-hook/shared/utils/typedefs.h" -#ifndef MAKE_HOOK_OFFSETLESS -#include "beatsaber-hook/shared/utils/hooking.hpp" -#endif - -#include "UnityEngine/Resources.hpp" -#include "UnityEngine/GameObject.hpp" -#include "UnityEngine/Component.hpp" -#include "UnityEngine/SceneManagement/Scene.hpp" -#include "UnityEngine/SceneManagement/SceneManager.hpp" -#include "UnityEngine/Application.hpp" - -#include "UnityEngine/ImageConversion.hpp" -#include "UnityEngine/Sprite.hpp" -#include "UnityEngine/RenderTexture.hpp" -#include "UnityEngine/Graphics.hpp" -#include "UnityEngine/Texture2D.hpp" -#include "UnityEngine/Rect.hpp" - -#include "System/Action.hpp" -#include "System/Action_1.hpp" -#include "System/Action_2.hpp" -#include "System/Action_3.hpp" -#include "System/Threading/Tasks/Task_1.hpp" -#include "System/Threading/Tasks/TaskStatus.hpp" -//#include "System/Func_2.hpp" - -//#include "System/Threading/CancellationTokenSource.hpp" - -#include "GlobalNamespace/IConnectedPlayer.hpp" -#include "GlobalNamespace/MultiplayerPlayersManager.hpp" -#include "GlobalNamespace/MultiplayerSessionManager.hpp" - -#include "GlobalNamespace/MultiplayerLobbyConnectionController.hpp" - -#include "GlobalNamespace/PracticeSettings.hpp" -#include "GlobalNamespace/ScoreController.hpp" -#include "GlobalNamespace/RelativeScoreAndImmediateRankCounter.hpp" -#include "GlobalNamespace/ScoreController.hpp" -#include "GlobalNamespace/GameEnergyUIPanel.hpp" -#include "GlobalNamespace/StandardLevelDetailView.hpp" -#include "GlobalNamespace/IDifficultyBeatmap.hpp" -#include "GlobalNamespace/IBeatmapLevel.hpp" -#include "GlobalNamespace/MultiplayerSettingsPanelController.hpp" -#include "GlobalNamespace/ServerCodeView.hpp" -#include "GlobalNamespace/ScoreController.hpp" -#include "GlobalNamespace/NoteController.hpp" -#include "GlobalNamespace/NoteCutInfo.hpp" -#include "GlobalNamespace/FPSCounter.hpp" -#include "GlobalNamespace/GameplayCoreInstaller.hpp" -#include "GlobalNamespace/BeatmapDifficulty.hpp" -#include "GlobalNamespace/OVRPlugin.hpp" -#include "GlobalNamespace/OVRPlugin_SystemHeadset.hpp" -#include "GlobalNamespace/PreviewBeatmapLevelSO.hpp" -#include "GlobalNamespace/CustomPreviewBeatmapLevel.hpp" -#include "GlobalNamespace/MainMenuViewController.hpp" -#include "GlobalNamespace/OptionsViewController.hpp" -#include "GlobalNamespace/PlayerTransforms.hpp" -#include "GlobalNamespace/StandardLevelScenesTransitionSetupDataSO.hpp" -#include "GlobalNamespace/MissionLevelScenesTransitionSetupDataSO.hpp" -#include "GlobalNamespace/MultiplayerLevelScenesTransitionSetupDataSO.hpp" -#include "GlobalNamespace/StandardLevelGameplayManager.hpp" -#include "GlobalNamespace/MultiplayerLocalActivePlayerGameplayManager.hpp" -#include "GlobalNamespace/TutorialSongController.hpp" -#include "GlobalNamespace/MissionLevelGameplayManager.hpp" -#include "GlobalNamespace/PauseController.hpp" -#include "GlobalNamespace/AudioTimeSyncController.hpp" -#include "GlobalNamespace/MultiplayerSpectatorController.hpp" -#include "GlobalNamespace/MultiplayerSessionManager_SessionType.hpp" -#include "GlobalNamespace/ComboController.hpp" -using namespace GlobalNamespace; - -#if !defined(MAKE_HOOK_MATCH) -#error No Compatible HOOK macro found -#endif - -//#define DEBUG_BUILD 1 - -DEFINE_CONFIG(ModConfig); - -ModInfo STModInfo; - -Logger& getLogger() { - static auto logger = new Logger(STModInfo, LoggerOptions(false, true)); // Set first bool to true to silence logger, second bool defines output to file - return *logger; -} - -static STManager* stManager = nullptr; - -void ResetScores() { - stManager->goodCuts = 0; - stManager->badCuts = 0; - stManager->missedNotes = 0; - stManager->combo = 0; - stManager->score = 0; - stManager->accuracy = 1.0f; - stManager->energy = 0.5f; -} - -UnityEngine::Texture2D* DuplicateTexture(UnityEngine::Sprite* source) { - using namespace UnityEngine; - Rect size = source->GetTextureRect(); - Texture2D* texture = source->get_texture(); - - RenderTexture* renderTex = RenderTexture::GetTemporary(texture->get_width(), texture->get_height(), 0, UnityEngine::RenderTextureFormat::Default, UnityEngine::RenderTextureReadWrite::Linear); - Graphics::Blit(texture, renderTex); - RenderTexture* previous = RenderTexture::get_active(); - RenderTexture::set_active(renderTex); - Texture2D* readableText = Texture2D::New_ctor(size.get_width() + 8, size.get_height() + 8); - //readableText->ReadPixels(UnityEngine::Rect(0, 0, renderTex->get_width(), renderTex->get_height()), 0, 0); // Shows the full 2048x2048 atlas - //readableText->ReadPixels(UnityEngine::Rect(size.get_x() - 4, size.get_y() - 4, size.get_width() + 8, size.get_height() + 8), 0, 0); // Shows the full 168x168 of the individual cover - readableText->ReadPixels(UnityEngine::Rect(size.get_x(), size.get_y(), size.get_width(), size.get_height()), 0, 0); // This works, image size 160x160 - readableText->Apply(); - RenderTexture::set_active(previous); - RenderTexture::ReleaseTemporary(renderTex); - return readableText; -} - -bool CoverChanged[4] = { false }; -int CoverStatus = Init; - -void GetCoverTexture(System::Threading::Tasks::Task_1* coverSpriteTask) { - using namespace System::Threading; - using namespace UnityEngine; - if (coverSpriteTask->get_IsCompleted()) { - Sprite* coverSprite; - coverSprite = coverSpriteTask->get_Result(); - UnityEngine::Texture2D* coverTexture; - // Check if the Texture is Readable and if not duplicate it and read from that - if (coverSprite->get_texture()->get_isReadable()) { - coverTexture = coverSprite->get_texture(); - } - else { - stManager->cv.notify_one(); - CoverStatus = Failed; - return; - } - stManager->statusLock.lock(); - stManager->coverTexture = coverTexture; - stManager->statusLock.unlock(); - for (int i = 0; i < 4; i++) { - CoverChanged[i] = true; - } - coverSpriteTask->Dispose(); - stManager->cv.notify_one(); - CoverStatus = Completed; - getLogger().info("Successfully loaded CoverImage"); - stManager->coverFetchable = true; - } - else if (coverSpriteTask->get_IsFaulted()) { - getLogger().error("GetCover Task Faulted: Satus is, %d", (int)coverSpriteTask->get_Status()); - stManager->cv.notify_one(); - CoverStatus = Failed; - coverSpriteTask->Dispose(); - } - else { - getLogger().error("Task Error: Status is %d", (int)coverSpriteTask->get_Status()); - stManager->cv.notify_one(); - CoverStatus = Failed; - } -} - -// TODO: Put the below in a lambda for the task -// Haven't figured out lambdas for tasks continuation yet -void GetCover(PreviewBeatmapLevelSO* level) { - using namespace System::Threading; - using namespace UnityEngine; - Tasks::Task_1* coverSpriteTask; - getLogger().debug("CoverSpriteTask"); - int Attempts = 0; -GetCoverTask: - CoverStatus = Running; - std::lock_guard lk(stManager->CoverLock); - - coverSpriteTask = level->GetCoverImageAsync(CancellationToken::get_None()); - bool CustomLevel = (il2cpp_functions::class_is_assignable_from(classof(CustomPreviewBeatmapLevel*), il2cpp_functions::object_get_class(reinterpret_cast(level)))); - if (CustomLevel) { - auto action = il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), coverSpriteTask, GetCoverTexture); - reinterpret_cast(coverSpriteTask)->ContinueWith(action); - } - else { - if (coverSpriteTask->get_IsFaulted() && Attempts < 5) { - coverSpriteTask->Dispose(); - Attempts++; - goto GetCoverTask; - } - else if (coverSpriteTask->get_Status().value == 1) { - getLogger().critical("Task queued, cannot wait cause it would result in the MainThread freezing! Skipping Task"); - stManager->cv.notify_one(); - CoverStatus = Failed; - return; - } - if (coverSpriteTask->get_IsCompleted()) { - Sprite* coverSprite; - coverSprite = coverSpriteTask->get_Result(); - UnityEngine::Texture2D* coverTexture; - // Check if the Texture is Readable and if not duplicate it and read from that - if (coverSprite->get_texture()->get_isReadable()) { - - coverTexture = coverSprite->get_texture(); - } - else { - coverTexture = DuplicateTexture(coverSprite); - } - stManager->coverTexture = coverTexture; - for (int i = 0; i < 4; i++) { - CoverChanged[i] = true; - } - coverSpriteTask->Dispose(); - stManager->cv.notify_one(); - CoverStatus = Completed; - getLogger().info("Successfully loaded CoverImage"); - } - else { - stManager->cv.notify_one(); - CoverStatus = Failed; - getLogger().error("Task Failed to load CoverImage: Status was: %d", coverSpriteTask->get_Status().value); - if (coverSpriteTask->get_Status().value != 1) coverSpriteTask->Dispose(); - } - } -} - -//bool GotBeatmapInfo = false; - -MAKE_HOOK_MATCH(RefreshContent, &StandardLevelDetailView::RefreshContent, void, StandardLevelDetailView* self) { - RefreshContent(self); - - // Null Check Level before trying to get any data - if (self->dyn__level()) { - stManager->statusLock.lock(); - stManager->levelName = std::string(reinterpret_cast(self->dyn__level())->get_songName()); - stManager->levelSubName = std::string(reinterpret_cast(self->dyn__level())->get_songSubName()); - stManager->levelAuthor = std::string(reinterpret_cast(self->dyn__level())->get_levelAuthorName()); - stManager->songAuthor = std::string(reinterpret_cast(self->dyn__level())->get_songAuthorName()); - stManager->id = std::string(reinterpret_cast(self->dyn__level())->get_levelID()); - stManager->bpm = reinterpret_cast(self->dyn__level())->get_beatsPerMinute(); - // Check if level can be assigned as CustomPreviewBeatmapLevel - bool CustomLevel = (il2cpp_functions::class_is_assignable_from(classof(CustomPreviewBeatmapLevel*), il2cpp_functions::object_get_class(reinterpret_cast(self->dyn__level())))); - stManager->njs = self->dyn__selectedDifficultyBeatmap()->get_noteJumpMovementSpeed(); - stManager->difficulty = self->dyn__selectedDifficultyBeatmap()->get_difficulty().value; - stManager->coverFetchable = false; - GetCover(reinterpret_cast(self->dyn__level())); - stManager->statusLock.unlock(); - //GotBeatmapInfo = true; - } - else { - //GotBeatmapInfo = false; - getLogger().info("BeatmapLevelSO is nullptr"); - } -} - -MAKE_HOOK_MATCH(SongStart, &StandardLevelScenesTransitionSetupDataSO::Init, void, StandardLevelScenesTransitionSetupDataSO* self, StringW gameMode, IDifficultyBeatmap* dbm, IPreviewBeatmapLevel* previewBeatmapLevel, OverrideEnvironmentSettings* overrideEnvironmentSettings, ColorScheme* overrideColorScheme, GameplayModifiers* gameplayModifiers, PlayerSpecificSettings* playerSpecificSettings, PracticeSettings* practiceSettings, StringW backButtonText, bool startPaused, bool useTestNoteCutSoundEffects) { - stManager->statusLock.lock(); - stManager->location = Solo_Song; - ResetScores(); - stManager->isPractice = practiceSettings; // If practice settings isn't null, then we're in practice mode - if (CoverStatus == Failed) GetCover(reinterpret_cast(previewBeatmapLevel)); // Try loading the Cover again if failed previously - stManager->statusLock.unlock(); - SongStart(self, gameMode, dbm, previewBeatmapLevel, overrideEnvironmentSettings, overrideColorScheme, gameplayModifiers, playerSpecificSettings, practiceSettings, backButtonText, startPaused, useTestNoteCutSoundEffects); -} - -MAKE_HOOK_MATCH(CampaignLevelStart, &MissionLevelScenesTransitionSetupDataSO::Init, void, MissionLevelScenesTransitionSetupDataSO* self, StringW missionId, IDifficultyBeatmap* difficultyBeatmap, IPreviewBeatmapLevel* previewBeatmapLevel, ArrayW missionObjectives, ColorScheme* overrideColorScheme, GameplayModifiers* gameplayModifiers, PlayerSpecificSettings* playerSpecificSettings, StringW backButtonText) { - stManager->statusLock.lock(); - stManager->location = Campaign; - ResetScores(); - stManager->statusLock.unlock(); - CampaignLevelStart(self, missionId, difficultyBeatmap, previewBeatmapLevel, missionObjectives, overrideColorScheme, gameplayModifiers, playerSpecificSettings, backButtonText); -} - -MAKE_HOOK_MATCH(RelativeScoreAndImmediateRankCounter_UpdateRelativeScoreAndImmediateRank, &RelativeScoreAndImmediateRankCounter::UpdateRelativeScoreAndImmediateRank, void, RelativeScoreAndImmediateRankCounter* self, int score, int modifiedScore, int maxPossibleScore, int maxPossibleModifiedScore) { - RelativeScoreAndImmediateRankCounter_UpdateRelativeScoreAndImmediateRank(self, score, modifiedScore, maxPossibleScore, maxPossibleModifiedScore); - stManager->statusLock.lock(); - stManager->score = modifiedScore; - stManager->rank = std::string(RankModel::GetRankName(self->get_immediateRank())); - stManager->accuracy = self->get_relativeScore(); - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(GameEnergyUIPanel_HandleGameEnergyDidChange, &GameEnergyUIPanel::HandleGameEnergyDidChange, void, GameEnergyUIPanel* self, float energy) { - GameEnergyUIPanel_HandleGameEnergyDidChange(self, energy); - stManager->statusLock.lock(); - stManager->energy = energy; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(ServerCodeView_RefreshText, &ServerCodeView::RefreshText, void, ServerCodeView* self, bool refreshText) { - ServerCodeView_RefreshText(self, refreshText); - if (self->dyn__serverCode()) { - stManager->statusLock.lock(); - stManager->mpGameId = to_utf8(csstrtostr(self->dyn__serverCode())); - stManager->mpGameIdShown = self->dyn__codeIsShown(); - stManager->statusLock.unlock(); - } -} - -void ComboChanged(int value) { - stManager->statusLock.lock(); - stManager->combo = value; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(ComboController_Start, &ComboController::Start, void, ComboController* self) { - self->add_comboDidChangeEvent( - il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), static_cast(nullptr), ComboChanged) - ); - - ComboController_Start(self); -} - - - -// Multiplayer song starting is handled differently -MAKE_HOOK_MATCH(MultiplayerSongStart, &MultiplayerLevelScenesTransitionSetupDataSO::Init, void, MultiplayerLevelScenesTransitionSetupDataSO* self, StringW gameMode, IPreviewBeatmapLevel* previewBeatmapLevel, BeatmapDifficulty beatmapDifficulty, BeatmapCharacteristicSO* beatmapCharacteristic, IDifficultyBeatmap* difficultyBeatmap, ColorScheme* overrideColorScheme, GameplayModifiers* gameplayModifiers, PlayerSpecificSettings* playerSpecificSettings, PracticeSettings* practiceSettings, bool useTestNoteCutSoundEffects) { - stManager->statusLock.lock(); - stManager->location = MP_Song; - ResetScores(); - if (previewBeatmapLevel) { - stManager->levelName = std::string(previewBeatmapLevel->get_songName()); - stManager->levelSubName = std::string(previewBeatmapLevel->get_songSubName()); - stManager->levelAuthor = std::string(previewBeatmapLevel->get_levelAuthorName()); - stManager->songAuthor = std::string(previewBeatmapLevel->get_songAuthorName()); - stManager->id = std::string(previewBeatmapLevel->get_levelID()); - stManager->bpm = previewBeatmapLevel->get_beatsPerMinute(); - stManager->njs = difficultyBeatmap->get_noteJumpMovementSpeed(); - stManager->difficulty = beatmapDifficulty.value; - stManager->coverFetchable = false; - GetCover(reinterpret_cast(previewBeatmapLevel)); - } - else { - getLogger().debug("IPreviewBeatmapLevel is %p", previewBeatmapLevel); - } - stManager->statusLock.unlock(); - - - MultiplayerSongStart(self, gameMode, previewBeatmapLevel, beatmapDifficulty, beatmapCharacteristic, difficultyBeatmap, overrideColorScheme, gameplayModifiers, playerSpecificSettings, practiceSettings, useTestNoteCutSoundEffects); -} - -void onLobbyJoin(MultiplayerSessionManager* sessionManager) { - stManager->statusLock.lock(); - stManager->location = MP_Lobby; - int players = sessionManager->get_connectedPlayerCount(); - if (players > 0) - stManager->players = players; - else stManager->players = players + 1; - stManager->maxPlayers = sessionManager->get_maxPlayerCount(); - stManager->statusLock.unlock(); -} - -void onPlayerJoin(MultiplayerSessionManager* sessionManager, IConnectedPlayer * player) { - stManager->statusLock.lock(); - if (!(player->get_isMe() && player->get_isConnectionOwner())) - stManager->players++; - stManager->statusLock.unlock(); -} - - -void onPlayerLeave() { - stManager->statusLock.lock(); - stManager->players--; - stManager->statusLock.unlock(); -} - -// Reset the lobby back to null when we leave back to the menu -void onLobbyDisconnect() { - stManager->statusLock.lock(); - stManager->location = Menu; - stManager->mpGameId = ""; - stManager->mpGameIdShown = false; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(MultiplayerJoinLobby, &MultiplayerSessionManager::StartSession, void, MultiplayerSessionManager* self, GlobalNamespace::MultiplayerSessionManager_SessionType sessionType, ConnectedPlayerManager* connectedPlayerManager) { - MultiplayerJoinLobby(self, sessionType, connectedPlayerManager); - - int maxPlayers = self->get_maxPlayerCount(); - int numActivePlayers = self->get_connectedPlayerCount(); - - // Register player join and leave events - self->add_playerDisconnectedEvent( - il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), static_cast(nullptr), onPlayerLeave) - ); - - self->add_playerConnectedEvent( - il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), self, onPlayerJoin) - ); - - // Register connect and disconnect from lobby events - self->add_disconnectedEvent( - il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), static_cast(nullptr), onLobbyDisconnect) - ); - - self->add_connectedEvent(il2cpp_utils::MakeDelegate(classof(System::Action*), self, onLobbyJoin)); -} -MAKE_HOOK_MATCH(SongEnd, &StandardLevelGameplayManager::OnDestroy, void, StandardLevelGameplayManager* self) { - stManager->statusLock.lock(); - stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu - stManager->location = Menu; - stManager->statusLock.unlock(); - SongEnd(self); -} - -MAKE_HOOK_MATCH(MultiplayerSongFinish, &MultiplayerLocalActivePlayerGameplayManager::HandleSongDidFinish, void, MultiplayerLocalActivePlayerGameplayManager* self) { - stManager->statusLock.lock(); - stManager->location = MP_Lobby; - stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu - stManager->statusLock.unlock(); - MultiplayerSongFinish(self); -} - -MAKE_HOOK_MATCH(MultiplayerSpectateStart, &MultiplayerSpectatorController::Start, void, MultiplayerSpectatorController* self) { - stManager->statusLock.lock(); - stManager->location = Spectator; - stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu - stManager->statusLock.unlock(); - MultiplayerSpectateStart(self); -} - -MAKE_HOOK_MATCH(MultiplayerSpectateDestroy, &MultiplayerSpectatorController::OnDestroy, void, MultiplayerSpectatorController* self) { - stManager->statusLock.lock(); - stManager->location = MP_Lobby; - stManager->statusLock.unlock(); - MultiplayerSpectateDestroy(self); -} - -MAKE_HOOK_MATCH(TutorialStart, &TutorialSongController::Awake, void, TutorialSongController* self) { - stManager->statusLock.lock(); - stManager->location = Tutorial; - ResetScores(); - stManager->statusLock.unlock(); - TutorialStart(self); -} -MAKE_HOOK_MATCH(TutorialEnd, &TutorialSongController::OnDestroy, void, TutorialSongController* self) { - stManager->statusLock.lock(); - stManager->location = Menu; - stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu - stManager->statusLock.unlock(); - TutorialEnd(self); -} - -MAKE_HOOK_MATCH(CampaignLevelEnd, &MissionLevelGameplayManager::OnDestroy, void, MissionLevelGameplayManager* self) { - stManager->statusLock.lock(); - stManager->location = Menu; - stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu - stManager->statusLock.unlock(); - CampaignLevelEnd(self); -} - -MAKE_HOOK_MATCH(GamePause, &PauseController::Pause, void, PauseController* self) { - stManager->statusLock.lock(); - stManager->paused = true; - stManager->statusLock.unlock(); - GamePause(self); -} -MAKE_HOOK_MATCH(GameResume, &PauseController::HandlePauseMenuManagerDidPressContinueButton, void, PauseController* self) { - stManager->statusLock.lock(); - stManager->paused = false; - stManager->statusLock.unlock(); - GameResume(self); -} - -MAKE_HOOK_MATCH(AudioUpdate, &AudioTimeSyncController::Update, void, AudioTimeSyncController* self) { - AudioUpdate(self); - - //float time = CRASH_UNLESS(il2cpp_utils::RunMethodUnsafe(self, "get_songTime")); - //float endTime = CRASH_UNLESS(il2cpp_utils::RunMethodUnsafe(self, "get_songEndTime")); - - float time = self->get_songTime(); - float endTime = self->get_songEndTime(); - - stManager->statusLock.lock(); - stManager->time = (int)time; - stManager->endTime = (int)endTime; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(ScoreController_HandleNoteWasMissed, &ScoreController::HandleNoteWasMissed, void, ScoreController* self, NoteController* note) { - ScoreController_HandleNoteWasMissed(self, note); - stManager->statusLock.lock(); - stManager->missedNotes++; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(ScoreController_HandleNoteWasCut, &ScoreController::HandleNoteWasCut, void, ScoreController* self, NoteController* noteController, ByRef noteCutInfo) -{ - ScoreController_HandleNoteWasCut(self, noteController, noteCutInfo); - stManager->statusLock.lock(); - if (noteCutInfo.heldRef.get_allIsOK()) stManager->goodCuts++; - else stManager->badCuts++; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(FPSCounter_Update, &FPSCounter::Update, void, FPSCounter* self) { - FPSCounter_Update(self); - - stManager->statusLock.lock(); - stManager->fps = self->get_currentFPS(); - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(PlayerTransforms_Update, &PlayerTransforms::Update, void, PlayerTransforms* self) { - PlayerTransforms_Update(self); - if (!self->dyn__overrideHeadPos()) { - stManager->statusLock.lock(); - if (self->dyn__headTransform()) - stManager->Head = self->dyn__headTransform(); - if (self->dyn__rightHandTransform()) - stManager->VR_Right = self->dyn__rightHandTransform(); - if (self->dyn__leftHandTransform()) - stManager->VR_Left = self->dyn__leftHandTransform(); - stManager->statusLock.unlock(); - } -} - -bool FPSObjectCreated = false; - -std::string GetHeadsetType() { - GlobalNamespace::OVRPlugin::SystemHeadset HeadsetType = GlobalNamespace::OVRPlugin::GetSystemHeadsetType(); - std::string result; - switch (HeadsetType.value) { - case HeadsetType.Oculus_Quest: - return result = "Oculus Quest"; - case 7: - return result = "Oculus Go"; - case HeadsetType.Oculus_Quest_2: - return result = "Oculus Quest 2"; - case 10: - return result = "Oculus Quest 3/2 Pro"; - default: - return result = "Unknown " + std::string(GlobalNamespace::OVRPlugin::get_productName()); - } -} - -MAKE_HOOK_MATCH(SceneManager_ActiveSceneChanged, &UnityEngine::SceneManagement::SceneManager::Internal_ActiveSceneChanged, void, UnityEngine::SceneManagement::Scene previousActiveScene, UnityEngine::SceneManagement::Scene newActiveScene) { - SceneManager_ActiveSceneChanged(previousActiveScene, newActiveScene); - if (newActiveScene.IsValid()) { - std::string sceneName = to_utf8(csstrtostr(newActiveScene.get_name())); - std::string shaderWarmup = "ShaderWarmup"; - std::string EmptyTransition = "EmptyTransition"; - if (sceneName == EmptyTransition) stManager->headsetType = GetHeadsetType(); - else if (sceneName == shaderWarmup) { - auto FPSCObject = UnityEngine::GameObject::New_ctor(il2cpp_utils::newcsstr("FPSC")); - UnityEngine::Object::DontDestroyOnLoad(FPSCObject->AddComponent()); - } - FPSObjectCreated = true; - } -} - -MAKE_HOOK_MATCH(OptionsViewController_DidActivate, &OptionsViewController::DidActivate, void, OptionsViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { - OptionsViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); - stManager->statusLock.lock(); - stManager->location = Options; - stManager->statusLock.unlock(); -} - -MAKE_HOOK_MATCH(MainMenuViewController_DidActivate, &MainMenuViewController::DidActivate, void, MainMenuViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { - MainMenuViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); - stManager->statusLock.lock(); - stManager->location = Menu; - stManager->statusLock.unlock(); -} - -extern "C" void setup(ModInfo& info) { - info.id = ID; - info.version = VERSION; - STModInfo = info; - - getLogger().info("Modloader name: %s tag: %s", Modloader::getInfo().name.c_str(), Modloader::getInfo().tag.c_str()); - - getLogger().info("Completed setup!"); -} - -extern "C" void load() { - getLogger().debug("Installing hooks..."); - il2cpp_functions::Init(); - QuestUI::Init(); - - getModConfig().Init(STModInfo); - - custom_types::Register::AutoRegister(); - QuestUI::Register::RegisterModSettingsViewController(STModInfo); - - // Install our function hooks - LoggerContextObject logger = getLogger().WithContext("Hook"); - INSTALL_HOOK(logger, PlayerTransforms_Update); - INSTALL_HOOK(logger, RefreshContent); - INSTALL_HOOK(logger, SongStart); - INSTALL_HOOK(logger, CampaignLevelStart); - INSTALL_HOOK(logger, RelativeScoreAndImmediateRankCounter_UpdateRelativeScoreAndImmediateRank); - INSTALL_HOOK(logger, ComboController_Start); - INSTALL_HOOK(logger, SongEnd); - INSTALL_HOOK(logger, CampaignLevelEnd); - INSTALL_HOOK(logger, TutorialStart); - INSTALL_HOOK(logger, TutorialEnd); - INSTALL_HOOK(logger, GamePause); - INSTALL_HOOK(logger, GameResume); - INSTALL_HOOK(logger, AudioUpdate); - INSTALL_HOOK(logger, MultiplayerSongStart); - INSTALL_HOOK(logger, MultiplayerJoinLobby); - INSTALL_HOOK(logger, MultiplayerSongFinish); - INSTALL_HOOK(logger, MultiplayerSpectateStart); - INSTALL_HOOK(logger, MultiplayerSpectateDestroy); - INSTALL_HOOK(logger, GameEnergyUIPanel_HandleGameEnergyDidChange); - INSTALL_HOOK(logger, ServerCodeView_RefreshText); - INSTALL_HOOK(logger, ScoreController_HandleNoteWasMissed); - INSTALL_HOOK(logger, ScoreController_HandleNoteWasCut); - INSTALL_HOOK(logger, FPSCounter_Update); - INSTALL_HOOK(logger, SceneManager_ActiveSceneChanged); - INSTALL_HOOK(logger, OptionsViewController_DidActivate); - INSTALL_HOOK(logger, MainMenuViewController_DidActivate); - - - getLogger().debug("Installed all hooks!"); - - stManager = new STManager(getLogger()); - stManager->gameVersion = to_utf8(csstrtostr(UnityEngine::Application::get_version())); - +#include "modloader/shared/modloader.hpp" +#include "STmanager.hpp" +#include "SettingsViewController.hpp" +#include "Config.hpp" + +#include "custom-types/shared/register.hpp" + +#include "beatsaber-hook/shared/utils/typedefs.h" +#include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" +#include "beatsaber-hook/shared/utils/utils.h" +#include "beatsaber-hook/shared/utils/logging.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-utils.hpp" +#include "beatsaber-hook/shared/utils/typedefs.h" +#ifndef MAKE_HOOK_OFFSETLESS +#include "beatsaber-hook/shared/utils/hooking.hpp" +#endif + +#include "UnityEngine/Resources.hpp" +#include "UnityEngine/GameObject.hpp" +#include "UnityEngine/Component.hpp" +#include "UnityEngine/SceneManagement/Scene.hpp" +#include "UnityEngine/SceneManagement/SceneManager.hpp" +#include "UnityEngine/Application.hpp" + +#include "UnityEngine/ImageConversion.hpp" +#include "UnityEngine/Sprite.hpp" +#include "UnityEngine/RenderTexture.hpp" +#include "UnityEngine/Graphics.hpp" +#include "UnityEngine/Texture2D.hpp" +#include "UnityEngine/Rect.hpp" + +#include "System/Action.hpp" +#include "System/Action_1.hpp" +#include "System/Action_2.hpp" +#include "System/Action_3.hpp" +#include "System/Threading/Tasks/Task_1.hpp" +#include "System/Threading/Tasks/TaskStatus.hpp" +//#include "System/Func_2.hpp" + +//#include "System/Threading/CancellationTokenSource.hpp" + +#include "GlobalNamespace/IConnectedPlayer.hpp" +#include "GlobalNamespace/MultiplayerPlayersManager.hpp" +#include "GlobalNamespace/MultiplayerSessionManager.hpp" + +#include "GlobalNamespace/MultiplayerLobbyConnectionController.hpp" + +#include "GlobalNamespace/PracticeSettings.hpp" +#include "GlobalNamespace/ScoreController.hpp" +#include "GlobalNamespace/RelativeScoreAndImmediateRankCounter.hpp" +#include "GlobalNamespace/ScoreController.hpp" +#include "GlobalNamespace/GameEnergyUIPanel.hpp" +#include "GlobalNamespace/StandardLevelDetailView.hpp" +#include "GlobalNamespace/IDifficultyBeatmap.hpp" +#include "GlobalNamespace/IBeatmapLevel.hpp" +#include "GlobalNamespace/MultiplayerSettingsPanelController.hpp" +#include "GlobalNamespace/ServerCodeView.hpp" +#include "GlobalNamespace/ScoreController.hpp" +#include "GlobalNamespace/NoteController.hpp" +#include "GlobalNamespace/NoteCutInfo.hpp" +#include "GlobalNamespace/FPSCounter.hpp" +#include "GlobalNamespace/GameplayCoreInstaller.hpp" +#include "GlobalNamespace/BeatmapDifficulty.hpp" +#include "GlobalNamespace/OVRPlugin.hpp" +#include "GlobalNamespace/OVRPlugin_SystemHeadset.hpp" +#include "GlobalNamespace/PreviewBeatmapLevelSO.hpp" +#include "GlobalNamespace/CustomPreviewBeatmapLevel.hpp" +#include "GlobalNamespace/MainMenuViewController.hpp" +#include "GlobalNamespace/OptionsViewController.hpp" +#include "GlobalNamespace/PlayerTransforms.hpp" +#include "GlobalNamespace/StandardLevelScenesTransitionSetupDataSO.hpp" +#include "GlobalNamespace/MissionLevelScenesTransitionSetupDataSO.hpp" +#include "GlobalNamespace/MultiplayerLevelScenesTransitionSetupDataSO.hpp" +#include "GlobalNamespace/StandardLevelGameplayManager.hpp" +#include "GlobalNamespace/MultiplayerLocalActivePlayerGameplayManager.hpp" +#include "GlobalNamespace/TutorialSongController.hpp" +#include "GlobalNamespace/MissionLevelGameplayManager.hpp" +#include "GlobalNamespace/PauseController.hpp" +#include "GlobalNamespace/AudioTimeSyncController.hpp" +#include "GlobalNamespace/MultiplayerSpectatorController.hpp" +#include "GlobalNamespace/MultiplayerSessionManager_SessionType.hpp" +#include "GlobalNamespace/ComboController.hpp" +using namespace GlobalNamespace; + +#if !defined(MAKE_HOOK_MATCH) +#error No Compatible HOOK macro found +#endif + +//#define DEBUG_BUILD 1 + +DEFINE_CONFIG(ModConfig); + +ModInfo STModInfo; + +Logger& getLogger() { + static auto logger = new Logger(STModInfo, LoggerOptions(false, true)); // Set first bool to true to silence logger, second bool defines output to file + return *logger; +} + +static STManager* stManager = nullptr; + +void ResetScores() { + stManager->goodCuts = 0; + stManager->badCuts = 0; + stManager->missedNotes = 0; + stManager->combo = 0; + stManager->score = 0; + stManager->accuracy = 1.0f; + stManager->energy = 0.5f; +} + +UnityEngine::Texture2D* DuplicateTexture(UnityEngine::Sprite* source) { + using namespace UnityEngine; + Rect size = source->GetTextureRect(); + Texture2D* texture = source->get_texture(); + + RenderTexture* renderTex = RenderTexture::GetTemporary(texture->get_width(), texture->get_height(), 0, UnityEngine::RenderTextureFormat::Default, UnityEngine::RenderTextureReadWrite::Linear); + Graphics::Blit(texture, renderTex); + RenderTexture* previous = RenderTexture::get_active(); + RenderTexture::set_active(renderTex); + Texture2D* readableText = Texture2D::New_ctor(size.get_width() + 8, size.get_height() + 8); + //readableText->ReadPixels(UnityEngine::Rect(0, 0, renderTex->get_width(), renderTex->get_height()), 0, 0); // Shows the full 2048x2048 atlas + //readableText->ReadPixels(UnityEngine::Rect(size.get_x() - 4, size.get_y() - 4, size.get_width() + 8, size.get_height() + 8), 0, 0); // Shows the full 168x168 of the individual cover + readableText->ReadPixels(UnityEngine::Rect(size.get_x(), size.get_y(), size.get_width(), size.get_height()), 0, 0); // This works, image size 160x160 + readableText->Apply(); + RenderTexture::set_active(previous); + RenderTexture::ReleaseTemporary(renderTex); + return readableText; +} + +bool CoverChanged[4] = { false }; +int CoverStatus = Init; + +void GetCoverTexture(System::Threading::Tasks::Task_1* coverSpriteTask) { + using namespace System::Threading; + using namespace UnityEngine; + if (coverSpriteTask->get_IsCompleted()) { + Sprite* coverSprite; + coverSprite = coverSpriteTask->get_Result(); + UnityEngine::Texture2D* coverTexture; + // Check if the Texture is Readable and if not duplicate it and read from that + if (coverSprite->get_texture()->get_isReadable()) { + coverTexture = coverSprite->get_texture(); + } + else { + stManager->cv.notify_one(); + CoverStatus = Failed; + return; + } + stManager->statusLock.lock(); + stManager->coverTexture = coverTexture; + stManager->statusLock.unlock(); + for (int i = 0; i < 4; i++) { + CoverChanged[i] = true; + } + coverSpriteTask->Dispose(); + stManager->cv.notify_one(); + CoverStatus = Completed; + getLogger().info("Successfully loaded CoverImage"); + stManager->coverFetchable = true; + } + else if (coverSpriteTask->get_IsFaulted()) { + getLogger().error("GetCover Task Faulted: Satus is, %d", (int)coverSpriteTask->get_Status()); + stManager->cv.notify_one(); + CoverStatus = Failed; + coverSpriteTask->Dispose(); + } + else { + getLogger().error("Task Error: Status is %d", (int)coverSpriteTask->get_Status()); + stManager->cv.notify_one(); + CoverStatus = Failed; + } +} + +// TODO: Put the below in a lambda for the task +// Haven't figured out lambdas for tasks continuation yet +void GetCover(PreviewBeatmapLevelSO* level) { + using namespace System::Threading; + using namespace UnityEngine; + Tasks::Task_1* coverSpriteTask; + getLogger().debug("CoverSpriteTask"); + int Attempts = 0; +GetCoverTask: + CoverStatus = Running; + std::lock_guard lk(stManager->CoverLock); + + coverSpriteTask = level->GetCoverImageAsync(CancellationToken::get_None()); + bool CustomLevel = (il2cpp_functions::class_is_assignable_from(classof(CustomPreviewBeatmapLevel*), il2cpp_functions::object_get_class(reinterpret_cast(level)))); + if (CustomLevel) { + auto action = il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), coverSpriteTask, GetCoverTexture); + reinterpret_cast(coverSpriteTask)->ContinueWith(action); + } + else { + if (coverSpriteTask->get_IsFaulted() && Attempts < 5) { + coverSpriteTask->Dispose(); + Attempts++; + goto GetCoverTask; + } + else if (coverSpriteTask->get_Status().value == 1) { + getLogger().critical("Task queued, cannot wait cause it would result in the MainThread freezing! Skipping Task"); + stManager->cv.notify_one(); + CoverStatus = Failed; + return; + } + if (coverSpriteTask->get_IsCompleted()) { + Sprite* coverSprite; + coverSprite = coverSpriteTask->get_Result(); + UnityEngine::Texture2D* coverTexture; + // Check if the Texture is Readable and if not duplicate it and read from that + if (coverSprite->get_texture()->get_isReadable()) { + + coverTexture = coverSprite->get_texture(); + } + else { + coverTexture = DuplicateTexture(coverSprite); + } + stManager->coverTexture = coverTexture; + for (int i = 0; i < 4; i++) { + CoverChanged[i] = true; + } + coverSpriteTask->Dispose(); + stManager->cv.notify_one(); + CoverStatus = Completed; + getLogger().info("Successfully loaded CoverImage"); + } + else { + stManager->cv.notify_one(); + CoverStatus = Failed; + getLogger().error("Task Failed to load CoverImage: Status was: %d", coverSpriteTask->get_Status().value); + if (coverSpriteTask->get_Status().value != 1) coverSpriteTask->Dispose(); + } + } +} + +//bool GotBeatmapInfo = false; + +MAKE_HOOK_MATCH(RefreshContent, &StandardLevelDetailView::RefreshContent, void, StandardLevelDetailView* self) { + RefreshContent(self); + + // Null Check Level before trying to get any data + if (self->dyn__level()) { + stManager->statusLock.lock(); + stManager->levelName = std::string(reinterpret_cast(self->dyn__level())->get_songName()); + stManager->levelSubName = std::string(reinterpret_cast(self->dyn__level())->get_songSubName()); + stManager->levelAuthor = std::string(reinterpret_cast(self->dyn__level())->get_levelAuthorName()); + stManager->songAuthor = std::string(reinterpret_cast(self->dyn__level())->get_songAuthorName()); + stManager->id = std::string(reinterpret_cast(self->dyn__level())->get_levelID()); + stManager->bpm = reinterpret_cast(self->dyn__level())->get_beatsPerMinute(); + // Check if level can be assigned as CustomPreviewBeatmapLevel + bool CustomLevel = (il2cpp_functions::class_is_assignable_from(classof(CustomPreviewBeatmapLevel*), il2cpp_functions::object_get_class(reinterpret_cast(self->dyn__level())))); + stManager->njs = self->dyn__selectedDifficultyBeatmap()->get_noteJumpMovementSpeed(); + stManager->difficulty = self->dyn__selectedDifficultyBeatmap()->get_difficulty().value; + stManager->coverFetchable = false; + GetCover(reinterpret_cast(self->dyn__level())); + stManager->statusLock.unlock(); + //GotBeatmapInfo = true; + } + else { + //GotBeatmapInfo = false; + getLogger().info("BeatmapLevelSO is nullptr"); + } +} + +MAKE_HOOK_MATCH(SongStart, &StandardLevelScenesTransitionSetupDataSO::Init, void, StandardLevelScenesTransitionSetupDataSO* self, StringW gameMode, IDifficultyBeatmap* dbm, IPreviewBeatmapLevel* previewBeatmapLevel, OverrideEnvironmentSettings* overrideEnvironmentSettings, ColorScheme* overrideColorScheme, GameplayModifiers* gameplayModifiers, PlayerSpecificSettings* playerSpecificSettings, PracticeSettings* practiceSettings, StringW backButtonText, bool startPaused, bool useTestNoteCutSoundEffects) { + stManager->statusLock.lock(); + stManager->location = Solo_Song; + ResetScores(); + stManager->isPractice = practiceSettings; // If practice settings isn't null, then we're in practice mode + if (CoverStatus == Failed) GetCover(reinterpret_cast(previewBeatmapLevel)); // Try loading the Cover again if failed previously + stManager->statusLock.unlock(); + SongStart(self, gameMode, dbm, previewBeatmapLevel, overrideEnvironmentSettings, overrideColorScheme, gameplayModifiers, playerSpecificSettings, practiceSettings, backButtonText, startPaused, useTestNoteCutSoundEffects); +} + +MAKE_HOOK_MATCH(CampaignLevelStart, &MissionLevelScenesTransitionSetupDataSO::Init, void, MissionLevelScenesTransitionSetupDataSO* self, StringW missionId, IDifficultyBeatmap* difficultyBeatmap, IPreviewBeatmapLevel* previewBeatmapLevel, ArrayW missionObjectives, ColorScheme* overrideColorScheme, GameplayModifiers* gameplayModifiers, PlayerSpecificSettings* playerSpecificSettings, StringW backButtonText) { + stManager->statusLock.lock(); + stManager->location = Campaign; + ResetScores(); + stManager->statusLock.unlock(); + CampaignLevelStart(self, missionId, difficultyBeatmap, previewBeatmapLevel, missionObjectives, overrideColorScheme, gameplayModifiers, playerSpecificSettings, backButtonText); +} + +MAKE_HOOK_MATCH(RelativeScoreAndImmediateRankCounter_UpdateRelativeScoreAndImmediateRank, &RelativeScoreAndImmediateRankCounter::UpdateRelativeScoreAndImmediateRank, void, RelativeScoreAndImmediateRankCounter* self, int score, int modifiedScore, int maxPossibleScore, int maxPossibleModifiedScore) { + RelativeScoreAndImmediateRankCounter_UpdateRelativeScoreAndImmediateRank(self, score, modifiedScore, maxPossibleScore, maxPossibleModifiedScore); + stManager->statusLock.lock(); + stManager->score = modifiedScore; + stManager->rank = std::string(RankModel::GetRankName(self->get_immediateRank())); + stManager->accuracy = self->get_relativeScore(); + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(GameEnergyUIPanel_HandleGameEnergyDidChange, &GameEnergyUIPanel::RefreshEnergyUI, void, GameEnergyUIPanel* self, float energy) { + GameEnergyUIPanel_HandleGameEnergyDidChange(self, energy); + stManager->statusLock.lock(); + stManager->energy = energy; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(ServerCodeView_RefreshText, &ServerCodeView::RefreshText, void, ServerCodeView* self, bool refreshText) { + ServerCodeView_RefreshText(self, refreshText); + if (self->dyn__serverCode()) { + stManager->statusLock.lock(); + stManager->mpGameId = to_utf8(csstrtostr(self->dyn__serverCode())); + stManager->mpGameIdShown = self->dyn__codeIsShown(); + stManager->statusLock.unlock(); + } +} + +void ComboChanged(int value) { + stManager->statusLock.lock(); + stManager->combo = value; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(ComboController_Start, &ComboController::Start, void, ComboController* self) { + self->add_comboDidChangeEvent( + il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), static_cast(nullptr), ComboChanged) + ); + + ComboController_Start(self); +} + + + +// Multiplayer song starting is handled differently +MAKE_HOOK_MATCH(MultiplayerSongStart, &MultiplayerLevelScenesTransitionSetupDataSO::Init, void, MultiplayerLevelScenesTransitionSetupDataSO* self, StringW gameMode, IPreviewBeatmapLevel* previewBeatmapLevel, BeatmapDifficulty beatmapDifficulty, BeatmapCharacteristicSO* beatmapCharacteristic, IDifficultyBeatmap* difficultyBeatmap, ColorScheme* overrideColorScheme, GameplayModifiers* gameplayModifiers, PlayerSpecificSettings* playerSpecificSettings, PracticeSettings* practiceSettings, bool useTestNoteCutSoundEffects) { + stManager->statusLock.lock(); + stManager->location = MP_Song; + ResetScores(); + if (previewBeatmapLevel) { + stManager->levelName = std::string(previewBeatmapLevel->get_songName()); + stManager->levelSubName = std::string(previewBeatmapLevel->get_songSubName()); + stManager->levelAuthor = std::string(previewBeatmapLevel->get_levelAuthorName()); + stManager->songAuthor = std::string(previewBeatmapLevel->get_songAuthorName()); + stManager->id = std::string(previewBeatmapLevel->get_levelID()); + stManager->bpm = previewBeatmapLevel->get_beatsPerMinute(); + stManager->njs = difficultyBeatmap->get_noteJumpMovementSpeed(); + stManager->difficulty = beatmapDifficulty.value; + stManager->coverFetchable = false; + GetCover(reinterpret_cast(previewBeatmapLevel)); + } + else { + getLogger().debug("IPreviewBeatmapLevel is %p", previewBeatmapLevel); + } + stManager->statusLock.unlock(); + + + MultiplayerSongStart(self, gameMode, previewBeatmapLevel, beatmapDifficulty, beatmapCharacteristic, difficultyBeatmap, overrideColorScheme, gameplayModifiers, playerSpecificSettings, practiceSettings, useTestNoteCutSoundEffects); +} + +void onLobbyJoin(MultiplayerSessionManager* sessionManager) { + stManager->statusLock.lock(); + stManager->location = MP_Lobby; + int players = sessionManager->get_connectedPlayerCount(); + if (players > 0) + stManager->players = players; + else stManager->players = players + 1; + stManager->maxPlayers = sessionManager->get_maxPlayerCount(); + stManager->statusLock.unlock(); +} + +void onPlayerJoin(MultiplayerSessionManager* sessionManager, IConnectedPlayer * player) { + stManager->statusLock.lock(); + if (!(player->get_isMe() && player->get_isConnectionOwner())) + stManager->players++; + stManager->statusLock.unlock(); +} + + +void onPlayerLeave() { + stManager->statusLock.lock(); + stManager->players--; + stManager->statusLock.unlock(); +} + +// Reset the lobby back to null when we leave back to the menu +void onLobbyDisconnect() { + stManager->statusLock.lock(); + stManager->location = Menu; + stManager->mpGameId = ""; + stManager->mpGameIdShown = false; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(MultiplayerJoinLobby, &MultiplayerSessionManager::StartSession, void, MultiplayerSessionManager* self, GlobalNamespace::MultiplayerSessionManager_SessionType sessionType, ConnectedPlayerManager* connectedPlayerManager) { + MultiplayerJoinLobby(self, sessionType, connectedPlayerManager); + + int maxPlayers = self->get_maxPlayerCount(); + int numActivePlayers = self->get_connectedPlayerCount(); + + // Register player join and leave events + self->add_playerDisconnectedEvent( + il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), static_cast(nullptr), onPlayerLeave) + ); + + self->add_playerConnectedEvent( + il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), self, onPlayerJoin) + ); + + // Register connect and disconnect from lobby events + self->add_disconnectedEvent( + il2cpp_utils::MakeDelegate*>(classof(System::Action_1*), static_cast(nullptr), onLobbyDisconnect) + ); + + self->add_connectedEvent(il2cpp_utils::MakeDelegate(classof(System::Action*), self, onLobbyJoin)); +} +MAKE_HOOK_MATCH(SongEnd, &StandardLevelGameplayManager::OnDestroy, void, StandardLevelGameplayManager* self) { + stManager->statusLock.lock(); + stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu + stManager->location = Menu; + stManager->statusLock.unlock(); + SongEnd(self); +} + +MAKE_HOOK_MATCH(MultiplayerSongFinish, &MultiplayerLocalActivePlayerGameplayManager::HandleSongDidFinish, void, MultiplayerLocalActivePlayerGameplayManager* self) { + stManager->statusLock.lock(); + stManager->location = MP_Lobby; + stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu + stManager->statusLock.unlock(); + MultiplayerSongFinish(self); +} + +MAKE_HOOK_MATCH(MultiplayerSpectateStart, &MultiplayerSpectatorController::Start, void, MultiplayerSpectatorController* self) { + stManager->statusLock.lock(); + stManager->location = Spectator; + stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu + stManager->statusLock.unlock(); + MultiplayerSpectateStart(self); +} + +MAKE_HOOK_MATCH(MultiplayerSpectateDestroy, &MultiplayerSpectatorController::OnDestroy, void, MultiplayerSpectatorController* self) { + stManager->statusLock.lock(); + stManager->location = MP_Lobby; + stManager->statusLock.unlock(); + MultiplayerSpectateDestroy(self); +} + +MAKE_HOOK_MATCH(TutorialStart, &TutorialSongController::Awake, void, TutorialSongController* self) { + stManager->statusLock.lock(); + stManager->location = Tutorial; + ResetScores(); + stManager->statusLock.unlock(); + TutorialStart(self); +} +MAKE_HOOK_MATCH(TutorialEnd, &TutorialSongController::OnDestroy, void, TutorialSongController* self) { + stManager->statusLock.lock(); + stManager->location = Menu; + stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu + stManager->statusLock.unlock(); + TutorialEnd(self); +} + +MAKE_HOOK_MATCH(CampaignLevelEnd, &MissionLevelGameplayManager::OnDestroy, void, MissionLevelGameplayManager* self) { + stManager->statusLock.lock(); + stManager->location = Menu; + stManager->paused = false; // If we are paused, unpause us, since we are returning to the menu + stManager->statusLock.unlock(); + CampaignLevelEnd(self); +} + +MAKE_HOOK_MATCH(GamePause, &PauseController::Pause, void, PauseController* self) { + stManager->statusLock.lock(); + stManager->paused = true; + stManager->statusLock.unlock(); + GamePause(self); +} +MAKE_HOOK_MATCH(GameResume, &PauseController::HandlePauseMenuManagerDidPressContinueButton, void, PauseController* self) { + stManager->statusLock.lock(); + stManager->paused = false; + stManager->statusLock.unlock(); + GameResume(self); +} + +MAKE_HOOK_MATCH(AudioUpdate, &AudioTimeSyncController::Update, void, AudioTimeSyncController* self) { + AudioUpdate(self); + + //float time = CRASH_UNLESS(il2cpp_utils::RunMethodUnsafe(self, "get_songTime")); + //float endTime = CRASH_UNLESS(il2cpp_utils::RunMethodUnsafe(self, "get_songEndTime")); + + float time = self->get_songTime(); + float endTime = self->get_songEndTime(); + + stManager->statusLock.lock(); + stManager->time = (int)time; + stManager->endTime = (int)endTime; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(ScoreController_HandleNoteWasMissed, &ScoreController::HandleNoteWasMissed, void, ScoreController* self, NoteController* note) { + ScoreController_HandleNoteWasMissed(self, note); + stManager->statusLock.lock(); + stManager->missedNotes++; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(ScoreController_HandleNoteWasCut, &ScoreController::HandleNoteWasCut, void, ScoreController* self, NoteController* noteController, ByRef noteCutInfo) +{ + ScoreController_HandleNoteWasCut(self, noteController, noteCutInfo); + stManager->statusLock.lock(); + if (noteCutInfo.heldRef.get_allIsOK()) stManager->goodCuts++; + else stManager->badCuts++; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(FPSCounter_Update, &FPSCounter::Update, void, FPSCounter* self) { + FPSCounter_Update(self); + + stManager->statusLock.lock(); + stManager->fps = self->get_currentFPS(); + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(PlayerTransforms_Update, &PlayerTransforms::Update, void, PlayerTransforms* self) { + PlayerTransforms_Update(self); + if (!self->dyn__overrideHeadPos()) { + stManager->statusLock.lock(); + if (self->dyn__headTransform()) + stManager->Head = self->dyn__headTransform(); + if (self->dyn__rightHandTransform()) + stManager->VR_Right = self->dyn__rightHandTransform(); + if (self->dyn__leftHandTransform()) + stManager->VR_Left = self->dyn__leftHandTransform(); + stManager->statusLock.unlock(); + } +} + +bool FPSObjectCreated = false; + +std::string GetHeadsetType() { + GlobalNamespace::OVRPlugin::SystemHeadset HeadsetType = GlobalNamespace::OVRPlugin::GetSystemHeadsetType(); + std::string result; + switch (HeadsetType.value) { + case HeadsetType.Oculus_Quest: + return result = "Oculus Quest"; + case 7: + return result = "Oculus Go"; + case HeadsetType.Oculus_Quest_2: + return result = "Oculus Quest 2"; + case 10: + return result = "Oculus Quest 3/2 Pro"; + default: + return result = "Unknown " + std::string(GlobalNamespace::OVRPlugin::get_productName()); + } +} + +MAKE_HOOK_MATCH(SceneManager_ActiveSceneChanged, &UnityEngine::SceneManagement::SceneManager::Internal_ActiveSceneChanged, void, UnityEngine::SceneManagement::Scene previousActiveScene, UnityEngine::SceneManagement::Scene newActiveScene) { + SceneManager_ActiveSceneChanged(previousActiveScene, newActiveScene); + if (newActiveScene.IsValid()) { + std::string sceneName = to_utf8(csstrtostr(newActiveScene.get_name())); + std::string shaderWarmup = "ShaderWarmup"; + std::string EmptyTransition = "EmptyTransition"; + if (sceneName == EmptyTransition) stManager->headsetType = GetHeadsetType(); + else if (sceneName == shaderWarmup) { + auto FPSCObject = UnityEngine::GameObject::New_ctor(il2cpp_utils::newcsstr("FPSC")); + UnityEngine::Object::DontDestroyOnLoad(FPSCObject->AddComponent()); + } + FPSObjectCreated = true; + } +} + +MAKE_HOOK_MATCH(OptionsViewController_DidActivate, &OptionsViewController::DidActivate, void, OptionsViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { + OptionsViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); + stManager->statusLock.lock(); + stManager->location = Options; + stManager->statusLock.unlock(); +} + +MAKE_HOOK_MATCH(MainMenuViewController_DidActivate, &MainMenuViewController::DidActivate, void, MainMenuViewController* self, bool firstActivation, bool addedToHierarchy, bool screenSystemEnabling) { + MainMenuViewController_DidActivate(self, firstActivation, addedToHierarchy, screenSystemEnabling); + stManager->statusLock.lock(); + stManager->location = Menu; + stManager->statusLock.unlock(); +} + +extern "C" void setup(ModInfo& info) { + info.id = ID; + info.version = VERSION; + STModInfo = info; + + getLogger().info("Modloader name: %s tag: %s", Modloader::getInfo().name.c_str(), Modloader::getInfo().tag.c_str()); + + getLogger().info("Completed setup!"); +} + +extern "C" void load() { + getLogger().debug("Installing hooks..."); + il2cpp_functions::Init(); + QuestUI::Init(); + + getModConfig().Init(STModInfo); + + custom_types::Register::AutoRegister(); + QuestUI::Register::RegisterModSettingsViewController(STModInfo); + + // Install our function hooks + LoggerContextObject logger = getLogger().WithContext("Hook"); + INSTALL_HOOK(logger, PlayerTransforms_Update); + INSTALL_HOOK(logger, RefreshContent); + INSTALL_HOOK(logger, SongStart); + INSTALL_HOOK(logger, CampaignLevelStart); + INSTALL_HOOK(logger, RelativeScoreAndImmediateRankCounter_UpdateRelativeScoreAndImmediateRank); + INSTALL_HOOK(logger, ComboController_Start); + INSTALL_HOOK(logger, SongEnd); + INSTALL_HOOK(logger, CampaignLevelEnd); + INSTALL_HOOK(logger, TutorialStart); + INSTALL_HOOK(logger, TutorialEnd); + INSTALL_HOOK(logger, GamePause); + INSTALL_HOOK(logger, GameResume); + INSTALL_HOOK(logger, AudioUpdate); + INSTALL_HOOK(logger, MultiplayerSongStart); + INSTALL_HOOK(logger, MultiplayerJoinLobby); + INSTALL_HOOK(logger, MultiplayerSongFinish); + INSTALL_HOOK(logger, MultiplayerSpectateStart); + INSTALL_HOOK(logger, MultiplayerSpectateDestroy); + INSTALL_HOOK(logger, GameEnergyUIPanel_HandleGameEnergyDidChange); + INSTALL_HOOK(logger, ServerCodeView_RefreshText); + INSTALL_HOOK(logger, ScoreController_HandleNoteWasMissed); + INSTALL_HOOK(logger, ScoreController_HandleNoteWasCut); + INSTALL_HOOK(logger, FPSCounter_Update); + INSTALL_HOOK(logger, SceneManager_ActiveSceneChanged); + INSTALL_HOOK(logger, OptionsViewController_DidActivate); + INSTALL_HOOK(logger, MainMenuViewController_DidActivate); + + + getLogger().debug("Installed all hooks!"); + + stManager = new STManager(getLogger()); + stManager->gameVersion = to_utf8(csstrtostr(UnityEngine::Application::get_version())); + } \ No newline at end of file