2 Commits 32124eadfa ... ac69797977

Auteur SHA1 Bericht Datum
  kiwec ac69797977 Import .osz beatmapsets by dropping on window 3 maanden geleden
  kiwec 5cb0d60752 Add local skin folder + import via dropping on window 3 maanden geleden

+ 2 - 0
src/App/Osu/Changelog.cpp

@@ -29,6 +29,8 @@ Changelog::Changelog() : ScreenBackable() {
     CHANGELOG latest;
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
+    latest.changes.push_back("- Added ability to drag-and-drop .osk and .osz files onto neosu");
+    latest.changes.push_back("- Added local skin folder");
     latest.changes.push_back("- Fixed songs failing to restart");
     changelogs.push_back(latest);
 

+ 6 - 4
src/App/Osu/Database.cpp

@@ -392,7 +392,8 @@ void Database::update() {
 
             // update progress (another thread checks if progress >= 1.f to know when we're done)
             float progress = (float)m_iCurRawBeatmapLoadIndex / (float)m_iNumBeatmapsToLoad;
-            if(progress >= 1.0f) progress = 0.99f;
+            if(progress == 0.f) progress = 0.01f;
+            if(progress >= 1.f) progress = 0.99f;
             m_fLoadingProgress = progress;
 
             // check if we are finished
@@ -768,7 +769,7 @@ int Database::getLevelForScore(unsigned long long score, int maxLevel) {
 }
 
 DatabaseBeatmap *Database::getBeatmapDifficulty(const MD5Hash &md5hash) {
-    if(!isFinished()) return NULL;
+    if(isLoading()) return NULL;
 
     for(size_t i = 0; i < m_databaseBeatmaps.size(); i++) {
         DatabaseBeatmap *beatmap = m_databaseBeatmaps[i];
@@ -785,7 +786,7 @@ DatabaseBeatmap *Database::getBeatmapDifficulty(const MD5Hash &md5hash) {
 }
 
 DatabaseBeatmap *Database::getBeatmapDifficulty(i32 map_id) {
-    if(!isFinished()) return NULL;
+    if(isLoading()) return NULL;
 
     for(size_t i = 0; i < m_databaseBeatmaps.size(); i++) {
         DatabaseBeatmap *beatmap = m_databaseBeatmaps[i];
@@ -802,7 +803,7 @@ DatabaseBeatmap *Database::getBeatmapDifficulty(i32 map_id) {
 }
 
 DatabaseBeatmap *Database::getBeatmapSet(i32 set_id) {
-    if(!isFinished()) return NULL;
+    if(isLoading()) return NULL;
 
     for(size_t i = 0; i < m_databaseBeatmaps.size(); i++) {
         DatabaseBeatmap *beatmap = m_databaseBeatmaps[i];
@@ -1008,6 +1009,7 @@ void Database::loadDB(Packet *db, bool &fallbackToRawLoad) {
 
         // update progress (another thread checks if progress >= 1.f to know when we're done)
         float progress = (float)i / (float)m_iNumBeatmapsToLoad;
+        if(progress == 0.f) progress = 0.01f;
         if(progress >= 1.f) progress = 0.99f;
         m_fLoadingProgress = progress;
 

+ 4 - 0
src/App/Osu/Database.h

@@ -83,6 +83,10 @@ class Database {
     static int getLevelForScore(unsigned long long score, int maxLevel = 120);
 
     inline float getProgress() const { return m_fLoadingProgress.load(); }
+    inline bool isLoading() const {
+        float progress = getProgress();
+        return progress > 0.f && progress < 1.f;
+    }
     inline bool isFinished() const { return (getProgress() >= 1.0f); }
     inline bool foundChanges() const { return m_bFoundChanges; }
 

+ 1 - 0
src/App/Osu/DatabaseBeatmap.cpp

@@ -147,6 +147,7 @@ DatabaseBeatmap::DatabaseBeatmap(std::vector<DatabaseBeatmap *> *difficulties) :
     m_sArtist = (*m_difficulties)[0]->m_sArtist;
     m_sCreator = (*m_difficulties)[0]->m_sCreator;
     m_sBackgroundImageFileName = (*m_difficulties)[0]->m_sBackgroundImageFileName;
+    m_iSetID = (*m_difficulties)[0]->m_iSetID;
 
     // also calculate largest representative values
     m_iLengthMS = 0;

+ 92 - 22
src/App/Osu/Downloader.cpp

@@ -215,49 +215,96 @@ end:
     curl_url_cleanup(urlu);
 }
 
-void download_beatmapset(u32 set_id, float* progress) {
-    // Check if we already have downloaded it
-    auto map_dir = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_id);
-    if(env->directoryExists(map_dir.toUtf8())) {
-        *progress = 1.f;
-        return;
+i32 get_beatmapset_id_from_osu_file(const u8* osu_data, size_t s_osu_data) {
+    i32 set_id = -1;
+    std::string line;
+    for(size_t i = 0; i < s_osu_data; i++) {
+        if(osu_data[i] == '\n') {
+            if(line.find("//") != 0) {
+                debugLog("%s\n", line.c_str());
+                sscanf(line.c_str(), " BeatmapSetID : %i \n", &set_id);
+                if(set_id != -1) return set_id;
+            }
+
+            line = "";
+        } else {
+            line.push_back(osu_data[i]);
+        }
     }
 
-    std::vector<u8> data;
-    auto mirror = convar->getConVarByName("beatmap_mirror")->getString();
-    mirror.append(UString::format("%d", set_id));
-    int response_code = 0;
-    download(mirror.toUtf8(), progress, data, &response_code);
-    if(response_code != 200) return;
+    if(line.find("//") != 0) {
+        debugLog("%s\n", line.c_str());
+        sscanf(line.c_str(), " BeatmapSetID : %i \n", &set_id);
+    }
+
+    return -1;
+}
+
+i32 extract_beatmapset_id(const u8* data, size_t data_s) {
+    i32 set_id = -1;
 
-    // Download succeeded: save map to disk
     mz_zip_archive zip = {0};
     mz_zip_archive_file_stat file_stat;
     mz_uint num_files = 0;
 
-    debugLog("Extracting beatmapset %d (%d bytes)\n", set_id, data.size());
-    if(!mz_zip_reader_init_mem(&zip, data.data(), data.size(), 0)) {
+    debugLog("Reading beatmapset (%d bytes)\n", data_s);
+    if(!mz_zip_reader_init_mem(&zip, data, data_s, 0)) {
         debugLog("Failed to open .osz file\n");
-        *progress = -1.f;
-        return;
+        return -1;
     }
 
     num_files = mz_zip_reader_get_num_files(&zip);
     if(num_files <= 0) {
         debugLog(".osz file is empty!\n");
         mz_zip_reader_end(&zip);
-        *progress = -1.f;
-        return;
+        return -1;
     }
-    if(!env->directoryExists(map_dir.toUtf8())) {
-        env->createDirectory(map_dir.toUtf8());
+    for(mz_uint i = 0; i < num_files; i++) {
+        if(!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
+        if(mz_zip_reader_is_file_a_directory(&zip, i)) continue;
+        if(env->getFileExtensionFromFilePath(file_stat.m_filename).compare("osu") != 0) continue;
+
+        size_t s_osu_data = 0;
+        u8* osu_data = (u8*)mz_zip_reader_extract_to_heap(&zip, i, &s_osu_data, 0);
+        set_id = get_beatmapset_id_from_osu_file(osu_data, s_osu_data);
+        mz_free(osu_data);
+        if(set_id != -1) break;
+
+    skip_file:;
+        // When a file can't be extracted we just ignore it (as long as the archive is valid).
+        // We'll check for errors when loading the beatmap.
+    }
+
+    mz_zip_reader_end(&zip);
+    return set_id;
+}
+
+bool extract_beatmapset(const u8* data, size_t data_s, std::string map_dir) {
+    mz_zip_archive zip = {0};
+    mz_zip_archive_file_stat file_stat;
+    mz_uint num_files = 0;
+
+    debugLog("Extracting beatmapset (%d bytes)\n", data_s);
+    if(!mz_zip_reader_init_mem(&zip, data, data_s, 0)) {
+        debugLog("Failed to open .osz file\n");
+        return false;
+    }
+
+    num_files = mz_zip_reader_get_num_files(&zip);
+    if(num_files <= 0) {
+        debugLog(".osz file is empty!\n");
+        mz_zip_reader_end(&zip);
+        return false;
+    }
+    if(!env->directoryExists(map_dir)) {
+        env->createDirectory(map_dir);
     }
     for(mz_uint i = 0; i < num_files; i++) {
         if(!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
         if(mz_zip_reader_is_file_a_directory(&zip, i)) continue;
 
         auto folders = UString(file_stat.m_filename).split("/");
-        std::string file_path = map_dir.toUtf8();
+        std::string file_path = map_dir;
         for(auto folder : folders) {
             if(!env->directoryExists(file_path)) {
                 env->createDirectory(file_path);
@@ -281,6 +328,29 @@ void download_beatmapset(u32 set_id, float* progress) {
 
     // Success
     mz_zip_reader_end(&zip);
+    return true;
+}
+
+void download_beatmapset(u32 set_id, float* progress) {
+    // Check if we already have downloaded it
+    auto map_dir = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_id);
+    if(env->directoryExists(map_dir.toUtf8())) {
+        *progress = 1.f;
+        return;
+    }
+
+    std::vector<u8> data;
+    auto mirror = convar->getConVarByName("beatmap_mirror")->getString();
+    mirror.append(UString::format("%d", set_id));
+    int response_code = 0;
+    download(mirror.toUtf8(), progress, data, &response_code);
+    if(response_code != 200) return;
+
+    // Download succeeded: save map to disk
+    if(!extract_beatmapset(data.data(), data.size(), map_dir.toUtf8())) {
+        *progress = -1.f;
+        return;
+    }
 }
 
 std::unordered_map<i32, i32> beatmap_to_beatmapset;

+ 3 - 0
src/App/Osu/Downloader.h

@@ -20,3 +20,6 @@ void download_beatmapset(u32 set_id, float *progress);
 DatabaseBeatmap *download_beatmap(i32 beatmap_id, MD5Hash beatmap_md5, float *progress);
 DatabaseBeatmap *download_beatmap(i32 beatmap_id, i32 beatmapset_id, float *progress);
 void process_beatmapset_info_response(Packet packet);
+
+i32 extract_beatmapset_id(const u8* data, size_t data_s);
+bool extract_beatmapset(const u8* data, size_t data_s, std::string map_dir);

+ 18 - 6
src/App/Osu/OptionsMenu.cpp

@@ -2397,11 +2397,16 @@ void OptionsMenu::openCurrentSkinFolder() {
         env->openDirectory(MCENGINE_DATA_DIR "materials/default");
 #endif
     } else {
-        UString skinFolder = convar->getConVarByName("osu_folder")->getString();
-        skinFolder.append(convar->getConVarByName("osu_folder_sub_skins")->getString());
-        skinFolder.append(current_skin);
-        std::string skin_folder_str(skinFolder.toUtf8());
-        env->openDirectory(skinFolder.toUtf8());
+        std::string neosuSkinFolder = MCENGINE_DATA_DIR "skins/";
+        neosuSkinFolder.append(current_skin.toUtf8());
+        if(env->directoryExists(neosuSkinFolder)) {
+            env->openDirectory(neosuSkinFolder);
+        } else {
+            UString skinFolder = convar->getConVarByName("osu_folder")->getString();
+            skinFolder.append(convar->getConVarByName("osu_folder_sub_skins")->getString());
+            skinFolder.append(current_skin);
+            env->openDirectory(skinFolder.toUtf8());
+        }
     }
 }
 
@@ -2412,7 +2417,14 @@ void OptionsMenu::onSkinSelect() {
 
     UString skinFolder = convar->getConVarByName("osu_folder")->getString();
     skinFolder.append(convar->getConVarByName("osu_folder_sub_skins")->getString());
-    std::vector<std::string> skinFolders = env->getFoldersInFolder(skinFolder.toUtf8());
+
+    std::vector<std::string> skinFolders;
+    for(auto skin : env->getFoldersInFolder(MCENGINE_DATA_DIR "skins/")) {
+        skinFolders.push_back(skin);
+    }
+    for(auto skin : env->getFoldersInFolder(skinFolder.toUtf8())) {
+        skinFolders.push_back(skin);
+    }
 
     if(convar->getConVarByName("sort_skins_by_name")->getBool()) {
         // Sort skins only by alphanum characters, ignore the others

+ 14 - 8
src/App/Osu/Osu.cpp

@@ -59,7 +59,7 @@ Osu *osu = NULL;
 
 // release configuration
 ConVar auto_update("auto_update", true, FCVAR_DEFAULT);
-ConVar osu_version("osu_version", 35.07f, FCVAR_DEFAULT | FCVAR_HIDDEN);
+ConVar osu_version("osu_version", 35.08f, FCVAR_DEFAULT | FCVAR_HIDDEN);
 
 #ifdef _DEBUG
 ConVar osu_debug("osu_debug", true, FCVAR_DEFAULT);
@@ -2019,13 +2019,19 @@ void Osu::onSkinChange(UString oldValue, UString newValue) {
         if(newValue.length() < 1) return;
     }
 
-    UString skinFolder = m_osu_folder_ref->getString();
-    skinFolder.append(m_osu_folder_sub_skins_ref->getString());
-    skinFolder.append(newValue);
-    skinFolder.append("/");
-    std::string sf = skinFolder.toUtf8();
-
-    m_skinScheduledToLoad = new Skin(newValue, sf, (newValue == UString("default")));
+    std::string neosuSkinFolder = MCENGINE_DATA_DIR "skins/";
+    neosuSkinFolder.append(newValue.toUtf8());
+    neosuSkinFolder.append("/");
+    if(env->directoryExists(neosuSkinFolder)) {
+        m_skinScheduledToLoad = new Skin(newValue, neosuSkinFolder, (newValue == UString("default")));
+    } else {
+        UString ppySkinFolder = m_osu_folder_ref->getString();
+        ppySkinFolder.append(m_osu_folder_sub_skins_ref->getString());
+        ppySkinFolder.append(newValue);
+        ppySkinFolder.append("/");
+        std::string sf = ppySkinFolder.toUtf8();
+        m_skinScheduledToLoad = new Skin(newValue, sf, (newValue == UString("default")));
+    }
 
     // initial load
     if(m_skin == NULL) m_skin = m_skinScheduledToLoad;

+ 0 - 1
src/App/Osu/Osu.h

@@ -92,7 +92,6 @@ class Osu : public App, public MouseListener {
 
     void saveScreenshot();
 
-    void setSkin(UString skin) { onSkinChange("", skin); }
     void reloadSkin() { onSkinReload(); }
 
     inline Vector2 getScreenSize() const { return g_vInternalResolution; }

+ 63 - 0
src/App/Osu/Skin.cpp

@@ -2,6 +2,8 @@
 
 #include <string.h>
 
+#include "miniz.h"
+
 #include "Beatmap.h"
 #include "ConVar.h"
 #include "Engine.h"
@@ -62,6 +64,67 @@ ConVar *Skin::m_osu_skin_hd = &osu_skin_hd;
 ConVar *Skin::m_osu_skin_ref = NULL;
 ConVar *Skin::m_osu_mod_fposu_ref = NULL;
 
+void Skin::unpack(const char* filepath) {
+    auto skin_name = env->getFileNameFromFilePath(filepath);
+    debugLog("Extracting %s...\n", skin_name.c_str());
+    skin_name.erase(skin_name.size() - 4); // remove .osk extension
+
+    auto skin_root = std::string(MCENGINE_DATA_DIR "skins/");
+    skin_root.append(skin_name);
+    skin_root.append("/");
+
+    File file(filepath);
+
+    mz_zip_archive zip = {0};
+    mz_zip_archive_file_stat file_stat;
+    mz_uint num_files = 0;
+
+    if(!mz_zip_reader_init_mem(&zip, file.readFile(), file.getFileSize(), 0)) {
+        debugLog("Failed to open .osk file\n");
+        return;
+    }
+
+    num_files = mz_zip_reader_get_num_files(&zip);
+    if(num_files <= 0) {
+        debugLog(".osk file is empty!\n");
+        mz_zip_reader_end(&zip);
+        return;
+    }
+
+    if(!env->directoryExists(skin_root)) {
+        env->createDirectory(skin_root);
+    }
+
+    for(mz_uint i = 0; i < num_files; i++) {
+        if(!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
+        if(mz_zip_reader_is_file_a_directory(&zip, i)) continue;
+
+        auto folders = UString(file_stat.m_filename).split("/");
+        std::string file_path = skin_root;
+        for(auto folder : folders) {
+            if(!env->directoryExists(file_path)) {
+                env->createDirectory(file_path);
+            }
+
+            if(folder == UString("..")) {
+                // Bro...
+                goto skip_file;
+            } else {
+                file_path.append("/");
+                file_path.append(folder.toUtf8());
+            }
+        }
+
+        mz_zip_reader_extract_to_file(&zip, i, file_path.c_str(), 0);
+
+    skip_file:;
+        // When a file can't be extracted we just ignore it (as long as the archive is valid).
+    }
+
+    // Success
+    mz_zip_reader_end(&zip);
+}
+
 Skin::Skin(UString name, std::string filepath, bool isDefaultSkin) {
     m_sName = name;
     m_sFilePath = filepath;

+ 1 - 0
src/App/Osu/Skin.h

@@ -11,6 +11,7 @@ class SkinImage;
 class Skin {
    public:
     static const char *OSUSKIN_DEFAULT_SKIN_PATH;
+    static void unpack(const char* filepath);
 
     static ConVar *m_osu_skin_async;
     static ConVar *m_osu_skin_hd;

+ 53 - 37
src/App/Osu/SongBrowser/SongBrowser.cpp

@@ -895,6 +895,43 @@ void SongBrowser::drawSelectedBeatmapBackgroundImage(Graphics *g, float alpha) {
     }
 }
 
+bool SongBrowser::selectBeatmapset(i32 set_id) {
+    auto beatmapset = getDatabase()->getBeatmapSet(set_id);
+    if(beatmapset == NULL) {
+        // Pasted from Downloader::download_beatmap
+        auto mapset_path = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_id);
+        // XXX: Make a permanent database for auto-downloaded songs, so we can load them like osu!.db's
+        osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path.toUtf8());
+        osu->m_songBrowser2->updateSongButtonSorting();
+        debugLog("Finished loading beatmapset %d.\n", set_id);
+
+        beatmapset = getDatabase()->getBeatmapSet(set_autodl);
+    }
+
+    if(beatmapset == NULL) {
+        return false;
+    }
+
+    // Just picking the hardest diff for now
+    DatabaseBeatmap *best_diff = NULL;
+    const std::vector<DatabaseBeatmap *> &diffs = beatmapset->getDifficulties();
+    for(size_t d = 0; d < diffs.size(); d++) {
+        DatabaseBeatmap *diff = diffs[d];
+        if(!best_diff || diff->getStarsNomod() > best_diff->getStarsNomod()) {
+            best_diff = diff;
+        }
+    }
+
+    if(best_diff == NULL) {
+        osu->getNotificationOverlay()->addNotification("Beatmapset has no difficulties :/");
+        return false;
+    } else {
+        osu->m_songBrowser2->onDifficultySelected(best_diff, false);
+        osu->m_songBrowser2->selectSelectedBeatmapSongButton();
+        return true;
+    }
+}
+
 void SongBrowser::mouse_update(bool *propagate_clicks) {
     if(!m_bVisible) return;
     ScreenBackable::mouse_update(propagate_clicks);
@@ -948,8 +985,10 @@ void SongBrowser::mouse_update(bool *propagate_clicks) {
             set_autodl = 0;
         }
     } else if(set_autodl) {
-        auto beatmapset = getDatabase()->getBeatmapSet(set_autodl);
-        if(beatmapset == NULL) {
+        if(selectBeatmapset(set_autodl)) {
+            map_autodl = 0;
+            set_autodl = 0;
+        } else {
             float progress = -1.f;
             download_beatmapset(set_autodl, &progress);
             if(progress == -1.f) {
@@ -962,37 +1001,11 @@ void SongBrowser::mouse_update(bool *propagate_clicks) {
                 auto text = UString::format("Downloading... %.2f%%", progress * 100.f);
                 osu->getNotificationOverlay()->addNotification(text);
             } else {
-                // Pasted from Downloader::download_beatmap
-                auto mapset_path = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_autodl);
-                // XXX: Make a permanent database for auto-downloaded songs, so we can load them like osu!.db's
-                osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path.toUtf8());
-                osu->m_songBrowser2->updateSongButtonSorting();
-                debugLog("Finished loading beatmapset %d.\n", set_autodl);
-
-                beatmapset = getDatabase()->getBeatmapSet(set_autodl);
-            }
-        }
+                selectBeatmapset(set_autodl);
 
-        if(beatmapset != NULL) {
-            // Just picking the hardest diff for now
-            DatabaseBeatmap *best_diff = NULL;
-            const std::vector<DatabaseBeatmap *> &diffs = beatmapset->getDifficulties();
-            for(size_t d = 0; d < diffs.size(); d++) {
-                DatabaseBeatmap *diff = diffs[d];
-                if(!best_diff || diff->getStarsNomod() > best_diff->getStarsNomod()) {
-                    best_diff = diff;
-                }
-            }
-
-            if(best_diff == NULL) {
-                osu->getNotificationOverlay()->addNotification("Beatmapset has no difficulties :/");
-            } else {
-                osu->m_songBrowser2->onDifficultySelected(best_diff, false);
-                osu->m_songBrowser2->selectSelectedBeatmapSongButton();
+                map_autodl = 0;
+                set_autodl = 0;
             }
-
-            map_autodl = 0;
-            set_autodl = 0;
         }
     }
 
@@ -1594,6 +1607,11 @@ void SongBrowser::refreshBeatmaps() {
         first_refresh = false;
     }
 
+    auto diff2 = m_selectedBeatmap->getSelectedDifficulty2();
+    if(diff2) {
+        beatmap_to_reselect_after_db_load = diff2->getMD5Hash();
+    }
+
     m_selectedBeatmap->pausePreviewMusic();
     m_selectedBeatmap->deselect();
     SAFE_DELETE(m_selectedBeatmap);
@@ -2983,12 +3001,10 @@ void SongBrowser::onDatabaseLoadingFinished() {
 
     if(osu_songbrowser_search_hardcoded_filter.getString().length() > 0) onSearchUpdate();
 
-    // main menu starts playing a song before the database is loaded,
-    // re-select it after the database has been loaded
-    if(osu->m_mainMenu->preloaded_beatmapset != NULL) {
-        auto matching_beatmap = getDatabase()->getBeatmapDifficulty(osu->m_mainMenu->preloaded_beatmap->getMD5Hash());
-        if(matching_beatmap) {
-            onDifficultySelected(matching_beatmap, false);
+    if(beatmap_to_reselect_after_db_load.hash[0] != 0) {
+        auto beatmap = getDatabase()->getBeatmapDifficulty(beatmap_to_reselect_after_db_load);
+        if(beatmap) {
+            onDifficultySelected(beatmap, false);
             selectSelectedBeatmapSongButton();
         }
 

+ 2 - 0
src/App/Osu/SongBrowser/SongBrowser.h

@@ -101,6 +101,7 @@ class SongBrowser : public ScreenBackable {
 
     virtual CBaseUIContainer *setVisible(bool visible);
 
+    bool selectBeatmapset(i32 set_id);
     void selectSelectedBeatmapSongButton();
     void onPlayEnd(bool quit = true);  // called when a beatmap is finished playing (or the player quit)
 
@@ -330,6 +331,7 @@ class SongBrowser : public ScreenBackable {
     std::unordered_map<MD5Hash, SongButton *> hashToSongButton;
     bool m_bBeatmapRefreshScheduled;
     UString m_sLastOsuFolder;
+    MD5Hash beatmap_to_reselect_after_db_load;
 
     // keys
     bool m_bF1Pressed;

+ 3 - 0
src/Engine/Engine.cpp

@@ -245,6 +245,9 @@ void Engine::loadApp() {
     if(!env->directoryExists(MCENGINE_DATA_DIR "screenshots")) {
         env->createDirectory(MCENGINE_DATA_DIR "screenshots");
     }
+    if(!env->directoryExists(MCENGINE_DATA_DIR "skins")) {
+        env->createDirectory(MCENGINE_DATA_DIR "skins");
+    }
 
     // load core default resources (these are required to be able to draw the loading screen)
     if(m_iLoadingScreenDelay == 0 || m_iLoadingScreenDelay == -2) {

+ 1 - 12
src/Engine/File.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2016, PG, All rights reserved. =================//
-//
-// Purpose:		file wrapper, for cross-platform unicode path support
-//
-// $NoKeywords: $file $os
-//===============================================================================//
-
-#ifndef FILE_H
-#define FILE_H
-
+#pragma once
 #include "cbase.h"
 
 class BaseFile;
@@ -80,5 +71,3 @@ class StdFile : public BaseFile {
     // full reader
     std::vector<u8> m_fullBuffer;
 };
-
-#endif

+ 79 - 81
src/Engine/Main/main_Windows.cpp

@@ -4,8 +4,19 @@
 // Include order matters
 #include "cbase.h"
 #include <dwmapi.h>
+#include <shellapi.h>
 // clang-format on
 
+#include "Database.h"
+#include "DatabaseBeatmap.h"
+#include "Downloader.h"  // for extract_beatmapset
+#include "File.h"
+#include "MainMenu.h"
+#include "OptionsMenu.h"
+#include "Osu.h"
+#include "SongBrowser/SongBrowser.h"
+#include "Skin.h"
+
 // NEXTRAWINPUTBLOCK macro requires this
 typedef uint64_t QWORD;
 
@@ -154,15 +165,6 @@ extern ConVar *win_realtimestylus;
 
 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
     switch(msg) {
-#ifdef WINDOW_GHOST
-
-        // window click-through
-        // case WM_NCHITTEST:
-        //	return HTNOWHERE;
-        //	break;
-
-#endif
-
         case WM_NCCREATE:
             if(g_bSupportsPerMonitorDpiAwareness) {
                 typedef BOOL(WINAPI * EPNCDS)(HWND);
@@ -172,46 +174,80 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
             }
             return DefWindowProcW(hwnd, msg, wParam, lParam);
 
-#if defined(WINDOW_FRAMELESS) && !defined(WINDOW_GHOST)
+        case WM_DROPFILES: {
+            HDROP hDrop = (HDROP)wParam;
+            UINT fileCount = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0);
+
+            std::string first_skin;
+
+            for (UINT i = 0; i < fileCount; i++) {
+                UINT pathLength = DragQueryFileW(hDrop, i, NULL, 0);
+                wchar_t *filePath = new wchar_t[pathLength + 1];
+                DragQueryFileW(hDrop, i, filePath, pathLength + 1);
+
+                // Convert filepath to UTF-8
+                int size = WideCharToMultiByte(CP_UTF8, 0, filePath, pathLength, NULL, 0, NULL, NULL);
+                std::string utf8filepath(size, 0);
+                WideCharToMultiByte(CP_UTF8, 0, filePath, size, (LPSTR)utf8filepath.c_str(), size, NULL, NULL);
+				delete[] filePath;
+
+                if(utf8filepath.length() < 4) continue;
+                auto extension = env->getFileExtensionFromFilePath(utf8filepath);
+                if(!extension.compare("osz")) {
+                    File osz(utf8filepath);
+                    i32 set_id = extract_beatmapset_id(osz.readFile(), osz.getFileSize());
+                    if(set_id == -1) {
+                        osu->getNotificationOverlay()->addNotification("Beatmapset doesn't have a valid ID.");
+                        continue;
+                    }
 
-            // ignore
-            /*
-            case WM_ERASEBKGND:
-                    return 1;
-            */
+                    std::string mapset_dir = MCENGINE_DATA_DIR "maps\\";
+                    mapset_dir.append(std::to_string(set_id));
+                    mapset_dir.append("\\");
+                    if(!env->directoryExists(mapset_dir)) {
+                        env->createDirectory(mapset_dir);
+                    }
+                    if(!extract_beatmapset(osz.readFile(), osz.getFileSize(), mapset_dir)) {
+                        osu->getNotificationOverlay()->addNotification("Failed to extract beatmapset");
+                        continue;
+                    }
 
-            // window border paint
-            /*
-            case WM_NCPAINT:
-                    {
-                            // draw beautifully blurred windows 7 background + shadows
-                            return DefWindowProcW(hwnd, msg, wParam, lParam);
-
-                            // draw white rectangle over everything except the shadows
-                            //HDC hdc;
-                            //hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN);
-                            //PAINTSTRUCT ps;
-                            //hdc = BeginPaint(hwnd, &ps);
-                            //RECT wr;
-                            //GetClientRect(hwnd, &wr);
-                            //HBRUSH br;
-                            //br = GetSysColorBrush(COLOR_WINDOW);
-                            //FillRect(hdc, &wr, br);
-                            //ReleaseDC(hwnd, hdc);
+                    osu->getSongBrowser()->getDatabase()->addBeatmap(mapset_dir);
+                    if(!osu->getSongBrowser()->selectBeatmapset(set_id)) {
+                        osu->getNotificationOverlay()->addNotification("Failed to import beatmapset");
+                        continue;
                     }
-                    /// return 0;
-            */
 
+                    // prevent song browser from picking main menu song after database loads
+                    // (we just loaded and selected another song, so previous no longer applies)
+                    SAFE_DELETE(osu->m_mainMenu->preloaded_beatmapset);
+                } else if(!extension.compare("osk") || !extension.compare("zip")) {
+                    Skin::unpack(utf8filepath.c_str());
+                    if(first_skin.length() == 0) {
+                        first_skin = utf8filepath;
+                    }
+                }
+            }
+
+            DragFinish(hDrop);
+
+            if(first_skin.length() > 0) {
+                auto folder_name = env->getFileNameFromFilePath(first_skin);
+                folder_name.erase(folder_name.size() - 4); // remove .osk extension
+
+                convar->getConVarByName("osu_skin")->setValue(env->getFileNameFromFilePath(folder_name).c_str());
+                osu->m_optionsMenu->updateSkinNameLabel();
+            }
+
+            return 0;
+        }
+
+
+#if defined(WINDOW_FRAMELESS) && !defined(WINDOW_GHOST)
         case WM_NCCALCSIZE: {
             if(wParam == TRUE) {
                 LPNCCALCSIZE_PARAMS pncc = (LPNCCALCSIZE_PARAMS)lParam;
 
-                // debugLog("new rectang: top = %i, right = %i, bottom = %i, left = %i\n", pncc->rgrc[0].top,
-                // pncc->rgrc[0].right, pncc->rgrc[0].bottom, pncc->rgrc[0].left); debugLog("old rectang: top = %i,
-                // right = %i, bottom = %i, left = %i\n", pncc->rgrc[1].top, pncc->rgrc[1].right, pncc->rgrc[1].bottom,
-                // pncc->rgrc[1].left); debugLog("client rect: top = %i, right = %i, bottom = %i, left = %i\n",
-                // pncc->rgrc[2].top, pncc->rgrc[2].right, pncc->rgrc[2].bottom, pncc->rgrc[2].left);
-
                 if(IsZoomed(hwnd)) {
                     // HACKHACK: use center instead of MonitorFromWindow() in order to workaround windows display
                     // scaling bullshit bug
@@ -226,26 +262,11 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
                     info.cbSize = sizeof(MONITORINFO);
                     GetMonitorInfo(monitor, &info);
 
-                    // McRect mr(info.rcMonitor.left, info.rcMonitor.top, std::abs(info.rcMonitor.left -
-                    // info.rcMonitor.right), std::abs(info.rcMonitor.top - info.rcMonitor.bottom)); printf("monitor.x =
-                    // %i, y = %i, width = %i, height = %i\n", (int)mr.getX(), (int)mr.getY(), (int)mr.getWidth(),
-                    // (int)mr.getHeight());
-
-                    // old (broken for multi-monitor setups)
-                    // pncc->rgrc[0].right += pncc->rgrc[0].left;
-                    // pncc->rgrc[0].bottom += pncc->rgrc[0].top;
-                    // pncc->rgrc[0].top = 0;
-                    // pncc->rgrc[0].left = 0;
-
-                    // new (still feels incorrect and fragile, but works for what I've tested it on)
                     pncc->rgrc[0].right += pncc->rgrc[0].left - info.rcMonitor.left;
                     pncc->rgrc[0].bottom += pncc->rgrc[0].top - info.rcMonitor.top;
                     pncc->rgrc[0].top = info.rcMonitor.top;
                     pncc->rgrc[0].left = info.rcMonitor.left;
                 }
-
-                // printf("after:  right = %i, bottom = %i, top = %i, left = %i\n", (int)pncc->rgrc[0].right,
-                // (int)pncc->rgrc[0].bottom, (int)pncc->rgrc[0].top, (int)pncc->rgrc[0].left);
             }
         }
             // "When wParam is TRUE, simply returning 0 without processing the NCCALCSIZE_PARAMS rectangles will cause
@@ -310,34 +331,9 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 
         // paint nothing on repaint
         case WM_PAINT: {
-            // variant 1 (apparently not the correct way of doing this?):
-            /*
-            ValidateRect(hwnd, NULL);
-            */
-
-            // variant 2 (seems to be what DefWindowProc is doing):
             PAINTSTRUCT ps;
             BeginPaint(hwnd, &ps);
             EndPaint(hwnd, &ps);
-
-            // debug:
-            /*
-            PAINTSTRUCT ps;
-            HDC hdc = BeginPaint(hwnd, &ps);
-
-            RECT wr;
-            GetClientRect(hwnd, &wr);
-            HBRUSH br;
-            br = (HBRUSH)GetStockObject(BLACK_BRUSH);
-            FillRect(hdc, &wr, br);
-
-            ///br = (HBRUSH)GetStockObject(GRAY_BRUSH);
-            ///wr.right = 100;
-            ///wr.bottom = 100;
-            ///FillRect(hdc, &wr, br);
-
-            EndPaint(hwnd,&ps);
-            */
         }
             return 0;
 
@@ -1104,6 +1100,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
 
     if(g_bHasFocus) g_engine->onFocusGained();
 
+    DragAcceptFiles(hwnd, TRUE);
+
     frameTimer->update();
     deltaTimer->update();
 

+ 5 - 3
src/Engine/Platform/WinEnvironment.cpp

@@ -378,13 +378,15 @@ std::string WinEnvironment::getFileExtensionFromFilePath(std::string filepath, b
 }
 
 std::string WinEnvironment::getFileNameFromFilePath(std::string filePath) {
-    // TODO: use PathStripPath
     if(filePath.length() < 1) return filePath;
 
     const size_t lastSlashIndex = filePath.find_last_of('/');
-    if(lastSlashIndex != std::string::npos) return filePath.substr(lastSlashIndex + 1);
+    const size_t lastBackSlashIndex = filePath.find_last_of('\\');
+    size_t idx = 0;
+    if(lastSlashIndex != std::string::npos) idx = lastSlashIndex + 1;
+    if(lastBackSlashIndex != std::string::npos) idx = max(idx, lastBackSlashIndex + 1);
 
-    return filePath;
+    return filePath.substr(idx);
 }
 
 void WinEnvironment::showMessageInfo(UString title, UString message) {