From f4dcd55f98188c9a9e812ccc51ef5d3f335943f1 Mon Sep 17 00:00:00 2001 From: Fabrice de Gans Date: Sat, 9 Mar 2024 14:38:47 -0800 Subject: [PATCH] [EXPERIMENTAL] Add Rust support --- .github/workflows/macos-build.yml | 3 + .github/workflows/msys2-build.yml | 3 + .github/workflows/ubuntu-build.yml | 3 + .github/workflows/visual-studio-build.yml | 3 + .gitignore | 4 + CMakeLists.txt | 18 +-- README.md | 1 - cmake/Flags.cmake | 21 +++ fex/CMakeLists.txt | 44 +++++++ installdeps | 54 ++------ rust-roolchain.toml | 3 + snapcraft.yaml | 1 - src/core/CMakeLists.txt | 5 +- src/core/gba/gbaCheats.cpp | 153 +++++++++------------- src/core/gba/gbaCheats.h | 15 +-- src/core/rust/Cargo.toml | 16 +++ src/core/rust/bindings.hpp | 76 +++++++++++ src/core/rust/build.rs | 17 +++ src/core/rust/src/base.rs | 7 + src/core/rust/src/base/panic.rs | 6 + src/core/rust/src/base/result.rs | 5 + src/core/rust/src/base/string.rs | 100 ++++++++++++++ src/core/rust/src/gba.rs | 2 + src/core/rust/src/gba/cheats.rs | 108 +++++++++++++++ src/core/rust/src/lib.rs | 22 ++++ src/sdl/CMakeLists.txt | 4 - src/wx/CMakeLists.txt | 2 +- src/wx/tests/tests.hpp | 2 +- tools/builder/core.sh | 1 - 29 files changed, 533 insertions(+), 166 deletions(-) create mode 100644 cmake/Flags.cmake create mode 100644 fex/CMakeLists.txt create mode 100644 rust-roolchain.toml create mode 100644 src/core/rust/Cargo.toml create mode 100644 src/core/rust/bindings.hpp create mode 100644 src/core/rust/build.rs create mode 100644 src/core/rust/src/base.rs create mode 100644 src/core/rust/src/base/panic.rs create mode 100644 src/core/rust/src/base/result.rs create mode 100644 src/core/rust/src/base/string.rs create mode 100644 src/core/rust/src/gba.rs create mode 100644 src/core/rust/src/gba/cheats.rs create mode 100644 src/core/rust/src/lib.rs diff --git a/.github/workflows/macos-build.yml b/.github/workflows/macos-build.yml index 73d04143..9a09dce3 100644 --- a/.github/workflows/macos-build.yml +++ b/.github/workflows/macos-build.yml @@ -11,6 +11,9 @@ jobs: runs-on: macos-latest steps: + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Checkout the code uses: actions/checkout@v2 with: diff --git a/.github/workflows/msys2-build.yml b/.github/workflows/msys2-build.yml index 805fcf9b..92c29747 100644 --- a/.github/workflows/msys2-build.yml +++ b/.github/workflows/msys2-build.yml @@ -15,6 +15,9 @@ jobs: shell: msys2 {0} steps: + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Checkout the code uses: actions/checkout@v2 with: diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml index 3e27b99f..c2af5bfc 100644 --- a/.github/workflows/ubuntu-build.yml +++ b/.github/workflows/ubuntu-build.yml @@ -10,6 +10,9 @@ jobs: cmake_options: ['', '-DENABLE_LINK=OFF', '-DENABLE_SDL=ON'] runs-on: ubuntu-latest steps: + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Checkout the code uses: actions/checkout@v2 with: diff --git a/.github/workflows/visual-studio-build.yml b/.github/workflows/visual-studio-build.yml index f512e194..258f11aa 100644 --- a/.github/workflows/visual-studio-build.yml +++ b/.github/workflows/visual-studio-build.yml @@ -12,6 +12,9 @@ jobs: cmake_options: ['', '-DENABLE_LINK=OFF', '-DENABLE_SDL=ON'] runs-on: windows-latest steps: + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Checkout the code uses: actions/checkout@v2 with: diff --git a/.gitignore b/.gitignore index afde9293..cb6defe3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ compile_commands.json # mac finder crap *.DS_Store + +# Rust build files +src/core/rust/target/ +src/core/rust/Cargo.lock diff --git a/CMakeLists.txt b/CMakeLists.txt index 4572c769..1dbed293 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) +# Rust library dependency +include(FetchContent) +FetchContent_Declare( + Corrosion + GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git + GIT_TAG v0.4 +) +FetchContent_MakeAvailable(Corrosion) + project(VBA-M C CXX) find_package(PkgConfig) @@ -263,15 +272,6 @@ add_compile_definitions(PKGDATADIR="${CMAKE_INSTALL_FULL_DATADIR}/vbam") add_compile_definitions(__STDC_FORMAT_MACROS) if(ENABLE_LINK) - # IPC linking code needs sem_timedwait which can be either in librt or pthreads - if(NOT WIN32) - find_library(RT_LIB rt) - if(RT_LIB) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${RT_LIB}) - set(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${RT_LIB}) - endif() - endif() - include(CheckFunctionExists) check_function_exists(sem_timedwait SEM_TIMEDWAIT) if(SEM_TIMEDWAIT) diff --git a/README.md b/README.md index 55fbe09b..049dda88 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,6 @@ Here is the complete list: | ENABLE_GBA_LOGGING | Enable extended GBA logging | ON | | ENABLE_DIRECT3D | Direct3D rendering for wxWidgets (Windows, **NOT IMPLEMENTED!!!**) | ON | | ENABLE_XAUDIO2 | Enable xaudio2 sound output for wxWidgets (Windows only) | ON | -| ENABLE_OPENAL | Enable OpenAL for the wxWidgets port | AUTO | | ENABLE_ASAN | Enable libasan sanitizers (by default address, only in debug mode) | OFF | | UPSTREAM_RELEASE | Do some release tasks, like codesigning, making zip and gpg sigs. | OFF | | BUILD_TESTING | Build the tests and enable ctest support. | ON | diff --git a/cmake/Flags.cmake b/cmake/Flags.cmake new file mode 100644 index 00000000..f19daa0f --- /dev/null +++ b/cmake/Flags.cmake @@ -0,0 +1,21 @@ +# Compiler stuff +include(ProcessorCount) +ProcessorCount(num_cpus) + +if(CMAKE_C_COMPILER_ID STREQUAL Clang AND CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT MSVC) + # TODO: This should also be done for clang-cl. + include(LLVMToolchain) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT MSVC) + include(Toolchain-gcc-clang) +elseif(MSVC) + include(Toolchain-msvc) +elseif(MINGW) + include(Toolchain-mingw) +endif() + +# Assembler flags +if(ASM_ENABLED) + string(REGEX REPLACE "" "-I${CMAKE_SOURCE_DIR}/src/filters/hq/asm/ -O1 -w-orphan-labels" CMAKE_ASM_NASM_COMPILE_OBJECT ${CMAKE_ASM_NASM_COMPILE_OBJECT}) +endif() diff --git a/fex/CMakeLists.txt b/fex/CMakeLists.txt new file mode 100644 index 00000000..498693e2 --- /dev/null +++ b/fex/CMakeLists.txt @@ -0,0 +1,44 @@ +#Do not use this file directly. Always use the top level CMakeLists.txt file +#File extractors so the user doesn't have to extract the rom before playing it + +# Source files definition +SET(SRC_FEX + 7z_C/7zAlloc.c + 7z_C/7zBuf.c + 7z_C/7zCrc.c + 7z_C/7zCrcOpt.c + 7z_C/7zDec.c + 7z_C/7zIn.c + 7z_C/7zStream.c + 7z_C/Bcj2.c + 7z_C/Bra86.c + 7z_C/Bra.c + 7z_C/CpuArch.c + 7z_C/Lzma2Dec.c + 7z_C/LzmaDec.c + 7z_C/Ppmd7.c + 7z_C/Ppmd7Dec.c + fex/Binary_Extractor.cpp + fex/blargg_common.cpp + fex/blargg_errors.cpp + fex/Data_Reader.cpp + fex/fex.cpp + fex/File_Extractor.cpp + fex/Gzip_Extractor.cpp + fex/Gzip_Reader.cpp + fex/Rar_Extractor.cpp + fex/Zip7_Extractor.cpp + fex/Zip_Extractor.cpp + fex/Zlib_Inflater.cpp +) + +ADD_LIBRARY( + fex + STATIC + ${SRC_FEX} +) + +add_dependencies(fex generate) + +target_include_directories(fex PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(fex PUBLIC ZLIB::ZLIB) diff --git a/installdeps b/installdeps index e313a9ad..4827800c 100755 --- a/installdeps +++ b/installdeps @@ -1,7 +1,6 @@ #!/bin/sh CMAKE=cmake -ENABLE_OPENAL=1 ENABLE_FFMPEG=1 main() { @@ -25,10 +24,6 @@ check_command_line_args() { usage quit 0 ;; - --no-openal) - ENABLE_OPENAL= - shift - ;; --no-ffmpeg) ENABLE_FFMPEG= shift @@ -355,9 +350,8 @@ debian_installdeps() { ;; esac - pkgs="build-essential g++ nasm cmake ccache gettext zlib1g-dev libgl1-mesa-dev libgettextpo-dev libsdl2-dev $sdl_lib libglu1-mesa-dev libglu1-mesa libgles2-mesa-dev libsfml-dev $sfml_libs $glew_lib $wx_libs libgtk2.0-dev libgtk-3-dev ccache zip ninja-build" + pkgs="build-essential g++ nasm cmake ccache gettext zlib1g-dev libgl1-mesa-dev libgettextpo-dev libsdl2-dev $sdl_lib libglu1-mesa-dev libglu1-mesa libgles2-mesa-dev libsfml-dev $sfml_libs $glew_lib $wx_libs libgtk2.0-dev libgtk-3-dev ccache zip ninja-build libopenal-dev" - [ -n "$ENABLE_OPENAL" ] && pkgs="$pkgs libopenal-dev" [ -n "$ENABLE_FFMPEG" ] && pkgs="$pkgs libavcodec-dev libavformat-dev libswscale-dev libavutil-dev $libswresample_dev" check sudo apt-get -qy install $pkgs @@ -407,8 +401,7 @@ debian_installdeps() { fi fi - deps="gcc zlib ffmpeg gettext sdl2 sfml openal wxwidgets" - [ -n "$ENABLE_OPENAL" ] && deps="$deps openal" + deps="gcc zlib ffmpeg gettext sdl2 sfml openal wxwidgets openal" [ -n "$ENABLE_FFMPEG" ] && deps="$deps ffmpeg" set -- @@ -510,9 +503,6 @@ fedora_installdeps() { *ffmpeg*) [ -z "$ENABLE_FFMPEG" ] && continue ;; - *openal*) - [ -z "$ENABLE_OPENAL" ] && continue - ;; esac pkg_arch= @@ -601,15 +591,9 @@ fedora_installdeps() { ;; esac # install static deps - for pkg in zlib gettext SDL2 wxWidgets3; do + for pkg in zlib gettext SDL2 wxWidgets3 openal-soft; do set -- "$@" "${target}-${pkg}-static" done - # install deps that are not available as static - if [ -n "$ENABLE_OPENAL" ]; then - for pkg in openal-soft; do - set -- "$@" "${target}-${pkg}" - done - fi # get the necessary win32 headers git submodule update --init --remote --recursive @@ -707,9 +691,6 @@ rhel_installdeps() { *ffmpeg*) [ -z "$ENABLE_FFMPEG" ] && continue ;; - *openal*) - [ -z "$ENABLE_OPENAL" ] && continue - ;; esac if [ -n "$amd64" ]; then @@ -790,15 +771,9 @@ rhel_installdeps() { ;; esac # install static deps - for pkg in zlib gettext SDL2 wxWidgets; do + for pkg in zlib gettext SDL2 wxWidgets openal-soft; do set -- "$@" "${target}-${pkg}-static" done - # install deps that are not available as static - if [ -n "$ENABLE_OPENAL" ]; then - for pkg in openal-soft; do - set -- "$@" "${target}-${pkg}" - done - fi # get the necessary win32 headers git submodule update --init --remote --recursive @@ -824,11 +799,9 @@ suse_installdeps() { tools="make cmake ccache nasm gettext-tools pkg-config ccache zip sfml2-devel ninja" - libs="gcc gcc-c++ libSDL2-devel wxWidgets-3_0-devel" # ffmpeg-devel + libs="gcc gcc-c++ libSDL2-devel wxWidgets-3_0-devel openal-soft-devel" # ffmpeg-devel - [ -n "$ENABLE_OPENAL" ] && libs="$libs openal-soft-devel" # ffmpeg requires packman repos - if [ "$target" = m32 ]; then libs=$(echo "$libs" | sed -E 's/([^ ]) ([^ ])/\1-32bit \2/g; s/$/-32bit/;') fi @@ -894,9 +867,8 @@ archlinux_installdeps() { $pacman -Q gtk3-classic >/dev/null 2>&1 && gtk=gtk3-classic - libs="zlib mesa gettext sdl2 wxgtk3 $gtk sfml" + libs="zlib mesa gettext sdl2 wxgtk3 $gtk sfml openal" - [ -n "$ENABLE_OPENAL" ] && libs="$libs openal" [ -n "$ENABLE_FFMPEG" ] && libs="$libs ffmpeg" if [ -z "$target" -o "$target" = m32 ]; then @@ -991,9 +963,7 @@ EOF fi done - deps="zlib gettext pkg-config sdl2 wxmsw" - - [ -n "$ENABLE_OPENAL" ] && deps="$deps openal" + deps="zlib gettext pkg-config sdl2 wxmsw openal" # and the actual deps for p in $deps; do @@ -1024,9 +994,7 @@ solus_installdeps() { check sudo eopkg -y install -c system.devel check sudo eopkg -y install git ccache ninja - set -- sdl2-devel wxwidgets-devel libgtk-2-devel libgtk-3-devel libglu-devel - - [ -n "$ENABLE_OPENAL" ] && set -- "$@" openal-soft-devel + set -- sdl2-devel wxwidgets-devel libgtk-2-devel libgtk-3-devel libglu-devel openal-soft-devel if [ -n "$amd64" -a "$target" = m32 ]; then info_msg 'Calculating dependencies, this will take a while..' @@ -1102,14 +1070,13 @@ gentoo_installdeps() { sys-devel/binutils \ media-libs/libsdl2 \ media-libs/libsfml \ + media-libs/openal \ x11-libs/wxGTK:$wx_slot \ sys-libs/zlib \ dev-util/pkgconf \ dev-lang/nasm \ dev-build/ninja" - [ -n "$ENABLE_OPENAL" ] && ebuilds="$ebuilds media-libs/openal" - [ -n "$ENABLE_FFMPEG" ] && ebuilds="$ebuilds media-video/ffmpeg" check sudo emerge -vna $ebuilds @@ -1152,9 +1119,8 @@ windows_installdeps() { ;; esac - pkgs="$pkgs SDL2 sfml wxWidgets3.2 zlib binutils cmake crt-git extra-cmake-modules headers-git make pkgconf tools-git windows-default-manifest libmangle-git ninja gdb ccache" + pkgs="$pkgs SDL2 sfml wxWidgets3.2 zlib binutils cmake crt-git extra-cmake-modules headers-git make pkgconf tools-git windows-default-manifest libmangle-git ninja gdb ccache openal" - [ -n "$ENABLE_OPENAL" ] && pkgs="$pkgs openal" [ -n "$ENABLE_FFMPEG" ] && pkgs="$pkgs ffmpeg" set -- diff --git a/rust-roolchain.toml b/rust-roolchain.toml new file mode 100644 index 00000000..2fc3eef2 --- /dev/null +++ b/rust-roolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +profile = "minimal" diff --git a/snapcraft.yaml b/snapcraft.yaml index e995e8b4..aaf50afd 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -51,7 +51,6 @@ parts: - libopenal-dev - libwxgtk3.0-gtk3-dev cmake-parameters: - - -DENABLE_OPENAL=ON - -DENABLE_SDL=OFF - -DCMAKE_INSTALL_PREFIX=/usr diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 275da503..fab6bc6c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -2,6 +2,9 @@ add_subdirectory(apu) add_subdirectory(base) add_subdirectory(fex) +# Set up "vbam_rust" library. +corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_LIST_DIR}/rust/Cargo.toml NO_STD) + # The vbam-core target contains both the Game Boy and Game Boy Advance # emulators. These should be broken down into 2 separate targets. The issue lies # with the Link and Sound emulation, which are tangled together between the two @@ -94,7 +97,7 @@ target_include_directories(vbam-core target_link_libraries(vbam-core PRIVATE vbam-core-apu vbam-fex - PUBLIC vbam-core-base ${ZLIB_LIBRARY} + PUBLIC vbam-core-base ${ZLIB_LIBRARY} vbam_rust ) if(ENABLE_DEBUGGER) diff --git a/src/core/gba/gbaCheats.cpp b/src/core/gba/gbaCheats.cpp index cbfc3aa3..166ec39c 100644 --- a/src/core/gba/gbaCheats.cpp +++ b/src/core/gba/gbaCheats.cpp @@ -6,8 +6,11 @@ #include "core/base/file_util.h" #include "core/base/message.h" #include "core/gba/gba.h" -#include "core/gba/gbaInline.h" #include "core/gba/gbaGlobals.h" +#include "core/gba/gbaInline.h" +#include "core/rust/bindings.hpp" + +using namespace core; /** * Gameshark code types: (based on AR v1.0) @@ -672,7 +675,7 @@ int cheatsCheckKeys(uint32_t keys, uint32_t extended) case GSA_16_BIT_ROM_PATCH: if ((cheatsList[i].status & 1) == 0) { if (CPUReadHalfWord(cheatsList[i].address) != cheatsList[i].value) { - cheatsList[i].oldValue = CPUReadHalfWord(cheatsList[i].address); + cheatsList[i].old_value = CPUReadHalfWord(cheatsList[i].address); cheatsList[i].status |= 1; CHEAT_PATCH_ROM_16BIT(cheatsList[i].address, cheatsList[i].value); } @@ -1280,6 +1283,33 @@ int cheatsCheckKeys(uint32_t keys, uint32_t extended) return ticks; } +void cheatsAdd(CheatsData data) { + if (cheatsNumber == MAX_CHEATS) { + return; + } + + int x = cheatsNumber; + cheatsList[x] = std::move(data); + switch (cheatsList[x].size) { + case INT_8_BIT_WRITE: + cheatsList[x].old_value = CPUReadByte(cheatsList[x].address); + break; + case INT_16_BIT_WRITE: + cheatsList[x].old_value = CPUReadHalfWord(cheatsList[x].address); + break; + case INT_32_BIT_WRITE: + cheatsList[x].old_value = CPUReadMemory(cheatsList[x].address); + break; + case CHEATS_16_BIT_WRITE: + cheatsList[x].old_value = CPUReadHalfWord(cheatsList[x].address); + break; + case CHEATS_32_BIT_WRITE: + cheatsList[x].old_value = CPUReadMemory(cheatsList[x].address); + break; + } + cheatsNumber++; +} + void cheatsAdd(const char* codeStr, const char* desc, uint32_t rawaddress, @@ -1304,19 +1334,19 @@ void cheatsAdd(const char* codeStr, // is taken care when it actually patches the ROM switch (cheatsList[x].size) { case INT_8_BIT_WRITE: - cheatsList[x].oldValue = CPUReadByte(address); + cheatsList[x].old_value = CPUReadByte(address); break; case INT_16_BIT_WRITE: - cheatsList[x].oldValue = CPUReadHalfWord(address); + cheatsList[x].old_value = CPUReadHalfWord(address); break; case INT_32_BIT_WRITE: - cheatsList[x].oldValue = CPUReadMemory(address); + cheatsList[x].old_value = CPUReadMemory(address); break; case CHEATS_16_BIT_WRITE: - cheatsList[x].oldValue = CPUReadHalfWord(address); + cheatsList[x].old_value = CPUReadHalfWord(address); break; case CHEATS_32_BIT_WRITE: - cheatsList[x].oldValue = CPUReadMemory(address); + cheatsList[x].old_value = CPUReadMemory(address); break; } cheatsNumber++; @@ -1331,33 +1361,33 @@ void cheatsDelete(int number, bool restore) if (restore) { switch (cheatsList[x].size) { case INT_8_BIT_WRITE: - CPUWriteByte(cheatsList[x].address, (uint8_t)cheatsList[x].oldValue); + CPUWriteByte(cheatsList[x].address, (uint8_t)cheatsList[x].old_value); break; case INT_16_BIT_WRITE: - CPUWriteHalfWord(cheatsList[x].address, (uint16_t)cheatsList[x].oldValue); + CPUWriteHalfWord(cheatsList[x].address, (uint16_t)cheatsList[x].old_value); break; case INT_32_BIT_WRITE: - CPUWriteMemory(cheatsList[x].address, cheatsList[x].oldValue); + CPUWriteMemory(cheatsList[x].address, cheatsList[x].old_value); break; case CHEATS_16_BIT_WRITE: if ((cheatsList[x].address >> 24) >= 0x08) { - CHEAT_PATCH_ROM_16BIT(cheatsList[x].address, cheatsList[x].oldValue); + CHEAT_PATCH_ROM_16BIT(cheatsList[x].address, cheatsList[x].old_value); } else { - CPUWriteHalfWord(cheatsList[x].address, cheatsList[x].oldValue); + CPUWriteHalfWord(cheatsList[x].address, cheatsList[x].old_value); } break; case CHEATS_32_BIT_WRITE: if ((cheatsList[x].address >> 24) >= 0x08) { - CHEAT_PATCH_ROM_32BIT(cheatsList[x].address, cheatsList[x].oldValue); + CHEAT_PATCH_ROM_32BIT(cheatsList[x].address, cheatsList[x].old_value); } else { - CPUWriteMemory(cheatsList[x].address, cheatsList[x].oldValue); + CPUWriteMemory(cheatsList[x].address, cheatsList[x].old_value); } /* fallthrough */ case GSA_16_BIT_ROM_PATCH: if (cheatsList[x].status & 1) { cheatsList[x].status &= ~1; CHEAT_PATCH_ROM_16BIT(cheatsList[x].address, - cheatsList[x].oldValue); + cheatsList[x].old_value); } break; case GSA_16_BIT_ROM_PATCH2C: @@ -1403,7 +1433,7 @@ void cheatsDisable(int i) if (cheatsList[i].status & 1) { cheatsList[i].status &= ~1; CHEAT_PATCH_ROM_16BIT(cheatsList[i].address, - cheatsList[i].oldValue); + cheatsList[i].old_value); } break; case GSA_16_BIT_ROM_PATCH2C: @@ -1424,72 +1454,15 @@ void cheatsDisable(int i) bool cheatsVerifyCheatCode(const char* code, const char* desc) { - size_t len = strlen(code); - if (len != 11 && len != 13 && len != 17) { - systemMessage(MSG_INVALID_CHEAT_CODE, N_("Invalid cheat code '%s': wrong length"), code); + auto converted = core::decode_code(core::GbaCheatsType::Generic, code, desc); + if (converted.tag == + core::Result_CheatsData__CheatsDecodeError_Tag::Err_CheatsData__CheatsDecodeError) { + systemMessage(MSG_INVALID_CHEAT_CODE, N_("Invalid cheat code '%s': %s"), code, + converted.err); return false; } - if (code[8] != ':') { - systemMessage(MSG_INVALID_CHEAT_CODE, N_("Invalid cheat code '%s': no colon"), code); - return false; - } - - size_t i; - for (i = 0; i < 8; i++) { - if (!CHEAT_IS_HEX(code[i])) { - // wrong cheat - systemMessage(MSG_INVALID_CHEAT_CODE, - N_("Invalid cheat code '%s': first part is not hex"), code); - return false; - } - } - for (i = 9; i < len; i++) { - if (!CHEAT_IS_HEX(code[i])) { - // wrong cheat - systemMessage(MSG_INVALID_CHEAT_CODE, - N_("Invalid cheat code '%s' second part is not hex"), code); - return false; - } - } - - uint32_t address = 0; - uint32_t value = 0; - - char buffer[10]; - strncpy(buffer, code, 8); - buffer[8] = 0; - sscanf(buffer, "%x", &address); - - switch (address >> 24) { - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - break; - default: - systemMessage(MSG_INVALID_CHEAT_CODE_ADDRESS, - N_("Invalid cheat code address: %08x"), - address); - return false; - } - - strncpy(buffer, &code[9], 8); - sscanf(buffer, "%x", &value); - int type = 0; - if (len == 13) - type = 114; - if (len == 17) - type = 115; - cheatsAdd(code, desc, address, address, value, type, type); + cheatsAdd(std::move(converted.ok)); return true; } @@ -2606,7 +2579,7 @@ void cheatsReadGame(gzFile file, int version) utilGzRead(file, &cheatsList[i].address, sizeof(uint32_t)); cheatsList[i].rawaddress = cheatsList[i].address; utilGzRead(file, &cheatsList[i].value, sizeof(uint32_t)); - utilGzRead(file, &cheatsList[i].oldValue, sizeof(uint32_t)); + utilGzRead(file, &cheatsList[i].old_value, sizeof(uint32_t)); utilGzRead(file, &cheatsList[i].codestring, 20 * sizeof(char)); utilGzRead(file, &cheatsList[i].desc, 32 * sizeof(char)); } @@ -2743,7 +2716,7 @@ bool cheatsLoadCheatList(const char* file) FREAD_UNCHECKED(&cheatsList[i].address, 1, sizeof(uint32_t), f); cheatsList[i].rawaddress = cheatsList[i].address; FREAD_UNCHECKED(&cheatsList[i].value, 1, sizeof(uint32_t), f); - FREAD_UNCHECKED(&cheatsList[i].oldValue, 1, sizeof(uint32_t), f); + FREAD_UNCHECKED(&cheatsList[i].old_value, 1, sizeof(uint32_t), f); FREAD_UNCHECKED(&cheatsList[i].codestring, 1, 20 * sizeof(char), f); if (fread(&cheatsList[i].desc, 1, 32 * sizeof(char), f) != 32 * sizeof(char)) { fclose(f); @@ -2829,9 +2802,9 @@ void cheatsWriteMemory(uint32_t address, uint32_t value) { if (cheatsNumber == 0) { int type = cheatsGetType(address); - uint32_t oldValue = debuggerReadMemory(address); - if (type == 1 || (type == 2 && oldValue != value)) { - debuggerBreakOnWrite(address, oldValue, value, 2, type); + uint32_t old_value = debuggerReadMemory(address); + if (type == 1 || (type == 2 && old_value != value)) { + debuggerBreakOnWrite(address, old_value, value, 2, type); cpuNextEvent = 0; } debuggerWriteMemory(address, value); @@ -2842,9 +2815,9 @@ void cheatsWriteHalfWord(uint32_t address, uint16_t value) { if (cheatsNumber == 0) { int type = cheatsGetType(address); - uint16_t oldValue = debuggerReadHalfWord(address); - if (type == 1 || (type == 2 && oldValue != value)) { - debuggerBreakOnWrite(address, oldValue, value, 1, type); + uint16_t old_value = debuggerReadHalfWord(address); + if (type == 1 || (type == 2 && old_value != value)) { + debuggerBreakOnWrite(address, old_value, value, 1, type); cpuNextEvent = 0; } debuggerWriteHalfWord(address, value); @@ -2855,9 +2828,9 @@ void cheatsWriteByte(uint32_t address, uint8_t value) { if (cheatsNumber == 0) { int type = cheatsGetType(address); - uint8_t oldValue = debuggerReadByte(address); - if (type == 1 || (type == 2 && oldValue != value)) { - debuggerBreakOnWrite(address, oldValue, value, 0, type); + uint8_t old_value = debuggerReadByte(address); + if (type == 1 || (type == 2 && old_value != value)) { + debuggerBreakOnWrite(address, old_value, value, 0, type); cpuNextEvent = 0; } debuggerWriteByte(address, value); diff --git a/src/core/gba/gbaCheats.h b/src/core/gba/gbaCheats.h index 8eadee7f..9c536431 100644 --- a/src/core/gba/gbaCheats.h +++ b/src/core/gba/gbaCheats.h @@ -9,18 +9,7 @@ #include #endif // defined(__LIBRETRO__) -struct CheatsData { - int code; - int size; - int status; - bool enabled; - uint32_t rawaddress; - uint32_t address; - uint32_t value; - uint32_t oldValue; - char codestring[20]; - char desc[32]; -}; +#include "core/rust/bindings.hpp" void cheatsAdd(const char* codeStr, const char* desc, uint32_t rawaddress, uint32_t address, uint32_t value, int code, int size); @@ -47,6 +36,6 @@ void cheatsWriteByte(uint32_t address, uint8_t value); int cheatsCheckKeys(uint32_t keys, uint32_t extended); extern int cheatsNumber; -extern CheatsData cheatsList[MAX_CHEATS]; +extern core::CheatsData cheatsList[MAX_CHEATS]; #endif // VBAM_CORE_GBA_GBACHEATS_H_ diff --git a/src/core/rust/Cargo.toml b/src/core/rust/Cargo.toml new file mode 100644 index 00000000..3503dea9 --- /dev/null +++ b/src/core/rust/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "vbam_rust" +version = "0.1.0" + +[lib] +name = "vbam_rust" +crate-type = ["staticlib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[build-dependencies] +cbindgen = "0.26.0" diff --git a/src/core/rust/bindings.hpp b/src/core/rust/bindings.hpp new file mode 100644 index 00000000..59c93827 --- /dev/null +++ b/src/core/rust/bindings.hpp @@ -0,0 +1,76 @@ +#ifndef VBAM_CORE_RUST_H_ +#define VBAM_CORE_RUST_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +namespace core { +#endif // __cplusplus + +typedef enum CheatsDecodeError { + UnsupportedType, + InvalidLength, + InvalidCharacter, + AddressNotHex, + ValueNotHex, +} CheatsDecodeError; + +typedef enum GbaCheatsType { + Generic, + GameSharkV2, + CodeBreaker, + ActionReplay, +} GbaCheatsType; + +typedef struct CheatsData { + int code; + int size; + int status; + bool enabled; + uint32_t rawaddress; + uint32_t address; + uint32_t value; + uint32_t old_value; + char codestring[20]; + char desc[32]; +} CheatsData; + +typedef enum Result_CheatsData__CheatsDecodeError_Tag { + Ok_CheatsData__CheatsDecodeError, + Err_CheatsData__CheatsDecodeError, +} Result_CheatsData__CheatsDecodeError_Tag; + +typedef struct Result_CheatsData__CheatsDecodeError { + Result_CheatsData__CheatsDecodeError_Tag tag; + union { + struct { + struct CheatsData ok; + }; + struct { + enum CheatsDecodeError err; + }; + }; +} Result_CheatsData__CheatsDecodeError; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +enum GbaCheatsType some_type(void); + +struct Result_CheatsData__CheatsDecodeError decode_code(enum GbaCheatsType cheat_type, + const char *code_string, + const char *description); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#ifdef __cplusplus +} // namespace core +#endif // __cplusplus + +#endif /* VBAM_CORE_RUST_H_ */ diff --git a/src/core/rust/build.rs b/src/core/rust/build.rs new file mode 100644 index 00000000..6c8aa940 --- /dev/null +++ b/src/core/rust/build.rs @@ -0,0 +1,17 @@ +extern crate cbindgen; + +use std::env; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(cbindgen::Language::C) + .with_cpp_compat(true) + .with_include_guard("VBAM_CORE_RUST_H_") + .with_namespace("core") + .generate() + .expect("Unable to generate bindings") + .write_to_file("bindings.hpp"); +} diff --git a/src/core/rust/src/base.rs b/src/core/rust/src/base.rs new file mode 100644 index 00000000..88362576 --- /dev/null +++ b/src/core/rust/src/base.rs @@ -0,0 +1,7 @@ +mod panic; + +mod result; +pub use crate::base::result::Result; + +mod string; +pub(crate) use crate::base::string::StrLike; diff --git a/src/core/rust/src/base/panic.rs b/src/core/rust/src/base/panic.rs new file mode 100644 index 00000000..0f0686a9 --- /dev/null +++ b/src/core/rust/src/base/panic.rs @@ -0,0 +1,6 @@ +#[panic_handler] +#[cfg(not(test))] +/// Panic handler, called on panic. Does nothing. +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/src/core/rust/src/base/result.rs b/src/core/rust/src/base/result.rs new file mode 100644 index 00000000..51040fba --- /dev/null +++ b/src/core/rust/src/base/result.rs @@ -0,0 +1,5 @@ +#[repr(C)] +pub enum Result { + Ok(T), + Err(E), +} diff --git a/src/core/rust/src/base/string.rs b/src/core/rust/src/base/string.rs new file mode 100644 index 00000000..573ba4b6 --- /dev/null +++ b/src/core/rust/src/base/string.rs @@ -0,0 +1,100 @@ +use core::ffi::c_char; +use core::ffi::CStr; + +pub(crate) trait CharLike { + fn to_hex(&self) -> Option; + fn is_hex(&self) -> bool { + self.to_hex().is_some() + } +} + +impl CharLike for u8 { + fn to_hex(&self) -> Option { + if *self >= b'0' && *self <= b'9' { + Some(*self - b'0') + } else if *self >= b'A' && *self <= b'F' { + Some(*self - b'A' + 10) + } else { + None + } + } +} + +pub(crate) trait StrLike { + fn to_hex(&self) -> Option; + fn clone_buffer(&self) -> [c_char; LEN]; +} + +impl StrLike for [u8] { + fn to_hex(&self) -> Option { + let mut result: u32 = 0; + for byte in self.iter() { + if let Some(value) = byte.to_hex() { + result = result.wrapping_mul(16).wrapping_add(value as u32); + } else { + return None; + } + } + Some(result) + } + + fn clone_buffer(&self) -> [c_char; LEN] { + let mut buffer = [0; LEN]; + let mut i: usize = 0; + for byte in self.iter() { + if i == LEN { + break; + } + buffer[i] = *byte as c_char; + i += 1; + } + if i == LEN { + buffer[LEN - 1] = 0; + } else { + buffer[i] = 0; + } + buffer + } +} + +impl StrLike for CStr { + fn to_hex(&self) -> Option { + self.to_bytes().to_hex() + } + + fn clone_buffer(&self) -> [c_char; LEN] { + self.to_bytes().clone_buffer() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_char_to_hex() { + assert_eq!(b'0'.to_hex(), Some(0)); + assert_eq!(b'9'.to_hex(), Some(9)); + assert_eq!(b'A'.to_hex(), Some(10)); + assert_eq!(b'F'.to_hex(), Some(15)); + assert_eq!(b'G'.to_hex(), None); + } + + #[test] + fn test_str_to_hex() { + assert_eq!([].to_hex(), Some(0)); + assert_eq!([b'0'].to_hex(), Some(0)); + assert_eq!([b'1', b'0'].to_hex(), Some(16)); + assert_eq!([b'F', b'F'].to_hex(), Some(255)); + assert_eq!([b'G'].to_hex(), None); + assert_eq!(b"ABCDEF".to_hex(), Some(11259375)); + } + + #[test] + fn test_str_clone_buffer() { + assert_eq!(b"test".clone_buffer(), [116, 101, 115, 116, 0]); + assert_eq!(b"test".clone_buffer(), [116, 101, 0]); + assert_eq!(b"test".clone_buffer(), [0]); + assert_eq!(b"".clone_buffer(), [0]); + } +} diff --git a/src/core/rust/src/gba.rs b/src/core/rust/src/gba.rs new file mode 100644 index 00000000..ac19f0d3 --- /dev/null +++ b/src/core/rust/src/gba.rs @@ -0,0 +1,2 @@ +mod cheats; +pub use crate::gba::cheats::GbaCheatsType; \ No newline at end of file diff --git a/src/core/rust/src/gba/cheats.rs b/src/core/rust/src/gba/cheats.rs new file mode 100644 index 00000000..063f8bca --- /dev/null +++ b/src/core/rust/src/gba/cheats.rs @@ -0,0 +1,108 @@ +use core::ffi::c_char; +use core::ffi::c_int; +use core::ffi::CStr; + +use crate::base::StrLike; +use crate::prelude::*; + +#[repr(C)] +pub enum GbaCheatsType { + Generic, + GameSharkV2, + CodeBreaker, + ActionReplay, +} + +impl GbaCheatsType { + #[no_mangle] + pub extern "C" fn some_type() -> GbaCheatsType { + GbaCheatsType::Generic + } +} + +#[repr(C)] +pub struct CheatsData { + code: c_int, + size: c_int, + status: c_int, + enabled: bool, + rawaddress: u32, + address: u32, + value: u32, + old_value: u32, + codestring: [c_char; 20], + desc: [c_char; 32], +} + +#[repr(C)] +pub enum CheatsDecodeError { + UnsupportedType, + InvalidLength, + InvalidCharacter, + AddressNotHex, + ValueNotHex, +} + +impl CheatsData { + fn decode_generic(code_string: &CStr, description: &CStr) -> Result { + let code_length = code_string.to_bytes().len(); + if code_length != 11 && code_length != 13 && code_length != 17 { + return Err(CheatsDecodeError::InvalidLength); + } + + if code_string.to_bytes()[8] != b':' { + return Err(CheatsDecodeError::InvalidCharacter); + } + + let address = + match code_string.to_bytes()[..8].to_hex() { + Some(address) => address, + None => { + return Err(CheatsDecodeError::AddressNotHex); + } + }; + + let value = + match code_string.to_bytes()[9..].to_hex() { + Some(value) => value, + None => { + return Err(CheatsDecodeError::ValueNotHex); + } + }; + + let code = if code_length == 1 { + 0 + } else if code_length == 13 { + 114 + } else { + 115 + }; + + Ok(CheatsData { + code, + size: code, + status: 0, + enabled: true, + rawaddress: address, + address, + value, + old_value: 0, + codestring: code_string.clone_buffer(), + desc: description.clone_buffer(), + }) + } + + #[no_mangle] + pub extern "C" fn decode_code( + cheat_type: GbaCheatsType, + code_string: *const c_char, + description: *const c_char, + ) -> Result { + let code = unsafe { CStr::from_ptr(code_string) }; + let description = unsafe { CStr::from_ptr(description) }; + match cheat_type { + GbaCheatsType::Generic => CheatsData::decode_generic(code, description), + _ => Err(CheatsDecodeError::UnsupportedType), + } + } +} diff --git a/src/core/rust/src/lib.rs b/src/core/rust/src/lib.rs new file mode 100644 index 00000000..6a5f48ac --- /dev/null +++ b/src/core/rust/src/lib.rs @@ -0,0 +1,22 @@ +//! A crate implementing part of the VBA-M core in Rust. This is meant to be +//! used as a library in the VBA-M core. +//! +//! Guidelines: +//! * `no_std`. In particular, this means no I/O and no file I/O. It's fine to +//! pass buffers of memory to and from the Rust code, but the Rust code itself +//! should not perform any I/O. +//! * Core code only. We may want to use Rust in other parts of the codebase but +//! let's leave it at the core for now. +//! * Exported types must be `#[repr(C)]`. Exported functions must be `extern +//! "C"` with `[no_mangle]`. + +#![no_std] + +pub mod base; +pub mod gba; + +mod prelude { + pub use crate::base::Result; + pub use crate::base::Result::Err; + pub use crate::base::Result::Ok; +} diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index 33caa609..2e6f4b84 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -34,10 +34,6 @@ if(MSVC) ) endif() -if(ENABLE_LIRC) - set(LIRC_CLIENT_LIBRARY lirc_client) -endif() - target_link_libraries(vbam vbam-core vbam-components-audio-sdl diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index 5f391379..4763ff08 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -427,7 +427,7 @@ host_compile(${CMAKE_CURRENT_SOURCE_DIR}/bin2c.c ${BIN2C}) # Override wxrc when cross-compiling. if(CMAKE_HOST_WIN32 AND CMAKE_CROSSCOMPILING) - set(WXRC ${CMAKE_SOURCE_DIR}/dependencies/wxrc/wxrc.exe) + find_program(WXRC NAMES wxrc) endif() # Configure wxrc. diff --git a/src/wx/tests/tests.hpp b/src/wx/tests/tests.hpp index f0dcfcc9..82a9d555 100644 --- a/src/wx/tests/tests.hpp +++ b/src/wx/tests/tests.hpp @@ -8,6 +8,6 @@ #define DOCTEST_THREAD_LOCAL // Avoid MinGW thread_local bug. #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include "doctest.h" +#include #endif diff --git a/tools/builder/core.sh b/tools/builder/core.sh index 23e2fa4b..ad7b2822 100644 --- a/tools/builder/core.sh +++ b/tools/builder/core.sh @@ -524,7 +524,6 @@ setup() { # binary smaller. if [ "$target_os" = windows ] && [ "$target_bits" -eq 32 ]; then BUILD_FFMPEG= - PROJECT_ARGS="$PROJECT_ARGS -DENABLE_OPENAL=NO" fi if [ -z "$BUILD_FFMPEG" ]; then