├── OsuBots ├── OsuBots.rc ├── icon1.ico ├── resource.h ├── Time.h ├── TimingPoint.h ├── Input.h ├── Timer.h ├── Timer.cpp ├── SigScanner.h ├── Time.cpp ├── Functions.h ├── Config.h ├── beatmapRelatedStructs.h ├── ProcessTools.h ├── Beatmap.h ├── main.cpp ├── TimingPoint.cpp ├── HitObject.h ├── OsuDbParser.h ├── Functions.cpp ├── OsuBot.h ├── OsuBots.vcxproj.filters ├── SigScanner.cpp ├── HitObject.cpp ├── Beatmap.cpp ├── ProcessTools.cpp ├── OsuBots.vcxproj ├── OsuDbParser.cpp ├── Config.cpp ├── Input.cpp └── OsuBot.cpp ├── LICENSE ├── OsuBots.sln ├── .gitattributes ├── README.md └── .gitignore /OsuBots/OsuBots.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CookieHoodie/OsuBot/HEAD/OsuBots/OsuBots.rc -------------------------------------------------------------------------------- /OsuBots/icon1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CookieHoodie/OsuBot/HEAD/OsuBots/icon1.ico -------------------------------------------------------------------------------- /OsuBots/resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CookieHoodie/OsuBot/HEAD/OsuBots/resource.h -------------------------------------------------------------------------------- /OsuBots/Time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Time 6 | { 7 | public: 8 | static LARGE_INTEGER frequency; 9 | static void initFrequency(); 10 | static LARGE_INTEGER now(); 11 | static bool exceedDuration(LARGE_INTEGER startTime, double duration); 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /OsuBots/TimingPoint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Functions.h" 6 | 7 | using namespace std; 8 | 9 | class TimingPoint 10 | { 11 | public: 12 | TimingPoint(string timingPointLine, float* lastPositiveMPB); 13 | 14 | int offset; 15 | float relativeMPB; // (Milliseconds per beat) -> duration of one beat 16 | float realMPB; // calculated MPB 17 | int meter; 18 | 19 | private: 20 | void processTimingPoints(string timingPointLine, float* lastPositiveMPB); 21 | }; -------------------------------------------------------------------------------- /OsuBots/Input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "HitObject.h" 10 | #include "Timer.h" 11 | #include "Config.h" 12 | 13 | using namespace std; 14 | 15 | class Input 16 | { 17 | public: 18 | // functions 19 | static void sentKeyInput(char key, bool pressed); 20 | static void circleLinearMove(POINT startScaledPoint, POINT endScaledPoint, double duration); 21 | static POINT spinnerMove(POINT center, double duration); 22 | static POINT sliderMove(HitObject currentHitObject, float pointsMultiplierX, float pointsMultiplierY, POINT cursorStartPoints); 23 | }; 24 | -------------------------------------------------------------------------------- /OsuBots/Timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using namespace std; 6 | 7 | class Timer 8 | { 9 | private: 10 | // all these typedefs are defined so that changes only need to be made here 11 | typedef chrono::steady_clock ClockTypeT; 12 | typedef nano PrefixT; 13 | typedef double DurationRepT; 14 | typedef chrono::duration DurationT; 15 | typedef chrono::time_point TimeT; 16 | 17 | // member var 18 | TimeT startTime; 19 | public: 20 | // cons & decons 21 | Timer(); 22 | ~Timer(); 23 | 24 | // const 25 | static const unsigned int prefix; 26 | 27 | // functions 28 | void start(); 29 | DurationRepT getTimePast(); 30 | }; -------------------------------------------------------------------------------- /OsuBots/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer.h" 2 | 3 | // prefix is the multiplier for durations during movements to get scaledDuration which is in the unit of PrefixT 4 | // divide by 1000 to get the correct multiplier 5 | const unsigned int Timer::prefix = PrefixT::den / 1000; 6 | 7 | Timer::Timer() 8 | { 9 | } 10 | 11 | Timer::~Timer() 12 | { 13 | } 14 | 15 | // assign startTime to member var 16 | void Timer::start() { 17 | (this)->startTime = ClockTypeT::now(); 18 | } 19 | 20 | // so that when call this, the elasped time can be determined without passing in startTime parameter 21 | Timer::DurationRepT Timer::getTimePast() { 22 | return DurationT(ClockTypeT::now() - (this)->startTime).count(); 23 | } -------------------------------------------------------------------------------- /OsuBots/SigScanner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class SigScanner 6 | { 7 | public: 8 | static DWORD findPattern(HANDLE processHandle, const unsigned char pattern[], const char* mask, const int offset, size_t begin = 0); 9 | //static void* patternScan(char* base, size_t size, char* pattern, char* mask); 10 | //static void* patternScanEx(HANDLE osuProcessHandle, uintptr_t begin, uintptr_t end, char* pattern, char* mask, int offset); 11 | //static void* patternScanEx(DWORD processID, uintptr_t begin, uintptr_t end, char* pattern, char* mask, int offset); 12 | //static void* patternScanExModule(DWORD processID, wchar_t* module, char* pattern, char* mask, int offset); 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /OsuBots/Time.cpp: -------------------------------------------------------------------------------- 1 | #include "Time.h" 2 | 3 | LARGE_INTEGER Time::frequency; 4 | 5 | void Time::initFrequency() { 6 | LARGE_INTEGER f; 7 | QueryPerformanceFrequency(&f); 8 | Time::frequency = f; 9 | } 10 | 11 | LARGE_INTEGER Time::now() { 12 | LARGE_INTEGER startTime; 13 | QueryPerformanceCounter(&startTime); 14 | return startTime; 15 | } 16 | 17 | bool Time::exceedDuration(LARGE_INTEGER startTime, double duration) { 18 | LARGE_INTEGER elapsedTime; 19 | auto currentTime = Time::now(); 20 | elapsedTime.QuadPart = currentTime.QuadPart - startTime.QuadPart; 21 | elapsedTime.QuadPart *= 1000000; 22 | elapsedTime.QuadPart /= Time::frequency.QuadPart; 23 | return elapsedTime.QuadPart >= duration * 1000 ? true : false; 24 | } -------------------------------------------------------------------------------- /OsuBots/Functions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "beatmapRelatedStructs.h" 12 | 13 | using namespace std; 14 | 15 | class Functions 16 | { 17 | public: 18 | // both are for spliting strings 19 | template 20 | static void split(const string &s, char delim, Out result); 21 | static vector split(const string &s, char delim); 22 | 23 | static bool almostEquals(const float a, const float b, const float tolerance = 0.25); 24 | static double binomialCoef(int n, int k); 25 | static FPointS bezierCurve(vector curvePointsV, float t); 26 | static double randomNumGenerator(int variance); 27 | private: 28 | static mt19937 generator; 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /OsuBots/Config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Functions.h" 8 | #include "ProcessTools.h" 9 | 10 | using namespace std; 11 | 12 | namespace Config 13 | { 14 | // in file 15 | extern string OSUROOTPATH; 16 | extern char LEFT_KEY; 17 | extern char RIGHT_KEY; 18 | extern int CLICK_OFFSET; 19 | extern unsigned int CLICK_OFFSET_DEVIATION; 20 | extern int SLIDER_DURATION_OFFSET; 21 | extern unsigned int RPM; 22 | // rarely changed 23 | extern unsigned int CIRCLE_SLEEPTIME; 24 | extern unsigned int MIN_WAIT_DURATION; 25 | 26 | // not in file 27 | extern string SONGFOLDER; 28 | extern string FILENAME; 29 | 30 | void loadConfigFile(string filename); 31 | void clearAndChangeConfig(); // wrapper 32 | void changeConfig(); // does not clear the screen 33 | void resetConfig(); 34 | } 35 | -------------------------------------------------------------------------------- /OsuBots/beatmapRelatedStructs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | // ----------------------------------------For HitObject----------------------------------------- 10 | typedef struct FPoint { 11 | float x; 12 | float y; 13 | } FPointS; 14 | 15 | // Struct for Slider ONLY! 16 | typedef struct CurvePoints { 17 | int x; 18 | int y; 19 | CurvePoints(int x, int y) { 20 | (this)->x = x; 21 | (this)->y = y; 22 | } 23 | } CurvePointsS; 24 | 25 | // ------------------------------------------For Beatmap----------------------------------- 26 | // Structs for beatmap info 27 | typedef struct General { 28 | int audioLeadIn; 29 | float stackLeniency; 30 | } GeneralS; 31 | 32 | typedef struct Difficulty { 33 | float hpDrainRate; 34 | float circleSize; 35 | float overallDifficulty; 36 | float approachRate; 37 | float sliderMultiplier; 38 | int sliderTickRate; 39 | } DifficultyS; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 CH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OsuBots/ProcessTools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using namespace std; 11 | 12 | class ProcessTools 13 | { 14 | public: 15 | static DWORD getProcessID(const wchar_t* processName); 16 | static HWND getWindowHandle(DWORD processID); 17 | static HWND getWindowTitleHandle(LPCSTR windowTitle); 18 | static string getWindowTextString(HWND windowHandle); 19 | static string promptToChooseFileAndGetPath(LPCWSTR customTitle = L""); 20 | static bool saveConsoleBuffer(CONSOLE_SCREEN_BUFFER_INFO &csbi, PCHAR_INFO consoleBuffer); 21 | static bool restoreConsoleBuffer(CONSOLE_SCREEN_BUFFER_INFO &csbi, PCHAR_INFO consoleBuffer); 22 | //static int readFromMemory(DWORD processID, void* memoryAddress); 23 | //static bool readFromMemory(HANDLE hProcess, void* memoryAddress, void* output, int size); 24 | //static int writeToMemory(DWORD processID, void* memoryAddress, byte* input, int size); 25 | /*static int writeToMemory(DWORD processID, int memoryAddress, int input);*/ 26 | //static MODULEENTRY32 getModule(DWORD processID, const wchar_t* processName); 27 | }; -------------------------------------------------------------------------------- /OsuBots/Beatmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | //#include 8 | #include 9 | 10 | #include "Functions.h" 11 | #include "beatmapRelatedStructs.h" 12 | #include "HitObject.h" 13 | #include "TimingPoint.h" 14 | #include "Config.h" 15 | 16 | using namespace std; 17 | //namespace fs = std::experimental::filesystem; // for C++ 14 18 | 19 | class Beatmap 20 | { 21 | public: 22 | // constructor & destructor 23 | Beatmap(string fullPathAfterSongsFolder); 24 | 25 | // variables 26 | string fullPathBeatmapFileName; 27 | bool allSet; // for checking if beatmap is successfully set 28 | GeneralS General; 29 | DifficultyS Difficulty; 30 | vector TimingPoints; 31 | vector HitObjects; 32 | 33 | // calculated variables 34 | float circleRadius; 35 | float timeRange50; 36 | float timeRange100; 37 | float timeRange300; 38 | int approachWindow; 39 | 40 | // function 41 | static double calcApproachWindow(double AR, double min = 1800, double mid = 1200, double max = 450); 42 | private: 43 | void processBeatmap(); 44 | //void setFullPathBeatmapFileName(); // deprecated 45 | }; 46 | -------------------------------------------------------------------------------- /OsuBots/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "OsuBot.h" 5 | #include "Config.h" 6 | 7 | 8 | #include 9 | 10 | using namespace std; 11 | 12 | int main() { 13 | wchar_t* processName = L"osu!.exe"; 14 | 15 | try { 16 | cout << "Reading constants..." << endl; 17 | Config::loadConfigFile("config.txt"); 18 | system("cls"); 19 | OsuBot bot = OsuBot(processName); 20 | bot.start(); 21 | } 22 | catch (OsuBotException e) { 23 | cerr << "OsuBotException: " << e.what() << endl; 24 | } 25 | catch (const runtime_error& re) 26 | { 27 | // speciffic handling for runtime_error 28 | cerr << "Runtime error: " << re.what() << endl; 29 | } 30 | catch (const exception& ex) 31 | { 32 | // speciffic handling for all exceptions extending std::exception, except 33 | // std::runtime_error which is handled explicitly 34 | cerr << "Error occurred: " << ex.what() << endl; 35 | } 36 | catch (...) 37 | { 38 | // catch any other errors (that we have no information about) 39 | cerr << "Unknown failure occurred. Possible memory corruption" << endl; 40 | } 41 | HWND consoleHandle = GetConsoleWindow(); 42 | SetForegroundWindow(consoleHandle); 43 | cout << "Please restart the program!" << endl; 44 | system("pause"); 45 | } -------------------------------------------------------------------------------- /OsuBots/TimingPoint.cpp: -------------------------------------------------------------------------------- 1 | #include "TimingPoint.h" 2 | 3 | TimingPoint::TimingPoint(string timingPointLine, float* lastPositiveMPB) 4 | { 5 | (this)->processTimingPoints(timingPointLine, lastPositiveMPB); 6 | } 7 | 8 | // ----------------------------------------Storing Timing Points---------------------------------------------- 9 | void TimingPoint::processTimingPoints(string timingPointLine, float* lastPositiveMPB) { 10 | vector elements = Functions::split(timingPointLine, ','); 11 | // storing original data from the line 12 | (this)->offset = stoi(elements.at(0)); 13 | (this)->relativeMPB = stof(elements.at(1)); 14 | (this)->meter = stoi(elements.at(2)); 15 | 16 | // if the timing point MPB is relative, calculate the realMPB base on lastPositiveMPB 17 | if ((this)->relativeMPB < 0) { 18 | // exception case where (fking bs) mapper customly set the relative timing point to less than -1000, which is not recognized by osu 19 | if ((this)->relativeMPB < -1000) { 20 | (this)->relativeMPB = -1000; 21 | } 22 | float percentage = (this)->relativeMPB / -100; 23 | (this)->realMPB = *lastPositiveMPB * percentage; 24 | } 25 | else { // if current timing point MPB is not relative (+ve real number), update the lastPositiveMPB to currentMPB 26 | (this)->realMPB = (this)->relativeMPB; 27 | *lastPositiveMPB = (this)->relativeMPB; 28 | } 29 | } -------------------------------------------------------------------------------- /OsuBots.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OsuBots", "OsuBots\OsuBots.vcxproj", "{7E228D58-1FC7-402A-8230-B04E26E40698}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Debug|x64.ActiveCfg = Debug|x64 17 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Debug|x64.Build.0 = Debug|x64 18 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Debug|x86.ActiveCfg = Debug|Win32 19 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Debug|x86.Build.0 = Debug|Win32 20 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Release|x64.ActiveCfg = Release|x64 21 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Release|x64.Build.0 = Release|x64 22 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Release|x86.ActiveCfg = Release|Win32 23 | {7E228D58-1FC7-402A-8230-B04E26E40698}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /OsuBots/HitObject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Functions.h" 10 | #include "TimingPoint.h" 11 | #include "beatmapRelatedStructs.h" 12 | 13 | using namespace std; 14 | 15 | class HitObject 16 | { 17 | public: 18 | enum class TypeE { 19 | circle, 20 | slider, 21 | spinner 22 | }; 23 | 24 | HitObject(string hitObjectLine, vector TimingPoints, int* timingPointIndex, DifficultyS Difficulty); 25 | 26 | // Shared by all hitObjects 27 | int x; 28 | int y; 29 | int time; 30 | TypeE type; 31 | 32 | // -----------------(Variables below this line WILL NOT be used if the hitobject is not of the types!)----------------- 33 | // Sliders: 34 | char sliderType; 35 | vector> CurvePoints; 36 | int repeat; 37 | float pixelLength; 38 | bool sliderPointsAreCalculated; 39 | // calculated variables 40 | double sliderDuration; 41 | vector pointsOnCurve; 42 | 43 | // Spinners: 44 | int spinnerEndTime; 45 | 46 | private: 47 | // storing info into member variables 48 | void processHitObjectLine(string hitObjectLine, vector TimingPoints, int* timingPointIndex, DifficultyS Difficulty); 49 | 50 | // calculations 51 | // calculate timing points related 52 | float getRealCurrentMPB(int hitObjectTime, vector TimingPoints, int* timingPointIndex); 53 | }; 54 | 55 | -------------------------------------------------------------------------------- /OsuBots/OsuDbParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | typedef struct OsuDbBeatmapDataMin // assume version >= 20140609 10 | { 11 | string artistName; 12 | string songTitle; 13 | string creatorName; 14 | string difficulty; 15 | string nameOfOsuFile; 16 | float AR; 17 | float CS; 18 | float HP; 19 | float OD; 20 | double sliderVelocity; 21 | unsigned int drainTime; // in sec 22 | unsigned int totalTime; // in milli sec 23 | unsigned int beatmapID; 24 | unsigned int beatmapSetID; 25 | unsigned short localOffset; 26 | unsigned short onlineOffset; 27 | string folderName; 28 | } OsuDbBeatmapDataMinS; 29 | 30 | class OsuDbParser 31 | { 32 | public: 33 | // constructor and destructor 34 | OsuDbParser(); // for class member variable 35 | OsuDbParser(string fullPathToOsuDb); // straight away parse data 36 | ~OsuDbParser(); 37 | 38 | // member variables 39 | unsigned int osuVersion; 40 | unsigned int folderCount; 41 | bool accountUnlocked; 42 | string playerName; 43 | unsigned int numberOfBeatmaps; 44 | unordered_map> beatmapsMin; 45 | 46 | // functions 47 | void startParsingData(string fullPathToOsuDb); 48 | 49 | private: 50 | // general functions 51 | bool readBool(ifstream &osuDbHandle); 52 | unsigned char readChar(ifstream &osuDbHandle); 53 | unsigned short readShort(ifstream &osuDbHandle); 54 | unsigned int readInt(ifstream &osuDbHandle); 55 | unsigned long long readLong(ifstream &osuDbHandle); // long in osu! webpage == longlong in c++ 56 | unsigned int readByteToInt(ifstream &osuDbHandle, int size); 57 | float readFloat(ifstream &osuDbHandle); 58 | double readDouble(ifstream &osuDbHandle); 59 | string readString(ifstream &osuDbHandle); 60 | 61 | void skipBytes(ifstream &osuDbHandle, int knownSize = 0, bool isString = true); 62 | void processDataMin(string fullPathToOsuDb); 63 | }; 64 | 65 | -------------------------------------------------------------------------------- /OsuBots/Functions.cpp: -------------------------------------------------------------------------------- 1 | #include "Functions.h" 2 | 3 | template 4 | void Functions::split(const string &s, char delim, Out result) { 5 | stringstream ss(s); 6 | string item; 7 | while (getline(ss, item, delim)) { 8 | *(result++) = item; 9 | } 10 | } 11 | 12 | vector Functions::split(const string &s, char delim) { 13 | vector elems; 14 | split(s, delim, back_inserter(elems)); 15 | return elems; 16 | } 17 | 18 | //! returns if a equals b, taking possible rounding errors into account 19 | bool Functions::almostEquals(const float a, const float b, const float tolerance) 20 | { 21 | return abs(a - b) < tolerance; 22 | } 23 | 24 | FPointS Functions::bezierCurve(vector curvePoints, float t) { 25 | // credit to Amryu from https://osu.ppy.sh/community/forums/topics/606522 26 | double bx = 0; 27 | double by = 0; 28 | int n = curvePoints.size() - 1; // degree 29 | if (n == 1) { // if linear 30 | bx = (1 - t) * curvePoints.at(0).x + t * curvePoints.at(1).x; 31 | by = (1 - t) * curvePoints.at(0).y + t * curvePoints[1].y; 32 | } 33 | else if (n == 2) { // if quadratic 34 | bx = (1 - t) * (1 - t) * curvePoints.at(0).x + 2 * (1 - t) * t * curvePoints.at(1).x + t * t * curvePoints.at(2).x; 35 | by = (1 - t) * (1 - t) * curvePoints.at(0).y + 2 * (1 - t) * t * curvePoints.at(1).y + t * t * curvePoints.at(2).y; 36 | } 37 | else if (n == 3) { // if cubic 38 | bx = (1 - t) * (1 - t) * (1 - t) * curvePoints.at(0).x + 3 * (1 - t) * (1 - t) * t * curvePoints.at(1).x + 3 * (1 - t) * t * t * curvePoints.at(2).x + t * t * t * curvePoints.at(3).x; 39 | by = (1 - t) * (1 - t) * (1 - t) * curvePoints.at(0).y + 3 * (1 - t) * (1 - t) * t * curvePoints.at(1).y + 3 * (1 - t) * t * t * curvePoints.at(2).y + t * t * t * curvePoints.at(3).y; 40 | } 41 | else { 42 | for (int i = 0; i <= n; i++) { 43 | bx += Functions::binomialCoef(n, i) * pow(1 - t, n - i) * pow(t, i) * curvePoints.at(i).x; 44 | by += Functions::binomialCoef(n, i) * pow(1 - t, n - i) * pow(t, i) * curvePoints.at(i).y; 45 | } 46 | } 47 | FPointS p; 48 | p.x = bx; 49 | p.y = by; 50 | return p; 51 | } 52 | 53 | // just some math formula 54 | double Functions::binomialCoef(int n, int k) { 55 | // credit to Amryu from https://osu.ppy.sh/community/forums/topics/606522 56 | double r = 1; 57 | if (k > n) { 58 | return 0; 59 | } 60 | for (int d = 1; d <= k; d++) { 61 | r *= n--; 62 | r /= d; 63 | } 64 | return r; 65 | } 66 | 67 | mt19937 Functions::generator = mt19937(random_device()()); 68 | 69 | double Functions::randomNumGenerator(int variation) { 70 | double randomNum = 0; 71 | if (variation != 0) { 72 | randomNum = uniform_real_distribution<>(-variation, variation)(Functions::generator); 73 | } 74 | return randomNum; 75 | } 76 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /OsuBots/OsuBot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "ProcessTools.h" 11 | #include "SigScanner.h" 12 | #include "Beatmap.h" 13 | #include "HitObject.h" 14 | #include "Input.h" 15 | #include "OsuDbParser.h" 16 | #include "Config.h" 17 | #include "Timer.h" 18 | 19 | using namespace std; 20 | 21 | // Most exception handling occurs in this class 22 | #include 23 | class OsuBotException : public runtime_error 24 | { 25 | public: 26 | OsuBotException(string mess) : runtime_error(mess) {} 27 | }; 28 | 29 | 30 | class OsuBot 31 | { 32 | public: 33 | // constructor & destructor 34 | OsuBot(wchar_t* processName); 35 | ~OsuBot(); 36 | 37 | // consts 38 | const static unsigned char TIME_SIG[]; // { 0xDB, 0x5D, 0xE8, 0x8B, 0x45, 0xE8, 0xA3 }; 39 | const static char* TIME_MASK; // "xxxxxxx"; 40 | const static int TIME_SIG_OFFSET; // 6 + 1; 41 | /*const static unsigned char PAUSE_SIGNAL_SIG[]; 42 | const static char* PAUSE_SIGNAL_MASK; 43 | const static int PAUSE_SIGNAL_SIG_OFFSET;*/ 44 | 45 | // functions 46 | void start(); 47 | 48 | private: 49 | // --------------------------variables----------------------------- 50 | // Process related variables 51 | DWORD processID; 52 | HANDLE osuHandle; 53 | HWND windowHandle; 54 | HWND windowTitleHandle; 55 | DWORD currentAudioTimeAddress; 56 | DWORD pauseSignalAddress; 57 | 58 | // Gameplay related variables 59 | float pointsMultiplierX; 60 | float pointsMultiplierY; 61 | POINT cursorStartPoints; 62 | bool isPlaying; 63 | 64 | // Beatmap related variables 65 | OsuDbParser osuDbMin; 66 | 67 | // -------------------------functions----------------------- 68 | // Process related functions 69 | bool processIsOpen(); 70 | DWORD getCurrentAudioTimeAddress(); 71 | int getCurrentAudioTime(); 72 | //DWORD getPauseSignalAddress(); 73 | //int getPauseSignal(); 74 | void updateIsPlaying(); 75 | 76 | // Gameplay related functions 77 | void setCursorStartPoints(); 78 | POINT getScaledPoints(int x, int y); 79 | void recalcSliderDuration(double &sliderDuration, unsigned int mod, double randomNum = 0); 80 | double getMoveToNextPointDuration(HitObject currentHitObject, HitObject nextHitObject, unsigned int mod, unsigned int divFactor); 81 | double getSpinDuration(HitObject currentHitObject, unsigned int mod); 82 | 83 | // Mods 84 | void startMod(Beatmap beatmap, unsigned int bot, unsigned int mod); // wrapper 85 | void modRelax(Beatmap beatmap, unsigned int mod); 86 | void modAutoPilot(Beatmap beatmap, unsigned int mod); 87 | void modAuto(Beatmap beatmap, unsigned int mod); 88 | 89 | // Calculations (pass by ref) 90 | void recalcHitObjectsAndSetPointsOnCurve(Beatmap &beatmap, unsigned int mod, future futureObj); 91 | void calcAndSetNewBeatmapAttributes(Beatmap &beatmap, unsigned int mod); 92 | }; 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New updates 2 | ## [2018/8/5] This project is discontinued. 3 | 4 | New setting - CLICK_OFFSET_DEVIATION 5 | 6 | #### Some important notes for users such as How to use, Known Issues, and Debug have been moved to the [User Documentation Wiki page](https://github.com/CookieHoodie/OsuBot/wiki/User-Documentation). Please read through the notes if you're using this bot! 7 | 8 | Now you can change the timing related offset to suit your gameplay in the settings screen. 9 | 10 | Stack circles problems are solved, only certain special cases (which are pretty unlikely to cause problem) are not accounted for. 11 | 12 | DT and HR DT are now supported! 13 | 14 | 15 | # OsuBot 16 | A bot which, for now, does auto, relax, and autopilot mod in osu!. 17 | 18 | Most of the codes come with comments, but feel free to ask me if you have any question regarding the codes! 19 | 20 | #### **This can only be run on windows platform, and is not supposed to be used in multiplayer. Restart is required if new beatmap is downloaded. 21 | 22 | Click on the image below to watch the demo: 23 | 24 | [![Osubot Auto demo](http://img.youtube.com/vi/fZAvsehGjHM/0.jpg)](http://www.youtube.com/watch?v=fZAvsehGjHM "OsuBot (CH) Auto mod demo") 25 | 26 | # Features 27 | Auto, Autopilot, and Relax with Nomod, HR only, DT only, or HR DT. 28 | 29 | Auto detection of currently played beatmap. 30 | 31 | Custom timing related offsets (can be changed when pausing). 32 | 33 | 34 | # Code overview 35 | ### Beatmap class, HitObject class, TimingPoint class, and beatmapRelatedStructs: 36 | These classes and header files are for parsing .osu file into a Beatmap object, which is used by the bots to play maps. 37 | 38 | ### Functions class: 39 | Contain general functions which are shared between classes. 40 | 41 | ### ProcessTools class: 42 | Contain functions dealing with windows processes. 43 | 44 | ### Config namespace: 45 | Place where constants such as OSUROOTPATH and LEFT_KEY are stored which can be accessed by other classes. User can also manipulate these settings in realtime during the gameplay. 46 | 47 | ### Timer class: 48 | Centralized place where timing settings are located just so changes related to timing are only made here. 49 | 50 | ### SigScanner class: 51 | Class dedicated to signature scanning. 52 | 53 | ### Input class: 54 | Contain functions for sending inputs (keypress, cursor position) during gameplay. 55 | 56 | ### OsuDbParser class: 57 | Class used for parsing osu!.db data into an object for faster access to each beatmap location. 58 | 59 | ### OsuBot class: 60 | Actual class that implements bots and interface. 61 | 62 | 63 | # User must know 64 | Go to [User Documentation Wiki page](https://github.com/CookieHoodie/OsuBot/wiki/User-Documentation) for more info. 65 | 66 | 67 | # Disclaimer 68 | I don't use the bot to cheat whatsoever, I just created it for fun and for learning. 69 | 70 | I'm not responsible for any ban for using this. 71 | 72 | 73 | # Support 74 | If you find this useful, please support me to help me get a better laptop! 75 | 76 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/CookieHoodie/0USD) 77 | -------------------------------------------------------------------------------- /OsuBots/OsuBots.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | Source Files 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | Source Files 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | 97 | 98 | Resource Files 99 | 100 | 101 | 102 | 103 | Resource Files 104 | 105 | 106 | Resource Files 107 | 108 | 109 | Resource Files 110 | 111 | 112 | -------------------------------------------------------------------------------- /OsuBots/SigScanner.cpp: -------------------------------------------------------------------------------- 1 | #include "SigScanner.h" 2 | 3 | // credit to Aixxe from https://aixxe.net/2016/10/osu-game-hacking 4 | DWORD SigScanner::findPattern(HANDLE processHandle, const unsigned char pattern[], const char* mask, const int offset, size_t begin) { 5 | // pattern in format: unsigned char pattern[] = { 0x90, 0xFF, 0xEE }; 6 | // mask in format: char* mask = "xxx?xxx"; 7 | // begin default is 0 8 | // this function searches the signature from begin or 0x00000000(default) to 0x7FFFFFFF 9 | const size_t signature_size = strlen(mask); 10 | const size_t read_size = 4096; 11 | bool hit = false; 12 | 13 | unsigned char chunk[read_size]; 14 | 15 | for (size_t i = begin; i < INT_MAX; i += read_size - signature_size) { 16 | ReadProcessMemory(processHandle, LPCVOID(i), &chunk, read_size, NULL); 17 | 18 | for (size_t a = 0; a < read_size; a++) { 19 | hit = true; 20 | 21 | for (size_t j = 0; j < signature_size && hit; j++) { 22 | if (mask[j] != '?' && chunk[a + j] != pattern[j]) { 23 | hit = false; 24 | } 25 | } 26 | 27 | if (hit) { 28 | return i + a + offset; 29 | } 30 | } 31 | } 32 | 33 | return NULL; 34 | } 35 | 36 | //void* SigScanner::patternScan(char* base, size_t size, char* pattern, char* mask) 37 | //{ 38 | // size_t patternLength = strlen(mask); 39 | // 40 | // for (unsigned int i = 0; i < size - patternLength; i++) 41 | // { 42 | // bool found = true; 43 | // for (unsigned int j = 0; j < patternLength; j++) 44 | // { 45 | // if (mask[j] != '?' && pattern[j] != *(base + i + j)) 46 | // { 47 | // found = false; 48 | // break; 49 | // } 50 | // } 51 | // if (found) 52 | // { 53 | // return (void*)(base + i); 54 | // } 55 | // } 56 | // return nullptr; 57 | //} 58 | // 59 | //void* SigScanner::patternScanEx(HANDLE osuProcessHandle, uintptr_t begin, uintptr_t end, char* pattern, char* mask, int offset) 60 | //{ 61 | // uintptr_t currentChunk = begin; 62 | // SIZE_T bytesRead; 63 | // 64 | // while (currentChunk < end) 65 | // { 66 | // char buffer[4096]; 67 | // 68 | // DWORD oldprotect; 69 | // VirtualProtectEx(osuProcessHandle, (void*)currentChunk, sizeof(buffer), PAGE_EXECUTE_READWRITE, &oldprotect); 70 | // ReadProcessMemory(osuProcessHandle, (void*)currentChunk, &buffer, sizeof(buffer), &bytesRead); 71 | // VirtualProtectEx(osuProcessHandle, (void*)currentChunk, sizeof(buffer), oldprotect, &oldprotect); 72 | // 73 | // if (bytesRead == 0) 74 | // { 75 | // return nullptr; 76 | // } 77 | // 78 | // void* internalAddress = SigScanner::patternScan((char*)&buffer, bytesRead, pattern, mask); 79 | // 80 | // if (internalAddress != nullptr) 81 | // { 82 | // //calculate from internal to external 83 | // uintptr_t offsetFromBuffer = (uintptr_t)internalAddress - (uintptr_t)&buffer; 84 | // return (void*)(currentChunk + offsetFromBuffer + offset); 85 | // } 86 | // else 87 | // { 88 | // //advance to next chunk 89 | // currentChunk = currentChunk + bytesRead; 90 | // } 91 | // } 92 | // return nullptr; 93 | //} 94 | 95 | 96 | //void* SigScanner::patternScanEx(DWORD processID, uintptr_t begin, uintptr_t end, char* pattern, char* mask, int offset) 97 | //{ 98 | // HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processID); 99 | // uintptr_t currentChunk = begin; 100 | // SIZE_T bytesRead; 101 | // 102 | // while (currentChunk < end) 103 | // { 104 | // char buffer[4096]; 105 | // 106 | // DWORD oldprotect; 107 | // VirtualProtectEx(hProcess, (void*)currentChunk, sizeof(buffer), PAGE_EXECUTE_READWRITE, &oldprotect); 108 | // ReadProcessMemory(hProcess, (void*)currentChunk, &buffer, sizeof(buffer), &bytesRead); 109 | // VirtualProtectEx(hProcess, (void*)currentChunk, sizeof(buffer), oldprotect, &oldprotect); 110 | // 111 | // if (bytesRead == 0) 112 | // { 113 | // return nullptr; 114 | // } 115 | // 116 | // void* internalAddress = SigScanner::patternScan((char*)&buffer, bytesRead, pattern, mask); 117 | // 118 | // if (internalAddress != nullptr) 119 | // { 120 | // //calculate from internal to external 121 | // uintptr_t offsetFromBuffer = (uintptr_t)internalAddress - (uintptr_t)&buffer; 122 | // return (void*)(currentChunk + offsetFromBuffer + offset); 123 | // } 124 | // else 125 | // { 126 | // //advance to next chunk 127 | // currentChunk = currentChunk + bytesRead; 128 | // } 129 | // } 130 | // return nullptr; 131 | //} 132 | 133 | //void* SigScanner::patternScanExModule(DWORD processID, wchar_t* module, char* pattern, char* mask, int offset) 134 | //{ 135 | // MODULEENTRY32 modEntry = ProcessTools::getModule(processID, module); 136 | // 137 | // if (!modEntry.th32ModuleID) 138 | // { 139 | // return nullptr; 140 | // } 141 | // uintptr_t begin = (uintptr_t)modEntry.modBaseAddr; 142 | // uintptr_t end = begin + modEntry.modBaseSize; 143 | // return SigScanner::patternScanEx(processID, begin, end, pattern, mask, offset); 144 | //} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | /OsuBots/config.txt 244 | /Setup 245 | -------------------------------------------------------------------------------- /OsuBots/HitObject.cpp: -------------------------------------------------------------------------------- 1 | #include "HitObject.h" 2 | 3 | // -----------------------------------Constructor & Destructor--------------------------------------- 4 | HitObject::HitObject(string hitObjectLine, vector TimingPoints, int* timingPointIndex, DifficultyS Difficulty) 5 | { 6 | processHitObjectLine(hitObjectLine, TimingPoints, timingPointIndex, Difficulty); 7 | } 8 | 9 | // ----------------------------Private function that actually stores info into member variables--------------------------- 10 | void HitObject::processHitObjectLine(string hitObjectLine, vector TimingPoints, int* timingPointIndex, DifficultyS Difficulty) { 11 | // remove white spaces 12 | hitObjectLine.erase(remove_if(hitObjectLine.begin(), hitObjectLine.end(), isspace), hitObjectLine.end()); 13 | // split strings into list 14 | vector elements = Functions::split(hitObjectLine, ','); 15 | 16 | // storing common info 17 | (this)->x = stoi(elements.at(0)); 18 | (this)->y = stoi(elements.at(1)); 19 | (this)->time = stoi(elements.at(2)); 20 | int type = stoi(elements.at(3)); 21 | // determining hitObject type 22 | if ((type & 1) == 1) { 23 | (this)->type = TypeE::circle; 24 | } 25 | else if ((type & 2) == 2) { 26 | (this)->type = TypeE::slider; 27 | (this)->sliderType = elements.at(5).at(0); 28 | string curvePointsString = elements.at(5).erase(0, 2); // erase sliderType and preceding '|' --> x:y|x:y|... 29 | vector pointsString = Functions::split(curvePointsString, '|'); // --> x:y, x:y, ... 30 | 31 | // vector for grouping and seperating connected curvePoints (for Bezier curve) 32 | vector tempVector; 33 | // storing currentObject.x and y in the first element of curvePointV for easier calculation 34 | tempVector.push_back(CurvePointsS((this)->x, (this)->y)); 35 | // storing the first element in the vector for reference of oldX and oldY later to eliminate repeated curvePoints 36 | int index = pointsString.at(0).find(':'); 37 | int x = stoi(pointsString.at(0).substr(0, index)); 38 | int y = stoi(pointsString.at(0).substr(index + 1)); 39 | tempVector.push_back(CurvePointsS(x, y)); 40 | for (int i = 1; i < pointsString.size(); i++) { // loop through the rest of the elements 41 | index = pointsString.at(i).find(':'); 42 | int newX = stoi(pointsString.at(i).substr(0, index)); 43 | int newY = stoi(pointsString.at(i).substr(index + 1)); 44 | // if the curvePoints is same as the last one, push the last grouped curvePoints in tempVector to real member vector 45 | if (newX == x && newY == y) { 46 | (this)->CurvePoints.push_back(tempVector); 47 | tempVector.clear(); // clean up for reuse 48 | tempVector.push_back(CurvePointsS(newX, newY)); // new group of curvePoints 49 | } 50 | else { // if not same, continue to push into tempVector 51 | tempVector.push_back(CurvePointsS(newX, newY)); 52 | // update value of oldX and oldY for next loop 53 | x = newX; 54 | y = newY; 55 | } 56 | } 57 | // after done pushing curvePoints into tempVector, push the last (or first for non-bezier curve) tempVector itself into the member vector 58 | (this)->CurvePoints.push_back(tempVector); 59 | 60 | (this)->repeat = stoi(elements.at(6)); 61 | (this)->pixelLength = stof(elements.at(7)); 62 | 63 | // calculated variable 64 | float realCurrentMPB = (this)->getRealCurrentMPB((this)->time, TimingPoints, timingPointIndex); 65 | (this)->sliderDuration = (this)->pixelLength / (100.0 * Difficulty.sliderMultiplier) * realCurrentMPB * (this)->repeat; 66 | //(this)->calcAndSetPointsOnCurve(); 67 | (this)->sliderPointsAreCalculated = false; 68 | } 69 | else if ((type & 8) == 8) { 70 | (this)->type = TypeE::spinner; 71 | (this)->spinnerEndTime = stoi(elements.at(5)); 72 | } 73 | } 74 | 75 | // -------------------------------Current Real Timing Points calculation related------------------------------- 76 | float HitObject::getRealCurrentMPB(int hitObjectTime, vector TimingPoints, int* timingPointIndex) { 77 | // determining which timing point this hitObject is currently at and return MPB at that timing point for calculation 78 | float currentMPB; 79 | // if at last element of TimingPoints 80 | if (*timingPointIndex == TimingPoints.size() - 1) { 81 | // currentMPB is sure to be the MPB of last timing point 82 | currentMPB = TimingPoints.at(*timingPointIndex).realMPB; 83 | } 84 | else { // normal case 85 | 86 | // if not yet next timing point 87 | if ((hitObjectTime >= TimingPoints.at(*timingPointIndex).offset && hitObjectTime < TimingPoints.at(*timingPointIndex + 1).offset) 88 | // account for weird case where hitObject offset comes b4 timing point offset 89 | || (hitObjectTime < TimingPoints.at(*timingPointIndex).offset && (*timingPointIndex) == 0)) { 90 | currentMPB = TimingPoints.at(*timingPointIndex).realMPB; 91 | } 92 | else { // if it's next timing point 93 | int counter = 1; 94 | // if last element is reached, MPB of last timing point is set, else 95 | while (*timingPointIndex + counter < TimingPoints.size() - 1 96 | // continues to proceed to the nearest timing points if there are multiple sandwiched between the hitObject time 97 | && TimingPoints.at(*timingPointIndex + 1 + counter).offset <= hitObjectTime) { 98 | counter++; 99 | } 100 | currentMPB = TimingPoints.at(*timingPointIndex + counter).realMPB; 101 | *timingPointIndex += counter; // update the index referring to current timing point position 102 | } 103 | } 104 | return currentMPB; 105 | } -------------------------------------------------------------------------------- /OsuBots/Beatmap.cpp: -------------------------------------------------------------------------------- 1 | #include "Beatmap.h" 2 | 3 | // -----------------------------------Constructor & Destructor--------------------------------------- 4 | Beatmap::Beatmap(string fullPathAfterSongsFolder) 5 | { 6 | //(this)->fileName = fileName; 7 | //(this)->setFullPathBeatmapFileName(); // deprecated 8 | (this)->fullPathBeatmapFileName = Config::SONGFOLDER + fullPathAfterSongsFolder; 9 | (this)->processBeatmap(); 10 | } 11 | 12 | // ------------------------------------General functions----------------------------------------------- 13 | // deprecated 14 | //void Beatmap::setFullPathBeatmapFileName() { // only depends on (this)->fileName and Beatmap::FOLDERPATH 15 | // string folderName = (this)->fileName.substr(0, (this)->fileName.find_last_of("(") - 1); // get name for finding folder (-1 to remove white space) 16 | // folderName.erase(remove_if(folderName.begin(), folderName.end(), [](char c) { return c == '.'; }), folderName.end()); // remove all dots 17 | // string songFolder = ""; 18 | // for (auto &p : fs::directory_iterator(Beatmap::FOLDERPATH)) { 19 | // if (p.path().string().find(folderName) != string::npos) { 20 | // songFolder = p.path().string(); 21 | // break; 22 | // } 23 | // } 24 | // if (!songFolder.empty()) { 25 | // fs::path dir(songFolder); 26 | // fs::path file(fileName); 27 | // fs::path full_path = dir / file; 28 | // (this)->fullPathBeatmapFileName = full_path.string(); 29 | // } 30 | // else { 31 | // (this)->fullPathBeatmapFileName = ""; 32 | // } 33 | //} 34 | 35 | void Beatmap::processBeatmap() { // only depends on (this)->fullPathBeatmapFileName 36 | ifstream osuFile; 37 | osuFile.open((this)->fullPathBeatmapFileName, ios::in); 38 | 39 | if (osuFile) { 40 | bool general = false; 41 | bool difficulty = false; 42 | bool hitObjects = false; 43 | bool timingPoints = false; 44 | int timingPointIndex = 0; // initialize timingIndex to point to 1st element of TimingPoints vector, and pass it byRef later 45 | float lastPositiveMPB = 0; // variable that stores the last realMPB(+ve MPB). Initialize to zero actually doesn't mean anything as it will change anyway. This is ByRef 46 | string line; 47 | while (getline(osuFile, line)) 48 | { 49 | if (line.find("[General]") != string::npos) { 50 | general = true; 51 | } 52 | else if (general) { 53 | if (line.find("AudioLeadIn") != string::npos) { 54 | (this)->General.audioLeadIn = stoi(line.substr(line.find(':') + 1)); 55 | } 56 | else if (line.find("StackLeniency") != string::npos) { 57 | (this)->General.stackLeniency = stof(line.substr(line.find(':') + 1)); 58 | } 59 | else if (line.find(':') == string::npos) { 60 | general = false; 61 | } 62 | } 63 | else if (line.find("[Difficulty]") != string::npos) { 64 | difficulty = true; 65 | } 66 | else if (difficulty) { 67 | if (line.find("HPDrainRate") != string::npos) { 68 | (this)->Difficulty.hpDrainRate = stof(line.substr(line.find(':') + 1)); 69 | } 70 | else if (line.find("CircleSize") != string::npos) { 71 | (this)->Difficulty.circleSize = stof(line.substr(line.find(':') + 1)); 72 | } 73 | else if (line.find("OverallDifficulty") != string::npos) { 74 | (this)->Difficulty.overallDifficulty = stof(line.substr(line.find(':') + 1)); 75 | } 76 | else if (line.find("ApproachRate") != string::npos) { 77 | (this)->Difficulty.approachRate = stof(line.substr(line.find(':') + 1)); 78 | } 79 | else if (line.find("SliderMultiplier") != string::npos) { 80 | (this)->Difficulty.sliderMultiplier = stof(line.substr(line.find(':') + 1)); 81 | } 82 | else if (line.find("SliderTickRate") != string::npos) { 83 | (this)->Difficulty.sliderTickRate = stoi(line.substr(line.find(':') + 1)); 84 | } 85 | else if (line.find(':') == string::npos) { 86 | difficulty = false; 87 | } 88 | } 89 | else if (line.find("[TimingPoints]") != string::npos) { 90 | timingPoints = true; 91 | } 92 | else if (timingPoints) { 93 | if (line.find(',') != string::npos) { 94 | (this)->TimingPoints.push_back(TimingPoint(line, &lastPositiveMPB)); // pass in the lastPositiveMPB for calculating the realMPB 95 | } 96 | else { 97 | timingPoints = false; 98 | } 99 | } 100 | else if (line.find("[HitObjects]") != string::npos) { 101 | hitObjects = true; 102 | } 103 | else if (hitObjects) { 104 | // passing in TimingPoints so that it can be determined which timing point this hitObject line is currently at for calculation 105 | // timingPointIndex helps to do that 106 | // difficulty is also for calculation, primarily for sliders 107 | (this)->HitObjects.push_back(HitObject(line, (this)->TimingPoints, &timingPointIndex, (this)->Difficulty)); 108 | } 109 | } 110 | // calculated variables 111 | (this)->circleRadius = 54.4 - 4.48 * (this)->Difficulty.circleSize; 112 | (this)->timeRange50 = abs(150 + 50 * (5 - (this)->Difficulty.overallDifficulty) / 5); 113 | (this)->timeRange100 = abs(100 + 40 * (5 - (this)->Difficulty.overallDifficulty) / 5); 114 | (this)->timeRange300 = abs(50 + 30 * (5 - (this)->Difficulty.overallDifficulty) / 5); 115 | (this)->approachWindow = Beatmap::calcApproachWindow((this)->Difficulty.approachRate); 116 | 117 | (this)->allSet = true; 118 | osuFile.close(); 119 | } 120 | else { 121 | (this)->allSet = false; 122 | } 123 | } 124 | 125 | // from https://github.com/ppy/osu/blob/9e7728d6b38dfdd61e0891ca7a9277fb18775c28/osu.Game/Beatmaps/BeatmapDifficulty.cs 126 | double Beatmap::calcApproachWindow(double AR, double min, double mid, double max) { 127 | if (AR > 5) 128 | return mid + (max - mid) * (AR - 5) / 5; 129 | if (AR < 5) 130 | return mid - (mid - min) * (5 - AR) / 5; 131 | return mid; 132 | } -------------------------------------------------------------------------------- /OsuBots/ProcessTools.cpp: -------------------------------------------------------------------------------- 1 | #include "ProcessTools.h" 2 | 3 | DWORD ProcessTools::getProcessID(const wchar_t* processName) 4 | // get processID from processName given 5 | // processName etc. L"chrome.exe" 6 | // return processID if found, null if not 7 | { 8 | DWORD process_id = NULL; 9 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 10 | 11 | PROCESSENTRY32 entry = { 0 }; 12 | entry.dwSize = sizeof(PROCESSENTRY32); 13 | 14 | if (Process32First(snapshot, &entry)) { 15 | do { 16 | if (_wcsicmp(entry.szExeFile, processName) == 0) { 17 | process_id = entry.th32ProcessID; 18 | break; 19 | } 20 | } while (Process32Next(snapshot, &entry)); 21 | } 22 | 23 | CloseHandle(snapshot); 24 | return process_id; 25 | } 26 | 27 | // window handle for calculating cursor 28 | HWND ProcessTools::getWindowHandle(DWORD processID) 29 | { 30 | //This works fine until the osu is in fullscreen--the window title cannot be retrieved from this handle 31 | std::pair params = { 0, processID }; 32 | 33 | // Enumerate the windows using a lambda to process each window 34 | BOOL bResult = EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL 35 | { 36 | auto pParams = (std::pair*)(lParam); 37 | 38 | DWORD processId; 39 | if (GetWindowThreadProcessId(hwnd, &processId) && processId == pParams->second) 40 | { 41 | // Stop enumerating 42 | SetLastError(-1); 43 | pParams->first = hwnd; 44 | return FALSE; 45 | } 46 | 47 | // Continue enumerating 48 | return TRUE; 49 | }, (LPARAM)¶ms); 50 | 51 | if (!bResult && GetLastError() == -1 && params.first) 52 | { 53 | return params.first; 54 | } 55 | 56 | return 0; 57 | } 58 | 59 | // get windowHandle for retrieving window title 60 | HWND ProcessTools::getWindowTitleHandle(LPCSTR windowTitle) { 61 | HWND windowHandle = FindWindowA(NULL, windowTitle); 62 | return windowHandle; // return NULL if not found 63 | } 64 | 65 | // read title in string 66 | string ProcessTools::getWindowTextString(HWND windowHandle) { 67 | char ctitle[255]; 68 | GetWindowTextA(windowHandle, ctitle, 255); 69 | return string(ctitle); 70 | } 71 | 72 | // prompt user to choose file and return the filename in string 73 | string ProcessTools::promptToChooseFileAndGetPath(LPCWSTR customTitle) { 74 | IFileDialog *pfd; 75 | string returnStr = ""; 76 | if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) 77 | { 78 | if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast(&pfd)))) 79 | { 80 | if (customTitle != L"") { 81 | pfd->SetTitle(customTitle); 82 | } 83 | if (SUCCEEDED(pfd->Show(NULL))) 84 | { 85 | IShellItem *psi; 86 | if (SUCCEEDED(pfd->GetResult(&psi))) 87 | { 88 | PWSTR pszFilePath; 89 | if (SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath))) 90 | { 91 | wstring wpath(pszFilePath); 92 | returnStr = string(wpath.begin(), wpath.end()); 93 | } 94 | psi->Release(); 95 | } 96 | } 97 | pfd->Release(); 98 | } 99 | CoUninitialize(); 100 | } 101 | return returnStr; 102 | } 103 | 104 | // save the output on console into buffer 105 | bool ProcessTools::saveConsoleBuffer(CONSOLE_SCREEN_BUFFER_INFO &csbi, PCHAR_INFO consoleBuffer) { 106 | HANDLE hStdout; 107 | COORD coordBufCoord; 108 | 109 | hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 110 | if (!GetConsoleScreenBufferInfo(hStdout, &csbi)) { 111 | return false; 112 | } 113 | // The top left destination cell of the temporary buffer is 114 | // row 0, col 0. 115 | coordBufCoord.X = 0; 116 | coordBufCoord.Y = 0; 117 | // Copy the block from the screen buffer to the temp. buffer. 118 | if (!ReadConsoleOutput( 119 | hStdout, // screen buffer to read from 120 | consoleBuffer, // buffer to copy into 121 | csbi.dwMaximumWindowSize, // col-row size of chiBuffer 122 | coordBufCoord, // top left dest. cell in chiBuffer 123 | &csbi.srWindow)) // screen buffer source rectangle 124 | { 125 | return false; 126 | } 127 | return true; 128 | } 129 | 130 | // write data in the buffer to the console output 131 | bool ProcessTools::restoreConsoleBuffer(CONSOLE_SCREEN_BUFFER_INFO &csbi, PCHAR_INFO consoleBuffer) { 132 | HANDLE hStdout; 133 | COORD coordBufCoord; 134 | 135 | hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 136 | 137 | // The top left destination cell of the temporary buffer is 138 | // row 0, col 0. 139 | coordBufCoord.X = 0; 140 | coordBufCoord.Y = 0; 141 | // Copy the block from the screen buffer to the temp. buffer. 142 | if (!WriteConsoleOutput( 143 | hStdout, // screen buffer to write to 144 | consoleBuffer, // buffer to copy from 145 | csbi.dwMaximumWindowSize, // col-row size of chiBuffer 146 | coordBufCoord, // top left src cell in chiBuffer 147 | &csbi.srWindow)) // dest. screen buffer rectangle 148 | { 149 | return false; 150 | } 151 | SetConsoleCursorPosition(hStdout, csbi.dwCursorPosition); 152 | return true; 153 | } 154 | 155 | //int ProcessTools::writeToMemory(DWORD processID, void* memoryAddress, byte* input, int size) 156 | //// write value in input to memoryAddress given 157 | //// size of input determines how many bytes of data to write to the specific address 158 | //// return 0 if write fails, other than 0 if successes 159 | //{ 160 | // int result = 0; 161 | // HANDLE handle = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, false, processID); 162 | // if (handle != NULL) { 163 | // result = WriteProcessMemory(handle, (LPVOID)memoryAddress, (void*)input, size, 0); 164 | // } 165 | // return result; 166 | //} 167 | // 168 | //bool ProcessTools::readFromMemory(HANDLE hProcess, void* memoryAddress, void* output, int size) 169 | //// read from memoryAddress given 170 | //{ 171 | // bool result = false; 172 | // SIZE_T bytesRead; 173 | // ReadProcessMemory(hProcess, (LPCVOID)memoryAddress, output, size, &bytesRead); 174 | // if (bytesRead != 0) { 175 | // result = true; 176 | // } 177 | // 178 | // return result; 179 | //} 180 | 181 | //MODULEENTRY32 ProcessTools::getModule(DWORD processID, const wchar_t* processName) { 182 | // MODULEENTRY32 modEntry = { 0 }; 183 | // HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processID); 184 | // if (snapshot != INVALID_HANDLE_VALUE) { 185 | // modEntry.dwSize = sizeof(MODULEENTRY32); 186 | // if (Module32First(snapshot, &modEntry)) { 187 | // do 188 | // { 189 | // if (!wcscmp(modEntry.szModule, processName)) { 190 | // break; 191 | // } 192 | // } while (Module32Next(snapshot, &modEntry)); 193 | // } 194 | // } 195 | // CloseHandle(snapshot); 196 | // return modEntry; 197 | //} 198 | // 199 | -------------------------------------------------------------------------------- /OsuBots/OsuBots.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {7E228D58-1FC7-402A-8230-B04E26E40698} 23 | Win32Proj 24 | OsuBots 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | true 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | 107 | 108 | Console 109 | true 110 | 111 | 112 | 113 | 114 | Level3 115 | 116 | 117 | MaxSpeed 118 | true 119 | true 120 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 121 | true 122 | 123 | 124 | Console 125 | true 126 | true 127 | true 128 | 129 | 130 | 131 | 132 | Level3 133 | 134 | 135 | MaxSpeed 136 | true 137 | true 138 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 139 | true 140 | 141 | 142 | Console 143 | true 144 | true 145 | true 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /OsuBots/OsuDbParser.cpp: -------------------------------------------------------------------------------- 1 | #include "OsuDbParser.h" 2 | 3 | OsuDbParser::OsuDbParser() 4 | { 5 | } 6 | 7 | 8 | OsuDbParser::OsuDbParser(string fullPathToOsuDb) 9 | { 10 | (this)->startParsingData(fullPathToOsuDb); 11 | } 12 | 13 | 14 | OsuDbParser::~OsuDbParser() 15 | { 16 | } 17 | 18 | void OsuDbParser::processDataMin(string fullPathToOsuDb) { 19 | ifstream osuDbHandle; 20 | osuDbHandle.open(fullPathToOsuDb, ios::binary | ios::in); 21 | if (osuDbHandle) { 22 | (this)->osuVersion = (this)->readInt(osuDbHandle); 23 | (this)->folderCount = (this)->readInt(osuDbHandle); 24 | (this)->accountUnlocked = (this)->readBool(osuDbHandle); 25 | // skip through weird dateUnlock field which has 8 bytes 26 | osuDbHandle.seekg(8, ios::cur); 27 | (this)->playerName = (this)->readString(osuDbHandle); 28 | (this)->numberOfBeatmaps = (this)->readInt(osuDbHandle); 29 | 30 | // start parsing beatmapdata 31 | for (int b = 0; b < (this)->numberOfBeatmaps; b++) { 32 | OsuDbBeatmapDataMinS beatmap; 33 | //beatmap.bytesOfBeatmapEntry = (this)->readInt(osuDbHandle); 34 | (this)->skipBytes(osuDbHandle, sizeof(int), false); 35 | beatmap.artistName = (this)->readString(osuDbHandle); 36 | //beatmap.artistNameUnicode = (this)->readString(osuDbHandle); 37 | (this)->skipBytes(osuDbHandle); 38 | beatmap.songTitle = (this)->readString(osuDbHandle); 39 | //beatmap.songTitleUnicode = (this)->readString(osuDbHandle); 40 | (this)->skipBytes(osuDbHandle); 41 | beatmap.creatorName = (this)->readString(osuDbHandle); 42 | beatmap.difficulty = (this)->readString(osuDbHandle); 43 | //beatmap.audioFileName = (this)->readString(osuDbHandle); 44 | (this)->skipBytes(osuDbHandle); 45 | //beatmap.MD5Hash = (this)->readString(osuDbHandle); 46 | (this)->skipBytes(osuDbHandle); 47 | beatmap.nameOfOsuFile = (this)->readString(osuDbHandle); 48 | //beatmap.rankedStatus = (this)->readChar(osuDbHandle); 49 | //beatmap.numOfHitcircles = (this)->readByteToInt(osuDbHandle, 2); 50 | //beatmap.numOfSliders = (this)->readByteToInt(osuDbHandle, 2); 51 | //beatmap.numOfSpinners = (this)->readByteToInt(osuDbHandle, 2); 52 | //beatmap.lastModificationTime = (this)->readLong(osuDbHandle); 53 | (this)->skipBytes(osuDbHandle, sizeof(char) + 6 + sizeof(long long), false); 54 | beatmap.AR = (this)->readFloat(osuDbHandle); 55 | beatmap.CS = (this)->readFloat(osuDbHandle); 56 | beatmap.HP = (this)->readFloat(osuDbHandle); 57 | beatmap.OD = (this)->readFloat(osuDbHandle); 58 | beatmap.sliderVelocity = (this)->readDouble(osuDbHandle); 59 | 60 | for (int i = 0; i < 4; i++) { 61 | unsigned int numberOfPairs = (this)->readInt(osuDbHandle); 62 | (this)->skipBytes(osuDbHandle, numberOfPairs * 14, false); 63 | } 64 | // ending of Int-Double pairs 65 | 66 | beatmap.drainTime = (this)->readInt(osuDbHandle); 67 | beatmap.totalTime = (this)->readInt(osuDbHandle); 68 | //beatmap.previewTime = (this)->readInt(osuDbHandle); 69 | (this)->skipBytes(osuDbHandle, sizeof(int), false); 70 | 71 | // starting of timing points 72 | unsigned int numberOfTimingPoints = (this)->readInt(osuDbHandle); 73 | (this)->skipBytes(osuDbHandle, numberOfTimingPoints * 17, false); 74 | // ending of timing points 75 | 76 | beatmap.beatmapID = (this)->readInt(osuDbHandle); 77 | beatmap.beatmapSetID = (this)->readInt(osuDbHandle); 78 | //beatmap.threadID = (this)->readInt(osuDbHandle); 79 | //beatmap.gradeStandard = (this)->readChar(osuDbHandle); 80 | //beatmap.gradeTaiko = (this)->readChar(osuDbHandle); 81 | //beatmap.gradeCTB = (this)->readChar(osuDbHandle); 82 | //beatmap.gradeMania = (this)->readChar(osuDbHandle); 83 | beatmap.localOffset = (this)->readShort(osuDbHandle); 84 | //beatmap.stackLeniency = (this)->readFloat(osuDbHandle); 85 | //beatmap.gameplayMode = (this)->readChar(osuDbHandle); 86 | (this)->skipBytes(osuDbHandle, sizeof(int) + sizeof(char) * 5 + sizeof(float), false); 87 | //beatmap.songSource = (this)->readString(osuDbHandle); 88 | (this)->skipBytes(osuDbHandle); 89 | //beatmap.songTags = (this)->readString(osuDbHandle); 90 | (this)->skipBytes(osuDbHandle); 91 | beatmap.onlineOffset = (this)->readShort(osuDbHandle); 92 | //beatmap.fontUsed = (this)->readString(osuDbHandle); 93 | (this)->skipBytes(osuDbHandle); 94 | //beatmap.isUnplayed = (this)->readBool(osuDbHandle); 95 | //beatmap.lastPlayedTime = (this)->readLong(osuDbHandle); 96 | //beatmap.isOsz2 = (this)->readBool(osuDbHandle); 97 | (this)->skipBytes(osuDbHandle, sizeof(bool) * 2 + sizeof(long long), false); 98 | beatmap.folderName = (this)->readString(osuDbHandle); 99 | //beatmap.lastCheckedTime = (this)->readLong(osuDbHandle); 100 | //beatmap.ignoreBeatmapSound = (this)->readBool(osuDbHandle); 101 | //beatmap.ignoreBeatmapSkin = (this)->readBool(osuDbHandle); 102 | //beatmap.disableStoryboard = (this)->readBool(osuDbHandle); 103 | //beatmap.disableVideo = (this)->readBool(osuDbHandle); 104 | //beatmap.visualOverride = (this)->readBool(osuDbHandle); 105 | // skip through unknown int (last modification time?) 106 | //osuDbHandle.seekg(sizeof(int), ios::cur); 107 | //beatmap.maniaScrollSpeed = (this)->readChar(osuDbHandle); 108 | (this)->skipBytes(osuDbHandle, sizeof(long long) + sizeof(bool) * 5 + sizeof(int) + sizeof(char), false); 109 | // finish parsing beatmap data 110 | 111 | // formating to get osu client window title format 112 | string osuWindowTitle = ""; 113 | if (beatmap.difficulty != "") { 114 | osuWindowTitle = "osu! - " + beatmap.artistName + " - " + beatmap.songTitle + " [" + beatmap.difficulty + "]"; 115 | } 116 | // special case where there is no difficulty 117 | else { 118 | osuWindowTitle = "osu! - " + beatmap.artistName + " - " + beatmap.songTitle; 119 | } 120 | 121 | auto beatmapIter = (this)->beatmapsMin.find(osuWindowTitle); 122 | if (beatmapIter == (this)->beatmapsMin.end()) { // if this beatmap is not yet added 123 | vector tempBeatmapVector; 124 | tempBeatmapVector.push_back(beatmap); 125 | (this)->beatmapsMin.insert({ osuWindowTitle, tempBeatmapVector }); 126 | } 127 | else { 128 | beatmapIter->second.push_back(beatmap); 129 | } 130 | } 131 | } 132 | osuDbHandle.close(); 133 | } 134 | 135 | void OsuDbParser::startParsingData(string fullPathToOsuDb) { 136 | (this)->processDataMin(fullPathToOsuDb); 137 | } 138 | 139 | bool OsuDbParser::readBool(ifstream &osuDbHandle) { 140 | bool output; 141 | osuDbHandle.read((char*)&output, sizeof(bool)); 142 | return output; 143 | } 144 | 145 | unsigned char OsuDbParser::readChar(ifstream &osuDbHandle) { 146 | unsigned char output; 147 | osuDbHandle.read((char*)&output, sizeof(char)); 148 | return output; 149 | } 150 | 151 | unsigned short OsuDbParser::readShort(ifstream &osuDbHandle) { 152 | unsigned short output; 153 | osuDbHandle.read((char*)&output, sizeof(short)); 154 | return output; 155 | } 156 | 157 | unsigned int OsuDbParser::readInt(ifstream &osuDbHandle) { 158 | unsigned int output; 159 | osuDbHandle.read((char*)&output, sizeof(int)); 160 | return output; 161 | } 162 | 163 | unsigned long long OsuDbParser::readLong(ifstream &osuDbHandle) { 164 | unsigned long long output; 165 | osuDbHandle.read((char*)&output, sizeof(long long)); 166 | return output; 167 | } 168 | 169 | unsigned int OsuDbParser::readByteToInt(ifstream &osuDbHandle, int size) { 170 | unsigned int output = 0; // initialize to prevent junk value 171 | osuDbHandle.read((char*)&output, size); 172 | return output; 173 | } 174 | 175 | float OsuDbParser::readFloat(ifstream &osuDbHandle) { 176 | float output; 177 | osuDbHandle.read((char*)&output, sizeof(float)); 178 | return output; 179 | } 180 | 181 | double OsuDbParser::readDouble(ifstream &osuDbHandle) { 182 | double output; 183 | osuDbHandle.read((char*)&output, sizeof(double)); 184 | return output; 185 | } 186 | 187 | string OsuDbParser::readString(ifstream &osuDbHandle) { 188 | string output = ""; // return empty string if string is not found 189 | unsigned char stringPresent = (this)->readChar(osuDbHandle); 190 | if (stringPresent == 0x0b) { // if string is present 191 | unsigned int byteLengthOfString = 0; 192 | int shift = 0; 193 | while (true) { 194 | unsigned char bufferByte = (this)->readChar(osuDbHandle); 195 | // & 127 gets last 7 bits 196 | byteLengthOfString |= (bufferByte & 127) << shift; 197 | // & 128 gets 1st bit 198 | // if 1st bit == 0, the decode process ends 199 | if (!(bufferByte & (unsigned int)128)) { 200 | break; 201 | } 202 | shift += 7; 203 | } 204 | char* tempOutput = new char[byteLengthOfString + 1]; 205 | osuDbHandle.read(tempOutput, byteLengthOfString); 206 | tempOutput[byteLengthOfString] = '\0'; 207 | output = string(tempOutput); 208 | delete[] tempOutput; // When done, free memory pointed to by a 209 | } 210 | return output; 211 | } 212 | 213 | void OsuDbParser::skipBytes(ifstream &osuDbHandle, int knownSize, bool isString) { 214 | // if knownSize is not given, skip string number of bytes 215 | if (knownSize == 0 && isString) { 216 | unsigned char stringPresent = (this)->readChar(osuDbHandle); 217 | if (stringPresent == 0x0b) { // if string is present 218 | unsigned int byteLengthOfString = 0; 219 | int shift = 0; 220 | while (true) { 221 | unsigned char bufferByte = (this)->readChar(osuDbHandle); 222 | // & 127 gets last 7 bits 223 | byteLengthOfString |= (bufferByte & 127) << shift; 224 | // & 128 gets 1st bit 225 | // if 1st bit == 0, the decode process ends 226 | if (!(bufferByte & (unsigned int)128)) { 227 | break; 228 | } 229 | shift += 7; 230 | } 231 | osuDbHandle.seekg(byteLengthOfString, ios::cur); 232 | } 233 | } 234 | else { 235 | if (knownSize != 0) { 236 | osuDbHandle.seekg(knownSize, ios::cur); 237 | } 238 | } 239 | } 240 | 241 | -------------------------------------------------------------------------------- /OsuBots/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.h" 2 | 3 | namespace Config 4 | { 5 | // These go according to the order of the constants stored in file 6 | string OSUROOTPATH = ""; 7 | char LEFT_KEY = 'z'; 8 | char RIGHT_KEY = 'x'; 9 | int CLICK_OFFSET = 0; 10 | unsigned int CLICK_OFFSET_DEVIATION = 0; 11 | int SLIDER_DURATION_OFFSET = 0; 12 | unsigned int RPM = 400; 13 | // rarely changed 14 | unsigned int CIRCLE_SLEEPTIME = 10; 15 | unsigned int MIN_WAIT_DURATION = 1; 16 | 17 | // Derived constants (not in file) 18 | string SONGFOLDER = ""; 19 | string FILENAME = "config.txt"; 20 | } 21 | 22 | // load constants from file into Config namespace 23 | void Config::loadConfigFile(string filename) 24 | { 25 | ifstream configTxtReader(filename); 26 | // if file exists, read from the file 27 | if (configTxtReader.is_open()) { 28 | string line; 29 | while (getline(configTxtReader, line)) 30 | { 31 | auto constVector = Functions::split(line, '='); 32 | if (constVector.front() == "OSUROOTPATH") { 33 | Config::OSUROOTPATH = constVector.back(); 34 | } 35 | else if (constVector.front() == "LEFT_KEY") { 36 | Config::LEFT_KEY = constVector.back().front(); 37 | } 38 | else if (constVector.front() == "RIGHT_KEY") { 39 | Config::RIGHT_KEY = constVector.back().front(); 40 | } 41 | else if (constVector.front() == "CLICK_OFFSET") { 42 | Config::CLICK_OFFSET = stoi(constVector.back()); 43 | } 44 | else if (constVector.front() == "CLICK_OFFSET_DEVIATION") { 45 | Config::CLICK_OFFSET_DEVIATION = stoi(constVector.back()); 46 | } 47 | else if (constVector.front() == "SLIDER_DURATION_OFFSET") { 48 | Config::SLIDER_DURATION_OFFSET = stoi(constVector.back()); 49 | } 50 | else if (constVector.front() == "RPM") { 51 | Config::RPM = stoi(constVector.back()); 52 | } 53 | else if (constVector.front() == "CIRCLE_SLEEPTIME") { 54 | Config::CIRCLE_SLEEPTIME = stoi(constVector.back()); 55 | } 56 | else if (constVector.front() == "MIN_WAIT_DURATION") { 57 | Config::MIN_WAIT_DURATION = stoi(constVector.back()); 58 | } 59 | 60 | } 61 | Config::SONGFOLDER = Config::OSUROOTPATH + "Songs\\"; 62 | Config::FILENAME = filename; 63 | configTxtReader.close(); 64 | } 65 | // else, ask for input and create file 66 | else { 67 | cout << "Initializing settings for first time use..." << endl << endl; 68 | string osuRootPath; 69 | char leftKey; 70 | char rightKey; 71 | 72 | cout << "Locate your osu!.exe or its shortcut: (if you accidentally pick the wrong file, please restart the program)" << endl; 73 | osuRootPath = ProcessTools::promptToChooseFileAndGetPath(L"Find osu!.exe or its shortcut"); 74 | if (osuRootPath == "") { 75 | throw runtime_error("No file is chosen"); 76 | } 77 | size_t found = osuRootPath.find_last_of("/\\"); 78 | osuRootPath = osuRootPath.substr(0, found + 1); 79 | 80 | cout << "Enter the 'left click' key according to your settings in osu! (default is 'z')" << endl; 81 | cin >> leftKey; 82 | leftKey = static_cast(tolower(leftKey)); 83 | 84 | cin.clear(); //clear errors/bad flags on cin 85 | cin.ignore(cin.rdbuf()->in_avail(), '\n');//precise amount of ignoring 86 | 87 | cout << "Enter the 'right click' key according to your settings in osu! (default is 'x')" << endl; 88 | cin >> rightKey; 89 | rightKey = static_cast(tolower(rightKey)); 90 | 91 | cout << "Saving..." << endl; 92 | ofstream configTxtWriter(filename); 93 | if (configTxtWriter.is_open()) { 94 | configTxtWriter << "OSUROOTPATH=" << osuRootPath << endl; 95 | configTxtWriter << "LEFT_KEY=" << leftKey << endl; 96 | configTxtWriter << "RIGHT_KEY=" << rightKey << endl; 97 | configTxtWriter << "CLICK_OFFSET=" << Config::CLICK_OFFSET << endl; 98 | configTxtWriter << "CLICK_OFFSET_DEVIATION=" << Config::CLICK_OFFSET_DEVIATION << endl; 99 | configTxtWriter << "SLIDER_DURATION_OFFSET=" << Config::SLIDER_DURATION_OFFSET << endl; 100 | configTxtWriter << "RPM=" << Config::RPM << endl; 101 | configTxtWriter << "CIRCLE_SLEEPTIME=" << Config::CIRCLE_SLEEPTIME << endl; 102 | configTxtWriter << "MIN_WAIT_DURATION=" << Config::MIN_WAIT_DURATION << endl; 103 | 104 | configTxtWriter.close(); 105 | 106 | // call itself to read data this time 107 | loadConfigFile(filename); 108 | } 109 | else { 110 | throw runtime_error("Failed to create config.txt. Probably file permission issue"); 111 | } 112 | } 113 | } 114 | 115 | void Config::clearAndChangeConfig() { 116 | CHAR_INFO consoleBuffer[58 * 80]; 117 | CONSOLE_SCREEN_BUFFER_INFO csbi; 118 | ProcessTools::saveConsoleBuffer(csbi, consoleBuffer); 119 | system("cls"); 120 | // clear the input buffer so that shift + c (C) is not shown when the setting page appears 121 | FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); 122 | // call function 123 | Config::changeConfig(); 124 | ProcessTools::restoreConsoleBuffer(csbi, consoleBuffer); 125 | } 126 | 127 | void Config::changeConfig() { 128 | // var for checking if need to save the change 129 | bool discard = false; 130 | while (true) { 131 | string input; 132 | cout << "(Press y to save the changes, n to discard the changes)" << endl; 133 | cout << "Make change to: (current value)" << endl; 134 | cout << "0) Reset All" << endl; 135 | cout << "1) LEFT_KEY (" << Config::LEFT_KEY << ")" << endl; 136 | cout << "2) RIGHT_KEY (" << Config::RIGHT_KEY << ")" << endl; 137 | cout << "3) CLICK_OFFSET (" << Config::CLICK_OFFSET << ")" << endl; 138 | cout << "4) CLICK_OFFSET_DEVIATION (" << Config::CLICK_OFFSET_DEVIATION << ")" << endl; 139 | cout << "5) SLIDER_DURATION_OFFSET (" << Config::SLIDER_DURATION_OFFSET << ")" << endl; 140 | cout << "6) RPM (" << Config::RPM << ")" << endl; 141 | cout << "7) CIRCLE_SLEEPTIME (" << Config::CIRCLE_SLEEPTIME << ")" << endl; 142 | cout << "8) MIN_WAIT_DURATION (" << Config::MIN_WAIT_DURATION << ")" << endl; 143 | 144 | cin >> input; 145 | // if y or n found, exit the loop 146 | if (input.front() == 'y' || input.front() == 'n') { 147 | discard = input.front() == 'n' ? true : false; 148 | break; 149 | } 150 | // var so that it can break out the outer loop 151 | bool loopBreak = false; 152 | while (!(all_of(input.begin(), input.end(), isdigit)) || stoi(input) < 0 || stoi(input) > 8) { 153 | cout << "Invalid input. Please enter again." << endl; 154 | cout << "Make change to: (current value)" << endl; 155 | cout << "0) Reset All" << endl; 156 | cout << "1) LEFT_KEY (" << Config::LEFT_KEY << ")" << endl; 157 | cout << "2) RIGHT_KEY (" << Config::RIGHT_KEY << ")" << endl; 158 | cout << "3) CLICK_OFFSET (" << Config::CLICK_OFFSET << ")" << endl; 159 | cout << "4) CLICK_OFFSET_DEVIATION (" << Config::CLICK_OFFSET_DEVIATION << ")" << endl; 160 | cout << "5) SLIDER_DURATION_OFFSET (" << Config::SLIDER_DURATION_OFFSET << ")" << endl; 161 | cout << "6) RPM (" << Config::RPM << ")" << endl; 162 | cout << "7) CIRCLE_SLEEPTIME (" << Config::CIRCLE_SLEEPTIME << ")" << endl; 163 | cout << "8) MIN_WAIT_DURATION (" << Config::MIN_WAIT_DURATION << ")" << endl; 164 | cin >> input; 165 | if (input.front() == 'y' || input.front() == 'n') { 166 | discard = input.front() == 'n' ? true : false; 167 | loopBreak = true; // set true to exit outer loop later 168 | break; 169 | } 170 | } 171 | if (loopBreak) break; // exit outer loop 172 | int choice = stoi(input); 173 | system("cls"); 174 | switch (choice) { 175 | case 0: { 176 | //cout << "Sure you wanna reset all? (y/n)" << endl; 177 | //cin >> input; 178 | //// if y or n found, exit the loop 179 | //while (input.front() != 'y' && input.front() != 'n') { 180 | // cout << "Invalid input. Try again: (y/n)" << endl; 181 | // cin >> input; 182 | //} 183 | //if (input.front() == 'y') { 184 | // Config::resetConfig(); 185 | //} 186 | Config::resetConfig(); 187 | break; 188 | } 189 | case 1: 190 | case 2: { 191 | char change; 192 | if (choice == 1) { 193 | cout << "Enter new LEFT_KEY: " << endl; 194 | } 195 | else { 196 | cout << "Enter new RIGHT_KEY: " << endl; 197 | } 198 | cin >> change; 199 | change = static_cast(tolower(change)); 200 | 201 | cin.clear(); //clear errors/bad flags on cin 202 | cin.ignore(cin.rdbuf()->in_avail(), '\n');//precise amount of ignoring 203 | 204 | if (choice == 1) { 205 | Config::LEFT_KEY = change; 206 | } 207 | else { 208 | Config::RIGHT_KEY = change; 209 | } 210 | break; 211 | } 212 | case 3: 213 | case 4: 214 | case 5: 215 | case 6: 216 | case 7: 217 | case 8: { 218 | string changeStr; 219 | bool isNegative = false; 220 | if (choice == 3) { 221 | cout << "Enter new CLICK_OFFSET: (recommended 0+-30)" << endl; 222 | } 223 | else if (choice == 4) { 224 | cout << "Enter new CLICK_OFFSET_DEVIATION: " << endl; 225 | } 226 | else if (choice == 5) { 227 | cout << "Enter new SLIDER_DURATION_OFFSET: (recommended 0+-20)" << endl; 228 | } 229 | else if (choice == 6) { 230 | cout << "Enter new RPM: (high value might cause lag)" << endl; 231 | } 232 | else if (choice == 7) { 233 | cout << "Enter new CIRCLE_SLEEPTIME: (recommended 10+ (rarely changed))" << endl; 234 | } 235 | else { 236 | cout << "Enter new MIN_WAIT_DURATION: (best using 1)" << endl; 237 | } 238 | cin >> changeStr; 239 | // user might have input -ve value, which would fail the isdigit check, so check if it's -ve first 240 | if (changeStr.front() == '-') { 241 | isNegative = true; 242 | // erase the -ve sign for isdigit validation 243 | changeStr.erase(changeStr.begin()); 244 | } 245 | if (!(all_of(changeStr.begin(), changeStr.end(), isdigit))) { 246 | cout << "Invalid value."; 247 | break; 248 | } 249 | else { 250 | int changeInt = stoi(changeStr); 251 | if (isNegative) { 252 | // now assign negative to the value if it is 253 | changeInt = -changeInt; 254 | } 255 | if (choice == 3) { 256 | Config::CLICK_OFFSET = changeInt; 257 | } 258 | else if (choice == 4) { 259 | Config::CLICK_OFFSET_DEVIATION = changeInt; 260 | } 261 | else if (choice == 5) { 262 | Config::SLIDER_DURATION_OFFSET = changeInt; 263 | } 264 | else if (choice == 6) { 265 | Config::RPM = changeInt; 266 | } 267 | else if (choice == 7) { 268 | Config::CIRCLE_SLEEPTIME = changeInt; 269 | } 270 | else { 271 | Config::MIN_WAIT_DURATION = changeInt; 272 | } 273 | } 274 | break; 275 | } 276 | } 277 | // clear the screen to show the choices only 278 | system("cls"); 279 | } 280 | // program will come to here after 'y' or 'n' is input 281 | // if confirm save, rewrite the file 282 | if (!discard) { 283 | cout << "Saving..." << endl; 284 | ofstream configTxtWriter(Config::FILENAME); 285 | if (configTxtWriter.is_open()) { 286 | configTxtWriter << "OSUROOTPATH=" << Config::OSUROOTPATH << endl; 287 | configTxtWriter << "LEFT_KEY=" << Config::LEFT_KEY << endl; 288 | configTxtWriter << "RIGHT_KEY=" << Config::RIGHT_KEY << endl; 289 | configTxtWriter << "CLICK_OFFSET=" << Config::CLICK_OFFSET << endl; 290 | configTxtWriter << "CLICK_OFFSET_DEVIATION=" << Config::CLICK_OFFSET_DEVIATION << endl; 291 | configTxtWriter << "SLIDER_DURATION_OFFSET=" << Config::SLIDER_DURATION_OFFSET << endl; 292 | configTxtWriter << "RPM=" << Config::RPM << endl; 293 | configTxtWriter << "CIRCLE_SLEEPTIME=" << Config::CIRCLE_SLEEPTIME << endl; 294 | configTxtWriter << "MIN_WAIT_DURATION=" << Config::MIN_WAIT_DURATION << endl; 295 | configTxtWriter.close(); 296 | } 297 | else { 298 | throw runtime_error("Failed to rewrite file. Please make sure the file is not opened by another program."); 299 | } 300 | } 301 | else { 302 | // reload the constants so that if the changes are discarded, the constants that have been modified are changed back 303 | Config::loadConfigFile(Config::FILENAME); 304 | } 305 | } 306 | 307 | void Config::resetConfig() { 308 | Config::LEFT_KEY = 'z'; 309 | Config::RIGHT_KEY = 'x'; 310 | Config::CLICK_OFFSET = 0; 311 | Config::CLICK_OFFSET_DEVIATION = 0; 312 | Config::SLIDER_DURATION_OFFSET = 0; 313 | Config::RPM = 400; 314 | // rarely changed 315 | Config::CIRCLE_SLEEPTIME = 10; 316 | Config::MIN_WAIT_DURATION = 1; 317 | } 318 | -------------------------------------------------------------------------------- /OsuBots/Input.cpp: -------------------------------------------------------------------------------- 1 | #include "Input.h" 2 | 3 | // credit to Aixxe from https://aixxe.net/2016/10/osu-game-hacking 4 | void Input::sentKeyInput(char key, bool pressed) { 5 | // send key press to screen 6 | // to release the key, set pressed = false 7 | INPUT key_press = { 0 }; 8 | key_press.type = INPUT_KEYBOARD; 9 | key_press.ki.wVk = VkKeyScanEx(key, GetKeyboardLayout(NULL)) & 0xFF; 10 | key_press.ki.wScan = 0; 11 | key_press.ki.dwExtraInfo = 0; 12 | key_press.ki.dwFlags = (pressed ? 0 : KEYEVENTF_KEYUP); 13 | SendInput(1, &key_press, sizeof INPUT); 14 | } 15 | 16 | void Input::circleLinearMove(POINT startScaledPoint, POINT endScaledPoint, double duration) { 17 | auto directionX = endScaledPoint.x - startScaledPoint.x; 18 | auto directionY = endScaledPoint.y - startScaledPoint.y; 19 | // special case: stationary 20 | if (directionX == 0 && directionY == 0) { 21 | // wait till the end of the duration then set the cursor 22 | Timer localTimer = Timer(); 23 | localTimer.start(); 24 | duration *= Timer::prefix; 25 | while (localTimer.getTimePast() < duration) {} 26 | SetCursorPos(endScaledPoint.x, endScaledPoint.y); 27 | return; // directly return 28 | } 29 | // otherwise use vector calculation to move to the destination 30 | auto distance = sqrt(directionX * directionX + directionY * directionY); 31 | auto unitVectorX = directionX / distance; 32 | auto unitVectorY = directionY / distance; 33 | double waitDuration; 34 | double distancePerWaitDuration; 35 | // see which one (distance/duration) is smaller, and use the smaller var to move so as to reduce the num of loops 36 | // * MIN_WAIT_DURATION to allow user to reduce the num of loops for slower comps 37 | if (distance * Config::MIN_WAIT_DURATION < duration) { 38 | // move using distance, which is incremented by 1 in each loop 39 | waitDuration = duration / distance * Timer::prefix; 40 | distancePerWaitDuration = 1; 41 | } 42 | else { 43 | // move using duration, at least 1ms 44 | waitDuration = duration / (int)duration * Config::MIN_WAIT_DURATION * Timer::prefix; 45 | distancePerWaitDuration = distance / duration * Config::MIN_WAIT_DURATION; 46 | } 47 | // start multiplier at 0 so that it starts at startScaledPoint 48 | // then increase the multiplier to move a fraction of distance until it reaches endPoint 49 | for (double multiplier = 0; multiplier <= distance; multiplier += distancePerWaitDuration) { 50 | Timer localTimer = Timer(); 51 | localTimer.start(); 52 | SetCursorPos(startScaledPoint.x + multiplier * unitVectorX, startScaledPoint.y + multiplier * unitVectorY); 53 | while (localTimer.getTimePast() < waitDuration) {} 54 | } 55 | } 56 | 57 | POINT Input::sliderMove(HitObject currentHitObject, float pointsMultiplierX, float pointsMultiplierY, POINT cursorStartPoints) { 58 | bool reverse = false; 59 | // if 'L' type, pointsOnCurve is not set, so use circleLinearMove instead 60 | if (currentHitObject.sliderType == 'L') { 61 | CurvePointsS start = currentHitObject.CurvePoints.at(0).front(); 62 | POINT startPoint; 63 | startPoint.x = start.x * pointsMultiplierX + cursorStartPoints.x; 64 | startPoint.y = start.y * pointsMultiplierY + cursorStartPoints.y; 65 | CurvePointsS end = currentHitObject.CurvePoints.at(0).back(); 66 | POINT endPoint; 67 | endPoint.x = end.x * pointsMultiplierX + cursorStartPoints.x; 68 | endPoint.y = end.y * pointsMultiplierY + cursorStartPoints.y; 69 | for (int i = 0; i < currentHitObject.repeat; i++) { 70 | if (!reverse) { 71 | Input::circleLinearMove(startPoint, endPoint, currentHitObject.sliderDuration / currentHitObject.repeat); 72 | reverse = true; 73 | } 74 | else { 75 | Input::circleLinearMove(endPoint, startPoint, currentHitObject.sliderDuration / currentHitObject.repeat); 76 | reverse = false; 77 | } 78 | } 79 | if (currentHitObject.repeat % 2 == 1) { 80 | return endPoint; 81 | } 82 | else { 83 | return startPoint; 84 | } 85 | } 86 | else { 87 | Timer globalTimer = Timer(); 88 | globalTimer.start(); 89 | double totalDuration = currentHitObject.sliderDuration * Timer::prefix; 90 | double waitDuration; 91 | double skippedIndex; 92 | // same as circleLinearMove concept, but this time use number of points on curve to represent the distance 93 | if (currentHitObject.pointsOnCurve.size() * currentHitObject.repeat * Config::MIN_WAIT_DURATION < currentHitObject.sliderDuration) { 94 | // if number of points is less than duration, use number of points to move (i.e. dun skip index) 95 | waitDuration = currentHitObject.sliderDuration / currentHitObject.pointsOnCurve.size() / currentHitObject.repeat * Timer::prefix; 96 | skippedIndex = 1; 97 | } 98 | else { 99 | // else, use waitDuration to move (at least 1ms) and skip certain points (by using skippedIndex) to reduce num of loops 100 | waitDuration = currentHitObject.sliderDuration / (int)currentHitObject.sliderDuration * Config::MIN_WAIT_DURATION * Timer::prefix; 101 | skippedIndex = currentHitObject.pointsOnCurve.size() * currentHitObject.repeat / currentHitObject.sliderDuration * Config::MIN_WAIT_DURATION; 102 | } 103 | FPointS unscaledEndPoint; 104 | int multiplier = 0; 105 | for (int i = 0; i < currentHitObject.repeat; i++) { 106 | if (!reverse) { 107 | // index = multiplier * skippedIndex to get new index, each skipped by amount of skippedIndex 108 | for (int index = 0; index < currentHitObject.pointsOnCurve.size() && globalTimer.getTimePast() < totalDuration; index = multiplier * skippedIndex) { 109 | Timer localTimer = Timer(); 110 | localTimer.start(); 111 | FPointS point = currentHitObject.pointsOnCurve.at(index); 112 | int scaledX = point.x * pointsMultiplierX + cursorStartPoints.x; 113 | int scaledY = point.y * pointsMultiplierY + cursorStartPoints.y; 114 | SetCursorPos(scaledX, scaledY); 115 | multiplier++; // increment each loop to get new index 116 | while (localTimer.getTimePast() < waitDuration) {} 117 | } 118 | reverse = true; 119 | unscaledEndPoint = currentHitObject.pointsOnCurve.back(); 120 | } 121 | else { 122 | // when repeat happens, multiplier at this point will always equal to pointsOnCurve.size(). -- it to point to last point 123 | multiplier--; 124 | for (int index = multiplier; index >= 0 && globalTimer.getTimePast() < totalDuration; index = multiplier * skippedIndex) { 125 | Timer localTimer = Timer(); 126 | localTimer.start(); 127 | FPointS point = currentHitObject.pointsOnCurve.at(index); 128 | int scaledX = point.x * pointsMultiplierX + cursorStartPoints.x; 129 | int scaledY = point.y * pointsMultiplierY + cursorStartPoints.y; 130 | SetCursorPos(scaledX, scaledY); 131 | multiplier--; 132 | while (localTimer.getTimePast() < waitDuration) {} 133 | } 134 | reverse = false; 135 | unscaledEndPoint = currentHitObject.pointsOnCurve.front(); 136 | } 137 | } 138 | POINT scaledEndPoint; // scale and return real end point 139 | scaledEndPoint.x = unscaledEndPoint.x * pointsMultiplierX + cursorStartPoints.x; 140 | scaledEndPoint.y = unscaledEndPoint.y * pointsMultiplierY + cursorStartPoints.y; 141 | return scaledEndPoint; 142 | } 143 | } 144 | 145 | POINT Input::spinnerMove(POINT scaledCenter, double duration) { 146 | Timer globalTimer = Timer(); 147 | globalTimer.start(); 148 | const int radius = 60; // fixed radius 149 | const double PI = 4 * atan(1); 150 | double angle = 0; // start angle at 0 151 | double x = 0; 152 | double y = 0; 153 | float angleIncrement = 0.25; 154 | 155 | // calculations for getting constant spinning speed 156 | double numberOfPointsInOneRound = 2 * PI / angleIncrement; 157 | double totalNumberOfRoundsNeeded = Config::RPM * (duration / 1000 / 60); // user determined RPM 158 | double totalNumberOfPoints = totalNumberOfRoundsNeeded * numberOfPointsInOneRound; 159 | auto scaledDuration = duration * Timer::prefix; 160 | auto scaledDurationPerPoint = scaledDuration / totalNumberOfPoints; 161 | for (int i = 0; i < totalNumberOfPoints && globalTimer.getTimePast() < scaledDuration; i++) { 162 | Timer localTimer = Timer(); 163 | localTimer.start(); 164 | angle += angleIncrement; 165 | 166 | x = cos(angle) * radius; 167 | y = sin(angle) * radius; 168 | 169 | x += scaledCenter.x; 170 | y += scaledCenter.y; 171 | SetCursorPos(x, y); 172 | while (localTimer.getTimePast() < scaledDurationPerPoint) {} 173 | } 174 | POINT scaledEndPoint; 175 | scaledEndPoint.x = x; 176 | scaledEndPoint.y = y; 177 | return scaledEndPoint; // return back scaled CursorEndPoint 178 | } 179 | 180 | 181 | // deprecated as these cause lag on certain machines due to too many calls on SetCursorPos 182 | 183 | //void Input::circleLinearMove(POINT startScaledPoint, POINT endScaledPoint, double duration) { 184 | // Timer benchmark = Timer(); 185 | // benchmark.start(); 186 | // int totalDistanceX = abs(endScaledPoint.x - startScaledPoint.x); 187 | // int totalDistanceY = abs(endScaledPoint.y - startScaledPoint.y); 188 | // // account for divide by zero error 189 | // bool stationary = totalDistanceX == 0 && totalDistanceY == 0 ? true : false; 190 | // bool vertical = totalDistanceX == 0 ? true : false; 191 | // 192 | // // straight line equation variables 193 | // float gradient = NAN; 194 | // float c = NAN; 195 | // if (!vertical && !stationary) { 196 | // gradient = (float)(endScaledPoint.y - startScaledPoint.y) / (float)(endScaledPoint.x - startScaledPoint.x); 197 | // c = startScaledPoint.y - (startScaledPoint.x * gradient); 198 | // } 199 | // 200 | // POINT currentPosition = startScaledPoint; 201 | // int distanceMoved = 0; // counter 202 | // auto scaledDuration = duration * Timer::prefix; 203 | // 204 | // // to ensure smoothness, decide whether to use x-axis or y-axis base on greater distance 205 | // bool useX = totalDistanceX > totalDistanceY ? true : false; 206 | // 207 | // if (!stationary) { // if currentPoint is right at the nextPoint, do nothing to prevent divide by zero error 208 | // if (useX && !vertical) { 209 | // auto scaledDurationPerX = scaledDuration / totalDistanceX; // duration of one X move 210 | // bool goingRight = startScaledPoint.x - endScaledPoint.x < 0 ? true : false; // determine direction 211 | // if (goingRight) { 212 | // do { 213 | // Timer localTimer = Timer(); 214 | // localTimer.start(); 215 | // int newX = currentPosition.x + 1; // move X by one 216 | // int newY = gradient * newX + c; // calculation 217 | // SetCursorPos(newX, newY); 218 | // currentPosition.x++; 219 | // distanceMoved++; 220 | // // wait for timing of next move 221 | // while (localTimer.getTimePast() < scaledDurationPerX) {} 222 | // } while (distanceMoved < totalDistanceX); // loop till it reaches the endPoint 223 | // } 224 | // else { // going left basically just change + to - 225 | // do { 226 | // Timer localTimer = Timer(); 227 | // localTimer.start(); 228 | // int newX = currentPosition.x - 1; 229 | // int newY = gradient * newX + c; 230 | // SetCursorPos(newX, newY); 231 | // currentPosition.x--; 232 | // distanceMoved++; 233 | // while (localTimer.getTimePast() < scaledDurationPerX) {} 234 | // } while (distanceMoved < totalDistanceX); 235 | // } 236 | // } 237 | // else if (vertical) { // if vertical, X is always the same. Change Y only 238 | // auto scaledDurationPerY = scaledDuration / totalDistanceY; 239 | // bool goingUp = startScaledPoint.y - endScaledPoint.y < 0 ? true : false; 240 | // if (goingUp) { 241 | // do { 242 | // Timer localTimer = Timer(); 243 | // localTimer.start(); 244 | // int newY = currentPosition.y + 1; 245 | // SetCursorPos(startScaledPoint.x, newY); 246 | // currentPosition.y++; 247 | // distanceMoved++; 248 | // while (localTimer.getTimePast() < scaledDurationPerY) {} 249 | // } while (distanceMoved < totalDistanceY); 250 | // } 251 | // else { 252 | // do { 253 | // Timer localTimer = Timer(); 254 | // localTimer.start(); 255 | // int newY = currentPosition.y - 1; 256 | // SetCursorPos(startScaledPoint.x, newY); 257 | // currentPosition.y--; 258 | // distanceMoved++; 259 | // while (localTimer.getTimePast() < scaledDurationPerY) {} 260 | // } while (distanceMoved < totalDistanceY); 261 | // } 262 | // } 263 | // else { // all the same except instead of using distance, duration, and position of X, use those of Y 264 | // auto scaledDurationPerY = scaledDuration / totalDistanceY; 265 | // bool goingUp = startScaledPoint.y - endScaledPoint.y < 0 ? true : false; 266 | // if (goingUp) { 267 | // do { 268 | // Timer localTimer = Timer(); 269 | // localTimer.start(); 270 | // int newY = currentPosition.y + 1; 271 | // int newX = (newY - c) / gradient; // calculation base on line equation also 272 | // SetCursorPos(newX, newY); 273 | // currentPosition.y++; 274 | // distanceMoved++; 275 | // while (localTimer.getTimePast() < scaledDurationPerY) {} 276 | // } while (distanceMoved < totalDistanceY); 277 | // } 278 | // else { 279 | // do { 280 | // Timer localTimer = Timer(); 281 | // localTimer.start(); 282 | // int newY = currentPosition.y - 1; 283 | // int newX = (newY - c) / gradient; 284 | // SetCursorPos(newX, newY); 285 | // currentPosition.y--; 286 | // distanceMoved++; 287 | // while (localTimer.getTimePast() < scaledDurationPerY) {} 288 | // } while (distanceMoved < totalDistanceY); 289 | // } 290 | // } 291 | // } 292 | // else { 293 | // SetCursorPos(endScaledPoint.x, endScaledPoint.y); 294 | // } 295 | // cout << "C: " << duration << " -- " << benchmark.getTimePast() / Timer::prefix << endl; 296 | //} 297 | 298 | //POINT Input::sliderMove(HitObject currentHitObject, float pointsMultiplierX, float pointsMultiplierY, POINT cursorStartPoints) { 299 | // Timer globalTimer = Timer(); 300 | // globalTimer.start(); 301 | // auto scaledDuration = currentHitObject.sliderDuration * Timer::prefix; 302 | // bool reverse = false; 303 | // FPointS unscaledEndPoint; 304 | // // if 'L' type, pointsOnCurve is not set, so use circleLinearMove instead 305 | // if (currentHitObject.sliderType == 'L') { 306 | // CurvePointsS start = currentHitObject.CurvePoints.at(0).front(); 307 | // POINT startPoint; 308 | // startPoint.x = start.x * pointsMultiplierX + cursorStartPoints.x; 309 | // startPoint.y = start.y * pointsMultiplierY + cursorStartPoints.y; 310 | // CurvePointsS end = currentHitObject.CurvePoints.at(0).back(); 311 | // POINT endPoint; 312 | // endPoint.x = end.x * pointsMultiplierX + cursorStartPoints.x; 313 | // endPoint.y = end.y * pointsMultiplierY + cursorStartPoints.y; 314 | // for (int i = 0; i < currentHitObject.repeat; i++) { 315 | // if (!reverse) { 316 | // Input::circleLinearMove(startPoint, endPoint, currentHitObject.sliderDuration / currentHitObject.repeat); 317 | // reverse = true; 318 | // } 319 | // else { 320 | // Input::circleLinearMove(endPoint, startPoint, currentHitObject.sliderDuration / currentHitObject.repeat); 321 | // reverse = false; 322 | // } 323 | // } 324 | // if (currentHitObject.repeat % 2 == 1) { 325 | // return endPoint; 326 | // } 327 | // else { 328 | // return startPoint; 329 | // } 330 | // } 331 | // else { 332 | // auto scaledDurationPerDistance = (scaledDuration) / (currentHitObject.pointsOnCurve.size() * currentHitObject.repeat); 333 | // for (int i = 0; i < currentHitObject.repeat; i++) { 334 | // if (!reverse) { 335 | // for (int j = 0; j < currentHitObject.pointsOnCurve.size() && globalTimer.getTimePast() < scaledDuration; j++) { 336 | // Timer localTimer = Timer(); 337 | // localTimer.start(); 338 | // FPointS point = currentHitObject.pointsOnCurve.at(j); 339 | // int scaledX = point.x * pointsMultiplierX + cursorStartPoints.x; 340 | // int scaledY = point.y * pointsMultiplierY + cursorStartPoints.y; 341 | // SetCursorPos(scaledX, scaledY); 342 | // while (localTimer.getTimePast() < scaledDurationPerDistance) {} 343 | // } 344 | // reverse = true; 345 | // unscaledEndPoint = currentHitObject.pointsOnCurve.back(); 346 | // } 347 | // else { 348 | // for (int j = currentHitObject.pointsOnCurve.size(); j-- > 0 && globalTimer.getTimePast() < scaledDuration;) { 349 | // Timer localTimer = Timer(); 350 | // localTimer.start(); 351 | // FPointS currentPoint = currentHitObject.pointsOnCurve.at(j); 352 | // int scaledX = currentPoint.x * pointsMultiplierX + cursorStartPoints.x; 353 | // int scaledY = currentPoint.y * pointsMultiplierY + cursorStartPoints.y; 354 | // SetCursorPos(scaledX, scaledY); 355 | // while (localTimer.getTimePast() < scaledDurationPerDistance) {} 356 | // } 357 | // reverse = false; 358 | // unscaledEndPoint = currentHitObject.pointsOnCurve.front(); 359 | // } 360 | // } 361 | // } 362 | // POINT scaledEndPoint; // scale and return real end point 363 | // scaledEndPoint.x = unscaledEndPoint.x * pointsMultiplierX + cursorStartPoints.x; 364 | // scaledEndPoint.y = unscaledEndPoint.y * pointsMultiplierY + cursorStartPoints.y; 365 | // return scaledEndPoint; 366 | //} -------------------------------------------------------------------------------- /OsuBots/OsuBot.cpp: -------------------------------------------------------------------------------- 1 | #include "OsuBot.h" 2 | 3 | // --------------------------------------Initialize constants--------------------------------------------------- 4 | const unsigned char OsuBot::TIME_SIG[] = { 0xDB, 0x5D, 0xE8, 0x8B, 0x45, 0xE8, 0xA3 }; // A3 is insturction 5 | const char* OsuBot::TIME_MASK = "xxxxxxx"; 6 | const int OsuBot::TIME_SIG_OFFSET = 6 + 1; // + 1 goes to address that points to the currentAudioTime value 7 | 8 | //const unsigned char OsuBot::PAUSE_SIGNAL_SIG[] = { 0x75, 0x26, 0xDD, 0x05 }; 9 | //const char* OsuBot::PAUSE_SIGNAL_MASK = "xxxx"; 10 | //const int OsuBot::PAUSE_SIGNAL_SIG_OFFSET = -5; 11 | 12 | // -----------------------------------Constructor & Destructor--------------------------------------- 13 | OsuBot::OsuBot(wchar_t* processName) 14 | { 15 | cout << "-----------------Initializing-----------------" << endl; 16 | cout << "Getting processID..." << endl; 17 | (this)->processID = ProcessTools::getProcessID(processName); 18 | if ((this)->processIsOpen()) { 19 | cout << "Start parsing data from osu!.db in separate thread..." << endl; 20 | thread osuDbThread(&OsuDbParser::startParsingData, &(this)->osuDbMin, Config::OSUROOTPATH + "osu!.db"); 21 | cout << "Storing process handle..." << endl; 22 | (this)->osuHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (this)->processID); 23 | if ((this)->osuHandle == NULL) { throw OsuBotException("Failed to get osuHandle."); } 24 | cout << "Storing window handle..." << endl; 25 | (this)->windowHandle = ProcessTools::getWindowHandle((this)->processID); 26 | cout << "Storing window title Handle..." << endl; 27 | (this)->windowTitleHandle = ProcessTools::getWindowTitleHandle("osu!"); 28 | if ((this)->windowTitleHandle == NULL) { 29 | cout << "Failed to get window title handle. Please make sure you aren't running any map. Retrying..." << endl; 30 | do { 31 | Sleep(1500); 32 | (this)->windowTitleHandle = ProcessTools::getWindowTitleHandle("osu!"); 33 | } while ((this)->windowTitleHandle == NULL); 34 | } 35 | if ((this)->windowHandle == 0) { throw OsuBotException("Failed to get windowHandle."); } 36 | cout << "Setting data needed for cursor position..." << endl; 37 | (this)->setCursorStartPoints(); 38 | cout << "Storing currentAudioTimeAddress... (This might take a while)" << endl; 39 | (this)->currentAudioTimeAddress = (this)->getCurrentAudioTimeAddress(); 40 | //cout << "Storing pauseSignalAddress..." << endl; 41 | //(this)->pauseSignalAddress = (this)->currentAudioTimeAddress + 0x24; // 0x24 is the offset to the pauseSignalAddress 42 | cout << "Waiting osuDbThread to join..." << endl; 43 | osuDbThread.join(); 44 | cout << "-----------------Initialization done!-----------------" << endl; 45 | } 46 | else { 47 | throw OsuBotException("Failed to get processID. Make sure osu! is running!"); 48 | } 49 | 50 | } 51 | 52 | OsuBot::~OsuBot() 53 | { 54 | CloseHandle((this)->osuHandle); 55 | } 56 | 57 | // ----------------------------------------Process related functions----------------------------------------------- 58 | bool OsuBot::processIsOpen() { 59 | return (this)->processID == NULL ? false : true; 60 | } 61 | 62 | DWORD OsuBot::getCurrentAudioTimeAddress() { 63 | // return address that stores currentAudioTime value if found, NULL if not 64 | // address to instruction that writes to the currentAudioTime 65 | DWORD currentAudioTimeInstructionAddress = SigScanner::findPattern((this)->osuHandle, OsuBot::TIME_SIG, OsuBot::TIME_MASK, OsuBot::TIME_SIG_OFFSET); 66 | if (currentAudioTimeInstructionAddress != NULL) { 67 | // address that stores the currentAudioTime value 68 | DWORD currentAudioTimeAddress = NULL; 69 | ReadProcessMemory((this)->osuHandle, (LPCVOID)currentAudioTimeInstructionAddress, ¤tAudioTimeAddress, sizeof DWORD, nullptr); 70 | if (currentAudioTimeAddress != NULL) { 71 | return currentAudioTimeAddress; 72 | } 73 | throw OsuBotException("Failed to get time address. Make sure the offset is correct."); 74 | } 75 | throw OsuBotException("Failed to get time instruction address. Try to restart the osu client."); 76 | //return NULL; 77 | } 78 | 79 | int OsuBot::getCurrentAudioTime() { 80 | // if failed to get data, return -1 81 | int currentAudioTime = -1; 82 | ReadProcessMemory((this)->osuHandle, (LPCVOID)(this)->currentAudioTimeAddress, ¤tAudioTime, sizeof(int), nullptr); 83 | return currentAudioTime; 84 | } 85 | 86 | //DWORD OsuBot::getPauseSignalAddress() { // TODO: instead of doing this, use offset from the timeAddress gotten. 87 | // DWORD pauseSignalInstructionAddress = SigScanner::findPattern((this)->osuHandle, OsuBot::PAUSE_SIGNAL_SIG, OsuBot::PAUSE_SIGNAL_MASK, OsuBot::PAUSE_SIGNAL_SIG_OFFSET); 88 | // if (pauseSignalInstructionAddress != NULL) { 89 | // DWORD pauseSignalAddress = NULL; 90 | // ReadProcessMemory((this)->osuHandle, (LPCVOID)pauseSignalInstructionAddress, &pauseSignalAddress, sizeof(DWORD), nullptr); 91 | // if (pauseSignalAddress != NULL) { 92 | // return pauseSignalAddress; 93 | // } 94 | // throw OsuBotException("Failed to get pause signal address."); 95 | // } 96 | // throw OsuBotException("Failed to get pause signal instruction address."); 97 | //} 98 | 99 | //int OsuBot::getPauseSignal() { 100 | // // if failed to get data, return -1 101 | // int pauseSignal = -1; 102 | // ReadProcessMemory((this)->osuHandle, (LPCVOID)(this)->pauseSignalAddress, &pauseSignal, sizeof(int), nullptr); 103 | // return pauseSignal; 104 | //} 105 | 106 | // -----------------------------------------Functions for threading------------------------------------------- 107 | void OsuBot::updateIsPlaying() { 108 | // this function is only called when beatmap is detected 109 | // so first set isPlaying to true and then check if it's still true 110 | (this)->isPlaying = true; 111 | // variable for checking if the time "decreases", which means the player replay the map 112 | int lastInstance = (this)->getCurrentAudioTime(); 113 | while ((this)->isPlaying) { 114 | if (ProcessTools::getWindowTextString((this)->windowTitleHandle) == "osu!") { 115 | // if map is exited, set isPlaying to false and exit this loop (and this thread) 116 | (this)->isPlaying = false; 117 | return; 118 | } 119 | int currentTime = (this)->getCurrentAudioTime(); 120 | // constantly check if map is replayed 121 | // Becuz the audioTime actually starts from -ve number when starting the map, >= 0 so that it won't go into this condition at the start of the map 122 | if (currentTime < lastInstance && currentTime >= 0) { 123 | (this)->isPlaying = false; 124 | return; 125 | } 126 | // if currentTime is increasing, set lastInstance to currentTime for comparision in next loop 127 | lastInstance = currentTime; 128 | Sleep(100); 129 | } 130 | } 131 | // -------------------------------------------Gameplay related functions---------------------------------------- 132 | // credit to Andrey Tokarev (CoderOffka) https://github.com/CoderOffka/osuAutoBot/blob/master/main.cpp 133 | void OsuBot::setCursorStartPoints() { // TODO: set this in thread and run in background to detect sizechange 134 | RECT rect; 135 | GetClientRect((this)->windowHandle, &rect); 136 | int x; 137 | int y; 138 | if (rect.right != 0 && rect.bottom != 0) { // if not fullscreen 139 | x = static_cast(rect.right); 140 | y = static_cast(rect.bottom); 141 | } 142 | else { // rect will give 0 if fullscreen and the window is not active, so size = screen resolution in this case 143 | x = GetSystemMetrics(SM_CXSCREEN); 144 | y = GetSystemMetrics(SM_CYSCREEN); 145 | } 146 | 147 | int screenWidth = x; 148 | int screenHeight = y; 149 | 150 | // some neccessary adjustment to screenSize ? 151 | if (screenWidth * 3 > screenHeight * 4) { 152 | screenWidth = screenHeight * 4 / 3; 153 | } 154 | else { 155 | screenHeight = screenWidth * 3 / 4; 156 | } 157 | 158 | // multiplier is needed as the grid size changes accordingly to screen size 159 | // IMPORTANT!! Use float instead of int or POINT or this will be inaccurate 160 | float multiplierX = screenWidth / 640.0f; 161 | float multiplierY = screenHeight / 480.0f; 162 | 163 | // default playField size before multipliying with multiplier 164 | POINT beatmapPlayfield; 165 | beatmapPlayfield.x = 512.0f; 166 | beatmapPlayfield.y = 384.0f; 167 | 168 | // another neccessary adjustment ? 169 | int offsetX = static_cast(x - beatmapPlayfield.x * multiplierX) / 2; 170 | int offsetY = static_cast(y - beatmapPlayfield.y * multiplierY) / 2; 171 | 172 | // no idea why x & y is initialized in this way but anyways 173 | POINT p; 174 | p.x = 1; 175 | p.y = static_cast(8.0f * multiplierY); 176 | ClientToScreen((this)->windowHandle, &p); 177 | 178 | 179 | POINT cursorStartPoints; 180 | cursorStartPoints.x = static_cast(p.x + offsetX); 181 | cursorStartPoints.y = static_cast(p.y + offsetY); 182 | 183 | // set to member variables 184 | (this)->pointsMultiplierX = multiplierX; 185 | (this)->pointsMultiplierY = multiplierY; 186 | (this)->cursorStartPoints = cursorStartPoints; 187 | } 188 | 189 | POINT OsuBot::getScaledPoints(int x, int y) { 190 | POINT p; 191 | p.x = x * (this)->pointsMultiplierX + (this)->cursorStartPoints.x; 192 | p.y = y * (this)->pointsMultiplierY + (this)->cursorStartPoints.y; 193 | return p; 194 | } 195 | 196 | void OsuBot::recalcSliderDuration(double &sliderDuration, unsigned int mod, double randomNum) { 197 | // account for user defined offset and also offset for randomNum so as to not miss the sliderEnds 198 | sliderDuration = sliderDuration + Config::SLIDER_DURATION_OFFSET - randomNum; 199 | if (mod == 64 || mod == 80) { 200 | sliderDuration /= 1.5; 201 | } 202 | } 203 | 204 | double OsuBot::getMoveToNextPointDuration(HitObject currentHitObject, HitObject nextHitObject, unsigned int mod, unsigned int divFactor) { 205 | double moveDuration; 206 | if (mod == 64 || mod == 80) { 207 | switch (currentHitObject.type) { 208 | case HitObject::TypeE::circle: { 209 | moveDuration = (nextHitObject.time - currentHitObject.time) * 0.67 / divFactor; 210 | break; 211 | } 212 | case HitObject::TypeE::slider: { 213 | moveDuration = ((nextHitObject.time - currentHitObject.time) * 0.67 - currentHitObject.sliderDuration) / divFactor; 214 | break; 215 | } 216 | case HitObject::TypeE::spinner: { 217 | moveDuration = (nextHitObject.time - currentHitObject.spinnerEndTime) * 0.67 / divFactor; 218 | break; 219 | } 220 | } 221 | } 222 | else { 223 | switch (currentHitObject.type) { 224 | case HitObject::TypeE::circle: { 225 | moveDuration = (nextHitObject.time - currentHitObject.time) / divFactor; 226 | break; 227 | } 228 | case HitObject::TypeE::slider: { 229 | moveDuration = (nextHitObject.time - currentHitObject.time - currentHitObject.sliderDuration) / divFactor; 230 | break; 231 | } 232 | case HitObject::TypeE::spinner: { 233 | moveDuration = (nextHitObject.time - currentHitObject.spinnerEndTime) / divFactor; 234 | break; 235 | } 236 | } 237 | } 238 | return moveDuration; 239 | } 240 | 241 | double OsuBot::getSpinDuration(HitObject currentHitObject, unsigned int mod) { 242 | double spinDuration; 243 | if (mod == 64 || mod == 80) { 244 | spinDuration = (currentHitObject.spinnerEndTime - currentHitObject.time) * 0.67; 245 | } 246 | else { 247 | spinDuration = currentHitObject.spinnerEndTime - currentHitObject.time; 248 | } 249 | return spinDuration; 250 | } 251 | 252 | // -------------------------------------------Mods------------------------------------------------- 253 | // wrapper function 254 | void OsuBot::startMod(Beatmap beatmap, unsigned int bot, unsigned int mod) { 255 | switch (bot) { 256 | case 1: 257 | (this)->modAuto(beatmap, mod); 258 | break; 259 | case 2: 260 | (this)->modAutoPilot(beatmap, mod); 261 | break; 262 | case 3: 263 | (this)->modRelax(beatmap, mod); 264 | break; 265 | } 266 | } 267 | 268 | void OsuBot::modRelax(Beatmap beatmap, unsigned int mod) { 269 | if ((this)->isPlaying == false) { return; } 270 | bool leftKeysTurn = true; 271 | for (auto hitObject : beatmap.HitObjects) { 272 | // generate random number in each loop to give more realistic plays 273 | double randomNum = Functions::randomNumGenerator(Config::CLICK_OFFSET_DEVIATION); 274 | // loop for waiting till timing to press comes 275 | while (true) { 276 | // constantly check if the map is still being played 277 | // if it's not, break out of this function to end the map 278 | if ((this)->isPlaying == false) { return; } 279 | else if (GetAsyncKeyState(VK_ESCAPE) & 1 && (GetConsoleWindow() == GetForegroundWindow())) { 280 | if ((this)->isPlaying) { 281 | cout << "Please exit the map first!" << endl; 282 | } 283 | } 284 | else if (GetAsyncKeyState(VK_SHIFT) & GetAsyncKeyState(0x43) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { // shift + c 285 | Config::clearAndChangeConfig(); 286 | } 287 | if (((this)->getCurrentAudioTime() > hitObject.time - beatmap.timeRange300 + Config::CLICK_OFFSET + randomNum)) { 288 | break; 289 | } 290 | } 291 | Timer localTimer = Timer(); 292 | if (hitObject.type == HitObject::TypeE::circle) { 293 | auto circleSleepTime = Config::CIRCLE_SLEEPTIME * Timer::prefix; 294 | if (leftKeysTurn) { 295 | Input::sentKeyInput(Config::LEFT_KEY, true); // press left key 296 | localTimer.start(); 297 | while (localTimer.getTimePast() < circleSleepTime) {} 298 | Input::sentKeyInput(Config::LEFT_KEY, false); // release left key 299 | leftKeysTurn = false; 300 | } 301 | else { 302 | Input::sentKeyInput(Config::RIGHT_KEY, true); // press right key 303 | localTimer.start(); 304 | while (localTimer.getTimePast() < circleSleepTime) {} 305 | Input::sentKeyInput(Config::RIGHT_KEY, false); // release right key 306 | leftKeysTurn = true; 307 | } 308 | } 309 | else if (hitObject.type == HitObject::TypeE::slider) { 310 | (this)->recalcSliderDuration(hitObject.sliderDuration, mod, randomNum); 311 | auto sliderSleepTime = hitObject.sliderDuration * Timer::prefix; 312 | if (leftKeysTurn) { 313 | Input::sentKeyInput(Config::LEFT_KEY, true); // press left key 314 | localTimer.start(); 315 | while (localTimer.getTimePast() < sliderSleepTime) {} 316 | Input::sentKeyInput(Config::LEFT_KEY, false); // release left key 317 | leftKeysTurn = false; 318 | } 319 | else { 320 | Input::sentKeyInput(Config::RIGHT_KEY, true); // press right key 321 | localTimer.start(); 322 | while (localTimer.getTimePast() < sliderSleepTime) {} 323 | Input::sentKeyInput(Config::RIGHT_KEY, false); // release right key 324 | leftKeysTurn = true; 325 | } 326 | } 327 | else if (hitObject.type == HitObject::TypeE::spinner) { 328 | double spinDuration = (this)->getSpinDuration(hitObject, mod); 329 | spinDuration *= Timer::prefix; 330 | if (leftKeysTurn) { 331 | Input::sentKeyInput(Config::LEFT_KEY, true); // press left key 332 | localTimer.start(); 333 | while (localTimer.getTimePast() < spinDuration) {} 334 | Input::sentKeyInput(Config::LEFT_KEY, false); // release left key 335 | leftKeysTurn = false; 336 | } 337 | else { 338 | Input::sentKeyInput(Config::RIGHT_KEY, true); // press right key 339 | localTimer.start(); 340 | while (localTimer.getTimePast() < spinDuration) {} 341 | Input::sentKeyInput(Config::RIGHT_KEY, false); // release right key 342 | leftKeysTurn = true; 343 | } 344 | } 345 | } 346 | 347 | // release both key to prevent unwanted behaviour 348 | Input::sentKeyInput(Config::LEFT_KEY, false); 349 | Input::sentKeyInput(Config::RIGHT_KEY, false); 350 | } 351 | 352 | void OsuBot::modAutoPilot(Beatmap beatmap, unsigned int mod) { 353 | if ((this)->isPlaying == false) { return; } 354 | // create exitSignal to be sent to setPointsOnCurveThread so that if map is not being played, the calculation can stop immediately 355 | // refer to http://thispointer.com/c11-how-to-stop-or-terminate-a-thread/ if have any doubt 356 | promise exitSignal; 357 | future futureObj = exitSignal.get_future(); 358 | // ref() is necessary for passing by ref in thread 359 | // start the thread to start calculating slider points 360 | thread setPointsOnCurveThread(&OsuBot::recalcHitObjectsAndSetPointsOnCurve, this, ref(beatmap), mod, move(futureObj)); 361 | POINT center = (this)->getScaledPoints(256, 192); // 256, 192 is always the center of osu virtual screen 362 | 363 | // move to first hitObject when the beatmap starts 364 | HitObject firstHitObject = beatmap.HitObjects.front(); 365 | // "-300" and "250" following are the preset adjustments as to when the cursor should move 366 | float firstWaitDuration = firstHitObject.time - 300; 367 | float firstMoveDuration = 250; 368 | // check if the time is too short 369 | if (firstWaitDuration <= 0) { 370 | firstWaitDuration = 1; 371 | firstMoveDuration = firstHitObject.time / 2; 372 | } 373 | if (mod == 64 || mod == 80) { 374 | firstMoveDuration *= 0.67; 375 | } 376 | while ((this)->getCurrentAudioTime() < firstWaitDuration || (this)->getCurrentAudioTime() > beatmap.HitObjects.back().time) { 377 | // while waiting for the time to hit hitObject, constantly check if the map is still being played 378 | // if it's not, break out of this function to end the map 379 | if ((this)->isPlaying == false) { 380 | exitSignal.set_value(); // signal thread to stop 381 | setPointsOnCurveThread.join(); // join thread before returning (or else error) 382 | return; 383 | } 384 | else if (GetAsyncKeyState(VK_ESCAPE) & 1 && (GetConsoleWindow() == GetForegroundWindow())) { 385 | if ((this)->isPlaying) { 386 | cout << "Please exit the map first!" << endl; 387 | } 388 | } 389 | else if (GetAsyncKeyState(VK_SHIFT) & GetAsyncKeyState(0x43) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { // shift + c 390 | Config::clearAndChangeConfig(); 391 | } 392 | } 393 | // refresh the firstHitObject just in case the worker thread didn't change the coordinates (if HR) before it was assigned 394 | firstHitObject = beatmap.HitObjects.front(); 395 | POINT startPoint = (this)->getScaledPoints(firstHitObject.x, firstHitObject.y); 396 | // determine current cursor position and move from there to the firstHitObject 397 | POINT currentCursorPos; 398 | GetCursorPos(¤tCursorPos); 399 | Input::circleLinearMove(currentCursorPos, startPoint, firstMoveDuration); 400 | 401 | 402 | // starting of first to last hitObject 403 | for (int i = 1; i < beatmap.HitObjects.size(); i++) { 404 | if ((this)->isPlaying == false) { 405 | exitSignal.set_value(); // signal thread to stop 406 | setPointsOnCurveThread.join(); // join thread before returning (or else error) 407 | return; 408 | } 409 | HitObject currentHitObject = beatmap.HitObjects.at(i - 1); 410 | HitObject nextHitObject = beatmap.HitObjects.at(i); 411 | POINT currentPoint = (this)->getScaledPoints(currentHitObject.x, currentHitObject.y); 412 | POINT nextPoint = (this)->getScaledPoints(nextHitObject.x, nextHitObject.y); 413 | // at this point, the cursor is already on the hitObject. 414 | // so, to allow for longer range of hit time, wait until the time exceeds the time range of 300 points, then move 415 | while ((this)->getCurrentAudioTime() < currentHitObject.time + beatmap.timeRange300 / 1.5 + Config::CLICK_OFFSET) { 416 | if ((this)->isPlaying == false) { 417 | exitSignal.set_value(); // signal thread to stop 418 | setPointsOnCurveThread.join(); // join thread before returning (or else error) 419 | return; 420 | } 421 | else if (GetAsyncKeyState(VK_ESCAPE) & 1 && (GetConsoleWindow() == GetForegroundWindow())) { 422 | if ((this)->isPlaying) { 423 | cout << "Please exit the map first!" << endl; 424 | } 425 | } 426 | else if (GetAsyncKeyState(VK_SHIFT) & GetAsyncKeyState(0x43) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { // shift + c 427 | Config::clearAndChangeConfig(); 428 | } 429 | } 430 | 431 | if (currentHitObject.type == HitObject::TypeE::slider) { 432 | // it's not known by this time if the thread has calculated the points on this slider, 433 | // so check and wait for it if it hasn't 434 | if (beatmap.HitObjects.at(i - 1).sliderPointsAreCalculated == false) { 435 | while (true) { 436 | if (beatmap.HitObjects.at(i - 1).sliderPointsAreCalculated) { 437 | break; 438 | } 439 | } 440 | // then rewrite currentHitObject to get the calculated points 441 | currentHitObject = beatmap.HitObjects.at(i - 1); 442 | } 443 | (this)->recalcSliderDuration(currentHitObject.sliderDuration, mod); 444 | // move slider regardless of type and after reaching the slider end, move linearly to next hitObject 445 | // the duration of moving linearly is divide by 2 to reduce latency and also improve readability 446 | POINT endPoint = Input::sliderMove(currentHitObject, (this)->pointsMultiplierX, (this)->pointsMultiplierY, (this)->cursorStartPoints); 447 | double moveDuration = (this)->getMoveToNextPointDuration(currentHitObject, nextHitObject, mod, 2); 448 | Input::circleLinearMove(endPoint, nextPoint, moveDuration); 449 | } 450 | else if (currentHitObject.type == HitObject::TypeE::spinner) { 451 | double spinDuration = (this)->getSpinDuration(currentHitObject, mod); 452 | POINT endPoint = Input::spinnerMove(center, spinDuration); 453 | 454 | double moveDuration = (this)->getMoveToNextPointDuration(currentHitObject, nextHitObject, mod, 2); 455 | Input::circleLinearMove(endPoint, nextPoint, moveDuration); 456 | } 457 | else { // circle 458 | double moveDuration = (this)->getMoveToNextPointDuration(currentHitObject, nextHitObject, mod, 2); 459 | Input::circleLinearMove(currentPoint, nextPoint, moveDuration); 460 | } 461 | } 462 | 463 | // play last hitObject as it is not played in the loop (if it's circle then it's already done) 464 | HitObject lastHitObject = beatmap.HitObjects.back(); 465 | while ((this)->getCurrentAudioTime() < lastHitObject.time + beatmap.timeRange300 / 1.5 + Config::CLICK_OFFSET) { 466 | if ((this)->isPlaying == false) { 467 | exitSignal.set_value(); // signal thread to stop 468 | setPointsOnCurveThread.join(); // join thread before returning (or else error) 469 | return; 470 | } 471 | } 472 | if (lastHitObject.type == HitObject::TypeE::slider) { 473 | (this)->recalcSliderDuration(lastHitObject.sliderDuration, mod); 474 | Input::sliderMove(lastHitObject, (this)->pointsMultiplierX, (this)->pointsMultiplierY, (this)->cursorStartPoints); 475 | } 476 | else if (lastHitObject.type == HitObject::TypeE::spinner) { 477 | double spinDuration = (this)->getSpinDuration(lastHitObject, mod); 478 | POINT endPoint = Input::spinnerMove(center, spinDuration); 479 | } 480 | setPointsOnCurveThread.join(); // dun forget to join the thread b4 exiting 481 | } 482 | 483 | void OsuBot::modAuto(Beatmap beatmap, unsigned int mod) { 484 | if ((this)->isPlaying == false) { return; } 485 | // create exitSignal to be sent to setPointsOnCurveThread so that if map is not being played, the calculation can stop immediately 486 | // refer to http://thispointer.com/c11-how-to-stop-or-terminate-a-thread/ if have any doubt 487 | promise exitSignal; 488 | future futureObj = exitSignal.get_future(); 489 | // ref() is necessary for passing by ref in thread 490 | // start the thread to start calculating slider points 491 | thread setPointsOnCurveThread(&OsuBot::recalcHitObjectsAndSetPointsOnCurve, this, ref(beatmap), mod, move(futureObj)); 492 | POINT center = (this)->getScaledPoints(256, 192); // 256, 192 is always the center of osu virtual screen 493 | bool leftKeysTurn = true; 494 | 495 | // move to first hitObject when the beatmap starts 496 | // "-300" and "250" are the preset adjustments as to when the cursor should move 497 | HitObject firstHitObject = beatmap.HitObjects.front(); 498 | float firstWaitDuration = firstHitObject.time - 300; 499 | float firstMoveDuration = 250; 500 | // check if the time is too short 501 | if (firstWaitDuration <= 0) { 502 | firstWaitDuration = 1; 503 | firstMoveDuration = firstHitObject.time / 2; 504 | } 505 | if (mod == 64 || mod == 80) { 506 | firstMoveDuration *= 0.67; 507 | } 508 | // OR condition becuz if the firstObject time is too short, the value in currentAudioTime is very large, unsigned int value 509 | while ((this)->getCurrentAudioTime() < firstWaitDuration || (this)->getCurrentAudioTime() > beatmap.HitObjects.back().time) { 510 | // while waiting for the time to hit hitObject, constantly check if the map is still being played 511 | // if it's not, break out of this function to end the map 512 | if ((this)->isPlaying == false) { 513 | exitSignal.set_value(); // signal thread to stop 514 | setPointsOnCurveThread.join(); // join thread before returning (or else error) 515 | return; 516 | } 517 | else if (GetAsyncKeyState(VK_ESCAPE) & 1 && (GetConsoleWindow() == GetForegroundWindow())) { 518 | if ((this)->isPlaying) { 519 | cout << "Please exit the map first!" << endl; 520 | } 521 | } 522 | else if (GetAsyncKeyState(VK_SHIFT) & GetAsyncKeyState(0x43) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { // shift + c 523 | Config::clearAndChangeConfig(); 524 | } 525 | } 526 | // refresh the firstHitObject just in case the worker thread didn't change the coordinates (if HR) before it was assigned 527 | firstHitObject = beatmap.HitObjects.front(); 528 | POINT startPoint = (this)->getScaledPoints(firstHitObject.x, firstHitObject.y); 529 | // determine current cursor position and move from there to the firstHitObject 530 | POINT currentCursorPos; 531 | GetCursorPos(¤tCursorPos); 532 | Input::circleLinearMove(currentCursorPos, startPoint, firstMoveDuration); 533 | 534 | // starting of first to last hitObject 535 | for (int i = 1; i < beatmap.HitObjects.size(); i++) { 536 | if ((this)->isPlaying == false) { 537 | exitSignal.set_value(); 538 | setPointsOnCurveThread.join(); 539 | return; 540 | } 541 | HitObject currentHitObject = beatmap.HitObjects.at(i - 1); 542 | HitObject nextHitObject = beatmap.HitObjects.at(i); 543 | POINT currentPoint = (this)->getScaledPoints(currentHitObject.x, currentHitObject.y); 544 | POINT nextPoint = (this)->getScaledPoints(nextHitObject.x, nextHitObject.y); 545 | double randomNum = Functions::randomNumGenerator(Config::CLICK_OFFSET_DEVIATION); 546 | 547 | // at this point, the cursor is already on the hitObject. 548 | // If it is DT or HR DT, timeRange300 is used to offset the rapid speed which causes misses and 100s 549 | auto setOffTime = currentHitObject.time + Config::CLICK_OFFSET + randomNum; 550 | if (mod == 64 || mod == 80) { 551 | setOffTime -= beatmap.timeRange300; 552 | } 553 | 554 | while ((this)->getCurrentAudioTime() < setOffTime) { 555 | if ((this)->isPlaying == false) { 556 | exitSignal.set_value(); 557 | setPointsOnCurveThread.join(); 558 | return; 559 | } 560 | else if (GetAsyncKeyState(VK_ESCAPE) & 1 && (GetConsoleWindow() == GetForegroundWindow())) { 561 | if ((this)->isPlaying) { 562 | cout << "Please exit the map first!" << endl; 563 | } 564 | } 565 | else if (GetAsyncKeyState(VK_SHIFT) & GetAsyncKeyState(0x43) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { // shift + c 566 | Config::clearAndChangeConfig(); 567 | } 568 | } 569 | 570 | // press key when reach the time 571 | if (leftKeysTurn) { 572 | Input::sentKeyInput(Config::LEFT_KEY, true); // press left key 573 | } 574 | else { 575 | Input::sentKeyInput(Config::RIGHT_KEY, true); // press right key 576 | } 577 | 578 | if (currentHitObject.type == HitObject::TypeE::slider) { 579 | // it's not known by this time if the thread has calculated the points on this slider, 580 | // so check and wait for it if it hasn't 581 | if (beatmap.HitObjects.at(i - 1).sliderPointsAreCalculated == false) { 582 | while (true) { 583 | if (beatmap.HitObjects.at(i - 1).sliderPointsAreCalculated) { 584 | break; 585 | } 586 | } 587 | // then rewrite currentHitObject to get the calculated points 588 | currentHitObject = beatmap.HitObjects.at(i - 1); 589 | } 590 | (this)->recalcSliderDuration(currentHitObject.sliderDuration, mod, randomNum); 591 | // move slider regardless of type and after reaching the slider end, move linearly to next hitObject 592 | // the duration of moving linearly is divide by 2 to reduce latency and also improve readability 593 | POINT endPoint = Input::sliderMove(currentHitObject, (this)->pointsMultiplierX, (this)->pointsMultiplierY, (this)->cursorStartPoints); 594 | // after the slider ends, release the key 595 | if (leftKeysTurn) { 596 | Input::sentKeyInput(Config::LEFT_KEY, false); // release left key 597 | leftKeysTurn = false; 598 | } 599 | else { 600 | Input::sentKeyInput(Config::RIGHT_KEY, false); // release right key 601 | leftKeysTurn = true; 602 | } 603 | double moveDuration = (this)->getMoveToNextPointDuration(currentHitObject, nextHitObject, mod, 2); 604 | Input::circleLinearMove(endPoint, nextPoint, moveDuration); 605 | } 606 | else if (currentHitObject.type == HitObject::TypeE::spinner) { 607 | double spinDuration = (this)->getSpinDuration(currentHitObject, mod); 608 | POINT endPoint = Input::spinnerMove(center, spinDuration); 609 | if (leftKeysTurn) { 610 | Input::sentKeyInput(Config::LEFT_KEY, false); // release left key 611 | leftKeysTurn = false; 612 | } 613 | else { 614 | Input::sentKeyInput(Config::RIGHT_KEY, false); // release right key 615 | leftKeysTurn = true; 616 | } 617 | double moveDuration = (this)->getMoveToNextPointDuration(currentHitObject, nextHitObject, mod, 2); 618 | Input::circleLinearMove(endPoint, nextPoint, moveDuration); 619 | } 620 | else { // circle 621 | // sleep so that the key press is detected by the game client 622 | // becuz if not sleep, pressing and releasing happen almost simultaneously and cannot be detected 623 | // should be at least 10 millisecs 624 | auto circleSleepTime = Config::CIRCLE_SLEEPTIME * Timer::prefix; 625 | Timer localTimer = Timer(); 626 | localTimer.start(); 627 | while (localTimer.getTimePast() < circleSleepTime) {} 628 | if (leftKeysTurn) { 629 | Input::sentKeyInput(Config::LEFT_KEY, false); // release left key 630 | leftKeysTurn = false; 631 | } 632 | else { 633 | Input::sentKeyInput(Config::RIGHT_KEY, false); // release right key 634 | leftKeysTurn = true; 635 | } 636 | double moveDuration = (this)->getMoveToNextPointDuration(currentHitObject, nextHitObject, mod, 2); 637 | Input::circleLinearMove(currentPoint, nextPoint, moveDuration); 638 | } 639 | } 640 | 641 | // play last hitObject as it is not played in the loop 642 | HitObject lastHitObject = beatmap.HitObjects.back(); 643 | double randomNum = Functions::randomNumGenerator(Config::CLICK_OFFSET_DEVIATION); 644 | auto setOffTime = lastHitObject.time + Config::CLICK_OFFSET + randomNum; 645 | if (mod == 64 || mod == 80) { 646 | setOffTime -= beatmap.timeRange300; 647 | } 648 | while ((this)->getCurrentAudioTime() < setOffTime) { 649 | if ((this)->isPlaying == false) { 650 | exitSignal.set_value(); 651 | setPointsOnCurveThread.join(); 652 | return; 653 | } 654 | } 655 | if (leftKeysTurn) { 656 | Input::sentKeyInput(Config::LEFT_KEY, true); // press left key 657 | } 658 | else { 659 | Input::sentKeyInput(Config::RIGHT_KEY, true); // press right key 660 | } 661 | 662 | if (lastHitObject.type == HitObject::TypeE::slider) { 663 | (this)->recalcSliderDuration(lastHitObject.sliderDuration, mod, randomNum); 664 | Input::sliderMove(lastHitObject, (this)->pointsMultiplierX, (this)->pointsMultiplierY, (this)->cursorStartPoints); 665 | } 666 | else if (lastHitObject.type == HitObject::TypeE::spinner) { 667 | double spinDuration = (this)->getSpinDuration(lastHitObject, mod); 668 | Input::spinnerMove(center, spinDuration); 669 | } 670 | else { // account for circle. 671 | Timer localTimer = Timer(); 672 | localTimer.start(); 673 | auto circleSleepTime = Config::CIRCLE_SLEEPTIME * Timer::prefix; 674 | while (localTimer.getTimePast() < circleSleepTime) {} 675 | } 676 | 677 | // release both key to prevent unwanted behaviour 678 | Input::sentKeyInput(Config::LEFT_KEY, false); 679 | Input::sentKeyInput(Config::RIGHT_KEY, false); 680 | setPointsOnCurveThread.join(); // dun forget to join the thread b4 exiting 681 | } 682 | 683 | // -----------------------------------------Calculations--------------------------------------------- 684 | // for threading 685 | // futureObj is simply stop signal from parent thread 686 | void OsuBot::recalcHitObjectsAndSetPointsOnCurve(Beatmap &beatmap, unsigned int mod, future futureObj) { 687 | int stackCount = 1; // 1 means no stack, 2 means 2 objects stack together 688 | // initialize to junk value so that 1st hitObject's attributes are assigned to them 689 | POINT previousStackedPoint; 690 | previousStackedPoint.x = -1000; 691 | previousStackedPoint.y = -1000; 692 | int previousTime = -1000; 693 | // calculation from https://github.com/CoderOffka/osuAutoBot/blob/fe45335697bc5200163be162c39ba595868b7c1b/main.cpp#L455 694 | double stackOffset = (512.0f / 16.0f) * (1.0f - 0.7f * (beatmap.Difficulty.circleSize - 5.0f) / 5.0f) / 10.0f; 695 | // calculation from https://github.com/ppy/osu/blob/0afe33d32fb647f88582286b1e9d5082f81f2670/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs 696 | double stackTimeThreshold = beatmap.approachWindow * beatmap.General.stackLeniency; 697 | 698 | for (int index = 0; index < beatmap.HitObjects.size(); index++) { 699 | // in each loop, check if stop signal is sent from the calling thread and stop calculating if true 700 | if (futureObj.wait_for(chrono::milliseconds(0)) != future_status::timeout) { 701 | return; 702 | } 703 | // hitObject is a copy, HitObjects.at(index) is a reference 704 | auto hitObject = beatmap.HitObjects.at(index); 705 | 706 | // calculating points on curve 707 | if (hitObject.type == HitObject::TypeE::slider) { 708 | if (hitObject.sliderType == 'P') { 709 | // calculation for 'P' type slider 710 | // this is the translation of code from official code: https://github.com/ppy/osu/blob/master/osu.Game/Rulesets/Objects/CircularArcApproximator.cs 711 | const float tolerance = 0.01f; // change from 0.1f (official) to enhance smoothness 712 | const double PI = 4 * atan(1); 713 | 714 | CurvePointsS a = hitObject.CurvePoints.at(0).at(0); // start 715 | CurvePointsS b = hitObject.CurvePoints.at(0).at(1); // pass through 716 | CurvePointsS c = hitObject.CurvePoints.at(0).at(2); // end 717 | 718 | // account for hardrock mod 719 | if (mod == 16 || mod == 80) { 720 | a.y = 384 - a.y; 721 | b.y = 384 - b.y; 722 | c.y = 384 - c.y; 723 | } 724 | 725 | // As there's no Vector2 data type in c++, I calculate each vector separately as x and y 726 | float ax = a.x; 727 | float ay = a.y; 728 | float bx = b.x; 729 | float by = b.y; 730 | float cx = c.x; 731 | float cy = c.y; 732 | 733 | // square of distance btw each point 734 | auto aSq = pow(bx - cx, 2) + pow(by - cy, 2); 735 | auto bSq = pow(ax - cx, 2) + pow(ay - cy, 2); 736 | auto cSq = pow(ax - bx, 2) + pow(ay - by, 2); 737 | 738 | // Account for "degenerate triangle" curve according to official code 739 | if (Functions::almostEquals(aSq, 0) || Functions::almostEquals(bSq, 0) || Functions::almostEquals(cSq, 0)) { 740 | beatmap.HitObjects.at(index).sliderType = 'B'; // fake sliderType to 'B' 741 | index--; // decrease index by 1 so that next loop goes to same hitObject but this time goes into 'B' if block 742 | continue; 743 | } 744 | 745 | // own calculation which checks if the circle is almost like linear to ensure smoothness 746 | auto linearDistance = sqrt(bSq); 747 | auto circleDistance = sqrt(aSq) + sqrt(cSq); 748 | if (Functions::almostEquals(linearDistance, circleDistance, 0.01)) { 749 | beatmap.HitObjects.at(index).sliderType = 'L'; // fake that this slider is Linear 750 | index--; // decrease index by 1 so that next loop goes to same hitObject but this time goes into 'B' if block 751 | continue; 752 | } 753 | 754 | float s = aSq * (bSq + cSq - aSq); 755 | float t = bSq * (aSq + cSq - bSq); 756 | float u = cSq * (aSq + bSq - cSq); 757 | 758 | float sum = s + t + u; 759 | 760 | if (Functions::almostEquals(sum, 0)) { 761 | beatmap.HitObjects.at(index).sliderType = 'B'; 762 | index--; 763 | continue; 764 | } 765 | 766 | // get the center of the circle 767 | float centerx = (s * ax + t * bx + u * cx) / sum; 768 | float centery = (s * ay + t * by + u * cy) / sum; 769 | 770 | float dAx = ax - centerx; 771 | float dAy = ay - centery; 772 | 773 | float dCx = cx - centerx; 774 | float dCy = cy - centery; 775 | 776 | // radius 777 | float r = sqrt(dAx * dAx + dAy * dAy); 778 | 779 | double thetaStart = atan2(dAy, dAx); 780 | double thetaEnd = atan2(dCy, dCx); 781 | 782 | while (thetaEnd < thetaStart) 783 | thetaEnd += 2 * PI; 784 | 785 | double dir = 1; 786 | double thetaRange = thetaEnd - thetaStart; 787 | 788 | float orthoAtoCxTemp = cx - ax; 789 | float orthoAtoCyTemp = cy - ay; 790 | float orthoAtoCx = orthoAtoCyTemp; 791 | float orthoAtoCy = -orthoAtoCxTemp; 792 | 793 | auto dot = orthoAtoCx * (bx - ax) + orthoAtoCy * (by - ay); 794 | if (dot < 0) 795 | { 796 | dir = -dir; 797 | thetaRange = 2 * 4 * atan(1) - thetaRange; 798 | } 799 | int amountPoints = 2 * r <= tolerance ? 2 : max(2, (int)ceil(thetaRange / (2 * acos(1 - tolerance / r)))); 800 | for (int i = 0; i < amountPoints; ++i) 801 | { 802 | double fract = (double)i / (amountPoints - 1); 803 | double thetaIncrement = dir * fract * thetaRange; 804 | double theta = thetaStart + thetaIncrement; 805 | // moving across the circle bit by bit 806 | float ox = cos(theta) * r; 807 | float oy = sin(theta) * r; 808 | FPointS p; 809 | p.x = centerx + ox; 810 | p.y = centery + oy; 811 | // straight away update the referenced object 812 | beatmap.HitObjects.at(index).pointsOnCurve.push_back(p); 813 | // calculate total arc length 814 | auto distance = abs(thetaIncrement * r); 815 | // if overshoot, stop 816 | if (distance >= hitObject.pixelLength) { 817 | break; 818 | } 819 | } 820 | beatmap.HitObjects.at(index).sliderPointsAreCalculated = true; 821 | } 822 | else if (hitObject.sliderType == 'L') { 823 | // calculation for hr 824 | if (mod == 16 || mod == 80) { 825 | beatmap.HitObjects.at(index).CurvePoints.front().front().y = 384 - beatmap.HitObjects.at(index).CurvePoints.front().front().y; 826 | beatmap.HitObjects.at(index).CurvePoints.front().back().y = 384 - beatmap.HitObjects.at(index).CurvePoints.front().back().y; 827 | } 828 | // store in variables as they're used multiple times in calculation 829 | CurvePointsS startPoint = beatmap.HitObjects.at(index).CurvePoints.front().front(); 830 | CurvePointsS endPoint = beatmap.HitObjects.at(index).CurvePoints.front().back(); 831 | // resolve overshooting 832 | auto distance = sqrt(pow(startPoint.y - endPoint.y, 2) + pow(startPoint.x - endPoint.x, 2)); 833 | // if ald overshoot, need to calculate the new endPoint using vector calculation 834 | if (distance > hitObject.pixelLength) { 835 | FPointS vec; 836 | // getting direction of currentPoint 837 | vec.x = endPoint.x - startPoint.x; 838 | vec.y = endPoint.y - startPoint.y; 839 | // calculate unit vector for moving 840 | auto unitVectorX = vec.x / (sqrt(vec.x * vec.x + vec.y * vec.y)); 841 | auto unitvectorY = vec.y / (sqrt(vec.x * vec.x + vec.y * vec.y)); 842 | CurvePointsS newEndPoint = CurvePointsS(startPoint.x + hitObject.pixelLength * unitVectorX, startPoint.y + hitObject.pixelLength * unitvectorY); 843 | // update hitObject 844 | beatmap.HitObjects.at(index).CurvePoints.front().back() = newEndPoint; 845 | } 846 | beatmap.HitObjects.at(index).sliderPointsAreCalculated = true; 847 | } 848 | else { 849 | // if not Perfect circle, calculate using bezier function 850 | // Getting points on bezier curve is easy but making them having same velocity is hard 851 | // the easiest way here is to get the passing points on curve, 852 | // and then move from each point a fixed distance and store the equidistant points into another array 853 | // refer to: https://love2d.org/forums/viewtopic.php?t=82612 854 | 855 | double distanceConst = 0.5; // define step size. large == inaccurate and vice versa 856 | double totalDistance = 0; // for resolving overshooting 857 | // If no idea abt what is going on here, google bezier curve calculation 858 | for (auto curvePointsV : hitObject.CurvePoints) { 859 | // change all y coordinates in CurvePoints if hardrock mod 860 | if (mod == 16 || mod == 80) { 861 | for (int i = 0; i < curvePointsV.size(); i++) { 862 | curvePointsV.at(i).y = 384 - curvePointsV.at(i).y; 863 | } 864 | } 865 | for (float t = 0; t <= 1; t += 0.01) { 866 | FPointS p = Functions::bezierCurve(curvePointsV, t); 867 | int sizeOfVector = beatmap.HitObjects.at(index).pointsOnCurve.size(); 868 | // if there are already more than 2 points calculated, it's time to calculate their distances 869 | if (sizeOfVector >= 1) { 870 | FPointS previousPoint = beatmap.HitObjects.at(index).pointsOnCurve.at(sizeOfVector - 1); 871 | auto distance = sqrt(pow(p.y - previousPoint.y, 2) + pow(p.x - previousPoint.x, 2)); 872 | // directly calculate equidistant points 873 | FPointS equalDistancePoint; 874 | // initialize equalDistancePoint first to last point's coordinate 875 | equalDistancePoint.x = previousPoint.x; 876 | equalDistancePoint.y = previousPoint.y; 877 | FPointS vec; 878 | // getting direction of currentPoint 879 | vec.x = p.x - previousPoint.x; 880 | vec.y = p.y - previousPoint.y; 881 | // calculate unit vector for moving 882 | auto unitVectorX = vec.x / (sqrt(vec.x * vec.x + vec.y * vec.y)); 883 | auto unitvectorY = vec.y / (sqrt(vec.x * vec.x + vec.y * vec.y)); 884 | // move for distanceConst in the direction to next PointOnCurve 885 | while (distance >= distanceConst) { 886 | // then keep on updating equalDistancePoint until condition is met 887 | equalDistancePoint.x = equalDistancePoint.x + distanceConst * unitVectorX; 888 | equalDistancePoint.y = equalDistancePoint.y + distanceConst * unitvectorY; 889 | // update pointsOnCurve 890 | beatmap.HitObjects.at(index).pointsOnCurve.push_back(equalDistancePoint); 891 | // move forward by distanceConst 892 | distance -= distanceConst; 893 | // everytime moving forward, totalDistance moved is also updated 894 | totalDistance += distanceConst; 895 | if (totalDistance >= hitObject.pixelLength) { 896 | break; 897 | } 898 | } 899 | // this line is disable as it is more accurate without considering every exact point on bezier curve 900 | //HitObjects.at(index).pointsOnCurve.push_back(p); 901 | // break out of calculation if exceeds pixelLength to avoid overshooting 902 | if (totalDistance >= hitObject.pixelLength) { 903 | break; 904 | } 905 | } 906 | else { // store 1st point into member var no matter what 907 | beatmap.HitObjects.at(index).pointsOnCurve.push_back(p); 908 | } 909 | } 910 | } 911 | beatmap.HitObjects.at(index).sliderPointsAreCalculated = true; 912 | } 913 | } 914 | 915 | // change all hitObjects y coordinate if it's HR 916 | if (mod == 16 || mod == 80) { 917 | beatmap.HitObjects.at(index).y = 384 - beatmap.HitObjects.at(index).y; 918 | } 919 | 920 | // modifications for stacked hitObjects 921 | // it is put here instead of at top so that the recalculation for "degenerated 'P' type slider" can happen 922 | // not same as last coordinate (ie. no stack or stacking has ended) 923 | if (previousStackedPoint.x != hitObject.x || previousStackedPoint.y != hitObject.y || hitObject.time - previousTime > stackTimeThreshold || hitObject.type == HitObject::TypeE::spinner) { 924 | // stacking occurs 925 | // 3 stacks is not gonna affect much but the bot might miss if >3, so only account for >3 926 | if (stackCount > 3) { 927 | HitObject baseHitObject = beatmap.HitObjects.at(index - 1); 928 | for (int i = 1; i < stackCount; i++) { 929 | // update the HitObjects 930 | beatmap.HitObjects.at(index - 1 - i).x = baseHitObject.x - i * stackOffset; 931 | beatmap.HitObjects.at(index - 1 - i).y = baseHitObject.y - i * stackOffset; 932 | } 933 | } 934 | // reset and initialize first object if it's first 935 | previousStackedPoint.x = hitObject.x; 936 | previousStackedPoint.y = hitObject.y; 937 | previousTime = hitObject.time; 938 | stackCount = 1; 939 | } 940 | // if stack 941 | else { 942 | // update then increment stackCount 943 | previousStackedPoint.x = hitObject.x; 944 | previousStackedPoint.y = hitObject.y; 945 | previousTime = hitObject.time; 946 | stackCount++; 947 | } 948 | } 949 | } 950 | 951 | void OsuBot::calcAndSetNewBeatmapAttributes(Beatmap &beatmap, unsigned int mod) { 952 | // info can be found here https://osu.ppy.sh/help/wiki/Beatmap_Editor/Song_Setup#difficulty 953 | // goes to HR first then if DT continues DT calculation 954 | // if HR 955 | if (mod == 16 || mod == 80) { 956 | beatmap.Difficulty.circleSize *= 1.3; 957 | if (beatmap.Difficulty.circleSize > 7) { 958 | beatmap.Difficulty.circleSize = 7; 959 | } 960 | beatmap.Difficulty.overallDifficulty *= 1.4; 961 | if (beatmap.Difficulty.overallDifficulty > 10) { 962 | beatmap.Difficulty.overallDifficulty = 10; 963 | } 964 | beatmap.Difficulty.approachRate *= 1.4; 965 | if (beatmap.Difficulty.approachRate > 10) { 966 | beatmap.Difficulty.approachRate = 10; 967 | } 968 | // update new approachWindow 969 | beatmap.approachWindow = Beatmap::calcApproachWindow(beatmap.Difficulty.approachRate); 970 | // calculate new time range 971 | beatmap.timeRange50 = abs(150 + 50 * (5 - beatmap.Difficulty.overallDifficulty) / 5); 972 | beatmap.timeRange100 = abs(100 + 40 * (5 - beatmap.Difficulty.overallDifficulty) / 5); 973 | beatmap.timeRange300 = abs(50 + 30 * (5 - beatmap.Difficulty.overallDifficulty) / 5); 974 | } 975 | // if DT or HR DT 976 | if (mod == 64 || mod == 80) { 977 | beatmap.timeRange50 *= 0.67; 978 | beatmap.timeRange100 *= 0.67; 979 | beatmap.timeRange300 *= 0.67; 980 | 981 | beatmap.approachWindow /= 1.5; 982 | } 983 | } 984 | 985 | // ---------------------------------------Interface-------------------------------------- 986 | void OsuBot::start() { 987 | while (true) { 988 | system("cls"); // clear the console screen 989 | int botChoice = 0; 990 | unsigned int modChoice = 0; 991 | // for display purpose 992 | string usingBot = "Auto"; 993 | string usingMod = "Nomod"; 994 | string input; 995 | cout << "1) Auto" << endl; 996 | cout << "2) Auto pilot" << endl; 997 | cout << "3) Relax" << endl; 998 | cout << "Please choose a bot: "; 999 | cin >> input; 1000 | // input validation 1001 | while (!(all_of(input.begin(), input.end(), isdigit)) || stoi(input) < 1 || stoi(input) > 3) { 1002 | cout << "Invalid input. Please enter again." << endl; 1003 | cout << "1) Auto" << endl; 1004 | cout << "2) Auto pilot" << endl; 1005 | cout << "3) Relax" << endl; 1006 | cout << "Please choose a bot: "; 1007 | cin >> input; 1008 | } 1009 | botChoice = stoi(input); 1010 | switch (botChoice) { 1011 | case 2: 1012 | usingBot = "Auto pilot"; 1013 | break; 1014 | case 3: 1015 | usingBot = "Relax"; 1016 | break; 1017 | } 1018 | system("cls"); 1019 | cout << "0) Go back" << endl; 1020 | cout << "1) No mod" << endl; 1021 | cout << "2) Hardrock" << endl; 1022 | cout << "3) Double time" << endl; 1023 | cout << "4) HR DT" << endl; 1024 | cout << "Please choose a mod: "; 1025 | cin >> input; 1026 | while (!(all_of(input.begin(), input.end(), isdigit)) || stoi(input) < 0 || stoi(input) > 4) { 1027 | cout << "Invalid input. Please enter again." << endl; 1028 | cout << "0) Go back" << endl; 1029 | cout << "1) No mod" << endl; 1030 | cout << "2) Hardrock" << endl; 1031 | cout << "3) Double time" << endl; 1032 | cout << "4) HR DT" << endl; 1033 | cout << "Please choose a mod: "; 1034 | cin >> input; 1035 | } 1036 | int tempModChoice = stoi(input); 1037 | // well, the mods are supposed to be determined using bitwise operator '|' 1038 | // but whatever 1039 | switch (tempModChoice) { 1040 | case 0: 1041 | continue; 1042 | break; 1043 | //case 1: 1044 | //modChoice = 0 1045 | //break; 1046 | case 2: 1047 | modChoice = 16; 1048 | usingMod = "HR"; 1049 | break; 1050 | case 3: 1051 | modChoice = 64; 1052 | usingMod = "DT"; 1053 | break; 1054 | case 4: 1055 | modChoice = 80; // 16 + 64 1056 | usingMod = "HR DT"; 1057 | break; 1058 | } 1059 | system("cls"); 1060 | cout << "(Press shift + c to configure the settings)" << endl; 1061 | cout << "You're using: " << usingBot << " (" << usingMod << ")" << endl; 1062 | if (usingMod != "Nomod") { 1063 | cout << "*Please turn on " << usingMod << " in game manually" << endl; 1064 | } 1065 | cout << endl; // new line 1066 | 1067 | cout << "Waiting for beatmap... (Press esc to return to menu)" << endl; 1068 | // more comprehensive checking var for unique title 1069 | string lastFullPathAfterSongFolder = ""; 1070 | // less accurate checking var for non-unique title 1071 | string lastTitle = ""; 1072 | vector lastPlayedBeatmap; 1073 | while (true) { 1074 | // Detect ESC key asynchronously to let user go back to menu to choose mod 1075 | if (GetAsyncKeyState(VK_ESCAPE) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { 1076 | cout << "Esc detected. Exiting." << endl; 1077 | Sleep(500); 1078 | break; 1079 | } 1080 | // shift + c 1081 | else if (GetAsyncKeyState(VK_SHIFT) & GetAsyncKeyState(0x43) & 0x8000 && (GetConsoleWindow() == GetForegroundWindow())) { 1082 | Config::clearAndChangeConfig(); 1083 | } 1084 | // constantly check title to see if any map is played 1085 | auto currentTitle = ProcessTools::getWindowTextString((this)->windowTitleHandle); 1086 | if (currentTitle != "osu!") { // if map is played 1087 | auto beatmapVec = (this)->osuDbMin.beatmapsMin.at(currentTitle); 1088 | if (beatmapVec.size() == 1) { // if the map played is unique in its title name 1089 | // formating for parsing to Beatmap class later 1090 | string fullPathAfterSongFolder = beatmapVec.at(0).folderName + "\\" + beatmapVec.at(0).nameOfOsuFile; 1091 | // run in a new thread checking if the map is still playing 1092 | thread checkIfIsPlaying(&OsuBot::updateIsPlaying, this); 1093 | cout << "Beatmap detected. Loading beatmap..." << endl; 1094 | // if new beatmap is detected 1095 | if (lastFullPathAfterSongFolder != fullPathAfterSongFolder) { 1096 | // set previous beatmap to this beatmap 1097 | lastFullPathAfterSongFolder = fullPathAfterSongFolder; 1098 | // set previous title to current title for non-unique maps 1099 | lastTitle = currentTitle; 1100 | // If this is not the 1st played beatmap (ie. vector not empty), pop_back so that last beatmap is discarded 1101 | if (lastPlayedBeatmap.size() != 0) { 1102 | lastPlayedBeatmap.pop_back(); 1103 | } 1104 | // then store the current beatmap into the vector 1105 | lastPlayedBeatmap.push_back(Beatmap(fullPathAfterSongFolder)); 1106 | } 1107 | Beatmap b = lastPlayedBeatmap.back(); 1108 | if (b.allSet) { 1109 | (this)->calcAndSetNewBeatmapAttributes(b, modChoice); 1110 | // start the bot 1111 | cout << "Starting: " << beatmapVec.at(0).nameOfOsuFile << endl; 1112 | (this)->startMod(b, botChoice, modChoice); 1113 | checkIfIsPlaying.join(); 1114 | cout << "Ending: " << beatmapVec.at(0).nameOfOsuFile << endl << endl; 1115 | } 1116 | else { 1117 | throw OsuBotException("Error loading beatmap: " + b.fullPathBeatmapFileName); 1118 | } 1119 | cout << "Waiting for beatmap... (Press esc to return to menu)" << endl; 1120 | } 1121 | else { // if multiple maps have same title 1122 | // check if it's retry. if it is, don't prompt for selection again 1123 | // note that however if two beatmaps with same title were played subsequently, the PREVIOUS map (wrong map) would be played instead 1124 | // if wrong choice is made same thing would happen also. In that case, return to menu to reset the program 1125 | if (lastTitle != currentTitle || lastTitle == "") { 1126 | // bring the console to foreground for user to pick the correct map 1127 | HWND consoleHandle = GetConsoleWindow(); 1128 | if (SetForegroundWindow(consoleHandle)) { 1129 | cout << "Multiple files are detected. Please pick the correct map: " << endl; 1130 | for (int index = 0; index < beatmapVec.size(); index++) { 1131 | cout << index + 1 << ") " << beatmapVec.at(index).nameOfOsuFile << endl; 1132 | } 1133 | cout << "Choice: "; 1134 | cin >> input; 1135 | while (!(all_of(input.begin(), input.end(), isdigit)) || (stoi(input) < 1 || stoi(input) > beatmapVec.size())) { 1136 | cout << "Invalid input. Please enter again." << endl; 1137 | for (int index = 0; index < beatmapVec.size(); index++) { 1138 | cout << index + 1 << ") " << beatmapVec.at(index).nameOfOsuFile << endl; 1139 | } 1140 | cout << "Choice: "; 1141 | cin >> input; 1142 | } 1143 | auto chosenMap = beatmapVec.at(stoi(input) - 1); 1144 | string fullPathAfterSongFolder = chosenMap.folderName + "\\" + chosenMap.nameOfOsuFile; 1145 | // set previous beatmap to this beatmap for unique map references 1146 | lastFullPathAfterSongFolder = fullPathAfterSongFolder; 1147 | // update last title 1148 | lastTitle = currentTitle; 1149 | // If this is not the 1st played beatmap (ie. vector not empty), pop_back so that last beatmap is discarded 1150 | if (lastPlayedBeatmap.size() != 0) { 1151 | lastPlayedBeatmap.pop_back(); 1152 | } 1153 | // then store the current beatmap into the vector 1154 | lastPlayedBeatmap.push_back(Beatmap(fullPathAfterSongFolder)); 1155 | } 1156 | else { 1157 | cout << "Failed to bring console to foreground." << endl; 1158 | continue; 1159 | } 1160 | } 1161 | thread checkIfIsPlaying(&OsuBot::updateIsPlaying, this); 1162 | cout << "Loading beatmap..." << endl; 1163 | Beatmap b = lastPlayedBeatmap.back(); 1164 | if (b.allSet) { 1165 | (this)->calcAndSetNewBeatmapAttributes(b, modChoice); 1166 | cout << "Starting: " << Functions::split(b.fullPathBeatmapFileName, '\\').back() << endl; 1167 | (this)->startMod(b, botChoice, modChoice); 1168 | checkIfIsPlaying.join(); 1169 | cout << "Ending: " << Functions::split(b.fullPathBeatmapFileName, '\\').back() << endl << endl; 1170 | } 1171 | else { 1172 | throw OsuBotException("Error loading beatmap: " + b.fullPathBeatmapFileName); 1173 | } 1174 | cout << "Waiting for beatmap... (Press esc to return to menu)" << endl; 1175 | } 1176 | } 1177 | Sleep(100); // Check for title every 0.1 sec 1178 | } 1179 | } 1180 | } --------------------------------------------------------------------------------