Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2022-05-29 22:10:06 -07:00
commit 915d5fde37
59 changed files with 12915 additions and 8750 deletions

View File

@ -7,7 +7,7 @@ configuration:
cache:
- C:\Tools\vcpkg
install:
- git -C C:\Tools\vcpkg clean -dfq ports toolsrc
- git -C C:\Tools\vcpkg clean -dfq docs ports scripts toolsrc versions
- git -C C:\Tools\vcpkg pull --force --quiet
- C:\Tools\vcpkg\bootstrap-vcpkg
- vcpkg --triplet x64-windows --recurse install ffmpeg libepoxy libpng libzip sdl2 sqlite3

30
CHANGES
View File

@ -50,11 +50,27 @@ Features:
- Additional scaling shaders
Emulation fixes:
- GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032)
- GB Video: Clear VRAM on reset (fixes mgba.io/i/2152)
- GB Video: Render SGB border when unmasking with ATTR/PAL_SET (fixes mgba.io/i/2261)
- GBA: Improve timing when not booting from BIOS
- GBA SIO: Fix SI value for unattached MULTI mode
- GBA Video: Fix backdrop color if DISPCNT is first set to 0 (fixes mgba.io/i/2260)
Other fixes:
- Core: Don't attempt to restore rewind diffs past start of rewind
- GB Video: Fix memory leak when reseting SGB games
- GBA: Fix out of bounds ROM accesses on patched ROMs smaller than 32 MiB
- Libretro: Fix crash when using Game Boy codes (fixes mgba.io/i/2281)
Misc:
- Core: Suspend runloop when a core crashes
- mGUI: Add margin to right-aligned menu text (fixes mgba.io/i/871)
- Qt: Rearrange menus some
- Qt: Clean up cheats dialog
- Qt: Only set default controller bindings if loading fails (fixes mgba.io/i/799)
0.9.2: (2021-07-10)
Emulation fixes:
- GB Video: Clear VRAM on reset (fixes mgba.io/i/2152)
- GBA SIO: Add missing NORMAL8 implementation bits (fixes mgba.io/i/2172)
- GBA SIO: Fix missing interrupt on an unattached NORMAL transfer
- GBA SIO: Fix SI value for unattached MULTI mode
- GBA Memory: Fix prefetch mask when swapping modes within a region
- GBA Serialize: Fix loading audio enable bit late (fixes mgba.io/i/2230)
- GBA Video: Revert scanline latching changes (fixes mgba.io/i/2153, mgba.io/i/2149)
@ -64,22 +80,18 @@ Other fixes:
- Core: Fix memory leak in opening games from the library
- Core: Fix memory searches for relative values (fixes mgba.io/i/2135)
- Core: Fix portable mode on macOS
- Core: Don't attempt to restore rewind diffs past start of rewind
- GB Audio: Fix audio channel 4 being slow to deserialize
- GB Core: Fix GBC colors setting breaking default model overrides (fixes mgba.io/i/2161)
- GBA: Fix out of bounds ROM accesses on patched ROMs smaller than 32 MiB
- mGUI: Cache save state screenshot validity in state menu (fixes mgba.io/i/2005)
- Qt: Fix infrequent deadlock when using sync to video
- Qt: Fix eventual deadlock when using sync to video
- Qt: Fix applying savetype-only overrides
- Qt: Fix crash in sprite view for partially out-of-bounds sprites (fixes mgba.io/i/2165)
- Qt: Fix having to press controller buttons twice for menu items (fixes mgba.io/i/2143)
- Qt: Redo sensor binding to be less fragile
- Qt: Reuse timer when rescheduling missing frames (fixes mgba.io/i/2236)
- Qt: Fix bounded fast forward with enhancement OpenGL renderer
- Util: Fix loading UPS patches that affect the last byte of the file
Misc:
- Core: Suspend runloop when a core crashes
- Qt: Rearrange menus some
- Qt: Clean up cheats dialog
- Qt: Only set default controller bindings if loading fails (fixes mgba.io/i/799)
- Util: Improve speed of UPS patch loading
0.9.1: (2021-04-18)

View File

@ -154,6 +154,7 @@ if (NOT DEFINED MANDIR)
endif()
include(FindFeature)
include(FindFunction)
# Version information
add_custom_target(${BINARY_NAME}-version-info ALL
@ -305,46 +306,38 @@ if(WII)
endif()
include(CheckCCompilerFlag)
include(CheckFunctionExists)
include(CheckIncludeFiles)
check_function_exists(strdup HAVE_STRDUP)
check_function_exists(strndup HAVE_STRNDUP)
check_function_exists(strlcpy HAVE_STRLCPY)
check_function_exists(vasprintf HAVE_VASPRINTF)
if(NOT DEFINED PSP2)
check_function_exists(localtime_r HAVE_LOCALTIME_R)
set(FUNCTION_DEFINES)
find_function(strdup)
find_function(strlcpy)
find_function(strndup)
find_function(vasprintf)
find_function(freelocale)
find_function(newlocale)
find_function(setlocale)
find_function(snprintf_l)
find_function(uselocale)
find_function(futimens)
find_function(futimes)
if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13)
find_function(localtime_r)
set(HAVE_STRTOF_L ON)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
find_function(localtime_r)
# The strtof_l on Linux not actually exposed nor actually strtof_l
set(HAVE_STRTOF_L OFF)
elseif(NOT DEFINED PSP2)
find_function(localtime_r)
find_function(strtof_l)
endif()
check_include_files("xlocale.h" HAVE_XLOCALE)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic")
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13)
set(HAVE_STRTOF_L ON)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# The strtof_l on Linux not actually exposed nor actually strtof_l
set(HAVE_STRTOF_L OFF)
else()
check_function_exists(strtof_l HAVE_STRTOF_L)
endif()
check_function_exists(newlocale HAVE_NEWLOCALE)
check_function_exists(freelocale HAVE_FREELOCALE)
check_function_exists(uselocale HAVE_USELOCALE)
check_function_exists(setlocale HAVE_SETLOCALE)
else()
if(DEFINED 3DS OR DEFINED WII OR DEFINED SWITCH)
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,snprintf_l)
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,strtof_l)
check_function_exists(strtof_l HAVE_STRTOF_L)
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,newlocale)
check_function_exists(newlocale HAVE_NEWLOCALE)
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,freelocale)
check_function_exists(freelocale HAVE_FREELOCALE)
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,uselocale)
check_function_exists(uselocale HAVE_USELOCALE)
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,setlocale)
check_function_exists(setlocale HAVE_SETLOCALE)
unset(CMAKE_REQUIRED_FLAGS)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Generic")
if(NOT IS_EMBEDDED)
set(DISABLE_DEPS ON CACHE BOOL "This platform cannot build with dependencies" FORCE)
endif()
@ -355,9 +348,6 @@ else()
set(ENABLE_EXTRA ON)
endif()
check_function_exists(chmod HAVE_CHMOD)
check_function_exists(umask HAVE_UMASK)
if(USE_PTHREADS)
check_include_files("pthread.h" HAVE_PTHREAD_H)
if(HAVE_PTHREAD_H)
@ -367,40 +357,18 @@ if(USE_PTHREADS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
check_function_exists(pthread_create HAVE_PTHREAD_CREATE)
find_function(pthread_create)
if(HAVE_PTHREAD_CREATE)
add_definitions(-DUSE_PTHREADS)
check_include_files("pthread_np.h" HAVE_PTHREAD_NP_H)
check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP)
check_function_exists(pthread_set_name_np HAVE_PTHREAD_SET_NAME_NP)
find_function(pthread_setname_np)
find_function(pthread_set_name_np)
endif()
endif()
endif()
set(FUNCTION_DEFINES)
if(HAVE_STRDUP)
list(APPEND FUNCTION_DEFINES HAVE_STRDUP)
endif()
if(HAVE_STRNDUP)
list(APPEND FUNCTION_DEFINES HAVE_STRNDUP)
endif()
if(HAVE_STRLCPY)
list(APPEND FUNCTION_DEFINES HAVE_STRLCPY)
endif()
if(HAVE_VASPRINTF)
list(APPEND FUNCTION_DEFINES HAVE_VASPRINTF)
endif()
if(HAVE_LOCALTIME_R)
list(APPEND FUNCTION_DEFINES HAVE_LOCALTIME_R)
endif()
if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE OR APPLE)
list(APPEND FUNCTION_DEFINES HAVE_LOCALE)
if (HAVE_SNPRINTF_L)
@ -408,38 +376,14 @@ if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE OR APPLE)
endif()
endif()
if(HAVE_SETLOCALE)
list(APPEND FUNCTION_DEFINES HAVE_SETLOCALE)
endif()
if (HAVE_STRTOF_L)
list(APPEND FUNCTION_DEFINES HAVE_STRTOF_L)
endif()
if(HAVE_XLOCALE)
list(APPEND FUNCTION_DEFINES HAVE_XLOCALE)
endif()
if(HAVE_CHMOD)
list(APPEND FUNCTION_DEFINES HAVE_CHMOD)
endif()
if(HAVE_UMASK)
list(APPEND FUNCTION_DEFINES HAVE_UMASK)
endif()
if(HAVE_PTHREAD_NP_H)
list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_NP_H)
endif()
if(HAVE_PTHREAD_SETNAME_NP)
list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SETNAME_NP)
endif()
if(HAVE_PTHREAD_SET_NAME_NP)
list(APPEND FUNCTION_DEFINES HAVE_PTHREAD_SET_NAME_NP)
endif()
# Feature dependencies
set(FEATURE_DEFINES)
set(FEATURE_FLAGS)
@ -993,6 +937,19 @@ if(BUILD_OPENEMU)
install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${OE_LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP)
endif()
if(BUILD_QT AND WIN32)
set(BUILD_UPDATER ON)
endif()
if(BUILD_UPDATER)
add_executable(updater-stub WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c)
target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY} ${BINARY_NAME})
if(NOT MSVC)
set_target_properties(updater-stub PROPERTIES LINK_FLAGS_RELEASE -s)
set_target_properties(updater-stub PROPERTIES LINK_FLAGS_RELWITHDEBINFO -s)
endif()
endif()
if(BUILD_SDL)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/sdl ${CMAKE_CURRENT_BINARY_DIR}/sdl)
endif()

