@@ -0,0 +1,98 @@
package org.dolphinemu.dolphinemu.dialogs;

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.widget.Toast;

import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;

import java.io.File;

public class GameSettingsDialog extends DialogFragment
{
public static final String TAG = "GameSettingsDialog";
public static final String ARG_GAMEID = "game_id";
public static final String ARG_PLATFORM = "platform";

public static GameSettingsDialog newInstance(String gameId, int platform)
{
GameSettingsDialog fragment = new GameSettingsDialog();

Bundle arguments = new Bundle();
arguments.putString(ARG_GAMEID, gameId);
arguments.putInt(ARG_PLATFORM, platform);
fragment.setArguments(arguments);

return fragment;
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());

String gameId = getArguments().getString(ARG_GAMEID);
int platform = getArguments().getInt(ARG_PLATFORM);

builder.setTitle(getActivity().getString(R.string.preferences_game_settings))
.setItems(platform == Platform.GAMECUBE.toInt() ?
R.array.gameSettingsMenusGC :
R.array.gameSettingsMenusWii, (dialog, which) ->
{
switch (which)
{
case 0:
SettingsActivity.launch(getContext(), MenuTag.CONFIG, gameId);
break;
case 1:
SettingsActivity.launch(getContext(), MenuTag.GRAPHICS, gameId);
break;
case 2:
SettingsActivity.launch(getContext(), MenuTag.GCPAD_TYPE, gameId);
break;
case 3:
// Clear option for GC, Wii controls for else
if (platform == Platform.GAMECUBE.toInt())
clearGameSettings(gameId);
else
SettingsActivity.launch(getActivity(), MenuTag.WIIMOTE, gameId);
break;
case 4:
clearGameSettings(gameId);
break;
}
});
return builder.create();
}


private void clearGameSettings(String gameId)
{
String path =
DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini";
File gameSettingsFile = new File(path);
if (gameSettingsFile.exists())
{
if (gameSettingsFile.delete())
{
Toast.makeText(getContext(), "Cleared settings for " + gameId, Toast.LENGTH_SHORT)
.show();
}
else
{
Toast.makeText(getContext(), "Unable to clear settings for " + gameId,
Toast.LENGTH_SHORT).show();
}
}
else
{
Toast.makeText(getContext(), "No game settings to delete", Toast.LENGTH_SHORT).show();
}
}
}
@@ -26,6 +26,8 @@
public static final String SECTION_WIIMOTE = "Wiimote";

public static final String SECTION_BINDINGS = "Android";
public static final String SECTION_CONTROLS = "Controls";
public static final String SECTION_PROFILE = "Profile";

public static final String SECTION_ANALYTICS = "Analytics";

@@ -134,6 +136,11 @@ private void loadCustomGameSettings(String gameId, SettingsActivityView view)
mergeSections(SettingsFile.readCustomGameSettings(gameId, view));
}

public void loadWiimoteProfile(String gameId, String padId)
{
mergeSections(SettingsFile.readWiimoteProfile(gameId, padId));
}

private void mergeSections(HashMap<String, SettingSection> updatedSections)
{
for (Map.Entry<String, SettingSection> entry : updatedSections.entrySet())
@@ -182,6 +189,5 @@ public void saveSettings(SettingsActivityView view)
view.showToastMessage("Saved settings for " + gameId);
SettingsFile.saveCustomGameSettings(gameId, sections);
}

}
}
@@ -11,9 +11,13 @@

