This commit is contained in:
aenu
2025-08-18 23:04:01 +08:00
parent 89ef2666dd
commit 54c4eff2a0
45 changed files with 2008 additions and 1202 deletions
+13
View File
@@ -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/
+1
View File
@@ -51,6 +51,7 @@ target_sources(emu
cpuinfo.cpp
glsl2spv.cpp
meminfo.cpp
emulator.cpp
)
target_link_libraries(emu PRIVATE rpcs3_emu)
+22 -143
View File
@@ -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<named_thread<pad_thread>>();
if(pad_thr){
auto xx=pad_thr->get_handlers().at(pad_handler::keyboard);
std::shared_ptr<AndroidVirtualPadHandler> padh=std::dynamic_pointer_cast<AndroidVirtualPadHandler>(xx);
padh->Key(static_cast<u32>(key_code), static_cast<bool>(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");
+1 -195
View File
@@ -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<std::string>().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<std::string>().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<std::string>().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<std::string> v=node.as<std::vector<std::string>>();
jobjectArray arr=env->NewObjectArray(v.size(),env->FindClass("java/lang/String"),NULL);
for(size_t i=0;i<v.size();i++){
env->SetObjectArrayElement(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<std::string> 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("-","");
+387 -348
View File
@@ -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<KeyboardHandlerBase, NullKeyboardHandler>(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<<std::this_thread::get_id();
return ss.str();
}();
LOGW("new thr: %s",tid.c_str());
pthread_mutex_init(&key_event_mutex, NULL);
pthread_mutex_init(&emu_mutex, NULL);
pthread_cond_init(&emu_cond, NULL);
init();
bool boot_ok=boot_game();
emu_status=STATUS_RUNNING;
while (true){
if (emu_status == STATUS_REQUEST_RESUME) {
pthread_mutex_lock(&emu_mutex);
Emu.Resume();
emu_status = STATUS_RUNNING;
pthread_cond_signal(&emu_cond);
pthread_mutex_unlock(&emu_mutex);
}
if (emu_status == STATUS_REQUEST_PAUSE) {
pthread_mutex_lock(&emu_mutex);
Emu.Pause(false,false);
emu_status = STATUS_PAUSED;
pthread_cond_signal(&emu_cond);
pthread_mutex_unlock(&emu_mutex);
}
if (emu_status == STATUS_REQUEST_STOP) {
pthread_mutex_lock(&emu_mutex);
if(boot_ok) Emu.Kill();
emu_status = STATUS_STOPPED;
pthread_cond_signal(&emu_cond);
pthread_mutex_unlock(&emu_mutex);
usleep(10);
break;
}
usleep(10);
}
pthread_mutex_destroy(&key_event_mutex);
pthread_mutex_destroy(&emu_mutex);
pthread_cond_destroy(&emu_cond);
}
void key_event(int key_code,bool pressed,int value){
pthread_mutex_lock(&key_event_mutex);
{
auto* pad_thr=g_fxo->try_get<named_thread<pad_thread>>();
if(pad_thr){
auto xx=pad_thr->get_handlers().at(pad_handler::keyboard);
std::shared_ptr<AndroidVirtualPadHandler> padh=std::dynamic_pointer_cast<AndroidVirtualPadHandler>(xx);
padh->Key(static_cast<u32>(key_code), static_cast<bool>(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<KeyboardHandlerBase, NullKeyboardHandler>(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<MouseHandlerBase, NullMouseHandler>(Emu.DeserialManager());
g_fxo->init<MouseHandlerBase, NullMouseHandler>(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<named_thread<pad_thread>>(nullptr, nullptr, title_id));
qt_events_aware_op(0, [](){ return !!pad::g_started; });
};
callbacks.get_audio = []() -> std::shared_ptr<AudioBackend>
{
PRE("get_audio");
std::shared_ptr<AudioBackend> result =std::make_shared<NullAudioBackend>();
switch (g_cfg.audio.renderer.get())
{
case audio_renderer::null: result = std::make_shared<NullAudioBackend>(); break;
case audio_renderer::cubeb: result = std::make_shared<CubebBackend>(); break;
#ifdef HAVE_FAUDIO
case audio_renderer::faudio: result = std::make_shared<FAudioBackend>(); 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<NullAudioBackend>();
}
return result;
};
callbacks.get_audio_enumerator = [](u64 renderer) -> std::shared_ptr<audio_device_enumerator>
{
PRE("get_audio_enumerator");
switch (static_cast<audio_renderer>(renderer))
{
case audio_renderer::null: return std::make_shared<null_enumerator>();
#ifdef _WIN32
case audio_renderer::xaudio: return std::make_shared<xaudio2_enumerator>();
#endif
case audio_renderer::cubeb: return std::make_shared<cubeb_enumerator>();
#ifdef HAVE_FAUDIO
case audio_renderer::faudio: return std::make_shared<faudio_enumerator>();
#endif
default: return std::make_shared<null_enumerator>();
}
};
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<int>(sv.size()))).canonicalFilePath().toStdString();
};
callbacks.on_install_pkgs = [](const std::vector<std::string>& pkgs)
{
PRE("on_install_pkgs");
return true;
};
callbacks.try_to_quit = [](bool force_quit, std::function<void()> 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<void()> func, atomic_t<u32>* 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<rsx::thread, named_thread<VKGSRender>>(ar);
break;
}
}
};
callbacks.get_camera_handler = []() -> std::shared_ptr<camera_handler_base>
{
PRE("get_camera_handler");
/*switch (g_cfg.io.camera.get())
{
case camera_handler::null:
case camera_handler::fake:
{
return std::make_shared<null_camera_handler>();
}
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<null_camera_handler>();
};
callbacks.get_music_handler = []() -> std::shared_ptr<music_handler_base>
{
PRE("get_music_handler");
switch (g_cfg.audio.music.get())
{
case music_handler::null:
{
return std::make_shared<null_music_handler>();
}
case music_handler::qt:
{
return std::make_shared<android_music_handler>();
}
}
};
callbacks.get_msg_dialog = []() -> std::shared_ptr<MsgDialogBase> {
PRE("get_msg_dialog");
return std::make_shared<dummy_msg_dialog>(); };
callbacks.get_osk_dialog = []() -> std::shared_ptr<OskDialogBase> {
PRE("get_osk_dialog");
return std::make_shared<dummy_osk_dialog>(); };
callbacks.get_save_dialog = []() -> std::unique_ptr<SaveDialogBase> {
PRE("get_save_dialog");
return std::make_unique<android_save_dialog>(); };
callbacks.get_trophy_notification_dialog = []() -> std::unique_ptr<TrophyNotificationBase> {
PRE("get_trophy_notification_dialog");
return std::make_unique<android_trophy_notification>(); };
callbacks.get_sendmessage_dialog=[]()->std::shared_ptr<SendMessageDialogBase>{
PRE("get_sendmessage_dialog");
return std::make_shared<dummy_send_message_dialog>();
};
callbacks.get_recvmessage_dialog=[]()->std::shared_ptr<RecvMessageDialogBase>{
PRE("get_recvmessage_dialog");
return std::make_shared<dummy_recv_message_dialog>();
};
callbacks.on_stop = []() {
PRE("on_stop");
};
callbacks.on_ready = []() {
PRE("on_ready");
};
callbacks.on_emulation_stop_no_response = [](std::shared_ptr<atomic_t<bool>> closed_successfully, int /*seconds_waiting_already*/)
{
PRE("on_emulation_stop_no_response");
};
callbacks.on_save_state_progress = [](std::shared_ptr<atomic_t<bool>>, stx::shared_ptr<utils::serial>, stx::atomic_ptr<std::string>*, std::shared_ptr<void>)
{
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<size_t>(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<named_thread<pad_thread>>(nullptr, nullptr, title_id));
qt_events_aware_op(0, [](){ return !!pad::g_started; });
};
std::string tid=[]{
std::stringstream ss;
ss<<std::this_thread::get_id();
return ss.str();
}();
LOGW("new thr: %s",tid.c_str());
pthread_mutex_init(&key_event_mutex, NULL);
pthread_mutex_init(&emu_mutex, NULL);
pthread_cond_init(&emu_cond, NULL);
init();
bool boot_ok=boot_game();
emu_status=STATUS_RUNNING;
while (true){
if (emu_status == STATUS_REQUEST_RESUME) {
pthread_mutex_lock(&emu_mutex);
Emu.Resume();
emu_status = STATUS_RUNNING;
pthread_cond_signal(&emu_cond);
pthread_mutex_unlock(&emu_mutex);
}
if (emu_status == STATUS_REQUEST_PAUSE) {
pthread_mutex_lock(&emu_mutex);
Emu.Pause(false,false);
emu_status = STATUS_PAUSED;
pthread_cond_signal(&emu_cond);
pthread_mutex_unlock(&emu_mutex);
}
if (emu_status == STATUS_REQUEST_STOP) {
pthread_mutex_lock(&emu_mutex);
if(boot_ok) Emu.Kill();
emu_status = STATUS_STOPPED;
pthread_cond_signal(&emu_cond);
pthread_mutex_unlock(&emu_mutex);
usleep(10);
break;
}
usleep(10);
callbacks.get_audio = []() -> std::shared_ptr<AudioBackend>
{
PRE("get_audio");
std::shared_ptr<AudioBackend> result =std::make_shared<NullAudioBackend>();
switch (g_cfg.audio.renderer.get())
{
case audio_renderer::null: result = std::make_shared<NullAudioBackend>(); break;
case audio_renderer::cubeb: result = std::make_shared<CubebBackend>(); break;
#ifdef HAVE_FAUDIO
case audio_renderer::faudio: result = std::make_shared<FAudioBackend>(); 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<NullAudioBackend>();
}
return result;
};
ANativeWindow* wnd;
int w;
int h;
callbacks.get_audio_enumerator = [](u64 renderer) -> std::shared_ptr<audio_device_enumerator>
{
PRE("get_audio_enumerator");
switch (static_cast<audio_renderer>(renderer))
{
case audio_renderer::null: return std::make_shared<null_enumerator>();
#ifdef _WIN32
case audio_renderer::xaudio: return std::make_shared<xaudio2_enumerator>();
#endif
case audio_renderer::cubeb: return std::make_shared<cubeb_enumerator>();
#ifdef HAVE_FAUDIO
case audio_renderer::faudio: return std::make_shared<faudio_enumerator>();
#endif
default: return std::make_shared<null_enumerator>();
}
};
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<int>(sv.size()))).canonicalFilePath().toStdString();
};
callbacks.on_install_pkgs = [](const std::vector<std::string>& pkgs)
{
PRE("on_install_pkgs");
return true;
};
callbacks.try_to_quit = [](bool force_quit, std::function<void()> 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<void()> func, atomic_t<u32>* 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<rsx::thread, named_thread<VKGSRender>>(ar);
break;
}
}
};
callbacks.get_camera_handler = []() -> std::shared_ptr<camera_handler_base>
{
PRE("get_camera_handler");
/*switch (g_cfg.io.camera.get())
{
case camera_handler::null:
case camera_handler::fake:
{
return std::make_shared<null_camera_handler>();
}
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<null_camera_handler>();
};
callbacks.get_music_handler = []() -> std::shared_ptr<music_handler_base>
{
PRE("get_music_handler");
switch (g_cfg.audio.music.get())
{
case music_handler::null:
{
return std::make_shared<null_music_handler>();
}
case music_handler::qt:
{
return std::make_shared<android_music_handler>();
}
}
};
callbacks.get_msg_dialog = []() -> std::shared_ptr<MsgDialogBase> {
PRE("get_msg_dialog");
return std::make_shared<dummy_msg_dialog>(); };
callbacks.get_osk_dialog = []() -> std::shared_ptr<OskDialogBase> {
PRE("get_osk_dialog");
return std::make_shared<dummy_osk_dialog>(); };
callbacks.get_save_dialog = []() -> std::unique_ptr<SaveDialogBase> {
PRE("get_save_dialog");
return std::make_unique<android_save_dialog>(); };
callbacks.get_trophy_notification_dialog = []() -> std::unique_ptr<TrophyNotificationBase> {
PRE("get_trophy_notification_dialog");
return std::make_unique<android_trophy_notification>(); };
callbacks.get_sendmessage_dialog=[]()->std::shared_ptr<SendMessageDialogBase>{
PRE("get_sendmessage_dialog");
return std::make_shared<dummy_send_message_dialog>();
};
callbacks.get_recvmessage_dialog=[]()->std::shared_ptr<RecvMessageDialogBase>{
PRE("get_recvmessage_dialog");
return std::make_shared<dummy_recv_message_dialog>();
};
callbacks.on_stop = []() {
PRE("on_stop");
};
callbacks.on_ready = []() {
PRE("on_ready");
};
callbacks.on_emulation_stop_no_response = [](std::shared_ptr<atomic_t<bool>> closed_successfully, int /*seconds_waiting_already*/)
{
PRE("on_emulation_stop_no_response");
};
callbacks.on_save_state_progress = [](std::shared_ptr<atomic_t<bool>>, stx::shared_ptr<utils::serial>, stx::atomic_ptr<std::string>*, std::shared_ptr<void>)
{
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<size_t>(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<logs::listener> 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<void>(utils::get_tsc_freq());
@@ -437,7 +481,7 @@ namespace aps3e_emu{
callbacks.get_gs_frame = [=]() -> std::unique_ptr<GSFrameBase>
{
PRE("get_gs_frame");
return std::unique_ptr<android_gs_frame>(new android_gs_frame(wnd,w,h));
return std::unique_ptr<android_gs_frame>(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;
}//);
}
}
}
+3 -3
View File
@@ -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
{
+318
View File
@@ -0,0 +1,318 @@
// SPDX-License-Identifier: WTFPL
#include "emulator.h"
#include <android/log.h>
#include <fstream>
#include <jni.h>
#include <thread>
#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<std::string>().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<std::string>().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<std::string>().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<std::string> v=node.as<std::vector<std::string>>();
jobjectArray arr=env->NewObjectArray(v.size(),env->FindClass("java/lang/String"),NULL);
for(size_t i=0;i<v.size();i++){
env->SetObjectArrayElement(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<std::string> 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<jstring>(path), nullptr);
ae::boot_type=ae::BOOT_TYPE_WITH_PATH;
ae::boot_game_path=std::string(_path);
env->ReleaseStringUTFChars(reinterpret_cast<jstring>(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]));
}
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: WTFPL
#ifndef APS3E_EMULATOR_H
#define APS3E_EMULATOR_H
#include <android/native_window_jni.h>
#include <string>
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
+2 -2
View File
@@ -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;
@@ -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<u64, 64>* range_lock = nullptr;
@@ -993,7 +993,7 @@ namespace rsx
g_fxo->get<vblank_thread>().set_thread(std::shared_ptr<named_thread<std::function<void()>>>(new named_thread<std::function<void()>>("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
+8 -4
View File
@@ -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 <utility>
@@ -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<void*>(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<void*>(data), FUTEX_WAKE_PRIVATE, INT_MAX);
+18 -6
View File
@@ -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();
@@ -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.
* <p/>
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
* <p/>
* "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<File> pending = new LinkedList<File>();
// 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<File> 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.
* <p/>
* 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.
* <p/>
* 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);
}
}
/**
* 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;
}
}
+22 -92
View File
@@ -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);
@@ -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();
}
}*/
}
@@ -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.
* <p/>
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
* <p/>
* "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<File> pending = new LinkedList<File>();
// 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<File> 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.
* <p/>
* 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.
* <p/>
* 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);
}
}
/**
* 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;
}
}
@@ -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(){
@@ -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<LinearLayout> 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<layout_list.size();i++){
layout_list.get(i).setVisibility(View.GONE);
}
if(!Application.device_support_vulkan()){
set_not_support_vulkan_layout();
return;
}
page=pos;
layout_list.get(page).setVisibility(View.VISIBLE);
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: WTFPL
package aenu.emulator;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.view.Surface;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import aenu.aps3e.Application;
public class Emulator {
public static class ConfigFileException extends Exception{
}
public static class BootException extends Exception{
}
public static class Config{
String config_path=null;
private long n_handle;
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 Emulator.Config open_config_file(String config_path) throws Emulator.ConfigFileException
{
Emulator.Config config=new Emulator.Config();
config.config_path=config_path;
if((config.n_handle=config.native_open_config_file(config_path))==0)
throw new Emulator.ConfigFileException();
return config;
}
public static Emulator.Config open_config_from_string(String config_str) throws Emulator.ConfigFileException
{
Emulator.Config config=new Emulator.Config();
if((config.n_handle=config.native_open_config(config_str))==0)
throw new Emulator.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 class Path{
String uri;
int fd;
public static Path from(String uri,int fd){
Path p=new Path();
p.uri=uri;
p.fd=fd;
return p;
}
}
public boolean support_custom_driver(){
try {
return new File("/dev/kgsl-3d0").exists();
}catch(Exception e){
return false;
}
}
public native void setup_game_path(String path);
public native void setup_game_path(Emulator.Path path);
public native void setup_surface(Surface sf);
public native void boot() throws Emulator.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);
}
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: WTFPL
package aenu.emulator.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
public class VirtualControl extends android.view.SurfaceView{
interface OnKeyEventListener{
public void onKeyEvent(int key_code,boolean pressed);
}
interface Component{
void set_scale(float scale);
void set_opacity(float opacity);
void draw(Canvas canvas);
}
static Bitmap create_ratio_bitmap(Context context,int res_id,float ratio){
DisplayMetrics dm=context.getResources().getDisplayMetrics();
int min_dimension=Math.min(dm.widthPixels,dm.heightPixels);
return Bitmap.createScaledBitmap(BitmapFactory.decodeResource(context.getResources(),res_id),
(int)(min_dimension*ratio),
(int)(min_dimension*ratio),
true);
}
class Dpad implements Component {
int x;
int y;
int res_id;
int pressed_res_id;
float scale=1.0f;
float opacity=1.0f;
Bitmap default_bitmap;
Bitmap default_pressed_bitmap;
Bitmap bitmap;
Bitmap pressed_bitmap;
Context context;
public Dpad(Context context,int res_id,int pressed_res_id){
this.context=context;
this.res_id=res_id;
this.pressed_res_id=pressed_res_id;
final float ratio=0.35f;
default_bitmap=VirtualControl.create_ratio_bitmap(context,res_id,ratio);
default_pressed_bitmap=VirtualControl.create_ratio_bitmap(context,pressed_res_id,ratio);
}
void setup_bitmap(){
}
@Override
public void set_scale(float scale) {
}
@Override
public void set_opacity(float opacity) {
}
@Override
public void draw(Canvas canvas) {
}
}
class ᐃ口OX{
}
public VirtualControl(Context context) {
this(context,null);
}
public VirtualControl(Context context, android.util.AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
requestFocus();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
public void onTouch(MotionEvent event) {
}
}
@@ -60,16 +60,12 @@ public class CheckBoxPreference extends androidx.preference.CheckBoxPreference{
TextView title_v=(TextView) holder.itemView.findViewById(android.R.id.title);
TextView summary_v=(TextView) holder.itemView.findViewById(android.R.id.summary);
if(is_modify_color){
if(title_v!=null)
title_v.setTextColor(modify_color);
if(summary_v!=null)
summary_v.setTextColor(modify_color);
if(title_v!=null) title_v.setTextColor(modify_color);
if(summary_v!=null) summary_v.setTextColor(modify_color);
}
else{
if(title_v!=null)
title_v.setTextColor(title_v.isEnabled()?title_color:Color.GRAY);
if(summary_v!=null)
summary_v.setTextColor(summary_v.isEnabled()?summary_color:Color.GRAY);
if(title_v!=null) title_v.setTextColor(title_v.isEnabled()?title_color:Color.GRAY);
if(summary_v!=null) summary_v.setTextColor(summary_v.isEnabled()?summary_color:Color.GRAY);
}
}
}
@@ -1,6 +1,4 @@
// SPDX-License-Identifier: Apache-2.0
// androidx.preference
// SPDX-License-Identifier: WTFPL
package aenu.preference;
import android.content.Context;
@@ -27,6 +25,7 @@ import androidx.preference.PreferenceViewHolder;
import androidx.preference.R;
//modify from https://github.com/androidx/androidx/blob/androidx-main/preference/preference/src/main/java/androidx/preference/SeekBarPreference.java
public class SeekBarPreference extends DialogPreference {
+3 -4
View File
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Fallback-->
<string name="emulator_settings_video_performance_overlay_font">Font</string>
<string name="emulator_settings_audio_disable_sampling_skip">Disable Sampling Skip</string>
<string name="device_unsupport_vulkan_msg">جهازك لا يدعم Vulkan ولا يفي الحد الأدنى من المتطلبات لتشغيل هذا التطبيق، يرجى الخروج.</string>
<string name="emulator_settings_video_performance_overlay_font">الخط</string>
<string name="emulator_settings_audio_disable_sampling_skip">تعطيل تخطي أخذ العينات</string>
<string name="use_cores_ppu">استخدام النوى (PPU)</string>
<string name="use_cores_spu">استخدام النوى (SPU)</string>
+2
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Ihr Gerät unterstützt Vulkan nicht und erfüllt nicht die mindestens erforderlichen Anforderungen, um diese Anwendung auszuführen. Bitte beenden Sie.</string>
<string name="use_cores_ppu">Kerne verwenden (PPU)</string>
<string name="use_cores_spu">Kerne verwenden (SPU)</string>
<string name="use_cores_rsx">Kerne verwenden (RSX)</string>
+2
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Su dispositivo no admite Vulkan y no cumple con los requisitos mínimos para ejecutar esta aplicación, por favor, salga.</string>
<string name="use_cores_ppu">Usar Núcleos(PPU)</string>
<string name="use_cores_spu">Usar Núcleos(SPU)</string>
<string name="use_cores_rsx">Usar Núcleos(RSX)</string>
+3
View File
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">دستگاه شما از Vulkan پشتیبانی نمی‌کند و حداقل نیازمندی‌های لازم را برای اجرای این برنامه فراهم نمی‌کند، لطفاً خارج شوید.</string>
<string name="use_cores_ppu">استفاده از هسته‌ها (PPU)</string>
<string name="use_cores_spu">استفاده از هسته‌ها (SPU)</string>
<string name="use_cores_rsx">استفاده از هسته‌ها (RSX)</string>
+13 -13
View File
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Fallback-->
<string name="HOME_MENU_SETTINGS_INPUT_PAD_MODE">Pad Handler Mode</string>
<string name="HOME_MENU_SETTINGS_INPUT_PAD_SLEEP">Pad Handler Sleep</string>
<string name="CELL_NP_RECVMESSAGE_DIALOG_TITLE_ADD_FRIEND">Add Friend</string>
<string name="RPCN_FRIEND_LOGGED_IN">RPCN: %0 logged in</string>
<string name="RPCN_FRIEND_LOGGED_OUT">RPCN: %0 logged out</string>
<string name="CELL_CROSS_CONTROLLER_MSG">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.</string>
<string name="gratitude_content">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</string>
<string name="device_unsupport_vulkan_msg">Votre appareil ne prend pas en charge Vulkan et ne satisfait pas aux exigences minimales requises pour exécuter cette application. Veuillez quitter.</string>
<string name="HOME_MENU_SETTINGS_INPUT_PAD_MODE">Mode du gestionnaire de manette</string>
<string name="HOME_MENU_SETTINGS_INPUT_PAD_SLEEP">Veille du gestionnaire de manette</string>
<string name="CELL_NP_RECVMESSAGE_DIALOG_TITLE_ADD_FRIEND">Ajouter un ami</string>
<string name="RPCN_FRIEND_LOGGED_IN">RPCN : %0 sest connecté</string>
<string name="RPCN_FRIEND_LOGGED_OUT">RPCN : %0 sest déconnecté</string>
<!--string name="CELL_CROSS_CONTROLLER_MSG">Démarrez sur le système PS Vita.\nSi vous ne lavez pas installé, accédez à [Lecture à distance] sur le système PS Vita et démarrez [Cross-Controller] depuis l'écran LiveArea™.</string-->
<!--string name="gratitude_content">Merci à Ruban pour toutes les icônes\n\n
Merci à SRC267 (Sting) pour la gestion de reddit/discord\n\n
Merci à 妖妖 pour les tests\n\n
Merci à 再见某人 pour le don dune GPD XP, très utile pendant le processus de test.\n\n
Merci à 再见某人 pour le don du matériel de développement, essentiel à l'avancement du projet.\n\n
Merci aux personnes suivantes qui ont apporté un soutien financier à ce projet (phase initiale, triés par date) :\n\n</string-->
<string name="use_cores_ppu">Utiliser les coeurs (PPU)</string>
+3
View File
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">आपका डिवाइस Vulkan का समर्थन नहीं करता है और इस एप्लिकेशन को चलाने के लिए न्यूनतम आवश्यकताओं को पूरा नहीं कर सकता है, कृपया बाहर निकलें।</string>
<string name="use_cores_ppu">पीपीयू कोर्स उपयोग करें</string>
<string name="use_cores_spu">एसपीयू कोर्स उपयोग करें</string>
<string name="use_cores_rsx">आरएसएक्स कोर्स उपयोग करें</string>
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Perangkat Anda tidak mendukung Vulkan dan tidak dapat memenuhi persyaratan minimum untuk menjalankan aplikasi ini, silakan keluar.</string>
<string name="use_cores_ppu">Gunakan Core(PPU)</string>
<string name="use_cores_spu">Gunakan Core(SPU)</string>
+7 -9
View File
@@ -1,14 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Fallback-->
<string name="gratitude_content">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</string>
<string name="device_unsupport_vulkan_msg">Il tuo dispositivo non supporta Vulkan e non soddisfa i requisiti minimi necessari per eseguire questa applicazione, per favore esci.</string>
<!--string name="gratitude_content">Grazie a Ruban per tutte le ICONE\n\n
Grazie a SRC267 (Sting) per la gestione di reddit/discord\n\n
Grazie a 妖妖 per i test\n\n
Grazie a 再见某人 per aver donato una GPD XP, che è stata di grande aiuto durante il processo di test.\n\n
Grazie a 再见某人 per aver donato l'equipaggiamento per lo sviluppo, fondamentale per far avanzare il progetto.\n\n
Grazie alle seguenti persone che hanno fornito supporto finanziario per questo progetto (fase iniziale, ordinate per data):\n\n</string-->
<string name="use_cores_ppu">Utilizza Core(PPU)</string>
<string name="use_cores_spu">Utilizza Core(SPU)</string>
+2
View File
@@ -2,6 +2,8 @@
<resources>
<string name="device_unsupport_vulkan_msg">お使いの端末はVulkanをサポートしていません。このアプリケーションを実行するために必要な最低限の要件を満たしていませんので、終了してください。</string>
<string name="use_cores_ppu">コアを使用する(PPU</string>
<string name="use_cores_spu">コアを使用する(SPU</string>
<string name="use_cores_rsx">コアを使用する(RSX</string>
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">사용 중인 기기는 Vulkan을 지원하지 않으며 이 애플리케이션을 실행하기 위한 최소 요구 사항을 충족시키지 못합니다. 종료하십시오.</string>
<string name="use_cores_ppu">코어(PPU) 사용</string>
<string name="use_cores_spu">코어(SPU) 사용</string>
+2
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Peranti anda tidak menyokong Vulkan dan tidak memenuhi keperluan minima untuk menjalankan aplikasi ini, sila keluar.</string>
<string name="use_cores_ppu">Gunakan Core(PPU)</string>
<string name="use_cores_spu">Gunakan Core(SPU)</string>
<string name="use_cores_rsx">Gunakan Core(RSX)</string>
+3 -1
View File
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">aPS3e</string>
<string name="device_unsupport_vulkan_msg">Twoje urządzenie nie obsługuje Vulkan oraz nie spełnia minimalnych wymagań koniecznych do uruchomienia tej aplikacji, proszę wyjść.</string>
<string name="use_cores_ppu">Używaj rdzeni (PPU)</string>
<string name="use_cores_spu">Używaj rdzeni (SPU)</string>
<string name="use_cores_rsx">Używaj rdzeni (RSX)</string>
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Seu dispositivo não suporta Vulkan e não atende aos requisitos mínimos para executar este aplicativo, por favor, saia.</string>
<string name="use_cores_ppu">Usar Núcleos(PPU)</string>
<string name="use_cores_spu">Usar Núcleos(SPU)</string>
<string name="use_cores_rsx">Usar Núcleos(RSX)</string>
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">O seu dispositivo não suporta Vulkan e não cumpre os requisitos mínimos para executar esta aplicação, por favor saia.</string>
<string name="use_cores_ppu">Usar Núcleos(PPU)</string>
<string name="use_cores_spu">Usar Núcleos(SPU)</string>
<string name="use_cores_rsx">Usar Núcleos(RSX)</string>
+2
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Ваше устройство не поддерживает Vulkan и не соответствует минимальным требованиям для запуска этого приложения, пожалуйста, выйдите.</string>
<string name="use_cores_ppu">Использовать ядра (PPU)</string>
<string name="use_cores_spu">Использовать ядра (SPU)</string>
<string name="use_cores_rsx">Использовать ядра (RSX)</string>
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">อุปกรณ์ของคุณไม่รองรับ Vulkan และไม่สามารถปฏิบัติตามข้อกำหนดขั้นต่ำเพื่อเรียกใช้งานแอปพลิเคชันนี้ได้ กรุณาออก</string>
<string name="use_cores_ppu">ใช้คอร์ (PPU)</string>
<string name="use_cores_spu">ใช้คอร์ (SPU)</string>
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Cihazınız Vulkan desteklemiyor ve bu uygulamayı çalıştırmak için gerekli olan minimum gereksinimleri karşılamıyor, lütfen çıkın.</string>
<string name="use_cores_ppu">Çekirdekleri Kullan(PPU)</string>
<string name="use_cores_spu">Çekirdekleri Kullan(SPU)</string>
+2
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">Ваш пристрій не підтримує Vulkan і не відповідає мінімальним вимогам для запуску цього додатка, будь ласка, вийдіть.</string>
<string name="use_cores_ppu">Використовувати ядра (PPU)</string>
<string name="use_cores_spu">Використовувати ядра (SPU)</string>
<string name="use_cores_rsx">Використовувати ядра (RSX)</string>
+2
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">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.</string>
<string name="use_cores_ppu">Sử dụng Nhân (PPU)</string>
<string name="use_cores_spu">Sử dụng Nhân (SPU)</string>
<string name="use_cores_rsx">Sử dụng Nhân (RSX)</string>
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">您的设备不支持 Vulkan,无法满足运行此应用程序的最低要求,请退出。</string>
<string name="use_cores_ppu">使用核心(PPU)</string>
<string name="use_cores_spu">使用核心(SPU)</string>
<string name="use_cores_rsx">使用核心(RSX)</string>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="device_unsupport_vulkan_msg">您的設備不支援 Vulkan,無法滿足運行此應用程序的最低要求,請退出。</string>
<string name="use_cores_ppu">使用核心(PPU)</string>
<string name="use_cores_spu">使用核心(SPU)</string>
<string name="use_cores_rsx">使用核心(RSX)</string>
+1
View File
@@ -3,6 +3,7 @@
<!--string name="app_name">aPS3e Premium</string-->
<string name="app_name">aPS3e</string>
<string name="device_unsupport_vulkan_msg">Your device does not support Vulkan and cannot meet the minimum requirements to run this application, please exit.</string>
<string name="use_cores_ppu">Use Cores(PPU)</string>
<string name="use_cores_spu">Use Cores(SPU)</string>
<string name="use_cores_rsx">Use Cores(RSX)</string>