Android: Add BIOS importer

This commit is contained in:
Connor McLaughlin 2020-10-10 17:41:54 +10:00
parent 423054e8ac
commit 13a9411b07
6 changed files with 126 additions and 7 deletions

View file

@ -1,9 +1,11 @@
#include "android_host_interface.h" #include "android_host_interface.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/audio_stream.h" #include "common/audio_stream.h"
#include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/string.h" #include "common/string.h"
#include "common/timestamp.h" #include "common/timestamp.h"
#include "core/bios.h"
#include "core/cheats.h" #include "core/cheats.h"
#include "core/controller.h" #include "core/controller.h"
#include "core/gpu.h" #include "core/gpu.h"
@ -848,3 +850,37 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_addOSDMessage, jobject obj, js
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
hi->AddOSDMessage(AndroidHelpers::JStringToString(env, message), duration); hi->AddOSDMessage(AndroidHelpers::JStringToString(env, message), duration);
} }
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasAnyBIOSImages, jobject obj)
{
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
return hi->HasAnyBIOSImages();
}
DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_importBIOSImage, jobject obj, jbyteArray data)
{
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
const jsize len = env->GetArrayLength(data);
if (len != BIOS::BIOS_SIZE)
return nullptr;
BIOS::Image image;
image.resize(static_cast<size_t>(len));
env->GetByteArrayRegion(data, 0, len, reinterpret_cast<jbyte*>(image.data()));
const BIOS::Hash hash = BIOS::GetHash(image);
const BIOS::ImageInfo* ii = BIOS::GetImageInfoForHash(hash);
const std::string dest_path(hi->GetUserDirectoryRelativePath("bios/%s.bin", hash.ToString().c_str()));
if (FileSystem::FileExists(dest_path.c_str()) ||
!FileSystem::WriteBinaryFile(dest_path.c_str(), image.data(), image.size()))
{
return nullptr;
}
if (ii)
return env->NewStringUTF(ii->description);
else
return env->NewStringUTF(hash.ToString().c_str());
}

View file