public class InputBindingSetting extends SettingsItem
{
public InputBindingSetting(String key, String section, int titleId, Setting setting)
private String gameId;

public InputBindingSetting(String key, String section, int titleId, Setting setting,
String gameId)
{
super(key, section, setting, titleId, 0);
this.gameId = gameId;
}

public String getValue()
@@ -98,4 +102,9 @@ public int getType()
{
return TYPE_INPUT_BINDING;
}

public String getGameId()
{
return gameId;
}
}
@@ -15,9 +15,10 @@
public class RumbleBindingSetting extends InputBindingSetting
{

public RumbleBindingSetting(String key, String section, int titleId, Setting setting)
public RumbleBindingSetting(String key, String section, int titleId, Setting setting,
String gameId)
{
super(key, section, titleId, setting);
super(key, section, titleId, setting, gameId);
}

@Override
@@ -1,7 +1,7 @@
package org.dolphinemu.dolphinemu.features.settings.model.view;

import org.dolphinemu.dolphinemu.features.settings.model.Setting;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
import org.dolphinemu.dolphinemu.features.settings.model.Setting;

/**
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -39,6 +39,7 @@
* @param nameId Resource ID for a text string to be displayed as this setting's name.
* @param descriptionId Resource ID for a text string to be displayed as this setting's description.
*/

public SettingsItem(String key, String section, Setting setting, int nameId, int descriptionId)
{
mKey = key;
@@ -292,7 +292,14 @@ public void onClick(DialogInterface dialog, int which)
else if (scSetting.getKey().equals(SettingsFile.KEY_WIIMOTE_EXTENSION))
{
putExtensionSetting(which, Character.getNumericValue(
scSetting.getSection().charAt(scSetting.getSection().length() - 1)));
scSetting.getSection().charAt(scSetting.getSection().length() - 1)), false);
}
else if (scSetting.getKey().contains(SettingsFile.KEY_WIIMOTE_EXTENSION) &&
scSetting.getSection().equals(Settings.SECTION_CONTROLS))
{
putExtensionSetting(which, Character
.getNumericValue(scSetting.getKey().charAt(scSetting.getKey().length() - 1)),
true);
}
}

@@ -443,11 +450,22 @@ private void putVideoBackendSetting(int which)
mView.putSetting(gfxBackend);
}

private void putExtensionSetting(int which, int wiimoteNumber)
private void putExtensionSetting(int which, int wiimoteNumber, boolean isGame)
{
StringSetting extension = new StringSetting(SettingsFile.KEY_WIIMOTE_EXTENSION,
Settings.SECTION_WIIMOTE + wiimoteNumber,
mContext.getResources().getStringArray(R.array.wiimoteExtensionsEntries)[which]);
mView.putSetting(extension);
if (!isGame)
{
StringSetting extension = new StringSetting(SettingsFile.KEY_WIIMOTE_EXTENSION,
Settings.SECTION_WIIMOTE + wiimoteNumber,
mContext.getResources().getStringArray(R.array.wiimoteExtensionsEntries)[which]);
mView.putSetting(extension);
}
else
{
StringSetting extension =
new StringSetting(SettingsFile.KEY_WIIMOTE_EXTENSION + wiimoteNumber,
Settings.SECTION_CONTROLS, mContext.getResources()
.getStringArray(R.array.wiimoteExtensionsEntries)[which]);
mView.putSetting(extension);
}
}
}

Large diffs are not rendered by default.

@@ -41,8 +41,9 @@ public void bind(SettingsItem item)

mItem = (InputBindingSetting) item;

mTextSettingName.setText(item.getNameId());
mTextSettingDescription.setText(sharedPreferences.getString(mItem.getKey(), ""));
mTextSettingName.setText(mItem.getNameId());
mTextSettingDescription
.setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), ""));
}

@Override
@@ -90,6 +90,7 @@
public static final String KEY_WAIT_FOR_SHADERS = "WaitForShadersBeforeStarting";

public static final String KEY_GCPAD_TYPE = "SIDevice";
public static final String KEY_GCPAD_G_TYPE = "PadType";

public static final String KEY_GCBIND_A = "InputA_";
public static final String KEY_GCBIND_B = "InputB_";
@@ -120,6 +121,10 @@
public static final String KEY_WIIMOTE_TYPE = "Source";
public static final String KEY_WIIMOTE_EXTENSION = "Extension";

// Controller keys for game specific settings
public static final String KEY_WIIMOTE_G_TYPE = "WiimoteSource";
public static final String KEY_WIIMOTE_PROFILE = "WiimoteProfile";

