diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce6fdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,340 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb \ No newline at end of file diff --git a/BottlEye/BottlEye.vcxproj b/BottlEye/BottlEye.vcxproj new file mode 100644 index 0000000..fa053c7 --- /dev/null +++ b/BottlEye/BottlEye.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6} + Win32Proj + BottlEye + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + ClangCL + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + D:\Sync\Programming\C++\Libraries\Include;$(IncludePath) + + + + Level3 + true + WIN32;_DEBUG;BOTTLEYE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + false + + + + + Level3 + true + _DEBUG;BOTTLEYE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;BOTTLEYE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + false + + + + + Level3 + true + true + true + NDEBUG;BOTTLEYE_EXPORTS;_WINDOWS;_USRDLL;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + stdcpplatest + + + Windows + true + true + true + false + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BottlEye/BottlEye.vcxproj.filters b/BottlEye/BottlEye.vcxproj.filters new file mode 100644 index 0000000..def9fb8 --- /dev/null +++ b/BottlEye/BottlEye.vcxproj.filters @@ -0,0 +1,68 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {0c4a0d52-9a82-4d3f-9a16-c7500b15325c} + + + {1c69057d-6d08-43b0-90f1-f14b1a42e09b} + + + {485f60e6-89b2-4606-8d47-d79821e828ba} + + + {16a4f5db-842f-48d4-ba43-77c6665893b9} + + + + + Source Files + + + Source Files\battleye + + + Source Files\battleye + + + Source Files\battleye + + + Source Files\fmt + + + Source Files\fmt + + + + + Header Files\logging + + + Header Files\logging + + + Header Files\battleye + + + Header Files\battleye + + + Header Files\battleye + + + Header Files\battleye + + + Header Files + + + \ No newline at end of file diff --git a/BottlEye/be_client.hpp b/BottlEye/be_client.hpp new file mode 100644 index 0000000..6868f0d --- /dev/null +++ b/BottlEye/be_client.hpp @@ -0,0 +1,58 @@ +#pragma once +#include + +namespace battleye +{ + enum instance_status + { + NONE, + NOT_INITIALIZED, + SUCCESSFULLY_INITIALIZED, + DESTROYING, + DESTROYED + }; + + struct becl_game_data + { + char* game_version; + std::uint32_t address; + std::uint16_t port; + + // FUNCTIONS + using print_message_t = void(*)(char* message); + print_message_t print_message; + + using request_restart_t = void(*)(std::uint32_t reason); + request_restart_t request_restart; + + using send_packet_t = void(*)(void* packet, std::uint32_t length); + send_packet_t send_packet; + + using disconnect_peer_t = void(*)(std::uint8_t* guid, std::uint32_t guid_length, char* reason); + disconnect_peer_t disconnect_peer; + }; + + struct becl_be_data + { + using exit_t = bool(*)(); + exit_t exit; + + using run_t = void(*)(); + run_t run; + + using command_t = void(*)(char* command); + command_t command; + + using received_packet_t = void(*)(std::uint8_t* received_packet, std::uint32_t length); + received_packet_t received_packet; + + using on_receive_auth_ticket_t = void(*)(std::uint8_t* ticket, std::uint32_t length); + on_receive_auth_ticket_t on_receive_auth_ticket; + + using add_peer_t = void(*)(std::uint8_t* guid, std::uint32_t guid_length); + add_peer_t add_peer; + + using remove_peer_t = void(*)(std::uint8_t* guid, std::uint32_t guid_length); + remove_peer_t remove_peer; + }; +} \ No newline at end of file diff --git a/BottlEye/be_packet.hpp b/BottlEye/be_packet.hpp new file mode 100644 index 0000000..d629b6d --- /dev/null +++ b/BottlEye/be_packet.hpp @@ -0,0 +1,49 @@ +#pragma once +#include + +namespace battleye +{ + enum packet_id : std::uint8_t + { + INIT = 0x00, + START = 0x02, + REQUEST = 0x04, + RESPONSE = 0x05, + HEARTBEAT = 0x09, + }; + + +#pragma pack(push, 1) + struct be_fragment + { + std::uint8_t count; + std::uint8_t index; + }; + + struct be_packet_header + { + std::uint8_t id; + std::uint8_t sequence; + }; + + struct be_packet : be_packet_header + { + union + { + be_fragment fragment; + + // DATA STARTS AT body[1] IF PACKET IS FRAGMENTED + struct + { + std::uint8_t no_fragmentation_flag; + std::uint8_t body[0]; + }; + }; + + inline bool fragmented() + { + return this->fragment.count != 0x00; + } + }; +#pragma pack(pop) +} \ No newline at end of file diff --git a/BottlEye/delegate.cpp b/BottlEye/delegate.cpp new file mode 100644 index 0000000..4c843d1 --- /dev/null +++ b/BottlEye/delegate.cpp @@ -0,0 +1,198 @@ +#include "delegate.hpp" +#include "singleton.hpp" +#include "be_packet.hpp" +#include +#include +#include + +battleye::becl_game_data::send_packet_t battleye::delegate::o_send_packet = nullptr; + +bool battleye::delegate::exit() +{ + singleton::emulator.console().log("BottlEye exiting."); + return true; +} + +void battleye::delegate::run() +{ + //singleton::emulator.console().log("Executed 'run'"); +} + +void battleye::delegate::command(char* command) +{ + singleton::emulator.console().log("Executed 'command'"); +} + +void battleye::delegate::received_packet(std::uint8_t* received_packet, std::uint32_t length) +{ + auto header = reinterpret_cast(received_packet); + + switch (header->id) + { + case battleye::packet_id::INIT: + { + singleton::emulator.console().log("INIT"); + singleton::emulator.console().log_indented<1, true>("Size (bytes)", length); + + auto info_packet = battleye::be_packet{}; + info_packet.id = battleye::packet_id::INIT; + info_packet.sequence = 0x05; + + battleye::delegate::o_send_packet(&info_packet, sizeof(info_packet)); + break; + } + + case battleye::packet_id::START: + { + singleton::emulator.console().log("START"); + singleton::emulator.console().log_indented<1, true>("Size (bytes)", length); + + battleye::delegate::o_send_packet(received_packet, sizeof(battleye::be_packet_header)); + break; + } + + case battleye::packet_id::REQUEST: + { + singleton::emulator.console().log("REQUEST", header->sequence); + + // HANDLE PACKET FRAGMENTATION + if (header->fragmented()) + { + singleton::emulator.console().log_indented<1, true>("Fragment count", header->fragment.count); + singleton::emulator.console().log_indented<1, true>("Fragment index", header->fragment.index); + } + + // IF NOT FRAGMENTED RESPOND IMMEDIATELY, ELSE ONLY RESPOND TO THE LAST FRAGMENT + const auto respond = !header->fragmented() || (header->fragment.index == header->fragment.count - 1); + if (!respond) + { + singleton::emulator.console().log_indented<1>("Ignoring!"); + return; + } + + // SEND BACK HEADER + battleye::delegate::o_send_packet(received_packet, sizeof(battleye::be_packet_header)); + singleton::emulator.console().log_indented<1>("Sending back header..."); + + switch (header->sequence) + { + case 0x01: + { + singleton::emulator.console().log_indented<1>("Replaying!"); + + battleye::delegate::respond(header->sequence, + { + // REDACTED + }); + + break; + } + case 0x02: + { + singleton::emulator.console().log_indented<1>("Replaying!"); + + battleye::delegate::respond(header->sequence, + { + // REDACTED + }); + + break; + } + + default: + { + singleton::emulator.console().log_indented<1>("Replying!"); + break; + } + } + + break; + } + + case battleye::packet_id::RESPONSE: + { + singleton::emulator.console().log("Acknowledgement of packet", header->sequence); + break; + } + + case battleye::packet_id::HEARTBEAT: + { + singleton::emulator.console().log("Heartbeat"); + battleye::delegate::o_send_packet(received_packet, length); + break; + } + + default: + { + //battleye::delegate::o_send_packet(received_packet, sizeof(battleye::be_packet)); + + singleton::emulator.console().log("Unhandled packet", header->id); + break; + } + } +} + +void battleye::delegate::on_receive_auth_ticket(std::uint8_t* ticket, std::uint32_t length) +{ + singleton::emulator.console().log("Executed 'on_receive_auth_ticket'"); +} + +void battleye::delegate::add_peer(std::uint8_t* guid, std::uint32_t guid_length) +{ + singleton::emulator.console().log("Executed 'add_peer'"); +} + +void battleye::delegate::remove_peer(std::uint8_t* guid, std::uint32_t guid_length) +{ + singleton::emulator.console().log("Executed 'remove_peer'"); +} + +void battleye::delegate::respond(std::uint8_t response_index, std::initializer_list data) +{ + // SETUP RESPONSE PACKET WITH TWO-BYTE HEADER + NO-FRAGMENTATION TOGGLE + + const auto size = sizeof(battleye::be_packet_header) + sizeof(battleye::be_fragment::count) + data.size(); + + auto packet = std::make_unique(size); + auto packet_buffer = packet.get(); + + packet_buffer[0] = (battleye::packet_id::RESPONSE); // PACKET ID + packet_buffer[1] = (response_index - 1); // RESPONSE INDEX + packet_buffer[2] = (0x00); // FRAGMENTATION DISABLED + + + for (size_t i = 0; i < data.size(); i++) + { + packet_buffer[3 + i] = data.begin()[i]; + } + + battleye::delegate::o_send_packet(packet_buffer, size); +} + +void battleye::delegate::respond_fragmented(std::uint8_t response_index, battleye::be_fragment fragment, std::initializer_list data) +{ + // SETUP RESPONSE PACKET WITH TWO-BYTE HEADER + FRAGMENTATION HEADER + + const auto size = sizeof(battleye::packet_id) + sizeof(response_index) + sizeof(fragment) + data.size(); + + if (size != 0x400 && fragment.index != fragment.count - 1) // ALL FRAGMENTS BUT THE LAST IS 0x400 LONG + { + singleton::emulator.console().log_error("Sending too little fragment..."); + } + + auto packet = std::make_unique(size); + auto packet_buffer = packet.get(); + + packet_buffer[0] = battleye::packet_id::RESPONSE; // PACKET ID + packet_buffer[1] = response_index - 1; // RESPONSE INDEX + packet_buffer[2] = fragment.count; // FRAGMENTATION COUNT + packet_buffer[3] = fragment.index; // FRAGMENTATION INDEX + + for (size_t i = 0; i < data.size(); i++) + { + packet_buffer[4 + i] = data.begin()[i]; + } + + // DEBUG PRINT + battleye::delegate::o_send_packet(packet_buffer, size); +} \ No newline at end of file diff --git a/BottlEye/delegate.hpp b/BottlEye/delegate.hpp new file mode 100644 index 0000000..e1f421b --- /dev/null +++ b/BottlEye/delegate.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "be_client.hpp" +#include "be_packet.hpp" +#include + +namespace battleye::delegate +{ + extern battleye::becl_game_data::send_packet_t o_send_packet; + + bool exit(); + void run(); + void command(char* command); + void received_packet(std::uint8_t* received_packet, std::uint32_t length); + void on_receive_auth_ticket(std::uint8_t* ticket, std::uint32_t length); + void add_peer(std::uint8_t* guid, std::uint32_t guid_length); + void remove_peer(std::uint8_t* guid, std::uint32_t guid_length); + + void respond(std::uint8_t response_index, std::initializer_list data); + void respond_fragmented(std::uint8_t response_index, battleye::be_fragment fragment, std::initializer_list data); +} \ No newline at end of file diff --git a/BottlEye/emulator.cpp b/BottlEye/emulator.cpp new file mode 100644 index 0000000..979ab24 --- /dev/null +++ b/BottlEye/emulator.cpp @@ -0,0 +1,25 @@ +#include "emulator.hpp" +#include "delegate.hpp" + +void battleye::emulator::setup_battleye(battleye::becl_game_data* game_data, + battleye::becl_be_data* client_data) +{ + this->console().log("Setting up game_data..."); + this->console().log_indented<1>("Game version", game_data->game_version); + this->console().log_indented<1, true>("Address", game_data->address); + this->console().log_indented<1, true>("Port", game_data->port); + + // CACHE RELEVANT FUNCTIONS + battleye::delegate::o_send_packet = game_data->send_packet; + + // SETUP CLIENT STRUCTURE + client_data->exit = battleye::delegate::exit; + client_data->run = battleye::delegate::run; + client_data->command = battleye::delegate::command; + client_data->received_packet = battleye::delegate::received_packet; + client_data->on_receive_auth_ticket = battleye::delegate::on_receive_auth_ticket; + client_data->add_peer = battleye::delegate::add_peer; + client_data->remove_peer = battleye::delegate::remove_peer; + + this->console().log_indented<1>("Done!"); +} diff --git a/BottlEye/emulator.hpp b/BottlEye/emulator.hpp new file mode 100644 index 0000000..a3f4f82 --- /dev/null +++ b/BottlEye/emulator.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "loggr.hpp" +#include "be_client.hpp" + +namespace battleye +{ + class emulator + { + public: + emulator() : m_logger() {} + + void setup_battleye(battleye::becl_game_data* game_data, + battleye::becl_be_data* client_data); + + inline loggr& console() + { + return this->m_logger; + } + + private: + loggr m_logger; + + }; +} \ No newline at end of file diff --git a/BottlEye/entry.cpp b/BottlEye/entry.cpp new file mode 100644 index 0000000..33d45f0 --- /dev/null +++ b/BottlEye/entry.cpp @@ -0,0 +1,34 @@ +#include +#include "be_client.hpp" +#include "singleton.hpp" + +extern "C" +{ + // BEClient_x64!Init + __declspec(dllexport) + battleye::instance_status Init(std::uint64_t integration_version, + battleye::becl_game_data* game_data, + battleye::becl_be_data* client_data) + { + singleton::emulator.console().log("Initialising BottlEye!"); + singleton::emulator.console().log_indented<1>("Brought to you by: https://secret.club/"); + singleton::emulator.setup_battleye(game_data, client_data); + + return battleye::instance_status::SUCCESSFULLY_INITIALIZED; + } + + // BEClient_x64!GetVer + __declspec(dllexport) + std::uint32_t GetVer() + { + return 0xFF; + } +} + + +std::uint32_t __stdcall DllMain(const void* mod_instance [[maybe_unused]], + const std::uint32_t call_reason [[maybe_unused]], + const void* reserved [[maybe_unused]]) +{ + return 1; +} \ No newline at end of file diff --git a/BottlEye/loggr.hpp b/BottlEye/loggr.hpp new file mode 100644 index 0000000..d541ee3 --- /dev/null +++ b/BottlEye/loggr.hpp @@ -0,0 +1,130 @@ +#pragma once +#include +#include +#include +#include + +class loggr +{ +public: + loggr() + { + // SETUP CONSOLE IF NOT PRESENT + auto console_window = GetConsoleWindow(); + if (console_window == 0x00) + { + if (!AllocConsole()) + { + // ???? SHOULD NEVER HAPPEN + return; + } + + std::freopen("CONOUT$", "w", stdout); + + m_did_allocate_console = true; + } + } + ~loggr() + { + // FREE CONSOLE IF DYNAMICALLY ALLOCATED + if (m_did_allocate_console) + { + const auto freed = FreeConsole(); + + if (!freed) + { + // ?? + } + } + } + + // CONSOLE LOGGING FUNCTIONS + template + inline void log_raw(T... arguments) const + { + fmt::print(arguments...); + } + + inline void log(std::string_view message) const + { + fmt::print("[+] {}\n", message); + } + inline void log_error(std::string_view message) const + { + fmt::print("[!] {}\n", message); + } + + template + inline void log(std::string_view variable_name, const T& value) const + { + constexpr auto format_string = hex ? + "[=] {:<15} {:X}\n" : + "[=] {:<15} {}\n"; + + fmt::print(format_string, variable_name, value); + } + + template + inline void log_error_indented(std::string_view message) const + { + fmt::print("[!] {:<{}} {}\n", ' ', indentation, message); + } + + template + inline void log_indented(std::string_view message) const + { + fmt::print("[+] {:<{}} {}\n", ' ', indentation, message); + } + + template + inline void log_indented(std::string_view variable_name, const T& value) const + { + constexpr auto format_string = hex ? + "[=] {:<{}} {:.<15} {:02X}\n" : + "[=] {:<{}} {:.<15} {}\n"; + + fmt::print(format_string, ' ', indentation, variable_name, value); + } + + + // CONSOLE MODIFICATION FUNCTIONS + inline COORD get_position() const + { + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)) + { + this->log_error("Failed to get cursor position"); + return { 0, 0 }; + } + + return info.dwCursorPosition; + } + inline void set_position(const COORD cursor) const + { + if (!SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursor)) + { + this->log_error("Failed to set cursor position"); + + } + } + inline void clear_line() const + { + // GET CURSOR POSITION + auto position = this->get_position(); + + + position.X = 0; + + // CLEAR LINE + DWORD count = 0; + const auto handle = GetStdHandle(STD_OUTPUT_HANDLE); + auto result = FillConsoleOutputCharacter(handle, ' ', 150, position, &count); + + // RESET POSITION + set_position(position); + } + +private: + bool m_did_allocate_console = false; + +}; \ No newline at end of file diff --git a/BottlEye/singleton.cpp b/BottlEye/singleton.cpp new file mode 100644 index 0000000..fda9f15 --- /dev/null +++ b/BottlEye/singleton.cpp @@ -0,0 +1,3 @@ +#include "singleton.hpp" + +battleye::emulator singleton::emulator = battleye::emulator(); \ No newline at end of file diff --git a/BottlEye/singleton.hpp b/BottlEye/singleton.hpp new file mode 100644 index 0000000..8caf6a1 --- /dev/null +++ b/BottlEye/singleton.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "emulator.hpp" + +namespace singleton +{ + extern battleye::emulator emulator; +} \ No newline at end of file diff --git a/BottlEye/spinner.hpp b/BottlEye/spinner.hpp new file mode 100644 index 0000000..590bc55 --- /dev/null +++ b/BottlEye/spinner.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "loggr.hpp" +#include + +class spinner +{ +public: + spinner(loggr* new_logger) : m_logger(new_logger) { } + + static constexpr std::uint8_t CHARACTER_SEQUENCE[] = + { + '|', '/', '-', '\\' + }; + + inline void start() + { + if (this->m_active) + { + this->m_logger->log_error("Cannot initialise spinner object more than once!"); + return; + } + + this->m_active = true; + } + + inline void update() + { + if (!this->m_active) + { + this->m_logger->log_error("Cannot initialise spinner object without initialization!"); + return; + } + + + // GET TIME SINCE LAST UPDATE + using clock_t = std::chrono::high_resolution_clock; + const auto time = clock_t::now(); + const auto time_delta = time - m_last_update_time; + const auto duration = std::chrono::duration_cast(time_delta); + + + // ONLY UPDATE SPINNER EVERY x MS + constexpr auto update_time = 150; + if (duration.count() <= update_time) + { + return; + } + + // UPDATE THE SPINNER CHARACTER + this->m_logger->clear_line(); + this->m_logger->log_raw("[{}]", static_cast(spinner::CHARACTER_SEQUENCE[this->m_seq_index])); + + // INCREMENT AND WRAP + this->m_seq_index++; + this->m_seq_index %= sizeof(spinner::CHARACTER_SEQUENCE); + + this->m_last_update_time = time; + + } + inline void stop() + { + if (!this->m_active) + { + this->m_logger->log_error("Spinner is not active!"); + return; + } + + this->m_logger->clear_line(); + this->m_active = false; + } + +private: + const loggr* m_logger; + bool m_active = false; + std::size_t m_seq_index = 0; + std::chrono::steady_clock::time_point m_last_update_time; +}; \ No newline at end of file diff --git a/Bottleye.sln b/Bottleye.sln new file mode 100644 index 0000000..e7b645f --- /dev/null +++ b/Bottleye.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29609.76 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BottlEye", "BottlEye\BottlEye.vcxproj", "{9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}" +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 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Debug|x64.ActiveCfg = Debug|x64 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Debug|x64.Build.0 = Debug|x64 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Debug|x86.ActiveCfg = Debug|Win32 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Debug|x86.Build.0 = Debug|Win32 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Release|x64.ActiveCfg = Release|x64 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Release|x64.Build.0 = Release|x64 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Release|x86.ActiveCfg = Release|Win32 + {9A6F6F8A-15CB-4276-BD96-1BA5E91DEED6}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1C8FC625-02F3-4B76-A958-B1AED03C2B75} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d8cce6 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ + + +# Overview +BottlEye is an usermode emulator for the commercial anti-cheat [BattlEye](battleye.com) that works by recreating the anti-cheat diff --git a/bottleye_logo.png b/bottleye_logo.png new file mode 100644 index 0000000..c5d16ce Binary files /dev/null and b/bottleye_logo.png differ diff --git a/bottleye_logo.svg b/bottleye_logo.svg new file mode 100644 index 0000000..59de4fe --- /dev/null +++ b/bottleye_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file