Browse Source

Fix skins with UTF-8 characters failing to open on Windows

kiwec 2 months ago
parent
commit
946342102e

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

@@ -30,8 +30,9 @@ Changelog::Changelog() : ScreenBackable() {
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
     latest.changes.push_back("- Changed \"Open Skins folder\" button to open the currently selected skin's folder");
-    latest.changes.push_back("- Fixed sliderslide and spinnerspin sounds not looping");
     latest.changes.push_back("- Fixed screenshots failing to save");
+    latest.changes.push_back("- Fixed skins with non-ANSI folder names failing to open on Windows");
+    latest.changes.push_back("- Fixed sliderslide and spinnerspin sounds not looping");
     latest.changes.push_back("- Improved sound engine reliability");
     latest.changes.push_back("- Re-added strain graphs");
     latest.changes.push_back("- Removed sliderhead fadeout animation (set osu_slider_sliderhead_fadeout to 1 for old behavior)");

+ 2 - 5
src/App/Osu/OptionsMenu.cpp

@@ -2421,12 +2421,9 @@ void OptionsMenu::onSkinSelect() {
         if(defaultText == m_osu_skin_ref->getString()) buttonDefault->setTextBrightColor(0xff00ff00);
 
         for(int i = 0; i < skinFolders.size(); i++) {
-            if(skinFolders[i].compare(".") == 0 ||
-               skinFolders[i].compare("..") == 0)  // is this universal in every file system? too lazy to check. should
-                                                   // probably fix this in the engine and not here
-                continue;
+            if(skinFolders[i].compare(".") == 0 || skinFolders[i].compare("..") == 0) continue;
 
-            CBaseUIButton *button = m_contextMenu->addButton(UString(skinFolders[i].c_str()));
+            CBaseUIButton *button = m_contextMenu->addButton(skinFolders[i].c_str());
             auto osu_skin = m_osu_skin_ref->getString();
             if(skinFolders[i].compare(osu_skin.toUtf8()) == 0) button->setTextBrightColor(0xff00ff00);
         }

+ 4 - 10
src/Engine/Platform/File.cpp

@@ -1,9 +1,4 @@
-//================ Copyright (c) 2016, PG, All rights reserved. =================//
-//
-// Purpose:		file wrapper, for cross-platform unicode path support
-//
-// $NoKeywords: $file
-//===============================================================================//
+#include <filesystem>
 
 #include "File.h"
 
@@ -49,7 +44,7 @@ StdFile::StdFile(std::string filePath, File::TYPE type) {
     m_iFileSize = 0;
 
     if(m_bRead) {
-        m_ifstream.open(filePath.c_str(), std::ios::in | std::ios::binary);
+        m_ifstream.open(std::filesystem::u8path(filePath.c_str()), std::ios::in | std::ios::binary);
 
         // check if we could open it at all
         if(!m_ifstream.good()) {
@@ -82,9 +77,8 @@ StdFile::StdFile(std::string filePath, File::TYPE type) {
         }
         m_ifstream.clear();  // clear potential error state due to the check above
         m_ifstream.seekg(0, std::ios::beg);
-    } else  // WRITE
-    {
-        m_ofstream.open(filePath.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
+    } else {  // WRITE
+        m_ofstream.open(std::filesystem::u8path(filePath.c_str()), std::ios::out | std::ios::trunc | std::ios::binary);
 
         // check if we could open it at all
         if(!m_ofstream.good()) {

+ 58 - 41
src/Engine/Platform/WinEnvironment.cpp

@@ -10,6 +10,7 @@
 #include <Commdlg.h>
 #include <shellapi.h>
 
+#include <filesystem>
 #include <string>
 
 #include "ConVar.h"
@@ -116,7 +117,7 @@ bool WinEnvironment::fileExists(std::string filename) {
     WIN32_FIND_DATA FindFileData;
     HANDLE handle = FindFirstFile(filename.c_str(), &FindFileData);
     if(handle == INVALID_HANDLE_VALUE)
-        return std::ifstream(filename.c_str()).good();
+        return std::ifstream(std::filesystem::u8path(filename.c_str())).good();
     else {
         FindClose(handle);
         return true;
@@ -246,33 +247,41 @@ UString WinEnvironment::openFolderWindow(UString title, UString initialpath) {
 }
 
 std::vector<std::string> WinEnvironment::getFilesInFolder(std::string folder) {
+    // Since we want to avoid wide strings in the codebase as much as possible,
+    // we convert wide paths to UTF-8 (as they fucking should be).
+    // We can't just use FindFirstFileA, because then any path with unicode
+    // characters will fail to open!
+    // Keep in mind that windows can't handle the way too modern 1993 UTF-8, so
+    // you have to use std::filesystem::u8path() or convert it back to a wstring
+    // before using the windows API.
+
     folder.append("*.*");
-    WIN32_FIND_DATA data;
-    std::string buffer;
+    WIN32_FIND_DATAW data;
+    std::wstring buffer;
     std::vector<std::string> files;
 
-    HANDLE handle = FindFirstFile(folder.c_str(), &data);
+    int size = MultiByteToWideChar(CP_UTF8, 0, folder.c_str(), folder.length(), NULL, 0);
+    std::wstring wfile(size, 0);
+    MultiByteToWideChar(CP_UTF8, 0, folder.c_str(), folder.length(), (LPWSTR)wfile.c_str(), wfile.length());
+
+    HANDLE handle = FindFirstFileW(wfile.c_str(), &data);
 
     while(true) {
-        std::string filename(data.cFileName);
-
-        if(filename != buffer) {
-            buffer = filename;
-
-            if(filename.length() > 0) {
-                if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)  // directory
-                {
-                    /// if (filename.length() > 0)
-                    ///	folders.push_back(filename.c_str());
-                } else  // file
-                {
-                    if(filename.length() > 0) files.push_back(filename.c_str());
-                }
+        std::wstring filename(data.cFileName);
+        if(filename == buffer) break;
+
+        buffer = filename;
+
+        if(filename.length() > 0) {
+            if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+                int size = WideCharToMultiByte(CP_UTF8, 0, filename.c_str(), filename.length(), NULL, 0, NULL, NULL);
+                std::string utf8filename(size, 0);
+                WideCharToMultiByte(CP_UTF8, 0, filename.c_str(), size, (LPSTR)utf8filename.c_str(), size, NULL, NULL);
+                files.push_back(utf8filename);
             }
+        }
 
-            FindNextFile(handle, &data);
-        } else
-            break;
+        FindNextFileW(handle, &data);
     }
 
     FindClose(handle);
@@ -280,33 +289,41 @@ std::vector<std::string> WinEnvironment::getFilesInFolder(std::string folder) {
 }
 
 std::vector<std::string> WinEnvironment::getFoldersInFolder(std::string folder) {
+    // Since we want to avoid wide strings in the codebase as much as possible,
+    // we convert wide paths to UTF-8 (as they fucking should be).
+    // We can't just use FindFirstFileA, because then any path with unicode
+    // characters will fail to open!
+    // Keep in mind that windows can't handle the way too modern 1993 UTF-8, so
+    // you have to use std::filesystem::u8path() or convert it back to a wstring
+    // before using the windows API.
+
     folder.append("*.*");
-    WIN32_FIND_DATA data;
-    std::string buffer;
+    WIN32_FIND_DATAW data;
+    std::wstring buffer;
     std::vector<std::string> folders;
 
-    HANDLE handle = FindFirstFile(folder.c_str(), &data);
+	int size = MultiByteToWideChar(CP_UTF8, 0, folder.c_str(), folder.length(), NULL, 0);
+    std::wstring wfolder(size, 0);
+    MultiByteToWideChar(CP_UTF8, 0, folder.c_str(), folder.length(), (LPWSTR)wfolder.c_str(), wfolder.length());
+
+    HANDLE handle = FindFirstFileW(wfolder.c_str(), &data);
 
     while(true) {
-        std::string filename(data.cFileName);
-
-        if(filename != buffer) {
-            buffer = filename;
-
-            if(filename.length() > 0) {
-                if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)  // directory
-                {
-                    if(filename.length() > 0) folders.push_back(filename.c_str());
-                } else  // file
-                {
-                    /// if (filename.length() > 0)
-                    ///	files.push_back(filename.c_str());
-                }
+        std::wstring filename(data.cFileName);
+        if(filename == buffer) break;
+
+        buffer = filename;
+
+        if(filename.length() > 0) {
+            if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+                int size = WideCharToMultiByte(CP_UTF8, 0, filename.c_str(), filename.length(), NULL, 0, NULL, NULL);
+                std::string utf8filename(size, 0);
+                WideCharToMultiByte(CP_UTF8, 0, filename.c_str(), size, (LPSTR)utf8filename.c_str(), size, NULL, NULL);
+                folders.push_back(utf8filename);
             }
+        }
 
-            FindNextFile(handle, &data);
-        } else
-            break;
+        FindNextFileW(handle, &data);
     }
 
     FindClose(handle);

+ 15 - 2
src/Engine/Sound.cpp

@@ -105,11 +105,21 @@ void Sound::initAsync() {
         }
     }
 
+#ifdef _WIN32
+    // On Windows, we need to convert the UTF-8 path to UTF-16, or paths with unicode characters will fail to open
+    int size = MultiByteToWideChar(CP_UTF8, 0, m_sFilePath.c_str(), m_sFilePath.length(), NULL, 0);
+    std::wstring file_path(size, 0);
+    MultiByteToWideChar(CP_UTF8, 0, m_sFilePath.c_str(), m_sFilePath.length(), (LPWSTR)file_path.c_str(), file_path.length());
+#else
+    std::string file_path = m_sFilePath;
+#endif
+
     if(m_bStream) {
         auto flags = BASS_STREAM_DECODE | BASS_SAMPLE_FLOAT;
         if(m_bPrescan) flags |= BASS_STREAM_PRESCAN;
+        if(env->getOS() == Environment::OS::WINDOWS) flags |= BASS_UNICODE;
 
-        m_stream = BASS_StreamCreateFile(false, m_sFilePath.c_str(), 0, 0, flags);
+        m_stream = BASS_StreamCreateFile(false, file_path.c_str(), 0, 0, flags);
         if(!m_stream) {
             debugLog("BASS_StreamCreateFile() returned error %d on file %s\n", BASS_ErrorGetCode(),
                      m_sFilePath.c_str());
@@ -128,7 +138,10 @@ void Sound::initAsync() {
         f64 lengthInMilliSeconds = lengthInSeconds * 1000.0;
         m_length = (u32)lengthInMilliSeconds;
     } else {
-        m_sample = BASS_SampleLoad(false, m_sFilePath.c_str(), 0, 0, 1, BASS_SAMPLE_FLOAT);
+        auto flags = BASS_SAMPLE_FLOAT;
+        if(env->getOS() == Environment::OS::WINDOWS) flags |= BASS_UNICODE;
+
+        m_sample = BASS_SampleLoad(false, file_path.c_str(), 0, 0, 1, flags);
         if(!m_sample) {
             debugLog("BASS_SampleLoad() returned error %d on file %s\n", BASS_ErrorGetCode(), m_sFilePath.c_str());
             return;