public static final String KEY_WIIBIND_A = "WiimoteA_";
public static final String KEY_WIIBIND_B = "WiimoteB_";
public static final String KEY_WIIBIND_1 = "Wiimote1_";
@@ -370,6 +375,12 @@ public static HashMap<String, SettingSection> readGenericGameSettingsForAllRegio
return readFile(getGenericGameSettingsForAllRegions(gameId), true, view);
}

public static HashMap<String, SettingSection> readWiimoteProfile(final String gameId,
final String padId)
{
String profile = gameId + "_Wii" + padId;
return readFile(getWiiProfile(profile, padId), true, null);
}

/**
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
@@ -432,14 +443,86 @@ public static void saveCustomGameSettings(final String gameId,
HashMap<String, Setting> settings = section.getSettings();
Set<String> sortedKeySet = new TreeSet<>(settings.keySet());

// Profile options(wii extension) are not saved, only used to properly display values
if (sectionKey.contains(Settings.SECTION_PROFILE))
{
continue;
}
else
{
NativeLibrary.LoadGameIniFile(gameId);
}
for (String settingKey : sortedKeySet)
{
Setting setting = settings.get(settingKey);
NativeLibrary
.SetUserSetting(gameId, mapSectionNameFromIni(section.getName()), setting.getKey(),
setting.getValueAsString());
// Special case. Extension gets saved into a controller profile
if (settingKey.contains(SettingsFile.KEY_WIIMOTE_EXTENSION))
{
String padId =
setting.getKey()
.substring(setting.getKey().length() - 1, setting.getKey().length());

saveCustomWiimoteSetting(gameId, KEY_WIIMOTE_EXTENSION, setting.getValueAsString(),
padId);
}
else
{
NativeLibrary.SetUserSetting(gameId, mapSectionNameFromIni(section.getName()),
setting.getKey(), setting.getValueAsString());
}
}
NativeLibrary.SaveGameIniFile(gameId);
}
}

public static void saveSingleCustomSetting(final String gameId, final String section,
final String key,
final String value)
{
NativeLibrary.LoadGameIniFile(gameId);
NativeLibrary.SetUserSetting(gameId, section,
key, value);
NativeLibrary.SaveGameIniFile(gameId);
}

/**
* Saves the wiimote setting in a profile and enables that profile.
*
* @param gameId
* @param key
* @param value
* @param padId
*/
public static void saveCustomWiimoteSetting(final String gameId, final String key,
final String value,
final String padId)
{
String profile = gameId + "_Wii" + padId;

String wiiConfigPath =
DirectoryInitialization.getUserDirectory() + "/Config/Profiles/Wiimote/" +
profile + ".ini";
File wiiProfile = new File(wiiConfigPath);
// If it doesn't exist, create it
if (!wiiProfile.exists())
{
String defautlWiiProfilePath =
DirectoryInitialization.getUserDirectory() +
"/Config/Profiles/Wiimote/WiimoteProfile.ini";
DirectoryInitialization.copyFile(defautlWiiProfilePath, wiiConfigPath);

NativeLibrary.SetProfileSetting(profile, Settings.SECTION_PROFILE, "Device",
"Android/" + (Integer.valueOf(padId) + 4) + "/Touchscreen");
}

NativeLibrary.SetProfileSetting(profile, Settings.SECTION_PROFILE, key,
value);

// Enable the profile
NativeLibrary.LoadGameIniFile(gameId);
NativeLibrary.SetUserSetting(gameId, Settings.SECTION_CONTROLS,
KEY_WIIMOTE_PROFILE + (Integer.valueOf(padId) + 1), profile);
NativeLibrary.SaveGameIniFile(gameId);
}

private static String mapSectionNameFromIni(String generalSectionName)
@@ -487,10 +570,20 @@ private static File getGenericGameSettingsFile(String gameId)

