mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
915d5fde37
|
@ -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
30
CHANGES
|
@ -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)
|
||||
|
|
135
CMakeLists.txt
135
CMakeLists.txt
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -2,6 +2,7 @@ include(ExportDirectory)
|
|||
set(SOURCE_FILES
|
||||
commandline.c
|
||||
thread-proxy.c
|
||||
updater.c
|
||||
video-logger.c)
|
||||
|
||||
set(GUI_FILES
|
||||
|
|
|
@ -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
|
|
@ -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*) §ionName[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(§ion[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(§ion[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;
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]{};
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource>
|
||||
<file alias="updater">${CMAKE_CURRENT_BINARY_DIR}/../updater-stub${CMAKE_EXECUTABLE_SUFFIX}</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue