Merge latest core changes
|
@ -712,10 +712,12 @@ else()
|
|||
include_directories(Externals/SOIL)
|
||||
endif()
|
||||
|
||||
find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c)
|
||||
find_path(ICONV_INCLUDE_DIR NAMES iconv.h)
|
||||
if (NOT ANDROID)
|
||||
find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c)
|
||||
find_path(ICONV_INCLUDE_DIR NAMES iconv.h)
|
||||
endif()
|
||||
|
||||
if (ICONV_LIBRARIES AND ICONV_INCLUDE_DIR)
|
||||
if (NOT ANDROID AND ICONV_LIBRARIES AND ICONV_INCLUDE_DIR)
|
||||
mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES)
|
||||
else()
|
||||
message(STATUS "Using static iconv from Externals")
|
||||
|
@ -758,10 +760,6 @@ if(ENABLE_WX)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD|NetBSD")
|
||||
set(LIBS ${LIBS} usbhid)
|
||||
endif()
|
||||
|
||||
########################################
|
||||
# Pre-build events: Define configuration variables and write SCM info header
|
||||
#
|
||||
|
|
4
Data/Sys/GameSettings/GP3.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
# GP3E78 - The Polar Express
|
||||
|
||||
[Video_Hacks]
|
||||
EFBEmulateFormatChanges = True
|
4
Data/Sys/GameSettings/SUU.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SUUE78 - uDraw Studio - Instant Artist
|
||||
|
||||
[Video_Settings]
|
||||
SafeTextureCacheColorSamples = 0
|
4
Data/Sys/GameSettings/SUW.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SUWE78 - uDraw Studio
|
||||
|
||||
[Video_Settings]
|
||||
SafeTextureCacheColorSamples = 0
|
62
Externals/hidapi/applied_patches/0001-macOS-Use-unique-IDs-for-HID-paths.patch
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
From 8d5810a1038347b9e56d41334d3f83641c913b3d Mon Sep 17 00:00:00 2001
|
||||
From: Vincent Duvert <vincent@duvert.net>
|
||||
Date: Sun, 7 Jan 2018 11:00:01 +0100
|
||||
Subject: [PATCH 1/2] macOS: Use unique IDs for HID paths
|
||||
|
||||
If available, use the system-generated unique ID for HID device paths instead of a transport/vid/pid/location tuple.
|
||||
The Mayflash Dolphinbar registers four HID devices (regardless of the number of connected Wiimotes) which had the same path with the previous path building method, causing a bit of confusion when detecting and connecting to Wiimotes.
|
||||
The unique IDs do not change if the computer is suspended and resumed, but do change if the HID device is unplugged/replugged.
|
||||
---
|
||||
Externals/hidapi/mac/hid.c | 21 ++++++++++++++++-----
|
||||
1 file changed, 16 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/Externals/hidapi/mac/hid.c b/Externals/hidapi/mac/hid.c
|
||||
index 38bb635af2..46a97886d7 100644
|
||||
--- a/Externals/hidapi/mac/hid.c
|
||||
+++ b/Externals/hidapi/mac/hid.c
|
||||
@@ -217,6 +217,11 @@ static int32_t get_location_id(IOHIDDeviceRef device)
|
||||
return get_int_property(device, CFSTR(kIOHIDLocationIDKey));
|
||||
}
|
||||
|
||||
+static int32_t get_unique_id(IOHIDDeviceRef device)
|
||||
+{
|
||||
+ return get_int_property(device, CFSTR(kIOHIDUniqueIDKey));
|
||||
+}
|
||||
+
|
||||
static int32_t get_max_report_length(IOHIDDeviceRef device)
|
||||
{
|
||||
return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey));
|
||||
@@ -337,6 +342,7 @@ static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
|
||||
unsigned short vid, pid;
|
||||
char transport[32];
|
||||
int32_t location;
|
||||
+ int32_t unique_id;
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
@@ -347,12 +353,17 @@ static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
|
||||
if (!res)
|
||||
return -1;
|
||||
|
||||
- location = get_location_id(device);
|
||||
- vid = get_vendor_id(device);
|
||||
- pid = get_product_id(device);
|
||||
+ unique_id = get_unique_id(device);
|
||||
+ if (unique_id != 0) {
|
||||
+ res = snprintf(buf, len, "id_%x", unique_id);
|
||||
+ } else {
|
||||
+ location = get_location_id(device);
|
||||
+ vid = get_vendor_id(device);
|
||||
+ pid = get_product_id(device);
|
||||
|
||||
- res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
|
||||
- transport, vid, pid, location);
|
||||
+ res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
|
||||
+ transport, vid, pid, location);
|
||||
+ }
|
||||
|
||||
|
||||
buf[len-1] = '\0';
|
||||
--
|
||||
2.14.3 (Apple Git-98)
|
||||
|
51
Externals/hidapi/applied_patches/0002-macOS-Add-errno-setting-in-set_report-HID.patch
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
From 3abc288e02089b3143547177e027d3820e5d7e59 Mon Sep 17 00:00:00 2001
|
||||
From: Vincent Duvert <vincent@duvert.net>
|
||||
Date: Sun, 7 Jan 2018 11:14:51 +0100
|
||||
Subject: [PATCH 2/2] macOS: Add errno setting in set_report (HID)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
IsDeviceUsable in IOhidapi.cpp uses errno to detect if hid_write failed because of an unconnected Wiimote on a Dolphinbar (it expects errno == EPIPE in this case).
|
||||
macOS’s implementation of hid_write detected this specific error (IOHIDDeviceSetReport returns kUSBHostReturnPipeStalled) but didn’t set errno so the check failed.
|
||||
This add errno assignment to failure cases of macOS’s hid_write.
|
||||
---
|
||||
Externals/hidapi/mac/hid.c | 13 ++++++++++---
|
||||
1 file changed, 10 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/Externals/hidapi/mac/hid.c b/Externals/hidapi/mac/hid.c
|
||||
index 46a97886d7..70b615d40d 100644
|
||||
--- a/Externals/hidapi/mac/hid.c
|
||||
+++ b/Externals/hidapi/mac/hid.c
|
||||
@@ -773,8 +773,10 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char
|
||||
IOReturn res;
|
||||
|
||||
/* Return if the device has been disconnected. */
|
||||
- if (dev->disconnected)
|
||||
+ if (dev->disconnected) {
|
||||
+ errno = ENODEV;
|
||||
return -1;
|
||||
+ }
|
||||
|
||||
if (data[0] == 0x0) {
|
||||
/* Not using numbered Reports.
|
||||
@@ -797,9 +799,14 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char
|
||||
|
||||
if (res == kIOReturnSuccess) {
|
||||
return length;
|
||||
- }
|
||||
- else
|
||||
+ } else if (res == (IOReturn)0xe0005000) {
|
||||
+ /* Kernel.framework's IOUSBHostFamily.h defines this error as kUSBHostReturnPipeStalled */
|
||||
+ errno = EPIPE;
|
||||
+ return -1;
|
||||
+ } else {
|
||||
+ errno = EBUSY;
|
||||
return -1;
|
||||
+ }
|
||||
}
|
||||
|
||||
return -1;
|
||||
--
|
||||
2.14.3 (Apple Git-98)
|
||||
|
34
Externals/hidapi/mac/hid.c
vendored
|
@ -217,6 +217,11 @@ static int32_t get_location_id(IOHIDDeviceRef device)
|
|||
return get_int_property(device, CFSTR(kIOHIDLocationIDKey));
|
||||
}
|
||||
|
||||
static int32_t get_unique_id(IOHIDDeviceRef device)
|
||||
{
|
||||
return get_int_property(device, CFSTR(kIOHIDUniqueIDKey));
|
||||
}
|
||||
|
||||
static int32_t get_max_report_length(IOHIDDeviceRef device)
|
||||
{
|
||||
return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey));
|
||||
|
@ -337,6 +342,7 @@ static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
|
|||
unsigned short vid, pid;
|
||||
char transport[32];
|
||||
int32_t location;
|
||||
int32_t unique_id;
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
|
@ -347,12 +353,17 @@ static int make_path(IOHIDDeviceRef device, char *buf, size_t len)
|
|||
if (!res)
|
||||
return -1;
|
||||
|
||||
location = get_location_id(device);
|
||||
vid = get_vendor_id(device);
|
||||
pid = get_product_id(device);
|
||||
unique_id = get_unique_id(device);
|
||||
if (unique_id != 0) {
|
||||
res = snprintf(buf, len, "id_%x", unique_id);
|
||||
} else {
|
||||
location = get_location_id(device);
|
||||
vid = get_vendor_id(device);
|
||||
pid = get_product_id(device);
|
||||
|
||||
res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
|
||||
transport, vid, pid, location);
|
||||
res = snprintf(buf, len, "%s_%04hx_%04hx_%x",
|
||||
transport, vid, pid, location);
|
||||
}
|
||||
|
||||
|
||||
buf[len-1] = '\0';
|
||||
|
@ -762,8 +773,10 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char
|
|||
IOReturn res;
|
||||
|
||||
/* Return if the device has been disconnected. */
|
||||
if (dev->disconnected)
|
||||
if (dev->disconnected) {
|
||||
errno = ENODEV;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data[0] == 0x0) {
|
||||
/* Not using numbered Reports.
|
||||
|
@ -786,9 +799,14 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char
|
|||
|
||||
if (res == kIOReturnSuccess) {
|
||||
return length;
|
||||
}
|
||||
else
|
||||
} else if (res == (IOReturn)0xe0005000) {
|
||||
/* Kernel.framework's IOUSBHostFamily.h defines this error as kUSBHostReturnPipeStalled */
|
||||
errno = EPIPE;
|
||||
return -1;
|
||||
} else {
|
||||
errno = EBUSY;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
|
|
@ -4,6 +4,11 @@ android {
|
|||
compileSdkVersion 26
|
||||
buildToolsVersion '26.0.2'
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
// This is important as it will run lint but not abort on error
|
||||
// Lint has some overly obnoxious "errors" that should really be warnings
|
||||
|
@ -75,22 +80,24 @@ ext {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
api "com.android.support:support-v13:$androidSupportVersion"
|
||||
api "com.android.support:cardview-v7:$androidSupportVersion"
|
||||
api "com.android.support:recyclerview-v7:$androidSupportVersion"
|
||||
api "com.android.support:design:$androidSupportVersion"
|
||||
implementation "com.android.support:support-v13:$androidSupportVersion"
|
||||
implementation "com.android.support:cardview-v7:$androidSupportVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$androidSupportVersion"
|
||||
implementation "com.android.support:design:$androidSupportVersion"
|
||||
|
||||
// Android TV UI libraries.
|
||||
api "com.android.support:leanback-v17:$androidSupportVersion"
|
||||
implementation "com.android.support:leanback-v17:$androidSupportVersion"
|
||||
|
||||
// For showing the banner as a circle a-la Material Design Guidelines
|
||||
api 'de.hdodenhof:circleimageview:2.1.0'
|
||||
implementation 'de.hdodenhof:circleimageview:2.1.0'
|
||||
|
||||
// For loading huge screenshots from the disk.
|
||||
api 'com.squareup.picasso:picasso:2.5.2'
|
||||
implementation 'com.squareup.picasso:picasso:2.5.2'
|
||||
|
||||
// Allows FRP-style asynchronous operations in Android.
|
||||
api 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
|
||||
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
||||
}
|
||||
|
||||
def getVersion() {
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="application_id">org.dolphinemu.dolphinemu.debug</string>
|
||||
</resources>
|
|
@ -50,11 +50,6 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.AddDirectoryActivity"
|
||||
android:theme="@style/DolphinGamecube"
|
||||
android:label="@string/add_directory_title"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.settings.SettingsActivity"
|
||||
android:theme="@style/DolphinSettingsGamecube"
|
||||
|
@ -64,8 +59,17 @@
|
|||
android:name=".activities.EmulationActivity"
|
||||
android:theme="@style/DolphinEmulationGamecube"/>
|
||||
|
||||
<activity
|
||||
android:name=".activities.CustomFilePickerActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/FilePickerTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".services.AssetCopyService"/>
|
||||
<service android:name=".services.DirectoryInitializationService"/>
|
||||
|
||||
<provider
|
||||
android:name=".model.GameProvider"
|
||||
|
@ -74,6 +78,16 @@
|
|||
android:exported="false">
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.filesprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -3,6 +3,8 @@ package org.dolphinemu.dolphinemu;
|
|||
import android.app.Application;
|
||||
|
||||
import org.dolphinemu.dolphinemu.model.GameDatabase;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||
|
||||
public class DolphinApplication extends Application
|
||||
{
|
||||
|
@ -13,6 +15,11 @@ public class DolphinApplication extends Application
|
|||
{
|
||||
super.onCreate();
|
||||
|
||||
NativeLibrary.SetUserDirectory(""); // Empty string means use the default path
|
||||
|
||||
if (PermissionsHandler.hasWriteAccess(getApplicationContext()))
|
||||
DirectoryInitializationService.startService(getApplicationContext());
|
||||
|
||||
databaseHelper = new GameDatabase(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.view.Surface;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -283,6 +288,8 @@ public final class NativeLibrary
|
|||
*/
|
||||
public static native String GetVersionString();
|
||||
|
||||
public static native String GetGitRevision();
|
||||
|
||||
/**
|
||||
* Saves a screen capture of the game
|
||||
*/
|
||||
|
@ -292,8 +299,19 @@ public final class NativeLibrary
|
|||
* Saves a game state to the slot number.
|
||||
*
|
||||
* @param slot The slot location to save state to.
|
||||
* @param wait If false, returns as early as possible.
|
||||
* If true, returns once the savestate has been written to disk.
|
||||
*/
|
||||
public static native void SaveState(int slot);
|
||||
public static native void SaveState(int slot, boolean wait);
|
||||
|
||||
/**
|
||||
* Saves a game state to the specified path.
|
||||
*
|
||||
* @param path The path to save state to.
|
||||
* @param wait If false, returns as early as possible.
|
||||
* If true, returns once the savestate has been written to disk.
|
||||
*/
|
||||
public static native void SaveStateAs(String path, boolean wait);
|
||||
|
||||
/**
|
||||
* Loads a game state from the slot number.
|
||||
|
@ -303,9 +321,11 @@ public final class NativeLibrary
|
|||
public static native void LoadState(int slot);
|
||||
|
||||
/**
|
||||
* Creates the initial folder structure in /sdcard/dolphin-emu/
|
||||
* Loads a game state from the specified path.
|
||||
*
|
||||
* @param path The path to load state from.
|
||||
*/
|
||||
public static native void CreateUserFolders();
|
||||
public static native void LoadStateAs(String path);
|
||||
|
||||
/**
|
||||
* Sets the current working user directory
|
||||
|
@ -325,6 +345,11 @@ public final class NativeLibrary
|
|||
*/
|
||||
public static native void Run(String path);
|
||||
|
||||
/**
|
||||
* Begins emulation from the specified savestate.
|
||||
*/
|
||||
public static native void Run(String path, String savestatePath, boolean deleteSavestate);
|
||||
|
||||
// Surface Handling
|
||||
public static native void SurfaceChanged(Surface surf);
|
||||
public static native void SurfaceDestroyed();
|
||||
|
@ -338,6 +363,9 @@ public final class NativeLibrary
|
|||
/** Stops emulation. */
|
||||
public static native void StopEmulation();
|
||||
|
||||
/** Returns true if emulation is running (or is paused). */
|
||||
public static native boolean IsRunning();
|
||||
|
||||
/**
|
||||
* Enables or disables CPU block profiling
|
||||
* @param enable
|
||||
|
@ -378,25 +406,81 @@ public final class NativeLibrary
|
|||
CacheClassesAndMethods();
|
||||
}
|
||||
|
||||
public static void displayAlertMsg(final String alert)
|
||||
private static boolean alertResult = false;
|
||||
public static boolean displayAlertMsg(final String caption, final String text, final boolean yesNo)
|
||||
{
|
||||
Log.error("[NativeLibrary] Alert: " + alert);
|
||||
Log.error("[NativeLibrary] Alert: " + text);
|
||||
final EmulationActivity emulationActivity = sEmulationActivity.get();
|
||||
if (emulationActivity != null)
|
||||
boolean result = false;
|
||||
if (emulationActivity == null)
|
||||
{
|
||||
emulationActivity.runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Toast.makeText(emulationActivity, "Panic Alert: " + alert, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic toast.");
|
||||
// Create object used for waiting.
|
||||
final Object lock = new Object();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity)
|
||||
.setTitle(caption)
|
||||
.setMessage(text);
|
||||
|
||||
// If not yes/no dialog just have one button that dismisses modal,
|
||||
// otherwise have a yes and no button that sets alertResult accordingly.
|
||||
if (!yesNo)
|
||||
{
|
||||
builder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("OK", (dialog, whichButton) ->
|
||||
{
|
||||
dialog.dismiss();
|
||||
synchronized (lock)
|
||||
{
|
||||
lock.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
alertResult = false;
|
||||
|
||||
builder
|
||||
.setPositiveButton("Yes", (dialog, whichButton) ->
|
||||
{
|
||||
alertResult = true;
|
||||
dialog.dismiss();
|
||||
synchronized (lock)
|
||||
{
|
||||
lock.notify();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("No", (dialog, whichButton) ->
|
||||
{
|
||||
alertResult = false;
|
||||
dialog.dismiss();
|
||||
synchronized (lock)
|
||||
{
|
||||
lock.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show the AlertDialog on the main thread.
|
||||
emulationActivity.runOnUiThread(() -> builder.show());
|
||||
|
||||
// Wait for the lock to notify that it is complete.
|
||||
synchronized (lock)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock.wait();
|
||||
}
|
||||
catch (Exception e) { }
|
||||
}
|
||||
|
||||
if (yesNo)
|
||||
result = alertResult;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void setEmulationActivity(EmulationActivity emulationActivity)
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
package org.dolphinemu.dolphinemu.activities;
|
||||
|
||||
import android.content.AsyncQueryHandler;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.adapters.FileAdapter;
|
||||
import org.dolphinemu.dolphinemu.model.GameDatabase;
|
||||
import org.dolphinemu.dolphinemu.model.GameProvider;
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||
|
||||
/**
|
||||
* An Activity that shows a list of files and folders, allowing the user to tell the app which folder(s)
|
||||
* contains the user's games.
|
||||
*/
|
||||
public class AddDirectoryActivity extends AppCompatActivity implements FileAdapter.FileClickListener
|
||||
{
|
||||
private static final String KEY_CURRENT_PATH = "path";
|
||||
|
||||
private FileAdapter mAdapter;
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_add_directory);
|
||||
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar_folder_list);
|
||||
setSupportActionBar(mToolbar);
|
||||
|
||||
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list_files);
|
||||
|
||||
// Specifying the LayoutManager determines how the RecyclerView arranges views.
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
String path;
|
||||
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
|
||||
if (savedInstanceState == null)
|
||||
{
|
||||
path = Environment.getExternalStorageDirectory().getPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the path we were looking at before we rotated.
|
||||
path = savedInstanceState.getString(KEY_CURRENT_PATH);
|
||||
}
|
||||
|
||||
mAdapter = new FileAdapter(path, this);
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.menu_add_directory, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case R.id.menu_up_one_level:
|
||||
mAdapter.upOneLevel();
|
||||
break;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
// Save the path we're looking at so when rotation is done, we start from same folder.
|
||||
outState.putString(KEY_CURRENT_PATH, mAdapter.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory to the library, and if successful, end the activity.
|
||||
*/
|
||||
@Override
|
||||
public void addDirectory()
|
||||
{
|
||||
// Set up a callback for when the addition is complete
|
||||
// TODO This has a nasty warning on it; find a cleaner way to do this Insert asynchronously
|
||||
AsyncQueryHandler handler = new AsyncQueryHandler(getContentResolver())
|
||||
{
|
||||
@Override
|
||||
protected void onInsertComplete(int token, Object cookie, Uri uri)
|
||||
{
|
||||
Intent resultData = new Intent();
|
||||
|
||||
resultData.putExtra(KEY_CURRENT_PATH, mAdapter.getPath());
|
||||
setResult(RESULT_OK, resultData);
|
||||
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
ContentValues file = new ContentValues();
|
||||
file.put(GameDatabase.KEY_FOLDER_PATH, mAdapter.getPath());
|
||||
|
||||
handler.startInsert(0, // We don't need to identify this call to the handler
|
||||
null, // We don't need to pass additional data to the handler
|
||||
GameProvider.URI_FOLDER, // Tell the GameProvider we are adding a folder
|
||||
file); // Tell the GameProvider what folder we are adding
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSubtitle(String path)
|
||||
{
|
||||
mToolbar.setSubtitle(path);
|
||||
}
|
||||
|
||||
public static void launch(FragmentActivity activity)
|
||||
{
|
||||
Intent fileChooser = new Intent(activity, AddDirectoryActivity.class);
|
||||
activity.startActivityForResult(fileChooser, MainPresenter.REQUEST_ADD_DIRECTORY);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package org.dolphinemu.dolphinemu.activities;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
|
||||
import org.dolphinemu.dolphinemu.fragments.CustomFilePickerFragment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CustomFilePickerActivity extends FilePickerActivity
|
||||
|
||||
{
|
||||
@Override
|
||||
protected AbstractFilePickerFragment<File> getFragment(
|
||||
@Nullable final String startPath, final int mode, final boolean allowMultiple,
|
||||
final boolean allowCreateDir, final boolean allowExistingFile,
|
||||
final boolean singleClick)
|
||||
{
|
||||
AbstractFilePickerFragment<File> fragment = new CustomFilePickerFragment();
|
||||
// startPath is allowed to be null. In that case, default folder should be SD-card and not "/"
|
||||
fragment.setArgs(startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(),
|
||||
mode, allowMultiple, allowCreateDir, allowExistingFile, singleClick);
|
||||
return fragment;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package org.dolphinemu.dolphinemu.activities;
|
|||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.hardware.usb.UsbManager;
|
||||
|
@ -39,9 +38,9 @@ import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment;
|
|||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||
import org.dolphinemu.dolphinemu.utils.Animations;
|
||||
import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.List;
|
||||
|
@ -57,6 +56,7 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
private EmulationFragment mEmulationFragment;
|
||||
|
||||
private SharedPreferences mPreferences;
|
||||
private ControllerMappingHelper mControllerMappingHelper;
|
||||
|
||||
// So that MainActivity knows which view to invalidate before the return animation.
|
||||
private int mPosition;
|
||||
|
@ -66,8 +66,15 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
private static boolean sIsGameCubeGame;
|
||||
|
||||
private boolean activityRecreated;
|
||||
private String mScreenPath;
|
||||
private String mSelectedTitle;
|
||||
private String mPath;
|
||||
|
||||
public static final String EXTRA_SELECTED_GAME = "SelectedGame";
|
||||
public static final String EXTRA_SELECTED_TITLE = "SelectedTitle";
|
||||
public static final String EXTRA_SCREEN_PATH = "ScreenPath";
|
||||
public static final String EXTRA_GRID_POSITION = "GridPosition";
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({MENU_ACTION_EDIT_CONTROLS_PLACEMENT, MENU_ACTION_TOGGLE_CONTROLS, MENU_ACTION_ADJUST_SCALE,
|
||||
|
@ -136,10 +143,10 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
{
|
||||
Intent launcher = new Intent(activity, EmulationActivity.class);
|
||||
|
||||
launcher.putExtra("SelectedGame", path);
|
||||
launcher.putExtra("SelectedTitle", title);
|
||||
launcher.putExtra("ScreenPath", screenshotPath);
|
||||
launcher.putExtra("GridPosition", position);
|
||||
launcher.putExtra(EXTRA_SELECTED_GAME, path);
|
||||
launcher.putExtra(EXTRA_SELECTED_TITLE, title);
|
||||
launcher.putExtra(EXTRA_SCREEN_PATH, screenshotPath);
|
||||
launcher.putExtra(EXTRA_GRID_POSITION, position);
|
||||
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
activity,
|
||||
|
@ -156,14 +163,25 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Get params we were passed
|
||||
Intent gameToEmulate = getIntent();
|
||||
String path = gameToEmulate.getStringExtra("SelectedGame");
|
||||
sIsGameCubeGame = Platform.fromNativeInt(NativeLibrary.GetPlatform(path)) == Platform.GAMECUBE;
|
||||
mSelectedTitle = gameToEmulate.getStringExtra("SelectedTitle");
|
||||
mScreenPath = gameToEmulate.getStringExtra("ScreenPath");
|
||||
mPosition = gameToEmulate.getIntExtra("GridPosition", -1);
|
||||
if (savedInstanceState == null)
|
||||
{
|
||||
// Get params we were passed
|
||||
Intent gameToEmulate = getIntent();
|
||||
mPath = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME);
|
||||
mSelectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE);
|
||||
mScreenPath = gameToEmulate.getStringExtra(EXTRA_SCREEN_PATH);
|
||||
mPosition = gameToEmulate.getIntExtra(EXTRA_GRID_POSITION, -1);
|
||||
activityRecreated = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
activityRecreated = true;
|
||||
restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
sIsGameCubeGame = Platform.fromNativeInt(NativeLibrary.GetPlatform(mPath)) == Platform.GAMECUBE;
|
||||
mDeviceHasTouchScreen = getPackageManager().hasSystemFeature("android.hardware.touchscreen");
|
||||
mControllerMappingHelper = new ControllerMappingHelper();
|
||||
|
||||
int themeId;
|
||||
if (mDeviceHasTouchScreen)
|
||||
|
@ -172,24 +190,13 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
// Get a handle to the Window containing the UI.
|
||||
mDecorView = getWindow().getDecorView();
|
||||
mDecorView.setOnSystemUiVisibilityChangeListener
|
||||
(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int visibility) {
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
|
||||
{
|
||||
// Go back to immersive fullscreen mode in 3s
|
||||
Handler handler = new Handler(getMainLooper());
|
||||
handler.postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
enableFullscreenImmersive();
|
||||
}
|
||||
},
|
||||
3000 /* 3s */);
|
||||
}
|
||||
mDecorView.setOnSystemUiVisibilityChangeListener(visibility ->
|
||||
{
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
|
||||
{
|
||||
// Go back to immersive fullscreen mode in 3s
|
||||
Handler handler = new Handler(getMainLooper());
|
||||
handler.postDelayed(this::enableFullscreenImmersive, 3000 /* 3s */);
|
||||
}
|
||||
});
|
||||
// Set these options now so that the SurfaceView the game renders into is the right size.
|
||||
|
@ -214,7 +221,7 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
.findFragmentById(R.id.frame_emulation_fragment);
|
||||
if (mEmulationFragment == null)
|
||||
{
|
||||
mEmulationFragment = EmulationFragment.newInstance(path);
|
||||
mEmulationFragment = EmulationFragment.newInstance(mPath);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.frame_emulation_fragment, mEmulationFragment)
|
||||
.commit();
|
||||
|
@ -248,14 +255,7 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
Animations.fadeViewOut(mImageView)
|
||||
.setStartDelay(2000)
|
||||
.withEndAction(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mImageView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
.withEndAction(() -> mImageView.setVisibility(View.GONE));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -271,6 +271,25 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
mEmulationFragment.saveTemporaryState();
|
||||
outState.putString(EXTRA_SELECTED_GAME, mPath);
|
||||
outState.putString(EXTRA_SELECTED_TITLE, mSelectedTitle);
|
||||
outState.putString(EXTRA_SCREEN_PATH, mScreenPath);
|
||||
outState.putInt(EXTRA_GRID_POSITION, mPosition);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
protected void restoreState(Bundle savedInstanceState)
|
||||
{
|
||||
mPath = savedInstanceState.getString(EXTRA_SELECTED_GAME);
|
||||
mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE);
|
||||
mScreenPath = savedInstanceState.getString(EXTRA_SCREEN_PATH);
|
||||
mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed()
|
||||
{
|
||||
|
@ -325,35 +344,27 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
public void exitWithAnimation()
|
||||
{
|
||||
runOnUiThread(new Runnable()
|
||||
public void exitWithAnimation() {
|
||||
runOnUiThread(() ->
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Picasso.with(EmulationActivity.this)
|
||||
.invalidate(mScreenPath);
|
||||
Picasso.with(EmulationActivity.this)
|
||||
.invalidate(mScreenPath);
|
||||
|
||||
Picasso.with(EmulationActivity.this)
|
||||
.load(mScreenPath)
|
||||
.noFade()
|
||||
.noPlaceholder()
|
||||
.into(mImageView, new Callback()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess()
|
||||
{
|
||||
showScreenshot();
|
||||
}
|
||||
Picasso.with(EmulationActivity.this)
|
||||
.load(mScreenPath)
|
||||
.noFade()
|
||||
.noPlaceholder()
|
||||
.into(mImageView, new Callback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
showScreenshot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError()
|
||||
{
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onError() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -435,7 +446,7 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
// Quick save / load
|
||||
case MENU_ACTION_QUICK_SAVE:
|
||||
NativeLibrary.SaveState(9);
|
||||
NativeLibrary.SaveState(9, false);
|
||||
return;
|
||||
|
||||
case MENU_ACTION_QUICK_LOAD:
|
||||
|
@ -459,27 +470,27 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
// Save state slots
|
||||
case MENU_ACTION_SAVE_SLOT1:
|
||||
NativeLibrary.SaveState(0);
|
||||
NativeLibrary.SaveState(0, false);
|
||||
return;
|
||||
|
||||
case MENU_ACTION_SAVE_SLOT2:
|
||||
NativeLibrary.SaveState(1);
|
||||
NativeLibrary.SaveState(1, false);
|
||||
return;
|
||||
|
||||
case MENU_ACTION_SAVE_SLOT3:
|
||||
NativeLibrary.SaveState(2);
|
||||
NativeLibrary.SaveState(2, false);
|
||||
return;
|
||||
|
||||
case MENU_ACTION_SAVE_SLOT4:
|
||||
NativeLibrary.SaveState(3);
|
||||
NativeLibrary.SaveState(3, false);
|
||||
return;
|
||||
|
||||
case MENU_ACTION_SAVE_SLOT5:
|
||||
NativeLibrary.SaveState(4);
|
||||
NativeLibrary.SaveState(4, false);
|
||||
return;
|
||||
|
||||
case MENU_ACTION_SAVE_SLOT6:
|
||||
NativeLibrary.SaveState(5);
|
||||
NativeLibrary.SaveState(5, false);
|
||||
return;
|
||||
|
||||
// Load state slots
|
||||
|
@ -572,60 +583,31 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
enabledButtons[i] = mPreferences.getBoolean("buttonToggleGc" + i, true);
|
||||
}
|
||||
builder.setMultiChoiceItems(R.array.gcpadButtons, enabledButtons,
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int indexSelected, boolean isChecked) {
|
||||
editor.putBoolean("buttonToggleGc" + indexSelected, isChecked);
|
||||
}
|
||||
});
|
||||
(dialog, indexSelected, isChecked) -> editor.putBoolean("buttonToggleGc" + indexSelected, isChecked));
|
||||
} else if (mPreferences.getInt("wiiController", 3) == 4) {
|
||||
for (int i = 0; i < enabledButtons.length; i++) {
|
||||
enabledButtons[i] = mPreferences.getBoolean("buttonToggleClassic" + i, true);
|
||||
}
|
||||
builder.setMultiChoiceItems(R.array.classicButtons, enabledButtons,
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int indexSelected, boolean isChecked) {
|
||||
editor.putBoolean("buttonToggleClassic" + indexSelected, isChecked);
|
||||
}
|
||||
});
|
||||
(dialog, indexSelected, isChecked) -> editor.putBoolean("buttonToggleClassic" + indexSelected, isChecked));
|
||||
} else {
|
||||
for (int i = 0; i < enabledButtons.length; i++) {
|
||||
enabledButtons[i] = mPreferences.getBoolean("buttonToggleWii" + i, true);
|
||||
}
|
||||
if (mPreferences.getInt("wiiController", 3) == 3) {
|
||||
builder.setMultiChoiceItems(R.array.nunchukButtons, enabledButtons,
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int indexSelected, boolean isChecked) {
|
||||
editor.putBoolean("buttonToggleWii" + indexSelected, isChecked);
|
||||
}
|
||||
});
|
||||
(dialog, indexSelected, isChecked) -> editor.putBoolean("buttonToggleWii" + indexSelected, isChecked));
|
||||
} else {
|
||||
builder.setMultiChoiceItems(R.array.wiimoteButtons, enabledButtons,
|
||||
new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int indexSelected, boolean isChecked) {
|
||||
editor.putBoolean("buttonToggleWii" + indexSelected, isChecked);
|
||||
}
|
||||
});
|
||||
(dialog, indexSelected, isChecked) -> editor.putBoolean("buttonToggleWii" + indexSelected, isChecked));
|
||||
}
|
||||
}
|
||||
builder.setNeutralButton(getString(R.string.emulation_toggle_all), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i)
|
||||
{
|
||||
mEmulationFragment.toggleInputOverlayVisibility();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i)
|
||||
{
|
||||
editor.apply();
|
||||
builder.setNeutralButton(getString(R.string.emulation_toggle_all), (dialogInterface, i) -> mEmulationFragment.toggleInputOverlayVisibility());
|
||||
builder.setPositiveButton(getString(R.string.ok), (dialogInterface, i) ->
|
||||
{
|
||||
editor.apply();
|
||||
|
||||
mEmulationFragment.refreshInputOverlay();
|
||||
}
|
||||
mEmulationFragment.refreshInputOverlay();
|
||||
});
|
||||
|
||||
AlertDialog alertDialog = builder.create();
|
||||
|
@ -662,15 +644,13 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.emulation_control_scale);
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putInt("controlScale", seekbar.getProgress());
|
||||
editor.apply();
|
||||
builder.setPositiveButton(getString(R.string.ok), (dialogInterface, i) ->
|
||||
{
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putInt("controlScale", seekbar.getProgress());
|
||||
editor.apply();
|
||||
|
||||
mEmulationFragment.refreshInputOverlay();
|
||||
}
|
||||
mEmulationFragment.refreshInputOverlay();
|
||||
});
|
||||
|
||||
AlertDialog alertDialog = builder.create();
|
||||
|
@ -682,24 +662,20 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.emulation_choose_controller);
|
||||
builder.setSingleChoiceItems(R.array.controllersEntries, mPreferences.getInt("wiiController", 3),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int indexSelected) {
|
||||
editor.putInt("wiiController", indexSelected);
|
||||
(dialog, indexSelected) ->
|
||||
{
|
||||
editor.putInt("wiiController", indexSelected);
|
||||
|
||||
NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Extension",
|
||||
getResources().getStringArray(R.array.controllersValues)[indexSelected]);
|
||||
}
|
||||
NativeLibrary.SetConfig("WiimoteNew.ini", "Wiimote1", "Extension",
|
||||
getResources().getStringArray(R.array.controllersValues)[indexSelected]);
|
||||
});
|
||||
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
editor.apply();
|
||||
builder.setPositiveButton(getString(R.string.ok), (dialogInterface, i) ->
|
||||
{
|
||||
editor.apply();
|
||||
|
||||
mEmulationFragment.refreshInputOverlay();
|
||||
mEmulationFragment.refreshInputOverlay();
|
||||
|
||||
Toast.makeText(getApplication(), R.string.emulation_controller_changed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Toast.makeText(getApplication(), R.string.emulation_controller_changed, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
AlertDialog alertDialog = builder.create();
|
||||
|
@ -729,7 +705,19 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
|
||||
for (InputDevice.MotionRange range : motions)
|
||||
{
|
||||
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), range.getAxis(), event.getAxisValue(range.getAxis()));
|
||||
int axis = range.getAxis();
|
||||
float origValue = event.getAxisValue(axis);
|
||||
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
|
||||
// If the input is still in the "flat" area, that means it's really zero.
|
||||
// This is used to compensate for imprecision in joysticks.
|
||||
if (Math.abs(value) > range.getFlat())
|
||||
{
|
||||
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
NativeLibrary.onGamePadMoveEvent(input.getDescriptor(), axis, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -762,4 +750,9 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
{
|
||||
return sIsGameCubeGame;
|
||||
}
|
||||
|
||||
public boolean isActivityRecreated()
|
||||
{
|
||||
return activityRecreated;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
package org.dolphinemu.dolphinemu.adapters;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.model.FileListItem;
|
||||
import org.dolphinemu.dolphinemu.viewholders.FileViewHolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
public final class FileAdapter extends RecyclerView.Adapter<FileViewHolder> implements View.OnClickListener
|
||||
{
|
||||
private ArrayList<FileListItem> mFileList;
|
||||
|
||||
private String mPath;
|
||||
|
||||
private FileClickListener mListener;
|
||||
|
||||
/**
|
||||
* Initializes the dataset to be displayed, and associates the Adapter with the
|
||||
* Activity as an event listener.
|
||||
*
|
||||
* @param path A String containing the path to the directory to be shown by this Adapter.
|
||||
* @param listener An Activity that can respond to callbacks from this Adapter.
|
||||
*/
|
||||
public FileAdapter(String path, FileClickListener listener)
|
||||
{
|
||||
mFileList = generateFileList(new File(path));
|
||||
mListener = listener;
|
||||
mListener.updateSubtitle(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the LayoutManager when it is necessary to create a new view.
|
||||
*
|
||||
* @param parent The RecyclerView (I think?) the created view will be thrown into.
|
||||
* @param viewType Not used here, but useful when more than one type of child will be used in the RecyclerView.
|
||||
* @return The created ViewHolder with references to all the child view's members.
|
||||
*/
|
||||
@Override
|
||||
public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
|
||||
{
|
||||
// Create a new view.
|
||||
View listItem = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.list_item_file, parent, false);
|
||||
|
||||
listItem.setOnClickListener(this);
|
||||
|
||||
// Use that view to create a ViewHolder.
|
||||
return new FileViewHolder(listItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the LayoutManager when a new view is not necessary because we can recycle
|
||||
* an existing one (for example, if a view just scrolled onto the screen from the bottom, we
|
||||
* can use the view that just scrolled off the top instead of inflating a new one.)
|
||||
*
|
||||
* @param holder A ViewHolder representing the view we're recycling.
|
||||
* @param position The position of the 'new' view in the dataset.
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(FileViewHolder holder, int position)
|
||||
{
|
||||
// Get a reference to the item from the dataset; we'll use this to fill in the view contents.
|
||||
final FileListItem file = mFileList.get(position);
|
||||
|
||||
// Fill in the view contents.
|
||||
switch (file.getType())
|
||||
{
|
||||
case FileListItem.TYPE_FOLDER:
|
||||
holder.imageType.setImageResource(R.drawable.ic_folder);
|
||||
break;
|
||||
|
||||
case FileListItem.TYPE_GC:
|
||||
holder.imageType.setImageResource(R.drawable.ic_gamecube);
|
||||
break;
|
||||
|
||||
case FileListItem.TYPE_WII:
|
||||
holder.imageType.setImageResource(R.drawable.ic_wii);
|
||||
break;
|
||||
|
||||
case FileListItem.TYPE_OTHER:
|
||||
holder.imageType.setImageResource(android.R.color.transparent);
|
||||
break;
|
||||
}
|
||||
|
||||
holder.textFileName.setText(file.getFilename());
|
||||
holder.itemView.setTag(file.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the LayoutManager to find out how much data we have.
|
||||
*
|
||||
* @return Size of the dataset.
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount()
|
||||
{
|
||||
return mFileList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* When a file is clicked, determine if it is a directory; if it is, show that new directory's
|
||||
* contents. If it is not, end the activity successfully.
|
||||
*
|
||||
* @param view The View representing the file the user clicked on.
|
||||
*/
|
||||
@Override
|
||||
public void onClick(final View view)
|
||||
{
|
||||
final String path = (String) view.getTag();
|
||||
|
||||
File clickedFile = new File(path);
|
||||
|
||||
if (clickedFile.isDirectory())
|
||||
{
|
||||
final ArrayList<FileListItem> fileList = generateFileList(clickedFile);
|
||||
|
||||
if (fileList.isEmpty())
|
||||
{
|
||||
Toast.makeText(view.getContext(), R.string.add_directory_empty_folder, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delay the loading of the new directory to give a little bit of time for UI feedback
|
||||
// to happen. Hacky, but good enough for now; this is necessary because we're modifying
|
||||
// the RecyclerView's contents, rather than constructing a new one.
|
||||
view.getHandler().postDelayed(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mFileList = fileList;
|
||||
notifyDataSetChanged();
|
||||
mListener.updateSubtitle(path);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass the activity the path of the parent directory of the clicked file.
|
||||
mListener.addDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given directory, return a list of Files it contains.
|
||||
*
|
||||
* @param directory A File representing the directory that should have its contents displayed.
|
||||
* @return The list of files contained in the directory.
|
||||
*/
|
||||
private ArrayList<FileListItem> generateFileList(File directory)
|
||||
{
|
||||
File[] children = directory.listFiles();
|
||||
mPath = directory.getAbsolutePath();
|
||||
ArrayList<FileListItem> fileList = new ArrayList<FileListItem>(0);
|
||||
|
||||
if (children != null)
|
||||
{
|
||||
|
||||
fileList = new ArrayList<FileListItem>(children.length);
|
||||
|
||||
for (File child : children)
|
||||
{
|
||||
if (!child.isHidden())
|
||||
{
|
||||
FileListItem item = new FileListItem(child);
|
||||
fileList.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(fileList);
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
|
||||
public String getPath()
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
public void setPath(String path)
|
||||
{
|
||||
File directory = new File(path);
|
||||
|
||||
mFileList = generateFileList(directory);
|
||||
notifyDataSetChanged();
|
||||
mListener.updateSubtitle(path);
|
||||
}
|
||||
|
||||
public void upOneLevel()
|
||||
{
|
||||
if (!mPath.equals("/"))
|
||||
{
|
||||
File currentDirectory = new File(mPath);
|
||||
File parentDirectory = currentDirectory.getParentFile();
|
||||
|
||||
mFileList = generateFileList(parentDirectory);
|
||||
notifyDataSetChanged();
|
||||
mListener.updateSubtitle(mPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to the containing Activity.
|
||||
*/
|
||||
public interface FileClickListener
|
||||
{
|
||||
void addDirectory();
|
||||
|
||||
void updateSubtitle(String path);
|
||||
}
|
||||
}
|
|
@ -18,9 +18,9 @@ import org.dolphinemu.dolphinemu.utils.PicassoUtils;
|
|||
import org.dolphinemu.dolphinemu.viewholders.GameViewHolder;
|
||||
|
||||
/**
|
||||
* This adapter, unlike {@link FileAdapter} which is backed by an ArrayList, gets its
|
||||
* information from a database Cursor. This fact, paired with the usage of ContentProviders
|
||||
* and Loaders, allows for efficient display of a limited view into a (possibly) large dataset.
|
||||
* This adapter gets its information from a database Cursor. This fact, paired with the usage of
|
||||
* ContentProviders and Loaders, allows for efficient display of a limited view into a (possibly)
|
||||
* large dataset.
|
||||
*/
|
||||
public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> implements
|
||||
View.OnClickListener,
|
||||
|
|
|
@ -68,19 +68,15 @@ public final class GameDetailsDialog extends DialogFragment
|
|||
textCountry.setText(country);
|
||||
textDate.setText(getArguments().getString(ARG_GAME_DATE));
|
||||
|
||||
buttonLaunch.setOnClickListener(new View.OnClickListener()
|
||||
buttonLaunch.setOnClickListener(view ->
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
// Start the emulation activity and send the path of the clicked ROM to it.
|
||||
EmulationActivity.launch(getActivity(),
|
||||
getArguments().getString(ARG_GAME_PATH),
|
||||
getArguments().getString(ARG_GAME_TITLE),
|
||||
getArguments().getString(ARG_GAME_SCREENSHOT_PATH),
|
||||
-1,
|
||||
imageGameScreen);
|
||||
}
|
||||
// Start the emulation activity and send the path of the clicked ROM to it.
|
||||
EmulationActivity.launch(getActivity(),
|
||||
getArguments().getString(ARG_GAME_PATH),
|
||||
getArguments().getString(ARG_GAME_TITLE),
|
||||
getArguments().getString(ARG_GAME_SCREENSHOT_PATH),
|
||||
-1,
|
||||
imageGameScreen);
|
||||
});
|
||||
|
||||
// Fill in the view contents.
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.view.KeyEvent;
|
|||
import android.view.MotionEvent;
|
||||
|
||||
import org.dolphinemu.dolphinemu.model.settings.view.InputBindingSetting;
|
||||
import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -21,6 +22,7 @@ public final class MotionAlertDialog extends AlertDialog
|
|||
{
|
||||
// The selected input preference
|
||||
private final InputBindingSetting setting;
|
||||
private final ControllerMappingHelper mControllerMappingHelper;
|
||||
private boolean mWaitingForEvent = true;
|
||||
|
||||
/**
|
||||
|
@ -34,6 +36,7 @@ public final class MotionAlertDialog extends AlertDialog
|
|||
super(context);
|
||||
|
||||
this.setting = setting;
|
||||
this.mControllerMappingHelper = new ControllerMappingHelper();
|
||||
}
|
||||
|
||||
public boolean onKeyEvent(int keyCode, KeyEvent event)
|
||||
|
@ -42,8 +45,11 @@ public final class MotionAlertDialog extends AlertDialog
|
|||
switch (event.getAction())
|
||||
{
|
||||
case KeyEvent.ACTION_DOWN:
|
||||
saveKeyInput(event);
|
||||
|
||||
if (!mControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode))
|
||||
{
|
||||
saveKeyInput(event);
|
||||
}
|
||||
// Even if we ignore the key, we still consume it. Thus return true regardless.
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@ -69,13 +75,15 @@ public final class MotionAlertDialog extends AlertDialog
|
|||
{
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
|
||||
return false;
|
||||
|
||||
Log.debug("[MotionAlertDialog] Received motion event: " + event.getAction());
|
||||
if (event.getAction() != MotionEvent.ACTION_MOVE)
|
||||
return false;
|
||||
|
||||
InputDevice input = event.getDevice();
|
||||
|
||||
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
|
||||
|
||||
int numMovedAxis = 0;
|
||||
float axisMoveValue = 0.0f;
|
||||
InputDevice.MotionRange lastMovedRange = null;
|
||||
char lastMovedDir = '?';
|
||||
if (mWaitingForEvent)
|
||||
|
@ -84,12 +92,23 @@ public final class MotionAlertDialog extends AlertDialog
|
|||
for (InputDevice.MotionRange range : motionRanges)
|
||||
{
|
||||
int axis = range.getAxis();
|
||||
float value = event.getAxisValue(axis);
|
||||
float origValue = event.getAxisValue(axis);
|
||||
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
|
||||
if (Math.abs(value) > 0.5f)
|
||||
{
|
||||
numMovedAxis++;
|
||||
lastMovedRange = range;
|
||||
lastMovedDir = value < 0.0f ? '-' : '+';
|
||||
// It is common to have multiple axis with the same physical input. For example,
|
||||
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
|
||||
// To handle this, we ignore an axis motion that's the exact same as a motion
|
||||
// we already saw. This way, we ignore axis with two names, but catch the case
|
||||
// where a joystick is moved in two directions.
|
||||
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
|
||||
if (value != axisMoveValue)
|
||||
{
|
||||
axisMoveValue = value;
|
||||
numMovedAxis++;
|
||||
lastMovedRange = range;
|
||||
lastMovedDir = value < 0.0f ? '-' : '+';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package org.dolphinemu.dolphinemu.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
import com.nononsenseapps.filepicker.FilePickerFragment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CustomFilePickerFragment extends FilePickerFragment
|
||||
{
|
||||
@NonNull
|
||||
@Override
|
||||
public Uri toUri(@NonNull final File file)
|
||||
{
|
||||
return FileProvider
|
||||
.getUriForFile(getContext(),
|
||||
getContext().getApplicationContext().getPackageName() + ".filesprovider",
|
||||
file);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package org.dolphinemu.dolphinemu.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
|
@ -12,13 +14,21 @@ import android.view.SurfaceView;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||
import org.dolphinemu.dolphinemu.overlay.InputOverlay;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import rx.functions.Action1;
|
||||
|
||||
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
|
||||
{
|
||||
private static final String KEY_GAMEPATH = "gamepath";
|
||||
|
@ -29,6 +39,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
|
||||
private EmulationState mEmulationState;
|
||||
|
||||
private DirectoryStateReceiver directoryStateReceiver;
|
||||
|
||||
private EmulationActivity activity;
|
||||
|
||||
public static EmulationFragment newInstance(String gamePath)
|
||||
{
|
||||
|
||||
|
@ -47,6 +61,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
|
||||
if (context instanceof EmulationActivity)
|
||||
{
|
||||
activity = (EmulationActivity)context;
|
||||
NativeLibrary.setEmulationActivity((EmulationActivity) context);
|
||||
}
|
||||
else
|
||||
|
@ -69,7 +84,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
|
||||
String gamePath = getArguments().getString(KEY_GAMEPATH);
|
||||
mEmulationState = new EmulationState(gamePath);
|
||||
mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,14 +111,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
Button doneButton = contents.findViewById(R.id.done_control_config);
|
||||
if (doneButton != null)
|
||||
{
|
||||
doneButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
stopConfiguringControls();
|
||||
}
|
||||
});
|
||||
doneButton.setOnClickListener(v -> stopConfiguringControls());
|
||||
}
|
||||
|
||||
// The new Surface created here will get passed to the native code via onSurfaceChanged.
|
||||
|
@ -115,12 +123,25 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
mEmulationState.run();
|
||||
if (DirectoryInitializationService.areDolphinDirectoriesReady())
|
||||
{
|
||||
mEmulationState.run(activity.isActivityRecreated());
|
||||
}
|
||||
else
|
||||
{
|
||||
setupDolphinDirectoriesThenStartEmulation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
if (directoryStateReceiver != null)
|
||||
{
|
||||
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(directoryStateReceiver);
|
||||
directoryStateReceiver = null;
|
||||
}
|
||||
|
||||
mEmulationState.pause();
|
||||
super.onPause();
|
||||
}
|
||||
|
@ -132,6 +153,27 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
super.onDetach();
|
||||
}
|
||||
|
||||
private void setupDolphinDirectoriesThenStartEmulation() {
|
||||
IntentFilter statusIntentFilter = new IntentFilter(
|
||||
DirectoryInitializationService.BROADCAST_ACTION);
|
||||
|
||||
directoryStateReceiver =
|
||||
new DirectoryStateReceiver(directoryInitializationState -> {
|
||||
if (directoryInitializationState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
|
||||
mEmulationState.run(activity.isActivityRecreated());
|
||||
} else if (directoryInitializationState == DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) {
|
||||
Toast.makeText(getContext(), R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Registers the DirectoryStateReceiver and its intent filters
|
||||
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
|
||||
directoryStateReceiver,
|
||||
statusIntentFilter);
|
||||
DirectoryInitializationService.startService(getActivity());
|
||||
}
|
||||
|
||||
public void toggleInputOverlayVisibility()
|
||||
{
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
|
@ -212,10 +254,13 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
private State state;
|
||||
private Surface mSurface;
|
||||
private boolean mRunWhenSurfaceIsValid;
|
||||
private boolean loadPreviousTemporaryState;
|
||||
private final String temporaryStatePath;
|
||||
|
||||
EmulationState(String gamePath)
|
||||
EmulationState(String gamePath, String temporaryStatePath)
|
||||
{
|
||||
mGamePath = gamePath;
|
||||
this.temporaryStatePath = temporaryStatePath;
|
||||
// Starting state is stopped.
|
||||
state = State.STOPPED;
|
||||
}
|
||||
|
@ -243,6 +288,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
{
|
||||
if (state != State.STOPPED)
|
||||
{
|
||||
Log.debug("[EmulationFragment] Stopping emulation.");
|
||||
state = State.STOPPED;
|
||||
NativeLibrary.StopEmulation();
|
||||
}
|
||||
|
@ -270,8 +316,29 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized void run()
|
||||
public synchronized void run(boolean isActivityRecreated)
|
||||
{
|
||||
if (isActivityRecreated)
|
||||
{
|
||||
if (NativeLibrary.IsRunning())
|
||||
{
|
||||
loadPreviousTemporaryState = false;
|
||||
state = State.PAUSED;
|
||||
deleteFile(temporaryStatePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadPreviousTemporaryState = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.debug("[EmulationFragment] activity resumed or fresh start");
|
||||
loadPreviousTemporaryState = false;
|
||||
// activity resumed without being killed or this is the first run
|
||||
deleteFile(temporaryStatePath);
|
||||
}
|
||||
|
||||
// If the surface is set, run now. Otherwise, wait for it to get set.
|
||||
if (mSurface != null)
|
||||
{
|
||||
|
@ -325,17 +392,20 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
mRunWhenSurfaceIsValid = false;
|
||||
if (state == State.STOPPED)
|
||||
{
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.");
|
||||
|
||||
mEmulationThread = new Thread(new Runnable()
|
||||
mEmulationThread = new Thread(() ->
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
NativeLibrary.SurfaceChanged(mSurface);
|
||||
if (loadPreviousTemporaryState)
|
||||
{
|
||||
NativeLibrary.SurfaceChanged(mSurface);
|
||||
Log.debug("[EmulationFragment] Starting emulation thread from previous state.");
|
||||
NativeLibrary.Run(mGamePath, temporaryStatePath, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.");
|
||||
NativeLibrary.Run(mGamePath);
|
||||
}},
|
||||
"NativeEmulation");
|
||||
}
|
||||
}, "NativeEmulation");
|
||||
mEmulationThread.start();
|
||||
|
||||
}
|
||||
|
@ -352,4 +422,27 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
|||
state = State.RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveTemporaryState()
|
||||
{
|
||||
NativeLibrary.SaveStateAs(getTemporaryStateFilePath(), true);
|
||||
}
|
||||
|
||||
private String getTemporaryStateFilePath()
|
||||
{
|
||||
return getContext().getFilesDir() + File.separator + "temp.sav";
|
||||
}
|
||||
|
||||
private static void deleteFile(String path)
|
||||
{
|
||||
try
|
||||
{
|
||||
File file = new File(path);
|
||||
file.delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// fail safely
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
package org.dolphinemu.dolphinemu.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.GridLayout;
|
||||
|
||||
import org.dolphinemu.dolphinemu.BuildConfig;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||
|
||||
public final class LoadStateFragment extends Fragment implements View.OnClickListener
|
||||
{
|
||||
public static final String FRAGMENT_TAG = BuildConfig.APPLICATION_ID + ".load_state";
|
||||
public static final int FRAGMENT_ID = R.layout.fragment_state_load;
|
||||
|
||||
private static SparseIntArray buttonsActionsMap = new SparseIntArray();
|
||||
static {
|
||||
buttonsActionsMap.append(R.id.menu_emulation_load_1, EmulationActivity.MENU_ACTION_LOAD_SLOT1);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_load_2, EmulationActivity.MENU_ACTION_LOAD_SLOT2);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_load_3, EmulationActivity.MENU_ACTION_LOAD_SLOT3);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_load_4, EmulationActivity.MENU_ACTION_LOAD_SLOT4);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_load_5, EmulationActivity.MENU_ACTION_LOAD_SLOT5);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_load_6, EmulationActivity.MENU_ACTION_LOAD_SLOT6);
|
||||
}
|
||||
|
||||
public static LoadStateFragment newInstance()
|
||||
{
|
||||
LoadStateFragment fragment = new LoadStateFragment();
|
||||
|
||||
// TODO Add any appropriate arguments to this fragment.
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
View rootView = inflater.inflate(FRAGMENT_ID, container, false);
|
||||
|
||||
GridLayout grid = (GridLayout) rootView.findViewById(R.id.grid_state_slots);
|
||||
for (int childIndex = 0; childIndex < grid.getChildCount(); childIndex++)
|
||||
{
|
||||
Button button = (Button) grid.getChildAt(childIndex);
|
||||
|
||||
button.setOnClickListener(this);
|
||||
}
|
||||
|
||||
// So that item clicked to start this Fragment is no longer the focused item.
|
||||
grid.requestFocus();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
@Override
|
||||
public void onClick(View button)
|
||||
{
|
||||
int action = buttonsActionsMap.get(button.getId(), -1);
|
||||
if (action >= 0)
|
||||
{
|
||||
((EmulationActivity) getActivity()).handleMenuAction(action);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package org.dolphinemu.dolphinemu.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.GridLayout;
|
||||
|
||||
import org.dolphinemu.dolphinemu.BuildConfig;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||
|
||||
public final class SaveStateFragment extends Fragment implements View.OnClickListener
|
||||
{
|
||||
public static final String FRAGMENT_TAG = BuildConfig.APPLICATION_ID + ".save_state";
|
||||
public static final int FRAGMENT_ID = R.layout.fragment_state_save;
|
||||
|
||||
private static SparseIntArray buttonsActionsMap = new SparseIntArray();
|
||||
static {
|
||||
buttonsActionsMap.append(R.id.menu_emulation_save_1, EmulationActivity.MENU_ACTION_SAVE_SLOT1);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_save_2, EmulationActivity.MENU_ACTION_SAVE_SLOT2);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_save_3, EmulationActivity.MENU_ACTION_SAVE_SLOT3);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_save_4, EmulationActivity.MENU_ACTION_SAVE_SLOT4);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_save_5, EmulationActivity.MENU_ACTION_SAVE_SLOT5);
|
||||
buttonsActionsMap.append(R.id.menu_emulation_save_6, EmulationActivity.MENU_ACTION_SAVE_SLOT6);
|
||||
}
|
||||
|
||||
public static SaveStateFragment newInstance()
|
||||
{
|
||||
SaveStateFragment fragment = new SaveStateFragment();
|
||||
|
||||
// TODO Add any appropriate arguments to this fragment.
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
||||
{
|
||||
View rootView = inflater.inflate(FRAGMENT_ID, container, false);
|
||||
|
||||
GridLayout grid = (GridLayout) rootView.findViewById(R.id.grid_state_slots);
|
||||
for (int childIndex = 0; childIndex < grid.getChildCount(); childIndex++)
|
||||
{
|
||||
Button button = (Button) grid.getChildAt(childIndex);
|
||||
|
||||
button.setOnClickListener(this);
|
||||
}
|
||||
|
||||
// So that item clicked to start this Fragment is no longer the focused item.
|
||||
grid.requestFocus();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
@Override
|
||||
public void onClick(View button)
|
||||
{
|
||||
int action = buttonsActionsMap.get(button.getId(), -1);
|
||||
if (action >= 0)
|
||||
{
|
||||
((EmulationActivity) getActivity()).handleMenuAction(action);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,6 +78,7 @@ public final class GameDatabase extends SQLiteOpenHelper
|
|||
+ KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
|
||||
+ KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")";
|
||||
|
||||
private static final String SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS " + TABLE_NAME_FOLDERS;
|
||||
private static final String SQL_DELETE_GAMES = "DROP TABLE IF EXISTS " + TABLE_NAME_GAMES;
|
||||
|
||||
public GameDatabase(Context context)
|
||||
|
@ -91,11 +92,19 @@ public final class GameDatabase extends SQLiteOpenHelper
|
|||
{
|
||||
Log.debug("[GameDatabase] GameDatabase - Creating database...");
|
||||
|
||||
Log.verbose("[GameDatabase] Executing SQL: " + SQL_CREATE_GAMES);
|
||||
database.execSQL(SQL_CREATE_GAMES);
|
||||
execSqlAndLog(database, SQL_CREATE_GAMES);
|
||||
execSqlAndLog(database, SQL_CREATE_FOLDERS);
|
||||
}
|
||||
|
||||
Log.verbose("[GameDatabase] Executing SQL: " + SQL_CREATE_FOLDERS);
|
||||
database.execSQL(SQL_CREATE_FOLDERS);
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion)
|
||||
{
|
||||
Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases..");
|
||||
execSqlAndLog(database, SQL_DELETE_FOLDERS);
|
||||
execSqlAndLog(database, SQL_CREATE_FOLDERS);
|
||||
|
||||
execSqlAndLog(database, SQL_DELETE_GAMES);
|
||||
execSqlAndLog(database, SQL_CREATE_GAMES);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -103,11 +112,9 @@ public final class GameDatabase extends SQLiteOpenHelper
|
|||
{
|
||||
Log.info("[GameDatabase] Upgrading database from schema version " + oldVersion + " to " + newVersion);
|
||||
|
||||
Log.verbose("[GameDatabase] Executing SQL: " + SQL_DELETE_GAMES);
|
||||
database.execSQL(SQL_DELETE_GAMES);
|
||||
|
||||
Log.verbose("[GameDatabase] Executing SQL: " + SQL_CREATE_GAMES);
|
||||
database.execSQL(SQL_CREATE_GAMES);
|
||||
// Delete all the games
|
||||
execSqlAndLog(database, SQL_DELETE_GAMES);
|
||||
execSqlAndLog(database, SQL_CREATE_GAMES);
|
||||
|
||||
Log.verbose("[GameDatabase] Re-scanning library with new schema.");
|
||||
scanLibrary(database);
|
||||
|
@ -255,32 +262,34 @@ public final class GameDatabase extends SQLiteOpenHelper
|
|||
|
||||
public Observable<Cursor> getGamesForPlatform(final Platform platform)
|
||||
{
|
||||
return Observable.create(new Observable.OnSubscribe<Cursor>()
|
||||
return Observable.create(subscriber ->
|
||||
{
|
||||
@Override
|
||||
public void call(Subscriber<? super Cursor> subscriber)
|
||||
{
|
||||
Log.info("[GameDatabase] Reading games list...");
|
||||
Log.info("[GameDatabase] Reading games list...");
|
||||
|
||||
String[] whereArgs = new String[]{Integer.toString(platform.toInt())};
|
||||
String[] whereArgs = new String[]{Integer.toString(platform.toInt())};
|
||||
|
||||
SQLiteDatabase database = getReadableDatabase();
|
||||
Cursor resultCursor = database.query(
|
||||
TABLE_NAME_GAMES,
|
||||
null,
|
||||
KEY_GAME_PLATFORM + " = ?",
|
||||
whereArgs,
|
||||
null,
|
||||
null,
|
||||
KEY_GAME_TITLE + " ASC"
|
||||
);
|
||||
SQLiteDatabase database = getReadableDatabase();
|
||||
Cursor resultCursor = database.query(
|
||||
TABLE_NAME_GAMES,
|
||||
null,
|
||||
KEY_GAME_PLATFORM + " = ?",
|
||||
whereArgs,
|
||||
null,
|
||||
null,
|
||||
KEY_GAME_TITLE + " ASC"
|
||||
);
|
||||
|
||||
// Pass the result cursor to the consumer.
|
||||
subscriber.onNext(resultCursor);
|
||||
// Pass the result cursor to the consumer.
|
||||
subscriber.onNext(resultCursor);
|
||||
|
||||
// Tell the consumer we're done; it will unsubscribe implicitly.
|
||||
subscriber.onCompleted();
|
||||
}
|
||||
// Tell the consumer we're done; it will unsubscribe implicitly.
|
||||
subscriber.onCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
private void execSqlAndLog(SQLiteDatabase database, String sql)
|
||||
{
|
||||
Log.verbose("[GameDatabase] Executing SQL: " + sql);
|
||||
database.execSQL(sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
/**
|
||||
* Copyright 2014 Dolphin Emulator Project
|
||||
* Licensed under GPLv2+
|
||||
* Refer to the license.txt file included.
|
||||
*/
|
||||
|
||||
package org.dolphinemu.dolphinemu.services;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A service that spawns its own thread in order to copy several binary and shader files
|
||||
* from the Dolphin APK to the external file system.
|
||||
*/
|
||||
public final class AssetCopyService extends IntentService
|
||||
{
|
||||
public AssetCopyService()
|
||||
{
|
||||
// Superclass constructor is called to name the thread on which this service executes.
|
||||
super("AssetCopyService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
String BaseDir = NativeLibrary.GetUserDirectory();
|
||||
String ConfigDir = BaseDir + File.separator + "Config";
|
||||
|
||||
// Copy assets if needed
|
||||
NativeLibrary.CreateUserFolders();
|
||||
copyAssetFolder("GC", BaseDir + File.separator + "GC", false);
|
||||
copyAssetFolder("Shaders", BaseDir + File.separator + "Shaders", false);
|
||||
copyAssetFolder("Wii", BaseDir + File.separator + "Wii", false);
|
||||
|
||||
// Always copy over the GCPad config in case of change or corruption.
|
||||
// Not a user configurable file.
|
||||
copyAsset("GCPadNew.ini", ConfigDir + File.separator + "GCPadNew.ini", true);
|
||||
copyAsset("WiimoteNew.ini", ConfigDir + File.separator + "WiimoteNew.ini", true);
|
||||
|
||||
// Record the fact that we've done this before, so we don't do it on every launch.
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
|
||||
editor.putBoolean("assetsCopied", true);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void copyAsset(String asset, String output, Boolean overwrite)
|
||||
{
|
||||
Log.verbose("[AssetCopyService] Copying File " + asset + " to " + output);
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
|
||||
try
|
||||
{
|
||||
File file = new File(output);
|
||||
if(!file.exists() || overwrite)
|
||||
{
|
||||
in = getAssets().open(asset);
|
||||
out = new FileOutputStream(output);
|
||||
copyFile(in, out);
|
||||
in.close();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.error("[AssetCopyService] Failed to copy asset file: " + asset + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAssetFolder(String assetFolder, String outputFolder, Boolean overwrite)
|
||||
{
|
||||
Log.verbose("[AssetCopyService] Copying Folder " + assetFolder + " to " + outputFolder);
|
||||
|
||||
try
|
||||
{
|
||||
for (String file : getAssets().list(assetFolder))
|
||||
{
|
||||
copyAssetFolder(assetFolder + File.separator + file, outputFolder + File.separator + file, overwrite);
|
||||
copyAsset(assetFolder + File.separator + file, outputFolder + File.separator + file, overwrite);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.error("[AssetCopyService] Failed to copy asset folder: " + assetFolder + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void copyFile(InputStream in, OutputStream out) throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
|
||||
while ((read = in.read(buffer)) != -1)
|
||||
{
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* Copyright 2014 Dolphin Emulator Project
|
||||
* Licensed under GPLv2+
|
||||
* Refer to the license.txt file included.
|
||||
*/
|
||||
|
||||
package org.dolphinemu.dolphinemu.services;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A service that spawns its own thread in order to copy several binary and shader files
|
||||
* from the Dolphin APK to the external file system.
|
||||
*/
|
||||
public final class DirectoryInitializationService extends IntentService
|
||||
{
|
||||
public static final String BROADCAST_ACTION = "org.dolphinemu.dolphinemu.BROADCAST";
|
||||
|
||||
public static final String EXTRA_STATE = "directoryState";
|
||||
private static DirectoryInitializationState directoryState = null;
|
||||
|
||||
public enum DirectoryInitializationState
|
||||
{
|
||||
DOLPHIN_DIRECTORIES_INITIALIZED,
|
||||
EXTERNAL_STORAGE_PERMISSION_NEEDED
|
||||
}
|
||||
|
||||
public DirectoryInitializationService()
|
||||
{
|
||||
// Superclass constructor is called to name the thread on which this service executes.
|
||||
super("DirectoryInitializationService");
|
||||
}
|
||||
|
||||
public static void startService(Context context)
|
||||
{
|
||||
Intent intent = new Intent(context, DirectoryInitializationService.class);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent)
|
||||
{
|
||||
if (directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
|
||||
{
|
||||
sendBroadcastState(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
|
||||
}
|
||||
else if (PermissionsHandler.hasWriteAccess(this))
|
||||
{
|
||||
initializeInternalStorage();
|
||||
initializeExternalStorage();
|
||||
directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
|
||||
sendBroadcastState(directoryState);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendBroadcastState(DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeInternalStorage()
|
||||
{
|
||||
File sysDirectory = new File(getFilesDir(), "Sys");
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String revision = NativeLibrary.GetGitRevision();
|
||||
if (!preferences.getString("sysDirectoryVersion", "").equals(revision))
|
||||
{
|
||||
// There is no extracted Sys directory, or there is a Sys directory from another
|
||||
// version of Dolphin that might contain outdated files. Let's (re-)extract Sys.
|
||||
deleteDirectoryRecursively(sysDirectory);
|
||||
copyAssetFolder("Sys", sysDirectory, true);
|
||||
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString("sysDirectoryVersion", revision);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
// Let the native code know where the Sys directory is.
|
||||
SetSysDirectory(sysDirectory.getPath());
|
||||
}
|
||||
|
||||
private void initializeExternalStorage()
|
||||
{
|
||||
// Create User directory structure and copy some NAND files from the extracted Sys directory.
|
||||
CreateUserDirectories();
|
||||
|
||||
// GCPadNew.ini and WiimoteNew.ini must contain specific values in order for controller
|
||||
// input to work as intended (they aren't user configurable), so we overwrite them just
|
||||
// in case the user has tried to modify them manually.
|
||||
//
|
||||
// ...Except WiimoteNew.ini contains the user configurable settings for Wii Remote
|
||||
// extensions in addition to all of its lines that aren't user configurable, so since we
|
||||
// don't want to lose the selected extensions, we don't overwrite that file if it exists.
|
||||
//
|
||||
// TODO: Redo the Android controller system so that we don't have to extract these INIs.
|
||||
String configDirectory = NativeLibrary.GetUserDirectory() + File.separator + "Config";
|
||||
copyAsset("GCPadNew.ini", new File(configDirectory, "GCPadNew.ini"), true);
|
||||
copyAsset("WiimoteNew.ini", new File(configDirectory,"WiimoteNew.ini"), false);
|
||||
}
|
||||
|
||||
private static void deleteDirectoryRecursively(File file)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
for (File child : file.listFiles())
|
||||
deleteDirectoryRecursively(child);
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
|
||||
public static boolean areDolphinDirectoriesReady()
|
||||
{
|
||||
return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
|
||||
}
|
||||
|
||||
private void sendBroadcastState(DirectoryInitializationState state)
|
||||
{
|
||||
Intent localIntent =
|
||||
new Intent(BROADCAST_ACTION)
|
||||
.putExtra(EXTRA_STATE, state);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
|
||||
}
|
||||
|
||||
private void copyAsset(String asset, File output, Boolean overwrite)
|
||||
{
|
||||
Log.verbose("[DirectoryInitializationService] Copying File " + asset + " to " + output);
|
||||
|
||||
try
|
||||
{
|
||||
if (!output.exists() || overwrite)
|
||||
{
|
||||
InputStream in = getAssets().open(asset);
|
||||
OutputStream out = new FileOutputStream(output);
|
||||
copyFile(in, out);
|
||||
in.close();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.error("[DirectoryInitializationService] Failed to copy asset file: " + asset + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite)
|
||||
{
|
||||
Log.verbose("[DirectoryInitializationService] Copying Folder " + assetFolder + " to " + outputFolder);
|
||||
|
||||
try
|
||||
{
|
||||
boolean createdFolder = false;
|
||||
for (String file : getAssets().list(assetFolder))
|
||||
{
|
||||
if (!createdFolder)
|
||||
{
|
||||
outputFolder.mkdir();
|
||||
createdFolder = true;
|
||||
}
|
||||
copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file), overwrite);
|
||||
copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.error("[DirectoryInitializationService] Failed to copy asset folder: " + assetFolder + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void copyFile(InputStream in, OutputStream out) throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
|
||||
while ((read = in.read(buffer)) != -1)
|
||||
{
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
private static native void CreateUserDirectories();
|
||||
private static native void SetSysDirectory(String path);
|
||||
}
|
|
@ -17,12 +17,14 @@ import android.view.View;
|
|||
import android.widget.Toast;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity;
|
||||
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter;
|
||||
import org.dolphinemu.dolphinemu.model.GameProvider;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||
import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
|
||||
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
|
||||
import org.dolphinemu.dolphinemu.utils.AddDirectoryHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||
import org.dolphinemu.dolphinemu.utils.StartupHandler;
|
||||
|
||||
|
@ -52,19 +54,11 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
mTabLayout.setupWithViewPager(mViewPager);
|
||||
|
||||
// Set up the FAB.
|
||||
mFab.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
mPresenter.onFabClick();
|
||||
}
|
||||
});
|
||||
mFab.setOnClickListener(view -> mPresenter.onFabClick());
|
||||
|
||||
mPresenter.onCreate();
|
||||
|
||||
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
|
||||
// TODO Split some of this stuff into Application.onCreate()
|
||||
if (savedInstanceState == null)
|
||||
StartupHandler.HandleInit(this);
|
||||
|
||||
|
@ -78,6 +72,13 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
|
||||
}
|
||||
|
||||
// TODO: Replace with a ButterKnife injection.
|
||||
private void findViews()
|
||||
{
|
||||
|
@ -134,7 +135,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
@Override
|
||||
public void launchFileListActivity()
|
||||
{
|
||||
AddDirectoryActivity.launch(this);
|
||||
FileBrowserHelper.openDirectoryPicker(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,8 +145,6 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
}
|
||||
|
||||
/**
|
||||
* Callback from AddDirectoryActivity. Applies any changes necessary to the GameGridActivity.
|
||||
*
|
||||
* @param requestCode An int describing whether the Activity that is returning did so successfully.
|
||||
* @param resultCode An int describing what Activity is giving us this callback.
|
||||
* @param result The information the returning Activity is providing us.
|
||||
|
@ -153,7 +152,20 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent result)
|
||||
{
|
||||
mPresenter.handleActivityResult(requestCode, resultCode);
|
||||
switch (requestCode)
|
||||
{
|
||||
case MainPresenter.REQUEST_ADD_DIRECTORY:
|
||||
// If the user picked a file, as opposed to just backing out.
|
||||
if (resultCode == MainActivity.RESULT_OK)
|
||||
{
|
||||
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result));
|
||||
}
|
||||
break;
|
||||
|
||||
case MainPresenter.REQUEST_EMULATE_GAME:
|
||||
mPresenter.refreshFragmentScreenshot(resultCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -161,7 +173,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
switch (requestCode) {
|
||||
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
StartupHandler.copyAssetsIfNeeded(this);
|
||||
DirectoryInitializationService.startService(this);
|
||||
|
||||
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
|
||||
getSupportFragmentManager(), this);
|
||||
|
@ -205,7 +217,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
|||
@Nullable
|
||||
private PlatformGamesView getPlatformGamesView(Platform platform)
|
||||
{
|
||||
String fragmentTag = "android:switcher:" + mViewPager.getId() + ":" + platform;
|
||||
String fragmentTag = "android:switcher:" + mViewPager.getId() + ":" + platform.toInt();
|
||||
|
||||
return (PlatformGamesView) getSupportFragmentManager().findFragmentByTag(fragmentTag);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
package org.dolphinemu.dolphinemu.ui.main;
|
||||
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.dolphinemu.dolphinemu.BuildConfig;
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.model.GameDatabase;
|
||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||
import org.dolphinemu.dolphinemu.utils.AddDirectoryHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.SettingsFile;
|
||||
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.functions.Action1;
|
||||
import rx.schedulers.Schedulers;
|
||||
|
||||
public final class MainPresenter
|
||||
|
@ -20,6 +18,7 @@ public final class MainPresenter
|
|||
public static final int REQUEST_EMULATE_GAME = 2;
|
||||
|
||||
private final MainView mView;
|
||||
private String mDirToAdd;
|
||||
|
||||
public MainPresenter(MainView view)
|
||||
{
|
||||
|
@ -71,24 +70,27 @@ public final class MainPresenter
|
|||
return false;
|
||||
}
|
||||
|
||||
public void handleActivityResult(int requestCode, int resultCode)
|
||||
public void addDirIfNeeded(AddDirectoryHelper helper)
|
||||
{
|
||||
switch (requestCode)
|
||||
if (mDirToAdd != null)
|
||||
{
|
||||
case REQUEST_ADD_DIRECTORY:
|
||||
// If the user picked a file, as opposed to just backing out.
|
||||
if (resultCode == MainActivity.RESULT_OK)
|
||||
{
|
||||
mView.refresh();
|
||||
}
|
||||
break;
|
||||
helper.addDirectory(mDirToAdd, mView::refresh);
|
||||
|
||||
case REQUEST_EMULATE_GAME:
|
||||
mView.refreshFragmentScreenshot(resultCode);
|
||||
break;
|
||||
mDirToAdd = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onDirectorySelected(String dir)
|
||||
{
|
||||
mDirToAdd = dir;
|
||||
}
|
||||
|
||||
public void refreshFragmentScreenshot(int resultCode)
|
||||
{
|
||||
mView.refreshFragmentScreenshot(resultCode);
|
||||
}
|
||||
|
||||
|
||||
public void loadGames(final Platform platform)
|
||||
{
|
||||
GameDatabase databaseHelper = DolphinApplication.databaseHelper;
|
||||
|
@ -96,14 +98,6 @@ public final class MainPresenter
|
|||
databaseHelper.getGamesForPlatform(platform)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Action1<Cursor>()
|
||||
{
|
||||
@Override
|
||||
public void call(Cursor games)
|
||||
{
|
||||
mView.showGames(platform, games);
|
||||
}
|
||||
}
|
||||
);
|
||||
.subscribe(games -> mView.showGames(platform, games));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,22 @@ import android.support.v17.leanback.widget.CursorObjectAdapter;
|
|||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity;
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||
import org.dolphinemu.dolphinemu.adapters.GameRowPresenter;
|
||||
import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter;
|
||||
import org.dolphinemu.dolphinemu.model.Game;
|
||||
import org.dolphinemu.dolphinemu.model.TvSettingsItem;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
|
||||
import org.dolphinemu.dolphinemu.utils.AddDirectoryHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||
import org.dolphinemu.dolphinemu.utils.StartupHandler;
|
||||
import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
|
||||
|
@ -57,6 +55,13 @@ public final class TvMainActivity extends FragmentActivity implements MainView
|
|||
StartupHandler.HandleInit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
|
||||
}
|
||||
|
||||
void setupUI() {
|
||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
mBrowseFragment = new BrowseSupportFragment();
|
||||
|
@ -71,31 +76,27 @@ public final class TvMainActivity extends FragmentActivity implements MainView
|
|||
buildRowsAdapter();
|
||||
|
||||
mBrowseFragment.setOnItemViewClickedListener(
|
||||
new OnItemViewClickedListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row)
|
||||
{
|
||||
// Special case: user clicked on a settings row item.
|
||||
if (item instanceof TvSettingsItem)
|
||||
{
|
||||
TvSettingsItem settingsItem = (TvSettingsItem) item;
|
||||
mPresenter.handleOptionSelection(settingsItem.getItemId());
|
||||
}
|
||||
else
|
||||
{
|
||||
TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder;
|
||||
(itemViewHolder, item, rowViewHolder, row) ->
|
||||
{
|
||||
// Special case: user clicked on a settings row item.
|
||||
if (item instanceof TvSettingsItem)
|
||||
{
|
||||
TvSettingsItem settingsItem = (TvSettingsItem) item;
|
||||
mPresenter.handleOptionSelection(settingsItem.getItemId());
|
||||
}
|
||||
else
|
||||
{
|
||||
TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder;
|
||||
|
||||
// Start the emulation activity and send the path of the clicked ISO to it.
|
||||
EmulationActivity.launch(TvMainActivity.this,
|
||||
holder.path,
|
||||
holder.title,
|
||||
holder.screenshotPath,
|
||||
-1,
|
||||
holder.imageScreenshot);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Start the emulation activity and send the path of the clicked ISO to it.
|
||||
EmulationActivity.launch(TvMainActivity.this,
|
||||
holder.path,
|
||||
holder.title,
|
||||
holder.screenshotPath,
|
||||
-1,
|
||||
holder.imageScreenshot);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* MainView
|
||||
|
@ -128,7 +129,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
|
|||
@Override
|
||||
public void launchFileListActivity()
|
||||
{
|
||||
AddDirectoryActivity.launch(this);
|
||||
FileBrowserHelper.openDirectoryPicker(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -153,7 +154,20 @@ public final class TvMainActivity extends FragmentActivity implements MainView
|
|||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent result)
|
||||
{
|
||||
mPresenter.handleActivityResult(requestCode, resultCode);
|
||||
switch (requestCode)
|
||||
{
|
||||
case MainPresenter.REQUEST_ADD_DIRECTORY:
|
||||
// If the user picked a file, as opposed to just backing out.
|
||||
if (resultCode == MainActivity.RESULT_OK)
|
||||
{
|
||||
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result));
|
||||
}
|
||||
break;
|
||||
|
||||
case MainPresenter.REQUEST_EMULATE_GAME:
|
||||
mPresenter.refreshFragmentScreenshot(resultCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -161,7 +175,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
|
|||
switch (requestCode) {
|
||||
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
StartupHandler.copyAssetsIfNeeded(this);
|
||||
DirectoryInitializationService.startService(this);
|
||||
loadGames();
|
||||
} else {
|
||||
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||
|
|
|
@ -47,15 +47,11 @@ public final class PlatformGamesPresenter
|
|||
databaseHelper.getGamesForPlatform(mPlatform)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Action1<Cursor>()
|
||||
.subscribe(games ->
|
||||
{
|
||||
@Override
|
||||
public void call(Cursor games)
|
||||
{
|
||||
Log.debug("[PlatformGamesPresenter] " + mPlatform + ": Load finished, swapping cursor...");
|
||||
Log.debug("[PlatformGamesPresenter] " + mPlatform + ": Load finished, swapping cursor...");
|
||||
|
||||
mView.showGames(games);
|
||||
}
|
||||
mView.showGames(games);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package org.dolphinemu.dolphinemu.ui.settings;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
@ -12,6 +15,8 @@ import android.widget.Toast;
|
|||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -22,6 +27,8 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
|||
private static final String FRAGMENT_TAG = "settings";
|
||||
private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this);
|
||||
|
||||
private ProgressDialog dialog;
|
||||
|
||||
public static void launch(Context context, String menuTag)
|
||||
{
|
||||
Intent settings = new Intent(context, SettingsActivity.class);
|
||||
|
@ -65,6 +72,13 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
|||
mPresenter.saveState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
mPresenter.onStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is called, the user has left the settings screen (potentially through the
|
||||
* home button) and will expect their changes to be persisted. So we kick off an
|
||||
|
@ -106,6 +120,47 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
|||
transaction.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter)
|
||||
{
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(
|
||||
receiver,
|
||||
filter);
|
||||
DirectoryInitializationService.startService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver)
|
||||
{
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoading()
|
||||
{
|
||||
if (dialog == null)
|
||||
{
|
||||
dialog = new ProgressDialog(this);
|
||||
dialog.setMessage(getString(R.string.load_settings));
|
||||
dialog.setIndeterminate(true);
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLoading()
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showPermissionNeededHint()
|
||||
{
|
||||
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, SettingSection> getSettings(int file)
|
||||
{
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
package org.dolphinemu.dolphinemu.ui.settings;
|
||||
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
import org.dolphinemu.dolphinemu.utils.SettingsFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import rx.functions.Action1;
|
||||
|
||||
public final class SettingsActivityPresenter
|
||||
{
|
||||
private static final String KEY_SHOULD_SAVE = "should_save";
|
||||
|
@ -22,6 +27,10 @@ public final class SettingsActivityPresenter
|
|||
|
||||
private boolean mShouldSave;
|
||||
|
||||
private DirectoryStateReceiver directoryStateReceiver;
|
||||
|
||||
private String menuTag;
|
||||
|
||||
public SettingsActivityPresenter(SettingsActivityView view)
|
||||
{
|
||||
mView = view;
|
||||
|
@ -31,12 +40,10 @@ public final class SettingsActivityPresenter
|
|||
{
|
||||
if (savedInstanceState == null)
|
||||
{
|
||||
mView.showSettingsFragment(menuTag, false);
|
||||
|
||||
mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile(SettingsFile.FILE_NAME_DOLPHIN, mView));
|
||||
mSettings.add(SettingsFile.SETTINGS_GFX, SettingsFile.readFile(SettingsFile.FILE_NAME_GFX, mView));
|
||||
mSettings.add(SettingsFile.SETTINGS_WIIMOTE, SettingsFile.readFile(SettingsFile.FILE_NAME_WIIMOTE, mView));
|
||||
mView.onSettingsFileLoaded(mSettings);
|
||||
this.menuTag = menuTag;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -44,6 +51,41 @@ public final class SettingsActivityPresenter
|
|||
}
|
||||
}
|
||||
|
||||
public void onStart()
|
||||
{
|
||||
prepareDolphinDirectoriesIfNeeded();
|
||||
}
|
||||
|
||||
void loadSettingsUI()
|
||||
{
|
||||
mView.showSettingsFragment(menuTag, false);
|
||||
mView.onSettingsFileLoaded(mSettings);
|
||||
}
|
||||
|
||||
private void prepareDolphinDirectoriesIfNeeded()
|
||||
{
|
||||
if (DirectoryInitializationService.areDolphinDirectoriesReady()) {
|
||||
loadSettingsUI();
|
||||
} else {
|
||||
mView.showLoading();
|
||||
IntentFilter statusIntentFilter = new IntentFilter(
|
||||
DirectoryInitializationService.BROADCAST_ACTION);
|
||||
|
||||
directoryStateReceiver =
|
||||
new DirectoryStateReceiver(directoryInitializationState -> {
|
||||
if (directoryInitializationState == DirectoryInitializationService.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
|
||||
mView.hideLoading();
|
||||
loadSettingsUI();
|
||||
} else if (directoryInitializationState == DirectoryInitializationService.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) {
|
||||
mView.showPermissionNeededHint();
|
||||
mView.hideLoading();
|
||||
}
|
||||
});
|
||||
|
||||
mView.startDirectoryInitializationService(directoryStateReceiver, statusIntentFilter);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSettings(ArrayList<HashMap<String, SettingSection>> settings)
|
||||
{
|
||||
mSettings = settings;
|
||||
|
@ -56,6 +98,12 @@ public final class SettingsActivityPresenter
|
|||
|
||||
public void onStop(boolean finishing)
|
||||
{
|
||||
if (directoryStateReceiver != null)
|
||||
{
|
||||
mView.stopListeningToDirectoryInitializationService(directoryStateReceiver);
|
||||
directoryStateReceiver = null;
|
||||
}
|
||||
|
||||
if (mSettings != null && finishing && mShouldSave)
|
||||
{
|
||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package org.dolphinemu.dolphinemu.ui.settings;
|
||||
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -99,4 +102,34 @@ public interface SettingsActivityView
|
|||
* @param value New setting for the extension.
|
||||
*/
|
||||
void onExtensionSettingChanged(String key, int value);
|
||||
|
||||
/**
|
||||
* Show loading dialog while loading the settings
|
||||
*/
|
||||
void showLoading();
|
||||
|
||||
/**
|
||||
* Hide the loading the dialog
|
||||
*/
|
||||
void hideLoading();
|
||||
|
||||
/**
|
||||
* Show a hint to the user that the app needs write to external storage access
|
||||
*/
|
||||
void showPermissionNeededHint();
|
||||
|
||||
/**
|
||||
* Start the DirectoryInitializationService and listen for the result.
|
||||
*
|
||||
* @param receiver the broadcast receiver for the DirectoryInitializationService
|
||||
* @param filter the Intent broadcasts to be received.
|
||||
*/
|
||||
void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter);
|
||||
|
||||
/**
|
||||
* Stop listening to the DirectoryInitializationService.
|
||||
*
|
||||
* @param receiver The broadcast receiver to unregister.
|
||||
*/
|
||||
void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver);
|
||||
}
|
||||
|
|
|
@ -201,34 +201,26 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
|||
dialog.setTitle(R.string.input_binding);
|
||||
dialog.setMessage(String.format(mContext.getString(R.string.input_binding_descrip), mContext.getString(item.getNameId())));
|
||||
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
|
||||
dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), new AlertDialog.OnClickListener()
|
||||
dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), (dialogInterface, i) ->
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i)
|
||||
{
|
||||
item.setValue("");
|
||||
item.setValue("");
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(item.getKey());
|
||||
editor.apply();
|
||||
}
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(item.getKey());
|
||||
editor.apply();
|
||||
});
|
||||
dialog.setOnDismissListener(new AlertDialog.OnDismissListener()
|
||||
dialog.setOnDismissListener(dialog1 ->
|
||||
{
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog)
|
||||
StringSetting setting = new StringSetting(item.getKey(), item.getSection(), item.getFile(), item.getValue());
|
||||
notifyItemChanged(position);
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
StringSetting setting = new StringSetting(item.getKey(), item.getSection(), item.getFile(), item.getValue());
|
||||
notifyItemChanged(position);
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
mView.putSetting(setting);
|
||||
}
|
||||
|
||||
mView.onSettingChanged();
|
||||
mView.putSetting(setting);
|
||||
}
|
||||
|
||||
mView.onSettingChanged();
|
||||
});
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.show();
|
||||
|
@ -270,10 +262,6 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
|||
{
|
||||
putVideoBackendSetting(which);
|
||||
}
|
||||
else if (scSetting.getKey().equals(SettingsFile.KEY_XFB_METHOD))
|
||||
{
|
||||
putXfbSetting(which);
|
||||
}
|
||||
else if (scSetting.getKey().equals(SettingsFile.KEY_UBERSHADER_MODE))
|
||||
{
|
||||
putUberShaderModeSetting(which);
|
||||
|
@ -414,33 +402,6 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
|||
mView.putSetting(gfxBackend);
|
||||
}
|
||||
|
||||
public void putXfbSetting(int which)
|
||||
{
|
||||
BooleanSetting xfbEnable = null;
|
||||
BooleanSetting xfbReal = null;
|
||||
|
||||
switch (which)
|
||||
{
|
||||
case 0:
|
||||
xfbEnable = new BooleanSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, false);
|
||||
xfbReal = new BooleanSetting(SettingsFile.KEY_XFB_REAL, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, false);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
xfbEnable = new BooleanSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, true);
|
||||
xfbReal = new BooleanSetting(SettingsFile.KEY_XFB_REAL, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, false);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
xfbEnable = new BooleanSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, true);
|
||||
xfbReal = new BooleanSetting(SettingsFile.KEY_XFB_REAL, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, true);
|
||||
break;
|
||||
}
|
||||
|
||||
mView.putSetting(xfbEnable);
|
||||
mView.putSetting(xfbReal);
|
||||
}
|
||||
|
||||
public void putUberShaderModeSetting(int which)
|
||||
{
|
||||
BooleanSetting disableSpecializedShaders = null;
|
||||
|
|
|
@ -266,7 +266,7 @@ public final class SettingsFragmentPresenter
|
|||
}
|
||||
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_VIDEO_BACKEND_INDEX, SettingsFile.SECTION_CORE, SettingsFile.SETTINGS_DOLPHIN, R.string.video_backend, R.string.video_backend_descrip, R.array.videoBackendEntries, R.array.videoBackendValues, 0, videoBackend));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHOW_FPS, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, R.string.show_fps, 0, true, showFps));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHOW_FPS, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, R.string.show_fps, 0, false, showFps));
|
||||
|
||||
sl.add(new SubmenuSetting(null, null, R.string.enhancements, 0, SettingsFile.SECTION_GFX_ENHANCEMENTS));
|
||||
sl.add(new SubmenuSetting(null, null, R.string.hacks, 0, SettingsFile.SECTION_GFX_HACKS));
|
||||
|
@ -317,14 +317,14 @@ public final class SettingsFragmentPresenter
|
|||
{
|
||||
boolean skipEFBValue = getInvertedBooleanValue(SettingsFile.SETTINGS_GFX, SettingsFile.SECTION_GFX_HACKS, SettingsFile.KEY_SKIP_EFB, false);
|
||||
boolean ignoreFormatValue = getInvertedBooleanValue(SettingsFile.SETTINGS_GFX, SettingsFile.SECTION_GFX_HACKS, SettingsFile.KEY_IGNORE_FORMAT, true);
|
||||
int xfbValue = getXfbValue();
|
||||
|
||||
BooleanSetting skipEFB = new BooleanSetting(SettingsFile.KEY_SKIP_EFB, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, skipEFBValue);
|
||||
BooleanSetting ignoreFormat = new BooleanSetting(SettingsFile.KEY_IGNORE_FORMAT, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, ignoreFormatValue);
|
||||
Setting efbToTexture = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_EFB_TEXTURE);
|
||||
Setting texCacheAccuracy = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_TEXCACHE_ACCURACY);
|
||||
Setting gpuTextureDecoding = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_GPU_TEXTURE_DECODING);
|
||||
IntSetting xfb = new IntSetting(SettingsFile.KEY_XFB, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, xfbValue);
|
||||
Setting xfbToTexture = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_XFB_TEXTURE);
|
||||
Setting immediateXfb = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_IMMEDIATE_XFB);
|
||||
Setting fastDepth = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_HACKS).getSetting(SettingsFile.KEY_FAST_DEPTH);
|
||||
Setting aspectRatio = mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_ASPECT_RATIO);
|
||||
|
||||
|
@ -338,7 +338,8 @@ public final class SettingsFragmentPresenter
|
|||
sl.add(new CheckBoxSetting(SettingsFile.KEY_GPU_TEXTURE_DECODING, SettingsFile.SECTION_GFX_SETTINGS, SettingsFile.SETTINGS_GFX, R.string.gpu_texture_decoding, R.string.gpu_texture_decoding_descrip, false, gpuTextureDecoding));
|
||||
|
||||
sl.add(new HeaderSetting(null, null, R.string.external_frame_buffer, 0));
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_XFB_METHOD, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, R.string.external_frame_buffer, R.string.external_frame_buffer_descrip, R.array.externalFrameBufferEntries, R.array.externalFrameBufferValues, 0, xfb));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_XFB_TEXTURE, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, R.string.xfb_copy_method, R.string.xfb_copy_method_descrip, true, xfbToTexture));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_IMMEDIATE_XFB, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, R.string.immediate_xfb, R.string.immediate_xfb_descrip, false, immediateXfb));
|
||||
|
||||
sl.add(new HeaderSetting(null, null, R.string.other, 0));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_FAST_DEPTH, SettingsFile.SECTION_GFX_HACKS, SettingsFile.SETTINGS_GFX, R.string.fast_depth_calculation, R.string.fast_depth_calculation_descrip, true, fastDepth));
|
||||
|
@ -798,36 +799,6 @@ public final class SettingsFragmentPresenter
|
|||
return videoBackendValue;
|
||||
}
|
||||
|
||||
private int getXfbValue()
|
||||
{
|
||||
int xfbValue;
|
||||
|
||||
try
|
||||
{
|
||||
boolean usingXFB = ((BooleanSetting) mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_XFB)).getValue();
|
||||
boolean usingRealXFB = ((BooleanSetting) mSettings.get(SettingsFile.SETTINGS_GFX).get(SettingsFile.SECTION_GFX_SETTINGS).getSetting(SettingsFile.KEY_XFB_REAL)).getValue();
|
||||
|
||||
if (!usingXFB)
|
||||
{
|
||||
xfbValue = 0;
|
||||
}
|
||||
else if (!usingRealXFB)
|
||||
{
|
||||
xfbValue = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
xfbValue = 2;
|
||||
}
|
||||
}
|
||||
catch (NullPointerException ex)
|
||||
{
|
||||
xfbValue = 0;
|
||||
}
|
||||
|
||||
return xfbValue;
|
||||
}
|
||||
|
||||
private int getUberShaderModeValue()
|
||||
{
|
||||
int uberShaderModeValue = 0;
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.content.AsyncQueryHandler;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.dolphinemu.dolphinemu.model.GameDatabase;
|
||||
import org.dolphinemu.dolphinemu.model.GameProvider;
|
||||
|
||||
public class AddDirectoryHelper
|
||||
{
|
||||
private Context mContext;
|
||||
|
||||
public interface AddDirectoryListener
|
||||
{
|
||||
void onDirectoryAdded();
|
||||
}
|
||||
|
||||
public AddDirectoryHelper(Context context)
|
||||
{
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void addDirectory(String dir, AddDirectoryListener addDirectoryListener)
|
||||
{
|
||||
AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver())
|
||||
{
|
||||
@Override
|
||||
protected void onInsertComplete(int token, Object cookie, Uri uri)
|
||||
{
|
||||
addDirectoryListener.onDirectoryAdded();
|
||||
}
|
||||
};
|
||||
|
||||
ContentValues file = new ContentValues();
|
||||
file.put(GameDatabase.KEY_FOLDER_PATH, dir);
|
||||
|
||||
handler.startInsert(0, // We don't need to identify this call to the handler
|
||||
null, // We don't need to pass additional data to the handler
|
||||
GameProvider.URI_FOLDER, // Tell the GameProvider we are adding a folder
|
||||
file);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/** Some controllers have incorrect mappings. This class has special-case fixes for them. */
|
||||
public class ControllerMappingHelper
|
||||
{
|
||||
/** Some controllers report extra button presses that can be ignored. */
|
||||
public boolean shouldKeyBeIgnored(InputDevice inputDevice, int keyCode)
|
||||
{
|
||||
if (isDualShock4(inputDevice)) {
|
||||
// The two analog triggers generate analog motion events as well as a keycode.
|
||||
// We always prefer to use the analog values, so throw away the button press
|
||||
// Even though the triggers are L/R2, without mappings they generate L/R1 events.
|
||||
return keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_R1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Scale an axis to be zero-centered with a proper range. */
|
||||
public float scaleAxis(InputDevice inputDevice, int axis, float value)
|
||||
{
|
||||
if (isDualShock4(inputDevice))
|
||||
{
|
||||
// Android doesn't have correct mappings for this controller's triggers. It reports them
|
||||
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
|
||||
// Scale them to properly zero-centered with a range of [0.0, 1.0].
|
||||
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY)
|
||||
{
|
||||
return (value + 1) / 2.0f;
|
||||
}
|
||||
}
|
||||
else if (isXboxOneWireless(inputDevice))
|
||||
{
|
||||
// Same as the DualShock 4, the mappings are missing.
|
||||
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ)
|
||||
{
|
||||
return (value + 1) / 2.0f;
|
||||
}
|
||||
if (axis == MotionEvent.AXIS_GENERIC_1)
|
||||
{
|
||||
// This axis is stuck at ~.5. Ignore it.
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
else if (isMogaPro2Hid(inputDevice))
|
||||
{
|
||||
// This controller has a broken axis that reports a constant value. Ignore it.
|
||||
if (axis == MotionEvent.AXIS_GENERIC_1)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private boolean isDualShock4(InputDevice inputDevice)
|
||||
{
|
||||
// Sony DualShock 4 controller
|
||||
return inputDevice.getVendorId() == 0x54c && inputDevice.getProductId() == 0x9cc;
|
||||
}
|
||||
|
||||
private boolean isXboxOneWireless(InputDevice inputDevice)
|
||||
{
|
||||
// Microsoft Xbox One controller
|
||||
return inputDevice.getVendorId() == 0x45e && inputDevice.getProductId() == 0x2e0;
|
||||
}
|
||||
|
||||
private boolean isMogaPro2Hid(InputDevice inputDevice)
|
||||
{
|
||||
// Moga Pro 2 HID
|
||||
return inputDevice.getVendorId() == 0x20d6 && inputDevice.getProductId() == 0x6271;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState;
|
||||
|
||||
import rx.functions.Action1;
|
||||
|
||||
public class DirectoryStateReceiver extends BroadcastReceiver {
|
||||
Action1<DirectoryInitializationState> callback;
|
||||
public DirectoryStateReceiver(Action1<DirectoryInitializationState> callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
DirectoryInitializationState state = (DirectoryInitializationState) intent.getSerializableExtra(DirectoryInitializationService.EXTRA_STATE);
|
||||
callback.call(state);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
import com.nononsenseapps.filepicker.Utils;
|
||||
|
||||
import org.dolphinemu.dolphinemu.activities.CustomFilePickerActivity;
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public final class FileBrowserHelper
|
||||
{
|
||||
public static void openDirectoryPicker(FragmentActivity activity) {
|
||||
Intent i = new Intent(activity, CustomFilePickerActivity.class);
|
||||
|
||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
|
||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
|
||||
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
|
||||
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath());
|
||||
|
||||
activity.startActivityForResult(i, MainPresenter.REQUEST_ADD_DIRECTORY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getSelectedDirectory(Intent result) {
|
||||
// Use the provided utility method to parse the result
|
||||
List<Uri> files = Utils.getSelectedFilesFromResult(result);
|
||||
if(!files.isEmpty()) {
|
||||
File file = Utils.getFileForUri(files.get(0));
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -137,14 +137,7 @@ public class Java_GCAdapter {
|
|||
final Activity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||
if (emulationActivity != null)
|
||||
{
|
||||
emulationActivity.runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Toast.makeText(emulationActivity, "GameCube Adapter couldn't be opened. Please re-plug the device.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
emulationActivity.runOnUiThread(() -> Toast.makeText(emulationActivity, "GameCube Adapter couldn't be opened. Please re-plug the device.", Toast.LENGTH_LONG).show());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.dolphinemu.dolphinemu.utils;
|
|||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
@ -27,13 +28,8 @@ public class PermissionsHandler {
|
|||
if (hasWritePermission != PackageManager.PERMISSION_GRANTED) {
|
||||
if (activity.shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
|
||||
showMessageOKCancel(activity, activity.getString(R.string.write_permission_needed),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
activity.requestPermissions(new String[] {WRITE_EXTERNAL_STORAGE},
|
||||
REQUEST_CODE_WRITE_PERMISSION);
|
||||
}
|
||||
});
|
||||
(dialog, which) -> activity.requestPermissions(new String[] {WRITE_EXTERNAL_STORAGE},
|
||||
REQUEST_CODE_WRITE_PERMISSION));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -45,9 +41,9 @@ public class PermissionsHandler {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static boolean hasWriteAccess(FragmentActivity activity) {
|
||||
public static boolean hasWriteAccess(Context context) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE);
|
||||
int hasWritePermission = ContextCompat.checkSelfPermission(context, WRITE_EXTERNAL_STORAGE);
|
||||
return hasWritePermission == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
|
@ -58,13 +54,8 @@ public class PermissionsHandler {
|
|||
new AlertDialog.Builder(activity)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, okListener)
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Toast.makeText(activity, R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
|
||||
Toast.makeText(activity, R.string.write_permission_needed, Toast.LENGTH_SHORT).show())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
public final class Pixels
|
||||
{
|
||||
private Pixels()
|
||||
{
|
||||
}
|
||||
|
||||
public static float convertDpToPx(float original, Context context)
|
||||
{
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
|
||||
return original * metrics.density;
|
||||
}
|
||||
|
||||
public static float convertPxToDp(float original, Context context)
|
||||
{
|
||||
Resources resources = context.getResources();
|
||||
DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
|
||||
return original / metrics.density;
|
||||
}
|
||||
}
|
|
@ -103,11 +103,10 @@ public final class SettingsFile
|
|||
public static final String KEY_EFB_TEXTURE = "EFBToTextureEnable";
|
||||
public static final String KEY_TEXCACHE_ACCURACY = "SafeTextureCacheColorSamples";
|
||||
public static final String KEY_GPU_TEXTURE_DECODING = "EnableGPUTextureDecoding";
|
||||
public static final String KEY_XFB = "UseXFB";
|
||||
public static final String KEY_XFB_REAL = "UseRealXFB";
|
||||
public static final String KEY_XFB_TEXTURE = "XFBToTextureEnable";
|
||||
public static final String KEY_IMMEDIATE_XFB = "ImmediateXFBEnable";
|
||||
public static final String KEY_FAST_DEPTH = "FastDepthCalc";
|
||||
public static final String KEY_ASPECT_RATIO = "AspectRatio";
|
||||
public static final String KEY_UBERSHADER_MODE = "UberShaderMode";
|
||||
public static final String KEY_DISABLE_SPECIALIZED_SHADERS = "DisableSpecializedShaders";
|
||||
public static final String KEY_BACKGROUND_SHADER_COMPILING = "BackgroundShaderCompiling";
|
||||
|
||||
|
@ -267,7 +266,7 @@ public final class SettingsFile
|
|||
|
||||
// Internal only, not actually found in settings file.
|
||||
public static final String KEY_VIDEO_BACKEND_INDEX = "VideoBackendIndex";
|
||||
public static final String KEY_XFB_METHOD = "XFBMethod";
|
||||
public static final String KEY_UBERSHADER_MODE = "UberShaderMode";
|
||||
|
||||
private SettingsFile()
|
||||
{
|
||||
|
@ -337,6 +336,11 @@ public final class SettingsFile
|
|||
}
|
||||
}
|
||||
|
||||
if (fileName.equals(SettingsFile.FILE_NAME_DOLPHIN))
|
||||
{
|
||||
addGcPadSettingsIfTheyDontExist(sections);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
|
@ -399,6 +403,23 @@ public final class SettingsFile
|
|||
return new SettingSection(sectionName);
|
||||
}
|
||||
|
||||
private static void addGcPadSettingsIfTheyDontExist(HashMap<String, SettingSection> sections)
|
||||
{
|
||||
SettingSection coreSection = sections.get(SettingsFile.SECTION_CORE);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
String key = SettingsFile.KEY_GCPAD_TYPE + i;
|
||||
if (coreSection.getSetting(key) == null)
|
||||
{
|
||||
Setting gcPadSetting = new IntSetting(key, SettingsFile.SECTION_CORE, SettingsFile.SETTINGS_DOLPHIN, 0);
|
||||
coreSection.putSetting(gcPadSetting);
|
||||
}
|
||||
}
|
||||
|
||||
sections.put(SettingsFile.SECTION_CORE, coreSection);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a line of text, determines what type of data is being represented, and returns
|
||||
* a Setting object containing this data.
|
||||
|
|
|
@ -1,60 +1,31 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||
import org.dolphinemu.dolphinemu.services.AssetCopyService;
|
||||
|
||||
public final class StartupHandler
|
||||
{
|
||||
public static boolean HandleInit(FragmentActivity parent)
|
||||
public static void HandleInit(FragmentActivity parent)
|
||||
{
|
||||
NativeLibrary.SetUserDirectory(""); // Auto-Detect
|
||||
|
||||
// Only perform these extensive copy operations once.
|
||||
if (PermissionsHandler.checkWritePermission(parent)) {
|
||||
copyAssetsIfNeeded(parent);
|
||||
}
|
||||
|
||||
Intent intent = parent.getIntent();
|
||||
Bundle extras = intent.getExtras();
|
||||
// Ask the user to grant write permission if it's not already granted
|
||||
PermissionsHandler.checkWritePermission(parent);
|
||||
|
||||
String start_file = "";
|
||||
Bundle extras = parent.getIntent().getExtras();
|
||||
if (extras != null)
|
||||
start_file = extras.getString("AutoStartFile");
|
||||
|
||||
if (!TextUtils.isEmpty(start_file))
|
||||
{
|
||||
String user_dir = extras.getString("UserDir");
|
||||
String start_file = extras.getString("AutoStartFile");
|
||||
|
||||
if (!TextUtils.isEmpty(user_dir))
|
||||
NativeLibrary.SetUserDirectory(user_dir);
|
||||
|
||||
if (!TextUtils.isEmpty(start_file))
|
||||
{
|
||||
// Start the emulation activity, send the ISO passed in and finish the main activity
|
||||
Intent emulation_intent = new Intent(parent, EmulationActivity.class);
|
||||
emulation_intent.putExtra("SelectedGame", start_file);
|
||||
parent.startActivity(emulation_intent);
|
||||
parent.finish();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void copyAssetsIfNeeded(FragmentActivity parent) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(parent);
|
||||
boolean assetsCopied = preferences.getBoolean("assetsCopied", false);
|
||||
|
||||
if (!assetsCopied)
|
||||
{
|
||||
// Copy assets into appropriate locations.
|
||||
Intent copyAssets = new Intent(parent, AssetCopyService.class);
|
||||
parent.startService(copyAssets);
|
||||
// Start the emulation activity, send the ISO passed in and finish the main activity
|
||||
Intent emulation_intent = new Intent(parent, EmulationActivity.class);
|
||||
emulation_intent.putExtra("SelectedGame", start_file);
|
||||
parent.startActivity(emulation_intent);
|
||||
parent.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<objectAnimator
|
||||
android:propertyName="translationX"
|
||||
android:valueType="floatType"
|
||||
android:valueFrom="1280"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:interpolator/decelerate_quad"
|
||||
android:duration="300"/>
|
||||
|
||||
<objectAnimator
|
||||
android:propertyName="alpha"
|
||||
android:valueType="floatType"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:interpolator/accelerate_quad"
|
||||
android:duration="300"/>
|
||||
|
||||
</set>
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- This animation is used ONLY when a submenu is replaced. -->
|
||||
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:propertyName="translationX"
|
||||
android:valueType="floatType"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1280"
|
||||
android:interpolator="@android:interpolator/decelerate_quad"
|
||||
android:duration="300"/>
|
||||
|
||||
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:propertyName="alpha"
|
||||
android:valueType="floatType"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:interpolator/decelerate_quad"
|
||||
android:duration="300"/>
|
||||
|
||||
</set>
|
Before Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 726 B |
Before Width: | Height: | Size: 108 B |
Before Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 447 B |
Before Width: | Height: | Size: 178 B |
Before Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 934 B |
Before Width: | Height: | Size: 115 B |
Before Width: | Height: | Size: 306 B |
Before Width: | Height: | Size: 555 B |
Before Width: | Height: | Size: 157 B |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 229 B |
Before Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 440 B |
|
@ -1,33 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.dolphinemu.dolphinemu.ui.settings.SettingsFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="@dimen/elevation_high"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/list_item_controller_one"
|
||||
layout="@layout/list_item_setting"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/list_item_controller_two"
|
||||
layout="@layout/list_item_setting"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/list_item_controller_two"
|
||||
layout="@layout/list_item_setting"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/list_item_controller_two"
|
||||
layout="@layout/list_item_setting"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.dolphinemu.dolphinemu.ui.settings.SettingsFrameLayout>
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#af000000"
|
||||
android:orientation="vertical">
|
||||
|
||||
<GridLayout
|
||||
android:id="@+id/grid_state_slots"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:rowCount="2"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_load_1"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot1"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_load_2"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot2"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_load_3"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot3"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_load_4"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot4"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_load_5"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot5"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_load_6"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot6"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
</GridLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#af000000"
|
||||
android:orientation="vertical">
|
||||
|
||||
<GridLayout
|
||||
android:id="@+id/grid_state_slots"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:rowCount="2"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_save_1"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot1"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_save_2"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot2"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_save_3"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot3"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_save_4"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot4"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_save_5"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot5"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_emulation_save_6"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:text="@string/emulation_slot6"
|
||||
style="@style/OverlayInGameMenuOption"/>
|
||||
|
||||
</GridLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<transitionSet>
|
||||
<changeImageTransform/>
|
||||
</transitionSet>
|
|
@ -91,18 +91,6 @@
|
|||
<item>0</item>
|
||||
</integer-array>
|
||||
|
||||
<!-- External Frame Buffer Preference -->
|
||||
<string-array name="externalFrameBufferEntries" translatable="false">
|
||||
<item>Disabled</item>
|
||||
<item>Virtual</item>
|
||||
<item>Real</item>
|
||||
</string-array>
|
||||
<integer-array name="externalFrameBufferValues" translatable="false">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<!-- Ubershader Mode Preference -->
|
||||
<string-array name="uberShaderModeEntries" translatable="false">
|
||||
<item>Disabled</item>
|
||||
|
|
|
@ -174,7 +174,10 @@
|
|||
<string name="gpu_texture_decoding">GPU Texture Decoding</string>
|
||||
<string name="gpu_texture_decoding_descrip">Decodes textures on the GPU using compute shaders where supported. May improve performance in some scenarios.</string>
|
||||
<string name="external_frame_buffer">External Frame Buffer</string>
|
||||
<string name="external_frame_buffer_descrip">Determines how the XFB will be emulated.</string>
|
||||
<string name="xfb_copy_method">Store XFB Copies to Texture Only</string>
|
||||
<string name="xfb_copy_method_descrip">Stores XFB Copies exclusively on the GPU, bypassing system memory. Causes graphical defects in a small number of games that need to readback from memory. If unsure, leave this checked.</string>
|
||||
<string name="immediate_xfb">Immediately Present XFB</string>
|
||||
<string name="immediate_xfb_descrip">Displays the XFB copies as soon as they are created, without waiting for scanout. Causes graphical defects in some games but reduces latency. If unsure, leave this unchecked.</string>
|
||||
<string name="disable_destination_alpha">Disable Destination Alpha</string>
|
||||
<string name="disable_destination_alpha_descrip">Disables emulation of a hardware feature called destination alpha, which is used in many games for various effects.</string>
|
||||
<string name="fast_depth_calculation">Fast Depth Calculation</string>
|
||||
|
@ -244,4 +247,6 @@
|
|||
<string name="header_controllers">Controllers</string>
|
||||
|
||||
<string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string>
|
||||
|
||||
<string name="load_settings">Loading Settings...</string>
|
||||
</resources>
|
||||
|
|
|
@ -145,4 +145,27 @@
|
|||
<item name="android:textColor">@color/button_text_color</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
<!-- You can also inherit from NNF_BaseTheme.Light -->
|
||||
<style name="FilePickerTheme" parent="NNF_BaseTheme.Light">
|
||||
<item name="colorPrimary">@color/dolphin_blue</item>
|
||||
<item name="colorPrimaryDark">@color/dolphin_blue_dark</item>
|
||||
<item name="colorAccent">@color/dolphin_accent_gamecube</item>
|
||||
|
||||
<!--<!– Setting a divider is entirely optional –>-->
|
||||
<item name="nnf_list_item_divider">?android:attr/listDivider</item>
|
||||
|
||||
<!-- Need to set this also to style create folder dialog -->
|
||||
<item name="alertDialogTheme">@style/FilePickerAlertDialogTheme</item>
|
||||
|
||||
<!-- If you want to set a specific toolbar theme, do it here -->
|
||||
|
||||
<item name="nnf_toolbarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
|
||||
</style>
|
||||
|
||||
<style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
|
||||
<item name="colorPrimary">@color/dolphin_blue</item>
|
||||
<item name="colorPrimaryDark">@color/dolphin_blue_dark</item>
|
||||
<item name="colorAccent">@color/dolphin_accent_gamecube</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -15,6 +15,8 @@ ${LIBS}
|
|||
)
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/Data/Sys/GameSettings ${CMAKE_SOURCE_DIR}/Data/Sys/GC ${CMAKE_SOURCE_DIR}/Data/Sys/Wii ${CMAKE_SOURCE_DIR}/Data/Sys/Shaders DESTINATION ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/)
|
||||
file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/)
|
||||
file(COPY ${CMAKE_SOURCE_DIR}/Data/Sys DESTINATION ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/)
|
||||
file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/Resources/) # not used on Android
|
||||
|
||||
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} ${SHARED_LIB})
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "ButtonManager.h"
|
||||
|
||||
|
@ -54,7 +56,6 @@ JavaVM* g_java_vm;
|
|||
namespace
|
||||
{
|
||||
ANativeWindow* s_surf;
|
||||
std::string s_set_userpath;
|
||||
|
||||
jclass s_jni_class;
|
||||
jmethodID s_jni_method_alert;
|
||||
|
@ -160,12 +161,14 @@ static bool MsgAlert(const char* caption, const char* text, bool yes_no, MsgType
|
|||
g_java_vm->AttachCurrentThread(&env, NULL);
|
||||
|
||||
// Execute the Java method.
|
||||
env->CallStaticVoidMethod(s_jni_class, s_jni_method_alert, env->NewStringUTF(text));
|
||||
jboolean result =
|
||||
env->CallStaticBooleanMethod(s_jni_class, s_jni_method_alert, env->NewStringUTF(caption),
|
||||
env->NewStringUTF(text), yes_no ? JNI_TRUE : JNI_FALSE);
|
||||
|
||||
// Must be called before the current thread exits; might as well do it here.
|
||||
g_java_vm->DetachCurrentThread();
|
||||
|
||||
return false;
|
||||
return result != JNI_FALSE;
|
||||
}
|
||||
|
||||
#define DVD_BANNER_WIDTH 96
|
||||
|
@ -404,6 +407,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulati
|
|||
jobject obj);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulation(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent(
|
||||
JNIEnv* env, jobject obj, jstring jDevice, jint Button, jint Action);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMoveEvent(
|
||||
|
@ -432,6 +437,8 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetPlatform(
|
|||
jstring jFilename);
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_GetVersionString(JNIEnv* env, jobject obj);
|
||||
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGitRevision(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveScreenShot(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_eglBindAPI(JNIEnv* env,
|
||||
|
@ -446,12 +453,21 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetFilename(
|
|||
jstring jFile);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv* env,
|
||||
jobject obj,
|
||||
jint slot);
|
||||
jint slot,
|
||||
jboolean wait);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring path,
|
||||
jboolean wait);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv* env,
|
||||
jobject obj,
|
||||
jint slot);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_CreateUserFolders(JNIEnv* env,
|
||||
jobject obj);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadStateAs(JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring path);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_CreateUserDirectories(
|
||||
JNIEnv* env, jobject obj);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirectory(
|
||||
JNIEnv* env, jobject obj, jstring jDirectory);
|
||||
JNIEXPORT jstring JNICALL
|
||||
|
@ -465,8 +481,11 @@ JNIEXPORT void JNICALL
|
|||
Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfileResults(JNIEnv* env, jobject obj);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env, jobject obj);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv* env, jobject obj,
|
||||
jstring jFile);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2(
|
||||
JNIEnv* env, jobject obj, jstring jFile);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
|
||||
JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate, jboolean jDeleteSavestate);
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChanged(JNIEnv* env,
|
||||
jobject obj,
|
||||
jobject surf);
|
||||
|
@ -494,11 +513,19 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio
|
|||
Core::Stop();
|
||||
s_update_main_frame_event.Set(); // Kick the waiting event
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return Core::IsRunning();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent(
|
||||
JNIEnv* env, jobject obj, jstring jDevice, jint Button, jint Action)
|
||||
{
|
||||
return ButtonManager::GamepadEvent(GetJString(env, jDevice), Button, Action);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMoveEvent(
|
||||
JNIEnv* env, jobject obj, jstring jDevice, jint Axis, jfloat Value)
|
||||
{
|
||||
|
@ -588,6 +615,12 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetVersio
|
|||
return env->NewStringUTF(Common::scm_rev_str.c_str());
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGitRevision(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return env->NewStringUTF(Common::scm_rev_git_str.c_str());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveScreenShot(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
|
@ -635,10 +668,20 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetConfig(
|
|||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv* env,
|
||||
jobject obj,
|
||||
jint slot)
|
||||
jint slot,
|
||||
jboolean wait)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
State::Save(slot);
|
||||
State::Save(slot, wait);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring path,
|
||||
jboolean wait)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
State::SaveAs(GetJString(env, path), wait);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv* env,
|
||||
|
@ -649,37 +692,34 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JN
|
|||
State::Load(slot);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_CreateUserFolders(JNIEnv* env,
|
||||
jobject obj)
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadStateAs(JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring path)
|
||||
{
|
||||
File::CreateFullPath(File::GetUserPath(D_CONFIG_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_WIIROOT_IDX) + DIR_SEP WII_WC24CONF_DIR DIR_SEP
|
||||
"mbox" DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_WIIROOT_IDX) + DIR_SEP "shared2" DIR_SEP
|
||||
"succession" DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_WIIROOT_IDX) + DIR_SEP "shared2" DIR_SEP "ec" DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_WIIROOT_IDX) + DIR_SEP WII_SYSCONF_DIR DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_CACHE_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPDSP_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPTEXTURES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_SCREENSHOTS_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_STATESAVES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_MAILLOGS_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX) + "Anaglyph" DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + USA_DIR DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + EUR_DIR DIR_SEP);
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + JAP_DIR DIR_SEP);
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
State::LoadAs(GetJString(env, path));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_SetSysDirectory(
|
||||
JNIEnv* env, jobject obj, jstring jPath)
|
||||
{
|
||||
const std::string path = GetJString(env, jPath);
|
||||
File::SetSysDirectory(path);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_CreateUserDirectories(
|
||||
JNIEnv* env, jobject obj)
|
||||
{
|
||||
UICommon::CreateDirectories();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirectory(
|
||||
JNIEnv* env, jobject obj, jstring jDirectory)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_host_identity_lock);
|
||||
std::string directory = GetJString(env, jDirectory);
|
||||
s_set_userpath = directory;
|
||||
UICommon::SetUserDirectory(directory);
|
||||
UICommon::SetUserDirectory(GetJString(env, jDirectory));
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetUserDirectory(JNIEnv* env,
|
||||
|
@ -729,8 +769,8 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env,
|
|||
|
||||
// Method signature taken from javap -s
|
||||
// Source/Android/app/build/intermediates/classes/arm/debug/org/dolphinemu/dolphinemu/NativeLibrary.class
|
||||
s_jni_method_alert =
|
||||
env->GetStaticMethodID(s_jni_class, "displayAlertMsg", "(Ljava/lang/String;)V");
|
||||
s_jni_method_alert = env->GetStaticMethodID(s_jni_class, "displayAlertMsg",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Z)Z");
|
||||
}
|
||||
|
||||
// Surface Handling
|
||||
|
@ -765,10 +805,9 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimo
|
|||
WiimoteReal::Refresh();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv* env, jobject obj,
|
||||
jstring jFile)
|
||||
static void Run(const std::string& path, std::optional<std::string> savestate_path = {},
|
||||
bool delete_savestate = false)
|
||||
{
|
||||
const std::string path = GetJString(env, jFile);
|
||||
__android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", path.c_str());
|
||||
|
||||
// Install our callbacks
|
||||
|
@ -778,14 +817,15 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv*
|
|||
RegisterMsgAlertHandler(&MsgAlert);
|
||||
|
||||
std::unique_lock<std::mutex> guard(s_host_identity_lock);
|
||||
UICommon::SetUserDirectory(s_set_userpath);
|
||||
UICommon::Init();
|
||||
|
||||
WiimoteReal::InitAdapterClass();
|
||||
|
||||
// No use running the loop when booting fails
|
||||
s_have_wm_user_stop = false;
|
||||
if (BootManager::BootCore(BootParameters::GenerateFromFile(path)))
|
||||
std::unique_ptr<BootParameters> boot = BootParameters::GenerateFromFile(path, savestate_path);
|
||||
boot->delete_savestate = delete_savestate;
|
||||
if (BootManager::BootCore(std::move(boot)))
|
||||
{
|
||||
static constexpr int TIMEOUT = 10000;
|
||||
static constexpr int WAIT_STEP = 25;
|
||||
|
@ -816,6 +856,19 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv*
|
|||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2(
|
||||
JNIEnv* env, jobject obj, jstring jFile)
|
||||
{
|
||||
Run(GetJString(env, jFile));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
|
||||
JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate, jboolean jDeleteSavestate)
|
||||
{
|
||||
Run(GetJString(env, jFile), GetJString(env, jSavestate), jDeleteSavestate);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -15,9 +15,19 @@ AlsaSound::AlsaSound()
|
|||
{
|
||||
}
|
||||
|
||||
bool AlsaSound::Start()
|
||||
AlsaSound::~AlsaSound()
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::RUNNING);
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPING);
|
||||
|
||||
// Give the opportunity to the audio thread
|
||||
// to realize we are stopping the emulation
|
||||
cv.notify_one();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
bool AlsaSound::Init()
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::PAUSED);
|
||||
if (!AlsaInit())
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPED);
|
||||
|
@ -28,16 +38,6 @@ bool AlsaSound::Start()
|
|||
return true;
|
||||
}
|
||||
|
||||
void AlsaSound::Stop()
|
||||
{
|
||||
m_thread_status.store(ALSAThreadStatus::STOPPING);
|
||||
|
||||
// Give the opportunity to the audio thread
|
||||
// to realize we are stopping the emulation
|
||||
cv.notify_one();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void AlsaSound::Update()
|
||||
{
|
||||
// don't need to do anything here.
|
||||
|
@ -78,10 +78,11 @@ void AlsaSound::SoundLoop()
|
|||
m_thread_status.store(ALSAThreadStatus::STOPPED);
|
||||
}
|
||||
|
||||
void AlsaSound::SetRunning(bool running)
|
||||
bool AlsaSound::SetRunning(bool running)
|
||||
{
|
||||
m_thread_status.store(running ? ALSAThreadStatus::RUNNING : ALSAThreadStatus::PAUSED);
|
||||
cv.notify_one(); // Notify thread that status has changed
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AlsaSound::AlsaInit()
|
||||
|
|
|
@ -21,12 +21,12 @@ class AlsaSound final : public SoundStream
|
|||
#if defined(HAVE_ALSA) && HAVE_ALSA
|
||||
public:
|
||||
AlsaSound();
|
||||
~AlsaSound() override;
|
||||
|
||||
bool Start() override;
|
||||
bool Init() override;
|
||||
void SoundLoop() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
void SetRunning(bool running) override;
|
||||
bool SetRunning(bool running) override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
std::unique_ptr<SoundStream> g_sound_stream;
|
||||
|
||||
static bool s_audio_dump_start = false;
|
||||
static bool s_sound_stream_running = false;
|
||||
|
||||
namespace AudioCommon
|
||||
{
|
||||
|
@ -50,23 +51,15 @@ void InitSoundStream()
|
|||
else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid())
|
||||
g_sound_stream = std::make_unique<OpenSLESStream>();
|
||||
|
||||
if (!g_sound_stream)
|
||||
if (!g_sound_stream || !g_sound_stream->Init())
|
||||
{
|
||||
WARN_LOG(AUDIO, "Could not initialize backend %s, using %s instead.", backend.c_str(),
|
||||
BACKEND_NULLSOUND);
|
||||
g_sound_stream = std::make_unique<NullSound>();
|
||||
}
|
||||
|
||||
if (!g_sound_stream->Start())
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Could not start backend %s, using %s instead", backend.c_str(),
|
||||
BACKEND_NULLSOUND);
|
||||
|
||||
g_sound_stream = std::make_unique<NullSound>();
|
||||
g_sound_stream->Start();
|
||||
}
|
||||
|
||||
UpdateSoundStream();
|
||||
SetSoundStreamRunning(true);
|
||||
|
||||
if (SConfig::GetInstance().m_DumpAudio && !s_audio_dump_start)
|
||||
StartAudioDump();
|
||||
|
@ -76,15 +69,11 @@ void ShutdownSoundStream()
|
|||
{
|
||||
INFO_LOG(AUDIO, "Shutting down sound stream");
|
||||
|
||||
if (g_sound_stream)
|
||||
{
|
||||
g_sound_stream->Stop();
|
||||
if (SConfig::GetInstance().m_DumpAudio && s_audio_dump_start)
|
||||
StopAudioDump();
|
||||
|
||||
if (SConfig::GetInstance().m_DumpAudio && s_audio_dump_start)
|
||||
StopAudioDump();
|
||||
|
||||
g_sound_stream.reset();
|
||||
}
|
||||
SetSoundStreamRunning(false);
|
||||
g_sound_stream.reset();
|
||||
|
||||
INFO_LOG(AUDIO, "Done shutting down sound stream");
|
||||
}
|
||||
|
@ -161,8 +150,19 @@ void UpdateSoundStream()
|
|||
|
||||
void SetSoundStreamRunning(bool running)
|
||||
{
|
||||
if (g_sound_stream)
|
||||
g_sound_stream->SetRunning(running);
|
||||
if (!g_sound_stream)
|
||||
return;
|
||||
|
||||
if (s_sound_stream_running == running)
|
||||
return;
|
||||
s_sound_stream_running = running;
|
||||
|
||||
if (g_sound_stream->SetRunning(running))
|
||||
return;
|
||||
if (running)
|
||||
ERROR_LOG(AUDIO, "Error starting stream.");
|
||||
else
|
||||
ERROR_LOG(AUDIO, "Error stopping stream.");
|
||||
}
|
||||
|
||||
void SendAIBuffer(const short* samples, unsigned int num_samples)
|
||||
|
@ -198,6 +198,8 @@ void StartAudioDump()
|
|||
|
||||
void StopAudioDump()
|
||||
{
|
||||
if (!g_sound_stream)
|
||||
return;
|
||||
g_sound_stream->GetMixer()->StopLogDTKAudio();
|
||||
g_sound_stream->GetMixer()->StopLogDSPAudio();
|
||||
s_audio_dump_start = false;
|
||||
|
|
|
@ -32,7 +32,7 @@ void CubebStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_sta
|
|||
{
|
||||
}
|
||||
|
||||
bool CubebStream::Start()
|
||||
bool CubebStream::Init()
|
||||
{
|
||||
m_ctx = CubebUtils::GetContext();
|
||||
if (!m_ctx)
|
||||
|
@ -60,28 +60,22 @@ bool CubebStream::Start()
|
|||
ERROR_LOG(AUDIO, "Error getting minimum latency");
|
||||
INFO_LOG(AUDIO, "Minimum latency: %i frames", minimum_latency);
|
||||
|
||||
if (cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, nullptr,
|
||||
¶ms, std::max(BUFFER_SAMPLES, minimum_latency), DataCallback,
|
||||
StateCallback, this) != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error initializing cubeb stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cubeb_stream_start(m_stream) != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error starting cubeb stream");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr,
|
||||
nullptr, ¶ms, std::max(BUFFER_SAMPLES, minimum_latency),
|
||||
DataCallback, StateCallback, this) == CUBEB_OK;
|
||||
}
|
||||
|
||||
void CubebStream::Stop()
|
||||
bool CubebStream::SetRunning(bool running)
|
||||
{
|
||||
if (cubeb_stream_stop(m_stream) != CUBEB_OK)
|
||||
{
|
||||
ERROR_LOG(AUDIO, "Error stopping cubeb stream");
|
||||
}
|
||||
if (running)
|
||||
return cubeb_stream_start(m_stream) == CUBEB_OK;
|
||||
else
|
||||
return cubeb_stream_stop(m_stream) == CUBEB_OK;
|
||||
}
|
||||
|
||||
CubebStream::~CubebStream()
|
||||
{
|
||||
SetRunning(false);
|
||||
cubeb_stream_destroy(m_stream);
|
||||
m_ctx.reset();
|
||||
}
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
class CubebStream final : public SoundStream
|
||||
{
|
||||
public:
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
~CubebStream() override;
|
||||
bool Init() override;
|
||||
bool SetRunning(bool running) override;
|
||||
void SetVolume(int) override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "AudioCommon/DPL2Decoder.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
@ -25,6 +26,13 @@ Mixer::~Mixer()
|
|||
{
|
||||
}
|
||||
|
||||
void Mixer::DoState(PointerWrap& p)
|
||||
{
|
||||
m_dma_mixer.DoState(p);
|
||||
m_streaming_mixer.DoState(p);
|
||||
m_wiimote_speaker_mixer.DoState(p);
|
||||
}
|
||||
|
||||
// Executed from sound stream thread
|
||||
unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
|
||||
bool consider_framelimit)
|
||||
|
@ -333,6 +341,13 @@ void Mixer::StopLogDSPAudio()
|
|||
}
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(m_input_sample_rate);
|
||||
p.Do(m_LVolume);
|
||||
p.Do(m_RVolume);
|
||||
}
|
||||
|
||||
void Mixer::MixerFifo::SetInputSampleRate(unsigned int rate)
|
||||
{
|
||||
m_input_sample_rate = rate;
|
||||
|
|
|
@ -11,12 +11,16 @@
|
|||
#include "AudioCommon/WaveFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
class Mixer final
|
||||
{
|
||||
public:
|
||||
explicit Mixer(unsigned int BackendSampleRate);
|
||||
~Mixer();
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
// Called from audio threads
|
||||
unsigned int Mix(short* samples, unsigned int numSamples);
|
||||
unsigned int MixSurround(float* samples, unsigned int num_samples);
|
||||
|
@ -53,6 +57,7 @@ private:
|
|||
MixerFifo(Mixer* mixer, unsigned sample_rate) : m_mixer(mixer), m_input_sample_rate(sample_rate)
|
||||
{
|
||||
}
|
||||
void DoState(PointerWrap& p);
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
void SetInputSampleRate(unsigned int rate);
|
||||
|
|
|
@ -8,7 +8,12 @@ void NullSound::SoundLoop()
|
|||
{
|
||||
}
|
||||
|
||||
bool NullSound::Start()
|
||||
bool NullSound::Init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullSound::SetRunning(bool running)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -20,7 +25,3 @@ void NullSound::SetVolume(int volume)
|
|||
void NullSound::Update()
|
||||
{
|
||||
}
|
||||
|
||||
void NullSound::Stop()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
class NullSound final : public SoundStream
|
||||
{
|
||||
public:
|
||||
bool Start() override;
|
||||
bool Init() override;
|
||||
void SoundLoop() override;
|
||||
bool SetRunning(bool running) override;
|
||||
void SetVolume(int volume) override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid() { return true; }
|
||||
|
|
|
@ -92,7 +92,7 @@ bool OpenALStream::isValid()
|
|||
//
|
||||
// AyuanX: Spec says OpenAL1.1 is thread safe already
|
||||
//
|
||||
bool OpenALStream::Start()
|
||||
bool OpenALStream::Init()
|
||||
{
|
||||
if (!palcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
|
||||
{
|
||||
|
@ -124,7 +124,7 @@ bool OpenALStream::Start()
|
|||
return true;
|
||||
}
|
||||
|
||||
void OpenALStream::Stop()
|
||||
OpenALStream::~OpenALStream()
|
||||
{
|
||||
m_run_thread.Clear();
|
||||
// kick the thread if it's waiting
|
||||
|
@ -161,7 +161,7 @@ void OpenALStream::Update()
|
|||
m_sound_sync_event.Set();
|
||||
}
|
||||
|
||||
void OpenALStream::SetRunning(bool running)
|
||||
bool OpenALStream::SetRunning(bool running)
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
|
@ -171,6 +171,7 @@ void OpenALStream::SetRunning(bool running)
|
|||
{
|
||||
palSourceStop(m_source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ALenum CheckALError(const char* desc)
|
||||
|
|
|
@ -55,11 +55,11 @@ class OpenALStream final : public SoundStream
|
|||
#ifdef _WIN32
|
||||
public:
|
||||
OpenALStream() : m_source(0) {}
|
||||
bool Start() override;
|
||||
~OpenALStream() override;
|
||||
bool Init() override;
|
||||
void SoundLoop() override;
|
||||
void SetVolume(int volume) override;
|
||||
void Stop() override;
|
||||
void SetRunning(bool running) override;
|
||||
bool SetRunning(bool running) override;
|
||||
void Update() override;
|
||||
|
||||
static bool isValid();
|
||||
|
|
|
@ -49,7 +49,7 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
|||
_assert_msg_(AUDIO, SL_RESULT_SUCCESS == result, "Couldn't enqueue audio stream.");
|
||||
}
|
||||
|
||||
bool OpenSLESStream::Start()
|
||||
bool OpenSLESStream::Init()
|
||||
{
|
||||
SLresult result;
|
||||
// create engine
|
||||
|
@ -110,7 +110,7 @@ bool OpenSLESStream::Start()
|
|||
return true;
|
||||
}
|
||||
|
||||
void OpenSLESStream::Stop()
|
||||
OpenSLESStream::~OpenSLESStream()
|
||||
{
|
||||
if (bqPlayerObject != nullptr)
|
||||
{
|
||||
|
|
|
@ -13,8 +13,9 @@ class OpenSLESStream final : public SoundStream
|
|||
{
|
||||
#ifdef ANDROID
|
||||
public:
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
~OpenSLESStream() override;
|
||||
bool Init() override;
|
||||
bool SetRunning(bool running) override { return running; }
|
||||
static bool isValid() { return true; }
|
||||
private:
|
||||
std::thread thread;
|
||||
|
|
|
@ -19,7 +19,7 @@ PulseAudio::PulseAudio() : m_thread(), m_run_thread()
|
|||
{
|
||||
}
|
||||
|
||||
bool PulseAudio::Start()
|
||||
bool PulseAudio::Init()
|
||||
{
|
||||
m_stereo = !SConfig::GetInstance().bDPL2Decoder;
|
||||
m_channels = m_stereo ? 2 : 5; // will tell PA we use a Stereo or 5.0 channel setup
|
||||
|
@ -32,17 +32,12 @@ bool PulseAudio::Start()
|
|||
return true;
|
||||
}
|
||||
|
||||
void PulseAudio::Stop()
|
||||
PulseAudio::~PulseAudio()
|
||||
{
|
||||
m_run_thread.Clear();
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void PulseAudio::Update()
|
||||
{
|
||||
// don't need to do anything here.
|
||||
}
|
||||
|
||||
// Called on audio thread.
|
||||
void PulseAudio::SoundLoop()
|
||||
{
|
||||
|
|
|
@ -18,11 +18,10 @@ class PulseAudio final : public SoundStream
|
|||
#if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO
|
||||
public:
|
||||
PulseAudio();
|
||||
~PulseAudio() override;
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
void Update() override;
|
||||
|
||||
bool Init() override;
|
||||
bool SetRunning(bool running) override { return running; }
|
||||
static bool isValid() { return true; }
|
||||
void StateCallback(pa_context* c);
|
||||
void WriteCallback(pa_stream* s, size_t length);
|
||||
|
|
|
@ -19,10 +19,9 @@ public:
|
|||
virtual ~SoundStream() {}
|
||||
static bool isValid() { return false; }
|
||||
Mixer* GetMixer() const { return m_mixer.get(); }
|
||||
virtual bool Start() { return false; }
|
||||
virtual bool Init() { return false; }
|
||||
virtual void SetVolume(int) {}
|
||||
virtual void SoundLoop() {}
|
||||
virtual void Stop() {}
|
||||
virtual void Update() {}
|
||||
virtual void SetRunning(bool running) {}
|
||||
virtual bool SetRunning(bool running) { return false; }
|
||||
};
|
||||
|
|
|
@ -169,7 +169,7 @@ XAudio2::~XAudio2()
|
|||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool XAudio2::Start()
|
||||
bool XAudio2::Init()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
|
@ -210,15 +210,17 @@ void XAudio2::SetVolume(int volume)
|
|||
m_mastering_voice->SetVolume(m_volume);
|
||||
}
|
||||
|
||||
void XAudio2::SetRunning(bool running)
|
||||
bool XAudio2::SetRunning(bool running)
|
||||
{
|
||||
if (m_voice_context)
|
||||
{
|
||||
if (running)
|
||||
m_voice_context->Play();
|
||||
else
|
||||
m_voice_context->Stop();
|
||||
}
|
||||
if (!m_voice_context)
|
||||
return false;
|
||||
|
||||
if (running)
|
||||
m_voice_context->Play();
|
||||
else
|
||||
m_voice_context->Stop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2::Stop()
|
||||
|
|
|
@ -51,15 +51,15 @@ private:
|
|||
static void* PXAudio2Create;
|
||||
|
||||
static bool InitLibrary();
|
||||
void Stop();
|
||||
|
||||
public:
|
||||
XAudio2();
|
||||
virtual ~XAudio2();
|
||||
~XAudio2() override;
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
bool Init() override;
|
||||
|
||||
void SetRunning(bool running) override;
|
||||
bool SetRunning(bool running) override;
|
||||
void SetVolume(int volume) override;
|
||||
|
||||
static bool isValid() { return InitLibrary(); }
|
||||
|
|
|
@ -157,7 +157,7 @@ XAudio2_7::~XAudio2_7()
|
|||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool XAudio2_7::Start()
|
||||
bool XAudio2_7::Init()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
|
@ -198,15 +198,17 @@ void XAudio2_7::SetVolume(int volume)
|
|||
m_mastering_voice->SetVolume(m_volume);
|
||||
}
|
||||
|
||||
void XAudio2_7::SetRunning(bool running)
|
||||
bool XAudio2_7::SetRunning(bool running)
|
||||
{
|
||||
if (m_voice_context)
|
||||
{
|
||||
if (running)
|
||||
m_voice_context->Play();
|
||||
else
|
||||
m_voice_context->Stop();
|
||||
}
|
||||
if (!m_voice_context)
|
||||
return false;
|
||||
|
||||
if (running)
|
||||
m_voice_context->Play();
|
||||
else
|
||||
m_voice_context->Stop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2_7::Stop()
|
||||
|
|
|
@ -56,15 +56,15 @@ private:
|
|||
static HMODULE m_xaudio2_dll;
|
||||
|
||||
static bool InitLibrary();
|
||||
void Stop();
|
||||
|
||||
public:
|
||||
XAudio2_7();
|
||||
virtual ~XAudio2_7();
|
||||
~XAudio2_7() override;
|
||||
|
||||
bool Start() override;
|
||||
void Stop() override;
|
||||
bool Init() override;
|
||||
|
||||
void SetRunning(bool running) override;
|
||||
bool SetRunning(bool running) override;
|
||||
void SetVolume(int volume) override;
|
||||
|
||||
static bool isValid() { return InitLibrary(); }
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/FifoQueue.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/HttpRequest.h"
|
||||
#include "Common/SPSCQueue.h"
|
||||
|
||||
// Utilities for analytics reporting in Dolphin. This reporting is designed to
|
||||
// provide anonymous data about how well Dolphin performs in the wild. It also
|
||||
|
@ -157,7 +157,7 @@ protected:
|
|||
std::thread m_reporter_thread;
|
||||
Common::Event m_reporter_event;
|
||||
Common::Flag m_reporter_stop_request;
|
||||
FifoQueue<std::string> m_reports_queue;
|
||||
SPSCQueue<std::string> m_reports_queue;
|
||||
};
|
||||
|
||||
// Analytics backend to be used for debugging purpose, which dumps reports to
|
||||
|
@ -180,7 +180,7 @@ public:
|
|||
|
||||
protected:
|
||||
std::string m_endpoint;
|
||||
HttpRequest m_http{std::chrono::seconds{5}};
|
||||
HttpRequest m_http{ std::chrono::seconds{ 5 } };
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -365,13 +365,13 @@ void ARM64XEmitter::FlushIcacheSection(u8* start, u8* end)
|
|||
// Exception generation
|
||||
static const u32 ExcEnc[][3] = {
|
||||
{ 0, 0, 1 }, // SVC
|
||||
{ 0, 0, 2 }, // HVC
|
||||
{ 0, 0, 3 }, // SMC
|
||||
{ 1, 0, 0 }, // BRK
|
||||
{ 2, 0, 0 }, // HLT
|
||||
{ 5, 0, 1 }, // DCPS1
|
||||
{ 5, 0, 2 }, // DCPS2
|
||||
{ 5, 0, 3 }, // DCPS3
|
||||
{ 0, 0, 2 }, // HVC
|
||||
{ 0, 0, 3 }, // SMC
|
||||
{ 1, 0, 0 }, // BRK
|
||||
{ 2, 0, 0 }, // HLT
|
||||
{ 5, 0, 1 }, // DCPS1
|
||||
{ 5, 0, 2 }, // DCPS2
|
||||
{ 5, 0, 3 }, // DCPS3
|
||||
};
|
||||
|
||||
// Arithmetic generation
|
||||
|
@ -383,19 +383,19 @@ static const u32 ArithEnc[] = {
|
|||
// Conditional Select
|
||||
static const u32 CondSelectEnc[][2] = {
|
||||
{ 0, 0 }, // CSEL
|
||||
{ 0, 1 }, // CSINC
|
||||
{ 1, 0 }, // CSINV
|
||||
{ 1, 1 }, // CSNEG
|
||||
{ 0, 1 }, // CSINC
|
||||
{ 1, 0 }, // CSINV
|
||||
{ 1, 1 }, // CSNEG
|
||||
};
|
||||
|
||||
// Data-Processing (1 source)
|
||||
static const u32 Data1SrcEnc[][2] = {
|
||||
{ 0, 0 }, // RBIT
|
||||
{ 0, 1 }, // REV16
|
||||
{ 0, 2 }, // REV32
|
||||
{ 0, 3 }, // REV64
|
||||
{ 0, 4 }, // CLZ
|
||||
{ 0, 5 }, // CLS
|
||||
{ 0, 1 }, // REV16
|
||||
{ 0, 2 }, // REV32
|
||||
{ 0, 3 }, // REV64
|
||||
{ 0, 4 }, // CLZ
|
||||
{ 0, 5 }, // CLS
|
||||
};
|
||||
|
||||
// Data-Processing (2 source)
|
||||
|
@ -419,61 +419,61 @@ static const u32 Data2SrcEnc[] = {
|
|||
// Data-Processing (3 source)
|
||||
static const u32 Data3SrcEnc[][2] = {
|
||||
{ 0, 0 }, // MADD
|
||||
{ 0, 1 }, // MSUB
|
||||
{ 1, 0 }, // SMADDL (64Bit Only)
|
||||
{ 1, 1 }, // SMSUBL (64Bit Only)
|
||||
{ 2, 0 }, // SMULH (64Bit Only)
|
||||
{ 5, 0 }, // UMADDL (64Bit Only)
|
||||
{ 5, 1 }, // UMSUBL (64Bit Only)
|
||||
{ 6, 0 }, // UMULH (64Bit Only)
|
||||
{ 0, 1 }, // MSUB
|
||||
{ 1, 0 }, // SMADDL (64Bit Only)
|
||||
{ 1, 1 }, // SMSUBL (64Bit Only)
|
||||
{ 2, 0 }, // SMULH (64Bit Only)
|
||||
{ 5, 0 }, // UMADDL (64Bit Only)
|
||||
{ 5, 1 }, // UMSUBL (64Bit Only)
|
||||
{ 6, 0 }, // UMULH (64Bit Only)
|
||||
};
|
||||
|
||||
// Logical (shifted register)
|
||||
static const u32 LogicalEnc[][2] = {
|
||||
{ 0, 0 }, // AND
|
||||
{ 0, 1 }, // BIC
|
||||
{ 1, 0 }, // OOR
|
||||
{ 1, 1 }, // ORN
|
||||
{ 2, 0 }, // EOR
|
||||
{ 2, 1 }, // EON
|
||||
{ 3, 0 }, // ANDS
|
||||
{ 3, 1 }, // BICS
|
||||
{ 0, 1 }, // BIC
|
||||
{ 1, 0 }, // OOR
|
||||
{ 1, 1 }, // ORN
|
||||
{ 2, 0 }, // EOR
|
||||
{ 2, 1 }, // EON
|
||||
{ 3, 0 }, // ANDS
|
||||
{ 3, 1 }, // BICS
|
||||
};
|
||||
|
||||
// Load/Store Exclusive
|
||||
static const u32 LoadStoreExcEnc[][5] = {
|
||||
{ 0, 0, 0, 0, 0 }, // STXRB
|
||||
{ 0, 0, 0, 0, 1 }, // STLXRB
|
||||
{ 0, 0, 1, 0, 0 }, // LDXRB
|
||||
{ 0, 0, 1, 0, 1 }, // LDAXRB
|
||||
{ 0, 1, 0, 0, 1 }, // STLRB
|
||||
{ 0, 1, 1, 0, 1 }, // LDARB
|
||||
{ 1, 0, 0, 0, 0 }, // STXRH
|
||||
{ 1, 0, 0, 0, 1 }, // STLXRH
|
||||
{ 1, 0, 1, 0, 0 }, // LDXRH
|
||||
{ 1, 0, 1, 0, 1 }, // LDAXRH
|
||||
{ 1, 1, 0, 0, 1 }, // STLRH
|
||||
{ 1, 1, 1, 0, 1 }, // LDARH
|
||||
{ 2, 0, 0, 0, 0 }, // STXR
|
||||
{ 3, 0, 0, 0, 0 }, // (64bit) STXR
|
||||
{ 2, 0, 0, 0, 1 }, // STLXR
|
||||
{ 3, 0, 0, 0, 1 }, // (64bit) STLXR
|
||||
{ 2, 0, 0, 1, 0 }, // STXP
|
||||
{ 3, 0, 0, 1, 0 }, // (64bit) STXP
|
||||
{ 2, 0, 0, 1, 1 }, // STLXP
|
||||
{ 3, 0, 0, 1, 1 }, // (64bit) STLXP
|
||||
{ 2, 0, 1, 0, 0 }, // LDXR
|
||||
{ 3, 0, 1, 0, 0 }, // (64bit) LDXR
|
||||
{ 2, 0, 1, 0, 1 }, // LDAXR
|
||||
{ 3, 0, 1, 0, 1 }, // (64bit) LDAXR
|
||||
{ 2, 0, 1, 1, 0 }, // LDXP
|
||||
{ 3, 0, 1, 1, 0 }, // (64bit) LDXP
|
||||
{ 2, 0, 1, 1, 1 }, // LDAXP
|
||||
{ 3, 0, 1, 1, 1 }, // (64bit) LDAXP
|
||||
{ 2, 1, 0, 0, 1 }, // STLR
|
||||
{ 3, 1, 0, 0, 1 }, // (64bit) STLR
|
||||
{ 2, 1, 1, 0, 1 }, // LDAR
|
||||
{ 3, 1, 1, 0, 1 }, // (64bit) LDAR
|
||||
{ 0, 0, 0, 0, 1 }, // STLXRB
|
||||
{ 0, 0, 1, 0, 0 }, // LDXRB
|
||||
{ 0, 0, 1, 0, 1 }, // LDAXRB
|
||||
{ 0, 1, 0, 0, 1 }, // STLRB
|
||||
{ 0, 1, 1, 0, 1 }, // LDARB
|
||||
{ 1, 0, 0, 0, 0 }, // STXRH
|
||||
{ 1, 0, 0, 0, 1 }, // STLXRH
|
||||
{ 1, 0, 1, 0, 0 }, // LDXRH
|
||||
{ 1, 0, 1, 0, 1 }, // LDAXRH
|
||||
{ 1, 1, 0, 0, 1 }, // STLRH
|
||||
{ 1, 1, 1, 0, 1 }, // LDARH
|
||||
{ 2, 0, 0, 0, 0 }, // STXR
|
||||
{ 3, 0, 0, 0, 0 }, // (64bit) STXR
|
||||
{ 2, 0, 0, 0, 1 }, // STLXR
|
||||
{ 3, 0, 0, 0, 1 }, // (64bit) STLXR
|
||||
{ 2, 0, 0, 1, 0 }, // STXP
|
||||
{ 3, 0, 0, 1, 0 }, // (64bit) STXP
|
||||
{ 2, 0, 0, 1, 1 }, // STLXP
|
||||
{ 3, 0, 0, 1, 1 }, // (64bit) STLXP
|
||||
{ 2, 0, 1, 0, 0 }, // LDXR
|
||||
{ 3, 0, 1, 0, 0 }, // (64bit) LDXR
|
||||
{ 2, 0, 1, 0, 1 }, // LDAXR
|
||||
{ 3, 0, 1, 0, 1 }, // (64bit) LDAXR
|
||||
{ 2, 0, 1, 1, 0 }, // LDXP
|
||||
{ 3, 0, 1, 1, 0 }, // (64bit) LDXP
|
||||
{ 2, 0, 1, 1, 1 }, // LDAXP
|
||||
{ 3, 0, 1, 1, 1 }, // (64bit) LDAXP
|
||||
{ 2, 1, 0, 0, 1 }, // STLR
|
||||
{ 3, 1, 0, 0, 1 }, // (64bit) STLR
|
||||
{ 2, 1, 1, 0, 1 }, // LDAR
|
||||
{ 3, 1, 1, 0, 1 }, // (64bit) LDAR
|
||||
};
|
||||
|
||||
void ARM64XEmitter::EncodeCompareBranchInst(u32 op, ARM64Reg Rt, const void* ptr)
|
||||
|
@ -2029,7 +2029,8 @@ void ARM64XEmitter::MOVI2R(ARM64Reg Rd, u64 imm, bool optimize)
|
|||
|
||||
u64 aligned_pc = (u64)GetCodePtr() & ~0xFFF;
|
||||
s64 aligned_offset = (s64)imm - (s64)aligned_pc;
|
||||
if (upload_part.Count() > 1 && std::abs(aligned_offset) < 0xFFFFFFFFLL)
|
||||
// The offset for ADR/ADRP is an s32, so make sure it can be represented in that
|
||||
if (upload_part.Count() > 1 && std::abs(aligned_offset) < 0x7FFFFFFFLL)
|
||||
{
|
||||
// Immediate we are loading is within 4GB of our aligned range
|
||||
// Most likely a address that we can load in one or two instructions
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
const char procfile[] = "/proc/cpuinfo";
|
||||
|
@ -22,7 +23,8 @@ static std::string GetCPUString()
|
|||
std::string cpu_string = "Unknown";
|
||||
|
||||
std::string line;
|
||||
std::ifstream file(procfile);
|
||||
std::ifstream file;
|
||||
File::OpenFStream(file, procfile, std::ios_base::in);
|
||||
|
||||
if (!file)
|
||||
return cpu_string;
|
||||
|
|
|
@ -124,15 +124,18 @@ public:
|
|||
// so that we can use this within unions
|
||||
constexpr BitField() = default;
|
||||
|
||||
// Visual Studio (as of VS2017) considers BitField to not be trivially
|
||||
// copyable if we delete this copy assignment operator.
|
||||
// https://developercommunity.visualstudio.com/content/problem/101208/c-compiler-is-overly-strict-regarding-whether-a-cl.html
|
||||
#ifndef _MSC_VER
|
||||
// We explicitly delete the copy assignment operator here, because the
|
||||
// default copy assignment would copy the full storage value, rather than
|
||||
// just the bits relevant to this particular bit field.
|
||||
// Ideally, we would just implement the copy assignment to copy only the
|
||||
// relevant bits, but this requires compiler support for unrestricted
|
||||
// unions.
|
||||
// TODO: Implement this operator properly once all target compilers
|
||||
// support unrestricted unions.
|
||||
// relevant bits, but we're prevented from doing that because the savestate
|
||||
// code expects that this class is trivially copyable.
|
||||
BitField& operator=(const BitField&) = delete;
|
||||
#endif
|
||||
|
||||
__forceinline BitField& operator=(T val)
|
||||
{
|
||||
|
|
|
@ -4,8 +4,8 @@ set(SRCS
|
|||
ColorUtil.cpp
|
||||
CommonFuncs.cpp
|
||||
Config/Config.cpp
|
||||
Config/ConfigInfo.cpp
|
||||
Config/Layer.cpp
|
||||
Config/Section.cpp
|
||||
Crypto/AES.cpp
|
||||
Crypto/bn.cpp
|
||||
Crypto/ec.cpp
|
||||
|
|