View File

@ -28,6 +28,8 @@ void ConfigurationSetUIntValue(struct Configuration*, const char* section, const
void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value);
bool ConfigurationHasSection(const struct Configuration*, const char* section);
void ConfigurationDeleteSection(struct Configuration*, const char* section);
const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key);
void ConfigurationClearValue(struct Configuration*, const char* section, const char* key);

View File

@ -0,0 +1,51 @@
/* Copyright (c) 2013-2021 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/. */
#ifndef M_UPDATER_H
#define M_UPDATER_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/core/config.h>
#include <mgba-util/configuration.h>
struct StringList;
struct Table;
struct mUpdaterContext {
struct Configuration manifest;
};
struct mUpdate {
const char* path;
size_t size;
int rev;
const char* version;
const char* commit;
const char* sha256;
};
bool mUpdaterInit(struct mUpdaterContext*, const char* manifest);
void mUpdaterDeinit(struct mUpdaterContext*);
void mUpdaterGetPlatforms(const struct mUpdaterContext*, struct StringList* out);
void mUpdaterGetUpdates(const struct mUpdaterContext*, const char* platform, struct Table* out);
void mUpdaterGetUpdateForChannel(const struct mUpdaterContext*, const char* platform, const char* channel, struct mUpdate* out);
const char* mUpdaterGetBucket(const struct mUpdaterContext*);
void mUpdateRecord(struct mCoreConfig*, const char* prefix, const struct mUpdate*);
bool mUpdateLoad(const struct mCoreConfig*, const char* prefix, struct mUpdate*);
void mUpdateRegister(struct mCoreConfig*, const char* arg0, const char* updatePath);
void mUpdateDeregister(struct mCoreConfig*);
const char* mUpdateGetRoot(const struct mCoreConfig*);
const char* mUpdateGetCommand(const struct mCoreConfig*);
const char* mUpdateGetArchiveExtension(const struct mCoreConfig*);
bool mUpdateGetArchivePath(const struct mCoreConfig*, char* out, size_t outLength);
CXX_GUARD_END
#endif

View File

@ -1,12 +1,13 @@
Miras Absar
Emily A. Bellows
Jaime J. Denizard
Benedikt Feih
Tyler Jenkins
Emily A. Bellows
gocha
Jaime J. Denizard
Jezzabel
Lucas Towers
MichaelK_
Lothar Serra Mari
Miras Absar
NimbusFox
Petru-Sebastian Toader
Lucas Towers
SquidHominid
Tyler Jenkins
Zach

View File

@ -1,12 +0,0 @@
/* Copyright (c) 2013-2016 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/. */
#ifndef TEST_M_CORE_H
#define TEST_M_CORE_H
#include <mgba-util/common.h>
int TestRunCore(void);
#endif

View File

