diff --git a/include/mgba-util/string.h b/include/mgba-util/string.h index 476b39848..de7de640f 100644 --- a/include/mgba-util/string.h +++ b/include/mgba-util/string.h @@ -33,7 +33,9 @@ int utfcmp(const uint16_t* utf16, const char* utf8, size_t utf16Length, size_t u char* utf16to8(const uint16_t* utf16, size_t length); uint32_t utf8Char(const char** unicode, size_t* length); uint32_t utf16Char(const uint16_t** unicode, size_t* length); +char* latin1ToUtf8(const char* latin1, size_t length); char* gbkToUtf8(const char* gbk, size_t length); +size_t utf8strlen(const char* string); int hexDigit(char digit); const char* hex32(const char* line, uint32_t* out); diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 59d75ebea..d2e356921 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -107,6 +107,7 @@ struct mCore { void (*setKeys)(struct mCore*, uint32_t keys); void (*addKeys)(struct mCore*, uint32_t keys); void (*clearKeys)(struct mCore*, uint32_t keys); + uint32_t (*getKeys)(struct mCore*); void (*setCursorLocation)(struct mCore*, int x, int y); void (*setCursorDown)(struct mCore*, bool down); diff --git a/src/ds/core.c b/src/ds/core.c index 6fddeea31..e5aa99e78 100644 --- a/src/ds/core.c +++ b/src/ds/core.c @@ -445,6 +445,11 @@ static void _DSCoreClearKeys(struct mCore* core, uint32_t keys) { dscore->keys &= ~keys; } +static uint32_t _DSCoreGetKeys(struct mCore* core) { + struct DSCore* dscore = (struct DSCore*) core; + return dscore->keys; +} + static void _DSCoreSetCursorLocation(struct mCore* core, int x, int y) { struct DSCore* dscore = (struct DSCore*) core; dscore->cursorX = x; @@ -735,6 +740,7 @@ struct mCore* DSCoreCreate(void) { core->setKeys = _DSCoreSetKeys; core->addKeys = _DSCoreAddKeys; core->clearKeys = _DSCoreClearKeys; + core->getKeys = _DSCoreGetKeys; core->setCursorLocation = _DSCoreSetCursorLocation; core->setCursorDown = _DSCoreSetCursorDown; core->frameCounter = _DSCoreFrameCounter; diff --git a/src/gb/core.c b/src/gb/core.c index 932f89ae7..4317f01a6 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -700,6 +700,11 @@ static void _GBCoreClearKeys(struct mCore* core, uint32_t keys) { gbcore->keys &= ~keys; } +static uint32_t _GBCoreGetKeys(struct mCore* core) { + struct GBCore* gbcore = (struct GBCore*) core; + return gbcore->keys; +} + static void _GBCoreSetCursorLocation(struct mCore* core, int x, int y) { UNUSED(core); UNUSED(x); @@ -1114,6 +1119,7 @@ struct mCore* GBCoreCreate(void) { core->setKeys = _GBCoreSetKeys; core->addKeys = _GBCoreAddKeys; core->clearKeys = _GBCoreClearKeys; + core->getKeys = _GBCoreGetKeys; core->setCursorLocation = _GBCoreSetCursorLocation; core->setCursorDown = _GBCoreSetCursorDown; core->frameCounter = _GBCoreFrameCounter; diff --git a/src/gb/gb.c b/src/gb/gb.c index 0582fff70..edcd548d1 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -229,6 +229,9 @@ static void GBSramDeinit(struct GB* gb) { bool GBLoadSave(struct GB* gb, struct VFile* vf) { GBSramDeinit(gb); gb->sramVf = vf; + if (gb->sramRealVf && gb->sramRealVf != vf) { + gb->sramRealVf->close(gb->sramRealVf); + } gb->sramRealVf = vf; if (gb->sramSize) { GBResizeSram(gb, gb->sramSize); diff --git a/src/gba/core.c b/src/gba/core.c index 4e45cdfb0..c0b495d76 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -734,6 +734,11 @@ static void _GBACoreClearKeys(struct mCore* core, uint32_t keys) { GBATestKeypadIRQ(gba); } +static uint32_t _GBACoreGetKeys(struct mCore* core) { + struct GBA* gba = core->board; + return gba->keysActive; +} + static void _GBACoreSetCursorLocation(struct mCore* core, int x, int y) { UNUSED(core); UNUSED(x); @@ -1231,6 +1236,7 @@ struct mCore* GBACoreCreate(void) { core->setKeys = _GBACoreSetKeys; core->addKeys = _GBACoreAddKeys; core->clearKeys = _GBACoreClearKeys; + core->getKeys = _GBACoreGetKeys; core->setCursorLocation = _GBACoreSetCursorLocation; core->setCursorDown = _GBACoreSetCursorDown; core->frameCounter = _GBACoreFrameCounter; diff --git a/src/gba/gba.c b/src/gba/gba.c index 495196b72..26844be0a 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -75,6 +75,8 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) { GBAMemoryInit(gba); gba->memory.savedata.timing = &gba->timing; + gba->memory.savedata.vf = NULL; + gba->memory.savedata.realVf = NULL; GBASavedataInit(&gba->memory.savedata, NULL); gba->video.p = gba; diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 182a6cc30..752d3874c 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -45,6 +45,9 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) { savedata->command = EEPROM_COMMAND_NULL; savedata->flashState = FLASH_STATE_RAW; savedata->vf = vf; + if (savedata->realVf && savedata->realVf != vf) { + savedata->realVf->close(savedata->realVf); + } savedata->realVf = vf; savedata->mapMode = MAP_WRITE; savedata->maskWriteback = false; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 5a2d61fb7..c93277282 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -24,14 +24,17 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/input) +set(QT_LIBRARIES) + find_package(Qt5 COMPONENTS Core Widgets Network Multimedia) +set(QT Qt5) if(NOT BUILD_GL AND NOT BUILD_GLES2 AND NOT BUILD_GLES3) message(WARNING "OpenGL is recommended to build the Qt port") endif() -set(FOUND_QT ${Qt5Widgets_FOUND} PARENT_SCOPE) -if(NOT Qt5Widgets_FOUND) +set(FOUND_QT ${${QT}Widgets_FOUND} PARENT_SCOPE) +if(NOT ${QT}Widgets_FOUND) message(WARNING "Cannot find Qt modules") return() endif() @@ -42,7 +45,9 @@ if(APPLE) list(APPEND QT_DEFINES USE_SHARE_WIDGET) endif() - if(Qt5Widgets_VERSION MATCHES "^5.15") + if(Qt6Widgets_VERSION) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.14") + elseif(Qt5Widgets_VERSION MATCHES "^5.15") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.13") elseif(Qt5Widgets_VERSION MATCHES "^5.1[234]") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.12") @@ -64,7 +69,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") endif() -get_target_property(QT_TYPE Qt5::Core TYPE) +get_target_property(QT_TYPE ${QT}::Core TYPE) if(QT_TYPE STREQUAL STATIC_LIBRARY) set(QT_STATIC ON) list(APPEND QT_DEFINES QT_STATIC) @@ -184,7 +189,6 @@ set(GB_SRC GBOverride.cpp PrinterView.cpp) -set(QT_LIBRARIES) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5") set(AUDIO_SRC) @@ -200,17 +204,17 @@ if(M_CORE_GB) list(APPEND PLATFORM_SRC ${GB_SRC}) endif() -if(Qt5Multimedia_FOUND) +if(${QT}Multimedia_FOUND) list(APPEND AUDIO_SRC AudioProcessorQt.cpp AudioDevice.cpp) list(APPEND SOURCE_FILES VideoDumper.cpp) if (WIN32 AND QT_STATIC) - list(APPEND QT_LIBRARIES Qt5::QWindowsAudioPlugin Qt5::DSServicePlugin Qt5::QWindowsVistaStylePlugin + list(APPEND QT_LIBRARIES ${QT}::QWindowsAudioPlugin ${QT}::DSServicePlugin ${QT}::QWindowsVistaStylePlugin strmiids mfuuid mfplat mf ksguid dxva2 evr d3d9) endif() - list(APPEND QT_LIBRARIES Qt5::Multimedia) + list(APPEND QT_LIBRARIES ${QT}::Multimedia) list(APPEND QT_DEFINES BUILD_QT_MULTIMEDIA) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5") endif() @@ -245,10 +249,18 @@ if(USE_DISCORD_RPC) list(APPEND SOURCE_FILES DiscordCoordinator.cpp) endif() -qt5_add_resources(RESOURCES resources.qrc) +if(TARGET Qt6::Core) + qt_add_resources(RESOURCES resources.qrc) +else() + qt5_add_resources(RESOURCES resources.qrc) +endif() if(BUILD_UPDATER) file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in) - qt5_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) + if(TARGET Qt6::Core) + qt_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) + else() + qt5_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc) + endif() set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/updater.qrc PROPERTIES GENERATED ON) list(APPEND RESOURCES ${UPDATER_RESOURCES}) endif() @@ -290,21 +302,29 @@ if(NOT WIN32 AND NOT APPLE) endif() endif() -find_package(Qt5LinguistTools) -if(Qt5LinguistTools_FOUND) +find_package(${QT}LinguistTools) +if(${QT}LinguistTools_FOUND) set(TRANSLATION_FILES) set(TRANSLATION_QRC "${CMAKE_CURRENT_BINARY_DIR}/ts.qrc") file(GLOB TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ts/${BINARY_NAME}-*.ts") if(UPDATE_TRANSLATIONS) - qt5_create_translation(TRANSLATION_FILES ${SOURCE_FILES} ${UI_FILES} ${TS_FILES} OPTIONS -locations absolute -no-obsolete) + if(TARGET Qt6::Core) + qt_create_translation(TRANSLATION_FILES ${SOURCE_FILES} ${UI_FILES} ${TS_FILES} OPTIONS -locations absolute -no-obsolete) + else() + qt5_create_translation(TRANSLATION_FILES ${SOURCE_FILES} ${UI_FILES} ${TS_FILES} OPTIONS -locations absolute -no-obsolete) + endif() list(REMOVE_ITEM TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ts/${BINARY_NAME}-template.ts") else() list(REMOVE_ITEM TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ts/${BINARY_NAME}-template.ts") - qt5_add_translation(TRANSLATION_FILES ${TS_FILES}) + if(TARGET Qt6::Core) + qt_add_translation(TRANSLATION_FILES ${TS_FILES}) + else() + qt5_add_translation(TRANSLATION_FILES ${TS_FILES}) + endif() endif() set(QT_QM_FILES) if(QT_STATIC) - get_target_property(QT_CORE_LOCATION Qt5::Core LOCATION) + get_target_property(QT_CORE_LOCATION ${QT}::Core LOCATION) get_filename_component(QT_CORE_LOCATION ${QT_CORE_LOCATION} DIRECTORY) get_filename_component(QT_QM_LOCATION "${QT_CORE_LOCATION}/../translations" ABSOLUTE) foreach(TS ${TS_FILES}) @@ -320,11 +340,19 @@ if(Qt5LinguistTools_FOUND) add_custom_command(OUTPUT ${TRANSLATION_QRC} COMMAND ${CMAKE_COMMAND} -DTRANSLATION_QRC:FILEPATH="${TRANSLATION_QRC}" -DQM_BASE="${CMAKE_CURRENT_BINARY_DIR}" "-DTRANSLATION_FILES='${TRANSLATION_FILES}'" -P "${CMAKE_CURRENT_SOURCE_DIR}/ts.cmake" DEPENDS ${TRANSLATION_FILES}) - qt5_add_resources(TRANSLATION_RESOURCES ${TRANSLATION_QRC}) + if(TARGET Qt6::Core) + qt_add_resources(TRANSLATION_RESOURCES ${TRANSLATION_QRC}) + else() + qt5_add_resources(TRANSLATION_RESOURCES ${TRANSLATION_QRC}) + endif() list(APPEND RESOURCES ${TRANSLATION_RESOURCES}) endif() -qt5_wrap_ui(UI_SRC ${UI_FILES}) +if(TARGET Qt6::Core) + qt_wrap_ui(UI_SRC ${UI_FILES}) +else() + qt5_wrap_ui(UI_SRC ${UI_FILES}) +endif() add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/icon.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_SRC} ${AUDIO_SRC} ${RESOURCES}) set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}" COMPILE_OPTIONS "${FEATURE_FLAGS}") @@ -336,7 +364,7 @@ if(WIN32) endif() endif() -list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::Network) +list(APPEND QT_LIBRARIES ${QT}::Widgets ${QT}::Network) if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY) list(APPEND QT_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) endif() @@ -344,20 +372,20 @@ if(QT_STATIC) find_library(QTPCRE NAMES qtpcre2 qtpcre) if(WIN32) if(CMAKE_CROSSCOMPILING) - set(QWINDOWS_DEPS Qt5EventDispatcherSupport Qt5FontDatabaseSupport Qt5ThemeSupport Qt5WindowsUIAutomationSupport) + set(QWINDOWS_DEPS ${QT}EventDispatcherSupport ${QT}FontDatabaseSupport ${QT}ThemeSupport ${QT}WindowsUIAutomationSupport) endif() - list(APPEND QT_LIBRARIES Qt5::QWindowsIntegrationPlugin ${QWINDOWS_DEPS} amstrmid dwmapi uxtheme imm32 -static-libgcc -static-libstdc++) - set_target_properties(Qt5::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32") - set_target_properties(Qt5::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) + list(APPEND QT_LIBRARIES ${QT}::QWindowsIntegrationPlugin ${QWINDOWS_DEPS} amstrmid dwmapi uxtheme imm32 -static-libgcc -static-libstdc++) + set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32") + set_target_properties(${QT}::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) elseif(APPLE) find_package(Cups) - find_package(Qt5PrintSupport) - list(APPEND QT_LIBRARIES Cups Qt5::PrintSupport Qt5::QCocoaIntegrationPlugin Qt5::CoreAudioPlugin Qt5::AVFServicePlugin Qt5::QCocoaPrinterSupportPlugin) - list(APPEND QT_LIBRARIES Qt5AccessibilitySupport Qt5CglSupport Qt5ClipboardSupport Qt5FontDatabaseSupport Qt5GraphicsSupport Qt5ThemeSupport) + find_package(${QT}PrintSupport) + list(APPEND QT_LIBRARIES Cups ${QT}::PrintSupport ${QT}::QCocoaIntegrationPlugin ${QT}::CoreAudioPlugin ${QT}::AVFServicePlugin ${QT}::QCocoaPrinterSupportPlugin) + list(APPEND QT_LIBRARIES ${QT}AccessibilitySupport ${QT}CglSupport ${QT}ClipboardSupport ${QT}FontDatabaseSupport ${QT}GraphicsSupport ${QT}ThemeSupport) list(APPEND QT_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework SystemConfiguration" "-framework Security") - set_target_properties(Qt5::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}") + set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}") elseif(UNIX) - list(APPEND QT_LIBRARIES Qt5::FontDatabaseSupport Qt5::XcbQpa) + list(APPEND QT_LIBRARIES ${QT}::FontDatabaseSupport ${QT}::XcbQpa) endif() endif() target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) @@ -377,9 +405,9 @@ if(APPLE OR WIN32) endif() if(APPLE) if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") - get_target_property(QTCOCOA Qt5::QCocoaIntegrationPlugin LOCATION) - get_target_property(COREAUDIO Qt5::CoreAudioPlugin LOCATION) - get_target_property(QTAVFSERVICE Qt5::AVFServicePlugin LOCATION) + get_target_property(QTCOCOA ${QT}::QCocoaIntegrationPlugin LOCATION) + get_target_property(COREAUDIO ${QT}::CoreAudioPlugin LOCATION) + get_target_property(QTAVFSERVICE ${QT}::AVFServicePlugin LOCATION) set(BUNDLE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.app) target_sources(${BINARY_NAME}-qt PRIVATE "${PLUGINS}") set_source_files_properties("${QTCOCOA}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns) diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 500e5b888..c84766b7e 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -88,8 +88,8 @@ void DisplayQt::resizeContext() { if (m_width != size.width() || m_height != size.height()) { m_width = size.width(); m_height = size.height(); - m_oldBacking = std::move(QImage()); - m_backing = std::move(QImage()); + m_oldBacking = QImage(); + m_backing = QImage(); } } diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index a563a4c42..d39f3a4a8 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -61,7 +61,6 @@ public: QFont monospaceFont() { return m_monospace; } QList windows() { return m_windows; } - Window* newWindow(); QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {}); QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = {}); @@ -81,6 +80,7 @@ public: public slots: void restartForUpdate(); + Window* newWindow(); signals: void jobFinished(qint64 jobId); diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index 8b11e76e4..d60771073 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/platform/qt/LogController.cpp @@ -13,8 +13,6 @@ using namespace QGBA; #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) #define endl Qt::endl -#else -#define endl std::endl #endif LogController LogController::s_global(mLOG_ALL); diff --git a/src/platform/qt/MemoryModel.cpp b/src/platform/qt/MemoryModel.cpp index 4c991982d..1777fddd0 100644 --- a/src/platform/qt/MemoryModel.cpp +++ b/src/platform/qt/MemoryModel.cpp @@ -678,7 +678,7 @@ void MemoryModel::adjustCursor(int adjust, bool shift) { } int cursorPosition = m_top; if (shift) { - uint32_t absolute; + uint32_t absolute = adjust; if (m_selectionAnchor == m_selection.first) { if (adjust < 0 && m_base - adjust > m_selection.second) { absolute = m_base - m_selection.second + m_align; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index bdfd5b903..a21e21bf6 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1093,7 +1093,7 @@ void Window::reloadDisplayDriver() { m_display->stopDrawing(); detachWidget(m_display.get()); } - m_display = std::unique_ptr(Display::create(this)); + m_display = std::unique_ptr(Display::create(this)); if (!m_display) { LOG(QT, ERROR) << tr("Failed to create an appropriate display device, falling back to software display. " "Games may run slowly, especially with larger windows."); @@ -1105,12 +1105,12 @@ void Window::reloadDisplayDriver() { m_shaderView = std::make_unique(m_display.get(), m_config); #endif - connect(m_display.get(), &Display::hideCursor, [this]() { + connect(m_display.get(), &QGBA::Display::hideCursor, [this]() { if (static_cast(m_screenWidget->layout())->currentWidget() == m_display.get()) { m_screenWidget->setCursor(Qt::BlankCursor); } }); - connect(m_display.get(), &Display::showCursor, [this]() { + connect(m_display.get(), &QGBA::Display::showCursor, [this]() { m_screenWidget->unsetCursor(); }); @@ -1459,9 +1459,7 @@ void Window::setupMenu(QMenuBar* menubar) { } m_actions.addSeparator("file"); - m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", [this]() { - GBAApp::app()->newWindow(); - }, "file"); + m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", GBAApp::app(), &GBAApp::newWindow, "file"); #ifdef M_CORE_GBA Action* dolphin = m_actions.addAction(tr("Connect to Dolphin..."), "connectDolphin", openNamedTView(&m_dolphinView, this), "file"); @@ -1973,7 +1971,7 @@ Action* Window::addGameAction(const QString& visibleName, const QString& name, A template Action* Window::addGameAction(const QString& visibleName, const QString& name, T* obj, V (T::*method)(), const QString& menu, const QKeySequence& shortcut) { - return addGameAction(visibleName, name, [this, obj, method]() { + return addGameAction(visibleName, name, [obj, method]() { (obj->*method)(); }, menu, shortcut); } @@ -2161,7 +2159,7 @@ void Window::setController(CoreController* controller, const QString& fname) { void Window::attachDisplay() { m_display->attach(m_controller); - connect(m_display.get(), &Display::drawingStarted, this, &Window::changeRenderer); + connect(m_display.get(), &QGBA::Display::drawingStarted, this, &Window::changeRenderer); m_display->startDrawing(m_controller); } diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index dd3dc8cda..d43f901f7 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -28,6 +28,8 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/string-parser.c + test/string-utf8.c test/text-codec.c test/vfs.c) diff --git a/src/util/configuration.c b/src/util/configuration.c index 4c7805580..dbcefef8f 100644 --- a/src/util/configuration.c +++ b/src/util/configuration.c @@ -183,7 +183,7 @@ bool ConfigurationWrite(const struct Configuration* configuration, const char* p } bool res = ConfigurationWriteVFile(configuration, vf); vf->close(vf); - return true; + return res; } bool ConfigurationWriteVFile(const struct Configuration* configuration, struct VFile* vf) { diff --git a/src/util/string.c b/src/util/string.c index d9dc701b1..fdb5305d4 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -108,11 +108,30 @@ uint32_t utf16Char(const uint16_t** unicode, size_t* length) { return (highSurrogate << 10) + lowSurrogate + 0x10000; } +static const uint8_t _utf8len[0x40] = { + /* 0000 xxxx */ 1, 1, 1, 1, + /* 0001 xxxx */ 1, 1, 1, 1, + /* 0010 xxxx */ 1, 1, 1, 1, + /* 0011 xxxx */ 1, 1, 1, 1, + /* 0100 xxxx */ 1, 1, 1, 1, + /* 0101 xxxx */ 1, 1, 1, 1, + /* 0110 xxxx */ 1, 1, 1, 1, + /* 0111 xxxx */ 1, 1, 1, 1, + /* 1000 xxxx */ 0, 0, 0, 0, + /* 1001 xxxx */ 0, 0, 0, 0, + /* 1010 xxxx */ 0, 0, 0, 0, + /* 1011 xxxx */ 0, 0, 0, 0, + /* 1100 xxxx */ 2, 2, 2, 2, + /* 1101 xxxx */ 2, 2, 2, 2, + /* 1110 xxxx */ 3, 3, 3, 3, + /* 1111 xxxx */ 4, 4, 0, 0 +}; + uint32_t utf8Char(const char** unicode, size_t* length) { if (*length == 0) { return 0; } - char byte = **unicode; + unsigned char byte = **unicode; --*length; ++*unicode; if (!(byte & 0x80)) { @@ -120,23 +139,17 @@ uint32_t utf8Char(const char** unicode, size_t* length) { } uint32_t unichar; static const int tops[4] = { 0xC0, 0xE0, 0xF0, 0xF8 }; - size_t numBytes; - for (numBytes = 0; numBytes < 3; ++numBytes) { - if ((byte & tops[numBytes + 1]) == tops[numBytes]) { - break; - } + size_t numBytes = _utf8len[byte >> 2]; + unichar = byte & ~tops[numBytes - 1]; + if (numBytes == 0) { + return 0xFFFD; } - unichar = byte & ~tops[numBytes]; - if (numBytes == 3) { - return 0; - } - ++numBytes; if (*length < numBytes) { *length = 0; - return 0; + return 0xFFFD; } size_t i; - for (i = 0; i < numBytes; ++i) { + for (i = 1; i < numBytes; ++i) { unichar <<= 6; byte = **unicode; --*length; @@ -272,6 +285,54 @@ char* utf16to8(const uint16_t* utf16, size_t length) { return newUTF8; } +char* latin1ToUtf8(const char* latin1, size_t length) { + char* utf8 = NULL; + char* utf8Offset = NULL; + size_t offset; + char buffer[4]; + size_t utf8TotalBytes = 0; + size_t utf8Length = 0; + for (offset = 0; offset < length; ++offset) { + if (length == 0) { + break; + } + uint8_t unichar = latin1[offset]; + size_t bytes = toUtf8(unichar, buffer); + utf8Length += bytes; + if (!utf8) { + utf8 = malloc(length); + if (!utf8) { + return NULL; + } + utf8TotalBytes = length; + memcpy(utf8, buffer, bytes); + utf8Offset = utf8 + bytes; + } else if (utf8Length < utf8TotalBytes) { + memcpy(utf8Offset, buffer, bytes); + utf8Offset += bytes; + } else if (utf8Length >= utf8TotalBytes) { + ptrdiff_t o = utf8Offset - utf8; + char* newUTF8 = realloc(utf8, utf8TotalBytes * 2); + utf8Offset = o + newUTF8; + if (!newUTF8) { + free(utf8); + return 0; + } + utf8 = newUTF8; + memcpy(utf8Offset, buffer, bytes); + utf8Offset += bytes; + } + } + + char* newUTF8 = realloc(utf8, utf8Length + 1); + if (!newUTF8) { + free(utf8); + return 0; + } + newUTF8[utf8Length] = '\0'; + return newUTF8; +} + extern const uint16_t gbkUnicodeTable[]; char* gbkToUtf8(const char* gbk, size_t length) { @@ -341,6 +402,29 @@ char* gbkToUtf8(const char* gbk, size_t length) { return newUTF8; } +size_t utf8strlen(const char* string) { + size_t size = 0; + for (size = 0; *string; ++size) { + size_t numBytes = 1; + if (*string & 0x80) { + numBytes = _utf8len[((uint8_t) *string) >> 2]; + if (!numBytes) { + numBytes = 1; + } else { + size_t i; + for (i = 1; i < numBytes; ++i) { + if ((string[i] & 0xC0) != 0x80) { + break; + } + } + numBytes = i; + } + } + string += numBytes; + } + return size; +} + int hexDigit(char digit) { switch (digit) { case '0': diff --git a/src/util/test/string-utf8.c b/src/util/test/string-utf8.c new file mode 100644 index 000000000..2f208538b --- /dev/null +++ b/src/util/test/string-utf8.c @@ -0,0 +1,132 @@ +/* Copyright (c) 2013-2022 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(strlenASCII) { + assert_int_equal(utf8strlen(""), 0); + assert_int_equal(utf8strlen("a"), 1); + assert_int_equal(utf8strlen("aa"), 2); + assert_int_equal(utf8strlen("aaa"), 3); +} + +M_TEST_DEFINE(strlenMultibyte) { + assert_int_equal(utf8strlen("\300\200"), 1); + assert_int_equal(utf8strlen("a\300\200"), 2); + assert_int_equal(utf8strlen("\300\200a"), 2); + assert_int_equal(utf8strlen("a\300\200a"), 3); + + assert_int_equal(utf8strlen("\300\200\300\200"), 2); + assert_int_equal(utf8strlen("a\300\200\300\200"), 3); + assert_int_equal(utf8strlen("\300\200a\300\200"), 3); + assert_int_equal(utf8strlen("\300\200\300\200a"), 3); + + assert_int_equal(utf8strlen("\340\200\200"), 1); + assert_int_equal(utf8strlen("a\340\200\200"), 2); + assert_int_equal(utf8strlen("\340\200\200a"), 2); + assert_int_equal(utf8strlen("a\340\200\200a"), 3); + + assert_int_equal(utf8strlen("\340\200\200\340\200\200"), 2); + assert_int_equal(utf8strlen("a\340\200\200\340\200\200"), 3); + assert_int_equal(utf8strlen("\340\200\200a\340\200\200"), 3); + assert_int_equal(utf8strlen("\340\200\200\340\200\200a"), 3); + + assert_int_equal(utf8strlen("\340\200\200\300\200"), 2); + assert_int_equal(utf8strlen("\300\200\340\200\200"), 2); + + assert_int_equal(utf8strlen("\360\200\200\200"), 1); + assert_int_equal(utf8strlen("a\360\200\200\200"), 2); + assert_int_equal(utf8strlen("\360\200\200\200a"), 2); + assert_int_equal(utf8strlen("a\360\200\200\200a"), 3); + + assert_int_equal(utf8strlen("\360\200\200\200\360\200\200\200"), 2); + assert_int_equal(utf8strlen("a\360\200\200\200\360\200\200\200"), 3); + assert_int_equal(utf8strlen("\360\200\200\200a\360\200\200\200"), 3); + assert_int_equal(utf8strlen("\360\200\200\200\360\200\200\200a"), 3); + + assert_int_equal(utf8strlen("\360\200\200\200\300\200"), 2); + assert_int_equal(utf8strlen("\300\200\360\200\200\200"), 2); + + assert_int_equal(utf8strlen("\360\200\200\200\340\200\200"), 2); + assert_int_equal(utf8strlen("\340\200\200\360\200\200\200"), 2); +} + +M_TEST_DEFINE(strlenDegenerate) { + assert_int_equal(utf8strlen("\200"), 1); + assert_int_equal(utf8strlen("\200a"), 2); + + assert_int_equal(utf8strlen("\300"), 1); + assert_int_equal(utf8strlen("\300a"), 2); + + assert_int_equal(utf8strlen("\300\300"), 2); + assert_int_equal(utf8strlen("\300\300a"), 3); + assert_int_equal(utf8strlen("\300\300\200"), 2); + + assert_int_equal(utf8strlen("\300\200\200"), 2); + assert_int_equal(utf8strlen("\300\200\200a"), 3); + + assert_int_equal(utf8strlen("\340"), 1); + assert_int_equal(utf8strlen("\340a"), 2); + assert_int_equal(utf8strlen("\340\300"), 2); + assert_int_equal(utf8strlen("\340\300a"), 3); + assert_int_equal(utf8strlen("\340\300\200"), 2); + assert_int_equal(utf8strlen("\340\200"), 1); + assert_int_equal(utf8strlen("\340\200a"), 2); + assert_int_equal(utf8strlen("\340\200\200\200"), 2); + assert_int_equal(utf8strlen("\340\200\200\200a"), 3); + + assert_int_equal(utf8strlen("\360"), 1); + assert_int_equal(utf8strlen("\360a"), 2); + assert_int_equal(utf8strlen("\360\300"), 2); + assert_int_equal(utf8strlen("\360\300a"), 3); + assert_int_equal(utf8strlen("\360\300\200"), 2); + assert_int_equal(utf8strlen("\360\200"), 1); + assert_int_equal(utf8strlen("\360\200a"), 2); + assert_int_equal(utf8strlen("\360\200\300"), 2); + assert_int_equal(utf8strlen("\360\200\300a"), 3); + assert_int_equal(utf8strlen("\360\200\300\200"), 2); + assert_int_equal(utf8strlen("\360\200\200"), 1); + assert_int_equal(utf8strlen("\360\200\200a"), 2); + + assert_int_equal(utf8strlen("\370"), 1); + assert_int_equal(utf8strlen("\370a"), 2); + assert_int_equal(utf8strlen("\370\200"), 2); + assert_int_equal(utf8strlen("\370\200a"), 3); + assert_int_equal(utf8strlen("\374"), 1); + assert_int_equal(utf8strlen("\374a"), 2); + assert_int_equal(utf8strlen("\374\200"), 2); + assert_int_equal(utf8strlen("\374\200a"), 3); + assert_int_equal(utf8strlen("\376"), 1); + assert_int_equal(utf8strlen("\376a"), 2); + assert_int_equal(utf8strlen("\376\200"), 2); + assert_int_equal(utf8strlen("\376\200a"), 3); + assert_int_equal(utf8strlen("\377"), 1); + assert_int_equal(utf8strlen("\377a"), 2); + assert_int_equal(utf8strlen("\377\200"), 2); + assert_int_equal(utf8strlen("\377\200a"), 3); +} + +M_TEST_DEFINE(roundtrip) { + uint32_t unichar; + char buf[8] = {0}; + for (unichar = 0; unichar < 0x110000; ++unichar) { + memset(buf, 0, sizeof(buf)); + size_t len = toUtf8(unichar, buf) + 1; + const char* ptr = buf; + assert_true(len); + assert_false(buf[len]); + assert_int_equal(utf8Char(&ptr, &len), unichar); + assert_int_equal(len, 1); + } +} + +M_TEST_SUITE_DEFINE(StringUTF8, + cmocka_unit_test(strlenASCII), + cmocka_unit_test(strlenMultibyte), + cmocka_unit_test(strlenDegenerate), + cmocka_unit_test(roundtrip), +)