commit 389794f82f6526633853c405ec6825569ddece31 Author: Jessica James Date: Thu May 13 17:07:07 2021 -0500 Initial commit from SVN diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..962f2c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs/ +Debug/ +Release/ diff --git a/Rx_TCPLink.sln b/Rx_TCPLink.sln new file mode 100644 index 0000000..c17bfdc --- /dev/null +++ b/Rx_TCPLink.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.9 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Rx_TCPLink", "Rx_TCPLink\Rx_TCPLink.vcxproj", "{988779E2-4B8D-4997-82B6-25BC4E0A705E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x64.ActiveCfg = Debug|x64 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x64.Build.0 = Debug|x64 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x86.ActiveCfg = Debug|Win32 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Debug|x86.Build.0 = Debug|Win32 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x64.ActiveCfg = Release|x64 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x64.Build.0 = Release|x64 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x86.ActiveCfg = Release|Win32 + {988779E2-4B8D-4997-82B6-25BC4E0A705E}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Rx_TCPLink/DiscordRpc.cpp b/Rx_TCPLink/DiscordRpc.cpp new file mode 100644 index 0000000..5556901 --- /dev/null +++ b/Rx_TCPLink/DiscordRpc.cpp @@ -0,0 +1,149 @@ +#define _CRT_SECURE_NO_WARNINGS + +#include "DiscordRpc.h" +#include +#include +#include +#include "discord_rpc.h" + +static constexpr int GDI_TEAM_NUM = 0; +static constexpr int NOD_TEAM_NUM = 1; + +int64_t now_seconds() { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +extern "C" __declspec(dllexport) void UpdateDiscordRPC(wchar_t* in_server_name, wchar_t* in_level_name, int in_player_count, int in_max_players, int in_team_num, int in_time_elapsed, int in_time_remaining, int in_is_firestorm, wchar_t* in_image_name) { + // Convert our input strings + char server_name[128]; + size_t server_name_length{}; + char level_name[128]; + size_t level_name_length{}; + char image_name[128]; + size_t image_name_length{}; + + server_name_length = std::wcstombs(server_name, in_server_name, sizeof(server_name)); + level_name_length = std::wcstombs(level_name, in_level_name, sizeof(level_name)); + image_name_length = std::wcstombs(image_name, in_image_name, sizeof(image_name)); + + // Populate our presence + DiscordRichPresence presence{}; + + // state + if (*level_name != '\0') { + // We're in a match + presence.state = level_name; + } + else { + // We're not in a level; we must be at the main menu + presence.state = "Main Menu"; + } + + // details, party info + if (*server_name != '\0') { + // We're in a multiplayer server + presence.details = server_name; + + presence.partySize = in_player_count; + presence.partyMax = in_max_players; + } + else { + // We're not in any server + if (strcmp(level_name, "Black Dawn") == 0) { + // We're playing campaign + presence.details = "Campaign"; + } + else if (*level_name != '\0') { + // We're playing skirmish + presence.details = "Singleplayer Skirmish"; + } + // else // we're sitting at the main menu + } + + if (in_is_firestorm == 0) { + presence.largeImageKey = "renegadex"; + presence.largeImageText = "Renegade X"; + + if (in_team_num == GDI_TEAM_NUM) { + presence.smallImageKey = "gdi"; + presence.smallImageText = "GDI"; + } + else if (in_team_num == NOD_TEAM_NUM) { + presence.smallImageKey = "nod"; + presence.smallImageText = "Nod"; + } + } + else { + presence.largeImageKey = "fs"; + presence.largeImageText = "Renegade X: Firestorm"; + + // For map-specfic images + if (*image_name != '\0') { + presence.largeImageKey = image_name; + } + + if (in_team_num == GDI_TEAM_NUM) { + presence.smallImageKey = "tsgdi"; + presence.smallImageText = "GDI"; + } + else if (in_team_num == NOD_TEAM_NUM) { + presence.smallImageKey = "tsnod"; + presence.smallImageText = "Nod"; + } + } + + + + // Discord client logic: + // If endTimestamp is present: display time remaining + + // If the match is over (negative in_time_remaining); don't display anything + if (presence.endTimestamp >= 0) { + presence.startTimestamp = now_seconds() - in_time_elapsed; + + // If it's a timed match, display the time remaining + if (in_time_remaining != 0) { + presence.endTimestamp = now_seconds() + in_time_remaining; + } + } + + // Update our presence + Discord_UpdatePresence(&presence); +} + +//typedef struct DiscordRichPresence { +// const char* state; /* max 128 bytes */ +// const char* details; /* max 128 bytes */ +// int64_t startTimestamp; +// int64_t endTimestamp; +// const char* largeImageKey; /* max 32 bytes */ +// const char* largeImageText; /* max 128 bytes */ +// const char* smallImageKey; /* max 32 bytes */ +// const char* smallImageText; /* max 128 bytes */ +// const char* partyId; /* max 128 bytes */ +// int partySize; +// int partyMax; +// const char* matchSecret; /* max 128 bytes */ +// const char* joinSecret; /* max 128 bytes */ +// const char* spectateSecret; /* max 128 bytes */ +// int8_t instance; +//} DiscordRichPresence; + +/*char buffer[256]; + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + discordPresence.state = "West of House"; + sprintf(buffer, "Frustration level: %d", FrustrationLevel); + discordPresence.details = buffer; + discordPresence.startTimestamp = StartTime; + discordPresence.endTimestamp = time(0) + 5 * 60; + discordPresence.largeImageKey = "canary-large"; + discordPresence.smallImageKey = "ptb-small"; + discordPresence.partyId = "party1234"; + discordPresence.partySize = 1; + discordPresence.partyMax = 6; + discordPresence.matchSecret = "xyzzy"; + discordPresence.joinSecret = "join"; + discordPresence.spectateSecret = "look"; + discordPresence.instance = 0; + Discord_UpdatePresence(&discordPresence);*/ \ No newline at end of file diff --git a/Rx_TCPLink/DiscordRpc.h b/Rx_TCPLink/DiscordRpc.h new file mode 100644 index 0000000..8fcbb6a --- /dev/null +++ b/Rx_TCPLink/DiscordRpc.h @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2018 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + __declspec(dllexport) void UpdateDiscordRPC(wchar_t* in_server_name, wchar_t* in_level_name, int in_player_count, int in_max_players, int in_team_num, int in_time_elapsed, int in_time_remaining, int is_in_firestorm, wchar_t* in_image_name); + +#ifdef __cplusplus +} +#endif // __cplusplus \ No newline at end of file diff --git a/Rx_TCPLink/HWID.cpp b/Rx_TCPLink/HWID.cpp new file mode 100644 index 0000000..2f1ad1d --- /dev/null +++ b/Rx_TCPLink/HWID.cpp @@ -0,0 +1,148 @@ +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include +#include +#include +#include +#include +#include "UDKArray.h" + +static std::string s_hwid; + +struct HWID { + union { + uint64_t hwid; + struct { + uint32_t left; + uint32_t right; + }; + }; +} hwid; + +#pragma pack(push, 4) +struct ByteArrayWrapper { + UDKArray data; +}; +#pragma pack(pop) + +std::string GetSystemDriveGUID() { + char* system_drive; + char system_drive_mount_point[64]; + char system_drive_volume_name[64]; + char* system_drive_guid_start; + char* system_drive_guid_end; + + system_drive = getenv("SystemDrive"); + if (system_drive != NULL) { + if (GetVolumePathNameA(system_drive, system_drive_mount_point, sizeof(system_drive_mount_point))) { + if (GetVolumeNameForVolumeMountPointA(system_drive_mount_point, system_drive_volume_name, sizeof(system_drive_volume_name))) { + // Find start of guid + system_drive_guid_start = system_drive_volume_name; + while (*system_drive_guid_start != '\0') { + if (*system_drive_guid_start == '{') { + // Found start of guid; interate over '{' and break + ++system_drive_guid_start; + break; + } + + ++system_drive_guid_start; + } + + // Find end of guid + system_drive_guid_end = system_drive_guid_start; + while (*system_drive_guid_end != '\0') { + if (*system_drive_guid_end == '}') { + // Found end of guid; break + break; + } + + ++system_drive_guid_end; + } + + // Copy guid to out_guid + return std::string{ system_drive_guid_start, system_drive_guid_end }; + } + } + } + + return {}; +} + +void fill_mac_hwid() { + IP_ADAPTER_ADDRESSES addresses[32]; + PIP_ADAPTER_ADDRESSES node; + ULONG addresses_size = sizeof(addresses); + + if (GetAdaptersAddresses(AF_UNSPEC, 0, NULL, addresses, &addresses_size) == ERROR_SUCCESS) { + for (node = addresses; node != NULL; node = node->Next) { + if (node->PhysicalAddressLength > 0) { + static_assert(MAX_ADAPTER_ADDRESS_LENGTH <= sizeof(struct HWID), "HWID struct is not large enough to fit adapter physical address"); + memcpy(&hwid, node->PhysicalAddress, node->PhysicalAddressLength); + + if (hwid.hwid != 0) { + return; + } + } + } + } + + hwid.hwid = 0; +} + +/** Integer to string (hexadecimal) conversion tables */ + +const char hexadecimal_rep_table_upper[][3] = +{ + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", + "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", + "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", + "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", + "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" +}; + +template +std::string to_hex(T in_integer) { + std::string result; + uint8_t* begin = reinterpret_cast(&in_integer); + uint8_t* itr = begin + sizeof(T); + + result.reserve(sizeof(in_integer) * 2); + while (itr != begin) { + --itr; + result += hexadecimal_rep_table_upper[*itr]; + } + + return result; +} + +extern "C" __declspec(dllexport) void c_token(ByteArrayWrapper* out_hwid) { + std::string hwid_str; + + // Try mac address + fill_mac_hwid(); + if (hwid.hwid != 0) { + hwid_str = 'm'; + hwid_str += to_hex(hwid.left) + to_hex(hwid.right); // keep this format so it matches old + } + else { + // Fallback to System Drive GUID + hwid_str = 'd'; + hwid_str += GetSystemDriveGUID(); + } + + // Return hwid_str through out_hwid + out_hwid->data.reserve(hwid_str.size()); + out_hwid->data.ArrayNum = hwid_str.size(); + memcpy(out_hwid->data.Data, hwid_str.data(), hwid_str.size()); +} diff --git a/Rx_TCPLink/Main.cpp b/Rx_TCPLink/Main.cpp new file mode 100644 index 0000000..c81a788 --- /dev/null +++ b/Rx_TCPLink/Main.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include "discord_rpc.h" +#include "discord_register.h" +#include "Rx_TCPLink.h" +#include "DiscordRpc.h" +#include "Viewport.h" + +extern "C" ReallocFunctionPtrType g_realloc_function = nullptr; + +void* searchMemory(const std::vector& bytes) +{ + MODULEINFO mi; + + GetModuleInformation(GetCurrentProcess(), GetModuleHandle(nullptr), &mi, sizeof mi); + + byte* moduleHead = static_cast(mi.lpBaseOfDll); + byte* moduleTail = moduleHead + mi.SizeOfImage; + + byte* region = moduleHead; + + while (region < moduleTail) + { + MEMORY_BASIC_INFORMATION mbi; + + if (!VirtualQuery(region, &mbi, sizeof mbi)) + { + break; + } + + byte* regionTail = region + mbi.RegionSize; + + constexpr DWORD protectionFlags = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; + + if (mbi.State & MEM_COMMIT && !(mbi.Protect & PAGE_GUARD) && mbi.AllocationProtect & protectionFlags) + { + void* result = std::search(region, regionTail, bytes.begin(), bytes.end()); + + if (result != regionTail) + { + return result; + } + } + + region = regionTail; + } + + return nullptr; +} + +#pragma pack(push, 4) +struct FDLLBindInitData +{ + INT Version; + ReallocFunctionPtrType ReallocFunctionPtr; +}; +#pragma pack(pop) + +void initViewport() +{ +#if _WIN64 + const auto address = searchMemory({ + 0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x6C, 0x24, 0x10, 0x48, 0x89, + 0x74, 0x24, 0x18, 0x57, 0x48, 0x83, 0xEC, 0x30, 0x33, 0xC0, 0x49, 0x8B, + 0xE8, 0x48, 0x8B, 0xFA, 0x48, 0x8B, 0xF1, 0x89, 0x42, 0x08, 0x39, 0x42, + 0x0C, 0x74, 0x19, 0x48, 0x8B, 0x0A, 0x89, 0x42, 0x0C, 0x48, 0x85, 0xC9, + 0x74, 0x0E, 0x44, 0x8D, 0x40, 0x08, 0x33, 0xD2, 0xE8, 0xA3, 0x5D, 0xC0 }); +#else + const auto address = searchMemory({ + 0x53, 0x56, 0x8B, 0x74, 0x24, 0x0C, 0x83, 0x7E, 0x08, 0x00, 0x57, 0x8B, + 0xD9, 0xC7, 0x46, 0x04, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1C, 0x8B, 0x06, + 0xC7, 0x46, 0x08, 0x00, 0x00, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x0F, 0x6A, + 0x08, 0x6A, 0x00, 0x50, 0xE8, 0x53, 0x75, 0xC6, 0xFF, 0x83, 0xC4, 0x0C, + 0x89, 0x06, 0x8B, 0x03, 0x8B, 0x50, 0x0C, 0x8B, 0xCB, 0xFF, 0xD2, 0x8B }); +#endif + + Viewport::setReadPixelsFunction(reinterpret_cast(address)); +} + +extern "C" __declspec(dllexport) void DLLBindInit(FDLLBindInitData* in_init_data) { + g_realloc_function = in_init_data->ReallocFunctionPtr; + + init_wsa(); + Discord_Initialize("482746941256499220", nullptr, 1, nullptr); + UpdateDiscordRPC(L"", L"", 0, 0, 0, 0, 0, 0, L""); + initViewport(); +} diff --git a/Rx_TCPLink/Ping.cpp b/Rx_TCPLink/Ping.cpp new file mode 100644 index 0000000..90709fb --- /dev/null +++ b/Rx_TCPLink/Ping.cpp @@ -0,0 +1,254 @@ +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#define PIO_APC_ROUTINE_DEFINED +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations and aliases +struct PingContext; +using request_map = std::unordered_map>; + +// Constants +constexpr int PING_IN_PROGRESS = -1; +constexpr int PING_FAILED = -2; + +unsigned char icmp_request_data[]{ "Renegade X Game Client Ping Data" }; +constexpr size_t icmp_request_data_length = sizeof(icmp_request_data) - 1; +constexpr DWORD icmp_reply_buffer_size = sizeof(ICMP_ECHO_REPLY) + icmp_request_data_length + 8 + sizeof(IO_STATUS_BLOCK); +constexpr DWORD icmp_timeout = 1000; // 1 second + +// Statics +std::atomic s_pings_in_progress{ 0 }; // Note, this is ALL ping requests in progress, not necessarily the ones in `s_pings` (and therefore, not necessarily contexts that still exist) +std::atomic s_ping_thread_active{ false }; +std::shared_ptr s_pings; +std::forward_list> s_pending_requests; +std::thread s_ping_thread; +//std::mutex s_pings_mutex; +std::mutex s_pending_requests_mutex; + +struct PingContext { + HANDLE icmp_handle{}; + IPAddr destination{}; + std::weak_ptr request_group; + std::unique_ptr reply_buffer; + std::atomic ping_time_milliseconds{ PING_IN_PROGRESS }; + + // Performs any single-run cleanup needed + void finish() { + // Cleanup handle + IcmpCloseHandle(icmp_handle); + icmp_handle = nullptr; + + // Mark no longer in progress + --s_pings_in_progress; + } + + PingContext() { + ++s_pings_in_progress; + } + + ~PingContext() { + if (icmp_handle != nullptr) { + IcmpCloseHandle(icmp_handle); + --s_pings_in_progress; + } + } +}; + +IPAddr parse_address(const wchar_t* in_str) { + IN_ADDR buffer_addr{}; + InetPtonW(AF_INET, in_str, &buffer_addr); + + return buffer_addr.S_un.S_addr; +} + +void WINAPI icmp_ping_callback(void* in_context, PIO_STATUS_BLOCK in_status_block, ULONG) { + std::unique_ptr> weak_context{ static_cast*>(in_context) }; + auto strong_context = weak_context->lock(); + + if (strong_context == nullptr) { + // Context has been destroyed already; s_pings must have changed + return; + } + + // Cleanup handle + strong_context->finish(); + + if (NT_ERROR(in_status_block->Status)) { + // Ping failed somehow; set status to error + strong_context->ping_time_milliseconds = PING_FAILED; + return; + } + + // Verify ping reply size + if (in_status_block->Information < icmp_request_data_length) { + // Ping did not reply with same data as sent; set status to error + strong_context->ping_time_milliseconds = PING_FAILED; + return; + } + + DWORD reply_count = IcmpParseReplies(strong_context->reply_buffer.get(), icmp_reply_buffer_size); + if (reply_count <= 0) { + // No response was received; this should never happen since one of the above checks would certainly have been hit + strong_context->ping_time_milliseconds = PING_FAILED; + return; + } + + ICMP_ECHO_REPLY* replies = reinterpret_cast(strong_context->reply_buffer.get()); + for (DWORD index = 0; index < reply_count; ++index) { // reply_count should never be anything other than 1, but hey, whatever + ICMP_ECHO_REPLY& reply = replies[index]; + + // Verify success + if (reply.Status != IP_SUCCESS) { + strong_context->ping_time_milliseconds = PING_FAILED; + return; + } + + // Verify reply size; this is probably redundant, but hey whatever + if (reply.DataSize != icmp_request_data_length) { + strong_context->ping_time_milliseconds = PING_FAILED; + return; + } + + // Verify ping data patches + if (std::memcmp(icmp_request_data, reply.Data, icmp_request_data_length) != 0) { + strong_context->ping_time_milliseconds = PING_FAILED; + return; + } + + // Ping request successful; set it + strong_context->ping_time_milliseconds = reply.RoundTripTime; + } +} + +void process_pending_requests() { + std::unique_lock lock{ s_pending_requests_mutex }; + // Loop until there are no pings in progress + while (s_pings_in_progress != 0) { + // Process pending any requests in the queue + while (!s_pending_requests.empty()) { + // Pop front pending request + auto pending_request = s_pending_requests.front().lock(); + s_pending_requests.pop_front(); + + if (pending_request != nullptr) { + // Kick off ICMP request + IcmpSendEcho2( + pending_request->icmp_handle, + nullptr, // event + &icmp_ping_callback, + new std::weak_ptr(pending_request), + pending_request->destination, + icmp_request_data, + icmp_request_data_length, + nullptr, // options + pending_request->reply_buffer.get(), + icmp_reply_buffer_size, + icmp_timeout); + } + } + + // Process any thread alerts / ICMP callbacks + lock.unlock(); + SleepEx(10, TRUE); + lock.lock(); + } + + s_ping_thread_active = false; +} + +void queue_pending_request(std::weak_ptr in_pending_request) { + std::lock_guard lock{ s_pending_requests_mutex }; + s_pending_requests.push_front(std::move(in_pending_request)); + + // Start thread if necessary + if (!s_ping_thread_active) { + // Join any existing thread + if (s_ping_thread.joinable()) { + s_ping_thread.join(); + } + + // Mark processing active + s_ping_thread_active = true; + + // Spin up thread + s_ping_thread = std::thread(&process_pending_requests); + } +} + +extern "C" __declspec(dllexport) bool start_ping_request(const wchar_t* in_str) { + // Parse address to ping + IPAddr destination_address = parse_address(in_str); + if (destination_address == 0) { + // Failed to parse + return false; + } + + // Get ICMP handle to send request + HANDLE icmp_handle = IcmpCreateFile(); + if (icmp_handle == INVALID_HANDLE_VALUE) { + // Failed to open handle for ICMP requests + return false; + } + + // Build request context + auto context = std::make_shared(); + context->icmp_handle = icmp_handle; + context->destination = destination_address; + context->reply_buffer = std::make_unique(icmp_reply_buffer_size); + { + //std::lock_guard guard{ s_pings_mutex }; + + if (s_pings == nullptr) { + s_pings = std::make_shared(); + } + + context->request_group = s_pings; + s_pings->emplace(in_str, context); + } + + // Queue request context + queue_pending_request(context); + + return true; +} + +extern "C" __declspec(dllexport) int get_ping(const wchar_t* in_str) { + //std::lock_guard guard{ s_pings_mutex }; + + // Safety check that s_pings actually exists + if (s_pings == nullptr) { + // Ping request does not exist + return PING_FAILED; + } + + // Search for IP in ping list + auto itr = s_pings->find(in_str); + if (itr == s_pings->end()) { + // Ping request does not exist + return PING_FAILED; + } + + // This is a massive hack, but we need to be able to join the processing thread when we're done to prevent a shutdown crash + if (!s_ping_thread_active && s_ping_thread.joinable()) { + s_ping_thread.join(); + } + + return itr->second->ping_time_milliseconds; +} + +extern "C" __declspec(dllexport) void clear_pings() { + //std::lock_guard guard{ s_pings_mutex }; + s_pings = nullptr; + + if (!s_ping_thread_active && s_ping_thread.joinable()) { + s_ping_thread.join(); + } +} diff --git a/Rx_TCPLink/Rx_TCPLink.c b/Rx_TCPLink/Rx_TCPLink.c new file mode 100644 index 0000000..14a4956 --- /dev/null +++ b/Rx_TCPLink/Rx_TCPLink.c @@ -0,0 +1,238 @@ +/** + * Copyright (C) 2016-2018 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include +#include +#include + +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include +#include + + /** Relative path to the launcher executable */ +#define LAUNCHER_PATH "..\\..\\..\\Launcher\\Renegade X Launcher.exe" + +bool init_wsa_success; + +#pragma pack(push, 4) +struct socket_t { + uint64_t sock; +} s_socket; + +struct AcceptedSocket +{ + uint64_t sock; + int32_t addr; + int32_t port; +} accepted_socket; +#pragma pack(pop) + +void init_wsa() +{ + WSADATA wsadata; + init_wsa_success = WSAStartup(WINSOCK_VERSION, &wsadata); +} + +void cleanup_wsa() +{ + if (init_wsa_success) + WSACleanup(); +} + +__declspec(dllexport) struct socket_t* c_socket() +{ + s_socket.sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + return &s_socket; +} + +// since UDK only supports IPv4, just return the IP address as an integer (or 0 on failure). +/*int32_t c_resolve(unsigned char *hostname) +{ + struct hostent *host_entry; + IN_ADDR address; + + host_entry = gethostbyname(hostname); + if (host_entry == NULL) + return 0; + + memcpy(&address, host_entry->h_addr_list[0], host_entry->h_length); + + return address.s_addr; +}*/ + +struct sockaddr_in helper_make_sockaddr_in(int32_t in_address, int32_t in_port) +{ + struct sockaddr_in address; + IN_ADDR in_addr; + + memset(&address, 0, sizeof(address)); + + in_addr.s_addr = htonl(in_address); + address.sin_addr = in_addr; + address.sin_family = AF_INET; + address.sin_port = htons(in_port); + + return address; +} + +// Binds to in_port, or fails +__declspec(dllexport) int32_t c_bind(struct socket_t* in_socket, int32_t in_port) +{ + struct sockaddr_in address; + + address = helper_make_sockaddr_in(INADDR_ANY, in_port); + + if (bind(in_socket->sock, (struct sockaddr *) &address, sizeof(address)) == 0) + return in_port; + + return 0; +} + +// Binds to in_port, or the next available +__declspec(dllexport) int32_t c_bind_next(struct socket_t* in_socket, int32_t in_port) +{ + struct sockaddr_in address; + + address = helper_make_sockaddr_in(INADDR_ANY, in_port); + + while (true) + { + if (bind(in_socket->sock, (struct sockaddr *) &address, sizeof(address)) == 0) + return in_port; + + if (WSAGetLastError() != WSAEADDRINUSE) + return 0; + + address.sin_port = htons(++in_port); + } +} + +// Binds to any port +__declspec(dllexport) int32_t c_bind_any(struct socket_t* in_socket) +{ + struct sockaddr_in address; + + address = helper_make_sockaddr_in(INADDR_ANY, 0); + + if (bind(in_socket->sock, (struct sockaddr *) &address, sizeof(address)) == 0) + return ntohs(address.sin_port); + + return 0; +} + +__declspec(dllexport) int32_t c_listen(struct socket_t* in_socket) +{ + return listen(in_socket->sock, SOMAXCONN); +} + +__declspec(dllexport) struct AcceptedSocket *c_accept(struct socket_t* in_socket) +{ + struct sockaddr addr; + int size; + + size = sizeof(addr); + accepted_socket.sock = accept(in_socket->sock, &addr, &size); + + if (accepted_socket.sock != INVALID_SOCKET) + { + accepted_socket.addr = ((struct sockaddr_in *) &addr)->sin_addr.s_addr; + accepted_socket.port = ((struct sockaddr_in *) &addr)->sin_port; + } + + return &accepted_socket; +} + +__declspec(dllexport) int32_t c_connect(struct socket_t* in_socket, int32_t in_address, int32_t in_port) +{ + struct sockaddr_in address; + + address = helper_make_sockaddr_in(in_address, in_port); + + return connect(in_socket->sock, (struct sockaddr *) &address, sizeof(address)); +} + +__declspec(dllexport) int32_t c_close(struct socket_t* in_socket) +{ + return closesocket(in_socket->sock); +} + +__declspec(dllexport) int32_t c_recv(struct socket_t* in_socket, unsigned char *out_buffer, int32_t in_buffer_size) +{ + return recv(in_socket->sock, out_buffer, in_buffer_size, 0); +} + +__declspec(dllexport) int32_t c_send(struct socket_t* in_socket, unsigned char *in_buffer, int32_t in_buffer_size) +{ + return send(in_socket->sock, in_buffer, in_buffer_size, 0); +} + +__declspec(dllexport) int32_t c_set_blocking(struct socket_t* in_socket, int32_t in_value) +{ + unsigned long block_mode; + block_mode = in_value == 0 ? 1 : 0; + return ioctlsocket(in_socket->sock, FIONBIO, &block_mode); +} + +__declspec(dllexport) int32_t c_get_last_error() +{ + return GetLastError(); +} + +__declspec(dllexport) int32_t c_check_status(struct socket_t* in_socket) +{ + fd_set set_read, set_err; + struct timeval time_val; + int value; + + time_val.tv_sec = 0; + time_val.tv_usec = 0; + FD_ZERO(&set_read); + FD_ZERO(&set_err); + FD_SET(in_socket->sock, &set_read); + FD_SET(in_socket->sock, &set_err); + + // check for success + value = select(in_socket->sock + 1, NULL, &set_read, &set_err, &time_val); + if (value > 0) // socket can be written to + { + if (FD_ISSET(in_socket->sock, &set_read)) + return 1; + if (FD_ISSET(in_socket->sock, &set_err)) + return -1; + } + + return value; +} + +__declspec(dllexport) bool UpdateGame() { + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInformation; + + memset(&startupInfo, 0, sizeof(startupInfo)); + memset(&processInformation, 0, sizeof(processInformation)); + + // Create process + if (!CreateProcessA(LAUNCHER_PATH, NULL, NULL, NULL, false, 0, NULL, NULL, &startupInfo, &processInformation)) { + return false; + } + + // Process successfully created; exit the application so that patching doesn't fail horribly + exit(EXIT_SUCCESS); + return true; // unreachable +} diff --git a/Rx_TCPLink/Rx_TCPLink.h b/Rx_TCPLink/Rx_TCPLink.h new file mode 100644 index 0000000..7e4866b --- /dev/null +++ b/Rx_TCPLink/Rx_TCPLink.h @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#if defined __cplusplus +extern "C" +{ +#endif // __cplusplus + + void init_wsa(); + void cleanup_wsa(); + + typedef void* (*ReallocFunctionPtrType)(void* Original, DWORD Count, DWORD Alignment); + extern ReallocFunctionPtrType g_realloc_function; + +#if defined __cplusplus +} + +#endif //__cplusplus \ No newline at end of file diff --git a/Rx_TCPLink/Rx_TCPLink.vcxproj b/Rx_TCPLink/Rx_TCPLink.vcxproj new file mode 100644 index 0000000..48948fd --- /dev/null +++ b/Rx_TCPLink/Rx_TCPLink.vcxproj @@ -0,0 +1,80 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {988779E2-4B8D-4997-82B6-25BC4E0A705E} + Rx_TCPLink + 10.0 + + + + DynamicLibrary + false + v142 + Unicode + + + + + + + + + + + + + + Level3 + Disabled + MultiThreaded + Default + ..\discord-rpc\win64-static\include;%(AdditionalIncludeDirectories) + ..\discord-rpc\win32-static\include;%(AdditionalIncludeDirectories) + + + true + ..\discord-rpc\win64-static\lib;%(AdditionalLibraryDirectories) + ..\discord-rpc\win32-static\lib;%(AdditionalLibraryDirectories) + Ws2_32.lib;iphlpapi.lib;discord-rpc.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Rx_TCPLink/Rx_TCPLink.vcxproj.user b/Rx_TCPLink/Rx_TCPLink.vcxproj.user new file mode 100644 index 0000000..c66f12b --- /dev/null +++ b/Rx_TCPLink/Rx_TCPLink.vcxproj.user @@ -0,0 +1,11 @@ + + + + D:\RenX\SVN\UDK_Uncooked\Binaries\Win64\UDK.exe + WindowsLocalDebugger + + + D:\RenX\SVN\UDK_Uncooked\Binaries\Win32\UDK.exe + WindowsLocalDebugger + + \ No newline at end of file diff --git a/Rx_TCPLink/Screenshot.cpp b/Rx_TCPLink/Screenshot.cpp new file mode 100644 index 0000000..c5d94b7 --- /dev/null +++ b/Rx_TCPLink/Screenshot.cpp @@ -0,0 +1,165 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include +#include +#include +#include +#include "Viewport.h" + +Viewport::ReadPixelsFunctionType Viewport::readPixelsFunction = nullptr; + +/** Filename related constants */ +static constexpr wchar_t prefix[]{ L"ScreenCapture " }; +static constexpr size_t prefix_length = sizeof(prefix) / sizeof(wchar_t) - 1; +static constexpr wchar_t extension[]{ L".jpg" }; +static constexpr size_t extension_length = sizeof(extension) / sizeof(wchar_t) - 1; +static constexpr size_t max_date_length = FILENAME_MAX - prefix_length - extension_length; + +#pragma pack(push, 4) +struct ScreenCapture +{ + UDKArray data; + + // For pushing data to captures + template + void push(const T& in_data) { + // Expand capture as necessary + int required_size = data.ArrayNum + sizeof(T); + if (required_size >= data.ArrayMax) { + // Expand to minimum required size, or double array size, whichever is larger + data.Reallocate(max(required_size, data.ArrayMax * 2)); + } + + // Copy data to capture + std::memcpy(data.Data + data.ArrayNum, &in_data, sizeof(T)); + data.ArrayNum += sizeof(T); + } + + // For popping from captures + template + T pop() { + T result{}; + if (sizeof(T) > data.ArrayNum) { + return result; + } + + // Copy item to result + uint8_t* item_begin = data.Data + data.ArrayNum - sizeof(T); + std::memcpy(&result, item_begin, sizeof(T)); + + // Pop item + data.ArrayNum -= sizeof(T); + + // Return result + return result; + } +}; +#pragma pack(pop) + +extern "C" __declspec(dllexport) void take_ss(Viewport::DllBindReference* viewport, ScreenCapture* result) +{ + // Read in screenshot data from UDK + viewport->value->readPixels(result->data, { 6, 0, 1, 16000.0F }); + int sizeX = viewport->value->getSizeX(); + int sizeY = viewport->value->getSizeY(); + + // Import screenCapture data into CImage + CImage outputImage; + outputImage.Create(sizeX, sizeY, 32); + for (int row = 0; row < sizeY; ++row) + { + const void* screenCaptureRowAddress = &result->data[row * sizeX * sizeof(UDKColor)]; + void* outputRowAddress = outputImage.GetPixelAddress(0, row); + memcpy(outputRowAddress, screenCaptureRowAddress, sizeX * 4); + } + + // Save JPEG image data to memory stream + auto stream = SHCreateMemStream(NULL, 0); + outputImage.Save(stream, Gdiplus::ImageFormatJPEG); + stream->Seek({}, STREAM_SEEK_SET, 0); + + // Get stream size + STATSTG stream_stat{}; + stream->Stat(&stream_stat, STATFLAG::STATFLAG_NONAME); + auto stream_size = stream_stat.cbSize.QuadPart; + + // Read data from stream into buffer + result->data.ArrayNum = stream_size; + stream->Read(result->data.Data, result->data.ArrayMax, nullptr); + stream->Release(); +} + +static_assert(sizeof(UDKColor) == 4, "UDKColor size != 4"); + +extern "C" __declspec(dllexport) void write_ss(ScreenCapture* screenCapture) +{ + // Get current date + auto currentTime = std::time(nullptr); + auto currentTm = localtime(¤tTime); + wchar_t timeString[max_date_length + 1]; + std::wcsftime(timeString, max_date_length, L"%F - %H-%M-%S", currentTm); + + // Build filename + std::wstring filename{ prefix }; + filename += timeString; + filename += extension; + + // Write to file + std::ofstream file{ filename, std::ios::binary }; + file.write(reinterpret_cast(screenCapture->data.Data), screenCapture->data.ArrayNum); +} + +constexpr size_t byte_buffer_size = 256; + +/** + * Copies data from a dynamic UDK byte array to a static UDK byte array + * + * @param out_destination Byte buffer to copy data to + * @param in_source Byte array to copy data from + * @param in_offset Position of in_source to start copying from + * @return Total number of bytes copied (will never exceed byte_buffer_size) + */ +extern "C" __declspec(dllexport) int copy_array_to_buffer(uint8_t* out_destination, ScreenCapture* in_source, int in_offset) { + // Sanity check length and offset + if (in_offset < 0 + || in_source == nullptr + || in_source->data.ArrayNum <= in_offset + || out_destination == nullptr) { + return 0; + } + + size_t length = min(byte_buffer_size, in_source->data.ArrayNum - in_offset); // Remaining bytes in array or buffer size, whichever is smaller + std::memcpy(out_destination, in_source->data.Data + in_offset, length); + return length; +} + +/** static shit */ + +uint8_t* s_cap_buffer = nullptr; +ScreenCapture* s_capture = nullptr; + +extern "C" __declspec(dllexport) void set_cap(uint8_t* buffer, ScreenCapture* screenCapture) { + s_cap_buffer = buffer; + s_capture = screenCapture; +} + +extern "C" __declspec(dllexport) int read_cap(int in_offset) { + int result = copy_array_to_buffer(s_cap_buffer, s_capture, in_offset); + return result; +} diff --git a/Rx_TCPLink/UDKArray.h b/Rx_TCPLink/UDKArray.h new file mode 100644 index 0000000..3896353 --- /dev/null +++ b/Rx_TCPLink/UDKArray.h @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include +#include "Rx_TCPLink.h" + +#pragma pack(push, 4) +template +struct UDKArray +{ + void Reallocate(int NewNum) + { + ArrayMax = NewNum; + Data = static_cast((*g_realloc_function)(Data, ArrayMax * sizeof(DataT), 8)); + } + + void reserve(int in_size) { + if (in_size > ArrayMax) { + Reallocate(in_size); + } + } + + DataT& operator[](const int index) + { + return Data[index]; + } + + const DataT& operator[](const int index) const + { + return Data[index]; + } + + void push_back(const DataT& in_item) { + if (ArrayNum >= ArrayMax) { + Reallocate(max(ArrayNum * 2, 8)); + } + + Data[ArrayNum] = in_item; + ++ArrayNum; + } + + void pop_back() { + --ArrayNum; + } + + DataT& back() const { + return Data[ArrayNum - 1]; + } + + DataT& front() const { + return Data[0]; + } + + DataT* Data; + int ArrayNum; + int ArrayMax; +}; +#pragma pack(pop) diff --git a/Rx_TCPLink/Viewport.h b/Rx_TCPLink/Viewport.h new file mode 100644 index 0000000..a5ae54f --- /dev/null +++ b/Rx_TCPLink/Viewport.h @@ -0,0 +1,105 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include +#include "UDKArray.h" + +#pragma pack(push, 4) +struct UDKColor +{ + union { + struct { + uint8_t b, g, r, a; + }; + + uint8_t bytes[4]; + uint32_t uint; + int32_t sint; + }; +}; + +static_assert(sizeof(UDKColor) == 4, "UDKColor has unexpected extra data; must be exactly 4"); + +struct ReadPixelsMetadata +{ + int cubeFace; + int compresssionMode; + int convertLinearToGamma; + float depth; +}; + +class Viewport +{ +public: + int readPixels(UDKArray& result, ReadPixelsMetadata metadata) + { + int value = readPixels(reinterpret_cast&>(result), metadata); + + // expand to bytes + result.ArrayNum *= sizeof(UDKColor); + result.ArrayMax *= sizeof(UDKColor); + return value; + } + + int readPixels(UDKArray& result, ReadPixelsMetadata metadata) + { + return readPixelsFunction(this, result, metadata); + } + + int getSizeX() const + { + return callVirtualFunction<2, int>(); + } + + int getSizeY() const + { + return callVirtualFunction<3, int>(); + } + + using ReadPixelsFunctionType = int(__thiscall*)(void*, UDKArray&, ReadPixelsMetadata); + + static void setReadPixelsFunction(const ReadPixelsFunctionType value) + { + readPixelsFunction = value; + } + + static ReadPixelsFunctionType readPixelsFunction; + + struct DllBindReference + { + Viewport* value; + }; + +private: + template ReturnType callVirtualFunction(ArgumentList... arguments) + { + using FunctionType = ReturnType(__thiscall*)(void*, ArgumentList...); + const auto p = (*reinterpret_cast(this))[Index]; + return p(this, arguments...); + } + + template ReturnType callVirtualFunction(ArgumentList... arguments) const + { + using FunctionType = ReturnType(__thiscall*)(const void*, ArgumentList...); + const auto p = (*reinterpret_cast(this))[Index]; + return p(this, arguments...); + } +}; +#pragma pack(pop) diff --git a/Rx_TCPLink/WSAInitCleanup.cpp b/Rx_TCPLink/WSAInitCleanup.cpp new file mode 100644 index 0000000..43e6162 --- /dev/null +++ b/Rx_TCPLink/WSAInitCleanup.cpp @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "Rx_TCPLink.h" + +class WSAInitCleanup +{ +public: + WSAInitCleanup() + { + init_wsa(); + } + ~WSAInitCleanup() + { + cleanup_wsa(); + } +} _wsa_init_cleanup; diff --git a/discord-rpc/win32-static/bin/send-presence.exe b/discord-rpc/win32-static/bin/send-presence.exe new file mode 100644 index 0000000..03ef5e5 Binary files /dev/null and b/discord-rpc/win32-static/bin/send-presence.exe differ diff --git a/discord-rpc/win32-static/include/discord_register.h b/discord-rpc/win32-static/include/discord_register.h new file mode 100644 index 0000000..16fb42f --- /dev/null +++ b/discord-rpc/win32-static/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/discord-rpc/win32-static/include/discord_rpc.h b/discord-rpc/win32-static/include/discord_rpc.h new file mode 100644 index 0000000..3e1441e --- /dev/null +++ b/discord-rpc/win32-static/include/discord_rpc.h @@ -0,0 +1,87 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* request); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/discord-rpc/win32-static/lib/discord-rpc.lib b/discord-rpc/win32-static/lib/discord-rpc.lib new file mode 100644 index 0000000..fe90a6a Binary files /dev/null and b/discord-rpc/win32-static/lib/discord-rpc.lib differ diff --git a/discord-rpc/win64-static/bin/send-presence.exe b/discord-rpc/win64-static/bin/send-presence.exe new file mode 100644 index 0000000..7f8d495 Binary files /dev/null and b/discord-rpc/win64-static/bin/send-presence.exe differ diff --git a/discord-rpc/win64-static/include/discord_register.h b/discord-rpc/win64-static/include/discord_register.h new file mode 100644 index 0000000..16fb42f --- /dev/null +++ b/discord-rpc/win64-static/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/discord-rpc/win64-static/include/discord_rpc.h b/discord-rpc/win64-static/include/discord_rpc.h new file mode 100644 index 0000000..3e1441e --- /dev/null +++ b/discord-rpc/win64-static/include/discord_rpc.h @@ -0,0 +1,87 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* request); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/discord-rpc/win64-static/lib/discord-rpc.lib b/discord-rpc/win64-static/lib/discord-rpc.lib new file mode 100644 index 0000000..97fadad Binary files /dev/null and b/discord-rpc/win64-static/lib/discord-rpc.lib differ