private static File getCustomGameSettingsFile(String gameId)
{

return new File(
DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini");
}

private static File getWiiProfile(String profile, String padId)
{
String wiiConfigPath =
DirectoryInitialization.getUserDirectory() + "/Config/Profiles/Wiimote/" +
profile + ".ini";

return new File(wiiConfigPath);
}

private static SettingSection sectionFromLine(String line, boolean isCustomGame)
{
String sectionName = line.substring(1, line.length() - 1);
@@ -626,4 +719,10 @@ private static String settingAsString(Setting setting)
{
return setting.getKey() + " = " + setting.getValueAsString();
}

private static String customWiimoteExtSettingAsString(Setting setting)
{
return setting.getKey().substring(0, setting.getKey().length() - 1) + " = " +
setting.getValueAsString();
}
}
@@ -16,6 +16,7 @@
import org.dolphinemu.dolphinemu.NativeLibrary;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -139,8 +140,14 @@ private static void initializeExternalStorage(Context context)
//
// TODO: Redo the Android controller system so that we don't have to extract these INIs.
String configDirectory = NativeLibrary.GetUserDirectory() + File.separator + "Config";
String profileDirectory =
NativeLibrary.GetUserDirectory() + File.separator + "Config/Profiles/Wiimote/";
createWiimoteProfileDirectory(profileDirectory);

copyAsset("GCPadNew.ini", new File(configDirectory, "GCPadNew.ini"), true, context);
copyAsset("WiimoteNew.ini", new File(configDirectory, "WiimoteNew.ini"), false, context);
copyAsset("WiimoteProfile.ini", new File(profileDirectory, "WiimoteProfile.ini"), true,
context);
}

private static void deleteDirectoryRecursively(File file)
@@ -247,6 +254,20 @@ private static void copyAssetFolder(String assetFolder, File outputFolder, Boole
}
}

public static void copyFile(String from, String to)
{
try
{
InputStream in = new FileInputStream(from);
OutputStream out = new FileOutputStream(to);
copyFile(in, out);
}
catch (IOException e)
{

}
}

private static void copyFile(InputStream in, OutputStream out) throws IOException
{
byte[] buffer = new byte[1024];
@@ -258,6 +279,15 @@ private static void copyFile(InputStream in, OutputStream out) throws IOExceptio
}
}

private static void createWiimoteProfileDirectory(String directory)
{
File wiiPath = new File(directory);
if (!wiiPath.isDirectory())
{
wiiPath.mkdirs();
}
}

private static native void CreateUserDirectories();

private static native void SetSysDirectory(String path);
@@ -306,9 +306,17 @@
<item>Right Stick</item>
</string-array>

<string-array name="gameSettingsMenus">
<string-array name="gameSettingsMenusGC">
<item>Core Settings</item>
<item>GFX Settings</item>
<item>GameCube Controller Settings</item>
<item>Clear Game Settings</item>
</string-array>
<string-array name="gameSettingsMenusWii">
<item>Core Settings</item>
<item>GFX Settings</item>
<item>GameCube Controller Settings</item>
<item>Wii Controller Settings</item>
<item>Clear Game Settings</item>
</string-array>
</resources>
@@ -335,7 +335,7 @@ static void AddBind(const std::string& dev, sBind* bind)
m_controllers[dev]->AddBind(bind);
}

void Init()
void Init(const std::string& gameId)
{
// Initialize our touchScreenKey buttons
for (int a = 0; a < 8; ++a)
@@ -592,6 +592,40 @@ void Init()
new sBind(padID, configTypes[a], type, bindnum, modifier == '-' ? -1.0f : 1.0f));
}
}

ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + std::string(gameId + ".ini"));
for (u32 a = 0; a < configStrings.size(); ++a)
{
for (int padID = 0; padID < 8; ++padID)
{
std::ostringstream config;
config << configStrings[a] << "_" << padID;
BindType type;
int bindnum;
char dev[128];
bool hasbind = false;
char modifier = '+';
std::string value;
ini.GetOrCreateSection("Android")->Get(config.str(), &value, "None");
if (value == "None")
continue;
if (std::string::npos != value.find("Axis"))
{
hasbind = true;
type = BIND_AXIS;
sscanf(value.c_str(), "Device '%127[^\']'-Axis %d%c", dev, &bindnum, &modifier);
}
else if (std::string::npos != value.find("Button"))
{
hasbind = true;
type = BIND_BUTTON;
sscanf(value.c_str(), "Device '%127[^\']'-Button %d", dev, &bindnum);
}
if (hasbind)
AddBind(std::string(dev),
new sBind(padID, configTypes[a], type, bindnum, modifier == '-' ? -1.0f : 1.0f));
}
}
}

bool GetButtonPressed(int padID, ButtonType button)
@@ -250,7 +250,7 @@ class InputDevice
float AxisValue(int padID, ButtonType axis);
};

void Init();
void Init(const std::string&);
bool GetButtonPressed(int padID, ButtonType button);
float GetAxisValue(int padID, ButtonType axis);
bool GamepadEvent(const std::string& dev, int button, int action);
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.

#include <EGL/egl.h>
#include <UICommon/GameFile.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <cinttypes>
@@ -60,6 +61,7 @@ namespace
static constexpr char DOLPHIN_TAG[] = "DolphinEmuNative";

ANativeWindow* s_surf;
IniFile s_ini;

// The Core only supports using a single Host thread.
// If multiple threads want to call host functions then they need to queue
@@ -250,8 +252,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfiling
jboolean enable);
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfileResults(JNIEnv* env, jobject obj);

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Z(
JNIEnv* env, jobject obj, jstring jFile, jboolean jfirstOpen);

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);
@@ -358,16 +362,50 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetUserSe
return ToJString(env, value.c_str());
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadGameIniFile(JNIEnv* env,
jobject obj,
jstring jGameID)
{
std::string gameid = GetJString(env, jGameID);
s_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + gameid + ".ini");
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveGameIniFile(JNIEnv* env,
jobject obj,
jstring jGameID)
{
std::string gameid = GetJString(env, jGameID);
s_ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + gameid + ".ini");
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserSetting(
JNIEnv* env, jobject obj, jstring jGameID, jstring jSection, jstring jKey, jstring jValue)
{
IniFile ini;
std::string gameid = GetJString(env, jGameID);
std::string section = GetJString(env, jSection);
std::string key = GetJString(env, jKey);
std::string val = GetJString(env, jValue);

ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + gameid + ".ini");
if (val != "-1")
{
s_ini.GetOrCreateSection(section)->Set(key, val);
}
else
{
s_ini.GetOrCreateSection(section)->Delete(key);
}
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfileSetting(
JNIEnv* env, jobject obj, jstring jProfile, jstring jSection, jstring jKey, jstring jValue)
{
IniFile ini;
std::string profile = GetJString(env, jProfile);
std::string section = GetJString(env, jSection);
std::string key = GetJString(env, jKey);
std::string val = GetJString(env, jValue);

ini.Load(File::GetUserPath(D_CONFIG_IDX) + "Profiles/Wiimote/" + profile + ".ini");

if (val != "-1")
{
@@ -378,7 +416,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserSetti
ini.GetOrCreateSection(section)->Delete(key);
}

ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + gameid + ".ini");
ini.Save(File::GetUserPath(D_CONFIG_IDX) + "Profiles/Wiimote/" + profile + ".ini");
}

JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetConfig(
@@ -539,7 +577,6 @@ static void Run(const std::string& path, bool first_open,
__android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", path.c_str());

// Install our callbacks
OSD::AddCallback(OSD::CallbackType::Initialization, ButtonManager::Init);
OSD::AddCallback(OSD::CallbackType::Shutdown, ButtonManager::Shutdown);

RegisterMsgAlertHandler(&MsgAlert);
@@ -563,6 +600,7 @@ static void Run(const std::string& path, bool first_open,
WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf);
if (BootManager::BootCore(std::move(boot), wsi))
{
ButtonManager::Init(SConfig::GetInstance().GetGameID());
static constexpr int TIMEOUT = 10000;
static constexpr int WAIT_STEP = 25;
int time_waited = 0;