Merge latest core changes

This commit is contained in:
Rodolfo Bogado 2018-02-20 23:34:54 -03:00
parent e64bcb86ba
commit 6b7c04b4da
341 changed files with 10091 additions and 7170 deletions

View file

@ -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
#

View file

@ -0,0 +1,4 @@
# GP3E78 - The Polar Express
[Video_Hacks]
EFBEmulateFormatChanges = True

View file

@ -0,0 +1,4 @@
# SUUE78 - uDraw Studio - Instant Artist
[Video_Settings]
SafeTextureCacheColorSamples = 0

View file

@ -0,0 +1,4 @@
# SUWE78 - uDraw Studio
[Video_Settings]
SafeTextureCacheColorSamples = 0

Binary file not shown.

View 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)

View 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).
macOSs implementation of hid_write detected this specific error (IOHIDDeviceSetReport returns kUSBHostReturnPipeStalled) but didnt set errno so the check failed.
This add errno assignment to failure cases of macOSs 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)

View file

@ -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;

View file

@ -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() {

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="application_id">org.dolphinemu.dolphinemu.debug</string>
</resources>

View file

@ -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>

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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.

View file

@ -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 ? '-' : '+';
}
}
}

View file

@ -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);
}
}

View 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
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -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)

View file

@ -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);
});
}
}

View file

@ -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)
{

View 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...");

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}

View 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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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
{

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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.

View file

@ -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();
}
}
}

View file

@ -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>

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<transitionSet>
<changeImageTransform/>
</transitionSet>

View file

@ -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>

View file

@ -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>

View file

@ -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>
<!--&lt;!&ndash; Setting a divider is entirely optional &ndash;&gt;-->
<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>

View file

@ -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})

View file

@ -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

View file

@ -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()

View file

@ -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:

View file

@ -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;

View file

@ -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,
&params, 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, &params, 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();
}

View file

@ -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:

View file

@ -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;

View file

@ -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);

View file

@ -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()
{
}

View file

@ -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; }

View file

@ -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)

View file

@ -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();

View file

@ -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)
{

View file

@ -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;

View file

@ -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()
{

View file

@ -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);

View file

@ -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; }
};

View file

@ -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()

View file

@ -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(); }

View file

@ -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()

View file

@ -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(); }

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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)
{

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more