mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-19 06:45:39 +00:00
Android: Add BIOS importer
This commit is contained in:
parent
423054e8ac
commit
13a9411b07
|
@ -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());
|
||||||
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue