├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CMakeLists.txt
├── README.md
├── about.md
├── changelog.md
├── logo.png
├── mod.json
├── resources
└── JB_ListLogo.png
└── src
├── filesystem.hpp
├── hooks
├── custom_song_widget.cpp
└── music_download_manager.cpp
├── main.cpp
├── managers
├── nong_manager.cpp
└── nong_manager.hpp
├── manifest.cpp
├── manifest.hpp
├── random_string.cpp
├── random_string.hpp
├── trim.cpp
├── trim.hpp
├── types
├── fetch_status.hpp
├── nong_list_type.hpp
├── nong_state.hpp
├── sfh_item.hpp
└── song_info.hpp
└── ui
├── list_cell.cpp
├── list_cell.hpp
├── nong_add_popup.cpp
├── nong_add_popup.hpp
├── nong_cell.cpp
├── nong_cell.hpp
├── nong_dropdown_layer.cpp
├── nong_dropdown_layer.hpp
├── song_cell.cpp
└── song_cell.hpp
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build Geode Mod
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - "main"
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | config:
15 | - name: Windows
16 | os: windows-latest
17 |
18 | # - name: macOS
19 | # os: macos-latest
20 | - name: Android32
21 | os: ubuntu-latest
22 | target: Android32
23 |
24 | - name: Android64
25 | os: ubuntu-latest
26 | target: Android64
27 | name: ${{matrix.config.name}}
28 | runs-on: ${{matrix.config.os}}
29 |
30 | steps:
31 | - uses: actions/checkout@v3
32 |
33 | - name: Build the mod
34 | uses: geode-sdk/build-geode-mod@main
35 | with:
36 | bindings: fleeym/sapphire-bindings
37 | bindings-ref: nongd
38 | combine: true
39 | target: ${{matrix.config.target}}
40 | package:
41 | name: Package builds
42 | runs-on: ubuntu-latest
43 | needs: ['build']
44 |
45 | steps:
46 | - uses: geode-sdk/build-geode-mod@combine
47 | id: build
48 |
49 | - uses: actions/upload-artifact@v3
50 | with:
51 | name: Build Output
52 | path: ${{steps.build.outputs.build-output}}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
34 | # Macos be like
35 | **/.DS_Store
36 |
37 | # Cache files for Sublime Text
38 | *.tmlanguage.cache
39 | *.tmPreferences.cache
40 | *.stTheme.cache
41 |
42 | # Workspace files are user-specific
43 | *.sublime-workspace
44 |
45 | # I need to find a way to automatically remove this
46 | Source/Geode/pkg/uber-apk-signer.jar
47 |
48 | # Ignore build folders
49 | **/build
50 |
51 | # ILY vscode
52 | **/.vscode
53 |
54 | build-android.bat
55 |
56 | # clangd cache
57 | .cache/clangd
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.5.0)
2 | set(CMAKE_CXX_STANDARD 20)
3 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
4 |
5 | project(jukebox VERSION 2.0.0)
6 |
7 | file(GLOB SOURCES
8 | src/ui/*.cpp
9 | src/managers/*.cpp
10 | src/hooks/*.cpp
11 | src/*.cpp
12 | )
13 |
14 | add_library(${PROJECT_NAME} SHARED ${SOURCES})
15 |
16 | if (NOT DEFINED ENV{GEODE_SDK})
17 | message(FATAL_ERROR "Unable to find Geode SDK! Please define GEODE_SDK environment variable to point to Geode")
18 | else()
19 | message(STATUS "Found Geode: $ENV{GEODE_SDK}")
20 | endif()
21 |
22 | add_subdirectory($ENV{GEODE_SDK} $ENV{GEODE_SDK}/build)
23 |
24 | target_link_libraries(${PROJECT_NAME} geode-sdk)
25 | create_geode_file(${PROJECT_NAME})
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jukebox
2 |
3 |
4 |
5 | The Jukebox Mod is a song manager for Geometry Dash. The primary goal is simplifying the process of swapping Newgrounds songs with any NONGs.
6 |
7 | ## What is a NONG, anyway?
8 |
9 | NONG stands for **Not On NewGrounds**. Basically, it means any song that is not on Newgrounds that was replaced manually through the game files.
10 |
11 | NONGs have always been a hassle to manage, because some level creators use popular Newgrounds song IDs and replace them with a NONG. So you have to swap those song files around quite a bit if you play a level with the Newgrounds song and a level with a NONG song.
12 |
13 | ## Start your jukebox!
14 |
15 | The Jukebox Mod makes the process of managing your songs a breeze. You have 2 choices for adding a song to the game.
16 |
17 | 1. Fetch from Song File Hub
18 | 2. Download MP3 manually and add it ingame
19 |
20 | You can download your NONGs using your method of choice. A recommandation of mine is [yt-dlp](https://github.com/yt-dlp/yt-dlp), a CLI application. After getting your MP3 file, you can enter a song and author name, for easier management.
21 |
22 | > Note that Jukebox copies imported MP3 files in the storage location designated by Geode. You can open this folder from ingame.
23 |
24 | Alternatively, you can download song data from **Song File Hub**, which is all tightly integrated inside the mod! Huge thanks to their team for helping out with the integration.
25 |
26 | ## So, how do I begin?
27 |
28 | You can open up the Jukebox menu form any Level page. Just click on the song name, and either a song list (if the level has multiple songs), or the song management screen (if the level only uses 1 song) will open. From here, you can add, remove, swap and fetch songs.
29 |
30 | ## Credits
31 |
32 | - The Geode team, for creating such an amazing toolkit
33 | - Song File Hub, for creating the best song archive in the community (and also letting me interact with their API)
--------------------------------------------------------------------------------
/about.md:
--------------------------------------------------------------------------------
1 | # Jukebox
2 |
3 | The Jukebox Mod is a song manager for Geometry Dash. The primary goal is simplifying the process of swapping Newgrounds songs with any NONGs.
4 |
5 | ## What is a NONG, anyway?
6 |
7 | NONG stands for **Not On NewGrounds**. Basically, it means any song that is not on Newgrounds that was replaced manually through the game files.
8 |
9 | NONGs have always been a hassle to manage, because some level creators use popular Newgrounds song IDs and replace them with a NONG. So you have to swap those song files around quite a bit if you play a level with the Newgrounds song and a level with a NONG song.
10 |
11 | ## Start your jukebox!
12 |
13 | The Jukebox Mod makes the process of managing your songs a breeze. You have 2 choices for adding a song to the game.
14 |
15 | 1. Fetch from Song File Hub
16 | 2. Download MP3 manually and add it ingame
17 |
18 | You can download your NONGs using your method of choice. A recommandation of mine is [yt-dlp](https://github.com/yt-dlp/yt-dlp), a CLI application. After getting your MP3 file, you can enter a song and author name, for easier management.
19 |
20 | > Note that Jukebox copies imported MP3 files in the storage location designated by Geode. You can open this folder from ingame.
21 |
22 | Alternatively, you can download song data from **Song File Hub**, which is all tightly integrated inside the mod! Huge thanks to their team for helping out with the integration.
23 |
24 | ## So, how do I begin?
25 |
26 | You can open up the Jukebox menu form any Level page. Just click on the song name, and either a song list (if the level has multiple songs), or the song management screen (if the level only uses 1 song) will open. From here, you can add, remove, swap and fetch songs.
27 |
28 | ## Credits
29 |
30 | - The Geode team, for creating such an amazing toolkit
31 | - Song File Hub, for creating the best song archive in the community (and also letting me interact with their API)
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v2.1.5
4 |
5 | * Reenable manual song add on Android
6 |
7 | ## v2.1.4
8 |
9 | * Temporarily disable manual song add on Android
10 | * Fix touch priority issues in the song list
11 |
12 | ## v2.1.3
13 |
14 | * Fix buttons not working in the song list
15 |
16 | ## v2.1.2
17 |
18 | * Fix crashes on Android
19 | * Fix manual song add on Android
20 |
21 | ## v2.1.1
22 |
23 | * Fix the Robtop Music Library being... a little weird
24 | * Fix the random Error text that would appear in the song widget sometimes
25 | * 0.0B fix now accounts for songs that are included with the game (GD/Resources/songs folder)
26 | * Fix some editor song select issues
27 | * Android support (experimental)
28 |
29 | ## v2.1.0
30 |
31 | * Correctly disable nongs for levels that have robtop levels
32 | * Fix a crash that happened when entering a level with an invalid song id
33 | * Store level name separate from song name (and display it in the song list)
34 | * Store song data as minified json
35 | * Fix 0.0B on multi asset levels (experimental)
36 |
37 | ## v2.0.1
38 |
39 | * Fix a crash that happens when entering a level with song info data not fetched
40 |
41 | ## v2.0.0
42 |
43 | * Add 2.2 support
44 | * Add support for levels with multiple songs (experimental)
45 | * Optimizations and bug fixes
46 | * Rebranding!
47 |
48 | This release is only available on Windows, next one should be available on Android too. (Sorry android fellas)
49 |
50 | ## v1.2.3
51 |
52 | * Fix crash when adding a NONG manually for the first time
53 |
54 | ## v1.2.2
55 |
56 | * Fix crashes on Android
57 |
58 | ## v1.2.1
59 |
60 | * Increased the Z Layer for the NONG popup
61 |
62 | ## v1.2.0
63 |
64 | * Replace old popup with a layer that fits the game more
65 | * Recompile for Android NDK r26b
66 |
67 | ## v1.1.1
68 |
69 | * Add experimental Android support
70 |
71 | ## v1.1.0
72 |
73 | * Changed the song size label to show N/A instead of 0.00MB for songs that are missing their file
74 | * Added a setting that prevents mashup downloading from Song File Hub
75 | * Added a "Remove All" button to the NONG list
76 | * Fixed aspect ratio issues in the popups
77 | * Copy locally added nongs to the mod storage instead of using the file provided by the user
78 | * Created a manifest system to track JSON structure updates
79 | * Added a button that opens the settings page in the nong popup
80 |
81 | ## v1.0.6
82 |
83 | * Fixed a bug that prevented saving songs to disk if the Song File Hub name contained Unicode characters
84 |
85 | ## v1.0.4
86 |
87 | * Switched to using the new API for Song File Hub
88 | * Gave the add song popup some elasticity
89 |
90 | ## v1.0.3
91 |
92 | * Removed all filters for the song file picker so that MacOS can actually use it.
93 |
94 | ## v1.0.2
95 |
96 | * Fixed a crash that occured by pressing ESC while downloading a NONG
97 | * Disable NONGd for levels that use Robtop level songs
98 | * Update json impl to match the new json library version API
99 | * Add a small indicator to the song label to hint that you can click it
100 | * Fixed text inputs for Geode v1.0.0-beta.14
101 |
102 | ## v1.0.1
103 |
104 | * Mod can now build on MacOS
105 |
106 | ## v1.0.0
107 |
108 | * Implement async file downloads
109 | * Fix some crashes
110 | * Only download one song at a time instead of downloading all of them
111 | * Fix size label showing 0.00mb for undownloaded newgrounds songs
112 | * Reduce calls to updateSongObject
113 |
114 | ## v1.0.0-beta.3
115 |
116 | * Fix the invalid SFH download popup being positioned weirdly
117 | * Update the Custom Song Widget on every nong update
118 | * Use layouts for list cells
119 | * Try to fix unicode not being parsed correctly
120 | * Fix nongd folder not being created properly on some occasions
121 |
122 | ## v1.0.0-beta.1
123 |
124 | * Initial version
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fleeym/nongd/7824b010d52387af7477d001769287e07323e95e/logo.png
--------------------------------------------------------------------------------
/mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "geode": "2.0.0",
3 | "version": "2.1.5",
4 | "id": "fleym.nongd",
5 | "name": "Jukebox",
6 | "gd": {
7 | "win": "2.204",
8 | "android": "2.205"
9 | },
10 | "developer": "Fleym",
11 | "description": "A simple song manager for Geometry Dash",
12 | "repository": "https://github.com/Fleeym/nongd",
13 | "issues": {
14 | "info": "For any issues regarding this mod, send me a message on my discord: 'fleeym'. If you can, please give the level or song ID you are having problems with."
15 | },
16 | "tags": [
17 | "Music"
18 | ],
19 | "settings": {
20 | "store-mashups": {
21 | "name": "Store Mashups",
22 | "type": "bool",
23 | "description": "Allows mashups to be downloaded from Song File Hub",
24 | "default": true
25 | },
26 | "fix-empty-size": {
27 | "name": "Fix 0.0B",
28 | "type": "bool",
29 | "description": "Fixes multi asset levels showing 0.0B size (experimental, might cause lag when downloading assets)",
30 | "default": false
31 | }
32 | },
33 | "resources": {
34 | "sprites": [
35 | "resources/*.png"
36 | ]
37 | }
38 | }
--------------------------------------------------------------------------------
/resources/JB_ListLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Fleeym/nongd/7824b010d52387af7477d001769287e07323e95e/resources/JB_ListLogo.png
--------------------------------------------------------------------------------
/src/filesystem.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #ifndef GEODE_IS_MACOS
6 | #include
7 | namespace fs = std::filesystem;
8 | #else
9 | namespace fs = ghc::filesystem;
10 | #endif
--------------------------------------------------------------------------------
/src/hooks/custom_song_widget.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "../types/song_info.hpp"
7 | #include "../managers/nong_manager.hpp"
8 | #include "../ui/nong_dropdown_layer.hpp"
9 |
10 | using namespace geode::prelude;
11 |
12 | class $modify(JBSongWidget, CustomSongWidget) {
13 | NongData nongs;
14 | CCMenu* menu;
15 | CCMenuItemSpriteExtra* songNameLabel;
16 | CCLabelBMFont* sizeIdLabel;
17 | std::string songIds = "";
18 | std::string sfxIds = "";
19 | bool fetchedAssetInfo = false;
20 | bool firstRun = true;
21 | bool searching = false;
22 | std::unordered_map assetNongData;
23 |
24 | bool init(
25 | SongInfoObject* songInfo,
26 | CustomSongDelegate* songDelegate,
27 | bool showSongSelect,
28 | bool showPlayMusic,
29 | bool showDownload,
30 | bool isRobtopSong,
31 | bool unk,
32 | bool isMusicLibrary
33 | ) {
34 | if (!CustomSongWidget::init(songInfo, songDelegate, showSongSelect, showPlayMusic, showDownload, isRobtopSong, unk, isMusicLibrary)) {
35 | return false;
36 | }
37 | if (isRobtopSong) {
38 | return true;
39 | }
40 | // log::info("songselect {}, playmusic {}, download {}, robtop {}, unk {}, musiclib {}", m_showSelectSongBtn, m_showPlayMusicBtn, m_showDownloadBtn, m_isRobtopSong, m_unkBool1, m_isMusicLibrary);
41 |
42 |
43 | m_songLabel->setVisible(false);
44 | return true;
45 | }
46 |
47 | void updateWithMultiAssets(gd::string p1, gd::string p2, int p3) {
48 | CustomSongWidget::updateWithMultiAssets(p1, p2, p3);
49 | m_fields->songIds = std::string(p1);
50 | m_fields->sfxIds = std::string(p2);
51 | if (m_fields->fetchedAssetInfo) {
52 | this->fixMultiAssetSize();
53 | }
54 | if (m_isRobtopSong) {
55 | return;
56 | }
57 | this->createSongLabels();
58 | }
59 |
60 | void updateMultiAssetInfo(bool p) {
61 | CustomSongWidget::updateMultiAssetInfo(p);
62 | if (m_fields->fetchedAssetInfo) {
63 | this->fixMultiAssetSize();
64 | }
65 | }
66 |
67 | void fixMultiAssetSize() {
68 | auto flag = Mod::get()->getSettingValue("fix-empty-size");
69 | if ((m_fields->songIds.empty() && m_fields->sfxIds.empty()) || !flag) {
70 | return;
71 | }
72 | NongManager::get()->getMultiAssetSizes(m_fields->songIds, m_fields->sfxIds, [this](std::string result) {
73 | std::stringstream ss;
74 | ss << "Songs: " << m_songs.size() << " SFX: " << m_sfx.size() << " Size: " << result;
75 | m_songIDLabel->setString(ss.str().c_str());
76 | });
77 | }
78 |
79 | void restoreUI() {
80 | m_songLabel->setVisible(true);
81 | if (m_fields->menu != nullptr) {
82 | m_fields->songNameLabel->removeFromParent();
83 | m_fields->songNameLabel = nullptr;
84 | m_fields->menu->removeFromParent();
85 | m_fields->menu = nullptr;
86 | }
87 | if (m_fields->sizeIdLabel != nullptr) {
88 | m_songIDLabel->setVisible(true);
89 | m_fields->sizeIdLabel->removeFromParent();
90 | m_fields->sizeIdLabel = nullptr;
91 | }
92 | }
93 |
94 | void updateSongObject(SongInfoObject* obj) {
95 | // log::info("{}, {}, {}, {}, {}", obj->m_songName, obj->m_artistName, obj->m_songUrl, obj->m_isUnkownSong, obj->m_songID);
96 | // log::info("songselect {}, playmusic {}, download {}, robtop {}, unk {}, musiclib {}", m_showSelectSongBtn, m_showPlayMusicBtn, m_showDownloadBtn, m_isRobtopSong, m_unkBool1, m_isMusicLibrary);
97 | CustomSongWidget::updateSongObject(obj);
98 | if (obj->m_songID == 0) {
99 | this->restoreUI();
100 | return;
101 | }
102 | if (m_showSelectSongBtn && obj->m_artistName.empty() && obj->m_songUrl.empty()) {
103 | // log::info("returning");
104 | this->restoreUI();
105 | return;
106 | }
107 | if (m_isRobtopSong) {
108 | return;
109 | }
110 | // log::info("not returning?");
111 | m_songLabel->setVisible(false);
112 | if (obj->m_artistName.empty() && obj->m_songUrl.empty()) {
113 | // we have an invalid songID
114 | auto res = NongManager::get()->getActiveNong(obj->m_songID);
115 | if (res.has_value()) {
116 | auto value = res.value();
117 | obj->m_artistName = value.authorName;
118 | } else {
119 | NongManager::get()->createUnknownDefault(obj->m_songID);
120 | obj->m_artistName = "Unknown";
121 | }
122 | }
123 | auto result = NongManager::get()->getNongs(obj->m_songID);
124 | if (!result.has_value()) {
125 | NongManager::get()->createDefault(obj->m_songID);
126 | result = NongManager::get()->getNongs(obj->m_songID);
127 | if (!result.has_value()) {
128 | return;
129 | }
130 | }
131 | m_fields->nongs = result.value();
132 | this->createSongLabels();
133 | auto active = NongManager::get()->getActiveNong(obj->m_songID).value();
134 | auto data = NongManager::get()->getNongs(obj->m_songID).value();
135 | if (active.path != data.defaultPath) {
136 | m_deleteBtn->setVisible(false);
137 | }
138 | }
139 |
140 | void updateSongInfo() {
141 | CustomSongWidget::updateSongInfo();
142 | if (m_isRobtopSong || m_songInfoObject == nullptr || m_songInfoObject->m_songID == 0) {
143 | return;
144 | }
145 | if (!m_fields->fetchedAssetInfo && m_songs.size() != 0) {
146 | this->getMultiAssetSongInfo();
147 | }
148 | }
149 |
150 | void getMultiAssetSongInfo() {
151 | bool allDownloaded = true;
152 | for (auto const& kv : m_songs) {
153 | auto result = NongManager::get()->getNongs(kv.first);
154 | if (!result.has_value()) {
155 | NongManager::get()->createDefault(kv.first);
156 | result = NongManager::get()->getNongs(kv.first);
157 | if (!result.has_value()) {
158 | // its downloading
159 | allDownloaded = false;
160 | continue;
161 | }
162 | }
163 | auto value = result.value();
164 | m_fields->assetNongData[kv.first] = value;
165 | }
166 | if (allDownloaded) {
167 | m_fields->fetchedAssetInfo = true;
168 | }
169 | }
170 |
171 | void createSongLabels() {
172 | int songID = m_songInfoObject->m_songID;
173 | auto active = NongManager::get()->getActiveNong(songID).value();
174 | if (m_fields->menu != nullptr) {
175 | m_fields->menu->removeFromParent();
176 | }
177 | auto menu = CCMenu::create();
178 | menu->setID("song-name-menu");
179 | auto label = CCLabelBMFont::create(active.songName.c_str(), "bigFont.fnt");
180 | if (!m_isMusicLibrary) {
181 | label->limitLabelWidth(220.f, 0.8f, 0.1f);
182 | } else {
183 | label->limitLabelWidth(130.f, 0.4f, 0.1f);
184 | }
185 | auto songNameMenuLabel = CCMenuItemSpriteExtra::create(
186 | label,
187 | this,
188 | menu_selector(JBSongWidget::addNongLayer)
189 | );
190 | songNameMenuLabel->setTag(songID);
191 | // // I am not even gonna try and understand why this works, but this places the label perfectly in the menu
192 | auto labelScale = label->getScale();
193 | songNameMenuLabel->setID("song-name-label");
194 | songNameMenuLabel->setPosition(ccp(0.f, 0.f));
195 | songNameMenuLabel->setAnchorPoint(ccp(0.f, 0.5f));
196 | m_fields->songNameLabel = songNameMenuLabel;
197 | menu->addChild(songNameMenuLabel);
198 | menu->setContentSize(ccp(label->getContentSize().width * labelScale, 25.f));
199 | if (!m_isMusicLibrary) {
200 | menu->setPosition(ccp(-140.f, 27.5f));
201 | } else {
202 | menu->setPosition(ccp(-150.f, 9.f));
203 | }
204 | songNameMenuLabel->setContentSize({ label->getContentSize().width * labelScale, labelScale * 30 });
205 | m_fields->menu = menu;
206 | this->addChild(menu);
207 | if (m_songs.size() == 0 && m_sfx.size() == 0 && !m_isMusicLibrary) {
208 | if (m_fields->sizeIdLabel != nullptr) {
209 | m_fields->sizeIdLabel->removeFromParent();
210 | }
211 | auto data = NongManager::get()->getNongs(songID).value();
212 |
213 | if (!fs::exists(active.path) && active.path == data.defaultPath) {
214 | m_songIDLabel->setVisible(true);
215 | return;
216 | } else if (m_songIDLabel) {
217 | m_songIDLabel->setVisible(false);
218 | }
219 |
220 | std::string sizeText;
221 | if (fs::exists(active.path)) {
222 | sizeText = NongManager::get()->getFormattedSize(active);
223 | } else {
224 | sizeText = "NA";
225 | }
226 | std::string labelText;
227 | if (active.path == data.defaultPath) {
228 | labelText = "SongID: " + std::to_string(songID) + " Size: " + sizeText;
229 | } else {
230 | labelText = "SongID: NONG Size: " + sizeText;
231 | }
232 |
233 | auto label = CCLabelBMFont::create(labelText.c_str(), "bigFont.fnt");
234 | label->setID("nongd-id-and-size-label");
235 | label->setPosition(ccp(-139.f, -31.f));
236 | label->setAnchorPoint({0, 0.5f});
237 | label->setScale(0.4f);
238 | this->addChild(label);
239 | m_fields->sizeIdLabel = label;
240 | } else {
241 | if (m_fields->sizeIdLabel) {
242 | m_fields->sizeIdLabel->setVisible(false);
243 | }
244 | m_songIDLabel->setVisible(true);
245 | }
246 | }
247 |
248 | void addNongLayer(CCObject* target) {
249 | if (m_songs.size() > 1 && !m_fields->fetchedAssetInfo) {
250 | this->getMultiAssetSongInfo();
251 | if (!m_fields->fetchedAssetInfo) {
252 | return;
253 | }
254 | }
255 | auto scene = CCDirector::sharedDirector()->getRunningScene();
256 | std::vector ids;
257 | if (m_songs.size() > 1) {
258 | for (auto const& kv : m_songs) {
259 | if (!NongManager::get()->getNongs(kv.first).has_value()) {
260 | return;
261 | }
262 | ids.push_back(kv.first);
263 | }
264 | } else {
265 | ids.push_back(m_songInfoObject->m_songID);
266 | }
267 | auto layer = NongDropdownLayer::create(ids, this, m_songInfoObject->m_songID);
268 | layer->m_noElasticity = true;
269 | // based robtroll
270 | layer->setZOrder(106);
271 | layer->show();
272 | }
273 | };
274 |
275 | class $modify(JBLevelInfoLayer, LevelInfoLayer) {
276 | bool init(GJGameLevel* level, bool p1) {
277 | if (!LevelInfoLayer::init(level, p1)) {
278 | return false;
279 | }
280 | if (Mod::get()->getSavedValue("show-tutorial", true) && GameManager::get()->m_levelEditorLayer == nullptr) {
281 | auto popup = FLAlertLayer::create("Jukebox", "Thank you for using Jukebox! To begin swapping songs, click on the song name!", "Ok");
282 | Mod::get()->setSavedValue("show-tutorial", false);
283 | popup->m_scene = this;
284 | popup->show();
285 | }
286 | return true;
287 | }
288 | };
289 |
290 | // class $modify(NongSongWidget, CustomSongWidget) {
291 | // NongData nongData;
292 | // int nongdSong;
293 |
294 | // bool hasDefaultSong = false;
295 | // bool firstRun = true;
296 |
297 | // bool init(SongInfoObject* songInfo, LevelSettingsObject* levelSettings, bool p2, bool p3, bool p4, bool hasDefaultSong, bool hideBackground) {
298 | // if (!CustomSongWidget::init(songInfo, levelSettings, p2, p3, p4, hasDefaultSong, hideBackground)) return false;
299 |
300 | // if (!songInfo) {
301 | // return true;
302 | // }
303 |
304 | // m_fields->firstRun = false;
305 |
306 | // if (hasDefaultSong) {
307 | // m_fields->hasDefaultSong = true;
308 | // this->updateSongObject(m_songInfo);
309 | // return true;
310 | // }
311 |
312 | // auto songNameLabel = typeinfo_cast(this->getChildByID("song-name-label"));
313 | // songNameLabel->setVisible(false);
314 |
315 | // auto idAndSizeLabel = typeinfo_cast(this->getChildByID("id-and-size-label"));
316 | // idAndSizeLabel->setVisible(false);
317 | // auto newLabel = CCLabelBMFont::create("new", "bigFont.fnt");
318 | // newLabel->setID("nongd-id-and-size-label");
319 | // newLabel->setPosition(ccp(0.f, -32.f));
320 | // newLabel->setScale(0.4f);
321 | // this->addChild(newLabel);
322 |
323 | // m_fields->nongdSong = songInfo->m_songID;
324 |
325 | // if (!NongManager::get()->checkIfNongsExist(songInfo->m_songID)) {
326 | // auto strPath = std::string(MusicDownloadManager::sharedState()->pathForSong(songInfo->m_songID));
327 |
328 | // SongInfo defaultSong = {
329 | // .path = ghc::filesystem::path(strPath),
330 | // .songName = songInfo->m_songName,
331 | // .authorName = songInfo->m_artistName,
332 | // .songUrl = songInfo->m_songURL,
333 | // };
334 |
335 | // NongManager::get()->createDefaultSongIfNull(defaultSong, songInfo->m_songID);
336 | // }
337 |
338 | // auto invalidSongs = NongManager::get()->validateNongs(songInfo->m_songID);
339 |
340 | // if (invalidSongs.size() > 0) {
341 | // std::string invalidSongList = "";
342 | // for (auto &song : invalidSongs) {
343 | // invalidSongList += song.songName + ", ";
344 | // }
345 |
346 | // invalidSongList = invalidSongList.substr(0, invalidSongList.size() - 2);
347 | // // If anyone asks this was mat's idea
348 | // Loader::get()->queueInMainThread([this, invalidSongList]() {
349 | // auto alert = FLAlertLayer::create("Invalid NONGs", "The NONGs [" + invalidSongList + "] have been deleted, because their paths were invalid.", "Ok");
350 | // alert->m_scene = this->getParent();
351 | // alert->show();
352 | // });
353 | // }
354 |
355 | // m_fields->nongData = NongManager::get()->getNongs(m_songInfo->m_songID);
356 | // SongInfo nong;
357 | // for (auto song : m_fields->nongData.songs) {
358 | // if (song.path == m_fields->nongData.active) {
359 | // nong = song;
360 | // }
361 | // }
362 |
363 | // m_songInfo->m_artistName = nong.authorName;
364 | // m_songInfo->m_songName = nong.songName;
365 | // this->updateSongObject(m_songInfo);
366 | // if (auto found = this->getChildByID("song-name-menu")) {
367 | // this->updateSongNameLabel(m_songInfo->m_songName, m_songInfo->m_songID);
368 | // } else {
369 | // this->addMenuItemLabel(m_songInfo->m_songName, m_songInfo->m_songID);
370 | // }
371 | // if (nong.path == m_fields->nongData.defaultPath) {
372 | // this->updateIDAndSizeLabel(nong, m_songInfo->m_songID);
373 | // } else {
374 | // this->updateIDAndSizeLabel(nong);
375 | // }
376 |
377 | // return true;
378 | // }
379 |
380 | // void addMenuItemLabel(std::string const& text, int songID) {
381 | // auto menu = CCMenu::create();
382 | // menu->setID("song-name-menu");
383 |
384 | // auto label = CCLabelBMFont::create(text.c_str(), "bigFont.fnt");
385 | // label->limitLabelWidth(220.f, 0.8f, 0.1f);
386 | // auto info = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
387 | // info->setScale(0.5f);
388 | // auto songNameMenuLabel = CCMenuItemSpriteExtra::create(
389 | // label,
390 | // this,
391 | // menu_selector(NongSongWidget::addNongLayer)
392 | // );
393 | // songNameMenuLabel->addChild(info);
394 | // songNameMenuLabel->setTag(songID);
395 | // // I am not even gonna try and understand why this works, but this places the label perfectly in the menu
396 | // auto labelScale = label->getScale();
397 | // songNameMenuLabel->setID("song-name-label");
398 | // songNameMenuLabel->setPosition(ccp(0.f, 0.f));
399 | // songNameMenuLabel->setAnchorPoint(ccp(0.f, 0.5f));
400 | // menu->addChild(songNameMenuLabel);
401 | // menu->setContentSize(ccp(220.f, 25.f));
402 | // menu->setPosition(ccp(-140.f, 27.5f));
403 | // auto layout = RowLayout::create();
404 | // layout->setAxisAlignment(AxisAlignment::Start);
405 | // layout->setAutoScale(false);
406 | // songNameMenuLabel->setLayout(layout);
407 | // songNameMenuLabel->setContentSize({ 220.f, labelScale * 30 });
408 |
409 | // this->addChild(menu);
410 | // }
411 |
412 | // void updateSongNameLabel(std::string const& text, int songID) {
413 | // auto menu = this->getChildByID("song-name-menu");
414 | // auto labelMenuItem = typeinfo_cast(menu->getChildByID("song-name-label"));
415 | // labelMenuItem->setTag(songID);
416 | // auto child = typeinfo_cast(labelMenuItem->getChildren()->objectAtIndex(0));
417 | // child->setString(text.c_str());
418 | // child->limitLabelWidth(220.f, 0.8f, 0.1f);
419 | // auto labelScale = child->getScale();
420 | // labelMenuItem->setContentSize({ 220.f, labelScale * 30 });
421 | // labelMenuItem->updateLayout();
422 | // }
423 |
424 | // void updateIDAndSizeLabel(SongInfo const& song, int songID = 0) {
425 | // auto label = typeinfo_cast(this->getChildByID("nongd-id-and-size-label"));
426 | // auto normalLabel = typeinfo_cast(this->getChildByID("id-and-size-label"));
427 | // auto defaultPath = m_fields->nongData.defaultPath;
428 |
429 | // if (!ghc::filesystem::exists(song.path) && song.path == defaultPath) {
430 | // label->setVisible(false);
431 | // this->getChildByID("id-and-size-label")->setVisible(true);
432 | // return;
433 | // } else if (normalLabel && normalLabel->isVisible()) {
434 | // normalLabel->setVisible(false);
435 | // label->setVisible(true);
436 | // }
437 |
438 | // std::string sizeText;
439 | // if (ghc::filesystem::exists(song.path)) {
440 | // sizeText = NongManager::get()->getFormattedSize(song);
441 | // } else {
442 | // sizeText = "NA";
443 | // }
444 | // std::string labelText;
445 | // if (songID != 0) {
446 | // labelText = "SongID: " + std::to_string(songID) + " Size: " + sizeText;
447 | // } else {
448 | // labelText = "SongID: NONG Size: " + sizeText;
449 | // }
450 |
451 | // if (label) {
452 | // label->setString(labelText.c_str());
453 | // }
454 | // }
455 |
456 | // void updateSongObject(SongInfoObject* song) {
457 | // if (m_fields->firstRun) {
458 | // CustomSongWidget::updateSongObject(song);
459 | // return;
460 | // }
461 |
462 | // if (m_fields->hasDefaultSong) {
463 | // CustomSongWidget::updateSongObject(song);
464 | // if (auto found = this->getChildByID("song-name-menu")) {
465 | // found->setVisible(false);
466 | // this->getChildByID("nongd-id-and-size-label")->setVisible(false);
467 | // }
468 | // this->getChildByID("id-and-size-label")->setVisible(true);
469 | // return;
470 | // }
471 |
472 | // m_fields->nongdSong = song->m_songID;
473 | // if (!NongManager::get()->checkIfNongsExist(song->m_songID)) {
474 | // auto strPath = std::string(MusicDownloadManager::sharedState()->pathForSong(song->m_songID));
475 |
476 | // SongInfo defaultSong = {
477 | // .path = ghc::filesystem::path(strPath),
478 | // .songName = song->m_songName,
479 | // .authorName = song->m_artistName,
480 | // .songUrl = song->m_songURL,
481 | // };
482 |
483 | // NongManager::get()->createDefaultSongIfNull(defaultSong, song->m_songID);
484 | // }
485 | // SongInfo active;
486 | // auto nongData = NongManager::get()->getNongs(song->m_songID);
487 | // for (auto nong : nongData.songs) {
488 | // if (nong.path == nongData.active) {
489 | // active = nong;
490 | // song->m_songName = nong.songName;
491 | // song->m_artistName = nong.authorName;
492 | // if (nong.songUrl != "local") {
493 | // song->m_songURL = nong.songUrl;
494 | // }
495 | // }
496 | // }
497 | // CustomSongWidget::updateSongObject(song);
498 | // if (auto found = this->getChildByID("song-name-menu")) {
499 | // this->updateSongNameLabel(song->m_songName, song->m_songID);
500 | // } else {
501 | // this->addMenuItemLabel(song->m_songName, song->m_songID);
502 | // }
503 | // if (active.path == nongData.defaultPath) {
504 | // this->updateIDAndSizeLabel(active, song->m_songID);
505 | // } else {
506 | // this->updateIDAndSizeLabel(active);
507 | // }
508 | // }
509 |
510 | // void addNongLayer(CCObject* target) {
511 | // auto scene = CCDirector::sharedDirector()->getRunningScene();
512 | // auto layer = NongDropdownLayer::create(m_fields->nongdSong, this);
513 | // // based robtroll
514 | // layer->setZOrder(106);
515 | // scene->addChild(layer);
516 | // layer->showLayer(false);
517 | // }
518 |
519 | // };
--------------------------------------------------------------------------------
/src/hooks/music_download_manager.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "../managers/nong_manager.hpp"
5 | #include "../types/song_info.hpp"
6 |
7 | class $modify(MusicDownloadManager) {
8 | gd::string pathForSong(int id) {
9 | auto active = NongManager::get()->getActiveNong(id);
10 | if (!active.has_value()) {
11 | return MusicDownloadManager::pathForSong(id);
12 | }
13 | auto value = active.value();
14 | if (!fs::exists(value.path)) {
15 | return MusicDownloadManager::pathForSong(id);
16 | }
17 | return value.path.string();
18 | }
19 | void onGetSongInfoCompleted(gd::string p1, gd::string p2) {
20 | MusicDownloadManager::onGetSongInfoCompleted(p1, p2);
21 | auto songID = std::stoi(p2);
22 | NongManager::get()->resolveSongInfoCallback(songID);
23 | }
24 |
25 | SongInfoObject* getSongInfoObject(int id) {
26 | auto og = MusicDownloadManager::getSongInfoObject(id);
27 | if (og == nullptr) {
28 | return og;
29 | }
30 | auto active = NongManager::get()->getActiveNong(id);
31 | if (active.has_value()) {
32 | auto value = active.value();
33 | og->m_songName = value.songName;
34 | og->m_artistName = value.authorName;
35 | }
36 | return og;
37 | }
38 | };
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | // hello :)
2 |
3 | #include "managers/nong_manager.hpp"
4 |
5 | $execute {
6 | NongManager::get()->loadSongs();
7 | };
--------------------------------------------------------------------------------
/src/managers/nong_manager.cpp:
--------------------------------------------------------------------------------
1 | #include "nong_manager.hpp"
2 |
3 | std::optional NongManager::getNongs(int songID) {
4 | if (!m_state.m_nongs.contains(songID)) {
5 | return std::nullopt;
6 | }
7 |
8 | return m_state.m_nongs[songID];
9 | }
10 |
11 | std::optional NongManager::getActiveNong(int songID) {
12 | auto nongs_res = this->getNongs(songID);
13 | if (!nongs_res.has_value()) {
14 | return std::nullopt;
15 | }
16 | auto nongs = nongs_res.value();
17 |
18 | for (auto &song : nongs.songs) {
19 | if (song.path == nongs.active) {
20 | return song;
21 | }
22 | }
23 |
24 | nongs.active = nongs.defaultPath;
25 |
26 | for (auto &song : nongs.songs) {
27 | if (song.path == nongs.active) {
28 | return song;
29 | }
30 | }
31 |
32 | return std::nullopt;
33 | }
34 |
35 | std::optional NongManager::getDefaultNong(int songID) {
36 | auto nongs_res = this->getNongs(songID);
37 | if (!nongs_res.has_value()) {
38 | return std::nullopt;
39 | }
40 | auto nongs = nongs_res.value();
41 |
42 | for (auto &song : nongs.songs) {
43 | if (song.path == nongs.defaultPath) {
44 | return song;
45 | }
46 | }
47 |
48 | return std::nullopt;
49 | }
50 |
51 | void NongManager::resolveSongInfoCallback(int id) {
52 | if (m_getSongInfoCallbacks.contains(id)) {
53 | m_getSongInfoCallbacks[id](id);
54 | m_getSongInfoCallbacks.erase(id);
55 | }
56 | }
57 |
58 | std::vector NongManager::validateNongs(int songID) {
59 | auto result = this->getNongs(songID);
60 | // Validate nong paths and delete those that don't exist anymore
61 | std::vector invalidSongs;
62 | std::vector validSongs;
63 | if (!result.has_value()) {
64 | return invalidSongs;
65 | }
66 | auto currentData = result.value();
67 |
68 | for (auto &song : currentData.songs) {
69 | if (!fs::exists(song.path) && currentData.defaultPath != song.path && song.songUrl == "local") {
70 | invalidSongs.push_back(song);
71 | if (song.path == currentData.active) {
72 | currentData.active = currentData.defaultPath;
73 | }
74 | } else {
75 | validSongs.push_back(song);
76 | }
77 | }
78 |
79 | if (invalidSongs.size() > 0) {
80 | NongData newData = {
81 | .active = currentData.active,
82 | .defaultPath = currentData.defaultPath,
83 | .songs = validSongs,
84 | };
85 |
86 | this->saveNongs(newData, songID);
87 | }
88 |
89 | return invalidSongs;
90 | }
91 |
92 | int NongManager::getCurrentManifestVersion() {
93 | return m_state.m_manifestVersion;
94 | }
95 |
96 | int NongManager::getStoredIDCount() {
97 | return m_state.m_nongs.size();
98 | }
99 |
100 | void NongManager::saveNongs(NongData const& data, int songID) {
101 | m_state.m_nongs[songID] = data;
102 | this->writeJson();
103 | }
104 |
105 | void NongManager::writeJson() {
106 | auto json = matjson::Serialize::to_json(m_state);
107 | auto path = this->getJsonPath();
108 | std::ofstream output(path.string());
109 | output << json.dump(matjson::NO_INDENTATION);
110 | output.close();
111 | }
112 |
113 | void NongManager::addNong(SongInfo const& song, int songID) {
114 | auto result = this->getNongs(songID);
115 | if (!result.has_value()) {
116 | return;
117 | }
118 | auto existingData = result.value();
119 |
120 | for (auto const& savedSong : existingData.songs) {
121 | if (song.path.string() == savedSong.path.string()) {
122 | return;
123 | }
124 | }
125 | existingData.songs.push_back(song);
126 | this->saveNongs(existingData, songID);
127 | }
128 |
129 | void NongManager::deleteAll(int songID) {
130 | std::vector newSongs;
131 | auto result = this->getNongs(songID);
132 | if (!result.has_value()) {
133 | return;
134 | }
135 | auto existingData = result.value();
136 |
137 | for (auto savedSong : existingData.songs) {
138 | if (savedSong.path != existingData.defaultPath) {
139 | if (fs::exists(savedSong.path)) {
140 | fs::remove(savedSong.path);
141 | }
142 | continue;
143 | }
144 | newSongs.push_back(savedSong);
145 | }
146 |
147 | NongData newData = {
148 | .active = existingData.defaultPath,
149 | .defaultPath = existingData.defaultPath,
150 | .songs = newSongs,
151 | };
152 | this->saveNongs(newData, songID);
153 | }
154 |
155 | void NongManager::deleteNong(SongInfo const& song, int songID) {
156 | std::vector newSongs;
157 | auto result = this->getNongs(songID);
158 | if(!result.has_value()) {
159 | return;
160 | }
161 | auto existingData = result.value();
162 | for (auto savedSong : existingData.songs) {
163 | if (savedSong.path == song.path) {
164 | if (song.path == existingData.active) {
165 | existingData.active = existingData.defaultPath;
166 | }
167 | if (song.songUrl != "local" && existingData.defaultPath != song.path && fs::exists(song.path)) {
168 | fs::remove(song.path);
169 | }
170 | continue;
171 | }
172 | newSongs.push_back(savedSong);
173 | }
174 | NongData newData = {
175 | .active = existingData.active,
176 | .defaultPath = existingData.defaultPath,
177 | .songs = newSongs,
178 | };
179 | this->saveNongs(newData, songID);
180 | }
181 |
182 | void NongManager::createDefault(int songID, bool fromCallback) {
183 | if (m_state.m_nongs.contains(songID)) {
184 | return;
185 | }
186 | SongInfoObject* songInfo = MusicDownloadManager::sharedState()->getSongInfoObject(songID);
187 | if (songInfo == nullptr && !m_getSongInfoCallbacks.contains(songID) && !fromCallback) {
188 | MusicDownloadManager::sharedState()->getSongInfo(songID, true);
189 | m_getSongInfoCallbacks[songID] = [this](int songID) {
190 | this->createDefault(songID, true);
191 | };
192 | return;
193 | }
194 | if (songInfo == nullptr) {
195 | return;
196 | }
197 | fs::path songPath = fs::path(std::string(MusicDownloadManager::sharedState()->pathForSong(songID)));
198 | NongData data;
199 | SongInfo defaultSong;
200 | defaultSong.authorName = songInfo->m_artistName;
201 | defaultSong.songName = songInfo->m_songName;
202 | defaultSong.path = songPath;
203 | defaultSong.songUrl = songInfo->m_songUrl;
204 | data.active = songPath;
205 | data.defaultPath = songPath;
206 | data.songs.push_back(defaultSong);
207 | m_state.m_nongs[songID] = data;
208 | }
209 |
210 | void NongManager::createUnknownDefault(int songID) {
211 | if (this->getNongs(songID).has_value()) {
212 | return;
213 | }
214 | fs::path songPath = fs::path(std::string(MusicDownloadManager::sharedState()->pathForSong(songID)));
215 | NongData data;
216 | SongInfo defaultSong;
217 | defaultSong.authorName = "Unknown";
218 | defaultSong.songName = "Unknown";
219 | defaultSong.path = songPath;
220 | defaultSong.songUrl = "";
221 | data.active = songPath;
222 | data.defaultPath = songPath;
223 | data.songs.push_back(defaultSong);
224 | data.defaultValid = false;
225 | m_state.m_nongs[songID] = data;
226 | this->writeJson();
227 | }
228 |
229 | std::string NongManager::getFormattedSize(SongInfo const& song) {
230 | try {
231 | auto size = fs::file_size(song.path);
232 | double toMegabytes = size / 1024.f / 1024.f;
233 | std::stringstream ss;
234 | ss << std::setprecision(3) << toMegabytes << "MB";
235 | return ss.str();
236 | } catch (fs::filesystem_error) {
237 | return "N/A";
238 | }
239 | }
240 |
241 | void NongManager::getMultiAssetSizes(std::string songs, std::string sfx, std::function callback) {
242 | fs::path resources = fs::path(CCFileUtils::get()->getWritablePath2().c_str()) / "Resources";
243 | fs::path songDir = fs::path(CCFileUtils::get()->getWritablePath().c_str());
244 | std::thread([this, songs, sfx, callback, resources, songDir]() {
245 | float sum = 0.f;
246 | std::istringstream stream(songs);
247 | std::string s;
248 | while (std::getline(stream, s, ',')) {
249 | int id = std::stoi(s);
250 | auto result = this->getActiveNong(id);
251 | if (!result.has_value()) {
252 | continue;
253 | }
254 | auto path = result.value().path;
255 | if (path.string().starts_with("songs/")) {
256 | path = resources / path;
257 | }
258 | if (fs::exists(path)) {
259 | sum += fs::file_size(path);
260 | }
261 | }
262 | stream = std::istringstream(sfx);
263 | while (std::getline(stream, s, ',')) {
264 | std::stringstream ss;
265 | ss << "s" << s << ".ogg";
266 | std::string filename = ss.str();
267 | auto localPath = resources / "sfx" / filename;
268 | if (fs::exists(localPath)) {
269 | sum += fs::file_size(localPath);
270 | continue;
271 | }
272 | auto path = songDir / filename;
273 | if (fs::exists(path)) {
274 | sum += fs::file_size(path);
275 | }
276 | }
277 |
278 | double toMegabytes = sum / 1024.f / 1024.f;
279 | std::stringstream ss;
280 | ss << std::setprecision(3) << toMegabytes << "MB";
281 | callback(ss.str());
282 | }).detach();
283 | }
284 |
285 | fs::path NongManager::getJsonPath() {
286 | auto savedir = fs::path(Mod::get()->getSaveDir().string());
287 | return savedir / "nong_data.json";
288 | }
289 |
290 | void NongManager::fetchSFH(int songID, std::function callback) {
291 | std::string url = "https://api.songfilehub.com/songs?songID=" + std::to_string(songID);
292 | web::AsyncWebRequest()
293 | .fetch(url)
294 | .text()
295 | .then([this, callback, songID](std::string const& text) {
296 | auto copy = text;
297 | copy.erase(std::remove_if(copy.begin(), copy.end(), [](char x) {
298 | return static_cast(x) < 0;
299 | }), copy.end());
300 | matjson::Value data;
301 | data = matjson::parse(copy);
302 | std::vector ret;
303 | if (!data.is_array()) {
304 | callback(nongd::FetchStatus::FAILED);
305 | return;
306 | }
307 | auto songs = data.as_array();
308 | bool getMashups = Mod::get()->getSettingValue("store-mashups");
309 | for (auto const& song : songs) {
310 | bool isMashup = song["state"].as_string() == "mashup";
311 | if (isMashup && !getMashups) {
312 | continue;
313 | }
314 | SFHItem item = {
315 | .songName = song["songName"].as_string(),
316 | .downloadUrl = song["downloadUrl"].as_string(),
317 | .levelName = song.contains("name") ? song["name"].as_string() : "",
318 | .songURL = song.contains("songURL") ? song["songURL"].as_string() : ""
319 | };
320 | ret.push_back(item);
321 | }
322 | if (ret.size() == 0) {
323 | callback(nongd::FetchStatus::NOTHING_FOUND);
324 | return;
325 | }
326 | if (!this->addNongsFromSFH(ret, songID)) {
327 | callback(nongd::FetchStatus::FAILED);
328 | } else {
329 | callback(nongd::FetchStatus::SUCCESS);
330 | }
331 | })
332 | .expect([callback](std::string const& error) {
333 | log::error("{}", error);
334 | callback(nongd::FetchStatus::FAILED);
335 | })
336 | .cancelled([callback](auto r) {
337 | callback(nongd::FetchStatus::FAILED);
338 | });
339 | }
340 |
341 | void NongManager::downloadSong(SongInfo const& song, std::function progress, std::function failed) {
342 | if (fs::exists(song.path)) {
343 | return;
344 | }
345 |
346 | web::AsyncWebRequest()
347 | .fetch(song.songUrl)
348 | .into(ghc::filesystem::path(song.path.string()))
349 | .then([progress](std::monostate state){
350 | progress(100.f);
351 | })
352 | .expect([song, failed](std::string const& error) {
353 | failed(song, error);
354 | })
355 | .cancelled([song, failed](auto request) {
356 | failed(song, "The download was cancelled");
357 | });
358 | }
359 |
360 | bool NongManager::addNongsFromSFH(std::vector const& songs, int songID) {
361 | auto savedir = fs::path(Mod::get()->getSaveDir().string());
362 | auto nongsPath = savedir / "nongs";
363 | if (!fs::exists(nongsPath)) {
364 | fs::create_directory(nongsPath);
365 | }
366 | auto result = this->getNongs(songID);
367 | if (!result.has_value()) {
368 | return false;
369 | }
370 | auto nongs = result.value();
371 | int index = 1;
372 | for (auto const& sfhSong : songs) {
373 | bool update = false;
374 | auto path = nongsPath;
375 | auto unique = nongd::random_string(16);
376 | path.append(std::to_string(songID) + "_" + sfhSong.levelName + "_" + unique + ".mp3");
377 | for (auto& localSong : nongs.songs) {
378 | if (localSong.songUrl == sfhSong.downloadUrl) {
379 | update = true;
380 | break;
381 | }
382 | }
383 |
384 | auto sfhSongName = sfhSong.songName;
385 | nongd::trim(sfhSongName);
386 |
387 | if (sfhSongName.find_first_of("-") == std::string::npos) {
388 | // probably dash doesn't exist because of unicode filtering, try to insert it by hand
389 | for (size_t i = 0; i < sfhSongName.size() - 1; i++) {
390 | if (sfhSongName.at(i) == ' ' && sfhSongName.at(i + 1) == ' ') {
391 | sfhSongName.insert(i + 1, "-");
392 | }
393 | }
394 | }
395 |
396 | std::string songName = "-";
397 | std::string artistName = "-";
398 | std::stringstream ss;
399 | ss << sfhSongName;
400 | std::string part;
401 | size_t i = 0;
402 | while (std::getline(ss, part, '-')) {
403 | if (i == 0) {
404 | artistName = part;
405 | nongd::right_trim(artistName);
406 | } else {
407 | songName = part;
408 | nongd::left_trim(songName);
409 | }
410 | i++;
411 | }
412 |
413 | if (songName == "-") {
414 | auto temp = songName;
415 | songName = artistName;
416 | artistName = temp;
417 | }
418 |
419 | SongInfo song = {
420 | .path = path,
421 | .songName = songName,
422 | .authorName = artistName,
423 | .songUrl = sfhSong.downloadUrl,
424 | .levelName = sfhSong.levelName
425 | };
426 |
427 | if (update) {
428 | for(auto& localSong : nongs.songs) {
429 | if (localSong.songUrl == song.songUrl) {
430 | localSong.songName = song.songName;
431 | localSong.authorName = song.authorName;
432 | localSong.levelName = song.levelName;
433 | }
434 | }
435 | } else {
436 | nongs.songs.push_back(song);
437 | }
438 | }
439 |
440 | this->saveNongs(nongs, songID);
441 | return true;
442 | }
443 |
444 | void NongManager::loadSongs() {
445 | auto path = this->getJsonPath();
446 | if (!fs::exists(path)) {
447 | return;
448 | }
449 | std::ifstream input(path.string());
450 | std::stringstream buffer;
451 | buffer << input.rdbuf();
452 | input.close();
453 |
454 | auto json = matjson::parse(std::string_view(buffer.str()));
455 | m_state = matjson::Serialize::from_json(json);
456 | }
--------------------------------------------------------------------------------
/src/managers/nong_manager.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include