diff options
9 files changed, 251 insertions, 3 deletions
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 88e1669cd..de4dbb948 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ android:supportsRtl="true" android:isGame="true" android:banner="@mipmap/ic_launcher" + android:extractNativeLibs="true" android:requestLegacyExternalStorage="true"> <activity diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java index e56196310..c09b711fd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java @@ -181,7 +181,7 @@ public final class NativeLibrary { public static native void SetAppDirectory(String directory); - public static native void SetGpuDriverParameters(String hookLibDir, String customDriverDir, String customDriverName, String fileRedirectDir); + public static native void InitializeGpuDriver(String hookLibDir, String customDriverDir, String customDriverName, String fileRedirectDir); public static native boolean ReloadKeys(); diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.java index 9d4ed80b4..830d0b18c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.java @@ -13,6 +13,7 @@ import android.os.Build; import org.yuzu.yuzu_emu.model.GameDatabase; import org.yuzu.yuzu_emu.utils.DocumentsTree; import org.yuzu.yuzu_emu.utils.DirectoryInitialization; +import org.yuzu.yuzu_emu.utils.GpuDriverHelper; public class YuzuApplication extends Application { public static GameDatabase databaseHelper; @@ -43,7 +44,7 @@ public class YuzuApplication extends Application { documentsTree = new DocumentsTree(); DirectoryInitialization.start(getApplicationContext()); - + GpuDriverHelper.initializeDriverParameters(getApplicationContext()); NativeLibrary.LogDeviceInfo(); // TODO(bunnei): Disable notifications until we support app suspension. diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java index 552232bd3..d5009bc60 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java @@ -9,6 +9,7 @@ import android.view.MenuItem; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; @@ -22,6 +23,7 @@ import org.yuzu.yuzu_emu.utils.AddDirectoryHelper; import org.yuzu.yuzu_emu.utils.DirectoryInitialization; import org.yuzu.yuzu_emu.utils.FileBrowserHelper; import org.yuzu.yuzu_emu.utils.FileUtil; +import org.yuzu.yuzu_emu.utils.GpuDriverHelper; import org.yuzu.yuzu_emu.utils.PicassoUtils; import org.yuzu.yuzu_emu.utils.StartupHandler; import org.yuzu.yuzu_emu.utils.ThemeUtil; @@ -128,6 +130,41 @@ public final class MainActivity extends AppCompatActivity implements MainView { MainPresenter.REQUEST_INSTALL_KEYS, R.string.install_keys); break; + case MainPresenter.REQUEST_SELECT_GPU_DRIVER: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + + // Get the driver name for the dialog message. + String driverName = GpuDriverHelper.getCustomDriverName(); + if (driverName == null) { + driverName = getString(R.string.system_gpu_driver); + } + + // Set the dialog message and title. + builder.setTitle(getString(R.string.select_gpu_driver_title)); + builder.setMessage(driverName); + + // Cancel button is a no-op. + builder.setNegativeButton(android.R.string.cancel, null); + + // Select the default system driver. + builder.setPositiveButton(R.string.select_gpu_driver_default, (dialogInterface, i) -> + { + GpuDriverHelper.installDefaultDriver(this); + Toast.makeText(this, R.string.select_gpu_driver_use_default, Toast.LENGTH_SHORT).show(); + }); + + // Use the file picker to install a custom driver. + builder.setNeutralButton(R.string.select_gpu_driver_install, (dialogInterface, i) -> { + FileBrowserHelper.openFilePicker(this, + MainPresenter.REQUEST_SELECT_GPU_DRIVER, + R.string.select_gpu_driver); + }); + + // Show the dialog. + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + + break; } } @@ -163,12 +200,26 @@ public final class MainActivity extends AppCompatActivity implements MainView { Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show(); refreshFragment(); } else { - Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_LONG).show(); launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); } } } break; + + case MainPresenter.REQUEST_SELECT_GPU_DRIVER: + if (resultCode == MainActivity.RESULT_OK) { + int takeFlags = (Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + getContentResolver().takePersistableUriPermission(Uri.parse(result.getDataString()), takeFlags); + GpuDriverHelper.installCustomDriver(this, result.getData()); + String driverName = GpuDriverHelper.getCustomDriverName(); + if (driverName != null) { + Toast.makeText(this, getString(R.string.select_gpu_driver_install_success) + " " + driverName, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, R.string.select_gpu_driver_error, Toast.LENGTH_LONG).show(); + } + } + break; } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java index 82667a98f..d2ef753bc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainPresenter.java @@ -12,6 +12,7 @@ import org.yuzu.yuzu_emu.utils.AddDirectoryHelper; public final class MainPresenter { public static final int REQUEST_ADD_DIRECTORY = 1; public static final int REQUEST_INSTALL_KEYS = 2; + public static final int REQUEST_SELECT_GPU_DRIVER = 3; private final MainView mView; private String mDirToAdd; private long mLastClickTime = 0; @@ -51,6 +52,10 @@ public final class MainPresenter { case R.id.button_install_keys: launchFileListActivity(REQUEST_INSTALL_KEYS); return true; + + case R.id.button_select_gpu_driver: + launchFileListActivity(REQUEST_SELECT_GPU_DRIVER); + return true; } return false; diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.java new file mode 100644 index 000000000..822a3f1f9 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.java @@ -0,0 +1,130 @@ +package org.yuzu.yuzu_emu.utils; + +import android.content.Context; +import android.net.Uri; + +import org.yuzu.yuzu_emu.NativeLibrary; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class GpuDriverHelper { + private static final String META_JSON_FILENAME = "meta.json"; + private static final String DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"; + private static String fileRedirectionPath; + private static String driverInstallationPath; + private static String hookLibPath; + + private static void unzip(String zipFilePath, String destDir) throws IOException { + File dir = new File(destDir); + + // Create output directory if it doesn't exist + if (!dir.exists()) dir.mkdirs(); + + // Unpack the files. + ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath)); + byte[] buffer = new byte[1024]; + ZipEntry ze = zis.getNextEntry(); + while (ze != null) { + String fileName = ze.getName(); + File newFile = new File(destDir + fileName); + newFile.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + zis.closeEntry(); + ze = zis.getNextEntry(); + } + zis.closeEntry(); + } + + public static void initializeDriverParameters(Context context) { + try { + // Initialize the file redirection directory. + fileRedirectionPath = context.getExternalFilesDir(null).getCanonicalPath() + "/gpu/vk_file_redirect/"; + + // Initialize the driver installation directory. + driverInstallationPath = context.getFilesDir().getCanonicalPath() + "/gpu_driver/"; + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Initialize directories. + initializeDirectories(); + + // Initialize hook libraries directory. + hookLibPath = context.getApplicationInfo().nativeLibraryDir + "/"; + + // Initialize GPU driver. + NativeLibrary.InitializeGpuDriver(hookLibPath, driverInstallationPath, getCustomDriverLibraryName(), fileRedirectionPath); + } + + public static void installDefaultDriver(Context context) { + // Removing the installed driver will result in the backend using the default system driver. + File driverInstallationDir = new File(driverInstallationPath); + deleteRecursive(driverInstallationDir); + initializeDriverParameters(context); + } + + public static void installCustomDriver(Context context, Uri driverPathUri) { + // Revert to system default in the event the specified driver is bad. + installDefaultDriver(context); + + // Ensure we have directories. + initializeDirectories(); + + // Copy the zip file URI into our private storage. + FileUtil.copyUriToInternalStorage(context, driverPathUri, driverInstallationPath, DRIVER_INTERNAL_FILENAME); + + // Unzip the driver. + try { + unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Initialize the driver parameters. + initializeDriverParameters(context); + } + + public static String getCustomDriverName() { + // Parse the custom driver metadata to retrieve the name. + GpuDriverMetadata metadata = new GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME); + return metadata.name; + } + + private static String getCustomDriverLibraryName() { + // Parse the custom driver metadata to retrieve the library name. + GpuDriverMetadata metadata = new GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME); + return metadata.libraryName; + } + + private static void initializeDirectories() { + // Ensure the file redirection directory exists. + File fileRedirectionDir = new File(fileRedirectionPath); + if (!fileRedirectionDir.exists()) { + fileRedirectionDir.mkdirs(); + } + // Ensure the driver installation directory exists. + File driverInstallationDir = new File(driverInstallationPath); + if (!driverInstallationDir.exists()) { + driverInstallationDir.mkdirs(); + } + } + + private static void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + for (File child : fileOrDirectory.listFiles()) { + deleteRecursive(child); + } + } + fileOrDirectory.delete(); + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.java new file mode 100644 index 000000000..d2bb2dd23 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.java @@ -0,0 +1,45 @@ +package org.yuzu.yuzu_emu.utils; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class GpuDriverMetadata { + + public String name; + public String description; + public String author; + public String vendor; + public String driverVersion; + public int minApi; + public String libraryName; + + public GpuDriverMetadata(String metadataFilePath) { + try { + JSONObject json = new JSONObject(getStringFromFile(metadataFilePath)); + name = json.getString("name"); + description = json.getString("description"); + author = json.getString("author"); + vendor = json.getString("vendor"); + driverVersion = json.getString("driverVersion"); + minApi = json.getInt("minApi"); + libraryName = json.getString("libraryName"); + } catch (JSONException e) { + // JSON is malformed, ignore and treat as unsupported metadata. + } catch (IOException e) { + // File is inaccessible, ignore and treat as unsupported metadata. + } + } + + private static String getStringFromFile(String filePath) throws IOException { + Path path = Paths.get(filePath); + byte[] bytes = Files.readAllBytes(path); + return new String(bytes, StandardCharsets.UTF_8); + } + +} diff --git a/src/android/app/src/main/res/menu/menu_game_grid.xml b/src/android/app/src/main/res/menu/menu_game_grid.xml index 3eb8cf817..6211f5494 100644 --- a/src/android/app/src/main/res/menu/menu_game_grid.xml +++ b/src/android/app/src/main/res/menu/menu_game_grid.xml @@ -18,6 +18,11 @@ android:icon="@drawable/ic_install" android:title="@string/install_keys" app:showAsAction="ifRoom" /> + <item + android:id="@+id/button_select_gpu_driver" + android:icon="@drawable/ic_settings_core" + android:title="@string/select_gpu_driver" + app:showAsAction="ifRoom" /> </menu> </item> <item diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index e450ee869..27749b287 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -53,6 +53,16 @@ <string name="install_keys_success">Keys successfully installed</string> <string name="install_keys_failure">Keys file (prod.keys) is invalid</string> + <!-- GPU driver installation --> + <string name="select_gpu_driver">Select GPU driver</string> + <string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string> + <string name="select_gpu_driver_install">Install</string> + <string name="select_gpu_driver_default">Default</string> + <string name="select_gpu_driver_install_success">Installed</string> + <string name="select_gpu_driver_use_default">Using default GPU driver</string> + <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string> + <string name="system_gpu_driver">System GPU driver</string> + <!-- Preferences Screen --> <string name="preferences_settings">Settings</string> <string name="preferences_general">General</string> |