diff --git a/.gitignore b/.gitignore index f91065b..9f1bc51 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,16 @@ app/src/main/cpp/glslang_out/ app/src/main/lib/ app/src/main/libs/ + +.vs/ +app/src/main/cpp/rpcs3/3rdparty/FAudio/csharp/obj/ +app/src/main/cpp/rpcs3/3rdparty/SoundTouch/soundtouch/source/csharp-example/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-DTLS-PSK-Server/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-DTLS-Server/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-Example-IOCallbacks/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-TLS-Client/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-TLS-PSK-Client/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-TLS-PSK-Server/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-TLS-Server/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL-TLS-ServerThreaded/obj/ +app/src/main/cpp/rpcs3/3rdparty/wolfssl/wolfssl/wrapper/CSharp/wolfSSL_CSharp/obj/ diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 299b5c8..2ed8ea5 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -51,6 +51,7 @@ target_sources(emu cpuinfo.cpp glsl2spv.cpp meminfo.cpp + emulator.cpp ) target_link_libraries(emu PRIVATE rpcs3_emu) diff --git a/app/src/main/cpp/aps3e.cpp b/app/src/main/cpp/aps3e.cpp index 3f69c8e..2059cb0 100644 --- a/app/src/main/cpp/aps3e.cpp +++ b/app/src/main/cpp/aps3e.cpp @@ -180,110 +180,6 @@ LOG_CHANNEL(sys_log, "SYS"); #include "aps3e_util.cpp" -static void j_setup_game_info(JNIEnv* env,jobject self,jobject meta_info ){ - - jclass clsMetaInfo = env->FindClass("aenu/aps3e/Emulator$MetaInfo"); - jfieldID f_eboot = env->GetFieldID(clsMetaInfo, "eboot_path", "Ljava/lang/String;"); - jfieldID f_iso_fd = env->GetFieldID(clsMetaInfo, "iso_fd", "I"); - jfieldID f_id = env->GetFieldID(clsMetaInfo, "serial", "Ljava/lang/String;"); - jstring f_eboot_v = (jstring) env->GetObjectField(meta_info, f_eboot ); - jstring f_id_v = (jstring) env->GetObjectField(meta_info, f_id); - - const char *id = env->GetStringUTFChars(f_id_v, nullptr); - - aps3e_emu::game_id = std::string(id); - - if(f_eboot_v!=NULL) { - const char *eboot = env->GetStringUTFChars(f_eboot_v, nullptr); - aps3e_emu::eboot_path = std::string(eboot); - - env->ReleaseStringUTFChars(f_eboot_v, eboot); - }else{ - aps3e_emu::iso_fd = env->GetIntField(meta_info, f_iso_fd); - } - - env->ReleaseStringUTFChars(f_id_v, id); - -} - -static void j_boot(JNIEnv* env,jobject self){ - LOGW("f_init_pre "); - aps3e_log.notice("f_init_pre "); - - LOGW("ANativeWindow %d %d",aps3e_emu::w,aps3e_emu::h); - - std::thread yy(aps3e_emu::g_emu_thr); - yy.detach(); - - LOGW("f_init_end"); - aps3e_log.notice("f_init_end"); - -} - -static void j_change_surface(JNIEnv* env,jobject self,jint w,jint h){ - - aps3e_emu::w=w; - aps3e_emu::h=h; -} - -static void j_setup_surface(JNIEnv* env,jobject self,jobject surface){ - - if(aps3e_emu::wnd){ - ANativeWindow_release(aps3e_emu::wnd); - aps3e_emu::wnd=nullptr;} - if(surface) { - aps3e_emu::wnd=ANativeWindow_fromSurface(env,surface); - aps3e_emu::w=ANativeWindow_getWidth(aps3e_emu::wnd); - aps3e_emu::h=ANativeWindow_getHeight(aps3e_emu::wnd); - } -} - -static void j_key_event(JNIEnv* env,jobject self,jint key_code,jboolean pressed,jint value){ - pthread_mutex_lock(&aps3e_emu::key_event_mutex); - { - auto* pad_thr=g_fxo->try_get>(); - if(pad_thr){ - auto xx=pad_thr->get_handlers().at(pad_handler::keyboard); - std::shared_ptr padh=std::dynamic_pointer_cast(xx); - padh->Key(static_cast(key_code), static_cast(pressed),value); - } - } - pthread_mutex_unlock(&aps3e_emu::key_event_mutex); -} - -static void j_pause(JNIEnv* env,jobject self){ - - pthread_mutex_lock(&aps3e_emu::emu_mutex); - aps3e_emu::emu_status=aps3e_emu::EmuThr::STATUS_REQUEST_PAUSE; - while(aps3e_emu::emu_status==aps3e_emu::EmuThr::STATUS_REQUEST_PAUSE) - pthread_cond_wait(&aps3e_emu::emu_cond,&aps3e_emu::emu_mutex); - pthread_mutex_unlock(&aps3e_emu::emu_mutex); -} - -static void j_resume(JNIEnv* env,jobject self){ - pthread_mutex_lock(&aps3e_emu::emu_mutex); - aps3e_emu::emu_status=aps3e_emu::EmuThr::STATUS_REQUEST_RESUME; - while(aps3e_emu::emu_status==aps3e_emu::EmuThr::STATUS_REQUEST_RESUME) - pthread_cond_wait(&aps3e_emu::emu_cond,&aps3e_emu::emu_mutex); - pthread_mutex_unlock(&aps3e_emu::emu_mutex); -} - -static jboolean j_is_running(JNIEnv* env,jobject self){ - return aps3e_emu::emu_status==aps3e_emu::EmuThr::STATUS_RUNNING; -} - -static jboolean j_is_paused(JNIEnv* env,jobject self){ - return aps3e_emu::emu_status==aps3e_emu::EmuThr::STATUS_PAUSED; -} - -static void j_quit(JNIEnv* env,jobject self){ - pthread_mutex_lock(&aps3e_emu::emu_mutex); - aps3e_emu::emu_status=aps3e_emu::EmuThr::STATUS_REQUEST_STOP; - while(aps3e_emu::emu_status==aps3e_emu::EmuThr::STATUS_REQUEST_STOP) - pthread_cond_wait(&aps3e_emu::emu_cond,&aps3e_emu::emu_mutex); - pthread_mutex_unlock(&aps3e_emu::emu_mutex); -} - /* static jboolean j_install_firmware(JNIEnv* env,jobject self,jstring pup_path){ jboolean is_copy=false; @@ -449,7 +345,11 @@ static jobject j_meta_info_from_iso(JNIEnv* env,jobject self,jint fd,jstring jis return meta_info; } - +static void j_setup_game_id(JNIEnv* env,jobject self,jstring id){ + const char* _id=env->GetStringUTFChars(id,NULL); + ae::game_id=_id; + env->ReleaseStringUTFChars(id,_id); +} /*public native int get_cpu_core_count(); public native String get_cpu_name(int core_idx); public native int get_cpu_max_mhz(int core_idx);*/ @@ -471,7 +371,7 @@ static jboolean support_custom_driver(JNIEnv* env,jobject self){ return access("/dev/kgsl-3d0",F_OK)==0; }*/ -int register_Emulator(JNIEnv* env){ +int register_aps3e_Emulator(JNIEnv* env){ static const JNINativeMethod methods[] = { @@ -495,22 +395,11 @@ int register_Emulator(JNIEnv* env){ { "meta_info_from_iso","(ILjava/lang/String;)Laenu/aps3e/Emulator$MetaInfo;",(void*)j_meta_info_from_iso}, - //{ "setup_context", "(Landroid/content/Context;)V", (void *) j_setup_context }, + { "setup_game_id", "(Ljava/lang/String;)V", (void *) j_setup_game_id }, - { "setup_game_info", "(Laenu/aps3e/Emulator$MetaInfo;)V", (void *) j_setup_game_info }, - { "boot", "()V", (void *) j_boot }, - { "pause", "()V", (void *) j_pause}, - { "resume", "()V", (void *) j_resume}, - { "is_running", "()Z", (void *) j_is_running}, - { "is_paused", "()Z", (void *) j_is_paused}, - - { "quit", "()V", (void *) j_quit}, - { "key_event", "(IZI)V", (void *) j_key_event}, { "install_pkg", "(I)Z", (void *) j_install_pkg }, - { "change_surface", "(II)V", (void *) j_change_surface }, - { "setup_surface", "(Landroid/view/Surface;)V", (void *) j_setup_surface }, }; jclass clazz = env->FindClass("aenu/aps3e/Emulator"); @@ -518,22 +407,6 @@ int register_Emulator(JNIEnv* env){ } -int register_Emulator_cfg(JNIEnv* env){ - - static const JNINativeMethod methods[] = { - - { "native_open_config", "(Ljava/lang/String;)J", (void *) open_config_str }, - { "native_close_config", "(J)Ljava/lang/String;", (void *) close_config_str }, - { "native_open_config_file", "(Ljava/lang/String;)J", (void *) open_config_file }, - { "native_load_config_entry", "(JLjava/lang/String;)Ljava/lang/String;", (void *) load_config_entry }, - { "native_load_config_entry_ty_arr", "(JLjava/lang/String;)[Ljava/lang/String;", (void *) load_config_entry_ty_arr }, - { "native_save_config_entry", "(JLjava/lang/String;Ljava/lang/String;)V", (void *) save_config_entry }, - { "native_save_config_entry_ty_arr", "(JLjava/lang/String;[Ljava/lang/String;)V", (void *) save_config_entry_ty_arr }, - { "native_close_config_file", "(JLjava/lang/String;)V", (void *) close_config_file }, - }; - jclass clazz = env->FindClass("aenu/aps3e/Emulator$Config"); - return env->RegisterNatives(clazz,methods, sizeof(methods)/sizeof(methods[0])); -} //esr ctx static const esr_context* find_esr_context(const ucontext_t* ctx) @@ -576,6 +449,9 @@ static void signal_handler(int /*sig*/, siginfo_t* info, void* uct) noexcept }; } +int register_Emulator(JNIEnv* env); +int register_Emulator$Config(JNIEnv* env); + extern "C" __attribute__((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved){ @@ -588,16 +464,19 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved){ goto bail; } - if (register_Emulator(env) != JNI_OK) { - LOGE("register_Emulator failed"); - goto bail; - } - - if (register_Emulator_cfg(env) != JNI_OK) { - LOGE("register_Emulator_cfg failed"); - goto bail; - } + if(register_Emulator(env) != JNI_OK){ + LOGE("register_Emulator failed"); + goto bail; + } + if(register_Emulator$Config(env) != JNI_OK){ + LOGE("register_Emulator$Config failed"); + goto bail; + } + if (register_aps3e_Emulator(env) != JNI_OK) { + LOGE("register_Emulator failed"); + goto bail; + } result = JNI_VERSION_1_6; LOGW("JNI_OnLoad OK"); diff --git a/app/src/main/cpp/aps3e_config.cpp b/app/src/main/cpp/aps3e_config.cpp index 70e17b3..7ee5049 100644 --- a/app/src/main/cpp/aps3e_config.cpp +++ b/app/src/main/cpp/aps3e_config.cpp @@ -3,201 +3,6 @@ // SPDX-License-Identifier: WTFPL // -static_assert(sizeof(YAML::Node*)==8,""); - -static YAML::Node* open_config_str(JNIEnv* env,jobject self,jstring jconfig_str){ - jboolean is_copy=false; - const char* config_str=env->GetStringUTFChars(jconfig_str,&is_copy); - std::istringstream config_stream(config_str); - YAML::Node* config_node = new YAML::Node(YAML::Load(config_stream)); - env->ReleaseStringUTFChars(jconfig_str,config_str); - return config_node; -} - -static jstring close_config_str(JNIEnv* env,jobject self,YAML::Node* config_node){ - YAML::Emitter out; - out << *config_node; - jboolean is_copy=false; - - jstring result=env->NewStringUTF(out.c_str()); - delete config_node; - return result; -} - -static YAML::Node* open_config_file(JNIEnv* env,jobject self,jstring config_path){ - jboolean is_copy=false; - const char* path=env->GetStringUTFChars(config_path,&is_copy); - - YAML::Node* config_node = new YAML::Node(YAML::LoadFile(path)); - env->ReleaseStringUTFChars(config_path,path); - return config_node; -} - -static jstring load_config_entry(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag){ - jboolean is_copy=false; - const char* tag_cstr=env->GetStringUTFChars(tag,&is_copy); - std::string tag_str(tag_cstr); - env->ReleaseStringUTFChars(tag,tag_cstr); - size_t pos=tag_str.find('|'); - std::string parent=tag_str.substr(0,pos); - std::string child=tag_str.substr(pos+1); - - switch(std::count(child.begin(),child.end(),'|')){ - case 0: { - YAML::Node node=(*config_node)[parent][child]; - if(node.IsDefined()) - return env->NewStringUTF(node.as().c_str()); - } - break; - case 1:{ - pos=child.find('|'); - std::string child2=child.substr(pos+1); - child=child.substr(0,pos); - - YAML::Node node=(*config_node)[parent][child][child2]; - if(node.IsDefined()) - return env->NewStringUTF(node.as().c_str()); - } - break; - - case 2:{ - - pos=child.find('|'); - std::string child2=child.substr(pos+1); - child=child.substr(0,pos); - pos=child2.find('|'); - std::string child3=child2.substr(pos+1); - child2=child2.substr(0,pos); - - YAML::Node node=(*config_node)[parent][child][child2][child3]; - if(node.IsDefined()) - return env->NewStringUTF(node.as().c_str()); - } - break; - - default: - aps3e_log.error("load_config_entry fail %s,level too deep",tag_str.c_str()); - LOGE("load_config_entry fail %s,level too deep",tag_str.c_str()); - break; - } - return NULL; -} - - -static jobjectArray load_config_entry_ty_arr(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag){ - jboolean is_copy=false; - const char* tag_cstr=env->GetStringUTFChars(tag,&is_copy); - std::string tag_str(tag_cstr); - env->ReleaseStringUTFChars(tag,tag_cstr); - size_t pos=tag_str.find('|'); - std::string parent=tag_str.substr(0,pos); - std::string child=tag_str.substr(pos+1); - - switch(std::count(child.begin(),child.end(),'|')){ - case 0: { - YAML::Node node=(*config_node)[parent][child]; - if(node.IsDefined()) { - std::vector v=node.as>(); - jobjectArray arr=env->NewObjectArray(v.size(),env->FindClass("java/lang/String"),NULL); - for(size_t i=0;iSetObjectArrayElement(arr,i,env->NewStringUTF(v[i].c_str())); - } - return arr; - } - } - - break; - - default: - aps3e_log.error("load_config_entry fail %s,level too deep",tag_str.c_str()); - LOGE("load_config_entry fail %s,level too deep",tag_str.c_str()); - break; - } - return NULL; -} - -static void save_config_entry(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag,jstring val){ - - jboolean is_copy=false; - const char* tag_cstr=env->GetStringUTFChars(tag,&is_copy); - std::string tag_str(tag_cstr); - env->ReleaseStringUTFChars(tag,tag_cstr); - size_t pos=tag_str.find('|'); - std::string parent=tag_str.substr(0,pos); - std::string child=tag_str.substr(pos+1); - const char* val_cstr=env->GetStringUTFChars(val,&is_copy); - - switch(std::count(child.begin(),child.end(),'|')){ - case 0: { - (*config_node)[parent][child] = std::string(val_cstr); - } - break; - case 1:{ - pos=child.find('|'); - std::string child2=child.substr(pos+1); - child=child.substr(0,pos); - (*config_node)[parent][child][child2]=std::string(val_cstr); - } - break; - - case 2:{ - - pos=child.find('|'); - std::string child2=child.substr(pos+1); - child=child.substr(0,pos); - pos=child2.find('|'); - std::string child3=child2.substr(pos+1); - child2=child2.substr(0,pos); - (*config_node)[parent][child][child2][child3]=std::string(val_cstr); - } - break; - - default: - aps3e_log.error("save_config_entry fail %s,level too deep",tag_str.c_str()); - LOGE("save_config_entry fail %s,level too deep",tag_str.c_str()); - break; - } - - env->ReleaseStringUTFChars(val,val_cstr); -} - - -static void save_config_entry_ty_arr(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag,jobjectArray val) { - - jboolean is_copy = false; - const char *tag_cstr = env->GetStringUTFChars(tag, &is_copy); - std::string tag_str(tag_cstr); - env->ReleaseStringUTFChars(tag, tag_cstr); - size_t pos = tag_str.find('|'); - std::string parent = tag_str.substr(0, pos); - std::string child = tag_str.substr(pos + 1); - - switch (std::count(child.begin(), child.end(), '|')) { - case 0: { - std::vector v; - for (int i = 0; i < env->GetArrayLength(val); i++) { - jstring jstr = (jstring) env->GetObjectArrayElement(val, i); - const char *str = env->GetStringUTFChars(jstr, &is_copy); - v.push_back(std::string(str)); - env->ReleaseStringUTFChars(jstr, str); - } - (*config_node)[parent][child] = v; - } - break; - } -} -static void close_config_file(JNIEnv* env,jobject self,YAML::Node* config_node,jstring config_path){ - YAML::Emitter out; - out << *config_node; - jboolean is_copy=false; - const char* path=env->GetStringUTFChars(config_path,&is_copy); - std::ofstream fout(path); - fout << out.c_str(); - fout.close(); - env->ReleaseStringUTFChars(config_path,path); - delete config_node; -} - static auto gen_key=[](const std::string& name)->std::string{ std::string k=name; if(size_t p=k.find("(");p!=std::string::npos){ @@ -216,6 +21,7 @@ static auto gen_key=[](const std::string& name)->std::string{ else if(c=='|'){ c='_'; } + } //replace("-",""); diff --git a/app/src/main/cpp/aps3e_emu.cpp b/app/src/main/cpp/aps3e_emu.cpp index 4514107..495c658 100644 --- a/app/src/main/cpp/aps3e_emu.cpp +++ b/app/src/main/cpp/aps3e_emu.cpp @@ -1,32 +1,159 @@ // SPDX-License-Identifier: GPL-2.0-only -/** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */ -EmuCallbacks create_emu_cb() -{ - EmuCallbacks callbacks{}; +#include "emulator.h" +namespace ae{ - callbacks.on_run = [](bool /*start_playtime*/) { - PRE("on_run"); + int boot_type; + + std::string boot_game_path; + int boot_game_fd; + + ANativeWindow* window; + int window_width; + int window_height; + + std::string game_id; + + enum{ + STATUS_RUNNING=1, + STATUS_STOPPED, + STATUS_PAUSED, + STATUS_REQUEST_PAUSE, + STATUS_REQUEST_RESUME, + STATUS_REQUEST_STOP, }; - callbacks.on_pause = []() { - PRE("on_pause"); - }; - callbacks.on_resume = []() { - PRE("on_resume"); - }; + pthread_mutex_t key_event_mutex; - callbacks.update_emu_settings = [](){PRE("update_emu_settings");}; - callbacks.save_emu_settings = [](){PRE("save_emu_settings");}; - callbacks.init_kb_handler = [](){PRE("init_kb_handler"); - g_fxo->init(Emu.DeserialManager()); - }; + pthread_mutex_t emu_mutex; + pthread_cond_t emu_cond; - callbacks.init_mouse_handler = []() + int emu_status=-1; + + + + void init(); + bool boot_game(); + + void main_thr(){ + + std::string tid=[]{ + std::stringstream ss; + ss<try_get>(); + if(pad_thr){ + auto xx=pad_thr->get_handlers().at(pad_handler::keyboard); + std::shared_ptr padh=std::dynamic_pointer_cast(xx); + padh->Key(static_cast(key_code), static_cast(pressed),value); + } + } + pthread_mutex_unlock(&key_event_mutex); + } + bool is_running(){ + return emu_status==STATUS_RUNNING; + } + bool is_paused(){ + return emu_status==STATUS_PAUSED; + } + void pause(){ + pthread_mutex_lock(&emu_mutex); + emu_status=STATUS_REQUEST_PAUSE; + while(emu_status==STATUS_REQUEST_PAUSE) + pthread_cond_wait(&emu_cond,&emu_mutex); + pthread_mutex_unlock(&emu_mutex); + } + void resume(){ + pthread_mutex_lock(&emu_mutex); + emu_status=STATUS_REQUEST_RESUME; + while(emu_status==STATUS_REQUEST_RESUME) + pthread_cond_wait(&emu_cond,&emu_mutex); + pthread_mutex_unlock(&emu_mutex); + } + void quit(){ + pthread_mutex_lock(&emu_mutex); + emu_status=STATUS_REQUEST_STOP; + while(emu_status==STATUS_REQUEST_STOP) + pthread_cond_wait(&emu_cond,&emu_mutex); + pthread_mutex_unlock(&emu_mutex); + } + + /** RPCS3 emulator has functions it desires to call from the GUI at times. Initialize them in here. */ + EmuCallbacks create_emu_cb() { - PRE("init_mouse_handler"); + EmuCallbacks callbacks{}; + + callbacks.on_run = [](bool /*start_playtime*/) { + PRE("on_run"); + }; + + callbacks.on_pause = []() { + PRE("on_pause"); + }; + callbacks.on_resume = []() { + PRE("on_resume"); + }; + + callbacks.update_emu_settings = [](){PRE("update_emu_settings");}; + callbacks.save_emu_settings = [](){PRE("save_emu_settings");}; + callbacks.init_kb_handler = [](){PRE("init_kb_handler"); + g_fxo->init(Emu.DeserialManager()); + }; + + callbacks.init_mouse_handler = []() + { + PRE("init_mouse_handler"); #if 0 - mouse_handler handler = g_cfg.io.mouse; + mouse_handler handler = g_cfg.io.mouse; if (handler == mouse_handler::null) { @@ -65,332 +192,249 @@ EmuCallbacks create_emu_cb() } } #else - g_fxo->init(Emu.DeserialManager()); + g_fxo->init(Emu.DeserialManager()); #endif - }; - - callbacks.init_pad_handler = [](std::string_view title_id) - { - PRE("init_pad_handler"); - aps3e_log.warning("init_pad_handler: title_id=%s", title_id); - - ensure(g_fxo->init>(nullptr, nullptr, title_id)); - qt_events_aware_op(0, [](){ return !!pad::g_started; }); - }; - - callbacks.get_audio = []() -> std::shared_ptr - { - PRE("get_audio"); - std::shared_ptr result =std::make_shared(); - switch (g_cfg.audio.renderer.get()) - { - case audio_renderer::null: result = std::make_shared(); break; - case audio_renderer::cubeb: result = std::make_shared(); break; -#ifdef HAVE_FAUDIO - case audio_renderer::faudio: result = std::make_shared(); break; -#endif - } - - if (!result->Initialized()) - { - PRE("!result->Initialized()"); - // Fall back to a null backend if something went wrong - //sys_log.error("Audio renderer %s could not be initialized, using a Null renderer instead. Make sure that no other application is running that might block audio access (e.g. Netflix).", result->GetName()); - result = std::make_shared(); - } - return result; - }; - - callbacks.get_audio_enumerator = [](u64 renderer) -> std::shared_ptr - { - PRE("get_audio_enumerator"); - switch (static_cast(renderer)) - { - case audio_renderer::null: return std::make_shared(); -#ifdef _WIN32 - case audio_renderer::xaudio: return std::make_shared(); -#endif - case audio_renderer::cubeb: return std::make_shared(); -#ifdef HAVE_FAUDIO - case audio_renderer::faudio: return std::make_shared(); -#endif - default: return std::make_shared(); - } - }; - - callbacks.get_image_info = [](const std::string& filename, std::string& sub_type, s32& width, s32& height, s32& orientation) -> bool - { - PRE("get_image_info"); - return false; - }; - - callbacks.get_scaled_image = [](const std::string& path, s32 target_width, s32 target_height, s32& width, s32& height, u8* dst, bool force_fit) -> bool - { - PRE("get_scaled_image"); - return false; - }; - - callbacks.resolve_path = [](std::string_view sv) - { - LOGW("resolve_path: %s",sv.data()); - if(sv.ends_with("/"sv)){ - sv.remove_suffix(1); - return std::string{sv}; - } - return std::string{sv}; - // May result in an empty string if path does not exist - //return QFileInfo(QString::fromUtf8(sv.data(), static_cast(sv.size()))).canonicalFilePath().toStdString(); - }; - - callbacks.on_install_pkgs = [](const std::vector& pkgs) - { - PRE("on_install_pkgs"); - return true; - }; - - callbacks.try_to_quit = [](bool force_quit, std::function on_exit) -> bool - { - PRE("try_to_quit"); - if (on_exit) - { - on_exit(); - } - /*if (force_quit) - { - if (on_exit) - { - on_exit(); - } - - quit(); - return true; - }*/ - return true; - }; - callbacks.call_from_main_thread = [](std::function func, atomic_t* wake_up) - { - PRE("call_from_main_thread"); - - func(); - - if (wake_up) - { - *wake_up = true; - wake_up->notify_one(); - } - }; - - callbacks.init_gs_render = [](utils::serial* ar) - { - PRE("init_gs_render"); - switch (g_cfg.video.renderer.get()) - { - case video_renderer::null: - { - LOGE("video_renderer::null"); - break; - } - case video_renderer::vulkan: - { - g_fxo->init>(ar); - break; - } - } - }; - - callbacks.get_camera_handler = []() -> std::shared_ptr - { - PRE("get_camera_handler"); - /*switch (g_cfg.io.camera.get()) - { - case camera_handler::null: - case camera_handler::fake: - { - return std::make_shared(); - } - case camera_handler::qt: - { - fmt::throw_exception("Headless mode can not be used with this camera handler. Current handler: %s", g_cfg.io.camera.get()); - } - }*/ - return std::make_shared(); - }; - - callbacks.get_music_handler = []() -> std::shared_ptr - { - PRE("get_music_handler"); - switch (g_cfg.audio.music.get()) - { - case music_handler::null: - { - return std::make_shared(); - } - case music_handler::qt: - { - return std::make_shared(); - } - } - }; - - callbacks.get_msg_dialog = []() -> std::shared_ptr { - PRE("get_msg_dialog"); - return std::make_shared(); }; - callbacks.get_osk_dialog = []() -> std::shared_ptr { - PRE("get_osk_dialog"); - return std::make_shared(); }; - callbacks.get_save_dialog = []() -> std::unique_ptr { - PRE("get_save_dialog"); - return std::make_unique(); }; - callbacks.get_trophy_notification_dialog = []() -> std::unique_ptr { - PRE("get_trophy_notification_dialog"); - return std::make_unique(); }; - callbacks.get_sendmessage_dialog=[]()->std::shared_ptr{ - PRE("get_sendmessage_dialog"); - return std::make_shared(); - }; - callbacks.get_recvmessage_dialog=[]()->std::shared_ptr{ - PRE("get_recvmessage_dialog"); - return std::make_shared(); - }; - callbacks.on_stop = []() { - PRE("on_stop"); - }; - callbacks.on_ready = []() { - PRE("on_ready"); - }; - callbacks.on_emulation_stop_no_response = [](std::shared_ptr> closed_successfully, int /*seconds_waiting_already*/) - { - PRE("on_emulation_stop_no_response"); - }; - - callbacks.on_save_state_progress = [](std::shared_ptr>, stx::shared_ptr, stx::atomic_ptr*, std::shared_ptr) - { - PRE("on_save_state_progress"); - }; - - callbacks.enable_disc_eject = [](bool) { - PRE("enable_disc_eject"); - - }; - callbacks.enable_disc_insert = [](bool) { - PRE("enable_disc_insert"); - }; - - callbacks.on_missing_fw = []() { - PRE("on_missing_fw"); - }; - - callbacks.handle_taskbar_progress = [](s32, s32) { - PRE("handle_taskbar_progress"); - }; - - callbacks.get_localized_string = [](localized_string_id id, const char* args) -> std::string { - PRE("get_localized_string"); - - std::string str = localized_strings[static_cast(id)]; - if(auto it=str.find("%0");it!=std::string::npos) { - str.replace(it, 2, args); - } - return str; - }; - /*callbacks.get_localized_u32string = [](localized_string_id, const char*) -> std::u32string { - PRE("get_localized_u32string"); - return {}; };*/ - - callbacks.play_sound = [](const std::string&){ - PRE("play_sound"); - - }; - callbacks.add_breakpoint = [](u32 /*addr*/){ - PRE("add_breakpoint"); - - }; - - return callbacks; -} - -namespace aps3e_emu{ - - pthread_mutex_t key_event_mutex; - - pthread_mutex_t emu_mutex; - pthread_cond_t emu_cond; - - int emu_status=-1; - - void init(); - bool boot_game(); - - struct EmuThr{ - - enum{ - STATUS_RUNNING=1, - STATUS_STOPPED, - STATUS_PAUSED, - STATUS_REQUEST_PAUSE, - STATUS_REQUEST_RESUME, - STATUS_REQUEST_STOP, }; + callbacks.init_pad_handler = [](std::string_view title_id) + { + PRE("init_pad_handler"); + aps3e_log.warning("init_pad_handler: title_id=%s", title_id); - void operator()(){ + ensure(g_fxo->init>(nullptr, nullptr, title_id)); + qt_events_aware_op(0, [](){ return !!pad::g_started; }); + }; - std::string tid=[]{ - std::stringstream ss; - ss< std::shared_ptr + { + PRE("get_audio"); + std::shared_ptr result =std::make_shared(); + switch (g_cfg.audio.renderer.get()) + { + case audio_renderer::null: result = std::make_shared(); break; + case audio_renderer::cubeb: result = std::make_shared(); break; +#ifdef HAVE_FAUDIO + case audio_renderer::faudio: result = std::make_shared(); break; +#endif } - pthread_mutex_destroy(&key_event_mutex); - pthread_mutex_destroy(&emu_mutex); - pthread_cond_destroy(&emu_cond); - } - } g_emu_thr; + if (!result->Initialized()) + { + PRE("!result->Initialized()"); + // Fall back to a null backend if something went wrong + //sys_log.error("Audio renderer %s could not be initialized, using a Null renderer instead. Make sure that no other application is running that might block audio access (e.g. Netflix).", result->GetName()); + result = std::make_shared(); + } + return result; + }; - ANativeWindow* wnd; - int w; - int h; + callbacks.get_audio_enumerator = [](u64 renderer) -> std::shared_ptr + { + PRE("get_audio_enumerator"); + switch (static_cast(renderer)) + { + case audio_renderer::null: return std::make_shared(); +#ifdef _WIN32 + case audio_renderer::xaudio: return std::make_shared(); +#endif + case audio_renderer::cubeb: return std::make_shared(); +#ifdef HAVE_FAUDIO + case audio_renderer::faudio: return std::make_shared(); +#endif + default: return std::make_shared(); + } + }; + callbacks.get_image_info = [](const std::string& filename, std::string& sub_type, s32& width, s32& height, s32& orientation) -> bool + { + PRE("get_image_info"); + return false; + }; + + callbacks.get_scaled_image = [](const std::string& path, s32 target_width, s32 target_height, s32& width, s32& height, u8* dst, bool force_fit) -> bool + { + PRE("get_scaled_image"); + return false; + }; + + callbacks.resolve_path = [](std::string_view sv) + { + LOGW("resolve_path: %s",sv.data()); + if(sv.ends_with("/"sv)){ + sv.remove_suffix(1); + return std::string{sv}; + } + return std::string{sv}; + // May result in an empty string if path does not exist + //return QFileInfo(QString::fromUtf8(sv.data(), static_cast(sv.size()))).canonicalFilePath().toStdString(); + }; + + callbacks.on_install_pkgs = [](const std::vector& pkgs) + { + PRE("on_install_pkgs"); + return true; + }; + + callbacks.try_to_quit = [](bool force_quit, std::function on_exit) -> bool + { + PRE("try_to_quit"); + if (on_exit) + { + on_exit(); + } + /*if (force_quit) + { + if (on_exit) + { + on_exit(); + } + + quit(); + return true; + }*/ + return true; + }; + callbacks.call_from_main_thread = [](std::function func, atomic_t* wake_up) + { + PRE("call_from_main_thread"); + + func(); + + if (wake_up) + { + *wake_up = true; + wake_up->notify_one(); + } + }; + + callbacks.init_gs_render = [](utils::serial* ar) + { + PRE("init_gs_render"); + switch (g_cfg.video.renderer.get()) + { + case video_renderer::null: + { + LOGE("video_renderer::null"); + break; + } + case video_renderer::vulkan: + { + g_fxo->init>(ar); + break; + } + } + }; + + callbacks.get_camera_handler = []() -> std::shared_ptr + { + PRE("get_camera_handler"); + /*switch (g_cfg.io.camera.get()) + { + case camera_handler::null: + case camera_handler::fake: + { + return std::make_shared(); + } + case camera_handler::qt: + { + fmt::throw_exception("Headless mode can not be used with this camera handler. Current handler: %s", g_cfg.io.camera.get()); + } + }*/ + return std::make_shared(); + }; + + callbacks.get_music_handler = []() -> std::shared_ptr + { + PRE("get_music_handler"); + switch (g_cfg.audio.music.get()) + { + case music_handler::null: + { + return std::make_shared(); + } + case music_handler::qt: + { + return std::make_shared(); + } + } + }; + + callbacks.get_msg_dialog = []() -> std::shared_ptr { + PRE("get_msg_dialog"); + return std::make_shared(); }; + callbacks.get_osk_dialog = []() -> std::shared_ptr { + PRE("get_osk_dialog"); + return std::make_shared(); }; + callbacks.get_save_dialog = []() -> std::unique_ptr { + PRE("get_save_dialog"); + return std::make_unique(); }; + callbacks.get_trophy_notification_dialog = []() -> std::unique_ptr { + PRE("get_trophy_notification_dialog"); + return std::make_unique(); }; + callbacks.get_sendmessage_dialog=[]()->std::shared_ptr{ + PRE("get_sendmessage_dialog"); + return std::make_shared(); + }; + callbacks.get_recvmessage_dialog=[]()->std::shared_ptr{ + PRE("get_recvmessage_dialog"); + return std::make_shared(); + }; + callbacks.on_stop = []() { + PRE("on_stop"); + }; + callbacks.on_ready = []() { + PRE("on_ready"); + }; + callbacks.on_emulation_stop_no_response = [](std::shared_ptr> closed_successfully, int /*seconds_waiting_already*/) + { + PRE("on_emulation_stop_no_response"); + }; + + callbacks.on_save_state_progress = [](std::shared_ptr>, stx::shared_ptr, stx::atomic_ptr*, std::shared_ptr) + { + PRE("on_save_state_progress"); + }; + + callbacks.enable_disc_eject = [](bool) { + PRE("enable_disc_eject"); + + }; + callbacks.enable_disc_insert = [](bool) { + PRE("enable_disc_insert"); + }; + + callbacks.on_missing_fw = []() { + PRE("on_missing_fw"); + }; + + callbacks.handle_taskbar_progress = [](s32, s32) { + PRE("handle_taskbar_progress"); + }; + + callbacks.get_localized_string = [](localized_string_id id, const char* args) -> std::string { + PRE("get_localized_string"); + + std::string str = localized_strings[static_cast(id)]; + if(auto it=str.find("%0");it!=std::string::npos) { + str.replace(it, 2, args); + } + return str; + }; + /*callbacks.get_localized_u32string = [](localized_string_id, const char*) -> std::u32string { + PRE("get_localized_u32string"); + return {}; };*/ + + callbacks.play_sound = [](const std::string&){ + PRE("play_sound"); + + }; + callbacks.add_breakpoint = [](u32 /*addr*/){ + PRE("add_breakpoint"); + + }; + + return callbacks; + } void init(){ const char* enable_log=getenv("APS3E_ENABLE_LOG"); @@ -398,7 +442,7 @@ namespace aps3e_emu{ static std::unique_ptr log_file = logs::make_file_listener(std::string(getenv("APS3E_LOG_DIR"))+"/rp3_log.txt", 1024*1024*1024); } - prctl(PR_SET_TIMERSLACK, 50000, 0, 0, 0); + prctl(PR_SET_TIMERSLACK, 1, 0, 0, 0); // Initialize TSC freq (in case it isn't) static_cast(utils::get_tsc_freq()); @@ -437,7 +481,7 @@ namespace aps3e_emu{ callbacks.get_gs_frame = [=]() -> std::unique_ptr { PRE("get_gs_frame"); - return std::unique_ptr(new android_gs_frame(wnd,w,h)); + return std::unique_ptr(new android_gs_frame(window,window_width,window_height)); }; Emu.SetCallbacks(std::move(callbacks)); @@ -446,11 +490,6 @@ namespace aps3e_emu{ Emu.Init(); } - - std::string game_id; - std::string eboot_path; - int iso_fd; - bool boot_game(){ //Emu.CallFromMainThread([=]() @@ -460,11 +499,11 @@ namespace aps3e_emu{ const char* config_path=getenv("APS3E_CUSTOM_CONFIG_YAML_PATH"); const cfg_mode config_mode = config_path?cfg_mode::custom:cfg_mode::global ; - aps3e_log.warning("iso_fd: %d",iso_fd); - const game_boot_result error =!eboot_path.empty()? Emu.BootGame(eboot_path, game_id, true, config_mode, config_path?:"") - :Emu.BootISO(":PS3_GAME/USRDIR/EBOOT.BIN",game_id,iso_fd,config_mode, config_path?:""); + aps3e_log.warning("iso_fd: %d",boot_game_fd); + const game_boot_result error =boot_type==BOOT_TYPE_WITH_PATH? Emu.BootGame(boot_game_path, game_id, true, config_mode, config_path?:"") + :Emu.BootISO(":PS3_GAME/USRDIR/EBOOT.BIN",game_id,boot_game_fd,config_mode, config_path?:""); LOGW("game_boot_result %d",error); return error==game_boot_result::no_errors; }//); } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/aps3e_pad_thread.cpp b/app/src/main/cpp/aps3e_pad_thread.cpp index 5979dab..e3feb12 100644 --- a/app/src/main/cpp/aps3e_pad_thread.cpp +++ b/app/src/main/cpp/aps3e_pad_thread.cpp @@ -216,7 +216,7 @@ void pad_thread::update_pad_states() } } -namespace aps3e_emu{ +namespace ae{ extern pthread_mutex_t key_event_mutex; } @@ -333,13 +333,13 @@ void pad_thread::operator()() if (pad_mode == pad_handler_mode::single_threaded) { - pthread_mutex_lock(&aps3e_emu::key_event_mutex); + pthread_mutex_lock(&ae::key_event_mutex); for (auto& handler : m_handlers) { handler.second->process(); connected_devices += handler.second->connected_devices; } - pthread_mutex_unlock(&aps3e_emu::key_event_mutex); + pthread_mutex_unlock(&ae::key_event_mutex); } else { diff --git a/app/src/main/cpp/emulator.cpp b/app/src/main/cpp/emulator.cpp new file mode 100644 index 0000000..0a869e3 --- /dev/null +++ b/app/src/main/cpp/emulator.cpp @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: WTFPL + +#include "emulator.h" +#include +#include +#include +#include + +#define LOG_TAG "Emulator_Config" +#define LOGE(...) { \ + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,"%s : %d",__FILE__,__LINE__);\ + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,__VA_ARGS__);\ +} + +#define CFG_TYPE_TOML 0 +#define CFG_TYPE_YAML 1 + +#if CFG_TYPE_YAML +#include "yaml-cpp/yaml.h" + +static_assert(sizeof(YAML::Node*)==8,""); + +static YAML::Node* j_open_config_str(JNIEnv* env,jobject self,jstring jconfig_str){ + jboolean is_copy=false; + const char* config_str=env->GetStringUTFChars(jconfig_str,&is_copy); + std::istringstream config_stream(config_str); + YAML::Node* config_node = new YAML::Node(YAML::Load(config_stream)); + env->ReleaseStringUTFChars(jconfig_str,config_str); + return config_node; +} + +static jstring j_close_config_str(JNIEnv* env,jobject self,YAML::Node* config_node){ + YAML::Emitter out; + out << *config_node; + jboolean is_copy=false; + + jstring result=env->NewStringUTF(out.c_str()); + delete config_node; + return result; +} + +static YAML::Node* j_open_config_file(JNIEnv* env,jobject self,jstring config_path){ + jboolean is_copy=false; + const char* path=env->GetStringUTFChars(config_path,&is_copy); + + YAML::Node* config_node = new YAML::Node(YAML::LoadFile(path)); + env->ReleaseStringUTFChars(config_path,path); + return config_node; +} + +static jstring j_load_config_entry(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag){ + jboolean is_copy=false; + const char* tag_cstr=env->GetStringUTFChars(tag,&is_copy); + std::string tag_str(tag_cstr); + env->ReleaseStringUTFChars(tag,tag_cstr); + size_t pos=tag_str.find('|'); + std::string parent=tag_str.substr(0,pos); + std::string child=tag_str.substr(pos+1); + + switch(std::count(child.begin(),child.end(),'|')){ + case 0: { + YAML::Node node=(*config_node)[parent][child]; + if(node.IsDefined()) + return env->NewStringUTF(node.as().c_str()); + } + break; + case 1:{ + pos=child.find('|'); + std::string child2=child.substr(pos+1); + child=child.substr(0,pos); + + YAML::Node node=(*config_node)[parent][child][child2]; + if(node.IsDefined()) + return env->NewStringUTF(node.as().c_str()); + } + break; + + case 2:{ + + pos=child.find('|'); + std::string child2=child.substr(pos+1); + child=child.substr(0,pos); + pos=child2.find('|'); + std::string child3=child2.substr(pos+1); + child2=child2.substr(0,pos); + + YAML::Node node=(*config_node)[parent][child][child2][child3]; + if(node.IsDefined()) + return env->NewStringUTF(node.as().c_str()); + } + break; + + default: + LOGE("load_config_entry fail %s,level too deep",tag_str.c_str()); + break; + } + return NULL; +} + + +static jobjectArray j_load_config_entry_ty_arr(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag){ + jboolean is_copy=false; + const char* tag_cstr=env->GetStringUTFChars(tag,&is_copy); + std::string tag_str(tag_cstr); + env->ReleaseStringUTFChars(tag,tag_cstr); + size_t pos=tag_str.find('|'); + std::string parent=tag_str.substr(0,pos); + std::string child=tag_str.substr(pos+1); + + switch(std::count(child.begin(),child.end(),'|')){ + case 0: { + YAML::Node node=(*config_node)[parent][child]; + if(node.IsDefined()) { + std::vector v=node.as>(); + jobjectArray arr=env->NewObjectArray(v.size(),env->FindClass("java/lang/String"),NULL); + for(size_t i=0;iSetObjectArrayElement(arr,i,env->NewStringUTF(v[i].c_str())); + } + return arr; + } + } + + break; + + default: + LOGE("load_config_entry fail %s,level too deep",tag_str.c_str()); + break; + } + return NULL; +} + +static void j_save_config_entry(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag,jstring val){ + + jboolean is_copy=false; + const char* tag_cstr=env->GetStringUTFChars(tag,&is_copy); + std::string tag_str(tag_cstr); + env->ReleaseStringUTFChars(tag,tag_cstr); + size_t pos=tag_str.find('|'); + std::string parent=tag_str.substr(0,pos); + std::string child=tag_str.substr(pos+1); + const char* val_cstr=env->GetStringUTFChars(val,&is_copy); + + switch(std::count(child.begin(),child.end(),'|')){ + case 0: { + (*config_node)[parent][child] = std::string(val_cstr); + } + break; + case 1:{ + pos=child.find('|'); + std::string child2=child.substr(pos+1); + child=child.substr(0,pos); + (*config_node)[parent][child][child2]=std::string(val_cstr); + } + break; + + case 2:{ + + pos=child.find('|'); + std::string child2=child.substr(pos+1); + child=child.substr(0,pos); + pos=child2.find('|'); + std::string child3=child2.substr(pos+1); + child2=child2.substr(0,pos); + (*config_node)[parent][child][child2][child3]=std::string(val_cstr); + } + break; + + default: + LOGE("save_config_entry fail %s,level too deep",tag_str.c_str()); + break; + } + + env->ReleaseStringUTFChars(val,val_cstr); +} + + +static void j_save_config_entry_ty_arr(JNIEnv* env,jobject self,YAML::Node* config_node,jstring tag,jobjectArray val) { + + jboolean is_copy = false; + const char *tag_cstr = env->GetStringUTFChars(tag, &is_copy); + std::string tag_str(tag_cstr); + env->ReleaseStringUTFChars(tag, tag_cstr); + size_t pos = tag_str.find('|'); + std::string parent = tag_str.substr(0, pos); + std::string child = tag_str.substr(pos + 1); + + switch (std::count(child.begin(), child.end(), '|')) { + case 0: { + std::vector v; + for (int i = 0; i < env->GetArrayLength(val); i++) { + jstring jstr = (jstring) env->GetObjectArrayElement(val, i); + const char *str = env->GetStringUTFChars(jstr, &is_copy); + v.push_back(std::string(str)); + env->ReleaseStringUTFChars(jstr, str); + } + (*config_node)[parent][child] = v; + } + break; + + + default: + LOGE("save_config_entry fail %s,level too deep",tag_str.c_str()); + break; + } +} +static void j_close_config_file(JNIEnv* env,jobject self,YAML::Node* config_node,jstring config_path){ + YAML::Emitter out; + out << *config_node; + jboolean is_copy=false; + const char* path=env->GetStringUTFChars(config_path,&is_copy); + std::ofstream fout(path); + fout << out.c_str(); + fout.close(); + env->ReleaseStringUTFChars(config_path,path); + delete config_node; +} +#elif CFG_TYPE_TOLM +#error "CFG_TYPE_TOLM not supported" +#else +#error "CFG_TYPE_XXX not defined" +#endif + +int register_Emulator$Config(JNIEnv* env){ + + static const JNINativeMethod methods[] = { + + { "native_open_config", "(Ljava/lang/String;)J", (void *) j_open_config_str }, + { "native_close_config", "(J)Ljava/lang/String;", (void *) j_close_config_str }, + { "native_open_config_file", "(Ljava/lang/String;)J", (void *) j_open_config_file }, + { "native_load_config_entry", "(JLjava/lang/String;)Ljava/lang/String;", (void *) j_load_config_entry }, + { "native_load_config_entry_ty_arr", "(JLjava/lang/String;)[Ljava/lang/String;", (void *) j_load_config_entry_ty_arr }, + { "native_save_config_entry", "(JLjava/lang/String;Ljava/lang/String;)V", (void *) j_save_config_entry }, + { "native_save_config_entry_ty_arr", "(JLjava/lang/String;[Ljava/lang/String;)V", (void *) j_save_config_entry_ty_arr }, + { "native_close_config_file", "(JLjava/lang/String;)V", (void *) j_close_config_file }, + }; + jclass clazz = env->FindClass("aenu/emulator/Emulator$Config"); + return env->RegisterNatives(clazz,methods, sizeof(methods)/sizeof(methods[0])); +} + +//public native void setup_game_path(String path); +//public native void setup_game_path(Emulator.Path path); +static void j_setup_game_path(JNIEnv* env,jobject self,jobject path ){ + + jclass path_cls; + if(env->IsInstanceOf(path,path_cls=env->FindClass("aenu/emulator/Emulator$Path"))){ + jfieldID f_fd = env->GetFieldID(path_cls, "fd", "I"); + ae::boot_type=ae::BOOT_TYPE_WITH_FD; + ae::boot_game_fd=env->GetIntField(path, f_fd); + } + else if(env->IsInstanceOf(path,path_cls=env->FindClass("java/lang/String"))){ + const char* _path = env->GetStringUTFChars(reinterpret_cast(path), nullptr); + ae::boot_type=ae::BOOT_TYPE_WITH_PATH; + ae::boot_game_path=std::string(_path); + env->ReleaseStringUTFChars(reinterpret_cast(path), _path); + } +} + +static void j_boot(JNIEnv* env,jobject self){ + std::thread(ae::main_thr).detach(); +} + +static void j_change_surface(JNIEnv* env,jobject self,jint w,jint h){ + ae::window_width=w; + ae::window_height=h; +} + +static void j_setup_surface(JNIEnv* env,jobject self,jobject surface){ + + if(ae::window){ + ANativeWindow_release(ae::window); + ae::window=nullptr;} + if(surface) { + ae::window=ANativeWindow_fromSurface(env,surface); + ae::window_width=ANativeWindow_getWidth(ae::window); + ae::window_height=ANativeWindow_getHeight(ae::window); + } +} + +static void j_key_event(JNIEnv* env,jobject self,jint key_code,jboolean pressed,jint value){ + ae::key_event(key_code,pressed,value); +} + +static void j_pause(JNIEnv* env,jobject self){ + ae::pause(); +} + +static void j_resume(JNIEnv* env,jobject self){ + ae::resume(); +} + +static jboolean j_is_running(JNIEnv* env,jobject self){ + return ae::is_running(); +} + +static jboolean j_is_paused(JNIEnv* env,jobject self){ + return ae::is_paused(); +} + +static void j_quit(JNIEnv* env,jobject self){ + ae::quit(); +} + +int register_Emulator(JNIEnv* env){ + static const JNINativeMethod methods[] = { + { "setup_game_path", "(Ljava/lang/String;)V", (void *) j_setup_game_path }, + { "setup_game_path", "(Laenu/emulator/Emulator$Path;)V", (void *) j_setup_game_path }, + { "setup_surface", "(Landroid/view/Surface;)V", (void *) j_setup_surface }, + { "boot", "()V", (void *) j_boot }, + { "key_event", "(IZI)V", (void *) j_key_event }, + { "quit", "()V", (void *) j_quit }, + { "is_running", "()Z", (void *) j_is_running }, + { "is_paused", "()Z", (void *) j_is_paused }, + { "pause", "()V", (void *) j_pause }, + { "resume", "()V", (void *) j_resume }, + { "change_surface", "(II)V", (void *) j_change_surface }, + }; + return env->RegisterNatives(env->FindClass("aenu/emulator/Emulator"),methods, sizeof(methods)/sizeof(methods[0])); +} \ No newline at end of file diff --git a/app/src/main/cpp/emulator.h b/app/src/main/cpp/emulator.h new file mode 100644 index 0000000..f0e89d0 --- /dev/null +++ b/app/src/main/cpp/emulator.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: WTFPL + +#ifndef APS3E_EMULATOR_H +#define APS3E_EMULATOR_H + +#include +#include + +namespace ae{ + constexpr int BOOT_TYPE_WITH_PATH=1; + constexpr int BOOT_TYPE_WITH_FD=2; + extern int boot_type; + + extern std::string boot_game_path; + extern int boot_game_fd; + + extern ANativeWindow* window; + extern int window_width; + extern int window_height; + + extern void main_thr(); + extern void key_event(int key_code,bool pressed,int value); + extern bool is_running(); + extern bool is_paused(); + extern void pause(); + extern void resume(); + extern void quit(); +} +#endif //APS3E_EMULATOR_H diff --git a/app/src/main/cpp/rpcs3/Utilities/Thread.cpp b/app/src/main/cpp/rpcs3/Utilities/Thread.cpp index 0b54270..86029b4 100644 --- a/app/src/main/cpp/rpcs3/Utilities/Thread.cpp +++ b/app/src/main/cpp/rpcs3/Utilities/Thread.cpp @@ -2433,7 +2433,7 @@ void thread_ctrl::wait_for(u64 usec, [[maybe_unused]] bool alert /* true */) usec = 50000; } -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) static thread_local struct linux_timer_handle_t { // Allocate timer only if needed (i.e. someone calls wait_for with alert and short period) @@ -2541,7 +2541,7 @@ void thread_ctrl::wait_for_accurate(u64 usec) fmt::throw_exception("thread_ctrl::wait_for_accurate: unsupported amount"); } -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) return wait_for(usec, false); #else using namespace std::chrono_literals; diff --git a/app/src/main/cpp/rpcs3/rpcs3/Emu/Cell/lv2/lv2.cpp b/app/src/main/cpp/rpcs3/rpcs3/Emu/Cell/lv2/lv2.cpp index 49b47b6..63ebd4c 100644 --- a/app/src/main/cpp/rpcs3/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/app/src/main/cpp/rpcs3/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -2160,7 +2160,7 @@ bool lv2_obj::wait_timeout(u64 usec, ppu_thread* cpu, bool scale, bool is_usleep } u64 remaining = usec - passed; -#if defined(__linux__)&&!defined(__ANDROID__) +#if defined(__linux__) // NOTE: Assumption that timer initialization has succeeded constexpr u64 host_min_quantum = 10; #else @@ -2178,7 +2178,7 @@ bool lv2_obj::wait_timeout(u64 usec, ppu_thread* cpu, bool scale, bool is_usleep { if (remaining > host_min_quantum) { -#if defined(__linux__)&&!defined(__ANDROID__) +#if defined(__linux__) // With timerslack set low, Linux is precise for all values above wait_for(remaining); #else @@ -2268,7 +2268,7 @@ void lv2_obj::notify_all() noexcept u32 notifies[total_waiters]{}; - // There may be 6 waiters, but checking them all may be performance expensive + // There may be 6 waiters, but checking them all may be performance expensive // Instead, check 2 at max, but use the CPU ID index to tell which index to start checking so the work would be distributed across all threads atomic_t* range_lock = nullptr; diff --git a/app/src/main/cpp/rpcs3/rpcs3/Emu/RSX/RSXThread.cpp b/app/src/main/cpp/rpcs3/rpcs3/Emu/RSX/RSXThread.cpp index d333cc1..d8ccffd 100644 --- a/app/src/main/cpp/rpcs3/rpcs3/Emu/RSX/RSXThread.cpp +++ b/app/src/main/cpp/rpcs3/rpcs3/Emu/RSX/RSXThread.cpp @@ -993,7 +993,7 @@ namespace rsx g_fxo->get().set_thread(std::shared_ptr>>(new named_thread>("VBlank Thread"sv, [this]() -> void { -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) constexpr u32 host_min_quantum = 10; #else constexpr u32 host_min_quantum = 500; @@ -1017,7 +1017,7 @@ namespace rsx // Calculate time remaining to that time (0 if we passed it) const u64 wait_for = current >= post_event_time ? 0 : post_event_time - current; -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) const u64 wait_sleep = wait_for; #else // Substract host operating system min sleep quantom to get sleep time @@ -2728,7 +2728,7 @@ namespace rsx while (true) { -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) // NOTE: Assumption that timer initialization has succeeded constexpr u64 host_min_quantum = 10; #else @@ -2738,7 +2738,7 @@ namespace rsx #endif if (remaining >= host_min_quantum) { -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) thread_ctrl::wait_for(remaining, false); #else // Wait on multiple of min quantum for large durations to avoid overloading low thread cpus diff --git a/app/src/main/cpp/rpcs3/rpcs3/util/atomic.cpp b/app/src/main/cpp/rpcs3/rpcs3/util/atomic.cpp index 1d58874..9a8638c 100644 --- a/app/src/main/cpp/rpcs3/rpcs3/util/atomic.cpp +++ b/app/src/main/cpp/rpcs3/rpcs3/util/atomic.cpp @@ -1,6 +1,6 @@ #include "atomic.hpp" -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) #define USE_FUTEX #elif !defined(_WIN32) #define USE_STD @@ -41,6 +41,10 @@ static bool has_waitv() return s_has_waitv; } +#elif defined(__ANDROID__) +constexpr bool has_waitv() { + return false; +} #endif #include @@ -860,7 +864,7 @@ atomic_wait_engine::wait(const void* data, u32 old_value, u64 timeout, atomic_wa uint ext_size = 0; -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) ::timespec ts{}; if (timeout + 1) { @@ -1257,7 +1261,7 @@ void atomic_wait_engine::set_one_time_use_wait_callback(bool(*cb)(u64 progress)) void atomic_wait_engine::notify_one(const void* data) { -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) if (has_waitv()) { futex(const_cast(data), FUTEX_WAKE_PRIVATE, 1); @@ -1281,7 +1285,7 @@ SAFE_BUFFERS(void) atomic_wait_engine::notify_all(const void* data) { -#if defined(__linux__) && !defined(__ANDROID__) +#if defined(__linux__) if (has_waitv()) { futex(const_cast(data), FUTEX_WAKE_PRIVATE, INT_MAX); diff --git a/app/src/main/java/aenu/aps3e/Application.java b/app/src/main/java/aenu/aps3e/Application.java index 246c7f9..9e5c886 100644 --- a/app/src/main/java/aenu/aps3e/Application.java +++ b/app/src/main/java/aenu/aps3e/Application.java @@ -13,6 +13,8 @@ import android.app.*; import java.io.*; import android.content.res.*; +import aenu.hardware.ProcessorInfo; + public class Application extends android.app.Application { @@ -33,7 +35,6 @@ public class Application extends android.app.Application public static void extractAssetsDir(Context context, String assertDir, File outputDir) { AssetManager assetManager = context.getAssets(); try { - // 创建输出目录,如果不存在 if (!outputDir.exists()) { outputDir.mkdirs(); } @@ -134,19 +135,30 @@ public class Application extends android.app.Application } } + static boolean device_support_vulkan() { + return gpu_device_name_vk!=null; + } + + static boolean should_delay_load() { + if(gpu_device_name_vk==null) + throw new RuntimeException("gpu_device_name_vk==null"); + return gpu_device_name_vk.contains("Adreno (TM) 5") + || gpu_device_name_vk.contains("Adreno (TM) 6"); + } + public static Context ctx; + public static String gpu_device_name_vk; @Override public void onCreate() { super.onCreate(); Application.ctx=this; - try { - Emulator.get.setup_env(this); - }finally { - //System.loadLibrary("e"); - } + gpu_device_name_vk= ProcessorInfo.gpu_get_physical_device_name_vk(); + Emulator.setup_preload_env(this); + if(!should_delay_load()) + Emulator.load_library(); //get_app_data_dir().mkdirs(); get_app_log_dir().mkdirs(); diff --git a/app/src/main/java/aenu/aps3e/DocumentsProvider.java b/app/src/main/java/aenu/aps3e/DocumentsProvider.java index 63fb227..27d24cb 100644 --- a/app/src/main/java/aenu/aps3e/DocumentsProvider.java +++ b/app/src/main/java/aenu/aps3e/DocumentsProvider.java @@ -1,50 +1,42 @@ -// SPDX-License-Identifier: GPL-3.0-only -// https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java +// SPDX-License-Identifier: WTFPL package aenu.aps3e; +import static android.os.Build.VERSION.SDK_INT; + import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.graphics.Point; +import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.Document; + +import androidx.annotation.Nullable; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + import android.provider.DocumentsContract.Root; -//import android.provider.DocumentsProvider; + +import android.provider.DocumentsContract.Document; import android.webkit.MimeTypeMap; -//import com.termux.R; -//import com.termux.shared.termux.TermuxConstants; - import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; +import java.util.Set; -/** - * A document provider for the Storage Access Framework which exposes the files in the - * $HOME/ directory to other apps. - *

- * Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent: - *

- * "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you - * support both of them simultaneously, your app will appear twice in the system picker UI, - * offering two different ways of accessing your stored data. This would be confusing for users." - * - http://developer.android.com/guide/topics/providers/document-provider.html#43 - */ - +//modify from https://github.com/android/storage-samples/blob/main/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java public class DocumentsProvider extends android.provider.DocumentsProvider{ - private static final String ALL_MIME_TYPES = "*/*"; + //private static final String TAG = "MyCloudProvider"; - //private static final File BASE_DIR = TermuxConstants.TERMUX_HOME_DIR; - private static final File BASE_DIR(){ - return Application.get_app_data_dir(); - } - - - // The default columns to return information about a root if no specific + // Use these as the default columns to return information about a root if no specific // columns are requested in a query. private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{ Root.COLUMN_ROOT_ID, @@ -57,7 +49,7 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ Root.COLUMN_AVAILABLE_BYTES }; - // The default columns to return information about a document if no specific + // Use these as the default columns to return information about a document if no specific // columns are requested in a query. private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{ Document.COLUMN_DOCUMENT_ID, @@ -68,33 +60,141 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ Document.COLUMN_SIZE }; - @Override - public Cursor queryRoots(String[] projection) { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); - final String applicationName = getContext().getString(R.string.app_name); + // No official policy on how many to return, but make sure you do limit the number of recent + // and search results. + private static final int MAX_SEARCH_RESULTS = 20; + private static final int MAX_LAST_MODIFIED = 5; - final MatrixCursor.RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR())); - row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR())); - row.add(Root.COLUMN_SUMMARY, null); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); - row.add(Root.COLUMN_TITLE, applicationName); - row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); - row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR().getFreeSpace()); - row.add(Root.COLUMN_ICON, R.drawable.app_icon); - return result; + private static final String ROOT = "root"; + + private static final File baseDir(){ + return Application.get_app_data_dir(); } + @Override + public boolean onCreate() { + return true; + } + @Override + public Cursor queryRoots(String[] projection) throws FileNotFoundException { + //Log.v(TAG, "queryRoots"); + + // Create a cursor with either the requested fields, or the default projection. This + // cursor is returned to the Android system picker UI and used to display all roots from + // this provider. + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + + // If user is not logged in, return an empty root cursor. This removes our provider from + // the list entirely. + /*if (!isUserLoggedIn()) { + return result; + }*/ + + // It's possible to have multiple roots (e.g. for multiple accounts in the same app) - + // just add multiple cursor rows. + // Construct one row for a root called "MyCloud". + final MatrixCursor.RowBuilder row = result.newRow(); + + row.add(Root.COLUMN_ROOT_ID, ROOT); + row.add(Root.COLUMN_SUMMARY, null); + + // FLAG_SUPPORTS_CREATE means at least one directory under the root supports creating + // documents. FLAG_SUPPORTS_RECENTS means your application's most recently used + // documents will show up in the "Recents" category. FLAG_SUPPORTS_SEARCH allows users + // to search all documents the application shares. FLAG_SUPPORTS_IS_CHILD allows + // testing parent child relationships, available after SDK 21 (Lollipop). + if (SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | + //Root.FLAG_SUPPORTS_RECENTS | + Root.FLAG_SUPPORTS_SEARCH ); + } else { + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | + //Root.FLAG_SUPPORTS_RECENTS | + Root.FLAG_SUPPORTS_SEARCH | + Root.FLAG_SUPPORTS_IS_CHILD); + } + + // COLUMN_TITLE is the root title (e.g. what will be displayed to identify your provider). + row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name)); + + // This document id must be unique within this provider and consistent across time. The + // system picker UI may save it and refer to it later. + row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(baseDir())); + + // The child MIME types are used to filter the roots and only present to the user roots + // that contain the desired type somewhere in their file hierarchy. + row.add(Root.COLUMN_MIME_TYPES, "*/*"); + row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir().getFreeSpace()); + row.add(Root.COLUMN_ICON, R.drawable.app_icon); + + return result; + } + @Override + public Cursor querySearchDocuments(String rootId, String query, String[] projection) + throws FileNotFoundException { + //Log.v(TAG, "querySearchDocuments"); + + // Create a cursor with the requested projection, or the default projection. + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(rootId); + + // This example implementation searches file names for the query and doesn't rank search + // results, so we can stop as soon as we find a sufficient number of matches. Other + // implementations might use other data about files, rather than the file name, to + // produce a match; it might also require a network call to query a remote server. + + // Iterate through all files in the file structure under the root until we reach the + // desired number of matches. + final LinkedList pending = new LinkedList(); + + // Start by adding the parent to the list of files to be processed + pending.add(parent); + + // Do while we still have unexamined files, and fewer than the max search results + while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { + // Take a file from the list of unprocessed files + final File file = pending.removeFirst(); + if (file.isDirectory()) { + // If it's a directory, add all its children to the unprocessed list + Collections.addAll(pending, file.listFiles()); + } else { + // If it's a file and it matches, add it to the result cursor. + if (file.getName().toLowerCase().contains(query)) { + includeFile(result, null, file); + } + } + } + return result; + } + @Override + public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, + CancellationSignal signal) + throws FileNotFoundException { + //Log.v(TAG, "openDocumentThumbnail"); + + final File file = getFileForDocId(documentId); + final ParcelFileDescriptor pfd = + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } @Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + //Log.v(TAG, "queryDocument"); + + // Create a cursor with the requested projection, or the default projection. + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); includeFile(result, documentId, null); return result; } @Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + /*Log.v(TAG, "queryChildDocuments, parentDocumentId: " + + parentDocumentId + + " sortOrder: " + + sortOrder);*/ + + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final File parent = getFileForDocId(parentDocumentId); for (File file : parent.listFiles()) { includeFile(result, null, file); @@ -103,47 +203,69 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ } @Override - public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { + public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException { + final File file = getFileForDocId(documentId); final int accessMode = ParcelFileDescriptor.parseMode(mode); return ParcelFileDescriptor.open(file, accessMode); } @Override - public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { - final File file = getFileForDocId(documentId); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - return new AssetFileDescriptor(pfd, 0, file.length()); + public boolean isChildDocument(String parentDocumentId, String documentId) { + return documentId.startsWith(parentDocumentId+"/"); } - @Override - public boolean onCreate() { - return true; - } + public String createDocument(String documentId, String mimeType, String displayName) + throws FileNotFoundException { - @Override - public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException { - File newFile = new File(parentDocumentId, displayName); - int noConflictId = 2; - while (newFile.exists()) { - newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")"); - } + File parent = getFileForDocId(documentId); + File file = new File(parent.getPath(), displayName); try { - boolean succeeded; + boolean created; if (Document.MIME_TYPE_DIR.equals(mimeType)) { - succeeded = newFile.mkdir(); + created = file.mkdir(); } else { - succeeded = newFile.createNewFile(); + created = file.createNewFile(); } - if (!succeeded) { - throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); + if (!created) { + throw new FileNotFoundException("Failed to create document with name " + + displayName +" and documentId " + documentId); } } catch (IOException e) { - throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); + throw new FileNotFoundException("Failed to create document with name " + + displayName +" and documentId " + documentId); } - return newFile.getPath(); + return getDocIdForFile(file); } + @Override + public String renameDocument(String documentId, String displayName) + throws FileNotFoundException { + //Log.v(TAG, "renameDocument"); + if (displayName == null) { + throw new FileNotFoundException("Failed to rename document, new name is null"); + } + // Create the destination file in the same directory as the source file + File sourceFile = getFileForDocId(documentId); + File sourceParentFile = sourceFile.getParentFile(); + if (sourceParentFile == null) { + throw new FileNotFoundException("Failed to rename document. File has no parent."); + } + File destFile = new File(sourceParentFile.getPath(), displayName); + + // Try to do the rename + try { + boolean renameSucceeded = sourceFile.renameTo(destFile); + if (!renameSucceeded) { + throw new FileNotFoundException("Failed to rename document. Renamed failed."); + } + } catch (Exception e) { + //Log.w(TAG, "Rename exception : " + e.getLocalizedMessage() + e.getCause()); + throw new FileNotFoundException("Failed to rename document. Error: " + e.getMessage()); + } + + return getDocIdForFile(destFile); + } void recursive_delete_sub_files(File dir) throws FileNotFoundException { File[] files = dir.listFiles(); if(files == null) @@ -152,10 +274,9 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ if(file.isDirectory()) { recursive_delete_sub_files(file); } - if(!file.delete()) { - throw new FileNotFoundException("Failed to delete document with id " + file.getPath()); - } - + if(!file.delete()) { + throw new FileNotFoundException("Failed to delete document with id " + file.getPath()); + } } } @Override @@ -164,102 +285,167 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ if(file.isDirectory()){ recursive_delete_sub_files(file); } - //else - { - if(!file.delete()) { - throw new FileNotFoundException("Failed to delete document with id " + documentId); + + if(!file.delete()) { + throw new FileNotFoundException("Failed to delete document with id " + documentId); + } + } + /*@Override + public void removeDocument(String documentId, String parentDocumentId) + throws FileNotFoundException { + //Log.v(TAG, "removeDocument"); + File parent = getFileForDocId(parentDocumentId); + File file = getFileForDocId(documentId); + + if (file == null) { + throw new FileNotFoundException("Failed to delete document with id " + documentId); + } + + deleteDocument(getDocIdForFile(file)); + }*/ + + void copy_file(File src_file,File dst_file) throws IOException { + FileInputStream in=new FileInputStream(src_file); + FileOutputStream out=new FileOutputStream(dst_file); + byte buf[]=new byte[16384]; + int n; + while((n=in.read(buf))!=-1) + out.write(buf,0,n); + in.close(); + out.close(); + } + void recursive_copy_sub_files(File src_dir,File dst_dir) throws IOException { + File[] files = src_dir.listFiles(); + if(files == null) + return; + for(File src_file : files){ + + File dst_file = new File(dst_dir.getPath(),src_file.getName()); + if(src_file.isDirectory()) { + if(dst_file.mkdir()) + recursive_copy_sub_files(src_file,dst_file); + else + throw new IOException("Failed to create directory " + dst_file.getPath()); } + else{ + if(dst_file.createNewFile()) { + copy_file(src_file,dst_file); + } + else + throw new IOException("Failed to create file " + dst_file.getPath()); + } + } + } + @Override + public String copyDocument(String sourceDocumentId, String targetParentDocumentId) + throws FileNotFoundException { + File src_file = getFileForDocId(sourceDocumentId); + File dst_file = new File(getFileForDocId(targetParentDocumentId).getPath(), src_file.getName()); + + try{ + if(src_file.isDirectory()){ + dst_file.mkdir(); + recursive_copy_sub_files(src_file,dst_file); + } + else{ + copy_file(src_file,dst_file); + } + } + catch(IOException e){ + throw new FileNotFoundException("Failed to copy document " + sourceDocumentId); + } + return getDocIdForFile(dst_file); + } + + @Override + public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, + String targetParentDocumentId) throws FileNotFoundException { + try { + File src_file = getFileForDocId(sourceDocumentId); + File dst_file = new File(getFileForDocId(targetParentDocumentId), src_file.getName()); + String newDocumentId = copyDocument(sourceDocumentId,getDocIdForFile(dst_file)); + deleteDocument(sourceDocumentId); + return newDocumentId; + } catch (FileNotFoundException e) { + throw new FileNotFoundException("Failed to move document " + sourceDocumentId); } } @Override public String getDocumentType(String documentId) throws FileNotFoundException { File file = getFileForDocId(documentId); - return getMimeType(file); - } - - @Override - public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); - final File parent = getFileForDocId(rootId); - - // This example implementation searches file names for the query and doesn't rank search - // results, so we can stop as soon as we find a sufficient number of matches. Other - // implementations might rank results and use other data about files, rather than the file - // name, to produce a match. - final LinkedList pending = new LinkedList<>(); - pending.add(parent); - - final int MAX_SEARCH_RESULTS = 50; - while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { - final File file = pending.removeFirst(); - // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. search - // through the whole SD card). - boolean isInsideHome; - try { - isInsideHome = file.getCanonicalPath().startsWith(BASE_DIR().getCanonicalPath()); - } catch (IOException e) { - isInsideHome = true; - } - if (isInsideHome) { - if (file.isDirectory()) { - Collections.addAll(pending, file.listFiles()); - } else { - if (file.getName().toLowerCase().contains(query)) { - includeFile(result, null, file); - } - } - } - } - - return result; - } - - @Override - public boolean isChildDocument(String parentDocumentId, String documentId) { - return documentId.startsWith(parentDocumentId); + return getTypeForFile(file); } /** - * Get the document id given a file. This document id must be consistent across time as other - * applications may save the ID and use it to reference documents later. - *

