diff --git a/include/jni_driver.hpp b/include/jni_driver.hpp index ff6230f12..a4d6895e7 100644 --- a/include/jni_driver.hpp +++ b/include/jni_driver.hpp @@ -1,7 +1,8 @@ #include + #include "helpers.hpp" -class Pandroid { -public: - static void onSmdhLoaded(const std::vector &smdh); + +namespace Pandroid { + static void onSmdhLoaded(const std::vector& smdh); }; diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 3e17790ce..550a5c717 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -259,14 +259,13 @@ bool NCCH::parseSMDH(const std::vector& smdh) { return false; } - - #ifdef __ANDROID__ - Pandroid::onSmdhLoaded(smdh); - #endif +#ifdef __ANDROID__ + Pandroid::onSmdhLoaded(smdh); +#endif // Bitmask showing which regions are allowed. // https://www.3dbrew.org/wiki/SMDH#Region_Lockout - const u32 regionMasks = *(u32*)&smdh[0x2018]; + const u32 regionMasks = *(u32 *)&smdh[0x2018]; // Detect when games are region free (ie all regions are allowed) for future use [[maybe_unused]] const bool isRegionFree = (regionMasks & 0x7f) == 0x7f; diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 806a00744..c38359b42 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -1,3 +1,5 @@ +#include "jni_driver.hpp" + #include #include #include @@ -8,8 +10,6 @@ #include "renderer_gl/renderer_gl.hpp" #include "services/hid.hpp" -#include "jni_driver.hpp" - std::unique_ptr emulator = nullptr; HIDService* hidService = nullptr; RendererGL* renderer = nullptr; @@ -24,41 +24,35 @@ void throwException(JNIEnv* env, const char* message) { env->ThrowNew(exceptionClass, message); } -JNIEnv* jniEnv(){ - JNIEnv* env; - auto status = jvm->GetEnv((void **)&env, JNI_VERSION_1_6); - if(status == JNI_EDETACHED){ - jvm->AttachCurrentThread(&env, nullptr); - } else if(status != JNI_OK){ - throw std::runtime_error("Failed to obtain JNIEnv from JVM!!"); - } - return env; -} +JNIEnv* jniEnv() { + JNIEnv* env; + auto status = jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + if (status == JNI_EDETACHED) { + jvm->AttachCurrentThread(&env, nullptr); + } else if (status != JNI_OK) { + throw std::runtime_error("Failed to obtain JNIEnv from JVM!!"); + } + return env; +} -void Pandroid::onSmdhLoaded(const std::vector &smdh){ - JNIEnv* env = jniEnv(); - int size = smdh.size(); - - jbyteArray result = env->NewByteArray(size); - - env->SetByteArrayRegion(result, 0, size, (jbyte*)smdh.data()); +void Pandroid::onSmdhLoaded(const std::vector& smdh) { + JNIEnv* env = jniEnv(); + int size = smdh.size(); - - auto clazz = env->FindClass(alberClass); - auto method = env->GetStaticMethodID(clazz, "OnSmdhLoaded", "([B)V"); + jbyteArray result = env->NewByteArray(size); + env->SetByteArrayRegion(result, 0, size, (jbyte*)smdh.data()); - env->CallStaticVoidMethod(clazz, method, result); + auto classLoader = env->FindClass(alberClass); + auto method = env->GetStaticMethodID(classLoader, "OnSmdhLoaded", "([B)V"); - env->DeleteLocalRef(result); + env->CallStaticVoidMethod(classLoader, method, result); + env->DeleteLocalRef(result); } - extern "C" { -AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { - env->GetJavaVM(&jvm); -} +AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); } AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) { emulator = std::make_unique(); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java index ed6ec32ce..527d49668 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java @@ -1,44 +1,38 @@ package com.panda3ds.pandroid.app; import android.os.Bundle; - import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; - import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.data.config.GlobalConfig; + public class BaseActivity extends AppCompatActivity { - private int currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME); - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - applyTheme(); - super.onCreate(savedInstanceState); - } - - @Override - protected void onResume() { - super.onResume(); - if (GlobalConfig.get(GlobalConfig.KEY_APP_THEME) != currentTheme){ - recreate(); - } - } - - private void applyTheme(){ - switch (GlobalConfig.get(GlobalConfig.KEY_APP_THEME)){ - case GlobalConfig.THEME_ANDROID: - setTheme(R.style.Theme_Pandroid); - break; - case GlobalConfig.THEME_LIGHT: - setTheme(R.style.Theme_Pandroid_Light); - break; - case GlobalConfig.THEME_DARK: - setTheme(R.style.Theme_Pandroid_Dark); - break; - case GlobalConfig.THEME_BLACK: - setTheme(R.style.Theme_Pandroid_Black); - break; - } - currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME); - } + private int currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + applyTheme(); + super.onCreate(savedInstanceState); + } + + @Override + protected void onResume() { + super.onResume(); + + if (GlobalConfig.get(GlobalConfig.KEY_APP_THEME) != currentTheme) { + recreate(); + } + } + + private void applyTheme() { + switch (GlobalConfig.get(GlobalConfig.KEY_APP_THEME)) { + case GlobalConfig.THEME_ANDROID: setTheme(R.style.Theme_Pandroid); break; + case GlobalConfig.THEME_LIGHT: setTheme(R.style.Theme_Pandroid_Light); break; + case GlobalConfig.THEME_DARK: setTheme(R.style.Theme_Pandroid_Dark); break; + case GlobalConfig.THEME_BLACK: setTheme(R.style.Theme_Pandroid_Black); break; + } + + currentTheme = GlobalConfig.get(GlobalConfig.KEY_APP_THEME); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java index 61b9cb1dc..03593b44c 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java @@ -10,9 +10,7 @@ import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.Toast; - import androidx.annotation.Nullable; - import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.app.game.AlberInputListener; @@ -24,7 +22,6 @@ import com.panda3ds.pandroid.view.PandaLayoutController; public class GameActivity extends BaseActivity { - private final AlberInputListener inputListener = new AlberInputListener(this); @Override @@ -43,7 +40,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setContentView(R.layout.game_activity); ((FrameLayout) findViewById(R.id.panda_gl_frame)) - .addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + .addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); PandaLayoutController controllerLayout = findViewById(R.id.controller_layout); controllerLayout.initialize(); @@ -75,25 +72,28 @@ protected void onPause() { @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (InputHandler.processKeyEvent(event)) + if (InputHandler.processKeyEvent(event)) { return true; + } return super.dispatchKeyEvent(event); } @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) { - if (InputHandler.processMotionEvent(ev)) + if (InputHandler.processMotionEvent(ev)) { return true; + } return super.dispatchGenericMotionEvent(ev); } @Override protected void onDestroy() { - if(AlberDriver.HasRomLoaded()){ + if (AlberDriver.HasRomLoaded()) { AlberDriver.Finalize(); } + super.onDestroy(); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java index 29070a889..ee7acf883 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java @@ -9,18 +9,17 @@ import android.os.Bundle; import android.os.Environment; import android.view.MenuItem; - import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; - import com.google.android.material.navigation.NavigationBarView; import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.app.main.GamesFragment; import com.panda3ds.pandroid.app.main.SearchFragment; import com.panda3ds.pandroid.app.main.SettingsFragment; + public class MainActivity extends BaseActivity implements NavigationBarView.OnItemSelectedListener { private static final int PICK_ROM = 2; private static final int PERMISSION_REQUEST_CODE = 3; @@ -45,8 +44,8 @@ protected void onCreate(Bundle savedInstanceState) { startActivity(intent); } } else { - ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); - ActivityCompat.requestPermissions(this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[] {READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[] {WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); } setContentView(R.layout.activity_main); @@ -71,9 +70,7 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { return false; } - manager.beginTransaction() - .replace(R.id.fragment_container, fragment) - .commitNow(); + manager.beginTransaction().replace(R.id.fragment_container, fragment).commitNow(); return true; } } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java index 770e5e81b..0ae779dd6 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java @@ -2,26 +2,25 @@ import android.app.Application; import android.content.Context; - import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.utils.GameUtils; + public class PandroidApplication extends Application { - private static Context appContext; + private static Context appContext; + + @Override + public void onCreate() { + super.onCreate(); + appContext = this; - @Override - public void onCreate() { - super.onCreate(); - appContext = this; - GlobalConfig.initialize(); - GameUtils.initialize(); - InputMap.initialize(); - AlberDriver.Setup(); - } + GlobalConfig.initialize(); + GameUtils.initialize(); + InputMap.initialize(); + AlberDriver.Setup(); + } - public static Context getAppContext() { - return appContext; - } + public static Context getAppContext() { return appContext; } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java index 3b8ebb2e6..64249c5b5 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java @@ -4,7 +4,6 @@ import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -13,42 +12,41 @@ import com.panda3ds.pandroid.utils.Constants; public class PreferenceActivity extends BaseActivity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Intent intent = getIntent(); - - setContentView(R.layout.activity_preference); - setSupportActionBar(findViewById(R.id.toolbar)); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - if (!intent.hasExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT)) { - finish(); - return; - } - - try { - Class clazz = getClassLoader().loadClass(intent.getStringExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT)); - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.fragment_container, (Fragment) clazz.newInstance()) - .commitNow(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static void launch(Context context, Class clazz) { - context.startActivity(new Intent(context, PreferenceActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT, clazz.getName())); - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) - finish(); - return super.onOptionsItemSelected(item); - } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + + setContentView(R.layout.activity_preference); + setSupportActionBar(findViewById(R.id.toolbar)); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + if (!intent.hasExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT)) { + finish(); + return; + } + + try { + Class clazz = getClassLoader().loadClass(intent.getStringExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT)); + getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, (Fragment) clazz.newInstance()).commitNow(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void launch(Context context, Class clazz) { + context.startActivity(new Intent(context, PreferenceActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT, clazz.getName())); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } + + return super.onOptionsItemSelected(item); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java index f459aa6dc..3cd28f4bf 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java @@ -1,19 +1,18 @@ package com.panda3ds.pandroid.app.base; import android.annotation.SuppressLint; - import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; - import com.panda3ds.pandroid.lang.Function; + public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { - @SuppressLint("RestrictedApi") - protected void setItemClick(String key, Function listener){ - findPreference(key).setOnPreferenceClickListener(preference -> { - listener.run(preference); - getPreferenceScreen().performClick(); - return false; - }); - } + @SuppressLint("RestrictedApi") + protected void setItemClick(String key, Function listener) { + findPreference(key).setOnPreferenceClickListener(preference -> { + listener.run(preference); + getPreferenceScreen().performClick(); + return false; + }); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java index 101676647..6daca9a61 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java @@ -2,67 +2,64 @@ import android.app.Activity; import android.view.KeyEvent; - import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.input.InputEvent; import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.input.KeyName; import com.panda3ds.pandroid.lang.Function; import com.panda3ds.pandroid.math.Vector2; - import java.util.Objects; -public class AlberInputListener implements Function { - private final Activity activity; - public AlberInputListener(Activity activity){ - this.activity = activity; - } - private final Vector2 axis = new Vector2(0.0f, 0.0f); +public class AlberInputListener implements Function { + private final Activity activity; + public AlberInputListener(Activity activity) { this.activity = activity; } - @Override - public void run(InputEvent event) { - KeyName key = InputMap.relative(event.getName()); + private final Vector2 axis = new Vector2(0.0f, 0.0f); - if (Objects.equals(event.getName(), "KEYCODE_BACK")){ - activity.onBackPressed(); - return; - } + @Override + public void run(InputEvent event) { + KeyName key = InputMap.relative(event.getName()); - if (key == KeyName.NULL) - return; + if (Objects.equals(event.getName(), "KEYCODE_BACK")) { + activity.onBackPressed(); + return; + } - boolean axisChanged = false; + if (key == KeyName.NULL) { + return; + } - switch (key) { - case AXIS_UP: - axis.y = event.getValue(); - axisChanged = true; - break; - case AXIS_DOWN: - axis.y = -event.getValue(); - axisChanged = true; - break; - case AXIS_LEFT: - axis.x = -event.getValue(); - axisChanged = true; - break; - case AXIS_RIGHT: - axis.x = event.getValue(); - axisChanged = true; - break; - default: - if (event.isDown()) { - AlberDriver.KeyDown(key.getKeyId()); - } else { - AlberDriver.KeyUp(key.getKeyId()); - } - break; - } + boolean axisChanged = false; - if (axisChanged) { - AlberDriver.SetCirclepadAxis(Math.round(axis.x * 0x9C), Math.round(axis.y * 0x9C)); - } - } + switch (key) { + case AXIS_UP: + axis.y = event.getValue(); + axisChanged = true; + break; + case AXIS_DOWN: + axis.y = -event.getValue(); + axisChanged = true; + break; + case AXIS_LEFT: + axis.x = -event.getValue(); + axisChanged = true; + break; + case AXIS_RIGHT: + axis.x = event.getValue(); + axisChanged = true; + break; + default: + if (event.isDown()) { + AlberDriver.KeyDown(key.getKeyId()); + } else { + AlberDriver.KeyUp(key.getKeyId()); + } + break; + } + if (axisChanged) { + AlberDriver.SetCirclepadAxis(Math.round(axis.x * 0x9C), Math.round(axis.y * 0x9C)); + } + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java index e2c97a17d..ff6e4dcae 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java @@ -6,75 +6,74 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; - import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.data.game.GameMetadata; import com.panda3ds.pandroid.utils.FileUtils; import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.view.gamesgrid.GamesGridView; + public class GamesFragment extends Fragment implements ActivityResultCallback { + private final ActivityResultContracts.OpenDocument openRomContract = new ActivityResultContracts.OpenDocument(); + private ActivityResultLauncher pickFileRequest; + private GamesGridView gameListView; - private final ActivityResultContracts.OpenDocument openRomContract = new ActivityResultContracts.OpenDocument(); - private ActivityResultLauncher pickFileRequest; - private GamesGridView gameListView; + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_games, container, false); + } - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_games, container, false); - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + gameListView = view.findViewById(R.id.games); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - gameListView = view.findViewById(R.id.games); + view.findViewById(R.id.add_rom).setOnClickListener((v) -> pickFileRequest.launch(new String[] {"*/*"})); + } - view.findViewById(R.id.add_rom).setOnClickListener((v) -> pickFileRequest.launch(new String[]{"*/*"})); - } + @Override + public void onResume() { + super.onResume(); + gameListView.setGameList(GameUtils.getGames()); + } - @Override - public void onResume() { - super.onResume(); - gameListView.setGameList(GameUtils.getGames()); - } + @Override + public void onActivityResult(Uri result) { + if (result != null) { + String uri = result.toString(); + if (GameUtils.findByRomPath(uri) == null) { + if (FileUtils.obtainRealPath(uri) == null) { + Toast.makeText(getContext(), "Invalid file path", Toast.LENGTH_LONG).show(); + return; + } + FileUtils.makeUriPermanent(uri, FileUtils.MODE_READ); + GameMetadata game = new GameMetadata(uri, FileUtils.getName(uri).split("\\.")[0], "Unknown"); + GameUtils.addGame(game); + GameUtils.launch(requireActivity(), game); + } + } + } - @Override - public void onActivityResult(Uri result) { - if (result != null) { - String uri = result.toString(); - if (GameUtils.findByRomPath(uri) == null) { - if (FileUtils.obtainRealPath(uri) == null){ - Toast.makeText(getContext(), "Invalid file path", Toast.LENGTH_LONG).show(); - return; - } - FileUtils.makeUriPermanent(uri, FileUtils.MODE_READ); - GameMetadata game = new GameMetadata(uri, FileUtils.getName(uri).split("\\.")[0],"Unknown"); - GameUtils.addGame(game); - GameUtils.launch(requireActivity(), game); - } - } - } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + pickFileRequest = registerForActivityResult(openRomContract, this); + } - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - pickFileRequest = registerForActivityResult(openRomContract, this); - } + @Override + public void onDestroy() { + if (pickFileRequest != null) { + pickFileRequest.unregister(); + pickFileRequest = null; + } - @Override - public void onDestroy() { - if (pickFileRequest != null) { - pickFileRequest.unregister(); - pickFileRequest = null; - } - super.onDestroy(); - } + super.onDestroy(); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java index 213d55fcb..e9db7f80a 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java @@ -4,64 +4,59 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatEditText; import androidx.fragment.app.Fragment; - import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.data.game.GameMetadata; import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.utils.SearchAgent; import com.panda3ds.pandroid.view.SimpleTextWatcher; import com.panda3ds.pandroid.view.gamesgrid.GamesGridView; - import java.util.ArrayList; import java.util.List; -public class SearchFragment extends Fragment { - - private final SearchAgent searchAgent = new SearchAgent(); - private GamesGridView gamesListView; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_search, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - gamesListView = view.findViewById(R.id.games); - - ((AppCompatEditText) view.findViewById(R.id.search_bar)) - .addTextChangedListener((SimpleTextWatcher) this::search); - } - - @Override - public void onResume() { - super.onResume(); - searchAgent.clearBuffer(); - for (GameMetadata game : GameUtils.getGames()) { - searchAgent.addToBuffer(game.getId(), game.getTitle(), game.getPublisher()); - } - search(""); - } - - private void search(String query) { - List resultIds = searchAgent.search(query); - ArrayList games = new ArrayList<>(GameUtils.getGames()); - Object[] resultObj = games.stream() - .filter(gameMetadata -> resultIds.contains(gameMetadata.getId())) - .toArray(); - - games.clear(); - for (Object res : resultObj) +public class SearchFragment extends Fragment { + private final SearchAgent searchAgent = new SearchAgent(); + private GamesGridView gamesListView; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_search, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + gamesListView = view.findViewById(R.id.games); + ((AppCompatEditText) view.findViewById(R.id.search_bar)).addTextChangedListener((SimpleTextWatcher) this::search); + } + + @Override + public void onResume() { + super.onResume(); + searchAgent.clearBuffer(); + for (GameMetadata game : GameUtils.getGames()) { + searchAgent.addToBuffer(game.getId(), game.getTitle(), game.getPublisher()); + } + + search(""); + } + + private void search(String query) { + List resultIds = searchAgent.search(query); + ArrayList games = new ArrayList<>(GameUtils.getGames()); + Object[] resultObj = games.stream().filter(gameMetadata -> resultIds.contains(gameMetadata.getId())).toArray(); + + games.clear(); + for (Object res : resultObj) { games.add((GameMetadata) res); + } - gamesListView.setGameList(games); - } + gamesListView.setGameList(games); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java index b7a2cbb49..dce56a5f5 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java @@ -28,8 +28,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onResume() { super.onResume(); + InputHandler.reset(); - InputHandler.setMotionDeadZone(0.8F); + InputHandler.setMotionDeadZone(0.8f); InputHandler.setEventListener(this::onInputEvent); } @@ -61,7 +62,6 @@ private void onInputEvent(InputEvent event) { public static final class Contract extends ActivityResultContract { - @NonNull @Override public Intent createIntent(@NonNull Context context, String s) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java index 00f031ac9..830244904 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java @@ -30,7 +30,10 @@ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable S ((BaseActivity) requireActivity()).getSupportActionBar().setTitle(R.string.controller_mapping); for (KeyName key : KeyName.values()) { - if (key == KeyName.NULL) continue; + if (key == KeyName.NULL) { + continue; + } + setItemClick(key.name(), this::onItemPressed); } @@ -59,6 +62,7 @@ public void onAttach(@NonNull Context context) { @Override public void onDetach() { super.onDetach(); + if (requestKey != null) { requestKey.unregister(); requestKey = null; @@ -77,10 +81,14 @@ public void onResume() { } private void refreshList() { - deadZonePreference.setValue((int)(InputMap.getDeadZone()*100)); - deadZonePreference.setSummary(deadZonePreference.getValue()+"%"); + deadZonePreference.setValue((int)(InputMap.getDeadZone() * 100)); + deadZonePreference.setSummary(deadZonePreference.getValue() + "%"); + for (KeyName key : KeyName.values()) { - if (key == KeyName.NULL) continue; + if (key == KeyName.NULL) { + continue; + } + findPreference(key.name()).setSummary(InputMap.relative(key)); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java index f88687f2d..f5025c32a 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/GsonConfigParser.java @@ -13,7 +13,7 @@ public GsonConfigParser(String name){ } private String getPath(){ - return FileUtils.getConfigPath()+"/"+name+".json"; + return FileUtils.getConfigPath()+ "/" + name + ".json"; } public void save(Object data){ @@ -26,7 +26,7 @@ public void save(Object data){ } public T load(Class clazz){ - String[] content = new String[]{"{}"}; + String[] content = new String[] {"{}"}; new Task(()->{ if (FileUtils.exists(getPath())){ content[0] = FileUtils.readTextFile(getPath()); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/SMDH.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/SMDH.java index 2401a4039..e1230f24f 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/SMDH.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/SMDH.java @@ -9,9 +9,8 @@ import java.nio.charset.StandardCharsets; public class SMDH { - - public static final int LANGUAGE_ENGLISH = 1; public static final int LANGUAGE_JAPANESE = 0; + public static final int LANGUAGE_ENGLISH = 1; public static final int LANGUAGE_CHINESE = 6; public static final int LANGUAGE_KOREAN = 7; @@ -61,6 +60,8 @@ private GameRegion parseRegion() { final boolean korea = (regionMasks & REGION_KOREAN_MASK) != 0; final boolean taiwan = (regionMasks & REGION_TAIWAN_MASK) != 0; + // Depending on the regions allowed in the region mask, pick one of the regions to use + // We prioritize English-speaking regions both here and in the emulator core, since users are most likely to speak English at least if (northAmerica) { region = GameRegion.NorthAmerican; } else if (europe) { @@ -71,14 +72,14 @@ private GameRegion parseRegion() { region = GameRegion.Japan; metaLanguage = LANGUAGE_JAPANESE; } else if (korea) { - metaLanguage = LANGUAGE_KOREAN; region = GameRegion.Korean; + metaLanguage = LANGUAGE_KOREAN; } else if (china) { - metaLanguage = LANGUAGE_CHINESE; region = GameRegion.China; - } else if (taiwan) { metaLanguage = LANGUAGE_CHINESE; + } else if (taiwan) { region = GameRegion.Taiwan; + metaLanguage = LANGUAGE_CHINESE; } else { region = GameRegion.None; } @@ -111,29 +112,30 @@ private int[] parseIcon() { int indexY = y & ~7; int indexX = x & ~7; - int i = mortonInterleave(x, y); - int offset = (i + (indexX * 8)) * 2; + int interleave = mortonInterleave(x, y); + int offset = (interleave + (indexX * 8)) * 2; offset = offset + indexY * ICON_SIZE * 2; smdh.position(offset + IMAGE_OFFSET); - int byte1 = smdh.get() & 0xFF; - int byte2 = smdh.get() & 0xFF; - - int texel = byte1 | (byte2 << 8); + int lowByte = smdh.get() & 0xFF; + int highByte = smdh.get() & 0xFF; + int texel = (highByte << 8) | lowByte; - int r = (texel >> 11) & 0x1f; - int g = (texel >> 5) & 0x3f; - int b = texel & 0x1f; + // Convert texel from RGB565 to RGB888 + int r = (texel >> 11) & 0x1F; + int g = (texel >> 5) & 0x3F; + int b = texel & 0x1F; - b = (b << 3) | (b >> 2); - g = (g << 2) | (g >> 4); r = (r << 3) | (r >> 2); + g = (g << 2) | (g >> 4); + b = (b << 3) | (b >> 2); icon[x + ICON_SIZE * y] = Color.rgb(r, g, b); } } + return icon; } @@ -160,7 +162,7 @@ public String getPublisher() { return publisher[metaLanguage]; } - // SMDH stores string in UTF-16LE format + // Strings in SMDH files are stored as UTF-16LE private static String convertString(byte[] buffer) { try { return new String(buffer, 0, buffer.length, StandardCharsets.UTF_16LE) @@ -174,6 +176,7 @@ private static String convertString(byte[] buffer) { private static int mortonInterleave(int u, int v) { int[] xlut = {0, 1, 4, 5, 16, 17, 20, 21}; int[] ylut = {0, 2, 8, 10, 32, 34, 40, 42}; + return xlut[u % 8] + ylut[v % 8]; } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java index 08142d06d..390708b99 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java @@ -26,11 +26,13 @@ public class InputHandler { private static final HashMap motionDownEvents = new HashMap<>(); - private static boolean containsSource(int[] sources, int sourceMasked) { + private static boolean containsSource(int[] sources, int sourceMask) { for (int source : sources) { - if ((sourceMasked & source) == source) + if ((source & sourceMask) == source) { return true; + } } + return false; } @@ -57,8 +59,9 @@ public static void setMotionDeadZone(float motionDeadZone) { } public static boolean processMotionEvent(MotionEvent event) { - if (!isSourceValid(event.getSource())) + if (!isSourceValid(event.getSource())) { return false; + } if (isGamepadSource(event.getSource())) { for (InputDevice.MotionRange range : event.getDevice().getMotionRanges()) { @@ -87,8 +90,9 @@ public static boolean processMotionEvent(MotionEvent event) { } public static boolean processKeyEvent(KeyEvent event) { - if (!isSourceValid(event.getSource())) + if (!isSourceValid(event.getSource())) { return false; + } if (isGamepadSource(event.getSource())) { // Dpad return motion event + key event, this remove the key event @@ -104,6 +108,7 @@ public static boolean processKeyEvent(KeyEvent event) { return true; } } + handleEvent(new InputEvent(KeyEvent.keyCodeToString(event.getKeyCode()), event.getAction() == KeyEvent.ACTION_UP ? 0.0f : 1.0f)); return true; } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java index 1b76c7576..b41a9e33e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java @@ -5,11 +5,11 @@ public Task(Runnable runnable){ super(runnable); } - public void runSync(){ start(); waitFinish(); } + public void waitFinish(){ try { join(); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java index 2b7ce281b..a065cd0ca 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java @@ -44,6 +44,7 @@ public static String getPrivatePath() { if (!file.exists()) { file.mkdirs(); } + return file.getAbsolutePath(); } @@ -52,6 +53,7 @@ public static String getConfigPath() { if (!file.exists()) { file.mkdirs(); } + return file.getAbsolutePath(); } @@ -61,8 +63,10 @@ public static boolean exists(String path) { public static boolean createDir(String path, String name) { DocumentFile folder = parseFile(path); - if (folder.findFile(name) != null) + if (folder.findFile(name) != null) { return true; + } + return folder.createDirectory(name) != null; } @@ -86,12 +90,14 @@ public static boolean writeTextFile(String path, String name, String content) { Log.e(Constants.LOG_TAG, "Error on write text file: ", e); return false; } + return true; } public static String readTextFile(String path) { - if (!exists(path)) + if (!exists(path)) { return null; + } try { InputStream stream = getInputStream(path); @@ -99,15 +105,15 @@ public static String readTextFile(String path) { int len; byte[] buffer = new byte[1024 * 8]; - while ((len = stream.read(buffer)) != -1) + while ((len = stream.read(buffer)) != -1) { output.write(buffer, 0, len); + } stream.close(); output.flush(); output.close(); byte[] data = output.toByteArray(); - return new String(data, 0, data.length); } catch (Exception e) { return null; @@ -124,8 +130,9 @@ public static OutputStream getOutputStream(String path) throws FileNotFoundExcep public static void makeUriPermanent(String uri, String mode) { int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION; - if (mode.toLowerCase().contains("w")) + if (mode.toLowerCase().contains("w")) { flags &= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + } getContext().getContentResolver().takePersistableUriPermission(Uri.parse(uri), flags); } @@ -141,6 +148,7 @@ public static String obtainRealPath(String uri) { ParcelFileDescriptor parcelDescriptor = getContext().getContentResolver().openFileDescriptor(Uri.parse(uri), "r"); int fd = parcelDescriptor.getFd(); File file = new File("/proc/self/fd/" + fd).getAbsoluteFile(); + for (int i = 0; i < CANONICAL_SEARCH_DEEP; i++) { try { String canonical = file.getCanonicalPath(); @@ -156,11 +164,14 @@ public static String obtainRealPath(String uri) { parcelDescriptor.close(); return file.getAbsolutePath(); } + String path = Os.readlink(file.getAbsolutePath()); parcelDescriptor.close(); - if (new File(path).exists()) + if (new File(path).exists()) { return path; + } + return null; } catch (Exception e) { return null; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java index b4ea2152d..749e1bd62 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java @@ -8,9 +8,8 @@ import java.util.List; public class SearchAgent { - - // Store all possibles results in map - // id->words + // Store all results in a hashmap + // Matches IDs -> Result string private final HashMap searchBuffer = new HashMap<>(); // Add search item to list @@ -19,15 +18,12 @@ public void addToBuffer(String id, String... words) { for (String word : words) { string.append(normalize(word)).append(" "); } + searchBuffer.put(id, string.toString()); } - /** - * Convert string to simple string with only a-z 0-9 for do this first it get the input string - * and apply lower case, after convert all chars to ASCII - * Ex: ç => c, á => a - * after replace all double space for single space - */ + // Convert string to lowercase alphanumeric string, converting all characters to ASCII and turning double spaces into single ones + // For example, é will be converted to e private String normalize(String string) { string = Normalizer.normalize(string, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""); @@ -40,26 +36,30 @@ private String normalize(String string) { public List search(String query) { String[] words = normalize(query).split("\\s"); - if (words.length == 0) + if (words.length == 0) { return Collections.emptyList(); + } // Map for add all search result: id -> probability HashMap results = new HashMap<>(); for (String key : searchBuffer.keySet()) { int probability = 0; String value = searchBuffer.get(key); + for (String word : words) { if (value.contains(word)) probability++; } - if (probability > 0) + + if (probability > 0) { results.put(key, probability); + } } - // Filter by probability average - // Ex: A = 10% B = 30% C = 70% (calc is (10+30+70)/3=36) - // After remove all result with probability < 36 + // Filter by probability average, ie by how closely they match to our query + // Ex: A = 10% B = 30% C = 70% (formula is (10+30+70)/3=36) + // Afterwards remove all results with probability < 36 int average = 0; for (String key : results.keySet()) { average += results.get(key); @@ -76,6 +76,7 @@ public List search(String query) { i = 0; continue; } + i++; } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java index df4d2a5c4..e73d8d083 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java @@ -11,7 +11,6 @@ import androidx.appcompat.widget.AppCompatImageView; public class GameIconView extends AppCompatImageView { - public GameIconView(@NonNull Context context) { super(context); } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java index f4ddfd12b..4f451c8f5 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/preferences/SingleSelectionPreferences.java @@ -56,6 +56,7 @@ public SingleSelectionPreferences(@NonNull Context context, @Nullable AttributeS @Override public void onAttached() { super.onAttached(); + for (int i = 0; i < getPreferenceCount();i++) { getPreference(i).setOnPreferenceClickListener(this); } @@ -68,6 +69,7 @@ public void setSelectedItem(int index){ @Override public boolean onPreferenceClick(@NonNull Preference preference) { int index = 0; + for (int i = 0; i < getPreferenceCount(); i++){ Preference item = getPreference(i); if (item == preference){