Browse Source

Add local skin folder + import via dropping on window

kiwec 2 months ago
parent
commit
5cb0d60752

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

@@ -34,6 +34,8 @@ Changelog::Changelog() : ScreenBackable() {
 
     CHANGELOG v35_07;
     v35_07.title = "35.07 (2024-06-27)";
+    v35_07.changes.push_back("- Added ability to drag-and-drop skins onto neosu");
+    v35_07.changes.push_back("- Added local skin folder");
     v35_07.changes.push_back("- Added sort_skins_by_name convar");
     v35_07.changes.push_back("- Added setting to prevent servers from replacing the main menu logo");
     v35_07.changes.push_back("- Chat: added missing chat commands");

+ 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;

+ 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

+ 46 - 82
src/Engine/Main/main_Windows.cpp

@@ -4,8 +4,13 @@
 // Include order matters
 #include "cbase.h"
 #include <dwmapi.h>
+#include <shellapi.h>
 // clang-format on
 
+#include "OptionsMenu.h"
+#include "Osu.h"
+#include "Skin.h"
+
 // NEXTRAWINPUTBLOCK macro requires this
 typedef uint64_t QWORD;
 
@@ -154,15 +159,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 +168,52 @@ 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("osk") || !extension.compare("zip")) {
+                    Skin::unpack(utf8filepath.c_str());
+                    if(first_skin.length() == 0) {
+                        first_skin = utf8filepath;
+                    }
+                }
+            }
 
-            // ignore
-            /*
-            case WM_ERASEBKGND:
-                    return 1;
-            */
+            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;
+        }
 
-            // 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);
-                    }
-                    /// 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 +228,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 +297,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 +1066,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) {