@ -2,6 +2,7 @@ include(ExportDirectory)
set(SOURCE_FILES
commandline.c
thread-proxy.c
updater.c
video-logger.c)
set(GUI_FILES

143
src/feature/updater-main.c Normal file
View File

@ -0,0 +1,143 @@
/* Copyright (c) 2013-2021 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 <mgba/core/config.h>
#include <mgba/feature/updater.h>
#include <mgba-util/vfs.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#include <io.h>
#include <process.h>
#define mkdir(X, Y) _mkdir(X)
#elif defined(_POSIX_C_SOURCE)
#include <unistd.h>
#endif
bool extractArchive(struct VDir* archive, const char* root) {
char path[PATH_MAX] = {0};
struct VDirEntry* vde;
uint8_t block[8192];
ssize_t size;
while ((vde = archive->listNext(archive))) {
struct VFile* vfIn;
struct VFile* vfOut;
const char* fname = strchr(vde->name(vde), '/');
if (!fname) {
continue;
}
snprintf(path, sizeof(path), "%s/%s", root, &fname[1]);
switch (vde->type(vde)) {
case VFS_DIRECTORY:
printf("mkdir %s\n", fname);
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
return false;
}
break;
case VFS_FILE:
printf("extract %s\n", fname);
vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY);
errno = 0;
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
if (!vfOut && errno == EACCES) {
sleep(1);
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
}
if (!vfOut) {
vfIn->close(vfIn);
return false;
}
while ((size = vfIn->read(vfIn, block, sizeof(block))) > 0) {
vfOut->write(vfOut, block, size);
}
vfOut->close(vfOut);
vfIn->close(vfIn);
if (size < 0) {
return false;
}
break;
case VFS_UNKNOWN:
return false;
}
}
return true;
}
int main(int argc, char* argv[]) {
UNUSED(argc);
UNUSED(argv);
struct mCoreConfig config;
char updateArchive[PATH_MAX] = {0};
const char* root;
int ok = 1;
mCoreConfigInit(&config, "updater");
if (!mCoreConfigLoad(&config)) {
puts("Failed to load config");
} else if (!mUpdateGetArchivePath(&config, updateArchive, sizeof(updateArchive)) || !(root = mUpdateGetRoot(&config))) {
puts("No pending update found");
} else if (access(root, W_OK)) {
puts("Cannot write to update path");
} else {
bool isPortable = mCoreConfigIsPortable();
struct VDir* archive = VDirOpenArchive(updateArchive);
if (!archive) {
puts("Cannot open update archive");
} else {
puts("Extracting update");
if (extractArchive(archive, root)) {
puts("Complete");
ok = 0;
mUpdateDeregister(&config);
} else {
puts("An error occurred");
}
archive->close(archive);
unlink(updateArchive);
}
if (!isPortable) {
char portableIni[PATH_MAX] = {0};
snprintf(portableIni, sizeof(portableIni), "%s/portable.ini", root);
unlink(portableIni);
}
}
const char* bin = mUpdateGetCommand(&config);
mCoreConfigDeinit(&config);
if (ok == 0) {
const char* argv[] = { bin, NULL };
#ifdef _WIN32
_execv(bin, argv);
#elif defined(_POSIX_C_SOURCE)
execv(bin, argv);
#endif
}
return 1;
}
#ifdef _WIN32
#include <mgba-util/string.h>
#include <mgba-util/vector.h>
int wmain(int argc, wchar_t* argv[]) {
struct StringList argv8;
StringListInit(&argv8, argc);
for (int i = 0; i < argc; ++i) {
*StringListAppend(&argv8) = utf16to8((uint16_t*) argv[i], wcslen(argv[i]) * 2);
}
int ret = main(argc, StringListGetPointer(&argv8, 0));
size_t i;
for (i = 0; i < StringListSize(&argv8); ++i) {
free(*StringListGetPointer(&argv8, i));
}
return ret;
}
#endif

224
src/feature/updater.c Normal file
View File

@ -0,0 +1,224 @@
/* Copyright (c) 2013-2021 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 <mgba/feature/updater.h>
#include <mgba-util/string.h>
#include <mgba-util/table.h>
#include <mgba-util/vector.h>
#include <mgba-util/vfs.h>
#define UPDATE_SECTION "update"
struct mUpdateMatch {
const char* channel;
struct mUpdate* out;
};
static void _updateListSections(const char* sectionName, void* user) {
struct StringList* out = user;
if (strncmp("platform.", sectionName, 9) == 0) {
*StringListAppend(out) = (char*) &sectionName[9];
}
}
static void _updateUpdate(struct mUpdate* update, const char* item, const char* value) {
if (strcmp("name", item) == 0) {
update->path = value;
} else if (strcmp("version", item) == 0) {
update->version = value;
} else if (strcmp("size", item) == 0) {
update->size = strtoull(value, NULL, 10);
} else if (strcmp("rev", item) == 0) {
update->rev = strtol(value, NULL, 10);
} else if (strcmp("commit", item) == 0) {
update->commit = value;
} else if (strcmp("sha256", item) == 0) {
update->sha256 = value;
}
}
static void _updateList(const char* key, const char* value, void* user) {
char channel[64] = {0};
const char* dotLoc;
if (strncmp("medusa.", key, 7) == 0) {
dotLoc = strchr(&key[7], '.');
} else {
dotLoc = strchr(key, '.');
}
if (!dotLoc) {
return;
}
size_t size = dotLoc - key;
if (size >= sizeof(channel)) {
return;
}
strncpy(channel, key, size);
const char* item = &key[size + 1];
struct Table* out = user;
struct mUpdate* update = HashTableLookup(out, channel);
if (!update) {
update = calloc(1, sizeof(*update));
HashTableInsert(out, channel, update);
}
_updateUpdate(update, item, value);
}
static void _updateMatch(const char* key, const char* value, void* user) {
struct mUpdateMatch* match = user;
size_t dotLoc = strlen(match->channel);
if (dotLoc >= strlen(key) || key[dotLoc] != '.') {
return;
}
if (strncmp(match->channel, key, dotLoc) != 0) {
return;
}
const char* item = &key[dotLoc + 1];
struct Table* out = user;
struct mUpdate* update = HashTableLookup(out, match->channel);
if (!update) {
update = calloc(1, sizeof(*update));
HashTableInsert(out, match->channel, update);
}
_updateUpdate(update, item, value);
}
bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) {
ConfigurationInit(&context->manifest);
struct VFile* vf = VFileFromConstMemory(manifest, strlen(manifest) + 1);
bool success = vf && ConfigurationReadVFile(&context->manifest, vf);
vf->close(vf);
if (!success) {
ConfigurationDeinit(&context->manifest);
}
return success;
}
void mUpdaterDeinit(struct mUpdaterContext* context) {
ConfigurationDeinit(&context->manifest);
}
void mUpdaterGetPlatforms(const struct mUpdaterContext* context, struct StringList* out) {
StringListClear(out);
ConfigurationEnumerateSections(&context->manifest, _updateListSections, out);
}
void mUpdaterGetUpdates(const struct mUpdaterContext* context, const char* platform, struct Table* out) {
char section[64] = {'p', 'l', 'a', 't', 'f', 'o', 'r', 'm', '.'};
strncpy(&section[9], platform, sizeof(section) - 10);
ConfigurationEnumerate(&context->manifest, section, _updateList, out);
}
void mUpdaterGetUpdateForChannel(const struct mUpdaterContext* context, const char* platform, const char* channel, struct mUpdate* out) {
char section[64] = {'p', 'l', 'a', 't', 'f', 'o', 'r', 'm', '.'};
strncpy(&section[9], platform, sizeof(section) - 10);
struct mUpdateMatch match = {
.channel = channel,
.out = out
};
ConfigurationEnumerate(&context->manifest, section, _updateMatch, &match);
}
const char* mUpdaterGetBucket(const struct mUpdaterContext* context) {
return ConfigurationGetValue(&context->manifest, "meta", "bucket");
}
void mUpdateRecord(struct mCoreConfig* config, const char* prefix, const struct mUpdate* update) {
char key[128];
snprintf(key, sizeof(key), "%s.path", prefix);
mCoreConfigSetValue(config, key, update->path);
snprintf(key, sizeof(key), "%s.size", prefix);
mCoreConfigSetUIntValue(config, key, update->size);
snprintf(key, sizeof(key), "%s.rev", prefix);
if (update->rev > 0) {
mCoreConfigSetIntValue(config, key, update->rev);
} else {
mCoreConfigSetValue(config, key, NULL);
}
snprintf(key, sizeof(key), "%s.version", prefix);
mCoreConfigSetValue(config, key, update->version);
snprintf(key, sizeof(key), "%s.commit", prefix);
mCoreConfigSetValue(config, key, update->commit);
snprintf(key, sizeof(key), "%s.sha256", prefix);
mCoreConfigSetValue(config, key, update->sha256);
}
bool mUpdateLoad(const struct mCoreConfig* config, const char* prefix, struct mUpdate* update) {
char key[128];
memset(update, 0, sizeof(*update));
snprintf(key, sizeof(key), "%s.path", prefix);
update->path = mCoreConfigGetValue(config, key);
snprintf(key, sizeof(key), "%s.size", prefix);
uint32_t size = 0;
mCoreConfigGetUIntValue(config, key, &size);
if (!update->path && !size) {
return false;
}
update->size = size;
snprintf(key, sizeof(key), "%s.rev", prefix);
mCoreConfigGetIntValue(config, key, &update->rev);
snprintf(key, sizeof(key), "%s.version", prefix);
update->version = mCoreConfigGetValue(config, key);
snprintf(key, sizeof(key), "%s.commit", prefix);
update->commit = mCoreConfigGetValue(config, key);
snprintf(key, sizeof(key), "%s.sha256", prefix);
update->sha256 = mCoreConfigGetValue(config, key);
return true;
}
void mUpdateRegister(struct mCoreConfig* config, const char* arg0, const char* updatePath) {
struct Configuration* cfg = &config->configTable;
char filename[PATH_MAX];
strlcpy(filename, arg0, sizeof(filename));
char* last;
#ifdef _WIN32
last = strrchr(filename, '\\');
#else
last = strrchr(filename, '/');
#endif
if (last) {
last[0] = '\0';
}
ConfigurationSetValue(cfg, UPDATE_SECTION, "bin", arg0);
ConfigurationSetValue(cfg, UPDATE_SECTION, "root", filename);
separatePath(updatePath, NULL, NULL, filename);
ConfigurationSetValue(cfg, UPDATE_SECTION, "extension", filename);
mCoreConfigSave(config);
}
void mUpdateDeregister(struct mCoreConfig* config) {
ConfigurationDeleteSection(&config->configTable, UPDATE_SECTION);
mCoreConfigSave(config);
}
const char* mUpdateGetRoot(const struct mCoreConfig* config) {
return ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "root");
}
const char* mUpdateGetCommand(const struct mCoreConfig* config) {
return ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "bin");
}
const char* mUpdateGetArchiveExtension(const struct mCoreConfig* config) {
return ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "extension");
}
bool mUpdateGetArchivePath(const struct mCoreConfig* config, char* out, size_t outLength) {
const char* extension = ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "extension");
if (!extension) {
return false;
}
mCoreConfigDirectory(out, outLength);
size_t start = strlen(out);
outLength -= start;
snprintf(&out[start], outLength, PATH_SEP "update.%s", extension);
return true;
}

View File

@ -780,6 +780,9 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer)
case SGB_ATTR_SET:
if (softwareRenderer->sgbPacket[1] & 0x40) {
renderer->sgbRenderMode = 0;
if (softwareRenderer->sgbBorders) {
_regenerateSGBBorder(softwareRenderer);
}
}
break;
case SGB_PAL_TRN:

View File

@ -87,20 +87,53 @@ void GBVideoReset(struct GBVideo* video) {
memset(&video->palette, 0, sizeof(video->palette));
if (video->p->model & GB_MODEL_SGB) {
video->renderer->sgbCharRam = anonymousMemoryMap(SGB_SIZE_CHAR_RAM);
video->renderer->sgbMapRam = anonymousMemoryMap(SGB_SIZE_MAP_RAM);
video->renderer->sgbPalRam = anonymousMemoryMap(SGB_SIZE_PAL_RAM);
video->renderer->sgbAttributeFiles = anonymousMemoryMap(SGB_SIZE_ATF_RAM);
video->renderer->sgbAttributes = malloc(90 * 45);
if (video->renderer->sgbCharRam) {
memset(video->renderer->sgbCharRam, 0, SGB_SIZE_CHAR_RAM);
} else {
video->renderer->sgbCharRam = anonymousMemoryMap(SGB_SIZE_CHAR_RAM);
}
if (video->renderer->sgbMapRam) {
memset(video->renderer->sgbMapRam, 0, SGB_SIZE_MAP_RAM);
} else {
video->renderer->sgbMapRam = anonymousMemoryMap(SGB_SIZE_MAP_RAM);
}
if (video->renderer->sgbPalRam) {
memset(video->renderer->sgbPalRam, 0, SGB_SIZE_PAL_RAM);
} else {
video->renderer->sgbPalRam = anonymousMemoryMap(SGB_SIZE_PAL_RAM);
}
if (video->renderer->sgbAttributeFiles) {
memset(video->renderer->sgbAttributeFiles, 0, SGB_SIZE_ATF_RAM);
} else {
video->renderer->sgbAttributeFiles = anonymousMemoryMap(SGB_SIZE_ATF_RAM);
}
if (!video->renderer->sgbAttributes) {
video->renderer->sgbAttributes = malloc(90 * 45);
}
memset(video->renderer->sgbAttributes, 0, 90 * 45);
video->sgbCommandHeader = 0;
video->sgbBufferIndex = 0;
} else {
video->renderer->sgbCharRam = NULL;
video->renderer->sgbMapRam = NULL;
video->renderer->sgbPalRam = NULL;
video->renderer->sgbAttributes = NULL;
video->renderer->sgbAttributeFiles = NULL;
if (video->renderer->sgbCharRam) {
mappedMemoryFree(video->renderer->sgbCharRam, SGB_SIZE_CHAR_RAM);
video->renderer->sgbCharRam = NULL;
}
if (video->renderer->sgbMapRam) {
mappedMemoryFree(video->renderer->sgbMapRam, SGB_SIZE_MAP_RAM);
video->renderer->sgbMapRam = NULL;
}
if (video->renderer->sgbPalRam) {
mappedMemoryFree(video->renderer->sgbPalRam, SGB_SIZE_PAL_RAM);
video->renderer->sgbPalRam = NULL;
}
if (video->renderer->sgbAttributeFiles) {
mappedMemoryFree(video->renderer->sgbAttributeFiles, SGB_SIZE_ATF_RAM);
video->renderer->sgbAttributeFiles = NULL;
}
if (video->renderer->sgbAttributes) {
free(video->renderer->sgbAttributes);
video->renderer->sgbAttributes = NULL;
}
}
video->palette[0] = video->dmgPalette[0];

View File

@ -933,6 +933,7 @@ void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) {
glRenderer->nextPalette = 0;
glRenderer->paletteDirtyScanlines = GBA_VIDEO_VERTICAL_PIXELS;
memset(glRenderer->shadowRegs, 0, sizeof(glRenderer->shadowRegs));
glRenderer->shadowRegs[REG_DISPCNT >> 1] = glRenderer->dispcnt;
glRenderer->regsDirty = 0xFFFFFFFFFFFEULL;
int i;

View File

@ -594,12 +594,12 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
int w;
unsigned priority;
for (priority = 0; priority < 4; ++priority) {
softwareRenderer->end = 0;
for (w = 0; w < softwareRenderer->nWindows; ++w) {
softwareRenderer->start = softwareRenderer->end;
softwareRenderer->end = softwareRenderer->windows[w].endX;
softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
softwareRenderer->end = 0;
for (w = 0; w < softwareRenderer->nWindows; ++w) {
softwareRenderer->start = softwareRenderer->end;
softwareRenderer->end = softwareRenderer->windows[w].endX;
softwareRenderer->currentWindow = softwareRenderer->windows[w].control;
for (priority = 0; priority < 4; ++priority) {
if (spriteLayers & (1 << priority)) {
GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority);
}

View File

@ -0,0 +1,14 @@
include(CheckFunctionExists)
function(find_function FUNCTION_NAME)
if(CMAKE_SYSTEM_NAME STREQUAL "Generic")
set(CMAKE_REQUIRED_FLAGS -Wl,--require-defined,${FUNCTION_NAME})
endif()
string(TOUPPER ${FUNCTION_NAME} FLAG_NAME)
check_function_exists(${FUNCTION_NAME} HAVE_${FLAG_NAME})
unset(CMAKE_REQUIRED_FLAGS)
set(HAVE_${FLAG_NAME} ${HAVE_${FLAG_NAME}} PARENT_SCOPE)
if(HAVE_${FLAG_NAME})
list(APPEND FUNCTION_DEFINES HAVE_${FLAG_NAME})
set(FUNCTION_DEFINES "${FUNCTION_DEFINES}" PARENT_SCOPE)
endif()
endfunction()

View File

@ -943,7 +943,9 @@ void retro_cheat_set(unsigned index, bool enabled, const char* code) {
}
}
#endif
cheatSet->refresh(cheatSet, device);
if (cheatSet->refresh) {
cheatSet->refresh(cheatSet, device);
}
}
unsigned retro_get_region(void) {

View File

@ -40,7 +40,17 @@ void AbstractUpdater::downloadUpdate() {
chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
}
void AbstractUpdater::progress(qint64 progress, qint64 max) {
if (!max) {
return;
}
emit updateProgress(static_cast<float>(progress) / static_cast<float>(max));
}
void AbstractUpdater::chaseRedirects(QNetworkReply* reply, void (AbstractUpdater::*cb)(QNetworkReply*)) {
if (m_isUpdating) {
connect(reply, &QNetworkReply::downloadProgress, this, &AbstractUpdater::progress);
}
connect(reply, &QNetworkReply::finished, this, [this, reply, cb]() {
// TODO: check domains, etc
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 == 3) {

View File

@ -28,12 +28,16 @@ public slots:
signals:
void updateAvailable(bool);
void updateDone(bool);
void updateProgress(float done);
protected:
virtual QUrl manifestLocation() const = 0;
virtual QUrl parseManifest(const QByteArray&) const = 0;
virtual QUrl parseManifest(const QByteArray&) = 0;
virtual QString destination() const = 0;
private slots:
void progress(qint64 progress, qint64 max);
private:
void chaseRedirects(QNetworkReply*, void (AbstractUpdater::*cb)(QNetworkReply*));
void manifestDownloaded(QNetworkReply*);
@ -44,4 +48,4 @@ private:
QByteArray m_manifest;
};
}
}

View File

@ -0,0 +1,71 @@
/* Copyright (c) 2013-2021 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 "ApplicationUpdatePrompt.h"
#include <QCryptographicHash>
#include <QPushButton>
#include "ApplicationUpdater.h"
#include "GBAApp.h"
#include "utils.h"
#include <mgba/core/version.h>
using namespace QGBA;
ApplicationUpdatePrompt::ApplicationUpdatePrompt(QWidget* parent)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
{
m_ui.setupUi(this);
ApplicationUpdater* updater = GBAApp::app()->updater();
ApplicationUpdater::UpdateInfo info = updater->updateInfo();
m_ui.text->setText(tr("An update to %1 is available.\nDo you want to download and install it now? You will need to restart the emulator when the download is complete.")
.arg(QLatin1String(projectName)));
m_ui.details->setText(tr("Current version: %1\nNew version: %2\nDownload size: %3")
.arg(QLatin1String(projectVersion))
.arg(info)
.arg(niceSizeFormat(info.size)));
m_ui.progressBar->setVisible(false);
connect(updater, &AbstractUpdater::updateProgress, this, [this](float progress) {
m_ui.progressBar->setValue(progress * 100);
});
m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate);
connect(updater, &AbstractUpdater::updateDone, this, &ApplicationUpdatePrompt::promptRestart);
}
void ApplicationUpdatePrompt::startUpdate() {
ApplicationUpdater* updater = GBAApp::app()->updater();
updater->downloadUpdate();
m_ui.buttonBox->disconnect(m_okDownload);
m_ui.progressBar->show();
m_ui.text->setText(tr("Downloading update..."));
m_ui.details->hide();
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
void ApplicationUpdatePrompt::promptRestart() {
ApplicationUpdater* updater = GBAApp::app()->updater();
QString filename = updater->destination();
QByteArray expectedHash = updater->updateInfo().sha256;
QCryptographicHash sha256(QCryptographicHash::Sha256);
QFile update(filename);
update.open(QIODevice::ReadOnly);
if (!sha256.addData(&update) || sha256.result() != expectedHash) {
update.close();
update.remove();
m_ui.text->setText(tr("Downloading failed. Please update manually.")
.arg(QLatin1String(projectName)));
} else {
m_ui.text->setText(tr("Downloading done. Press OK to restart %1 and install the update.")
.arg(QLatin1String(projectName)));
}
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
}

View File

@ -0,0 +1,29 @@
/* Copyright (c) 2013-2021 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/. */
#pragma once
#include <QDialog>
#include "ui_ApplicationUpdatePrompt.h"
namespace QGBA {
class ApplicationUpdatePrompt : public QDialog {
Q_OBJECT
public:
ApplicationUpdatePrompt(QWidget* parent = nullptr);
private slots:
void startUpdate();
void promptRestart();
private:
Ui::ApplicationUpdatePrompt m_ui;
QMetaObject::Connection m_okDownload;
};
}

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ApplicationUpdatePrompt</class>
<widget class="QDialog" name="ApplicationUpdatePrompt">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>186</width>
<height>127</height>
</rect>
</property>
<property name="windowTitle">
<string>An update is available</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<widget class="QLabel" name="text">
<property name="text">
<string>{text}</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="details">
<property name="text">
<string>{details}</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ApplicationUpdatePrompt</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,227 @@
/* Copyright (c) 2013-2021 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 "ApplicationUpdater.h"
#include <QDir>
#include <QFileInfo>
#include "ApplicationUpdatePrompt.h"
#include "ConfigController.h"
#include "GBAApp.h"
#include <mgba/core/version.h>
#include <mgba/feature/updater.h>
#include <mgba-util/table.h>
using namespace QGBA;
ApplicationUpdater::ApplicationUpdater(ConfigController* config, QObject* parent)
: AbstractUpdater(parent)
, m_config(config)
, m_channel(currentChannel())
{
QVariant lastCheck = config->getQtOption("lastUpdateCheck");
if (lastCheck.isValid()) {
m_lastCheck = lastCheck.toDateTime();
}
QByteArray bucket(m_config->getOption("update.bucket").toLatin1());
if (!bucket.isNull()) {
mUpdate lastUpdate;
if (mUpdateLoad(m_config->config(), "update.stable", &lastUpdate)) {
m_updates[QLatin1String("stable")] = UpdateInfo(bucket.constData(), &lastUpdate);
}
if (mUpdateLoad(m_config->config(), "update.dev", &lastUpdate)) {
m_updates[QLatin1String("dev")] = UpdateInfo(bucket.constData(), &lastUpdate);
}
}
connect(this, &AbstractUpdater::updateAvailable, this, [this, config](bool available) {
m_lastCheck = QDateTime::currentDateTimeUtc();
config->setQtOption("lastUpdateCheck", m_lastCheck);
if (available && currentVersion() < updateInfo()) {
#ifdef Q_OS_WIN
// Only works on Windows at the moment
ApplicationUpdatePrompt* prompt = new ApplicationUpdatePrompt;
connect(prompt, &QDialog::accepted, GBAApp::app(), &GBAApp::restartForUpdate);
prompt->setAttribute(Qt::WA_DeleteOnClose);
prompt->show();
#endif
}
});
connect(this, &AbstractUpdater::updateDone, this, [this, config]() {
QByteArray arg0 = GBAApp::app()->arguments().at(0).toUtf8();
QByteArray path = updateInfo().url.path().toUtf8();
mUpdateRegister(config->config(), arg0.constData(), path.constData());
config->write();
});
}
QUrl ApplicationUpdater::manifestLocation() const {
return {"https://mgba.io/latest.ini"};
}
QStringList ApplicationUpdater::listChannels() {
QStringList channels;
channels << QLatin1String("stable");
channels << QLatin1String("dev");
return channels;
}
QString ApplicationUpdater::currentChannel() {
QLatin1String version(projectVersion);
QLatin1String branch(gitBranch);
if (branch == QLatin1String("heads/") + version) {
return QLatin1String("stable");
} else {
return QLatin1String("dev");
}
}
QString ApplicationUpdater::readableChannel(const QString& channel) {
if (channel.isEmpty()) {
return readableChannel(currentChannel());
}
if (channel == QLatin1String("stable")) {
return tr("Stable");
}
if (channel == QLatin1String("dev")) {
return tr("Development");
}
return tr("Unknown");
}
ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() {
UpdateInfo info;
info.version = QLatin1String(projectVersion);
info.rev = gitRevision;
info.commit = QLatin1String(gitCommit);
return info;
}
QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
const char* bytes = manifest.constData();
mUpdaterContext context;
if (!mUpdaterInit(&context, bytes)) {
return {};
}
m_bucket = QLatin1String(mUpdaterGetBucket(&context));
m_config->setOption("update.bucket", m_bucket);
Table updates;
HashTableInit(&updates, 4, free);
mUpdaterGetUpdates(&context, platform(), &updates);
m_updates.clear();
HashTableEnumerate(&updates, [](const char* key, void* value, void* user) {
const mUpdate* update = static_cast<mUpdate*>(value);
ApplicationUpdater* self = static_cast<ApplicationUpdater*>(user);
self->m_updates[QString::fromUtf8(key)] = UpdateInfo(self->m_bucket, update);
QByteArray prefix(QString("update.%1").arg(key).toUtf8());
mUpdateRecord(self->m_config->config(), prefix.constData(), update);
}, static_cast<void*>(this));
HashTableDeinit(&updates);
mUpdaterDeinit(&context);
if (!m_updates.contains(m_channel)) {
return {};
}
return m_updates[m_channel].url;
}
QString ApplicationUpdater::destination() const {
QFileInfo path(updateInfo().url.path());
QDir dir(ConfigController::configDir());
return dir.filePath(QLatin1String("update.") + path.completeSuffix());
}
const char* ApplicationUpdater::platform() {
#ifdef Q_OS_WIN
QFileInfo exe(GBAApp::app()->arguments().at(0));
QFileInfo uninstallInfo(exe.dir().filePath("unins000.dat"));
#ifdef Q_OS_WIN64
return uninstallInfo.exists() ? "win64-installer" : "win64";
#elif defined(Q_OS_WIN32)
return uninstallInfo.exists() ? "win32-installer" : "win32";
#endif
#elif defined(Q_OS_MACOS)
return "osx";
#else
// Return one that will be up to date, but we can't download
return "win64";
#endif
}
ApplicationUpdater::UpdateInfo::UpdateInfo(const QString& prefix, const mUpdate* update)
: size(update->size)
, url(prefix + update->path)
{
if (update->rev > 0) {
rev = update->rev;
}
if (update->commit) {
commit = update->commit;
}
if (update->version) {
version = QLatin1String(update->version);
}
if (update->sha256) {
sha256 = QByteArray::fromHex(update->sha256);
}
}
bool ApplicationUpdater::UpdateInfo::operator<(const ApplicationUpdater::UpdateInfo& other) const {
if (rev > 0 && other.rev > 0) {
return rev < other.rev;
}
if (!version.isNull() && !other.version.isNull()) {
QStringList components = version.split(QChar('.'));
QStringList otherComponents = other.version.split(QChar('.'));
for (int i = 0; i < std::max<int>(components.count(), otherComponents.count()); ++i) {
int component = -1;
int otherComponent = -1;
if (i < components.count()) {
bool ok = true;
component = components[i].toInt(&ok);
if (!ok) {
return false;
}
}
if (i < otherComponents.count()) {
bool ok = true;
otherComponent = otherComponents[i].toInt(&ok);
if (!ok) {
return false;
}
}
if (component < otherComponent) {
return true;
}
}
return false;
}
return false;
}
ApplicationUpdater::UpdateInfo::operator QString() const {
if (!version.isNull()) {
return version;
}
if (rev <= 0) {
return ApplicationUpdater::tr("(None)");
}
int len = strlen(gitCommitShort);
const char* pos = strchr(gitCommitShort, '-');
if (pos) {
len = pos - gitCommitShort;
}
return QString("r%1-%2").arg(rev).arg(commit.left(len));
}

View File

@ -0,0 +1,68 @@
/* Copyright (c) 2013-2021 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/. */
#pragma once
#include "AbstractUpdater.h"
#include <QDateTime>
#include <QHash>
#include <QUrl>
struct mUpdate;
namespace QGBA {
class ConfigController;
class ApplicationUpdater : public AbstractUpdater {
Q_OBJECT
public:
struct UpdateInfo {
UpdateInfo() = default;
UpdateInfo(const QString& prefix, const mUpdate*);
QString version;
int rev;
QString commit;
size_t size;
QUrl url;
QByteArray sha256;
bool operator<(const UpdateInfo&) const;
operator QString() const;
};
ApplicationUpdater(ConfigController* config, QObject* parent = nullptr);
static QStringList listChannels();
void setChannel(const QString& channel) { m_channel = channel; }
static QString currentChannel();
static QString readableChannel(const QString& channel = {});
QHash<QString, UpdateInfo> listUpdates() const { return m_updates; }
UpdateInfo updateInfo() const { return m_updates[m_channel]; }
static UpdateInfo currentVersion();
QDateTime lastCheck() const { return m_lastCheck; }
virtual QString destination() const override;
protected:
virtual QUrl manifestLocation() const override;
virtual QUrl parseManifest(const QByteArray&) override;
private:
static const char* platform();
ConfigController* m_config;
QHash<QString, UpdateInfo> m_updates;
QString m_channel;
QString m_bucket;
QDateTime m_lastCheck;
};
}

View File

@ -24,7 +24,7 @@ QUrl BattleChipUpdater::manifestLocation() const {
return {"https://api.github.com/repos/mgba-emu/chip-assets/releases/latest"};
}
QUrl BattleChipUpdater::parseManifest(const QByteArray& manifest) const {
QUrl BattleChipUpdater::parseManifest(const QByteArray& manifest) {
QJsonDocument manifestDoc(QJsonDocument::fromJson(manifest));
if (manifestDoc.isNull()) {
return QUrl();
@ -44,4 +44,4 @@ QString BattleChipUpdater::destination() const {
return info.filePath();
}
return ConfigController::configDir() + "/chips.rcc";
}
}

View File

@ -15,8 +15,8 @@ public:
protected:
virtual QUrl manifestLocation() const override;
virtual QUrl parseManifest(const QByteArray&) const override;
virtual QUrl parseManifest(const QByteArray&) override;
virtual QString destination() const override;
};
}
}

