diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d592505..fed4df4 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -2,10 +2,10 @@ name: Android CI on: push: - branches: main + branches: inject paths-ignore: '**.md' pull_request: - branches: main + branches: inject paths-ignore: '**.md' jobs: diff --git a/.gitmodules b/.gitmodules index 92949f3..e29d781 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "app/src/main/cpp/Dobby"] - path = app/src/main/cpp/Dobby +[submodule "inject/src/main/cpp/Dobby"] + path = inject/src/main/cpp/Dobby url = https://github.com/JingMatrix/Dobby.git diff --git a/app/build.gradle.kts b/app/build.gradle.kts deleted file mode 100644 index 2f555e0..0000000 --- a/app/build.gradle.kts +++ /dev/null @@ -1,134 +0,0 @@ -plugins { - alias(libs.plugins.android.application) -} - -android { - namespace = "es.chiteroman.playintegrityfix" - compileSdk = 35 - - buildFeatures { - prefab = true - } - - packaging { - jniLibs { - excludes += "**/libdobby.so" - } - resources { - excludes += "**" - } - } - - defaultConfig { - applicationId = "es.chiteroman.playintegrityfix" - minSdk = 26 - targetSdk = 35 - versionCode = 18900 - versionName = "v18.9" - multiDexEnabled = false - - externalNativeBuild { - cmake { - abiFilters( - "arm64-v8a", - "armeabi-v7a" - ) - - arguments( - "-DCMAKE_BUILD_TYPE=MinSizeRel", - "-DANDROID_STL=none" - ) - - cFlags( - "-std=c23", - "-fvisibility=hidden", - "-fvisibility-inlines-hidden" - ) - - cppFlags( - "-std=c++26", - "-fno-exceptions", - "-fno-rtti", - "-fvisibility=hidden", - "-fvisibility-inlines-hidden" - ) - } - } - } - - buildTypes { - release { - isMinifyEnabled = true - isShrinkResources = true - multiDexEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" - ) - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - } - - externalNativeBuild { - cmake { - path = file("src/main/cpp/CMakeLists.txt") - } - } -} - -dependencies { - implementation(libs.cxx) - implementation(libs.hiddenapibypass) -} - -tasks.register("updateModuleProp") { - doLast { - val versionName = project.android.defaultConfig.versionName - val versionCode = project.android.defaultConfig.versionCode - - val modulePropFile = project.rootDir.resolve("module/module.prop") - - var content = modulePropFile.readText() - - content = content.replace(Regex("version=.*"), "version=$versionName") - content = content.replace(Regex("versionCode=.*"), "versionCode=$versionCode") - - modulePropFile.writeText(content) - } -} - -tasks.register("copyFiles") { - dependsOn("updateModuleProp") - - doLast { - val moduleFolder = project.rootDir.resolve("module") - val dexFile = - project.layout.buildDirectory.get().asFile.resolve("intermediates/dex/release/minifyReleaseWithR8/classes.dex") - val soDir = - project.layout.buildDirectory.get().asFile.resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib") - - dexFile.copyTo(moduleFolder.resolve("classes.dex"), overwrite = true) - - soDir.walk().filter { it.isFile && it.extension == "so" }.forEach { soFile -> - val abiFolder = soFile.parentFile.name - val destination = moduleFolder.resolve("zygisk/$abiFolder.so") - soFile.copyTo(destination, overwrite = true) - } - } -} - -tasks.register("zip") { - dependsOn("copyFiles") - - archiveFileName.set("PlayIntegrityFix_${project.android.defaultConfig.versionName}.zip") - destinationDirectory.set(project.rootDir.resolve("out")) - - from(project.rootDir.resolve("module")) -} - -afterEvaluate { - tasks["assembleRelease"].finalizedBy("updateModuleProp", "copyFiles", "zip") -} diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp deleted file mode 100644 index 52b8642..0000000 --- a/app/src/main/cpp/main.cpp +++ /dev/null @@ -1,483 +0,0 @@ -#include -#include -#include -#include "zygisk.hpp" -#include "dobby.h" -#include "json.hpp" - -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) - -#define DEX_PATH "/data/adb/modules/playintegrityfix/classes.dex" - -#define TS_PATH "/data/adb/modules/tricky_store" - -#define DEFAULT_JSON "/data/adb/modules/playintegrityfix/pif.json" -#define CUSTOM_JSON_FORK "/data/adb/modules/playintegrityfix/custom.pif.json" -#define CUSTOM_JSON "/data/adb/pif.json" - -static ssize_t xread(int fd, void *buffer, size_t count) { - ssize_t total = 0; - char *buf = static_cast(buffer); - while (count > 0) { - ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, count)); - if (ret < 0) return -1; - buf += ret; - total += ret; - count -= ret; - } - return total; -} - -static ssize_t xwrite(int fd, const void *buffer, size_t count) { - ssize_t total = 0; - char *buf = (char *) buffer; - while (count > 0) { - ssize_t ret = TEMP_FAILURE_RETRY(write(fd, buf, count)); - if (ret < 0) return -1; - buf += ret; - total += ret; - count -= ret; - } - return total; -} - -static bool DEBUG = false; -static std::string DEVICE_INITIAL_SDK_INT = "21", SECURITY_PATCH, BUILD_ID; - -typedef void (*T_Callback)(void *, const char *, const char *, uint32_t); - -static T_Callback o_callback = nullptr; - -static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) { - - if (!cookie || !name || !value || !o_callback) return; - - const char *oldValue = value; - - std::string_view prop(name); - - if (prop == "init.svc.adbd") { - value = "stopped"; - } else if (prop == "sys.usb.state") { - value = "mtp"; - } else if (prop.ends_with("api_level")) { - if (!DEVICE_INITIAL_SDK_INT.empty()) { - value = DEVICE_INITIAL_SDK_INT.c_str(); - } - } else if (prop.ends_with(".security_patch")) { - if (!SECURITY_PATCH.empty()) { - value = SECURITY_PATCH.c_str(); - } - } else if (prop.ends_with(".build.id")) { - if (!BUILD_ID.empty()) { - value = BUILD_ID.c_str(); - } - } - - if (strcmp(oldValue, value) == 0) { - if (DEBUG) LOGD("[%s]: %s (unchanged)", name, oldValue); - } else { - LOGD("[%s]: %s -> %s", name, oldValue, value); - } - - return o_callback(cookie, name, value, serial); -} - -static void (*o_system_property_read_callback)(prop_info *, T_Callback, void *) = nullptr; - -static void my_system_property_read_callback(prop_info *pi, T_Callback callback, void *cookie) { - if (pi && callback && cookie) o_callback = callback; - return o_system_property_read_callback(pi, modify_callback, cookie); -} - -static bool doHook() { - void *ptr = DobbySymbolResolver(nullptr, "__system_property_read_callback"); - - if (ptr && DobbyHook(ptr, (void *) my_system_property_read_callback, - (void **) &o_system_property_read_callback) == 0) { - LOGD("hook __system_property_read_callback successful at %p", ptr); - return true; - } - - LOGE("hook __system_property_read_callback failed!"); - return false; -} - -class PlayIntegrityFix : public zygisk::ModuleBase { -public: - void onLoad(zygisk::Api *_api, JNIEnv *_env) override { - this->api = _api; - this->env = _env; - } - - void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { - const char *rawDir = env->GetStringUTFChars(args->app_data_dir, nullptr); - const char *rawName = env->GetStringUTFChars(args->nice_name, nullptr); - - std::string dir, name; - - if (rawDir) { - dir = rawDir; - env->ReleaseStringUTFChars(args->app_data_dir, rawDir); - } - - if (rawName) { - name = rawName; - env->ReleaseStringUTFChars(args->nice_name, rawName); - } - - bool isGms = dir.ends_with("/com.google.android.gms"); - bool isGmsUnstable = name == "com.google.android.gms.unstable"; - - if (!isGms) { - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - return; - } - - api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT); - - if (!isGmsUnstable) { - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - return; - } - - int fd = api->connectCompanion(); - - size_t dexSize = 0, jsonSize = 0; - std::string jsonStr; - - xread(fd, &dexSize, sizeof(size_t)); - xread(fd, &jsonSize, sizeof(size_t)); - - if (dexSize > 0) { - dexVector.resize(dexSize); - xread(fd, dexVector.data(), dexSize); - } - - if (jsonSize > 0) { - jsonStr.resize(jsonSize); - jsonSize = xread(fd, jsonStr.data(), jsonSize); - jsonStr[jsonSize] = '\0'; - json = nlohmann::json::parse(jsonStr, nullptr, false, true); - } - - bool trickyStore = false; - xread(fd, &trickyStore, sizeof(bool)); - - bool testSignedRom = false; - xread(fd, &testSignedRom, sizeof(bool)); - - close(fd); - - LOGD("Dex file size: %zu", dexSize); - LOGD("Json file size: %zu", jsonSize); - - parseJSON(); - - if (trickyStore) { - LOGD("TrickyStore module detected!"); - spoofProvider = false; - spoofProps = false; - } - - if (testSignedRom) { - LOGD("--- ROM IS SIGNED WITH TEST KEYS ---"); - spoofSignature = true; - } - } - - void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override { - if (dexVector.empty() || json.empty()) return; - - UpdateBuildFields(); - - if (spoofProvider || spoofSignature) { - injectDex(); - } else { - LOGD("Dex file won't be injected due spoofProvider and spoofSignature are false"); - } - - if (spoofProps) { - if (!doHook()) { - dlclose(); - } - } else { - dlclose(); - } - - json.clear(); - - dexVector.clear(); - dexVector.shrink_to_fit(); - } - - void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override { - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - } - -private: - zygisk::Api *api = nullptr; - JNIEnv *env = nullptr; - std::vector dexVector; - nlohmann::json json; - bool spoofProps = true; - bool spoofProvider = true; - bool spoofSignature = false; - - void dlclose() { - LOGD("dlclose zygisk lib"); - api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); - } - - void parseJSON() { - if (json.empty()) return; - - if (json.contains("DEVICE_INITIAL_SDK_INT")) { - if (json["DEVICE_INITIAL_SDK_INT"].is_string()) { - DEVICE_INITIAL_SDK_INT = json["DEVICE_INITIAL_SDK_INT"].get(); - } else if (json["DEVICE_INITIAL_SDK_INT"].is_number_integer()) { - DEVICE_INITIAL_SDK_INT = std::to_string(json["DEVICE_INITIAL_SDK_INT"].get()); - } else { - LOGE("Couldn't parse DEVICE_INITIAL_SDK_INT value!"); - } - json.erase("DEVICE_INITIAL_SDK_INT"); - } - - if (json.contains("spoofProvider") && json["spoofProvider"].is_boolean()) { - spoofProvider = json["spoofProvider"].get(); - json.erase("spoofProvider"); - } - - if (json.contains("spoofProps") && json["spoofProps"].is_boolean()) { - spoofProps = json["spoofProps"].get(); - json.erase("spoofProps"); - } - - if (json.contains("spoofSignature") && json["spoofSignature"].is_boolean()) { - spoofSignature = json["spoofSignature"].get(); - json.erase("spoofSignature"); - } - - if (json.contains("DEBUG") && json["DEBUG"].is_boolean()) { - DEBUG = json["DEBUG"].get(); - json.erase("DEBUG"); - } - - if (json.contains("FINGERPRINT") && json["FINGERPRINT"].is_string()) { - std::string fingerprint = json["FINGERPRINT"].get(); - - std::vector vector; - auto parts = fingerprint | std::views::split('/'); - - for (const auto &part: parts) { - auto subParts = std::string(part.begin(), part.end()) | std::views::split(':'); - for (const auto &subPart: subParts) { - vector.emplace_back(subPart.begin(), subPart.end()); - } - } - - if (vector.size() == 8) { - json["BRAND"] = vector[0]; - json["PRODUCT"] = vector[1]; - json["DEVICE"] = vector[2]; - json["RELEASE"] = vector[3]; - json["ID"] = vector[4]; - json["INCREMENTAL"] = vector[5]; - json["TYPE"] = vector[6]; - json["TAGS"] = vector[7]; - } else { - LOGE("Error parsing fingerprint values!"); - } - } - - if (json.contains("SECURITY_PATCH") && json["SECURITY_PATCH"].is_string()) { - SECURITY_PATCH = json["SECURITY_PATCH"].get(); - } - - if (json.contains("ID") && json["ID"].is_string()) { - BUILD_ID = json["ID"].get(); - } - } - - void injectDex() { - LOGD("get system classloader"); - auto clClass = env->FindClass("java/lang/ClassLoader"); - auto getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader", - "()Ljava/lang/ClassLoader;"); - auto systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader); - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - - LOGD("create class loader"); - auto dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader"); - auto dexClInit = env->GetMethodID(dexClClass, "", - "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); - auto buffer = env->NewDirectByteBuffer(dexVector.data(), - static_cast(dexVector.size())); - auto dexCl = env->NewObject(dexClClass, dexClInit, buffer, systemClassLoader); - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - - LOGD("load class"); - auto loadClass = env->GetMethodID(clClass, "loadClass", - "(Ljava/lang/String;)Ljava/lang/Class;"); - auto entryClassName = env->NewStringUTF("es.chiteroman.playintegrityfix.EntryPoint"); - auto entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName); - auto entryPointClass = (jclass) entryClassObj; - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - - LOGD("call init"); - auto entryInit = env->GetStaticMethodID(entryPointClass, "init", "(Ljava/lang/String;ZZ)V"); - auto jsonStr = env->NewStringUTF(json.dump().c_str()); - env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, spoofProvider, - spoofSignature); - - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->DeleteLocalRef(entryClassName); - env->DeleteLocalRef(entryClassObj); - env->DeleteLocalRef(jsonStr); - env->DeleteLocalRef(dexCl); - env->DeleteLocalRef(buffer); - env->DeleteLocalRef(dexClClass); - env->DeleteLocalRef(clClass); - - LOGD("jni memory free"); - } - - void UpdateBuildFields() { - jclass buildClass = env->FindClass("android/os/Build"); - jclass versionClass = env->FindClass("android/os/Build$VERSION"); - - for (auto &[key, val]: json.items()) { - if (!val.is_string()) continue; - - const char *fieldName = key.c_str(); - - jfieldID fieldID = env->GetStaticFieldID(buildClass, fieldName, "Ljava/lang/String;"); - - if (env->ExceptionCheck()) { - env->ExceptionClear(); - - fieldID = env->GetStaticFieldID(versionClass, fieldName, "Ljava/lang/String;"); - - if (env->ExceptionCheck()) { - env->ExceptionClear(); - continue; - } - } - - if (fieldID != nullptr) { - std::string str = val.get(); - const char *value = str.c_str(); - jstring jValue = env->NewStringUTF(value); - - env->SetStaticObjectField(buildClass, fieldID, jValue); - if (env->ExceptionCheck()) { - env->ExceptionClear(); - continue; - } - - LOGD("Set '%s' to '%s'", fieldName, value); - } - } - } -}; - -static std::vector readFile(const char *path) { - FILE *file = fopen(path, "rb"); - - if (!file) return {}; - - fseek(file, 0, SEEK_END); - long size = ftell(file); - fseek(file, 0, SEEK_SET); - - std::vector vector(size); - fread(vector.data(), 1, size, file); - - fclose(file); - - return vector; -} - -static bool checkOtaZip() { - std::array buffer{}; - std::string result; - bool found = false; - - std::unique_ptr pipe( - popen("unzip -l /system/etc/security/otacerts.zip", "r"), pclose); - if (!pipe) return false; - - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { - result += buffer.data(); - if (result.find("test") != std::string::npos) { - found = true; - break; - } - } - - return found; -} - -static void companion(int fd) { - - std::vector dex, json; - - if (std::filesystem::exists(DEX_PATH)) { - dex = readFile(DEX_PATH); - } - - if (std::filesystem::exists(CUSTOM_JSON)) { - json = readFile(CUSTOM_JSON); - } else if (std::filesystem::exists(CUSTOM_JSON_FORK)) { - json = readFile(CUSTOM_JSON_FORK); - } else if (std::filesystem::exists(DEFAULT_JSON)) { - json = readFile(DEFAULT_JSON); - } - - size_t dexSize = dex.size(); - size_t jsonSize = json.size(); - - xwrite(fd, &dexSize, sizeof(size_t)); - xwrite(fd, &jsonSize, sizeof(size_t)); - - if (dexSize > 0) { - xwrite(fd, dex.data(), dexSize); - } - - if (jsonSize > 0) { - xwrite(fd, json.data(), jsonSize); - } - - std::string ts(TS_PATH); - bool trickyStore = std::filesystem::exists(ts) && - !std::filesystem::exists(ts + "/disable") && - !std::filesystem::exists(ts + "/remove"); - xwrite(fd, &trickyStore, sizeof(bool)); - - bool testSignedRom = checkOtaZip(); - xwrite(fd, &testSignedRom, sizeof(bool)); -} - -REGISTER_ZYGISK_MODULE(PlayIntegrityFix) - -REGISTER_ZYGISK_COMPANION(companion) diff --git a/app/src/main/cpp/zygisk.hpp b/app/src/main/cpp/zygisk.hpp deleted file mode 100644 index 7272633..0000000 --- a/app/src/main/cpp/zygisk.hpp +++ /dev/null @@ -1,384 +0,0 @@ -/* Copyright 2022-2023 John "topjohnwu" Wu - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -// This is the public API for Zygisk modules. -// DO NOT MODIFY ANY CODE IN THIS HEADER. - -#pragma once - -#include - -#define ZYGISK_API_VERSION 2 - -/* - -*************** -* Introduction -*************** - -On Android, all app processes are forked from a special daemon called "Zygote". -For each new app process, zygote will fork a new process and perform "specialization". -This specialization operation enforces the Android security sandbox on the newly forked -process to make sure that 3rd party application code is only loaded after it is being -restricted within a sandbox. - -On Android, there is also this special process called "system_server". This single -process hosts a significant portion of system services, which controls how the -Android operating system and apps interact with each other. - -The Zygisk framework provides a way to allow developers to build modules and run custom -code before and after system_server and any app processes' specialization. -This enable developers to inject code and alter the behavior of system_server and app processes. - -Please note that modules will only be loaded after zygote has forked the child process. -THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON! - -********************* -* Development Guide -********************* - -Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. -Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. - -Example code: - -static jint (*orig_logger_entry_max)(JNIEnv *env); -static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } - -class ExampleModule : public zygisk::ModuleBase { -public: - void onLoad(zygisk::Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { - JNINativeMethod methods[] = { - { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, - }; - api->hookJniNativeMethods(env, "android/util/Log", methods, 1); - *(void **) &orig_logger_entry_max = methods[0].fnPtr; - } -private: - zygisk::Api *api; - JNIEnv *env; -}; - -REGISTER_ZYGISK_MODULE(ExampleModule) - ------------------------------------------------------------------------------------------ - -Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize, -or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class -never runs in a true superuser environment. - -If your module require access to superuser permissions, you can create and register -a root companion handler function. This function runs in a separate root companion -daemon process, and an Unix domain socket is provided to allow you to perform IPC between -your target process and the root companion process. - -Example code: - -static void example_handler(int socket) { ... } - -REGISTER_ZYGISK_COMPANION(example_handler) - -*/ - -namespace zygisk { - -struct Api; -struct AppSpecializeArgs; -struct ServerSpecializeArgs; - -class ModuleBase { -public: - - // This method is called as soon as the module is loaded into the target process. - // A Zygisk API handle will be passed as an argument. - virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} - - // This method is called before the app process is specialized. - // At this point, the process just got forked from zygote, but no app specific specialization - // is applied. This means that the process does not have any sandbox restrictions and - // still runs with the same privilege of zygote. - // - // All the arguments that will be sent and used for app specialization is passed as a single - // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app - // process will be specialized. - // - // If you need to run some operations as superuser, you can call Api::connectCompanion() to - // get a socket to do IPC calls with a root companion process. - // See Api::connectCompanion() for more info. - virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} - - // This method is called after the app process is specialized. - // At this point, the process has all sandbox restrictions enabled for this application. - // This means that this method runs with the same privilege of the app's own code. - virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} - - // This method is called before the system server process is specialized. - // See preAppSpecialize(args) for more info. - virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} - - // This method is called after the system server process is specialized. - // At this point, the process runs with the privilege of system_server. - virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} -}; - -struct AppSpecializeArgs { - // Required arguments. These arguments are guaranteed to exist on all Android versions. - jint &uid; - jint &gid; - jintArray &gids; - jint &runtime_flags; - jint &mount_external; - jstring &se_info; - jstring &nice_name; - jstring &instruction_set; - jstring &app_data_dir; - - // Optional arguments. Please check whether the pointer is null before de-referencing - jboolean *const is_child_zygote; - jboolean *const is_top_app; - jobjectArray *const pkg_data_info_list; - jobjectArray *const whitelisted_data_info_list; - jboolean *const mount_data_dirs; - jboolean *const mount_storage_dirs; - - AppSpecializeArgs() = delete; -}; - -struct ServerSpecializeArgs { - jint &uid; - jint &gid; - jintArray &gids; - jint &runtime_flags; - jlong &permitted_capabilities; - jlong &effective_capabilities; - - ServerSpecializeArgs() = delete; -}; - -namespace internal { -struct api_table; -template void entry_impl(api_table *, JNIEnv *); -} - -// These values are used in Api::setOption(Option) -enum Option : int { - // Force Magisk's denylist unmount routines to run on this process. - // - // Setting this option only makes sense in preAppSpecialize. - // The actual unmounting happens during app process specialization. - // - // Set this option to force all Magisk and modules' files to be unmounted from the - // mount namespace of the process, regardless of the denylist enforcement status. - FORCE_DENYLIST_UNMOUNT = 0, - - // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. - // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. - // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. - DLCLOSE_MODULE_LIBRARY = 1, -}; - -// Bit masks of the return value of Api::getFlags() -enum StateFlag : uint32_t { - // The user has granted root access to the current process - PROCESS_GRANTED_ROOT = (1u << 0), - - // The current process was added on the denylist - PROCESS_ON_DENYLIST = (1u << 1), -}; - -// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded -// from the specialized process afterwards. -struct Api { - - // Connect to a root companion process and get a Unix domain socket for IPC. - // - // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. - // - // The pre[XXX]Specialize methods run with the same privilege of zygote. - // If you would like to do some operations with superuser permissions, register a handler - // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). - // Another good use case for a companion process is that if you want to share some resources - // across multiple processes, hold the resources in the companion process and pass it over. - // - // The root companion process is ABI aware; that is, when calling this method from a 32-bit - // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. - // - // Returns a file descriptor to a socket that is connected to the socket passed to your - // module's companion request handler. Returns -1 if the connection attempt failed. - int connectCompanion(); - - // Get the file descriptor of the root folder of the current module. - // - // This API only works in the pre[XXX]Specialize methods. - // Accessing the directory returned is only possible in the pre[XXX]Specialize methods - // or in the root companion process (assuming that you sent the fd over the socket). - // Both restrictions are due to SELinux and UID. - // - // Returns -1 if errors occurred. - int getModuleDir(); - - // Set various options for your module. - // Please note that this method accepts one single option at a time. - // Check zygisk::Option for the full list of options available. - void setOption(Option opt); - - // Get information about the current process. - // Returns bitwise-or'd zygisk::StateFlag values. - uint32_t getFlags(); - - // Hook JNI native methods for a class - // - // Lookup all registered JNI native methods and replace it with your own methods. - // The original function pointer will be saved in each JNINativeMethod's fnPtr. - // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr - // will be set to nullptr. - void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); - - // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory. - // - // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example: - // - //
- // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64 - // (More details: https://man7.org/linux/man-pages/man5/proc.5.html) - // - // For ELFs loaded in memory with pathname matching `regex`, replace function `symbol` with `newFunc`. - // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. - void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc); - - // For ELFs loaded in memory with pathname matching `regex`, exclude hooks registered for `symbol`. - // If `symbol` is nullptr, then all symbols will be excluded. - void pltHookExclude(const char *regex, const char *symbol); - - // Commit all the hooks that was previously registered. - // Returns false if an error occurred. - bool pltHookCommit(); - -private: - internal::api_table *tbl; - template friend void internal::entry_impl(internal::api_table *, JNIEnv *); -}; - -// Register a class as a Zygisk module - -#define REGISTER_ZYGISK_MODULE(clazz) \ -void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ - zygisk::internal::entry_impl(table, env); \ -} - -// Register a root companion request handler function for your module -// -// The function runs in a superuser daemon process and handles a root companion request from -// your module running in a target process. The function has to accept an integer value, -// which is a Unix domain socket that is connected to the target process. -// See Api::connectCompanion() for more info. -// -// NOTE: the function can run concurrently on multiple threads. -// Be aware of race conditions if you have globally shared resources. - -#define REGISTER_ZYGISK_COMPANION(func) \ -void zygisk_companion_entry(int client) { func(client); } - -/********************************************************* - * The following is internal ABI implementation detail. - * You do not have to understand what it is doing. - *********************************************************/ - -namespace internal { - -struct module_abi { - long api_version; - ModuleBase *impl; - - void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); - void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); - void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); - void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); - - module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) { - preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); }; - postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); }; - preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); }; - postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); }; - } -}; - -struct api_table { - // Base - void *impl; - bool (*registerModule)(api_table *, module_abi *); - - void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); - void (*pltHookRegister)(const char *, const char *, void *, void **); - void (*pltHookExclude)(const char *, const char *); - bool (*pltHookCommit)(); - int (*connectCompanion)(void * /* impl */); - void (*setOption)(void * /* impl */, Option); - int (*getModuleDir)(void * /* impl */); - uint32_t (*getFlags)(void * /* impl */); -}; - -template -void entry_impl(api_table *table, JNIEnv *env) { - static Api api; - api.tbl = table; - static T module; - ModuleBase *m = &module; - static module_abi abi(m); - if (!table->registerModule(table, &abi)) return; - m->onLoad(&api, env); -} - -} // namespace internal - -inline int Api::connectCompanion() { - return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1; -} -inline int Api::getModuleDir() { - return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; -} -inline void Api::setOption(Option opt) { - if (tbl->setOption) tbl->setOption(tbl->impl, opt); -} -inline uint32_t Api::getFlags() { - return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; -} -inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { - if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods); -} -inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { - if (tbl->pltHookRegister) tbl->pltHookRegister(regex, symbol, newFunc, oldFunc); -} -inline void Api::pltHookExclude(const char *regex, const char *symbol) { - if (tbl->pltHookExclude) tbl->pltHookExclude(regex, symbol); -} -inline bool Api::pltHookCommit() { - return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); -} - -} // namespace zygisk - -extern "C" { - -[[gnu::visibility("default"), maybe_unused]] -void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); - -[[gnu::visibility("default"), maybe_unused]] -void zygisk_companion_entry(int); - -} // extern "C" diff --git a/build.gradle.kts b/build.gradle.kts index 3756278..39ab1e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,57 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false +} + +tasks.register("copyZygiskFiles") { + doLast { + val moduleFolder = project.rootDir.resolve("module") + + val zygiskModule = project.project(":zygisk") + val zygiskBuildDir = zygiskModule.layout.buildDirectory.get().asFile + + val classesJar = zygiskBuildDir + .resolve("intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar") + classesJar.copyTo(moduleFolder.resolve("classes.jar"), overwrite = true) + + val zygiskSoDir = zygiskBuildDir + .resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib") + + zygiskSoDir.walk() + .filter { it.isFile && it.name == "libzygisk.so" } + .forEach { soFile -> + val abiFolder = soFile.parentFile.name + val destination = moduleFolder.resolve("zygisk/$abiFolder.so") + soFile.copyTo(destination, overwrite = true) + } + } +} + +tasks.register("copyInjectFiles") { + doLast { + val moduleFolder = project.rootDir.resolve("module") + + val injectModule = project.project(":inject") + val injectBuildDir = injectModule.layout.buildDirectory.get().asFile + + val injectSoDir = injectBuildDir + .resolve("intermediates/stripped_native_libs/release/stripReleaseDebugSymbols/out/lib") + + injectSoDir.walk() + .filter { it.isFile && it.name == "libinject.so" } + .forEach { soFile -> + val abiFolder = soFile.parentFile.name + val destination = moduleFolder.resolve("inject/$abiFolder.so") + soFile.copyTo(destination, overwrite = true) + } + } +} + +tasks.register("zip") { + dependsOn("copyZygiskFiles", "copyInjectFiles") + + archiveFileName.set("PlayIntegrityFix.zip") + destinationDirectory.set(project.rootDir.resolve("out")) + + from(project.rootDir.resolve("module")) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9694151..686d1ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,4 +8,4 @@ cxx = { group = "org.lsposed.libcxx", name = "libcxx", version.ref = "cxx" } hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version.ref = "hiddenapibypass" } [plugins] -android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } diff --git a/app/.gitignore b/inject/.gitignore similarity index 100% rename from app/.gitignore rename to inject/.gitignore diff --git a/inject/build.gradle.kts b/inject/build.gradle.kts new file mode 100644 index 0000000..278f151 --- /dev/null +++ b/inject/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "es.chiteroman.inject" + compileSdk = 35 + + buildFeatures { + prefab = true + } + + packaging { + jniLibs { + excludes += "**/libdobby.so" + } + resources { + excludes += "**" + } + } + + defaultConfig { + minSdk = 26 + + externalNativeBuild { + cmake { + abiFilters( + "arm64-v8a", + "armeabi-v7a" + ) + + arguments( + "-DCMAKE_BUILD_TYPE=MinSizeRel", + "-DANDROID_STL=none" + ) + + cFlags( + "-std=c23", + "-fvisibility=hidden", + "-fvisibility-inlines-hidden" + ) + + cppFlags( + "-std=c++26", + "-fno-exceptions", + "-fno-rtti", + "-fvisibility=hidden", + "-fvisibility-inlines-hidden" + ) + } + } + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + } + } + + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + } + } +} + +dependencies { + implementation(libs.cxx) +} + +afterEvaluate { + tasks.named("assembleRelease") { + finalizedBy( + rootProject.tasks["copyInjectFiles"], + rootProject.tasks["zip"] + ) + } +} \ No newline at end of file diff --git a/inject/consumer-rules.pro b/inject/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/inject/proguard-rules.pro b/inject/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/inject/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/inject/src/main/AndroidManifest.xml b/inject/src/main/AndroidManifest.xml new file mode 100644 index 0000000..227314e --- /dev/null +++ b/inject/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/cpp/CMakeLists.txt b/inject/src/main/cpp/CMakeLists.txt similarity index 50% rename from app/src/main/cpp/CMakeLists.txt rename to inject/src/main/cpp/CMakeLists.txt index 97dd201..446fe23 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/inject/src/main/cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -project("playintegrityfix") +project("inject") link_libraries(log) @@ -8,8 +8,8 @@ find_package(cxx REQUIRED CONFIG) link_libraries(cxx::cxx) -add_library(${CMAKE_PROJECT_NAME} SHARED main.cpp) +add_library(inject SHARED inject.cpp) add_subdirectory(Dobby) -target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE dobby_static) +target_link_libraries(inject PRIVATE dobby_static) diff --git a/app/src/main/cpp/Dobby b/inject/src/main/cpp/Dobby similarity index 100% rename from app/src/main/cpp/Dobby rename to inject/src/main/cpp/Dobby diff --git a/inject/src/main/cpp/inject.cpp b/inject/src/main/cpp/inject.cpp new file mode 100644 index 0000000..365c821 --- /dev/null +++ b/inject/src/main/cpp/inject.cpp @@ -0,0 +1,302 @@ +#include "dobby.h" +#include "json.hpp" +#include +#include +#include + +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) + +static std::string dir; +static JNIEnv *env; + +static nlohmann::json json; + +static bool spoofProps = true, spoofProvider = true, spoofSignature = false; + +static bool DEBUG = false; +static std::string DEVICE_INITIAL_SDK_INT, SECURITY_PATCH, BUILD_ID; + +typedef void (*T_Callback)(void *, const char *, const char *, uint32_t); + +static T_Callback o_callback = nullptr; + +static void modify_callback(void *cookie, const char *name, const char *value, + uint32_t serial) { + + if (!cookie || !name || !value || !o_callback) + return; + + const char *oldValue = value; + + std::string_view prop(name); + + if (prop == "init.svc.adbd") { + value = "stopped"; + } else if (prop == "sys.usb.state") { + value = "mtp"; + } else if (prop.ends_with("api_level")) { + if (!DEVICE_INITIAL_SDK_INT.empty()) { + value = DEVICE_INITIAL_SDK_INT.c_str(); + } + } else if (prop.ends_with(".security_patch")) { + if (!SECURITY_PATCH.empty()) { + value = SECURITY_PATCH.c_str(); + } + } else if (prop.ends_with(".build.id")) { + if (!BUILD_ID.empty()) { + value = BUILD_ID.c_str(); + } + } + + if (strcmp(oldValue, value) == 0) { + if (DEBUG) + LOGD("[%s]: %s (unchanged)", name, oldValue); + } else { + LOGD("[%s]: %s -> %s", name, oldValue, value); + } + + return o_callback(cookie, name, value, serial); +} + +static void (*o_system_property_read_callback)(prop_info *, T_Callback, + void *) = nullptr; + +static void my_system_property_read_callback(prop_info *pi, T_Callback callback, + void *cookie) { + if (pi && callback && cookie) + o_callback = callback; + return o_system_property_read_callback(pi, modify_callback, cookie); +} + +static bool doHook() { + void *ptr = DobbySymbolResolver(nullptr, "__system_property_read_callback"); + + if (ptr && DobbyHook(ptr, (void *) my_system_property_read_callback, + (void **) &o_system_property_read_callback) == 0) { + LOGD("hook __system_property_read_callback successful at %p", ptr); + return true; + } + + LOGE("hook __system_property_read_callback failed!"); + return false; +} + +static void parseJSON() { + if (json.empty()) + return; + + if (json.contains("DEVICE_INITIAL_SDK_INT")) { + if (json["DEVICE_INITIAL_SDK_INT"].is_string()) { + DEVICE_INITIAL_SDK_INT = + json["DEVICE_INITIAL_SDK_INT"].get(); + } else if (json["DEVICE_INITIAL_SDK_INT"].is_number_integer()) { + DEVICE_INITIAL_SDK_INT = + std::to_string(json["DEVICE_INITIAL_SDK_INT"].get()); + } else { + LOGE("Couldn't parse DEVICE_INITIAL_SDK_INT value!"); + } + json.erase("DEVICE_INITIAL_SDK_INT"); + } + + if (json.contains("spoofProvider") && json["spoofProvider"].is_boolean()) { + spoofProvider = json["spoofProvider"].get(); + json.erase("spoofProvider"); + } + + if (json.contains("spoofProps") && json["spoofProps"].is_boolean()) { + spoofProps = json["spoofProps"].get(); + json.erase("spoofProps"); + } + + if (json.contains("spoofSignature") && json["spoofSignature"].is_boolean()) { + spoofSignature = json["spoofSignature"].get(); + json.erase("spoofSignature"); + } + + if (json.contains("DEBUG") && json["DEBUG"].is_boolean()) { + DEBUG = json["DEBUG"].get(); + json.erase("DEBUG"); + } + + if (json.contains("FINGERPRINT") && json["FINGERPRINT"].is_string()) { + std::string fingerprint = json["FINGERPRINT"].get(); + + std::vector vector; + auto parts = fingerprint | std::views::split('/'); + + for (const auto &part: parts) { + auto subParts = + std::string(part.begin(), part.end()) | std::views::split(':'); + for (const auto &subPart: subParts) { + vector.emplace_back(subPart.begin(), subPart.end()); + } + } + + if (vector.size() == 8) { + json["BRAND"] = vector[0]; + json["PRODUCT"] = vector[1]; + json["DEVICE"] = vector[2]; + json["RELEASE"] = vector[3]; + json["ID"] = vector[4]; + json["INCREMENTAL"] = vector[5]; + json["TYPE"] = vector[6]; + json["TAGS"] = vector[7]; + } else { + LOGE("Error parsing fingerprint values!"); + } + } + + if (json.contains("SECURITY_PATCH") && json["SECURITY_PATCH"].is_string()) { + SECURITY_PATCH = json["SECURITY_PATCH"].get(); + } + + if (json.contains("ID") && json["ID"].is_string()) { + BUILD_ID = json["ID"].get(); + } +} + +static void UpdateBuildFields() { + jclass buildClass = env->FindClass("android/os/Build"); + jclass versionClass = env->FindClass("android/os/Build$VERSION"); + + for (auto &[key, val]: json.items()) { + if (!val.is_string()) + continue; + + const char *fieldName = key.c_str(); + + jfieldID fieldID = + env->GetStaticFieldID(buildClass, fieldName, "Ljava/lang/String;"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + + fieldID = + env->GetStaticFieldID(versionClass, fieldName, "Ljava/lang/String;"); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + } + + if (fieldID != nullptr) { + std::string str = val.get(); + const char *value = str.c_str(); + jstring jValue = env->NewStringUTF(value); + + env->SetStaticObjectField(buildClass, fieldID, jValue); + if (env->ExceptionCheck()) { + env->ExceptionClear(); + continue; + } + + LOGD("Set '%s' to '%s'", fieldName, value); + } + } +} + +static void injectDex() { + LOGD("get system classloader"); + auto clClass = env->FindClass("java/lang/ClassLoader"); + auto getSystemClassLoader = env->GetStaticMethodID( + clClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); + auto systemClassLoader = + env->CallStaticObjectMethod(clClass, getSystemClassLoader); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + + LOGD("create class loader"); + auto dexClClass = env->FindClass("dalvik/system/PathClassLoader"); + auto dexClInit = env->GetMethodID( + dexClClass, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V"); + auto str1 = env->NewStringUTF((dir + "/classes.dex").c_str()); + auto str2 = env->NewStringUTF(dir.c_str()); + auto dexCl = + env->NewObject(dexClClass, dexClInit, str1, str2, systemClassLoader); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + + LOGD("load class"); + auto loadClass = env->GetMethodID(clClass, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + auto entryClassName = + env->NewStringUTF("es.chiteroman.playintegrityfix.EntryPoint"); + auto entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName); + auto entryPointClass = (jclass) entryClassObj; + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return; + } + + LOGD("call init"); + auto entryInit = env->GetStaticMethodID(entryPointClass, "init", + "(Ljava/lang/String;ZZ)V"); + auto jsonStr = env->NewStringUTF(json.dump().c_str()); + env->CallStaticVoidMethod(entryPointClass, entryInit, jsonStr, spoofProvider, + spoofSignature); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + + env->DeleteLocalRef(entryClassName); + env->DeleteLocalRef(entryClassObj); + env->DeleteLocalRef(jsonStr); + env->DeleteLocalRef(dexCl); + env->DeleteLocalRef(str1); + env->DeleteLocalRef(str2); + env->DeleteLocalRef(dexClClass); + env->DeleteLocalRef(clClass); + + LOGD("jni memory free"); +} + +extern "C" [[gnu::visibility("default"), maybe_unused]] bool +init(JavaVM *vm, const std::string &gmsDir) { + bool close = true; + + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + LOGE("[INJECT] JNI_ERR!"); + return true; + } + + dir = gmsDir; + LOGD("[INJECT] GMS dir: %s", dir.c_str()); + + FILE *f = fopen((dir + "/pif.json").c_str(), "r"); + json = nlohmann::json::parse(f, nullptr, false, true); + fclose(f); + + parseJSON(); + + UpdateBuildFields(); + + if (spoofProvider || spoofSignature) { + injectDex(); + } else { + LOGD("[INJECT] Dex file won't be injected due spoofProvider and " + "spoofSignature are false"); + } + + if (spoofProps) { + close = !doHook(); + } + + LOGD("[INJECT] Done!"); + + return close; +} \ No newline at end of file diff --git a/app/src/main/cpp/json.hpp b/inject/src/main/cpp/json.hpp similarity index 94% rename from app/src/main/cpp/json.hpp rename to inject/src/main/cpp/json.hpp index 8b72ea6..9433db8 100644 --- a/app/src/main/cpp/json.hpp +++ b/inject/src/main/cpp/json.hpp @@ -3,7 +3,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT /****************************************************************************\ @@ -37,7 +37,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -50,7 +50,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -73,6 +73,10 @@ #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif @@ -83,6 +87,12 @@ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS #endif +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp #else @@ -94,14 +104,15 @@ #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ @@ -152,7 +163,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -162,6 +173,9 @@ #include // forward_list #include // inserter, front_inserter, end #include // map +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible @@ -175,7 +189,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -195,7 +209,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -211,7 +225,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -223,7 +237,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -236,7 +250,7 @@ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -323,8 +337,8 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson // SPDX-License-Identifier: MIT /* Hedley - https://nemequ.github.io/hedley @@ -2384,15 +2398,20 @@ JSON_HEDLEY_DIAGNOSTIC_POP // C++ language standard detection // if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) + #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // the cpp 11 flag is always specified because it is the minimal required version @@ -2568,7 +2587,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ @@ -2580,7 +2601,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ @@ -2743,42 +2766,146 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } // inspired from https://stackoverflow.com/a/26745591 -// allows to call any std function as if (e.g. with begin): +// allows calling any std function as if (e.g., with begin): // using std::begin; begin(x); // // it allows using the detected idiom to retrieve the return type @@ -2942,7 +3069,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3017,7 +3144,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3059,7 +3186,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-FileCopyrightText: 2018 The Abseil Authors // SPDX-License-Identifier: MIT @@ -3219,7 +3346,7 @@ struct static_const #endif template -inline constexpr std::array make_array(Args&& ... args) +constexpr std::array make_array(Args&& ... args) { return std::array {{static_cast(std::forward(args))...}}; } @@ -3233,16 +3360,16 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // numeric_limits +#include // char_traits +#include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval -#include // tuple -#include // char_traits // #include // __ _____ _____ _____ @@ -3250,7 +3377,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3293,7 +3420,7 @@ struct iterator_traits template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types + : iterator_types { }; @@ -3318,7 +3445,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3338,7 +3465,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3362,7 +3489,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -3624,7 +3751,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3648,7 +3775,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3674,19 +3801,19 @@ struct is_default_constructible : std::is_default_constructible {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_constructible : std::is_constructible {}; @@ -3884,8 +4011,8 @@ is_detected::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 !std::is_same>::value&& - is_complete_type < - detected_t>::value >> +is_complete_type < +detected_t>::value >> { using value_type = range_value_t; @@ -4008,12 +4135,12 @@ using is_usable_as_key_type = typename std::conditional < template> using is_usable_as_basic_json_key_type = typename std::conditional < - is_usable_as_key_type::value - && !is_json_iterator_of::value, - std::true_type, - std::false_type >::type; + is_usable_as_key_type::value + && !is_json_iterator_of::value, + std::true_type, + std::false_type >::type; template using detect_erase_with_key_type = decltype(std::declval().erase(std::declval())); @@ -4147,7 +4274,7 @@ struct value_in_range_of_impl1 }; template -inline constexpr bool value_in_range_of(T val) +constexpr bool value_in_range_of(T val) { return value_in_range_of_impl1::test(val); } @@ -4163,7 +4290,7 @@ namespace impl { template -inline constexpr bool is_c_string() +constexpr bool is_c_string() { using TUnExt = typename std::remove_extent::type; using TUnCVExt = typename std::remove_cv::type; @@ -4191,7 +4318,7 @@ namespace impl { template -inline constexpr bool is_transparent() +constexpr bool is_transparent() { return is_detected::value; } @@ -4213,7 +4340,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4358,6 +4485,18 @@ inline OutStringType concat(Args && ... args) NLOHMANN_JSON_NAMESPACE_END +// With -Wweak-vtables, Clang will complain about the exception classes as they +// have no out-of-line virtual method definitions and their vtable will be +// emitted in every translation unit. This issue cannot be fixed with a +// header-only library as there is no implementation file to move these +// functions to. As a result, we suppress this warning here to avoid client +// code to stumble over this. See https://github.com/nlohmann/json/issues/4087 +// for a discussion. +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4452,16 +4591,34 @@ class exception : public std::exception { return concat(a, '/', detail::escape(b)); }); - return concat('(', str, ") "); + + return concat('(', str, ") ", get_byte_positions(leaf_element)); #else - static_cast(leaf_element); - return ""; + return get_byte_positions(leaf_element); #endif } private: /// an exception object as storage for error messages std::runtime_error m; +#if JSON_DIAGNOSTIC_POSITIONS + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + { + return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); + } + return ""; + } +#else + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + static_cast(leaf_element); + return ""; + } +#endif }; /// @brief exception indicating a parse error @@ -4589,6 +4746,10 @@ class other_error : public exception } // namespace detail NLOHMANN_JSON_NAMESPACE_END +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + // #include // #include @@ -4599,7 +4760,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4623,7 +4784,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4640,7 +4801,7 @@ namespace std_fs = std::experimental::filesystem; } // namespace detail NLOHMANN_JSON_NAMESPACE_END #elif JSON_HAS_FILESYSTEM -#include +#include // NOLINT(build/c++17) NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4670,6 +4831,24 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) n = nullptr; } +#ifdef JSON_HAS_CPP_17 +#ifndef JSON_USE_IMPLICIT_CONVERSIONS +template +void from_json(const BasicJsonType& j, std::optional& opt) +{ + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt.emplace(j.template get()); + } +} + +#endif // JSON_USE_IMPLICIT_CONVERSIONS +#endif // JSON_HAS_CPP_17 + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -4817,6 +4996,54 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } } +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + arr[i1][i2] = j.at(i1).at(i2).template get(); + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get(); + } + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + for (std::size_t i4 = 0; i4 < N4; ++i4) + { + arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get(); + } + } + } + } +} + template inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { @@ -4902,7 +5129,7 @@ void()) template < typename BasicJsonType, typename T, std::size_t... Idx > std::array from_json_inplace_array_impl(BasicJsonType&& j, - identity_tag> /*unused*/, index_sequence /*unused*/) + identity_tag> /*unused*/, index_sequence /*unused*/) { return { { std::forward(j).at(Idx).template get()... } }; } @@ -5006,6 +5233,12 @@ std::tuple from_json_tuple_impl_base(BasicJsonType&& j, index_sequence< return std::make_tuple(std::forward(j).at(Idx).template get()...); } +template +std::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/) +{ + return {}; +} + template < typename BasicJsonType, class A1, class A2 > std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) { @@ -5091,7 +5324,12 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); } - p = *j.template get_ptr(); + const auto& s = *j.template get_ptr(); +#ifdef JSON_HAS_CPP_20 + p = std_fs::path(std::u8string_view(reinterpret_cast(s.data()), s.size())); +#else + p = std_fs::u8path(s); // accepts UTF-8 encoded std::string in C++17, deprecated in C++20 +#endif } #endif @@ -5129,11 +5367,17 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT +// #include +// JSON_HAS_CPP_17 +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif + #include // copy #include // begin, end #include // string @@ -5149,14 +5393,13 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // size_t -#include // input_iterator_tag -#include // string, to_string +#include // forward_iterator_tag #include // tuple_size, get, tuple_element #include // move @@ -5168,6 +5411,46 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // string, to_string + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +void int_to_string(StringType& target, std::size_t value) +{ + // For ADL + using std::to_string; + target = to_string(value); +} + +template +StringType to_string(std::size_t value) +{ + StringType result; + int_to_string(result, value); + return result; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include @@ -5175,13 +5458,6 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -template -void int_to_string( string_type& target, std::size_t value ) -{ - // For ADL - using std::to_string; - target = to_string(value); -} template class iteration_proxy_value { public: @@ -5189,7 +5465,7 @@ template class iteration_proxy_value using value_type = iteration_proxy_value; using pointer = value_type *; using reference = value_type &; - using iterator_category = std::input_iterator_tag; + using iterator_category = std::forward_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: @@ -5369,7 +5645,7 @@ namespace std #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> // NOLINT(cert-dcl58-cpp) - : public std::integral_constant {}; + : public std::integral_constant {}; template class tuple_element> // NOLINT(cert-dcl58-cpp) @@ -5390,8 +5666,6 @@ class tuple_element> inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; #endif -// #include - // #include // #include @@ -5637,6 +5911,22 @@ struct external_constructor // to_json // ///////////// +#ifdef JSON_HAS_CPP_17 +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::optional& opt) +{ + if (opt.has_value()) + { + j = *opt; + } + else + { + j = nullptr; + } +} +#endif + template::value, int> = 0> inline void to_json(BasicJsonType& j, T b) noexcept @@ -5697,7 +5987,8 @@ template::type; - external_constructor::construct(j, static_cast(e)); + static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; + external_constructor::construct(j, static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -5782,6 +6073,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = { std::get(t)... }; } +template +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/) +{ + using array_t = typename BasicJsonType::array_t; + j = array_t(); +} + template::value, int > = 0> inline void to_json(BasicJsonType& j, const T& t) { @@ -5792,7 +6090,12 @@ inline void to_json(BasicJsonType& j, const T& t) template inline void to_json(BasicJsonType& j, const std_fs::path& p) { - j = p.string(); +#ifdef JSON_HAS_CPP_20 + const std::u8string s = p.u8string(); + j = std::string(s.begin(), s.end()); +#else + j = p.u8string(); // returns std::string in C++17 +#endif } #endif @@ -5870,7 +6173,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -5982,7 +6285,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6115,7 +6418,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6132,6 +6435,9 @@ NLOHMANN_JSON_NAMESPACE_END #include // char_traits, string #include // make_pair, move #include // vector +#ifdef __cpp_lib_byteswap + #include //byteswap +#endif // #include @@ -6141,7 +6447,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6161,6 +6467,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // istream #endif // JSON_NO_IO +// #include + // #include // #include @@ -6208,6 +6516,13 @@ class file_input_adapter return std::fgetc(m_file); } + // returns the number of characters successfully read + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + return fread(dest, 1, sizeof(T) * count, m_file); + } + private: /// the file pointer to read from std::FILE* m_file; @@ -6267,6 +6582,17 @@ class input_stream_adapter return res; } + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto res = static_cast(sb->sgetn(reinterpret_cast(dest), static_cast(count * sizeof(T)))); + if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T))) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + private: /// the associated input stream std::istream* is = nullptr; @@ -6298,6 +6624,26 @@ class iterator_input_adapter return char_traits::eof(); } + // for general iterators, we cannot really do something better than falling back to processing the range one-by-one + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto* ptr = reinterpret_cast(dest); + for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index) + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + ptr[read_index] = static_cast(*current); + std::advance(current, 1); + } + else + { + return read_index; + } + } + return count * sizeof(T); + } + private: IteratorType current; IteratorType end; @@ -6461,6 +6807,13 @@ class wide_string_input_adapter return utf8_bytes[utf8_bytes_index++]; } + // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here + template + std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) + { + JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + } + private: BaseInputAdapter base_adapter; @@ -6557,10 +6910,17 @@ typename container_input_adapter_factory_impl::container_input_adapter_factory::create(container); } +// specialization for std::string +using string_input_adapter_type = decltype(input_adapter(std::declval())); + #ifndef JSON_NO_IO // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { + if (file == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } return file_input_adapter(file); } @@ -6587,9 +6947,13 @@ template < typename CharT, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { + if (b == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); - return input_adapter(ptr, ptr + length); + return input_adapter(ptr, ptr + length); // cppcheck-suppress[nullPointerArithmeticRedundantCheck] } template @@ -6638,739 +7002,26 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include #include // string +#include // enable_if_t #include // move #include // vector // #include -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -/*! -@brief SAX interface - -This class describes the SAX interface used by @ref nlohmann::json::sax_parse. -Each function is called in different situations while the input is parsed. The -boolean return value informs the parser whether to continue processing the -input. -*/ -template -struct json_sax -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @brief a null value was read - @return whether parsing should proceed - */ - virtual bool null() = 0; - - /*! - @brief a boolean value was read - @param[in] val boolean value - @return whether parsing should proceed - */ - virtual bool boolean(bool val) = 0; - - /*! - @brief an integer number was read - @param[in] val integer value - @return whether parsing should proceed - */ - virtual bool number_integer(number_integer_t val) = 0; - - /*! - @brief an unsigned integer number was read - @param[in] val unsigned integer value - @return whether parsing should proceed - */ - virtual bool number_unsigned(number_unsigned_t val) = 0; - - /*! - @brief a floating-point number was read - @param[in] val floating-point value - @param[in] s raw token value - @return whether parsing should proceed - */ - virtual bool number_float(number_float_t val, const string_t& s) = 0; - - /*! - @brief a string value was read - @param[in] val string value - @return whether parsing should proceed - @note It is safe to move the passed string value. - */ - virtual bool string(string_t& val) = 0; - - /*! - @brief a binary value was read - @param[in] val binary value - @return whether parsing should proceed - @note It is safe to move the passed binary value. - */ - virtual bool binary(binary_t& val) = 0; - - /*! - @brief the beginning of an object was read - @param[in] elements number of object elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_object(std::size_t elements) = 0; - - /*! - @brief an object key was read - @param[in] val object key - @return whether parsing should proceed - @note It is safe to move the passed string. - */ - virtual bool key(string_t& val) = 0; - - /*! - @brief the end of an object was read - @return whether parsing should proceed - */ - virtual bool end_object() = 0; - - /*! - @brief the beginning of an array was read - @param[in] elements number of array elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_array(std::size_t elements) = 0; - - /*! - @brief the end of an array was read - @return whether parsing should proceed - */ - virtual bool end_array() = 0; - - /*! - @brief a parse error occurred - @param[in] position the position in the input where the error occurs - @param[in] last_token the last read token - @param[in] ex an exception object describing the error - @return whether parsing should proceed (must return false) - */ - virtual bool parse_error(std::size_t position, - const std::string& last_token, - const detail::exception& ex) = 0; - - json_sax() = default; - json_sax(const json_sax&) = default; - json_sax(json_sax&&) noexcept = default; - json_sax& operator=(const json_sax&) = default; - json_sax& operator=(json_sax&&) noexcept = default; - virtual ~json_sax() = default; -}; - -namespace detail -{ -/*! -@brief SAX implementation to create a JSON value from SAX events - -This class implements the @ref json_sax interface and processes the SAX events -to create a JSON value which makes it basically a DOM parser. The structure or -hierarchy of the JSON value is managed by the stack `ref_stack` which contains -a pointer to the respective array or object for each recursion depth. - -After successful parsing, the value that is passed by reference to the -constructor contains the parsed value. - -@tparam BasicJsonType the JSON type -*/ -template -class json_sax_dom_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @param[in,out] r reference to a JSON value that is manipulated while - parsing - @param[in] allow_exceptions_ whether parse errors yield exceptions - */ - explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) - : root(r), allow_exceptions(allow_exceptions_) - {} - - // make class move-only - json_sax_dom_parser(const json_sax_dom_parser&) = delete; - json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; - json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_parser() = default; - - bool null() - { - handle_value(nullptr); - return true; - } - - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } - - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } - - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } - - bool start_object(std::size_t len) - { - ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); - - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool key(string_t& val) - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); - - // add null at given key and store the reference for later - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); - return true; - } - - bool end_object() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); - - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } - - bool start_array(std::size_t len) - { - ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); - - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool end_array() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_array()); - - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - */ - template - JSON_HEDLEY_RETURNS_NON_NULL - BasicJsonType* handle_value(Value&& v) - { - if (ref_stack.empty()) - { - root = BasicJsonType(std::forward(v)); - return &root; - } - - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); - return &(ref_stack.back()->m_data.m_value.array->back()); - } - - JSON_ASSERT(ref_stack.back()->is_object()); - JSON_ASSERT(object_element); - *object_element = BasicJsonType(std::forward(v)); - return object_element; - } - - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; - -template -class json_sax_dom_callback_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using parser_callback_t = typename BasicJsonType::parser_callback_t; - using parse_event_t = typename BasicJsonType::parse_event_t; - - json_sax_dom_callback_parser(BasicJsonType& r, - const parser_callback_t cb, - const bool allow_exceptions_ = true) - : root(r), callback(cb), allow_exceptions(allow_exceptions_) - { - keep_stack.push_back(true); - } - - // make class move-only - json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_callback_parser() = default; - - bool null() - { - handle_value(nullptr); - return true; - } - - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } - - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } - - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } - - bool start_object(std::size_t len) - { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); - keep_stack.push_back(keep); - - auto val = handle_value(BasicJsonType::value_t::object, true); - ref_stack.push_back(val.second); - - // check object limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool key(string_t& val) - { - BasicJsonType k = BasicJsonType(val); - - // check callback for key - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); - key_keep_stack.push_back(keep); - - // add discarded value at given key and store the reference for later - if (keep && ref_stack.back()) - { - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); - } - - return true; - } - - bool end_object() - { - if (ref_stack.back()) - { - if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) - { - // discard object - *ref_stack.back() = discarded; - } - else - { - ref_stack.back()->set_parents(); - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) - { - // remove discarded value - for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) - { - if (it->is_discarded()) - { - ref_stack.back()->erase(it); - break; - } - } - } - - return true; - } - - bool start_array(std::size_t len) - { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); - keep_stack.push_back(keep); - - auto val = handle_value(BasicJsonType::value_t::array, true); - ref_stack.push_back(val.second); - - // check array limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); - } - - return true; - } - - bool end_array() - { - bool keep = true; - - if (ref_stack.back()) - { - keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); - if (keep) - { - ref_stack.back()->set_parents(); - } - else - { - // discard array - *ref_stack.back() = discarded; - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - // remove discarded value - if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->pop_back(); - } - - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @param[in] v value to add to the JSON value we build during parsing - @param[in] skip_callback whether we should skip calling the callback - function; this is required after start_array() and - start_object() SAX events, because otherwise we would call the - callback function with an empty array or object, respectively. - - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - - @return pair of boolean (whether value should be kept) and pointer (to the - passed value in the ref_stack hierarchy; nullptr if not kept) - */ - template - std::pair handle_value(Value&& v, const bool skip_callback = false) - { - JSON_ASSERT(!keep_stack.empty()); - - // do not handle this value if we know it would be added to a discarded - // container - if (!keep_stack.back()) - { - return {false, nullptr}; - } - - // create value - auto value = BasicJsonType(std::forward(v)); - - // check callback - const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); - - // do not handle this value if we just learnt it shall be discarded - if (!keep) - { - return {false, nullptr}; - } - - if (ref_stack.empty()) - { - root = std::move(value); - return {true, & root}; - } - - // skip this value if we already decided to skip the parent - // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) - if (!ref_stack.back()) - { - return {false, nullptr}; - } - - // we now only expect arrays and objects - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - - // array - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); - return {true, & (ref_stack.back()->m_data.m_value.array->back())}; - } - - // object - JSON_ASSERT(ref_stack.back()->is_object()); - // check if we should store an element for the current key - JSON_ASSERT(!key_keep_stack.empty()); - const bool store_element = key_keep_stack.back(); - key_keep_stack.pop_back(); - - if (!store_element) - { - return {false, nullptr}; - } - - JSON_ASSERT(object_element); - *object_element = std::move(value); - return {true, object_element}; - } - - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// stack to manage which values to keep - std::vector keep_stack {}; - /// stack to manage which object keys to keep - std::vector key_keep_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// callback function - const parser_callback_t callback = nullptr; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; - /// a discarded value for the callback - BasicJsonType discarded = BasicJsonType::value_t::discarded; -}; - -template -class json_sax_acceptor -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - bool null() - { - return true; - } - - bool boolean(bool /*unused*/) - { - return true; - } - - bool number_integer(number_integer_t /*unused*/) - { - return true; - } - - bool number_unsigned(number_unsigned_t /*unused*/) - { - return true; - } - - bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) - { - return true; - } - - bool string(string_t& /*unused*/) - { - return true; - } - - bool binary(binary_t& /*unused*/) - { - return true; - } - - bool start_object(std::size_t /*unused*/ = static_cast(-1)) - { - return true; - } - - bool key(string_t& /*unused*/) - { - return true; - } - - bool end_object() - { - return true; - } - - bool start_array(std::size_t /*unused*/ = static_cast(-1)) - { - return true; - } - - bool end_array() - { - return true; - } - - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) - { - return false; - } -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -8338,7 +7989,7 @@ class lexer : public lexer_base locale's decimal point is used instead of `.` to work with the locale-dependent converters. */ - token_type scan_number() // lgtm [cpp/use-of-goto] + token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated. { // reset token_buffer to store the number's bytes reset(); @@ -8420,6 +8071,7 @@ scan_number_zero: case '.': { add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; goto scan_number_decimal1; } @@ -8456,6 +8108,7 @@ scan_number_any1: case '.': { add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; goto scan_number_decimal1; } @@ -8616,7 +8269,7 @@ scan_number_done: // we are done scanning a number) unget(); - char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) errno = 0; // try to parse integers first and fall back to floats @@ -8627,7 +8280,7 @@ scan_number_done: // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - if (errno == 0) + if (errno != ERANGE) { value_unsigned = static_cast(x); if (value_unsigned == x) @@ -8643,7 +8296,7 @@ scan_number_done: // we checked the number format before JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - if (errno == 0) + if (errno != ERANGE) { value_integer = static_cast(x); if (value_integer == x) @@ -8693,6 +8346,7 @@ scan_number_done: { token_buffer.clear(); token_string.clear(); + decimal_point_position = std::string::npos; token_string.push_back(char_traits::to_char_type(current)); } @@ -8801,6 +8455,11 @@ scan_number_done: /// return current string value (implicitly resets the token; useful only once) string_t& get_string() { + // translate decimal points from locale back to '.' (#4084) + if (decimal_point_char != '.' && decimal_point_position != std::string::npos) + { + token_buffer[decimal_point_position] = '.'; + } return token_buffer; } @@ -8998,6 +8657,8 @@ scan_number_done: /// the decimal point const char_int_type decimal_point_char = '.'; + /// the position of the decimal point in the input + std::size_t decimal_point_position = std::string::npos; }; } // namespace detail @@ -9005,13 +8666,986 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/*! +@brief SAX interface + +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; + + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; + + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; + + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; + + /*! + @brief a floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; + + /*! + @brief a string value was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string value. + */ + virtual bool string(string_t& val) = 0; + + /*! + @brief a binary value was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary value. + */ + virtual bool binary(binary_t& val) = 0; + + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; + + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; + + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; + + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; + + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; + +namespace detail +{ +constexpr std::size_t unknown_size() +{ + return (std::numeric_limits::max)(); +} + +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using lexer_t = lexer; + + /*! + @param[in,out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) + : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool key(string_t& val) + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); + return true; + } + + bool end_object() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } + + return true; + } + + bool end_array() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) + { + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + // As we handle the start and end positions for values created during parsing, + // we do not expect the following value type to be called. Regardless, set the positions + // in case this is created manually or through a different constructor. Exclude from lcov + // since the exact condition of this switch is esoteric. + // LCOV_EXCL_START + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + // LCOV_EXCL_STOP + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE + } + } + } +#endif + + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(root); +#endif + + return &root; + } + + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back()); +#endif + + return &(ref_stack.back()->m_data.m_value.array->back()); + } + + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(*object_element); +#endif + + return object_element; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; + +template +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + using lexer_t = lexer; + + json_sax_dom_callback_parser(BasicJsonType& r, + parser_callback_t cb, + const bool allow_exceptions_ = true, + lexer_t* lexer_ = nullptr) + : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + { + keep_stack.push_back(true); + } + + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_callback_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + if (ref_stack.back()) + { + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + // check object limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + } + return true; + } + + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); + + // check callback for key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); + + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); + } + + return true; + } + + bool end_object() + { + if (ref_stack.back()) + { + if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; + +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded object. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + else + { + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) + { + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) + { + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } + } + } + + return true; + } + + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + if (ref_stack.back()) + { + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the array, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + // check array limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + } + } + + return true; + } + + bool end_array() + { + bool keep = true; + + if (ref_stack.back()) + { + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (keep) + { + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + } + else + { + // discard array + *ref_stack.back() = discarded; + +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded array. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->pop_back(); + } + + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) + { + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE + } + } + } +#endif + + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. + + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) + */ + template + std::pair handle_value(Value&& v, const bool skip_callback = false) + { + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) + { + return {false, nullptr}; + } + + // create value + auto value = BasicJsonType(std::forward(v)); + +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(value); +#endif + + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } + + if (ref_stack.empty()) + { + root = std::move(value); + return {true, & root}; + } + + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } + + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); + return {true, & (ref_stack.back()->m_data.m_value.array->back())}; + } + + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) + { + return {false, nullptr}; + } + + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; // NOLINT(readability-redundant-member-init) + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; // NOLINT(readability-redundant-member-init) + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; + +template +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } + + bool number_integer(number_integer_t /*unused*/) + { + return true; + } + + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } + + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } + + bool string(string_t& /*unused*/) + { + return true; + } + + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } + + bool key(string_t& /*unused*/) + { + return true; + } + + bool end_object() + { + return true; + } + + bool start_array(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } + + bool end_array() + { + return true; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -9207,7 +9841,7 @@ static inline bool little_endianness(int num = 1) noexcept /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ -template> +template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; @@ -9314,7 +9948,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -9470,6 +10104,12 @@ class binary_reader return get_number(input_format_t::bson, value) && sax->number_integer(value); } + case 0x11: // uint64 + { + std::uint64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_unsigned(value); + } + default: // anything else not supported (yet) { std::array cr{{}}; @@ -9536,7 +10176,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -9796,7 +10436,7 @@ class binary_reader } case 0x9F: // array (indefinite length) - return get_cbor_array(static_cast(-1), tag_handler); + return get_cbor_array(detail::unknown_size(), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: @@ -9850,7 +10490,7 @@ class binary_reader } case 0xBF: // map (indefinite length) - return get_cbor_object(static_cast(-1), tag_handler); + return get_cbor_object(detail::unknown_size(), tag_handler); case 0xC6: // tagged item case 0xC7: @@ -10238,7 +10878,7 @@ class binary_reader } /*! - @param[in] len the length of the array or static_cast(-1) for an + @param[in] len the length of the array or detail::unknown_size() for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed @@ -10251,7 +10891,7 @@ class binary_reader return false; } - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -10276,7 +10916,7 @@ class binary_reader } /*! - @param[in] len the length of the object or static_cast(-1) for an + @param[in] len the length of the object or detail::unknown_size() for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed @@ -10292,7 +10932,7 @@ class binary_reader if (len != 0) { string_t key; - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -11455,6 +12095,16 @@ class binary_reader case 'Z': // null return sax->null(); + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + case 'U': { std::uint8_t number{}; @@ -11655,7 +12305,7 @@ class binary_reader return false; } - if (size_and_type.second == 'C') + if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; } @@ -11677,6 +12327,13 @@ class binary_reader return (sax->end_array() && sax->end_object()); } + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + if (size_and_type.first != npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -11710,7 +12367,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -11788,7 +12445,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -11899,6 +12556,29 @@ class binary_reader return current = ia.get_character(); } + /*! + @brief get_to read into a primitive type + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns false instead + + @return bool, whether the read was successful + */ + template + bool get_to(T& dest, const input_format_t format, const char* context) + { + auto new_chars_read = ia.get_elements(&dest); + chars_read += new_chars_read; + if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) + { + // in case of failure, advance position by 1 to report failing location + ++chars_read; + sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + return false; + } + return true; + } + /*! @return character read from the input after ignoring all 'N' entries */ @@ -11913,6 +12593,28 @@ class binary_reader return current; } + template + static void byte_swap(NumberType& number) + { + constexpr std::size_t sz = sizeof(number); +#ifdef __cpp_lib_byteswap + if constexpr (sz == 1) + { + return; + } + if constexpr(std::is_integral_v) + { + number = std::byteswap(number); + return; + } +#endif + auto* ptr = reinterpret_cast(&number); + for (std::size_t i = 0; i < sz / 2; ++i) + { + std::swap(ptr[i], ptr[sz - i - 1]); + } + } + /* @brief read a number from the input @@ -11931,29 +12633,16 @@ class binary_reader template bool get_number(const input_format_t format, NumberType& result) { - // step 1: read input into array with system's byte order - std::array vec{}; - for (std::size_t i = 0; i < sizeof(NumberType); ++i) + // read in the original format + + if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, "number"))) { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) - { - return false; - } - - // reverse byte order prior to conversion if necessary - if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) - { - vec[sizeof(NumberType) - i - 1] = static_cast(current); - } - else - { - vec[i] = static_cast(current); // LCOV_EXCL_LINE - } + return false; + } + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) + { + byte_swap(result); } - - // step 2: convert array into number of type T and return - std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } @@ -12092,7 +12781,7 @@ class binary_reader } private: - static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast(-1); + static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size(); /// input adapter InputAdapterType ia; @@ -12118,6 +12807,7 @@ class binary_reader #define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ make_array( \ + bjd_type{'B', "byte"}, \ bjd_type{'C', "char"}, \ bjd_type{'D', "double"}, \ bjd_type{'I', "int16"}, \ @@ -12163,7 +12853,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12237,10 +12927,10 @@ class parser public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool skip_comments = false) - : callback(cb) + : callback(std::move(cb)) , m_lexer(std::move(adapter), skip_comments) , allow_exceptions(allow_exceptions_) { @@ -12262,7 +12952,7 @@ class parser { if (callback) { - json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12290,7 +12980,7 @@ class parser } else { - json_sax_dom_parser sdp(result, allow_exceptions); + json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12362,7 +13052,7 @@ class parser { case token_type::begin_object: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -12407,7 +13097,7 @@ class parser case token_type::begin_array: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -12692,7 +13382,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12705,7 +13395,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12864,7 +13554,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13331,7 +14021,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator==(const IterImpl& other) const @@ -13342,7 +14032,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + return true; + } switch (m_object->m_data.m_type) { @@ -13367,7 +14061,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator!=(const IterImpl& other) const @@ -13377,7 +14071,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<(const iter_impl& other) const { @@ -13387,7 +14081,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + // the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false + return false; + } switch (m_object->m_data.m_type) { @@ -13412,7 +14111,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<=(const iter_impl& other) const { @@ -13421,7 +14120,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator>(const iter_impl& other) const { @@ -13430,7 +14129,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized. */ bool operator>=(const iter_impl& other) const { @@ -13626,7 +14325,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13761,7 +14460,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13803,7 +14502,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14033,7 +14732,7 @@ class json_pointer } const char* p = s.c_str(); - char* p_end = nullptr; + char* p_end = nullptr; // NOLINT(misc-const-correctness) errno = 0; // strtoull doesn't reset errno const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) if (p == p_end // invalid input or empty string @@ -14555,7 +15254,7 @@ class json_pointer // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) { - flatten(detail::concat(reference_string, '/', std::to_string(i)), + flatten(detail::concat(reference_string, '/', std::to_string(i)), value.m_data.m_value.array->operator[](i), result); } } @@ -14574,7 +15273,7 @@ class json_pointer // iterate object and use keys as reference string for (const auto& element : *value.m_data.m_value.object) { - flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); + flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); } } break; @@ -14798,7 +15497,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14880,6 +15579,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -14890,7 +15591,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14916,7 +15617,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -15067,6 +15768,13 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + /////////////////// // binary writer // /////////////////// @@ -15651,7 +16359,7 @@ class binary_writer case value_t::binary: { // step 0: determine if the binary type has a set subtype to - // determine whether or not to use the ext or fixext types + // determine whether to use the ext or fixext types const bool use_ext = j.m_data.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length @@ -15774,11 +16482,14 @@ class binary_writer @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, - const bool use_bjdata = false) + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) { + const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3; + switch (j.type()) { case value_t::null: @@ -15868,7 +16579,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -15886,11 +16597,11 @@ class binary_writer oa->write_character(to_char_type('[')); } - if (use_type && !j.m_data.m_value.binary->empty()) + if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); - oa->write_character('U'); + oa->write_character(bjdata_draft3 ? 'B' : 'U'); } if (use_count) @@ -15909,7 +16620,7 @@ class binary_writer { for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) { - oa->write_character(to_char_type('U')); + oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -15926,7 +16637,7 @@ class binary_writer { if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) { - if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) { break; } @@ -15970,7 +16681,7 @@ class binary_writer oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -16126,7 +16837,8 @@ class binary_writer } else { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); } } @@ -16654,10 +17366,11 @@ class binary_writer /*! @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid */ - bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) { std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, - {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} }; string_t key = "_ArrayType_"; @@ -16687,10 +17400,10 @@ class binary_writer oa->write_character('#'); key = "_ArraySize_"; - write_ubjson(value.at(key), use_count, use_type, true, true); + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { @@ -16884,8 +17597,8 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2008 - 2009 Björn Hoehrmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -16910,7 +17623,7 @@ NLOHMANN_JSON_NAMESPACE_END // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2009 Florian Loitsch -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -17146,10 +17859,10 @@ boundaries compute_boundaries(FloatType value) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; - const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_plus = diyfp((2 * v.f) + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer - ? diyfp(4 * v.f - 1, v.e - 2) // (B) - : diyfp(2 * v.f - 1, v.e - 1); // (A) + ? diyfp((4 * v.f) - 1, v.e - 2) // (B) + : diyfp((2 * v.f) - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); @@ -17379,7 +18092,7 @@ inline cached_power get_cached_power_for_binary_exponent(int e) JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; - const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + const int k = ((f * 78913) / (1 << 18)) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); @@ -17857,15 +18570,15 @@ inline char* append_exponent(char* buf, int e) } else if (k < 100) { - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } else { - *buf++ = static_cast('0' + k / 100); + *buf++ = static_cast('0' + (k / 100)); k %= 100; - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } @@ -18651,7 +19364,7 @@ class serializer @param[in] x unsigned integer number to count its digits @return number of decimal digits */ - inline unsigned int count_digits(number_unsigned_t x) noexcept + unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) @@ -18934,7 +19647,7 @@ class serializer ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); - const std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + const std::size_t index = 256u + (static_cast(state) * 16u) + static_cast(type); JSON_ASSERT(index < utf8d.size()); state = utf8d[index]; return state; @@ -18960,7 +19673,7 @@ class serializer * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ - inline number_unsigned_t remove_sign(number_integer_t x) noexcept + number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) return static_cast(-(x + 1)) + 1; @@ -19005,7 +19718,7 @@ NLOHMANN_JSON_NAMESPACE_END // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -19030,7 +19743,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> - struct ordered_map : std::vector, Allocator> + struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; @@ -19345,7 +20058,7 @@ template , template using require_input_iter = typename std::enable_if::iterator_category, - std::input_iterator_tag>::value>::type; + std::input_iterator_tag>::value>::type; template> void insert(InputIt first, InputIt last) @@ -19416,9 +20129,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; - template + template friend class ::nlohmann::detail::json_sax_dom_parser; - template + template friend class ::nlohmann::detail::json_sax_dom_callback_parser; friend class ::nlohmann::detail::exception; @@ -19439,7 +20152,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec ) { return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments); + std::move(cb), allow_exceptions, ignore_comments); } private: @@ -19472,6 +20185,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -20052,10 +20767,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } - reference set_parent(reference j, std::size_t old_capacity = static_cast(-1)) + reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size()) { #if JSON_DIAGNOSTICS - if (old_capacity != static_cast(-1)) + if (old_capacity != detail::unknown_size()) { // see https://github.com/nlohmann/json/issues/2838 JSON_ASSERT(type() == value_t::array); @@ -20135,8 +20850,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) - JSONSerializer::to_json(std::declval(), - std::forward(val)))) + JSONSerializer::to_json(std::declval(), + std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); set_parents(); @@ -20149,6 +20864,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) +#if JSON_DIAGNOSTIC_POSITIONS + : start_position(val.start_pos()), + end_position(val.end_pos()) +#endif { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; @@ -20195,6 +20914,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } JSON_ASSERT(m_data.m_type == val.type()); + set_parents(); assert_invariant(); } @@ -20331,7 +21051,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > - basic_json(InputIT first, InputIT last) + basic_json(InputIT first, InputIT last) // NOLINT(performance-unnecessary-value-param) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); @@ -20446,6 +21166,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) : json_base_class_t(other) +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) + , end_position(other.end_position) +#endif { m_data.m_type = other.m_data.m_type; // check of passed value is valid @@ -20515,15 +21239,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept : json_base_class_t(std::forward(other)), - m_data(std::move(other.m_data)) + m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check + , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check +#endif { // check that passed value is valid - other.assert_invariant(false); + other.assert_invariant(false); // cppcheck-suppress[accessForwarded] // invalidate payload other.m_data.m_type = value_t::null; other.m_data.m_value = {}; +#if JSON_DIAGNOSTIC_POSITIONS + other.start_position = std::string::npos; + other.end_position = std::string::npos; +#endif + set_parents(); assert_invariant(); } @@ -20544,6 +21277,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using std::swap; swap(m_data.m_type, other.m_data.m_type); swap(m_data.m_value, other.m_data.m_value); + +#if JSON_DIAGNOSTIC_POSITIONS + swap(start_position, other.start_position); + swap(end_position, other.end_position); +#endif + json_base_class_t::operator=(std::move(other)); set_parents(); @@ -20765,13 +21504,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) @@ -20906,7 +21645,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), std::declval()))) + JSONSerializer::from_json(std::declval(), std::declval()))) { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); @@ -20948,7 +21687,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_non_default_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval()))) + JSONSerializer::from_json(std::declval()))) { return JSONSerializer::from_json(*this); } @@ -21098,7 +21837,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), v))) + JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; @@ -21250,7 +21989,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // create better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21273,7 +22012,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // create better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21418,7 +22157,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ - reference operator[](typename object_t::key_type key) + reference operator[](typename object_t::key_type key) // NOLINT(performance-unnecessary-value-param) { // implicitly convert null value to an empty object if (is_null()) @@ -21728,7 +22467,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType pos) + IteratorType erase(IteratorType pos) // NOLINT(performance-unnecessary-value-param) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) @@ -21798,7 +22537,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType first, IteratorType last) + IteratorType erase(IteratorType first, IteratorType last) // NOLINT(performance-unnecessary-value-param) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) @@ -22565,7 +23304,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template - iterator insert_iterator(const_iterator pos, Args&& ... args) + iterator insert_iterator(const_iterator pos, Args&& ... args) // NOLINT(performance-unnecessary-value-param) { iterator result(this); JSON_ASSERT(m_data.m_value.array != nullptr); @@ -22584,7 +23323,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const basic_json& val) + iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22604,14 +23343,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, basic_json&& val) + iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { return insert(pos, val); } /// @brief inserts copies of element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22631,7 +23370,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) + iterator insert(const_iterator pos, const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22662,7 +23401,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts elements from initializer list into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, initializer_list_t ilist) + iterator insert(const_iterator pos, initializer_list_t ilist) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22682,7 +23421,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into object /// @sa https://json.nlohmann.me/api/basic_json/insert/ - void insert(const_iterator first, const_iterator last) + void insert(const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) @@ -22703,6 +23442,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + set_parents(); } /// @brief updates a JSON object from another object, overwriting existing keys @@ -22714,7 +23454,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief updates a JSON object from another object, overwriting existing keys /// @sa https://json.nlohmann.me/api/basic_json/update/ - void update(const_iterator first, const_iterator last, bool merge_objects = false) + void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param) { // implicitly convert null value to an empty object if (is_null()) @@ -23315,12 +24055,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] return result; } @@ -23330,24 +24070,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved] return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(i.get(), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved] return result; } @@ -23526,6 +24266,23 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json* m_parent = nullptr; #endif +#if JSON_DIAGNOSTIC_POSITIONS + /// the start position of the value + std::size_t start_position = std::string::npos; + /// the end position of the value + std::size_t end_position = std::string::npos; + public: + constexpr std::size_t start_pos() const noexcept + { + return start_position; + } + + constexpr std::size_t end_pos() const noexcept + { + return end_position; + } +#endif + ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -23611,27 +24368,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - to_bjdata(j, result, use_size, use_type); + to_bjdata(j, result, use_size, use_type, version); return result; } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value @@ -23667,9 +24427,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23683,9 +24443,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23708,10 +24468,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23724,9 +24484,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23739,9 +24499,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23762,10 +24522,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23778,9 +24538,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23793,9 +24553,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23816,10 +24576,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23832,9 +24592,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23847,9 +24607,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23862,9 +24622,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23877,9 +24637,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23900,10 +24660,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } /// @} @@ -24004,7 +24764,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - const auto get_op = [](const std::string & op) + const auto get_op = [](const string_t& op) { if (op == "add") { @@ -24035,7 +24795,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec }; // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) + const auto operation_add = [&result](json_pointer & ptr, const basic_json & val) { // adding to the root of the target document means replacing it if (ptr.empty()) @@ -24141,15 +24901,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (const auto& val : json_patch) { // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, + const auto get_value = [&val](const string_t& op, + const string_t& member, bool string_type) -> basic_json & { // find value auto it = val.m_data.m_value.object->find(member); // context-sensitive error message - const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); + const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); // NOLINT(bugprone-unused-local-non-trivial-variable) // check if desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) @@ -24176,8 +24936,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // collect mandatory members - const auto op = get_value("op", "op", true).template get(); - const auto path = get_value(op, "path", true).template get(); + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) @@ -24203,7 +24963,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::move: { - const auto from_path = get_value("move", "from", true).template get(); + const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24220,7 +24980,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::copy: { - const auto from_path = get_value("copy", "from", true).template get(); + const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24280,7 +25040,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/diff/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, - const std::string& path = "") + const string_t& path = "") { // the patch basic_json result(value_t::array); @@ -24310,7 +25070,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', std::to_string(i))); + auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', detail::to_string(i))); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } @@ -24327,7 +25087,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.insert(result.begin() + end_index, object( { {"op", "remove"}, - {"path", detail::concat(path, '/', std::to_string(i))} + {"path", detail::concat(path, '/', detail::to_string(i))} })); ++i; } @@ -24338,7 +25098,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.push_back( { {"op", "add"}, - {"path", detail::concat(path, "/-")}, + {"path", detail::concat(path, "/-")}, {"value", target[i]} }); ++i; @@ -24353,7 +25113,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); if (target.find(it.key()) != target.end()) { @@ -24377,7 +25137,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); result.push_back( { {"op", "add"}, {"path", path_key}, @@ -24561,7 +25321,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -24592,6 +25352,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_20 + #undef JSON_HAS_CPP_23 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON @@ -24606,7 +25367,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // | | |__ | | | | | | version 3.11.3 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT diff --git a/module/classes.jar b/module/classes.jar new file mode 100644 index 0000000..c1bd6c7 Binary files /dev/null and b/module/classes.jar differ diff --git a/settings.gradle.kts b/settings.gradle.kts index 7408c2f..521ce36 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,4 +20,5 @@ dependencyResolutionManagement { } rootProject.name = "PlayIntegrityFix" -include(":app") +include(":zygisk") +include(":inject") diff --git a/zygisk/.gitignore b/zygisk/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/zygisk/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/zygisk/build.gradle.kts b/zygisk/build.gradle.kts new file mode 100644 index 0000000..e539e56 --- /dev/null +++ b/zygisk/build.gradle.kts @@ -0,0 +1,86 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "es.chiteroman.playintegrityfix" + compileSdk = 35 + + buildFeatures { + prefab = true + } + + packaging { + resources { + excludes += "**" + } + } + + defaultConfig { + minSdk = 26 + multiDexEnabled = false + + externalNativeBuild { + cmake { + abiFilters( + "arm64-v8a", + "armeabi-v7a" + ) + + arguments( + "-DCMAKE_BUILD_TYPE=MinSizeRel", + "-DANDROID_STL=none" + ) + + cFlags( + "-std=c23", + "-fvisibility=hidden", + "-fvisibility-inlines-hidden" + ) + + cppFlags( + "-std=c++26", + "-fno-exceptions", + "-fno-rtti", + "-fvisibility=hidden", + "-fvisibility-inlines-hidden" + ) + } + } + } + + buildTypes { + release { + isMinifyEnabled = true + multiDexEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + } + } +} + +dependencies { + implementation(libs.cxx) + implementation(libs.hiddenapibypass) +} + +afterEvaluate { + tasks.named("assembleRelease") { + finalizedBy( + rootProject.tasks["copyZygiskFiles"], + rootProject.tasks["zip"] + ) + } +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/zygisk/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to zygisk/proguard-rules.pro diff --git a/app/src/main/AndroidManifest.xml b/zygisk/src/main/AndroidManifest.xml similarity index 100% rename from app/src/main/AndroidManifest.xml rename to zygisk/src/main/AndroidManifest.xml diff --git a/zygisk/src/main/cpp/CMakeLists.txt b/zygisk/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..70acfc1 --- /dev/null +++ b/zygisk/src/main/cpp/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) + +project("zygisk") + +link_libraries(log) + +find_package(cxx REQUIRED CONFIG) + +link_libraries(cxx::cxx) + +add_library(zygisk SHARED zygisk.cpp) + +add_subdirectory(xdl) + +target_link_libraries(zygisk PRIVATE xdl) diff --git a/zygisk/src/main/cpp/xdl/CMakeLists.txt b/zygisk/src/main/cpp/xdl/CMakeLists.txt new file mode 100644 index 0000000..da2f351 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.22.1) +project(xdl) + +file(GLOB XDL_SRC *.c) +add_library(xdl STATIC ${XDL_SRC}) +target_compile_features(xdl PRIVATE c_std_17) +target_compile_options(xdl PRIVATE -std=c17) +target_include_directories(xdl PUBLIC include .) +#target_link_libraries(xdl log) + +if (USEASAN) + target_compile_options(xdl PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(xdl PRIVATE -fsanitize=address) +else () + target_compile_options(xdl PRIVATE -Oz -ffunction-sections -fdata-sections) + target_link_options(xdl PRIVATE -Oz -Wl,--exclude-libs,ALL -Wl,--gc-sections -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/xdl.map.txt) +endif () + +if ((${ANDROID_ABI} STREQUAL "arm64-v8a") OR (${ANDROID_ABI} STREQUAL "x86_64")) + target_link_options(xdl PRIVATE "-Wl,-z,max-page-size=16384") +endif () diff --git a/zygisk/src/main/cpp/xdl/include/xdl.h b/zygisk/src/main/cpp/xdl/include/xdl.h new file mode 100644 index 0000000..80d77a2 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/include/xdl.h @@ -0,0 +1,95 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-10-04. + +// +// xDL version: 2.2.0 +// +// xDL is an enhanced implementation of the Android DL series functions. +// For more information, documentation, and the latest version please check: +// https://github.com/hexhacking/xDL +// + +#ifndef IO_GITHUB_HEXHACKING_XDL +#define IO_GITHUB_HEXHACKING_XDL + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // same as Dl_info: + const char *dli_fname; // Pathname of shared object that contains address. + void *dli_fbase; // Address at which shared object is loaded. + const char *dli_sname; // Name of nearest symbol with address lower than addr. + void *dli_saddr; // Exact address of symbol named in dli_sname. + // added by xDL: + size_t dli_ssize; // Symbol size of nearest symbol with address lower than addr. + const ElfW(Phdr) *dlpi_phdr; // Pointer to array of ELF program headers for this object. + size_t dlpi_phnum; // Number of items in dlpi_phdr. +} xdl_info_t; + +// +// Default value for flags in xdl_open(), xdl_addr4(), and xdl_iterate_phdr(). +// +#define XDL_DEFAULT 0x00 + +// +// Enhanced dlopen() / dlclose() / dlsym(). +// +#define XDL_TRY_FORCE_LOAD 0x01 +#define XDL_ALWAYS_FORCE_LOAD 0x02 +void *xdl_open(const char *filename, int flags); +void *xdl_open2(struct dl_phdr_info *info); +void *xdl_close(void *handle); +void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size); +void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size); + +// +// Enhanced dladdr(). +// +#define XDL_NON_SYM 0x01 +int xdl_addr(void *addr, xdl_info_t *info, void **cache); +int xdl_addr4(void *addr, xdl_info_t *info, void **cache, int flags); +void xdl_addr_clean(void **cache); + +// +// Enhanced dl_iterate_phdr(). +// +#define XDL_FULL_PATHNAME 0x01 +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags); + +// +// Custom dlinfo(). +// +#define XDL_DI_DLINFO 1 // type of info: xdl_info_t +int xdl_info(void *handle, int request, void *info); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/zygisk/src/main/cpp/xdl/xdl.c b/zygisk/src/main/cpp/xdl/xdl.c new file mode 100644 index 0000000..14031c2 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl.c @@ -0,0 +1,1014 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-10-04. + +#include "xdl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdl_iterate.h" +#include "xdl_linker.h" +#include "xdl_lzma.h" +#include "xdl_util.h" + +#ifndef STT_GNU_IFUNC +#define STT_GNU_IFUNC 10 +#endif + +#ifndef __LP64__ +#define XDL_LIB_PATH "/system/lib" +#else +#define XDL_LIB_PATH "/system/lib64" +#endif + +#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx)) +#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) \ + (SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE)) + +extern __attribute((weak)) unsigned long int getauxval(unsigned long int); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" + +typedef struct xdl { + char *pathname; + uintptr_t load_bias; + const ElfW(Phdr) *dlpi_phdr; + ElfW(Half) dlpi_phnum; + + struct xdl *next; // to next xdl obj for cache in xdl_addr() + void *linker_handle; // hold handle returned by xdl_linker_force_dlopen() + + // + // (1) for searching symbols from .dynsym + // + + bool dynsym_try_load; + ElfW(Sym) *dynsym; // .dynsym + const char *dynstr; // .dynstr + + // .hash (SYSV hash for .dynstr) + struct { + const uint32_t *buckets; + uint32_t buckets_cnt; + const uint32_t *chains; + uint32_t chains_cnt; + } sysv_hash; + + // .gnu.hash (GNU hash for .dynstr) + struct { + const uint32_t *buckets; + uint32_t buckets_cnt; + const uint32_t *chains; + uint32_t symoffset; + const ElfW(Addr) *bloom; + uint32_t bloom_cnt; + uint32_t bloom_shift; + } gnu_hash; + + // + // (2) for searching symbols from .symtab + // + + bool symtab_try_load; + uintptr_t base; + + ElfW(Sym) *symtab; // .symtab + size_t symtab_cnt; + char *strtab; // .strtab + size_t strtab_sz; +} xdl_t; + +#pragma clang diagnostic pop + +static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr); +static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr); + +// load from memory +static int xdl_dynsym_load(xdl_t *self) { + // find the dynamic segment + ElfW(Dyn) *dynamic = NULL; + for (size_t i = 0; i < self->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]); + if (PT_DYNAMIC == phdr->p_type) { + dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr); + break; + } + } + if (NULL == dynamic) return -1; + + // iterate the dynamic segment + for (ElfW(Dyn) *entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) { + switch (entry->d_tag) { + case DT_SYMTAB: //.dynsym + self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr); + break; + case DT_STRTAB: //.dynstr + self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr); + break; + case DT_HASH: //.hash + self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0]; + self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1]; + self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]); + self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]); + break; + case DT_GNU_HASH: //.gnu.hash + self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0]; + self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1]; + self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]; + self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3]; + self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16); + self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt])); + self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt])); + break; + default: + break; + } + } + + if (NULL == self->dynsym || NULL == self->dynstr || + (0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) { + self->dynsym = NULL; + self->dynstr = NULL; + self->sysv_hash.buckets_cnt = 0; + self->gnu_hash.buckets_cnt = 0; + return -1; + } + + return 0; +} + +static void *xdl_read_file_to_heap(int file_fd, size_t file_sz, size_t data_offset, size_t data_len) { + if (0 == data_len) return NULL; + if (data_offset >= file_sz) return NULL; + if (data_offset + data_len > file_sz) return NULL; + + if (data_offset != (size_t)lseek(file_fd, (off_t)data_offset, SEEK_SET)) return NULL; + + void *data = malloc(data_len); + if (NULL == data) return NULL; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-statement-expression" + if ((ssize_t)data_len != XDL_UTIL_TEMP_FAILURE_RETRY(read(file_fd, data, data_len))) +#pragma clang diagnostic pop + { + free(data); + return NULL; + } + + return data; +} + +static void *xdl_read_file_to_heap_by_section(int file_fd, size_t file_sz, ElfW(Shdr) *shdr) { + return xdl_read_file_to_heap(file_fd, file_sz, (size_t)shdr->sh_offset, shdr->sh_size); +} + +static void *xdl_read_memory_to_heap(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) { + if (0 == data_len) return NULL; + if (data_offset >= mem_sz) return NULL; + if (data_offset + data_len > mem_sz) return NULL; + + void *data = malloc(data_len); + if (NULL == data) return NULL; + + memcpy(data, (void *)((uintptr_t)mem + data_offset), data_len); + return data; +} + +static void *xdl_read_memory_to_heap_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) { + return xdl_read_memory_to_heap(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size); +} + +static void *xdl_get_memory(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) { + if (0 == data_len) return NULL; + if (data_offset >= mem_sz) return NULL; + if (data_offset + data_len > mem_sz) return NULL; + + return (void *)((uintptr_t)mem + data_offset); +} + +static void *xdl_get_memory_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) { + return xdl_get_memory(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size); +} + +// load from disk and memory +static int xdl_symtab_load_from_debugdata(xdl_t *self, int file_fd, size_t file_sz, + ElfW(Shdr) *shdr_debugdata) { + void *debugdata = NULL; + ElfW(Shdr) *shdrs = NULL; + int r = -1; + + // get zipped .gnu_debugdata + uint8_t *debugdata_zip = (uint8_t *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_debugdata); + if (NULL == debugdata_zip) return -1; + + // get unzipped .gnu_debugdata + size_t debugdata_sz; + if (0 != xdl_lzma_decompress(debugdata_zip, shdr_debugdata->sh_size, (uint8_t **)&debugdata, &debugdata_sz)) + goto end; + + // get ELF header + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)debugdata; + if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end; + + // get section headers + shdrs = (ElfW(Shdr) *)xdl_read_memory_to_heap(debugdata, debugdata_sz, (size_t)ehdr->e_shoff, + ehdr->e_shentsize * ehdr->e_shnum); + if (NULL == shdrs) goto end; + + // get .shstrtab + if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end; + char *shstrtab = (char *)xdl_get_memory_by_section(debugdata, debugdata_sz, shdrs + ehdr->e_shstrndx); + if (NULL == shstrtab) goto end; + + // find .symtab & .strtab + for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) { + char *shdr_name = shstrtab + shdr->sh_name; + + if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) { + // get & check associated .strtab section + if (shdr->sh_link >= ehdr->e_shnum) continue; + ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link; + if (SHT_STRTAB != shdr_strtab->sh_type) continue; + + // get .symtab & .strtab + ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr); + if (NULL == symtab) continue; + char *strtab = (char *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr_strtab); + if (NULL == strtab) { + free(symtab); + continue; + } + + // OK + self->symtab = symtab; + self->symtab_cnt = shdr->sh_size / shdr->sh_entsize; + self->strtab = strtab; + self->strtab_sz = shdr_strtab->sh_size; + r = 0; + break; + } + } + +end: + free(debugdata_zip); + if (NULL != debugdata) free(debugdata); + if (NULL != shdrs) free(shdrs); + return r; +} + +// load from disk and memory +static int xdl_symtab_load(xdl_t *self) { + if ('[' == self->pathname[0]) return -1; + + int r = -1; + ElfW(Shdr) *shdrs = NULL; + char *shstrtab = NULL; + + // get base address + uintptr_t vaddr_min = UINTPTR_MAX; + for (size_t i = 0; i < self->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + if (vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr; + } + } + if (UINTPTR_MAX == vaddr_min) return -1; + self->base = self->load_bias + vaddr_min; + + // open file + int flags = O_RDONLY | O_CLOEXEC; + int file_fd; + if ('/' == self->pathname[0]) { + file_fd = open(self->pathname, flags); + } else { + char full_pathname[1024]; + // try the fast method + snprintf(full_pathname, sizeof(full_pathname), "%s/%s", XDL_LIB_PATH, self->pathname); + file_fd = open(full_pathname, flags); + if (file_fd < 0) { + // try the slow method + if (0 != xdl_iterate_get_full_pathname(self->base, full_pathname, sizeof(full_pathname))) return -1; + file_fd = open(full_pathname, flags); + } + } + if (file_fd < 0) return -1; + struct stat st; + if (0 != fstat(file_fd, &st)) goto end; + size_t file_sz = (size_t)st.st_size; + + // get ELF header + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)self->base; + if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end; + + // get section headers + shdrs = (ElfW(Shdr) *)xdl_read_file_to_heap(file_fd, file_sz, (size_t)ehdr->e_shoff, + ehdr->e_shentsize * ehdr->e_shnum); + if (NULL == shdrs) goto end; + + // get .shstrtab + if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end; + shstrtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdrs + ehdr->e_shstrndx); + if (NULL == shstrtab) goto end; + + // find .symtab & .strtab + for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) { + char *shdr_name = shstrtab + shdr->sh_name; + + if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) { + // get & check associated .strtab section + if (shdr->sh_link >= ehdr->e_shnum) continue; + ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link; + if (SHT_STRTAB != shdr_strtab->sh_type) continue; + + // get .symtab & .strtab + ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr); + if (NULL == symtab) continue; + char *strtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_strtab); + if (NULL == strtab) { + free(symtab); + continue; + } + + // OK + self->symtab = symtab; + self->symtab_cnt = shdr->sh_size / shdr->sh_entsize; + self->strtab = strtab; + self->strtab_sz = shdr_strtab->sh_size; + r = 0; + break; + } else if (SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shdr_name)) { + if (0 == xdl_symtab_load_from_debugdata(self, file_fd, file_sz, shdr)) { + // OK + r = 0; + break; + } + } + } + +end: + close(file_fd); + if (NULL != shdrs) free(shdrs); + if (NULL != shstrtab) free(shstrtab); + return r; +} + +static xdl_t *xdl_find_from_auxv(unsigned long type, const char *pathname) { + if (NULL == getauxval) return NULL; // API level < 18 + + uintptr_t val = (uintptr_t)getauxval(type); + if (0 == val) return NULL; + + // get base + uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val); + if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return NULL; + + // ELF info + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + ElfW(Half) dlpi_phnum = ehdr->e_phnum; + + // get bias + uintptr_t min_vaddr = UINTPTR_MAX; + for (size_t i = 0; i < dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr; + } + } + if (UINTPTR_MAX == min_vaddr || base < min_vaddr) return NULL; + uintptr_t load_bias = base - min_vaddr; + + // create xDL object + xdl_t *self; + if (NULL == (self = calloc(1, sizeof(xdl_t)))) return NULL; + if (NULL == (self->pathname = strdup(pathname))) { + free(self); + return NULL; + } + self->load_bias = load_bias; + self->dlpi_phdr = dlpi_phdr; + self->dlpi_phnum = dlpi_phnum; + self->dynsym_try_load = false; + self->symtab_try_load = false; + return self; +} + +static int xdl_find_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) { + (void)size; + + uintptr_t *pkg = (uintptr_t *)arg; + xdl_t **self = (xdl_t **)*pkg++; + const char *filename = (const char *)*pkg; + + // check load_bias + if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; + + // check pathname + if ('[' == filename[0]) { + if (0 != strcmp(info->dlpi_name, filename)) return 0; + } else if ('/' == filename[0]) { + if ('/' == info->dlpi_name[0]) { + if (0 != strcmp(info->dlpi_name, filename)) return 0; + } else { + if (!xdl_util_ends_with(filename, info->dlpi_name)) return 0; + } + } else { + if ('/' == info->dlpi_name[0]) { + if (!xdl_util_ends_with(info->dlpi_name, filename)) return 0; + } else { + if (0 != strcmp(info->dlpi_name, filename)) return 0; + } + } + + // found the target ELF + if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // return failed + if (NULL == ((*self)->pathname = strdup((const char *)info->dlpi_name))) { + free(*self); + *self = NULL; + return 1; // return failed + } + (*self)->load_bias = info->dlpi_addr; + (*self)->dlpi_phdr = info->dlpi_phdr; + (*self)->dlpi_phnum = info->dlpi_phnum; + (*self)->dynsym_try_load = false; + (*self)->symtab_try_load = false; + return 1; // return OK +} + +static xdl_t *xdl_find(const char *filename) { + // from auxv (linker, vDSO) + xdl_t *self = NULL; + if (xdl_util_ends_with(filename, XDL_UTIL_LINKER_BASENAME)) + self = xdl_find_from_auxv(AT_BASE, XDL_UTIL_LINKER_PATHNAME); + else if (xdl_util_ends_with(filename, XDL_UTIL_VDSO_BASENAME)) + self = xdl_find_from_auxv(AT_SYSINFO_EHDR, XDL_UTIL_VDSO_BASENAME); + + // from auxv (app_process) + const char *basename, *pathname; +#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__ + if (xdl_util_get_api_level() < __ANDROID_API_L__) { + basename = XDL_UTIL_APP_PROCESS_BASENAME_K; + pathname = XDL_UTIL_APP_PROCESS_PATHNAME_K; + } else +#endif + { + basename = XDL_UTIL_APP_PROCESS_BASENAME; + pathname = XDL_UTIL_APP_PROCESS_PATHNAME; + } + if (xdl_util_ends_with(filename, basename)) self = xdl_find_from_auxv(AT_PHDR, pathname); + + if (NULL != self) return self; + + // from dl_iterate_phdr + uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename}; + xdl_iterate_phdr(xdl_find_iterate_cb, pkg, XDL_DEFAULT); + return self; +} + +static void *xdl_open_always_force(const char *filename) { + // always force dlopen() + void *linker_handle = xdl_linker_force_dlopen(filename); + if (NULL == linker_handle) return NULL; + + // find + xdl_t *self = xdl_find(filename); + if (NULL == self) + dlclose(linker_handle); + else + self->linker_handle = linker_handle; + + return (void *)self; +} + +static void *xdl_open_try_force(const char *filename) { + // find + xdl_t *self = xdl_find(filename); + if (NULL != self) return (void *)self; + + // try force dlopen() + void *linker_handle = xdl_linker_force_dlopen(filename); + if (NULL == linker_handle) return NULL; + + // find again + self = xdl_find(filename); + if (NULL == self) + dlclose(linker_handle); + else + self->linker_handle = linker_handle; + + return (void *)self; +} + +void *xdl_open(const char *filename, int flags) { + if (NULL == filename) return NULL; + + if (flags & XDL_ALWAYS_FORCE_LOAD) + return xdl_open_always_force(filename); + else if (flags & XDL_TRY_FORCE_LOAD) + return xdl_open_try_force(filename); + else + return xdl_find(filename); +} + +void *xdl_open2(struct dl_phdr_info *info) { + xdl_t *self = calloc(1, sizeof(xdl_t)); + if (NULL == self) return NULL; + if (NULL == (self->pathname = strdup((const char *)info->dlpi_name))) { + free(self); + return NULL; + } + self->load_bias = info->dlpi_addr; + self->dlpi_phdr = info->dlpi_phdr; + self->dlpi_phnum = info->dlpi_phnum; + self->dynsym_try_load = false; + self->symtab_try_load = false; + return self; +} + +void *xdl_close(void *handle) { + if (NULL == handle) return NULL; + + xdl_t *self = (xdl_t *)handle; + if (NULL != self->pathname) free(self->pathname); + if (NULL != self->symtab) free(self->symtab); + if (NULL != self->strtab) free(self->strtab); + + void *linker_handle = self->linker_handle; + free(self); + return linker_handle; +} + +static uint32_t xdl_sysv_hash(const uint8_t *name) { + uint32_t h = 0, g; + + while (*name) { + h = (h << 4) + *name++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + return h; +} + +static uint32_t xdl_gnu_hash(const uint8_t *name) { + uint32_t h = 5381; + + while (*name) { + h += (h << 5) + *name++; + } + return h; +} + +static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char *sym_name) { + uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name); + + for (uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i; + i = self->sysv_hash.chains[i]) { + ElfW(Sym) *sym = self->dynsym + i; + if (0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue; + return sym; + } + + return NULL; +} + +static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char *sym_name) { + uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name); + + static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8; + size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt]; + size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) | + (size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits); + + // if at least one bit is not set, this symbol is surely missing + if ((word & mask) != mask) return NULL; + + // ignore STN_UNDEF + uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt]; + if (i < self->gnu_hash.symoffset) return NULL; + + // loop through the chain + while (1) { + ElfW(Sym) *sym = self->dynsym + i; + uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset]; + + if ((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) { + if (0 == strcmp(self->dynstr + sym->st_name, sym_name)) { + return sym; + } + } + + // chain ends with an element with the lowest bit set to 1 + if (sym_hash & (uint32_t)1) break; + + i++; + } + + return NULL; +} + +typedef struct { + unsigned long size; /** Set to sizeof(__ifunc_arg_t). */ + unsigned long hwcap; /** Set to getauxval(AT_HWCAP). */ + unsigned long hwcap2; /** Set to getauxval(AT_HWCAP2). */ +} xdl_ifunc_arg_t; + +#if defined(__aarch64__) +#define XDL_IFUNC_ARG_HWCAP (1ULL << 62) +#endif + +static void *xdl_resolve_symbol_address(xdl_t *self, ElfW(Sym) *sym, size_t *symbol_size) { + if (ELF_ST_TYPE(sym->st_info) == STT_TLS) { + return NULL; + } else if (ELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC) { + void *sym_addr = (void *)(self->load_bias + sym->st_value); + void *real_sym_addr = NULL; + if (xdl_util_get_api_level() < __ANDROID_API_R__) { + // Android [4.x, 10] + typedef void *(*ifunc_resolver_t)(void); + real_sym_addr = ((ifunc_resolver_t)sym_addr)(); + } else { + // Android [11, ...) +#if defined(__aarch64__) + if (NULL == getauxval) return NULL; + typedef void *(*ifunc_resolver_t)(uint64_t, xdl_ifunc_arg_t *); + static xdl_ifunc_arg_t arg; + static bool initialized = false; + if (!initialized) { + arg.size = sizeof(xdl_ifunc_arg_t); + arg.hwcap = getauxval(AT_HWCAP); + arg.hwcap2 = getauxval(AT_HWCAP2); + initialized = true; + } + real_sym_addr = ((ifunc_resolver_t)sym_addr)(arg.hwcap | XDL_IFUNC_ARG_HWCAP, &arg); +#elif defined(__arm__) + if (NULL == getauxval) return NULL; + typedef void *(*ifunc_resolver_t)(unsigned long); + static unsigned long hwcap; + static bool initialized = false; + if (!initialized) { + hwcap = getauxval(AT_HWCAP); + initialized = true; + } + real_sym_addr = ((ifunc_resolver_t)sym_addr)(hwcap); +#else + typedef void *(*ifunc_resolver_t)(void); + real_sym_addr = ((ifunc_resolver_t)sym_addr)(); +#endif + } + if (NULL != symbol_size && NULL != real_sym_addr) { + ElfW(Sym) *real_sym = xdl_sym_by_addr(self, real_sym_addr); + if (NULL == real_sym) real_sym = xdl_dsym_by_addr(self, real_sym_addr); + if (NULL != real_sym) *symbol_size = real_sym->st_size; + } + return real_sym_addr; + } else { + if (NULL != symbol_size) *symbol_size = sym->st_size; + return (void *)(self->load_bias + sym->st_value); + } +} + +void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size) { + if (NULL == handle || NULL == symbol) return NULL; + if (NULL != symbol_size) *symbol_size = 0; + + xdl_t *self = (xdl_t *)handle; + + // load .dynsym only once + if (!self->dynsym_try_load) { + self->dynsym_try_load = true; + if (0 != xdl_dynsym_load(self)) return NULL; + } + + // find symbol + if (NULL == self->dynsym) return NULL; + ElfW(Sym) *sym = NULL; + if (self->gnu_hash.buckets_cnt > 0) { + // use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1) + sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol); + } + if (NULL == sym && self->sysv_hash.buckets_cnt > 0) { + // use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1) + sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol); + } + if (NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL; + + return xdl_resolve_symbol_address(self, sym, symbol_size); +} + +// clang-format off +/* + * For internal symbols in .symtab, LLVM may add some suffixes (for example for thinLTO). + * The format of the suffix is: ".xxxx.[hash]". LLVM may add multiple suffixes at once. + * The symbol name after removing these all suffixes is called canonical name. + * + * Because the hash part in the suffix may change when recompiled, so here we only match + * the canonical name. + * + * IN ADDITION: According to C/C++ syntax, it is illegal for a function name to contain + * dot character('.'), either in the middle or at the end. + * + * samples: + * + * symbol name in .symtab lookup is match + * ---------------------- ---------------- -------- + * abcd abc N + * abcd abcde N + * abcd abcd Y + * abcd.llvm.10190306339727611508 abc N + * abcd.llvm.10190306339727611508 abcd Y + * abcd.llvm.10190306339727611508 abcd. N + * abcd.llvm.10190306339727611508 abcd.llvm Y + * abcd.llvm.10190306339727611508 abcd.llvm. N + * abcd.__uniq.513291356003753 abcd.__uniq.51329 N + * abcd.__uniq.513291356003753 abcd.__uniq.513291356003753 Y + */ +// clang-format on +static inline bool xdl_dsym_is_match(const char *str, const char *sym, size_t sym_len) { + size_t str_len = strlen(str); + if (0 == str_len) return false; + + if (str_len < sym_len) { + return false; + } else { + bool sym_len_match = (0 == memcmp(str, sym, sym_len)); + if (str_len == sym_len) + return sym_len_match; + else // str_len > sym_len + return sym_len_match && (str[sym_len] == '.' || str[sym_len] == '$'); + } +} + +void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size) { + if (NULL == handle || NULL == symbol) return NULL; + if (NULL != symbol_size) *symbol_size = 0; + + xdl_t *self = (xdl_t *)handle; + + // load .symtab only once + if (!self->symtab_try_load) { + self->symtab_try_load = true; + if (0 != xdl_symtab_load(self)) return NULL; + } + + // find symbol + if (NULL == self->symtab) return NULL; + size_t symbol_len = strlen(symbol); + for (size_t i = 0; i < self->symtab_cnt; i++) { + ElfW(Sym) *sym = self->symtab + i; + + if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue; + // if (0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue; + if (!xdl_dsym_is_match(self->strtab + sym->st_name, symbol, symbol_len)) continue; + + if (NULL != symbol_size) *symbol_size = sym->st_size; + return (void *)(self->load_bias + sym->st_value); + } + + return NULL; +} + +static bool xdl_elf_is_match(uintptr_t load_bias, const ElfW(Phdr) *dlpi_phdr, ElfW(Half) dlpi_phnum, + uintptr_t addr) { + if (addr < load_bias) return false; + + uintptr_t vaddr = addr - load_bias; + for (size_t i = 0; i < dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(dlpi_phdr[i]); + if (PT_LOAD != phdr->p_type) continue; + + if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true; + } + + return false; +} + +static int xdl_open_by_addr_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) { + (void)size; + + uintptr_t *pkg = (uintptr_t *)arg; + xdl_t **self = (xdl_t **)*pkg++; + uintptr_t addr = *pkg; + + if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue + + if (xdl_elf_is_match(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, addr)) { + // found the target ELF + if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // failed + if (NULL == ((*self)->pathname = strdup((const char *)info->dlpi_name))) { + free(*self); + *self = NULL; + return 1; // failed + } + (*self)->load_bias = info->dlpi_addr; + (*self)->dlpi_phdr = info->dlpi_phdr; + (*self)->dlpi_phnum = info->dlpi_phnum; + (*self)->dynsym_try_load = false; + (*self)->symtab_try_load = false; + return 1; // OK + } + + return 0; // continue +} + +static void *xdl_open_by_addr(void *addr) { + if (NULL == addr) return NULL; + + xdl_t *self = NULL; + uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)addr}; + xdl_iterate_phdr(xdl_open_by_addr_iterate_cb, pkg, XDL_DEFAULT); + + return (void *)self; +} + +static bool xdl_sym_is_match(ElfW(Sym) *sym, uintptr_t offset, bool is_symtab) { + if (is_symtab) { + if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) return false; + } else { + if (!XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return false; + } + if (ELF_ST_TYPE(sym->st_info) == STT_TLS) return false; + + // For thumb instructions, "st_value" is an odd number, and the instructions are stored + // in the range: [st_value - 1, st_value - 1 + st_size). + // NOTE: The dladdr() implementation in the Android bionic linker does NOT fix this for + // thumb and is therefore incorrect. + uintptr_t sym_st_value_fixed = sym->st_value; +#if defined(__arm__) && defined(__thumb__) +#define CLEAR_BIT0(addr) ((addr)&0xFFFFFFFE) + sym_st_value_fixed = CLEAR_BIT0(sym->st_value); +#endif + + return offset >= sym_st_value_fixed && offset < sym_st_value_fixed + sym->st_size; +} + +static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr) { + xdl_t *self = (xdl_t *)handle; + + // load .dynsym only once + if (!self->dynsym_try_load) { + self->dynsym_try_load = true; + if (0 != xdl_dynsym_load(self)) return NULL; + } + + // find symbol + if (NULL == self->dynsym) return NULL; + uintptr_t offset = (uintptr_t)addr - self->load_bias; + if (self->gnu_hash.buckets_cnt > 0) { + const uint32_t *chains_all = self->gnu_hash.chains - self->gnu_hash.symoffset; + for (size_t i = 0; i < self->gnu_hash.buckets_cnt; i++) { + uint32_t n = self->gnu_hash.buckets[i]; + if (n < self->gnu_hash.symoffset) continue; + do { + ElfW(Sym) *sym = self->dynsym + n; + if (xdl_sym_is_match(sym, offset, false)) return sym; + } while ((chains_all[n++] & 1) == 0); + } + } else if (self->sysv_hash.chains_cnt > 0) { + for (size_t i = 0; i < self->sysv_hash.chains_cnt; i++) { + ElfW(Sym) *sym = self->dynsym + i; + if (xdl_sym_is_match(sym, offset, false)) return sym; + } + } + + return NULL; +} + +static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr) { + xdl_t *self = (xdl_t *)handle; + + // load .symtab only once + if (!self->symtab_try_load) { + self->symtab_try_load = true; + if (0 != xdl_symtab_load(self)) return NULL; + } + + // find symbol + if (NULL == self->symtab) return NULL; + uintptr_t offset = (uintptr_t)addr - self->load_bias; + for (size_t i = 0; i < self->symtab_cnt; i++) { + ElfW(Sym) *sym = self->symtab + i; + if (xdl_sym_is_match(sym, offset, true)) return sym; + } + + return NULL; +} + +int xdl_addr(void *addr, xdl_info_t *info, void **cache) { + return xdl_addr4(addr, info, cache, XDL_DEFAULT); +} + +int xdl_addr4(void *addr, xdl_info_t *info, void **cache, int flags) { + if (NULL == addr || NULL == info || NULL == cache) return 0; + + memset(info, 0, sizeof(Dl_info)); + + // find handle from cache + xdl_t *handle = NULL; + for (handle = *((xdl_t **)cache); NULL != handle; handle = handle->next) + if (xdl_elf_is_match(handle->load_bias, handle->dlpi_phdr, handle->dlpi_phnum, (uintptr_t)addr)) break; + + // create new handle, save handle to cache + if (NULL == handle) { + handle = (xdl_t *)xdl_open_by_addr(addr); + if (NULL == handle) return 0; + handle->next = *(xdl_t **)cache; + *(xdl_t **)cache = handle; + } + + // we have at least: load_bias, pathname, dlpi_phdr, dlpi_phnum + info->dli_fbase = (void *)handle->load_bias; + info->dli_fname = handle->pathname; + info->dli_sname = NULL; + info->dli_saddr = 0; + info->dli_ssize = 0; + info->dlpi_phdr = handle->dlpi_phdr; + info->dlpi_phnum = (size_t)handle->dlpi_phnum; + + // keep looking for: symbol name, symbol offset, symbol size + if (!(flags & XDL_NON_SYM)) { + ElfW(Sym) *sym; + if (NULL != (sym = xdl_sym_by_addr((void *)handle, addr))) { + info->dli_sname = handle->dynstr + sym->st_name; + info->dli_saddr = (void *)(handle->load_bias + sym->st_value); + info->dli_ssize = sym->st_size; + } else if (NULL != (sym = xdl_dsym_by_addr((void *)handle, addr))) { + info->dli_sname = handle->strtab + sym->st_name; + info->dli_saddr = (void *)(handle->load_bias + sym->st_value); + info->dli_ssize = sym->st_size; + } + } + + return 1; +} + +void xdl_addr_clean(void **cache) { + if (NULL == cache) return; + + xdl_t *handle = *((xdl_t **)cache); + while (NULL != handle) { + xdl_t *tmp = handle; + handle = handle->next; + xdl_close(tmp); + } + *cache = NULL; +} + +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) { + if (NULL == callback) return 0; + + return xdl_iterate_phdr_impl(callback, data, flags); +} + +int xdl_info(void *handle, int request, void *info) { + if (NULL == handle || XDL_DI_DLINFO != request || NULL == info) return -1; + + xdl_t *self = (xdl_t *)handle; + xdl_info_t *dlinfo = (xdl_info_t *)info; + + dlinfo->dli_fbase = (void *)self->load_bias; + dlinfo->dli_fname = self->pathname; + dlinfo->dli_sname = NULL; + dlinfo->dli_saddr = 0; + dlinfo->dli_ssize = 0; + dlinfo->dlpi_phdr = self->dlpi_phdr; + dlinfo->dlpi_phnum = (size_t)self->dlpi_phnum; + return 0; +} diff --git a/zygisk/src/main/cpp/xdl/xdl.map.txt b/zygisk/src/main/cpp/xdl/xdl.map.txt new file mode 100644 index 0000000..02558da --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl.map.txt @@ -0,0 +1,16 @@ +{ + global: + xdl_open; + xdl_open2; + xdl_close; + xdl_sym; + xdl_dsym; + xdl_addr; + xdl_addr4; + xdl_addr_clean; + xdl_iterate_phdr; + xdl_info; + + local: + *; +}; diff --git a/zygisk/src/main/cpp/xdl/xdl_iterate.c b/zygisk/src/main/cpp/xdl/xdl_iterate.c new file mode 100644 index 0000000..1bcc3f6 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_iterate.c @@ -0,0 +1,297 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-10-04. + +#include "xdl_iterate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdl.h" +#include "xdl_linker.h" +#include "xdl_util.h" + +/* + * ========================================================================================================= + * API-LEVEL ANDROID-VERSION SOLUTION + * ========================================================================================================= + * 16 4.1 /proc/self/maps + * 17 4.2 /proc/self/maps + * 18 4.3 /proc/self/maps + * 19 4.4 /proc/self/maps + * 20 4.4W /proc/self/maps + * --------------------------------------------------------------------------------------------------------- + * 21 5.0 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3) + * 22 5.1 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3) + * --------------------------------------------------------------------------------------------------------- + * 23 >= 6.0 dl_iterate_phdr() + linker/linker64 from getauxval(3) + * ========================================================================================================= + */ + +extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *); +extern __attribute((weak)) unsigned long int getauxval(unsigned long int); + +static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) { + uintptr_t min_vaddr = UINTPTR_MAX; + for (size_t i = 0; i < info->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr; + } + } + return min_vaddr; +} + +static int xdl_iterate_open_or_rewind_maps(FILE **maps) { + if (NULL == *maps) { + *maps = fopen("/proc/self/maps", "r"); + if (NULL == *maps) return -1; + } else + rewind(*maps); + + return 0; +} + +static int xdl_iterate_get_pathname_from_maps(uintptr_t base, char *buf, size_t buf_len, FILE **maps) { + // open or rewind maps-file + if (0 != xdl_iterate_open_or_rewind_maps(maps)) return -1; // failed + + char line[1024]; + while (fgets(line, sizeof(line), *maps)) { + // check base address + uintptr_t start, end; + if (2 != sscanf(line, "%" SCNxPTR "-%" SCNxPTR " r", &start, &end)) continue; + if (base < start) break; // failed + if (base >= end) continue; + + // get pathname + char *pathname = strchr(line, '/'); + if (NULL == pathname) break; // failed + xdl_util_trim_ending(pathname); + + // found it + strlcpy(buf, pathname, buf_len); + return 0; // OK + } + + return -1; // failed +} + +static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) { + uintptr_t *pkg = (uintptr_t *)arg; + xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++; + void *cb_arg = (void *)*pkg++; + FILE **maps = (FILE **)*pkg++; + uintptr_t linker_load_bias = *pkg++; + int flags = (int)*pkg; + + // ignore invalid ELF + if (0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0; + + // ignore linker if we have returned it already + if (linker_load_bias == info->dlpi_addr) return 0; + + struct dl_phdr_info info_fixed; + info_fixed.dlpi_addr = info->dlpi_addr; + info_fixed.dlpi_name = info->dlpi_name; + info_fixed.dlpi_phdr = info->dlpi_phdr; + info_fixed.dlpi_phnum = info->dlpi_phnum; + info = &info_fixed; + + // fix dlpi_phdr & dlpi_phnum (from memory) + if (NULL == info->dlpi_phdr || 0 == info->dlpi_phnum) { + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)info->dlpi_addr; + info->dlpi_phdr = (ElfW(Phdr) *)(info->dlpi_addr + ehdr->e_phoff); + info->dlpi_phnum = ehdr->e_phnum; + } + + // fix dlpi_name (from /proc/self/maps) + if ('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) { + // get base address + uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info); + if (UINTPTR_MAX == min_vaddr) return 0; // ignore this ELF + uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr); + + char buf[1024]; + if (0 != xdl_iterate_get_pathname_from_maps(base, buf, sizeof(buf), maps)) return 0; // ignore this ELF + + info->dlpi_name = (const char *)buf; + } + + // callback + return cb(info, size, cb_arg); +} + +static uintptr_t xdl_iterate_get_linker_base(void) { + if (NULL == getauxval) return 0; + + uintptr_t base = (uintptr_t)getauxval(AT_BASE); + if (0 == base) return 0; + if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return 0; + + return base; +} + +static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base, + const char *pathname, uintptr_t *load_bias) { + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + + struct dl_phdr_info info; + info.dlpi_name = pathname; + info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + info.dlpi_phnum = ehdr->e_phnum; + + // get load bias + uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info); + if (UINTPTR_MAX == min_vaddr) return 0; // ignore invalid ELF + info.dlpi_addr = (ElfW(Addr))(base - min_vaddr); + if (NULL != load_bias) *load_bias = info.dlpi_addr; + + return cb(&info, sizeof(struct dl_phdr_info), cb_arg); +} + +static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) { + if (NULL == dl_iterate_phdr) return 0; + + int api_level = xdl_util_get_api_level(); + FILE *maps = NULL; + int r; + + // dl_iterate_phdr(3) does NOT contain linker/linker64 when Android version < 8.1 (API level 27). + // Here we always try to get linker base address from auxv. + uintptr_t linker_load_bias = 0; + uintptr_t linker_base = xdl_iterate_get_linker_base(); + if (0 != linker_base) { + if (0 != + (r = xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_UTIL_LINKER_PATHNAME, &linker_load_bias))) + return r; + } + + // for other ELF + uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags}; + if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_lock(); + r = dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg); + if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_unlock(); + + if (NULL != maps) fclose(maps); + return r; +} + +#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__ +static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) { + FILE *maps = fopen("/proc/self/maps", "r"); + if (NULL == maps) return 0; + + int r = 0; + char buf1[1024], buf2[1024]; + char *line = buf1; + uintptr_t prev_base = 0; + bool try_next_line = false; + + while (fgets(line, sizeof(buf1), maps)) { + // Try to find an ELF which loaded by linker. + uintptr_t base, offset; + char exec; + if (3 != sscanf(line, "%" SCNxPTR "-%*" SCNxPTR " r%*c%cp %" SCNxPTR " ", &base, &exec, &offset)) + goto clean; + + if ('-' == exec && 0 == offset) { + // r--p + prev_base = base; + line = (line == buf1 ? buf2 : buf1); + try_next_line = true; + continue; + } else if (exec == 'x') { + // r-xp + char *pathname = NULL; + if (try_next_line && 0 != offset) { + char *prev = (line == buf1 ? buf2 : buf1); + char *prev_pathname = strchr(prev, '/'); + if (NULL == prev_pathname) goto clean; + + pathname = strchr(line, '/'); + if (NULL == pathname) goto clean; + + xdl_util_trim_ending(prev_pathname); + xdl_util_trim_ending(pathname); + if (0 != strcmp(prev_pathname, pathname)) goto clean; + + // we found the line with r-xp in the next line + base = prev_base; + offset = 0; + } + + if (0 != offset) goto clean; + + // get pathname + if (NULL == pathname) { + pathname = strchr(line, '/'); + if (NULL == pathname) goto clean; + xdl_util_trim_ending(pathname); + } + + if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) goto clean; + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + struct dl_phdr_info info; + info.dlpi_name = pathname; + info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + info.dlpi_phnum = ehdr->e_phnum; + + // callback + if (0 != (r = xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL))) break; + } + + clean: + try_next_line = false; + } + + fclose(maps); + return r; +} +#endif + +int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) { + // iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86) +#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__ + if (xdl_util_get_api_level() < __ANDROID_API_L__) return xdl_iterate_by_maps(cb, cb_arg); +#endif + + // iterate by dl_iterate_phdr() + return xdl_iterate_by_linker(cb, cb_arg, flags); +} + +int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len) { + FILE *maps = NULL; + int r = xdl_iterate_get_pathname_from_maps(base, buf, buf_len, &maps); + if (NULL != maps) fclose(maps); + return r; +} diff --git a/zygisk/src/main/cpp/xdl/xdl_iterate.h b/zygisk/src/main/cpp/xdl/xdl_iterate.h new file mode 100644 index 0000000..b0bfa94 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_iterate.h @@ -0,0 +1,43 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_GITHUB_HEXHACKING_XDL_ITERATE +#define IO_GITHUB_HEXHACKING_XDL_ITERATE + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg); +int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags); + +int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/zygisk/src/main/cpp/xdl/xdl_linker.c b/zygisk/src/main/cpp/xdl/xdl_linker.c new file mode 100644 index 0000000..687b3db --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_linker.c @@ -0,0 +1,231 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2021-02-21. + +#include "xdl_linker.h" + +#include +#include +#include +#include + +#include "xdl.h" +#include "xdl_iterate.h" +#include "xdl_util.h" + +#define XDL_LINKER_SYM_MUTEX "__dl__ZL10g_dl_mutex" +#define XDL_LINKER_SYM_DLOPEN_EXT_N "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv" +#define XDL_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv" +#define XDL_LINKER_SYM_DLOPEN_O "__dl__Z8__dlopenPKciPKv" +#define XDL_LINKER_SYM_LOADER_DLOPEN_P "__loader_dlopen" + +#ifndef __LP64__ +#define LIB "lib" +#else +#define LIB "lib64" +#endif + +typedef void *(*xdl_linker_dlopen_n_t)(const char *, int, const void *, void *); +typedef void *(*xdl_linker_dlopen_o_t)(const char *, int, const void *); + +static pthread_mutex_t *xdl_linker_mutex = NULL; +static void *xdl_linker_dlopen = NULL; + +typedef enum { MATCH_PREFIX, MATCH_SUFFIX } xdl_linker_match_type_t; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +typedef struct { + xdl_linker_match_type_t type; + const char *value; +} xdl_linker_match_t; +#pragma clang diagnostic pop + +typedef struct { + void *addr; + xdl_linker_match_t *matches; + size_t matches_cursor; +} xdl_linker_caller_t; + +// https://source.android.com/docs/core/architecture/vndk/linker-namespace +// The following rules are loose and incomplete, you can add more according to your needs. +static xdl_linker_match_t xdl_linker_match_default[] = {{MATCH_SUFFIX, "/libc.so"}}; +static xdl_linker_match_t xdl_linker_match_art[] = {{MATCH_SUFFIX, "/libart.so"}}; +static xdl_linker_match_t xdl_linker_match_sphal[] = {{MATCH_PREFIX, "/vendor/" LIB "/egl/"}, + {MATCH_PREFIX, "/vendor/" LIB "/hw/"}, + {MATCH_PREFIX, "/vendor/" LIB "/"}, + {MATCH_PREFIX, "/odm/" LIB "/"}}; +static xdl_linker_match_t xdl_linker_match_vndk[] = {{MATCH_PREFIX, "/apex/com.android.vndk.v"}, + {MATCH_PREFIX, "/vendor/" LIB "/vndk-sp/"}, + {MATCH_PREFIX, "/odm/" LIB "/vndk-sp/"}}; +static xdl_linker_caller_t xdl_linker_callers[] = { + {NULL, xdl_linker_match_default, sizeof(xdl_linker_match_default) / sizeof(xdl_linker_match_t)}, + {NULL, xdl_linker_match_art, sizeof(xdl_linker_match_art) / sizeof(xdl_linker_match_t)}, + {NULL, xdl_linker_match_sphal, sizeof(xdl_linker_match_sphal) / sizeof(xdl_linker_match_t)}, + {NULL, xdl_linker_match_vndk, sizeof(xdl_linker_match_vndk) / sizeof(xdl_linker_match_t)}}; + +static void xdl_linker_init_symbols_impl(void) { + // find linker from: /proc/self/maps (API level < 18) or getauxval (API level >= 18) + void *handle = xdl_open(XDL_UTIL_LINKER_BASENAME, XDL_DEFAULT); + if (NULL == handle) return; + + int api_level = xdl_util_get_api_level(); + if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) { + // == Android 5.x + xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL); + } else if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) { + // == Android 7.x + xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_EXT_N, NULL); + if (NULL == xdl_linker_dlopen) { + xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DO_DLOPEN_N, NULL); + xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL); + } + } else if (__ANDROID_API_O__ == api_level || __ANDROID_API_O_MR1__ == api_level) { + // == Android 8.x + xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_O, NULL); + } else if (api_level >= __ANDROID_API_P__) { + // >= Android 9.0 + xdl_linker_dlopen = xdl_sym(handle, XDL_LINKER_SYM_LOADER_DLOPEN_P, NULL); + } + + xdl_close(handle); +} + +static void xdl_linker_init_symbols(void) { + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static bool inited = false; + if (!inited) { + pthread_mutex_lock(&lock); + if (!inited) { + xdl_linker_init_symbols_impl(); + inited = true; + } + pthread_mutex_unlock(&lock); + } +} + +void xdl_linker_lock(void) { + xdl_linker_init_symbols(); + + if (NULL != xdl_linker_mutex) pthread_mutex_lock(xdl_linker_mutex); +} + +void xdl_linker_unlock(void) { + if (NULL != xdl_linker_mutex) pthread_mutex_unlock(xdl_linker_mutex); +} + +static void *xdl_linker_get_caller_addr(struct dl_phdr_info *info) { + for (size_t i = 0; i < info->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + return (void *)(info->dlpi_addr + phdr->p_vaddr); + } + } + return NULL; +} + +static void xdl_linker_save_caller_addr(struct dl_phdr_info *info, xdl_linker_caller_t *caller, + size_t cursor) { + void *addr = xdl_linker_get_caller_addr(info); + if (NULL != addr) { + caller->addr = addr; + caller->matches_cursor = cursor; + } +} + +static int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) { + (void)size, (void)arg; + if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue + + int ret = 1; // OK + for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) { + xdl_linker_caller_t *caller = &xdl_linker_callers[i]; + for (size_t j = 0; j < caller->matches_cursor; j++) { + xdl_linker_match_t *match = &caller->matches[j]; + if (MATCH_PREFIX == match->type) { + if (xdl_util_starts_with(info->dlpi_name, match->value)) { + xdl_linker_save_caller_addr(info, caller, j); + } + } else if (MATCH_SUFFIX == match->type) { + if (xdl_util_ends_with(info->dlpi_name, match->value)) { + xdl_linker_save_caller_addr(info, caller, j); + } + } + } + if (NULL == caller->addr || 0 != caller->matches_cursor) ret = 0; // continue + } + return ret; +} + +static void xdl_linker_init_caller_addr_impl(void) { + xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, NULL, XDL_DEFAULT); +} + +static void xdl_linker_init_caller_addr(void) { + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static bool inited = false; + if (!inited) { + pthread_mutex_lock(&lock); + if (!inited) { + xdl_linker_init_caller_addr_impl(); + inited = true; + } + pthread_mutex_unlock(&lock); + } +} + +void *xdl_linker_force_dlopen(const char *filename) { + int api_level = xdl_util_get_api_level(); + + if (api_level <= __ANDROID_API_M__) { + // <= Android 6.0 + return dlopen(filename, RTLD_NOW); + } else { + xdl_linker_init_symbols(); + if (NULL == xdl_linker_dlopen) return NULL; + xdl_linker_init_caller_addr(); + + void *handle = NULL; + if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) { + // == Android 7.x + xdl_linker_lock(); + for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) { + xdl_linker_caller_t *caller = &xdl_linker_callers[i]; + if (NULL != caller->addr) { + handle = ((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, caller->addr); + if (NULL != handle) break; + } + } + xdl_linker_unlock(); + } else { + // >= Android 8.0 + for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) { + xdl_linker_caller_t *caller = &xdl_linker_callers[i]; + if (NULL != caller->addr) { + handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, caller->addr); + if (NULL != handle) break; + } + } + } + return handle; + } +} diff --git a/zygisk/src/main/cpp/xdl/xdl_linker.h b/zygisk/src/main/cpp/xdl/xdl_linker.h new file mode 100644 index 0000000..2d40cf4 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_linker.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2021-02-21. + +#ifndef IO_GITHUB_HEXHACKING_XDL_LINKER +#define IO_GITHUB_HEXHACKING_XDL_LINKER + +#ifdef __cplusplus +extern "C" { +#endif + +void xdl_linker_lock(void); +void xdl_linker_unlock(void); + +void *xdl_linker_force_dlopen(const char *filename); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/zygisk/src/main/cpp/xdl/xdl_lzma.c b/zygisk/src/main/cpp/xdl/xdl_lzma.c new file mode 100644 index 0000000..aff6d48 --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_lzma.c @@ -0,0 +1,187 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-11-08. + +#include "xdl_lzma.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdl.h" +#include "xdl_util.h" + +// LZMA library pathname & symbol names +#ifndef __LP64__ +#define XDL_LZMA_PATHNAME "/system/lib/liblzma.so" +#else +#define XDL_LZMA_PATHNAME "/system/lib64/liblzma.so" +#endif +#define XDL_LZMA_SYM_CRCGEN "CrcGenerateTable" +#define XDL_LZMA_SYM_CRC64GEN "Crc64GenerateTable" +#define XDL_LZMA_SYM_CONSTRUCT "XzUnpacker_Construct" +#define XDL_LZMA_SYM_ISFINISHED "XzUnpacker_IsStreamWasFinished" +#define XDL_LZMA_SYM_FREE "XzUnpacker_Free" +#define XDL_LZMA_SYM_CODE "XzUnpacker_Code" + +// LZMA data type definition +#define SZ_OK 0 +typedef struct ISzAlloc ISzAlloc; +typedef const ISzAlloc *ISzAllocPtr; +struct ISzAlloc { + void *(*Alloc)(ISzAllocPtr p, size_t size); + void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */ +}; +typedef enum { + CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */ + CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + CODER_STATUS_NOT_FINISHED, /* stream was not finished */ + CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */ +} ECoderStatus; +typedef enum { + CODER_FINISH_ANY, /* finish at any point */ + CODER_FINISH_END /* block must be finished at the end */ +} ECoderFinishMode; + +// LZMA function type definition +typedef void (*xdl_lzma_crcgen_t)(void); +typedef void (*xdl_lzma_crc64gen_t)(void); +typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr); +typedef int (*xdl_lzma_isfinished_t)(const void *); +typedef void (*xdl_lzma_free_t)(void *); +typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode, + ECoderStatus *); +typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int, + ECoderFinishMode, ECoderStatus *); + +// LZMA function pointor +static xdl_lzma_construct_t xdl_lzma_construct = NULL; +static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL; +static xdl_lzma_free_t xdl_lzma_free = NULL; +static void *xdl_lzma_code = NULL; + +// LZMA init +static void xdl_lzma_init(void) { + void *lzma = xdl_open(XDL_LZMA_PATHNAME, XDL_TRY_FORCE_LOAD); + if (NULL == lzma) return; + + xdl_lzma_crcgen_t crcgen = NULL; + xdl_lzma_crc64gen_t crc64gen = NULL; + if (NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRCGEN, NULL))) goto end; + if (NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRC64GEN, NULL))) goto end; + if (NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_LZMA_SYM_CONSTRUCT, NULL))) + goto end; + if (NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_LZMA_SYM_ISFINISHED, NULL))) + goto end; + if (NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_LZMA_SYM_FREE, NULL))) goto end; + if (NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_LZMA_SYM_CODE, NULL))) goto end; + crcgen(); + crc64gen(); + +end: + xdl_close(lzma); +} + +// LZMA internal alloc / free +static void *xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) { + (void)p; + return malloc(size); +} +static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) { + (void)p; + free(address); +} + +int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) { + size_t src_offset = 0; + size_t dst_offset = 0; + size_t src_remaining; + size_t dst_remaining; + ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free}; + long long state[4096 / sizeof(long long)]; // must be enough, 8-bit aligned + ECoderStatus status; + int api_level = xdl_util_get_api_level(); + + // init and check + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static bool inited = false; + if (!inited) { + pthread_mutex_lock(&lock); + if (!inited) { + xdl_lzma_init(); + inited = true; + } + pthread_mutex_unlock(&lock); + } + if (NULL == xdl_lzma_code) return -1; + + xdl_lzma_construct(&state, &alloc); + + *dst_size = 2 * src_size; + *dst = NULL; + do { + *dst_size *= 2; + if (NULL == (*dst = realloc(*dst, *dst_size))) { + xdl_lzma_free(&state); + return -1; + } + + src_remaining = src_size - src_offset; + dst_remaining = *dst_size - dst_offset; + + int result; + if (api_level >= __ANDROID_API_Q__) { + xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code; + result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1, + CODER_FINISH_ANY, &status); + } else { + xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code; + result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, + CODER_FINISH_ANY, &status); + } + if (SZ_OK != result) { + free(*dst); + xdl_lzma_free(&state); + return -1; + } + + src_offset += src_remaining; + dst_offset += dst_remaining; + } while (status == CODER_STATUS_NOT_FINISHED); + + xdl_lzma_free(&state); + + if (!xdl_lzma_isfinished(&state)) { + free(*dst); + return -1; + } + + *dst_size = dst_offset; + *dst = realloc(*dst, *dst_size); + return 0; +} diff --git a/zygisk/src/main/cpp/xdl/xdl_lzma.h b/zygisk/src/main/cpp/xdl/xdl_lzma.h new file mode 100644 index 0000000..b1eef1d --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_lzma.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-11-08. + +#ifndef IO_GITHUB_HEXHACKING_XDL_LZMA +#define IO_GITHUB_HEXHACKING_XDL_LZMA + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/zygisk/src/main/cpp/xdl/xdl_util.c b/zygisk/src/main/cpp/xdl/xdl_util.c new file mode 100644 index 0000000..96f15dd --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_util.c @@ -0,0 +1,95 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-10-04. + +#include "xdl_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool xdl_util_starts_with(const char *str, const char *start) { + while (*str && *str == *start) { + str++; + start++; + } + + return '\0' == *start; +} + +bool xdl_util_ends_with(const char *str, const char *ending) { + size_t str_len = strlen(str); + size_t ending_len = strlen(ending); + + if (ending_len > str_len) return false; + + return 0 == strcmp(str + (str_len - ending_len), ending); +} + +size_t xdl_util_trim_ending(char *start) { + char *end = start + strlen(start); + while (start < end && isspace((int)(*(end - 1)))) { + end--; + *end = '\0'; + } + return (size_t)(end - start); +} + +static int xdl_util_get_api_level_from_build_prop(void) { + char buf[128]; + int api_level = -1; + + FILE *fp = fopen("/system/build.prop", "r"); + if (NULL == fp) goto end; + + while (fgets(buf, sizeof(buf), fp)) { + if (xdl_util_starts_with(buf, "ro.build.version.sdk=")) { + api_level = atoi(buf + 21); + break; + } + } + fclose(fp); + +end: + return (api_level > 0) ? api_level : -1; +} + +int xdl_util_get_api_level(void) { + static int xdl_util_api_level = -1; + + if (xdl_util_api_level < 0) { + int api_level = android_get_device_api_level(); + if (api_level < 0) + api_level = xdl_util_get_api_level_from_build_prop(); // compatible with unusual models + if (api_level < __ANDROID_API_J__) api_level = __ANDROID_API_J__; + + __atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST); + } + + return xdl_util_api_level; +} diff --git a/zygisk/src/main/cpp/xdl/xdl_util.h b/zygisk/src/main/cpp/xdl/xdl_util.h new file mode 100644 index 0000000..8641d1a --- /dev/null +++ b/zygisk/src/main/cpp/xdl/xdl_util.h @@ -0,0 +1,71 @@ +// Copyright (c) 2020-2024 HexHacking Team +// +// 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. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_GITHUB_HEXHACKING_XDL_UTIL +#define IO_GITHUB_HEXHACKING_XDL_UTIL + +#include +#include +#include + +#ifndef __LP64__ +#define XDL_UTIL_LINKER_BASENAME "linker" +#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker" +#define XDL_UTIL_APP_PROCESS_BASENAME "app_process32" +#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process32" +#define XDL_UTIL_APP_PROCESS_BASENAME_K "app_process" +#define XDL_UTIL_APP_PROCESS_PATHNAME_K "/system/bin/app_process" +#else +#define XDL_UTIL_LINKER_BASENAME "linker64" +#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker64" +#define XDL_UTIL_APP_PROCESS_BASENAME "app_process64" +#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process64" +#endif +#define XDL_UTIL_VDSO_BASENAME "[vdso]" + +#define XDL_UTIL_TEMP_FAILURE_RETRY(exp) \ + ({ \ + __typeof__(exp) _rc; \ + do { \ + errno = 0; \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) + +#ifdef __cplusplus +extern "C" { +#endif + +bool xdl_util_starts_with(const char *str, const char *start); +bool xdl_util_ends_with(const char *str, const char *ending); + +size_t xdl_util_trim_ending(char *start); + +int xdl_util_get_api_level(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/zygisk/src/main/cpp/zygisk.cpp b/zygisk/src/main/cpp/zygisk.cpp new file mode 100644 index 0000000..6e4f171 --- /dev/null +++ b/zygisk/src/main/cpp/zygisk.cpp @@ -0,0 +1,192 @@ +#include "zygisk.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "xdl.h" + +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "PIF", __VA_ARGS__) + +#define DEX_PATH "/data/adb/modules/playintegrityfix/classes.jar" + +#define LIB_64 "/data/adb/modules/playintegrityfix/inject/arm64-v8a.so" +#define LIB_32 "/data/adb/modules/playintegrityfix/inject/armeabi-v7a.so" + +#define DEFAULT_JSON "/data/adb/modules/playintegrityfix/pif.json" +#define CUSTOM_JSON_FORK "/data/adb/modules/playintegrityfix/custom.pif.json" +#define CUSTOM_JSON "/data/adb/pif.json" + +static ssize_t xread(int fd, void *buffer, size_t count) { + ssize_t total = 0; + auto buf = static_cast(buffer); + while (count > 0) { + ssize_t ret = TEMP_FAILURE_RETRY(read(fd, buf, count)); + if (ret < 0) + return -1; + buf += ret; + total += ret; + count -= ret; + } + return total; +} + +static ssize_t xwrite(int fd, const void *buffer, size_t count) { + ssize_t total = 0; + auto buf = (char *) buffer; + while (count > 0) { + ssize_t ret = TEMP_FAILURE_RETRY(write(fd, buf, count)); + if (ret < 0) + return -1; + buf += ret; + total += ret; + count -= ret; + } + return total; +} + +static bool copyFile(const std::string &from, const std::string &to, mode_t perms = 0777) { + return std::filesystem::exists(from) && + std::filesystem::copy_file( + from, + to, + std::filesystem::copy_options::overwrite_existing + ) && + !chmod( + to.c_str(), + perms + ); +} + +static void companion(int fd) { + bool ok = true; + + int length = 0; + xread(fd, &length, sizeof(int)); + + std::string dir; + dir.resize(length + 1); + auto bytes = xread(fd, dir.data(), length); + dir.resize(bytes); + dir[bytes - 1] = '\0'; + + LOGD("[COMPANION] GMS dir: %s", dir.c_str()); + + auto libFile = dir + "/libinject.so"; +#if defined(__aarch64__) + ok &= copyFile(LIB_64, libFile); +#elif defined(__arm__) + ok &= copyFile(LIB_32, libFile); +#endif + + LOGD("[COMPANION] copied inject lib"); + + auto dexFile = dir + "/classes.jar"; + ok &= copyFile(DEX_PATH, dexFile, 0644); + + LOGD("[COMPANION] copied dex"); + + auto jsonFile = dir + "/pif.json"; + if (!copyFile(CUSTOM_JSON, jsonFile)) { + if (!copyFile(CUSTOM_JSON_FORK, jsonFile)) { + if (!copyFile(DEFAULT_JSON, jsonFile)) { + ok = false; + } + } + } + + LOGD("[COMPANION] copied json"); + + xwrite(fd, &ok, sizeof(bool)); +} + +using namespace zygisk; + +class PlayIntegrityFix : public ModuleBase { +public: + void onLoad(Api *api_, JNIEnv *env_) override { + this->api = api_; + this->env = env_; + } + + void preAppSpecialize(AppSpecializeArgs *args) override { + api->setOption(DLCLOSE_MODULE_LIBRARY); + + if (!args) + return; + + auto rawDir = env->GetStringUTFChars(args->app_data_dir, nullptr); + auto rawName = env->GetStringUTFChars(args->nice_name, nullptr); + + std::string dir, name; + + if (rawDir) { + dir = rawDir; + env->ReleaseStringUTFChars(args->app_data_dir, rawDir); + } + + if (rawName) { + name = rawName; + env->ReleaseStringUTFChars(args->nice_name, rawName); + } + + bool isGms = dir.ends_with("/com.google.android.gms"); + bool isGmsUnstable = isGms && name == "com.google.android.gms.unstable"; + + if (!isGms) + return; + + api->setOption(FORCE_DENYLIST_UNMOUNT); + + if (!isGmsUnstable) + return; + + auto fd = api->connectCompanion(); + + int size = static_cast(dir.length()); + xwrite(fd, &size, sizeof(int)); + + xwrite(fd, dir.data(), size); + + bool ok = false; + xread(fd, &ok, sizeof(bool)); + + close(fd); + + if (ok) + gmsDir = dir; + } + + void postAppSpecialize(const AppSpecializeArgs *args) override { + if (gmsDir.empty()) + return; + + typedef bool (*InitFuncPtr)(JavaVM *, const std::string &); + + auto handle = xdl_open((gmsDir + "/libinject.so").c_str(), XDL_DEFAULT); + auto init_func = reinterpret_cast(xdl_sym(handle, "init", nullptr)); + + JavaVM *vm = nullptr; + env->GetJavaVM(&vm); + + init_func(vm, gmsDir); + + xdl_close(handle); + } + + void preServerSpecialize(ServerSpecializeArgs *args) override { + api->setOption(DLCLOSE_MODULE_LIBRARY); + } + +private: + Api *api = nullptr; + JNIEnv *env = nullptr; + std::string gmsDir; +}; + +REGISTER_ZYGISK_MODULE(PlayIntegrityFix) + +REGISTER_ZYGISK_COMPANION(companion) \ No newline at end of file diff --git a/zygisk/src/main/cpp/zygisk.hpp b/zygisk/src/main/cpp/zygisk.hpp new file mode 100644 index 0000000..128e9d1 --- /dev/null +++ b/zygisk/src/main/cpp/zygisk.hpp @@ -0,0 +1,411 @@ +/* Copyright 2022-2023 John "topjohnwu" Wu + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +// This is the public API for Zygisk modules. +// DO NOT MODIFY ANY CODE IN THIS HEADER. + +#pragma once + +#include + +#define ZYGISK_API_VERSION 2 + +/* + +*************** +* Introduction +*************** + +On Android, all app processes are forked from a special daemon called "Zygote". +For each new app process, zygote will fork a new process and perform "specialization". +This specialization operation enforces the Android security sandbox on the newly forked +process to make sure that 3rd party application code is only loaded after it is being +restricted within a sandbox. + +On Android, there is also this special process called "system_server". This single +process hosts a significant portion of system services, which controls how the +Android operating system and apps interact with each other. + +The Zygisk framework provides a way to allow developers to build modules and run custom +code before and after system_server and any app processes' specialization. +This enable developers to inject code and alter the behavior of system_server and app processes. + +Please note that modules will only be loaded after zygote has forked the child process. +THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON! + +********************* +* Development Guide +********************* + +Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. +Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. + +Example code: + +static jint (*orig_logger_entry_max)(JNIEnv *env); +static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } + +class ExampleModule : public zygisk::ModuleBase { +public: + void onLoad(zygisk::Api *api, JNIEnv *env) override { + this->api = api; + this->env = env; + } + void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { + JNINativeMethod methods[] = { + { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, + }; + api->hookJniNativeMethods(env, "android/util/Log", methods, 1); + *(void **) &orig_logger_entry_max = methods[0].fnPtr; + } +private: + zygisk::Api *api; + JNIEnv *env; +}; + +REGISTER_ZYGISK_MODULE(ExampleModule) + +----------------------------------------------------------------------------------------- + +Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize, +or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class +never runs in a true superuser environment. + +If your module require access to superuser permissions, you can create and register +a root companion handler function. This function runs in a separate root companion +daemon process, and an Unix domain socket is provided to allow you to perform IPC between +your target process and the root companion process. + +Example code: + +static void example_handler(int socket) { ... } + +REGISTER_ZYGISK_COMPANION(example_handler) + +*/ + +namespace zygisk { + + struct Api; + struct AppSpecializeArgs; + struct ServerSpecializeArgs; + + class ModuleBase { + public: + + // This method is called as soon as the module is loaded into the target process. + // A Zygisk API handle will be passed as an argument. + virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} + + // This method is called before the app process is specialized. + // At this point, the process just got forked from zygote, but no app specific specialization + // is applied. This means that the process does not have any sandbox restrictions and + // still runs with the same privilege of zygote. + // + // All the arguments that will be sent and used for app specialization is passed as a single + // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app + // process will be specialized. + // + // If you need to run some operations as superuser, you can call Api::connectCompanion() to + // get a socket to do IPC calls with a root companion process. + // See Api::connectCompanion() for more info. + virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} + + // This method is called after the app process is specialized. + // At this point, the process has all sandbox restrictions enabled for this application. + // This means that this method runs with the same privilege of the app's own code. + virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} + + // This method is called before the system server process is specialized. + // See preAppSpecialize(args) for more info. + virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} + + // This method is called after the system server process is specialized. + // At this point, the process runs with the privilege of system_server. + virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} + }; + + struct AppSpecializeArgs { + // Required arguments. These arguments are guaranteed to exist on all Android versions. + jint &uid; + jint &gid; + jintArray &gids; + jint &runtime_flags; + jint &mount_external; + jstring &se_info; + jstring &nice_name; + jstring &instruction_set; + jstring &app_data_dir; + + // Optional arguments. Please check whether the pointer is null before de-referencing + jboolean *const is_child_zygote; + jboolean *const is_top_app; + jobjectArray *const pkg_data_info_list; + jobjectArray *const whitelisted_data_info_list; + jboolean *const mount_data_dirs; + jboolean *const mount_storage_dirs; + + AppSpecializeArgs() = delete; + }; + + struct ServerSpecializeArgs { + jint &uid; + jint &gid; + jintArray &gids; + jint &runtime_flags; + jlong &permitted_capabilities; + jlong &effective_capabilities; + + ServerSpecializeArgs() = delete; + }; + + namespace internal { + struct api_table; + + template + void entry_impl(api_table *, JNIEnv *); + } + +// These values are used in Api::setOption(Option) + enum Option : int { + // Force Magisk's denylist unmount routines to run on this process. + // + // Setting this option only makes sense in preAppSpecialize. + // The actual unmounting happens during app process specialization. + // + // Set this option to force all Magisk and modules' files to be unmounted from the + // mount namespace of the process, regardless of the denylist enforcement status. + FORCE_DENYLIST_UNMOUNT = 0, + + // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. + // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. + // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. + DLCLOSE_MODULE_LIBRARY = 1, + }; + +// Bit masks of the return value of Api::getFlags() + enum StateFlag : uint32_t { + // The user has granted root access to the current process + PROCESS_GRANTED_ROOT = (1u << 0), + + // The current process was added on the denylist + PROCESS_ON_DENYLIST = (1u << 1), + }; + +// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded +// from the specialized process afterwards. + struct Api { + + // Connect to a root companion process and get a Unix domain socket for IPC. + // + // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. + // + // The pre[XXX]Specialize methods run with the same privilege of zygote. + // If you would like to do some operations with superuser permissions, register a handler + // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). + // Another good use case for a companion process is that if you want to share some resources + // across multiple processes, hold the resources in the companion process and pass it over. + // + // The root companion process is ABI aware; that is, when calling this method from a 32-bit + // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. + // + // Returns a file descriptor to a socket that is connected to the socket passed to your + // module's companion request handler. Returns -1 if the connection attempt failed. + int connectCompanion(); + + // Get the file descriptor of the root folder of the current module. + // + // This API only works in the pre[XXX]Specialize methods. + // Accessing the directory returned is only possible in the pre[XXX]Specialize methods + // or in the root companion process (assuming that you sent the fd over the socket). + // Both restrictions are due to SELinux and UID. + // + // Returns -1 if errors occurred. + int getModuleDir(); + + // Set various options for your module. + // Please note that this method accepts one single option at a time. + // Check zygisk::Option for the full list of options available. + void setOption(Option opt); + + // Get information about the current process. + // Returns bitwise-or'd zygisk::StateFlag values. + uint32_t getFlags(); + + // Hook JNI native methods for a class + // + // Lookup all registered JNI native methods and replace it with your own methods. + // The original function pointer will be saved in each JNINativeMethod's fnPtr. + // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr + // will be set to nullptr. + void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, + int numMethods); + + // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory. + // + // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example: + // + //
+ // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64 + // (More details: https://man7.org/linux/man-pages/man5/proc.5.html) + // + // For ELFs loaded in memory with pathname matching `regex`, replace function `symbol` with `newFunc`. + // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. + void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc); + + // For ELFs loaded in memory with pathname matching `regex`, exclude hooks registered for `symbol`. + // If `symbol` is nullptr, then all symbols will be excluded. + void pltHookExclude(const char *regex, const char *symbol); + + // Commit all the hooks that was previously registered. + // Returns false if an error occurred. + bool pltHookCommit(); + + private: + internal::api_table *tbl; + + template + friend void internal::entry_impl(internal::api_table *, JNIEnv *); + }; + +// Register a class as a Zygisk module + +#define REGISTER_ZYGISK_MODULE(clazz) \ +void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ + zygisk::internal::entry_impl(table, env); \ +} + +// Register a root companion request handler function for your module +// +// The function runs in a superuser daemon process and handles a root companion request from +// your module running in a target process. The function has to accept an integer value, +// which is a Unix domain socket that is connected to the target process. +// See Api::connectCompanion() for more info. +// +// NOTE: the function can run concurrently on multiple threads. +// Be aware of race conditions if you have globally shared resources. + +#define REGISTER_ZYGISK_COMPANION(func) \ +void zygisk_companion_entry(int client) { func(client); } + +/********************************************************* + * The following is internal ABI implementation detail. + * You do not have to understand what it is doing. + *********************************************************/ + + namespace internal { + + struct module_abi { + long api_version; + ModuleBase *impl; + + void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); + + void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); + + void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); + + void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); + + module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) { + preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); }; + postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); }; + preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); }; + postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); }; + } + }; + + struct api_table { + // Base + void *impl; + + bool (*registerModule)(api_table *, module_abi *); + + void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); + + void (*pltHookRegister)(const char *, const char *, void *, void **); + + void (*pltHookExclude)(const char *, const char *); + + bool (*pltHookCommit)(); + + int (*connectCompanion)(void * /* impl */); + + void (*setOption)(void * /* impl */, Option); + + int (*getModuleDir)(void * /* impl */); + + uint32_t (*getFlags)(void * /* impl */); + }; + + template + void entry_impl(api_table *table, JNIEnv *env) { + static Api api; + api.tbl = table; + static T module; + ModuleBase *m = &module; + static module_abi abi(m); + if (!table->registerModule(table, &abi)) return; + m->onLoad(&api, env); + } + + } // namespace internal + + inline int Api::connectCompanion() { + return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1; + } + + inline int Api::getModuleDir() { + return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; + } + + inline void Api::setOption(Option opt) { + if (tbl->setOption) tbl->setOption(tbl->impl, opt); + } + + inline uint32_t Api::getFlags() { + return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; + } + + inline void + Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, + int numMethods) { + if (tbl->hookJniNativeMethods) + tbl->hookJniNativeMethods(env, className, methods, numMethods); + } + + inline void + Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { + if (tbl->pltHookRegister) tbl->pltHookRegister(regex, symbol, newFunc, oldFunc); + } + + inline void Api::pltHookExclude(const char *regex, const char *symbol) { + if (tbl->pltHookExclude) tbl->pltHookExclude(regex, symbol); + } + + inline bool Api::pltHookCommit() { + return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); + } + +} // namespace zygisk + +extern "C" { + +[[gnu::visibility("default"), maybe_unused]] +void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); + +[[gnu::visibility("default"), maybe_unused]] +void zygisk_companion_entry(int); + +} // extern "C" diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java b/zygisk/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java similarity index 100% rename from app/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java rename to zygisk/src/main/java/es/chiteroman/playintegrityfix/CustomKeyStoreSpi.java diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/CustomPackageInfoCreator.java b/zygisk/src/main/java/es/chiteroman/playintegrityfix/CustomPackageInfoCreator.java similarity index 100% rename from app/src/main/java/es/chiteroman/playintegrityfix/CustomPackageInfoCreator.java rename to zygisk/src/main/java/es/chiteroman/playintegrityfix/CustomPackageInfoCreator.java diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java b/zygisk/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java similarity index 100% rename from app/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java rename to zygisk/src/main/java/es/chiteroman/playintegrityfix/CustomProvider.java diff --git a/app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java b/zygisk/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java similarity index 100% rename from app/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java rename to zygisk/src/main/java/es/chiteroman/playintegrityfix/EntryPoint.java