@ -76,6 +76,9 @@ public class AndroidHostInterface {
public native void addOSDMessage(String message, float duration); public native void addOSDMessage(String message, float duration);
public native boolean hasAnyBIOSImages();
public native String importBIOSImage(byte[] data);
static { static {
System.loadLibrary("duckstation-native"); System.loadLibrary("duckstation-native");
} }

View file

@ -1,6 +1,7 @@
package com.github.stenzek.duckstation; package com.github.stenzek.duckstation;
import android.Manifest; import android.Manifest;
import android.content.ContentResolver;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
@ -33,7 +34,11 @@ import android.widget.ListView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.Toast; import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
@ -43,6 +48,7 @@ import static com.google.android.material.snackbar.Snackbar.make;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1; private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1;
private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2; private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2;
private static final int REQUEST_IMPORT_BIOS_IMAGE = 3;
private GameList mGameList; private GameList mGameList;
private ListView mGameListView; private ListView mGameListView;
@ -153,11 +159,11 @@ public class MainActivity extends AppCompatActivity {
startAddGameDirectory(); startAddGameDirectory();
} else if (id == R.id.action_scan_for_new_games) { } else if (id == R.id.action_scan_for_new_games) {
mGameList.refresh(false, false); mGameList.refresh(false, false);
} } else if (id == R.id.action_rescan_all_games) {
if (id == R.id.action_rescan_all_games) {
mGameList.refresh(true, true); mGameList.refresh(true, true);
} } else if (id == R.id.action_import_bios) {
if (id == R.id.action_settings) { importBIOSImage();
} else if (id == R.id.action_settings) {
Intent intent = new Intent(this, SettingsActivity.class); Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
@ -202,6 +208,14 @@ public class MainActivity extends AppCompatActivity {
mGameList.refresh(false, false); mGameList.refresh(false, false);
} }
break; break;
case REQUEST_IMPORT_BIOS_IMAGE: {
if (resultCode != RESULT_OK)
return;
onImportBIOSImageResult(data.getData());
}
break;
} }
} }
@ -240,10 +254,65 @@ public class MainActivity extends AppCompatActivity {
} }
private boolean startEmulation(String bootPath, boolean resumeState) { private boolean startEmulation(String bootPath, boolean resumeState) {
if (!doBIOSCheck())
return false;
Intent intent = new Intent(this, EmulationActivity.class); Intent intent = new Intent(this, EmulationActivity.class);
intent.putExtra("bootPath", bootPath); intent.putExtra("bootPath", bootPath);
intent.putExtra("resumeState", resumeState); intent.putExtra("resumeState", resumeState);
startActivity(intent); startActivity(intent);
return true; return true;
} }
private boolean doBIOSCheck() {
if (AndroidHostInterface.getInstance().hasAnyBIOSImages())
return true;
new AlertDialog.Builder(this)
.setTitle("Missing BIOS Image")
.setMessage("No BIOS image was found in DuckStation's bios directory. Do you with to locate and import a BIOS image now?")
.setPositiveButton("Yes", (dialog, button) -> importBIOSImage())
.setNegativeButton("No", (dialog, button) -> {})
.create()
.show();
return false;
}
private void importBIOSImage() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(intent, "Choose BIOS Image"), REQUEST_IMPORT_BIOS_IMAGE);
}
private void onImportBIOSImageResult(Uri uri) {
InputStream stream = null;
try {
stream = getContentResolver().openInputStream(uri);
} catch (FileNotFoundException e) {
Toast.makeText(this, "Failed to open BIOS image.", Toast.LENGTH_LONG);
return;
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[512 * 1024];
int len;
while ((len = stream.read(buffer)) > 0)
os.write(buffer, 0, len);
} catch (IOException e) {
Toast.makeText(this, "Failed to read BIOS image.", Toast.LENGTH_LONG);
return;
}
String importResult = AndroidHostInterface.getInstance().importBIOSImage(os.toByteArray());
String message = (importResult == null) ? "This BIOS image is invalid, or has already been imported." : ("BIOS '" + importResult + "' imported.");
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", (dialog, button) -> {})
.create()
.show();
}
} }

View file

@ -20,6 +20,9 @@
<item <item
android:id="@+id/action_rescan_all_games" android:id="@+id/action_rescan_all_games"
android:title="Rescan All Games" /> android:title="Rescan All Games" />
<item
android:id="@+id/action_import_bios"
android:title="Import BIOS" />
</group> </group>
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"

View file

@ -334,6 +334,12 @@ HostInterface::FindBIOSImagesInDirectory(const char* directory)
return results; return results;
} }
bool HostInterface::HasAnyBIOSImages()
{
const std::string dir = GetBIOSDirectory();
return (FindBIOSImageInDirectory(ConsoleRegion::NTSC_U, dir.c_str()).has_value());
}
bool HostInterface::LoadState(const char* filename) bool HostInterface::LoadState(const char* filename)
{ {
std::unique_ptr<ByteStream> stream = FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED); std::unique_ptr<ByteStream> stream = FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);

View file

@ -22,8 +22,7 @@ class GameList;
struct SystemBootParameters; struct SystemBootParameters;
namespace BIOS namespace BIOS {
{
struct ImageInfo; struct ImageInfo;
} }
@ -131,6 +130,9 @@ public:
/// Returns a list of filenames and descriptions for BIOS images in a directory. /// Returns a list of filenames and descriptions for BIOS images in a directory.
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory); std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory);
/// Returns true if any BIOS images are found in the configured BIOS directory.
bool HasAnyBIOSImages();
virtual void OnRunningGameChanged(); virtual void OnRunningGameChanged();
virtual void OnSystemPerformanceCountersUpdated(); virtual void OnSystemPerformanceCountersUpdated();