View File

@ -61,6 +61,8 @@ set(SOURCE_FILES
AbstractUpdater.cpp
Action.cpp
ActionMapper.cpp
ApplicationUpdater.cpp
ApplicationUpdatePrompt.cpp
AssetInfo.cpp
AssetTile.cpp
AssetView.cpp
@ -125,6 +127,7 @@ set(SOURCE_FILES
set(UI_FILES
AboutScreen.ui
ApplicationUpdatePrompt.ui
ArchiveInspector.ui
AssetTile.ui
BattleChipView.ui
@ -230,6 +233,11 @@ if(USE_DISCORD_RPC)
endif()
qt5_add_resources(RESOURCES resources.qrc)
if(BUILD_UPDATER)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
qt5_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
list(APPEND RESOURCES ${UPDATER_RESOURCES})
endif()
if(APPLE)
set(MACOSX_BUNDLE_ICON_FILE icon.icns)
set(MACOSX_BUNDLE_BUNDLE_VERSION ${LIB_VERSION_STRING})

View File

@ -50,7 +50,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
m_painter = std::make_unique<PainterGL>(windowHandle(), format);
m_drawThread.setObjectName("Painter Thread");
m_painter->moveToThread(&m_drawThread);
m_painter->setThread(&m_drawThread);
connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
connect(m_painter.get(), &PainterGL::started, this, [this] {
@ -266,6 +266,8 @@ PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
for (auto& buf : m_buffers) {
m_free.append(&buf.front());
}
connect(&m_drawTimer, &QTimer::timeout, this, &PainterGL::draw);
m_drawTimer.setSingleShot(true);
}
PainterGL::~PainterGL() {
@ -274,6 +276,11 @@ PainterGL::~PainterGL() {
}
}
void PainterGL::setThread(QThread* thread) {
moveToThread(thread);
m_drawTimer.moveToThread(thread);
}
void PainterGL::makeCurrent() {
m_gl->makeCurrent(m_surface);
#if defined(_WIN32) && defined(USE_EPOXY)
@ -436,8 +443,14 @@ void PainterGL::draw() {
mCoreSync* sync = &m_context->thread()->impl->sync;
if (!mCoreSyncWaitFrameStart(sync)) {
mCoreSyncWaitFrameEnd(sync);
if ((sync->audioWait || sync->videoFrameWait) && m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
QTimer::singleShot(1, this, &PainterGL::draw);
if (!sync->audioWait && !sync->videoFrameWait) {
return;
}
if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) {
return;
}
if (!m_drawTimer.isActive()) {
m_drawTimer.start(1);
}
return;
}
@ -447,12 +460,13 @@ void PainterGL::draw() {
m_delayTimer.start();
} else {
if (sync->audioWait || sync->videoFrameWait) {
while (m_delayTimer.nsecsElapsed() + 1'000'000 < 1'000'000'000 / sync->fpsTarget) {
while (m_delayTimer.nsecsElapsed() + 1000000 < 1000000000 / sync->fpsTarget) {
QThread::usleep(500);
}
forceRedraw = true;
} else if (!forceRedraw) {
forceRedraw = m_delayTimer.nsecsElapsed() + 1'000'000 >= 1'000'000'000 / m_surface->screen()->refreshRate();
forceRedraw = sync->videoFrameWait;
}
if (!forceRedraw) {
forceRedraw = m_delayTimer.nsecsElapsed() + 1000000 >= 1000000000 / m_surface->screen()->refreshRate();
}
}
mCoreSyncWaitFrameEnd(sync);
@ -476,6 +490,7 @@ void PainterGL::forceDraw() {
}
void PainterGL::stop() {
m_drawTimer.stop();
m_active = false;
m_started = false;
dequeueAll(false);
@ -499,6 +514,7 @@ void PainterGL::stop() {
}
void PainterGL::pause() {
m_drawTimer.stop();
m_active = false;
dequeueAll(true);
}

View File

@ -96,11 +96,13 @@ public:
PainterGL(QWindow* surface, const QSurfaceFormat& format);
~PainterGL();
void setThread(QThread*);
void setContext(std::shared_ptr<CoreController>);
void setMessagePainter(MessagePainter*);
void enqueue(const uint32_t* backing);
bool supportsShaders() const { return m_supportsShaders; }
int glTex();
void setVideoProxy(std::shared_ptr<VideoProxy>);
void interrupt();
@ -127,8 +129,6 @@ public slots:
void clearShaders();
VideoShader* shaders();
int glTex();
signals:
void started();
@ -150,6 +150,7 @@ private:
std::unique_ptr<QOpenGLContext> m_gl;
bool m_active = false;
bool m_started = false;
QTimer m_drawTimer;
std::shared_ptr<CoreController> m_context;
CoreController::Interrupter m_interrupter;
bool m_supportsShaders;

View File

@ -19,6 +19,7 @@
#include <QFontDatabase>
#include <QIcon>
#include <mgba/feature/updater.h>
#include <mgba-util/socket.h>
#include <mgba-util/vfs.h>
@ -39,6 +40,7 @@ mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
: QApplication(argc, argv)
, m_configController(config)
, m_updater(config)
, m_monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont))
{
g_app = this;
@ -79,7 +81,12 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
m_configController->updateOption("useDiscordPresence");
#endif
cleanupAfterUpdate();
connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup);
if (m_configController->getOption("updateAutoCheck", 0).toInt()) {
QMetaObject::invokeMethod(&m_updater, "checkUpdate", Qt::QueuedConnection);
}
}
void GBAApp::cleanup() {
@ -264,7 +271,6 @@ bool GBAApp::removeWorkerJob(qint64 jobId) {
return success;
}
bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback) {
if (!m_workerJobs.contains(jobId)) {
return false;
@ -282,6 +288,52 @@ bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> ca
return true;
}
void GBAApp::cleanupAfterUpdate() {
// Remove leftover updater if there's one present
QDir configDir(ConfigController::configDir());
QString extractedPath = configDir.filePath(QLatin1String("updater"));
#ifdef Q_OS_WIN
extractedPath += ".exe";
#endif
QFile updater(extractedPath);
if (updater.exists()) {
updater.remove();
}
#ifdef Q_OS_WIN
// Remove the installer exe if we downloaded that too
extractedPath = configDir.filePath(QLatin1String("update.exe"));
QFile update(extractedPath);
if (update.exists()) {
update.remove();
}
#endif
}
void GBAApp::restartForUpdate() {
QFileInfo updaterPath(m_updater.updateInfo().url.path());
QDir configDir(ConfigController::configDir());
if (updaterPath.completeSuffix() == "exe") {
m_invokeOnExit = configDir.filePath(QLatin1String("update.exe"));
} else {
QFile updater(":/updater");
QString extractedPath = configDir.filePath(QLatin1String("updater"));
#ifdef Q_OS_WIN
extractedPath += ".exe";
#endif
updater.copy(extractedPath);
#ifndef Q_OS_WIN
QFile(extractedPath).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
#endif
m_invokeOnExit = extractedPath;
}
for (auto& window : m_windows) {
window->deleteLater();
}
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
}
void GBAApp::finishJob(qint64 jobId) {
m_workerJobs.remove(jobId);
emit jobFinished(jobId);

View File

@ -18,6 +18,7 @@
#include <functional>
#include "ApplicationUpdater.h"
#include "CoreManager.h"
#include "MultiplayerController.h"
@ -75,6 +76,12 @@ public:
bool removeWorkerJob(qint64 jobId);
bool waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback);
ApplicationUpdater* updater() { return &m_updater; }
QString invokeOnExit() { return m_invokeOnExit; }
public slots:
void restartForUpdate();
signals:
void jobFinished(qint64 jobId);
@ -101,6 +108,8 @@ private:
Window* newWindowInternal();
void cleanupAfterUpdate();
void pauseAll(QList<Window*>* paused);
void continueAll(const QList<Window*>& paused);
@ -108,6 +117,8 @@ private:
QList<Window*> m_windows;
MultiplayerController m_multiplayer;
CoreManager m_manager;
ApplicationUpdater m_updater;
QString m_invokeOnExit;
QMap<qint64, WorkerJob*> m_workerJobs;
QMultiMap<qint64, QMetaObject::Connection> m_workerJobCallbacks;

View File

@ -27,6 +27,9 @@ QVariant LogConfigModel::data(const QModelIndex& index, int role) const {
}
int levels;
if (index.row() == 0) {
if (index.column() == 0) {
return QVariant();
}
levels = m_levels;
} else {
levels = m_cache[index.row() - 1].levels;
@ -46,6 +49,9 @@ bool LogConfigModel::setData(const QModelIndex& index, const QVariant& value, in
}
int levels;
if (index.row() == 0) {
if (index.column() == 0) {
return false;
}
levels = m_levels;
} else {
levels = m_cache[index.row() - 1].levels;
@ -60,7 +66,7 @@ bool LogConfigModel::setData(const QModelIndex& index, const QVariant& value, in
if (value.value<Qt::CheckState>() == Qt::Unchecked) {
levels &= ~bit;
} else {
levels |= bit;
levels |= bit;
}
}
if (index.row() == 0) {
@ -132,10 +138,10 @@ int LogConfigModel::rowCount(const QModelIndex& parent) const {
}
Qt::ItemFlags LogConfigModel::flags(const QModelIndex& index) const {
if (!index.isValid()) {
if (!index.isValid() || (index.row() == 0 && index.column() == 0)) {
return 0;
}
return Qt::ItemIsUserCheckable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
}
void LogConfigModel::reset() {

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2014 Jeffrey Pfau
/* Copyright (c) 2013-2021 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
@ -35,14 +35,15 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
m_pageIndex[Page::AV] = 0;
m_pageIndex[Page::INTERFACE] = 1;
m_pageIndex[Page::EMULATION] = 2;
m_pageIndex[Page::ENHANCEMENTS] = 3;
m_pageIndex[Page::BIOS] = 4;
m_pageIndex[Page::PATHS] = 5;
m_pageIndex[Page::LOGGING] = 6;
m_pageIndex[Page::UPDATE] = 2;
m_pageIndex[Page::EMULATION] = 3;
m_pageIndex[Page::ENHANCEMENTS] = 4;
m_pageIndex[Page::BIOS] = 5;
m_pageIndex[Page::PATHS] = 6;
m_pageIndex[Page::LOGGING] = 7;
#ifdef M_CORE_GB
m_pageIndex[Page::GB] = 7;
m_pageIndex[Page::GB] = 8;
for (auto model : GameBoy::modelList()) {
m_ui.gbModel->addItem(GameBoy::modelName(model), model);
@ -175,6 +176,38 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
}
#endif
ApplicationUpdater* updater = GBAApp::app()->updater();
m_ui.currentChannel->setText(ApplicationUpdater::readableChannel());
m_ui.currentVersion->setText(ApplicationUpdater::currentVersion());
QDateTime lastCheck = updater->lastCheck();
if (!lastCheck.isNull()) {
m_ui.lastChecked->setText(lastCheck.toLocalTime().toString());
}
connect(m_ui.checkUpdate, &QAbstractButton::pressed, updater, &ApplicationUpdater::checkUpdate);
connect(updater, &ApplicationUpdater::updateAvailable, this, [this, updater](bool hasUpdate) {
updateChecked();
if (hasUpdate) {
m_ui.availVersion->setText(updater->updateInfo());
}
});
for (const QString& channel : ApplicationUpdater::listChannels()) {
m_ui.updateChannel->addItem(ApplicationUpdater::readableChannel(channel), channel);
if (channel == ApplicationUpdater::currentChannel()) {
m_ui.updateChannel->setCurrentIndex(m_ui.updateChannel->count() - 1);
}
}
connect(m_ui.updateChannel, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, updater](int) {
QString channel = m_ui.updateChannel->currentData().toString();
updater->setChannel(channel);
auto updates = updater->listUpdates();
if (updates.contains(channel)) {
m_ui.availVersion->setText(updates[channel]);
} else {
m_ui.availVersion->setText(tr("None"));
}
});
m_ui.availVersion->setText(updater->updateInfo());
// TODO: Move to reloadConfig()
QVariant cameraDriver = m_controller->getQtOption("cameraDriver");
m_ui.cameraDriver->addItem(tr("None (Still Image)"), static_cast<int>(InputController::CameraDriver::NONE));
@ -331,6 +364,12 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
}
});
m_checkTimer.setInterval(60);
m_checkTimer.setSingleShot(false);
connect(&m_checkTimer, &QTimer::timeout, this, &SettingsView::updateChecked);
m_checkTimer.start();
updateChecked();
ShortcutView* shortcutView = new ShortcutView();
shortcutView->setController(shortcutController);
shortcutView->setInputController(inputController);
@ -448,6 +487,7 @@ void SettingsView::updateConfig() {
saveSetting("videoScale", m_ui.videoScale);
saveSetting("gba.forceGbp", m_ui.forceGbp);
saveSetting("vbaBugCompat", m_ui.vbaBugCompat);
saveSetting("updateAutoCheck", m_ui.updateAutoCheck);
if (m_ui.audioBufferSize->currentText().toInt() > 8192) {
m_ui.audioBufferSize->setCurrentText("8192");
@ -666,6 +706,7 @@ void SettingsView::reloadConfig() {
loadSetting("dynamicTitle", m_ui.dynamicTitle, true);
loadSetting("gba.forceGbp", m_ui.forceGbp);
loadSetting("vbaBugCompat", m_ui.vbaBugCompat, true);
loadSetting("updateAutoCheck", m_ui.updateAutoCheck);
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());
@ -788,6 +829,31 @@ void SettingsView::reloadConfig() {
}
}
void SettingsView::updateChecked() {
QDateTime now(QDateTime::currentDateTimeUtc());
QDateTime lastCheck(GBAApp::app()->updater()->lastCheck());
if (!lastCheck.isValid()) {
m_ui.lastChecked->setText(tr("Never"));
return;
}
qint64 ago = GBAApp::app()->updater()->lastCheck().secsTo(now);
if (ago < 60) {
m_ui.lastChecked->setText(tr("Just now"));
return;
}
if (ago < 3600) {
m_ui.lastChecked->setText(tr("Less than an hour ago"));
return;
}
ago /= 3600;
if (ago < 24) {
m_ui.lastChecked->setText(tr("%n hour(s) ago", nullptr, ago));
return;
}
ago /= 24;
m_ui.lastChecked->setText(tr("%n day(s) ago", nullptr, ago));
}
void SettingsView::addPage(const QString& name, QWidget* view, Page index) {
m_pageIndex[index] = m_ui.tabs->count();
m_ui.tabs->addItem(name);

View File

@ -7,6 +7,7 @@
#include <QDialog>
#include <QMap>
#include <QTimer>
#include "ColorPicker.h"
#include "LogConfigModel.h"
@ -34,6 +35,7 @@ public:
enum class Page {
AV,
INTERFACE,
UPDATE,
EMULATION,
ENHANCEMENTS,
BIOS,
@ -70,6 +72,7 @@ private slots:
void selectPath(QLineEdit*, QCheckBox*);
void updateConfig();
void reloadConfig();
void updateChecked();
private:
Ui::SettingsView m_ui;
@ -78,6 +81,7 @@ private:
InputController* m_input;
ShaderSelector* m_shader = nullptr;
LogConfigModel m_logModel;
QTimer m_checkTimer;
#ifdef M_CORE_GB
uint32_t m_gbColors[12]{};

View File

@ -50,6 +50,11 @@
<string>Interface</string>
</property>
</item>
<item>
<property name="text">
<string>Update</string>
</property>
</item>
<item>
<property name="text">
<string>Emulation</string>
@ -762,6 +767,110 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="update">
<layout class="QFormLayout" name="formLayout_11">
<item row="0" column="0">
<widget class="QLabel" name="label_46">
<property name="text">
<string>Current channel:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="currentChannel">
<property name="text">
<string notr="true">None</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_50">
<property name="text">
<string>Current version:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="currentVersion">
<property name="text">
<string notr="true">0</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="Line" name="line_20">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_45">
<property name="text">
<string>Update channel:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="updateChannel"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_44">
<property name="text">
<string>Available version:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="availVersion">
<property name="text">
<string>(Unknown)</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_49">
<property name="text">
<string>Last checked:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="lastChecked">
<property name="text">
<string notr="true">Never</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="Line" name="line_11">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="updateAutoCheck">
<property name="text">
<string>Automatically check on start</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="checkUpdate">
<property name="text">
<string>Check now</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="emulation">
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">

View File

@ -39,6 +39,12 @@ Q_IMPORT_PLUGIN(AVFServicePlugin);
#endif
#endif
#ifdef Q_OS_WIN
#include <process.h>
#else
#include <unistd.h>
#endif
using namespace QGBA;
int main(int argc, char* argv[]) {
@ -121,7 +127,21 @@ int main(int argc, char* argv[]) {
w->show();
return application.exec();
int ret = application.exec();
if (ret != 0) {
return ret;
}
QString invoke = application.invokeOnExit();
if (!invoke.isNull()) {
QByteArray proc = invoke.toUtf8();
#ifdef Q_OS_WIN
_execl(proc.constData(), proc.constData(), NULL);
#else
execl(proc.constData(), proc.constData(), NULL);
#endif
}
return ret;
}
#ifdef _WIN32

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
<RCC>
<qresource>
<file alias="updater">${CMAKE_CURRENT_BINARY_DIR}/../updater-stub${CMAKE_EXECUTABLE_SUFFIX}</file>
</qresource>
</RCC>

View File

@ -659,7 +659,7 @@ static int _batteryState(void) {
} else {
return BATTERY_NOT_PRESENT;
}
ChargerType type;
PsmChargerType type;
if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
state |= BATTERY_CHARGING;
}

View File

@ -872,7 +872,7 @@ static void _write4UpDiff(const struct CInemaImage* expected, const struct CInem
.height = expected->height * 2,
.stride = expected->width * 2,
};
out.data = malloc(out.width * out.stride * 4);
out.data = malloc(out.height * out.stride * 4);
uint32_t* outdata = out.data;
size_t x;
size_t y;
@ -882,7 +882,6 @@ static void _write4UpDiff(const struct CInemaImage* expected, const struct CInem
memcpy(&outdata[base], &((uint32_t*) expected->data)[inbase], expected->width * 4);
memcpy(&outdata[base + expected->width], &((uint32_t*) result->data)[inbase], expected->width * 4);
memcpy(&outdata[base + expected->height * out.stride], &diff[inbase * 4], expected->width * 4);
int x;
for (x = 0; x < expected->width; ++x) {
size_t pix = (expected->stride * y + x) * 4;
size_t outpix = base + expected->height * out.stride + expected->width + x;
@ -899,7 +898,7 @@ static void _write4UpDiff(const struct CInemaImage* expected, const struct CInem
}
}
_writeDiff(name, &out, frame, "4up");
free(out.data);
}
static void _writeDiffSet(const struct CInemaImage* expected, const char* name, uint8_t* diff, int frame, int max, bool xfail) {

View File

@ -137,6 +137,10 @@ bool ConfigurationHasSection(const struct Configuration* configuration, const ch
return HashTableLookup(&configuration->sections, section);
}
void ConfigurationDeleteSection(struct Configuration* configuration, const char* section) {
HashTableRemove(&configuration->sections, section);
}
const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) {
const struct Table* currentSection = &configuration->root;
if (section) {

View File

@ -163,6 +163,8 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
GUIFontPrint(params->font, 0, y * 2, GUI_ALIGN_LEFT, 0xFFFFFFFF, menu->subtitle);
}
y += 2 * lineHeight;
unsigned right;
GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0);
size_t itemsPerScreen = (params->height - y) / lineHeight;
size_t i;
for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) {
@ -174,7 +176,7 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i);
GUIFontPrint(params->font, lineHeight, y, GUI_ALIGN_LEFT, color, item->title);
if (item->validStates && item->validStates[item->state]) {
GUIFontPrintf(params->font, params->width, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]);
GUIFontPrintf(params->font, params->width - right - 8, y, GUI_ALIGN_RIGHT, color, "%s ", item->validStates[item->state]);
}
y += lineHeight;
if (y + lineHeight > params->height) {
@ -186,8 +188,6 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
size_t top = 2 * lineHeight;
size_t bottom = params->height - 8;
unsigned w;
unsigned right;
GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_BUTTON, &right, 0);
GUIFontIconMetrics(params->font, GUI_ICON_SCROLLBAR_TRACK, &w, 0);
right = (right - w) / 2;
GUIFontDrawIconSize(params->font, params->width - right - 8, top, 0, bottom - top, 0xA0FFFFFF, GUI_ICON_SCROLLBAR_TRACK);

View File

@ -200,9 +200,9 @@ static bool _vfdSync(struct VFile* vf, void* buffer, size_t size) {
UNUSED(size);
struct VFileFD* vfd = (struct VFileFD*) vf;
#ifndef _WIN32
#ifdef __HAIKU__
#ifdef HAVE_FUTIMENS
futimens(vfd->fd, NULL);
#else
#elif defined(HAVE_FUTIMES)
futimes(vfd->fd, NULL);
#endif
if (buffer && size) {