├── .gitattributes ├── .gitignore ├── Osu!Bot V3.sln ├── Osu!Bot V3 ├── Common │ ├── App.cpp │ ├── Config.ini │ ├── ConfigurationIni.h │ ├── DeviceResources.cpp │ ├── DeviceResources.h │ ├── Pch.cpp │ ├── Pch.h │ ├── Size.h │ ├── SplitString.h │ ├── StepTimer.h │ ├── Targetver.h │ └── Vec2f.h ├── Content │ ├── AppMain.cpp │ ├── AppMain.h │ ├── OsuBot.cpp │ ├── OsuBot.h │ ├── OsuBot │ │ ├── Beatmap.cpp │ │ ├── Beatmap.h │ │ ├── MovementModes.cpp │ │ ├── MovementModes.h │ │ ├── SigScan.cpp │ │ ├── SigScan.h │ │ └── SongsSelection.cpp │ ├── Resources │ │ ├── Font │ │ │ ├── OFL.txt │ │ │ ├── SourceCodePro-Black.ttf │ │ │ ├── SourceCodePro-Bold.ttf │ │ │ ├── SourceCodePro-ExtraLight.ttf │ │ │ ├── SourceCodePro-Light.ttf │ │ │ ├── SourceCodePro-Medium.ttf │ │ │ ├── SourceCodePro-Regular.ttf │ │ │ └── SourceCodePro-Semibold.ttf │ │ ├── Icons │ │ │ ├── Icon.ico │ │ │ └── IconSm.ico │ │ ├── Resource.h │ │ └── Resource.rc │ └── UI Elements │ │ ├── StaticText.cpp │ │ └── StaticText.h ├── Osu!Bot V3.vcxproj └── Osu!Bot V3.vcxproj.filters ├── Output └── Osu!Bot V3 │ ├── Config.ini │ ├── Osu!Bot V3_x64.exe │ └── Osu!Bot V3_x86.exe └── README.md /.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 | -------------------------------------------------------------------------------- /.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 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 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 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Osu!Bot V3.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Osu!Bot V3", "Osu!Bot V3\Osu!Bot V3.vcxproj", "{9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Debug|Win32.Build.0 = Debug|Win32 18 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Debug|x64.ActiveCfg = Debug|x64 19 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Debug|x64.Build.0 = Debug|x64 20 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Release|Win32.ActiveCfg = Release|Win32 21 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Release|Win32.Build.0 = Release|Win32 22 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Release|x64.ActiveCfg = Release|x64 23 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3D8D7AF1-789D-433C-A24A-DF2D5333A90C} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Osu!Bot V3/Common/App.cpp: -------------------------------------------------------------------------------- 1 | // App.cpp : Defines the entry point for the application 2 | // and holds the message processor function. 3 | 4 | #include 5 | 6 | #include 7 | 8 | 9 | // Forward declarations. 10 | int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int); 11 | LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM); 12 | 13 | // Global variables. 14 | std::shared_ptr g_main; 15 | std::shared_ptr g_deviceResources; 16 | bool g_tryOnce = TRUE; 17 | 18 | 19 | // Main entry point of the app. 20 | int WINAPI wWinMain( 21 | _In_ HINSTANCE hInstance, 22 | _In_opt_ HINSTANCE hPrevInstance, 23 | _In_ LPWSTR lpCmdLine, 24 | _In_ int nCmdShow 25 | ) { 26 | UNREFERENCED_PARAMETER(hPrevInstance); 27 | UNREFERENCED_PARAMETER(lpCmdLine); 28 | 29 | // Use HeapSetInformation to specify that the process should 30 | // terminate if the heap manager detects an error in any heap used 31 | // by the process. 32 | // The return value is ignored, because we want to continue running in the 33 | // unlikely event that HeapSetInformation fails. 34 | //HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); 35 | 36 | 37 | // TODO: Place pre-init startup code here. 38 | 39 | // Intitalize and store class instances. 40 | g_deviceResources = std::make_shared(); 41 | g_main = std::make_shared(g_deviceResources); 42 | 43 | // Initialize the app strings. 44 | LoadStringW(hInstance, IDS_TITLE, g_main->m_title, MAX_LOADSTRING); 45 | LoadStringW(hInstance, IDC_OSUBOT_V3, g_main->m_windowClass, MAX_LOADSTRING); 46 | 47 | // Register the app class. 48 | g_main->MyRegisterClass(hInstance, WindowProc); 49 | 50 | // Initialize the app instance. 51 | if (g_main->GetWorkingRect()) { 52 | if (g_main->InitInstance(hInstance, nCmdShow)) { 53 | // Initialize the app contents. 54 | g_main->InitContent(); 55 | } 56 | else { 57 | // Cleanup and throw error code. 58 | 59 | 60 | // Stop the application. 61 | return FALSE; 62 | } 63 | } 64 | 65 | // Register the app hotkeys. 66 | RegisterHotKey(g_main->m_hWnd, 1, MOD_NOREPEAT, VK_HOME); // Toggle HUD. 67 | RegisterHotKey(g_main->m_hWnd, 2, MOD_NOREPEAT, VK_TAB); // Toggle fps counter. 68 | RegisterHotKey(g_main->m_hWnd, 3, MOD_NOREPEAT, VK_INSERT); // Open file dialog to queue a beatmap. 69 | 70 | 71 | // Initialize threads. 72 | std::thread bot([&]() { 73 | while (!g_main->m_quit) { 74 | g_main->m_osuBot->CheckGameActive(&g_main); 75 | 76 | if (g_main->m_osuBot->m_targetHwnd) { 77 | g_main->m_osuBot->AutoPlay(); 78 | } 79 | } 80 | }); 81 | std::thread app([&]() { 82 | while (!g_main->m_quit) { 83 | g_main->Update(); 84 | g_main->Draw(); 85 | } 86 | }); 87 | 88 | 89 | // Main message loop. 90 | MSG msg = { 0 }; 91 | while (true) { 92 | // Look for messages in the queue. 93 | if (PeekMessageW(&msg, g_main->m_hWnd, 0U, 0U, PM_REMOVE)) { 94 | // Handle the message. 95 | TranslateMessage(&msg); 96 | DispatchMessageW(&msg); 97 | } 98 | // Stop loop when flagged. 99 | else if (g_main->m_quit) { 100 | bot.join(); 101 | app.join(); 102 | break; 103 | } 104 | } 105 | 106 | g_deviceResources.reset(); 107 | g_main.reset(); 108 | 109 | // Return with last code. 110 | return (INT)msg.wParam; 111 | } 112 | 113 | // Main app window message processor. 114 | LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { 115 | switch (message) { 116 | case WM_DISPLAYCHANGE: 117 | { 118 | // Update the working rect. 119 | g_main->GetWorkingRect(); 120 | } 121 | case WM_HOTKEY: 122 | { 123 | // Toggle HUD. 124 | if (LOWORD(lParam) == NULL && HIWORD(lParam) == VK_HOME) { 125 | if (g_main->m_hudVisible) { 126 | // Dissable HUD intractability. 127 | SetWindowLongW(g_main->m_hWnd, GWL_EXSTYLE, WS_SYSMENU | WS_EX_TRANSPARENT); 128 | 129 | // Set flag to not draw the HUD. 130 | g_main->m_hudVisible = FALSE; 131 | 132 | 133 | // Clear the HUD from the frame. 134 | g_deviceResources->GetD2DRenderTarget()->BeginDraw(); 135 | g_deviceResources->GetD2DRenderTarget()->Clear(D2D1::ColorF(D2D1::ColorF::Black)); 136 | HRESULT hr = g_deviceResources->GetD2DRenderTarget()->EndDraw(); 137 | 138 | if (hr == D2DERR_RECREATE_TARGET) { 139 | // If the render target failed to draw for any reason and a new target must be created. 140 | g_deviceResources->HandleDeviceLost(); 141 | } 142 | else { 143 | ThrowIfFailed(hr); 144 | } 145 | } 146 | else { 147 | // Enable HUD intractability. 148 | SetWindowLongW(g_main->m_hWnd, GWL_EXSTYLE, WS_SYSMENU); 149 | 150 | // Set flag to draw the HUD. 151 | g_main->m_hudVisible = TRUE; 152 | } 153 | 154 | // Set the window with the new parameters. 155 | SetWindowPos( 156 | g_main->m_hWnd, 157 | HWND_TOPMOST, 158 | g_main->m_rect.left, g_main->m_rect.top, 159 | (INT)roundf(g_main->m_windowSize.Width), (INT)roundf(g_main->m_windowSize.Height), 160 | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS 161 | ); 162 | } 163 | 164 | // Toggle fps counter. 165 | if (LOWORD(lParam) == NULL && HIWORD(lParam) == VK_TAB) { 166 | if (g_main->m_debugInfoVisible) { 167 | // Set flag to not show fps counter. 168 | g_main->m_debugInfoVisible = FALSE; 169 | } 170 | else { 171 | // Set flag to show fps counter. 172 | g_main->m_debugInfoVisible = TRUE; 173 | } 174 | } 175 | 176 | // Open file dialog to queue a beatmap. 177 | if (LOWORD(lParam) == NULL && HIWORD(lParam) == VK_INSERT) { 178 | // Get the path to a beatmap file. 179 | std::wstring path = g_main->m_osuBot->GetSongFromFolderPath(); 180 | g_main->m_timer.ResetElapsedTime(); 181 | g_main->m_osuBot->m_logicTimer.ResetElapsedTime(); 182 | 183 | g_main->m_osuBot->AddBeatmapToQueue(path); 184 | } 185 | break; 186 | } 187 | case WM_DESTROY: 188 | { 189 | // Post quit message to message queue. 190 | PostQuitMessage(0); 191 | 192 | // Set flag to quit the app. 193 | g_main->m_quit = TRUE; 194 | break; 195 | } 196 | default: 197 | return DefWindowProcW(hWnd, message, wParam, lParam); 198 | } 199 | 200 | return FALSE; 201 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Common/Config.ini: -------------------------------------------------------------------------------- 1 | ################################################## 2 | # 3 | # Min - Max values for settings. 4 | # 5 | ################################################## 6 | # 7 | # [CONFIGURATION] 8 | # TARGET_FPS 1 - X 9 | # TRANSPARENCY_ALPHA 0 - 255 10 | # TRANSPARENCY_COLOR 0 - 16777215 11 | # 12 | ################################################## 13 | 14 | 15 | [TIME] 16 | TIME_SIGNATURE=DB\5D\E8\8B\45\E8\A3\00\00\00\00\8B\35\00\00\00\00\85\F6 17 | SONG_OFFSET=16.0 18 | 19 | [CONFIGURATION] 20 | LOGIC_UPS=500 21 | WINDOW_FPS=60 22 | TRANSPARENCY_ALPHA=220 23 | TRANSPARENCY_COLOR=0 -------------------------------------------------------------------------------- /Osu!Bot V3/Common/ConfigurationIni.h: -------------------------------------------------------------------------------- 1 | // ConfigurationIni.h : Defines a class responsible for read and writing 2 | // from and to a configuration ini file. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | 9 | namespace ConfigurationIni 10 | { 11 | class ConfigIni { 12 | public: 13 | // Constructor and destructor. 14 | ConfigIni() : 15 | m_configFilePath(L"Config.ini") 16 | { 17 | // Open file for reading. 18 | configFile = std::unique_ptr(_wfsopen(m_configFilePath, L"r", _SH_DENYNO)); 19 | } 20 | ~ConfigIni() { 21 | fclose(configFile.get()); 22 | configFile.reset(); 23 | } 24 | 25 | 26 | public: 27 | // Configuration Enumuration and array of strings. 28 | enum configSections { 29 | time, 30 | configuration, 31 | count 32 | }; 33 | 34 | private: 35 | const wchar_t* configStrings[configSections::count] = { 36 | L"[TIME]", 37 | L"[CONFIGURATION]" 38 | }; 39 | 40 | 41 | // Configuration ini functions. 42 | public: 43 | template 44 | BOOL _stdcall ReadFromConfigFile( 45 | _In_ configSections configSection, 46 | _In_ LPCWSTR lpConfigKey, 47 | _In_ _T* lpResultValue, 48 | _In_opt_ int maxLenght = MAX_READSTRING, 49 | _In_opt_ _T lpDefaultValue = NULL 50 | ) { 51 | std::wstring readLine; 52 | LPWSTR lpReadLine = new wchar_t[maxLenght]; 53 | 54 | // Get configuration string from the array with the enum value. 55 | LPCWSTR lpConfigSection = configStrings[configSection]; 56 | 57 | // Read the file. 58 | if (configFile.get() != nullptr) { 59 | // Set the read position to the begin of the file. 60 | fpos_t pos = 0; 61 | fsetpos(configFile.get(), &pos); 62 | 63 | // Find the configuration header in the file. 64 | while (TRUE) { 65 | if (feof(configFile.get())) { 66 | // End of file, return FALSE with the default value as result. 67 | *lpResultValue = lpDefaultValue; 68 | 69 | delete lpReadLine; 70 | return FALSE; 71 | } 72 | else if (readLine.find(lpConfigSection) == std::wstring::npos) { 73 | // Header not found, read next line. 74 | fgetws(lpReadLine, maxLenght, configFile.get()); 75 | readLine.assign(lpReadLine); 76 | } 77 | else { 78 | // Header found, read the value from the key. 79 | if (ReadConfigKeyValue(lpConfigKey, lpResultValue, maxLenght)) { 80 | // return TRUE when succeeded with the value as result. 81 | delete lpReadLine; 82 | return TRUE; 83 | } 84 | } 85 | } 86 | } 87 | 88 | // File invalid, return FALSE with the default value as result. 89 | *lpResultValue = lpDefaultValue; 90 | delete lpReadLine; 91 | return FALSE; 92 | } 93 | 94 | private: 95 | template 96 | BOOL _stdcall ReadConfigKeyValue( 97 | _In_ LPCWSTR lpConfigKey, 98 | _In_ _T lpResultValue, 99 | _In_ int maxLenght 100 | ) { 101 | std::wstring readLine; 102 | LPWSTR lpReadLine = new wchar_t[maxLenght]; 103 | const std::wstring numbers = L"0123456789"; 104 | 105 | // Find the configuration key in the file. 106 | while (TRUE) { 107 | if (feof(configFile.get())) { 108 | // End of file, return FALSE. 109 | delete lpReadLine; 110 | return FALSE; 111 | } 112 | else if (readLine.find(lpConfigKey) == std::wstring::npos) { 113 | // Key not found, read the next line. 114 | fgetws(lpReadLine, maxLenght, configFile.get()); 115 | readLine.assign(lpReadLine); 116 | } 117 | else if (readLine.find(m_comment) != std::wstring::npos) { 118 | // Comment found, read the next line. 119 | fgetws(lpReadLine, maxLenght, configFile.get()); 120 | readLine.assign(lpReadLine); 121 | } 122 | else { 123 | // Key is found. 124 | if (readLine.substr(0U, readLine.find(m_delimiter)) == lpConfigKey) { 125 | // Key is exactly the same, get the value. 126 | UINT valuePos = (UINT)readLine.find_first_of(m_delimiter) + 1U; 127 | 128 | if (valuePos == (UINT)readLine.size()) { 129 | // Pos is equal to the string size, return FALSE with no value. 130 | delete lpReadLine; 131 | return FALSE; 132 | } 133 | 134 | 135 | if (readLine.at(valuePos) == L'-') { 136 | // Value is negative. 137 | valuePos += 1U; 138 | } 139 | 140 | // Set the value. 141 | if (numbers.find(readLine.at(valuePos)) != std::wstring::npos) { 142 | // The character at valuePos is a number. 143 | if (readLine.find(L'.') != std::wstring::npos) { 144 | // Value is has a decimal. 145 | *reinterpret_cast(lpResultValue) = std::stod(readLine.substr(valuePos)); 146 | } 147 | else { 148 | // Value is of type UINT. 149 | *reinterpret_cast(lpResultValue) = std::stoi(readLine.substr(valuePos)); 150 | } 151 | } 152 | else { 153 | // Return the wstring. 154 | *reinterpret_cast(lpResultValue) = readLine.substr(valuePos).data(); 155 | } 156 | 157 | 158 | // Value set, return TRUE. 159 | delete lpReadLine; 160 | return TRUE; 161 | } 162 | } 163 | } 164 | } 165 | 166 | 167 | private: 168 | // Configuration ini variables. 169 | std::unique_ptr configFile; 170 | static const wchar_t m_delimiter = L'='; 171 | static const wchar_t m_comment = L'#'; 172 | 173 | public: 174 | LPCWSTR m_configFilePath; 175 | }; 176 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Common/DeviceResources.cpp: -------------------------------------------------------------------------------- 1 | // DeviceResources.cpp : Holds the definitions for all the 2 | // DirectX device resources. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | using namespace DX; 10 | 11 | 12 | namespace DisplayMetrics 13 | { 14 | // High resolution displays can require a lot of GPU and battery power to render. 15 | // High resolution phones, for example, may suffer from poor battery life if 16 | // games attempt to render at 60 frames per second at full fidelity. 17 | // The decision to render at full fidelity across all platforms and form factors 18 | // should be deliberate. 19 | static const bool SupportHighResolutions = false; 20 | 21 | // The default thresholds that define a "high resolution" display. If the thresholds 22 | // are exceeded and SupportHighResolutions is false, the dimensions will be scaled 23 | // by 50%. 24 | static const float DpiThreshold = 192.0f; // 200% of standard desktop display. 25 | static const float WidthThreshold = 1920.0f; // 1080p width. 26 | static const float HeightThreshold = 1080.0f; // 1080p height. 27 | }; 28 | 29 | // Constructor for DeviceResources. 30 | DeviceResources::DeviceResources() : 31 | m_logicalSize(), 32 | m_outputSize(), 33 | m_dpi(96.f), 34 | m_effectiveDpi(-1.f), 35 | m_deviceNotify(nullptr) 36 | { 37 | CreateDeviceIndependentResources(); 38 | } 39 | 40 | // Configures independent resources. 41 | void DeviceResources::CreateDeviceIndependentResources() { 42 | // Initiallize Direct2D resources. 43 | D2D1_FACTORY_OPTIONS options; 44 | ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS)); 45 | 46 | #if defined(_DEBUG) 47 | // If the projects is in a debug build, enable Direct2D debugging via SKD layers. 48 | options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; 49 | #endif 50 | 51 | // Initialize the Direct2D Factory. 52 | ThrowIfFailed( 53 | D2D1CreateFactory( 54 | D2D1_FACTORY_TYPE_SINGLE_THREADED, 55 | __uuidof(ID2D1Factory3), 56 | &options, 57 | &m_d2dFactory 58 | ) 59 | ); 60 | 61 | // Initialize the DirectWrite Factory. 62 | ThrowIfFailed( 63 | DWriteCreateFactory( 64 | DWRITE_FACTORY_TYPE_SHARED, 65 | __uuidof(IDWriteFactory3), 66 | &m_dwriteFactory 67 | ) 68 | ); 69 | } 70 | 71 | // These resources need to be recreated every time we resize the window. 72 | void DeviceResources::CreateWindowSizeDependentResources() { 73 | UpdateRenderTargetSize(); 74 | 75 | // The width and height of the swap chain must be based on the window's 76 | // natively-oriented width and height. If the window is not in the native 77 | // orientation, the dimensions must be reversed. 78 | DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation(); 79 | 80 | bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270; 81 | m_d2dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width; 82 | m_d2dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height; 83 | 84 | 85 | // Create the render target properties. 86 | D2D1_RENDER_TARGET_PROPERTIES rtp; 87 | ZeroMemory(&rtp, sizeof(rtp)); 88 | rtp.type = D2D1_RENDER_TARGET_TYPE_HARDWARE; 89 | rtp.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE); 90 | rtp.dpiX = m_effectiveDpi; 91 | rtp.dpiY = m_effectiveDpi; 92 | rtp.usage = D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE; 93 | rtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; 94 | 95 | // Create the hwnd render target properties. 96 | D2D1_HWND_RENDER_TARGET_PROPERTIES hrtp; 97 | ZeroMemory(&hrtp, sizeof(hrtp)); 98 | hrtp.hwnd = reinterpret_cast(m_main)->m_hWnd; 99 | hrtp.presentOptions = D2D1_PRESENT_OPTIONS_IMMEDIATELY; 100 | hrtp.pixelSize = D2D1::SizeU( 101 | (UINT32)roundf(reinterpret_cast(m_main)->m_windowSize.Width), 102 | (UINT32)roundf(reinterpret_cast(m_main)->m_windowSize.Height) 103 | ); 104 | 105 | // Create a new render target. 106 | HRESULT hr = GetD2DFactory()->CreateHwndRenderTarget( 107 | &rtp, 108 | &hrtp, 109 | &m_d2dRenderTarget 110 | ); 111 | 112 | if (hr == D2DERR_RECREATE_TARGET) { 113 | // If the render target failed to create for any reason, a new target must be created. 114 | HandleDeviceLost(); 115 | 116 | // Everything is setup now. Do NOT continue execution of this function. 117 | // HandleDeviceLost has re-entered the function and correctly setup the new device. 118 | return; 119 | } 120 | else { 121 | ThrowIfFailed(hr); 122 | } 123 | 124 | 125 | // Set the proper orientation for the transforms. 126 | switch (displayRotation) { 127 | case DXGI_MODE_ROTATION_IDENTITY: 128 | m_orientationTransform2D = D2D1::Matrix3x2F::Identity(); 129 | break; 130 | 131 | case DXGI_MODE_ROTATION_ROTATE90: 132 | m_orientationTransform2D = D2D1::Matrix3x2F::Rotation(90.f) * D2D1::Matrix3x2F::Translation(m_logicalSize.Height, 0.f); 133 | break; 134 | 135 | case DXGI_MODE_ROTATION_ROTATE180: 136 | m_orientationTransform2D = D2D1::Matrix3x2F::Rotation(180.f) * D2D1::Matrix3x2F::Translation(m_logicalSize.Width, m_logicalSize.Height); 137 | break; 138 | 139 | case DXGI_MODE_ROTATION_ROTATE270: 140 | m_orientationTransform2D = D2D1::Matrix3x2F::Rotation(270.f) * D2D1::Matrix3x2F::Translation(0.f, m_logicalSize.Width); 141 | break; 142 | 143 | default: 144 | m_orientationTransform2D = D2D1::Matrix3x2F::Identity(); 145 | break; 146 | } 147 | } 148 | 149 | // Determine the dimensions of the render target and whether it will be scaled down. 150 | void DeviceResources::UpdateRenderTargetSize() { 151 | m_effectiveDpi = m_dpi; 152 | 153 | // To improve battery life on high resolution devices, render to a smaller render target 154 | // and allow the GPU to scale the output when it is presented. 155 | if (!DisplayMetrics::SupportHighResolutions && m_dpi > DisplayMetrics::DpiThreshold) { 156 | float width = ConvertDipsToPixels((FLOAT)m_logicalSize.Width, m_dpi); 157 | float height = ConvertDipsToPixels((FLOAT)m_logicalSize.Height, m_dpi); 158 | 159 | // When the device is in portrait orientation, height > width. Compare the 160 | // larger dimension against the width threshold and the smaller dimension 161 | // against the height threshold. 162 | if (max(width, height) > DisplayMetrics::WidthThreshold && min(width, height) > DisplayMetrics::HeightThreshold) { 163 | // To scale the app we change the effective DPI. Logical size does not change. 164 | m_effectiveDpi /= 2.0f; 165 | } 166 | } 167 | 168 | // Calculate the necessary render target size in pixels. 169 | m_outputSize.Width = (UINT)roundf(DX::ConvertDipsToPixels(m_logicalSize.Width, m_effectiveDpi)); 170 | m_outputSize.Height = (UINT)roundf(DX::ConvertDipsToPixels(m_logicalSize.Height, m_effectiveDpi)); 171 | 172 | // Prevent zero size DirectX content from being created. 173 | m_outputSize.Width = max(m_outputSize.Width, 1U); 174 | m_outputSize.Height = max(m_outputSize.Height, 1U); 175 | } 176 | 177 | // This function should be called when the main window is recreated (or resized). 178 | void DeviceResources::SetMainWindow(_In_opt_ LPVOID main) { 179 | if (main != nullptr) { 180 | m_main = main; 181 | } 182 | 183 | m_logicalSize = reinterpret_cast(m_main)->m_windowSize; 184 | m_dpi = (FLOAT)GetDpiForWindow(reinterpret_cast(m_main)->m_hWnd); 185 | 186 | CreateWindowSizeDependentResources(); 187 | } 188 | 189 | // Recreate all device resources and set them back to the current state. 190 | void DeviceResources::HandleDeviceLost() { 191 | if (m_deviceNotify != nullptr) { 192 | m_deviceNotify->OnDeviceLost(); 193 | } 194 | 195 | CreateWindowSizeDependentResources(); 196 | 197 | if (m_deviceNotify != nullptr) { 198 | m_deviceNotify->OnDeviceRestored(); 199 | } 200 | } 201 | 202 | // Register our DeviceNotify to be informed on device lost and creation. 203 | void DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) { 204 | m_deviceNotify = deviceNotify; 205 | } 206 | 207 | // This function determines the rotation between the display device's native orientation and the 208 | // current display orientation. 209 | DXGI_MODE_ROTATION DX::DeviceResources::ComputeDisplayRotation() { 210 | DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED; 211 | 212 | // Note: NativeOrientation can only be Landscape or Portrait even though 213 | // the DisplayOrientations enum has other values. 214 | switch (m_currentOrientation) { 215 | case DMDO_DEFAULT: 216 | rotation = DXGI_MODE_ROTATION_IDENTITY; 217 | break; 218 | 219 | case DMDO_90: 220 | rotation = DXGI_MODE_ROTATION_ROTATE90; 221 | break; 222 | 223 | case DMDO_180: 224 | rotation = DXGI_MODE_ROTATION_ROTATE180; 225 | break; 226 | 227 | case DMDO_270: 228 | rotation = DXGI_MODE_ROTATION_ROTATE270; 229 | break; 230 | } 231 | return rotation; 232 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Common/DeviceResources.h: -------------------------------------------------------------------------------- 1 | // DeviceResources.h : Declares everything that is needed 2 | // for DirectX device resources. 3 | 4 | #pragma once 5 | 6 | 7 | namespace DX 8 | { 9 | // Converts a length in device-independent pixels (DIPs) to a length in physical pixels. 10 | inline float ConvertDipsToPixels(float dips, float dpi) { 11 | static const float dipsPerInch = 96.0f; 12 | return roundf(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer. 13 | } 14 | 15 | 16 | // Provides an interface for an application that owns DeviceResources to be notified of the device being lost or created. 17 | __interface IDeviceNotify { 18 | virtual void OnDeviceLost() = 0; 19 | virtual void OnDeviceRestored() = 0; 20 | }; 21 | 22 | 23 | // Controls all DirectX device resources. 24 | class DeviceResources { 25 | public: 26 | DeviceResources(); 27 | void SetMainWindow(_In_opt_ LPVOID main = nullptr); 28 | void HandleDeviceLost(); 29 | void RegisterDeviceNotify(IDeviceNotify* deviceNotify); 30 | 31 | // The size of the render target, in pixels. 32 | DX::Size GetOutputSize() const { return m_outputSize; } 33 | 34 | // The size of the render target, in dips. 35 | DX::Size GetLogicalSize() const { return m_logicalSize; } 36 | float GetDpi() const { return m_effectiveDpi; } 37 | 38 | // D2D Accessors. 39 | ID2D1Factory3* GetD2DFactory() const { return m_d2dFactory.Get(); } 40 | ID2D1HwndRenderTarget* GetD2DRenderTarget() const { return m_d2dRenderTarget.Get(); } 41 | IDWriteFactory2* GetDWriteFactory() const { return m_dwriteFactory.Get(); } 42 | D2D1::Matrix3x2F GetOrientationTransform2D() const { return m_orientationTransform2D; } 43 | 44 | 45 | private: 46 | // Functions. 47 | void CreateDeviceIndependentResources(); 48 | void CreateWindowSizeDependentResources(); 49 | void UpdateRenderTargetSize(); 50 | DXGI_MODE_ROTATION ComputeDisplayRotation(); 51 | 52 | // Direct2D drawing components. 53 | Microsoft::WRL::ComPtr m_d2dFactory; 54 | Microsoft::WRL::ComPtr m_d2dRenderTarget; 55 | Microsoft::WRL::ComPtr m_dwriteFactory; 56 | 57 | // Cached device properties. 58 | DX::Size m_d2dRenderTargetSize; 59 | DX::Size m_outputSize; 60 | DX::Size m_logicalSize; 61 | float m_dpi; 62 | float m_effectiveDpi; 63 | 64 | // Transforms used for display. 65 | D2D1::Matrix3x2F m_orientationTransform2D; 66 | 67 | // The IDeviceNotify can be held directly as it owns the DeviceResources. 68 | IDeviceNotify* m_deviceNotify; 69 | 70 | public: 71 | // Flag that indicates drawing started. 72 | bool m_drawing; 73 | bool m_resizeing; 74 | 75 | // Display orientation; 76 | DWORD m_currentOrientation; 77 | 78 | // Pointer to main app. 79 | LPVOID m_main; 80 | }; 81 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Common/Pch.cpp: -------------------------------------------------------------------------------- 1 | // Pch.cpp : Source file that includes just the standard includes 2 | // $safeprojectname$.pch will be the pre-compiled header 3 | // Pch.obj will contain the pre-compiled type information 4 | 5 | #include -------------------------------------------------------------------------------- /Osu!Bot V3/Common/Pch.h: -------------------------------------------------------------------------------- 1 | // Pch.h : include file for standard system include files, 2 | // or project specific include files that are use frequently, 3 | // but are changed infrequently. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | // Exclude rarely-used stuff from windows headers. 10 | #define WIN32_LEAN_AND_MEAN 11 | // Windows header files: 12 | #include 13 | #include 14 | #include 15 | 16 | // C RunTime header files: 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | // TODO: Reference additional header files: 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | // Usefull functions for all files to include: 37 | inline void ThrowIfFailed(HRESULT hr) { 38 | if (FAILED(hr)) { 39 | throw hr; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Osu!Bot V3/Common/Size.h: -------------------------------------------------------------------------------- 1 | // Size.h : Holds the definition of a template class Size 2 | // that can hold the Width and Height of a object. 3 | // Good for storing a 2D rectangular area. 4 | 5 | #pragma once 6 | 7 | namespace DX 8 | { 9 | // A template of a class Size to hold a Width and Height of a rectangular 2D area. 10 | template class Size { 11 | public: 12 | _T Width; 13 | _T Height; 14 | 15 | Size() : Width(0), Height(0) {}; 16 | Size(_T Width, _T Height) : Width(Width), Height(Height) {}; 17 | }; 18 | 19 | // Operator overloads. 20 | template inline bool operator== (const Size<_T>& lhs, const Size<_T>& rhs) { 21 | return (lhs.Width == rhs.Width && lhs.Height == rhs.Height); 22 | } 23 | 24 | template inline bool operator!= (const Size<_T>& lhs, const Size<_T>& rhs) { 25 | return !(lhs == rhs); 26 | } 27 | 28 | template inline Size<_T> operator+ (const Size<_T>& lhs, const Size<_T>& rhs) { 29 | return Size<_T>(lhs.Width + rhs.Width, lhs.Height + rhs.Height); 30 | } 31 | 32 | template inline Size<_T> operator- (const Size<_T>& lhs, const Size<_T>& rhs) { 33 | return Size<_T>(lhs.Width - rhs.Width, lhs.Height - rhs.Height); 34 | } 35 | 36 | template inline Size<_T> operator* (const Size<_T>& lhs, const Size<_T>& rhs) { 37 | return Size<_T>(lhs.Width * rhs.Width, lhs.Height * rhs.Height); 38 | } 39 | 40 | template inline Size<_T> operator* (const Size<_T>& lhs, const _T& rhs) { 41 | return Size<_T>(lhs.Width * rhs, lhs.Height * rhs); 42 | } 43 | 44 | template inline Size<_T> operator/ (const Size<_T>& lhs, const Size<_T>& rhs) { 45 | return Size<_T>(lhs.Width / rhs.Width, lhs.Height / rhs.Height); 46 | } 47 | 48 | template inline Size<_T> operator/ (const Size<_T>& lhs, const _T& rhs) { 49 | return Size<_T>(lhs.Width / rhs, lhs.Height / rhs); 50 | } 51 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Common/SplitString.h: -------------------------------------------------------------------------------- 1 | // SplitString.h : Defines a funtion that splits a std::string on a delimiter 2 | // into a std::vector. Also has std::wstring support. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | 9 | // SplitString for std::string. 10 | inline std::vector SplitString( 11 | _In_ const std::string& str, 12 | _In_opt_ const std::string& delimiter = " ", 13 | _In_opt_ size_t startPosition = 0 14 | ) { 15 | std::vector result; 16 | 17 | if (startPosition >= str.size()) { 18 | // The startPosition is beyond the last character in the string, return empty result. 19 | return result; 20 | } 21 | 22 | size_t currentPosition = 0; 23 | while (str.find(delimiter, startPosition + 1U) != std::string::npos) { 24 | // Delimiter found, push the substring to the result vector. 25 | result.push_back(str.substr(currentPosition, str.find(delimiter, startPosition + 1U) - currentPosition)); 26 | 27 | // Move the startPosition and currentPosition for the next segment. 28 | currentPosition = startPosition = str.find(delimiter, startPosition + 1U) + 1U; 29 | } 30 | 31 | // Push the last remaining segment to the result vector. 32 | result.push_back(str.substr(startPosition)); 33 | 34 | // return the result with the splitted string. 35 | return result; 36 | } 37 | 38 | // SplitString for std::wstring. (WIDE CONVERSIONS). 39 | inline std::vector SplitString( 40 | _In_ const std::wstring& str, 41 | _In_opt_ const std::wstring& delimiter = L" ", 42 | _In_opt_ size_t startPosition = 0 43 | ) { 44 | std::vector result; 45 | 46 | if (startPosition >= str.size()) { 47 | // The startPosition is beyond the last character in the string, return empty result. 48 | return result; 49 | } 50 | 51 | size_t currentPosition = 0; 52 | while (str.find(delimiter, startPosition + 1U) != std::wstring::npos) { 53 | // Delimiter found, push the substring to the result vector. 54 | result.push_back(str.substr(currentPosition, str.find(delimiter, startPosition + 1U) - currentPosition)); 55 | 56 | // Move the startPosition and currentPosition for the next segment. 57 | currentPosition = startPosition = str.find(delimiter, startPosition + 1U) + 1U; 58 | } 59 | 60 | // Push the last remaining segment to the result vector. 61 | result.push_back(str.substr(startPosition)); 62 | 63 | // return the result with the splitted string. 64 | return result; 65 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Common/StepTimer.h: -------------------------------------------------------------------------------- 1 | // StepTimer.h : Defines a high performance and accurate timer. 2 | // Good for keeping track of animation and simulation timing. 3 | 4 | #pragma once 5 | 6 | namespace DX 7 | { 8 | // Helper class for animation and simulation timing. 9 | class StepTimer { 10 | public: 11 | StepTimer() : 12 | m_elapsedTicks(0), 13 | m_totalTicks(0), 14 | m_leftOverTicks(0), 15 | m_frameCount(0), 16 | m_framesPerSecond(0), 17 | m_framesThisSecond(0), 18 | m_qpcSecondCounter(0), 19 | m_isFixedTimeStep(false), 20 | m_targetElapsedTicks(TicksPerSecond / 60) { 21 | if (!QueryPerformanceFrequency(&m_qpcFrequency)) { 22 | throw ERROR_INVALID_DATA; 23 | } 24 | 25 | if (!QueryPerformanceCounter(&m_qpcLastTime)) { 26 | throw ERROR_INVALID_DATA; 27 | } 28 | 29 | // Initialize max delta to 1/10 of a second. 30 | m_qpcMaxDelta = m_qpcFrequency.QuadPart / 10; 31 | } 32 | 33 | // Get elapsed time since the previous Update call. 34 | uint64_t GetElapsedTicks() const { return m_elapsedTicks; } 35 | double GetElapsedSeconds() const { return TicksToSeconds(m_elapsedTicks); } 36 | 37 | // Get total time since the start of the program. 38 | uint64_t GetTotalTicks() const { return m_totalTicks; } 39 | double GetTotalSeconds() const { return TicksToSeconds(m_totalTicks); } 40 | 41 | // Get total number of updates since start of the program. 42 | uint32_t GetFrameCount() const { return m_frameCount; } 43 | 44 | // Get the current framerate. 45 | uint32_t GetFramesPerSecond() const { return m_framesPerSecond; } 46 | 47 | // Set whether to use fixed or variable timestep mode. 48 | void SetFixedTimeStep(bool isFixedTimestep) { m_isFixedTimeStep = isFixedTimestep; } 49 | 50 | // Set how often to call Update when in fixed timestep mode. 51 | void SetTargetElapsedTicks(uint64_t targetElapsed) { m_targetElapsedTicks = targetElapsed; } 52 | void SetTargetElapsedSeconds(double targetElapsed) { m_targetElapsedTicks = SecondsToTicks(targetElapsed); } 53 | 54 | // Integer format represents time using 10,000,000 ticks per second. 55 | static const uint64_t TicksPerSecond = 10000000; 56 | 57 | static double TicksToSeconds(uint64_t ticks) { return static_cast(ticks) / TicksPerSecond; } 58 | static uint64_t SecondsToTicks(double seconds) { return static_cast(seconds * TicksPerSecond); } 59 | 60 | // After an intentional timing discontinuity (for instance a blocking IO operation) 61 | // call this to avoid having the fixed timestep logic attempt a set of catch-up 62 | // Update calls. 63 | 64 | void ResetElapsedTime() { 65 | if (!QueryPerformanceCounter(&m_qpcLastTime)) { 66 | throw ERROR_INVALID_DATA; 67 | } 68 | 69 | m_leftOverTicks = 0; 70 | m_framesPerSecond = 0; 71 | m_framesThisSecond = 0; 72 | m_qpcSecondCounter = 0; 73 | } 74 | 75 | // Update timer state, calling the specified Update function the appropriate number of times. 76 | template 77 | void Tick(const TUpdate& update) { 78 | // Query the current time. 79 | LARGE_INTEGER currentTime; 80 | 81 | if (!QueryPerformanceCounter(¤tTime)) { 82 | throw ERROR_INVALID_DATA; 83 | } 84 | 85 | uint64_t timeDelta = currentTime.QuadPart - m_qpcLastTime.QuadPart; 86 | 87 | m_qpcLastTime = currentTime; 88 | m_qpcSecondCounter += timeDelta; 89 | 90 | // Clamp excessively large time deltas (e.g. after paused in the debugger). 91 | if (timeDelta > m_qpcMaxDelta) { 92 | timeDelta = m_qpcMaxDelta; 93 | } 94 | 95 | // Convert QPC units into a canonical tick format. This cannot overflow due to the previous clamp. 96 | timeDelta *= TicksPerSecond; 97 | timeDelta /= m_qpcFrequency.QuadPart; 98 | 99 | uint32_t lastFrameCount = m_frameCount; 100 | 101 | if (m_isFixedTimeStep) { 102 | // Fixed timestep update logic 103 | 104 | // If the app is running very close to the target elapsed time (within 1/4 of a millisecond) just clamp 105 | // the clock to exactly match the target value. This prevents tiny and irrelevant errors 106 | // from accumulating over time. Without this clamping, a game that requested a 60 fps 107 | // fixed update, running with vsync enabled on a 59.94 NTSC display, would eventually 108 | // accumulate enough tiny errors that it would drop a frame. It is better to just round 109 | // small deviations down to zero to leave things running smoothly. 110 | 111 | if (abs(static_cast(timeDelta - m_targetElapsedTicks)) < TicksPerSecond / 4000) { 112 | timeDelta = m_targetElapsedTicks; 113 | } 114 | 115 | m_leftOverTicks += timeDelta; 116 | 117 | while (m_leftOverTicks >= m_targetElapsedTicks) { 118 | m_elapsedTicks = m_targetElapsedTicks; 119 | m_totalTicks += m_targetElapsedTicks; 120 | m_leftOverTicks -= m_targetElapsedTicks; 121 | m_frameCount++; 122 | 123 | update(); 124 | } 125 | } 126 | else { 127 | // Variable timestep update logic. 128 | m_elapsedTicks = timeDelta; 129 | m_totalTicks += timeDelta; 130 | m_leftOverTicks = 0; 131 | m_frameCount++; 132 | 133 | update(); 134 | } 135 | 136 | // Track the current framerate. 137 | if (m_frameCount != lastFrameCount) { 138 | m_framesThisSecond++; 139 | } 140 | 141 | if (m_qpcSecondCounter >= static_cast(m_qpcFrequency.QuadPart)) { 142 | m_framesPerSecond = m_framesThisSecond; 143 | m_framesThisSecond = 0; 144 | m_qpcSecondCounter %= m_qpcFrequency.QuadPart; 145 | } 146 | } 147 | 148 | private: 149 | // Source timing data uses QPC units. 150 | LARGE_INTEGER m_qpcFrequency; 151 | LARGE_INTEGER m_qpcLastTime; 152 | uint64_t m_qpcMaxDelta; 153 | 154 | // Derived timing data uses a canonical tick format. 155 | uint64_t m_elapsedTicks; 156 | uint64_t m_totalTicks; 157 | uint64_t m_leftOverTicks; 158 | 159 | // Members for tracking the framerate. 160 | uint32_t m_frameCount; 161 | uint32_t m_framesPerSecond; 162 | uint32_t m_framesThisSecond; 163 | uint64_t m_qpcSecondCounter; 164 | 165 | // Members for configuring fixed timestep mode. 166 | bool m_isFixedTimeStep; 167 | uint64_t m_targetElapsedTicks; 168 | }; 169 | } 170 | -------------------------------------------------------------------------------- /Osu!Bot V3/Common/Targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h define the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT marco to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include -------------------------------------------------------------------------------- /Osu!Bot V3/Common/Vec2f.h: -------------------------------------------------------------------------------- 1 | // Vec2f.h : Holds the definition of a 2D vector (a point in 2D space) 2 | 3 | #pragma once 4 | 5 | class vec2f { 6 | public: 7 | // Member variables. 8 | float X, Y; 9 | 10 | // Constructors. 11 | vec2f(float x, float y) : X(x), Y(y) {} 12 | vec2f() : X(0.f), Y(0.f) {} 13 | 14 | 15 | // Member functions. 16 | float Length() const { 17 | return sqrtf(X * X + Y * Y); 18 | } 19 | 20 | vec2f MidPoint(const vec2f& vec) const { 21 | return vec2f((X + vec.X) / 2.f, (Y + vec.Y) / 2.f); 22 | } 23 | 24 | vec2f Add(const vec2f& vec) { 25 | X += vec.X; 26 | Y += vec.Y; 27 | return *this; 28 | } 29 | 30 | vec2f Add(float x, float y) { 31 | X += x; 32 | Y += y; 33 | return *this; 34 | } 35 | 36 | vec2f Sub(const vec2f& vec) { 37 | X -= vec.X; 38 | Y -= vec.Y; 39 | return *this; 40 | } 41 | 42 | vec2f Sub(float x, float y) { 43 | X -= x; 44 | Y -= y; 45 | return *this; 46 | } 47 | 48 | vec2f Sub(float n) { 49 | X -= n; 50 | Y -= n; 51 | return *this; 52 | } 53 | 54 | vec2f Mult(const vec2f& vec) { 55 | X *= vec.X; 56 | Y *= vec.Y; 57 | return *this; 58 | } 59 | 60 | vec2f Mult(float x, float y) { 61 | X *= x; 62 | Y *= y; 63 | return *this; 64 | } 65 | 66 | vec2f Mult(float n) { 67 | X *= n; 68 | Y *= n; 69 | return *this; 70 | } 71 | 72 | vec2f Dev(const vec2f& vec) { 73 | X /= vec.X; 74 | Y /= vec.Y; 75 | return *this; 76 | } 77 | 78 | vec2f Dev(float n) { 79 | X /= n; 80 | Y /= n; 81 | return *this; 82 | } 83 | 84 | vec2f Copy() const { 85 | return vec2f(X, Y); 86 | } 87 | 88 | vec2f Rotate(float angle) { 89 | float oX = X; 90 | float oY = Y; 91 | X = (oX * cosf(angle) - oY * sinf(angle)); 92 | Y = (oX * sinf(angle) + oY * cosf(angle)); 93 | return *this; 94 | } 95 | 96 | vec2f Nor() { 97 | float nX = -Y; 98 | float nY = X; 99 | X = nX; 100 | Y = nY; 101 | return *this; 102 | } 103 | 104 | vec2f Normalize() { 105 | return this->Dev(this->Length()); 106 | } 107 | 108 | vec2f ConvertToWindowSpace(const float& stackOffset, const UINT& stackIndex, const DX::Size& multiplier, const DX::Size& offset) { 109 | this->Sub(stackOffset * (FLOAT)stackIndex); 110 | this->Mult((FLOAT)multiplier.Width, (FLOAT)multiplier.Height); 111 | this->Add((FLOAT)offset.Width, (FLOAT)offset.Height); 112 | 113 | //X = (X - stackOffset * (FLOAT)stackIndex) * multiplier.Width + (FLOAT)offset.Width; 114 | //Y = (Y - stackOffset * (FLOAT)stackIndex) * multiplier.Height + (FLOAT)offset.Height; 115 | 116 | return *this; 117 | } 118 | }; 119 | 120 | // Inline operators. 121 | inline vec2f operator+ (vec2f lhs, const vec2f& rhs) { 122 | return lhs.Copy().Add(rhs); 123 | } 124 | 125 | inline vec2f operator- (vec2f lhs, const vec2f& rhs) { 126 | return lhs.Copy().Sub(rhs); 127 | } 128 | 129 | inline vec2f operator* (vec2f lhs, const vec2f& rhs) { 130 | return lhs.Copy().Mult(rhs); 131 | } 132 | 133 | inline vec2f operator* (vec2f lhs, float rhs) { 134 | return lhs.Copy().Mult(rhs); 135 | } 136 | 137 | inline vec2f operator* (float lhs, vec2f rhs) { 138 | // This is a odd case of rhs being the base. 139 | return rhs.Copy().Mult(lhs); 140 | } 141 | 142 | inline vec2f operator/ (vec2f& lhs, const vec2f& rhs) { 143 | return lhs.Copy().Dev(rhs); 144 | } 145 | 146 | inline vec2f operator/ (vec2f lhs, float rhs) { 147 | return lhs.Copy().Dev(rhs); 148 | } 149 | 150 | inline vec2f operator/ (float lhs, vec2f rhs) { 151 | // This is a odd case of rhs being the base. 152 | return rhs.Copy().Dev(lhs); 153 | } 154 | 155 | inline bool operator== (const vec2f& lhs, const vec2f& rhs) { 156 | return (lhs.X == rhs.X) && (lhs.Y == rhs.Y); 157 | } 158 | 159 | inline bool operator!= (const vec2f& lhs, const vec2f& rhs) { 160 | return !(lhs == rhs); 161 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/AppMain.cpp: -------------------------------------------------------------------------------- 1 | // AppMain.cpp : Holds the main app class definitions. 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | using namespace OsuBot; 9 | 10 | 11 | // Constructor of the app class. 12 | // With initialiaztion code. 13 | AppMain::AppMain(const std::shared_ptr& deviceResources) : 14 | m_deviceResources(deviceResources), 15 | m_windowTransparencyColor(RGB(0x00, 0x00, 0x00)), 16 | m_windowTransparencyAlpha(0xff), 17 | m_windowFps(60U), 18 | m_hInstance(NULL), 19 | m_hudVisible(TRUE), 20 | m_debugInfoVisible(FALSE), 21 | m_quit(FALSE) { 22 | // Assign the device resources. 23 | m_deviceResources->RegisterDeviceNotify(this); 24 | 25 | // Read the config ini for defined app settings. 26 | ReadConfigSettings(); 27 | 28 | // Set fixed timestep update logic. 29 | m_timer.SetFixedTimeStep(TRUE); 30 | m_timer.SetTargetElapsedSeconds(1.0 / (DOUBLE)m_windowFps); 31 | } 32 | 33 | // Destructor of the app class. 34 | AppMain::~AppMain() { 35 | // Unassign the device resources. 36 | m_deviceResources->RegisterDeviceNotify(nullptr); 37 | m_deviceResources->m_main = nullptr; 38 | } 39 | 40 | 41 | // Initializes the app contents. 42 | void AppMain::InitContent() { 43 | // Assign a pointer to the app to the device resources. 44 | m_deviceResources->SetMainWindow(this); 45 | 46 | // TODO: place app content initializers here. 47 | m_osuBot = std::make_unique( 48 | m_targetFps, 49 | m_songTimeOffset, 50 | m_timeAddressSignature 51 | ); 52 | 53 | m_songNameRenderer = std::make_unique( 54 | m_deviceResources, 55 | m_windowTransparencyAlpha, 56 | D2D1::ColorF::Red, 57 | DX::Size(800.f, 20.f), 58 | 24.f, 59 | L"", 60 | TRUE, 61 | RECT { 0, 0, 804, 64 }, 62 | D2D1::ColorF::Gray, 63 | DWRITE_TEXT_ALIGNMENT_LEADING 64 | ); 65 | 66 | m_beatmapQueueNamesRenderer = std::make_unique( 67 | m_deviceResources, 68 | m_windowTransparencyAlpha, 69 | D2D1::ColorF::LightSkyBlue, 70 | DX::Size(300.f, 600.f), 71 | 16.f, 72 | L"", 73 | TRUE, 74 | RECT { 0, 0, 304, 604 }, 75 | D2D1::ColorF::Gray, 76 | DWRITE_TEXT_ALIGNMENT_LEADING 77 | ); 78 | 79 | 80 | // Debug renderers. 81 | m_timeRenderer = std::make_unique( 82 | m_deviceResources, 83 | m_windowTransparencyAlpha, 84 | D2D1::ColorF::YellowGreen, 85 | DX::Size(260.f, 10.f), 86 | 20.f, 87 | L"", 88 | TRUE, 89 | RECT { 0, 0, 264, 24 }, 90 | D2D1::ColorF::Blue 91 | ); 92 | 93 | m_fpsRenderer = std::make_unique( 94 | m_deviceResources, 95 | m_windowTransparencyAlpha, 96 | D2D1::ColorF::YellowGreen, 97 | DX::Size(140.f, 10.f), 98 | 32.f, 99 | L"", 100 | TRUE, 101 | RECT { 0, 0, 144, 36 }, 102 | D2D1::ColorF::Blue 103 | ); 104 | } 105 | 106 | 107 | // Read the values from the config ini for the app. 108 | void AppMain::ReadConfigSettings() { 109 | m_configIni = std::make_unique(); 110 | 111 | // TODO: add aditional variables that need to be read from the config ini. 112 | m_configIni->ReadFromConfigFile(m_configIni->time, L"TIME_SIGNATURE", &m_timeAddressSignature, MAX_READSTRING, std::wstring()); 113 | m_configIni->ReadFromConfigFile(m_configIni->time, L"SONG_OFFSET", &m_songTimeOffset, MAX_READSTRING, DOUBLE(0.0)); 114 | 115 | m_configIni->ReadFromConfigFile(m_configIni->configuration, L"WINDOW_FPS", &m_windowFps, MAX_READSTRING, (UINT)60U); 116 | m_configIni->ReadFromConfigFile(m_configIni->configuration, L"LOGIC_UPS", &m_targetFps, MAX_READSTRING, (UINT)60U); 117 | m_configIni->ReadFromConfigFile(m_configIni->configuration, L"TRANSPARENCY_COLOR", &m_windowTransparencyColor, MAX_READSTRING, (COLORREF)0); 118 | m_configIni->ReadFromConfigFile(m_configIni->configuration, L"TRANSPARENCY_ALPHA", &m_windowTransparencyAlpha, MAX_READSTRING, (BYTE)0xff); 119 | 120 | m_configIni.release(); 121 | } 122 | 123 | 124 | // Update the app state when the window size changes. 125 | void AppMain::CreateWindowSizeDependentResources() { 126 | // TODO: place size-dependent initialization functions of app contents. 127 | 128 | } 129 | 130 | // Updates the app content once per rendered frame. 131 | void AppMain::Update() { 132 | // Update the scene. 133 | m_timer.Tick([&]() { 134 | // Only execute if HUD is active. 135 | // Check this here otherwise timer tries to catch up with the fixed fps. 136 | if (!m_hudVisible) { 137 | return; 138 | } 139 | 140 | // TODO: place app content update functions here. 141 | m_songNameRenderer->SetTranslation(DX::Size(3.f, 1.f)); 142 | if (m_osuBot->m_songName != L"Idle") { 143 | m_songNameRenderer->Update(L"Now playing : " + m_osuBot->m_songName); 144 | } 145 | else { 146 | m_songNameRenderer->Update(L"Idle"); 147 | } 148 | 149 | m_beatmapQueueNamesRenderer->SetTranslation(DX::Size(3.f, 80.f)); 150 | std::wstring names = L"Beatmap Queue (Insert to add)\n-----------------------------\n"; 151 | for (auto beatmap : m_osuBot->m_beatmapQueue) { 152 | names += beatmap.GetTitle(); 153 | names += L"\n"; 154 | } 155 | m_beatmapQueueNamesRenderer->Update(names); 156 | 157 | 158 | // Update debug information (normaly not used). 159 | if (m_debugInfoVisible) { 160 | // Current song time. 161 | std::wstring time; 162 | if (m_osuBot->m_sigFound) { 163 | time = std::to_wstring(std::trunc(m_osuBot->GetSongTime()) / 1000); 164 | time = time.substr(0U, time.length() - 3U).append(L"s"); 165 | } 166 | else { 167 | time = L"Signature not found!"; 168 | } 169 | 170 | m_timeRenderer->SetTranslation(DX::Size(3.f, m_deviceResources->GetLogicalSize().Height - 27.f)); 171 | m_timeRenderer->Update(time); 172 | 173 | // Current fps. 174 | UINT fps = m_osuBot->m_logicTimer.GetFramesPerSecond(); 175 | std::wstring fpsString = (fps > 0U) ? std::to_wstring(fps) + L" FPS" : L" - FPS"; 176 | 177 | m_fpsRenderer->SetTranslation(m_deviceResources->GetLogicalSize() - DX::Size(147.f, 39.f)); 178 | m_fpsRenderer->Update(fpsString); 179 | } 180 | }); 181 | } 182 | 183 | // Draw the current frame according to the current app content. 184 | // Returns true if the frame was drawn and is ready. 185 | // Returns false if the render target needed to be recreated. 186 | bool AppMain::Draw() { 187 | // Don't draw anything before any updated scenes. 188 | if (!m_hudVisible || m_timer.GetFrameCount() == m_timerFrameCount) { 189 | return FALSE; 190 | } 191 | m_timerFrameCount = m_timer.GetFrameCount(); 192 | 193 | if (m_deviceResources->GetD2DRenderTarget() != nullptr || m_deviceResources->m_resizeing == FALSE) { 194 | try { 195 | m_deviceResources->m_drawing = TRUE; 196 | 197 | // Clear the previous scene. 198 | m_deviceResources->GetD2DRenderTarget()->BeginDraw(); 199 | m_deviceResources->GetD2DRenderTarget()->Clear(D2D1::ColorF(D2D1::ColorF::Black)); 200 | 201 | // TODO: place app contents draw functions here. 202 | m_songNameRenderer->Draw(); 203 | m_beatmapQueueNamesRenderer->Draw(); 204 | 205 | 206 | // Show debug information to HUD (normaly not used). 207 | if (m_debugInfoVisible) { 208 | m_timeRenderer->Draw(); 209 | m_fpsRenderer->Draw(); 210 | } 211 | 212 | HRESULT hr = m_deviceResources->GetD2DRenderTarget()->EndDraw(); 213 | if (hr == D2DERR_RECREATE_TARGET) { 214 | // If the render target failed to draw for any reason and a new target must be created. 215 | m_deviceResources->HandleDeviceLost(); 216 | } 217 | else { 218 | ThrowIfFailed(hr); 219 | } 220 | 221 | m_deviceResources->m_drawing = FALSE; 222 | } 223 | catch (...) { 224 | // Oops threads are fighting against each other. sorry. 225 | // TODO: throw error if needed. 226 | 227 | // The threading need some more safety measures to prevent this. 228 | 229 | m_deviceResources->GetD2DRenderTarget()->Flush(); 230 | m_deviceResources->m_drawing = FALSE; 231 | } 232 | } 233 | // Frame is ready return. 234 | return TRUE; 235 | } 236 | 237 | 238 | // Register the app window class. 239 | WORD AppMain::MyRegisterClass(HINSTANCE hInstance, WNDPROC windowProc) { 240 | WNDCLASSEXW wcex; 241 | 242 | wcex.cbSize = sizeof(WNDCLASSEXW); 243 | 244 | wcex.style = CS_HREDRAW | CS_VREDRAW; 245 | wcex.lpfnWndProc = windowProc; 246 | wcex.hInstance = hInstance; 247 | wcex.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_OSUBOT_V3)); 248 | wcex.hIconSm = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_SMALL)); 249 | wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); 250 | wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 251 | wcex.lpszClassName = m_windowClass; 252 | wcex.lpszMenuName = NULL; 253 | wcex.cbClsExtra = NULL; 254 | wcex.cbWndExtra = NULL; 255 | 256 | return RegisterClassExW(&wcex); 257 | } 258 | 259 | // Initialize and setup the app window. 260 | bool AppMain::InitInstance(HINSTANCE hInstance, int nCmdShow) { 261 | // Store the instance as the app member variable. 262 | m_hInstance = hInstance; 263 | 264 | // Create the WINAPI window. 265 | m_hWnd = CreateWindowExW( 266 | WS_SYSMENU | WS_EX_TOPMOST | WS_EX_TRANSPARENT, 267 | m_windowClass, 268 | m_title, 269 | WS_POPUP, // Dissable for debugging with window. 270 | //WS_OVERLAPPEDWINDOW, // Enable for debugging with window. 271 | m_rect.left, m_rect.top, 272 | (INT)roundf(m_windowSize.Width), (INT)roundf(m_windowSize.Height), 273 | NULL, 274 | NULL, 275 | m_hInstance, 276 | nullptr 277 | ); 278 | 279 | if (!m_hWnd) { 280 | // Return when failed to create the window. 281 | return FALSE; 282 | } 283 | 284 | // Set the window transparency. 285 | SetLayeredWindowAttributes(m_hWnd, m_windowTransparencyColor, m_windowTransparencyAlpha, ULW_COLORKEY | LWA_ALPHA); 286 | 287 | // Keep window as top with topmost flag. 288 | // and resize window to desired size. 289 | SetWindowPos(m_hWnd, HWND_TOPMOST, m_rect.left, m_rect.top, (INT)roundf(m_windowSize.Width), (INT)roundf(m_windowSize.Height), SWP_ASYNCWINDOWPOS); 290 | 291 | // Show the window. 292 | ShowWindow(m_hWnd, nCmdShow); 293 | UpdateWindow(m_hWnd); 294 | 295 | // Return when succeeded to create and setup the app window. 296 | return TRUE; 297 | } 298 | 299 | 300 | // Get the rect for the working space of the app. 301 | bool AppMain::GetWorkingRect() { 302 | RECT backupRect = m_rect; 303 | 304 | // Store working rect to app member rect. 305 | m_rect = { 0, 0, 800, 600 }; 306 | 307 | if (m_osuBot) { 308 | if (m_osuBot->m_targetHwnd != NULL) { 309 | GetWindowRect(m_osuBot->m_targetHwnd, &m_rect); 310 | } 311 | } 312 | 313 | // Calculate and store the app width, height. 314 | m_windowSize.Width = (FLOAT)(m_rect.right - m_rect.left); 315 | m_windowSize.Height = (FLOAT)(m_rect.bottom - m_rect.top); 316 | 317 | 318 | // Only update sizes if content is initialized. 319 | if (m_osuBot && !EqualRect(&backupRect, &m_rect)) { 320 | // Rescale the app window to the new size. 321 | SetWindowPos( 322 | m_hWnd, 323 | HWND_TOPMOST, 324 | m_rect.left, 325 | m_rect.top, 326 | (INT)roundf(m_windowSize.Width), 327 | (INT)roundf(m_windowSize.Height), 328 | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS 329 | ); 330 | 331 | // Update window size dependent device resources. 332 | m_deviceResources->SetMainWindow(); 333 | CreateWindowSizeDependentResources(); 334 | } 335 | 336 | return TRUE; 337 | } 338 | 339 | 340 | // Notifies renderers that device resources need to be released. 341 | void AppMain::OnDeviceLost() { 342 | // TODO: place contents device resources release funtions. 343 | m_songNameRenderer->ReleaseDeviceDependentResources(); 344 | 345 | m_timeRenderer->ReleaseDeviceDependentResources(); 346 | m_fpsRenderer->ReleaseDeviceDependentResources(); 347 | } 348 | 349 | // Notifies renderers that device resources may now be recreated. 350 | void AppMain::OnDeviceRestored() { 351 | // TODO: place contents device resources creation functions. 352 | m_songNameRenderer->CreateDeviceDependentResources(m_windowTransparencyAlpha, D2D1::ColorF::Red, D2D1::ColorF::DarkGray); 353 | 354 | m_timeRenderer->CreateDeviceDependentResources(m_windowTransparencyAlpha, D2D1::ColorF::YellowGreen, D2D1::ColorF::Blue); 355 | m_fpsRenderer->CreateDeviceDependentResources(m_windowTransparencyAlpha, D2D1::ColorF::YellowGreen, D2D1::ColorF::Blue); 356 | 357 | // Recreate the window size dependent resources. 358 | CreateWindowSizeDependentResources(); 359 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/AppMain.h: -------------------------------------------------------------------------------- 1 | // AppMain.h : Declares the main app class and holds 2 | // all the content of the app as members of itself. 3 | 4 | #pragma once 5 | 6 | // TODO: Include app content. 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | 14 | namespace OsuBot 15 | { 16 | class AppMain : public DX::IDeviceNotify { 17 | public: 18 | // Constructor and destructor. 19 | AppMain(const std::shared_ptr& deviceResources); 20 | ~AppMain(); 21 | 22 | // App functions. 23 | WORD MyRegisterClass(HINSTANCE hInstance, WNDPROC windowProc); 24 | bool InitInstance(HINSTANCE hInstance, int nCmdShow); 25 | bool GetWorkingRect(); 26 | void InitContent(); 27 | void ReadConfigSettings(); 28 | void CreateWindowSizeDependentResources(); 29 | void Update(); 30 | bool Draw(); 31 | 32 | private: 33 | // IDeviceNotify. 34 | virtual void OnDeviceLost(); 35 | virtual void OnDeviceRestored(); 36 | 37 | 38 | public: 39 | // Public app variables. 40 | HWND m_hWnd; 41 | RECT m_rect; 42 | WCHAR m_windowClass[MAX_LOADSTRING]; 43 | WCHAR m_title[MAX_LOADSTRING]; 44 | DX::Size m_windowSize; 45 | bool m_debugInfoVisible; 46 | bool m_hudVisible; 47 | bool m_quit; 48 | 49 | private: 50 | // App variables. 51 | double m_songTimeOffset; 52 | UINT m_windowFps; 53 | UINT m_targetFps; 54 | UINT m_timerFrameCount; 55 | HINSTANCE m_hInstance; 56 | COLORREF m_windowTransparencyColor; 57 | BYTE m_windowTransparencyAlpha; 58 | std::wstring m_timeAddressSignature; 59 | 60 | 61 | public: 62 | // Cached pointer to the device resources. 63 | std::shared_ptr m_deviceResources; 64 | 65 | // Cached pointer to the bot. 66 | std::unique_ptr m_osuBot; 67 | 68 | private: 69 | // TODO: place app content pointer here. 70 | std::unique_ptr m_songNameRenderer; 71 | std::unique_ptr m_beatmapQueueNamesRenderer; 72 | 73 | std::unique_ptr m_timeRenderer; 74 | std::unique_ptr m_fpsRenderer; 75 | 76 | std::unique_ptr m_configIni; 77 | 78 | public: 79 | // Drawing loop timer. 80 | DX::StepTimer m_timer; 81 | }; 82 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot.cpp: -------------------------------------------------------------------------------- 1 | // OsuBot.cpp : Defines the constructor, destructor 2 | // and all member functions for the Bot class. 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | 10 | using namespace OsuBot; 11 | 12 | 13 | // Constructor of the Bot with Initialiazion code. 14 | Bot::Bot(UINT targetFps, double songTimeOffset, std::wstring timeAddressSignature) : 15 | m_gameProcessHandle(nullptr), 16 | m_gameTitle(L""), 17 | m_songName(L"Idle"), 18 | m_targetFps(targetFps), 19 | m_targetHwnd(NULL), 20 | m_sigFound(FALSE), 21 | m_songStarted(FALSE), 22 | m_songPaused(FALSE), 23 | m_songTimeOffset(songTimeOffset), 24 | m_prevSongTime(0.0), 25 | m_offset(0, 0), 26 | m_multiplier(0.f, 0.f), 27 | m_hitObjectIndex(0U), 28 | m_movementAmplifier(1.f), 29 | m_beatmapAuto(FALSE), 30 | m_hardrock(FALSE), 31 | m_autoClickPressed(FALSE), 32 | m_selectedBeatmapIndex(UINT_MAX), 33 | m_timeAddressSignature(timeAddressSignature), 34 | m_timeAddress(nullptr) 35 | { 36 | // Set fixed timestep update logic. 37 | m_logicTimer.SetFixedTimeStep(TRUE); 38 | m_logicTimer.SetTargetElapsedSeconds(1.0 / (DOUBLE)m_targetFps); 39 | 40 | // Initialize the input. 41 | m_input.type = INPUT_MOUSE; 42 | ZeroMemory(&m_input, sizeof(m_input)); 43 | 44 | // Set the movement variables. 45 | m_movementModeCircle = MODE_PREDICTING; 46 | m_movementModeSlider = MODE_STANDARD; 47 | m_movementModeSpinner = MODE_STANDARD; 48 | m_spinnerRadius = 450.f; 49 | } 50 | 51 | // Destructor of the Bot class. 52 | Bot::~Bot() { 53 | m_targetHwnd = NULL; 54 | } 55 | 56 | 57 | // This function should only be used to check if the game is running or not. 58 | // When the game is active (running) it updates the screen metrics if the 59 | // target rect changed. And sets m_targetHwnd to a valid HWND. 60 | void Bot::CheckGameActive(const LPVOID& main) { 61 | if (m_targetHwnd == NULL) { 62 | // Get the HWND if not yet set. 63 | m_targetHwnd = FindWindowW(NULL, L"osu!"); 64 | } 65 | else if (m_logicTimer.GetFrameCount() == m_timerFrameCount) { 66 | // Only continue the function if the logic timer has made a tick. 67 | return; 68 | } 69 | else { 70 | // Update the elapsed frame count. 71 | m_timerFrameCount = m_logicTimer.GetFrameCount(); 72 | 73 | WCHAR buffer[MAX_LOADSTRING]; 74 | 75 | // Check if the game is still running. 76 | GetWindowTextW(m_targetHwnd, buffer, MAX_LOADSTRING); 77 | m_gameTitle.assign(buffer); 78 | if (m_gameTitle == L"" && !m_songStarted) { 79 | m_targetHwnd = FindWindowW(NULL, L"osu!"); 80 | if (m_targetHwnd == NULL) { 81 | // The game has exited. 82 | m_targetHwnd = NULL; 83 | m_sigFound = FALSE; 84 | } 85 | } 86 | else { 87 | if ((*reinterpret_cast*>(main))->m_deviceResources->m_drawing == FALSE) { 88 | (*reinterpret_cast*>(main))->m_deviceResources->m_resizeing = TRUE; 89 | 90 | // Get the current working rect. 91 | (*reinterpret_cast*>(main))->GetWorkingRect(); 92 | 93 | (*reinterpret_cast*>(main))->m_deviceResources->m_resizeing = FALSE; 94 | } 95 | 96 | RECT rect; 97 | CopyRect(&rect, &((*reinterpret_cast*>(main))->m_rect)); 98 | 99 | // Check if the working rect changed. 100 | if (!EqualRect(&rect, &m_targetRect)) { 101 | // Get the new screen metrics. 102 | RECT clientRect; 103 | POINT w = { 0, 6 }; 104 | GetClientRect(m_targetHwnd, &clientRect); 105 | ClientToScreen(m_targetHwnd, &w); 106 | 107 | int x = min(clientRect.right, GetSystemMetrics(SM_CXSCREEN)); 108 | int y = min(clientRect.bottom, GetSystemMetrics(SM_CXSCREEN)); 109 | 110 | // Get x,y multipliers for the movement calculations. 111 | int sWidth = x; 112 | int sHeight = y; 113 | 114 | if (sWidth * 3 > sHeight * 4) { 115 | sWidth = sHeight * 4 / 3; 116 | } 117 | else { 118 | sHeight = sWidth * 3 / 4; 119 | } 120 | 121 | m_multiplier.Width = sWidth / 640.f; 122 | m_multiplier.Height = sHeight / 480.f; 123 | 124 | // Get the x,y offsets for the movement calculations. 125 | int xOffset = (INT)floorf(x - 512.f * m_multiplier.Width) / 2; 126 | int yOffset = (INT)floorf(y - 384.f * m_multiplier.Height) / 2; 127 | 128 | m_offset.Width = w.x + xOffset; 129 | m_offset.Height = w.y + yOffset; 130 | 131 | CopyRect(&m_targetRect, &rect); 132 | } 133 | } 134 | } 135 | } 136 | 137 | // This function should be called every time the bot logic updates. 138 | // To check whether the song is playing, paused, or stopped. 139 | void Bot::CheckSongActive() { 140 | // Check if the songs folder has been assigned. 141 | if (m_songsFolderPath != L"") { 142 | if (m_gameTitle != L"osu!" && m_gameTitle != L"") { 143 | // Check if the song has been paused. 144 | if (m_songStarted && GetSongTime() == m_prevSongTime) { 145 | m_songPaused = TRUE; 146 | 147 | ClipCursor(nullptr); 148 | } 149 | else { 150 | m_songPaused = FALSE; 151 | 152 | ClipCursor(&m_targetRect); 153 | } 154 | 155 | // Get the current beatmap name and difficulty. 156 | GetCurrentSong(); 157 | 158 | if (m_beatmapAuto) { 159 | // Find the beatmap set with the beatmap name. 160 | // TODO: Implement auto beatmap search function. 161 | } 162 | else { 163 | // Check for beatmaps in the queue. 164 | if (m_beatmapQueue.empty()) { 165 | // No beatmaps queued don't start the autoplay. 166 | m_songStarted = FALSE; 167 | 168 | // Send notification to user that no beatmaps were queued. 169 | static bool warning = FALSE; 170 | if (!warning) { 171 | OutputDebugStringW(L"WARNING : No beatmaps queued to select the currently playing song from.\n"); 172 | warning = TRUE; 173 | } 174 | } 175 | else { 176 | // Check any queued map matches current selected map. 177 | for (UINT i = 0U; i < m_beatmapQueue.size(); i++) { 178 | m_songName = (GetBeatmapAtIndex(i)->GetArtist() + L" - " + GetBeatmapAtIndex(i)->GetTitle()); 179 | 180 | if (m_songName == m_currentSongName) { 181 | m_songStarted = TRUE; 182 | m_selectedBeatmapIndex = i; 183 | 184 | ClipCursor(&m_targetRect); 185 | break; 186 | } 187 | else { 188 | m_songName = L"Idle"; 189 | } 190 | } 191 | } 192 | } 193 | } 194 | else if (m_songStarted && m_songName != L"Idle") { 195 | // Not playing a beatmap anymore. Reset the current state. 196 | m_songStarted = FALSE; 197 | m_songPaused = FALSE; 198 | m_beatmapFinished = TRUE; 199 | 200 | m_hitObjectIndex = 0U; 201 | m_songName = L"Idle"; 202 | 203 | ClipCursor(nullptr); 204 | } 205 | else if (!m_beatmapQueue.empty() && m_beatmapFinished && m_selectedBeatmapIndex != UINT_MAX) { 206 | try { 207 | // Remove the beatmap from the queue. 208 | m_beatmapQueue.erase(m_beatmapQueue.begin() + m_selectedBeatmapIndex); 209 | 210 | m_beatmapFinished = FALSE; 211 | } 212 | catch (...) { 213 | // Oops something when wrong while trying to delete a beatmap from the queue. 214 | // TODO: Throw error if needed. 215 | 216 | } 217 | m_selectedBeatmapIndex = UINT_MAX; 218 | } 219 | } 220 | else { 221 | // The songs folder was not assigned, get the folder from the osu!.exe. 222 | GetSongsFolderPath(); 223 | } 224 | } 225 | 226 | // Add a beatmap the the queue if it parsed. 227 | void Bot::AddBeatmapToQueue(const std::wstring& path) { 228 | try { 229 | // Create a beatmap and assign the file. 230 | BeatmapInfo::Beatmap beatmap(path.c_str()); 231 | 232 | // Parse the beatmap. 233 | if (beatmap.ParseBeatmap()) { 234 | // On success, add it to the queue. 235 | m_beatmapQueue.push_back(beatmap); 236 | } 237 | } 238 | catch (...) { 239 | // Oops something when wrong with creating/parsing a beatmap. 240 | // TODO: Thow error if needed. 241 | 242 | OutputDebugStringW(L"ERROR : Beatmap could not be queued.\n"); 243 | } 244 | } 245 | 246 | 247 | // This function handles the autoplay feature of OsuBot. 248 | void Bot::AutoPlay() { 249 | // Don't make unnecessary updates. 250 | m_logicTimer.Tick([&]() { 251 | // Update the song time. 252 | UpdateSongTime(); 253 | 254 | // Check if the song is playing. 255 | CheckSongActive(); 256 | 257 | if (m_songStarted && !m_songPaused && m_hitObjectIndex <= GetBeatmapAtIndex(m_selectedBeatmapIndex)->GetHitObjectsCount()) { 258 | // Get the current hit object from the current beatmap in the queue. 259 | const BeatmapInfo::HitObject* currentObject = GetBeatmapAtIndex(m_selectedBeatmapIndex)->GetHitObjectAtIndex(m_hitObjectIndex); 260 | if (currentObject == nullptr) { 261 | // The hitobject was not set, return to prevent read access exceptions. 262 | return; 263 | } 264 | 265 | // Song has started playing, call movement functions. 266 | if (currentObject->GetStartTime() > GetSongTime()) { 267 | switch (m_movementModeCircle) { 268 | case MODE_NONE: 269 | break; 270 | 271 | case MODE_STANDARD: 272 | MoveToObject(this, &MovementModes::ControlPointStandard); 273 | break; 274 | 275 | case MODE_FLOWING: 276 | MoveToObject(this, &MovementModes::ControlPointFlowing); 277 | break; 278 | 279 | case MODE_PREDICTING: 280 | MoveToObject(this, &MovementModes::ControlPointPredicting); 281 | break; 282 | } 283 | } 284 | 285 | if (currentObject->GetObjectType() == HITOBJECT_SLIDER && currentObject->GetStartTime() < GetSongTime()) { 286 | switch (m_movementModeSlider) { 287 | case MODE_NONE: 288 | break; 289 | 290 | case MODE_STANDARD: 291 | MovementSlider(this, &MovementModes::ControlPointStandard); 292 | break; 293 | 294 | case MODE_FLOWING: 295 | MovementSlider(this, &MovementModes::ControlPointFlowing); 296 | break; 297 | 298 | case MODE_PREDICTING: 299 | MovementSlider(this, &MovementModes::ControlPointPredicting); 300 | break; 301 | } 302 | } 303 | else if (currentObject->GetObjectType() == HITOBJECT_SPINNER && currentObject->GetStartTime() < GetSongTime()) { 304 | switch (m_movementModeSpinner) { 305 | case MODE_NONE: 306 | break; 307 | 308 | case MODE_STANDARD: 309 | MovementSpinner(this, &MovementModes::ControlPointStandard); 310 | break; 311 | 312 | case MODE_FLOWING: 313 | MovementSpinner(this, &MovementModes::ControlPointFlowing); 314 | break; 315 | 316 | case MODE_PREDICTING: 317 | MovementSpinner(this, &MovementModes::ControlPointPredicting); 318 | break; 319 | } 320 | } 321 | 322 | if (currentObject->GetStartTime() <= GetSongTime()) { 323 | if (!m_autoClickPressed) { 324 | // Press. 325 | m_input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; 326 | SendInput(1U, &m_input, sizeof(m_input)); 327 | //OutputDebugStringW((L"Pressed - " + std::to_wstring(m_hitObjectIndex)).c_str()); 328 | 329 | m_autoClickPressed = TRUE; 330 | } 331 | if (currentObject->GetEndTime() <= GetSongTime()) { 332 | // Release. 333 | m_input.mi.dwFlags = MOUSEEVENTF_LEFTUP; 334 | SendInput(1U, &m_input, sizeof(m_input)); 335 | //OutputDebugStringW(L" - Released\n"); 336 | 337 | m_autoClickPressed = FALSE; 338 | 339 | if (m_bezierPts.size() >= 2U) { 340 | // Clear the bezier vector. 341 | m_bezierPts.clear(); 342 | } 343 | 344 | // Add one to the hitObject index. 345 | m_hitObjectIndex++; 346 | } 347 | } 348 | } 349 | }); 350 | } 351 | 352 | 353 | // This function is used to get the currently playing song time. 354 | void Bot::UpdateSongTime() { 355 | // Check if time address is set. 356 | if (!m_timeAddress.get()) { 357 | // Find the time address. 358 | GetTimeAddress(); 359 | } 360 | 361 | // Store the song time into prev song time. 362 | m_prevSongTime = m_songTime; 363 | 364 | // Read the new song time from memory to m_songTime. 365 | ReadProcessMemory(m_gameProcessHandle, reinterpret_cast(*m_timeAddress.get()), &m_songTime, sizeof(DOUBLE), nullptr); 366 | 367 | // Offset the songtime for better timing accuracy. 368 | m_songTime += m_songTimeOffset; 369 | } 370 | 371 | // This function is used to get the time address of the game. 372 | void Bot::GetTimeAddress() { 373 | // First find the process. 374 | if (m_sigScanner.GetProcess(L"osu!.exe")) { 375 | // Then set store the process handle in m_gameProcessHandle. 376 | m_gameProcessHandle = m_sigScanner.GetTargetProcessHandle(); 377 | 378 | // Now find the signature in the process memory space. 379 | m_sigScanner.FindSignature(&m_timeAddressSignature); 380 | 381 | // Set flag to notify the user of status. 382 | m_sigFound = m_sigScanner.SigFound(); 383 | 384 | // Continue if the signature was found. 385 | if (m_sigFound) { 386 | DWORD sigAddress = m_sigScanner.GetResultAddress(); 387 | 388 | // Offset the sig to the timeAddress. 389 | sigAddress -= 0xA; 390 | 391 | // Get the time address from the result. 392 | DWORD resultAddress; 393 | ReadProcessMemory(m_gameProcessHandle, reinterpret_cast(sigAddress), &resultAddress, 4UL, nullptr); 394 | 395 | // Store the resulting address in m_timeAddress. 396 | m_timeAddress = std::make_unique(resultAddress); 397 | } 398 | } 399 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot.h: -------------------------------------------------------------------------------- 1 | // OsuBot.h : Declares the Bot class with all its 2 | // member functions and variables. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | namespace OsuBot 12 | { 13 | class Bot : public MovementModes { 14 | public: 15 | // Constructor and destructor. 16 | Bot(UINT targetFps, double songTimeOffset, std::wstring timeAddressSignature); 17 | ~Bot(); 18 | 19 | // Bot public functions (called outside OsuBot.cpp). 20 | void CheckGameActive(const LPVOID& main); 21 | std::wstring GetSongFromFolderPath(); 22 | void AddBeatmapToQueue(const std::wstring& path); 23 | void UpdateSongTime(); 24 | void AutoPlay(); 25 | 26 | private: 27 | // Bot functions. 28 | std::wstring GetOsuFolderPath(); 29 | void GetSongsFolderPath(); 30 | 31 | void CheckSongActive(); 32 | void GetCurrentSong(); 33 | 34 | void GetTimeAddress(); 35 | 36 | public: 37 | // Bot accessor functions. 38 | double GetSongTime() const { return m_songTime; } 39 | 40 | DX::Size GetOffset() const { return m_offset; } 41 | DX::Size GetMultiplier() const { return m_multiplier; } 42 | 43 | const BeatmapInfo::Beatmap* GetBeatmapAtIndex(const UINT& index) const { return &m_beatmapQueue.at(index); } 44 | 45 | 46 | public: 47 | // Public bot variables. 48 | HWND m_targetHwnd; 49 | bool m_beatmapAuto; 50 | bool m_sigFound; 51 | bool m_autoClickPressed; 52 | std::wstring m_songName; 53 | UINT m_selectedBeatmapIndex; 54 | UINT m_hitObjectIndex; 55 | POINT m_cursorPosition; 56 | std::vector m_beatmapQueue; 57 | 58 | 59 | private: 60 | // Bot variables. 61 | HANDLE m_gameProcessHandle; 62 | std::wstring m_gameTitle; 63 | UINT m_timerFrameCount; 64 | UINT m_targetFps; 65 | RECT m_targetRect; 66 | INPUT m_input; 67 | double m_prevSongTime; 68 | double m_songTime; 69 | double m_songTimeOffset; 70 | bool m_songStarted; 71 | bool m_songPaused; 72 | bool m_beatmapFinished; 73 | 74 | // Beatmap song variables. 75 | std::wstring m_currentSongName; 76 | std::wstring m_currentSongVersion; 77 | std::wstring m_songsFolderPath; 78 | std::vector m_BeatmapSetNames; 79 | 80 | // Movement variables. 81 | DX::Size m_offset; 82 | DX::Size m_multiplier; 83 | float m_movementAmplifier; 84 | bool m_hardrock; 85 | 86 | // Time variables. 87 | std::unique_ptr m_timeAddress; 88 | 89 | 90 | public: 91 | // Bot logic loop timer. 92 | DX::StepTimer m_logicTimer; 93 | 94 | private: 95 | // SigScanner for time address. 96 | std::wstring m_timeAddressSignature; 97 | SigScan::SigScanner m_sigScanner; 98 | }; 99 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/Beatmap.cpp: -------------------------------------------------------------------------------- 1 | // Beatmap.cpp : Defines the content in Beatmap.h 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | using namespace OsuBot::BeatmapInfo; 9 | 10 | 11 | // Timing point constructor. 12 | TimingPoint::TimingPoint(_In_ std::wstring timingString) { 13 | // Split the timingString into tokens. 14 | std::vector tokens = SplitString(timingString, L","); 15 | 16 | // Get the time from the tokens at index [0]. 17 | m_time = std::stoi(tokens.at(0)); 18 | 19 | // Try to get the bpm value from the tokens at index [1]. 20 | try { 21 | m_bpm = std::stof(tokens.at(1)); 22 | } 23 | catch (...) { 24 | // Could not convert from string to float with std::stof(). 25 | // This means that the value is either: 26 | // Greater than FLT_MAX. 27 | // Less than 0. 28 | // Almost one of the two. 29 | 30 | if (tokens.at(1).find('-') != std::wstring::npos) { 31 | // The string contains a negative value, set the bpm to 0. 32 | m_bpm = 0.f; 33 | } 34 | else { 35 | // Set the bpm to the maximum float value. 36 | m_bpm = FLT_MAX; 37 | } 38 | } 39 | } 40 | 41 | 42 | 43 | // Hit object constructor. 44 | HitObject::HitObject( 45 | _In_ std::wstring hitString, 46 | _In_ std::vector* timingPoints, 47 | _In_ float beatmapSliderMultiplier, 48 | _In_ float beatmapSliderTickRate 49 | ) : 50 | m_sliderCenter(0.f, 0.f), 51 | m_startPosition(0.f, 0.f), 52 | m_startTime(0), 53 | m_endTime(0), 54 | m_sliderTime(0), 55 | m_startAngle(0.f), 56 | m_endAngle(0.f), 57 | m_sliderRadius(0.f), 58 | m_pixelLenght(0.f), 59 | m_beatLenght(0.f), 60 | m_beatLenghtBase(0.f), 61 | m_sliderTickCount(0.f), 62 | m_sliderRepeatCount(0U), 63 | m_stackIndex(0U), 64 | m_objectType(0U), 65 | m_sliderType(0x00) 66 | { 67 | // Split the hitString into tokens. 68 | std::vector tokens = SplitString(hitString, L","); 69 | 70 | // Get the start positions and time values of the object. 71 | m_startPosition = vec2f(std::stof(tokens.at(0)), std::stof(tokens.at(1))); 72 | m_startTime = std::stoi(tokens.at(2)); 73 | 74 | // Get the object type. 75 | m_objectType = _wtoi(tokens.at(3).c_str()); 76 | 77 | if (GetObjectType() == HITOBJECT_SLIDER) { 78 | // The object is a slider, get the required information from the hitString. 79 | // and Calculate other required information. 80 | GetSliderInfo(&tokens, timingPoints, beatmapSliderMultiplier, beatmapSliderTickRate); 81 | } 82 | else if (GetObjectType() == HITOBJECT_SPINNER) { 83 | // The object is a spinner, get the end time from the hitString. 84 | GetSpinnerInfo(&tokens); 85 | } 86 | else { 87 | // Oops something went wrong with the object type. 88 | // TODO: Thow error if needed. 89 | } 90 | } 91 | 92 | // This function should only be called when the object is a slider. 93 | // The function gets all the required information and stores it in the HitObject class. 94 | void HitObject::GetSliderInfo( 95 | _In_ std::vector* tokens, 96 | _In_ std::vector* timingPoints, 97 | _In_ float beatmapSliderMultiplier, 98 | _In_ float beatmapSliderTickRate 99 | ) { 100 | // Get the beat lenght base. 101 | float bpm = m_beatLenghtBase = timingPoints->at(0).GetBpm(); 102 | 103 | // Get the repeat count. 104 | m_sliderRepeatCount = (UINT)std::stoi(tokens->at(6)); 105 | 106 | // Get the pixel lenght. 107 | m_pixelLenght = std::stof(tokens->at(7)); 108 | 109 | for (TimingPoint point : *timingPoints) { 110 | // Check if the timing point is valid. 111 | if (point.GetTime() <= m_startTime) { 112 | if (point.GetBpm() >= 0.f) { 113 | // Valid timimg point, set the beat lenght base to the bpm. 114 | m_beatLenghtBase = point.GetBpm(); 115 | } 116 | // Always set bpm to the timing points bpm. 117 | bpm = point.GetBpm(); 118 | } 119 | } 120 | 121 | if (bpm < 0.f) { 122 | // Calculate the correct bpm. 123 | bpm = m_beatLenghtBase * bpm / -100.f; 124 | } 125 | // Set the beat lenght. 126 | m_beatLenght = bpm; 127 | 128 | 129 | // Calculate the slider end time and slider time (duration). 130 | m_sliderTime = (INT)roundf(m_beatLenght * (m_pixelLenght / beatmapSliderMultiplier) / 100.f); 131 | m_endTime = (INT)roundf(static_cast(m_sliderTime) * m_sliderRepeatCount) + m_startTime; 132 | 133 | // Calculate the slider tick count and limit the minimum value to 1.0f. 134 | m_sliderTickCount = m_pixelLenght / (((100.f * beatmapSliderMultiplier) / beatmapSliderTickRate) / (m_beatLenght / m_beatLenghtBase)); 135 | m_sliderTickCount = max(m_sliderTickCount, 1.f); 136 | 137 | 138 | // Push the start position to the slider points vector. 139 | std::vector sliderPoints; 140 | sliderPoints.push_back(m_startPosition); 141 | 142 | // Split the tokens at index [5] into tokens that hold the slider points. 143 | std::vector sliderTokens = SplitString(tokens->at(5), L"|"); 144 | // Start at index 1 to skip the slider type byte. 145 | for (UINT i = 1U; i < (UINT)sliderTokens.size(); i++) { 146 | // Split the point string into tokens that hold the point x and y values. 147 | std::vector pointTokens = SplitString(sliderTokens.at(i), L":"); 148 | 149 | // Create a vec2f point from the point tokens. 150 | vec2f point(std::stof(pointTokens.at(0)), std::stof(pointTokens.at(1))); 151 | 152 | // Push the point the slider points vector. 153 | sliderPoints.push_back(point); 154 | } 155 | 156 | // Remove the last point back, if it is the same as the second to last one. 157 | if (sliderPoints.at(sliderPoints.size() - 1U) == sliderPoints.at(sliderPoints.size() - 2U)) { 158 | sliderPoints.pop_back(); 159 | } 160 | 161 | 162 | // Get the slider type from the slider tokens. 163 | m_sliderType = (BYTE)sliderTokens.at(0).front(); 164 | 165 | // Do the calculations for the correct slider type. 166 | if (m_sliderType == 0x4C || m_sliderType == 0x43) { 167 | // Slider is of type L'L' (0x4C) or L'C' (0x43). 168 | // This means the slider has only linear segments. 169 | GetLinearSliderInfo(&sliderPoints); 170 | } 171 | else if (m_sliderType == 0x50 && sliderPoints.size() == 3U) { 172 | // Slider is of type L'P' (0x50) and has exactly 3 points. 173 | // This means the slider has a circluar body. 174 | GetCircularSliderInfo(&sliderPoints); 175 | } 176 | else { 177 | m_sliderType = 0x42; 178 | // Slider type does not require specific calculations. 179 | // And is set to L'B' (0x42). 180 | // This means the slider body can be calculated using bezier curves. 181 | GetBezierSliderInfo(&sliderPoints); 182 | } 183 | } 184 | 185 | // This function should only be called when the slider has only linear segements. 186 | // The function gets the segment points and stores it in the m_sliderSegemnts vector. 187 | void HitObject::GetLinearSliderInfo(_In_ std::vector* sliderPoints) { 188 | for (UINT i = 1; i < (UINT)sliderPoints->size(); i++) { 189 | // Create a segment with the slider points. 190 | Segment seg({ sliderPoints->at(i - 1U), sliderPoints->at(i) }); 191 | 192 | // Push the segment to the slider segments vector. 193 | m_sliderSegments.push_back(seg); 194 | } 195 | } 196 | 197 | // This function should only be called when the slider has only circular segments. 198 | // The function calculates: 199 | // Slider center stores into m_sliderCenter. 200 | // Slider starting angle stores into m_startAngle. 201 | // Slider ending angle stores into m_endAngle. 202 | // Slider radius stores into m_sliderRadius. 203 | void HitObject::GetCircularSliderInfo(_In_ std::vector* sliderPoints) { 204 | // Calculate slider center. 205 | vec2f midA = sliderPoints->at(0).MidPoint(sliderPoints->at(1)); 206 | vec2f midB = sliderPoints->at(2).MidPoint(sliderPoints->at(1)); 207 | vec2f norA = sliderPoints->at(1).Copy().Sub(sliderPoints->at(0)).Nor(); 208 | vec2f norB = sliderPoints->at(1).Copy().Sub(sliderPoints->at(2)).Nor(); 209 | 210 | m_sliderCenter = Intersect(midA, norA, midB, norB); 211 | 212 | // Calculate the slider angles. 213 | vec2f startAnglePoint = sliderPoints->at(0).Copy().Sub(m_sliderCenter); 214 | vec2f midAnglePoint = sliderPoints->at(1).Copy().Sub(m_sliderCenter); 215 | vec2f endAnglePoint = sliderPoints->at(2).Copy().Sub(m_sliderCenter); 216 | 217 | m_startAngle = atan2f(startAnglePoint.Y, startAnglePoint.X); 218 | float midAngle = atan2f(midAnglePoint.Y, midAnglePoint.X); 219 | m_endAngle = atan2f(endAnglePoint.Y, endAnglePoint.X); 220 | 221 | // Correct the angles. 222 | if (!IsInside(m_startAngle, midAngle, m_endAngle)) { 223 | if (fabsf(m_startAngle + M_2PI - m_endAngle) < M_2PI && IsInside(m_startAngle + M_2PI, midAngle, m_endAngle)) { 224 | m_startAngle += M_2PI; 225 | } 226 | else if (fabsf(m_startAngle - (m_endAngle + M_2PI)) < M_2PI && IsInside(m_startAngle, midAngle, m_endAngle + M_2PI)) { 227 | m_endAngle += M_2PI; 228 | } 229 | else if (fabsf(m_startAngle - M_2PI - m_endAngle) < M_2PI && IsInside(m_startAngle - M_2PI, midAngle, m_endAngle)) { 230 | m_startAngle -= M_2PI; 231 | } 232 | else if (fabsf(m_startAngle - (m_endAngle - M_2PI)) < M_2PI && IsInside(m_startAngle, midAngle, m_endAngle - M_2PI)) { 233 | m_endAngle -= M_2PI; 234 | } 235 | else { 236 | // Something when wrong with correcting the angles. 237 | // TODO: Thow error if needed. 238 | 239 | // The function will continue, but might give an unexpected result. 240 | } 241 | } 242 | // Last correction with the arcing angle. 243 | if (m_endAngle > m_startAngle) { 244 | m_endAngle = m_startAngle + (m_pixelLenght / startAnglePoint.Length()); 245 | } 246 | else { 247 | m_endAngle = m_startAngle - (m_pixelLenght / startAnglePoint.Length()); 248 | } 249 | 250 | // Get the slider radius. 251 | m_sliderRadius = startAnglePoint.Length(); 252 | } 253 | 254 | // This function should be called when the slider has neither only linear or circular segments. 255 | // The function calculates the curves and stores the segments in the m_sliderSegments vector. 256 | void HitObject::GetBezierSliderInfo(_In_ std::vector* sliderPoints) { 257 | std::vector> curveList; 258 | std::vector curve; 259 | 260 | for (vec2f point : *sliderPoints) { 261 | // Store the points in the curve. 262 | if ((UINT)curve.size() > 1U) { 263 | if (point == curve.at(curve.size() - 1U)) { 264 | // Point is the last point in the curve. 265 | // Store the curve in the curveList. 266 | curveList.push_back(curve); 267 | curve.clear(); 268 | } 269 | } 270 | curve.push_back(point); 271 | } 272 | // Store the remaining part of the curve in the curveList. 273 | curveList.push_back(curve); 274 | curve.clear(); 275 | 276 | // Store the curves from the curveList in the slider segments. 277 | for (Segment seg : curveList) { 278 | m_sliderSegments.push_back(seg); 279 | } 280 | } 281 | 282 | 283 | // This function should only be called when the object is a spinner. 284 | // The function gets the end time of the spinner and stores it in the HitObject class. 285 | void HitObject::GetSpinnerInfo(_In_ std::vector* tokens) { 286 | // Get the spinner end time. 287 | m_endTime = _wtoi(tokens->at(5).c_str()); 288 | } 289 | 290 | 291 | // This function is used to get the point on a slider at a specified time. 292 | vec2f HitObject::GetPointByT(_In_ const double& time) const { 293 | double pointTime = time; //static_cast(floor(time)) % 2 == 0 ? time - floor(time) : floor(time) + 1.0 - time; 294 | 295 | if (m_sliderType == 0x50) { 296 | // Slider has a circluar body. 297 | // Construct a point using unit circle calculus. 298 | float angle = m_startAngle * static_cast(1.0 - pointTime) + m_endAngle * static_cast(pointTime); 299 | return vec2f(m_sliderCenter.X + m_sliderRadius * cosf(angle), m_sliderCenter.Y + m_sliderRadius * sinf(angle)); 300 | } 301 | 302 | // For other slider types do: 303 | 304 | // Check if m_sliderSegments is valid. 305 | vec2f oldPoint; 306 | try { 307 | oldPoint = m_sliderSegments.at(0).m_points.at(0); 308 | } 309 | catch (...) { 310 | // Something went wrong with accessing the first point in the slider segment vector. 311 | // TODO: Thow error if needed. 312 | OutputDebugStringW(L"ERROR: oldPoint (sliderSegments) failed!\n"); 313 | 314 | // Return from the function with a pre-determined point. 315 | return GetStartPosition(); 316 | } 317 | 318 | double currentDistance = 0.0; 319 | double pointPixelLength = (DOUBLE)m_pixelLenght * pointTime; 320 | 321 | for (auto seg : m_sliderSegments) { 322 | if (seg == m_sliderSegments.back()) { 323 | double currentTime = 0.0; 324 | while (currentDistance < m_pixelLenght) { 325 | vec2f p = GetPointOnBezier(seg.m_points, currentTime); 326 | currentDistance += (oldPoint - p).Length(); 327 | 328 | if (currentDistance > pointPixelLength) { 329 | return oldPoint; 330 | } 331 | 332 | oldPoint = p; 333 | currentTime += 1.0 / static_cast(seg.m_points.size() * 50U - 1U); 334 | } 335 | } 336 | 337 | for (double currentTime = 0.0; currentTime < 1.0 + (1.0 / static_cast(seg.m_points.size() * 50U - 1U)); currentTime += (1.0 / static_cast(seg.m_points.size() * 50U - 1U))) { 338 | vec2f p = GetPointOnBezier(seg.m_points, currentTime); 339 | 340 | currentDistance += (oldPoint - p).Length(); 341 | if (currentDistance > pointPixelLength) { 342 | return oldPoint; 343 | } 344 | 345 | oldPoint = p; 346 | } 347 | } 348 | 349 | return oldPoint; 350 | } 351 | 352 | 353 | // This function is used to get the hit object type. 354 | // The result is either: 355 | // HITOBJECT_CIRCLE with value 1 356 | // HITOBJECT_SLIDER with value 2 357 | // HITOBJECT_SPINNER with value 8 358 | int HitObject::GetObjectType() const { 359 | if ((m_objectType & 2) > 0) { 360 | // The object is a slider. 361 | return HITOBJECT_SLIDER; 362 | } 363 | else if ((m_objectType & 8) > 0) { 364 | // The object is a spinner. 365 | return HITOBJECT_SPINNER; 366 | } 367 | else { 368 | // The object is a circle. 369 | return HITOBJECT_CIRCLE; 370 | } 371 | } 372 | 373 | // This function is used to get the end position of the object. 374 | // If the object type is NOT a slider, return the start position instead. 375 | vec2f HitObject::GetEndPosition() const { 376 | if (GetObjectType() == HITOBJECT_SLIDER) { 377 | // Object is a slider, return the slider end position. 378 | return GetPointByT(static_cast(GetSliderRepeatCount())); 379 | } 380 | // Object is not a slider, return the start position of the object. 381 | return GetStartPosition(); 382 | } 383 | 384 | // This function is used to get the end time of the object. 385 | // If the object type is a circle, return the start time instead. 386 | // NOTE: circles don't have an end time specified. 387 | int HitObject::GetEndTime() const { 388 | if (GetObjectType() != HITOBJECT_CIRCLE) { 389 | // Object is a slider, return the slider end time. 390 | return m_endTime; 391 | } 392 | // Object is not a slider, return the start time of the object. 393 | return m_startTime; 394 | } 395 | 396 | 397 | // This function should be called to parse the beatmap information 398 | // then optionaly pushing the beatmap to a queue. 399 | bool Beatmap::ParseBeatmap() { 400 | // Create a index for the headers. 401 | UINT headerIndex = 0U; 402 | 403 | if (m_beatmapFile != nullptr) { 404 | // Open success, start reading. 405 | while (TRUE) { 406 | if (feof(m_beatmapFile)) { 407 | // End of file. 408 | // TODO: Send a notifier if needed. 409 | 410 | // Return early from the function. 411 | break; 412 | } 413 | else { 414 | // Find the headers. 415 | if (FindHeader(headerIndex)) { 416 | // Read the keys under the header. 417 | ReadKeysUnderHeader(headerIndex); 418 | 419 | // Set index for the next header. 420 | headerIndex++; 421 | 422 | // Reset all header flags. 423 | m_headerGeneral = FALSE; 424 | m_headerEditor = FALSE; 425 | m_headerMetadata = FALSE; 426 | m_headerDifficulty = FALSE; 427 | m_headerTimingPoints = FALSE; 428 | m_headerColours = FALSE; 429 | m_headerHitObjects = FALSE; 430 | } 431 | } 432 | } 433 | } 434 | else { 435 | // Couldn't open file, reset the FILE. 436 | return FALSE; 437 | } 438 | 439 | // After parsing, there needs to be some calculations done for the stacking of the HitObjects. 440 | // TODO: Make a correctly working one this time :stuck_out_tongue_winking_eye: 441 | 442 | // (The calculations in V2 didn't seem to be correctly working.) 443 | // This shouldn't have a too large impact on the autoplaying. 444 | 445 | 446 | // Calculate the stacking offset with the circle size. 447 | m_stackOffset = ((512.0f / 16.0f) * (1.0f - 0.7f * (m_circleSize - 5.0f) / 5.0f) / 10.0f) / m_circleSize; 448 | 449 | return TRUE; 450 | } 451 | 452 | // This function is used to find a header in a opened beatmap. 453 | bool Beatmap::FindHeader(_In_ const UINT& headerIndex) { 454 | // Create a buffer to assign. 455 | std::wstring readLine; 456 | LPWSTR lpReadLine = new wchar_t[MAX_READSTRING]; 457 | 458 | // Reset the cursor position. 459 | fpos_t fpos = 0U; 460 | fsetpos(m_beatmapFile, &fpos); 461 | 462 | 463 | // Find the header at the array with the index. 464 | while (readLine.find(headerStrings[headerIndex]) == std::wstring::npos) { 465 | if (feof(m_beatmapFile)) { 466 | // End of file, return false. 467 | 468 | delete lpReadLine; 469 | return FALSE; 470 | } 471 | else { 472 | // Read a new line. 473 | auto debugstr = fgetws(lpReadLine, MAX_READSTRING, m_beatmapFile); 474 | readLine.assign(lpReadLine); 475 | } 476 | } 477 | 478 | // Header found, flag the header. 479 | switch (headerIndex) { 480 | case beatmapHeaders::General: 481 | m_headerGeneral = TRUE; 482 | break; 483 | 484 | case beatmapHeaders::Editor: 485 | m_headerEditor = TRUE; 486 | break; 487 | 488 | case beatmapHeaders::Metadata: 489 | m_headerMetadata = TRUE; 490 | break; 491 | 492 | case beatmapHeaders::Difficulty: 493 | m_headerDifficulty = TRUE; 494 | break; 495 | 496 | case beatmapHeaders::TimingPoints: 497 | m_headerTimingPoints = TRUE; 498 | break; 499 | 500 | case beatmapHeaders::Colours: 501 | m_headerColours = TRUE; 502 | break; 503 | 504 | case beatmapHeaders::HitObjects: 505 | m_headerHitObjects = TRUE; 506 | break; 507 | } 508 | 509 | delete lpReadLine; 510 | return TRUE; 511 | } 512 | 513 | // This function reads the values under the specified header from the beatmap. 514 | void Beatmap::ReadKeysUnderHeader(_In_ UINT& headerIndex) { 515 | // Create a buffer to assign. 516 | LPWSTR lpReadLine = new wchar_t[MAX_READSTRING]; 517 | std::wstring readLine; 518 | 519 | // Calculate next header index (don't if next index out of range). 520 | UINT nextIndex = headerIndex; 521 | if (headerIndex + 1U != beatmapHeaders::count) { 522 | nextIndex = headerIndex + 1U; 523 | } 524 | 525 | // Read until the next header. 526 | while (readLine.find(headerStrings[nextIndex]) == std::wstring::npos) { 527 | if (feof(m_beatmapFile)) { 528 | // End of file, return. 529 | return; 530 | } 531 | else { 532 | // Read a new line. 533 | auto debugstr = fgetws(lpReadLine, MAX_READSTRING, m_beatmapFile); 534 | readLine.assign(lpReadLine); 535 | } 536 | 537 | // Read the keys under the current header, if the header was found. 538 | switch (headerIndex) { 539 | case beatmapHeaders::General: 540 | if (m_headerGeneral) { 541 | if (readLine.find(L"StackLeniency") != std::wstring::npos) { 542 | m_stackLeniency = std::stof(GetValueString(&readLine)); 543 | } 544 | else if (readLine.find(L"Mode") != std::wstring::npos) { 545 | m_gameMode = std::stoi(GetValueString(&readLine)); 546 | } 547 | } 548 | break; 549 | 550 | case beatmapHeaders::Editor: 551 | if (m_headerEditor) { 552 | if (readLine.find(L"BeatDivisor") != std::wstring::npos) { 553 | m_beatDivisor = std::stof(GetValueString(&readLine)); 554 | } 555 | } 556 | break; 557 | 558 | case beatmapHeaders::Metadata: 559 | if (m_headerMetadata) { 560 | if (readLine.find(L"Title") != std::wstring::npos) { 561 | if (readLine.find(L"Unicode") == std::wstring::npos) { 562 | m_title = GetValueString(&readLine); 563 | } 564 | } 565 | else if (readLine.find(L"Artist") != std::wstring::npos) { 566 | if (readLine.find(L"Unicode") == std::wstring::npos) { 567 | m_artist = GetValueString(&readLine); 568 | } 569 | } 570 | else if (readLine.find(L"Creator") != std::wstring::npos) { 571 | m_creator = GetValueString(&readLine); 572 | } 573 | else if (readLine.find(L"Version") != std::wstring::npos) { 574 | m_version = GetValueString(&readLine); 575 | } 576 | else if (readLine.find(L"BeatmapID") != std::wstring::npos) { 577 | m_beatmapID = (UINT)std::stoi(GetValueString(&readLine)); 578 | } 579 | } 580 | break; 581 | 582 | case beatmapHeaders::Difficulty: 583 | if (m_headerDifficulty) { 584 | if (readLine.find(L"CircleSize") != std::wstring::npos) { 585 | m_circleSize = std::stof(GetValueString(&readLine)); 586 | } 587 | else if (readLine.find(L"OverallDifficulty") != std::wstring::npos) { 588 | m_overallDifficulty = std::stof(GetValueString(&readLine)); 589 | } 590 | else if (readLine.find(L"ApproachRate") != std::wstring::npos) { 591 | m_approachRate = std::stof(GetValueString(&readLine)); 592 | } 593 | else if (readLine.find(L"SliderMultiplier") != std::wstring::npos) { 594 | m_sliderMultiplier = std::stof(GetValueString(&readLine)); 595 | } 596 | else if (readLine.find(L"SliderTickRate") != std::wstring::npos) { 597 | m_sliderTickRate = std::stof(GetValueString(&readLine)); 598 | } 599 | } 600 | break; 601 | 602 | case beatmapHeaders::TimingPoints: 603 | if (m_headerTimingPoints) { 604 | if (readLine.find(L',') != std::wstring::npos) { 605 | m_timingPoints.push_back(TimingPoint(readLine)); 606 | } 607 | } 608 | break; 609 | 610 | case beatmapHeaders::HitObjects: 611 | if (m_headerHitObjects) { 612 | if (readLine.find(L',') != std::wstring::npos) { 613 | m_hitObjects.push_back(HitObject(readLine, &m_timingPoints, m_sliderMultiplier, m_sliderTickRate)); 614 | } 615 | } 616 | break; 617 | } 618 | } 619 | delete lpReadLine; 620 | } 621 | 622 | // Returns a string of the value from a key-value string (the immediate string from a beatmap). 623 | std::wstring Beatmap::GetValueString(_In_ const std::wstring* readLine) const { 624 | auto str = readLine->substr(readLine->find(L":") + 1U); 625 | 626 | // Pop back the endline character. 627 | if (str.back() == L'\n') { 628 | str.pop_back(); 629 | } 630 | 631 | return str; 632 | } 633 | 634 | 635 | // This function should be used to get a pointer to the hit object at specified time. 636 | const HitObject* Beatmap::FindHitObjectAtT(_In_ const double& songTime) const { 637 | for (auto object = m_hitObjects.begin(); object != m_hitObjects.end(); ++object) { 638 | if (object->GetStartTime() >= songTime && object->GetObjectType() == HITOBJECT_CIRCLE) { 639 | // The object is a circle and song time is smaller than the start time. 640 | return &(*object); 641 | } 642 | else if (object->GetEndTime() > songTime) { 643 | // Continue if the object end time is greater than the song time. 644 | continue; 645 | } 646 | 647 | // Check if the object is not a circle and the end time smaller than the song time. 648 | if (object->GetEndTime() <= songTime && object->GetObjectType() != HITOBJECT_CIRCLE) { 649 | // Object found, return a pointer to it. 650 | return &(*object); 651 | } 652 | else if (*object == m_hitObjects.back()) { 653 | // The object is the last in the list (most likely already passed). 654 | return &(*object); 655 | } 656 | } 657 | 658 | // No object found, return a nullptr. 659 | return nullptr; 660 | } 661 | 662 | // This function retrives a pointer to a hitObject at a given index. 663 | const HitObject* Beatmap::GetHitObjectAtIndex(_In_ const UINT& index) const { 664 | if (m_hitObjects.size() > 0U) { 665 | if (index < 0U) { 666 | // Index is smaller than 0U, return the first object. 667 | return &m_hitObjects.front(); 668 | } 669 | else if (index >= m_hitObjects.size()) { 670 | // Index is greater than the size, return the last object. 671 | return &m_hitObjects.back(); 672 | } 673 | 674 | // Return the object at index. 675 | return &m_hitObjects.at(index); 676 | } 677 | // No hitObjects in the vector, return a nullptr. 678 | return nullptr; 679 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/Beatmap.h: -------------------------------------------------------------------------------- 1 | // Beatmap.h : Declares the classes that holds the usefull information 2 | // from a beatmap for the Bot. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | 12 | namespace OsuBot 13 | { 14 | namespace BeatmapInfo 15 | { 16 | // A class that holds the points of a slider segment. 17 | class Segment { 18 | public: 19 | // Constructor. 20 | Segment(std::vector points) : m_points(points) {}; 21 | 22 | // Member variables. 23 | std::vector m_points; 24 | }; 25 | 26 | // A class that holds information about a timing point for a hit object. 27 | class TimingPoint { 28 | public: 29 | // Constructor. 30 | explicit TimingPoint(_In_ std::wstring timingString); 31 | 32 | // Accessor functions. 33 | int GetTime() const { return m_time; } 34 | float GetBpm() const { return m_bpm; } 35 | 36 | 37 | private: 38 | // Member variables. 39 | int m_time; 40 | float m_bpm; 41 | }; 42 | 43 | // A class that holds information about a hit object from a beatmap. 44 | class HitObject { 45 | public: 46 | // Constructor. 47 | HitObject( 48 | _In_ std::wstring hitString, 49 | _In_ std::vector* timingPoints, 50 | _In_ float beatmapSliderMultiplier, 51 | _In_ float beatmapSliderTickRate 52 | ); 53 | 54 | private: 55 | // Internal functions. 56 | // Slider info functions. 57 | void GetSliderInfo( 58 | _In_ std::vector* tokens, 59 | _In_ std::vector* timingPoints, 60 | _In_ float beatmapSliderMultiplier, 61 | _In_ float beatmapSliderTickRate 62 | ); 63 | void GetLinearSliderInfo(_In_ std::vector* sliderPoints); 64 | void GetCircularSliderInfo(_In_ std::vector* sliderPoints); 65 | void GetBezierSliderInfo(_In_ std::vector* sliderPoints); 66 | 67 | // Spinner info function. 68 | void GetSpinnerInfo(_In_ std::vector* tokens); 69 | 70 | 71 | public: 72 | // Get point on slider by time. 73 | vec2f GetPointByT(_In_ const double& time) const; 74 | 75 | // Accessor functions. 76 | int GetObjectType() const; 77 | 78 | vec2f GetStartPosition() const { return m_startPosition; } 79 | vec2f GetEndPosition() const; 80 | 81 | int GetStartTime() const { return m_startTime; } 82 | int GetEndTime() const; 83 | int GetSliderTime() const { return m_sliderTime; } 84 | float GetSliderTickCount() const { return m_sliderTickCount; } 85 | UINT GetSliderRepeatCount() const { return m_sliderRepeatCount;} 86 | UINT GetStackIndex() const { return m_stackIndex; } 87 | 88 | 89 | private: 90 | // Member variables. 91 | vec2f m_sliderCenter; 92 | vec2f m_startPosition; 93 | int m_startTime; 94 | int m_endTime; 95 | int m_sliderTime; 96 | float m_startAngle; 97 | float m_endAngle; 98 | float m_sliderRadius; 99 | float m_pixelLenght; 100 | float m_beatLenght; 101 | float m_beatLenghtBase; 102 | float m_sliderTickCount; 103 | UINT m_sliderRepeatCount; 104 | UINT m_stackIndex; 105 | UINT m_objectType; 106 | BYTE m_sliderType; 107 | std::vector m_sliderSegments; 108 | }; 109 | 110 | // A class that holds all usefull information about a beatmap for the bot. 111 | class Beatmap { 112 | public: 113 | // Constructor and destructor. 114 | Beatmap(const wchar_t* path) : 115 | m_filePath(path) 116 | { 117 | m_beatmapFile = _wfsopen(m_filePath, L"r", _SH_DENYNO); 118 | } 119 | ~Beatmap() { 120 | if (m_beatmapFile) { 121 | fclose(m_beatmapFile); 122 | } 123 | } 124 | 125 | // Member functions. 126 | bool ParseBeatmap(); 127 | 128 | 129 | public: 130 | // Accessor functions. 131 | const HitObject* FindHitObjectAtT(_In_ const double& songTime) const; 132 | const HitObject* GetHitObjectAtIndex(_In_ const UINT& index) const; 133 | 134 | UINT GetHitObjectsCount() const { return (UINT)m_hitObjects.size(); } 135 | 136 | float GetStackOffset() const { return m_stackOffset; } 137 | float GetCircleSize() const { return m_circleSize; } 138 | 139 | std::wstring GetTitle() const { return m_title; } 140 | std::wstring GetArtist() const { return m_artist; } 141 | std::wstring GetCreator() const { return m_creator; } 142 | std::wstring GetVersion() const { return m_version; } 143 | UINT GetBeatmapID() const { return m_beatmapID; } 144 | 145 | private: 146 | // Internal function. 147 | bool FindHeader(_In_ const UINT& headerIndex); 148 | void ReadKeysUnderHeader(_In_ UINT& headerIndex); 149 | std::wstring GetValueString(_In_ const std::wstring* readLine) const; 150 | 151 | 152 | private: 153 | // Header enum and wstring array. 154 | const enum beatmapHeaders : UINT { 155 | General, 156 | Editor, 157 | Metadata, 158 | Difficulty, 159 | TimingPoints, 160 | Colours, 161 | HitObjects, 162 | count 163 | }; 164 | 165 | constexpr static const wchar_t* headerStrings[beatmapHeaders::count] = { 166 | L"[General]", 167 | L"[Editor]", 168 | L"[Metadata]", 169 | L"[Difficulty]", 170 | L"[TimingPoints]", 171 | L"[Colours]", 172 | L"[HitObjects]" 173 | }; 174 | 175 | public: 176 | // Member variables. 177 | FILE* m_beatmapFile; 178 | LPCWSTR m_filePath; 179 | float m_stackOffset; 180 | 181 | private: 182 | // Beatmap headers. 183 | bool m_headerGeneral; 184 | bool m_headerEditor; 185 | bool m_headerMetadata; 186 | bool m_headerDifficulty; 187 | bool m_headerTimingPoints; 188 | bool m_headerColours; 189 | bool m_headerHitObjects; 190 | 191 | // General header. 192 | float m_stackLeniency; 193 | UINT m_gameMode; 194 | 195 | // Editor header. 196 | float m_beatDivisor; 197 | 198 | // Metadata header. 199 | std::wstring m_title; 200 | std::wstring m_artist; 201 | std::wstring m_creator; 202 | std::wstring m_version; 203 | UINT m_beatmapID; 204 | 205 | // Difficulty header. 206 | float m_circleSize; 207 | float m_overallDifficulty; 208 | float m_approachRate; 209 | float m_sliderMultiplier; 210 | float m_sliderTickRate; 211 | 212 | // Timingpoints header. 213 | std::vector m_timingPoints; 214 | 215 | // HitObjects header. 216 | std::vector m_hitObjects; 217 | }; 218 | 219 | 220 | // Usefull functions with vec2f objects. 221 | 222 | // calculates the binomial coefficient. 223 | inline double BinomialCoefficient(const UINT& n, const UINT& i) { 224 | if (i < 0U || i > n) { 225 | return 0.0; 226 | } 227 | else if (i == 0U || i == n) { 228 | return 1.0; 229 | } 230 | 231 | double c = 1.0; 232 | for (UINT u = 0U; u < min(i, n - i); u++) { 233 | c = c * static_cast(n - u) / static_cast(u + 1U); 234 | } 235 | 236 | return c; 237 | } 238 | 239 | // Calculates the Bernstein using a binomial coefficient. 240 | inline double Bernstein(const UINT& i, const UINT& n, const double& t) { 241 | return BinomialCoefficient(n, i) * pow(t, static_cast(i)) * pow(1.0 - t, static_cast(n) - static_cast(i)); 242 | } 243 | 244 | // Returns the point on a bezier curve 245 | // with time 0 - 1. 246 | inline vec2f GetPointOnBezier( 247 | _In_ const std::vector& bezier, 248 | _In_ const double& time, 249 | _In_opt_ const UINT& repeatCount = 0U 250 | ) { 251 | vec2f point(0.f, 0.f); 252 | UINT pointsSize = (UINT)bezier.size() - 1U; 253 | 254 | // Calculate the point using a berstein calculation. 255 | for (UINT i = 0U; i <= pointsSize; i++) { 256 | // Translate the point for each segment. 257 | double b = Bernstein(i, pointsSize, time); 258 | point.Add(bezier.at(i + (repeatCount * pointsSize)).Copy().Mult(static_cast(b))); 259 | } 260 | 261 | return point; 262 | } 263 | 264 | // Returns the point on a circle 265 | // with angle in radians. 266 | inline vec2f GetPointOnCircle( 267 | _In_ const vec2f& center, 268 | _In_ const float& radius, 269 | _In_ const float& angle 270 | ) { 271 | vec2f point; 272 | // Calculate the point on a circle. 273 | // Using unit circle calculus. 274 | point.X = radius * cosf(angle); 275 | point.Y = radius * sinf(angle); 276 | 277 | // Translate the point with the center vec2f. 278 | point.Add(center); 279 | 280 | return point; 281 | } 282 | 283 | // Returns the intersecting point bewteen the vec2f objects. 284 | inline vec2f Intersect( 285 | _In_ const vec2f& vec1, 286 | _In_ const vec2f& vec2, 287 | _In_ const vec2f& vec3, 288 | _In_ const vec2f& vec4 289 | ) { 290 | float d = vec4.X * vec2.Y - vec4.Y * vec2.X; 291 | if (fabsf(d) < 0.00001f) { 292 | // D is too small. 293 | // This means that the vec2f objects can be called parallel. 294 | // TODO: Thow error if needed. 295 | 296 | // The function will continue, but might give an unexpected result. 297 | } 298 | 299 | float u = ((vec3.Y - vec1.Y) * vec2.X + (vec1.X - vec3.X) * vec2.Y) / d; 300 | 301 | return vec3.Copy().Add(vec4.Copy().Mult(u)); 302 | } 303 | 304 | // Check if the mid angle is inside the start and end angles. 305 | inline bool IsInside( 306 | _In_ const float& startAngle, 307 | _In_ const float& midAngle, 308 | _In_ const float& endAngle 309 | ) { 310 | return (midAngle > startAngle && midAngle < endAngle) || (midAngle < startAngle && midAngle > endAngle); 311 | } 312 | 313 | // Check if both segments (points at same index) are exactly equal. 314 | inline bool operator== (const Segment& lhs, const Segment& rhs) { 315 | if (lhs.m_points.size() != rhs.m_points.size()) { 316 | // The segments have different sizes. 317 | return FALSE; 318 | } 319 | for (UINT i = 0U; i < lhs.m_points.size(); i++) { 320 | if (lhs.m_points.at(i) != rhs.m_points.at(i)) { 321 | // The points in the segment are not equal. 322 | return FALSE; 323 | } 324 | } 325 | 326 | // All points (at same index) in the segments are equal. 327 | return TRUE; 328 | } 329 | 330 | // Check if both hitObjects are the exact same. 331 | inline bool operator== (const HitObject& lhs, const HitObject& rhs) { 332 | // If any important data mismatches, return FALSE. 333 | if (lhs.GetEndTime() != rhs.GetEndTime()) { 334 | return FALSE; 335 | } 336 | if (lhs.GetEndPosition() != rhs.GetEndPosition()) { 337 | return FALSE; 338 | } 339 | if (lhs.GetObjectType() != rhs.GetObjectType()) { 340 | return FALSE; 341 | } 342 | if (lhs.GetStartPosition() != rhs.GetStartPosition()) { 343 | return FALSE; 344 | } 345 | if (lhs.GetStartTime() != rhs.GetStartTime()) { 346 | return FALSE; 347 | } 348 | 349 | // If no data mismatched, return TRUE. 350 | return TRUE; 351 | } 352 | } 353 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/MovementModes.cpp: -------------------------------------------------------------------------------- 1 | // MovementModes.h : Defines the movement functions of the cursor for OsuBot. 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | 10 | using namespace OsuBot; 11 | 12 | 13 | 14 | // Movement function to move to the next object. 15 | void MovementModes::MoveToObject(Bot* bot, vec2f(MovementModes::*callback)(const UINT&)) { 16 | // Calculate the bezier pts if needed. 17 | if (m_bezierPts.size() == 0U) { 18 | // Retrive local pointers to 19 | // object before last (object that came before the last one), 20 | // previous (object that just ended), 21 | // current (object to move to), 22 | // next (object that comes after this move) 23 | // hitobjects. 24 | const BeatmapInfo::Beatmap* beatmap = bot->GetBeatmapAtIndex(bot->m_selectedBeatmapIndex); 25 | const BeatmapInfo::HitObject* objectBeforeLast = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex - 2U); 26 | const BeatmapInfo::HitObject* previousObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex - 1U); 27 | const BeatmapInfo::HitObject* currentObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex); 28 | const BeatmapInfo::HitObject* nextObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex + 1U); 29 | 30 | 31 | // Calculte the previous, begin, end and next points. 32 | if (bot->m_hitObjectIndex == 0U) { 33 | // Get the current cursor position. 34 | GetCursorPos(&bot->m_cursorPosition); 35 | 36 | m_previousPoint = m_beginPoint = m_backupPoint = vec2f(static_cast(bot->m_cursorPosition.x), static_cast(bot->m_cursorPosition.y)); 37 | } 38 | else { 39 | // Calculate the previous point. 40 | if (previousObject->GetObjectType() == HITOBJECT_SLIDER) { 41 | double previousPointTime = ((DOUBLE)previousObject->GetSliderTickCount() - 1.0) / (DOUBLE)previousObject->GetSliderTickCount(); 42 | previousPointTime = previousObject->GetSliderRepeatCount() % 2 == 0 ? 1.0 - previousPointTime : previousPointTime; 43 | m_previousPoint = previousObject->GetPointByT(previousPointTime); 44 | m_previousPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), previousObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 45 | } 46 | else if (bot->m_hitObjectIndex != 1U) { 47 | // The previous point is the object before the last object. 48 | m_previousPoint = objectBeforeLast->GetEndPosition(); 49 | m_previousPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), objectBeforeLast->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 50 | } 51 | 52 | // The end point of the previous move (current cursor position) becomes the begin point of this move. 53 | GetCursorPos(&bot->m_cursorPosition); 54 | m_beginPoint = vec2f(static_cast(bot->m_cursorPosition.x), static_cast(bot->m_cursorPosition.y)); 55 | 56 | //m_beginPoint = previousObject->GetEndPosition(); 57 | //m_beginPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), previousObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 58 | } 59 | 60 | m_endPoint = currentObject->GetStartPosition(); 61 | m_endPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), currentObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 62 | 63 | if (currentObject->GetObjectType() == HITOBJECT_SLIDER) { 64 | double nextPointTime = 1.0 / max(1.0, (FLOAT)currentObject->GetSliderTickCount()); 65 | m_nextPoint = currentObject->GetPointByT(nextPointTime); 66 | m_nextPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), currentObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 67 | } 68 | else { 69 | m_nextPoint = nextObject->GetStartPosition(); 70 | m_nextPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), nextObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 71 | } 72 | 73 | 74 | m_interpolateTime = TRUE; 75 | 76 | // Calculate the control point(s). 77 | m_controlPoint0 = m_beginPoint.Copy().Sub(m_backupPoint).Add(m_beginPoint); 78 | 79 | if (m_endPoint.Copy().Sub(m_beginPoint).Length() < (1.f / beatmap->GetCircleSize() * 400.f)) { 80 | m_controlPoint1 = ControlPointFlowing(1U); 81 | m_controlPoint0 = m_beginPoint.Copy().Sub(m_backupPoint).Normalize().Mult(m_endPoint.Copy().Sub(m_beginPoint).Length() / 2.f).Add(m_beginPoint); 82 | 83 | m_interpolateTime = FALSE; 84 | } 85 | else { 86 | m_controlPoint1 = (bot->*callback)(1U); 87 | } 88 | 89 | // Overwrite controlPoints for a linear move. 90 | if (m_movementModeCircle == MODE_STANDARD) { 91 | m_controlPoint0 = (bot->*callback)(0U); 92 | m_controlPoint1 = (bot->*callback)(1U); 93 | } 94 | 95 | // Fill the bezier pts vector. 96 | m_bezierPts = { 97 | m_beginPoint, 98 | m_controlPoint0, 99 | m_controlPoint1, 100 | m_endPoint 101 | }; 102 | 103 | // Save controlPoint1 into backupPoint for next object. 104 | m_backupPoint = m_controlPoint1; 105 | 106 | // Save the current song time to calculate the time delta. 107 | m_savedSongTime = bot->GetSongTime(); 108 | m_startTime = currentObject->GetStartTime(); 109 | } 110 | 111 | // Calculate the time (0.0 - 1.0) until current object should be hit. 112 | double deltaTime = m_startTime - m_savedSongTime; 113 | double time = (deltaTime - (m_startTime - bot->GetSongTime())) / deltaTime; 114 | 115 | if (m_interpolateTime) { 116 | // Interpolate the time with a hermite curve. 117 | time = HermiteInterpolation(time); 118 | } 119 | 120 | // Clamp the time between 0.0 - 1.0. 121 | time = CLAMP(0.0, time, 1.0); 122 | 123 | 124 | // Get the next point on the bezier curve. 125 | vec2f bezierPoint = BeatmapInfo::GetPointOnBezier(m_bezierPts, time); 126 | vec2f resultPoint = bezierPoint; 127 | vec2f newPoint = bezierPoint; 128 | 129 | vec2f sliderPointCurrent, sliderPointPrevious; 130 | 131 | // Blend the resultPoint with the sliderPoint. 132 | const BeatmapInfo::Beatmap* beatmap = bot->GetBeatmapAtIndex(bot->m_selectedBeatmapIndex); 133 | const BeatmapInfo::HitObject* previousObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex - 1U); 134 | const BeatmapInfo::HitObject* currentObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex); 135 | if (currentObject->GetObjectType() == HITOBJECT_SLIDER && m_interpolateTime) { 136 | // Movement into slider. 137 | sliderPointCurrent = currentObject->GetPointByT(time - 1.0); 138 | sliderPointCurrent.ConvertToWindowSpace(beatmap->GetStackOffset(), currentObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 139 | 140 | // Store sliderPoint in newPoint. 141 | newPoint = sliderPointCurrent; 142 | 143 | // Blend the points into a result. 144 | time = HermiteInterpolation(time, -0.6); 145 | resultPoint = bezierPoint.Copy().Mult(static_cast(1.0 - time)).Add(sliderPointCurrent.Copy().Mult(static_cast(time))); 146 | } 147 | if (previousObject->GetObjectType() == HITOBJECT_SLIDER && m_interpolateTime) { 148 | // Movement out of slider. 149 | sliderPointPrevious = previousObject->GetPointByT(time + 1.0); 150 | sliderPointPrevious.ConvertToWindowSpace(beatmap->GetStackOffset(), previousObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 151 | 152 | // Blend the points into a result. 153 | // NOTICE: Use newPoint instead of bezierPoint, so that movement out of slider can blend with movement into slider. 154 | time = HermiteInterpolation(time, 0.6); 155 | resultPoint = sliderPointPrevious.Copy().Mult(static_cast(1.0 - time)).Add(newPoint.Copy().Mult(static_cast(time))); 156 | } 157 | 158 | // Set the cursor to the result point. 159 | SetCursorPos(static_cast(resultPoint.X), static_cast(resultPoint.Y)); 160 | } 161 | 162 | // Movement function to move along a slider. 163 | void MovementModes::MovementSlider(Bot* bot, vec2f(MovementModes::*callback)(const UINT&)) { 164 | // NOTICE: Movement modes not yet implemented! 165 | UNREFERENCED_PARAMETER(callback); 166 | 167 | // Execute different code for standard slider mode. 168 | if (m_movementModeSlider == MODE_STANDARD) { 169 | // Retrive local pointers to the current object (slider). 170 | const BeatmapInfo::Beatmap* beatmap = bot->GetBeatmapAtIndex(bot->m_selectedBeatmapIndex); 171 | const BeatmapInfo::HitObject* currentObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex); 172 | 173 | // Calculate the time (0.0 - 1.0), slider repeats are handled after. 174 | double time = (bot->GetSongTime() - currentObject->GetStartTime()) / currentObject->GetSliderTime(); 175 | time = CLAMP(0.0, time, (DOUBLE)currentObject->GetSliderRepeatCount()); 176 | time = static_cast(floor(time)) % 2 == 0 ? time - floor(time) : floor(time) + 1.0 - time; 177 | time = CLAMP(0.0, time, 1.0); 178 | 179 | // Calculate the next point on the slider. 180 | vec2f resultPoint = currentObject->GetPointByT(time); 181 | resultPoint.ConvertToWindowSpace(beatmap->GetStackOffset(), currentObject->GetStackIndex(), bot->GetMultiplier(), bot->GetOffset()); 182 | 183 | // Setthe cursor to the correct point on the slider body. 184 | SetCursorPos(static_cast(resultPoint.X), static_cast(resultPoint.Y)); 185 | } 186 | else { 187 | // Calculate the slider point if needed. 188 | //if () { 189 | // // Retrive local pointers to 190 | // // object before last (object that came before the last one), 191 | // // previous (object that just ended), 192 | // // current (object to move to), 193 | // // next (object that comes after this move) 194 | // // hitobjects. 195 | // const BeatmapInfo::Beatmap* beatmap = bot->GetBeatmapAtIndex(bot->m_selectedBeatmapIndex); 196 | // const BeatmapInfo::HitObject* objectBeforeLast = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex - 2U); 197 | // const BeatmapInfo::HitObject* previousObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex - 1U); 198 | // const BeatmapInfo::HitObject* currentObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex); 199 | // const BeatmapInfo::HitObject* nextObject = beatmap->GetHitObjectAtIndex(bot->m_hitObjectIndex + 1U); 200 | //} 201 | } 202 | } 203 | 204 | // Movement function to spin the spinners. 205 | void MovementModes::MovementSpinner(Bot* bot, vec2f(MovementModes::*callback)(const UINT&)) { 206 | // NOTICE: Movement modes not yet implemented! 207 | UNREFERENCED_PARAMETER(callback); 208 | 209 | // Calculate the spinner center if needed. 210 | if (m_spinnerCenter == vec2f()) { 211 | const BeatmapInfo::HitObject* currentObject = bot->GetBeatmapAtIndex(bot->m_selectedBeatmapIndex)->GetHitObjectAtIndex(bot->m_hitObjectIndex); 212 | 213 | // Set the radius with the beatmap circle size. 214 | m_currentRadius = m_spinnerRadius * (1.f / bot->GetBeatmapAtIndex(bot->m_selectedBeatmapIndex)->GetCircleSize()); 215 | 216 | m_spinnerCenter = currentObject->GetStartPosition(); 217 | m_spinnerCenter.Add(0.f, 6.f); // Offset the center the back from the global offset (POINT w = { 0, 6 } @ CheckGameActive() in OsuBot.cpp). 218 | m_spinnerCenter.ConvertToWindowSpace(0.f, 0U, bot->GetMultiplier(), bot->GetOffset()); 219 | } 220 | 221 | // Calculate the next rotation point in the spinner. 222 | vec2f resultPoint = vec2f(cosf(m_currentAngle) * m_currentRadius, sinf(m_currentAngle) * m_currentRadius); 223 | resultPoint.Add(m_spinnerCenter); 224 | 225 | // Spin the spinner. 226 | SetCursorPos(static_cast(resultPoint.X), static_cast(resultPoint.Y)); 227 | 228 | // Modify the current angle of the spinner. 229 | m_currentAngle += M_PI / -12.f; 230 | } 231 | 232 | 233 | // Returns a control point that follows a linear movement. 234 | vec2f MovementModes::ControlPointStandard(const UINT& index) { 235 | vec2f cp0 = m_beginPoint.Copy().Mult(0.667f).Add(m_endPoint.Copy().Mult(0.334f)); 236 | vec2f cp1 = m_beginPoint.Copy().Mult(0.334f).Add(m_endPoint.Copy().Mult(0.667f)); 237 | 238 | return index ? cp0 : cp1; 239 | } 240 | 241 | // Returns a control point that follows a flowing movement. 242 | vec2f MovementModes::ControlPointFlowing(const UINT& index) { 243 | UNREFERENCED_PARAMETER(index); 244 | 245 | vec2f d0 = m_previousPoint.Copy().Sub(m_beginPoint); 246 | vec2f d1 = m_beginPoint.Copy().Sub(m_endPoint); 247 | vec2f d2 = m_endPoint.Copy().Sub(m_nextPoint); 248 | 249 | float l0 = d0.Length(); 250 | float l1 = d1.Length(); 251 | float l2 = d2.Length(); 252 | 253 | vec2f m0 = m_previousPoint.MidPoint(m_beginPoint); 254 | vec2f m1 = m_beginPoint.MidPoint(m_endPoint); 255 | vec2f m2 = m_endPoint.MidPoint(m_nextPoint); 256 | 257 | float amplifier0 = (atan2f(l2 / 480.f, 1.85f * (l2 / 960.f)) / ((40000.f / 1.f) / l1)) + 1.f; 258 | float amplifier1 = (atan2f(l1 / 480.f, 1.85f * (l1 / 960.f)) / ((40000.f / 1.f) / l1)) + 1.f; 259 | 260 | vec2f cp0 = m1 + (m_beginPoint - (m1 + (m0 - m1) * ((l1 * amplifier1) / (l0 + l1)))); 261 | vec2f cp1 = m0 + (m_beginPoint - (m1 + (m0 - m1) * ((l1 * amplifier1) / (l0 + l1)))); 262 | vec2f cp2 = m2 + (m_endPoint - (m2 + (m1 - m2) * ((l2 * amplifier0) / (l1 + l2)))); 263 | vec2f cp3 = m1 + (m_endPoint - (m2 + (m1 - m2) * ((l2 * amplifier0) / (l1 + l2)))); 264 | 265 | // We only need cp3 for controlPoint1 for the bezier curve (for now). 266 | return cp3; 267 | } 268 | 269 | // Returns a control point that follows an movement that looks to be able to predict the next movement. 270 | vec2f MovementModes::ControlPointPredicting(const UINT& index) { 271 | UNREFERENCED_PARAMETER(index); 272 | 273 | // Big complicated calculation that cannot be explaned. 274 | // As it was made with mostly trial and error (what looked good/bad). 275 | // And I also forgot why I did these steps :stuck_out_tongue_winking_eye: 276 | return m_nextPoint.MidPoint(m_endPoint).Sub(m_nextPoint).Mult(m_beginPoint.Copy().Sub(m_endPoint).Length() / (860.f / 1.f)).Add(m_endPoint).MidPoint(m_controlPoint0); 277 | } 278 | 279 | 280 | // Interpolates a double with a hermite curve. 281 | double MovementModes::HermiteInterpolation(const double& _X, const double& bias) { 282 | // TODO: Change thse 6 variables to customize the hermite curve. 283 | double _Y0 = 0.1; // In target 284 | double _Y1 = 0.0; // Start 285 | double _Y2 = 1.0; // End 286 | double _Y3 = 1.1; // Out target 287 | 288 | double tension = -0.2; // 1 : high, 0 : normal, -1 : low 289 | //double bias = 0.3; // >0 : first segment, 0 : mid, <0 : next segment 290 | 291 | 292 | double a0, a1, a2, a3; 293 | double m0, m1; 294 | 295 | double _X2 = _X * _X; 296 | double _X3 = _X2 * _X; 297 | 298 | m0 = (_Y1 - _Y0) * (1.0 + bias) * (1.0 - tension) / 2.0; 299 | m0 += (_Y2 - _Y1) * (1.0 - bias) * (1.0 - tension) / 2.0; 300 | m1 = (_Y2 - _Y1) * (1.0 + bias) * (1.0 - tension) / 2.0; 301 | m1 += (_Y3 - _Y2) * (1.0 - bias) * (1.0 - tension) / 2.0; 302 | 303 | a0 = 2.0 * _X3 - 3.0 * _X2 + 1.0; 304 | a1 = _X3 - 2.0 * _X2 + _X; 305 | a2 = _X3 - _X2; 306 | a3 = -2.0 * _X3 + 3.0 * _X2; 307 | 308 | // Calculate the resulting value on the hermite curve. 309 | double _Y = (a0 * _Y1 + a1 * m0 + a2 * m1 + a3 * _Y2); 310 | 311 | return CLAMP(0.0, _Y, 1.0); 312 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/MovementModes.h: -------------------------------------------------------------------------------- 1 | // MovementModes.h : Declares a class that holds everything that 2 | // involves the movement of the cursor for OsuBot. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | 9 | namespace OsuBot 10 | { 11 | // Forward declare the bot class. 12 | class Bot; 13 | 14 | class MovementModes { 15 | public: 16 | // Base movement functions. 17 | void MoveToObject(Bot* bot, vec2f (MovementModes::*callback)(const UINT&)); 18 | void MovementSlider(Bot* bot, vec2f(MovementModes::*callback)(const UINT&)); 19 | void MovementSpinner(Bot* bot, vec2f(MovementModes::*callback)(const UINT&)); 20 | 21 | // TODO: Add movement variant calculation functions here. 22 | vec2f ControlPointStandard(const UINT& index); 23 | vec2f ControlPointFlowing(const UINT& index); 24 | vec2f ControlPointPredicting(const UINT& index); 25 | 26 | // Hermite interpolation function. 27 | double HermiteInterpolation(const double& time, const double& bias = 0.3); 28 | 29 | public: 30 | // Member variables. 31 | BYTE m_movementModeCircle; 32 | BYTE m_movementModeSlider; 33 | BYTE m_movementModeSpinner; 34 | float m_spinnerRadius; 35 | private: 36 | bool m_interpolateTime; 37 | double m_savedSongTime; 38 | double m_startTime; 39 | float m_currentRadius; 40 | float m_currentAngle; 41 | 42 | // Frequently used vec2f objects for calculations. 43 | vec2f m_beginPoint; 44 | vec2f m_endPoint; 45 | vec2f m_controlPoint0; 46 | vec2f m_controlPoint1; 47 | vec2f m_previousPoint; 48 | vec2f m_nextPoint; 49 | vec2f m_spinnerCenter; 50 | public: 51 | vec2f m_backupPoint; 52 | 53 | public: 54 | // Bezier pts vector. 55 | std::vector m_bezierPts; 56 | std::vector m_sliderPoints; 57 | }; 58 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/SigScan.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | 10 | using namespace OsuBot::SigScan; 11 | 12 | 13 | // Get the process handle and ID with a process name. 14 | bool SigScanner::GetProcess(_In_ std::wstring processName) { 15 | // Get a handle of a process. 16 | HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 17 | 18 | PROCESSENTRY32W processEntry; 19 | processEntry.dwSize = sizeof(PROCESSENTRY32W); 20 | 21 | // Find the process name with a PROCESSENTRY32W object. 22 | do { 23 | if (processEntry.szExeFile == processName) { 24 | // Process name found, set the variables of the SigScanner. 25 | m_targetID = processEntry.th32ProcessID; 26 | m_targetProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_targetID); 27 | 28 | // Close the handle and return TRUE. 29 | CloseHandle(hProcess); 30 | return TRUE; 31 | } 32 | } while (Process32NextW(hProcess, &processEntry)); 33 | 34 | // No process found with the same name. 35 | // Close the handle and return FALSE. 36 | CloseHandle(hProcess); 37 | return FALSE; 38 | } 39 | 40 | // Fills the m_targetRegion struct. 41 | void SigScanner::GetRegion(_In_opt_ const UINT& startAddress) { 42 | MEMORY_BASIC_INFORMATION mbi; 43 | LPVOID address = NULL; 44 | 45 | // If given set the address to the argument. 46 | if (startAddress != NULL) { 47 | address = reinterpret_cast(startAddress); 48 | } 49 | 50 | // Find a memory region with Commit state. 51 | do { 52 | // Get a region in the target process. 53 | VirtualQueryEx(m_targetProcess, address, &mbi, sizeof(mbi)); 54 | 55 | // Fill the region struct. 56 | m_targetRegion.dwBase = reinterpret_cast(mbi.BaseAddress); 57 | m_targetRegion.dwSize = (DWORD)mbi.RegionSize; 58 | 59 | // Set the address to the next region. 60 | address = reinterpret_cast(m_targetRegion.dwBase + m_targetRegion.dwSize); 61 | } while (mbi.State != MEM_COMMIT || mbi.Protect != PAGE_EXECUTE_READWRITE); 62 | 63 | 64 | //// Get the module base (entry point). 65 | //MODULEINFO mInfo; 66 | //if (K32GetModuleInformation(m_targetProcess, nullptr, &mInfo, sizeof(mInfo))) { 67 | // // Info get, set the base address. 68 | // m_targetModule.dwBase = reinterpret_cast(mInfo.EntryPoint); 69 | //} 70 | //else { 71 | // // When not found fill with NULL. 72 | // m_targetModule.dwBase = NULL; 73 | //} 74 | // 75 | //// Get the module size (working set size). 76 | //PROCESS_MEMORY_COUNTERS pmcInfo; 77 | //if (K32GetProcessMemoryInfo(m_targetProcess, &pmcInfo, sizeof(pmcInfo))) { 78 | // // Info get, set the module size. 79 | // m_targetModule.dwSize = static_cast(pmcInfo.WorkingSetSize); 80 | //} 81 | //else { 82 | // // When not found fill with NULL. 83 | // m_targetModule.dwSize = NULL; 84 | //} 85 | } 86 | 87 | 88 | // Finding the signature and returns the address in the memory. 89 | void SigScanner::FindSignature( 90 | _In_ const std::wstring* signatureString 91 | ) { 92 | // Spilt the sig string into tokens. 93 | std::vector tokens = SplitString(*signatureString, L"\\"); 94 | 95 | // Prepair the tokens for conversion. 96 | if (tokens.front() == L"\\") tokens.at(0) = tokens.begin()->substr(1); 97 | if (tokens.back() == L"\n") tokens.end()->pop_back(); 98 | 99 | 100 | // Make BYTE array. 101 | std::vector signature; 102 | std::wstringstream ss; 103 | for (auto set : tokens) { 104 | ss << std::hex << set; 105 | UINT x; ss >> x; 106 | 107 | signature.push_back((BYTE)x); 108 | 109 | ss.clear(); 110 | } 111 | 112 | 113 | const DWORD mult = 4096UL; 114 | BYTE data[mult]; 115 | DWORD startAddress; 116 | DWORD endAddress = NULL; // This is set the NULL, for the first memory region. 117 | bool hit = TRUE; 118 | 119 | // Iterate trough memory regions. 120 | do { 121 | // Get the start and end addresses. 122 | GetRegion(endAddress); 123 | 124 | // Set the start and end addresses. 125 | startAddress = m_targetRegion.dwBase; 126 | endAddress = m_targetRegion.dwBase + m_targetRegion.dwSize; 127 | 128 | // Search the memory region. 129 | for (DWORD i = startAddress; i < endAddress; i += mult) { 130 | // Read the data in. 131 | ReadProcessMemory(m_targetProcess, reinterpret_cast(i), &data, mult, nullptr); 132 | for (DWORD a = 0UL; a < mult; a++) { 133 | hit = TRUE; 134 | 135 | // Search the data block for the signature. 136 | for (DWORD j = 0UL; j < (DWORD)signature.size() && hit; j++) { 137 | // Check if current byte is a wildcard. 138 | if (signature[j] != 0x00) { 139 | // Check for mis matching bytes. 140 | if (data[a + j] != signature[j]) { 141 | // Bytes are not equal. 142 | hit = FALSE; 143 | } 144 | } 145 | } 146 | if (hit) { 147 | // Signature found, set the result and exit function. 148 | m_resultAddress = i + a; 149 | m_sigFound = TRUE; 150 | return; 151 | } 152 | } 153 | } 154 | } while (!hit); 155 | 156 | // Signature not found. 157 | m_sigFound = FALSE; 158 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/SigScan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace OsuBot 4 | { 5 | namespace SigScan 6 | { 7 | struct MODULE { 8 | DWORD dwBase; 9 | DWORD dwSize; 10 | }; 11 | 12 | class SigScanner { 13 | public: 14 | // Member functions. 15 | bool GetProcess(_In_ std::wstring processName); 16 | void GetRegion(_In_opt_ const UINT& startAddress = NULL); 17 | 18 | // Signature functions. 19 | void FindSignature( 20 | _In_ const std::wstring* signature 21 | ); 22 | 23 | // Accessor functions. 24 | public: 25 | HANDLE GetTargetProcessHandle() const { return m_targetProcess; } 26 | DWORD GetResultAddress() const { return m_resultAddress; } 27 | DWORD GetTargetProcessID() const { return m_targetID; } 28 | bool SigFound() const { return m_sigFound; } 29 | 30 | 31 | // Member variables. 32 | private: 33 | MODULE m_targetRegion; 34 | HANDLE m_targetProcess; 35 | DWORD m_targetID; 36 | DWORD m_resultAddress; 37 | bool m_sigFound; 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/OsuBot/SongsSelection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | 10 | using namespace OsuBot; 11 | 12 | 13 | // Gets the currently playing song name and difficulty from the game title. 14 | void Bot::GetCurrentSong() { 15 | UINT bracketCount = 0U; 16 | UINT hit = NULL; 17 | 18 | // Get the version name from the game title. 19 | for (UINT i = (UINT)m_gameTitle.size(); i-- > 0U;) { 20 | if (m_gameTitle.at(i) == L']') { 21 | // Increase the bracket count by 1. 22 | bracketCount++; 23 | 24 | // Mark the bracket index. (closing bracket) 25 | if (hit == NULL) { 26 | hit = i; 27 | } 28 | } 29 | else if (m_gameTitle.at(i) == L'[') { 30 | // Subtract 1 from the bracket count. 31 | bracketCount--; 32 | } 33 | 34 | // Check if the amount of opening brackets equal the closing brackets. 35 | if (bracketCount == 0U && hit != NULL) { 36 | // Set vesion string without the outer brackets. 37 | m_currentSongVersion = m_gameTitle.substr(i + 1U, hit - i - 1U); 38 | break; 39 | } 40 | } 41 | 42 | 43 | // Get the beatmap name from the game title to the index of version. 44 | // Beatmap name has format "{artistName} - {songTitle}" (without the parentheses). 45 | m_currentSongName = m_gameTitle.substr(8U, m_gameTitle.size() - m_currentSongVersion.size() - 11U); 46 | 47 | // Pop the last character back, if it is a space. 48 | if (m_currentSongName.back() == L' ') { 49 | m_currentSongName.pop_back(); 50 | } 51 | } 52 | 53 | 54 | // Get the osu songs folder path. 55 | void Bot::GetSongsFolderPath() { 56 | // Get the osu folder path. 57 | std::wstring path = GetOsuFolderPath(); 58 | 59 | // Truncate the "osu!.exe" part. 60 | path = path.substr(0U, path.find_last_of(L'\\') + 1U); 61 | 62 | // Append "Songs". 63 | path.append(L"Songs"); 64 | 65 | // Assign the songs folder path. 66 | m_songsFolderPath = path; 67 | } 68 | 69 | // Retrives the full path to osu!.exe. 70 | std::wstring Bot::GetOsuFolderPath() { 71 | // Get a handle to a module. 72 | HANDLE hModule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, m_sigScanner.GetTargetProcessID()); 73 | 74 | MODULEENTRY32W mEntry; 75 | mEntry.dwSize = sizeof(mEntry); 76 | 77 | // Find the process name with a MODULEENTRY32W object. 78 | do { 79 | if (std::wstring(mEntry.szModule) == L"osu!.exe") { 80 | // Name found, close the handle and return path. 81 | CloseHandle(hModule); 82 | return mEntry.szExePath; 83 | } 84 | } while (Module32NextW(hModule, &mEntry)); 85 | 86 | // No module found with the name. 87 | // Close the handle and return empty string. 88 | CloseHandle(hModule); 89 | return NULL; 90 | } 91 | 92 | 93 | // This function querries the user to select a beatmap to append to the beatmap queue. 94 | std::wstring Bot::GetSongFromFolderPath() { 95 | std::wstring result; 96 | IFileOpenDialog* pFileDialog; 97 | 98 | // Create the FileOpenDialog object. 99 | HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileDialog)); 100 | 101 | if (SUCCEEDED(hr)) { 102 | DWORD dwOptions; 103 | std::vector filterSpec = { 104 | { L"osu! beatmap", L"*.osu" } 105 | }; 106 | 107 | // Force the folder to be set to the songs folder. 108 | IShellItem* pSongsFolder; 109 | SHCreateItemFromParsingName(m_songsFolderPath.data(), NULL, IID_PPV_ARGS(&pSongsFolder)); 110 | 111 | // Get the options. 112 | pFileDialog->GetOptions(&dwOptions); 113 | 114 | // Configure the options. 115 | // Only allow filtered files to be selected. 116 | pFileDialog->SetFolder(pSongsFolder); 117 | pFileDialog->SetOptions(dwOptions | FOS_STRICTFILETYPES); 118 | pFileDialog->SetFileTypes((UINT)filterSpec.size(), filterSpec.data()); 119 | 120 | // Show the dialog. 121 | hr = pFileDialog->Show(NULL); 122 | 123 | if (SUCCEEDED(hr)) { 124 | IShellItem* pItem; 125 | 126 | // Get the result. 127 | hr = pFileDialog->GetResult(&pItem); 128 | 129 | if (SUCCEEDED(hr)) { 130 | LPWSTR lpName; 131 | 132 | // Get the file name from the result. 133 | hr = pItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &lpName); 134 | 135 | // Store the display name in the result string. 136 | if (SUCCEEDED(hr)) { 137 | result = lpName; 138 | } 139 | 140 | // Function is done, Release the resources. 141 | CoTaskMemFree(lpName); 142 | pItem->Release(); 143 | } 144 | pSongsFolder->Release(); 145 | } 146 | pFileDialog->Release(); 147 | } 148 | 149 | return result; 150 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-Black.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-ExtraLight.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-Light.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Font/SourceCodePro-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Font/SourceCodePro-Semibold.ttf -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Icons/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Icons/Icon.ico -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Icons/IconSm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Icons/IconSm.ico -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Resource.rc 4 | // 5 | 6 | constexpr auto HITOBJECT_CIRCLE = 1; 7 | constexpr auto HITOBJECT_SLIDER = 2; 8 | constexpr auto HITOBJECT_SPINNER = 8; 9 | 10 | constexpr auto MODE_NONE = 0; 11 | constexpr auto MODE_STANDARD = 1; 12 | constexpr auto MODE_FLOWING = 2; 13 | constexpr auto MODE_PREDICTING = 3; 14 | 15 | constexpr auto M_PI = 3.14159265358979323846f; 16 | constexpr auto M_2PI = 6.28318530717958647693f; 17 | 18 | constexpr auto MAX_LOADSTRING = 100; 19 | constexpr auto MAX_READSTRING = 255; 20 | 21 | #define CLAMP(_min, _x, _max) _min > _x ? _min : _x < _max ? _x : _max 22 | 23 | #define IDS_TITLE 1 24 | #define IDC_OSUBOT_V3 1001 25 | 26 | #define IDI_OSUBOT_V3 501 27 | #define IDI_SMALL 502 28 | 29 | // Next default values for new objects 30 | // 31 | #ifdef APSTUDIO_INVOKED 32 | #ifndef APSTUDIO_READONLY_SYMBOLS 33 | #define _APS_NEXT_RESOURCE_VALUE 104 34 | #define _APS_NEXT_COMMAND_VALUE 40001 35 | #define _APS_NEXT_CONTROL_VALUE 1001 36 | #define _APS_NEXT_SYMED_VALUE 101 37 | #endif 38 | #endif 39 | -------------------------------------------------------------------------------- /Osu!Bot V3/Content/Resources/Resource.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Osu!Bot V3/Content/Resources/Resource.rc -------------------------------------------------------------------------------- /Osu!Bot V3/Content/UI Elements/StaticText.cpp: -------------------------------------------------------------------------------- 1 | // StaticText.cpp : Defines everything about the StaticText class. 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | using namespace OsuBot::UIElements; 9 | 10 | 11 | StaticText::StaticText( 12 | _In_ const std::shared_ptr& deviceResources, 13 | _In_opt_ BYTE alpha, 14 | _In_opt_ D2D1::ColorF::Enum color, 15 | _In_opt_ DX::Size maxSize, 16 | _In_opt_ float fontSize, 17 | _In_opt_ std::wstring text, 18 | _In_opt_ bool background, 19 | _In_opt_ RECT backgroundRect, 20 | _In_opt_ D2D1::ColorF::Enum backgroundColor, 21 | _In_opt_ DWRITE_TEXT_ALIGNMENT textAlignment, 22 | _In_opt_ std::wstring fontName, 23 | _In_opt_ std::wstring localeName 24 | ) : 25 | m_deviceResources(deviceResources), 26 | m_maxSize(maxSize), 27 | m_screenTranslation(D2D1::Matrix3x2F::Identity()), 28 | m_text(text), 29 | m_background(background), 30 | m_backgroundRect(backgroundRect), 31 | m_textAlignment(textAlignment) 32 | { 33 | ZeroMemory(&m_textMetrics, sizeof(DWRITE_TEXT_METRICS)); 34 | 35 | // Create device independent resources. 36 | Microsoft::WRL::ComPtr textFormat; 37 | ThrowIfFailed( 38 | m_deviceResources->GetDWriteFactory()->CreateTextFormat( 39 | fontName.c_str(), 40 | nullptr, 41 | DWRITE_FONT_WEIGHT_LIGHT, 42 | DWRITE_FONT_STYLE_NORMAL, 43 | DWRITE_FONT_STRETCH_NORMAL, 44 | fontSize, 45 | localeName.c_str(), 46 | &textFormat 47 | ) 48 | ); 49 | 50 | ThrowIfFailed( 51 | textFormat.As(&m_textFormat) 52 | ); 53 | 54 | ThrowIfFailed( 55 | m_textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR) 56 | ); 57 | 58 | ThrowIfFailed( 59 | m_deviceResources->GetD2DFactory()->CreateDrawingStateBlock(&m_stateBlock) 60 | ); 61 | 62 | CreateDeviceDependentResources(alpha, color, backgroundColor); 63 | } 64 | 65 | 66 | // Change the translation of the text. 67 | void StaticText::SetTranslation(DX::Size translation) { 68 | m_screenTranslation = D2D1::Matrix3x2F::Translation( 69 | translation.Width, 70 | translation.Height 71 | ); 72 | } 73 | 74 | // Update the static text with new parameters. 75 | void StaticText::Update( 76 | std::wstring newText 77 | ) { 78 | // Update displayed text. 79 | if (!newText.empty()) { 80 | m_text = newText; 81 | 82 | Microsoft::WRL::ComPtr textLayout; 83 | ThrowIfFailed( 84 | m_deviceResources->GetDWriteFactory()->CreateTextLayout( 85 | m_text.c_str(), 86 | (uint32_t)m_text.length(), 87 | m_textFormat.Get(), 88 | m_maxSize.Width, 89 | m_maxSize.Height, 90 | &textLayout 91 | ) 92 | ); 93 | 94 | ThrowIfFailed( 95 | textLayout.As(&m_textLayout) 96 | ); 97 | 98 | ThrowIfFailed( 99 | m_textLayout->GetMetrics(&m_textMetrics) 100 | ); 101 | } 102 | } 103 | 104 | // Draw the text to the display. 105 | void StaticText::Draw() { 106 | auto renderTarget = m_deviceResources->GetD2DRenderTarget(); 107 | if (renderTarget == nullptr) return; 108 | 109 | renderTarget->SaveDrawingState(m_stateBlock.Get()); 110 | 111 | renderTarget->SetTransform(m_screenTranslation * m_deviceResources->GetOrientationTransform2D()); 112 | 113 | ThrowIfFailed( 114 | m_textFormat->SetTextAlignment(m_textAlignment) 115 | ); 116 | 117 | if (m_textLayout.Get() && m_textBrush.Get()) { 118 | if (m_background) { 119 | renderTarget->FillRectangle( 120 | D2D1::RectF( 121 | (FLOAT)m_backgroundRect.left, 122 | (FLOAT)m_backgroundRect.top, 123 | (FLOAT)m_backgroundRect.right, 124 | (FLOAT)m_backgroundRect.bottom 125 | ), 126 | m_backgroundBrush.Get() 127 | ); 128 | } 129 | 130 | renderTarget->DrawTextLayout( 131 | D2D1::Point2F(0.f, 0.f), 132 | m_textLayout.Get(), 133 | m_textBrush.Get() 134 | ); 135 | } 136 | 137 | renderTarget->RestoreDrawingState(m_stateBlock.Get()); 138 | } 139 | 140 | 141 | // Create the device dependent resources for the text. 142 | void StaticText::CreateDeviceDependentResources(BYTE alpha, D2D1::ColorF::Enum color, D2D1::ColorF::Enum backgroundColor) { 143 | ThrowIfFailed( 144 | m_deviceResources->GetD2DRenderTarget()->CreateSolidColorBrush(D2D1::ColorF(color, (FLOAT)alpha / 0xff), &m_textBrush) 145 | ); 146 | 147 | ThrowIfFailed( 148 | m_deviceResources->GetD2DRenderTarget()->CreateSolidColorBrush(D2D1::ColorF(backgroundColor, (FLOAT)alpha / 0xff), &m_backgroundBrush) 149 | ); 150 | } 151 | 152 | // Release the device dependent resoures for the text. 153 | void StaticText::ReleaseDeviceDependentResources() { 154 | m_textBrush.Reset(); 155 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Content/UI Elements/StaticText.h: -------------------------------------------------------------------------------- 1 | // StaticText.h : Declares a UI Element that is just a static text. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | 10 | namespace OsuBot 11 | { 12 | namespace UIElements 13 | { 14 | class StaticText { 15 | public: 16 | StaticText( 17 | _In_ const std::shared_ptr& deviceResources, 18 | _In_opt_ BYTE alpha = 0xff, 19 | _In_opt_ D2D1::ColorF::Enum color = D2D1::ColorF::White, 20 | _In_opt_ DX::Size maxSize = { 120.f, 10.f }, 21 | _In_opt_ float fontSize = 32.f, 22 | _In_opt_ std::wstring text = L"", 23 | _In_opt_ bool background = FALSE, 24 | _In_opt_ RECT backgroundRect = { 0L, 0L, 120L, 10L }, 25 | _In_opt_ D2D1::ColorF::Enum backgroundColor = D2D1::ColorF::Black, 26 | _In_opt_ DWRITE_TEXT_ALIGNMENT textAlignment = DWRITE_TEXT_ALIGNMENT_TRAILING, 27 | _In_opt_ std::wstring fontName = L"Source Code Pro", 28 | _In_opt_ std::wstring localeName = L"en_US" 29 | ); 30 | 31 | void CreateDeviceDependentResources(_In_opt_ BYTE alpha = 0xff, _In_opt_ D2D1::ColorF::Enum color = D2D1::ColorF::White, _In_opt_ D2D1::ColorF::Enum backgroundColor = D2D1::ColorF::Black); 32 | void ReleaseDeviceDependentResources(); 33 | void SetTranslation(DX::Size translation); 34 | void Update(std::wstring newText = NULL); 35 | void Draw(); 36 | 37 | private: 38 | // Cached pointer to device resources. 39 | std::shared_ptr m_deviceResources; 40 | 41 | // Resources related to text rendering. 42 | bool m_background; 43 | float m_fontSize; 44 | std::wstring m_text; 45 | std::wstring m_fontName; 46 | std::wstring m_localeName; 47 | DWRITE_TEXT_METRICS m_textMetrics; 48 | DX::Size m_maxSize; 49 | RECT m_backgroundRect; 50 | Microsoft::WRL::ComPtr m_textBrush; 51 | Microsoft::WRL::ComPtr m_backgroundBrush; 52 | Microsoft::WRL::ComPtr m_stateBlock; 53 | Microsoft::WRL::ComPtr m_textLayout; 54 | Microsoft::WRL::ComPtr m_textFormat; 55 | DWRITE_TEXT_ALIGNMENT m_textAlignment; 56 | D2D1::Matrix3x2F m_screenTranslation; 57 | }; 58 | } 59 | } -------------------------------------------------------------------------------- /Osu!Bot V3/Osu!Bot V3.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 | 15.0 23 | {9AC3DEA2-E3B6-4B92-AF1B-12E75D961DCD} 24 | OsuBotV3 25 | 10.0.17763.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v141 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v141 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 | $(SolutionDir)$(Configuration)\$(Platform)\ 74 | $(Configuration)\$(Platform)\ 75 | $(ProjectName)_x86 76 | E:\Source Codes\Osu!Bot V3\Osu!Bot V3;$(IncludePath) 77 | 78 | 79 | $(SolutionDir)$(Configuration)\$(Platform)\ 80 | $(Configuration)\$(Platform)\ 81 | $(ProjectName)_x86 82 | E:\Source Codes\Osu!Bot V3\Osu!Bot V3;$(IncludePath) 83 | 84 | 85 | $(SolutionDir)$(Configuration)\$(Platform)\ 86 | $(Configuration)\$(Platform)\ 87 | $(ProjectName)_x64 88 | E:\Source Codes\Osu!Bot V3\Osu!Bot V3;$(IncludePath) 89 | 90 | 91 | $(SolutionDir)$(Configuration)\$(Platform)\ 92 | $(Configuration)\$(Platform)\ 93 | $(ProjectName)_x64 94 | E:\Source Codes\Osu!Bot V3\Osu!Bot V3;$(IncludePath) 95 | 96 | 97 | 98 | Level4 99 | Disabled 100 | true 101 | true 102 | _DEBUG;DEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) 103 | Use 104 | Common/Pch.h 105 | Async 106 | 107 | 108 | Windows 109 | d2d1.lib;dwrite.lib;windowscodecs.lib;%(AdditionalDependencies) 110 | 111 | 112 | copy "$(ProjectDir)Common\Config.ini" "$(OutDir)" 113 | 114 | 115 | 116 | 117 | Level4 118 | Disabled 119 | true 120 | true 121 | _DEBUG;DEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) 122 | Use 123 | Common/Pch.h 124 | Async 125 | 126 | 127 | Windows 128 | d2d1.lib;dwrite.lib;windowscodecs.lib;%(AdditionalDependencies) 129 | 130 | 131 | copy "$(ProjectDir)Common\Config.ini" "$(OutDir)" 132 | 133 | 134 | 135 | 136 | Level4 137 | MaxSpeed 138 | true 139 | true 140 | true 141 | true 142 | Use 143 | Common/Pch.h 144 | Async 145 | 146 | 147 | true 148 | true 149 | d2d1.lib;dwrite.lib;windowscodecs.lib;%(AdditionalDependencies) 150 | 151 | 152 | copy "$(ProjectDir)Common\Config.ini" "$(OutDir)" 153 | 154 | mkdir "$(SolutionDir)Output\$(ProjectName)" 155 | copy "$(OutDir)*.exe" "$(SolutionDir)Output\$(ProjectName)\*.exe" 156 | copy "$(OutDir)Config.ini" "$(SolutionDir)Output\$(ProjectName)\*" 157 | 158 | 159 | 160 | 161 | Level4 162 | MaxSpeed 163 | true 164 | true 165 | true 166 | true 167 | Use 168 | Common/Pch.h 169 | Async 170 | 171 | 172 | true 173 | true 174 | d2d1.lib;dwrite.lib;windowscodecs.lib;%(AdditionalDependencies) 175 | 176 | 177 | copy "$(ProjectDir)Common\Config.ini" "$(OutDir)" 178 | 179 | mkdir "$(SolutionDir)Output\$(ProjectName)" 180 | copy "$(OutDir)*.exe" "$(SolutionDir)Output\$(ProjectName)\*.exe" 181 | copy "$(OutDir)Config.ini" "$(SolutionDir)Output\$(ProjectName)\*" 182 | 183 | 184 | 185 | 186 | 187 | 188 | Create 189 | Create 190 | Create 191 | Create 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /Osu!Bot V3/Osu!Bot V3.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;ipp;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 | {ef80603b-f0e2-4777-8bd9-7ea1c948bec7} 18 | 19 | 20 | {a9bf13bb-832d-47f5-a42f-836d0ea30761} 21 | 22 | 23 | {5155006f-40e7-46cc-9a3f-f12bb172d028} 24 | 25 | 26 | {3603702f-0b66-46ef-95ce-bd40dd719cd8} 27 | 28 | 29 | {29f2df2d-c7f0-49c1-a4a5-7dd2d1db214e} 30 | 31 | 32 | {85b14b13-34a0-4c24-bdfd-0ad45b35c505} 33 | 34 | 35 | {da66b084-9586-4d7f-89dd-b18fe9265b88} 36 | 37 | 38 | {afada04d-ce1f-4880-9d6e-9ea0ea8d8d57} 39 | 40 | 41 | {4467641c-5c85-43db-b2fa-2d802ee5b276} 42 | 43 | 44 | {b9b28961-7696-456e-b59e-36de4319a07e} 45 | 46 | 47 | 48 | 49 | Source Files\Common 50 | 51 | 52 | Source Files\Common 53 | 54 | 55 | Source Files\Content 56 | 57 | 58 | Source Files\Common 59 | 60 | 61 | Source Files\Content\UI Elements 62 | 63 | 64 | Source Files\Content 65 | 66 | 67 | Source Files\Content\OsuBot 68 | 69 | 70 | Source Files\Content\OsuBot 71 | 72 | 73 | Source Files\Content\OsuBot 74 | 75 | 76 | Source Files\Content\OsuBot 77 | 78 | 79 | 80 | 81 | Header Files\Common 82 | 83 | 84 | Header Files\Common 85 | 86 | 87 | Header Files\Content 88 | 89 | 90 | Header Files\Content 91 | 92 | 93 | Header Files\Common 94 | 95 | 96 | Header Files\Common 97 | 98 | 99 | Header Files\Content\UI Elements 100 | 101 | 102 | Header Files\Common 103 | 104 | 105 | Header Files\Content 106 | 107 | 108 | Header Files\Content\OsuBot 109 | 110 | 111 | Header Files\Common 112 | 113 | 114 | Header Files\Common 115 | 116 | 117 | Header Files\Content\OsuBot 118 | 119 | 120 | Header Files\Common 121 | 122 | 123 | Header Files\Content\OsuBot 124 | 125 | 126 | 127 | 128 | Resource Files 129 | 130 | 131 | 132 | 133 | Resource Files\Icons 134 | 135 | 136 | Resource Files\Icons 137 | 138 | 139 | 140 | 141 | Project Files 142 | 143 | 144 | -------------------------------------------------------------------------------- /Output/Osu!Bot V3/Config.ini: -------------------------------------------------------------------------------- 1 | ################################################## 2 | # 3 | # Min - Max values for settings. 4 | # 5 | ################################################## 6 | # 7 | # [CONFIGURATION] 8 | # TARGET_FPS 1 - X 9 | # TRANSPARENCY_ALPHA 0 - 255 10 | # TRANSPARENCY_COLOR 0 - 16777215 11 | # 12 | ################################################## 13 | 14 | 15 | [TIME] 16 | TIME_SIGNATURE=DB\5D\E8\8B\45\E8\A3\00\00\00\00\8B\35\00\00\00\00\85\F6 17 | SONG_OFFSET=16.0 18 | 19 | [CONFIGURATION] 20 | LOGIC_UPS=500 21 | WINDOW_FPS=60 22 | TRANSPARENCY_ALPHA=220 23 | TRANSPARENCY_COLOR=0 -------------------------------------------------------------------------------- /Output/Osu!Bot V3/Osu!Bot V3_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Output/Osu!Bot V3/Osu!Bot V3_x64.exe -------------------------------------------------------------------------------- /Output/Osu!Bot V3/Osu!Bot V3_x86.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheCodingNina/Osu-Bot-V3/120d7c5e1b1033b5468a6c026ab5d7ef307c49cf/Output/Osu!Bot V3/Osu!Bot V3_x86.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Osu!Bot V3 2 | Version 3 of my cursor dancing bot that can, play the Rhythm game called Osu! (standard mode). 3 | 4 | USE THIS WITH RESPONSABLITY!! 5 | I DO NOT, I REPEAT, I DO NOT TAKE RESPONSABILITY IF YOU GET YOUR ACCOUNT BANNED/RESTRICTED ON OSU! 6 | 7 | **PROTIP: It is safer to logout of your account before letting Osu!Bot play any beatmap.** 8 | 9 | ## Video Showcase Playlist 10 | *COMING SOON* 11 | 12 | ## Downloadable Executables 13 | [Latest release](https://github.com/DDDinggo22/Osu-Bot-V3/releases/tag/v190205 "Goto latest release") 14 | [Releases](https://github.com/DDDinggo22/Osu-Bot-V3/releases "All releases") 15 | 16 | ## Usage Instructions 17 | 0. Unzip anywhere you like. 18 | 1. Execute (Osu!Bot V3). 19 | - Osu!Bot V3_x64.exe on 64-bit windows systems 20 | - Osu!Bot V3_x86.exe on 32-bit windows systems 21 | 2. Press \***insert**\* to queue a beatmap. 22 | 3. Play any of the queued maps. 23 | 4. Relax and see Osu!Bot V3 dance over the playing field. 24 | 25 | ### Hotkey layout 26 | * \***Home**\* toggles the HUD 27 | * \***TAB**\* toggles Debug UI elements 28 | - Current time / "signature not found" message 29 | - Current updates per second the bot is running at 30 | * \***Insert**\* Allows the user to add beatmaps to the queue 31 | 32 | ## Remarks 33 | ### Known issues 34 | * Sometimes the beatmap does not get added to the queue. 35 | I am investigating the problem, but it does not seem to affect other features of the bot. 36 | * Hotkeys are system wide, meaning they will activate from anywhere. 37 | This will soon change to a localized key input. 38 | 39 | ### Compatibility 40 | * Osu!Bot V3 is **NOT** yet able to play with the "**Hardrock**" mod. 41 | But works fine with all other mods. 42 | * Osu!Bot V3 does **NOT** have the ability to select the beatmap automaticly, like V2 could. 43 | I am still working on this. 44 | * Osu!Bot V3 has a HUD that works **ONLY** when osu is in **borderless** or **windowed** mode. 45 | Making it work in fullscreen mode is extremly difficult. And will not be a priority feature. 46 | * Osu!Bot V3 is using "**mouse button 1**" as its form of clicking. 47 | Soon this will be customizable to keyboard too. 48 | * For now dance modes are hard coded. 49 | But they will be back very soon. 50 | 51 | ### Features 52 | * Any new feature request can be made on my discord server [here](discord.me/Disguard "Join Disguard"). 53 | --------------------------------------------------------------------------------