3DS: Modernize 3DS port
jump to
@@ -0,0 +1,13 @@
+language: c +compiler: + - gcc + - clang + +before_install: + - sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y + - sudo apt-add-repository ppa:zoogie/sdl2-snapshots -y + - sudo apt-get update -qq + - sudo apt-get purge cmake -qq + - sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev + +script: mkdir build && cd build && cmake .. && make
@@ -0,0 +1,127 @@
+0.2.0: (Future) +Features: + - Support for gamepad axes, e.g. analog sticks or triggers + - Add scale presets for up to 6x + - Debugger: Add CLI "frame", frame advance command + - Settings window + - Bilinear resampling option + - Add option to skip BIOS start screen + - List of recently opened games + - Support for games using the Solar Sensor + - Debugger: Add CLI functions for writing to memory + - Better audio resampling via blip-buf + - Game Pak overrides dialog for setting savetype and sensor values + - Support for games using the tilt sensor + - Remappable shortcuts for keyboard and gamepad + - Rewinding of emulation + - Implemented BIOS routines SoftReset, RegisterRamReset, Diff8bitUnFilterWram, Diff8bitUnFilterVram, and Diff16bitUnFilter + - Support IPv6 + - Save directory of last loaded file + - Support BPS patches + - Automatically detect and optimize out idle loops + - Configurable game overrides + - Support loading 7-Zip files + - Drag and drop game loading + - Cheat code support + - Debugger: Add CLI functions for examining memory regions + - Runtime configurable audio driver + - Debugger: Add CLI function for writing a register + - Libretro core for use with RetroArch and other front-ends +Bugfixes: + - ARM7: Extend prefetch by one stage + - GBA Audio: Support 16-bit writes to FIFO audio + - GBA Audio: Audio buffer sizes are now correct sizes for both sample rates + - GBA BIOS: Fix BIOS prefetch after returning from an IRQ + - GBA BIOS: Fix BIOS prefetch after reset + - GBA Memory: Fix alignment of open bus 8- and 16-bit loads + - GBA Thread: Fix possible hang when loading an archive + - Perf: Fix crash when the GBA thread fails to start + - SDL: Properly clean up if a game doesn't launch + - Debugger: Disassembly now lists PSR bitmasks (fixes #191) + - GBA BIOS: Prevent CpuSet and CpuFastSet from using BIOS addresses as a source (fixes #184) + - GBA RR: Fix fallthrough error when reading tags from a movie + - GBA Thread: Fix possible deadlock in video sync + - GBA: Fix savestate loading of DISPSTAT and WAITCNT registers + - Qt: Fix crash starting a GDB stub if a game isn't loaded + - Qt: Fix crash when adjusting settings after closing a game + - Qt: Fix crash when starting GDB stub after closing a game + - Qt: Fix patch loading while a game is running + - Util: Fix sockets on Windows + - Qt: Fix crash when loading a game after stopping GDB server + - GBA BIOS: Fix BIOS decompression routines with invalid source addresses + - GBA: Initialize gba.sync to null +Misc: + - GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples + - GBA Memory: Simplify memory API and use fixed bus width + - GBA Video: Start video at the last scanline instead of the first + - Debugger: Watchpoints now work on STM/LDM instructions + - GBA: Improve accuracy of event timing + - Debugger: Clean up GDB stub network interfacing + - Debugger: Simplify debugger state machine to play nicer with the GBA thread loop + - Debugger: Merge Thumb BL instructions when disassembling + - Debugger: Clean up debugger interface, removing obsolete state (fixes #67) + - Debugger: Watchpoints now report address watched (fixes #68) + - GBA: Add API for getting Configuration structs for overrides and input + - GBA: Refactor gba-sensors and gba-gpio into gba-hardware + - GBA: Refactor gba directory, dropping gba- prefix and making supervisor directory + - Debugger: Add support for soft breakpoints + - Util: Use proper locale for reading and writing float values + - Debugger: Make I/O register names be addresses instead of values + - Debugger: Rename read/write commands + - Qt: Optimize logo drawing + - Qt: Move frame upload back onto main thread + - All: Enable link-time optimization + - GBA Thread: Make GBASyncWaitFrameStart time out + - GBA: Move A/V stream interface into core + +0.1.1: (2015-01-24) +Bugfixes: + - ARM7: Fix LDM writeback to a register already written + - GBA: Fix timers 2 and 3 updating incorrectly + - GBA Audio: Make larger buffer sizes than 2048 actually work properly + - GBA Audio: Fix GB audio channels being too quiet (fixes #159) + - GBA Audio: Properly initialize audio FIFO channels + - GBA BIOS: Fix HLE Lz77 and RL functions to properly account for width and invalid addresses + - GBA BIOS: Fix BIOS prefetch after returning from a SWI + - GBA BIOS: Fix LZ77UnCompVram to use 16-bit loads from decompressed memory + - GBA BIOS: Fix HuffUnComp to work when games pass an invalid bit length + - GBA BIOS: Fix GetBiosChecksum to return the value of a real GBA, regardless of used BIOS + - GBA BIOS: Fix HuffUnComp boundary conditions + - GBA Memory: Don't call into GPIO write calls if GPIO devices are absent + - GBA Memory: Properly initialize 1 Mb flash, and add debug logging + - GBA Memory: Filter out top nybble of DMA addresses + - GBA Memory: Properly bounds-check VRAM accesses + - GBA Memory: Fix initial DMA state + - GBA Thread: Allow halted games to exit cleanly + - GBA Video: Fix blend issues with obscured middle layers + - GBA Video: Fix windows not disabling target 1 appropriately (fixes #161) + - GBA Video: Fix sprite mis-ordering behavior in some cases (fixes #168) + - GBA Video: Fix window interactions with 16-color mode 0 mosaic + - GBA Video: Fix sprite boundary conditions with mosaic + - GBA Video: Fix mode 0 being able to read tiles above appropriate tile range + - Qt: Fix issue with set frame sizes being the wrong height + - Qt: Fix emulator crashing when full screen if a game is not running + - Qt: Fix window focus issues + - Qt: Properly set default video recording settings + - Qt: Fix a race condition when a game crashes immediately + - Qt: Fix some cases where key mapping can break if focus is adjusted + - Qt: Fix crash if a game pauses before any frames are shown + - Debugger: Negative PC-relative loads now properly subtract the offset + - Debugger: Align PC-relative loads in Thumb + - Debugger: Fix watchpoints triggering too late + - Debugger: Fix binary print putting spaces between digits + - Video: Ensure FFmpeg encoder has audio frames + - Video: Fix uncompressed PCM audio recording + - Video: Fix FFmpeg crashing when the file extension is wrong + - Util: Fix SOCKET_FAILED macro +Misc: + - GBA: Exit cleanly on FATAL if the port supports it + - GBA Memory: Implement 16- and 32-bit loads from SRAM + - Qt: Disable sync to video by default + - Qt: Handle a game crash without crashing + - Qt: Set default log level to FATAL, ERROR and WARN + - Qt: Clarify some phrasing in the menus + - Qt: Clear active buttons when focus is lost + +0.1.0: (2014-12-13) + - Initial release
@@ -8,11 +8,18 @@ set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support") set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable ZIP support") set(USE_MAGICK ON CACHE BOOL "Whether or not to enable ImageMagick support") +set(USE_BLIP ON CACHE BOOL "Whether or not to enable blip_buf support") +set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support") set(BUILD_QT ON CACHE BOOL "Build Qt frontend") set(BUILD_SDL ON CACHE BOOL "Build SDL frontend") +set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core") set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") +set(BUILD_STATIC OFF CACHE BOOL "Build a static library") +set(BUILD_SHARED ON CACHE BOOL "Build a shared library") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c) file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) +file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) +file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c) file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)@@ -20,14 +27,16 @@ file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c)
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) source_group("ARM core" FILES ${ARM_SRC}) source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC}) +source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}}) include_directories(${CMAKE_SOURCE_DIR}/src/arm) -include_directories(${CMAKE_SOURCE_DIR}/src/gba) include_directories(${CMAKE_SOURCE_DIR}/src) if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) endif() + +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") include(GNUInstallDirs)@@ -61,16 +70,17 @@ endfunction()
# Version information set(LIB_VERSION_MAJOR 0) -set(LIB_VERSION_MINOR 1) +set(LIB_VERSION_MINOR 2) set(LIB_VERSION_PATCH 0) -set(LIB_VERSION_ABI 0.1) +set(LIB_VERSION_ABI 0.2) set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) # Advanced settings -set(BUILD_PGO CACHE BOOL "Build with profiling-guided optimization") +set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization") +set(BUILD_PGO OFF CACHE BOOL "Build with profiling-guided optimization") set(PGO_STAGE_2 CACHE BOOL "Rebuild for profiling-guided optimization after profiles have been generated") set(PGO_DIR "/tmp/gba-pgo/" CACHE PATH "Profiling-guided optimization profiles path") -mark_as_advanced(BUILD_PGO PGO_STAGE_2 PGO_DIR) +mark_as_advanced(BUILD_LTO BUILD_PGO PGO_STAGE_2 PGO_DIR) set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR} -fprofile-arcs") set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities")@@ -87,21 +97,18 @@
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}") # Feature dependencies -find_feature(USE_CLI_DEBUGGER "libedit") +set(FEATURES) +if(CMAKE_SYSTEM_NAME MATCHES .*BSD) + set(LIBEDIT_LIBRARIES -ledit) +else() + find_feature(USE_CLI_DEBUGGER "libedit") +endif() find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") find_feature(USE_PNG "ZLIB;PNG") find_feature(USE_LIBZIP "libzip") find_feature(USE_MAGICK "MagickWand") -include(CheckFunctionExists) -check_function_exists(strndup HAVE_STRNDUP) - -if(HAVE_STRNDUP) - add_definitions(-DHAVE_STRNDUP) -endif() - # Platform support -set(BINARY_TYPE SHARED) if(WIN32) add_definitions(-D_WIN32_WINNT=0x0600) list(APPEND OS_LIB ws2_32)@@ -109,94 +116,220 @@ file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
source_group("Windows-specific code" FILES ${OS_SRC}) elseif(UNIX) add_definitions(-DUSE_PTHREADS) - list(APPEND OS_LIB pthread) + if(NOT APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + endif() file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c) source_group("POSIX-specific code" FILES ${OS_SRC}) elseif(3DS) - enable_language(ASM) add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5) list(APPEND OS_LIB ctru) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/3ds/3ds-*.c) source_group("3DS-specific code" FILES ${OS_SRC}) - set(BINARY_TYPE STATIC) endif() if(APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") +endif() + +if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto") endif() if(BUILD_BBB OR BUILD_RASPI) - enable_language(ASM) if(NOT BUILD_EGL) add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5) endif() endif() +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") + enable_language(ASM) +endif() + +include(CheckFunctionExists) +check_function_exists(strndup HAVE_STRNDUP) +check_function_exists(snprintf_l HAVE_SNPRINTF_L) +if(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) + +if(HAVE_STRNDUP) + add_definitions(-DHAVE_STRNDUP) +endif() + +if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE) + add_definitions(-DHAVE_LOCALE) + if (HAVE_STRTOF_L) + add_definitions(-DHAVE_STRTOF_L) + endif() + if (HAVE_SNPRINTF_L) + add_definitions(-DHAVE_SNPRINTF_L) + endif() +endif() + + # Features set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c) +set(FEATURE_SRC) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6") if(USE_CLI_DEBUGGER) - add_definitions(-DUSE_CLI_DEBUGGER) - list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c) - list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c) - list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/gba/gba-cli.c) + list(APPEND FEATURES CLI_DEBUGGER) + list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c) + list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c) + list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c) include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS}) link_directories(${LIBEDIT_LIBRARY_DIRS}) set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES}) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libedit2") else() set(DEBUGGER_LIB "") endif() if(USE_GDB_STUB) - add_definitions(-DUSE_GDB_STUB) + list(APPEND FEATURES GDB_STUB) list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c) endif() source_group("ARM debugger" FILES ${DEBUGGER_SRC}) if(USE_FFMPEG) - add_definitions(-DUSE_FFMPEG) + list(APPEND FEATURES FFMPEG) + pkg_search_module(LIBSWRESAMPLE QUIET libswresample) + if(NOT LIBSWRESAMPLE_FOUND) + list(APPEND FEATURES LIBAV) + endif() include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS}) link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS}) - list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c") + list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c") + string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION}) + string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION}) + string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION}) + string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION}) + string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION}) list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES}) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR},libavformat${LIBAVFORMAT_VERSION_MAJOR},libavresample${LIBAVRESAMPLE_VERSION_MAJOR},libavutil${LIBAVUTIL_VERSION_MAJOR},libswscale${LIBSWSCALE_VERSION_MAJOR}") + set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libavcodec-extra") +endif() + +if(USE_BLIP) + list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c") + add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_BLIP_BUF) +else() + add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN) endif() if(USE_MAGICK) - add_definitions(-DUSE_MAGICK) + list(APPEND FEATURES MAGICK) include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS}) link_directories(${MAGICKWAND_LIBRARY_DIRS}) - list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c") + list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c") list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES}) + string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION}) + if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7") + set(MAGICKWAND_DEB_VERSION "5") + else() + set(MAGICKWAND_DEB_VERSION "-6.q16-2") + endif() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libmagickwand${MAGICKWAND_DEB_VERSION}") endif() if(USE_PNG) - add_definitions(-DUSE_PNG) + list(APPEND FEATURES PNG) include_directories(AFTER ${PNG_INCLUDE_DIRS}) list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES}) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0,zlib1g") endif() if(USE_LIBZIP) include_directories(AFTER ${LIBZIP_INCLUDE_DIRS}) link_directories(${LIBZIP_LIBRARY_DIRS}) list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES}) - add_definitions(-DENABLE_LIBZIP) + list(APPEND FEATURES LIBZIP) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2") endif() +if (USE_LZMA) + include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma) + add_definitions(-D_7ZIP_PPMD_SUPPPORT) + set(LZMA_SRC + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf2.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zCrc.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zCrcOpt.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zDec.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/CpuArch.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/LzmaDec.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/Lzma2Dec.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bra.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bra86.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/Bcj2.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zFile.c + ${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zStream.c) + list(APPEND FEATURE_SRC ${LZMA_SRC}) + list(APPEND FEATURES LZMA) +endif() + +set(FEATURE_DEFINES) +foreach(FEATURE IN LISTS FEATURES) + list(APPEND FEATURE_DEFINES "USE_${FEATURE}") +endforeach() + # Binaries -add_library(${BINARY_NAME} ${BINARY_TYPE} +set(CORE_SRC ${ARM_SRC} ${GBA_SRC} + ${GBA_RR_SRC} + ${GBA_SV_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} ${UTIL_SRC} ${VFS_SRC} ${OS_SRC} ${THIRD_PARTY_SRC}) + +set(SRC + ${CORE_SRC} + ${FEATURE_SRC}) + +if(NOT BUILD_STATIC AND NOT BUILD_SHARED) + set(BUILD_SHARED ON) +endif() + +if(BUILD_SHARED) + add_library(${BINARY_NAME} SHARED ${SRC}) + if(BUILD_STATIC) + add_library(${BINARY_NAME}-static STATIC ${SRC}) + target_compile_definitions(${BINARY_NAME}-static PRIVATE ${FEATURE_DEFINES}) + install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME}) + endif() +else() + add_library(${BINARY_NAME} STATIC ${SRC}) +endif() + target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB}) -install(TARGETS ${BINARY_NAME} DESTINATION lib) +target_compile_definitions(${BINARY_NAME} PRIVATE ${FEATURE_DEFINES}) +install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME}) set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI}) +if(BUILD_LIBRETRO) + file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c) + add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC}) + set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "") + target_compile_definitions(${BINARY_NAME}_libretro PRIVATE COLOR_16_BIT;COLOR_5_6_5 PUBLIC "" INTERFACE "") + target_link_libraries(${BINARY_NAME}_libretro m ${OS_LIB}) +endif() + if(BUILD_SDL) add_definitions(-DBUILD_SDL) add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/sdl ${CMAKE_BINARY_DIR}/sdl)@@ -214,8 +347,8 @@ endif()
add_executable(${BINARY_NAME}-perf ${PERF_SRC}) target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB}) - install(TARGETS ${BINARY_NAME}-perf DESTINATION bin) - install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}") + install(TARGETS ${BINARY_NAME}-perf DESTINATION bin COMPONENT ${BINARY_NAME}-perf) + install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf) endif() if(3DS)@@ -229,8 +362,19 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${LIB_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${LIB_VERSION_PATCH}) set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE) +set(CPACK_RESOURCE_FILE_README ${CMAKE_SOURCE_DIR}/README.md) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "mGBA Game Boy Advance Emulator") +set(CPACK_PACKAGE_VENDOR "Jeffrey Pfau") +set(CPACK_PACKAGE_CONTACT "Jeffrey Pfau <jeffrey@endrift.com>") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_DEBIAN_PACKAGE_SECTION "games") + +SET(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_STRIP_FILES ${BINARY_NAME}) + +install(FILES ${CMAKE_SOURCE_DIR}/README.md ${CMAKE_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME}) include(CPack)@@ -242,7 +386,13 @@ message(STATUS " Video recording: ${USE_FFMPEG}")
message(STATUS " GIF recording: ${USE_MAGICK}") message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}") message(STATUS " ZIP support: ${USE_LIBZIP}") +message(STATUS " 7-Zip support: ${USE_LZMA}") +message(STATUS " Better audio resampling: ${USE_BLIP}") message(STATUS "Frontend summary:") message(STATUS " Qt: ${BUILD_QT}") message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}") +message(STATUS " Libretro core: ${BUILD_LIBRETRO}") message(STATUS " Profiling: ${BUILD_PERF}") +message(STATUS "Library summary:") +message(STATUS " Static: ${BUILD_STATIC}") +message(STATUS " Shared: ${BUILD_SHARED}")
@@ -1,7 +1,11 @@
mGBA ==== -mGBA is a new emulator for running Game Boy Advance games. +mGBA is a new emulator for running Game Boy Advance games. It aims to be faster and more accurate than many existing Game Boy Advance emulators, as well as adding features that other emulators lack. + +Up-to-date news and downloads can be found at [endrift.com/mgba](https://endrift.com/mgba/). + + Features --------@@ -15,18 +19,19 @@ - A built-in BIOS implementation, and ability to load external BIOS files.
- Turbo/fast-forward support by holding Tab. - Frameskip, configurable up to 9. - Screenshot support. +- Cheat code support. - 9 savestate slots. Savestates are also viewable as screenshots. - Video and GIF recording. - Remappable controls for both keyboards and gamepads. -- Loading from ZIP files. -- IPS and UPS patch support. +- Loading from ZIP and 7z files. +- IPS, UPS and BPS patch support. - Game debugging via a command-line interface (not available with Qt port) and GDB remote support. +- Configurable emulation rewinding. ### Planned features - Local and networked multiplayer link cable support ([Bug #1](https://endrift.com/mgba/bugs/show_bug.cgi?id=1)). - Dolphin/JOY bus link cable support ([Bug #73](https://endrift.com/mgba/bugs/show_bug.cgi?id=73)). -- Cheat codes ([Bug #58](https://endrift.com/mgba/bugs/show_bug.cgi?id=58)). - Re-recording support for tool-assist runs. ([Bugzilla keyword "TASBlocker"](https://endrift.com/mgba/bugs/buglist.cgi?quicksearch=TASBlocker)) - Lua support for scripting ([Bug #62](https://endrift.com/mgba/bugs/show_bug.cgi?id=62)). - A comprehensive debug suite ([Bug #132](https://endrift.com/mgba/bugs/show_bug.cgi?id=132)).@@ -43,11 +48,27 @@ - FreeBSD
Other Unix-like platforms work as well, but are untested. +### System requirements + +Requirements are minimal. Any computer that can run Windows Vista or newer should be able to handle emulation. Support for OpenGL 1.1 or newer is also required. + Downloads --------- Downloads can be found on the official website, in the [Downloads][downloads] section. The source code can be found on [GitHub][source]. +Controls +-------- + +Controls are configurable in the menu. The default gamepad controls are mapped so as to work with a DualShock 3. The default keyboard controls are as follows: + +- **A**: X +- **B**: Z +- **L**: A +- **R**: S +- **Start**: Enter +- **Select**: Backspace + Compiling ---------@@ -55,11 +76,11 @@ Compiling requires using CMake 2.8.11 or newer. To use CMake to build on a Unix-based system, the recommended commands are as follows:
mkdir build cd build - cmake .. + cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .. make - make install + sudo make install -Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown at the end of the `cmake` command. +This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies that are installed will be automatically detected, and features that are disabled if the dependencies are not found will be shown after running the `cmake` command after warnings about being unable to find them. ### Dependencies@@ -69,7 +90,7 @@ - Qt 5: for the GUI frontend. Qt Multimedia or SDL are required for audio.
- SDL: for a more basic frontend and gamepad support in the Qt frontend. SDL 2 is recommended, but 1.2 is supported. - zlib and libpng: for screenshot support and savestate-in-PNG support. - libedit: for command-line debugger support. -- ffmpeg or libav: for video recording. Libav is untested. +- ffmpeg or libav: for video recording. - libzip: for loading ROMs stored in zip files. - ImageMagick: for GIF recording.@@ -80,7 +101,10 @@ <a name="missing">[1]</a> Currently missing features are
- OBJ window for modes 3, 4 and 5 ([Bug #5](https://endrift.com/mgba/bugs/show_bug.cgi?id=5)) - Mosaic for transformed OBJs ([Bug #9](https://endrift.com/mgba/bugs/show_bug.cgi?id=9)) -- Cartridges with light sensors (Boktai: The Sun is in Your Hand and Boktai 2: Solar Boy Django) ([Bug #46](https://endrift.com/mgba/bugs/show_bug.cgi?id=46)) +- BIOS call RegisterRamReset is partially stubbed out ([Bug #141](https://endrift.com/mgba/bugs/show_bug.cgi?id=141)) +- Audio channel reset flags ([Bug #142](https://endrift.com/mgba/bugs/show_bug.cgi?id=142)) +- Game Pak prefetch ([Bug #195](https://endrift.com/mgba/bugs/show_bug.cgi?id=195)) +- BIOS call Stop, for entering sleep mode ([Bug #199](https://endrift.com/mgba/bugs/show_bug.cgi?id=199)) <a name="flashdetect">[2]</a> Flash memory size detection does not work in some cases, and may require overrides, which are not yet user configurable. Filing a bug is recommended if such a case is encountered.@@ -92,4 +116,10 @@
Copyright --------- -mGBA is Copyright © 2013 – 2014 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.+mGBA is Copyright © 2013 – 2015 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. + +mGBA contains the following third-party libraries: + +- [inih](https://code.google.com/p/inih/), which is copyright © 2009 Brush Technology and used under a BSD 3-clause license. +- [blip-buf](https://code.google.com/p/blip-buf/), which is copyright © 2003 – 2009 Shay Green and used under a Lesser GNU Public License. +- [LZMA SDK](http://www.7-zip.org/sdk.html), which is public doman.
@@ -22,12 +22,16 @@ <key>CFBundleSignature</key>
<string>????</string> <key>CFBundleVersion</key> <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> + <key>CFBundleShortVersionString</key> + <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> <key>CSResourcesFileMapped</key> <true/> <key>NSHighResolutionCapable</key> <true/> <key>NSHumanReadableCopyright</key> <string>${MACOSX_BUNDLE_COPYRIGHT}</string> + <key>NSSupportsAutomaticGraphicsSwitching</key> + <true/> <key>CFBundleDocumentTypes</key> <array> <dict>
@@ -71,9 +71,11 @@ }
void ARMInit(struct ARMCore* cpu) { cpu->master->init(cpu, cpu->master); - int i; + size_t i; for (i = 0; i < cpu->numComponents; ++i) { - cpu->components[i]->init(cpu, cpu->components[i]); + if (cpu->components[i] && cpu->components[i]->init) { + cpu->components[i]->init(cpu, cpu->components[i]); + } } }@@ -81,9 +83,9 @@ void ARMDeinit(struct ARMCore* cpu) {
if (cpu->master->deinit) { cpu->master->deinit(cpu->master); } - int i; + size_t i; for (i = 0; i < cpu->numComponents; ++i) { - if (cpu->components[i]->deinit) { + if (cpu->components[i] && cpu->components[i]->deinit) { cpu->components[i]->deinit(cpu->components[i]); } }@@ -95,6 +97,19 @@ cpu->numComponents = extra;
cpu->components = extras; } +void ARMHotplugAttach(struct ARMCore* cpu, size_t slot) { + if (slot >= cpu->numComponents) { + return; + } + cpu->components[slot]->init(cpu, cpu->components[slot]); +} + +void ARMHotplugDetach(struct ARMCore* cpu, size_t slot) { + if (slot >= cpu->numComponents) { + return; + } + cpu->components[slot]->init(cpu, cpu->components[slot]); +} void ARMReset(struct ARMCore* cpu) { int i;@@ -176,9 +191,10 @@ cpu->cpsr.i = 1;
} static inline void ARMStep(struct ARMCore* cpu) { - uint32_t opcode = cpu->prefetch; - LOAD_32(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); + uint32_t opcode = cpu->prefetch[0]; + cpu->prefetch[0] = cpu->prefetch[1]; cpu->gprs[ARM_PC] += WORD_SIZE_ARM; + LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); unsigned condition = opcode >> 28; if (condition != 0xE) {@@ -239,9 +255,10 @@ instruction(cpu, opcode);
} static inline void ThumbStep(struct ARMCore* cpu) { - uint32_t opcode = cpu->prefetch; - LOAD_16(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); + uint32_t opcode = cpu->prefetch[0]; + cpu->prefetch[0] = cpu->prefetch[1]; cpu->gprs[ARM_PC] += WORD_SIZE_THUMB; + LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); ThumbInstruction instruction = _thumbTable[opcode >> 6]; instruction(cpu, opcode); }@@ -269,3 +286,13 @@ }
} cpu->irqh.processEvents(cpu); } + +void ARMRunFake(struct ARMCore* cpu, uint32_t opcode) { + if (cpu->executionMode== MODE_ARM) { + cpu->gprs[ARM_PC] -= WORD_SIZE_ARM; + } else { + cpu->gprs[ARM_PC] -= WORD_SIZE_THUMB; + } + cpu->prefetch[1] = cpu->prefetch[0]; + cpu->prefetch[0] = opcode; +}
@@ -93,11 +93,9 @@ int32_t packed;
}; struct ARMMemory { - int32_t (*load32)(struct ARMCore*, uint32_t address, int* cycleCounter); - int16_t (*load16)(struct ARMCore*, uint32_t address, int* cycleCounter); - uint16_t (*loadU16)(struct ARMCore*, uint32_t address, int* cycleCounter); - int8_t (*load8)(struct ARMCore*, uint32_t address, int* cycleCounter); - uint8_t (*loadU8)(struct ARMCore*, uint32_t address, int* cycleCounter); + uint32_t (*load32)(struct ARMCore*, uint32_t address, int* cycleCounter); + uint32_t (*load16)(struct ARMCore*, uint32_t address, int* cycleCounter); + uint32_t (*load8)(struct ARMCore*, uint32_t address, int* cycleCounter); void (*store32)(struct ARMCore*, uint32_t address, int32_t value, int* cycleCounter); void (*store16)(struct ARMCore*, uint32_t address, int16_t value, int* cycleCounter);@@ -123,6 +121,8 @@ void (*processEvents)(struct ARMCore* cpu);
void (*swi16)(struct ARMCore* cpu, int immediate); void (*swi32)(struct ARMCore* cpu, int immediate); void (*hitIllegal)(struct ARMCore* cpu, uint32_t opcode); + void (*bkpt16)(struct ARMCore* cpu, int immediate); + void (*bkpt32)(struct ARMCore* cpu, int immediate); void (*readCPSR)(struct ARMCore* cpu); void (*hitStub)(struct ARMCore* cpu, uint32_t opcode);@@ -149,7 +149,7 @@
int32_t shifterOperand; int32_t shifterCarryOut; - uint32_t prefetch; + uint32_t prefetch[2]; enum ExecutionMode executionMode; enum PrivilegeMode privilegeMode;@@ -158,13 +158,15 @@ struct ARMInterruptHandler irqh;
struct ARMComponent* master; - int numComponents; + size_t numComponents; struct ARMComponent** components; }; void ARMInit(struct ARMCore* cpu); void ARMDeinit(struct ARMCore* cpu); void ARMSetComponents(struct ARMCore* cpu, struct ARMComponent* master, int extra, struct ARMComponent** extras); +void ARMHotplugAttach(struct ARMCore* cpu, size_t slot); +void ARMHotplugDetach(struct ARMCore* cpu, size_t slot); void ARMReset(struct ARMCore* cpu); void ARMSetPrivilegeMode(struct ARMCore*, enum PrivilegeMode);@@ -173,5 +175,6 @@ void ARMRaiseSWI(struct ARMCore*);
void ARMRun(struct ARMCore* cpu); void ARMRunLoop(struct ARMCore* cpu); +void ARMRunFake(struct ARMCore* cpu, uint32_t opcode); #endif
@@ -47,7 +47,7 @@
#define ADDR_MODE_1_IMM \ int rotate = (opcode & 0x00000F00) >> 7; \ int immediate = opcode & 0x000000FF; \ - info->op3.immediate = ARM_ROR(immediate, rotate); \ + info->op3.immediate = ROR(immediate, rotate); \ info->operandFormat |= ARM_OPERAND_IMMEDIATE_3; #define ADDR_MODE_2_SHIFT(OP) \@@ -213,7 +213,6 @@ ARM_MEMORY_POST_INCREMENT | \
ARM_MEMORY_WRITEBACK, \ ADDRESSING_MODE, CYCLES, TYPE) \ DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## P, MNEMONIC, \ - ARM_MEMORY_PRE_INCREMENT | \ ARM_MEMORY_OFFSET_SUBTRACT, \ ADDRESSING_MODE, CYCLES, TYPE) \ DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## PW, MNEMONIC, \@@ -222,10 +221,9 @@ ARM_MEMORY_WRITEBACK | \
ARM_MEMORY_OFFSET_SUBTRACT, \ ADDRESSING_MODE, CYCLES, TYPE) \ DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## PU, MNEMONIC, \ - ARM_MEMORY_PRE_INCREMENT, \ + 0, \ ADDRESSING_MODE, CYCLES, TYPE) \ DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## PUW, MNEMONIC, \ - ARM_MEMORY_PRE_INCREMENT | \ ARM_MEMORY_WRITEBACK, \ ADDRESSING_MODE, CYCLES, TYPE)@@ -243,10 +241,12 @@
#define DEFINE_LOAD_STORE_T_DECODER_SET_ARM(NAME, MNEMONIC, ADDRESSING_MODE, CYCLES, TYPE) \ DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME, MNEMONIC, \ ARM_MEMORY_POST_INCREMENT | \ + ARM_MEMORY_WRITEBACK | \ ARM_MEMORY_OFFSET_SUBTRACT, \ ADDRESSING_MODE, CYCLES, TYPE) \ DEFINE_LOAD_STORE_DECODER_EX_ARM(NAME ## U, MNEMONIC, \ - ARM_MEMORY_POST_INCREMENT, \ + ARM_MEMORY_POST_INCREMENT | \ + ARM_MEMORY_WRITEBACK, \ ADDRESSING_MODE, CYCLES, TYPE) #define DEFINE_LOAD_STORE_T_DECODER_ARM(NAME, MNEMONIC, CYCLES, TYPE) \@@ -386,6 +386,7 @@
DEFINE_DECODER_ARM(MSR, MSR, info->affectsCPSR = 1; info->op1.reg = ARM_CPSR; + info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK; info->op2.reg = opcode & 0x0000000F; info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 |@@ -393,42 +394,45 @@ ARM_OPERAND_REGISTER_2;)
DEFINE_DECODER_ARM(MSRR, MSR, info->op1.reg = ARM_SPSR; + info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK; info->op2.reg = opcode & 0x0000000F; info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 | ARM_OPERAND_REGISTER_2;) -DEFINE_DECODER_ARM(MRS, MRS, info->affectsCPSR = 1; +DEFINE_DECODER_ARM(MRS, MRS, info->affectsCPSR = 1; info->op1.reg = (opcode >> 12) & 0xF; info->op2.reg = ARM_CPSR; + info->op2.psrBits = 0; info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 | ARM_OPERAND_REGISTER_2;) -DEFINE_DECODER_ARM(MRSR, MRS, info->affectsCPSR = 1; - info->affectsCPSR = 1; +DEFINE_DECODER_ARM(MRSR, MRS, info->op1.reg = (opcode >> 12) & 0xF; info->op2.reg = ARM_SPSR; + info->op2.psrBits = 0; info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 | ARM_OPERAND_REGISTER_2;) -DEFINE_DECODER_ARM(MSRI, MSR, info->affectsCPSR = 1; +DEFINE_DECODER_ARM(MSRI, MSR, int rotate = (opcode & 0x00000F00) >> 7; - int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate); + int32_t operand = ROR(opcode & 0x000000FF, rotate); info->affectsCPSR = 1; info->op1.reg = ARM_CPSR; + info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK; info->op2.immediate = operand; info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 | ARM_OPERAND_IMMEDIATE_2;) -DEFINE_DECODER_ARM(MSRRI, MSR, info->affectsCPSR = 1; +DEFINE_DECODER_ARM(MSRRI, MSR, int rotate = (opcode & 0x00000F00) >> 7; - int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate); - info->affectsCPSR = 1; + int32_t operand = ROR(opcode & 0x000000FF, rotate); info->op1.reg = ARM_SPSR; + info->op1.psrBits = (opcode >> 16) & ARM_PSR_MASK; info->op2.immediate = operand; info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 |
@@ -290,14 +290,21 @@ info->op1.immediate = (((int32_t) immediate) >> 4);
info->operandFormat = ARM_OPERAND_IMMEDIATE_1; info->branchType = ARM_BRANCH;) -DEFINE_THUMB_DECODER(BL1, BLH, +DEFINE_THUMB_DECODER(BL1, BL, int16_t immediate = (opcode & 0x07FF) << 5; - info->op1.immediate = (((int32_t) immediate) << 7); - info->operandFormat = ARM_OPERAND_IMMEDIATE_1;) + info->op1.reg = ARM_LR; + info->op2.reg = ARM_PC; + info->op3.immediate = (((int32_t) immediate) << 7); + info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 | + ARM_OPERAND_REGISTER_2 | ARM_OPERAND_AFFECTED_2 | + ARM_OPERAND_IMMEDIATE_3;) DEFINE_THUMB_DECODER(BL2, BL, - info->op1.immediate = (opcode & 0x07FF) << 1; - info->operandFormat = ARM_OPERAND_IMMEDIATE_1; + info->op1.reg = ARM_PC; + info->op2.reg = ARM_LR; + info->op3.immediate = (opcode & 0x07FF) << 1; + info->operandFormat = ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1 | + ARM_OPERAND_REGISTER_2 | ARM_OPERAND_IMMEDIATE_3; info->branchType = ARM_BRANCH_LINKED;) DEFINE_THUMB_DECODER(BX, BX,@@ -332,3 +339,33 @@ info->cCycles = 0;
ThumbDecoder decoder = _thumbDecoderTable[opcode >> 6]; decoder(opcode, info); } + +bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, struct ARMInstructionInfo* out) { + if (info1->execMode != MODE_THUMB || info1->mnemonic != ARM_MN_BL) { + return false; + } + if (info2->execMode != MODE_THUMB || info2->mnemonic != ARM_MN_BL) { + return false; + } + if (info1->op1.reg != ARM_LR || info1->op2.reg != ARM_PC) { + return false; + } + if (info2->op1.reg != ARM_PC || info2->op2.reg != ARM_LR) { + return false; + } + out->op1.immediate = info1->op3.immediate | info2->op3.immediate; + out->operandFormat = ARM_OPERAND_IMMEDIATE_1; + out->execMode = MODE_THUMB; + out->mnemonic = ARM_MN_BL; + out->branchType = ARM_BRANCH_LINKED; + out->traps = 0; + out->affectsCPSR = 0; + out->condition = ARM_CONDITION_AL; + out->sDataCycles = 0; + out->nDataCycles = 0; + out->sInstructionCycles = 2; + out->nInstructionCycles = 0; + out->iCycles = 0; + out->cCycles = 0; + return true; +}
@@ -18,6 +18,7 @@ blen -= AMOUNT;
static int _decodeRegister(int reg, char* buffer, int blen); static int _decodeRegisterList(int list, char* buffer, int blen); +static int _decodePSR(int bits, char* buffer, int blen); static int _decodePCRelative(uint32_t address, uint32_t pc, char* buffer, int blen); static int _decodeMemory(struct ARMMemoryAccess memory, int pc, char* buffer, int blen); static int _decodeShift(union ARMOperand operand, bool reg, char* buffer, int blen);@@ -113,6 +114,32 @@ ADVANCE(1);
return total; } +static int _decodePSR(int psrBits, char* buffer, int blen) { + if (!psrBits) { + return 0; + } + int total = 0; + strncpy(buffer, "_", blen - 1); + ADVANCE(1); + if (psrBits & ARM_PSR_C) { + strncpy(buffer, "c", blen - 1); + ADVANCE(1); + } + if (psrBits & ARM_PSR_X) { + strncpy(buffer, "x", blen - 1); + ADVANCE(1); + } + if (psrBits & ARM_PSR_S) { + strncpy(buffer, "s", blen - 1); + ADVANCE(1); + } + if (psrBits & ARM_PSR_F) { + strncpy(buffer, "f", blen - 1); + ADVANCE(1); + } + return total; +} + static int _decodePCRelative(uint32_t address, uint32_t pc, char* buffer, int blen) { return snprintf(buffer, blen - 1, "$%08X", address + pc); }@@ -127,7 +154,7 @@ ADVANCE(1);
int written; if (memory.format & ARM_MEMORY_REGISTER_BASE) { if (memory.baseReg == ARM_PC && memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) { - written = _decodePCRelative(memory.offset.immediate, pc, buffer, blen); + written = _decodePCRelative(memory.format & ARM_MEMORY_OFFSET_SUBTRACT ? -memory.offset.immediate : memory.offset.immediate, pc & 0xFFFFFFFC, buffer, blen); ADVANCE(written); } else { written = _decodeRegister(memory.baseReg, buffer, blen);@@ -223,7 +250,6 @@ "b",
"bic", "bkpt", "bl", - "blh", "bx", "cmn", "cmp",@@ -355,8 +381,11 @@ ADVANCE(1);
} break; case ARM_MN_B: - written = _decodePCRelative(info->op1.immediate, pc, buffer, blen); - ADVANCE(written); + case ARM_MN_BL: + if (info->operandFormat & ARM_OPERAND_IMMEDIATE_1) { + written = _decodePCRelative(info->op1.immediate, pc, buffer, blen); + ADVANCE(written); + } break; default: if (info->operandFormat & ARM_OPERAND_IMMEDIATE_1) {@@ -368,6 +397,10 @@ ADVANCE(written);
} else if (info->operandFormat & ARM_OPERAND_REGISTER_1) { written = _decodeRegister(info->op1.reg, buffer, blen); ADVANCE(written); + if (info->op1.reg > ARM_PC) { + written = _decodePSR(info->op1.psrBits, buffer, blen); + ADVANCE(written); + } } if (info->operandFormat & ARM_OPERAND_SHIFT_REGISTER_1) { written = _decodeShift(info->op1, true, buffer, blen);
@@ -62,6 +62,12 @@ #define ARM_MEMORY_DECREMENT_BEFORE 0x0200
#define ARM_MEMORY_INCREMENT_BEFORE 0x0300 #define ARM_MEMORY_SPSR_SWAP 0x0400 +#define ARM_PSR_C 1 +#define ARM_PSR_X 2 +#define ARM_PSR_S 4 +#define ARM_PSR_F 8 +#define ARM_PSR_MASK 0xF + #define MEMORY_FORMAT_TO_DIRECTION(F) (((F) >> 8) & 0x3) enum ARMCondition {@@ -99,6 +105,7 @@ uint8_t shifterOp;
union { uint8_t shifterReg; uint8_t shifterImm; + uint8_t psrBits; }; }; int32_t immediate;@@ -138,7 +145,6 @@ ARM_MN_B,
ARM_MN_BIC, ARM_MN_BKPT, ARM_MN_BL, - ARM_MN_BLH, ARM_MN_BX, ARM_MN_CMN, ARM_MN_CMP,@@ -203,6 +209,7 @@ };
void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info); void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info); +bool ARMDecodeThumbCombine(struct ARMInstructionInfo* info1, struct ARMInstructionInfo* info2, struct ARMInstructionInfo* out); int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, int blen); #endif
@@ -138,7 +138,7 @@ static inline void _shiftROR(struct ARMCore* cpu, uint32_t opcode) {
int rm = opcode & 0x0000000F; int immediate = (opcode & 0x00000F80) >> 7; if (immediate) { - cpu->shifterOperand = ARM_ROR(cpu->gprs[rm], immediate); + cpu->shifterOperand = ROR(cpu->gprs[rm], immediate); cpu->shifterCarryOut = (cpu->gprs[rm] >> (immediate - 1)) & 1; } else { // RRX@@ -165,7 +165,7 @@ if (!shift) {
cpu->shifterOperand = shiftVal; cpu->shifterCarryOut = cpu->cpsr.c; } else if (rotate) { - cpu->shifterOperand = ARM_ROR(shiftVal, rotate); + cpu->shifterOperand = ROR(shiftVal, rotate); cpu->shifterCarryOut = (shiftVal >> (rotate - 1)) & 1; } else { cpu->shifterOperand = shiftVal;@@ -180,7 +180,7 @@ if (!rotate) {
cpu->shifterOperand = immediate; cpu->shifterCarryOut = cpu->cpsr.c; } else { - cpu->shifterOperand = ARM_ROR(immediate, rotate); + cpu->shifterOperand = ROR(immediate, rotate); cpu->shifterCarryOut = ARM_SIGN(cpu->shifterOperand); } }@@ -237,7 +237,7 @@ #define ADDR_MODE_2_WRITEBACK(ADDR) (cpu->gprs[rn] = ADDR)
#define ADDR_MODE_2_LSL (cpu->gprs[rm] << ADDR_MODE_2_I) #define ADDR_MODE_2_LSR (ADDR_MODE_2_I_TEST ? ((uint32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : 0) #define ADDR_MODE_2_ASR (ADDR_MODE_2_I_TEST ? ((int32_t) cpu->gprs[rm]) >> ADDR_MODE_2_I : ((int32_t) cpu->gprs[rm]) >> 31) -#define ADDR_MODE_2_ROR (ADDR_MODE_2_I_TEST ? ARM_ROR(cpu->gprs[rm], ADDR_MODE_2_I) : (cpu->cpsr.c << 31) | (((uint32_t) cpu->gprs[rm]) >> 1)) +#define ADDR_MODE_2_ROR (ADDR_MODE_2_I_TEST ? ROR(cpu->gprs[rm], ADDR_MODE_2_I) : (cpu->cpsr.c << 31) | (((uint32_t) cpu->gprs[rm]) >> 1)) #define ADDR_MODE_3_ADDRESS ADDR_MODE_2_ADDRESS #define ADDR_MODE_3_RN ADDR_MODE_2_RN@@ -246,7 +246,12 @@ #define ADDR_MODE_3_IMMEDIATE (((opcode & 0x00000F00) >> 4) | (opcode & 0x0000000F))
#define ADDR_MODE_3_INDEX(U_OP, M) ADDR_MODE_2_INDEX(U_OP, M) #define ADDR_MODE_3_WRITEBACK(ADDR) ADDR_MODE_2_WRITEBACK(ADDR) -#define ADDR_MODE_4_WRITEBACK cpu->gprs[rn] = address +#define ADDR_MODE_4_WRITEBACK_LDM \ + if (!((1 << rn) & rs)) { \ + cpu->gprs[rn] = address; \ + } + +#define ADDR_MODE_4_WRITEBACK_STM cpu->gprs[rn] = address; #define ARM_LOAD_POST_BODY \ ++currentCycles; \@@ -405,22 +410,22 @@ WRITEBACK;)
#define DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_ARM(NAME, LS, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DA, LS, , , , DA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DAW, LS, ADDR_MODE_4_WRITEBACK, , , DA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DB, LS, , , , DB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DBW, LS, ADDR_MODE_4_WRITEBACK, , , DB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IA, LS, , , , IA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IAW, LS, ADDR_MODE_4_WRITEBACK, , , IA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IB, LS, , , , IB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IBW, LS, ADDR_MODE_4_WRITEBACK, , , IB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDA, LS, , ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDAW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDB, LS, , ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDBW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIA, LS, , ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIAW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIB, LS, , ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY) \ - DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIBW, LS, ADDR_MODE_4_WRITEBACK, ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY) + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DA, LS, , , , DA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , DA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DB, LS, , , , DB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## DBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , DB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IA, LS, , , , IA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , IA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IB, LS, , , , IB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## IBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, , , IB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDA, LS, , ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, DA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDB, LS, , ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SDBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, DB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIA, LS, , ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIAW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, IA, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIB, LS, , ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY) \ + DEFINE_LOAD_STORE_MULTIPLE_INSTRUCTION_EX_ARM(NAME ## SIBW, LS, ADDR_MODE_4_WRITEBACK_ ## NAME, ARM_MS_PRE, ARM_MS_POST, IB, POST_BODY) // Begin ALU definitions@@ -519,10 +524,10 @@
// Begin load/store definitions DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDR, cpu->gprs[rd] = cpu->memory.load32(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDRB, cpu->gprs[rd] = cpu->memory.loadU8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRH, cpu->gprs[rd] = cpu->memory.loadU16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSB, cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = cpu->memory.load16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_INSTRUCTION_ARM(LDRB, cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRH, cpu->gprs[rd] = cpu->memory.load16(cpu, address, ¤tCycles); ARM_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, address, ¤tCycles)); ARM_LOAD_POST_BODY;) DEFINE_LOAD_STORE_INSTRUCTION_ARM(STR, cpu->memory.store32(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;) DEFINE_LOAD_STORE_INSTRUCTION_ARM(STRB, cpu->memory.store8(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;) DEFINE_LOAD_STORE_MODE_3_INSTRUCTION_ARM(STRH, cpu->memory.store16(cpu, address, cpu->gprs[rd], ¤tCycles); ARM_STORE_POST_BODY;)@@ -530,7 +535,7 @@
DEFINE_LOAD_STORE_T_INSTRUCTION_ARM(LDRBT, enum PrivilegeMode priv = cpu->privilegeMode; ARMSetPrivilegeMode(cpu, MODE_USER); - cpu->gprs[rd] = cpu->memory.loadU8(cpu, address, ¤tCycles); + cpu->gprs[rd] = cpu->memory.load8(cpu, address, ¤tCycles); ARMSetPrivilegeMode(cpu, priv); ARM_LOAD_POST_BODY;)@@ -578,7 +583,7 @@ DEFINE_INSTRUCTION_ARM(SWPB,
int rm = opcode & 0xF; int rd = (opcode >> 12) & 0xF; int rn = (opcode >> 16) & 0xF; - int32_t d = cpu->memory.loadU8(cpu, cpu->gprs[rn], ¤tCycles); + int32_t d = cpu->memory.load8(cpu, cpu->gprs[rn], ¤tCycles); cpu->memory.store8(cpu, cpu->gprs[rn], cpu->gprs[rm], ¤tCycles); cpu->gprs[rd] = d;)@@ -620,7 +625,7 @@ DEFINE_INSTRUCTION_ARM(MRC, ARM_STUB)
// Begin miscellaneous definitions -DEFINE_INSTRUCTION_ARM(BKPT, ARM_STUB) // Not strictly in ARMv4T, but here for convenience +DEFINE_INSTRUCTION_ARM(BKPT, cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF))); // Not strictly in ARMv4T, but here for convenience DEFINE_INSTRUCTION_ARM(ILL, ARM_ILL) // Illegal opcode DEFINE_INSTRUCTION_ARM(MSR,@@ -657,7 +662,7 @@ DEFINE_INSTRUCTION_ARM(MSRI,
int c = opcode & 0x00010000; int f = opcode & 0x00080000; int rotate = (opcode & 0x00000F00) >> 7; - int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate); + int32_t operand = ROR(opcode & 0x000000FF, rotate); int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); if (mask & PSR_USER_MASK) { cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK);@@ -672,7 +677,7 @@ DEFINE_INSTRUCTION_ARM(MSRRI,
int c = opcode & 0x00010000; int f = opcode & 0x00080000; int rotate = (opcode & 0x00000F00) >> 7; - int32_t operand = ARM_ROR(opcode & 0x000000FF, rotate); + int32_t operand = ROR(opcode & 0x000000FF, rotate); int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK; cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask);)
@@ -27,8 +27,8 @@ #define ARM_COND_LE (cpu->cpsr.z || !cpu->cpsr.n != !cpu->cpsr.v)
#define ARM_COND_AL 1 #define ARM_SIGN(I) ((I) >> 31) -#define ARM_ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31))) - +#define ARM_SXT_8(I) (((int8_t) (I) << 24) >> 24) +#define ARM_SXT_16(I) (((int16_t) (I) << 16) >> 16) #define ARM_CARRY_FROM(M, N, D) (((uint32_t) (M) >> 31) + ((uint32_t) (N) >> 31) > ((uint32_t) (D) >> 31)) #define ARM_BORROW_FROM(M, N, D) (((uint32_t) (M)) >= ((uint32_t) (N)))@@ -52,15 +52,17 @@
#define ARM_WRITE_PC \ cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_ARM); \ cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \ - LOAD_32(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ + LOAD_32(cpu->prefetch[0], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ cpu->gprs[ARM_PC] += WORD_SIZE_ARM; \ + LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ currentCycles += 2 + cpu->memory.activeUncachedCycles32 + cpu->memory.activeSeqCycles32; #define THUMB_WRITE_PC \ cpu->gprs[ARM_PC] = (cpu->gprs[ARM_PC] & -WORD_SIZE_THUMB); \ cpu->memory.setActiveRegion(cpu, cpu->gprs[ARM_PC]); \ - LOAD_16(cpu->prefetch, cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ + LOAD_16(cpu->prefetch[0], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ cpu->gprs[ARM_PC] += WORD_SIZE_THUMB; \ + LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); \ currentCycles += 2 + cpu->memory.activeUncachedCycles16 + cpu->memory.activeSeqCycles16; static inline int _ARMModeHasSPSR(enum PrivilegeMode mode) {@@ -87,6 +89,17 @@ static inline void _ARMReadCPSR(struct ARMCore* cpu) {
_ARMSetMode(cpu, cpu->cpsr.t); ARMSetPrivilegeMode(cpu, cpu->cpsr.priv); cpu->irqh.readCPSR(cpu); +} + +static inline uint32_t _ARMPCAddress(struct ARMCore* cpu) { + int instructionLength; + enum ExecutionMode mode = cpu->cpsr.t; + if (mode == MODE_ARM) { + instructionLength = WORD_SIZE_ARM; + } else { + instructionLength = WORD_SIZE_THUMB; + } + return cpu->gprs[ARM_PC] - instructionLength * 2; } #endif
@@ -97,8 +97,8 @@ }
THUMB_NEUTRAL_S( , , cpu->gprs[rd]);) DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDR1, cpu->gprs[rd] = cpu->memory.load32(cpu, cpu->gprs[rm] + immediate * 4, ¤tCycles); THUMB_LOAD_POST_BODY;) -DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRB1, cpu->gprs[rd] = cpu->memory.loadU8(cpu, cpu->gprs[rm] + immediate, ¤tCycles); THUMB_LOAD_POST_BODY;) -DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRH1, cpu->gprs[rd] = cpu->memory.loadU16(cpu, cpu->gprs[rm] + immediate * 2, ¤tCycles); THUMB_LOAD_POST_BODY;) +DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRB1, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rm] + immediate, ¤tCycles); THUMB_LOAD_POST_BODY;) +DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(LDRH1, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rm] + immediate * 2, ¤tCycles); THUMB_LOAD_POST_BODY;) DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STR1, cpu->memory.store32(cpu, cpu->gprs[rm] + immediate * 4, cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;) DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STRB1, cpu->memory.store8(cpu, cpu->gprs[rm] + immediate, cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;) DEFINE_IMMEDIATE_5_INSTRUCTION_THUMB(STRH1, cpu->memory.store16(cpu, cpu->gprs[rm] + immediate * 2, cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)@@ -219,7 +219,7 @@ if (rs) {
int r4 = rs & 0x1F; if (r4 > 0) { cpu->cpsr.c = (cpu->gprs[rd] >> (r4 - 1)) & 1; - cpu->gprs[rd] = ARM_ROR(cpu->gprs[rd], r4); + cpu->gprs[rd] = ROR(cpu->gprs[rd], r4); } else { cpu->cpsr.c = ARM_SIGN(cpu->gprs[rd]); }@@ -286,10 +286,10 @@ #define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, BODY) \
COUNT_CALL_3(DEFINE_LOAD_STORE_WITH_REGISTER_EX_THUMB, NAME ## _R, BODY) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, cpu->gprs[rd] = cpu->memory.load32(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, cpu->gprs[rd] = cpu->memory.loadU8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.loadU16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, cpu->gprs[rd] = cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, cpu->gprs[rd] = cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles); THUMB_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, cpu->gprs[rd] = ARM_SXT_8(cpu->memory.load8(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles)); THUMB_LOAD_POST_BODY;) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, cpu->gprs[rd] = ARM_SXT_16(cpu->memory.load16(cpu, cpu->gprs[rn] + cpu->gprs[rm], ¤tCycles)); THUMB_LOAD_POST_BODY;) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, cpu->memory.store32(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, cpu->memory.store8(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;) DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, cpu->memory.store16(cpu, cpu->gprs[rn] + cpu->gprs[rm], cpu->gprs[rd], ¤tCycles); THUMB_STORE_POST_BODY;)@@ -381,7 +381,7 @@ THUMB_STORE_POST_BODY;
cpu->gprs[ARM_SP] = address) DEFINE_INSTRUCTION_THUMB(ILL, ARM_ILL) -DEFINE_INSTRUCTION_THUMB(BKPT, ARM_STUB) +DEFINE_INSTRUCTION_THUMB(BKPT, cpu->irqh.bkpt16(cpu, opcode & 0xFF);) DEFINE_INSTRUCTION_THUMB(B, int16_t immediate = (opcode & 0x07FF) << 5; cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4);
@@ -69,4 +69,7 @@ #define DECL_BIT(TYPE, FIELD, BIT) DECL_BITS(TYPE, FIELD, BIT, 1)
#define LIKELY(X) __builtin_expect(!!(X), 1) #define UNLIKELY(X) __builtin_expect(!!(X), 0) + +#define ROR(I, ROTATE) ((((uint32_t) (I)) >> ROTATE) | ((uint32_t) (I) << ((-ROTATE) & 31))) + #endif
@@ -14,6 +14,7 @@ #include <pthread.h>
#endif static const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share +static const char* ERROR_OVERFLOW = "Arguments overflow"; static struct CLIDebugger* _activeDebugger;@@ -34,15 +35,26 @@ static void _reset(struct CLIDebugger*, struct CLIDebugVector*);
static void _readHalfword(struct CLIDebugger*, struct CLIDebugVector*); static void _readWord(struct CLIDebugger*, struct CLIDebugVector*); static void _setBreakpoint(struct CLIDebugger*, struct CLIDebugVector*); +static void _setBreakpointARM(struct CLIDebugger*, struct CLIDebugVector*); +static void _setBreakpointThumb(struct CLIDebugger*, struct CLIDebugVector*); static void _clearBreakpoint(struct CLIDebugger*, struct CLIDebugVector*); static void _setWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); +static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*); +static void _writeHalfword(struct CLIDebugger*, struct CLIDebugVector*); +static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*); +static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*); +static void _dumpByte(struct CLIDebugger*, struct CLIDebugVector*); +static void _dumpHalfword(struct CLIDebugger*, struct CLIDebugVector*); +static void _dumpWord(struct CLIDebugger*, struct CLIDebugVector*); static void _breakIntoDefault(int signal); static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum ExecutionMode mode); -static void _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode); +static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode); static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, + { "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, + { "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, { "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, { "c", _continue, 0, "Continue execution" }, { "continue", _continue, 0, "Continue execution" },@@ -71,14 +83,21 @@ { "print/t", _printBin, CLIDVParse, "Print a value as binary" },
{ "print/x", _printHex, CLIDVParse, "Print a value as hexadecimal" }, { "q", _quit, 0, "Quit the emulator" }, { "quit", _quit, 0, "Quit the emulator" }, - { "rb", _readByte, CLIDVParse, "Read a byte from a specified offset" }, { "reset", _reset, 0, "Reset the emulation" }, - { "rh", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" }, - { "rw", _readWord, CLIDVParse, "Read a word from a specified offset" }, + { "r/1", _readByte, CLIDVParse, "Read a byte from a specified offset" }, + { "r/2", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" }, + { "r/4", _readWord, CLIDVParse, "Read a word from a specified offset" }, { "status", _printStatus, 0, "Print the current status" }, { "w", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, { "watch", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, - { "x", _breakInto, 0, "Break into attached debugger (for developers)" }, + { "w/1", _writeByte, CLIDVParse, "Write a byte at a specified offset" }, + { "w/2", _writeHalfword, CLIDVParse, "Write a halfword at a specified offset" }, + { "w/4", _writeWord, CLIDVParse, "Write a word at a specified offset" }, + { "w/r", _writeRegister, CLIDVParse, "Write a register" }, + { "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" }, + { "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" }, + { "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" }, + { "!", _breakInto, 0, "Break into attached debugger (for developers)" }, { 0, 0, 0, 0 } };@@ -122,6 +141,12 @@ }
static void _next(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); + if (debugger->d.currentBreakpoint) { + if (debugger->d.currentBreakpoint->isSw && debugger->d.setSoftwareBreakpoint) { + debugger->d.setSoftwareBreakpoint(&debugger->d, debugger->d.currentBreakpoint->address, debugger->d.currentBreakpoint->sw.mode, &debugger->d.currentBreakpoint->sw.opcode); + } + debugger->d.currentBreakpoint = 0; + } ARMRun(debugger->d.cpu); _printStatus(debugger, 0); }@@ -165,8 +190,7 @@ }
int i; for (i = 0; i < size; ++i) { - _printLine(debugger, address, mode); - address += wordSize; + address += _printLine(debugger, address, mode);; } }@@ -184,7 +208,7 @@ for ( ; dv; dv = dv->next) {
printf(" 0b"); int i = 32; while (i--) { - printf(" %u", (dv->intValue >> i) & 1); + printf("%u", (dv->intValue >> i) & 1); } } printf("\n");@@ -231,7 +255,7 @@ }
} } -static inline void _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) { +static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) { char disassembly[48]; struct ARMInstructionInfo info; printf("%08X: ", address);@@ -240,11 +264,23 @@ uint32_t instruction = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0);
ARMDecodeARM(instruction, &info); ARMDisassemble(&info, address + WORD_SIZE_ARM * 2, disassembly, sizeof(disassembly)); printf("%08X\t%s\n", instruction, disassembly); + return WORD_SIZE_ARM; } else { - uint16_t instruction = debugger->d.cpu->memory.loadU16(debugger->d.cpu, address, 0); + struct ARMInstructionInfo info2; + struct ARMInstructionInfo combined; + uint16_t instruction = debugger->d.cpu->memory.load16(debugger->d.cpu, address, 0); + uint16_t instruction2 = debugger->d.cpu->memory.load16(debugger->d.cpu, address + WORD_SIZE_THUMB, 0); ARMDecodeThumb(instruction, &info); - ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); - printf("%04X\t%s\n", instruction, disassembly); + ARMDecodeThumb(instruction2, &info2); + if (ARMDecodeThumbCombine(&info, &info2, &combined)) { + ARMDisassemble(&combined, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); + printf("%04X %04X\t%s\n", instruction, instruction2, disassembly); + return WORD_SIZE_THUMB * 2; + } else { + ARMDisassemble(&info, address + WORD_SIZE_THUMB * 2, disassembly, sizeof(disassembly)); + printf("%04X \t%s\n", instruction, disassembly); + return WORD_SIZE_THUMB; + } } }@@ -280,7 +316,7 @@ printf("%s\n", ERROR_MISSING_ARGS);
return; } uint32_t address = dv->intValue; - uint8_t value = debugger->d.cpu->memory.loadU8(debugger->d.cpu, address, 0); + uint8_t value = debugger->d.cpu->memory.load8(debugger->d.cpu, address, 0); printf(" 0x%02X\n", value); }@@ -296,7 +332,7 @@ printf("%s\n", ERROR_MISSING_ARGS);
return; } uint32_t address = dv->intValue; - uint16_t value = debugger->d.cpu->memory.loadU16(debugger->d.cpu, address, 0); + uint16_t value = debugger->d.cpu->memory.load16(debugger->d.cpu, address & ~1, 0); printf(" 0x%04X\n", value); }@@ -306,10 +342,149 @@ printf("%s\n", ERROR_MISSING_ARGS);
return; } uint32_t address = dv->intValue; - uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0); + uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address & ~3, 0); printf(" 0x%08X\n", value); } +static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + uint32_t value = dv->next->intValue; + if (value > 0xFF) { + printf("%s\n", ERROR_OVERFLOW); + return; + } + debugger->d.cpu->memory.store8(debugger->d.cpu, address, value, 0); +} + +static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + uint32_t value = dv->next->intValue; + if (value > 0xFFFF) { + printf("%s\n", ERROR_OVERFLOW); + return; + } + debugger->d.cpu->memory.store16(debugger->d.cpu, address, value, 0); +} + +static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + uint32_t value = dv->next->intValue; + debugger->d.cpu->memory.store32(debugger->d.cpu, address, value, 0); +} + +static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t regid = dv->intValue; + uint32_t value = dv->next->intValue; + if (regid >= ARM_PC) { + return; + } + debugger->d.cpu->gprs[regid] = value; +} + +static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + uint32_t words = 16; + if (dv->next && dv->next->type == CLIDV_INT_TYPE) { + words = dv->next->intValue; + } + while (words) { + uint32_t line = 16; + if (line > words) { + line = words; + } + printf("0x%08X:", address); + for (; line > 0; --line, ++address, --words) { + uint32_t value = debugger->d.cpu->memory.load8(debugger->d.cpu, address, 0); + printf(" %02X", value); + } + printf("\n"); + } +} + +static void _dumpHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + uint32_t words = 8; + if (dv->next && dv->next->type == CLIDV_INT_TYPE) { + words = dv->next->intValue; + } + while (words) { + uint32_t line = 8; + if (line > words) { + line = words; + } + printf("0x%08X:", address); + for (; line > 0; --line, address += 2, --words) { + uint32_t value = debugger->d.cpu->memory.load16(debugger->d.cpu, address, 0); + printf(" %04X", value); + } + printf("\n"); + } +} + +static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + uint32_t words = 4; + if (dv->next && dv->next->type == CLIDV_INT_TYPE) { + words = dv->next->intValue; + } + while (words) { + uint32_t line = 4; + if (line > words) { + line = words; + } + printf("0x%08X:", address); + for (; line > 0; --line, address += 4, --words) { + uint32_t value = debugger->d.cpu->memory.load32(debugger->d.cpu, address, 0); + printf(" %08X", value); + } + printf("\n"); + } +} + static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!dv || dv->type != CLIDV_INT_TYPE) { printf("%s\n", ERROR_MISSING_ARGS);@@ -319,6 +494,24 @@ uint32_t address = dv->intValue;
ARMDebuggerSetBreakpoint(&debugger->d, address); } +static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_ARM); +} + +static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + uint32_t address = dv->intValue; + ARMDebuggerSetSoftwareBreakpoint(&debugger->d, address, MODE_THUMB); +} + static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!dv || dv->type != CLIDV_INT_TYPE) { printf("%s\n", ERROR_MISSING_ARGS);@@ -339,7 +532,7 @@ }
static void _breakIntoDefault(int signal) { UNUSED(signal); - ARMDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL); + ARMDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL, 0); } static uint32_t _performOperation(enum Operation operation, uint32_t current, uint32_t next, struct CLIDebugVector* dv) {@@ -572,7 +765,6 @@ HistEvent ev;
while (debugger->state == DEBUGGER_PAUSED) { line = el_gets(cliDebugger->elstate, &count); if (!line) { - debugger->state = DEBUGGER_EXITING; return; } if (line[0] == '\n') {@@ -586,20 +778,32 @@ }
} } -static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) { +static void _reportEntry(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) { UNUSED(debugger); switch (reason) { case DEBUGGER_ENTER_MANUAL: case DEBUGGER_ENTER_ATTACHED: break; case DEBUGGER_ENTER_BREAKPOINT: - printf("Hit breakpoint\n"); + if (info) { + printf("Hit breakpoint at 0x%08X\n", info->address); + } else { + printf("Hit breakpoint\n"); + } break; case DEBUGGER_ENTER_WATCHPOINT: - printf("Hit watchpoint\n"); + if (info) { + printf("Hit watchpoint at 0x%08X: (old value = 0x%08X)\n", info->address, info->oldValue); + } else { + printf("Hit watchpoint\n"); + } break; case DEBUGGER_ENTER_ILLEGAL_OP: - printf("Hit illegal opcode\n"); + if (info) { + printf("Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->opcode); + } else { + printf("Hit illegal opcode\n"); + } break; } }@@ -684,10 +888,22 @@ cliDebugger->system = 0;
} } +static void _cliDebuggerCustom(struct ARMDebugger* debugger) { + struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; + bool retain = false; + if (cliDebugger->system) { + retain = cliDebugger->system->custom(cliDebugger->system); + } + if (!retain && debugger->state == DEBUGGER_CUSTOM) { + debugger->state = DEBUGGER_RUNNING; + } +} + void CLIDebuggerCreate(struct CLIDebugger* debugger) { ARMDebuggerCreate(&debugger->d); debugger->d.init = _cliDebuggerInit; debugger->d.deinit = _cliDebuggerDeinit; + debugger->d.custom = _cliDebuggerCustom; debugger->d.paused = _commandLine; debugger->d.entered = _reportEntry;
@@ -43,6 +43,7 @@ struct CLIDebugger* p;
void (*init)(struct CLIDebuggerSystem*); void (*deinit)(struct CLIDebuggerSystem*); + bool (*custom)(struct CLIDebuggerSystem*); uint32_t (*lookupIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv);
@@ -6,13 +6,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "debugger.h" #include "arm.h" +#include "isa-inlines.h" #include "memory-debugger.h" const uint32_t ARM_DEBUGGER_ID = 0xDEADBEEF; +static struct DebugBreakpoint* _lookupBreakpoint(struct DebugBreakpoint* breakpoints, uint32_t address) { + for (; breakpoints; breakpoints = breakpoints->next) { + if (breakpoints->address == address) { + return breakpoints; + } + } + return 0; +} + static void _checkBreakpoints(struct ARMDebugger* debugger) { - struct DebugBreakpoint* breakpoint; int instructionLength; enum ExecutionMode mode = debugger->cpu->cpsr.t; if (mode == MODE_ARM) {@@ -20,12 +29,14 @@ instructionLength = WORD_SIZE_ARM;
} else { instructionLength = WORD_SIZE_THUMB; } - for (breakpoint = debugger->breakpoints; breakpoint; breakpoint = breakpoint->next) { - if (breakpoint->address + instructionLength == (uint32_t) debugger->cpu->gprs[ARM_PC]) { - ARMDebuggerEnter(debugger, DEBUGGER_ENTER_BREAKPOINT); - break; - } + struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength); + if (!breakpoint) { + return; } + struct DebuggerEntryInfo info = { + .address = breakpoint->address + }; + ARMDebuggerEnter(debugger, DEBUGGER_ENTER_BREAKPOINT, &info); } static void ARMDebuggerInit(struct ARMCore*, struct ARMComponent*);@@ -42,8 +53,10 @@ struct ARMDebugger* debugger = (struct ARMDebugger*) component;
debugger->cpu = cpu; debugger->state = DEBUGGER_RUNNING; debugger->breakpoints = 0; + debugger->swBreakpoints = 0; debugger->originalMemory = cpu->memory; debugger->watchpoints = 0; + debugger->currentBreakpoint = 0; if (debugger->init) { debugger->init(debugger); }@@ -55,41 +68,56 @@ debugger->deinit(debugger);
} void ARMDebuggerRun(struct ARMDebugger* debugger) { - if (debugger->state == DEBUGGER_EXITING) { - debugger->state = DEBUGGER_RUNNING; - } - while (debugger->state < DEBUGGER_EXITING) { - if (!debugger->breakpoints) { - while (debugger->state == DEBUGGER_RUNNING) { - ARMRun(debugger->cpu); - } + switch (debugger->state) { + case DEBUGGER_RUNNING: + if (!debugger->breakpoints && !debugger->watchpoints) { + ARMRunLoop(debugger->cpu); } else { - while (debugger->state == DEBUGGER_RUNNING) { - ARMRun(debugger->cpu); - _checkBreakpoints(debugger); - } + ARMRun(debugger->cpu); + _checkBreakpoints(debugger); } - switch (debugger->state) { - case DEBUGGER_RUNNING: - break; - case DEBUGGER_PAUSED: - if (debugger->paused) { - debugger->paused(debugger); - } else { - debugger->state = DEBUGGER_RUNNING; + break; + case DEBUGGER_CUSTOM: + ARMRun(debugger->cpu); + _checkBreakpoints(debugger); + debugger->custom(debugger); + break; + case DEBUGGER_PAUSED: + if (debugger->paused) { + debugger->paused(debugger); + } else { + debugger->state = DEBUGGER_RUNNING; + } + if (debugger->state != DEBUGGER_PAUSED && debugger->currentBreakpoint) { + if (debugger->currentBreakpoint->isSw && debugger->setSoftwareBreakpoint) { + debugger->setSoftwareBreakpoint(debugger, debugger->currentBreakpoint->address, debugger->currentBreakpoint->sw.mode, &debugger->currentBreakpoint->sw.opcode); } - break; - case DEBUGGER_EXITING: - case DEBUGGER_SHUTDOWN: - return; + debugger->currentBreakpoint = 0; } + break; + case DEBUGGER_SHUTDOWN: + return; } } -void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) { +void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) { debugger->state = DEBUGGER_PAUSED; + struct ARMCore* cpu = debugger->cpu; + cpu->nextEvent = 0; + if (reason == DEBUGGER_ENTER_BREAKPOINT) { + struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu)); + debugger->currentBreakpoint = breakpoint; + if (breakpoint && breakpoint->isSw) { + info->address = breakpoint->address; + if (debugger->clearSoftwareBreakpoint) { + debugger->clearSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, breakpoint->sw.opcode); + } + + ARMRunFake(cpu, breakpoint->sw.opcode); + } + } if (debugger->entered) { - debugger->entered(debugger, reason); + debugger->entered(debugger, reason, info); } }@@ -97,9 +125,27 @@ void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address) {
struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint)); breakpoint->address = address; breakpoint->next = debugger->breakpoints; + breakpoint->isSw = false; debugger->breakpoints = breakpoint; } +bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode) { + uint32_t opcode; + if (!debugger->setSoftwareBreakpoint || !debugger->setSoftwareBreakpoint(debugger, address, mode, &opcode)) { + return false; + } + + struct DebugBreakpoint* breakpoint = malloc(sizeof(struct DebugBreakpoint)); + breakpoint->address = address; + breakpoint->next = debugger->swBreakpoints; + breakpoint->isSw = true; + breakpoint->sw.opcode = opcode; + breakpoint->sw.mode = mode; + debugger->swBreakpoints = breakpoint; + + return true; +} + void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address) { struct DebugBreakpoint** previous = &debugger->breakpoints; struct DebugBreakpoint* breakpoint;@@ -115,15 +161,15 @@ void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address) {
if (!debugger->watchpoints) { ARMDebuggerInstallMemoryShim(debugger); } - struct DebugBreakpoint* watchpoint = malloc(sizeof(struct DebugBreakpoint)); + struct DebugWatchpoint* watchpoint = malloc(sizeof(struct DebugWatchpoint)); watchpoint->address = address; watchpoint->next = debugger->watchpoints; debugger->watchpoints = watchpoint; } void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address) { - struct DebugBreakpoint** previous = &debugger->watchpoints; - struct DebugBreakpoint* breakpoint; + struct DebugWatchpoint** previous = &debugger->watchpoints; + struct DebugWatchpoint* breakpoint; for (; (breakpoint = *previous); previous = &breakpoint->next) { if (breakpoint->address == address) { *previous = breakpoint->next;
@@ -15,13 +15,30 @@
enum DebuggerState { DEBUGGER_PAUSED, DEBUGGER_RUNNING, - DEBUGGER_EXITING, + DEBUGGER_CUSTOM, DEBUGGER_SHUTDOWN }; struct DebugBreakpoint { struct DebugBreakpoint* next; uint32_t address; + bool isSw; + struct { + uint32_t opcode; + enum ExecutionMode mode; + } sw; +}; + +enum WatchpointType { + WATCHPOINT_WRITE = 1, + WATCHPOINT_READ = 2, + WATCHPOINT_RW = 3 +}; + +struct DebugWatchpoint { + struct DebugWatchpoint* next; + uint32_t address; + enum WatchpointType type; }; enum DebuggerEntryReason {@@ -32,6 +49,20 @@ DEBUGGER_ENTER_WATCHPOINT,
DEBUGGER_ENTER_ILLEGAL_OP }; +struct DebuggerEntryInfo { + uint32_t address; + union { + struct { + uint32_t oldValue; + enum WatchpointType watchType; + }; + + struct { + uint32_t opcode; + }; + }; +}; + enum DebuggerLogLevel { DEBUGGER_LOG_DEBUG = 0x01, DEBUGGER_LOG_INFO = 0x02,@@ -45,13 +76,20 @@ enum DebuggerState state;
struct ARMCore* cpu; struct DebugBreakpoint* breakpoints; - struct DebugBreakpoint* watchpoints; + struct DebugBreakpoint* swBreakpoints; + struct DebugWatchpoint* watchpoints; struct ARMMemory originalMemory; + struct DebugBreakpoint* currentBreakpoint; + void (*init)(struct ARMDebugger*); void (*deinit)(struct ARMDebugger*); void (*paused)(struct ARMDebugger*); - void (*entered)(struct ARMDebugger*, enum DebuggerEntryReason); + void (*entered)(struct ARMDebugger*, enum DebuggerEntryReason, struct DebuggerEntryInfo*); + void (*custom)(struct ARMDebugger*); + + bool (*setSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); + bool (*clearSoftwareBreakpoint)(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode); __attribute__((format (printf, 3, 4))) void (*log)(struct ARMDebugger*, enum DebuggerLogLevel, const char* format, ...);@@ -59,8 +97,9 @@ };
void ARMDebuggerCreate(struct ARMDebugger*); void ARMDebuggerRun(struct ARMDebugger*); -void ARMDebuggerEnter(struct ARMDebugger*, enum DebuggerEntryReason); +void ARMDebuggerEnter(struct ARMDebugger*, enum DebuggerEntryReason, struct DebuggerEntryInfo*); void ARMDebuggerSetBreakpoint(struct ARMDebugger* debugger, uint32_t address); +bool ARMDebuggerSetSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode); void ARMDebuggerClearBreakpoint(struct ARMDebugger* debugger, uint32_t address); void ARMDebuggerSetWatchpoint(struct ARMDebugger* debugger, uint32_t address); void ARMDebuggerClearWatchpoint(struct ARMDebugger* debugger, uint32_t address);
@@ -5,12 +5,13 @@ * 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 "gdb-stub.h" -#include <errno.h> #include <signal.h> #ifndef SIGTRAP #define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */ #endif + +#define SOCKET_TIMEOUT 50 enum GDBError { GDB_NO_ERROR = 0x00,@@ -32,15 +33,33 @@ GDBStubShutdown(stub);
} } -static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason) { +static void _gdbStubEntered(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) { struct GDBStub* stub = (struct GDBStub*) debugger; switch (reason) { case DEBUGGER_ENTER_MANUAL: snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT); break; case DEBUGGER_ENTER_BREAKPOINT: + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); + break; case DEBUGGER_ENTER_WATCHPOINT: // TODO: Make watchpoints raise with address - snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); + if (info) { + const char* type = 0; + switch (info->watchType) { + case WATCHPOINT_WRITE: + type = "watch"; + break; + case WATCHPOINT_READ: + type = "rwatch"; + break; + case WATCHPOINT_RW: + type = "awatch"; + break; + } + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08X", SIGTRAP, type, info->address); + } else { + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); + } break; case DEBUGGER_ENTER_ILLEGAL_OP: snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL);@@ -53,15 +72,19 @@ }
static void _gdbStubPoll(struct ARMDebugger* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; - while (stub->d.state == DEBUGGER_PAUSED) { - if (!SOCKET_FAILED(stub->connection)) { - if (!SocketSetBlocking(stub->connection, 1)) { - GDBStubHangup(stub); - return; - } - } - GDBStubUpdate(stub); + --stub->untilPoll; + if (stub->untilPoll > 0) { + return; } + stub->untilPoll = GDB_STUB_INTERVAL; + stub->shouldBlock = false; + GDBStubUpdate(stub); +} + +static void _gdbStubWait(struct ARMDebugger* debugger) { + struct GDBStub* stub = (struct GDBStub*) debugger; + stub->shouldBlock = true; + GDBStubUpdate(stub); } static void _ack(struct GDBStub* stub) {@@ -169,13 +192,8 @@ _sendMessage(stub);
} static void _continue(struct GDBStub* stub, const char* message) { - stub->d.state = DEBUGGER_RUNNING; - if (!SOCKET_FAILED(stub->connection)) { - if (!SocketSetBlocking(stub->connection, 0)) { - GDBStubHangup(stub); - return; - } - } + stub->d.state = DEBUGGER_CUSTOM; + stub->untilPoll = GDB_STUB_INTERVAL; // TODO: parse message UNUSED(message); }@@ -278,7 +296,7 @@ static void _processVReadCommand(struct GDBStub* stub, const char* message) {
stub->outgoing[0] = '\0'; if (!strncmp("Attach", message, 6)) { strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4); - ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL); + ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); } _sendMessage(stub); }@@ -347,7 +365,7 @@ case '$':
++message; break; case '\x03': - ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL); + ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); return parsed; default: _nak(stub);@@ -441,46 +459,47 @@ stub->socket = INVALID_SOCKET;
stub->connection = INVALID_SOCKET; stub->d.init = 0; stub->d.deinit = _gdbStubDeinit; - stub->d.paused = _gdbStubPoll; + stub->d.paused = _gdbStubWait; stub->d.entered = _gdbStubEntered; + stub->d.custom = _gdbStubPoll; stub->d.log = 0; + stub->untilPoll = GDB_STUB_INTERVAL; } -int GDBStubListen(struct GDBStub* stub, int port, uint32_t bindAddress) { +bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) { if (!SOCKET_FAILED(stub->socket)) { GDBStubShutdown(stub); } - // TODO: support IPv6 stub->socket = SocketOpenTCP(port, bindAddress); if (SOCKET_FAILED(stub->socket)) { if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't open socket"); } - return 0; + return false; + } + if (!SocketSetBlocking(stub->socket, false)) { + goto cleanup; } int err = SocketListen(stub->socket, 1); if (err) { goto cleanup; } - if (!SocketSetBlocking(stub->socket, 0)) { - goto cleanup; - } - return 1; + return true; cleanup: if (stub->d.log) { stub->d.log(&stub->d, DEBUGGER_LOG_ERROR, "Couldn't listen on port"); } SocketClose(stub->socket); - stub->socket = -1; - return 0; + stub->socket = INVALID_SOCKET; + return false; } void GDBStubHangup(struct GDBStub* stub) { if (!SOCKET_FAILED(stub->connection)) { SocketClose(stub->connection); - stub->connection = -1; + stub->connection = INVALID_SOCKET; } if (stub->d.state == DEBUGGER_PAUSED) { stub->d.state = DEBUGGER_RUNNING;@@ -491,34 +510,45 @@ void GDBStubShutdown(struct GDBStub* stub) {
GDBStubHangup(stub); if (!SOCKET_FAILED(stub->socket)) { SocketClose(stub->socket); - stub->socket = -1; + stub->socket = INVALID_SOCKET; } } void GDBStubUpdate(struct GDBStub* stub) { if (stub->socket == INVALID_SOCKET) { + if (stub->d.state == DEBUGGER_PAUSED) { + stub->d.state = DEBUGGER_RUNNING; + } return; } if (stub->connection == INVALID_SOCKET) { - stub->connection = SocketAccept(stub->socket, 0, 0); + if (stub->shouldBlock) { + Socket reads = stub->socket; + SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); + } + stub->connection = SocketAccept(stub->socket, 0); if (!SOCKET_FAILED(stub->connection)) { - if (!SocketSetBlocking(stub->connection, 0)) { + if (!SocketSetBlocking(stub->connection, false)) { goto connectionLost; } - ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED); - } else if (errno == EWOULDBLOCK || errno == EAGAIN) { + ARMDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0); + } else if (SocketWouldBlock()) { return; } else { goto connectionLost; } } while (true) { + if (stub->shouldBlock) { + Socket reads = stub->connection; + SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); + } ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1); if (messageLen == 0) { goto connectionLost; } if (messageLen == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) { + if (SocketWouldBlock()) { return; } goto connectionLost;
@@ -13,6 +13,7 @@
#include "util/socket.h" #define GDB_STUB_MAX_LINE 1200 +#define GDB_STUB_INTERVAL 32 enum GDBStubAckState { GDB_ACK_PENDING = 0,@@ -30,10 +31,13 @@ enum GDBStubAckState lineAck;
Socket socket; Socket connection; + + bool shouldBlock; + int untilPoll; }; void GDBStubCreate(struct GDBStub*); -int GDBStubListen(struct GDBStub*, int port, uint32_t bindAddress); +bool GDBStubListen(struct GDBStub*, int port, const struct Address* bindAddress); void GDBStubHangup(struct GDBStub*); void GDBStubShutdown(struct GDBStub*);
@@ -9,12 +9,18 @@ #include "debugger.h"
#include <string.h> -static bool _checkWatchpoints(struct DebugBreakpoint* watchpoints, uint32_t address, int width); +static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width); + +static uint32_t _popcount32(unsigned bits) { + bits = bits - ((bits >> 1) & 0x55555555); + bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333); + return (((bits + (bits >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; +} #define FIND_DEBUGGER(DEBUGGER, CPU) \ { \ DEBUGGER = 0; \ - int i; \ + size_t i; \ for (i = 0; i < CPU->numComponents; ++i) { \ if (CPU->components[i]->id == ARM_DEBUGGER_ID) { \ DEBUGGER = (struct ARMDebugger*) cpu->components[i]; \@@ -34,26 +40,65 @@ #define CREATE_WATCHPOINT_SHIM(NAME, WIDTH, RETURN, TYPES, ARGS...) \
static RETURN ARMDebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - if (_checkWatchpoints(debugger->watchpoints, address, WIDTH)) { \ - ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT); \ + struct DebuggerEntryInfo info; \ + if (_checkWatchpoints(debugger, address, &info, WIDTH)) { \ + ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \ } \ return debugger->originalMemory.NAME(cpu, ARGS); \ } -CREATE_WATCHPOINT_SHIM(load32, 4, int32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(load16, 2, int16_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(loadU16, 2, uint16_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(load8, 1, int8_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) -CREATE_WATCHPOINT_SHIM(loadU8, 1, uint8_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) +#define CREATE_MULTIPLE_WATCHPOINT_SHIM(NAME) \ + static uint32_t ARMDebuggerShim_ ## NAME (struct ARMCore* cpu, uint32_t address, int mask, enum LSMDirection direction, int* cycleCounter) { \ + struct ARMDebugger* debugger; \ + FIND_DEBUGGER(debugger, cpu); \ + uint32_t popcount = _popcount32(mask); \ + int offset = 4; \ + int base = address; \ + if (direction & LSM_D) { \ + offset = -4; \ + base -= (popcount << 2) - 4; \ + } \ + if (direction & LSM_B) { \ + base += offset; \ + } \ + unsigned i; \ + for (i = 0; i < popcount; ++i) { \ + struct DebuggerEntryInfo info; \ + if (_checkWatchpoints(debugger, base + 4 * i, &info, 4)) { \ + ARMDebuggerEnter(debugger, DEBUGGER_ENTER_WATCHPOINT, &info); \ + } \ + } \ + return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \ + } + +CREATE_WATCHPOINT_SHIM(load32, 4, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) +CREATE_WATCHPOINT_SHIM(load16, 2, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) +CREATE_WATCHPOINT_SHIM(load8, 1, uint32_t, (struct ARMCore* cpu, uint32_t address, int* cycleCounter), address, cycleCounter) CREATE_WATCHPOINT_SHIM(store32, 4, void, (struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter), address, value, cycleCounter) CREATE_WATCHPOINT_SHIM(store16, 2, void, (struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter), address, value, cycleCounter) CREATE_WATCHPOINT_SHIM(store8, 1, void, (struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter), address, value, cycleCounter) +CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple) +CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple) CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address) -static bool _checkWatchpoints(struct DebugBreakpoint* watchpoints, uint32_t address, int width) { - width -= 1; - for (; watchpoints; watchpoints = watchpoints->next) { +static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct DebuggerEntryInfo* info, int width) { + --width; + struct DebugWatchpoint* watchpoints; + for (watchpoints = debugger->watchpoints; watchpoints; watchpoints = watchpoints->next) { if (!((watchpoints->address ^ address) & ~width)) { + switch (width + 1) { + case 1: + info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); + break; + case 2: + info->oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0); + break; + case 4: + info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0); + break; + } + info->address = address; + info->watchType = watchpoints->type; return true; } }@@ -67,9 +112,9 @@ debugger->cpu->memory.store16 = ARMDebuggerShim_store16;
debugger->cpu->memory.store8 = ARMDebuggerShim_store8; debugger->cpu->memory.load32 = ARMDebuggerShim_load32; debugger->cpu->memory.load16 = ARMDebuggerShim_load16; - debugger->cpu->memory.loadU16 = ARMDebuggerShim_loadU16; debugger->cpu->memory.load8 = ARMDebuggerShim_load8; - debugger->cpu->memory.loadU8 = ARMDebuggerShim_loadU8; + debugger->cpu->memory.storeMultiple = ARMDebuggerShim_storeMultiple; + debugger->cpu->memory.loadMultiple = ARMDebuggerShim_loadMultiple; debugger->cpu->memory.setActiveRegion = ARMDebuggerShim_setActiveRegion; }@@ -79,8 +124,8 @@ debugger->cpu->memory.store16 = debugger->originalMemory.store16;
debugger->cpu->memory.store8 = debugger->originalMemory.store8; debugger->cpu->memory.load32 = debugger->originalMemory.load32; debugger->cpu->memory.load16 = debugger->originalMemory.load16; - debugger->cpu->memory.loadU16 = debugger->originalMemory.loadU16; debugger->cpu->memory.load8 = debugger->originalMemory.load8; - debugger->cpu->memory.loadU8 = debugger->originalMemory.loadU8; + debugger->cpu->memory.storeMultiple = debugger->originalMemory.storeMultiple; + debugger->cpu->memory.loadMultiple = debugger->originalMemory.loadMultiple; debugger->cpu->memory.setActiveRegion = debugger->originalMemory.setActiveRegion; }
@@ -0,0 +1,575 @@
+/* Copyright (c) 2013-2015 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 "bios.h" + +#include "gba/gba.h" +#include "gba/io.h" +#include "gba/memory.h" +#include "isa-inlines.h" + +const uint32_t GBA_BIOS_CHECKSUM = 0xBAAE187F; +const uint32_t GBA_DS_BIOS_CHECKSUM = 0xBAAE1880; + +static void _unLz77(struct GBA* gba, int width); +static void _unHuffman(struct GBA* gba); +static void _unRl(struct GBA* gba, int width); +static void _unFilter(struct GBA* gba, int inwidth, int outwidth); + +static void _SoftReset(struct GBA* gba) { + struct ARMCore* cpu = gba->cpu; + ARMSetPrivilegeMode(cpu, MODE_IRQ); + cpu->spsr.packed = 0; + cpu->gprs[ARM_LR] = 0; + cpu->gprs[ARM_SP] = SP_BASE_IRQ; + ARMSetPrivilegeMode(cpu, MODE_SUPERVISOR); + cpu->spsr.packed = 0; + cpu->gprs[ARM_LR] = 0; + cpu->gprs[ARM_SP] = SP_BASE_SUPERVISOR; + ARMSetPrivilegeMode(cpu, MODE_SYSTEM); + cpu->gprs[ARM_LR] = 0; + cpu->gprs[ARM_SP] = SP_BASE_SYSTEM; + int8_t flag = ((int8_t*) gba->memory.iwram)[0x7FFA]; + memset(((int8_t*) gba->memory.iwram) + SIZE_WORKING_IRAM - 0x200, 0, 0x200); + if (flag) { + cpu->gprs[ARM_PC] = BASE_WORKING_RAM; + } else { + cpu->gprs[ARM_PC] = BASE_CART0; + } + _ARMSetMode(cpu, MODE_ARM); + int currentCycles = 0; + ARM_WRITE_PC; +} + +static void _RegisterRamReset(struct GBA* gba) { + uint32_t registers = gba->cpu->gprs[0]; + struct ARMCore* cpu = gba->cpu; + cpu->memory.store16(cpu, BASE_IO | REG_DISPCNT, 0x0080, 0); + if (registers & 0x01) { + memset(gba->memory.wram, 0, SIZE_WORKING_RAM); + } + if (registers & 0x02) { + memset(gba->memory.iwram, 0, SIZE_WORKING_IRAM - 0x200); + } + if (registers & 0x04) { + memset(gba->video.palette, 0, SIZE_PALETTE_RAM); + } + if (registers & 0x08) { + memset(gba->video.renderer->vram, 0, SIZE_VRAM); + } + if (registers & 0x10) { + memset(gba->video.oam.raw, 0, SIZE_OAM); + } + if (registers & 0x20) { + GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on SIO unimplemented"); + } + if (registers & 0x40) { + GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on Audio unimplemented"); + } + if (registers & 0x80) { + GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on IO unimplemented"); + } +} + +static void _BgAffineSet(struct GBA* gba) { + struct ARMCore* cpu = gba->cpu; + int i = cpu->gprs[2]; + float ox, oy; + float cx, cy; + float sx, sy; + float theta; + int offset = cpu->gprs[0]; + int destination = cpu->gprs[1]; + float a, b, c, d; + float rx, ry; + while (i--) { + // [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ] + // [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ] + // [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] + ox = cpu->memory.load32(cpu, offset, 0) / 256.f; + oy = cpu->memory.load32(cpu, offset + 4, 0) / 256.f; + cx = (int16_t) cpu->memory.load16(cpu, offset + 8, 0); + cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0); + sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f; + sy = (int16_t) cpu->memory.load16(cpu, offset + 14, 0) / 256.f; + theta = (cpu->memory.load16(cpu, offset + 16, 0) >> 8) / 128.f * M_PI; + offset += 20; + // Rotation + a = d = cosf(theta); + b = c = sinf(theta); + // Scale + a *= sx; + b *= -sx; + c *= sy; + d *= sy; + // Translate + rx = ox - (a * cx + b * cy); + ry = oy - (c * cx + d * cy); + cpu->memory.store16(cpu, destination, a * 256, 0); + cpu->memory.store16(cpu, destination + 2, b * 256, 0); + cpu->memory.store16(cpu, destination + 4, c * 256, 0); + cpu->memory.store16(cpu, destination + 6, d * 256, 0); + cpu->memory.store32(cpu, destination + 8, rx * 256, 0); + cpu->memory.store32(cpu, destination + 12, ry * 256, 0); + destination += 16; + } +} + +static void _ObjAffineSet(struct GBA* gba) { + struct ARMCore* cpu = gba->cpu; + int i = cpu->gprs[2]; + float sx, sy; + float theta; + int offset = cpu->gprs[0]; + int destination = cpu->gprs[1]; + int diff = cpu->gprs[3]; + float a, b, c, d; + while (i--) { + // [ sx 0 ] [ cos(theta) -sin(theta) ] [ A B ] + // [ 0 sy ] * [ sin(theta) cos(theta) ] = [ C D ] + sx = (int16_t) cpu->memory.load16(cpu, offset, 0) / 256.f; + sy = (int16_t) cpu->memory.load16(cpu, offset + 2, 0) / 256.f; + theta = (cpu->memory.load16(cpu, offset + 4, 0) >> 8) / 128.f * M_PI; + offset += 8; + // Rotation + a = d = cosf(theta); + b = c = sinf(theta); + // Scale + a *= sx; + b *= -sx; + c *= sy; + d *= sy; + cpu->memory.store16(cpu, destination, a * 256, 0); + cpu->memory.store16(cpu, destination + diff, b * 256, 0); + cpu->memory.store16(cpu, destination + diff * 2, c * 256, 0); + cpu->memory.store16(cpu, destination + diff * 3, d * 256, 0); + destination += diff * 4; + } +} + +static void _MidiKey2Freq(struct GBA* gba) { + struct ARMCore* cpu = gba->cpu; + uint32_t key = cpu->memory.load32(cpu, cpu->gprs[0] + 4, 0); + cpu->gprs[0] = key / powf(2, (180.f - cpu->gprs[1] - cpu->gprs[2] / 256.f) / 12.f); +} + +static void _Div(struct GBA* gba, int32_t num, int32_t denom) { + struct ARMCore* cpu = gba->cpu; + if (denom != 0) { + div_t result = div(num, denom); + cpu->gprs[0] = result.quot; + cpu->gprs[1] = result.rem; + cpu->gprs[3] = abs(result.quot); + } else { + GBALog(gba, GBA_LOG_GAME_ERROR, "Attempting to divide %i by zero!", num); + // If abs(num) > 1, this should hang, but that would be painful to + // emulate in HLE, and no game will get into a state where it hangs... + cpu->gprs[0] = (num < 0) ? -1 : 1; + cpu->gprs[1] = num; + cpu->gprs[3] = 1; + } +} + +void GBASwi16(struct ARMCore* cpu, int immediate) { + struct GBA* gba = (struct GBA*) cpu->master; + GBALog(gba, GBA_LOG_SWI, "SWI: %02X r0: %08X r1: %08X r2: %08X r3: %08X", + immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); + + if (gba->memory.fullBios) { + ARMRaiseSWI(cpu); + return; + } + switch (immediate) { + case 0x0: + _SoftReset(gba); + break; + case 0x1: + _RegisterRamReset(gba); + break; + case 0x2: + GBAHalt(gba); + break; + case 0x05: + // VBlankIntrWait + // Fall through: + case 0x04: + // IntrWait + ARMRaiseSWI(cpu); + break; + case 0x6: + _Div(gba, cpu->gprs[0], cpu->gprs[1]); + break; + case 0x7: + _Div(gba, cpu->gprs[1], cpu->gprs[0]); + break; + case 0x8: + cpu->gprs[0] = sqrt(cpu->gprs[0]); + break; + case 0xA: + cpu->gprs[0] = atan2f(cpu->gprs[1] / 16384.f, cpu->gprs[0] / 16384.f) / (2 * M_PI) * 0x10000; + break; + case 0xB: + case 0xC: + if (cpu->gprs[0] >> BASE_OFFSET == REGION_BIOS) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Cannot CpuSet from BIOS"); + return; + } + ARMRaiseSWI(cpu); + break; + case 0xD: + cpu->gprs[0] = GBA_BIOS_CHECKSUM; + cpu->gprs[1] = 1; + cpu->gprs[3] = SIZE_BIOS; + break; + case 0xE: + _BgAffineSet(gba); + break; + case 0xF: + _ObjAffineSet(gba); + break; + case 0x11: + case 0x12: + if (cpu->gprs[0] < BASE_WORKING_RAM) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 source"); + break; + } + switch (cpu->gprs[1] >> BASE_OFFSET) { + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination"); + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unLz77(gba, immediate == 0x11 ? 1 : 2); + break; + } + break; + case 0x13: + if (cpu->gprs[0] < BASE_WORKING_RAM) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman source"); + break; + } + switch (cpu->gprs[1] >> BASE_OFFSET) { + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination"); + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unHuffman(gba); + break; + } + break; + case 0x14: + case 0x15: + if (cpu->gprs[0] < BASE_WORKING_RAM) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL source"); + break; + } + switch (cpu->gprs[1] >> BASE_OFFSET) { + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination"); + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unRl(gba, immediate == 0x14 ? 1 : 2); + break; + } + break; + case 0x16: + case 0x17: + case 0x18: + if (cpu->gprs[0] < BASE_WORKING_RAM) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter source"); + break; + } + switch (cpu->gprs[1] >> BASE_OFFSET) { + default: + GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter destination"); + case REGION_WORKING_RAM: + case REGION_WORKING_IRAM: + case REGION_VRAM: + _unFilter(gba, immediate == 0x18 ? 2 : 1, immediate == 0x16 ? 1 : 2); + break; + } + break; + case 0x1F: + _MidiKey2Freq(gba); + break; + default: + GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: %02X", immediate); + } + gba->memory.biosPrefetch = 0xE3A02004; +} + +void GBASwi32(struct ARMCore* cpu, int immediate) { + GBASwi16(cpu, immediate >> 16); +} + +uint32_t GBAChecksum(uint32_t* memory, size_t size) { + size_t i; + uint32_t sum = 0; + for (i = 0; i < size; i += 4) { + sum += memory[i >> 2]; + } + return sum; +} + +static void _unLz77(struct GBA* gba, int width) { + struct ARMCore* cpu = gba->cpu; + uint32_t source = cpu->gprs[0]; + uint32_t dest = cpu->gprs[1]; + int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8; + // We assume the signature byte (0x10) is correct + int blockheader = 0; // Some compilers warn if this isn't set, even though it's trivially provably always set + source += 4; + int blocksRemaining = 0; + uint32_t disp; + int bytes; + int byte; + int halfword; + while (remaining > 0) { + if (blocksRemaining) { + if (blockheader & 0x80) { + // Compressed + int block = cpu->memory.load8(cpu, source + 1, 0) | (cpu->memory.load8(cpu, source, 0) << 8); + source += 2; + disp = dest - (block & 0x0FFF) - 1; + bytes = (block >> 12) + 3; + while (bytes-- && remaining) { + --remaining; + if (width == 2) { + byte = (int16_t) cpu->memory.load16(cpu, disp & ~1, 0); + if (dest & 1) { + byte >>= (disp & 1) * 8; + halfword |= byte << 8; + cpu->memory.store16(cpu, dest ^ 1, halfword, 0); + } else { + byte >>= (disp & 1) * 8; + halfword = byte & 0xFF; + } + } else { + byte = cpu->memory.load8(cpu, disp, 0); + cpu->memory.store8(cpu, dest, byte, 0); + } + ++disp; + ++dest; + } + } else { + // Uncompressed + byte = cpu->memory.load8(cpu, source, 0); + ++source; + if (width == 2) { + if (dest & 1) { + halfword |= byte << 8; + cpu->memory.store16(cpu, dest ^ 1, halfword, 0); + } else { + halfword = byte; + } + } else { + cpu->memory.store8(cpu, dest, byte, 0); + } + ++dest; + --remaining; + } + blockheader <<= 1; + --blocksRemaining; + } else { + blockheader = cpu->memory.load8(cpu, source, 0); + ++source; + blocksRemaining = 8; + } + } + cpu->gprs[0] = source; + cpu->gprs[1] = dest; + cpu->gprs[3] = 0; +} + +DECL_BITFIELD(HuffmanNode, uint8_t); +DECL_BITS(HuffmanNode, Offset, 0, 6); +DECL_BIT(HuffmanNode, RTerm, 6); +DECL_BIT(HuffmanNode, LTerm, 7); + +static void _unHuffman(struct GBA* gba) { + struct ARMCore* cpu = gba->cpu; + uint32_t source = cpu->gprs[0] & 0xFFFFFFFC; + uint32_t dest = cpu->gprs[1]; + uint32_t header = cpu->memory.load32(cpu, source, 0); + int remaining = header >> 8; + int bits = header & 0xF; + if (bits == 0) { + GBALog(gba, GBA_LOG_GAME_ERROR, "Invalid Huffman bits"); + bits = 8; + } + if (32 % bits || bits == 1) { + GBALog(gba, GBA_LOG_STUB, "Unimplemented unaligned Huffman"); + return; + } + // We assume the signature byte (0x20) is correct + int treesize = (cpu->memory.load8(cpu, source + 4, 0) << 1) + 1; + int block = 0; + uint32_t treeBase = source + 5; + source += 5 + treesize; + uint32_t nPointer = treeBase; + HuffmanNode node; + int bitsRemaining; + int readBits; + int bitsSeen = 0; + node = cpu->memory.load8(cpu, nPointer, 0); + while (remaining > 0) { + uint32_t bitstream = cpu->memory.load32(cpu, source, 0); + source += 4; + for (bitsRemaining = 32; bitsRemaining > 0 && remaining > 0; --bitsRemaining, bitstream <<= 1) { + uint32_t next = (nPointer & ~1) + HuffmanNodeGetOffset(node) * 2 + 2; + if (bitstream & 0x80000000) { + // Go right + if (HuffmanNodeIsRTerm(node)) { + readBits = cpu->memory.load8(cpu, next + 1, 0); + } else { + nPointer = next + 1; + node = cpu->memory.load8(cpu, nPointer, 0); + continue; + } + } else { + // Go left + if (HuffmanNodeIsLTerm(node)) { + readBits = cpu->memory.load8(cpu, next, 0); + } else { + nPointer = next; + node = cpu->memory.load8(cpu, nPointer, 0); + continue; + } + } + + block |= (readBits & ((1 << bits) - 1)) << bitsSeen; + bitsSeen += bits; + nPointer = treeBase; + node = cpu->memory.load8(cpu, nPointer, 0); + if (bitsSeen == 32) { + bitsSeen = 0; + cpu->memory.store32(cpu, dest, block, 0); + dest += 4; + remaining -= 4; + block = 0; + } + } + + } + cpu->gprs[0] = source; + cpu->gprs[1] = dest; +} + +static void _unRl(struct GBA* gba, int width) { + struct ARMCore* cpu = gba->cpu; + uint32_t source = cpu->gprs[0] & 0xFFFFFFFC; + int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8; + int padding = (4 - remaining) & 0x3; + // We assume the signature byte (0x30) is correct + int blockheader; + int block; + source += 4; + uint32_t dest = cpu->gprs[1]; + int halfword = 0; + while (remaining > 0) { + blockheader = cpu->memory.load8(cpu, source, 0); + ++source; + if (blockheader & 0x80) { + // Compressed + blockheader &= 0x7F; + blockheader += 3; + block = cpu->memory.load8(cpu, source, 0); + ++source; + while (blockheader-- && remaining) { + --remaining; + if (width == 2) { + if (dest & 1) { + halfword |= block << 8; + cpu->memory.store16(cpu, dest ^ 1, halfword, 0); + } else { + halfword = block; + } + } else { + cpu->memory.store8(cpu, dest, block, 0); + } + ++dest; + } + } else { + // Uncompressed + blockheader++; + while (blockheader-- && remaining) { + --remaining; + int byte = cpu->memory.load8(cpu, source, 0); + ++source; + if (width == 2) { + if (dest & 1) { + halfword |= byte << 8; + cpu->memory.store16(cpu, dest ^ 1, halfword, 0); + } else { + halfword = byte; + } + } else { + cpu->memory.store8(cpu, dest, byte, 0); + } + ++dest; + } + } + } + if (width == 2) { + if (dest & 1) { + --padding; + ++dest; + } + for (; padding > 0; padding -= 2, dest += 2) { + cpu->memory.store16(cpu, dest, 0, 0); + } + } else { + while (padding--) { + cpu->memory.store8(cpu, dest, 0, 0); + ++dest; + } + } + cpu->gprs[0] = source; + cpu->gprs[1] = dest; +} + +static void _unFilter(struct GBA* gba, int inwidth, int outwidth) { + struct ARMCore* cpu = gba->cpu; + uint32_t source = cpu->gprs[0] & 0xFFFFFFFC; + uint32_t dest = cpu->gprs[1]; + uint32_t header = cpu->memory.load32(cpu, source, 0); + int remaining = header >> 8; + // We assume the signature nybble (0x8) is correct + uint16_t halfword = 0; + uint16_t old = 0; + source += 4; + while (remaining > 0) { + uint16_t new; + if (inwidth == 1) { + new = cpu->memory.load8(cpu, source, 0); + } else { + new = cpu->memory.load16(cpu, source, 0); + } + new += old; + if (outwidth > inwidth) { + halfword >>= 8; + halfword |= (new << 8); + if (source & 1) { + cpu->memory.store16(cpu, dest, halfword, 0); + dest += outwidth; + remaining -= outwidth; + } + } else if (outwidth == 1) { + cpu->memory.store8(cpu, dest, new, 0); + dest += outwidth; + remaining -= outwidth; + } else { + cpu->memory.store16(cpu, dest, new, 0); + dest += outwidth; + remaining -= outwidth; + } + old = new; + source += inwidth; + } + cpu->gprs[0] = source; + cpu->gprs[1] = dest; +}
@@ -0,0 +1,967 @@
+/* Copyright (c) 2013-2015 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 "cheats.h" + +#include "gba/gba.h" +#include "gba/io.h" +#include "util/vfs.h" + +#define MAX_LINE_LENGTH 128 + +const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE; + +DEFINE_VECTOR(GBACheatList, struct GBACheat); +DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*); +DEFINE_VECTOR(StringList, char*); + +static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 }; +static const uint32_t _par3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 }; + +static const uint8_t _gsa1T1[256] = { + 0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94, + 0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B, + 0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45, + 0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9, + 0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA, + 0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E, + 0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79, + 0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31, + 0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B, + 0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB, + 0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77, + 0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93, + 0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25, + 0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52, + 0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F, + 0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50 +}; + +static const uint8_t _gsa1T2[256] = { + 0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6, + 0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1, + 0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B, + 0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48, + 0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6, + 0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8, + 0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE, + 0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39, + 0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B, + 0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4, + 0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5, + 0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90, + 0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75, + 0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76, + 0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2, + 0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C +}; + +static const uint8_t _par3T1[256] = { + 0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A, + 0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45, + 0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7, + 0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5, + 0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54, + 0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A, + 0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB, + 0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC, + 0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2, + 0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2, + 0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31, + 0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35, + 0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11, + 0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C, + 0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA, + 0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30 +}; + +static const uint8_t _par3T2[256] = { + 0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA, + 0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F, + 0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93, + 0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B, + 0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD, + 0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC, + 0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F, + 0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9, + 0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1, + 0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B, + 0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC, + 0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA, + 0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8, + 0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D, + 0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C, + 0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC +}; + +static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) { + switch (width) { + case 1: + return cpu->memory.load8(cpu, address, 0); + case 2: + return cpu->memory.load16(cpu, address, 0); + case 4: + return cpu->memory.load32(cpu, address, 0); + } + return 0; +} + +static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t value) { + switch (width) { + case 1: + cpu->memory.store8(cpu, address, value, 0); + break; + case 2: + cpu->memory.store16(cpu, address, value, 0); + break; + case 4: + cpu->memory.store32(cpu, address, value, 0); + break; + } +} + +static int _hexDigit(char digit) { + switch (digit) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return digit - '0'; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return digit - 'a' + 10; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return digit - 'A' + 10; + + default: + return -1; + } +} + +static const char* _hex32(const char* line, uint32_t* out) { + uint32_t value = 0; + int i; + for (i = 0; i < 8; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = _hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} + +static const char* _hex16(const char* line, uint16_t* out) { + uint16_t value = 0; + *out = 0; + int i; + for (i = 0; i < 4; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = _hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} + +static void _registerLine(struct GBACheatSet* cheats, const char* line) { + *StringListAppend(&cheats->lines) = strdup(line); +} + +// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm +static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) { + uint32_t sum = 0xC6EF3720; + int i; + for (i = 0; i < 32; ++i) { + *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]); + *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]); + sum -= 0x9E3779B9; + } +} + +static void _reseedGameShark(uint32_t* seeds, uint16_t params, const uint8_t* t1, const uint8_t* t2) { + int x, y; + int s0 = params >> 8; + int s1 = params & 0xFF; + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + uint8_t z = t1[(s0 + x) & 0xFF] + t2[(s1 + y) & 0xFF]; + seeds[y] <<= 8; + seeds[y] |= z; + } + } +} + +static void _setGameSharkVersion(struct GBACheatSet* cheats, int version) { + cheats->gsaVersion = 1; + switch (version) { + case 1: + memcpy(cheats->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t)); + break; + case 3: + memcpy(cheats->gsaSeeds, _par3S, 4 * sizeof(uint32_t)); + break; + } +} + +static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAGameSharkType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses == 0) { + cheats->incompleteCheat = 0; + } + return true; + } + + switch (type) { + case GSA_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_LIST: + cheats->remainingAddresses = (op1 & 0xFFFF) - 1; + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheats->incompleteCheat = cheat; + break; + case GSA_PATCH: + cheats->romPatches[0].address = (op1 & 0xFFFFFF) << 1; + cheats->romPatches[0].newValue = op2; + cheats->romPatches[0].applied = false; + cheats->romPatches[0].exists = true; + return true; + case GSA_BUTTON: + // TODO: Implement button + return false; + case GSA_IF_EQ: + if (op1 == 0xDEADFACE) { + _reseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); + return true; + } + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_IF_EQ_RANGE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op2 & 0x0FFFFFFF; + cheat->operand = op1 & 0xFFFF; + cheat->repeat = (op1 >> 16) & 0xFF; + return true; + case GSA_HOOK: + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + default: + return false; + } + cheat->operand = op2; + cheat->repeat = 1; + return true; +} + +static bool _addPAR3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + // TODO + UNUSED(cheats); + UNUSED(op1); + UNUSED(op2); + UNUSED(_par3T1); + UNUSED(_par3T2); + return false; +} + +static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + if (!device->p || !cheats->hook) { + return; + } + ++cheats->hook->reentries; + if (cheats->hook->reentries > 1) { + return; + } + GBASetBreakpoint(device->p, &device->d, cheats->hook->address, cheats->hook->mode, &cheats->hook->patchedOpcode); +} + +static void _removeBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + if (!device->p || !cheats->hook) { + return; + } + --cheats->hook->reentries; + if (cheats->hook->reentries > 0) { + return; + } + GBAClearBreakpoint(device->p, cheats->hook->address, cheats->hook->mode, cheats->hook->patchedOpcode); +} + +static void _patchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + if (!device->p) { + return; + } + int i; + for (i = 0; i < MAX_ROM_PATCHES; ++i) { + if (!cheats->romPatches[i].exists || cheats->romPatches[i].applied) { + continue; + } + GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].newValue, &cheats->romPatches[i].oldValue); + cheats->romPatches[i].applied = true; + } +} + +static void _unpatchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + if (!device->p) { + return; + } + int i; + for (i = 0; i < MAX_ROM_PATCHES; ++i) { + if (!cheats->romPatches[i].exists || !cheats->romPatches[i].applied) { + continue; + } + GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].oldValue, 0); + cheats->romPatches[i].applied = false; + } +} + +static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*); +static void GBACheatDeviceDeinit(struct ARMComponent*); + +void GBACheatDeviceCreate(struct GBACheatDevice* device) { + device->d.id = GBA_CHEAT_DEVICE_ID; + device->d.init = GBACheatDeviceInit; + device->d.deinit = GBACheatDeviceDeinit; + GBACheatSetsInit(&device->cheats, 4); +} + +void GBACheatDeviceDestroy(struct GBACheatDevice* device) { + size_t i; + for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { + struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i); + GBACheatSetDeinit(set); + free(set); + } + GBACheatSetsDeinit(&device->cheats); +} + +void GBACheatSetInit(struct GBACheatSet* set, const char* name) { + GBACheatListInit(&set->list, 4); + StringListInit(&set->lines, 4); + set->incompleteCheat = 0; + set->gsaVersion = 0; + set->remainingAddresses = 0; + set->hook = 0; + int i; + for (i = 0; i < MAX_ROM_PATCHES; ++i) { + set->romPatches[i].exists = false; + } + if (name) { + set->name = strdup(name); + } else { + set->name = 0; + } + set->enabled = true; +} + +void GBACheatSetDeinit(struct GBACheatSet* set) { + GBACheatListDeinit(&set->list); + size_t i; + for (i = 0; i < StringListSize(&set->lines); ++i) { + free(*StringListGetPointer(&set->lines, i)); + } + if (set->name) { + free(set->name); + } + if (set->hook) { + --set->hook->refs; + if (set->hook->refs == 0) { + free(set->hook); + } + } +} + +void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) { + if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { + ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE); + } + gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d; + ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE); +} + +void GBACheatAddSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + *GBACheatSetsAppend(&device->cheats) = cheats; + _addBreakpoint(device, cheats); + _patchROM(device, cheats); +} + +void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + size_t i; + for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { + if (*GBACheatSetsGetPointer(&device->cheats, i) == cheats) { + break; + } + } + if (i == GBACheatSetsSize(&device->cheats)) { + return; + } + GBACheatSetsShift(&device->cheats, i, 1); + _unpatchROM(device, cheats); + _removeBreakpoint(device, cheats); +} + +bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { + char line[14] = "XXXXXXXX XXXX"; + snprintf(line, sizeof(line), "%08X %04X", op1, op2); + _registerLine(cheats, line); + + enum GBACodeBreakerType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + cheats->incompleteCheat->repeat = op1 & 0xFFFF; + cheats->incompleteCheat->addressOffset = op2; + cheats->incompleteCheat->operandOffset = 0; + cheats->incompleteCheat = 0; + return true; + } + + switch (type) { + case CB_GAME_ID: + // TODO: Run checksum + return true; + case CB_HOOK: + if (cheats->hook) { + return false; + } + cheats->hook = malloc(sizeof(*cheats->hook)); + cheats->hook->address = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hook->mode = MODE_THUMB; + cheats->hook->refs = 1; + cheats->hook->reentries = 0; + return true; + case CB_OR_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_OR; + cheat->width = 2; + break; + case CB_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + break; + case CB_FILL: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case CB_FILL_8: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + case CB_AND_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_AND; + cheat->width = 2; + break; + case CB_IF_EQ: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + break; + case CB_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + break; + case CB_ENCRYPT: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); + return false; + case CB_IF_NE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_NE; + cheat->width = 2; + break; + case CB_IF_GT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_GT; + cheat->width = 2; + break; + case CB_IF_LT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_LT; + cheat->width = 2; + break; + case CB_IF_SPECIAL: + switch (op1 & 0x0FFFFFFF) { + case 0x20: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + cheat->address = BASE_IO | REG_JOYSTAT; + cheat->operand = op2; + cheat->repeat = 1; + return true; + default: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + } + case CB_ADD_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ADD; + cheat->width = 2; + break; + case CB_IF_AND: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + break; + } + + cheat->address = op1 & 0x0FFFFFFF; + cheat->operand = op2; + cheat->repeat = 1; + return true; +} + +bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint16_t op2; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = _hex16(line, &op2); + if (!line) { + return false; + } + return GBACheatAddCodeBreaker(cheats, op1, op2); +} + +bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + _registerLine(set, line); + + switch (set->gsaVersion) { + case 0: + _setGameSharkVersion(set, 1); + // Fall through + case 1: + _decryptGameShark(&o1, &o2, set->gsaSeeds); + return _addGSA1(set, o1, o2); + } + return false; +} + +bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = _hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddGameShark(cheats, op1, op2); +} + +bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + char line[18] = "XXXXXXXX XXXXXXXX"; + snprintf(line, sizeof(line), "%08X %08X", op1, op2); + _registerLine(set, line); + + switch (set->gsaVersion) { + case 0: + // Try to detect GameShark version + _decryptGameShark(&o1, &o2, _gsa1S); + if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) { + _setGameSharkVersion(set, 1); + return _addGSA1(set, o1, o2); + } + o1 = op1; + o2 = op2; + _decryptGameShark(&o1, &o2, _par3S); + if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) { + _setGameSharkVersion(set, 3); + return _addPAR3(set, o1, o2); + } + break; + case 1: + _decryptGameShark(&o1, &o2, set->gsaSeeds); + return _addGSA1(set, o1, o2); + case 3: + _decryptGameShark(&o1, &o2, set->gsaSeeds); + return _addPAR3(set, o1, o2); + } + return false; +} + +bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = _hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddAutodetect(cheats, op1, op2); +} + +bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) { + char cheat[MAX_LINE_LENGTH]; + struct GBACheatSet* set = 0; + struct GBACheatSet* newSet; + int gsaVersion = 0; + bool nextDisabled = false; + bool reset = false; + while (true) { + size_t i = 0; + ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat)); + if (bytesRead == 0) { + break; + } + if (bytesRead < 0) { + return false; + } + while (isspace(cheat[i])) { + ++i; + } + switch (cheat[i]) { + case '#': + do { + ++i; + } while (isspace(cheat[i])); + newSet = malloc(sizeof(*set)); + GBACheatSetInit(newSet, &cheat[i]); + newSet->enabled = !nextDisabled; + nextDisabled = false; + if (set) { + GBACheatAddSet(device, set); + } + if (set && !reset) { + GBACheatSetCopyProperties(newSet, set); + } else { + _setGameSharkVersion(newSet, gsaVersion); + } + reset = false; + set = newSet; + break; + case '!': + do { + ++i; + } while (isspace(cheat[i])); + if (strncasecmp(&cheat[i], "GSAv", 4) == 0 || strncasecmp(&cheat[i], "PARv", 4) == 0) { + i += 4; + gsaVersion = atoi(&cheat[i]); + break; + } + if (strcasecmp(&cheat[i], "disabled") == 0) { + nextDisabled = true; + break; + } + if (strcasecmp(&cheat[i], "reset") == 0) { + reset = true; + break; + } + break; + default: + if (!set) { + set = malloc(sizeof(*set)); + GBACheatSetInit(set, 0); + set->enabled = !nextDisabled; + nextDisabled = false; + _setGameSharkVersion(set, gsaVersion); + } + GBACheatAddLine(set, cheat); + break; + } + } + if (set) { + GBACheatAddSet(device, set); + } + return true; +} + +bool GBACheatSaveFile(struct GBACheatDevice* device, struct VFile* vf) { + static const char lineStart[3] = "# "; + static const char lineEnd = '\n'; + + struct GBACheatHook* lastHook = 0; + + size_t i; + for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { + struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i); + if (lastHook && set->hook != lastHook) { + static const char* resetDirective = "!reset\n"; + vf->write(vf, resetDirective, strlen(resetDirective)); + } + switch (set->gsaVersion) { + case 1: { + static const char* versionDirective = "!GSAv1\n"; + vf->write(vf, versionDirective, strlen(versionDirective)); + break; + } + case 3: { + static const char* versionDirective = "!PARv3\n"; + vf->write(vf, versionDirective, strlen(versionDirective)); + break; + } + default: + break; + } + lastHook = set->hook; + if (!set->enabled) { + static const char* disabledDirective = "!disabled\n"; + vf->write(vf, disabledDirective, strlen(disabledDirective)); + } + + vf->write(vf, lineStart, 2); + if (set->name) { + vf->write(vf, set->name, strlen(set->name)); + } + vf->write(vf, &lineEnd, 1); + size_t c; + for (c = 0; c < StringListSize(&set->lines); ++c) { + const char* line = *StringListGetPointer(&set->lines, c); + vf->write(vf, line, strlen(line)); + vf->write(vf, &lineEnd, 1); + } + } + return true; +} + +bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint16_t op2; + uint16_t op3; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (isspace(line[0])) { + ++line; + } + line = _hex16(line, &op2); + if (!line) { + return false; + } + if (!line[0] || isspace(line[0])) { + return GBACheatAddCodeBreaker(cheats, op1, op2); + } + line = _hex16(line, &op3); + if (!line) { + return false; + } + uint32_t realOp2 = op2; + realOp2 <<= 16; + realOp2 |= op3; + return GBACheatAddAutodetect(cheats, op1, realOp2); +} + +void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + if (!cheats->enabled) { + return; + } + bool condition = true; + int conditionRemaining = 0; + _patchROM(device, cheats); + + size_t nCodes = GBACheatListSize(&cheats->list); + size_t i; + for (i = 0; i < nCodes; ++i) { + if (conditionRemaining > 0) { + --conditionRemaining; + if (!condition) { + continue; + } + } else { + condition = true; + } + struct GBACheat* cheat = GBACheatListGetPointer(&cheats->list, i); + int32_t value = 0; + int32_t operand = cheat->operand; + uint32_t operationsRemaining = cheat->repeat; + uint32_t address = cheat->address; + bool performAssignment = false; + for (; operationsRemaining; --operationsRemaining) { + switch (cheat->type) { + case CHEAT_ASSIGN: + value = operand; + performAssignment = true; + break; + case CHEAT_AND: + value = _readMem(device->p->cpu, address, cheat->width) & operand; + performAssignment = true; + break; + case CHEAT_ADD: + value = _readMem(device->p->cpu, address, cheat->width) + operand; + performAssignment = true; + break; + case CHEAT_OR: + value = _readMem(device->p->cpu, address, cheat->width) | operand; + performAssignment = true; + break; + case CHEAT_IF_EQ: + condition = _readMem(device->p->cpu, address, cheat->width) == operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_NE: + condition = _readMem(device->p->cpu, address, cheat->width) != operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_LT: + condition = _readMem(device->p->cpu, address, cheat->width) < operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_GT: + condition = _readMem(device->p->cpu, address, cheat->width) > operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_ULT: + condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_UGT: + condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_AND: + condition = _readMem(device->p->cpu, address, cheat->width) & operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_LAND: + condition = _readMem(device->p->cpu, address, cheat->width) && operand; + conditionRemaining = cheat->repeat; + break; + } + + if (performAssignment) { + _writeMem(device->p->cpu, address, cheat->width, value); + } + + address += cheat->addressOffset; + operand += cheat->operandOffset; + } + } +} + +void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) { + newSet->gsaVersion = set->gsaVersion; + memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds)); + if (set->hook) { + if (newSet->hook) { + --newSet->hook->refs; + if (newSet->hook->refs == 0) { + free(newSet->hook); + } + } + newSet->hook = set->hook; + ++newSet->hook->refs; + } +} + +void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) { + struct GBACheatDevice* device = (struct GBACheatDevice*) component; + device->p = (struct GBA*) cpu->master; + size_t i; + for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { + struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i); + _addBreakpoint(device, cheats); + _patchROM(device, cheats); + } +} + +void GBACheatDeviceDeinit(struct ARMComponent* component) { + struct GBACheatDevice* device = (struct GBACheatDevice*) component; + size_t i; + for (i = GBACheatSetsSize(&device->cheats); i--;) { + struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i); + _unpatchROM(device, cheats); + _removeBreakpoint(device, cheats); + } +}
@@ -0,0 +1,209 @@
+/* Copyright (c) 2013-2015 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 GBA_CHEATS_H +#define GBA_CHEATS_H + +#include "util/common.h" + +#include "arm.h" +#include "util/vector.h" + +#define MAX_ROM_PATCHES 4 + +enum GBACheatType { + CHEAT_ASSIGN, + CHEAT_AND, + CHEAT_ADD, + CHEAT_OR, + CHEAT_IF_EQ, + CHEAT_IF_NE, + CHEAT_IF_LT, + CHEAT_IF_GT, + CHEAT_IF_ULT, + CHEAT_IF_UGT, + CHEAT_IF_AND, + CHEAT_IF_LAND +}; + +enum GBACodeBreakerType { + CB_GAME_ID = 0x0, + CB_HOOK = 0x1, + CB_OR_2 = 0x2, + CB_ASSIGN_1 = 0x3, + CB_FILL = 0x4, + CB_FILL_8 = 0x5, + CB_AND_2 = 0x6, + CB_IF_EQ = 0x7, + CB_ASSIGN_2 = 0x8, + CB_ENCRYPT = 0x9, + CB_IF_NE = 0xA, + CB_IF_GT = 0xB, + CB_IF_LT = 0xC, + CB_IF_SPECIAL = 0xD, + CB_ADD_2 = 0xE, + CB_IF_AND = 0xF, +}; + +enum GBAGameSharkType { + GSA_ASSIGN_1 = 0x0, + GSA_ASSIGN_2 = 0x1, + GSA_ASSIGN_4 = 0x2, + GSA_ASSIGN_LIST = 0x3, + GSA_PATCH = 0x6, + GSA_BUTTON = 0x8, + GSA_IF_EQ = 0xD, + GSA_IF_EQ_RANGE = 0xE, + GSA_HOOK = 0xF +}; + +enum GBAActionReplay3Condition { + PAR3_COND_OTHER = 0x00000000, + PAR3_COND_EQ = 0x08000000, + PAR3_COND_NE = 0x10000000, + PAR3_COND_LT = 0x18000000, + PAR3_COND_GT = 0x20000000, + PAR3_COND_ULT = 0x28000000, + PAR3_COND_UGT = 0x30000000, + PAR3_COND_LAND = 0x38000000, +}; + +enum GBAActionReplay3Width { + PAR3_WIDTH_1 = 0x00000000, + PAR3_WIDTH_2 = 0x02000000, + PAR3_WIDTH_4 = 0x04000000, + PAR3_WIDTH_FALSE = 0x06000000, +}; + +enum GBAActionReplay3Action { + PAR3_ACTION_NEXT = 0x00000000, + PAR3_ACTION_NEXT_TWO = 0x40000000, + PAR3_ACTION_BLOCK = 0x80000000, + PAR3_ACTION_DISABLE = 0xC0000000, +}; + +enum GBAActionReplay3Base { + PAR3_BASE_ASSIGN_1 = 0x00000000, + PAR3_BASE_ASSIGN_2 = 0x02000000, + PAR3_BASE_ASSIGN_4 = 0x04000000, + PAR3_BASE_INDIRECT_1 = 0x40000000, + PAR3_BASE_INDIRECT_2 = 0x42000000, + PAR3_BASE_INDIRECT_4 = 0x44000000, + PAR3_BASE_ADD_1 = 0x80000000, + PAR3_BASE_ADD_2 = 0x82000000, + PAR3_BASE_ADD_4 = 0x84000000, + PAR3_BASE_HOOK = 0xC4000000, + PAR3_BASE_IO_2 = 0xC6000000, + PAR3_BASE_IO_3 = 0xC7000000, +}; + +enum GBAActionReplay3Other { + PAR3_OTHER_END = 0x00000000, + PAR3_OTHER_SLOWDOWN = 0x08000000, + PAR3_OTHER_BUTTON_1 = 0x10000000, + PAR3_OTHER_BUTTON_2 = 0x12000000, + PAR3_OTHER_BUTTON_4 = 0x14000000, + PAR3_OTHER_PATCH_1 = 0x18000000, + PAR3_OTHER_PATCH_2 = 0x1A000000, + PAR3_OTHER_PATCH_3 = 0x1C000000, + PAR3_OTHER_PATCH_4 = 0x1E000000, + PAR3_OTHER_ENDIF = 0x40000000, + PAR3_OTHER_ELSE = 0x60000000, + PAR3_OTHER_FILL_1 = 0x80000000, + PAR3_OTHER_FILL_2 = 0x82000000, + PAR3_OTHER_FILL_4 = 0x84000000, +}; + +enum { + PAR3_COND = 0x38000000, + PAR3_WIDTH = 0x06000000, + PAR3_ACTION = 0xC0000000 +}; + +struct GBACheat { + enum GBACheatType type; + int width; + uint32_t address; + uint32_t operand; + uint32_t repeat; + + int32_t addressOffset; + int32_t operandOffset; +}; + +struct GBACheatHook { + uint32_t address; + enum ExecutionMode mode; + uint32_t patchedOpcode; + size_t refs; + size_t reentries; +}; + +DECLARE_VECTOR(GBACheatList, struct GBACheat); +DECLARE_VECTOR(StringList, char*); + +struct GBACheatSet { + struct GBACheatHook* hook; + struct GBACheatList list; + + struct GBACheat* incompleteCheat; + + struct GBACheatPatch { + uint32_t address; + int16_t newValue; + int16_t oldValue; + bool applied; + bool exists; + } romPatches[MAX_ROM_PATCHES]; + + int gsaVersion; + uint32_t gsaSeeds[4]; + int remainingAddresses; + + char* name; + bool enabled; + struct StringList lines; +}; + +DECLARE_VECTOR(GBACheatSets, struct GBACheatSet*); + +struct GBACheatDevice { + struct ARMComponent d; + struct GBA* p; + + struct GBACheatSets cheats; +}; + +struct VFile; + +void GBACheatDeviceCreate(struct GBACheatDevice*); +void GBACheatDeviceDestroy(struct GBACheatDevice*); + +void GBACheatSetInit(struct GBACheatSet*, const char* name); +void GBACheatSetDeinit(struct GBACheatSet*); + +void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice*); + +void GBACheatAddSet(struct GBACheatDevice*, struct GBACheatSet*); +void GBACheatRemoveSet(struct GBACheatDevice*, struct GBACheatSet*); +void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set); + +bool GBACheatAddCodeBreaker(struct GBACheatSet*, uint32_t op1, uint16_t op2); +bool GBACheatAddCodeBreakerLine(struct GBACheatSet*, const char* line); + +bool GBACheatAddGameShark(struct GBACheatSet*, uint32_t op1, uint32_t op2); +bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line); + +bool GBACheatAddAutodetect(struct GBACheatSet*, uint32_t op1, uint32_t op2); +bool GBACheatAddAutodetectLine(struct GBACheatSet*, const char* line); + +bool GBACheatParseFile(struct GBACheatDevice*, struct VFile*); +bool GBACheatSaveFile(struct GBACheatDevice*, struct VFile*); + +bool GBACheatAddLine(struct GBACheatSet*, const char* line); + +void GBACheatRefresh(struct GBACheatDevice*, struct GBACheatSet*); + +#endif
@@ -1,17 +1,18 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-audio.h" +#include "audio.h" -#include "gba.h" -#include "gba-io.h" -#include "gba-serialize.h" -#include "gba-thread.h" -#include "gba-video.h" +#include "gba/gba.h" +#include "gba/io.h" +#include "gba/serialize.h" +#include "gba/supervisor/thread.h" +#include "gba/video.h" const unsigned GBA_AUDIO_SAMPLES = 2048; +const unsigned BLIP_BUFFER_SIZE = 0x4000; const unsigned GBA_AUDIO_FIFO_SIZE = 8 * sizeof(int32_t); #define SWEEP_CYCLES (GBA_ARM7TDMI_FREQUENCY / 128)@@ -27,8 +28,17 @@ static int _applyBias(struct GBAAudio* audio, int sample);
static void _sample(struct GBAAudio* audio); void GBAAudioInit(struct GBAAudio* audio, size_t samples) { - CircleBufferInit(&audio->left, samples * sizeof(int32_t)); - CircleBufferInit(&audio->right, samples * sizeof(int32_t)); + audio->samples = samples; +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF + CircleBufferInit(&audio->left, samples * sizeof(int16_t)); + CircleBufferInit(&audio->right, samples * sizeof(int16_t)); +#else + audio->left = blip_new(BLIP_BUFFER_SIZE); + audio->right = blip_new(BLIP_BUFFER_SIZE); + // Guess too large; we hang producing extra samples if we guess too low + blip_set_rates(audio->left, GBA_ARM7TDMI_FREQUENCY, 96000); + blip_set_rates(audio->right, GBA_ARM7TDMI_FREQUENCY, 96000); +#endif CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); }@@ -43,8 +53,8 @@ audio->ch1 = (struct GBAAudioChannel1) { .envelope = { .nextStep = INT_MAX }, .nextSweep = INT_MAX };
audio->ch2 = (struct GBAAudioChannel2) { .envelope = { .nextStep = INT_MAX } }; audio->ch3 = (struct GBAAudioChannel3) { .bank = { .bank = 0 } }; audio->ch4 = (struct GBAAudioChannel4) { .envelope = { .nextStep = INT_MAX } }; - audio->chA.dmaSource = 0; - audio->chB.dmaSource = 0; + audio->chA.dmaSource = 1; + audio->chB.dmaSource = 2; audio->chA.sample = 0; audio->chB.sample = 0; audio->eventDiff = 0;@@ -77,50 +87,67 @@ audio->playingCh4 = false;
audio->enable = false; audio->sampleInterval = GBA_ARM7TDMI_FREQUENCY / audio->sampleRate; +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF CircleBufferClear(&audio->left); CircleBufferClear(&audio->right); +#else + blip_clear(audio->left); + blip_clear(audio->right); + audio->clock = 0; +#endif CircleBufferClear(&audio->chA.fifo); CircleBufferClear(&audio->chB.fifo); } void GBAAudioDeinit(struct GBAAudio* audio) { +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF CircleBufferDeinit(&audio->left); CircleBufferDeinit(&audio->right); +#else + blip_delete(audio->left); + blip_delete(audio->right); +#endif CircleBufferDeinit(&audio->chA.fifo); CircleBufferDeinit(&audio->chB.fifo); } void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { - if (samples > GBA_AUDIO_SAMPLES) { - return; - } - GBASyncLockAudio(audio->p->sync); - int32_t buffer[GBA_AUDIO_SAMPLES]; - int32_t dummy; + audio->samples = samples; +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF + size_t oldCapacity = audio->left.capacity; + int16_t* buffer = malloc(oldCapacity); + int16_t dummy; size_t read; size_t i; - read = CircleBufferDump(&audio->left, buffer, sizeof(buffer)); + read = CircleBufferDump(&audio->left, buffer, oldCapacity); CircleBufferDeinit(&audio->left); - CircleBufferInit(&audio->left, samples * sizeof(int32_t)); - for (i = 0; i * sizeof(int32_t) < read; ++i) { - if (!CircleBufferWrite32(&audio->left, buffer[i])) { - CircleBufferRead32(&audio->left, &dummy); - CircleBufferWrite32(&audio->left, buffer[i]); + CircleBufferInit(&audio->left, samples * sizeof(int16_t)); + for (i = 0; i * sizeof(int16_t) < read; ++i) { + if (!CircleBufferWrite16(&audio->left, buffer[i])) { + CircleBufferRead16(&audio->left, &dummy); + CircleBufferWrite16(&audio->left, buffer[i]); } } - read = CircleBufferDump(&audio->right, buffer, sizeof(buffer)); + read = CircleBufferDump(&audio->right, buffer, oldCapacity); CircleBufferDeinit(&audio->right); - CircleBufferInit(&audio->right, samples * sizeof(int32_t)); - for (i = 0; i * sizeof(int32_t) < read; ++i) { - if (!CircleBufferWrite32(&audio->right, buffer[i])) { - CircleBufferRead32(&audio->right, &dummy); - CircleBufferWrite32(&audio->right, buffer[i]); + CircleBufferInit(&audio->right, samples * sizeof(int16_t)); + for (i = 0; i * sizeof(int16_t) < read; ++i) { + if (!CircleBufferWrite16(&audio->right, buffer[i])) { + CircleBufferRead16(&audio->right, &dummy); + CircleBufferWrite16(&audio->right, buffer[i]); } } + free(buffer); +#else + blip_clear(audio->left); + blip_clear(audio->right); + audio->clock = 0; +#endif + GBASyncConsumeAudio(audio->p->sync); }@@ -453,6 +480,30 @@ }
} } +void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value) { + struct CircleBuffer* fifo; + switch (address) { + case REG_FIFO_A_LO: + case REG_FIFO_A_HI: + fifo = &audio->chA.fifo; + break; + case REG_FIFO_B_LO: + case REG_FIFO_B_HI: + fifo = &audio->chB.fifo; + break; + default: + GBALog(audio->p, GBA_LOG_ERROR, "Bad FIFO write to address 0x%03x", address); + return; + } + int i; + for (i = 0; i < 2; ++i) { + while (!CircleBufferWrite8(fifo, value >> (8 * i))) { + int8_t dummy; + CircleBufferRead8(fifo, &dummy); + } + } +} + void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { struct GBAAudioFIFO* channel; if (fifoId == 0) {@@ -472,20 +523,21 @@ }
CircleBufferRead8(&channel->fifo, &channel->sample); } +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples) { GBASyncLockAudio(audio->p->sync); unsigned read = 0; if (left) { - unsigned readL = CircleBufferRead(&audio->left, left, nSamples * sizeof(int32_t)) >> 2; + unsigned readL = CircleBufferRead(&audio->left, left, nSamples * sizeof(int16_t)) >> 1; if (readL < nSamples) { - memset((int32_t*) left + readL, 0, nSamples - readL); + memset((int16_t*) left + readL, 0, nSamples - readL); } read = readL; } if (right) { - unsigned readR = CircleBufferRead(&audio->right, right, nSamples * sizeof(int32_t)) >> 2; + unsigned readR = CircleBufferRead(&audio->right, right, nSamples * sizeof(int16_t)) >> 1; if (readR < nSamples) { - memset((int32_t*) right + readR, 0, nSamples - readR); + memset((int16_t*) right + readR, 0, nSamples - readR); } read = read >= readR ? read : readR; }@@ -494,8 +546,8 @@ return read;
} unsigned GBAAudioResampleNN(struct GBAAudio* audio, float ratio, float* drift, struct GBAStereoSample* output, unsigned nSamples) { - int32_t left[GBA_AUDIO_SAMPLES]; - int32_t right[GBA_AUDIO_SAMPLES]; + int16_t left[GBA_AUDIO_SAMPLES]; + int16_t right[GBA_AUDIO_SAMPLES]; // toRead is in GBA samples // TODO: Do this with fixed-point math@@ -530,6 +582,7 @@ }
} return totalRead; } +#endif bool _writeEnvelope(struct GBAAudioEnvelope* envelope, uint16_t value) { envelope->length = GBAAudioRegisterEnvelopeGetLength(value);@@ -685,13 +738,13 @@ sample = 0x3FF;
} else if (sample < 0) { sample = 0; } - return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 6; + return (sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) << 5; } static void _sample(struct GBAAudio* audio) { - int32_t sampleLeft = 0; - int32_t sampleRight = 0; - int psgShift = 6 - audio->volume; + int16_t sampleLeft = 0; + int16_t sampleRight = 0; + int psgShift = 5 - audio->volume; if (audio->ch1Left) { sampleLeft += audio->ch1.sample;@@ -748,14 +801,31 @@ sampleLeft = _applyBias(audio, sampleLeft);
sampleRight = _applyBias(audio, sampleRight); GBASyncLockAudio(audio->p->sync); - CircleBufferWrite32(&audio->left, sampleLeft); - CircleBufferWrite32(&audio->right, sampleRight); - unsigned produced = CircleBufferSize(&audio->left); - struct GBAThread* thread = GBAThreadGetContext(); - if (thread && thread->stream) { - thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight); + unsigned produced; +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF + CircleBufferWrite16(&audio->left, sampleLeft); + CircleBufferWrite16(&audio->right, sampleRight); + produced = CircleBufferSize(&audio->left) / 2; +#else + if ((size_t) blip_samples_avail(audio->left) < audio->samples) { + blip_add_delta(audio->left, audio->clock, sampleLeft - audio->lastLeft); + blip_add_delta(audio->right, audio->clock, sampleRight - audio->lastRight); + audio->lastLeft = sampleLeft; + audio->lastRight = sampleRight; + audio->clock += audio->sampleInterval; + int clockNeeded = blip_clocks_needed(audio->left, audio->samples / 32); + if (audio->clock >= clockNeeded) { + blip_end_frame(audio->left, audio->clock); + blip_end_frame(audio->right, audio->clock); + audio->clock -= clockNeeded; + } } - GBASyncProduceAudio(audio->p->sync, produced >= CircleBufferCapacity(&audio->left) / sizeof(int32_t) * 3); + produced = blip_samples_avail(audio->left); +#endif + if (audio->p->stream) { + audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight); + } + GBASyncProduceAudio(audio->p->sync, produced >= audio->samples); } void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {@@ -838,6 +908,6 @@ audio->eventDiff = state->audio.eventDiff;
audio->nextSample = state->audio.nextSample; } -float GBAAudioCalculateRatio(struct GBAAudio* audio, float desiredFPS, float desiredSampleRate) { - return desiredSampleRate * GBA_ARM7TDMI_FREQUENCY / (VIDEO_TOTAL_LENGTH * desiredFPS * audio->sampleRate); +float GBAAudioCalculateRatio(float inputSampleRate, float desiredFPS, float desiredSampleRate) { + return desiredSampleRate * GBA_ARM7TDMI_FREQUENCY / (VIDEO_TOTAL_LENGTH * desiredFPS * inputSampleRate); }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -11,9 +11,16 @@ #include "macros.h"
#include "util/circle-buffer.h" +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF +#include "third-party/blip_buf/blip_buf.h" +#endif + struct GBADMA; extern const unsigned GBA_AUDIO_SAMPLES; + +#define RESAMPLE_NN 0 +#define RESAMPLE_BLIP_BUF 2 DECL_BITFIELD(GBAAudioRegisterEnvelope, uint16_t); DECL_BITS(GBAAudioRegisterEnvelope, Length, 0, 6);@@ -177,8 +184,16 @@
struct GBAAudioFIFO chA; struct GBAAudioFIFO chB; +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF struct CircleBuffer left; struct CircleBuffer right; +#else + blip_t* left; + blip_t* right; + int16_t lastLeft; + int16_t lastRight; + int clock; +#endif uint8_t volumeRight; uint8_t volumeLeft;@@ -207,6 +222,7 @@ bool playingCh3;
bool playingCh4; bool enable; + size_t samples; unsigned sampleRate; GBARegisterSOUNDBIAS soundbias;@@ -252,16 +268,19 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value);
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value); void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value); +void GBAAudioWriteFIFO16(struct GBAAudio* audio, int address, uint16_t value); void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value); void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles); +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples); unsigned GBAAudioResampleNN(struct GBAAudio*, float ratio, float* drift, struct GBAStereoSample* output, unsigned nSamples); +#endif struct GBASerializedState; void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state); void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state); -float GBAAudioCalculateRatio(struct GBAAudio* audio, float desiredFPS, float desiredSampleRatio); +float GBAAudioCalculateRatio(float inputSampleRate, float desiredFPS, float desiredSampleRatio); #endif
@@ -1,403 +0,0 @@
-/* Copyright (c) 2013-2014 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 "gba-bios.h" - -#include "gba.h" -#include "gba-io.h" -#include "gba-memory.h" - -const uint32_t GBA_BIOS_CHECKSUM = 0xBAAE187F; -const uint32_t GBA_DS_BIOS_CHECKSUM = 0xBAAE1880; - -static void _unLz77(struct GBA* gba, uint32_t source, uint8_t* dest); -static void _unHuffman(struct GBA* gba, uint32_t source, uint32_t* dest); -static void _unRl(struct GBA* gba, uint32_t source, uint8_t* dest); - -static void _RegisterRamReset(struct GBA* gba) { - uint32_t registers = gba->cpu->gprs[0]; - UNUSED(registers); - GBALog(gba, GBA_LOG_STUB, "RegisterRamReset unimplemented"); -} - -static void _BgAffineSet(struct GBA* gba) { - struct ARMCore* cpu = gba->cpu; - int i = cpu->gprs[2]; - float ox, oy; - float cx, cy; - float sx, sy; - float theta; - int offset = cpu->gprs[0]; - int destination = cpu->gprs[1]; - float a, b, c, d; - float rx, ry; - while (i--) { - // [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ] - // [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ] - // [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] - ox = cpu->memory.load32(cpu, offset, 0) / 256.f; - oy = cpu->memory.load32(cpu, offset + 4, 0) / 256.f; - cx = cpu->memory.load16(cpu, offset + 8, 0); - cy = cpu->memory.load16(cpu, offset + 10, 0); - sx = cpu->memory.load16(cpu, offset + 12, 0) / 256.f; - sy = cpu->memory.load16(cpu, offset + 14, 0) / 256.f; - theta = (cpu->memory.loadU16(cpu, offset + 16, 0) >> 8) / 128.f * M_PI; - offset += 20; - // Rotation - a = d = cosf(theta); - b = c = sinf(theta); - // Scale - a *= sx; - b *= -sx; - c *= sy; - d *= sy; - // Translate - rx = ox - (a * cx + b * cy); - ry = oy - (c * cx + d * cy); - cpu->memory.store16(cpu, destination, a * 256, 0); - cpu->memory.store16(cpu, destination + 2, b * 256, 0); - cpu->memory.store16(cpu, destination + 4, c * 256, 0); - cpu->memory.store16(cpu, destination + 6, d * 256, 0); - cpu->memory.store32(cpu, destination + 8, rx * 256, 0); - cpu->memory.store32(cpu, destination + 12, ry * 256, 0); - destination += 16; - } -} - -static void _ObjAffineSet(struct GBA* gba) { - struct ARMCore* cpu = gba->cpu; - int i = cpu->gprs[2]; - float sx, sy; - float theta; - int offset = cpu->gprs[0]; - int destination = cpu->gprs[1]; - int diff = cpu->gprs[3]; - float a, b, c, d; - while (i--) { - // [ sx 0 ] [ cos(theta) -sin(theta) ] [ A B ] - // [ 0 sy ] * [ sin(theta) cos(theta) ] = [ C D ] - sx = cpu->memory.load16(cpu, offset, 0) / 256.f; - sy = cpu->memory.load16(cpu, offset + 2, 0) / 256.f; - theta = (cpu->memory.loadU16(cpu, offset + 4, 0) >> 8) / 128.f * M_PI; - offset += 8; - // Rotation - a = d = cosf(theta); - b = c = sinf(theta); - // Scale - a *= sx; - b *= -sx; - c *= sy; - d *= sy; - cpu->memory.store16(cpu, destination, a * 256, 0); - cpu->memory.store16(cpu, destination + diff, b * 256, 0); - cpu->memory.store16(cpu, destination + diff * 2, c * 256, 0); - cpu->memory.store16(cpu, destination + diff * 3, d * 256, 0); - destination += diff * 4; - } -} - -static void _MidiKey2Freq(struct GBA* gba) { - struct ARMCore* cpu = gba->cpu; - uint32_t key = cpu->memory.load32(cpu, cpu->gprs[0] + 4, 0); - cpu->gprs[0] = key / powf(2, (180.f - cpu->gprs[1] - cpu->gprs[2] / 256.f) / 12.f); -} - -static void _Div(struct GBA* gba, int32_t num, int32_t denom) { - struct ARMCore* cpu = gba->cpu; - if (denom != 0) { - div_t result = div(num, denom); - cpu->gprs[0] = result.quot; - cpu->gprs[1] = result.rem; - cpu->gprs[3] = abs(result.quot); - } else { - GBALog(gba, GBA_LOG_GAME_ERROR, "Attempting to divide %i by zero!", num); - // If abs(num) > 1, this should hang, but that would be painful to - // emulate in HLE, and no game will get into a state where it hangs... - cpu->gprs[0] = (num < 0) ? -1 : 1; - cpu->gprs[1] = num; - cpu->gprs[3] = 1; - } -} - -void GBASwi16(struct ARMCore* cpu, int immediate) { - struct GBA* gba = (struct GBA*) cpu->master; - GBALog(gba, GBA_LOG_SWI, "SWI: %02X r0: %08X r1: %08X r2: %08X r3: %08X", - immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); - - if (gba->memory.fullBios) { - ARMRaiseSWI(cpu); - return; - } - switch (immediate) { - case 0x1: - _RegisterRamReset(gba); - break; - case 0x2: - GBAHalt(gba); - break; - case 0x05: - // VBlankIntrWait - // Fall through: - case 0x04: - // IntrWait - ARMRaiseSWI(cpu); - break; - case 0x6: - _Div(gba, cpu->gprs[0], cpu->gprs[1]); - break; - case 0x7: - _Div(gba, cpu->gprs[1], cpu->gprs[0]); - break; - case 0x8: - cpu->gprs[0] = sqrt(cpu->gprs[0]); - break; - case 0xA: - cpu->gprs[0] = atan2f(cpu->gprs[1] / 16384.f, cpu->gprs[0] / 16384.f) / (2 * M_PI) * 0x10000; - break; - case 0xB: - case 0xC: - ARMRaiseSWI(cpu); - break; - case 0xD: - cpu->gprs[0] = GBAChecksum(gba->memory.bios, SIZE_BIOS); - case 0xE: - _BgAffineSet(gba); - break; - case 0xF: - _ObjAffineSet(gba); - break; - case 0x11: - case 0x12: - if (cpu->gprs[0] < BASE_WORKING_RAM) { - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 source"); - break; - } - switch (cpu->gprs[1] >> BASE_OFFSET) { - case REGION_WORKING_RAM: - _unLz77(gba, cpu->gprs[0], &((uint8_t*) gba->memory.wram)[(cpu->gprs[1] & (SIZE_WORKING_RAM - 1))]); - break; - case REGION_WORKING_IRAM: - _unLz77(gba, cpu->gprs[0], &((uint8_t*) gba->memory.iwram)[(cpu->gprs[1] & (SIZE_WORKING_IRAM - 1))]); - break; - case REGION_VRAM: - _unLz77(gba, cpu->gprs[0], &((uint8_t*) gba->video.renderer->vram)[(cpu->gprs[1] & 0x0001FFFF)]); - break; - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 destination"); - break; - } - break; - case 0x13: - if (cpu->gprs[0] < BASE_WORKING_RAM) { - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman source"); - break; - } - switch (cpu->gprs[1] >> BASE_OFFSET) { - case REGION_WORKING_RAM: - _unHuffman(gba, cpu->gprs[0], &((uint32_t*) gba->memory.wram)[(cpu->gprs[1] & (SIZE_WORKING_RAM - 3)) >> 2]); - break; - case REGION_WORKING_IRAM: - _unHuffman(gba, cpu->gprs[0], &((uint32_t*) gba->memory.iwram)[(cpu->gprs[1] & (SIZE_WORKING_IRAM - 3)) >> 2]); - break; - case REGION_VRAM: - _unHuffman(gba, cpu->gprs[0], &((uint32_t*) gba->video.renderer->vram)[(cpu->gprs[1] & 0x0001FFFC) >> 2]); - break; - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman destination"); - break; - } - break; - case 0x14: - case 0x15: - if (cpu->gprs[0] < BASE_WORKING_RAM) { - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL source"); - break; - } - switch (cpu->gprs[1] >> BASE_OFFSET) { - case REGION_WORKING_RAM: - _unRl(gba, cpu->gprs[0], &((uint8_t*) gba->memory.wram)[(cpu->gprs[1] & (SIZE_WORKING_RAM - 1))]); - break; - case REGION_WORKING_IRAM: - _unRl(gba, cpu->gprs[0], &((uint8_t*) gba->memory.iwram)[(cpu->gprs[1] & (SIZE_WORKING_IRAM - 1))]); - break; - case REGION_VRAM: - _unRl(gba, cpu->gprs[0], &((uint8_t*) gba->video.renderer->vram)[(cpu->gprs[1] & 0x0001FFFF)]); - break; - default: - GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL destination"); - break; - } - break; - case 0x1F: - _MidiKey2Freq(gba); - break; - default: - GBALog(gba, GBA_LOG_STUB, "Stub software interrupt: %02X", immediate); - } -} - -void GBASwi32(struct ARMCore* cpu, int immediate) { - GBASwi16(cpu, immediate >> 16); -} - -uint32_t GBAChecksum(uint32_t* memory, size_t size) { - size_t i; - uint32_t sum = 0; - for (i = 0; i < size; i += 4) { - sum += memory[i >> 2]; - } - return sum; -} - -static void _unLz77(struct GBA* gba, uint32_t source, uint8_t* dest) { - struct ARMCore* cpu = gba->cpu; - int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8; - // We assume the signature byte (0x10) is correct - int blockheader; - uint32_t sPointer = source + 4; - uint8_t* dPointer = dest; - int blocksRemaining = 0; - int block; - uint8_t* disp; - int bytes; - while (remaining > 0) { - if (blocksRemaining) { - if (blockheader & 0x80) { - // Compressed - block = cpu->memory.loadU8(cpu, sPointer, 0) | (cpu->memory.loadU8(cpu, sPointer + 1, 0) << 8); - sPointer += 2; - disp = dPointer - (((block & 0x000F) << 8) | ((block & 0xFF00) >> 8)) - 1; - bytes = ((block & 0x00F0) >> 4) + 3; - while (bytes-- && remaining) { - --remaining; - *dPointer = *disp; - ++disp; - ++dPointer; - } - } else { - // Uncompressed - *dPointer = cpu->memory.loadU8(cpu, sPointer++, 0); - ++dPointer; - --remaining; - } - blockheader <<= 1; - --blocksRemaining; - } else { - blockheader = cpu->memory.loadU8(cpu, sPointer++, 0); - blocksRemaining = 8; - } - } -} - -DECL_BITFIELD(HuffmanNode, uint8_t); -DECL_BITS(HuffmanNode, Offset, 0, 6); -DECL_BIT(HuffmanNode, RTerm, 6); -DECL_BIT(HuffmanNode, LTerm, 7); - -static void _unHuffman(struct GBA* gba, uint32_t source, uint32_t* dest) { - struct ARMCore* cpu = gba->cpu; - source = source & 0xFFFFFFFC; - uint32_t header = cpu->memory.load32(cpu, source, 0); - int remaining = header >> 8; - int bits = header & 0xF; - if (32 % bits) { - GBALog(gba, GBA_LOG_STUB, "Unimplemented unaligned Huffman"); - return; - } - int padding = (4 - remaining) & 0x3; - remaining &= 0xFFFFFFFC; - // We assume the signature byte (0x20) is correct - int treesize = (cpu->memory.loadU8(cpu, source + 4, 0) << 1) + 1; - int block = 0; - uint32_t treeBase = source + 5; - uint32_t sPointer = source + 5 + treesize; - uint32_t* dPointer = dest; - uint32_t nPointer = treeBase; - HuffmanNode node; - int bitsRemaining; - int readBits; - int bitsSeen = 0; - node = cpu->memory.load8(cpu, nPointer, 0); - while (remaining > 0) { - uint32_t bitstream = cpu->memory.load32(cpu, sPointer, 0); - sPointer += 4; - for (bitsRemaining = 32; bitsRemaining > 0 && remaining > 0; --bitsRemaining, bitstream <<= 1) { - uint32_t next = (nPointer & ~1) + HuffmanNodeGetOffset(node) * 2 + 2; - if (bitstream & 0x80000000) { - // Go right - if (HuffmanNodeIsRTerm(node)) { - readBits = cpu->memory.load8(cpu, next + 1, 0); - } else { - nPointer = next + 1; - node = cpu->memory.load8(cpu, nPointer, 0); - continue; - } - } else { - // Go left - if (HuffmanNodeIsLTerm(node)) { - readBits = cpu->memory.load8(cpu, next, 0); - } else { - nPointer = next; - node = cpu->memory.load8(cpu, nPointer, 0); - continue; - } - } - - block |= (readBits & ((1 << bits) - 1)) << bitsSeen; - bitsSeen += bits; - nPointer = treeBase; - node = cpu->memory.load8(cpu, nPointer, 0); - if (bitsSeen == 32) { - bitsSeen = 0; - *dPointer = block; - ++dPointer; - remaining -= 4; - block = 0; - } - } - - } - if (padding) { - *dPointer = block; - } -} - -static void _unRl(struct GBA* gba, uint32_t source, uint8_t* dest) { - struct ARMCore* cpu = gba->cpu; - source = source & 0xFFFFFFFC; - int remaining = (cpu->memory.load32(cpu, source, 0) & 0xFFFFFF00) >> 8; - int padding = (4 - remaining) & 0x3; - // We assume the signature byte (0x30) is correct - int blockheader; - int block; - uint32_t sPointer = source + 4; - uint8_t* dPointer = dest; - while (remaining > 0) { - blockheader = cpu->memory.loadU8(cpu, sPointer++, 0); - if (blockheader & 0x80) { - // Compressed - blockheader &= 0x7F; - blockheader += 3; - block = cpu->memory.loadU8(cpu, sPointer++, 0); - while (blockheader-- && remaining) { - --remaining; - *dPointer = block; - ++dPointer; - } - } else { - // Uncompressed - blockheader++; - while (blockheader-- && remaining) { - --remaining; - *dPointer = cpu->memory.loadU8(cpu, sPointer++, 0); - ++dPointer; - } - } - } - while (padding--) { - *dPointer = 0; - ++dPointer; - } -}
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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
@@ -1,99 +0,0 @@
-/* Copyright (c) 2013-2014 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 "gba-cli.h" - -#include "gba-io.h" -#include "gba-serialize.h" -#include "gba-thread.h" - -#ifdef USE_CLI_DEBUGGER - -static const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share - -static void _GBACLIDebuggerInit(struct CLIDebuggerSystem*); -static void _GBACLIDebuggerDeinit(struct CLIDebuggerSystem*); -static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); - -static void _load(struct CLIDebugger*, struct CLIDebugVector*); -static void _save(struct CLIDebugger*, struct CLIDebugVector*); - -struct CLIDebuggerCommandSummary _GBACLIDebuggerCommands[] = { - { "load", _load, CLIDVParse, "Load a savestate" }, - { "save", _save, CLIDVParse, "Save a savestate" }, - { 0, 0, 0, 0 } -}; -#endif - -struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread* context) { - struct GBACLIDebugger* debugger = malloc(sizeof(struct GBACLIDebugger)); -#ifdef USE_CLI_DEBUGGER - debugger->d.init = _GBACLIDebuggerInit; - debugger->d.deinit = _GBACLIDebuggerDeinit; - debugger->d.lookupIdentifier = _GBACLIDebuggerLookupIdentifier; - - debugger->d.name = "Game Boy Advance"; - debugger->d.commands = _GBACLIDebuggerCommands; - - debugger->context = context; -#endif - - return debugger; -} - -#ifdef USE_CLI_DEBUGGER -static void _GBACLIDebuggerInit(struct CLIDebuggerSystem* debugger) { - UNUSED(debugger); -} - -static void _GBACLIDebuggerDeinit(struct CLIDebuggerSystem* debugger) { - UNUSED(debugger); -} - -static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger; - int i; - for (i = 0; i < REG_MAX; i += 2) { - const char* reg = GBAIORegisterNames[i >> 1]; - if (reg && strcasecmp(reg, name) == 0) { - return GBALoad16(gbaDebugger->context->gba->cpu, BASE_IO | i, 0); - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - -static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { - printf("%s\n", ERROR_MISSING_ARGS); - return; - } - - int state = dv->intValue; - if (state < 1 || state > 9) { - printf("State %u out of range", state); - } - - struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; - - GBALoadState(gbaDebugger->context->gba, gbaDebugger->context->stateDir, dv->intValue); -} - -static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { - printf("%s\n", ERROR_MISSING_ARGS); - return; - } - - int state = dv->intValue; - if (state < 1 || state > 9) { - printf("State %u out of range", state); - } - - struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; - - GBASaveState(gbaDebugger->context->gba, gbaDebugger->context->stateDir, dv->intValue, true); -} -#endif
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -15,6 +15,9 @@ #ifdef USE_CLI_DEBUGGER
struct CLIDebuggerSystem d; struct GBAThread* context; + + bool frameAdvance; + bool inVblank; #endif };
@@ -1,11 +1,11 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-config.h" +#include "config.h" -#include "platform/commandline.h" +#include "util/formatting.h" #include <sys/stat.h>@@ -13,6 +13,9 @@ #ifdef _WIN32
#include <windows.h> #include <shlobj.h> #include <strsafe.h> +#define PATH_SEP "\\" +#else +#define PATH_SEP "/" #endif #define SECTION_NAME_MAX 128@@ -84,7 +87,7 @@ if (!charValue) {
return false; } char* end; - float value = strtof(charValue, &end); + float value = strtof_u(charValue, &end); if (*end) { return false; }@@ -111,34 +114,31 @@ }
bool GBAConfigLoad(struct GBAConfig* config) { char path[PATH_MAX]; -#ifndef _WIN32 - char* home = getenv("HOME"); - snprintf(path, PATH_MAX, "%s/.config/%s/config.ini", home, BINARY_NAME); -#else - char home[MAX_PATH]; - SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home); - snprintf(path, PATH_MAX, "%s/%s/config.ini", home, PROJECT_NAME); -#endif + GBAConfigDirectory(path, PATH_MAX); + strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); return ConfigurationRead(&config->configTable, path); } bool GBAConfigSave(const struct GBAConfig* config) { char path[PATH_MAX]; + GBAConfigDirectory(path, PATH_MAX); + strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); + return ConfigurationWrite(&config->configTable, path); +} + +void GBAConfigDirectory(char* out, size_t outLength) { #ifndef _WIN32 char* home = getenv("HOME"); - snprintf(path, PATH_MAX, "%s/.config", home); - mkdir(path, 0755); - snprintf(path, PATH_MAX, "%s/.config/%s", home, BINARY_NAME); - mkdir(path, 0755); - snprintf(path, PATH_MAX, "%s/.config/%s/config.ini", home, BINARY_NAME); + snprintf(out, outLength, "%s/.config", home); + mkdir(out, 0755); + snprintf(out, outLength, "%s/.config/%s", home, BINARY_NAME); + mkdir(out, 0755); #else char home[MAX_PATH]; SHGetFolderPath(0, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, home); - snprintf(path, PATH_MAX, "%s/%s", home, PROJECT_NAME); - CreateDirectoryA(path, NULL); - snprintf(path, PATH_MAX, "%s/%s/config.ini", home, PROJECT_NAME); + snprintf(out, outLength, "%s\\%s", home, PROJECT_NAME); + CreateDirectoryA(out, NULL); #endif - return ConfigurationWrite(&config->configTable, path); } const char* GBAConfigGetValue(const struct GBAConfig* config, const char* key) {@@ -196,16 +196,42 @@ }
if (_lookupIntValue(config, "videoSync", &fakeBool)) { opts->videoSync = fakeBool; } + if (_lookupIntValue(config, "lockAspectRatio", &fakeBool)) { + opts->lockAspectRatio = fakeBool; + } + if (_lookupIntValue(config, "resampleVideo", &fakeBool)) { + opts->resampleVideo = fakeBool; + } + if (_lookupIntValue(config, "skipBios", &fakeBool)) { + opts->skipBios = fakeBool; + } + if (_lookupIntValue(config, "rewindEnable", &fakeBool)) { + opts->rewindEnable = fakeBool; + } _lookupIntValue(config, "fullscreen", &opts->fullscreen); _lookupIntValue(config, "width", &opts->width); _lookupIntValue(config, "height", &opts->height); + + char* idleOptimization = 0; + if (_lookupCharValue(config, "idleOptimization", &idleOptimization)) { + if (strcasecmp(idleOptimization, "ignore") == 0) { + opts->idleOptimization = IDLE_LOOP_IGNORE; + } else if (strcasecmp(idleOptimization, "remove") == 0) { + opts->idleOptimization = IDLE_LOOP_REMOVE; + } else if (strcasecmp(idleOptimization, "detect") == 0) { + opts->idleOptimization = IDLE_LOOP_DETECT; + } + free(idleOptimization); + } } void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) { ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios); + ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios); ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel); ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip); + ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget);@@ -215,6 +241,29 @@ ConfigurationSetIntValue(&config->defaultsTable, 0, "videoSync", opts->videoSync);
ConfigurationSetIntValue(&config->defaultsTable, 0, "fullscreen", opts->fullscreen); ConfigurationSetIntValue(&config->defaultsTable, 0, "width", opts->width); ConfigurationSetIntValue(&config->defaultsTable, 0, "height", opts->height); + ConfigurationSetIntValue(&config->defaultsTable, 0, "lockAspectRatio", opts->lockAspectRatio); + ConfigurationSetIntValue(&config->defaultsTable, 0, "resampleVideo", opts->resampleVideo); + + switch (opts->idleOptimization) { + case IDLE_LOOP_IGNORE: + ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "ignore"); + break; + case IDLE_LOOP_REMOVE: + ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "remove"); + break; + case IDLE_LOOP_DETECT: + ConfigurationSetValue(&config->defaultsTable, 0, "idleOptimization", "detect"); + break; + } +} + +// These two are basically placeholders in case the internal layout changes, e.g. for loading separate files +struct Configuration* GBAConfigGetInput(struct GBAConfig* config) { + return &config->configTable; +} + +struct Configuration* GBAConfigGetOverrides(struct GBAConfig* config) { + return &config->configTable; } void GBAConfigFreeOpts(struct GBAOptions* opts) {
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -7,6 +7,8 @@ #ifndef GBA_CONFIG_H
#define GBA_CONFIG_H #include "util/common.h" + +#include "gba/gba.h" #include "util/configuration.h"@@ -18,8 +20,10 @@ };
struct GBAOptions { char* bios; + bool skipBios; int logLevel; int frameskip; + bool rewindEnable; int rewindBufferCapacity; int rewindBufferInterval; float fpsTarget;@@ -28,9 +32,13 @@
int fullscreen; int width; int height; + bool lockAspectRatio; + bool resampleVideo; bool videoSync; bool audioSync; + + enum GBAIdleLoopOptimization idleOptimization; }; void GBAConfigInit(struct GBAConfig*, const char* port);@@ -38,6 +46,8 @@ void GBAConfigDeinit(struct GBAConfig*);
bool GBAConfigLoad(struct GBAConfig*); bool GBAConfigSave(const struct GBAConfig*); + +void GBAConfigDirectory(char* out, size_t outLength); const char* GBAConfigGetValue(const struct GBAConfig*, const char* key);@@ -53,6 +63,9 @@ void GBAConfigSetDefaultFloatValue(struct GBAConfig*, const char* key, float value);
void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts); void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts); + +struct Configuration* GBAConfigGetInput(struct GBAConfig*); +struct Configuration* GBAConfigGetOverrides(struct GBAConfig*); void GBAConfigFreeOpts(struct GBAOptions* opts);
@@ -1,332 +0,0 @@
-/* Copyright (c) 2013-2014 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 "gba.h" - -#include "gba-gpio.h" -#include "gba-sensors.h" -#include "gba-serialize.h" - -#include <time.h> - -static void _readPins(struct GBACartridgeGPIO* gpio); -static void _outputPins(struct GBACartridgeGPIO* gpio, unsigned pins); - -static void _rtcReadPins(struct GBACartridgeGPIO* gpio); -static unsigned _rtcOutput(struct GBACartridgeGPIO* gpio); -static void _rtcProcessByte(struct GBACartridgeGPIO* gpio); -static void _rtcUpdateClock(struct GBACartridgeGPIO* gpio); -static unsigned _rtcBCD(unsigned value); - -static void _gyroReadPins(struct GBACartridgeGPIO* gpio); - -static void _rumbleReadPins(struct GBACartridgeGPIO* gpio); - -static const int RTC_BYTES[8] = { - 0, // Force reset - 0, // Empty - 7, // Date/Time - 0, // Force IRQ - 1, // Control register - 0, // Empty - 3, // Time - 0 // Empty -}; - -void GBAGPIOInit(struct GBACartridgeGPIO* gpio, uint16_t* base) { - gpio->gpioDevices = GPIO_NONE; - gpio->direction = GPIO_WRITE_ONLY; - gpio->gpioBase = base; - gpio->pinState = 0; - gpio->direction = 0; -} - -void GBAGPIOWrite(struct GBACartridgeGPIO* gpio, uint32_t address, uint16_t value) { - switch (address) { - case GPIO_REG_DATA: - gpio->pinState = value; - _readPins(gpio); - break; - case GPIO_REG_DIRECTION: - gpio->direction = value; - break; - case GPIO_REG_CONTROL: - gpio->readWrite = value; - break; - default: - GBALog(gpio->p, GBA_LOG_WARN, "Invalid GPIO address"); - } - - if (gpio->readWrite) { - uint16_t old = gpio->gpioBase[0]; - old &= ~gpio->direction; - gpio->gpioBase[0] = old | (value & gpio->direction); - } -} - -void GBAGPIOInitRTC(struct GBACartridgeGPIO* gpio) { - gpio->gpioDevices |= GPIO_RTC; - gpio->rtc.bytesRemaining = 0; - - gpio->rtc.transferStep = 0; - - gpio->rtc.bitsRead = 0; - gpio->rtc.bits = 0; - gpio->rtc.commandActive = 0; - gpio->rtc.command.packed = 0; - gpio->rtc.control.packed = 0x40; - memset(gpio->rtc.time, 0, sizeof(gpio->rtc.time)); -} - -void _readPins(struct GBACartridgeGPIO* gpio) { - if (gpio->gpioDevices & GPIO_RTC) { - _rtcReadPins(gpio); - } - - if (gpio->gpioDevices & GPIO_GYRO) { - _gyroReadPins(gpio); - } - - if (gpio->gpioDevices & GPIO_RUMBLE) { - _rumbleReadPins(gpio); - } -} - -void _outputPins(struct GBACartridgeGPIO* gpio, unsigned pins) { - if (gpio->readWrite) { - uint16_t old = gpio->gpioBase[0]; - old &= gpio->direction; - gpio->gpioBase[0] = old | (pins & ~gpio->direction & 0xF); - } -} - -// == RTC - -void _rtcReadPins(struct GBACartridgeGPIO* gpio) { - // Transfer sequence: - // P: 0 | 1 | 2 | 3 - // == Initiate - // > HI | - | LO | - - // > HI | - | HI | - - // == Transfer bit (x8) - // > LO | x | HI | - - // > HI | - | HI | - - // < ?? | x | ?? | - - // == Terminate - // > - | - | LO | - - switch (gpio->rtc.transferStep) { - case 0: - if ((gpio->pinState & 5) == 1) { - gpio->rtc.transferStep = 1; - } - break; - case 1: - if ((gpio->pinState & 5) == 5) { - gpio->rtc.transferStep = 2; - } - break; - case 2: - if (!gpio->p0) { - gpio->rtc.bits &= ~(1 << gpio->rtc.bitsRead); - gpio->rtc.bits |= gpio->p1 << gpio->rtc.bitsRead; - } else { - if (gpio->p2) { - // GPIO direction should always != reading - if (gpio->dir1) { - if (gpio->rtc.command.reading) { - GBALog(gpio->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode"); - } - ++gpio->rtc.bitsRead; - if (gpio->rtc.bitsRead == 8) { - _rtcProcessByte(gpio); - } - } else { - _outputPins(gpio, 5 | (_rtcOutput(gpio) << 1)); - ++gpio->rtc.bitsRead; - if (gpio->rtc.bitsRead == 8) { - --gpio->rtc.bytesRemaining; - if (gpio->rtc.bytesRemaining <= 0) { - gpio->rtc.commandActive = 0; - gpio->rtc.command.reading = 0; - } - gpio->rtc.bitsRead = 0; - } - } - } else { - gpio->rtc.bitsRead = 0; - gpio->rtc.bytesRemaining = 0; - gpio->rtc.commandActive = 0; - gpio->rtc.command.reading = 0; - gpio->rtc.transferStep = 0; - } - } - break; - } -} - -void _rtcProcessByte(struct GBACartridgeGPIO* gpio) { - --gpio->rtc.bytesRemaining; - if (!gpio->rtc.commandActive) { - union RTCCommandData command; - command.packed = gpio->rtc.bits; - if (command.magic == 0x06) { - gpio->rtc.command = command; - - gpio->rtc.bytesRemaining = RTC_BYTES[gpio->rtc.command.command]; - gpio->rtc.commandActive = gpio->rtc.bytesRemaining > 0; - switch (command.command) { - case RTC_RESET: - gpio->rtc.control.packed = 0; - break; - case RTC_DATETIME: - case RTC_TIME: - _rtcUpdateClock(gpio); - break; - case RTC_FORCE_IRQ: - case RTC_CONTROL: - break; - } - } else { - GBALog(gpio->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", gpio->rtc.bits); - } - } else { - switch (gpio->rtc.command.command) { - case RTC_CONTROL: - gpio->rtc.control.packed = gpio->rtc.bits; - break; - case RTC_FORCE_IRQ: - GBALog(gpio->p, GBA_LOG_STUB, "Unimplemented RTC command %u", gpio->rtc.command.command); - break; - case RTC_RESET: - case RTC_DATETIME: - case RTC_TIME: - break; - } - } - - gpio->rtc.bits = 0; - gpio->rtc.bitsRead = 0; - if (!gpio->rtc.bytesRemaining) { - gpio->rtc.commandActive = 0; - gpio->rtc.command.reading = 0; - } -} - -unsigned _rtcOutput(struct GBACartridgeGPIO* gpio) { - uint8_t outputByte = 0; - switch (gpio->rtc.command.command) { - case RTC_CONTROL: - outputByte = gpio->rtc.control.packed; - break; - case RTC_DATETIME: - case RTC_TIME: - outputByte = gpio->rtc.time[7 - gpio->rtc.bytesRemaining]; - break; - case RTC_FORCE_IRQ: - case RTC_RESET: - break; - } - unsigned output = (outputByte >> gpio->rtc.bitsRead) & 1; - return output; -} - -void _rtcUpdateClock(struct GBACartridgeGPIO* gpio) { - time_t t = time(0); - struct tm date; -#ifdef _WIN32 - date = *localtime(&t); -#else - localtime_r(&t, &date); -#endif - gpio->rtc.time[0] = _rtcBCD(date.tm_year - 100); - gpio->rtc.time[1] = _rtcBCD(date.tm_mon + 1); - gpio->rtc.time[2] = _rtcBCD(date.tm_mday); - gpio->rtc.time[3] = _rtcBCD(date.tm_wday); - if (gpio->rtc.control.hour24) { - gpio->rtc.time[4] = _rtcBCD(date.tm_hour); - } else { - gpio->rtc.time[4] = _rtcBCD(date.tm_hour % 12); - } - gpio->rtc.time[5] = _rtcBCD(date.tm_min); - gpio->rtc.time[6] = _rtcBCD(date.tm_sec); -} - -unsigned _rtcBCD(unsigned value) { - int counter = value % 10; - value /= 10; - counter += (value % 10) << 4; - return counter; -} - -// == Gyro - -void GBAGPIOInitGyro(struct GBACartridgeGPIO* gpio) { - gpio->gpioDevices |= GPIO_GYRO; - gpio->gyroSample = 0; - gpio->gyroEdge = 0; -} - -void _gyroReadPins(struct GBACartridgeGPIO* gpio) { - struct GBARotationSource* gyro = gpio->p->rotationSource; - if (!gyro) { - return; - } - - if (gpio->p0) { - if (gyro->sample) { - gyro->sample(gyro); - } - int32_t sample = gyro->readGyroZ(gyro); - - // Normalize to ~12 bits, focused on 0x6C0 - gpio->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative - } - - if (gpio->gyroEdge && !gpio->p1) { - // Write bit on falling edge - unsigned bit = gpio->gyroSample >> 15; - gpio->gyroSample <<= 1; - _outputPins(gpio, bit << 2); - } - - gpio->gyroEdge = gpio->p1; -} - -// == Rumble - -void GBAGPIOInitRumble(struct GBACartridgeGPIO* gpio) { - gpio->gpioDevices |= GPIO_RUMBLE; -} - -void _rumbleReadPins(struct GBACartridgeGPIO* gpio) { - struct GBARumble* rumble = gpio->p->rumble; - if (!rumble) { - return; - } - - rumble->setRumble(rumble, gpio->p3); -} - -// == Serialization - -void GBAGPIOSerialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state) { - state->gpio.readWrite = gpio->readWrite; - state->gpio.pinState = gpio->pinState; - state->gpio.pinDirection = gpio->direction; - state->gpio.devices = gpio->gpioDevices; - state->gpio.rtc = gpio->rtc; - state->gpio.gyroSample = gpio->gyroSample; - state->gpio.gyroEdge = gpio->gyroEdge; -} - -void GBAGPIODeserialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state) { - gpio->readWrite = state->gpio.readWrite; - gpio->pinState = state->gpio.pinState; - gpio->direction = state->gpio.pinDirection; - // TODO: Deterministic RTC - gpio->rtc = state->gpio.rtc; - gpio->gyroSample = state->gpio.gyroSample; - gpio->gyroEdge = state->gpio.gyroEdge; -}
@@ -1,121 +0,0 @@
-/* Copyright (c) 2013-2014 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 GBA_GPIO_H -#define GBA_GPIO_H - -#include "util/common.h" - -#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL) - -enum GPIODevice { - GPIO_NONE = 0, - GPIO_RTC = 1, - GPIO_RUMBLE = 2, - GPIO_LIGHT_SENSOR = 4, - GPIO_GYRO = 8, - GPIO_TILT = 16 -}; - -enum GPIORegister { - GPIO_REG_DATA = 0xC4, - GPIO_REG_DIRECTION = 0xC6, - GPIO_REG_CONTROL = 0xC8 -}; - -enum GPIODirection { - GPIO_WRITE_ONLY = 0, - GPIO_READ_WRITE = 1 -}; - -union RTCControl { - struct { - unsigned : 3; - unsigned minIRQ : 1; - unsigned : 2; - unsigned hour24 : 1; - unsigned poweroff : 1; - }; - uint8_t packed; -}; - -enum RTCCommand { - RTC_RESET = 0, - RTC_DATETIME = 2, - RTC_FORCE_IRQ = 3, - RTC_CONTROL = 4, - RTC_TIME = 6 -}; - -union RTCCommandData { - struct { - unsigned magic : 4; - enum RTCCommand command : 3; - unsigned reading : 1; - }; - uint8_t packed; -}; - -struct GBARTC { - int bytesRemaining; - int transferStep; - int bitsRead; - int bits; - int commandActive; - union RTCCommandData command; - union RTCControl control; - uint8_t time[7]; -} __attribute__((packed)); - -struct GBARumble { - void (*setRumble)(struct GBARumble*, int enable); -}; - -struct GBACartridgeGPIO { - struct GBA* p; - int gpioDevices; - enum GPIODirection readWrite; - uint16_t* gpioBase; - - union { - struct { - unsigned p0 : 1; - unsigned p1 : 1; - unsigned p2 : 1; - unsigned p3 : 1; - }; - uint16_t pinState; - }; - - union { - struct { - unsigned dir0 : 1; - unsigned dir1 : 1; - unsigned dir2 : 1; - unsigned dir3 : 1; - }; - uint16_t direction; - }; - - struct GBARTC rtc; - - uint16_t gyroSample; - bool gyroEdge; -}; - -void GBAGPIOInit(struct GBACartridgeGPIO* gpio, uint16_t* gpioBase); -void GBAGPIOWrite(struct GBACartridgeGPIO* gpio, uint32_t address, uint16_t value); - -void GBAGPIOInitRTC(struct GBACartridgeGPIO* gpio); - -void GBAGPIOInitGyro(struct GBACartridgeGPIO* gpio); - -void GBAGPIOInitRumble(struct GBACartridgeGPIO* gpio); - -struct GBASerializedState; -void GBAGPIOSerialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state); -void GBAGPIODeserialize(struct GBACartridgeGPIO* gpio, struct GBASerializedState* state); - -#endif
@@ -1,181 +0,0 @@
-/* Copyright (c) 2013-2014 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 "gba-input.h" - -#include "util/configuration.h" - -#include <inttypes.h> - -#define SECTION_NAME_MAX 128 -#define KEY_NAME_MAX 32 -#define KEY_VALUE_MAX 16 - -struct GBAInputMapImpl { - int* map; - uint32_t type; -}; - -static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) { - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; - - char keyKey[KEY_NAME_MAX]; - snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName); - keyKey[KEY_NAME_MAX - 1] = '\0'; - - const char* value = ConfigurationGetValue(config, sectionName, keyKey); - if (!value) { - return; - } - char* end; - long intValue = strtol(value, &end, 10); - if (*end) { - return; - } - GBAInputBindKey(map, type, intValue, key); -} - -static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) { - char sectionName[SECTION_NAME_MAX]; - snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); - sectionName[SECTION_NAME_MAX - 1] = '\0'; - - char keyKey[KEY_NAME_MAX]; - snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName); - keyKey[KEY_NAME_MAX - 1] = '\0'; - - int value = GBAInputQueryBinding(map, type, key); - char keyValue[KEY_VALUE_MAX]; - snprintf(keyValue, KEY_VALUE_MAX, "%" PRIi32, value); - - ConfigurationSetValue(config, sectionName, keyKey, keyValue); -} - -void GBAInputMapInit(struct GBAInputMap* map) { - map->maps = 0; - map->numMaps = 0; -} - -void GBAInputMapDeinit(struct GBAInputMap* map) { - size_t m; - for (m = 0; m < map->numMaps; ++m) { - free(map->maps[m].map); - } - free(map->maps); - map->maps = 0; - map->numMaps = 0; -} - -enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key) { - size_t m; - const struct GBAInputMapImpl* impl = 0; - for (m = 0; m < map->numMaps; ++m) { - if (map->maps[m].type == type) { - impl = &map->maps[m]; - break; - } - } - if (!impl || !impl->map) { - return GBA_KEY_NONE; - } - - for (m = 0; m < GBA_KEY_MAX; ++m) { - if (impl->map[m] == key) { - return m; - } - } - return GBA_KEY_NONE; -} - -void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) { - struct GBAInputMapImpl* impl = 0; - if (map->numMaps == 0) { - map->maps = malloc(sizeof(*map->maps)); - map->numMaps = 1; - impl = &map->maps[0]; - impl->type = type; - impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); - } else { - size_t m; - for (m = 0; m < map->numMaps; ++m) { - if (map->maps[m].type == type) { - impl = &map->maps[m]; - break; - } - } - } - if (!impl) { - size_t m; - for (m = 0; m < map->numMaps; ++m) { - if (!map->maps[m].type) { - impl = &map->maps[m]; - break; - } - } - if (impl) { - impl->type = type; - impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); - } else { - map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2); - for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) { - map->maps[m].type = 0; - map->maps[m].map = 0; - } - map->numMaps *= 2; - impl = &map->maps[m]; - impl->type = type; - impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); - } - } - impl->map[input] = key; -} - -int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) { - if (input >= GBA_KEY_MAX) { - return 0; - } - - size_t m; - const struct GBAInputMapImpl* impl = 0; - for (m = 0; m < map->numMaps; ++m) { - if (map->maps[m].type == type) { - impl = &map->maps[m]; - break; - } - } - if (!impl || !impl->map) { - return 0; - } - - return impl->map[input]; -} - -void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) { - _loadKey(map, type, config, GBA_KEY_A, "A"); - _loadKey(map, type, config, GBA_KEY_B, "B"); - _loadKey(map, type, config, GBA_KEY_L, "L"); - _loadKey(map, type, config, GBA_KEY_R, "R"); - _loadKey(map, type, config, GBA_KEY_START, "Start"); - _loadKey(map, type, config, GBA_KEY_SELECT, "Select"); - _loadKey(map, type, config, GBA_KEY_UP, "Up"); - _loadKey(map, type, config, GBA_KEY_DOWN, "Down"); - _loadKey(map, type, config, GBA_KEY_LEFT, "Left"); - _loadKey(map, type, config, GBA_KEY_RIGHT, "Right"); -} - -void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) { - _saveKey(map, type, config, GBA_KEY_A, "A"); - _saveKey(map, type, config, GBA_KEY_B, "B"); - _saveKey(map, type, config, GBA_KEY_L, "L"); - _saveKey(map, type, config, GBA_KEY_R, "R"); - _saveKey(map, type, config, GBA_KEY_START, "Start"); - _saveKey(map, type, config, GBA_KEY_SELECT, "Select"); - _saveKey(map, type, config, GBA_KEY_UP, "Up"); - _saveKey(map, type, config, GBA_KEY_DOWN, "Down"); - _saveKey(map, type, config, GBA_KEY_LEFT, "Left"); - _saveKey(map, type, config, GBA_KEY_RIGHT, "Right"); -}
@@ -1,28 +0,0 @@
-/* Copyright (c) 2013-2014 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 GBA_INPUT_H -#define GBA_INPUT_H - -#include "gba.h" - -struct Configuration; - -struct GBAInputMap { - struct GBAInputMapImpl* maps; - size_t numMaps; -}; - -void GBAInputMapInit(struct GBAInputMap*); -void GBAInputMapDeinit(struct GBAInputMap*); - -enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key); -void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input); -int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input); - -void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*); -void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*); - -#endif
@@ -1,14 +1,14 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-io.h" +#include "io.h" -#include "gba-rr.h" -#include "gba-serialize.h" -#include "gba-sio.h" -#include "gba-video.h" +#include "gba/supervisor/rr.h" +#include "gba/serialize.h" +#include "gba/sio.h" +#include "gba/video.h" const char* GBAIORegisterNames[] = { // Video@@ -86,7 +86,7 @@ "SOUNDBIAS",
0, 0, 0, - "WAVE_RAM0_LO" + "WAVE_RAM0_LO", "WAVE_RAM0_HI", "WAVE_RAM1_LO", "WAVE_RAM1_HI",@@ -141,7 +141,6 @@ "TM2CNT_LO",
"TM2CNT_HI", "TM3CNT_LO", "TM3CNT_HI", - 0, 0, 0, 0, 0, 0, 0, 0, 0,@@ -173,7 +172,7 @@ 0,
"JOY_RECV_LO", "JOY_RECV_HI", "JOY_TRANS_LO", - "JOY_RECV_HI", + "JOY_TRANS_HI", "JOYSTAT", 0, 0,@@ -242,7 +241,7 @@ };
static const int _isSpecialRegister[REG_MAX >> 1] = { // Video - 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,@@ -279,7 +278,7 @@ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Interrupts - 1, 1, 1, 0, 1 + 1, 1, 0, 0, 1 }; void GBAIOInit(struct GBA* gba) {@@ -298,7 +297,7 @@ // Video
case REG_DISPSTAT: value &= 0xFFF8; GBAVideoWriteDISPSTAT(&gba->video, value); - break; + return; // Audio case REG_SOUND1CNT_LO:@@ -357,8 +356,6 @@ case REG_WAVE_RAM0_LO:
case REG_WAVE_RAM1_LO: case REG_WAVE_RAM2_LO: case REG_WAVE_RAM3_LO: - case REG_FIFO_A_LO: - case REG_FIFO_B_LO: GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); break;@@ -366,9 +363,26 @@ case REG_WAVE_RAM0_HI:
case REG_WAVE_RAM1_HI: case REG_WAVE_RAM2_HI: case REG_WAVE_RAM3_HI: + GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); + break; + + // TODO: Confirm this behavior on real hardware + case REG_FIFO_A_LO: + case REG_FIFO_B_LO: + if (gba->performingDMA) { + GBAAudioWriteFIFO16(&gba->audio, address, value); + } else { + GBAIOWrite32(gba, address, (gba->memory.io[(address >> 1) + 1] << 16) | value); + } + break; + case REG_FIFO_A_HI: case REG_FIFO_B_HI: - GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); + if (gba->performingDMA) { + GBAAudioWriteFIFO16(&gba->audio, address, value); + } else { + GBAIOWrite32(gba, address - 2, gba->memory.io[(address >> 1) - 1] | (value << 16)); + } break; // DMA@@ -553,6 +567,7 @@ gba->memory.io[(address >> 1) + 1] = value >> 16;
} uint16_t GBAIORead(struct GBA* gba, uint32_t address) { + gba->lastJump = -1; // IO reads need to invalidate detected idle loops switch (address) { case REG_TM0CNT_LO: GBATimerUpdateRegister(gba, 0);@@ -568,12 +583,12 @@ GBATimerUpdateRegister(gba, 3);
break; case REG_KEYINPUT: - if (GBARRIsPlaying(gba->rr)) { - return 0x3FF ^ GBARRQueryInput(gba->rr); + if (gba->rr && gba->rr->isPlaying(gba->rr)) { + return 0x3FF ^ gba->rr->queryInput(gba->rr); } else if (gba->keySource) { uint16_t input = *gba->keySource; - if (GBARRIsRecording(gba->rr)) { - GBARRLogInput(gba->rr, input); + if (gba->rr && gba->rr->isRecording(gba->rr)) { + gba->rr->logInput(gba->rr, input); } return 0x3FF ^ input; }@@ -657,10 +672,10 @@ state->dma[i].nextEvent = gba->memory.dma[i].nextEvent;
} memcpy(state->timers, gba->timers, sizeof(state->timers)); - GBAGPIOSerialize(&gba->memory.gpio, state); + GBAHardwareSerialize(&gba->memory.hw, state); } -void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state) { +void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { int i; for (i = 0; i < REG_MAX; i += 2) { if (_isSpecialRegister[i >> 1]) {@@ -686,5 +701,5 @@ if (gba->timers[i].enable) {
gba->timersEnabled |= 1 << i; } } - GBAGPIODeserialize(&gba->memory.gpio, state); + GBAHardwareDeserialize(&gba->memory.hw, state); }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,7 +8,7 @@ #define GBA_IO_H
#include "util/common.h" -#include "gba.h" +#include "gba/gba.h" enum GBAIORegisters { // Video@@ -161,6 +161,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address);
struct GBASerializedState; void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state); -void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state); +void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state); #endif
@@ -1,19 +1,23 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-memory.h" +#include "memory.h" #include "macros.h" -#include "gba-gpio.h" -#include "gba-io.h" -#include "gba-serialize.h" -#include "hle-bios.h" +#include "decoder.h" +#include "gba/hardware.h" +#include "gba/io.h" +#include "gba/serialize.h" +#include "gba/hle-bios.h" #include "util/memory.h" +#define IDLE_LOOP_THRESHOLD 10000 + static uint32_t _popcount32(unsigned bits); +static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE }; static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info);@@ -30,9 +34,7 @@ void GBAMemoryInit(struct GBA* gba) {
struct ARMCore* cpu = gba->cpu; cpu->memory.load32 = GBALoad32; cpu->memory.load16 = GBALoad16; - cpu->memory.loadU16 = GBALoadU16; cpu->memory.load8 = GBALoad8; - cpu->memory.loadU8 = GBALoadU8; cpu->memory.loadMultiple = GBALoadMultiple; cpu->memory.store32 = GBAStore32; cpu->memory.store16 = GBAStore16;@@ -44,7 +46,7 @@ gba->memory.fullBios = 0;
gba->memory.wram = 0; gba->memory.iwram = 0; gba->memory.rom = 0; - gba->memory.gpio.p = gba; + gba->memory.hw.p = gba; int i; for (i = 0; i < 16; ++i) {@@ -101,9 +103,10 @@ memset(gba->memory.io, 0, sizeof(gba->memory.io));
memset(gba->memory.dma, 0, sizeof(gba->memory.dma)); int i; for (i = 0; i < 4; ++i) { - gba->memory.dma[i].count = 0x10000; + gba->memory.dma[i].count = 0x4000; gba->memory.dma[i].nextEvent = INT_MAX; } + gba->memory.dma[3].count = 0x10000; gba->memory.activeDMA = -1; gba->memory.nextDMA = INT_MAX; gba->memory.eventDiff = 0;@@ -114,20 +117,120 @@ GBALog(gba, GBA_LOG_FATAL, "Could not map memory");
} } +static void _analyzeForIdleLoop(struct GBA* gba, struct ARMCore* cpu, uint32_t address) { + struct ARMInstructionInfo info; + uint32_t nextAddress = address; + memset(gba->taintedRegisters, 0, sizeof(gba->taintedRegisters)); + if (cpu->executionMode == MODE_THUMB) { + while (true) { + uint16_t opcode; + LOAD_16(opcode, nextAddress & cpu->memory.activeMask, cpu->memory.activeRegion); + ARMDecodeThumb(opcode, &info); + switch (info.branchType) { + case ARM_BRANCH_NONE: + if (info.operandFormat & ARM_OPERAND_MEMORY_2) { + if (info.mnemonic == ARM_MN_STR || gba->taintedRegisters[info.memory.baseReg]) { + gba->idleDetectionStep = -1; + return; + } + uint32_t loadAddress = gba->cachedRegisters[info.memory.baseReg]; + uint32_t offset = 0; + if (info.memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) { + offset = info.memory.offset.immediate; + } else if (info.memory.format & ARM_MEMORY_REGISTER_OFFSET) { + int reg = info.memory.offset.reg; + if (gba->cachedRegisters[reg]) { + gba->idleDetectionStep = -1; + return; + } + offset = gba->cachedRegisters[reg]; + } + if (info.memory.format & ARM_MEMORY_OFFSET_SUBTRACT) { + loadAddress -= offset; + } else { + loadAddress += offset; + } + if ((loadAddress >> BASE_OFFSET) == REGION_IO) { + gba->idleDetectionStep = -1; + return; + } + if ((loadAddress >> BASE_OFFSET) < REGION_CART0 || (loadAddress >> BASE_OFFSET) > REGION_CART2_EX) { + gba->taintedRegisters[info.op1.reg] = true; + } else { + switch (info.memory.width) { + case 1: + gba->cachedRegisters[info.op1.reg] = GBALoad8(cpu, loadAddress, 0); + break; + case 2: + gba->cachedRegisters[info.op1.reg] = GBALoad16(cpu, loadAddress, 0); + break; + case 4: + gba->cachedRegisters[info.op1.reg] = GBALoad32(cpu, loadAddress, 0); + break; + } + } + } else if (info.operandFormat & ARM_OPERAND_AFFECTED_1) { + gba->taintedRegisters[info.op1.reg] = true; + } + nextAddress += WORD_SIZE_THUMB; + break; + case ARM_BRANCH: + if ((uint32_t) info.op1.immediate + nextAddress + WORD_SIZE_THUMB * 2 == address) { + gba->idleLoop = address; + gba->idleOptimization = IDLE_LOOP_REMOVE; + } + gba->idleDetectionStep = -1; + return; + default: + gba->idleDetectionStep = -1; + return; + } + } + } else { + gba->idleDetectionStep = -1; + } +} + static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { struct GBA* gba = (struct GBA*) cpu->master; struct GBAMemory* memory = &gba->memory; - if (address == gba->busyLoop && memory->activeRegion != REGION_BIOS) { - GBAHalt(gba); + int newRegion = address >> BASE_OFFSET; + if (gba->idleOptimization >= IDLE_LOOP_REMOVE && memory->activeRegion != REGION_BIOS) { + if (address == gba->lastJump && address == gba->idleLoop) { + GBAHalt(gba); + } else if (gba->idleOptimization >= IDLE_LOOP_DETECT && newRegion == memory->activeRegion) { + if (address == gba->lastJump) { + switch (gba->idleDetectionStep) { + case 0: + memcpy(gba->cachedRegisters, cpu->gprs, sizeof(gba->cachedRegisters)); + ++gba->idleDetectionStep; + break; + case 1: + if (memcmp(gba->cachedRegisters, cpu->gprs, sizeof(gba->cachedRegisters))) { + gba->idleDetectionStep = -1; + ++gba->idleDetectionFailures; + if (gba->idleDetectionFailures > IDLE_LOOP_THRESHOLD) { + gba->idleOptimization = IDLE_LOOP_IGNORE; + } + break; + } + _analyzeForIdleLoop(gba, cpu, address); + break; + } + } else { + gba->idleDetectionStep = 0; + } + } } - int newRegion = address >> BASE_OFFSET; + gba->lastJump = address; if (newRegion == memory->activeRegion) { return; } + if (memory->activeRegion == REGION_BIOS) { - memory->biosPrefetch = cpu->prefetch; + memory->biosPrefetch = cpu->prefetch[1]; } memory->activeRegion = newRegion; switch (address & ~OFFSET_MASK) {@@ -157,7 +260,7 @@ cpu->memory.activeRegion = memory->rom;
cpu->memory.activeMask = SIZE_CART0 - 1; break; default: - cpu->memory.activeRegion = 0; + cpu->memory.activeRegion = _deadbeef; cpu->memory.activeMask = 0; GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address"); break;@@ -172,10 +275,10 @@ }
#define LOAD_BAD \ GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load32: 0x%08X", address); \ - if (cpu->cycles >= cpu->nextEvent) { \ + if (gba->performingDMA) { \ value = gba->bus; \ } else { \ - value = cpu->prefetch; \ + value = cpu->prefetch[1]; \ if (cpu->executionMode == MODE_THUMB) { \ value |= value << 16; \ } \@@ -205,7 +308,11 @@ LOAD_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); \
++wait; #define LOAD_VRAM \ - LOAD_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \ + if ((address & 0x0001FFFF) < SIZE_VRAM) { \ + LOAD_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \ + } else { \ + LOAD_32(value, address & 0x00017FFF, gba->video.renderer->vram); \ + } \ ++wait; #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw);@@ -221,10 +328,12 @@ value |= value << 16; \
} #define LOAD_SRAM \ - GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load32: 0x%08X", address); \ - value = 0xDEADBEEF; + wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; \ + value = GBALoad8(cpu, address, 0); \ + value |= value << 8; \ + value |= value << 16; -int32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { +uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { struct GBA* gba = (struct GBA*) cpu->master; struct GBAMemory* memory = &gba->memory; uint32_t value = 0;@@ -275,17 +384,13 @@ *cycleCounter += 2 + wait;
} // Unaligned 32-bit loads are "rotated" so they make some semblance of sense int rotate = (address & 3) << 3; - return (value >> rotate) | (value << (32 - rotate)); -} - -uint16_t GBALoadU16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { - return GBALoad16(cpu, address, cycleCounter); + return ROR(value, rotate); } -int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { +uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { struct GBA* gba = (struct GBA*) cpu->master; struct GBAMemory* memory = &gba->memory; - uint16_t value = 0; + uint32_t value = 0; int wait = 0; switch (address >> BASE_OFFSET) {@@ -295,14 +400,18 @@ if (memory->activeRegion == REGION_BIOS) {
LOAD_16(value, address, memory->bios); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load16: 0x%08X", address); - value = memory->biosPrefetch; + LOAD_16(value, address & 2, &memory->biosPrefetch); } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); - if (cpu->cycles >= cpu->nextEvent) { - value = gba->bus; + if (gba->performingDMA) { + LOAD_16(value, address & 2, &gba->bus); } else { - value = cpu->prefetch; + uint32_t prefetch = cpu->prefetch[1]; + if (cpu->executionMode == MODE_THUMB) { + prefetch |= prefetch << 16; + } + LOAD_16(value, address & 2, &prefetch); } } break;@@ -320,7 +429,11 @@ case REGION_PALETTE_RAM:
LOAD_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); break; case REGION_VRAM: - LOAD_16(value, address & 0x0001FFFF, gba->video.renderer->vram); + if ((address & 0x0001FFFF) < SIZE_VRAM) { + LOAD_16(value, address & 0x0001FFFF, gba->video.renderer->vram); + } else { + LOAD_16(value, address & 0x00017FFF, gba->video.renderer->vram); + } break; case REGION_OAM: LOAD_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);@@ -351,14 +464,20 @@ }
break; case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: - GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load16: 0x%08X", address); + wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; + value = GBALoad8(cpu, address, 0); + value |= value << 8; break; default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address); - if (cpu->cycles >= cpu->nextEvent) { - value = gba->bus; + if (gba->performingDMA) { + LOAD_16(value, address & 2, &gba->bus); } else { - value = cpu->prefetch; + uint32_t prefetch = cpu->prefetch[1]; + if (cpu->executionMode == MODE_THUMB) { + prefetch |= prefetch << 16; + } + LOAD_16(value, address & 2, &prefetch); } break; }@@ -368,17 +487,13 @@ *cycleCounter += 2 + wait;
} // Unaligned 16-bit loads are "unpredictable", but the GBA rotates them, so we have to, too. int rotate = (address & 1) << 3; - return (value >> rotate) | (value << (16 - rotate)); -} - -uint8_t GBALoadU8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { - return GBALoad8(cpu, address, cycleCounter); + return ROR(value, rotate); } -int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { +uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { struct GBA* gba = (struct GBA*) cpu->master; struct GBAMemory* memory = &gba->memory; - int8_t value = 0; + uint8_t value = 0; int wait = 0; switch (address >> BASE_OFFSET) {@@ -388,14 +503,18 @@ if (memory->activeRegion == REGION_BIOS) {
value = ((int8_t*) memory->bios)[address]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address); - value = memory->biosPrefetch; + value = ((uint8_t*) &memory->biosPrefetch)[address & 3]; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); - if (cpu->cycles >= cpu->nextEvent) { - value = gba->bus; + if (gba->performingDMA) { + value = ((uint8_t*) &gba->bus)[address & 3]; } else { - value = cpu->prefetch; + uint32_t prefetch = cpu->prefetch[1]; + if (cpu->executionMode == MODE_THUMB) { + prefetch |= prefetch << 16; + } + value = ((uint8_t*) &prefetch)[address & 3]; } } break;@@ -413,7 +532,11 @@ case REGION_PALETTE_RAM:
value = ((int8_t*) gba->video.palette)[address & (SIZE_PALETTE_RAM - 1)]; break; case REGION_VRAM: - value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; + if ((address & 0x0001FFFF) < SIZE_VRAM) { + value = ((int8_t*) gba->video.renderer->vram)[address & 0x0001FFFF]; + } else { + value = ((int8_t*) gba->video.renderer->vram)[address & 0x00017FFF]; + } break; case REGION_OAM: GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Load8: 0x%08X", address);@@ -435,7 +558,7 @@ break;
case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; - if (memory->savedata.type == SAVEDATA_NONE) { + if (memory->savedata.type == SAVEDATA_AUTODETECT) { GBALog(gba, GBA_LOG_INFO, "Detected SRAM savegame"); GBASavedataInitSRAM(&memory->savedata); }@@ -443,17 +566,23 @@ if (memory->savedata.type == SAVEDATA_SRAM) {
value = memory->savedata.data[address & (SIZE_CART_SRAM - 1)]; } else if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) { value = GBASavedataReadFlash(&memory->savedata, address); + } else if (memory->hw.devices & HW_TILT) { + value = GBAHardwareTiltRead(&memory->hw, address & OFFSET_MASK); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Reading from non-existent SRAM: 0x%08X", address); - value = 7; + value = 0xFF; } break; default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); - if (cpu->cycles >= cpu->nextEvent) { - value = gba->bus; + if (gba->performingDMA) { + value = ((uint8_t*) &gba->bus)[address & 3]; } else { - value = cpu->prefetch; + uint32_t prefetch = cpu->prefetch[1]; + if (cpu->executionMode == MODE_THUMB) { + prefetch |= prefetch << 16; + } + value = ((uint8_t*) &prefetch)[address & 3]; } break; }@@ -481,9 +610,9 @@ ++wait; \
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); #define STORE_VRAM \ - if ((address & OFFSET_MASK) < SIZE_VRAM) { \ + if ((address & 0x0001FFFF) < SIZE_VRAM) { \ STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram); \ - } else if ((address & OFFSET_MASK) < 0x00020000) { \ + } else { \ STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram); \ } \ ++wait;@@ -570,9 +699,9 @@ STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette);
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); break; case REGION_VRAM: - if ((address & OFFSET_MASK) < SIZE_VRAM) { + if ((address & 0x0001FFFF) < SIZE_VRAM) { STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram); - } else if ((address & OFFSET_MASK) < 0x00020000) { + } else { STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram); } break;@@ -581,15 +710,15 @@ STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw);
gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1); break; case REGION_CART0: - if (IS_GPIO_REGISTER(address & 0xFFFFFF)) { + if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFF)) { uint32_t reg = address & 0xFFFFFF; - GBAGPIOWrite(&memory->gpio, reg, value); + GBAHardwareGPIOWrite(&memory->hw, reg, value); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); } break; case REGION_CART2_EX: - if (memory->savedata.type == SAVEDATA_NONE) { + if (memory->savedata.type == SAVEDATA_AUTODETECT) { GBALog(gba, GBA_LOG_INFO, "Detected EEPROM savegame"); GBASavedataInitEEPROM(&memory->savedata); }@@ -645,7 +774,7 @@ GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Store8: 0x%08X", address);
break; case REGION_CART_SRAM: case REGION_CART_SRAM_MIRROR: - if (memory->savedata.type == SAVEDATA_NONE) { + if (memory->savedata.type == SAVEDATA_AUTODETECT) { if (address == SAVEDATA_FLASH_BASE) { GBALog(gba, GBA_LOG_INFO, "Detected Flash savegame"); GBASavedataInitFlash(&memory->savedata);@@ -658,6 +787,8 @@ if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) {
GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == SAVEDATA_SRAM) { memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value; + } else if (memory->hw.devices & HW_TILT) { + GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); }@@ -673,6 +804,142 @@ *cycleCounter += 1 + wait;
} } +void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old) { + struct GBA* gba = (struct GBA*) cpu->master; + struct GBAMemory* memory = &gba->memory; + int32_t oldValue = -1; + + switch (address >> BASE_OFFSET) { + case REGION_WORKING_RAM: + LOAD_32(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram); + STORE_32(value, address & (SIZE_WORKING_RAM - 1), memory->wram); + break; + case REGION_WORKING_IRAM: + LOAD_32(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + STORE_32(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + break; + case REGION_IO: + GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch32: 0x%08X", address); + break; + case REGION_PALETTE_RAM: + LOAD_32(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); + STORE_32(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); + gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); + gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 1)) + 2, value >> 16); + break; + case REGION_VRAM: + if ((address & 0x0001FFFF) < SIZE_VRAM) { + LOAD_32(oldValue, address & 0x0001FFFF, gba->video.renderer->vram); + STORE_32(value, address & 0x0001FFFF, gba->video.renderer->vram); + } else { + LOAD_32(oldValue, address & 0x00017FFF, gba->video.renderer->vram); + STORE_32(value, address & 0x00017FFF, gba->video.renderer->vram); + } + break; + case REGION_OAM: + LOAD_32(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw); + STORE_32(value, address & (SIZE_OAM - 1), gba->video.oam.raw); + gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1); + gba->video.renderer->writeOAM(gba->video.renderer, ((address & (SIZE_OAM - 1)) + 2) >> 1); + break; + case REGION_CART0: + case REGION_CART0_EX: + case REGION_CART1: + case REGION_CART1_EX: + case REGION_CART2: + case REGION_CART2_EX: + if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) { + LOAD_32(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); + STORE_32(value, address & (SIZE_CART0 - 1), gba->memory.rom); + } else { + GBALog(gba, GBA_LOG_WARN, "Bad memory Patch32: 0x%08X", address); + } + break; + case REGION_CART_SRAM: + case REGION_CART_SRAM_MIRROR: + if (memory->savedata.type == SAVEDATA_SRAM) { + LOAD_32(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data); + STORE_32(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data); + } else { + GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); + } + break; + default: + GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address); + break; + } + if (old) { + *old = oldValue; + } +} + +void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old) { + struct GBA* gba = (struct GBA*) cpu->master; + struct GBAMemory* memory = &gba->memory; + int16_t oldValue = -1; + + switch (address >> BASE_OFFSET) { + case REGION_WORKING_RAM: + LOAD_16(oldValue, address & (SIZE_WORKING_RAM - 1), memory->wram); + STORE_16(value, address & (SIZE_WORKING_RAM - 1), memory->wram); + break; + case REGION_WORKING_IRAM: + LOAD_16(oldValue, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + STORE_16(value, address & (SIZE_WORKING_IRAM - 1), memory->iwram); + break; + case REGION_IO: + GBALog(gba, GBA_LOG_STUB, "Unimplemented memory Patch16: 0x%08X", address); + break; + case REGION_PALETTE_RAM: + LOAD_16(oldValue, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); + STORE_16(value, address & (SIZE_PALETTE_RAM - 1), gba->video.palette); + gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 1), value); + break; + case REGION_VRAM: + if ((address & 0x0001FFFF) < SIZE_VRAM) { + LOAD_16(oldValue, address & 0x0001FFFF, gba->video.renderer->vram); + STORE_16(value, address & 0x0001FFFF, gba->video.renderer->vram); + } else { + LOAD_16(oldValue, address & 0x00017FFF, gba->video.renderer->vram); + STORE_16(value, address & 0x00017FFF, gba->video.renderer->vram); + } + break; + case REGION_OAM: + LOAD_16(oldValue, address & (SIZE_OAM - 1), gba->video.oam.raw); + STORE_16(value, address & (SIZE_OAM - 1), gba->video.oam.raw); + gba->video.renderer->writeOAM(gba->video.renderer, (address & (SIZE_OAM - 1)) >> 1); + break; + case REGION_CART0: + case REGION_CART0_EX: + case REGION_CART1: + case REGION_CART1_EX: + case REGION_CART2: + case REGION_CART2_EX: + if ((address & (SIZE_CART0 - 1)) < gba->memory.romSize) { + LOAD_16(oldValue, address & (SIZE_CART0 - 1), gba->memory.rom); + STORE_16(value, address & (SIZE_CART0 - 1), gba->memory.rom); + } else { + GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address); + } + break; + case REGION_CART_SRAM: + case REGION_CART_SRAM_MIRROR: + if (memory->savedata.type == SAVEDATA_SRAM) { + LOAD_16(oldValue, address & (SIZE_CART_SRAM - 1), memory->savedata.data); + STORE_16(value, address & (SIZE_CART_SRAM - 1), memory->savedata.data); + } else { + GBALog(gba, GBA_LOG_GAME_ERROR, "Writing to non-existent SRAM: 0x%08X", address); + } + break; + default: + GBALog(gba, GBA_LOG_WARN, "Bad memory Patch16: 0x%08X", address); + break; + } + if (old) { + *old = oldValue; + } +} + #define LDM_LOOP(LDM) \ for (i = 0; i < 16; i += 4) { \ if (UNLIKELY(mask & (1 << i))) { \@@ -967,12 +1234,12 @@ }
void GBAMemoryWriteDMASAD(struct GBA* gba, int dma, uint32_t address) { struct GBAMemory* memory = &gba->memory; - memory->dma[dma].source = address & 0xFFFFFFFE; + memory->dma[dma].source = address & 0x0FFFFFFE; } void GBAMemoryWriteDMADAD(struct GBA* gba, int dma, uint32_t address) { struct GBAMemory* memory = &gba->memory; - memory->dma[dma].dest = address & 0xFFFFFFFE; + memory->dma[dma].dest = address & 0x0FFFFFFE; } void GBAMemoryWriteDMACNT_LO(struct GBA* gba, int dma, uint16_t count) {@@ -1106,7 +1373,7 @@ uint32_t source = info->nextSource;
uint32_t dest = info->nextDest; uint32_t sourceRegion = source >> BASE_OFFSET; uint32_t destRegion = dest >> BASE_OFFSET; - int32_t cycles = 0; + int32_t cycles = 2; if (source == info->source) { // TODO: support 4 cycles for ROM access@@ -1126,6 +1393,7 @@ cycles += memory->waitstatesSeq16[sourceRegion] + memory->waitstatesSeq16[destRegion];
} } + gba->performingDMA = true; int32_t word; if (width == 4) { word = cpu->memory.load32(cpu, source, 0);@@ -1143,7 +1411,7 @@ source += sourceOffset;
dest += destOffset; --wordsRemaining; } else if (destRegion == REGION_CART2_EX) { - if (memory->savedata.type == SAVEDATA_NONE) { + if (memory->savedata.type == SAVEDATA_AUTODETECT) { GBALog(gba, GBA_LOG_INFO, "Detected EEPROM savegame"); GBASavedataInitEEPROM(&memory->savedata); }@@ -1162,9 +1430,10 @@ dest += destOffset;
--wordsRemaining; } } + gba->performingDMA = false; if (!wordsRemaining) { - if (!GBADMARegisterIsRepeat(info->reg)) { + if (!GBADMARegisterIsRepeat(info->reg) || GBADMARegisterGetTiming(info->reg) == DMA_TIMING_NOW) { info->reg = GBADMARegisterClearEnable(info->reg); info->nextEvent = INT_MAX;@@ -1192,12 +1461,12 @@ }
cpu->cycles += cycles; } -void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state) { +void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state) { memcpy(state->wram, memory->wram, SIZE_WORKING_RAM); memcpy(state->iwram, memory->iwram, SIZE_WORKING_IRAM); } -void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state) { +void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state) { memcpy(memory->wram, state->wram, SIZE_WORKING_RAM); memcpy(memory->iwram, state->iwram, SIZE_WORKING_IRAM); }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -11,8 +11,8 @@
#include "arm.h" #include "macros.h" -#include "gba-gpio.h" -#include "gba-savedata.h" +#include "gba/hardware.h" +#include "gba/savedata.h" enum GBAMemoryRegion { REGION_BIOS = 0x0,@@ -116,7 +116,7 @@ uint32_t* iwram;
uint32_t* rom; uint16_t io[SIZE_IO >> 1]; - struct GBACartridgeGPIO gpio; + struct GBACartridgeHardware hw; struct GBASavedata savedata; size_t romSize; uint16_t romID;@@ -144,15 +144,16 @@ void GBAMemoryDeinit(struct GBA* gba);
void GBAMemoryReset(struct GBA* gba); -int32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter); -int16_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter); -uint16_t GBALoadU16(struct ARMCore* cpu, uint32_t address, int* cycleCounter); -int8_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter); -uint8_t GBALoadU8(struct ARMCore* cpu, uint32_t address, int* cycleCounter); +uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter); +uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter); +uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter); void GBAStore32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycleCounter); void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycleCounter); void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCounter); + +void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* old); +void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* old); uint32_t GBALoadMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter); uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum LSMDirection direction, int* cycleCounter);@@ -171,7 +172,7 @@ void GBAMemoryUpdateDMAs(struct GBA* gba, int32_t cycles);
int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles); struct GBASerializedState; -void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state); -void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state); +void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state); +void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state); #endif
@@ -1,571 +0,0 @@
-/* Copyright (c) 2013-2014 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 "gba-rr.h" - -#include "gba.h" -#include "gba-serialize.h" -#include "util/vfs.h" - -#define BINARY_EXT ".dat" -#define BINARY_MAGIC "GBAb" -#define METADATA_FILENAME "metadata" BINARY_EXT - -enum { - INVALID_INPUT = 0x8000 -}; - -static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf); -static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf); -static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf); -static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag); -static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag); -static bool _emitEnd(struct GBARRContext* rr, struct VFile* vf); - -static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf); - -static bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive); -static void _streamEndReached(struct GBARRContext* rr); - -static struct VFile* _openSavedata(struct GBARRContext* rr, int flags); -static struct VFile* _openSavestate(struct GBARRContext* rr, int flags); - -void GBARRContextCreate(struct GBA* gba) { - if (gba->rr) { - return; - } - - gba->rr = calloc(1, sizeof(*gba->rr)); -} - -void GBARRContextDestroy(struct GBA* gba) { - if (!gba->rr) { - return; - } - - if (GBARRIsPlaying(gba->rr)) { - GBARRStopPlaying(gba->rr); - } - if (GBARRIsRecording(gba->rr)) { - GBARRStopRecording(gba->rr); - } - if (gba->rr->metadataFile) { - gba->rr->metadataFile->close(gba->rr->metadataFile); - } - if (gba->rr->savedata) { - gba->rr->savedata->close(gba->rr->savedata); - } - - free(gba->rr); - gba->rr = 0; -} - -void GBARRSaveState(struct GBA* gba) { - if (!gba || !gba->rr) { - return; - } - - if (gba->rr->initFrom & INIT_FROM_SAVEGAME) { - if (gba->rr->savedata) { - gba->rr->savedata->close(gba->rr->savedata); - } - gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY); - GBASavedataClone(&gba->memory.savedata, gba->rr->savedata); - gba->rr->savedata->close(gba->rr->savedata); - gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY); - GBASavedataMask(&gba->memory.savedata, gba->rr->savedata); - } else { - GBASavedataMask(&gba->memory.savedata, 0); - } - - if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { - struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR); - GBASaveStateNamed(gba, vf, false); - vf->close(vf); - } else { - ARMReset(gba->cpu); - } -} - -void GBARRLoadState(struct GBA* gba) { - if (!gba || !gba->rr) { - return; - } - - if (gba->rr->initFrom & INIT_FROM_SAVEGAME) { - if (gba->rr->savedata) { - gba->rr->savedata->close(gba->rr->savedata); - } - gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY); - GBASavedataMask(&gba->memory.savedata, gba->rr->savedata); - } else { - GBASavedataMask(&gba->memory.savedata, 0); - } - - if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { - struct VFile* vf = _openSavestate(gba->rr, O_RDONLY); - GBALoadStateNamed(gba, vf); - vf->close(vf); - } else { - ARMReset(gba->cpu); - } -} - -bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) { - if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) { - return false; - } - - if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) { - return false; - } - - rr->streamDir = stream; - rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR); - rr->currentInput = INVALID_INPUT; - if (!_parseMetadata(rr, rr->metadataFile)) { - rr->metadataFile->close(rr->metadataFile); - rr->metadataFile = 0; - rr->maxStreamId = 0; - } - rr->streamId = 1; - rr->movieStream = 0; - return true; -} - -bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) { - if (!rr) { - return false; - } - - if (rr->metadataFile) { - rr->metadataFile->truncate(rr->metadataFile, 0); - } else { - rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR); - } - _emitMagic(rr, rr->metadataFile); - - rr->initFrom = initFrom; - rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - _emitTag(rr, rr->metadataFile, TAG_INIT | initFrom); - - rr->streamId = 0; - rr->maxStreamId = 0; - _emitTag(rr, rr->metadataFile, TAG_MAX_STREAM); - rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); - - rr->rrCount = 0; - _emitTag(rr, rr->metadataFile, TAG_RR_COUNT); - rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount)); - return true; -} - -bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) { - if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) { - return false; - } - rr->movieStream = 0; - rr->streamId = streamId; - rr->currentInput = INVALID_INPUT; - char buffer[14]; - snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId); - if (GBARRIsRecording(rr)) { - int flags = O_CREAT | O_RDWR; - if (streamId > rr->maxStreamId) { - flags |= O_TRUNC; - } - rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags); - } else if (GBARRIsPlaying(rr)) { - rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY); - rr->peekedTag = TAG_INVALID; - if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) { - GBARRStopPlaying(rr); - } - } - GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId); - rr->frames = 0; - rr->lagFrames = 0; - return true; -} - -bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) { - uint32_t newStreamId = rr->maxStreamId + 1; - uint32_t oldStreamId = rr->streamId; - if (GBARRIsRecording(rr) && rr->movieStream) { - if (!_markStreamNext(rr, newStreamId, recursive)) { - return false; - } - } - if (!GBARRLoadStream(rr, newStreamId)) { - return false; - } - GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId); - _emitMagic(rr, rr->movieStream); - rr->maxStreamId = newStreamId; - _emitTag(rr, rr->movieStream, TAG_PREVIOUSLY); - rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId)); - _emitTag(rr, rr->movieStream, TAG_BEGIN); - - rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET); - rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); - rr->previously = oldStreamId; - return true; -} - -bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) { - if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) { - return false; - } - - rr->isPlaying = true; - if (!GBARRLoadStream(rr, 1)) { - rr->isPlaying = false; - return false; - } - rr->autorecord = autorecord; - return true; -} - -void GBARRStopPlaying(struct GBARRContext* rr) { - if (!GBARRIsPlaying(rr)) { - return; - } - rr->isPlaying = false; - if (rr->movieStream) { - rr->movieStream->close(rr->movieStream); - rr->movieStream = 0; - } -} - -bool GBARRStartRecording(struct GBARRContext* rr) { - if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) { - return false; - } - - if (!rr->maxStreamIdOffset) { - _emitTag(rr, rr->metadataFile, TAG_MAX_STREAM); - rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); - } - - rr->isRecording = true; - return GBARRIncrementStream(rr, false); -} - -void GBARRStopRecording(struct GBARRContext* rr) { - if (!GBARRIsRecording(rr)) { - return; - } - rr->isRecording = false; - if (rr->movieStream) { - _emitEnd(rr, rr->movieStream); - rr->movieStream->close(rr->movieStream); - rr->movieStream = 0; - } -} - -bool GBARRIsPlaying(struct GBARRContext* rr) { - return rr && rr->isPlaying; -} - -bool GBARRIsRecording(struct GBARRContext* rr) { - return rr && rr->isRecording; -} - -void GBARRNextFrame(struct GBARRContext* rr) { - if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) { - return; - } - - if (GBARRIsPlaying(rr)) { - while (rr->peekedTag == TAG_INPUT) { - _readTag(rr, rr->movieStream); - GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!"); - } - if (rr->peekedTag == TAG_LAG) { - GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream"); - if (rr->inputThisFrame) { - GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie"); - } - } - } - - ++rr->frames; - GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames); - if (!rr->inputThisFrame) { - ++rr->lagFrames; - GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames); - } - - if (GBARRIsRecording(rr)) { - if (!rr->inputThisFrame) { - _emitTag(rr, rr->movieStream, TAG_LAG); - } - _emitTag(rr, rr->movieStream, TAG_FRAME); - rr->inputThisFrame = false; - } else { - if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) { - _streamEndReached(rr); - } - } -} - -void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) { - if (!GBARRIsRecording(rr)) { - return; - } - - if (keys != rr->currentInput) { - _emitTag(rr, rr->movieStream, TAG_INPUT); - rr->movieStream->write(rr->movieStream, &keys, sizeof(keys)); - rr->currentInput = keys; - } - GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput); - rr->inputThisFrame = true; -} - -uint16_t GBARRQueryInput(struct GBARRContext* rr) { - if (!GBARRIsPlaying(rr)) { - return 0; - } - - if (rr->peekedTag == TAG_INPUT) { - _readTag(rr, rr->movieStream); - } - rr->inputThisFrame = true; - if (rr->currentInput == INVALID_INPUT) { - GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input"); - } - GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput); - return rr->currentInput; -} - -bool GBARRFinishSegment(struct GBARRContext* rr) { - if (rr->movieStream) { - if (!_emitEnd(rr, rr->movieStream)) { - return false; - } - } - return GBARRIncrementStream(rr, false); -} - -bool GBARRSkipSegment(struct GBARRContext* rr) { - rr->nextTime = 0; - while (_readTag(rr, rr->movieStream) != TAG_EOF); - if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) { - _streamEndReached(rr); - return false; - } - return true; -} - -bool GBARRMarkRerecord(struct GBARRContext* rr) { - ++rr->rrCount; - rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET); - rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount)); - return true; -} - -bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) { - UNUSED(rr); - return vf->write(vf, BINARY_MAGIC, 4) == 4; -} - -bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) { - UNUSED(rr); - char buffer[4]; - if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) { - return false; - } - if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) { - return false; - } - return true; -} - -enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) { - if (!rr || !vf) { - return TAG_EOF; - } - - enum GBARRTag tag = rr->peekedTag; - switch (tag) { - case TAG_INPUT: - vf->read(vf, &rr->currentInput, sizeof(uint16_t)); - break; - case TAG_PREVIOUSLY: - vf->read(vf, &rr->previously, sizeof(rr->previously)); - break; - case TAG_NEXT_TIME: - vf->read(vf, &rr->nextTime, sizeof(rr->nextTime)); - break; - case TAG_MAX_STREAM: - vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId)); - break; - case TAG_FRAME_COUNT: - vf->read(vf, &rr->frames, sizeof(rr->frames)); - break; - case TAG_LAG_COUNT: - vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames)); - break; - case TAG_RR_COUNT: - vf->read(vf, &rr->rrCount, sizeof(rr->rrCount)); - break; - - case TAG_INIT_EX_NIHILO: - rr->initFrom = INIT_EX_NIHILO; - break; - case TAG_INIT_FROM_SAVEGAME: - rr->initFrom = INIT_FROM_SAVEGAME; - break; - case TAG_INIT_FROM_SAVESTATE: - rr->initFrom = INIT_FROM_SAVESTATE; - case TAG_INIT_FROM_BOTH: - rr->initFrom = INIT_FROM_BOTH; - break; - - // To be spec'd - case TAG_AUTHOR: - case TAG_COMMENT: - break; - - // Empty markers - case TAG_FRAME: - case TAG_LAG: - case TAG_BEGIN: - case TAG_END: - case TAG_INVALID: - case TAG_EOF: - break; - } - - uint8_t tagBuffer; - if (vf->read(vf, &tagBuffer, 1) != 1) { - rr->peekedTag = TAG_EOF; - } else { - rr->peekedTag = tagBuffer; - } - - if (rr->peekedTag == TAG_END) { - GBARRSkipSegment(rr); - } - return tag; -} - -bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) { - enum GBARRTag readTag; - while ((readTag = _readTag(rr, vf)) != tag) { - if (readTag == TAG_EOF) { - return false; - } - } - return true; -} - -bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) { - UNUSED(rr); - return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag); -} - -bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) { - if (!_verifyMagic(rr, vf)) { - return false; - } - while (_readTag(rr, vf) != TAG_EOF) { - switch (rr->peekedTag) { - case TAG_MAX_STREAM: - rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR); - break; - case TAG_INIT_EX_NIHILO: - case TAG_INIT_FROM_SAVEGAME: - case TAG_INIT_FROM_SAVESTATE: - case TAG_INIT_FROM_BOTH: - rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR); - break; - case TAG_RR_COUNT: - rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR); - break; - default: - break; - } - } - return true; -} - -bool _emitEnd(struct GBARRContext* rr, struct VFile* vf) { - // TODO: Error check - _emitTag(rr, vf, TAG_END); - _emitTag(rr, vf, TAG_FRAME_COUNT); - vf->write(vf, &rr->frames, sizeof(rr->frames)); - _emitTag(rr, vf, TAG_LAG_COUNT); - vf->write(vf, &rr->lagFrames, sizeof(rr->lagFrames)); - _emitTag(rr, vf, TAG_NEXT_TIME); - - uint32_t newStreamId = 0; - vf->write(vf, &newStreamId, sizeof(newStreamId)); - return true; -} - -bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive) { - if (rr->movieStream->seek(rr->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) { - return false; - } - - uint8_t tagBuffer; - if (rr->movieStream->read(rr->movieStream, &tagBuffer, 1) != 1) { - return false; - } - if (tagBuffer != TAG_NEXT_TIME) { - return false; - } - if (rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) { - return false; - } - if (recursive) { - if (rr->movieStream->seek(rr->movieStream, 0, SEEK_SET) < 0) { - return false; - } - if (!_verifyMagic(rr, rr->movieStream)) { - return false; - } - _readTag(rr, rr->movieStream); - if (_readTag(rr, rr->movieStream) != TAG_PREVIOUSLY) { - return false; - } - if (rr->previously == 0) { - return true; - } - uint32_t currentStreamId = rr->streamId; - if (!GBARRLoadStream(rr, rr->previously)) { - return false; - } - return _markStreamNext(rr, currentStreamId, rr->previously); - } - return true; -} - -void _streamEndReached(struct GBARRContext* rr) { - if (!GBARRIsPlaying(rr)) { - return; - } - - uint32_t endStreamId = rr->streamId; - GBARRStopPlaying(rr); - if (rr->autorecord) { - rr->isRecording = true; - GBARRLoadStream(rr, endStreamId); - GBARRIncrementStream(rr, false); - } -} - -struct VFile* _openSavedata(struct GBARRContext* rr, int flags) { - return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags); -} - -struct VFile* _openSavestate(struct GBARRContext* rr, int flags) { - return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags); -}
@@ -1,113 +0,0 @@
-/* Copyright (c) 2013-2014 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 GBA_RR_H -#define GBA_RR_H - -#include "util/common.h" - -struct GBA; -struct VDir; -struct VFile; - -enum GBARRInitFrom { - INIT_EX_NIHILO = 0, - INIT_FROM_SAVEGAME = 1, - INIT_FROM_SAVESTATE = 2, - INIT_FROM_BOTH = 3, -}; - -enum GBARRTag { - // Playback tags - TAG_INVALID = 0x00, - TAG_INPUT = 0x01, - TAG_FRAME = 0x02, - TAG_LAG = 0x03, - - // Stream chunking tags - TAG_BEGIN = 0x10, - TAG_END = 0x11, - TAG_PREVIOUSLY = 0x12, - TAG_NEXT_TIME = 0x13, - TAG_MAX_STREAM = 0x14, - - // Recording information tags - TAG_FRAME_COUNT = 0x20, - TAG_LAG_COUNT = 0x21, - TAG_RR_COUNT = 0x22, - TAG_INIT = 0x24, - TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO, - TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME, - TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE, - TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH, - - // User metadata tags - TAG_AUTHOR = 0x30, - TAG_COMMENT = 0x31, - - TAG_EOF = INT_MAX -}; - -struct GBARRContext { - // Playback state - bool isPlaying; - bool autorecord; - - // Recording state - bool isRecording; - bool inputThisFrame; - - // Metadata - uint32_t frames; - uint32_t lagFrames; - uint32_t streamId; - - uint32_t maxStreamId; - off_t maxStreamIdOffset; - - enum GBARRInitFrom initFrom; - off_t initFromOffset; - - uint32_t rrCount; - off_t rrCountOffset; - - struct VFile* savedata; - - // Streaming state - struct VDir* streamDir; - struct VFile* metadataFile; - struct VFile* movieStream; - uint16_t currentInput; - enum GBARRTag peekedTag; - uint32_t nextTime; - uint32_t previously; -}; - -void GBARRContextCreate(struct GBA*); -void GBARRContextDestroy(struct GBA*); -void GBARRSaveState(struct GBA*); -void GBARRLoadState(struct GBA*); - -bool GBARRInitStream(struct GBARRContext*, struct VDir*); -bool GBARRReinitStream(struct GBARRContext*, enum GBARRInitFrom); -bool GBARRLoadStream(struct GBARRContext*, uint32_t streamId); -bool GBARRIncrementStream(struct GBARRContext*, bool recursive); -bool GBARRFinishSegment(struct GBARRContext*); -bool GBARRSkipSegment(struct GBARRContext*); -bool GBARRMarkRerecord(struct GBARRContext*); - -bool GBARRStartPlaying(struct GBARRContext*, bool autorecord); -void GBARRStopPlaying(struct GBARRContext*); -bool GBARRStartRecording(struct GBARRContext*); -void GBARRStopRecording(struct GBARRContext*); - -bool GBARRIsPlaying(struct GBARRContext*); -bool GBARRIsRecording(struct GBARRContext*); - -void GBARRNextFrame(struct GBARRContext*); -void GBARRLogInput(struct GBARRContext*, uint16_t input); -uint16_t GBARRQueryInput(struct GBARRContext*); - -#endif
@@ -1,11 +1,11 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-savedata.h" +#include "savedata.h" -#include "gba.h" +#include "gba/gba.h" #include "util/memory.h" #include "util/vfs.h"@@ -18,7 +18,7 @@ static void _flashErase(struct GBASavedata* savedata);
static void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart); void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) { - savedata->type = SAVEDATA_NONE; + savedata->type = SAVEDATA_AUTODETECT; savedata->data = 0; savedata->command = EEPROM_COMMAND_NULL; savedata->flashState = FLASH_STATE_RAW;@@ -42,7 +42,8 @@ break;
case SAVEDATA_EEPROM: savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_EEPROM); break; - case SAVEDATA_NONE: + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: break; } savedata->vf = 0;@@ -60,12 +61,13 @@ break;
case SAVEDATA_EEPROM: mappedMemoryFree(savedata->data, SIZE_CART_EEPROM); break; - case SAVEDATA_NONE: + case SAVEDATA_FORCE_NONE: + case SAVEDATA_AUTODETECT: break; } } savedata->data = 0; - savedata->type = SAVEDATA_NONE; + savedata->type = SAVEDATA_AUTODETECT; } void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf) {@@ -94,7 +96,8 @@ case SAVEDATA_FLASH1M:
return out->write(out, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M; case SAVEDATA_EEPROM: return out->write(out, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM; - case SAVEDATA_NONE: + case SAVEDATA_AUTODETECT: + case SAVEDATA_FORCE_NONE: return true; } } else if (savedata->vf) {@@ -109,34 +112,62 @@ }
return true; } +void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type) { + if (savedata->type != SAVEDATA_AUTODETECT) { + struct VFile* vf = savedata->vf; + GBASavedataDeinit(savedata); + GBASavedataInit(savedata, vf); + } + switch (type) { + case SAVEDATA_FLASH512: + case SAVEDATA_FLASH1M: + savedata->type = type; + GBASavedataInitFlash(savedata); + break; + case SAVEDATA_EEPROM: + GBASavedataInitEEPROM(savedata); + break; + case SAVEDATA_SRAM: + GBASavedataInitSRAM(savedata); + break; + case SAVEDATA_FORCE_NONE: + savedata->type = SAVEDATA_FORCE_NONE; + break; + case SAVEDATA_AUTODETECT: + break; + } +} + void GBASavedataInitFlash(struct GBASavedata* savedata) { - if (savedata->type == SAVEDATA_NONE) { + if (savedata->type == SAVEDATA_AUTODETECT) { savedata->type = SAVEDATA_FLASH512; } if (savedata->type != SAVEDATA_FLASH512 && savedata->type != SAVEDATA_FLASH1M) { GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata"); return; } + size_t flashSize = SIZE_CART_FLASH512; off_t end; if (!savedata->vf) { end = 0; savedata->data = anonymousMemoryMap(SIZE_CART_FLASH1M); } else { - end = savedata->vf->seek(savedata->vf, 0, SEEK_END); + end = savedata->vf->size(savedata->vf); if (end < SIZE_CART_FLASH512) { savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); + flashSize = SIZE_CART_FLASH1M; } savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode); } savedata->currentBank = savedata->data; if (end < SIZE_CART_FLASH512) { - memset(&savedata->data[end], 0xFF, SIZE_CART_FLASH512 - end); + memset(&savedata->data[end], 0xFF, flashSize - end); } } void GBASavedataInitEEPROM(struct GBASavedata* savedata) { - if (savedata->type == SAVEDATA_NONE) { + if (savedata->type == SAVEDATA_AUTODETECT) { savedata->type = SAVEDATA_EEPROM; } else { GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");@@ -147,7 +178,7 @@ if (!savedata->vf) {
end = 0; savedata->data = anonymousMemoryMap(SIZE_CART_EEPROM); } else { - end = savedata->vf->seek(savedata->vf, 0, SEEK_END); + end = savedata->vf->size(savedata->vf); if (end < SIZE_CART_EEPROM) { savedata->vf->truncate(savedata->vf, SIZE_CART_EEPROM); }@@ -159,7 +190,7 @@ }
} void GBASavedataInitSRAM(struct GBASavedata* savedata) { - if (savedata->type == SAVEDATA_NONE) { + if (savedata->type == SAVEDATA_AUTODETECT) { savedata->type = SAVEDATA_SRAM; } else { GBALog(0, GBA_LOG_WARN, "Can't re-initialize savedata");@@ -170,7 +201,7 @@ if (!savedata->vf) {
end = 0; savedata->data = anonymousMemoryMap(SIZE_CART_SRAM); } else { - end = savedata->vf->seek(savedata->vf, 0, SEEK_END); + end = savedata->vf->size(savedata->vf); if (end < SIZE_CART_SRAM) { savedata->vf->truncate(savedata->vf, SIZE_CART_SRAM); }@@ -348,27 +379,31 @@ return 0;
} void _flashSwitchBank(struct GBASavedata* savedata, int bank) { + GBALog(0, GBA_LOG_DEBUG, "Performing flash bank switch to bank %i", bank); savedata->currentBank = &savedata->data[bank << 16]; - if (bank > 0) { + if (bank > 0 && savedata->type == SAVEDATA_FLASH512) { savedata->type = SAVEDATA_FLASH1M; if (savedata->vf) { savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); + memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512); } } } void _flashErase(struct GBASavedata* savedata) { - size_t size = 0x10000; + GBALog(0, GBA_LOG_DEBUG, "Performing flash chip erase"); + size_t size = SIZE_CART_FLASH512; if (savedata->type == SAVEDATA_FLASH1M) { - size = 0x20000; + size = SIZE_CART_FLASH1M; } memset(savedata->data, 0xFF, size); } void _flashEraseSector(struct GBASavedata* savedata, uint16_t sectorStart) { + GBALog(0, GBA_LOG_DEBUG, "Performing flash sector erase at 0x%04x", sectorStart); size_t size = 0x1000; if (savedata->type == SAVEDATA_FLASH1M) { - GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at %#04x", sectorStart); + GBALog(0, GBA_LOG_DEBUG, "Performing unknown sector-size erase at 0x%04x", sectorStart); } memset(&savedata->currentBank[sectorStart & ~(size - 1)], 0xFF, size); }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -11,7 +11,8 @@
struct VFile; enum SavedataType { - SAVEDATA_NONE = 0, + SAVEDATA_AUTODETECT = -1, + SAVEDATA_FORCE_NONE = 0, SAVEDATA_SRAM, SAVEDATA_FLASH512, SAVEDATA_FLASH1M,@@ -83,6 +84,7 @@
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataUnmask(struct GBASavedata* savedata); bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out); +void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type); void GBASavedataInitFlash(struct GBASavedata* savedata); void GBASavedataInitEEPROM(struct GBASavedata* savedata);
@@ -1,20 +0,0 @@
-/* Copyright (c) 2013-2014 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 GBA_SENSORS_H -#define GBA_SENSORS_H - -#include "util/common.h" - -struct GBARotationSource { - void (*sample)(struct GBARotationSource*); - - int32_t (*readTiltX)(struct GBARotationSource*); - int32_t (*readTiltY)(struct GBARotationSource*); - - int32_t (*readGyroZ)(struct GBARotationSource*); -}; - -#endif
@@ -1,15 +1,15 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-serialize.h" +#include "serialize.h" -#include "gba-audio.h" -#include "gba-io.h" -#include "gba-rr.h" -#include "gba-thread.h" -#include "gba-video.h" +#include "gba/audio.h" +#include "gba/io.h" +#include "gba/supervisor/rr.h" +#include "gba/supervisor/thread.h" +#include "gba/video.h" #include "util/memory.h" #include "util/vfs.h"@@ -40,20 +40,22 @@ state->cpu.nextEvent = gba->cpu->nextEvent;
memcpy(state->cpu.bankedRegisters, gba->cpu->bankedRegisters, 6 * 7 * sizeof(int32_t)); memcpy(state->cpu.bankedSPSRs, gba->cpu->bankedSPSRs, 6 * sizeof(int32_t)); + state->biosPrefetch = gba->memory.biosPrefetch; + state->cpuPrefetch[0] = gba->cpu->prefetch[0]; + state->cpuPrefetch[1] = gba->cpu->prefetch[1]; + GBAMemorySerialize(&gba->memory, state); GBAIOSerialize(gba, state); GBAVideoSerialize(&gba->video, state); GBAAudioSerialize(&gba->audio, state); - if (GBARRIsRecording(gba->rr)) { - state->associatedStreamId = gba->rr->streamId; - GBARRFinishSegment(gba->rr); - } else { - state->associatedStreamId = 0; + state->associatedStreamId = 0; + if (gba->rr) { + gba->rr->stateSaved(gba->rr, state); } } -void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) { +void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { if (state->versionMagic != GBA_SAVESTATE_MAGIC) { GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate"); return;@@ -80,12 +82,29 @@ memcpy(gba->cpu->bankedRegisters, state->cpu.bankedRegisters, 6 * 7 * sizeof(int32_t));
memcpy(gba->cpu->bankedSPSRs, state->cpu.bankedSPSRs, 6 * sizeof(int32_t)); gba->cpu->privilegeMode = gba->cpu->cpsr.priv; gba->cpu->memory.setActiveRegion(gba->cpu, gba->cpu->gprs[ARM_PC]); + if (state->biosPrefetch) { + gba->memory.biosPrefetch = state->biosPrefetch; + } if (gba->cpu->cpsr.t) { gba->cpu->executionMode = MODE_THUMB; - LOAD_16(gba->cpu->prefetch, (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); + if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) { + gba->cpu->prefetch[0] = state->cpuPrefetch[0] & 0xFFFF; + gba->cpu->prefetch[1] = state->cpuPrefetch[1] & 0xFFFF; + } else { + // Maintain backwards compat + LOAD_16(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); + LOAD_16(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); + } } else { gba->cpu->executionMode = MODE_ARM; - LOAD_32(gba->cpu->prefetch, (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); + if (state->cpuPrefetch[0] && state->cpuPrefetch[1]) { + gba->cpu->prefetch[0] = state->cpuPrefetch[0]; + gba->cpu->prefetch[1] = state->cpuPrefetch[1]; + } else { + // Maintain backwards compat + LOAD_32(gba->cpu->prefetch[0], (gba->cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); + LOAD_32(gba->cpu->prefetch[1], (gba->cpu->gprs[ARM_PC]) & gba->cpu->memory.activeMask, gba->cpu->memory.activeRegion); + } } GBAMemoryDeserialize(&gba->memory, state);@@ -93,17 +112,8 @@ GBAIODeserialize(gba, state);
GBAVideoDeserialize(&gba->video, state); GBAAudioDeserialize(&gba->audio, state); - if (GBARRIsRecording(gba->rr)) { - if (state->associatedStreamId != gba->rr->streamId) { - GBARRLoadStream(gba->rr, state->associatedStreamId); - GBARRIncrementStream(gba->rr, true); - } else { - GBARRFinishSegment(gba->rr); - } - GBARRMarkRerecord(gba->rr); - } else if (GBARRIsPlaying(gba->rr)) { - GBARRLoadStream(gba->rr, state->associatedStreamId); - GBARRSkipSegment(gba->rr); + if (gba->rr) { + gba->rr->stateLoaded(gba->rr, state); } }@@ -173,22 +183,23 @@ return true;
} #endif -bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot) { - struct VFile* vf = GBAGetState(gba, dir, slot, true); +bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, bool screenshot) { + struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, true); if (!vf) { return false; } - bool success = GBASaveStateNamed(gba, vf, screenshot); + bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot); vf->close(vf); return success; } -bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot) { - struct VFile* vf = GBAGetState(gba, dir, slot, false); +bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) { + struct VFile* vf = GBAGetState(threadContext->gba, dir, slot, false); if (!vf) { return false; } - bool success = GBALoadStateNamed(gba, vf); + threadContext->rewindBufferSize = 0; + bool success = GBALoadStateNamed(threadContext->gba, vf); vf->close(vf); return success; }@@ -247,6 +258,28 @@ thread->rewindBufferSize = thread->rewindBufferSize == thread->rewindBufferCapacity ? thread->rewindBufferCapacity : thread->rewindBufferSize + 1;
thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity; } +void GBARewindSettingsChanged(struct GBAThread* threadContext, int newCapacity, int newInterval) { + if (newCapacity == threadContext->rewindBufferCapacity && newInterval == threadContext->rewindBufferInterval) { + return; + } + threadContext->rewindBufferInterval = newInterval; + threadContext->rewindBufferNext = threadContext->rewindBufferInterval; + threadContext->rewindBufferSize = 0; + if (threadContext->rewindBuffer) { + int i; + for (i = 0; i < threadContext->rewindBufferCapacity; ++i) { + GBADeallocateState(threadContext->rewindBuffer[i]); + } + free(threadContext->rewindBuffer); + } + threadContext->rewindBufferCapacity = newCapacity; + if (threadContext->rewindBufferCapacity > 0) { + threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(struct GBASerializedState*)); + } else { + threadContext->rewindBuffer = 0; + } +} + void GBARewind(struct GBAThread* thread, int nStates) { if (nStates > thread->rewindBufferSize || nStates < 0) { nStates = thread->rewindBufferSize;@@ -262,7 +295,11 @@ struct GBASerializedState* state = thread->rewindBuffer[offset];
if (!state) { return; } - thread->rewindBufferSize -= nStates; - thread->rewindBufferWriteOffset = (offset + thread->rewindBufferCapacity - nStates) % thread->rewindBufferCapacity; + thread->rewindBufferSize -= nStates - 1; + thread->rewindBufferWriteOffset = (offset + 1) % thread->rewindBufferCapacity; GBADeserialize(thread->gba, state); } + +void GBARewindAll(struct GBAThread* thread) { + GBARewind(thread, thread->rewindBufferSize); +}
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,7 +8,7 @@ #define GBA_SERIALIZE_H
#include "util/common.h" -#include "gba.h" +#include "gba/gba.h" extern const uint32_t GBA_SAVESTATE_MAGIC;@@ -77,7 +77,7 @@ * | 0x001EC - 0x001EF: Next hblank
* | 0x001F0 - 0x001F3: Next hblank IRQ * | 0x001F4 - 0x001F7: Next vblank IRQ * | 0x001F8 - 0x001FB: Next vcounter IRQ - * | 0x001FC - 0x001FF: Reserved + * | 0x001FC - 0x001FF: Frame counter * 0x00200 - 0x00213: Timer 0 * | 0x00200 - 0x00201: Reload value * | 0x00202 - 0x00203: Old reload value@@ -126,25 +126,35 @@ * | 0x00280 - 0x00283: DMA next source
* | 0x00284 - 0x00287: DMA next destination * | 0x00288 - 0x0028B: DMA next count * | 0x0028C - 0x0028F: DMA next event - * 0x00290 - 0x002BF: GPIO state + * 0x00290 - 0x002C3: GPIO state * | 0x00290 - 0x00291: Pin state * | 0x00292 - 0x00293: Direction state - * | 0x00294 - 0x002B6: RTC state (see gba-gpio.h for format) + * | 0x00294 - 0x002B6: RTC state (see gba-hardware.h for format) * | 0x002B7 - 0x002B7: GPIO devices * | bit 0: Has RTC values * | bit 1: Has rumble value (reserved) - * | bit 2: Has light sensor value (reserved) + * | bit 2: Has light sensor value * | bit 3: Has gyroscope value - * | bit 4: Has tilt values (reserved) + * | bit 4: Has tilt values * | bits 5 - 7: Reserved * | 0x002B8 - 0x002B9: Gyroscope sample - * | 0x002BA - 0x002BB: Tilt x sample (reserved) - * | 0x002BC - 0x002BD: Tilt y sample (reserved) + * | 0x002BA - 0x002BB: Tilt x sample + * | 0x002BC - 0x002BD: Tilt y sample * | 0x002BE - 0x002BF: Flags * | bit 0: Is read enabled * | bit 1: Gyroscope sample is edge - * | bits 2 - 15: Reserved - * 0x002C0 - 0x002FF: Reserved (leave zero) + * | bit 2: Light sample is edge + * | bit 3: Reserved + * | bits 4 - 15: Light counter + * | 0x002C0 - 0x002C0: Light sample + * | 0x002C1 - 0x002C3: Flags + * | bits 0 - 1: Tilt state machine + * | bits 2 - 31: Reserved + * 0x002C4 - 0x002F3: Reserved (leave zero) + * 0x002F4 - 0x002FF: Prefetch + * | 0x002F4 - 0x002F7: GBA BIOS bus prefetch + * | 0x002F8 - 0x002FB: CPU prefecth (decode slot) + * | 0x002FC - 0x002FF: CPU prefetch (fetch slot) * 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream) * 0x00304 - 0x003FF: Reserved (leave zero) * 0x00400 - 0x007FF: I/O memory@@ -230,7 +240,7 @@ int32_t nextHblank;
int32_t nextHblankIRQ; int32_t nextVblankIRQ; int32_t nextVcounterIRQ; - int32_t : 32; + int32_t frameCounter; } video; struct GBATimer timers[4];@@ -247,15 +257,24 @@ uint16_t pinState;
uint16_t pinDirection; struct GBARTC rtc; uint8_t devices; + // Do not change these to uint16_t, this breaks bincompat with some older compilers unsigned gyroSample : 16; unsigned tiltSampleX : 16; unsigned tiltSampleY : 16; unsigned readWrite : 1; unsigned gyroEdge : 1; - unsigned reserved : 14; - } gpio; + unsigned lightEdge : 1; + unsigned : 1; + unsigned lightCounter : 12; + unsigned lightSample : 8; + unsigned tiltState : 2; + unsigned : 22; + } hw; + + uint32_t reservedHardware[12]; - uint32_t reservedGpio[16]; + uint32_t biosPrefetch; + uint32_t cpuPrefetch[2]; uint32_t associatedStreamId;@@ -270,12 +289,13 @@ uint8_t wram[SIZE_WORKING_RAM];
}; struct VDir; +struct GBAThread; void GBASerialize(struct GBA* gba, struct GBASerializedState* state); -void GBADeserialize(struct GBA* gba, struct GBASerializedState* state); +void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); -bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot); -bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot); +bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot); +bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot); struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write); bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot);@@ -284,8 +304,9 @@
struct GBASerializedState* GBAAllocateState(void); void GBADeallocateState(struct GBASerializedState* state); -struct GBAThread; void GBARecordFrame(struct GBAThread* thread); +void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int newInterval); void GBARewind(struct GBAThread* thread, int nStates); +void GBARewindAll(struct GBAThread* thread); #endif
@@ -1,11 +1,18 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-sio.h" +#include "sio.h" + +#include "gba/io.h" -#include "gba-io.h" +const int GBASIOCyclesPerTransfer[4][MAX_GBAS] = { + { 38326, 73003, 107680, 142356 }, + { 9582, 18251, 26920, 35589 }, + { 6388, 12167, 17947, 23726 }, + { 3194, 6075, 8973, 11863 } +}; static struct GBASIODriver* _lookupDriver(struct GBASIO* sio, enum GBASIOMode mode) { switch (mode) {@@ -22,18 +29,18 @@ }
} static void _switchMode(struct GBASIO* sio) { - unsigned mode = ((sio->rcnt >> 14) & 0xC) | ((sio->siocnt >> 12) & 0x3); + unsigned mode = ((sio->rcnt & 0xC000) | (sio->siocnt & 0x3000)) >> 12; enum GBASIOMode oldMode = sio->mode; if (mode < 8) { sio->mode = (enum GBASIOMode) (mode & 0x3); } else { sio->mode = (enum GBASIOMode) (mode & 0xC); } - if (oldMode != mode) { + if (oldMode != sio->mode) { if (sio->activeDriver && sio->activeDriver->unload) { sio->activeDriver->unload(sio->activeDriver); } - sio->activeDriver = _lookupDriver(sio, mode); + sio->activeDriver = _lookupDriver(sio, sio->mode); if (sio->activeDriver && sio->activeDriver->load) { sio->activeDriver->load(sio->activeDriver); }@@ -52,6 +59,9 @@ _switchMode(sio);
} void GBASIODeinit(struct GBASIO* sio) { + if (sio->activeDriver && sio->activeDriver->unload) { + sio->activeDriver->unload(sio->activeDriver); + } if (sio->drivers.multiplayer && sio->drivers.multiplayer->deinit) { sio->drivers.multiplayer->deinit(sio->drivers.multiplayer); }@@ -112,7 +122,8 @@ *driverLoc = driver;
} void GBASIOWriteRCNT(struct GBASIO* sio, uint16_t value) { - sio->rcnt = value; + sio->rcnt &= 0xF; + sio->rcnt |= value & ~0xF; _switchMode(sio); if (sio->activeDriver && sio->activeDriver->writeRegister) { sio->activeDriver->writeRegister(sio->activeDriver, REG_RCNT, value);
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -7,6 +7,10 @@ #ifndef GBA_SIO_H
#define GBA_SIO_H #include "util/common.h" + +#define MAX_GBAS 4 + +extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS]; enum GBASIOMode { SIO_NORMAL_8 = 0,
@@ -1,14 +1,17 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-thread.h" +#include "thread.h" #include "arm.h" -#include "gba.h" -#include "gba-config.h" -#include "gba-serialize.h" +#include "gba/gba.h" +#include "gba/cheats.h" +#include "gba/serialize.h" +#include "gba/supervisor/config.h" +#include "gba/rr/mgm.h" +#include "gba/rr/vbm.h" #include "debugger/debugger.h"@@ -59,6 +62,11 @@ }
} static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) { + MutexLock(&threadContext->sync.videoFrameMutex); + bool videoFrameWait = threadContext->sync.videoFrameWait; + threadContext->sync.videoFrameWait = false; + MutexUnlock(&threadContext->sync.videoFrameMutex); + while (threadContext->state == oldState) { MutexUnlock(&threadContext->stateMutex);@@ -73,12 +81,13 @@
MutexLock(&threadContext->stateMutex); ConditionWake(&threadContext->stateCond); } + + MutexLock(&threadContext->sync.videoFrameMutex); + threadContext->sync.videoFrameWait = videoFrameWait; + MutexUnlock(&threadContext->sync.videoFrameMutex); } static void _pauseThread(struct GBAThread* threadContext, bool onThread) { - if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) { - threadContext->debugger->state = DEBUGGER_EXITING; - } threadContext->state = THREAD_PAUSING; if (!onThread) { _waitUntilNotState(threadContext, THREAD_PAUSING);@@ -107,14 +116,11 @@
struct GBA gba; struct ARMCore cpu; struct Patch patch; + struct GBACheatDevice cheatDevice; struct GBAThread* threadContext = context; - struct ARMComponent* components[1] = {}; - int numComponents = 0; - - if (threadContext->debugger) { - components[numComponents] = &threadContext->debugger->d; - ++numComponents; - } + struct ARMComponent* components[GBA_COMPONENT_MAX] = {}; + struct GBARRContext* movie = 0; + int numComponents = GBA_COMPONENT_MAX; #if !defined(_WIN32) && defined(USE_PTHREADS) sigset_t signals;@@ -128,6 +134,9 @@ ARMInit(&cpu);
gba.sync = &threadContext->sync; threadContext->gba = &gba; gba.logLevel = threadContext->logLevel; + gba.logHandler = threadContext->logHandler; + gba.stream = threadContext->stream; + gba.idleOptimization = threadContext->idleOptimization; #ifdef USE_PTHREADS pthread_setspecific(_contextKey, threadContext); #else@@ -146,7 +155,18 @@ }
if (threadContext->rom) { GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname); - if (threadContext->bios) { + + struct GBACartridgeOverride override; + const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom; + memcpy(override.id, &cart->id, sizeof(override.id)); + if (GBAOverrideFind(threadContext->overrides, &override)) { + GBAOverrideApply(&gba, &override); + } + if (threadContext->hasOverride) { + GBAOverrideApply(&gba, &threadContext->override); + } + + if (threadContext->bios && GBAIsBIOS(threadContext->bios)) { GBALoadBIOS(&gba, threadContext->bios); }@@ -155,12 +175,60 @@ GBAApplyPatch(&gba, &patch);
} } + if (threadContext->movie) { + struct VDir* movieDir = VDirOpen(threadContext->movie); +#ifdef USE_LIBZIP + if (!movieDir) { + movieDir = VDirOpenZip(threadContext->movie, 0); + } +#endif + if (movieDir) { + struct GBAMGMContext* mgm = malloc(sizeof(*mgm)); + GBAMGMContextCreate(mgm); + if (!GBAMGMSetStream(mgm, movieDir)) { + mgm->d.destroy(&mgm->d); + } else { + movie = &mgm->d; + } + } else { + struct VFile* movieFile = VFileOpen(threadContext->movie, O_RDONLY); + if (movieFile) { + struct GBAVBMContext* vbm = malloc(sizeof(*vbm)); + GBAVBMContextCreate(vbm); + if (!GBAVBMSetStream(vbm, movieFile)) { + vbm->d.destroy(&vbm->d); + } else { + movie = &vbm->d; + } + } + } + } + ARMReset(&cpu); + if (movie) { + gba.rr = movie; + movie->startPlaying(movie, false); + GBARRInitPlay(&gba); + } + + if (threadContext->skipBios) { + GBASkipBIOS(&cpu); + } + + if (!threadContext->cheats) { + GBACheatDeviceCreate(&cheatDevice); + threadContext->cheats = &cheatDevice; + } + if (threadContext->cheatsFile) { + GBACheatParseFile(threadContext->cheats, threadContext->cheatsFile); + } + GBACheatAttachDevice(&gba, threadContext->cheats); + if (threadContext->debugger) { threadContext->debugger->log = GBADebuggerLogShim; GBAAttachDebugger(&gba, threadContext->debugger); - ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED); + ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED, 0); } GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);@@ -208,10 +276,13 @@ }
MutexUnlock(&threadContext->stateMutex); if (resetScheduled) { ARMReset(&cpu); + if (threadContext->skipBios) { + GBASkipBIOS(&cpu); + } } } - while (threadContext->state != THREAD_SHUTDOWN) { + while (threadContext->state < THREAD_SHUTDOWN) { _changeState(threadContext, THREAD_SHUTDOWN, false); }@@ -222,6 +293,14 @@
threadContext->gba = 0; ARMDeinit(&cpu); GBADestroy(&gba); + if (&cheatDevice == threadContext->cheats) { + GBACheatDeviceDestroy(&cheatDevice); + } + + if (movie) { + movie->destroy(movie); + free(movie); + } threadContext->sync.videoFrameOn = false; ConditionWake(&threadContext->sync.videoFrameAvailableCond);@@ -234,8 +313,13 @@ void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) {
threadContext->bios = VFileOpen(opts->bios, O_RDONLY); threadContext->frameskip = opts->frameskip; threadContext->logLevel = opts->logLevel; - threadContext->rewindBufferCapacity = opts->rewindBufferCapacity; - threadContext->rewindBufferInterval = opts->rewindBufferInterval; + if (opts->rewindEnable) { + threadContext->rewindBufferCapacity = opts->rewindBufferCapacity; + threadContext->rewindBufferInterval = opts->rewindBufferInterval; + } else { + threadContext->rewindBufferCapacity = 0; + } + threadContext->skipBios = opts->skipBios; threadContext->sync.audioWait = opts->audioSync; threadContext->sync.videoFrameWait = opts->videoSync;@@ -246,6 +330,8 @@
if (opts->audioBuffers) { threadContext->audioBuffers = opts->audioBuffers; } + + threadContext->idleOptimization = opts->idleOptimization; } void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {@@ -254,12 +340,22 @@ threadContext->gameDir = VDirOpen(args->fname);
threadContext->stateDir = threadContext->gameDir; } else { threadContext->rom = VFileOpen(args->fname, O_RDONLY); -#if ENABLE_LIBZIP - threadContext->gameDir = VDirOpenZip(args->fname, 0); + threadContext->gameDir = 0; +#if USE_LIBZIP + if (!threadContext->gameDir) { + threadContext->gameDir = VDirOpenZip(args->fname, 0); + } +#endif +#if USE_LZMA + if (!threadContext->gameDir) { + threadContext->gameDir = VDirOpen7z(args->fname, 0); + } #endif } threadContext->fname = args->fname; threadContext->patch = VFileOpen(args->patch, O_RDONLY); + threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY); + threadContext->movie = args->movie; } bool GBAThreadStart(struct GBAThread* threadContext) {@@ -269,13 +365,12 @@ threadContext->state = THREAD_INITIALIZED;
threadContext->sync.videoFrameOn = true; threadContext->sync.videoFrameSkip = 0; - threadContext->rewindBufferNext = threadContext->rewindBufferInterval; - threadContext->rewindBufferSize = 0; - if (threadContext->rewindBufferCapacity) { - threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*)); - } else { - threadContext->rewindBuffer = 0; - } + threadContext->rewindBuffer = 0; + int newCapacity = threadContext->rewindBufferCapacity; + int newInterval = threadContext->rewindBufferInterval; + threadContext->rewindBufferCapacity = 0; + threadContext->rewindBufferInterval = 0; + GBARewindSettingsChanged(threadContext, newCapacity, newInterval); if (!threadContext->fpsTarget) { threadContext->fpsTarget = _defaultFPSTarget;@@ -293,6 +388,7 @@ while (dirent) {
struct Patch patchTemp; struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY); if (!vf) { + dirent = threadContext->gameDir->listNext(threadContext->gameDir); continue; } if (!threadContext->rom && GBAIsROM(vf)) {@@ -351,18 +447,42 @@ MutexUnlock(&threadContext->stateMutex);
return hasStarted; } +bool GBAThreadHasExited(struct GBAThread* threadContext) { + bool hasExited; + MutexLock(&threadContext->stateMutex); + hasExited = threadContext->state > THREAD_EXITING; + MutexUnlock(&threadContext->stateMutex); + return hasExited; +} + +bool GBAThreadHasCrashed(struct GBAThread* threadContext) { + bool hasExited; + MutexLock(&threadContext->stateMutex); + hasExited = threadContext->state == THREAD_CRASHED; + MutexUnlock(&threadContext->stateMutex); + return hasExited; +} + void GBAThreadEnd(struct GBAThread* threadContext) { MutexLock(&threadContext->stateMutex); - if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) { - threadContext->debugger->state = DEBUGGER_EXITING; - } + _waitOnInterrupt(threadContext); threadContext->state = THREAD_EXITING; + if (threadContext->gba) { + threadContext->gba->cpu->halted = false; + } ConditionWake(&threadContext->stateCond); MutexUnlock(&threadContext->stateMutex); MutexLock(&threadContext->sync.audioBufferMutex); threadContext->sync.audioWait = 0; ConditionWake(&threadContext->sync.audioRequiredCond); MutexUnlock(&threadContext->sync.audioBufferMutex); + + MutexLock(&threadContext->sync.videoFrameMutex); + threadContext->sync.videoFrameWait = false; + threadContext->sync.videoFrameOn = false; + ConditionWake(&threadContext->sync.videoFrameRequiredCond); + ConditionWake(&threadContext->sync.videoFrameAvailableCond); + MutexUnlock(&threadContext->sync.videoFrameMutex); } void GBAThreadReset(struct GBAThread* threadContext) {@@ -374,11 +494,6 @@ MutexUnlock(&threadContext->stateMutex);
} void GBAThreadJoin(struct GBAThread* threadContext) { - MutexLock(&threadContext->sync.videoFrameMutex); - threadContext->sync.videoFrameWait = 0; - ConditionWake(&threadContext->sync.videoFrameRequiredCond); - MutexUnlock(&threadContext->sync.videoFrameMutex); - ThreadJoin(threadContext->thread); MutexDeinit(&threadContext->stateMutex);@@ -450,9 +565,7 @@ }
threadContext->savedState = threadContext->state; _waitOnInterrupt(threadContext); threadContext->state = THREAD_INTERRUPTING; - if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) { - threadContext->debugger->state = DEBUGGER_EXITING; - } + threadContext->gba->cpu->nextEvent = 0; ConditionWake(&threadContext->stateCond); _waitUntilNotState(threadContext, THREAD_INTERRUPTING); MutexUnlock(&threadContext->stateMutex);@@ -580,25 +693,6 @@ }
} while (sync->videoFrameWait && sync->videoFramePending); } MutexUnlock(&sync->videoFrameMutex); - - struct GBAThread* thread = GBAThreadGetContext(); - if (!thread) { - return; - } - - if (thread->rewindBuffer) { - --thread->rewindBufferNext; - if (thread->rewindBufferNext <= 0) { - thread->rewindBufferNext = thread->rewindBufferInterval; - GBARecordFrame(thread); - } - } - if (thread->stream) { - thread->stream->postVideoFrame(thread->stream, thread->renderer); - } - if (thread->frameCallback) { - thread->frameCallback(thread); - } } bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {@@ -611,8 +705,10 @@ ConditionWake(&sync->videoFrameRequiredCond);
if (!sync->videoFrameOn && !sync->videoFramePending) { return false; } - if (sync->videoFrameOn && !sync->videoFramePending) { - ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex); + if (sync->videoFrameOn) { + if (ConditionWaitTimed(&sync->videoFrameAvailableCond, &sync->videoFrameMutex, 50)) { + return false; + } } sync->videoFramePending = 0; sync->videoFrameSkip = frameskip;
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,16 +8,18 @@ #define GBA_THREAD_H
#include "util/common.h" -#include "gba.h" -#include "gba-input.h" +#include "gba/gba.h" +#include "gba/input.h" +#include "gba/supervisor/overrides.h" #include "util/threading.h" struct GBAThread; struct GBAArguments; +struct GBACheatSet; struct GBAOptions; + typedef void (*ThreadCallback)(struct GBAThread* threadContext); -typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args); enum ThreadState { THREAD_INITIALIZED = -1,@@ -28,7 +30,8 @@ THREAD_PAUSED,
THREAD_PAUSING, THREAD_RESETING, THREAD_EXITING, - THREAD_SHUTDOWN + THREAD_SHUTDOWN, + THREAD_CRASHED }; struct GBASync {@@ -45,11 +48,6 @@ Condition audioRequiredCond;
Mutex audioBufferMutex; }; -struct GBAAVStream { - void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer); - void (*postAudioFrame)(struct GBAAVStream*, int32_t left, int32_t right); -}; - struct GBAThread { // Output enum ThreadState state;@@ -66,14 +64,22 @@ struct VFile* rom;
struct VFile* save; struct VFile* bios; struct VFile* patch; + struct VFile* cheatsFile; const char* fname; + const char* movie; int activeKeys; struct GBAAVStream* stream; + struct Configuration* overrides; + enum GBAIdleLoopOptimization idleOptimization; + + bool hasOverride; + struct GBACartridgeOverride override; // Run-time options int frameskip; float fpsTarget; size_t audioBuffers; + bool skipBios; // Threading state Thread thread;@@ -83,7 +89,7 @@ Condition stateCond;
enum ThreadState savedState; int interruptDepth; - LogHandler logHandler; + GBALogHandler logHandler; int logLevel; ThreadCallback startCallback; ThreadCallback cleanCallback;@@ -98,6 +104,8 @@ int rewindBufferInterval;
int rewindBufferNext; struct GBASerializedState** rewindBuffer; int rewindBufferWriteOffset; + + struct GBACheatDevice* cheats; }; void GBAMapOptionsToContext(const struct GBAOptions*, struct GBAThread*);@@ -105,6 +113,8 @@ void GBAMapArgumentsToContext(const struct GBAArguments*, struct GBAThread*);
bool GBAThreadStart(struct GBAThread* threadContext); bool GBAThreadHasStarted(struct GBAThread* threadContext); +bool GBAThreadHasExited(struct GBAThread* threadContext); +bool GBAThreadHasCrashed(struct GBAThread* threadContext); void GBAThreadEnd(struct GBAThread* threadContext); void GBAThreadReset(struct GBAThread* threadContext); void GBAThreadJoin(struct GBAThread* threadContext);
@@ -1,15 +1,15 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-video.h" +#include "video.h" -#include "gba.h" -#include "gba-io.h" -#include "gba-rr.h" -#include "gba-serialize.h" -#include "gba-thread.h" +#include "gba/gba.h" +#include "gba/io.h" +#include "gba/serialize.h" +#include "gba/supervisor/rr.h" +#include "gba/supervisor/thread.h" #include "util/memory.h"@@ -41,8 +41,7 @@ video->vram = 0;
} void GBAVideoReset(struct GBAVideo* video) { - video->dispstat = 0; - video->vcount = 0; + video->vcount = VIDEO_VERTICAL_TOTAL_PIXELS - 1; video->lastHblank = 0; video->nextHblank = VIDEO_HDRAW_LENGTH;@@ -53,6 +52,8 @@ video->nextHblankIRQ = 0;
video->nextVblankIRQ = 0; video->nextVcounterIRQ = 0; + video->frameCounter = 0; + if (video->vram) { mappedMemoryFree(video->vram, SIZE_VRAM); }@@ -94,52 +95,57 @@ video->lastHblank -= video->eventDiff;
video->nextHblank -= video->eventDiff; video->nextHblankIRQ -= video->eventDiff; video->nextVcounterIRQ -= video->eventDiff; + video->eventDiff = 0; + uint16_t dispstat = video->p->memory.io[REG_DISPSTAT >> 1]; - if (GBARegisterDISPSTATIsInHblank(video->dispstat)) { + if (GBARegisterDISPSTATIsInHblank(dispstat)) { // End Hblank - video->dispstat = GBARegisterDISPSTATClearInHblank(video->dispstat); + dispstat = GBARegisterDISPSTATClearInHblank(dispstat); video->nextEvent = video->nextHblank; ++video->vcount; + if (video->vcount == VIDEO_VERTICAL_TOTAL_PIXELS) { + video->vcount = 0; + } video->p->memory.io[REG_VCOUNT >> 1] = video->vcount; + if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) { + dispstat = GBARegisterDISPSTATFillVcounter(dispstat); + if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { + GBARaiseIRQ(video->p, IRQ_VCOUNTER); + video->nextVcounterIRQ += VIDEO_TOTAL_LENGTH; + } + } else { + dispstat = GBARegisterDISPSTATClearVcounter(dispstat); + } + video->p->memory.io[REG_DISPSTAT >> 1] = dispstat; + + // Note: state may be recorded during callbacks, so ensure it is consistent! switch (video->vcount) { + case 0: + GBAFrameStarted(video->p); + break; case VIDEO_VERTICAL_PIXELS: - video->dispstat = GBARegisterDISPSTATFillInVblank(video->dispstat); + video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat); if (GBASyncDrawingFrame(video->p->sync)) { video->renderer->finishFrame(video->renderer); } video->nextVblankIRQ = video->nextEvent + VIDEO_TOTAL_LENGTH; GBAMemoryRunVblankDMAs(video->p, lastEvent); - if (GBARegisterDISPSTATIsVblankIRQ(video->dispstat)) { + if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) { GBARaiseIRQ(video->p, IRQ_VBLANK); } + GBAFrameEnded(video->p); GBASyncPostFrame(video->p->sync); + ++video->frameCounter; break; case VIDEO_VERTICAL_TOTAL_PIXELS - 1: - if (video->p->rr) { - GBARRNextFrame(video->p->rr); - } - video->dispstat = GBARegisterDISPSTATClearInVblank(video->dispstat); - break; - case VIDEO_VERTICAL_TOTAL_PIXELS: - video->vcount = 0; - video->p->memory.io[REG_VCOUNT >> 1] = 0; + video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat); break; } - - if (video->vcount == GBARegisterDISPSTATGetVcountSetting(video->dispstat)) { - video->dispstat = GBARegisterDISPSTATFillVcounter(video->dispstat); - if (GBARegisterDISPSTATIsVcounterIRQ(video->dispstat)) { - GBARaiseIRQ(video->p, IRQ_VCOUNTER); - video->nextVcounterIRQ += VIDEO_TOTAL_LENGTH; - } - } else { - video->dispstat = GBARegisterDISPSTATClearVcounter(video->dispstat); - } } else { // Begin Hblank - video->dispstat = GBARegisterDISPSTATFillInHblank(video->dispstat); + dispstat = GBARegisterDISPSTATFillInHblank(dispstat); video->lastHblank = video->nextHblank; video->nextEvent = video->lastHblank + VIDEO_HBLANK_LENGTH; video->nextHblank = video->nextEvent + VIDEO_HDRAW_LENGTH;@@ -152,25 +158,24 @@
if (video->vcount < VIDEO_VERTICAL_PIXELS) { GBAMemoryRunHblankDMAs(video->p, lastEvent); } - if (GBARegisterDISPSTATIsHblankIRQ(video->dispstat)) { + if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { GBARaiseIRQ(video->p, IRQ_HBLANK); } + video->p->memory.io[REG_DISPSTAT >> 1] = dispstat; } - - video->eventDiff = 0; } - video->p->memory.io[REG_DISPSTAT >> 1] &= 0xFFF8; - video->p->memory.io[REG_DISPSTAT >> 1] |= video->dispstat & 0x7; return video->nextEvent; } void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value) { - video->dispstat &= 0x7; - video->dispstat |= value & 0xFFF8; + video->p->memory.io[REG_DISPSTAT >> 1] &= 0x7; + video->p->memory.io[REG_DISPSTAT >> 1] |= value; + + uint16_t dispstat = video->p->memory.io[REG_DISPSTAT >> 1]; - if (GBARegisterDISPSTATIsVcounterIRQ(video->dispstat)) { + if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { // FIXME: this can be too late if we're in the middle of an Hblank - video->nextVcounterIRQ = video->nextHblank + VIDEO_HBLANK_LENGTH + (GBARegisterDISPSTATGetVcountSetting(video->dispstat) - video->vcount) * VIDEO_HORIZONTAL_LENGTH; + video->nextVcounterIRQ = video->nextHblank + VIDEO_HBLANK_LENGTH + (GBARegisterDISPSTATGetVcountSetting(dispstat) - video->vcount) * VIDEO_HORIZONTAL_LENGTH; if (video->nextVcounterIRQ < video->nextEvent) { video->nextVcounterIRQ += VIDEO_TOTAL_LENGTH; }@@ -230,11 +235,10 @@ // Nothing to do
} -void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state) { +void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) { memcpy(state->vram, video->renderer->vram, SIZE_VRAM); memcpy(state->oam, video->oam.raw, SIZE_OAM); memcpy(state->pram, video->palette, SIZE_PALETTE_RAM); - state->io[REG_DISPSTAT >> 1] = video->dispstat; state->video.nextEvent = video->nextEvent; state->video.eventDiff = video->eventDiff; state->video.lastHblank = video->lastHblank;@@ -242,9 +246,10 @@ state->video.nextHblank = video->nextHblank;
state->video.nextHblankIRQ = video->nextHblankIRQ; state->video.nextVblankIRQ = video->nextVblankIRQ; state->video.nextVcounterIRQ = video->nextVcounterIRQ; + state->video.frameCounter = video->frameCounter; } -void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state) { +void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) { memcpy(video->renderer->vram, state->vram, SIZE_VRAM); int i; for (i = 0; i < SIZE_OAM; i += 2) {@@ -253,7 +258,6 @@ }
for (i = 0; i < SIZE_PALETTE_RAM; i += 2) { GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, state->pram[i >> 1], 0); } - video->dispstat = state->io[REG_DISPSTAT >> 1]; video->nextEvent = state->video.nextEvent; video->eventDiff = state->video.eventDiff; video->lastHblank = state->video.lastHblank;@@ -261,5 +265,6 @@ video->nextHblank = state->video.nextHblank;
video->nextHblankIRQ = state->video.nextHblankIRQ; video->nextVblankIRQ = state->video.nextVblankIRQ; video->nextVcounterIRQ = state->video.nextVcounterIRQ; + video->frameCounter = state->video.frameCounter; video->vcount = state->io[REG_VCOUNT >> 1]; }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,7 +8,7 @@ #define GBA_VIDEO_H
#include "util/common.h" -#include "gba-memory.h" +#include "gba/memory.h" #include "macros.h" #ifdef COLOR_16_BIT@@ -173,8 +173,6 @@ struct GBAVideo {
struct GBA* p; struct GBAVideoRenderer* renderer; - GBARegisterDISPSTAT dispstat; - // VCOUNT int vcount;@@ -190,6 +188,8 @@
uint16_t palette[SIZE_PALETTE_RAM >> 1]; uint16_t* vram; union GBAOAM oam; + + int32_t frameCounter; }; void GBAVideoInit(struct GBAVideo* video);@@ -201,7 +201,7 @@
void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value); struct GBASerializedState; -void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state); -void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state); +void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state); +void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state); #endif
@@ -1,15 +1,19 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba.h" -#include "gba-bios.h" -#include "gba-io.h" -#include "gba-rr.h" -#include "gba-sio.h" -#include "gba-thread.h" +#include "gba/bios.h" +#include "gba/cheats.h" +#include "gba/io.h" +#include "gba/supervisor/rr.h" +#include "gba/supervisor/thread.h" +#include "gba/serialize.h" +#include "gba/sio.h" + +#include "isa-inlines.h" #include "util/crc32.h" #include "util/memory.h"@@ -19,99 +23,8 @@
const uint32_t GBA_ARM7TDMI_FREQUENCY = 0x1000000; const uint32_t GBA_COMPONENT_MAGIC = 0x1000000; -static const size_t GBA_ROM_MAGIC_OFFSET = 2; -static const uint8_t GBA_ROM_MAGIC[] = { 0x00, 0xEA }; - -enum { - SP_BASE_SYSTEM = 0x03FFFF00, - SP_BASE_IRQ = 0x03FFFFA0, - SP_BASE_SUPERVISOR = 0x03FFFFE0 -}; - -struct GBACartridgeOverride { - const char id[4]; - enum SavedataType type; - int gpio; - uint32_t busyLoop; -}; - -static const struct GBACartridgeOverride _overrides[] = { - // Boktai: The Sun is in Your Hand - { "U3IE", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 }, - { "U3IP", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 }, - - // Boktai 2: Solar Boy Django - { "U32E", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 }, - { "U32P", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 }, - - // Drill Dozer - { "V49J", SAVEDATA_SRAM, GPIO_RUMBLE, -1 }, - { "V49E", SAVEDATA_SRAM, GPIO_RUMBLE, -1 }, - - // Final Fantasy Tactics Advance - { "AFXE", SAVEDATA_FLASH512, GPIO_NONE, 0x8000418 }, - - // Mega Man Battle Network - { "AREE", SAVEDATA_SRAM, GPIO_NONE, 0x800032E }, - - // Pokemon Ruby - { "AXVJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXVE", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXVP", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXVI", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXVS", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXVD", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXVF", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - - // Pokemon Sapphire - { "AXPJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXPE", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXPP", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXPI", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXPS", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXPD", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "AXPF", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - - // Pokemon Emerald - { "BPEJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "BPEE", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "BPEP", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "BPEI", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "BPES", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "BPED", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - { "BPEF", SAVEDATA_FLASH1M, GPIO_RTC, -1 }, - - // Pokemon Mystery Dungeon - { "B24J", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "B24E", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "B24P", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "B24U", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - - // Pokemon FireRed - { "BPRJ", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "BPRE", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "BPRP", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - - // Pokemon LeafGreen - { "BPGJ", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "BPGE", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "BPGP", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - - // RockMan EXE 4.5 - Real Operation - { "BR4J", SAVEDATA_FLASH512, GPIO_RTC, -1 }, - - // Super Mario Advance 4 - { "AX4J", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "AX4E", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - { "AX4P", SAVEDATA_FLASH1M, GPIO_NONE, -1 }, - - // Wario Ware Twisted - { "RZWJ", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 }, - { "RZWE", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 }, - { "RZWP", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 }, - - { { 0, 0, 0, 0 }, 0, 0, -1 } -}; +static const size_t GBA_ROM_MAGIC_OFFSET = 3; +static const uint8_t GBA_ROM_MAGIC[] = { 0xEA }; static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component); static void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh);@@ -119,8 +32,10 @@ static void GBAProcessEvents(struct ARMCore* cpu);
static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles); static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode); static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode); +static void GBABreakpoint(struct ARMCore* cpu, int immediate); -static void _checkOverrides(struct GBA* gba, uint32_t code); +static bool _setSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); +static bool _clearSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode); void GBACreate(struct GBA* gba) { gba->d.id = GBA_COMPONENT_MAGIC;@@ -132,6 +47,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
struct GBA* gba = (struct GBA*) component; gba->cpu = cpu; gba->debugger = 0; + gba->sync = 0; GBAInterruptHandlerInit(&cpu->irqh); GBAMemoryInit(gba);@@ -154,17 +70,26 @@
gba->springIRQ = 0; gba->keySource = 0; gba->rotationSource = 0; + gba->luminanceSource = 0; + gba->rtcSource = 0; gba->rumble = 0; gba->rr = 0; gba->romVf = 0; gba->biosVf = 0; + gba->logHandler = 0; gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; + gba->stream = 0; gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS); - gba->busyLoop = -1; + gba->idleOptimization = IDLE_LOOP_REMOVE; + gba->idleLoop = IDLE_LOOP_NONE; + gba->lastJump = 0; + gba->idleDetectionStep = 0; + gba->idleDetectionFailures = 0; + gba->performingDMA = false; } void GBADestroy(struct GBA* gba) {@@ -183,7 +108,8 @@
GBAMemoryDeinit(gba); GBAVideoDeinit(&gba->video); GBAAudioDeinit(&gba->audio); - GBARRContextDestroy(gba); + GBASIODeinit(&gba->sio); + gba->rr = 0; } void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {@@ -194,6 +120,8 @@ irqh->swi32 = GBASwi32;
irqh->hitIllegal = GBAIllegal; irqh->readCPSR = GBATestIRQ; irqh->hitStub = GBAHitStub; + irqh->bkpt16 = GBABreakpoint; + irqh->bkpt32 = GBABreakpoint; } void GBAReset(struct ARMCore* cpu) {@@ -205,7 +133,7 @@ ARMSetPrivilegeMode(cpu, MODE_SYSTEM);
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM; struct GBA* gba = (struct GBA*) cpu->master; - if (!GBARRIsPlaying(gba->rr) && !GBARRIsRecording(gba->rr)) { + if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) { GBASavedataUnmask(&gba->memory.savedata); } GBAMemoryReset(gba);@@ -220,16 +148,24 @@ gba->timersEnabled = 0;
memset(gba->timers, 0, sizeof(gba->timers)); } +void GBASkipBIOS(struct ARMCore* cpu) { + if (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) { + cpu->gprs[ARM_PC] = BASE_CART0; + int currentCycles = 0; + ARM_WRITE_PC; + } +} + static void GBAProcessEvents(struct ARMCore* cpu) { do { struct GBA* gba = (struct GBA*) cpu->master; - int32_t cycles = cpu->cycles; + int32_t cycles = cpu->nextEvent; int32_t nextEvent = INT_MAX; int32_t testEvent; - gba->bus = cpu->prefetch; + gba->bus = cpu->prefetch[1]; if (cpu->executionMode == MODE_THUMB) { - gba->bus |= cpu->prefetch << 16; + gba->bus |= cpu->prefetch[1] << 16; } if (gba->springIRQ) {@@ -357,7 +293,6 @@ timer = &gba->timers[2];
if (timer->enable) { timer->nextEvent -= cycles; timer->lastEvent -= cycles; - nextEvent = timer->nextEvent; if (timer->nextEvent <= 0) { timer->lastEvent = timer->nextEvent; timer->nextEvent += timer->overflowInterval;@@ -389,7 +324,6 @@ timer = &gba->timers[3];
if (timer->enable) { timer->nextEvent -= cycles; timer->lastEvent -= cycles; - nextEvent = timer->nextEvent; if (timer->nextEvent <= 0) { timer->lastEvent = timer->nextEvent; timer->nextEvent += timer->overflowInterval;@@ -413,34 +347,48 @@ return nextEvent;
} void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger) { + debugger->setSoftwareBreakpoint = _setSoftwareBreakpoint; + debugger->clearSoftwareBreakpoint = _clearSoftwareBreakpoint; gba->debugger = debugger; + gba->cpu->components[GBA_COMPONENT_DEBUGGER] = &debugger->d; + ARMHotplugAttach(gba->cpu, GBA_COMPONENT_DEBUGGER); } void GBADetachDebugger(struct GBA* gba) { gba->debugger = 0; + ARMHotplugDetach(gba->cpu, GBA_COMPONENT_DEBUGGER); + gba->cpu->components[GBA_COMPONENT_DEBUGGER] = 0; } void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname) { gba->romVf = vf; - gba->pristineRomSize = vf->seek(vf, 0, SEEK_END); + gba->pristineRomSize = vf->size(vf); vf->seek(vf, 0, SEEK_SET); if (gba->pristineRomSize > SIZE_CART0) { gba->pristineRomSize = SIZE_CART0; } gba->pristineRom = vf->map(vf, gba->pristineRomSize, MAP_READ); + if (!gba->pristineRom) { + GBALog(gba, GBA_LOG_WARN, "Couldn't map ROM"); + return; + } gba->memory.rom = gba->pristineRom; gba->activeFile = fname; gba->memory.romSize = gba->pristineRomSize; gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize); GBASavedataInit(&gba->memory.savedata, sav); - GBAGPIOInit(&gba->memory.gpio, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]); - _checkOverrides(gba, ((struct GBACartridge*) gba->memory.rom)->id); + GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]); // TODO: error check } void GBALoadBIOS(struct GBA* gba, struct VFile* vf) { gba->biosVf = vf; - gba->memory.bios = vf->map(vf, SIZE_BIOS, MAP_READ); + uint32_t* bios = vf->map(vf, SIZE_BIOS, MAP_READ); + if (!bios) { + GBALog(gba, GBA_LOG_WARN, "Couldn't map BIOS"); + return; + } + gba->memory.bios = bios; gba->memory.fullBios = 1; uint32_t checksum = GBAChecksum(gba->memory.bios, SIZE_BIOS); GBALog(gba, GBA_LOG_DEBUG, "BIOS Checksum: 0x%X", checksum);@@ -464,8 +412,7 @@ if (!patchedSize) {
return; } gba->memory.rom = anonymousMemoryMap(patchedSize); - memcpy(gba->memory.rom, gba->pristineRom, gba->memory.romSize > patchedSize ? patchedSize : gba->memory.romSize); - if (!patch->applyPatch(patch, gba->memory.rom, patchedSize)) { + if (!patch->applyPatch(patch, gba->pristineRom, gba->pristineRomSize, gba->memory.rom, patchedSize)) { mappedMemoryFree(gba->memory.rom, patchedSize); gba->memory.rom = gba->pristineRom; return;@@ -578,25 +525,41 @@ }
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) { struct GBAThread* threadContext = GBAThreadGetContext(); + enum GBALogLevel logLevel = -1; + + if (gba) { + logLevel = gba->logLevel; + } + if (threadContext) { - if (!gba) { - gba = threadContext->gba; - } + logLevel = threadContext->logLevel; + gba = threadContext->gba; } - if (gba && !(level & gba->logLevel) && level != GBA_LOG_FATAL) { + if (!(level & logLevel) && level != GBA_LOG_FATAL) { return; } - if (threadContext && threadContext->logHandler) { - threadContext->logHandler(threadContext, level, format, args); + if (level == GBA_LOG_FATAL && gba) { + gba->cpu->nextEvent = 0; + } + + if (threadContext) { + if (level == GBA_LOG_FATAL) { + MutexLock(&threadContext->stateMutex); + threadContext->state = THREAD_CRASHED; + MutexUnlock(&threadContext->stateMutex); + } + } + if (gba && gba->logHandler) { + gba->logHandler(threadContext, level, format, args); return; } vprintf(format, args); printf("\n"); - if (level == GBA_LOG_FATAL) { + if (level == GBA_LOG_FATAL && !threadContext) { abort(); } }@@ -647,6 +610,23 @@ }
return memcmp(signature, GBA_ROM_MAGIC, sizeof(signature)) == 0; } +bool GBAIsBIOS(struct VFile* vf) { + if (vf->seek(vf, 0, SEEK_SET) < 0) { + return false; + } + uint32_t interruptTable[7]; + if (vf->read(vf, &interruptTable, sizeof(interruptTable)) != sizeof(interruptTable)) { + return false; + } + int i; + for (i = 0; i < 7; ++i) { + if ((interruptTable[i] & 0xFFFF0000) != 0xEA000000) { + return false; + } + } + return true; +} + void GBAGetGameCode(struct GBA* gba, char* out) { memcpy(out, &((struct GBACartridge*) gba->memory.rom)->id, 4); }@@ -660,7 +640,11 @@ struct GBA* gba = (struct GBA*) cpu->master;
enum GBALogLevel level = GBA_LOG_FATAL; if (gba->debugger) { level = GBA_LOG_STUB; - ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP); + struct DebuggerEntryInfo info = { + .address = _ARMPCAddress(cpu), + .opcode = opcode + }; + ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info); } GBALog(gba, level, "Stub opcode: %08x", opcode); }@@ -669,52 +653,140 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) {
struct GBA* gba = (struct GBA*) cpu->master; GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode); if (gba->debugger) { - ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP); + struct DebuggerEntryInfo info = { + .address = _ARMPCAddress(cpu), + .opcode = opcode + }; + ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info); } } -void _checkOverrides(struct GBA* gba, uint32_t id) { - int i; - gba->busyLoop = -1; - if ((id & 0xFF) == 'F') { - GBALog(gba, GBA_LOG_DEBUG, "Found Classic NES Series game, using EEPROM saves"); - GBASavedataInitEEPROM(&gba->memory.savedata); +void GBABreakpoint(struct ARMCore* cpu, int immediate) { + struct GBA* gba = (struct GBA*) cpu->master; + if (immediate >= GBA_COMPONENT_MAX) { return; } - for (i = 0; _overrides[i].id[0]; ++i) { - const uint32_t* overrideId = (const uint32_t*) _overrides[i].id; - if (*overrideId == id) { - GBALog(gba, GBA_LOG_DEBUG, "Found override for game %s!", _overrides[i].id); - switch (_overrides[i].type) { - case SAVEDATA_FLASH512: - case SAVEDATA_FLASH1M: - gba->memory.savedata.type = _overrides[i].type; - GBASavedataInitFlash(&gba->memory.savedata); - break; - case SAVEDATA_EEPROM: - GBASavedataInitEEPROM(&gba->memory.savedata); - break; - case SAVEDATA_SRAM: - GBASavedataInitSRAM(&gba->memory.savedata); - break; - case SAVEDATA_NONE: - break; + switch (immediate) { + case GBA_COMPONENT_DEBUGGER: + if (gba->debugger) { + struct DebuggerEntryInfo info = { + .address = _ARMPCAddress(cpu) + }; + ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_BREAKPOINT, &info); + } + break; + case GBA_COMPONENT_CHEAT_DEVICE: + if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { + struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]; + struct GBACheatHook* hook = 0; + size_t i; + for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { + struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i); + if (cheats->hook && cheats->hook->address == _ARMPCAddress(cpu)) { + GBACheatRefresh(device, cheats); + hook = cheats->hook; + } } + if (hook) { + ARMRunFake(cpu, hook->patchedOpcode); + } + } + break; + default: + break; + } +} - if (_overrides[i].gpio & GPIO_RTC) { - GBAGPIOInitRTC(&gba->memory.gpio); - } +void GBAFrameStarted(struct GBA* gba) { + UNUSED(gba); - if (_overrides[i].gpio & GPIO_GYRO) { - GBAGPIOInitGyro(&gba->memory.gpio); - } + struct GBAThread* thread = GBAThreadGetContext(); + if (!thread) { + return; + } - if (_overrides[i].gpio & GPIO_RUMBLE) { - GBAGPIOInitRumble(&gba->memory.gpio); + if (thread->rewindBuffer) { + --thread->rewindBufferNext; + if (thread->rewindBufferNext <= 0) { + thread->rewindBufferNext = thread->rewindBufferInterval; + GBARecordFrame(thread); + } + } +} + +void GBAFrameEnded(struct GBA* gba) { + if (gba->rr) { + gba->rr->nextFrame(gba->rr); + } + + if (gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { + struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]; + size_t i; + for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) { + struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i); + if (!cheats->hook) { + GBACheatRefresh(device, cheats); } + } + } - gba->busyLoop = _overrides[i].busyLoop; - return; + + struct GBAThread* thread = GBAThreadGetContext(); + if (!thread) { + return; + } + + if (gba->stream) { + gba->stream->postVideoFrame(gba->stream, gba->video.renderer); + } + + if (thread->frameCallback) { + thread->frameCallback(thread); + } +} + +void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { + size_t immediate; + for (immediate = 0; immediate < gba->cpu->numComponents; ++immediate) { + if (gba->cpu->components[immediate] == component) { + break; } } + if (immediate == gba->cpu->numComponents) { + return; + } + if (mode == MODE_ARM) { + int32_t value; + int32_t old; + value = 0xE1200070; + value |= immediate & 0xF; + value |= (immediate & 0xFFF0) << 4; + GBAPatch32(gba->cpu, address, value, &old); + *opcode = old; + } else { + int16_t value; + int16_t old; + value = 0xBE00; + value |= immediate & 0xFF; + GBAPatch16(gba->cpu, address, value, &old); + *opcode = (uint16_t) old; + } +} + +void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode) { + if (mode == MODE_ARM) { + GBAPatch32(gba->cpu, address, opcode, 0); + } else { + GBAPatch16(gba->cpu, address, opcode, 0); + } +} + +static bool _setSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { + GBASetBreakpoint((struct GBA*) debugger->cpu->master, &debugger->d, address, mode, opcode); + return true; +} + +static bool _clearSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t opcode) { + GBAClearBreakpoint((struct GBA*) debugger->cpu->master, address, mode, opcode); + return true; }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -11,10 +11,10 @@
#include "arm.h" #include "debugger/debugger.h" -#include "gba-memory.h" -#include "gba-video.h" -#include "gba-audio.h" -#include "gba-sio.h" +#include "gba/memory.h" +#include "gba/video.h" +#include "gba/audio.h" +#include "gba/sio.h" extern const uint32_t GBA_ARM7TDMI_FREQUENCY;@@ -33,11 +33,6 @@ IRQ_DMA2 = 0xA,
IRQ_DMA3 = 0xB, IRQ_KEYPAD = 0xC, IRQ_GAMEPAK = 0xD -}; - -enum GBAError { - GBA_NO_ERROR = 0, - GBA_OUT_OF_MEMORY = -1 }; enum GBALogLevel {@@ -73,12 +68,38 @@ GBA_KEY_R = 8,
GBA_KEY_L = 9, GBA_KEY_MAX, GBA_KEY_NONE = -1 +}; + +enum GBAComponent { + GBA_COMPONENT_DEBUGGER, + GBA_COMPONENT_CHEAT_DEVICE, + GBA_COMPONENT_MAX +}; + +enum GBAIdleLoopOptimization { + IDLE_LOOP_IGNORE = -1, + IDLE_LOOP_REMOVE = 0, + IDLE_LOOP_DETECT +}; + +enum { + SP_BASE_SYSTEM = 0x03007F00, + SP_BASE_IRQ = 0x03007FA0, + SP_BASE_SUPERVISOR = 0x03007FE0 }; struct GBA; struct GBARotationSource; +struct GBAThread; struct Patch; struct VFile; + +typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args); + +struct GBAAVStream { + void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer); + void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right); +}; struct GBATimer { uint16_t reload;@@ -106,6 +127,7 @@
struct ARMDebugger* debugger; uint32_t bus; + bool performingDMA; int timersEnabled; struct GBATimer timers[4];@@ -113,9 +135,11 @@
int springIRQ; uint32_t biosChecksum; int* keySource; - uint32_t busyLoop; struct GBARotationSource* rotationSource; + struct GBALuminanceSource* luminanceSource; + struct GBARTCSource* rtcSource; struct GBARumble* rumble; + struct GBARRContext* rr; void* pristineRom; size_t pristineRomSize;@@ -125,7 +149,17 @@ struct VFile* biosVf;
const char* activeFile; - int logLevel; + GBALogHandler logHandler; + enum GBALogLevel logLevel; + struct GBAAVStream* stream; + + enum GBAIdleLoopOptimization idleOptimization; + uint32_t idleLoop; + uint32_t lastJump; + int idleDetectionStep; + int idleDetectionFailures; + int32_t cachedRegisters[16]; + bool taintedRegisters[16]; }; struct GBACartridge {@@ -147,6 +181,7 @@ void GBACreate(struct GBA* gba);
void GBADestroy(struct GBA* gba); void GBAReset(struct ARMCore* cpu); +void GBASkipBIOS(struct ARMCore* cpu); void GBATimerUpdateRegister(struct GBA* gba, int timer); void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t value);@@ -160,14 +195,21 @@ void GBAHalt(struct GBA* gba);
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger); void GBADetachDebugger(struct GBA* gba); + +void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); +void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode); void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname); void GBALoadBIOS(struct GBA* gba, struct VFile* vf); void GBAApplyPatch(struct GBA* gba, struct Patch* patch); bool GBAIsROM(struct VFile* vf); +bool GBAIsBIOS(struct VFile* vf); void GBAGetGameCode(struct GBA* gba, char* out); void GBAGetGameTitle(struct GBA* gba, char* out); + +void GBAFrameStarted(struct GBA* gba); +void GBAFrameEnded(struct GBA* gba); __attribute__((format (printf, 3, 4))) void GBALog(struct GBA* gba, enum GBALogLevel level, const char* format, ...);
@@ -0,0 +1,457 @@
+/* Copyright (c) 2013-2015 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 "hardware.h" + +#include "gba/serialize.h" + +#include <time.h> + +static void _readPins(struct GBACartridgeHardware* hw); +static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins); + +static void _rtcReadPins(struct GBACartridgeHardware* hw); +static unsigned _rtcOutput(struct GBACartridgeHardware* hw); +static void _rtcProcessByte(struct GBACartridgeHardware* hw); +static void _rtcUpdateClock(struct GBACartridgeHardware* hw); +static unsigned _rtcBCD(unsigned value); + +static void _gyroReadPins(struct GBACartridgeHardware* hw); + +static void _rumbleReadPins(struct GBACartridgeHardware* hw); + +static void _lightReadPins(struct GBACartridgeHardware* hw); + +static const int RTC_BYTES[8] = { + 0, // Force reset + 0, // Empty + 7, // Date/Time + 0, // Force IRQ + 1, // Control register + 0, // Empty + 3, // Time + 0 // Empty +}; + +void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) { + hw->gpioBase = base; + GBAHardwareClear(hw); +} + +void GBAHardwareClear(struct GBACartridgeHardware* hw) { + hw->devices = HW_NONE; + hw->direction = GPIO_WRITE_ONLY; + hw->pinState = 0; + hw->direction = 0; +} + +void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uint16_t value) { + switch (address) { + case GPIO_REG_DATA: + hw->pinState &= ~hw->direction; + hw->pinState |= value; + _readPins(hw); + break; + case GPIO_REG_DIRECTION: + hw->direction = value; + break; + case GPIO_REG_CONTROL: + hw->readWrite = value; + break; + default: + GBALog(hw->p, GBA_LOG_WARN, "Invalid GPIO address"); + } + if (hw->readWrite) { + uint16_t old = hw->gpioBase[0]; + old &= ~hw->direction; + hw->gpioBase[0] = old | hw->pinState; + } else { + hw->gpioBase[0] = 0; + } +} + +void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) { + hw->devices |= HW_RTC; + hw->rtc.bytesRemaining = 0; + + hw->rtc.transferStep = 0; + + hw->rtc.bitsRead = 0; + hw->rtc.bits = 0; + hw->rtc.commandActive = 0; + hw->rtc.command.packed = 0; + hw->rtc.control.packed = 0x40; + memset(hw->rtc.time, 0, sizeof(hw->rtc.time)); +} + +void _readPins(struct GBACartridgeHardware* hw) { + if (hw->devices & HW_RTC) { + _rtcReadPins(hw); + } + + if (hw->devices & HW_GYRO) { + _gyroReadPins(hw); + } + + if (hw->devices & HW_RUMBLE) { + _rumbleReadPins(hw); + } + + if (hw->devices & HW_LIGHT_SENSOR) { + _lightReadPins(hw); + } +} + +void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) { + if (hw->readWrite) { + uint16_t old = hw->gpioBase[0]; + old &= hw->direction; + hw->pinState = old | (pins & ~hw->direction & 0xF); + hw->gpioBase[0] = hw->pinState; + } +} + +// == RTC + +void _rtcReadPins(struct GBACartridgeHardware* hw) { + // Transfer sequence: + // P: 0 | 1 | 2 | 3 + // == Initiate + // > HI | - | LO | - + // > HI | - | HI | - + // == Transfer bit (x8) + // > LO | x | HI | - + // > HI | - | HI | - + // < ?? | x | ?? | - + // == Terminate + // > - | - | LO | - + switch (hw->rtc.transferStep) { + case 0: + if ((hw->pinState & 5) == 1) { + hw->rtc.transferStep = 1; + } + break; + case 1: + if ((hw->pinState & 5) == 5) { + hw->rtc.transferStep = 2; + } + break; + case 2: + if (!hw->p0) { + hw->rtc.bits &= ~(1 << hw->rtc.bitsRead); + hw->rtc.bits |= hw->p1 << hw->rtc.bitsRead; + } else { + if (hw->p2) { + // GPIO direction should always != reading + if (hw->dir1) { + if (hw->rtc.command.reading) { + GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode"); + } + ++hw->rtc.bitsRead; + if (hw->rtc.bitsRead == 8) { + _rtcProcessByte(hw); + } + } else { + _outputPins(hw, 5 | (_rtcOutput(hw) << 1)); + ++hw->rtc.bitsRead; + if (hw->rtc.bitsRead == 8) { + --hw->rtc.bytesRemaining; + if (hw->rtc.bytesRemaining <= 0) { + hw->rtc.commandActive = 0; + hw->rtc.command.reading = 0; + } + hw->rtc.bitsRead = 0; + } + } + } else { + hw->rtc.bitsRead = 0; + hw->rtc.bytesRemaining = 0; + hw->rtc.commandActive = 0; + hw->rtc.command.reading = 0; + hw->rtc.transferStep = 0; + } + } + break; + } +} + +void _rtcProcessByte(struct GBACartridgeHardware* hw) { + --hw->rtc.bytesRemaining; + if (!hw->rtc.commandActive) { + union RTCCommandData command; + command.packed = hw->rtc.bits; + if (command.magic == 0x06) { + hw->rtc.command = command; + + hw->rtc.bytesRemaining = RTC_BYTES[hw->rtc.command.command]; + hw->rtc.commandActive = hw->rtc.bytesRemaining > 0; + switch (command.command) { + case RTC_RESET: + hw->rtc.control.packed = 0; + break; + case RTC_DATETIME: + case RTC_TIME: + _rtcUpdateClock(hw); + break; + case RTC_FORCE_IRQ: + case RTC_CONTROL: + break; + } + } else { + GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits); + } + } else { + switch (hw->rtc.command.command) { + case RTC_CONTROL: + hw->rtc.control.packed = hw->rtc.bits; + break; + case RTC_FORCE_IRQ: + GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", hw->rtc.command.command); + break; + case RTC_RESET: + case RTC_DATETIME: + case RTC_TIME: + break; + } + } + + hw->rtc.bits = 0; + hw->rtc.bitsRead = 0; + if (!hw->rtc.bytesRemaining) { + hw->rtc.commandActive = 0; + hw->rtc.command.reading = 0; + } +} + +unsigned _rtcOutput(struct GBACartridgeHardware* hw) { + uint8_t outputByte = 0; + switch (hw->rtc.command.command) { + case RTC_CONTROL: + outputByte = hw->rtc.control.packed; + break; + case RTC_DATETIME: + case RTC_TIME: + outputByte = hw->rtc.time[7 - hw->rtc.bytesRemaining]; + break; + case RTC_FORCE_IRQ: + case RTC_RESET: + break; + } + unsigned output = (outputByte >> hw->rtc.bitsRead) & 1; + return output; +} + +void _rtcUpdateClock(struct GBACartridgeHardware* hw) { + time_t t; + struct GBARTCSource* rtc = hw->p->rtcSource; + if (rtc) { + rtc->sample(rtc); + t = rtc->unixTime(rtc); + } else { + t = time(0); + } + struct tm date; +#ifdef _WIN32 + date = *localtime(&t); +#else + localtime_r(&t, &date); +#endif + hw->rtc.time[0] = _rtcBCD(date.tm_year - 100); + hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1); + hw->rtc.time[2] = _rtcBCD(date.tm_mday); + hw->rtc.time[3] = _rtcBCD(date.tm_wday); + if (hw->rtc.control.hour24) { + hw->rtc.time[4] = _rtcBCD(date.tm_hour); + } else { + hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12); + } + hw->rtc.time[5] = _rtcBCD(date.tm_min); + hw->rtc.time[6] = _rtcBCD(date.tm_sec); +} + +unsigned _rtcBCD(unsigned value) { + int counter = value % 10; + value /= 10; + counter += (value % 10) << 4; + return counter; +} + +// == Gyro + +void GBAHardwareInitGyro(struct GBACartridgeHardware* hw) { + hw->devices |= HW_GYRO; + hw->gyroSample = 0; + hw->gyroEdge = 0; +} + +void _gyroReadPins(struct GBACartridgeHardware* hw) { + struct GBARotationSource* gyro = hw->p->rotationSource; + if (!gyro) { + return; + } + + if (hw->p0) { + if (gyro->sample) { + gyro->sample(gyro); + } + int32_t sample = gyro->readGyroZ(gyro); + + // Normalize to ~12 bits, focused on 0x6C0 + hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative + } + + if (hw->gyroEdge && !hw->p1) { + // Write bit on falling edge + unsigned bit = hw->gyroSample >> 15; + hw->gyroSample <<= 1; + _outputPins(hw, bit << 2); + } + + hw->gyroEdge = hw->p1; +} + +// == Rumble + +void GBAHardwareInitRumble(struct GBACartridgeHardware* hw) { + hw->devices |= HW_RUMBLE; +} + +void _rumbleReadPins(struct GBACartridgeHardware* hw) { + struct GBARumble* rumble = hw->p->rumble; + if (!rumble) { + return; + } + + rumble->setRumble(rumble, hw->p3); +} + +// == Light sensor + +void GBAHardwareInitLight(struct GBACartridgeHardware* hw) { + hw->devices |= HW_LIGHT_SENSOR; + hw->lightCounter = 0; + hw->lightEdge = false; + hw->lightSample = 0xFF; +} + +void _lightReadPins(struct GBACartridgeHardware* hw) { + if (hw->p2) { + // Boktai chip select + return; + } + if (hw->p1) { + struct GBALuminanceSource* lux = hw->p->luminanceSource; + GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset"); + hw->lightCounter = 0; + if (lux) { + lux->sample(lux); + hw->lightSample = lux->readLuminance(lux); + } else { + hw->lightSample = 0xFF; + } + } + if (hw->p0 && hw->lightEdge) { + ++hw->lightCounter; + } + hw->lightEdge = !hw->p0; + + bool sendBit = hw->lightCounter >= hw->lightSample; + _outputPins(hw, sendBit << 3); + GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Output %u with pins %u", hw->lightCounter, hw->pinState); +} + +// == Tilt + +void GBAHardwareInitTilt(struct GBACartridgeHardware* hw) { + hw->devices |= HW_TILT; + hw->tiltX = 0xFFF; + hw->tiltY = 0xFFF; + hw->tiltState = 0; +} + +void GBAHardwareTiltWrite(struct GBACartridgeHardware* hw, uint32_t address, uint8_t value) { + switch (address) { + case 0x8000: + if (value == 0x55) { + hw->tiltState = 1; + } else { + GBALog(hw->p, GBA_LOG_GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value); + } + break; + case 0x8100: + if (value == 0xAA && hw->tiltState == 1) { + hw->tiltState = 0; + struct GBARotationSource* rotationSource = hw->p->rotationSource; + if (!rotationSource || !rotationSource->readTiltX || !rotationSource->readTiltY) { + return; + } + if (rotationSource->sample) { + rotationSource->sample(rotationSource); + } + int32_t x = rotationSource->readTiltX(rotationSource); + int32_t y = rotationSource->readTiltY(rotationSource); + // Normalize to ~12 bits, focused on 0x3A0 + hw->tiltX = (x >> 21) + 0x3A0; // Crop off an extra bit so that we can't go negative + hw->tiltY = (y >> 21) + 0x3A0; + } else { + GBALog(hw->p, GBA_LOG_GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value); + } + break; + default: + GBALog(hw->p, GBA_LOG_GAME_ERROR, "Invalid tilt sensor write to %04x: %02x", address, value); + break; + } +} + +uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) { + switch (address) { + case 0x8200: + return hw->tiltX & 0xFF; + case 0x8300: + return ((hw->tiltX >> 8) & 0xF) | 0x80; + case 0x8400: + return hw->tiltY & 0xFF; + case 0x8500: + return (hw->tiltY >> 8) & 0xF; + default: + GBALog(hw->p, GBA_LOG_GAME_ERROR, "Invalid tilt sensor read from %04x", address); + break; + } + return 0xFF; +} + +// == Serialization + +void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) { + state->hw.readWrite = hw->readWrite; + state->hw.pinState = hw->pinState; + state->hw.pinDirection = hw->direction; + state->hw.devices = hw->devices; + state->hw.rtc = hw->rtc; + state->hw.gyroSample = hw->gyroSample; + state->hw.gyroEdge = hw->gyroEdge; + state->hw.tiltSampleX = hw->tiltX; + state->hw.tiltSampleY = hw->tiltY; + state->hw.tiltState = hw->tiltState; + state->hw.lightCounter = hw->lightCounter; + state->hw.lightSample = hw->lightSample; + state->hw.lightEdge = hw->lightEdge; +} + +void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) { + hw->readWrite = state->hw.readWrite; + hw->pinState = state->hw.pinState; + hw->direction = state->hw.pinDirection; + // TODO: Deterministic RTC + hw->rtc = state->hw.rtc; + hw->gyroSample = state->hw.gyroSample; + hw->gyroEdge = state->hw.gyroEdge; + hw->tiltX = state->hw.tiltSampleX; + hw->tiltY = state->hw.tiltSampleY; + hw->tiltState = state->hw.tiltState; + hw->lightCounter = state->hw.lightCounter; + hw->lightSample = state->hw.lightSample; + hw->lightEdge = state->hw.lightEdge; +}
@@ -0,0 +1,155 @@
+/* Copyright (c) 2013-2015 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 GBA_HARDWARE_H +#define GBA_HARDWARE_H + +#include "util/common.h" + +#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL) + +struct GBARotationSource { + void (*sample)(struct GBARotationSource*); + + int32_t (*readTiltX)(struct GBARotationSource*); + int32_t (*readTiltY)(struct GBARotationSource*); + + int32_t (*readGyroZ)(struct GBARotationSource*); +}; + +struct GBALuminanceSource { + void (*sample)(struct GBALuminanceSource*); + + uint8_t (*readLuminance)(struct GBALuminanceSource*); +}; + +struct GBARTCSource { + void (*sample)(struct GBARTCSource*); + + time_t (*unixTime)(struct GBARTCSource*); +}; + +enum GBAHardwareDevice { + HW_NO_OVERRIDE = 0x8000, + HW_NONE = 0, + HW_RTC = 1, + HW_RUMBLE = 2, + HW_LIGHT_SENSOR = 4, + HW_GYRO = 8, + HW_TILT = 16 +}; + +enum GPIORegister { + GPIO_REG_DATA = 0xC4, + GPIO_REG_DIRECTION = 0xC6, + GPIO_REG_CONTROL = 0xC8 +}; + +enum GPIODirection { + GPIO_WRITE_ONLY = 0, + GPIO_READ_WRITE = 1 +}; + +union RTCControl { + struct { + unsigned : 3; + unsigned minIRQ : 1; + unsigned : 2; + unsigned hour24 : 1; + unsigned poweroff : 1; + }; + uint8_t packed; +}; + +enum RTCCommand { + RTC_RESET = 0, + RTC_DATETIME = 2, + RTC_FORCE_IRQ = 3, + RTC_CONTROL = 4, + RTC_TIME = 6 +}; + +union RTCCommandData { + struct { + unsigned magic : 4; + enum RTCCommand command : 3; + unsigned reading : 1; + }; + uint8_t packed; +}; + +struct GBARTC { + int bytesRemaining; + int transferStep; + int bitsRead; + int bits; + int commandActive; + union RTCCommandData command; + union RTCControl control; + uint8_t time[7]; +} __attribute__((packed)); + +struct GBARumble { + void (*setRumble)(struct GBARumble*, int enable); +}; + +struct GBACartridgeHardware { + struct GBA* p; + int devices; + enum GPIODirection readWrite; + uint16_t* gpioBase; + + union { + struct { + unsigned p0 : 1; + unsigned p1 : 1; + unsigned p2 : 1; + unsigned p3 : 1; + }; + uint16_t pinState; + }; + + union { + struct { + unsigned dir0 : 1; + unsigned dir1 : 1; + unsigned dir2 : 1; + unsigned dir3 : 1; + }; + uint16_t direction; + }; + + struct GBARTC rtc; + + uint16_t gyroSample; + bool gyroEdge; + + unsigned lightCounter : 12; + uint8_t lightSample; + bool lightEdge; + + uint16_t tiltX; + uint16_t tiltY; + int tiltState; +}; + +void GBAHardwareInit(struct GBACartridgeHardware* gpio, uint16_t* gpioBase); +void GBAHardwareClear(struct GBACartridgeHardware* gpio); + +void GBAHardwareInitRTC(struct GBACartridgeHardware* gpio); +void GBAHardwareInitGyro(struct GBACartridgeHardware* gpio); +void GBAHardwareInitRumble(struct GBACartridgeHardware* gpio); +void GBAHardwareInitLight(struct GBACartridgeHardware* gpio); +void GBAHardwareInitTilt(struct GBACartridgeHardware* gpio); + +void GBAHardwareGPIOWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint16_t value); +void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, uint8_t value); +uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address); + +struct GBASerializedState; +void GBAHardwareSerialize(const struct GBACartridgeHardware* gpio, struct GBASerializedState* state); +void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, const struct GBASerializedState* state); + +#endif
@@ -1,30 +1,27 @@
-/* Copyright (c) 2013-2014 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 "hle-bios.h" -#include "gba-memory.h" +#include "gba/memory.h" const uint8_t hleBios[SIZE_BIOS] = { - 0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x05, 0x00, 0x00, 0xea, + 0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x07, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1, - 0x24, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3, - 0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02, - 0x00, 0x58, 0x2d, 0xe9, 0x02, 0xb0, 0x5e, 0xe5, 0x7c, 0xc0, 0xa0, 0xe3, - 0x0b, 0xb1, 0x9c, 0xe7, 0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1, - 0x00, 0x10, 0x2d, 0xe9, 0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3, - 0x0c, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1, - 0x1b, 0xff, 0x2f, 0x11, 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, - 0x00, 0x10, 0xbd, 0xe8, 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, - 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, - 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0x00, 0x00, 0x5d, 0xe3, + 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02, 0x00, 0x58, 0x2d, 0xe9, + 0x02, 0xb0, 0x5e, 0xe5, 0x8c, 0xc0, 0xa0, 0xe3, 0x0b, 0xb1, 0x9c, 0xe7, + 0x00, 0x00, 0x5b, 0xe3, 0x00, 0xc0, 0x4f, 0xe1, 0x00, 0x10, 0x2d, 0xe9, + 0x80, 0xc0, 0x0c, 0xe2, 0x1f, 0xc0, 0x8c, 0xe3, 0x0c, 0xf0, 0x29, 0xe1, + 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1, 0x1b, 0xff, 0x2f, 0x11, + 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3, 0x00, 0x10, 0xbd, 0xe8, + 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x58, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe8, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x01, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9, - 0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5, - 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x01, 0x00, 0xa0, 0xe3, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, + 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, + 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, + 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5, 0x01, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0xc3, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3, 0x03, 0x00, 0x00, 0x0a, 0xb8, 0x30, 0x5c, 0xe1, 0x01, 0x30, 0xc3, 0xe1,
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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
@@ -13,6 +13,6 @@
hle-bios.c: hle-bios.bin echo '#include "hle-bios.h"' > $@ echo >> $@ - echo '#include "gba-memory.h"' >> $@ + echo '#include "gba/memory.h"' >> $@ echo >> $@ xxd -i $< | sed -e 's/unsigned char hle_bios_bin\[\]/const uint8_t hleBios[SIZE_BIOS]/' | grep -v hle_bios_bin_len >> $@
@@ -18,6 +18,8 @@ b fiqBase
resetBase: mov pc, #0x8000000 +.word 0 +.word 0xE129F000 swiBase: cmp sp, #0@@ -42,6 +44,8 @@ ldmfd sp!, {r12}
msr spsr, r12 ldmfd sp!, {r11-r12, lr} movs pc, lr +.word 0 +.word 0xE3A02004 swiTable: .word SoftReset@@ -66,6 +70,8 @@ add lr, pc, #0
ldr pc, [r0, #-4] ldmfd sp!, {r0-r3, r12, lr} subs pc, lr, #4 +.word 0 +.word 0xE55EC002 VBlankIntrWait: mov r0, #1
@@ -0,0 +1,472 @@
+/* Copyright (c) 2013-2015 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 "input.h" + +#include "util/configuration.h" +#include "util/table.h" + +#include <inttypes.h> + +#define SECTION_NAME_MAX 128 +#define KEY_NAME_MAX 32 +#define KEY_VALUE_MAX 16 +#define AXIS_INFO_MAX 12 + +struct GBAInputMapImpl { + int* map; + uint32_t type; + + struct Table axes; +}; + +struct GBAAxisSave { + struct Configuration* config; + uint32_t type; +}; + +struct GBAAxisEnumerate { + void (*handler)(int axis, const struct GBAAxis* description, void* user); + void* user; +}; + +const char* GBAKeyNames[] = { + "A", + "B", + "Select", + "Start", + "Right", + "Left", + "Up", + "Down", + "R", + "L" +}; + +static bool _getIntValue(const struct Configuration* config, const char* section, const char* key, int* value) { + const char* strValue = ConfigurationGetValue(config, section, key); + if (!strValue) { + return false; + } + char* end; + long intValue = strtol(strValue, &end, 10); + if (*end) { + return false; + } + *value = intValue; + return true; +} + +static struct GBAInputMapImpl* _lookupMap(struct GBAInputMap* map, uint32_t type) { + size_t m; + struct GBAInputMapImpl* impl = 0; + for (m = 0; m < map->numMaps; ++m) { + if (map->maps[m].type == type) { + impl = &map->maps[m]; + break; + } + } + return impl; +} + +static const struct GBAInputMapImpl* _lookupMapConst(const struct GBAInputMap* map, uint32_t type) { + size_t m; + const struct GBAInputMapImpl* impl = 0; + for (m = 0; m < map->numMaps; ++m) { + if (map->maps[m].type == type) { + impl = &map->maps[m]; + break; + } + } + return impl; +} + +static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t type) { + struct GBAInputMapImpl* impl = 0; + if (map->numMaps == 0) { + map->maps = malloc(sizeof(*map->maps)); + map->numMaps = 1; + impl = &map->maps[0]; + impl->type = type; + impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); + TableInit(&impl->axes, 2, free); + } else { + impl = _lookupMap(map, type); + } + if (!impl) { + size_t m; + for (m = 0; m < map->numMaps; ++m) { + if (!map->maps[m].type) { + impl = &map->maps[m]; + break; + } + } + if (impl) { + impl->type = type; + impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); + } else { + map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2); + for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) { + map->maps[m].type = 0; + map->maps[m].map = 0; + } + map->numMaps *= 2; + impl = &map->maps[m]; + impl->type = type; + impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey)); + } + TableInit(&impl->axes, 2, free); + } + return impl; +} + +static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) { + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + + char keyKey[KEY_NAME_MAX]; + snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName); + keyKey[KEY_NAME_MAX - 1] = '\0'; + + int value; + if (!_getIntValue(config, sectionName, keyKey, &value)) { + return; + } + GBAInputBindKey(map, type, value, key); +} + +static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey direction, const char* axisName) { + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + + char axisKey[KEY_NAME_MAX]; + snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + int value; + if (!_getIntValue(config, sectionName, axisKey, &value)) { + return; + } + + snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", axisName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + int axis; + const char* strValue = ConfigurationGetValue(config, sectionName, axisKey); + if (!strValue || !strValue[0]) { + return; + } + char* end; + axis = strtoul(&strValue[1], &end, 10); + if (*end) { + return; + } + + const struct GBAAxis* description = GBAInputQueryAxis(map, type, axis); + struct GBAAxis realDescription = { GBA_KEY_NONE, GBA_KEY_NONE, 0, 0 }; + if (description) { + realDescription = *description; + } + if (strValue[0] == '+') { + realDescription.deadHigh = value; + realDescription.highDirection = direction; + } else if (strValue[0] == '-') { + realDescription.deadLow = value; + realDescription.lowDirection = direction; + } + GBAInputBindAxis(map, type, axis, &realDescription); +} + +static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) { + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + + char keyKey[KEY_NAME_MAX]; + snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName); + keyKey[KEY_NAME_MAX - 1] = '\0'; + + int value = GBAInputQueryBinding(map, type, key); + char keyValue[KEY_VALUE_MAX]; + snprintf(keyValue, KEY_VALUE_MAX, "%" PRIi32, value); + + ConfigurationSetValue(config, sectionName, keyKey, keyValue); +} + +static void _clearAxis(uint32_t type, struct Configuration* config, const char* axisName) { + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + + char axisKey[KEY_NAME_MAX]; + snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + ConfigurationClearValue(config, sectionName, axisKey); + + snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", axisName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + ConfigurationClearValue(config, sectionName, axisKey); +} + +static void _saveAxis(uint32_t axis, void* dp, void* up) { + struct GBAAxisSave* user = up; + const struct GBAAxis* description = dp; + + uint32_t type = user->type; + char sectionName[SECTION_NAME_MAX]; + snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type); + sectionName[SECTION_NAME_MAX - 1] = '\0'; + + if (description->lowDirection != GBA_KEY_NONE) { + const char* keyName = GBAKeyNames[description->lowDirection]; + + char axisKey[KEY_NAME_MAX]; + snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", keyName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + ConfigurationSetIntValue(user->config, sectionName, axisKey, description->deadLow); + + snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", keyName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + + char axisInfo[AXIS_INFO_MAX]; + snprintf(axisInfo, AXIS_INFO_MAX, "-%u", axis); + axisInfo[AXIS_INFO_MAX - 1] = '\0'; + ConfigurationSetValue(user->config, sectionName, axisKey, axisInfo); + } + if (description->highDirection != GBA_KEY_NONE) { + const char* keyName = GBAKeyNames[description->highDirection]; + + char axisKey[KEY_NAME_MAX]; + snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", keyName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + ConfigurationSetIntValue(user->config, sectionName, axisKey, description->deadHigh); + + snprintf(axisKey, KEY_NAME_MAX, "axis%sAxis", keyName); + axisKey[KEY_NAME_MAX - 1] = '\0'; + + char axisInfo[AXIS_INFO_MAX]; + snprintf(axisInfo, AXIS_INFO_MAX, "+%u", axis); + axisInfo[AXIS_INFO_MAX - 1] = '\0'; + ConfigurationSetValue(user->config, sectionName, axisKey, axisInfo); + } +} + +void _enumerateAxis(uint32_t axis, void* dp, void* ep) { + struct GBAAxisEnumerate* enumUser = ep; + const struct GBAAxis* description = dp; + enumUser->handler(axis, description, enumUser->user); +} + +void _unbindAxis(uint32_t axis, void* dp, void* user) { + UNUSED(axis); + enum GBAKey* key = user; + struct GBAAxis* description = dp; + if (description->highDirection == *key) { + description->highDirection = GBA_KEY_NONE; + } + if (description->lowDirection == *key) { + description->lowDirection = GBA_KEY_NONE; + } +} + +void GBAInputMapInit(struct GBAInputMap* map) { + map->maps = 0; + map->numMaps = 0; +} + +void GBAInputMapDeinit(struct GBAInputMap* map) { + size_t m; + for (m = 0; m < map->numMaps; ++m) { + if (map->maps[m].type) { + free(map->maps[m].map); + TableDeinit(&map->maps[m].axes); + } + } + free(map->maps); + map->maps = 0; + map->numMaps = 0; +} + +enum GBAKey GBAInputMapKey(const struct GBAInputMap* map, uint32_t type, int key) { + size_t m; + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl || !impl->map) { + return GBA_KEY_NONE; + } + + for (m = 0; m < GBA_KEY_MAX; ++m) { + if (impl->map[m] == key) { + return m; + } + } + return GBA_KEY_NONE; +} + +void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) { + struct GBAInputMapImpl* impl = _guaranteeMap(map, type); + GBAInputUnbindKey(map, type, input); + impl->map[input] = key; +} + +void GBAInputUnbindKey(struct GBAInputMap* map, uint32_t type, enum GBAKey input) { + struct GBAInputMapImpl* impl = _lookupMap(map, type); + if (input < 0 || input >= GBA_KEY_MAX) { + return; + } + if (impl) { + impl->map[input] = GBA_NO_MAPPING; + } + TableEnumerate(&impl->axes, _unbindAxis, &input); +} + +int GBAInputQueryBinding(const struct GBAInputMap* map, uint32_t type, enum GBAKey input) { + if (input >= GBA_KEY_MAX) { + return 0; + } + + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl || !impl->map) { + return 0; + } + + return impl->map[input]; +} + +enum GBAKey GBAInputMapAxis(const struct GBAInputMap* map, uint32_t type, int axis, int value) { + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl) { + return GBA_KEY_NONE; + } + struct GBAAxis* description = TableLookup(&impl->axes, axis); + if (!description) { + return GBA_KEY_NONE; + } + int state = 0; + if (value < description->deadLow) { + state = -1; + } else if (value > description->deadHigh) { + state = 1; + } + if (state > 0) { + return description->highDirection; + } + if (state < 0) { + return description->lowDirection; + } + return GBA_KEY_NONE; +} + +int GBAInputClearAxis(const struct GBAInputMap* map, uint32_t type, int axis, int keys) { + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl) { + return keys; + } + struct GBAAxis* description = TableLookup(&impl->axes, axis); + if (!description) { + return keys; + } + return keys &= ~((1 << description->highDirection) | (1 << description->lowDirection)); +} + +void GBAInputBindAxis(struct GBAInputMap* map, uint32_t type, int axis, const struct GBAAxis* description) { + struct GBAInputMapImpl* impl = _guaranteeMap(map, type); + struct GBAAxis* dup = malloc(sizeof(struct GBAAxis)); + GBAInputUnbindKey(map, type, description->lowDirection); + GBAInputUnbindKey(map, type, description->highDirection); + *dup = *description; + TableInsert(&impl->axes, axis, dup); +} + +void GBAInputUnbindAxis(struct GBAInputMap* map, uint32_t type, int axis) { + struct GBAInputMapImpl* impl = _lookupMap(map, type); + if (impl) { + TableRemove(&impl->axes, axis); + } +} + +void GBAInputUnbindAllAxes(struct GBAInputMap* map, uint32_t type) { + struct GBAInputMapImpl* impl = _lookupMap(map, type); + if (impl) { + TableClear(&impl->axes); + } +} + +const struct GBAAxis* GBAInputQueryAxis(const struct GBAInputMap* map, uint32_t type, int axis) { + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl) { + return 0; + } + return TableLookup(&impl->axes, axis); +} + +void GBAInputEnumerateAxes(const struct GBAInputMap* map, uint32_t type, void (handler(int axis, const struct GBAAxis* description, void* user)), void* user) { + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl) { + return; + } + struct GBAAxisEnumerate enumUser = { + handler, + user + }; + TableEnumerate(&impl->axes, _enumerateAxis, &enumUser); +} + +void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) { + _loadKey(map, type, config, GBA_KEY_A, "A"); + _loadKey(map, type, config, GBA_KEY_B, "B"); + _loadKey(map, type, config, GBA_KEY_L, "L"); + _loadKey(map, type, config, GBA_KEY_R, "R"); + _loadKey(map, type, config, GBA_KEY_START, "Start"); + _loadKey(map, type, config, GBA_KEY_SELECT, "Select"); + _loadKey(map, type, config, GBA_KEY_UP, "Up"); + _loadKey(map, type, config, GBA_KEY_DOWN, "Down"); + _loadKey(map, type, config, GBA_KEY_LEFT, "Left"); + _loadKey(map, type, config, GBA_KEY_RIGHT, "Right"); + + _loadAxis(map, type, config, GBA_KEY_A, "A"); + _loadAxis(map, type, config, GBA_KEY_B, "B"); + _loadAxis(map, type, config, GBA_KEY_L, "L"); + _loadAxis(map, type, config, GBA_KEY_R, "R"); + _loadAxis(map, type, config, GBA_KEY_START, "Start"); + _loadAxis(map, type, config, GBA_KEY_SELECT, "Select"); + _loadAxis(map, type, config, GBA_KEY_UP, "Up"); + _loadAxis(map, type, config, GBA_KEY_DOWN, "Down"); + _loadAxis(map, type, config, GBA_KEY_LEFT, "Left"); + _loadAxis(map, type, config, GBA_KEY_RIGHT, "Right"); +} + +void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) { + _saveKey(map, type, config, GBA_KEY_A, "A"); + _saveKey(map, type, config, GBA_KEY_B, "B"); + _saveKey(map, type, config, GBA_KEY_L, "L"); + _saveKey(map, type, config, GBA_KEY_R, "R"); + _saveKey(map, type, config, GBA_KEY_START, "Start"); + _saveKey(map, type, config, GBA_KEY_SELECT, "Select"); + _saveKey(map, type, config, GBA_KEY_UP, "Up"); + _saveKey(map, type, config, GBA_KEY_DOWN, "Down"); + _saveKey(map, type, config, GBA_KEY_LEFT, "Left"); + _saveKey(map, type, config, GBA_KEY_RIGHT, "Right"); + + _clearAxis(type, config, "A"); + _clearAxis(type, config, "B"); + _clearAxis(type, config, "L"); + _clearAxis(type, config, "R"); + _clearAxis(type, config, "Start"); + _clearAxis(type, config, "Select"); + _clearAxis(type, config, "Up"); + _clearAxis(type, config, "Down"); + _clearAxis(type, config, "Left"); + _clearAxis(type, config, "Right"); + + const struct GBAInputMapImpl* impl = _lookupMapConst(map, type); + if (!impl) { + return; + } + struct GBAAxisSave save = { + config, + type + }; + TableEnumerate(&impl->axes, _saveAxis, &save); +}
@@ -0,0 +1,48 @@
+/* Copyright (c) 2013-2015 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 GBA_INPUT_H +#define GBA_INPUT_H + +#include "gba/gba.h" + +struct Configuration; + +struct GBAInputMap { + struct GBAInputMapImpl* maps; + size_t numMaps; +}; + +struct GBAAxis { + enum GBAKey highDirection; + enum GBAKey lowDirection; + int32_t deadHigh; + int32_t deadLow; +}; + +#define GBA_NO_MAPPING -1 + +extern const char* GBAKeyNames[]; + +void GBAInputMapInit(struct GBAInputMap*); +void GBAInputMapDeinit(struct GBAInputMap*); + +enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key); +void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input); +void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, int key); +int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input); + +enum GBAKey GBAInputMapAxis(const struct GBAInputMap*, uint32_t type, int axis, int value); +int GBAInputClearAxis(const struct GBAInputMap*, uint32_t type, int axis, int keys); +void GBAInputBindAxis(struct GBAInputMap*, uint32_t type, int axis, const struct GBAAxis* description); +void GBAInputUnbindAxis(struct GBAInputMap*, uint32_t type, int axis); +void GBAInputUnbindAllAxes(struct GBAInputMap*, uint32_t type); +const struct GBAAxis* GBAInputQueryAxis(const struct GBAInputMap*, uint32_t type, int axis); +void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handler(int axis, const struct GBAAxis* description, void* user)), void* user); + +void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*); +void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*); + +#endif
@@ -1,280 +0,0 @@
-#include "video-glsl.h" - -#include "gba-io.h" - -#include <string.h> - -#define UNIFORM_LOCATION(UNIFORM) (glGetUniformLocation(glslRenderer->program, UNIFORM)) - -static const GLfloat _vertices[4] = { - -1, 0, - 1, 0 -}; - -static const GLchar* _fragmentShader = - "varying float x;\n" - "uniform float y;\n" - "uniform sampler2D vram;\n" - "uniform float dispcnt;\n" - "uniform float bg0cnt;\n" - "uniform float bg1cnt;\n" - "uniform float bg2cnt;\n" - "uniform float bg3cnt;\n" - "uniform float bg0hofs;\n" - "uniform float bg0vofs;\n" - "uniform float bg1hofs;\n" - "uniform float bg1vofs;\n" - "uniform float bg2hofs;\n" - "uniform float bg2vofs;\n" - "uniform float bg3hofs;\n" - "uniform float bg3vofs;\n" - "#define PALETTE_INDEX(i) texture2D(vram, (vec2(mod(i + 1.0, 512.0) / 512.0 - 1.0 / 1024.0, (floor(i / 512.0) + 1.0) / 256.0 - 1.0 / 1024.0)))\n" - "#define VRAM_INDEX(i) texture2D(vram, (vec2(mod(i + 1.0, 512.0) / 512.0 - 1.0 / 1024.0, (floor(i / 512.0) + 161.0) / 256.0 - 1.0 / 1024.0)))\n" - "#define DESERIALIZE(vec) dot(vec4(63488.0, 1984.0, 62.0, 1.0), vec)\n" - "#define IMOD(a, b) mod(floor(a), b)\n" - "#define BIT_CHECK(a, b) (IMOD(a / b, 2.0) > 0.0)\n" - "#define DEBUG(fl) return vec4(fract(fl / 256.0), fract(floor(fl / 256.0) / 256.0), fract(floor(fl / 65536.0) / 256.0), 1.0)\n" - - "vec4 backgroundMode0(float bgcnt, float hofs, float vofs) {\n" - " float charBase = IMOD(bgcnt / 4.0, 4.0) * 8192.0;\n" - " float screenBase = IMOD(bgcnt / 256.0, 32.0) * 1024.0;\n" - " float size = IMOD(bgcnt / 16384.0, 4.0);\n" - " vec2 local = vec2(hofs + x, vofs + y);\n" - " vec2 base = IMOD(local, 256.0);\n" - " base -= IMOD(local, 8.0);\n" - " if (size == 1.0) {\n" - " base.x += IMOD(local.x / 256.0, 2.0) * 8192.0;\n" - " } else if (size == 2.0) {\n" - " base.y += IMOD(local.y / 256.0, 2.0) * 256.0;\n" - " } else if (size == 3.0) {\n" - " base += IMOD(local / 256.0, 2.0) * vec2(8192.0, 512.0);\n" - " }\n" - " screenBase += dot(base, vec2(1.0 / 8.0, 4.0));\n" - " float mapData = DESERIALIZE(VRAM_INDEX(screenBase));\n" - " charBase += IMOD(mapData, 1024.0) * 16.0 + dot(IMOD(local * vec2(1.0 / 4.0, 1.0), vec2(2.0, 8.0)), vec2(1.0, 2.0));\n" - " float tileData = DESERIALIZE(VRAM_INDEX(charBase));\n" - " tileData *= pow(0.5, IMOD(local.x, 4.0) * 4.0);\n" - " tileData = IMOD(tileData, 16.0);\n" - " if (tileData == 0.0) {\n" - " return vec4(0, 0, 0, 0);\n" - " }\n" - " return PALETTE_INDEX(tileData + floor(mapData / 4096.0) * 16.0);\n" - "}\n" - - "void runPriority(float priority, inout vec4 color) {\n" - " if (color.a > 0.0) {\n" - " return;\n" - " }\n" - " if (BIT_CHECK(dispcnt, 256.0) && IMOD(bg0cnt, 4.0) == priority) {\n" - " color = backgroundMode0(bg0cnt, bg0hofs, bg0vofs);\n" - " }\n" - " if (color.a > 0.0) {\n" - " return;\n" - " }\n" - " if (BIT_CHECK(dispcnt, 512.0) && IMOD(bg1cnt, 4.0) == priority) {\n" - " color = backgroundMode0(bg1cnt, bg1hofs, bg1vofs);\n" - " }\n" - " if (color.a > 0.0) {\n" - " return;\n" - " }\n" - " if (BIT_CHECK(dispcnt, 1024.0) && IMOD(bg2cnt, 4.0) == priority) {\n" - " color = backgroundMode0(bg2cnt, bg2hofs, bg2vofs);\n" - " }\n" - " if (color.a > 0.0) {\n" - " return;\n" - " }\n" - " if (BIT_CHECK(dispcnt, 2048.0) && IMOD(bg3cnt, 4.0) == priority) {\n" - " color = backgroundMode0(bg3cnt, bg3hofs, bg3vofs);\n" - " }\n" - "}\n" - - "void main() {\n" - " vec4 color = vec4(0.0, 0.0, 0.0, 0.0);\n" - " runPriority(0.0, color);\n" - " runPriority(1.0, color);\n" - " runPriority(2.0, color);\n" - " runPriority(3.0, color);\n" - " if (color.a == 0.0) {\n" - " color = texture2D(vram, vec2(0.0, y / 256.0));\n" - " }\n" - " gl_FragColor = color;\n" - "}\n"; - -static const GLchar* _vertexShader[] = { - "varying float x;", - "attribute vec2 vert;", - "uniform float y;", - - "void main() {", - " x = vert.x * 120.0 + 120.0;", - " gl_Position = vec4(vert.x, 1.0 - (y + 1.0) / 80.0, 0, 1.0);", - "}" -}; - -static void GBAVideoGLSLRendererInit(struct GBAVideoRenderer* renderer); -static void GBAVideoGLSLRendererDeinit(struct GBAVideoRenderer* renderer); -static void GBAVideoGLSLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); -static uint16_t GBAVideoGLSLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); -static void GBAVideoGLSLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y); -static void GBAVideoGLSLRendererFinishFrame(struct GBAVideoRenderer* renderer); - -void GBAVideoGLSLRendererCreate(struct GBAVideoGLSLRenderer* glslRenderer) { - glslRenderer->d.init = GBAVideoGLSLRendererInit; - glslRenderer->d.deinit = GBAVideoGLSLRendererDeinit; - glslRenderer->d.writeVideoRegister = GBAVideoGLSLRendererWriteVideoRegister; - glslRenderer->d.writePalette = GBAVideoGLSLRendererWritePalette; - glslRenderer->d.drawScanline = GBAVideoGLSLRendererDrawScanline; - glslRenderer->d.finishFrame = GBAVideoGLSLRendererFinishFrame; - - glslRenderer->d.turbo = 0; - glslRenderer->d.framesPending = 0; - glslRenderer->d.frameskip = 0; - - glslRenderer->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glslRenderer->vertexShader = glCreateShader(GL_VERTEX_SHADER); - glslRenderer->program = glCreateProgram(); - - glShaderSource(glslRenderer->fragmentShader, 1, (const GLchar**) &_fragmentShader, 0); - glShaderSource(glslRenderer->vertexShader, 7, _vertexShader, 0); - - glAttachShader(glslRenderer->program, glslRenderer->vertexShader); - glAttachShader(glslRenderer->program, glslRenderer->fragmentShader); - char log[1024]; - glCompileShader(glslRenderer->fragmentShader); - glCompileShader(glslRenderer->vertexShader); - glGetShaderInfoLog(glslRenderer->fragmentShader, 1024, 0, log); - glGetShaderInfoLog(glslRenderer->vertexShader, 1024, 0, log); - glLinkProgram(glslRenderer->program); - - glGenTextures(1, &glslRenderer->vramTexture); - glBindTexture(GL_TEXTURE_2D, glslRenderer->vramTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - memset(glslRenderer->vram, 0, sizeof (glslRenderer->vram)); - - { - pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - glslRenderer->mutex = mutex; - pthread_cond_t cond = PTHREAD_COND_INITIALIZER; - glslRenderer->upCond = cond; - glslRenderer->downCond = cond; - } -} - -void GBAVideoGLSLRendererProcessEvents(struct GBAVideoGLSLRenderer* glslRenderer) { - glUseProgram(glslRenderer->program); - glUniform1i(UNIFORM_LOCATION("vram"), 0); - glUniform1f(UNIFORM_LOCATION("dispcnt"), glslRenderer->io[0][REG_DISPCNT >> 1]); - glUniform1f(UNIFORM_LOCATION("bg0cnt"), glslRenderer->io[0][REG_BG0CNT >> 1]); - glUniform1f(UNIFORM_LOCATION("bg1cnt"), glslRenderer->io[0][REG_BG1CNT >> 1]); - glUniform1f(UNIFORM_LOCATION("bg2cnt"), glslRenderer->io[0][REG_BG2CNT >> 1]); - glUniform1f(UNIFORM_LOCATION("bg3cnt"), glslRenderer->io[0][REG_BG3CNT >> 1]); - glUniform1f(UNIFORM_LOCATION("bg0hofs"), glslRenderer->io[0][REG_BG0HOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg0vofs"), glslRenderer->io[0][REG_BG0VOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg1hofs"), glslRenderer->io[0][REG_BG1HOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg1vofs"), glslRenderer->io[0][REG_BG1VOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg2hofs"), glslRenderer->io[0][REG_BG2HOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg2vofs"), glslRenderer->io[0][REG_BG2VOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg3hofs"), glslRenderer->io[0][REG_BG3HOFS >> 1]); - glUniform1f(UNIFORM_LOCATION("bg3vofs"), glslRenderer->io[0][REG_BG3VOFS >> 1]); - - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, glslRenderer->vramTexture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, glslRenderer->vram); - - GLuint location = glGetAttribLocation(glslRenderer->program, "vert"); - glEnableVertexAttribArray(location); - glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, _vertices); - int y; - for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { - glUniform1f(UNIFORM_LOCATION("y"), y); - glDrawArrays(GL_LINES, 0, 2); - } - glDisableVertexAttribArray(location); - glFlush(); -} - -static void GBAVideoGLSLRendererInit(struct GBAVideoRenderer* renderer) { - struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer; - - glslRenderer->state = GLSL_NONE; - glslRenderer->y = 0; - - glslRenderer->oldVram = renderer->vram; - renderer->vram = &glslRenderer->vram[512 * 160]; - - pthread_mutex_init(&glslRenderer->mutex, 0); - pthread_cond_init(&glslRenderer->upCond, 0); - pthread_cond_init(&glslRenderer->downCond, 0); -} - -static void GBAVideoGLSLRendererDeinit(struct GBAVideoRenderer* renderer) { - struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer; - - /*glDeleteShader(glslRenderer->fragmentShader); - glDeleteShader(glslRenderer->vertexShader); - glDeleteProgram(glslRenderer->program); - - glDeleteTextures(1, &glslRenderer->paletteTexture);*/ - - renderer->vram = glslRenderer->oldVram; - - pthread_mutex_lock(&glslRenderer->mutex); - pthread_cond_broadcast(&glslRenderer->upCond); - pthread_mutex_unlock(&glslRenderer->mutex); - - pthread_mutex_destroy(&glslRenderer->mutex); - pthread_cond_destroy(&glslRenderer->upCond); - pthread_cond_destroy(&glslRenderer->downCond); -} - -static void GBAVideoGLSLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { - struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer; - GLshort color = 1; - color |= (value & 0x001F) << 11; - color |= (value & 0x03E0) << 1; - color |= (value & 0x7C00) >> 9; - glslRenderer->vram[(address >> 1) + glslRenderer->y * 512] = color; -} - -static uint16_t GBAVideoGLSLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { - struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer; - glslRenderer->io[glslRenderer->y][address >> 1] = value; - - return value; -} - -static void GBAVideoGLSLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { - struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer; - - glslRenderer->y = y + 1; - if (y + 1 < VIDEO_VERTICAL_PIXELS) { - memcpy(&glslRenderer->vram[(y + 1) * 512], &glslRenderer->vram[y * 512], 1024); - memcpy(glslRenderer->io[y + 1], glslRenderer->io[y], sizeof(*glslRenderer->io)); - } else { - glslRenderer->y = 0; - memcpy(&glslRenderer->vram[0], &glslRenderer->vram[y * 512], 1024); - memcpy(glslRenderer->io[0], glslRenderer->io[y], sizeof(*glslRenderer->io)); - } -} - -static void GBAVideoGLSLRendererFinishFrame(struct GBAVideoRenderer* renderer) { - struct GBAVideoGLSLRenderer* glslRenderer = (struct GBAVideoGLSLRenderer*) renderer; - - pthread_mutex_lock(&glslRenderer->mutex); - glslRenderer->state = GLSL_NONE; - if (renderer->frameskip > 0) { - --renderer->frameskip; - } else { - renderer->framesPending++; - pthread_cond_broadcast(&glslRenderer->upCond); - if (!renderer->turbo) { - pthread_cond_wait(&glslRenderer->downCond, &glslRenderer->mutex); - } - } - pthread_mutex_unlock(&glslRenderer->mutex); -}
@@ -1,44 +0,0 @@
-#ifndef VIDEO_GLSL_H -#define VIDEO_GLSL_H - -#include "util/common.h" - -#include "gba-video.h" - -#include <pthread.h> - -#ifdef __APPLE__ -#include <OpenGL/gl.h> -#else -#include <GL/gl.h> -#endif - -struct GBAVideoGLSLRenderer { - struct GBAVideoRenderer d; - - int y; - enum { - GLSL_NONE, - GLSL_DRAW_SCANLINE, - GLSL_FINISH_FRAME - } state; - - GLuint fragmentShader; - GLuint vertexShader; - GLuint program; - - GLuint vramTexture; - GLushort vram[512 * 256]; - GLushort io[160][0x30]; - - uint16_t* oldVram; - - pthread_mutex_t mutex; - pthread_cond_t upCond; - pthread_cond_t downCond; -}; - -void GBAVideoGLSLRendererCreate(struct GBAVideoGLSLRenderer* renderer); -void GBAVideoGLSLRendererProcessEvents(struct GBAVideoGLSLRenderer* renderer); - -#endif
@@ -1,12 +1,14 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "video-software.h" -#include "gba.h" -#include "gba-io.h" +#include "gba/gba.h" +#include "gba/io.h" + +#include "util/arm-algo.h" #ifdef NDEBUG #define VIDEO_CHECKS false@@ -35,6 +37,7 @@ };
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer); +static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam); static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value); static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);@@ -75,7 +78,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) { renderer->d.init = GBAVideoSoftwareRendererInit; - renderer->d.reset = GBAVideoSoftwareRendererInit; + renderer->d.reset = GBAVideoSoftwareRendererReset; renderer->d.deinit = GBAVideoSoftwareRendererDeinit; renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister; renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;@@ -87,6 +90,21 @@ renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
} static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) { + GBAVideoSoftwareRendererReset(renderer); + + struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer; + + int y; + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; + int x; + for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { + row[x] = GBA_COLOR_WHITE; + } + } +} + +static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) { struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer; int i;@@ -807,7 +825,11 @@
#define COMPOSITE_16_OBJWIN(BLEND) \ if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \ - _composite ## BLEND ## Objwin(renderer, pixel, color | flags, current); \ + unsigned mergedFlags = flags; \ + if (current & FLAG_OBJWIN) { \ + mergedFlags = objwinFlags; \ + } \ + _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ } #define COMPOSITE_16_NO_OBJWIN(BLEND) \@@ -816,7 +838,11 @@
#define COMPOSITE_256_OBJWIN(BLEND) \ if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \ - _composite ## BLEND ## Objwin(renderer, pixel, color | flags, current); \ + unsigned mergedFlags = flags; \ + if (current & FLAG_OBJWIN) { \ + mergedFlags = objwinFlags; \ + } \ + _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ } #define COMPOSITE_256_NO_OBJWIN(BLEND) \@@ -851,16 +877,17 @@ if (GBA_TEXT_MAP_VFLIP(mapData)) { \
localY = 7 - localY; \ } +// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5 #define PREPARE_OBJWIN \ int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \ int objwinOnly = 0; \ int objwinForceEnable = 0; \ - color_t* objwinPalette; \ + UNUSED(objwinForceEnable); \ + color_t* objwinPalette = renderer->normalPalette; \ + UNUSED(objwinPalette); \ if (objwinSlowPath) { \ if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ objwinPalette = renderer->variantPalette; \ - } else { \ - objwinPalette = renderer->normalPalette; \ } \ switch (background->index) { \ case 0: \@@ -933,11 +960,42 @@ } \
} #define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ + x = inX & 7; \ + if (mosaicWait) { \ + int baseX = x - (mosaicH - mosaicWait); \ + if (baseX < 0) { \ + int disturbX = (16 + baseX) >> 3; \ + inX -= disturbX << 3; \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + baseX -= disturbX << 3; \ + inX += disturbX << 3; \ + } else { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + } \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= 4 * baseX; \ + } else { \ + tileData >>= 4 * (7 - baseX); \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 12; \ + tileData |= tileData << 16; \ + tileData |= tileData << 20; \ + tileData |= tileData << 24; \ + tileData |= tileData << 28; \ + carryData = tileData; \ + } \ + for (; length; ++tileX) { \ BACKGROUND_TEXT_SELECT_CHARACTER; \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ tileData = carryData; \ - for (x = 0; x < 8; ++x) { \ + for (; x < 8 && length; ++x, --length) { \ if (!mosaicWait) { \ paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ palette = &mainPalette[paletteData]; \@@ -962,6 +1020,7 @@ --mosaicWait; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ ++pixel; \ } \ + x = 0; \ } #define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \@@ -970,6 +1029,10 @@ BACKGROUND_TEXT_SELECT_CHARACTER; \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ palette = &mainPalette[paletteData]; \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + pixel += 8; \ + continue; \ + } \ LOAD_32(tileData, charBase, vram); \ if (tileData) { \ if (!GBA_TEXT_MAP_HFLIP(mapData)) { \@@ -1018,35 +1081,43 @@ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
int end2 = end - 4; \ if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ int shift = inX & 0x3; \ - if (end2 > outX) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= 8 * shift; \ - shift = 0; \ - for (; outX < end2; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + if (LIKELY(charBase < 0x10000)) { \ + if (end2 > outX) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= 8 * shift; \ + shift = 0; \ + for (; outX < end2; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ } \ } \ \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= 8 * shift; \ - for (; outX < end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + if (LIKELY(charBase < 0x10000)) { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= 8 * shift; \ + for (; outX < end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ } \ } else { \ int start = outX; \ outX = end - 1; \ pixel = &renderer->row[outX]; \ - if (end2 > start) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX >= end2; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + if (LIKELY(charBase < 0x10000)) { \ + if (end2 > start) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= end2; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + charBase += 4; \ } \ - charBase += 4; \ } \ \ - LOAD_32(tileData, charBase, vram); \ - for (; outX >= renderer->start; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + if (LIKELY(charBase < 0x10000)) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= renderer->start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ } \ outX = end; \ pixel = &renderer->row[outX]; \@@ -1054,6 +1125,9 @@ }
#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + return; \ + } \ int end = mod8 - 4; \ pixel = &renderer->row[outX]; \ if (!GBA_TEXT_MAP_HFLIP(mapData)) { \@@ -1099,6 +1173,10 @@ #define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \
for (; tileX < tileEnd; ++tileX) { \ BACKGROUND_TEXT_SELECT_CHARACTER; \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + pixel += 8; \ + continue; \ + } \ if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ LOAD_32(tileData, charBase, vram); \ if (tileData) { \@@ -1194,7 +1272,7 @@ uint32_t* pixel = &renderer->row[outX]; \
if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \ int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \ int x; \ - int mosaicWait = outX % mosaicH; \ + int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \ int carryData = 0; \ paletteData = 0; /* Quiets compiler warning */ \ DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \@@ -1276,10 +1354,13 @@
unsigned xBase; int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; - flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA); flags |= FLAG_TARGET_2 * background->target2; + int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); + objwinFlags |= flags; + flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); if (renderer->blda == 0x10 && renderer->bldb == 0) { flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ } uint32_t screenBase;@@ -1303,7 +1384,7 @@ int tileEnd = ((length + inX) >> 3) - (inX >> 3);
uint16_t* vram = renderer->d.vram; if (!objwinSlowPath) { - if (!(flags & FLAG_TARGET_2)) { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { if (!background->multipalette) { DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN); } else {@@ -1317,7 +1398,7 @@ DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN);
} } } else { - if (!(flags & FLAG_TARGET_2)) { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { if (!background->multipalette) { DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN); } else {@@ -1349,16 +1430,20 @@ int32_t localX; \
int32_t localY; \ \ int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \ - flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA); \ flags |= FLAG_TARGET_2 * background->target2; \ + int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \ + objwinFlags |= flags; \ + flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ if (renderer->blda == 0x10 && renderer->bldb == 0) { \ flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ } \ int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ color_t* palette = renderer->normalPalette; \ if (variant) { \ palette = renderer->variantPalette; \ } \ + UNUSED(palette); \ PREPARE_OBJWIN; #define BACKGROUND_BITMAP_ITERATE(W, H) \@@ -1412,7 +1497,11 @@ if (!objwinSlowPath) {
_compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | flags, current); + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); } } }@@ -1445,12 +1534,16 @@ }
uint32_t current = *pixel; if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } if (!variant) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); + _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); } else if (renderer->blendEffect == BLEND_BRIGHTEN) { - _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | flags, current); + _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); } else if (renderer->blendEffect == BLEND_DARKEN) { - _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | flags, current); + _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); } } }@@ -1484,7 +1577,11 @@ if (!objwinSlowPath) {
_compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current); } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - _compositeBlendObjwin(renderer, pixel, currentPalette[color] | flags, current); + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + _compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current); } } }@@ -1520,12 +1617,16 @@ }
uint32_t current = *pixel; if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } if (!variant) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); + _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); } else if (renderer->blendEffect == BLEND_BRIGHTEN) { - _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | flags, current); + _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); } else if (renderer->blendEffect == BLEND_DARKEN) { - _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | flags, current); + _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); } } }@@ -1546,14 +1647,22 @@ #define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \
SPRITE_YBASE_ ## DEPTH(inY); \ unsigned tileData; \ if (outX % mosaicH) { \ - inX += (mosaicH - (outX % mosaicH)) * xOffset; \ - outX += mosaicH - (outX % mosaicH); \ + if (!inX && xOffset > 0) { \ + inX = mosaicH - (outX % mosaicH); \ + outX += mosaicH - (outX % mosaicH); \ + } else if (inX == width - xOffset) { \ + inX = mosaicH + (outX % mosaicH); \ + outX += mosaicH - (outX % mosaicH); \ + } \ } \ for (; outX < condition; ++outX, inX += xOffset) { \ if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ continue; \ } \ int localX = inX - xOffset * (outX % mosaicH); \ + if (localX < 0 || localX > width - 1) { \ + continue; \ + } \ SPRITE_XBASE_ ## DEPTH(localX); \ SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ }@@ -1584,8 +1693,13 @@
#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \ tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ - if (tileData && (renderer->spriteLayer[outX] & FLAG_ORDER_MASK) > flags) { \ - renderer->spriteLayer[outX] = palette[tileData] | flags; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ } #define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \@@ -1601,8 +1715,13 @@
#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFF), vramBase); \ tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ - if (tileData && (renderer->spriteLayer[outX] & FLAG_ORDER_MASK) > flags) { \ - renderer->spriteLayer[outX] = palette[tileData] | flags; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ } #define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \@@ -1636,6 +1755,7 @@ }
int inY = y - (int) GBAObjAttributesAGetY(sprite->a); + uint32_t current; if (GBAObjAttributesAIsTransformed(sprite->a)) { int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,7 +8,7 @@ #define VIDEO_SOFTWARE_H
#include "util/common.h" -#include "gba-video.h" +#include "gba/video.h" #ifdef COLOR_16_BIT typedef uint16_t color_t;@@ -113,7 +113,7 @@ struct GBAVideoSoftwareRenderer {
struct GBAVideoRenderer d; color_t* outputBuffer; - unsigned outputBufferStride; + int outputBufferStride; GBARegisterDISPCNT dispcnt;
@@ -0,0 +1,578 @@
+/* Copyright (c) 2013-2015 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 "mgm.h" + +#include "gba/gba.h" +#include "gba/serialize.h" +#include "util/vfs.h" + +#define BINARY_EXT ".mgm" +#define BINARY_MAGIC "GBAb" +#define METADATA_FILENAME "metadata" BINARY_EXT + +enum { + INVALID_INPUT = 0x8000 +}; + +static void GBAMGMContextDestroy(struct GBARRContext*); + +static bool GBAMGMStartPlaying(struct GBARRContext*, bool autorecord); +static void GBAMGMStopPlaying(struct GBARRContext*); +static bool GBAMGMStartRecording(struct GBARRContext*); +static void GBAMGMStopRecording(struct GBARRContext*); + +static bool GBAMGMIsPlaying(const struct GBARRContext*); +static bool GBAMGMIsRecording(const struct GBARRContext*); + +static void GBAMGMNextFrame(struct GBARRContext*); +static void GBAMGMLogInput(struct GBARRContext*, uint16_t input); +static uint16_t GBAMGMQueryInput(struct GBARRContext*); + +static void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state); +static void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state); + +static bool _loadStream(struct GBAMGMContext*, uint32_t streamId); +static bool _incrementStream(struct GBAMGMContext*, bool recursive); +static bool _finishSegment(struct GBAMGMContext*); +static bool _skipSegment(struct GBAMGMContext*); +static bool _markRerecord(struct GBAMGMContext*); + +static bool _emitMagic(struct GBAMGMContext*, struct VFile* vf); +static bool _verifyMagic(struct GBAMGMContext*, struct VFile* vf); +static enum GBAMGMTag _readTag(struct GBAMGMContext*, struct VFile* vf); +static bool _seekTag(struct GBAMGMContext*, struct VFile* vf, enum GBAMGMTag tag); +static bool _emitTag(struct GBAMGMContext*, struct VFile* vf, uint8_t tag); +static bool _emitEnd(struct GBAMGMContext*, struct VFile* vf); + +static bool _parseMetadata(struct GBAMGMContext*, struct VFile* vf); + +static bool _markStreamNext(struct GBAMGMContext*, uint32_t newStreamId, bool recursive); +static void _streamEndReached(struct GBAMGMContext*); + +static struct VFile* GBAMGMOpenSavedata(struct GBARRContext*, int flags); +static struct VFile* GBAMGMOpenSavestate(struct GBARRContext*, int flags); + +void GBAMGMContextCreate(struct GBAMGMContext* mgm) { + memset(mgm, 0, sizeof(*mgm)); + + mgm->d.destroy = GBAMGMContextDestroy; + + mgm->d.startPlaying = GBAMGMStartPlaying; + mgm->d.stopPlaying = GBAMGMStopPlaying; + mgm->d.startRecording = GBAMGMStartRecording; + mgm->d.stopRecording = GBAMGMStopRecording; + + mgm->d.isPlaying = GBAMGMIsPlaying; + mgm->d.isRecording = GBAMGMIsRecording; + + mgm->d.nextFrame = GBAMGMNextFrame; + mgm->d.logInput = GBAMGMLogInput; + mgm->d.queryInput = GBAMGMQueryInput; + + mgm->d.stateSaved = GBAMGMStateSaved; + mgm->d.stateLoaded = GBAMGMStateLoaded; + + mgm->d.openSavedata = GBAMGMOpenSavedata; + mgm->d.openSavestate = GBAMGMOpenSavestate; +} + +void GBAMGMContextDestroy(struct GBARRContext* rr) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (mgm->metadataFile) { + mgm->metadataFile->close(mgm->metadataFile); + } +} + +bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream) { + if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) { + return false; + } + + if (mgm->metadataFile && !mgm->metadataFile->close(mgm->metadataFile)) { + return false; + } + + mgm->streamDir = stream; + mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR); + mgm->currentInput = INVALID_INPUT; + if (!_parseMetadata(mgm, mgm->metadataFile)) { + mgm->metadataFile->close(mgm->metadataFile); + mgm->metadataFile = 0; + mgm->maxStreamId = 0; + } + mgm->streamId = 1; + mgm->movieStream = 0; + return true; +} + +bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom) { + if (mgm->metadataFile) { + mgm->metadataFile->truncate(mgm->metadataFile, 0); + } else { + mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR); + } + _emitMagic(mgm, mgm->metadataFile); + + mgm->d.initFrom = initFrom; + mgm->initFromOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + _emitTag(mgm, mgm->metadataFile, TAG_INIT | initFrom); + + mgm->streamId = 0; + mgm->maxStreamId = 0; + _emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM); + mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + + mgm->d.rrCount = 0; + _emitTag(mgm, mgm->metadataFile, TAG_RR_COUNT); + mgm->rrCountOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount)); + return true; +} + +bool _loadStream(struct GBAMGMContext* mgm, uint32_t streamId) { + if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) { + return false; + } + mgm->movieStream = 0; + mgm->streamId = streamId; + mgm->currentInput = INVALID_INPUT; + char buffer[14]; + snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId); + if (mgm->d.isRecording(&mgm->d)) { + int flags = O_CREAT | O_RDWR; + if (streamId > mgm->maxStreamId) { + flags |= O_TRUNC; + } + mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, flags); + } else if (mgm->d.isPlaying(&mgm->d)) { + mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, O_RDONLY); + mgm->peekedTag = TAG_INVALID; + if (!mgm->movieStream || !_verifyMagic(mgm, mgm->movieStream) || !_seekTag(mgm, mgm->movieStream, TAG_BEGIN)) { + mgm->d.stopPlaying(&mgm->d); + } + } + GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId); + mgm->d.frames = 0; + mgm->d.lagFrames = 0; + return true; +} + +bool _incrementStream(struct GBAMGMContext* mgm, bool recursive) { + uint32_t newStreamId = mgm->maxStreamId + 1; + uint32_t oldStreamId = mgm->streamId; + if (mgm->d.isRecording(&mgm->d) && mgm->movieStream) { + if (!_markStreamNext(mgm, newStreamId, recursive)) { + return false; + } + } + if (!_loadStream(mgm, newStreamId)) { + return false; + } + GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId); + _emitMagic(mgm, mgm->movieStream); + mgm->maxStreamId = newStreamId; + _emitTag(mgm, mgm->movieStream, TAG_PREVIOUSLY); + mgm->movieStream->write(mgm->movieStream, &oldStreamId, sizeof(oldStreamId)); + _emitTag(mgm, mgm->movieStream, TAG_BEGIN); + + mgm->metadataFile->seek(mgm->metadataFile, mgm->maxStreamIdOffset, SEEK_SET); + mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + mgm->previously = oldStreamId; + return true; +} + +bool GBAMGMStartPlaying(struct GBARRContext* rr, bool autorecord) { + if (rr->isRecording(rr) || rr->isPlaying(rr)) { + return false; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + mgm->isPlaying = true; + if (!_loadStream(mgm, 1)) { + mgm->isPlaying = false; + return false; + } + mgm->autorecord = autorecord; + return true; +} + +void GBAMGMStopPlaying(struct GBARRContext* rr) { + if (!rr->isPlaying(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + mgm->isPlaying = false; + if (mgm->movieStream) { + mgm->movieStream->close(mgm->movieStream); + mgm->movieStream = 0; + } +} + +bool GBAMGMStartRecording(struct GBARRContext* rr) { + if (rr->isRecording(rr) || rr->isPlaying(rr)) { + return false; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (!mgm->maxStreamIdOffset) { + _emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM); + mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + } + + mgm->isRecording = true; + return _incrementStream(mgm, false); +} + +void GBAMGMStopRecording(struct GBARRContext* rr) { + if (!rr->isRecording(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + mgm->isRecording = false; + if (mgm->movieStream) { + _emitEnd(mgm, mgm->movieStream); + mgm->movieStream->close(mgm->movieStream); + mgm->movieStream = 0; + } +} + +bool GBAMGMIsPlaying(const struct GBARRContext* rr) { + const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr; + return mgm->isPlaying; +} + +bool GBAMGMIsRecording(const struct GBARRContext* rr) { + const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr; + return mgm->isRecording; +} + +void GBAMGMNextFrame(struct GBARRContext* rr) { + if (!rr->isRecording(rr) && !rr->isPlaying(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (rr->isPlaying(rr)) { + while (mgm->peekedTag == TAG_INPUT) { + _readTag(mgm, mgm->movieStream); + GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!"); + } + if (mgm->peekedTag == TAG_LAG) { + GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream"); + if (mgm->inputThisFrame) { + GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie"); + } + } + } + + ++mgm->d.frames; + GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", mgm->d.frames); + if (!mgm->inputThisFrame) { + ++mgm->d.lagFrames; + GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", mgm->d.lagFrames); + } + + if (rr->isRecording(rr)) { + if (!mgm->inputThisFrame) { + _emitTag(mgm, mgm->movieStream, TAG_LAG); + } + _emitTag(mgm, mgm->movieStream, TAG_FRAME); + mgm->inputThisFrame = false; + } else { + if (!_seekTag(mgm, mgm->movieStream, TAG_FRAME)) { + _streamEndReached(mgm); + } + } +} + +void GBAMGMLogInput(struct GBARRContext* rr, uint16_t keys) { + if (!rr->isRecording(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (keys != mgm->currentInput) { + _emitTag(mgm, mgm->movieStream, TAG_INPUT); + mgm->movieStream->write(mgm->movieStream, &keys, sizeof(keys)); + mgm->currentInput = keys; + } + GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", mgm->currentInput); + mgm->inputThisFrame = true; +} + +uint16_t GBAMGMQueryInput(struct GBARRContext* rr) { + if (!rr->isPlaying(rr)) { + return 0; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (mgm->peekedTag == TAG_INPUT) { + _readTag(mgm, mgm->movieStream); + } + mgm->inputThisFrame = true; + if (mgm->currentInput == INVALID_INPUT) { + GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input"); + } + GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", mgm->currentInput); + return mgm->currentInput; +} + +void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (rr->isRecording(rr)) { + state->associatedStreamId = mgm->streamId; + _finishSegment(mgm); + } +} + +void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (rr->isRecording(rr)) { + if (state->associatedStreamId != mgm->streamId) { + _loadStream(mgm, state->associatedStreamId); + _incrementStream(mgm, true); + } else { + _finishSegment(mgm); + } + _markRerecord(mgm); + } else if (rr->isPlaying(rr)) { + _loadStream(mgm, state->associatedStreamId); + _skipSegment(mgm); + } +} + +bool _finishSegment(struct GBAMGMContext* mgm) { + if (mgm->movieStream) { + if (!_emitEnd(mgm, mgm->movieStream)) { + return false; + } + } + return _incrementStream(mgm, false); +} + +bool _skipSegment(struct GBAMGMContext* mgm) { + mgm->nextTime = 0; + while (_readTag(mgm, mgm->movieStream) != TAG_EOF); + if (!mgm->nextTime || !_loadStream(mgm, mgm->nextTime)) { + _streamEndReached(mgm); + return false; + } + return true; +} + +bool _markRerecord(struct GBAMGMContext* mgm) { + ++mgm->d.rrCount; + mgm->metadataFile->seek(mgm->metadataFile, mgm->rrCountOffset, SEEK_SET); + mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount)); + return true; +} + +bool _emitMagic(struct GBAMGMContext* mgm, struct VFile* vf) { + UNUSED(mgm); + return vf->write(vf, BINARY_MAGIC, 4) == 4; +} + +bool _verifyMagic(struct GBAMGMContext* mgm, struct VFile* vf) { + UNUSED(mgm); + char buffer[4]; + if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) { + return false; + } + if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) { + return false; + } + return true; +} + +enum GBAMGMTag _readTag(struct GBAMGMContext* mgm, struct VFile* vf) { + if (!mgm || !vf) { + return TAG_EOF; + } + + enum GBAMGMTag tag = mgm->peekedTag; + switch (tag) { + case TAG_INPUT: + vf->read(vf, &mgm->currentInput, sizeof(uint16_t)); + break; + case TAG_PREVIOUSLY: + vf->read(vf, &mgm->previously, sizeof(mgm->previously)); + break; + case TAG_NEXT_TIME: + vf->read(vf, &mgm->nextTime, sizeof(mgm->nextTime)); + break; + case TAG_MAX_STREAM: + vf->read(vf, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + break; + case TAG_FRAME_COUNT: + vf->read(vf, &mgm->d.frames, sizeof(mgm->d.frames)); + break; + case TAG_LAG_COUNT: + vf->read(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames)); + break; + case TAG_RR_COUNT: + vf->read(vf, &mgm->d.rrCount, sizeof(mgm->d.rrCount)); + break; + + case TAG_INIT_EX_NIHILO: + mgm->d.initFrom = INIT_EX_NIHILO; + break; + case TAG_INIT_FROM_SAVEGAME: + mgm->d.initFrom = INIT_FROM_SAVEGAME; + break; + case TAG_INIT_FROM_SAVESTATE: + mgm->d.initFrom = INIT_FROM_SAVESTATE; + break; + case TAG_INIT_FROM_BOTH: + mgm->d.initFrom = INIT_FROM_BOTH; + break; + + // To be spec'd + case TAG_AUTHOR: + case TAG_COMMENT: + break; + + // Empty markers + case TAG_FRAME: + case TAG_LAG: + case TAG_BEGIN: + case TAG_END: + case TAG_INVALID: + case TAG_EOF: + break; + } + + uint8_t tagBuffer; + if (vf->read(vf, &tagBuffer, 1) != 1) { + mgm->peekedTag = TAG_EOF; + } else { + mgm->peekedTag = tagBuffer; + } + + if (mgm->peekedTag == TAG_END) { + _skipSegment(mgm); + } + return tag; +} + +bool _seekTag(struct GBAMGMContext* mgm, struct VFile* vf, enum GBAMGMTag tag) { + enum GBAMGMTag readTag; + while ((readTag = _readTag(mgm, vf)) != tag) { + if (readTag == TAG_EOF) { + return false; + } + } + return true; +} + +bool _emitTag(struct GBAMGMContext* mgm, struct VFile* vf, uint8_t tag) { + UNUSED(mgm); + return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag); +} + +bool _parseMetadata(struct GBAMGMContext* mgm, struct VFile* vf) { + if (!_verifyMagic(mgm, vf)) { + return false; + } + while (_readTag(mgm, vf) != TAG_EOF) { + switch (mgm->peekedTag) { + case TAG_MAX_STREAM: + mgm->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR); + break; + case TAG_INIT_EX_NIHILO: + case TAG_INIT_FROM_SAVEGAME: + case TAG_INIT_FROM_SAVESTATE: + case TAG_INIT_FROM_BOTH: + mgm->initFromOffset = vf->seek(vf, 0, SEEK_CUR); + break; + case TAG_RR_COUNT: + mgm->rrCountOffset = vf->seek(vf, 0, SEEK_CUR); + break; + default: + break; + } + } + return true; +} + +bool _emitEnd(struct GBAMGMContext* mgm, struct VFile* vf) { + // TODO: Error check + _emitTag(mgm, vf, TAG_END); + _emitTag(mgm, vf, TAG_FRAME_COUNT); + vf->write(vf, &mgm->d.frames, sizeof(mgm->d.frames)); + _emitTag(mgm, vf, TAG_LAG_COUNT); + vf->write(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames)); + _emitTag(mgm, vf, TAG_NEXT_TIME); + + uint32_t newStreamId = 0; + vf->write(vf, &newStreamId, sizeof(newStreamId)); + return true; +} + +bool _markStreamNext(struct GBAMGMContext* mgm, uint32_t newStreamId, bool recursive) { + if (mgm->movieStream->seek(mgm->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) { + return false; + } + + uint8_t tagBuffer; + if (mgm->movieStream->read(mgm->movieStream, &tagBuffer, 1) != 1) { + return false; + } + if (tagBuffer != TAG_NEXT_TIME) { + return false; + } + if (mgm->movieStream->write(mgm->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) { + return false; + } + if (recursive) { + if (mgm->movieStream->seek(mgm->movieStream, 0, SEEK_SET) < 0) { + return false; + } + if (!_verifyMagic(mgm, mgm->movieStream)) { + return false; + } + _readTag(mgm, mgm->movieStream); + if (_readTag(mgm, mgm->movieStream) != TAG_PREVIOUSLY) { + return false; + } + if (mgm->previously == 0) { + return true; + } + uint32_t currentStreamId = mgm->streamId; + if (!_loadStream(mgm, mgm->previously)) { + return false; + } + return _markStreamNext(mgm, currentStreamId, mgm->previously); + } + return true; +} + +void _streamEndReached(struct GBAMGMContext* mgm) { + if (!mgm->d.isPlaying(&mgm->d)) { + return; + } + + uint32_t endStreamId = mgm->streamId; + mgm->d.stopPlaying(&mgm->d); + if (mgm->autorecord) { + mgm->isRecording = true; + _loadStream(mgm, endStreamId); + _incrementStream(mgm, false); + } +} + +struct VFile* GBAMGMOpenSavedata(struct GBARRContext* rr, int flags) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + return mgm->streamDir->openFile(mgm->streamDir, "movie.sav", flags); +} + +struct VFile* GBAMGMOpenSavestate(struct GBARRContext* rr, int flags) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + return mgm->streamDir->openFile(mgm->streamDir, "movie.ssm", flags); +}
@@ -0,0 +1,82 @@
+/* Copyright (c) 2013-2015 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 RR_MGM_H +#define RR_MGM_H + +#include "util/common.h" + +#include "gba/supervisor/rr.h" + +struct GBA; +struct VDir; +struct VFile; + +enum GBAMGMTag { + // Playback tags + TAG_INVALID = 0x00, + TAG_INPUT = 0x01, + TAG_FRAME = 0x02, + TAG_LAG = 0x03, + + // Stream chunking tags + TAG_BEGIN = 0x10, + TAG_END = 0x11, + TAG_PREVIOUSLY = 0x12, + TAG_NEXT_TIME = 0x13, + TAG_MAX_STREAM = 0x14, + + // Recording information tags + TAG_FRAME_COUNT = 0x20, + TAG_LAG_COUNT = 0x21, + TAG_RR_COUNT = 0x22, + TAG_INIT = 0x24, + TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO, + TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME, + TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE, + TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH, + + // User metadata tags + TAG_AUTHOR = 0x30, + TAG_COMMENT = 0x31, + + TAG_EOF = INT_MAX +}; + +struct GBAMGMContext { + struct GBARRContext d; + + // Playback state + bool isPlaying; + bool autorecord; + + // Recording state + bool isRecording; + bool inputThisFrame; + + // Metadata + uint32_t streamId; + + uint32_t maxStreamId; + off_t maxStreamIdOffset; + off_t initFromOffset; + off_t rrCountOffset; + + // Streaming state + struct VDir* streamDir; + struct VFile* metadataFile; + struct VFile* movieStream; + uint16_t currentInput; + enum GBAMGMTag peekedTag; + uint32_t nextTime; + uint32_t previously; +}; + +void GBAMGMContextCreate(struct GBAMGMContext*); + +bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream); +bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom); + +#endif
@@ -0,0 +1,183 @@
+/* Copyright (c) 2013-2015 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 "vbm.h" + +#include "gba/gba.h" +#include "gba/serialize.h" +#include "util/vfs.h" + +static const char VBM_MAGIC[] = "VBM\x1A"; + +static void GBAVBMContextDestroy(struct GBARRContext*); + +static bool GBAVBMStartPlaying(struct GBARRContext*, bool autorecord); +static void GBAVBMStopPlaying(struct GBARRContext*); +static bool GBAVBMStartRecording(struct GBARRContext*); +static void GBAVBMStopRecording(struct GBARRContext*); + +static bool GBAVBMIsPlaying(const struct GBARRContext*); +static bool GBAVBMIsRecording(const struct GBARRContext*); + +static void GBAVBMNextFrame(struct GBARRContext*); +static uint16_t GBAVBMQueryInput(struct GBARRContext*); + +static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state); +static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state); + +static struct VFile* GBAVBMOpenSavedata(struct GBARRContext*, int flags); +static struct VFile* GBAVBMOpenSavestate(struct GBARRContext*, int flags); + +void GBAVBMContextCreate(struct GBAVBMContext* vbm) { + memset(vbm, 0, sizeof(*vbm)); + + vbm->d.destroy = GBAVBMContextDestroy; + + vbm->d.startPlaying = GBAVBMStartPlaying; + vbm->d.stopPlaying = GBAVBMStopPlaying; + vbm->d.startRecording = GBAVBMStartRecording; + vbm->d.stopRecording = GBAVBMStopRecording; + + vbm->d.isPlaying = GBAVBMIsPlaying; + vbm->d.isRecording = GBAVBMIsRecording; + + vbm->d.nextFrame = GBAVBMNextFrame; + vbm->d.logInput = 0; + vbm->d.queryInput = GBAVBMQueryInput; + + vbm->d.stateSaved = GBAVBMStateSaved; + vbm->d.stateLoaded = GBAVBMStateLoaded; + + vbm->d.openSavedata = GBAVBMOpenSavedata; + vbm->d.openSavestate = GBAVBMOpenSavestate; +} + +bool GBAVBMStartPlaying(struct GBARRContext* rr, bool autorecord) { + if (rr->isRecording(rr) || rr->isPlaying(rr) || autorecord) { + return false; + } + + struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; + vbm->isPlaying = true; + vbm->vbmFile->seek(vbm->vbmFile, vbm->inputOffset, SEEK_SET); + return true; +} + +void GBAVBMStopPlaying(struct GBARRContext* rr) { + if (!rr->isPlaying(rr)) { + return; + } + + struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; + vbm->isPlaying = false; +} + +bool GBAVBMStartRecording(struct GBARRContext* rr) { + UNUSED(rr); + return false; +} + +void GBAVBMStopRecording(struct GBARRContext* rr) { + UNUSED(rr); +} + +bool GBAVBMIsPlaying(const struct GBARRContext* rr) { + struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; + return vbm->isPlaying; +} + +bool GBAVBMIsRecording(const struct GBARRContext* rr) { + UNUSED(rr); + return false; +} + +void GBAVBMNextFrame(struct GBARRContext* rr) { + UNUSED(rr); +} + +uint16_t GBAVBMQueryInput(struct GBARRContext* rr) { + if (!rr->isPlaying(rr)) { + return 0; + } + + struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; + uint16_t input; + vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input)); + return input & 0x3FF; +} + +void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) { + UNUSED(rr); + UNUSED(state); +} + +void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) { + UNUSED(rr); + UNUSED(state); +} + +struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) { + UNUSED(rr); + UNUSED(flags); + return 0; +} + +struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) { + UNUSED(rr); + UNUSED(flags); + return 0; +} + +void GBAVBMContextDestroy(struct GBARRContext* rr) { + struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr; + if (vbm->vbmFile) { + vbm->vbmFile->close(vbm->vbmFile); + } +} + +bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) { + vf->seek(vf, 0, SEEK_SET); + char magic[4]; + vf->read(vf, magic, sizeof(magic)); + if (memcmp(magic, VBM_MAGIC, sizeof(magic)) != 0) { + return false; + } + + uint32_t id; + vf->read(vf, &id, sizeof(id)); + if (id != 1) { + return false; + } + + vf->seek(vf, 4, SEEK_CUR); + vf->read(vf, &vbm->d.frames, sizeof(vbm->d.frames)); + vf->read(vf, &vbm->d.rrCount, sizeof(vbm->d.rrCount)); + + uint8_t flags; + vf->read(vf, &flags, sizeof(flags)); + if (flags & 1) { + // Incompatible savestate format + return false; + } + if (flags & 2) { + // TODO: Implement SRAM loading + return false; + } + + vf->seek(vf, 1, SEEK_CUR); + vf->read(vf, &flags, sizeof(flags)); + if ((flags & 0x7) != 1) { + // Non-GBA movie + return false; + } + + // TODO: parse more flags + + vf->seek(vf, 0x3C, SEEK_SET); + vf->read(vf, &vbm->inputOffset, sizeof(vbm->inputOffset)); + vf->seek(vf, vbm->inputOffset, SEEK_SET); + vbm->vbmFile = vf; + return true; +}
@@ -0,0 +1,21 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/common.h" + +#include "gba/supervisor/rr.h" + +struct GBAVBMContext { + struct GBARRContext d; + + bool isPlaying; + + struct VFile* vbmFile; + int32_t inputOffset; +}; + +void GBAVBMContextCreate(struct GBAVBMContext*); + +bool GBAVBMSetStream(struct GBAVBMContext*, struct VFile*);
@@ -0,0 +1,144 @@
+/* Copyright (c) 2013-2015 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 "cli.h" + +#include "gba/io.h" +#include "gba/serialize.h" +#include "gba/supervisor/thread.h" + +#ifdef USE_CLI_DEBUGGER + +static const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share + +static void _GBACLIDebuggerInit(struct CLIDebuggerSystem*); +static void _GBACLIDebuggerDeinit(struct CLIDebuggerSystem*); +static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem*); +static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); + +static void _frame(struct CLIDebugger*, struct CLIDebugVector*); +static void _load(struct CLIDebugger*, struct CLIDebugVector*); +static void _rewind(struct CLIDebugger*, struct CLIDebugVector*); +static void _save(struct CLIDebugger*, struct CLIDebugVector*); + +struct CLIDebuggerCommandSummary _GBACLIDebuggerCommands[] = { + { "frame", _frame, 0, "Frame advance" }, + { "load", _load, CLIDVParse, "Load a savestate" }, + { "rewind", _rewind, CLIDVParse, "Rewind the emulation a number of recorded intervals" }, + { "save", _save, CLIDVParse, "Save a savestate" }, + { 0, 0, 0, 0 } +}; +#endif + +struct GBACLIDebugger* GBACLIDebuggerCreate(struct GBAThread* context) { + struct GBACLIDebugger* debugger = malloc(sizeof(struct GBACLIDebugger)); +#ifdef USE_CLI_DEBUGGER + debugger->d.init = _GBACLIDebuggerInit; + debugger->d.deinit = _GBACLIDebuggerDeinit; + debugger->d.custom = _GBACLIDebuggerCustom; + debugger->d.lookupIdentifier = _GBACLIDebuggerLookupIdentifier; + + debugger->d.name = "Game Boy Advance"; + debugger->d.commands = _GBACLIDebuggerCommands; + + debugger->context = context; +#else + UNUSED(context); +#endif + + return debugger; +} + +#ifdef USE_CLI_DEBUGGER +static void _GBACLIDebuggerInit(struct CLIDebuggerSystem* debugger) { + struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger; + + gbaDebugger->frameAdvance = false; +} + +static void _GBACLIDebuggerDeinit(struct CLIDebuggerSystem* debugger) { + UNUSED(debugger); +} + +static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { + struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger; + + if (gbaDebugger->frameAdvance) { + if (!gbaDebugger->inVblank && GBARegisterDISPSTATIsInVblank(gbaDebugger->context->gba->memory.io[REG_DISPSTAT >> 1])) { + ARMDebuggerEnter(&gbaDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0); + gbaDebugger->frameAdvance = false; + return false; + } + gbaDebugger->inVblank = GBARegisterDISPSTATGetInVblank(gbaDebugger->context->gba->memory.io[REG_DISPSTAT >> 1]); + return true; + } + return false; +} + +static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { + UNUSED(debugger); + int i; + for (i = 0; i < REG_MAX; i += 2) { + const char* reg = GBAIORegisterNames[i >> 1]; + if (reg && strcasecmp(reg, name) == 0) { + return BASE_IO | i; + } + } + dv->type = CLIDV_ERROR_TYPE; + return 0; +} + +static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + UNUSED(dv); + debugger->d.state = DEBUGGER_CUSTOM; + + struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; + gbaDebugger->frameAdvance = true; + gbaDebugger->inVblank = GBARegisterDISPSTATGetInVblank(gbaDebugger->context->gba->memory.io[REG_DISPSTAT >> 1]); +} + +static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + + int state = dv->intValue; + if (state < 1 || state > 9) { + printf("State %u out of range", state); + } + + struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; + + GBALoadState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue); +} + +static void _rewind(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; + if (!dv) { + GBARewindAll(gbaDebugger->context); + } else if (dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + } else { + GBARewind(gbaDebugger->context, dv->intValue); + } +} + +static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + + int state = dv->intValue; + if (state < 1 || state > 9) { + printf("State %u out of range", state); + } + + struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; + + GBASaveState(gbaDebugger->context, gbaDebugger->context->stateDir, dv->intValue, true); +} +#endif
@@ -0,0 +1,260 @@
+/* Copyright (c) 2013-2015 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 "overrides.h" + +#include "gba/gba.h" +#include "gba/hardware.h" + + #include "util/configuration.h" + +static const struct GBACartridgeOverride _overrides[] = { + // Boktai: The Sun is in Your Hand + { "U3IJ", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + { "U3IE", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + { "U3IP", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + + // Boktai 2: Solar Boy Django + { "U32J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + { "U32E", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + { "U32P", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + + // Drill Dozer + { "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE }, + { "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE }, + + // Final Fantasy Tactics Advance + { "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 }, + + // Golden Sun: The Lost Age + { "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A }, + + // Koro Koro Puzzle - Happy Panechu! + { "KHPJ", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE }, + + // Mega Man Battle Network + { "AREE", SAVEDATA_SRAM, HW_NONE, 0x800032E }, + + // Metal Slug Advance + { "BSME", SAVEDATA_EEPROM, HW_NONE, 0x8000290 }, + + // Pokemon Ruby + { "AXVJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXVE", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXVP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXVI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXVS", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXVD", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXVF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + + // Pokemon Sapphire + { "AXPJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXPE", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXPP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXPI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXPS", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXPD", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "AXPF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + + // Pokemon Emerald + { "BPEJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEE", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6 }, + { "BPEP", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEI", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPES", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPED", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + { "BPEF", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE }, + + // Pokemon Mystery Dungeon + { "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + + // Pokemon FireRed + { "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPRP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + + // Pokemon LeafGreen + { "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "BPGP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + + // RockMan EXE 4.5 - Real Operation + { "BR4J", SAVEDATA_FLASH512, HW_RTC, IDLE_LOOP_NONE }, + + // Shin Bokura no Taiyou: Gyakushuu no Sabata + { "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE }, + + // Super Mario Advance 2 + { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, + + // Super Mario Advance 3 + { "A3AE", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C }, + + // Super Mario Advance 4 + { "AX4J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "AX4E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + { "AX4P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE }, + + // Top Gun - Combat Zones + { "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE }, + + // Wario Ware Twisted + { "RZWJ", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE }, + { "RZWE", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE }, + { "RZWP", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE }, + + // Yoshi's Universal Gravitation + { "KYGJ", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE }, + { "KYGE", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE }, + { "KYGP", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE }, + + { { 0, 0, 0, 0 }, 0, 0, IDLE_LOOP_NONE } +}; + +bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOverride* override) { + override->savetype = SAVEDATA_AUTODETECT; + override->hardware = HW_NONE; + override->idleLoop = IDLE_LOOP_NONE; + bool found; + + if (override->id[0] == 'F') { + // Classic NES Series + override->savetype = SAVEDATA_EEPROM; + found = true; + } else { + int i; + for (i = 0; _overrides[i].id[0]; ++i) { + if (memcmp(override->id, _overrides[i].id, sizeof(override->id)) == 0) { + *override = _overrides[i]; + found = true; + break; + } + } + } + + if (config) { + char sectionName[16]; + snprintf(sectionName, sizeof(sectionName), "override.%c%c%c%c", override->id[0], override->id[1], override->id[2], override->id[3]); + const char* savetype = ConfigurationGetValue(config, sectionName, "savetype"); + const char* hardware = ConfigurationGetValue(config, sectionName, "hardware"); + const char* idleLoop = ConfigurationGetValue(config, sectionName, "idleLoop"); + + if (savetype) { + if (strcasecmp(savetype, "SRAM") == 0) { + found = true; + override->savetype = SAVEDATA_SRAM; + } else if (strcasecmp(savetype, "EEPROM") == 0) { + found = true; + override->savetype = SAVEDATA_EEPROM; + } else if (strcasecmp(savetype, "FLASH512") == 0) { + found = true; + override->savetype = SAVEDATA_FLASH512; + } else if (strcasecmp(savetype, "FLASH1M") == 0) { + found = true; + override->savetype = SAVEDATA_FLASH1M; + } else if (strcasecmp(savetype, "NONE") == 0) { + found = true; + override->savetype = SAVEDATA_FORCE_NONE; + } + } + + if (hardware) { + char* end; + long type = strtoul(hardware, &end, 0); + if (end && !*end) { + override->hardware = type; + found = true; + } + } + + if (idleLoop) { + char* end; + uint32_t address = strtoul(idleLoop, &end, 16); + if (end && !*end) { + override->idleLoop = address; + found = true; + } + } + } + return found; +} + +void GBAOverrideSave(struct Configuration* config, const struct GBACartridgeOverride* override) { + char sectionName[16]; + snprintf(sectionName, sizeof(sectionName), "override.%c%c%c%c", override->id[0], override->id[1], override->id[2], override->id[3]); + const char* savetype = 0; + switch (override->savetype) { + case SAVEDATA_SRAM: + savetype = "SRAM"; + break; + case SAVEDATA_EEPROM: + savetype = "EEPROM"; + break; + case SAVEDATA_FLASH512: + savetype = "FLASH512"; + break; + case SAVEDATA_FLASH1M: + savetype = "FLASH1M"; + break; + case SAVEDATA_FORCE_NONE: + savetype = "NONE"; + break; + case SAVEDATA_AUTODETECT: + break; + } + ConfigurationSetValue(config, sectionName, "savetype", savetype); + + if (override->hardware != HW_NO_OVERRIDE) { + ConfigurationSetIntValue(config, sectionName, "hardware", override->hardware); + } else { + ConfigurationClearValue(config, sectionName, "hardware"); + } + + if (override->idleLoop != IDLE_LOOP_NONE) { + ConfigurationSetUIntValue(config, sectionName, "idleLoop", override->idleLoop); + } else { + ConfigurationClearValue(config, sectionName, "idleLoop"); + } +} + +void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* override) { + if (override->savetype != SAVEDATA_AUTODETECT) { + GBASavedataForceType(&gba->memory.savedata, override->savetype); + } + + if (override->hardware != HW_NO_OVERRIDE) { + GBAHardwareClear(&gba->memory.hw); + + if (override->hardware & HW_RTC) { + GBAHardwareInitRTC(&gba->memory.hw); + } + + if (override->hardware & HW_GYRO) { + GBAHardwareInitGyro(&gba->memory.hw); + } + + if (override->hardware & HW_RUMBLE) { + GBAHardwareInitRumble(&gba->memory.hw); + } + + if (override->hardware & HW_LIGHT_SENSOR) { + GBAHardwareInitLight(&gba->memory.hw); + } + + if (override->hardware & HW_TILT) { + GBAHardwareInitTilt(&gba->memory.hw); + } + } + + if (override->idleLoop != IDLE_LOOP_NONE) { + gba->idleLoop = override->idleLoop; + if (gba->idleOptimization == IDLE_LOOP_DETECT) { + gba->idleOptimization = IDLE_LOOP_REMOVE; + } + } +}
@@ -0,0 +1,29 @@
+/* Copyright (c) 2013-2015 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 GBA_OVERRIDES_H +#define GBA_OVERRIDES_H + +#include "util/common.h" + +#include "gba/savedata.h" + +#define IDLE_LOOP_NONE 0xFFFFFFFF + +struct GBACartridgeOverride { + char id[4]; + enum SavedataType savetype; + int hardware; + uint32_t idleLoop; +}; + +struct Configuration; +bool GBAOverrideFind(const struct Configuration*, struct GBACartridgeOverride* override); +void GBAOverrideSave(struct Configuration*, const struct GBACartridgeOverride* override); + +struct GBA; +void GBAOverrideApply(struct GBA*, const struct GBACartridgeOverride*); + +#endif
@@ -0,0 +1,73 @@
+/* Copyright (c) 2013-2015 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 "rr.h" + +#include "util/vfs.h" + +void GBARRInitRecord(struct GBA* gba) { + if (!gba || !gba->rr) { + return; + } + + if (gba->rr->initFrom & INIT_FROM_SAVEGAME) { + if (gba->rr->savedata) { + gba->rr->savedata->close(gba->rr->savedata); + } + gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY); + GBASavedataClone(&gba->memory.savedata, gba->rr->savedata); + gba->rr->savedata->close(gba->rr->savedata); + gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY); + GBASavedataMask(&gba->memory.savedata, gba->rr->savedata); + } else { + GBASavedataMask(&gba->memory.savedata, 0); + } + + if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { + struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR); + GBASaveStateNamed(gba, vf, false); + vf->close(vf); + } else { + ARMReset(gba->cpu); + } +} + +void GBARRInitPlay(struct GBA* gba) { + if (!gba || !gba->rr) { + return; + } + + if (gba->rr->initFrom & INIT_FROM_SAVEGAME) { + if (gba->rr->savedata) { + gba->rr->savedata->close(gba->rr->savedata); + } + gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY); + GBASavedataMask(&gba->memory.savedata, gba->rr->savedata); + } else { + GBASavedataMask(&gba->memory.savedata, 0); + } + + if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { + struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY); + GBALoadStateNamed(gba, vf); + vf->close(vf); + } else { + ARMReset(gba->cpu); + } +} + +void GBARRDestroy(struct GBARRContext* rr) { + if (rr->isPlaying(rr)) { + rr->stopPlaying(rr); + } + if (rr->isRecording(rr)) { + rr->stopRecording(rr); + } + if (rr->savedata) { + rr->savedata->close(rr->savedata); + rr->savedata = 0; + } + rr->destroy(rr); +}
@@ -0,0 +1,57 @@
+/* Copyright (c) 2013-2015 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 GBA_RR_H +#define GBA_RR_H + +#include "util/common.h" + +#include "gba/serialize.h" + +struct VFile; + +enum GBARRInitFrom { + INIT_EX_NIHILO = 0, + INIT_FROM_SAVEGAME = 1, + INIT_FROM_SAVESTATE = 2, + INIT_FROM_BOTH = 3, +}; + +struct GBARRContext { + void (*destroy)(struct GBARRContext*); + + bool (*startPlaying)(struct GBARRContext*, bool autorecord); + void (*stopPlaying)(struct GBARRContext*); + bool (*startRecording)(struct GBARRContext*); + void (*stopRecording)(struct GBARRContext*); + + bool (*isPlaying)(const struct GBARRContext*); + bool (*isRecording)(const struct GBARRContext*); + + void (*nextFrame)(struct GBARRContext*); + void (*logInput)(struct GBARRContext*, uint16_t input); + uint16_t (*queryInput)(struct GBARRContext*); + + void (*stateSaved)(struct GBARRContext*, struct GBASerializedState*); + void (*stateLoaded)(struct GBARRContext*, const struct GBASerializedState*); + + struct VFile* (*openSavedata)(struct GBARRContext* mgm, int flags); + struct VFile* (*openSavestate)(struct GBARRContext* mgm, int flags); + + uint32_t frames; + uint32_t lagFrames; + enum GBARRInitFrom initFrom; + + uint32_t rrCount; + + struct VFile* savedata; +}; + +void GBARRDestroy(struct GBARRContext*); + +void GBARRInitRecord(struct GBA*); +void GBARRInitPlay(struct GBA*); + +#endif
@@ -21,15 +21,16 @@ static ssize_t _vf3dWrite(struct VFile* vf, const void* buffer, size_t size);
static void* _vf3dMap(struct VFile* vf, size_t size, int flags); static void _vf3dUnmap(struct VFile* vf, void* memory, size_t size); static void _vf3dTruncate(struct VFile* vf, size_t size); +static ssize_t _vf3dSize(struct VFile* vf); -struct VFile* VFileOpen3DS(FS_archive archive, const char* path, int flags) { +struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags) { struct VFile3DS* vf3d = malloc(sizeof(struct VFile3DS)); if (!vf3d) { return 0; } FS_path newPath = FS_makePath(PATH_CHAR, path); - Result res = FSUSER_OpenFile(0, &vf3d->handle, archive, newPath, flags, FS_ATTRIBUTE_NONE); + Result res = FSUSER_OpenFile(0, &vf3d->handle, *archive, newPath, flags, FS_ATTRIBUTE_NONE); if (res & 0xFFFC03FF) { free(vf3d); return 0;@@ -45,6 +46,7 @@ vf3d->d.write = _vf3dWrite;
vf3d->d.map = _vf3dMap; vf3d->d.unmap = _vf3dUnmap; vf3d->d.truncate = _vf3dTruncate; + vf3d->d.size = _vf3dSize; return &vf3d->d; }@@ -117,3 +119,10 @@ static void _vf3dTruncate(struct VFile* vf, size_t size) {
struct VFile3DS* vf3d = (struct VFile3DS*) vf; FSFILE_SetSize(vf3d->handle, size); } + +ssize_t _vf3dSize(struct VFile* vf) { + struct VFile3DS* vf3d = (struct VFile3DS*) vf; + u64 size; + FSFILE_GetSize(vf3d->handle, &size); + return size; +}
@@ -7,4 +7,4 @@ #include "util/vfs.h"
#include <3ds.h> -struct VFile* VFileOpen3DS(FS_archive archive, const char* path, int flags); +struct VFile* VFileOpen3DS(FS_archive* archive, const char* path, int flags);
@@ -13,7 +13,7 @@
set(toolchain_bin_dir ${DEVKITARM}/bin) set(cross_prefix ${toolchain_bin_dir}/arm-none-eabi-) set(inc_flags -I${DEVKITPRO}/libctru/include) -set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=softfp") +set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard") set(link_flags "-L${DEVKITPRO}/libctru/lib -lctru -lm -specs=3dsx.specs ${arch_flags}") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name")
@@ -1,12 +1,12 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba.h" -#include "gba-video.h" +#include "gba/gba.h" +#include "gba/video.h" -#include "renderers/video-software.h" +#include "gba/renderers/video-software.h" #include "util/memory.h" #include "3ds-vfs.h"@@ -17,10 +17,8 @@ int main() {
srvInit(); aptInit(); hidInit(0); - gfxInit(); + gfxInit(GSP_RGB565_OES, GSP_RGB565_OES, false); fsInit(); - - gfxSetScreenFormat(GFX_BOTTOM, GSP_RGB565_OES); struct GBAVideoSoftwareRenderer renderer; GBAVideoSoftwareRendererCreate(&renderer);@@ -41,9 +39,9 @@ 0, 0
}; FSUSER_OpenArchive(0, &sdmcArchive); - struct VFile* rom = VFileOpen3DS(sdmcArchive, "/rom.gba", FS_OPEN_READ); + struct VFile* rom = VFileOpen3DS(&sdmcArchive, "/rom.gba", FS_OPEN_READ); - struct VFile* save = VFileOpen3DS(sdmcArchive, "/rom.sav", FS_OPEN_WRITE | FS_OPEN_CREATE); + struct VFile* save = VFileOpen3DS(&sdmcArchive, "/rom.sav", FS_OPEN_WRITE | FS_OPEN_CREATE); GBACreate(gba); ARMSetComponents(cpu, &gba->d, 0, 0);@@ -58,33 +56,31 @@ GBALoadROM(gba, rom, save, 0);
ARMReset(cpu); - bool inVblank = false; + int frameCounter = 0; while (aptMainLoop()) { ARMRunLoop(cpu); - if (!inVblank) { - if (GBARegisterDISPSTATIsInVblank(gba->video.dispstat)) { - u16 width, height; - u16* screen = (u16*) gfxGetFramebuffer(GFX_BOTTOM, GFX_BOTTOM, &height, &width); - u32 startX = (width - VIDEO_HORIZONTAL_PIXELS) / 2; - u32 startY = (height + VIDEO_VERTICAL_PIXELS) / 2 - 1; - u32 x, y; - for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { - for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { - screen[startY - y + (startX + x) * height] = videoBuffer[y * VIDEO_HORIZONTAL_PIXELS + x]; - } - } - gfxFlushBuffers(); - gfxSwapBuffersGpu(); - gspWaitForVBlank(); - hidScanInput(); - activeKeys = hidKeysHeld() & 0x3FF; - if (hidKeysDown() & KEY_X) { - break; + if (frameCounter != gba->video.frameCounter) { + u16 width, height; + u16* screen = (u16*) gfxGetFramebuffer(GFX_BOTTOM, GFX_BOTTOM, &height, &width); + u32 startX = (width - VIDEO_HORIZONTAL_PIXELS) / 2; + u32 startY = (height + VIDEO_VERTICAL_PIXELS) / 2 - 1; + u32 x, y; + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { + screen[startY - y + (startX + x) * height] = videoBuffer[y * VIDEO_HORIZONTAL_PIXELS + x]; } } + gfxFlushBuffers(); + gfxSwapBuffersGpu(); + gspWaitForVBlank(); + hidScanInput(); + activeKeys = hidKeysHeld() & 0x3FF; + if (hidKeysDown() & KEY_X) { + break; + } + frameCounter = gba->video.frameCounter; } - inVblank = GBARegisterDISPSTATGetInVblank(gba->video.dispstat); } ARMDeinit(cpu);
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -9,30 +9,33 @@ #include "debugger/debugger.h"
#ifdef USE_CLI_DEBUGGER #include "debugger/cli-debugger.h" -#include "gba/gba-cli.h" +#include "gba/supervisor/cli.h" #endif #ifdef USE_GDB_STUB #include "debugger/gdb-stub.h" #endif -#include "gba/gba-video.h" +#include "gba/video.h" #include <fcntl.h> #include <getopt.h> -#define GRAPHICS_OPTIONS "1234f" +#define GRAPHICS_OPTIONS "123456f" #define GRAPHICS_USAGE \ "\nGraphics options:\n" \ " -1 1x viewport\n" \ " -2 2x viewport\n" \ " -3 3x viewport\n" \ " -4 4x viewport\n" \ + " -5 5x viewport\n" \ + " -6 6x viewport\n" \ " -f Start full-screen" static const struct option _options[] = { { "bios", required_argument, 0, 'b' }, - { "dirmode", required_argument, 0, 'D' }, + { "cheats", required_argument, 0, 'c' }, + { "dirmode", required_argument, 0, 'D' }, { "frameskip", required_argument, 0, 's' }, #ifdef USE_CLI_DEBUGGER { "debug", no_argument, 0, 'd' },@@ -40,6 +43,7 @@ #endif
#ifdef USE_GDB_STUB { "gdb", no_argument, 0, 'g' }, #endif + { "movie", required_argument, 0, 'v' }, { "patch", required_argument, 0, 'p' }, { 0, 0, 0, 0 } };@@ -49,7 +53,7 @@
bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) { int ch; char options[64] = - "b:Dl:p:s:" + "b:c:Dl:p:s:v:" #ifdef USE_CLI_DEBUGGER "d" #endif@@ -57,6 +61,7 @@ #ifdef USE_GDB_STUB
"g" #endif ; + memset(opts, 0, sizeof(*opts)); if (subparser && subparser->extraOptions) { // TODO: modularize options to subparsers strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);@@ -65,6 +70,9 @@ while ((ch = getopt_long(argc, argv, options, _options, 0)) != -1) {
switch (ch) { case 'b': GBAConfigSetDefaultValue(config, "bios", optarg); + break; + case 'c': + opts->cheatsFile = strdup(optarg); break; case 'D': opts->dirmode = true;@@ -94,6 +102,9 @@ break;
case 's': GBAConfigSetDefaultValue(config, "frameskip", optarg); break; + case 'v': + opts->movie = strdup(optarg); + break; default: if (subparser) { if (!subparser->parse(subparser, config, ch, optarg)) {@@ -118,6 +129,9 @@ opts->fname = 0;
free(opts->patch); opts->patch = 0; + + free(opts->movie); + opts->movie = 0; } void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {@@ -139,6 +153,8 @@ case '1':
case '2': case '3': case '4': + case '5': + case '6': if (graphicsOpts->multiplier) { return false; }@@ -195,13 +211,15 @@ void usage(const char* arg0, const char* extraOptions) {
printf("usage: %s [option ...] file\n", arg0); puts("\nGeneric options:"); puts(" -b, --bios FILE GBA BIOS file to use"); + puts(" -c, --cheats FILE Apply cheat codes from a file"); #ifdef USE_CLI_DEBUGGER puts(" -d, --debug Use command-line debugger"); #endif #ifdef USE_GDB_STUB puts(" -g, --gdb Start GDB session (default port 2345)"); #endif - puts(" -p, --patch Apply a specified patch file when running"); + puts(" -v, --movie FILE Play back a movie of recorded input"); + puts(" -p, --patch FILE Apply a specified patch file when running"); puts(" -s, --frameskip N Skip every N frames"); if (extraOptions) { puts(extraOptions);
@@ -8,7 +8,7 @@ #define COMMAND_LINE_H
#include "util/common.h" -#include "gba-config.h" +#include "gba/supervisor/config.h" enum DebuggerType { DEBUGGER_NONE = 0,@@ -24,7 +24,9 @@
struct GBAArguments { char* fname; char* patch; + char* cheatsFile; bool dirmode; + char* movie; enum DebuggerType debuggerType; bool debugAtStart;
@@ -1,22 +1,28 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "ffmpeg-encoder.h" -#include "gba-video.h" +#include "gba/video.h" +#include <libavcodec/version.h> #include <libavcodec/avcodec.h> +#include <libavutil/version.h> +#if LIBAVUTIL_VERSION_MAJOR >= 53 +#include <libavutil/buffer.h> +#endif #include <libavutil/imgutils.h> +#include <libavutil/mathematics.h> #include <libavutil/opt.h> #include <libavresample/avresample.h> #include <libswscale/swscale.h> static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); -static void _ffmpegPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right); +static void _ffmpegPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right); enum { PREFERRED_SAMPLE_RATE = 0x8000@@ -113,12 +119,12 @@ { AV_PIX_FMT_RGB565, 1 },
{ AV_PIX_FMT_BGR565, 1 }, { AV_PIX_FMT_RGB24, 2 }, { AV_PIX_FMT_BGR24, 2 }, +#ifndef USE_LIBAV { AV_PIX_FMT_BGR0, 3 }, { AV_PIX_FMT_RGB0, 3 }, { AV_PIX_FMT_0BGR, 3 }, { AV_PIX_FMT_0RGB, 3 }, - { AV_PIX_FMT_RGB8, 3 }, - { AV_PIX_FMT_BGR8, 3 }, +#endif { AV_PIX_FMT_YUV422P, 4 }, { AV_PIX_FMT_YUV444P, 5 }, { AV_PIX_FMT_YUV420P, 6 }@@ -190,9 +196,14 @@ encoder->currentAudioFrame = 0;
encoder->currentVideoFrame = 0; encoder->nextAudioPts = 0; - avformat_alloc_output_context2(&encoder->context, 0, 0, outfile); - - encoder->context->oformat = av_guess_format(encoder->containerFormat, 0, 0); + AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0); +#ifndef USE_LIBAV + avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile); +#else + encoder->context = avformat_alloc_context(); + strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename)); + encoder->context->oformat = oformat; +#endif if (acodec) { encoder->audioStream = avformat_new_stream(encoder->context, acodec);@@ -209,7 +220,14 @@ encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER;
} avcodec_open2(encoder->audio, acodec, &opts); av_dict_free(&opts); +#if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->audioFrame = av_frame_alloc(); +#else + encoder->audioFrame = avcodec_alloc_frame(); +#endif + if (!encoder->audio->frame_size) { + encoder->audio->frame_size = 1; + } encoder->audioFrame->nb_samples = encoder->audio->frame_size; encoder->audioFrame->format = encoder->audio->sample_fmt; encoder->audioFrame->pts = 0;@@ -260,12 +278,29 @@ }
av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0); } avcodec_open2(encoder->video, vcodec, 0); +#if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->videoFrame = av_frame_alloc(); +#else + encoder->videoFrame = avcodec_alloc_frame(); +#endif encoder->videoFrame->format = encoder->video->pix_fmt; encoder->videoFrame->width = encoder->video->width; encoder->videoFrame->height = encoder->video->height; encoder->videoFrame->pts = 0; - encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, AV_PIX_FMT_0BGR32, + encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + AV_PIX_FMT_RGB565, +#else + AV_PIX_FMT_BGR555, +#endif +#else +#ifndef USE_LIBAV + AV_PIX_FMT_0BGR32, +#else + AV_PIX_FMT_BGR32, +#endif +#endif encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt, SWS_POINT, 0, 0, 0); av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32);@@ -288,7 +323,11 @@ av_free(encoder->postaudioBuffer);
if (encoder->audioBuffer) { av_free(encoder->audioBuffer); } +#if LIBAVCODEC_VERSION_MAJOR >= 55 av_frame_free(&encoder->audioFrame); +#else + avcodec_free_frame(&encoder->audioFrame); +#endif avcodec_close(encoder->audio); if (encoder->resampleContext) {@@ -301,7 +340,11 @@ encoder->absf = 0;
} } +#if LIBAVCODEC_VERSION_MAJOR >= 55 av_frame_free(&encoder->videoFrame); +#else + avcodec_free_frame(&encoder->videoFrame); +#endif avcodec_close(encoder->video); sws_freeContext(encoder->scaleContext);@@ -314,13 +357,12 @@ bool FFmpegEncoderIsOpen(struct FFmpegEncoder* encoder) {
return !!encoder->context; } -void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) { +void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) { struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; if (!encoder->context || !encoder->audioCodec) { return; } - av_frame_make_writable(encoder->audioFrame); encoder->audioBuffer[encoder->currentAudioSample * 2] = left; encoder->audioBuffer[encoder->currentAudioSample * 2 + 1] = right;@@ -334,11 +376,14 @@ encoder->currentAudioSample = 0;
int channelSize = 2 * av_get_bytes_per_sample(encoder->audio->sample_fmt); avresample_convert(encoder->resampleContext, - 0, 0, encoder->postaudioBufferSize / channelSize, + 0, 0, 0, (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); - if ((ssize_t) avresample_available(encoder->resampleContext) < (ssize_t) encoder->postaudioBufferSize / channelSize) { + if (avresample_available(encoder->resampleContext) < encoder->audioFrame->nb_samples) { return; } +#if LIBAVCODEC_VERSION_MAJOR >= 55 + av_frame_make_writable(encoder->audioFrame); +#endif avresample_read(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize); AVRational timeBase = { 1, PREFERRED_SAMPLE_RATE };@@ -358,7 +403,9 @@ int success = av_bitstream_filter_filter(encoder->absf, encoder->audio, 0,
&tempPacket.data, &tempPacket.size, packet.data, packet.size, 0); if (success > 0) { +#if LIBAVUTIL_VERSION_MAJOR >= 53 tempPacket.buf = av_buffer_create(tempPacket.data, tempPacket.size, av_buffer_default_free, 0, 0); +#endif av_free_packet(&packet); } packet = tempPacket;@@ -377,14 +424,16 @@ }
uint8_t* pixels; unsigned stride; renderer->getPixels(renderer, &stride, (void**) &pixels); - stride *= 4; + stride *= BYTES_PER_PIXEL; AVPacket packet; av_init_packet(&packet); packet.data = 0; packet.size = 0; +#if LIBAVCODEC_VERSION_MAJOR >= 55 av_frame_make_writable(encoder->videoFrame); +#endif encoder->videoFrame->pts = av_rescale_q(encoder->currentVideoFrame, encoder->video->time_base, encoder->videoStream->time_base); ++encoder->currentVideoFrame;
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef FFMPEG_ENCODER #define FFMPEG_ENCODER -#include "gba-thread.h" +#include "gba/supervisor/thread.h" #include <libavformat/avformat.h>
@@ -1,14 +1,14 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "imagemagick-gif-encoder.h" -#include "gba-video.h" +#include "gba/video.h" static void _magickPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); -static void _magickPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right); +static void _magickPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right); void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) { encoder->wand = 0;@@ -70,7 +70,7 @@ MagickSetImageDelay(encoder->wand, nts - ts);
++encoder->currentFrame; } -static void _magickPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) { +static void _magickPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) { UNUSED(stream); UNUSED(left); UNUSED(right);
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IMAGEMAGICK_GIF_ENCODER #define IMAGEMAGICK_GIF_ENCODER -#include "gba-thread.h" +#include "gba/supervisor/thread.h" #define MAGICKCORE_HDRI_ENABLE 0 #define MAGICKCORE_QUANTUM_DEPTH 8
@@ -0,0 +1,330 @@
+/* Copyright (c) 2013-2015 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 "libretro.h" + +#include "gba/gba.h" +#include "gba/renderers/video-software.h" +#include "gba/serialize.h" +#include "gba/supervisor/overrides.h" +#include "gba/video.h" +#include "util/vfs.h" + +static retro_environment_t environCallback; +static retro_video_refresh_t videoCallback; +static retro_audio_sample_t audioCallback; +static retro_input_poll_t inputPollCallback; +static retro_input_state_t inputCallback; +static retro_log_printf_t logCallback; + +static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); + +static void _postAudioFrame(struct GBAAVStream*, int16_t left, int16_t right); +static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer); + +static struct GBA gba; +static struct ARMCore cpu; +static struct GBAVideoSoftwareRenderer renderer; +static struct VFile* rom; +static void* data; +static struct VFile* save; +static void* savedata; +static struct GBAAVStream stream; + +unsigned retro_api_version(void) { + return RETRO_API_VERSION; +} + +void retro_set_environment(retro_environment_t environ) { + environCallback = environ; +} + +void retro_set_video_refresh(retro_video_refresh_t video) { + videoCallback = video; +} + +void retro_set_audio_sample(retro_audio_sample_t audio) { + audioCallback = audio; +} + +void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) { + UNUSED(audioBatch); +} + +void retro_set_input_poll(retro_input_poll_t inputPoll) { + inputPollCallback = inputPoll; +} + +void retro_set_input_state(retro_input_state_t input) { + inputCallback = input; +} + +void retro_get_system_info(struct retro_system_info* info) { + info->need_fullpath = false; + info->valid_extensions = "gba"; + info->library_version = PROJECT_VERSION; + info->library_name = PROJECT_NAME; + info->block_extract = false; +} + +void retro_get_system_av_info(struct retro_system_av_info* info) { + info->geometry.base_width = VIDEO_HORIZONTAL_PIXELS; + info->geometry.base_height = VIDEO_VERTICAL_PIXELS; + info->geometry.max_width = VIDEO_HORIZONTAL_PIXELS; + info->geometry.max_height = VIDEO_VERTICAL_PIXELS; + info->timing.fps = GBA_ARM7TDMI_FREQUENCY / (float) VIDEO_TOTAL_LENGTH; + info->timing.sample_rate = 32768; +} + +void retro_init(void) { + enum retro_pixel_format fmt; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + fmt = RETRO_PIXEL_FORMAT_RGB565; +#else +#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5 + fmt = RETRO_PIXEL_FORMAT_0RGB1555; +#endif +#else +#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5 + fmt = RETRO_PIXEL_FORMAT_XRGB8888; +#endif + environCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt); + + struct retro_input_descriptor inputDescriptors[] = { + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" }, + { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" } + }; + environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors); + + // TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported + // TODO: RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE + + struct retro_log_callback log; + if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) { + logCallback = log.log; + } else { + logCallback = 0; + } + + stream.postAudioFrame = _postAudioFrame; + stream.postVideoFrame = _postVideoFrame; + + GBACreate(&gba); + ARMSetComponents(&cpu, &gba.d, 0, 0); + ARMInit(&cpu); + gba.logLevel = 0; // TODO: Settings + gba.logHandler = GBARetroLog; + gba.stream = &stream; + gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings + rom = 0; + + GBAVideoSoftwareRendererCreate(&renderer); + renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + renderer.outputBufferStride = 256; + GBAVideoAssociateRenderer(&gba.video, &renderer.d); +} + +void retro_deinit(void) { + GBADestroy(&gba); +} + +void retro_run(void) { + int keys; + gba.keySource = &keys; + inputPollCallback(); + + keys = 0; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)) << 2; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)) << 3; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) << 4; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) << 5; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) << 6; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8; + keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9; + + int frameCount = gba.video.frameCounter; + while (gba.video.frameCounter == frameCount) { + ARMRunLoop(&cpu); + } + videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * 256); +} + +void retro_reset(void) { + ARMReset(&cpu); +} + +bool retro_load_game(const struct retro_game_info* game) { + if (game->data) { + data = malloc(game->size); + memcpy(data, game->data, game->size); + rom = VFileFromMemory(data, game->size); + } else { + data = 0; + rom = VFileOpen(game->path, O_RDONLY); + } + if (!rom) { + return false; + } + if (!GBAIsROM(rom)) { + return false; + } + + savedata = malloc(SIZE_CART_FLASH1M); + save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); + + GBALoadROM(&gba, rom, save, game->path); + + struct GBACartridgeOverride override; + const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom; + memcpy(override.id, &cart->id, sizeof(override.id)); + if (GBAOverrideFind(0, &override)) { + GBAOverrideApply(&gba, &override); + } + + ARMReset(&cpu); + return true; +} + +void retro_unload_game(void) { + rom->close(rom); + rom = 0; + free(data); + data = 0; + save->close(save); + save = 0; + free(savedata); + savedata = 0; +} + +size_t retro_serialize_size(void) { + return sizeof(struct GBASerializedState); +} + +bool retro_serialize(void* data, size_t size) { + if (size != retro_serialize_size()) { + return false; + } + GBASerialize(&gba, data); + return true; +} + +bool retro_unserialize(const void* data, size_t size) { + if (size != retro_serialize_size()) { + return false; + } + GBADeserialize(&gba, data); + return true; +} + +void retro_cheat_reset(void) { + // TODO: Cheats +} + +void retro_cheat_set(unsigned index, bool enabled, const char* code) { + // TODO: Cheats + UNUSED(index); + UNUSED(enabled); + UNUSED(code); +} + +unsigned retro_get_region(void) { + return RETRO_REGION_NTSC; // TODO: This isn't strictly true +} + +void retro_set_controller_port_device(unsigned port, unsigned device) { + UNUSED(port); + UNUSED(device); +} + +bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) { + UNUSED(game_type); + UNUSED(info); + UNUSED(num_info); + return false; +} + +void* retro_get_memory_data(unsigned id) { + if (id != RETRO_MEMORY_SAVE_RAM) { + return 0; + } + return savedata; +} + +size_t retro_get_memory_size(unsigned id) { + if (id != RETRO_MEMORY_SAVE_RAM) { + return 0; + } + switch (gba.memory.savedata.type) { + case SAVEDATA_AUTODETECT: + case SAVEDATA_FLASH1M: + return SIZE_CART_FLASH1M; + case SAVEDATA_FLASH512: + return SIZE_CART_FLASH512; + case SAVEDATA_EEPROM: + return SIZE_CART_EEPROM; + case SAVEDATA_SRAM: + return SIZE_CART_SRAM; + case SAVEDATA_FORCE_NONE: + return 0; + } + return 0; +} + +void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { + UNUSED(thread); + if (!logCallback) { + return; + } + + char message[128]; + vsnprintf(message, sizeof(message), format, args); + + enum retro_log_level retroLevel = RETRO_LOG_INFO; + switch (level) { + case GBA_LOG_ALL: + case GBA_LOG_ERROR: + case GBA_LOG_FATAL: + retroLevel = RETRO_LOG_ERROR; + break; + case GBA_LOG_WARN: + retroLevel = RETRO_LOG_WARN; + break; + case GBA_LOG_INFO: + case GBA_LOG_GAME_ERROR: + case GBA_LOG_SWI: + retroLevel = RETRO_LOG_INFO; + break; + case GBA_LOG_DEBUG: + case GBA_LOG_STUB: + retroLevel = RETRO_LOG_DEBUG; + break; + } + logCallback(retroLevel, "%s\n", message); +} + +static void _postAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) { + UNUSED(stream); + audioCallback(left, right); +} + +static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) { + UNUSED(stream); + void* pixels; + unsigned stride; + renderer->getPixels(renderer, &stride, &pixels); + videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride); +}
@@ -0,0 +1,1926 @@
+/* Copyright (C) 2010-2015 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this libretro API header (libretro.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef LIBRETRO_H__ +#define LIBRETRO_H__ + +#include <stdint.h> +#include <stddef.h> +#include <limits.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __cplusplus +#if defined(_MSC_VER) && !defined(SN_TARGET_PS3) +/* Hack applied for MSVC when compiling in C89 mode + * as it isn't C99-compliant. */ +#define bool unsigned char +#define true 1 +#define false 0 +#else +#include <stdbool.h> +#endif +#endif + +/* Used for checking API/ABI mismatches that can break libretro + * implementations. + * It is not incremented for compatible changes to the API. + */ +#define RETRO_API_VERSION 1 + +/* + * Libretro's fundamental device abstractions. + * + * Libretro's input system consists of some standardized device types, + * such as a joypad (with/without analog), mouse, keyboard, lightgun + * and a pointer. + * + * The functionality of these devices are fixed, and individual cores + * map their own concept of a controller to libretro's abstractions. + * This makes it possible for frontends to map the abstract types to a + * real input device, and not having to worry about binding input + * correctly to arbitrary controller layouts. + */ + +#define RETRO_DEVICE_TYPE_SHIFT 8 +#define RETRO_DEVICE_MASK ((1 << RETRO_DEVICE_TYPE_SHIFT) - 1) +#define RETRO_DEVICE_SUBCLASS(base, id) (((id + 1) << RETRO_DEVICE_TYPE_SHIFT) | base) + +/* Input disabled. */ +#define RETRO_DEVICE_NONE 0 + +/* The JOYPAD is called RetroPad. It is essentially a Super Nintendo + * controller, but with additional L2/R2/L3/R3 buttons, similar to a + * PS1 DualShock. */ +#define RETRO_DEVICE_JOYPAD 1 + +/* The mouse is a simple mouse, similar to Super Nintendo's mouse. + * X and Y coordinates are reported relatively to last poll (poll callback). + * It is up to the libretro implementation to keep track of where the mouse + * pointer is supposed to be on the screen. + * The frontend must make sure not to interfere with its own hardware + * mouse pointer. + */ +#define RETRO_DEVICE_MOUSE 2 + +/* KEYBOARD device lets one poll for raw key pressed. + * It is poll based, so input callback will return with the current + * pressed state. + * For event/text based keyboard input, see + * RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. + */ +#define RETRO_DEVICE_KEYBOARD 3 + +/* Lightgun X/Y coordinates are reported relatively to last poll, + * similar to mouse. */ +#define RETRO_DEVICE_LIGHTGUN 4 + +/* The ANALOG device is an extension to JOYPAD (RetroPad). + * Similar to DualShock it adds two analog sticks. + * This is treated as a separate device type as it returns values in the + * full analog range of [-0x8000, 0x7fff]. Positive X axis is right. + * Positive Y axis is down. + * Only use ANALOG type when polling for analog values of the axes. + */ +#define RETRO_DEVICE_ANALOG 5 + +/* Abstracts the concept of a pointing mechanism, e.g. touch. + * This allows libretro to query in absolute coordinates where on the + * screen a mouse (or something similar) is being placed. + * For a touch centric device, coordinates reported are the coordinates + * of the press. + * + * Coordinates in X and Y are reported as: + * [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen, + * and 0x7fff corresponds to the far right/bottom of the screen. + * The "screen" is here defined as area that is passed to the frontend and + * later displayed on the monitor. + * + * The frontend is free to scale/resize this screen as it sees fit, however, + * (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the + * game image, etc. + * + * To check if the pointer coordinates are valid (e.g. a touch display + * actually being touched), PRESSED returns 1 or 0. + * + * If using a mouse on a desktop, PRESSED will usually correspond to the + * left mouse button, but this is a frontend decision. + * PRESSED will only return 1 if the pointer is inside the game screen. + * + * For multi-touch, the index variable can be used to successively query + * more presses. + * If index = 0 returns true for _PRESSED, coordinates can be extracted + * with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with + * index = 1, and so on. + * Eventually _PRESSED will return false for an index. No further presses + * are registered at this point. */ +#define RETRO_DEVICE_POINTER 6 + +/* Buttons for the RetroPad (JOYPAD). + * The placement of these is equivalent to placements on the + * Super Nintendo controller. + * L2/R2/L3/R3 buttons correspond to the PS1 DualShock. */ +#define RETRO_DEVICE_ID_JOYPAD_B 0 +#define RETRO_DEVICE_ID_JOYPAD_Y 1 +#define RETRO_DEVICE_ID_JOYPAD_SELECT 2 +#define RETRO_DEVICE_ID_JOYPAD_START 3 +#define RETRO_DEVICE_ID_JOYPAD_UP 4 +#define RETRO_DEVICE_ID_JOYPAD_DOWN 5 +#define RETRO_DEVICE_ID_JOYPAD_LEFT 6 +#define RETRO_DEVICE_ID_JOYPAD_RIGHT 7 +#define RETRO_DEVICE_ID_JOYPAD_A 8 +#define RETRO_DEVICE_ID_JOYPAD_X 9 +#define RETRO_DEVICE_ID_JOYPAD_L 10 +#define RETRO_DEVICE_ID_JOYPAD_R 11 +#define RETRO_DEVICE_ID_JOYPAD_L2 12 +#define RETRO_DEVICE_ID_JOYPAD_R2 13 +#define RETRO_DEVICE_ID_JOYPAD_L3 14 +#define RETRO_DEVICE_ID_JOYPAD_R3 15 + +/* Index / Id values for ANALOG device. */ +#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 +#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 +#define RETRO_DEVICE_ID_ANALOG_X 0 +#define RETRO_DEVICE_ID_ANALOG_Y 1 + +/* Id values for MOUSE. */ +#define RETRO_DEVICE_ID_MOUSE_X 0 +#define RETRO_DEVICE_ID_MOUSE_Y 1 +#define RETRO_DEVICE_ID_MOUSE_LEFT 2 +#define RETRO_DEVICE_ID_MOUSE_RIGHT 3 +#define RETRO_DEVICE_ID_MOUSE_WHEELUP 4 +#define RETRO_DEVICE_ID_MOUSE_WHEELDOWN 5 +#define RETRO_DEVICE_ID_MOUSE_MIDDLE 6 + +/* Id values for LIGHTGUN types. */ +#define RETRO_DEVICE_ID_LIGHTGUN_X 0 +#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 +#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 +#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 +#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 +#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 +#define RETRO_DEVICE_ID_LIGHTGUN_START 6 + +/* Id values for POINTER. */ +#define RETRO_DEVICE_ID_POINTER_X 0 +#define RETRO_DEVICE_ID_POINTER_Y 1 +#define RETRO_DEVICE_ID_POINTER_PRESSED 2 + +/* Returned from retro_get_region(). */ +#define RETRO_REGION_NTSC 0 +#define RETRO_REGION_PAL 1 + +/* Id values for LANGUAGE */ +enum retro_language +{ + RETRO_LANGUAGE_ENGLISH = 0, + RETRO_LANGUAGE_JAPANESE = 1, + RETRO_LANGUAGE_FRENCH = 2, + RETRO_LANGUAGE_SPANISH = 3, + RETRO_LANGUAGE_GERMAN = 4, + RETRO_LANGUAGE_ITALIAN = 5, + RETRO_LANGUAGE_DUTCH = 6, + RETRO_LANGUAGE_PORTUGUESE = 7, + RETRO_LANGUAGE_RUSSIAN = 8, + RETRO_LANGUAGE_KOREAN = 9, + RETRO_LANGUAGE_CHINESE_TRADITIONAL = 10, + RETRO_LANGUAGE_CHINESE_SIMPLIFIED = 11, + RETRO_LANGUAGE_LAST, + + /* Ensure sizeof(enum) == sizeof(int) */ + RETRO_LANGUAGE_DUMMY = INT_MAX +}; + +/* Passed to retro_get_memory_data/size(). + * If the memory type doesn't apply to the + * implementation NULL/0 can be returned. + */ +#define RETRO_MEMORY_MASK 0xff + +/* Regular save RAM. This RAM is usually found on a game cartridge, + * backed up by a battery. + * If save game data is too complex for a single memory buffer, + * the SAVE_DIRECTORY (preferably) or SYSTEM_DIRECTORY environment + * callback can be used. */ +#define RETRO_MEMORY_SAVE_RAM 0 + +/* Some games have a built-in clock to keep track of time. + * This memory is usually just a couple of bytes to keep track of time. + */ +#define RETRO_MEMORY_RTC 1 + +/* System ram lets a frontend peek into a game systems main RAM. */ +#define RETRO_MEMORY_SYSTEM_RAM 2 + +/* Video ram lets a frontend peek into a game systems video RAM (VRAM). */ +#define RETRO_MEMORY_VIDEO_RAM 3 + +/* Keysyms used for ID in input state callback when polling RETRO_KEYBOARD. */ +enum retro_key +{ + RETROK_UNKNOWN = 0, + RETROK_FIRST = 0, + RETROK_BACKSPACE = 8, + RETROK_TAB = 9, + RETROK_CLEAR = 12, + RETROK_RETURN = 13, + RETROK_PAUSE = 19, + RETROK_ESCAPE = 27, + RETROK_SPACE = 32, + RETROK_EXCLAIM = 33, + RETROK_QUOTEDBL = 34, + RETROK_HASH = 35, + RETROK_DOLLAR = 36, + RETROK_AMPERSAND = 38, + RETROK_QUOTE = 39, + RETROK_LEFTPAREN = 40, + RETROK_RIGHTPAREN = 41, + RETROK_ASTERISK = 42, + RETROK_PLUS = 43, + RETROK_COMMA = 44, + RETROK_MINUS = 45, + RETROK_PERIOD = 46, + RETROK_SLASH = 47, + RETROK_0 = 48, + RETROK_1 = 49, + RETROK_2 = 50, + RETROK_3 = 51, + RETROK_4 = 52, + RETROK_5 = 53, + RETROK_6 = 54, + RETROK_7 = 55, + RETROK_8 = 56, + RETROK_9 = 57, + RETROK_COLON = 58, + RETROK_SEMICOLON = 59, + RETROK_LESS = 60, + RETROK_EQUALS = 61, + RETROK_GREATER = 62, + RETROK_QUESTION = 63, + RETROK_AT = 64, + RETROK_LEFTBRACKET = 91, + RETROK_BACKSLASH = 92, + RETROK_RIGHTBRACKET = 93, + RETROK_CARET = 94, + RETROK_UNDERSCORE = 95, + RETROK_BACKQUOTE = 96, + RETROK_a = 97, + RETROK_b = 98, + RETROK_c = 99, + RETROK_d = 100, + RETROK_e = 101, + RETROK_f = 102, + RETROK_g = 103, + RETROK_h = 104, + RETROK_i = 105, + RETROK_j = 106, + RETROK_k = 107, + RETROK_l = 108, + RETROK_m = 109, + RETROK_n = 110, + RETROK_o = 111, + RETROK_p = 112, + RETROK_q = 113, + RETROK_r = 114, + RETROK_s = 115, + RETROK_t = 116, + RETROK_u = 117, + RETROK_v = 118, + RETROK_w = 119, + RETROK_x = 120, + RETROK_y = 121, + RETROK_z = 122, + RETROK_DELETE = 127, + + RETROK_KP0 = 256, + RETROK_KP1 = 257, + RETROK_KP2 = 258, + RETROK_KP3 = 259, + RETROK_KP4 = 260, + RETROK_KP5 = 261, + RETROK_KP6 = 262, + RETROK_KP7 = 263, + RETROK_KP8 = 264, + RETROK_KP9 = 265, + RETROK_KP_PERIOD = 266, + RETROK_KP_DIVIDE = 267, + RETROK_KP_MULTIPLY = 268, + RETROK_KP_MINUS = 269, + RETROK_KP_PLUS = 270, + RETROK_KP_ENTER = 271, + RETROK_KP_EQUALS = 272, + + RETROK_UP = 273, + RETROK_DOWN = 274, + RETROK_RIGHT = 275, + RETROK_LEFT = 276, + RETROK_INSERT = 277, + RETROK_HOME = 278, + RETROK_END = 279, + RETROK_PAGEUP = 280, + RETROK_PAGEDOWN = 281, + + RETROK_F1 = 282, + RETROK_F2 = 283, + RETROK_F3 = 284, + RETROK_F4 = 285, + RETROK_F5 = 286, + RETROK_F6 = 287, + RETROK_F7 = 288, + RETROK_F8 = 289, + RETROK_F9 = 290, + RETROK_F10 = 291, + RETROK_F11 = 292, + RETROK_F12 = 293, + RETROK_F13 = 294, + RETROK_F14 = 295, + RETROK_F15 = 296, + + RETROK_NUMLOCK = 300, + RETROK_CAPSLOCK = 301, + RETROK_SCROLLOCK = 302, + RETROK_RSHIFT = 303, + RETROK_LSHIFT = 304, + RETROK_RCTRL = 305, + RETROK_LCTRL = 306, + RETROK_RALT = 307, + RETROK_LALT = 308, + RETROK_RMETA = 309, + RETROK_LMETA = 310, + RETROK_LSUPER = 311, + RETROK_RSUPER = 312, + RETROK_MODE = 313, + RETROK_COMPOSE = 314, + + RETROK_HELP = 315, + RETROK_PRINT = 316, + RETROK_SYSREQ = 317, + RETROK_BREAK = 318, + RETROK_MENU = 319, + RETROK_POWER = 320, + RETROK_EURO = 321, + RETROK_UNDO = 322, + + RETROK_LAST, + + RETROK_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ +}; + +enum retro_mod +{ + RETROKMOD_NONE = 0x0000, + + RETROKMOD_SHIFT = 0x01, + RETROKMOD_CTRL = 0x02, + RETROKMOD_ALT = 0x04, + RETROKMOD_META = 0x08, + + RETROKMOD_NUMLOCK = 0x10, + RETROKMOD_CAPSLOCK = 0x20, + RETROKMOD_SCROLLOCK = 0x40, + + RETROKMOD_DUMMY = INT_MAX /* Ensure sizeof(enum) == sizeof(int) */ +}; + +/* If set, this call is not part of the public libretro API yet. It can + * change or be removed at any time. */ +#define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000 +/* Environment callback to be used internally in frontend. */ +#define RETRO_ENVIRONMENT_PRIVATE 0x20000 + +/* Environment commands. */ +#define RETRO_ENVIRONMENT_SET_ROTATION 1 /* const unsigned * -- + * Sets screen rotation of graphics. + * Is only implemented if rotation can be accelerated by hardware. + * Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, + * 270 degrees counter-clockwise respectively. + */ +#define RETRO_ENVIRONMENT_GET_OVERSCAN 2 /* bool * -- + * Boolean value whether or not the implementation should use overscan, + * or crop away overscan. + */ +#define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 /* bool * -- + * Boolean value whether or not frontend supports frame duping, + * passing NULL to video frame callback. + */ + + /* Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), + * and reserved to avoid possible ABI clash. + */ + +#define RETRO_ENVIRONMENT_SET_MESSAGE 6 /* const struct retro_message * -- + * Sets a message to be displayed in implementation-specific manner + * for a certain amount of 'frames'. + * Should not be used for trivial messages, which should simply be + * logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a + * fallback, stderr). + */ +#define RETRO_ENVIRONMENT_SHUTDOWN 7 /* N/A (NULL) -- + * Requests the frontend to shutdown. + * Should only be used if game has a specific + * way to shutdown the game from a menu item or similar. + */ +#define RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL 8 + /* const unsigned * -- + * Gives a hint to the frontend how demanding this implementation + * is on a system. E.g. reporting a level of 2 means + * this implementation should run decently on all frontends + * of level 2 and up. + * + * It can be used by the frontend to potentially warn + * about too demanding implementations. + * + * The levels are "floating". + * + * This function can be called on a per-game basis, + * as certain games an implementation can play might be + * particularly demanding. + * If called, it should be called in retro_load_game(). + */ +#define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9 + /* const char ** -- + * Returns the "system" directory of the frontend. + * This directory can be used to store system specific + * content such as BIOSes, configuration data, etc. + * The returned value can be NULL. + * If so, no such directory is defined, + * and it's up to the implementation to find a suitable directory. + * + * NOTE: Some cores used this folder also for "save" data such as + * memory cards, etc, for lack of a better place to put it. + * This is now discouraged, and if possible, cores should try to + * use the new GET_SAVE_DIRECTORY. + */ +#define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 + /* const enum retro_pixel_format * -- + * Sets the internal pixel format used by the implementation. + * The default pixel format is RETRO_PIXEL_FORMAT_0RGB1555. + * This pixel format however, is deprecated (see enum retro_pixel_format). + * If the call returns false, the frontend does not support this pixel + * format. + * + * This function should be called inside retro_load_game() or + * retro_get_system_av_info(). + */ +#define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11 + /* const struct retro_input_descriptor * -- + * Sets an array of retro_input_descriptors. + * It is up to the frontend to present this in a usable way. + * The array is terminated by retro_input_descriptor::description + * being set to NULL. + * This function can be called at any time, but it is recommended + * to call it as early as possible. + */ +#define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12 + /* const struct retro_keyboard_callback * -- + * Sets a callback function used to notify core about keyboard events. + */ +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13 + /* const struct retro_disk_control_callback * -- + * Sets an interface which frontend can use to eject and insert + * disk images. + * This is used for games which consist of multiple images and + * must be manually swapped out by the user (e.g. PSX). + */ +#define RETRO_ENVIRONMENT_SET_HW_RENDER 14 + /* struct retro_hw_render_callback * -- + * Sets an interface to let a libretro core render with + * hardware acceleration. + * Should be called in retro_load_game(). + * If successful, libretro cores will be able to render to a + * frontend-provided framebuffer. + * The size of this framebuffer will be at least as large as + * max_width/max_height provided in get_av_info(). + * If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or + * NULL to retro_video_refresh_t. + */ +#define RETRO_ENVIRONMENT_GET_VARIABLE 15 + /* struct retro_variable * -- + * Interface to acquire user-defined information from environment + * that cannot feasibly be supported in a multi-system way. + * 'key' should be set to a key which has already been set by + * SET_VARIABLES. + * 'data' will be set to a value or NULL. + */ +#define RETRO_ENVIRONMENT_SET_VARIABLES 16 + /* const struct retro_variable * -- + * Allows an implementation to signal the environment + * which variables it might want to check for later using + * GET_VARIABLE. + * This allows the frontend to present these variables to + * a user dynamically. + * This should be called as early as possible (ideally in + * retro_set_environment). + * + * 'data' points to an array of retro_variable structs + * terminated by a { NULL, NULL } element. + * retro_variable::key should be namespaced to not collide + * with other implementations' keys. E.g. A core called + * 'foo' should use keys named as 'foo_option'. + * retro_variable::value should contain a human readable + * description of the key as well as a '|' delimited list + * of expected values. + * + * The number of possible options should be very limited, + * i.e. it should be feasible to cycle through options + * without a keyboard. + * + * First entry should be treated as a default. + * + * Example entry: + * { "foo_option", "Speed hack coprocessor X; false|true" } + * + * Text before first ';' is description. This ';' must be + * followed by a space, and followed by a list of possible + * values split up with '|'. + * + * Only strings are operated on. The possible values will + * generally be displayed and stored as-is by the frontend. + */ +#define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17 + /* bool * -- + * Result is set to true if some variables are updated by + * frontend since last call to RETRO_ENVIRONMENT_GET_VARIABLE. + * Variables should be queried with GET_VARIABLE. + */ +#define RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME 18 + /* const bool * -- + * If true, the libretro implementation supports calls to + * retro_load_game() with NULL as argument. + * Used by cores which can run without particular game data. + * This should be called within retro_set_environment() only. + */ +#define RETRO_ENVIRONMENT_GET_LIBRETRO_PATH 19 + /* const char ** -- + * Retrieves the absolute path from where this libretro + * implementation was loaded. + * NULL is returned if the libretro was loaded statically + * (i.e. linked statically to frontend), or if the path cannot be + * determined. + * Mostly useful in cooperation with SET_SUPPORT_NO_GAME as assets can + * be loaded without ugly hacks. + */ + + /* Environment 20 was an obsolete version of SET_AUDIO_CALLBACK. + * It was not used by any known core at the time, + * and was removed from the API. */ +#define RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK 22 + /* const struct retro_audio_callback * -- + * Sets an interface which is used to notify a libretro core about audio + * being available for writing. + * The callback can be called from any thread, so a core using this must + * have a thread safe audio implementation. + * It is intended for games where audio and video are completely + * asynchronous and audio can be generated on the fly. + * This interface is not recommended for use with emulators which have + * highly synchronous audio. + * + * The callback only notifies about writability; the libretro core still + * has to call the normal audio callbacks + * to write audio. The audio callbacks must be called from within the + * notification callback. + * The amount of audio data to write is up to the implementation. + * Generally, the audio callback will be called continously in a loop. + * + * Due to thread safety guarantees and lack of sync between audio and + * video, a frontend can selectively disallow this interface based on + * internal configuration. A core using this interface must also + * implement the "normal" audio interface. + * + * A libretro core using SET_AUDIO_CALLBACK should also make use of + * SET_FRAME_TIME_CALLBACK. + */ +#define RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK 21 + /* const struct retro_frame_time_callback * -- + * Lets the core know how much time has passed since last + * invocation of retro_run(). + * The frontend can tamper with the timing to fake fast-forward, + * slow-motion, frame stepping, etc. + * In this case the delta time will use the reference value + * in frame_time_callback.. + */ +#define RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE 23 + /* struct retro_rumble_interface * -- + * Gets an interface which is used by a libretro core to set + * state of rumble motors in controllers. + * A strong and weak motor is supported, and they can be + * controlled indepedently. + */ +#define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24 + /* uint64_t * -- + * Gets a bitmask telling which device type are expected to be + * handled properly in a call to retro_input_state_t. + * Devices which are not handled or recognized always return + * 0 in retro_input_state_t. + * Example bitmask: caps = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG). + * Should only be called in retro_run(). + */ +#define RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE (25 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_sensor_interface * -- + * Gets access to the sensor interface. + * The purpose of this interface is to allow + * setting state related to sensors such as polling rate, + * enabling/disable it entirely, etc. + * Reading sensor state is done via the normal + * input_state_callback API. + */ +#define RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE (26 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* struct retro_camera_callback * -- + * Gets an interface to a video camera driver. + * A libretro core can use this interface to get access to a + * video camera. + * New video frames are delivered in a callback in same + * thread as retro_run(). + * + * GET_CAMERA_INTERFACE should be called in retro_load_game(). + * + * Depending on the camera implementation used, camera frames + * will be delivered as a raw framebuffer, + * or as an OpenGL texture directly. + * + * The core has to tell the frontend here which types of + * buffers can be handled properly. + * An OpenGL texture can only be handled when using a + * libretro GL core (SET_HW_RENDER). + * It is recommended to use a libretro GL core when + * using camera interface. + * + * The camera is not started automatically. The retrieved start/stop + * functions must be used to explicitly + * start and stop the camera driver. + */ +#define RETRO_ENVIRONMENT_GET_LOG_INTERFACE 27 + /* struct retro_log_callback * -- + * Gets an interface for logging. This is useful for + * logging in a cross-platform way + * as certain platforms cannot use use stderr for logging. + * It also allows the frontend to + * show logging information in a more suitable way. + * If this interface is not used, libretro cores should + * log to stderr as desired. + */ +#define RETRO_ENVIRONMENT_GET_PERF_INTERFACE 28 + /* struct retro_perf_callback * -- + * Gets an interface for performance counters. This is useful + * for performance logging in a cross-platform way and for detecting + * architecture-specific features, such as SIMD support. + */ +#define RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE 29 + /* struct retro_location_callback * -- + * Gets access to the location interface. + * The purpose of this interface is to be able to retrieve + * location-based information from the host device, + * such as current latitude / longitude. + */ +#define RETRO_ENVIRONMENT_GET_CONTENT_DIRECTORY 30 + /* const char ** -- + * Returns the "content" directory of the frontend. + * This directory can be used to store specific assets that the + * core relies upon, such as art assets, + * input data, etc etc. + * The returned value can be NULL. + * If so, no such directory is defined, + * and it's up to the implementation to find a suitable directory. + */ +#define RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY 31 + /* const char ** -- + * Returns the "save" directory of the frontend. + * This directory can be used to store SRAM, memory cards, + * high scores, etc, if the libretro core + * cannot use the regular memory interface (retro_get_memory_data()). + * + * NOTE: libretro cores used to check GET_SYSTEM_DIRECTORY for + * similar things before. + * They should still check GET_SYSTEM_DIRECTORY if they want to + * be backwards compatible. + * The path here can be NULL. It should only be non-NULL if the + * frontend user has set a specific save path. + */ +#define RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO 32 + /* const struct retro_system_av_info * -- + * Sets a new av_info structure. This can only be called from + * within retro_run(). + * This should *only* be used if the core is completely altering the + * internal resolutions, aspect ratios, timings, sampling rate, etc. + * Calling this can require a full reinitialization of video/audio + * drivers in the frontend, + * + * so it is important to call it very sparingly, and usually only with + * the users explicit consent. + * An eventual driver reinitialize will happen so that video and + * audio callbacks + * happening after this call within the same retro_run() call will + * target the newly initialized driver. + * + * This callback makes it possible to support configurable resolutions + * in games, which can be useful to + * avoid setting the "worst case" in max_width/max_height. + * + * ***HIGHLY RECOMMENDED*** Do not call this callback every time + * resolution changes in an emulator core if it's + * expected to be a temporary change, for the reasons of possible + * driver reinitialization. + * This call is not a free pass for not trying to provide + * correct values in retro_get_system_av_info(). If you need to change + * things like aspect ratio or nominal width/height, + * use RETRO_ENVIRONMENT_SET_GEOMETRY, which is a softer variant + * of SET_SYSTEM_AV_INFO. + * + * If this returns false, the frontend does not acknowledge a + * changed av_info struct. + */ +#define RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK 33 + /* const struct retro_get_proc_address_interface * -- + * Allows a libretro core to announce support for the + * get_proc_address() interface. + * This interface allows for a standard way to extend libretro where + * use of environment calls are too indirect, + * e.g. for cases where the frontend wants to call directly into the core. + * + * If a core wants to expose this interface, SET_PROC_ADDRESS_CALLBACK + * **MUST** be called from within retro_set_environment(). + */ +#define RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO 34 + /* const struct retro_subsystem_info * -- + * This environment call introduces the concept of libretro "subsystems". + * A subsystem is a variant of a libretro core which supports + * different kinds of games. + * The purpose of this is to support e.g. emulators which might + * have special needs, e.g. Super Nintendo's Super GameBoy, Sufami Turbo. + * It can also be used to pick among subsystems in an explicit way + * if the libretro implementation is a multi-system emulator itself. + * + * Loading a game via a subsystem is done with retro_load_game_special(), + * and this environment call allows a libretro core to expose which + * subsystems are supported for use with retro_load_game_special(). + * A core passes an array of retro_game_special_info which is terminated + * with a zeroed out retro_game_special_info struct. + * + * If a core wants to use this functionality, SET_SUBSYSTEM_INFO + * **MUST** be called from within retro_set_environment(). + */ +#define RETRO_ENVIRONMENT_SET_CONTROLLER_INFO 35 + /* const struct retro_controller_info * -- + * This environment call lets a libretro core tell the frontend + * which controller types are recognized in calls to + * retro_set_controller_port_device(). + * + * Some emulators such as Super Nintendo + * support multiple lightgun types which must be specifically + * selected from. + * It is therefore sometimes necessary for a frontend to be able + * to tell the core about a special kind of input device which is + * not covered by the libretro input API. + * + * In order for a frontend to understand the workings of an input device, + * it must be a specialized type + * of the generic device types already defined in the libretro API. + * + * Which devices are supported can vary per input port. + * The core must pass an array of const struct retro_controller_info which + * is terminated with a blanked out struct. Each element of the struct + * corresponds to an ascending port index to + * retro_set_controller_port_device(). + * Even if special device types are set in the libretro core, + * libretro should only poll input based on the base input device types. + */ +#define RETRO_ENVIRONMENT_SET_MEMORY_MAPS (36 | RETRO_ENVIRONMENT_EXPERIMENTAL) + /* const struct retro_memory_map * -- + * This environment call lets a libretro core tell the frontend + * about the memory maps this core emulates. + * This can be used to implement, for example, cheats in a core-agnostic way. + * + * Should only be used by emulators; it doesn't make much sense for + * anything else. + * It is recommended to expose all relevant pointers through + * retro_get_memory_* as well. + * + * Can be called from retro_init and retro_load_game. + */ +#define RETRO_ENVIRONMENT_SET_GEOMETRY 37 + /* const struct retro_game_geometry * -- + * This environment call is similar to SET_SYSTEM_AV_INFO for changing + * video parameters, but provides a guarantee that drivers will not be + * reinitialized. + * This can only be called from within retro_run(). + * + * The purpose of this call is to allow a core to alter nominal + * width/heights as well as aspect ratios on-the-fly, which can be + * useful for some emulators to change in run-time. + * + * max_width/max_height arguments are ignored and cannot be changed + * with this call as this could potentially require a reinitialization or a + * non-constant time operation. + * If max_width/max_height are to be changed, SET_SYSTEM_AV_INFO is required. + * + * A frontend must guarantee that this environment call completes in + * constant time. + */ +#define RETRO_ENVIRONMENT_GET_USERNAME 38 + /* const char ** + * Returns the specified username of the frontend, if specified by the user. + * This username can be used as a nickname for a core that has online facilities + * or any other mode where personalization of the user is desirable. + * The returned value can be NULL. + * If this environ callback is used by a core that requires a valid username, + * a default username should be specified by the core. + */ +#define RETRO_ENVIRONMENT_GET_LANGUAGE 39 + /* unsigned * -- + * Returns the specified language of the frontend, if specified by the user. + * It can be used by the core for localization purposes. + */ + +#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */ +#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */ +#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */ +#define RETRO_MEMDESC_ALIGN_4 (2 << 16) +#define RETRO_MEMDESC_ALIGN_8 (3 << 16) +#define RETRO_MEMDESC_MINSIZE_2 (1 << 24) /* All memory in this region is accessed at least 2 bytes at the time. */ +#define RETRO_MEMDESC_MINSIZE_4 (2 << 24) +#define RETRO_MEMDESC_MINSIZE_8 (3 << 24) +struct retro_memory_descriptor +{ + uint64_t flags; + + /* Pointer to the start of the relevant ROM or RAM chip. + * It's strongly recommended to use 'offset' if possible, rather than + * doing math on the pointer. + * + * If the same byte is mapped my multiple descriptors, their descriptors + * must have the same pointer. + * If 'start' does not point to the first byte in the pointer, put the + * difference in 'offset' instead. + * + * May be NULL if there's nothing usable here (e.g. hardware registers and + * open bus). No flags should be set if the pointer is NULL. + * It's recommended to minimize the number of descriptors if possible, + * but not mandatory. */ + void *ptr; + size_t offset; + + /* This is the location in the emulated address space + * where the mapping starts. */ + size_t start; + + /* Which bits must be same as in 'start' for this mapping to apply. + * The first memory descriptor to claim a certain byte is the one + * that applies. + * A bit which is set in 'start' must also be set in this. + * Can be zero, in which case each byte is assumed mapped exactly once. + * In this case, 'len' must be a power of two. */ + size_t select; + + /* If this is nonzero, the set bits are assumed not connected to the + * memory chip's address pins. */ + size_t disconnect; + + /* This one tells the size of the current memory area. + * If, after start+disconnect are applied, the address is higher than + * this, the highest bit of the address is cleared. + * + * If the address is still too high, the next highest bit is cleared. + * Can be zero, in which case it's assumed to be infinite (as limited + * by 'select' and 'disconnect'). */ + size_t len; + + /* To go from emulated address to physical address, the following + * order applies: + * Subtract 'start', pick off 'disconnect', apply 'len', add 'offset'. + * + * The address space name must consist of only a-zA-Z0-9_-, + * should be as short as feasible (maximum length is 8 plus the NUL), + * and may not be any other address space plus one or more 0-9A-F + * at the end. + * However, multiple memory descriptors for the same address space is + * allowed, and the address space name can be empty. NULL is treated + * as empty. + * + * Address space names are case sensitive, but avoid lowercase if possible. + * The same pointer may exist in multiple address spaces. + * + * Examples: + * blank+blank - valid (multiple things may be mapped in the same namespace) + * 'Sp'+'Sp' - valid (multiple things may be mapped in the same namespace) + * 'A'+'B' - valid (neither is a prefix of each other) + * 'S'+blank - valid ('S' is not in 0-9A-F) + * 'a'+blank - valid ('a' is not in 0-9A-F) + * 'a'+'A' - valid (neither is a prefix of each other) + * 'AR'+blank - valid ('R' is not in 0-9A-F) + * 'ARB'+blank - valid (the B can't be part of the address either, because + * there is no namespace 'AR') + * blank+'B' - not valid, because it's ambigous which address space B1234 + * would refer to. + * The length can't be used for that purpose; the frontend may want + * to append arbitrary data to an address, without a separator. */ + const char *addrspace; +}; + +/* The frontend may use the largest value of 'start'+'select' in a + * certain namespace to infer the size of the address space. + * + * If the address space is larger than that, a mapping with .ptr=NULL + * should be at the end of the array, with .select set to all ones for + * as long as the address space is big. + * + * Sample descriptors (minus .ptr, and RETRO_MEMFLAG_ on the flags): + * SNES WRAM: + * .start=0x7E0000, .len=0x20000 + * (Note that this must be mapped before the ROM in most cases; some of the + * ROM mappers + * try to claim $7E0000, or at least $7E8000.) + * SNES SPC700 RAM: + * .addrspace="S", .len=0x10000 + * SNES WRAM mirrors: + * .flags=MIRROR, .start=0x000000, .select=0xC0E000, .len=0x2000 + * .flags=MIRROR, .start=0x800000, .select=0xC0E000, .len=0x2000 + * SNES WRAM mirrors, alternate equivalent descriptor: + * .flags=MIRROR, .select=0x40E000, .disconnect=~0x1FFF + * (Various similar constructions can be created by combining parts of + * the above two.) + * SNES LoROM (512KB, mirrored a couple of times): + * .flags=CONST, .start=0x008000, .select=0x408000, .disconnect=0x8000, .len=512*1024 + * .flags=CONST, .start=0x400000, .select=0x400000, .disconnect=0x8000, .len=512*1024 + * SNES HiROM (4MB): + * .flags=CONST, .start=0x400000, .select=0x400000, .len=4*1024*1024 + * .flags=CONST, .offset=0x8000, .start=0x008000, .select=0x408000, .len=4*1024*1024 + * SNES ExHiROM (8MB): + * .flags=CONST, .offset=0, .start=0xC00000, .select=0xC00000, .len=4*1024*1024 + * .flags=CONST, .offset=4*1024*1024, .start=0x400000, .select=0xC00000, .len=4*1024*1024 + * .flags=CONST, .offset=0x8000, .start=0x808000, .select=0xC08000, .len=4*1024*1024 + * .flags=CONST, .offset=4*1024*1024+0x8000, .start=0x008000, .select=0xC08000, .len=4*1024*1024 + * Clarify the size of the address space: + * .ptr=NULL, .select=0xFFFFFF + * .len can be implied by .select in many of them, but was included for clarity. + */ + +struct retro_memory_map +{ + const struct retro_memory_descriptor *descriptors; + unsigned num_descriptors; +}; + +struct retro_controller_description +{ + /* Human-readable description of the controller. Even if using a generic + * input device type, this can be set to the particular device type the + * core uses. */ + const char *desc; + + /* Device type passed to retro_set_controller_port_device(). If the device + * type is a sub-class of a generic input device type, use the + * RETRO_DEVICE_SUBCLASS macro to create an ID. + * + * E.g. RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1). */ + unsigned id; +}; + +struct retro_controller_info +{ + const struct retro_controller_description *types; + unsigned num_types; +}; + +struct retro_subsystem_memory_info +{ + /* The extension associated with a memory type, e.g. "psram". */ + const char *extension; + + /* The memory type for retro_get_memory(). This should be at + * least 0x100 to avoid conflict with standardized + * libretro memory types. */ + unsigned type; +}; + +struct retro_subsystem_rom_info +{ + /* Describes what the content is (SGB BIOS, GB ROM, etc). */ + const char *desc; + + /* Same definition as retro_get_system_info(). */ + const char *valid_extensions; + + /* Same definition as retro_get_system_info(). */ + bool need_fullpath; + + /* Same definition as retro_get_system_info(). */ + bool block_extract; + + /* This is set if the content is required to load a game. + * If this is set to false, a zeroed-out retro_game_info can be passed. */ + bool required; + + /* Content can have multiple associated persistent + * memory types (retro_get_memory()). */ + const struct retro_subsystem_memory_info *memory; + unsigned num_memory; +}; + +struct retro_subsystem_info +{ + /* Human-readable string of the subsystem type, e.g. "Super GameBoy" */ + const char *desc; + + /* A computer friendly short string identifier for the subsystem type. + * This name must be [a-z]. + * E.g. if desc is "Super GameBoy", this can be "sgb". + * This identifier can be used for command-line interfaces, etc. + */ + const char *ident; + + /* Infos for each content file. The first entry is assumed to be the + * "most significant" content for frontend purposes. + * E.g. with Super GameBoy, the first content should be the GameBoy ROM, + * as it is the most "significant" content to a user. + * If a frontend creates new file paths based on the content used + * (e.g. savestates), it should use the path for the first ROM to do so. */ + const struct retro_subsystem_rom_info *roms; + + /* Number of content files associated with a subsystem. */ + unsigned num_roms; + + /* The type passed to retro_load_game_special(). */ + unsigned id; +}; + +typedef void (*retro_proc_address_t)(void); + +/* libretro API extension functions: + * (None here so far). + * + * Get a symbol from a libretro core. + * Cores should only return symbols which are actual + * extensions to the libretro API. + * + * Frontends should not use this to obtain symbols to standard + * libretro entry points (static linking or dlsym). + * + * The symbol name must be equal to the function name, + * e.g. if void retro_foo(void); exists, the symbol must be called "retro_foo". + * The returned function pointer must be cast to the corresponding type. + */ +typedef retro_proc_address_t (*retro_get_proc_address_t)(const char *sym); + +struct retro_get_proc_address_interface +{ + retro_get_proc_address_t get_proc_address; +}; + +enum retro_log_level +{ + RETRO_LOG_DEBUG = 0, + RETRO_LOG_INFO, + RETRO_LOG_WARN, + RETRO_LOG_ERROR, + + RETRO_LOG_DUMMY = INT_MAX +}; + +/* Logging function. Takes log level argument as well. */ +typedef void (*retro_log_printf_t)(enum retro_log_level level, + const char *fmt, ...); + +struct retro_log_callback +{ + retro_log_printf_t log; +}; + +/* Performance related functions */ + +/* ID values for SIMD CPU features */ +#define RETRO_SIMD_SSE (1 << 0) +#define RETRO_SIMD_SSE2 (1 << 1) +#define RETRO_SIMD_VMX (1 << 2) +#define RETRO_SIMD_VMX128 (1 << 3) +#define RETRO_SIMD_AVX (1 << 4) +#define RETRO_SIMD_NEON (1 << 5) +#define RETRO_SIMD_SSE3 (1 << 6) +#define RETRO_SIMD_SSSE3 (1 << 7) +#define RETRO_SIMD_MMX (1 << 8) +#define RETRO_SIMD_MMXEXT (1 << 9) +#define RETRO_SIMD_SSE4 (1 << 10) +#define RETRO_SIMD_SSE42 (1 << 11) +#define RETRO_SIMD_AVX2 (1 << 12) +#define RETRO_SIMD_VFPU (1 << 13) +#define RETRO_SIMD_PS (1 << 14) +#define RETRO_SIMD_AES (1 << 15) + +typedef uint64_t retro_perf_tick_t; +typedef int64_t retro_time_t; + +struct retro_perf_counter +{ + const char *ident; + retro_perf_tick_t start; + retro_perf_tick_t total; + retro_perf_tick_t call_cnt; + + bool registered; +}; + +/* Returns current time in microseconds. + * Tries to use the most accurate timer available. + */ +typedef retro_time_t (*retro_perf_get_time_usec_t)(void); + +/* A simple counter. Usually nanoseconds, but can also be CPU cycles. + * Can be used directly if desired (when creating a more sophisticated + * performance counter system). + * */ +typedef retro_perf_tick_t (*retro_perf_get_counter_t)(void); + +/* Returns a bit-mask of detected CPU features (RETRO_SIMD_*). */ +typedef uint64_t (*retro_get_cpu_features_t)(void); + +/* Asks frontend to log and/or display the state of performance counters. + * Performance counters can always be poked into manually as well. + */ +typedef void (*retro_perf_log_t)(void); + +/* Register a performance counter. + * ident field must be set with a discrete value and other values in + * retro_perf_counter must be 0. + * Registering can be called multiple times. To avoid calling to + * frontend redundantly, you can check registered field first. */ +typedef void (*retro_perf_register_t)(struct retro_perf_counter *counter); + +/* Starts a registered counter. */ +typedef void (*retro_perf_start_t)(struct retro_perf_counter *counter); + +/* Stops a registered counter. */ +typedef void (*retro_perf_stop_t)(struct retro_perf_counter *counter); + +/* For convenience it can be useful to wrap register, start and stop in macros. + * E.g.: + * #ifdef LOG_PERFORMANCE + * #define RETRO_PERFORMANCE_INIT(perf_cb, name) static struct retro_perf_counter name = {#name}; if (!name.registered) perf_cb.perf_register(&(name)) + * #define RETRO_PERFORMANCE_START(perf_cb, name) perf_cb.perf_start(&(name)) + * #define RETRO_PERFORMANCE_STOP(perf_cb, name) perf_cb.perf_stop(&(name)) + * #else + * ... Blank macros ... + * #endif + * + * These can then be used mid-functions around code snippets. + * + * extern struct retro_perf_callback perf_cb; * Somewhere in the core. + * + * void do_some_heavy_work(void) + * { + * RETRO_PERFORMANCE_INIT(cb, work_1; + * RETRO_PERFORMANCE_START(cb, work_1); + * heavy_work_1(); + * RETRO_PERFORMANCE_STOP(cb, work_1); + * + * RETRO_PERFORMANCE_INIT(cb, work_2); + * RETRO_PERFORMANCE_START(cb, work_2); + * heavy_work_2(); + * RETRO_PERFORMANCE_STOP(cb, work_2); + * } + * + * void retro_deinit(void) + * { + * perf_cb.perf_log(); * Log all perf counters here for example. + * } + */ + +struct retro_perf_callback +{ + retro_perf_get_time_usec_t get_time_usec; + retro_get_cpu_features_t get_cpu_features; + + retro_perf_get_counter_t get_perf_counter; + retro_perf_register_t perf_register; + retro_perf_start_t perf_start; + retro_perf_stop_t perf_stop; + retro_perf_log_t perf_log; +}; + +/* FIXME: Document the sensor API and work out behavior. + * It will be marked as experimental until then. + */ +enum retro_sensor_action +{ + RETRO_SENSOR_ACCELEROMETER_ENABLE = 0, + RETRO_SENSOR_ACCELEROMETER_DISABLE, + + RETRO_SENSOR_DUMMY = INT_MAX +}; + +/* Id values for SENSOR types. */ +#define RETRO_SENSOR_ACCELEROMETER_X 0 +#define RETRO_SENSOR_ACCELEROMETER_Y 1 +#define RETRO_SENSOR_ACCELEROMETER_Z 2 + +typedef bool (*retro_set_sensor_state_t)(unsigned port, + enum retro_sensor_action action, unsigned rate); + +typedef float (*retro_sensor_get_input_t)(unsigned port, unsigned id); + +struct retro_sensor_interface +{ + retro_set_sensor_state_t set_sensor_state; + retro_sensor_get_input_t get_sensor_input; +}; + +enum retro_camera_buffer +{ + RETRO_CAMERA_BUFFER_OPENGL_TEXTURE = 0, + RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER, + + RETRO_CAMERA_BUFFER_DUMMY = INT_MAX +}; + +/* Starts the camera driver. Can only be called in retro_run(). */ +typedef bool (*retro_camera_start_t)(void); + +/* Stops the camera driver. Can only be called in retro_run(). */ +typedef void (*retro_camera_stop_t)(void); + +/* Callback which signals when the camera driver is initialized + * and/or deinitialized. + * retro_camera_start_t can be called in initialized callback. + */ +typedef void (*retro_camera_lifetime_status_t)(void); + +/* A callback for raw framebuffer data. buffer points to an XRGB8888 buffer. + * Width, height and pitch are similar to retro_video_refresh_t. + * First pixel is top-left origin. + */ +typedef void (*retro_camera_frame_raw_framebuffer_t)(const uint32_t *buffer, + unsigned width, unsigned height, size_t pitch); + +/* A callback for when OpenGL textures are used. + * + * texture_id is a texture owned by camera driver. + * Its state or content should be considered immutable, except for things like + * texture filtering and clamping. + * + * texture_target is the texture target for the GL texture. + * These can include e.g. GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE, and possibly + * more depending on extensions. + * + * affine points to a packed 3x3 column-major matrix used to apply an affine + * transform to texture coordinates. (affine_matrix * vec3(coord_x, coord_y, 1.0)) + * After transform, normalized texture coord (0, 0) should be bottom-left + * and (1, 1) should be top-right (or (width, height) for RECTANGLE). + * + * GL-specific typedefs are avoided here to avoid relying on gl.h in + * the API definition. + */ +typedef void (*retro_camera_frame_opengl_texture_t)(unsigned texture_id, + unsigned texture_target, const float *affine); + +struct retro_camera_callback +{ + /* Set by libretro core. + * Example bitmask: caps = (1 << RETRO_CAMERA_BUFFER_OPENGL_TEXTURE) | (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER). + */ + uint64_t caps; + + unsigned width; /* Desired resolution for camera. Is only used as a hint. */ + unsigned height; + retro_camera_start_t start; /* Set by frontend. */ + retro_camera_stop_t stop; /* Set by frontend. */ + + /* Set by libretro core if raw framebuffer callbacks will be used. */ + retro_camera_frame_raw_framebuffer_t frame_raw_framebuffer; + /* Set by libretro core if OpenGL texture callbacks will be used. */ + retro_camera_frame_opengl_texture_t frame_opengl_texture; + + /* Set by libretro core. Called after camera driver is initialized and + * ready to be started. + * Can be NULL, in which this callback is not called. + */ + retro_camera_lifetime_status_t initialized; + + /* Set by libretro core. Called right before camera driver is + * deinitialized. + * Can be NULL, in which this callback is not called. + */ + retro_camera_lifetime_status_t deinitialized; +}; + +/* Sets the interval of time and/or distance at which to update/poll + * location-based data. + * + * To ensure compatibility with all location-based implementations, + * values for both interval_ms and interval_distance should be provided. + * + * interval_ms is the interval expressed in milliseconds. + * interval_distance is the distance interval expressed in meters. + */ +typedef void (*retro_location_set_interval_t)(unsigned interval_ms, + unsigned interval_distance); + +/* Start location services. The device will start listening for changes to the + * current location at regular intervals (which are defined with + * retro_location_set_interval_t). */ +typedef bool (*retro_location_start_t)(void); + +/* Stop location services. The device will stop listening for changes + * to the current location. */ +typedef void (*retro_location_stop_t)(void); + +/* Get the position of the current location. Will set parameters to + * 0 if no new location update has happened since the last time. */ +typedef bool (*retro_location_get_position_t)(double *lat, double *lon, + double *horiz_accuracy, double *vert_accuracy); + +/* Callback which signals when the location driver is initialized + * and/or deinitialized. + * retro_location_start_t can be called in initialized callback. + */ +typedef void (*retro_location_lifetime_status_t)(void); + +struct retro_location_callback +{ + retro_location_start_t start; + retro_location_stop_t stop; + retro_location_get_position_t get_position; + retro_location_set_interval_t set_interval; + + retro_location_lifetime_status_t initialized; + retro_location_lifetime_status_t deinitialized; +}; + +enum retro_rumble_effect +{ + RETRO_RUMBLE_STRONG = 0, + RETRO_RUMBLE_WEAK = 1, + + RETRO_RUMBLE_DUMMY = INT_MAX +}; + +/* Sets rumble state for joypad plugged in port 'port'. + * Rumble effects are controlled independently, + * and setting e.g. strong rumble does not override weak rumble. + * Strength has a range of [0, 0xffff]. + * + * Returns true if rumble state request was honored. + * Calling this before first retro_run() is likely to return false. */ +typedef bool (*retro_set_rumble_state_t)(unsigned port, + enum retro_rumble_effect effect, uint16_t strength); + +struct retro_rumble_interface +{ + retro_set_rumble_state_t set_rumble_state; +}; + +/* Notifies libretro that audio data should be written. */ +typedef void (*retro_audio_callback_t)(void); + +/* True: Audio driver in frontend is active, and callback is + * expected to be called regularily. + * False: Audio driver in frontend is paused or inactive. + * Audio callback will not be called until set_state has been + * called with true. + * Initial state is false (inactive). + */ +typedef void (*retro_audio_set_state_callback_t)(bool enabled); + +struct retro_audio_callback +{ + retro_audio_callback_t callback; + retro_audio_set_state_callback_t set_state; +}; + +/* Notifies a libretro core of time spent since last invocation + * of retro_run() in microseconds. + * + * It will be called right before retro_run() every frame. + * The frontend can tamper with timing to support cases like + * fast-forward, slow-motion and framestepping. + * + * In those scenarios the reference frame time value will be used. */ +typedef int64_t retro_usec_t; +typedef void (*retro_frame_time_callback_t)(retro_usec_t usec); +struct retro_frame_time_callback +{ + retro_frame_time_callback_t callback; + /* Represents the time of one frame. It is computed as + * 1000000 / fps, but the implementation will resolve the + * rounding to ensure that framestepping, etc is exact. */ + retro_usec_t reference; +}; + +/* Pass this to retro_video_refresh_t if rendering to hardware. + * Passing NULL to retro_video_refresh_t is still a frame dupe as normal. + * */ +#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1) + +/* Invalidates the current HW context. + * Any GL state is lost, and must not be deinitialized explicitly. + * If explicit deinitialization is desired by the libretro core, + * it should implement context_destroy callback. + * If called, all GPU resources must be reinitialized. + * Usually called when frontend reinits video driver. + * Also called first time video driver is initialized, + * allowing libretro core to initialize resources. + */ +typedef void (*retro_hw_context_reset_t)(void); + +/* Gets current framebuffer which is to be rendered to. + * Could change every frame potentially. + */ +typedef uintptr_t (*retro_hw_get_current_framebuffer_t)(void); + +/* Get a symbol from HW context. */ +typedef retro_proc_address_t (*retro_hw_get_proc_address_t)(const char *sym); + +enum retro_hw_context_type +{ + RETRO_HW_CONTEXT_NONE = 0, + /* OpenGL 2.x. Driver can choose to use latest compatibility context. */ + RETRO_HW_CONTEXT_OPENGL = 1, + /* OpenGL ES 2.0. */ + RETRO_HW_CONTEXT_OPENGLES2 = 2, + /* Modern desktop core GL context. Use version_major/ + * version_minor fields to set GL version. */ + RETRO_HW_CONTEXT_OPENGL_CORE = 3, + /* OpenGL ES 3.0 */ + RETRO_HW_CONTEXT_OPENGLES3 = 4, + /* OpenGL ES 3.1+. Set version_major/version_minor. For GLES2 and GLES3, + * use the corresponding enums directly. */ + RETRO_HW_CONTEXT_OPENGLES_VERSION = 5, + + RETRO_HW_CONTEXT_DUMMY = INT_MAX +}; + +struct retro_hw_render_callback +{ + /* Which API to use. Set by libretro core. */ + enum retro_hw_context_type context_type; + + /* Called when a context has been created or when it has been reset. + * An OpenGL context is only valid after context_reset() has been called. + * + * When context_reset is called, OpenGL resources in the libretro + * implementation are guaranteed to be invalid. + * + * It is possible that context_reset is called multiple times during an + * application lifecycle. + * If context_reset is called without any notification (context_destroy), + * the OpenGL context was lost and resources should just be recreated + * without any attempt to "free" old resources. + */ + retro_hw_context_reset_t context_reset; + + /* Set by frontend. */ + retro_hw_get_current_framebuffer_t get_current_framebuffer; + + /* Set by frontend. */ + retro_hw_get_proc_address_t get_proc_address; + + /* Set if render buffers should have depth component attached. */ + bool depth; + + /* Set if stencil buffers should be attached. */ + bool stencil; + + /* If depth and stencil are true, a packed 24/8 buffer will be added. + * Only attaching stencil is invalid and will be ignored. */ + + /* Use conventional bottom-left origin convention. If false, + * standard libretro top-left origin semantics are used. */ + bool bottom_left_origin; + + /* Major version number for core GL context or GLES 3.1+. */ + unsigned version_major; + + /* Minor version number for core GL context or GLES 3.1+. */ + unsigned version_minor; + + /* If this is true, the frontend will go very far to avoid + * resetting context in scenarios like toggling fullscreen, etc. + */ + bool cache_context; + + /* The reset callback might still be called in extreme situations + * such as if the context is lost beyond recovery. + * + * For optimal stability, set this to false, and allow context to be + * reset at any time. + */ + + /* A callback to be called before the context is destroyed in a + * controlled way by the frontend. */ + retro_hw_context_reset_t context_destroy; + + /* OpenGL resources can be deinitialized cleanly at this step. + * context_destroy can be set to NULL, in which resources will + * just be destroyed without any notification. + * + * Even when context_destroy is non-NULL, it is possible that + * context_reset is called without any destroy notification. + * This happens if context is lost by external factors (such as + * notified by GL_ARB_robustness). + * + * In this case, the context is assumed to be already dead, + * and the libretro implementation must not try to free any OpenGL + * resources in the subsequent context_reset. + */ + + /* Creates a debug context. */ + bool debug_context; +}; + +/* Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. + * Called by the frontend in response to keyboard events. + * down is set if the key is being pressed, or false if it is being released. + * keycode is the RETROK value of the char. + * character is the text character of the pressed key. (UTF-32). + * key_modifiers is a set of RETROKMOD values or'ed together. + * + * The pressed/keycode state can be indepedent of the character. + * It is also possible that multiple characters are generated from a + * single keypress. + * Keycode events should be treated separately from character events. + * However, when possible, the frontend should try to synchronize these. + * If only a character is posted, keycode should be RETROK_UNKNOWN. + * + * Similarily if only a keycode event is generated with no corresponding + * character, character should be 0. + */ +typedef void (*retro_keyboard_event_t)(bool down, unsigned keycode, + uint32_t character, uint16_t key_modifiers); + +struct retro_keyboard_callback +{ + retro_keyboard_event_t callback; +}; + +/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. + * Should be set for implementations which can swap out multiple disk + * images in runtime. + * + * If the implementation can do this automatically, it should strive to do so. + * However, there are cases where the user must manually do so. + * + * Overview: To swap a disk image, eject the disk image with + * set_eject_state(true). + * Set the disk index with set_image_index(index). Insert the disk again + * with set_eject_state(false). + */ + +/* If ejected is true, "ejects" the virtual disk tray. + * When ejected, the disk image index can be set. + */ +typedef bool (*retro_set_eject_state_t)(bool ejected); + +/* Gets current eject state. The initial state is 'not ejected'. */ +typedef bool (*retro_get_eject_state_t)(void); + +/* Gets current disk index. First disk is index 0. + * If return value is >= get_num_images(), no disk is currently inserted. + */ +typedef unsigned (*retro_get_image_index_t)(void); + +/* Sets image index. Can only be called when disk is ejected. + * The implementation supports setting "no disk" by using an + * index >= get_num_images(). + */ +typedef bool (*retro_set_image_index_t)(unsigned index); + +/* Gets total number of images which are available to use. */ +typedef unsigned (*retro_get_num_images_t)(void); + +struct retro_game_info; + +/* Replaces the disk image associated with index. + * Arguments to pass in info have same requirements as retro_load_game(). + * Virtual disk tray must be ejected when calling this. + * + * Replacing a disk image with info = NULL will remove the disk image + * from the internal list. + * As a result, calls to get_image_index() can change. + * + * E.g. replace_image_index(1, NULL), and previous get_image_index() + * returned 4 before. + * Index 1 will be removed, and the new index is 3. + */ +typedef bool (*retro_replace_image_index_t)(unsigned index, + const struct retro_game_info *info); + +/* Adds a new valid index (get_num_images()) to the internal disk list. + * This will increment subsequent return values from get_num_images() by 1. + * This image index cannot be used until a disk image has been set + * with replace_image_index. */ +typedef bool (*retro_add_image_index_t)(void); + +struct retro_disk_control_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; +}; + +enum retro_pixel_format +{ + /* 0RGB1555, native endian. + * 0 bit must be set to 0. + * This pixel format is default for compatibility concerns only. + * If a 15/16-bit pixel format is desired, consider using RGB565. */ + RETRO_PIXEL_FORMAT_0RGB1555 = 0, + + /* XRGB8888, native endian. + * X bits are ignored. */ + RETRO_PIXEL_FORMAT_XRGB8888 = 1, + + /* RGB565, native endian. + * This pixel format is the recommended format to use if a 15/16-bit + * format is desired as it is the pixel format that is typically + * available on a wide range of low-power devices. + * + * It is also natively supported in APIs like OpenGL ES. */ + RETRO_PIXEL_FORMAT_RGB565 = 2, + + /* Ensure sizeof() == sizeof(int). */ + RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX +}; + +struct retro_message +{ + const char *msg; /* Message to be displayed. */ + unsigned frames; /* Duration in frames of message. */ +}; + +/* Describes how the libretro implementation maps a libretro input bind + * to its internal input system through a human readable string. + * This string can be used to better let a user configure input. */ +struct retro_input_descriptor +{ + /* Associates given parameters with a description. */ + unsigned port; + unsigned device; + unsigned index; + unsigned id; + + /* Human readable description for parameters. + * The pointer must remain valid until + * retro_unload_game() is called. */ + const char *description; +}; + +struct retro_system_info +{ + /* All pointers are owned by libretro implementation, and pointers must + * remain valid until retro_deinit() is called. */ + + const char *library_name; /* Descriptive name of library. Should not + * contain any version numbers, etc. */ + const char *library_version; /* Descriptive version of core. */ + + const char *valid_extensions; /* A string listing probably content + * extensions the core will be able to + * load, separated with pipe. + * I.e. "bin|rom|iso". + * Typically used for a GUI to filter + * out extensions. */ + + /* If true, retro_load_game() is guaranteed to provide a valid pathname + * in retro_game_info::path. + * ::data and ::size are both invalid. + * + * If false, ::data and ::size are guaranteed to be valid, but ::path + * might not be valid. + * + * This is typically set to true for libretro implementations that must + * load from file. + * Implementations should strive for setting this to false, as it allows + * the frontend to perform patching, etc. */ + bool need_fullpath; + + /* If true, the frontend is not allowed to extract any archives before + * loading the real content. + * Necessary for certain libretro implementations that load games + * from zipped archives. */ + bool block_extract; +}; + +struct retro_game_geometry +{ + unsigned base_width; /* Nominal video width of game. */ + unsigned base_height; /* Nominal video height of game. */ + unsigned max_width; /* Maximum possible width of game. */ + unsigned max_height; /* Maximum possible height of game. */ + + float aspect_ratio; /* Nominal aspect ratio of game. If + * aspect_ratio is <= 0.0, an aspect ratio + * of base_width / base_height is assumed. + * A frontend could override this setting, + * if desired. */ +}; + +struct retro_system_timing +{ + double fps; /* FPS of video content. */ + double sample_rate; /* Sampling rate of audio. */ +}; + +struct retro_system_av_info +{ + struct retro_game_geometry geometry; + struct retro_system_timing timing; +}; + +struct retro_variable +{ + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * If NULL, obtains the complete environment string if more + * complex parsing is necessary. + * The environment string is formatted as key-value pairs + * delimited by semicolons as so: + * "key1=value1;key2=value2;..." + */ + const char *key; + + /* Value to be obtained. If key does not exist, it is set to NULL. */ + const char *value; +}; + +struct retro_game_info +{ + const char *path; /* Path to game, UTF-8 encoded. + * Usually used as a reference. + * May be NULL if rom was loaded from stdin + * or similar. + * retro_system_info::need_fullpath guaranteed + * that this path is valid. */ + const void *data; /* Memory buffer of loaded game. Will be NULL + * if need_fullpath was set. */ + size_t size; /* Size of memory buffer. */ + const char *meta; /* String of implementation specific meta-data. */ +}; + +/* Callbacks */ + +/* Environment callback. Gives implementations a way of performing + * uncommon tasks. Extensible. */ +typedef bool (*retro_environment_t)(unsigned cmd, void *data); + +/* Render a frame. Pixel format is 15-bit 0RGB1555 native endian + * unless changed (see RETRO_ENVIRONMENT_SET_PIXEL_FORMAT). + * + * Width and height specify dimensions of buffer. + * Pitch specifices length in bytes between two lines in buffer. + * + * For performance reasons, it is highly recommended to have a frame + * that is packed in memory, i.e. pitch == width * byte_per_pixel. + * Certain graphic APIs, such as OpenGL ES, do not like textures + * that are not packed in memory. + */ +typedef void (*retro_video_refresh_t)(const void *data, unsigned width, + unsigned height, size_t pitch); + +/* Renders a single audio frame. Should only be used if implementation + * generates a single sample at a time. + * Format is signed 16-bit native endian. + */ +typedef void (*retro_audio_sample_t)(int16_t left, int16_t right); + +/* Renders multiple audio frames in one go. + * + * One frame is defined as a sample of left and right channels, interleaved. + * I.e. int16_t buf[4] = { l, r, l, r }; would be 2 frames. + * Only one of the audio callbacks must ever be used. + */ +typedef size_t (*retro_audio_sample_batch_t)(const int16_t *data, + size_t frames); + +/* Polls input. */ +typedef void (*retro_input_poll_t)(void); + +/* Queries for input for player 'port'. device will be masked with + * RETRO_DEVICE_MASK. + * + * Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that + * have been set with retro_set_controller_port_device() + * will still use the higher level RETRO_DEVICE_JOYPAD to request input. + */ +typedef int16_t (*retro_input_state_t)(unsigned port, unsigned device, + unsigned index, unsigned id); + +/* Sets callbacks. retro_set_environment() is guaranteed to be called + * before retro_init(). + * + * The rest of the set_* functions are guaranteed to have been called + * before the first call to retro_run() is made. */ +void retro_set_environment(retro_environment_t); +void retro_set_video_refresh(retro_video_refresh_t); +void retro_set_audio_sample(retro_audio_sample_t); +void retro_set_audio_sample_batch(retro_audio_sample_batch_t); +void retro_set_input_poll(retro_input_poll_t); +void retro_set_input_state(retro_input_state_t); + +/* Library global initialization/deinitialization. */ +void retro_init(void); +void retro_deinit(void); + +/* Must return RETRO_API_VERSION. Used to validate ABI compatibility + * when the API is revised. */ +unsigned retro_api_version(void); + +/* Gets statically known system info. Pointers provided in *info + * must be statically allocated. + * Can be called at any time, even before retro_init(). */ +void retro_get_system_info(struct retro_system_info *info); + +/* Gets information about system audio/video timings and geometry. + * Can be called only after retro_load_game() has successfully completed. + * NOTE: The implementation of this function might not initialize every + * variable if needed. + * E.g. geom.aspect_ratio might not be initialized if core doesn't + * desire a particular aspect ratio. */ +void retro_get_system_av_info(struct retro_system_av_info *info); + +/* Sets device to be used for player 'port'. + * By default, RETRO_DEVICE_JOYPAD is assumed to be plugged into all + * available ports. + * Setting a particular device type is not a guarantee that libretro cores + * will only poll input based on that particular device type. It is only a + * hint to the libretro core when a core cannot automatically detect the + * appropriate input device type on its own. It is also relevant when a + * core can change its behavior depending on device type. */ +void retro_set_controller_port_device(unsigned port, unsigned device); + +/* Resets the current game. */ +void retro_reset(void); + +/* Runs the game for one video frame. + * During retro_run(), input_poll callback must be called at least once. + * + * If a frame is not rendered for reasons where a game "dropped" a frame, + * this still counts as a frame, and retro_run() should explicitly dupe + * a frame if GET_CAN_DUPE returns true. + * In this case, the video callback can take a NULL argument for data. + */ +void retro_run(void); + +/* Returns the amount of data the implementation requires to serialize + * internal state (save states). + * Between calls to retro_load_game() and retro_unload_game(), the + * returned size is never allowed to be larger than a previous returned + * value, to ensure that the frontend can allocate a save state buffer once. + */ +size_t retro_serialize_size(void); + +/* Serializes internal state. If failed, or size is lower than + * retro_serialize_size(), it should return false, true otherwise. */ +bool retro_serialize(void *data, size_t size); +bool retro_unserialize(const void *data, size_t size); + +void retro_cheat_reset(void); +void retro_cheat_set(unsigned index, bool enabled, const char *code); + +/* Loads a game. */ +bool retro_load_game(const struct retro_game_info *game); + +/* Loads a "special" kind of game. Should not be used, + * except in extreme cases. */ +bool retro_load_game_special( + unsigned game_type, + const struct retro_game_info *info, size_t num_info +); + +/* Unloads a currently loaded game. */ +void retro_unload_game(void); + +/* Gets region of game. */ +unsigned retro_get_region(void); + +/* Gets region of memory. */ +void *retro_get_memory_data(unsigned id); +size_t retro_get_memory_size(unsigned id); + +#ifdef __cplusplus +} +#endif + +#endif
@@ -1,12 +1,12 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "gba-thread.h" -#include "gba-config.h" -#include "gba.h" -#include "renderers/video-software.h" +#include "gba/supervisor/thread.h" +#include "gba/supervisor/config.h" +#include "gba/gba.h" +#include "gba/renderers/video-software.h" #include "platform/commandline.h"@@ -36,6 +36,7 @@ static void _GBAPerfShutdown(int signal);
static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg); static struct GBAThread* _thread; +static bool _dispatchExiting = false; int main(int argc, char** argv) { signal(SIGINT, _GBAPerfShutdown);@@ -53,9 +54,14 @@ };
struct GBAConfig config; GBAConfigInit(&config, "perf"); + GBAConfigLoad(&config); - struct GBAOptions opts = {}; - struct GBAArguments args = {}; + struct GBAOptions opts = { + .idleOptimization = IDLE_LOOP_DETECT + }; + GBAConfigLoadDefaults(&config, &opts); + + struct GBAArguments args; if (!parseArguments(&args, &config, argc, argv, &subparser)) { usage(argv[0], PERF_USAGE); freeArguments(&args);@@ -67,7 +73,7 @@
renderer.outputBuffer = malloc(256 * 256 * 4); renderer.outputBufferStride = 256; - struct GBAThread context = { }; + struct GBAThread context = {}; _thread = &context; if (!perfOpts.noVideo) {@@ -75,6 +81,7 @@ context.renderer = &renderer.d;
} context.debugger = createDebugger(&args, &context); + context.overrides = GBAConfigGetOverrides(&config); char gameCode[5] = { 0 }; GBAConfigMap(&config, &opts);@@ -83,7 +90,11 @@ opts.videoSync = false;
GBAMapArgumentsToContext(&args, &context); GBAMapOptionsToContext(&opts, &context); - GBAThreadStart(&context); + int didStart = GBAThreadStart(&context); + + if (!didStart) { + goto cleanup; + } GBAGetGameCode(context.gba, gameCode); int frames = perfOpts.frames;@@ -99,12 +110,6 @@ uint64_t end = 1000000LL * tv.tv_sec + tv.tv_usec;
uint64_t duration = end - start; GBAThreadJoin(&context); - GBAConfigFreeOpts(&opts); - freeArguments(&args); - GBAConfigDeinit(&config); - free(context.debugger); - - free(renderer.outputBuffer); float scaledFrames = frames * 1000000.f; if (perfOpts.csv) {@@ -120,7 +125,14 @@ } else {
printf("%u frames in %" PRIu64 " microseconds: %g fps (%gx)\n", frames, duration, scaledFrames / duration, scaledFrames / (duration * 60.f)); } - return 0; +cleanup: + GBAConfigFreeOpts(&opts); + freeArguments(&args); + GBAConfigDeinit(&config); + free(context.debugger); + free(renderer.outputBuffer); + + return !didStart || GBAThreadHasCrashed(&context); } static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet) {@@ -152,6 +164,9 @@ GBASyncWaitFrameEnd(&context->sync);
if (*frames == duration) { _GBAPerfShutdown(0); } + if (_dispatchExiting) { + GBAThreadEnd(context); + } } if (!quiet) { printf("\033[2K\r");@@ -160,7 +175,9 @@ }
static void _GBAPerfShutdown(int signal) { UNUSED(signal); - GBAThreadEnd(_thread); + // This will come in ON the GBA thread, so we have to handle it carefully + _dispatchExiting = true; + ConditionWake(&_thread->sync.videoFrameAvailableCond); } static bool _parsePerfOpts(struct SubParser* parser, struct GBAConfig* config, int option, const char* arg) {
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,9 +6,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AudioDevice.h" extern "C" { -#include "gba.h" -#include "gba-audio.h" -#include "gba-thread.h" +#include "gba/gba.h" +#include "gba/audio.h" +#include "gba/supervisor/thread.h" } using namespace QGBA;@@ -22,12 +22,20 @@ setOpenMode(ReadOnly);
} void AudioDevice::setFormat(const QAudioFormat& format) { - if (!GBAThreadHasStarted(m_context)) { + if (!GBAThreadIsActive(m_context)) { return; } +#if RESAMPLE_LIBRARY == RESAMPLE_NN GBAThreadInterrupt(m_context); - m_ratio = GBAAudioCalculateRatio(&m_context->gba->audio, m_context->fpsTarget, format.sampleRate()); + m_ratio = GBAAudioCalculateRatio(m_context->gba->audio.sampleRate, m_context->fpsTarget, format.sampleRate()); GBAThreadContinue(m_context); +#elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + double fauxClock = GBAAudioCalculateRatio(1, m_context->fpsTarget, 1); + GBASyncLockAudio(&m_context->sync); + blip_set_rates(m_context->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, format.sampleRate() * fauxClock); + blip_set_rates(m_context->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, format.sampleRate() * fauxClock); + GBASyncUnlockAudio(&m_context->sync); +#endif } void AudioDevice::setInput(GBAThread* input) {@@ -43,7 +51,19 @@ if (!m_context->gba) {
return 0; } +#if RESAMPLE_LIBRARY == RESAMPLE_NN return GBAAudioResampleNN(&m_context->gba->audio, m_ratio, &m_drift, reinterpret_cast<GBAStereoSample*>(data), maxSize / sizeof(GBAStereoSample)) * sizeof(GBAStereoSample); +#elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + GBASyncLockAudio(&m_context->sync); + int available = blip_samples_avail(m_context->gba->audio.left); + if (available > maxSize / sizeof(GBAStereoSample)) { + available = maxSize / sizeof(GBAStereoSample); + } + blip_read_samples(m_context->gba->audio.left, &reinterpret_cast<GBAStereoSample*>(data)->left, available, true); + blip_read_samples(m_context->gba->audio.right, &reinterpret_cast<GBAStereoSample*>(data)->right, available, true); + GBASyncConsumeAudio(&m_context->sync); + return available * sizeof(GBAStereoSample); +#endif } qint64 AudioDevice::writeData(const char*, qint64) {
@@ -5,6 +5,7 @@ * 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 QGBA_AUDIO_DEVICE #define QGBA_AUDIO_DEVICE + #include <QAudioFormat> #include <QIODevice>
@@ -1,32 +1,49 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "AudioProcessor.h" -#include "AudioDevice.h" - #ifdef BUILD_SDL #include "AudioProcessorSDL.h" -#else +#endif + +#ifdef BUILD_QT_MULTIMEDIA #include "AudioProcessorQt.h" #endif -#include <QAudioOutput> - extern "C" { -#include "gba-thread.h" +#include "gba/supervisor/thread.h" } using namespace QGBA; +#ifdef BUILD_QT_MULTIMEDIA +AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::QT_MULTIMEDIA; +#else +AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::SDL; +#endif + AudioProcessor* AudioProcessor::create() { + switch (s_driver) { #ifdef BUILD_SDL - return new AudioProcessorSDL(); + case Driver::SDL: + return new AudioProcessorSDL(); +#endif + +#ifdef BUILD_QT_MULTIMEDIA + case Driver::QT_MULTIMEDIA: + return new AudioProcessorQt(); +#endif + + default: +#ifdef BUILD_QT_MULTIMEDIA + return new AudioProcessorQt(); #else - return new AudioProcessorQt(); -#endif + return new AudioProcessorSDL(); +#endif + } } AudioProcessor::AudioProcessor(QObject* parent)@@ -37,3 +54,7 @@
void AudioProcessor::setInput(GBAThread* input) { m_context = input; } + +void AudioProcessor::setBufferSamples(int samples) { + m_samples = samples; +}
@@ -15,10 +15,22 @@ class AudioProcessor : public QObject {
Q_OBJECT public: + enum class Driver { +#ifdef BUILD_QT_MULTIMEDIA + QT_MULTIMEDIA = 0, +#endif +#ifdef BUILD_SDL + SDL = 1, +#endif + }; + static AudioProcessor* create(); + static void setDriver(Driver driver) { s_driver = driver; } + AudioProcessor(QObject* parent = nullptr); virtual void setInput(GBAThread* input); + int getBufferSamples() const { return m_samples; } public slots: virtual void start() = 0;@@ -29,8 +41,11 @@ virtual void inputParametersChanged() = 0;
protected: GBAThread* input() { return m_context; } + private: GBAThread* m_context; + int m_samples; + static Driver s_driver; }; }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -10,7 +10,7 @@
#include <QAudioOutput> extern "C" { -#include "gba-thread.h" +#include "gba/supervisor/thread.h" } using namespace QGBA;@@ -47,6 +47,7 @@ format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt); m_audioOutput = new QAudioOutput(format, this); + m_audioOutput->setCategory("game"); } m_device->setInput(input());@@ -63,6 +64,7 @@ }
} void AudioProcessorQt::setBufferSamples(int samples) { + AudioProcessor::setBufferSamples(samples); if (m_audioOutput) { m_audioOutput->stop(); m_audioOutput->setBufferSize(samples * 4);
@@ -7,6 +7,8 @@ #ifndef QGBA_AUDIO_PROCESSOR_QT
#define QGBA_AUDIO_PROCESSOR_QT #include "AudioProcessor.h" +class QAudioOutput; + namespace QGBA { class AudioDevice;
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AudioProcessorSDL.h" extern "C" { -#include "gba-thread.h" +#include "gba/supervisor/thread.h" } using namespace QGBA;@@ -25,7 +25,9 @@ void AudioProcessorSDL::start() {
if (m_audio.thread) { GBASDLResumeAudio(&m_audio); } else { - m_audio.samples = input()->audioBuffers; + if (!m_audio.samples) { + m_audio.samples = input()->audioBuffers; + } GBASDLInitAudio(&m_audio, input()); } }@@ -35,9 +37,10 @@ GBASDLPauseAudio(&m_audio);
} void AudioProcessorSDL::setBufferSamples(int samples) { + AudioProcessor::setBufferSamples(samples); + m_audio.samples = samples; if (m_audio.thread) { GBASDLDeinitAudio(&m_audio); - m_audio.samples = samples; GBASDLInitAudio(&m_audio, input()); } }
@@ -3,6 +3,12 @@ enable_language(CXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11") +if(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7 -stdlib=libc++") +endif() + +set(PLATFORM_SRC) + if(BUILD_SDL) if(NOT SDL_FOUND AND NOT SDL2_FOUND) find_package(SDL 1.2 REQUIRED)@@ -10,8 +16,8 @@ endif()
if(SDL2_FOUND) link_directories(${SDL2_LIBDIR}) endif() - set(PLATFORM_LIBRARY "${PLATFORM_LIBRARY};${SDL_LIBRARY};${SDLMAIN_LIBRARY}") - set(PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-audio.c) + list(APPEND PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY}) + list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-audio.c) include_directories(${SDL_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src/platform/sdl) endif()@@ -30,41 +36,67 @@ return()
endif() set(SOURCE_FILES - AudioDevice.cpp AudioProcessor.cpp + CheatsModel.cpp + CheatsView.cpp ConfigController.cpp Display.cpp GBAApp.cpp GBAKeyEditor.cpp GIFView.cpp GameController.cpp + GamepadAxisEvent.cpp + GamepadButtonEvent.cpp InputController.cpp KeyEditor.cpp LoadSaveState.cpp LogView.cpp + OverrideView.cpp SavestateButton.cpp + SensorView.cpp + SettingsView.cpp + ShortcutController.cpp + ShortcutView.cpp Window.cpp VFileDevice.cpp VideoView.cpp) qt5_wrap_ui(UI_FILES + CheatsView.ui GIFView.ui LoadSaveState.ui LogView.ui + OverrideView.ui + SensorView.ui + SettingsView.ui + ShortcutView.ui VideoView.ui) +set(QT_LIBRARIES) +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5" PARENT_SCOPE) + +set(AUDIO_SRC) if(BUILD_SDL) - list(APPEND SOURCE_FILES AudioProcessorSDL.cpp) -elseif(Qt5Multimedia_FOUND) - list(APPEND SOURCE_FILES AudioProcessorQt.cpp) -else() + list(APPEND AUDIO_SRC AudioProcessorSDL.cpp) +endif() + +if(Qt5Multimedia_FOUND) + list(APPEND AUDIO_SRC + AudioProcessorQt.cpp + AudioDevice.cpp) + list(APPEND QT_LIBRARIES Qt5::Multimedia) + add_definitions(-DBUILD_QT_MULTIMEDIA) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5" PARENT_SCOPE) +endif() + +if(NOT AUDIO_SRC) message(WARNING "No supported audio modules found") set(BUILD_QT OFF PARENT_SCOPE) return() endif() if(USE_GDB_STUB) - set(SOURCE_FILES ${PLATFORM_SRC} ${SOURCE_FILES} GDBController.cpp GDBWindow.cpp) + list(APPEND PLATFORM_SRC GDBController.cpp GDBWindow.cpp) endif() set(MACOSX_BUNDLE_ICON_FILE mgba.icns) set(MACOSX_BUNDLE_BUNDLE_VERSION ${LIB_VERSION_STRING})@@ -76,22 +108,29 @@ qt5_add_resources(RESOURCES resources.qrc)
if(WIN32) list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc) endif() -add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${UI_FILES} ${RESOURCES}) +add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES}) +target_compile_definitions(${BINARY_NAME}-qt PRIVATE ${FEATURE_DEFINES}) set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in) -set(QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) -if(Qt5Multimedia_FOUND) - list(APPEND QT_LIBRARIES Qt5::Multimedia) -endif() +list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES}) -install(TARGETS ${BINARY_NAME}-qt RUNTIME DESTINATION bin BUNDLE DESTINATION /Applications) +install(TARGETS ${BINARY_NAME}-qt + RUNTIME DESTINATION bin COMPONENT ${BINARY_NAME}-qt + BUNDLE DESTINATION Applications COMPONENT ${BINARY_NAME}-qt) if(APPLE OR WIN32) set_target_properties(${BINARY_NAME}-qt PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) endif() -if(APPLE AND MACDEPLOYQT) - add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND ${MACDEPLOYQT} ${PROJECT_NAME}.app) - add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND rm -r ${PROJECT_NAME}.app/Contents/Plugins/bearer) - add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND rm -r ${PROJECT_NAME}.app/Contents/Plugins/imageformats) - add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND rm -r ${PROJECT_NAME}.app/Contents/Plugins/printsupport) +if(APPLE) + set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib) + if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool") + set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -I ${CMAKE_INSTALL_NAME_TOOL}) + endif() + if(DEFINED CMAKE_OTOOL AND NOT CMAKE_OTOOL EQUAL "otool") + set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -O ${CMAKE_OTOOL}) + endif() + if(DEFINED CROSS_ROOT) + set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R ${CROSS_ROOT}) + endif() + add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND ${CMAKE_SOURCE_DIR}/tools/deploy-mac.py ${DEPLOY_OPTIONS} ${PROJECT_NAME}.app) endif()
@@ -0,0 +1,236 @@
+/* Copyright (c) 2013-2015 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 "CheatsModel.h" + +#include <QFont> +#include <QSet> + +extern "C" { +#include "gba/cheats.h" +#include "util/vfs.h" +} + +using namespace QGBA; + +CheatsModel::CheatsModel(GBACheatDevice* device, QObject* parent) + : QAbstractItemModel(parent) + , m_device(device) +{ +} + +QVariant CheatsModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + + if (index.parent().isValid()) { + int row = index.row(); + GBACheatSet* cheats = static_cast<GBACheatSet*>(index.internalPointer()); + const char* line = *StringListGetPointer(&cheats->lines, row); + switch (role) { + case Qt::DisplayRole: + return line; + case Qt::FontRole: + return QFont("Courier New", 13); + default: + return QVariant(); + } + } + + int row = index.row(); + const GBACheatSet* cheats = *GBACheatSetsGetPointer(&m_device->cheats, index.row()); + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return cheats->name ? cheats->name : tr("(untitled)"); + case Qt::CheckStateRole: + return cheats->enabled ? Qt::Checked : Qt::Unchecked; + default: + return QVariant(); + } +} + +bool CheatsModel::setData(const QModelIndex& index, const QVariant& value, int role) { + if (!index.isValid() || index.parent().isValid()) { + return false; + } + + int row = index.row(); + GBACheatSet* cheats = *GBACheatSetsGetPointer(&m_device->cheats, index.row()); + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + if (cheats->name) { + free(cheats->name); + cheats->name = nullptr; + } + cheats->name = strdup(value.toString().toLocal8Bit().constData()); + emit dataChanged(index, index); + return true; + case Qt::CheckStateRole: + cheats->enabled = value == Qt::Checked; + emit dataChanged(index, index); + return true; + default: + return false; + } +} + +QModelIndex CheatsModel::index(int row, int column, const QModelIndex& parent) const { + if (parent.isValid()) { + return createIndex(row, column, *GBACheatSetsGetPointer(&m_device->cheats, parent.row())); + } else { + return createIndex(row, column, nullptr); + } +} + +QModelIndex CheatsModel::parent(const QModelIndex& index) const { + if (!index.isValid()) { + return QModelIndex(); + } + const GBACheatSet* cheats = static_cast<const GBACheatSet*>(index.internalPointer()); + if (!cheats) { + return QModelIndex(); + } + for (size_t i = 0; i < GBACheatSetsSize(&m_device->cheats); ++i) { + if (cheats == *GBACheatSetsGetPointer(&m_device->cheats, i)) { + return createIndex(i, 0, nullptr); + } + } + return QModelIndex(); +} + +Qt::ItemFlags CheatsModel::flags(const QModelIndex &index) const { + if (!index.isValid()) { + return 0; + } + + if (index.parent().isValid()) { + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + + return Qt::ItemIsUserCheckable | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +int CheatsModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +int CheatsModel::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) { + if (parent.internalPointer()) { + return 0; + } + const GBACheatSet* set = *GBACheatSetsGetPointer(&m_device->cheats, parent.row()); + return StringListSize(&set->lines); + } + return GBACheatSetsSize(&m_device->cheats); +} + +GBACheatSet* CheatsModel::itemAt(const QModelIndex& index) { + if (!index.isValid()) { + return nullptr; + } + if (index.parent().isValid()) { + return static_cast<GBACheatSet*>(index.internalPointer()); + } + return *GBACheatSetsGetPointer(&m_device->cheats, index.row()); +} + +void CheatsModel::removeAt(const QModelIndex& index) { + if (!index.isValid() || index.parent().isValid()) { + return; + } + int row = index.row(); + GBACheatSet* set = *GBACheatSetsGetPointer(&m_device->cheats, index.row()); + beginRemoveRows(QModelIndex(), row, row); + GBACheatRemoveSet(m_device, set); + GBACheatSetDeinit(set); + delete set; + endInsertRows(); + +} + +QString CheatsModel::toString(const QModelIndexList& indices) const { + QMap<int, GBACheatSet*> setOrder; + QMap<GBACheatSet*, QSet<size_t>> setIndices; + for (const QModelIndex& index : indices) { + GBACheatSet* set = static_cast<GBACheatSet*>(index.internalPointer()); + if (!set) { + set = *GBACheatSetsGetPointer(&m_device->cheats, index.row()); + setOrder[index.row()] = set; + QSet<size_t> range; + for (size_t i = 0; i < StringListSize(&set->lines); ++i) { + range.insert(i); + } + setIndices[set] = range; + } else { + setOrder[index.parent().row()] = set; + setIndices[set].insert(index.row()); + } + } + + QStringList strings; + QList<int> order = setOrder.keys(); + std::sort(order.begin(), order.end()); + for (int i : order) { + GBACheatSet* set = setOrder[i]; + QList<size_t> indexOrdex = setIndices[set].toList(); + std::sort(indexOrdex.begin(), indexOrdex.end()); + for (size_t j : indexOrdex) { + strings.append(*StringListGetPointer(&set->lines, j)); + } + } + + return strings.join('\n'); +} + +void CheatsModel::beginAppendRow(const QModelIndex& index) { + if (index.parent().isValid()) { + beginInsertRows(index.parent(), rowCount(index.parent()), rowCount(index.parent())); + return; + } + beginInsertRows(index, rowCount(index), rowCount(index)); +} + +void CheatsModel::endAppendRow() { + endInsertRows(); +} + +void CheatsModel::loadFile(const QString& path) { + VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_RDONLY); + if (!vf) { + return; + } + beginResetModel(); + GBACheatParseFile(m_device, vf); + endResetModel(); + vf->close(vf); +} + +void CheatsModel::saveFile(const QString& path) { + VFile* vf = VFileOpen(path.toLocal8Bit().constData(), O_TRUNC | O_CREAT | O_WRONLY); + if (!vf) { + return; + } + GBACheatSaveFile(m_device, vf); + vf->close(vf); +} + +void CheatsModel::addSet(GBACheatSet* set) { + beginInsertRows(QModelIndex(), GBACheatSetsSize(&m_device->cheats), GBACheatSetsSize(&m_device->cheats)); + size_t size = GBACheatSetsSize(&m_device->cheats); + if (size) { + GBACheatSetCopyProperties(set, *GBACheatSetsGetPointer(&m_device->cheats, size - 1)); + } + GBACheatAddSet(m_device, set); + endInsertRows(); +} + +void CheatsModel::invalidated() { + beginResetModel(); + endResetModel(); +}
@@ -0,0 +1,53 @@
+/* Copyright (c) 2013-2015 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 QGBA_CHEATS_MODEL +#define QGBA_CHEATS_MODEL + +#include <QAbstractItemModel> + +struct GBACheatDevice; +struct GBACheatSet; + +namespace QGBA { + +class CheatsModel : public QAbstractItemModel { +Q_OBJECT + +public: + CheatsModel(GBACheatDevice* m_device, QObject* parent = nullptr); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + GBACheatSet* itemAt(const QModelIndex& index); + void removeAt(const QModelIndex& index); + QString toString(const QModelIndexList& indices) const; + + void beginAppendRow(const QModelIndex& index); + void endAppendRow(); + + void loadFile(const QString& path); + void saveFile(const QString& path); + + void addSet(GBACheatSet* set); + +public slots: + void invalidated(); + +private: + GBACheatDevice* m_device; +}; + +} + +#endif
@@ -0,0 +1,116 @@
+/* Copyright (c) 2013-2015 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 "CheatsView.h" + +#include "GameController.h" + +#include <QClipboard> +#include <QFileDialog> + +extern "C" { +#include "gba/cheats.h" +} + +using namespace QGBA; + +CheatsView::CheatsView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) + , m_model(controller->cheatDevice()) +{ + m_ui.setupUi(this); + + m_ui.cheatList->installEventFilter(this); + m_ui.cheatList->setModel(&m_model); + + connect(m_ui.load, SIGNAL(clicked()), this, SLOT(load())); + connect(m_ui.save, SIGNAL(clicked()), this, SLOT(save())); + connect(m_ui.addSet, SIGNAL(clicked()), this, SLOT(addSet())); + connect(m_ui.remove, SIGNAL(clicked()), this, SLOT(removeSet())); + connect(controller, SIGNAL(gameStopped(GBAThread*)), &m_model, SLOT(invalidated())); + + connect(m_ui.add, &QPushButton::clicked, [this]() { + enterCheat(GBACheatAddLine); + }); + + connect(m_ui.addGSA, &QPushButton::clicked, [this]() { + enterCheat(GBACheatAddGameSharkLine); + }); + + connect(m_ui.addCB, &QPushButton::clicked, [this]() { + enterCheat(GBACheatAddCodeBreakerLine); + }); +} + +bool CheatsView::eventFilter(QObject* object, QEvent* event) { + if (object != m_ui.cheatList) { + return false; + } + if (event->type() != QEvent::KeyPress) { + return false; + } + if (static_cast<QKeyEvent*>(event) == QKeySequence::Copy) { + QApplication::clipboard()->setText(m_model.toString(m_ui.cheatList->selectionModel()->selectedIndexes())); + return true; + } + return false; +} + +void CheatsView::load() { + QString filename = QFileDialog::getOpenFileName(this, tr("Select cheats file")); + if (!filename.isEmpty()) { + m_model.loadFile(filename); + } +} + +void CheatsView::save() { + QString filename = QFileDialog::getSaveFileName(this, tr("Select cheats file")); + if (!filename.isEmpty()) { + m_model.saveFile(filename); + } +} + +void CheatsView::addSet() { + GBACheatSet* set = new GBACheatSet; + GBACheatSetInit(set, nullptr); + m_controller->threadInterrupt(); + m_model.addSet(set); + m_controller->threadContinue(); +} + +void CheatsView::removeSet() { + GBACheatSet* set; + QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes(); + if (selection.count() < 1) { + return; + } + m_controller->threadInterrupt(); + for (const QModelIndex& index : selection) { + m_model.removeAt(selection[0]); + } + m_controller->threadContinue(); +} + +void CheatsView::enterCheat(std::function<bool(GBACheatSet*, const char*)> callback) { + GBACheatSet* set; + QModelIndexList selection = m_ui.cheatList->selectionModel()->selectedIndexes(); + if (selection.count() != 1) { + return; + } + set = m_model.itemAt(selection[0]); + if (!set) { + return; + } + m_controller->threadInterrupt(); + QStringList cheats = m_ui.codeEntry->toPlainText().split('\n', QString::SkipEmptyParts); + for (const QString& string : cheats) { + m_model.beginAppendRow(selection[0]); + callback(set, string.toLocal8Bit().constData()); + m_model.endAppendRow(); + } + m_controller->threadContinue(); + m_ui.codeEntry->clear(); +}
@@ -0,0 +1,47 @@
+/* Copyright (c) 2013-2015 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 QGBA_CHEATS_VIEW +#define QGBA_CHEATS_VIEW + +#include <QWidget> + +#include <functional> + +#include "CheatsModel.h" + +#include "ui_CheatsView.h" + +struct GBACheatDevice; + +namespace QGBA { + +class GameController; + +class CheatsView : public QWidget { +Q_OBJECT + +public: + CheatsView(GameController* controller, QWidget* parent = nullptr); + + virtual bool eventFilter(QObject*, QEvent*) override; + +private slots: + void load(); + void save(); + void addSet(); + void removeSet(); + +private: + void enterCheat(std::function<bool(GBACheatSet*, const char*)> callback); + + Ui::CheatsView m_ui; + GameController* m_controller; + CheatsModel m_model; +}; + +} + +#endif
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CheatsView</class> + <widget class="QWidget" name="CheatsView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>422</width> + <height>389</height> + </rect> + </property> + <property name="windowTitle"> + <string>Cheats</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="1" colspan="2"> + <widget class="QPushButton" name="remove"> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QPushButton" name="addSet"> + <property name="text"> + <string>Add New Set</string> + </property> + </widget> + </item> + <item row="7" column="1" colspan="2"> + <widget class="QPushButton" name="addGSA"> + <property name="text"> + <string>Add GameShark</string> + </property> + </widget> + </item> + <item row="8" column="1" colspan="2"> + <widget class="QPushButton" name="addPAR"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Add Pro Action Replay</string> + </property> + </widget> + </item> + <item row="9" column="1" colspan="2"> + <widget class="QPushButton" name="addCB"> + <property name="text"> + <string>Add CodeBreaker</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="load"> + <property name="text"> + <string>Load</string> + </property> + </widget> + </item> + <item row="6" column="1" colspan="2"> + <widget class="QPushButton" name="add"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="save"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item row="3" column="1" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0" rowspan="10"> + <widget class="QTreeView" name="cheatList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="defaultDropAction"> + <enum>Qt::MoveAction</enum> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="headerHidden"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1" rowspan="2" colspan="2"> + <widget class="QPlainTextEdit" name="codeEntry"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="font"> + <font> + <family>Courier New</family> + </font> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>cheatList</tabstop> + <tabstop>addSet</tabstop> + <tabstop>load</tabstop> + <tabstop>save</tabstop> + <tabstop>remove</tabstop> + <tabstop>codeEntry</tabstop> + <tabstop>add</tabstop> + <tabstop>addGSA</tabstop> + <tabstop>addPAR</tabstop> + <tabstop>addCB</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,9 +8,11 @@
#include "GameController.h" #include <QAction> +#include <QDir> #include <QMenu> extern "C" { +#include "gba/supervisor/overrides.h" #include "platform/commandline.h" }@@ -81,12 +83,23 @@ ConfigController::ConfigController(QObject* parent)
: QObject(parent) , m_opts() { + char path[PATH_MAX]; + GBAConfigDirectory(path, sizeof(path)); + QString fileName(path); + fileName.append(QDir::separator()); + fileName.append("qt.ini"); + m_settings = new QSettings(fileName, QSettings::IniFormat, this); + GBAConfigInit(&m_config, PORT); m_opts.audioSync = GameController::AUDIO_SYNC; m_opts.videoSync = GameController::VIDEO_SYNC; m_opts.fpsTarget = 60; - m_opts.audioBuffers = 768; + m_opts.audioBuffers = 2048; + m_opts.logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL; + m_opts.rewindEnable = false; + m_opts.rewindBufferInterval = 0; + m_opts.rewindBufferCapacity = 0; GBAConfigLoadDefaults(&m_config, &m_opts); GBAConfigLoad(&m_config); GBAConfigMap(&m_config, &m_opts);@@ -130,35 +143,55 @@ }
m_optionSet[optionName]->setValue(GBAConfigGetValue(&m_config, key)); } +QString ConfigController::getOption(const char* key) const { + return QString(GBAConfigGetValue(&m_config, key)); +} + +QVariant ConfigController::getQtOption(const QString& key, const QString& group) const { + if (!group.isNull()) { + m_settings->beginGroup(group); + } + QVariant value = m_settings->value(key); + if (!group.isNull()) { + m_settings->endGroup(); + } + return value; +} + +void ConfigController::saveOverride(const GBACartridgeOverride& override) { + GBAOverrideSave(overrides(), &override); + write(); +} + void ConfigController::setOption(const char* key, bool value) { GBAConfigSetIntValue(&m_config, key, value); - ConfigOption* option = m_optionSet[QString(key)]; - if (option) { - option->setValue(value); + QString optionName(key); + if (m_optionSet.contains(optionName)) { + m_optionSet[optionName]->setValue(value); } } void ConfigController::setOption(const char* key, int value) { GBAConfigSetIntValue(&m_config, key, value); - ConfigOption* option = m_optionSet[QString(key)]; - if (option) { - option->setValue(value); + QString optionName(key); + if (m_optionSet.contains(optionName)) { + m_optionSet[optionName]->setValue(value); } } void ConfigController::setOption(const char* key, unsigned value) { GBAConfigSetUIntValue(&m_config, key, value); - ConfigOption* option = m_optionSet[QString(key)]; - if (option) { - option->setValue(value); + QString optionName(key); + if (m_optionSet.contains(optionName)) { + m_optionSet[optionName]->setValue(value); } } void ConfigController::setOption(const char* key, const char* value) { GBAConfigSetValue(&m_config, key, value); - ConfigOption* option = m_optionSet[QString(key)]; - if (option) { - option->setValue(value); + QString optionName(key); + if (m_optionSet.contains(optionName)) { + m_optionSet[optionName]->setValue(value); } }@@ -171,6 +204,44 @@ QString stringValue(value.toString());
setOption(key, stringValue.toLocal8Bit().constData()); } +void ConfigController::setQtOption(const QString& key, const QVariant& value, const QString& group) { + if (!group.isNull()) { + m_settings->beginGroup(group); + } + m_settings->setValue(key, value); + if (!group.isNull()) { + m_settings->endGroup(); + } +} + +QList<QString> ConfigController::getMRU() const { + QList<QString> mru; + m_settings->beginGroup("mru"); + for (int i = 0; i < MRU_LIST_SIZE; ++i) { + QString item = m_settings->value(QString::number(i)).toString(); + if (item.isNull()) { + continue; + } + mru.append(item); + } + m_settings->endGroup(); + return mru; +} + +void ConfigController::setMRU(const QList<QString>& mru) { + int i = 0; + m_settings->beginGroup("mru"); + for (const QString& item : mru) { + m_settings->setValue(QString::number(i), item); + ++i; + if (i >= MRU_LIST_SIZE) { + break; + } + } + m_settings->endGroup(); +} + void ConfigController::write() { GBAConfigSave(&m_config); + m_settings->sync(); }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,13 +8,13 @@ #define QGBA_CONFIG_CONTROLLER
#include <QMap> #include <QObject> -#include <QScopedPointer> +#include <QSettings> #include <QVariant> #include <functional> extern "C" { -#include "gba-config.h" +#include "gba/supervisor/config.h" #include "util/configuration.h" }@@ -22,6 +22,7 @@ class QAction;
class QMenu; struct GBAArguments; +struct GBACartridgeOverride; namespace QGBA {@@ -57,6 +58,7 @@ Q_OBJECT
public: constexpr static const char* const PORT = "qt"; + static const int MRU_LIST_SIZE = 10; ConfigController(QObject* parent = nullptr); ~ConfigController();@@ -67,12 +69,23 @@
ConfigOption* addOption(const char* key); void updateOption(const char* key); + QString getOption(const char* key) const; + + QVariant getQtOption(const QString& key, const QString& group = QString()) const; + + QList<QString> getMRU() const; + void setMRU(const QList<QString>& mru); + + Configuration* overrides() { return GBAConfigGetOverrides(&m_config); } + void saveOverride(const GBACartridgeOverride&); + public slots: void setOption(const char* key, bool value); void setOption(const char* key, int value); void setOption(const char* key, unsigned value); void setOption(const char* key, const char* value); void setOption(const char* key, const QVariant& value); + void setQtOption(const QString& key, const QVariant& value, const QString& group = QString()); void write();@@ -86,6 +99,7 @@ GBAConfig m_config;
GBAOptions m_opts; QMap<QString, ConfigOption*> m_optionSet; + QSettings* m_settings; }; }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -9,7 +9,7 @@ #include <QApplication>
#include <QResizeEvent> extern "C" { -#include "gba-thread.h" +#include "gba/supervisor/thread.h" } using namespace QGBA;@@ -31,7 +31,7 @@
Display::Display(QGLFormat format, QWidget* parent) : QGLWidget(format, parent) , m_painter(nullptr) - , m_drawThread(nullptr) + , m_started(false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setMinimumSize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);@@ -40,30 +40,29 @@ setCursor(Qt::BlankCursor);
} void Display::startDrawing(const uint32_t* buffer, GBAThread* thread) { - if (m_drawThread) { + if (m_started) { return; } - m_drawThread = new QThread(this); m_painter = new Painter(this); m_painter->setContext(thread); m_painter->setBacking(buffer); - m_painter->moveToThread(m_drawThread); m_context = thread; doneCurrent(); - context()->moveToThread(m_drawThread); - connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start())); - m_drawThread->start(QThread::TimeCriticalPriority); + m_painter->start(); + m_started = true; + + lockAspectRatio(m_lockAspectRatio); + filter(m_filter); } void Display::stopDrawing() { - if (m_drawThread) { + if (m_started) { if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); GBASyncSuspendDrawing(&m_context->sync); } - QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection); - m_drawThread->exit(); - m_drawThread = nullptr; + m_painter->stop(); + m_started = false; if (GBAThreadIsActive(m_context)) { GBASyncResumeDrawing(&m_context->sync); GBAThreadContinue(m_context);@@ -72,12 +71,12 @@ }
} void Display::pauseDrawing() { - if (m_drawThread) { + if (m_started) { if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); GBASyncSuspendDrawing(&m_context->sync); } - QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection); + m_painter->pause(); if (GBAThreadIsActive(m_context)) { GBASyncResumeDrawing(&m_context->sync); GBAThreadContinue(m_context);@@ -86,12 +85,12 @@ }
} void Display::unpauseDrawing() { - if (m_drawThread) { + if (m_started) { if (GBAThreadIsActive(m_context)) { GBAThreadInterrupt(m_context); GBASyncSuspendDrawing(&m_context->sync); } - QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection); + m_painter->unpause(); if (GBAThreadIsActive(m_context)) { GBASyncResumeDrawing(&m_context->sync); GBAThreadContinue(m_context);@@ -100,8 +99,22 @@ }
} void Display::forceDraw() { - if (m_drawThread) { - QMetaObject::invokeMethod(m_painter, "forceDraw", Qt::QueuedConnection); + if (m_started) { + m_painter->forceDraw(); + } +} + +void Display::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + if (m_started) { + m_painter->lockAspectRatio(lock); + } +} + +void Display::filter(bool filter) { + m_filter = filter; + if (m_started) { + m_painter->filter(filter); } }@@ -120,10 +133,10 @@ swapBuffers();
} void Display::resizeEvent(QResizeEvent* event) { - if (m_drawThread) { + if (m_started) { GBAThreadInterrupt(m_context); GBASyncSuspendDrawing(&m_context->sync); - QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, event->size())); + m_painter->resize(event->size()); GBASyncResumeDrawing(&m_context->sync); GBAThreadContinue(m_context); }@@ -131,6 +144,8 @@ }
Painter::Painter(Display* parent) : m_gl(parent) + , m_lockAspectRatio(false) + , m_filter(false) { m_size = parent->size(); }@@ -145,11 +160,26 @@ }
void Painter::resize(const QSize& size) { m_size = size; + forceDraw(); + forceDraw(); +} + +void Painter::lockAspectRatio(bool lock) { + m_lockAspectRatio = lock; + forceDraw(); + forceDraw(); +} + +void Painter::filter(bool filter) { + m_filter = filter; m_gl->makeCurrent(); - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - m_gl->swapBuffers(); + if (m_filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } m_gl->doneCurrent(); + forceDraw(); } void Painter::start() {@@ -159,7 +189,11 @@ glGenTextures(1, &m_tex);
glBindTexture(GL_TEXTURE_2D, m_tex); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + if (m_filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_INT, 0, _glVertices);@@ -180,14 +214,8 @@ }
void Painter::draw() { m_gl->makeCurrent(); - if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip)) { - glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - if (m_context->sync.videoFrameWait) { - glFlush(); - } - } + GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip); + performDraw(); GBASyncWaitFrameEnd(&m_context->sync); m_gl->swapBuffers(); m_gl->doneCurrent();@@ -196,11 +224,8 @@
void Painter::forceDraw() { m_gl->makeCurrent(); glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - if (m_context->sync.videoFrameWait) { - glFlush(); - } + glClear(GL_COLOR_BUFFER_BIT); + performDraw(); m_gl->swapBuffers(); m_gl->doneCurrent(); }@@ -226,3 +251,37 @@
void Painter::unpause() { m_drawTimer->start(); } + +void Painter::performDraw() { + int w = m_size.width() * m_gl->devicePixelRatio(); + int h = m_size.height() * m_gl->devicePixelRatio(); +#ifndef Q_OS_MAC + // TODO: This seems to cause framerates to drag down to 120 FPS on OS X, + // even if the emulator can go faster. Look into why. + glViewport(0, 0, m_size.width() * m_gl->devicePixelRatio(), m_size.height() * m_gl->devicePixelRatio()); + glClear(GL_COLOR_BUFFER_BIT); +#endif + int drawW = w; + int drawH = h; + if (m_lockAspectRatio) { + if (w * 2 > h * 3) { + drawW = h * 3 / 2; + } else if (w * 2 < h * 3) { + drawH = w * 2 / 3; + } + } + glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH); +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing); +#endif + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + if (m_context->sync.videoFrameWait) { + glFlush(); + } +}
@@ -27,6 +27,8 @@ void stopDrawing();
void pauseDrawing(); void unpauseDrawing(); void forceDraw(); + void lockAspectRatio(bool lock); + void filter(bool filter); #ifdef USE_PNG void screenshot(); #endif@@ -38,8 +40,10 @@ virtual void resizeEvent(QResizeEvent*) override;
private: Painter* m_painter; - QThread* m_drawThread; + bool m_started; GBAThread* m_context; + bool m_lockAspectRatio; + bool m_filter; }; class Painter : public QObject {@@ -59,14 +63,20 @@ void stop();
void pause(); void unpause(); void resize(const QSize& size); + void lockAspectRatio(bool lock); + void filter(bool filter); private: + void performDraw(); + QTimer* m_drawTimer; GBAThread* m_context; const uint32_t* m_backing; GLuint m_tex; QGLWidget* m_gl; QSize m_size; + bool m_lockAspectRatio; + bool m_filter; }; }
@@ -5,12 +5,14 @@ * 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 "GBAApp.h" +#include "AudioProcessor.h" #include "GameController.h" #include <QFileOpenEvent> extern "C" { #include "platform/commandline.h" +#include "util/socket.h" } using namespace QGBA;@@ -23,18 +25,29 @@ #ifdef BUILD_SDL
SDL_Init(SDL_INIT_NOPARACHUTE); #endif - QApplication::setApplicationName(PROJECT_NAME); - QApplication::setApplicationVersion(PROJECT_VERSION); + SocketSubsystemInit(); + + QApplication::setApplicationName(PROJECT_NAME); + QApplication::setApplicationVersion(PROJECT_VERSION); - GBAArguments args = {}; - if (m_configController.parseArguments(&args, argc, argv)) { - m_window.argumentsPassed(&args); - } else { - m_window.loadConfig(); - } +#ifndef Q_OS_MAC + m_window.show(); +#endif + + GBAArguments args; + if (m_configController.parseArguments(&args, argc, argv)) { + m_window.argumentsPassed(&args); + } else { + m_window.loadConfig(); + } freeArguments(&args); - m_window.show(); + AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(m_configController.getQtOption("audioDriver").toInt())); + m_window.controller()->reloadAudioDriver(); + +#ifdef Q_OS_MAC + m_window.show(); +#endif } bool GBAApp::event(QEvent* event) {
@@ -8,15 +8,10 @@
#include <QPaintEvent> #include <QPainter> #include <QPushButton> -#include <QTimer> #include <QVBoxLayout> #include "InputController.h" #include "KeyEditor.h" - -extern "C" { -#include "gba-input.h" -} using namespace QGBA;@@ -46,32 +41,21 @@ m_keyB = new KeyEditor(this);
m_keyL = new KeyEditor(this); m_keyR = new KeyEditor(this); + lookupBinding(map, m_keyDU, GBA_KEY_UP); + lookupBinding(map, m_keyDD, GBA_KEY_DOWN); + lookupBinding(map, m_keyDL, GBA_KEY_LEFT); + lookupBinding(map, m_keyDR, GBA_KEY_RIGHT); + lookupBinding(map, m_keySelect, GBA_KEY_SELECT); + lookupBinding(map, m_keyStart, GBA_KEY_START); + lookupBinding(map, m_keyA, GBA_KEY_A); + lookupBinding(map, m_keyB, GBA_KEY_B); + lookupBinding(map, m_keyL, GBA_KEY_L); + lookupBinding(map, m_keyR, GBA_KEY_R); + #ifdef BUILD_SDL - if (type == SDL_BINDING_BUTTON) { - m_keyDU->setNumeric(true); - m_keyDD->setNumeric(true); - m_keyDL->setNumeric(true); - m_keyDR->setNumeric(true); - m_keySelect->setNumeric(true); - m_keyStart->setNumeric(true); - m_keyA->setNumeric(true); - m_keyB->setNumeric(true); - m_keyL->setNumeric(true); - m_keyR->setNumeric(true); - } + lookupAxes(map); #endif - m_keyDU->setValue(GBAInputQueryBinding(map, type, GBA_KEY_UP)); - m_keyDD->setValue(GBAInputQueryBinding(map, type, GBA_KEY_DOWN)); - m_keyDL->setValue(GBAInputQueryBinding(map, type, GBA_KEY_LEFT)); - m_keyDR->setValue(GBAInputQueryBinding(map, type, GBA_KEY_RIGHT)); - m_keySelect->setValue(GBAInputQueryBinding(map, type, GBA_KEY_SELECT)); - m_keyStart->setValue(GBAInputQueryBinding(map, type, GBA_KEY_START)); - m_keyA->setValue(GBAInputQueryBinding(map, type, GBA_KEY_A)); - m_keyB->setValue(GBAInputQueryBinding(map, type, GBA_KEY_B)); - m_keyL->setValue(GBAInputQueryBinding(map, type, GBA_KEY_L)); - m_keyR->setValue(GBAInputQueryBinding(map, type, GBA_KEY_R)); - connect(m_keyDU, SIGNAL(valueChanged(int)), this, SLOT(setNext())); connect(m_keyDD, SIGNAL(valueChanged(int)), this, SLOT(setNext())); connect(m_keyDL, SIGNAL(valueChanged(int)), this, SLOT(setNext()));@@ -83,6 +67,17 @@ connect(m_keyB, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
connect(m_keyL, SIGNAL(valueChanged(int)), this, SLOT(setNext())); connect(m_keyR, SIGNAL(valueChanged(int)), this, SLOT(setNext())); + connect(m_keyDU, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyDD, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyDL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyDR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keySelect, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyStart, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyA, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyB, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyL, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + connect(m_keyR, SIGNAL(axisChanged(int, int)), this, SLOT(setNext())); + m_buttons = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout; m_buttons->setLayout(layout);@@ -114,15 +109,6 @@
m_background.load(":/res/keymap.qpic"); setAll->setFocus(); - -#ifdef BUILD_SDL - if (type == SDL_BINDING_BUTTON) { - m_gamepadTimer = new QTimer(this); - connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad())); - m_gamepadTimer->setInterval(100); - m_gamepadTimer->start(); - } -#endif } void GBAKeyEditor::setAll() {@@ -151,12 +137,10 @@ painter.drawPicture(0, 0, m_background);
} void GBAKeyEditor::setNext() { + findFocus(); + if (m_currentKey == m_keyOrder.end()) { return; - } - - if (!(*m_currentKey)->hasFocus()) { - m_currentKey = m_keyOrder.end(); } ++m_currentKey;@@ -168,33 +152,111 @@ }
} void GBAKeyEditor::save() { - m_controller->bindKey(m_type, m_keyDU->value(), GBA_KEY_UP); - m_controller->bindKey(m_type, m_keyDD->value(), GBA_KEY_DOWN); - m_controller->bindKey(m_type, m_keyDL->value(), GBA_KEY_LEFT); - m_controller->bindKey(m_type, m_keyDR->value(), GBA_KEY_RIGHT); - m_controller->bindKey(m_type, m_keySelect->value(), GBA_KEY_SELECT); - m_controller->bindKey(m_type, m_keyStart->value(), GBA_KEY_START); - m_controller->bindKey(m_type, m_keyA->value(), GBA_KEY_A); - m_controller->bindKey(m_type, m_keyB->value(), GBA_KEY_B); - m_controller->bindKey(m_type, m_keyL->value(), GBA_KEY_L); - m_controller->bindKey(m_type, m_keyR->value(), GBA_KEY_R); + bindKey(m_keyDU, GBA_KEY_UP); + bindKey(m_keyDD, GBA_KEY_DOWN); + bindKey(m_keyDL, GBA_KEY_LEFT); + bindKey(m_keyDR, GBA_KEY_RIGHT); + bindKey(m_keySelect, GBA_KEY_SELECT); + bindKey(m_keyStart, GBA_KEY_START); + bindKey(m_keyA, GBA_KEY_A); + bindKey(m_keyB, GBA_KEY_B); + bindKey(m_keyL, GBA_KEY_L); + bindKey(m_keyR, GBA_KEY_R); m_controller->saveConfiguration(m_type); } -#ifdef BUILD_SDL -void GBAKeyEditor::testGamepad() { - QSet<int> activeKeys = m_controller->activeGamepadButtons(); - if (activeKeys.empty()) { +void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) { + #ifdef BUILD_SDL + if (m_type == SDL_BINDING_BUTTON) { + int value = GBAInputQueryBinding(map, m_type, key); + if (value != GBA_NO_MAPPING) { + keyEditor->setValueButton(value); + } return; } - for (KeyEditor* key : m_keyOrder) { - if (!key->hasFocus()) { - continue; + #endif + keyEditor->setValueKey(GBAInputQueryBinding(map, m_type, key)); +} + +#ifdef BUILD_SDL +void GBAKeyEditor::lookupAxes(const GBAInputMap* map) { + GBAInputEnumerateAxes(map, m_type, [](int axis, const GBAAxis* description, void* user) { + GBAKeyEditor* self = static_cast<GBAKeyEditor*>(user); + if (description->highDirection != GBA_KEY_NONE) { + KeyEditor* key = self->keyById(description->highDirection); + if (key) { + key->setValueAxis(axis, description->deadHigh); + } + } + if (description->lowDirection != GBA_KEY_NONE) { + KeyEditor* key = self->keyById(description->lowDirection); + if (key) { + key->setValueAxis(axis, description->deadLow); + } } - key->setValue(*activeKeys.begin()); + }, this); +} +#endif + +void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) { + if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) { + m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key); + } else { + m_controller->bindKey(m_type, keyEditor->value(), key); } } + +bool GBAKeyEditor::findFocus() { + if (m_currentKey != m_keyOrder.end() && (*m_currentKey)->hasFocus()) { + return true; + } + + for (auto key = m_keyOrder.begin(); key != m_keyOrder.end(); ++key) { + if ((*key)->hasFocus()) { + m_currentKey = key; + return true; + } + } + return false; +} + +#ifdef BUILD_SDL +void GBAKeyEditor::setAxisValue(int axis, int32_t value) { + if (!findFocus()) { + return; + } + KeyEditor* focused = *m_currentKey; + focused->setValueAxis(axis, value); +} #endif + +KeyEditor* GBAKeyEditor::keyById(GBAKey key) { + switch (key) { + case GBA_KEY_UP: + return m_keyDU; + case GBA_KEY_DOWN: + return m_keyDD; + case GBA_KEY_LEFT: + return m_keyDL; + case GBA_KEY_RIGHT: + return m_keyDR; + case GBA_KEY_A: + return m_keyA; + case GBA_KEY_B: + return m_keyB; + case GBA_KEY_L: + return m_keyL; + case GBA_KEY_R: + return m_keyR; + case GBA_KEY_SELECT: + return m_keySelect; + case GBA_KEY_START: + return m_keyStart; + default: + break; + } + return nullptr; +} void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) { QSize s = size();
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -8,7 +8,14 @@ #define QGBA_GBA_KEY_EDITOR
#include <QList> #include <QPicture> +#include <QSet> #include <QWidget> + +extern "C" { +#include "gba/input.h" +} + +class QTimer; namespace QGBA {@@ -32,7 +39,7 @@ private slots:
void setNext(); void save(); #ifdef BUILD_SDL - void testGamepad(); + void setAxisValue(int axis, int32_t value); #endif private:@@ -43,10 +50,16 @@ static const qreal DPAD_HEIGHT;
void setLocation(QWidget* widget, qreal x, qreal y); + void lookupBinding(const GBAInputMap*, KeyEditor*, GBAKey); + void bindKey(const KeyEditor*, GBAKey); + + bool findFocus(); #ifdef BUILD_SDL - QTimer* m_gamepadTimer; + void lookupAxes(const GBAInputMap*); #endif + + KeyEditor* keyById(GBAKey); QWidget* m_buttons; KeyEditor* m_keyDU;
@@ -13,17 +13,13 @@ GDBController::GDBController(GameController* controller, QObject* parent)
: QObject(parent) , m_gameController(controller) , m_port(2345) - , m_bindAddress(0) + , m_bindAddress({ IPV4, 0 }) { GDBStubCreate(&m_gdbStub); } ushort GDBController::port() { return m_port; -} - -uint32_t GDBController::bindAddress() { - return m_bindAddress; } bool GDBController::isAttached() {@@ -35,7 +31,8 @@ m_port = port;
} void GDBController::setBindAddress(uint32_t bindAddress) { - m_bindAddress = bindAddress; + m_bindAddress.version = IPV4; + m_bindAddress.ipv4 = htonl(bindAddress); } void GDBController::attach() {@@ -43,34 +40,38 @@ if (isAttached()) {
return; } m_gameController->setDebugger(&m_gdbStub.d); + if (m_gameController->isLoaded()) { + ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0); + } else { + QObject::disconnect(m_autoattach); + m_autoattach = connect(m_gameController, &GameController::gameStarted, [this] () { + QObject::disconnect(m_autoattach); + ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0); + }); + } } void GDBController::detach() { + QObject::disconnect(m_autoattach); if (!isAttached()) { return; } - bool wasPaused = m_gameController->isPaused(); - disconnect(m_gameController, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateGDB())); - m_gameController->setPaused(true); + m_gameController->threadInterrupt(); GDBStubShutdown(&m_gdbStub); m_gameController->setDebugger(nullptr); - m_gameController->setPaused(wasPaused); + m_gameController->threadContinue(); } void GDBController::listen() { + m_gameController->threadInterrupt(); if (!isAttached()) { attach(); } - bool wasPaused = m_gameController->isPaused(); - connect(m_gameController, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateGDB())); - m_gameController->setPaused(true); - GDBStubListen(&m_gdbStub, m_port, m_bindAddress); - m_gameController->setPaused(wasPaused); -} - -void GDBController::updateGDB() { - bool wasPaused = m_gameController->isPaused(); - m_gameController->setPaused(true); - GDBStubUpdate(&m_gdbStub); - m_gameController->setPaused(wasPaused); + if (GDBStubListen(&m_gdbStub, m_port, &m_bindAddress)) { + emit listening(); + } else { + detach(); + emit listenFailed(); + } + m_gameController->threadContinue(); }
@@ -8,6 +8,8 @@ #define QGBA_GDB_CONTROLLER
#include <QObject> +#ifdef USE_GDB_STUB + extern "C" { #include "debugger/gdb-stub.h" }@@ -24,7 +26,6 @@ GDBController(GameController* controller, QObject* parent = nullptr);
public: ushort port(); - uint32_t bindAddress(); bool isAttached(); public slots:@@ -34,16 +35,22 @@ void attach();
void detach(); void listen(); -private slots: - void updateGDB(); +signals: + void listening(); + void listenFailed(); private: GDBStub m_gdbStub; GameController* m_gameController; ushort m_port; - uint32_t m_bindAddress; + Address m_bindAddress; + + QMetaObject::Connection m_autoattach; }; } + +#endif + #endif
@@ -9,6 +9,7 @@ #include <QGridLayout>
#include <QGroupBox> #include <QLabel> #include <QLineEdit> +#include <QMessageBox> #include <QPushButton> #include <QVBoxLayout>@@ -46,6 +47,9 @@ settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft);
m_startStopButton = new QPushButton; mainSegment->addWidget(m_startStopButton); + connect(m_gdbController, SIGNAL(listening()), this, SLOT(started())); + connect(m_gdbController, SIGNAL(listenFailed()), this, SLOT(failed())); + if (m_gdbController->isAttached()) { started(); } else {@@ -88,7 +92,6 @@ m_portEdit->setEnabled(false);
m_bindAddressEdit->setEnabled(false); m_startStopButton->setText(tr("Stop")); disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen())); - disconnect(m_startStopButton, SIGNAL(clicked()), this, SLOT(started())); connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach())); connect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped())); }@@ -100,5 +103,12 @@ m_startStopButton->setText(tr("Start"));
disconnect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(detach())); disconnect(m_startStopButton, SIGNAL(clicked()), this, SLOT(stopped())); connect(m_startStopButton, SIGNAL(clicked()), m_gdbController, SLOT(listen())); - connect(m_startStopButton, SIGNAL(clicked()), this, SLOT(started())); +} + +void GDBWindow::failed() { + QMessageBox* failure = new QMessageBox(QMessageBox::Warning, tr("Crash"), + tr("Could not start GDB server"), + QMessageBox::Ok, this, Qt::Sheet); + failure->setAttribute(Qt::WA_DeleteOnClose); + failure->show(); }
@@ -28,6 +28,8 @@
void started(); void stopped(); + void failed(); + private: GDBController* m_gdbController;
@@ -8,17 +8,24 @@
#include "AudioProcessor.h" #include "InputController.h" +#include <QDateTime> #include <QThread> +#include <ctime> + extern "C" { -#include "gba.h" -#include "gba-audio.h" -#include "gba-serialize.h" -#include "renderers/video-software.h" +#include "gba/audio.h" +#include "gba/gba.h" +#include "gba/serialize.h" +#include "gba/renderers/video-software.h" +#include "gba/supervisor/config.h" #include "util/vfs.h" } using namespace QGBA; +using namespace std; + +const int GameController::LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; GameController::GameController(QObject* parent) : QObject(parent)@@ -39,6 +46,9 @@ m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer); m_renderer->outputBuffer = (color_t*) m_drawContext; m_renderer->outputBufferStride = 256; + + GBACheatDeviceCreate(&m_cheatDevice); + m_threadContext.state = THREAD_INITIALIZED; m_threadContext.debugger = 0; m_threadContext.frameskip = 0;@@ -46,11 +56,44 @@ m_threadContext.bios = 0;
m_threadContext.renderer = &m_renderer->d; m_threadContext.userData = this; m_threadContext.rewindBufferCapacity = 0; + m_threadContext.cheats = &m_cheatDevice; m_threadContext.logLevel = -1; + m_lux.p = this; + m_lux.sample = [] (GBALuminanceSource* context) { + GameControllerLux* lux = static_cast<GameControllerLux*>(context); + lux->value = 0xFF - lux->p->m_luxValue; + }; + + m_lux.readLuminance = [] (GBALuminanceSource* context) { + GameControllerLux* lux = static_cast<GameControllerLux*>(context); + return lux->value; + }; + setLuminanceLevel(0); + + m_rtc.p = this; + m_rtc.override = GameControllerRTC::NO_OVERRIDE; + m_rtc.sample = [] (GBARTCSource* context) { }; + m_rtc.unixTime = [] (GBARTCSource* context) -> time_t { + GameControllerRTC* rtc = static_cast<GameControllerRTC*>(context); + switch (rtc->override) { + case GameControllerRTC::NO_OVERRIDE: + default: + return time(nullptr); + case GameControllerRTC::FIXED: + return rtc->value; + case GameControllerRTC::FAKE_EPOCH: + return rtc->value + rtc->p->m_threadContext.gba->video.frameCounter * (int64_t) VIDEO_TOTAL_LENGTH / GBA_ARM7TDMI_FREQUENCY; + } + }; + m_threadContext.startCallback = [] (GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); controller->m_audioProcessor->setInput(context); + // Override the GBA object's log level to prevent stdout spew + context->gba->logLevel = GBA_LOG_FATAL; + context->gba->luminanceSource = &controller->m_lux; + context->gba->rtcSource = &controller->m_rtc; controller->gameStarted(context); };@@ -73,7 +116,9 @@ };
m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) { GameController* controller = static_cast<GameController*>(context->userData); - if (!(controller->m_logLevels & level)) { + if (level == GBA_LOG_FATAL) { + QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args))); + } else if (!(controller->m_logLevels & level)) { return; } controller->postLog(level, QString().vsprintf(format, args));@@ -96,26 +141,45 @@ m_audioThread->quit();
m_audioThread->wait(); disconnect(); closeGame(); + GBACheatDeviceDestroy(&m_cheatDevice); delete m_renderer; delete[] m_drawContext; } +void GameController::setOverride(const GBACartridgeOverride& override) { + m_threadContext.override = override; + m_threadContext.hasOverride = true; +} + +void GameController::setOptions(const GBAOptions* opts) { + setFrameskip(opts->frameskip); + setAudioSync(opts->audioSync); + setVideoSync(opts->videoSync); + setSkipBIOS(opts->skipBios); + setRewind(opts->rewindEnable, opts->rewindBufferCapacity, opts->rewindBufferInterval); + + threadInterrupt(); + m_threadContext.idleOptimization = opts->idleOptimization; + threadContinue(); +} + +#ifdef USE_GDB_STUB ARMDebugger* GameController::debugger() { return m_threadContext.debugger; } void GameController::setDebugger(ARMDebugger* debugger) { - bool wasPaused = isPaused(); - setPaused(true); - if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) { + threadInterrupt(); + if (m_threadContext.debugger && GBAThreadIsActive(&m_threadContext)) { GBADetachDebugger(m_threadContext.gba); } m_threadContext.debugger = debugger; - if (m_threadContext.debugger && GBAThreadHasStarted(&m_threadContext)) { + if (m_threadContext.debugger && GBAThreadIsActive(&m_threadContext)) { GBAAttachDebugger(m_threadContext.gba, m_threadContext.debugger); } - setPaused(wasPaused); + threadContinue(); } +#endif void GameController::loadGame(const QString& path, bool dirmode) { closeGame();@@ -145,14 +209,22 @@ m_threadContext.sync.videoFrameWait = m_videoSync;
m_threadContext.sync.audioWait = m_audioSync; } + m_threadContext.gameDir = 0; m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); if (m_dirmode) { m_threadContext.gameDir = VDirOpen(m_threadContext.fname); m_threadContext.stateDir = m_threadContext.gameDir; } else { m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); -#if ENABLE_LIBZIP - m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); +#if USE_LIBZIP + if (!m_threadContext.gameDir) { + m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); + } +#endif +#if USE_LZMA + if (!m_threadContext.gameDir) { + m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); + } #endif }@@ -166,10 +238,14 @@ }
if (!GBAThreadStart(&m_threadContext)) { m_gameOpen = false; + emit gameFailed(); } } void GameController::loadBIOS(const QString& path) { + if (m_bios == path) { + return; + } m_bios = path; if (m_gameOpen) { closeGame();@@ -178,10 +254,12 @@ }
} void GameController::loadPatch(const QString& path) { - m_patch = path; if (m_gameOpen) { closeGame(); + m_patch = path; openGame(); + } else { + m_patch = path; } }@@ -201,11 +279,26 @@ }
m_patch = QString(); + for (size_t i = 0; i < GBACheatSetsSize(&m_cheatDevice.cheats); ++i) { + GBACheatSet* set = *GBACheatSetsGetPointer(&m_cheatDevice.cheats, i); + GBACheatSetDeinit(set); + delete set; + } + GBACheatSetsClear(&m_cheatDevice.cheats); + m_gameOpen = false; emit gameStopped(&m_threadContext); } +void GameController::crashGame(const QString& crashMessage) { + closeGame(); + emit gameCrashed(crashMessage); +} + bool GameController::isPaused() { + if (!m_gameOpen) { + return false; + } return GBAThreadIsPaused(&m_threadContext); }@@ -245,6 +338,32 @@ setPaused(false);
m_pauseMutex.unlock(); } +void GameController::setRewind(bool enable, int capacity, int interval) { + if (m_gameOpen) { + threadInterrupt(); + GBARewindSettingsChanged(&m_threadContext, enable ? capacity : 0, enable ? interval : 0); + threadContinue(); + } else { + if (enable) { + m_threadContext.rewindBufferInterval = interval; + m_threadContext.rewindBufferCapacity = capacity; + } else { + m_threadContext.rewindBufferInterval = 0; + m_threadContext.rewindBufferCapacity = 0; + } + } +} + +void GameController::rewind(int states) { + threadInterrupt(); + if (!states) { + GBARewindAll(&m_threadContext); + } else { + GBARewind(&m_threadContext, states); + } + threadContinue(); +} + void GameController::keyPressed(int key) { int mappedKey = 1 << key; m_activeKeys |= mappedKey;@@ -257,29 +376,35 @@ m_activeKeys &= ~mappedKey;
updateKeys(); } -void GameController::setAudioBufferSamples(int samples) { - if (m_gameOpen) { - threadInterrupt(); - m_threadContext.audioBuffers = samples; - GBAAudioResizeBuffer(&m_threadContext.gba->audio, samples); - threadContinue(); - } else { - m_threadContext.audioBuffers = samples; +void GameController::clearKeys() { + m_activeKeys = 0; + updateKeys(); +} - } +void GameController::setAudioBufferSamples(int samples) { + threadInterrupt(); + redoSamples(samples); + threadContinue(); QMetaObject::invokeMethod(m_audioProcessor, "setBufferSamples", Q_ARG(int, samples)); } void GameController::setFPSTarget(float fps) { threadInterrupt(); m_threadContext.fpsTarget = fps; + redoSamples(m_audioProcessor->getBufferSamples()); threadContinue(); QMetaObject::invokeMethod(m_audioProcessor, "inputParametersChanged"); } +void GameController::setSkipBIOS(bool set) { + threadInterrupt(); + m_threadContext.skipBios = set; + threadContinue(); +} + void GameController::loadState(int slot) { threadInterrupt(); - GBALoadState(m_threadContext.gba, m_threadContext.stateDir, slot); + GBALoadState(&m_threadContext, m_threadContext.stateDir, slot); threadContinue(); emit stateLoaded(&m_threadContext); emit frameAvailable(m_drawContext);@@ -287,7 +412,7 @@ }
void GameController::saveState(int slot) { threadInterrupt(); - GBASaveState(m_threadContext.gba, m_threadContext.stateDir, slot, true); + GBASaveState(&m_threadContext, m_threadContext.stateDir, slot, true); threadContinue(); }@@ -318,11 +443,7 @@ if (m_turboForced && !forced) {
return; } m_turbo = set; - if (set) { - m_turboForced = forced; - } else { - m_turboForced = false; - } + m_turboForced = set && forced; threadInterrupt(); m_threadContext.sync.audioWait = set ? false : m_audioSync; m_threadContext.sync.videoFrameWait = set ? false : m_videoSync;@@ -341,12 +462,81 @@ m_threadContext.stream = nullptr;
threadContinue(); } +void GameController::reloadAudioDriver() { + QMetaObject::invokeMethod(m_audioProcessor, "pause", Qt::BlockingQueuedConnection); + int samples = m_audioProcessor->getBufferSamples(); + delete m_audioProcessor; + m_audioProcessor = AudioProcessor::create(); + m_audioProcessor->setBufferSamples(samples); + m_audioProcessor->moveToThread(m_audioThread); + connect(this, SIGNAL(gameStarted(GBAThread*)), m_audioProcessor, SLOT(start())); + connect(this, SIGNAL(gameStopped(GBAThread*)), m_audioProcessor, SLOT(pause())); + connect(this, SIGNAL(gamePaused(GBAThread*)), m_audioProcessor, SLOT(pause())); + connect(this, SIGNAL(gameUnpaused(GBAThread*)), m_audioProcessor, SLOT(start())); + if (isLoaded()) { + m_audioProcessor->setInput(&m_threadContext); + QMetaObject::invokeMethod(m_audioProcessor, "start"); + } +} + +void GameController::setLuminanceValue(uint8_t value) { + m_luxValue = value; + value = std::max<int>(value - 0x16, 0); + m_luxLevel = 10; + for (int i = 0; i < 10; ++i) { + if (value < LUX_LEVELS[i]) { + m_luxLevel = i; + break; + } + } +} + +void GameController::setLuminanceLevel(int level) { + int value = 0x16; + level = std::max(0, std::min(10, level)); + if (level > 0) { + value += LUX_LEVELS[level - 1]; + } + setLuminanceValue(value); +} + +void GameController::setRealTime() { + m_rtc.override = GameControllerRTC::NO_OVERRIDE; +} + +void GameController::setFixedTime(const QDateTime& time) { + m_rtc.override = GameControllerRTC::FIXED; + m_rtc.value = time.toMSecsSinceEpoch() / 1000; +} + +void GameController::setFakeEpoch(const QDateTime& time) { + m_rtc.override = GameControllerRTC::FAKE_EPOCH; + m_rtc.value = time.toMSecsSinceEpoch() / 1000; +} + void GameController::updateKeys() { int activeKeys = m_activeKeys; #ifdef BUILD_SDL activeKeys |= m_activeButtons; #endif m_threadContext.activeKeys = activeKeys; +} + +void GameController::redoSamples(int samples) { +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF + float sampleRate = 0x8000; + float ratio; + if (m_threadContext.gba) { + sampleRate = m_threadContext.gba->audio.sampleRate; + } + ratio = GBAAudioCalculateRatio(sampleRate, m_threadContext.fpsTarget, 44100); + m_threadContext.audioBuffers = ceil(samples / ratio); +#else + m_threadContext.audioBuffers = samples; +#endif + if (m_threadContext.gba) { + GBAAudioResizeBuffer(&m_threadContext.gba->audio, m_threadContext.audioBuffers); + } } void GameController::setLogLevel(int levels) {
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -13,14 +13,18 @@ #include <QMutex>
#include <QString> extern "C" { -#include "gba-thread.h" +#include "gba/cheats.h" +#include "gba/hardware.h" +#include "gba/supervisor/thread.h" #ifdef BUILD_SDL #include "sdl-events.h" #endif } struct GBAAudio; +struct GBAOptions; struct GBAVideoSoftwareRenderer; +struct Configuration; class QThread;@@ -33,7 +37,7 @@ class GameController : public QObject {
Q_OBJECT public: - static const bool VIDEO_SYNC = true; + static const bool VIDEO_SYNC = false; static const bool AUDIO_SYNC = true; GameController(QObject* parent = nullptr);@@ -41,6 +45,7 @@ ~GameController();
const uint32_t* drawContext() const { return m_drawContext; } GBAThread* thread() { return &m_threadContext; } + GBACheatDevice* cheatDevice() { return &m_cheatDevice; } void threadInterrupt(); void threadContinue();@@ -52,6 +57,12 @@ bool audioSync() const { return m_audioSync; }
bool videoSync() const { return m_videoSync; } void setInputController(InputController* controller) { m_inputController = controller; } + void setOverrides(Configuration* overrides) { m_threadContext.overrides = overrides; } + + void setOverride(const GBACartridgeOverride& override); + void clearOverride() { m_threadContext.hasOverride = false; } + + void setOptions(const GBAOptions*); #ifdef USE_GDB_STUB ARMDebugger* debugger();@@ -64,6 +75,8 @@ void gameStarted(GBAThread*);
void gameStopped(GBAThread*); void gamePaused(GBAThread*); void gameUnpaused(GBAThread*); + void gameCrashed(const QString& errorMessage); + void gameFailed(); void stateLoaded(GBAThread*); void postLog(int level, const QString& log);@@ -71,14 +84,18 @@
public slots: void loadGame(const QString& path, bool dirmode = false); void loadBIOS(const QString& path); + void setSkipBIOS(bool); void loadPatch(const QString& path); void openGame(); void closeGame(); void setPaused(bool paused); void reset(); void frameAdvance(); + void setRewind(bool enable, int capacity, int interval); + void rewind(int states = 0); void keyPressed(int key); void keyReleased(int key); + void clearKeys(); void setAudioBufferSamples(int samples); void setFPSTarget(float fps); void loadState(int slot);@@ -89,13 +106,25 @@ void setFrameskip(int);
void setTurbo(bool, bool forced = true); void setAVStream(GBAAVStream*); void clearAVStream(); + void reloadAudioDriver(); + + void setLuminanceValue(uint8_t value); + void setLuminanceLevel(int level); + void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); } + void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); } + + void setRealTime(); + void setFixedTime(const QDateTime& time); + void setFakeEpoch(const QDateTime& time); void setLogLevel(int); void enableLogLevel(int); void disableLogLevel(int); -#ifdef BUILD_SDL private slots: + void crashGame(const QString& crashMessage); + +#ifdef BUILD_SDL void testSDLEvents(); private:@@ -105,10 +134,12 @@ #endif
private: void updateKeys(); + void redoSamples(int samples); uint32_t* m_drawContext; GBAThread m_threadContext; GBAVideoSoftwareRenderer* m_renderer; + GBACheatDevice m_cheatDevice; int m_activeKeys; int m_logLevels;@@ -131,6 +162,25 @@ bool m_turbo;
bool m_turboForced; InputController* m_inputController; + + struct GameControllerLux : GBALuminanceSource { + GameController* p; + uint8_t value; + } m_lux; + uint8_t m_luxValue; + int m_luxLevel; + + static const int LUX_LEVELS[10]; + + struct GameControllerRTC : GBARTCSource { + GameController* p; + enum { + NO_OVERRIDE, + FIXED, + FAKE_EPOCH + } override; + int64_t value; + } m_rtc; }; }
@@ -0,0 +1,35 @@
+/* Copyright (c) 2013-2015 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 "GamepadAxisEvent.h" + +#include "InputController.h" + +using namespace QGBA; + +QEvent::Type GamepadAxisEvent::s_type = QEvent::None; + +GamepadAxisEvent::GamepadAxisEvent(int axis, Direction direction, bool isNew, InputController* controller) + : QEvent(Type()) + , m_axis(axis) + , m_direction(direction) + , m_isNew(isNew) + , m_controller(controller) + , m_key(GBA_KEY_NONE) +{ + ignore(); +#ifdef BUILD_SDL + if (controller) { + m_key = GBAInputMapAxis(controller->map(), SDL_BINDING_BUTTON, axis, direction * INT_MAX); + } +#endif +} + +QEvent::Type GamepadAxisEvent::Type() { + if (s_type == None) { + s_type = static_cast<enum Type>(registerEventType()); + } + return s_type; +}
@@ -0,0 +1,48 @@
+/* Copyright (c) 2013-2015 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 QGBA_GAMEPAD_AXIS_EVENT +#define QGBA_GAMEPAD_AXIS_EVENT + +#include <QEvent> + +extern "C" { +#include "gba/input.h" +} + +namespace QGBA { + +class InputController; + +class GamepadAxisEvent : public QEvent { +public: + enum Direction { + NEUTRAL = 0, + POSITIVE = 1, + NEGATIVE = -1 + }; + + GamepadAxisEvent(int axis, Direction direction, bool isNew, InputController* controller = nullptr); + + int axis() const { return m_axis; } + Direction direction() const { return m_direction; } + bool isNew() const { return m_isNew; } + GBAKey gbaKey() const { return m_key; } + + static enum Type Type(); + +private: + static enum Type s_type; + + int m_axis; + Direction m_direction; + bool m_isNew; + InputController* m_controller; + GBAKey m_key; +}; + +} + +#endif
@@ -0,0 +1,41 @@
+/* Copyright (c) 2013-2015 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 "GamepadButtonEvent.h" + +#include "InputController.h" + +using namespace QGBA; + +QEvent::Type GamepadButtonEvent::s_downType = QEvent::None; +QEvent::Type GamepadButtonEvent::s_upType = QEvent::None; + +GamepadButtonEvent::GamepadButtonEvent(QEvent::Type type, int button, InputController* controller) + : QEvent(type) + , m_button(button) + , m_controller(controller) + , m_key(GBA_KEY_NONE) +{ + ignore(); +#ifdef BUILD_SDL + if (controller) { + m_key = GBAInputMapKey(controller->map(), SDL_BINDING_BUTTON, button); + } +#endif +} + +QEvent::Type GamepadButtonEvent::Down() { + if (s_downType == None) { + s_downType = static_cast<Type>(registerEventType()); + } + return s_downType; +} + +QEvent::Type GamepadButtonEvent::Up() { + if (s_upType == None) { + s_upType = static_cast<Type>(registerEventType()); + } + return s_upType; +}
@@ -0,0 +1,40 @@
+/* Copyright (c) 2013-2015 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 QGBA_GAMEPAD_BUTTON_EVENT +#define QGBA_GAMEPAD_BUTTON_EVENT + +#include <QEvent> + +extern "C" { +#include "gba/input.h" +} + +namespace QGBA { + +class InputController; + +class GamepadButtonEvent : public QEvent { +public: + GamepadButtonEvent(Type type, int button, InputController* controller = nullptr); + + int value() const { return m_button; } + GBAKey gbaKey() const { return m_key; } + + static Type Down(); + static Type Up(); + +private: + static Type s_downType; + static Type s_upType; + + int m_button; + InputController* m_controller; + GBAKey m_key; +}; + +} + +#endif
@@ -6,6 +6,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "InputController.h" #include "ConfigController.h" +#include "GamepadAxisEvent.h" +#include "GamepadButtonEvent.h" + +#include <QApplication> +#include <QTimer> +#include <QWidget> extern "C" { #include "util/configuration.h"@@ -13,7 +19,11 @@ }
using namespace QGBA; -InputController::InputController() { +InputController::InputController(QObject* parent) + : QObject(parent) + , m_config(nullptr) + , m_gamepadTimer(nullptr) +{ GBAInputMapInit(&m_inputMap); #ifdef BUILD_SDL@@ -21,6 +31,11 @@ m_sdlEvents.bindings = &m_inputMap;
GBASDLInitEvents(&m_sdlEvents); GBASDLInitBindings(&m_inputMap); SDL_JoystickEventState(SDL_QUERY); + + m_gamepadTimer = new QTimer(this); + connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad())); + m_gamepadTimer->setInterval(50); + m_gamepadTimer->start(); #endif GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);@@ -80,6 +95,9 @@ GBAKey key = GBAInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i);
if (key == GBA_KEY_NONE) { continue; } + if (hasPendingEvent(key)) { + continue; + } if (SDL_JoystickGetButton(joystick, i)) { activeButtons |= 1 << key; }@@ -100,6 +118,16 @@ if (hat & SDL_HAT_RIGHT) {
activeButtons |= 1 << GBA_KEY_RIGHT; } } + + int numAxes = SDL_JoystickNumAxes(joystick); + for (i = 0; i < numAxes; ++i) { + int value = SDL_JoystickGetAxis(joystick, i); + + enum GBAKey key = GBAInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value); + if (key != GBA_KEY_NONE) { + activeButtons |= 1 << key; + } + } return activeButtons; }@@ -116,4 +144,102 @@ }
} return activeButtons; } + +QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes() { + SDL_Joystick* joystick = m_sdlEvents.joystick; + SDL_JoystickUpdate(); + int numButtons = SDL_JoystickNumAxes(joystick); + QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes; + int i; + for (i = 0; i < numButtons; ++i) { + int32_t axis = SDL_JoystickGetAxis(joystick, i); + if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) { + activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE)); + } + } + return activeAxes; +} + +void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) { + const GBAAxis* old = GBAInputQueryAxis(&m_inputMap, SDL_BINDING_BUTTON, axis); + GBAAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD }; + if (old) { + description = *old; + } + switch (direction) { + case GamepadAxisEvent::NEGATIVE: + description.lowDirection = key; + description.deadLow = -AXIS_THRESHOLD; + break; + case GamepadAxisEvent::POSITIVE: + description.highDirection = key; + description.deadHigh = AXIS_THRESHOLD; + break; + default: + return; + } + GBAInputBindAxis(&m_inputMap, SDL_BINDING_BUTTON, axis, &description); +} #endif + +void InputController::testGamepad() { +#ifdef BUILD_SDL + auto activeAxes = activeGamepadAxes(); + auto oldAxes = m_activeAxes; + m_activeAxes = activeAxes; + + auto activeButtons = activeGamepadButtons(); + auto oldButtons = m_activeButtons; + m_activeButtons = activeButtons; + + if (!QApplication::focusWidget()) { + return; + } + + activeAxes.subtract(oldAxes); + oldAxes.subtract(m_activeAxes); + + for (auto& axis : m_activeAxes) { + bool newlyAboveThreshold = activeAxes.contains(axis); + GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this); + if (newlyAboveThreshold) { + postPendingEvent(event->gbaKey()); + if (!event->isAccepted()) { + clearPendingEvent(event->gbaKey()); + } + } else if (oldAxes.contains(axis)) { + clearPendingEvent(event->gbaKey()); + } + QApplication::sendEvent(QApplication::focusWidget(), event); + } + + activeButtons.subtract(oldButtons); + oldButtons.subtract(m_activeButtons); + + for (int button : activeButtons) { + GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, this); + postPendingEvent(event->gbaKey()); + QApplication::sendEvent(QApplication::focusWidget(), event); + if (!event->isAccepted()) { + clearPendingEvent(event->gbaKey()); + } + } + for (int button : oldButtons) { + GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, this); + clearPendingEvent(event->gbaKey()); + QApplication::sendEvent(QApplication::focusWidget(), event); + } +#endif +} + +void InputController::postPendingEvent(GBAKey key) { + m_pendingEvents.insert(key); +} + +void InputController::clearPendingEvent(GBAKey key) { + m_pendingEvents.remove(key); +} + +bool InputController::hasPendingEvent(GBAKey key) const { + return m_pendingEvents.contains(key); +}
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,25 +6,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_INPUT_CONTROLLER_H #define QGBA_INPUT_CONTROLLER_H +#include "GamepadAxisEvent.h" + +#include <QObject> +#include <QSet> + +class QTimer; + extern "C" { -#include "gba-input.h" +#include "gba/input.h" #ifdef BUILD_SDL #include "platform/sdl/sdl-events.h" #endif } - -#include <QSet> namespace QGBA { class ConfigController; -class InputController { +class InputController : public QObject { +Q_OBJECT + public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(); + InputController(QObject* parent = nullptr); ~InputController(); void setConfiguration(ConfigController* config);@@ -38,17 +45,35 @@
const GBAInputMap* map() const { return &m_inputMap; } #ifdef BUILD_SDL + static const int32_t AXIS_THRESHOLD = 0x3000; + int testSDLEvents(); QSet<int> activeGamepadButtons(); + QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes(); + + void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey); #endif +public slots: + void testGamepad(); + private: + void postPendingEvent(GBAKey); + void clearPendingEvent(GBAKey); + bool hasPendingEvent(GBAKey) const; + GBAInputMap m_inputMap; ConfigController* m_config; #ifdef BUILD_SDL GBASDLEvents m_sdlEvents; #endif + + QSet<int> m_activeButtons; + QSet<QPair<int, GamepadAxisEvent::Direction>> m_activeAxes; + QTimer* m_gamepadTimer; + + QSet<GBAKey> m_pendingEvents; }; }
@@ -5,20 +5,27 @@ * 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 "KeyEditor.h" +#include "GamepadAxisEvent.h" +#include "GamepadButtonEvent.h" + #include <QKeyEvent> using namespace QGBA; KeyEditor::KeyEditor(QWidget* parent) : QLineEdit(parent) - , m_numeric(false) + , m_direction(GamepadAxisEvent::NEUTRAL) { setAlignment(Qt::AlignCenter); } void KeyEditor::setValue(int key) { - if (m_numeric) { - setText(QString::number(key)); + if (m_button) { + if (key < 0) { + clear(); + } else { + setText(QString::number(key)); + } } else { setText(QKeySequence(key).toString(QKeySequence::NativeText)); }@@ -26,6 +33,25 @@ m_key = key;
emit valueChanged(key); } +void KeyEditor::setValueKey(int key) { + m_button = false; + setValue(key); +} + +void KeyEditor::setValueButton(int button) { + m_button = true; + m_direction = GamepadAxisEvent::NEUTRAL; + setValue(button); +} + +void KeyEditor::setValueAxis(int axis, int32_t value) { + m_button = true; + m_key = axis; + m_direction = value < 0 ? GamepadAxisEvent::NEGATIVE : GamepadAxisEvent::POSITIVE; + setText((value < 0 ? "-" : "+") + QString::number(axis)); + emit axisChanged(axis, m_direction); +} + QSize KeyEditor::sizeHint() const { QSize hint = QLineEdit::sizeHint(); hint.setWidth(40);@@ -33,8 +59,28 @@ return hint;
} void KeyEditor::keyPressEvent(QKeyEvent* event) { - if (!m_numeric) { + if (!m_button) { setValue(event->key()); } event->accept(); } + +bool KeyEditor::event(QEvent* event) { + if (!m_button) { + return QWidget::event(event); + } + if (event->type() == GamepadButtonEvent::Down()) { + setValueButton(static_cast<GamepadButtonEvent*>(event)->value()); + event->accept(); + return true; + } + if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); + if (gae->isNew()) { + setValueAxis(gae->axis(), gae->direction()); + } + event->accept(); + return true; + } + return QWidget::event(event); +}
@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_KEY_EDITOR #define QGBA_KEY_EDITOR +#include "GamepadAxisEvent.h" #include <QLineEdit> namespace QGBA {@@ -16,22 +17,30 @@
public: KeyEditor(QWidget* parent = nullptr); - void setValue(int key); int value() const { return m_key; } - void setNumeric(bool numeric) { m_numeric = numeric; } + GamepadAxisEvent::Direction direction() const { return m_direction; } virtual QSize sizeHint() const override; +public slots: + void setValue(int key); + void setValueKey(int key); + void setValueButton(int button); + void setValueAxis(int axis, int32_t value); + signals: void valueChanged(int key); + void axisChanged(int key, int direction); protected: virtual void keyPressEvent(QKeyEvent* event) override; + virtual bool event(QEvent* event) override; private: int m_key; - bool m_numeric; + bool m_button; + GamepadAxisEvent::Direction m_direction; }; }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,14 +6,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LoadSaveState.h" #include "GameController.h" +#include "GamepadAxisEvent.h" +#include "GamepadButtonEvent.h" #include "VFileDevice.h" #include <QKeyEvent> #include <QPainter> extern "C" { -#include "gba-serialize.h" -#include "gba-video.h" +#include "gba/serialize.h" +#include "gba/video.h" } using namespace QGBA;@@ -103,6 +105,52 @@ m_slots[m_currentFocus]->setFocus();
return true; } } + } + if (event->type() == GamepadButtonEvent::Down() || event->type() == GamepadAxisEvent::Type()) { + int column = m_currentFocus % 3; + int row = m_currentFocus - column; + GBAKey key = GBA_KEY_NONE; + if (event->type() == GamepadButtonEvent::Down()) { + key = static_cast<GamepadButtonEvent*>(event)->gbaKey(); + } else if (event->type() == GamepadAxisEvent::Type()) { + GamepadAxisEvent* gae = static_cast<GamepadAxisEvent*>(event); + if (gae->isNew()) { + key = gae->gbaKey(); + } else { + return false; + } + } + switch (key) { + case GBA_KEY_UP: + row += 6; + break; + case GBA_KEY_DOWN: + row += 3; + break; + case GBA_KEY_LEFT: + column += 2; + break; + case GBA_KEY_RIGHT: + column += 1; + break; + case GBA_KEY_B: + event->accept(); + close(); + return true; + case GBA_KEY_A: + case GBA_KEY_START: + event->accept(); + triggerState(m_currentFocus + 1); + return true; + default: + return false; + } + column %= 3; + row %= 9; + m_currentFocus = column + row; + m_slots[m_currentFocus]->setFocus(); + event->accept(); + return true; } return false; }
@@ -13,6 +13,7 @@
namespace QGBA { class GameController; +class InputController; class SavestateButton; enum class LoadSave {@@ -28,6 +29,7 @@ const static int NUM_SLOTS = 9;
LoadSaveState(GameController* controller, QWidget* parent = nullptr); + void setInputController(InputController* controller); void setMode(LoadSave mode); signals:@@ -45,6 +47,7 @@ void triggerState(int slot);
Ui::LoadSaveState m_ui; GameController* m_controller; + InputController* m_inputController; SavestateButton* m_slots[NUM_SLOTS]; LoadSave m_mode;
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -11,7 +11,7 @@
#include "ui_LogView.h" extern "C" { -#include "gba-thread.h" +#include "gba/supervisor/thread.h" } namespace QGBA {
@@ -0,0 +1,156 @@
+/* Copyright (c) 2013-2015 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 "OverrideView.h" + +#include "ConfigController.h" +#include "GameController.h" + +extern "C" { +#include "gba/supervisor/thread.h" +} + +using namespace QGBA; + +OverrideView::OverrideView(GameController* controller, ConfigController* config, QWidget* parent) + : QWidget(parent) + , m_controller(controller) + , m_config(config) +{ + m_ui.setupUi(this); + + connect(controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*))); + connect(controller, SIGNAL(gameStopped(GBAThread*)), this, SLOT(gameStopped())); + + connect(m_ui.hwAutodetect, &QAbstractButton::toggled, [this] (bool enabled) { + m_ui.hwRTC->setEnabled(!enabled); + m_ui.hwGyro->setEnabled(!enabled); + m_ui.hwLight->setEnabled(!enabled); + m_ui.hwTilt->setEnabled(!enabled); + m_ui.hwRumble->setEnabled(!enabled); + }); + + connect(m_ui.savetype, SIGNAL(currentIndexChanged(int)), this, SLOT(updateOverrides())); + connect(m_ui.hwAutodetect, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.hwRTC, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.hwGyro, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.hwLight, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.hwTilt, SIGNAL(clicked()), this, SLOT(updateOverrides())); + connect(m_ui.hwRumble, SIGNAL(clicked()), this, SLOT(updateOverrides())); + + connect(m_ui.save, SIGNAL(clicked()), this, SLOT(saveOverride())); + + if (controller->isLoaded()) { + gameStarted(controller->thread()); + } +} + +void OverrideView::saveOverride() { + if (!m_config) { + return; + } + m_config->saveOverride(m_override); +} + +void OverrideView::updateOverrides() { + m_override = (GBACartridgeOverride) { + "", + static_cast<SavedataType>(m_ui.savetype->currentIndex() - 1), + HW_NO_OVERRIDE, + IDLE_LOOP_NONE + }; + + if (!m_ui.hwAutodetect->isChecked()) { + m_override.hardware = HW_NONE; + if (m_ui.hwRTC->isChecked()) { + m_override.hardware |= HW_RTC; + } + if (m_ui.hwGyro->isChecked()) { + m_override.hardware |= HW_GYRO; + } + if (m_ui.hwLight->isChecked()) { + m_override.hardware |= HW_LIGHT_SENSOR; + } + if (m_ui.hwTilt->isChecked()) { + m_override.hardware |= HW_TILT; + } + if (m_ui.hwRumble->isChecked()) { + m_override.hardware |= HW_RUMBLE; + } + } + + bool ok; + uint32_t parsedIdleLoop = m_ui.idleLoop->text().toInt(&ok, 16); + if (ok) { + m_override.idleLoop = parsedIdleLoop; + } + + if (m_override.savetype != SAVEDATA_AUTODETECT || m_override.hardware != HW_NO_OVERRIDE || m_override.idleLoop != IDLE_LOOP_NONE) { + m_controller->setOverride(m_override); + } else { + m_controller->clearOverride(); + } +} + +void OverrideView::gameStarted(GBAThread* thread) { + if (!thread->gba) { + gameStopped(); + return; + } + m_ui.savetype->setCurrentIndex(thread->gba->memory.savedata.type + 1); + m_ui.savetype->setEnabled(false); + + m_ui.hwAutodetect->setEnabled(false); + m_ui.hwRTC->setEnabled(false); + m_ui.hwGyro->setEnabled(false); + m_ui.hwLight->setEnabled(false); + m_ui.hwTilt->setEnabled(false); + m_ui.hwRumble->setEnabled(false); + + m_ui.hwRTC->setChecked(thread->gba->memory.hw.devices & HW_RTC); + m_ui.hwGyro->setChecked(thread->gba->memory.hw.devices & HW_GYRO); + m_ui.hwLight->setChecked(thread->gba->memory.hw.devices & HW_LIGHT_SENSOR); + m_ui.hwTilt->setChecked(thread->gba->memory.hw.devices & HW_TILT); + m_ui.hwRumble->setChecked(thread->gba->memory.hw.devices & HW_RUMBLE); + + if (thread->gba->idleLoop != IDLE_LOOP_NONE) { + m_ui.idleLoop->setText(QString::number(thread->gba->idleLoop, 16)); + } else { + m_ui.idleLoop->clear(); + + } + + GBAGetGameCode(thread->gba, m_override.id); + m_override.hardware = thread->gba->memory.hw.devices; + m_override.savetype = thread->gba->memory.savedata.type; + m_override.idleLoop = thread->gba->idleLoop; + + m_ui.idleLoop->setEnabled(false); + + m_ui.save->setEnabled(m_config); +} + +void OverrideView::gameStopped() { + m_ui.savetype->setCurrentIndex(0); + m_ui.savetype->setEnabled(true); + + m_ui.hwAutodetect->setEnabled(true); + m_ui.hwRTC->setEnabled(!m_ui.hwAutodetect->isChecked()); + m_ui.hwGyro->setEnabled(!m_ui.hwAutodetect->isChecked()); + m_ui.hwLight->setEnabled(!m_ui.hwAutodetect->isChecked()); + m_ui.hwTilt->setEnabled(!m_ui.hwAutodetect->isChecked()); + m_ui.hwRumble->setEnabled(!m_ui.hwAutodetect->isChecked()); + + m_ui.hwRTC->setChecked(false); + m_ui.hwGyro->setChecked(false); + m_ui.hwLight->setChecked(false); + m_ui.hwTilt->setChecked(false); + m_ui.hwRumble->setChecked(false); + + m_ui.idleLoop->setEnabled(true); + + m_ui.clear->setEnabled(false); + m_ui.save->setEnabled(false); +}
@@ -0,0 +1,48 @@
+/* Copyright (c) 2013-2015 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 QGBA_OVERRIDE_VIEW +#define QGBA_OVERRIDE_VIEW + +#include <QWidget> + +#include "ui_OverrideView.h" + +extern "C" { +#include "gba/supervisor/overrides.h" +} + +struct GBAThread; + +namespace QGBA { + +class ConfigController; +class GameController; + +class OverrideView : public QWidget { +Q_OBJECT + +public: + OverrideView(GameController* controller, ConfigController* config, QWidget* parent = nullptr); + +public slots: + void saveOverride(); + +private slots: + void updateOverrides(); + void gameStarted(GBAThread*); + void gameStopped(); + +private: + Ui::OverrideView m_ui; + + GameController* m_controller; + ConfigController* m_config; + GBACartridgeOverride m_override; +}; + +} + +#endif
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>OverrideView</class> + <widget class="QWidget" name="OverrideView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>409</width> + <height>228</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Game Overrides</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="clear"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="save"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0" rowspan="3"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string/> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="hwAutodetect"> + <property name="text"> + <string>Autodetect</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="hwRTC"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Realtime clock</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="hwGyro"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Gyroscope</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="hwTilt"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Tilt</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="hwLight"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Light sensor</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="hwRumble"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Rumble</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string/> + </property> + <layout class="QFormLayout" name="formLayout_5"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::AllNonFixedFieldsGrow</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Save type</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="savetype"> + <item> + <property name="text"> + <string>Autodetect</string> + </property> + </item> + <item> + <property name="text"> + <string>None</string> + </property> + </item> + <item> + <property name="text"> + <string>SRAM</string> + </property> + </item> + <item> + <property name="text"> + <string>Flash 512kb</string> + </property> + </item> + <item> + <property name="text"> + <string>Flash 1Mb</string> + </property> + </item> + <item> + <property name="text"> + <string>EEPROM</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Idle loop</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="idleLoop"/> + </item> + <item row="1" column="1"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
@@ -0,0 +1,49 @@
+/* Copyright (c) 2013-2015 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 "SensorView.h" + +#include "GameController.h" + +using namespace QGBA; + +SensorView::SensorView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) + { + m_ui.setupUi(this); + + connect(m_ui.lightSpin, SIGNAL(valueChanged(int)), this, SLOT(setLuminanceValue(int))); + connect(m_ui.lightSlide, SIGNAL(valueChanged(int)), this, SLOT(setLuminanceValue(int))); + + connect(m_ui.timeNoOverride, SIGNAL(clicked()), controller, SLOT(setRealTime())); + connect(m_ui.timeFixed, &QRadioButton::clicked, [controller, this] () { + controller->setFixedTime(m_ui.time->dateTime()); + }); + connect(m_ui.timeFakeEpoch, &QRadioButton::clicked, [controller, this] () { + controller->setFakeEpoch(m_ui.time->dateTime()); + }); + connect(m_ui.time, &QDateTimeEdit::dateTimeChanged, [controller, this] (const QDateTime&) { + m_ui.timeButtons->checkedButton()->clicked(); + }); + connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () { + m_ui.time->setDateTime(QDateTime::currentDateTime()); + }); + } + +void SensorView::setLuminanceValue(int value) { + bool oldState; + value = std::max(0, std::min(value, 255)); + + oldState = m_ui.lightSpin->blockSignals(true); + m_ui.lightSpin->setValue(value); + m_ui.lightSpin->blockSignals(oldState); + + oldState = m_ui.lightSlide->blockSignals(true); + m_ui.lightSlide->setValue(value); + m_ui.lightSlide->blockSignals(oldState); + + m_controller->setLuminanceValue(value); +}
@@ -0,0 +1,35 @@
+/* Copyright (c) 2013-2015 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 QGBA_SENSOR_VIEW +#define QGBA_SENSOR_VIEW + +#include <QWidget> + +#include "ui_SensorView.h" + +namespace QGBA { + +class ConfigController; +class GameController; + +class SensorView : public QWidget { +Q_OBJECT + +public: + SensorView(GameController* controller, QWidget* parent = nullptr); + +private slots: + void setLuminanceValue(int); + +private: + Ui::SensorView m_ui; + + GameController* m_controller; +}; + +} + +#endif
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SensorView</class> + <widget class="QWidget" name="SensorView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>411</width> + <height>170</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Sensors</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Realtime clock</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="2" column="0"> + <widget class="QRadioButton" name="timeFakeEpoch"> + <property name="text"> + <string>Start time at</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">timeButtons</string> + </attribute> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="timeFixed"> + <property name="text"> + <string>Fixed time</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">timeButtons</string> + </attribute> + </widget> + </item> + <item row="0" column="0"> + <widget class="QRadioButton" name="timeNoOverride"> + <property name="text"> + <string>System time</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">timeButtons</string> + </attribute> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QDateTimeEdit" name="time"> + <property name="wrapping"> + <bool>true</bool> + </property> + <property name="maximumDate"> + <date> + <year>2100</year> + <month>1</month> + <day>2</day> + </date> + </property> + <property name="minimumDate"> + <date> + <year>2000</year> + <month>1</month> + <day>1</day> + </date> + </property> + <property name="currentSection"> + <enum>QDateTimeEdit::MonthSection</enum> + </property> + <property name="displayFormat"> + <string>MM/dd/yy hh:mm:ss AP</string> + </property> + <property name="timeSpec"> + <enum>Qt::UTC</enum> + </property> + </widget> + </item> + <item row="0" column="1" rowspan="3"> + <widget class="QPushButton" name="timeNow"> + <property name="text"> + <string>Now</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Light sensor</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Brightness</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="lightSpin"> + <property name="maximum"> + <number>255</number> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QSlider" name="lightSlide"> + <property name="maximum"> + <number>255</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksBelow</enum> + </property> + <property name="tickInterval"> + <number>16</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> + <buttongroups> + <buttongroup name="timeButtons"/> + </buttongroups> +</ui>
@@ -0,0 +1,150 @@
+/* Copyright (c) 2013-2014 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 "SettingsView.h" + +#include "AudioProcessor.h" +#include "ConfigController.h" + +#include <QFileDialog> + +using namespace QGBA; + +SettingsView::SettingsView(ConfigController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + loadSetting("bios", m_ui.bios); + loadSetting("skipBios", m_ui.skipBios); + loadSetting("audioBuffers", m_ui.audioBufferSize); + loadSetting("videoSync", m_ui.videoSync); + loadSetting("audioSync", m_ui.audioSync); + loadSetting("frameskip", m_ui.frameskip); + loadSetting("fpsTarget", m_ui.fpsTarget); + loadSetting("lockAspectRatio", m_ui.lockAspectRatio); + loadSetting("rewindEnable", m_ui.rewind); + loadSetting("rewindBufferInterval", m_ui.rewindInterval); + loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); + loadSetting("resampleVideo", m_ui.resampleVideo); + + QString idleOptimization = loadSetting("idleOptimization"); + if (idleOptimization == "ignore") { + m_ui.idleOptimization->setCurrentIndex(0); + } else if (idleOptimization == "remove") { + m_ui.idleOptimization->setCurrentIndex(1); + } else if (idleOptimization == "detect") { + m_ui.idleOptimization->setCurrentIndex(2); + } + + int audioDriver = m_controller->getQtOption("audioDriver").toInt(); +#ifdef BUILD_QT_MULTIMEDIA + m_ui.audioDriver->addItem(tr("Qt Multimedia"), static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)); + if (audioDriver == static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)) { + m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1); + } +#endif + +#ifdef BUILD_SDL + m_ui.audioDriver->addItem(tr("SDL"), static_cast<int>(AudioProcessor::Driver::SDL)); + if (audioDriver == static_cast<int>(AudioProcessor::Driver::SDL)) { + m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1); + } +#endif + + connect(m_ui.biosBrowse, SIGNAL(clicked()), this, SLOT(selectBios())); + connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); +} + +void SettingsView::selectBios() { + QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS")); + if (!filename.isEmpty()) { + m_ui.bios->setText(filename); + } +} + +void SettingsView::updateConfig() { + saveSetting("bios", m_ui.bios); + saveSetting("skipBios", m_ui.skipBios); + saveSetting("audioBuffers", m_ui.audioBufferSize); + saveSetting("videoSync", m_ui.videoSync); + saveSetting("audioSync", m_ui.audioSync); + saveSetting("frameskip", m_ui.frameskip); + saveSetting("fpsTarget", m_ui.fpsTarget); + saveSetting("lockAspectRatio", m_ui.lockAspectRatio); + saveSetting("rewindEnable", m_ui.rewind); + saveSetting("rewindBufferInterval", m_ui.rewindInterval); + saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); + saveSetting("resampleVideo", m_ui.resampleVideo); + + switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) { + case IDLE_LOOP_IGNORE: + saveSetting("idleOptimization", "ignore"); + break; + case IDLE_LOOP_REMOVE: + saveSetting("idleOptimization", "remove"); + break; + case IDLE_LOOP_DETECT: + saveSetting("idleOptimization", "detect"); + break; + } + + QVariant audioDriver = m_ui.audioDriver->itemData(m_ui.audioDriver->currentIndex()); + if (audioDriver != m_controller->getQtOption("audioDriver")) { + m_controller->setQtOption("audioDriver", audioDriver); + AudioProcessor::setDriver(static_cast<AudioProcessor::Driver>(audioDriver.toInt())); + emit audioDriverChanged(); + } + + m_controller->write(); + + emit biosLoaded(m_ui.bios->text()); +} + +void SettingsView::saveSetting(const char* key, const QAbstractButton* field) { + m_controller->setOption(key, field->isChecked()); + m_controller->updateOption(key); +} + +void SettingsView::saveSetting(const char* key, const QComboBox* field) { + saveSetting(key, field->lineEdit()); +} + +void SettingsView::saveSetting(const char* key, const QLineEdit* field) { + saveSetting(key, field->text()); +} + +void SettingsView::saveSetting(const char* key, const QSpinBox* field) { + saveSetting(key, field->cleanText()); +} + +void SettingsView::saveSetting(const char* key, const QString& field) { + m_controller->setOption(key, field); + m_controller->updateOption(key); +} + +void SettingsView::loadSetting(const char* key, QAbstractButton* field) { + QString option = loadSetting(key); + field->setChecked(option != "0"); +} + +void SettingsView::loadSetting(const char* key, QComboBox* field) { + loadSetting(key, field->lineEdit()); +} + +void SettingsView::loadSetting(const char* key, QLineEdit* field) { + QString option = loadSetting(key); + field->setText(option); +} + +void SettingsView::loadSetting(const char* key, QSpinBox* field) { + QString option = loadSetting(key); + field->setValue(option.toInt()); +} + +QString SettingsView::loadSetting(const char* key) { + return m_controller->getOption(key); +}
@@ -0,0 +1,51 @@
+/* Copyright (c) 2013-2014 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 QGBA_SETTINGS_VIEW +#define QGBA_SETTINGS_VIEW + +#include <QWidget> + +#include "ui_SettingsView.h" + +namespace QGBA { + +class ConfigController; + +class SettingsView : public QWidget { +Q_OBJECT + +public: + SettingsView(ConfigController* controller, QWidget* parent = nullptr); + +signals: + void biosLoaded(const QString&); + void audioDriverChanged(); + +private slots: + void selectBios(); + void updateConfig(); + +private: + Ui::SettingsView m_ui; + + ConfigController* m_controller; + + void saveSetting(const char* key, const QAbstractButton*); + void saveSetting(const char* key, const QComboBox*); + void saveSetting(const char* key, const QLineEdit*); + void saveSetting(const char* key, const QSpinBox*); + void saveSetting(const char* key, const QString&); + + void loadSetting(const char* key, QAbstractButton*); + void loadSetting(const char* key, QComboBox*); + void loadSetting(const char* key, QLineEdit*); + void loadSetting(const char* key, QSpinBox*); + QString loadSetting(const char* key); +}; + +} + +#endif
@@ -0,0 +1,395 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SettingsView</class> + <widget class="QWidget" name="SettingsView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>355</width> + <height>501</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Settings</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>BIOS file:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="bios"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="biosBrowse"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="skipBios"> + <property name="text"> + <string>Skip BIOS intro</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="useBios"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Use BIOS file</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Audio driver:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QComboBox" name="audioDriver"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="audioBufferSizeLabel"> + <property name="text"> + <string>Audio buffer:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QComboBox" name="audioBufferSize"> + <property name="editable"> + <bool>true</bool> + </property> + <property name="currentText"> + <string>2048</string> + </property> + <property name="currentIndex"> + <number>2</number> + </property> + <item> + <property name="text"> + <string>512</string> + </property> + </item> + <item> + <property name="text"> + <string>1024</string> + </property> + </item> + <item> + <property name="text"> + <string>2048</string> + </property> + </item> + <item> + <property name="text"> + <string>4096</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>samples</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Sync:</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_10"> + <item> + <widget class="QCheckBox" name="videoSync"> + <property name="text"> + <string>Video</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="audioSync"> + <property name="text"> + <string>Audio</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Frameskip:</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_16"> + <item> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Skip every</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="frameskip"/> + </item> + <item> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>frames</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="9" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="10" column="1"> + <widget class="QCheckBox" name="lockAspectRatio"> + <property name="text"> + <string>Lock aspect ratio</string> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QCheckBox" name="resampleVideo"> + <property name="text"> + <string>Resample video</string> + </property> + </widget> + </item> + <item row="12" column="0" colspan="2"> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QCheckBox" name="rewind"> + <property name="text"> + <string>Enable rewind</string> + </property> + </widget> + </item> + <item row="14" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Rewind interval:</string> + </property> + </widget> + </item> + <item row="14" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Every</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="rewindInterval"/> + </item> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>frames</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="15" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Rewind length:</string> + </property> + </widget> + </item> + <item row="15" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <item> + <widget class="QSpinBox" name="rewindCapacity"/> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>intervals</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>FPS target:</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QSpinBox" name="fpsTarget"> + <property name="maximum"> + <number>240</number> + </property> + <property name="value"> + <number>60</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>frames per second</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="17" column="1"> + <widget class="QComboBox" name="idleOptimization"> + <item> + <property name="text"> + <string>Run all</string> + </property> + </item> + <item> + <property name="text"> + <string>Remove known</string> + </property> + </item> + <item> + <property name="text"> + <string>Detect and remove</string> + </property> + </item> + </widget> + </item> + <item row="16" column="0" colspan="2"> + <widget class="Line" name="line_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="17" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Idle loops</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>SettingsView</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>169</x> + <y>453</y> + </hint> + <hint type="destinationlabel"> + <x>169</x> + <y>236</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>SettingsView</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>169</x> + <y>453</y> + </hint> + <hint type="destinationlabel"> + <x>169</x> + <y>236</y> + </hint> + </hints> + </connection> + </connections> +</ui>
@@ -0,0 +1,391 @@
+/* Copyright (c) 2013-2015 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 "ShortcutController.h" + +#include "ConfigController.h" +#include "GamepadButtonEvent.h" + +#include <QAction> +#include <QKeyEvent> +#include <QMenu> + +using namespace QGBA; + +ShortcutController::ShortcutController(QObject* parent) + : QAbstractItemModel(parent) + , m_rootMenu(nullptr) + , m_config(nullptr) +{ +} + +void ShortcutController::setConfigController(ConfigController* controller) { + m_config = controller; +} + +QVariant ShortcutController::data(const QModelIndex& index, int role) const { + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } + int row = index.row(); + const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer()); + switch (index.column()) { + case 0: + return item->visibleName(); + case 1: + return item->shortcut().toString(QKeySequence::NativeText); + case 2: + if (item->button() >= 0) { + return item->button(); + } + break; + } + return QVariant(); +} + +QVariant ShortcutController::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole) { + return QAbstractItemModel::headerData(section, orientation, role); + } + if (orientation == Qt::Horizontal) { + switch (section) { + case 0: + return tr("Action"); + case 1: + return tr("Keyboard"); + case 2: + return tr("Gamepad"); + } + } + return section; +} + +QModelIndex ShortcutController::index(int row, int column, const QModelIndex& parent) const { + const ShortcutItem* pmenu = &m_rootMenu; + if (parent.isValid()) { + pmenu = static_cast<ShortcutItem*>(parent.internalPointer()); + } + return createIndex(row, column, const_cast<ShortcutItem*>(&pmenu->items()[row])); +} + +QModelIndex ShortcutController::parent(const QModelIndex& index) const { + if (!index.isValid() || !index.internalPointer()) { + return QModelIndex(); + } + ShortcutItem* item = static_cast<ShortcutItem*>(index.internalPointer()); + if (!item->parent() || !item->parent()->parent()) { + return QModelIndex(); + } + return createIndex(item->parent()->parent()->items().indexOf(*item->parent()), 0, item->parent()); +} + +int ShortcutController::columnCount(const QModelIndex& index) const { + return 3; +} + +int ShortcutController::rowCount(const QModelIndex& index) const { + if (!index.isValid()) { + return m_rootMenu.items().count(); + } + const ShortcutItem* item = static_cast<const ShortcutItem*>(index.internalPointer()); + return item->items().count(); +} + +void ShortcutController::addAction(QMenu* menu, QAction* action, const QString& name) { + ShortcutItem* smenu = m_menuMap[menu]; + if (!smenu) { + return; + } + ShortcutItem* pmenu = smenu->parent(); + int row = pmenu->items().indexOf(*smenu); + QModelIndex parent = createIndex(row, 0, smenu); + beginInsertRows(parent, smenu->items().count(), smenu->items().count()); + smenu->addAction(action, name); + endInsertRows(); + ShortcutItem* item = &smenu->items().last(); + if (m_config) { + loadShortcuts(item); + } + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); +} + +void ShortcutController::addFunctions(QMenu* menu, std::function<void ()> press, std::function<void ()> release, const QKeySequence& shortcut, const QString& visibleName, const QString& name) { + ShortcutItem* smenu = m_menuMap[menu]; + if (!smenu) { + return; + } + ShortcutItem* pmenu = smenu->parent(); + int row = pmenu->items().indexOf(*smenu); + QModelIndex parent = createIndex(row, 0, smenu); + beginInsertRows(parent, smenu->items().count(), smenu->items().count()); + smenu->addFunctions(qMakePair(press, release), shortcut, visibleName, name); + endInsertRows(); + ShortcutItem* item = &smenu->items().last(); + if (m_config) { + loadShortcuts(item); + } + m_heldKeys[shortcut] = item; + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); +} + +void ShortcutController::addMenu(QMenu* menu, QMenu* parentMenu) { + ShortcutItem* smenu = m_menuMap[parentMenu]; + if (!smenu) { + smenu = &m_rootMenu; + } + QModelIndex parent; + ShortcutItem* pmenu = smenu->parent(); + if (pmenu) { + int row = pmenu->items().indexOf(*smenu); + parent = createIndex(row, 0, smenu); + } + beginInsertRows(parent, smenu->items().count(), smenu->items().count()); + smenu->addSubmenu(menu); + endInsertRows(); + ShortcutItem* item = &smenu->items().last(); + emit dataChanged(createIndex(smenu->items().count() - 1, 0, item), createIndex(smenu->items().count() - 1, 2, item)); + m_menuMap[menu] = item; +} + +ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) { + if (!index.isValid()) { + return nullptr; + } + return static_cast<ShortcutItem*>(index.internalPointer()); +} + +const ShortcutController::ShortcutItem* ShortcutController::itemAt(const QModelIndex& index) const { + if (!index.isValid()) { + return nullptr; + } + return static_cast<const ShortcutItem*>(index.internalPointer()); +} + +QKeySequence ShortcutController::shortcutAt(const QModelIndex& index) const { + const ShortcutItem* item = itemAt(index); + if (!item) { + return QKeySequence(); + } + return item->shortcut(); +} + +bool ShortcutController::isMenuAt(const QModelIndex& index) const { + const ShortcutItem* item = itemAt(index); + if (!item) { + return false; + } + return item->menu(); +} + +void ShortcutController::updateKey(const QModelIndex& index, const QKeySequence& keySequence) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + ShortcutItem* item = itemAt(index); + if (item->functions().first) { + QKeySequence oldShortcut = item->shortcut(); + if (!oldShortcut.isEmpty()) { + m_heldKeys.take(oldShortcut); + } + if (!keySequence.isEmpty()) { + m_heldKeys[keySequence] = item; + } + } + item->setShortcut(keySequence); + if (m_config) { + m_config->setQtOption(item->name(), keySequence.toString(), KEY_SECTION); + } + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); +} + +void ShortcutController::updateButton(const QModelIndex& index, int button) { + if (!index.isValid()) { + return; + } + const QModelIndex& parent = index.parent(); + if (!parent.isValid()) { + return; + } + ShortcutItem* item = itemAt(index); + int oldButton = item->button(); + item->setButton(button); + if (oldButton >= 0) { + m_buttons.take(oldButton); + } + if (button >= 0) { + m_buttons[button] = item; + } + if (m_config) { + m_config->setQtOption(item->name(), button, BUTTON_SECTION); + } + emit dataChanged(createIndex(index.row(), 0, index.internalPointer()), createIndex(index.row(), 2, index.internalPointer())); +} + +void ShortcutController::clearKey(const QModelIndex& index) { + updateKey(index, QKeySequence()); +} + +void ShortcutController::clearButton(const QModelIndex& index) { + updateButton(index, -1); +} + +bool ShortcutController::eventFilter(QObject*, QEvent* event) { + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->isAutoRepeat()) { + return false; + } + auto item = m_heldKeys.find(keyEventToSequence(keyEvent)); + if (item == m_heldKeys.end()) { + return false; + } + ShortcutItem::Functions pair = item.value()->functions(); + if (event->type() == QEvent::KeyPress) { + if (pair.first) { + pair.first(); + } + } else { + if (pair.second) { + pair.second(); + } + } + event->accept(); + return true; + } + if (event->type() == GamepadButtonEvent::Down()) { + auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value()); + if (item == m_buttons.end()) { + return false; + } + QAction* action = item.value()->action(); + if (action && action->isEnabled()) { + action->trigger(); + } + ShortcutItem::Functions pair = item.value()->functions(); + if (pair.first) { + pair.first(); + } + event->accept(); + return true; + } + if (event->type() == GamepadButtonEvent::Up()) { + auto item = m_buttons.find(static_cast<GamepadButtonEvent*>(event)->value()); + if (item == m_buttons.end()) { + return false; + } + ShortcutItem::Functions pair = item.value()->functions(); + if (pair.second) { + pair.second(); + } + event->accept(); + return true; + } + return false; +} + +void ShortcutController::loadShortcuts(ShortcutItem* item) { + QVariant shortcut = m_config->getQtOption(item->name(), KEY_SECTION); + if (!shortcut.isNull()) { + QKeySequence keySequence(shortcut.toString()); + if (item->functions().first) { + QKeySequence oldShortcut = item->shortcut(); + if (!oldShortcut.isEmpty()) { + m_heldKeys.take(oldShortcut); + } + m_heldKeys[keySequence] = item; + } + item->setShortcut(keySequence); + } + QVariant button = m_config->getQtOption(item->name(), BUTTON_SECTION); + if (!button.isNull()) { + int oldButton = item->button(); + item->setButton(button.toInt()); + if (oldButton >= 0) { + m_buttons.take(oldButton); + } + m_buttons[button.toInt()] = item; + } +} + +QKeySequence ShortcutController::keyEventToSequence(const QKeyEvent* event) { + QString modifier = QString::null; + + if (event->modifiers() & Qt::ShiftModifier) { + modifier += "Shift+"; + } + if (event->modifiers() & Qt::ControlModifier) { + modifier += "Ctrl+"; + } + if (event->modifiers() & Qt::AltModifier) { + modifier += "Alt+"; + } + if (event->modifiers() & Qt::MetaModifier) { + modifier += "Meta+"; + } + + QString key = QKeySequence(event->key()).toString(); + return QKeySequence(modifier + key); +} + +ShortcutController::ShortcutItem::ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent) + : m_action(action) + , m_shortcut(action->shortcut()) + , m_menu(nullptr) + , m_name(name) + , m_button(-1) + , m_parent(parent) +{ + m_visibleName = action->text() + .remove(QRegExp("&(?!&)")) + .remove("..."); +} + +ShortcutController::ShortcutItem::ShortcutItem(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent) + : m_action(nullptr) + , m_shortcut(shortcut) + , m_functions(functions) + , m_menu(nullptr) + , m_name(name) + , m_visibleName(visibleName) + , m_button(-1) + , m_parent(parent) +{ +} + +ShortcutController::ShortcutItem::ShortcutItem(QMenu* menu, ShortcutItem* parent) + : m_action(nullptr) + , m_menu(menu) + , m_button(-1) + , m_parent(parent) +{ + if (menu) { + m_visibleName = menu->title() + .remove(QRegExp("&(?!&)")) + .remove("..."); + } +} + +void ShortcutController::ShortcutItem::addAction(QAction* action, const QString& name) { + m_items.append(ShortcutItem(action, name, this)); +} + +void ShortcutController::ShortcutItem::addFunctions(ShortcutController::ShortcutItem::Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name) { + m_items.append(ShortcutItem(functions, shortcut, visibleName, name, this)); +} + +void ShortcutController::ShortcutItem::addSubmenu(QMenu* menu) { + m_items.append(ShortcutItem(menu, this)); +} + +void ShortcutController::ShortcutItem::setShortcut(const QKeySequence& shortcut) { + m_shortcut = shortcut; + if (m_action) { + m_action->setShortcut(shortcut); + } +}
@@ -0,0 +1,117 @@
+/* Copyright (c) 2013-2015 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 QGBA_SHORTCUT_MODEL +#define QGBA_SHORTCUT_MODEL + +#include <QAbstractItemModel> +#include <QKeySequence> + +#include <functional> + +class QAction; +class QKeyEvent; +class QMenu; +class QString; + +namespace QGBA { + +class ConfigController; + +class ShortcutController : public QAbstractItemModel { +Q_OBJECT + +private: + constexpr static const char* const KEY_SECTION = "shortcutKey"; + constexpr static const char* const BUTTON_SECTION = "shortcutButton"; + + class ShortcutItem { + public: + typedef QPair<std::function<void ()>, std::function<void ()>> Functions; + + ShortcutItem(QAction* action, const QString& name, ShortcutItem* parent = nullptr); + ShortcutItem(Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name, ShortcutItem* parent = nullptr); + ShortcutItem(QMenu* action, ShortcutItem* parent = nullptr); + + QAction* action() { return m_action; } + const QAction* action() const { return m_action; } + const QKeySequence& shortcut() const { return m_shortcut; } + Functions functions() const { return m_functions; } + QMenu* menu() { return m_menu; } + const QMenu* menu() const { return m_menu; } + const QString& visibleName() const { return m_visibleName; } + const QString& name() const { return m_name; } + QList<ShortcutItem>& items() { return m_items; } + const QList<ShortcutItem>& items() const { return m_items; } + ShortcutItem* parent() { return m_parent; } + const ShortcutItem* parent() const { return m_parent; } + void addAction(QAction* action, const QString& name); + void addFunctions(Functions functions, const QKeySequence& shortcut, const QString& visibleName, const QString& name); + void addSubmenu(QMenu* menu); + int button() const { return m_button; } + void setShortcut(const QKeySequence& sequence); + void setButton(int button) { m_button = button; } + + bool operator==(const ShortcutItem& other) const { return m_menu == other.m_menu && m_action == other.m_action; } + + private: + QAction* m_action; + QKeySequence m_shortcut; + QMenu* m_menu; + Functions m_functions; + QString m_name; + QString m_visibleName; + int m_button; + QList<ShortcutItem> m_items; + ShortcutItem* m_parent; + }; + +public: + ShortcutController(QObject* parent = nullptr); + + void setConfigController(ConfigController* controller); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + void addAction(QMenu* menu, QAction* action, const QString& name); + void addFunctions(QMenu* menu, std::function<void ()> press, std::function<void()> release, const QKeySequence& shortcut, const QString& visibleName, const QString& name); + void addMenu(QMenu* menu, QMenu* parent = nullptr); + + QKeySequence shortcutAt(const QModelIndex& index) const; + bool isMenuAt(const QModelIndex& index) const; + + void updateKey(const QModelIndex& index, const QKeySequence& keySequence); + void updateButton(const QModelIndex& index, int button); + + void clearKey(const QModelIndex& index); + void clearButton(const QModelIndex& index); + + static QKeySequence keyEventToSequence(const QKeyEvent*); + +protected: + bool eventFilter(QObject*, QEvent*) override; + +private: + ShortcutItem* itemAt(const QModelIndex& index); + const ShortcutItem* itemAt(const QModelIndex& index) const; + void loadShortcuts(ShortcutItem*); + + ShortcutItem m_rootMenu; + QMap<QMenu*, ShortcutItem*> m_menuMap; + QMap<int, ShortcutItem*> m_buttons; + QMap<QKeySequence, ShortcutItem*> m_heldKeys; + ConfigController* m_config; +}; + +} + +#endif
@@ -0,0 +1,110 @@
+/* Copyright (c) 2013-2015 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 "ShortcutView.h" + +#include "GamepadButtonEvent.h" +#include "ShortcutController.h" + +#include <QKeyEvent> + +using namespace QGBA; + +ShortcutView::ShortcutView(QWidget* parent) + : QWidget(parent) + , m_controller(nullptr) +{ + m_ui.setupUi(this); + m_ui.keyEdit->setValueButton(-1); + m_ui.keySequenceEdit->installEventFilter(this); + + connect(m_ui.keySequenceEdit, SIGNAL(keySequenceChanged(const QKeySequence&)), this, SLOT(updateKey(const QKeySequence&))); + connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int))); + connect(m_ui.shortcutTable, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(load(const QModelIndex&))); + connect(m_ui.clearButton, SIGNAL(clicked()), this, SLOT(clear())); +} + +void ShortcutView::setController(ShortcutController* controller) { + m_controller = controller; + m_ui.shortcutTable->setModel(controller); +} + +bool ShortcutView::event(QEvent* event) { + if (event->type() == GamepadButtonEvent::Down()) { + updateButton(static_cast<GamepadButtonEvent*>(event)->value()); + event->accept(); + return true; + } + return QWidget::event(event); +} + +bool ShortcutView::eventFilter(QObject*, QEvent* event) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() != Qt::Key_Tab && keyEvent->key() != Qt::Key_Backtab) { + return false; + } + if (!(keyEvent->modifiers() & ~Qt::ShiftModifier)) { + m_ui.keySequenceEdit->setKeySequence(ShortcutController::keyEventToSequence(keyEvent)); + keyEvent->accept(); + return true; + } + } + return false; +} + +void ShortcutView::load(const QModelIndex& index) { + if (!m_controller) { + return; + } + if (m_controller->isMenuAt(index)) { + return; + } + QKeySequence sequence = m_controller->shortcutAt(index); + if (index.column() == 1) { + m_ui.keyboardButton->click(); + } else if (index.column() == 2) { + m_ui.gamepadButton->click(); + } + if (m_ui.gamepadButton->isChecked()) { + m_ui.keyEdit->setFocus(); + m_ui.keyEdit->setValueButton(-1); // There are no default bindings + } else { + m_ui.keySequenceEdit->setFocus(); + m_ui.keySequenceEdit->setKeySequence(sequence); + } +} + +void ShortcutView::clear() { + if (!m_controller) { + return; + } + QModelIndex index = m_ui.shortcutTable->selectionModel()->currentIndex(); + if (m_controller->isMenuAt(index)) { + return; + } + if (m_ui.gamepadButton->isChecked()) { + m_controller->clearButton(index); + m_ui.keyEdit->setValueButton(-1); + } else { + m_controller->clearKey(index); + m_ui.keySequenceEdit->setKeySequence(QKeySequence()); + } +} + +void ShortcutView::updateKey(const QKeySequence& shortcut) { + if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + return; + } + m_controller->updateKey(m_ui.shortcutTable->selectionModel()->currentIndex(), shortcut); +} + +void ShortcutView::updateButton(int button) { + if (!m_controller || m_controller->isMenuAt(m_ui.shortcutTable->selectionModel()->currentIndex())) { + return; + } + m_controller->updateButton(m_ui.shortcutTable->selectionModel()->currentIndex(), button); + +}
@@ -0,0 +1,43 @@
+/* Copyright (c) 2013-2015 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 QGBA_SHORTCUT_VIEW +#define QGBA_SHORTCUT_VIEW + +#include <QWidget> + +#include "ui_ShortcutView.h" + +namespace QGBA { + +class ShortcutController; + +class ShortcutView : public QWidget { +Q_OBJECT + +public: + ShortcutView(QWidget* parent = nullptr); + + void setController(ShortcutController* controller); + +protected: + virtual bool event(QEvent* event) override; + virtual bool eventFilter(QObject* obj, QEvent* event) override; + +private slots: + void load(const QModelIndex&); + void clear(); + void updateKey(const QKeySequence&); + void updateButton(int button); + +private: + Ui::ShortcutView m_ui; + + ShortcutController* m_controller; +}; + +} + +#endif
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ShortcutView</class> + <widget class="QWidget" name="ShortcutView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>425</width> + <height>443</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Shortcuts</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeView" name="shortcutTable"> + <attribute name="headerDefaultSectionSize"> + <number>120</number> + </attribute> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="keyboardButton"> + <property name="text"> + <string>Keyboard</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="gamepadButton"> + <property name="text"> + <string>Gamepad</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="clearButton"> + <property name="text"> + <string>Clear</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QKeySequenceEdit" name="keySequenceEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QGBA::KeyEditor" name="keyEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="visible"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QGBA::KeyEditor</class> + <extends>QLineEdit</extends> + <header>KeyEditor.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>keyboardButton</sender> + <signal>toggled(bool)</signal> + <receiver>keySequenceEdit</receiver> + <slot>setVisible(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>86</x> + <y>374</y> + </hint> + <hint type="destinationlabel"> + <x>66</x> + <y>20</y> + </hint> + </hints> + </connection> + <connection> + <sender>gamepadButton</sender> + <signal>toggled(bool)</signal> + <receiver>keyEdit</receiver> + <slot>setVisible(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>213</x> + <y>374</y> + </hint> + <hint type="destinationlabel"> + <x>206</x> + <y>340</y> + </hint> + </hints> + </connection> + </connections> +</ui>
@@ -27,9 +27,5 @@ return m_vf->write(m_vf, data, maxSize);
} qint64 VFileDevice::size() const { - // TODO: Add size method to VFile so this can be actually const - ssize_t pos = m_vf->seek(m_vf, 0, SEEK_CUR); - qint64 size = m_vf->seek(m_vf, 0, SEEK_END); - m_vf->seek(m_vf, pos, SEEK_SET); - return size; + return m_vf->size(m_vf); }
@@ -172,13 +172,15 @@ .width = 240,
.height = 160, }); - setAudioCodec(m_ui.audio->currentText()); - setVideoCodec(m_ui.video->currentText()); - setAudioBitrate(m_ui.abr->value()); - setVideoBitrate(m_ui.vbr->value()); - setContainer(m_ui.container->currentText()); - setWidth(m_ui.width->value()); - setHeight(m_ui.height->value()); + setPreset((Preset) { + .container = "MKV", + .vcodec = "PNG", + .acodec = "FLAC", + .vbr = 0, + .abr = 0, + .width = 240, + .height = 160, + }); showAdvanced(false); }
@@ -6,11 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Window.h" #include <QFileDialog> +#include <QFileInfo> #include <QKeyEvent> #include <QKeySequence> #include <QMenuBar> +#include <QMessageBox> +#include <QMimeData> #include <QStackedLayout> +#include "CheatsView.h" #include "ConfigController.h" #include "GameController.h" #include "GBAKeyEditor.h"@@ -19,6 +23,11 @@ #include "GDBWindow.h"
#include "GIFView.h" #include "LoadSaveState.h" #include "LogView.h" +#include "OverrideView.h" +#include "SensorView.h" +#include "SettingsView.h" +#include "ShortcutController.h" +#include "ShortcutView.h" #include "VideoView.h" extern "C" {@@ -43,18 +52,28 @@ #endif
#ifdef USE_GDB_STUB , m_gdbController(nullptr) #endif + , m_mruMenu(nullptr) + , m_shortcutController(new ShortcutController(this)) { setWindowTitle(PROJECT_NAME); + setFocusPolicy(Qt::StrongFocus); + setAcceptDrops(true); m_controller = new GameController(this); m_controller->setInputController(&m_inputController); + m_controller->setOverrides(m_config->overrides()); QGLFormat format(QGLFormat(QGL::Rgba | QGL::DoubleBuffer)); format.setSwapInterval(1); m_display = new Display(format); + m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); + m_logo = m_logo; // Free memory left over in old pixmap + m_screenWidget->setMinimumSize(m_display->minimumSize()); m_screenWidget->setSizePolicy(m_display->sizePolicy()); m_screenWidget->setSizeHint(m_display->minimumSize() * 2); + m_screenWidget->setPixmap(m_logo); + m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); setCentralWidget(m_screenWidget); connect(m_controller, SIGNAL(gameStarted(GBAThread*)), this, SLOT(gameStarted(GBAThread*)));@@ -73,6 +92,8 @@ #endif
connect(m_controller, SIGNAL(gameUnpaused(GBAThread*)), m_display, SLOT(unpauseDrawing())); connect(m_controller, SIGNAL(postLog(int, const QString&)), m_logView, SLOT(postLog(int, const QString&))); connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(recordFrame())); + connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&))); + connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed())); connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int))); connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int))); connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int)));@@ -87,6 +108,7 @@
m_logView->setLevels(GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL); m_fpsTimer.setInterval(FPS_TIMER_INTERVAL); + m_shortcutController->setConfigController(m_config); setupMenu(menuBar()); }@@ -114,6 +136,13 @@ m_controller->loadGame(args->fname, args->dirmode);
} } +void Window::resizeFrame(int width, int height) { + QSize newSize(width, height); + newSize -= m_screenWidget->size(); + newSize += size(); + resize(newSize); +} + void Window::setConfig(ConfigController* config) { m_config = config; }@@ -123,9 +152,9 @@ const GBAOptions* opts = m_config->options();
m_logView->setLevels(opts->logLevel); - m_controller->setFrameskip(opts->frameskip); - m_controller->setAudioSync(opts->audioSync); - m_controller->setVideoSync(opts->videoSync); + m_controller->setOptions(opts); + m_display->lockAspectRatio(opts->lockAspectRatio); + m_display->filter(opts->resampleVideo); if (opts->bios) { m_controller->loadBIOS(opts->bios);@@ -140,9 +169,12 @@ emit audioBufferSamplesChanged(opts->audioBuffers);
} if (opts->width && opts->height) { - m_screenWidget->setSizeHint(QSize(opts->width, opts->height)); + resizeFrame(opts->width, opts->height); } + m_mruFiles = m_config->getMRU(); + updateMRU(); + m_inputController.setConfiguration(m_config); }@@ -151,22 +183,27 @@ m_config->write();
} void Window::selectROM() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM")); + QString filename = QFileDialog::getOpenFileName(this, tr("Select ROM"), m_config->getQtOption("lastDirectory").toString(), tr("Game Boy Advance ROMs (*.gba *.zip *.rom *.bin)")); if (!filename.isEmpty()) { + m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_controller->loadGame(filename); } } void Window::selectBIOS() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS")); + QString filename = QFileDialog::getOpenFileName(this, tr("Select BIOS"), m_config->getQtOption("lastDirectory").toString()); if (!filename.isEmpty()) { + m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); + m_config->setOption("bios", filename); + m_config->updateOption("bios"); m_controller->loadBIOS(filename); } } void Window::selectPatch() { - QString filename = QFileDialog::getOpenFileName(this, tr("Select patch"), QString(), tr("Patches (*.ips *.ups)")); + QString filename = QFileDialog::getOpenFileName(this, tr("Select patch"), m_config->getQtOption("lastDirectory").toString(), tr("Patches (*.ips *.ups *.bps)")); if (!filename.isEmpty()) { + m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path()); m_controller->loadPatch(filename); } }@@ -178,6 +215,44 @@ keyEditor->setAttribute(Qt::WA_DeleteOnClose);
keyEditor->show(); } +void Window::openSettingsWindow() { + SettingsView* settingsWindow = new SettingsView(m_config); + connect(this, SIGNAL(shutdown()), settingsWindow, SLOT(close())); + connect(settingsWindow, SIGNAL(biosLoaded(const QString&)), m_controller, SLOT(loadBIOS(const QString&))); + connect(settingsWindow, SIGNAL(audioDriverChanged()), m_controller, SLOT(reloadAudioDriver())); + settingsWindow->setAttribute(Qt::WA_DeleteOnClose); + settingsWindow->show(); +} + +void Window::openShortcutWindow() { + ShortcutView* shortcutView = new ShortcutView(); + shortcutView->setController(m_shortcutController); + connect(this, SIGNAL(shutdown()), shortcutView, SLOT(close())); + shortcutView->setAttribute(Qt::WA_DeleteOnClose); + shortcutView->show(); +} + +void Window::openOverrideWindow() { + OverrideView* overrideWindow = new OverrideView(m_controller, m_config); + connect(this, SIGNAL(shutdown()), overrideWindow, SLOT(close())); + overrideWindow->setAttribute(Qt::WA_DeleteOnClose); + overrideWindow->show(); +} + +void Window::openSensorWindow() { + SensorView* sensorWindow = new SensorView(m_controller); + connect(this, SIGNAL(shutdown()), sensorWindow, SLOT(close())); + sensorWindow->setAttribute(Qt::WA_DeleteOnClose); + sensorWindow->show(); +} + +void Window::openCheatsWindow() { + CheatsView* cheatsWindow = new CheatsView(m_controller); + connect(this, SIGNAL(shutdown()), cheatsWindow, SLOT(close())); + cheatsWindow->setAttribute(Qt::WA_DeleteOnClose); + cheatsWindow->show(); +} + #ifdef BUILD_SDL void Window::openGamepadWindow() { GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON);@@ -221,6 +296,8 @@ if (!m_gdbController) {
m_gdbController = new GDBController(m_controller, this); } GDBWindow* window = new GDBWindow(m_gdbController); + connect(this, SIGNAL(shutdown()), window, SLOT(close())); + window->setAttribute(Qt::WA_DeleteOnClose); window->show(); } #endif@@ -229,9 +306,6 @@ void Window::keyPressEvent(QKeyEvent* event) {
if (event->isAutoRepeat()) { QWidget::keyPressEvent(event); return; - } - if (event->key() == Qt::Key_Tab) { - m_controller->setTurbo(true, false); } GBAKey key = m_inputController.mapKeyboard(event->key()); if (key == GBA_KEY_NONE) {@@ -247,9 +321,6 @@ if (event->isAutoRepeat()) {
QWidget::keyReleaseEvent(event); return; } - if (event->key() == Qt::Key_Tab) { - m_controller->setTurbo(false, false); - } GBAKey key = m_inputController.mapKeyboard(event->key()); if (key == GBA_KEY_NONE) { QWidget::keyPressEvent(event);@@ -260,7 +331,6 @@ event->accept();
} void Window::resizeEvent(QResizeEvent*) { - redoLogo(); m_config->setOption("height", m_screenWidget->height()); m_config->setOption("width", m_screenWidget->width()); }@@ -270,6 +340,33 @@ emit shutdown();
QMainWindow::closeEvent(event); } +void Window::focusOutEvent(QFocusEvent*) { + m_controller->setTurbo(false, false); + m_controller->clearKeys(); +} + +void Window::dragEnterEvent(QDragEnterEvent* event) { + if (event->mimeData()->hasFormat("text/uri-list")) { + event->acceptProposedAction(); + } +} + +void Window::dropEvent(QDropEvent* event) { + QString uris = event->mimeData()->data("text/uri-list"); + uris = uris.trimmed(); + if (uris.contains("\n")) { + // Only one file please + return; + } + QUrl url(uris); + if (!url.isLocalFile()) { + // No remote loading + return; + } + event->accept(); + m_controller->loadGame(url.path()); +} + void Window::toggleFullScreen() { if (isFullScreen()) { showNormal();@@ -277,7 +374,7 @@ menuBar()->show();
} else { showFullScreen(); #ifndef Q_OS_MAC - if (!m_controller->isPaused()) { + if (m_controller->isLoaded() && !m_controller->isPaused()) { menuBar()->hide(); } #endif@@ -285,15 +382,28 @@ }
} void Window::gameStarted(GBAThread* context) { - emit startDrawing(m_controller->drawContext(), context); + char title[13] = { '\0' }; + MutexLock(&context->stateMutex); + if (context->state < THREAD_EXITING) { + emit startDrawing(m_controller->drawContext(), context); + GBAGetGameTitle(context->gba, title); + } else { + MutexUnlock(&context->stateMutex); + return; + } + MutexUnlock(&context->stateMutex); foreach (QAction* action, m_gameActions) { action->setDisabled(false); } - char title[13] = { '\0' }; - GBAGetGameTitle(context->gba, title); + appendMRU(context->fname); setWindowTitle(tr(PROJECT_NAME " - %1").arg(title)); attachWidget(m_display); - m_screenWidget->setScaledContents(true); + +#ifndef Q_OS_MAC + if(isFullScreen()) { + menuBar()->hide(); + } +#endif m_fpsTimer.start(); }@@ -304,19 +414,26 @@ action->setDisabled(true);
} setWindowTitle(tr(PROJECT_NAME)); detachWidget(m_display); - m_screenWidget->setScaledContents(false); - redoLogo(); + m_screenWidget->setLockAspectRatio(m_logo.width(), m_logo.height()); + m_screenWidget->setPixmap(m_logo); m_fpsTimer.stop(); } -void Window::redoLogo() { - if (m_controller->isLoaded()) { - return; - } - QPixmap logo(m_logo.scaled(m_screenWidget->size() * m_screenWidget->devicePixelRatio(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); - logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio()); - m_screenWidget->setPixmap(logo); +void Window::gameCrashed(const QString& errorMessage) { + QMessageBox* crash = new QMessageBox(QMessageBox::Critical, tr("Crash"), + tr("The game has crashed with the following error:\n\n%1").arg(errorMessage), + QMessageBox::Ok, this, Qt::Sheet); + crash->setAttribute(Qt::WA_DeleteOnClose); + crash->show(); +} + +void Window::gameFailed() { + QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Couldn't Load"), + tr("Could not load game. Are you sure it's in the correct format?"), + QMessageBox::Ok, this, Qt::Sheet); + fail->setAttribute(Qt::WA_DeleteOnClose); + fail->show(); } void Window::recordFrame() {@@ -327,11 +444,15 @@ }
} void Window::showFPS() { + char title[13] = { '\0' }; + GBAGetGameTitle(m_controller->thread()->gba, title); + if (m_frameList.isEmpty()) { + setWindowTitle(tr(PROJECT_NAME " - %1").arg(title)); + return; + } qint64 interval = m_frameList.first().msecsTo(m_frameList.last()); float fps = (m_frameList.count() - 1) * 10000.f / interval; fps = round(fps) / 10.f; - char title[13] = { '\0' }; - GBAGetGameTitle(m_controller->thread()->gba, title); setWindowTitle(tr(PROJECT_NAME " - %1 (%2 fps)").arg(title).arg(fps)); }@@ -360,9 +481,13 @@
void Window::setupMenu(QMenuBar* menubar) { menubar->clear(); QMenu* fileMenu = menubar->addMenu(tr("&File")); - addAction(fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open)); - fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())); - fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())); + m_shortcutController->addMenu(fileMenu); + installEventFilter(m_shortcutController); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); + + m_mruMenu = fileMenu->addMenu(tr("Recent")); fileMenu->addSeparator();@@ -370,15 +495,13 @@ QAction* loadState = new QAction(tr("&Load state"), fileMenu);
loadState->setShortcut(tr("F10")); connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); }); m_gameActions.append(loadState); - addAction(loadState); - fileMenu->addAction(loadState); + addControlledAction(fileMenu, loadState, "loadState"); QAction* saveState = new QAction(tr("&Save state"), fileMenu); saveState->setShortcut(tr("Shift+F10")); connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); }); m_gameActions.append(saveState); - addAction(saveState); - fileMenu->addAction(saveState); + addControlledAction(fileMenu, saveState, "saveState"); QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));@@ -401,21 +524,21 @@ }
#ifndef Q_OS_MAC fileMenu->addSeparator(); - fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit); + addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit"); #endif QMenu* emulationMenu = menubar->addMenu(tr("&Emulation")); + m_shortcutController->addMenu(emulationMenu); QAction* reset = new QAction(tr("&Reset"), emulationMenu); reset->setShortcut(tr("Ctrl+R")); connect(reset, SIGNAL(triggered()), m_controller, SLOT(reset())); m_gameActions.append(reset); - addAction(reset); - emulationMenu->addAction(reset); + addControlledAction(emulationMenu, reset, "reset"); QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu); connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame())); m_gameActions.append(shutdown); - emulationMenu->addAction(shutdown); + addControlledAction(emulationMenu, shutdown, "shutdown"); emulationMenu->addSeparator(); QAction* pause = new QAction(tr("&Pause"), emulationMenu);@@ -430,28 +553,32 @@ QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 1024, QImage::Format_RGB32);
QPixmap pixmap; pixmap.convertFromImage(currentImage.rgbSwapped()); m_screenWidget->setPixmap(pixmap); + m_screenWidget->setLockAspectRatio(3, 2); }); connect(m_controller, &GameController::gameUnpaused, [pause]() { pause->setChecked(false); }); m_gameActions.append(pause); - addAction(pause); - emulationMenu->addAction(pause); + addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu); frameAdvance->setShortcut(tr("Ctrl+N")); connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance())); m_gameActions.append(frameAdvance); - addAction(frameAdvance); - emulationMenu->addAction(frameAdvance); + addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); emulationMenu->addSeparator(); - QAction* turbo = new QAction(tr("T&urbo"), emulationMenu); + QAction* turbo = new QAction(tr("&Fast forward"), emulationMenu); turbo->setCheckable(true); turbo->setChecked(false); turbo->setShortcut(tr("Shift+Tab")); connect(turbo, SIGNAL(triggered(bool)), m_controller, SLOT(setTurbo(bool))); - addAction(turbo); - emulationMenu->addAction(turbo); + addControlledAction(emulationMenu, turbo, "fastForward"); + + QAction* rewind = new QAction(tr("Re&wind"), emulationMenu); + rewind->setShortcut(tr("`")); + connect(rewind, SIGNAL(triggered()), m_controller, SLOT(rewind())); + m_gameActions.append(rewind); + addControlledAction(emulationMenu, rewind, "rewind"); ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu);@@ -463,44 +590,29 @@ audioSync->addBoolean(tr("Sync to &audio"), emulationMenu);
audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); }); m_config->updateOption("audioSync"); - emulationMenu->addSeparator(); - QAction* keymap = new QAction(tr("Remap keyboard..."), emulationMenu); - connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow())); - emulationMenu->addAction(keymap); - -#ifdef BUILD_SDL - QAction* gamepad = new QAction(tr("Remap gamepad..."), emulationMenu); - connect(gamepad, SIGNAL(triggered()), this, SLOT(openGamepadWindow())); - emulationMenu->addAction(gamepad); -#endif - QMenu* avMenu = menubar->addMenu(tr("Audio/&Video")); + m_shortcutController->addMenu(avMenu); QMenu* frameMenu = avMenu->addMenu(tr("Frame size")); - QAction* setSize = new QAction(tr("1x"), avMenu); - connect(setSize, &QAction::triggered, [this]() { - showNormal(); - resize(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - }); - frameMenu->addAction(setSize); - setSize = new QAction(tr("2x"), avMenu); - connect(setSize, &QAction::triggered, [this]() { - showNormal(); - resize(VIDEO_HORIZONTAL_PIXELS * 2, VIDEO_VERTICAL_PIXELS * 2); - }); - frameMenu->addAction(setSize); - setSize = new QAction(tr("3x"), avMenu); - connect(setSize, &QAction::triggered, [this]() { - showNormal(); - resize(VIDEO_HORIZONTAL_PIXELS * 3, VIDEO_VERTICAL_PIXELS * 3); - }); - frameMenu->addAction(setSize); - setSize = new QAction(tr("4x"), avMenu); - connect(setSize, &QAction::triggered, [this]() { - showNormal(); - resize(VIDEO_HORIZONTAL_PIXELS * 4, VIDEO_VERTICAL_PIXELS * 4); - }); - frameMenu->addAction(setSize); - addAction(frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F"))); + m_shortcutController->addMenu(frameMenu, avMenu); + for (int i = 1; i <= 6; ++i) { + QAction* setSize = new QAction(tr("%1x").arg(QString::number(i)), avMenu); + connect(setSize, &QAction::triggered, [this, i]() { + showNormal(); + resizeFrame(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i); + }); + addControlledAction(frameMenu, setSize, tr("frame%1x").arg(QString::number(i))); + } + addControlledAction(frameMenu, frameMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), QKeySequence("Ctrl+F")), "fullscreen"); + + ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); + lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu); + lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); }); + m_config->updateOption("lockAspectRatio"); + + ConfigOption* resampleVideo = m_config->addOption("resampleVideo"); + resampleVideo->addBoolean(tr("Resample video"), avMenu); + resampleVideo->connect([this](const QVariant& value) { m_display->filter(value.toBool()); }); + m_config->updateOption("resampleVideo"); QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip")); ConfigOption* skip = m_config->addOption("frameskip");@@ -512,7 +624,7 @@ m_config->updateOption("frameskip");
avMenu->addSeparator(); - QMenu* buffersMenu = avMenu->addMenu(tr("Buffer &size")); + QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size")); ConfigOption* buffers = m_config->addOption("audioBuffers"); buffers->connect([this](const QVariant& value) { emit audioBufferSamplesChanged(value.toInt()); }); buffers->addValue(tr("512"), 512, buffersMenu);@@ -545,36 +657,98 @@ QAction* screenshot = new QAction(tr("Take &screenshot"), avMenu);
screenshot->setShortcut(tr("F12")); connect(screenshot, SIGNAL(triggered()), m_display, SLOT(screenshot())); m_gameActions.append(screenshot); - addAction(screenshot); - avMenu->addAction(screenshot); + addControlledAction(avMenu, screenshot, "screenshot"); #endif #ifdef USE_FFMPEG QAction* recordOutput = new QAction(tr("Record output..."), avMenu); recordOutput->setShortcut(tr("F11")); connect(recordOutput, SIGNAL(triggered()), this, SLOT(openVideoWindow())); - addAction(recordOutput); - avMenu->addAction(recordOutput); + addControlledAction(avMenu, recordOutput, "recordOutput"); #endif #ifdef USE_MAGICK QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu); recordGIF->setShortcut(tr("Shift+F11")); connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow())); - addAction(recordGIF); - avMenu->addAction(recordGIF); + addControlledAction(avMenu, recordGIF, "recordGIF"); #endif - QMenu* debuggingMenu = menubar->addMenu(tr("&Debugging")); - QAction* viewLogs = new QAction(tr("View &logs..."), debuggingMenu); + QMenu* toolsMenu = menubar->addMenu(tr("&Tools")); + m_shortcutController->addMenu(toolsMenu); + QAction* viewLogs = new QAction(tr("View &logs..."), toolsMenu); connect(viewLogs, SIGNAL(triggered()), m_logView, SLOT(show())); - debuggingMenu->addAction(viewLogs); + addControlledAction(toolsMenu, viewLogs, "viewLogs"); + + QAction* overrides = new QAction(tr("Game &overrides..."), toolsMenu); + connect(overrides, SIGNAL(triggered()), this, SLOT(openOverrideWindow())); + addControlledAction(toolsMenu, overrides, "overrideWindow"); + + QAction* sensors = new QAction(tr("Game &Pak sensors..."), toolsMenu); + connect(sensors, SIGNAL(triggered()), this, SLOT(openSensorWindow())); + addControlledAction(toolsMenu, sensors, "sensorWindow"); + + QAction* cheats = new QAction(tr("&Cheats..."), toolsMenu); + connect(cheats, SIGNAL(triggered()), this, SLOT(openCheatsWindow())); + addControlledAction(toolsMenu, cheats, "cheatsWindow"); + #ifdef USE_GDB_STUB - QAction* gdbWindow = new QAction(tr("Start &GDB server..."), debuggingMenu); + QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); connect(gdbWindow, SIGNAL(triggered()), this, SLOT(gdbOpen())); - debuggingMenu->addAction(gdbWindow); + addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif + toolsMenu->addSeparator(); + QAction* solarIncrease = new QAction(tr("Increase solar level"), toolsMenu); + connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel())); + addControlledAction(toolsMenu, solarIncrease, "increaseLuminanceLevel"); + + QAction* solarDecrease = new QAction(tr("Decrease solar level"), toolsMenu); + connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel())); + addControlledAction(toolsMenu, solarDecrease, "decreaseLuminanceLevel"); + + QAction* maxSolar = new QAction(tr("Brightest solar level"), toolsMenu); + connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); }); + addControlledAction(toolsMenu, maxSolar, "maxLuminanceLevel"); + + QAction* minSolar = new QAction(tr("Darkest solar level"), toolsMenu); + connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); }); + addControlledAction(toolsMenu, minSolar, "minLuminanceLevel"); + + toolsMenu->addSeparator(); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings"); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Edit shortcuts..."), this, SLOT(openShortcutWindow())), "shortcuts"); + + QAction* keymap = new QAction(tr("Remap keyboard..."), toolsMenu); + connect(keymap, SIGNAL(triggered()), this, SLOT(openKeymapWindow())); + addControlledAction(toolsMenu, keymap, "remapKeyboard"); + +#ifdef BUILD_SDL + QAction* gamepad = new QAction(tr("Remap gamepad..."), toolsMenu); + connect(gamepad, SIGNAL(triggered()), this, SLOT(openGamepadWindow())); + addControlledAction(toolsMenu, gamepad, "remapGamepad"); +#endif + + ConfigOption* skipBios = m_config->addOption("skipBios"); + skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); }); + + ConfigOption* rewindEnable = m_config->addOption("rewindEnable"); + rewindEnable->connect([this](const QVariant& value) { m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); }); + + ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity"); + rewindBufferCapacity->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); }); + + ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval"); + rewindBufferInterval->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); }); + + QMenu* other = new QMenu(tr("Other"), this); + m_shortcutController->addMenu(other); + m_shortcutController->addFunctions(other, [this]() { + m_controller->setTurbo(true, false); + }, [this]() { + m_controller->setTurbo(false, false); + }, QKeySequence(Qt::Key_Tab), tr("Fast Forward (held)"), "holdFastForward"); + foreach (QAction* action, m_gameActions) { action->setDisabled(true); }@@ -589,16 +763,49 @@ void Window::detachWidget(QWidget* widget) {
m_screenWidget->layout()->removeWidget(widget); } +void Window::appendMRU(const QString& fname) { + int index = m_mruFiles.indexOf(fname); + if (index >= 0) { + m_mruFiles.removeAt(index); + } + m_mruFiles.prepend(fname); + while (m_mruFiles.size() > ConfigController::MRU_LIST_SIZE) { + m_mruFiles.removeLast(); + } + updateMRU(); +} + +void Window::updateMRU() { + if (!m_mruMenu) { + return; + } + m_mruMenu->clear(); + int i = 0; + for (const QString& file : m_mruFiles) { + QAction* item = new QAction(file, m_mruMenu); + item->setShortcut(QString("Ctrl+%1").arg(i)); + connect(item, &QAction::triggered, [this, file]() { m_controller->loadGame(file); }); + m_mruMenu->addAction(item); + ++i; + } + m_config->setMRU(m_mruFiles); + m_config->write(); + m_mruMenu->setEnabled(i > 0); +} + +QAction* Window::addControlledAction(QMenu* menu, QAction* action, const QString& name) { + m_shortcutController->addAction(menu, action, name); + menu->addAction(action); + addAction(action); + return action; +} + WindowBackground::WindowBackground(QWidget* parent) : QLabel(parent) { setLayout(new QStackedLayout()); layout()->setContentsMargins(0, 0, 0, 0); setAlignment(Qt::AlignCenter); - QPalette p = palette(); - p.setColor(backgroundRole(), Qt::black); - setPalette(p); - setAutoFillBackground(true); } void WindowBackground::setSizeHint(const QSize& hint) {@@ -608,3 +815,28 @@
QSize WindowBackground::sizeHint() const { return m_sizeHint; } + +void WindowBackground::setLockAspectRatio(int width, int height) { + m_aspectWidth = width; + m_aspectHeight = height; +} + +void WindowBackground::paintEvent(QPaintEvent*) { + QPainter painter(this); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + const QPixmap* logo = pixmap(); + painter.fillRect(QRect(QPoint(), size()), Qt::black); + if (!logo) { + return; + } + QSize s = size(); + QSize ds = s; + if (s.width() * m_aspectHeight > s.height() * m_aspectWidth) { + ds.setWidth(s.height() * m_aspectWidth / m_aspectHeight); + } else if (s.width() * m_aspectHeight < s.height() * m_aspectWidth) { + ds.setHeight(s.width() * m_aspectHeight / m_aspectWidth); + } + QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2); + QRect full(origin, ds); + painter.drawPixmap(full, *logo); +}
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,14 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_WINDOW #define QGBA_WINDOW -#include <QAudioOutput> #include <QDateTime> #include <QList> #include <QMainWindow> #include <QTimer> + +#include <functional> extern "C" { -#include "gba.h" +#include "gba/gba.h" } #include "GDBController.h"@@ -30,6 +31,7 @@ class ConfigController;
class GameController; class GIFView; class LogView; +class ShortcutController; class VideoView; class WindowBackground;@@ -45,6 +47,8 @@
void setConfig(ConfigController*); void argumentsPassed(GBAArguments*); + void resizeFrame(int width, int height); + signals: void startDrawing(const uint32_t*, GBAThread*); void shutdown();@@ -60,6 +64,12 @@ void loadConfig();
void saveConfig(); void openKeymapWindow(); + void openSettingsWindow(); + void openShortcutWindow(); + + void openOverrideWindow(); + void openSensorWindow(); + void openCheatsWindow(); #ifdef BUILD_SDL void openGamepadWindow();@@ -82,11 +92,15 @@ virtual void keyPressEvent(QKeyEvent* event) override;
virtual void keyReleaseEvent(QKeyEvent* event) override; virtual void resizeEvent(QResizeEvent*) override; virtual void closeEvent(QCloseEvent*) override; + virtual void focusOutEvent(QFocusEvent*) override; + virtual void dragEnterEvent(QDragEnterEvent*) override; + virtual void dropEvent(QDropEvent*) override; private slots: void gameStarted(GBAThread*); void gameStopped(); - void redoLogo(); + void gameCrashed(const QString&); + void gameFailed(); void recordFrame(); void showFPS();@@ -100,6 +114,11 @@ void openStateWindow(LoadSave);
void attachWidget(QWidget* widget); void detachWidget(QWidget* widget); + + void appendMRU(const QString& fname); + void updateMRU(); + + QAction* addControlledAction(QMenu* menu, QAction* action, const QString& name); GameController* m_controller; Display* m_display;@@ -112,6 +131,9 @@ ConfigController* m_config;
InputController m_inputController; QList<QDateTime> m_frameList; QTimer m_fpsTimer; + QList<QString> m_mruFiles; + QMenu* m_mruMenu; + ShortcutController* m_shortcutController; #ifdef USE_FFMPEG VideoView* m_videoView;@@ -134,9 +156,15 @@ WindowBackground(QWidget* parent = 0);
void setSizeHint(const QSize& size); virtual QSize sizeHint() const override; + void setLockAspectRatio(int width, int height); + +protected: + virtual void paintEvent(QPaintEvent*) override; private: QSize m_sizeHint; + int m_aspectWidth; + int m_aspectHeight; }; }
@@ -9,12 +9,14 @@ set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS} CACHE INTERNAL "")
set(SDL_LIBRARY ${SDL2_LIBRARIES} CACHE INTERNAL "") set(SDLMAIN_LIBRARY "" CACHE INTERNAL "") link_directories(${SDL2_LIBDIR}) + set(SDL_VERSION_DEBIAN "2-2.0-0") endif() endif() if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND) find_package(SDL 1.2) set(SDL_VERSION "1.2" PARENT_SCOPE) + set(SDL_VERSION_DEBIAN "1.2debian") endif() if (NOT SDL2_FOUND AND NOT SDL_FOUND)@@ -22,6 +24,8 @@ set(BUILD_SDL OFF PARENT_SCOPE)
return() endif() +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VERSION_DEBIAN}" PARENT_SCOPE) + file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c) set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY}) include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR})@@ -34,8 +38,9 @@ set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-sdl.c)
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline") add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC}) + target_compile_definitions(${BINARY_NAME}-rpi PRIVATE ${FEATURE_DEFINES}) target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY}) - install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin) + install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi) endif() if(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL)@@ -48,6 +53,7 @@ include_directories(${OPENGL_INCLUDE_DIR})
endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) +target_compile_definitions(${BINARY_NAME}-sdl PRIVATE ${FEATURE_DEFINES}) target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) -install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin) +install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
@@ -1,11 +1,11 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "main.h" -#include "gba-thread.h" +#include "gba/supervisor/thread.h" #ifdef __APPLE__ #include <OpenGL/gl.h>@@ -59,18 +59,34 @@ SDL_SetVideoMode(renderer->viewportWidth, renderer->viewportHeight, 32, SDL_OPENGL);
#endif #endif - renderer->d.outputBuffer = malloc(256 * 256 * 4); - renderer->d.outputBufferStride = 256; + renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); + renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; glGenTextures(1, &renderer->tex); glBindTexture(GL_TEXTURE_2D, renderer->tex); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + if (renderer->filter) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } #ifndef _WIN32 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #endif +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif + + glViewport(0, 0, renderer->viewportWidth, renderer->viewportHeight); return true;@@ -104,12 +120,12 @@ if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
glBindTexture(GL_TEXTURE_2D, renderer->tex); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, renderer->d.outputBuffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, renderer->d.outputBuffer); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, renderer->d.outputBuffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, renderer->d.outputBuffer); #endif #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, GL_RGBA, GL_UNSIGNED_BYTE, renderer->d.outputBuffer); #endif if (context->sync.videoFrameWait) { glFlush();
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -13,10 +13,10 @@ #ifdef USE_GDB_STUB
#include "debugger/gdb-stub.h" #endif -#include "gba-thread.h" -#include "gba.h" -#include "gba-config.h" -#include "gba-video.h" +#include "gba/gba.h" +#include "gba/supervisor/config.h" +#include "gba/supervisor/thread.h" +#include "gba/video.h" #include "platform/commandline.h" #include "util/configuration.h"@@ -45,14 +45,15 @@
struct GBAOptions opts = { .width = VIDEO_HORIZONTAL_PIXELS, .height = VIDEO_VERTICAL_PIXELS, + .rewindEnable = true, .audioBuffers = 512, .videoSync = false, .audioSync = true, }; GBAConfigLoadDefaults(&config, &opts); - struct GBAArguments args = {}; - struct GraphicsOpts graphicsOpts = {}; + struct GBAArguments args; + struct GraphicsOpts graphicsOpts; struct SubParser subparser;@@ -75,6 +76,9 @@ renderer.events.windowUpdated = 0;
#endif renderer.ratio = graphicsOpts.multiplier; + renderer.lockAspectRatio = opts.lockAspectRatio; + renderer.filter = opts.resampleVideo; + if (!_GBASDLInit(&renderer)) { freeArguments(&args); GBAConfigFreeOpts(&opts);@@ -98,13 +102,22 @@
renderer.events.bindings = &inputMap; GBASDLInitBindings(&inputMap); GBASDLInitEvents(&renderer.events); - GBASDLEventsLoadConfig(&renderer.events, &config.configTable); // TODO: Don't use this directly - - GBAThreadStart(&context); + GBASDLEventsLoadConfig(&renderer.events, GBAConfigGetInput(&config)); + context.overrides = GBAConfigGetOverrides(&config); - GBASDLRunloop(&context, &renderer); + int didFail = 0; + if (GBAThreadStart(&context)) { + GBASDLRunloop(&context, &renderer); + GBAThreadJoin(&context); + } else { + didFail = 1; + printf("Could not run game. Are you sure the file exists and is a Game Boy Advance game?\n"); + } - GBAThreadJoin(&context); + if (GBAThreadHasCrashed(&context)) { + didFail = 1; + printf("The game crashed!\n"); + } freeArguments(&args); GBAConfigFreeOpts(&opts); GBAConfigDeinit(&config);@@ -113,11 +126,12 @@ GBAInputMapDeinit(&inputMap);
_GBASDLDeinit(&renderer); - return 0; + return didFail; } static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { + printf("Could not initialize video: %s\n", SDL_GetError()); return false; }
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SDL_MAIN_H #define SDL_MAIN_H -#include "renderers/video-software.h" +#include "gba/renderers/video-software.h" #include "sdl-audio.h" #include "sdl-events.h"@@ -47,6 +47,9 @@
int viewportWidth; int viewportHeight; int ratio; + + bool lockAspectRatio; + bool filter; #ifdef BUILD_GL GLuint tex;
@@ -1,12 +1,16 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "sdl-audio.h" -#include "gba.h" -#include "gba-thread.h" +#include "gba/gba.h" +#include "gba/supervisor/thread.h" + +#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF +#include "third-party/blip_buf/blip_buf.h" +#endif #define BUFFER_SIZE (GBA_AUDIO_SAMPLES >> 2)@@ -24,13 +28,17 @@ context->desiredSpec.channels = 2;
context->desiredSpec.samples = context->samples; context->desiredSpec.callback = _GBASDLAudioCallback; context->desiredSpec.userdata = context; +#if RESAMPLE_LIBRARY == RESAMPLE_NN context->drift = 0.f; +#endif if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) { GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; } context->thread = threadContext; context->samples = context->obtainedSpec.samples; + float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); + threadContext->audioBuffers = context->samples / ratio; if (context->samples > threadContext->audioBuffers) { threadContext->audioBuffers = context->samples * 2; }@@ -62,7 +70,8 @@ if (!context || !audioContext->thread || !audioContext->thread->gba) {
memset(data, 0, len); return; } - audioContext->ratio = GBAAudioCalculateRatio(&audioContext->thread->gba->audio, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq); +#if RESAMPLE_LIBRARY == RESAMPLE_NN + audioContext->ratio = GBAAudioCalculateRatio(audioContext->thread->gba->audio.sampleRate, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq); if (audioContext->ratio == INFINITY) { memset(data, 0, len); return;@@ -72,4 +81,23 @@ len /= 2 * audioContext->obtainedSpec.channels;
if (audioContext->obtainedSpec.channels == 2) { GBAAudioResampleNN(&audioContext->thread->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len); } +#elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF + double fauxClock = GBAAudioCalculateRatio(1, audioContext->thread->fpsTarget, 1); + GBASyncLockAudio(&audioContext->thread->sync); + blip_set_rates(audioContext->thread->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); + blip_set_rates(audioContext->thread->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); + len /= 2 * audioContext->obtainedSpec.channels; + int available = blip_samples_avail(audioContext->thread->gba->audio.left); + if (available > len) { + available = len; + } + blip_read_samples(audioContext->thread->gba->audio.left, (short*) data, available, audioContext->obtainedSpec.channels == 2); + if (audioContext->obtainedSpec.channels == 2) { + blip_read_samples(audioContext->thread->gba->audio.right, ((short*) data) + 1, available, 1); + } + GBASyncConsumeAudio(&audioContext->thread->sync); + if (available < len) { + memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short)); + } +#endif }
@@ -10,6 +10,8 @@ #include "util/common.h"
#include <SDL.h> +#include "gba/audio.h" + struct GBASDLAudio { // Input size_t samples;@@ -17,8 +19,12 @@
// State SDL_AudioSpec desiredSpec; SDL_AudioSpec obtainedSpec; - float drift; +#if RESAMPLE_LIBRARY != RESAMPLE_BLIP_BUF float ratio; +#endif +#if RESAMPLE_LIBRARY == RESAMPLE_NN + float drift; +#endif struct GBAThread* thread; };
@@ -6,11 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "sdl-events.h" #include "debugger/debugger.h" -#include "gba-io.h" -#include "gba-rr.h" -#include "gba-serialize.h" -#include "gba-video.h" -#include "renderers/video-software.h" +#include "gba/io.h" +#include "gba/supervisor/rr.h" +#include "gba/serialize.h" +#include "gba/video.h" +#include "gba/renderers/video-software.h" #include "util/vfs.h" #if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__)@@ -69,6 +69,11 @@ GBAInputBindKey(inputMap, SDL_BINDING_BUTTON, 4, GBA_KEY_UP);
GBAInputBindKey(inputMap, SDL_BINDING_BUTTON, 6, GBA_KEY_DOWN); GBAInputBindKey(inputMap, SDL_BINDING_BUTTON, 7, GBA_KEY_LEFT); GBAInputBindKey(inputMap, SDL_BINDING_BUTTON, 5, GBA_KEY_RIGHT); + + struct GBAAxis description = { GBA_KEY_RIGHT, GBA_KEY_LEFT, 0x4000, -0x4000 }; + GBAInputBindAxis(inputMap, SDL_BINDING_BUTTON, 0, &description); + description = (struct GBAAxis) { GBA_KEY_DOWN, GBA_KEY_UP, 0x4000, -0x4000 }; + GBAInputBindAxis(inputMap, SDL_BINDING_BUTTON, 1, &description); } void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) {@@ -107,46 +112,35 @@ context->activeKeys &= ~(1 << key);
} return; } - switch (event->keysym.sym) { - case SDLK_F11: - if (event->type == SDL_KEYDOWN && context->debugger) { - ARMDebuggerEnter(context->debugger, DEBUGGER_ENTER_MANUAL); - } + if (event->keysym.sym == SDLK_TAB) { + context->sync.audioWait = event->type != SDL_KEYDOWN; return; + } + if (event->type == SDL_KEYDOWN) { + switch (event->keysym.sym) { + case SDLK_F11: + if (context->debugger) { + ARMDebuggerEnter(context->debugger, DEBUGGER_ENTER_MANUAL, 0); + } + return; #ifdef USE_PNG - case SDLK_F12: - if (event->type == SDL_KEYDOWN) { + case SDLK_F12: GBAThreadInterrupt(context); GBAThreadTakeScreenshot(context); GBAThreadContinue(context); - } - return; + return; #endif - case SDLK_TAB: - context->sync.audioWait = event->type != SDL_KEYDOWN; - return; - case SDLK_BACKSLASH: - if (event->type == SDL_KEYDOWN) { + case SDLK_BACKSLASH: GBAThreadPause(context); context->frameCallback = _pauseAfterFrame; GBAThreadUnpause(context); - } - return; - case SDLK_LEFTBRACKET: - GBAThreadInterrupt(context); - GBARewind(context, 10); - GBAThreadContinue(context); - return; - case SDLK_ESCAPE: - GBAThreadInterrupt(context); - if (context->gba->rr) { - GBARRStopPlaying(context->gba->rr); - GBARRStopRecording(context->gba->rr); - } - GBAThreadContinue(context); - return; - default: - if (event->type == SDL_KEYDOWN) { + return; + case SDLK_BACKQUOTE: + GBAThreadInterrupt(context); + GBARewind(context, 10); + GBAThreadContinue(context); + return; + default: if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) { switch (event->keysym.sym) { #if SDL_VERSION_ATLEAST(2, 0, 0)@@ -167,31 +161,6 @@ break;
case SDLK_r: GBAThreadReset(context); break; - case SDLK_t: - if (context->stateDir) { - GBAThreadInterrupt(context); - GBARRContextCreate(context->gba); - if (!GBARRIsRecording(context->gba->rr)) { - GBARRStopPlaying(context->gba->rr); - GBARRInitStream(context->gba->rr, context->stateDir); - GBARRReinitStream(context->gba->rr, INIT_EX_NIHILO); - GBARRStartRecording(context->gba->rr); - GBARRSaveState(context->gba); - } - GBAThreadContinue(context); - } - break; - case SDLK_y: - if (context->stateDir) { - GBAThreadInterrupt(context); - GBARRContextCreate(context->gba); - GBARRStopRecording(context->gba->rr); - GBARRInitStream(context->gba->rr, context->stateDir); - GBARRStartPlaying(context->gba->rr, false); - GBARRLoadState(context->gba); - GBAThreadContinue(context); - } - break; default: break; }@@ -208,7 +177,7 @@ case SDLK_F7:
case SDLK_F8: case SDLK_F9: GBAThreadInterrupt(context); - GBASaveState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1 + 1, true); + GBASaveState(context, context->stateDir, event->keysym.sym - SDLK_F1 + 1, true); GBAThreadContinue(context); break; default:@@ -226,15 +195,15 @@ case SDLK_F7:
case SDLK_F8: case SDLK_F9: GBAThreadInterrupt(context); - GBALoadState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1 + 1); + GBALoadState(context, context->stateDir, event->keysym.sym - SDLK_F1 + 1); GBAThreadContinue(context); break; default: break; } } + return; } - return; } }@@ -272,6 +241,18 @@ context->activeKeys &= ~((1 << GBA_KEY_UP) | (1 << GBA_KEY_LEFT) | (1 << GBA_KEY_DOWN) | (1 << GBA_KEY_RIGHT));
context->activeKeys |= key; } +static void _GBASDLHandleJoyAxis(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_JoyAxisEvent* event) { + int keys = context->activeKeys; + + keys = GBAInputClearAxis(sdlContext->bindings, SDL_BINDING_BUTTON, event->axis, keys); + enum GBAKey key = GBAInputMapAxis(sdlContext->bindings, SDL_BINDING_BUTTON, event->axis, event->value); + if (key != GBA_KEY_NONE) { + keys |= 1 << key; + } + + context->activeKeys = keys; +} + #if SDL_VERSION_ATLEAST(2, 0, 0) static void _GBASDLHandleWindowEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_WindowEvent* event) { UNUSED(context);@@ -303,5 +284,9 @@ _GBASDLHandleJoyButton(context, sdlContext, &event->jbutton);
break; case SDL_JOYHATMOTION: _GBASDLHandleJoyHat(context, &event->jhat); + break; + case SDL_JOYAXISMOTION: + _GBASDLHandleJoyAxis(context, sdlContext, &event->jaxis); + break; } }
@@ -8,7 +8,7 @@ #define SDL_EVENTS_H
#include "util/common.h" -#include "gba-thread.h" +#include "gba/supervisor/thread.h" #include <SDL.h>
@@ -1,16 +1,12 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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 "main.h" -#include "gba-thread.h" - -#if defined(__ARM_NEON) -void _neon2x(void* dest, void* src, int width, int height); -void _neon4x(void* dest, void* src, int width, int height); -#endif +#include "gba/supervisor/thread.h" +#include "util/arm-algo.h" bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { #if !SDL_VERSION_ATLEAST(2, 0, 0)@@ -36,7 +32,7 @@ #else
renderer->tex = SDL_CreateTexture(renderer->sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); #endif - SDL_LockTexture(renderer->tex, 0, &renderer->d.outputBuffer, &renderer->d.outputBufferStride); + SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); renderer->d.outputBufferStride /= BYTES_PER_PIXEL; #else SDL_Surface* surface = SDL_GetVideoSurface();@@ -74,7 +70,7 @@ #if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_UnlockTexture(renderer->tex); SDL_RenderCopy(renderer->sdlRenderer, renderer->tex, 0, 0); SDL_RenderPresent(renderer->sdlRenderer); - SDL_LockTexture(renderer->tex, 0, &renderer->d.outputBuffer, &renderer->d.outputBufferStride); + SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); renderer->d.outputBufferStride /= BYTES_PER_PIXEL; #else switch (renderer->ratio) {
@@ -0,0 +1,344 @@
+/* blip_buf 1.1.0. http://www.slack.net/~ant/ */ + +#include "blip_buf.h" + +#include <assert.h> +#include <limits.h> +#include <string.h> +#include <stdlib.h> + +/* Library Copyright (C) 2003-2009 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#if defined (BLARGG_TEST) && BLARGG_TEST + #include "blargg_test.h" +#endif + +/* Equivalent to ULONG_MAX >= 0xFFFFFFFF00000000. +Avoids constants that don't fit in 32 bits. */ +#if ULONG_MAX/0xFFFFFFFF > 0xFFFFFFFF + typedef unsigned long fixed_t; + enum { pre_shift = 32 }; + +#elif defined(ULLONG_MAX) + typedef unsigned long long fixed_t; + enum { pre_shift = 32 }; + +#else + typedef unsigned fixed_t; + enum { pre_shift = 0 }; + +#endif + +enum { time_bits = pre_shift + 20 }; + +static fixed_t const time_unit = (fixed_t) 1 << time_bits; + +enum { bass_shift = 9 }; /* affects high-pass filter breakpoint frequency */ +enum { end_frame_extra = 2 }; /* allows deltas slightly after frame length */ + +enum { half_width = 8 }; +enum { buf_extra = half_width*2 + end_frame_extra }; +enum { phase_bits = 5 }; +enum { phase_count = 1 << phase_bits }; +enum { delta_bits = 15 }; +enum { delta_unit = 1 << delta_bits }; +enum { frac_bits = time_bits - pre_shift }; + +/* We could eliminate avail and encode whole samples in offset, but that would +limit the total buffered samples to blip_max_frame. That could only be +increased by decreasing time_bits, which would reduce resample ratio accuracy. +*/ + +/** Sample buffer that resamples to output rate and accumulates samples +until they're read out */ +struct blip_t +{ + fixed_t factor; + fixed_t offset; + int avail; + int size; + int integrator; +}; + +typedef int buf_t; + +/* probably not totally portable */ +#define SAMPLES( buf ) ((buf_t*) ((buf) + 1)) + +/* Arithmetic (sign-preserving) right shift */ +#define ARITH_SHIFT( n, shift ) \ + ((n) >> (shift)) + +enum { max_sample = +32767 }; +enum { min_sample = -32768 }; + +#define CLAMP( n ) \ + {\ + if ( (short) n != n )\ + n = ARITH_SHIFT( n, 16 ) ^ max_sample;\ + } + +static void check_assumptions( void ) +{ + int n; + + #if INT_MAX < 0x7FFFFFFF || UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" + #endif + + assert( (-3 >> 1) == -2 ); /* right shift must preserve sign */ + + n = max_sample * 2; + CLAMP( n ); + assert( n == max_sample ); + + n = min_sample * 2; + CLAMP( n ); + assert( n == min_sample ); + + assert( blip_max_ratio <= time_unit ); + assert( blip_max_frame <= (fixed_t) -1 >> time_bits ); +} + +blip_t* blip_new( int size ) +{ + blip_t* m; + assert( size >= 0 ); + + m = (blip_t*) malloc( sizeof *m + (size + buf_extra) * sizeof (buf_t) ); + if ( m ) + { + m->factor = time_unit / blip_max_ratio; + m->size = size; + blip_clear( m ); + check_assumptions(); + } + return m; +} + +void blip_delete( blip_t* m ) +{ + if ( m != NULL ) + { + /* Clear fields in case user tries to use after freeing */ + memset( m, 0, sizeof *m ); + free( m ); + } +} + +void blip_set_rates( blip_t* m, double clock_rate, double sample_rate ) +{ + double factor = time_unit * sample_rate / clock_rate; + m->factor = (fixed_t) factor; + + /* Fails if clock_rate exceeds maximum, relative to sample_rate */ + assert( 0 <= factor - m->factor && factor - m->factor < 1 ); + + /* Avoid requiring math.h. Equivalent to + m->factor = (int) ceil( factor ) */ + if ( m->factor < factor ) + m->factor++; + + /* At this point, factor is most likely rounded up, but could still + have been rounded down in the floating-point calculation. */ +} + +void blip_clear( blip_t* m ) +{ + /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if + factor is rounded up. factor-1 is suitable if factor is rounded down. + Since we don't know rounding direction, factor/2 accommodates either, + with the slight loss of showing an error in half the time. Since for + a 64-bit factor this is years, the halving isn't a problem. */ + + m->offset = m->factor / 2; + m->avail = 0; + m->integrator = 0; + memset( SAMPLES( m ), 0, (m->size + buf_extra) * sizeof (buf_t) ); +} + +int blip_clocks_needed( const blip_t* m, int samples ) +{ + fixed_t needed; + + /* Fails if buffer can't hold that many more samples */ + assert( samples >= 0 && m->avail + samples <= m->size ); + + needed = (fixed_t) samples * time_unit; + if ( needed < m->offset ) + return 0; + + return (needed - m->offset + m->factor - 1) / m->factor; +} + +void blip_end_frame( blip_t* m, unsigned t ) +{ + fixed_t off = t * m->factor + m->offset; + m->avail += off >> time_bits; + m->offset = off & (time_unit - 1); + + /* Fails if buffer size was exceeded */ + assert( m->avail <= m->size ); +} + +int blip_samples_avail( const blip_t* m ) +{ + return m->avail; +} + +static void remove_samples( blip_t* m, int count ) +{ + buf_t* buf = SAMPLES( m ); + int remain = m->avail + buf_extra - count; + m->avail -= count; + + memmove( &buf [0], &buf [count], remain * sizeof buf [0] ); + memset( &buf [remain], 0, count * sizeof buf [0] ); +} + +int blip_read_samples( blip_t* m, short out [], int count, int stereo ) +{ + assert( count >= 0 ); + + if ( count > m->avail ) + count = m->avail; + + if ( count ) + { + int const step = stereo ? 2 : 1; + buf_t const* in = SAMPLES( m ); + buf_t const* end = in + count; + int sum = m->integrator; + do + { + /* Eliminate fraction */ + int s = ARITH_SHIFT( sum, delta_bits ); + + sum += *in++; + + CLAMP( s ); + + *out = s; + out += step; + + /* High-pass filter */ + sum -= s << (delta_bits - bass_shift); + } + while ( in != end ); + m->integrator = sum; + + remove_samples( m, count ); + } + + return count; +} + +/* Things that didn't help performance on x86: + __attribute__((aligned(128))) + #define short int + restrict +*/ + +/* Sinc_Generator( 0.9, 0.55, 4.5 ) */ +static short const bl_step [phase_count + 1] [half_width] = +{ +{ 43, -115, 350, -488, 1136, -914, 5861,21022}, +{ 44, -118, 348, -473, 1076, -799, 5274,21001}, +{ 45, -121, 344, -454, 1011, -677, 4706,20936}, +{ 46, -122, 336, -431, 942, -549, 4156,20829}, +{ 47, -123, 327, -404, 868, -418, 3629,20679}, +{ 47, -122, 316, -375, 792, -285, 3124,20488}, +{ 47, -120, 303, -344, 714, -151, 2644,20256}, +{ 46, -117, 289, -310, 634, -17, 2188,19985}, +{ 46, -114, 273, -275, 553, 117, 1758,19675}, +{ 44, -108, 255, -237, 471, 247, 1356,19327}, +{ 43, -103, 237, -199, 390, 373, 981,18944}, +{ 42, -98, 218, -160, 310, 495, 633,18527}, +{ 40, -91, 198, -121, 231, 611, 314,18078}, +{ 38, -84, 178, -81, 153, 722, 22,17599}, +{ 36, -76, 157, -43, 80, 824, -241,17092}, +{ 34, -68, 135, -3, 8, 919, -476,16558}, +{ 32, -61, 115, 34, -60, 1006, -683,16001}, +{ 29, -52, 94, 70, -123, 1083, -862,15422}, +{ 27, -44, 73, 106, -184, 1152,-1015,14824}, +{ 25, -36, 53, 139, -239, 1211,-1142,14210}, +{ 22, -27, 34, 170, -290, 1261,-1244,13582}, +{ 20, -20, 16, 199, -335, 1301,-1322,12942}, +{ 18, -12, -3, 226, -375, 1331,-1376,12293}, +{ 15, -4, -19, 250, -410, 1351,-1408,11638}, +{ 13, 3, -35, 272, -439, 1361,-1419,10979}, +{ 11, 9, -49, 292, -464, 1362,-1410,10319}, +{ 9, 16, -63, 309, -483, 1354,-1383, 9660}, +{ 7, 22, -75, 322, -496, 1337,-1339, 9005}, +{ 6, 26, -85, 333, -504, 1312,-1280, 8355}, +{ 4, 31, -94, 341, -507, 1278,-1205, 7713}, +{ 3, 35, -102, 347, -506, 1238,-1119, 7082}, +{ 1, 40, -110, 350, -499, 1190,-1021, 6464}, +{ 0, 43, -115, 350, -488, 1136, -914, 5861} +}; + +/* Shifting by pre_shift allows calculation using unsigned int rather than +possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. +And by having pre_shift 32, a 32-bit platform can easily do the shift by +simply ignoring the low half. */ + +void blip_add_delta( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int const phase_shift = frac_bits - phase_bits; + int phase = fixed >> phase_shift & (phase_count - 1); + short const* in = bl_step [phase]; + short const* rev = bl_step [phase_count - phase]; + + int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); + int delta2 = (delta * interp) >> delta_bits; + delta -= delta2; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [0] += in[0]*delta + in[half_width+0]*delta2; + out [1] += in[1]*delta + in[half_width+1]*delta2; + out [2] += in[2]*delta + in[half_width+2]*delta2; + out [3] += in[3]*delta + in[half_width+3]*delta2; + out [4] += in[4]*delta + in[half_width+4]*delta2; + out [5] += in[5]*delta + in[half_width+5]*delta2; + out [6] += in[6]*delta + in[half_width+6]*delta2; + out [7] += in[7]*delta + in[half_width+7]*delta2; + + in = rev; + out [ 8] += in[7]*delta + in[7-half_width]*delta2; + out [ 9] += in[6]*delta + in[6-half_width]*delta2; + out [10] += in[5]*delta + in[5-half_width]*delta2; + out [11] += in[4]*delta + in[4-half_width]*delta2; + out [12] += in[3]*delta + in[3-half_width]*delta2; + out [13] += in[2]*delta + in[2-half_width]*delta2; + out [14] += in[1]*delta + in[1-half_width]*delta2; + out [15] += in[0]*delta + in[0-half_width]*delta2; +} + +void blip_add_delta_fast( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); + int delta2 = delta * interp; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [7] += delta * delta_unit - delta2; + out [8] += delta2; +}
@@ -0,0 +1,72 @@
+/** \file +Sample buffer that resamples from input clock rate to output sample rate */ + +/* blip_buf 1.1.0 */ +#ifndef BLIP_BUF_H +#define BLIP_BUF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/** First parameter of most functions is blip_t*, or const blip_t* if nothing +is changed. */ +typedef struct blip_t blip_t; + +/** Creates new buffer that can hold at most sample_count samples. Sets rates +so that there are blip_max_ratio clocks per sample. Returns pointer to new +buffer, or NULL if insufficient memory. */ +blip_t* blip_new( int sample_count ); + +/** Sets approximate input clock rate and output sample rate. For every +clock_rate input clocks, approximately sample_rate samples are generated. */ +void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); + +enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, +clock_rate must not be greater than sample_rate*blip_max_ratio. */ +blip_max_ratio = 1 << 20 }; + +/** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ +void blip_clear( blip_t* ); + +/** Adds positive/negative delta into buffer at specified clock time. */ +void blip_add_delta( blip_t*, unsigned int clock_time, int delta ); + +/** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ +void blip_add_delta_fast( blip_t*, unsigned int clock_time, int delta ); + +/** Length of time frame, in clocks, needed to make sample_count additional +samples available. */ +int blip_clocks_needed( const blip_t*, int sample_count ); + +enum { /** Maximum number of samples that can be generated from one time frame. */ +blip_max_frame = 4000 }; + +/** Makes input clocks before clock_duration available for reading as output +samples. Also begins new time frame at clock_duration, so that clock time 0 in +the new time frame specifies the same clock as clock_duration in the old time +frame specified. Deltas can have been added slightly past clock_duration (up to +however many clocks there are in two output samples). */ +void blip_end_frame( blip_t*, unsigned int clock_duration ); + +/** Number of buffered samples available for reading. */ +int blip_samples_avail( const blip_t* ); + +/** Reads and removes at most 'count' samples and writes them to 'out'. If +'stereo' is true, writes output to every other element of 'out', allowing easy +interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed +samples. Returns number of samples actually read. */ +int blip_read_samples( blip_t*, short out [], int count, int stereo ); + +/** Frees buffer. No effect if NULL is passed. */ +void blip_delete( blip_t* ); + + +/* Deprecated */ +typedef blip_t blip_buffer_t; + +#ifdef __cplusplus + } +#endif + +#endif
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + +
@@ -0,0 +1,216 @@
+/* 7z.h -- 7z interface +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_H +#define __7Z_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define k7zStartHeaderSize 0x20 +#define k7zSignatureSize 6 + +extern Byte k7zSignature[k7zSignatureSize]; + +typedef struct +{ + const Byte *Data; + size_t Size; +} CSzData; + +/* CSzCoderInfo & CSzFolder support only default methods */ + +typedef struct +{ + size_t PropsOffset; + UInt32 MethodID; + Byte NumInStreams; + Byte NumOutStreams; + Byte PropsSize; +} CSzCoderInfo; + +typedef struct +{ + UInt32 InIndex; + UInt32 OutIndex; +} CSzBindPair; + +#define SZ_NUM_CODERS_IN_FOLDER_MAX 4 +#define SZ_NUM_BINDS_IN_FOLDER_MAX 3 +#define SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX 4 +#define SZ_NUM_CODERS_OUT_STREAMS_IN_FOLDER_MAX 4 + +typedef struct +{ + UInt32 NumCoders; + UInt32 NumBindPairs; + UInt32 NumPackStreams; + UInt32 MainOutStream; + UInt32 PackStreams[SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX]; + CSzBindPair BindPairs[SZ_NUM_BINDS_IN_FOLDER_MAX]; + CSzCoderInfo Coders[SZ_NUM_CODERS_IN_FOLDER_MAX]; + UInt64 CodersUnpackSizes[SZ_NUM_CODERS_OUT_STREAMS_IN_FOLDER_MAX]; +} CSzFolder; + +/* +typedef struct +{ + size_t CodersDataOffset; + size_t UnpackSizeDataOffset; + // UInt32 StartCoderUnpackSizesIndex; + UInt32 StartPackStreamIndex; + // UInt32 IndexOfMainOutStream; +} CSzFolder2; +*/ + +SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd, CSzData *sdSizes); + +typedef struct +{ + UInt32 Low; + UInt32 High; +} CNtfsFileTime; + +typedef struct +{ + Byte *Defs; /* MSB 0 bit numbering */ + UInt32 *Vals; +} CSzBitUi32s; + +typedef struct +{ + Byte *Defs; /* MSB 0 bit numbering */ + // UInt64 *Vals; + CNtfsFileTime *Vals; +} CSzBitUi64s; + +#define SzBitArray_Check(p, i) (((p)[(i) >> 3] & (0x80 >> ((i) & 7))) != 0) + +#define SzBitWithVals_Check(p, i) ((p)->Defs && ((p)->Defs[(i) >> 3] & (0x80 >> ((i) & 7))) != 0) + +typedef struct +{ + UInt32 NumPackStreams; + UInt32 NumFolders; + + UInt64 *PackPositions; // NumPackStreams + 1 + CSzBitUi32s FolderCRCs; + + size_t *FoCodersOffsets; + size_t *FoSizesOffsets; + // UInt32 StartCoderUnpackSizesIndex; + UInt32 *FoStartPackStreamIndex; + + // CSzFolder2 *Folders; // +1 item for sum values + Byte *CodersData; + Byte *UnpackSizesData; + size_t UnpackSizesDataSize; + // UInt64 *CoderUnpackSizes; +} CSzAr; + + +SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, + ILookInStream *stream, UInt64 startPos, + Byte *outBuffer, size_t outSize, + ISzAlloc *allocMain); + +/* + SzExtract extracts file from archive + + *outBuffer must be 0 before first call for each new archive. + + Extracting cache: + If you need to decompress more than one file, you can send + these values from previous call: + *blockIndex, + *outBuffer, + *outBufferSize + You can consider "*outBuffer" as cache of solid block. If your archive is solid, + it will increase decompression speed. + + If you use external function, you can declare these 3 cache variables + (blockIndex, outBuffer, outBufferSize) as static in that external function. + + Free *outBuffer and set *outBuffer to 0, if you want to flush cache. +*/ + +typedef struct +{ + CSzAr db; + + UInt64 startPosAfterHeader; + UInt64 dataPos; + + UInt32 NumFiles; + + UInt64 *UnpackPositions; + // Byte *IsEmptyFiles; + Byte *IsDirs; + CSzBitUi32s CRCs; + + CSzBitUi32s Attribs; + // CSzBitUi32s Parents; + CSzBitUi64s MTime; + CSzBitUi64s CTime; + + // UInt32 *FolderStartPackStreamIndex; + UInt32 *FolderStartFileIndex; // + 1 + UInt32 *FileIndexToFolderIndexMap; + + size_t *FileNameOffsets; /* in 2-byte steps */ + Byte *FileNames; /* UTF-16-LE */ +} CSzArEx; + +#define SzArEx_IsDir(p, i) (SzBitArray_Check((p)->IsDirs, i)) + +#define SzArEx_GetFileSize(p, i) ((p)->UnpackPositions[(i) + 1] - (p)->UnpackPositions[i]) + +void SzArEx_Init(CSzArEx *p); +void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc); +UInt64 SzArEx_GetFolderStreamPos(const CSzArEx *p, UInt32 folderIndex, UInt32 indexInFolder); +int SzArEx_GetFolderFullPackSize(const CSzArEx *p, UInt32 folderIndex, UInt64 *resSize); + +/* +if dest == NULL, the return value specifies the required size of the buffer, + in 16-bit characters, including the null-terminating character. +if dest != NULL, the return value specifies the number of 16-bit characters that + are written to the dest, including the null-terminating character. */ + +size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest); + +/* +size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex); +UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest); +*/ + +SRes SzArEx_Extract( + const CSzArEx *db, + ILookInStream *inStream, + UInt32 fileIndex, /* index of file */ + UInt32 *blockIndex, /* index of solid block */ + Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */ + size_t *outBufferSize, /* buffer size for output buffer */ + size_t *offset, /* offset of stream for required file in *outBuffer */ + size_t *outSizeProcessed, /* size of file in *outBuffer */ + ISzAlloc *allocMain, + ISzAlloc *allocTemp); + + +/* +SzArEx_Open Errors: +SZ_ERROR_NO_ARCHIVE +SZ_ERROR_ARCHIVE +SZ_ERROR_UNSUPPORTED +SZ_ERROR_MEM +SZ_ERROR_CRC +SZ_ERROR_INPUT_EOF +SZ_ERROR_FAIL +*/ + +SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, + ISzAlloc *allocMain, ISzAlloc *allocTemp); + +EXTERN_C_END + +#endif
@@ -0,0 +1,78 @@
+/* 7zAlloc.c -- Allocation functions +2010-10-29 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "7zAlloc.h" + +/* #define _SZ_ALLOC_DEBUG */ +/* use _SZ_ALLOC_DEBUG to debug alloc/free operations */ + +#ifdef _SZ_ALLOC_DEBUG + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <stdio.h> +int g_allocCount = 0; +int g_allocCountTemp = 0; + +#endif + +void *SzAlloc(void *p, size_t size) +{ + (void) p; + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + fprintf(stderr, "\nAlloc %10d bytes; count = %10d", size, g_allocCount); + g_allocCount++; + #endif + return malloc(size); +} + +void SzFree(void *p, void *address) +{ + (void) p; + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + { + g_allocCount--; + fprintf(stderr, "\nFree; count = %10d", g_allocCount); + } + #endif + free(address); +} + +void *SzAllocTemp(void *p, size_t size) +{ + (void) p; + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + fprintf(stderr, "\nAlloc_temp %10d bytes; count = %10d", size, g_allocCountTemp); + g_allocCountTemp++; + #ifdef _WIN32 + return HeapAlloc(GetProcessHeap(), 0, size); + #endif + #endif + return malloc(size); +} + +void SzFreeTemp(void *p, void *address) +{ + (void) p; + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + { + g_allocCountTemp--; + fprintf(stderr, "\nFree_temp; count = %10d", g_allocCountTemp); + } + #ifdef _WIN32 + HeapFree(GetProcessHeap(), 0, address); + return; + #endif + #endif + free(address); +}
@@ -0,0 +1,15 @@
+/* 7zAlloc.h -- Allocation functions +2010-10-29 : Igor Pavlov : Public domain */ + +#ifndef __7Z_ALLOC_H +#define __7Z_ALLOC_H + +#include <stdlib.h> + +void *SzAlloc(void *p, size_t size); +void SzFree(void *p, void *address); + +void *SzAllocTemp(void *p, size_t size); +void SzFreeTemp(void *p, void *address); + +#endif
@@ -0,0 +1,1839 @@
+/* 7zArcIn.c -- 7z Input functions +2014-06-16 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +#include "7z.h" +#include "7zBuf.h" +#include "7zCrc.h" +#include "CpuArch.h" + +#define MY_ALLOC(T, p, size, alloc) { if ((size) == 0) p = 0; else \ + if ((p = (T *)IAlloc_Alloc(alloc, (size) * sizeof(T))) == 0) return SZ_ERROR_MEM; } + +#define k7zMajorVersion 0 + +enum EIdEnum +{ + k7zIdEnd, + k7zIdHeader, + k7zIdArchiveProperties, + k7zIdAdditionalStreamsInfo, + k7zIdMainStreamsInfo, + k7zIdFilesInfo, + k7zIdPackInfo, + k7zIdUnpackInfo, + k7zIdSubStreamsInfo, + k7zIdSize, + k7zIdCRC, + k7zIdFolder, + k7zIdCodersUnpackSize, + k7zIdNumUnpackStream, + k7zIdEmptyStream, + k7zIdEmptyFile, + k7zIdAnti, + k7zIdName, + k7zIdCTime, + k7zIdATime, + k7zIdMTime, + k7zIdWinAttrib, + k7zIdComment, + k7zIdEncodedHeader, + k7zIdStartPos, + k7zIdDummy + // k7zNtSecure, + // k7zParent, + // k7zIsReal +}; + +Byte k7zSignature[k7zSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; + +#define NUM_FOLDER_CODERS_MAX 32 +#define NUM_CODER_STREAMS_MAX 32 + +/* +static int SzFolder_FindBindPairForInStream(const CSzFolder *p, UInt32 inStreamIndex) +{ + UInt32 i; + for (i = 0; i < p->NumBindPairs; i++) + if (p->BindPairs[i].InIndex == inStreamIndex) + return i; + return -1; +} +*/ + +#define SzBitUi32s_Init(p) { (p)->Defs = 0; (p)->Vals = 0; } + +static SRes SzBitUi32s_Alloc(CSzBitUi32s *p, size_t num, ISzAlloc *alloc) +{ + MY_ALLOC(Byte, p->Defs, (num + 7) >> 3, alloc); + MY_ALLOC(UInt32, p->Vals, num, alloc); + return SZ_OK; +} + +void SzBitUi32s_Free(CSzBitUi32s *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->Defs); p->Defs = 0; + IAlloc_Free(alloc, p->Vals); p->Vals = 0; +} + +#define SzBitUi64s_Init(p) { (p)->Defs = 0; (p)->Vals = 0; } + +void SzBitUi64s_Free(CSzBitUi64s *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->Defs); p->Defs = 0; + IAlloc_Free(alloc, p->Vals); p->Vals = 0; +} + +static void SzAr_Init(CSzAr *p) +{ + p->NumPackStreams = 0; + p->NumFolders = 0; + p->PackPositions = 0; + SzBitUi32s_Init(&p->FolderCRCs); + // p->Folders = 0; + p->FoCodersOffsets = 0; + p->FoSizesOffsets = 0; + p->FoStartPackStreamIndex = 0; + + p->CodersData = 0; + // p->CoderUnpackSizes = 0; + p->UnpackSizesData = 0; +} + +static void SzAr_Free(CSzAr *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->UnpackSizesData); + IAlloc_Free(alloc, p->CodersData); + // IAlloc_Free(alloc, p->CoderUnpackSizes); + + IAlloc_Free(alloc, p->PackPositions); + + // IAlloc_Free(alloc, p->Folders); + IAlloc_Free(alloc, p->FoCodersOffsets); + IAlloc_Free(alloc, p->FoSizesOffsets); + IAlloc_Free(alloc, p->FoStartPackStreamIndex); + + SzBitUi32s_Free(&p->FolderCRCs, alloc); + + SzAr_Init(p); +} + + +void SzArEx_Init(CSzArEx *p) +{ + SzAr_Init(&p->db); + p->NumFiles = 0; + p->dataPos = 0; + // p->Files = 0; + p->UnpackPositions = 0; + // p->IsEmptyFiles = 0; + p->IsDirs = 0; + // p->FolderStartPackStreamIndex = 0; + // p->PackStreamStartPositions = 0; + p->FolderStartFileIndex = 0; + p->FileIndexToFolderIndexMap = 0; + p->FileNameOffsets = 0; + p->FileNames = 0; + SzBitUi32s_Init(&p->CRCs); + SzBitUi32s_Init(&p->Attribs); + // SzBitUi32s_Init(&p->Parents); + SzBitUi64s_Init(&p->MTime); + SzBitUi64s_Init(&p->CTime); +} + +void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc) +{ + // IAlloc_Free(alloc, p->FolderStartPackStreamIndex); + // IAlloc_Free(alloc, p->PackStreamStartPositions); + IAlloc_Free(alloc, p->FolderStartFileIndex); + IAlloc_Free(alloc, p->FileIndexToFolderIndexMap); + + IAlloc_Free(alloc, p->FileNameOffsets); + IAlloc_Free(alloc, p->FileNames); + + SzBitUi64s_Free(&p->CTime, alloc); + SzBitUi64s_Free(&p->MTime, alloc); + SzBitUi32s_Free(&p->CRCs, alloc); + // SzBitUi32s_Free(&p->Parents, alloc); + SzBitUi32s_Free(&p->Attribs, alloc); + IAlloc_Free(alloc, p->IsDirs); + // IAlloc_Free(alloc, p->IsEmptyFiles); + IAlloc_Free(alloc, p->UnpackPositions); + // IAlloc_Free(alloc, p->Files); + + SzAr_Free(&p->db, alloc); + SzArEx_Init(p); +} + +static int TestSignatureCandidate(Byte *testBytes) +{ + size_t i; + for (i = 0; i < k7zSignatureSize; i++) + if (testBytes[i] != k7zSignature[i]) + return 0; + return 1; +} + +#define SzData_Clear(p) { (p)->Data = 0; (p)->Size = 0; } + +static SRes SzReadByte(CSzData *sd, Byte *b) +{ + if (sd->Size == 0) + return SZ_ERROR_ARCHIVE; + sd->Size--; + *b = *sd->Data++; + return SZ_OK; +} + +#define SZ_READ_BYTE_SD(_sd_, dest) if ((_sd_)->Size == 0) return SZ_ERROR_ARCHIVE; (_sd_)->Size--; dest = *(_sd_)->Data++; +#define SZ_READ_BYTE(dest) SZ_READ_BYTE_SD(sd, dest) +#define SZ_READ_BYTE_2(dest) if (sd.Size == 0) return SZ_ERROR_ARCHIVE; sd.Size--; dest = *sd.Data++; + +#define SKIP_DATA(sd, size) { sd->Size -= (size_t)(size); sd->Data += (size_t)(size); } +#define SKIP_DATA2(sd, size) { sd.Size -= (size_t)(size); sd.Data += (size_t)(size); } + +#define SZ_READ_32(dest) if (sd.Size < 4) return SZ_ERROR_ARCHIVE; \ + dest = GetUi32(sd.Data); SKIP_DATA2(sd, 4); + +static MY_NO_INLINE SRes ReadNumber(CSzData *sd, UInt64 *value) +{ + Byte firstByte, mask; + unsigned i; + UInt32 v; + + SZ_READ_BYTE(firstByte); + if ((firstByte & 0x80) == 0) + { + *value = firstByte; + return SZ_OK; + } + SZ_READ_BYTE(v); + if ((firstByte & 0x40) == 0) + { + *value = (((UInt32)firstByte & 0x3F) << 8) | v; + return SZ_OK; + } + SZ_READ_BYTE(mask); + *value = v | ((UInt32)mask << 8); + mask = 0x20; + for (i = 2; i < 8; i++) + { + Byte b; + if ((firstByte & mask) == 0) + { + UInt64 highPart = firstByte & (mask - 1); + *value |= (highPart << (8 * i)); + return SZ_OK; + } + SZ_READ_BYTE(b); + *value |= ((UInt64)b << (8 * i)); + mask >>= 1; + } + return SZ_OK; +} + +/* +static MY_NO_INLINE const Byte *SzReadNumbers(const Byte *data, const Byte *dataLim, UInt64 *values, UInt32 num) +{ + for (; num != 0; num--) + { + Byte firstByte; + Byte mask; + + unsigned i; + UInt32 v; + UInt64 value; + + if (data == dataLim) + return NULL; + firstByte = *data++; + + if ((firstByte & 0x80) == 0) + { + *values++ = firstByte; + continue; + } + if (data == dataLim) + return NULL; + v = *data++; + if ((firstByte & 0x40) == 0) + { + *values++ = (((UInt32)firstByte & 0x3F) << 8) | v; + continue; + } + if (data == dataLim) + return NULL; + value = v | ((UInt32)*data++ << 8); + mask = 0x20; + for (i = 2; i < 8; i++) + { + if ((firstByte & mask) == 0) + { + UInt64 highPart = firstByte & (mask - 1); + value |= (highPart << (8 * i)); + break; + } + if (data == dataLim) + return NULL; + value |= ((UInt64)*data++ << (8 * i)); + mask >>= 1; + } + *values++ = value; + } + return data; +} +*/ + +static MY_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value) +{ + Byte firstByte; + UInt64 value64; + if (sd->Size == 0) + return SZ_ERROR_ARCHIVE; + firstByte = *sd->Data; + if ((firstByte & 0x80) == 0) + { + *value = firstByte; + sd->Data++; + sd->Size--; + return SZ_OK; + } + RINOK(ReadNumber(sd, &value64)); + if (value64 >= (UInt32)0x80000000 - 1) + return SZ_ERROR_UNSUPPORTED; + if (value64 >= ((UInt64)(1) << ((sizeof(size_t) - 1) * 8 + 4))) + return SZ_ERROR_UNSUPPORTED; + *value = (UInt32)value64; + return SZ_OK; +} + +#define ReadID(sd, value) ReadNumber(sd, value) + +static SRes SkipData(CSzData *sd) +{ + UInt64 size; + RINOK(ReadNumber(sd, &size)); + if (size > sd->Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA(sd, size); + return SZ_OK; +} + +static SRes WaitId(CSzData *sd, UInt64 id) +{ + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == id) + return SZ_OK; + if (type == k7zIdEnd) + return SZ_ERROR_ARCHIVE; + RINOK(SkipData(sd)); + } +} + +static SRes RememberBitVector(CSzData *sd, UInt32 numItems, const Byte **v) +{ + UInt32 numBytes = (numItems + 7) >> 3; + if (numBytes > sd->Size) + return SZ_ERROR_ARCHIVE; + *v = sd->Data; + SKIP_DATA(sd, numBytes); + return SZ_OK; +} + +static UInt32 CountDefinedBits(const Byte *bits, UInt32 numItems) +{ + Byte b = 0; + unsigned m = 0; + UInt32 sum = 0; + for (; numItems != 0; numItems--) + { + if (m == 0) + { + b = *bits++; + m = 8; + } + m--; + sum += ((b >> m) & 1); + } + return sum ; +} + +static MY_NO_INLINE SRes ReadBitVector(CSzData *sd, UInt32 numItems, Byte **v, ISzAlloc *alloc) +{ + Byte allAreDefined; + UInt32 i; + Byte *v2; + UInt32 numBytes = (numItems + 7) >> 3; + RINOK(SzReadByte(sd, &allAreDefined)); + if (allAreDefined == 0) + { + if (numBytes > sd->Size) + return SZ_ERROR_ARCHIVE; + MY_ALLOC(Byte, *v, numBytes, alloc); + memcpy(*v, sd->Data, numBytes); + SKIP_DATA(sd, numBytes); + return SZ_OK; + } + MY_ALLOC(Byte, *v, numBytes, alloc); + v2 = *v; + for (i = 0; i < numBytes; i++) + v2[i] = 0xFF; + { + unsigned numBits = (unsigned)numItems & 7; + if (numBits != 0) + v2[numBytes - 1] = (Byte)((((UInt32)1 << numBits) - 1) << (8 - numBits)); + } + return SZ_OK; +} + +static MY_NO_INLINE SRes ReadUi32s(CSzData *sd2, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc) +{ + UInt32 i; + CSzData sd; + UInt32 *vals; + const Byte *defs; + MY_ALLOC(UInt32, crcs->Vals, numItems, alloc); + sd = *sd2; + defs = crcs->Defs; + vals = crcs->Vals; + for (i = 0; i < numItems; i++) + if (SzBitArray_Check(defs, i)) + { + SZ_READ_32(vals[i]); + } + else + vals[i] = 0; + *sd2 = sd; + return SZ_OK; +} + +static SRes ReadBitUi32s(CSzData *sd, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc) +{ + SzBitUi32s_Free(crcs, alloc); + RINOK(ReadBitVector(sd, numItems, &crcs->Defs, alloc)); + return ReadUi32s(sd, numItems, crcs, alloc); +} + +static SRes SkipBitUi32s(CSzData *sd, UInt32 numItems) +{ + Byte allAreDefined; + UInt32 numDefined = numItems; + RINOK(SzReadByte(sd, &allAreDefined)); + if (!allAreDefined) + { + size_t numBytes = (numItems + 7) >> 3; + if (numBytes > sd->Size) + return SZ_ERROR_ARCHIVE; + numDefined = CountDefinedBits(sd->Data, numItems); + SKIP_DATA(sd, numBytes); + } + if (numDefined > (sd->Size >> 2)) + return SZ_ERROR_ARCHIVE; + SKIP_DATA(sd, (size_t)numDefined * 4); + return SZ_OK; +} + +static SRes ReadPackInfo(CSzAr *p, CSzData *sd, ISzAlloc *alloc) +{ + RINOK(SzReadNumber32(sd, &p->NumPackStreams)); + + RINOK(WaitId(sd, k7zIdSize)); + MY_ALLOC(UInt64, p->PackPositions, (size_t)p->NumPackStreams + 1, alloc); + { + UInt64 sum = 0; + UInt32 i; + UInt32 numPackStreams = p->NumPackStreams; + for (i = 0; i < numPackStreams; i++) + { + UInt64 packSize; + p->PackPositions[i] = sum; + RINOK(ReadNumber(sd, &packSize)); + sum += packSize; + if (sum < packSize) + return SZ_ERROR_ARCHIVE; + } + p->PackPositions[i] = sum; + } + + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + return SZ_OK; + if (type == k7zIdCRC) + { + /* CRC of packed streams is unused now */ + RINOK(SkipBitUi32s(sd, p->NumPackStreams)); + continue; + } + RINOK(SkipData(sd)); + } +} + +/* +static SRes SzReadSwitch(CSzData *sd) +{ + Byte external; + RINOK(SzReadByte(sd, &external)); + return (external == 0) ? SZ_OK: SZ_ERROR_UNSUPPORTED; +} +*/ + +#define SZ_NUM_IN_STREAMS_IN_FOLDER_MAX 16 + +SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd, CSzData *sdSizes) +{ + UInt32 numCoders, numBindPairs, numPackStreams, i; + UInt32 numInStreams = 0, numOutStreams = 0; + const Byte *dataStart = sd->Data; + Byte inStreamUsed[SZ_NUM_IN_STREAMS_IN_FOLDER_MAX]; + + RINOK(SzReadNumber32(sd, &numCoders)); + if (numCoders > SZ_NUM_CODERS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + f->NumCoders = numCoders; + + for (i = 0; i < numCoders; i++) + { + Byte mainByte; + CSzCoderInfo *coder = f->Coders + i; + unsigned idSize, j; + UInt64 id; + RINOK(SzReadByte(sd, &mainByte)); + if ((mainByte & 0xC0) != 0) + return SZ_ERROR_UNSUPPORTED; + idSize = (unsigned)(mainByte & 0xF); + if (idSize > sizeof(id)) + return SZ_ERROR_UNSUPPORTED; + if (idSize > sd->Size) + return SZ_ERROR_ARCHIVE; + id = 0; + for (j = 0; j < idSize; j++) + { + id = ((id << 8) | *sd->Data); + sd->Data++; + sd->Size--; + } + if (id > (UInt32)0xFFFFFFFF) + return SZ_ERROR_UNSUPPORTED; + coder->MethodID = (UInt32)id; + + coder->NumInStreams = 1; + coder->NumOutStreams = 1; + coder->PropsOffset = 0; + coder->PropsSize = 0; + + if ((mainByte & 0x10) != 0) + { + UInt32 numStreams; + RINOK(SzReadNumber32(sd, &numStreams)); + if (numStreams > NUM_CODER_STREAMS_MAX) + return SZ_ERROR_UNSUPPORTED; + coder->NumInStreams = (Byte)numStreams; + RINOK(SzReadNumber32(sd, &numStreams)); + if (numStreams > NUM_CODER_STREAMS_MAX) + return SZ_ERROR_UNSUPPORTED; + coder->NumOutStreams = (Byte)numStreams; + } + if ((mainByte & 0x20) != 0) + { + UInt32 propsSize = 0; + RINOK(SzReadNumber32(sd, &propsSize)); + if (propsSize >= 0x40) + return SZ_ERROR_UNSUPPORTED; + if (propsSize > sd->Size) + return SZ_ERROR_ARCHIVE; + coder->PropsOffset = sd->Data - dataStart; + coder->PropsSize = (Byte)propsSize; + sd->Data += (size_t)propsSize; + sd->Size -= (size_t)propsSize; + } + numInStreams += coder->NumInStreams; + numOutStreams += coder->NumOutStreams; + } + + if (numOutStreams == 0) + return SZ_ERROR_UNSUPPORTED; + + f->NumBindPairs = numBindPairs = numOutStreams - 1; + if (numInStreams < numBindPairs) + return SZ_ERROR_ARCHIVE; + if (numInStreams > SZ_NUM_IN_STREAMS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + f->MainOutStream = 0; + f->NumPackStreams = numPackStreams = numInStreams - numBindPairs; + if (numPackStreams > SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + for (i = 0; i < numInStreams; i++) + inStreamUsed[i] = False; + if (numBindPairs != 0) + { + Byte outStreamUsed[SZ_NUM_CODERS_OUT_STREAMS_IN_FOLDER_MAX]; + + if (numBindPairs > SZ_NUM_BINDS_IN_FOLDER_MAX) + return SZ_ERROR_UNSUPPORTED; + + for (i = 0; i < numOutStreams; i++) + outStreamUsed[i] = False; + + for (i = 0; i < numBindPairs; i++) + { + CSzBindPair *bp = f->BindPairs + i; + RINOK(SzReadNumber32(sd, &bp->InIndex)); + if (bp->InIndex >= numInStreams) + return SZ_ERROR_ARCHIVE; + inStreamUsed[bp->InIndex] = True; + RINOK(SzReadNumber32(sd, &bp->OutIndex)); + if (bp->OutIndex >= numInStreams) + return SZ_ERROR_ARCHIVE; + outStreamUsed[bp->OutIndex] = True; + } + for (i = 0; i < numOutStreams; i++) + if (!outStreamUsed[i]) + { + f->MainOutStream = i; + break; + } + if (i == numOutStreams) + return SZ_ERROR_ARCHIVE; + } + + if (numPackStreams == 1) + { + for (i = 0; i < numInStreams; i++) + if (!inStreamUsed[i]) + break; + if (i == numInStreams) + return SZ_ERROR_ARCHIVE; + f->PackStreams[0] = i; + } + else + for (i = 0; i < numPackStreams; i++) + { + RINOK(SzReadNumber32(sd, f->PackStreams + i)); + } + + for (i = 0; i < numOutStreams; i++) + { + RINOK(ReadNumber(sdSizes, f->CodersUnpackSizes + i)); + } + + return SZ_OK; +} + +static MY_NO_INLINE SRes SkipNumbers(CSzData *sd2, UInt32 num) +{ + CSzData sd; + sd = *sd2; + for (; num != 0; num--) + { + Byte firstByte, mask; + unsigned i; + SZ_READ_BYTE_2(firstByte); + if ((firstByte & 0x80) == 0) + continue; + if ((firstByte & 0x40) == 0) + { + if (sd.Size == 0) + return SZ_ERROR_ARCHIVE; + sd.Size--; + sd.Data++; + continue; + } + mask = 0x20; + for (i = 2; i < 8 && (firstByte & mask) != 0; i++) + mask >>= 1; + if (i > sd.Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA2(sd, i); + } + *sd2 = sd; + return SZ_OK; +} + +#define k_InStreamUsed_MAX 64 +#define k_OutStreamUsed_MAX 64 + +static SRes ReadUnpackInfo(CSzAr *p, + CSzData *sd2, + UInt32 numFoldersMax, const CBuf *tempBufs, UInt32 numTempBufs, + ISzAlloc *alloc) +{ + CSzData sd; + Byte inStreamUsed[k_InStreamUsed_MAX]; + Byte outStreamUsed[k_OutStreamUsed_MAX]; + UInt32 fo, numFolders, numCodersOutStreams, packStreamIndex; + const Byte *startBufPtr; + Byte external; + + RINOK(WaitId(sd2, k7zIdFolder)); + RINOK(SzReadNumber32(sd2, &numFolders)); + if (p->NumFolders > numFoldersMax) + return SZ_ERROR_UNSUPPORTED; + p->NumFolders = numFolders; + + SZ_READ_BYTE_SD(sd2, external); + if (external == 0) + sd = *sd2; + else + { + UInt32 index; + SzReadNumber32(sd2, &index); + if (index >= numTempBufs) + return SZ_ERROR_ARCHIVE; + sd.Data = tempBufs[index].data; + sd.Size = tempBufs[index].size; + } + + MY_ALLOC(size_t, p->FoCodersOffsets, (size_t)numFolders + 1, alloc); + MY_ALLOC(size_t, p->FoSizesOffsets, (size_t)numFolders + 1, alloc); + MY_ALLOC(UInt32, p->FoStartPackStreamIndex, (size_t)numFolders + 1, alloc); + + startBufPtr = sd.Data; + + packStreamIndex = 0; + numCodersOutStreams = 0; + + for (fo = 0; fo < numFolders; fo++) + { + UInt32 numCoders, ci, numInStreams = 0, numOutStreams = 0; + + p->FoCodersOffsets[fo] = sd.Data - startBufPtr; + RINOK(SzReadNumber32(&sd, &numCoders)); + if (numCoders > NUM_FOLDER_CODERS_MAX) + return SZ_ERROR_UNSUPPORTED; + + for (ci = 0; ci < numCoders; ci++) + { + Byte mainByte; + unsigned idSize; + UInt32 coderInStreams, coderOutStreams; + + SZ_READ_BYTE_2(mainByte); + if ((mainByte & 0xC0) != 0) + return SZ_ERROR_UNSUPPORTED; + idSize = (mainByte & 0xF); + if (idSize > 8) + return SZ_ERROR_UNSUPPORTED; + if (idSize > sd.Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA2(sd, idSize); + + coderInStreams = 1; + coderOutStreams = 1; + if ((mainByte & 0x10) != 0) + { + RINOK(SzReadNumber32(&sd, &coderInStreams)); + RINOK(SzReadNumber32(&sd, &coderOutStreams)); + if (coderInStreams > NUM_CODER_STREAMS_MAX || + coderOutStreams > NUM_CODER_STREAMS_MAX) + return SZ_ERROR_UNSUPPORTED; + } + numInStreams += coderInStreams; + numOutStreams += coderOutStreams; + if ((mainByte & 0x20) != 0) + { + UInt32 propsSize; + RINOK(SzReadNumber32(&sd, &propsSize)); + if (propsSize > sd.Size) + return SZ_ERROR_ARCHIVE; + SKIP_DATA2(sd, propsSize); + } + } + + { + UInt32 indexOfMainStream = 0; + UInt32 numPackStreams = 1; + if (numOutStreams != 1 || numInStreams != 1) + { + UInt32 i; + UInt32 numBindPairs = numOutStreams - 1; + if (numOutStreams == 0 || numInStreams < numBindPairs) + return SZ_ERROR_ARCHIVE; + + if (numInStreams > k_InStreamUsed_MAX || + numOutStreams > k_OutStreamUsed_MAX) + return SZ_ERROR_UNSUPPORTED; + + for (i = 0; i < numInStreams; i++) + inStreamUsed[i] = False; + for (i = 0; i < numOutStreams; i++) + outStreamUsed[i] = False; + + for (i = 0; i < numBindPairs; i++) + { + UInt32 index; + RINOK(SzReadNumber32(&sd, &index)); + if (index >= numInStreams || inStreamUsed[index]) + return SZ_ERROR_ARCHIVE; + inStreamUsed[index] = True; + RINOK(SzReadNumber32(&sd, &index)); + if (index >= numInStreams || outStreamUsed[index]) + return SZ_ERROR_ARCHIVE; + outStreamUsed[index] = True; + } + + numPackStreams = numInStreams - numBindPairs; + + if (numPackStreams != 1) + for (i = 0; i < numPackStreams; i++) + { + UInt32 temp; + RINOK(SzReadNumber32(&sd, &temp)); + if (temp >= numInStreams) + return SZ_ERROR_ARCHIVE; + } + + for (i = 0; i < numOutStreams; i++) + if (!outStreamUsed[i]) + { + indexOfMainStream = i; + break; + } + + if (i == numOutStreams) + return SZ_ERROR_ARCHIVE; + } + p->FoStartPackStreamIndex[fo] = packStreamIndex; + p->FoSizesOffsets[fo] = (numOutStreams << 8) | indexOfMainStream; + numCodersOutStreams += numOutStreams; + if (numCodersOutStreams < numOutStreams) + return SZ_ERROR_UNSUPPORTED; + packStreamIndex += numPackStreams; + if (packStreamIndex < numPackStreams) + return SZ_ERROR_UNSUPPORTED; + if (packStreamIndex > p->NumPackStreams) + return SZ_ERROR_ARCHIVE; + } + } + + { + size_t dataSize = sd.Data - startBufPtr; + p->FoStartPackStreamIndex[fo] = packStreamIndex; + p->FoCodersOffsets[fo] = dataSize; + MY_ALLOC(Byte, p->CodersData, dataSize, alloc); + memcpy(p->CodersData, startBufPtr, dataSize); + } + + if (external != 0) + { + if (sd.Size != 0) + return SZ_ERROR_ARCHIVE; + sd = *sd2; + } + + RINOK(WaitId(&sd, k7zIdCodersUnpackSize)); + + // MY_ALLOC(UInt64, p->CoderUnpackSizes, (size_t)numCodersOutStreams, alloc); + { + size_t dataSize = sd.Size; + /* + UInt32 i; + for (i = 0; i < numCodersOutStreams; i++) + { + RINOK(ReadNumber(&sd, p->CoderUnpackSizes + i)); + } + */ + RINOK(SkipNumbers(&sd, numCodersOutStreams)); + dataSize -= sd.Size; + MY_ALLOC(Byte, p->UnpackSizesData, dataSize, alloc); + memcpy(p->UnpackSizesData, sd.Data - dataSize, dataSize); + p->UnpackSizesDataSize = dataSize; + /* + const Byte *data = SzReadNumbers(sd.Data, sd.Data + sd.Size, p->CoderUnpackSizes, numCodersOutStreams); + if (data == NULL) + return SZ_ERROR_ARCHIVE; + sd.Size = sd.Data + sd.Size - data; + sd.Data = data; + */ + } + + for (;;) + { + UInt64 type; + RINOK(ReadID(&sd, &type)); + if (type == k7zIdEnd) + { + *sd2 = sd; + return SZ_OK; + } + if (type == k7zIdCRC) + { + RINOK(ReadBitUi32s(&sd, numFolders, &p->FolderCRCs, alloc)); + continue; + } + RINOK(SkipData(&sd)); + } +} + +typedef struct +{ + UInt32 NumTotalSubStreams; + UInt32 NumSubDigests; + CSzData sdNumSubStreams; + CSzData sdSizes; + CSzData sdCRCs; +} CSubStreamInfo; + +#define SzUi32IndexMax (((UInt32)1 << 31) - 2) + +static SRes ReadSubStreamsInfo(CSzAr *p, CSzData *sd, CSubStreamInfo *ssi) +{ + UInt64 type = 0; + UInt32 i; + UInt32 numSubDigests = 0; + UInt32 numFolders = p->NumFolders; + UInt32 numUnpackStreams = numFolders; + UInt32 numUnpackSizesInData = 0; + + for (;;) + { + RINOK(ReadID(sd, &type)); + if (type == k7zIdNumUnpackStream) + { + ssi->sdNumSubStreams.Data = sd->Data; + numUnpackStreams = 0; + numSubDigests = 0; + for (i = 0; i < numFolders; i++) + { + UInt32 numStreams; + RINOK(SzReadNumber32(sd, &numStreams)); + if (numUnpackStreams > numUnpackStreams + numStreams) + return SZ_ERROR_UNSUPPORTED; + numUnpackStreams += numStreams; + if (numStreams != 0) + numUnpackSizesInData += (numStreams - 1); + if (numStreams != 1 || !SzBitWithVals_Check(&p->FolderCRCs, i)) + numSubDigests += numStreams; + } + ssi->sdNumSubStreams.Size = sd->Data - ssi->sdNumSubStreams.Data; + continue; + } + if (type == k7zIdCRC || type == k7zIdSize || type == k7zIdEnd) + break; + RINOK(SkipData(sd)); + } + + if (!ssi->sdNumSubStreams.Data) + { + numSubDigests = numFolders; + if (p->FolderCRCs.Defs) + numSubDigests = numFolders - CountDefinedBits(p->FolderCRCs.Defs, numFolders); + } + + ssi->NumTotalSubStreams = numUnpackStreams; + ssi->NumSubDigests = numSubDigests; + + if (type == k7zIdSize) + { + ssi->sdSizes.Data = sd->Data; + RINOK(SkipNumbers(sd, numUnpackSizesInData)); + ssi->sdSizes.Size = sd->Data - ssi->sdSizes.Data; + RINOK(ReadID(sd, &type)); + } + + for (;;) + { + if (type == k7zIdEnd) + return SZ_OK; + if (type == k7zIdCRC) + { + ssi->sdCRCs.Data = sd->Data; + RINOK(SkipBitUi32s(sd, numSubDigests)); + ssi->sdCRCs.Size = sd->Data - ssi->sdCRCs.Data; + } + else + { + RINOK(SkipData(sd)); + } + RINOK(ReadID(sd, &type)); + } +} + +static SRes SzReadStreamsInfo(CSzAr *p, + CSzData *sd, + UInt32 numFoldersMax, const CBuf *tempBufs, UInt32 numTempBufs, + UInt64 *dataOffset, + CSubStreamInfo *ssi, + ISzAlloc *alloc) +{ + UInt64 type; + + SzData_Clear(&ssi->sdSizes); + SzData_Clear(&ssi->sdCRCs); + SzData_Clear(&ssi->sdNumSubStreams); + + *dataOffset = 0; + RINOK(ReadID(sd, &type)); + if (type == k7zIdPackInfo) + { + RINOK(ReadNumber(sd, dataOffset)); + RINOK(ReadPackInfo(p, sd, alloc)); + RINOK(ReadID(sd, &type)); + } + if (type == k7zIdUnpackInfo) + { + RINOK(ReadUnpackInfo(p, sd, numFoldersMax, tempBufs, numTempBufs, alloc)); + RINOK(ReadID(sd, &type)); + } + if (type == k7zIdSubStreamsInfo) + { + RINOK(ReadSubStreamsInfo(p, sd, ssi)); + RINOK(ReadID(sd, &type)); + } + else + { + ssi->NumTotalSubStreams = p->NumFolders; + // ssi->NumSubDigests = 0; + } + + return (type == k7zIdEnd ? SZ_OK : SZ_ERROR_UNSUPPORTED); +} + +static SRes SzReadAndDecodePackedStreams( + ILookInStream *inStream, + CSzData *sd, + CBuf *tempBufs, + UInt32 numFoldersMax, + UInt64 baseOffset, + CSzAr *p, + ISzAlloc *allocTemp) +{ + UInt64 dataStartPos; + UInt32 fo; + CSubStreamInfo ssi; + CSzData sdCodersUnpSizes; + + RINOK(SzReadStreamsInfo(p, sd, numFoldersMax, NULL, 0, &dataStartPos, &ssi, allocTemp)); + + dataStartPos += baseOffset; + if (p->NumFolders == 0) + return SZ_ERROR_ARCHIVE; + + sdCodersUnpSizes.Data = p->UnpackSizesData; + sdCodersUnpSizes.Size = p->UnpackSizesDataSize; + for (fo = 0; fo < p->NumFolders; fo++) + Buf_Init(tempBufs + fo); + for (fo = 0; fo < p->NumFolders; fo++) + { + CBuf *tempBuf = tempBufs + fo; + // folder = p->Folders; + // unpackSize = SzAr_GetFolderUnpackSize(p, 0); + UInt32 mix = (UInt32)p->FoSizesOffsets[fo]; + UInt32 mainIndex = mix & 0xFF; + UInt32 numOutStreams = mix >> 8; + UInt32 si; + UInt64 unpackSize = 0; + p->FoSizesOffsets[fo] = sdCodersUnpSizes.Data - p->UnpackSizesData; + for (si = 0; si < numOutStreams; si++) + { + UInt64 curSize; + RINOK(ReadNumber(&sdCodersUnpSizes, &curSize)); + if (si == mainIndex) + { + unpackSize = curSize; + break; + } + } + if (si == numOutStreams) + return SZ_ERROR_FAIL; + if ((size_t)unpackSize != unpackSize) + return SZ_ERROR_MEM; + if (!Buf_Create(tempBuf, (size_t)unpackSize, allocTemp)) + return SZ_ERROR_MEM; + } + p->FoSizesOffsets[fo] = sdCodersUnpSizes.Data - p->UnpackSizesData; + + for (fo = 0; fo < p->NumFolders; fo++) + { + const CBuf *tempBuf = tempBufs + fo; + RINOK(LookInStream_SeekTo(inStream, dataStartPos)); + RINOK(SzAr_DecodeFolder(p, fo, inStream, dataStartPos, tempBuf->data, tempBuf->size, allocTemp)); + if (SzBitWithVals_Check(&p->FolderCRCs, fo)) + if (CrcCalc(tempBuf->data, tempBuf->size) != p->FolderCRCs.Vals[fo]) + return SZ_ERROR_CRC; + } + return SZ_OK; +} + +static SRes SzReadFileNames(const Byte *data, size_t size, UInt32 numFiles, size_t *offsets) +{ + size_t pos = 0; + *offsets++ = 0; + if (numFiles == 0) + return (size == 0) ? SZ_OK : SZ_ERROR_ARCHIVE; + if (data[size - 2] != 0 || data[size - 1] != 0) + return SZ_ERROR_ARCHIVE; + do + { + const Byte *p; + if (pos == size) + return SZ_ERROR_ARCHIVE; + for (p = data + pos; + #ifdef _WIN32 + *(const UInt16 *)p != 0 + #else + p[0] != 0 || p[1] != 0 + #endif + ; p += 2); + pos = p - data + 2; + *offsets++ = (pos >> 1); + } + while (--numFiles); + return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE; +} + +static MY_NO_INLINE SRes ReadTime(CSzBitUi64s *p, UInt32 num, + CSzData *sd2, + const CBuf *tempBufs, UInt32 numTempBufs, + ISzAlloc *alloc) +{ + CSzData sd; + UInt32 i; + CNtfsFileTime *vals; + Byte *defs; + Byte external; + RINOK(ReadBitVector(sd2, num, &p->Defs, alloc)); + RINOK(SzReadByte(sd2, &external)); + if (external == 0) + sd = *sd2; + else + { + UInt32 index; + SzReadNumber32(sd2, &index); + if (index >= numTempBufs) + return SZ_ERROR_ARCHIVE; + sd.Data = tempBufs[index].data; + sd.Size = tempBufs[index].size; + } + MY_ALLOC(CNtfsFileTime, p->Vals, num, alloc); + vals = p->Vals; + defs = p->Defs; + for (i = 0; i < num; i++) + if (SzBitArray_Check(defs, i)) + { + if (sd.Size < 8) + return SZ_ERROR_ARCHIVE; + vals[i].Low = GetUi32(sd.Data); + vals[i].High = GetUi32(sd.Data + 4); + SKIP_DATA2(sd, 8); + } + else + vals[i].High = vals[i].Low = 0; + if (external == 0) + *sd2 = sd; + return SZ_OK; +} + +#define NUM_ADDITIONAL_STREAMS_MAX 8 + +static SRes SzReadHeader2( + CSzArEx *p, /* allocMain */ + CSzData *sd, + // Byte **emptyStreamVector, /* allocTemp */ + // Byte **emptyFileVector, /* allocTemp */ + // Byte **lwtVector, /* allocTemp */ + ILookInStream *inStream, + CBuf *tempBufs, + UInt32 *numTempBufs, + ISzAlloc *allocMain, + ISzAlloc *allocTemp + ) +{ + UInt64 type; + UInt32 numFiles = 0; + UInt32 numEmptyStreams = 0; + UInt32 i; + CSubStreamInfo ssi; + const Byte *emptyStreams = 0; + const Byte *emptyFiles = 0; + + SzData_Clear(&ssi.sdSizes); + SzData_Clear(&ssi.sdCRCs); + SzData_Clear(&ssi.sdNumSubStreams); + + ssi.NumSubDigests = 0; + ssi.NumTotalSubStreams = 0; + + RINOK(ReadID(sd, &type)); + + if (type == k7zIdArchiveProperties) + { + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + break; + RINOK(SkipData(sd)); + } + RINOK(ReadID(sd, &type)); + } + + // if (type == k7zIdAdditionalStreamsInfo) return SZ_ERROR_UNSUPPORTED; + + if (type == k7zIdAdditionalStreamsInfo) + { + CSzAr tempAr; + SRes res; + UInt32 numTempFolders; + + SzAr_Init(&tempAr); + res = SzReadAndDecodePackedStreams(inStream, sd, tempBufs, NUM_ADDITIONAL_STREAMS_MAX, + p->startPosAfterHeader, &tempAr, allocTemp); + numTempFolders = tempAr.NumFolders; + SzAr_Free(&tempAr, allocTemp); + if (res != SZ_OK) + return res; + *numTempBufs = numTempFolders; + RINOK(ReadID(sd, &type)); + } + + if (type == k7zIdMainStreamsInfo) + { + RINOK(SzReadStreamsInfo(&p->db, sd, (UInt32)1 << 30, tempBufs, *numTempBufs, + &p->dataPos, &ssi, allocMain)); + p->dataPos += p->startPosAfterHeader; + RINOK(ReadID(sd, &type)); + } + + if (type == k7zIdEnd) + { + // *sd2 = sd; + return SZ_OK; + } + if (type != k7zIdFilesInfo) + return SZ_ERROR_ARCHIVE; + + RINOK(SzReadNumber32(sd, &numFiles)); + p->NumFiles = numFiles; + + for (;;) + { + UInt64 type; + UInt64 size; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + break; + RINOK(ReadNumber(sd, &size)); + if (size > sd->Size) + return SZ_ERROR_ARCHIVE; + if ((UInt64)(int)type != type) + { + SKIP_DATA(sd, size); + } + else switch((int)type) + { + case k7zIdName: + { + size_t namesSize; + const Byte *namesData; + Byte external; + + SZ_READ_BYTE(external); + if (external == 0) + { + namesSize = (size_t)size - 1; + namesData = sd->Data; + } + else + { + UInt32 index; + SzReadNumber32(sd, &index); + if (index >= *numTempBufs) + return SZ_ERROR_ARCHIVE; + namesData = (tempBufs)[index].data; + namesSize = (tempBufs)[index].size; + } + + if ((namesSize & 1) != 0) + return SZ_ERROR_ARCHIVE; + MY_ALLOC(Byte, p->FileNames, namesSize, allocMain); + MY_ALLOC(size_t, p->FileNameOffsets, numFiles + 1, allocMain); + memcpy(p->FileNames, namesData, namesSize); + RINOK(SzReadFileNames(p->FileNames, namesSize, numFiles, p->FileNameOffsets)) + if (external == 0) + { + SKIP_DATA(sd, namesSize); + } + break; + } + case k7zIdEmptyStream: + { + RINOK(RememberBitVector(sd, numFiles, &emptyStreams)); + numEmptyStreams = CountDefinedBits(emptyStreams, numFiles); + break; + } + case k7zIdEmptyFile: + { + RINOK(RememberBitVector(sd, numEmptyStreams, &emptyFiles)); + break; + } + case k7zIdWinAttrib: + { + Byte external; + CSzData sdSwitch; + CSzData *sdPtr; + SzBitUi32s_Free(&p->Attribs, allocMain); + RINOK(ReadBitVector(sd, numFiles, &p->Attribs.Defs, allocMain)); + + SZ_READ_BYTE(external); + if (external == 0) + sdPtr = sd; + else + { + UInt32 index; + SzReadNumber32(sd, &index); + if (index >= *numTempBufs) + return SZ_ERROR_ARCHIVE; + sdSwitch.Data = (tempBufs)[index].data; + sdSwitch.Size = (tempBufs)[index].size; + sdPtr = &sdSwitch; + } + RINOK(ReadUi32s(sdPtr, numFiles, &p->Attribs, allocMain)); + break; + } + /* + case k7zParent: + { + SzBitUi32s_Free(&p->Parents, allocMain); + RINOK(ReadBitVector(sd, numFiles, &p->Parents.Defs, allocMain)); + RINOK(SzReadSwitch(sd)); + RINOK(ReadUi32s(sd, numFiles, &p->Parents, allocMain)); + break; + } + */ + case k7zIdMTime: RINOK(ReadTime(&p->MTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break; + case k7zIdCTime: RINOK(ReadTime(&p->CTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break; + default: + { + SKIP_DATA(sd, size); + } + } + } + + if (numFiles - numEmptyStreams != ssi.NumTotalSubStreams) + return SZ_ERROR_ARCHIVE; + + for (;;) + { + UInt64 type; + RINOK(ReadID(sd, &type)); + if (type == k7zIdEnd) + break; + RINOK(SkipData(sd)); + } + + { + UInt32 emptyFileIndex = 0; + + UInt32 folderIndex = 0; + UInt32 indexInFolder = 0; + UInt64 unpackPos = 0; + const Byte *digestsDefs = 0; + const Byte *digestsVals = 0; + UInt32 digestsValsIndex = 0; + UInt32 digestIndex; + Byte allDigestsDefined = 0; + UInt32 curNumSubStreams = (UInt32)(Int32)-1; + Byte isDirMask = 0; + Byte crcMask = 0; + Byte mask = 0x80; + // size_t unpSizesOffset = 0; + CSzData sdCodersUnpSizes; + sdCodersUnpSizes.Data = p->db.UnpackSizesData; + sdCodersUnpSizes.Size = p->db.UnpackSizesDataSize; + + MY_ALLOC(UInt32, p->FolderStartFileIndex, p->db.NumFolders + 1, allocMain); + MY_ALLOC(UInt32, p->FileIndexToFolderIndexMap, p->NumFiles, allocMain); + MY_ALLOC(UInt64, p->UnpackPositions, p->NumFiles + 1, allocMain); + MY_ALLOC(Byte, p->IsDirs, (p->NumFiles + 7) >> 3, allocMain); + + RINOK(SzBitUi32s_Alloc(&p->CRCs, p->NumFiles, allocMain)); + + if (ssi.sdCRCs.Size != 0) + { + RINOK(SzReadByte(&ssi.sdCRCs, &allDigestsDefined)); + if (allDigestsDefined) + digestsVals = ssi.sdCRCs.Data; + else + { + size_t numBytes = (ssi.NumSubDigests + 7) >> 3; + digestsDefs = ssi.sdCRCs.Data; + digestsVals = digestsDefs + numBytes; + } + } + + digestIndex = 0; + for (i = 0; i < numFiles; i++, mask >>= 1) + { + if (mask == 0) + { + UInt32 byteIndex = (i - 1) >> 3; + p->IsDirs[byteIndex] = isDirMask; + p->CRCs.Defs[byteIndex] = crcMask; + isDirMask = 0; + crcMask = 0; + mask = 0x80; + } + + p->UnpackPositions[i] = unpackPos; + p->CRCs.Vals[i] = 0; + // p->CRCs.Defs[i] = 0; + if (emptyStreams && SzBitArray_Check(emptyStreams , i)) + { + if (!emptyFiles || !SzBitArray_Check(emptyFiles, emptyFileIndex)) + isDirMask |= mask; + emptyFileIndex++; + if (indexInFolder == 0) + { + p->FileIndexToFolderIndexMap[i] = (UInt32)-1; + continue; + } + } + if (indexInFolder == 0) + { + /* + v3.13 incorrectly worked with empty folders + v4.07: Loop for skipping empty folders + */ + for (;;) + { + if (folderIndex >= p->db.NumFolders) + return SZ_ERROR_ARCHIVE; + p->FolderStartFileIndex[folderIndex] = i; + if (curNumSubStreams == (UInt32)(Int32)-1) + { + curNumSubStreams = 1; + if (ssi.sdNumSubStreams.Data != 0) + { + RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &curNumSubStreams)); + } + } + if (curNumSubStreams != 0) + break; + curNumSubStreams = (UInt32)(Int32)-1; + folderIndex++; // check it + } + } + p->FileIndexToFolderIndexMap[i] = folderIndex; + if (emptyStreams && SzBitArray_Check(emptyStreams , i)) + continue; + + indexInFolder++; + if (indexInFolder >= curNumSubStreams) + { + UInt64 folderUnpackSize = 0; + UInt64 startFolderUnpackPos; + { + UInt32 mix = (UInt32)p->db.FoSizesOffsets[folderIndex]; + UInt32 mainIndex = mix & 0xFF; + UInt32 numOutStreams = mix >> 8; + UInt32 si; + p->db.FoSizesOffsets[folderIndex] = sdCodersUnpSizes.Data - p->db.UnpackSizesData; + for (si = 0; si < numOutStreams; si++) + { + UInt64 curSize; + RINOK(ReadNumber(&sdCodersUnpSizes, &curSize)); + if (si == mainIndex) + { + folderUnpackSize = curSize; + break; + } + } + if (si == numOutStreams) + return SZ_ERROR_FAIL; + } + + // UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + startFolderUnpackPos = p->UnpackPositions[p->FolderStartFileIndex[folderIndex]]; + if (folderUnpackSize < unpackPos - startFolderUnpackPos) + return SZ_ERROR_ARCHIVE; + unpackPos = startFolderUnpackPos + folderUnpackSize; + + if (curNumSubStreams == 1 && SzBitWithVals_Check(&p->db.FolderCRCs, i)) + { + p->CRCs.Vals[i] = p->db.FolderCRCs.Vals[folderIndex]; + crcMask |= mask; + } + else if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex))) + { + p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4); + digestsValsIndex++; + crcMask |= mask; + } + folderIndex++; + indexInFolder = 0; + } + else + { + UInt64 v; + RINOK(ReadNumber(&ssi.sdSizes, &v)); + unpackPos += v; + if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex))) + { + p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4); + digestsValsIndex++; + crcMask |= mask; + } + } + } + if (mask != 0x80) + { + UInt32 byteIndex = (i - 1) >> 3; + p->IsDirs[byteIndex] = isDirMask; + p->CRCs.Defs[byteIndex] = crcMask; + } + p->UnpackPositions[i] = unpackPos; + p->FolderStartFileIndex[folderIndex] = i; + p->db.FoSizesOffsets[folderIndex] = sdCodersUnpSizes.Data - p->db.UnpackSizesData; + } + return SZ_OK; +} + +static SRes SzReadHeader( + CSzArEx *p, + CSzData *sd, + ILookInStream *inStream, + ISzAlloc *allocMain + ,ISzAlloc *allocTemp + ) +{ + // Byte *emptyStreamVector = 0; + // Byte *emptyFileVector = 0; + // Byte *lwtVector = 0; + UInt32 i; + UInt32 numTempBufs = 0; + SRes res; + CBuf tempBufs[NUM_ADDITIONAL_STREAMS_MAX]; + + for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++) + Buf_Init(tempBufs + i); + // SzBitUi32s_Init(&digests); + + res = SzReadHeader2(p, sd, + // &emptyStreamVector, + // &emptyFileVector, + // &lwtVector, + inStream, + tempBufs, &numTempBufs, + allocMain, allocTemp + ); + + for (i = 0; i < numTempBufs; i++) + Buf_Free(tempBufs + i, allocTemp); + + // IAlloc_Free(allocTemp, emptyStreamVector); + // IAlloc_Free(allocTemp, emptyFileVector); + // IAlloc_Free(allocTemp, lwtVector); + + RINOK(res); + { + if (sd->Size != 0) + return SZ_ERROR_FAIL; + } + + return res; +} + +/* +static UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex) +{ + const CSzFolder2 *f = p->Folders + folderIndex; + + // return p->CoderUnpackSizes[f->StartCoderUnpackSizesIndex + f->IndexOfMainOutStream]; + + UInt32 si; + CSzData sdCodersUnpSizes; + sdCodersUnpSizes.Data = p->UnpackSizesData + f->UnpackSizeDataOffset; + sdCodersUnpSizes.Size = p->UnpackSizesDataSize - f->UnpackSizeDataOffset; + for (si = 0; si < numOutStreams; si++) + { + UInt64 curSize; + ReadNumber(&sdCodersUnpSizes, &curSize); + if (si == mainIndex) + return curSize; + } + return 0; +} +*/ + +static SRes SzArEx_Open2( + CSzArEx *p, + ILookInStream *inStream, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + Byte header[k7zStartHeaderSize]; + Int64 startArcPos; + UInt64 nextHeaderOffset, nextHeaderSize; + size_t nextHeaderSizeT; + UInt32 nextHeaderCRC; + CBuf buf; + SRes res; + + startArcPos = 0; + RINOK(inStream->Seek(inStream, &startArcPos, SZ_SEEK_CUR)); + + RINOK(LookInStream_Read2(inStream, header, k7zStartHeaderSize, SZ_ERROR_NO_ARCHIVE)); + + if (!TestSignatureCandidate(header)) + return SZ_ERROR_NO_ARCHIVE; + if (header[6] != k7zMajorVersion) + return SZ_ERROR_UNSUPPORTED; + + nextHeaderOffset = GetUi64(header + 12); + nextHeaderSize = GetUi64(header + 20); + nextHeaderCRC = GetUi32(header + 28); + + p->startPosAfterHeader = startArcPos + k7zStartHeaderSize; + + if (CrcCalc(header + 12, 20) != GetUi32(header + 8)) + return SZ_ERROR_CRC; + + nextHeaderSizeT = (size_t)nextHeaderSize; + if (nextHeaderSizeT != nextHeaderSize) + return SZ_ERROR_MEM; + if (nextHeaderSizeT == 0) + return SZ_OK; + if (nextHeaderOffset > nextHeaderOffset + nextHeaderSize || + nextHeaderOffset > nextHeaderOffset + nextHeaderSize + k7zStartHeaderSize) + return SZ_ERROR_NO_ARCHIVE; + + { + Int64 pos = 0; + RINOK(inStream->Seek(inStream, &pos, SZ_SEEK_END)); + if ((UInt64)pos < startArcPos + nextHeaderOffset || + (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset || + (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize) + return SZ_ERROR_INPUT_EOF; + } + + RINOK(LookInStream_SeekTo(inStream, startArcPos + k7zStartHeaderSize + nextHeaderOffset)); + + if (!Buf_Create(&buf, nextHeaderSizeT, allocTemp)) + return SZ_ERROR_MEM; + + res = LookInStream_Read(inStream, buf.data, nextHeaderSizeT); + if (res == SZ_OK) + { + res = SZ_ERROR_ARCHIVE; + if (CrcCalc(buf.data, nextHeaderSizeT) == nextHeaderCRC) + { + CSzData sd; + UInt64 type; + sd.Data = buf.data; + sd.Size = buf.size; + res = ReadID(&sd, &type); + if (res == SZ_OK && type == k7zIdEncodedHeader) + { + CSzAr tempAr; + CBuf tempBuf; + Buf_Init(&tempBuf); + + SzAr_Init(&tempAr); + res = SzReadAndDecodePackedStreams(inStream, &sd, &tempBuf, 1, p->startPosAfterHeader, &tempAr, allocTemp); + SzAr_Free(&tempAr, allocTemp); + + if (res != SZ_OK) + { + Buf_Free(&tempBuf, allocTemp); + } + else + { + Buf_Free(&buf, allocTemp); + buf.data = tempBuf.data; + buf.size = tempBuf.size; + sd.Data = buf.data; + sd.Size = buf.size; + res = ReadID(&sd, &type); + } + } + if (res == SZ_OK) + { + if (type == k7zIdHeader) + { + CSzData sd2; + int ttt; + for (ttt = 0; ttt < 1; ttt++) + // for (ttt = 0; ttt < 40000; ttt++) + { + SzArEx_Free(p, allocMain); + sd2 = sd; + res = SzReadHeader(p, &sd2, inStream, allocMain, allocTemp + ); + if (res != SZ_OK) + break; + } + + // res = SzReadHeader(p, &sd, allocMain, allocTemp); + } + else + res = SZ_ERROR_UNSUPPORTED; + } + } + } + Buf_Free(&buf, allocTemp); + return res; +} + +// #include <stdio.h> + +SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, + ISzAlloc *allocMain, ISzAlloc *allocTemp) +{ + SRes res = SzArEx_Open2(p, inStream, allocMain, allocTemp); + if (res != SZ_OK) + SzArEx_Free(p, allocMain); + // printf ("\nrrr=%d\n", rrr); + return res; +} + +SRes SzArEx_Extract( + const CSzArEx *p, + ILookInStream *inStream, + UInt32 fileIndex, + UInt32 *blockIndex, + Byte **tempBuf, + size_t *outBufferSize, + size_t *offset, + size_t *outSizeProcessed, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + UInt32 folderIndex = p->FileIndexToFolderIndexMap[fileIndex]; + SRes res = SZ_OK; + *offset = 0; + *outSizeProcessed = 0; + if (folderIndex == (UInt32)-1) + { + IAlloc_Free(allocMain, *tempBuf); + *blockIndex = folderIndex; + *tempBuf = 0; + *outBufferSize = 0; + return SZ_OK; + } + + if (*tempBuf == 0 || *blockIndex != folderIndex) + { + // UInt64 unpackSizeSpec = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + UInt64 unpackSizeSpec = + p->UnpackPositions[p->FolderStartFileIndex[folderIndex + 1]] - + p->UnpackPositions[p->FolderStartFileIndex[folderIndex]]; + size_t unpackSize = (size_t)unpackSizeSpec; + + if (unpackSize != unpackSizeSpec) + return SZ_ERROR_MEM; + *blockIndex = folderIndex; + IAlloc_Free(allocMain, *tempBuf); + *tempBuf = 0; + + // RINOK(LookInStream_SeekTo(inStream, startOffset)); + + if (res == SZ_OK) + { + *outBufferSize = unpackSize; + if (unpackSize != 0) + { + *tempBuf = (Byte *)IAlloc_Alloc(allocMain, unpackSize); + if (*tempBuf == 0) + res = SZ_ERROR_MEM; + } + if (res == SZ_OK) + { + res = SzAr_DecodeFolder(&p->db, folderIndex, + inStream, + p->dataPos, + *tempBuf, unpackSize, allocTemp); + if (res == SZ_OK) + { + if (SzBitWithVals_Check(&p->db.FolderCRCs, folderIndex)) + { + if (CrcCalc(*tempBuf, unpackSize) != p->db.FolderCRCs.Vals[folderIndex]) + res = SZ_ERROR_CRC; + } + } + } + } + } + if (res == SZ_OK) + { + UInt64 unpackPos = p->UnpackPositions[fileIndex]; + *offset = (size_t)(unpackPos - p->UnpackPositions[p->FolderStartFileIndex[folderIndex]]); + *outSizeProcessed = (size_t)(p->UnpackPositions[fileIndex + 1] - unpackPos); + if (*offset + *outSizeProcessed > *outBufferSize) + return SZ_ERROR_FAIL; + if (SzBitWithVals_Check(&p->CRCs, fileIndex) && CrcCalc(*tempBuf + *offset, *outSizeProcessed) != p->CRCs.Vals[fileIndex]) + res = SZ_ERROR_CRC; + } + return res; +} + + +size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest) +{ + size_t offs = p->FileNameOffsets[fileIndex]; + size_t len = p->FileNameOffsets[fileIndex + 1] - offs; + if (dest != 0) + { + size_t i; + const Byte *src = p->FileNames + offs * 2; + for (i = 0; i < len; i++) + dest[i] = GetUi16(src + i * 2); + } + return len; +} + +/* +size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex) +{ + size_t len; + if (!p->FileNameOffsets) + return 1; + len = 0; + for (;;) + { + UInt32 parent = (UInt32)(Int32)-1; + len += p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; + if SzBitWithVals_Check(&p->Parents, fileIndex) + parent = p->Parents.Vals[fileIndex]; + if (parent == (UInt32)(Int32)-1) + return len; + fileIndex = parent; + } +} + +UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest) +{ + Bool needSlash; + if (!p->FileNameOffsets) + { + *(--dest) = 0; + return dest; + } + needSlash = False; + for (;;) + { + UInt32 parent = (UInt32)(Int32)-1; + size_t curLen = p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; + SzArEx_GetFileNameUtf16(p, fileIndex, dest - curLen); + if (needSlash) + *(dest - 1) = '/'; + needSlash = True; + dest -= curLen; + + if SzBitWithVals_Check(&p->Parents, fileIndex) + parent = p->Parents.Vals[fileIndex]; + if (parent == (UInt32)(Int32)-1) + return dest; + fileIndex = parent; + } +} +*/
@@ -0,0 +1,36 @@
+/* 7zBuf.c -- Byte Buffer +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "7zBuf.h" + +void Buf_Init(CBuf *p) +{ + p->data = 0; + p->size = 0; +} + +int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc) +{ + p->size = 0; + if (size == 0) + { + p->data = 0; + return 1; + } + p->data = (Byte *)alloc->Alloc(alloc, size); + if (p->data != 0) + { + p->size = size; + return 1; + } + return 0; +} + +void Buf_Free(CBuf *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->data); + p->data = 0; + p->size = 0; +}
@@ -0,0 +1,35 @@
+/* 7zBuf.h -- Byte Buffer +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_BUF_H +#define __7Z_BUF_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +typedef struct +{ + Byte *data; + size_t size; +} CBuf; + +void Buf_Init(CBuf *p); +int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc); +void Buf_Free(CBuf *p, ISzAlloc *alloc); + +typedef struct +{ + Byte *data; + size_t size; + size_t pos; +} CDynBuf; + +void DynBuf_Construct(CDynBuf *p); +void DynBuf_SeekToBeg(CDynBuf *p); +int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAlloc *alloc); +void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc); + +EXTERN_C_END + +#endif
@@ -0,0 +1,48 @@
+/* 7zBuf2.c -- Byte Buffer +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +#include "7zBuf.h" + +void DynBuf_Construct(CDynBuf *p) +{ + p->data = 0; + p->size = 0; + p->pos = 0; +} + +void DynBuf_SeekToBeg(CDynBuf *p) +{ + p->pos = 0; +} + +int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAlloc *alloc) +{ + if (size > p->size - p->pos) + { + size_t newSize = p->pos + size; + Byte *data; + newSize += newSize / 4; + data = (Byte *)alloc->Alloc(alloc, newSize); + if (data == 0) + return 0; + p->size = newSize; + memcpy(data, p->data, p->pos); + alloc->Free(alloc, p->data); + p->data = data; + } + memcpy(p->data + p->pos, buf, size); + p->pos += size; + return 1; +} + +void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->data); + p->data = 0; + p->size = 0; + p->pos = 0; +}
@@ -0,0 +1,85 @@
+/* 7zCrc.c -- CRC32 init +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "7zCrc.h" +#include "CpuArch.h" + +#define kCrcPoly 0xEDB88320 + +#ifdef MY_CPU_X86_OR_AMD64 + #define CRC_NUM_TABLES 8 + UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table); +#elif defined(MY_CPU_LE) + #define CRC_NUM_TABLES 4 +#else + #define CRC_NUM_TABLES 5 + #define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24)) + UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif + +#ifndef MY_CPU_BE + UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif + +typedef UInt32 (MY_FAST_CALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table); + +CRC_FUNC g_CrcUpdate; +UInt32 g_CrcTable[256 * CRC_NUM_TABLES]; + +UInt32 MY_FAST_CALL CrcUpdate(UInt32 v, const void *data, size_t size) +{ + return g_CrcUpdate(v, data, size, g_CrcTable); +} + +UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size) +{ + return g_CrcUpdate(CRC_INIT_VAL, data, size, g_CrcTable) ^ CRC_INIT_VAL; +} + +void MY_FAST_CALL CrcGenerateTable() +{ + UInt32 i; + for (i = 0; i < 256; i++) + { + UInt32 r = i; + unsigned j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + g_CrcTable[i] = r; + } + for (; i < 256 * CRC_NUM_TABLES; i++) + { + UInt32 r = g_CrcTable[i - 256]; + g_CrcTable[i] = g_CrcTable[r & 0xFF] ^ (r >> 8); + } + + #ifdef MY_CPU_LE + + g_CrcUpdate = CrcUpdateT4; + + #if CRC_NUM_TABLES == 8 + if (!CPU_Is_InOrder()) + g_CrcUpdate = CrcUpdateT8; + #endif + + #else + { + #ifndef MY_CPU_BE + UInt32 k = 1; + if (*(const Byte *)&k == 1) + g_CrcUpdate = CrcUpdateT4; + else + #endif + { + for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--) + { + UInt32 x = g_CrcTable[i - 256]; + g_CrcTable[i] = CRC_UINT32_SWAP(x); + } + g_CrcUpdate = CrcUpdateT1_BeT4; + } + } + #endif +}
@@ -0,0 +1,25 @@
+/* 7zCrc.h -- CRC32 calculation +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_CRC_H +#define __7Z_CRC_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +extern UInt32 g_CrcTable[]; + +/* Call CrcGenerateTable one time before other CRC functions */ +void MY_FAST_CALL CrcGenerateTable(void); + +#define CRC_INIT_VAL 0xFFFFFFFF +#define CRC_GET_DIGEST(crc) ((crc) ^ CRC_INIT_VAL) +#define CRC_UPDATE_BYTE(crc, b) (g_CrcTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +UInt32 MY_FAST_CALL CrcUpdate(UInt32 crc, const void *data, size_t size); +UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size); + +EXTERN_C_END + +#endif
@@ -0,0 +1,66 @@
+/* 7zCrcOpt.c -- CRC32 calculation +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "CpuArch.h" + +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +#ifndef MY_CPU_BE + +UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + for (; size >= 4; size -= 4, p += 4) + { + v ^= *(const UInt32 *)p; + v = + table[0x300 + (v & 0xFF)] ^ + table[0x200 + ((v >> 8) & 0xFF)] ^ + table[0x100 + ((v >> 16) & 0xFF)] ^ + table[0x000 + ((v >> 24))]; + } + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + return CrcUpdateT4(v, data, size, table); +} + +#endif + + +#ifndef MY_CPU_LE + +#define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24)) + +UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + v = CRC_UINT32_SWAP(v); + table += 0x100; + for (; size >= 4; size -= 4, p += 4) + { + v ^= *(const UInt32 *)p; + v = + table[0x000 + (v & 0xFF)] ^ + table[0x100 + ((v >> 8) & 0xFF)] ^ + table[0x200 + ((v >> 16) & 0xFF)] ^ + table[0x300 + ((v >> 24))]; + } + table -= 0x100; + v = CRC_UINT32_SWAP(v); + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +#endif
@@ -0,0 +1,493 @@
+/* 7zDec.c -- Decoding from 7z folder +2014-06-16 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +/* #define _7ZIP_PPMD_SUPPPORT */ + +#include "7z.h" + +#include "Bcj2.h" +#include "Bra.h" +#include "CpuArch.h" +#include "LzmaDec.h" +#include "Lzma2Dec.h" +#ifdef _7ZIP_PPMD_SUPPPORT +#include "Ppmd7.h" +#endif + +#define k_Copy 0 +#define k_LZMA2 0x21 +#define k_LZMA 0x30101 +#define k_BCJ 0x03030103 +#define k_PPC 0x03030205 +#define k_ARM 0x03030501 +#define k_ARMT 0x03030701 +#define k_SPARC 0x03030805 +#define k_BCJ2 0x0303011B + +#ifdef _7ZIP_PPMD_SUPPPORT + +#define k_PPMD 0x30401 + +typedef struct +{ + IByteIn p; + const Byte *cur; + const Byte *end; + const Byte *begin; + UInt64 processed; + Bool extra; + SRes res; + ILookInStream *inStream; +} CByteInToLook; + +static Byte ReadByte(void *pp) +{ + CByteInToLook *p = (CByteInToLook *)pp; + if (p->cur != p->end) + return *p->cur++; + if (p->res == SZ_OK) + { + size_t size = p->cur - p->begin; + p->processed += size; + p->res = p->inStream->Skip(p->inStream, size); + size = (1 << 25); + p->res = p->inStream->Look(p->inStream, (const void **)&p->begin, &size); + p->cur = p->begin; + p->end = p->begin + size; + if (size != 0) + return *p->cur++;; + } + p->extra = True; + return 0; +} + +static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CPpmd7 ppmd; + CByteInToLook s; + SRes res = SZ_OK; + + s.p.Read = ReadByte; + s.inStream = inStream; + s.begin = s.end = s.cur = NULL; + s.extra = False; + s.res = SZ_OK; + s.processed = 0; + + if (propsSize != 5) + return SZ_ERROR_UNSUPPORTED; + + { + unsigned order = props[0]; + UInt32 memSize = GetUi32(props + 1); + if (order < PPMD7_MIN_ORDER || + order > PPMD7_MAX_ORDER || + memSize < PPMD7_MIN_MEM_SIZE || + memSize > PPMD7_MAX_MEM_SIZE) + return SZ_ERROR_UNSUPPORTED; + Ppmd7_Construct(&ppmd); + if (!Ppmd7_Alloc(&ppmd, memSize, allocMain)) + return SZ_ERROR_MEM; + Ppmd7_Init(&ppmd, order); + } + { + CPpmd7z_RangeDec rc; + Ppmd7z_RangeDec_CreateVTable(&rc); + rc.Stream = &s.p; + if (!Ppmd7z_RangeDec_Init(&rc)) + res = SZ_ERROR_DATA; + else if (s.extra) + res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); + else + { + SizeT i; + for (i = 0; i < outSize; i++) + { + int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.p); + if (s.extra || sym < 0) + break; + outBuffer[i] = (Byte)sym; + } + if (i != outSize) + res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); + else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc)) + res = SZ_ERROR_DATA; + } + } + Ppmd7_Free(&ppmd, allocMain); + return res; +} + +#endif + + +static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CLzmaDec state; + SRes res = SZ_OK; + + LzmaDec_Construct(&state); + RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain)); + state.dic = outBuffer; + state.dicBufSize = outSize; + LzmaDec_Init(&state); + + for (;;) + { + Byte *inBuf = NULL; + size_t lookahead = (1 << 18); + if (lookahead > inSize) + lookahead = (size_t)inSize; + res = inStream->Look((void *)inStream, (const void **)&inBuf, &lookahead); + if (res != SZ_OK) + break; + + { + SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos; + ELzmaStatus status; + res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + lookahead -= inProcessed; + inSize -= inProcessed; + if (res != SZ_OK) + break; + if (state.dicPos == state.dicBufSize || (inProcessed == 0 && dicPos == state.dicPos)) + { + if (state.dicBufSize != outSize || lookahead != 0 || + (status != LZMA_STATUS_FINISHED_WITH_MARK && + status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)) + res = SZ_ERROR_DATA; + break; + } + res = inStream->Skip((void *)inStream, inProcessed); + if (res != SZ_OK) + break; + } + } + + LzmaDec_FreeProbs(&state, allocMain); + return res; +} + +static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CLzma2Dec state; + SRes res = SZ_OK; + + Lzma2Dec_Construct(&state); + if (propsSize != 1) + return SZ_ERROR_DATA; + RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain)); + state.decoder.dic = outBuffer; + state.decoder.dicBufSize = outSize; + Lzma2Dec_Init(&state); + + for (;;) + { + Byte *inBuf = NULL; + size_t lookahead = (1 << 18); + if (lookahead > inSize) + lookahead = (size_t)inSize; + res = inStream->Look((void *)inStream, (const void **)&inBuf, &lookahead); + if (res != SZ_OK) + break; + + { + SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos; + ELzmaStatus status; + res = Lzma2Dec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + lookahead -= inProcessed; + inSize -= inProcessed; + if (res != SZ_OK) + break; + if (state.decoder.dicPos == state.decoder.dicBufSize || (inProcessed == 0 && dicPos == state.decoder.dicPos)) + { + if (state.decoder.dicBufSize != outSize || lookahead != 0 || + (status != LZMA_STATUS_FINISHED_WITH_MARK)) + res = SZ_ERROR_DATA; + break; + } + res = inStream->Skip((void *)inStream, inProcessed); + if (res != SZ_OK) + break; + } + } + + Lzma2Dec_FreeProbs(&state, allocMain); + return res; +} + +static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer) +{ + while (inSize > 0) + { + void *inBuf; + size_t curSize = (1 << 18); + if (curSize > inSize) + curSize = (size_t)inSize; + RINOK(inStream->Look((void *)inStream, (const void **)&inBuf, &curSize)); + if (curSize == 0) + return SZ_ERROR_INPUT_EOF; + memcpy(outBuffer, inBuf, curSize); + outBuffer += curSize; + inSize -= curSize; + RINOK(inStream->Skip((void *)inStream, curSize)); + } + return SZ_OK; +} + +static Bool IS_MAIN_METHOD(UInt32 m) +{ + switch (m) + { + case k_Copy: + case k_LZMA: + case k_LZMA2: + #ifdef _7ZIP_PPMD_SUPPPORT + case k_PPMD: + #endif + return True; + } + return False; +} + +static Bool IS_SUPPORTED_CODER(const CSzCoderInfo *c) +{ + return + c->NumInStreams == 1 && + c->NumOutStreams == 1 && + /* c->MethodID <= (UInt32)0xFFFFFFFF && */ + IS_MAIN_METHOD((UInt32)c->MethodID); +} + +#define IS_BCJ2(c) ((c)->MethodID == k_BCJ2 && (c)->NumInStreams == 4 && (c)->NumOutStreams == 1) + +static SRes CheckSupportedFolder(const CSzFolder *f) +{ + if (f->NumCoders < 1 || f->NumCoders > 4) + return SZ_ERROR_UNSUPPORTED; + if (!IS_SUPPORTED_CODER(&f->Coders[0])) + return SZ_ERROR_UNSUPPORTED; + if (f->NumCoders == 1) + { + if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBindPairs != 0) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + if (f->NumCoders == 2) + { + const CSzCoderInfo *c = &f->Coders[1]; + if ( + /* c->MethodID > (UInt32)0xFFFFFFFF || */ + c->NumInStreams != 1 || + c->NumOutStreams != 1 || + f->NumPackStreams != 1 || + f->PackStreams[0] != 0 || + f->NumBindPairs != 1 || + f->BindPairs[0].InIndex != 1 || + f->BindPairs[0].OutIndex != 0) + return SZ_ERROR_UNSUPPORTED; + switch ((UInt32)c->MethodID) + { + case k_BCJ: + case k_ARM: + break; + default: + return SZ_ERROR_UNSUPPORTED; + } + return SZ_OK; + } + if (f->NumCoders == 4) + { + if (!IS_SUPPORTED_CODER(&f->Coders[1]) || + !IS_SUPPORTED_CODER(&f->Coders[2]) || + !IS_BCJ2(&f->Coders[3])) + return SZ_ERROR_UNSUPPORTED; + if (f->NumPackStreams != 4 || + f->PackStreams[0] != 2 || + f->PackStreams[1] != 6 || + f->PackStreams[2] != 1 || + f->PackStreams[3] != 0 || + f->NumBindPairs != 3 || + f->BindPairs[0].InIndex != 5 || f->BindPairs[0].OutIndex != 0 || + f->BindPairs[1].InIndex != 4 || f->BindPairs[1].OutIndex != 1 || + f->BindPairs[2].InIndex != 3 || f->BindPairs[2].OutIndex != 2) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + return SZ_ERROR_UNSUPPORTED; +} + +#define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break; + +static SRes SzFolder_Decode2(const CSzFolder *folder, + const Byte *propsData, + const UInt64 *unpackSizes, + const UInt64 *packPositions, + ILookInStream *inStream, UInt64 startPos, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain, + Byte *tempBuf[]) +{ + UInt32 ci; + SizeT tempSizes[3] = { 0, 0, 0}; + SizeT tempSize3 = 0; + Byte *tempBuf3 = 0; + + RINOK(CheckSupportedFolder(folder)); + + for (ci = 0; ci < folder->NumCoders; ci++) + { + const CSzCoderInfo *coder = &folder->Coders[ci]; + + if (IS_MAIN_METHOD((UInt32)coder->MethodID)) + { + UInt32 si = 0; + UInt64 offset; + UInt64 inSize; + Byte *outBufCur = outBuffer; + SizeT outSizeCur = outSize; + if (folder->NumCoders == 4) + { + UInt32 indices[] = { 3, 2, 0 }; + UInt64 unpackSize = unpackSizes[ci]; + si = indices[ci]; + if (ci < 2) + { + Byte *temp; + outSizeCur = (SizeT)unpackSize; + if (outSizeCur != unpackSize) + return SZ_ERROR_MEM; + temp = (Byte *)IAlloc_Alloc(allocMain, outSizeCur); + if (temp == 0 && outSizeCur != 0) + return SZ_ERROR_MEM; + outBufCur = tempBuf[1 - ci] = temp; + tempSizes[1 - ci] = outSizeCur; + } + else if (ci == 2) + { + if (unpackSize > outSize) /* check it */ + return SZ_ERROR_PARAM; + tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize); + tempSize3 = outSizeCur = (SizeT)unpackSize; + } + else + return SZ_ERROR_UNSUPPORTED; + } + offset = packPositions[si]; + inSize = packPositions[si + 1] - offset; + RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + + if (coder->MethodID == k_Copy) + { + if (inSize != outSizeCur) /* check it */ + return SZ_ERROR_DATA; + RINOK(SzDecodeCopy(inSize, inStream, outBufCur)); + } + else if (coder->MethodID == k_LZMA) + { + RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + } + else if (coder->MethodID == k_LZMA2) + { + RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + } + else + { + #ifdef _7ZIP_PPMD_SUPPPORT + RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + #else + return SZ_ERROR_UNSUPPORTED; + #endif + } + } + else if (coder->MethodID == k_BCJ2) + { + UInt64 offset = packPositions[1]; + UInt64 s3Size = packPositions[2] - offset; + SRes res; + if (ci != 3) + return SZ_ERROR_UNSUPPORTED; + RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + tempSizes[2] = (SizeT)s3Size; + if (tempSizes[2] != s3Size) + return SZ_ERROR_MEM; + tempBuf[2] = (Byte *)IAlloc_Alloc(allocMain, tempSizes[2]); + if (tempBuf[2] == 0 && tempSizes[2] != 0) + return SZ_ERROR_MEM; + res = SzDecodeCopy(s3Size, inStream, tempBuf[2]); + RINOK(res) + + res = Bcj2_Decode( + tempBuf3, tempSize3, + tempBuf[0], tempSizes[0], + tempBuf[1], tempSizes[1], + tempBuf[2], tempSizes[2], + outBuffer, outSize); + RINOK(res) + } + else + { + if (ci != 1) + return SZ_ERROR_UNSUPPORTED; + switch (coder->MethodID) + { + case k_BCJ: + { + UInt32 state; + x86_Convert_Init(state); + x86_Convert(outBuffer, outSize, 0, &state, 0); + break; + } + CASE_BRA_CONV(ARM) + default: + return SZ_ERROR_UNSUPPORTED; + } + } + } + return SZ_OK; +} + +SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, + ILookInStream *inStream, UInt64 startPos, + Byte *outBuffer, size_t outSize, + ISzAlloc *allocMain) +{ + SRes res; + CSzFolder folder; + CSzData sd; + CSzData sdSizes; + + const Byte *data = p->CodersData + p->FoCodersOffsets[folderIndex]; + sd.Data = data; + sd.Size = p->FoCodersOffsets[folderIndex + 1] - p->FoCodersOffsets[folderIndex]; + + sdSizes.Data = p->UnpackSizesData + p->FoSizesOffsets[folderIndex]; + sdSizes.Size = + p->FoSizesOffsets[folderIndex + 1] - + p->FoSizesOffsets[folderIndex]; + + res = SzGetNextFolderItem(&folder, &sd, &sdSizes); + + if (res != SZ_OK) + return res; + + if (sd.Size != 0 || outSize != folder.CodersUnpackSizes[folder.MainOutStream]) + return SZ_ERROR_FAIL; + { + int i; + Byte *tempBuf[3] = { 0, 0, 0}; + res = SzFolder_Decode2(&folder, data, folder.CodersUnpackSizes, + p->PackPositions + p->FoStartPackStreamIndex[folderIndex], + inStream, startPos, + outBuffer, (SizeT)outSize, allocMain, tempBuf); + for (i = 0; i < 3; i++) + IAlloc_Free(allocMain, tempBuf[i]); + return res; + } +}
@@ -0,0 +1,286 @@
+/* 7zFile.c -- File IO +2009-11-24 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "7zFile.h" + +#ifndef USE_WINDOWS_FILE + +#ifndef UNDER_CE +#include <errno.h> +#endif + +#else + +/* + ReadFile and WriteFile functions in Windows have BUG: + If you Read or Write 64MB or more (probably min_failure_size = 64MB - 32KB + 1) + from/to Network file, it returns ERROR_NO_SYSTEM_RESOURCES + (Insufficient system resources exist to complete the requested service). + Probably in some version of Windows there are problems with other sizes: + for 32 MB (maybe also for 16 MB). + And message can be "Network connection was lost" +*/ + +#define kChunkSizeMax (1 << 22) + +#endif + +void File_Construct(CSzFile *p) +{ + #ifdef USE_WINDOWS_FILE + p->handle = INVALID_HANDLE_VALUE; + #else + p->file = NULL; + #endif +} + +#if !defined(UNDER_CE) || !defined(USE_WINDOWS_FILE) +static WRes File_Open(CSzFile *p, const char *name, int writeMode) +{ + #ifdef USE_WINDOWS_FILE + p->handle = CreateFileA(name, + writeMode ? GENERIC_WRITE : GENERIC_READ, + FILE_SHARE_READ, NULL, + writeMode ? CREATE_ALWAYS : OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + return (p->handle != INVALID_HANDLE_VALUE) ? 0 : GetLastError(); + #else + p->file = fopen(name, writeMode ? "wb+" : "rb"); + return (p->file != 0) ? 0 : + #ifdef UNDER_CE + 2; /* ENOENT */ + #else + errno; + #endif + #endif +} + +WRes InFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 0); } +WRes OutFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 1); } +#endif + +#ifdef USE_WINDOWS_FILE +static WRes File_OpenW(CSzFile *p, const WCHAR *name, int writeMode) +{ + p->handle = CreateFileW(name, + writeMode ? GENERIC_WRITE : GENERIC_READ, + FILE_SHARE_READ, NULL, + writeMode ? CREATE_ALWAYS : OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + return (p->handle != INVALID_HANDLE_VALUE) ? 0 : GetLastError(); +} +WRes InFile_OpenW(CSzFile *p, const WCHAR *name) { return File_OpenW(p, name, 0); } +WRes OutFile_OpenW(CSzFile *p, const WCHAR *name) { return File_OpenW(p, name, 1); } +#endif + +WRes File_Close(CSzFile *p) +{ + #ifdef USE_WINDOWS_FILE + if (p->handle != INVALID_HANDLE_VALUE) + { + if (!CloseHandle(p->handle)) + return GetLastError(); + p->handle = INVALID_HANDLE_VALUE; + } + #else + if (p->file != NULL) + { + int res = fclose(p->file); + if (res != 0) + return res; + p->file = NULL; + } + #endif + return 0; +} + +WRes File_Read(CSzFile *p, void *data, size_t *size) +{ + size_t originalSize = *size; + if (originalSize == 0) + return 0; + + #ifdef USE_WINDOWS_FILE + + *size = 0; + do + { + DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize; + DWORD processed = 0; + BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL); + data = (void *)((Byte *)data + processed); + originalSize -= processed; + *size += processed; + if (!res) + return GetLastError(); + if (processed == 0) + break; + } + while (originalSize > 0); + return 0; + + #else + + *size = fread(data, 1, originalSize, p->file); + if (*size == originalSize) + return 0; + return ferror(p->file); + + #endif +} + +WRes File_Write(CSzFile *p, const void *data, size_t *size) +{ + size_t originalSize = *size; + if (originalSize == 0) + return 0; + + #ifdef USE_WINDOWS_FILE + + *size = 0; + do + { + DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize; + DWORD processed = 0; + BOOL res = WriteFile(p->handle, data, curSize, &processed, NULL); + data = (void *)((Byte *)data + processed); + originalSize -= processed; + *size += processed; + if (!res) + return GetLastError(); + if (processed == 0) + break; + } + while (originalSize > 0); + return 0; + + #else + + *size = fwrite(data, 1, originalSize, p->file); + if (*size == originalSize) + return 0; + return ferror(p->file); + + #endif +} + +WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin) +{ + #ifdef USE_WINDOWS_FILE + + LARGE_INTEGER value; + DWORD moveMethod; + value.LowPart = (DWORD)*pos; + value.HighPart = (LONG)((UInt64)*pos >> 16 >> 16); /* for case when UInt64 is 32-bit only */ + switch (origin) + { + case SZ_SEEK_SET: moveMethod = FILE_BEGIN; break; + case SZ_SEEK_CUR: moveMethod = FILE_CURRENT; break; + case SZ_SEEK_END: moveMethod = FILE_END; break; + default: return ERROR_INVALID_PARAMETER; + } + value.LowPart = SetFilePointer(p->handle, value.LowPart, &value.HighPart, moveMethod); + if (value.LowPart == 0xFFFFFFFF) + { + WRes res = GetLastError(); + if (res != NO_ERROR) + return res; + } + *pos = ((Int64)value.HighPart << 32) | value.LowPart; + return 0; + + #else + + int moveMethod; + int res; + switch (origin) + { + case SZ_SEEK_SET: moveMethod = SEEK_SET; break; + case SZ_SEEK_CUR: moveMethod = SEEK_CUR; break; + case SZ_SEEK_END: moveMethod = SEEK_END; break; + default: return 1; + } + res = fseek(p->file, (long)*pos, moveMethod); + *pos = ftell(p->file); + return res; + + #endif +} + +WRes File_GetLength(CSzFile *p, UInt64 *length) +{ + #ifdef USE_WINDOWS_FILE + + DWORD sizeHigh; + DWORD sizeLow = GetFileSize(p->handle, &sizeHigh); + if (sizeLow == 0xFFFFFFFF) + { + DWORD res = GetLastError(); + if (res != NO_ERROR) + return res; + } + *length = (((UInt64)sizeHigh) << 32) + sizeLow; + return 0; + + #else + + long pos = ftell(p->file); + int res = fseek(p->file, 0, SEEK_END); + *length = ftell(p->file); + fseek(p->file, pos, SEEK_SET); + return res; + + #endif +} + + +/* ---------- FileSeqInStream ---------- */ + +static SRes FileSeqInStream_Read(void *pp, void *buf, size_t *size) +{ + CFileSeqInStream *p = (CFileSeqInStream *)pp; + return File_Read(&p->file, buf, size) == 0 ? SZ_OK : SZ_ERROR_READ; +} + +void FileSeqInStream_CreateVTable(CFileSeqInStream *p) +{ + p->s.Read = FileSeqInStream_Read; +} + + +/* ---------- FileInStream ---------- */ + +static SRes FileInStream_Read(void *pp, void *buf, size_t *size) +{ + CFileInStream *p = (CFileInStream *)pp; + return (File_Read(&p->file, buf, size) == 0) ? SZ_OK : SZ_ERROR_READ; +} + +static SRes FileInStream_Seek(void *pp, Int64 *pos, ESzSeek origin) +{ + CFileInStream *p = (CFileInStream *)pp; + return File_Seek(&p->file, pos, origin); +} + +void FileInStream_CreateVTable(CFileInStream *p) +{ + p->s.Read = FileInStream_Read; + p->s.Seek = FileInStream_Seek; +} + + +/* ---------- FileOutStream ---------- */ + +static size_t FileOutStream_Write(void *pp, const void *data, size_t size) +{ + CFileOutStream *p = (CFileOutStream *)pp; + File_Write(&p->file, data, &size); + return size; +} + +void FileOutStream_CreateVTable(CFileOutStream *p) +{ + p->s.Write = FileOutStream_Write; +}
@@ -0,0 +1,83 @@
+/* 7zFile.h -- File IO +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __7Z_FILE_H +#define __7Z_FILE_H + +#ifdef _WIN32 +#define USE_WINDOWS_FILE +#endif + +#ifdef USE_WINDOWS_FILE +#include <windows.h> +#else +#include <stdio.h> +#endif + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +/* ---------- File ---------- */ + +typedef struct +{ + #ifdef USE_WINDOWS_FILE + HANDLE handle; + #else + FILE *file; + #endif +} CSzFile; + +void File_Construct(CSzFile *p); +#if !defined(UNDER_CE) || !defined(USE_WINDOWS_FILE) +WRes InFile_Open(CSzFile *p, const char *name); +WRes OutFile_Open(CSzFile *p, const char *name); +#endif +#ifdef USE_WINDOWS_FILE +WRes InFile_OpenW(CSzFile *p, const WCHAR *name); +WRes OutFile_OpenW(CSzFile *p, const WCHAR *name); +#endif +WRes File_Close(CSzFile *p); + +/* reads max(*size, remain file's size) bytes */ +WRes File_Read(CSzFile *p, void *data, size_t *size); + +/* writes *size bytes */ +WRes File_Write(CSzFile *p, const void *data, size_t *size); + +WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin); +WRes File_GetLength(CSzFile *p, UInt64 *length); + + +/* ---------- FileInStream ---------- */ + +typedef struct +{ + ISeqInStream s; + CSzFile file; +} CFileSeqInStream; + +void FileSeqInStream_CreateVTable(CFileSeqInStream *p); + + +typedef struct +{ + ISeekInStream s; + CSzFile file; +} CFileInStream; + +void FileInStream_CreateVTable(CFileInStream *p); + + +typedef struct +{ + ISeqOutStream s; + CSzFile file; +} CFileOutStream; + +void FileOutStream_CreateVTable(CFileOutStream *p); + +EXTERN_C_END + +#endif
@@ -0,0 +1,171 @@
+/* 7zStream.c -- 7z Stream functions +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +#include "7zTypes.h" + +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType) +{ + while (size != 0) + { + size_t processed = size; + RINOK(stream->Read(stream, buf, &processed)); + if (processed == 0) + return errorType; + buf = (void *)((Byte *)buf + processed); + size -= processed; + } + return SZ_OK; +} + +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size) +{ + return SeqInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); +} + +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf) +{ + size_t processed = 1; + RINOK(stream->Read(stream, buf, &processed)); + return (processed == 1) ? SZ_OK : SZ_ERROR_INPUT_EOF; +} + +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset) +{ + Int64 t = offset; + return stream->Seek(stream, &t, SZ_SEEK_SET); +} + +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size) +{ + const void *lookBuf; + if (*size == 0) + return SZ_OK; + RINOK(stream->Look(stream, &lookBuf, size)); + memcpy(buf, lookBuf, *size); + return stream->Skip(stream, *size); +} + +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType) +{ + while (size != 0) + { + size_t processed = size; + RINOK(stream->Read(stream, buf, &processed)); + if (processed == 0) + return errorType; + buf = (void *)((Byte *)buf + processed); + size -= processed; + } + return SZ_OK; +} + +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size) +{ + return LookInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); +} + +static SRes LookToRead_Look_Lookahead(void *pp, const void **buf, size_t *size) +{ + SRes res = SZ_OK; + CLookToRead *p = (CLookToRead *)pp; + size_t size2 = p->size - p->pos; + if (size2 == 0 && *size > 0) + { + p->pos = 0; + size2 = LookToRead_BUF_SIZE; + res = p->realStream->Read(p->realStream, p->buf, &size2); + p->size = size2; + } + if (size2 < *size) + *size = size2; + *buf = p->buf + p->pos; + return res; +} + +static SRes LookToRead_Look_Exact(void *pp, const void **buf, size_t *size) +{ + SRes res = SZ_OK; + CLookToRead *p = (CLookToRead *)pp; + size_t size2 = p->size - p->pos; + if (size2 == 0 && *size > 0) + { + p->pos = 0; + if (*size > LookToRead_BUF_SIZE) + *size = LookToRead_BUF_SIZE; + res = p->realStream->Read(p->realStream, p->buf, size); + size2 = p->size = *size; + } + if (size2 < *size) + *size = size2; + *buf = p->buf + p->pos; + return res; +} + +static SRes LookToRead_Skip(void *pp, size_t offset) +{ + CLookToRead *p = (CLookToRead *)pp; + p->pos += offset; + return SZ_OK; +} + +static SRes LookToRead_Read(void *pp, void *buf, size_t *size) +{ + CLookToRead *p = (CLookToRead *)pp; + size_t rem = p->size - p->pos; + if (rem == 0) + return p->realStream->Read(p->realStream, buf, size); + if (rem > *size) + rem = *size; + memcpy(buf, p->buf + p->pos, rem); + p->pos += rem; + *size = rem; + return SZ_OK; +} + +static SRes LookToRead_Seek(void *pp, Int64 *pos, ESzSeek origin) +{ + CLookToRead *p = (CLookToRead *)pp; + p->pos = p->size = 0; + return p->realStream->Seek(p->realStream, pos, origin); +} + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead) +{ + p->s.Look = lookahead ? + LookToRead_Look_Lookahead : + LookToRead_Look_Exact; + p->s.Skip = LookToRead_Skip; + p->s.Read = LookToRead_Read; + p->s.Seek = LookToRead_Seek; +} + +void LookToRead_Init(CLookToRead *p) +{ + p->pos = p->size = 0; +} + +static SRes SecToLook_Read(void *pp, void *buf, size_t *size) +{ + CSecToLook *p = (CSecToLook *)pp; + return LookInStream_LookRead(p->realStream, buf, size); +} + +void SecToLook_CreateVTable(CSecToLook *p) +{ + p->s.Read = SecToLook_Read; +} + +static SRes SecToRead_Read(void *pp, void *buf, size_t *size) +{ + CSecToRead *p = (CSecToRead *)pp; + return p->realStream->Read(p->realStream, buf, size); +} + +void SecToRead_CreateVTable(CSecToRead *p) +{ + p->s.Read = SecToRead_Read; +}
@@ -0,0 +1,256 @@
+/* 7zTypes.h -- Basic types +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +#ifdef _WIN32 +/* #include <windows.h> */ +#endif + +#include <stddef.h> + +#ifndef EXTERN_C_BEGIN +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +#endif + +EXTERN_C_BEGIN + +#define SZ_OK 0 + +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define SZ_ERROR_THREAD 12 + +#define SZ_ERROR_ARCHIVE 16 +#define SZ_ERROR_NO_ARCHIVE 17 + +typedef int SRes; + +#ifdef _WIN32 +/* typedef DWORD WRes; */ +typedef unsigned WRes; +#else +typedef int WRes; +#endif + +#ifndef RINOK +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#endif + +typedef unsigned char Byte; +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#define UINT64_CONST(n) n +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int Bool; +#define True 1 +#define False 0 + + +#ifdef _WIN32 +#define MY_STD_CALL __stdcall +#else +#define MY_STD_CALL +#endif + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_CDECL __cdecl +#define MY_FAST_CALL __fastcall + +#else + +#define MY_NO_INLINE +#define MY_CDECL +#define MY_FAST_CALL + +#endif + + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ +} IByteIn; + +typedef struct +{ + void (*Write)(void *p, Byte b); +} IByteOut; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +} ISeqInStream; + +/* it can return SZ_ERROR_INPUT_EOF */ +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size); +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf); + +typedef struct +{ + size_t (*Write)(void *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +} ISeqOutStream; + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ISeekInStream; + +typedef struct +{ + SRes (*Look)(void *p, const void **buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) > input(*size)) is not allowed + (output(*size) < input(*size)) is allowed */ + SRes (*Skip)(void *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(void *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ILookInStream; + +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); + +#define LookToRead_BUF_SIZE (1 << 14) + +typedef struct +{ + ILookInStream s; + ISeekInStream *realStream; + size_t pos; + size_t size; + Byte buf[LookToRead_BUF_SIZE]; +} CLookToRead; + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead); +void LookToRead_Init(CLookToRead *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToLook; + +void SecToLook_CreateVTable(CSecToLook *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToRead; + +void SecToRead_CreateVTable(CSecToRead *p); + +typedef struct +{ + SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +} ICompressProgress; + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) +#define IAlloc_Free(p, a) (p)->Free((p), a) + +#ifdef _WIN32 + +#define CHAR_PATH_SEPARATOR '\\' +#define WCHAR_PATH_SEPARATOR L'\\' +#define STRING_PATH_SEPARATOR "\\" +#define WSTRING_PATH_SEPARATOR L"\\" + +#else + +#define CHAR_PATH_SEPARATOR '/' +#define WCHAR_PATH_SEPARATOR L'/' +#define STRING_PATH_SEPARATOR "/" +#define WSTRING_PATH_SEPARATOR L"/" + +#endif + +EXTERN_C_END + +#endif
@@ -0,0 +1,10 @@
+#define MY_VER_MAJOR 9 +#define MY_VER_MINOR 38 +#define MY_VER_BUILD 00 +#define MY_VERSION "9.38 beta" +// #define MY_7ZIP_VERSION "9.38" +#define MY_DATE "2015-01-03" +#undef MY_COPYRIGHT +#undef MY_VERSION_COPYRIGHT_DATE +#define MY_COPYRIGHT ": Igor Pavlov : Public domain" +#define MY_VERSION_COPYRIGHT_DATE MY_VERSION " " MY_COPYRIGHT " : " MY_DATE
@@ -0,0 +1,55 @@
+#define MY_VS_FFI_FILEFLAGSMASK 0x0000003FL +#define MY_VOS_NT_WINDOWS32 0x00040004L +#define MY_VOS_CE_WINDOWS32 0x00050004L + +#define MY_VFT_APP 0x00000001L +#define MY_VFT_DLL 0x00000002L + +// #include <WinVer.h> + +#ifndef MY_VERSION +#include "7zVersion.h" +#endif + +#define MY_VER MY_VER_MAJOR,MY_VER_MINOR,MY_VER_BUILD,0 + +#ifdef DEBUG +#define DBG_FL VS_FF_DEBUG +#else +#define DBG_FL 0 +#endif + +#define MY_VERSION_INFO(fileType, descr, intName, origName) \ +LANGUAGE 9, 1 \ +1 VERSIONINFO \ + FILEVERSION MY_VER \ + PRODUCTVERSION MY_VER \ + FILEFLAGSMASK MY_VS_FFI_FILEFLAGSMASK \ + FILEFLAGS DBG_FL \ + FILEOS MY_VOS_NT_WINDOWS32 \ + FILETYPE fileType \ + FILESUBTYPE 0x0L \ +BEGIN \ + BLOCK "StringFileInfo" \ + BEGIN \ + BLOCK "040904b0" \ + BEGIN \ + VALUE "CompanyName", "Igor Pavlov" \ + VALUE "FileDescription", descr \ + VALUE "FileVersion", MY_VERSION \ + VALUE "InternalName", intName \ + VALUE "LegalCopyright", MY_COPYRIGHT \ + VALUE "OriginalFilename", origName \ + VALUE "ProductName", "7-Zip" \ + VALUE "ProductVersion", MY_VERSION \ + END \ + END \ + BLOCK "VarFileInfo" \ + BEGIN \ + VALUE "Translation", 0x409, 1200 \ + END \ +END + +#define MY_VERSION_INFO_APP(descr, intName) MY_VERSION_INFO(MY_VFT_APP, descr, intName, intName ".exe") + +#define MY_VERSION_INFO_DLL(descr, intName) MY_VERSION_INFO(MY_VFT_DLL, descr, intName, intName ".dll")
@@ -0,0 +1,284 @@
+/* Aes.c -- AES encryption / decryption +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Aes.h" +#include "CpuArch.h" + +static UInt32 T[256 * 4]; +static Byte Sbox[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + +void MY_FAST_CALL AesCbc_Encode(UInt32 *ivAes, Byte *data, size_t numBlocks); +void MY_FAST_CALL AesCbc_Decode(UInt32 *ivAes, Byte *data, size_t numBlocks); +void MY_FAST_CALL AesCtr_Code(UInt32 *ivAes, Byte *data, size_t numBlocks); + +void MY_FAST_CALL AesCbc_Encode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); +void MY_FAST_CALL AesCbc_Decode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); +void MY_FAST_CALL AesCtr_Code_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); + +AES_CODE_FUNC g_AesCbc_Encode; +AES_CODE_FUNC g_AesCbc_Decode; +AES_CODE_FUNC g_AesCtr_Code; + +static UInt32 D[256 * 4]; +static Byte InvS[256]; + +static Byte Rcon[11] = { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +#define xtime(x) ((((x) << 1) ^ (((x) & 0x80) != 0 ? 0x1B : 0)) & 0xFF) + +#define Ui32(a0, a1, a2, a3) ((UInt32)(a0) | ((UInt32)(a1) << 8) | ((UInt32)(a2) << 16) | ((UInt32)(a3) << 24)) + +#define gb0(x) ( (x) & 0xFF) +#define gb1(x) (((x) >> ( 8)) & 0xFF) +#define gb2(x) (((x) >> (16)) & 0xFF) +#define gb3(x) (((x) >> (24)) & 0xFF) + +void AesGenTables(void) +{ + unsigned i; + for (i = 0; i < 256; i++) + InvS[Sbox[i]] = (Byte)i; + for (i = 0; i < 256; i++) + { + { + UInt32 a1 = Sbox[i]; + UInt32 a2 = xtime(a1); + UInt32 a3 = a2 ^ a1; + T[ i] = Ui32(a2, a1, a1, a3); + T[0x100 + i] = Ui32(a3, a2, a1, a1); + T[0x200 + i] = Ui32(a1, a3, a2, a1); + T[0x300 + i] = Ui32(a1, a1, a3, a2); + } + { + UInt32 a1 = InvS[i]; + UInt32 a2 = xtime(a1); + UInt32 a4 = xtime(a2); + UInt32 a8 = xtime(a4); + UInt32 a9 = a8 ^ a1; + UInt32 aB = a8 ^ a2 ^ a1; + UInt32 aD = a8 ^ a4 ^ a1; + UInt32 aE = a8 ^ a4 ^ a2; + D[ i] = Ui32(aE, a9, aD, aB); + D[0x100 + i] = Ui32(aB, aE, a9, aD); + D[0x200 + i] = Ui32(aD, aB, aE, a9); + D[0x300 + i] = Ui32(a9, aD, aB, aE); + } + } + g_AesCbc_Encode = AesCbc_Encode; + g_AesCbc_Decode = AesCbc_Decode; + g_AesCtr_Code = AesCtr_Code; + #ifdef MY_CPU_X86_OR_AMD64 + if (CPU_Is_Aes_Supported()) + { + g_AesCbc_Encode = AesCbc_Encode_Intel; + g_AesCbc_Decode = AesCbc_Decode_Intel; + g_AesCtr_Code = AesCtr_Code_Intel; + } + #endif +} + +#define HT(i, x, s) (T + (x << 8))[gb ## x(s[(i + x) & 3])] +#define HT4(m, i, s, p) m[i] = \ + HT(i, 0, s) ^ \ + HT(i, 1, s) ^ \ + HT(i, 2, s) ^ \ + HT(i, 3, s) ^ w[p + i] +/* such order (2031) in HT16 is for VC6/K8 speed optimization) */ +#define HT16(m, s, p) \ + HT4(m, 2, s, p); \ + HT4(m, 0, s, p); \ + HT4(m, 3, s, p); \ + HT4(m, 1, s, p); \ + +#define FT(i, x) Sbox[gb ## x(m[(i + x) & 3])] +#define FT4(i) dest[i] = Ui32(FT(i, 0), FT(i, 1), FT(i, 2), FT(i, 3)) ^ w[i]; + +#define HD(i, x, s) (D + (x << 8))[gb ## x(s[(i - x) & 3])] +#define HD4(m, i, s, p) m[i] = \ + HD(i, 0, s) ^ \ + HD(i, 1, s) ^ \ + HD(i, 2, s) ^ \ + HD(i, 3, s) ^ w[p + i]; +/* such order (0231) in HD16 is for VC6/K8 speed optimization) */ +#define HD16(m, s, p) \ + HD4(m, 0, s, p); \ + HD4(m, 2, s, p); \ + HD4(m, 3, s, p); \ + HD4(m, 1, s, p); \ + +#define FD(i, x) InvS[gb ## x(m[(i - x) & 3])] +#define FD4(i) dest[i] = Ui32(FD(i, 0), FD(i, 1), FD(i, 2), FD(i, 3)) ^ w[i]; + +void MY_FAST_CALL Aes_SetKey_Enc(UInt32 *w, const Byte *key, unsigned keySize) +{ + unsigned i, wSize; + wSize = keySize + 28; + keySize /= 4; + w[0] = ((UInt32)keySize / 2) + 3; + w += 4; + + for (i = 0; i < keySize; i++, key += 4) + w[i] = GetUi32(key); + + for (; i < wSize; i++) + { + UInt32 t = w[i - 1]; + unsigned rem = i % keySize; + if (rem == 0) + t = Ui32(Sbox[gb1(t)] ^ Rcon[i / keySize], Sbox[gb2(t)], Sbox[gb3(t)], Sbox[gb0(t)]); + else if (keySize > 6 && rem == 4) + t = Ui32(Sbox[gb0(t)], Sbox[gb1(t)], Sbox[gb2(t)], Sbox[gb3(t)]); + w[i] = w[i - keySize] ^ t; + } +} + +void MY_FAST_CALL Aes_SetKey_Dec(UInt32 *w, const Byte *key, unsigned keySize) +{ + unsigned i, num; + Aes_SetKey_Enc(w, key, keySize); + num = keySize + 20; + w += 8; + for (i = 0; i < num; i++) + { + UInt32 r = w[i]; + w[i] = + D[ Sbox[gb0(r)]] ^ + D[0x100 + Sbox[gb1(r)]] ^ + D[0x200 + Sbox[gb2(r)]] ^ + D[0x300 + Sbox[gb3(r)]]; + } +} + +/* Aes_Encode and Aes_Decode functions work with little-endian words. + src and dest are pointers to 4 UInt32 words. + arc and dest can point to same block */ + +static void Aes_Encode(const UInt32 *w, UInt32 *dest, const UInt32 *src) +{ + UInt32 s[4]; + UInt32 m[4]; + UInt32 numRounds2 = w[0]; + w += 4; + s[0] = src[0] ^ w[0]; + s[1] = src[1] ^ w[1]; + s[2] = src[2] ^ w[2]; + s[3] = src[3] ^ w[3]; + w += 4; + for (;;) + { + HT16(m, s, 0); + if (--numRounds2 == 0) + break; + HT16(s, m, 4); + w += 8; + } + w += 4; + FT4(0); FT4(1); FT4(2); FT4(3); +} + +static void Aes_Decode(const UInt32 *w, UInt32 *dest, const UInt32 *src) +{ + UInt32 s[4]; + UInt32 m[4]; + UInt32 numRounds2 = w[0]; + w += 4 + numRounds2 * 8; + s[0] = src[0] ^ w[0]; + s[1] = src[1] ^ w[1]; + s[2] = src[2] ^ w[2]; + s[3] = src[3] ^ w[3]; + for (;;) + { + w -= 8; + HD16(m, s, 4); + if (--numRounds2 == 0) + break; + HD16(s, m, 0); + } + FD4(0); FD4(1); FD4(2); FD4(3); +} + +void AesCbc_Init(UInt32 *p, const Byte *iv) +{ + unsigned i; + for (i = 0; i < 4; i++) + p[i] = GetUi32(iv + i * 4); +} + +void MY_FAST_CALL AesCbc_Encode(UInt32 *p, Byte *data, size_t numBlocks) +{ + for (; numBlocks != 0; numBlocks--, data += AES_BLOCK_SIZE) + { + p[0] ^= GetUi32(data); + p[1] ^= GetUi32(data + 4); + p[2] ^= GetUi32(data + 8); + p[3] ^= GetUi32(data + 12); + + Aes_Encode(p + 4, p, p); + + SetUi32(data, p[0]); + SetUi32(data + 4, p[1]); + SetUi32(data + 8, p[2]); + SetUi32(data + 12, p[3]); + } +} + +void MY_FAST_CALL AesCbc_Decode(UInt32 *p, Byte *data, size_t numBlocks) +{ + UInt32 in[4], out[4]; + for (; numBlocks != 0; numBlocks--, data += AES_BLOCK_SIZE) + { + in[0] = GetUi32(data); + in[1] = GetUi32(data + 4); + in[2] = GetUi32(data + 8); + in[3] = GetUi32(data + 12); + + Aes_Decode(p + 4, out, in); + + SetUi32(data, p[0] ^ out[0]); + SetUi32(data + 4, p[1] ^ out[1]); + SetUi32(data + 8, p[2] ^ out[2]); + SetUi32(data + 12, p[3] ^ out[3]); + + p[0] = in[0]; + p[1] = in[1]; + p[2] = in[2]; + p[3] = in[3]; + } +} + +void MY_FAST_CALL AesCtr_Code(UInt32 *p, Byte *data, size_t numBlocks) +{ + for (; numBlocks != 0; numBlocks--) + { + UInt32 temp[4]; + Byte buf[16]; + int i; + if (++p[0] == 0) + p[1]++; + Aes_Encode(p + 4, temp, p); + SetUi32(buf, temp[0]); + SetUi32(buf + 4, temp[1]); + SetUi32(buf + 8, temp[2]); + SetUi32(buf + 12, temp[3]); + for (i = 0; i < 16; i++) + *data++ ^= buf[i]; + } +}
@@ -0,0 +1,38 @@
+/* Aes.h -- AES encryption / decryption +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __AES_H +#define __AES_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define AES_BLOCK_SIZE 16 + +/* Call AesGenTables one time before other AES functions */ +void AesGenTables(void); + +/* UInt32 pointers must be 16-byte aligned */ + +/* 16-byte (4 * 32-bit words) blocks: 1 (IV) + 1 (keyMode) + 15 (AES-256 roundKeys) */ +#define AES_NUM_IVMRK_WORDS ((1 + 1 + 15) * 4) + +/* aes - 16-byte aligned pointer to keyMode+roundKeys sequence */ +/* keySize = 16 or 24 or 32 (bytes) */ +typedef void (MY_FAST_CALL *AES_SET_KEY_FUNC)(UInt32 *aes, const Byte *key, unsigned keySize); +void MY_FAST_CALL Aes_SetKey_Enc(UInt32 *aes, const Byte *key, unsigned keySize); +void MY_FAST_CALL Aes_SetKey_Dec(UInt32 *aes, const Byte *key, unsigned keySize); + +/* ivAes - 16-byte aligned pointer to iv+keyMode+roundKeys sequence: UInt32[AES_NUM_IVMRK_WORDS] */ +void AesCbc_Init(UInt32 *ivAes, const Byte *iv); /* iv size is AES_BLOCK_SIZE */ +/* data - 16-byte aligned pointer to data */ +/* numBlocks - the number of 16-byte blocks in data array */ +typedef void (MY_FAST_CALL *AES_CODE_FUNC)(UInt32 *ivAes, Byte *data, size_t numBlocks); +extern AES_CODE_FUNC g_AesCbc_Encode; +extern AES_CODE_FUNC g_AesCbc_Decode; +extern AES_CODE_FUNC g_AesCtr_Code; + +EXTERN_C_END + +#endif
@@ -0,0 +1,184 @@
+/* AesOpt.c -- Intel's AES +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "CpuArch.h" + +#ifdef MY_CPU_X86_OR_AMD64 +#if _MSC_VER >= 1500 +#define USE_INTEL_AES +#endif +#endif + +#ifdef USE_INTEL_AES + +#include <wmmintrin.h> + +void MY_FAST_CALL AesCbc_Encode_Intel(__m128i *p, __m128i *data, size_t numBlocks) +{ + __m128i m = *p; + for (; numBlocks != 0; numBlocks--, data++) + { + UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; + const __m128i *w = p + 3; + m = _mm_xor_si128(m, *data); + m = _mm_xor_si128(m, p[2]); + do + { + m = _mm_aesenc_si128(m, w[0]); + m = _mm_aesenc_si128(m, w[1]); + w += 2; + } + while (--numRounds2 != 0); + m = _mm_aesenc_si128(m, w[0]); + m = _mm_aesenclast_si128(m, w[1]); + *data = m; + } + *p = m; +} + +#define NUM_WAYS 3 + +#define AES_OP_W(op, n) { \ + const __m128i t = w[n]; \ + m0 = op(m0, t); \ + m1 = op(m1, t); \ + m2 = op(m2, t); \ + } + +#define AES_DEC(n) AES_OP_W(_mm_aesdec_si128, n) +#define AES_DEC_LAST(n) AES_OP_W(_mm_aesdeclast_si128, n) +#define AES_ENC(n) AES_OP_W(_mm_aesenc_si128, n) +#define AES_ENC_LAST(n) AES_OP_W(_mm_aesenclast_si128, n) + +void MY_FAST_CALL AesCbc_Decode_Intel(__m128i *p, __m128i *data, size_t numBlocks) +{ + __m128i iv = *p; + for (; numBlocks >= NUM_WAYS; numBlocks -= NUM_WAYS, data += NUM_WAYS) + { + UInt32 numRounds2 = *(const UInt32 *)(p + 1); + const __m128i *w = p + numRounds2 * 2; + __m128i m0, m1, m2; + { + const __m128i t = w[2]; + m0 = _mm_xor_si128(t, data[0]); + m1 = _mm_xor_si128(t, data[1]); + m2 = _mm_xor_si128(t, data[2]); + } + numRounds2--; + do + { + AES_DEC(1) + AES_DEC(0) + w -= 2; + } + while (--numRounds2 != 0); + AES_DEC(1) + AES_DEC_LAST(0) + + { + __m128i t; + t = _mm_xor_si128(m0, iv); iv = data[0]; data[0] = t; + t = _mm_xor_si128(m1, iv); iv = data[1]; data[1] = t; + t = _mm_xor_si128(m2, iv); iv = data[2]; data[2] = t; + } + } + for (; numBlocks != 0; numBlocks--, data++) + { + UInt32 numRounds2 = *(const UInt32 *)(p + 1); + const __m128i *w = p + numRounds2 * 2; + __m128i m = _mm_xor_si128(w[2], *data); + numRounds2--; + do + { + m = _mm_aesdec_si128(m, w[1]); + m = _mm_aesdec_si128(m, w[0]); + w -= 2; + } + while (--numRounds2 != 0); + m = _mm_aesdec_si128(m, w[1]); + m = _mm_aesdeclast_si128(m, w[0]); + + m = _mm_xor_si128(m, iv); + iv = *data; + *data = m; + } + *p = iv; +} + +void MY_FAST_CALL AesCtr_Code_Intel(__m128i *p, __m128i *data, size_t numBlocks) +{ + __m128i ctr = *p; + __m128i one; + one.m128i_u64[0] = 1; + one.m128i_u64[1] = 0; + for (; numBlocks >= NUM_WAYS; numBlocks -= NUM_WAYS, data += NUM_WAYS) + { + UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; + const __m128i *w = p; + __m128i m0, m1, m2; + { + const __m128i t = w[2]; + ctr = _mm_add_epi64(ctr, one); m0 = _mm_xor_si128(ctr, t); + ctr = _mm_add_epi64(ctr, one); m1 = _mm_xor_si128(ctr, t); + ctr = _mm_add_epi64(ctr, one); m2 = _mm_xor_si128(ctr, t); + } + w += 3; + do + { + AES_ENC(0) + AES_ENC(1) + w += 2; + } + while (--numRounds2 != 0); + AES_ENC(0) + AES_ENC_LAST(1) + data[0] = _mm_xor_si128(data[0], m0); + data[1] = _mm_xor_si128(data[1], m1); + data[2] = _mm_xor_si128(data[2], m2); + } + for (; numBlocks != 0; numBlocks--, data++) + { + UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; + const __m128i *w = p; + __m128i m; + ctr = _mm_add_epi64(ctr, one); + m = _mm_xor_si128(ctr, p[2]); + w += 3; + do + { + m = _mm_aesenc_si128(m, w[0]); + m = _mm_aesenc_si128(m, w[1]); + w += 2; + } + while (--numRounds2 != 0); + m = _mm_aesenc_si128(m, w[0]); + m = _mm_aesenclast_si128(m, w[1]); + *data = _mm_xor_si128(*data, m); + } + *p = ctr; +} + +#else + +void MY_FAST_CALL AesCbc_Encode(UInt32 *ivAes, Byte *data, size_t numBlocks); +void MY_FAST_CALL AesCbc_Decode(UInt32 *ivAes, Byte *data, size_t numBlocks); +void MY_FAST_CALL AesCtr_Code(UInt32 *ivAes, Byte *data, size_t numBlocks); + +void MY_FAST_CALL AesCbc_Encode_Intel(UInt32 *p, Byte *data, size_t numBlocks) +{ + AesCbc_Encode(p, data, numBlocks); +} + +void MY_FAST_CALL AesCbc_Decode_Intel(UInt32 *p, Byte *data, size_t numBlocks) +{ + AesCbc_Decode(p, data, numBlocks); +} + +void MY_FAST_CALL AesCtr_Code_Intel(UInt32 *p, Byte *data, size_t numBlocks) +{ + AesCtr_Code(p, data, numBlocks); +} + +#endif
@@ -0,0 +1,127 @@
+/* Alloc.c -- Memory allocation functions +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#ifdef _WIN32 +#include <windows.h> +#endif +#include <stdlib.h> + +#include "Alloc.h" + +/* #define _SZ_ALLOC_DEBUG */ + +/* use _SZ_ALLOC_DEBUG to debug alloc/free operations */ +#ifdef _SZ_ALLOC_DEBUG +#include <stdio.h> +int g_allocCount = 0; +int g_allocCountMid = 0; +int g_allocCountBig = 0; +#endif + +void *MyAlloc(size_t size) +{ + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + { + void *p = malloc(size); + fprintf(stderr, "\nAlloc %10d bytes, count = %10d, addr = %8X", size, g_allocCount++, (unsigned)p); + return p; + } + #else + return malloc(size); + #endif +} + +void MyFree(void *address) +{ + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + fprintf(stderr, "\nFree; count = %10d, addr = %8X", --g_allocCount, (unsigned)address); + #endif + free(address); +} + +#ifdef _WIN32 + +void *MidAlloc(size_t size) +{ + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + fprintf(stderr, "\nAlloc_Mid %10d bytes; count = %10d", size, g_allocCountMid++); + #endif + return VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); +} + +void MidFree(void *address) +{ + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + fprintf(stderr, "\nFree_Mid; count = %10d", --g_allocCountMid); + #endif + if (address == 0) + return; + VirtualFree(address, 0, MEM_RELEASE); +} + +#ifndef MEM_LARGE_PAGES +#undef _7ZIP_LARGE_PAGES +#endif + +#ifdef _7ZIP_LARGE_PAGES +SIZE_T g_LargePageSize = 0; +typedef SIZE_T (WINAPI *GetLargePageMinimumP)(); +#endif + +void SetLargePageSize() +{ + #ifdef _7ZIP_LARGE_PAGES + SIZE_T size = 0; + GetLargePageMinimumP largePageMinimum = (GetLargePageMinimumP) + GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetLargePageMinimum"); + if (largePageMinimum == 0) + return; + size = largePageMinimum(); + if (size == 0 || (size & (size - 1)) != 0) + return; + g_LargePageSize = size; + #endif +} + + +void *BigAlloc(size_t size) +{ + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + fprintf(stderr, "\nAlloc_Big %10d bytes; count = %10d", size, g_allocCountBig++); + #endif + + #ifdef _7ZIP_LARGE_PAGES + if (g_LargePageSize != 0 && g_LargePageSize <= (1 << 30) && size >= (1 << 18)) + { + void *res = VirtualAlloc(0, (size + g_LargePageSize - 1) & (~(g_LargePageSize - 1)), + MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + if (res != 0) + return res; + } + #endif + return VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); +} + +void BigFree(void *address) +{ + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + fprintf(stderr, "\nFree_Big; count = %10d", --g_allocCountBig); + #endif + + if (address == 0) + return; + VirtualFree(address, 0, MEM_RELEASE); +} + +#endif
@@ -0,0 +1,38 @@
+/* Alloc.h -- Memory allocation functions +2009-02-07 : Igor Pavlov : Public domain */ + +#ifndef __COMMON_ALLOC_H +#define __COMMON_ALLOC_H + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void *MyAlloc(size_t size); +void MyFree(void *address); + +#ifdef _WIN32 + +void SetLargePageSize(); + +void *MidAlloc(size_t size); +void MidFree(void *address); +void *BigAlloc(size_t size); +void BigFree(void *address); + +#else + +#define MidAlloc(size) MyAlloc(size) +#define MidFree(address) MyFree(address) +#define BigAlloc(size) MyAlloc(size) +#define BigFree(address) MyFree(address) + +#endif + +#ifdef __cplusplus +} +#endif + +#endif
@@ -0,0 +1,134 @@
+/* Bcj2.c -- Converter for x86 code (BCJ2) +2008-10-04 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Bcj2.h" + +#ifdef _LZMA_PROB32 +#define CProb UInt32 +#else +#define CProb UInt16 +#endif + +#define IsJcc(b0, b1) ((b0) == 0x0F && ((b1) & 0xF0) == 0x80) +#define IsJ(b0, b1) ((b1 & 0xFE) == 0xE8 || IsJcc(b0, b1)) + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_READ_BYTE (*buffer++) +#define RC_TEST { if (buffer == bufferLim) return SZ_ERROR_DATA; } +#define RC_INIT2 code = 0; range = 0xFFFFFFFF; \ + { int i; for (i = 0; i < 5; i++) { RC_TEST; code = (code << 8) | RC_READ_BYTE; }} + +#define NORMALIZE if (range < kTopValue) { RC_TEST; range <<= 8; code = (code << 8) | RC_READ_BYTE; } + +#define IF_BIT_0(p) ttt = *(p); bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); NORMALIZE; +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CProb)(ttt - (ttt >> kNumMoveBits)); NORMALIZE; + +int Bcj2_Decode( + const Byte *buf0, SizeT size0, + const Byte *buf1, SizeT size1, + const Byte *buf2, SizeT size2, + const Byte *buf3, SizeT size3, + Byte *outBuf, SizeT outSize) +{ + CProb p[256 + 2]; + SizeT inPos = 0, outPos = 0; + + const Byte *buffer, *bufferLim; + UInt32 range, code; + Byte prevByte = 0; + + unsigned int i; + for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) + p[i] = kBitModelTotal >> 1; + + buffer = buf3; + bufferLim = buffer + size3; + RC_INIT2 + + if (outSize == 0) + return SZ_OK; + + for (;;) + { + Byte b; + CProb *prob; + UInt32 bound; + UInt32 ttt; + + SizeT limit = size0 - inPos; + if (outSize - outPos < limit) + limit = outSize - outPos; + while (limit != 0) + { + Byte b = buf0[inPos]; + outBuf[outPos++] = b; + if (IsJ(prevByte, b)) + break; + inPos++; + prevByte = b; + limit--; + } + + if (limit == 0 || outPos == outSize) + break; + + b = buf0[inPos++]; + + if (b == 0xE8) + prob = p + prevByte; + else if (b == 0xE9) + prob = p + 256; + else + prob = p + 257; + + IF_BIT_0(prob) + { + UPDATE_0(prob) + prevByte = b; + } + else + { + UInt32 dest; + const Byte *v; + UPDATE_1(prob) + if (b == 0xE8) + { + v = buf1; + if (size1 < 4) + return SZ_ERROR_DATA; + buf1 += 4; + size1 -= 4; + } + else + { + v = buf2; + if (size2 < 4) + return SZ_ERROR_DATA; + buf2 += 4; + size2 -= 4; + } + dest = (((UInt32)v[0] << 24) | ((UInt32)v[1] << 16) | + ((UInt32)v[2] << 8) | ((UInt32)v[3])) - ((UInt32)outPos + 4); + outBuf[outPos++] = (Byte)dest; + if (outPos == outSize) + break; + outBuf[outPos++] = (Byte)(dest >> 8); + if (outPos == outSize) + break; + outBuf[outPos++] = (Byte)(dest >> 16); + if (outPos == outSize) + break; + outBuf[outPos++] = prevByte = (Byte)(dest >> 24); + } + } + return (outPos == outSize) ? SZ_OK : SZ_ERROR_DATA; +}
@@ -0,0 +1,34 @@
+/* Bcj2.h -- Converter for x86 code (BCJ2) +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __BCJ2_H +#define __BCJ2_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +/* +Conditions: + outSize <= FullOutputSize, + where FullOutputSize is full size of output stream of x86_2 filter. + +If buf0 overlaps outBuf, there are two required conditions: + 1) (buf0 >= outBuf) + 2) (buf0 + size0 >= outBuf + FullOutputSize). + +Returns: + SZ_OK + SZ_ERROR_DATA - Data error +*/ + +int Bcj2_Decode( + const Byte *buf0, SizeT size0, + const Byte *buf1, SizeT size1, + const Byte *buf2, SizeT size2, + const Byte *buf3, SizeT size3, + Byte *outBuf, SizeT outSize); + +EXTERN_C_END + +#endif
@@ -0,0 +1,135 @@
+/* Bra.c -- Converters for RISC code +2010-04-16 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Bra.h" + +SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 4) + return 0; + size -= 4; + ip += 8; + for (i = 0; i <= size; i += 4) + { + if (data[i + 3] == 0xEB) + { + UInt32 dest; + UInt32 src = ((UInt32)data[i + 2] << 16) | ((UInt32)data[i + 1] << 8) | (data[i + 0]); + src <<= 2; + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + dest >>= 2; + data[i + 2] = (Byte)(dest >> 16); + data[i + 1] = (Byte)(dest >> 8); + data[i + 0] = (Byte)dest; + } + } + return i; +} + +SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 4) + return 0; + size -= 4; + ip += 4; + for (i = 0; i <= size; i += 2) + { + if ((data[i + 1] & 0xF8) == 0xF0 && + (data[i + 3] & 0xF8) == 0xF8) + { + UInt32 dest; + UInt32 src = + (((UInt32)data[i + 1] & 0x7) << 19) | + ((UInt32)data[i + 0] << 11) | + (((UInt32)data[i + 3] & 0x7) << 8) | + (data[i + 2]); + + src <<= 1; + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + dest >>= 1; + + data[i + 1] = (Byte)(0xF0 | ((dest >> 19) & 0x7)); + data[i + 0] = (Byte)(dest >> 11); + data[i + 3] = (Byte)(0xF8 | ((dest >> 8) & 0x7)); + data[i + 2] = (Byte)dest; + i += 2; + } + } + return i; +} + +SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 4) + return 0; + size -= 4; + for (i = 0; i <= size; i += 4) + { + if ((data[i] >> 2) == 0x12 && (data[i + 3] & 3) == 1) + { + UInt32 src = ((UInt32)(data[i + 0] & 3) << 24) | + ((UInt32)data[i + 1] << 16) | + ((UInt32)data[i + 2] << 8) | + ((UInt32)data[i + 3] & (~3)); + + UInt32 dest; + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + data[i + 0] = (Byte)(0x48 | ((dest >> 24) & 0x3)); + data[i + 1] = (Byte)(dest >> 16); + data[i + 2] = (Byte)(dest >> 8); + data[i + 3] &= 0x3; + data[i + 3] |= dest; + } + } + return i; +} + +SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + UInt32 i; + if (size < 4) + return 0; + size -= 4; + for (i = 0; i <= size; i += 4) + { + if ((data[i] == 0x40 && (data[i + 1] & 0xC0) == 0x00) || + (data[i] == 0x7F && (data[i + 1] & 0xC0) == 0xC0)) + { + UInt32 src = + ((UInt32)data[i + 0] << 24) | + ((UInt32)data[i + 1] << 16) | + ((UInt32)data[i + 2] << 8) | + ((UInt32)data[i + 3]); + UInt32 dest; + + src <<= 2; + if (encoding) + dest = ip + i + src; + else + dest = src - (ip + i); + dest >>= 2; + + dest = (((0 - ((dest >> 22) & 1)) << 22) & 0x3FFFFFFF) | (dest & 0x3FFFFF) | 0x40000000; + + data[i + 0] = (Byte)(dest >> 24); + data[i + 1] = (Byte)(dest >> 16); + data[i + 2] = (Byte)(dest >> 8); + data[i + 3] = (Byte)dest; + } + } + return i; +}
@@ -0,0 +1,64 @@
+/* Bra.h -- Branch converters for executables +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __BRA_H +#define __BRA_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +/* +These functions convert relative addresses to absolute addresses +in CALL instructions to increase the compression ratio. + + In: + data - data buffer + size - size of data + ip - current virtual Instruction Pinter (IP) value + state - state variable for x86 converter + encoding - 0 (for decoding), 1 (for encoding) + + Out: + state - state variable for x86 converter + + Returns: + The number of processed bytes. If you call these functions with multiple calls, + you must start next call with first byte after block of processed bytes. + + Type Endian Alignment LookAhead + + x86 little 1 4 + ARMT little 2 2 + ARM little 4 0 + PPC big 4 0 + SPARC big 4 0 + IA64 little 16 0 + + size must be >= Alignment + LookAhead, if it's not last block. + If (size < Alignment + LookAhead), converter returns 0. + + Example: + + UInt32 ip = 0; + for () + { + ; size must be >= Alignment + LookAhead, if it's not last block + SizeT processed = Convert(data, size, ip, 1); + data += processed; + size -= processed; + ip += processed; + } +*/ + +#define x86_Convert_Init(state) { state = 0; } +SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding); +SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); + +EXTERN_C_END + +#endif
@@ -0,0 +1,82 @@
+/* Bra86.c -- Converter for x86 code (BCJ) +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Bra.h" + +#define Test86MSByte(b) ((((b) + 1) & 0xFE) == 0) + +SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding) +{ + SizeT pos = 0; + UInt32 mask = *state & 7; + if (size < 5) + return 0; + size -= 4; + ip += 5; + + for (;;) + { + Byte *p = data + pos; + const Byte *limit = data + size; + for (; p < limit; p++) + if ((*p & 0xFE) == 0xE8) + break; + + { + SizeT d = (SizeT)(p - data - pos); + pos = (SizeT)(p - data); + if (p >= limit) + { + *state = (d > 2 ? 0 : mask >> (unsigned)d); + return pos; + } + if (d > 2) + mask = 0; + else + { + mask >>= (unsigned)d; + if (mask != 0 && (mask > 4 || mask == 3 || Test86MSByte(p[(mask >> 1) + 1]))) + { + mask = (mask >> 1) | 4; + pos++; + continue; + } + } + } + + if (Test86MSByte(p[4])) + { + UInt32 v = ((UInt32)p[4] << 24) | ((UInt32)p[3] << 16) | ((UInt32)p[2] << 8) | ((UInt32)p[1]); + UInt32 cur = ip + (UInt32)pos; + pos += 5; + if (encoding) + v += cur; + else + v -= cur; + if (mask != 0) + { + unsigned sh = (mask & 6) << 2; + if (Test86MSByte((Byte)(v >> sh))) + { + v ^= (((UInt32)0x100 << sh) - 1); + if (encoding) + v += cur; + else + v -= cur; + } + mask = 0; + } + p[1] = (Byte)v; + p[2] = (Byte)(v >> 8); + p[3] = (Byte)(v >> 16); + p[4] = (Byte)(0 - ((v >> 24) & 1)); + } + else + { + mask = (mask >> 1) | 4; + pos++; + } + } +}
@@ -0,0 +1,69 @@
+/* BraIA64.c -- Converter for IA-64 code +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Bra.h" + +static const Byte kBranchTable[32] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 6, 6, 0, 0, 7, 7, + 4, 4, 0, 0, 4, 4, 0, 0 +}; + +SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +{ + SizeT i; + if (size < 16) + return 0; + size -= 16; + for (i = 0; i <= size; i += 16) + { + UInt32 instrTemplate = data[i] & 0x1F; + UInt32 mask = kBranchTable[instrTemplate]; + UInt32 bitPos = 5; + int slot; + for (slot = 0; slot < 3; slot++, bitPos += 41) + { + UInt32 bytePos, bitRes; + UInt64 instruction, instNorm; + int j; + if (((mask >> slot) & 1) == 0) + continue; + bytePos = (bitPos >> 3); + bitRes = bitPos & 0x7; + instruction = 0; + for (j = 0; j < 6; j++) + instruction += (UInt64)data[i + j + bytePos] << (8 * j); + + instNorm = instruction >> bitRes; + if (((instNorm >> 37) & 0xF) == 0x5 && ((instNorm >> 9) & 0x7) == 0) + { + UInt32 src = (UInt32)((instNorm >> 13) & 0xFFFFF); + UInt32 dest; + src |= ((UInt32)(instNorm >> 36) & 1) << 20; + + src <<= 4; + + if (encoding) + dest = ip + (UInt32)i + src; + else + dest = src - (ip + (UInt32)i); + + dest >>= 4; + + instNorm &= ~((UInt64)(0x8FFFFF) << 13); + instNorm |= ((UInt64)(dest & 0xFFFFF) << 13); + instNorm |= ((UInt64)(dest & 0x100000) << (36 - 20)); + + instruction &= (1 << bitRes) - 1; + instruction |= (instNorm << bitRes); + for (j = 0; j < 6; j++) + data[i + j + bytePos] = (Byte)(instruction >> (8 * j)); + } + } + } + return i; +}
@@ -0,0 +1,28 @@
+/* Compiler.h -- Compiler ypes +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_COMPILER_H +#define __7Z_COMPILER_H + +#ifdef _MSC_VER + + #ifdef UNDER_CE + #define RPC_NO_WINDOWS_H + /* #pragma warning(disable : 4115) // '_RPC_ASYNC_STATE' : named type definition in parentheses */ + #pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union + #pragma warning(disable : 4214) // nonstandard extension used : bit field types other than int + #endif + + #if _MSC_VER >= 1300 + #pragma warning(disable : 4996) // This function or variable may be unsafe + #else + #pragma warning(disable : 4511) // copy constructor could not be generated + #pragma warning(disable : 4512) // assignment operator could not be generated + #pragma warning(disable : 4702) // unreachable code + #pragma warning(disable : 4710) // not inlined + #pragma warning(disable : 4786) // identifier was truncated to '255' characters in the debug information + #endif + +#endif + +#endif
@@ -0,0 +1,190 @@
+/* CpuArch.c -- CPU specific code +2012-05-29: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "CpuArch.h" + +#ifdef MY_CPU_X86_OR_AMD64 + +#if (defined(_MSC_VER) && !defined(MY_CPU_AMD64)) || defined(__GNUC__) +#define USE_ASM +#endif + +#if !defined(USE_ASM) && _MSC_VER >= 1500 +#include <intrin.h> +#endif + +#if defined(USE_ASM) && !defined(MY_CPU_AMD64) +static UInt32 CheckFlag(UInt32 flag) +{ + #ifdef _MSC_VER + __asm pushfd; + __asm pop EAX; + __asm mov EDX, EAX; + __asm xor EAX, flag; + __asm push EAX; + __asm popfd; + __asm pushfd; + __asm pop EAX; + __asm xor EAX, EDX; + __asm push EDX; + __asm popfd; + __asm and flag, EAX; + #else + __asm__ __volatile__ ( + "pushf\n\t" + "pop %%EAX\n\t" + "movl %%EAX,%%EDX\n\t" + "xorl %0,%%EAX\n\t" + "push %%EAX\n\t" + "popf\n\t" + "pushf\n\t" + "pop %%EAX\n\t" + "xorl %%EDX,%%EAX\n\t" + "push %%EDX\n\t" + "popf\n\t" + "andl %%EAX, %0\n\t": + "=c" (flag) : "c" (flag)); + #endif + return flag; +} +#define CHECK_CPUID_IS_SUPPORTED if (CheckFlag(1 << 18) == 0 || CheckFlag(1 << 21) == 0) return False; +#else +#define CHECK_CPUID_IS_SUPPORTED +#endif + +static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d) +{ + #ifdef USE_ASM + + #ifdef _MSC_VER + + UInt32 a2, b2, c2, d2; + __asm xor EBX, EBX; + __asm xor ECX, ECX; + __asm xor EDX, EDX; + __asm mov EAX, function; + __asm cpuid; + __asm mov a2, EAX; + __asm mov b2, EBX; + __asm mov c2, ECX; + __asm mov d2, EDX; + + *a = a2; + *b = b2; + *c = c2; + *d = d2; + + #else + + __asm__ __volatile__ ( + #if defined(MY_CPU_X86) && defined(__PIC__) + "mov %%ebx, %%edi;" + "cpuid;" + "xchgl %%ebx, %%edi;" + : "=a" (*a) , + "=D" (*b) , + #else + "cpuid" + : "=a" (*a) , + "=b" (*b) , + #endif + "=c" (*c) , + "=d" (*d) + : "0" (function)) ; + + #endif + + #else + + int CPUInfo[4]; + __cpuid(CPUInfo, function); + *a = CPUInfo[0]; + *b = CPUInfo[1]; + *c = CPUInfo[2]; + *d = CPUInfo[3]; + + #endif +} + +Bool x86cpuid_CheckAndRead(Cx86cpuid *p) +{ + CHECK_CPUID_IS_SUPPORTED + MyCPUID(0, &p->maxFunc, &p->vendor[0], &p->vendor[2], &p->vendor[1]); + MyCPUID(1, &p->ver, &p->b, &p->c, &p->d); + return True; +} + +static UInt32 kVendors[][3] = +{ + { 0x756E6547, 0x49656E69, 0x6C65746E}, + { 0x68747541, 0x69746E65, 0x444D4163}, + { 0x746E6543, 0x48727561, 0x736C7561} +}; + +int x86cpuid_GetFirm(const Cx86cpuid *p) +{ + unsigned i; + for (i = 0; i < sizeof(kVendors) / sizeof(kVendors[i]); i++) + { + const UInt32 *v = kVendors[i]; + if (v[0] == p->vendor[0] && + v[1] == p->vendor[1] && + v[2] == p->vendor[2]) + return (int)i; + } + return -1; +} + +Bool CPU_Is_InOrder() +{ + Cx86cpuid p; + int firm; + UInt32 family, model; + if (!x86cpuid_CheckAndRead(&p)) + return True; + family = x86cpuid_GetFamily(&p); + model = x86cpuid_GetModel(&p); + firm = x86cpuid_GetFirm(&p); + switch (firm) + { + case CPU_FIRM_INTEL: return (family < 6 || (family == 6 && ( + /* Atom CPU */ + model == 0x100C /* 45 nm, N4xx, D4xx, N5xx, D5xx, 230, 330 */ + || model == 0x2006 /* 45 nm, Z6xx */ + || model == 0x2007 /* 32 nm, Z2460 */ + || model == 0x3005 /* 32 nm, Z2760 */ + || model == 0x3006 /* 32 nm, N2xxx, D2xxx */ + ))); + case CPU_FIRM_AMD: return (family < 5 || (family == 5 && (model < 6 || model == 0xA))); + case CPU_FIRM_VIA: return (family < 6 || (family == 6 && model < 0xF)); + } + return True; +} + +#if !defined(MY_CPU_AMD64) && defined(_WIN32) +#include <windows.h> +static Bool CPU_Sys_Is_SSE_Supported() +{ + OSVERSIONINFO vi; + vi.dwOSVersionInfoSize = sizeof(vi); + if (!GetVersionEx(&vi)) + return False; + return (vi.dwMajorVersion >= 5); +} +#define CHECK_SYS_SSE_SUPPORT if (!CPU_Sys_Is_SSE_Supported()) return False; +#else +#define CHECK_SYS_SSE_SUPPORT +#endif + +Bool CPU_Is_Aes_Supported() +{ + Cx86cpuid p; + CHECK_SYS_SSE_SUPPORT + if (!x86cpuid_CheckAndRead(&p)) + return False; + return (p.c >> 25) & 1; +} + +#endif
@@ -0,0 +1,157 @@
+/* CpuArch.h -- CPU specific code +2013-11-12: Igor Pavlov : Public domain */ + +#ifndef __CPU_ARCH_H +#define __CPU_ARCH_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +/* +MY_CPU_LE means that CPU is LITTLE ENDIAN. +If MY_CPU_LE is not defined, we don't know about that property of platform (it can be LITTLE ENDIAN). + +MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned memory accesses. +If MY_CPU_LE_UNALIGN is not defined, we don't know about these properties of platform. +*/ + +#if defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__) +#define MY_CPU_AMD64 +#endif + +#if defined(MY_CPU_AMD64) || defined(_M_IA64) +#define MY_CPU_64BIT +#endif + +#if defined(_M_IX86) || defined(__i386__) +#define MY_CPU_X86 +#endif + +#if defined(MY_CPU_X86) || defined(MY_CPU_AMD64) +#define MY_CPU_X86_OR_AMD64 +#endif + +#if defined(MY_CPU_X86) || defined(_M_ARM) +#define MY_CPU_32BIT +#endif + +#if defined(_WIN32) && defined(_M_ARM) +#define MY_CPU_ARM_LE +#endif + +#if defined(_WIN32) && defined(_M_IA64) +#define MY_CPU_IA64_LE +#endif + +#if defined(MY_CPU_X86_OR_AMD64) +#define MY_CPU_LE_UNALIGN +#endif + +#if defined(MY_CPU_X86_OR_AMD64) || defined(MY_CPU_ARM_LE) || defined(MY_CPU_IA64_LE) || defined(__ARMEL__) || defined(__MIPSEL__) || defined(__LITTLE_ENDIAN__) +#define MY_CPU_LE +#endif + +#if defined(__BIG_ENDIAN__) || defined(__m68k__) || defined(__ARMEB__) || defined(__MIPSEB__) +#define MY_CPU_BE +#endif + +#if defined(MY_CPU_LE) && defined(MY_CPU_BE) +Stop_Compiling_Bad_Endian +#endif + +#ifdef MY_CPU_LE_UNALIGN + +#define GetUi16(p) (*(const UInt16 *)(const void *)(p)) +#define GetUi32(p) (*(const UInt32 *)(const void *)(p)) +#define GetUi64(p) (*(const UInt64 *)(const void *)(p)) +#define SetUi16(p, d) *(UInt16 *)(p) = (d); +#define SetUi32(p, d) *(UInt32 *)(p) = (d); +#define SetUi64(p, d) *(UInt64 *)(p) = (d); + +#else + +#define GetUi16(p) (((const Byte *)(p))[0] | ((UInt16)((const Byte *)(p))[1] << 8)) + +#define GetUi32(p) ( \ + ((const Byte *)(p))[0] | \ + ((UInt32)((const Byte *)(p))[1] << 8) | \ + ((UInt32)((const Byte *)(p))[2] << 16) | \ + ((UInt32)((const Byte *)(p))[3] << 24)) + +#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32)) + +#define SetUi16(p, d) { UInt32 _x_ = (d); \ + ((Byte *)(p))[0] = (Byte)_x_; \ + ((Byte *)(p))[1] = (Byte)(_x_ >> 8); } + +#define SetUi32(p, d) { UInt32 _x_ = (d); \ + ((Byte *)(p))[0] = (Byte)_x_; \ + ((Byte *)(p))[1] = (Byte)(_x_ >> 8); \ + ((Byte *)(p))[2] = (Byte)(_x_ >> 16); \ + ((Byte *)(p))[3] = (Byte)(_x_ >> 24); } + +#define SetUi64(p, d) { UInt64 _x64_ = (d); \ + SetUi32(p, (UInt32)_x64_); \ + SetUi32(((Byte *)(p)) + 4, (UInt32)(_x64_ >> 32)); } + +#endif + +#if defined(MY_CPU_LE_UNALIGN) && defined(_WIN64) && (_MSC_VER >= 1300) + +#include <stdlib.h> + +#pragma intrinsic(_byteswap_ulong) +#pragma intrinsic(_byteswap_uint64) +#define GetBe32(p) _byteswap_ulong(*(const UInt32 *)(const Byte *)(p)) +#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const Byte *)(p)) + +#else + +#define GetBe32(p) ( \ + ((UInt32)((const Byte *)(p))[0] << 24) | \ + ((UInt32)((const Byte *)(p))[1] << 16) | \ + ((UInt32)((const Byte *)(p))[2] << 8) | \ + ((const Byte *)(p))[3] ) + +#define GetBe64(p) (((UInt64)GetBe32(p) << 32) | GetBe32(((const Byte *)(p)) + 4)) + +#endif + +#define GetBe16(p) ((UInt16)(((UInt16)((const Byte *)(p))[0] << 8) | ((const Byte *)(p))[1])) + + +#ifdef MY_CPU_X86_OR_AMD64 + +typedef struct +{ + UInt32 maxFunc; + UInt32 vendor[3]; + UInt32 ver; + UInt32 b; + UInt32 c; + UInt32 d; +} Cx86cpuid; + +enum +{ + CPU_FIRM_INTEL, + CPU_FIRM_AMD, + CPU_FIRM_VIA +}; + +Bool x86cpuid_CheckAndRead(Cx86cpuid *p); +int x86cpuid_GetFirm(const Cx86cpuid *p); + +#define x86cpuid_GetFamily(p) (((p)->ver >> 8) & 0xFF00F) +#define x86cpuid_GetModel(p) (((p)->ver >> 4) & 0xF00F) +#define x86cpuid_GetStepping(p) ((p)->ver & 0xF) + +Bool CPU_Is_InOrder(); +Bool CPU_Is_Aes_Supported(); + +#endif + +EXTERN_C_END + +#endif
@@ -0,0 +1,64 @@
+/* Delta.c -- Delta converter +2009-05-26 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Delta.h" + +void Delta_Init(Byte *state) +{ + unsigned i; + for (i = 0; i < DELTA_STATE_SIZE; i++) + state[i] = 0; +} + +static void MyMemCpy(Byte *dest, const Byte *src, unsigned size) +{ + unsigned i; + for (i = 0; i < size; i++) + dest[i] = src[i]; +} + +void Delta_Encode(Byte *state, unsigned delta, Byte *data, SizeT size) +{ + Byte buf[DELTA_STATE_SIZE]; + unsigned j = 0; + MyMemCpy(buf, state, delta); + { + SizeT i; + for (i = 0; i < size;) + { + for (j = 0; j < delta && i < size; i++, j++) + { + Byte b = data[i]; + data[i] = (Byte)(b - buf[j]); + buf[j] = b; + } + } + } + if (j == delta) + j = 0; + MyMemCpy(state, buf + j, delta - j); + MyMemCpy(state + delta - j, buf, j); +} + +void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size) +{ + Byte buf[DELTA_STATE_SIZE]; + unsigned j = 0; + MyMemCpy(buf, state, delta); + { + SizeT i; + for (i = 0; i < size;) + { + for (j = 0; j < delta && i < size; i++, j++) + { + buf[j] = data[i] = (Byte)(buf[j] + data[i]); + } + } + } + if (j == delta) + j = 0; + MyMemCpy(state, buf + j, delta - j); + MyMemCpy(state + delta - j, buf, j); +}
@@ -0,0 +1,19 @@
+/* Delta.h -- Delta converter +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __DELTA_H +#define __DELTA_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define DELTA_STATE_SIZE 256 + +void Delta_Init(Byte *state); +void Delta_Encode(Byte *state, unsigned delta, Byte *data, SizeT size); +void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size); + +EXTERN_C_END + +#endif
@@ -0,0 +1,763 @@
+/* LzFind.c -- Match finder for LZ algorithms +2009-04-22 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +#include "LzFind.h" +#include "LzHash.h" + +#define kEmptyHashValue 0 +#define kMaxValForNormalize ((UInt32)0xFFFFFFFF) +#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */ +#define kNormalizeMask (~(kNormalizeStepMin - 1)) +#define kMaxHistorySize ((UInt32)3 << 30) + +#define kStartMaxLen 3 + +static void LzInWindow_Free(CMatchFinder *p, ISzAlloc *alloc) +{ + if (!p->directInput) + { + alloc->Free(alloc, p->bufferBase); + p->bufferBase = 0; + } +} + +/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */ + +static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAlloc *alloc) +{ + UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv; + if (p->directInput) + { + p->blockSize = blockSize; + return 1; + } + if (p->bufferBase == 0 || p->blockSize != blockSize) + { + LzInWindow_Free(p, alloc); + p->blockSize = blockSize; + p->bufferBase = (Byte *)alloc->Alloc(alloc, (size_t)blockSize); + } + return (p->bufferBase != 0); +} + +Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; } +Byte MatchFinder_GetIndexByte(CMatchFinder *p, Int32 index) { return p->buffer[index]; } + +UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; } + +void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue) +{ + p->posLimit -= subValue; + p->pos -= subValue; + p->streamPos -= subValue; +} + +static void MatchFinder_ReadBlock(CMatchFinder *p) +{ + if (p->streamEndWasReached || p->result != SZ_OK) + return; + if (p->directInput) + { + UInt32 curSize = 0xFFFFFFFF - p->streamPos; + if (curSize > p->directInputRem) + curSize = (UInt32)p->directInputRem; + p->directInputRem -= curSize; + p->streamPos += curSize; + if (p->directInputRem == 0) + p->streamEndWasReached = 1; + return; + } + for (;;) + { + Byte *dest = p->buffer + (p->streamPos - p->pos); + size_t size = (p->bufferBase + p->blockSize - dest); + if (size == 0) + return; + p->result = p->stream->Read(p->stream, dest, &size); + if (p->result != SZ_OK) + return; + if (size == 0) + { + p->streamEndWasReached = 1; + return; + } + p->streamPos += (UInt32)size; + if (p->streamPos - p->pos > p->keepSizeAfter) + return; + } +} + +void MatchFinder_MoveBlock(CMatchFinder *p) +{ + memmove(p->bufferBase, + p->buffer - p->keepSizeBefore, + (size_t)(p->streamPos - p->pos + p->keepSizeBefore)); + p->buffer = p->bufferBase + p->keepSizeBefore; +} + +int MatchFinder_NeedMove(CMatchFinder *p) +{ + if (p->directInput) + return 0; + /* if (p->streamEndWasReached) return 0; */ + return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter); +} + +void MatchFinder_ReadIfRequired(CMatchFinder *p) +{ + if (p->streamEndWasReached) + return; + if (p->keepSizeAfter >= p->streamPos - p->pos) + MatchFinder_ReadBlock(p); +} + +static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p) +{ + if (MatchFinder_NeedMove(p)) + MatchFinder_MoveBlock(p); + MatchFinder_ReadBlock(p); +} + +static void MatchFinder_SetDefaultSettings(CMatchFinder *p) +{ + p->cutValue = 32; + p->btMode = 1; + p->numHashBytes = 4; + p->bigHash = 0; +} + +#define kCrcPoly 0xEDB88320 + +void MatchFinder_Construct(CMatchFinder *p) +{ + UInt32 i; + p->bufferBase = 0; + p->directInput = 0; + p->hash = 0; + MatchFinder_SetDefaultSettings(p); + + for (i = 0; i < 256; i++) + { + UInt32 r = i; + int j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + p->crc[i] = r; + } +} + +static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->hash); + p->hash = 0; +} + +void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc) +{ + MatchFinder_FreeThisClassMemory(p, alloc); + LzInWindow_Free(p, alloc); +} + +static CLzRef* AllocRefs(UInt32 num, ISzAlloc *alloc) +{ + size_t sizeInBytes = (size_t)num * sizeof(CLzRef); + if (sizeInBytes / sizeof(CLzRef) != num) + return 0; + return (CLzRef *)alloc->Alloc(alloc, sizeInBytes); +} + +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAlloc *alloc) +{ + UInt32 sizeReserv; + if (historySize > kMaxHistorySize) + { + MatchFinder_Free(p, alloc); + return 0; + } + sizeReserv = historySize >> 1; + if (historySize > ((UInt32)2 << 30)) + sizeReserv = historySize >> 2; + sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19); + + p->keepSizeBefore = historySize + keepAddBufferBefore + 1; + p->keepSizeAfter = matchMaxLen + keepAddBufferAfter; + /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */ + if (LzInWindow_Create(p, sizeReserv, alloc)) + { + UInt32 newCyclicBufferSize = historySize + 1; + UInt32 hs; + p->matchMaxLen = matchMaxLen; + { + p->fixedHashSize = 0; + if (p->numHashBytes == 2) + hs = (1 << 16) - 1; + else + { + hs = historySize - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + hs |= 0xFFFF; /* don't change it! It's required for Deflate */ + if (hs > (1 << 24)) + { + if (p->numHashBytes == 3) + hs = (1 << 24) - 1; + else + hs >>= 1; + } + } + p->hashMask = hs; + hs++; + if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size; + if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size; + if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size; + hs += p->fixedHashSize; + } + + { + UInt32 prevSize = p->hashSizeSum + p->numSons; + UInt32 newSize; + p->historySize = historySize; + p->hashSizeSum = hs; + p->cyclicBufferSize = newCyclicBufferSize; + p->numSons = (p->btMode ? newCyclicBufferSize * 2 : newCyclicBufferSize); + newSize = p->hashSizeSum + p->numSons; + if (p->hash != 0 && prevSize == newSize) + return 1; + MatchFinder_FreeThisClassMemory(p, alloc); + p->hash = AllocRefs(newSize, alloc); + if (p->hash != 0) + { + p->son = p->hash + p->hashSizeSum; + return 1; + } + } + } + MatchFinder_Free(p, alloc); + return 0; +} + +static void MatchFinder_SetLimits(CMatchFinder *p) +{ + UInt32 limit = kMaxValForNormalize - p->pos; + UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos; + if (limit2 < limit) + limit = limit2; + limit2 = p->streamPos - p->pos; + if (limit2 <= p->keepSizeAfter) + { + if (limit2 > 0) + limit2 = 1; + } + else + limit2 -= p->keepSizeAfter; + if (limit2 < limit) + limit = limit2; + { + UInt32 lenLimit = p->streamPos - p->pos; + if (lenLimit > p->matchMaxLen) + lenLimit = p->matchMaxLen; + p->lenLimit = lenLimit; + } + p->posLimit = p->pos + limit; +} + +void MatchFinder_Init(CMatchFinder *p) +{ + UInt32 i; + for (i = 0; i < p->hashSizeSum; i++) + p->hash[i] = kEmptyHashValue; + p->cyclicBufferPos = 0; + p->buffer = p->bufferBase; + p->pos = p->streamPos = p->cyclicBufferSize; + p->result = SZ_OK; + p->streamEndWasReached = 0; + MatchFinder_ReadBlock(p); + MatchFinder_SetLimits(p); +} + +static UInt32 MatchFinder_GetSubValue(CMatchFinder *p) +{ + return (p->pos - p->historySize - 1) & kNormalizeMask; +} + +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems) +{ + UInt32 i; + for (i = 0; i < numItems; i++) + { + UInt32 value = items[i]; + if (value <= subValue) + value = kEmptyHashValue; + else + value -= subValue; + items[i] = value; + } +} + +static void MatchFinder_Normalize(CMatchFinder *p) +{ + UInt32 subValue = MatchFinder_GetSubValue(p); + MatchFinder_Normalize3(subValue, p->hash, p->hashSizeSum + p->numSons); + MatchFinder_ReduceOffsets(p, subValue); +} + +static void MatchFinder_CheckLimits(CMatchFinder *p) +{ + if (p->pos == kMaxValForNormalize) + MatchFinder_Normalize(p); + if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos) + MatchFinder_CheckAndMoveAndRead(p); + if (p->cyclicBufferPos == p->cyclicBufferSize) + p->cyclicBufferPos = 0; + MatchFinder_SetLimits(p); +} + +static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *distances, UInt32 maxLen) +{ + son[_cyclicBufferPos] = curMatch; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + return distances; + { + const Byte *pb = cur - delta; + curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)]; + if (pb[maxLen] == cur[maxLen] && *pb == *cur) + { + UInt32 len = 0; + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + *distances++ = maxLen = len; + *distances++ = delta - 1; + if (len == lenLimit) + return distances; + } + } + } + } +} + +UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *distances, UInt32 maxLen) +{ + CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + (_cyclicBufferPos << 1); + UInt32 len0 = 0, len1 = 0; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + return distances; + } + { + CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + UInt32 len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + if (++len != lenLimit && pb[len] == cur[len]) + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + *distances++ = maxLen = len; + *distances++ = delta - 1; + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + return distances; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } +} + +static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue) +{ + CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + (_cyclicBufferPos << 1); + UInt32 len0 = 0, len1 = 0; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + return; + } + { + CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + UInt32 len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + { + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + return; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } +} + +#define MOVE_POS \ + ++p->cyclicBufferPos; \ + p->buffer++; \ + if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p); + +#define MOVE_POS_RET MOVE_POS return offset; + +static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; } + +#define GET_MATCHES_HEADER2(minLen, ret_op) \ + UInt32 lenLimit; UInt32 hashValue; const Byte *cur; UInt32 curMatch; \ + lenLimit = p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \ + cur = p->buffer; + +#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0) +#define SKIP_HEADER(minLen) GET_MATCHES_HEADER2(minLen, continue) + +#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue + +#define GET_MATCHES_FOOTER(offset, maxLen) \ + offset = (UInt32)(GetMatchesSpec1(lenLimit, curMatch, MF_PARAMS(p), \ + distances + offset, maxLen) - distances); MOVE_POS_RET; + +#define SKIP_FOOTER \ + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS; + +static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 offset; + GET_MATCHES_HEADER(2) + HASH2_CALC; + curMatch = p->hash[hashValue]; + p->hash[hashValue] = p->pos; + offset = 0; + GET_MATCHES_FOOTER(offset, 1) +} + +UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 offset; + GET_MATCHES_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hashValue]; + p->hash[hashValue] = p->pos; + offset = 0; + GET_MATCHES_FOOTER(offset, 2) +} + +static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 hash2Value, delta2, maxLen, offset; + GET_MATCHES_HEADER(3) + + HASH3_CALC; + + delta2 = p->pos - p->hash[hash2Value]; + curMatch = p->hash[kFix3HashSize + hashValue]; + + p->hash[hash2Value] = + p->hash[kFix3HashSize + hashValue] = p->pos; + + + maxLen = 2; + offset = 0; + if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur) + { + for (; maxLen != lenLimit; maxLen++) + if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen]) + break; + distances[0] = maxLen; + distances[1] = delta2 - 1; + offset = 2; + if (maxLen == lenLimit) + { + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + GET_MATCHES_FOOTER(offset, maxLen) +} + +static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset; + GET_MATCHES_HEADER(4) + + HASH4_CALC; + + delta2 = p->pos - p->hash[ hash2Value]; + delta3 = p->pos - p->hash[kFix3HashSize + hash3Value]; + curMatch = p->hash[kFix4HashSize + hashValue]; + + p->hash[ hash2Value] = + p->hash[kFix3HashSize + hash3Value] = + p->hash[kFix4HashSize + hashValue] = p->pos; + + maxLen = 1; + offset = 0; + if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = delta2 - 1; + offset = 2; + } + if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur) + { + maxLen = 3; + distances[offset + 1] = delta3 - 1; + offset += 2; + delta2 = delta3; + } + if (offset != 0) + { + for (; maxLen != lenLimit; maxLen++) + if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen]) + break; + distances[offset - 2] = maxLen; + if (maxLen == lenLimit) + { + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + if (maxLen < 3) + maxLen = 3; + GET_MATCHES_FOOTER(offset, maxLen) +} + +static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 hash2Value, hash3Value, delta2, delta3, maxLen, offset; + GET_MATCHES_HEADER(4) + + HASH4_CALC; + + delta2 = p->pos - p->hash[ hash2Value]; + delta3 = p->pos - p->hash[kFix3HashSize + hash3Value]; + curMatch = p->hash[kFix4HashSize + hashValue]; + + p->hash[ hash2Value] = + p->hash[kFix3HashSize + hash3Value] = + p->hash[kFix4HashSize + hashValue] = p->pos; + + maxLen = 1; + offset = 0; + if (delta2 < p->cyclicBufferSize && *(cur - delta2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = delta2 - 1; + offset = 2; + } + if (delta2 != delta3 && delta3 < p->cyclicBufferSize && *(cur - delta3) == *cur) + { + maxLen = 3; + distances[offset + 1] = delta3 - 1; + offset += 2; + delta2 = delta3; + } + if (offset != 0) + { + for (; maxLen != lenLimit; maxLen++) + if (cur[(ptrdiff_t)maxLen - delta2] != cur[maxLen]) + break; + distances[offset - 2] = maxLen; + if (maxLen == lenLimit) + { + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS_RET; + } + } + if (maxLen < 3) + maxLen = 3; + offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances + offset, maxLen) - (distances)); + MOVE_POS_RET +} + +UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 offset; + GET_MATCHES_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hashValue]; + p->hash[hashValue] = p->pos; + offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances, 2) - (distances)); + MOVE_POS_RET +} + +static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(2) + HASH2_CALC; + curMatch = p->hash[hashValue]; + p->hash[hashValue] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hashValue]; + p->hash[hashValue] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 hash2Value; + SKIP_HEADER(3) + HASH3_CALC; + curMatch = p->hash[kFix3HashSize + hashValue]; + p->hash[hash2Value] = + p->hash[kFix3HashSize + hashValue] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 hash2Value, hash3Value; + SKIP_HEADER(4) + HASH4_CALC; + curMatch = p->hash[kFix4HashSize + hashValue]; + p->hash[ hash2Value] = + p->hash[kFix3HashSize + hash3Value] = p->pos; + p->hash[kFix4HashSize + hashValue] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 hash2Value, hash3Value; + SKIP_HEADER(4) + HASH4_CALC; + curMatch = p->hash[kFix4HashSize + hashValue]; + p->hash[ hash2Value] = + p->hash[kFix3HashSize + hash3Value] = + p->hash[kFix4HashSize + hashValue] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} + +void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hashValue]; + p->hash[hashValue] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable) +{ + vTable->Init = (Mf_Init_Func)MatchFinder_Init; + vTable->GetIndexByte = (Mf_GetIndexByte_Func)MatchFinder_GetIndexByte; + vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos; + if (!p->btMode) + { + vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip; + } + else if (p->numHashBytes == 2) + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip; + } + else if (p->numHashBytes == 3) + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip; + } + else + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip; + } +}
@@ -0,0 +1,111 @@
+/* LzFind.h -- Match finder for LZ algorithms +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZ_FIND_H +#define __LZ_FIND_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +typedef UInt32 CLzRef; + +typedef struct _CMatchFinder +{ + Byte *buffer; + UInt32 pos; + UInt32 posLimit; + UInt32 streamPos; + UInt32 lenLimit; + + UInt32 cyclicBufferPos; + UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */ + + UInt32 matchMaxLen; + CLzRef *hash; + CLzRef *son; + UInt32 hashMask; + UInt32 cutValue; + + Byte *bufferBase; + ISeqInStream *stream; + int streamEndWasReached; + + UInt32 blockSize; + UInt32 keepSizeBefore; + UInt32 keepSizeAfter; + + UInt32 numHashBytes; + int directInput; + size_t directInputRem; + int btMode; + int bigHash; + UInt32 historySize; + UInt32 fixedHashSize; + UInt32 hashSizeSum; + UInt32 numSons; + SRes result; + UInt32 crc[256]; +} CMatchFinder; + +#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer) +#define Inline_MatchFinder_GetIndexByte(p, index) ((p)->buffer[(Int32)(index)]) + +#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos) + +int MatchFinder_NeedMove(CMatchFinder *p); +Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p); +void MatchFinder_MoveBlock(CMatchFinder *p); +void MatchFinder_ReadIfRequired(CMatchFinder *p); + +void MatchFinder_Construct(CMatchFinder *p); + +/* Conditions: + historySize <= 3 GB + keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB +*/ +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAlloc *alloc); +void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc); +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, UInt32 numItems); +void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue); + +UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, + UInt32 *distances, UInt32 maxLen); + +/* +Conditions: + Mf_GetNumAvailableBytes_Func must be called before each Mf_GetMatchLen_Func. + Mf_GetPointerToCurrentPos_Func's result must be used only before any other function +*/ + +typedef void (*Mf_Init_Func)(void *object); +typedef Byte (*Mf_GetIndexByte_Func)(void *object, Int32 index); +typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object); +typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object); +typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances); +typedef void (*Mf_Skip_Func)(void *object, UInt32); + +typedef struct _IMatchFinder +{ + Mf_Init_Func Init; + Mf_GetIndexByte_Func GetIndexByte; + Mf_GetNumAvailableBytes_Func GetNumAvailableBytes; + Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos; + Mf_GetMatches_Func GetMatches; + Mf_Skip_Func Skip; +} IMatchFinder; + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable); + +void MatchFinder_Init(CMatchFinder *p); +UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); +UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); +void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); +void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); + +EXTERN_C_END + +#endif
@@ -0,0 +1,794 @@
+/* LzFindMt.c -- multithreaded Match finder for LZ algorithms +2014-12-29 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "LzHash.h" + +#include "LzFindMt.h" + +void MtSync_Construct(CMtSync *p) +{ + p->wasCreated = False; + p->csWasInitialized = False; + p->csWasEntered = False; + Thread_Construct(&p->thread); + Event_Construct(&p->canStart); + Event_Construct(&p->wasStarted); + Event_Construct(&p->wasStopped); + Semaphore_Construct(&p->freeSemaphore); + Semaphore_Construct(&p->filledSemaphore); +} + +void MtSync_GetNextBlock(CMtSync *p) +{ + if (p->needStart) + { + p->numProcessedBlocks = 1; + p->needStart = False; + p->stopWriting = False; + p->exit = False; + Event_Reset(&p->wasStarted); + Event_Reset(&p->wasStopped); + + Event_Set(&p->canStart); + Event_Wait(&p->wasStarted); + } + else + { + CriticalSection_Leave(&p->cs); + p->csWasEntered = False; + p->numProcessedBlocks++; + Semaphore_Release1(&p->freeSemaphore); + } + Semaphore_Wait(&p->filledSemaphore); + CriticalSection_Enter(&p->cs); + p->csWasEntered = True; +} + +/* MtSync_StopWriting must be called if Writing was started */ + +void MtSync_StopWriting(CMtSync *p) +{ + UInt32 myNumBlocks = p->numProcessedBlocks; + if (!Thread_WasCreated(&p->thread) || p->needStart) + return; + p->stopWriting = True; + if (p->csWasEntered) + { + CriticalSection_Leave(&p->cs); + p->csWasEntered = False; + } + Semaphore_Release1(&p->freeSemaphore); + + Event_Wait(&p->wasStopped); + + while (myNumBlocks++ != p->numProcessedBlocks) + { + Semaphore_Wait(&p->filledSemaphore); + Semaphore_Release1(&p->freeSemaphore); + } + p->needStart = True; +} + +void MtSync_Destruct(CMtSync *p) +{ + if (Thread_WasCreated(&p->thread)) + { + MtSync_StopWriting(p); + p->exit = True; + if (p->needStart) + Event_Set(&p->canStart); + Thread_Wait(&p->thread); + Thread_Close(&p->thread); + } + if (p->csWasInitialized) + { + CriticalSection_Delete(&p->cs); + p->csWasInitialized = False; + } + + Event_Close(&p->canStart); + Event_Close(&p->wasStarted); + Event_Close(&p->wasStopped); + Semaphore_Close(&p->freeSemaphore); + Semaphore_Close(&p->filledSemaphore); + + p->wasCreated = False; +} + +#define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; } + +static SRes MtSync_Create2(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj, UInt32 numBlocks) +{ + if (p->wasCreated) + return SZ_OK; + + RINOK_THREAD(CriticalSection_Init(&p->cs)); + p->csWasInitialized = True; + + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canStart)); + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStarted)); + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStopped)); + + RINOK_THREAD(Semaphore_Create(&p->freeSemaphore, numBlocks, numBlocks)); + RINOK_THREAD(Semaphore_Create(&p->filledSemaphore, 0, numBlocks)); + + p->needStart = True; + + RINOK_THREAD(Thread_Create(&p->thread, startAddress, obj)); + p->wasCreated = True; + return SZ_OK; +} + +static SRes MtSync_Create(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj, UInt32 numBlocks) +{ + SRes res = MtSync_Create2(p, startAddress, obj, numBlocks); + if (res != SZ_OK) + MtSync_Destruct(p); + return res; +} + +void MtSync_Init(CMtSync *p) { p->needStart = True; } + +#define kMtMaxValForNormalize 0xFFFFFFFF + +#define DEF_GetHeads2(name, v, action) \ +static void GetHeads ## name(const Byte *p, UInt32 pos, \ +UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc) \ +{ action; for (; numHeads != 0; numHeads--) { \ +const UInt32 value = (v); p++; *heads++ = pos - hash[value]; hash[value] = pos++; } } + +#define DEF_GetHeads(name, v) DEF_GetHeads2(name, v, ;) + +DEF_GetHeads2(2, (p[0] | ((UInt32)p[1] << 8)), hashMask = hashMask; crc = crc; ) +DEF_GetHeads(3, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8)) & hashMask) +DEF_GetHeads(4, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5)) & hashMask) +DEF_GetHeads(4b, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ ((UInt32)p[3] << 16)) & hashMask) +/* DEF_GetHeads(5, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5) ^ (crc[p[4]] << 3)) & hashMask) */ + +void HashThreadFunc(CMatchFinderMt *mt) +{ + CMtSync *p = &mt->hashSync; + for (;;) + { + UInt32 numProcessedBlocks = 0; + Event_Wait(&p->canStart); + Event_Set(&p->wasStarted); + for (;;) + { + if (p->exit) + return; + if (p->stopWriting) + { + p->numProcessedBlocks = numProcessedBlocks; + Event_Set(&p->wasStopped); + break; + } + + { + CMatchFinder *mf = mt->MatchFinder; + if (MatchFinder_NeedMove(mf)) + { + CriticalSection_Enter(&mt->btSync.cs); + CriticalSection_Enter(&mt->hashSync.cs); + { + const Byte *beforePtr = MatchFinder_GetPointerToCurrentPos(mf); + const Byte *afterPtr; + MatchFinder_MoveBlock(mf); + afterPtr = MatchFinder_GetPointerToCurrentPos(mf); + mt->pointerToCurPos -= beforePtr - afterPtr; + mt->buffer -= beforePtr - afterPtr; + } + CriticalSection_Leave(&mt->btSync.cs); + CriticalSection_Leave(&mt->hashSync.cs); + continue; + } + + Semaphore_Wait(&p->freeSemaphore); + + MatchFinder_ReadIfRequired(mf); + if (mf->pos > (kMtMaxValForNormalize - kMtHashBlockSize)) + { + UInt32 subValue = (mf->pos - mf->historySize - 1); + MatchFinder_ReduceOffsets(mf, subValue); + MatchFinder_Normalize3(subValue, mf->hash + mf->fixedHashSize, mf->hashMask + 1); + } + { + UInt32 *heads = mt->hashBuf + ((numProcessedBlocks++) & kMtHashNumBlocksMask) * kMtHashBlockSize; + UInt32 num = mf->streamPos - mf->pos; + heads[0] = 2; + heads[1] = num; + if (num >= mf->numHashBytes) + { + num = num - mf->numHashBytes + 1; + if (num > kMtHashBlockSize - 2) + num = kMtHashBlockSize - 2; + mt->GetHeadsFunc(mf->buffer, mf->pos, mf->hash + mf->fixedHashSize, mf->hashMask, heads + 2, num, mf->crc); + heads[0] += num; + } + mf->pos += num; + mf->buffer += num; + } + } + + Semaphore_Release1(&p->filledSemaphore); + } + } +} + +void MatchFinderMt_GetNextBlock_Hash(CMatchFinderMt *p) +{ + MtSync_GetNextBlock(&p->hashSync); + p->hashBufPosLimit = p->hashBufPos = ((p->hashSync.numProcessedBlocks - 1) & kMtHashNumBlocksMask) * kMtHashBlockSize; + p->hashBufPosLimit += p->hashBuf[p->hashBufPos++]; + p->hashNumAvail = p->hashBuf[p->hashBufPos++]; +} + +#define kEmptyHashValue 0 + +/* #define MFMT_GM_INLINE */ + +#ifdef MFMT_GM_INLINE + +#define NO_INLINE MY_FAST_CALL + +Int32 NO_INLINE GetMatchesSpecN(UInt32 lenLimit, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, + UInt32 *_distances, UInt32 _maxLen, const UInt32 *hash, Int32 limit, UInt32 size, UInt32 *posRes) +{ + do + { + UInt32 *distances = _distances + 1; + UInt32 curMatch = pos - *hash++; + + CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + (_cyclicBufferPos << 1); + UInt32 len0 = 0, len1 = 0; + UInt32 cutValue = _cutValue; + UInt32 maxLen = _maxLen; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + break; + } + { + CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + UInt32 len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + if (++len != lenLimit && pb[len] == cur[len]) + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + *distances++ = maxLen = len; + *distances++ = delta - 1; + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + break; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } + pos++; + _cyclicBufferPos++; + cur++; + { + UInt32 num = (UInt32)(distances - _distances); + *_distances = num - 1; + _distances += num; + limit -= num; + } + } + while (limit > 0 && --size != 0); + *posRes = pos; + return limit; +} + +#endif + +void BtGetMatches(CMatchFinderMt *p, UInt32 *distances) +{ + UInt32 numProcessed = 0; + UInt32 curPos = 2; + UInt32 limit = kMtBtBlockSize - (p->matchMaxLen * 2); + distances[1] = p->hashNumAvail; + while (curPos < limit) + { + if (p->hashBufPos == p->hashBufPosLimit) + { + MatchFinderMt_GetNextBlock_Hash(p); + distances[1] = numProcessed + p->hashNumAvail; + if (p->hashNumAvail >= p->numHashBytes) + continue; + for (; p->hashNumAvail != 0; p->hashNumAvail--) + distances[curPos++] = 0; + break; + } + { + UInt32 size = p->hashBufPosLimit - p->hashBufPos; + UInt32 lenLimit = p->matchMaxLen; + UInt32 pos = p->pos; + UInt32 cyclicBufferPos = p->cyclicBufferPos; + if (lenLimit >= p->hashNumAvail) + lenLimit = p->hashNumAvail; + { + UInt32 size2 = p->hashNumAvail - lenLimit + 1; + if (size2 < size) + size = size2; + size2 = p->cyclicBufferSize - cyclicBufferPos; + if (size2 < size) + size = size2; + } + #ifndef MFMT_GM_INLINE + while (curPos < limit && size-- != 0) + { + UInt32 *startDistances = distances + curPos; + UInt32 num = (UInt32)(GetMatchesSpec1(lenLimit, pos - p->hashBuf[p->hashBufPos++], + pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue, + startDistances + 1, p->numHashBytes - 1) - startDistances); + *startDistances = num - 1; + curPos += num; + cyclicBufferPos++; + pos++; + p->buffer++; + } + #else + { + UInt32 posRes; + curPos = limit - GetMatchesSpecN(lenLimit, pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue, + distances + curPos, p->numHashBytes - 1, p->hashBuf + p->hashBufPos, (Int32)(limit - curPos) , size, &posRes); + p->hashBufPos += posRes - pos; + cyclicBufferPos += posRes - pos; + p->buffer += posRes - pos; + pos = posRes; + } + #endif + + numProcessed += pos - p->pos; + p->hashNumAvail -= pos - p->pos; + p->pos = pos; + if (cyclicBufferPos == p->cyclicBufferSize) + cyclicBufferPos = 0; + p->cyclicBufferPos = cyclicBufferPos; + } + } + distances[0] = curPos; +} + +void BtFillBlock(CMatchFinderMt *p, UInt32 globalBlockIndex) +{ + CMtSync *sync = &p->hashSync; + if (!sync->needStart) + { + CriticalSection_Enter(&sync->cs); + sync->csWasEntered = True; + } + + BtGetMatches(p, p->btBuf + (globalBlockIndex & kMtBtNumBlocksMask) * kMtBtBlockSize); + + if (p->pos > kMtMaxValForNormalize - kMtBtBlockSize) + { + UInt32 subValue = p->pos - p->cyclicBufferSize; + MatchFinder_Normalize3(subValue, p->son, p->cyclicBufferSize * 2); + p->pos -= subValue; + } + + if (!sync->needStart) + { + CriticalSection_Leave(&sync->cs); + sync->csWasEntered = False; + } +} + +void BtThreadFunc(CMatchFinderMt *mt) +{ + CMtSync *p = &mt->btSync; + for (;;) + { + UInt32 blockIndex = 0; + Event_Wait(&p->canStart); + Event_Set(&p->wasStarted); + for (;;) + { + if (p->exit) + return; + if (p->stopWriting) + { + p->numProcessedBlocks = blockIndex; + MtSync_StopWriting(&mt->hashSync); + Event_Set(&p->wasStopped); + break; + } + Semaphore_Wait(&p->freeSemaphore); + BtFillBlock(mt, blockIndex++); + Semaphore_Release1(&p->filledSemaphore); + } + } +} + +void MatchFinderMt_Construct(CMatchFinderMt *p) +{ + p->hashBuf = 0; + MtSync_Construct(&p->hashSync); + MtSync_Construct(&p->btSync); +} + +void MatchFinderMt_FreeMem(CMatchFinderMt *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->hashBuf); + p->hashBuf = 0; +} + +void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc) +{ + MtSync_Destruct(&p->hashSync); + MtSync_Destruct(&p->btSync); + MatchFinderMt_FreeMem(p, alloc); +} + +#define kHashBufferSize (kMtHashBlockSize * kMtHashNumBlocks) +#define kBtBufferSize (kMtBtBlockSize * kMtBtNumBlocks) + +static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE HashThreadFunc2(void *p) { HashThreadFunc((CMatchFinderMt *)p); return 0; } +static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE BtThreadFunc2(void *p) +{ + Byte allocaDummy[0x180]; + allocaDummy[0] = 0; + allocaDummy[1] = allocaDummy[0]; + BtThreadFunc((CMatchFinderMt *)p); + return 0; +} + +SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc) +{ + CMatchFinder *mf = p->MatchFinder; + p->historySize = historySize; + if (kMtBtBlockSize <= matchMaxLen * 4) + return SZ_ERROR_PARAM; + if (p->hashBuf == 0) + { + p->hashBuf = (UInt32 *)alloc->Alloc(alloc, (kHashBufferSize + kBtBufferSize) * sizeof(UInt32)); + if (p->hashBuf == 0) + return SZ_ERROR_MEM; + p->btBuf = p->hashBuf + kHashBufferSize; + } + keepAddBufferBefore += (kHashBufferSize + kBtBufferSize); + keepAddBufferAfter += kMtHashBlockSize; + if (!MatchFinder_Create(mf, historySize, keepAddBufferBefore, matchMaxLen, keepAddBufferAfter, alloc)) + return SZ_ERROR_MEM; + + RINOK(MtSync_Create(&p->hashSync, HashThreadFunc2, p, kMtHashNumBlocks)); + RINOK(MtSync_Create(&p->btSync, BtThreadFunc2, p, kMtBtNumBlocks)); + return SZ_OK; +} + +/* Call it after ReleaseStream / SetStream */ +void MatchFinderMt_Init(CMatchFinderMt *p) +{ + CMatchFinder *mf = p->MatchFinder; + p->btBufPos = p->btBufPosLimit = 0; + p->hashBufPos = p->hashBufPosLimit = 0; + MatchFinder_Init(mf); + p->pointerToCurPos = MatchFinder_GetPointerToCurrentPos(mf); + p->btNumAvailBytes = 0; + p->lzPos = p->historySize + 1; + + p->hash = mf->hash; + p->fixedHashSize = mf->fixedHashSize; + p->crc = mf->crc; + + p->son = mf->son; + p->matchMaxLen = mf->matchMaxLen; + p->numHashBytes = mf->numHashBytes; + p->pos = mf->pos; + p->buffer = mf->buffer; + p->cyclicBufferPos = mf->cyclicBufferPos; + p->cyclicBufferSize = mf->cyclicBufferSize; + p->cutValue = mf->cutValue; +} + +/* ReleaseStream is required to finish multithreading */ +void MatchFinderMt_ReleaseStream(CMatchFinderMt *p) +{ + MtSync_StopWriting(&p->btSync); + /* p->MatchFinder->ReleaseStream(); */ +} + +void MatchFinderMt_Normalize(CMatchFinderMt *p) +{ + MatchFinder_Normalize3(p->lzPos - p->historySize - 1, p->hash, p->fixedHashSize); + p->lzPos = p->historySize + 1; +} + +void MatchFinderMt_GetNextBlock_Bt(CMatchFinderMt *p) +{ + UInt32 blockIndex; + MtSync_GetNextBlock(&p->btSync); + blockIndex = ((p->btSync.numProcessedBlocks - 1) & kMtBtNumBlocksMask); + p->btBufPosLimit = p->btBufPos = blockIndex * kMtBtBlockSize; + p->btBufPosLimit += p->btBuf[p->btBufPos++]; + p->btNumAvailBytes = p->btBuf[p->btBufPos++]; + if (p->lzPos >= kMtMaxValForNormalize - kMtBtBlockSize) + MatchFinderMt_Normalize(p); +} + +const Byte * MatchFinderMt_GetPointerToCurrentPos(CMatchFinderMt *p) +{ + return p->pointerToCurPos; +} + +#define GET_NEXT_BLOCK_IF_REQUIRED if (p->btBufPos == p->btBufPosLimit) MatchFinderMt_GetNextBlock_Bt(p); + +UInt32 MatchFinderMt_GetNumAvailableBytes(CMatchFinderMt *p) +{ + GET_NEXT_BLOCK_IF_REQUIRED; + return p->btNumAvailBytes; +} + +Byte MatchFinderMt_GetIndexByte(CMatchFinderMt *p, Int32 index) +{ + return p->pointerToCurPos[index]; +} + +UInt32 * MixMatches2(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +{ + UInt32 hash2Value, curMatch2; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + UInt32 lzPos = p->lzPos; + MT_HASH2_CALC + + curMatch2 = hash[hash2Value]; + hash[hash2Value] = lzPos; + + if (curMatch2 >= matchMinPos) + if (cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + { + *distances++ = 2; + *distances++ = lzPos - curMatch2 - 1; + } + return distances; +} + +UInt32 * MixMatches3(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +{ + UInt32 hash2Value, hash3Value, curMatch2, curMatch3; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + UInt32 lzPos = p->lzPos; + MT_HASH3_CALC + + curMatch2 = hash[ hash2Value]; + curMatch3 = hash[kFix3HashSize + hash3Value]; + + hash[ hash2Value] = + hash[kFix3HashSize + hash3Value] = + lzPos; + + if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + { + distances[1] = lzPos - curMatch2 - 1; + if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2]) + { + distances[0] = 3; + return distances + 2; + } + distances[0] = 2; + distances += 2; + } + if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0]) + { + *distances++ = 3; + *distances++ = lzPos - curMatch3 - 1; + } + return distances; +} + +/* +UInt32 *MixMatches4(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +{ + UInt32 hash2Value, hash3Value, hash4Value, curMatch2, curMatch3, curMatch4; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + UInt32 lzPos = p->lzPos; + MT_HASH4_CALC + + curMatch2 = hash[ hash2Value]; + curMatch3 = hash[kFix3HashSize + hash3Value]; + curMatch4 = hash[kFix4HashSize + hash4Value]; + + hash[ hash2Value] = + hash[kFix3HashSize + hash3Value] = + hash[kFix4HashSize + hash4Value] = + lzPos; + + if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + { + distances[1] = lzPos - curMatch2 - 1; + if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2]) + { + distances[0] = (cur[(ptrdiff_t)curMatch2 - lzPos + 3] == cur[3]) ? 4 : 3; + return distances + 2; + } + distances[0] = 2; + distances += 2; + } + if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0]) + { + distances[1] = lzPos - curMatch3 - 1; + if (cur[(ptrdiff_t)curMatch3 - lzPos + 3] == cur[3]) + { + distances[0] = 4; + return distances + 2; + } + distances[0] = 3; + distances += 2; + } + + if (curMatch4 >= matchMinPos) + if ( + cur[(ptrdiff_t)curMatch4 - lzPos] == cur[0] && + cur[(ptrdiff_t)curMatch4 - lzPos + 3] == cur[3] + ) + { + *distances++ = 4; + *distances++ = lzPos - curMatch4 - 1; + } + return distances; +} +*/ + +#define INCREASE_LZ_POS p->lzPos++; p->pointerToCurPos++; + +UInt32 MatchFinderMt2_GetMatches(CMatchFinderMt *p, UInt32 *distances) +{ + const UInt32 *btBuf = p->btBuf + p->btBufPos; + UInt32 len = *btBuf++; + p->btBufPos += 1 + len; + p->btNumAvailBytes--; + { + UInt32 i; + for (i = 0; i < len; i += 2) + { + *distances++ = *btBuf++; + *distances++ = *btBuf++; + } + } + INCREASE_LZ_POS + return len; +} + +UInt32 MatchFinderMt_GetMatches(CMatchFinderMt *p, UInt32 *distances) +{ + const UInt32 *btBuf = p->btBuf + p->btBufPos; + UInt32 len = *btBuf++; + p->btBufPos += 1 + len; + + if (len == 0) + { + if (p->btNumAvailBytes-- >= 4) + len = (UInt32)(p->MixMatchesFunc(p, p->lzPos - p->historySize, distances) - (distances)); + } + else + { + /* Condition: there are matches in btBuf with length < p->numHashBytes */ + UInt32 *distances2; + p->btNumAvailBytes--; + distances2 = p->MixMatchesFunc(p, p->lzPos - btBuf[1], distances); + do + { + *distances2++ = *btBuf++; + *distances2++ = *btBuf++; + } + while ((len -= 2) != 0); + len = (UInt32)(distances2 - (distances)); + } + INCREASE_LZ_POS + return len; +} + +#define SKIP_HEADER2_MT do { GET_NEXT_BLOCK_IF_REQUIRED +#define SKIP_HEADER_MT(n) SKIP_HEADER2_MT if (p->btNumAvailBytes-- >= (n)) { const Byte *cur = p->pointerToCurPos; UInt32 *hash = p->hash; +#define SKIP_FOOTER_MT } INCREASE_LZ_POS p->btBufPos += p->btBuf[p->btBufPos] + 1; } while (--num != 0); + +void MatchFinderMt0_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER2_MT { p->btNumAvailBytes--; + SKIP_FOOTER_MT +} + +void MatchFinderMt2_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER_MT(2) + UInt32 hash2Value; + MT_HASH2_CALC + hash[hash2Value] = p->lzPos; + SKIP_FOOTER_MT +} + +void MatchFinderMt3_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER_MT(3) + UInt32 hash2Value, hash3Value; + MT_HASH3_CALC + hash[kFix3HashSize + hash3Value] = + hash[ hash2Value] = + p->lzPos; + SKIP_FOOTER_MT +} + +/* +void MatchFinderMt4_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER_MT(4) + UInt32 hash2Value, hash3Value, hash4Value; + MT_HASH4_CALC + hash[kFix4HashSize + hash4Value] = + hash[kFix3HashSize + hash3Value] = + hash[ hash2Value] = + p->lzPos; + SKIP_FOOTER_MT +} +*/ + +void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable) +{ + vTable->Init = (Mf_Init_Func)MatchFinderMt_Init; + vTable->GetIndexByte = (Mf_GetIndexByte_Func)MatchFinderMt_GetIndexByte; + vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinderMt_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinderMt_GetPointerToCurrentPos; + vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt_GetMatches; + switch(p->MatchFinder->numHashBytes) + { + case 2: + p->GetHeadsFunc = GetHeads2; + p->MixMatchesFunc = (Mf_Mix_Matches)0; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt0_Skip; + vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt2_GetMatches; + break; + case 3: + p->GetHeadsFunc = GetHeads3; + p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches2; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt2_Skip; + break; + default: + /* case 4: */ + p->GetHeadsFunc = p->MatchFinder->bigHash ? GetHeads4b : GetHeads4; + /* p->GetHeadsFunc = GetHeads4; */ + p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches3; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt3_Skip; + break; + /* + default: + p->GetHeadsFunc = GetHeads5; + p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches4; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt4_Skip; + break; + */ + } +}
@@ -0,0 +1,101 @@
+/* LzFindMt.h -- multithreaded Match finder for LZ algorithms +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZ_FIND_MT_H +#define __LZ_FIND_MT_H + +#include "LzFind.h" +#include "Threads.h" + +EXTERN_C_BEGIN + +#define kMtHashBlockSize (1 << 13) +#define kMtHashNumBlocks (1 << 3) +#define kMtHashNumBlocksMask (kMtHashNumBlocks - 1) + +#define kMtBtBlockSize (1 << 14) +#define kMtBtNumBlocks (1 << 6) +#define kMtBtNumBlocksMask (kMtBtNumBlocks - 1) + +typedef struct _CMtSync +{ + Bool wasCreated; + Bool needStart; + Bool exit; + Bool stopWriting; + + CThread thread; + CAutoResetEvent canStart; + CAutoResetEvent wasStarted; + CAutoResetEvent wasStopped; + CSemaphore freeSemaphore; + CSemaphore filledSemaphore; + Bool csWasInitialized; + Bool csWasEntered; + CCriticalSection cs; + UInt32 numProcessedBlocks; +} CMtSync; + +typedef UInt32 * (*Mf_Mix_Matches)(void *p, UInt32 matchMinPos, UInt32 *distances); + +/* kMtCacheLineDummy must be >= size_of_CPU_cache_line */ +#define kMtCacheLineDummy 128 + +typedef void (*Mf_GetHeads)(const Byte *buffer, UInt32 pos, + UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc); + +typedef struct _CMatchFinderMt +{ + /* LZ */ + const Byte *pointerToCurPos; + UInt32 *btBuf; + UInt32 btBufPos; + UInt32 btBufPosLimit; + UInt32 lzPos; + UInt32 btNumAvailBytes; + + UInt32 *hash; + UInt32 fixedHashSize; + UInt32 historySize; + const UInt32 *crc; + + Mf_Mix_Matches MixMatchesFunc; + + /* LZ + BT */ + CMtSync btSync; + Byte btDummy[kMtCacheLineDummy]; + + /* BT */ + UInt32 *hashBuf; + UInt32 hashBufPos; + UInt32 hashBufPosLimit; + UInt32 hashNumAvail; + + CLzRef *son; + UInt32 matchMaxLen; + UInt32 numHashBytes; + UInt32 pos; + Byte *buffer; + UInt32 cyclicBufferPos; + UInt32 cyclicBufferSize; /* it must be historySize + 1 */ + UInt32 cutValue; + + /* BT + Hash */ + CMtSync hashSync; + /* Byte hashDummy[kMtCacheLineDummy]; */ + + /* Hash */ + Mf_GetHeads GetHeadsFunc; + CMatchFinder *MatchFinder; +} CMatchFinderMt; + +void MatchFinderMt_Construct(CMatchFinderMt *p); +void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc); +SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc); +void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable); +void MatchFinderMt_ReleaseStream(CMatchFinderMt *p); + +EXTERN_C_END + +#endif
@@ -0,0 +1,54 @@
+/* LzHash.h -- HASH functions for LZ algorithms +2009-02-07 : Igor Pavlov : Public domain */ + +#ifndef __LZ_HASH_H +#define __LZ_HASH_H + +#define kHash2Size (1 << 10) +#define kHash3Size (1 << 16) +#define kHash4Size (1 << 20) + +#define kFix3HashSize (kHash2Size) +#define kFix4HashSize (kHash2Size + kHash3Size) +#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size) + +#define HASH2_CALC hashValue = cur[0] | ((UInt32)cur[1] << 8); + +#define HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + hash2Value = temp & (kHash2Size - 1); \ + hashValue = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; } + +#define HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + hash2Value = temp & (kHash2Size - 1); \ + hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \ + hashValue = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & p->hashMask; } + +#define HASH5_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + hash2Value = temp & (kHash2Size - 1); \ + hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \ + hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)); \ + hashValue = (hash4Value ^ (p->crc[cur[4]] << 3)) & p->hashMask; \ + hash4Value &= (kHash4Size - 1); } + +/* #define HASH_ZIP_CALC hashValue = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */ +#define HASH_ZIP_CALC hashValue = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF; + + +#define MT_HASH2_CALC \ + hash2Value = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1); + +#define MT_HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + hash2Value = temp & (kHash2Size - 1); \ + hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); } + +#define MT_HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + hash2Value = temp & (kHash2Size - 1); \ + hash3Value = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); \ + hash4Value = (temp ^ ((UInt32)cur[2] << 8) ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); } + +#endif
@@ -0,0 +1,352 @@
+/* Lzma2Dec.c -- LZMA2 Decoder +2010-12-15 : Igor Pavlov : Public domain */ + +/* #define SHOW_DEBUG_INFO */ + +#include "Precomp.h" + +#ifdef SHOW_DEBUG_INFO +#include <stdio.h> +#endif + +#include <string.h> + +#include "Lzma2Dec.h" + +/* +00000000 - EOS +00000001 U U - Uncompressed Reset Dic +00000010 U U - Uncompressed No Reset +100uuuuu U U P P - LZMA no reset +101uuuuu U U P P - LZMA reset state +110uuuuu U U P P S - LZMA reset state + new prop +111uuuuu U U P P S - LZMA reset state + new prop + reset dic + + u, U - Unpack Size + P - Pack Size + S - Props +*/ + +#define LZMA2_CONTROL_LZMA (1 << 7) +#define LZMA2_CONTROL_COPY_NO_RESET 2 +#define LZMA2_CONTROL_COPY_RESET_DIC 1 +#define LZMA2_CONTROL_EOF 0 + +#define LZMA2_IS_UNCOMPRESSED_STATE(p) (((p)->control & LZMA2_CONTROL_LZMA) == 0) + +#define LZMA2_GET_LZMA_MODE(p) (((p)->control >> 5) & 3) +#define LZMA2_IS_THERE_PROP(mode) ((mode) >= 2) + +#define LZMA2_LCLP_MAX 4 +#define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11)) + +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif + +typedef enum +{ + LZMA2_STATE_CONTROL, + LZMA2_STATE_UNPACK0, + LZMA2_STATE_UNPACK1, + LZMA2_STATE_PACK0, + LZMA2_STATE_PACK1, + LZMA2_STATE_PROP, + LZMA2_STATE_DATA, + LZMA2_STATE_DATA_CONT, + LZMA2_STATE_FINISHED, + LZMA2_STATE_ERROR +} ELzma2State; + +static SRes Lzma2Dec_GetOldProps(Byte prop, Byte *props) +{ + UInt32 dicSize; + if (prop > 40) + return SZ_ERROR_UNSUPPORTED; + dicSize = (prop == 40) ? 0xFFFFFFFF : LZMA2_DIC_SIZE_FROM_PROP(prop); + props[0] = (Byte)LZMA2_LCLP_MAX; + props[1] = (Byte)(dicSize); + props[2] = (Byte)(dicSize >> 8); + props[3] = (Byte)(dicSize >> 16); + props[4] = (Byte)(dicSize >> 24); + return SZ_OK; +} + +SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc) +{ + Byte props[LZMA_PROPS_SIZE]; + RINOK(Lzma2Dec_GetOldProps(prop, props)); + return LzmaDec_AllocateProbs(&p->decoder, props, LZMA_PROPS_SIZE, alloc); +} + +SRes Lzma2Dec_Allocate(CLzma2Dec *p, Byte prop, ISzAlloc *alloc) +{ + Byte props[LZMA_PROPS_SIZE]; + RINOK(Lzma2Dec_GetOldProps(prop, props)); + return LzmaDec_Allocate(&p->decoder, props, LZMA_PROPS_SIZE, alloc); +} + +void Lzma2Dec_Init(CLzma2Dec *p) +{ + p->state = LZMA2_STATE_CONTROL; + p->needInitDic = True; + p->needInitState = True; + p->needInitProp = True; + LzmaDec_Init(&p->decoder); +} + +static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) +{ + switch(p->state) + { + case LZMA2_STATE_CONTROL: + p->control = b; + PRF(printf("\n %4X ", p->decoder.dicPos)); + PRF(printf(" %2X", b)); + if (p->control == 0) + return LZMA2_STATE_FINISHED; + if (LZMA2_IS_UNCOMPRESSED_STATE(p)) + { + if ((p->control & 0x7F) > 2) + return LZMA2_STATE_ERROR; + p->unpackSize = 0; + } + else + p->unpackSize = (UInt32)(p->control & 0x1F) << 16; + return LZMA2_STATE_UNPACK0; + + case LZMA2_STATE_UNPACK0: + p->unpackSize |= (UInt32)b << 8; + return LZMA2_STATE_UNPACK1; + + case LZMA2_STATE_UNPACK1: + p->unpackSize |= (UInt32)b; + p->unpackSize++; + PRF(printf(" %8d", p->unpackSize)); + return (LZMA2_IS_UNCOMPRESSED_STATE(p)) ? LZMA2_STATE_DATA : LZMA2_STATE_PACK0; + + case LZMA2_STATE_PACK0: + p->packSize = (UInt32)b << 8; + return LZMA2_STATE_PACK1; + + case LZMA2_STATE_PACK1: + p->packSize |= (UInt32)b; + p->packSize++; + PRF(printf(" %8d", p->packSize)); + return LZMA2_IS_THERE_PROP(LZMA2_GET_LZMA_MODE(p)) ? LZMA2_STATE_PROP: + (p->needInitProp ? LZMA2_STATE_ERROR : LZMA2_STATE_DATA); + + case LZMA2_STATE_PROP: + { + int lc, lp; + if (b >= (9 * 5 * 5)) + return LZMA2_STATE_ERROR; + lc = b % 9; + b /= 9; + p->decoder.prop.pb = b / 5; + lp = b % 5; + if (lc + lp > LZMA2_LCLP_MAX) + return LZMA2_STATE_ERROR; + p->decoder.prop.lc = lc; + p->decoder.prop.lp = lp; + p->needInitProp = False; + return LZMA2_STATE_DATA; + } + } + return LZMA2_STATE_ERROR; +} + +static void LzmaDec_UpdateWithUncompressed(CLzmaDec *p, const Byte *src, SizeT size) +{ + memcpy(p->dic + p->dicPos, src, size); + p->dicPos += size; + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= size) + p->checkDicSize = p->prop.dicSize; + p->processedPos += (UInt32)size; +} + +void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState); + +SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + + while (p->state != LZMA2_STATE_FINISHED) + { + SizeT dicPos = p->decoder.dicPos; + if (p->state == LZMA2_STATE_ERROR) + return SZ_ERROR_DATA; + if (dicPos == dicLimit && finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + if (p->state != LZMA2_STATE_DATA && p->state != LZMA2_STATE_DATA_CONT) + { + if (*srcLen == inSize) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + (*srcLen)++; + p->state = Lzma2Dec_UpdateState(p, *src++); + continue; + } + { + SizeT destSizeCur = dicLimit - dicPos; + SizeT srcSizeCur = inSize - *srcLen; + ELzmaFinishMode curFinishMode = LZMA_FINISH_ANY; + + if (p->unpackSize <= destSizeCur) + { + destSizeCur = (SizeT)p->unpackSize; + curFinishMode = LZMA_FINISH_END; + } + + if (LZMA2_IS_UNCOMPRESSED_STATE(p)) + { + if (*srcLen == inSize) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + + if (p->state == LZMA2_STATE_DATA) + { + Bool initDic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC); + if (initDic) + p->needInitProp = p->needInitState = True; + else if (p->needInitDic) + return SZ_ERROR_DATA; + p->needInitDic = False; + LzmaDec_InitDicAndState(&p->decoder, initDic, False); + } + + if (srcSizeCur > destSizeCur) + srcSizeCur = destSizeCur; + + if (srcSizeCur == 0) + return SZ_ERROR_DATA; + + LzmaDec_UpdateWithUncompressed(&p->decoder, src, srcSizeCur); + + src += srcSizeCur; + *srcLen += srcSizeCur; + p->unpackSize -= (UInt32)srcSizeCur; + p->state = (p->unpackSize == 0) ? LZMA2_STATE_CONTROL : LZMA2_STATE_DATA_CONT; + } + else + { + SizeT outSizeProcessed; + SRes res; + + if (p->state == LZMA2_STATE_DATA) + { + int mode = LZMA2_GET_LZMA_MODE(p); + Bool initDic = (mode == 3); + Bool initState = (mode > 0); + if ((!initDic && p->needInitDic) || (!initState && p->needInitState)) + return SZ_ERROR_DATA; + + LzmaDec_InitDicAndState(&p->decoder, initDic, initState); + p->needInitDic = False; + p->needInitState = False; + p->state = LZMA2_STATE_DATA_CONT; + } + if (srcSizeCur > p->packSize) + srcSizeCur = (SizeT)p->packSize; + + res = LzmaDec_DecodeToDic(&p->decoder, dicPos + destSizeCur, src, &srcSizeCur, curFinishMode, status); + + src += srcSizeCur; + *srcLen += srcSizeCur; + p->packSize -= (UInt32)srcSizeCur; + + outSizeProcessed = p->decoder.dicPos - dicPos; + p->unpackSize -= (UInt32)outSizeProcessed; + + RINOK(res); + if (*status == LZMA_STATUS_NEEDS_MORE_INPUT) + return res; + + if (srcSizeCur == 0 && outSizeProcessed == 0) + { + if (*status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK || + p->unpackSize != 0 || p->packSize != 0) + return SZ_ERROR_DATA; + p->state = LZMA2_STATE_CONTROL; + } + if (*status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + *status = LZMA_STATUS_NOT_FINISHED; + } + } + } + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return SZ_OK; +} + +SRes Lzma2Dec_DecodeToBuf(CLzma2Dec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT outSize = *destLen, inSize = *srcLen; + *srcLen = *destLen = 0; + for (;;) + { + SizeT srcSizeCur = inSize, outSizeCur, dicPos; + ELzmaFinishMode curFinishMode; + SRes res; + if (p->decoder.dicPos == p->decoder.dicBufSize) + p->decoder.dicPos = 0; + dicPos = p->decoder.dicPos; + if (outSize > p->decoder.dicBufSize - dicPos) + { + outSizeCur = p->decoder.dicBufSize; + curFinishMode = LZMA_FINISH_ANY; + } + else + { + outSizeCur = dicPos + outSize; + curFinishMode = finishMode; + } + + res = Lzma2Dec_DecodeToDic(p, outSizeCur, src, &srcSizeCur, curFinishMode, status); + src += srcSizeCur; + inSize -= srcSizeCur; + *srcLen += srcSizeCur; + outSizeCur = p->decoder.dicPos - dicPos; + memcpy(dest, p->decoder.dic + dicPos, outSizeCur); + dest += outSizeCur; + outSize -= outSizeCur; + *destLen += outSizeCur; + if (res != 0) + return res; + if (outSizeCur == 0 || outSize == 0) + return SZ_OK; + } +} + +SRes Lzma2Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + Byte prop, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAlloc *alloc) +{ + CLzma2Dec p; + SRes res; + SizeT outSize = *destLen, inSize = *srcLen; + *destLen = *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + Lzma2Dec_Construct(&p); + RINOK(Lzma2Dec_AllocateProbs(&p, prop, alloc)); + p.decoder.dic = dest; + p.decoder.dicBufSize = outSize; + Lzma2Dec_Init(&p); + *srcLen = inSize; + res = Lzma2Dec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); + *destLen = p.decoder.dicPos; + if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + Lzma2Dec_FreeProbs(&p, alloc); + return res; +}
@@ -0,0 +1,80 @@
+/* Lzma2Dec.h -- LZMA2 Decoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA2_DEC_H +#define __LZMA2_DEC_H + +#include "LzmaDec.h" + +EXTERN_C_BEGIN + +/* ---------- State Interface ---------- */ + +typedef struct +{ + CLzmaDec decoder; + UInt32 packSize; + UInt32 unpackSize; + int state; + Byte control; + Bool needInitDic; + Bool needInitState; + Bool needInitProp; +} CLzma2Dec; + +#define Lzma2Dec_Construct(p) LzmaDec_Construct(&(p)->decoder) +#define Lzma2Dec_FreeProbs(p, alloc) LzmaDec_FreeProbs(&(p)->decoder, alloc); +#define Lzma2Dec_Free(p, alloc) LzmaDec_Free(&(p)->decoder, alloc); + +SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc); +SRes Lzma2Dec_Allocate(CLzma2Dec *p, Byte prop, ISzAlloc *alloc); +void Lzma2Dec_Init(CLzma2Dec *p); + + +/* +finishMode: + It has meaning only if the decoding reaches output limit (*destLen or dicLimit). + LZMA_FINISH_ANY - use smallest number of input bytes + LZMA_FINISH_END - read EndOfStream marker after decoding + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + SZ_ERROR_DATA - Data error +*/ + +SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + +SRes Lzma2Dec_DecodeToBuf(CLzma2Dec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- One Call Interface ---------- */ + +/* +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - use smallest number of input bytes + LZMA_FINISH_END - read EndOfStream marker after decoding + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). +*/ + +SRes Lzma2Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + Byte prop, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAlloc *alloc); + +EXTERN_C_END + +#endif
@@ -0,0 +1,493 @@
+/* Lzma2Enc.c -- LZMA2 Encoder +2012-06-19 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +/* #include <stdio.h> */ +#include <string.h> + +/* #define _7ZIP_ST */ + +#include "Lzma2Enc.h" + +#ifndef _7ZIP_ST +#include "MtCoder.h" +#else +#define NUM_MT_CODER_THREADS_MAX 1 +#endif + +#define LZMA2_CONTROL_LZMA (1 << 7) +#define LZMA2_CONTROL_COPY_NO_RESET 2 +#define LZMA2_CONTROL_COPY_RESET_DIC 1 +#define LZMA2_CONTROL_EOF 0 + +#define LZMA2_LCLP_MAX 4 + +#define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11)) + +#define LZMA2_PACK_SIZE_MAX (1 << 16) +#define LZMA2_COPY_CHUNK_SIZE LZMA2_PACK_SIZE_MAX +#define LZMA2_UNPACK_SIZE_MAX (1 << 21) +#define LZMA2_KEEP_WINDOW_SIZE LZMA2_UNPACK_SIZE_MAX + +#define LZMA2_CHUNK_SIZE_COMPRESSED_MAX ((1 << 16) + 16) + + +#define PRF(x) /* x */ + +/* ---------- CLzma2EncInt ---------- */ + +typedef struct +{ + CLzmaEncHandle enc; + UInt64 srcPos; + Byte props; + Bool needInitState; + Bool needInitProp; +} CLzma2EncInt; + +static SRes Lzma2EncInt_Init(CLzma2EncInt *p, const CLzma2EncProps *props) +{ + Byte propsEncoded[LZMA_PROPS_SIZE]; + SizeT propsSize = LZMA_PROPS_SIZE; + RINOK(LzmaEnc_SetProps(p->enc, &props->lzmaProps)); + RINOK(LzmaEnc_WriteProperties(p->enc, propsEncoded, &propsSize)); + p->srcPos = 0; + p->props = propsEncoded[0]; + p->needInitState = True; + p->needInitProp = True; + return SZ_OK; +} + +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, ISeqInStream *inStream, UInt32 keepWindowSize, + ISzAlloc *alloc, ISzAlloc *allocBig); +SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig); +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, + Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize); +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp); +void LzmaEnc_Finish(CLzmaEncHandle pp); +void LzmaEnc_SaveState(CLzmaEncHandle pp); +void LzmaEnc_RestoreState(CLzmaEncHandle pp); + + +static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, + size_t *packSizeRes, ISeqOutStream *outStream) +{ + size_t packSizeLimit = *packSizeRes; + size_t packSize = packSizeLimit; + UInt32 unpackSize = LZMA2_UNPACK_SIZE_MAX; + unsigned lzHeaderSize = 5 + (p->needInitProp ? 1 : 0); + Bool useCopyBlock; + SRes res; + + *packSizeRes = 0; + if (packSize < lzHeaderSize) + return SZ_ERROR_OUTPUT_EOF; + packSize -= lzHeaderSize; + + LzmaEnc_SaveState(p->enc); + res = LzmaEnc_CodeOneMemBlock(p->enc, p->needInitState, + outBuf + lzHeaderSize, &packSize, LZMA2_PACK_SIZE_MAX, &unpackSize); + + PRF(printf("\npackSize = %7d unpackSize = %7d ", packSize, unpackSize)); + + if (unpackSize == 0) + return res; + + if (res == SZ_OK) + useCopyBlock = (packSize + 2 >= unpackSize || packSize > (1 << 16)); + else + { + if (res != SZ_ERROR_OUTPUT_EOF) + return res; + res = SZ_OK; + useCopyBlock = True; + } + + if (useCopyBlock) + { + size_t destPos = 0; + PRF(printf("################# COPY ")); + while (unpackSize > 0) + { + UInt32 u = (unpackSize < LZMA2_COPY_CHUNK_SIZE) ? unpackSize : LZMA2_COPY_CHUNK_SIZE; + if (packSizeLimit - destPos < u + 3) + return SZ_ERROR_OUTPUT_EOF; + outBuf[destPos++] = (Byte)(p->srcPos == 0 ? LZMA2_CONTROL_COPY_RESET_DIC : LZMA2_CONTROL_COPY_NO_RESET); + outBuf[destPos++] = (Byte)((u - 1) >> 8); + outBuf[destPos++] = (Byte)(u - 1); + memcpy(outBuf + destPos, LzmaEnc_GetCurBuf(p->enc) - unpackSize, u); + unpackSize -= u; + destPos += u; + p->srcPos += u; + if (outStream) + { + *packSizeRes += destPos; + if (outStream->Write(outStream, outBuf, destPos) != destPos) + return SZ_ERROR_WRITE; + destPos = 0; + } + else + *packSizeRes = destPos; + /* needInitState = True; */ + } + LzmaEnc_RestoreState(p->enc); + return SZ_OK; + } + { + size_t destPos = 0; + UInt32 u = unpackSize - 1; + UInt32 pm = (UInt32)(packSize - 1); + unsigned mode = (p->srcPos == 0) ? 3 : (p->needInitState ? (p->needInitProp ? 2 : 1) : 0); + + PRF(printf(" ")); + + outBuf[destPos++] = (Byte)(LZMA2_CONTROL_LZMA | (mode << 5) | ((u >> 16) & 0x1F)); + outBuf[destPos++] = (Byte)(u >> 8); + outBuf[destPos++] = (Byte)u; + outBuf[destPos++] = (Byte)(pm >> 8); + outBuf[destPos++] = (Byte)pm; + + if (p->needInitProp) + outBuf[destPos++] = p->props; + + p->needInitProp = False; + p->needInitState = False; + destPos += packSize; + p->srcPos += unpackSize; + + if (outStream) + if (outStream->Write(outStream, outBuf, destPos) != destPos) + return SZ_ERROR_WRITE; + *packSizeRes = destPos; + return SZ_OK; + } +} + +/* ---------- Lzma2 Props ---------- */ + +void Lzma2EncProps_Init(CLzma2EncProps *p) +{ + LzmaEncProps_Init(&p->lzmaProps); + p->numTotalThreads = -1; + p->numBlockThreads = -1; + p->blockSize = 0; +} + +void Lzma2EncProps_Normalize(CLzma2EncProps *p) +{ + int t1, t1n, t2, t3; + { + CLzmaEncProps lzmaProps = p->lzmaProps; + LzmaEncProps_Normalize(&lzmaProps); + t1n = lzmaProps.numThreads; + } + + t1 = p->lzmaProps.numThreads; + t2 = p->numBlockThreads; + t3 = p->numTotalThreads; + + if (t2 > NUM_MT_CODER_THREADS_MAX) + t2 = NUM_MT_CODER_THREADS_MAX; + + if (t3 <= 0) + { + if (t2 <= 0) + t2 = 1; + t3 = t1n * t2; + } + else if (t2 <= 0) + { + t2 = t3 / t1n; + if (t2 == 0) + { + t1 = 1; + t2 = t3; + } + if (t2 > NUM_MT_CODER_THREADS_MAX) + t2 = NUM_MT_CODER_THREADS_MAX; + } + else if (t1 <= 0) + { + t1 = t3 / t2; + if (t1 == 0) + t1 = 1; + } + else + t3 = t1n * t2; + + p->lzmaProps.numThreads = t1; + + LzmaEncProps_Normalize(&p->lzmaProps); + + if (p->blockSize == 0) + { + UInt32 dictSize = p->lzmaProps.dictSize; + UInt64 blockSize = (UInt64)dictSize << 2; + const UInt32 kMinSize = (UInt32)1 << 20; + const UInt32 kMaxSize = (UInt32)1 << 28; + if (blockSize < kMinSize) blockSize = kMinSize; + if (blockSize > kMaxSize) blockSize = kMaxSize; + if (blockSize < dictSize) blockSize = dictSize; + p->blockSize = (size_t)blockSize; + } + if (t2 > 1) + { + UInt64 temp = p->lzmaProps.reduceSize + p->blockSize - 1; + if (temp > p->lzmaProps.reduceSize) + { + UInt64 numBlocks = temp / p->blockSize; + if (numBlocks < t2) + { + t2 = (UInt32)numBlocks; + t3 = t1 * t2; + } + } + } + p->numBlockThreads = t2; + p->numTotalThreads = t3; +} + +static SRes Progress(ICompressProgress *p, UInt64 inSize, UInt64 outSize) +{ + return (p && p->Progress(p, inSize, outSize) != SZ_OK) ? SZ_ERROR_PROGRESS : SZ_OK; +} + +/* ---------- Lzma2 ---------- */ + +typedef struct +{ + Byte propEncoded; + CLzma2EncProps props; + + Byte *outBuf; + + ISzAlloc *alloc; + ISzAlloc *allocBig; + + CLzma2EncInt coders[NUM_MT_CODER_THREADS_MAX]; + + #ifndef _7ZIP_ST + CMtCoder mtCoder; + #endif + +} CLzma2Enc; + + +/* ---------- Lzma2EncThread ---------- */ + +static SRes Lzma2Enc_EncodeMt1(CLzma2EncInt *p, CLzma2Enc *mainEncoder, + ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress) +{ + UInt64 packTotal = 0; + SRes res = SZ_OK; + + if (mainEncoder->outBuf == 0) + { + mainEncoder->outBuf = (Byte *)IAlloc_Alloc(mainEncoder->alloc, LZMA2_CHUNK_SIZE_COMPRESSED_MAX); + if (mainEncoder->outBuf == 0) + return SZ_ERROR_MEM; + } + RINOK(Lzma2EncInt_Init(p, &mainEncoder->props)); + RINOK(LzmaEnc_PrepareForLzma2(p->enc, inStream, LZMA2_KEEP_WINDOW_SIZE, + mainEncoder->alloc, mainEncoder->allocBig)); + for (;;) + { + size_t packSize = LZMA2_CHUNK_SIZE_COMPRESSED_MAX; + res = Lzma2EncInt_EncodeSubblock(p, mainEncoder->outBuf, &packSize, outStream); + if (res != SZ_OK) + break; + packTotal += packSize; + res = Progress(progress, p->srcPos, packTotal); + if (res != SZ_OK) + break; + if (packSize == 0) + break; + } + LzmaEnc_Finish(p->enc); + if (res == SZ_OK) + { + Byte b = 0; + if (outStream->Write(outStream, &b, 1) != 1) + return SZ_ERROR_WRITE; + } + return res; +} + +#ifndef _7ZIP_ST + +typedef struct +{ + IMtCoderCallback funcTable; + CLzma2Enc *lzma2Enc; +} CMtCallbackImp; + +static SRes MtCallbackImp_Code(void *pp, unsigned index, Byte *dest, size_t *destSize, + const Byte *src, size_t srcSize, int finished) +{ + CMtCallbackImp *imp = (CMtCallbackImp *)pp; + CLzma2Enc *mainEncoder = imp->lzma2Enc; + CLzma2EncInt *p = &mainEncoder->coders[index]; + + SRes res = SZ_OK; + { + size_t destLim = *destSize; + *destSize = 0; + + if (srcSize != 0) + { + RINOK(Lzma2EncInt_Init(p, &mainEncoder->props)); + + RINOK(LzmaEnc_MemPrepare(p->enc, src, srcSize, LZMA2_KEEP_WINDOW_SIZE, + mainEncoder->alloc, mainEncoder->allocBig)); + + while (p->srcPos < srcSize) + { + size_t packSize = destLim - *destSize; + res = Lzma2EncInt_EncodeSubblock(p, dest + *destSize, &packSize, NULL); + if (res != SZ_OK) + break; + *destSize += packSize; + + if (packSize == 0) + { + res = SZ_ERROR_FAIL; + break; + } + + if (MtProgress_Set(&mainEncoder->mtCoder.mtProgress, index, p->srcPos, *destSize) != SZ_OK) + { + res = SZ_ERROR_PROGRESS; + break; + } + } + LzmaEnc_Finish(p->enc); + if (res != SZ_OK) + return res; + } + if (finished) + { + if (*destSize == destLim) + return SZ_ERROR_OUTPUT_EOF; + dest[(*destSize)++] = 0; + } + } + return res; +} + +#endif + +/* ---------- Lzma2Enc ---------- */ + +CLzma2EncHandle Lzma2Enc_Create(ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzma2Enc *p = (CLzma2Enc *)alloc->Alloc(alloc, sizeof(CLzma2Enc)); + if (p == 0) + return NULL; + Lzma2EncProps_Init(&p->props); + Lzma2EncProps_Normalize(&p->props); + p->outBuf = 0; + p->alloc = alloc; + p->allocBig = allocBig; + { + unsigned i; + for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + p->coders[i].enc = 0; + } + #ifndef _7ZIP_ST + MtCoder_Construct(&p->mtCoder); + #endif + + return p; +} + +void Lzma2Enc_Destroy(CLzma2EncHandle pp) +{ + CLzma2Enc *p = (CLzma2Enc *)pp; + unsigned i; + for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + { + CLzma2EncInt *t = &p->coders[i]; + if (t->enc) + { + LzmaEnc_Destroy(t->enc, p->alloc, p->allocBig); + t->enc = 0; + } + } + + #ifndef _7ZIP_ST + MtCoder_Destruct(&p->mtCoder); + #endif + + IAlloc_Free(p->alloc, p->outBuf); + IAlloc_Free(p->alloc, pp); +} + +SRes Lzma2Enc_SetProps(CLzma2EncHandle pp, const CLzma2EncProps *props) +{ + CLzma2Enc *p = (CLzma2Enc *)pp; + CLzmaEncProps lzmaProps = props->lzmaProps; + LzmaEncProps_Normalize(&lzmaProps); + if (lzmaProps.lc + lzmaProps.lp > LZMA2_LCLP_MAX) + return SZ_ERROR_PARAM; + p->props = *props; + Lzma2EncProps_Normalize(&p->props); + return SZ_OK; +} + +Byte Lzma2Enc_WriteProperties(CLzma2EncHandle pp) +{ + CLzma2Enc *p = (CLzma2Enc *)pp; + unsigned i; + UInt32 dicSize = LzmaEncProps_GetDictSize(&p->props.lzmaProps); + for (i = 0; i < 40; i++) + if (dicSize <= LZMA2_DIC_SIZE_FROM_PROP(i)) + break; + return (Byte)i; +} + +SRes Lzma2Enc_Encode(CLzma2EncHandle pp, + ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress) +{ + CLzma2Enc *p = (CLzma2Enc *)pp; + int i; + + for (i = 0; i < p->props.numBlockThreads; i++) + { + CLzma2EncInt *t = &p->coders[i]; + if (t->enc == NULL) + { + t->enc = LzmaEnc_Create(p->alloc); + if (t->enc == NULL) + return SZ_ERROR_MEM; + } + } + + #ifndef _7ZIP_ST + if (p->props.numBlockThreads <= 1) + #endif + return Lzma2Enc_EncodeMt1(&p->coders[0], p, outStream, inStream, progress); + + #ifndef _7ZIP_ST + + { + CMtCallbackImp mtCallback; + + mtCallback.funcTable.Code = MtCallbackImp_Code; + mtCallback.lzma2Enc = p; + + p->mtCoder.progress = progress; + p->mtCoder.inStream = inStream; + p->mtCoder.outStream = outStream; + p->mtCoder.alloc = p->alloc; + p->mtCoder.mtCallback = &mtCallback.funcTable; + + p->mtCoder.blockSize = p->props.blockSize; + p->mtCoder.destBlockSize = p->props.blockSize + (p->props.blockSize >> 10) + 16; + p->mtCoder.numThreads = p->props.numBlockThreads; + + return MtCoder_Code(&p->mtCoder); + } + #endif +}
@@ -0,0 +1,62 @@
+/* Lzma2Enc.h -- LZMA2 Encoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA2_ENC_H +#define __LZMA2_ENC_H + +#include "LzmaEnc.h" + +EXTERN_C_BEGIN + +typedef struct +{ + CLzmaEncProps lzmaProps; + size_t blockSize; + int numBlockThreads; + int numTotalThreads; +} CLzma2EncProps; + +void Lzma2EncProps_Init(CLzma2EncProps *p); +void Lzma2EncProps_Normalize(CLzma2EncProps *p); + +/* ---------- CLzmaEnc2Handle Interface ---------- */ + +/* Lzma2Enc_* functions can return the following exit codes: +Returns: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater in props + SZ_ERROR_WRITE - Write callback error + SZ_ERROR_PROGRESS - some break from progress callback + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +typedef void * CLzma2EncHandle; + +CLzma2EncHandle Lzma2Enc_Create(ISzAlloc *alloc, ISzAlloc *allocBig); +void Lzma2Enc_Destroy(CLzma2EncHandle p); +SRes Lzma2Enc_SetProps(CLzma2EncHandle p, const CLzma2EncProps *props); +Byte Lzma2Enc_WriteProperties(CLzma2EncHandle p); +SRes Lzma2Enc_Encode(CLzma2EncHandle p, + ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress); + +/* ---------- One Call Interface ---------- */ + +/* Lzma2Encode +Return code: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +/* +SRes Lzma2Encode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); +*/ + +EXTERN_C_END + +#endif
@@ -0,0 +1,111 @@
+/* Lzma86.h -- LZMA + x86 (BCJ) Filter +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA86_H +#define __LZMA86_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define LZMA86_SIZE_OFFSET (1 + 5) +#define LZMA86_HEADER_SIZE (LZMA86_SIZE_OFFSET + 8) + +/* +It's an example for LZMA + x86 Filter use. +You can use .lzma86 extension, if you write that stream to file. +.lzma86 header adds one additional byte to standard .lzma header. +.lzma86 header (14 bytes): + Offset Size Description + 0 1 = 0 - no filter, pure LZMA + = 1 - x86 filter + LZMA + 1 1 lc, lp and pb in encoded form + 2 4 dictSize (little endian) + 6 8 uncompressed size (little endian) + + +Lzma86_Encode +------------- +level - compression level: 0 <= level <= 9, the default value for "level" is 5. + +dictSize - The dictionary size in bytes. The maximum value is + 128 MB = (1 << 27) bytes for 32-bit version + 1 GB = (1 << 30) bytes for 64-bit version + The default value is 16 MB = (1 << 24) bytes, for level = 5. + It's recommended to use the dictionary that is larger than 4 KB and + that can be calculated as (1 << N) or (3 << N) sizes. + For better compression ratio dictSize must be >= inSize. + +filterMode: + SZ_FILTER_NO - no Filter + SZ_FILTER_YES - x86 Filter + SZ_FILTER_AUTO - it tries both alternatives to select best. + Encoder will use 2 or 3 passes: + 2 passes when FILTER_NO provides better compression. + 3 passes when FILTER_YES provides better compression. + +Lzma86Encode allocates Data with MyAlloc functions. +RAM Requirements for compressing: + RamSize = dictionarySize * 11.5 + 6MB + FilterBlockSize + filterMode FilterBlockSize + SZ_FILTER_NO 0 + SZ_FILTER_YES inSize + SZ_FILTER_AUTO inSize + + +Return code: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +enum ESzFilterMode +{ + SZ_FILTER_NO, + SZ_FILTER_YES, + SZ_FILTER_AUTO +}; + +SRes Lzma86_Encode(Byte *dest, size_t *destLen, const Byte *src, size_t srcLen, + int level, UInt32 dictSize, int filterMode); + + +/* +Lzma86_GetUnpackSize: + In: + src - input data + srcLen - input data size + Out: + unpackSize - size of uncompressed stream + Return code: + SZ_OK - OK + SZ_ERROR_INPUT_EOF - Error in headers +*/ + +SRes Lzma86_GetUnpackSize(const Byte *src, SizeT srcLen, UInt64 *unpackSize); + +/* +Lzma86_Decode: + In: + dest - output data + destLen - output data size + src - input data + srcLen - input data size + Out: + destLen - processed output size + srcLen - processed input size + Return code: + SZ_OK - OK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - unsupported file + SZ_ERROR_INPUT_EOF - it needs more bytes in input buffer +*/ + +SRes Lzma86_Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen); + +EXTERN_C_END + +#endif
@@ -0,0 +1,56 @@
+/* Lzma86Dec.c -- LZMA + x86 (BCJ) Filter Decoder +2009-08-14 : Igor Pavlov : Public domain */ + +#include "Lzma86.h" + +#include "Alloc.h" +#include "Bra.h" +#include "LzmaDec.h" + +static void *SzAlloc(void *p, size_t size) { p = p; return MyAlloc(size); } +static void SzFree(void *p, void *address) { p = p; MyFree(address); } + +SRes Lzma86_GetUnpackSize(const Byte *src, SizeT srcLen, UInt64 *unpackSize) +{ + unsigned i; + if (srcLen < LZMA86_HEADER_SIZE) + return SZ_ERROR_INPUT_EOF; + *unpackSize = 0; + for (i = 0; i < sizeof(UInt64); i++) + *unpackSize += ((UInt64)src[LZMA86_SIZE_OFFSET + i]) << (8 * i); + return SZ_OK; +} + +SRes Lzma86_Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen) +{ + ISzAlloc g_Alloc = { SzAlloc, SzFree }; + SRes res; + int useFilter; + SizeT inSizePure; + ELzmaStatus status; + + if (*srcLen < LZMA86_HEADER_SIZE) + return SZ_ERROR_INPUT_EOF; + + useFilter = src[0]; + + if (useFilter > 1) + { + *destLen = 0; + return SZ_ERROR_UNSUPPORTED; + } + + inSizePure = *srcLen - LZMA86_HEADER_SIZE; + res = LzmaDecode(dest, destLen, src + LZMA86_HEADER_SIZE, &inSizePure, + src + 1, LZMA_PROPS_SIZE, LZMA_FINISH_ANY, &status, &g_Alloc); + *srcLen = inSizePure + LZMA86_HEADER_SIZE; + if (res != SZ_OK) + return res; + if (useFilter == 1) + { + UInt32 x86State; + x86_Convert_Init(x86State); + x86_Convert(dest, *destLen, 0, &x86State, 0); + } + return SZ_OK; +}
@@ -0,0 +1,108 @@
+/* Lzma86Enc.c -- LZMA + x86 (BCJ) Filter Encoder +2009-08-14 : Igor Pavlov : Public domain */ + +#include <string.h> + +#include "Lzma86.h" + +#include "Alloc.h" +#include "Bra.h" +#include "LzmaEnc.h" + +#define SZE_OUT_OVERFLOW SZE_DATA_ERROR + +static void *SzAlloc(void *p, size_t size) { p = p; return MyAlloc(size); } +static void SzFree(void *p, void *address) { p = p; MyFree(address); } + +int Lzma86_Encode(Byte *dest, size_t *destLen, const Byte *src, size_t srcLen, + int level, UInt32 dictSize, int filterMode) +{ + ISzAlloc g_Alloc = { SzAlloc, SzFree }; + size_t outSize2 = *destLen; + Byte *filteredStream; + Bool useFilter; + int mainResult = SZ_ERROR_OUTPUT_EOF; + CLzmaEncProps props; + LzmaEncProps_Init(&props); + props.level = level; + props.dictSize = dictSize; + + *destLen = 0; + if (outSize2 < LZMA86_HEADER_SIZE) + return SZ_ERROR_OUTPUT_EOF; + + { + int i; + UInt64 t = srcLen; + for (i = 0; i < 8; i++, t >>= 8) + dest[LZMA86_SIZE_OFFSET + i] = (Byte)t; + } + + filteredStream = 0; + useFilter = (filterMode != SZ_FILTER_NO); + if (useFilter) + { + if (srcLen != 0) + { + filteredStream = (Byte *)MyAlloc(srcLen); + if (filteredStream == 0) + return SZ_ERROR_MEM; + memcpy(filteredStream, src, srcLen); + } + { + UInt32 x86State; + x86_Convert_Init(x86State); + x86_Convert(filteredStream, srcLen, 0, &x86State, 1); + } + } + + { + size_t minSize = 0; + Bool bestIsFiltered = False; + + /* passes for SZ_FILTER_AUTO: + 0 - BCJ + LZMA + 1 - LZMA + 2 - BCJ + LZMA agaian, if pass 0 (BCJ + LZMA) is better. + */ + int numPasses = (filterMode == SZ_FILTER_AUTO) ? 3 : 1; + + int i; + for (i = 0; i < numPasses; i++) + { + size_t outSizeProcessed = outSize2 - LZMA86_HEADER_SIZE; + size_t outPropsSize = 5; + SRes curRes; + Bool curModeIsFiltered = (numPasses > 1 && i == numPasses - 1); + if (curModeIsFiltered && !bestIsFiltered) + break; + if (useFilter && i == 0) + curModeIsFiltered = True; + + curRes = LzmaEncode(dest + LZMA86_HEADER_SIZE, &outSizeProcessed, + curModeIsFiltered ? filteredStream : src, srcLen, + &props, dest + 1, &outPropsSize, 0, + NULL, &g_Alloc, &g_Alloc); + + if (curRes != SZ_ERROR_OUTPUT_EOF) + { + if (curRes != SZ_OK) + { + mainResult = curRes; + break; + } + if (outSizeProcessed <= minSize || mainResult != SZ_OK) + { + minSize = outSizeProcessed; + bestIsFiltered = curModeIsFiltered; + mainResult = SZ_OK; + } + } + } + dest[0] = (Byte)(bestIsFiltered ? 1 : 0); + *destLen = LZMA86_HEADER_SIZE + minSize; + } + if (useFilter) + MyFree(filteredStream); + return mainResult; +}
@@ -0,0 +1,1025 @@
+/* LzmaDec.c -- LZMA Decoder +2015-01-01 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "LzmaDec.h" + +#include <string.h> + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_INIT_SIZE 5 + +#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); +#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ + { UPDATE_0(p); i = (i + i); A0; } else \ + { UPDATE_1(p); i = (i + i) + 1; A1; } +#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;) + +#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); } +#define TREE_DECODE(probs, limit, i) \ + { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } + +/* #define _LZMA_SIZE_OPT */ + +#ifdef _LZMA_SIZE_OPT +#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) +#else +#define TREE_6_DECODE(probs, i) \ + { i = 1; \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + i -= 0x40; } +#endif + +#define NORMAL_LITER_DEC GET_BIT(prob + symbol, symbol) +#define MATCHED_LITER_DEC \ + matchByte <<= 1; \ + bit = (matchByte & offs); \ + probLit = prob + offs + bit + symbol; \ + GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit) + +#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0_CHECK range = bound; +#define UPDATE_1_CHECK range -= bound; code -= bound; +#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ + { UPDATE_0_CHECK; i = (i + i); A0; } else \ + { UPDATE_1_CHECK; i = (i + i) + 1; A1; } +#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) +#define TREE_DECODE_CHECK(probs, limit, i) \ + { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } + + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumMidBits 3 +#define kLenNumMidSymbols (1 << kLenNumMidBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenChoice 0 +#define LenChoice2 (LenChoice + 1) +#define LenLow (LenChoice2 + 1) +#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits)) +#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + + +#define kNumStates 12 +#define kNumLitStates 7 + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#define kNumPosSlotBits 6 +#define kNumLenToPosStates 4 + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) + +#define kMatchMinLen 2 +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) + +#define IsMatch 0 +#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax)) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define IsRep0Long (IsRepG2 + kNumStates) +#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax)) +#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex) +#define LenCoder (Align + kAlignTableSize) +#define RepLenCoder (LenCoder + kNumLenProbs) +#define Literal (RepLenCoder + kNumLenProbs) + +#define LZMA_BASE_SIZE 1846 +#define LZMA_LIT_SIZE 768 + +#define LzmaProps_GetNumProbs(p) ((UInt32)LZMA_BASE_SIZE + (LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + +#if Literal != LZMA_BASE_SIZE +StopCompilingDueBUG +#endif + +#define LZMA_DIC_MIN (1 << 12) + +/* First LZMA-symbol is always decoded. +And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization +Out: + Result: + SZ_OK - OK + SZ_ERROR_DATA - Error + p->remainLen: + < kMatchSpecLenStart : normal remain + = kMatchSpecLenStart : finished + = kMatchSpecLenStart + 1 : Flush marker + = kMatchSpecLenStart + 2 : State Init Marker +*/ + +static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = p->probs; + + unsigned state = p->state; + UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; + unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; + unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1; + unsigned lc = p->prop.lc; + + Byte *dic = p->dic; + SizeT dicBufSize = p->dicBufSize; + SizeT dicPos = p->dicPos; + + UInt32 processedPos = p->processedPos; + UInt32 checkDicSize = p->checkDicSize; + unsigned len = 0; + + const Byte *buf = p->buf; + UInt32 range = p->range; + UInt32 code = p->code; + + do + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = processedPos & pbMask; + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (checkDicSize != 0 || processedPos != 0) + prob += (LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) + + (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc)))); + + if (state < kNumLitStates) + { + state -= (state < 4) ? state : 3; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do { NORMAL_LITER_DEC } while (symbol < 0x100); + #else + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + #endif + } + else + { + unsigned matchByte = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)]; + unsigned offs = 0x100; + state -= (state < 10) ? 3 : 6; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + } + while (symbol < 0x100); + #else + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + } + #endif + } + dic[dicPos++] = (Byte)symbol; + processedPos++; + continue; + } + else + { + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + if (checkDicSize == 0 && processedPos == 0) + return SZ_ERROR_DATA; + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + UPDATE_0(prob); + dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)]; + dicPos++; + processedPos++; + state = state < kNumLitStates ? 9 : 11; + continue; + } + UPDATE_1(prob); + } + else + { + UInt32 distance; + UPDATE_1(prob); + prob = probs + IsRepG1 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep1; + } + else + { + UPDATE_1(prob); + prob = probs + IsRepG2 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep2; + } + else + { + UPDATE_1(prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < kNumLitStates ? 8 : 11; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + limit = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + limit = (1 << kLenNumMidBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + limit = (1 << kLenNumHighBits); + } + } + TREE_DECODE(probLen, limit, len); + len += offset; + } + + if (state >= kNumStates) + { + UInt32 distance; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); + TREE_6_DECODE(prob, distance); + if (distance >= kStartPosModelIndex) + { + unsigned posSlot = (unsigned)distance; + int numDirectBits = (int)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (posSlot < kEndPosModelIndex) + { + distance <<= numDirectBits; + prob = probs + SpecPos + distance - posSlot - 1; + { + UInt32 mask = 1; + unsigned i = 1; + do + { + GET_BIT2(prob + i, i, ; , distance |= mask); + mask <<= 1; + } + while (--numDirectBits != 0); + } + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE + range >>= 1; + + { + UInt32 t; + code -= range; + t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */ + distance = (distance << 1) + (t + 1); + code += range & t; + } + /* + distance <<= 1; + if (code >= range) + { + code -= range; + distance |= 1; + } + */ + } + while (--numDirectBits != 0); + prob = probs + Align; + distance <<= kNumAlignBits; + { + unsigned i = 1; + GET_BIT2(prob + i, i, ; , distance |= 1); + GET_BIT2(prob + i, i, ; , distance |= 2); + GET_BIT2(prob + i, i, ; , distance |= 4); + GET_BIT2(prob + i, i, ; , distance |= 8); + } + if (distance == (UInt32)0xFFFFFFFF) + { + len += kMatchSpecLenStart; + state -= kNumStates; + break; + } + } + } + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + if (checkDicSize == 0) + { + if (distance >= processedPos) + return SZ_ERROR_DATA; + } + else if (distance >= checkDicSize) + return SZ_ERROR_DATA; + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + } + + len += kMatchMinLen; + + if (limit == dicPos) + return SZ_ERROR_DATA; + { + SizeT rem = limit - dicPos; + unsigned curLen = ((rem < len) ? (unsigned)rem : len); + SizeT pos = (dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0); + + processedPos += curLen; + + len -= curLen; + if (pos + curLen <= dicBufSize) + { + Byte *dest = dic + dicPos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; + const Byte *lim = dest + curLen; + dicPos += curLen; + do + *(dest) = (Byte)*(dest + src); + while (++dest != lim); + } + else + { + do + { + dic[dicPos++] = dic[pos]; + if (++pos == dicBufSize) + pos = 0; + } + while (--curLen != 0); + } + } + } + } + while (dicPos < limit && buf < bufLimit); + NORMALIZE; + p->buf = buf; + p->range = range; + p->code = code; + p->remainLen = len; + p->dicPos = dicPos; + p->processedPos = processedPos; + p->reps[0] = rep0; + p->reps[1] = rep1; + p->reps[2] = rep2; + p->reps[3] = rep3; + p->state = state; + + return SZ_OK; +} + +static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) +{ + if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) + { + Byte *dic = p->dic; + SizeT dicPos = p->dicPos; + SizeT dicBufSize = p->dicBufSize; + unsigned len = p->remainLen; + UInt32 rep0 = p->reps[0]; + if (limit - dicPos < len) + len = (unsigned)(limit - dicPos); + + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) + p->checkDicSize = p->prop.dicSize; + + p->processedPos += len; + p->remainLen -= len; + while (len != 0) + { + len--; + dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)]; + dicPos++; + } + p->dicPos = dicPos; + } +} + +static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + do + { + SizeT limit2 = limit; + if (p->checkDicSize == 0) + { + UInt32 rem = p->prop.dicSize - p->processedPos; + if (limit - p->dicPos > rem) + limit2 = p->dicPos + rem; + } + RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit)); + if (p->processedPos >= p->prop.dicSize) + p->checkDicSize = p->prop.dicSize; + LzmaDec_WriteRem(p, limit); + } + while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); + + if (p->remainLen > kMatchSpecLenStart) + { + p->remainLen = kMatchSpecLenStart; + } + return 0; +} + +typedef enum +{ + DUMMY_ERROR, /* unexpected end of input stream */ + DUMMY_LIT, + DUMMY_MATCH, + DUMMY_REP +} ELzmaDummy; + +static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) +{ + UInt32 range = p->range; + UInt32 code = p->code; + const Byte *bufLimit = buf + inSize; + CLzmaProb *probs = p->probs; + unsigned state = p->state; + ELzmaDummy res; + + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1); + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK + + /* if (bufLimit - buf >= 7) return DUMMY_LIT; */ + + prob = probs + Literal; + if (p->checkDicSize != 0 || p->processedPos != 0) + prob += (LZMA_LIT_SIZE * + ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + + (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); + + if (state < kNumLitStates) + { + unsigned symbol = 1; + do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned matchByte = p->dic[p->dicPos - p->reps[0] + + ((p->dicPos < p->reps[0]) ? p->dicBufSize : 0)]; + unsigned offs = 0x100; + unsigned symbol = 1; + do + { + unsigned bit; + CLzmaProb *probLit; + matchByte <<= 1; + bit = (matchByte & offs); + probLit = prob + offs + bit + symbol; + GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit) + } + while (symbol < 0x100); + } + res = DUMMY_LIT; + } + else + { + unsigned len; + UPDATE_1_CHECK; + + prob = probs + IsRep + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + state = 0; + prob = probs + LenCoder; + res = DUMMY_MATCH; + } + else + { + UPDATE_1_CHECK; + res = DUMMY_REP; + prob = probs + IsRepG0 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + NORMALIZE_CHECK; + return DUMMY_REP; + } + else + { + UPDATE_1_CHECK; + } + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG1 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG2 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + } + } + } + state = kNumStates; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenChoice2; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumMidBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(probLen, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned posSlot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + if (posSlot >= kStartPosModelIndex) + { + int numDirectBits = ((posSlot >> 1) - 1); + + /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ + + if (posSlot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1; + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE_CHECK + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + /* if (code >= range) code -= range; */ + } + while (--numDirectBits != 0); + prob = probs + Align; + numDirectBits = kNumAlignBits; + } + { + unsigned i = 1; + do + { + GET_BIT_CHECK(prob + i, i); + } + while (--numDirectBits != 0); + } + } + } + } + } + NORMALIZE_CHECK; + return res; +} + + +static void LzmaDec_InitRc(CLzmaDec *p, const Byte *data) +{ + p->code = ((UInt32)data[1] << 24) | ((UInt32)data[2] << 16) | ((UInt32)data[3] << 8) | ((UInt32)data[4]); + p->range = 0xFFFFFFFF; + p->needFlush = 0; +} + +void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState) +{ + p->needFlush = 1; + p->remainLen = 0; + p->tempBufSize = 0; + + if (initDic) + { + p->processedPos = 0; + p->checkDicSize = 0; + p->needInitState = 1; + } + if (initState) + p->needInitState = 1; +} + +void LzmaDec_Init(CLzmaDec *p) +{ + p->dicPos = 0; + LzmaDec_InitDicAndState(p, True, True); +} + +static void LzmaDec_InitStateReal(CLzmaDec *p) +{ + UInt32 numProbs = Literal + ((UInt32)LZMA_LIT_SIZE << (p->prop.lc + p->prop.lp)); + UInt32 i; + CLzmaProb *probs = p->probs; + for (i = 0; i < numProbs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + p->needInitState = 0; +} + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, + ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + (*srcLen) = 0; + LzmaDec_WriteRem(p, dicLimit); + + *status = LZMA_STATUS_NOT_SPECIFIED; + + while (p->remainLen != kMatchSpecLenStart) + { + int checkEndMarkNow; + + if (p->needFlush != 0) + { + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + + LzmaDec_InitRc(p, p->tempBuf); + p->tempBufSize = 0; + } + + checkEndMarkNow = 0; + if (p->dicPos >= dicLimit) + { + if (p->remainLen == 0 && p->code == 0) + { + *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK; + return SZ_OK; + } + if (finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + if (p->remainLen != 0) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + checkEndMarkNow = 1; + } + + if (p->needInitState) + LzmaDec_InitStateReal(p); + + if (p->tempBufSize == 0) + { + SizeT processed; + const Byte *bufLimit; + if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, src, inSize); + if (dummyRes == DUMMY_ERROR) + { + memcpy(p->tempBuf, src, inSize); + p->tempBufSize = (unsigned)inSize; + (*srcLen) += inSize; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + bufLimit = src; + } + else + bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; + p->buf = src; + if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) + return SZ_ERROR_DATA; + processed = (SizeT)(p->buf - src); + (*srcLen) += processed; + src += processed; + inSize -= processed; + } + else + { + unsigned rem = p->tempBufSize, lookAhead = 0; + while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) + p->tempBuf[rem++] = src[lookAhead++]; + p->tempBufSize = rem; + if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem); + if (dummyRes == DUMMY_ERROR) + { + (*srcLen) += lookAhead; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + } + p->buf = p->tempBuf; + if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) + return SZ_ERROR_DATA; + lookAhead -= (rem - (unsigned)(p->buf - p->tempBuf)); + (*srcLen) += lookAhead; + src += lookAhead; + inSize -= lookAhead; + p->tempBufSize = 0; + } + } + if (p->code == 0) + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA; +} + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT outSize = *destLen; + SizeT inSize = *srcLen; + *srcLen = *destLen = 0; + for (;;) + { + SizeT inSizeCur = inSize, outSizeCur, dicPos; + ELzmaFinishMode curFinishMode; + SRes res; + if (p->dicPos == p->dicBufSize) + p->dicPos = 0; + dicPos = p->dicPos; + if (outSize > p->dicBufSize - dicPos) + { + outSizeCur = p->dicBufSize; + curFinishMode = LZMA_FINISH_ANY; + } + else + { + outSizeCur = dicPos + outSize; + curFinishMode = finishMode; + } + + res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status); + src += inSizeCur; + inSize -= inSizeCur; + *srcLen += inSizeCur; + outSizeCur = p->dicPos - dicPos; + memcpy(dest, p->dic + dicPos, outSizeCur); + dest += outSizeCur; + outSize -= outSizeCur; + *destLen += outSizeCur; + if (res != 0) + return res; + if (outSizeCur == 0 || outSize == 0) + return SZ_OK; + } +} + +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->probs); + p->probs = 0; +} + +static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->dic); + p->dic = 0; +} + +void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc) +{ + LzmaDec_FreeProbs(p, alloc); + LzmaDec_FreeDict(p, alloc); +} + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) +{ + UInt32 dicSize; + Byte d; + + if (size < LZMA_PROPS_SIZE) + return SZ_ERROR_UNSUPPORTED; + else + dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24); + + if (dicSize < LZMA_DIC_MIN) + dicSize = LZMA_DIC_MIN; + p->dicSize = dicSize; + + d = data[0]; + if (d >= (9 * 5 * 5)) + return SZ_ERROR_UNSUPPORTED; + + p->lc = d % 9; + d /= 9; + p->pb = d / 5; + p->lp = d % 5; + + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc) +{ + UInt32 numProbs = LzmaProps_GetNumProbs(propNew); + if (p->probs == 0 || numProbs != p->numProbs) + { + LzmaDec_FreeProbs(p, alloc); + p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb)); + p->numProbs = numProbs; + if (p->probs == 0) + return SZ_ERROR_MEM; + } + return SZ_OK; +} + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + SizeT dicBufSize; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + dicBufSize = propNew.dicSize; + if (p->dic == 0 || dicBufSize != p->dicBufSize) + { + LzmaDec_FreeDict(p, alloc); + p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize); + if (p->dic == 0) + { + LzmaDec_FreeProbs(p, alloc); + return SZ_ERROR_MEM; + } + } + p->dicBufSize = dicBufSize; + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc) +{ + CLzmaDec p; + SRes res; + SizeT outSize = *destLen, inSize = *srcLen; + *destLen = *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + if (inSize < RC_INIT_SIZE) + return SZ_ERROR_INPUT_EOF; + LzmaDec_Construct(&p); + RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)); + p.dic = dest; + p.dicBufSize = outSize; + LzmaDec_Init(&p); + *srcLen = inSize; + res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); + *destLen = p.dicPos; + if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + LzmaDec_FreeProbs(&p, alloc); + return res; +}
@@ -0,0 +1,227 @@
+/* LzmaDec.h -- LZMA Decoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_DEC_H +#define __LZMA_DEC_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +/* #define _LZMA_PROB32 */ +/* _LZMA_PROB32 can increase the speed on some CPUs, + but memory usage for CLzmaDec::probs will be doubled in that case */ + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + + +/* ---------- LZMA Properties ---------- */ + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + unsigned lc, lp, pb; + UInt32 dicSize; +} CLzmaProps; + +/* LzmaProps_Decode - decodes properties +Returns: + SZ_OK + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); + + +/* ---------- LZMA Decoder state ---------- */ + +/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. + Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ + +#define LZMA_REQUIRED_INPUT_MAX 20 + +typedef struct +{ + CLzmaProps prop; + CLzmaProb *probs; + Byte *dic; + const Byte *buf; + UInt32 range, code; + SizeT dicPos; + SizeT dicBufSize; + UInt32 processedPos; + UInt32 checkDicSize; + unsigned state; + UInt32 reps[4]; + unsigned remainLen; + int needFlush; + int needInitState; + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; } + +void LzmaDec_Init(CLzmaDec *p); + +/* There are two types of LZMA streams: + 0) Stream with end mark. That end mark adds about 6 bytes to compressed size. + 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */ + +typedef enum +{ + LZMA_FINISH_ANY, /* finish at any point */ + LZMA_FINISH_END /* block must be finished at the end */ +} ELzmaFinishMode; + +/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! + + You must use LZMA_FINISH_END, when you know that current output buffer + covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. + + If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, + and output value of destLen will be less than output buffer size limit. + You can check status result also. + + You can use multiple checks to test data integrity after full decompression: + 1) Check Result and "status" variable. + 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. + 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. + You must use correct finish mode in that case. */ + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ + LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ + LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ +} ELzmaStatus; + +/* ELzmaStatus is used only as output value for function call */ + + +/* ---------- Interfaces ---------- */ + +/* There are 3 levels of interfaces: + 1) Dictionary Interface + 2) Buffer Interface + 3) One Call Interface + You can select any of these interfaces, but don't mix functions from different + groups for same object. */ + + +/* There are two variants to allocate state for Dictionary Interface: + 1) LzmaDec_Allocate / LzmaDec_Free + 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs + You can use variant 2, if you set dictionary buffer manually. + For Buffer Interface you must always use variant 1. + +LzmaDec_Allocate* can return: + SZ_OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc); + +SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); + +/* ---------- Dictionary Interface ---------- */ + +/* You can use it, if you want to eliminate the overhead for data copying from + dictionary to some other external buffer. + You must work with CLzmaDec variables directly in this interface. + + STEPS: + LzmaDec_Constr() + LzmaDec_Allocate() + for (each new stream) + { + LzmaDec_Init() + while (it needs more decompression) + { + LzmaDec_DecodeToDic() + use data from CLzmaDec::dic and update CLzmaDec::dicPos + } + } + LzmaDec_Free() +*/ + +/* LzmaDec_DecodeToDic + + The decoding to internal dictionary buffer (CLzmaDec::dic). + You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! + +finishMode: + It has meaning only if the decoding reaches output limit (dicLimit). + LZMA_FINISH_ANY - Decode just dicLimit bytes. + LZMA_FINISH_END - Stream must be finished after dicLimit. + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error +*/ + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- Buffer Interface ---------- */ + +/* It's zlib-like interface. + See LzmaDec_DecodeToDic description for information about STEPS and return results, + but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need + to work with CLzmaDec variables manually. + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). +*/ + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- One Call Interface ---------- */ + +/* LzmaDecode + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). +*/ + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc); + +EXTERN_C_END + +#endif
@@ -0,0 +1,2278 @@
+/* LzmaEnc.c -- LZMA Encoder +2014-12-29 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +/* #define SHOW_STAT */ +/* #define SHOW_STAT2 */ + +#if defined(SHOW_STAT) || defined(SHOW_STAT2) +#include <stdio.h> +#endif + +#include "LzmaEnc.h" + +#include "LzFind.h" +#ifndef _7ZIP_ST +#include "LzFindMt.h" +#endif + +#ifdef SHOW_STAT +static unsigned g_STAT_OFFSET = 0; +#endif + +#define kBlockSizeMax ((1 << LZMA_NUM_BLOCK_SIZE_BITS) - 1) + +#define kBlockSize (9 << 10) +#define kUnpackBlockSize (1 << 18) +#define kMatchArraySize (1 << 21) +#define kMatchRecordMaxSize ((LZMA_MATCH_LEN_MAX * 2 + 3) * LZMA_MATCH_LEN_MAX) + +#define kNumMaxDirectBits (31) + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 +#define kProbInitValue (kBitModelTotal >> 1) + +#define kNumMoveReducingBits 4 +#define kNumBitPriceShiftBits 4 +#define kBitPrice (1 << kNumBitPriceShiftBits) + +void LzmaEncProps_Init(CLzmaEncProps *p) +{ + p->level = 5; + p->dictSize = p->mc = 0; + p->reduceSize = (UInt64)(Int64)-1; + p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1; + p->writeEndMark = 0; +} + +void LzmaEncProps_Normalize(CLzmaEncProps *p) +{ + int level = p->level; + if (level < 0) level = 5; + p->level = level; + if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26))); + if (p->dictSize > p->reduceSize) + { + unsigned i; + for (i = 11; i <= 30; i++) + { + if ((UInt32)p->reduceSize <= ((UInt32)2 << i)) { p->dictSize = ((UInt32)2 << i); break; } + if ((UInt32)p->reduceSize <= ((UInt32)3 << i)) { p->dictSize = ((UInt32)3 << i); break; } + } + } + if (p->lc < 0) p->lc = 3; + if (p->lp < 0) p->lp = 0; + if (p->pb < 0) p->pb = 2; + if (p->algo < 0) p->algo = (level < 5 ? 0 : 1); + if (p->fb < 0) p->fb = (level < 7 ? 32 : 64); + if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1); + if (p->numHashBytes < 0) p->numHashBytes = 4; + if (p->mc == 0) p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1); + if (p->numThreads < 0) + p->numThreads = + #ifndef _7ZIP_ST + ((p->btMode && p->algo) ? 2 : 1); + #else + 1; + #endif +} + +UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2) +{ + CLzmaEncProps props = *props2; + LzmaEncProps_Normalize(&props); + return props.dictSize; +} + +/* #define LZMA_LOG_BSR */ +/* Define it for Intel's CPU */ + + +#ifdef LZMA_LOG_BSR + +#define kDicLogSizeMaxCompress 30 + +#define BSR2_RET(pos, res) { unsigned long i; _BitScanReverse(&i, (pos)); res = (i + i) + ((pos >> (i - 1)) & 1); } + +UInt32 GetPosSlot1(UInt32 pos) +{ + UInt32 res; + BSR2_RET(pos, res); + return res; +} +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); } + +#else + +#define kNumLogBits (9 + (int)sizeof(size_t) / 2) +#define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7) + +void LzmaEnc_FastPosInit(Byte *g_FastPos) +{ + int c = 2, slotFast; + g_FastPos[0] = 0; + g_FastPos[1] = 1; + + for (slotFast = 2; slotFast < kNumLogBits * 2; slotFast++) + { + UInt32 k = (1 << ((slotFast >> 1) - 1)); + UInt32 j; + for (j = 0; j < k; j++, c++) + g_FastPos[c] = (Byte)slotFast; + } +} + +#define BSR2_RET(pos, res) { UInt32 i = 6 + ((kNumLogBits - 1) & \ + (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \ + res = p->g_FastPos[pos >> i] + (i * 2); } +/* +#define BSR2_RET(pos, res) { res = (pos < (1 << (kNumLogBits + 6))) ? \ + p->g_FastPos[pos >> 6] + 12 : \ + p->g_FastPos[pos >> (6 + kNumLogBits - 1)] + (6 + (kNumLogBits - 1)) * 2; } +*/ + +#define GetPosSlot1(pos) p->g_FastPos[pos] +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos]; else BSR2_RET(pos, res); } + +#endif + + +#define LZMA_NUM_REPS 4 + +typedef unsigned CState; + +typedef struct +{ + UInt32 price; + + CState state; + int prev1IsChar; + int prev2; + + UInt32 posPrev2; + UInt32 backPrev2; + + UInt32 posPrev; + UInt32 backPrev; + UInt32 backs[LZMA_NUM_REPS]; +} COptimal; + +#define kNumOpts (1 << 12) + +#define kNumLenToPosStates 4 +#define kNumPosSlotBits 6 +#define kDicLogSizeMin 0 +#define kDicLogSizeMax 32 +#define kDistTableSizeMax (kDicLogSizeMax * 2) + + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) +#define kAlignMask (kAlignTableSize - 1) + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex) + +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + +#define LZMA_PB_MAX 4 +#define LZMA_LC_MAX 8 +#define LZMA_LP_MAX 4 + +#define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX) + + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumMidBits 3 +#define kLenNumMidSymbols (1 << kLenNumMidBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define kLenNumSymbolsTotal (kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) + +#define LZMA_MATCH_LEN_MIN 2 +#define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1) + +#define kNumStates 12 + +typedef struct +{ + CLzmaProb choice; + CLzmaProb choice2; + CLzmaProb low[LZMA_NUM_PB_STATES_MAX << kLenNumLowBits]; + CLzmaProb mid[LZMA_NUM_PB_STATES_MAX << kLenNumMidBits]; + CLzmaProb high[kLenNumHighSymbols]; +} CLenEnc; + +typedef struct +{ + CLenEnc p; + UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal]; + UInt32 tableSize; + UInt32 counters[LZMA_NUM_PB_STATES_MAX]; +} CLenPriceEnc; + +typedef struct +{ + UInt32 range; + Byte cache; + UInt64 low; + UInt64 cacheSize; + Byte *buf; + Byte *bufLim; + Byte *bufBase; + ISeqOutStream *outStream; + UInt64 processed; + SRes res; +} CRangeEnc; + +typedef struct +{ + CLzmaProb *litProbs; + + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb isRep[kNumStates]; + CLzmaProb isRepG0[kNumStates]; + CLzmaProb isRepG1[kNumStates]; + CLzmaProb isRepG2[kNumStates]; + CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; + + CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; + CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex]; + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + + CLenPriceEnc lenEnc; + CLenPriceEnc repLenEnc; + + UInt32 reps[LZMA_NUM_REPS]; + UInt32 state; +} CSaveState; + +typedef struct +{ + IMatchFinder matchFinder; + void *matchFinderObj; + + #ifndef _7ZIP_ST + Bool mtMode; + CMatchFinderMt matchFinderMt; + #endif + + CMatchFinder matchFinderBase; + + #ifndef _7ZIP_ST + Byte pad[128]; + #endif + + UInt32 optimumEndIndex; + UInt32 optimumCurrentIndex; + + UInt32 longestMatchLength; + UInt32 numPairs; + UInt32 numAvail; + COptimal opt[kNumOpts]; + + #ifndef LZMA_LOG_BSR + Byte g_FastPos[1 << kNumLogBits]; + #endif + + UInt32 ProbPrices[kBitModelTotal >> kNumMoveReducingBits]; + UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1]; + UInt32 numFastBytes; + UInt32 additionalOffset; + UInt32 reps[LZMA_NUM_REPS]; + UInt32 state; + + UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax]; + UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances]; + UInt32 alignPrices[kAlignTableSize]; + UInt32 alignPriceCount; + + UInt32 distTableSize; + + unsigned lc, lp, pb; + unsigned lpMask, pbMask; + + CLzmaProb *litProbs; + + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb isRep[kNumStates]; + CLzmaProb isRepG0[kNumStates]; + CLzmaProb isRepG1[kNumStates]; + CLzmaProb isRepG2[kNumStates]; + CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; + + CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; + CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex]; + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + + CLenPriceEnc lenEnc; + CLenPriceEnc repLenEnc; + + unsigned lclp; + + Bool fastMode; + + CRangeEnc rc; + + Bool writeEndMark; + UInt64 nowPos64; + UInt32 matchPriceCount; + Bool finished; + Bool multiThread; + + SRes result; + UInt32 dictSize; + + int needInit; + + CSaveState saveState; +} CLzmaEnc; + +void LzmaEnc_SaveState(CLzmaEncHandle pp) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + CSaveState *dest = &p->saveState; + int i; + dest->lenEnc = p->lenEnc; + dest->repLenEnc = p->repLenEnc; + dest->state = p->state; + + for (i = 0; i < kNumStates; i++) + { + memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i])); + memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i])); + } + for (i = 0; i < kNumLenToPosStates; i++) + memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i])); + memcpy(dest->isRep, p->isRep, sizeof(p->isRep)); + memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0)); + memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1)); + memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2)); + memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders)); + memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder)); + memcpy(dest->reps, p->reps, sizeof(p->reps)); + memcpy(dest->litProbs, p->litProbs, (0x300 << p->lclp) * sizeof(CLzmaProb)); +} + +void LzmaEnc_RestoreState(CLzmaEncHandle pp) +{ + CLzmaEnc *dest = (CLzmaEnc *)pp; + const CSaveState *p = &dest->saveState; + int i; + dest->lenEnc = p->lenEnc; + dest->repLenEnc = p->repLenEnc; + dest->state = p->state; + + for (i = 0; i < kNumStates; i++) + { + memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i])); + memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i])); + } + for (i = 0; i < kNumLenToPosStates; i++) + memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i])); + memcpy(dest->isRep, p->isRep, sizeof(p->isRep)); + memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0)); + memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1)); + memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2)); + memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders)); + memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder)); + memcpy(dest->reps, p->reps, sizeof(p->reps)); + memcpy(dest->litProbs, p->litProbs, (0x300 << dest->lclp) * sizeof(CLzmaProb)); +} + +SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + CLzmaEncProps props = *props2; + LzmaEncProps_Normalize(&props); + + if (props.lc > LZMA_LC_MAX || props.lp > LZMA_LP_MAX || props.pb > LZMA_PB_MAX || + props.dictSize > ((UInt32)1 << kDicLogSizeMaxCompress) || props.dictSize > ((UInt32)1 << 30)) + return SZ_ERROR_PARAM; + p->dictSize = props.dictSize; + { + unsigned fb = props.fb; + if (fb < 5) + fb = 5; + if (fb > LZMA_MATCH_LEN_MAX) + fb = LZMA_MATCH_LEN_MAX; + p->numFastBytes = fb; + } + p->lc = props.lc; + p->lp = props.lp; + p->pb = props.pb; + p->fastMode = (props.algo == 0); + p->matchFinderBase.btMode = props.btMode; + { + UInt32 numHashBytes = 4; + if (props.btMode) + { + if (props.numHashBytes < 2) + numHashBytes = 2; + else if (props.numHashBytes < 4) + numHashBytes = props.numHashBytes; + } + p->matchFinderBase.numHashBytes = numHashBytes; + } + + p->matchFinderBase.cutValue = props.mc; + + p->writeEndMark = props.writeEndMark; + + #ifndef _7ZIP_ST + /* + if (newMultiThread != _multiThread) + { + ReleaseMatchFinder(); + _multiThread = newMultiThread; + } + */ + p->multiThread = (props.numThreads > 1); + #endif + + return SZ_OK; +} + +static const int kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; +static const int kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; +static const int kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; +static const int kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + +#define IsCharState(s) ((s) < 7) + +#define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1) + +#define kInfinityPrice (1 << 30) + +static void RangeEnc_Construct(CRangeEnc *p) +{ + p->outStream = 0; + p->bufBase = 0; +} + +#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize) + +#define RC_BUF_SIZE (1 << 16) +static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc) +{ + if (p->bufBase == 0) + { + p->bufBase = (Byte *)alloc->Alloc(alloc, RC_BUF_SIZE); + if (p->bufBase == 0) + return 0; + p->bufLim = p->bufBase + RC_BUF_SIZE; + } + return 1; +} + +static void RangeEnc_Free(CRangeEnc *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->bufBase); + p->bufBase = 0; +} + +static void RangeEnc_Init(CRangeEnc *p) +{ + /* Stream.Init(); */ + p->low = 0; + p->range = 0xFFFFFFFF; + p->cacheSize = 1; + p->cache = 0; + + p->buf = p->bufBase; + + p->processed = 0; + p->res = SZ_OK; +} + +static void RangeEnc_FlushStream(CRangeEnc *p) +{ + size_t num; + if (p->res != SZ_OK) + return; + num = p->buf - p->bufBase; + if (num != p->outStream->Write(p->outStream, p->bufBase, num)) + p->res = SZ_ERROR_WRITE; + p->processed += num; + p->buf = p->bufBase; +} + +static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p) +{ + if ((UInt32)p->low < (UInt32)0xFF000000 || (unsigned)(p->low >> 32) != 0) + { + Byte temp = p->cache; + do + { + Byte *buf = p->buf; + *buf++ = (Byte)(temp + (Byte)(p->low >> 32)); + p->buf = buf; + if (buf == p->bufLim) + RangeEnc_FlushStream(p); + temp = 0xFF; + } + while (--p->cacheSize != 0); + p->cache = (Byte)((UInt32)p->low >> 24); + } + p->cacheSize++; + p->low = (UInt32)p->low << 8; +} + +static void RangeEnc_FlushData(CRangeEnc *p) +{ + int i; + for (i = 0; i < 5; i++) + RangeEnc_ShiftLow(p); +} + +static void RangeEnc_EncodeDirectBits(CRangeEnc *p, UInt32 value, unsigned numBits) +{ + do + { + p->range >>= 1; + p->low += p->range & (0 - ((value >> --numBits) & 1)); + if (p->range < kTopValue) + { + p->range <<= 8; + RangeEnc_ShiftLow(p); + } + } + while (numBits != 0); +} + +static void RangeEnc_EncodeBit(CRangeEnc *p, CLzmaProb *prob, UInt32 symbol) +{ + UInt32 ttt = *prob; + UInt32 newBound = (p->range >> kNumBitModelTotalBits) * ttt; + if (symbol == 0) + { + p->range = newBound; + ttt += (kBitModelTotal - ttt) >> kNumMoveBits; + } + else + { + p->low += newBound; + p->range -= newBound; + ttt -= ttt >> kNumMoveBits; + } + *prob = (CLzmaProb)ttt; + if (p->range < kTopValue) + { + p->range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol) +{ + symbol |= 0x100; + do + { + RangeEnc_EncodeBit(p, probs + (symbol >> 8), (symbol >> 7) & 1); + symbol <<= 1; + } + while (symbol < 0x10000); +} + +static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol, UInt32 matchByte) +{ + UInt32 offs = 0x100; + symbol |= 0x100; + do + { + matchByte <<= 1; + RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (symbol >> 8)), (symbol >> 7) & 1); + symbol <<= 1; + offs &= ~(matchByte ^ symbol); + } + while (symbol < 0x10000); +} + +void LzmaEnc_InitPriceTables(UInt32 *ProbPrices) +{ + UInt32 i; + for (i = (1 << kNumMoveReducingBits) / 2; i < kBitModelTotal; i += (1 << kNumMoveReducingBits)) + { + const int kCyclesBits = kNumBitPriceShiftBits; + UInt32 w = i; + UInt32 bitCount = 0; + int j; + for (j = 0; j < kCyclesBits; j++) + { + w = w * w; + bitCount <<= 1; + while (w >= ((UInt32)1 << 16)) + { + w >>= 1; + bitCount++; + } + } + ProbPrices[i >> kNumMoveReducingBits] = ((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount); + } +} + + +#define GET_PRICE(prob, symbol) \ + p->ProbPrices[((prob) ^ (((-(int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; + +#define GET_PRICEa(prob, symbol) \ + ProbPrices[((prob) ^ ((-((int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; + +#define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + +#define GET_PRICE_0a(prob) ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICE_1a(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + +static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 symbol, UInt32 *ProbPrices) +{ + UInt32 price = 0; + symbol |= 0x100; + do + { + price += GET_PRICEa(probs[symbol >> 8], (symbol >> 7) & 1); + symbol <<= 1; + } + while (symbol < 0x10000); + return price; +} + +static UInt32 LitEnc_GetPriceMatched(const CLzmaProb *probs, UInt32 symbol, UInt32 matchByte, UInt32 *ProbPrices) +{ + UInt32 price = 0; + UInt32 offs = 0x100; + symbol |= 0x100; + do + { + matchByte <<= 1; + price += GET_PRICEa(probs[offs + (matchByte & offs) + (symbol >> 8)], (symbol >> 7) & 1); + symbol <<= 1; + offs &= ~(matchByte ^ symbol); + } + while (symbol < 0x10000); + return price; +} + + +static void RcTree_Encode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol) +{ + UInt32 m = 1; + int i; + for (i = numBitLevels; i != 0;) + { + UInt32 bit; + i--; + bit = (symbol >> i) & 1; + RangeEnc_EncodeBit(rc, probs + m, bit); + m = (m << 1) | bit; + } +} + +static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol) +{ + UInt32 m = 1; + int i; + for (i = 0; i < numBitLevels; i++) + { + UInt32 bit = symbol & 1; + RangeEnc_EncodeBit(rc, probs + m, bit); + m = (m << 1) | bit; + symbol >>= 1; + } +} + +static UInt32 RcTree_GetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices) +{ + UInt32 price = 0; + symbol |= (1 << numBitLevels); + while (symbol != 1) + { + price += GET_PRICEa(probs[symbol >> 1], symbol & 1); + symbol >>= 1; + } + return price; +} + +static UInt32 RcTree_ReverseGetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, UInt32 *ProbPrices) +{ + UInt32 price = 0; + UInt32 m = 1; + int i; + for (i = numBitLevels; i != 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += GET_PRICEa(probs[m], bit); + m = (m << 1) | bit; + } + return price; +} + + +static void LenEnc_Init(CLenEnc *p) +{ + unsigned i; + p->choice = p->choice2 = kProbInitValue; + for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumLowBits); i++) + p->low[i] = kProbInitValue; + for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumMidBits); i++) + p->mid[i] = kProbInitValue; + for (i = 0; i < kLenNumHighSymbols; i++) + p->high[i] = kProbInitValue; +} + +static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState) +{ + if (symbol < kLenNumLowSymbols) + { + RangeEnc_EncodeBit(rc, &p->choice, 0); + RcTree_Encode(rc, p->low + (posState << kLenNumLowBits), kLenNumLowBits, symbol); + } + else + { + RangeEnc_EncodeBit(rc, &p->choice, 1); + if (symbol < kLenNumLowSymbols + kLenNumMidSymbols) + { + RangeEnc_EncodeBit(rc, &p->choice2, 0); + RcTree_Encode(rc, p->mid + (posState << kLenNumMidBits), kLenNumMidBits, symbol - kLenNumLowSymbols); + } + else + { + RangeEnc_EncodeBit(rc, &p->choice2, 1); + RcTree_Encode(rc, p->high, kLenNumHighBits, symbol - kLenNumLowSymbols - kLenNumMidSymbols); + } + } +} + +static void LenEnc_SetPrices(CLenEnc *p, UInt32 posState, UInt32 numSymbols, UInt32 *prices, UInt32 *ProbPrices) +{ + UInt32 a0 = GET_PRICE_0a(p->choice); + UInt32 a1 = GET_PRICE_1a(p->choice); + UInt32 b0 = a1 + GET_PRICE_0a(p->choice2); + UInt32 b1 = a1 + GET_PRICE_1a(p->choice2); + UInt32 i = 0; + for (i = 0; i < kLenNumLowSymbols; i++) + { + if (i >= numSymbols) + return; + prices[i] = a0 + RcTree_GetPrice(p->low + (posState << kLenNumLowBits), kLenNumLowBits, i, ProbPrices); + } + for (; i < kLenNumLowSymbols + kLenNumMidSymbols; i++) + { + if (i >= numSymbols) + return; + prices[i] = b0 + RcTree_GetPrice(p->mid + (posState << kLenNumMidBits), kLenNumMidBits, i - kLenNumLowSymbols, ProbPrices); + } + for (; i < numSymbols; i++) + prices[i] = b1 + RcTree_GetPrice(p->high, kLenNumHighBits, i - kLenNumLowSymbols - kLenNumMidSymbols, ProbPrices); +} + +static void MY_FAST_CALL LenPriceEnc_UpdateTable(CLenPriceEnc *p, UInt32 posState, UInt32 *ProbPrices) +{ + LenEnc_SetPrices(&p->p, posState, p->tableSize, p->prices[posState], ProbPrices); + p->counters[posState] = p->tableSize; +} + +static void LenPriceEnc_UpdateTables(CLenPriceEnc *p, UInt32 numPosStates, UInt32 *ProbPrices) +{ + UInt32 posState; + for (posState = 0; posState < numPosStates; posState++) + LenPriceEnc_UpdateTable(p, posState, ProbPrices); +} + +static void LenEnc_Encode2(CLenPriceEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState, Bool updatePrice, UInt32 *ProbPrices) +{ + LenEnc_Encode(&p->p, rc, symbol, posState); + if (updatePrice) + if (--p->counters[posState] == 0) + LenPriceEnc_UpdateTable(p, posState, ProbPrices); +} + + + + +static void MovePos(CLzmaEnc *p, UInt32 num) +{ + #ifdef SHOW_STAT + g_STAT_OFFSET += num; + printf("\n MovePos %d", num); + #endif + + if (num != 0) + { + p->additionalOffset += num; + p->matchFinder.Skip(p->matchFinderObj, num); + } +} + +static UInt32 ReadMatchDistances(CLzmaEnc *p, UInt32 *numDistancePairsRes) +{ + UInt32 lenRes = 0, numPairs; + p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); + numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches); + + #ifdef SHOW_STAT + printf("\n i = %d numPairs = %d ", g_STAT_OFFSET, numPairs / 2); + g_STAT_OFFSET++; + { + UInt32 i; + for (i = 0; i < numPairs; i += 2) + printf("%2d %6d | ", p->matches[i], p->matches[i + 1]); + } + #endif + + if (numPairs > 0) + { + lenRes = p->matches[numPairs - 2]; + if (lenRes == p->numFastBytes) + { + const Byte *pby = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + UInt32 distance = p->matches[numPairs - 1] + 1; + UInt32 numAvail = p->numAvail; + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + { + const Byte *pby2 = pby - distance; + for (; lenRes < numAvail && pby[lenRes] == pby2[lenRes]; lenRes++); + } + } + } + p->additionalOffset++; + *numDistancePairsRes = numPairs; + return lenRes; +} + + +#define MakeAsChar(p) (p)->backPrev = (UInt32)(-1); (p)->prev1IsChar = False; +#define MakeAsShortRep(p) (p)->backPrev = 0; (p)->prev1IsChar = False; +#define IsShortRep(p) ((p)->backPrev == 0) + +static UInt32 GetRepLen1Price(CLzmaEnc *p, UInt32 state, UInt32 posState) +{ + return + GET_PRICE_0(p->isRepG0[state]) + + GET_PRICE_0(p->isRep0Long[state][posState]); +} + +static UInt32 GetPureRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 state, UInt32 posState) +{ + UInt32 price; + if (repIndex == 0) + { + price = GET_PRICE_0(p->isRepG0[state]); + price += GET_PRICE_1(p->isRep0Long[state][posState]); + } + else + { + price = GET_PRICE_1(p->isRepG0[state]); + if (repIndex == 1) + price += GET_PRICE_0(p->isRepG1[state]); + else + { + price += GET_PRICE_1(p->isRepG1[state]); + price += GET_PRICE(p->isRepG2[state], repIndex - 2); + } + } + return price; +} + +static UInt32 GetRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 len, UInt32 state, UInt32 posState) +{ + return p->repLenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN] + + GetPureRepPrice(p, repIndex, state, posState); +} + +static UInt32 Backward(CLzmaEnc *p, UInt32 *backRes, UInt32 cur) +{ + UInt32 posMem = p->opt[cur].posPrev; + UInt32 backMem = p->opt[cur].backPrev; + p->optimumEndIndex = cur; + do + { + if (p->opt[cur].prev1IsChar) + { + MakeAsChar(&p->opt[posMem]) + p->opt[posMem].posPrev = posMem - 1; + if (p->opt[cur].prev2) + { + p->opt[posMem - 1].prev1IsChar = False; + p->opt[posMem - 1].posPrev = p->opt[cur].posPrev2; + p->opt[posMem - 1].backPrev = p->opt[cur].backPrev2; + } + } + { + UInt32 posPrev = posMem; + UInt32 backCur = backMem; + + backMem = p->opt[posPrev].backPrev; + posMem = p->opt[posPrev].posPrev; + + p->opt[posPrev].backPrev = backCur; + p->opt[posPrev].posPrev = cur; + cur = posPrev; + } + } + while (cur != 0); + *backRes = p->opt[0].backPrev; + p->optimumCurrentIndex = p->opt[0].posPrev; + return p->optimumCurrentIndex; +} + +#define LIT_PROBS(pos, prevByte) (p->litProbs + ((((pos) & p->lpMask) << p->lc) + ((prevByte) >> (8 - p->lc))) * 0x300) + +static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes) +{ + UInt32 numAvail, mainLen, numPairs, repMaxIndex, i, posState, lenEnd, len, cur; + UInt32 matchPrice, repMatchPrice, normalMatchPrice; + UInt32 reps[LZMA_NUM_REPS], repLens[LZMA_NUM_REPS]; + UInt32 *matches; + const Byte *data; + Byte curByte, matchByte; + if (p->optimumEndIndex != p->optimumCurrentIndex) + { + const COptimal *opt = &p->opt[p->optimumCurrentIndex]; + UInt32 lenRes = opt->posPrev - p->optimumCurrentIndex; + *backRes = opt->backPrev; + p->optimumCurrentIndex = opt->posPrev; + return lenRes; + } + p->optimumCurrentIndex = p->optimumEndIndex = 0; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else + { + mainLen = p->longestMatchLength; + numPairs = p->numPairs; + } + + numAvail = p->numAvail; + if (numAvail < 2) + { + *backRes = (UInt32)(-1); + return 1; + } + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + repMaxIndex = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 lenTest; + const Byte *data2; + reps[i] = p->reps[i]; + data2 = data - (reps[i] + 1); + if (data[0] != data2[0] || data[1] != data2[1]) + { + repLens[i] = 0; + continue; + } + for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++); + repLens[i] = lenTest; + if (lenTest > repLens[repMaxIndex]) + repMaxIndex = i; + } + if (repLens[repMaxIndex] >= p->numFastBytes) + { + UInt32 lenRes; + *backRes = repMaxIndex; + lenRes = repLens[repMaxIndex]; + MovePos(p, lenRes - 1); + return lenRes; + } + + matches = p->matches; + if (mainLen >= p->numFastBytes) + { + *backRes = matches[numPairs - 1] + LZMA_NUM_REPS; + MovePos(p, mainLen - 1); + return mainLen; + } + curByte = *data; + matchByte = *(data - (reps[0] + 1)); + + if (mainLen < 2 && curByte != matchByte && repLens[repMaxIndex] < 2) + { + *backRes = (UInt32)-1; + return 1; + } + + p->opt[0].state = (CState)p->state; + + posState = (position & p->pbMask); + + { + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) + + (!IsCharState(p->state) ? + LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + } + + MakeAsChar(&p->opt[1]); + + matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]); + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]); + + if (matchByte == curByte) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, p->state, posState); + if (shortRepPrice < p->opt[1].price) + { + p->opt[1].price = shortRepPrice; + MakeAsShortRep(&p->opt[1]); + } + } + lenEnd = ((mainLen >= repLens[repMaxIndex]) ? mainLen : repLens[repMaxIndex]); + + if (lenEnd < 2) + { + *backRes = p->opt[1].backPrev; + return 1; + } + + p->opt[1].posPrev = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) + p->opt[0].backs[i] = reps[i]; + + len = lenEnd; + do + p->opt[len--].price = kInfinityPrice; + while (len >= 2); + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 repLen = repLens[i]; + UInt32 price; + if (repLen < 2) + continue; + price = repMatchPrice + GetPureRepPrice(p, i, p->state, posState); + do + { + UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][repLen - 2]; + COptimal *opt = &p->opt[repLen]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = 0; + opt->backPrev = i; + opt->prev1IsChar = False; + } + } + while (--repLen >= 2); + } + + normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]); + + len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2); + if (len <= mainLen) + { + UInt32 offs = 0; + while (len > matches[offs]) + offs += 2; + for (; ; len++) + { + COptimal *opt; + UInt32 distance = matches[offs + 1]; + + UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN]; + UInt32 lenToPosState = GetLenToPosState(len); + if (distance < kNumFullDistances) + curAndLenPrice += p->distancesPrices[lenToPosState][distance]; + else + { + UInt32 slot; + GetPosSlot2(distance, slot); + curAndLenPrice += p->alignPrices[distance & kAlignMask] + p->posSlotPrices[lenToPosState][slot]; + } + opt = &p->opt[len]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = 0; + opt->backPrev = distance + LZMA_NUM_REPS; + opt->prev1IsChar = False; + } + if (len == matches[offs]) + { + offs += 2; + if (offs == numPairs) + break; + } + } + } + + cur = 0; + + #ifdef SHOW_STAT2 + if (position >= 0) + { + unsigned i; + printf("\n pos = %4X", position); + for (i = cur; i <= lenEnd; i++) + printf("\nprice[%4X] = %d", position - cur + i, p->opt[i].price); + } + #endif + + for (;;) + { + UInt32 numAvailFull, newLen, numPairs, posPrev, state, posState, startLen; + UInt32 curPrice, curAnd1Price, matchPrice, repMatchPrice; + Bool nextIsChar; + Byte curByte, matchByte; + const Byte *data; + COptimal *curOpt; + COptimal *nextOpt; + + cur++; + if (cur == lenEnd) + return Backward(p, backRes, cur); + + newLen = ReadMatchDistances(p, &numPairs); + if (newLen >= p->numFastBytes) + { + p->numPairs = numPairs; + p->longestMatchLength = newLen; + return Backward(p, backRes, cur); + } + position++; + curOpt = &p->opt[cur]; + posPrev = curOpt->posPrev; + if (curOpt->prev1IsChar) + { + posPrev--; + if (curOpt->prev2) + { + state = p->opt[curOpt->posPrev2].state; + if (curOpt->backPrev2 < LZMA_NUM_REPS) + state = kRepNextStates[state]; + else + state = kMatchNextStates[state]; + } + else + state = p->opt[posPrev].state; + state = kLiteralNextStates[state]; + } + else + state = p->opt[posPrev].state; + if (posPrev == cur - 1) + { + if (IsShortRep(curOpt)) + state = kShortRepNextStates[state]; + else + state = kLiteralNextStates[state]; + } + else + { + UInt32 pos; + const COptimal *prevOpt; + if (curOpt->prev1IsChar && curOpt->prev2) + { + posPrev = curOpt->posPrev2; + pos = curOpt->backPrev2; + state = kRepNextStates[state]; + } + else + { + pos = curOpt->backPrev; + if (pos < LZMA_NUM_REPS) + state = kRepNextStates[state]; + else + state = kMatchNextStates[state]; + } + prevOpt = &p->opt[posPrev]; + if (pos < LZMA_NUM_REPS) + { + UInt32 i; + reps[0] = prevOpt->backs[pos]; + for (i = 1; i <= pos; i++) + reps[i] = prevOpt->backs[i - 1]; + for (; i < LZMA_NUM_REPS; i++) + reps[i] = prevOpt->backs[i]; + } + else + { + UInt32 i; + reps[0] = (pos - LZMA_NUM_REPS); + for (i = 1; i < LZMA_NUM_REPS; i++) + reps[i] = prevOpt->backs[i - 1]; + } + } + curOpt->state = (CState)state; + + curOpt->backs[0] = reps[0]; + curOpt->backs[1] = reps[1]; + curOpt->backs[2] = reps[2]; + curOpt->backs[3] = reps[3]; + + curPrice = curOpt->price; + nextIsChar = False; + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + curByte = *data; + matchByte = *(data - (reps[0] + 1)); + + posState = (position & p->pbMask); + + curAnd1Price = curPrice + GET_PRICE_0(p->isMatch[state][posState]); + { + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + curAnd1Price += + (!IsCharState(state) ? + LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + } + + nextOpt = &p->opt[cur + 1]; + + if (curAnd1Price < nextOpt->price) + { + nextOpt->price = curAnd1Price; + nextOpt->posPrev = cur; + MakeAsChar(nextOpt); + nextIsChar = True; + } + + matchPrice = curPrice + GET_PRICE_1(p->isMatch[state][posState]); + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]); + + if (matchByte == curByte && !(nextOpt->posPrev < cur && nextOpt->backPrev == 0)) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, state, posState); + if (shortRepPrice <= nextOpt->price) + { + nextOpt->price = shortRepPrice; + nextOpt->posPrev = cur; + MakeAsShortRep(nextOpt); + nextIsChar = True; + } + } + numAvailFull = p->numAvail; + { + UInt32 temp = kNumOpts - 1 - cur; + if (temp < numAvailFull) + numAvailFull = temp; + } + + if (numAvailFull < 2) + continue; + numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes); + + if (!nextIsChar && matchByte != curByte) /* speed optimization */ + { + /* try Literal + rep0 */ + UInt32 temp; + UInt32 lenTest2; + const Byte *data2 = data - (reps[0] + 1); + UInt32 limit = p->numFastBytes + 1; + if (limit > numAvailFull) + limit = numAvailFull; + + for (temp = 1; temp < limit && data[temp] == data2[temp]; temp++); + lenTest2 = temp - 1; + if (lenTest2 >= 2) + { + UInt32 state2 = kLiteralNextStates[state]; + UInt32 posStateNext = (position + 1) & p->pbMask; + UInt32 nextRepMatchPrice = curAnd1Price + + GET_PRICE_1(p->isMatch[state2][posStateNext]) + + GET_PRICE_1(p->isRep[state2]); + /* for (; lenTest2 >= 2; lenTest2--) */ + { + UInt32 curAndLenPrice; + COptimal *opt; + UInt32 offset = cur + 1 + lenTest2; + while (lenEnd < offset) + p->opt[++lenEnd].price = kInfinityPrice; + curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + opt = &p->opt[offset]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur + 1; + opt->backPrev = 0; + opt->prev1IsChar = True; + opt->prev2 = False; + } + } + } + } + + startLen = 2; /* speed optimization */ + { + UInt32 repIndex; + for (repIndex = 0; repIndex < LZMA_NUM_REPS; repIndex++) + { + UInt32 lenTest; + UInt32 lenTestTemp; + UInt32 price; + const Byte *data2 = data - (reps[repIndex] + 1); + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++); + while (lenEnd < cur + lenTest) + p->opt[++lenEnd].price = kInfinityPrice; + lenTestTemp = lenTest; + price = repMatchPrice + GetPureRepPrice(p, repIndex, state, posState); + do + { + UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][lenTest - 2]; + COptimal *opt = &p->opt[cur + lenTest]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur; + opt->backPrev = repIndex; + opt->prev1IsChar = False; + } + } + while (--lenTest >= 2); + lenTest = lenTestTemp; + + if (repIndex == 0) + startLen = lenTest + 1; + + /* if (_maxMode) */ + { + UInt32 lenTest2 = lenTest + 1; + UInt32 limit = lenTest2 + p->numFastBytes; + UInt32 nextRepMatchPrice; + if (limit > numAvailFull) + limit = numAvailFull; + for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++); + lenTest2 -= lenTest + 1; + if (lenTest2 >= 2) + { + UInt32 state2 = kRepNextStates[state]; + UInt32 posStateNext = (position + lenTest) & p->pbMask; + UInt32 curAndLenCharPrice = + price + p->repLenEnc.prices[posState][lenTest - 2] + + GET_PRICE_0(p->isMatch[state2][posStateNext]) + + LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]), + data[lenTest], data2[lenTest], p->ProbPrices); + state2 = kLiteralNextStates[state2]; + posStateNext = (position + lenTest + 1) & p->pbMask; + nextRepMatchPrice = curAndLenCharPrice + + GET_PRICE_1(p->isMatch[state2][posStateNext]) + + GET_PRICE_1(p->isRep[state2]); + + /* for (; lenTest2 >= 2; lenTest2--) */ + { + UInt32 curAndLenPrice; + COptimal *opt; + UInt32 offset = cur + lenTest + 1 + lenTest2; + while (lenEnd < offset) + p->opt[++lenEnd].price = kInfinityPrice; + curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + opt = &p->opt[offset]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur + lenTest + 1; + opt->backPrev = 0; + opt->prev1IsChar = True; + opt->prev2 = True; + opt->posPrev2 = cur; + opt->backPrev2 = repIndex; + } + } + } + } + } + } + /* for (UInt32 lenTest = 2; lenTest <= newLen; lenTest++) */ + if (newLen > numAvail) + { + newLen = numAvail; + for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2); + matches[numPairs] = newLen; + numPairs += 2; + } + if (newLen >= startLen) + { + UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]); + UInt32 offs, curBack, posSlot; + UInt32 lenTest; + while (lenEnd < cur + newLen) + p->opt[++lenEnd].price = kInfinityPrice; + + offs = 0; + while (startLen > matches[offs]) + offs += 2; + curBack = matches[offs + 1]; + GetPosSlot2(curBack, posSlot); + for (lenTest = /*2*/ startLen; ; lenTest++) + { + UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][lenTest - LZMA_MATCH_LEN_MIN]; + UInt32 lenToPosState = GetLenToPosState(lenTest); + COptimal *opt; + if (curBack < kNumFullDistances) + curAndLenPrice += p->distancesPrices[lenToPosState][curBack]; + else + curAndLenPrice += p->posSlotPrices[lenToPosState][posSlot] + p->alignPrices[curBack & kAlignMask]; + + opt = &p->opt[cur + lenTest]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur; + opt->backPrev = curBack + LZMA_NUM_REPS; + opt->prev1IsChar = False; + } + + if (/*_maxMode && */lenTest == matches[offs]) + { + /* Try Match + Literal + Rep0 */ + const Byte *data2 = data - (curBack + 1); + UInt32 lenTest2 = lenTest + 1; + UInt32 limit = lenTest2 + p->numFastBytes; + UInt32 nextRepMatchPrice; + if (limit > numAvailFull) + limit = numAvailFull; + for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++); + lenTest2 -= lenTest + 1; + if (lenTest2 >= 2) + { + UInt32 state2 = kMatchNextStates[state]; + UInt32 posStateNext = (position + lenTest) & p->pbMask; + UInt32 curAndLenCharPrice = curAndLenPrice + + GET_PRICE_0(p->isMatch[state2][posStateNext]) + + LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]), + data[lenTest], data2[lenTest], p->ProbPrices); + state2 = kLiteralNextStates[state2]; + posStateNext = (posStateNext + 1) & p->pbMask; + nextRepMatchPrice = curAndLenCharPrice + + GET_PRICE_1(p->isMatch[state2][posStateNext]) + + GET_PRICE_1(p->isRep[state2]); + + /* for (; lenTest2 >= 2; lenTest2--) */ + { + UInt32 offset = cur + lenTest + 1 + lenTest2; + UInt32 curAndLenPrice; + COptimal *opt; + while (lenEnd < offset) + p->opt[++lenEnd].price = kInfinityPrice; + curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + opt = &p->opt[offset]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur + lenTest + 1; + opt->backPrev = 0; + opt->prev1IsChar = True; + opt->prev2 = True; + opt->posPrev2 = cur; + opt->backPrev2 = curBack + LZMA_NUM_REPS; + } + } + } + offs += 2; + if (offs == numPairs) + break; + curBack = matches[offs + 1]; + if (curBack >= kNumFullDistances) + GetPosSlot2(curBack, posSlot); + } + } + } + } +} + +#define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist)) + +static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes) +{ + UInt32 numAvail, mainLen, mainDist, numPairs, repIndex, repLen, i; + const Byte *data; + const UInt32 *matches; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else + { + mainLen = p->longestMatchLength; + numPairs = p->numPairs; + } + + numAvail = p->numAvail; + *backRes = (UInt32)-1; + if (numAvail < 2) + return 1; + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + + repLen = repIndex = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 len; + const Byte *data2 = data - (p->reps[i] + 1); + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + for (len = 2; len < numAvail && data[len] == data2[len]; len++); + if (len >= p->numFastBytes) + { + *backRes = i; + MovePos(p, len - 1); + return len; + } + if (len > repLen) + { + repIndex = i; + repLen = len; + } + } + + matches = p->matches; + if (mainLen >= p->numFastBytes) + { + *backRes = matches[numPairs - 1] + LZMA_NUM_REPS; + MovePos(p, mainLen - 1); + return mainLen; + } + + mainDist = 0; /* for GCC */ + if (mainLen >= 2) + { + mainDist = matches[numPairs - 1]; + while (numPairs > 2 && mainLen == matches[numPairs - 4] + 1) + { + if (!ChangePair(matches[numPairs - 3], mainDist)) + break; + numPairs -= 2; + mainLen = matches[numPairs - 2]; + mainDist = matches[numPairs - 1]; + } + if (mainLen == 2 && mainDist >= 0x80) + mainLen = 1; + } + + if (repLen >= 2 && ( + (repLen + 1 >= mainLen) || + (repLen + 2 >= mainLen && mainDist >= (1 << 9)) || + (repLen + 3 >= mainLen && mainDist >= (1 << 15)))) + { + *backRes = repIndex; + MovePos(p, repLen - 1); + return repLen; + } + + if (mainLen < 2 || numAvail <= 2) + return 1; + + p->longestMatchLength = ReadMatchDistances(p, &p->numPairs); + if (p->longestMatchLength >= 2) + { + UInt32 newDistance = matches[p->numPairs - 1]; + if ((p->longestMatchLength >= mainLen && newDistance < mainDist) || + (p->longestMatchLength == mainLen + 1 && !ChangePair(mainDist, newDistance)) || + (p->longestMatchLength > mainLen + 1) || + (p->longestMatchLength + 1 >= mainLen && mainLen >= 3 && ChangePair(newDistance, mainDist))) + return 1; + } + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 len, limit; + const Byte *data2 = data - (p->reps[i] + 1); + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + limit = mainLen - 1; + for (len = 2; len < limit && data[len] == data2[len]; len++); + if (len >= limit) + return 1; + } + *backRes = mainDist + LZMA_NUM_REPS; + MovePos(p, mainLen - 2); + return mainLen; +} + +static void WriteEndMarker(CLzmaEnc *p, UInt32 posState) +{ + UInt32 len; + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1); + RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0); + p->state = kMatchNextStates[p->state]; + len = LZMA_MATCH_LEN_MIN; + LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, (1 << kNumPosSlotBits) - 1); + RangeEnc_EncodeDirectBits(&p->rc, (((UInt32)1 << 30) - 1) >> kNumAlignBits, 30 - kNumAlignBits); + RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask); +} + +static SRes CheckErrors(CLzmaEnc *p) +{ + if (p->result != SZ_OK) + return p->result; + if (p->rc.res != SZ_OK) + p->result = SZ_ERROR_WRITE; + if (p->matchFinderBase.result != SZ_OK) + p->result = SZ_ERROR_READ; + if (p->result != SZ_OK) + p->finished = True; + return p->result; +} + +static SRes Flush(CLzmaEnc *p, UInt32 nowPos) +{ + /* ReleaseMFStream(); */ + p->finished = True; + if (p->writeEndMark) + WriteEndMarker(p, nowPos & p->pbMask); + RangeEnc_FlushData(&p->rc); + RangeEnc_FlushStream(&p->rc); + return CheckErrors(p); +} + +static void FillAlignPrices(CLzmaEnc *p) +{ + UInt32 i; + for (i = 0; i < kAlignTableSize; i++) + p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices); + p->alignPriceCount = 0; +} + +static void FillDistancesPrices(CLzmaEnc *p) +{ + UInt32 tempPrices[kNumFullDistances]; + UInt32 i, lenToPosState; + for (i = kStartPosModelIndex; i < kNumFullDistances; i++) + { + UInt32 posSlot = GetPosSlot1(i); + UInt32 footerBits = ((posSlot >> 1) - 1); + UInt32 base = ((2 | (posSlot & 1)) << footerBits); + tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base - posSlot - 1, footerBits, i - base, p->ProbPrices); + } + + for (lenToPosState = 0; lenToPosState < kNumLenToPosStates; lenToPosState++) + { + UInt32 posSlot; + const CLzmaProb *encoder = p->posSlotEncoder[lenToPosState]; + UInt32 *posSlotPrices = p->posSlotPrices[lenToPosState]; + for (posSlot = 0; posSlot < p->distTableSize; posSlot++) + posSlotPrices[posSlot] = RcTree_GetPrice(encoder, kNumPosSlotBits, posSlot, p->ProbPrices); + for (posSlot = kEndPosModelIndex; posSlot < p->distTableSize; posSlot++) + posSlotPrices[posSlot] += ((((posSlot >> 1) - 1) - kNumAlignBits) << kNumBitPriceShiftBits); + + { + UInt32 *distancesPrices = p->distancesPrices[lenToPosState]; + UInt32 i; + for (i = 0; i < kStartPosModelIndex; i++) + distancesPrices[i] = posSlotPrices[i]; + for (; i < kNumFullDistances; i++) + distancesPrices[i] = posSlotPrices[GetPosSlot1(i)] + tempPrices[i]; + } + } + p->matchPriceCount = 0; +} + +void LzmaEnc_Construct(CLzmaEnc *p) +{ + RangeEnc_Construct(&p->rc); + MatchFinder_Construct(&p->matchFinderBase); + #ifndef _7ZIP_ST + MatchFinderMt_Construct(&p->matchFinderMt); + p->matchFinderMt.MatchFinder = &p->matchFinderBase; + #endif + + { + CLzmaEncProps props; + LzmaEncProps_Init(&props); + LzmaEnc_SetProps(p, &props); + } + + #ifndef LZMA_LOG_BSR + LzmaEnc_FastPosInit(p->g_FastPos); + #endif + + LzmaEnc_InitPriceTables(p->ProbPrices); + p->litProbs = 0; + p->saveState.litProbs = 0; +} + +CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc) +{ + void *p; + p = alloc->Alloc(alloc, sizeof(CLzmaEnc)); + if (p != 0) + LzmaEnc_Construct((CLzmaEnc *)p); + return p; +} + +void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->litProbs); + alloc->Free(alloc, p->saveState.litProbs); + p->litProbs = 0; + p->saveState.litProbs = 0; +} + +void LzmaEnc_Destruct(CLzmaEnc *p, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + #ifndef _7ZIP_ST + MatchFinderMt_Destruct(&p->matchFinderMt, allocBig); + #endif + MatchFinder_Free(&p->matchFinderBase, allocBig); + LzmaEnc_FreeLits(p, alloc); + RangeEnc_Free(&p->rc, alloc); +} + +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig); + alloc->Free(alloc, p); +} + +static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize, UInt32 maxUnpackSize) +{ + UInt32 nowPos32, startPos32; + if (p->needInit) + { + p->matchFinder.Init(p->matchFinderObj); + p->needInit = 0; + } + + if (p->finished) + return p->result; + RINOK(CheckErrors(p)); + + nowPos32 = (UInt32)p->nowPos64; + startPos32 = nowPos32; + + if (p->nowPos64 == 0) + { + UInt32 numPairs; + Byte curByte; + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) + return Flush(p, nowPos32); + ReadMatchDistances(p, &numPairs); + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][0], 0); + p->state = kLiteralNextStates[p->state]; + curByte = p->matchFinder.GetIndexByte(p->matchFinderObj, 0 - p->additionalOffset); + LitEnc_Encode(&p->rc, p->litProbs, curByte); + p->additionalOffset--; + nowPos32++; + } + + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0) + for (;;) + { + UInt32 pos, len, posState; + + if (p->fastMode) + len = GetOptimumFast(p, &pos); + else + len = GetOptimum(p, nowPos32, &pos); + + #ifdef SHOW_STAT2 + printf("\n pos = %4X, len = %d pos = %d", nowPos32, len, pos); + #endif + + posState = nowPos32 & p->pbMask; + if (len == 1 && pos == (UInt32)-1) + { + Byte curByte; + CLzmaProb *probs; + const Byte *data; + + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 0); + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; + curByte = *data; + probs = LIT_PROBS(nowPos32, *(data - 1)); + if (IsCharState(p->state)) + LitEnc_Encode(&p->rc, probs, curByte); + else + LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0] - 1)); + p->state = kLiteralNextStates[p->state]; + } + else + { + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1); + if (pos < LZMA_NUM_REPS) + { + RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 1); + if (pos == 0) + { + RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 0); + RangeEnc_EncodeBit(&p->rc, &p->isRep0Long[p->state][posState], ((len == 1) ? 0 : 1)); + } + else + { + UInt32 distance = p->reps[pos]; + RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 1); + if (pos == 1) + RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 0); + else + { + RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 1); + RangeEnc_EncodeBit(&p->rc, &p->isRepG2[p->state], pos - 2); + if (pos == 3) + p->reps[3] = p->reps[2]; + p->reps[2] = p->reps[1]; + } + p->reps[1] = p->reps[0]; + p->reps[0] = distance; + } + if (len == 1) + p->state = kShortRepNextStates[p->state]; + else + { + LenEnc_Encode2(&p->repLenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + p->state = kRepNextStates[p->state]; + } + } + else + { + UInt32 posSlot; + RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0); + p->state = kMatchNextStates[p->state]; + LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + pos -= LZMA_NUM_REPS; + GetPosSlot(pos, posSlot); + RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, posSlot); + + if (posSlot >= kStartPosModelIndex) + { + UInt32 footerBits = ((posSlot >> 1) - 1); + UInt32 base = ((2 | (posSlot & 1)) << footerBits); + UInt32 posReduced = pos - base; + + if (posSlot < kEndPosModelIndex) + RcTree_ReverseEncode(&p->rc, p->posEncoders + base - posSlot - 1, footerBits, posReduced); + else + { + RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits); + RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask); + p->alignPriceCount++; + } + } + p->reps[3] = p->reps[2]; + p->reps[2] = p->reps[1]; + p->reps[1] = p->reps[0]; + p->reps[0] = pos; + p->matchPriceCount++; + } + } + p->additionalOffset -= len; + nowPos32 += len; + if (p->additionalOffset == 0) + { + UInt32 processed; + if (!p->fastMode) + { + if (p->matchPriceCount >= (1 << 7)) + FillDistancesPrices(p); + if (p->alignPriceCount >= kAlignTableSize) + FillAlignPrices(p); + } + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) + break; + processed = nowPos32 - startPos32; + if (useLimits) + { + if (processed + kNumOpts + 300 >= maxUnpackSize || + RangeEnc_GetProcessed(&p->rc) + kNumOpts * 2 >= maxPackSize) + break; + } + else if (processed >= (1 << 15)) + { + p->nowPos64 += nowPos32 - startPos32; + return CheckErrors(p); + } + } + } + p->nowPos64 += nowPos32 - startPos32; + return Flush(p, nowPos32); +} + +#define kBigHashDicLimit ((UInt32)1 << 24) + +static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + UInt32 beforeSize = kNumOpts; + if (!RangeEnc_Alloc(&p->rc, alloc)) + return SZ_ERROR_MEM; + #ifndef _7ZIP_ST + p->mtMode = (p->multiThread && !p->fastMode && (p->matchFinderBase.btMode != 0)); + #endif + + { + unsigned lclp = p->lc + p->lp; + if (p->litProbs == 0 || p->saveState.litProbs == 0 || p->lclp != lclp) + { + LzmaEnc_FreeLits(p, alloc); + p->litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb)); + p->saveState.litProbs = (CLzmaProb *)alloc->Alloc(alloc, (0x300 << lclp) * sizeof(CLzmaProb)); + if (p->litProbs == 0 || p->saveState.litProbs == 0) + { + LzmaEnc_FreeLits(p, alloc); + return SZ_ERROR_MEM; + } + p->lclp = lclp; + } + } + + p->matchFinderBase.bigHash = (p->dictSize > kBigHashDicLimit); + + if (beforeSize + p->dictSize < keepWindowSize) + beforeSize = keepWindowSize - p->dictSize; + + #ifndef _7ZIP_ST + if (p->mtMode) + { + RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)); + p->matchFinderObj = &p->matchFinderMt; + MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder); + } + else + #endif + { + if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)) + return SZ_ERROR_MEM; + p->matchFinderObj = &p->matchFinderBase; + MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder); + } + return SZ_OK; +} + +void LzmaEnc_Init(CLzmaEnc *p) +{ + UInt32 i; + p->state = 0; + for (i = 0 ; i < LZMA_NUM_REPS; i++) + p->reps[i] = 0; + + RangeEnc_Init(&p->rc); + + + for (i = 0; i < kNumStates; i++) + { + UInt32 j; + for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++) + { + p->isMatch[i][j] = kProbInitValue; + p->isRep0Long[i][j] = kProbInitValue; + } + p->isRep[i] = kProbInitValue; + p->isRepG0[i] = kProbInitValue; + p->isRepG1[i] = kProbInitValue; + p->isRepG2[i] = kProbInitValue; + } + + { + UInt32 num = 0x300 << (p->lp + p->lc); + for (i = 0; i < num; i++) + p->litProbs[i] = kProbInitValue; + } + + { + for (i = 0; i < kNumLenToPosStates; i++) + { + CLzmaProb *probs = p->posSlotEncoder[i]; + UInt32 j; + for (j = 0; j < (1 << kNumPosSlotBits); j++) + probs[j] = kProbInitValue; + } + } + { + for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++) + p->posEncoders[i] = kProbInitValue; + } + + LenEnc_Init(&p->lenEnc.p); + LenEnc_Init(&p->repLenEnc.p); + + for (i = 0; i < (1 << kNumAlignBits); i++) + p->posAlignEncoder[i] = kProbInitValue; + + p->optimumEndIndex = 0; + p->optimumCurrentIndex = 0; + p->additionalOffset = 0; + + p->pbMask = (1 << p->pb) - 1; + p->lpMask = (1 << p->lp) - 1; +} + +void LzmaEnc_InitPrices(CLzmaEnc *p) +{ + if (!p->fastMode) + { + FillDistancesPrices(p); + FillAlignPrices(p); + } + + p->lenEnc.tableSize = + p->repLenEnc.tableSize = + p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN; + LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, p->ProbPrices); + LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, p->ProbPrices); +} + +static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + UInt32 i; + for (i = 0; i < (UInt32)kDicLogSizeMaxCompress; i++) + if (p->dictSize <= ((UInt32)1 << i)) + break; + p->distTableSize = i * 2; + + p->finished = False; + p->result = SZ_OK; + RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig)); + LzmaEnc_Init(p); + LzmaEnc_InitPrices(p); + p->nowPos64 = 0; + return SZ_OK; +} + +static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, + ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.stream = inStream; + p->needInit = 1; + p->rc.outStream = outStream; + return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig); +} + +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, + ISeqInStream *inStream, UInt32 keepWindowSize, + ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.stream = inStream; + p->needInit = 1; + return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); +} + +static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen) +{ + p->matchFinderBase.directInput = 1; + p->matchFinderBase.bufferBase = (Byte *)src; + p->matchFinderBase.directInputRem = srcLen; +} + +SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + LzmaEnc_SetInputBuf(p, src, srcLen); + p->needInit = 1; + + return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); +} + +void LzmaEnc_Finish(CLzmaEncHandle pp) +{ + #ifndef _7ZIP_ST + CLzmaEnc *p = (CLzmaEnc *)pp; + if (p->mtMode) + MatchFinderMt_ReleaseStream(&p->matchFinderMt); + #else + pp = pp; + #endif +} + +typedef struct +{ + ISeqOutStream funcTable; + Byte *data; + SizeT rem; + Bool overflow; +} CSeqOutStreamBuf; + +static size_t MyWrite(void *pp, const void *data, size_t size) +{ + CSeqOutStreamBuf *p = (CSeqOutStreamBuf *)pp; + if (p->rem < size) + { + size = p->rem; + p->overflow = True; + } + memcpy(p->data, data, size); + p->rem -= size; + p->data += size; + return size; +} + + +UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp) +{ + const CLzmaEnc *p = (CLzmaEnc *)pp; + return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); +} + +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp) +{ + const CLzmaEnc *p = (CLzmaEnc *)pp; + return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; +} + +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, + Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + UInt64 nowPos64; + SRes res; + CSeqOutStreamBuf outStream; + + outStream.funcTable.Write = MyWrite; + outStream.data = dest; + outStream.rem = *destLen; + outStream.overflow = False; + + p->writeEndMark = False; + p->finished = False; + p->result = SZ_OK; + + if (reInit) + LzmaEnc_Init(p); + LzmaEnc_InitPrices(p); + nowPos64 = p->nowPos64; + RangeEnc_Init(&p->rc); + p->rc.outStream = &outStream.funcTable; + + res = LzmaEnc_CodeOneBlock(p, True, desiredPackSize, *unpackSize); + + *unpackSize = (UInt32)(p->nowPos64 - nowPos64); + *destLen -= outStream.rem; + if (outStream.overflow) + return SZ_ERROR_OUTPUT_EOF; + + return res; +} + +static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) +{ + SRes res = SZ_OK; + + #ifndef _7ZIP_ST + Byte allocaDummy[0x300]; + allocaDummy[0] = 0; + allocaDummy[1] = allocaDummy[0]; + #endif + + for (;;) + { + res = LzmaEnc_CodeOneBlock(p, False, 0, 0); + if (res != SZ_OK || p->finished != 0) + break; + if (progress != 0) + { + res = progress->Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc)); + if (res != SZ_OK) + { + res = SZ_ERROR_PROGRESS; + break; + } + } + } + LzmaEnc_Finish(p); + return res; +} + +SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress, + ISzAlloc *alloc, ISzAlloc *allocBig) +{ + RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig)); + return LzmaEnc_Encode2((CLzmaEnc *)pp, progress); +} + +SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + int i; + UInt32 dictSize = p->dictSize; + if (*size < LZMA_PROPS_SIZE) + return SZ_ERROR_PARAM; + *size = LZMA_PROPS_SIZE; + props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc); + + for (i = 11; i <= 30; i++) + { + if (dictSize <= ((UInt32)2 << i)) + { + dictSize = (2 << i); + break; + } + if (dictSize <= ((UInt32)3 << i)) + { + dictSize = (3 << i); + break; + } + } + + for (i = 0; i < 4; i++) + props[1 + i] = (Byte)(dictSize >> (8 * i)); + return SZ_OK; +} + +SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + SRes res; + CLzmaEnc *p = (CLzmaEnc *)pp; + + CSeqOutStreamBuf outStream; + + LzmaEnc_SetInputBuf(p, src, srcLen); + + outStream.funcTable.Write = MyWrite; + outStream.data = dest; + outStream.rem = *destLen; + outStream.overflow = False; + + p->writeEndMark = writeEndMark; + + p->rc.outStream = &outStream.funcTable; + res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig); + if (res == SZ_OK) + res = LzmaEnc_Encode2(p, progress); + + *destLen -= outStream.rem; + if (outStream.overflow) + return SZ_ERROR_OUTPUT_EOF; + return res; +} + +SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc); + SRes res; + if (p == 0) + return SZ_ERROR_MEM; + + res = LzmaEnc_SetProps(p, props); + if (res == SZ_OK) + { + res = LzmaEnc_WriteProperties(p, propsEncoded, propsSize); + if (res == SZ_OK) + res = LzmaEnc_MemEncode(p, dest, destLen, src, srcLen, + writeEndMark, progress, alloc, allocBig); + } + + LzmaEnc_Destroy(p, alloc, allocBig); + return res; +}
@@ -0,0 +1,78 @@
+/* LzmaEnc.h -- LZMA Encoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_ENC_H +#define __LZMA_ENC_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaEncProps +{ + int level; /* 0 <= level <= 9 */ + UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version + (1 << 12) <= dictSize <= (1 << 30) for 64-bit version + default = (1 << 24) */ + UInt64 reduceSize; /* estimated size of data that will be compressed. default = 0xFFFFFFFF. + Encoder uses this value to reduce dictionary size */ + int lc; /* 0 <= lc <= 8, default = 3 */ + int lp; /* 0 <= lp <= 4, default = 0 */ + int pb; /* 0 <= pb <= 4, default = 2 */ + int algo; /* 0 - fast, 1 - normal, default = 1 */ + int fb; /* 5 <= fb <= 273, default = 32 */ + int btMode; /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */ + int numHashBytes; /* 2, 3 or 4, default = 4 */ + UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */ + unsigned writeEndMark; /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */ + int numThreads; /* 1 or 2, default = 2 */ +} CLzmaEncProps; + +void LzmaEncProps_Init(CLzmaEncProps *p); +void LzmaEncProps_Normalize(CLzmaEncProps *p); +UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2); + + +/* ---------- CLzmaEncHandle Interface ---------- */ + +/* LzmaEnc_* functions can return the following exit codes: +Returns: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater in props + SZ_ERROR_WRITE - Write callback error. + SZ_ERROR_PROGRESS - some break from progress callback + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +typedef void * CLzmaEncHandle; + +CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc); +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig); +SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props); +SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size); +SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); +SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +/* ---------- One Call Interface ---------- */ + +/* LzmaEncode +Return code: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +EXTERN_C_END + +#endif
@@ -0,0 +1,46 @@
+/* LzmaLib.c -- LZMA library wrapper +2008-08-05 +Igor Pavlov +Public domain */ + +#include "LzmaEnc.h" +#include "LzmaDec.h" +#include "Alloc.h" +#include "LzmaLib.h" + +static void *SzAlloc(void *p, size_t size) { p = p; return MyAlloc(size); } +static void SzFree(void *p, void *address) { p = p; MyFree(address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, + unsigned char *outProps, size_t *outPropsSize, + int level, /* 0 <= level <= 9, default = 5 */ + unsigned dictSize, /* use (1 << N) or (3 << N). 4 KB < dictSize <= 128 MB */ + int lc, /* 0 <= lc <= 8, default = 3 */ + int lp, /* 0 <= lp <= 4, default = 0 */ + int pb, /* 0 <= pb <= 4, default = 2 */ + int fb, /* 5 <= fb <= 273, default = 32 */ + int numThreads /* 1 or 2, default = 2 */ +) +{ + CLzmaEncProps props; + LzmaEncProps_Init(&props); + props.level = level; + props.dictSize = dictSize; + props.lc = lc; + props.lp = lp; + props.pb = pb; + props.fb = fb; + props.numThreads = numThreads; + + return LzmaEncode(dest, destLen, src, srcLen, &props, outProps, outPropsSize, 0, + NULL, &g_Alloc, &g_Alloc); +} + + +MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t *srcLen, + const unsigned char *props, size_t propsSize) +{ + ELzmaStatus status; + return LzmaDecode(dest, destLen, src, srcLen, props, (unsigned)propsSize, LZMA_FINISH_ANY, &status, &g_Alloc); +}
@@ -0,0 +1,131 @@
+/* LzmaLib.h -- LZMA library interface +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_LIB_H +#define __LZMA_LIB_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define MY_STDAPI int MY_STD_CALL + +#define LZMA_PROPS_SIZE 5 + +/* +RAM requirements for LZMA: + for compression: (dictSize * 11.5 + 6 MB) + state_size + for decompression: dictSize + state_size + state_size = (4 + (1.5 << (lc + lp))) KB + by default (lc=3, lp=0), state_size = 16 KB. + +LZMA properties (5 bytes) format + Offset Size Description + 0 1 lc, lp and pb in encoded form. + 1 4 dictSize (little endian). +*/ + +/* +LzmaCompress +------------ + +outPropsSize - + In: the pointer to the size of outProps buffer; *outPropsSize = LZMA_PROPS_SIZE = 5. + Out: the pointer to the size of written properties in outProps buffer; *outPropsSize = LZMA_PROPS_SIZE = 5. + + LZMA Encoder will use defult values for any parameter, if it is + -1 for any from: level, loc, lp, pb, fb, numThreads + 0 for dictSize + +level - compression level: 0 <= level <= 9; + + level dictSize algo fb + 0: 16 KB 0 32 + 1: 64 KB 0 32 + 2: 256 KB 0 32 + 3: 1 MB 0 32 + 4: 4 MB 0 32 + 5: 16 MB 1 32 + 6: 32 MB 1 32 + 7+: 64 MB 1 64 + + The default value for "level" is 5. + + algo = 0 means fast method + algo = 1 means normal method + +dictSize - The dictionary size in bytes. The maximum value is + 128 MB = (1 << 27) bytes for 32-bit version + 1 GB = (1 << 30) bytes for 64-bit version + The default value is 16 MB = (1 << 24) bytes. + It's recommended to use the dictionary that is larger than 4 KB and + that can be calculated as (1 << N) or (3 << N) sizes. + +lc - The number of literal context bits (high bits of previous literal). + It can be in the range from 0 to 8. The default value is 3. + Sometimes lc=4 gives the gain for big files. + +lp - The number of literal pos bits (low bits of current position for literals). + It can be in the range from 0 to 4. The default value is 0. + The lp switch is intended for periodical data when the period is equal to 2^lp. + For example, for 32-bit (4 bytes) periodical data you can use lp=2. Often it's + better to set lc=0, if you change lp switch. + +pb - The number of pos bits (low bits of current position). + It can be in the range from 0 to 4. The default value is 2. + The pb switch is intended for periodical data when the period is equal 2^pb. + +fb - Word size (the number of fast bytes). + It can be in the range from 5 to 273. The default value is 32. + Usually, a big number gives a little bit better compression ratio and + slower compression process. + +numThreads - The number of thereads. 1 or 2. The default value is 2. + Fast mode (algo = 0) can use only 1 thread. + +Out: + destLen - processed output size +Returns: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, + unsigned char *outProps, size_t *outPropsSize, /* *outPropsSize must be = 5 */ + int level, /* 0 <= level <= 9, default = 5 */ + unsigned dictSize, /* default = (1 << 24) */ + int lc, /* 0 <= lc <= 8, default = 3 */ + int lp, /* 0 <= lp <= 4, default = 0 */ + int pb, /* 0 <= pb <= 4, default = 2 */ + int fb, /* 5 <= fb <= 273, default = 32 */ + int numThreads /* 1 or 2, default = 2 */ + ); + +/* +LzmaUncompress +-------------- +In: + dest - output data + destLen - output data size + src - input data + srcLen - input data size +Out: + destLen - processed output size + srcLen - processed input size +Returns: + SZ_OK - OK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation arror + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - it needs more bytes in input buffer (src) +*/ + +MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, SizeT *srcLen, + const unsigned char *props, size_t propsSize); + +EXTERN_C_END + +#endif
@@ -0,0 +1,329 @@
+/* MtCoder.c -- Multi-thread Coder +2010-09-24 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <stdio.h> + +#include "MtCoder.h" + +void LoopThread_Construct(CLoopThread *p) +{ + Thread_Construct(&p->thread); + Event_Construct(&p->startEvent); + Event_Construct(&p->finishedEvent); +} + +void LoopThread_Close(CLoopThread *p) +{ + Thread_Close(&p->thread); + Event_Close(&p->startEvent); + Event_Close(&p->finishedEvent); +} + +static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE LoopThreadFunc(void *pp) +{ + CLoopThread *p = (CLoopThread *)pp; + for (;;) + { + if (Event_Wait(&p->startEvent) != 0) + return SZ_ERROR_THREAD; + if (p->stop) + return 0; + p->res = p->func(p->param); + if (Event_Set(&p->finishedEvent) != 0) + return SZ_ERROR_THREAD; + } +} + +WRes LoopThread_Create(CLoopThread *p) +{ + p->stop = 0; + RINOK(AutoResetEvent_CreateNotSignaled(&p->startEvent)); + RINOK(AutoResetEvent_CreateNotSignaled(&p->finishedEvent)); + return Thread_Create(&p->thread, LoopThreadFunc, p); +} + +WRes LoopThread_StopAndWait(CLoopThread *p) +{ + p->stop = 1; + if (Event_Set(&p->startEvent) != 0) + return SZ_ERROR_THREAD; + return Thread_Wait(&p->thread); +} + +WRes LoopThread_StartSubThread(CLoopThread *p) { return Event_Set(&p->startEvent); } +WRes LoopThread_WaitSubThread(CLoopThread *p) { return Event_Wait(&p->finishedEvent); } + +static SRes Progress(ICompressProgress *p, UInt64 inSize, UInt64 outSize) +{ + return (p && p->Progress(p, inSize, outSize) != SZ_OK) ? SZ_ERROR_PROGRESS : SZ_OK; +} + +static void MtProgress_Init(CMtProgress *p, ICompressProgress *progress) +{ + unsigned i; + for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + p->inSizes[i] = p->outSizes[i] = 0; + p->totalInSize = p->totalOutSize = 0; + p->progress = progress; + p->res = SZ_OK; +} + +static void MtProgress_Reinit(CMtProgress *p, unsigned index) +{ + p->inSizes[index] = 0; + p->outSizes[index] = 0; +} + +#define UPDATE_PROGRESS(size, prev, total) \ + if (size != (UInt64)(Int64)-1) { total += size - prev; prev = size; } + +SRes MtProgress_Set(CMtProgress *p, unsigned index, UInt64 inSize, UInt64 outSize) +{ + SRes res; + CriticalSection_Enter(&p->cs); + UPDATE_PROGRESS(inSize, p->inSizes[index], p->totalInSize) + UPDATE_PROGRESS(outSize, p->outSizes[index], p->totalOutSize) + if (p->res == SZ_OK) + p->res = Progress(p->progress, p->totalInSize, p->totalOutSize); + res = p->res; + CriticalSection_Leave(&p->cs); + return res; +} + +static void MtProgress_SetError(CMtProgress *p, SRes res) +{ + CriticalSection_Enter(&p->cs); + if (p->res == SZ_OK) + p->res = res; + CriticalSection_Leave(&p->cs); +} + +static void MtCoder_SetError(CMtCoder* p, SRes res) +{ + CriticalSection_Enter(&p->cs); + if (p->res == SZ_OK) + p->res = res; + CriticalSection_Leave(&p->cs); +} + +/* ---------- MtThread ---------- */ + +void CMtThread_Construct(CMtThread *p, CMtCoder *mtCoder) +{ + p->mtCoder = mtCoder; + p->outBuf = 0; + p->inBuf = 0; + Event_Construct(&p->canRead); + Event_Construct(&p->canWrite); + LoopThread_Construct(&p->thread); +} + +#define RINOK_THREAD(x) { if((x) != 0) return SZ_ERROR_THREAD; } + +static void CMtThread_CloseEvents(CMtThread *p) +{ + Event_Close(&p->canRead); + Event_Close(&p->canWrite); +} + +static void CMtThread_Destruct(CMtThread *p) +{ + CMtThread_CloseEvents(p); + + if (Thread_WasCreated(&p->thread.thread)) + { + LoopThread_StopAndWait(&p->thread); + LoopThread_Close(&p->thread); + } + + if (p->mtCoder->alloc) + IAlloc_Free(p->mtCoder->alloc, p->outBuf); + p->outBuf = 0; + + if (p->mtCoder->alloc) + IAlloc_Free(p->mtCoder->alloc, p->inBuf); + p->inBuf = 0; +} + +#define MY_BUF_ALLOC(buf, size, newSize) \ + if (buf == 0 || size != newSize) \ + { IAlloc_Free(p->mtCoder->alloc, buf); \ + size = newSize; buf = (Byte *)IAlloc_Alloc(p->mtCoder->alloc, size); \ + if (buf == 0) return SZ_ERROR_MEM; } + +static SRes CMtThread_Prepare(CMtThread *p) +{ + MY_BUF_ALLOC(p->inBuf, p->inBufSize, p->mtCoder->blockSize) + MY_BUF_ALLOC(p->outBuf, p->outBufSize, p->mtCoder->destBlockSize) + + p->stopReading = False; + p->stopWriting = False; + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canRead)); + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canWrite)); + + return SZ_OK; +} + +static SRes FullRead(ISeqInStream *stream, Byte *data, size_t *processedSize) +{ + size_t size = *processedSize; + *processedSize = 0; + while (size != 0) + { + size_t curSize = size; + SRes res = stream->Read(stream, data, &curSize); + *processedSize += curSize; + data += curSize; + size -= curSize; + RINOK(res); + if (curSize == 0) + return SZ_OK; + } + return SZ_OK; +} + +#define GET_NEXT_THREAD(p) &p->mtCoder->threads[p->index == p->mtCoder->numThreads - 1 ? 0 : p->index + 1] + +static SRes MtThread_Process(CMtThread *p, Bool *stop) +{ + CMtThread *next; + *stop = True; + if (Event_Wait(&p->canRead) != 0) + return SZ_ERROR_THREAD; + + next = GET_NEXT_THREAD(p); + + if (p->stopReading) + { + next->stopReading = True; + return Event_Set(&next->canRead) == 0 ? SZ_OK : SZ_ERROR_THREAD; + } + + { + size_t size = p->mtCoder->blockSize; + size_t destSize = p->outBufSize; + + RINOK(FullRead(p->mtCoder->inStream, p->inBuf, &size)); + next->stopReading = *stop = (size != p->mtCoder->blockSize); + if (Event_Set(&next->canRead) != 0) + return SZ_ERROR_THREAD; + + RINOK(p->mtCoder->mtCallback->Code(p->mtCoder->mtCallback, p->index, + p->outBuf, &destSize, p->inBuf, size, *stop)); + + MtProgress_Reinit(&p->mtCoder->mtProgress, p->index); + + if (Event_Wait(&p->canWrite) != 0) + return SZ_ERROR_THREAD; + if (p->stopWriting) + return SZ_ERROR_FAIL; + if (p->mtCoder->outStream->Write(p->mtCoder->outStream, p->outBuf, destSize) != destSize) + return SZ_ERROR_WRITE; + return Event_Set(&next->canWrite) == 0 ? SZ_OK : SZ_ERROR_THREAD; + } +} + +static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE ThreadFunc(void *pp) +{ + CMtThread *p = (CMtThread *)pp; + for (;;) + { + Bool stop; + CMtThread *next = GET_NEXT_THREAD(p); + SRes res = MtThread_Process(p, &stop); + if (res != SZ_OK) + { + MtCoder_SetError(p->mtCoder, res); + MtProgress_SetError(&p->mtCoder->mtProgress, res); + next->stopReading = True; + next->stopWriting = True; + Event_Set(&next->canRead); + Event_Set(&next->canWrite); + return res; + } + if (stop) + return 0; + } +} + +void MtCoder_Construct(CMtCoder* p) +{ + unsigned i; + p->alloc = 0; + for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + { + CMtThread *t = &p->threads[i]; + t->index = i; + CMtThread_Construct(t, p); + } + CriticalSection_Init(&p->cs); + CriticalSection_Init(&p->mtProgress.cs); +} + +void MtCoder_Destruct(CMtCoder* p) +{ + unsigned i; + for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + CMtThread_Destruct(&p->threads[i]); + CriticalSection_Delete(&p->cs); + CriticalSection_Delete(&p->mtProgress.cs); +} + +SRes MtCoder_Code(CMtCoder *p) +{ + unsigned i, numThreads = p->numThreads; + SRes res = SZ_OK; + p->res = SZ_OK; + + MtProgress_Init(&p->mtProgress, p->progress); + + for (i = 0; i < numThreads; i++) + { + RINOK(CMtThread_Prepare(&p->threads[i])); + } + + for (i = 0; i < numThreads; i++) + { + CMtThread *t = &p->threads[i]; + CLoopThread *lt = &t->thread; + + if (!Thread_WasCreated(<->thread)) + { + lt->func = ThreadFunc; + lt->param = t; + + if (LoopThread_Create(lt) != SZ_OK) + { + res = SZ_ERROR_THREAD; + break; + } + } + } + + if (res == SZ_OK) + { + unsigned j; + for (i = 0; i < numThreads; i++) + { + CMtThread *t = &p->threads[i]; + if (LoopThread_StartSubThread(&t->thread) != SZ_OK) + { + res = SZ_ERROR_THREAD; + p->threads[0].stopReading = True; + break; + } + } + + Event_Set(&p->threads[0].canWrite); + Event_Set(&p->threads[0].canRead); + + for (j = 0; j < i; j++) + LoopThread_WaitSubThread(&p->threads[j].thread); + } + + for (i = 0; i < numThreads; i++) + CMtThread_CloseEvents(&p->threads[i]); + return (res == SZ_OK) ? p->res : res; +}
@@ -0,0 +1,98 @@
+/* MtCoder.h -- Multi-thread Coder +2009-11-19 : Igor Pavlov : Public domain */ + +#ifndef __MT_CODER_H +#define __MT_CODER_H + +#include "Threads.h" + +EXTERN_C_BEGIN + +typedef struct +{ + CThread thread; + CAutoResetEvent startEvent; + CAutoResetEvent finishedEvent; + int stop; + + THREAD_FUNC_TYPE func; + LPVOID param; + THREAD_FUNC_RET_TYPE res; +} CLoopThread; + +void LoopThread_Construct(CLoopThread *p); +void LoopThread_Close(CLoopThread *p); +WRes LoopThread_Create(CLoopThread *p); +WRes LoopThread_StopAndWait(CLoopThread *p); +WRes LoopThread_StartSubThread(CLoopThread *p); +WRes LoopThread_WaitSubThread(CLoopThread *p); + +#ifndef _7ZIP_ST +#define NUM_MT_CODER_THREADS_MAX 32 +#else +#define NUM_MT_CODER_THREADS_MAX 1 +#endif + +typedef struct +{ + UInt64 totalInSize; + UInt64 totalOutSize; + ICompressProgress *progress; + SRes res; + CCriticalSection cs; + UInt64 inSizes[NUM_MT_CODER_THREADS_MAX]; + UInt64 outSizes[NUM_MT_CODER_THREADS_MAX]; +} CMtProgress; + +SRes MtProgress_Set(CMtProgress *p, unsigned index, UInt64 inSize, UInt64 outSize); + +struct _CMtCoder; + +typedef struct +{ + struct _CMtCoder *mtCoder; + Byte *outBuf; + size_t outBufSize; + Byte *inBuf; + size_t inBufSize; + unsigned index; + CLoopThread thread; + + Bool stopReading; + Bool stopWriting; + CAutoResetEvent canRead; + CAutoResetEvent canWrite; +} CMtThread; + +typedef struct +{ + SRes (*Code)(void *p, unsigned index, Byte *dest, size_t *destSize, + const Byte *src, size_t srcSize, int finished); +} IMtCoderCallback; + +typedef struct _CMtCoder +{ + size_t blockSize; + size_t destBlockSize; + unsigned numThreads; + + ISeqInStream *inStream; + ISeqOutStream *outStream; + ICompressProgress *progress; + ISzAlloc *alloc; + + IMtCoderCallback *mtCallback; + CCriticalSection cs; + SRes res; + + CMtProgress mtProgress; + CMtThread threads[NUM_MT_CODER_THREADS_MAX]; +} CMtCoder; + +void MtCoder_Construct(CMtCoder* p); +void MtCoder_Destruct(CMtCoder* p); +SRes MtCoder_Code(CMtCoder *p); + +EXTERN_C_END + +#endif
@@ -0,0 +1,85 @@
+/* Ppmd.h -- PPMD codec common code +2013-01-18 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +#ifndef __PPMD_H +#define __PPMD_H + +#include "CpuArch.h" + +EXTERN_C_BEGIN + +#ifdef MY_CPU_32BIT + #define PPMD_32BIT +#endif + +#define PPMD_INT_BITS 7 +#define PPMD_PERIOD_BITS 7 +#define PPMD_BIN_SCALE (1 << (PPMD_INT_BITS + PPMD_PERIOD_BITS)) + +#define PPMD_GET_MEAN_SPEC(summ, shift, round) (((summ) + (1 << ((shift) - (round)))) >> (shift)) +#define PPMD_GET_MEAN(summ) PPMD_GET_MEAN_SPEC((summ), PPMD_PERIOD_BITS, 2) +#define PPMD_UPDATE_PROB_0(prob) ((prob) + (1 << PPMD_INT_BITS) - PPMD_GET_MEAN(prob)) +#define PPMD_UPDATE_PROB_1(prob) ((prob) - PPMD_GET_MEAN(prob)) + +#define PPMD_N1 4 +#define PPMD_N2 4 +#define PPMD_N3 4 +#define PPMD_N4 ((128 + 3 - 1 * PPMD_N1 - 2 * PPMD_N2 - 3 * PPMD_N3) / 4) +#define PPMD_NUM_INDEXES (PPMD_N1 + PPMD_N2 + PPMD_N3 + PPMD_N4) + +#pragma pack(push, 1) +/* Most compilers works OK here even without #pragma pack(push, 1), but some GCC compilers need it. */ + +/* SEE-contexts for PPM-contexts with masked symbols */ +typedef struct +{ + UInt16 Summ; /* Freq */ + Byte Shift; /* Speed of Freq change; low Shift is for fast change */ + Byte Count; /* Count to next change of Shift */ +} CPpmd_See; + +#define Ppmd_See_Update(p) if ((p)->Shift < PPMD_PERIOD_BITS && --(p)->Count == 0) \ + { (p)->Summ <<= 1; (p)->Count = (Byte)(3 << (p)->Shift++); } + +typedef struct +{ + Byte Symbol; + Byte Freq; + UInt16 SuccessorLow; + UInt16 SuccessorHigh; +} CPpmd_State; + +#pragma pack(pop) + +typedef + #ifdef PPMD_32BIT + CPpmd_State * + #else + UInt32 + #endif + CPpmd_State_Ref; + +typedef + #ifdef PPMD_32BIT + void * + #else + UInt32 + #endif + CPpmd_Void_Ref; + +typedef + #ifdef PPMD_32BIT + Byte * + #else + UInt32 + #endif + CPpmd_Byte_Ref; + +#define PPMD_SetAllBitsIn256Bytes(p) \ + { unsigned i; for (i = 0; i < 256 / sizeof(p[0]); i += 8) { \ + p[i+7] = p[i+6] = p[i+5] = p[i+4] = p[i+3] = p[i+2] = p[i+1] = p[i+0] = ~(size_t)0; }} + +EXTERN_C_END + +#endif
@@ -0,0 +1,710 @@
+/* Ppmd7.c -- PPMdH codec +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +#include "Precomp.h" + +#include <memory.h> + +#include "Ppmd7.h" + +const Byte PPMD7_kExpEscape[16] = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; +static const UInt16 kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051}; + +#define MAX_FREQ 124 +#define UNIT_SIZE 12 + +#define U2B(nu) ((UInt32)(nu) * UNIT_SIZE) +#define U2I(nu) (p->Units2Indx[(nu) - 1]) +#define I2U(indx) (p->Indx2Units[indx]) + +#ifdef PPMD_32BIT + #define REF(ptr) (ptr) +#else + #define REF(ptr) ((UInt32)((Byte *)(ptr) - (p)->Base)) +#endif + +#define STATS_REF(ptr) ((CPpmd_State_Ref)REF(ptr)) + +#define CTX(ref) ((CPpmd7_Context *)Ppmd7_GetContext(p, ref)) +#define STATS(ctx) Ppmd7_GetStats(p, ctx) +#define ONE_STATE(ctx) Ppmd7Context_OneState(ctx) +#define SUFFIX(ctx) CTX((ctx)->Suffix) + +typedef CPpmd7_Context * CTX_PTR; + +struct CPpmd7_Node_; + +typedef + #ifdef PPMD_32BIT + struct CPpmd7_Node_ * + #else + UInt32 + #endif + CPpmd7_Node_Ref; + +typedef struct CPpmd7_Node_ +{ + UInt16 Stamp; /* must be at offset 0 as CPpmd7_Context::NumStats. Stamp=0 means free */ + UInt16 NU; + CPpmd7_Node_Ref Next; /* must be at offset >= 4 */ + CPpmd7_Node_Ref Prev; +} CPpmd7_Node; + +#ifdef PPMD_32BIT + #define NODE(ptr) (ptr) +#else + #define NODE(offs) ((CPpmd7_Node *)(p->Base + (offs))) +#endif + +void Ppmd7_Construct(CPpmd7 *p) +{ + unsigned i, k, m; + + p->Base = 0; + + for (i = 0, k = 0; i < PPMD_NUM_INDEXES; i++) + { + unsigned step = (i >= 12 ? 4 : (i >> 2) + 1); + do { p->Units2Indx[k++] = (Byte)i; } while(--step); + p->Indx2Units[i] = (Byte)k; + } + + p->NS2BSIndx[0] = (0 << 1); + p->NS2BSIndx[1] = (1 << 1); + memset(p->NS2BSIndx + 2, (2 << 1), 9); + memset(p->NS2BSIndx + 11, (3 << 1), 256 - 11); + + for (i = 0; i < 3; i++) + p->NS2Indx[i] = (Byte)i; + for (m = i, k = 1; i < 256; i++) + { + p->NS2Indx[i] = (Byte)m; + if (--k == 0) + k = (++m) - 2; + } + + memset(p->HB2Flag, 0, 0x40); + memset(p->HB2Flag + 0x40, 8, 0x100 - 0x40); +} + +void Ppmd7_Free(CPpmd7 *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->Base); + p->Size = 0; + p->Base = 0; +} + +Bool Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAlloc *alloc) +{ + if (p->Base == 0 || p->Size != size) + { + Ppmd7_Free(p, alloc); + p->AlignOffset = + #ifdef PPMD_32BIT + (4 - size) & 3; + #else + 4 - (size & 3); + #endif + if ((p->Base = (Byte *)alloc->Alloc(alloc, p->AlignOffset + size + #ifndef PPMD_32BIT + + UNIT_SIZE + #endif + )) == 0) + return False; + p->Size = size; + } + return True; +} + +static void InsertNode(CPpmd7 *p, void *node, unsigned indx) +{ + *((CPpmd_Void_Ref *)node) = p->FreeList[indx]; + p->FreeList[indx] = REF(node); +} + +static void *RemoveNode(CPpmd7 *p, unsigned indx) +{ + CPpmd_Void_Ref *node = (CPpmd_Void_Ref *)Ppmd7_GetPtr(p, p->FreeList[indx]); + p->FreeList[indx] = *node; + return node; +} + +static void SplitBlock(CPpmd7 *p, void *ptr, unsigned oldIndx, unsigned newIndx) +{ + unsigned i, nu = I2U(oldIndx) - I2U(newIndx); + ptr = (Byte *)ptr + U2B(I2U(newIndx)); + if (I2U(i = U2I(nu)) != nu) + { + unsigned k = I2U(--i); + InsertNode(p, ((Byte *)ptr) + U2B(k), nu - k - 1); + } + InsertNode(p, ptr, i); +} + +static void GlueFreeBlocks(CPpmd7 *p) +{ + #ifdef PPMD_32BIT + CPpmd7_Node headItem; + CPpmd7_Node_Ref head = &headItem; + #else + CPpmd7_Node_Ref head = p->AlignOffset + p->Size; + #endif + + CPpmd7_Node_Ref n = head; + unsigned i; + + p->GlueCount = 255; + + /* create doubly-linked list of free blocks */ + for (i = 0; i < PPMD_NUM_INDEXES; i++) + { + UInt16 nu = I2U(i); + CPpmd7_Node_Ref next = (CPpmd7_Node_Ref)p->FreeList[i]; + p->FreeList[i] = 0; + while (next != 0) + { + CPpmd7_Node *node = NODE(next); + node->Next = n; + n = NODE(n)->Prev = next; + next = *(const CPpmd7_Node_Ref *)node; + node->Stamp = 0; + node->NU = (UInt16)nu; + } + } + NODE(head)->Stamp = 1; + NODE(head)->Next = n; + NODE(n)->Prev = head; + if (p->LoUnit != p->HiUnit) + ((CPpmd7_Node *)p->LoUnit)->Stamp = 1; + + /* Glue free blocks */ + while (n != head) + { + CPpmd7_Node *node = NODE(n); + UInt32 nu = (UInt32)node->NU; + for (;;) + { + CPpmd7_Node *node2 = NODE(n) + nu; + nu += node2->NU; + if (node2->Stamp != 0 || nu >= 0x10000) + break; + NODE(node2->Prev)->Next = node2->Next; + NODE(node2->Next)->Prev = node2->Prev; + node->NU = (UInt16)nu; + } + n = node->Next; + } + + /* Fill lists of free blocks */ + for (n = NODE(head)->Next; n != head;) + { + CPpmd7_Node *node = NODE(n); + unsigned nu; + CPpmd7_Node_Ref next = node->Next; + for (nu = node->NU; nu > 128; nu -= 128, node += 128) + InsertNode(p, node, PPMD_NUM_INDEXES - 1); + if (I2U(i = U2I(nu)) != nu) + { + unsigned k = I2U(--i); + InsertNode(p, node + k, nu - k - 1); + } + InsertNode(p, node, i); + n = next; + } +} + +static void *AllocUnitsRare(CPpmd7 *p, unsigned indx) +{ + unsigned i; + void *retVal; + if (p->GlueCount == 0) + { + GlueFreeBlocks(p); + if (p->FreeList[indx] != 0) + return RemoveNode(p, indx); + } + i = indx; + do + { + if (++i == PPMD_NUM_INDEXES) + { + UInt32 numBytes = U2B(I2U(indx)); + p->GlueCount--; + return ((UInt32)(p->UnitsStart - p->Text) > numBytes) ? (p->UnitsStart -= numBytes) : (NULL); + } + } + while (p->FreeList[i] == 0); + retVal = RemoveNode(p, i); + SplitBlock(p, retVal, i, indx); + return retVal; +} + +static void *AllocUnits(CPpmd7 *p, unsigned indx) +{ + UInt32 numBytes; + if (p->FreeList[indx] != 0) + return RemoveNode(p, indx); + numBytes = U2B(I2U(indx)); + if (numBytes <= (UInt32)(p->HiUnit - p->LoUnit)) + { + void *retVal = p->LoUnit; + p->LoUnit += numBytes; + return retVal; + } + return AllocUnitsRare(p, indx); +} + +#define MyMem12Cpy(dest, src, num) \ + { UInt32 *d = (UInt32 *)dest; const UInt32 *s = (const UInt32 *)src; UInt32 n = num; \ + do { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; s += 3; d += 3; } while(--n); } + +static void *ShrinkUnits(CPpmd7 *p, void *oldPtr, unsigned oldNU, unsigned newNU) +{ + unsigned i0 = U2I(oldNU); + unsigned i1 = U2I(newNU); + if (i0 == i1) + return oldPtr; + if (p->FreeList[i1] != 0) + { + void *ptr = RemoveNode(p, i1); + MyMem12Cpy(ptr, oldPtr, newNU); + InsertNode(p, oldPtr, i0); + return ptr; + } + SplitBlock(p, oldPtr, i0, i1); + return oldPtr; +} + +#define SUCCESSOR(p) ((CPpmd_Void_Ref)((p)->SuccessorLow | ((UInt32)(p)->SuccessorHigh << 16))) + +static void SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v) +{ + (p)->SuccessorLow = (UInt16)((UInt32)(v) & 0xFFFF); + (p)->SuccessorHigh = (UInt16)(((UInt32)(v) >> 16) & 0xFFFF); +} + +static void RestartModel(CPpmd7 *p) +{ + unsigned i, k, m; + + memset(p->FreeList, 0, sizeof(p->FreeList)); + p->Text = p->Base + p->AlignOffset; + p->HiUnit = p->Text + p->Size; + p->LoUnit = p->UnitsStart = p->HiUnit - p->Size / 8 / UNIT_SIZE * 7 * UNIT_SIZE; + p->GlueCount = 0; + + p->OrderFall = p->MaxOrder; + p->RunLength = p->InitRL = -(Int32)((p->MaxOrder < 12) ? p->MaxOrder : 12) - 1; + p->PrevSuccess = 0; + + p->MinContext = p->MaxContext = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); /* AllocContext(p); */ + p->MinContext->Suffix = 0; + p->MinContext->NumStats = 256; + p->MinContext->SummFreq = 256 + 1; + p->FoundState = (CPpmd_State *)p->LoUnit; /* AllocUnits(p, PPMD_NUM_INDEXES - 1); */ + p->LoUnit += U2B(256 / 2); + p->MinContext->Stats = REF(p->FoundState); + for (i = 0; i < 256; i++) + { + CPpmd_State *s = &p->FoundState[i]; + s->Symbol = (Byte)i; + s->Freq = 1; + SetSuccessor(s, 0); + } + + for (i = 0; i < 128; i++) + for (k = 0; k < 8; k++) + { + UInt16 *dest = p->BinSumm[i] + k; + UInt16 val = (UInt16)(PPMD_BIN_SCALE - kInitBinEsc[k] / (i + 2)); + for (m = 0; m < 64; m += 8) + dest[m] = val; + } + + for (i = 0; i < 25; i++) + for (k = 0; k < 16; k++) + { + CPpmd_See *s = &p->See[i][k]; + s->Summ = (UInt16)((5 * i + 10) << (s->Shift = PPMD_PERIOD_BITS - 4)); + s->Count = 4; + } +} + +void Ppmd7_Init(CPpmd7 *p, unsigned maxOrder) +{ + p->MaxOrder = maxOrder; + RestartModel(p); + p->DummySee.Shift = PPMD_PERIOD_BITS; + p->DummySee.Summ = 0; /* unused */ + p->DummySee.Count = 64; /* unused */ +} + +static CTX_PTR CreateSuccessors(CPpmd7 *p, Bool skip) +{ + CPpmd_State upState; + CTX_PTR c = p->MinContext; + CPpmd_Byte_Ref upBranch = (CPpmd_Byte_Ref)SUCCESSOR(p->FoundState); + CPpmd_State *ps[PPMD7_MAX_ORDER]; + unsigned numPs = 0; + + if (!skip) + ps[numPs++] = p->FoundState; + + while (c->Suffix) + { + CPpmd_Void_Ref successor; + CPpmd_State *s; + c = SUFFIX(c); + if (c->NumStats != 1) + { + for (s = STATS(c); s->Symbol != p->FoundState->Symbol; s++); + } + else + s = ONE_STATE(c); + successor = SUCCESSOR(s); + if (successor != upBranch) + { + c = CTX(successor); + if (numPs == 0) + return c; + break; + } + ps[numPs++] = s; + } + + upState.Symbol = *(const Byte *)Ppmd7_GetPtr(p, upBranch); + SetSuccessor(&upState, upBranch + 1); + + if (c->NumStats == 1) + upState.Freq = ONE_STATE(c)->Freq; + else + { + UInt32 cf, s0; + CPpmd_State *s; + for (s = STATS(c); s->Symbol != upState.Symbol; s++); + cf = s->Freq - 1; + s0 = c->SummFreq - c->NumStats - cf; + upState.Freq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : ((2 * cf + 3 * s0 - 1) / (2 * s0)))); + } + + do + { + /* Create Child */ + CTX_PTR c1; /* = AllocContext(p); */ + if (p->HiUnit != p->LoUnit) + c1 = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); + else if (p->FreeList[0] != 0) + c1 = (CTX_PTR)RemoveNode(p, 0); + else + { + c1 = (CTX_PTR)AllocUnitsRare(p, 0); + if (!c1) + return NULL; + } + c1->NumStats = 1; + *ONE_STATE(c1) = upState; + c1->Suffix = REF(c); + SetSuccessor(ps[--numPs], REF(c1)); + c = c1; + } + while (numPs != 0); + + return c; +} + +static void SwapStates(CPpmd_State *t1, CPpmd_State *t2) +{ + CPpmd_State tmp = *t1; + *t1 = *t2; + *t2 = tmp; +} + +static void UpdateModel(CPpmd7 *p) +{ + CPpmd_Void_Ref successor, fSuccessor = SUCCESSOR(p->FoundState); + CTX_PTR c; + unsigned s0, ns; + + if (p->FoundState->Freq < MAX_FREQ / 4 && p->MinContext->Suffix != 0) + { + c = SUFFIX(p->MinContext); + + if (c->NumStats == 1) + { + CPpmd_State *s = ONE_STATE(c); + if (s->Freq < 32) + s->Freq++; + } + else + { + CPpmd_State *s = STATS(c); + if (s->Symbol != p->FoundState->Symbol) + { + do { s++; } while (s->Symbol != p->FoundState->Symbol); + if (s[0].Freq >= s[-1].Freq) + { + SwapStates(&s[0], &s[-1]); + s--; + } + } + if (s->Freq < MAX_FREQ - 9) + { + s->Freq += 2; + c->SummFreq += 2; + } + } + } + + if (p->OrderFall == 0) + { + p->MinContext = p->MaxContext = CreateSuccessors(p, True); + if (p->MinContext == 0) + { + RestartModel(p); + return; + } + SetSuccessor(p->FoundState, REF(p->MinContext)); + return; + } + + *p->Text++ = p->FoundState->Symbol; + successor = REF(p->Text); + if (p->Text >= p->UnitsStart) + { + RestartModel(p); + return; + } + + if (fSuccessor) + { + if (fSuccessor <= successor) + { + CTX_PTR cs = CreateSuccessors(p, False); + if (cs == NULL) + { + RestartModel(p); + return; + } + fSuccessor = REF(cs); + } + if (--p->OrderFall == 0) + { + successor = fSuccessor; + p->Text -= (p->MaxContext != p->MinContext); + } + } + else + { + SetSuccessor(p->FoundState, successor); + fSuccessor = REF(p->MinContext); + } + + s0 = p->MinContext->SummFreq - (ns = p->MinContext->NumStats) - (p->FoundState->Freq - 1); + + for (c = p->MaxContext; c != p->MinContext; c = SUFFIX(c)) + { + unsigned ns1; + UInt32 cf, sf; + if ((ns1 = c->NumStats) != 1) + { + if ((ns1 & 1) == 0) + { + /* Expand for one UNIT */ + unsigned oldNU = ns1 >> 1; + unsigned i = U2I(oldNU); + if (i != U2I(oldNU + 1)) + { + void *ptr = AllocUnits(p, i + 1); + void *oldPtr; + if (!ptr) + { + RestartModel(p); + return; + } + oldPtr = STATS(c); + MyMem12Cpy(ptr, oldPtr, oldNU); + InsertNode(p, oldPtr, i); + c->Stats = STATS_REF(ptr); + } + } + c->SummFreq = (UInt16)(c->SummFreq + (2 * ns1 < ns) + 2 * ((4 * ns1 <= ns) & (c->SummFreq <= 8 * ns1))); + } + else + { + CPpmd_State *s = (CPpmd_State*)AllocUnits(p, 0); + if (!s) + { + RestartModel(p); + return; + } + *s = *ONE_STATE(c); + c->Stats = REF(s); + if (s->Freq < MAX_FREQ / 4 - 1) + s->Freq <<= 1; + else + s->Freq = MAX_FREQ - 4; + c->SummFreq = (UInt16)(s->Freq + p->InitEsc + (ns > 3)); + } + cf = 2 * (UInt32)p->FoundState->Freq * (c->SummFreq + 6); + sf = (UInt32)s0 + c->SummFreq; + if (cf < 6 * sf) + { + cf = 1 + (cf > sf) + (cf >= 4 * sf); + c->SummFreq += 3; + } + else + { + cf = 4 + (cf >= 9 * sf) + (cf >= 12 * sf) + (cf >= 15 * sf); + c->SummFreq = (UInt16)(c->SummFreq + cf); + } + { + CPpmd_State *s = STATS(c) + ns1; + SetSuccessor(s, successor); + s->Symbol = p->FoundState->Symbol; + s->Freq = (Byte)cf; + c->NumStats = (UInt16)(ns1 + 1); + } + } + p->MaxContext = p->MinContext = CTX(fSuccessor); +} + +static void Rescale(CPpmd7 *p) +{ + unsigned i, adder, sumFreq, escFreq; + CPpmd_State *stats = STATS(p->MinContext); + CPpmd_State *s = p->FoundState; + { + CPpmd_State tmp = *s; + for (; s != stats; s--) + s[0] = s[-1]; + *s = tmp; + } + escFreq = p->MinContext->SummFreq - s->Freq; + s->Freq += 4; + adder = (p->OrderFall != 0); + s->Freq = (Byte)((s->Freq + adder) >> 1); + sumFreq = s->Freq; + + i = p->MinContext->NumStats - 1; + do + { + escFreq -= (++s)->Freq; + s->Freq = (Byte)((s->Freq + adder) >> 1); + sumFreq += s->Freq; + if (s[0].Freq > s[-1].Freq) + { + CPpmd_State *s1 = s; + CPpmd_State tmp = *s1; + do + s1[0] = s1[-1]; + while (--s1 != stats && tmp.Freq > s1[-1].Freq); + *s1 = tmp; + } + } + while (--i); + + if (s->Freq == 0) + { + unsigned numStats = p->MinContext->NumStats; + unsigned n0, n1; + do { i++; } while ((--s)->Freq == 0); + escFreq += i; + p->MinContext->NumStats = (UInt16)(p->MinContext->NumStats - i); + if (p->MinContext->NumStats == 1) + { + CPpmd_State tmp = *stats; + do + { + tmp.Freq = (Byte)(tmp.Freq - (tmp.Freq >> 1)); + escFreq >>= 1; + } + while (escFreq > 1); + InsertNode(p, stats, U2I(((numStats + 1) >> 1))); + *(p->FoundState = ONE_STATE(p->MinContext)) = tmp; + return; + } + n0 = (numStats + 1) >> 1; + n1 = (p->MinContext->NumStats + 1) >> 1; + if (n0 != n1) + p->MinContext->Stats = STATS_REF(ShrinkUnits(p, stats, n0, n1)); + } + p->MinContext->SummFreq = (UInt16)(sumFreq + escFreq - (escFreq >> 1)); + p->FoundState = STATS(p->MinContext); +} + +CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, UInt32 *escFreq) +{ + CPpmd_See *see; + unsigned nonMasked = p->MinContext->NumStats - numMasked; + if (p->MinContext->NumStats != 256) + { + see = p->See[p->NS2Indx[nonMasked - 1]] + + (nonMasked < (unsigned)SUFFIX(p->MinContext)->NumStats - p->MinContext->NumStats) + + 2 * (p->MinContext->SummFreq < 11 * p->MinContext->NumStats) + + 4 * (numMasked > nonMasked) + + p->HiBitsFlag; + { + unsigned r = (see->Summ >> see->Shift); + see->Summ = (UInt16)(see->Summ - r); + *escFreq = r + (r == 0); + } + } + else + { + see = &p->DummySee; + *escFreq = 1; + } + return see; +} + +static void NextContext(CPpmd7 *p) +{ + CTX_PTR c = CTX(SUCCESSOR(p->FoundState)); + if (p->OrderFall == 0 && (Byte *)c > p->Text) + p->MinContext = p->MaxContext = c; + else + UpdateModel(p); +} + +void Ppmd7_Update1(CPpmd7 *p) +{ + CPpmd_State *s = p->FoundState; + s->Freq += 4; + p->MinContext->SummFreq += 4; + if (s[0].Freq > s[-1].Freq) + { + SwapStates(&s[0], &s[-1]); + p->FoundState = --s; + if (s->Freq > MAX_FREQ) + Rescale(p); + } + NextContext(p); +} + +void Ppmd7_Update1_0(CPpmd7 *p) +{ + p->PrevSuccess = (2 * p->FoundState->Freq > p->MinContext->SummFreq); + p->RunLength += p->PrevSuccess; + p->MinContext->SummFreq += 4; + if ((p->FoundState->Freq += 4) > MAX_FREQ) + Rescale(p); + NextContext(p); +} + +void Ppmd7_UpdateBin(CPpmd7 *p) +{ + p->FoundState->Freq = (Byte)(p->FoundState->Freq + (p->FoundState->Freq < 128 ? 1: 0)); + p->PrevSuccess = 1; + p->RunLength++; + NextContext(p); +} + +void Ppmd7_Update2(CPpmd7 *p) +{ + p->MinContext->SummFreq += 4; + if ((p->FoundState->Freq += 4) > MAX_FREQ) + Rescale(p); + p->RunLength = p->InitRL; + UpdateModel(p); +}
@@ -0,0 +1,140 @@
+/* Ppmd7.h -- PPMdH compression codec +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +/* This code supports virtual RangeDecoder and includes the implementation +of RangeCoder from 7z, instead of RangeCoder from original PPMd var.H. +If you need the compatibility with original PPMd var.H, you can use external RangeDecoder */ + +#ifndef __PPMD7_H +#define __PPMD7_H + +#include "Ppmd.h" + +EXTERN_C_BEGIN + +#define PPMD7_MIN_ORDER 2 +#define PPMD7_MAX_ORDER 64 + +#define PPMD7_MIN_MEM_SIZE (1 << 11) +#define PPMD7_MAX_MEM_SIZE (0xFFFFFFFF - 12 * 3) + +struct CPpmd7_Context_; + +typedef + #ifdef PPMD_32BIT + struct CPpmd7_Context_ * + #else + UInt32 + #endif + CPpmd7_Context_Ref; + +typedef struct CPpmd7_Context_ +{ + UInt16 NumStats; + UInt16 SummFreq; + CPpmd_State_Ref Stats; + CPpmd7_Context_Ref Suffix; +} CPpmd7_Context; + +#define Ppmd7Context_OneState(p) ((CPpmd_State *)&(p)->SummFreq) + +typedef struct +{ + CPpmd7_Context *MinContext, *MaxContext; + CPpmd_State *FoundState; + unsigned OrderFall, InitEsc, PrevSuccess, MaxOrder, HiBitsFlag; + Int32 RunLength, InitRL; /* must be 32-bit at least */ + + UInt32 Size; + UInt32 GlueCount; + Byte *Base, *LoUnit, *HiUnit, *Text, *UnitsStart; + UInt32 AlignOffset; + + Byte Indx2Units[PPMD_NUM_INDEXES]; + Byte Units2Indx[128]; + CPpmd_Void_Ref FreeList[PPMD_NUM_INDEXES]; + Byte NS2Indx[256], NS2BSIndx[256], HB2Flag[256]; + CPpmd_See DummySee, See[25][16]; + UInt16 BinSumm[128][64]; +} CPpmd7; + +void Ppmd7_Construct(CPpmd7 *p); +Bool Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAlloc *alloc); +void Ppmd7_Free(CPpmd7 *p, ISzAlloc *alloc); +void Ppmd7_Init(CPpmd7 *p, unsigned maxOrder); +#define Ppmd7_WasAllocated(p) ((p)->Base != NULL) + + +/* ---------- Internal Functions ---------- */ + +extern const Byte PPMD7_kExpEscape[16]; + +#ifdef PPMD_32BIT + #define Ppmd7_GetPtr(p, ptr) (ptr) + #define Ppmd7_GetContext(p, ptr) (ptr) + #define Ppmd7_GetStats(p, ctx) ((ctx)->Stats) +#else + #define Ppmd7_GetPtr(p, offs) ((void *)((p)->Base + (offs))) + #define Ppmd7_GetContext(p, offs) ((CPpmd7_Context *)Ppmd7_GetPtr((p), (offs))) + #define Ppmd7_GetStats(p, ctx) ((CPpmd_State *)Ppmd7_GetPtr((p), ((ctx)->Stats))) +#endif + +void Ppmd7_Update1(CPpmd7 *p); +void Ppmd7_Update1_0(CPpmd7 *p); +void Ppmd7_Update2(CPpmd7 *p); +void Ppmd7_UpdateBin(CPpmd7 *p); + +#define Ppmd7_GetBinSumm(p) \ + &p->BinSumm[Ppmd7Context_OneState(p->MinContext)->Freq - 1][p->PrevSuccess + \ + p->NS2BSIndx[Ppmd7_GetContext(p, p->MinContext->Suffix)->NumStats - 1] + \ + (p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]) + \ + 2 * p->HB2Flag[Ppmd7Context_OneState(p->MinContext)->Symbol] + \ + ((p->RunLength >> 26) & 0x20)] + +CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, UInt32 *scale); + + +/* ---------- Decode ---------- */ + +typedef struct +{ + UInt32 (*GetThreshold)(void *p, UInt32 total); + void (*Decode)(void *p, UInt32 start, UInt32 size); + UInt32 (*DecodeBit)(void *p, UInt32 size0); +} IPpmd7_RangeDec; + +typedef struct +{ + IPpmd7_RangeDec p; + UInt32 Range; + UInt32 Code; + IByteIn *Stream; +} CPpmd7z_RangeDec; + +void Ppmd7z_RangeDec_CreateVTable(CPpmd7z_RangeDec *p); +Bool Ppmd7z_RangeDec_Init(CPpmd7z_RangeDec *p); +#define Ppmd7z_RangeDec_IsFinishedOK(p) ((p)->Code == 0) + +int Ppmd7_DecodeSymbol(CPpmd7 *p, IPpmd7_RangeDec *rc); + + +/* ---------- Encode ---------- */ + +typedef struct +{ + UInt64 Low; + UInt32 Range; + Byte Cache; + UInt64 CacheSize; + IByteOut *Stream; +} CPpmd7z_RangeEnc; + +void Ppmd7z_RangeEnc_Init(CPpmd7z_RangeEnc *p); +void Ppmd7z_RangeEnc_FlushData(CPpmd7z_RangeEnc *p); + +void Ppmd7_EncodeSymbol(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol); + +EXTERN_C_END + +#endif
@@ -0,0 +1,189 @@
+/* Ppmd7Dec.c -- PPMdH Decoder +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +#include "Precomp.h" + +#include "Ppmd7.h" + +#define kTopValue (1 << 24) + +Bool Ppmd7z_RangeDec_Init(CPpmd7z_RangeDec *p) +{ + unsigned i; + p->Code = 0; + p->Range = 0xFFFFFFFF; + if (p->Stream->Read((void *)p->Stream) != 0) + return False; + for (i = 0; i < 4; i++) + p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); + return (p->Code < 0xFFFFFFFF); +} + +static UInt32 Range_GetThreshold(void *pp, UInt32 total) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + return (p->Code) / (p->Range /= total); +} + +static void Range_Normalize(CPpmd7z_RangeDec *p) +{ + if (p->Range < kTopValue) + { + p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); + p->Range <<= 8; + if (p->Range < kTopValue) + { + p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); + p->Range <<= 8; + } + } +} + +static void Range_Decode(void *pp, UInt32 start, UInt32 size) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + p->Code -= start * p->Range; + p->Range *= size; + Range_Normalize(p); +} + +static UInt32 Range_DecodeBit(void *pp, UInt32 size0) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + UInt32 newBound = (p->Range >> 14) * size0; + UInt32 symbol; + if (p->Code < newBound) + { + symbol = 0; + p->Range = newBound; + } + else + { + symbol = 1; + p->Code -= newBound; + p->Range -= newBound; + } + Range_Normalize(p); + return symbol; +} + +void Ppmd7z_RangeDec_CreateVTable(CPpmd7z_RangeDec *p) +{ + p->p.GetThreshold = Range_GetThreshold; + p->p.Decode = Range_Decode; + p->p.DecodeBit = Range_DecodeBit; +} + + +#define MASK(sym) ((signed char *)charMask)[sym] + +int Ppmd7_DecodeSymbol(CPpmd7 *p, IPpmd7_RangeDec *rc) +{ + size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 1) + { + CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); + unsigned i; + UInt32 count, hiCnt; + if ((count = rc->GetThreshold(rc, p->MinContext->SummFreq)) < (hiCnt = s->Freq)) + { + Byte symbol; + rc->Decode(rc, 0, s->Freq); + p->FoundState = s; + symbol = s->Symbol; + Ppmd7_Update1_0(p); + return symbol; + } + p->PrevSuccess = 0; + i = p->MinContext->NumStats - 1; + do + { + if ((hiCnt += (++s)->Freq) > count) + { + Byte symbol; + rc->Decode(rc, hiCnt - s->Freq, s->Freq); + p->FoundState = s; + symbol = s->Symbol; + Ppmd7_Update1(p); + return symbol; + } + } + while (--i); + if (count >= p->MinContext->SummFreq) + return -2; + p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]; + rc->Decode(rc, hiCnt, p->MinContext->SummFreq - hiCnt); + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(s->Symbol) = 0; + i = p->MinContext->NumStats - 1; + do { MASK((--s)->Symbol) = 0; } while (--i); + } + else + { + UInt16 *prob = Ppmd7_GetBinSumm(p); + if (rc->DecodeBit(rc, *prob) == 0) + { + Byte symbol; + *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); + symbol = (p->FoundState = Ppmd7Context_OneState(p->MinContext))->Symbol; + Ppmd7_UpdateBin(p); + return symbol; + } + *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); + p->InitEsc = PPMD7_kExpEscape[*prob >> 10]; + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(Ppmd7Context_OneState(p->MinContext)->Symbol) = 0; + p->PrevSuccess = 0; + } + for (;;) + { + CPpmd_State *ps[256], *s; + UInt32 freqSum, count, hiCnt; + CPpmd_See *see; + unsigned i, num, numMasked = p->MinContext->NumStats; + do + { + p->OrderFall++; + if (!p->MinContext->Suffix) + return -1; + p->MinContext = Ppmd7_GetContext(p, p->MinContext->Suffix); + } + while (p->MinContext->NumStats == numMasked); + hiCnt = 0; + s = Ppmd7_GetStats(p, p->MinContext); + i = 0; + num = p->MinContext->NumStats - numMasked; + do + { + int k = (int)(MASK(s->Symbol)); + hiCnt += (s->Freq & k); + ps[i] = s++; + i -= k; + } + while (i != num); + + see = Ppmd7_MakeEscFreq(p, numMasked, &freqSum); + freqSum += hiCnt; + count = rc->GetThreshold(rc, freqSum); + + if (count < hiCnt) + { + Byte symbol; + CPpmd_State **pps = ps; + for (hiCnt = 0; (hiCnt += (*pps)->Freq) <= count; pps++); + s = *pps; + rc->Decode(rc, hiCnt - s->Freq, s->Freq); + Ppmd_See_Update(see); + p->FoundState = s; + symbol = s->Symbol; + Ppmd7_Update2(p); + return symbol; + } + if (count >= freqSum) + return -2; + rc->Decode(rc, hiCnt, freqSum - hiCnt); + see->Summ = (UInt16)(see->Summ + freqSum); + do { MASK(ps[--i]->Symbol) = 0; } while (i != 0); + } +}
@@ -0,0 +1,187 @@
+/* Ppmd7Enc.c -- PPMdH Encoder +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +#include "Precomp.h" + +#include "Ppmd7.h" + +#define kTopValue (1 << 24) + +void Ppmd7z_RangeEnc_Init(CPpmd7z_RangeEnc *p) +{ + p->Low = 0; + p->Range = 0xFFFFFFFF; + p->Cache = 0; + p->CacheSize = 1; +} + +static void RangeEnc_ShiftLow(CPpmd7z_RangeEnc *p) +{ + if ((UInt32)p->Low < (UInt32)0xFF000000 || (unsigned)(p->Low >> 32) != 0) + { + Byte temp = p->Cache; + do + { + p->Stream->Write(p->Stream, (Byte)(temp + (Byte)(p->Low >> 32))); + temp = 0xFF; + } + while(--p->CacheSize != 0); + p->Cache = (Byte)((UInt32)p->Low >> 24); + } + p->CacheSize++; + p->Low = (UInt32)p->Low << 8; +} + +static void RangeEnc_Encode(CPpmd7z_RangeEnc *p, UInt32 start, UInt32 size, UInt32 total) +{ + p->Low += start * (p->Range /= total); + p->Range *= size; + while (p->Range < kTopValue) + { + p->Range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void RangeEnc_EncodeBit_0(CPpmd7z_RangeEnc *p, UInt32 size0) +{ + p->Range = (p->Range >> 14) * size0; + while (p->Range < kTopValue) + { + p->Range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void RangeEnc_EncodeBit_1(CPpmd7z_RangeEnc *p, UInt32 size0) +{ + UInt32 newBound = (p->Range >> 14) * size0; + p->Low += newBound; + p->Range -= newBound; + while (p->Range < kTopValue) + { + p->Range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +void Ppmd7z_RangeEnc_FlushData(CPpmd7z_RangeEnc *p) +{ + unsigned i; + for (i = 0; i < 5; i++) + RangeEnc_ShiftLow(p); +} + + +#define MASK(sym) ((signed char *)charMask)[sym] + +void Ppmd7_EncodeSymbol(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol) +{ + size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 1) + { + CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); + UInt32 sum; + unsigned i; + if (s->Symbol == symbol) + { + RangeEnc_Encode(rc, 0, s->Freq, p->MinContext->SummFreq); + p->FoundState = s; + Ppmd7_Update1_0(p); + return; + } + p->PrevSuccess = 0; + sum = s->Freq; + i = p->MinContext->NumStats - 1; + do + { + if ((++s)->Symbol == symbol) + { + RangeEnc_Encode(rc, sum, s->Freq, p->MinContext->SummFreq); + p->FoundState = s; + Ppmd7_Update1(p); + return; + } + sum += s->Freq; + } + while (--i); + + p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]; + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(s->Symbol) = 0; + i = p->MinContext->NumStats - 1; + do { MASK((--s)->Symbol) = 0; } while (--i); + RangeEnc_Encode(rc, sum, p->MinContext->SummFreq - sum, p->MinContext->SummFreq); + } + else + { + UInt16 *prob = Ppmd7_GetBinSumm(p); + CPpmd_State *s = Ppmd7Context_OneState(p->MinContext); + if (s->Symbol == symbol) + { + RangeEnc_EncodeBit_0(rc, *prob); + *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); + p->FoundState = s; + Ppmd7_UpdateBin(p); + return; + } + else + { + RangeEnc_EncodeBit_1(rc, *prob); + *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); + p->InitEsc = PPMD7_kExpEscape[*prob >> 10]; + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(s->Symbol) = 0; + p->PrevSuccess = 0; + } + } + for (;;) + { + UInt32 escFreq; + CPpmd_See *see; + CPpmd_State *s; + UInt32 sum; + unsigned i, numMasked = p->MinContext->NumStats; + do + { + p->OrderFall++; + if (!p->MinContext->Suffix) + return; /* EndMarker (symbol = -1) */ + p->MinContext = Ppmd7_GetContext(p, p->MinContext->Suffix); + } + while (p->MinContext->NumStats == numMasked); + + see = Ppmd7_MakeEscFreq(p, numMasked, &escFreq); + s = Ppmd7_GetStats(p, p->MinContext); + sum = 0; + i = p->MinContext->NumStats; + do + { + int cur = s->Symbol; + if (cur == symbol) + { + UInt32 low = sum; + CPpmd_State *s1 = s; + do + { + sum += (s->Freq & (int)(MASK(s->Symbol))); + s++; + } + while (--i); + RangeEnc_Encode(rc, low, s1->Freq, sum + escFreq); + Ppmd_See_Update(see); + p->FoundState = s1; + Ppmd7_Update2(p); + return; + } + sum += (s->Freq & (int)(MASK(cur))); + MASK(cur) = 0; + s++; + } + while (--i); + + RangeEnc_Encode(rc, sum, escFreq, sum + escFreq); + see->Summ = (UInt16)(see->Summ + sum + escFreq); + } +}
@@ -0,0 +1,10 @@
+/* Precomp.h -- StdAfx +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_PRECOMP_H +#define __7Z_PRECOMP_H + +#include "Compiler.h" +/* #include "7zTypes.h" */ + +#endif
@@ -0,0 +1,26 @@
+/* RotateDefs.h -- Rotate functions +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __ROTATE_DEFS_H +#define __ROTATE_DEFS_H + +#ifdef _MSC_VER + +#include <stdlib.h> + +// #if (_MSC_VER >= 1200) +#pragma intrinsic(_rotl) +#pragma intrinsic(_rotr) +// #endif + +#define rotlFixed(x, n) _rotl((x), (n)) +#define rotrFixed(x, n) _rotr((x), (n)) + +#else + +#define rotlFixed(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define rotrFixed(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) + +#endif + +#endif
@@ -0,0 +1,206 @@
+/* Crypto/Sha256.c -- SHA-256 Hash +2010-06-11 : Igor Pavlov : Public domain +This code is based on public domain code from Wei Dai's Crypto++ library. */ + +#include "Precomp.h" + +#include "RotateDefs.h" +#include "Sha256.h" + +/* define it for speed optimization */ +/* #define _SHA256_UNROLL */ +/* #define _SHA256_UNROLL2 */ + +void Sha256_Init(CSha256 *p) +{ + p->state[0] = 0x6a09e667; + p->state[1] = 0xbb67ae85; + p->state[2] = 0x3c6ef372; + p->state[3] = 0xa54ff53a; + p->state[4] = 0x510e527f; + p->state[5] = 0x9b05688c; + p->state[6] = 0x1f83d9ab; + p->state[7] = 0x5be0cd19; + p->count = 0; +} + +#define S0(x) (rotrFixed(x, 2) ^ rotrFixed(x,13) ^ rotrFixed(x, 22)) +#define S1(x) (rotrFixed(x, 6) ^ rotrFixed(x,11) ^ rotrFixed(x, 25)) +#define s0(x) (rotrFixed(x, 7) ^ rotrFixed(x,18) ^ (x >> 3)) +#define s1(x) (rotrFixed(x,17) ^ rotrFixed(x,19) ^ (x >> 10)) + +#define blk0(i) (W[i] = data[i]) +#define blk2(i) (W[i&15] += s1(W[(i-2)&15]) + W[(i-7)&15] + s0(W[(i-15)&15])) + +#define Ch(x,y,z) (z^(x&(y^z))) +#define Maj(x,y,z) ((x&y)|(z&(x|y))) + +#define a(i) T[(0-(i))&7] +#define b(i) T[(1-(i))&7] +#define c(i) T[(2-(i))&7] +#define d(i) T[(3-(i))&7] +#define e(i) T[(4-(i))&7] +#define f(i) T[(5-(i))&7] +#define g(i) T[(6-(i))&7] +#define h(i) T[(7-(i))&7] + + +#ifdef _SHA256_UNROLL2 + +#define R(a,b,c,d,e,f,g,h, i) h += S1(e) + Ch(e,f,g) + K[i+j] + (j?blk2(i):blk0(i));\ + d += h; h += S0(a) + Maj(a, b, c) + +#define RX_8(i) \ + R(a,b,c,d,e,f,g,h, i); \ + R(h,a,b,c,d,e,f,g, i+1); \ + R(g,h,a,b,c,d,e,f, i+2); \ + R(f,g,h,a,b,c,d,e, i+3); \ + R(e,f,g,h,a,b,c,d, i+4); \ + R(d,e,f,g,h,a,b,c, i+5); \ + R(c,d,e,f,g,h,a,b, i+6); \ + R(b,c,d,e,f,g,h,a, i+7) + +#else + +#define R(i) h(i) += S1(e(i)) + Ch(e(i),f(i),g(i)) + K[i+j] + (j?blk2(i):blk0(i));\ + d(i) += h(i); h(i) += S0(a(i)) + Maj(a(i), b(i), c(i)) + +#ifdef _SHA256_UNROLL + +#define RX_8(i) R(i+0); R(i+1); R(i+2); R(i+3); R(i+4); R(i+5); R(i+6); R(i+7); + +#endif + +#endif + +static const UInt32 K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static void Sha256_Transform(UInt32 *state, const UInt32 *data) +{ + UInt32 W[16]; + unsigned j; + #ifdef _SHA256_UNROLL2 + UInt32 a,b,c,d,e,f,g,h; + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + f = state[5]; + g = state[6]; + h = state[7]; + #else + UInt32 T[8]; + for (j = 0; j < 8; j++) + T[j] = state[j]; + #endif + + for (j = 0; j < 64; j += 16) + { + #if defined(_SHA256_UNROLL) || defined(_SHA256_UNROLL2) + RX_8(0); RX_8(8); + #else + unsigned i; + for (i = 0; i < 16; i++) { R(i); } + #endif + } + + #ifdef _SHA256_UNROLL2 + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; + #else + for (j = 0; j < 8; j++) + state[j] += T[j]; + #endif + + /* Wipe variables */ + /* memset(W, 0, sizeof(W)); */ + /* memset(T, 0, sizeof(T)); */ +} + +#undef S0 +#undef S1 +#undef s0 +#undef s1 + +static void Sha256_WriteByteBlock(CSha256 *p) +{ + UInt32 data32[16]; + unsigned i; + for (i = 0; i < 16; i++) + data32[i] = + ((UInt32)(p->buffer[i * 4 ]) << 24) + + ((UInt32)(p->buffer[i * 4 + 1]) << 16) + + ((UInt32)(p->buffer[i * 4 + 2]) << 8) + + ((UInt32)(p->buffer[i * 4 + 3])); + Sha256_Transform(p->state, data32); +} + +void Sha256_Update(CSha256 *p, const Byte *data, size_t size) +{ + UInt32 curBufferPos = (UInt32)p->count & 0x3F; + while (size > 0) + { + p->buffer[curBufferPos++] = *data++; + p->count++; + size--; + if (curBufferPos == 64) + { + curBufferPos = 0; + Sha256_WriteByteBlock(p); + } + } +} + +void Sha256_Final(CSha256 *p, Byte *digest) +{ + UInt64 lenInBits = (p->count << 3); + UInt32 curBufferPos = (UInt32)p->count & 0x3F; + unsigned i; + p->buffer[curBufferPos++] = 0x80; + while (curBufferPos != (64 - 8)) + { + curBufferPos &= 0x3F; + if (curBufferPos == 0) + Sha256_WriteByteBlock(p); + p->buffer[curBufferPos++] = 0; + } + for (i = 0; i < 8; i++) + { + p->buffer[curBufferPos++] = (Byte)(lenInBits >> 56); + lenInBits <<= 8; + } + Sha256_WriteByteBlock(p); + + for (i = 0; i < 8; i++) + { + *digest++ = (Byte)(p->state[i] >> 24); + *digest++ = (Byte)(p->state[i] >> 16); + *digest++ = (Byte)(p->state[i] >> 8); + *digest++ = (Byte)(p->state[i]); + } + Sha256_Init(p); +}
@@ -0,0 +1,26 @@
+/* Sha256.h -- SHA-256 Hash +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __CRYPTO_SHA256_H +#define __CRYPTO_SHA256_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define SHA256_DIGEST_SIZE 32 + +typedef struct +{ + UInt32 state[8]; + UInt64 count; + Byte buffer[64]; +} CSha256; + +void Sha256_Init(CSha256 *p); +void Sha256_Update(CSha256 *p, const Byte *data, size_t size); +void Sha256_Final(CSha256 *p, Byte *digest); + +EXTERN_C_END + +#endif
@@ -0,0 +1,141 @@
+/* Sort.c -- Sort functions +2014-04-05 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Sort.h" + +#define HeapSortDown(p, k, size, temp) \ + { for (;;) { \ + size_t s = (k << 1); \ + if (s > size) break; \ + if (s < size && p[s + 1] > p[s]) s++; \ + if (temp >= p[s]) break; \ + p[k] = p[s]; k = s; \ + } p[k] = temp; } + +void HeapSort(UInt32 *p, size_t size) +{ + if (size <= 1) + return; + p--; + { + size_t i = size / 2; + do + { + UInt32 temp = p[i]; + size_t k = i; + HeapSortDown(p, k, size, temp) + } + while (--i != 0); + } + /* + do + { + size_t k = 1; + UInt32 temp = p[size]; + p[size--] = p[1]; + HeapSortDown(p, k, size, temp) + } + while (size > 1); + */ + while (size > 3) + { + UInt32 temp = p[size]; + size_t k = (p[3] > p[2]) ? 3 : 2; + p[size--] = p[1]; + p[1] = p[k]; + HeapSortDown(p, k, size, temp) + } + { + UInt32 temp = p[size]; + p[size] = p[1]; + if (size > 2 && p[2] < temp) + { + p[1] = p[2]; + p[2] = temp; + } + else + p[1] = temp; + } +} + +void HeapSort64(UInt64 *p, size_t size) +{ + if (size <= 1) + return; + p--; + { + size_t i = size / 2; + do + { + UInt64 temp = p[i]; + size_t k = i; + HeapSortDown(p, k, size, temp) + } + while (--i != 0); + } + /* + do + { + size_t k = 1; + UInt64 temp = p[size]; + p[size--] = p[1]; + HeapSortDown(p, k, size, temp) + } + while (size > 1); + */ + while (size > 3) + { + UInt64 temp = p[size]; + size_t k = (p[3] > p[2]) ? 3 : 2; + p[size--] = p[1]; + p[1] = p[k]; + HeapSortDown(p, k, size, temp) + } + { + UInt64 temp = p[size]; + p[size] = p[1]; + if (size > 2 && p[2] < temp) + { + p[1] = p[2]; + p[2] = temp; + } + else + p[1] = temp; + } +} + +/* +#define HeapSortRefDown(p, vals, n, size, temp) \ + { size_t k = n; UInt32 val = vals[temp]; for (;;) { \ + size_t s = (k << 1); \ + if (s > size) break; \ + if (s < size && vals[p[s + 1]] > vals[p[s]]) s++; \ + if (val >= vals[p[s]]) break; \ + p[k] = p[s]; k = s; \ + } p[k] = temp; } + +void HeapSortRef(UInt32 *p, UInt32 *vals, size_t size) +{ + if (size <= 1) + return; + p--; + { + size_t i = size / 2; + do + { + UInt32 temp = p[i]; + HeapSortRefDown(p, vals, i, size, temp); + } + while (--i != 0); + } + do + { + UInt32 temp = p[size]; + p[size--] = p[1]; + HeapSortRefDown(p, vals, 1, size, temp); + } + while (size > 1); +} +*/
@@ -0,0 +1,18 @@
+/* Sort.h -- Sort functions +2014-04-05 : Igor Pavlov : Public domain */ + +#ifndef __7Z_SORT_H +#define __7Z_SORT_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +void HeapSort(UInt32 *p, size_t size); +void HeapSort64(UInt64 *p, size_t size); + +/* void HeapSortRef(UInt32 *p, UInt32 *vals, size_t size); */ + +EXTERN_C_END + +#endif
@@ -0,0 +1,93 @@
+/* Threads.c -- multithreading library +2013-11-12 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#ifndef _WIN32_WCE +#include <process.h> +#endif + +#include "Threads.h" + +static WRes GetError() +{ + DWORD res = GetLastError(); + return (res) ? (WRes)(res) : 1; +} + +WRes HandleToWRes(HANDLE h) { return (h != 0) ? 0 : GetError(); } +WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); } + +WRes HandlePtr_Close(HANDLE *p) +{ + if (*p != NULL) + if (!CloseHandle(*p)) + return GetError(); + *p = NULL; + return 0; +} + +WRes Handle_WaitObject(HANDLE h) { return (WRes)WaitForSingleObject(h, INFINITE); } + +WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param) +{ + /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */ + + #ifdef UNDER_CE + + DWORD threadId; + *p = CreateThread(0, 0, func, param, 0, &threadId); + + #else + + unsigned threadId; + *p = (HANDLE)_beginthreadex(NULL, 0, func, param, 0, &threadId); + + #endif + + /* maybe we must use errno here, but probably GetLastError() is also OK. */ + return HandleToWRes(*p); +} + +WRes Event_Create(CEvent *p, BOOL manualReset, int signaled) +{ + *p = CreateEvent(NULL, manualReset, (signaled ? TRUE : FALSE), NULL); + return HandleToWRes(*p); +} + +WRes Event_Set(CEvent *p) { return BOOLToWRes(SetEvent(*p)); } +WRes Event_Reset(CEvent *p) { return BOOLToWRes(ResetEvent(*p)); } + +WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled) { return Event_Create(p, TRUE, signaled); } +WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled) { return Event_Create(p, FALSE, signaled); } +WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p) { return ManualResetEvent_Create(p, 0); } +WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) { return AutoResetEvent_Create(p, 0); } + + +WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount) +{ + *p = CreateSemaphore(NULL, (LONG)initCount, (LONG)maxCount, NULL); + return HandleToWRes(*p); +} + +static WRes Semaphore_Release(CSemaphore *p, LONG releaseCount, LONG *previousCount) + { return BOOLToWRes(ReleaseSemaphore(*p, releaseCount, previousCount)); } +WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num) + { return Semaphore_Release(p, (LONG)num, NULL); } +WRes Semaphore_Release1(CSemaphore *p) { return Semaphore_ReleaseN(p, 1); } + +WRes CriticalSection_Init(CCriticalSection *p) +{ + /* InitializeCriticalSection can raise only STATUS_NO_MEMORY exception */ + #ifdef _MSC_VER + __try + #endif + { + InitializeCriticalSection(p); + /* InitializeCriticalSectionAndSpinCount(p, 0); */ + } + #ifdef _MSC_VER + __except (EXCEPTION_EXECUTE_HANDLER) { return 1; } + #endif + return 0; +}
@@ -0,0 +1,67 @@
+/* Threads.h -- multithreading library +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_THREADS_H +#define __7Z_THREADS_H + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +WRes HandlePtr_Close(HANDLE *h); +WRes Handle_WaitObject(HANDLE h); + +typedef HANDLE CThread; +#define Thread_Construct(p) *(p) = NULL +#define Thread_WasCreated(p) (*(p) != NULL) +#define Thread_Close(p) HandlePtr_Close(p) +#define Thread_Wait(p) Handle_WaitObject(*(p)) + +typedef +#ifdef UNDER_CE + DWORD +#else + unsigned +#endif + THREAD_FUNC_RET_TYPE; + +#define THREAD_FUNC_CALL_TYPE MY_STD_CALL +#define THREAD_FUNC_DECL THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE +typedef THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE * THREAD_FUNC_TYPE)(void *); +WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param); + +typedef HANDLE CEvent; +typedef CEvent CAutoResetEvent; +typedef CEvent CManualResetEvent; +#define Event_Construct(p) *(p) = NULL +#define Event_IsCreated(p) (*(p) != NULL) +#define Event_Close(p) HandlePtr_Close(p) +#define Event_Wait(p) Handle_WaitObject(*(p)) +WRes Event_Set(CEvent *p); +WRes Event_Reset(CEvent *p); +WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled); +WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p); +WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled); +WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p); + +typedef HANDLE CSemaphore; +#define Semaphore_Construct(p) (*p) = NULL +#define Semaphore_Close(p) HandlePtr_Close(p) +#define Semaphore_Wait(p) Handle_WaitObject(*(p)) +WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount); +WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num); +WRes Semaphore_Release1(CSemaphore *p); + +typedef CRITICAL_SECTION CCriticalSection; +WRes CriticalSection_Init(CCriticalSection *p); +#define CriticalSection_Delete(p) DeleteCriticalSection(p) +#define CriticalSection_Enter(p) EnterCriticalSection(p) +#define CriticalSection_Leave(p) LeaveCriticalSection(p) + +EXTERN_C_END + +#endif
@@ -0,0 +1,229 @@
+# Microsoft Developer Studio Project File - Name="7z" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=7z - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "7z.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "7z.mak" CFG="7z - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "7z - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "7z - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "7z - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W4 /WX /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FAs /Yu"Precomp.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\util\7zDec.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_SZ_ALLOC_DEBUG2" /D "_SZ_NO_INT_64_A" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Yu"Precomp.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\util\7zDec.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "7z - Win32 Release" +# Name "7z - Win32 Debug" +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7z.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zArcIn.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrcOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zDec.c +# ADD CPP /D "_7ZIP_PPMD_SUPPPORT" +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra86.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.c +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd.h +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd7.c +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd7.h +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd7Dec.c +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compiler.h +# End Source File +# Begin Source File + +SOURCE=.\Precomp.c +# ADD CPP /Yc"Precomp.h" +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7zMain.c +# End Source File +# End Target +# End Project
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "7z"=.\7z.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +
@@ -0,0 +1,546 @@
+/* 7zMain.c - Test application for 7z Decoder +2015-01-02 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <stdio.h> +#include <string.h> + +#include "../../7z.h" +#include "../../7zAlloc.h" +#include "../../7zBuf.h" +#include "../../7zCrc.h" +#include "../../7zFile.h" +#include "../../7zVersion.h" + +#ifndef USE_WINDOWS_FILE +/* for mkdir */ +#ifdef _WIN32 +#include <direct.h> +#else +#include <sys/stat.h> +#include <errno.h> +#endif +#endif + +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +static int Buf_EnsureSize(CBuf *dest, size_t size) +{ + if (dest->size >= size) + return 1; + Buf_Free(dest, &g_Alloc); + return Buf_Create(dest, size, &g_Alloc); +} + +#ifndef _WIN32 + +static Byte kUtf8Limits[5] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +static Bool Utf16_To_Utf8(Byte *dest, size_t *destLen, const UInt16 *src, size_t srcLen) +{ + size_t destPos = 0, srcPos = 0; + for (;;) + { + unsigned numAdds; + UInt32 value; + if (srcPos == srcLen) + { + *destLen = destPos; + return True; + } + value = src[srcPos++]; + if (value < 0x80) + { + if (dest) + dest[destPos] = (char)value; + destPos++; + continue; + } + if (value >= 0xD800 && value < 0xE000) + { + UInt32 c2; + if (value >= 0xDC00 || srcPos == srcLen) + break; + c2 = src[srcPos++]; + if (c2 < 0xDC00 || c2 >= 0xE000) + break; + value = (((value - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000; + } + for (numAdds = 1; numAdds < 5; numAdds++) + if (value < (((UInt32)1) << (numAdds * 5 + 6))) + break; + if (dest) + dest[destPos] = (char)(kUtf8Limits[numAdds - 1] + (value >> (6 * numAdds))); + destPos++; + do + { + numAdds--; + if (dest) + dest[destPos] = (char)(0x80 + ((value >> (6 * numAdds)) & 0x3F)); + destPos++; + } + while (numAdds != 0); + } + *destLen = destPos; + return False; +} + +static SRes Utf16_To_Utf8Buf(CBuf *dest, const UInt16 *src, size_t srcLen) +{ + size_t destLen = 0; + Bool res; + Utf16_To_Utf8(NULL, &destLen, src, srcLen); + destLen += 1; + if (!Buf_EnsureSize(dest, destLen)) + return SZ_ERROR_MEM; + res = Utf16_To_Utf8(dest->data, &destLen, src, srcLen); + dest->data[destLen] = 0; + return res ? SZ_OK : SZ_ERROR_FAIL; +} + +#endif + +static SRes Utf16_To_Char(CBuf *buf, const UInt16 *s + #ifdef _WIN32 + , UINT codePage + #endif + ) +{ + unsigned len = 0; + for (len = 0; s[len] != 0; len++); + + #ifdef _WIN32 + { + unsigned size = len * 3 + 100; + if (!Buf_EnsureSize(buf, size)) + return SZ_ERROR_MEM; + { + buf->data[0] = 0; + if (len != 0) + { + char defaultChar = '_'; + BOOL defUsed; + unsigned numChars = 0; + numChars = WideCharToMultiByte(codePage, 0, s, len, (char *)buf->data, size, &defaultChar, &defUsed); + if (numChars == 0 || numChars >= size) + return SZ_ERROR_FAIL; + buf->data[numChars] = 0; + } + return SZ_OK; + } + } + #else + return Utf16_To_Utf8Buf(buf, s, len); + #endif +} + +#ifdef _WIN32 + #ifndef USE_WINDOWS_FILE + static UINT g_FileCodePage = CP_ACP; + #endif + #define MY_FILE_CODE_PAGE_PARAM ,g_FileCodePage +#else + #define MY_FILE_CODE_PAGE_PARAM +#endif + +static WRes MyCreateDir(const UInt16 *name) +{ + #ifdef USE_WINDOWS_FILE + + return CreateDirectoryW(name, NULL) ? 0 : GetLastError(); + + #else + + CBuf buf; + WRes res; + Buf_Init(&buf); + RINOK(Utf16_To_Char(&buf, name MY_FILE_CODE_PAGE_PARAM)); + + res = + #ifdef _WIN32 + _mkdir((const char *)buf.data) + #else + mkdir((const char *)buf.data, 0777) + #endif + == 0 ? 0 : errno; + Buf_Free(&buf, &g_Alloc); + return res; + + #endif +} + +static WRes OutFile_OpenUtf16(CSzFile *p, const UInt16 *name) +{ + #ifdef USE_WINDOWS_FILE + return OutFile_OpenW(p, name); + #else + CBuf buf; + WRes res; + Buf_Init(&buf); + RINOK(Utf16_To_Char(&buf, name MY_FILE_CODE_PAGE_PARAM)); + res = OutFile_Open(p, (const char *)buf.data); + Buf_Free(&buf, &g_Alloc); + return res; + #endif +} + +static SRes PrintString(const UInt16 *s) +{ + CBuf buf; + SRes res; + Buf_Init(&buf); + res = Utf16_To_Char(&buf, s + #ifdef _WIN32 + , CP_OEMCP + #endif + ); + if (res == SZ_OK) + fputs((const char *)buf.data, stdout); + Buf_Free(&buf, &g_Alloc); + return res; +} + +static void UInt64ToStr(UInt64 value, char *s) +{ + char temp[32]; + int pos = 0; + do + { + temp[pos++] = (char)('0' + (unsigned)(value % 10)); + value /= 10; + } + while (value != 0); + do + *s++ = temp[--pos]; + while (pos); + *s = '\0'; +} + +static char *UIntToStr(char *s, unsigned value, int numDigits) +{ + char temp[16]; + int pos = 0; + do + temp[pos++] = (char)('0' + (value % 10)); + while (value /= 10); + for (numDigits -= pos; numDigits > 0; numDigits--) + *s++ = '0'; + do + *s++ = temp[--pos]; + while (pos); + *s = '\0'; + return s; +} + +static void UIntToStr_2(char *s, unsigned value) +{ + s[0] = (char)('0' + (value / 10)); + s[1] = (char)('0' + (value % 10)); +} + +#define PERIOD_4 (4 * 365 + 1) +#define PERIOD_100 (PERIOD_4 * 25 - 1) +#define PERIOD_400 (PERIOD_100 * 4 + 1) + +static void ConvertFileTimeToString(const CNtfsFileTime *nt, char *s) +{ + unsigned year, mon, hour, min, sec; + Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + unsigned t; + UInt32 v; + UInt64 v64 = nt->Low | ((UInt64)nt->High << 32); + v64 /= 10000000; + sec = (unsigned)(v64 % 60); v64 /= 60; + min = (unsigned)(v64 % 60); v64 /= 60; + hour = (unsigned)(v64 % 24); v64 /= 24; + + v = (UInt32)v64; + + year = (unsigned)(1601 + v / PERIOD_400 * 400); + v %= PERIOD_400; + + t = v / PERIOD_100; if (t == 4) t = 3; year += t * 100; v -= t * PERIOD_100; + t = v / PERIOD_4; if (t == 25) t = 24; year += t * 4; v -= t * PERIOD_4; + t = v / 365; if (t == 4) t = 3; year += t; v -= t * 365; + + if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + ms[1] = 29; + for (mon = 0;; mon++) + { + unsigned s = ms[mon]; + if (v < s) + break; + v -= s; + } + s = UIntToStr(s, year, 4); *s++ = '-'; + UIntToStr_2(s, mon + 1); s[2] = '-'; s += 3; + UIntToStr_2(s, (unsigned)v + 1); s[2] = ' '; s += 3; + UIntToStr_2(s, hour); s[2] = ':'; s += 3; + UIntToStr_2(s, min); s[2] = ':'; s += 3; + UIntToStr_2(s, sec); s[2] = 0; +} + +void PrintError(const char *sz) +{ + printf("\nERROR: %s\n", sz); +} + +#ifdef USE_WINDOWS_FILE +static void GetAttribString(UInt32 wa, Bool isDir, char *s) +{ + s[0] = (char)(((wa & FILE_ATTRIBUTE_DIRECTORY) != 0 || isDir) ? 'D' : '.'); + s[1] = (char)(((wa & FILE_ATTRIBUTE_READONLY ) != 0) ? 'R': '.'); + s[2] = (char)(((wa & FILE_ATTRIBUTE_HIDDEN ) != 0) ? 'H': '.'); + s[3] = (char)(((wa & FILE_ATTRIBUTE_SYSTEM ) != 0) ? 'S': '.'); + s[4] = (char)(((wa & FILE_ATTRIBUTE_ARCHIVE ) != 0) ? 'A': '.'); + s[5] = '\0'; +} +#else +static void GetAttribString(UInt32, Bool, char *s) +{ + s[0] = '\0'; +} +#endif + +// #define NUM_PARENTS_MAX 128 + +int MY_CDECL main(int numargs, char *args[]) +{ + CFileInStream archiveStream; + CLookToRead lookStream; + CSzArEx db; + SRes res; + ISzAlloc allocImp; + ISzAlloc allocTempImp; + UInt16 *temp = NULL; + size_t tempSize = 0; + // UInt32 parents[NUM_PARENTS_MAX]; + + printf("\n7z ANSI-C Decoder " MY_VERSION_COPYRIGHT_DATE "\n\n"); + if (numargs == 1) + { + printf( + "Usage: 7zDec <command> <archive_name>\n\n" + "<Commands>\n" + " e: Extract files from archive (without using directory names)\n" + " l: List contents of archive\n" + " t: Test integrity of archive\n" + " x: eXtract files with full paths\n"); + return 0; + } + if (numargs < 3) + { + PrintError("incorrect command"); + return 1; + } + + #if defined(_WIN32) && !defined(USE_WINDOWS_FILE) && !defined(UNDER_CE) + g_FileCodePage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; + #endif + + allocImp.Alloc = SzAlloc; + allocImp.Free = SzFree; + + allocTempImp.Alloc = SzAllocTemp; + allocTempImp.Free = SzFreeTemp; + + #ifdef UNDER_CE + if (InFile_OpenW(&archiveStream.file, L"\test.7z")) + #else + if (InFile_Open(&archiveStream.file, args[2])) + #endif + { + PrintError("can not open input file"); + return 1; + } + + FileInStream_CreateVTable(&archiveStream); + LookToRead_CreateVTable(&lookStream, False); + + lookStream.realStream = &archiveStream.s; + LookToRead_Init(&lookStream); + + CrcGenerateTable(); + + SzArEx_Init(&db); + res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp); + if (res == SZ_OK) + { + char *command = args[1]; + int listCommand = 0, testCommand = 0, fullPaths = 0; + if (strcmp(command, "l") == 0) listCommand = 1; + else if (strcmp(command, "t") == 0) testCommand = 1; + else if (strcmp(command, "e") == 0) { } + else if (strcmp(command, "x") == 0) { fullPaths = 1; } + else + { + PrintError("incorrect command"); + res = SZ_ERROR_FAIL; + } + + if (res == SZ_OK) + { + UInt32 i; + + /* + if you need cache, use these 3 variables. + if you use external function, you can make these variable as static. + */ + UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */ + Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */ + size_t outBufferSize = 0; /* it can have any value before first call (if outBuffer = 0) */ + + for (i = 0; i < db.NumFiles; i++) + { + size_t offset = 0; + size_t outSizeProcessed = 0; + // const CSzFileItem *f = db.Files + i; + size_t len; + int isDir = SzArEx_IsDir(&db, i); + if (listCommand == 0 && isDir && !fullPaths) + continue; + len = SzArEx_GetFileNameUtf16(&db, i, NULL); + // len = SzArEx_GetFullNameLen(&db, i); + + if (len > tempSize) + { + SzFree(NULL, temp); + tempSize = len; + temp = (UInt16 *)SzAlloc(NULL, tempSize * sizeof(temp[0])); + if (!temp) + { + res = SZ_ERROR_MEM; + break; + } + } + + SzArEx_GetFileNameUtf16(&db, i, temp); + /* + if (SzArEx_GetFullNameUtf16_Back(&db, i, temp + len) != temp) + { + res = SZ_ERROR_FAIL; + break; + } + */ + + if (listCommand) + { + char attr[8], s[32], t[32]; + UInt64 fileSize; + + GetAttribString(SzBitWithVals_Check(&db.Attribs, i) ? db.Attribs.Vals[i] : 0, isDir, attr); + + fileSize = SzArEx_GetFileSize(&db, i); + UInt64ToStr(fileSize, s); + if (SzBitWithVals_Check(&db.MTime, i)) + ConvertFileTimeToString(&db.MTime.Vals[i], t); + else + { + size_t j; + for (j = 0; j < 19; j++) + t[j] = ' '; + t[j] = '\0'; + } + + printf("%s %s %10s ", t, attr, s); + res = PrintString(temp); + if (res != SZ_OK) + break; + if (isDir) + printf("/"); + printf("\n"); + continue; + } + fputs(testCommand ? + "Testing ": + "Extracting ", + stdout); + res = PrintString(temp); + if (res != SZ_OK) + break; + if (isDir) + printf("/"); + else + { + res = SzArEx_Extract(&db, &lookStream.s, i, + &blockIndex, &outBuffer, &outBufferSize, + &offset, &outSizeProcessed, + &allocImp, &allocTempImp); + if (res != SZ_OK) + break; + } + if (!testCommand) + { + CSzFile outFile; + size_t processedSize; + size_t j; + UInt16 *name = (UInt16 *)temp; + const UInt16 *destPath = (const UInt16 *)name; + for (j = 0; name[j] != 0; j++) + if (name[j] == '/') + { + if (fullPaths) + { + name[j] = 0; + MyCreateDir(name); + name[j] = CHAR_PATH_SEPARATOR; + } + else + destPath = name + j + 1; + } + + if (isDir) + { + MyCreateDir(destPath); + printf("\n"); + continue; + } + else if (OutFile_OpenUtf16(&outFile, destPath)) + { + PrintError("can not open output file"); + res = SZ_ERROR_FAIL; + break; + } + processedSize = outSizeProcessed; + if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed) + { + PrintError("can not write output file"); + res = SZ_ERROR_FAIL; + break; + } + if (File_Close(&outFile)) + { + PrintError("can not close output file"); + res = SZ_ERROR_FAIL; + break; + } + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.Attribs, i)) + SetFileAttributesW(destPath, db.Attribs.Vals[i]); + #endif + } + printf("\n"); + } + IAlloc_Free(&allocImp, outBuffer); + } + } + SzArEx_Free(&db, &allocImp); + SzFree(NULL, temp); + + File_Close(&archiveStream.file); + if (res == SZ_OK) + { + printf("\nEverything is Ok\n"); + return 0; + } + if (res == SZ_ERROR_UNSUPPORTED) + PrintError("decoder doesn't support this archive"); + else if (res == SZ_ERROR_MEM) + PrintError("can not allocate memory"); + else if (res == SZ_ERROR_CRC) + PrintError("CRC error"); + else + printf("\nERROR #%d\n", res); + return 1; +}
@@ -0,0 +1,4 @@
+/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h"
@@ -0,0 +1,10 @@
+/* Precomp.h -- StdAfx +2013-06-16 : Igor Pavlov : Public domain */ + +#ifndef __7Z_PRECOMP_H +#define __7Z_PRECOMP_H + +#include "../../Compiler.h" +#include "../../7zTypes.h" + +#endif
@@ -0,0 +1,39 @@
+# MY_STATIC_LINK=1 +CFLAGS = $(CFLAGS) -D_7ZIP_PPMD_SUPPPORT + +PROG = 7zDec.exe + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zBuf.obj \ + $O\7zCrc.obj \ + $O\7zCrcOpt.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zArcIn.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\CpuArch.obj \ + $O\Lzma2Dec.obj \ + $O\LzmaDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + +7Z_OBJS = \ + $O\7zMain.obj \ + +OBJS = \ + $O\Precomp.obj \ + $(7Z_OBJS) \ + $(C_OBJS) \ + +!include "../../../CPP/Build.mak" + +$(7Z_OBJS): $(*B).c + $(CCOMPL_USE) +$(C_OBJS): ../../$(*B).c + $(CCOMPL_USE) +$O\Precomp.obj: Precomp.c + $(CCOMPL_PCH)
@@ -0,0 +1,70 @@
+PROG = 7zDec +CXX = g++ +LIB = +RM = rm -f +CFLAGS = -c -O2 -Wall + +OBJS = 7zMain.o 7zAlloc.o 7zArcIn.o 7zBuf.o 7zBuf2.o 7zCrc.o 7zCrcOpt.o 7zDec.o CpuArch.o LzmaDec.o Lzma2Dec.o Bra.o Bra86.o Bcj2.o Ppmd7.o Ppmd7Dec.o 7zFile.o 7zStream.o + +all: $(PROG) + +$(PROG): $(OBJS) + $(CXX) -o $(PROG) $(LDFLAGS) $(OBJS) $(LIB) + +7zMain.o: 7zMain.c + $(CXX) $(CFLAGS) 7zMain.c + +7zAlloc.o: ../../7zAlloc.c + $(CXX) $(CFLAGS) ../../7zAlloc.c + +7zArcIn.o: ../../7zArcIn.c + $(CXX) $(CFLAGS) ../../7zArcIn.c + +7zBuf.o: ../../7zBuf.c + $(CXX) $(CFLAGS) ../../7zBuf.c + +7zBuf2.o: ../../7zBuf2.c + $(CXX) $(CFLAGS) ../../7zBuf2.c + +7zCrc.o: ../../7zCrc.c + $(CXX) $(CFLAGS) ../../7zCrc.c + +7zCrcOpt.o: ../../7zCrc.c + $(CXX) $(CFLAGS) ../../7zCrcOpt.c + +7zDec.o: ../../7zDec.c + $(CXX) $(CFLAGS) -D_7ZIP_PPMD_SUPPPORT ../../7zDec.c + +CpuArch.o: ../../CpuArch.c + $(CXX) $(CFLAGS) ../../CpuArch.c + +LzmaDec.o: ../../LzmaDec.c + $(CXX) $(CFLAGS) ../../LzmaDec.c + +Lzma2Dec.o: ../../Lzma2Dec.c + $(CXX) $(CFLAGS) ../../Lzma2Dec.c + +Bra.o: ../../Bra.c + $(CXX) $(CFLAGS) ../../Bra.c + +Bra86.o: ../../Bra86.c + $(CXX) $(CFLAGS) ../../Bra86.c + +Bcj2.o: ../../Bcj2.c + $(CXX) $(CFLAGS) ../../Bcj2.c + +Ppmd7.o: ../../Ppmd7.c + $(CXX) $(CFLAGS) ../../Ppmd7.c + +Ppmd7Dec.o: ../../Ppmd7Dec.c + $(CXX) $(CFLAGS) ../../Ppmd7Dec.c + +7zFile.o: ../../7zFile.c + $(CXX) $(CFLAGS) ../../7zFile.c + +7zStream.o: ../../7zStream.c + $(CXX) $(CFLAGS) ../../7zStream.c + +clean: + -$(RM) $(PROG) $(OBJS) +
@@ -0,0 +1,254 @@
+/* LzmaUtil.c -- Test application for LZMA compression +2014-12-31 : Igor Pavlov : Public domain */ + +#include "../../Precomp.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../../Alloc.h" +#include "../../7zFile.h" +#include "../../7zVersion.h" +#include "../../LzmaDec.h" +#include "../../LzmaEnc.h" + +const char *kCantReadMessage = "Can not read input file"; +const char *kCantWriteMessage = "Can not write output file"; +const char *kCantAllocateMessage = "Can not allocate memory"; +const char *kDataErrorMessage = "Data error"; + +static void *SzAlloc(void *p, size_t size) { p = p; return MyAlloc(size); } +static void SzFree(void *p, void *address) { p = p; MyFree(address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +void PrintHelp(char *buffer) +{ + strcat(buffer, "\nLZMA Utility " MY_VERSION_COPYRIGHT_DATE "\n" + "\nUsage: lzma <e|d> inputFile outputFile\n" + " e: encode file\n" + " d: decode file\n"); +} + +int PrintError(char *buffer, const char *message) +{ + strcat(buffer, "\nError: "); + strcat(buffer, message); + strcat(buffer, "\n"); + return 1; +} + +int PrintErrorNumber(char *buffer, SRes val) +{ + sprintf(buffer + strlen(buffer), "\nError code: %x\n", (unsigned)val); + return 1; +} + +int PrintUserError(char *buffer) +{ + return PrintError(buffer, "Incorrect command"); +} + +#define IN_BUF_SIZE (1 << 16) +#define OUT_BUF_SIZE (1 << 16) + +static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, + UInt64 unpackSize) +{ + int thereIsSize = (unpackSize != (UInt64)(Int64)-1); + Byte inBuf[IN_BUF_SIZE]; + Byte outBuf[OUT_BUF_SIZE]; + size_t inPos = 0, inSize = 0, outPos = 0; + LzmaDec_Init(state); + for (;;) + { + if (inPos == inSize) + { + inSize = IN_BUF_SIZE; + RINOK(inStream->Read(inStream, inBuf, &inSize)); + inPos = 0; + } + { + SRes res; + SizeT inProcessed = inSize - inPos; + SizeT outProcessed = OUT_BUF_SIZE - outPos; + ELzmaFinishMode finishMode = LZMA_FINISH_ANY; + ELzmaStatus status; + if (thereIsSize && outProcessed > unpackSize) + { + outProcessed = (SizeT)unpackSize; + finishMode = LZMA_FINISH_END; + } + + res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, + inBuf + inPos, &inProcessed, finishMode, &status); + inPos += inProcessed; + outPos += outProcessed; + unpackSize -= outProcessed; + + if (outStream) + if (outStream->Write(outStream, outBuf, outPos) != outPos) + return SZ_ERROR_WRITE; + + outPos = 0; + + if (res != SZ_OK || (thereIsSize && unpackSize == 0)) + return res; + + if (inProcessed == 0 && outProcessed == 0) + { + if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) + return SZ_ERROR_DATA; + return res; + } + } + } +} + +static SRes Decode(ISeqOutStream *outStream, ISeqInStream *inStream) +{ + UInt64 unpackSize; + int i; + SRes res = 0; + + CLzmaDec state; + + /* header: 5 bytes of LZMA properties and 8 bytes of uncompressed size */ + unsigned char header[LZMA_PROPS_SIZE + 8]; + + /* Read and parse header */ + + RINOK(SeqInStream_Read(inStream, header, sizeof(header))); + + unpackSize = 0; + for (i = 0; i < 8; i++) + unpackSize += (UInt64)header[LZMA_PROPS_SIZE + i] << (i * 8); + + LzmaDec_Construct(&state); + RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)); + res = Decode2(&state, outStream, inStream, unpackSize); + LzmaDec_Free(&state, &g_Alloc); + return res; +} + +static SRes Encode(ISeqOutStream *outStream, ISeqInStream *inStream, UInt64 fileSize, char *rs) +{ + CLzmaEncHandle enc; + SRes res; + CLzmaEncProps props; + + rs = rs; + + enc = LzmaEnc_Create(&g_Alloc); + if (enc == 0) + return SZ_ERROR_MEM; + + LzmaEncProps_Init(&props); + res = LzmaEnc_SetProps(enc, &props); + + if (res == SZ_OK) + { + Byte header[LZMA_PROPS_SIZE + 8]; + size_t headerSize = LZMA_PROPS_SIZE; + int i; + + res = LzmaEnc_WriteProperties(enc, header, &headerSize); + for (i = 0; i < 8; i++) + header[headerSize++] = (Byte)(fileSize >> (8 * i)); + if (outStream->Write(outStream, header, headerSize) != headerSize) + res = SZ_ERROR_WRITE; + else + { + if (res == SZ_OK) + res = LzmaEnc_Encode(enc, outStream, inStream, NULL, &g_Alloc, &g_Alloc); + } + } + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return res; +} + +int main2(int numArgs, const char *args[], char *rs) +{ + CFileSeqInStream inStream; + CFileOutStream outStream; + char c; + int res; + int encodeMode; + Bool useOutFile = False; + + FileSeqInStream_CreateVTable(&inStream); + File_Construct(&inStream.file); + + FileOutStream_CreateVTable(&outStream); + File_Construct(&outStream.file); + + if (numArgs == 1) + { + PrintHelp(rs); + return 0; + } + + if (numArgs < 3 || numArgs > 4 || strlen(args[1]) != 1) + return PrintUserError(rs); + + c = args[1][0]; + encodeMode = (c == 'e' || c == 'E'); + if (!encodeMode && c != 'd' && c != 'D') + return PrintUserError(rs); + + { + size_t t4 = sizeof(UInt32); + size_t t8 = sizeof(UInt64); + if (t4 != 4 || t8 != 8) + return PrintError(rs, "Incorrect UInt32 or UInt64"); + } + + if (InFile_Open(&inStream.file, args[2]) != 0) + return PrintError(rs, "Can not open input file"); + + if (numArgs > 3) + { + useOutFile = True; + if (OutFile_Open(&outStream.file, args[3]) != 0) + return PrintError(rs, "Can not open output file"); + } + else if (encodeMode) + PrintUserError(rs); + + if (encodeMode) + { + UInt64 fileSize; + File_GetLength(&inStream.file, &fileSize); + res = Encode(&outStream.s, &inStream.s, fileSize, rs); + } + else + { + res = Decode(&outStream.s, useOutFile ? &inStream.s : NULL); + } + + if (useOutFile) + File_Close(&outStream.file); + File_Close(&inStream.file); + + if (res != SZ_OK) + { + if (res == SZ_ERROR_MEM) + return PrintError(rs, kCantAllocateMessage); + else if (res == SZ_ERROR_DATA) + return PrintError(rs, kDataErrorMessage); + else if (res == SZ_ERROR_WRITE) + return PrintError(rs, kCantWriteMessage); + else if (res == SZ_ERROR_READ) + return PrintError(rs, kCantReadMessage); + return PrintErrorNumber(rs, res); + } + return 0; +} + +int MY_CDECL main(int numArgs, const char *args[]) +{ + char rs[800] = { 0 }; + int res = main2(numArgs, args, rs); + fputs(rs, stdout); + return res; +}
@@ -0,0 +1,168 @@
+# Microsoft Developer Studio Project File - Name="LzmaUtil" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=LzmaUtil - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "LzmaUtil.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "LzmaUtil.mak" CFG="LzmaUtil - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "LzmaUtil - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "LzmaUtil - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "LzmaUtil - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W4 /WX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\util\7lzma.exe" + +!ELSEIF "$(CFG)" == "LzmaUtil - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /WX /Gm /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\util\7lzma.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "LzmaUtil - Win32 Release" +# Name "LzmaUtil - Win32 Debug" +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=.\LzmaUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.c +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.h +# End Source File +# Begin Source File + +SOURCE=..\..\Types.h +# End Source File +# End Target +# End Project
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "LzmaUtil"=.\LzmaUtil.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +
@@ -0,0 +1,28 @@
+# MY_STATIC_LINK=1 +PROG = LZMAc.exe + +CFLAGS = $(CFLAGS) \ + +LIB_OBJS = \ + $O\LzmaUtil.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\7zFile.obj \ + $O\7zStream.obj \ + $O\Threads.obj \ + +OBJS = \ + $(LIB_OBJS) \ + $(C_OBJS) \ + +!include "../../../CPP/Build.mak" + +$(LIB_OBJS): $(*B).c + $(COMPL_O2) +$(C_OBJS): ../../$(*B).c + $(COMPL_O2)
@@ -0,0 +1,44 @@
+PROG = lzma +CXX = g++ +LIB = +RM = rm -f +CFLAGS = -c -O2 -Wall -D_7ZIP_ST + +OBJS = \ + LzmaUtil.o \ + Alloc.o \ + LzFind.o \ + LzmaDec.o \ + LzmaEnc.o \ + 7zFile.o \ + 7zStream.o \ + + +all: $(PROG) + +$(PROG): $(OBJS) + $(CXX) -o $(PROG) $(LDFLAGS) $(OBJS) $(LIB) $(LIB2) + +LzmaUtil.o: LzmaUtil.c + $(CXX) $(CFLAGS) LzmaUtil.c + +Alloc.o: ../../Alloc.c + $(CXX) $(CFLAGS) ../../Alloc.c + +LzFind.o: ../../LzFind.c + $(CXX) $(CFLAGS) ../../LzFind.c + +LzmaDec.o: ../../LzmaDec.c + $(CXX) $(CFLAGS) ../../LzmaDec.c + +LzmaEnc.o: ../../LzmaEnc.c + $(CXX) $(CFLAGS) ../../LzmaEnc.c + +7zFile.o: ../../7zFile.c + $(CXX) $(CFLAGS) ../../7zFile.c + +7zStream.o: ../../7zStream.c + $(CXX) $(CFLAGS) ../../7zStream.c + +clean: + -$(RM) $(PROG) $(OBJS)
@@ -0,0 +1,4 @@
+EXPORTS + LzmaCompress + LzmaUncompress +
@@ -0,0 +1,178 @@
+# Microsoft Developer Studio Project File - Name="LzmaLib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=LzmaLib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "LzmaLib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "LzmaLib.mak" CFG="LzmaLib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "LzmaLib - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "LzmaLib - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "LzmaLib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /YX /FD /c +# ADD CPP /nologo /Gr /MT /W3 /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"C:\Util\LZMA.dll" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "LzmaLib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /D "COMPRESS_MF_MT" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"C:\Util\LZMA.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "LzmaLib - Win32 Release" +# Name "LzmaLib - Win32 Debug" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\LzmaLib.def +# End Source File +# Begin Source File + +SOURCE=.\LzmaLibExports.c +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaLib.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaLib.h +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.c +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.h +# End Source File +# End Target +# End Project
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "LzmaLib"=.\LzmaLib.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +
@@ -0,0 +1,12 @@
+/* LzmaLibExports.c -- LZMA library DLL Entry point +2008-10-04 : Igor Pavlov : Public domain */ + +#include <windows.h> + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + hInstance = hInstance; + dwReason = dwReason; + lpReserved = lpReserved; + return TRUE; +}
@@ -0,0 +1,34 @@
+MY_STATIC_LINK=1 +SLIB = sLZMA.lib +PROG = LZMA.dll +SLIBPATH = $O\$(SLIB) + +DEF_FILE = LzmaLib.def +CFLAGS = $(CFLAGS) \ + +LIB_OBJS = \ + $O\LzmaLibExports.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\LzmaLib.obj \ + $O\Threads.obj \ + +OBJS = \ + $(LIB_OBJS) \ + $(C_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(SLIBPATH): $O $(OBJS) + lib -out:$(SLIBPATH) $(OBJS) $(LIBS) + +$(LIB_OBJS): $(*B).c + $(COMPL_O2) +$(C_OBJS): ../../$(*B).c + $(COMPL_O2)
@@ -0,0 +1,3 @@
+#include "../../7zVersion.rc" + +MY_VERSION_INFO_DLL("LZMA library", "LZMA")
@@ -0,0 +1,4 @@
+/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h"
@@ -0,0 +1,10 @@
+/* Precomp.h -- StdAfx +2013-06-16 : Igor Pavlov : Public domain */ + +#ifndef __7Z_PRECOMP_H +#define __7Z_PRECOMP_H + +#include "../../Compiler.h" +#include "../../7zTypes.h" + +#endif
@@ -0,0 +1,617 @@
+/* SfxSetup.c - 7z SFX Setup +2014-12-07 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#ifndef UNICODE +#define UNICODE +#endif + +#ifndef _UNICODE +#define _UNICODE +#endif + +#ifdef _CONSOLE +#include <stdio.h> +#endif + +#include "../../7z.h" +#include "../../7zAlloc.h" +#include "../../7zCrc.h" +#include "../../7zFile.h" +#include "../../CpuArch.h" + +#define k_EXE_ExtIndex 2 + +static const char *kExts[] = +{ + "bat" + , "cmd" + , "exe" + , "inf" + , "msi" + #ifdef UNDER_CE + , "cab" + #endif + , "html" + , "htm" +}; + +static const char *kNames[] = +{ + "setup" + , "install" + , "run" + , "start" +}; + +static unsigned FindExt(const wchar_t *s, unsigned *extLen) +{ + unsigned len = (unsigned)wcslen(s); + unsigned i; + for (i = len; i > 0; i--) + { + if (s[i - 1] == '.') + { + *extLen = len - i; + return i - 1; + } + } + *extLen = 0; + return len; +} + +#define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) -= 0x20 : (c))) + +static unsigned FindItem(const char **items, unsigned num, const wchar_t *s, unsigned len) +{ + unsigned i; + for (i = 0; i < num; i++) + { + const char *item = items[i]; + unsigned itemLen = (unsigned)strlen(item); + unsigned j; + if (len != itemLen) + continue; + for (j = 0; j < len; j++) + { + unsigned c = item[j]; + if (c != s[j] && MAKE_CHAR_UPPER(c) != s[j]) + break; + } + if (j == len) + return i; + } + return i; +} + +#ifdef _CONSOLE +static BOOL WINAPI HandlerRoutine(DWORD ctrlType) +{ + ctrlType = ctrlType; + return TRUE; +} +#endif + +static void PrintErrorMessage(const char *message) +{ + #ifdef _CONSOLE + printf("\n7-Zip Error: %s\n", message); + #else + #ifdef UNDER_CE + WCHAR messageW[256 + 4]; + unsigned i; + for (i = 0; i < 256 && message[i] != 0; i++) + messageW[i] = message[i]; + messageW[i] = 0; + MessageBoxW(0, messageW, L"7-Zip Error", MB_ICONERROR); + #else + MessageBoxA(0, message, "7-Zip Error", MB_ICONERROR); + #endif + #endif +} + +static WRes MyCreateDir(const WCHAR *name) +{ + return CreateDirectoryW(name, NULL) ? 0 : GetLastError(); +} + +#ifdef UNDER_CE +#define kBufferSize (1 << 13) +#else +#define kBufferSize (1 << 15) +#endif + +#define kSignatureSearchLimit (1 << 22) + +static Bool FindSignature(CSzFile *stream, UInt64 *resPos) +{ + Byte buf[kBufferSize]; + size_t numPrevBytes = 0; + *resPos = 0; + for (;;) + { + size_t processed, pos; + if (*resPos > kSignatureSearchLimit) + return False; + processed = kBufferSize - numPrevBytes; + if (File_Read(stream, buf + numPrevBytes, &processed) != 0) + return False; + processed += numPrevBytes; + if (processed < k7zStartHeaderSize || + (processed == k7zStartHeaderSize && numPrevBytes != 0)) + return False; + processed -= k7zStartHeaderSize; + for (pos = 0; pos <= processed; pos++) + { + for (; buf[pos] != '7' && pos <= processed; pos++); + if (pos > processed) + break; + if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0) + if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8)) + { + *resPos += pos; + return True; + } + } + *resPos += processed; + numPrevBytes = k7zStartHeaderSize; + memmove(buf, buf + processed, k7zStartHeaderSize); + } +} + +static Bool DoesFileOrDirExist(const WCHAR *path) +{ + WIN32_FIND_DATAW fd; + HANDLE handle; + handle = FindFirstFileW(path, &fd); + if (handle == INVALID_HANDLE_VALUE) + return False; + FindClose(handle); + return True; +} + +static WRes RemoveDirWithSubItems(WCHAR *path) +{ + WIN32_FIND_DATAW fd; + HANDLE handle; + WRes res = 0; + size_t len = wcslen(path); + wcscpy(path + len, L"*"); + handle = FindFirstFileW(path, &fd); + path[len] = L'\0'; + if (handle == INVALID_HANDLE_VALUE) + return GetLastError(); + for (;;) + { + if (wcscmp(fd.cFileName, L".") != 0 && + wcscmp(fd.cFileName, L"..") != 0) + { + wcscpy(path + len, fd.cFileName); + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + wcscat(path, WSTRING_PATH_SEPARATOR); + res = RemoveDirWithSubItems(path); + } + else + { + SetFileAttributesW(path, 0); + if (DeleteFileW(path) == 0) + res = GetLastError(); + } + if (res != 0) + break; + } + if (!FindNextFileW(handle, &fd)) + { + res = GetLastError(); + if (res == ERROR_NO_MORE_FILES) + res = 0; + break; + } + } + path[len] = L'\0'; + FindClose(handle); + if (res == 0) + { + if (!RemoveDirectoryW(path)) + res = GetLastError(); + } + return res; +} + +#ifdef _CONSOLE +int MY_CDECL main() +#else +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + lpCmdLine, int nCmdShow) +#endif +{ + CFileInStream archiveStream; + CLookToRead lookStream; + CSzArEx db; + SRes res = SZ_OK; + ISzAlloc allocImp; + ISzAlloc allocTempImp; + WCHAR sfxPath[MAX_PATH + 2]; + WCHAR path[MAX_PATH * 3 + 2]; + #ifndef UNDER_CE + WCHAR workCurDir[MAX_PATH + 32]; + #endif + size_t pathLen; + DWORD winRes; + const wchar_t *cmdLineParams; + const char *errorMessage = NULL; + Bool useShellExecute = True; + + #ifdef _CONSOLE + SetConsoleCtrlHandler(HandlerRoutine, TRUE); + #else + hInstance = hInstance; + hPrevInstance = hPrevInstance; + lpCmdLine = lpCmdLine; + nCmdShow = nCmdShow; + #endif + + CrcGenerateTable(); + + allocImp.Alloc = SzAlloc; + allocImp.Free = SzFree; + + allocTempImp.Alloc = SzAllocTemp; + allocTempImp.Free = SzFreeTemp; + + FileInStream_CreateVTable(&archiveStream); + LookToRead_CreateVTable(&lookStream, False); + + winRes = GetModuleFileNameW(NULL, sfxPath, MAX_PATH); + if (winRes == 0 || winRes > MAX_PATH) + return 1; + { + cmdLineParams = GetCommandLineW(); + #ifndef UNDER_CE + { + Bool quoteMode = False; + for (;; cmdLineParams++) + { + wchar_t c = *cmdLineParams; + if (c == L'\"') + quoteMode = !quoteMode; + else if (c == 0 || (c == L' ' && !quoteMode)) + break; + } + } + #endif + } + + { + unsigned i; + DWORD d; + winRes = GetTempPathW(MAX_PATH, path); + if (winRes == 0 || winRes > MAX_PATH) + return 1; + pathLen = wcslen(path); + d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); + + for (i = 0;; i++, d += GetTickCount()) + { + if (i >= 100) + { + res = SZ_ERROR_FAIL; + break; + } + wcscpy(path + pathLen, L"7z"); + + { + wchar_t *s = path + wcslen(path); + UInt32 value = d; + unsigned k; + for (k = 0; k < 8; k++) + { + unsigned t = value & 0xF; + value >>= 4; + s[7 - k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + } + s[k] = '\0'; + } + + if (DoesFileOrDirExist(path)) + continue; + if (CreateDirectoryW(path, NULL)) + { + wcscat(path, WSTRING_PATH_SEPARATOR); + pathLen = wcslen(path); + break; + } + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + res = SZ_ERROR_FAIL; + break; + } + } + + #ifndef UNDER_CE + wcscpy(workCurDir, path); + #endif + if (res != SZ_OK) + errorMessage = "Can't create temp folder"; + } + + if (res != SZ_OK) + { + if (!errorMessage) + errorMessage = "Error"; + PrintErrorMessage(errorMessage); + return 1; + } + + if (InFile_OpenW(&archiveStream.file, sfxPath) != 0) + { + errorMessage = "can not open input file"; + res = SZ_ERROR_FAIL; + } + else + { + UInt64 pos = 0; + if (!FindSignature(&archiveStream.file, &pos)) + res = SZ_ERROR_FAIL; + else if (File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET) != 0) + res = SZ_ERROR_FAIL; + if (res != 0) + errorMessage = "Can't find 7z archive"; + } + + if (res == SZ_OK) + { + lookStream.realStream = &archiveStream.s; + LookToRead_Init(&lookStream); + } + + SzArEx_Init(&db); + if (res == SZ_OK) + { + res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp); + } + + if (res == SZ_OK) + { + UInt32 executeFileIndex = (UInt32)(Int32)-1; + UInt32 minPrice = 1 << 30; + UInt32 i; + UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */ + Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */ + size_t outBufferSize = 0; /* it can have any value before first call (if outBuffer = 0) */ + + for (i = 0; i < db.NumFiles; i++) + { + size_t offset = 0; + size_t outSizeProcessed = 0; + size_t len; + WCHAR *temp; + len = SzArEx_GetFileNameUtf16(&db, i, NULL); + + if (len >= MAX_PATH) + { + res = SZ_ERROR_FAIL; + break; + } + + temp = path + pathLen; + + SzArEx_GetFileNameUtf16(&db, i, temp); + { + res = SzArEx_Extract(&db, &lookStream.s, i, + &blockIndex, &outBuffer, &outBufferSize, + &offset, &outSizeProcessed, + &allocImp, &allocTempImp); + if (res != SZ_OK) + break; + } + { + CSzFile outFile; + size_t processedSize; + size_t j; + size_t nameStartPos = 0; + for (j = 0; temp[j] != 0; j++) + { + if (temp[j] == '/') + { + temp[j] = 0; + MyCreateDir(path); + temp[j] = CHAR_PATH_SEPARATOR; + nameStartPos = j + 1; + } + } + + if (SzArEx_IsDir(&db, i)) + { + MyCreateDir(path); + continue; + } + else + { + unsigned extLen; + const WCHAR *name = temp + nameStartPos; + unsigned len = (unsigned)wcslen(name); + unsigned nameLen = FindExt(temp + nameStartPos, &extLen); + unsigned extPrice = FindItem(kExts, sizeof(kExts) / sizeof(kExts[0]), name + len - extLen, extLen); + unsigned namePrice = FindItem(kNames, sizeof(kNames) / sizeof(kNames[0]), name, nameLen); + + unsigned price = namePrice + extPrice * 64 + (nameStartPos == 0 ? 0 : (1 << 12)); + if (minPrice > price) + { + minPrice = price; + executeFileIndex = i; + useShellExecute = (extPrice != k_EXE_ExtIndex); + } + + if (DoesFileOrDirExist(path)) + { + errorMessage = "Duplicate file"; + res = SZ_ERROR_FAIL; + break; + } + if (OutFile_OpenW(&outFile, path)) + { + errorMessage = "Can't open output file"; + res = SZ_ERROR_FAIL; + break; + } + } + + processedSize = outSizeProcessed; + if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed) + { + errorMessage = "Can't write output file"; + res = SZ_ERROR_FAIL; + } + + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.MTime, i)) + { + const CNtfsFileTime *t = db.MTime.Vals + i; + FILETIME mTime; + mTime.dwLowDateTime = t->Low; + mTime.dwHighDateTime = t->High; + SetFileTime(outFile.handle, NULL, NULL, &mTime); + } + #endif + + { + SRes res2 = File_Close(&outFile); + if (res != SZ_OK) + break; + if (res2 != SZ_OK) + { + res = res2; + break; + } + } + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.Attribs, i)) + SetFileAttributesW(path, db.Attribs.Vals[i]); + #endif + } + } + + if (res == SZ_OK) + { + if (executeFileIndex == (UInt32)(Int32)-1) + { + errorMessage = "There is no file to execute"; + res = SZ_ERROR_FAIL; + } + else + { + WCHAR *temp = path + pathLen; + UInt32 j; + SzArEx_GetFileNameUtf16(&db, executeFileIndex, temp); + for (j = 0; temp[j] != 0; j++) + if (temp[j] == '/') + temp[j] = CHAR_PATH_SEPARATOR; + } + } + IAlloc_Free(&allocImp, outBuffer); + } + SzArEx_Free(&db, &allocImp); + + File_Close(&archiveStream.file); + + if (res == SZ_OK) + { + HANDLE hProcess = 0; + + #ifndef UNDER_CE + WCHAR oldCurDir[MAX_PATH + 2]; + oldCurDir[0] = 0; + { + DWORD needLen = GetCurrentDirectory(MAX_PATH + 1, oldCurDir); + if (needLen == 0 || needLen > MAX_PATH) + oldCurDir[0] = 0; + SetCurrentDirectory(workCurDir); + } + #endif + + if (useShellExecute) + { + SHELLEXECUTEINFO ei; + UINT32 executeRes; + BOOL success; + + memset(&ei, 0, sizeof(ei)); + ei.cbSize = sizeof(ei); + ei.lpFile = path; + ei.fMask = SEE_MASK_NOCLOSEPROCESS + #ifndef UNDER_CE + | SEE_MASK_FLAG_DDEWAIT + #endif + /* | SEE_MASK_NO_CONSOLE */ + ; + if (wcslen(cmdLineParams) != 0) + ei.lpParameters = cmdLineParams; + ei.nShow = SW_SHOWNORMAL; /* SW_HIDE; */ + success = ShellExecuteEx(&ei); + executeRes = (UINT32)(UINT_PTR)ei.hInstApp; + if (!success || (executeRes <= 32 && executeRes != 0)) /* executeRes = 0 in Windows CE */ + res = SZ_ERROR_FAIL; + else + hProcess = ei.hProcess; + } + else + { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + WCHAR cmdLine[MAX_PATH * 3]; + + wcscpy(cmdLine, path); + wcscat(cmdLine, cmdLineParams); + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0) + res = SZ_ERROR_FAIL; + else + { + CloseHandle(pi.hThread); + hProcess = pi.hProcess; + } + } + + if (hProcess != 0) + { + WaitForSingleObject(hProcess, INFINITE); + CloseHandle(hProcess); + } + + #ifndef UNDER_CE + SetCurrentDirectory(oldCurDir); + #endif + } + + path[pathLen] = L'\0'; + RemoveDirWithSubItems(path); + + if (res == SZ_OK) + return 0; + + { + if (res == SZ_ERROR_UNSUPPORTED) + errorMessage = "Decoder doesn't support this archive"; + else if (res == SZ_ERROR_MEM) + errorMessage = "Can't allocate required memory"; + else if (res == SZ_ERROR_CRC) + errorMessage = "CRC error"; + else + { + if (!errorMessage) + errorMessage = "ERROR"; + } + if (errorMessage) + PrintErrorMessage(errorMessage); + } + return 1; +}
@@ -0,0 +1,211 @@
+# Microsoft Developer Studio Project File - Name="SfxSetup" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=SfxSetup - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "SfxSetup.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "SfxSetup.mak" CFG="SfxSetup - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SfxSetup - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "SfxSetup - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SfxSetup - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W4 /WX /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /Yu"Precomp.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 + +!ELSEIF "$(CFG)" == "SfxSetup - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /Yu"Precomp.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "SfxSetup - Win32 Release" +# Name "SfxSetup - Win32 Debug" +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7z.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zArcIn.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrcOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra86.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.c +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\Precomp.c +# ADD CPP /Yc"Precomp.h" +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\SfxSetup.c +# End Source File +# End Target +# End Project
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SfxSetup"=.\SfxSetup.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### +
@@ -0,0 +1,33 @@
+PROG = 7zS2.sfx + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zArcIn.obj \ + $O\7zBuf.obj \ + $O\7zBuf2.obj \ + $O\7zCrc.obj \ + $O\7zCrcOpt.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\CpuArch.obj \ + $O\Lzma2Dec.obj \ + $O\LzmaDec.obj \ + +7Z_OBJS = \ + $O\SfxSetup.obj \ + +OBJS = \ + $(7Z_OBJS) \ + $(C_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(7Z_OBJS): $(*B).c + $(COMPL_O1) +$(C_OBJS): ../../$(*B).c + $(COMPL_O1)
@@ -0,0 +1,34 @@
+PROG = 7zS2con.sfx +CFLAGS = $(CFLAGS) -D_CONSOLE + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zArcIn.obj \ + $O\7zBuf.obj \ + $O\7zBuf2.obj \ + $O\7zCrc.obj \ + $O\7zCrcOpt.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\CpuArch.obj \ + $O\Lzma2Dec.obj \ + $O\LzmaDec.obj \ + +7Z_OBJS = \ + $O\SfxSetup.obj \ + +OBJS = \ + $(7Z_OBJS) \ + $(C_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(7Z_OBJS): $(*B).c + $(COMPL_O1) +$(C_OBJS): ../../$(*B).c + $(COMPL_O1)
@@ -0,0 +1,5 @@
+#include "../../7zVersion.rc" + +MY_VERSION_INFO_APP("7z Setup SFX small", "7zS2.sfx") + +1 ICON "setup.ico"
@@ -0,0 +1,90 @@
+/* Xz.c - Xz +2009-04-15 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "7zCrc.h" +#include "CpuArch.h" +#include "Xz.h" +#include "XzCrc64.h" + +Byte XZ_SIG[XZ_SIG_SIZE] = { 0xFD, '7', 'z', 'X', 'Z', 0 }; +Byte XZ_FOOTER_SIG[XZ_FOOTER_SIG_SIZE] = { 'Y', 'Z' }; + +unsigned Xz_WriteVarInt(Byte *buf, UInt64 v) +{ + unsigned i = 0; + do + { + buf[i++] = (Byte)((v & 0x7F) | 0x80); + v >>= 7; + } + while (v != 0); + buf[i - 1] &= 0x7F; + return i; +} + +void Xz_Construct(CXzStream *p) +{ + p->numBlocks = p->numBlocksAllocated = 0; + p->blocks = 0; + p->flags = 0; +} + +void Xz_Free(CXzStream *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->blocks); + p->numBlocks = p->numBlocksAllocated = 0; + p->blocks = 0; +} + +unsigned XzFlags_GetCheckSize(CXzStreamFlags f) +{ + int t = XzFlags_GetCheckType(f); + return (t == 0) ? 0 : (4 << ((t - 1) / 3)); +} + +void XzCheck_Init(CXzCheck *p, int mode) +{ + p->mode = mode; + switch (mode) + { + case XZ_CHECK_CRC32: p->crc = CRC_INIT_VAL; break; + case XZ_CHECK_CRC64: p->crc64 = CRC64_INIT_VAL; break; + case XZ_CHECK_SHA256: Sha256_Init(&p->sha); break; + } +} + +void XzCheck_Update(CXzCheck *p, const void *data, size_t size) +{ + switch (p->mode) + { + case XZ_CHECK_CRC32: p->crc = CrcUpdate(p->crc, data, size); break; + case XZ_CHECK_CRC64: p->crc64 = Crc64Update(p->crc64, data, size); break; + case XZ_CHECK_SHA256: Sha256_Update(&p->sha, (const Byte *)data, size); break; + } +} + +int XzCheck_Final(CXzCheck *p, Byte *digest) +{ + switch (p->mode) + { + case XZ_CHECK_CRC32: + SetUi32(digest, CRC_GET_DIGEST(p->crc)); + break; + case XZ_CHECK_CRC64: + { + int i; + UInt64 v = CRC64_GET_DIGEST(p->crc64); + for (i = 0; i < 8; i++, v >>= 8) + digest[i] = (Byte)(v & 0xFF); + break; + } + case XZ_CHECK_SHA256: + Sha256_Final(&p->sha, digest); + break; + default: + return 0; + } + return 1; +}
@@ -0,0 +1,275 @@
+/* Xz.h - Xz interface +2014-12-30 : Igor Pavlov : Public domain */ + +#ifndef __XZ_H +#define __XZ_H + +#include "Sha256.h" + +EXTERN_C_BEGIN + +#define XZ_ID_Subblock 1 +#define XZ_ID_Delta 3 +#define XZ_ID_X86 4 +#define XZ_ID_PPC 5 +#define XZ_ID_IA64 6 +#define XZ_ID_ARM 7 +#define XZ_ID_ARMT 8 +#define XZ_ID_SPARC 9 +#define XZ_ID_LZMA2 0x21 + +unsigned Xz_ReadVarInt(const Byte *p, size_t maxSize, UInt64 *value); +unsigned Xz_WriteVarInt(Byte *buf, UInt64 v); + +/* ---------- xz block ---------- */ + +#define XZ_BLOCK_HEADER_SIZE_MAX 1024 + +#define XZ_NUM_FILTERS_MAX 4 +#define XZ_BF_NUM_FILTERS_MASK 3 +#define XZ_BF_PACK_SIZE (1 << 6) +#define XZ_BF_UNPACK_SIZE (1 << 7) + +#define XZ_FILTER_PROPS_SIZE_MAX 20 + +typedef struct +{ + UInt64 id; + UInt32 propsSize; + Byte props[XZ_FILTER_PROPS_SIZE_MAX]; +} CXzFilter; + +typedef struct +{ + UInt64 packSize; + UInt64 unpackSize; + Byte flags; + CXzFilter filters[XZ_NUM_FILTERS_MAX]; +} CXzBlock; + +#define XzBlock_GetNumFilters(p) (((p)->flags & XZ_BF_NUM_FILTERS_MASK) + 1) +#define XzBlock_HasPackSize(p) (((p)->flags & XZ_BF_PACK_SIZE) != 0) +#define XzBlock_HasUnpackSize(p) (((p)->flags & XZ_BF_UNPACK_SIZE) != 0) + +SRes XzBlock_Parse(CXzBlock *p, const Byte *header); +SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, Bool *isIndex, UInt32 *headerSizeRes); + +/* ---------- xz stream ---------- */ + +#define XZ_SIG_SIZE 6 +#define XZ_FOOTER_SIG_SIZE 2 + +extern Byte XZ_SIG[XZ_SIG_SIZE]; +extern Byte XZ_FOOTER_SIG[XZ_FOOTER_SIG_SIZE]; + +#define XZ_STREAM_FLAGS_SIZE 2 +#define XZ_STREAM_CRC_SIZE 4 + +#define XZ_STREAM_HEADER_SIZE (XZ_SIG_SIZE + XZ_STREAM_FLAGS_SIZE + XZ_STREAM_CRC_SIZE) +#define XZ_STREAM_FOOTER_SIZE (XZ_FOOTER_SIG_SIZE + XZ_STREAM_FLAGS_SIZE + XZ_STREAM_CRC_SIZE + 4) + +#define XZ_CHECK_MASK 0xF +#define XZ_CHECK_NO 0 +#define XZ_CHECK_CRC32 1 +#define XZ_CHECK_CRC64 4 +#define XZ_CHECK_SHA256 10 + +typedef struct +{ + int mode; + UInt32 crc; + UInt64 crc64; + CSha256 sha; +} CXzCheck; + +void XzCheck_Init(CXzCheck *p, int mode); +void XzCheck_Update(CXzCheck *p, const void *data, size_t size); +int XzCheck_Final(CXzCheck *p, Byte *digest); + +typedef UInt16 CXzStreamFlags; + +#define XzFlags_IsSupported(f) ((f) <= XZ_CHECK_MASK) +#define XzFlags_GetCheckType(f) ((f) & XZ_CHECK_MASK) +#define XzFlags_HasDataCrc32(f) (Xz_GetCheckType(f) == XZ_CHECK_CRC32) +unsigned XzFlags_GetCheckSize(CXzStreamFlags f); + +SRes Xz_ParseHeader(CXzStreamFlags *p, const Byte *buf); +SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStream *inStream); + +typedef struct +{ + UInt64 unpackSize; + UInt64 totalSize; +} CXzBlockSizes; + +typedef struct +{ + CXzStreamFlags flags; + size_t numBlocks; + size_t numBlocksAllocated; + CXzBlockSizes *blocks; + UInt64 startOffset; +} CXzStream; + +void Xz_Construct(CXzStream *p); +void Xz_Free(CXzStream *p, ISzAlloc *alloc); + +#define XZ_SIZE_OVERFLOW ((UInt64)(Int64)-1) + +UInt64 Xz_GetUnpackSize(const CXzStream *p); +UInt64 Xz_GetPackSize(const CXzStream *p); + +typedef struct +{ + size_t num; + size_t numAllocated; + CXzStream *streams; +} CXzs; + +void Xzs_Construct(CXzs *p); +void Xzs_Free(CXzs *p, ISzAlloc *alloc); +SRes Xzs_ReadBackward(CXzs *p, ILookInStream *inStream, Int64 *startOffset, ICompressProgress *progress, ISzAlloc *alloc); + +UInt64 Xzs_GetNumBlocks(const CXzs *p); +UInt64 Xzs_GetUnpackSize(const CXzs *p); + +typedef enum +{ + CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */ + CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + CODER_STATUS_NOT_FINISHED, /* stream was not finished */ + CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */ +} ECoderStatus; + +typedef enum +{ + CODER_FINISH_ANY, /* finish at any point */ + CODER_FINISH_END /* block must be finished at the end */ +} ECoderFinishMode; + +typedef struct _IStateCoder +{ + void *p; + void (*Free)(void *p, ISzAlloc *alloc); + SRes (*SetProps)(void *p, const Byte *props, size_t propSize, ISzAlloc *alloc); + void (*Init)(void *p); + SRes (*Code)(void *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished); +} IStateCoder; + +#define MIXCODER_NUM_FILTERS_MAX 4 + +typedef struct +{ + ISzAlloc *alloc; + Byte *buf; + int numCoders; + int finished[MIXCODER_NUM_FILTERS_MAX - 1]; + size_t pos[MIXCODER_NUM_FILTERS_MAX - 1]; + size_t size[MIXCODER_NUM_FILTERS_MAX - 1]; + UInt64 ids[MIXCODER_NUM_FILTERS_MAX]; + IStateCoder coders[MIXCODER_NUM_FILTERS_MAX]; +} CMixCoder; + +void MixCoder_Construct(CMixCoder *p, ISzAlloc *alloc); +void MixCoder_Free(CMixCoder *p); +void MixCoder_Init(CMixCoder *p); +SRes MixCoder_SetFromMethod(CMixCoder *p, int coderIndex, UInt64 methodId); +SRes MixCoder_Code(CMixCoder *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, int srcWasFinished, + ECoderFinishMode finishMode, ECoderStatus *status); + +typedef enum +{ + XZ_STATE_STREAM_HEADER, + XZ_STATE_STREAM_INDEX, + XZ_STATE_STREAM_INDEX_CRC, + XZ_STATE_STREAM_FOOTER, + XZ_STATE_STREAM_PADDING, + XZ_STATE_BLOCK_HEADER, + XZ_STATE_BLOCK, + XZ_STATE_BLOCK_FOOTER +} EXzState; + +typedef struct +{ + EXzState state; + UInt32 pos; + unsigned alignPos; + unsigned indexPreSize; + + CXzStreamFlags streamFlags; + + UInt32 blockHeaderSize; + UInt64 packSize; + UInt64 unpackSize; + + UInt64 numBlocks; + UInt64 indexSize; + UInt64 indexPos; + UInt64 padSize; + + UInt64 numStartedStreams; + UInt64 numFinishedStreams; + UInt64 numTotalBlocks; + + UInt32 crc; + CMixCoder decoder; + CXzBlock block; + CXzCheck check; + CSha256 sha; + Byte shaDigest[SHA256_DIGEST_SIZE]; + Byte buf[XZ_BLOCK_HEADER_SIZE_MAX]; +} CXzUnpacker; + +void XzUnpacker_Construct(CXzUnpacker *p, ISzAlloc *alloc); +void XzUnpacker_Init(CXzUnpacker *p); +void XzUnpacker_Free(CXzUnpacker *p); + +/* +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + CODER_FINISH_ANY - use smallest number of input bytes + CODER_FINISH_END - read EndOfStream marker after decoding + +Returns: + SZ_OK + status: + CODER_STATUS_NOT_FINISHED, + CODER_STATUS_NEEDS_MORE_INPUT - maybe there are more xz streams, + call XzUnpacker_IsStreamWasFinished to check that current stream was finished + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_DATA - Data error + SZ_ERROR_UNSUPPORTED - Unsupported method or method properties + SZ_ERROR_CRC - CRC error + // SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). + + SZ_ERROR_NO_ARCHIVE - the error with xz Stream Header with one of the following reasons: + - xz Stream Signature failure + - CRC32 of xz Stream Header is failed + - The size of Stream padding is not multiple of four bytes. + It's possible to get that error, if xz stream was finished and the stream + contains some another data. In that case you can call XzUnpacker_GetExtraSize() + function to get real size of xz stream. +*/ + + +SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ECoderFinishMode finishMode, + ECoderStatus *status); + +Bool XzUnpacker_IsStreamWasFinished(CXzUnpacker *p); + +/* +Call XzUnpacker_GetExtraSize after XzUnpacker_Code function to detect real size of +xz stream in two cases: +XzUnpacker_Code() returns: + res == SZ_OK && status == CODER_STATUS_NEEDS_MORE_INPUT + res == SZ_ERROR_NO_ARCHIVE +*/ + +UInt64 XzUnpacker_GetExtraSize(CXzUnpacker *p); + +EXTERN_C_END + +#endif
@@ -0,0 +1,90 @@
+/* XzCrc64.c -- CRC64 calculation +2011-06-28 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "XzCrc64.h" +#include "CpuArch.h" + +#define kCrc64Poly UINT64_CONST(0xC96C5795D7870F42) + +#ifdef MY_CPU_LE + #define CRC_NUM_TABLES 4 +#else + #define CRC_NUM_TABLES 5 + #define CRC_UINT64_SWAP(v) \ + ((v >> 56) | \ + ((v >> 40) & ((UInt64)0xFF << 8)) | \ + ((v >> 24) & ((UInt64)0xFF << 16)) | \ + ((v >> 8) & ((UInt64)0xFF << 24)) | \ + ((v << 8) & ((UInt64)0xFF << 32)) | \ + ((v << 24) & ((UInt64)0xFF << 40)) | \ + ((v << 40) & ((UInt64)0xFF << 48)) | \ + (v << 56)) + UInt64 MY_FAST_CALL XzCrc64UpdateT1_BeT4(UInt64 v, const void *data, size_t size, const UInt64 *table); +#endif + +#ifndef MY_CPU_BE + UInt64 MY_FAST_CALL XzCrc64UpdateT4(UInt64 v, const void *data, size_t size, const UInt64 *table); +#endif + +typedef UInt64 (MY_FAST_CALL *CRC_FUNC)(UInt64 v, const void *data, size_t size, const UInt64 *table); + +static CRC_FUNC g_Crc64Update; +UInt64 g_Crc64Table[256 * CRC_NUM_TABLES]; + +UInt64 MY_FAST_CALL Crc64Update(UInt64 v, const void *data, size_t size) +{ + return g_Crc64Update(v, data, size, g_Crc64Table); +} + +UInt64 MY_FAST_CALL Crc64Calc(const void *data, size_t size) +{ + return g_Crc64Update(CRC64_INIT_VAL, data, size, g_Crc64Table) ^ CRC64_INIT_VAL; +} + +void MY_FAST_CALL Crc64GenerateTable() +{ + UInt32 i; + for (i = 0; i < 256; i++) + { + UInt64 r = i; + unsigned j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrc64Poly & ~((r & 1) - 1)); + g_Crc64Table[i] = r; + } + for (; i < 256 * CRC_NUM_TABLES; i++) + { + UInt64 r = g_Crc64Table[i - 256]; + g_Crc64Table[i] = g_Crc64Table[r & 0xFF] ^ (r >> 8); + } + + #ifdef MY_CPU_LE + + g_Crc64Update = XzCrc64UpdateT4; + + + + + + + #else + { + #ifndef MY_CPU_BE + UInt32 k = 1; + if (*(const Byte *)&k == 1) + g_Crc64Update = XzCrc64UpdateT4; + else + #endif + { + for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--) + { + UInt64 x = g_Crc64Table[i - 256]; + g_Crc64Table[i] = CRC_UINT64_SWAP(x); + } + g_Crc64Update = XzCrc64UpdateT1_BeT4; + } + } + #endif +}
@@ -0,0 +1,26 @@
+/* XzCrc64.h -- CRC64 calculation +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __XZ_CRC64_H +#define __XZ_CRC64_H + +#include <stddef.h> + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +extern UInt64 g_Crc64Table[]; + +void MY_FAST_CALL Crc64GenerateTable(void); + +#define CRC64_INIT_VAL UINT64_CONST(0xFFFFFFFFFFFFFFFF) +#define CRC64_GET_DIGEST(crc) ((crc) ^ CRC64_INIT_VAL) +#define CRC64_UPDATE_BYTE(crc, b) (g_Crc64Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +UInt64 MY_FAST_CALL Crc64Update(UInt64 crc, const void *data, size_t size); +UInt64 MY_FAST_CALL Crc64Calc(const void *data, size_t size); + +EXTERN_C_END + +#endif
@@ -0,0 +1,69 @@
+/* XzCrc64Opt.c -- CRC64 calculation +2011-06-28 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "CpuArch.h" + +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +#ifndef MY_CPU_BE + +UInt64 MY_FAST_CALL XzCrc64UpdateT4(UInt64 v, const void *data, size_t size, const UInt64 *table) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + for (; size >= 4; size -= 4, p += 4) + { + UInt32 d = (UInt32)v ^ *(const UInt32 *)p; + v = (v >> 32) ^ + table[0x300 + ((d ) & 0xFF)] ^ + table[0x200 + ((d >> 8) & 0xFF)] ^ + table[0x100 + ((d >> 16) & 0xFF)] ^ + table[0x000 + ((d >> 24))]; + } + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +#endif + + +#ifndef MY_CPU_LE + +#define CRC_UINT64_SWAP(v) \ + ((v >> 56) | \ + ((v >> 40) & ((UInt64)0xFF << 8)) | \ + ((v >> 24) & ((UInt64)0xFF << 16)) | \ + ((v >> 8) & ((UInt64)0xFF << 24)) | \ + ((v << 8) & ((UInt64)0xFF << 32)) | \ + ((v << 24) & ((UInt64)0xFF << 40)) | \ + ((v << 40) & ((UInt64)0xFF << 48)) | \ + (v << 56)) + +UInt64 MY_FAST_CALL XzCrc64UpdateT1_BeT4(UInt64 v, const void *data, size_t size, const UInt64 *table) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + v = CRC_UINT64_SWAP(v); + table += 0x100; + for (; size >= 4; size -= 4, p += 4) + { + UInt32 d = (UInt32)(v >> 32) ^ *(const UInt32 *)p; + v = (v << 32) ^ + table[0x000 + ((d ) & 0xFF)] ^ + table[0x100 + ((d >> 8) & 0xFF)] ^ + table[0x200 + ((d >> 16) & 0xFF)] ^ + table[0x300 + ((d >> 24))]; + } + table -= 0x100; + v = CRC_UINT64_SWAP(v); + for (; size > 0; size--, p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +#endif
@@ -0,0 +1,909 @@
+/* XzDec.c -- Xz Decode +2014-12-30 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +/* #define XZ_DUMP */ + +#ifdef XZ_DUMP +#include <stdio.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#include "7zCrc.h" +#include "Alloc.h" +#include "Bra.h" +#include "CpuArch.h" +#include "Delta.h" +#include "Lzma2Dec.h" + +#ifdef USE_SUBBLOCK +#include "Bcj3Dec.c" +#include "SbDec.c" +#endif + +#include "Xz.h" + +#define XZ_CHECK_SIZE_MAX 64 + +#define CODER_BUF_SIZE (1 << 17) + +unsigned Xz_ReadVarInt(const Byte *p, size_t maxSize, UInt64 *value) +{ + int i, limit; + *value = 0; + limit = (maxSize > 9) ? 9 : (int)maxSize; + + for (i = 0; i < limit;) + { + Byte b = p[i]; + *value |= (UInt64)(b & 0x7F) << (7 * i++); + if ((b & 0x80) == 0) + return (b == 0 && i != 1) ? 0 : i; + } + return 0; +} + +/* ---------- BraState ---------- */ + +#define BRA_BUF_SIZE (1 << 14) + +typedef struct +{ + size_t bufPos; + size_t bufConv; + size_t bufTotal; + + UInt32 methodId; + int encodeMode; + UInt32 delta; + UInt32 ip; + UInt32 x86State; + Byte deltaState[DELTA_STATE_SIZE]; + + Byte buf[BRA_BUF_SIZE]; +} CBraState; + +void BraState_Free(void *pp, ISzAlloc *alloc) +{ + alloc->Free(alloc, pp); +} + +SRes BraState_SetProps(void *pp, const Byte *props, size_t propSize, ISzAlloc *alloc) +{ + CBraState *p = ((CBraState *)pp); + alloc = alloc; + p->ip = 0; + if (p->methodId == XZ_ID_Delta) + { + if (propSize != 1) + return SZ_ERROR_UNSUPPORTED; + p->delta = (unsigned)props[0] + 1; + } + else + { + if (propSize == 4) + { + UInt32 v = GetUi32(props); + switch(p->methodId) + { + case XZ_ID_PPC: + case XZ_ID_ARM: + case XZ_ID_SPARC: + if ((v & 3) != 0) + return SZ_ERROR_UNSUPPORTED; + break; + case XZ_ID_ARMT: + if ((v & 1) != 0) + return SZ_ERROR_UNSUPPORTED; + break; + case XZ_ID_IA64: + if ((v & 0xF) != 0) + return SZ_ERROR_UNSUPPORTED; + break; + } + p->ip = v; + } + else if (propSize != 0) + return SZ_ERROR_UNSUPPORTED; + } + return SZ_OK; +} + +void BraState_Init(void *pp) +{ + CBraState *p = ((CBraState *)pp); + p->bufPos = p->bufConv = p->bufTotal = 0; + x86_Convert_Init(p->x86State); + if (p->methodId == XZ_ID_Delta) + Delta_Init(p->deltaState); +} + +#define CASE_BRA_CONV(isa) case XZ_ID_ ## isa: p->bufConv = isa ## _Convert(p->buf, p->bufTotal, p->ip, p->encodeMode); break; + +static SRes BraState_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished) +{ + CBraState *p = ((CBraState *)pp); + SizeT destLenOrig = *destLen; + SizeT srcLenOrig = *srcLen; + *destLen = 0; + *srcLen = 0; + finishMode = finishMode; + *wasFinished = 0; + while (destLenOrig > 0) + { + if (p->bufPos != p->bufConv) + { + size_t curSize = p->bufConv - p->bufPos; + if (curSize > destLenOrig) + curSize = destLenOrig; + memcpy(dest, p->buf + p->bufPos, curSize); + p->bufPos += curSize; + *destLen += curSize; + dest += curSize; + destLenOrig -= curSize; + continue; + } + p->bufTotal -= p->bufPos; + memmove(p->buf, p->buf + p->bufPos, p->bufTotal); + p->bufPos = 0; + p->bufConv = 0; + { + size_t curSize = BRA_BUF_SIZE - p->bufTotal; + if (curSize > srcLenOrig) + curSize = srcLenOrig; + memcpy(p->buf + p->bufTotal, src, curSize); + *srcLen += curSize; + src += curSize; + srcLenOrig -= curSize; + p->bufTotal += curSize; + } + if (p->bufTotal == 0) + break; + switch(p->methodId) + { + case XZ_ID_Delta: + if (p->encodeMode) + Delta_Encode(p->deltaState, p->delta, p->buf, p->bufTotal); + else + Delta_Decode(p->deltaState, p->delta, p->buf, p->bufTotal); + p->bufConv = p->bufTotal; + break; + case XZ_ID_X86: + p->bufConv = x86_Convert(p->buf, p->bufTotal, p->ip, &p->x86State, p->encodeMode); + break; + CASE_BRA_CONV(PPC) + CASE_BRA_CONV(IA64) + CASE_BRA_CONV(ARM) + CASE_BRA_CONV(ARMT) + CASE_BRA_CONV(SPARC) + default: + return SZ_ERROR_UNSUPPORTED; + } + p->ip += (UInt32)p->bufConv; + + if (p->bufConv == 0) + { + if (!srcWasFinished) + break; + p->bufConv = p->bufTotal; + } + } + if (p->bufTotal == p->bufPos && srcLenOrig == 0 && srcWasFinished) + *wasFinished = 1; + return SZ_OK; +} + +SRes BraState_SetFromMethod(IStateCoder *p, UInt64 id, int encodeMode, ISzAlloc *alloc) +{ + CBraState *decoder; + if (id != XZ_ID_Delta && + id != XZ_ID_X86 && + id != XZ_ID_PPC && + id != XZ_ID_IA64 && + id != XZ_ID_ARM && + id != XZ_ID_ARMT && + id != XZ_ID_SPARC) + return SZ_ERROR_UNSUPPORTED; + p->p = 0; + decoder = (CBraState *)alloc->Alloc(alloc, sizeof(CBraState)); + if (decoder == 0) + return SZ_ERROR_MEM; + decoder->methodId = (UInt32)id; + decoder->encodeMode = encodeMode; + p->p = decoder; + p->Free = BraState_Free; + p->SetProps = BraState_SetProps; + p->Init = BraState_Init; + p->Code = BraState_Code; + return SZ_OK; +} + +/* ---------- SbState ---------- */ + +#ifdef USE_SUBBLOCK + +static void SbState_Free(void *pp, ISzAlloc *alloc) +{ + CSbDec *p = (CSbDec *)pp; + SbDec_Free(p); + alloc->Free(alloc, pp); +} + +static SRes SbState_SetProps(void *pp, const Byte *props, size_t propSize, ISzAlloc *alloc) +{ + pp = pp; + props = props; + alloc = alloc; + return (propSize == 0) ? SZ_OK : SZ_ERROR_UNSUPPORTED; +} + +static void SbState_Init(void *pp) +{ + SbDec_Init((CSbDec *)pp); +} + +static SRes SbState_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished) +{ + CSbDec *p = (CSbDec *)pp; + SRes res; + srcWasFinished = srcWasFinished; + p->dest = dest; + p->destLen = *destLen; + p->src = src; + p->srcLen = *srcLen; + p->finish = finishMode; /* change it */ + res = SbDec_Decode((CSbDec *)pp); + *destLen -= p->destLen; + *srcLen -= p->srcLen; + *wasFinished = (*destLen == 0 && *srcLen == 0); /* change it */ + return res; +} + +SRes SbState_SetFromMethod(IStateCoder *p, ISzAlloc *alloc) +{ + CSbDec *decoder; + p->p = 0; + decoder = alloc->Alloc(alloc, sizeof(CSbDec)); + if (decoder == 0) + return SZ_ERROR_MEM; + p->p = decoder; + p->Free = SbState_Free; + p->SetProps = SbState_SetProps; + p->Init = SbState_Init; + p->Code = SbState_Code; + SbDec_Construct(decoder); + SbDec_SetAlloc(decoder, alloc); + return SZ_OK; +} +#endif + +/* ---------- Lzma2State ---------- */ + +static void Lzma2State_Free(void *pp, ISzAlloc *alloc) +{ + Lzma2Dec_Free((CLzma2Dec *)pp, alloc); + alloc->Free(alloc, pp); +} + +static SRes Lzma2State_SetProps(void *pp, const Byte *props, size_t propSize, ISzAlloc *alloc) +{ + if (propSize != 1) + return SZ_ERROR_UNSUPPORTED; + return Lzma2Dec_Allocate((CLzma2Dec *)pp, props[0], alloc); +} + +static void Lzma2State_Init(void *pp) +{ + Lzma2Dec_Init((CLzma2Dec *)pp); +} + +static SRes Lzma2State_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished) +{ + ELzmaStatus status; + /* ELzmaFinishMode fm = (finishMode == LZMA_FINISH_ANY) ? LZMA_FINISH_ANY : LZMA_FINISH_END; */ + SRes res = Lzma2Dec_DecodeToBuf((CLzma2Dec *)pp, dest, destLen, src, srcLen, (ELzmaFinishMode)finishMode, &status); + srcWasFinished = srcWasFinished; + *wasFinished = (status == LZMA_STATUS_FINISHED_WITH_MARK); + return res; +} + +static SRes Lzma2State_SetFromMethod(IStateCoder *p, ISzAlloc *alloc) +{ + CLzma2Dec *decoder = (CLzma2Dec *)alloc->Alloc(alloc, sizeof(CLzma2Dec)); + p->p = decoder; + if (decoder == 0) + return SZ_ERROR_MEM; + p->Free = Lzma2State_Free; + p->SetProps = Lzma2State_SetProps; + p->Init = Lzma2State_Init; + p->Code = Lzma2State_Code; + Lzma2Dec_Construct(decoder); + return SZ_OK; +} + + +void MixCoder_Construct(CMixCoder *p, ISzAlloc *alloc) +{ + int i; + p->alloc = alloc; + p->buf = 0; + p->numCoders = 0; + for (i = 0; i < MIXCODER_NUM_FILTERS_MAX; i++) + p->coders[i].p = NULL; +} + +void MixCoder_Free(CMixCoder *p) +{ + int i; + for (i = 0; i < p->numCoders; i++) + { + IStateCoder *sc = &p->coders[i]; + if (p->alloc && sc->p) + sc->Free(sc->p, p->alloc); + } + p->numCoders = 0; + if (p->buf) + { + p->alloc->Free(p->alloc, p->buf); + p->buf = 0; /* 9.31: the BUG was fixed */ + } +} + +void MixCoder_Init(CMixCoder *p) +{ + int i; + for (i = 0; i < p->numCoders - 1; i++) + { + p->size[i] = 0; + p->pos[i] = 0; + p->finished[i] = 0; + } + for (i = 0; i < p->numCoders; i++) + { + IStateCoder *coder = &p->coders[i]; + coder->Init(coder->p); + } +} + +SRes MixCoder_SetFromMethod(CMixCoder *p, int coderIndex, UInt64 methodId) +{ + IStateCoder *sc = &p->coders[coderIndex]; + p->ids[coderIndex] = methodId; + switch(methodId) + { + case XZ_ID_LZMA2: return Lzma2State_SetFromMethod(sc, p->alloc); + #ifdef USE_SUBBLOCK + case XZ_ID_Subblock: return SbState_SetFromMethod(sc, p->alloc); + #endif + } + if (coderIndex == 0) + return SZ_ERROR_UNSUPPORTED; + return BraState_SetFromMethod(sc, methodId, 0, p->alloc); +} + +SRes MixCoder_Code(CMixCoder *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, int srcWasFinished, + ECoderFinishMode finishMode, ECoderStatus *status) +{ + SizeT destLenOrig = *destLen; + SizeT srcLenOrig = *srcLen; + Bool allFinished = True; + *destLen = 0; + *srcLen = 0; + *status = CODER_STATUS_NOT_FINISHED; + + if (p->buf == 0) + { + p->buf = (Byte *)p->alloc->Alloc(p->alloc, CODER_BUF_SIZE * (MIXCODER_NUM_FILTERS_MAX - 1)); + if (p->buf == 0) + return SZ_ERROR_MEM; + } + + if (p->numCoders != 1) + finishMode = CODER_FINISH_ANY; + + for (;;) + { + Bool processed = False; + int i; + /* + if (p->numCoders == 1 && *destLen == destLenOrig && finishMode == LZMA_FINISH_ANY) + break; + */ + + for (i = 0; i < p->numCoders; i++) + { + SRes res; + IStateCoder *coder = &p->coders[i]; + Byte *destCur; + SizeT destLenCur, srcLenCur; + const Byte *srcCur; + int srcFinishedCur; + int encodingWasFinished; + + if (i == 0) + { + srcCur = src; + srcLenCur = srcLenOrig - *srcLen; + srcFinishedCur = srcWasFinished; + } + else + { + srcCur = p->buf + (CODER_BUF_SIZE * (i - 1)) + p->pos[i - 1]; + srcLenCur = p->size[i - 1] - p->pos[i - 1]; + srcFinishedCur = p->finished[i - 1]; + } + + if (i == p->numCoders - 1) + { + destCur = dest; + destLenCur = destLenOrig - *destLen; + } + else + { + if (p->pos[i] != p->size[i]) + continue; + destCur = p->buf + (CODER_BUF_SIZE * i); + destLenCur = CODER_BUF_SIZE; + } + + res = coder->Code(coder->p, destCur, &destLenCur, srcCur, &srcLenCur, srcFinishedCur, finishMode, &encodingWasFinished); + + if (!encodingWasFinished) + allFinished = False; + + if (i == 0) + { + *srcLen += srcLenCur; + src += srcLenCur; + } + else + { + p->pos[i - 1] += srcLenCur; + } + + if (i == p->numCoders - 1) + { + *destLen += destLenCur; + dest += destLenCur; + } + else + { + p->size[i] = destLenCur; + p->pos[i] = 0; + p->finished[i] = encodingWasFinished; + } + + if (res != SZ_OK) + return res; + + if (destLenCur != 0 || srcLenCur != 0) + processed = True; + } + if (!processed) + break; + } + if (allFinished) + *status = CODER_STATUS_FINISHED_WITH_MARK; + return SZ_OK; +} + +SRes Xz_ParseHeader(CXzStreamFlags *p, const Byte *buf) +{ + *p = (CXzStreamFlags)GetBe16(buf + XZ_SIG_SIZE); + if (CrcCalc(buf + XZ_SIG_SIZE, XZ_STREAM_FLAGS_SIZE) != + GetUi32(buf + XZ_SIG_SIZE + XZ_STREAM_FLAGS_SIZE)) + return SZ_ERROR_NO_ARCHIVE; + return XzFlags_IsSupported(*p) ? SZ_OK : SZ_ERROR_UNSUPPORTED; +} + +static Bool Xz_CheckFooter(CXzStreamFlags flags, UInt64 indexSize, const Byte *buf) +{ + return + indexSize == (((UInt64)GetUi32(buf + 4) + 1) << 2) && + (GetUi32(buf) == CrcCalc(buf + 4, 6) && + flags == GetBe16(buf + 8) && + memcmp(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) == 0); +} + +#define READ_VARINT_AND_CHECK(buf, pos, size, res) \ + { unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \ + if (s == 0) return SZ_ERROR_ARCHIVE; pos += s; } + + +SRes XzBlock_Parse(CXzBlock *p, const Byte *header) +{ + unsigned pos; + int numFilters, i; + UInt32 headerSize = (UInt32)header[0] << 2; + + if (CrcCalc(header, headerSize) != GetUi32(header + headerSize)) + return SZ_ERROR_ARCHIVE; + + pos = 1; + if (pos == headerSize) + return SZ_ERROR_ARCHIVE; + p->flags = header[pos++]; + + if (XzBlock_HasPackSize(p)) + { + READ_VARINT_AND_CHECK(header, pos, headerSize, &p->packSize); + if (p->packSize == 0 || p->packSize + headerSize >= (UInt64)1 << 63) + return SZ_ERROR_ARCHIVE; + } + + if (XzBlock_HasUnpackSize(p)) + READ_VARINT_AND_CHECK(header, pos, headerSize, &p->unpackSize); + + numFilters = XzBlock_GetNumFilters(p); + for (i = 0; i < numFilters; i++) + { + CXzFilter *filter = p->filters + i; + UInt64 size; + READ_VARINT_AND_CHECK(header, pos, headerSize, &filter->id); + READ_VARINT_AND_CHECK(header, pos, headerSize, &size); + if (size > headerSize - pos || size > XZ_FILTER_PROPS_SIZE_MAX) + return SZ_ERROR_ARCHIVE; + filter->propsSize = (UInt32)size; + memcpy(filter->props, header + pos, (size_t)size); + pos += (unsigned)size; + + #ifdef XZ_DUMP + printf("\nf[%d] = %2X: ", i, filter->id); + { + int i; + for (i = 0; i < size; i++) + printf(" %2X", filter->props[i]); + } + #endif + } + + while (pos < headerSize) + if (header[pos++] != 0) + return SZ_ERROR_ARCHIVE; + return SZ_OK; +} + +SRes XzDec_Init(CMixCoder *p, const CXzBlock *block) +{ + int i; + Bool needReInit = True; + int numFilters = XzBlock_GetNumFilters(block); + if (numFilters == p->numCoders) + { + for (i = 0; i < numFilters; i++) + if (p->ids[i] != block->filters[numFilters - 1 - i].id) + break; + needReInit = (i != numFilters); + } + if (needReInit) + { + MixCoder_Free(p); + p->numCoders = numFilters; + for (i = 0; i < numFilters; i++) + { + const CXzFilter *f = &block->filters[numFilters - 1 - i]; + RINOK(MixCoder_SetFromMethod(p, i, f->id)); + } + } + for (i = 0; i < numFilters; i++) + { + const CXzFilter *f = &block->filters[numFilters - 1 - i]; + IStateCoder *sc = &p->coders[i]; + RINOK(sc->SetProps(sc->p, f->props, f->propsSize, p->alloc)); + } + MixCoder_Init(p); + return SZ_OK; +} + +void XzUnpacker_Init(CXzUnpacker *p) +{ + p->state = XZ_STATE_STREAM_HEADER; + p->pos = 0; + p->numStartedStreams = 0; + p->numFinishedStreams = 0; + p->numTotalBlocks = 0; + p->padSize = 0; +} + +void XzUnpacker_Construct(CXzUnpacker *p, ISzAlloc *alloc) +{ + MixCoder_Construct(&p->decoder, alloc); + XzUnpacker_Init(p); +} + +void XzUnpacker_Free(CXzUnpacker *p) +{ + MixCoder_Free(&p->decoder); +} + +SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ECoderFinishMode finishMode, ECoderStatus *status) +{ + SizeT destLenOrig = *destLen; + SizeT srcLenOrig = *srcLen; + *destLen = 0; + *srcLen = 0; + *status = CODER_STATUS_NOT_SPECIFIED; + for (;;) + { + SizeT srcRem = srcLenOrig - *srcLen; + + if (p->state == XZ_STATE_BLOCK) + { + SizeT destLen2 = destLenOrig - *destLen; + SizeT srcLen2 = srcLenOrig - *srcLen; + SRes res; + if (srcLen2 == 0 && destLen2 == 0) + { + *status = CODER_STATUS_NOT_FINISHED; + return SZ_OK; + } + + res = MixCoder_Code(&p->decoder, dest, &destLen2, src, &srcLen2, False, finishMode, status); + XzCheck_Update(&p->check, dest, destLen2); + + (*srcLen) += srcLen2; + src += srcLen2; + p->packSize += srcLen2; + + (*destLen) += destLen2; + dest += destLen2; + p->unpackSize += destLen2; + + RINOK(res); + + if (*status == CODER_STATUS_FINISHED_WITH_MARK) + { + Byte temp[32]; + unsigned num = Xz_WriteVarInt(temp, p->packSize + p->blockHeaderSize + XzFlags_GetCheckSize(p->streamFlags)); + num += Xz_WriteVarInt(temp + num, p->unpackSize); + Sha256_Update(&p->sha, temp, num); + p->indexSize += num; + p->numBlocks++; + + p->state = XZ_STATE_BLOCK_FOOTER; + p->pos = 0; + p->alignPos = 0; + } + else if (srcLen2 == 0 && destLen2 == 0) + return SZ_OK; + + continue; + } + + if (srcRem == 0) + { + *status = CODER_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + + switch (p->state) + { + case XZ_STATE_STREAM_HEADER: + { + if (p->pos < XZ_STREAM_HEADER_SIZE) + { + if (p->pos < XZ_SIG_SIZE && *src != XZ_SIG[p->pos]) + return SZ_ERROR_NO_ARCHIVE; + p->buf[p->pos++] = *src++; + (*srcLen)++; + } + else + { + RINOK(Xz_ParseHeader(&p->streamFlags, p->buf)); + p->numStartedStreams++; + p->state = XZ_STATE_BLOCK_HEADER; + Sha256_Init(&p->sha); + p->indexSize = 0; + p->numBlocks = 0; + p->pos = 0; + } + break; + } + + case XZ_STATE_BLOCK_HEADER: + { + if (p->pos == 0) + { + p->buf[p->pos++] = *src++; + (*srcLen)++; + if (p->buf[0] == 0) + { + p->indexPreSize = 1 + Xz_WriteVarInt(p->buf + 1, p->numBlocks); + p->indexPos = p->indexPreSize; + p->indexSize += p->indexPreSize; + Sha256_Final(&p->sha, p->shaDigest); + Sha256_Init(&p->sha); + p->crc = CrcUpdate(CRC_INIT_VAL, p->buf, p->indexPreSize); + p->state = XZ_STATE_STREAM_INDEX; + } + p->blockHeaderSize = ((UInt32)p->buf[0] << 2) + 4; + } + else if (p->pos != p->blockHeaderSize) + { + UInt32 cur = p->blockHeaderSize - p->pos; + if (cur > srcRem) + cur = (UInt32)srcRem; + memcpy(p->buf + p->pos, src, cur); + p->pos += cur; + (*srcLen) += cur; + src += cur; + } + else + { + RINOK(XzBlock_Parse(&p->block, p->buf)); + p->numTotalBlocks++; + p->state = XZ_STATE_BLOCK; + p->packSize = 0; + p->unpackSize = 0; + XzCheck_Init(&p->check, XzFlags_GetCheckType(p->streamFlags)); + RINOK(XzDec_Init(&p->decoder, &p->block)); + } + break; + } + + case XZ_STATE_BLOCK_FOOTER: + { + if (((p->packSize + p->alignPos) & 3) != 0) + { + (*srcLen)++; + p->alignPos++; + if (*src++ != 0) + return SZ_ERROR_CRC; + } + else + { + UInt32 checkSize = XzFlags_GetCheckSize(p->streamFlags); + UInt32 cur = checkSize - p->pos; + if (cur != 0) + { + if (cur > srcRem) + cur = (UInt32)srcRem; + memcpy(p->buf + p->pos, src, cur); + p->pos += cur; + (*srcLen) += cur; + src += cur; + } + else + { + Byte digest[XZ_CHECK_SIZE_MAX]; + p->state = XZ_STATE_BLOCK_HEADER; + p->pos = 0; + if (XzCheck_Final(&p->check, digest) && memcmp(digest, p->buf, checkSize) != 0) + return SZ_ERROR_CRC; + } + } + break; + } + + case XZ_STATE_STREAM_INDEX: + { + if (p->pos < p->indexPreSize) + { + (*srcLen)++; + if (*src++ != p->buf[p->pos++]) + return SZ_ERROR_CRC; + } + else + { + if (p->indexPos < p->indexSize) + { + UInt64 cur = p->indexSize - p->indexPos; + if (srcRem > cur) + srcRem = (SizeT)cur; + p->crc = CrcUpdate(p->crc, src, srcRem); + Sha256_Update(&p->sha, src, srcRem); + (*srcLen) += srcRem; + src += srcRem; + p->indexPos += srcRem; + } + else if ((p->indexPos & 3) != 0) + { + Byte b = *src++; + p->crc = CRC_UPDATE_BYTE(p->crc, b); + (*srcLen)++; + p->indexPos++; + p->indexSize++; + if (b != 0) + return SZ_ERROR_CRC; + } + else + { + Byte digest[SHA256_DIGEST_SIZE]; + p->state = XZ_STATE_STREAM_INDEX_CRC; + p->indexSize += 4; + p->pos = 0; + Sha256_Final(&p->sha, digest); + if (memcmp(digest, p->shaDigest, SHA256_DIGEST_SIZE) != 0) + return SZ_ERROR_CRC; + } + } + break; + } + + case XZ_STATE_STREAM_INDEX_CRC: + { + if (p->pos < 4) + { + (*srcLen)++; + p->buf[p->pos++] = *src++; + } + else + { + p->state = XZ_STATE_STREAM_FOOTER; + p->pos = 0; + if (CRC_GET_DIGEST(p->crc) != GetUi32(p->buf)) + return SZ_ERROR_CRC; + } + break; + } + + case XZ_STATE_STREAM_FOOTER: + { + UInt32 cur = XZ_STREAM_FOOTER_SIZE - p->pos; + if (cur > srcRem) + cur = (UInt32)srcRem; + memcpy(p->buf + p->pos, src, cur); + p->pos += cur; + (*srcLen) += cur; + src += cur; + if (p->pos == XZ_STREAM_FOOTER_SIZE) + { + p->state = XZ_STATE_STREAM_PADDING; + p->numFinishedStreams++; + p->padSize = 0; + if (!Xz_CheckFooter(p->streamFlags, p->indexSize, p->buf)) + return SZ_ERROR_CRC; + } + break; + } + + case XZ_STATE_STREAM_PADDING: + { + if (*src != 0) + { + if (((UInt32)p->padSize & 3) != 0) + return SZ_ERROR_NO_ARCHIVE; + p->pos = 0; + p->state = XZ_STATE_STREAM_HEADER; + } + else + { + (*srcLen)++; + src++; + p->padSize++; + } + break; + } + + case XZ_STATE_BLOCK: break; /* to disable GCC warning */ + } + } + /* + if (p->state == XZ_STATE_FINISHED) + *status = CODER_STATUS_FINISHED_WITH_MARK; + return SZ_OK; + */ +} + +Bool XzUnpacker_IsStreamWasFinished(CXzUnpacker *p) +{ + return (p->state == XZ_STATE_STREAM_PADDING) && (((UInt32)p->padSize & 3) == 0); +} + +UInt64 XzUnpacker_GetExtraSize(CXzUnpacker *p) +{ + UInt64 num = 0; + if (p->state == XZ_STATE_STREAM_PADDING) + num += p->padSize; + else if (p->state == XZ_STATE_STREAM_HEADER) + num += p->padSize + p->pos; + return num; +}
@@ -0,0 +1,522 @@
+/* XzEnc.c -- Xz Encode +2014-12-30 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <stdlib.h> +#include <string.h> + +#include "7zCrc.h" +#include "Alloc.h" +#include "Bra.h" +#include "CpuArch.h" +#ifdef USE_SUBBLOCK +#include "Bcj3Enc.c" +#include "SbFind.c" +#include "SbEnc.c" +#endif + +#include "XzEnc.h" + +static void *SzBigAlloc(void *p, size_t size) { p = p; return BigAlloc(size); } +static void SzBigFree(void *p, void *address) { p = p; BigFree(address); } +static ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree }; + +static void *SzAlloc(void *p, size_t size) { p = p; return MyAlloc(size); } +static void SzFree(void *p, void *address) { p = p; MyFree(address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +#define XzBlock_ClearFlags(p) (p)->flags = 0; +#define XzBlock_SetNumFilters(p, n) (p)->flags |= ((n) - 1); +#define XzBlock_SetHasPackSize(p) (p)->flags |= XZ_BF_PACK_SIZE; +#define XzBlock_SetHasUnpackSize(p) (p)->flags |= XZ_BF_UNPACK_SIZE; + +static SRes WriteBytes(ISeqOutStream *s, const void *buf, UInt32 size) +{ + return (s->Write(s, buf, size) == size) ? SZ_OK : SZ_ERROR_WRITE; +} + +static SRes WriteBytesAndCrc(ISeqOutStream *s, const void *buf, UInt32 size, UInt32 *crc) +{ + *crc = CrcUpdate(*crc, buf, size); + return WriteBytes(s, buf, size); +} + +SRes Xz_WriteHeader(CXzStreamFlags f, ISeqOutStream *s) +{ + UInt32 crc; + Byte header[XZ_STREAM_HEADER_SIZE]; + memcpy(header, XZ_SIG, XZ_SIG_SIZE); + header[XZ_SIG_SIZE] = (Byte)(f >> 8); + header[XZ_SIG_SIZE + 1] = (Byte)(f & 0xFF); + crc = CrcCalc(header + XZ_SIG_SIZE, XZ_STREAM_FLAGS_SIZE); + SetUi32(header + XZ_SIG_SIZE + XZ_STREAM_FLAGS_SIZE, crc); + return WriteBytes(s, header, XZ_STREAM_HEADER_SIZE); +} + +SRes XzBlock_WriteHeader(const CXzBlock *p, ISeqOutStream *s) +{ + Byte header[XZ_BLOCK_HEADER_SIZE_MAX]; + + unsigned pos = 1; + int numFilters, i; + header[pos++] = p->flags; + + if (XzBlock_HasPackSize(p)) pos += Xz_WriteVarInt(header + pos, p->packSize); + if (XzBlock_HasUnpackSize(p)) pos += Xz_WriteVarInt(header + pos, p->unpackSize); + numFilters = XzBlock_GetNumFilters(p); + for (i = 0; i < numFilters; i++) + { + const CXzFilter *f = &p->filters[i]; + pos += Xz_WriteVarInt(header + pos, f->id); + pos += Xz_WriteVarInt(header + pos, f->propsSize); + memcpy(header + pos, f->props, f->propsSize); + pos += f->propsSize; + } + while((pos & 3) != 0) + header[pos++] = 0; + header[0] = (Byte)(pos >> 2); + SetUi32(header + pos, CrcCalc(header, pos)); + return WriteBytes(s, header, pos + 4); +} + +SRes Xz_WriteFooter(CXzStream *p, ISeqOutStream *s) +{ + Byte buf[32]; + UInt64 globalPos; + { + UInt32 crc = CRC_INIT_VAL; + unsigned pos = 1 + Xz_WriteVarInt(buf + 1, p->numBlocks); + size_t i; + + globalPos = pos; + buf[0] = 0; + RINOK(WriteBytesAndCrc(s, buf, pos, &crc)); + for (i = 0; i < p->numBlocks; i++) + { + const CXzBlockSizes *block = &p->blocks[i]; + pos = Xz_WriteVarInt(buf, block->totalSize); + pos += Xz_WriteVarInt(buf + pos, block->unpackSize); + globalPos += pos; + RINOK(WriteBytesAndCrc(s, buf, pos, &crc)); + } + pos = ((unsigned)globalPos & 3); + if (pos != 0) + { + buf[0] = buf[1] = buf[2] = 0; + RINOK(WriteBytesAndCrc(s, buf, 4 - pos, &crc)); + globalPos += 4 - pos; + } + { + SetUi32(buf, CRC_GET_DIGEST(crc)); + RINOK(WriteBytes(s, buf, 4)); + globalPos += 4; + } + } + + { + UInt32 indexSize = (UInt32)((globalPos >> 2) - 1); + SetUi32(buf + 4, indexSize); + buf[8] = (Byte)(p->flags >> 8); + buf[9] = (Byte)(p->flags & 0xFF); + SetUi32(buf, CrcCalc(buf + 4, 6)); + memcpy(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE); + return WriteBytes(s, buf, 12); + } +} + +SRes Xz_AddIndexRecord(CXzStream *p, UInt64 unpackSize, UInt64 totalSize, ISzAlloc *alloc) +{ + if (p->blocks == 0 || p->numBlocksAllocated == p->numBlocks) + { + size_t num = (p->numBlocks + 1) * 2; + size_t newSize = sizeof(CXzBlockSizes) * num; + CXzBlockSizes *blocks; + if (newSize / sizeof(CXzBlockSizes) != num) + return SZ_ERROR_MEM; + blocks = (CXzBlockSizes *)alloc->Alloc(alloc, newSize); + if (blocks == 0) + return SZ_ERROR_MEM; + if (p->numBlocks != 0) + { + memcpy(blocks, p->blocks, p->numBlocks * sizeof(CXzBlockSizes)); + Xz_Free(p, alloc); + } + p->blocks = blocks; + p->numBlocksAllocated = num; + } + { + CXzBlockSizes *block = &p->blocks[p->numBlocks++]; + block->totalSize = totalSize; + block->unpackSize = unpackSize; + } + return SZ_OK; +} + +/* ---------- CSeqCheckInStream ---------- */ + +typedef struct +{ + ISeqInStream p; + ISeqInStream *realStream; + UInt64 processed; + CXzCheck check; +} CSeqCheckInStream; + +void SeqCheckInStream_Init(CSeqCheckInStream *p, int mode) +{ + p->processed = 0; + XzCheck_Init(&p->check, mode); +} + +void SeqCheckInStream_GetDigest(CSeqCheckInStream *p, Byte *digest) +{ + XzCheck_Final(&p->check, digest); +} + +static SRes SeqCheckInStream_Read(void *pp, void *data, size_t *size) +{ + CSeqCheckInStream *p = (CSeqCheckInStream *)pp; + SRes res = p->realStream->Read(p->realStream, data, size); + XzCheck_Update(&p->check, data, *size); + p->processed += *size; + return res; +} + +/* ---------- CSeqSizeOutStream ---------- */ + +typedef struct +{ + ISeqOutStream p; + ISeqOutStream *realStream; + UInt64 processed; +} CSeqSizeOutStream; + +static size_t MyWrite(void *pp, const void *data, size_t size) +{ + CSeqSizeOutStream *p = (CSeqSizeOutStream *)pp; + size = p->realStream->Write(p->realStream, data, size); + p->processed += size; + return size; +} + +/* ---------- CSeqInFilter ---------- */ + +#define FILTER_BUF_SIZE (1 << 20) + +typedef struct +{ + ISeqInStream p; + ISeqInStream *realStream; + IStateCoder StateCoder; + Byte *buf; + size_t curPos; + size_t endPos; + int srcWasFinished; +} CSeqInFilter; + +static SRes SeqInFilter_Read(void *pp, void *data, size_t *size) +{ + CSeqInFilter *p = (CSeqInFilter *)pp; + size_t sizeOriginal = *size; + if (sizeOriginal == 0) + return SZ_OK; + *size = 0; + for (;;) + { + if (!p->srcWasFinished && p->curPos == p->endPos) + { + p->curPos = 0; + p->endPos = FILTER_BUF_SIZE; + RINOK(p->realStream->Read(p->realStream, p->buf, &p->endPos)); + if (p->endPos == 0) + p->srcWasFinished = 1; + } + { + SizeT srcLen = p->endPos - p->curPos; + int wasFinished; + SRes res; + *size = sizeOriginal; + res = p->StateCoder.Code(p->StateCoder.p, data, size, p->buf + p->curPos, &srcLen, + p->srcWasFinished, CODER_FINISH_ANY, &wasFinished); + p->curPos += srcLen; + if (*size != 0 || srcLen == 0 || res != 0) + return res; + } + } +} + +static void SeqInFilter_Construct(CSeqInFilter *p) +{ + p->buf = NULL; + p->p.Read = SeqInFilter_Read; +} + +static void SeqInFilter_Free(CSeqInFilter *p) +{ + if (p->buf) + { + g_Alloc.Free(&g_Alloc, p->buf); + p->buf = NULL; + } +} + +SRes BraState_SetFromMethod(IStateCoder *p, UInt64 id, int encodeMode, ISzAlloc *alloc); + +static SRes SeqInFilter_Init(CSeqInFilter *p, const CXzFilter *props) +{ + if (!p->buf) + { + p->buf = g_Alloc.Alloc(&g_Alloc, FILTER_BUF_SIZE); + if (!p->buf) + return SZ_ERROR_MEM; + } + p->curPos = p->endPos = 0; + p->srcWasFinished = 0; + RINOK(BraState_SetFromMethod(&p->StateCoder, props->id, 1, &g_Alloc)); + RINOK(p->StateCoder.SetProps(p->StateCoder.p, props->props, props->propsSize, &g_Alloc)); + p->StateCoder.Init(p->StateCoder.p); + return SZ_OK; +} + +/* ---------- CSbEncInStream ---------- */ + +#ifdef USE_SUBBLOCK + +typedef struct +{ + ISeqInStream p; + ISeqInStream *inStream; + CSbEnc enc; +} CSbEncInStream; + +static SRes SbEncInStream_Read(void *pp, void *data, size_t *size) +{ + CSbEncInStream *p = (CSbEncInStream *)pp; + size_t sizeOriginal = *size; + if (sizeOriginal == 0) + return S_OK; + for (;;) + { + if (p->enc.needRead && !p->enc.readWasFinished) + { + size_t processed = p->enc.needReadSizeMax; + RINOK(p->inStream->Read(p->inStream, p->enc.buf + p->enc.readPos, &processed)); + p->enc.readPos += processed; + if (processed == 0) + { + p->enc.readWasFinished = True; + p->enc.isFinalFinished = True; + } + p->enc.needRead = False; + } + *size = sizeOriginal; + RINOK(SbEnc_Read(&p->enc, data, size)); + if (*size != 0 || !p->enc.needRead) + return S_OK; + } +} + +void SbEncInStream_Construct(CSbEncInStream *p, ISzAlloc *alloc) +{ + SbEnc_Construct(&p->enc, alloc); + p->p.Read = SbEncInStream_Read; +} + +SRes SbEncInStream_Init(CSbEncInStream *p) +{ + return SbEnc_Init(&p->enc); +} + +void SbEncInStream_Free(CSbEncInStream *p) +{ + SbEnc_Free(&p->enc); +} + +#endif + + +typedef struct +{ + CLzma2EncHandle lzma2; + #ifdef USE_SUBBLOCK + CSbEncInStream sb; + #endif + CSeqInFilter filter; + ISzAlloc *alloc; + ISzAlloc *bigAlloc; +} CLzma2WithFilters; + + +static void Lzma2WithFilters_Construct(CLzma2WithFilters *p, ISzAlloc *alloc, ISzAlloc *bigAlloc) +{ + p->alloc = alloc; + p->bigAlloc = bigAlloc; + p->lzma2 = NULL; + #ifdef USE_SUBBLOCK + SbEncInStream_Construct(&p->sb, alloc); + #endif + SeqInFilter_Construct(&p->filter); +} + +static SRes Lzma2WithFilters_Create(CLzma2WithFilters *p) +{ + p->lzma2 = Lzma2Enc_Create(p->alloc, p->bigAlloc); + if (p->lzma2 == 0) + return SZ_ERROR_MEM; + return SZ_OK; +} + +static void Lzma2WithFilters_Free(CLzma2WithFilters *p) +{ + SeqInFilter_Free(&p->filter); + #ifdef USE_SUBBLOCK + SbEncInStream_Free(&p->sb); + #endif + if (p->lzma2) + { + Lzma2Enc_Destroy(p->lzma2); + p->lzma2 = NULL; + } +} + +void XzProps_Init(CXzProps *p) +{ + p->lzma2Props = 0; + p->filterProps = 0; + p->checkId = XZ_CHECK_CRC32; +} + +void XzFilterProps_Init(CXzFilterProps *p) +{ + p->id = 0; + p->delta = 0; + p->ip= 0; + p->ipDefined = False; +} + +static SRes Xz_Compress(CXzStream *xz, CLzma2WithFilters *lzmaf, + ISeqOutStream *outStream, ISeqInStream *inStream, + const CXzProps *props, ICompressProgress *progress) +{ + xz->flags = (Byte)props->checkId; + + RINOK(Lzma2Enc_SetProps(lzmaf->lzma2, props->lzma2Props)); + RINOK(Xz_WriteHeader(xz->flags, outStream)); + + { + CSeqCheckInStream checkInStream; + CSeqSizeOutStream seqSizeOutStream; + CXzBlock block; + int filterIndex = 0; + CXzFilter *filter = NULL; + const CXzFilterProps *fp = props->filterProps; + + XzBlock_ClearFlags(&block); + XzBlock_SetNumFilters(&block, 1 + (fp ? 1 : 0)); + + if (fp) + { + filter = &block.filters[filterIndex++]; + filter->id = fp->id; + filter->propsSize = 0; + if (fp->id == XZ_ID_Delta) + { + filter->props[0] = (Byte)(fp->delta - 1); + filter->propsSize = 1; + } + else if (fp->ipDefined) + { + SetUi32(filter->props, fp->ip); + filter->propsSize = 4; + } + } + + { + CXzFilter *f = &block.filters[filterIndex++]; + f->id = XZ_ID_LZMA2; + f->propsSize = 1; + f->props[0] = Lzma2Enc_WriteProperties(lzmaf->lzma2); + } + + seqSizeOutStream.p.Write = MyWrite; + seqSizeOutStream.realStream = outStream; + seqSizeOutStream.processed = 0; + + RINOK(XzBlock_WriteHeader(&block, &seqSizeOutStream.p)); + + checkInStream.p.Read = SeqCheckInStream_Read; + checkInStream.realStream = inStream; + SeqCheckInStream_Init(&checkInStream, XzFlags_GetCheckType(xz->flags)); + + if (fp) + { + #ifdef USE_SUBBLOCK + if (fp->id == XZ_ID_Subblock) + { + lzmaf->sb.inStream = &checkInStream.p; + RINOK(SbEncInStream_Init(&lzmaf->sb)); + } + else + #endif + { + lzmaf->filter.realStream = &checkInStream.p; + RINOK(SeqInFilter_Init(&lzmaf->filter, filter)); + } + } + + { + UInt64 packPos = seqSizeOutStream.processed; + SRes res = Lzma2Enc_Encode(lzmaf->lzma2, &seqSizeOutStream.p, + fp ? + #ifdef USE_SUBBLOCK + (fp->id == XZ_ID_Subblock) ? &lzmaf->sb.p: + #endif + &lzmaf->filter.p: + &checkInStream.p, + progress); + RINOK(res); + block.unpackSize = checkInStream.processed; + block.packSize = seqSizeOutStream.processed - packPos; + } + + { + unsigned padSize = 0; + Byte buf[128]; + while((((unsigned)block.packSize + padSize) & 3) != 0) + buf[padSize++] = 0; + SeqCheckInStream_GetDigest(&checkInStream, buf + padSize); + RINOK(WriteBytes(&seqSizeOutStream.p, buf, padSize + XzFlags_GetCheckSize(xz->flags))); + RINOK(Xz_AddIndexRecord(xz, block.unpackSize, seqSizeOutStream.processed - padSize, &g_Alloc)); + } + } + return Xz_WriteFooter(xz, outStream); +} + +SRes Xz_Encode(ISeqOutStream *outStream, ISeqInStream *inStream, + const CXzProps *props, ICompressProgress *progress) +{ + SRes res; + CXzStream xz; + CLzma2WithFilters lzmaf; + Xz_Construct(&xz); + Lzma2WithFilters_Construct(&lzmaf, &g_Alloc, &g_BigAlloc); + res = Lzma2WithFilters_Create(&lzmaf); + if (res == SZ_OK) + res = Xz_Compress(&xz, &lzmaf, outStream, inStream, props, progress); + Lzma2WithFilters_Free(&lzmaf); + Xz_Free(&xz, &g_Alloc); + return res; +} + +SRes Xz_EncodeEmpty(ISeqOutStream *outStream) +{ + SRes res; + CXzStream xz; + Xz_Construct(&xz); + res = Xz_WriteHeader(xz.flags, outStream); + if (res == SZ_OK) + res = Xz_WriteFooter(&xz, outStream); + Xz_Free(&xz, &g_Alloc); + return res; +}
@@ -0,0 +1,39 @@
+/* XzEnc.h -- Xz Encode +2011-02-07 : Igor Pavlov : Public domain */ + +#ifndef __XZ_ENC_H +#define __XZ_ENC_H + +#include "Lzma2Enc.h" + +#include "Xz.h" + +EXTERN_C_BEGIN + +typedef struct +{ + UInt32 id; + UInt32 delta; + UInt32 ip; + int ipDefined; +} CXzFilterProps; + +void XzFilterProps_Init(CXzFilterProps *p); + +typedef struct +{ + const CLzma2EncProps *lzma2Props; + const CXzFilterProps *filterProps; + unsigned checkId; +} CXzProps; + +void XzProps_Init(CXzProps *p); + +SRes Xz_Encode(ISeqOutStream *outStream, ISeqInStream *inStream, + const CXzProps *props, ICompressProgress *progress); + +SRes Xz_EncodeEmpty(ISeqOutStream *outStream); + +EXTERN_C_END + +#endif
@@ -0,0 +1,307 @@
+/* XzIn.c - Xz input +2014-12-30 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include <string.h> + +#include "7zCrc.h" +#include "CpuArch.h" +#include "Xz.h" + +SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStream *inStream) +{ + Byte sig[XZ_STREAM_HEADER_SIZE]; + RINOK(SeqInStream_Read2(inStream, sig, XZ_STREAM_HEADER_SIZE, SZ_ERROR_NO_ARCHIVE)); + if (memcmp(sig, XZ_SIG, XZ_SIG_SIZE) != 0) + return SZ_ERROR_NO_ARCHIVE; + return Xz_ParseHeader(p, sig); +} + +#define READ_VARINT_AND_CHECK(buf, pos, size, res) \ + { unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \ + if (s == 0) return SZ_ERROR_ARCHIVE; pos += s; } + +SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, Bool *isIndex, UInt32 *headerSizeRes) +{ + Byte header[XZ_BLOCK_HEADER_SIZE_MAX]; + unsigned headerSize; + *headerSizeRes = 0; + RINOK(SeqInStream_ReadByte(inStream, &header[0])); + headerSize = ((unsigned)header[0] << 2) + 4; + if (headerSize == 0) + { + *headerSizeRes = 1; + *isIndex = True; + return SZ_OK; + } + + *isIndex = False; + *headerSizeRes = headerSize; + RINOK(SeqInStream_Read(inStream, header + 1, headerSize - 1)); + return XzBlock_Parse(p, header); +} + +#define ADD_SIZE_CHECH(size, val) \ + { UInt64 newSize = size + (val); if (newSize < size) return XZ_SIZE_OVERFLOW; size = newSize; } + +UInt64 Xz_GetUnpackSize(const CXzStream *p) +{ + UInt64 size = 0; + size_t i; + for (i = 0; i < p->numBlocks; i++) + ADD_SIZE_CHECH(size, p->blocks[i].unpackSize); + return size; +} + +UInt64 Xz_GetPackSize(const CXzStream *p) +{ + UInt64 size = 0; + size_t i; + for (i = 0; i < p->numBlocks; i++) + ADD_SIZE_CHECH(size, (p->blocks[i].totalSize + 3) & ~(UInt64)3); + return size; +} + +/* +SRes XzBlock_ReadFooter(CXzBlock *p, CXzStreamFlags f, ISeqInStream *inStream) +{ + return SeqInStream_Read(inStream, p->check, XzFlags_GetCheckSize(f)); +} +*/ + +static SRes Xz_ReadIndex2(CXzStream *p, const Byte *buf, size_t size, ISzAlloc *alloc) +{ + size_t i, numBlocks, pos = 1; + UInt32 crc; + + if (size < 5 || buf[0] != 0) + return SZ_ERROR_ARCHIVE; + + size -= 4; + crc = CrcCalc(buf, size); + if (crc != GetUi32(buf + size)) + return SZ_ERROR_ARCHIVE; + + { + UInt64 numBlocks64; + READ_VARINT_AND_CHECK(buf, pos, size, &numBlocks64); + numBlocks = (size_t)numBlocks64; + if (numBlocks != numBlocks64 || numBlocks * 2 > size) + return SZ_ERROR_ARCHIVE; + } + + Xz_Free(p, alloc); + if (numBlocks != 0) + { + p->numBlocks = numBlocks; + p->numBlocksAllocated = numBlocks; + p->blocks = alloc->Alloc(alloc, sizeof(CXzBlockSizes) * numBlocks); + if (p->blocks == 0) + return SZ_ERROR_MEM; + for (i = 0; i < numBlocks; i++) + { + CXzBlockSizes *block = &p->blocks[i]; + READ_VARINT_AND_CHECK(buf, pos, size, &block->totalSize); + READ_VARINT_AND_CHECK(buf, pos, size, &block->unpackSize); + if (block->totalSize == 0) + return SZ_ERROR_ARCHIVE; + } + } + while ((pos & 3) != 0) + if (buf[pos++] != 0) + return SZ_ERROR_ARCHIVE; + return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE; +} + +static SRes Xz_ReadIndex(CXzStream *p, ILookInStream *stream, UInt64 indexSize, ISzAlloc *alloc) +{ + SRes res; + size_t size; + Byte *buf; + if (indexSize > ((UInt32)1 << 31)) + return SZ_ERROR_UNSUPPORTED; + size = (size_t)indexSize; + if (size != indexSize) + return SZ_ERROR_UNSUPPORTED; + buf = alloc->Alloc(alloc, size); + if (buf == 0) + return SZ_ERROR_MEM; + res = LookInStream_Read2(stream, buf, size, SZ_ERROR_UNSUPPORTED); + if (res == SZ_OK) + res = Xz_ReadIndex2(p, buf, size, alloc); + alloc->Free(alloc, buf); + return res; +} + +static SRes SeekFromCur(ILookInStream *inStream, Int64 *res) +{ + return inStream->Seek(inStream, res, SZ_SEEK_CUR); +} + +static SRes Xz_ReadBackward(CXzStream *p, ILookInStream *stream, Int64 *startOffset, ISzAlloc *alloc) +{ + UInt64 indexSize; + Byte buf[XZ_STREAM_FOOTER_SIZE]; + + if ((*startOffset & 3) != 0 || *startOffset < XZ_STREAM_FOOTER_SIZE) + return SZ_ERROR_NO_ARCHIVE; + *startOffset = -XZ_STREAM_FOOTER_SIZE; + RINOK(SeekFromCur(stream, startOffset)); + + RINOK(LookInStream_Read2(stream, buf, XZ_STREAM_FOOTER_SIZE, SZ_ERROR_NO_ARCHIVE)); + + if (memcmp(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) != 0) + { + UInt32 total = 0; + *startOffset += XZ_STREAM_FOOTER_SIZE; + for (;;) + { + size_t i; + #define TEMP_BUF_SIZE (1 << 10) + Byte tempBuf[TEMP_BUF_SIZE]; + if (*startOffset < XZ_STREAM_FOOTER_SIZE || total > (1 << 16)) + return SZ_ERROR_NO_ARCHIVE; + i = (*startOffset > TEMP_BUF_SIZE) ? TEMP_BUF_SIZE : (size_t)*startOffset; + total += (UInt32)i; + *startOffset = -(Int64)i; + RINOK(SeekFromCur(stream, startOffset)); + RINOK(LookInStream_Read2(stream, tempBuf, i, SZ_ERROR_NO_ARCHIVE)); + for (; i != 0; i--) + if (tempBuf[i - 1] != 0) + break; + if (i != 0) + { + if ((i & 3) != 0) + return SZ_ERROR_NO_ARCHIVE; + *startOffset += i; + break; + } + } + if (*startOffset < XZ_STREAM_FOOTER_SIZE) + return SZ_ERROR_NO_ARCHIVE; + *startOffset -= XZ_STREAM_FOOTER_SIZE; + RINOK(stream->Seek(stream, startOffset, SZ_SEEK_SET)); + RINOK(LookInStream_Read2(stream, buf, XZ_STREAM_FOOTER_SIZE, SZ_ERROR_NO_ARCHIVE)); + if (memcmp(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) != 0) + return SZ_ERROR_NO_ARCHIVE; + } + + p->flags = (CXzStreamFlags)GetBe16(buf + 8); + + if (!XzFlags_IsSupported(p->flags)) + return SZ_ERROR_UNSUPPORTED; + + if (GetUi32(buf) != CrcCalc(buf + 4, 6)) + return SZ_ERROR_ARCHIVE; + + indexSize = ((UInt64)GetUi32(buf + 4) + 1) << 2; + + *startOffset = -(Int64)(indexSize + XZ_STREAM_FOOTER_SIZE); + RINOK(SeekFromCur(stream, startOffset)); + + RINOK(Xz_ReadIndex(p, stream, indexSize, alloc)); + + { + UInt64 totalSize = Xz_GetPackSize(p); + UInt64 sum = XZ_STREAM_HEADER_SIZE + totalSize + indexSize; + if (totalSize == XZ_SIZE_OVERFLOW || + sum >= ((UInt64)1 << 63) || + totalSize >= ((UInt64)1 << 63)) + return SZ_ERROR_ARCHIVE; + *startOffset = -(Int64)sum; + RINOK(SeekFromCur(stream, startOffset)); + } + { + CXzStreamFlags headerFlags; + CSecToRead secToRead; + SecToRead_CreateVTable(&secToRead); + secToRead.realStream = stream; + + RINOK(Xz_ReadHeader(&headerFlags, &secToRead.s)); + return (p->flags == headerFlags) ? SZ_OK : SZ_ERROR_ARCHIVE; + } +} + + +/* ---------- Xz Streams ---------- */ + +void Xzs_Construct(CXzs *p) +{ + p->num = p->numAllocated = 0; + p->streams = 0; +} + +void Xzs_Free(CXzs *p, ISzAlloc *alloc) +{ + size_t i; + for (i = 0; i < p->num; i++) + Xz_Free(&p->streams[i], alloc); + alloc->Free(alloc, p->streams); + p->num = p->numAllocated = 0; + p->streams = 0; +} + +UInt64 Xzs_GetNumBlocks(const CXzs *p) +{ + UInt64 num = 0; + size_t i; + for (i = 0; i < p->num; i++) + num += p->streams[i].numBlocks; + return num; +} + +UInt64 Xzs_GetUnpackSize(const CXzs *p) +{ + UInt64 size = 0; + size_t i; + for (i = 0; i < p->num; i++) + ADD_SIZE_CHECH(size, Xz_GetUnpackSize(&p->streams[i])); + return size; +} + +/* +UInt64 Xzs_GetPackSize(const CXzs *p) +{ + UInt64 size = 0; + size_t i; + for (i = 0; i < p->num; i++) + ADD_SIZE_CHECH(size, Xz_GetTotalSize(&p->streams[i])); + return size; +} +*/ + +SRes Xzs_ReadBackward(CXzs *p, ILookInStream *stream, Int64 *startOffset, ICompressProgress *progress, ISzAlloc *alloc) +{ + Int64 endOffset = 0; + RINOK(stream->Seek(stream, &endOffset, SZ_SEEK_END)); + *startOffset = endOffset; + for (;;) + { + CXzStream st; + SRes res; + Xz_Construct(&st); + res = Xz_ReadBackward(&st, stream, startOffset, alloc); + st.startOffset = *startOffset; + RINOK(res); + if (p->num == p->numAllocated) + { + size_t newNum = p->num + p->num / 4 + 1; + Byte *data = (Byte *)alloc->Alloc(alloc, newNum * sizeof(CXzStream)); + if (data == 0) + return SZ_ERROR_MEM; + p->numAllocated = newNum; + if (p->num != 0) + memcpy(data, p->streams, p->num * sizeof(CXzStream)); + alloc->Free(alloc, p->streams); + p->streams = (CXzStream *)data; + } + p->streams[p->num++] = st; + if (*startOffset == 0) + break; + RINOK(stream->Seek(stream, startOffset, SZ_SEEK_SET)); + if (progress && progress->Progress(progress, endOffset - *startOffset, (UInt64)(Int64)-1) != SZ_OK) + return SZ_ERROR_PROGRESS; + } + return SZ_OK; +}
@@ -0,0 +1,18 @@
+/* Copyright (c) 2013-2015 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 ARM_ALGO_H +#define ARM_ALGO_H + +#ifdef __arm__ +void _to16Bit(uint16_t* dest, uint32_t* src, size_t words); + +#if defined(__ARM_NEON) +void _neon2x(void* dest, void* src, int width, int height); +void _neon4x(void* dest, void* src, int width, int height); +#endif +#endif + +#endif
@@ -97,6 +97,34 @@ #endif
return 4; } +int CircleBufferWrite16(struct CircleBuffer* buffer, int16_t value) { + int16_t* data = buffer->writePtr; + if (buffer->size + sizeof(int16_t) > buffer->capacity) { + return 0; + } + if ((intptr_t) data & 0x3) { + int written = 0; + written += CircleBufferWrite8(buffer, ((int8_t*) &value)[0]); + written += CircleBufferWrite8(buffer, ((int8_t*) &value)[1]); + return written; + } + *data = value; + ++data; + size_t size = (int8_t*) data - (int8_t*) buffer->data; + if (size < buffer->capacity) { + buffer->writePtr = data; + } else { + buffer->writePtr = buffer->data; + } + buffer->size += sizeof(int16_t); +#ifndef NDEBUG + if (!_checkIntegrity(buffer)) { + abort(); + } +#endif + return 2; +} + int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value) { int8_t* data = buffer->readPtr; if (buffer->size < sizeof(int8_t)) {@@ -117,6 +145,34 @@ abort();
} #endif return 1; +} + +int CircleBufferRead16(struct CircleBuffer* buffer, int16_t* value) { + int16_t* data = buffer->readPtr; + if (buffer->size < sizeof(int16_t)) { + return 0; + } + if ((intptr_t) data & 0x3) { + int read = 0; + read += CircleBufferRead8(buffer, &((int8_t*) value)[0]); + read += CircleBufferRead8(buffer, &((int8_t*) value)[1]); + return read; + } + *value = *data; + ++data; + size_t size = (int8_t*) data - (int8_t*) buffer->data; + if (size < buffer->capacity) { + buffer->readPtr = data; + } else { + buffer->readPtr = buffer->data; + } + buffer->size -= sizeof(int16_t); +#ifndef NDEBUG + if (!_checkIntegrity(buffer)) { + abort(); + } +#endif + return 2; } int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value) {
@@ -22,8 +22,10 @@ size_t CircleBufferSize(const struct CircleBuffer* buffer);
size_t CircleBufferCapacity(const struct CircleBuffer* buffer); void CircleBufferClear(struct CircleBuffer* buffer); int CircleBufferWrite8(struct CircleBuffer* buffer, int8_t value); +int CircleBufferWrite16(struct CircleBuffer* buffer, int16_t value); int CircleBufferWrite32(struct CircleBuffer* buffer, int32_t value); int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value); +int CircleBufferRead16(struct CircleBuffer* buffer, int16_t* value); int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value); size_t CircleBufferRead(struct CircleBuffer* buffer, void* output, size_t length); size_t CircleBufferDump(const struct CircleBuffer* buffer, void* output, size_t length);
@@ -22,4 +22,8 @@ #include <unistd.h>
#define UNUSED(V) (void)(V) +#ifdef _3DS +#define SSIZE_MAX (SIZE_MAX >> 1) +#endif + #endif
@@ -5,6 +5,7 @@ * 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 "configuration.h" +#include "util/formatting.h" #include "util/vfs.h" #include "third-party/inih/ini.h"@@ -78,9 +79,20 @@ ConfigurationSetValue(configuration, section, key, charValue);
} void ConfigurationSetFloatValue(struct Configuration* configuration, const char* section, const char* key, float value) { - char charValue[FLT_DIG + 7]; - sprintf(charValue, "%.*g", FLT_DIG, value); + char charValue[16]; + ftostr_u(charValue, sizeof(charValue), value); ConfigurationSetValue(configuration, section, key, charValue); +} + +void ConfigurationClearValue(struct Configuration* configuration, const char* section, const char* key) { + struct Table* currentSection = &configuration->root; + if (section) { + currentSection = HashTableLookup(&configuration->sections, section); + if (!currentSection) { + return; + } + } + HashTableRemove(currentSection, key); } const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) {
@@ -25,6 +25,8 @@ void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value);
const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key); +void ConfigurationClearValue(struct Configuration*, const char* section, const char* key); + bool ConfigurationRead(struct Configuration*, const char* path); bool ConfigurationWrite(const struct Configuration*, const char* path); bool ConfigurationWriteSection(const struct Configuration*, const char* path, const char* section);
@@ -0,0 +1,66 @@
+/* Copyright (c) 2013-2015 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 "formatting.h" + +#include <float.h> + +int ftostr_l(char* restrict str, size_t size, float f, locale_t locale) { +#ifdef HAVE_SNPRINTF_L + return snprintf_l(str, size, locale, "%*.g", FLT_DIG, f); +#elif defined(HAVE_LOCALE) + locale_t old = uselocale(locale); + int res = snprintf(str, size, "%*.g", FLT_DIG, f); + uselocale(old); + return res; +#else + char* old = setlocale(LC_NUMERIC, locale); + int res = snprintf(str, size, "%*.g", FLT_DIG, f); + setlocale(LC_NUMERIC, old); + return res; +#endif +} + +#ifndef HAVE_STRTOF_L +float strtof_l(const char* restrict str, char** restrict end, locale_t locale) { +#ifdef HAVE_LOCALE + locale_t old = uselocale(locale); + float res = strtof(str, end); + uselocale(old); + return res; +#else + char* old = setlocale(LC_NUMERIC, locale); + float res = strtof(str, end); + setlocale(LC_NUMERIC, old); + return res; +#endif +} +#endif + +int ftostr_u(char* restrict str, size_t size, float f) { +#if HAVE_LOCALE + locale_t l = newlocale(LC_NUMERIC_MASK, "C", 0); +#else + locale_t l = "C"; +#endif + int res = ftostr_l(str, size, f, l); +#if HAVE_LOCALE + freelocale(l); +#endif + return res; +} + +float strtof_u(const char* restrict str, char** restrict end) { +#if HAVE_LOCALE + locale_t l = newlocale(LC_NUMERIC_MASK, "C", 0); +#else + locale_t l = "C"; +#endif + float res = strtof_l(str, end, l); +#if HAVE_LOCALE + freelocale(l); +#endif + return res; +}
@@ -0,0 +1,30 @@
+/* Copyright (c) 2013-2015 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 FORMATTING_H +#define FORMATTING_H + +#include "util/common.h" + +#include "locale.h" + +#if defined(__APPLE__) || defined(__FreeBSD__) +#include "xlocale.h" +#endif + +#ifndef HAVE_LOCALE +typedef const char* locale_t; +#endif + +int ftostr_l(char* restrict str, size_t size, float f, locale_t locale); + +#ifndef HAVE_STRTOF_L +float strtof_l(const char* restrict str, char** restrict end, locale_t locale); +#endif + +int ftostr_u(char* restrict str, size_t size, float f); +float strtof_u(const char* restrict str, char** restrict end); + +#endif
@@ -9,7 +9,7 @@ #include "util/patch.h"
#include "util/vfs.h" static size_t _IPSOutputSize(struct Patch* patch, size_t inSize); -static bool _IPSApplyPatch(struct Patch* patch, void* out, size_t outSize); +static bool _IPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize); bool loadPatchIPS(struct Patch* patch) { patch->vf->seek(patch->vf, 0, SEEK_SET);@@ -42,10 +42,11 @@ UNUSED(patch);
return inSize; } -bool _IPSApplyPatch(struct Patch* patch, void* out, size_t outSize) { +bool _IPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) { if (patch->vf->seek(patch->vf, 5, SEEK_SET) != 5) { return false; } + memcpy(out, in, inSize > outSize ? outSize : inSize); uint8_t* buf = out; while (true) {
@@ -16,8 +16,11 @@ PATCH_CHECKSUM = -4,
}; static size_t _UPSOutputSize(struct Patch* patch, size_t inSize); -static bool _UPSApplyPatch(struct Patch* patch, void* out, size_t outSize); -static size_t _UPSDecodeLength(struct VFile* vf); + +static bool _UPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize); +static bool _BPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize); + +static size_t _decodeLength(struct VFile* vf); bool loadPatchUPS(struct Patch* patch) { patch->vf->seek(patch->vf, 0, SEEK_SET);@@ -27,11 +30,15 @@ if (patch->vf->read(patch->vf, buffer, 4) != 4) {
return false; } - if (memcmp(buffer, "UPS1", 4) != 0) { + if (memcmp(buffer, "UPS1", 4) == 0) { + patch->applyPatch = _UPSApplyPatch; + } else if (memcmp(buffer, "BPS1", 4) == 0) { + patch->applyPatch = _BPSApplyPatch; + } else { return false; } - size_t filesize = patch->vf->seek(patch->vf, 0, SEEK_END); + size_t filesize = patch->vf->size(patch->vf); uint32_t goodCrc32; patch->vf->seek(patch->vf, PATCH_CHECKSUM, SEEK_END);@@ -45,34 +52,35 @@ return false;
} patch->outputSize = _UPSOutputSize; - patch->applyPatch = _UPSApplyPatch; return true; } size_t _UPSOutputSize(struct Patch* patch, size_t inSize) { UNUSED(inSize); patch->vf->seek(patch->vf, 4, SEEK_SET); - if (_UPSDecodeLength(patch->vf) != inSize) { + if (_decodeLength(patch->vf) != inSize) { return 0; } - return _UPSDecodeLength(patch->vf); + return _decodeLength(patch->vf); } -bool _UPSApplyPatch(struct Patch* patch, void* out, size_t outSize) { +bool _UPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) { // TODO: Input checksum - size_t filesize = patch->vf->seek(patch->vf, 0, SEEK_END); + size_t filesize = patch->vf->size(patch->vf); patch->vf->seek(patch->vf, 4, SEEK_SET); - _UPSDecodeLength(patch->vf); // Discard input size - if (_UPSDecodeLength(patch->vf) != outSize) { + _decodeLength(patch->vf); // Discard input size + if (_decodeLength(patch->vf) != outSize) { return false; } + memcpy(out, in, inSize > outSize ? outSize : inSize); + size_t offset = 0; size_t alreadyRead = 0; uint8_t* buf = out; while (alreadyRead < filesize + IN_CHECKSUM) { - offset += _UPSDecodeLength(patch->vf); + offset += _decodeLength(patch->vf); uint8_t byte; while (true) {@@ -101,7 +109,103 @@ }
return true; } -size_t _UPSDecodeLength(struct VFile* vf) { +bool _BPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) { + patch->vf->seek(patch->vf, IN_CHECKSUM, SEEK_END); + uint32_t expectedInChecksum; + uint32_t expectedOutChecksum; + patch->vf->read(patch->vf, &expectedInChecksum, sizeof(expectedInChecksum)); + patch->vf->read(patch->vf, &expectedOutChecksum, sizeof(expectedOutChecksum)); + + uint32_t inputChecksum = doCrc32(in, inSize); + uint32_t outputChecksum = 0; + + if (inputChecksum != expectedInChecksum) { + return false; + } + + ssize_t filesize = patch->vf->size(patch->vf); + patch->vf->seek(patch->vf, 4, SEEK_SET); + _decodeLength(patch->vf); // Discard input size + if (_decodeLength(patch->vf) != outSize) { + return false; + } + if (inSize > SSIZE_MAX || outSize > SSIZE_MAX) { + return false; + } + size_t metadataLength = _decodeLength(patch->vf); + patch->vf->seek(patch->vf, metadataLength, SEEK_CUR); // Skip metadata + size_t writeLocation = 0; + ssize_t readSourceLocation = 0; + ssize_t readTargetLocation = 0; + size_t readOffset; + uint8_t* writeBuffer = out; + uint8_t* readBuffer = in; + while (patch->vf->seek(patch->vf, 0, SEEK_CUR) < filesize + IN_CHECKSUM) { + size_t command = _decodeLength(patch->vf); + size_t length = (command >> 2) + 1; + if (writeLocation + length > outSize) { + return false; + } + size_t i; + switch (command & 0x3) { + case 0x0: + // SourceRead + memmove(&writeBuffer[writeLocation], &readBuffer[writeLocation], length); + outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length); + writeLocation += length; + break; + case 0x1: + // TargetRead + if (patch->vf->read(patch->vf, &writeBuffer[writeLocation], length) != (ssize_t) length) { + return false; + } + outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length); + writeLocation += length; + break; + case 0x2: + // SourceCopy + readOffset = _decodeLength(patch->vf); + if (readOffset & 1) { + readSourceLocation -= readOffset >> 1; + } else { + readSourceLocation += readOffset >> 1; + } + if (readSourceLocation < 0 || readSourceLocation > (ssize_t) inSize) { + return false; + } + memmove(&writeBuffer[writeLocation], &readBuffer[readSourceLocation], length); + outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length); + writeLocation += length; + readSourceLocation += length; + break; + case 0x3: + // TargetCopy + readOffset = _decodeLength(patch->vf); + if (readOffset & 1) { + readTargetLocation -= readOffset >> 1; + } else { + readTargetLocation += readOffset >> 1; + } + if (readTargetLocation < 0 || readTargetLocation > (ssize_t) outSize) { + return false; + } + for (i = 0; i < length; ++i) { + // This needs to be bytewise as it can overlap + writeBuffer[writeLocation] = writeBuffer[readTargetLocation]; + ++writeLocation; + ++readTargetLocation; + } + outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation - length], length); + break; + } + } + if (expectedOutChecksum != outputChecksum) { + return false; + } + return true; +} + +size_t _decodeLength(struct VFile* vf) { size_t shift = 1; size_t value = 0; uint8_t byte;
@@ -14,7 +14,7 @@ struct Patch {
struct VFile* vf; size_t (*outputSize)(struct Patch* patch, size_t inSize); - bool (*applyPatch)(struct Patch* patch, void* out, size_t outSize); + bool (*applyPatch)(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize); }; bool loadPatch(struct VFile* vf, struct Patch* patch);
@@ -149,21 +149,21 @@ }
uint8_t* pixelData = pixels; unsigned pngHeight = png_get_image_height(png, info); - if (height > pngHeight) { - height = pngHeight; + if (height < pngHeight) { + pngHeight = height; } unsigned pngWidth = png_get_image_width(png, info); - if (width > pngWidth) { - width = pngWidth; + if (width < pngWidth) { + pngWidth = width; } unsigned i; png_bytep row = malloc(png_get_rowbytes(png, info)); - for (i = 0; i < height; ++i) { + for (i = 0; i < pngHeight; ++i) { png_read_row(png, row, 0); unsigned x; - for (x = 0; x < width; ++x) { + for (x = 0; x < pngWidth; ++x) { pixelData[stride * i * 4 + x * 4] = row[x * 3]; pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 1]; pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 2];
@@ -16,70 +16,140 @@ #ifdef _WIN32
#include <winsock2.h> #include <ws2tcpip.h> -#define SOCKET_FAILED(s) (s) == INVALID_SOCKET +#define SOCKET_FAILED(s) ((s) == INVALID_SOCKET) typedef SOCKET Socket; #else +#include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <sys/socket.h> #define INVALID_SOCKET (-1) -#define SOCKET_FAILED(s) (s) < 0 +#define SOCKET_FAILED(s) ((s) < 0) typedef int Socket; #endif +enum IP { + IPV4, + IPV6 +}; -static inline void SocketSubsystemInitialize() { +struct Address { + enum IP version; + union { + uint32_t ipv4; + uint8_t ipv6[16]; + }; +}; + +static inline void SocketSubsystemInit() { #ifdef _WIN32 - WSAStartup(MAKEWORD(2, 2), 0); + WSADATA data; + WSAStartup(MAKEWORD(2, 2), &data); +#endif +} + +static inline int SocketError() { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +static inline bool SocketWouldBlock() { +#ifdef _WIN32 + return SocketError() == WSAEWOULDBLOCK; +#else + return SocketError() == EWOULDBLOCK || SocketError() == EAGAIN; #endif } static inline ssize_t SocketSend(Socket socket, const void* buffer, size_t size) { +#ifdef _WIN32 + return send(socket, (const char*) buffer, size, 0); +#else return write(socket, buffer, size); +#endif } static inline ssize_t SocketRecv(Socket socket, void* buffer, size_t size) { +#ifdef _WIN32 + return recv(socket, (char*) buffer, size, 0); +#else return read(socket, buffer, size); +#endif } -static inline Socket SocketOpenTCP(int port, uint32_t bindAddress) { +static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) { Socket sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (SOCKET_FAILED(sock)) { return sock; } - struct sockaddr_in bindInfo = { - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr = { 0 } - }; - bindInfo.sin_addr.s_addr = htonl(bindAddress); - int err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(struct sockaddr_in)); + int err; + if (!bindAddress) { + struct sockaddr_in bindInfo; + memset(&bindInfo, 0, sizeof(bindInfo)); + bindInfo.sin_family = AF_INET; + bindInfo.sin_port = htons(port); + err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); + } else if (bindAddress->version == IPV4) { + struct sockaddr_in bindInfo; + memset(&bindInfo, 0, sizeof(bindInfo)); + bindInfo.sin_family = AF_INET; + bindInfo.sin_port = htons(port); + bindInfo.sin_addr.s_addr = bindAddress->ipv4; + err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); + } else { + struct sockaddr_in6 bindInfo; + memset(&bindInfo, 0, sizeof(bindInfo)); + bindInfo.sin6_family = AF_INET6; + bindInfo.sin6_port = htons(port); + memcpy(bindInfo.sin6_addr.s6_addr, bindAddress->ipv6, sizeof(bindInfo.sin6_addr.s6_addr)); + err = bind(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); + + } if (err) { close(sock); - return -1; + return INVALID_SOCKET; } return sock; } -static inline Socket SocketConnectTCP(int port, uint32_t destinationAddress) { +static inline Socket SocketConnectTCP(int port, const struct Address* destinationAddress) { Socket sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (SOCKET_FAILED(sock)) { return sock; } - struct sockaddr_in bindInfo = { - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr = { 0 } - }; - bindInfo.sin_addr.s_addr = htonl(destinationAddress); - int err = connect(sock, (const struct sockaddr*) &bindInfo, sizeof(struct sockaddr_in)); + int err; + if (!destinationAddress) { + struct sockaddr_in bindInfo; + memset(&bindInfo, 0, sizeof(bindInfo)); + bindInfo.sin_family = AF_INET; + bindInfo.sin_port = htons(port); + err = connect(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); + } else if (destinationAddress->version == IPV4) { + struct sockaddr_in bindInfo; + memset(&bindInfo, 0, sizeof(bindInfo)); + bindInfo.sin_family = AF_INET; + bindInfo.sin_port = htons(port); + bindInfo.sin_addr.s_addr = destinationAddress->ipv4; + err = connect(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); + } else { + struct sockaddr_in6 bindInfo; + memset(&bindInfo, 0, sizeof(bindInfo)); + bindInfo.sin6_family = AF_INET6; + bindInfo.sin6_port = htons(port); + memcpy(bindInfo.sin6_addr.s6_addr, destinationAddress->ipv6, sizeof(bindInfo.sin6_addr.s6_addr)); + err = connect(sock, (const struct sockaddr*) &bindInfo, sizeof(bindInfo)); + } + if (err) { close(sock); - return -1; + return INVALID_SOCKET; } return sock; }@@ -88,15 +158,32 @@ static inline Socket SocketListen(Socket socket, int queueLength) {
return listen(socket, queueLength); } -static inline Socket SocketAccept(Socket socket, struct sockaddr* restrict address, socklen_t* restrict addressLength) { - return accept(socket, address, addressLength); +static inline Socket SocketAccept(Socket socket, struct Address* address) { + if (!address) { + return accept(socket, 0, 0); + } + if (address->version == IPV4) { + struct sockaddr_in addrInfo; + memset(&addrInfo, 0, sizeof(addrInfo)); + addrInfo.sin_family = AF_INET; + addrInfo.sin_addr.s_addr = address->ipv4; + socklen_t len = sizeof(addrInfo); + return accept(socket, (struct sockaddr*) &addrInfo, &len); + } else { + struct sockaddr_in6 addrInfo; + memset(&addrInfo, 0, sizeof(addrInfo)); + addrInfo.sin6_family = AF_INET6; + memcpy(addrInfo.sin6_addr.s6_addr, address->ipv6, sizeof(addrInfo.sin6_addr.s6_addr)); + socklen_t len = sizeof(addrInfo); + return accept(socket, (struct sockaddr*) &addrInfo, &len); + } } static inline int SocketClose(Socket socket) { return close(socket) >= 0; } -static inline int SocketSetBlocking(Socket socket, int blocking) { +static inline int SocketSetBlocking(Socket socket, bool blocking) { #ifdef _WIN32 u_long unblocking = !blocking; return ioctlsocket(socket, FIONBIO, &unblocking) == NO_ERROR;@@ -116,6 +203,76 @@ }
static inline int SocketSetTCPPush(Socket socket, int push) { return setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*) &push, sizeof(int)) >= 0; +} + +static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Socket* errors, int64_t timeoutMillis) { + fd_set rset; + fd_set wset; + fd_set eset; + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&eset); + size_t i; + Socket maxFd = 0; + if (reads) { + for (i = 0; i < nSockets; ++i) { + if (SOCKET_FAILED(reads[i])) { + break; + } + if (reads[i] > maxFd) { + maxFd = reads[i]; + } + FD_SET(reads[i], &rset); + reads[i] = INVALID_SOCKET; + } + } + if (writes) { + for (i = 0; i < nSockets; ++i) { + if (SOCKET_FAILED(writes[i])) { + break; + } + if (writes[i] > maxFd) { + maxFd = writes[i]; + } + FD_SET(writes[i], &wset); + writes[i] = INVALID_SOCKET; + } + } + if (errors) { + for (i = 0; i < nSockets; ++i) { + if (SOCKET_FAILED(errors[i])) { + break; + } + if (errors[i] > maxFd) { + maxFd = errors[i]; + } + FD_SET(errors[i], &eset); + errors[i] = INVALID_SOCKET; + } + } + struct timeval tv; + tv.tv_sec = timeoutMillis / 1000; + tv.tv_usec = (timeoutMillis % 1000) * 1000; + int result = select(maxFd + 1, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv); + int r = 0; + int w = 0; + int e = 0; + Socket j; + for (j = 0; j < maxFd; ++j) { + if (reads && FD_ISSET(j, &rset)) { + reads[r] = j; + ++r; + } + if (writes && FD_ISSET(j, &wset)) { + writes[w] = j; + ++w; + } + if (errors && FD_ISSET(j, &eset)) { + errors[e] = j; + ++e; + } + } + return result; } #endif
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2015 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@@ -28,3 +28,172 @@ }
} return last; } + +static uint32_t _utf16Char(const uint16_t** unicode, size_t* length) { + if (*length < 2) { + *length = 0; + return 0; + } + uint32_t unichar = **unicode; + ++*unicode; + *length -= 2; + if (unichar < 0xD800 || unichar >= 0xE000) { + return unichar; + } + if (*length < 2) { + *length = 0; + return 0; + } + uint16_t highSurrogate = unichar; + uint16_t lowSurrogate = **unicode; + ++*unicode; + *length -= 2; + if (highSurrogate >= 0xDC00) { + return 0; + } + if (lowSurrogate < 0xDC00 || lowSurrogate >= 0xE000) { + return 0; + } + highSurrogate -= 0xD800; + lowSurrogate -= 0xDC00; + return (highSurrogate << 10) + lowSurrogate + 0x10000; +} + +static uint32_t _utf8Char(const char** unicode, size_t* length) { + if (*length == 0) { + return 0; + } + char byte = **unicode; + --*length; + ++*unicode; + if (!(byte & 0x80)) { + return byte; + } + uint32_t unichar; + static int tops[4] = { 0xC0, 0xE0, 0xF0, 0xF8 }; + size_t numBytes; + for (numBytes = 0; numBytes < 3; ++numBytes) { + if ((byte & tops[numBytes + 1]) == tops[numBytes]) { + break; + } + } + unichar = byte & ~tops[numBytes]; + if (numBytes == 3) { + return 0; + } + ++numBytes; + if (*length < numBytes) { + *length = 0; + return 0; + } + size_t i; + for (i = 0; i < numBytes; ++i) { + unichar <<= 6; + byte = **unicode; + --*length; + ++*unicode; + if ((byte & 0xC0) != 0x80) { + return 0; + } + unichar |= byte & 0x3F; + } + return unichar; +} + +static size_t _toUtf8(uint32_t unichar, char* buffer) { + if (unichar > 0x10FFFF) { + unichar = 0xFFFD; + } + if (unichar < 0x80) { + buffer[0] = unichar; + return 1; + } + if (unichar < 0x800) { + buffer[0] = (unichar >> 6) | 0xC0; + buffer[1] = (unichar & 0x3F) | 0x80; + return 2; + } + if (unichar < 0x10000) { + buffer[0] = (unichar >> 12) | 0xE0; + buffer[1] = ((unichar >> 6) & 0x3F) | 0x80; + buffer[2] = (unichar & 0x3F) | 0x80; + return 3; + } + if (unichar < 0x200000) { + buffer[0] = (unichar >> 18) | 0xF0; + buffer[1] = ((unichar >> 12) & 0x3F) | 0x80; + buffer[2] = ((unichar >> 6) & 0x3F) | 0x80; + buffer[3] = (unichar & 0x3F) | 0x80; + return 4; + } + + // This shouldn't be possible + return 0; +} + +int utfcmp(const uint16_t* utf16, const char* utf8, size_t utf16Length, size_t utf8Length) { + uint32_t char1 = 0, char2 = 0; + while (utf16Length > 0 && utf8Length > 0) { + if (char1 < char2) { + return -1; + } + if (char1 > char2) { + return 1; + } + char1 = _utf16Char(&utf16, &utf16Length); + char2 = _utf8Char(&utf8, &utf8Length); + } + if (utf16Length == 0 && utf8Length > 0) { + return -1; + } + if (utf16Length > 0 && utf8Length == 0) { + return 1; + } + return 0; +} + +char* utf16to8(const uint16_t* utf16, size_t length) { + char* utf8 = 0; + char* offset = 0; + char buffer[4]; + size_t utf8TotalBytes = 0; + size_t utf8Length = 0; + while (true) { + if (length == 0) { + break; + } + uint32_t unichar = _utf16Char(&utf16, &length); + size_t bytes = _toUtf8(unichar, buffer); + utf8Length += bytes; + if (utf8Length < utf8TotalBytes) { + memcpy(offset, buffer, bytes); + offset += bytes; + } else if (!utf8) { + utf8 = malloc(length); + if (!utf8) { + return 0; + } + utf8TotalBytes = length; + memcpy(utf8, buffer, bytes); + offset = utf8 + bytes; + } else if (utf8Length >= utf8TotalBytes) { + char* newUTF8 = realloc(utf8, utf8TotalBytes * 2); + if (newUTF8 != utf8) { + free(utf8); + } + if (!newUTF8) { + return 0; + } + offset = offset - utf8 + newUTF8; + memcpy(offset, buffer, bytes); + offset += bytes; + } + } + + char* newUTF8 = realloc(utf8, utf8Length + 1); + if (newUTF8 != utf8) { + free(utf8); + } + newUTF8[utf8Length] = '\0'; + return newUTF8; +}
@@ -15,4 +15,7 @@ #endif
char* strnrstr(const char* restrict s1, const char* restrict s2, size_t len); +int utfcmp(const uint16_t* utf16, const char* utf8, size_t utf16Length, size_t utf8Length); +char* utf16to8(const uint16_t* utf16, size_t length); + #endif
@@ -144,13 +144,13 @@ list->list = calloc(LIST_INITIAL_SIZE, sizeof(struct TableTuple));
} } -void TableEnumerate(const struct Table* table, void (handler(void* value, void* user)), void* user) { +void TableEnumerate(const struct Table* table, void (handler(uint32_t key, void* value, void* user)), void* user) { size_t i; for (i = 0; i < table->tableSize; ++i) { const struct TableList* list = &table->table[i]; size_t j; for (j = 0; j < list->nEntries; ++j) { - handler(list->list[j].value, user); + handler(list->list[j].key, list->list[j].value, user); } } }
@@ -25,7 +25,7 @@
void TableRemove(struct Table*, uint32_t key); void TableClear(struct Table*); -void TableEnumerate(const struct Table*, void (handler(void* value, void* user)), void* user); +void TableEnumerate(const struct Table*, void (handler(uint32_t key, void* value, void* user)), void* user); static inline void HashTableInit(struct Table* table, size_t initialSize, void (deinitializer(void*))) { TableInit(table, initialSize, deinitializer);
@@ -10,6 +10,7 @@ #include "util/common.h"
#ifdef USE_PTHREADS #include <pthread.h> +#include <sys/time.h> #define THREAD_ENTRY void* typedef THREAD_ENTRY (*ThreadEntry)(void*);@@ -44,6 +45,21 @@ }
static inline int ConditionWait(Condition* cond, Mutex* mutex) { return pthread_cond_wait(cond, mutex); +} + +static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { + struct timespec ts; + struct timeval tv; + + gettimeofday(&tv, 0); + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = (tv.tv_usec + timeoutMs * 1000L) * 1000L; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_nsec -= 1000000000L; + ++ts.tv_sec; + } + + return pthread_cond_timedwait(cond, mutex, &ts); } static inline int ConditionWake(Condition* cond) {@@ -104,6 +120,11 @@ SleepConditionVariableCS(cond, mutex, INFINITE);
return GetLastError(); } +static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { + SleepConditionVariableCS(cond, mutex, timeoutMs); + return GetLastError(); +} + static inline int ConditionWake(Condition* cond) { WakeAllConditionVariable(cond); return GetLastError();@@ -186,6 +207,16 @@ MutexLock(mutex);
return 0; } +static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { + MutexLock(&cond->mutex); + ++cond->waiting; + MutexUnlock(mutex); + MutexUnlock(&cond->mutex); + svcWaitSynchronization(cond->semaphore, timeoutMs * 10000000LL); + MutexLock(mutex); + return 0; +} + static inline int ConditionWake(Condition* cond) { MutexLock(&cond->mutex); if (cond->waiting) {@@ -245,6 +276,13 @@
static inline int ConditionWait(Condition* cond, Mutex* mutex) { UNUSED(cond); UNUSED(mutex); + return 0; +} + +static inline int ConditionWaitTimed(Condition* cond, Mutex* mutex, int32_t timeoutMs) { + UNUSED(cond); + UNUSED(mutex); + UNUSED(timeoutMs); return 0; }
@@ -0,0 +1,79 @@
+/* Copyright (c) 2013-2015 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 VECTOR_H +#define VECTOR_H + +#include "util/common.h" + +#define DECLARE_VECTOR(NAME, TYPE) \ + struct NAME { \ + TYPE* vector; \ + size_t size; \ + size_t capacity; \ + }; \ + void NAME ## Init(struct NAME* vector, size_t capacity); \ + void NAME ## Deinit(struct NAME* vector); \ + TYPE* NAME ## GetPointer(struct NAME* vector, size_t location); \ + TYPE* NAME ## Append(struct NAME* vector); \ + void NAME ## Clear(struct NAME* vector); \ + void NAME ## Resize(struct NAME* vector, ssize_t change); \ + void NAME ## Shift(struct NAME* vector, size_t location, size_t difference); \ + void NAME ## Unshift(struct NAME* vector, size_t location, size_t difference); \ + void NAME ## EnsureCapacity(struct NAME* vector, size_t capacity); \ + size_t NAME ## Size(const struct NAME* vector); \ + +#define DEFINE_VECTOR(NAME, TYPE) \ + void NAME ## Init(struct NAME* vector, size_t capacity) { \ + vector->size = 0; \ + if (capacity == 0) { \ + capacity = 4; \ + } \ + vector->capacity = capacity; \ + vector->vector = malloc(sizeof(TYPE) * capacity); \ + } \ + void NAME ## Deinit(struct NAME* vector) { \ + free(vector->vector); \ + vector->vector = 0; \ + vector->capacity = 0; \ + } \ + TYPE* NAME ## GetPointer(struct NAME* vector, size_t location) { \ + return &vector->vector[location]; \ + } \ + TYPE* NAME ## Append(struct NAME* vector) { \ + NAME ## Resize(vector, 1); \ + return &vector->vector[vector->size - 1]; \ + } \ + void NAME ## Resize(struct NAME* vector, ssize_t change) { \ + if (change > 0) { \ + NAME ## EnsureCapacity(vector, vector->size + change); \ + } \ + vector->size += change; \ + } \ + void NAME ## Clear(struct NAME* vector) { \ + vector->size = 0; \ + } \ + void NAME ## EnsureCapacity(struct NAME* vector, size_t capacity) { \ + if (capacity <= vector->capacity) { \ + return; \ + } \ + while (capacity > vector->capacity) { \ + vector->capacity <<= 1; \ + } \ + vector->vector = realloc(vector->vector, vector->capacity * sizeof(TYPE)); \ + } \ + void NAME ## Shift(struct NAME* vector, size_t location, size_t difference) { \ + memmove(&vector->vector[location], &vector->vector[location + difference], (vector->size - location - difference) * sizeof(TYPE)); \ + vector->size -= difference; \ + } \ + void NAME ## Unshift(struct NAME* vector, size_t location, size_t difference) { \ + NAME ## Resize(vector, difference); \ + memmove(&vector->vector[location + difference], &vector->vector[location], (vector->size - location - difference) * sizeof(TYPE)); \ + } \ + size_t NAME ## Size(const struct NAME* vector) { \ + return vector->size; \ + } \ + +#endif
@@ -9,6 +9,7 @@ #include "util/string.h"
#include <fcntl.h> #include <dirent.h> +#include <sys/stat.h> #ifdef _WIN32 #include <io.h>@@ -38,6 +39,7 @@ static ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size);
static void* _vfdMap(struct VFile* vf, size_t size, int flags); static void _vfdUnmap(struct VFile* vf, void* memory, size_t size); static void _vfdTruncate(struct VFile* vf, size_t size); +static ssize_t _vfdSize(struct VFile* vf); static bool _vdClose(struct VDir* vd); static void _vdRewind(struct VDir* vd);@@ -76,6 +78,7 @@ vfd->d.write = _vfdWrite;
vfd->d.map = _vfdMap; vfd->d.unmap = _vfdUnmap; vfd->d.truncate = _vfdTruncate; + vfd->d.size = _vfdSize; return &vfd->d; }@@ -104,12 +107,13 @@ struct VFileFD* vfd = (struct VFileFD*) vf;
size_t bytesRead = 0; while (bytesRead < size - 1) { size_t newRead = read(vfd->fd, &buffer[bytesRead], 1); - bytesRead += newRead; if (!newRead || buffer[bytesRead] == '\n') { break; } + bytesRead += newRead; } - return buffer[bytesRead] = '\0'; + buffer[bytesRead] = '\0'; + return bytesRead; } ssize_t _vfdWrite(struct VFile* vf, const void* buffer, size_t size) {@@ -126,9 +130,12 @@ if (flags & MAP_WRITE) {
createFlags = PAGE_READWRITE; mapFiles = FILE_MAP_WRITE; } - size_t location = lseek(vfd->fd, 0, SEEK_CUR); - size_t fileSize = lseek(vfd->fd, 0, SEEK_END); - lseek(vfd->fd, location, SEEK_SET); + size_t fileSize; + struct stat stat; + if (fstat(vfd->fd, &stat) < 0) { + return 0; + } + fileSize = stat.st_size; if (size > fileSize) { size = fileSize; }@@ -175,6 +182,15 @@
static void _vfdTruncate(struct VFile* vf, size_t size) { struct VFileFD* vfd = (struct VFileFD*) vf; ftruncate(vfd->fd, size); +} + +static ssize_t _vfdSize(struct VFile* vf) { + struct VFileFD* vfd = (struct VFileFD*) vf; + struct stat stat; + if (fstat(vfd->fd, &stat) < 0) { + return -1; + } + return stat.st_size; } struct VDirEntryDE {@@ -378,3 +394,15 @@ return vdede->ent->d_name;
} return 0; } + +ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { + size_t bytesRead = 0; + while (bytesRead < size - 1) { + size_t newRead = vf->read(vf, &buffer[bytesRead], 1); + bytesRead += newRead; + if (!newRead || buffer[bytesRead] == '\n') { + break; + } + } + return buffer[bytesRead] = '\0'; +}
@@ -22,6 +22,7 @@ ssize_t (*write)(struct VFile* vf, const void* buffer, size_t size);
void* (*map)(struct VFile* vf, size_t size, int flags); void (*unmap)(struct VFile* vf, void* memory, size_t size); void (*truncate)(struct VFile* vf, size_t size); + ssize_t (*size)(struct VFile* vf); }; struct VDirEntry {@@ -37,14 +38,21 @@ };
struct VFile* VFileOpen(const char* path, int flags); struct VFile* VFileFromFD(int fd); +struct VFile* VFileFromMemory(void* mem, size_t size); struct VDir* VDirOpen(const char* path); -#ifdef ENABLE_LIBZIP +#ifdef USE_LIBZIP struct VDir* VDirOpenZip(const char* path, int flags); #endif +#ifdef USE_LZMA +struct VDir* VDirOpen7z(const char* path, int flags); +#endif + struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode); struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode); + +ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size); #endif
@@ -0,0 +1,311 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/vfs.h" + +#ifdef USE_LZMA + +#include "util/string.h" + +#include "third-party/lzma/7z.h" +#include "third-party/lzma/7zAlloc.h" +#include "third-party/lzma/7zBuf.h" +#include "third-party/lzma/7zCrc.h" +#include "third-party/lzma/7zFile.h" +#include "third-party/lzma/7zVersion.h" + +struct VDirEntry7z { + struct VDirEntry d; + + struct VDir7z* vd; + UInt32 index; + char* utf8; +}; + +struct VDir7z { + struct VDir d; + struct VDirEntry7z dirent; + + // What is all this garbage? + CFileInStream archiveStream; + CLookToRead lookStream; + CSzArEx db; + ISzAlloc allocImp; + ISzAlloc allocTempImp; +}; + +struct VFile7z { + struct VFile d; + + struct VDir7z* vd; + + size_t offset; + + Byte* outBuffer; + size_t bufferOffset; + size_t size; +}; + +static bool _vf7zClose(struct VFile* vf); +static off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size); +static void* _vf7zMap(struct VFile* vf, size_t size, int flags); +static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size); +static void _vf7zTruncate(struct VFile* vf, size_t size); +static ssize_t _vf7zSize(struct VFile* vf); + +static bool _vd7zClose(struct VDir* vd); +static void _vd7zRewind(struct VDir* vd); +static struct VDirEntry* _vd7zListNext(struct VDir* vd); +static struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode); + +static const char* _vde7zName(struct VDirEntry* vde); + +struct VDir* VDirOpen7z(const char* path, int flags) { + struct VDir7z* vd = malloc(sizeof(struct VDir7z)); + + if (flags & O_WRONLY || flags & O_CREAT) { + return 0; + } + + // What does any of this mean, Igor? + if (InFile_Open(&vd->archiveStream.file, path)) { + free(vd); + return 0; + } + + vd->allocImp.Alloc = SzAlloc; + vd->allocImp.Free = SzFree; + + vd->allocTempImp.Alloc = SzAllocTemp; + vd->allocTempImp.Free = SzFreeTemp; + + FileInStream_CreateVTable(&vd->archiveStream); + LookToRead_CreateVTable(&vd->lookStream, False); + + vd->lookStream.realStream = &vd->archiveStream.s; + LookToRead_Init(&vd->lookStream); + + CrcGenerateTable(); + + SzArEx_Init(&vd->db); + SRes res = SzArEx_Open(&vd->db, &vd->lookStream.s, &vd->allocImp, &vd->allocTempImp); + if (res != SZ_OK) { + free(vd); + return 0; + } + + vd->dirent.index = 0; + vd->dirent.utf8 = 0; + vd->dirent.vd = vd; + vd->dirent.d.name = _vde7zName; + + vd->d.close = _vd7zClose; + vd->d.rewind = _vd7zRewind; + vd->d.listNext = _vd7zListNext; + vd->d.openFile = _vd7zOpenFile; + + return &vd->d; +} + +bool _vf7zClose(struct VFile* vf) { + struct VFile7z* vf7z = (struct VFile7z*) vf; + IAlloc_Free(&vf7z->vd->allocImp, vf7z->outBuffer); + return true; +} + +off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence) { + struct VFile7z* vf7z = (struct VFile7z*) vf; + + size_t position; + switch (whence) { + case SEEK_SET: + position = offset; + break; + case SEEK_CUR: + if (offset < 0 && ((vf7z->offset < (size_t) -offset) || (offset == INT_MIN))) { + return -1; + } + position = vf7z->offset + offset; + break; + case SEEK_END: + if (offset < 0 && ((vf7z->size < (size_t) -offset) || (offset == INT_MIN))) { + return -1; + } + position = vf7z->size + offset; + break; + default: + return -1; + } + + if (position > vf7z->size) { + return -1; + } + + vf7z->offset = position; + return position; +} + +ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size) { + struct VFile7z* vf7z = (struct VFile7z*) vf; + + if (size + vf7z->offset >= vf7z->size) { + size = vf7z->size - vf7z->offset; + } + + memcpy(buffer, vf7z->outBuffer + vf7z->offset + vf7z->bufferOffset, size); + vf7z->offset += size; + return size; +} + +ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size) { + // TODO + UNUSED(vf); + UNUSED(buffer); + UNUSED(size); + return -1; +} + +void* _vf7zMap(struct VFile* vf, size_t size, int flags) { + struct VFile7z* vf7z = (struct VFile7z*) vf; + + UNUSED(flags); + if (size > vf7z->size) { + return 0; + } + + return vf7z->outBuffer + vf7z->bufferOffset; +} + +void _vf7zUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); +} + +void _vf7zTruncate(struct VFile* vf, size_t size) { + // TODO + UNUSED(vf); + UNUSED(size); +} + +ssize_t _vf7zSize(struct VFile* vf) { + struct VFile7z* vf7z = (struct VFile7z*) vf; + return vf7z->size; +} + +bool _vd7zClose(struct VDir* vd) { + struct VDir7z* vd7z = (struct VDir7z*) vd; + SzArEx_Free(&vd7z->db, &vd7z->allocImp); + File_Close(&vd7z->archiveStream.file); + + free(vd7z->dirent.utf8); + vd7z->dirent.utf8 = 0; + + free(vd7z); + return true; +} + +void _vd7zRewind(struct VDir* vd) { + struct VDir7z* vd7z = (struct VDir7z*) vd; + free(vd7z->dirent.utf8); + vd7z->dirent.utf8 = 0; + vd7z->dirent.index = -1; +} + +struct VDirEntry* _vd7zListNext(struct VDir* vd) { + struct VDir7z* vd7z = (struct VDir7z*) vd; + if (vd7z->db.NumFiles <= vd7z->dirent.index + 1) { + return 0; + } + free(vd7z->dirent.utf8); + vd7z->dirent.utf8 = 0; + ++vd7z->dirent.index; + return &vd7z->dirent.d; +} + +struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode) { + UNUSED(mode); + // TODO: support truncating, appending and creating, and write + struct VDir7z* vd7z = (struct VDir7z*) vd; + + if ((mode & O_RDWR) == O_RDWR) { + // Read/Write support is not yet implemented. + return 0; + } + + if (mode & O_WRONLY) { + // Write support is not yet implemented. + return 0; + } + + size_t pathLength = strlen(path); + + UInt32 i; + for (i = 0; i < vd7z->db.NumFiles; ++i) { + if (SzArEx_IsDir(&vd7z->db, i)) { + continue; + } + size_t nameLength = SzArEx_GetFileNameUtf16(&vd7z->db, i, 0) * sizeof(UInt16); + UInt16* name = malloc(nameLength); + SzArEx_GetFileNameUtf16(&vd7z->db, i, name); + + if (utfcmp(name, path, nameLength - sizeof(UInt16), pathLength) == 0) { + free(name); + break; + } + + free(name); + } + + if (i == vd7z->db.NumFiles) { + return 0; // No file found + } + + struct VFile7z* vf = malloc(sizeof(struct VFile7z)); + vf->vd = vd7z; + + size_t outBufferSize; + UInt32 blockIndex; + + vf->outBuffer = 0; + SRes res = SzArEx_Extract(&vd7z->db, &vd7z->lookStream.s, i, &blockIndex, + &vf->outBuffer, &outBufferSize, + &vf->bufferOffset, &vf->size, + &vd7z->allocImp, &vd7z->allocTempImp); + + if (res != SZ_OK) { + free(vf); + return 0; + } + + vf->d.close = _vf7zClose; + vf->d.seek = _vf7zSeek; + vf->d.read = _vf7zRead; + vf->d.readline = VFileReadline; + vf->d.write = _vf7zWrite; + vf->d.map = _vf7zMap; + vf->d.unmap = _vf7zUnmap; + vf->d.truncate = _vf7zTruncate; + vf->d.size = _vf7zSize; + + return &vf->d; +} + +const char* _vde7zName(struct VDirEntry* vde) { + struct VDirEntry7z* vde7z = (struct VDirEntry7z*) vde; + if (!vde7z->utf8) { + size_t nameLength = SzArEx_GetFileNameUtf16(&vde7z->vd->db, vde7z->index, 0) * sizeof(UInt16); + UInt16* name = malloc(nameLength); + SzArEx_GetFileNameUtf16(&vde7z->vd->db, vde7z->index, name); + vde7z->utf8 = utf16to8(name, nameLength - sizeof(UInt16)); + free(name); + } + + return vde7z->utf8; +} + +#endif
@@ -0,0 +1,139 @@
+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/vfs.h" + +struct VFileMem { + struct VFile d; + void* mem; + size_t size; + size_t offset; +}; + +static bool _vfmClose(struct VFile* vf); +static off_t _vfmSeek(struct VFile* vf, off_t offset, int whence); +static ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size); +static ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size); +static void* _vfmMap(struct VFile* vf, size_t size, int flags); +static void _vfmUnmap(struct VFile* vf, void* memory, size_t size); +static void _vfmTruncate(struct VFile* vf, size_t size); +static ssize_t _vfmSize(struct VFile* vf); + +struct VFile* VFileFromMemory(void* mem, size_t size) { + if (!mem || !size) { + return 0; + } + + struct VFileMem* vfm = malloc(sizeof(struct VFileMem)); + if (!vfm) { + return 0; + } + + vfm->mem = mem; + vfm->size = size; + vfm->offset = 0; + vfm->d.close = _vfmClose; + vfm->d.seek = _vfmSeek; + vfm->d.read = _vfmRead; + vfm->d.readline = VFileReadline; + vfm->d.write = _vfmWrite; + vfm->d.map = _vfmMap; + vfm->d.unmap = _vfmUnmap; + vfm->d.truncate = _vfmTruncate; + vfm->d.size = _vfmSize; + + return &vfm->d; +} + +bool _vfmClose(struct VFile* vf) { + struct VFileMem* vfm = (struct VFileMem*) vf; + vfm->mem = 0; + free(vfm); + return true; +} + +off_t _vfmSeek(struct VFile* vf, off_t offset, int whence) { + struct VFileMem* vfm = (struct VFileMem*) vf; + + size_t position; + switch (whence) { + case SEEK_SET: + position = offset; + break; + case SEEK_CUR: + if (offset < 0 && ((vfm->offset < (size_t) -offset) || (offset == INT_MIN))) { + return -1; + } + position = vfm->offset + offset; + break; + case SEEK_END: + if (offset < 0 && ((vfm->size < (size_t) -offset) || (offset == INT_MIN))) { + return -1; + } + position = vfm->size + offset; + break; + default: + return -1; + } + + if (position > vfm->size) { + return -1; + } + + vfm->offset = position; + return position; +} + +ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size) { + struct VFileMem* vfm = (struct VFileMem*) vf; + + if (size + vfm->offset >= vfm->size) { + size = vfm->size - vfm->offset; + } + + memcpy(buffer, vfm->mem + vfm->offset, size); + vfm->offset += size; + return size; +} + +ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size) { + struct VFileMem* vfm = (struct VFileMem*) vf; + + if (size + vfm->offset >= vfm->size) { + size = vfm->size - vfm->offset; + } + + memcpy(vfm->mem + vfm->offset, buffer, size); + vfm->offset += size; + return size; +} + +void* _vfmMap(struct VFile* vf, size_t size, int flags) { + struct VFileMem* vfm = (struct VFileMem*) vf; + + UNUSED(flags); + if (size > vfm->size) { + return 0; + } + + return vfm->mem; +} + +void _vfmUnmap(struct VFile* vf, void* memory, size_t size) { + UNUSED(vf); + UNUSED(memory); + UNUSED(size); +} + +void _vfmTruncate(struct VFile* vf, size_t size) { + // TODO: Return value? + UNUSED(vf); + UNUSED(size); +} + +ssize_t _vfmSize(struct VFile* vf) { + struct VFileMem* vfm = (struct VFileMem*) vf; + return vfm->size; +}
@@ -5,7 +5,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/vfs.h" -#ifdef ENABLE_LIBZIP +#ifdef USE_LIBZIP #include <zip.h>@@ -38,11 +38,11 @@
static bool _vfzClose(struct VFile* vf); static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence); static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size); -static ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size); static ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size); static void* _vfzMap(struct VFile* vf, size_t size, int flags); static void _vfzUnmap(struct VFile* vf, void* memory, size_t size); static void _vfzTruncate(struct VFile* vf, size_t size); +static ssize_t _vfzSize(struct VFile* vf); static bool _vdzClose(struct VDir* vd); static void _vdzRewind(struct VDir* vd);@@ -187,18 +187,6 @@ }
return bytesRead; } -ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size) { - size_t bytesRead = 0; - while (bytesRead < size - 1) { - size_t newRead = vf->read(vf, &buffer[bytesRead], 1); - bytesRead += newRead; - if (!newRead || buffer[bytesRead] == '\n') { - break; - } - } - return buffer[bytesRead] = '\0'; -} - ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) { // TODO UNUSED(vf);@@ -227,6 +215,11 @@ void _vfzTruncate(struct VFile* vf, size_t size) {
// TODO UNUSED(vf); UNUSED(size); +} + +ssize_t _vfzSize(struct VFile* vf) { + struct VFileZip* vfz = (struct VFileZip*) vf; + return vfz->fileSize; } bool _vdzClose(struct VDir* vd) {@@ -290,11 +283,12 @@
vfz->d.close = _vfzClose; vfz->d.seek = _vfzSeek; vfz->d.read = _vfzRead; - vfz->d.readline = _vfzReadline; + vfz->d.readline = VFileReadline; vfz->d.write = _vfzWrite; vfz->d.map = _vfzMap; vfz->d.unmap = _vfzUnmap; vfz->d.truncate = _vfzTruncate; + vfz->d.size = _vfzSize; return &vfz->d; }
@@ -0,0 +1,161 @@
+#!/usr/bin/env python +from __future__ import print_function +import argparse +import errno +import os +import re +import shutil +import subprocess + +qtPath = None +verbose = False + +def splitPath(path): + folders = [] + while True: + path, folder = os.path.split(path) + if folder != '': + folders.append(folder) + else: + if path != '': + folders.append(path) + break + folders.reverse() + return folders + +def joinPath(path): + return reduce(os.path.join, path, '') + +def findFramework(path): + child = [] + while path and not path[-1].endswith('.framework'): + child.append(path.pop()) + child.reverse() + return path, child + +def findQtPath(path): + parent, child = findFramework(splitPath(path)) + return joinPath(parent[:-2]) + +def makedirs(path): + split = splitPath(path) + accum = [] + split.reverse() + while split: + accum.append(split.pop()) + newPath = joinPath(accum) + try: + os.mkdir(newPath) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + +def parseOtoolLine(line, execPath, root): + if not line.startswith('\t'): + return None, None, None, None + line = line[1:] + match = re.match('([@/].*) \(compatibility version.*\)', line) + path = match.group(1) + split = splitPath(path) + newExecPath = ['@executable_path', '..', 'Frameworks'] + newPath = execPath[:-1] + newPath.append('Frameworks') + if split[:3] == ['/', 'usr', 'lib'] or split[:2] == ['/', 'System']: + return None, None, None, None + if split[0] == '@executable_path': + split[:1] = execPath + if split[0] == '/' and not os.access(joinPath(split), os.F_OK): + split[:1] = root + isFramework = False + if not split[-1].endswith('.dylib'): + isFramework = True + split, framework = findFramework(split) + newPath.append(split[-1]) + newExecPath.append(split[-1]) + if isFramework: + newPath.extend(framework) + newExecPath.extend(framework) + split.extend(framework) + newPath = joinPath(newPath) + newExecPath = joinPath(newExecPath) + return joinPath(split), newPath, path, newExecPath + +def updateMachO(bin, execPath, root): + global qtPath + otoolOutput = subprocess.check_output([otool, '-L', bin]) + toUpdate = [] + for line in otoolOutput.split('\n'): + oldPath, newPath, oldExecPath, newExecPath = parseOtoolLine(line, execPath, root) + if not newPath: + continue + if os.access(newPath, os.F_OK): + if verbose: + print('Skipping copying {}, already done.'.format(oldPath)) + elif os.path.abspath(oldPath) != os.path.abspath(newPath): + if verbose: + print('Copying {} to {}...'.format(oldPath, newPath)) + parent, child = os.path.split(newPath) + makedirs(parent) + shutil.copy2(oldPath, newPath) + os.chmod(newPath, 0o644) + toUpdate.append((newPath, oldExecPath, newExecPath)) + if not qtPath and 'Qt' in oldPath: + qtPath = findQtPath(oldPath) + if verbose: + print('Found Qt path at {}.'.format(qtPath)) + for path, oldExecPath, newExecPath in toUpdate: + if path != bin: + updateMachO(path, execPath, root) + if verbose: + print('Updating Mach-O load from {} to {}...'.format(oldExecPath, newExecPath)) + subprocess.check_call([installNameTool, '-change', oldExecPath, newExecPath, bin]) + else: + if verbose: + print('Updating Mach-O id from {} to {}...'.format(oldExecPath, newExecPath)) + subprocess.check_call([installNameTool, '-id', newExecPath, bin]) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-R', '--root', metavar='ROOT', default='/', help='root directory to search') + parser.add_argument('-I', '--install-name-tool', metavar='INSTALL_NAME_TOOL', default='install_name_tool', help='path to install_name_tool') + parser.add_argument('-O', '--otool', metavar='OTOOL', default='otool', help='path to otool') + parser.add_argument('-p', '--qt-plugins', metavar='PLUGINS', default='', help='Qt plugins to include (comma-separated)') + parser.add_argument('-v', '--verbose', action='store_true', default=False, help='output more information') + parser.add_argument('bundle', help='application bundle to deploy') + args = parser.parse_args() + + otool = args.otool + installNameTool = args.install_name_tool + verbose = args.verbose + + try: + shutil.rmtree(os.path.join(args.bundle, 'Contents/Frameworks/')) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + for executable in os.listdir(os.path.join(args.bundle, 'Contents/MacOS')): + if executable.endswith('.dSYM'): + continue + fullPath = os.path.join(args.bundle, 'Contents/MacOS/', executable) + updateMachO(fullPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root)) + if args.qt_plugins: + try: + shutil.rmtree(os.path.join(args.bundle, 'Contents/PlugIns/')) + except OSError as e: + if e.errno != errno.ENOENT: + raise + makedirs(os.path.join(args.bundle, 'Contents/PlugIns')) + makedirs(os.path.join(args.bundle, 'Contents/Resources')) + with open(os.path.join(args.bundle, 'Contents/Resources/qt.conf'), 'w') as conf: + conf.write('[Paths]\nPlugins = PlugIns\n') + plugins = args.qt_plugins.split(',') + for plugin in plugins: + plugin = plugin.strip() + kind, plug = os.path.split(plugin) + newDir = os.path.join(args.bundle, 'Contents/PlugIns/', kind) + makedirs(newDir) + newPath = os.path.join(newDir, plug) + shutil.copy2(os.path.join(qtPath, 'plugins', plugin), newPath) + updateMachO(newPath, splitPath(os.path.join(args.bundle, 'Contents/MacOS')), splitPath(args.root))
@@ -40,7 +40,7 @@ proc.wait()
except: proc.kill() raise - if proc.returncode < 0: + if proc.returncode: print('Game crashed!', file=sys.stderr) return reader = csv.DictReader(proc.stdout)@@ -76,7 +76,7 @@
def collect_tests(self): roms = [] for f in os.listdir(self.cwd): - if f.endswith('.gba'): + if f.endswith('.gba') or f.endswith('.zip'): roms.append(f) roms.sort() for rom in roms:
@@ -0,0 +1,61 @@
+#!/bin/sh +# Clean up the caveats that CPack leaves behind +BINARY=mgba + +rmdep () { + local DEP=$1 + echo Removing dependency $DEP + sed -i~ "s/[^, ]*$DEP[^,]*//g" deb-temp/DEBIAN/control +} + +adddep() { + local DEP=$1 + echo Adding dependency $DEP + sed -i~ "s/^Depends: /&$DEP,/" deb-temp/DEBIAN/control +} + +while [ $# -gt 0 ]; do + DEB=$1 + dpkg-deb -R $DEB deb-temp + mv $DEB $DEB~ + sed -i~ s/mgba-// deb-temp/DEBIAN/control + PKG=`head -n1 deb-temp/DEBIAN/control | cut -f2 -d ' '` + echo Found pacakge $PKG + + case $PKG in + lib$BINARY) + rmdep sdl + rmdep qt + ;; + $BINARY-qt) + rmdep libav + rmdep libedit + rmdep libpng + rmdep libzip + rmdep libmagickwand + rmdep libswscale + rmdep zlib + adddep lib$BINARY + ;; + $BINARY-sdl) + rmdep libav + rmdep libedit + rmdep libpng + rmdep qt + rmdep libzip + rmdep libmagickwand + rmdep libswscale + rmdep zlib + adddep lib$BINARY + ;; + *) + echo Unknown package! + esac + + sed -i~ "s/,,*/,/g" deb-temp/DEBIAN/control + sed -i~ "s/,$//g" deb-temp/DEBIAN/control + sed -i~ "/^[^:]*: $/d" deb-temp/DEBIAN/control + rm deb-temp/DEBIAN/control~ + dpkg-deb -b deb-temp $DEB + shift +done