cocos_android/Classes/JcWallet.cpp
2023-10-24 16:32:19 +08:00

356 lines
11 KiB
C++

#include "JcWallet.h"
#include "WalletEvent.h"
#include <atomic>
#include <iostream>
#include <cstdarg>
#include <string>
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
#include "cocos/scripting/js-bindings/manual/jsb_global.h"
#include "scripting/js-bindings/event/EventDispatcher.h"
#include "scripting/js-bindings/manual/jsb_conversions.hpp"
#include "platform/CCApplication.h"
#include "base/CCScheduler.h"
#include "scrypt/native-crypto.h"
#include <jni.h>
#include <jni/JniImp.h>
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#ifndef COM_JC_JCFW_CLASS_NAME
#define COM_JC_JCFW_CLASS_NAME com_jc_jcfw_JcSDK
#endif
#define JNI_JCFW(FUNC) JNI_METHOD1(COM_JC_JCFW_CLASS_NAME, FUNC)
#define JNI_IMP_LOG_TAG "JcWallet"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, JNI_IMP_LOG_TAG, __VA_ARGS__)
cocos2d::Application *cocos_android_app_init(int width, int height);
#endif
using namespace cocos2d;
NS_CC_BEGIN
cocos2d::Application *g_app = nullptr;
JcWallet *JcWallet::_instance = nullptr;
uv_loop_t *loop = nullptr;
bool _isStarted = false;
uv_async_t gasync = {nullptr};
struct AsyncTaskData
{
std::mutex mtx;
std::list<std::function<void()>> tasks;
};
// run in server thread loop
void flush_tasks_in_server_loop_cb(uv_async_t *asyn)
{
auto *data = (AsyncTaskData *)asyn->data;
std::lock_guard<std::mutex> guard(data->mtx);
while (!data->tasks.empty())
{
// fetch task, run task
data->tasks.front()();
// drop task
data->tasks.pop_front();
}
}
void init_libuv_async_handle(uv_async_t *async)
{
memset(async, 0, sizeof(uv_async_t));
// uv_loop_t *loop = uv_default_loop();
loop = (uv_loop_t *)malloc(sizeof(uv_loop_t));
uv_loop_init(loop);
uv_async_init(loop, async, cocos2d::flush_tasks_in_server_loop_cb);
async->data = new AsyncTaskData();
uv_run(loop, UV_RUN_DEFAULT);
}
// run in game thread, dispatch runnable object into server loop
int schedule_task_into_server_thread_task_queue(uv_async_t *asyn, const std::function<void()>& func)
{
auto *data = (AsyncTaskData *)asyn->data;
{
std::lock_guard<std::mutex> guard(data->mtx);
data->tasks.emplace_back(func);
}
// notify server thread to invoke `flush_tasks_in_server_loop_cb()`
return uv_async_send(asyn);
}
#define RUN_IN_GAMETHREAD(task) \
do \
{ \
JcWallet::getInstance()->performFunctionInCocosThread([=]() { task; }); \
} while (0)
#define RUN_IN_SERVERTHREAD(task) \
do \
{ \
schedule_task_into_server_thread_task_queue(&gasync, [=]() { task; }); \
} while (0)
bool runGlobalMethod(const char *name, const se::ValueArray& args, se::Value *value)
{
se::AutoHandleScope scope;
bool ok = false;
auto global = se::ScriptEngine::getInstance()->getGlobalObject();
se::Value func;
if (global->getProperty(name, &func) && func.isObject())
{
ok = func.toObject()->call(args, global, value);
}
return ok;
}
bool addToArgArray(se::ValueArray *args, const char *valChar)
{
std::string strVal(valChar);
se::Value tmpVal;
bool ok = true;
ok &= std_string_to_seval(strVal, &tmpVal);
args->push_back(tmpVal);
return ok;
}
JcWallet::JcWallet()
{
JcWallet::_instance = this;
_functionsToPerform.reserve(30);
std::unique_lock<std::mutex> lock(_performMutex);
}
void JcWallet::initEnv()
{
if (!_isStarted)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
g_app = new AppDelegate(1, 1);
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
g_app = cocos_android_app_init(1, 1);
#endif
EventDispatcher::init();
g_app->start();
_isStarted = true;
WalletEvent::Emit("wallet_init_event", "{}");
cocos2d::init_libuv_async_handle(&gasync);
}
}
void JcWallet::initJSThread(const std::shared_ptr<JcWallet>& wallet)
{
JcWallet::initEnv();
}
void JcWallet::tick(float dt)
{
g_app->getScheduler()->update(dt);
EventDispatcher::dispatchTickEvent(0);
}
JcWallet::~JcWallet()
{
EventDispatcher::destroy();
se::ScriptEngine::destroyInstance();
uv_loop_close(loop);
JcWallet::_instance = nullptr;
}
char *JcWallet::runJsMethod(const std::shared_ptr<JSMethodParam>& data)
{
cocos2d::log("thread: %ld call method %s params: %lu", uv_thread_self(), data->methodName.c_str(), data->args.size());
se::Value value;
bool ok = cocos2d::runGlobalMethod(data->methodName.c_str(), data->args, &value);
static std::string result;
if (value.isString())
{
if (ok && !value.isNullOrUndefined())
{
result = value.toString();
}
else
{
result = "";
}
cocos2d::log("method: %s ::result: %s", data->methodName.c_str(), result.c_str());
RUN_IN_GAMETHREAD(JcWallet::jsToUnity(data->funId, result));
// WalletEvent::Emit(data->funId.c_str(), result.c_str());
}
return const_cast<char *>(result.c_str());
}
void JcWallet::performFunctionInCocosThread(const std::function<void()> &function)
{
_performMutex.lock();
_functionsToPerform.push_back(function);
_performMutex.unlock();
}
void JcWallet::tickRun()
{
_performMutex.lock();
if (!_functionsToPerform.empty())
{
// fixed #4123: Save the callback functions, they must be invoked after '_performMutex.unlock()', otherwise if new functions are added in callback, it will cause thread deadlock.
auto temp = _functionsToPerform;
for (const auto &function : temp)
{
function();
}
_functionsToPerform.clear();
}
_performMutex.unlock();
}
void JcWallet::jsToUnity(const std::string& funId, const std::string& msg)
{
WalletEvent::Emit(funId.c_str(), msg.c_str());
}
extern "C"
{
void initEnv()
{
if (!_isStarted)
{
new JcWallet();
std::shared_ptr<JcWallet> wallet(JcWallet::getInstance());
std::thread([=]()
{ JcWallet::initJSThread(wallet); })
.detach();
}
}
void tick(float dt)
{
if (_isStarted)
{
schedule_task_into_server_thread_task_queue(&gasync, [=]()
{ JcWallet::getInstance()->tick(dt); });
JcWallet::getInstance()->tickRun();
}
}
void tick2(float dt)
{
// if (_isStarted)
// {
// schedule_task_into_server_thread_task_queue(&gasync, [=]()
// { JcWallet::getInstance()->tick(dt); });
// JcWallet::getInstance()->tickRun();
// }
}
int runWalletMethod(const char *funId, const char *methodName, int paramCount, char **paramList)
{
auto *data = new JSMethodParam();
std::string methodNameStr(methodName);
data->methodName = methodNameStr;
data->funId = funId;
data->paramCount = paramCount;
addToArgArray(&data->args, funId);
for (int i = 0; i < paramCount; i++)
{
char *arg = *(paramList + i);
addToArgArray(&data->args, arg);
}
std::shared_ptr<JSMethodParam> params(data);
int result = schedule_task_into_server_thread_task_queue(&gasync, [=]()
{ JcWallet::runJsMethod(params); });
return result == 0 ? 1 : 0;
}
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JNIEXPORT jint JNICALL JNI_JCFW(runJS)(JNIEnv *env, jclass clazz, jstring j_fun_id, jstring j_method_name, jobjectArray j_params)
{
if (!_isStarted) {
return 0;
}
std::string funId = JniHelper::jstring2string(j_fun_id);
std::string methodName = JniHelper::jstring2string(j_method_name);
LOGD("jni call %s | %s", methodName.c_str(), funId.c_str());
auto *data = new JSMethodParam();
data->methodName = methodName;
data->funId = funId;
jsize count = env->GetArrayLength(j_params);
data->paramCount = count;
addToArgArray(&data->args, funId.c_str());
for (int i = 0; i < count; i++)
{
auto j_str = (jstring)env->GetObjectArrayElement(j_params, i);
std::string s_str = JniHelper::jstring2string(j_str);
addToArgArray(&data->args, s_str.c_str());
}
std::shared_ptr<JSMethodParam> params(data);
int result = schedule_task_into_server_thread_task_queue(&gasync, [=]()
{ JcWallet::runJsMethod(params); });
return result == 0 ? 1 : 0;
}
JNIEXPORT jstring JNICALL JNI_JCFW(decryptPass)(JNIEnv *env, jclass clazz, jstring jaccount, jstring jpass)
{
std::string pass_encrypted = JniHelper::jstring2string(jpass);
std::string account = JniHelper::jstring2string(jaccount);
std::string keyStr = account + "0x741482aE1480E552735E44Ff3A733448AcBbeD8d";
std::string passDecrypt = decrypt_aes(pass_encrypted, keyStr);
return env->NewStringUTF(passDecrypt.c_str());
}
JNIEXPORT void JNICALL JNI_JCFW(tick)(JNIEnv *env, jclass clazz, jfloat dt)
{
tick2(dt);
}
#endif
}
NS_CC_END
static bool getOrCreatePlainObject_r(const char *name, se::Object *parent, se::Object **outObj)
{
assert(parent != nullptr);
assert(outObj != nullptr);
se::Value tmp;
if (parent->getProperty(name, &tmp) && tmp.isObject())
{
*outObj = tmp.toObject();
(*outObj)->incRef();
}
else
{
*outObj = se::Object::createPlainObject();
parent->setProperty(name, se::Value(*outObj));
}
return true;
}
bool jsb_wallet_callback(se::State &s)
{
const auto &args = s.args();
size_t argc = args.size();
if (argc >= 2)
{
bool ok;
std::string funId;
ok = seval_to_std_string(args[0], &funId);
SE_PRECONDITION2(ok, false, "Error processing arguments");
std::string msg;
ok = seval_to_std_string(args[1], &msg);
SE_PRECONDITION2(ok, false, "Error processing arguments");
if (funId.find("webpage_") == 0)
{
onProxyCBJNI(funId, msg);
}
else
{
RUN_IN_GAMETHREAD(JcWallet::jsToUnity(funId, msg));
}
return true;
}
return false;
}
SE_BIND_FUNC(jsb_wallet_callback)
bool jsb_register_walletevent_modules(se::Object *global)
{
getOrCreatePlainObject_r("jsb", global, &__jsbObj);
__jsbObj->defineFunction("jcCallback", _SE(jsb_wallet_callback));
return true;
}