- * The reverse of @{link #getFileForDocId}. + * @param projection the requested root column projection + * @return either the requested root column projection, or the default projection if the + * requested projection is null. */ - private static String getDocIdForFile(File file) { - return file.getAbsolutePath(); + private static String[] resolveRootProjection(String[] projection) { + return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } + private static String[] resolveDocumentProjection(String[] projection) { + return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; + } + + /** - * Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). + * Get a file's MIME type + * + * @param file the File object whose type we want + * @return the MIME type of the file */ - private static File getFileForDocId(String docId) throws FileNotFoundException { - final File f = new File(docId); - if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); - return f; - } - - private static String getMimeType(File file) { + private static String getTypeForFile(File file) { if (file.isDirectory()) { return Document.MIME_TYPE_DIR; } else { - final String name = file.getName(); - final int lastDot = name.lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1).toLowerCase(); - final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mime != null) return mime; - } - return "application/octet-stream"; + return getTypeForName(file.getName()); } } + /** + * Get the MIME data type of a document, given its filename. + * + * @param name the filename of the document + * @return the MIME data type of a document + */ + private static String getTypeForName(String name) { + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + return "application/octet-stream"; + } + + /** + * Get the document ID given a File. The document id must be consistent across time. Other + * applications may save the ID and use it to reference documents later. + *

+ * This implementation is specific to this demo. It assumes only one root and is built + * directly from the file structure. However, it is possible for a document to be a child of + * multiple directories (for example "android" and "images"), in which case the file must have + * the same consistent, unique document ID in both cases. + * + * @param file the File whose document ID you want + * @return the corresponding document ID + */ + private String getDocIdForFile(File file) { + return file.getAbsolutePath(); + } + /** * Add a representation of a file to a cursor. * * @param result the cursor to modify * @param docId the document ID representing the desired file (may be null if given file) * @param file the File object representing the desired file (may be null if given docID) + * @throws FileNotFoundException */ private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException { @@ -270,16 +456,42 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ } int flags = 0; + if (file.isDirectory()) { - if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + + flags |= Document.FLAG_SUPPORTS_DELETE; + if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + flags |= Document.FLAG_SUPPORTS_RENAME; + } + if (SDK_INT >= Build.VERSION_CODES.N) { + flags |= Document.FLAG_SUPPORTS_MOVE; + flags |= Document.FLAG_SUPPORTS_COPY; + } } else if (file.canWrite()) { + // If the file is writable set FLAG_SUPPORTS_WRITE and + // FLAG_SUPPORTS_DELETE flags |= Document.FLAG_SUPPORTS_WRITE; + flags |= Document.FLAG_SUPPORTS_DELETE; + + // Add SDK specific flags if appropriate + if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + flags |= Document.FLAG_SUPPORTS_RENAME; + } + if (SDK_INT >= Build.VERSION_CODES.N) { + //flags |= Document.FLAG_SUPPORTS_REMOVE; + flags |= Document.FLAG_SUPPORTS_MOVE; + flags |= Document.FLAG_SUPPORTS_COPY; + } } - if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE; final String displayName = file.getName(); - final String mimeType = getMimeType(file); - if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL; + final String mimeType = getTypeForFile(file); + + if (mimeType.startsWith("image/")) { + // Allow the image to be represented by a thumbnail rather than an icon + flags |= Document.FLAG_SUPPORTS_THUMBNAIL; + } final MatrixCursor.RowBuilder row = result.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, docId); @@ -288,7 +500,21 @@ public class DocumentsProvider extends android.provider.DocumentsProvider{ row.add(Document.COLUMN_MIME_TYPE, mimeType); row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); row.add(Document.COLUMN_FLAGS, flags); + + // Add a custom icon row.add(Document.COLUMN_ICON, R.drawable.app_icon); } -} \ No newline at end of file + /** + * Translate your custom URI scheme into a File object. + * + * @param docId the document ID representing the desired file + * @return a File represented by the given document ID + * @throws java.io.FileNotFoundException + */ + private File getFileForDocId(String docId) throws FileNotFoundException { + File f = new File(docId); + if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); + return f; + } +} diff --git a/app/src/main/java/aenu/aps3e/Emulator.java b/app/src/main/java/aenu/aps3e/Emulator.java index 2a624a3..f66e937 100644 --- a/app/src/main/java/aenu/aps3e/Emulator.java +++ b/app/src/main/java/aenu/aps3e/Emulator.java @@ -17,12 +17,8 @@ import androidx.annotation.NonNull; import org.json.JSONException; import org.json.JSONObject; -public class Emulator +public class Emulator extends aenu.emulator.Emulator { - public static class BootException extends Exception{ - } - public static class ConfigFileException extends Exception{ - } public static class MetaInfo implements Serializable{ String eboot_path; String iso_uri; @@ -152,86 +148,7 @@ public class Emulator } } - public static class Config{ - - String config_path=null; - private long n_handle; - - /*private Config(String config_path) throws ConfigFileException - { - this.config_path=config_path; - if((n_handle=native_open_config_file(config_path))==0) - throw new ConfigFileException(); - }*/ - - private native long native_open_config(String config_str) ; - private native String native_close_config(long n_handle); - private native long native_open_config_file(String config_path) ; - private native String native_load_config_entry(long n_handle,String tag); - - private native String[] native_load_config_entry_ty_arr(long n_handle,String tag); - private native void native_save_config_entry(long n_handle,String tag,String val); - - private native void native_save_config_entry_ty_arr(long n_handle,String tag,String[] val); - private native void native_close_config_file(long n_handle,String config_path); - public static Config open_config_file(String config_path) throws ConfigFileException - { - Config config=new Config(); - config.config_path=config_path; - if((config.n_handle=config.native_open_config_file(config_path))==0) - throw new ConfigFileException(); - return config; - } - - public static Config open_config_from_string(String config_str) throws ConfigFileException - { - Config config=new Config(); - if((config.n_handle=config.native_open_config(config_str))==0) - throw new ConfigFileException(); - return config; - } - - public String load_config_entry(String tag) - { - return native_load_config_entry(n_handle,tag); - } - - - public String[] load_config_entry_ty_arr(String tag) - { - return native_load_config_entry_ty_arr(n_handle,tag); - } - public void save_config_entry(String tag,String val) - { - native_save_config_entry(n_handle,tag,val); - } - - public void save_config_entry_ty_arr(String tag,String[] val) - { - native_save_config_entry_ty_arr(n_handle,tag,val); - } - public void close_config_file() - { - if(config_path==null) - throw new RuntimeException("should use method close_config"); - native_close_config_file(n_handle,config_path); - } - - public String close_config() - { - if(config_path!=null) - throw new RuntimeException("should use method close_config_file"); - return native_close_config(n_handle); - } - } - - /*public static String nc_get_emu_proc_name(){ - return Application.get_emu_proc_name(); - }*/ - - public final static Emulator get=new Emulator(); - - public void setup_env(Application app){ + public static void setup_preload_env(Application app){ aenu.lang.System.setenv("APS3E_DATA_DIR",app.get_app_data_dir().getAbsolutePath()); aenu.lang.System.setenv("APS3E_LOG_DIR",app.get_app_log_dir().getAbsolutePath()); aenu.lang.System.setenv("APS3E_NATIVE_LIB_DIR",app.get_native_lib_dir()); @@ -561,6 +478,14 @@ public class Emulator } } + public static Emulator get=null; + + public static void load_library(){ + if(get!=null) + throw new RuntimeException("Library already loaded"); + get=new Emulator(); + System.loadLibrary("e"); + } public boolean support_custom_driver(){ try { return new File("/dev/kgsl-3d0").exists(); @@ -569,6 +494,11 @@ public class Emulator } } + public void set_env(String k,String v){ + aenu.lang.System.setenv(k,v); + } + public native void setup_game_id(String id); + public native String[] get_support_llvm_cpu_list(); @@ -611,24 +541,24 @@ public class Emulator return true; } public native boolean install_pkg(int pkg_fd); - + //public native boolean inatall_iso(String iso_path,String game_dir); - public native void setup_game_info(MetaInfo info); + /*public native void setup_game_info(MetaInfo info); public native void setup_surface(Surface sf); public native void boot() throws BootException; - + public native void key_event(int key_code,boolean pressed,int value); public native void quit(); public native boolean is_running(); public native boolean is_paused(); - + public native void pause(); - + public native void resume(); - - public native void change_surface(int w,int h); + + public native void change_surface(int w,int h);*/ public void key_event(int key_code,boolean pressed){ key_event(key_code,pressed,255); diff --git a/app/src/main/java/aenu/aps3e/EmulatorActivity.java b/app/src/main/java/aenu/aps3e/EmulatorActivity.java index 7c588c8..1be5f1a 100644 --- a/app/src/main/java/aenu/aps3e/EmulatorActivity.java +++ b/app/src/main/java/aenu/aps3e/EmulatorActivity.java @@ -30,7 +30,7 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi public static final String EXTRA_ISO_URI="iso_uri"; public static final String EXTRA_GAME_DIR="game_dir"; - static final int DELAY_ON_CREATE=0xaa01; + static final int DELAY_ON_CREATE=0xaeae0001; private SparseIntArray keysMap = new SparseIntArray(); private GameFrameView gv; @@ -38,16 +38,6 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi private VibrationEffect vibrationEffect=null; boolean started=false; - void setup_env(String serial){ - File custom_cfg=Application.get_custom_cfg_file(serial); - if(custom_cfg.exists()) - aenu.lang.System.setenv("APS3E_CUSTOM_CONFIG_YAML_PATH",custom_cfg.getAbsolutePath()); - - boolean enable_log=getSharedPreferences("debug",MODE_PRIVATE).getBoolean("enable_log",false); - aenu.lang.System.setenv("APS3E_ENABLE_LOG",Boolean.toString(enable_log)); - } - - int open_iso_fd(String iso_uri) { try { ParcelFileDescriptor pfd_ = getContentResolver().openFileDescriptor(Uri.parse(iso_uri), "r"); @@ -69,22 +59,28 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi delay_dialog.dismiss(); delay_dialog=null; } + on_create(); + return true; + } + }); - System.loadLibrary("e"); - setContentView(R.layout.emulator_view); - gv=(GameFrameView)findViewById(R.id.emulator_view); + void on_create(){ + setContentView(R.layout.emulator_view); + gv=(GameFrameView)findViewById(R.id.emulator_view); - gv.setFocusable(true); - gv.setFocusableInTouchMode(true); - gv.requestFocus(); + gv.setFocusable(true); + gv.setFocusableInTouchMode(true); + gv.requestFocus(); - gv.setOnGenericMotionListener(EmulatorActivity.this); + gv.setOnGenericMotionListener(EmulatorActivity.this); - gv.getHolder().addCallback(EmulatorActivity.this); + gv.getHolder().addCallback(EmulatorActivity.this); - load_key_map_and_vibrator(); + load_key_map_and_vibrator(); - Emulator.MetaInfo meta_info; + Emulator.MetaInfo meta_info=null; + //get meta info + { try{ if((meta_info=(Emulator.MetaInfo) getIntent().getSerializableExtra("meta_info"))!=null){ @@ -107,37 +103,58 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi } }catch (Exception e) { Toast.makeText(EmulatorActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); - return true; } - setup_env(meta_info.serial); - Emulator.get.setup_game_info(meta_info); - - return true; } - }); + + //setenv + { + File custom_cfg=Application.get_custom_cfg_file(meta_info.serial); + if(custom_cfg.exists()) + Emulator.get.set_env("APS3E_CUSTOM_CONFIG_YAML_PATH",custom_cfg.getAbsolutePath()); + + boolean enable_log=getSharedPreferences("debug",MODE_PRIVATE).getBoolean("enable_log",false); + Emulator.get.set_env("APS3E_ENABLE_LOG",Boolean.toString(enable_log)); + } + + //setup game path + { + if(meta_info.eboot_path!=null) + Emulator.get.setup_game_path(meta_info.eboot_path); + else if(meta_info.iso_uri!=null) + Emulator.get.setup_game_path(aenu.emulator.Emulator.Path.from(meta_info.iso_uri, meta_info.iso_fd)); + else + throw new RuntimeException("Failed to get meta info"); + } + + Emulator.get.setup_game_id(meta_info.serial); + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); VirtualPadEdit.enable_fullscreen(getWindow()); - if(ProcessorInfo.gpu_get_physical_device_name_vk().contains("Adreno (TM) 5") - || ProcessorInfo.gpu_get_physical_device_name_vk().contains("Adreno (TM) 6")) { - delay_dialog=ProgressTask.create_progress_dialog( this,getString(R.string.loading)); - delay_dialog.show(); - new Thread() { - @Override - public void run() { - try { - Thread.sleep(500); - delay_on_create.sendEmptyMessage(DELAY_ON_CREATE); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - }.start(); + + if(!Application.should_delay_load()){ + on_create(); return; } - delay_on_create.sendEmptyMessage(DELAY_ON_CREATE); + + delay_dialog=ProgressTask.create_progress_dialog( this,getString(R.string.loading)); + delay_dialog.show(); + new Thread() { + @Override + public void run() { + try { + Thread.sleep(500); + Emulator.load_library(); + Thread.sleep(100); + delay_on_create.sendEmptyMessage(DELAY_ON_CREATE); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }.start(); + return; } @Override @@ -145,6 +162,30 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi super.onStart(); } + void show_closing_dialog(){ + Dialog closing_dialog=ProgressTask.create_progress_dialog( this,null); + closing_dialog.show(); + Handler handler=new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(@NonNull Message msg) { + closing_dialog.dismiss(); + finish(); + return true; + } + }); + + new Thread(){ + @Override + public void run() { + try { + Emulator.get.quit(); + handler.sendEmptyMessage(0); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }.start(); + } @Override public void onBackPressed() { @@ -158,7 +199,9 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi @Override public void onClick(DialogInterface p1, int p2) { - try{ + p1.cancel(); + show_closing_dialog(); + /*try{ //if(Emulator.get.is_running()) //Emulator.get.pause(); Emulator.get.quit(); @@ -166,7 +209,7 @@ public class EmulatorActivity extends Activity implements View.OnGenericMotionLi finally{ p1.cancel(); finish(); - } + }*/ } diff --git a/app/src/main/java/aenu/aps3e/InnerDocumentsProvider.java b/app/src/main/java/aenu/aps3e/InnerDocumentsProvider.java index 2bbc6fe..b2b267a 100644 --- a/app/src/main/java/aenu/aps3e/InnerDocumentsProvider.java +++ b/app/src/main/java/aenu/aps3e/InnerDocumentsProvider.java @@ -1,50 +1,42 @@ -// SPDX-License-Identifier: GPL-3.0-only -// https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java +// SPDX-License-Identifier: WTFPL package aenu.aps3e; +import static android.os.Build.VERSION.SDK_INT; + import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.graphics.Point; +import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.Document; + +import androidx.annotation.Nullable; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + import android.provider.DocumentsContract.Root; -//import android.provider.DocumentsProvider; + +import android.provider.DocumentsContract.Document; import android.webkit.MimeTypeMap; -//import com.termux.R; -//import com.termux.shared.termux.TermuxConstants; - import java.io.File; -import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; +import java.util.Set; -/** - * A document provider for the Storage Access Framework which exposes the files in the - * $HOME/ directory to other apps. - *

- * Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent: - *

- * "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you - * support both of them simultaneously, your app will appear twice in the system picker UI, - * offering two different ways of accessing your stored data. This would be confusing for users." - * - http://developer.android.com/guide/topics/providers/document-provider.html#43 - */ - +//modify from https://github.com/android/storage-samples/blob/main/StorageProvider/Application/src/main/java/com/example/android/storageprovider/MyCloudProvider.java public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ - private static final String ALL_MIME_TYPES = "*/*"; + //private static final String TAG = "MyCloudProvider"; - //private static final File BASE_DIR = TermuxConstants.TERMUX_HOME_DIR; - private static final File BASE_DIR(){ - return Application.get_internal_data_dir(); - } - - - // The default columns to return information about a root if no specific + // Use these as the default columns to return information about a root if no specific // columns are requested in a query. private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{ Root.COLUMN_ROOT_ID, @@ -57,7 +49,7 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ Root.COLUMN_AVAILABLE_BYTES }; - // The default columns to return information about a document if no specific + // Use these as the default columns to return information about a document if no specific // columns are requested in a query. private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{ Document.COLUMN_DOCUMENT_ID, @@ -68,33 +60,141 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ Document.COLUMN_SIZE }; - @Override - public Cursor queryRoots(String[] projection) { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); - final String title = getContext().getString(R.string.app_name) +" Inner"; + // No official policy on how many to return, but make sure you do limit the number of recent + // and search results. + private static final int MAX_SEARCH_RESULTS = 20; + private static final int MAX_LAST_MODIFIED = 5; - final MatrixCursor.RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR())); - row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR())); - row.add(Root.COLUMN_SUMMARY, null); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); - row.add(Root.COLUMN_TITLE, title); - row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); - row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR().getFreeSpace()); - row.add(Root.COLUMN_ICON, R.drawable.app_icon); - return result; + private static final String ROOT = "root"; + + private static final File baseDir(){ + return Application.get_internal_data_dir(); } + @Override + public boolean onCreate() { + return true; + } + @Override + public Cursor queryRoots(String[] projection) throws FileNotFoundException { + //Log.v(TAG, "queryRoots"); + + // Create a cursor with either the requested fields, or the default projection. This + // cursor is returned to the Android system picker UI and used to display all roots from + // this provider. + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + + // If user is not logged in, return an empty root cursor. This removes our provider from + // the list entirely. + /*if (!isUserLoggedIn()) { + return result; + }*/ + + // It's possible to have multiple roots (e.g. for multiple accounts in the same app) - + // just add multiple cursor rows. + // Construct one row for a root called "MyCloud". + final MatrixCursor.RowBuilder row = result.newRow(); + + row.add(Root.COLUMN_ROOT_ID, ROOT); + row.add(Root.COLUMN_SUMMARY, null); + + // FLAG_SUPPORTS_CREATE means at least one directory under the root supports creating + // documents. FLAG_SUPPORTS_RECENTS means your application's most recently used + // documents will show up in the "Recents" category. FLAG_SUPPORTS_SEARCH allows users + // to search all documents the application shares. FLAG_SUPPORTS_IS_CHILD allows + // testing parent child relationships, available after SDK 21 (Lollipop). + if (SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | + //Root.FLAG_SUPPORTS_RECENTS | + Root.FLAG_SUPPORTS_SEARCH ); + } else { + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | + //Root.FLAG_SUPPORTS_RECENTS | + Root.FLAG_SUPPORTS_SEARCH | + Root.FLAG_SUPPORTS_IS_CHILD); + } + + // COLUMN_TITLE is the root title (e.g. what will be displayed to identify your provider). + row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name)); + + // This document id must be unique within this provider and consistent across time. The + // system picker UI may save it and refer to it later. + row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(baseDir())); + + // The child MIME types are used to filter the roots and only present to the user roots + // that contain the desired type somewhere in their file hierarchy. + row.add(Root.COLUMN_MIME_TYPES, "*/*"); + row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir().getFreeSpace()); + row.add(Root.COLUMN_ICON, R.drawable.app_icon); + + return result; + } + @Override + public Cursor querySearchDocuments(String rootId, String query, String[] projection) + throws FileNotFoundException { + //Log.v(TAG, "querySearchDocuments"); + + // Create a cursor with the requested projection, or the default projection. + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(rootId); + + // This example implementation searches file names for the query and doesn't rank search + // results, so we can stop as soon as we find a sufficient number of matches. Other + // implementations might use other data about files, rather than the file name, to + // produce a match; it might also require a network call to query a remote server. + + // Iterate through all files in the file structure under the root until we reach the + // desired number of matches. + final LinkedList pending = new LinkedList(); + + // Start by adding the parent to the list of files to be processed + pending.add(parent); + + // Do while we still have unexamined files, and fewer than the max search results + while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { + // Take a file from the list of unprocessed files + final File file = pending.removeFirst(); + if (file.isDirectory()) { + // If it's a directory, add all its children to the unprocessed list + Collections.addAll(pending, file.listFiles()); + } else { + // If it's a file and it matches, add it to the result cursor. + if (file.getName().toLowerCase().contains(query)) { + includeFile(result, null, file); + } + } + } + return result; + } + @Override + public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, + CancellationSignal signal) + throws FileNotFoundException { + //Log.v(TAG, "openDocumentThumbnail"); + + final File file = getFileForDocId(documentId); + final ParcelFileDescriptor pfd = + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } @Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + //Log.v(TAG, "queryDocument"); + + // Create a cursor with the requested projection, or the default projection. + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); includeFile(result, documentId, null); return result; } @Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + /*Log.v(TAG, "queryChildDocuments, parentDocumentId: " + + parentDocumentId + + " sortOrder: " + + sortOrder);*/ + + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final File parent = getFileForDocId(parentDocumentId); for (File file : parent.listFiles()) { includeFile(result, null, file); @@ -103,47 +203,69 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ } @Override - public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { + public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException { + final File file = getFileForDocId(documentId); final int accessMode = ParcelFileDescriptor.parseMode(mode); return ParcelFileDescriptor.open(file, accessMode); } @Override - public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { - final File file = getFileForDocId(documentId); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - return new AssetFileDescriptor(pfd, 0, file.length()); + public boolean isChildDocument(String parentDocumentId, String documentId) { + return documentId.startsWith(parentDocumentId+"/"); } - @Override - public boolean onCreate() { - return true; - } + public String createDocument(String documentId, String mimeType, String displayName) + throws FileNotFoundException { - @Override - public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException { - File newFile = new File(parentDocumentId, displayName); - int noConflictId = 2; - while (newFile.exists()) { - newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")"); - } + File parent = getFileForDocId(documentId); + File file = new File(parent.getPath(), displayName); try { - boolean succeeded; + boolean created; if (Document.MIME_TYPE_DIR.equals(mimeType)) { - succeeded = newFile.mkdir(); + created = file.mkdir(); } else { - succeeded = newFile.createNewFile(); + created = file.createNewFile(); } - if (!succeeded) { - throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); + if (!created) { + throw new FileNotFoundException("Failed to create document with name " + + displayName +" and documentId " + documentId); } } catch (IOException e) { - throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); + throw new FileNotFoundException("Failed to create document with name " + + displayName +" and documentId " + documentId); } - return newFile.getPath(); + return getDocIdForFile(file); } + @Override + public String renameDocument(String documentId, String displayName) + throws FileNotFoundException { + //Log.v(TAG, "renameDocument"); + if (displayName == null) { + throw new FileNotFoundException("Failed to rename document, new name is null"); + } + // Create the destination file in the same directory as the source file + File sourceFile = getFileForDocId(documentId); + File sourceParentFile = sourceFile.getParentFile(); + if (sourceParentFile == null) { + throw new FileNotFoundException("Failed to rename document. File has no parent."); + } + File destFile = new File(sourceParentFile.getPath(), displayName); + + // Try to do the rename + try { + boolean renameSucceeded = sourceFile.renameTo(destFile); + if (!renameSucceeded) { + throw new FileNotFoundException("Failed to rename document. Renamed failed."); + } + } catch (Exception e) { + //Log.w(TAG, "Rename exception : " + e.getLocalizedMessage() + e.getCause()); + throw new FileNotFoundException("Failed to rename document. Error: " + e.getMessage()); + } + + return getDocIdForFile(destFile); + } void recursive_delete_sub_files(File dir) throws FileNotFoundException { File[] files = dir.listFiles(); if(files == null) @@ -155,7 +277,6 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ if(!file.delete()) { throw new FileNotFoundException("Failed to delete document with id " + file.getPath()); } - } } @Override @@ -164,102 +285,167 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ if(file.isDirectory()){ recursive_delete_sub_files(file); } - //else - { - if(!file.delete()) { - throw new FileNotFoundException("Failed to delete document with id " + documentId); + + if(!file.delete()) { + throw new FileNotFoundException("Failed to delete document with id " + documentId); + } + } + /*@Override + public void removeDocument(String documentId, String parentDocumentId) + throws FileNotFoundException { + //Log.v(TAG, "removeDocument"); + File parent = getFileForDocId(parentDocumentId); + File file = getFileForDocId(documentId); + + if (file == null) { + throw new FileNotFoundException("Failed to delete document with id " + documentId); + } + + deleteDocument(getDocIdForFile(file)); + }*/ + + void copy_file(File src_file,File dst_file) throws IOException { + FileInputStream in=new FileInputStream(src_file); + FileOutputStream out=new FileOutputStream(dst_file); + byte buf[]=new byte[16384]; + int n; + while((n=in.read(buf))!=-1) + out.write(buf,0,n); + in.close(); + out.close(); + } + void recursive_copy_sub_files(File src_dir,File dst_dir) throws IOException { + File[] files = src_dir.listFiles(); + if(files == null) + return; + for(File src_file : files){ + + File dst_file = new File(dst_dir.getPath(),src_file.getName()); + if(src_file.isDirectory()) { + if(dst_file.mkdir()) + recursive_copy_sub_files(src_file,dst_file); + else + throw new IOException("Failed to create directory " + dst_file.getPath()); } + else{ + if(dst_file.createNewFile()) { + copy_file(src_file,dst_file); + } + else + throw new IOException("Failed to create file " + dst_file.getPath()); + } + } + } + @Override + public String copyDocument(String sourceDocumentId, String targetParentDocumentId) + throws FileNotFoundException { + File src_file = getFileForDocId(sourceDocumentId); + File dst_file = new File(getFileForDocId(targetParentDocumentId).getPath(), src_file.getName()); + + try{ + if(src_file.isDirectory()){ + dst_file.mkdir(); + recursive_copy_sub_files(src_file,dst_file); + } + else{ + copy_file(src_file,dst_file); + } + } + catch(IOException e){ + throw new FileNotFoundException("Failed to copy document " + sourceDocumentId); + } + return getDocIdForFile(dst_file); + } + + @Override + public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, + String targetParentDocumentId) throws FileNotFoundException { + try { + File src_file = getFileForDocId(sourceDocumentId); + File dst_file = new File(getFileForDocId(targetParentDocumentId), src_file.getName()); + String newDocumentId = copyDocument(sourceDocumentId,getDocIdForFile(dst_file)); + deleteDocument(sourceDocumentId); + return newDocumentId; + } catch (FileNotFoundException e) { + throw new FileNotFoundException("Failed to move document " + sourceDocumentId); } } @Override public String getDocumentType(String documentId) throws FileNotFoundException { File file = getFileForDocId(documentId); - return getMimeType(file); - } - - @Override - public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); - final File parent = getFileForDocId(rootId); - - // This example implementation searches file names for the query and doesn't rank search - // results, so we can stop as soon as we find a sufficient number of matches. Other - // implementations might rank results and use other data about files, rather than the file - // name, to produce a match. - final LinkedList pending = new LinkedList<>(); - pending.add(parent); - - final int MAX_SEARCH_RESULTS = 50; - while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { - final File file = pending.removeFirst(); - // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. search - // through the whole SD card). - boolean isInsideHome; - try { - isInsideHome = file.getCanonicalPath().startsWith(BASE_DIR().getCanonicalPath()); - } catch (IOException e) { - isInsideHome = true; - } - if (isInsideHome) { - if (file.isDirectory()) { - Collections.addAll(pending, file.listFiles()); - } else { - if (file.getName().toLowerCase().contains(query)) { - includeFile(result, null, file); - } - } - } - } - - return result; - } - - @Override - public boolean isChildDocument(String parentDocumentId, String documentId) { - return documentId.startsWith(parentDocumentId); + return getTypeForFile(file); } /** - * Get the document id given a file. This document id must be consistent across time as other - * applications may save the ID and use it to reference documents later. - *

- * The reverse of @{link #getFileForDocId}. + * @param projection the requested root column projection + * @return either the requested root column projection, or the default projection if the + * requested projection is null. */ - private static String getDocIdForFile(File file) { - return file.getAbsolutePath(); + private static String[] resolveRootProjection(String[] projection) { + return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } + private static String[] resolveDocumentProjection(String[] projection) { + return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; + } + + /** - * Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). + * Get a file's MIME type + * + * @param file the File object whose type we want + * @return the MIME type of the file */ - private static File getFileForDocId(String docId) throws FileNotFoundException { - final File f = new File(docId); - if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); - return f; - } - - private static String getMimeType(File file) { + private static String getTypeForFile(File file) { if (file.isDirectory()) { return Document.MIME_TYPE_DIR; } else { - final String name = file.getName(); - final int lastDot = name.lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1).toLowerCase(); - final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mime != null) return mime; - } - return "application/octet-stream"; + return getTypeForName(file.getName()); } } + /** + * Get the MIME data type of a document, given its filename. + * + * @param name the filename of the document + * @return the MIME data type of a document + */ + private static String getTypeForName(String name) { + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + return "application/octet-stream"; + } + + /** + * Get the document ID given a File. The document id must be consistent across time. Other + * applications may save the ID and use it to reference documents later. + *

+ * This implementation is specific to this demo. It assumes only one root and is built + * directly from the file structure. However, it is possible for a document to be a child of + * multiple directories (for example "android" and "images"), in which case the file must have + * the same consistent, unique document ID in both cases. + * + * @param file the File whose document ID you want + * @return the corresponding document ID + */ + private String getDocIdForFile(File file) { + return file.getAbsolutePath(); + } + /** * Add a representation of a file to a cursor. * * @param result the cursor to modify * @param docId the document ID representing the desired file (may be null if given file) * @param file the File object representing the desired file (may be null if given docID) + * @throws FileNotFoundException */ private void includeFile(MatrixCursor result, String docId, File file) throws FileNotFoundException { @@ -270,16 +456,42 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ } int flags = 0; + if (file.isDirectory()) { - if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + + flags |= Document.FLAG_SUPPORTS_DELETE; + if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + flags |= Document.FLAG_SUPPORTS_RENAME; + } + if (SDK_INT >= Build.VERSION_CODES.N) { + flags |= Document.FLAG_SUPPORTS_MOVE; + flags |= Document.FLAG_SUPPORTS_COPY; + } } else if (file.canWrite()) { + // If the file is writable set FLAG_SUPPORTS_WRITE and + // FLAG_SUPPORTS_DELETE flags |= Document.FLAG_SUPPORTS_WRITE; + flags |= Document.FLAG_SUPPORTS_DELETE; + + // Add SDK specific flags if appropriate + if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + flags |= Document.FLAG_SUPPORTS_RENAME; + } + if (SDK_INT >= Build.VERSION_CODES.N) { + //flags |= Document.FLAG_SUPPORTS_REMOVE; + flags |= Document.FLAG_SUPPORTS_MOVE; + flags |= Document.FLAG_SUPPORTS_COPY; + } } - if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE; final String displayName = file.getName(); - final String mimeType = getMimeType(file); - if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL; + final String mimeType = getTypeForFile(file); + + if (mimeType.startsWith("image/")) { + // Allow the image to be represented by a thumbnail rather than an icon + flags |= Document.FLAG_SUPPORTS_THUMBNAIL; + } final MatrixCursor.RowBuilder row = result.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, docId); @@ -288,7 +500,21 @@ public class InnerDocumentsProvider extends android.provider.DocumentsProvider{ row.add(Document.COLUMN_MIME_TYPE, mimeType); row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); row.add(Document.COLUMN_FLAGS, flags); + + // Add a custom icon row.add(Document.COLUMN_ICON, R.drawable.app_icon); } -} \ No newline at end of file + /** + * Translate your custom URI scheme into a File object. + * + * @param docId the document ID representing the desired file + * @return a File represented by the given document ID + * @throws java.io.FileNotFoundException + */ + private File getFileForDocId(String docId) throws FileNotFoundException { + File f = new File(docId); + if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); + return f; + } +} diff --git a/app/src/main/java/aenu/aps3e/ProgressTask.java b/app/src/main/java/aenu/aps3e/ProgressTask.java index c20138f..6667fbf 100644 --- a/app/src/main/java/aenu/aps3e/ProgressTask.java +++ b/app/src/main/java/aenu/aps3e/ProgressTask.java @@ -61,6 +61,7 @@ public class ProgressTask { }; static final Dialog create_progress_dialog(Context context, String progress_message){ ProgressDialog d=new ProgressDialog(context); + if(progress_message!=null) d.setMessage(progress_message); d.setCanceledOnTouchOutside(false); d.setOnKeyListener(new DialogInterface.OnKeyListener(){ diff --git a/app/src/main/java/aenu/aps3e/QuickStartActivity.java b/app/src/main/java/aenu/aps3e/QuickStartActivity.java index 030ae5e..223d964 100644 --- a/app/src/main/java/aenu/aps3e/QuickStartActivity.java +++ b/app/src/main/java/aenu/aps3e/QuickStartActivity.java @@ -47,7 +47,7 @@ import kotlin.contracts.Returns; public class QuickStartActivity extends AppCompatActivity { static final String ACTION_REENTRY="aenu.intent.action.REENTRY_QUISK_START"; - static final int DELAY_ON_CREATE=0xaa00; + static final int DELAY_ON_CREATE=0xaeae0000; List layout_list; ProgressBar progress; @@ -64,42 +64,48 @@ public class QuickStartActivity extends AppCompatActivity { delay_dialog.dismiss(); delay_dialog=null; } - System.loadLibrary("e"); - if(!ACTION_REENTRY.equals(getIntent().getAction())&&Application.get_default_config_file().exists()){ - goto_main_activity(); - return true; - } - - getSupportActionBar().setTitle(R.string.welcome); - setContentView(R.layout.activity_quick_start); - MainActivity.mk_dirs(); - try{config=Emulator.Config.open_config_from_string(load_default_config_str(QuickStartActivity.this));}catch (Exception e){} - init_layout_list(); - select_layout(0); + on_create(); return true; } }); + + void on_create(){ + if(!ACTION_REENTRY.equals(getIntent().getAction())&&Application.get_default_config_file().exists()){ + goto_main_activity(); + return; + } + + getSupportActionBar().setTitle(R.string.welcome); + setContentView(R.layout.activity_quick_start); + MainActivity.mk_dirs(); + try{config=Emulator.Config.open_config_from_string(load_default_config_str(QuickStartActivity.this));}catch (Exception e){} + init_layout_list(); + select_layout(0); + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if(ProcessorInfo.gpu_get_physical_device_name_vk().contains("Adreno (TM) 5") - || ProcessorInfo.gpu_get_physical_device_name_vk().contains("Adreno (TM) 6")){ - delay_dialog=ProgressTask.create_progress_dialog( this,getString(R.string.loading)); - delay_dialog.show(); - new Thread(){ - @Override - public void run() { - try { - Thread.sleep(500); - delay_on_create.sendEmptyMessage(DELAY_ON_CREATE); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - }.start(); + + if(!Application.should_delay_load()){ + on_create(); return; } - delay_on_create.sendEmptyMessage(DELAY_ON_CREATE); + + delay_dialog=ProgressTask.create_progress_dialog( this,getString(R.string.loading)); + delay_dialog.show(); + new Thread(){ + @Override + public void run() { + try { + Thread.sleep(500); + Emulator.load_library(); + Thread.sleep(100); + delay_on_create.sendEmptyMessage(DELAY_ON_CREATE); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }.start(); } @Override @@ -233,11 +239,33 @@ public class QuickStartActivity extends AppCompatActivity { }); } + void set_not_support_vulkan_layout(){ + layout_list.get(0).setVisibility(View.VISIBLE); + + progress.setMax(1); + progress.setProgress(1); + + findViewById(R.id.prev_step).setVisibility(View.GONE); + + ((Button)findViewById(R.id.next_step)).setText(R.string.quit); + ((Button)findViewById(R.id.next_step)).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) {QuickStartActivity.this.finish();} + }); + + ((TextView)layout_list.get(0).findViewById(R.id.welcome_text2)) + .setText(R.string.device_unsupport_vulkan_msg); + } void select_layout(int pos){ for(int i=0;i - - - Font - Disable Sampling Skip + جهازك لا يدعم Vulkan ولا يفي الحد الأدنى من المتطلبات لتشغيل هذا التطبيق، يرجى الخروج. + الخط + تعطيل تخطي أخذ العينات استخدام النوى (PPU) استخدام النوى (SPU) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9ed14dc..109fb1f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,5 +1,7 @@ + Ihr Gerät unterstützt Vulkan nicht und erfüllt nicht die mindestens erforderlichen Anforderungen, um diese Anwendung auszuführen. Bitte beenden Sie. + Kerne verwenden (PPU) Kerne verwenden (SPU) Kerne verwenden (RSX) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2b69ab9..cb784c5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,5 +1,7 @@ +Su dispositivo no admite Vulkan y no cumple con los requisitos mínimos para ejecutar esta aplicación, por favor, salga. + Usar Núcleos(PPU) Usar Núcleos(SPU) Usar Núcleos(RSX) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 30c229f..e57803e 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,5 +1,8 @@ + + دستگاه شما از Vulkan پشتیبانی نمی‌کند و حداقل نیازمندی‌های لازم را برای اجرای این برنامه فراهم نمی‌کند، لطفاً خارج شوید. + استفاده از هسته‌ها (PPU) استفاده از هسته‌ها (SPU) استفاده از هسته‌ها (RSX) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2fa6f90..73c43b1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,19 +1,19 @@ - - Pad Handler Mode - Pad Handler Sleep - Add Friend - RPCN: %0 logged in - RPCN: %0 logged out - Start on the PS Vita system.\nIf you have not installed, go to [Remote Play] on the PS Vita system and start [Cross-Controller] from the LiveArea™ screen. - Thanks Ruban for all the ICONS\n\n - Thanks to SRC267 (Sting) for managing reddit/discord\n\n - Thanks to 妖妖 for test\n\n - Thanks to 再见某人 for donating GPD XP, which has been a great help in the testing process.\n\n - Thanks to 再见某人 for donating the development equipment, which has been instrumental in advancing the project.\n\n - Thanks to the following people who provided financial support for this project (Initial stage,sort by date):\n\n + Votre appareil ne prend pas en charge Vulkan et ne satisfait pas aux exigences minimales requises pour exécuter cette application. Veuillez quitter. + Mode du gestionnaire de manette + Veille du gestionnaire de manette + Ajouter un ami + RPCN : %0 s‘est connecté + RPCN : %0 s’est déconnecté + + Utiliser les coeurs (PPU) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index e2b7149..6f4eb30 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,5 +1,8 @@ + + आपका डिवाइस Vulkan का समर्थन नहीं करता है और इस एप्लिकेशन को चलाने के लिए न्यूनतम आवश्यकताओं को पूरा नहीं कर सकता है, कृपया बाहर निकलें। + पीपीयू कोर्स उपयोग करें एसपीयू कोर्स उपयोग करें आरएसएक्स कोर्स उपयोग करें diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 7f3d256..dcf0017 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -1,5 +1,6 @@ + Perangkat Anda tidak mendukung Vulkan dan tidak dapat memenuhi persyaratan minimum untuk menjalankan aplikasi ini, silakan keluar. Gunakan Core(PPU) Gunakan Core(SPU) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6c5dc8e..a44d310 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,14 +1,12 @@ - - - Thanks Ruban for all the ICONS\n\n - Thanks to SRC267 (Sting) for managing reddit/discord\n\n - Thanks to 妖妖 for test\n\n - Thanks to 再见某人 for donating GPD XP, which has been a great help in the testing process.\n\n - Thanks to 再见某人 for donating the development equipment, which has been instrumental in advancing the project.\n\n - Thanks to the following people who provided financial support for this project (Initial stage,sort by date):\n\n - + Il tuo dispositivo non supporta Vulkan e non soddisfa i requisiti minimi necessari per eseguire questa applicazione, per favore esci. + Utilizza Core(PPU) Utilizza Core(SPU) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index d6198eb..c1e4424 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2,6 +2,8 @@ + お使いの端末はVulkanをサポートしていません。このアプリケーションを実行するために必要な最低限の要件を満たしていませんので、終了してください。 + コアを使用する(PPU) コアを使用する(SPU) コアを使用する(RSX) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4562e8a..c11753e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1,5 +1,6 @@ +사용 중인 기기는 Vulkan을 지원하지 않으며 이 애플리케이션을 실행하기 위한 최소 요구 사항을 충족시키지 못합니다. 종료하십시오. 코어(PPU) 사용 코어(SPU) 사용 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 8fb2b06..95977e0 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -1,5 +1,7 @@ +Peranti anda tidak menyokong Vulkan dan tidak memenuhi keperluan minima untuk menjalankan aplikasi ini, sila keluar. + Gunakan Core(PPU) Gunakan Core(SPU) Gunakan Core(RSX) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d9becc2..83a7039 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,6 +1,8 @@ - aPS3e + + Twoje urządzenie nie obsługuje Vulkan oraz nie spełnia minimalnych wymagań koniecznych do uruchomienia tej aplikacji, proszę wyjść. + Używaj rdzeni (PPU) Używaj rdzeni (SPU) Używaj rdzeni (RSX) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index fd7bde5..5a12264 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,5 +1,7 @@ + Seu dispositivo não suporta Vulkan e não atende aos requisitos mínimos para executar este aplicativo, por favor, saia. + Usar Núcleos(PPU) Usar Núcleos(SPU) Usar Núcleos(RSX) diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 4dc2d52..16b4ab4 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -1,5 +1,8 @@ + + O seu dispositivo não suporta Vulkan e não cumpre os requisitos mínimos para executar esta aplicação, por favor saia. + Usar Núcleos(PPU) Usar Núcleos(SPU) Usar Núcleos(RSX) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2dff883..bdb67e9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,5 +1,7 @@ +Ваше устройство не поддерживает Vulkan и не соответствует минимальным требованиям для запуска этого приложения, пожалуйста, выйдите. + Использовать ядра (PPU) Использовать ядра (SPU) Использовать ядра (RSX) diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 6f00bc3..633af44 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1,5 +1,6 @@ +อุปกรณ์ของคุณไม่รองรับ Vulkan และไม่สามารถปฏิบัติตามข้อกำหนดขั้นต่ำเพื่อเรียกใช้งานแอปพลิเคชันนี้ได้ กรุณาออก ใช้คอร์ (PPU) ใช้คอร์ (SPU) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 7ef0000..3c45442 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,5 +1,6 @@ +Cihazınız Vulkan desteklemiyor ve bu uygulamayı çalıştırmak için gerekli olan minimum gereksinimleri karşılamıyor, lütfen çıkın. Çekirdekleri Kullan(PPU) Çekirdekleri Kullan(SPU) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 3b23b1e..74cef8e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1,5 +1,7 @@ +Ваш пристрій не підтримує Vulkan і не відповідає мінімальним вимогам для запуску цього додатка, будь ласка, вийдіть. + Використовувати ядра (PPU) Використовувати ядра (SPU) Використовувати ядра (RSX) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 9286c06..6b00142 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,5 +1,7 @@ +Thiết bị của bạn không hỗ trợ Vulkan và không đáp ứng được các yêu cầu tối thiểu để chạy ứng dụng này, vui lòng thoát. + Sử dụng Nhân (PPU) Sử dụng Nhân (SPU) Sử dụng Nhân (RSX) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 43240ac..c5db920 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,6 +1,8 @@ + 您的设备不支持 Vulkan,无法满足运行此应用程序的最低要求,请退出。 + 使用核心(PPU) 使用核心(SPU) 使用核心(RSX) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 256b0f4..4d40825 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,7 +1,7 @@ - + 您的設備不支援 Vulkan,無法滿足運行此應用程序的最低要求,請退出。 使用核心(PPU) 使用核心(SPU) 使用核心(RSX) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a206e0e..cc8cd81 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,7 @@ aPS3e + Your device does not support Vulkan and cannot meet the minimum requirements to run this application, please exit. Use Cores(PPU) Use Cores(SPU) Use Cores(RSX)