Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2022-05-30 17:38:05 -07:00
commit 14405369cb
18 changed files with 329 additions and 58 deletions

View File

@ -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);

View File

@ -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);

View File

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

View File

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

View File

@ -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);

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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();
}
}

View File

@ -61,7 +61,6 @@ public:
QFont monospaceFont() { return m_monospace; }
QList<Window*> 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);

View File

@ -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);

View File

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

View File

@ -1093,7 +1093,7 @@ void Window::reloadDisplayDriver() {
m_display->stopDrawing();
detachWidget(m_display.get());
}
m_display = std::unique_ptr<Display>(Display::create(this));
m_display = std::unique_ptr<QGBA::Display>(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<ShaderSelector>(m_display.get(), m_config);
#endif
connect(m_display.get(), &Display::hideCursor, [this]() {
connect(m_display.get(), &QGBA::Display::hideCursor, [this]() {
if (static_cast<QStackedLayout*>(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<DolphinConnector>(&m_dolphinView, this), "file");
@ -1973,7 +1971,7 @@ Action* Window::addGameAction(const QString& visibleName, const QString& name, A
template<typename T, typename V>
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);
}

View File

@ -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)

View File

@ -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) {

View File

@ -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':

132
src/util/test/string-utf8.c Normal file
View File

@ -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 <mgba-util/string.h>
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),
)