From c954ef76cbe4a2bcb75399271d53290525871675 Mon Sep 17 00:00:00 2001 From: Yimura Date: Tue, 17 Aug 2021 16:56:41 +0200 Subject: [PATCH] feat(Handling): Added online profile getting --- BigBaseV2/src/api/api.hpp | 125 +++ BigBaseV2/src/api/http_request.hpp | 761 ++++++++++++++++++ BigBaseV2/src/backend/backend.cpp | 6 + .../src/backend/looped/api/login_session.cpp | 13 + BigBaseV2/src/backend/looped/looped.hpp | 3 + .../src/backend/looped/self/off_radar.cpp | 16 + .../src/backend/looped/vehicle/horn_boost.cpp | 2 +- BigBaseV2/src/core/class/CPlayerInfo.hpp | 24 +- .../gui/window/handling/handling_general.cpp | 7 - .../src/gui/window/handling/handling_tabs.hpp | 6 + .../window/handling/modals/save_handling.cpp | 75 ++ .../src/gui/window/main/tab_settings.cpp | 2 + BigBaseV2/src/gui/window/window_handling.cpp | 143 +++- BigBaseV2/src/services/vehicle_service.cpp | 186 +++++ BigBaseV2/src/services/vehicle_service.hpp | 122 +++ 15 files changed, 1463 insertions(+), 28 deletions(-) create mode 100644 BigBaseV2/src/api/api.hpp create mode 100644 BigBaseV2/src/api/http_request.hpp create mode 100644 BigBaseV2/src/backend/looped/api/login_session.cpp create mode 100644 BigBaseV2/src/gui/window/handling/modals/save_handling.cpp diff --git a/BigBaseV2/src/api/api.hpp b/BigBaseV2/src/api/api.hpp new file mode 100644 index 00000000..5ce4066d --- /dev/null +++ b/BigBaseV2/src/api/api.hpp @@ -0,0 +1,125 @@ +#pragma once +#include "http_request.hpp" + +namespace big::api +{ + const std::string domain = "http://localhost:8080/api/v1"; + inline std::string session_id; + + namespace util + { + static std::string authorization_header() + { + return std::string("Authorization: ") + api::session_id; + } + + static bool parse_body(http::Response& res, nlohmann::json& out) + { + try + { + out = nlohmann::json::parse(res.body.begin(), res.body.end()); + + return out["status"] == std::string("success"); + } + catch (const std::exception& e) + { + out = nullptr; + + LOG(INFO) << "Failed to parse request body: " << std::endl << e.what(); + + return false; + } + } + + static bool signed_in() + { + return !session_id.empty(); + } + } + + namespace auth + { + static bool create_session() + { + if (g_local_player == nullptr) return false; + + const std::string path = "/auth/create_session"; + + http::Request request(domain + path); + + CPlayerInfo* player_info = g_local_player->m_player_info; + nlohmann::json body = { + { "username", std::string(player_info->m_name) }, + { "rockstar_id", player_info->m_rockstar_id2 } + }; + + http::Response res = request.send("POST", body.dump(), { + "Content-Type: application/json" + }); + + nlohmann::json json; + if (util::parse_body(res, json)) + { + session_id = json["data"]["sessionId"].get(); + + LOG(INFO) << "Create session and received ID: " << session_id.c_str(); + + return true; + } + + LOG(INFO) << "Failed to create a session."; + + return false; + } + } + + namespace vehicle + { + namespace handling + { + static bool create_profile(uint32_t handling_hash, const char* name, const char* description, nlohmann::json &handling_data) + { + const std::string path = "/vehicle/handling/create"; + + http::Request request(domain + path); + + nlohmann::json json; + json["handling_hash"] = handling_hash; + json["name"] = std::string(name); + json["description"] = std::string(description); + json["data"] = handling_data; + + http::Response res = request.send("POST", json.dump(), { + util::authorization_header() + }); + return util::parse_body(res, json); + } + + static bool get_by_share_code(std::string share_code, nlohmann::json& out) + { + const std::string path = "/vehicle/handling/get_by_share_code?share_code="; + + http::Request request(domain + path + share_code); + + http::Response res = request.send("GET", "", { + util::authorization_header() + }); + + return util::parse_body(res, out); + } + + static bool get_my_handling(uint32_t handling_hash, nlohmann::json &out) + { + const std::string path = "/vehicle/handling/get_mine?handling_hash="; + + http::Request request(domain + path + std::to_string(handling_hash)); + + http::Response res = request.send("GET", "", { + util::authorization_header() + }); + + return util::parse_body(res, out); + } + } + } +} \ No newline at end of file diff --git a/BigBaseV2/src/api/http_request.hpp b/BigBaseV2/src/api/http_request.hpp new file mode 100644 index 00000000..f5643daf --- /dev/null +++ b/BigBaseV2/src/api/http_request.hpp @@ -0,0 +1,761 @@ +// +// HTTPRequest +// + +#ifndef HTTPREQUEST_HPP +#define HTTPREQUEST_HPP +#pragma comment(lib, "ws2_32.lib") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# pragma push_macro("WIN32_LEAN_AND_MEAN") +# pragma push_macro("NOMINMAX") +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif // WIN32_LEAN_AND_MEAN +# ifndef NOMINMAX +# define NOMINMAX +# endif // NOMINMAX +# include +# if _WIN32_WINNT < _WIN32_WINNT_WINXP +extern "C" char* _strdup(const char* strSource); +# define strdup _strdup +# include +# endif // _WIN32_WINNT < _WIN32_WINNT_WINXP +# include +# pragma pop_macro("WIN32_LEAN_AND_MEAN") +# pragma pop_macro("NOMINMAX") +#else +# include +# include +# include +# include +# include +# include +# include +#endif // _WIN32 + +namespace http +{ + class RequestError final : public std::logic_error + { + public: + explicit RequestError(const char* str) : std::logic_error{ str } {} + explicit RequestError(const std::string& str) : std::logic_error{ str } {} + }; + + class ResponseError final : public std::runtime_error + { + public: + explicit ResponseError(const char* str) : std::runtime_error{ str } {} + explicit ResponseError(const std::string& str) : std::runtime_error{ str } {} + }; + + enum class InternetProtocol : std::uint8_t + { + V4, + V6 + }; + + inline namespace detail + { +#ifdef _WIN32 + class WinSock final + { + public: + WinSock() + { + WSADATA wsaData; + const auto error = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (error != 0) + throw std::system_error(error, std::system_category(), "WSAStartup failed"); + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) + { + WSACleanup(); + throw std::runtime_error("Invalid WinSock version"); + } + + started = true; + } + + ~WinSock() + { + if (started) WSACleanup(); + } + + WinSock(WinSock&& other) noexcept : + started{ other.started } + { + other.started = false; + } + + WinSock& operator=(WinSock&& other) noexcept + { + if (&other == this) return *this; + if (started) WSACleanup(); + started = other.started; + other.started = false; + return *this; + } + + private: + bool started = false; + }; +#endif // _WIN32 + + inline int getLastError() noexcept + { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif // _WIN32 + } + + constexpr int getAddressFamily(InternetProtocol internetProtocol) + { + return (internetProtocol == InternetProtocol::V4) ? AF_INET : + (internetProtocol == InternetProtocol::V6) ? AF_INET6 : + throw RequestError("Unsupported protocol"); + } + + class Socket final + { + public: +#ifdef _WIN32 + using Type = SOCKET; + static constexpr Type invalid = INVALID_SOCKET; +#else + using Type = int; + static constexpr Type invalid = -1; +#endif // _WIN32 + + explicit Socket(InternetProtocol internetProtocol) : + endpoint{ socket(getAddressFamily(internetProtocol), SOCK_STREAM, IPPROTO_TCP) } + { + if (endpoint == invalid) + throw std::system_error(getLastError(), std::system_category(), "Failed to create socket"); + +#ifdef _WIN32 + unsigned long mode = 1; + if (ioctlsocket(endpoint, FIONBIO, &mode) != 0) + { + close(); + throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to get socket flags"); + } +#else + const auto flags = fcntl(endpoint, F_GETFL); + if (flags == -1) + { + close(); + throw std::system_error(errno, std::system_category(), "Failed to get socket flags"); + } + + if (fcntl(endpoint, F_SETFL, flags | O_NONBLOCK) == -1) + { + close(); + throw std::system_error(errno, std::system_category(), "Failed to set socket flags"); + } +#endif // _WIN32 + +#ifdef __APPLE__ + const int value = 1; + if (setsockopt(endpoint, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)) == -1) + { + close(); + throw std::system_error(errno, std::system_category(), "Failed to set socket option"); + } +#endif // __APPLE__ + } + + ~Socket() + { + if (endpoint != invalid) close(); + } + + Socket(Socket&& other) noexcept : + endpoint{ other.endpoint } + { + other.endpoint = invalid; + } + + Socket& operator=(Socket&& other) noexcept + { + if (&other == this) return *this; + if (endpoint != invalid) close(); + endpoint = other.endpoint; + other.endpoint = invalid; + return *this; + } + + void connect(const struct sockaddr* address, const socklen_t addressSize, const std::int64_t timeout) + { +#ifdef _WIN32 + auto result = ::connect(endpoint, address, addressSize); + while (result == -1 && WSAGetLastError() == WSAEINTR) + result = ::connect(endpoint, address, addressSize); + + if (result == -1) + { + if (WSAGetLastError() == WSAEWOULDBLOCK) + { + select(SelectType::write, timeout); + + char socketErrorPointer[sizeof(int)]; + socklen_t optionLength = sizeof(socketErrorPointer); + if (getsockopt(endpoint, SOL_SOCKET, SO_ERROR, socketErrorPointer, &optionLength) == -1) + throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to get socket option"); + + int socketError; + std::memcpy(&socketError, socketErrorPointer, sizeof(socketErrorPointer)); + + if (socketError != 0) + throw std::system_error(socketError, std::system_category(), "Failed to connect"); + } + else + throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to connect"); + } +#else + auto result = ::connect(endpoint, address, addressSize); + while (result == -1 && errno == EINTR) + result = ::connect(endpoint, address, addressSize); + + if (result == -1) + { + if (errno == EINPROGRESS) + { + select(SelectType::write, timeout); + + int socketError; + socklen_t optionLength = sizeof(socketError); + if (getsockopt(endpoint, SOL_SOCKET, SO_ERROR, &socketError, &optionLength) == -1) + throw std::system_error(errno, std::system_category(), "Failed to get socket option"); + + if (socketError != 0) + throw std::system_error(socketError, std::system_category(), "Failed to connect"); + } + else + throw std::system_error(errno, std::system_category(), "Failed to connect"); + } +#endif // _WIN32 + } + + std::size_t send(const void* buffer, const std::size_t length, const std::int64_t timeout) + { + select(SelectType::write, timeout); +#ifdef _WIN32 + auto result = ::send(endpoint, reinterpret_cast(buffer), + static_cast(length), 0); + + while (result == -1 && WSAGetLastError() == WSAEINTR) + result = ::send(endpoint, reinterpret_cast(buffer), + static_cast(length), 0); + + if (result == -1) + throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to send data"); +#else + auto result = ::send(endpoint, reinterpret_cast(buffer), + length, noSignal); + + while (result == -1 && errno == EINTR) + result = ::send(endpoint, reinterpret_cast(buffer), + length, noSignal); + + if (result == -1) + throw std::system_error(errno, std::system_category(), "Failed to send data"); +#endif // _WIN32 + return static_cast(result); + } + + std::size_t recv(void* buffer, const std::size_t length, const std::int64_t timeout) + { + select(SelectType::read, timeout); +#ifdef _WIN32 + auto result = ::recv(endpoint, reinterpret_cast(buffer), + static_cast(length), 0); + + while (result == -1 && WSAGetLastError() == WSAEINTR) + result = ::recv(endpoint, reinterpret_cast(buffer), + static_cast(length), 0); + + if (result == -1) + throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to read data"); +#else + auto result = ::recv(endpoint, reinterpret_cast(buffer), + length, noSignal); + + while (result == -1 && errno == EINTR) + result = ::recv(endpoint, reinterpret_cast(buffer), + length, noSignal); + + if (result == -1) + throw std::system_error(errno, std::system_category(), "Failed to read data"); +#endif // _WIN32 + return static_cast(result); + } + + operator Type() const noexcept { return endpoint; } + + private: + enum class SelectType + { + read, + write + }; + + void select(const SelectType type, const std::int64_t timeout) + { + fd_set descriptorSet; + FD_ZERO(&descriptorSet); + FD_SET(endpoint, &descriptorSet); + + timeval selectTimeout{ + static_cast(timeout / 1000), + static_cast((timeout % 1000) * 1000) + }; +#ifdef _WIN32 + auto count = ::select(0, + (type == SelectType::read) ? &descriptorSet : nullptr, + (type == SelectType::write) ? &descriptorSet : nullptr, + nullptr, + (timeout >= 0) ? &selectTimeout : nullptr); + + while (count == -1 && WSAGetLastError() == WSAEINTR) + count = ::select(0, + (type == SelectType::read) ? &descriptorSet : nullptr, + (type == SelectType::write) ? &descriptorSet : nullptr, + nullptr, + (timeout >= 0) ? &selectTimeout : nullptr); + + if (count == -1) + throw std::system_error(WSAGetLastError(), std::system_category(), "Failed to select socket"); + else if (count == 0) + throw ResponseError("Request timed out"); +#else + auto count = ::select(endpoint + 1, + (type == SelectType::read) ? &descriptorSet : nullptr, + (type == SelectType::write) ? &descriptorSet : nullptr, + nullptr, + (timeout >= 0) ? &selectTimeout : nullptr); + + while (count == -1 && errno == EINTR) + count = ::select(endpoint + 1, + (type == SelectType::read) ? &descriptorSet : nullptr, + (type == SelectType::write) ? &descriptorSet : nullptr, + nullptr, + (timeout >= 0) ? &selectTimeout : nullptr); + + if (count == -1) + throw std::system_error(errno, std::system_category(), "Failed to select socket"); + else if (count == 0) + throw ResponseError("Request timed out"); +#endif // _WIN32 + } + + void close() noexcept + { +#ifdef _WIN32 + closesocket(endpoint); +#else + ::close(endpoint); +#endif // _WIN32 + } + +#if defined(__unix__) && !defined(__APPLE__) + static constexpr int noSignal = MSG_NOSIGNAL; +#else + static constexpr int noSignal = 0; +#endif // defined(__unix__) && !defined(__APPLE__) + + Type endpoint = invalid; + }; + } + + struct Response final + { + enum Status + { + Continue = 100, + SwitchingProtocol = 101, + Processing = 102, + EarlyHints = 103, + + Ok = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultiStatus = 207, + AlreadyReported = 208, + ImUsed = 226, + + MultipleChoice = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + TemporaryRedirect = 307, + PermanentRedirect = 308, + + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + PayloadTooLarge = 413, + UriTooLong = 414, + UnsupportedMediaType = 415, + RangeNotSatisfiable = 416, + ExpectationFailed = 417, + MisdirectedRequest = 421, + UnprocessableEntity = 422, + Locked = 423, + FailedDependency = 424, + TooEarly = 425, + UpgradeRequired = 426, + PreconditionRequired = 428, + TooManyRequests = 429, + RequestHeaderFieldsTooLarge = 431, + UnavailableForLegalReasons = 451, + + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HttpVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + InsufficientStorage = 507, + LoopDetected = 508, + NotExtended = 510, + NetworkAuthenticationRequired = 511 + }; + + int status = 0; + std::string description; + std::vector headers; + std::vector body; + }; + + class Request final + { + public: + explicit Request(const std::string& url, + const InternetProtocol protocol = InternetProtocol::V4) : + internetProtocol{ protocol } + { + const auto schemeEndPosition = url.find("://"); + + if (schemeEndPosition != std::string::npos) + { + scheme = url.substr(0, schemeEndPosition); + path = url.substr(schemeEndPosition + 3); + } + else + { + scheme = "http"; + path = url; + } + + const auto fragmentPosition = path.find('#'); + + // remove the fragment part + if (fragmentPosition != std::string::npos) + path.resize(fragmentPosition); + + const auto pathPosition = path.find('/'); + + if (pathPosition == std::string::npos) + { + domain = path; + path = "/"; + } + else + { + domain = path.substr(0, pathPosition); + path = path.substr(pathPosition); + } + + const auto portPosition = domain.find(':'); + + if (portPosition != std::string::npos) + { + port = domain.substr(portPosition + 1); + domain.resize(portPosition); + } + else + port = "80"; + } + + Response send(const std::string& method = "GET", + const std::string& body = "", + const std::vector& headers = {}, + const std::chrono::milliseconds timeout = std::chrono::milliseconds{ -1 }) + { + return send(method, + std::vector(body.begin(), body.end()), + headers, + timeout); + } + + Response send(const std::string& method, + const std::vector& body, + const std::vector& headers, + const std::chrono::milliseconds timeout = std::chrono::milliseconds{ -1 }) + { + const auto stopTime = std::chrono::steady_clock::now() + timeout; + + if (scheme != "http") + throw RequestError("Only HTTP scheme is supported"); + + addrinfo hints = {}; + hints.ai_family = getAddressFamily(internetProtocol); + hints.ai_socktype = SOCK_STREAM; + + addrinfo* info; + if (getaddrinfo(domain.c_str(), port.c_str(), &hints, &info) != 0) + throw std::system_error(getLastError(), std::system_category(), "Failed to get address info of " + domain); + + std::unique_ptr addressInfo(info, freeaddrinfo); + + // RFC 7230, 3.1.1. Request Line + std::string headerData = method + " " + path + " HTTP/1.1\r\n"; + + for (const auto& header : headers) + headerData += header + "\r\n"; + + // RFC 7230, 3.2. Header Fields + headerData += "Host: " + domain + "\r\n" + "Content-Length: " + std::to_string(body.size()) + "\r\n" + "\r\n"; + + std::vector requestData(headerData.begin(), headerData.end()); + requestData.insert(requestData.end(), body.begin(), body.end()); + + Socket socket(internetProtocol); + + // take the first address from the list + socket.connect(addressInfo->ai_addr, static_cast(addressInfo->ai_addrlen), + (timeout.count() >= 0) ? getRemainingMilliseconds(stopTime) : -1); + + auto remaining = requestData.size(); + auto sendData = requestData.data(); + + // send the request + while (remaining > 0) + { + const auto size = socket.send(sendData, remaining, + (timeout.count() >= 0) ? getRemainingMilliseconds(stopTime) : -1); + remaining -= size; + sendData += size; + } + + std::array tempBuffer; + constexpr std::array crlf = { '\r', '\n' }; + Response response; + std::vector responseData; + enum class State + { + statusLine, + headers, + body + } state = State::statusLine; + bool contentLengthReceived = false; + std::size_t contentLength = 0; + bool chunkedResponse = false; + std::size_t expectedChunkSize = 0; + bool removeCrlfAfterChunk = false; + + // read the response + for (;;) + { + const auto size = socket.recv(tempBuffer.data(), tempBuffer.size(), + (timeout.count() >= 0) ? getRemainingMilliseconds(stopTime) : -1); + if (size == 0) // disconnected + return response; + + responseData.insert(responseData.end(), tempBuffer.begin(), tempBuffer.begin() + size); + + if (state != State::body) + for (;;) + { + // RFC 7230, 3. Message Format + const auto i = std::search(responseData.begin(), responseData.end(), crlf.begin(), crlf.end()); + + // didn't find a newline + if (i == responseData.end()) break; + + const std::string line(responseData.begin(), i); + responseData.erase(responseData.begin(), i + 2); + + // empty line indicates the end of the header section + if (line.empty()) + { + state = State::body; + break; + } + else if (state == State::statusLine) // RFC 7230, 3.1.2. Status Line + { + state = State::headers; + std::size_t partNum = 0; + + // tokenize the status line + for (auto beginIterator = line.begin(); beginIterator != line.end();) + { + const auto endIterator = std::find(beginIterator, line.end(), ' '); + const std::string part{ beginIterator, endIterator }; + + switch (++partNum) + { + case 2: response.status = std::stoi(part); break; + case 3: response.description = part; break; + } + + if (endIterator == line.end()) break; + beginIterator = endIterator + 1; + } + } + else if (state == State::headers) // RFC 7230, 3.2. Header Fields + { + response.headers.push_back(line); + + const auto loumnPosition = line.find(':'); + + if (loumnPosition == std::string::npos) + throw ResponseError("Invalid header: " + line); + + const auto headerName = line.substr(0, loumnPosition); + auto headerValue = line.substr(loumnPosition + 1); + + // RFC 7230, Appendix B. Collected ABNF + auto isNotWhiteSpace = [](char c) { + return c != ' ' && c != '\t'; + }; + + // ltrim + headerValue.erase(headerValue.begin(), std::find_if(headerValue.begin(), headerValue.end(), isNotWhiteSpace)); + + // rtrim + headerValue.erase(std::find_if(headerValue.rbegin(), headerValue.rend(), isNotWhiteSpace).base(), headerValue.end()); + + if (headerName == "Content-Length") + { + contentLength = std::stoul(headerValue); + contentLengthReceived = true; + response.body.reserve(contentLength); + } + else if (headerName == "Transfer-Encoding") + { + if (headerValue == "chunked") + chunkedResponse = true; + else + throw ResponseError("Unsupported transfer encoding: " + headerValue); + } + } + } + + if (state == State::body) + { + // Content-Length must be ignored if Transfer-Encoding is received + if (chunkedResponse) + { + for (;;) + { + if (expectedChunkSize > 0) + { + const auto toWrite = (std::min)(expectedChunkSize, responseData.size()); + response.body.insert(response.body.end(), responseData.begin(), responseData.begin() + static_cast(toWrite)); + responseData.erase(responseData.begin(), responseData.begin() + static_cast(toWrite)); + expectedChunkSize -= toWrite; + + if (expectedChunkSize == 0) removeCrlfAfterChunk = true; + if (responseData.empty()) break; + } + else + { + if (removeCrlfAfterChunk) + { + if (responseData.size() < 2) break; + + if (!std::equal(crlf.begin(), crlf.end(), responseData.begin())) + throw ResponseError("Invalid chunk"); + + removeCrlfAfterChunk = false; + responseData.erase(responseData.begin(), responseData.begin() + 2); + } + + const auto i = std::search(responseData.begin(), responseData.end(), crlf.begin(), crlf.end()); + + if (i == responseData.end()) break; + + const std::string line(responseData.begin(), i); + responseData.erase(responseData.begin(), i + 2); + + expectedChunkSize = std::stoul(line, nullptr, 16); + if (expectedChunkSize == 0) + return response; + } + } + } + else + { + response.body.insert(response.body.end(), responseData.begin(), responseData.end()); + responseData.clear(); + + // got the whole content + if (contentLengthReceived && response.body.size() >= contentLength) + return response; + } + } + } + + return response; + } + + private: + static std::int64_t getRemainingMilliseconds(const std::chrono::steady_clock::time_point time) + { + const auto now = std::chrono::steady_clock::now(); + const auto remainingTime = std::chrono::duration_cast(time - now); + return (remainingTime.count() > 0) ? remainingTime.count() : 0; + } + +#ifdef _WIN32 + WinSock winSock; +#endif // _WIN32 + InternetProtocol internetProtocol; + std::string scheme; + std::string domain; + std::string port; + std::string path; + }; +} + +#endif // HTTPREQUEST_HPP \ No newline at end of file diff --git a/BigBaseV2/src/backend/backend.cpp b/BigBaseV2/src/backend/backend.cpp index 0bafcddd..29a08966 100644 --- a/BigBaseV2/src/backend/backend.cpp +++ b/BigBaseV2/src/backend/backend.cpp @@ -17,6 +17,11 @@ namespace big looped::system_update_pointers(); }QUEUE_JOB_END_CLAUSE + g_fiber_pool->queue_job([] + { + looped::api_login_session(); + }); + QUEUE_JOB_BEGIN_CLAUSE() { looped::protections_replay_interface(); @@ -59,6 +64,7 @@ namespace big QUEUE_JOB_BEGIN_CLAUSE() { + looped::vehicle_god_mode(); looped::vehicle_horn_boost(); looped::vehicle_speedo_meter(); }QUEUE_JOB_END_CLAUSE diff --git a/BigBaseV2/src/backend/looped/api/login_session.cpp b/BigBaseV2/src/backend/looped/api/login_session.cpp new file mode 100644 index 00000000..d799975f --- /dev/null +++ b/BigBaseV2/src/backend/looped/api/login_session.cpp @@ -0,0 +1,13 @@ +#include "api/api.hpp" +#include "backend/looped/looped.hpp" + +namespace big +{ + void looped::api_login_session() + { + if (g_local_player == nullptr || api::util::signed_in()) + return; + + api::auth::create_session(); + } +} \ No newline at end of file diff --git a/BigBaseV2/src/backend/looped/looped.hpp b/BigBaseV2/src/backend/looped/looped.hpp index 7674f930..fc7bd63e 100644 --- a/BigBaseV2/src/backend/looped/looped.hpp +++ b/BigBaseV2/src/backend/looped/looped.hpp @@ -5,6 +5,8 @@ namespace big { class looped { public: + static void api_login_session(); + static void tunables_disable_phone(); static void tunables_no_idle_kick(); @@ -30,6 +32,7 @@ namespace big static void weapons_steal_vehicle_gun(); static void weapons_vehicle_gun(); + static void vehicle_god_mode(); static void vehicle_horn_boost(); static void vehicle_speedo_meter(); }; diff --git a/BigBaseV2/src/backend/looped/self/off_radar.cpp b/BigBaseV2/src/backend/looped/self/off_radar.cpp index e0927cc2..c1b373b9 100644 --- a/BigBaseV2/src/backend/looped/self/off_radar.cpp +++ b/BigBaseV2/src/backend/looped/self/off_radar.cpp @@ -1,4 +1,5 @@ #include "backend/looped/looped.hpp" +#include "pointers.hpp" #include "natives.hpp" #include "script_global.hpp" @@ -8,6 +9,21 @@ namespace big { if (g.self.off_radar) { + /*Player playerId = PLAYER::PLAYER_ID(); + + int off_radar[] = { + (int)RemoteEvent::RemoteOffradar, + playerId, + NETWORK::GET_NETWORK_TIME(), + 0, + 0, + 0, + *script_global(1630816).at(playerId, 597).at(508).as() + }; + + g_pointers->m_trigger_script_event(1, off_radar, 7, 1 << playerId);*/ + //SCRIPT::TRIGGER_SCRIPT_EVENT(1, off_radar, 7, 1 << playerId); + *script_global(2426865).at(PLAYER::PLAYER_ID(), 449).at(209).as() = true; *script_global(2441237).at(70).as() = NETWORK::GET_NETWORK_TIME() + 1; } diff --git a/BigBaseV2/src/backend/looped/vehicle/horn_boost.cpp b/BigBaseV2/src/backend/looped/vehicle/horn_boost.cpp index 650148a2..332534e4 100644 --- a/BigBaseV2/src/backend/looped/vehicle/horn_boost.cpp +++ b/BigBaseV2/src/backend/looped/vehicle/horn_boost.cpp @@ -9,7 +9,7 @@ namespace big { if (!g.vehicle.horn_boost) return; - if (PAD::IS_DISABLED_CONTROL_PRESSED(0, (int)ControllerInputs::INPUT_VEH_HORN)) + if (PAD::IS_CONTROL_PRESSED(0, (int)ControllerInputs::INPUT_VEH_HORN)) { Vehicle veh = PED::GET_VEHICLE_PED_IS_IN(PLAYER::PLAYER_PED_ID(), false); diff --git a/BigBaseV2/src/core/class/CPlayerInfo.hpp b/BigBaseV2/src/core/class/CPlayerInfo.hpp index d327332d..239c0519 100644 --- a/BigBaseV2/src/core/class/CPlayerInfo.hpp +++ b/BigBaseV2/src/core/class/CPlayerInfo.hpp @@ -5,16 +5,20 @@ class CPlayerInfo { public: - char pad_0000[52]; //0x0000 - uint32_t m_internal_ip; //0x0034 - uint16_t m_internal_port; //0x0038 - char pad_003A[2]; //0x003A - uint32_t m_relay_ip; //0x003C - uint16_t m_relay_port; //0x0040 - char pad_0042[2]; //0x0042 - uint32_t m_external_ip; //0x0044 - uint16_t m_external_port; //0x0048 - char pad_004A[90]; //0x004A + char pad_0000[40]; //0x0000 + uint64_t m_rockstar_id; //0x0028 + char pad_0030[8]; //0x0030 + uint32_t m_internal_ip; //0x0038 + uint16_t m_internal_port; //0x003C + char pad_003E[2]; //0x003E + uint32_t m_relay_ip; //0x0040 + uint16_t m_relay_port; //0x0044 + char pad_0046[2]; //0x0046 + uint32_t m_external_ip; //0x0048 + uint16_t m_external_port; //0x004C + char pad_004E[66]; //0x004E + uint64_t m_rockstar_id2; //0x0090 + char pad_0098[12]; //0x0098 char m_name[20]; //0x00A4 char pad_00B8[184]; //0x00B8 float m_swim_speed; //0x0170 diff --git a/BigBaseV2/src/gui/window/handling/handling_general.cpp b/BigBaseV2/src/gui/window/handling/handling_general.cpp index fb7c2e39..cbcdcc5a 100644 --- a/BigBaseV2/src/gui/window/handling/handling_general.cpp +++ b/BigBaseV2/src/gui/window/handling/handling_general.cpp @@ -6,13 +6,6 @@ namespace big { if (ImGui::BeginTabItem("General")) { - if (ImGui::Button("Restore Handling Data")) - { - g_vehicle_service->restore_vehicle(); - } - - ImGui::Separator(); - ImGui::Text("Gravity"); ImGui::SliderFloat("##Gravity", &g_local_player->m_vehicle->m_gravity, -50.f, 50.f); diff --git a/BigBaseV2/src/gui/window/handling/handling_tabs.hpp b/BigBaseV2/src/gui/window/handling/handling_tabs.hpp index 46be770c..f766ae57 100644 --- a/BigBaseV2/src/gui/window/handling/handling_tabs.hpp +++ b/BigBaseV2/src/gui/window/handling/handling_tabs.hpp @@ -18,4 +18,10 @@ namespace big static void tab_traction(); static void tab_transmission(); }; + + class modal_handling + { + public: + static void modal_save_handling(); + }; } \ No newline at end of file diff --git a/BigBaseV2/src/gui/window/handling/modals/save_handling.cpp b/BigBaseV2/src/gui/window/handling/modals/save_handling.cpp new file mode 100644 index 00000000..32712a55 --- /dev/null +++ b/BigBaseV2/src/gui/window/handling/modals/save_handling.cpp @@ -0,0 +1,75 @@ +#include "../handling_tabs.hpp" +#include "fiber_pool.hpp" +#include "natives.hpp" +#include "script.hpp" + +namespace big +{ + void modal_handling::modal_save_handling() + { + ImGui::SetNextWindowSize({ 520, 325 }, ImGuiCond_Always); + if (ImGui::BeginPopupModal("Save Handling")) + { + static char name[32], description[256] = "No description."; + + switch (g_vehicle_service->publish_status()) + { + case PublishStatus::SAVING: + ImGui::Text("Saving..."); + + return ImGui::EndPopup(); + case PublishStatus::SAVED: + strcpy(name, ""); + strcpy(description, ""); + + g_vehicle_service->publish_status(PublishStatus::IDLE); + g_vehicle_service->update_mine(true); + + ImGui::CloseCurrentPopup(); + + return ImGui::EndPopup(); + case PublishStatus::FAILED: + ImGui::TextColored({ 255, 0, 0, 1 }, "Failed to save handling profile."); + + ImGui::Separator(); + } + + QUEUE_JOB_BEGIN_CLAUSE() + { + PAD::DISABLE_ALL_CONTROL_ACTIONS(0); + }QUEUE_JOB_END_CLAUSE + + ImGui::BeginGroup(); + + ImGui::Text("Name:"); + ImGui::Text("Description:"); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + + ImGui::InputText("##modal_handling_name", name, sizeof(name)); + ImGui::InputTextMultiline("##modal_handling_description", description, sizeof(description)); + + ImGui::EndGroup(); + + if (ImGui::Button("Cancel")) + { + strcpy(name, ""); + strcpy(description, "No description."); + + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Save")) + { + g_fiber_pool->queue_job([&] + { + g_vehicle_service->publish_profile(name, description); + }); + } + + ImGui::EndPopup(); + } + } +} \ No newline at end of file diff --git a/BigBaseV2/src/gui/window/main/tab_settings.cpp b/BigBaseV2/src/gui/window/main/tab_settings.cpp index 7376586e..78561c98 100644 --- a/BigBaseV2/src/gui/window/main/tab_settings.cpp +++ b/BigBaseV2/src/gui/window/main/tab_settings.cpp @@ -1,3 +1,5 @@ +//#include "api/api.hpp" +#include "fiber_pool.hpp" #include "main_tabs.hpp" #include "util/system.hpp" diff --git a/BigBaseV2/src/gui/window/window_handling.cpp b/BigBaseV2/src/gui/window/window_handling.cpp index ad4b0f10..480082f7 100644 --- a/BigBaseV2/src/gui/window/window_handling.cpp +++ b/BigBaseV2/src/gui/window/window_handling.cpp @@ -1,3 +1,4 @@ +#include "fiber_pool.hpp" #include "gui/window.hpp" #include "handling/handling_tabs.hpp" #include "imgui.h" @@ -18,16 +19,138 @@ namespace big } g_vehicle_service->attempt_save(); - ImGui::BeginTabBar("handling_tabbar"); - tab_handling::tab_general(); - tab_handling::tab_other(); - tab_handling::tab_brakes(); - tab_handling::tab_gearing(); - tab_handling::tab_traction(); - tab_handling::tab_steering(); - tab_handling::tab_suspension(); - tab_handling::tab_rollbars(); - tab_handling::tab_roll_centre_height(); + ImGui::BeginTabBar("handling_profiles"); + if (ImGui::BeginTabItem("Current Profile")) + { + if (ImGui::Button("Save Profile")) + { + ImGui::OpenPopup("Save Handling"); + } + + modal_handling::modal_save_handling(); + ImGui::SameLine(); + if (ImGui::Button("Restore Handling")) + { + g_vehicle_service->restore_vehicle(); + } + + ImGui::Separator(); + + ImGui::BeginTabBar("handling_tabbar"); + tab_handling::tab_general(); + tab_handling::tab_other(); + tab_handling::tab_brakes(); + tab_handling::tab_gearing(); + tab_handling::tab_traction(); + tab_handling::tab_steering(); + tab_handling::tab_suspension(); + tab_handling::tab_rollbars(); + tab_handling::tab_roll_centre_height(); + ImGui::EndTabBar(); + + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("My Profiles")) + { + if (!g_vehicle_service->update_mine()) + ImGui::Text("Loading profiles..."); + else + { + if (g_vehicle_service->m_my_profiles.size() == 0) + ImGui::Text("You have no profiles available for this vehicle."); + for (auto &key : g_vehicle_service->m_my_profiles) + { + if (auto it = g_vehicle_service->m_handling_profiles.find(key); it != g_vehicle_service->m_handling_profiles.end()) + { + auto& profile = it->second; + + ImGui::BeginGroup(); + + ImGui::Text("Name:"); + ImGui::Text("Description:"); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + + ImGui::Text(profile.name.c_str()); + ImGui::TextWrapped(profile.description.c_str()); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + + ImGui::Text("Share Code: %s", profile.share_code.c_str()); + if (ImGui::Button("Load Profile")) + { + *g_local_player->m_vehicle->m_handling = profile.data; + } + + ImGui::EndGroup(); + + ImGui::Separator(); + } + } + } + + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Search")) + { + static char search[13]; + ImGui::InputTextWithHint("##search_share_code", "Search by share code", search, sizeof(search)); + ImGui::SameLine(); + if (ImGui::Button("Search")) + g_fiber_pool->queue_job([&] { g_vehicle_service->get_by_share_code(search); }); + + switch (g_vehicle_service->m_search_status) + { + case SearchStatus::SEARCHING: + ImGui::Text("Searching..."); + + break; + case SearchStatus::NO_RESULT: + ImGui::Text("No results found for %s", search); + + break; + case SearchStatus::FAILED: + ImGui::Text("Search failed, host is down or response body is invalid..."); + + break; + case SearchStatus::FOUND: + if (auto it = g_vehicle_service->m_handling_profiles.find(search); it != g_vehicle_service->m_handling_profiles.end()) + { + auto& profile = it->second; + ImGui::BeginGroup(); + + ImGui::Text("Name:"); + ImGui::Text("Description:"); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + + ImGui::Text(profile.name.c_str()); + ImGui::TextWrapped(profile.description.c_str()); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + + ImGui::Text("Share Code: %s", profile.share_code.c_str()); + if (ImGui::Button("Load Profile")) + { + *g_local_player->m_vehicle->m_handling = profile.data; + } + + ImGui::EndGroup(); + } + + break; + } + + ImGui::EndTabItem(); + } ImGui::EndTabBar(); ImGui::End(); diff --git a/BigBaseV2/src/services/vehicle_service.cpp b/BigBaseV2/src/services/vehicle_service.cpp index 00c42d45..a913471b 100644 --- a/BigBaseV2/src/services/vehicle_service.cpp +++ b/BigBaseV2/src/services/vehicle_service.cpp @@ -1,3 +1,5 @@ +#include "api/api.hpp" +#include "fiber_pool.hpp" #include "vehicle_service.hpp" namespace big @@ -12,6 +14,17 @@ namespace big g_vehicle_service = nullptr; } + bool vehicle_service::apply_from_cache(std::string id) + { + if (auto it = m_handling_profiles.find(id); it != m_handling_profiles.end()) + { + *g_local_player->m_vehicle->m_handling = it->second.data; + + return true; + } + return false; + } + int vehicle_service::attempt_save() { if (g_local_player == nullptr || g_local_player->m_in_vehicle == 0x10 || g_local_player->m_vehicle == nullptr) @@ -26,6 +39,134 @@ namespace big return 1; } + bool vehicle_service::get_by_share_code(const char* share_code) + { + static std::string up_to_date = ""; + + if (m_search_status == SearchStatus::SEARCHING) + return false; + + if (g_local_player == nullptr || g_local_player->m_vehicle == nullptr) + return false; + + if (up_to_date == share_code) + return true; + m_search_status = SearchStatus::SEARCHING; + + nlohmann::json json; + if (api::vehicle::handling::get_by_share_code(std::string(share_code), json)) + { + if (json["data"].is_null()) + m_search_status = SearchStatus::NO_RESULT; + else + { + auto& data = json["data"]; + + HandlingProfile profile = HandlingProfile(data, g_local_player->m_vehicle->m_handling); + + if (auto it = m_handling_profiles.find(data["share_code"]); it != m_handling_profiles.end()) + it->second = profile; + else m_handling_profiles.emplace(data["share_code"], profile); + + up_to_date = data["share_code"]; + m_search_status = SearchStatus::FOUND; + } + } + else m_search_status = SearchStatus::FAILED; + + return true; + } + + bool vehicle_service::publish_profile(const char* name, const char* description) + { + if (this->m_publish_status == PublishStatus::SAVED) + return true; + if (this->m_publish_status == PublishStatus::SAVING) + return false; + + if (g_local_player == nullptr || g_local_player->m_vehicle == nullptr) return false; + + this->m_publish_status = PublishStatus::SAVING; + + CHandlingData handling_data = *g_local_player->m_vehicle->m_handling; + uint32_t hash = handling_data.m_model_hash; + + nlohmann::json data = { + { "centre_of_mass", + { + { "x", handling_data.m_centre_of_mass.x }, + { "y", handling_data.m_centre_of_mass.y }, + { "z", handling_data.m_centre_of_mass.z } + } + }, + { "inertia_mult", + { + { "x", handling_data.m_inertia_mult.x }, + { "y", handling_data.m_inertia_mult.y }, + { "z", handling_data.m_inertia_mult.z } + } + }, + { "mass", handling_data.m_mass }, + { "downforce_mult", handling_data.m_downforce_multiplier }, + { "buoyancy", handling_data.m_buoyancy }, + { "drive_bias_rear", handling_data.m_drive_bias_rear }, + { "drive_bias_front", handling_data.m_drive_bias_front }, + { "acceleration_mult", handling_data.m_acceleration }, + { "initial_drive_gears", handling_data.m_initial_drive_gears }, + { "upshift", handling_data.m_upshift }, + { "downshift", handling_data.m_downshift }, + { "drive_inertia", handling_data.m_drive_inertia }, + { "initial_drive_force", handling_data.m_initial_drive_force }, + { "drive_max_flat_vel", handling_data.m_drive_max_flat_velocity }, + { "brake_force", handling_data.m_brake_force }, + { "brake_bias_front", handling_data.m_brake_bias_front }, + { "brake_bias_rear", handling_data.m_brake_bias_rear }, + { "handbrake_force", handling_data.m_handbrake_force }, + { "steering_lock", handling_data.m_steering_lock }, + { "steering_lock_ratio", handling_data.m_steering_lock_ratio }, + { "traction_curve_max", handling_data.m_traction_curve_max }, + { "traction_curve_lateral", handling_data.m_traction_curve_lateral }, + { "traction_curve_min", handling_data.m_traction_curve_min }, + { "traction_curve_ratio", handling_data.m_traction_curve_ratio }, + { "traction_bias_front", handling_data.m_traction_bias_front }, + { "traction_bias_rear", handling_data.m_traction_bias_rear }, + { "traction_loss_mult", handling_data.m_traction_loss_mult }, + { "curve_lateral", handling_data.m_curve_lateral }, + { "curve_lateral_ratio", handling_data.m_curve_lateral_ratio }, + { "traction_spring_delta_max", handling_data.m_traction_spring_delta_max }, + { "traction_spring_delta_max_ratio", handling_data.m_traction_spring_delta_max_ratio }, + { "low_speed_traction_loss_mult", handling_data.m_low_speed_traction_loss_mult }, + { "camber_stiffness", handling_data.m_camber_stiffness }, + { "suspension_force", handling_data.m_suspension_force }, + { "suspension_comp_damp", handling_data.m_suspension_comp_damp }, + { "suspension_rebound_damp", handling_data.m_suspension_rebound_damp }, + { "suspension_upper_limit", handling_data.m_suspension_upper_limit }, + { "suspension_lower_limit", handling_data.m_suspension_lower_limit }, + { "suspension_raise", handling_data.m_suspension_raise }, + { "suspension_bias_front", handling_data.m_suspension_bias_front }, + { "suspension_bias_rear", handling_data.m_suspension_bias_rear }, + { "anti_rollbar_force", handling_data.m_anti_rollbar_force }, + { "anti_rollbar_bias_front", handling_data.m_anti_rollbar_bias_front }, + { "anti_rollbar_bias_rear", handling_data.m_anti_rollbar_bias_rear }, + { "roll_centre_height_front", handling_data.m_roll_centre_height_front }, + { "roll_centre_height_rear", handling_data.m_roll_centre_height_rear } + }; + + if (api::vehicle::handling::create_profile(hash, name, description, data)) + m_publish_status = PublishStatus::SAVED; + else m_publish_status = PublishStatus::FAILED; + + return false; + } + + PublishStatus vehicle_service::publish_status(PublishStatus new_status) + { + if (new_status != PublishStatus::NONE) + this->m_publish_status = new_status; + + return this->m_publish_status; + } + bool vehicle_service::restore_vehicle() { if (auto it = m_handling_backup.find(g_local_player->m_vehicle->m_handling->m_model_hash); it != m_handling_backup.end()) @@ -37,4 +178,49 @@ namespace big return false; } + + bool vehicle_service::update_mine(bool force_update) + { + static bool busy = false; + static uint32_t up_to_date = 0; + + if (busy) + return false; + + if (g_local_player == nullptr || g_local_player->m_vehicle == nullptr) + return false; + + if (!force_update && up_to_date == g_local_player->m_vehicle->m_handling->m_model_hash) + return true; + + busy = true; + + g_fiber_pool->queue_job([&] { + nlohmann::json json; + if (!api::vehicle::handling::get_my_handling(g_local_player->m_vehicle->m_handling->m_model_hash, json) || json == nullptr) + { + busy = false; + + return; + } + + m_my_profiles.clear(); + for (auto& el : json["data"]) + { + LOG(INFO) << "Registered profile '" << el["name"].get().c_str() << "' with share code " << el["share_code"].get().c_str(); + + HandlingProfile profile = HandlingProfile(el, g_local_player->m_vehicle->m_handling); + + if (auto it = m_handling_profiles.find(el["share_code"]); it != m_handling_profiles.end()) + it->second = profile; + else m_handling_profiles.emplace(el["share_code"], profile); + m_my_profiles.push_back(el["share_code"]); + } + + busy = false; + up_to_date = g_local_player->m_vehicle->m_handling->m_model_hash; + }); + + return false; + } } \ No newline at end of file diff --git a/BigBaseV2/src/services/vehicle_service.hpp b/BigBaseV2/src/services/vehicle_service.hpp index c3b2aa10..c1335ff2 100644 --- a/BigBaseV2/src/services/vehicle_service.hpp +++ b/BigBaseV2/src/services/vehicle_service.hpp @@ -3,15 +3,137 @@ namespace big { + enum class PublishStatus { + NONE = -2, + IDLE = -1, + SAVING, + SAVED, + FAILED + }; + + enum class SearchStatus { + NONE = -2, + IDLE = -1, + SEARCHING, + FOUND, + NO_RESULT, + FAILED + }; + class vehicle_service { + private: + + class HandlingProfile + { + public: + HandlingProfile(nlohmann::json &in, CHandlingData *handling_data) + { + this->handling_hash = in["handling_hash"]; + this->share_code = in["share_code"]; + + this->name = in["name"]; + this->description = in["description"]; + + nlohmann::json& data = in["data"]; + + // Make sure we copy the values that we don't modify to prevent corrupting the handling + this->data = *handling_data; + + this->data.m_centre_of_mass.x = data["centre_of_mass"]["x"]; + this->data.m_centre_of_mass.y = data["centre_of_mass"]["y"]; + this->data.m_centre_of_mass.z = data["centre_of_mass"]["z"]; + + this->data.m_inertia_mult.x = data["inertia_mult"]["x"]; + this->data.m_inertia_mult.y = data["inertia_mult"]["y"]; + this->data.m_inertia_mult.z = data["inertia_mult"]["z"]; + + this->data.m_mass = data["mass"]; + this->data.m_downforce_multiplier = data["downforce_mult"]; + this->data.m_buoyancy = data["buoyancy"]; + + this->data.m_drive_bias_rear = data["drive_bias_rear"]; + this->data.m_drive_bias_front = data["drive_bias_front"]; + + this->data.m_acceleration = data["acceleration_mult"]; + + this->data.m_initial_drive_gears = data["initial_drive_gears"]; + this->data.m_upshift = data["upshift"]; + this->data.m_downshift = data["downshift"]; + + this->data.m_drive_inertia = data["drive_inertia"]; + + this->data.m_drive_max_flat_velocity = data["drive_max_flat_vel"]; + + this->data.m_brake_force = data["brake_force"]; + this->data.m_brake_bias_front = data["brake_bias_front"]; + this->data.m_brake_bias_rear = data["brake_bias_rear"]; + this->data.m_handbrake_force = data["handbrake_force"]; + + this->data.m_steering_lock = data["steering_lock"]; + this->data.m_steering_lock_ratio = data["steering_lock_ratio"]; + + this->data.m_traction_curve_max = data["traction_curve_max"]; + this->data.m_traction_curve_lateral = data["traction_curve_lateral"]; + this->data.m_traction_curve_min = data["traction_curve_min"]; + this->data.m_traction_curve_ratio = data["traction_curve_ratio"]; + + this->data.m_curve_lateral = data["curve_lateral"]; + this->data.m_curve_lateral_ratio = data["curve_lateral_ratio"]; + + this->data.m_traction_spring_delta_max = data["traction_spring_delta_max"]; + this->data.m_traction_spring_delta_max_ratio = data["traction_spring_delta_max_ratio"]; + + this->data.m_low_speed_traction_loss_mult = data["low_speed_traction_loss_mult"]; + this->data.m_camber_stiffness = data["camber_stiffness"]; + + this->data.m_suspension_force = data["suspension_force"]; + this->data.m_suspension_comp_damp = data["suspension_comp_damp"]; + this->data.m_suspension_rebound_damp = data["suspension_rebound_damp"]; + this->data.m_suspension_upper_limit = data["suspension_upper_limit"]; + this->data.m_suspension_lower_limit = data["suspension_lower_limit"]; + this->data.m_suspension_raise = data["suspension_raise"]; + this->data.m_suspension_bias_front = data["suspension_bias_front"]; + this->data.m_suspension_bias_rear = data["suspension_bias_rear"]; + + this->data.m_anti_rollbar_force = data["anti_rollbar_force"]; + this->data.m_anti_rollbar_bias_front = data["anti_rollbar_bias_front"]; + this->data.m_anti_rollbar_bias_rear = data["anti_rollbar_bias_rear"]; + + this->data.m_roll_centre_height_front = data["roll_centre_height_front"]; + this->data.m_roll_centre_height_rear = data["roll_centre_height_rear"]; + } + + CHandlingData data; + + uint32_t handling_hash; + std::string share_code; + + std::string name; + std::string description; + }; + public: vehicle_service(); ~vehicle_service(); + bool apply_from_cache(std::string id); + int attempt_save(); + bool get_by_share_code(const char* share_code); + bool publish_profile(const char* name, const char* description); + PublishStatus publish_status(PublishStatus new_status = PublishStatus::NONE); bool restore_vehicle(); + + bool update_mine(bool force_update = false); + + inline static std::vector m_my_profiles; + inline static std::unordered_map m_handling_profiles; + + SearchStatus m_search_status = SearchStatus::IDLE; private: + PublishStatus m_publish_status = PublishStatus::IDLE; + inline static std::unordered_map m_handling_backup; };