diff --git a/native/jni/zygisk/api.hpp b/native/jni/zygisk/api.hpp index 0021b171a..aee25359e 100644 --- a/native/jni/zygisk/api.hpp +++ b/native/jni/zygisk/api.hpp @@ -9,7 +9,7 @@ #include -#define ZYGISK_API_VERSION 2 +#define ZYGISK_API_VERSION 3 /* @@ -98,6 +98,7 @@ struct AppSpecializeArgs { jint &gid; jintArray &gids; jint &runtime_flags; + jobjectArray &rlimits; jint &mount_external; jstring &se_info; jstring &nice_name; @@ -105,6 +106,7 @@ struct AppSpecializeArgs { jstring &app_data_dir; // Optional arguments. Please check whether the pointer is null before de-referencing + jintArray *const fds_to_ignore; jboolean *const is_child_zygote; jboolean *const is_top_app; jobjectArray *const pkg_data_info_list; diff --git a/native/jni/zygisk/gen_jni_hooks.py b/native/jni/zygisk/gen_jni_hooks.py index f5e3e80bc..665673397 100755 --- a/native/jni/zygisk/gen_jni_hooks.py +++ b/native/jni/zygisk/gen_jni_hooks.py @@ -87,7 +87,7 @@ class ForkAndSpec(JNIHook): return 'nativeForkAndSpecialize' def init_args(self): - return 'AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);' + return 'AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);' def body(self): decl = '' @@ -118,7 +118,7 @@ class ForkServer(ForkAndSpec): return 'nativeForkSystemServer' def init_args(self): - return 'ServerSpecializeArgsImpl args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);' + return 'ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);' # Common args uid = Argument('uid', jint) @@ -134,7 +134,7 @@ instruction_set = Argument('instruction_set', jstring) app_data_dir = Argument('app_data_dir', jstring) # o -fds_to_ignore = Argument('fds_to_ignore', jintArray) +fds_to_ignore = Argument('fds_to_ignore', jintArray, True) # p is_child_zygote = Argument('is_child_zygote', jboolean, True) diff --git a/native/jni/zygisk/hook.cpp b/native/jni/zygisk/hook.cpp index a279bdf27..476342422 100644 --- a/native/jni/zygisk/hook.cpp +++ b/native/jni/zygisk/hook.cpp @@ -43,8 +43,8 @@ void name##_post(); struct HookContext { JNIEnv *env; union { - AppSpecializeArgsImpl *args; - ServerSpecializeArgsImpl *server_args; + AppSpecializeArgs_v3 *args; + ServerSpecializeArgs_v1 *server_args; void *raw_args; }; const char *process; @@ -292,10 +292,11 @@ bool ZygiskModule::RegisterModule(ApiTable *table, long *module) { // Fill in API accordingly with module API version switch (ver) { + case 3: case 2: table->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; table->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; - [[fallthrough]]; + // fallthrough case 1: table->v1.hookJniNativeMethods = &hookJniNativeMethods; table->v1.pltHookRegister = [](const char *p, const char *s, void *n, void **o) { @@ -391,6 +392,19 @@ void HookContext::run_modules_pre(const vector &fds) { } } + // Add all ignored fd onto whitelist + if (state[APP_SPECIALIZE] && args->fds_to_ignore) { + int len = env->GetArrayLength(*args->fds_to_ignore); + int *arr = env->GetIntArrayElements(*args->fds_to_ignore, nullptr); + for (int i = 0; i < len; ++i) { + int fd = arr[i]; + if (fd >= 0 && fd < 1024) { + open_fds[fd] = true; + } + } + env->ReleaseIntArrayElements(*args->fds_to_ignore, arr, 0); + } + // Close all unrecorded fds rewinddir(dir.get()); for (dirent *entry; (entry = xreaddir(dir.get()));) { diff --git a/native/jni/zygisk/jni_hooks.hpp b/native/jni/zygisk/jni_hooks.hpp index 972c5a294..1eab79fa9 100644 --- a/native/jni/zygisk/jni_hooks.hpp +++ b/native/jni/zygisk/jni_hooks.hpp @@ -2,7 +2,7 @@ void *nativeForkAndSpecialize_orig = nullptr; jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; ctx.raw_args = &args; @@ -14,7 +14,8 @@ jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, ji return ctx.pid; } jint nativeForkAndSpecialize_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); + args.fds_to_ignore = &fds_to_ignore; HookContext ctx; ctx.env = env; ctx.raw_args = &args; @@ -26,7 +27,8 @@ jint nativeForkAndSpecialize_o(JNIEnv *env, jclass clazz, jint uid, jint gid, ji return ctx.pid; } jint nativeForkAndSpecialize_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); + args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; @@ -39,7 +41,8 @@ jint nativeForkAndSpecialize_p(JNIEnv *env, jclass clazz, jint uid, jint gid, ji return ctx.pid; } jint nativeForkAndSpecialize_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); + args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; HookContext ctx; @@ -53,7 +56,8 @@ jint nativeForkAndSpecialize_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid return ctx.pid; } jint nativeForkAndSpecialize_r(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); + args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; args.pkg_data_info_list = &pkg_data_info_list; @@ -71,7 +75,7 @@ jint nativeForkAndSpecialize_r(JNIEnv *env, jclass clazz, jint uid, jint gid, ji return ctx.pid; } jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; ctx.raw_args = &args; @@ -83,7 +87,7 @@ jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint return ctx.pid; } jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; ctx.raw_args = &args; @@ -95,7 +99,8 @@ jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint return ctx.pid; } jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); + args.fds_to_ignore = &fds_to_ignore; HookContext ctx; ctx.env = env; ctx.raw_args = &args; @@ -107,7 +112,8 @@ jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint return ctx.pid; } jint nativeForkAndSpecialize_samsung_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); + args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; @@ -170,7 +176,7 @@ constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpeci void *nativeSpecializeAppProcess_orig = nullptr; void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; @@ -182,7 +188,7 @@ void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, ctx.nativeSpecializeAppProcess_post(); } void nativeSpecializeAppProcess_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; HookContext ctx; @@ -195,7 +201,7 @@ void nativeSpecializeAppProcess_q_alt(JNIEnv *env, jclass clazz, jint uid, jint ctx.nativeSpecializeAppProcess_post(); } void nativeSpecializeAppProcess_r(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; args.pkg_data_info_list = &pkg_data_info_list; @@ -212,7 +218,7 @@ void nativeSpecializeAppProcess_r(JNIEnv *env, jclass clazz, jint uid, jint gid, ctx.nativeSpecializeAppProcess_post(); } void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { - AppSpecializeArgsImpl args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); + AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; @@ -249,7 +255,7 @@ constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializ void *nativeForkSystemServer_orig = nullptr; jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { - ServerSpecializeArgsImpl args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); + ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); HookContext ctx; ctx.env = env; ctx.raw_args = &args; @@ -261,7 +267,7 @@ jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jin return ctx.pid; } jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _11, jint _12, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { - ServerSpecializeArgsImpl args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); + ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); HookContext ctx; ctx.env = env; ctx.raw_args = &args; diff --git a/native/jni/zygisk/module.hpp b/native/jni/zygisk/module.hpp index b4fa1ae24..edb58c75d 100644 --- a/native/jni/zygisk/module.hpp +++ b/native/jni/zygisk/module.hpp @@ -4,11 +4,39 @@ namespace { -using module_abi_v1 = zygisk::internal::module_abi; struct HookContext; struct ZygiskModule; -struct AppSpecializeArgsImpl { +struct AppSpecializeArgs_v3 { + jint &uid; + jint &gid; + jintArray &gids; + jint &runtime_flags; + jobjectArray &rlimits; + jint &mount_external; + jstring &se_info; + jstring &nice_name; + jstring &instruction_set; + jstring &app_data_dir; + + jintArray *fds_to_ignore = nullptr; + jboolean *is_child_zygote = nullptr; + jboolean *is_top_app = nullptr; + jobjectArray *pkg_data_info_list = nullptr; + jobjectArray *whitelisted_data_info_list = nullptr; + jboolean *mount_data_dirs = nullptr; + jboolean *mount_storage_dirs = nullptr; + + AppSpecializeArgs_v3( + jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, + jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name, + jstring &instruction_set, jstring &app_data_dir) : + uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), rlimits(rlimits), + mount_external(mount_external), se_info(se_info), nice_name(nice_name), + instruction_set(instruction_set), app_data_dir(app_data_dir) {} +}; + +struct AppSpecializeArgs_v1 { jint &uid; jint &gid; jintArray &gids; @@ -19,24 +47,35 @@ struct AppSpecializeArgsImpl { jstring &instruction_set; jstring &app_data_dir; - /* Optional */ - jboolean *is_child_zygote = nullptr; - jboolean *is_top_app = nullptr; - jobjectArray *pkg_data_info_list = nullptr; - jobjectArray *whitelisted_data_info_list = nullptr; - jboolean *mount_data_dirs = nullptr; - jboolean *mount_storage_dirs = nullptr; + 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; - AppSpecializeArgsImpl( - 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) : - uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), - mount_external(mount_external), se_info(se_info), nice_name(nice_name), - instruction_set(instruction_set), app_data_dir(app_data_dir) {} + AppSpecializeArgs_v1(const AppSpecializeArgs_v3 *v3) : + uid(v3->uid), gid(v3->gid), gids(v3->gids), runtime_flags(v3->runtime_flags), + mount_external(v3->mount_external), se_info(v3->se_info), nice_name(v3->nice_name), + instruction_set(v3->instruction_set), app_data_dir(v3->app_data_dir), + is_child_zygote(v3->is_child_zygote), is_top_app(v3->is_top_app), + pkg_data_info_list(v3->pkg_data_info_list), + whitelisted_data_info_list(v3->whitelisted_data_info_list), + mount_data_dirs(v3->mount_data_dirs), mount_storage_dirs(v3->mount_storage_dirs) {} }; -struct ServerSpecializeArgsImpl { +struct module_abi_raw { + long api_version; + void *_this; + void (*preAppSpecialize)(void *, void *); + void (*postAppSpecialize)(void *, const void *); + void (*preServerSpecialize)(void *, void *); + void (*postServerSpecialize)(void *, const void *); +}; + +using module_abi_v1 = module_abi_raw; + +struct ServerSpecializeArgs_v1 { jint &uid; jint &gid; jintArray &gids; @@ -44,7 +83,7 @@ struct ServerSpecializeArgsImpl { jlong &permitted_capabilities; jlong &effective_capabilities; - ServerSpecializeArgsImpl( + ServerSpecializeArgs_v1( jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, jlong &permitted_capabilities, jlong &effective_capabilities) : uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), @@ -63,21 +102,6 @@ enum : uint32_t { PRIVATE_MASK = (0x3u << 30) }; -template -struct force_cast_wrapper { - template - operator U() const { return reinterpret_cast(mX); } - force_cast_wrapper(T &&x) : mX(std::forward(x)) {} - force_cast_wrapper &operator=(const force_cast_wrapper &) = delete; -private: - T &&mX; -}; - -template -force_cast_wrapper force_cast(R &&x) { - return force_cast_wrapper(std::forward(x)); -} - struct ApiTable { // These first 2 entries are permanent ZygiskModule *module; @@ -100,18 +124,32 @@ struct ApiTable { ApiTable(ZygiskModule *m); }; +#define call_app(method) \ +switch (*ver) { \ +case 1: \ +case 2: { \ + AppSpecializeArgs_v1 a(args); \ + v1->method(v1->_this, &a); \ + break; \ +} \ +case 3: \ + v1->method(v1->_this, args); \ + break; \ +} + struct ZygiskModule { - void preAppSpecialize(AppSpecializeArgsImpl *args) const { - v1->preAppSpecialize(v1->_this, force_cast(args)); + + void preAppSpecialize(AppSpecializeArgs_v3 *args) const { + call_app(preAppSpecialize) } - void postAppSpecialize(const AppSpecializeArgsImpl *args) const { - v1->postAppSpecialize(v1->_this, force_cast(args)); + void postAppSpecialize(const AppSpecializeArgs_v3 *args) const { + call_app(postAppSpecialize) } - void preServerSpecialize(ServerSpecializeArgsImpl *args) const { - v1->preServerSpecialize(v1->_this, force_cast(args)); + void preServerSpecialize(ServerSpecializeArgs_v1 *args) const { + v1->preServerSpecialize(v1->_this, args); } - void postServerSpecialize(const ServerSpecializeArgsImpl *args) const { - v1->postServerSpecialize(v1->_this, force_cast(args)); + void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const { + v1->postServerSpecialize(v1->_this, args); } int connectCompanion() const;