├── .clang-format ├── .gitignore ├── .gitmodules ├── Downloader ├── Downloader.vcxproj ├── Downloader.vcxproj.filters ├── Downloader.vcxproj.user ├── Resource.aps ├── Resource.rc ├── libraries │ ├── curl │ │ ├── curl.h │ │ ├── curlver.h │ │ ├── easy.h │ │ ├── header.h │ │ ├── libcurl-x64.lib │ │ ├── libcurl-x86.lib │ │ ├── mprintf.h │ │ ├── multi.h │ │ ├── options.h │ │ ├── stdcheaders.h │ │ ├── system.h │ │ ├── typecheck-gcc.h │ │ ├── urlapi.h │ │ └── websockets.h │ ├── detours │ │ ├── detours-x64.lib │ │ ├── detours-x86.lib │ │ ├── detours.h │ │ └── detver.h │ ├── imgui-notify │ │ ├── fa_solid_900.h │ │ ├── font_awesome_5.h │ │ └── imgui_notify.h │ └── json │ │ └── json.hpp ├── res │ ├── MiSans-Normal.ttf │ └── language │ │ ├── en_us.json │ │ └── zh_cn.json ├── resource.h ├── src │ ├── HookManager.hpp │ ├── Logger.h │ ├── api │ │ ├── Bancho.cpp │ │ ├── Bancho.h │ │ ├── Chimu.cpp │ │ ├── Chimu.h │ │ ├── Provider.cpp │ │ ├── Provider.h │ │ ├── Sayobot.cpp │ │ ├── Sayobot.h │ │ └── chimu │ │ │ ├── Map.hpp │ │ │ └── Mapset.hpp │ ├── config │ │ ├── Field.cpp │ │ ├── Field.h │ │ └── I18nManager.h │ ├── dllmain.cpp │ ├── dlver.h.default │ ├── features │ │ ├── About.cpp │ │ ├── About.h │ │ ├── CustomHotkey.cpp │ │ ├── CustomHotkey.h │ │ ├── DownloadQueue.cpp │ │ ├── DownloadQueue.h │ │ ├── Downloader.cpp │ │ ├── Downloader.h │ │ ├── Feature.h │ │ ├── HandleLinkHook.cpp │ │ ├── HandleLinkHook.h │ │ ├── MultiDownload.cpp │ │ ├── MultiDownload.h │ │ ├── Settings.cpp │ │ └── Settings.h │ ├── framework.h │ ├── main.hpp │ ├── misc │ │ ├── Color.h │ │ ├── Hotkey.hpp │ │ ├── ISerializable.h │ │ ├── ResourcesLoader.hpp │ │ ├── VersionManager.cpp │ │ ├── VersionManager.h │ │ └── glob.h │ ├── network │ │ ├── HttpRequest.cpp │ │ └── HttpRequest.h │ ├── osu │ │ ├── Account.cpp │ │ ├── Account.h │ │ ├── Beatmap.cpp │ │ ├── Beatmap.h │ │ ├── BeatmapManager.cpp │ │ ├── BeatmapManager.h │ │ ├── LinkParser.hpp │ │ ├── OsuConfigManager.cpp │ │ └── OsuConfigManager.h │ ├── pch.cpp │ ├── pch.h │ ├── renderer │ │ ├── backend │ │ │ ├── DirectX.cpp │ │ │ ├── DirectX.h │ │ │ ├── OpenGL.cpp │ │ │ └── OpenGL.h │ │ ├── renderer.cpp │ │ └── renderer.h │ ├── ui │ │ ├── BeatmapIdSearchUi.cpp │ │ ├── BeatmapIdSearchUi.h │ │ ├── BlockingInput.hpp │ │ ├── MainUi.cpp │ │ ├── MainUi.h │ │ ├── SearchResultUi.cpp │ │ └── SearchResultUi.h │ └── utils │ │ ├── MD5.hpp │ │ ├── Utils.cpp │ │ ├── Utils.h │ │ ├── gui_utils.cpp │ │ └── gui_utils.h └── update_version.py ├── Injector ├── Injector.vcxproj ├── Injector.vcxproj.filters ├── Injector.vcxproj.user └── src │ └── main.cpp ├── OsuBeatmapDownloader.sln ├── README.md ├── README_zh_cn.md └── docs └── img ├── en_us ├── 1.png ├── 2.png └── 3.png └── zh_cn ├── 1.png ├── 2.png └── 3.png /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignOperands: false 8 | AlignTrailingComments: false 9 | AlwaysBreakTemplateDeclarations: Yes 10 | BraceWrapping: 11 | AfterCaseLabel: false 12 | AfterClass: false 13 | AfterControlStatement: false 14 | AfterEnum: false 15 | AfterFunction: false 16 | AfterNamespace: false 17 | AfterStruct: false 18 | AfterUnion: false 19 | AfterExternBlock: false 20 | BeforeCatch: false 21 | BeforeElse: false 22 | BeforeLambdaBody: false 23 | BeforeWhile: false 24 | SplitEmptyFunction: true 25 | SplitEmptyRecord: true 26 | SplitEmptyNamespace: true 27 | BreakBeforeBraces: Custom 28 | BreakConstructorInitializers: AfterColon 29 | ColumnLimit: 144 30 | IncludeCategories: 31 | - Regex: '^<.*' 32 | Priority: 1 33 | - Regex: '^".*' 34 | Priority: 2 35 | - Regex: '.*' 36 | Priority: 3 37 | IncludeIsMainRegex: '([-_](test|unittest))?$' 38 | IndentWidth: 4 39 | MacroBlockBegin: '' 40 | MacroBlockEnd: '' 41 | MaxEmptyLinesToKeep: 2 42 | SpacesInAngles: false 43 | TabWidth: 4 44 | ... 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /.vscode 3 | /.idea 4 | /bin 5 | /Downloader/src/dlver.h -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "imgui"] 2 | path = Downloader/libraries/imgui 3 | url = https://github.com/ocornut/imgui.git 4 | [submodule "utility"] 5 | path = Downloader/libraries/utility 6 | url = https://github.com/KyuubiRan/utility 7 | -------------------------------------------------------------------------------- /Downloader/Downloader.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | 7 | true 8 | WindowsLocalDebugger 9 | NativeOnly 10 | 11 | 12 | true 13 | WindowsLocalDebugger 14 | NativeOnly 15 | 16 | -------------------------------------------------------------------------------- /Downloader/Resource.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/Resource.aps -------------------------------------------------------------------------------- /Downloader/Resource.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/Resource.rc -------------------------------------------------------------------------------- /Downloader/libraries/curl/curlver.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_CURLVER_H 2 | #define CURLINC_CURLVER_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | /* This header file contains nothing but libcurl version info, generated by 28 | a script at release-time. This was made its own header file in 7.11.2 */ 29 | 30 | /* This is the global package copyright */ 31 | #define LIBCURL_COPYRIGHT "Daniel Stenberg, ." 32 | 33 | /* This is the version number of the libcurl package from which this header 34 | file origins: */ 35 | #define LIBCURL_VERSION "8.1.2" 36 | 37 | /* The numeric version number is also available "in parts" by using these 38 | defines: */ 39 | #define LIBCURL_VERSION_MAJOR 8 40 | #define LIBCURL_VERSION_MINOR 1 41 | #define LIBCURL_VERSION_PATCH 2 42 | 43 | /* This is the numeric version of the libcurl version number, meant for easier 44 | parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will 45 | always follow this syntax: 46 | 47 | 0xXXYYZZ 48 | 49 | Where XX, YY and ZZ are the main version, release and patch numbers in 50 | hexadecimal (using 8 bits each). All three numbers are always represented 51 | using two digits. 1.2 would appear as "0x010200" while version 9.11.7 52 | appears as "0x090b07". 53 | 54 | This 6-digit (24 bits) hexadecimal number does not show pre-release number, 55 | and it is always a greater number in a more recent release. It makes 56 | comparisons with greater than and less than work. 57 | 58 | Note: This define is the full hex number and _does not_ use the 59 | CURL_VERSION_BITS() macro since curl's own configure script greps for it 60 | and needs it to contain the full number. 61 | */ 62 | #define LIBCURL_VERSION_NUM 0x080102 63 | 64 | /* 65 | * This is the date and time when the full source package was created. The 66 | * timestamp is not stored in git, as the timestamp is properly set in the 67 | * tarballs by the maketgz script. 68 | * 69 | * The format of the date follows this template: 70 | * 71 | * "2007-11-23" 72 | */ 73 | #define LIBCURL_TIMESTAMP "2023-05-30" 74 | 75 | #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z)) 76 | #define CURL_AT_LEAST_VERSION(x,y,z) \ 77 | (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) 78 | 79 | #endif /* CURLINC_CURLVER_H */ 80 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/easy.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_EASY_H 2 | #define CURLINC_EASY_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | /* Flag bits in the curl_blob struct: */ 31 | #define CURL_BLOB_COPY 1 /* tell libcurl to copy the data */ 32 | #define CURL_BLOB_NOCOPY 0 /* tell libcurl to NOT copy the data */ 33 | 34 | struct curl_blob { 35 | void *data; 36 | size_t len; 37 | unsigned int flags; /* bit 0 is defined, the rest are reserved and should be 38 | left zeroes */ 39 | }; 40 | 41 | CURL_EXTERN CURL *curl_easy_init(void); 42 | CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); 43 | CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); 44 | CURL_EXTERN void curl_easy_cleanup(CURL *curl); 45 | 46 | /* 47 | * NAME curl_easy_getinfo() 48 | * 49 | * DESCRIPTION 50 | * 51 | * Request internal information from the curl session with this function. 52 | * The third argument MUST be pointing to the specific type of the used option 53 | * which is documented in each man page of the option. The data pointed to 54 | * will be filled in accordingly and can be relied upon only if the function 55 | * returns CURLE_OK. This function is intended to get used *AFTER* a performed 56 | * transfer, all results from this function are undefined until the transfer 57 | * is completed. 58 | */ 59 | CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); 60 | 61 | 62 | /* 63 | * NAME curl_easy_duphandle() 64 | * 65 | * DESCRIPTION 66 | * 67 | * Creates a new curl session handle with the same options set for the handle 68 | * passed in. Duplicating a handle could only be a matter of cloning data and 69 | * options, internal state info and things like persistent connections cannot 70 | * be transferred. It is useful in multithreaded applications when you can run 71 | * curl_easy_duphandle() for each new thread to avoid a series of identical 72 | * curl_easy_setopt() invokes in every thread. 73 | */ 74 | CURL_EXTERN CURL *curl_easy_duphandle(CURL *curl); 75 | 76 | /* 77 | * NAME curl_easy_reset() 78 | * 79 | * DESCRIPTION 80 | * 81 | * Re-initializes a CURL handle to the default values. This puts back the 82 | * handle to the same state as it was in when it was just created. 83 | * 84 | * It does keep: live connections, the Session ID cache, the DNS cache and the 85 | * cookies. 86 | */ 87 | CURL_EXTERN void curl_easy_reset(CURL *curl); 88 | 89 | /* 90 | * NAME curl_easy_recv() 91 | * 92 | * DESCRIPTION 93 | * 94 | * Receives data from the connected socket. Use after successful 95 | * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. 96 | */ 97 | CURL_EXTERN CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, 98 | size_t *n); 99 | 100 | /* 101 | * NAME curl_easy_send() 102 | * 103 | * DESCRIPTION 104 | * 105 | * Sends data over the connected socket. Use after successful 106 | * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. 107 | */ 108 | CURL_EXTERN CURLcode curl_easy_send(CURL *curl, const void *buffer, 109 | size_t buflen, size_t *n); 110 | 111 | 112 | /* 113 | * NAME curl_easy_upkeep() 114 | * 115 | * DESCRIPTION 116 | * 117 | * Performs connection upkeep for the given session handle. 118 | */ 119 | CURL_EXTERN CURLcode curl_easy_upkeep(CURL *curl); 120 | 121 | #ifdef __cplusplus 122 | } /* end of extern "C" */ 123 | #endif 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/header.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_HEADER_H 2 | #define CURLINC_HEADER_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | struct curl_header { 32 | char *name; /* this might not use the same case */ 33 | char *value; 34 | size_t amount; /* number of headers using this name */ 35 | size_t index; /* ... of this instance, 0 or higher */ 36 | unsigned int origin; /* see bits below */ 37 | void *anchor; /* handle privately used by libcurl */ 38 | }; 39 | 40 | /* 'origin' bits */ 41 | #define CURLH_HEADER (1<<0) /* plain server header */ 42 | #define CURLH_TRAILER (1<<1) /* trailers */ 43 | #define CURLH_CONNECT (1<<2) /* CONNECT headers */ 44 | #define CURLH_1XX (1<<3) /* 1xx headers */ 45 | #define CURLH_PSEUDO (1<<4) /* pseudo headers */ 46 | 47 | typedef enum { 48 | CURLHE_OK, 49 | CURLHE_BADINDEX, /* header exists but not with this index */ 50 | CURLHE_MISSING, /* no such header exists */ 51 | CURLHE_NOHEADERS, /* no headers at all exist (yet) */ 52 | CURLHE_NOREQUEST, /* no request with this number was used */ 53 | CURLHE_OUT_OF_MEMORY, /* out of memory while processing */ 54 | CURLHE_BAD_ARGUMENT, /* a function argument was not okay */ 55 | CURLHE_NOT_BUILT_IN /* if API was disabled in the build */ 56 | } CURLHcode; 57 | 58 | CURL_EXTERN CURLHcode curl_easy_header(CURL *easy, 59 | const char *name, 60 | size_t index, 61 | unsigned int origin, 62 | int request, 63 | struct curl_header **hout); 64 | 65 | CURL_EXTERN struct curl_header *curl_easy_nextheader(CURL *easy, 66 | unsigned int origin, 67 | int request, 68 | struct curl_header *prev); 69 | 70 | #ifdef __cplusplus 71 | } /* end of extern "C" */ 72 | #endif 73 | 74 | #endif /* CURLINC_HEADER_H */ 75 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/libcurl-x64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/libraries/curl/libcurl-x64.lib -------------------------------------------------------------------------------- /Downloader/libraries/curl/libcurl-x86.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/libraries/curl/libcurl-x86.lib -------------------------------------------------------------------------------- /Downloader/libraries/curl/mprintf.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_MPRINTF_H 2 | #define CURLINC_MPRINTF_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | #include 28 | #include /* needed for FILE */ 29 | #include "curl.h" /* for CURL_EXTERN */ 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | CURL_EXTERN int curl_mprintf(const char *format, ...); 36 | CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...); 37 | CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...); 38 | CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, 39 | const char *format, ...); 40 | CURL_EXTERN int curl_mvprintf(const char *format, va_list args); 41 | CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args); 42 | CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args); 43 | CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength, 44 | const char *format, va_list args); 45 | CURL_EXTERN char *curl_maprintf(const char *format, ...); 46 | CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args); 47 | 48 | #ifdef __cplusplus 49 | } /* end of extern "C" */ 50 | #endif 51 | 52 | #endif /* CURLINC_MPRINTF_H */ 53 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/options.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_OPTIONS_H 2 | #define CURLINC_OPTIONS_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | typedef enum { 32 | CURLOT_LONG, /* long (a range of values) */ 33 | CURLOT_VALUES, /* (a defined set or bitmask) */ 34 | CURLOT_OFF_T, /* curl_off_t (a range of values) */ 35 | CURLOT_OBJECT, /* pointer (void *) */ 36 | CURLOT_STRING, /* (char * to null-terminated buffer) */ 37 | CURLOT_SLIST, /* (struct curl_slist *) */ 38 | CURLOT_CBPTR, /* (void * passed as-is to a callback) */ 39 | CURLOT_BLOB, /* blob (struct curl_blob *) */ 40 | CURLOT_FUNCTION /* function pointer */ 41 | } curl_easytype; 42 | 43 | /* Flag bits */ 44 | 45 | /* "alias" means it is provided for old programs to remain functional, 46 | we prefer another name */ 47 | #define CURLOT_FLAG_ALIAS (1<<0) 48 | 49 | /* The CURLOPTTYPE_* id ranges can still be used to figure out what type/size 50 | to use for curl_easy_setopt() for the given id */ 51 | struct curl_easyoption { 52 | const char *name; 53 | CURLoption id; 54 | curl_easytype type; 55 | unsigned int flags; 56 | }; 57 | 58 | CURL_EXTERN const struct curl_easyoption * 59 | curl_easy_option_by_name(const char *name); 60 | 61 | CURL_EXTERN const struct curl_easyoption * 62 | curl_easy_option_by_id(CURLoption id); 63 | 64 | CURL_EXTERN const struct curl_easyoption * 65 | curl_easy_option_next(const struct curl_easyoption *prev); 66 | 67 | #ifdef __cplusplus 68 | } /* end of extern "C" */ 69 | #endif 70 | #endif /* CURLINC_OPTIONS_H */ 71 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/stdcheaders.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_STDCHEADERS_H 2 | #define CURLINC_STDCHEADERS_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | #include 28 | 29 | size_t fread(void *, size_t, size_t, FILE *); 30 | size_t fwrite(const void *, size_t, size_t, FILE *); 31 | 32 | int strcasecmp(const char *, const char *); 33 | int strncasecmp(const char *, const char *, size_t); 34 | 35 | #endif /* CURLINC_STDCHEADERS_H */ 36 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/urlapi.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_URLAPI_H 2 | #define CURLINC_URLAPI_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | #include "curl.h" 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | /* the error codes for the URL API */ 34 | typedef enum { 35 | CURLUE_OK, 36 | CURLUE_BAD_HANDLE, /* 1 */ 37 | CURLUE_BAD_PARTPOINTER, /* 2 */ 38 | CURLUE_MALFORMED_INPUT, /* 3 */ 39 | CURLUE_BAD_PORT_NUMBER, /* 4 */ 40 | CURLUE_UNSUPPORTED_SCHEME, /* 5 */ 41 | CURLUE_URLDECODE, /* 6 */ 42 | CURLUE_OUT_OF_MEMORY, /* 7 */ 43 | CURLUE_USER_NOT_ALLOWED, /* 8 */ 44 | CURLUE_UNKNOWN_PART, /* 9 */ 45 | CURLUE_NO_SCHEME, /* 10 */ 46 | CURLUE_NO_USER, /* 11 */ 47 | CURLUE_NO_PASSWORD, /* 12 */ 48 | CURLUE_NO_OPTIONS, /* 13 */ 49 | CURLUE_NO_HOST, /* 14 */ 50 | CURLUE_NO_PORT, /* 15 */ 51 | CURLUE_NO_QUERY, /* 16 */ 52 | CURLUE_NO_FRAGMENT, /* 17 */ 53 | CURLUE_NO_ZONEID, /* 18 */ 54 | CURLUE_BAD_FILE_URL, /* 19 */ 55 | CURLUE_BAD_FRAGMENT, /* 20 */ 56 | CURLUE_BAD_HOSTNAME, /* 21 */ 57 | CURLUE_BAD_IPV6, /* 22 */ 58 | CURLUE_BAD_LOGIN, /* 23 */ 59 | CURLUE_BAD_PASSWORD, /* 24 */ 60 | CURLUE_BAD_PATH, /* 25 */ 61 | CURLUE_BAD_QUERY, /* 26 */ 62 | CURLUE_BAD_SCHEME, /* 27 */ 63 | CURLUE_BAD_SLASHES, /* 28 */ 64 | CURLUE_BAD_USER, /* 29 */ 65 | CURLUE_LACKS_IDN, /* 30 */ 66 | CURLUE_LAST 67 | } CURLUcode; 68 | 69 | typedef enum { 70 | CURLUPART_URL, 71 | CURLUPART_SCHEME, 72 | CURLUPART_USER, 73 | CURLUPART_PASSWORD, 74 | CURLUPART_OPTIONS, 75 | CURLUPART_HOST, 76 | CURLUPART_PORT, 77 | CURLUPART_PATH, 78 | CURLUPART_QUERY, 79 | CURLUPART_FRAGMENT, 80 | CURLUPART_ZONEID /* added in 7.65.0 */ 81 | } CURLUPart; 82 | 83 | #define CURLU_DEFAULT_PORT (1<<0) /* return default port number */ 84 | #define CURLU_NO_DEFAULT_PORT (1<<1) /* act as if no port number was set, 85 | if the port number matches the 86 | default for the scheme */ 87 | #define CURLU_DEFAULT_SCHEME (1<<2) /* return default scheme if 88 | missing */ 89 | #define CURLU_NON_SUPPORT_SCHEME (1<<3) /* allow non-supported scheme */ 90 | #define CURLU_PATH_AS_IS (1<<4) /* leave dot sequences */ 91 | #define CURLU_DISALLOW_USER (1<<5) /* no user+password allowed */ 92 | #define CURLU_URLDECODE (1<<6) /* URL decode on get */ 93 | #define CURLU_URLENCODE (1<<7) /* URL encode on set */ 94 | #define CURLU_APPENDQUERY (1<<8) /* append a form style part */ 95 | #define CURLU_GUESS_SCHEME (1<<9) /* legacy curl-style guessing */ 96 | #define CURLU_NO_AUTHORITY (1<<10) /* Allow empty authority when the 97 | scheme is unknown. */ 98 | #define CURLU_ALLOW_SPACE (1<<11) /* Allow spaces in the URL */ 99 | #define CURLU_PUNYCODE (1<<12) /* get the host name in pynycode */ 100 | 101 | typedef struct Curl_URL CURLU; 102 | 103 | /* 104 | * curl_url() creates a new CURLU handle and returns a pointer to it. 105 | * Must be freed with curl_url_cleanup(). 106 | */ 107 | CURL_EXTERN CURLU *curl_url(void); 108 | 109 | /* 110 | * curl_url_cleanup() frees the CURLU handle and related resources used for 111 | * the URL parsing. It will not free strings previously returned with the URL 112 | * API. 113 | */ 114 | CURL_EXTERN void curl_url_cleanup(CURLU *handle); 115 | 116 | /* 117 | * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new 118 | * handle must also be freed with curl_url_cleanup(). 119 | */ 120 | CURL_EXTERN CURLU *curl_url_dup(const CURLU *in); 121 | 122 | /* 123 | * curl_url_get() extracts a specific part of the URL from a CURLU 124 | * handle. Returns error code. The returned pointer MUST be freed with 125 | * curl_free() afterwards. 126 | */ 127 | CURL_EXTERN CURLUcode curl_url_get(const CURLU *handle, CURLUPart what, 128 | char **part, unsigned int flags); 129 | 130 | /* 131 | * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns 132 | * error code. The passed in string will be copied. Passing a NULL instead of 133 | * a part string, clears that part. 134 | */ 135 | CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what, 136 | const char *part, unsigned int flags); 137 | 138 | /* 139 | * curl_url_strerror() turns a CURLUcode value into the equivalent human 140 | * readable error string. This is useful for printing meaningful error 141 | * messages. 142 | */ 143 | CURL_EXTERN const char *curl_url_strerror(CURLUcode); 144 | 145 | #ifdef __cplusplus 146 | } /* end of extern "C" */ 147 | #endif 148 | 149 | #endif /* CURLINC_URLAPI_H */ 150 | -------------------------------------------------------------------------------- /Downloader/libraries/curl/websockets.h: -------------------------------------------------------------------------------- 1 | #ifndef CURLINC_WEBSOCKETS_H 2 | #define CURLINC_WEBSOCKETS_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at https://curl.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | * SPDX-License-Identifier: curl 24 | * 25 | ***************************************************************************/ 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | struct curl_ws_frame { 32 | int age; /* zero */ 33 | int flags; /* See the CURLWS_* defines */ 34 | curl_off_t offset; /* the offset of this data into the frame */ 35 | curl_off_t bytesleft; /* number of pending bytes left of the payload */ 36 | size_t len; /* size of the current data chunk */ 37 | }; 38 | 39 | /* flag bits */ 40 | #define CURLWS_TEXT (1<<0) 41 | #define CURLWS_BINARY (1<<1) 42 | #define CURLWS_CONT (1<<2) 43 | #define CURLWS_CLOSE (1<<3) 44 | #define CURLWS_PING (1<<4) 45 | #define CURLWS_OFFSET (1<<5) 46 | 47 | /* 48 | * NAME curl_ws_recv() 49 | * 50 | * DESCRIPTION 51 | * 52 | * Receives data from the websocket connection. Use after successful 53 | * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. 54 | */ 55 | CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, 56 | size_t *recv, 57 | struct curl_ws_frame **metap); 58 | 59 | /* sendflags for curl_ws_send() */ 60 | #define CURLWS_PONG (1<<6) 61 | 62 | /* 63 | * NAME curl_easy_send() 64 | * 65 | * DESCRIPTION 66 | * 67 | * Sends data over the websocket connection. Use after successful 68 | * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. 69 | */ 70 | CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, 71 | size_t buflen, size_t *sent, 72 | curl_off_t framesize, 73 | unsigned int sendflags); 74 | 75 | /* bits for the CURLOPT_WS_OPTIONS bitmask: */ 76 | #define CURLWS_RAW_MODE (1<<0) 77 | 78 | CURL_EXTERN struct curl_ws_frame *curl_ws_meta(CURL *curl); 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | #endif /* CURLINC_WEBSOCKETS_H */ 85 | -------------------------------------------------------------------------------- /Downloader/libraries/detours/detours-x64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/libraries/detours/detours-x64.lib -------------------------------------------------------------------------------- /Downloader/libraries/detours/detours-x86.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/libraries/detours/detours-x86.lib -------------------------------------------------------------------------------- /Downloader/libraries/detours/detver.h: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Common version parameters. 4 | // 5 | // Microsoft Research Detours Package, Version 4.0.1 6 | // 7 | // Copyright (c) Microsoft Corporation. All rights reserved. 8 | // 9 | 10 | #define _USING_V110_SDK71_ 1 11 | #include "winver.h" 12 | #if 0 13 | #include 14 | #include 15 | #else 16 | #ifndef DETOURS_STRINGIFY 17 | #define DETOURS_STRINGIFY_(x) #x 18 | #define DETOURS_STRINGIFY(x) DETOURS_STRINGIFY_(x) 19 | #endif 20 | 21 | #define VER_FILEFLAGSMASK 0x3fL 22 | #define VER_FILEFLAGS 0x0L 23 | #define VER_FILEOS 0x00040004L 24 | #define VER_FILETYPE 0x00000002L 25 | #define VER_FILESUBTYPE 0x00000000L 26 | #endif 27 | #define VER_DETOURS_BITS DETOURS_STRINGIFY(DETOURS_BITS) 28 | -------------------------------------------------------------------------------- /Downloader/res/MiSans-Normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/Downloader/res/MiSans-Normal.ttf -------------------------------------------------------------------------------- /Downloader/res/language/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "Download": "Download", 3 | "Downloader": "Downloader", 4 | "OsuBeatmapDownloader": "Osu! Beatmap Downloader", 5 | "Settings": "Settings", 6 | "Mirror": "Download Mirror", 7 | "About": "About", 8 | "ProjectAuthor": "Author: KyuubiRan", 9 | "ProjectLink": "Project link:", 10 | "Language": "Language", 11 | "Enabled": "Enabled", 12 | "Disabled": "Disabled", 13 | "Domain": "Domain", 14 | "HandleLink": "Handle download link", 15 | "HandleLinkDesc": "If enabled, the download link will be handled by the downloader.\nYou can hold the Ctrl and click the link to skip handle the link.", 16 | "HandleLinkDomainDesc": "If you are in private server, you should change this, otherwise, keep it default.", 17 | "GrantOsuAccount": "Grant osu! account", 18 | "GrantOsuAccountDesc": "Grant osu! account, then you can download beatmaps by officially, just like osu!direct.\nNOTICE: If you are in private server, please don not enable this.", 19 | "NotGrantOsuAccountButUseOfficialWarn": "WARNING: You are NOT grant osu! account but using official download!\nThe all of download tasks will AUTO CANCELLED!", 20 | "ProxyServerType": "Set Proxy Server", 21 | "ProxyServerDesc": "Use proxy server to search/download beatmaps.", 22 | "ProxyServer": "Proxy Server", 23 | "ProxyServerPassword": "Proxy Server Password", 24 | "EnableCustomUserAgent": "Enable Custom User-Agent", 25 | "CustomUserAgentDesc": "Enable Custom User-Agent, If you don't know what it is, please keep it disabled.", 26 | "DownloadType": "Download Type", 27 | "Title": "Title: %s", 28 | "Artist": "Artist: %s", 29 | "Mapper": "Mapper: %s", 30 | "Cancel": "Cancel", 31 | "ReDownload": "Re-Download", 32 | "ViewWebsite": "View Website", 33 | "BeatmapInfo": "Beatmap Info", 34 | "DownloadQueue": "Queue", 35 | "CancelDownload": "Cancel Download", 36 | "Hotkey": "Hotkey", 37 | "HotkeyDesc": "[%s] Toggle MainUi Show.\n[%s] Toggle BeatmapIdSearchUi Show.\nHold [Ctrl] + Click the link to skip handle the link.\nHold [SHIFT] + Click the link to direct download the beatmap(except exists beatmap).", 38 | "Empty": "Nothing here~", 39 | "SearchBeatmapId": "Beatmap Id Searcher", 40 | "Search": "Search", 41 | "SearchBeatmapIdDesc": "Search beatmap by id.By BeatmapSetsId: beatmapsets123456 | s123456。\nBy BeatmapId: beatmaps123456 | b123456\nOnly number id: by left combo box selected.", 42 | "Clear": "Clear", 43 | "Username": "Username", 44 | "Password": "Password", 45 | "Toast": "Toast", 46 | "EnableToast": "Enable Toast", 47 | "ToastDuration": "Toast Duration", 48 | "Debug": "Debug", 49 | "EnableConsole": "Enable Console", 50 | "RestartToApply": "Restart to apply changes.", 51 | "DownloaderLoadSuccess": "Welcome to use BeatmapDownloader!", 52 | "Success": "Success", 53 | "Warning": "Warning", 54 | "Info": "Info", 55 | "Error": "Error", 56 | "DownloadFailedOORT": "Download beatmapsets(%d) failed! (out of retry times)", 57 | "DownloadSuccess": "Finished download beatmapsets: %d!", 58 | "StartDownload": "Start download beatmapsets: %d", 59 | "SearchFailed": "Search beatmap(%s=%d) failed!", 60 | "CurlError": "Curl request error: %d", 61 | "OsuPath": "Custom Osu! Path", 62 | "OsuPathDesc": "Use custom osu! path, or empty to auto detect the path.\nIf you don't know what it is, please keep it empty.", 63 | "MoveToOsuFolder": "Move Downloaded Beatmap", 64 | "MoveToOsuFolderDesc": "Move downloaded beatmap to osu! Songs folder instead open the .osz file.", 65 | "MultiDL": "Multi DL", 66 | "MultiDownloader": "Multi Downloader", 67 | "MultiDownloaderDesc": "Download multiple beatmaps line by line.\ne.g.:\ns123456\n123456\nhttps://osu.ppy.sh/beatmapsets/123456", 68 | "Type": "Type", 69 | "IDSearchDefaultType": "Only Number ID Search Type", 70 | "Mode": "Mode", 71 | "BPDownloader": "BP Downloader", 72 | "BPDownloadFailedUserNotFound": "BP Download failed: User(%d) not found!", 73 | "BPDownloadFailedResponseCodeNotOk": "BP Download failed: Response code: %d", 74 | "BPDownloadFailedParseFailed": "BP Download failed: Parse response body failed!", 75 | "ExistsBeatmapSkipAutoDownload": "Already has beatmapsets(%d), skip auto download.", 76 | "Connecting": "Connecting", 77 | "Range": "Range", 78 | "InvalidOsuPath": "Invalid custom osu! path, will not be applied!", 79 | "BPDLRangeDesc": "BP download range, 0~100", 80 | "InvalidDLRange": "Invalid download range! Begin=%d, End=%d", 81 | "StartDownloadBP": "Start download bp(uid=%d): From %d to %d", 82 | "InvalidInput": "Invalid input: %s", 83 | "FavoriteDownloader": "Favorite Beatmap Downloader", 84 | "MapperDownloader": "Mapper Beatmap Downloader", 85 | "FavDownloadFailedUserNotFound": "Favorite Download failed: User(%d) not found!", 86 | "FavDownloadFailedResponseCodeNotOk": "Favorite Download failed: Response code: %d", 87 | "FavDownloadFailedParseFailed": "Favorite Download failed: Parse response body failed!", 88 | "StartDownloadFav": "Start download favorite(uid=%d): From %d to %d", 89 | "MapperDownloadFailedUserNotFound": "Mapper Download failed: User(%d) not found!", 90 | "MapperDownloadFailedResponseCodeNotOk": "Mapper Download failed: Response code: %d", 91 | "MapperDownloadFailedParseFailed": "Mapper Download failed: Parse response body failed!", 92 | "StartDownloadMapper": "Start download mapper(uid=%d): From %d to %d, Beatmap Status: %s", 93 | "BeatmapStatus": "Beatmap Status", 94 | "BeatmapStatusDesc": "Ranked: Ranked Beatmaps\nLoved: Loved Beatmaps\nGuest: Guest Participation Beatmaps\nPending: Pending Beatmaps\nGraveyard: Graveyarded Beatmaps\nNominated: Nominated Ranked Beatmaps", 95 | "StartDownloadMostPlayed": "Start download most played(uid=%d): From %d to %d", 96 | "MostPlayedDownloader": "Most Played Beatmaps Downloader", 97 | "MostPlayedDownloadFailedUserNotFound": "Most Played Download failed: User(%d) not found!", 98 | "MostPlayedDownloadFailedResponseCodeNotOk": "Most Played Download failed: Response code: %d", 99 | "MostPlayedDownloadFailedParseFailed": "Most Played Download failed: Parse response body failed!", 100 | "MainMenuHotkey": "Toggle Main Menu UI Show", 101 | "IdSearchHotkey": "Toggle Sid/Bid Search UI Show", 102 | "Main": "Main", 103 | "Theme": "Theme", 104 | "Paste": "Paste", 105 | "VersionChecking": "Checking update for BeatmapDownloader...", 106 | "VersionIsLatest": "BeatmapDownloader is up to date!", 107 | "FoundNewVersion": "Found new version: %s(%d)", 108 | "VersionCheckFailed": "Cannot check latest version! code:%d", 109 | "CurrentVersion": "Current Version: %s(%d)", 110 | "GotoDownload": "Goto Download", 111 | "CheckUpdate": "Check Update", 112 | "AlreadyHasDownloadTask": "Already has download task: %d %s-%s [%s]", 113 | "BeatmapPackId": "Beatmap Pack ID", 114 | "BeatmapPackDownloader": "Beatmap Pack Downloader", 115 | "BeatmapPackDownloadFailedNoSuchPackFound": "No such beatmap pack ID=%d(%s) found!", 116 | "StartDownloadPack": "Start download beatmap pack: %d(%s)", 117 | "BeatmapPackDownloadFailedResponseCodeNotOk": "Beatmap Pack Download failed: Response code: %d" 118 | } -------------------------------------------------------------------------------- /Downloader/res/language/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "Download": "下载", 3 | "Downloader": "下载器", 4 | "OsuBeatmapDownloader": "Osu! 谱面下载器", 5 | "Settings": "设置", 6 | "Mirror": "镜像站", 7 | "About": "关于", 8 | "ProjectAuthor": "作者: KyuubiRan", 9 | "ProjectLink": "项目链接:", 10 | "Language": "语言", 11 | "Enabled": "启用", 12 | "Disabled": "禁用", 13 | "Domain": "域名", 14 | "HandleLink": "代理下载链接", 15 | "HandleLinkDesc": "如果启用,下载器会代理谱面链接。\n你可以通过按住Ctrl键点击链接来临时跳过代理。", 16 | "HandleLinkDomainDesc": "如果你在游玩私服,请修改此项,否则请保持默认。", 17 | "GrantOsuAccount": "授权使用osu!账号", 18 | "GrantOsuAccountDesc": "授权使用osu!账号,你可以选择使用osu!官方渠道下载谱面,就像osu!direct一样。\n注意:如果你在游玩私有服务器,请不要勾选此项。", 19 | "NotGrantOsuAccountButUseOfficialWarn": "警告:你没有授权使用osu!账号,但是你选择了官方下载渠道。\n所有的下载任务将会被自动取消!", 20 | "ProxyServerType": "设置代理服务器", 21 | "ProxyServerDesc": "使用代理服务器下载/搜索谱面。", 22 | "ProxyServer": "代理服务器", 23 | "ProxyServerPassword": "代理服务器密码", 24 | "EnableCustomUserAgent": "启用自定义User-Agent", 25 | "CustomUserAgentDesc": "启用自定义User-Agent,如果你不知道它是什么,请禁用它。", 26 | "DownloadType": "下载类型", 27 | "Title": "标题: %s", 28 | "Artist": "艺术家: %s", 29 | "Mapper": "谱面作者: %s", 30 | "Cancel": "取消", 31 | "ReDownload": "重新下载", 32 | "ViewWebsite": "查看网页", 33 | "BeatmapInfo": "谱面信息", 34 | "DownloadQueue": "下载队列", 35 | "CancelDownload": "取消下载", 36 | "Hotkey": "快捷键", 37 | "HotkeyDesc": "[%s] 切换主菜单显示。\n[%s] 切换谱面ID搜索显示。\n长按 [Ctrl] + 点击链接跳过代理搜图。\n长按 [Shift] + 点击链接直接开始下载(已经存在的谱面除外)", 38 | "Empty": "空空如也~", 39 | "SearchBeatmapId": "谱面ID搜索器", 40 | "Search": "搜索", 41 | "SearchBeatmapIdDesc": "根据ID来搜索谱面。支持的格式如下:\n根据BeatmapSetsId: beatmapsets123456 | s123456。\n根据BeatmapId: beatmaps123456 | b123456。\n如果只有数字id:按照左边的选择框来搜索。", 42 | "Clear": "清除", 43 | "Username": "用户名", 44 | "Password": "密码", 45 | "Toast": "通知", 46 | "EnableToast": "启用通知", 47 | "ToastDuration": "通知显示时间", 48 | "Debug": "调试", 49 | "EnableConsole": "启用控制台", 50 | "RestartToApply": "重启后生效", 51 | "DownloaderLoadSuccess": "欢迎使用谱面下载器!", 52 | "Success": "成功", 53 | "Warning": "警告", 54 | "Info": "信息", 55 | "Error": "错误", 56 | "DownloadFailedOORT": "下载谱面集合(%d)失败!(超出最大重试次数)", 57 | "DownloadSuccess": "谱面集合(%d)下载完成!", 58 | "StartDownload": "开始下载谱面集合: %d", 59 | "SearchFailed": "谱面(%s=%d)搜索失败!", 60 | "CurlError": "Curl 请求出错: %d", 61 | "OsuPath": "自定义osu!路径", 62 | "OsuPathDesc": "使用自定义路径,如果它为空,则自动检测路径。\n如果你不知道它是什么,请将它留空。", 63 | "MoveToOsuFolder": "移动下载的谱面", 64 | "MoveToOsuFolderDesc": "移动下载完的谱面到osu!的Songs目录下,而不是打开它。", 65 | "MultiDL": "批量下载", 66 | "MultiDownloader": "批量下载器", 67 | "MultiDownloaderDesc": "按着行来搜索谱面并进行下载。\n举例:\ns123456\n123456\nhttps://osu.ppy.sh/beatmapsets/123456", 68 | "Type": "类型", 69 | "IDSearchDefaultType": "仅数字ID识别类型", 70 | "Mode": "模式", 71 | "BPDownloader": "BP下载器", 72 | "BPDownloadFailedUserNotFound": "BP下载失败:找不到用户(%d)!", 73 | "BPDownloadFailedResponseCodeNotOk": "BP下载失败:响应码:%d", 74 | "BPDownloadFailedParseFailed": "BP下载失败:解析响应体失败!", 75 | "ExistsBeatmapSkipAutoDownload": "已存在的谱面集(%d),跳过自动下载。", 76 | "Connecting": "连接中", 77 | "Range": "范围", 78 | "InvalidOsuPath": "无效的osu!路径,将不会应用变更!", 79 | "BPDLRangeDesc": "BP抓取范围,0~100", 80 | "InvalidDLRange": "无效的范围!起始=%d,结束=%d", 81 | "StartDownloadBP": "开始下载BP(uid=%d):从 %d 到 %d", 82 | "InvalidInput": "无效的输入:%s", 83 | "FavoriteDownloader": "收藏谱面下载器", 84 | "MapperDownloader": "谱师谱面下载器", 85 | "FavDownloadFailedUserNotFound": "收藏谱面下载失败:找不到用户(%d)!", 86 | "FavDownloadFailedResponseCodeNotOk": "收藏谱面下载失败:响应码:%d", 87 | "FavDownloadFailedParseFailed": "收藏谱面下载失败:解析响应体失败!", 88 | "StartDownloadFav": "开始下载收藏谱面(uid=%d):从 %d 到 %d", 89 | "MapperDownloadFailedUserNotFound": "谱师谱面下载失败:找不到用户(%d)!", 90 | "MapperDownloadFailedResponseCodeNotOk": "谱师谱面下载失败:响应码:%d", 91 | "MapperDownloadFailedParseFailed": "谱师谱面下载失败:找不到用户(%d)!", 92 | "StartDownloadMapper": "开始下载谱师谱面(uid=%d):从 %d 到 %d,谱面状态:%s", 93 | "BeatmapStatus": "谱面状态", 94 | "BeatmapStatusDesc": "Ranked:上架 (Ranked) 谱面\nLoved:社区喜爱 (Loved) 谱面\nGuest:客串制作的谱面\nPending:待定 (Pending) 谱面\nGraveyard:已停更的谱面\nNominated:提名并上架 (Ranked) 的谱面", 95 | "StartDownloadMostPlayed": "开始下载最多游玩谱面(uid=%d):从 %d 到 %d", 96 | "MostPlayedDownloader": "最多游玩谱面下载器", 97 | "MostPlayedDownloadFailedUserNotFound": "最多游玩谱面下载失败:找不到用户(%d)!", 98 | "MostPlayedDownloadFailedResponseCodeNotOk": "最多游玩谱面下载失败:响应码:%d", 99 | "MostPlayedDownloadFailedParseFailed": "最多游玩谱面下载失败:解析响应体失败!", 100 | "MainMenuHotkey": "切换主菜单显示", 101 | "IdSearchHotkey": "切换Sid/Bid搜索界面显示", 102 | "Main": "主要", 103 | "Theme": "主题", 104 | "Paste": "粘贴", 105 | "VersionChecking": "正在检查新版本...", 106 | "VersionIsLatest": "谱面下载器已是最新版本!", 107 | "FoundNewVersion": "发现新版本:%s(%d)", 108 | "VersionCheckFailed": "版本更新检查失败!代码:%d", 109 | "CurrentVersion": "当前版本:%s(%d)", 110 | "GotoDownload": "前往下载", 111 | "CheckUpdate": "检查更新", 112 | "AlreadyHasDownloadTask": "已存在的下载任务:%d %s-%s [%s]", 113 | "BeatmapPackId": "曲包ID", 114 | "BeatmapPackDownloader": "曲包下载器", 115 | "BeatmapPackDownloadFailedNoSuchPackFound": "没有找到ID为%d(%s)的曲包!", 116 | "StartDownloadPack": "开始下载曲包:%d(%s)", 117 | "BeatmapPackDownloadFailedResponseCodeNotOk": "曲包下载失败:响应码:%d" 118 | } -------------------------------------------------------------------------------- /Downloader/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ 生成的包含文件。 3 | // 供 Resource.rc 使用 4 | // 5 | #define IDR_FONT 102 6 | 7 | #define IDR_LANG_EN_US 104 8 | #define IDR_LANG_ZH_CN 105 9 | 10 | // Next default values for new objects 11 | // 12 | #ifdef APSTUDIO_INVOKED 13 | #ifndef APSTUDIO_READONLY_SYMBOLS 14 | #define _APS_NEXT_RESOURCE_VALUE 106 15 | #define _APS_NEXT_COMMAND_VALUE 40001 16 | #define _APS_NEXT_CONTROL_VALUE 1001 17 | #define _APS_NEXT_SYMED_VALUE 101 18 | #endif 19 | #endif 20 | -------------------------------------------------------------------------------- /Downloader/src/HookManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "detours.h" 5 | 6 | class HookManager { 7 | public: 8 | template 9 | static void InstallHook(Fn original, Fn handler) { 10 | Enable(original, handler); 11 | s_FunctionMap[reinterpret_cast(handler)] = reinterpret_cast(original); 12 | } 13 | 14 | template 15 | static void UninstallHook(Fn handler) { 16 | Disable(handler); 17 | s_FunctionMap.erase(reinterpret_cast(handler)); 18 | } 19 | 20 | template 21 | static Fn GetOriginal(Fn handler) { 22 | if (s_FunctionMap.count(reinterpret_cast(handler)) == 0) { 23 | LOGE("No such original function found!"); 24 | return nullptr; 25 | } 26 | return reinterpret_cast(s_FunctionMap[reinterpret_cast(handler)]); 27 | } 28 | 29 | static void UninstallAll() noexcept { 30 | for (const auto &[k, v] : s_FunctionMap) { 31 | Disable(k); 32 | } 33 | s_FunctionMap.clear(); 34 | } 35 | 36 | template 37 | static ReturnType CallOriginal(ReturnType(*handler)(Params...), Params... params) { 38 | auto original = GetOriginal(handler); 39 | if constexpr (std::is_same_v) { 40 | if (original) original(params...); 41 | } else { 42 | return original ? original(params...) : ReturnType{}; 43 | } 44 | } 45 | 46 | #ifndef _WIN64 47 | template 48 | static ReturnType CallOriginal(ReturnType(__fastcall *handler)(Params...), Params... params) { 49 | auto original = GetOriginal(handler); 50 | if constexpr (std::is_same_v) { 51 | if (original) original(params...); 52 | } else { 53 | return original ? original(params...) : ReturnType{}; 54 | } 55 | } 56 | 57 | template 58 | static ReturnType CallOriginal(ReturnType(__stdcall *handler)(Params...), Params... params) { 59 | auto original = GetOriginal(handler); 60 | if constexpr (std::is_same_v) { 61 | if (original) original(params...); 62 | } else { 63 | return original ? original(params...) : ReturnType{}; 64 | } 65 | } 66 | #endif 67 | 68 | private: 69 | HookManager() = default; 70 | 71 | inline static std::map s_FunctionMap{}; 72 | 73 | template 74 | static void Enable(Fn &original, Fn handler) { 75 | DetourTransactionBegin(); 76 | DetourUpdateThread(GetCurrentThread()); 77 | DetourAttach(&(PVOID &)original, (PVOID)handler); 78 | DetourTransactionCommit(); 79 | } 80 | 81 | template 82 | static void Disable(Fn handler) { 83 | Fn original = GetOriginal(handler); 84 | if (!original) { 85 | return; 86 | } 87 | 88 | DetourTransactionBegin(); 89 | DetourUpdateThread(GetCurrentThread()); 90 | DetourDetach(&(PVOID &)original, handler); 91 | DetourTransactionCommit(); 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /Downloader/src/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef _DEBUG 6 | #define LOGD(msg, ...) printf_s("[DEBUG] " msg "\n", ##__VA_ARGS__) 7 | #define WLOGD(msg, ...) wprintf_s(L"[DEBUG] " msg L"\n", ##__VA_ARGS__) 8 | #else 9 | #define LOGD(msg, ...) 10 | #define WLOGD(msg, ...) 11 | #endif 12 | 13 | #define LOGI(msg, ...) printf_s("[INFO] " msg "\n", ##__VA_ARGS__) 14 | #define LOGW(msg, ...) printf_s("[WARNING] " msg "\n", ##__VA_ARGS__) 15 | #define LOGE(msg, ...) printf_s("[ERROR] " msg "\n", ##__VA_ARGS__) 16 | 17 | #define WLOGI(msg, ...) wprintf_s(L"[INFO] " msg L"\n", ##__VA_ARGS__) 18 | #define WLOGW(msg, ...) wprintf_s(L"[WARNING] " msg L"\n", ##__VA_ARGS__) 19 | #define WLOGE(msg, ...) wprintf_s(L"[ERROR] " msg L"\n", ##__VA_ARGS__) 20 | 21 | -------------------------------------------------------------------------------- /Downloader/src/api/Bancho.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Bancho.h" 3 | 4 | #include "features/Downloader.h" 5 | #include "network/HttpRequest.h" 6 | #include "utils/Utils.h" 7 | #include "utils/gui_utils.h" 8 | 9 | api::Bancho::Bancho() : Provider("Bancho", "https://github.com/ppy/osu-api/wiki", features::downloader::DownloadMirror::OsuOfficial) {} 10 | 11 | std::optional api::Bancho::searchBeatmap(const features::downloader::BeatmapInfo &info) const { 12 | auto &dl = features::Downloader::GetInstance(); 13 | 14 | auto un = dl.f_OsuAccount->username(); 15 | auto pw = dl.f_OsuAccount->password().md5(); 16 | 17 | if (un.empty() || pw.empty()) { 18 | LOGW("Search failed: Osu account not set"); 19 | return {}; 20 | } 21 | 22 | LOGD("Begin official search type=%s, id=%d", info.type == features::downloader::BeatmapType::Sid ? "Sid" : "Bid", info.id); 23 | 24 | // https://osu.ppy.sh/web/osu-search-set.php?u={username}&h={md5hash}&{type}={id}; 25 | 26 | auto url = std::format("https://osu.ppy.sh/web/osu-search-set.php?u={0}&h={1}&{2}={3}", un, pw, 27 | info.type == features::downloader::BeatmapType::Sid ? "s" : "b", info.id); 28 | // replace ' ' to "%20" 29 | for (size_t i = 0; i < url.size(); ++i) { 30 | if (url[i] == ' ') { 31 | url.replace(i, 1, "%20"); 32 | } 33 | } 34 | 35 | std::string res; 36 | int ret = -1; 37 | if (const CURLcode code = net::curl_get(url.c_str(), res, &ret); code == CURLE_OK && ret == 200) { 38 | LOGD("Official search result: %s", res.c_str()); 39 | if (res.empty()) { 40 | LOGW("Official search failed! response is empty!"); 41 | return {}; 42 | } 43 | 44 | const auto l = utils::split(res, '|'); 45 | if (l.empty()) { 46 | LOGW("Official search failed! Cannot parse result!"); 47 | return {}; 48 | } 49 | 50 | #pragma warning(push) 51 | #pragma warning(disable : 4834) 52 | 53 | try { 54 | auto bm = osu::Beatmap{}; 55 | size_t i = 0; 56 | l[i++]; // serverFilename 57 | bm.artist = l[i++]; 58 | bm.title = l[i++]; 59 | bm.author = l[i++]; 60 | l[i++]; // status 61 | /* 62 | "1" => SubmissionStatus.Ranked, 63 | "2" => SubmissionStatus.Approved, 64 | "3" => SubmissionStatus.Qualified, 65 | "4" => SubmissionStatus.Loved, 66 | _ => SubmissionStatus.Pending 67 | */ 68 | 69 | l[i++]; // rating 70 | l[i++]; // lastupdate 71 | 72 | bm.sid = std::stoi(l[i++]); 73 | l[i++]; // threadid 74 | 75 | bm.hasVideo = l[i++] == "1"; 76 | l[i++]; // hasStoryboard 77 | l[i++]; // filesize 78 | 79 | LOGD("Parsed official search result: %d %s - %s", bm.sid, bm.artist.c_str(), bm.title.c_str()); 80 | return bm; 81 | } catch (...) { 82 | LOGW("Cannot parse bancho search result, maybe beatmap not exists!"); 83 | } 84 | 85 | #pragma warning(pop) 86 | 87 | } else { 88 | LOGW("Osu official search failed: CURL_CODE=%d, RESPONSE_CODE=%d", code, ret); 89 | } 90 | 91 | return {}; 92 | } 93 | 94 | bool api::Bancho::downloadBeatmap(const osu::Beatmap &bm) const { 95 | auto &dl = features::Downloader::GetInstance(); 96 | 97 | auto un = dl.f_OsuAccount->username(); 98 | auto pw = dl.f_OsuAccount->password().md5(); 99 | 100 | if (un.empty() || pw.empty()) { 101 | LOGW("Download failed: Osu account not set"); 102 | return false; 103 | } 104 | 105 | const bool noVideo = dl.f_DownloadType.getValue() == features::downloader::DownloadType::NoVideo && bm.hasVideo; 106 | 107 | // https://osu.ppy.sh/d/{setid}?u={username}&h={md5hash}&vv=2 108 | // https://osu.ppy.sh/d/{0}{novideo}?u={username}&h={md5hash}&vv=2 109 | // https://osu.ppy.sh/d/22276?u=abcd&h=abcd&vv=2 110 | // https://osu.ppy.sh/d/22276n?u=abcd&h=abcd&vv=2 111 | auto url = std::format("https://osu.ppy.sh/d/{0}{1}?u={2}&h={3}&vv=2", bm.sid, noVideo ? "n" : "", un, pw); 112 | // replace ' ' to "%20" 113 | for (size_t i = 0; i < url.size(); ++i) { 114 | if (url[i] == ' ') { 115 | url.replace(i, 1, "%20"); 116 | } 117 | } 118 | 119 | auto path = utils::GetCurrentDirPath() / L"downloads" / (std::to_string(bm.sid) + ".osz"); 120 | auto *tsk = features::DownloadQueue::GetInstance().getTask(bm); 121 | int ret = -1; 122 | if (const CURLcode code = net::curl_download(url.c_str(), path, tsk, &ret); code == CURLE_OK && ret == 200) { 123 | LOGI("Success download beatmapsets: %d", bm.sid); 124 | return true; 125 | } else { 126 | LOGW("Osu official download failed: CURL_CODE=%d, RESPONSE_CODE=%d", ret, ret); 127 | return false; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Downloader/src/api/Bancho.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "features/Downloader.h" 3 | #include "osu/Beatmap.h" 4 | #include "Provider.h" 5 | 6 | namespace api { 7 | class Bancho : public Provider { 8 | public: 9 | Bancho(); 10 | 11 | // Inherited via Provider 12 | std::optional searchBeatmap(const features::downloader::BeatmapInfo &) const override; 13 | bool downloadBeatmap(const osu::Beatmap &) const override; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /Downloader/src/api/Chimu.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Chimu.h" 3 | #include "network/HttpRequest.h" 4 | #include "utils/Utils.h" 5 | #include "api/chimu/Mapset.hpp" 6 | #include "api/chimu/Map.hpp" 7 | 8 | int32_t api::Chimu::_convertToSetId(const features::downloader::BeatmapInfo &info) const { 9 | if (info.type == features::downloader::BeatmapType::Sid) { 10 | return info.id; 11 | } 12 | 13 | const auto s = std::format("https://api.chimu.moe/v1/map/{0}", info.id); 14 | 15 | std::string response; 16 | int32_t resCode; 17 | if (const CURLcode code = net::curl_get(s.c_str(), response, &resCode); code != CURLE_OK || resCode != 200) { 18 | LOGW("Chimu search failed: code=%d %s", resCode, response.c_str()); 19 | return -1; 20 | } 21 | 22 | const auto j = nlohmann::json::parse(response); 23 | api::chumi::ChumiMap map; 24 | api::chumi::from_json(j, map); 25 | if (map.error_message.has_value()) { 26 | LOGW("Chimu search failed: code=%d %s", resCode, map.error_message.value().c_str()); 27 | return -1; 28 | } 29 | 30 | return static_cast(map.parent_set_id.value()); 31 | } 32 | 33 | api::Chimu::Chimu() : Provider("Chimu", "https://chimu.moe/docs", features::downloader::DownloadMirror::Chimu) {} 34 | 35 | std::optional api::Chimu::searchBeatmap(const features::downloader::BeatmapInfo &info) const { 36 | int32_t id = _convertToSetId(info); 37 | if (id == -1) return {}; 38 | 39 | const auto s = std::format("https://api.chimu.moe/v1/set/{0}", id); 40 | 41 | std::string response; 42 | int32_t resCode; 43 | if (const CURLcode code = net::curl_get(s.c_str(), response, &resCode); code != CURLE_OK || resCode != 200) { 44 | LOGW("Chimu search failed: code=%d %s", resCode, response.c_str()); 45 | return {}; 46 | } 47 | 48 | const auto j = nlohmann::json::parse(response); 49 | api::chumi::ChumiMapset mapset; 50 | api::chumi::from_json(j, mapset); 51 | 52 | if (mapset.error_code.has_value() || mapset.error_message.has_value()) { 53 | LOGW("Chimu search failed: code=%s %s", mapset.error_code.value().c_str(), mapset.error_message.value().c_str()); 54 | return {}; 55 | } 56 | 57 | return mapset.to_beatmap(); 58 | } 59 | 60 | bool api::Chimu::downloadBeatmap(const osu::Beatmap &bm) const { 61 | auto &dl = features::Downloader::GetInstance(); 62 | 63 | const auto s = std::format("https://api.chimu.moe/v1/download/{0}", bm.sid); 64 | 65 | auto path = utils::GetCurrentDirPath() / L"downloads" / (std::to_string(bm.sid) + ".osz"); 66 | auto *tsk = features::DownloadQueue::GetInstance().getTask(bm); 67 | 68 | int resCode = -1; 69 | if (const auto code = net::curl_download(s.c_str(), path, tsk, &resCode); code == CURLE_OK && resCode == 200) { 70 | LOGI("Success download beatmapsets: %d", bm.sid); 71 | return true; 72 | } else { 73 | LOGW("Chimu download failed: CURL_CODE=%d, RESPONSE_CODE=%d", code, resCode); 74 | } 75 | 76 | return false; 77 | } 78 | -------------------------------------------------------------------------------- /Downloader/src/api/Chimu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Provider.h" 3 | 4 | namespace api { 5 | class Chimu : public Provider { 6 | int32_t _convertToSetId(const features::downloader::BeatmapInfo &info) const; 7 | 8 | public: 9 | Chimu(); 10 | 11 | // Inherited via Provider 12 | std::optional searchBeatmap(const features::downloader::BeatmapInfo &) const override; 13 | bool downloadBeatmap(const osu::Beatmap &) const override; 14 | }; 15 | }; // namespace api 16 | -------------------------------------------------------------------------------- /Downloader/src/api/Provider.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Provider.h" 3 | #include "Provider.h" 4 | 5 | namespace api { 6 | 7 | Provider::Provider(std::string name, std::string doc, features::downloader::DownloadMirror mirror) : 8 | _name(std::move(name)), _doc(std::move(doc)), _enum(mirror) { 9 | } 10 | 11 | std::string_view Provider::getDoc() const { return _doc; } 12 | std::string_view Provider::getName() const { return _name; } 13 | 14 | features::downloader::DownloadMirror Provider::getEnum() const { return _enum; } 15 | 16 | Provider const *Provider::GetRegisteredByName(std::string name) { 17 | auto &vec = GetRegistered(); 18 | return *std::ranges::find_if(vec, [&name](auto p) { return p->getName() == name; }); 19 | } 20 | 21 | Provider const *Provider::GetRegisteredByEnum(features::downloader::DownloadMirror mirror) { 22 | auto &vec = GetRegistered(); 23 | return *std::ranges::find_if(vec, [&mirror](auto p) { return p->getEnum() == mirror; }); 24 | } 25 | 26 | std::vector Provider::_providers; 27 | 28 | void Provider::Register(const Provider *provider) { _providers.push_back(provider); } 29 | 30 | void Provider::UnRegisterAll() { 31 | for (const auto it : _providers) { 32 | delete it; 33 | } 34 | _providers.clear(); 35 | } 36 | 37 | } // namespace api 38 | -------------------------------------------------------------------------------- /Downloader/src/api/Provider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "features/Downloader.h" 5 | 6 | namespace api { 7 | 8 | class Provider { 9 | private: 10 | static std::vector _providers; 11 | 12 | std::string _name, _doc; 13 | features::downloader::DownloadMirror _enum; 14 | 15 | public: 16 | virtual ~Provider() = default; 17 | Provider(std::string, std::string, features::downloader::DownloadMirror); 18 | 19 | std::string_view getDoc() const; 20 | std::string_view getName() const; 21 | 22 | features::downloader::DownloadMirror getEnum() const; 23 | 24 | virtual std::optional searchBeatmap(const features::downloader::BeatmapInfo &) const = 0; 25 | virtual bool downloadBeatmap(const osu::Beatmap &) const = 0; 26 | 27 | static const std::vector &GetRegistered() { return _providers; } 28 | static Provider const *GetRegisteredByName(std::string); 29 | static Provider const *GetRegisteredByEnum(features::downloader::DownloadMirror); 30 | static void Register(const Provider *provider); 31 | 32 | static void UnRegisterAll(); 33 | }; 34 | 35 | } // namespace api 36 | -------------------------------------------------------------------------------- /Downloader/src/api/Sayobot.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Sayobot.h" 3 | 4 | #include "network/HttpRequest.h" 5 | #include "utils/Utils.h" 6 | 7 | namespace api::sayobot { 8 | void BidData::to_json(nlohmann::json &j) const { 9 | j.at("bid") = bid; 10 | j.at("mode") = mode; 11 | } 12 | 13 | void BidData::from_json(const nlohmann::json &j) { 14 | j.at("bid").get_to(bid); 15 | j.at("mode").get_to(mode); 16 | } 17 | 18 | void SayoBeatmapDataV2::to_json(nlohmann::json &j) const { 19 | j.at("approved") = approved; 20 | j.at("artist") = artist; 21 | j.at("artistU") = artistU; 22 | j.at("creator") = creator; 23 | j.at("creator_id") = creatorId; 24 | j.at("title") = title; 25 | j.at("titleU") = titleU; 26 | j.at("sid") = sid; 27 | j.at("video") = video; 28 | j.at("bid_data") = bidData; 29 | } 30 | 31 | void SayoBeatmapDataV2::from_json(const nlohmann::json &j) { 32 | j.at("approved").get_to(approved); 33 | j.at("artist").get_to(artist); 34 | j.at("artistU").get_to(artistU); 35 | j.at("creator").get_to(creator); 36 | j.at("creator_id").get_to(creatorId); 37 | j.at("title").get_to(title); 38 | j.at("titleU").get_to(titleU); 39 | j.at("sid").get_to(sid); 40 | j.at("video").get_to(video); 41 | j.at("bid_data").get_to(bidData); 42 | } 43 | 44 | osu::Beatmap SayoBeatmapDataV2::to_beatmap() const { 45 | std::vector bids; 46 | bids.reserve(bidData.size()); 47 | for (auto &bd : bidData) { 48 | bids.push_back(bd.bid); 49 | } 50 | return {title, artist, creator, bids, sid, video != 0}; 51 | } 52 | 53 | }; 54 | 55 | void nlohmann::adl_serializer::to_json(nlohmann::json &j, const api::sayobot::BidData &data) { data.to_json(j); } 56 | 57 | void nlohmann::adl_serializer::from_json(const nlohmann::json &j, api::sayobot::BidData &data) { data.from_json(j); } 58 | 59 | api::Sayobot::Sayobot() : 60 | Provider("Sayobot", "https://www.showdoc.com.cn/SoulDee?page_id=3969517351482508", features::downloader::DownloadMirror::Sayobot) {} 61 | 62 | std::optional api::Sayobot::searchBeatmap(const features::downloader::BeatmapInfo &info) const { 63 | const auto s = std::format("https://api.sayobot.cn/v2/beatmapinfo?K={0}&T={1}", info.id, (uint8_t)info.type); 64 | int resCode = -1; 65 | std::string response; 66 | 67 | LOGD("Begin sayobot search type=%s, id=%d", info.type == features::downloader::BeatmapType::Sid ? "Auto" : "Bid", info.id); 68 | 69 | if (const CURLcode code = net::curl_get(s.c_str(), response, &resCode); code == CURLE_OK && resCode == 200) { 70 | // LOGD("Sayo search response: %s", response.c_str()); 71 | const auto j = nlohmann::json::parse(response); 72 | api::sayobot::SayoResult data; 73 | data.from_json(j); 74 | 75 | if (data.status == 0) { 76 | if (data.data.has_value()) { 77 | return std::optional(data.data.value().to_beatmap()); 78 | } else { 79 | return {}; 80 | } 81 | } 82 | 83 | LOGW("Sayo search failed: code=%d", data.status); 84 | } else { 85 | LOGW("Sayo search failed: CURL_CODE=%d, RESPONSE_CODE=%d", code, resCode); 86 | } 87 | 88 | return {}; 89 | } 90 | 91 | bool api::Sayobot::downloadBeatmap(const osu::Beatmap &bm) const { 92 | auto &dl = features::Downloader::GetInstance(); 93 | 94 | const auto s = std::format("https://txy1.sayobot.cn/beatmaps/download/{0}/{1}?server=auto", 95 | dl.f_DownloadType.getValue() == features::downloader::DownloadType::NoVideo ? "novideo" : "full", bm.sid); 96 | 97 | auto path = utils::GetCurrentDirPath() / L"downloads" / (std::to_string(bm.sid) + ".osz"); 98 | auto *tsk = features::DownloadQueue::GetInstance().getTask(bm); 99 | 100 | int resCode = -1; 101 | if (const auto code = net::curl_download(s.c_str(), path, tsk, &resCode); code == CURLE_OK && resCode == 200) { 102 | LOGI("Success download beatmapsets: %d", bm.sid); 103 | return true; 104 | } else { 105 | LOGW("Sayo download failed: CURL_CODE=%d, RESPONSE_CODE=%d", code, resCode); 106 | } 107 | 108 | return false; 109 | } 110 | -------------------------------------------------------------------------------- /Downloader/src/api/Sayobot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "misc/ISerializable.h" 6 | #include "features/Downloader.h" 7 | #include "Provider.h" 8 | 9 | namespace api { 10 | class Sayobot : public Provider { 11 | public: 12 | Sayobot(); 13 | 14 | // Inherited via Provider 15 | virtual std::optional searchBeatmap(const features::downloader::BeatmapInfo &) const override; 16 | virtual bool downloadBeatmap(const osu::Beatmap &) const override; 17 | }; 18 | }; 19 | 20 | namespace api::sayobot { 21 | struct BidData : ISerializable { 22 | int bid; 23 | int mode; 24 | 25 | void to_json(nlohmann::json &j) const override; 26 | void from_json(const nlohmann::json &j) override; 27 | }; 28 | 29 | struct SayoBeatmapDataV2 : ISerializable { 30 | int approved; 31 | std::string artist; 32 | std::string artistU; 33 | std::string creator; 34 | int creatorId; 35 | std::string title; 36 | std::string titleU; 37 | int sid; 38 | int video; 39 | std::vector bidData; 40 | 41 | void to_json(nlohmann::json &j) const override; 42 | void from_json(const nlohmann::json &j) override; 43 | 44 | osu::Beatmap to_beatmap() const; 45 | }; 46 | 47 | template >* = nullptr> 48 | struct SayoResult : ISerializable { 49 | int status; 50 | std::optional data; 51 | 52 | void to_json(nlohmann::json &j) const override; 53 | void from_json(const nlohmann::json &j) override; 54 | }; 55 | 56 | template >*E0> 57 | void SayoResult::to_json(nlohmann::json &j) const { 58 | j["status"] = status; 59 | if (data) { 60 | auto &d = *data; 61 | d.to_json(j["data"]); 62 | } 63 | } 64 | 65 | template >*E0> 66 | void SayoResult::from_json(const nlohmann::json &j) { 67 | status = j["status"]; 68 | if (status == 0) { 69 | if (!data) data = T(); 70 | auto &d = *data; 71 | d.from_json(j["data"]); 72 | } 73 | } 74 | 75 | } 76 | 77 | template <> 78 | struct nlohmann::adl_serializer { 79 | static void to_json(nlohmann::json &j, const api::sayobot::BidData &data); 80 | 81 | static void from_json(const nlohmann::json &j, api::sayobot::BidData &data); 82 | }; 83 | -------------------------------------------------------------------------------- /Downloader/src/api/chimu/Map.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // To parse this JSON data, first install 3 | // 4 | // json.hpp https://github.com/nlohmann/json 5 | // 6 | // Then include this file, and then do 7 | // 8 | // ChumiMap data = nlohmann::json::parse(jsonString); 9 | 10 | #pragma once 11 | 12 | #include 13 | #include "json.hpp" 14 | 15 | #ifndef NLOHMANN_OPT_HELPER 16 | #define NLOHMANN_OPT_HELPER 17 | namespace nlohmann { 18 | template 19 | struct adl_serializer> { 20 | static void to_json(json &j, const std::shared_ptr &opt) { 21 | if (!opt) 22 | j = nullptr; 23 | else 24 | j = *opt; 25 | } 26 | 27 | static std::shared_ptr from_json(const json &j) { 28 | if (j.is_null()) 29 | return std::make_shared(); 30 | else 31 | return std::make_shared(j.get()); 32 | } 33 | }; 34 | template 35 | struct adl_serializer> { 36 | static void to_json(json &j, const std::optional &opt) { 37 | if (!opt) 38 | j = nullptr; 39 | else 40 | j = *opt; 41 | } 42 | 43 | static std::optional from_json(const json &j) { 44 | if (j.is_null()) 45 | return std::make_optional(); 46 | else 47 | return std::make_optional(j.get()); 48 | } 49 | }; 50 | } // namespace nlohmann 51 | #endif 52 | 53 | namespace api { 54 | namespace chumi { 55 | using nlohmann::json; 56 | 57 | #ifndef NLOHMANN_UNTYPED_api_chumi_HELPER 58 | #define NLOHMANN_UNTYPED_api_chumi_HELPER 59 | inline json get_untyped(const json &j, const char *property) { 60 | if (j.find(property) != j.end()) { 61 | return j.at(property).get(); 62 | } 63 | return json(); 64 | } 65 | 66 | inline json get_untyped(const json &j, std::string property) { return get_untyped(j, property.data()); } 67 | #endif 68 | 69 | #ifndef NLOHMANN_OPTIONAL_api_chumi_HELPER 70 | #define NLOHMANN_OPTIONAL_api_chumi_HELPER 71 | template 72 | inline std::shared_ptr get_heap_optional(const json &j, const char *property) { 73 | auto it = j.find(property); 74 | if (it != j.end() && !it->is_null()) { 75 | return j.at(property).get>(); 76 | } 77 | return std::shared_ptr(); 78 | } 79 | 80 | template 81 | inline std::shared_ptr get_heap_optional(const json &j, std::string property) { 82 | return get_heap_optional(j, property.data()); 83 | } 84 | template 85 | inline std::optional get_stack_optional(const json &j, const char *property) { 86 | auto it = j.find(property); 87 | if (it != j.end() && !it->is_null()) { 88 | return j.at(property).get>(); 89 | } 90 | return std::optional(); 91 | } 92 | 93 | template 94 | inline std::optional get_stack_optional(const json &j, std::string property) { 95 | return get_stack_optional(j, property.data()); 96 | } 97 | #endif 98 | 99 | struct ChumiMap { 100 | std::optional beatmap_id; 101 | std::optional parent_set_id; 102 | std::optional diff_name; 103 | std::optional file_md5; 104 | std::optional mode; 105 | std::optional bpm; 106 | std::optional ar; 107 | std::optional od; 108 | std::optional cs; 109 | std::optional hp; 110 | std::optional total_length; 111 | std::optional hit_length; 112 | std::optional playcount; 113 | std::optional passcount; 114 | std::optional max_combo; 115 | std::optional difficulty_rating; 116 | std::optional osu_file; 117 | std::optional download_path; 118 | std::optional error_message; 119 | std::optional error_code; 120 | }; 121 | } // namespace chumi 122 | } // namespace api 123 | 124 | namespace api { 125 | namespace chumi { 126 | void from_json(const json &j, ChumiMap &x); 127 | void to_json(json &j, const ChumiMap &x); 128 | 129 | inline void from_json(const json &j, ChumiMap &x) { 130 | x.beatmap_id = get_stack_optional(j, "BeatmapId"); 131 | x.parent_set_id = get_stack_optional(j, "ParentSetId"); 132 | x.diff_name = get_stack_optional(j, "DiffName"); 133 | x.file_md5 = get_stack_optional(j, "FileMD5"); 134 | x.mode = get_stack_optional(j, "Mode"); 135 | x.bpm = get_stack_optional(j, "BPM"); 136 | x.ar = get_stack_optional(j, "AR"); 137 | x.od = get_stack_optional(j, "OD"); 138 | x.cs = get_stack_optional(j, "CS"); 139 | x.hp = get_stack_optional(j, "HP"); 140 | x.total_length = get_stack_optional(j, "TotalLength"); 141 | x.hit_length = get_stack_optional(j, "HitLength"); 142 | x.playcount = get_stack_optional(j, "Playcount"); 143 | x.passcount = get_stack_optional(j, "Passcount"); 144 | x.max_combo = get_stack_optional(j, "MaxCombo"); 145 | x.difficulty_rating = get_stack_optional(j, "DifficultyRating"); 146 | x.osu_file = get_stack_optional(j, "OsuFile"); 147 | x.download_path = get_stack_optional(j, "DownloadPath"); 148 | x.error_message = get_stack_optional(j, "error_message"); 149 | x.error_code = get_stack_optional(j, "error_code"); 150 | } 151 | 152 | inline void to_json(json &j, const ChumiMap &x) { 153 | j = json::object(); 154 | j["BeatmapId"] = x.beatmap_id; 155 | j["ParentSetId"] = x.parent_set_id; 156 | j["DiffName"] = x.diff_name; 157 | j["FileMD5"] = x.file_md5; 158 | j["Mode"] = x.mode; 159 | j["BPM"] = x.bpm; 160 | j["AR"] = x.ar; 161 | j["OD"] = x.od; 162 | j["CS"] = x.cs; 163 | j["HP"] = x.hp; 164 | j["TotalLength"] = x.total_length; 165 | j["HitLength"] = x.hit_length; 166 | j["Playcount"] = x.playcount; 167 | j["Passcount"] = x.passcount; 168 | j["MaxCombo"] = x.max_combo; 169 | j["DifficultyRating"] = x.difficulty_rating; 170 | j["OsuFile"] = x.osu_file; 171 | j["DownloadPath"] = x.download_path; 172 | j["error_message"] = x.error_message; 173 | j["error_code"] = x.error_code; 174 | } 175 | } // namespace chumi 176 | } // namespace api 177 | -------------------------------------------------------------------------------- /Downloader/src/api/chimu/Mapset.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // To parse this JSON data, first install 3 | // 4 | // json.hpp https://github.com/nlohmann/json 5 | // 6 | // Then include this file, and then do 7 | // 8 | // ChumiMapset data = nlohmann::json::parse(jsonString); 9 | 10 | #pragma once 11 | 12 | #include 13 | #include "json.hpp" 14 | 15 | #ifndef NLOHMANN_OPT_HELPER 16 | #define NLOHMANN_OPT_HELPER 17 | namespace nlohmann { 18 | template 19 | struct adl_serializer> { 20 | static void to_json(json &j, const std::shared_ptr &opt) { 21 | if (!opt) 22 | j = nullptr; 23 | else 24 | j = *opt; 25 | } 26 | 27 | static std::shared_ptr from_json(const json &j) { 28 | if (j.is_null()) 29 | return std::make_shared(); 30 | else 31 | return std::make_shared(j.get()); 32 | } 33 | }; 34 | template 35 | struct adl_serializer> { 36 | static void to_json(json &j, const std::optional &opt) { 37 | if (!opt) 38 | j = nullptr; 39 | else 40 | j = *opt; 41 | } 42 | 43 | static std::optional from_json(const json &j) { 44 | if (j.is_null()) 45 | return std::make_optional(); 46 | else 47 | return std::make_optional(j.get()); 48 | } 49 | }; 50 | } // namespace nlohmann 51 | #endif 52 | 53 | namespace api { 54 | namespace chumi { 55 | using nlohmann::json; 56 | 57 | #ifndef NLOHMANN_UNTYPED_api_chumi_HELPER 58 | #define NLOHMANN_UNTYPED_api_chumi_HELPER 59 | inline json get_untyped(const json &j, const char *property) { 60 | if (j.find(property) != j.end()) { 61 | return j.at(property).get(); 62 | } 63 | return json(); 64 | } 65 | 66 | inline json get_untyped(const json &j, std::string property) { return get_untyped(j, property.data()); } 67 | #endif 68 | 69 | #ifndef NLOHMANN_OPTIONAL_api_chumi_HELPER 70 | #define NLOHMANN_OPTIONAL_api_chumi_HELPER 71 | template 72 | inline std::shared_ptr get_heap_optional(const json &j, const char *property) { 73 | auto it = j.find(property); 74 | if (it != j.end() && !it->is_null()) { 75 | return j.at(property).get>(); 76 | } 77 | return std::shared_ptr(); 78 | } 79 | 80 | template 81 | inline std::shared_ptr get_heap_optional(const json &j, std::string property) { 82 | return get_heap_optional(j, property.data()); 83 | } 84 | template 85 | inline std::optional get_stack_optional(const json &j, const char *property) { 86 | auto it = j.find(property); 87 | if (it != j.end() && !it->is_null()) { 88 | return j.at(property).get>(); 89 | } 90 | return std::optional(); 91 | } 92 | 93 | template 94 | inline std::optional get_stack_optional(const json &j, std::string property) { 95 | return get_stack_optional(j, property.data()); 96 | } 97 | #endif 98 | 99 | struct ChildrenBeatmap { 100 | std::optional beatmap_id; 101 | std::optional parent_set_id; 102 | std::optional diff_name; 103 | std::optional file_md5; 104 | std::optional mode; 105 | std::optional bpm; 106 | std::optional ar; 107 | std::optional od; 108 | std::optional cs; 109 | std::optional hp; 110 | std::optional total_length; 111 | std::optional hit_length; 112 | std::optional playcount; 113 | std::optional passcount; 114 | std::optional max_combo; 115 | std::optional difficulty_rating; 116 | std::optional osu_file; 117 | std::optional download_path; 118 | }; 119 | 120 | struct ChumiMapset { 121 | std::optional set_id; 122 | std::optional> children_beatmaps; 123 | std::optional ranked_status; 124 | std::optional approved_date; 125 | std::optional last_update; 126 | std::optional last_checked; 127 | std::optional artist; 128 | std::optional title; 129 | std::optional creator; 130 | std::optional source; 131 | std::optional tags; 132 | std::optional has_video; 133 | std::optional genre; 134 | std::optional language; 135 | std::optional favourites; 136 | std::optional disabled; 137 | std::optional error_message; 138 | std::optional error_code; 139 | 140 | osu::Beatmap to_beatmap() const; 141 | }; 142 | 143 | osu::Beatmap ChumiMapset::to_beatmap() const { 144 | std::vector ids; 145 | ids.reserve(children_beatmaps.value().size()); 146 | for (auto it : children_beatmaps.value()) { 147 | ids.push_back(static_cast(it.beatmap_id.value())); 148 | } 149 | return {title.value(), artist.value(), creator.value(), ids, static_cast(set_id.value()), has_video.value() != 0}; 150 | } 151 | 152 | } // namespace chumi 153 | } // namespace api 154 | 155 | namespace api { 156 | namespace chumi { 157 | void from_json(const json &j, ChildrenBeatmap &x); 158 | void to_json(json &j, const ChildrenBeatmap &x); 159 | 160 | void from_json(const json &j, ChumiMapset &x); 161 | void to_json(json &j, const ChumiMapset &x); 162 | 163 | inline void from_json(const json &j, ChildrenBeatmap &x) { 164 | x.beatmap_id = get_stack_optional(j, "BeatmapId"); 165 | x.parent_set_id = get_stack_optional(j, "ParentSetId"); 166 | x.diff_name = get_stack_optional(j, "DiffName"); 167 | x.file_md5 = get_stack_optional(j, "FileMD5"); 168 | x.mode = get_stack_optional(j, "Mode"); 169 | x.bpm = get_stack_optional(j, "BPM"); 170 | x.ar = get_stack_optional(j, "AR"); 171 | x.od = get_stack_optional(j, "OD"); 172 | x.cs = get_stack_optional(j, "CS"); 173 | x.hp = get_stack_optional(j, "HP"); 174 | x.total_length = get_stack_optional(j, "TotalLength"); 175 | x.hit_length = get_stack_optional(j, "HitLength"); 176 | x.playcount = get_stack_optional(j, "Playcount"); 177 | x.passcount = get_stack_optional(j, "Passcount"); 178 | x.max_combo = get_stack_optional(j, "MaxCombo"); 179 | x.difficulty_rating = get_stack_optional(j, "DifficultyRating"); 180 | x.osu_file = get_stack_optional(j, "OsuFile"); 181 | x.download_path = get_stack_optional(j, "DownloadPath"); 182 | } 183 | 184 | inline void to_json(json &j, const ChildrenBeatmap &x) { 185 | j = json::object(); 186 | j["BeatmapId"] = x.beatmap_id; 187 | j["ParentSetId"] = x.parent_set_id; 188 | j["DiffName"] = x.diff_name; 189 | j["FileMD5"] = x.file_md5; 190 | j["Mode"] = x.mode; 191 | j["BPM"] = x.bpm; 192 | j["AR"] = x.ar; 193 | j["OD"] = x.od; 194 | j["CS"] = x.cs; 195 | j["HP"] = x.hp; 196 | j["TotalLength"] = x.total_length; 197 | j["HitLength"] = x.hit_length; 198 | j["Playcount"] = x.playcount; 199 | j["Passcount"] = x.passcount; 200 | j["MaxCombo"] = x.max_combo; 201 | j["DifficultyRating"] = x.difficulty_rating; 202 | j["OsuFile"] = x.osu_file; 203 | j["DownloadPath"] = x.download_path; 204 | } 205 | 206 | inline void from_json(const json &j, ChumiMapset &x) { 207 | x.set_id = get_stack_optional(j, "SetId"); 208 | x.children_beatmaps = get_stack_optional>(j, "ChildrenBeatmaps"); 209 | x.ranked_status = get_stack_optional(j, "RankedStatus"); 210 | x.approved_date = get_stack_optional(j, "ApprovedDate"); 211 | x.last_update = get_stack_optional(j, "LastUpdate"); 212 | x.last_checked = get_stack_optional(j, "LastChecked"); 213 | x.artist = get_stack_optional(j, "Artist"); 214 | x.title = get_stack_optional(j, "Title"); 215 | x.creator = get_stack_optional(j, "Creator"); 216 | x.source = get_stack_optional(j, "Source"); 217 | x.tags = get_stack_optional(j, "Tags"); 218 | x.has_video = get_stack_optional(j, "HasVideo"); 219 | x.genre = get_stack_optional(j, "Genre"); 220 | x.language = get_stack_optional(j, "Language"); 221 | x.favourites = get_stack_optional(j, "Favourites"); 222 | x.disabled = get_stack_optional(j, "Disabled"); 223 | x.error_message = get_stack_optional(j, "error_message"); 224 | x.error_code = get_stack_optional(j, "error_code"); 225 | } 226 | 227 | inline void to_json(json &j, const ChumiMapset &x) { 228 | j = json::object(); 229 | j["SetId"] = x.set_id; 230 | j["ChildrenBeatmaps"] = x.children_beatmaps; 231 | j["RankedStatus"] = x.ranked_status; 232 | j["ApprovedDate"] = x.approved_date; 233 | j["LastUpdate"] = x.last_update; 234 | j["LastChecked"] = x.last_checked; 235 | j["Artist"] = x.artist; 236 | j["Title"] = x.title; 237 | j["Creator"] = x.creator; 238 | j["Source"] = x.source; 239 | j["Tags"] = x.tags; 240 | j["HasVideo"] = x.has_video; 241 | j["Genre"] = x.genre; 242 | j["Language"] = x.language; 243 | j["Favourites"] = x.favourites; 244 | j["Disabled"] = x.disabled; 245 | j["error_message"] = x.error_message; 246 | j["error_code"] = x.error_code; 247 | } 248 | } // namespace chumi 249 | } // namespace api 250 | -------------------------------------------------------------------------------- /Downloader/src/config/Field.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | #include 4 | #include "Field.h" 5 | #include "utils/Utils.h" 6 | 7 | static nlohmann::json g_config; 8 | static std::vector g_fields; 9 | static std::mutex g_lock; 10 | 11 | void config::Register(ISerializable *cfg) { 12 | g_fields.push_back(cfg); 13 | } 14 | 15 | void config::Init() { 16 | std::lock_guard lock(g_lock); 17 | 18 | auto cfgPath = utils::GetCurrentDirPath() / "cfg.json"; 19 | if (exists(cfgPath)) { 20 | std::ifstream ifs(cfgPath, std::ios::binary); 21 | try { 22 | ifs >> g_config; 23 | } catch (...) { 24 | LOGW("Config file broken, reset to default"); 25 | } 26 | } 27 | } 28 | 29 | void config::Load(ISerializable *cfg) { 30 | cfg->from_json(g_config); 31 | } 32 | 33 | void config::Save() { 34 | std::lock_guard lock(g_lock); 35 | 36 | for (auto &i : g_fields) { 37 | i->to_json(g_config); 38 | } 39 | auto cfgPath = utils::GetCurrentDirPath() / "cfg.json"; 40 | std::ofstream ofs(cfgPath, std::ios::binary | std::ios::trunc); 41 | ofs << g_config.dump(); 42 | } -------------------------------------------------------------------------------- /Downloader/src/config/Field.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "misc/ISerializable.h" 4 | 5 | namespace config { 6 | void Register(ISerializable *cfg); 7 | void Load(ISerializable *cfg); 8 | void Init(); 9 | void Save(); 10 | 11 | template 12 | class Field : ISerializable { 13 | std::string_view name; 14 | T value; 15 | T defaultValue; 16 | 17 | public: 18 | Field(std::string_view name, T defaultValue = T{}) : name(name), defaultValue(defaultValue) { 19 | Register(this); 20 | Load(this); 21 | } 22 | 23 | std::string_view getName() const { 24 | return name; 25 | } 26 | 27 | T& getValue() { 28 | return value; 29 | } 30 | 31 | void setValue(T _value) { 32 | this->value = _value; 33 | } 34 | 35 | operator T&() { 36 | return value; 37 | } 38 | 39 | Field& operator=(T _value) { 40 | setValue(_value); 41 | return *this; 42 | } 43 | 44 | T* operator->() { 45 | return &value; 46 | } 47 | 48 | T* getPtr() { 49 | return &value; 50 | } 51 | 52 | void reset() { 53 | value = defaultValue; 54 | } 55 | 56 | void to_json(nlohmann::json &j) const override { 57 | j[name] = value; 58 | } 59 | 60 | void from_json(const nlohmann::json &j) override { 61 | if (j.contains(name)) { 62 | value = j[name].get(); 63 | } else { 64 | value = defaultValue; 65 | } 66 | } 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /Downloader/src/config/I18nManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Field.h" 6 | #include "misc/ResourcesLoader.hpp" 7 | #include "../resource.h" 8 | 9 | namespace i18n { 10 | enum class Language : uint8_t { 11 | EN_US = 0, 12 | ZH_CN = 1, 13 | }; 14 | 15 | class I18nManager { 16 | // i18nMap["en_us"]["Downloader"] -> "Downloader" 17 | inline static std::map> i18nMap; 18 | 19 | static const char *GetLanguageName(Language lang) { 20 | switch (lang) { 21 | case Language::EN_US: 22 | return "EN_US"; 23 | case Language::ZH_CN: 24 | return "ZH_CN"; 25 | } 26 | 27 | return "EN_US"; 28 | } 29 | 30 | public: 31 | config::Field lang; 32 | 33 | static I18nManager &GetInstance() { 34 | static I18nManager instance; 35 | return instance; 36 | } 37 | 38 | std::string_view getText(std::string_view key) { 39 | if (auto &map = i18nMap[GetLanguageName(lang)]; map.contains(key.data())) { 40 | return map[key.data()]; 41 | } 42 | if (i18nMap["EN_US"].contains(key.data())) { 43 | return i18nMap["EN_US"][key.data()]; 44 | } 45 | return key; 46 | } 47 | 48 | static std::string_view GetText(std::string_view key) { 49 | return GetInstance().getText(key); 50 | } 51 | 52 | std::string_view getText(const char *key) { 53 | if (auto &map = i18nMap[GetLanguageName(lang)]; map.contains(key)) { 54 | return map[key]; 55 | } 56 | if (i18nMap["EN_US"].contains(key)) { 57 | return i18nMap["EN_US"][key]; 58 | } 59 | return key; 60 | } 61 | 62 | static std::string_view GetText(const char *key) { 63 | return GetInstance().getText(key); 64 | } 65 | 66 | const char *getTextCStr(std::string_view key) { 67 | if (auto &map = i18nMap[GetLanguageName(lang)]; map.contains(key.data())) { 68 | return map[key.data()].c_str(); 69 | } 70 | if (i18nMap["EN_US"].contains(key.data())) { 71 | return i18nMap["EN_US"][key.data()].c_str(); 72 | } 73 | return key.data(); 74 | } 75 | 76 | static const char *GetTextCStr(std::string_view key) { 77 | return GetInstance().getTextCStr(key); 78 | } 79 | 80 | const char *getTextCStr(const char *key) { 81 | if (auto &map = i18nMap[GetLanguageName(lang)]; map.contains(key)) { 82 | return map[key].c_str(); 83 | } 84 | if (i18nMap["EN_US"].contains(key)) { 85 | return i18nMap["EN_US"][key].c_str(); 86 | } 87 | return key; 88 | } 89 | 90 | static const char *GetTextCStr(const char *key) { 91 | return GetInstance().getTextCStr(key); 92 | } 93 | 94 | private: 95 | I18nManager() : 96 | lang(config::Field("Language", Language::EN_US)) { 97 | BYTE *data = nullptr; 98 | 99 | #define LOAD_LANG_FILE(lang) try { \ 100 | if (res::LoadEx(IDR_LANG_##lang, L"LANG", &data)) { \ 101 | std::string s = std::string(reinterpret_cast(data)); \ 102 | nlohmann::json j = nlohmann::json::parse(s); \ 103 | for (auto &i : j.items()) i18nMap[#lang][i.key()] = i.value(); \ 104 | LOGD("Loaded language file: %s, json size: %zu, map size: %zu", #lang, j.size(), i18nMap[#lang].size()); \ 105 | } else { \ 106 | LOGD("Cannot load language file: %s", #lang); \ 107 | } \ 108 | } catch (...) { \ 109 | LOGE("Cannot initialize language file: %s", #lang); \ 110 | } 111 | 112 | LOAD_LANG_FILE(EN_US) 113 | LOAD_LANG_FILE(ZH_CN) 114 | 115 | #undef LOAD_LANG_FILE 116 | } 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /Downloader/src/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "main.hpp" 3 | #include "config/Field.h" 4 | 5 | BOOL APIENTRY DllMain(HMODULE hModule, 6 | DWORD ul_reason_for_call, 7 | LPVOID lpReserved 8 | ) { 9 | switch (ul_reason_for_call) { 10 | case DLL_PROCESS_ATTACH: 11 | CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Run, new HMODULE(hModule), 0, NULL); 12 | break; 13 | case DLL_THREAD_ATTACH: 14 | break; 15 | case DLL_THREAD_DETACH: 16 | break; 17 | case DLL_PROCESS_DETACH: 18 | if (renderer::g_msgHook != NULL) { 19 | UnhookWindowsHookEx(renderer::g_msgHook); 20 | } 21 | config::Save(); 22 | api::Provider::UnRegisterAll(); 23 | break; 24 | } 25 | return TRUE; 26 | } 27 | -------------------------------------------------------------------------------- /Downloader/src/dlver.h.default: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LATEST_UPDATE_TIMESTAMP 0 4 | #define DOWNLOADER_VERSION 0 5 | #define DOWNLOADER_VERSION_STR "unknown" 6 | -------------------------------------------------------------------------------- /Downloader/src/features/About.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "About.h" 3 | 4 | #include "CustomHotkey.h" 5 | #include "config/I18nManager.h" 6 | #include "misc/Color.h" 7 | #include "utils/gui_utils.h" 8 | #include 9 | #include "misc/VersionManager.h" 10 | 11 | namespace features { 12 | 13 | About::About() : 14 | Feature() { 15 | } 16 | 17 | FeatureInfo &About::getInfo() { 18 | static auto info = FeatureInfo{ 19 | .category = "About", 20 | .groupName = "" 21 | }; 22 | 23 | return info; 24 | } 25 | 26 | using misc::VersionManager; 27 | 28 | void About::drawMain() { 29 | auto &lang = i18n::I18nManager::GetInstance(); 30 | ImGui::BeginGroupPanel(lang.getTextCStr("About")); 31 | ImGui::Text("%s", lang.getTextCStr("ProjectAuthor")); 32 | ImGui::Text("%s", lang.getTextCStr("ProjectLink")); 33 | ImGui::SameLine(); 34 | ImGui::TextUrl("https://github.com/KyuubiRan/BeatmapDownloader"); 35 | ImGui::TextColored(color::Green, lang.getTextCStr("CurrentVersion"), VersionManager::GetCurrentVersionName().data(), 36 | VersionManager::GetCurrentVersionCode()); 37 | ImGui::SameLine(); 38 | if (ImGui::Button(lang.getTextCStr("CheckUpdate"))) { 39 | VersionManager::CheckUpdate(true); 40 | } 41 | if (!VersionManager::IsLatest()) { 42 | ImGui::TextColored(color::Orange, lang.getTextCStr("FoundNewVersion"), VersionManager::GetLatestVersionName().data(), 43 | VersionManager::GetLatestVersionCode()); 44 | ImGui::SameLine(); 45 | if (ImGui::Button(lang.getTextCStr("GotoDownload"))) { 46 | ShellExecuteW(nullptr, L"open", L"https://github.com/KyuubiRan/BeatmapDownloader/releases/latest", nullptr, nullptr, SW_HIDE); 47 | } 48 | } 49 | ImGui::EndGroupPanel(); 50 | 51 | ImGui::BeginGroupPanel(lang.getTextCStr("Hotkey")); 52 | auto &htk = features::CustomHotkey::GetInstance(); 53 | ImGui::Text(lang.getTextCStr("HotkeyDesc"), htk.f_MainMenuHotkey->toString().c_str(), htk.f_SearchHotkey->toString().c_str()); 54 | ImGui::EndGroupPanel(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Downloader/src/features/About.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Feature.h" 3 | 4 | namespace features { 5 | 6 | class About : public Feature { 7 | About(); 8 | 9 | public: 10 | static About &GetInstance() { 11 | static About instance; 12 | return instance; 13 | } 14 | 15 | FeatureInfo &getInfo() override; 16 | 17 | void drawMain() override; 18 | 19 | }; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Downloader/src/features/CustomHotkey.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "CustomHotkey.h" 3 | 4 | #include "Feature.h" 5 | #include "ui/BeatmapIdSearchUi.h" 6 | #include "ui/MainUi.h" 7 | #include "utils/gui_utils.h" 8 | 9 | namespace features { 10 | 11 | CustomHotkey::CustomHotkey() : 12 | Feature(), 13 | f_MainMenuHotkey("MainMenuHotkey", {VK_HOME}), 14 | f_SearchHotkey("SearchHotkey", {VK_END}) { 15 | f_MainMenuHotkey.getValue() += [] { 16 | ui::main::ToggleShow(); 17 | }; 18 | f_SearchHotkey.getValue() += [] { 19 | ui::search::beatmapid::ToggleShow(); 20 | }; 21 | LOGI("Inited Hotkey"); 22 | } 23 | 24 | void CustomHotkey::drawMain() { 25 | GET_LANG(); 26 | ImGui::BeginGroupPanel(lang.getTextCStr("Main")); 27 | ImGui::HotkeyWidget(lang.getTextCStr("MainMenuHotkey"), f_MainMenuHotkey); 28 | ImGui::HotkeyWidget(lang.getTextCStr("IdSearchHotkey"), f_SearchHotkey); 29 | ImGui::EndGroupPanel(); 30 | } 31 | 32 | FeatureInfo &CustomHotkey::getInfo() { 33 | static FeatureInfo info{ 34 | "Hotkey", ""}; 35 | return info; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Downloader/src/features/CustomHotkey.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Feature.h" 3 | #include "config/Field.h" 4 | #include "misc/Hotkey.hpp" 5 | 6 | namespace features { 7 | 8 | class CustomHotkey : public Feature { 9 | CustomHotkey(); 10 | 11 | public: 12 | config::Field f_MainMenuHotkey; 13 | config::Field f_SearchHotkey; 14 | 15 | static CustomHotkey &GetInstance() { 16 | static CustomHotkey inst; 17 | return inst; 18 | } 19 | 20 | void drawMain() override; 21 | FeatureInfo &getInfo() override; 22 | }; 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Downloader/src/features/DownloadQueue.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DownloadQueue.h" 3 | 4 | #include 5 | 6 | #include "Downloader.h" 7 | #include "config/I18nManager.h" 8 | #include "utils/gui_utils.h" 9 | 10 | namespace features { 11 | 12 | std::set removed{}; 13 | 14 | void DownloadQueue::cancel(const int sid) const { 15 | if (const auto iter = findTaskBySid(sid); iter == m_TaskQueueSet.end()) { 16 | LOGW("No download task: %d", sid); 17 | return; 18 | } 19 | removed.insert(sid); 20 | Downloader::CancelDownload(sid); 21 | } 22 | 23 | bool DownloadQueue::addTask(const osu::Beatmap &bm) { 24 | std::unique_lock _g(m_Mutex); 25 | 26 | if (const auto iter = findTaskBySid(bm.sid); iter != m_TaskQueueSet.end()) { 27 | LOGW("Already has download task: %d %s-%s (%s)", bm.sid, bm.artist.c_str(), bm.title.c_str(), bm.author.c_str()); 28 | return false; 29 | } 30 | m_TaskQueueSet.insert(DownloadTask{ 31 | .bm = bm, 32 | .insertTime = GetTickCount64(), 33 | }); 34 | 35 | return true; 36 | } 37 | 38 | DownloadTask *DownloadQueue::getTask(const osu::Beatmap &bm) { 39 | return getTask(bm.sid); 40 | } 41 | 42 | DownloadTask *DownloadQueue::getTask(int sid) { 43 | std::unique_lock _g(m_Mutex); 44 | if (const auto iter = findTaskBySid(sid); iter != m_TaskQueueSet.end()) { 45 | auto *ret = &const_cast(*iter); 46 | ret->started = true; 47 | return ret; 48 | } 49 | 50 | return nullptr; 51 | } 52 | 53 | void DownloadQueue::notifyFinished(const int sid) { 54 | std::unique_lock _g(m_Mutex); 55 | if (const auto iter = findTaskBySid(sid); iter != m_TaskQueueSet.end()) { 56 | m_TaskQueueSet.erase(iter); 57 | } 58 | Downloader::RemoveCancelDownload(sid); 59 | } 60 | 61 | DownloadQueue::DownloadQueue() { 62 | LOGI("Inited Download Queue"); 63 | } 64 | 65 | void DownloadQueue::drawTaskItem(const DownloadTask &item) const { 66 | auto &lang = i18n::I18nManager::GetInstance(); 67 | ImGui::BeginGroupPanel(std::to_string(item.bm.sid).c_str()); 68 | 69 | ImGui::Text("%s - %s [%s]", item.bm.artist.c_str(), item.bm.title.c_str(), item.bm.author.c_str()); 70 | 71 | if (item.started) { 72 | const auto progress = static_cast(item.getProgress()); 73 | const std::string t = !item.prepared() 74 | ? lang.getTextCStr("Connecting") 75 | : std::format("{:.2f}MB / {:.2f}MB ({:.2f}%%)", item.dlSize / 1024 / 1024, 76 | item.totalSize / 1024 / 1024, progress * 100); 77 | ImGui::Text(t.c_str()); 78 | ImGui::ProgressBar(progress, ImVec2(-1, 1)); 79 | } else { 80 | if (ImGui::Button(lang.getTextCStr("CancelDownload"))) { 81 | cancel(item.bm.sid); 82 | } 83 | } 84 | 85 | ImGui::EndGroupPanel(); 86 | } 87 | 88 | 89 | void DownloadQueue::drawMain() { 90 | auto &lang = i18n::I18nManager::GetInstance(); 91 | 92 | { 93 | std::shared_lock _g(m_Mutex); 94 | 95 | if (m_TaskQueueSet.empty()) { 96 | ImGui::SetCursorPosX((ImGui::GetWindowSize().x - ImGui::CalcTextSize(lang.getTextCStr("Empty")).x) * 0.5f); 97 | ImGui::SetCursorPosY((ImGui::GetWindowSize().y - ImGui::CalcTextSize(lang.getTextCStr("Empty")).y) * 0.5f); 98 | ImGui::Text(lang.getTextCStr("Empty")); 99 | return; 100 | } 101 | 102 | for (auto &item : m_TaskQueueSet) { 103 | drawTaskItem(item); 104 | } 105 | } 106 | 107 | { 108 | std::unique_lock _g(m_Mutex); 109 | for (auto &sid : removed) { 110 | m_TaskQueueSet.erase(findTaskBySid(sid)); 111 | } 112 | removed.clear(); 113 | } 114 | } 115 | 116 | FeatureInfo &DownloadQueue::getInfo() { 117 | static auto info = FeatureInfo{ 118 | "DownloadQueue", 119 | "" 120 | }; 121 | return info; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /Downloader/src/features/DownloadQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "osu/Beatmap.h" 6 | #include "Feature.h" 7 | 8 | namespace features { 9 | struct DownloadTask { 10 | osu::Beatmap bm; 11 | double dlSize{}; 12 | double totalSize{}; 13 | bool started = false; 14 | uint64_t insertTime = UINT64_MAX; 15 | 16 | [[nodiscard]] double getProgress() const { 17 | return totalSize == 0 ? 0 : dlSize / totalSize; 18 | } 19 | 20 | [[nodiscard]] bool prepared() const { 21 | return dlSize != 0 && totalSize != 0; 22 | } 23 | 24 | bool operator==(const int sid) const { 25 | return bm.sid == sid; 26 | } 27 | 28 | bool operator<(const DownloadTask &dl) const { 29 | return insertTime < dl.insertTime; 30 | } 31 | }; 32 | 33 | class DownloadQueue : public Feature { 34 | DownloadQueue(); 35 | 36 | std::set m_TaskQueueSet; 37 | 38 | inline static std::shared_mutex m_Mutex{}; 39 | 40 | void drawTaskItem(const DownloadTask &item) const; 41 | 42 | void cancel(int sid) const; 43 | 44 | public: 45 | using TaskIter = std::set::iterator; 46 | using TaskIterConst = std::set::const_iterator; 47 | 48 | static DownloadQueue &GetInstance() { 49 | static DownloadQueue instance; 50 | return instance; 51 | } 52 | 53 | bool hasDownloadMapSet(const int id) const { 54 | return std::ranges::any_of(m_TaskQueueSet.begin(), m_TaskQueueSet.end(), 55 | [id](const DownloadTask &task) { return task.bm.sid == id; }); 56 | } 57 | 58 | bool hasDownloadMap(const osu::Beatmap &bm) const { 59 | return hasDownloadMapSet(bm.sid); 60 | } 61 | 62 | TaskIter findTaskBySid(const int id) const { 63 | return std::ranges::find_if(m_TaskQueueSet, [id](const DownloadTask &task) { return task.bm.sid == id; }); 64 | } 65 | 66 | bool addTask(const osu::Beatmap &bm); 67 | DownloadTask *getTask(const osu::Beatmap &bm); 68 | DownloadTask *getTask(int sid); 69 | 70 | void notifyFinished(int sid); 71 | 72 | void drawMain() override; 73 | FeatureInfo &getInfo() override; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /Downloader/src/features/Downloader.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Downloader.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "DownloadQueue.h" 8 | #include "api/Sayobot.h" 9 | #include "config/I18nManager.h" 10 | #include "misc/Color.h" 11 | #include "osu/BeatmapManager.h" 12 | #include "ui/SearchResultUi.h" 13 | #include "utils/gui_utils.h" 14 | 15 | static std::set s_Canceled{}; 16 | 17 | features::Downloader::Downloader() : 18 | Feature(), 19 | f_GrantOsuAccount("GrantOsuAccount", false), 20 | f_OsuAccount("OsuAccount", osu::Account{}), 21 | f_Mirror("DownloadMirror", downloader::DownloadMirror::Sayobot), 22 | f_DownloadType("DownloadType", downloader::DownloadType::Full), 23 | f_MoveToOsuFolder("MoveToOsuFolder", false), 24 | f_ProxySeverType("ProxyServerType", downloader::ProxyServerType::Disabled), 25 | f_ProxySever("ProxyServer", "localhost:7890"), 26 | f_ProxySeverPassword("ProxyServerPassword", ""), 27 | f_EnableCustomUserAgent("EnableCustomUserAgent", false), 28 | f_CustomUserAgent("CustomUserAgent", 29 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36") { 30 | t_DownloadThread = std::thread(DownloadThread); 31 | t_SearchThread = std::thread(SearchThread); 32 | 33 | t_DownloadThread.detach(); 34 | t_SearchThread.detach(); 35 | LOGI("Inited Downloader"); 36 | } 37 | 38 | [[noreturn]] void features::Downloader::DownloadThread() { 39 | auto &inst = GetInstance(); 40 | auto &lang = i18n::I18nManager::GetInstance(); 41 | 42 | while (true) { 43 | beg: 44 | auto bm = inst.m_DownloadQueue.pop_front(); 45 | LOGD("Poped dl: %d", bm.sid); 46 | if (bm.sid <= 0) { 47 | LOGW("Invalid beatmapsets id: %d, skipped download.", bm.sid); 48 | continue; 49 | } 50 | if (s_Canceled.contains(bm.sid)) { 51 | LOGI("Canceled: %d, skipped download.", bm.sid); 52 | s_Canceled.erase(bm.sid); 53 | continue; 54 | } 55 | 56 | uint8_t retry = 0; 57 | bool success = false; 58 | LOGI("Start download: %d %s-%s (%s)", bm.sid, bm.artist.c_str(), bm.title.c_str(), bm.author.c_str()); 59 | GuiHelper::ShowInfoToast(lang.getTextCStr("StartDownload"), bm.sid); 60 | while (!success && retry++ < 3) { 61 | if (inst.f_Mirror.getValue() == downloader::DownloadMirror::OsuOfficial && !inst.f_GrantOsuAccount.getValue()) { 62 | LOGW("Permission denied on download beatmap by osu!(Official) mirror."); 63 | goto beg; 64 | } 65 | 66 | auto provider = api::Provider::GetRegisteredByEnum(inst.f_Mirror.getValue()); 67 | success = provider->downloadBeatmap(bm); 68 | } 69 | 70 | DownloadQueue::GetInstance().notifyFinished(bm.sid); 71 | 72 | if (!success) { 73 | LOGW("Download failed (out of retry times): %d", bm.sid); 74 | GuiHelper::ShowWarnToast(lang.getTextCStr("DownloadFailedOORT"), bm.sid); 75 | continue; 76 | } 77 | 78 | std::string fn = std::to_string(bm.sid) + ".osz"; 79 | auto path = utils::GetCurrentDirPath() / L"downloads"; 80 | if (!exists(path)) 81 | create_directories(path); 82 | 83 | path /= fn; 84 | 85 | if (!exists(path)) { 86 | LOGW("Cannot open file (file not exists): %s", path.string().c_str()); 87 | continue; 88 | } 89 | 90 | if (inst.f_MoveToOsuFolder.getValue()) { 91 | auto song = utils::GetOsuDirPath() / "Songs"; 92 | if (!exists(song)) 93 | create_directories(song); 94 | song /= fn; 95 | 96 | if (exists(song)) 97 | remove(song); 98 | 99 | MoveFileW(path.wstring().c_str(), song.wstring().c_str()); 100 | } else { 101 | ShellExecuteW(nullptr, L"open", path.c_str(), nullptr, nullptr, SW_HIDE); 102 | } 103 | 104 | osu::BeatmapManager::GetInstance().addBeatmap(bm); 105 | 106 | LOGD("Finished download beatmapsets: %d", bm.sid); 107 | GuiHelper::ShowSuccessToast(lang.getTextCStr("DownloadSuccess"), bm.sid); 108 | } 109 | } 110 | 111 | [[noreturn]] void features::Downloader::SearchThread() { 112 | auto &inst = GetInstance(); 113 | auto &lang = i18n::I18nManager::GetInstance(); 114 | 115 | while (true) { 116 | auto info = inst.m_SearchQueue.pop_front(); 117 | LOGD("Poped search: %d", info.id); 118 | 119 | const auto provider = api::Provider::GetRegisteredByEnum(inst.f_Mirror.getValue()); 120 | if (auto ret = provider->searchBeatmap(info); ret.has_value()) { 121 | auto &bm = *ret; 122 | if (osu::BeatmapManager::GetInstance().hasBeatmap(bm) && info.directDownload) { 123 | LOGI("Already has beatmapsets: %d, skipped direct download.", bm.sid); 124 | GuiHelper::ShowInfoToast(lang.getTextCStr("ExistsBeatmapSkipAutoDownload"), bm.sid); 125 | continue; 126 | } 127 | 128 | info.directDownload ? inst.postDownload(bm) : ui::search::result::ShowSearchInfo(bm); 129 | } else { 130 | LOGW("No such map found on %s. ID=%d, Type=%s", provider->getName().data(), info.id, 131 | info.type == downloader::BeatmapType::Sid ? "beatmapsets" : "beatmapid"); 132 | GuiHelper::ShowWarnToast(lang.getTextCStr("SearchFailed"), info.type == downloader::BeatmapType::Sid ? "sid" : "bid", info.id); 133 | } 134 | } 135 | } 136 | 137 | void features::Downloader::drawMain() { 138 | auto &lang = i18n::I18nManager::GetInstance(); 139 | ImGui::Checkbox(lang.getTextCStr("GrantOsuAccount"), f_GrantOsuAccount.getPtr()); 140 | GuiHelper::ShowTooltip(lang.getTextCStr("GrantOsuAccountDesc")); 141 | 142 | if (f_GrantOsuAccount.getValue()) { 143 | static std::string un = f_OsuAccount->username(); 144 | static std::string pw = f_OsuAccount->password().md5(); 145 | ImGui::Indent(); 146 | if (ImGui::InputText(lang.getTextCStr("Username"), &un)) { 147 | f_OsuAccount->setUsername(un); 148 | } 149 | if (ImGui::PasswordInputText(lang.getTextCStr("Password"), &pw)) { 150 | f_OsuAccount->setPassword(pw); 151 | } 152 | ImGui::Unindent(); 153 | } 154 | 155 | ImGui::Checkbox(lang.GetTextCStr("MoveToOsuFolder"), f_MoveToOsuFolder.getPtr()); 156 | GuiHelper::ShowTooltip(lang.GetTextCStr("MoveToOsuFolderDesc")); 157 | 158 | #pragma region Mirror 159 | static const char *mirrorNames[] = { 160 | "Osu! (official)", 161 | "Sayobot", 162 | "Chimu", 163 | }; 164 | static int mirrorIdx = (int)f_Mirror.getValue(); 165 | if (ImGui::Combo(lang.getTextCStr("Mirror"), &mirrorIdx, mirrorNames, IM_ARRAYSIZE(mirrorNames))) { 166 | f_Mirror.setValue(static_cast(mirrorIdx)); 167 | } 168 | #pragma endregion 169 | 170 | #pragma region DownloadType 171 | static const char *downloadTypeNames[] = { 172 | "Full", 173 | "NoVideo", 174 | }; 175 | static int downloadTypeIdx = (int)f_DownloadType.getValue(); 176 | if (ImGui::Combo(lang.getTextCStr("DownloadType"), &downloadTypeIdx, downloadTypeNames, IM_ARRAYSIZE(downloadTypeNames))) { 177 | f_DownloadType.setValue(static_cast(downloadTypeIdx)); 178 | } 179 | #pragma endregion 180 | 181 | if (f_Mirror.getValue() == downloader::DownloadMirror::OsuOfficial && !f_GrantOsuAccount.getValue()) { 182 | ImGui::TextColored(color::Orange, "%s", lang.getTextCStr("NotGrantOsuAccountButUseOfficialWarn")); 183 | } 184 | 185 | static const char *proxyTypeNames[] = { 186 | "Disabled", 187 | "HTTP", 188 | "Socks5" 189 | }; 190 | static int proxyTypeIdx = (int)f_ProxySeverType.getValue(); 191 | if (ImGui::Combo(lang.getTextCStr("ProxyServerType"), &proxyTypeIdx, proxyTypeNames, IM_ARRAYSIZE(proxyTypeNames))) { 192 | f_ProxySeverType.setValue(static_cast(proxyTypeIdx)); 193 | }; 194 | GuiHelper::ShowTooltip(lang.getTextCStr("ProxyServerDesc")); 195 | 196 | if (f_ProxySeverType.getValue() != downloader::ProxyServerType::Disabled) { 197 | ImGui::Indent(); 198 | ImGui::InputText(lang.getTextCStr("ProxyServer"), f_ProxySever.getPtr()); 199 | GuiHelper::ShowTooltip("e.g.: localhost:7890"); 200 | ImGui::InputText(lang.getTextCStr("ProxyServerPassword"), f_ProxySeverPassword.getPtr()); 201 | GuiHelper::ShowTooltip("e.g.: username:password"); 202 | ImGui::Unindent(); 203 | } 204 | 205 | ImGui::Checkbox(lang.getTextCStr("EnableCustomUserAgent"), f_EnableCustomUserAgent.getPtr()); 206 | GuiHelper::ShowTooltip(lang.getTextCStr("CustomUserAgentDesc")); 207 | if (f_EnableCustomUserAgent.getValue()) { 208 | ImGui::Indent(); 209 | ImGui::InputText("User-Agent", f_CustomUserAgent.getPtr()); 210 | ImGui::Unindent(); 211 | } 212 | } 213 | 214 | features::FeatureInfo &features::Downloader::getInfo() { 215 | static FeatureInfo info = {"Downloader", "Download"}; 216 | return info; 217 | } 218 | 219 | void features::Downloader::CancelDownload(int sid) { 220 | s_Canceled.insert(sid); 221 | } 222 | 223 | void features::Downloader::RemoveCancelDownload(int sid) { 224 | if (s_Canceled.contains(sid)) { 225 | s_Canceled.erase(sid); 226 | } 227 | } 228 | 229 | void features::Downloader::postSearch(const downloader::BeatmapInfo &info) { 230 | m_SearchQueue.push_back(info); 231 | } 232 | 233 | void features::Downloader::postDownload(const osu::Beatmap &bm) { 234 | if (bm.sid <= 0) { 235 | LOGW("Invalid beatmapsets id: %d, skipped download.", bm.sid); 236 | return; 237 | } 238 | 239 | if (!DownloadQueue::GetInstance().addTask(bm)) 240 | return; 241 | 242 | m_DownloadQueue.push_back(bm); 243 | } 244 | -------------------------------------------------------------------------------- /Downloader/src/features/Downloader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config/Field.h" 3 | #include "Feature.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #include "osu/Account.h" 9 | #include "osu/Beatmap.h" 10 | 11 | namespace features { 12 | namespace downloader { 13 | 14 | enum class DownloadMirror : uint8_t { 15 | OsuOfficial = 0, 16 | Sayobot, 17 | Chimu, 18 | }; 19 | 20 | enum class BeatmapType : uint8_t { 21 | Sid = 0, 22 | Bid, 23 | }; 24 | 25 | enum class DownloadType : uint8_t { 26 | Full = 0, 27 | NoVideo 28 | }; 29 | 30 | struct BeatmapInfo { 31 | BeatmapType type; 32 | int32_t id; 33 | bool directDownload = false; 34 | }; 35 | 36 | struct DownloadInfo { 37 | DownloadType type; 38 | int32_t id; 39 | }; 40 | 41 | enum class ProxyServerType : uint8_t { 42 | Disabled = 0, 43 | Http, 44 | Socks5, 45 | }; 46 | 47 | } 48 | 49 | 50 | class Downloader : public Feature { 51 | Downloader(); 52 | 53 | container::Blocking> m_DownloadQueue; 54 | container::Blocking> m_SearchQueue; 55 | 56 | [[noreturn]] static void DownloadThread(); 57 | [[noreturn]] static void SearchThread(); 58 | 59 | std::thread t_DownloadThread; 60 | std::thread t_SearchThread; 61 | 62 | public: 63 | inline const static char *DEFAULT_USER_AGENT = 64 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"; 65 | 66 | config::Field f_GrantOsuAccount; 67 | config::Field f_OsuAccount; 68 | config::Field f_Mirror; 69 | config::Field f_DownloadType; 70 | config::Field f_MoveToOsuFolder; 71 | 72 | config::Field f_ProxySeverType; 73 | config::Field f_ProxySever; 74 | config::Field f_ProxySeverPassword; 75 | 76 | config::Field f_EnableCustomUserAgent; 77 | config::Field f_CustomUserAgent; 78 | 79 | 80 | static Downloader &GetInstance() { 81 | static Downloader instance; 82 | return instance; 83 | } 84 | 85 | void drawMain() override; 86 | FeatureInfo &getInfo() override; 87 | 88 | static void CancelDownload(int sid); 89 | static void RemoveCancelDownload(int sid); 90 | 91 | void postSearch(const downloader::BeatmapInfo &info); 92 | void postDownload(const osu::Beatmap &bm); 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /Downloader/src/features/Feature.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace features { 8 | 9 | struct FeatureInfo { 10 | std::string_view category; 11 | std::string_view groupName; 12 | }; 13 | 14 | class Feature { 15 | public: 16 | Feature() = default; 17 | virtual ~Feature() = default; 18 | 19 | virtual void drawMain() = 0; 20 | virtual FeatureInfo& getInfo() = 0; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /Downloader/src/features/HandleLinkHook.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | #include "HandleLinkHook.h" 4 | 5 | #include 6 | 7 | #include "Downloader.h" 8 | #include "config/I18nManager.h" 9 | #include "misc/Hotkey.hpp" 10 | #include "osu/BeatmapManager.h" 11 | #include "utils/gui_utils.h" 12 | 13 | namespace features { 14 | void HandleLinkHook::drawMain() { 15 | auto &lang = i18n::I18nManager::GetInstance(); 16 | 17 | ImGui::Checkbox(lang.getTextCStr("Enabled"), f_Enabled.getPtr()); 18 | GuiHelper::ShowTooltip(lang.getTextCStr("HandleLinkDesc")); 19 | 20 | ImGui::InputText(lang.getTextCStr("Domain"), f_Domain.getPtr()); 21 | GuiHelper::ShowTooltip(lang.getTextCStr("HandleLinkDomainDesc")); 22 | } 23 | 24 | FeatureInfo &HandleLinkHook::getInfo() { 25 | static auto info = FeatureInfo{ 26 | .category = "Downloader", 27 | .groupName = "HandleLink" 28 | }; 29 | 30 | return info; 31 | } 32 | 33 | BOOL __stdcall HandleLinkHook::ShellExecuteExW_Hook(SHELLEXECUTEINFOW *pExecInfo) { 34 | auto &inst = GetInstance(); 35 | if (!inst.f_Enabled.getValue()) 36 | return HookManager::CallOriginal(ShellExecuteExW_Hook, pExecInfo); 37 | 38 | if (std::wstring ws = pExecInfo->lpFile; !ws.starts_with(L"http")) { 39 | LOGD("Not a link, skip handle link"); 40 | return HookManager::CallOriginal(ShellExecuteExW_Hook, pExecInfo); 41 | } 42 | 43 | if (misc::Hotkey::IsDown(VK_CONTROL)) { 44 | LOGI("Ctrl pressed, skip handle link"); 45 | return HookManager::CallOriginal(ShellExecuteExW_Hook, pExecInfo); 46 | } 47 | 48 | bool LShiftDown = misc::Hotkey::IsDown(VK_SHIFT); 49 | 50 | static std::string https = "https://"; 51 | static std::string http = "http://"; 52 | auto s = inst.f_Domain.getValue(); 53 | if (s.find(http) == 0) { 54 | s.erase(0, http.size()); 55 | } else if (s.find(https) == 0) { 56 | s.erase(0, https.size()); 57 | } 58 | if (s.back() == '/') { 59 | s.pop_back(); 60 | } 61 | 62 | auto &bmmgr = osu::BeatmapManager::GetInstance(); 63 | auto &dl = Downloader::GetInstance(); 64 | auto url = utils::ws2s(pExecInfo->lpFile); 65 | 66 | const std::string domainPattern1 = s + R"(/(beatmapsets|s)/(\d+))"; 67 | std::smatch match; 68 | if (std::regex re1(domainPattern1); std::regex_search(url, match, re1) && match.size() > 2) { 69 | const int id = std::stoi(match[2].str()); 70 | LOGI("Handle link beatmapsets: %d", id); 71 | dl.postSearch({downloader::BeatmapType::Sid, id, LShiftDown && !bmmgr.hasBeatmapSet(id)}); 72 | return false; 73 | } 74 | 75 | std::string domainPattern2 = s + R"(/(beatmaps|b)/(\d+))"; 76 | 77 | if (std::regex re2(domainPattern2); std::regex_search(url, match, re2) && match.size() > 2) { 78 | const int id = std::stoi(match[2].str()); 79 | LOGI("Handle link beatmaps: %d", id); 80 | dl.postSearch({downloader::BeatmapType::Bid, id, LShiftDown && !bmmgr.hasBeatmap(id)}); 81 | return false; 82 | } 83 | 84 | return HookManager::CallOriginal(ShellExecuteExW_Hook, pExecInfo); 85 | } 86 | 87 | HandleLinkHook::HandleLinkHook() : 88 | Feature(), 89 | f_Enabled("HandleLinkEnabled", true), 90 | f_Domain("HandleLinkDomain", "osu.ppy.sh") { 91 | const HMODULE hMod = GetModuleHandleA("shell32.dll"); 92 | if (!hMod) { 93 | LOGE("Cannot get shell32.dll handle! Error code: %d, HandleLink hook will not be initilized!", GetLastError()); 94 | return; 95 | } 96 | const auto pFunc = (decltype(&ShellExecuteExW_Hook))GetProcAddress(hMod, "ShellExecuteExW"); 97 | if (!pFunc) { 98 | LOGE("Cannot get ShellExecuteExW address! Error code: %d, HandleLink hook will not be initilized!", GetLastError()); 99 | return; 100 | } 101 | HookManager::InstallHook(pFunc, ShellExecuteExW_Hook); 102 | LOGI("Inited HandleLink hook"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Downloader/src/features/HandleLinkHook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "config/Field.h" 5 | #include "Feature.h" 6 | 7 | namespace features { 8 | 9 | class HandleLinkHook : public Feature { 10 | public: 11 | config::Field f_Enabled; 12 | config::Field f_Domain; 13 | 14 | static BOOL __stdcall ShellExecuteExW_Hook(SHELLEXECUTEINFOW *pExecInfo); 15 | 16 | static HandleLinkHook &GetInstance() { 17 | static HandleLinkHook instance; 18 | return instance; 19 | } 20 | 21 | void drawMain() override; 22 | virtual FeatureInfo &getInfo() override; 23 | 24 | private: 25 | HandleLinkHook(); 26 | 27 | }; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Downloader/src/features/MultiDownload.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "BlockingContainer.hpp" 7 | #include "Feature.h" 8 | 9 | 10 | namespace features { 11 | 12 | class MultiDownload : public Feature { 13 | MultiDownload(); 14 | std::thread t_SearchThread; 15 | 16 | public: 17 | struct Task { 18 | std::string url; 19 | int beg, end; 20 | // code | response | this 21 | std::function callback; 22 | bool canceled = false; 23 | // for logger 24 | int id; 25 | }; 26 | 27 | private: 28 | container::Blocking> m_Queue; 29 | 30 | [[noreturn]] static void SearchThread(); 31 | 32 | public: 33 | static MultiDownload &GetInstance() { 34 | static MultiDownload instance; 35 | return instance; 36 | } 37 | 38 | void doRequest(const Task &task); 39 | 40 | FeatureInfo &getInfo() override; 41 | void drawMain() override; 42 | 43 | }; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Downloader/src/features/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Settings.h" 3 | 4 | #include "utils/gui_utils.h" 5 | 6 | namespace features { 7 | 8 | static const char *s_Themes[] = {"Sakura", "ImGuiDark", "ImGuiLight", "ImGuiClassic"}; 9 | 10 | Settings::Settings() : 11 | Feature(), 12 | f_EnableToast("EnableToast", true), 13 | f_ToastDuration("ToastDuration", 3000), 14 | f_EnableConsole("EnableConsole", true), 15 | f_OsuPath("OsuPath", ""), 16 | f_Theme("Theme", 0) { 17 | } 18 | 19 | void Settings::applyTheme() { 20 | if (ImGui::GetCurrentContext() == nullptr) { 21 | LOGW("Apply theme failed: ImGui Context == nullptr"); 22 | return; 23 | } 24 | ImGui::SetTheme(s_Themes[f_Theme.getValue()]); 25 | } 26 | 27 | void Settings::drawMain() { 28 | auto &lang = i18n::I18nManager::GetInstance(); 29 | 30 | ImGui::BeginGroupPanel(lang.getTextCStr("Settings")); 31 | 32 | static const char *langs[] = {"English | en_us", "简体中文 | zh_cn"}; 33 | 34 | static int langIdx = (int)lang.lang.getValue(); 35 | if (ImGui::Combo(lang.getTextCStr("Language"), &langIdx, langs, IM_ARRAYSIZE(langs))) { 36 | lang.lang.setValue(static_cast(langIdx)); 37 | } 38 | 39 | if (ImGui::Combo(lang.getTextCStr("Theme"), f_Theme.getPtr(), s_Themes, IM_ARRAYSIZE(s_Themes))) { 40 | ImGui::SetTheme(s_Themes[f_Theme.getValue()]); 41 | } 42 | 43 | if (ImGui::InputText(lang.getTextCStr("OsuPath"), f_OsuPath.getPtr())) { 44 | if (!f_OsuPath->empty()) { 45 | if (const auto op = std::filesystem::path(f_OsuPath.getValue()) / "osu!.exe"; exists(op)) { 46 | utils::SetOsuDirPath(op.parent_path()); 47 | } else { 48 | GuiHelper::ShowWarnToast(lang.getTextCStr("InvalidOsuPath")); 49 | } 50 | } 51 | } 52 | GuiHelper::ShowTooltip(lang.getTextCStr("OsuPathDesc")); 53 | 54 | ImGui::EndGroupPanel(); 55 | 56 | ImGui::BeginGroupPanel(lang.getTextCStr("Toast")); 57 | ImGui::Checkbox(lang.getTextCStr("EnableToast"), f_EnableToast.getPtr()); 58 | if (f_EnableToast.getValue()) { 59 | ImGui::Indent(); 60 | ImGui::DragInt(lang.getTextCStr("ToastDuration"), f_ToastDuration.getPtr(), 1.0f, 500, 10000, "%d ms", ImGuiSliderFlags_AlwaysClamp); 61 | ImGui::Unindent(); 62 | } 63 | ImGui::EndGroupPanel(); 64 | 65 | ImGui::BeginGroupPanel(lang.getTextCStr("Debug")); 66 | ImGui::Checkbox(lang.getTextCStr("EnableConsole"), f_EnableConsole.getPtr()); 67 | GuiHelper::ShowTooltip(lang.getTextCStr("RestartToApply")); 68 | ImGui::EndGroupPanel(); 69 | } 70 | 71 | FeatureInfo &Settings::getInfo() { 72 | static auto info = FeatureInfo{ 73 | .category = "Settings", 74 | .groupName = "" 75 | }; 76 | return info; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Downloader/src/features/Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Feature.h" 3 | #include "config/Field.h" 4 | #include "config/I18nManager.h" 5 | #include "misc/Hotkey.hpp" 6 | 7 | namespace features { 8 | 9 | class Settings : public Feature { 10 | Settings(); 11 | 12 | public: 13 | config::Field f_EnableToast; 14 | config::Field f_ToastDuration; 15 | config::Field f_EnableConsole; 16 | config::Field f_OsuPath; 17 | config::Field f_Theme; 18 | 19 | static Settings &GetInstance() { 20 | static Settings instance; 21 | return instance; 22 | } 23 | 24 | void applyTheme(); 25 | 26 | void drawMain() override; 27 | virtual FeatureInfo &getInfo() override; 28 | }; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Downloader/src/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | -------------------------------------------------------------------------------- /Downloader/src/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Logger.h" 8 | #include "renderer/renderer.h" 9 | #include "utils/Utils.h" 10 | #include "config/Field.h" 11 | #include "misc/ResourcesLoader.hpp" 12 | #include "osu/BeatmapManager.h" 13 | #include "osu/OsuConfigManager.h" 14 | #include "ui/MainUi.h" 15 | #include "features/Settings.h" 16 | #include "misc/VersionManager.h" 17 | #include "utils/gui_utils.h" 18 | 19 | void InitFeatures(); 20 | 21 | void Run(HMODULE *phModule) { 22 | utils::SetMyModuleHandle(*phModule); 23 | utils::SetCurrentDirPath(utils::GetModulePath(phModule).parent_path()); 24 | 25 | config::Init(); 26 | 27 | auto &settings = features::Settings::GetInstance(); 28 | 29 | if (settings.f_EnableConsole.getValue()) { 30 | AllocConsole(); 31 | freopen_s((FILE **)stdout, "CONOUT$", "w", stdout); 32 | freopen_s((FILE **)stdin, "CONIN$", "r", stdin); 33 | freopen_s((FILE **)stderr, "CONOUT$", "w", stderr); 34 | DeleteMenu(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE, MF_BYCOMMAND); 35 | } 36 | 37 | LOGI("Waiting for osu! initialization..."); 38 | Sleep(5000); 39 | 40 | osu::BeatmapManager::GetInstance(); 41 | 42 | ImGui::CreateContext(); 43 | ImGuiIO &io = ImGui::GetIO(); 44 | static auto s = (utils::GetCurrentDirPath() / "imgui.ini").string(); 45 | LOGD("Imgui config path: %s", s.c_str()); 46 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; 47 | io.IniFilename = s.c_str(); 48 | io.SetPlatformImeDataFn = nullptr; 49 | settings.applyTheme(); 50 | 51 | InitFeatures(); 52 | 53 | LPBYTE bytes = nullptr; 54 | if (const DWORD size = res::LoadEx(IDR_FONT, RT_FONT, &bytes)) { 55 | renderer::SetCurrentFont(bytes, size, 18.0f, nullptr, ImGui::GetIO().Fonts->GetGlyphRangesChineseFull()); 56 | ImGui::MergeIconsWithLatestFont(18.f, false); 57 | } 58 | 59 | LOGD("Check graphics api..."); 60 | 61 | wchar_t processPath[MAX_PATH]; 62 | GetModuleFileNameW(nullptr, processPath, MAX_PATH); 63 | auto path = std::filesystem::path(processPath); 64 | utils::SetOsuDirPath(path.parent_path()); 65 | 66 | // set custom osu path 67 | if (!settings.f_OsuPath.getValue().empty()) { 68 | if (const auto op = std::filesystem::path(settings.f_OsuPath.getValue()) / "osu!.exe"; exists(op)) { 69 | utils::SetOsuDirPath(op.parent_path()); 70 | LOGI("Using custom osu path: %s", op.string().c_str()); 71 | } else { 72 | LOGW("Osu! path not correct, use deteceted path."); 73 | } 74 | } 75 | 76 | wchar_t username[256 + 1]; 77 | DWORD usernameLen = 256 + 1; 78 | GetUserNameW(username, &usernameLen); 79 | std::wstring cfgName = L"osu!."; 80 | cfgName.append(username).append(L".cfg"); 81 | path = path.parent_path() / cfgName; 82 | osu::OsuConfigManager::Init(path); 83 | 84 | const renderer::GraphicsApiType graphicsApiType = osu::OsuConfigManager::GetInt("CompatibilityContext") == 0 85 | ? renderer::GraphicsApiType::OpenGL3 86 | : renderer::GraphicsApiType::D3D11; 87 | 88 | LOGD("Api detected: %s", graphicsApiType == renderer::GraphicsApiType::OpenGL3 ? "OpenGL" : "DirectX"); 89 | 90 | renderer::Init(graphicsApiType); 91 | ui::main::ToggleShow(); 92 | 93 | GuiHelper::ShowSuccessToast(i18n::I18nManager::GetInstance().getTextCStr("DownloaderLoadSuccess")); 94 | misc::VersionManager::CheckUpdate(); 95 | } 96 | 97 | #include "features/About.h" 98 | #include "features/HandleLinkHook.h" 99 | #include "features/Downloader.h" 100 | #include "features/DownloadQueue.h" 101 | #include "features/MultiDownload.h" 102 | #include "features/CustomHotkey.h" 103 | #include "api/Provider.h" 104 | #include "api/Bancho.h" 105 | #include "api/Sayobot.h" 106 | #include "api/Chimu.h" 107 | 108 | #define INIT_FEAT(NAME) ui::main::AddFeature(&features::NAME::GetInstance()) 109 | 110 | inline void InitFeatures() { 111 | INIT_FEAT(About); 112 | INIT_FEAT(CustomHotkey); 113 | INIT_FEAT(Settings); 114 | INIT_FEAT(Downloader); 115 | INIT_FEAT(HandleLinkHook); 116 | INIT_FEAT(MultiDownload); 117 | INIT_FEAT(DownloadQueue); 118 | 119 | api::Provider::Register(new api::Bancho()); 120 | api::Provider::Register(new api::Sayobot()); 121 | api::Provider::Register(new api::Chimu()); 122 | } 123 | -------------------------------------------------------------------------------- /Downloader/src/misc/Color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | namespace color { 6 | inline static ImVec4 Red{1.0f, 0.0f, 0.0f, 1.0f}; 7 | inline static ImVec4 Green{0.0f, 1.0f, 0.0f, 1.0f}; 8 | inline static ImVec4 Blue{0.0f, 0.0f, 1.0f, 1.0f}; 9 | inline static ImVec4 Orange{1.0f, 0.5f, 0.0f, 1.0f}; 10 | inline static ImVec4 DeepSkyBlue{0.0f, 0.75f, 1.0f, 1.0f}; 11 | inline static ImVec4 DodgerBlue{0.117f, 0.564f, 1.0f, 1.0f}; 12 | } 13 | -------------------------------------------------------------------------------- /Downloader/src/misc/ISerializable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class ISerializable { 5 | public: 6 | virtual ~ISerializable() = default; 7 | 8 | virtual void to_json(nlohmann::json &j) const { 9 | } 10 | 11 | virtual void from_json(const nlohmann::json &j) { 12 | } 13 | }; 14 | 15 | template <> 16 | struct nlohmann::adl_serializer { 17 | static void to_json(json &j, ISerializable &obj) { 18 | obj.to_json(j); 19 | } 20 | 21 | static void from_json(json &j, ISerializable &obj) { 22 | obj.from_json(j); 23 | } 24 | }; 25 | 26 | template<> 27 | struct nlohmann::adl_serializer> { 28 | static void to_json(json &j, std::vector &obj) { 29 | for (auto &i : obj) { 30 | i.to_json(j); 31 | } 32 | } 33 | 34 | static void from_json(json &j, std::vector &obj) { 35 | for (auto &i : obj) { 36 | i.from_json(j); 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /Downloader/src/misc/ResourcesLoader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "utils/Utils.h" 5 | 6 | namespace res { 7 | 8 | static DWORD LoadEx(LPCWSTR name, LPCWSTR type, BYTE **pDest) { 9 | if (!utils::GetMyModuleHandle()) 10 | return 0; 11 | 12 | if (!name) 13 | return 0; 14 | 15 | if (!type) 16 | return 0; 17 | 18 | const HRSRC hResource = FindResourceW(utils::GetMyModuleHandle(), name, type); 19 | if (!hResource) 20 | return 0; 21 | 22 | const HGLOBAL hGlob = LoadResource(utils::GetMyModuleHandle(), hResource); 23 | if (!hGlob) 24 | return 0; 25 | 26 | const DWORD size = SizeofResource(utils::GetMyModuleHandle(), hResource); 27 | *pDest = static_cast(LockResource(hGlob)); 28 | if (size > 0 && *pDest) 29 | return size; 30 | 31 | return 0; 32 | } 33 | 34 | static DWORD LoadEx(int id, LPCWSTR type, BYTE **pDest) { 35 | if (!type) 36 | return 0; 37 | 38 | return LoadEx(MAKEINTRESOURCEW(id), type, pDest); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Downloader/src/misc/VersionManager.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "VersionManager.h" 3 | 4 | #include "network/HttpRequest.h" 5 | #include "utils/gui_utils.h" 6 | 7 | void misc::VersionManager::CheckUpdate(const bool force) { 8 | #ifdef SKIP_VERSION_CHECK 9 | LOGI("Skipped version check!"); 10 | GuiHelper::ShowInfoToast("Skipped version check!"); 11 | #endif 12 | 13 | static bool checked = false; 14 | static bool inCheck = false; 15 | 16 | if (inCheck) 17 | return; 18 | 19 | if (checked && !force) 20 | return; 21 | 22 | GET_LANG(); 23 | inCheck = true; 24 | GuiHelper::ShowInfoToast(lang.getTextCStr("VersionChecking")); 25 | static std::vector headers = { 26 | "Accept: application/vnd.github+json", 27 | "X-GitHub-Api-Version: 2022-11-28" 28 | }; 29 | net::curl_get_async( 30 | "https://api.github.com/repos/KyuubiRan/BeatmapDownloader/releases/latest", headers, 31 | [&](const int code, std::string &res) { 32 | if (code != 200) { 33 | LOGW("Cannot check update, response code: %d, response body: %s", code, res.c_str()); 34 | GuiHelper::ShowWarnToast(lang.getTextCStr("VersionCheckFailed"), code); 35 | inCheck = false; 36 | return; 37 | } 38 | nlohmann::json j = nlohmann::json::parse(res); 39 | s_LatestVersionName = j["name"]; 40 | const std::string vc = j["tag_name"]; 41 | s_LatestVersionCode = std::stoi(vc); 42 | checked = true; 43 | inCheck = false; 44 | if (IsLatest()) { 45 | LOGI("Downloader is up to date"); 46 | GuiHelper::ShowSuccessToast(lang.getTextCStr("VersionIsLatest")); 47 | } else { 48 | LOGI("Found new version: %s(%d)", s_LatestVersionName.c_str(), s_LatestVersionCode); 49 | GuiHelper::ShowInfoToast(lang.getTextCStr("FoundNewVersion"), s_LatestVersionName.c_str(), 50 | s_LatestVersionCode); 51 | } 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /Downloader/src/misc/VersionManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _DEBUG 4 | #define SKIP_VERSION_CHECK 5 | #endif 6 | 7 | #include 8 | 9 | // dlver.h will auto generated by `update_version.py` before build 10 | // if you cant run the script, please copy the `dlver.h.default` to `dlver.h` 11 | // then you can build the project manually 12 | 13 | #include "dlver.h" 14 | 15 | namespace misc { 16 | class VersionManager { 17 | inline static int s_LatestVersionCode = 0; 18 | inline static std::string s_LatestVersionName = "unknown"; 19 | 20 | public: 21 | static void CheckUpdate(bool force = false); 22 | static int GetLatestVersionCode() { return s_LatestVersionCode; } 23 | static std::string_view GetLatestVersionName() { return s_LatestVersionName; } 24 | 25 | static int GetCurrentVersionCode() { return DOWNLOADER_VERSION; } 26 | static std::string_view GetCurrentVersionName() { return DOWNLOADER_VERSION_STR; } 27 | 28 | static bool IsLatest() { 29 | #ifdef SKIP_VERSION_CHECK 30 | return true; 31 | #endif 32 | return GetCurrentVersionCode() >= s_LatestVersionCode; 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /Downloader/src/misc/glob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace glob { 6 | inline static thread::ThreadPool s_ThreadPool{}; 7 | } 8 | -------------------------------------------------------------------------------- /Downloader/src/network/HttpRequest.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "HttpRequest.h" 3 | 4 | #include "features/Downloader.h" 5 | #include "misc/glob.h" 6 | #include "utils/gui_utils.h" 7 | 8 | #pragma comment(lib, "ws2_32.lib") 9 | #pragma comment(lib, "Wldap32.lib") 10 | 11 | size_t string_write_fn(char *data, size_t size, size_t nmemb, std::string *writerData) { 12 | if (!data) 13 | return 0; 14 | writerData->append(data, size * nmemb); 15 | return size * nmemb; 16 | } 17 | 18 | CURLcode net::curl_get(const char *url, std::string &response, const std::vector &extraHeader, int32_t *resCode) { 19 | CURL *curl = curl_easy_init(); 20 | if (!curl) { 21 | LOGE("Cannot init curl"); 22 | return CURLE_FAILED_INIT; 23 | } 24 | 25 | auto &dl = features::Downloader::GetInstance(); 26 | 27 | curl_slist *headers = nullptr; 28 | 29 | const std::string ua = "User-Agent: " + (dl.f_EnableCustomUserAgent.getValue() 30 | ? dl.f_CustomUserAgent.getValue() 31 | : features::Downloader::DEFAULT_USER_AGENT); 32 | headers = curl_slist_append(headers, ua.c_str()); 33 | 34 | for (const auto &s : extraHeader) { 35 | headers = curl_slist_append(headers, s.c_str()); 36 | } 37 | 38 | curl_easy_setopt(curl, CURLOPT_URL, url); 39 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 40 | curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); 41 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); 42 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); 43 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); 44 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); 45 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, string_write_fn); 46 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 47 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 30000); 48 | 49 | 50 | if (dl.f_ProxySeverType.getValue() != features::downloader::ProxyServerType::Disabled) { 51 | curl_easy_setopt(curl, CURLOPT_PROXY, dl.f_ProxySever->c_str()); 52 | curl_easy_setopt(curl, CURLOPT_PROXYTYPE, 53 | dl.f_ProxySeverType == features::downloader::ProxyServerType::Socks5 ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP); 54 | if (!dl.f_ProxySeverPassword->empty()) 55 | curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, dl.f_ProxySeverPassword->c_str()); 56 | } 57 | 58 | CURLcode res = curl_easy_perform(curl); 59 | 60 | if (res == CURLE_OK) { 61 | int32_t code; 62 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); 63 | if (resCode) 64 | *resCode = code; 65 | } else { 66 | LOGW("Curl req(get) error: %d", res); 67 | GuiHelper::ShowErrorToast(i18n::I18nManager::GetTextCStr("CurlError"), res); 68 | } 69 | 70 | curl_slist_free_all(headers); 71 | curl_easy_cleanup(curl); 72 | return res; 73 | } 74 | 75 | void net::curl_get_async(std::string url, std::vector extraHeader, std::function callback) { 76 | glob::s_ThreadPool.post([u = std::move(url), h = std::move(extraHeader), c = std::move(callback)] { 77 | std::string response; 78 | int32_t code; 79 | curl_get(u.c_str(), response, h, &code); 80 | c(code, response); 81 | }); 82 | } 83 | 84 | size_t file_writer(char *data, size_t size, size_t nmemb, FILE *pFile) { 85 | if (!data) 86 | return 0; 87 | fwrite(data, size, nmemb, pFile); 88 | return size * nmemb; 89 | } 90 | 91 | int xferinfo_fn(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { 92 | const auto pTask = (features::DownloadTask *)clientp; 93 | pTask->dlSize = static_cast(dlnow); 94 | pTask->totalSize = static_cast(dltotal); 95 | return 0; 96 | } 97 | 98 | CURLcode net::curl_download(const char *url, std::filesystem::path &path, std::vector &extraHeader, features::DownloadTask *task, 99 | int32_t *resCode) { 100 | CURL *curl = curl_easy_init(); 101 | if (!curl) { 102 | LOGE("Cannot init curl"); 103 | return CURLE_FAILED_INIT; 104 | } 105 | 106 | FILE *f; 107 | if (!exists(path.parent_path())) 108 | create_directories(path.parent_path()); 109 | 110 | fopen_s(&f, path.string().c_str(), "wb"); 111 | if (!f) { 112 | LOGW("Cannot open file %s", path.string().c_str()); 113 | return CURLE_FAILED_INIT; 114 | } 115 | 116 | auto &dl = features::Downloader::GetInstance(); 117 | 118 | curl_slist *headers = nullptr; 119 | const std::string ua = "User-Agent: " + (dl.f_EnableCustomUserAgent.getValue() 120 | ? dl.f_CustomUserAgent.getValue() 121 | : features::Downloader::DEFAULT_USER_AGENT); 122 | 123 | for (const auto &s : extraHeader) { 124 | curl_slist_append(headers, s.c_str()); 125 | } 126 | curl_slist_append(headers, ua.c_str()); 127 | curl_easy_setopt(curl, CURLOPT_URL, url); 128 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 129 | curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); 130 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); 131 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); 132 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); 133 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); 134 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, file_writer); 135 | if (task != nullptr) { 136 | curl_easy_setopt(curl, CURLOPT_XFERINFODATA, task); 137 | curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo_fn); 138 | } 139 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); 140 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); 141 | curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 30000); 142 | 143 | if (dl.f_ProxySeverType.getValue() != features::downloader::ProxyServerType::Disabled) { 144 | curl_easy_setopt(curl, CURLOPT_PROXY, dl.f_ProxySever->c_str()); 145 | curl_easy_setopt(curl, CURLOPT_PROXYTYPE, 146 | dl.f_ProxySeverType == features::downloader::ProxyServerType::Socks5 ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP); 147 | if (!dl.f_ProxySeverPassword->empty()) 148 | curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, dl.f_ProxySeverPassword->c_str()); 149 | } 150 | 151 | const CURLcode res = curl_easy_perform(curl); 152 | fclose(f); 153 | 154 | if (res == CURLE_OK) { 155 | int32_t code; 156 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); 157 | if (resCode) 158 | *resCode = code; 159 | } else { 160 | LOGW("Curl req(dl) error: %d", res); 161 | GuiHelper::ShowErrorToast(i18n::I18nManager::GetTextCStr("CurlError"), res); 162 | } 163 | 164 | curl_slist_free_all(headers); 165 | curl_easy_cleanup(curl); 166 | return res; 167 | } 168 | -------------------------------------------------------------------------------- /Downloader/src/network/HttpRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "features/DownloadQueue.h" 8 | 9 | #pragma comment(lib, "crypt32.lib") 10 | 11 | namespace net { 12 | CURLcode curl_get(const char *url, std::string &response, const std::vector &extraHeader, int32_t *resCode = nullptr); 13 | 14 | inline CURLcode curl_get(const char *url, std::string &response, int32_t *resCode = nullptr) { 15 | const static std::vector empty = {}; 16 | return curl_get(url, response, empty, resCode); 17 | } 18 | 19 | void curl_get_async(std::string url, std::vector extraHeader, std::function callback); 20 | 21 | inline void curl_get_async(std::string url, std::function callback) { 22 | return curl_get_async(std::move(url), std::vector{}, std::move(callback)); 23 | } 24 | 25 | CURLcode curl_download(const char *url, std::filesystem::path &path, std::vector &extraHeader, 26 | features::DownloadTask *task = nullptr, 27 | int32_t *resCode = nullptr); 28 | 29 | inline CURLcode curl_download(const char *url, std::filesystem::path &path, features::DownloadTask *task = nullptr, 30 | int32_t *resCode = nullptr) { 31 | std::vector empty = {}; 32 | return curl_download(url, path, empty, task, resCode); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Downloader/src/osu/Account.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Account.h" 3 | 4 | #include 5 | 6 | #include "utils/MD5.hpp" 7 | 8 | void osu::Password::to_json(nlohmann::json &j) const { 9 | j["PasswordHash"] = md5(); 10 | } 11 | 12 | void osu::Password::from_json(const nlohmann::json &j) { 13 | j["PasswordHash"].get_to(m_Password); 14 | } 15 | 16 | std::string osu::Password::md5() const { 17 | if (m_Password.empty()) 18 | return ""; 19 | 20 | static const std::string md5Regex = "^[a-f0-9]{32}$"; 21 | if (std::regex_match(m_Password, std::regex(md5Regex))) { 22 | return m_Password; 23 | } 24 | MD5 md5{}; 25 | md5.update(m_Password); 26 | return md5.str(); 27 | } 28 | 29 | void osu::Account::to_json(nlohmann::json &j) const { 30 | j["PasswordHash"] = m_Password.md5(); 31 | j["Username"] = m_Username; 32 | } 33 | 34 | void osu::Account::from_json(const nlohmann::json &j) { 35 | j["PasswordHash"].get_to(m_Password.m_Password); 36 | j["Username"].get_to(m_Username); 37 | } 38 | -------------------------------------------------------------------------------- /Downloader/src/osu/Account.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "misc/ISerializable.h" 5 | 6 | namespace osu { 7 | class Password : public ISerializable { 8 | std::string m_Password; 9 | 10 | friend class Account; 11 | 12 | public: 13 | Password() = default; 14 | 15 | explicit Password(std::string pw) : 16 | m_Password(std::move(pw)) { 17 | } 18 | 19 | void to_json(nlohmann::json &j) const override; 20 | void from_json(const nlohmann::json &j) override; 21 | 22 | std::string md5() const; 23 | }; 24 | 25 | class Account : public ISerializable { 26 | std::string m_Username; 27 | Password m_Password; 28 | 29 | public: 30 | Account() = default; 31 | 32 | Account(std::string username, Password pw) : 33 | m_Username(std::move(username)), m_Password(std::move(pw)) { 34 | } 35 | 36 | [[nodiscard]] const std::string &username() const { return m_Username; } 37 | [[nodiscard]] const Password &password() const { return m_Password; } 38 | 39 | 40 | void setUsername(const std::string &un) { 41 | m_Username = un; 42 | } 43 | 44 | void setPassword(const std::string &pw) { 45 | m_Password.m_Password = pw; 46 | } 47 | 48 | void to_json(nlohmann::json &j) const override; 49 | void from_json(const nlohmann::json &j) override; 50 | }; 51 | } 52 | 53 | template <> 54 | struct nlohmann::adl_serializer { 55 | static void to_json(json &j, const osu::Account &acc) { 56 | acc.to_json(j); 57 | } 58 | 59 | static void from_json(const json &j, osu::Account &acc) { 60 | acc.from_json(j); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /Downloader/src/osu/Beatmap.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Beatmap.h" 3 | -------------------------------------------------------------------------------- /Downloader/src/osu/Beatmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | namespace osu { 7 | 8 | struct Beatmap { 9 | std::string title; 10 | std::string artist; 11 | std::string author; 12 | std::vector bid; 13 | int32_t sid; 14 | bool hasVideo; 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Downloader/src/osu/BeatmapManager.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | 4 | #include 5 | #include "BeatmapManager.h" 6 | 7 | #include "features/HandleLinkHook.h" 8 | #include "utils/Utils.h" 9 | 10 | osu::BeatmapManager::BeatmapManager() { 11 | initDatabase(); 12 | } 13 | 14 | // taken from https://github.com/veritas501/Osu-Ingame-Downloader/blob/master/OsuIngameDownloader/map_db.cpp#L15 15 | #define fr(obj) fs.read((char*) &(obj), sizeof(obj)) 16 | #define fp(size) fs.seekg(size, std::ios::cur) 17 | 18 | unsigned int UnpackULEB128(std::ifstream &fs) { 19 | char flag = 0; 20 | fr(flag); 21 | if (flag != 0xb) { 22 | return 0; 23 | } 24 | unsigned char tmpByte = 0; 25 | unsigned int decodeNum = 0; 26 | int i = 0; 27 | do { 28 | fr(tmpByte); 29 | decodeNum += (tmpByte & 0x7f) << 7 * i; 30 | i++; 31 | } while (tmpByte >= 0x80); 32 | return decodeNum; 33 | } 34 | 35 | std::string UnpackOsuStr(std::ifstream &fs) { 36 | const uint32_t size = UnpackULEB128(fs); 37 | if (!size) 38 | return ""; 39 | 40 | char *tmpStr = new char[size + 1]; 41 | fs.read(tmpStr, size); 42 | tmpStr[size] = 0; 43 | 44 | std::string ans(tmpStr); 45 | delete[] tmpStr; 46 | return ans; 47 | } 48 | 49 | void PassOsuStr(std::ifstream &fs) { 50 | const uint32_t size = UnpackULEB128(fs); 51 | if (!size) 52 | return; 53 | fp(size); 54 | } 55 | 56 | void osu::BeatmapManager::initDatabase() { 57 | const auto osuDatabase = utils::GetOsuDirPath() / "osu!.db"; 58 | if (!exists(osuDatabase)) { 59 | LOGW("osu!.db not found!"); 60 | return; 61 | } 62 | 63 | LOGI("Start initialize osu! database..."); 64 | std::ifstream fs; 65 | int bid, sid; 66 | 67 | fs.open(osuDatabase, std::ios::binary | std::ios::in); 68 | fp(0x11); 69 | PassOsuStr(fs); 70 | int sumBeatmaps; 71 | fr(sumBeatmaps); 72 | for (int i = 0; i < sumBeatmaps; i++) { 73 | for (int j = 0; j < 9; j++) { 74 | PassOsuStr(fs); 75 | } 76 | fp(1 + 2 * 3 + 8 + 4 * 4 + 8); 77 | for (int j = 0; j < 4; j++) { 78 | int length = 0; 79 | fr(length); 80 | for (int k = 0; k < length; k++) { 81 | fp(1 + 4 + 1 + 8); 82 | } 83 | } 84 | fp(4 * 3); 85 | int timingPointsLength; 86 | fr(timingPointsLength); 87 | for (int j = 0; j < timingPointsLength; j++) { 88 | fp(8 * 2 + 1); 89 | } 90 | fr(bid); 91 | fr(sid); 92 | fp(4 + 1 * 4 + 2 + 4 + 1); 93 | PassOsuStr(fs); 94 | PassOsuStr(fs); 95 | fp(2); 96 | PassOsuStr(fs); 97 | fp(1 + 8 + 1); 98 | PassOsuStr(fs); 99 | fp(8 + 1 * 5 + 4 + 1); 100 | if (bid != -1 && bid != 0) { 101 | m_BeatmapIds.insert(bid); 102 | } 103 | if (sid != -1 && sid != 0) { 104 | m_BeatmapSetIds.insert(sid); 105 | } 106 | } 107 | LOGI("Finished initialize osu! database! sid size = %zu, bid size = %zu", m_BeatmapSetIds.size(), m_BeatmapIds.size()); 108 | } 109 | 110 | void osu::BeatmapManager::openBeatmapPage(Beatmap &bm) { 111 | if (bm.sid <= 0) { 112 | LOGW("Cannot open website: Invalid beatmap id %d!", bm.sid); 113 | return; 114 | } 115 | std::wstring s = L"https://osu.ppy.sh/s/"; 116 | s.append(std::to_wstring(bm.sid)); 117 | WLOGI(L"Opening website: %s", s.c_str()); 118 | 119 | SHELLEXECUTEINFOW sei = { 120 | sizeof(sei), 121 | 0, 122 | nullptr, 123 | L"open", 124 | s.c_str(), 125 | nullptr, 126 | nullptr, 127 | SW_HIDE, 128 | nullptr, 129 | nullptr, 130 | nullptr, 131 | nullptr, 132 | 0, 133 | nullptr, 134 | nullptr 135 | }; 136 | 137 | // prevent trigger search 138 | HookManager::CallOriginal(features::HandleLinkHook::ShellExecuteExW_Hook, &sei); 139 | } 140 | 141 | #undef fr 142 | #undef fp 143 | -------------------------------------------------------------------------------- /Downloader/src/osu/BeatmapManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Beatmap.h" 7 | #include "features/Downloader.h" 8 | 9 | namespace osu { 10 | class BeatmapManager { 11 | BeatmapManager(); 12 | 13 | std::set m_BeatmapSetIds = {}; 14 | std::set m_BeatmapIds = {}; 15 | std::shared_mutex m_rwlock; 16 | 17 | void initDatabase(); 18 | 19 | public: 20 | static BeatmapManager &GetInstance() { 21 | static BeatmapManager instance; 22 | return instance; 23 | } 24 | 25 | bool hasBeatmap(int32_t beatmapId) { 26 | std::shared_lock _g(m_rwlock); 27 | return beatmapId > 0 && m_BeatmapIds.contains(beatmapId); 28 | } 29 | 30 | bool hasBeatmapSet(int32_t beatmapSetId) { 31 | std::shared_lock _g(m_rwlock); 32 | return beatmapSetId > 0 && m_BeatmapSetIds.contains(beatmapSetId); 33 | } 34 | 35 | bool hasBeatmap(const Beatmap &bm) { 36 | std::shared_lock _g(m_rwlock); 37 | if (bm.sid > 0 && m_BeatmapSetIds.contains(bm.sid)) 38 | return true; 39 | if (!bm.bid.empty() && std::ranges::any_of(bm.bid, [&](const int32_t i) { 40 | return m_BeatmapIds.contains(i); 41 | })) { 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | void openBeatmapPage(Beatmap &bm); 48 | 49 | void addBeatmap(const Beatmap &bm) { 50 | std::unique_lock _g(m_rwlock); 51 | if (bm.sid > 0) 52 | m_BeatmapSetIds.insert(bm.sid); 53 | 54 | for (auto i : bm.bid) { 55 | if (i > 0) 56 | m_BeatmapIds.insert(i); 57 | } 58 | } 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /Downloader/src/osu/LinkParser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "features/Downloader.h" 7 | 8 | namespace osu { 9 | 10 | class LinkParser { 11 | public: 12 | static std::optional ParseLink( 13 | const std::string &link, 14 | const features::downloader::BeatmapType defIdType = features::downloader::BeatmapType::Sid) { 15 | 16 | const static std::string sidRegex = R"(/?(beatmapsets|s)/?(\d+))"; 17 | std::smatch match; 18 | if (const std::regex re1(sidRegex); std::regex_search(link, match, re1) && match.size() > 2) { 19 | const int id = std::stoi(match[2].str()); 20 | return features::downloader::BeatmapInfo{features::downloader::BeatmapType::Sid, id}; 21 | } 22 | 23 | const static std::string bidRegex = R"(/?(beatmaps|b)/?(\d+))"; 24 | if (const std::regex re2(bidRegex); std::regex_search(link, match, re2) && match.size() > 2) { 25 | const int id = std::stoi(match[2].str()); 26 | return features::downloader::BeatmapInfo{features::downloader::BeatmapType::Bid, id}; 27 | } 28 | 29 | const static std::string idRegex = R"(^(\d+)$)"; 30 | if (const std::regex re3(idRegex); std::regex_search(link, match, re3) && match.size() > 1) { 31 | const int id = std::stoi(match[1].str()); 32 | return features::downloader::BeatmapInfo{defIdType, id}; 33 | } 34 | 35 | return {}; 36 | } 37 | }; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Downloader/src/osu/OsuConfigManager.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "OsuConfigManager.h" 3 | 4 | #include 5 | 6 | #include "utils/Utils.h" 7 | 8 | void osu::OsuConfigManager::parseLine(const std::string &line) { 9 | const size_t pos = line.find('='); 10 | 11 | if (pos == std::string::npos) // Not k v pair 12 | return; 13 | 14 | std::string key = line.substr(0, pos); 15 | utils::trim(key); 16 | std::string value = line.substr(pos + 1); 17 | utils::trim(value); 18 | 19 | // Int: VolumeEffect = 85 20 | // String: keyOsuLeft = Z 21 | // Float: ScoreMeterScale = 1.5 22 | 23 | s_Config[key] = value; 24 | // LOGD("Parsed config: %s=%s", key.c_str(), value.c_str()); 25 | } 26 | 27 | void osu::OsuConfigManager::Init(const std::filesystem::path &path) { 28 | if (!exists(path)) { 29 | LOGE("Config file not exists!"); 30 | return; 31 | } 32 | s_Path = path; 33 | std::ifstream ifs(path, std::ios::binary); 34 | if (ifs.bad()) { 35 | LOGE("Cannot open config file!"); 36 | return; 37 | } 38 | 39 | LOGD("Osu! config path: %s", path.string().c_str()); 40 | 41 | std::string line; 42 | while (std::getline(ifs, line)) { 43 | parseLine(line); 44 | } 45 | LOGD("Finished parse config file!"); 46 | } 47 | 48 | int osu::OsuConfigManager::GetInt(const std::string &key, const int def) { 49 | if (!s_Config.contains(key)) { 50 | LOGW("No such key '%s' found in config", key.c_str()); 51 | return def; 52 | } 53 | 54 | try { 55 | return std::stoi(s_Config[key]); 56 | } catch (...) { 57 | LOGW("Not int value '%s' in config", key.c_str()); 58 | } 59 | 60 | return def; 61 | } 62 | 63 | std::string osu::OsuConfigManager::GetString(const std::string &key) { 64 | if (!s_Config.contains(key)) { 65 | LOGW("No such key '%s' found in config", key.c_str()); 66 | return {}; 67 | } 68 | 69 | return s_Config[key]; 70 | } 71 | 72 | float osu::OsuConfigManager::GetFloat(const std::string &key, const float def) { 73 | if (!s_Config.contains(key)) { 74 | LOGW("No such key '%s' found in config", key.c_str()); 75 | return def; 76 | } 77 | 78 | try { 79 | return std::stof(s_Config[key]); 80 | } catch (...) { 81 | LOGW("Not float value '%s' in config", key.c_str()); 82 | } 83 | 84 | return def; 85 | } 86 | -------------------------------------------------------------------------------- /Downloader/src/osu/OsuConfigManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace osu { 6 | 7 | class OsuConfigManager { 8 | inline static std::filesystem::path s_Path{}; 9 | 10 | inline static std::map s_Config{}; 11 | 12 | static void parseLine(const std::string &line); 13 | 14 | public: 15 | static void Init(const std::filesystem::path &path); 16 | 17 | static int GetInt(const std::string &key, int def = 0); 18 | static std::string GetString(const std::string &key); 19 | static float GetFloat(const std::string &key, float def = 0.0f); 20 | }; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Downloader/src/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /Downloader/src/pch.h: -------------------------------------------------------------------------------- 1 | #ifndef PCH_H 2 | #define PCH_H 3 | 4 | #include "framework.h" 5 | #include "Logger.h" 6 | #include "HookManager.hpp" 7 | #include 8 | #include "config/I18nManager.h" 9 | #include 10 | 11 | #define GET_LANG() auto &lang = i18n::I18nManager::GetInstance() 12 | #endif 13 | -------------------------------------------------------------------------------- /Downloader/src/renderer/backend/DirectX.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DirectX.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #pragma comment(lib, "D3dcompiler.lib") 9 | #pragma comment(lib, "d3d11.lib") 10 | #pragma comment(lib, "winmm.lib") 11 | 12 | typedef HRESULT(__stdcall *IDXGISwapChainPresent)(IDXGISwapChain *pSwapChain, UINT SyncInterval, UINT Flags); 13 | 14 | static IDXGISwapChainPresent fnIDXGISwapChainPresent; 15 | static ID3D11Device *pDevice = nullptr; 16 | 17 | HRESULT __stdcall Present_Hook(IDXGISwapChain *pChain, const UINT SyncInterval, const UINT Flags) { 18 | static BOOL inited = false; 19 | 20 | // Main D3D11 Objects 21 | static ID3D11DeviceContext *pContext = nullptr; 22 | 23 | if (!inited) { 24 | auto result = (HRESULT)pChain->GetDevice(__uuidof(pDevice), reinterpret_cast(&pDevice)); 25 | 26 | if (SUCCEEDED(result)) { 27 | pDevice->GetImmediateContext(&pContext); 28 | 29 | DXGI_SWAP_CHAIN_DESC sd; 30 | pChain->GetDesc(&sd); 31 | 32 | renderer::OnInitializeDX11(sd.OutputWindow, pDevice, pContext, pChain); 33 | 34 | inited = true; 35 | LOGD("Success initialized dx11!"); 36 | } else { 37 | LOGE("DirectX initialize failed! result: %d", result); 38 | } 39 | } 40 | 41 | // render function 42 | if (inited) 43 | renderer::OnRenderDX11(pContext); 44 | 45 | return HookManager::CallOriginal(Present_Hook, pChain, SyncInterval, Flags); 46 | } 47 | 48 | static IDXGISwapChainPresent findDirect11Present() { 49 | WNDCLASSEX wc{ 0 }; 50 | wc.cbSize = sizeof(wc); 51 | wc.lpfnWndProc = DefWindowProc; 52 | wc.lpszClassName = TEXT("Class"); 53 | 54 | if (!RegisterClassEx(&wc)) { 55 | return nullptr; 56 | } 57 | 58 | HWND hWnd = CreateWindow(wc.lpszClassName, TEXT(""), WS_DISABLED, 0, 0, 0, 0, NULL, NULL, NULL, nullptr); 59 | 60 | IDXGISwapChain *pSwapChain; 61 | 62 | D3D_FEATURE_LEVEL featureLevel; 63 | DXGI_SWAP_CHAIN_DESC swapChainDesc; 64 | ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); 65 | swapChainDesc.BufferCount = 1; 66 | swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 67 | swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 68 | swapChainDesc.OutputWindow = hWnd; 69 | swapChainDesc.SampleDesc.Count = 1; 70 | swapChainDesc.Windowed = TRUE; //((GetWindowLong(hWnd, GWL_STYLE) & WS_POPUP) != 0) ? FALSE : TRUE; 71 | swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; 72 | swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; 73 | swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; 74 | 75 | // Main D3D11 Objects 76 | ID3D11DeviceContext *pContext = nullptr; 77 | ID3D11Device *pDevice = nullptr; 78 | 79 | if (FAILED(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0, nullptr, 1, D3D11_SDK_VERSION, 80 | &swapChainDesc, &pSwapChain, &pDevice, &featureLevel, &pContext)) && 81 | FAILED(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, 82 | &swapChainDesc, &pSwapChain, &pDevice, &featureLevel, &pContext))) { 83 | DestroyWindow(swapChainDesc.OutputWindow); 84 | UnregisterClass(wc.lpszClassName, GetModuleHandle(nullptr)); 85 | 86 | return nullptr; 87 | } 88 | 89 | const DWORD_PTR *pSwapChainVtable = reinterpret_cast(pSwapChain); 90 | pSwapChainVtable = reinterpret_cast(pSwapChainVtable[0]); 91 | 92 | auto swapChainPresent = reinterpret_cast(pSwapChainVtable[8]); 93 | 94 | pDevice->Release(); 95 | // pContext->Release(); 96 | pSwapChain->Release(); 97 | 98 | DestroyWindow(swapChainDesc.OutputWindow); 99 | UnregisterClass(wc.lpszClassName, GetModuleHandle(nullptr)); 100 | 101 | return swapChainPresent; 102 | } 103 | 104 | void backend::InitDX11Hooks() { 105 | LOGD("Start init dx11 hook."); 106 | fnIDXGISwapChainPresent = findDirect11Present(); 107 | if (fnIDXGISwapChainPresent == nullptr) { 108 | LOGE("Failed to find `Present` function for dx11."); 109 | return; 110 | } 111 | LOGD("SwapChain Present: 0x%p", fnIDXGISwapChainPresent); 112 | 113 | HookManager::InstallHook(fnIDXGISwapChainPresent, Present_Hook); 114 | LOGD("Finished initialize dx11 hooks!"); 115 | } 116 | -------------------------------------------------------------------------------- /Downloader/src/renderer/backend/DirectX.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace backend { 7 | void InitDX11Hooks(); 8 | } -------------------------------------------------------------------------------- /Downloader/src/renderer/backend/OpenGL.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "OpenGL.h" 3 | #include "renderer/renderer.h" 4 | 5 | #pragma comment(lib, "OpenGL32.lib") 6 | 7 | namespace backend { 8 | 9 | typedef BOOL(WINAPI *FnSwapBuffers)(HDC); 10 | 11 | BOOL WINAPI OnSwapBuffers(HDC hdc) { 12 | renderer::OnRenderGL(hdc); 13 | return HookManager::CallOriginal(OnSwapBuffers, hdc); 14 | } 15 | 16 | void InitGLHooks() { 17 | HMODULE hModule = GetModuleHandleW(L"gdi32.dll"); 18 | if (hModule == nullptr) { 19 | LOGE("Cannot get gdi32.dll handle!"); 20 | return; 21 | } 22 | 23 | auto pFn = (FnSwapBuffers)GetProcAddress(hModule, "SwapBuffers"); 24 | LOGD("SwapBuffers address: 0x%p", pFn); 25 | HookManager::InstallHook(pFn, OnSwapBuffers); 26 | LOGD("OpenGL rendering hooks installed!"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Downloader/src/renderer/backend/OpenGL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace backend { 7 | void InitGLHooks(); 8 | } -------------------------------------------------------------------------------- /Downloader/src/renderer/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "renderer.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "backend/DirectX.h" 10 | #include "backend/OpenGL.h" 11 | #include "ui/MainUi.h" 12 | #include 13 | #include 14 | #include "../ui/BlockingInput.hpp" 15 | #include "misc/Hotkey.hpp" 16 | #include "ui/BeatmapIdSearchUi.h" 17 | #include "ui/SearchResultUi.h" 18 | #include "utils/gui_utils.h" 19 | 20 | extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 21 | 22 | namespace renderer { 23 | static HWND s_OsuHwnd; 24 | 25 | void Update() { 26 | ImGuiIO &io = ImGui::GetIO(); 27 | io.MouseDrawCursor = ui::InputBlock::IsBlocked(); 28 | ui::main::Update(); 29 | ui::search::result::Update(); 30 | ui::search::beatmapid::Update(); 31 | GuiHelper::RenderToast(); 32 | } 33 | 34 | ImFont *_currentFont = nullptr; 35 | 36 | // taken from https://github.com/veritas501/Osu-Ingame-Downloader/blob/master/OsuIngameDownloader/game_hook.cpp#L166 37 | LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) { 38 | if (nCode != HC_ACTION) { 39 | return CallNextHookEx(g_msgHook, nCode, wParam, lParam); 40 | } 41 | MSG *msg = (MSG *)lParam; 42 | 43 | if (wParam == PM_REMOVE) { 44 | switch (msg->message) { 45 | case WM_KEYDOWN: { 46 | misc::Hotkey::AddKeyDown(msg->wParam); 47 | // switch (msg->wParam) { 48 | // case VK_HOME: 49 | // ui::main::ToggleShow(); 50 | // break; 51 | // case VK_END: 52 | // ui::search::beatmapid::ToggleShow(); 53 | // break; 54 | // } 55 | break; 56 | } 57 | case WM_KEYUP: { 58 | misc::Hotkey::RemoveKeyDown(msg->wParam); 59 | break; 60 | } 61 | } 62 | 63 | if (ui::InputBlock::IsBlocked()) 64 | ImGui_ImplWin32_WndProcHandler(msg->hwnd, msg->message, msg->wParam, msg->lParam); 65 | } 66 | 67 | if (ui::InputBlock::IsBlocked()) { 68 | if (msg->message == WM_CHAR) { 69 | msg->message = WM_NULL; 70 | return 1; 71 | } 72 | if ((WM_MOUSEFIRST <= msg->message && msg->message <= WM_MOUSELAST) || (msg->message == WM_NCHITTEST) || (msg->message == 73 | WM_SETCURSOR)) { 74 | msg->message = WM_NULL; 75 | return 1; 76 | } 77 | } 78 | return CallNextHookEx(g_msgHook, nCode, wParam, lParam); 79 | } 80 | 81 | DWORD WINAPI MsgHookThread(LPVOID lpParam) { 82 | DWORD tid = *(DWORD *)lpParam; 83 | g_msgHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, GetModuleHandle(NULL), tid); 84 | if (!g_msgHook) { 85 | LOGE("Cannot set message hook!"); 86 | return 1; 87 | } 88 | 89 | LOGD("Successfully to set message hook."); 90 | MSG msg; 91 | while (GetMessage(&msg, NULL, 0, 0)) { 92 | TranslateMessage(&msg); 93 | DispatchMessage(&msg); 94 | } 95 | return 0; 96 | } 97 | 98 | LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { 99 | switch (msg) { 100 | case WM_ACTIVATE: 101 | if (wParam == WA_INACTIVE) 102 | misc::Hotkey::ClearPressedKey(); 103 | break; 104 | } 105 | 106 | return CallWindowProc(OriginalWndProcHandler, hwnd, msg, wParam, lParam); 107 | } 108 | 109 | 110 | void OnRenderGL(HDC hdc) { 111 | ImGuiIO &io = ImGui::GetIO(); 112 | ImGuiContext *g = ImGui::GetCurrentContext(); 113 | if (g == nullptr) 114 | return; 115 | 116 | if (!io.BackendRendererUserData) { 117 | ImGui_ImplOpenGL3_Init(); 118 | s_OsuHwnd = WindowFromDC(hdc); 119 | ImGui_ImplWin32_InitForOpenGL(s_OsuHwnd); 120 | DWORD tid = GetCurrentThreadId(); 121 | CreateThread(nullptr, 0, MsgHookThread, &tid, 0, nullptr); 122 | OriginalWndProcHandler = (WNDPROC)SetWindowLongPtr(s_OsuHwnd, GWLP_WNDPROC, (LONG_PTR)WndProc); 123 | } 124 | 125 | ImGui_ImplOpenGL3_NewFrame(); 126 | ImGui_ImplWin32_NewFrame(); 127 | 128 | if (_currentFont != nullptr) 129 | io.FontDefault = _currentFont; 130 | 131 | 132 | ImGui::NewFrame(); 133 | __try { 134 | Update(); 135 | } __except (1) { 136 | LOGW("Exception on update"); 137 | } 138 | ImGui::EndFrame(); 139 | ImGui::Render(); 140 | 141 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 142 | } 143 | 144 | void Init(GraphicsApiType version) { 145 | switch (version) { 146 | case GraphicsApiType::D3D11: 147 | LOGE("D3D is not impl yet! Please turn off the compatibility mode!"); 148 | backend::InitDX11Hooks(); 149 | break; 150 | case GraphicsApiType::OpenGL3: 151 | backend::InitGLHooks(); 152 | break; 153 | default: 154 | LOGE("Unsupported api version!"); 155 | return; 156 | } 157 | } 158 | 159 | static ID3D11RenderTargetView *mainRenderTargetView; 160 | 161 | void OnInitializeDX11(HWND window, ID3D11Device *pDevice, ID3D11DeviceContext *pContext, IDXGISwapChain *pChain) { 162 | /* 163 | ImGuiIO &io = ImGui::GetIO(); 164 | if (!io.BackendRendererUserData) 165 | return; 166 | 167 | ImGui_ImplWin32_Init(window); 168 | ImGui_ImplDX11_Init(pDevice, pContext); 169 | 170 | ID3D11Texture2D *pBackBuffer; 171 | pChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&pBackBuffer)); 172 | pDevice->CreateRenderTargetView(pBackBuffer, nullptr, &mainRenderTargetView); 173 | pBackBuffer->Release(); 174 | LOGD("Success initialized dx11!"); 175 | */ 176 | } 177 | 178 | void OnRenderDX11(ID3D11DeviceContext *pContext) { 179 | /* 180 | ImGuiIO &io = ImGui::GetIO(); 181 | 182 | if (!io.Fonts->IsBuilt()) { 183 | io.Fonts->Build(); 184 | ImGui_ImplDX11_InvalidateDeviceObjects(); 185 | } 186 | 187 | ImGui_ImplDX11_NewFrame(); 188 | ImGui_ImplWin32_NewFrame(); 189 | 190 | if (_currentFont != nullptr) 191 | io.FontDefault = _currentFont; 192 | 193 | ImGui::NewFrame(); 194 | Update(); 195 | ImGui::Render(); 196 | 197 | pContext->OMSetRenderTargets(1, &mainRenderTargetView, nullptr); 198 | ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); 199 | */ 200 | } 201 | 202 | void SetCurrentFont(uint8_t *byte, size_t size, float pixelSize, ImFontConfig *cfg, const ImWchar *glyphRanges) { 203 | if (byte == nullptr || size == 0) { 204 | LOGE("Font file doesn't exists!"); 205 | return; 206 | } 207 | 208 | LOGD("Begin change font"); 209 | ImGuiIO &io = ImGui::GetIO(); 210 | _currentFont = io.Fonts->AddFontFromMemoryTTF(byte, size, pixelSize, cfg, glyphRanges); 211 | LOGD("Finished change font"); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Downloader/src/renderer/renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace renderer { 7 | inline HHOOK g_msgHook = nullptr; 8 | 9 | enum class GraphicsApiType { 10 | D3D9, 11 | D3D10, 12 | D3D11, 13 | D3D12, 14 | OpenGL2, 15 | OpenGL3, 16 | Vulkan 17 | }; 18 | 19 | inline static WNDPROC OriginalWndProcHandler = nullptr; 20 | 21 | void Init(GraphicsApiType version = GraphicsApiType::OpenGL3); 22 | 23 | void SetCurrentFont(uint8_t *byte, size_t size, float pixelSize, ImFontConfig *cfg, const ImWchar *glyphRanges); 24 | 25 | void OnRenderDX11(ID3D11DeviceContext *pContext); 26 | void OnInitializeDX11(HWND window, ID3D11Device *pDevice, ID3D11DeviceContext *pContext, IDXGISwapChain *pChain); 27 | 28 | void OnRenderGL(HDC hdc); 29 | } 30 | -------------------------------------------------------------------------------- /Downloader/src/ui/BeatmapIdSearchUi.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "BeatmapIdSearchUi.h" 3 | 4 | #include 5 | 6 | #include "BlockingInput.hpp" 7 | #include "config/I18nManager.h" 8 | #include "features/Downloader.h" 9 | #include "osu/LinkParser.hpp" 10 | #include "utils/gui_utils.h" 11 | 12 | static bool show = false; 13 | 14 | static bool focus = false; 15 | 16 | bool ui::search::beatmapid::IsShowed() { 17 | return show; 18 | } 19 | 20 | void ui::search::beatmapid::ToggleShow() { 21 | show = !show; 22 | focus = false; 23 | show ? InputBlock::Push() : InputBlock::Pop(); 24 | } 25 | 26 | // 0 = Sid, 1 = Bid 27 | static int selected = 0; 28 | 29 | void ui::search::beatmapid::Update() { 30 | if (!show) 31 | return; 32 | 33 | auto &lang = i18n::I18nManager::GetInstance(); 34 | 35 | constexpr static ImVec2 windowSize = ImVec2(425, 65); 36 | if (static bool inited = false; !inited) { 37 | const ImVec2 screenSize = ImGui::GetIO().DisplaySize; 38 | const ImVec2 windowPos(screenSize.x / 2 - windowSize.x / 2, screenSize.y / 2 - windowSize.y / 2); 39 | ImGui::SetNextWindowSize(windowSize); 40 | ImGui::SetNextWindowPos(windowPos); 41 | inited = true; 42 | } 43 | 44 | ImGui::Begin(lang.getTextCStr("SearchBeatmapId"), nullptr, 45 | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize); 46 | static std::string input; 47 | static const char *items[] = {"Sid", "Bid"}; 48 | 49 | ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3); 50 | ImGui::Combo("##type", &selected, items, IM_ARRAYSIZE(items)); 51 | 52 | ImGui::SameLine(); 53 | ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10); 54 | if (!focus) { 55 | ImGui::SetKeyboardFocusHere(); 56 | focus = true; 57 | } 58 | ImGui::InputText("##search", &input); 59 | 60 | ImGui::SameLine(); 61 | if (ImGui::Button(lang.getTextCStr("Clear"))) { 62 | input = ""; 63 | } 64 | ImGui::SameLine(); 65 | if (ImGui::Button(lang.getTextCStr("Paste"))) { 66 | input = ImGui::GetClipboardText(); 67 | } 68 | 69 | ImGui::SameLine(); 70 | if (ImGui::Button(lang.getTextCStr("Search"))) { 71 | if (const auto bi = osu::LinkParser::ParseLink(input, (features::downloader::BeatmapType)selected); !bi) { 72 | GuiHelper::ShowWarnToast(lang.getTextCStr("InvalidInput"), input.c_str()); 73 | } else { 74 | features::Downloader::GetInstance().postSearch(*bi); 75 | } 76 | } 77 | 78 | GuiHelper::ShowTooltip(lang.getTextCStr("SearchBeatmapIdDesc")); 79 | 80 | ImGui::End(); 81 | } 82 | -------------------------------------------------------------------------------- /Downloader/src/ui/BeatmapIdSearchUi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ui::search::beatmapid { 4 | bool IsShowed(); 5 | void ToggleShow(); 6 | void Update(); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /Downloader/src/ui/BlockingInput.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ui { 4 | class InputBlock { 5 | inline static int block = 0; 6 | 7 | public: 8 | InputBlock() = delete; 9 | 10 | static void Push() { 11 | ++block; 12 | } 13 | 14 | static void Pop() { 15 | if (--block < 0) 16 | block = 0; 17 | } 18 | 19 | static bool IsBlocked() { 20 | return block; 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /Downloader/src/ui/MainUi.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "MainUi.h" 3 | #include "config/I18nManager.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "utils/gui_utils.h" 9 | #include "BlockingInput.hpp" 10 | 11 | namespace ui::main { 12 | static bool show = false; 13 | static std::unordered_map> s_Features; 14 | static const std::string_view *s_CurrentSelectedCategory = nullptr; 15 | 16 | bool IsShowed() { 17 | return show; 18 | } 19 | 20 | void ToggleShow() { 21 | show = !show; 22 | show ? InputBlock::Push() : InputBlock::Pop(); 23 | } 24 | 25 | void Update() { 26 | if (!show) 27 | return; 28 | 29 | ImGui::SetNextWindowSize(ImVec2(600, 300), ImGuiCond_FirstUseEver); 30 | 31 | i18n::I18nManager &lang = i18n::I18nManager::GetInstance(); 32 | ImGui::Begin(lang.GetTextCStr("OsuBeatmapDownloader"), nullptr, ImGuiWindowFlags_None); 33 | 34 | ImGui::BeginGroup(); 35 | 36 | // Draw category 37 | if (ImGui::BeginListBox("##feature category list", ImVec2(100, -FLT_MIN))) { 38 | for (const auto &category : s_Features | std::views::keys) { 39 | if (s_CurrentSelectedCategory == nullptr) { 40 | s_CurrentSelectedCategory = &category; 41 | } 42 | 43 | const bool isSelected = s_CurrentSelectedCategory == &category; 44 | 45 | if (ImGui::Selectable(lang.GetTextCStr(category), isSelected)) { 46 | s_CurrentSelectedCategory = &category; 47 | } 48 | 49 | if (isSelected) { 50 | ImGui::SetItemDefaultFocus(); 51 | } 52 | } 53 | 54 | ImGui::EndListBox(); 55 | } 56 | 57 | ImGui::EndGroup(); 58 | 59 | ImGui::SameLine(); 60 | 61 | ImGui::BeginGroup(); 62 | 63 | ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; 64 | ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); 65 | ImGui::BeginChild("ChildR", ImVec2(0, 0), true, window_flags); 66 | 67 | // Draw group 68 | if (s_CurrentSelectedCategory) { 69 | for (auto &needDrawFeatures = s_Features[*s_CurrentSelectedCategory]; const auto feature : 70 | std::ranges::reverse_view(needDrawFeatures)) { 71 | const auto &info = feature->getInfo(); 72 | auto group = lang.getText(info.groupName); 73 | 74 | if (group.empty()) { 75 | feature->drawMain(); 76 | } else { 77 | ImGui::BeginGroupPanel(group.data()); 78 | 79 | ImGui::PushID(feature); 80 | feature->drawMain(); 81 | ImGui::PopID(); 82 | 83 | ImGui::EndGroupPanel(); 84 | } 85 | } 86 | } 87 | 88 | ImGui::EndChild(); 89 | ImGui::PopStyleVar(); 90 | 91 | ImGui::EndGroup(); 92 | 93 | ImGui::End(); 94 | } 95 | 96 | void AddFeature(features::Feature *feature) { 97 | auto &info = feature->getInfo(); 98 | if (!s_Features.contains(info.category)) 99 | s_Features[info.category] = {}; 100 | s_Features[info.category].insert(feature); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Downloader/src/ui/MainUi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "features/Feature.h" 6 | 7 | namespace ui::main { 8 | bool IsShowed(); 9 | void ToggleShow(); 10 | void Update(); 11 | 12 | void AddFeature(features::Feature *feature); 13 | } 14 | -------------------------------------------------------------------------------- /Downloader/src/ui/SearchResultUi.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "SearchResultUi.h" 3 | 4 | #include 5 | 6 | #include "BlockingInput.hpp" 7 | #include "config/I18nManager.h" 8 | #include "features/Downloader.h" 9 | #include "osu/BeatmapManager.h" 10 | #include "utils/gui_utils.h" 11 | 12 | 13 | namespace ui::search::result { 14 | 15 | static bool isShow = false; 16 | 17 | bool IsShowed() { 18 | return isShow; 19 | } 20 | 21 | std::optional curShowBm; 22 | 23 | void ShowSearchInfo(osu::Beatmap &bm) { 24 | curShowBm = bm; 25 | if (isShow) return; 26 | isShow = true; 27 | InputBlock::Push(); 28 | } 29 | 30 | void Update() { 31 | if (!isShow) return; 32 | if (!curShowBm.has_value()) return; 33 | 34 | auto &bm = *curShowBm; 35 | ImGui::SetNextWindowSize(ImVec2(300, 225), ImGuiCond_FirstUseEver); 36 | auto &lang = i18n::I18nManager::GetInstance(); 37 | 38 | ImGui::Begin(lang.getTextCStr("BeatmapInfo"), nullptr, ImGuiWindowFlags_NoCollapse); 39 | 40 | ImGui::Text(lang.getTextCStr("Title"), bm.title.c_str()); 41 | ImGui::Text(lang.getTextCStr("Artist"), bm.artist.c_str()); 42 | ImGui::Text(lang.getTextCStr("Mapper"), bm.author.c_str()); 43 | 44 | ImGui::NewLine(); 45 | 46 | const bool hasMap = osu::BeatmapManager::GetInstance().hasBeatmap(bm); 47 | 48 | ImGui::SetCursorPosX((ImGui::GetWindowSize().x - ImGui::CalcTextSize(lang.getTextCStr(hasMap ? "ReDownload" : "Download")).x) * 0.5f); 49 | if (ImGui::Button(lang.getTextCStr(hasMap ? "ReDownload" : "Download"))) { 50 | features::Downloader::GetInstance().postDownload(bm); 51 | isShow = false; 52 | InputBlock::Pop(); 53 | } 54 | 55 | ImGui::SetCursorPosX((ImGui::GetWindowSize().x - ImGui::CalcTextSize(lang.getTextCStr("ViewWebsite")).x) * 0.5f); 56 | if (ImGui::Button(lang.getTextCStr("ViewWebsite"))) { 57 | osu::BeatmapManager::GetInstance().openBeatmapPage(bm); 58 | } 59 | 60 | ImGui::SetCursorPosX((ImGui::GetWindowSize().x - ImGui::CalcTextSize(lang.getTextCStr("Cancel")).x) * 0.5f); 61 | if (ImGui::Button(lang.getTextCStr("Cancel"))) { 62 | curShowBm = {}; 63 | isShow = false; 64 | InputBlock::Pop(); 65 | } 66 | 67 | ImGui::End(); 68 | } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /Downloader/src/ui/SearchResultUi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "osu/Beatmap.h" 3 | 4 | namespace ui::search::result { 5 | bool IsShowed(); 6 | void ShowSearchInfo(osu::Beatmap &bm); 7 | void Update(); 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /Downloader/src/utils/MD5.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class MD5 { 10 | static constexpr unsigned S[64] = { 11 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 12 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 13 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 14 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 15 | }; 16 | 17 | static constexpr unsigned K[64] = { 18 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 19 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 20 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 21 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 22 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 23 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 24 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 25 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 26 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 27 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 28 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 29 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 30 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 31 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 32 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 33 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 34 | }; 35 | 36 | unsigned a0 = 0x67452301; 37 | unsigned b0 = 0xefcdab89; 38 | unsigned c0 = 0x98badcfe; 39 | unsigned d0 = 0x10325476; 40 | 41 | unsigned long long bitlen = 0; 42 | std::vector buffer; 43 | bool finalized = false; 44 | 45 | static unsigned F(unsigned x, unsigned y, unsigned z) { return (x & y) | (~x & z); } 46 | 47 | static unsigned G(unsigned x, unsigned y, unsigned z) { return (x & z) | (y & ~z); } 48 | 49 | static unsigned H(unsigned x, unsigned y, unsigned z) { return x ^ y ^ z; } 50 | 51 | static unsigned I(unsigned x, unsigned y, unsigned z) { return y ^ (x | ~z); } 52 | 53 | static unsigned rotateLeft(unsigned x, unsigned n) { return (x << n) | (x >> (32 - n)); } 54 | 55 | void transform(const unsigned char block[64]) { 56 | unsigned a = a0, b = b0, c = c0, d = d0; 57 | unsigned M[16]; 58 | 59 | for (int i = 0; i < 16; i++) { 60 | M[i] = (block[i * 4] & 0xFF) | 61 | (block[i * 4 + 1] & 0xFF) << 8 | 62 | (block[i * 4 + 2] & 0xFF) << 16 | 63 | (block[i * 4 + 3] & 0xFF) << 24; 64 | } 65 | 66 | for (unsigned i = 0; i < 64; i++) { 67 | unsigned f, g; 68 | if (i < 16) { 69 | f = F(b, c, d); 70 | g = i; 71 | } else if (i < 32) { 72 | f = G(b, c, d); 73 | g = (5 * i + 1) % 16; 74 | } else if (i < 48) { 75 | f = H(b, c, d); 76 | g = (3 * i + 5) % 16; 77 | } else { 78 | f = I(b, c, d); 79 | g = (7 * i) % 16; 80 | } 81 | 82 | unsigned temp = d; 83 | d = c; 84 | c = b; 85 | b = b + rotateLeft((a + f + K[i] + M[g]), S[i]); 86 | a = temp; 87 | } 88 | 89 | a0 += a; 90 | b0 += b; 91 | c0 += c; 92 | d0 += d; 93 | } 94 | 95 | public: 96 | void update(const std::string &s) { 97 | const auto *input = reinterpret_cast(s.c_str()); 98 | unsigned len = s.length(); 99 | unsigned i = 0, idx = bitlen / 8 % 64; 100 | bitlen += len << 3; 101 | 102 | if (idx) { 103 | unsigned fill = 64 - idx; 104 | if (fill > len) fill = len; 105 | for (unsigned j = 0; j < fill; j++) 106 | buffer.push_back(input[j]); 107 | i += fill; 108 | if (idx + fill < 64) 109 | return; 110 | transform(&buffer[0]); 111 | buffer.clear(); 112 | } 113 | 114 | for (; i + 64 <= len; i += 64) 115 | transform(input + i); 116 | 117 | if (i < len) 118 | for (unsigned j = 0; j < len - i; j++) 119 | buffer.push_back(input[i + j]); 120 | } 121 | 122 | void finalize() { 123 | if (!finalized) { 124 | buffer.push_back(0x80); 125 | unsigned idx = buffer.size(); 126 | if (idx > 56) { 127 | buffer.resize(64, 0); 128 | transform(&buffer[0]); 129 | buffer.clear(); 130 | idx = 0; 131 | } 132 | 133 | buffer.resize(56, 0); 134 | for (int i = 0; i < 8; i++) buffer.push_back((bitlen >> (i * 8)) & 0xFF); 135 | transform(&buffer[0]); 136 | 137 | finalized = true; 138 | } 139 | } 140 | 141 | std::string str() { 142 | finalize(); 143 | std::stringstream ss; 144 | ss << std::hex << std::setfill('0'); 145 | ss << std::setw(2) << ((a0 >> 0) & 0xFF) << std::setw(2) << ((a0 >> 8) & 0xFF) 146 | << std::setw(2) << ((a0 >> 16) & 0xFF) << std::setw(2) << ((a0 >> 24) & 0xFF); 147 | ss << std::setw(2) << ((b0 >> 0) & 0xFF) << std::setw(2) << ((b0 >> 8) & 0xFF) 148 | << std::setw(2) << ((b0 >> 16) & 0xFF) << std::setw(2) << ((b0 >> 24) & 0xFF); 149 | ss << std::setw(2) << ((c0 >> 0) & 0xFF) << std::setw(2) << ((c0 >> 8) & 0xFF) 150 | << std::setw(2) << ((c0 >> 16) & 0xFF) << std::setw(2) << ((c0 >> 24) & 0xFF); 151 | ss << std::setw(2) << ((d0 >> 0) & 0xFF) << std::setw(2) << ((d0 >> 8) & 0xFF) 152 | << std::setw(2) << ((d0 >> 16) & 0xFF) << std::setw(2) << ((d0 >> 24) & 0xFF); 153 | return ss.str(); 154 | } 155 | 156 | std::array digest() { 157 | finalize(); 158 | std::array out{}; 159 | for (int i = 0; i < 4; i++) { 160 | out[i] = (a0 >> (i * 8)) & 0xFF; 161 | out[i + 4] = (b0 >> (i * 8)) & 0xFF; 162 | out[i + 8] = (c0 >> (i * 8)) & 0xFF; 163 | out[i + 12] = (d0 >> (i * 8)) & 0xFF; 164 | } 165 | return out; 166 | } 167 | }; 168 | -------------------------------------------------------------------------------- /Downloader/src/utils/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Utils.h" 3 | 4 | namespace utils { 5 | static HMODULE g_hModule = NULL; 6 | static std::filesystem::path g_curDirPath; 7 | static std::filesystem::path g_osuDirPath; 8 | 9 | void SetMyModuleHandle(HMODULE hModule) { 10 | g_hModule = hModule; 11 | } 12 | 13 | HMODULE GetMyModuleHandle() { 14 | return g_hModule; 15 | } 16 | 17 | void SetCurrentDirPath(const std::filesystem::path &path) { 18 | g_curDirPath = path; 19 | } 20 | 21 | std::filesystem::path GetModulePath(HMODULE *hModule) { 22 | wchar_t pathOut[MAX_PATH] = {0}; 23 | GetModuleFileNameW(*hModule, pathOut, MAX_PATH); 24 | return {pathOut}; 25 | } 26 | 27 | void SetOsuDirPath(const std::filesystem::path &path) { 28 | g_osuDirPath = path; 29 | } 30 | 31 | std::filesystem::path GetCurrentDirPath() { 32 | return g_curDirPath; 33 | } 34 | 35 | std::filesystem::path GetOsuDirPath() { 36 | return g_osuDirPath; 37 | } 38 | 39 | std::wstring s2ws(const std::string &s) { 40 | int len; 41 | int slength = (int)s.length() + 1; 42 | len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); 43 | wchar_t *buf = new wchar_t[len]; 44 | MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); 45 | std::wstring r(buf); 46 | delete[] buf; 47 | return r; 48 | } 49 | 50 | std::string ws2s(const std::wstring &s) { 51 | int len; 52 | int slength = (int)s.length() + 1; 53 | len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0); 54 | char *buf = new char[len]; 55 | WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, buf, len, 0, 0); 56 | std::string r(buf); 57 | delete[] buf; 58 | return r; 59 | } 60 | 61 | std::string &trim_end(std::string &str) { 62 | str.erase(std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) { 63 | return !std::isspace(ch); 64 | }).base(), str.end()); 65 | 66 | return str; 67 | } 68 | 69 | std::string &trim_begin(std::string &str) { 70 | str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](unsigned char ch) { 71 | return !std::isspace(ch); 72 | })); 73 | return str; 74 | } 75 | 76 | std::string &trim(std::string &str) { 77 | return trim_begin(trim_end(str)); 78 | } 79 | 80 | std::vector split(std::string res, const char split) { 81 | std::vector result; 82 | if (res.empty()) 83 | return result; 84 | 85 | std::string::size_type pos; 86 | while ((pos = res.find(split)) != std::string::npos) { 87 | auto r = res.substr(0, pos); 88 | result.push_back(r); 89 | res.erase(0, pos + 1); 90 | } 91 | 92 | result.push_back(res); 93 | return result; 94 | } 95 | 96 | std::vector split(std::string res, const std::string &split) { 97 | std::vector result; 98 | if (res.empty()) 99 | return result; 100 | 101 | std::string::size_type pos; 102 | while ((pos = res.find(split)) != std::string::npos) { 103 | auto r = res.substr(0, pos); 104 | result.push_back(r); 105 | res.erase(0, pos + split.length()); 106 | } 107 | 108 | result.push_back(res); 109 | return result; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Downloader/src/utils/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace utils { 8 | 9 | void SetMyModuleHandle(HMODULE hModule); 10 | HMODULE GetMyModuleHandle(); 11 | 12 | void SetCurrentDirPath(const std::filesystem::path &path); 13 | std::filesystem::path GetCurrentDirPath(); 14 | std::filesystem::path GetModulePath(HMODULE *hModule); 15 | void SetOsuDirPath(const std::filesystem::path &path); 16 | std::filesystem::path GetOsuDirPath(); 17 | 18 | std::wstring s2ws(const std::string &s); 19 | std::string ws2s(const std::wstring &s); 20 | 21 | std::string &trim_end(std::string &str); 22 | std::string &trim_begin(std::string &str); 23 | std::string &trim(std::string &str); 24 | 25 | std::vector split(std::string res, char split); 26 | std::vector split(std::string res, const std::string &split); 27 | } 28 | -------------------------------------------------------------------------------- /Downloader/src/utils/gui_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "features/Settings.h" 7 | 8 | namespace GuiHelper { 9 | void ShowTooltip(const char *s, bool marked = true); 10 | 11 | template 12 | void ShowToast(const char *s, Fmt... fmt) { 13 | auto &settings = features::Settings::GetInstance(); 14 | if (!settings.f_EnableToast.getValue()) 15 | return; 16 | 17 | ImGui::InsertNotification({ImGuiToastType_None, settings.f_ToastDuration.getValue(), s, fmt...}); 18 | } 19 | 20 | template 21 | void ShowWarnToast(const char *s, Fmt... fmt) { 22 | auto &settings = features::Settings::GetInstance(); 23 | if (!settings.f_EnableToast.getValue()) 24 | return; 25 | 26 | ImGuiToast t{ImGuiToastType_Warning, settings.f_ToastDuration.getValue(), s, fmt...}; 27 | t.set_title(i18n::I18nManager::GetInstance().getTextCStr("Warning")); 28 | 29 | ImGui::InsertNotification(t); 30 | } 31 | 32 | template 33 | void ShowSuccessToast(const char *s, Fmt... fmt) { 34 | auto &settings = features::Settings::GetInstance(); 35 | if (!settings.f_EnableToast.getValue()) 36 | return; 37 | 38 | ImGuiToast t{ImGuiToastType_Success, settings.f_ToastDuration.getValue(), s, fmt...}; 39 | t.set_title(i18n::I18nManager::GetInstance().getTextCStr("Success")); 40 | 41 | ImGui::InsertNotification(t); 42 | } 43 | 44 | template 45 | void ShowErrorToast(const char *s, Fmt... fmt) { 46 | auto &settings = features::Settings::GetInstance(); 47 | if (!settings.f_EnableToast.getValue()) 48 | return; 49 | 50 | ImGuiToast t{ImGuiToastType_Error, settings.f_ToastDuration.getValue(), s, fmt...}; 51 | t.set_title(i18n::I18nManager::GetInstance().getTextCStr("Error")); 52 | 53 | ImGui::InsertNotification(t); 54 | } 55 | 56 | template 57 | void ShowInfoToast(const char *s, Fmt... fmt) { 58 | auto &settings = features::Settings::GetInstance(); 59 | if (!settings.f_EnableToast.getValue()) 60 | return; 61 | 62 | ImGuiToast t{ImGuiToastType_Info, settings.f_ToastDuration.getValue(), s, fmt...}; 63 | t.set_title(i18n::I18nManager::GetInstance().getTextCStr("Info")); 64 | 65 | ImGui::InsertNotification(t); 66 | } 67 | 68 | inline void RenderToast() { 69 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.f); 70 | ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(43.f / 255.f, 43.f / 255.f, 43.f / 255.f, 100.f / 255.f)); 71 | ImGui::RenderNotifications(); 72 | ImGui::PopStyleVar(1); // Don't forget to Pop() 73 | ImGui::PopStyleColor(1); 74 | } 75 | 76 | } 77 | 78 | namespace ImGui { 79 | 80 | bool Splitter(bool split_vertically, float thickness, float *size1, float *size2, float min_size1, float min_size2, 81 | float splitter_long_axis_size = -1.0f); 82 | 83 | bool BeginGroupPanel(const char *label, bool node = false, const ImVec2 &size = ImVec2(-1.0f, 0.0f)); 84 | void EndGroupPanel(); 85 | 86 | bool PasswordInputText(const char *label, std::string *s, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, 87 | void *user_data = nullptr); 88 | 89 | void TextUrl(const char *url); 90 | 91 | bool HotkeyWidget(const char *label, misc::Hotkey &hotkey, const ImVec2 &size = ImVec2(0, 0)); 92 | 93 | void SetTheme(std::string_view theme); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Downloader/update_version.py: -------------------------------------------------------------------------------- 1 | import http.client 2 | import json 3 | import os 4 | import time 5 | 6 | version_file = "./src/dlver.h" 7 | 8 | class Version: 9 | def __init__(self): 10 | self.version = -1 11 | self.version_str = "unknown" 12 | 13 | 14 | def update_version_file(ver: Version): 15 | with open(version_file, "w") as f: 16 | f.write("#pragma once\n") 17 | f.write("\n") 18 | f.write("#define LATEST_UPDATE_TIMESTAMP " + str(int(time.time())) + "\n") 19 | f.write("#define DOWNLOADER_VERSION " + str(ver.version) + "\n") 20 | f.write('#define DOWNLOADER_VERSION_STR "' + ver.version_str + '"\n') 21 | 22 | 23 | def get_last_update_timestamp() -> int: 24 | with open(version_file, "r") as f: 25 | lines = f.readlines() 26 | for line in lines: 27 | if line.startswith("#define LATEST_UPDATE_TIMESTAMP"): 28 | return int(line.split(" ")[2]) 29 | return 0 30 | 31 | 32 | def create_default_version_file_if_not_exists(): 33 | if os.path.exists(version_file): 34 | return 35 | 36 | with open(version_file, "w") as f: 37 | f.write("#pragma once\n") 38 | f.write("\n") 39 | f.write("#define LATEST_UPDATE_TIMESTAMP 0\n") 40 | f.write("#define DOWNLOADER_VERSION -1\n") 41 | f.write('#define DOWNLOADER_VERSION_STR "unknown"\n') 42 | 43 | 44 | def get_version() -> Version: 45 | ver = Version() 46 | try: 47 | conn = http.client.HTTPSConnection("api.github.com") 48 | headers = { 49 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", 50 | "Accept": "application/vnd.github+json", 51 | "X-GitHub-Api-Version": "2022-11-28", 52 | } 53 | conn.request( 54 | "GET", "/repos/KyuubiRan/BeatmapDownloader/releases/latest", headers=headers 55 | ) 56 | response = conn.getresponse() 57 | code = response.getcode() 58 | data = response.read() 59 | de = data.decode("utf-8") 60 | j = json.loads(de) 61 | 62 | if code != 200: 63 | print("get version failed, code: ", code) 64 | print("msg: ", j["message"]) 65 | return ver 66 | 67 | version = j["tag_name"] 68 | ver.version_str = j["name"] 69 | ver.version = int(version) 70 | return ver 71 | except Exception as e: 72 | print(e) 73 | return ver 74 | 75 | 76 | def main(): 77 | create_default_version_file_if_not_exists() 78 | last_update_timestamp = get_last_update_timestamp() 79 | # 5 min 80 | if int(time.time()) - last_update_timestamp < 5 * 60: 81 | print("update too frequently, skip") 82 | return 83 | 84 | version = get_version() 85 | if version.version == -1: 86 | print("get version failed") 87 | return 88 | 89 | print(f"current version: {version.version_str}({version.version})") 90 | version.version += 1 91 | version.version_str = str(float(version.version_str) + 0.1) 92 | 93 | update_version_file(version) 94 | 95 | 96 | if __name__ == "__main__": 97 | main() 98 | -------------------------------------------------------------------------------- /Injector/Injector.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 | 16.0 23 | Win32Proj 24 | {cea5171c-606f-49b2-8fad-a8feafe36e63} 25 | Injector 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\ 75 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\obj\$(ProjectName)\ 76 | 77 | 78 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\ 79 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\obj\$(ProjectName)\ 80 | 81 | 82 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\ 83 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\obj\$(ProjectName)\ 84 | 85 | 86 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\ 87 | $(SolutionDir)bin\$(Configuration)-$(PlatformShortName)\obj\$(ProjectName)\ 88 | 89 | 90 | 91 | Level3 92 | true 93 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 94 | true 95 | stdcpp20 96 | stdc17 97 | 98 | 99 | Console 100 | true 101 | RequireAdministrator 102 | 103 | 104 | 105 | 106 | Level3 107 | true 108 | true 109 | true 110 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 111 | true 112 | stdcpp20 113 | stdc17 114 | 115 | 116 | Console 117 | true 118 | true 119 | true 120 | RequireAdministrator 121 | 122 | 123 | 124 | 125 | Level3 126 | true 127 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 128 | true 129 | stdcpp20 130 | stdc17 131 | 132 | 133 | Console 134 | true 135 | RequireAdministrator 136 | 137 | 138 | 139 | 140 | Level3 141 | true 142 | true 143 | true 144 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 145 | true 146 | stdcpp20 147 | stdc17 148 | 149 | 150 | Console 151 | true 152 | true 153 | true 154 | RequireAdministrator 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /Injector/Injector.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;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 | 18 | 19 | 源文件 20 | 21 | 22 | -------------------------------------------------------------------------------- /Injector/Injector.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /Injector/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | #define LOGD(msg, ...) printf_s("[DEBUG] " msg "\n", ##__VA_ARGS__) 13 | #define LOGI(msg, ...) printf_s("[INFO] " msg "\n", ##__VA_ARGS__) 14 | #define LOGW(msg, ...) printf_s("[WARNING] " msg "\n", ##__VA_ARGS__) 15 | #define LOGE(msg, ...) printf_s("[ERROR] " msg "\n", ##__VA_ARGS__) 16 | #define OSU_NAME L"osu!.exe" 17 | 18 | bool InjectDll(const HANDLE hProc, const char *path) { 19 | const HMODULE hKernel32 = GetModuleHandleA("Kernel32.dll"); 20 | if (hKernel32 == nullptr) { 21 | LOGE("Failed to get kernel32.dll handle, error code: %d", GetLastError()); 22 | return false; 23 | } 24 | const FARPROC pLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA"); 25 | if (pLoadLibrary == nullptr) { 26 | LOGE("Failed to get LoadLibraryA address, error code: %d", GetLastError()); 27 | return false; 28 | } 29 | const LPVOID lpBaseAddress = VirtualAllocEx(hProc, nullptr, strlen(path) + 1, MEM_COMMIT, PAGE_READWRITE); 30 | if (lpBaseAddress == nullptr) { 31 | LOGE("Failed to allocate memory in target process, error code: %d", GetLastError()); 32 | return false; 33 | } 34 | if (!WriteProcessMemory(hProc, lpBaseAddress, path, strlen(path) + 1, nullptr)) { 35 | LOGE("Failed to write dll name to target process, error code: %d", GetLastError()); 36 | return false; 37 | } 38 | const HANDLE hThread = CreateRemoteThread(hProc, nullptr, 0, (LPTHREAD_START_ROUTINE)pLoadLibrary, lpBaseAddress, 0, nullptr); 39 | if (hThread == nullptr) { 40 | LOGE("Failed to create remote thread, error code: %d", GetLastError()); 41 | return false; 42 | } 43 | WaitForSingleObject(hThread, INFINITE); 44 | CloseHandle(hThread); 45 | VirtualFreeEx(hProc, lpBaseAddress, 0, MEM_RELEASE); 46 | return true; 47 | } 48 | 49 | DWORD GetPidByName(LPCWSTR name) { 50 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 51 | 52 | if (hSnapshot == INVALID_HANDLE_VALUE) 53 | return 0; 54 | 55 | PROCESSENTRY32W pe{}; 56 | pe.dwSize = sizeof pe; 57 | 58 | if (Process32FirstW(hSnapshot, &pe)) { 59 | if (lstrcmpW(pe.szExeFile, name) == 0) { 60 | CloseHandle(hSnapshot); 61 | return pe.th32ProcessID; 62 | } 63 | } else { 64 | CloseHandle(hSnapshot); 65 | return 0; 66 | } 67 | 68 | while (Process32NextW(hSnapshot, &pe)) { 69 | if (lstrcmpW(pe.szExeFile, name) == 0) { 70 | CloseHandle(hSnapshot); 71 | return pe.th32ProcessID; 72 | } 73 | } 74 | 75 | CloseHandle(hSnapshot); 76 | return 0; 77 | } 78 | 79 | std::string ws2s(const std::wstring &s) { 80 | const int strlen = (int)s.length() + 1; 81 | const int len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), strlen, nullptr, 0, nullptr, nullptr); 82 | char *buf = new char[len]; 83 | WideCharToMultiByte(CP_ACP, 0, s.c_str(), strlen, buf, len, nullptr, nullptr); 84 | std::string r(buf); 85 | delete[] buf; 86 | return r; 87 | } 88 | 89 | std::filesystem::path GetOsuPath() { 90 | LPCTSTR rootKey = L"osu!\\shell\\open\\command"; 91 | LPCTSTR localKey = L"SOFTWARE\\Classes\\osu!\\shell\\open\\command"; 92 | 93 | HKEY hKey = nullptr; 94 | if ((ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, rootKey, 0, KEY_READ, &hKey)) || 95 | (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, localKey, 0, KEY_READ, &hKey))) { 96 | WCHAR value[MAX_PATH]{}; 97 | DWORD dwSize = sizeof(value); 98 | DWORD dwType = REG_SZ; 99 | 100 | if (ERROR_SUCCESS == RegQueryValueEx(hKey, nullptr, nullptr, &dwType, (LPBYTE)&value, &dwSize)) { 101 | std::wstring wstr(value); 102 | if (!wstr.empty()) { 103 | wstr = wstr.substr(1, wstr.length() - 7); 104 | std::filesystem::path path(wstr); 105 | RegCloseKey(hKey); 106 | return path; 107 | } 108 | } 109 | RegCloseKey(hKey); 110 | } 111 | return ""; 112 | } 113 | 114 | 115 | int main(int argc, char *argv[]) { 116 | bool noAutoStart = false; 117 | 118 | if (argc > 1) { 119 | if (strcmp(argv[1], "--no-auto-start") == 0) { 120 | noAutoStart = true; 121 | } 122 | } 123 | 124 | const auto dllPath = std::filesystem::current_path() / "Downloader.dll"; 125 | 126 | if (DWORD pid = 0; noAutoStart || (pid = GetPidByName(OSU_NAME)) != 0) { 127 | if (pid == 0) { 128 | LOGI("Waiting for osu! to start..."); 129 | while ((pid = GetPidByName(OSU_NAME)) == 0) { 130 | Sleep(1000); 131 | } 132 | } 133 | LOGI("osu! found, pid: %d", pid); 134 | HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); 135 | if (hProc == nullptr) { 136 | LOGE("Failed to open process, error code: %d", GetLastError()); 137 | } 138 | 139 | LOGI("Injecting dll..."); 140 | const auto sPath = dllPath.string(); 141 | LOGI("DLL path: %s", sPath.c_str()); 142 | 143 | if (InjectDll(hProc, sPath.c_str())) { 144 | LOGI("Dll injected successfully"); 145 | } 146 | } else { 147 | auto path = std::filesystem::current_path() / "osuPath.txt"; 148 | std::string line; 149 | 150 | if (exists(path)) { 151 | std::ifstream ifs(path); 152 | if (!ifs) { 153 | LOGE("Cannot open osuPath.txt!"); 154 | Sleep(3000); 155 | exit(1); 156 | } 157 | std::getline(ifs, line); 158 | } 159 | 160 | if (line.empty() || !filesystem::exists(line)) { 161 | LOGI("Attempt to find the path of osu!.exe."); 162 | auto osuPath = GetOsuPath(); 163 | if (exists(osuPath)) { 164 | line = osuPath.string(); 165 | } 166 | } 167 | 168 | if (line.empty() || !filesystem::exists(line)) { 169 | // Show select osu!.exe dialog 170 | OPENFILENAME ofn; 171 | TCHAR szFile[MAX_PATH] = {0}; 172 | ZeroMemory(&ofn, sizeof ofn); 173 | ofn.lStructSize = sizeof ofn; 174 | ofn.hwndOwner = nullptr; 175 | ofn.lpstrFilter = _T("osu!.exe\0osu!.exe\0"); 176 | ofn.lpstrFile = szFile; 177 | ofn.nMaxFile = sizeof(szFile); 178 | ofn.lpstrTitle = _T("Select osu!.exe"); 179 | ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST; 180 | 181 | if (GetOpenFileNameW(&ofn)) { 182 | line = ws2s(ofn.lpstrFile); 183 | std::ofstream ofs(path, std::ios::out | std::ios::trunc); 184 | ofs << line; 185 | LOGD("Line: %s", line.c_str()); 186 | } else { 187 | LOGE("Failed to select osu!.exe!"); 188 | Sleep(3000); 189 | exit(1); 190 | } 191 | } 192 | 193 | if (line.empty() || !filesystem::exists(line)) { 194 | LOGE("Cannot find osu!.exe!"); 195 | Sleep(3000); 196 | exit(1); 197 | } 198 | 199 | std::filesystem::path osuPath = line; 200 | LOGI("osu! path: %s", osuPath.string().c_str()); 201 | 202 | // Start osu! as user 203 | // OpenTabletDriver wont work using 204 | STARTUPINFOW si{}; 205 | PROCESS_INFORMATION pi{}; 206 | si.cb = sizeof si; 207 | 208 | #ifndef _DEBUG 209 | if (!CreateProcessW(nullptr, (L"explorer.exe " + osuPath.wstring()).data(), nullptr, nullptr, FALSE, 0, nullptr, osuPath.parent_path().c_str(), &si, &pi)) { 210 | LOGE("Failed to start osu!, error code: %d", GetLastError()); 211 | Sleep(3000); 212 | exit(1); 213 | } 214 | 215 | LOGI("Waiting for explorer..."); 216 | // Waitting for explorer 217 | WaitForSingleObject(pi.hProcess, -1); // explorer dies after process started. 218 | 219 | while ((pid = GetPidByName(OSU_NAME)) == 0) { 220 | Sleep(1000); 221 | } 222 | HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); 223 | if (hProc == nullptr) { 224 | LOGE("Failed to open process, error code: %d", GetLastError()); 225 | } 226 | if (InjectDll(hProc, dllPath.string().c_str())) { 227 | LOGI("Dll injected successfully"); 228 | } 229 | CloseHandle(hProc); 230 | #else 231 | if (!CreateProcessW(nullptr, osuPath.wstring().data(), 232 | nullptr, nullptr, FALSE, 233 | CREATE_SUSPENDED, nullptr, osuPath.parent_path().c_str(), &si, &pi)) { 234 | LOGE("Failed to start osu!, error code: %d", GetLastError()); 235 | Sleep(3000); 236 | exit(1); 237 | } 238 | if (InjectDll(pi.hProcess, dllPath.string().c_str())) { 239 | LOGI("Dll injected successfully"); 240 | } 241 | ResumeThread(pi.hThread); 242 | #endif 243 | 244 | CloseHandle(pi.hProcess); 245 | CloseHandle(pi.hThread); 246 | } 247 | 248 | Sleep(3000); 249 | } 250 | -------------------------------------------------------------------------------- /OsuBeatmapDownloader.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33627.172 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Downloader", "Downloader\Downloader.vcxproj", "{CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Injector", "Injector\Injector.vcxproj", "{CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Debug|x64.ActiveCfg = Debug|x64 19 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Debug|x64.Build.0 = Debug|x64 20 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Debug|x86.ActiveCfg = Debug|Win32 21 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Debug|x86.Build.0 = Debug|Win32 22 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Release|x64.ActiveCfg = Release|x64 23 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Release|x64.Build.0 = Release|x64 24 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Release|x86.ActiveCfg = Release|Win32 25 | {CFD3C85C-24D9-4088-B20C-0818AE2DE1D1}.Release|x86.Build.0 = Release|Win32 26 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Debug|x64.ActiveCfg = Debug|x64 27 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Debug|x64.Build.0 = Debug|x64 28 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Debug|x86.ActiveCfg = Debug|Win32 29 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Debug|x86.Build.0 = Debug|Win32 30 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Release|x64.ActiveCfg = Release|x64 31 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Release|x64.Build.0 = Release|x64 32 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Release|x86.ActiveCfg = Release|Win32 33 | {CEA5171C-606F-49B2-8FAD-A8FEAFE36E63}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {77B386D7-C5E4-463E-97F6-C7FB4C50E9D9} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeatmapDownloader 2 | 3 | An in-game beatmap downloader for osu!stable 4 | [中文介绍点这里](README_zh_cn.md) 5 | 6 | ## Usage 7 | 8 | 1. Download the latest release from [here](https://github.com/KyuubiRan/BeatmapDownloader/releases) 9 | 2. Extract the zip file 10 | 3. Run `Injector.exe` 11 | 4. Select `osu!.exe` 12 | 5. Enjoy! 13 | 14 | ## TODO / Features 15 | 16 | - [x] Handle link 17 | - [x] Sayobot Search & Download 18 | - [x] Bancho Search & Download 19 | - [x] Chimu(Bloodcat) Search & Download by [EnergoStalin](https://github.com/EnergoStalin) 20 | - [x] Sid/Bid quick search 21 | - [x] Multi beatmap download 22 | - [x] BP download 23 | - [x] Favorite beatmaps download 24 | - [x] Mapper beatmaps download 25 | - [x] Most played beatmaps download 26 | - [x] Beatmap pack download 27 | - [ ] (Maybe) Compatibility mode support 28 | 29 | ## UI 30 | 31 | ![image1](docs/img/en_us/1.png) 32 | ![image2](docs/img/en_us/2.png) 33 | ![image3](docs/img/en_us/3.png) 34 | 35 | ## Contributors 36 | 37 | 38 | Contributors 39 | 40 | 41 | 42 | ## Thanks 43 | 44 | Osu-Ingame-Downloader: https://github.com/veritas501/Osu-Ingame-Downloader 45 | 46 | Sakura Theme by [Small-Ku](https://github.com/Small-Ku) 47 | -------------------------------------------------------------------------------- /README_zh_cn.md: -------------------------------------------------------------------------------- 1 | # BeatmapDownloader 2 | 3 | 一个游戏内谱面下载器 4 | [介绍视频](https://www.bilibili.com/video/BV1kP411i7An) by JunMoyan 5 | 6 | ## 如何使用 7 | 8 | 1. 从[此处](https://github.com/KyuubiRan/BeatmapDownloader/releases)下载最新版本 9 | 2. 解压zip文件 10 | 3. 运行 `Injector.exe` 11 | 4. 选择 `osu!.exe` 12 | 5. 完工! 13 | 14 | ## 待做事项 / 功能 15 | 16 | - [x] 处理游戏内链接(也支持拖动官网连接到游戏内解析) 17 | - [x] Sayobot 搜索/下载 18 | - [x] Bancho 搜索/下载 19 | - [x] Chimu(Bloodcat) 搜索/下载 by [EnergoStalin](https://github.com/EnergoStalin) 20 | - [x] Sid/Bid 搜索 21 | - [x] 批量下图 22 | - [x] 一键扒BP 23 | - [x] 收藏谱面下载 24 | - [x] 指定谱师谱面下载 25 | - [x] 玩家最多游玩的谱面下载 26 | - [x] 曲包下载 27 | - [ ] (也许) 适配兼容模式 28 | 29 | ## 界面 30 | 31 | ![image1](docs/img/zh_cn/1.png) 32 | ![image2](docs/img/zh_cn/2.png) 33 | ![image3](docs/img/zh_cn/3.png) 34 | 35 | ## 贡献者 36 | 37 | 38 | 贡献者 39 | 40 | 41 | ## 鸣谢 42 | 43 | Osu-Ingame-Downloader: https://github.com/veritas501/Osu-Ingame-Downloader 44 | 45 | Sakura 主题 by [Small-Ku](https://github.com/Small-Ku) 46 | -------------------------------------------------------------------------------- /docs/img/en_us/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/docs/img/en_us/1.png -------------------------------------------------------------------------------- /docs/img/en_us/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/docs/img/en_us/2.png -------------------------------------------------------------------------------- /docs/img/en_us/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/docs/img/en_us/3.png -------------------------------------------------------------------------------- /docs/img/zh_cn/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/docs/img/zh_cn/1.png -------------------------------------------------------------------------------- /docs/img/zh_cn/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/docs/img/zh_cn/2.png -------------------------------------------------------------------------------- /docs/img/zh_cn/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyuubiRan/BeatmapDownloader/9892bf62b4243184edb56a404ea8c14f58c37ef6/docs/img/zh_cn/3.png --------------------------------------------------------------------------------