diff --git a/.github/workflows/dockcross.yml b/.github/workflows/dockcross.yml index a314e6b..5c069bb 100644 --- a/.github/workflows/dockcross.yml +++ b/.github/workflows/dockcross.yml @@ -83,7 +83,7 @@ jobs: - name: Setup tmate session if failed if: ${{ failure() }} - uses: mxschmitt/action-tmate@v3 + uses: owenthereal/action-upterm@v1 build: @@ -177,7 +177,7 @@ jobs: ./dockcross bash -c "echo hello world" # - name: Setup tmate session detached - # uses: mxschmitt/action-tmate@v3 + # uses: owenthereal/action-upterm@v1 # with: # detached: true @@ -204,7 +204,7 @@ jobs: # - name: Setup tmate session to test android-emulator on x86 and x86_64 # if: (startsWith(matrix.dockcross_image, 'android') && contains(matrix.dockcross_image,'x86')) - # uses: mxschmitt/action-tmate@v3 + # uses: owenthereal/action-upterm@v1 # with: # detached: true @@ -245,7 +245,7 @@ jobs: - name: Setup tmate session if failed if: ${{ failure() }} - uses: mxschmitt/action-tmate@v3 + uses: owenthereal/action-upterm@v1 # - name: get_executable # id: get_executable @@ -320,7 +320,7 @@ jobs: - name: Setup tmate session if failed if: ${{ failure() }} - uses: mxschmitt/action-tmate@v3 + uses: owenthereal/action-upterm@v1 finalize_release: needs: [build, create_release] diff --git a/.gitignore b/.gitignore index 4d8acf4..6f1c9c7 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ dockcross_lin* dockcross_android* .cliffi_history -.cliffi_init \ No newline at end of file +.cliffi_init +build_check/ +build_android_test/ +dockcross-android-x86_64 diff --git a/CMakeLists.txt b/CMakeLists.txt index ed26856..1ca573f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,7 +143,29 @@ target_include_directories(cliffi_common_deps INTERFACE target_link_libraries(cliffi PRIVATE cliffi_common_deps) -# Compile cliffi_testlib.c into a shared library +if(ANDROID) + target_sources(cliffi PRIVATE + src/android/jni_support.c + src/android/jni_impl.c + src/android/FalsoJNI/FalsoJNI.c + src/android/FalsoJNI/FalsoJNI_ImplBridge.c + src/android/FalsoJNI/FalsoJNI_Logger.c + src/android/FalsoJNI/ConvertUTF.c + ) + target_include_directories(cliffi PRIVATE src/android/FalsoJNI) + # Required: signal chain symbols must be visible to libandroid_runtime.so + target_link_options(cliffi PRIVATE -Wl,--export-dynamic) + # FalsoJNI vendored files: suppress warnings that are harmless in stub/upstream code. + # -Wunknown-pragmas: CLion IDE pragmas; -Wunused-parameter: stub impls; -Wsign-compare: int vs size_t loops. + # Pointer-to-int-cast warnings are NOT suppressed — those were real bugs and have been fixed. + set_source_files_properties( + src/android/FalsoJNI/FalsoJNI.c + src/android/FalsoJNI/FalsoJNI_ImplBridge.c + src/android/FalsoJNI/FalsoJNI_Logger.c + src/android/FalsoJNI/ConvertUTF.c + PROPERTIES COMPILE_OPTIONS "-Wno-unknown-pragmas;-Wno-unused-parameter;-Wno-sign-compare" + ) +endif() add_library(cliffitest SHARED test/lib/cliffi_testlib.c) # Set the output name of the library to match the platform conventions @@ -182,6 +204,18 @@ target_link_libraries(cliffi_unit_tests PRIVATE ) target_compile_definitions(cliffi_unit_tests PRIVATE CLIFFI_UNIT_TESTING) # this prevents main() from main.c from being compiled +if(ANDROID) + target_sources(cliffi_unit_tests PRIVATE + src/android/jni_support.c + src/android/jni_impl.c + src/android/FalsoJNI/FalsoJNI.c + src/android/FalsoJNI/FalsoJNI_ImplBridge.c + src/android/FalsoJNI/FalsoJNI_Logger.c + src/android/FalsoJNI/ConvertUTF.c + ) + target_include_directories(cliffi_unit_tests PRIVATE src/android/FalsoJNI) + target_link_options(cliffi_unit_tests PRIVATE -Wl,--export-dynamic) +endif() add_test(NAME cliffi_unit_tests COMMAND cliffi_unit_tests) diff --git a/dev_android.sh b/dev_android.sh new file mode 100755 index 0000000..4ef1b57 --- /dev/null +++ b/dev_android.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# dev_android.sh — Local Android build and test helper for cliffi. +# +# Mirrors what the CI does (dockcross cross-compile + ADB push/test) so you +# can iterate without pushing to GitHub. +# +# Prerequisites: +# docker — for dockcross cross-compilation +# adb — Android Debug Bridge (from Android SDK platform-tools) +# A running emulator or connected device accessible via adb +# +# Quickstart for x86_64 emulator (easiest, no KVM quirks on Linux): +# 1. Install Android Studio (or sdkmanager), then: +# sdkmanager "system-images;android-30;google_apis;x86_64" +# avdmanager create avd -n test -k "system-images;android-30;google_apis;x86_64" +# emulator -avd test -no-window & +# 2. ./dev_android.sh build # cross-compile inside dockcross +# 3. ./dev_android.sh push # adb push cliffi + testlib to device +# 4. ./dev_android.sh test # run ctest via adb +# 5. ./dev_android.sh all # build + push + test in one go + +set -euo pipefail + +# ── Config ─────────────────────────────────────────────────────────────────── + +# Change this to android-arm64, android-arm, etc. for other targets. +# x86_64 is recommended for local emulator (AVD) use. +ARCH="${CLIFFI_ANDROID_ARCH:-android-x86_64}" + +TARGET_DIR=/data/local/tmp +DOCKCROSS_IMAGE="dockcross/${ARCH}" +DOCKCROSS_SCRIPT="./dockcross-${ARCH}" +BUILD_DIR="build-${ARCH}" + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +log() { echo "[dev_android] $*"; } +die() { echo "[dev_android] ERROR: $*" >&2; exit 1; } + +check_adb() { + adb devices | grep -v "List of" | grep -q "device$" \ + || die "No Android device/emulator found. Start an emulator or connect a device." +} + +# ── Commands ────────────────────────────────────────────────────────────────── + +cmd_setup() { + log "Pulling dockcross image: ${DOCKCROSS_IMAGE}" + docker run --rm "${DOCKCROSS_IMAGE}" > "${DOCKCROSS_SCRIPT}" + chmod +x "${DOCKCROSS_SCRIPT}" + log "Dockcross script written to ${DOCKCROSS_SCRIPT}" +} + +cmd_build() { + if [ ! -f "${DOCKCROSS_SCRIPT}" ]; then + log "Dockcross script not found — running setup first" + cmd_setup + fi + + log "Cross-compiling for ${ARCH} inside dockcross..." + "${DOCKCROSS_SCRIPT}" bash -c " + set -e + ./profile_script.sh + mkdir -p ${BUILD_DIR} + rm -rf ${BUILD_DIR}/* CMakeCache.txt CMakeFiles + conan install . --output-folder=${BUILD_DIR} --build=missing -pr build -g=CMakeDeps + cd ${BUILD_DIR} + cmake .. -DUSE_FIND_PACKAGE=ON -DCMAKE_C_FLAGS='-Werror' + make -j\$(nproc) + " + log "Build complete: ${BUILD_DIR}/cliffi" +} + +cmd_push() { + check_adb + log "Pushing binaries to device at ${TARGET_DIR}" + adb push "${BUILD_DIR}/cliffi" "${TARGET_DIR}/cliffi" + adb push "${BUILD_DIR}/libcliffi_test.so" "${TARGET_DIR}/libcliffi_test.so" + adb shell chmod +x "${TARGET_DIR}/cliffi" + + # Unit test binary (may not exist on build-only passes) + if [ -f "${BUILD_DIR}/cliffi_unit_tests" ]; then + adb push "${BUILD_DIR}/cliffi_unit_tests" "${TARGET_DIR}/cliffi_unit_tests" + adb shell chmod +x "${TARGET_DIR}/cliffi_unit_tests" + fi + + # .cliffi_init if present + if [ -f "${BUILD_DIR}/.cliffi_init" ]; then + adb push "${BUILD_DIR}/.cliffi_init" "${TARGET_DIR}/.cliffi_init" + fi + + log "Push complete." +} + +cmd_test() { + check_adb + log "Running ctest (redirecting test execution via adb)..." + # run_on_adb.sh is the per-test wrapper used by ctest's COMMAND + cd "${BUILD_DIR}" + ctest --output-on-failure --parallel \ + || ctest --rerun-failed --output-on-failure --extra-verbose +} + +cmd_shell() { + check_adb + log "Opening adb shell on device" + adb shell "cd ${TARGET_DIR} && sh" +} + +cmd_run() { + # Pass any extra args directly to cliffi on the device + check_adb + shift # remove 'run' from args + adb shell "cd ${TARGET_DIR} && ./cliffi $*" +} + +cmd_all() { + cmd_build + cmd_push + cmd_test +} + +# ── Dispatch ───────────────────────────────────────────────────────────────── + +COMMAND="${1:-help}" +case "${COMMAND}" in + setup) cmd_setup ;; + build) cmd_build ;; + push) cmd_push ;; + test) cmd_test ;; + shell) cmd_shell ;; + run) cmd_run "$@" ;; + all) cmd_all ;; + help|*) + cat <<'EOF' +Usage: ./dev_android.sh [args] + +Commands: + setup Pull dockcross image and write helper script + build Cross-compile cliffi for Android inside dockcross + push adb-push built binaries to running emulator/device + test Run ctest suite on device via adb + shell Open an interactive shell on the device at /data/local/tmp + run ... Run cliffi on device with given args + all build + push + test + +Environment: + CLIFFI_ANDROID_ARCH dockcross image suffix (default: android-x86_64) + Options: android-arm, android-arm64, + android-x86, android-x86_64 + +Examples: + CLIFFI_ANDROID_ARCH=android-arm64 ./dev_android.sh build + ./dev_android.sh all + ./dev_android.sh run libc.so i strlen hello + +Android emulator quickstart (x86_64 on Linux with KVM): + sdkmanager "system-images;android-30;google_apis;x86_64" + avdmanager create avd -n cliffi_test -k "system-images;android-30;google_apis;x86_64" + emulator -avd cliffi_test -no-window -no-audio & + adb wait-for-device + ./dev_android.sh all +EOF + ;; +esac diff --git a/run_on_adb.sh b/run_on_adb.sh old mode 100644 new mode 100755 diff --git a/src/android/FalsoJNI/ConvertUTF.c b/src/android/FalsoJNI/ConvertUTF.c new file mode 100644 index 0000000..bcfc7e1 --- /dev/null +++ b/src/android/FalsoJNI/ConvertUTF.c @@ -0,0 +1,549 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + + +#include "ConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_HIGH_END (UTF32)0xDBFF +#define UNI_SUR_LOW_START (UTF32)0xDC00 +#define UNI_SUR_LOW_END (UTF32)0xDFFF +#define false 0 +#define true 1 + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; +#ifdef CVTUTF_DEBUG +if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); +} +#endif + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 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,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,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,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, 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,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,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + + // TPN: substitute all control characters except for NULL, TAB, LF or CR + if (ch && (ch != (UTF32)0x09) && (ch != (UTF32)0x0a) && (ch != (UTF32)0x0d) && (ch < (UTF32)0x20) ) { + ch = (UTF32)0x3f; + } + // TPN: filter out byte order marks and invalid character 0xFFFF + if((ch == (UTF32)0xFEFF) || (ch == (UTF32)0xFFFE)|| (ch == (UTF32)0xFFFF)) { + continue; + } + + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +Boolean isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +/* + * Exported function to return whether a UTF-8 sequence is legal or not. + * This is not used here; it's just exported. + */ +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ diff --git a/src/android/FalsoJNI/ConvertUTF.h b/src/android/FalsoJNI/ConvertUTF.h new file mode 100644 index 0000000..e264915 --- /dev/null +++ b/src/android/FalsoJNI/ConvertUTF.h @@ -0,0 +1,149 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ + +typedef unsigned long UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +typedef unsigned char Boolean; /* 0 or 1 */ + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +/* This is for C++ and does no harm in C */ +#ifdef __cplusplus +extern "C" { +#endif + +ConversionResult ConvertUTF8toUTF16 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF8 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF16toUTF32 ( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF16 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + +Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + +#ifdef __cplusplus +} +#endif + +/* --------------------------------------------------------------------- */ diff --git a/src/android/FalsoJNI/FalsoJNI.c b/src/android/FalsoJNI/FalsoJNI.c new file mode 100644 index 0000000..3286ba0 --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI.c @@ -0,0 +1,2020 @@ +/* + * FalsoJNI.c + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2022-2023 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * Contains parts of Dalvik implementation of JNI interfaces, + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +#pragma ide diagnostic ignored "UnusedParameter" + +#include +#include +#include +#include + +#include "FalsoJNI_ImplBridge.h" +#include "FalsoJNI_Logger.h" +#include "ConvertUTF.h" + +// Objects to be passed to client applications: +JavaVM jvm; +JNIEnv jni; + +// Private structs backing the above objects: +struct JNIInvokeInterface * _jvm; // NOLINT(bugprone-reserved-identifier) +struct JNINativeInterface * _jni; // NOLINT(bugprone-reserved-identifier) + +/* + * JavaVM Methods + */ + +jint DestroyJavaVM(JavaVM* vm) { + fjni_log_dbg("[JVM] DestroyJavaVM(): ignored"); + return 0; +} + +jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) { + fjni_log_dbg("[JVM] AttachCurrentThread(vm, *p_env, thr_args)"); + *p_env = &jni; + return 0; +} + +jint DetachCurrentThread(JavaVM* vm) { + fjni_log_dbg("[JVM] DetachCurrentThread(): ignored"); + // Since we don't operate the actual Java VM, we don't need to care about + // it being available only in one thread. Anyway, due to this restriction, + // client applications are guaranteed to use JNI in a safe way. + return 0; +} + +jint GetEnv(JavaVM* vm, void** env, jint version) { + fjni_logv_dbg("[JVM] GetEnv(vm, **env, version:%i)", version); + if (!env) { + fjni_logv_err("[JVM] GetEnv(vm, **env, version:%i): env is NULL!", version); + return JNI_EINVAL; + } + *env = &jni; + return JNI_OK; +} + +jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** penv, void* thr_args) { + fjni_log_dbg("[JVM] AttachCurrentThreadAsDaemon(vm, *p_env, thr_args)"); + if (!penv) { + fjni_log_err("[JVM] AttachCurrentThreadAsDaemon(vm, *p_env, thr_args): p_env is NULL!"); + return JNI_EINVAL; + } + *penv = &jni; + return JNI_OK; +} + +/* + * JNIEnv Methods + */ + +jint GetVersion(JNIEnv * env) { + fjni_log_dbg("[JNI] GetVersion(env)"); + return JNI_VERSION_1_6; +} + +jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf, jsize bufLen) { + // Supposed to load Java class from `.class`-containing `buf`. Can not be implemented. + fjni_logv_warn("[JNI] DefineClass(env, \"%s\", 0x%x, 0x%x, %i): not implemented", name, loader, buf, bufLen); + return NULL; +} + +jclass FindClass(JNIEnv* env, const char* name) { + // While we don't manipulate any actual classes here, the following code + // serves two purposes: + // 1) Being able to uniquely identify constructor methods for classes + // (""), since in GetMethodID we only receive class pointer. + // 2) Providing a valid pointer to a valid object so that it behaves + // normally in memory and can be freed. + + jclass clazz = (jclass) strdup(name); + fjni_logv_dbg("[JNI] FindClass(%s): 0x%" PRIxPTR "", name, (uintptr_t)clazz); + return clazz; +} + +jclass GetSuperclass(JNIEnv* env, jclass clazz) { + fjni_logv_warn("[JNI] GetSuperclass(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)clazz); + return NULL; +} + +jboolean IsAssignableFrom(JNIEnv* env, jclass clazz1, jclass clazz2) { + // Supposed to determine whether an object of clazz1 can be safely cast to clazz2. + // We can not check that, so return JNI_TRUE always. + fjni_logv_warn("[JNI] IsAssignableFrom(env, 0x%" PRIxPTR ", 0x%" PRIxPTR "): not implemented", (uintptr_t)clazz1, (uintptr_t)clazz2); + return JNI_TRUE; +} + +jmethodID FromReflectedMethod(JNIEnv* env, jobject method) { + fjni_logv_warn("[JNI] FromReflectedMethod(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)method); + return NULL; +} + +jfieldID FromReflectedField(JNIEnv* env, jobject field) { + fjni_logv_warn("[JNI] FromReflectedField(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)field); + return NULL; +} + +jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID methodID, jboolean isStatic) { + fjni_logv_warn("[JNI] ToReflectedMethod(env, 0x%" PRIxPTR ", %i, %i): not implemented", (uintptr_t)cls, methodID, isStatic); + return NULL; +} + +jobject ToReflectedField(JNIEnv* env, jclass cls, jfieldID fieldID, jboolean isStatic) { + fjni_logv_warn("[JNI] ToReflectedField(env, 0x%" PRIxPTR ", %i, %i): not implemented", (uintptr_t)cls, fieldID, isStatic); + return 0; +} + +// TODO: Handle exceptions + +jint Throw(JNIEnv* env, jthrowable obj) { + fjni_logv_warn("[JNI] Throw(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)obj); + return 0; +} + +jint ThrowNew(JNIEnv* env, jclass clazz, const char* message) { + fjni_logv_warn("[JNI] ThrowNew(env, 0x%" PRIxPTR ", \"%s\"): not implemented", (uintptr_t)clazz, message); + return 0; +} + +jthrowable ExceptionOccurred(JNIEnv* env) { + fjni_log_dbg("[JNI] ExceptionOccurred(env): ignored"); + // We never have exceptions. + return NULL; +} + +void ExceptionDescribe(JNIEnv* env) { + fjni_log_dbg("[JNI] ExceptionDescribe(env): ignored"); + // We never have exceptions. +} + +void ExceptionClear(JNIEnv* env) { + fjni_log_dbg("[JNI] ExceptionClear(env): ignored"); + // We never have exceptions. +} + +void FatalError(JNIEnv* env, const char* msg) { + fjni_log_err(msg); + abort(); +} + +jint PushLocalFrame(JNIEnv* env, jint capacity) { + fjni_logv_dbg("[JNI] PushLocalFrame(env, %i): ignored", capacity); + // Since we operate with global pointers everywhere, no need to scope them. + return 0; +} + +jobject PopLocalFrame(JNIEnv* env, jobject result) { + fjni_logv_dbg("[JNI] PopLocalFrame(env, 0x%" PRIxPTR "): ignored", (uintptr_t)result); + // Since we operate with global pointers everywhere, no need to scope them. + return NULL; +} + +jobject NewGlobalRef(JNIEnv* env, jobject obj) { + fjni_logv_dbg("[JNI] NewGlobalRef(env, 0x%" PRIxPTR ")", (uintptr_t)obj); + + // The concept of global/local references really makes sense only with + // a real JVM. Here, since we basically operate with shared global pointers + // everywhere, it should be safe to just return `obj` back + + return obj; +} + +void DeleteGlobalRef(JNIEnv* env, jobject obj) { + fjni_logv_dbg("[JNI] DeleteGlobalRef(env, 0x%" PRIxPTR ")", (uintptr_t)obj); + + // Empirically, DeleteGlobalRef() is called on dynamically allocated things + // returned from other JNI functions. For most of them it's safe to just + // call free(), except for dynamically allocated arrays. We provide + // a separate "destructor" for those. + + // Reserved fake identifiers + if ((uintptr_t)obj != 0x42424242 && (uintptr_t)obj != 0x69696969) { + if (jda_free(obj) == JNI_FALSE) { + if (obj) free(obj); + } + } +} + +void DeleteLocalRef(JNIEnv* env, jobject obj) { + fjni_logv_dbg("[JNI] DeleteLocalRef(env, 0x%" PRIxPTR "): ignored", (uintptr_t)obj); + // Local references are useless in the setting of our fake JVM. +} + +jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) { + fjni_logv_dbg("[JNI] IsSameObject(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)ref1, (uintptr_t)ref2); + return (ref1 == ref2) ? JNI_TRUE : JNI_FALSE; +} + +jobject NewLocalRef(JNIEnv* env, jobject obj) { + fjni_logv_dbg("[JNI] NewLocalRef(env, 0x%" PRIxPTR "): ignored", (uintptr_t)obj); + // Local references are useless in the setting of our fake JVM. + // Just return `obj` back. + return obj; +} + +jint EnsureLocalCapacity(JNIEnv* env, jint capacity) { + fjni_logv_dbg("[JNI] EnsureLocalCapacity(env, %i)", capacity); + return JNI_OK; +} + +jobject AllocObject(JNIEnv* env, jclass clazz) { + fjni_logv_warn("[JNI] AllocObject(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)clazz); + return (jobject)0x42424242; +} + +jobject NewObject(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] NewObject(env, 0x%x, %i)", clazz, methodID); + + jobject ret; + va_list args; + va_start(args, methodID); + ret = methodObjectCall(methodID, args); + va_end(args); + + return ret; +} + +jobject NewObjectV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] NewObjectV(env, 0x%x, %i)", clazz, methodID); + return methodObjectCall(methodID, args); +} + +jobject NewObjectA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue *args) { + fjni_logv_dbg("[JNI] NewObjectA(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, methodID); + return methodObjectCall(methodID, _AtoV(0, args)); +} + +jclass GetObjectClass(JNIEnv* env, jobject obj) { + fjni_logv_dbg("[JNI] GetObjectClass(0x%" PRIxPTR ")", (uintptr_t)obj); + // Due to the way we implement class methods, it's safe to not waste + // resources on mapping objects to classess and return just a constant val. + return (jclass)0x42424242; +} + +jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass clazz) { + fjni_logv_warn("[JNI] IsInstanceOf(env, 0x%" PRIxPTR ", 0x%" PRIxPTR "): not implemented", (uintptr_t)obj, (uintptr_t)clazz); + return JNI_FALSE; +} + +jmethodID GetMethodID(JNIEnv* env, jclass clazz, const char* _name, const char* sig) { + jmethodID ret; + char name[512]; + + if (strcmp("", _name) == 0) { + if (!clazz) { + fjni_log_err("Cannot find constructor method ID for class NULL"); + return NULL; + } + + // In FindClass we return a char ptr of class name as `clazz`, so we + // can use it here for distinguishing different constructors + snprintf(name, sizeof(name), "%s/%s", (char*)clazz, _name); + } else { + snprintf(name, sizeof(name), "%s", _name); + } + + ret = getMethodIdByName(name); + + if (ret != NULL) { + fjni_logv_dbg("[JNI] GetMethodID(env, 0x%" PRIxPTR ", \"%s\", \"%s\"): %" PRIxPTR "", (uintptr_t)clazz, name, sig, (uintptr_t)ret); + } else { + fjni_logv_err("[JNI] GetMethodID(env, 0x%" PRIxPTR ", \"%s\", \"%s\"): not found", (uintptr_t)clazz, name, sig, (uintptr_t)ret); + } + + return ret; +} + +jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallObjectMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, methodID); + + jobject ret; + va_list args; + va_start(args, methodID); + ret = methodObjectCall(methodID, args); + va_end(args); + + return ret; +} + +jobject CallObjectMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallObjectMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodObjectCall(methodID, args); +} + +jobject CallObjectMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallObjectMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodObjectCall(methodID, _AtoV(0, args)); +} + +jboolean CallBooleanMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallBooleanMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, methodID); + + jboolean ret; + va_list args; + va_start(args, methodID); + ret = methodBooleanCall(methodID, args); + va_end(args); + + return ret; +} + +jboolean CallBooleanMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallBooleanMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodBooleanCall(methodID, args); +} + +jboolean CallBooleanMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallBooleanMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodBooleanCall(methodID, _AtoV(0, args)); +} + +jbyte CallByteMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallByteMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, methodID); + + jbyte ret; + va_list args; + va_start(args, methodID); + ret = methodByteCall(methodID, args); + va_end(args); + + return ret; +} + +jbyte CallByteMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallByteMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodByteCall(methodID, args); +} + +jbyte CallByteMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallByteMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodByteCall(methodID, _AtoV(0, args)); +} + +jchar CallCharMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallCharMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, methodID); + + jchar ret; + va_list args; + va_start(args, methodID); + ret = methodCharCall(methodID, args); + va_end(args); + + return ret; +} + +jchar CallCharMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallCharMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodCharCall(methodID, args); +} + +jchar CallCharMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallCharMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodCharCall(methodID, _AtoV(0, args)); +} + +jshort CallShortMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallShortMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, methodID); + + jshort ret; + va_list args; + va_start(args, methodID); + ret = methodShortCall(methodID, args); + va_end(args); + + return ret; +} + +jshort CallShortMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallShortMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodShortCall(methodID, args); +} + +jshort CallShortMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallShortMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)obj, (uintptr_t)methodID); + return methodShortCall(methodID, _AtoV(0, args)); +} + +jint CallIntMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallIntMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, methodID); + + jint ret; + va_list args; + va_start(args, methodID); + ret = methodIntCall(methodID, args); + va_end(args); + + return ret; +} + +jint CallIntMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallIntMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodIntCall(methodID, args); +} + +jint CallIntMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallIntMethodA(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodIntCall(methodID, _AtoV(0, args)); +} + +jlong CallLongMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallLongMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, methodID); + + jlong ret; + va_list args; + va_start(args, methodID); + ret = methodLongCall(methodID, args); + va_end(args); + + return ret; +} + +jlong CallLongMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallLongMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodLongCall(methodID, args); +} + +jlong CallLongMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallLongMethodA(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodLongCall(methodID, _AtoV(0, args)); +} + +jfloat CallFloatMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallFloatMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, methodID); + + jfloat ret; + va_list args; + va_start(args, methodID); + ret = methodFloatCall(methodID, args); + va_end(args); + + return ret; +} + +jfloat CallFloatMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallFloatMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodFloatCall(methodID, args); +} + +jfloat CallFloatMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallFloatMethodA(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodFloatCall(methodID, _AtoV(0, args)); +} + +jdouble CallDoubleMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallDoubleMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, methodID); + + jdouble ret; + va_list args; + va_start(args, methodID); + ret = methodDoubleCall(methodID, args); + va_end(args); + + return ret; +} + +jdouble CallDoubleMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallDoubleMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodDoubleCall(methodID, args); +} + +jdouble CallDoubleMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallDoubleMethodA(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + return methodDoubleCall(methodID, _AtoV(0, args)); +} + +void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallVoidMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, methodID); + + va_list args; + va_start(args, methodID); + methodVoidCall(methodID, args); + va_end(args); +} + +void CallVoidMethodV(JNIEnv* env, jobject obj, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallVoidMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + methodVoidCall(methodID, args); +} + +void CallVoidMethodA(JNIEnv* env, jobject obj, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallVoidMethodA(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, methodID); + methodVoidCall(methodID, _AtoV(0, args)); +} + +jobject CallNonvirtualObjectMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualObjectMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jobject ret; + va_list args; + va_start(args, methodID); + ret = methodObjectCall(methodID, args); + va_end(args); + + return ret; +} + +jobject CallNonvirtualObjectMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualObjectMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodObjectCall(methodID, args); +} + +jobject CallNonvirtualObjectMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualObjectMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodObjectCall(methodID, _AtoV(0, args)); +} + +jboolean CallNonvirtualBooleanMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualBooleanMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jboolean ret; + va_list args; + va_start(args, methodID); + ret = methodBooleanCall(methodID, args); + va_end(args); + + return ret; +} + +jboolean CallNonvirtualBooleanMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualBooleanMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodBooleanCall(methodID, args); +} + +jboolean CallNonvirtualBooleanMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualBooleanMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodBooleanCall(methodID, _AtoV(0, args)); +} + +jbyte CallNonvirtualByteMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualByteMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jbyte ret; + va_list args; + va_start(args, methodID); + ret = methodByteCall(methodID, args); + va_end(args); + + return ret; +} + +jbyte CallNonvirtualByteMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualByteMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodByteCall(methodID, args); +} + +jbyte CallNonvirtualByteMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualByteMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodByteCall(methodID, _AtoV(0, args)); +} + +jchar CallNonvirtualCharMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualCharMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jchar ret; + va_list args; + va_start(args, methodID); + ret = methodCharCall(methodID, args); + va_end(args); + + return ret; +} + +jchar CallNonvirtualCharMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualCharMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodCharCall(methodID, args); +} + +jchar CallNonvirtualCharMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualCharMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodCharCall(methodID, _AtoV(0, args)); +} + +jshort CallNonvirtualShortMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualShortMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jshort ret; + va_list args; + va_start(args, methodID); + ret = methodShortCall(methodID, args); + va_end(args); + + return ret; +} + +jshort CallNonvirtualShortMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualShortMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodShortCall(methodID, args); +} + +jshort CallNonvirtualShortMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualShortMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodShortCall(methodID, _AtoV(0, args)); +} + +jint CallNonvirtualIntMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualIntMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jint ret; + va_list args; + va_start(args, methodID); + ret = methodIntCall(methodID, args); + va_end(args); + + return ret; +} + +jint CallNonvirtualIntMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualIntMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodIntCall(methodID, args); +} + +jint CallNonvirtualIntMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualIntMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodIntCall(methodID, _AtoV(0, args)); +} + +jlong CallNonvirtualLongMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualLongMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jlong ret; + va_list args; + va_start(args, methodID); + ret = methodLongCall(methodID, args); + va_end(args); + + return ret; +} + +jlong CallNonvirtualLongMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualLongMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodLongCall(methodID, args); +} + +jlong CallNonvirtualLongMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualLongMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodLongCall(methodID, _AtoV(0, args)); +} + +jfloat CallNonvirtualFloatMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualFloatMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jfloat ret; + va_list args; + va_start(args, methodID); + ret = methodFloatCall(methodID, args); + va_end(args); + + return ret; +} + +jfloat CallNonvirtualFloatMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualFloatMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodFloatCall(methodID, args); +} + +jfloat CallNonvirtualFloatMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualFloatMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodFloatCall(methodID, _AtoV(0, args)); +} + +jdouble CallNonvirtualDoubleMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualDoubleMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + jdouble ret; + va_list args; + va_start(args, methodID); + ret = methodDoubleCall(methodID, args); + va_end(args); + + return ret; +} + +jdouble CallNonvirtualDoubleMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualDoubleMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodDoubleCall(methodID, args); +} + +jdouble CallNonvirtualDoubleMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualDoubleMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + return methodDoubleCall(methodID, _AtoV(0, args)); +} + +void CallNonvirtualVoidMethod(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallNonvirtualVoidMethod(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, ...)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + + va_list args; + va_start(args, methodID); + methodVoidCall(methodID, args); + va_end(args); +} + +void CallNonvirtualVoidMethodV(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallNonvirtualVoidMethodV(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + methodVoidCall(methodID, args); +} + +void CallNonvirtualVoidMethodA(JNIEnv* env, jobject obj, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallNonvirtualVoidMethodA(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i, args)", (uintptr_t)obj, (uintptr_t)clazz, methodID); + methodVoidCall(methodID, _AtoV(0, args)); +} + +jfieldID GetFieldID(JNIEnv * env, jclass clazz, const char* name, const char* t) { + fjni_logv_dbg("[JNI] GetFieldID(env, 0x%" PRIxPTR ", \"%s\", \"%s\")", (uintptr_t)clazz, name, t); + return getFieldIdByName(name); +} + +jobject GetObjectField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetObjectField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getObjectFieldValueById(fieldID); +} + +jboolean GetBooleanField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetBooleanField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getBooleanFieldValueById(fieldID); +} + +jbyte GetByteField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetByteField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getByteFieldValueById(fieldID); +} + +jchar GetCharField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetCharField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getCharFieldValueById(fieldID); +} + +jshort GetShortField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetShortField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getShortFieldValueById(fieldID); +} + +jint GetIntField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetIntField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getIntFieldValueById(fieldID); +} + +jlong GetLongField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetLongField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getLongFieldValueById(fieldID); +} + +jfloat GetFloatField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetFloatField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getFloatFieldValueById(fieldID); +} + +jdouble GetDoubleField(JNIEnv* env, jobject obj, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetFloatField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)obj, fieldID); + return getDoubleFieldValueById(fieldID); +} + +void SetObjectField(JNIEnv* env, jobject obj, jfieldID fieldID, jobject value) { + fjni_logv_dbg("[JNI] SetObjectField(env, 0x%" PRIxPTR ", %i, 0x%" PRIxPTR ")", (uintptr_t)obj, fieldID, (uintptr_t)value); + setObjectFieldValueById(fieldID, value); +} + +void SetBooleanField(JNIEnv* env, jobject obj, jfieldID fieldID, jboolean value) { + fjni_logv_dbg("[JNI] SetBooleanField(env, 0x%" PRIxPTR ", %i, 0x%" PRIxPTR ")", (uintptr_t)obj, fieldID, (uintptr_t)value); + setBooleanFieldValueById(fieldID, value); +} + +void SetByteField(JNIEnv* env, jobject obj, jfieldID fieldID, jbyte value) { + fjni_logv_dbg("[JNI] SetByteField(env, 0x%" PRIxPTR ", %i, 0x%" PRIxPTR ")", (uintptr_t)obj, fieldID, (uintptr_t)value); + setByteFieldValueById(fieldID, value); +} + +void SetCharField(JNIEnv* env, jobject obj, jfieldID fieldID, jchar value) { + fjni_logv_dbg("[JNI] SetCharField(env, 0x%" PRIxPTR ", %i, '%s')", (uintptr_t)obj, fieldID, value); + setCharFieldValueById(fieldID, value); +} + +void SetShortField(JNIEnv* env, jobject obj, jfieldID fieldID, jshort value) { + fjni_logv_dbg("[JNI] SetShortField(env, 0x%" PRIxPTR ", %" PRIxPTR ", %" PRIxPTR ")", (uintptr_t)obj, fieldID, (uintptr_t)value); + setShortFieldValueById(fieldID, value); +} + +void SetIntField(JNIEnv* env, jobject obj, jfieldID fieldID, jint value) { + fjni_logv_dbg("[JNI] SetIntField(env, 0x%" PRIxPTR ", %" PRIxPTR ", %" PRIxPTR ")", (uintptr_t)obj, fieldID, (uintptr_t)value); + setIntFieldValueById(fieldID, value); +} + +void SetLongField(JNIEnv* env, jobject obj, jfieldID fieldID, jlong value) { + fjni_logv_dbg("[JNI] SetLongField(env, 0x%" PRIxPTR ", %" PRIxPTR ", %" PRIxPTR ")", (uintptr_t)obj, fieldID, (uintptr_t)value); + setLongFieldValueById(fieldID, value); +} + +void SetFloatField(JNIEnv* env, jobject obj, jfieldID fieldID, jfloat value) { + fjni_logv_dbg("[JNI] SetFloatField(env, 0x%" PRIxPTR ", %i, %f)", (uintptr_t)obj, fieldID, value); + setFloatFieldValueById(fieldID, value); +} + +void SetDoubleField(JNIEnv* env, jobject obj, jfieldID fieldID, jdouble value) { + fjni_logv_dbg("[JNI] SetFloatField(env, 0x%" PRIxPTR ", %i, %i)", (uintptr_t)obj, fieldID, value); + setDoubleFieldValueById(fieldID, value); +} + +jmethodID GetStaticMethodID(JNIEnv* env, jclass clazz, const char* _name, const char* sig) { + jmethodID ret; + char name[512]; + + if (strcmp("", _name) == 0) { + if (!clazz) { + fjni_log_err("Cannot find constructor method ID for class NULL"); + return NULL; + } + + // In FindClass we return a char ptr of class name as `clazz`, so we + // can use it here for distinguishing different constructors + snprintf(name, sizeof(name), "%s/%s", (char*)clazz, _name); + } else { + snprintf(name, sizeof(name), "%s", _name); + } + + ret = getMethodIdByName(name); + + if (ret != NULL) { + fjni_logv_dbg("[JNI] GetStaticMethodID(env, 0x%" PRIxPTR ", \"%s\", \"%s\"): %" PRIxPTR "", (uintptr_t)clazz, name, sig, (uintptr_t)ret); + } else { + fjni_logv_err("[JNI] GetStaticMethodID(env, 0x%" PRIxPTR ", \"%s\", \"%s\"): not found", (uintptr_t)clazz, name, sig, (uintptr_t)ret); + } + + return ret; +} + +jobject CallStaticObjectMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticObjectMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, methodID); + + jobject ret; + va_list args; + va_start(args, methodID); + ret = methodObjectCall(methodID, args); + va_end(args); + + return ret; +} + +jobject CallStaticObjectMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticObjectMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodObjectCall(methodID, args); +} + +jobject CallStaticObjectMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticObjectMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodObjectCall(methodID, _AtoV(0, args)); +} + +jboolean CallStaticBooleanMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticBooleanMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, methodID); + + jboolean ret; + va_list args; + va_start(args, methodID); + ret = methodBooleanCall(methodID, args); + va_end(args); + + return ret; +} + +jboolean CallStaticBooleanMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticBooleanMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodBooleanCall(methodID, args); +} + +jboolean CallStaticBooleanMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticBooleanMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodBooleanCall(methodID, _AtoV(0, args)); +} + +jbyte CallStaticByteMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticByteMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, methodID); + + jbyte ret; + va_list args; + va_start(args, methodID); + ret = methodByteCall(methodID, args); + va_end(args); + + return ret; +} + +jbyte CallStaticByteMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticByteMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodByteCall(methodID, args); +} + +jbyte CallStaticByteMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticByteMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodByteCall(methodID, _AtoV(0, args)); +} + +jchar CallStaticCharMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticCharMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, methodID); + + jchar ret; + va_list args; + va_start(args, methodID); + ret = methodCharCall(methodID, args); + va_end(args); + + return ret; +} + +jchar CallStaticCharMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticCharMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodCharCall(methodID, args); +} + +jchar CallStaticCharMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticCharMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodCharCall(methodID, _AtoV(0, args)); +} + +jshort CallStaticShortMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticShortMethod(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, methodID); + + jshort ret; + va_list args; + va_start(args, methodID); + ret = methodShortCall(methodID, args); + va_end(args); + + return ret; +} + +jshort CallStaticShortMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticShortMethodV(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodShortCall(methodID, args); +} + +jshort CallStaticShortMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticShortMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodShortCall(methodID, _AtoV(0, args)); +} + +jint CallStaticIntMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticIntMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)clazz, methodID); + + jint ret; + va_list args; + va_start(args, methodID); + ret = methodIntCall(methodID, args); + va_end(args); + + return ret; +} + +jint CallStaticIntMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticIntMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)clazz, methodID); + return methodIntCall(methodID, args); +} + +jint CallStaticIntMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticIntMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodIntCall(methodID, _AtoV(0, args)); +} + +jlong CallStaticLongMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticLongMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)clazz, methodID); + + jlong ret; + va_list args; + va_start(args, methodID); + ret = methodLongCall(methodID, args); + va_end(args); + + return ret; +} + +jlong CallStaticLongMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticLongMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)clazz, methodID); + return methodLongCall(methodID, args); +} + +jlong CallStaticLongMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticLongMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodLongCall(methodID, _AtoV(0, args)); +} + +jfloat CallStaticFloatMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticFloatMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)clazz, methodID); + + jfloat ret; + va_list args; + va_start(args, methodID); + ret = methodFloatCall(methodID, args); + va_end(args); + + return ret; +} + +jfloat CallStaticFloatMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticFloatMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)clazz, methodID); + return methodFloatCall(methodID, args); +} + +jfloat CallStaticFloatMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticFloatMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodFloatCall(methodID, _AtoV(0, args)); +} + +jdouble CallStaticDoubleMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticDoubleMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)clazz, methodID); + + jdouble ret; + va_list args; + va_start(args, methodID); + ret = methodDoubleCall(methodID, args); + va_end(args); + + return ret; +} + +jdouble CallStaticDoubleMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticDoubleMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)clazz, methodID); + return methodDoubleCall(methodID, args); +} + +jdouble CallStaticDoubleMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticDoubleMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + return methodDoubleCall(methodID, _AtoV(0, args)); +} + +void CallStaticVoidMethod(JNIEnv* env, jclass clazz, jmethodID methodID, ...) { + fjni_logv_dbg("[JNI] CallStaticVoidMethod(env, 0x%" PRIxPTR ", %i, ...)", (uintptr_t)clazz, methodID); + + va_list args; + va_start(args, methodID); + methodVoidCall(methodID, args); + va_end(args); +} + +void CallStaticVoidMethodV(JNIEnv* env, jclass clazz, jmethodID methodID, va_list args) { + fjni_logv_dbg("[JNI] CallStaticVoidMethodV(env, 0x%" PRIxPTR ", %i, args)", (uintptr_t)clazz, methodID); + methodVoidCall(methodID, args); +} + +void CallStaticVoidMethodA(JNIEnv* env, jclass clazz, jmethodID methodID, const jvalue* args) { + fjni_logv_dbg("[JNI] CallStaticVoidMethodA(env, 0x%" PRIxPTR ", %" PRIxPTR ", args)", (uintptr_t)clazz, (uintptr_t)methodID); + methodVoidCall(methodID, _AtoV(0, args)); +} + +jfieldID GetStaticFieldID(JNIEnv* env, jclass clazz, const char* name, const char* t) { + fjni_logv_dbg("[JNI] GetStaticFieldID(env, 0x%" PRIxPTR ", \"%s\", \"%s\")", (uintptr_t)clazz, name, t); + return getFieldIdByName(name); +} + +jobject GetStaticObjectField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticObjectField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getObjectFieldValueById(fieldID); +} + +jboolean GetStaticBooleanField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticBooleanField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getBooleanFieldValueById(fieldID); +} + +jbyte GetStaticByteField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticByteField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getByteFieldValueById(fieldID); +} + +jchar GetStaticCharField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticCharField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getCharFieldValueById(fieldID); +} + +jshort GetStaticShortField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticShortField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getShortFieldValueById(fieldID); +} + +jint GetStaticIntField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticIntField(env, 0x%" PRIxPTR ", %i): ", (uintptr_t)clazz, fieldID); + return getIntFieldValueById(fieldID); +} + +jlong GetStaticLongField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticLongField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getLongFieldValueById(fieldID); +} + +jfloat GetStaticFloatField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticFloatField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getFloatFieldValueById(fieldID); +} + +jdouble GetStaticDoubleField(JNIEnv* env, jclass clazz, jfieldID fieldID) { + fjni_logv_dbg("[JNI] GetStaticDoubleField(env, 0x%" PRIxPTR ", %i)", (uintptr_t)clazz, fieldID); + return getDoubleFieldValueById(fieldID); +} + +void SetStaticObjectField(JNIEnv* env, jclass clazz, jfieldID fieldID, jobject value) { + fjni_logv_dbg("[JNI] SetStaticObjectField(env, 0x%" PRIxPTR ", %i, 0x%" PRIxPTR ")", (uintptr_t)clazz, fieldID, (uintptr_t)value); + setObjectFieldValueById(fieldID, value); +} + +void SetStaticBooleanField(JNIEnv* env, jclass clazz, jfieldID fieldID, jboolean value) { + fjni_logv_dbg("[JNI] SetStaticBooleanField(env, 0x%" PRIxPTR ", %i, 0x%" PRIxPTR ")", (uintptr_t)clazz, fieldID, (uintptr_t)value); + setBooleanFieldValueById(fieldID, value); +} + +void SetStaticByteField(JNIEnv* env, jclass clazz, jfieldID fieldID, jbyte value) { + fjni_logv_dbg("[JNI] SetStaticByteField(env, 0x%" PRIxPTR ", %i, 0x%" PRIxPTR ")", (uintptr_t)clazz, fieldID, (uintptr_t)value); + setByteFieldValueById(fieldID, value); +} + +void SetStaticCharField(JNIEnv* env, jclass clazz, jfieldID fieldID, jchar value) { + fjni_logv_dbg("[JNI] SetStaticCharField(env, 0x%" PRIxPTR ", %i, '%s')", (uintptr_t)clazz, fieldID, value); + setCharFieldValueById(fieldID, value); +} + +void SetStaticShortField(JNIEnv* env, jclass clazz, jfieldID fieldID, jshort value) { + fjni_logv_dbg("[JNI] SetStaticShortField(env, 0x%" PRIxPTR ", %" PRIxPTR ", %" PRIxPTR ")", (uintptr_t)clazz, fieldID, (uintptr_t)value); + setShortFieldValueById(fieldID, value); +} + +void SetStaticIntField(JNIEnv* env, jclass clazz, jfieldID fieldID, jint value) { + fjni_logv_dbg("[JNI] SetStaticIntField(env, 0x%" PRIxPTR ", %" PRIxPTR ", %" PRIxPTR ")", (uintptr_t)clazz, fieldID, (uintptr_t)value); + setIntFieldValueById(fieldID, value); +} + +void SetStaticLongField(JNIEnv* env, jclass clazz, jfieldID fieldID, jlong value) { + fjni_logv_dbg("[JNI] SetStaticLongField(env, 0x%" PRIxPTR ", %" PRIxPTR ", %" PRIxPTR ")", (uintptr_t)clazz, fieldID, (uintptr_t)value); + setLongFieldValueById(fieldID, value); +} + +void SetStaticFloatField(JNIEnv* env, jclass clazz, jfieldID fieldID, jfloat value) { + fjni_logv_dbg("[JNI] SetStaticFloatField(env, 0x%" PRIxPTR ", %i, %f)", (uintptr_t)clazz, fieldID, value); + setFloatFieldValueById(fieldID, value); +} + +void SetStaticDoubleField(JNIEnv* env, jclass clazz, jfieldID fieldID, jdouble value) { + fjni_logv_dbg("[JNI] SetStaticFloatField(env, 0x%" PRIxPTR ", %i, %i)", (uintptr_t)clazz, fieldID, value); + setDoubleFieldValueById(fieldID, value); +} + +jstring NewString(JNIEnv* env, const jchar* chars, jsize char_count) { + fjni_logv_dbg("[JNI] NewString(env, \"%ls\", %i)", chars, char_count); + + // abort()s here replicate Dalvik behavior. + if (char_count < 0) { + fjni_logv_err("Fatal: char_count < 0: %d! Aborting.", char_count); + abort(); + } + + if (chars == NULL && char_count > 0) { + fjni_log_err("Fatal: chars == null && char_count > 0"); + abort(); + } + + UTF8 * dst = malloc((char_count+1) * sizeof(UTF8)); + + UTF16 * sourceStart = &(((UTF16*)chars)[0]); + UTF16 * sourceEnd = &(((UTF16*)chars)[char_count]); + UTF8 * targetStart = &dst[0]; + UTF8 * targetEnd = &dst[char_count]; + ConversionResult res = ConvertUTF16toUTF8((const UTF16**)&sourceStart, sourceEnd, + &targetStart, targetEnd, 1); + if (res != conversionOK) { + fjni_logv_err("Fatal: utf16 => utf8 conversion failed (%i)", res); + abort(); + } + + dst[char_count] = '\0'; + return dst; +} + +jsize GetStringLength(JNIEnv* env, jstring string) { + fjni_logv_dbg("[JNI] GetStringLength(env, 0x%" PRIxPTR "/\"%s\")", (uintptr_t)string, (char*)string); + return (jsize)strlen(string); +} + +const jchar * GetStringChars(JNIEnv* env, jstring string, jboolean *isCopy) { + fjni_logv_dbg("[JNI] GetStringChars(env, 0x%x/\"%s\", *isCopy)", string, string); + + if (!string) { + fjni_log_err("String is null."); + return NULL; + } + + if (isCopy != NULL) { + *isCopy = JNI_TRUE; + } + + size_t size = strlen(string); + UTF16 * dst = malloc((size+1) * sizeof(UTF16)); + + UTF8 * sourceStart = (UTF8 *)string; + UTF8 * sourceEnd = &(((UTF8*)string)[size]); + UTF16 * targetStart = &dst[0]; + UTF16 * targetEnd = &dst[size]; + ConversionResult res = ConvertUTF8toUTF16((const UTF8 **)&sourceStart, sourceEnd, + &targetStart, targetEnd, 1); + if (res != conversionOK) { + fjni_logv_err("Fatal: utf8 => utf16 conversion failed (%i)", res); + abort(); + } + + dst[size] = 0; + return dst; +} + +void ReleaseStringChars(JNIEnv* env, jstring string, const jchar *chars) { + fjni_logv_dbg("[JNI] ReleaseStringChars(env, 0x%x/\"%s\", 0x%x)", string, string, chars); + + if (!chars) { + fjni_log_err("Chars is null"); + return; + } + + free((char*)chars); +} + +jstring NewStringUTF(JNIEnv* env, const char* bytes) { + fjni_logv_dbg("[JNI] NewStringUTF(env, \"%s\")", bytes); + + char* newStr; + if (bytes == NULL) { + /* this shouldn't happen; throw NPE? */ + newStr = NULL; + } else { + newStr = strdup(bytes); + if (newStr == NULL) { + /* assume memory failure */ + fjni_log_err("native heap string alloc failed! aborting."); + abort(); + } + } + + return newStr; +} + +jsize GetStringUTFLength(JNIEnv* env, jstring string) { + fjni_logv_dbg("[JNI] GetStringUTFLength(env, \"%s\")", string); + + if (string != NULL) { + return (jsize)strlen(string); + } + + return 0; +} + +const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy) { + fjni_logv_dbg("[JNI] GetStringUTFChars(env, \"%s\", *isCopy)", string); + + char* newStr; + if (string == NULL) { + /* this shouldn't happen; throw NPE? */ + newStr = NULL; + } else { + if (isCopy != NULL) + *isCopy = JNI_TRUE; + newStr = strdup(string); + if (newStr == NULL) { + /* assume memory failure */ + fjni_log_err("native heap string alloc failed! aborting."); + abort(); + } + } + + return newStr; +} + +void ReleaseStringUTFChars(JNIEnv* env, jstring string, char* chars) { + fjni_logv_dbg("[JNI] ReleaseStringUTFChars(env, 0x%" PRIxPTR ", \"%s\")", (uintptr_t)string, chars); + if (chars) { + free(chars); + } +} + +jsize GetArrayLength(JNIEnv* env, jarray array) { + fjni_logv_dbg("[JNI] GetArrayLength(env, 0x%" PRIxPTR ")", (uintptr_t)array); + + // TODO: this can theoretically be called for ObjectField values. Need to keep track of their sizes too? + jsize ret = jda_sizeof(array); + if (ret > -1) return ret; + + fjni_logv_warn("Array 0x%" PRIxPTR " not found. Unknown array type?", (uintptr_t)array); + return 0; +} + +jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass elementClass, jobject initialElement) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_OBJECT); + if (!jda) { + fjni_logv_err("[JNI] NewObjectArray(env, %i, 0x%x, 0x%x): Could not allocate a new array!", length, elementClass, initialElement); + return NULL; + } + + jobject* arr = jda->array; + for (int i = 0; i < length; ++i) + arr[i] = initialElement; + + fjni_logv_dbg("[JNI] NewObjectArray(env, %i, 0x%" PRIxPTR ", 0x%" PRIxPTR "): 0x%" PRIxPTR "", length, elementClass, initialElement, (uintptr_t)jda); + return jda; +} + +jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetObjectArrayElement(env, 0x%x, idx:%i): Could not find the array", array, index); + return NULL; + } + + if (index >= jda->len || index < 0) { + fjni_logv_err("[JNI] GetObjectArrayElement(env, 0x%x, idx:%i): Index out of bounds", array, index); + return NULL; + } + + jobject * arr = jda->array; + fjni_logv_dbg("[JNI] GetObjectArrayElement(env, 0x%" PRIxPTR ", idx:%i): 0x%" PRIxPTR "", array, index, (uintptr_t)arr[index]); + return arr[index]; +} + +void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] SetObjectArrayElement(env, 0x%x, idx:%i, val:0x%x): Could not find the array", array, index, value); + return; + } + + if (index >= jda->len || index < 0) { + fjni_logv_err("[JNI] SetObjectArrayElement(env, 0x%x, idx:%i, val:0x%x): Index out of bounds", array, index, value); + return; + } + + fjni_logv_dbg("[JNI] SetObjectArrayElement(env, 0x%x, idx:%i, val:0x%x)", array, index, value); + + jobject * arr = jda->array; + arr[index] = value; +} + +jbooleanArray NewBooleanArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_BOOLEAN); + if (!jda) { + fjni_logv_err("[JNI] NewBooleanArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewBooleanArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jbyteArray NewByteArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_BYTE); + if (!jda) { + fjni_logv_err("[JNI] NewByteArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewByteArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jcharArray NewCharArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_CHAR); + if (!jda) { + fjni_logv_err("[JNI] NewCharArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewCharArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jshortArray NewShortArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_SHORT); + if (!jda) { + fjni_logv_err("[JNI] NewShortArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewShortArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jintArray NewIntArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_INT); + if (!jda) { + fjni_logv_err("[JNI] NewIntArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewIntArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jlongArray NewLongArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_LONG); + if (!jda) { + fjni_logv_err("[JNI] NewLongArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewLongArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jfloatArray NewFloatArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_FLOAT); + if (!jda) { + fjni_logv_err("[JNI] NewFloatArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewFloatArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jdoubleArray NewDoubleArray(JNIEnv* env, jsize length) { + JavaDynArray * jda = jda_alloc(length, FIELD_TYPE_DOUBLE); + if (!jda) { + fjni_logv_err("[JNI] NewDoubleArray(env, %i): Could not allocate a new array!", length); + return NULL; + } + + fjni_logv_dbg("[JNI] NewDoubleArray(env, %i): 0x%" PRIxPTR "", length, (uintptr_t)jda); + return jda; +} + +jboolean* GetBooleanArrayElements(JNIEnv* env, jbooleanArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetBooleanArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetBooleanArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jbyte* GetByteArrayElements(JNIEnv* env, jbyteArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetByteArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetByteArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jchar* GetCharArrayElements(JNIEnv* env, jcharArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetCharArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetCharArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jshort* GetShortArrayElements(JNIEnv* env, jshortArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetShortArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetShortArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jint* GetIntArrayElements(JNIEnv* env, jintArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetIntArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetIntArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jlong* GetLongArrayElements(JNIEnv* env, jlongArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetLongArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetLongArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jfloat* GetFloatArrayElements(JNIEnv* env, jfloatArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetFloatArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetFloatArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +jdouble* GetDoubleArrayElements(JNIEnv* env, jdoubleArray array, jboolean* isCopy) { + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetDoubleArrayElements(env, 0x%x, 0x%x): Could not find the array", array, isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetDoubleArrayElements(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + if (isCopy != NULL) *isCopy = JNI_FALSE; + return jda->array; +} + +// In GetArrayElements we never make copies, so ReleaseArrayElements can be ignored + +void ReleaseBooleanArrayElements(JNIEnv* env, jbooleanArray array, jboolean* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseBooleanArrayElements(): ignored"); } +void ReleaseByteArrayElements(JNIEnv* env, jbyteArray array, jbyte* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseByteArrayElements(): ignored"); } +void ReleaseCharArrayElements(JNIEnv* env, jcharArray array, jchar* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseCharArrayElements(): ignored"); } +void ReleaseShortArrayElements(JNIEnv* env, jshortArray array, jshort* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseShortArrayElements(): ignored"); } +void ReleaseIntArrayElements(JNIEnv* env, jintArray array, jint* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseIntArrayElements(): ignored"); } +void ReleaseLongArrayElements(JNIEnv* env, jlongArray array, jlong* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseLongArrayElements(): ignored"); } +void ReleaseFloatArrayElements(JNIEnv* env, jfloatArray array, jfloat* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseFloatArrayElements(): ignored"); } +void ReleaseDoubleArrayElements(JNIEnv* env, jdoubleArray array, jdouble* elems, jint mode) { fjni_log_dbg("[JNI] ReleaseDoubleArrayElements(): ignored"); } + +void GetBooleanArrayRegion(JNIEnv* env, jbooleanArray array, jsize start, jsize length, jboolean* buffer) { + GetPrimitiveArrayRegion("GetBooleanArrayRegion", FIELD_TYPE_BOOLEAN, jboolean, array, start, length, buffer); +} + +void GetByteArrayRegion(JNIEnv* env, jbyteArray array, jsize start, jsize length, jbyte* buffer) { + GetPrimitiveArrayRegion("GetByteArrayRegion", FIELD_TYPE_BYTE, jbyte, array, start, length, buffer); +} + +void GetCharArrayRegion(JNIEnv* env, jcharArray array, jsize start, jsize length, jchar* buffer) { + GetPrimitiveArrayRegion("GetCharArrayRegion", FIELD_TYPE_CHAR, jchar, array, start, length, buffer); +} + +void GetShortArrayRegion(JNIEnv* env, jshortArray array, jsize start, jsize length, jshort* buffer) { + GetPrimitiveArrayRegion("GetShortArrayRegion", FIELD_TYPE_SHORT, jshort, array, start, length, buffer); +} + +void GetIntArrayRegion(JNIEnv* env, jintArray array, jsize start, jsize length, jint* buffer) { + GetPrimitiveArrayRegion("GetIntArrayRegion", FIELD_TYPE_INT, jint, array, start, length, buffer); +} + +void GetLongArrayRegion(JNIEnv* env, jlongArray array, jsize start, jsize length, jlong* buffer) { + GetPrimitiveArrayRegion("GetLongArrayRegion", FIELD_TYPE_LONG, jlong, array, start, length, buffer); +} + +void GetFloatArrayRegion(JNIEnv* env, jfloatArray array, jsize start, jsize length, jfloat* buffer) { + GetPrimitiveArrayRegion("GetFloatArrayRegion", FIELD_TYPE_FLOAT, jfloat, array, start, length, buffer); +} + +void GetDoubleArrayRegion(JNIEnv* env, jdoubleArray array, jsize start, jsize length, jdouble* buffer) { + GetPrimitiveArrayRegion("GetDoubleArrayRegion", FIELD_TYPE_DOUBLE, jdouble, array, start, length, buffer); +} + +void SetBooleanArrayRegion(JNIEnv* env, jbooleanArray array, jsize start, jsize len, const jboolean* buf) { + SetPrimitiveArrayRegion("SetBooleanArrayRegion", FIELD_TYPE_BOOLEAN, jboolean, array, start, len, buf); +} + +void SetByteArrayRegion(JNIEnv* env, jbyteArray array, jsize start, jsize len, const jbyte* buf) { + SetPrimitiveArrayRegion("SetByteArrayRegion", FIELD_TYPE_BYTE, jbyte, array, start, len, buf); +} + +void SetCharArrayRegion(JNIEnv* env, jcharArray array, jsize start, jsize len, const jchar* buf) { + SetPrimitiveArrayRegion("SetCharArrayRegion", FIELD_TYPE_CHAR, jchar, array, start, len, buf); +} + +void SetShortArrayRegion(JNIEnv* env, jshortArray array, jsize start, jsize len, const jshort* buf) { + SetPrimitiveArrayRegion("SetShortArrayRegion", FIELD_TYPE_SHORT, jshort, array, start, len, buf); +} + +void SetIntArrayRegion(JNIEnv* env, jintArray array, jsize start, jsize len, const jint* buf) { + SetPrimitiveArrayRegion("SetIntArrayRegion", FIELD_TYPE_INT, jint, array, start, len, buf); +} + +void SetLongArrayRegion(JNIEnv* env, jlongArray array, jsize start, jsize len, const jlong* buf) { + SetPrimitiveArrayRegion("SetLongArrayRegion", FIELD_TYPE_LONG, jlong, array, start, len, buf); +} + +void SetFloatArrayRegion(JNIEnv* env, jfloatArray array, jsize start, jsize len, const jfloat* buf) { + SetPrimitiveArrayRegion("SetFloatArrayRegion", FIELD_TYPE_FLOAT, jfloat, array, start, len, buf); +} + +void SetDoubleArrayRegion(JNIEnv* env, jdoubleArray array, jsize start, jsize len, const jdouble* buf) { + SetPrimitiveArrayRegion("SetDoubleArrayRegion", FIELD_TYPE_DOUBLE, jdouble, array, start, len, buf); +} + +// Due to the way we define and execute functions, Register/UnregisterNatives are redundant + +jint RegisterNatives(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, jint nMethods) { + fjni_logv_dbg("[JNI] RegisterNatives(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", n:%i): ignored", (uintptr_t)clazz, (uintptr_t)methods, nMethods); + return JNI_OK; +} + +jint UnregisterNatives(JNIEnv* env, jclass clazz) { + fjni_logv_dbg("[JNI] UnregisterNatives(env, 0x%" PRIxPTR "): ignored", (uintptr_t)clazz); + return JNI_OK; +} + +// TODO: Implement MonitorEnter/MonitorExit with semaphores? + +jint MonitorEnter(JNIEnv* env, jobject obj) { + // Here reduced log level to dbg to avoid a million logs + fjni_logv_dbg("[JNI] MonitorEnter(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)obj); + return JNI_OK; +} + +jint MonitorExit(JNIEnv* env, jobject obj) { + // Here reduced log level to dbg to avoid a million logs + fjni_logv_dbg("[JNI] MonitorExit(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)obj); + return JNI_OK; +} + +jint GetJavaVM(JNIEnv* env, JavaVM** vm) { + fjni_log_dbg("[JNI] GetJavaVM(env, *vm)"); + *vm = &jvm; + return JNI_OK; +} + +void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf) { + fjni_logv_dbg("[JNI] GetStringRegion(env, 0x%" PRIxPTR ", start:%i, len:%i, 0x%" PRIxPTR ")", (uintptr_t)str, start, len, (uintptr_t)buf); + + if (str == NULL) { + fjni_log_err("str is NULL"); + return; + } + + if ((start + len) > strlen(str)) { + fjni_log_err("StringIndexOutOfBoundsException"); + return; + } + + if (buf == NULL) { + buf = malloc(len+1); + } + + strncpy((char*)buf, (const char*)str + start, len); +} + +void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start, jsize len, char* buf) { + fjni_logv_dbg("[JNI] GetStringUTFRegion(env, 0x%" PRIxPTR ", start:%i, len:%i, 0x%" PRIxPTR ")", (uintptr_t)str, start, len, (uintptr_t)buf); + + if (str == NULL) { + fjni_log_err("str is NULL"); + return; + } + + if ((start + len) > strlen(str)) { + fjni_log_err("StringIndexOutOfBoundsException"); + return; + } + + if (buf == NULL) { + buf = malloc(len+1); + } + + strncpy(buf, (const char*)str + start, len); +} + +void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* isCopy) { + if (isCopy) *isCopy = JNI_FALSE; + + JavaDynArray * jda = jda_find((void *) array); + if (!jda) { + fjni_logv_err("[JNI] GetPrimitiveArrayCritical(env, 0x%" PRIxPTR ", 0x%" PRIxPTR "): Array not found.", (uintptr_t)array, (uintptr_t)isCopy); + return NULL; + } + + fjni_logv_dbg("[JNI] GetPrimitiveArrayCritical(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ")", (uintptr_t)array, (uintptr_t)isCopy); + return jda->array; +} + +void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) { + // We never copy in GetPrimitiveArrayCritical, so can ignore Release* + fjni_logv_dbg("[JNI] ReleasePrimitiveArrayCritical(env, 0x%" PRIxPTR ", 0x%" PRIxPTR ", %i): ignored", (uintptr_t)array, (uintptr_t)carray, mode); +} + +const jchar* GetStringCritical(JNIEnv* env, jstring string, jboolean* isCopy) { + fjni_logv_dbg("[JNI] GetStringCritical(env, 0x%x/\"%s\", *isCopy)", string, string); + + if (!string) { + fjni_log_err("String is null."); + return NULL; + } + + if (isCopy != NULL) { + *isCopy = JNI_TRUE; + } + + return (const jchar *)strdup((const char*)string); +} + +void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* carray) { + fjni_logv_dbg("[JNI] ReleaseStringCritical(env, 0x%x/\"%s\", 0x%x)", string, string, carray); + + if (!carray) { + fjni_log_err("carray is null"); + return; + } + + free((char*)carray); +} + +jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) { + fjni_logv_dbg("[JNI] NewWeakGlobalRef(env, 0x%" PRIxPTR "): ignored", (uintptr_t)obj); + return (jweak)obj; +} + +void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) { + fjni_logv_dbg("[JNI] DeleteWeakGlobalRef(env, 0x%" PRIxPTR ")", (uintptr_t)obj); + // Do not free, since we have not created any additional references above. +} + +jboolean ExceptionCheck(JNIEnv* env) { + fjni_log_dbg("[JNI] ExceptionCheck(env): ignored"); + return JNI_FALSE; +} + +// TODO: Implement DirectByteBuffers + +jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) { + fjni_logv_warn("[JNI] NewDirectByteBuffer(env, 0x%" PRIxPTR ", %i): not implemented", (uintptr_t)address, capacity); + return NULL; +} + +void* GetDirectBufferAddress(JNIEnv* env, jobject buf) { + fjni_logv_warn("[JNI] GetDirectBufferAddress(env, 0x%" PRIxPTR ")", (uintptr_t)buf); + return NULL; +} + +jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) { + fjni_logv_warn("[JNI] GetDirectBufferCapacity(env, 0x%" PRIxPTR ")", (uintptr_t)buf); + return 0; +} + +jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) { + fjni_logv_warn("[JNI] GetObjectRefType(env, 0x%" PRIxPTR "): not implemented", (uintptr_t)obj); + return JNIInvalidRefType; +} + +void jni_init() { + _jvm = (struct JNIInvokeInterface *) malloc(sizeof(struct JNIInvokeInterface)); + _jvm->DestroyJavaVM = DestroyJavaVM; + _jvm->AttachCurrentThread = AttachCurrentThread; + _jvm->DetachCurrentThread = DetachCurrentThread; + _jvm->GetEnv = GetEnv; + _jvm->AttachCurrentThreadAsDaemon = AttachCurrentThreadAsDaemon; + + _jni = (struct JNINativeInterface *) malloc(sizeof(struct JNINativeInterface)); + _jni->GetVersion = GetVersion; + _jni->DefineClass = DefineClass; + _jni->FindClass = FindClass; + _jni->FromReflectedMethod = FromReflectedMethod; + _jni->FromReflectedField = FromReflectedField; + _jni->ToReflectedMethod = ToReflectedMethod; + _jni->GetSuperclass = GetSuperclass; + _jni->IsAssignableFrom = IsAssignableFrom; + _jni->ToReflectedField = ToReflectedField; + _jni->Throw = Throw; + _jni->ThrowNew = ThrowNew; + _jni->ExceptionOccurred = ExceptionOccurred; + _jni->ExceptionDescribe = ExceptionDescribe; + _jni->ExceptionClear = ExceptionClear; + _jni->FatalError = FatalError; + _jni->PushLocalFrame = PushLocalFrame; + _jni->PopLocalFrame = PopLocalFrame; + _jni->NewGlobalRef = NewGlobalRef; + _jni->DeleteGlobalRef = DeleteGlobalRef; + _jni->DeleteLocalRef = DeleteLocalRef; + _jni->IsSameObject = IsSameObject; + _jni->NewLocalRef = NewLocalRef; + _jni->EnsureLocalCapacity = EnsureLocalCapacity; + _jni->AllocObject = AllocObject; + _jni->NewObject = NewObject; + _jni->NewObjectV = NewObjectV; + _jni->NewObjectA = NewObjectA; + _jni->GetObjectClass = GetObjectClass; + _jni->IsInstanceOf = IsInstanceOf; + _jni->GetMethodID = GetMethodID; + _jni->CallObjectMethod = CallObjectMethod; + _jni->CallObjectMethodV = CallObjectMethodV; + _jni->CallObjectMethodA = CallObjectMethodA; + _jni->CallBooleanMethod = CallBooleanMethod; + _jni->CallBooleanMethodV = CallBooleanMethodV; + _jni->CallBooleanMethodA = CallBooleanMethodA; + _jni->CallByteMethod = CallByteMethod; + _jni->CallByteMethodV = CallByteMethodV; + _jni->CallByteMethodA = CallByteMethodA; + _jni->CallCharMethod = CallCharMethod; + _jni->CallCharMethodV = CallCharMethodV; + _jni->CallCharMethodA = CallCharMethodA; + _jni->CallShortMethod = CallShortMethod; + _jni->CallShortMethodV = CallShortMethodV; + _jni->CallShortMethodA = CallShortMethodA; + _jni->CallIntMethod = CallIntMethod; + _jni->CallIntMethodV = CallIntMethodV; + _jni->CallIntMethodA = CallIntMethodA; + _jni->CallLongMethod = CallLongMethod; + _jni->CallLongMethodV = CallLongMethodV; + _jni->CallLongMethodA = CallLongMethodA; + _jni->CallFloatMethod = CallFloatMethod; + _jni->CallFloatMethodV = CallFloatMethodV; + _jni->CallFloatMethodA = CallFloatMethodA; + _jni->CallDoubleMethod = CallDoubleMethod; + _jni->CallDoubleMethodV = CallDoubleMethodV; + _jni->CallDoubleMethodA = CallDoubleMethodA; + _jni->CallVoidMethod = CallVoidMethod; + _jni->CallVoidMethodV = CallVoidMethodV; + _jni->CallVoidMethodA = CallVoidMethodA; + _jni->CallNonvirtualObjectMethod = CallNonvirtualObjectMethod; + _jni->CallNonvirtualObjectMethodV = CallNonvirtualObjectMethodV; + _jni->CallNonvirtualObjectMethodA = CallNonvirtualObjectMethodA; + _jni->CallNonvirtualBooleanMethod = CallNonvirtualBooleanMethod; + _jni->CallNonvirtualBooleanMethodV = CallNonvirtualBooleanMethodV; + _jni->CallNonvirtualBooleanMethodA = CallNonvirtualBooleanMethodA; + _jni->CallNonvirtualByteMethod = CallNonvirtualByteMethod; + _jni->CallNonvirtualByteMethodV = CallNonvirtualByteMethodV; + _jni->CallNonvirtualByteMethodA = CallNonvirtualByteMethodA; + _jni->CallNonvirtualCharMethod = CallNonvirtualCharMethod; + _jni->CallNonvirtualCharMethodV = CallNonvirtualCharMethodV; + _jni->CallNonvirtualCharMethodA = CallNonvirtualCharMethodA; + _jni->CallNonvirtualShortMethod = CallNonvirtualShortMethod; + _jni->CallNonvirtualShortMethodV = CallNonvirtualShortMethodV; + _jni->CallNonvirtualShortMethodA = CallNonvirtualShortMethodA; + _jni->CallNonvirtualIntMethod = CallNonvirtualIntMethod; + _jni->CallNonvirtualIntMethodV = CallNonvirtualIntMethodV; + _jni->CallNonvirtualIntMethodA = CallNonvirtualIntMethodA; + _jni->CallNonvirtualLongMethod = CallNonvirtualLongMethod; + _jni->CallNonvirtualLongMethodV = CallNonvirtualLongMethodV; + _jni->CallNonvirtualLongMethodA = CallNonvirtualLongMethodA; + _jni->CallNonvirtualFloatMethod = CallNonvirtualFloatMethod; + _jni->CallNonvirtualFloatMethodV = CallNonvirtualFloatMethodV; + _jni->CallNonvirtualFloatMethodA = CallNonvirtualFloatMethodA; + _jni->CallNonvirtualDoubleMethod = CallNonvirtualDoubleMethod; + _jni->CallNonvirtualDoubleMethodV = CallNonvirtualDoubleMethodV; + _jni->CallNonvirtualDoubleMethodA = CallNonvirtualDoubleMethodA; + _jni->CallNonvirtualVoidMethod = CallNonvirtualVoidMethod; + _jni->CallNonvirtualVoidMethodV = CallNonvirtualVoidMethodV; + _jni->CallNonvirtualVoidMethodA = CallNonvirtualVoidMethodA; + _jni->GetFieldID = GetFieldID; + _jni->GetObjectField = GetObjectField; + _jni->GetBooleanField = GetBooleanField; + _jni->GetByteField = GetByteField; + _jni->GetCharField = GetCharField; + _jni->GetShortField = GetShortField; + _jni->GetIntField = GetIntField; + _jni->GetLongField = GetLongField; + _jni->GetFloatField = GetFloatField; + _jni->GetDoubleField = GetDoubleField; + _jni->SetObjectField = SetObjectField; + _jni->SetBooleanField = SetBooleanField; + _jni->SetByteField = SetByteField; + _jni->SetCharField = SetCharField; + _jni->SetShortField = SetShortField; + _jni->SetIntField = SetIntField; + _jni->SetLongField = SetLongField; + _jni->SetFloatField = SetFloatField; + _jni->SetDoubleField = SetDoubleField; + _jni->GetStaticMethodID = GetStaticMethodID; + _jni->CallStaticObjectMethod = CallStaticObjectMethod; + _jni->CallStaticObjectMethodV = CallStaticObjectMethodV; + _jni->CallStaticObjectMethodA = CallStaticObjectMethodA; + _jni->CallStaticBooleanMethod = CallStaticBooleanMethod; + _jni->CallStaticBooleanMethodV = CallStaticBooleanMethodV; + _jni->CallStaticBooleanMethodA = CallStaticBooleanMethodA; + _jni->CallStaticByteMethod = CallStaticByteMethod; + _jni->CallStaticByteMethodV = CallStaticByteMethodV; + _jni->CallStaticByteMethodA = CallStaticByteMethodA; + _jni->CallStaticCharMethod = CallStaticCharMethod; + _jni->CallStaticCharMethodV = CallStaticCharMethodV; + _jni->CallStaticCharMethodA = CallStaticCharMethodA; + _jni->CallStaticShortMethod = CallStaticShortMethod; + _jni->CallStaticShortMethodV = CallStaticShortMethodV; + _jni->CallStaticShortMethodA = CallStaticShortMethodA; + _jni->CallStaticIntMethod = CallStaticIntMethod; + _jni->CallStaticIntMethodV = CallStaticIntMethodV; + _jni->CallStaticIntMethodA = CallStaticIntMethodA; + _jni->CallStaticLongMethod = CallStaticLongMethod; + _jni->CallStaticLongMethodV = CallStaticLongMethodV; + _jni->CallStaticLongMethodA = CallStaticLongMethodA; + _jni->CallStaticFloatMethod = CallStaticFloatMethod; + _jni->CallStaticFloatMethodV = CallStaticFloatMethodV; + _jni->CallStaticFloatMethodA = CallStaticFloatMethodA; + _jni->CallStaticDoubleMethod = CallStaticDoubleMethod; + _jni->CallStaticDoubleMethodV = CallStaticDoubleMethodV; + _jni->CallStaticDoubleMethodA = CallStaticDoubleMethodA; + _jni->CallStaticVoidMethod = CallStaticVoidMethod; + _jni->CallStaticVoidMethodV = CallStaticVoidMethodV; + _jni->CallStaticVoidMethodA = CallStaticVoidMethodA; + _jni->GetStaticFieldID = GetStaticFieldID; + _jni->GetStaticObjectField = GetStaticObjectField; + _jni->GetStaticBooleanField = GetStaticBooleanField; + _jni->GetStaticByteField = GetStaticByteField; + _jni->GetStaticCharField = GetStaticCharField; + _jni->GetStaticShortField = GetStaticShortField; + _jni->GetStaticIntField = GetStaticIntField; + _jni->GetStaticLongField = GetStaticLongField; + _jni->GetStaticFloatField = GetStaticFloatField; + _jni->GetStaticDoubleField = GetStaticDoubleField; + _jni->SetStaticObjectField = SetStaticObjectField; + _jni->SetStaticBooleanField = SetStaticBooleanField; + _jni->SetStaticByteField = SetStaticByteField; + _jni->SetStaticCharField = SetStaticCharField; + _jni->SetStaticShortField = SetStaticShortField; + _jni->SetStaticIntField = SetStaticIntField; + _jni->SetStaticLongField = SetStaticLongField; + _jni->SetStaticFloatField = SetStaticFloatField; + _jni->SetStaticDoubleField = SetStaticDoubleField; + _jni->NewString = NewString; + _jni->GetStringLength = GetStringLength; + _jni->GetStringChars = GetStringChars; + _jni->ReleaseStringChars = ReleaseStringChars; + _jni->NewStringUTF = NewStringUTF; + _jni->GetStringUTFLength = GetStringUTFLength; + _jni->GetStringUTFChars = GetStringUTFChars; + _jni->ReleaseStringUTFChars = ReleaseStringUTFChars; + _jni->GetArrayLength = GetArrayLength; + _jni->NewObjectArray = NewObjectArray; + _jni->GetObjectArrayElement = GetObjectArrayElement; + _jni->SetObjectArrayElement = SetObjectArrayElement; + _jni->NewBooleanArray = NewBooleanArray; + _jni->NewByteArray = NewByteArray; + _jni->NewCharArray = NewCharArray; + _jni->NewShortArray = NewShortArray; + _jni->NewIntArray = NewIntArray; + _jni->NewLongArray = NewLongArray; + _jni->NewFloatArray = NewFloatArray; + _jni->NewDoubleArray = NewDoubleArray; + _jni->GetBooleanArrayElements = GetBooleanArrayElements; + _jni->GetByteArrayElements = GetByteArrayElements; + _jni->GetCharArrayElements = GetCharArrayElements; + _jni->GetShortArrayElements = GetShortArrayElements; + _jni->GetIntArrayElements = GetIntArrayElements; + _jni->GetLongArrayElements = GetLongArrayElements; + _jni->GetFloatArrayElements = GetFloatArrayElements; + _jni->GetDoubleArrayElements = GetDoubleArrayElements; + _jni->ReleaseBooleanArrayElements = ReleaseBooleanArrayElements; + _jni->ReleaseByteArrayElements = ReleaseByteArrayElements; + _jni->ReleaseCharArrayElements = ReleaseCharArrayElements; + _jni->ReleaseShortArrayElements = ReleaseShortArrayElements; + _jni->ReleaseIntArrayElements = ReleaseIntArrayElements; + _jni->ReleaseLongArrayElements = ReleaseLongArrayElements; + _jni->ReleaseFloatArrayElements = ReleaseFloatArrayElements; + _jni->ReleaseDoubleArrayElements = ReleaseDoubleArrayElements; + _jni->GetBooleanArrayRegion = GetBooleanArrayRegion; + _jni->GetByteArrayRegion = GetByteArrayRegion; + _jni->GetCharArrayRegion = GetCharArrayRegion; + _jni->GetShortArrayRegion = GetShortArrayRegion; + _jni->GetIntArrayRegion = GetIntArrayRegion; + _jni->GetLongArrayRegion = GetLongArrayRegion; + _jni->GetFloatArrayRegion = GetFloatArrayRegion; + _jni->GetDoubleArrayRegion = GetDoubleArrayRegion; + _jni->SetBooleanArrayRegion = SetBooleanArrayRegion; + _jni->SetByteArrayRegion = SetByteArrayRegion; + _jni->SetCharArrayRegion = SetCharArrayRegion; + _jni->SetShortArrayRegion = SetShortArrayRegion; + _jni->SetIntArrayRegion = SetIntArrayRegion; + _jni->SetLongArrayRegion = SetLongArrayRegion; + _jni->SetFloatArrayRegion = SetFloatArrayRegion; + _jni->SetDoubleArrayRegion = SetDoubleArrayRegion; + _jni->RegisterNatives = RegisterNatives; + _jni->UnregisterNatives = UnregisterNatives; + _jni->MonitorEnter = MonitorEnter; + _jni->MonitorExit = MonitorExit; + _jni->GetJavaVM = GetJavaVM; + _jni->GetStringRegion = GetStringRegion; + _jni->GetStringUTFRegion = GetStringUTFRegion; + _jni->GetPrimitiveArrayCritical = GetPrimitiveArrayCritical; + _jni->ReleasePrimitiveArrayCritical = ReleasePrimitiveArrayCritical; + _jni->GetStringCritical = GetStringCritical; + _jni->ReleaseStringCritical = ReleaseStringCritical; + _jni->NewWeakGlobalRef = NewWeakGlobalRef; + _jni->DeleteWeakGlobalRef = DeleteWeakGlobalRef; + _jni->ExceptionCheck = ExceptionCheck; + _jni->NewDirectByteBuffer = NewDirectByteBuffer; + _jni->GetDirectBufferAddress = GetDirectBufferAddress; + _jni->GetDirectBufferCapacity = GetDirectBufferCapacity; + _jni->GetObjectRefType = GetObjectRefType; + + jvm = _jvm; + jni = _jni; +} diff --git a/src/android/FalsoJNI/FalsoJNI.h b/src/android/FalsoJNI/FalsoJNI.h new file mode 100644 index 0000000..ccff872 --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI.h @@ -0,0 +1,36 @@ +/* + * FalsoJNI.h + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2022 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef FALSOJNI_H +#define FALSOJNI_H + +#define FALSOJNI_DEBUG_NO 4 +#define FALSOJNI_DEBUG_ERROR 3 +#define FALSOJNI_DEBUG_WARN 2 +#define FALSOJNI_DEBUG_INFO 1 +#define FALSOJNI_DEBUG_ALL 0 + +#ifndef FALSOJNI_DEBUGLEVEL +#define FALSOJNI_DEBUGLEVEL FALSOJNI_DEBUG_WARN +#endif + +#include "jni.h" + +#ifdef FALSOJNI_IMPLEMENTATION_SAMPLE +#include "FalsoJNI_ImplSample.h" +#endif + +extern JavaVM jvm; +extern JNIEnv jni; + +void jni_init(); + +#endif // FALSOJNI_H diff --git a/src/android/FalsoJNI/FalsoJNI_Impl.h b/src/android/FalsoJNI/FalsoJNI_Impl.h new file mode 100644 index 0000000..ab53f14 --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI_Impl.h @@ -0,0 +1,91 @@ +/* + * FalsoJNI_Impl.h + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2022 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef FALSOJNI_IMPL +#define FALSOJNI_IMPL + + +#include "FalsoJNI_ImplBridge.h" + +extern NameToMethodID nameToMethodId[]; + +extern MethodsBoolean methodsBoolean[]; +extern MethodsByte methodsByte[]; +extern MethodsChar methodsChar[]; +extern MethodsDouble methodsDouble[]; +extern MethodsFloat methodsFloat[]; +extern MethodsInt methodsInt[]; +extern MethodsLong methodsLong[]; +extern MethodsObject methodsObject[]; +extern MethodsShort methodsShort[]; +extern MethodsVoid methodsVoid[]; + +extern NameToFieldID nameToFieldId[]; + +extern FieldsBoolean fieldsBoolean[]; +extern FieldsByte fieldsByte[]; +extern FieldsChar fieldsChar[]; +extern FieldsDouble fieldsDouble[]; +extern FieldsFloat fieldsFloat[]; +extern FieldsInt fieldsInt[]; +extern FieldsObject fieldsObject[]; +extern FieldsLong fieldsLong[]; +extern FieldsShort fieldsShort[]; + +extern size_t nameToMethodId_size(); + +extern size_t methodsBoolean_size(); +extern size_t methodsByte_size(); +extern size_t methodsChar_size(); +extern size_t methodsDouble_size(); +extern size_t methodsFloat_size(); +extern size_t methodsInt_size(); +extern size_t methodsLong_size(); +extern size_t methodsObject_size(); +extern size_t methodsShort_size(); +extern size_t methodsVoid_size(); + +extern size_t nameToFieldId_size(); + +extern size_t fieldsBoolean_size(); +extern size_t fieldsByte_size(); +extern size_t fieldsChar_size(); +extern size_t fieldsDouble_size(); +extern size_t fieldsFloat_size(); +extern size_t fieldsInt_size(); +extern size_t fieldsObject_size(); +extern size_t fieldsLong_size(); +extern size_t fieldsShort_size(); + +#define __FALSOJNI_IMPL_CONTAINER_SIZES \ +size_t nameToMethodId_size() { return sizeof nameToMethodId; } \ +size_t methodsBoolean_size() { return sizeof methodsBoolean; } \ +size_t methodsByte_size() { return sizeof methodsByte; } \ +size_t methodsChar_size() { return sizeof methodsChar; } \ +size_t methodsDouble_size() { return sizeof methodsDouble; } \ +size_t methodsFloat_size() { return sizeof methodsFloat; } \ +size_t methodsInt_size() { return sizeof methodsInt; } \ +size_t methodsLong_size() { return sizeof methodsLong; } \ +size_t methodsObject_size() { return sizeof methodsObject; } \ +size_t methodsShort_size() { return sizeof methodsShort; } \ +size_t methodsVoid_size() { return sizeof methodsVoid; } \ +size_t nameToFieldId_size() { return sizeof nameToFieldId; } \ +size_t fieldsBoolean_size() { return sizeof fieldsBoolean; } \ +size_t fieldsByte_size() { return sizeof fieldsByte; } \ +size_t fieldsChar_size() { return sizeof fieldsChar; } \ +size_t fieldsDouble_size() { return sizeof fieldsDouble; } \ +size_t fieldsFloat_size() { return sizeof fieldsFloat; } \ +size_t fieldsInt_size() { return sizeof fieldsInt; } \ +size_t fieldsObject_size() { return sizeof fieldsObject; } \ +size_t fieldsLong_size() { return sizeof fieldsLong; } \ +size_t fieldsShort_size() { return sizeof fieldsShort; } + +#endif // FALSOJNI_IMPL diff --git a/src/android/FalsoJNI/FalsoJNI_ImplBridge.c b/src/android/FalsoJNI/FalsoJNI_ImplBridge.c new file mode 100644 index 0000000..a41e106 --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI_ImplBridge.c @@ -0,0 +1,591 @@ +/* + * FalsoJNI_ImplBridge.c + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2021 Andy Nguyen + * Copyright (C) 2021 Rinnegatamante + * Copyright (C) 2022 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#include "FalsoJNI_Impl.h" +#include "FalsoJNI_Logger.h" + +#include "FalsoJNI_ImplBridge.h" + +#include +#include +#include +#include + +JavaDynArray* javaDynArrays = NULL; +pthread_mutex_t* javaDynArrays_mutex = NULL; +size_t javaDynArrays_free = 0; +size_t javaDynArrays_taken = 0; + +JavaDynArray** javaDynArrays_static = NULL; +size_t javaDynArrays_static_count = 0; +size_t javaDynArrays_static_capacity = 16; + +jfieldID getFieldIdByName(const char* name) { + for (int i = 0; i < nameToFieldId_size() / sizeof(NameToFieldID); i++) { + if (strcmp(name, nameToFieldId[i].name) == 0) { + return (jfieldID)(uintptr_t)nameToFieldId[i].id; + } + } + + fjni_logv_warn("Unknown field name \"%s\"", name); + return NULL; +} + +const char* fieldTypeToStr(FIELD_TYPE t) { + switch (t) { + case FIELD_TYPE_INT: + return "FIELD_TYPE_INT"; + case FIELD_TYPE_OBJECT: + return "FIELD_TYPE_OBJECT"; + case FIELD_TYPE_BOOLEAN: + return "FIELD_TYPE_BOOLEAN"; + case FIELD_TYPE_BYTE: + return "FIELD_TYPE_BYTE"; + case FIELD_TYPE_CHAR: + return "FIELD_TYPE_CHAR"; + case FIELD_TYPE_SHORT: + return "FIELD_TYPE_SHORT"; + case FIELD_TYPE_LONG: + return "FIELD_TYPE_LONG"; + case FIELD_TYPE_FLOAT: + return "FIELD_TYPE_FLOAT"; + case FIELD_TYPE_DOUBLE: + return "FIELD_TYPE_DOUBLE"; + default: + return "FIELD_TYPE_UNKNOWN"; + } +} + +jsize getFieldTypeSize(FIELD_TYPE fieldType) { + switch (fieldType) { + case FIELD_TYPE_OBJECT: + return sizeof(jobject); + case FIELD_TYPE_BOOLEAN: + return sizeof(jboolean); + case FIELD_TYPE_BYTE: + return sizeof(jbyte); + case FIELD_TYPE_CHAR: + return sizeof(jchar); + case FIELD_TYPE_SHORT: + return sizeof(jshort); + case FIELD_TYPE_INT: + return sizeof(jint); + case FIELD_TYPE_LONG: + return sizeof(jlong); + case FIELD_TYPE_FLOAT: + return sizeof(jfloat); + case FIELD_TYPE_DOUBLE: + return sizeof(jdouble); + default: + return sizeof(void *); + } +} + +jobject getObjectFieldValueById(jfieldID id) { + getFieldValueById(jobject, FIELD_TYPE_OBJECT, FieldsObject, fieldsObject, fieldsObject_size, id, (jobject)0x42424242); +} + +jint getIntFieldValueById(jfieldID id) { + getFieldValueById(jint, FIELD_TYPE_INT, FieldsInt, fieldsInt, fieldsInt_size, id, 1); +} + +jboolean getBooleanFieldValueById(jfieldID id) { + getFieldValueById(jboolean, FIELD_TYPE_BOOLEAN, FieldsBoolean, fieldsBoolean, fieldsBoolean_size, id, JNI_FALSE); +} + +jbyte getByteFieldValueById(jfieldID id) { + getFieldValueById(jbyte, FIELD_TYPE_BYTE, FieldsByte, fieldsByte, fieldsByte_size, id, 'a'); +} + +jchar getCharFieldValueById(jfieldID id) { + getFieldValueById(jchar, FIELD_TYPE_CHAR, FieldsChar, fieldsChar, fieldsChar_size, id, 'b'); +} + +jshort getShortFieldValueById(jfieldID id) { + getFieldValueById(jshort, FIELD_TYPE_SHORT, FieldsShort, fieldsShort, fieldsShort_size, id, 1); +} + +jlong getLongFieldValueById(jfieldID id) { + getFieldValueById(jlong, FIELD_TYPE_LONG, FieldsLong, fieldsLong, fieldsLong_size, id, 1); +} + +jfloat getFloatFieldValueById(jfieldID id) { + getFieldValueById(jfloat, FIELD_TYPE_FLOAT, FieldsFloat, fieldsFloat, fieldsFloat_size, id, 1.0f); +} + +jdouble getDoubleFieldValueById(jfieldID id) { + getFieldValueById(jdouble, FIELD_TYPE_DOUBLE, FieldsDouble, fieldsDouble, fieldsDouble_size, id, 1); +} + +void setObjectFieldValueById(jfieldID id, jobject value) { + setFieldValueById(jobject, FIELD_TYPE_OBJECT, FieldsObject, fieldsObject, fieldsObject_size, id, value); +} + +void setIntFieldValueById(jfieldID id, jint value) { + setFieldValueById(jint, FIELD_TYPE_INT, FieldsInt, fieldsInt, fieldsInt_size, id, value); +} + +void setBooleanFieldValueById(jfieldID id, jboolean value) { + setFieldValueById(jboolean, FIELD_TYPE_BOOLEAN, FieldsBoolean, fieldsBoolean, fieldsBoolean_size, id, value); +} + +void setByteFieldValueById(jfieldID id, jbyte value) { + setFieldValueById(jbyte, FIELD_TYPE_BYTE, FieldsByte, fieldsByte, fieldsByte_size, id, value); +} + +void setCharFieldValueById(jfieldID id, jchar value) { + setFieldValueById(jchar, FIELD_TYPE_CHAR, FieldsChar, fieldsChar, fieldsChar_size, id, value); +} + +void setShortFieldValueById(jfieldID id, jshort value) { + setFieldValueById(jshort, FIELD_TYPE_SHORT, FieldsShort, fieldsShort, fieldsShort_size, id, value); +} + +void setLongFieldValueById(jfieldID id, jlong value) { + setFieldValueById(jlong, FIELD_TYPE_LONG, FieldsLong, fieldsLong, fieldsLong_size, id, value); +} + +void setFloatFieldValueById(jfieldID id, jfloat value) { + setFieldValueById(jfloat, FIELD_TYPE_FLOAT, FieldsFloat, fieldsFloat, fieldsFloat_size, id, value); +} + +void setDoubleFieldValueById(jfieldID id, jdouble value) { + setFieldValueById(jdouble, FIELD_TYPE_DOUBLE, FieldsDouble, fieldsDouble, fieldsDouble_size, id, value); +} + +jmethodID getMethodIdByName(const char* name) { + for (int i = 0; i < nameToMethodId_size() / sizeof(NameToMethodID); i++) { + if (strcmp(name, nameToMethodId[i].name) == 0) { + return (jmethodID)(uintptr_t)nameToMethodId[i].id; + } + } + return NULL; +} + +jobject methodObjectCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsObject_size() / sizeof(MethodsObject); i++) { + if (methodsObject[i].id == (int)(uintptr_t)id) { + return methodsObject[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return NULL; +} + +void methodVoidCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsVoid_size() / sizeof(MethodsVoid); i++) { + if (methodsVoid[i].id == (int)(uintptr_t)id) { + return methodsVoid[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); +} + +jboolean methodBooleanCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsBoolean_size() / sizeof(MethodsBoolean); i++) { + if (methodsBoolean[i].id == (int)(uintptr_t)id) { + return methodsBoolean[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return JNI_FALSE; +} + +jbyte methodByteCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsByte_size() / sizeof(MethodsByte); i++) { + if (methodsByte[i].id == (int)(uintptr_t)id) { + return methodsByte[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return 0; +} + +jshort methodShortCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsShort_size() / sizeof(MethodsShort); i++) { + if (methodsShort[i].id == (int)(uintptr_t)id) { + return methodsShort[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return 0; +} + +jdouble methodDoubleCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsDouble_size() / sizeof(MethodsDouble); i++) { + if (methodsDouble[i].id == (int)(uintptr_t)id) { + return methodsDouble[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return 0; +} + +jchar methodCharCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsChar_size() / sizeof(MethodsChar); i++) { + if (methodsChar[i].id == (int)(uintptr_t)id) { + return methodsChar[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return 0; +} + +jlong methodLongCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsLong_size() / sizeof(MethodsLong); i++) { + if (methodsLong[i].id == (int)(uintptr_t)id) { + return methodsLong[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return -1; +} + +jint methodIntCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsInt_size() / sizeof(MethodsInt); i++) { + if (methodsInt[i].id == (int)(uintptr_t)id) { + return methodsInt[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return -1; +} + +jfloat methodFloatCall(jmethodID id, va_list args) { + for (int i = 0; i < methodsFloat_size() / sizeof(MethodsFloat); i++) { + if (methodsFloat[i].id == (int)(uintptr_t)id) { + return methodsFloat[i].Method(id, args); + } + } + + fjni_logv_warn("method ID %i not found!", (int)(uintptr_t)id); + return -1; +} + +void jda_lock() { + if (javaDynArrays_mutex == NULL) { + pthread_mutex_t initTmpNormal; + javaDynArrays_mutex = malloc(sizeof(pthread_mutex_t)); + memcpy(javaDynArrays_mutex, &initTmpNormal, sizeof(pthread_mutex_t)); + + if (pthread_mutex_init(javaDynArrays_mutex, NULL) != 0) { + fjni_log_err("Failed to allocate dynarr mutex"); + javaDynArrays_mutex = NULL; + return; + } + } + + pthread_mutex_lock(javaDynArrays_mutex); +} + +void jda_unlock() { + if (javaDynArrays_mutex != NULL) { + pthread_mutex_unlock(javaDynArrays_mutex); + } +} + +jboolean jda_tryinit() { + if (javaDynArrays == NULL) { + javaDynArrays = malloc(16 * sizeof(JavaDynArray)); + if (!javaDynArrays) + return JNI_FALSE; + + for (int i = 0; i < 16; ++i) { + javaDynArrays[i].array = NULL; + javaDynArrays[i].len = -1; + javaDynArrays[i].type = FIELD_TYPE_UNKNOWN; + } + + javaDynArrays_free = 16; + javaDynArrays_taken = 0; + } + + if (javaDynArrays_static == NULL) { + javaDynArrays_static = malloc(16 * sizeof(JavaDynArray *)); + if (!javaDynArrays_static) + return JNI_FALSE; + + for (int i = 0; i < 16; ++i) { + javaDynArrays_static[i] = NULL; + } + + javaDynArrays_static_capacity = 16; + javaDynArrays_static_count = 0; + } + + return JNI_TRUE; +} + +jboolean jda_extend() { + if (jda_tryinit() == JNI_FALSE) + return JNI_FALSE; + + if (javaDynArrays_free == 0) { + if (javaDynArrays == NULL) + return JNI_FALSE; + + javaDynArrays = realloc(javaDynArrays, (sizeof(JavaDynArray) * (javaDynArrays_taken+16))); + + if (javaDynArrays == NULL) + return JNI_FALSE; + + for (int i = javaDynArrays_taken; i < javaDynArrays_taken+16; ++i) { + javaDynArrays[i].array = NULL; + javaDynArrays[i].len = -1; + javaDynArrays[i].type = FIELD_TYPE_UNKNOWN; + } + + javaDynArrays_free = 16; + } + + if (javaDynArrays_static_capacity - javaDynArrays_static_count == 0) { + if (javaDynArrays_static == NULL) + return JNI_FALSE; + + javaDynArrays_static = realloc(javaDynArrays_static, (sizeof(JavaDynArray *) * (javaDynArrays_static_count+16))); + + for (int i = javaDynArrays_static_count; i < javaDynArrays_static_count + 16; ++i) { + javaDynArrays_static[i] = NULL; + } + + javaDynArrays_static_capacity += 16; + } + + return JNI_TRUE; +} + +JavaDynArray * jda_alloc(jsize len, FIELD_TYPE type) { + jda_lock(); + + void * array = malloc(len * getFieldTypeSize(type)); + if (!array) { + jda_unlock(); + return NULL; + } + + if (jda_extend() == JNI_FALSE) { + jda_unlock(); + free(array); + return NULL; + } + + int index = -1; + for (int i = 0; i < (javaDynArrays_taken+javaDynArrays_free); ++i) { + if (javaDynArrays[i].array == NULL) { + index = i; + } + } + + if (index == -1) { + jda_unlock(); + free(array); + return NULL; + } + + javaDynArrays[index].array = array; + javaDynArrays[index].len = len; + javaDynArrays[index].type = type; + + javaDynArrays_taken++; + javaDynArrays_free--; + + JavaDynArray * ret = &javaDynArrays[index]; + jda_unlock(); + return ret; +} + +jboolean jda_alloc_static(JavaDynArray * jda, jsize len, FIELD_TYPE type) { + jda_lock(); + + void * array = malloc(len * getFieldTypeSize(type)); + if (!array) { + jda_unlock(); + return JNI_FALSE; + } + + if (jda_extend() == JNI_FALSE) { + jda_unlock(); + free(array); + return JNI_FALSE; + } + + int index = -1; + for (int i = 0; i < javaDynArrays_static_capacity; ++i) { + if (javaDynArrays_static[i] == NULL) { + index = i; + } + } + + if (index == -1) { + jda_unlock(); + free(array); + return JNI_FALSE; + } + + javaDynArrays_static[index] = jda; + + jda->array = array; + jda->len = len; + jda->type = type; + + javaDynArrays_static_count++; + + jda_unlock(); + return JNI_TRUE; +} + +jsize jda_sizeof(JavaDynArray * jda) { + if (!jda) return -1; + if (!javaDynArrays && !javaDynArrays_static) return -1; + + jda_lock(); + if (javaDynArrays_taken == 0 && javaDynArrays_static_count == 0) { + jda_unlock(); + return -1; + } + + for (int i = 0; i < (javaDynArrays_taken+javaDynArrays_free); i++) { + if (jda == &javaDynArrays[i]) { + if (javaDynArrays[i].array == NULL) { + jda_unlock(); + return -1; + } + jsize ret = javaDynArrays[i].len; + jda_unlock(); + return ret; + } + } + + for (int i = 0; i < (javaDynArrays_static_capacity); i++) { + if (jda == javaDynArrays_static[i]) { + if (javaDynArrays_static[i]->array == NULL) { + jda_unlock(); + return -1; + } + jsize ret = javaDynArrays_static[i]->len; + jda_unlock(); + return ret; + } + } + + jda_unlock(); + return -1; +} + +jboolean jda_free(JavaDynArray * jda) { + if (!jda) return JNI_FALSE; + if (!javaDynArrays && !javaDynArrays_static) return JNI_FALSE; + + jda_lock(); + if (javaDynArrays_taken == 0 && javaDynArrays_static_count == 0) { + jda_unlock(); + return JNI_FALSE; + } + + int index = -1; + for (int i = 0; i < (javaDynArrays_taken+javaDynArrays_free); ++i) { + if (jda == &javaDynArrays[i]) { + index = i; + } + } + + if (index != -1) { + free(javaDynArrays[index].array); + javaDynArrays[index].array = NULL; + javaDynArrays[index].type = FIELD_TYPE_UNKNOWN; + javaDynArrays[index].len = 0; + + javaDynArrays_taken--; + javaDynArrays_free++; + jda_unlock(); + return JNI_TRUE; + } + + for (int i = 0; i < javaDynArrays_static_capacity; ++i) { + if (jda == javaDynArrays_static[i]) { + index = i; + } + } + + if (index != -1) { + free(javaDynArrays_static[index]->array); + javaDynArrays_static[index]->array = NULL; + javaDynArrays_static[index]->type = FIELD_TYPE_UNKNOWN; + javaDynArrays_static[index]->len = 0; + + javaDynArrays_static[index] = NULL; + javaDynArrays_static_count--; + + jda_unlock(); + return JNI_TRUE; + } + + jda_unlock(); + return JNI_FALSE; +} + +JavaDynArray * jda_find(void * arr) { + if (!arr) return NULL; + if (!javaDynArrays && !javaDynArrays_static) return NULL; + + jda_lock(); + if (javaDynArrays_taken == 0 && javaDynArrays_static_count == 0) { + jda_unlock(); + return NULL; + } + + for (int i = 0; i < (javaDynArrays_taken+javaDynArrays_free); ++i) { + if (arr == &javaDynArrays[i]) { + if (javaDynArrays[i].array == NULL) { + jda_unlock(); + return NULL; + } + JavaDynArray * ret = &javaDynArrays[i]; + jda_unlock(); + return ret; + } + } + + for (int i = 0; i < javaDynArrays_static_capacity; ++i) { + if (arr == javaDynArrays_static[i]) { + if (javaDynArrays_static[i]->array == NULL) { + jda_unlock(); + return NULL; + } + JavaDynArray * ret = javaDynArrays_static[i]; + jda_unlock(); + return ret; + } + } + + jda_unlock(); + return NULL; +} + +va_list _AtoV(int dummy, ...) { + va_list args1; + va_start(args1, dummy); + va_list args2; + va_copy(args2, args1); + va_end(args1); + return args2; +} diff --git a/src/android/FalsoJNI/FalsoJNI_ImplBridge.h b/src/android/FalsoJNI/FalsoJNI_ImplBridge.h new file mode 100644 index 0000000..9a48894 --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI_ImplBridge.h @@ -0,0 +1,243 @@ +/* + * FalsoJNI_ImplBridge.h + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2021 Andy Nguyen + * Copyright (C) 2021 Rinnegatamante + * Copyright (C) 2022 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef FALSOJNI_IMPL_BRIDGE +#define FALSOJNI_IMPL_BRIDGE + +#include +#include +#include "jni.h" + +/* + * Type definitions for Fields + */ + +typedef enum FIELD_TYPE { + FIELD_TYPE_UNKNOWN = 0, + FIELD_TYPE_OBJECT = 1, + FIELD_TYPE_BOOLEAN = 2, + FIELD_TYPE_BYTE = 3, + FIELD_TYPE_CHAR = 4, + FIELD_TYPE_SHORT = 5, + FIELD_TYPE_INT = 6, + FIELD_TYPE_LONG = 7, + FIELD_TYPE_FLOAT = 8, + FIELD_TYPE_DOUBLE = 9 +} FIELD_TYPE; + +typedef struct { + int id; + char *name; + FIELD_TYPE f; +} NameToFieldID; + +typedef struct { int id; jobject value; } FieldsObject; +typedef struct { int id; jboolean value; } FieldsBoolean; +typedef struct { int id; jbyte value; } FieldsByte; +typedef struct { int id; jchar value; } FieldsChar; +typedef struct { int id; jshort value; } FieldsShort; +typedef struct { int id; jint value; } FieldsInt; +typedef struct { int id; jlong value; } FieldsLong; +typedef struct { int id; jfloat value; } FieldsFloat; +typedef struct { int id; jdouble value; } FieldsDouble; + +jfieldID getFieldIdByName(const char* name); +jsize getFieldTypeSize(FIELD_TYPE fieldType); + +jobject getObjectFieldValueById(jfieldID id); +jboolean getBooleanFieldValueById(jfieldID id); +jbyte getByteFieldValueById(jfieldID id); +jchar getCharFieldValueById(jfieldID id); +jshort getShortFieldValueById(jfieldID id); +jint getIntFieldValueById(jfieldID id); +jlong getLongFieldValueById(jfieldID id); +jfloat getFloatFieldValueById(jfieldID id); +jdouble getDoubleFieldValueById(jfieldID id); + +void setObjectFieldValueById(jfieldID id, jobject value); +void setBooleanFieldValueById(jfieldID id, jboolean value); +void setByteFieldValueById(jfieldID id, jbyte value); +void setCharFieldValueById(jfieldID id, jchar value); +void setShortFieldValueById(jfieldID id, jshort value); +void setIntFieldValueById(jfieldID id, jint value); +void setLongFieldValueById(jfieldID id, jlong value); +void setFloatFieldValueById(jfieldID id, jfloat value); +void setDoubleFieldValueById(jfieldID id, jdouble value); + +/* + * Type definitions for Methods + */ + +typedef enum METHOD_TYPE { + METHOD_TYPE_UNKNOWN = 0, + METHOD_TYPE_VOID = 1, + METHOD_TYPE_OBJECT = 2, + METHOD_TYPE_BOOLEAN = 3, + METHOD_TYPE_BYTE = 4, + METHOD_TYPE_CHAR = 5, + METHOD_TYPE_SHORT = 6, + METHOD_TYPE_INT = 7, + METHOD_TYPE_LONG = 8, + METHOD_TYPE_FLOAT = 9, + METHOD_TYPE_DOUBLE = 10 +} METHOD_TYPE; + +typedef struct { + int id; + char *name; + METHOD_TYPE f; +} NameToMethodID; + +typedef struct { int id; void (*Method)(jmethodID id, va_list args); } MethodsVoid; +typedef struct { int id; jobject (*Method)(jmethodID id, va_list args); } MethodsObject; +typedef struct { int id; jboolean (*Method)(jmethodID id, va_list args); } MethodsBoolean; +typedef struct { int id; jbyte (*Method)(jmethodID id, va_list args); } MethodsByte; +typedef struct { int id; jchar (*Method)(jmethodID id, va_list args); } MethodsChar; +typedef struct { int id; jshort (*Method)(jmethodID id, va_list args); } MethodsShort; +typedef struct { int id; jint (*Method)(jmethodID id, va_list args); } MethodsInt; +typedef struct { int id; jlong (*Method)(jmethodID id, va_list args); } MethodsLong; +typedef struct { int id; jfloat (*Method)(jmethodID id, va_list args); } MethodsFloat; +typedef struct { int id; jdouble (*Method)(jmethodID id, va_list args); } MethodsDouble; + +jmethodID getMethodIdByName(const char* name); + +void methodVoidCall(jmethodID id, va_list args); +jobject methodObjectCall(jmethodID id, va_list args); +jboolean methodBooleanCall(jmethodID id, va_list args); +jbyte methodByteCall(jmethodID id, va_list args); +jchar methodCharCall(jmethodID id, va_list args); +jshort methodShortCall(jmethodID id, va_list args); +jint methodIntCall(jmethodID id, va_list args); +jlong methodLongCall(jmethodID id, va_list args); +jfloat methodFloatCall(jmethodID id, va_list args); +jdouble methodDoubleCall(jmethodID id, va_list args); + +/* + * Dynamically allocated arrays + */ + +typedef struct { + jarray array; + jsize len; + FIELD_TYPE type; +} JavaDynArray; + +JavaDynArray * jda_alloc(jsize len, FIELD_TYPE type); +jboolean jda_alloc_static(JavaDynArray * jda, jsize len, FIELD_TYPE type); +jsize jda_sizeof(JavaDynArray * jda); +jboolean jda_free(JavaDynArray * jda); +JavaDynArray * jda_find(void * arr); + +/* + * Helper macros / functions + */ + +va_list _AtoV(int dummy, ...); + +#define getFieldValueById(jtype, fieldtype, containertype, container, containersize, id, defaultval) ({ \ + for (int i = 0; i < nameToFieldId_size() / sizeof(NameToFieldID); i++) { \ + if (nameToFieldId[i].id == (int)(uintptr_t)(id)) { \ + if (nameToFieldId[i].f != (fieldtype)) { \ + fjni_logv_err("Field type mismatch for field #%i: expected %s, found %s", (int)(uintptr_t)id, fieldTypeToStr(fieldtype), fieldTypeToStr(nameToFieldId[i].f)); \ + return defaultval; \ + } \ + \ + const jtype * x = NULL; \ + for (int u = 0; u < containersize() / sizeof(containertype); u++) { \ + if (container[u].id == (int)(uintptr_t)id) { \ + x = &container[u].value; \ + } \ + } \ + \ + if (!x) { \ + fjni_logv_err("Field #%i is defined in NameToFieldID table but has no value set", (int)(uintptr_t)id); \ + return defaultval; \ + } \ + \ + return *x; \ + } \ + } \ + fjni_logv_err("Undefined fieldID #%i", (int)(uintptr_t)id); \ + return defaultval; \ +}) + +#define setFieldValueById(jtype, fieldtype, containertype, container, containersize, id, value) ({ \ + for (int i = 0; i < nameToFieldId_size() / sizeof(NameToFieldID); i++) { \ + if (nameToFieldId[i].id == (int)(uintptr_t)(id)) { \ + if (nameToFieldId[i].f != (fieldtype)) { \ + fjni_logv_err("Field type mismatch for field #%i: expected %s, found %s", (int)(uintptr_t)(id), fieldTypeToStr(fieldtype), fieldTypeToStr(nameToFieldId[i].f)); \ + return; \ + } \ + \ + jtype * x = NULL; \ + for (int u = 0; u < containersize() / sizeof(containertype); u++) { \ + if ((container)[u].id == (int)(uintptr_t)(id)) { \ + x = &(container)[u].value; \ + } \ + } \ + \ + if (!x) { \ + fjni_logv_err("Field #%i is defined in NameToFieldID table but has no value set", (int)(uintptr_t)(id)); \ + return; \ + } \ + \ + *x = value; \ + } \ + } \ + fjni_logv_err("Undefined fieldID #%i", (int)(uintptr_t)(id)); \ + return; \ +}) + +#define GetPrimitiveArrayRegion(fun_name, fieldType, jType, array, start, length, buffer) ({ \ + JavaDynArray * jda = jda_find((void *) array); \ + if (!jda) { \ + fjni_logv_err("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR "): Array not found.", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer)); \ + return; \ + } \ + \ + if (start < 0 || length > jda->len) { \ + fjni_logv_err("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR "): Index out of bounds! (real length: %i)", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer), jda->len); \ + return; \ + } \ + \ + fjni_logv_dbg("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR ")", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer)); \ + \ + if (!buffer) \ + buffer = (jType*) malloc(length); \ + \ + jType* arr = jda->array; \ + memcpy(buffer, &arr[start], length * getFieldTypeSize(fieldType));\ +}) + +#define SetPrimitiveArrayRegion(fun_name, fieldType, jType, array, start, length, buffer) ({ \ + JavaDynArray * jda = jda_find((void *) array); \ + if (!jda) { \ + fjni_logv_err("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR "): Array not found!", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer)); \ + return; \ + } \ + \ + if (start < 0 || length > jda->len) { \ + fjni_logv_err("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR "): Index out of bounds! (real length: %i)", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer), jda->len); \ + return; \ + } \ + \ + fjni_logv_dbg("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR ")", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer)); \ + \ + if (!buffer) \ + fjni_logv_warn("[JNI] %s(env, 0x%" PRIxPTR ", %i, %i, 0x%" PRIxPTR "): buffer is NULL", fun_name, (uintptr_t)array, start, length, (uintptr_t)(void*)(buffer)); \ + \ + jType* arr = jda->array; \ + memcpy(&arr[start], buffer, length * getFieldTypeSize(fieldType));\ +}) + +#endif // FALSOJNI_IMPL_BRIDGE diff --git a/src/android/FalsoJNI/FalsoJNI_ImplSample.h b/src/android/FalsoJNI/FalsoJNI_ImplSample.h new file mode 100644 index 0000000..c50c4cd --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI_ImplSample.h @@ -0,0 +1,60 @@ +/* + * FalsoJNI_ImplSample.h + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2022 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef FALSOJNI_IMPL_SAMPLE +#define FALSOJNI_IMPL_SAMPLE + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * COPY STARTING FROM HERE! + */ + +#include "FalsoJNI_Impl.h" + +NameToMethodID nameToMethodId[] = {}; + +MethodsBoolean methodsBoolean[] = {}; +MethodsByte methodsByte[] = {}; +MethodsChar methodsChar[] = {}; +MethodsDouble methodsDouble[] = {}; +MethodsFloat methodsFloat[] = {}; +MethodsInt methodsInt[] = {}; +MethodsLong methodsLong[] = {}; +MethodsObject methodsObject[] = {}; +MethodsShort methodsShort[] = {}; +MethodsVoid methodsVoid[] = {}; + +NameToFieldID nameToFieldId[] = {}; + +FieldsBoolean fieldsBoolean[] = {}; +FieldsByte fieldsByte[] = {}; +FieldsChar fieldsChar[] = {}; +FieldsDouble fieldsDouble[] = {}; +FieldsFloat fieldsFloat[] = {}; +FieldsInt fieldsInt[] = {}; +FieldsObject fieldsObject[] = {}; +FieldsLong fieldsLong[] = {}; +FieldsShort fieldsShort[] = {}; + +__FALSOJNI_IMPL_CONTAINER_SIZES + +/* + * COPY UP TO HERE! + */ + +#ifdef __cplusplus +}; +#endif + +#endif // FALSOJNI_IMPL_SAMPLE diff --git a/src/android/FalsoJNI/FalsoJNI_Logger.c b/src/android/FalsoJNI/FalsoJNI_Logger.c new file mode 100644 index 0000000..d44c964 --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI_Logger.c @@ -0,0 +1,90 @@ +/* + * FalsoJNI_Logger.c + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2022-2023 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#include +#include +#include +#include +#include + +#include "FalsoJNI_Logger.h" +#include "FalsoJNI.h" + +#define COLOR_RED "\x1B[31m" +#define COLOR_ORANGE "\x1B[33m" +#define COLOR_BLUE "\x1B[34m" +#define COLOR_END "\033[0m" + +static pthread_mutex_t _fjni_log_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define LOG_LOCK \ + pthread_mutex_lock(&_fjni_log_mutex); + +#define LOG_UNLOCK \ + pthread_mutex_unlock(&_fjni_log_mutex); + +#define LOG_PRINT \ + va_list list; \ + va_start(list, fmt); \ + vsnprintf(_fjni_log_buffer_2, sizeof(_fjni_log_buffer_2), _fjni_log_buffer_1, list); \ + va_end(list); \ + printf("%s", _fjni_log_buffer_2); + +static char _fjni_log_buffer_1[2048]; +static char _fjni_log_buffer_2[2048]; + +void _fjni_log_info(const char *fi, int li, const char *fn, const char* fmt, ...) { +#if FALSOJNI_DEBUGLEVEL <= FALSOJNI_DEBUG_INFO + LOG_LOCK + + snprintf(_fjni_log_buffer_1, sizeof(_fjni_log_buffer_1), + "%s[INFO] %s%s\n", COLOR_BLUE, fmt, COLOR_END); + + LOG_PRINT + LOG_UNLOCK +#endif +} + +void _fjni_log_warn(const char *fi, int li, const char *fn, const char* fmt, ...) { +#if FALSOJNI_DEBUGLEVEL <= FALSOJNI_DEBUG_WARN + LOG_LOCK + + snprintf(_fjni_log_buffer_1, sizeof(_fjni_log_buffer_1), + "%s[WARN][%s:%d][%s] %s%s\n", COLOR_ORANGE, fi, li, fn, fmt, COLOR_END); + + LOG_PRINT + LOG_UNLOCK +#endif +} + +void _fjni_log_debug(const char *fi, int li, const char *fn, const char* fmt, ...) { +#if FALSOJNI_DEBUGLEVEL <= FALSOJNI_DEBUG_ALL + LOG_LOCK + + snprintf(_fjni_log_buffer_1, sizeof(_fjni_log_buffer_1), + "[DBG][%s:%d][%s] %s\n", fi, li, fn, fmt); + + LOG_PRINT + LOG_UNLOCK +#endif +} + +void _fjni_log_error(const char *fi, int li, const char *fn, const char* fmt, ...) { +#if FALSOJNI_DEBUGLEVEL <= FALSOJNI_DEBUG_ERROR + LOG_LOCK + + snprintf(_fjni_log_buffer_1, sizeof(_fjni_log_buffer_1), + "%s[ERROR][%s:%d][%s] %s%s\n", COLOR_RED, fi, li, fn, fmt, COLOR_END); + + LOG_PRINT + LOG_UNLOCK +#endif +} diff --git a/src/android/FalsoJNI/FalsoJNI_Logger.h b/src/android/FalsoJNI/FalsoJNI_Logger.h new file mode 100644 index 0000000..0ae76dd --- /dev/null +++ b/src/android/FalsoJNI/FalsoJNI_Logger.h @@ -0,0 +1,43 @@ +/* + * FalsoJNI_Logger.h + * + * Fake Java Native Interface, providing JavaVM and JNIEnv objects. + * + * Copyright (C) 2022-2023 Volodymyr Atamanenko + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "bugprone-reserved-identifier" + +#ifndef FALSOJNI_LOGGER_H +#define FALSOJNI_LOGGER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define fjni_logv_info(fmt, ...) _fjni_log_info(__FILE__, __LINE__, __func__, fmt, __VA_ARGS__) +#define fjni_logv_warn(fmt, ...) _fjni_log_warn(__FILE__, __LINE__, __func__, fmt, __VA_ARGS__) +#define fjni_logv_dbg(fmt, ...) _fjni_log_debug(__FILE__, __LINE__, __func__, fmt, __VA_ARGS__) +#define fjni_logv_err(fmt, ...) _fjni_log_error(__FILE__, __LINE__, __func__, fmt, __VA_ARGS__) + +#define fjni_log_info(fmt) _fjni_log_info(__FILE__, __LINE__, __func__, fmt) +#define fjni_log_warn(fmt) _fjni_log_warn(__FILE__, __LINE__, __func__, fmt) +#define fjni_log_dbg(fmt) _fjni_log_debug(__FILE__, __LINE__, __func__, fmt) +#define fjni_log_err(fmt) _fjni_log_error(__FILE__, __LINE__, __func__, fmt) + +void _fjni_log_info(const char *fi, int li, const char *fn, const char* fmt, ...); +void _fjni_log_warn(const char *fi, int li, const char *fn, const char* fmt, ...); +void _fjni_log_debug(const char *fi, int li, const char *fn, const char* fmt, ...); +void _fjni_log_error(const char *fi, int li, const char *fn, const char* fmt, ...); + +#ifdef __cplusplus +}; +#endif + +#endif // FALSOJNI_LOGGER_H diff --git a/src/android/FalsoJNI/LICENSE b/src/android/FalsoJNI/LICENSE new file mode 100644 index 0000000..4e687d7 --- /dev/null +++ b/src/android/FalsoJNI/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 Volodymyr Atamanenko + +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. diff --git a/src/android/FalsoJNI/jni.h b/src/android/FalsoJNI/jni.h new file mode 100644 index 0000000..cc0b279 --- /dev/null +++ b/src/android/FalsoJNI/jni.h @@ -0,0 +1,1141 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * JNI specification, as defined by Sun: + * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + * + * Everything here is expected to be VM-neutral. + */ + +#pragma once + +#include +#include + +/* Primitive types that match up with Java equivalents. */ +typedef uint8_t jboolean; /* unsigned 8 bits */ +typedef int8_t jbyte; /* signed 8 bits */ +typedef uint16_t jchar; /* unsigned 16 bits */ +typedef int16_t jshort; /* signed 16 bits */ +typedef int32_t jint; /* signed 32 bits */ +typedef int64_t jlong; /* signed 64 bits */ +typedef float jfloat; /* 32-bit IEEE 754 */ +typedef double jdouble; /* 64-bit IEEE 754 */ + +/* "cardinal indices and sizes" */ +typedef jint jsize; + +#ifdef __cplusplus +/* + * Reference types, in C++ + */ +class _jobject {}; +class _jclass : public _jobject {}; +class _jstring : public _jobject {}; +class _jarray : public _jobject {}; +class _jobjectArray : public _jarray {}; +class _jbooleanArray : public _jarray {}; +class _jbyteArray : public _jarray {}; +class _jcharArray : public _jarray {}; +class _jshortArray : public _jarray {}; +class _jintArray : public _jarray {}; +class _jlongArray : public _jarray {}; +class _jfloatArray : public _jarray {}; +class _jdoubleArray : public _jarray {}; +class _jthrowable : public _jobject {}; + +typedef _jobject* jobject; +typedef _jclass* jclass; +typedef _jstring* jstring; +typedef _jarray* jarray; +typedef _jobjectArray* jobjectArray; +typedef _jbooleanArray* jbooleanArray; +typedef _jbyteArray* jbyteArray; +typedef _jcharArray* jcharArray; +typedef _jshortArray* jshortArray; +typedef _jintArray* jintArray; +typedef _jlongArray* jlongArray; +typedef _jfloatArray* jfloatArray; +typedef _jdoubleArray* jdoubleArray; +typedef _jthrowable* jthrowable; +typedef _jobject* jweak; + + +#else /* not __cplusplus */ + +/* + * Reference types, in C. + */ +typedef void* jobject; +typedef jobject jclass; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jobjectArray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jobject jthrowable; +typedef jobject jweak; + +#endif /* not __cplusplus */ + +struct _jfieldID; /* opaque structure */ +typedef struct _jfieldID* jfieldID; /* field IDs */ + +struct _jmethodID; /* opaque structure */ +typedef struct _jmethodID* jmethodID; /* method IDs */ + +struct JNIInvokeInterface; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +typedef enum jobjectRefType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + +typedef struct { + const char* name; + const char* signature; + void* fnPtr; +} JNINativeMethod; + +struct _JNIEnv; +struct _JavaVM; +typedef const struct JNINativeInterface* C_JNIEnv; + +#if defined(__cplusplus) +typedef _JNIEnv JNIEnv; +typedef _JavaVM JavaVM; +#else +typedef const struct JNINativeInterface* JNIEnv; +typedef const struct JNIInvokeInterface* JavaVM; +#endif + +/* + * Table of interface function pointers. + */ +struct JNINativeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + void* reserved3; + + jint (*GetVersion)(JNIEnv *); + + jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, + jsize); + jclass (*FindClass)(JNIEnv*, const char*); + + jmethodID (*FromReflectedMethod)(JNIEnv*, jobject); + jfieldID (*FromReflectedField)(JNIEnv*, jobject); + /* spec doesn't show jboolean parameter */ + jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean); + + jclass (*GetSuperclass)(JNIEnv*, jclass); + jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass); + + /* spec doesn't show jboolean parameter */ + jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean); + + jint (*Throw)(JNIEnv*, jthrowable); + jint (*ThrowNew)(JNIEnv *, jclass, const char *); + jthrowable (*ExceptionOccurred)(JNIEnv*); + void (*ExceptionDescribe)(JNIEnv*); + void (*ExceptionClear)(JNIEnv*); + void (*FatalError)(JNIEnv*, const char*); + + jint (*PushLocalFrame)(JNIEnv*, jint); + jobject (*PopLocalFrame)(JNIEnv*, jobject); + + jobject (*NewGlobalRef)(JNIEnv*, jobject); + void (*DeleteGlobalRef)(JNIEnv*, jobject); + void (*DeleteLocalRef)(JNIEnv*, jobject); + jboolean (*IsSameObject)(JNIEnv*, jobject, jobject); + + jobject (*NewLocalRef)(JNIEnv*, jobject); + jint (*EnsureLocalCapacity)(JNIEnv*, jint); + + jobject (*AllocObject)(JNIEnv*, jclass); + jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); + jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*); + + jclass (*GetObjectClass)(JNIEnv*, jobject); + jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass); + jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); + jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); + jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); + jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); + jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); + jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); + jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); + jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); + jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); + jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); + void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list); + void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + + jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jobject (*CallNonvirtualObjectMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jbyte (*CallNonvirtualByteMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jchar (*CallNonvirtualCharMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jshort (*CallNonvirtualShortMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jint (*CallNonvirtualIntMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jlong (*CallNonvirtualLongMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jfloat (*CallNonvirtualFloatMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + void (*CallNonvirtualVoidMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + + jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); + jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); + jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID); + jchar (*GetCharField)(JNIEnv*, jobject, jfieldID); + jshort (*GetShortField)(JNIEnv*, jobject, jfieldID); + jint (*GetIntField)(JNIEnv*, jobject, jfieldID); + jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); + jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID); + jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID); + + void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject); + void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); + void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte); + void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar); + void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort); + void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint); + void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); + void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat); + void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble); + + jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); + jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); + jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID, + va_list); + jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); + jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); + jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); + jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); + jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); + jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); + jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); + jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); + void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list); + void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + + jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*, + const char*); + + jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID); + jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID); + jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID); + jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID); + jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID); + jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID); + jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID); + jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID); + jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID); + + void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject); + void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean); + void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte); + void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar); + void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort); + void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint); + void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong); + void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat); + void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble); + + jstring (*NewString)(JNIEnv*, const jchar*, jsize); + jsize (*GetStringLength)(JNIEnv*, jstring); + const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*); + jstring (*NewStringUTF)(JNIEnv*, const char*); + jsize (*GetStringUTFLength)(JNIEnv*, jstring); + /* JNI spec says this returns const jbyte*, but that's inconsistent */ + const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringUTFChars)(JNIEnv*, jstring, char*); + jsize (*GetArrayLength)(JNIEnv*, jarray); + jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject); + jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize); + void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject); + + jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); + jbyteArray (*NewByteArray)(JNIEnv*, jsize); + jcharArray (*NewCharArray)(JNIEnv*, jsize); + jshortArray (*NewShortArray)(JNIEnv*, jsize); + jintArray (*NewIntArray)(JNIEnv*, jsize); + jlongArray (*NewLongArray)(JNIEnv*, jsize); + jfloatArray (*NewFloatArray)(JNIEnv*, jsize); + jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize); + + jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*); + jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*); + jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*); + jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*); + jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); + jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*); + jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*); + jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*); + + void (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray, + jboolean*, jint); + void (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray, + jbyte*, jint); + void (*ReleaseCharArrayElements)(JNIEnv*, jcharArray, + jchar*, jint); + void (*ReleaseShortArrayElements)(JNIEnv*, jshortArray, + jshort*, jint); + void (*ReleaseIntArrayElements)(JNIEnv*, jintArray, + jint*, jint); + void (*ReleaseLongArrayElements)(JNIEnv*, jlongArray, + jlong*, jint); + void (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray, + jfloat*, jint); + void (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray, + jdouble*, jint); + + void (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray, + jsize, jsize, jboolean*); + void (*GetByteArrayRegion)(JNIEnv*, jbyteArray, + jsize, jsize, jbyte*); + void (*GetCharArrayRegion)(JNIEnv*, jcharArray, + jsize, jsize, jchar*); + void (*GetShortArrayRegion)(JNIEnv*, jshortArray, + jsize, jsize, jshort*); + void (*GetIntArrayRegion)(JNIEnv*, jintArray, + jsize, jsize, jint*); + void (*GetLongArrayRegion)(JNIEnv*, jlongArray, + jsize, jsize, jlong*); + void (*GetFloatArrayRegion)(JNIEnv*, jfloatArray, + jsize, jsize, jfloat*); + void (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray, + jsize, jsize, jdouble*); + + /* spec shows these without const; some jni.h do, some don't */ + void (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray, + jsize, jsize, const jboolean*); + void (*SetByteArrayRegion)(JNIEnv*, jbyteArray, + jsize, jsize, const jbyte*); + void (*SetCharArrayRegion)(JNIEnv*, jcharArray, + jsize, jsize, const jchar*); + void (*SetShortArrayRegion)(JNIEnv*, jshortArray, + jsize, jsize, const jshort*); + void (*SetIntArrayRegion)(JNIEnv*, jintArray, + jsize, jsize, const jint*); + void (*SetLongArrayRegion)(JNIEnv*, jlongArray, + jsize, jsize, const jlong*); + void (*SetFloatArrayRegion)(JNIEnv*, jfloatArray, + jsize, jsize, const jfloat*); + void (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray, + jsize, jsize, const jdouble*); + + jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, + jint); + jint (*UnregisterNatives)(JNIEnv*, jclass); + jint (*MonitorEnter)(JNIEnv*, jobject); + jint (*MonitorExit)(JNIEnv*, jobject); + jint (*GetJavaVM)(JNIEnv*, JavaVM**); + + void (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*); + void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*); + + void* (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*); + void (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint); + + const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*); + + jweak (*NewWeakGlobalRef)(JNIEnv*, jobject); + void (*DeleteWeakGlobalRef)(JNIEnv*, jweak); + + jboolean (*ExceptionCheck)(JNIEnv*); + + jobject (*NewDirectByteBuffer)(JNIEnv*, void*, jlong); + void* (*GetDirectBufferAddress)(JNIEnv*, jobject); + jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject); + + /* added in JNI 1.6 */ + jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject); +}; + +/* + * C++ object wrapper. + * + * This is usually overlaid on a C struct whose first element is a + * JNINativeInterface*. We rely somewhat on compiler behavior. + */ +struct _JNIEnv { + /* do not rename this; it does not seem to be entirely opaque */ + const struct JNINativeInterface* functions; + +#if defined(__cplusplus) + + jint GetVersion() + { return functions->GetVersion(this); } + + jclass DefineClass(const char *name, jobject loader, const jbyte* buf, + jsize bufLen) + { return functions->DefineClass(this, name, loader, buf, bufLen); } + + jclass FindClass(const char* name) + { return functions->FindClass(this, name); } + + jmethodID FromReflectedMethod(jobject method) + { return functions->FromReflectedMethod(this, method); } + + jfieldID FromReflectedField(jobject field) + { return functions->FromReflectedField(this, field); } + + jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) + { return functions->ToReflectedMethod(this, cls, methodID, isStatic); } + + jclass GetSuperclass(jclass clazz) + { return functions->GetSuperclass(this, clazz); } + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2) + { return functions->IsAssignableFrom(this, clazz1, clazz2); } + + jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) + { return functions->ToReflectedField(this, cls, fieldID, isStatic); } + + jint Throw(jthrowable obj) + { return functions->Throw(this, obj); } + + jint ThrowNew(jclass clazz, const char* message) + { return functions->ThrowNew(this, clazz, message); } + + jthrowable ExceptionOccurred() + { return functions->ExceptionOccurred(this); } + + void ExceptionDescribe() + { functions->ExceptionDescribe(this); } + + void ExceptionClear() + { functions->ExceptionClear(this); } + + void FatalError(const char* msg) + { functions->FatalError(this, msg); } + + jint PushLocalFrame(jint capacity) + { return functions->PushLocalFrame(this, capacity); } + + jobject PopLocalFrame(jobject result) + { return functions->PopLocalFrame(this, result); } + + jobject NewGlobalRef(jobject obj) + { return functions->NewGlobalRef(this, obj); } + + void DeleteGlobalRef(jobject globalRef) + { functions->DeleteGlobalRef(this, globalRef); } + + void DeleteLocalRef(jobject localRef) + { functions->DeleteLocalRef(this, localRef); } + + jboolean IsSameObject(jobject ref1, jobject ref2) + { return functions->IsSameObject(this, ref1, ref2); } + + jobject NewLocalRef(jobject ref) + { return functions->NewLocalRef(this, ref); } + + jint EnsureLocalCapacity(jint capacity) + { return functions->EnsureLocalCapacity(this, capacity); } + + jobject AllocObject(jclass clazz) + { return functions->AllocObject(this, clazz); } + + jobject NewObject(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + jobject result = functions->NewObjectV(this, clazz, methodID, args); + va_end(args); + return result; + } + + jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) + { return functions->NewObjectV(this, clazz, methodID, args); } + + jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args) + { return functions->NewObjectA(this, clazz, methodID, args); } + + jclass GetObjectClass(jobject obj) + { return functions->GetObjectClass(this, obj); } + + jboolean IsInstanceOf(jobject obj, jclass clazz) + { return functions->IsInstanceOf(this, obj, clazz); } + + jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetMethodID(this, clazz, name, sig); } + +#define CALL_TYPE_METHOD(_jtype, _jname) \ + _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->Call##_jname##MethodV(this, obj, methodID, \ + args); \ + va_end(args); \ + return result; \ + } +#define CALL_TYPE_METHODV(_jtype, _jname) \ + _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID, \ + va_list args) \ + { return functions->Call##_jname##MethodV(this, obj, methodID, args); } +#define CALL_TYPE_METHODA(_jtype, _jname) \ + _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \ + const jvalue* args) \ + { return functions->Call##_jname##MethodA(this, obj, methodID, args); } + +#define CALL_TYPE(_jtype, _jname) \ + CALL_TYPE_METHOD(_jtype, _jname) \ + CALL_TYPE_METHODV(_jtype, _jname) \ + CALL_TYPE_METHODA(_jtype, _jname) + + CALL_TYPE(jobject, Object) + CALL_TYPE(jboolean, Boolean) + CALL_TYPE(jbyte, Byte) + CALL_TYPE(jchar, Char) + CALL_TYPE(jshort, Short) + CALL_TYPE(jint, Int) + CALL_TYPE(jlong, Long) + CALL_TYPE(jfloat, Float) + CALL_TYPE(jdouble, Double) + + void CallVoidMethod(jobject obj, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallVoidMethodV(this, obj, methodID, args); + va_end(args); + } + void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) + { functions->CallVoidMethodV(this, obj, methodID, args); } + void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue* args) + { functions->CallVoidMethodA(this, obj, methodID, args); } + +#define CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##Method(jobject obj, jclass clazz, \ + jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallNonvirtual##_jname##MethodV(this, obj, \ + clazz, methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodV(jobject obj, jclass clazz, \ + jmethodID methodID, va_list args) \ + { return functions->CallNonvirtual##_jname##MethodV(this, obj, clazz, \ + methodID, args); } +#define CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodA(jobject obj, jclass clazz, \ + jmethodID methodID, const jvalue* args) \ + { return functions->CallNonvirtual##_jname##MethodA(this, obj, clazz, \ + methodID, args); } + +#define CALL_NONVIRT_TYPE(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) + + CALL_NONVIRT_TYPE(jobject, Object) + CALL_NONVIRT_TYPE(jboolean, Boolean) + CALL_NONVIRT_TYPE(jbyte, Byte) + CALL_NONVIRT_TYPE(jchar, Char) + CALL_NONVIRT_TYPE(jshort, Short) + CALL_NONVIRT_TYPE(jint, Int) + CALL_NONVIRT_TYPE(jlong, Long) + CALL_NONVIRT_TYPE(jfloat, Float) + CALL_NONVIRT_TYPE(jdouble, Double) + + void CallNonvirtualVoidMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); + va_end(args); + } + void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) + { functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); } + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args) + { functions->CallNonvirtualVoidMethodA(this, obj, clazz, methodID, args); } + + jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetFieldID(this, clazz, name, sig); } + + jobject GetObjectField(jobject obj, jfieldID fieldID) + { return functions->GetObjectField(this, obj, fieldID); } + jboolean GetBooleanField(jobject obj, jfieldID fieldID) + { return functions->GetBooleanField(this, obj, fieldID); } + jbyte GetByteField(jobject obj, jfieldID fieldID) + { return functions->GetByteField(this, obj, fieldID); } + jchar GetCharField(jobject obj, jfieldID fieldID) + { return functions->GetCharField(this, obj, fieldID); } + jshort GetShortField(jobject obj, jfieldID fieldID) + { return functions->GetShortField(this, obj, fieldID); } + jint GetIntField(jobject obj, jfieldID fieldID) + { return functions->GetIntField(this, obj, fieldID); } + jlong GetLongField(jobject obj, jfieldID fieldID) + { return functions->GetLongField(this, obj, fieldID); } + jfloat GetFloatField(jobject obj, jfieldID fieldID) + { return functions->GetFloatField(this, obj, fieldID); } + jdouble GetDoubleField(jobject obj, jfieldID fieldID) + { return functions->GetDoubleField(this, obj, fieldID); } + + void SetObjectField(jobject obj, jfieldID fieldID, jobject value) + { functions->SetObjectField(this, obj, fieldID, value); } + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) + { functions->SetBooleanField(this, obj, fieldID, value); } + void SetByteField(jobject obj, jfieldID fieldID, jbyte value) + { functions->SetByteField(this, obj, fieldID, value); } + void SetCharField(jobject obj, jfieldID fieldID, jchar value) + { functions->SetCharField(this, obj, fieldID, value); } + void SetShortField(jobject obj, jfieldID fieldID, jshort value) + { functions->SetShortField(this, obj, fieldID, value); } + void SetIntField(jobject obj, jfieldID fieldID, jint value) + { functions->SetIntField(this, obj, fieldID, value); } + void SetLongField(jobject obj, jfieldID fieldID, jlong value) + { functions->SetLongField(this, obj, fieldID, value); } + void SetFloatField(jobject obj, jfieldID fieldID, jfloat value) + { functions->SetFloatField(this, obj, fieldID, value); } + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) + { functions->SetDoubleField(this, obj, fieldID, value); } + + jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticMethodID(this, clazz, name, sig); } + +#define CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, \ + ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallStatic##_jname##MethodV(this, clazz, \ + methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodV(jclass clazz, jmethodID methodID, \ + va_list args) \ + { return functions->CallStatic##_jname##MethodV(this, clazz, methodID, \ + args); } +#define CALL_STATIC_TYPE_METHODA(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodA(jclass clazz, jmethodID methodID, \ + const jvalue* args) \ + { return functions->CallStatic##_jname##MethodA(this, clazz, methodID, \ + args); } + +#define CALL_STATIC_TYPE(_jtype, _jname) \ + CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODA(_jtype, _jname) + + CALL_STATIC_TYPE(jobject, Object) + CALL_STATIC_TYPE(jboolean, Boolean) + CALL_STATIC_TYPE(jbyte, Byte) + CALL_STATIC_TYPE(jchar, Char) + CALL_STATIC_TYPE(jshort, Short) + CALL_STATIC_TYPE(jint, Int) + CALL_STATIC_TYPE(jlong, Long) + CALL_STATIC_TYPE(jfloat, Float) + CALL_STATIC_TYPE(jdouble, Double) + + __attribute__((no_stack_protector)) + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallStaticVoidMethodV(this, clazz, methodID, args); + va_end(args); + } + void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args) + { functions->CallStaticVoidMethodV(this, clazz, methodID, args); } + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue* args) + { functions->CallStaticVoidMethodA(this, clazz, methodID, args); } + + jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticFieldID(this, clazz, name, sig); } + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticObjectField(this, clazz, fieldID); } + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticBooleanField(this, clazz, fieldID); } + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticByteField(this, clazz, fieldID); } + jchar GetStaticCharField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticCharField(this, clazz, fieldID); } + jshort GetStaticShortField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticShortField(this, clazz, fieldID); } + jint GetStaticIntField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticIntField(this, clazz, fieldID); } + jlong GetStaticLongField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticLongField(this, clazz, fieldID); } + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticFloatField(this, clazz, fieldID); } + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticDoubleField(this, clazz, fieldID); } + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) + { functions->SetStaticObjectField(this, clazz, fieldID, value); } + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value) + { functions->SetStaticBooleanField(this, clazz, fieldID, value); } + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value) + { functions->SetStaticByteField(this, clazz, fieldID, value); } + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value) + { functions->SetStaticCharField(this, clazz, fieldID, value); } + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value) + { functions->SetStaticShortField(this, clazz, fieldID, value); } + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value) + { functions->SetStaticIntField(this, clazz, fieldID, value); } + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value) + { functions->SetStaticLongField(this, clazz, fieldID, value); } + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value) + { functions->SetStaticFloatField(this, clazz, fieldID, value); } + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value) + { functions->SetStaticDoubleField(this, clazz, fieldID, value); } + + jstring NewString(const jchar* unicodeChars, jsize len) + { return functions->NewString(this, unicodeChars, len); } + + jsize GetStringLength(jstring string) + { return functions->GetStringLength(this, string); } + + const jchar* GetStringChars(jstring string, jboolean* isCopy) + { return functions->GetStringChars(this, string, isCopy); } + + void ReleaseStringChars(jstring string, const jchar* chars) + { functions->ReleaseStringChars(this, string, chars); } + + jstring NewStringUTF(const char* bytes) + { return functions->NewStringUTF(this, bytes); } + + jsize GetStringUTFLength(jstring string) + { return functions->GetStringUTFLength(this, string); } + + const char* GetStringUTFChars(jstring string, jboolean* isCopy) + { return functions->GetStringUTFChars(this, string, isCopy); } + + void ReleaseStringUTFChars(jstring string, const char* utf) + { functions->ReleaseStringUTFChars(this, string, (char *) utf); } + + jsize GetArrayLength(jarray array) + { return functions->GetArrayLength(this, array); } + + jobjectArray NewObjectArray(jsize length, jclass elementClass, + jobject initialElement) + { return functions->NewObjectArray(this, length, elementClass, + initialElement); } + + jobject GetObjectArrayElement(jobjectArray array, jsize index) + { return functions->GetObjectArrayElement(this, array, index); } + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject value) + { functions->SetObjectArrayElement(this, array, index, value); } + + jbooleanArray NewBooleanArray(jsize length) + { return functions->NewBooleanArray(this, length); } + jbyteArray NewByteArray(jsize length) + { return functions->NewByteArray(this, length); } + jcharArray NewCharArray(jsize length) + { return functions->NewCharArray(this, length); } + jshortArray NewShortArray(jsize length) + { return functions->NewShortArray(this, length); } + jintArray NewIntArray(jsize length) + { return functions->NewIntArray(this, length); } + jlongArray NewLongArray(jsize length) + { return functions->NewLongArray(this, length); } + jfloatArray NewFloatArray(jsize length) + { return functions->NewFloatArray(this, length); } + jdoubleArray NewDoubleArray(jsize length) + { return functions->NewDoubleArray(this, length); } + + jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy) + { return functions->GetBooleanArrayElements(this, array, isCopy); } + jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy) + { return functions->GetByteArrayElements(this, array, isCopy); } + jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy) + { return functions->GetCharArrayElements(this, array, isCopy); } + jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy) + { return functions->GetShortArrayElements(this, array, isCopy); } + jint* GetIntArrayElements(jintArray array, jboolean* isCopy) + { return functions->GetIntArrayElements(this, array, isCopy); } + jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy) + { return functions->GetLongArrayElements(this, array, isCopy); } + jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy) + { return functions->GetFloatArrayElements(this, array, isCopy); } + jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy) + { return functions->GetDoubleArrayElements(this, array, isCopy); } + + void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, + jint mode) + { functions->ReleaseBooleanArrayElements(this, array, elems, mode); } + void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, + jint mode) + { functions->ReleaseByteArrayElements(this, array, elems, mode); } + void ReleaseCharArrayElements(jcharArray array, jchar* elems, + jint mode) + { functions->ReleaseCharArrayElements(this, array, elems, mode); } + void ReleaseShortArrayElements(jshortArray array, jshort* elems, + jint mode) + { functions->ReleaseShortArrayElements(this, array, elems, mode); } + void ReleaseIntArrayElements(jintArray array, jint* elems, + jint mode) + { functions->ReleaseIntArrayElements(this, array, elems, mode); } + void ReleaseLongArrayElements(jlongArray array, jlong* elems, + jint mode) + { functions->ReleaseLongArrayElements(this, array, elems, mode); } + void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, + jint mode) + { functions->ReleaseFloatArrayElements(this, array, elems, mode); } + void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, + jint mode) + { functions->ReleaseDoubleArrayElements(this, array, elems, mode); } + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + jboolean* buf) + { functions->GetBooleanArrayRegion(this, array, start, len, buf); } + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, + jbyte* buf) + { functions->GetByteArrayRegion(this, array, start, len, buf); } + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, + jchar* buf) + { functions->GetCharArrayRegion(this, array, start, len, buf); } + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, + jshort* buf) + { functions->GetShortArrayRegion(this, array, start, len, buf); } + void GetIntArrayRegion(jintArray array, jsize start, jsize len, + jint* buf) + { functions->GetIntArrayRegion(this, array, start, len, buf); } + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, + jlong* buf) + { functions->GetLongArrayRegion(this, array, start, len, buf); } + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + jfloat* buf) + { functions->GetFloatArrayRegion(this, array, start, len, buf); } + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + jdouble* buf) + { functions->GetDoubleArrayRegion(this, array, start, len, buf); } + + void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + const jboolean* buf) + { functions->SetBooleanArrayRegion(this, array, start, len, buf); } + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, + const jbyte* buf) + { functions->SetByteArrayRegion(this, array, start, len, buf); } + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, + const jchar* buf) + { functions->SetCharArrayRegion(this, array, start, len, buf); } + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, + const jshort* buf) + { functions->SetShortArrayRegion(this, array, start, len, buf); } + void SetIntArrayRegion(jintArray array, jsize start, jsize len, + const jint* buf) + { functions->SetIntArrayRegion(this, array, start, len, buf); } + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, + const jlong* buf) + { functions->SetLongArrayRegion(this, array, start, len, buf); } + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + const jfloat* buf) + { functions->SetFloatArrayRegion(this, array, start, len, buf); } + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + const jdouble* buf) + { functions->SetDoubleArrayRegion(this, array, start, len, buf); } + + jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, + jint nMethods) + { return functions->RegisterNatives(this, clazz, methods, nMethods); } + + jint UnregisterNatives(jclass clazz) + { return functions->UnregisterNatives(this, clazz); } + + jint MonitorEnter(jobject obj) + { return functions->MonitorEnter(this, obj); } + + jint MonitorExit(jobject obj) + { return functions->MonitorExit(this, obj); } + + jint GetJavaVM(JavaVM** vm) + { return functions->GetJavaVM(this, vm); } + + void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf) + { functions->GetStringRegion(this, str, start, len, buf); } + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf) + { return functions->GetStringUTFRegion(this, str, start, len, buf); } + + void* GetPrimitiveArrayCritical(jarray array, jboolean* isCopy) + { return functions->GetPrimitiveArrayCritical(this, array, isCopy); } + + void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode) + { functions->ReleasePrimitiveArrayCritical(this, array, carray, mode); } + + const jchar* GetStringCritical(jstring string, jboolean* isCopy) + { return functions->GetStringCritical(this, string, isCopy); } + + void ReleaseStringCritical(jstring string, const jchar* carray) + { functions->ReleaseStringCritical(this, string, carray); } + + jweak NewWeakGlobalRef(jobject obj) + { return functions->NewWeakGlobalRef(this, obj); } + + void DeleteWeakGlobalRef(jweak obj) + { functions->DeleteWeakGlobalRef(this, obj); } + + jboolean ExceptionCheck() + { return functions->ExceptionCheck(this); } + + jobject NewDirectByteBuffer(void* address, jlong capacity) + { return functions->NewDirectByteBuffer(this, address, capacity); } + + void* GetDirectBufferAddress(jobject buf) + { return functions->GetDirectBufferAddress(this, buf); } + + jlong GetDirectBufferCapacity(jobject buf) + { return functions->GetDirectBufferCapacity(this, buf); } + + /* added in JNI 1.6 */ + jobjectRefType GetObjectRefType(jobject obj) + { return functions->GetObjectRefType(this, obj); } +#endif /*__cplusplus*/ +}; + + +/* + * JNI invocation interface. + */ +struct JNIInvokeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + + jint (*DestroyJavaVM)(JavaVM*); + jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); + jint (*DetachCurrentThread)(JavaVM*); + jint (*GetEnv)(JavaVM*, void**, jint); + jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); +}; + +/* + * C++ version. + */ +struct _JavaVM { + const struct JNIInvokeInterface* functions; + +#if defined(__cplusplus) + jint DestroyJavaVM() + { return functions->DestroyJavaVM(this); } + jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThread(this, p_env, thr_args); } + jint DetachCurrentThread() + { return functions->DetachCurrentThread(this); } + jint GetEnv(void** env, jint version) + { return functions->GetEnv(this, env, version); } + jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); } +#endif /*__cplusplus*/ +}; + +struct JavaVMAttachArgs { + jint version; /* must be >= JNI_VERSION_1_2 */ + const char* name; /* NULL or name of thread as modified UTF-8 str */ + jobject group; /* global ref of a ThreadGroup object, or NULL */ +}; +typedef struct JavaVMAttachArgs JavaVMAttachArgs; + +/* + * JNI 1.2+ initialization. (As of 1.6, the pre-1.2 structures are no + * longer supported.) + */ +typedef struct JavaVMOption { + const char* optionString; + void* extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; /* use JNI_VERSION_1_2 or later */ + + jint nOptions; + JavaVMOption* options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +#ifdef __cplusplus +extern "C" { +#endif +/* + * VM initialization functions. + * + * Note these are the only symbols exported for JNI by the VM. + */ +jint JNI_GetDefaultJavaVMInitArgs(void*); +jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*); +jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*); + +#define JNIIMPORT +#define JNIEXPORT __attribute__ ((visibility ("default"))) +#define JNICALL + +/* + * Prototypes for functions exported by loadable shared libs. These are + * called by JNI, not provided by JNI. + */ +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved); +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved); + +#ifdef __cplusplus +} +#endif + + +/* + * Manifest constants. + */ +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 + +#define JNI_OK (0) /* no error */ +#define JNI_ERR (-1) /* generic error */ +#define JNI_EDETACHED (-2) /* thread detached from the VM */ +#define JNI_EVERSION (-3) /* JNI version error */ +#define JNI_ENOMEM (-4) /* Out of memory */ +#define JNI_EEXIST (-5) /* VM already created */ +#define JNI_EINVAL (-6) /* Invalid argument */ + +#define JNI_COMMIT 1 /* copy content, do not free buffer */ +#define JNI_ABORT 2 /* free buffer w/o copying back */ diff --git a/src/android/jni_impl.c b/src/android/jni_impl.c new file mode 100644 index 0000000..656cd65 --- /dev/null +++ b/src/android/jni_impl.c @@ -0,0 +1,54 @@ +// FalsoJNI implementation tables for cliffi. +// +// FalsoJNI dispatches JNI method/field calls (env->CallVoidMethodV, etc.) +// through these tables. The default empty tables mean any Java method the +// native library calls back into will hit a logged stub that returns null/zero. +// +// For deeper RE work where a library makes specific Java callbacks you care +// about, populate these tables following the FalsoJNI README pattern. Example: +// +// static void my_Log_i(jmethodID id, va_list args) { +// jstring tag = va_arg(args, jstring); +// jstring msg = va_arg(args, jstring); +// printf("[Java/Log.i] %s: %s\n", (char*)tag, (char*)msg); +// } +// +// NameToMethodID nameToMethodId[] = { +// { 1, "i", METHOD_TYPE_VOID }, +// }; +// MethodsVoid methodsVoid[] = { +// { 1, my_Log_i }, +// }; + +#ifdef __ANDROID__ + +#include "FalsoJNI_Impl.h" + +NameToMethodID nameToMethodId[1] = { { 0, "", METHOD_TYPE_UNKNOWN } }; + +MethodsBoolean methodsBoolean[1] = { { 0, NULL } }; +MethodsByte methodsByte[1] = { { 0, NULL } }; +MethodsChar methodsChar[1] = { { 0, NULL } }; +MethodsDouble methodsDouble[1] = { { 0, NULL } }; +MethodsFloat methodsFloat[1] = { { 0, NULL } }; +MethodsInt methodsInt[1] = { { 0, NULL } }; +MethodsLong methodsLong[1] = { { 0, NULL } }; +MethodsObject methodsObject[1] = { { 0, NULL } }; +MethodsShort methodsShort[1] = { { 0, NULL } }; +MethodsVoid methodsVoid[1] = { { 0, NULL } }; + +NameToFieldID nameToFieldId[1] = { { 0, "", FIELD_TYPE_UNKNOWN } }; + +FieldsBoolean fieldsBoolean[1] = { { 0, 0 } }; +FieldsByte fieldsByte[1] = { { 0, 0 } }; +FieldsChar fieldsChar[1] = { { 0, 0 } }; +FieldsDouble fieldsDouble[1] = { { 0, 0 } }; +FieldsFloat fieldsFloat[1] = { { 0, 0 } }; +FieldsInt fieldsInt[1] = { { 0, 0 } }; +FieldsObject fieldsObject[1] = { { 0, 0 } }; +FieldsLong fieldsLong[1] = { { 0, 0 } }; +FieldsShort fieldsShort[1] = { { 0, 0 } }; + +__FALSOJNI_IMPL_CONTAINER_SIZES + +#endif // __ANDROID__ diff --git a/src/android/jni_support.c b/src/android/jni_support.c new file mode 100644 index 0000000..959fa09 --- /dev/null +++ b/src/android/jni_support.c @@ -0,0 +1,278 @@ +// Android JNI support for cliffi. +// +// Two initialization paths are provided: +// +// FalsoJNI (default): A fake JNI environment (github.com/v-atamanenko/FalsoJNI) +// that stubs the full JNIEnv/JavaVM vtable. Works on every Android version +// without any ART dependency. Sufficient for loading a library, triggering +// JNI_OnLoad so native methods are registered, then calling those methods +// via cliffi's normal FFI. Java callbacks from the native side will hit +// logged stubs — good for shallow RE or libs that are mostly self-contained. +// +// Real ART JVM (--real flag): Bootstraps a genuine ART/Dalvik runtime by +// dlopening libart.so/libdvm.so and calling JNI_CreateJavaVM. The native +// lib gets a live JNIEnv and can round-trip into real Java classes. This +// works reliably only on Android < 8 (or custom/rooted devices); Google +// progressively restricted JNI_CreateJavaVM in ART from API 24 onward. + +#ifdef __ANDROID__ + +#include "jni_support.h" + +#include +#include +#include +#include +#include +#include + +// FalsoJNI headers — found via target_include_directories(... src/android/FalsoJNI) +#include "FalsoJNI.h" +#include "FalsoJNI_Impl.h" + +// cliffi headers — found via target_include_directories(... src/) +#include "exception_handling.h" +#include "library_manager.h" +#include "parse_address.h" +#include "tokenize.h" +#include "var_map.h" + +// ── FalsoJNI global state ──────────────────────────────────────────────────── + +static bool g_fake_jvm_initialized = false; + +// When true, jni_notify_library_loaded auto-calls JNI_OnLoad via FalsoJNI. +// Default is false: only a reminder is printed and the user calls initjni. +static bool g_auto_jni_onload = false; + +// Set true while parseInitJNI is active so jni_notify_library_loaded does +// not fire during the controlled load (prevents double JNI_OnLoad call even +// when g_auto_jni_onload is on). +static bool g_suppress_jni_notify = false; + +static void ensure_fake_jvm(void) { + if (!g_fake_jvm_initialized) { + jni_init(); + g_fake_jvm_initialized = true; + } +} + +// ── Real ART/Dalvik bootstrap ──────────────────────────────────────────────── + +#if __aarch64__ +#define APEX_LIBART "/apex/com.android.runtime/lib64/libart.so" +#else +#define APEX_LIBART "/apex/com.android.runtime/lib/libart.so" +#endif + +typedef int (*JNI_CreateJavaVM_t)(JavaVM**, JNIEnv**, void*); +typedef int (*registerNatives_t)(JNIEnv*, jclass); + +// Returns 0 on success. Negative values indicate which step failed. +// Error -1: libart/libdvm not found +// Error -2: JNI_CreateJavaVM symbol not found +// Error -3: libandroid_runtime.so or registerNatives not found +// Error -4: JNI_CreateJavaVM call failed +// Error -5: registerNatives call failed +static int init_real_jvm(JavaVM** p_vm, JNIEnv** p_env, + int opt_count, char** opts) { + // Build JVM options + JavaVMOption* options = NULL; + if (opt_count > 0) { + options = calloc(opt_count, sizeof(JavaVMOption)); + for (int i = 0; i < opt_count; i++) + options[i].optionString = opts[i]; + } + JavaVMInitArgs args = { + .version = JNI_VERSION_1_6, + .options = options, + .nOptions = opt_count, + .ignoreUnrecognized = JNI_FALSE, + }; + + // Try APEX path (Android 10+) then generic path then old Dalvik + void* libart = dlopen(APEX_LIBART, RTLD_NOW); + if (!libart) libart = dlopen("libart.so", RTLD_NOW); + if (!libart) libart = dlopen("libdvm.so", RTLD_NOW); + + if (!libart) { free(options); return -1; } + + JNI_CreateJavaVM_t createVM = + (JNI_CreateJavaVM_t)dlsym(libart, "JNI_CreateJavaVM"); + if (!createVM) { free(options); return -2; } + + void* libruntime = dlopen("libandroid_runtime.so", RTLD_NOW); + if (!libruntime) { free(options); return -3; } + + // Symbol name changed across Android versions + registerNatives_t regNatives = + (registerNatives_t)dlsym(libruntime, + "Java_com_android_internal_util_WithFramework_registerNatives"); + if (!regNatives) + regNatives = (registerNatives_t)dlsym(libruntime, + "registerFrameworkNatives"); + if (!regNatives) { free(options); return -3; } + + if (createVM(p_vm, p_env, &args)) { free(options); return -4; } + if (regNatives(*p_env, 0)) { free(options); return -5; } + + free(options); + return 0; +} + +// ── Signal chain stubs ─────────────────────────────────────────────────────── +// libandroid_runtime.so dlsym-lookups these symbols on the calling binary via +// RTLD_DEFAULT. They must be exported (enforced by -Wl,--export-dynamic in +// CMakeLists). Empty stubs are fine for cliffi's use case. + +typedef bool (*SpecialSignalHandlerFn)(int, siginfo_t*, void*); + +__attribute__((visibility("default"))) void InitializeSignalChain(void) {} +__attribute__((visibility("default"))) void ClaimSignalChain(void) {} +__attribute__((visibility("default"))) void UnclaimSignalChain(void) {} +__attribute__((visibility("default"))) void InvokeUserSignalHandler(void) {} +__attribute__((visibility("default"))) void EnsureFrontOfChain(void) {} +__attribute__((visibility("default"))) void AddSpecialSignalHandlerFn(void){} +__attribute__((visibility("default"))) void RemoveSpecialSignalHandlerFn(void){} + +// ── Library-load hook (called by library_manager) ──────────────────────────── + +typedef int (*JNI_OnLoadFunc)(void* vm, void* reserved); + +void jni_notify_library_loaded(void* lib_handle, const char* lib_path) { + if (g_suppress_jni_notify) return; + + JNI_OnLoadFunc onLoad = + (JNI_OnLoadFunc)dlsym(lib_handle, "JNI_OnLoad"); + if (!onLoad) return; + + if (g_auto_jni_onload) { + bool first_init = !g_fake_jvm_initialized; + ensure_fake_jvm(); + if (first_init) + printf("[jni] %s exports JNI_OnLoad — auto-initialized FalsoJNI\n", + lib_path); + printf("[jni] Calling JNI_OnLoad for %s\n", lib_path); + onLoad(&jvm, NULL); + printf("[jni] JNI_OnLoad complete. Native methods registered.\n" + "[jni] vm = %p (use: set vm %p)\n" + "[jni] env = %p (use: set env %p)\n" + "[jni] Or use 'initjni %s [--real]' " + "to store them in named variables.\n", + (void*)&jvm, (void*)&jvm, + (void*)&jni, (void*)&jni, + lib_path); + } else { + printf("[jni] %s exports JNI_OnLoad — native methods not yet registered.\n" + "[jni] To initialize: initjni %s \n" + "[jni] To auto-call on every library load: autojni on\n", + lib_path, lib_path); + } +} + +// ── REPL command: autojni ──────────────────────────────────────────────────── + +void parseAutoJNI(char* args) { + while (*args == ' ') args++; + if (*args == '\0') { + printf("[jni] autojni is %s\n", g_auto_jni_onload ? "on" : "off"); + } else if (strcmp(args, "on") == 0 || strcmp(args, "1") == 0) { + g_auto_jni_onload = true; + printf("[jni] Auto JNI_OnLoad on library load: enabled\n"); + } else if (strcmp(args, "off") == 0 || strcmp(args, "0") == 0) { + g_auto_jni_onload = false; + printf("[jni] Auto JNI_OnLoad on library load: disabled\n"); + } else { + raiseException(1, "Error: autojni expects 'on' or 'off'\n"); + } +} + +// ── REPL command: initjni ──────────────────────────────────────────────────── + +void parseInitJNI(char* args) { + int argc; + char** argv; + tokenize(args, &argc, &argv); + + // initjni [--real [jvm_opt ...]] + if (argc < 3) { + raiseException(1, "Error: initjni requires: " + " [--real [jvm_opts...]]\n"); + return; + } + + char* libraryName = argv[0]; + char* vmStr = argv[1]; + char* envStr = argv[2]; + + bool use_real = false; + int jvm_opts_start = 3; + if (argc > 3 && strcmp(argv[3], "--real") == 0) { + use_real = true; + jvm_opts_start = 4; + } + + // Suppress the notify hook so jni_notify_library_loaded does not fire + // during this controlled load — initjni owns calling JNI_OnLoad itself. + g_suppress_jni_notify = true; + void* lib_handle = getOrLoadLibrary(libraryName); + g_suppress_jni_notify = false; + if (!lib_handle) { + raiseException(1, "Error: Could not load library: %s\n", libraryName); + return; + } + + JNI_OnLoadFunc onLoad = + (JNI_OnLoadFunc)dlsym(lib_handle, "JNI_OnLoad"); + if (!onLoad) { + printf("[jni] Warning: %s has no JNI_OnLoad\n", libraryName); + } + + JavaVM* vm = NULL; + JNIEnv* env = NULL; + + if (use_real) { + printf("[jni] Attempting real ART/Dalvik JVM bootstrap...\n"); + int jvm_opt_count = argc - jvm_opts_start; + int status = init_real_jvm(&vm, &env, + jvm_opt_count, argv + jvm_opts_start); + if (status != 0) { + const char* why; + switch (status) { + case -1: why = "libart.so/libdvm.so not found"; break; + case -2: why = "JNI_CreateJavaVM symbol not exported " + "(likely Android 8+ restriction)"; break; + case -3: why = "libandroid_runtime.so or registerNatives " + "not found"; break; + case -4: why = "JNI_CreateJavaVM() returned error"; break; + case -5: why = "registerNatives() returned error"; break; + default: why = "unknown"; + } + printf("[jni] Real JVM failed (%d: %s). " + "Falling back to FalsoJNI.\n", status, why); + use_real = false; + } else { + printf("[jni] Real JVM ready (vm=%p env=%p)\n", (void*)vm, (void*)env); + } + } + + if (!use_real) { + ensure_fake_jvm(); + vm = &jvm; + env = &jni; + printf("[jni] FalsoJNI ready (vm=%p env=%p)\n", (void*)vm, (void*)env); + } + + if (onLoad) { + printf("[jni] Calling JNI_OnLoad for %s\n", libraryName); + onLoad(vm, NULL); + printf("[jni] JNI_OnLoad complete. Native methods registered.\n"); + } + + // JavaVM and JNIEnv are both pointer typedefs (pointer-to-interface-struct). + // vm/env here are JavaVM*/JNIEnv* — store those pointer values directly. + setVar(vmStr, getPVar((void*)vm)); + setVar(envStr, getPVar((void*)env)); +} + +#endif // __ANDROID__ diff --git a/src/android/jni_support.h b/src/android/jni_support.h new file mode 100644 index 0000000..7cfab50 --- /dev/null +++ b/src/android/jni_support.h @@ -0,0 +1,25 @@ +#ifndef JNI_SUPPORT_H +#define JNI_SUPPORT_H + +#ifdef __ANDROID__ + +// Called by library_manager after every successful dlopen on Android. +// If the library exports JNI_OnLoad, either calls it automatically (when +// autojni is on) or prints a reminder showing the initjni invocation. +void jni_notify_library_loaded(void* lib_handle, const char* lib_path); + +// REPL command handler: initjni [--real [jvm_opts...]] +// Default (no flag): FalsoJNI fake environment — works on any Android version. +// --real : ART/Dalvik bootstrap via JNI_CreateJavaVM; needs a JVM- +// capable device (generally Android < 8 or rooted). Lets +// the native lib actually call back into real Java classes. +void parseInitJNI(char* args); + +// REPL command handler: autojni [on|off] +// on — future library loads auto-call JNI_OnLoad via FalsoJNI +// off — only print a reminder (default) +// (no arg) — print current setting +void parseAutoJNI(char* args); + +#endif // __ANDROID__ +#endif // JNI_SUPPORT_H diff --git a/src/library_manager.c b/src/library_manager.c index 39d968c..ed84f13 100644 --- a/src/library_manager.c +++ b/src/library_manager.c @@ -11,6 +11,10 @@ #include #endif +#ifdef __ANDROID__ +#include "android/jni_support.h" +#endif + typedef struct { char* libraryPath; void* handle; @@ -44,6 +48,9 @@ void* loadLibraryDirectly(const char* libraryPath) { fprintf(stderr, "Failed to load library: %s\n", dlerror()); return NULL; } +#endif +#ifdef __ANDROID__ + jni_notify_library_loaded(handle, libraryPath); #endif return handle; } diff --git a/src/main.c b/src/main.c index 833b2b8..870a377 100644 --- a/src/main.c +++ b/src/main.c @@ -35,6 +35,10 @@ #include "shims.h" +#ifdef __ANDROID__ +#include "android/jni_support.h" +#endif + const char* NAME = "cliffi"; const char* VERSION = "v1.12.6"; const char* BASIC_USAGE_STRING = " [[-typeflag] .. [ ... ..] ]\n"; @@ -569,6 +573,18 @@ int parseREPLCommand(char* command){ "Shell commands:\n" " !: Run a shell command\n" " shell: Drop into an interactive shell\n" +#ifdef __ANDROID__ + "JNI (Android):\n" + " initjni [--real [jvm_opts...]]:\n" + " Initialize JNI for a library: call JNI_OnLoad and store JavaVM*/JNIEnv*.\n" + " Default: FalsoJNI stub environment (works on any Android version).\n" + " --real: live ART JVM via JNI_CreateJavaVM (Android < 8 / rooted);\n" + " allows the lib to call back into real Java classes.\n" + " autojni [on|off]:\n" + " When on, JNI_OnLoad is called automatically via FalsoJNI whenever\n" + " a library that exports it is loaded. Default: off (prints a reminder\n" + " and lets you choose when and how to initialize via initjni).\n" +#endif "REPL Management:\n" " exit: Quit the REPL\n"); } else if (strcmp(command, "docs") == 0) { @@ -599,6 +615,13 @@ int parseREPLCommand(char* command){ parseCalculateOffset(command + 17); } else if (strncmp(command, "hexdump ", 8) == 0) { parseHexdump(command + 8); // could also be done by dump aC
+#ifdef __ANDROID__ + } else if (strncmp(command, "initjni ", 8) == 0) { + parseInitJNI(command + 8); + } else if (strncmp(command, "autojni", 7) == 0 && + (command[7] == '\0' || command[7] == ' ')) { + parseAutoJNI(command + 7); +#endif } else if (command[0] == '!') { system(command + 1); // could also be done via libc.so v system "" but this is more direct and convenient } else if (strcmp(command, "shell") == 0) {