@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0-or-later

package org.dolphinemu.dolphinemu.features.infinitybase.ui

import android.app.AlertDialog
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.activities.EmulationActivity
import org.dolphinemu.dolphinemu.databinding.DialogCreateInfinityFigureBinding
import org.dolphinemu.dolphinemu.databinding.ListItemNfcFigureSlotBinding
import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig
import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig.removeFigure

class FigureSlotAdapter(
private val figures: List<FigureSlot>,
private val activity: EmulationActivity
) : RecyclerView.Adapter<FigureSlotAdapter.ViewHolder>(),
OnItemClickListener {

class ViewHolder(var binding: ListItemNfcFigureSlotBinding) :
RecyclerView.ViewHolder(binding.getRoot())

private lateinit var binding: DialogCreateInfinityFigureBinding

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ListItemNfcFigureSlotBinding.inflate(inflater, parent, false)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val figure = figures[position]
holder.binding.textFigureName.text = figure.label

holder.binding.buttonClearFigure.setOnClickListener {
removeFigure(figure.position)
activity.clearInfinityFigure(position)
}

holder.binding.buttonLoadFigure.setOnClickListener {
val loadFigure = Intent(Intent.ACTION_OPEN_DOCUMENT)
loadFigure.addCategory(Intent.CATEGORY_OPENABLE)
loadFigure.type = "*/*"
activity.setInfinityFigureData(0, "", figure.position, position)
activity.startActivityForResult(
loadFigure,
EmulationActivity.REQUEST_INFINITY_FIGURE_FILE
)
}

val inflater = LayoutInflater.from(activity)
binding = DialogCreateInfinityFigureBinding.inflate(inflater)

binding.infinityDropdown.onItemClickListener = this

holder.binding.buttonCreateFigure.setOnClickListener {
var validFigures = InfinityConfig.REVERSE_LIST_FIGURES
// Filter adapter list by position, either Hexagon Pieces, Characters or Abilities
validFigures = when (figure.position) {
0 -> {
// Hexagon Pieces
validFigures.filter { (_, value) -> value in 2000000..2999999 || value in 4000000..4999999 }
}

1, 2 -> {
// Characters
validFigures.filter { (_, value) -> value in 1000000..1999999 }
}

else -> {
// Abilities
validFigures.filter { (_, value) -> value in 3000000..3999999 }
}
}
val figureListKeys = validFigures.keys.toMutableList()
figureListKeys.sort()
val figureNames: ArrayList<String> = ArrayList(figureListKeys)
binding.infinityDropdown.setAdapter(
ArrayAdapter(
activity, R.layout.support_simple_spinner_dropdown_item,
figureNames
)
)

if (binding.getRoot().parent != null) {
(binding.getRoot().parent as ViewGroup).removeAllViews()
}
val createDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.create_figure_title)
.setView(binding.getRoot())
.setPositiveButton(R.string.create_figure, null)
.setNegativeButton(R.string.cancel, null)
.show()
createDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (binding.infinityNum.text.toString().isNotBlank()) {
val createFigure = Intent(Intent.ACTION_CREATE_DOCUMENT)
createFigure.addCategory(Intent.CATEGORY_OPENABLE)
createFigure.type = "*/*"
val num = binding.infinityNum.text.toString().toLong()
val name = InfinityConfig.LIST_FIGURES[num]
if (name != null) {
createFigure.putExtra(
Intent.EXTRA_TITLE,
"$name.bin"
)
activity.setInfinityFigureData(num, name, figure.position, position)
} else {
createFigure.putExtra(
Intent.EXTRA_TITLE,
"Unknown(Number: $num).bin"
)
activity.setInfinityFigureData(num, "Unknown", figure.position, position)
}
activity.startActivityForResult(
createFigure,
EmulationActivity.REQUEST_CREATE_INFINITY_FIGURE
)
createDialog.dismiss()
} else {
Toast.makeText(
activity, R.string.invalid_infinity_figure,
Toast.LENGTH_SHORT
).show()
}
}
}
}

override fun getItemCount(): Int {
return figures.size
}

override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val figureNumber = InfinityConfig.REVERSE_LIST_FIGURES[parent.getItemAtPosition(position)]
binding.infinityNum.setText(figureNumber.toString())
}
}
Expand Up @@ -217,6 +217,12 @@ enum class BooleanSetting(
"EmulateSkylanderPortal",
false
),
MAIN_EMULATE_INFINITY_BASE(
Settings.FILE_DOLPHIN,
Settings.SECTION_EMULATED_USB_DEVICES,
"EmulateInfinityBase",
false
),
MAIN_SHOW_GAME_TITLES(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_ANDROID,
Expand Down Expand Up @@ -719,7 +725,8 @@ enum class BooleanSetting(
MAIN_RAM_OVERRIDE_ENABLE,
MAIN_CUSTOM_RTC_ENABLE,
MAIN_DSP_JIT,
MAIN_EMULATE_SKYLANDER_PORTAL
MAIN_EMULATE_SKYLANDER_PORTAL,
MAIN_EMULATE_INFINITY_BASE
)
private val NOT_RUNTIME_EDITABLE: Set<BooleanSetting> =
HashSet(listOf(*NOT_RUNTIME_EDITABLE_ARRAY))
Expand Down
Expand Up @@ -115,13 +115,15 @@ class SettingsFragmentPresenter(
controllerNumber,
controllerType
)

MenuTag.WIIMOTE_1,
MenuTag.WIIMOTE_2,
MenuTag.WIIMOTE_3,
MenuTag.WIIMOTE_4 -> addWiimoteSubSettings(
sl,
controllerNumber
)

MenuTag.WIIMOTE_EXTENSION_1,
MenuTag.WIIMOTE_EXTENSION_2,
MenuTag.WIIMOTE_EXTENSION_3,
Expand All @@ -130,27 +132,31 @@ class SettingsFragmentPresenter(
controllerNumber,
controllerType
)

MenuTag.WIIMOTE_GENERAL_1,
MenuTag.WIIMOTE_GENERAL_2,
MenuTag.WIIMOTE_GENERAL_3,
MenuTag.WIIMOTE_GENERAL_4 -> addWiimoteGeneralSubSettings(
sl,
controllerNumber
)

MenuTag.WIIMOTE_MOTION_SIMULATION_1,
MenuTag.WIIMOTE_MOTION_SIMULATION_2,
MenuTag.WIIMOTE_MOTION_SIMULATION_3,
MenuTag.WIIMOTE_MOTION_SIMULATION_4 -> addWiimoteMotionSimulationSubSettings(
sl,
controllerNumber
)

MenuTag.WIIMOTE_MOTION_INPUT_1,
MenuTag.WIIMOTE_MOTION_INPUT_2,
MenuTag.WIIMOTE_MOTION_INPUT_3,
MenuTag.WIIMOTE_MOTION_INPUT_4 -> addWiimoteMotionInputSubSettings(
sl,
controllerNumber
)

else -> throw UnsupportedOperationException("Unimplemented menu")
}

Expand Down Expand Up @@ -454,10 +460,12 @@ class SettingsFragmentPresenter(
BooleanSetting.MAIN_DSP_HLE.setBoolean(settings, true)
BooleanSetting.MAIN_DSP_JIT.setBoolean(settings, true)
}

DSP_LLE_RECOMPILER -> {
BooleanSetting.MAIN_DSP_HLE.setBoolean(settings, false)
BooleanSetting.MAIN_DSP_JIT.setBoolean(settings, true)
}

DSP_LLE_INTERPRETER -> {
BooleanSetting.MAIN_DSP_HLE.setBoolean(settings, false)
BooleanSetting.MAIN_DSP_JIT.setBoolean(settings, false)
Expand Down Expand Up @@ -834,6 +842,14 @@ class SettingsFragmentPresenter(
0
)
)
sl.add(
SwitchSetting(
context,
BooleanSetting.MAIN_EMULATE_INFINITY_BASE,
R.string.emulate_infinity_base,
0
)
)
}

private fun addAdvancedSettings(sl: ArrayList<SettingsItem>) {
Expand All @@ -856,10 +872,12 @@ class SettingsFragmentPresenter(
BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.setBoolean(settings, false)
BooleanSetting.MAIN_SYNC_GPU.setBoolean(settings, false)
}

SYNC_GPU_ON_IDLE_SKIP -> {
BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.setBoolean(settings, true)
BooleanSetting.MAIN_SYNC_GPU.setBoolean(settings, false)
}

SYNC_GPU_ALWAYS -> {
BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.setBoolean(settings, true)
BooleanSetting.MAIN_SYNC_GPU.setBoolean(settings, true)
Expand Down Expand Up @@ -893,10 +911,12 @@ class SettingsFragmentPresenter(
emuCoresEntries = R.array.emuCoresEntriesX86_64
emuCoresValues = R.array.emuCoresValuesX86_64
}

4 -> {
emuCoresEntries = R.array.emuCoresEntriesARM64
emuCoresValues = R.array.emuCoresValuesARM64
}

else -> {
emuCoresEntries = R.array.emuCoresEntriesGeneric
emuCoresValues = R.array.emuCoresValuesGeneric
Expand Down Expand Up @@ -2246,6 +2266,7 @@ class SettingsFragmentPresenter(
setting.uiSuffix
)
)

NumericSetting.TYPE_BOOLEAN -> sl.add(
SwitchSetting(
InputMappingBooleanSetting(setting),
Expand Down
Expand Up @@ -16,7 +16,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.activities.EmulationActivity
import org.dolphinemu.dolphinemu.databinding.DialogCreateSkylanderBinding
import org.dolphinemu.dolphinemu.databinding.ListItemSkylanderSlotBinding
import org.dolphinemu.dolphinemu.databinding.ListItemNfcFigureSlotBinding
import org.dolphinemu.dolphinemu.features.skylanders.SkylanderConfig
import org.dolphinemu.dolphinemu.features.skylanders.SkylanderConfig.removeSkylander
import org.dolphinemu.dolphinemu.features.skylanders.model.SkylanderPair
Expand All @@ -25,27 +25,27 @@ class SkylanderSlotAdapter(
private val slots: List<SkylanderSlot>,
private val activity: EmulationActivity
) : RecyclerView.Adapter<SkylanderSlotAdapter.ViewHolder>(), OnItemClickListener {
class ViewHolder(var binding: ListItemSkylanderSlotBinding) :
class ViewHolder(var binding: ListItemNfcFigureSlotBinding) :
RecyclerView.ViewHolder(binding.getRoot())

private lateinit var binding: DialogCreateSkylanderBinding

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ListItemSkylanderSlotBinding.inflate(inflater, parent, false)
val binding = ListItemNfcFigureSlotBinding.inflate(inflater, parent, false)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val slot = slots[position]
holder.binding.textSkylanderName.text = slot.label
holder.binding.textFigureName.text = slot.label

holder.binding.buttonClearSkylander.setOnClickListener {
holder.binding.buttonClearFigure.setOnClickListener {
removeSkylander(slot.portalSlot)
activity.clearSkylander(slot.slotNum)
}

holder.binding.buttonLoadSkylander.setOnClickListener {
holder.binding.buttonLoadFigure.setOnClickListener {
val loadSkylander = Intent(Intent.ACTION_OPEN_DOCUMENT)
loadSkylander.addCategory(Intent.CATEGORY_OPENABLE)
loadSkylander.type = "*/*"
Expand All @@ -71,14 +71,14 @@ class SkylanderSlotAdapter(
)
binding.skylanderDropdown.onItemClickListener = this

holder.binding.buttonCreateSkylander.setOnClickListener {
holder.binding.buttonCreateFigure.setOnClickListener {
if (binding.getRoot().parent != null) {
(binding.getRoot().parent as ViewGroup).removeAllViews()
}
val createDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.create_skylander_title)
.setView(binding.getRoot())
.setPositiveButton(R.string.create_skylander, null)
.setPositiveButton(R.string.create_figure, null)
.setNegativeButton(R.string.cancel, null)
.show()
createDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
Expand Down
Expand Up @@ -61,6 +61,7 @@ public final class MenuFragment extends Fragment implements View.OnClickListener
buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT);
buttonsActionsMap.append(R.id.menu_settings, EmulationActivity.MENU_ACTION_SETTINGS);
buttonsActionsMap.append(R.id.menu_skylanders, EmulationActivity.MENU_ACTION_SKYLANDERS);
buttonsActionsMap.append(R.id.menu_infinitybase, EmulationActivity.MENU_ACTION_INFINITY_BASE);
}

private FragmentIngameMenuBinding mBinding;
Expand Down
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/root"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_medlarge">

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_infinity_dropdown"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/spacing_medlarge"
android:paddingHorizontal="@dimen/spacing_medlarge"
android:hint="@string/infinity_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">

<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/infinity_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dialog"
android:imeOptions="actionDone" />

</com.google.android.material.textfield.TextInputLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/layout_infinity_dropdown"
android:gravity="center_vertical"
android:baselineAligned="false">

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_infinity_num"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/infinity_number"
android:paddingHorizontal="@dimen/spacing_medlarge">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/infinity_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />

</com.google.android.material.textfield.TextInputLayout>

</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
Expand Up @@ -4,10 +4,10 @@
android:layout_height="wrap_content">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/skylanders_manager"
android:id="@+id/figure_manager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:scrollbars="vertical"
android:fadeScrollbars="false"/>
android:fadeScrollbars="false"
android:scrollbars="vertical" />
</androidx.appcompat.widget.LinearLayoutCompat>
Expand Up @@ -106,6 +106,11 @@
android:text="@string/emulate_skylander_portal"
style="@style/InGameMenuOption" />

<Button
android:id="@+id/menu_infinitybase"
android:text="@string/emulate_infinity_base"
style="@style/InGameMenuOption" />

</LinearLayout>

</ScrollView>
Expand Down
Expand Up @@ -8,51 +8,51 @@
android:padding="@dimen/spacing_medlarge">

<Button
android:id="@+id/button_create_skylander"
android:id="@+id/button_create_figure"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_toStartOf="@id/button_load_skylander"
android:contentDescription="@string/create_skylander"
android:tooltipText="@string/create_skylander"
android:layout_toStartOf="@id/button_load_figure"
android:contentDescription="@string/create_figure"
android:tooltipText="@string/create_figure"
app:icon="@drawable/ic_add" />

<Button
android:id="@+id/button_load_skylander"
android:id="@+id/button_load_figure"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_toStartOf="@+id/button_clear_skylander"
android:contentDescription="@string/load_skylander"
android:tooltipText="@string/load_skylander"
android:layout_toStartOf="@+id/button_clear_figure"
android:contentDescription="@string/load_figure"
android:tooltipText="@string/load_figure"
app:icon="@drawable/ic_load" />

<Button
android:id="@+id/button_clear_skylander"
android:id="@+id/button_clear_figure"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/spacing_large"
android:contentDescription="@string/remove_skylander"
android:tooltipText="@string/remove_skylander"
android:contentDescription="@string/remove_figure"
android:tooltipText="@string/remove_figure"
app:icon="@drawable/ic_clear" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_skylander_name"
android:id="@+id/text_figure_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginBottom="@dimen/spacing_medlarge"
android:layout_marginEnd="@dimen/spacing_large"
android:layout_marginStart="@dimen/spacing_large"
android:layout_toStartOf="@+id/button_create_skylander"
android:layout_toStartOf="@+id/button_create_figure"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnSurface"
android:textSize="16sp"
Expand Down
22 changes: 19 additions & 3 deletions Source/Android/app/src/main/res/values/strings.xml
Expand Up @@ -846,14 +846,30 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="emulated_usb_devices">Emulated USB Devices</string>
<string name="emulate_skylander_portal">Skylanders Portal</string>
<string name="skylanders_manager">Skylanders Manager</string>
<string name="load_skylander">Load</string>
<string name="remove_skylander">Remove</string>
<string name="create_skylander">Create</string>
<string name="create_skylander_title">Create Skylander</string>
<string name="skylander_label">Skylander</string>
<string name="skylander_slot">Slot %1$d</string>
<string name="skylander_id">ID</string>
<string name="skylander_variant">Variant</string>
<string name="invalid_skylander">Invalid Skylander Selection</string>

<string name="emulate_infinity_base">Infinity Base</string>
<string name="infinity_manager">Infinity Manager</string>
<string name="load_figure">Load</string>
<string name="remove_figure">Remove</string>
<string name="create_figure">Create</string>
<string name="create_figure_title">Create Figure</string>
<string name="infinity_label">Infinity Figure</string>
<string name="infinity_number">Figure Number</string>
<string name="invalid_infinity_figure">Invalid Figure Selection</string>
<string name="infinity_hexagon_label">Power Disc/Play Set</string>
<string name="infinity_p1_label">Player One</string>
<string name="infinity_p2_label">Player Two</string>
<string name="infinity_p1a1_label">P1 Ability One</string>
<string name="infinity_p2a1_label">P2 Ability One</string>
<string name="infinity_p1a2_label">P1 Ability Two</string>
<string name="infinity_p2a2_label">P2 Ability Two</string>
<string name="incompatible_figure_selected">Incompatible Figure Selected</string>
<string name="select_compatible_figure">Please select a compatible figure file</string>

</resources>
33 changes: 30 additions & 3 deletions Source/Android/jni/AndroidCommon/IDCache.cpp
Expand Up @@ -32,6 +32,10 @@ static jclass s_linked_hash_map_class;
static jmethodID s_linked_hash_map_init;
static jmethodID s_linked_hash_map_put;

static jclass s_hash_map_class;
static jmethodID s_hash_map_init;
static jmethodID s_hash_map_put;

static jclass s_ini_file_class;
static jfieldID s_ini_file_pointer;
static jclass s_ini_file_section_class;
Expand Down Expand Up @@ -221,6 +225,21 @@ jmethodID GetLinkedHashMapPut()
return s_linked_hash_map_put;
}

jclass GetHashMapClass()
{
return s_hash_map_class;
}

jmethodID GetHashMapInit()
{
return s_hash_map_init;
}

jmethodID GetHashMapPut()
{
return s_hash_map_put;
}

jclass GetIniFileClass()
{
return s_ini_file_class;
Expand Down Expand Up @@ -575,12 +594,19 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
ini_file_section_class, "<init>", "(Lorg/dolphinemu/dolphinemu/utils/IniFile;J)V");
env->DeleteLocalRef(ini_file_section_class);

const jclass map_class = env->FindClass("java/util/LinkedHashMap");
s_linked_hash_map_class = reinterpret_cast<jclass>(env->NewGlobalRef(map_class));
const jclass linked_hash_map_class = env->FindClass("java/util/LinkedHashMap");
s_linked_hash_map_class = reinterpret_cast<jclass>(env->NewGlobalRef(linked_hash_map_class));
s_linked_hash_map_init = env->GetMethodID(s_linked_hash_map_class, "<init>", "(I)V");
s_linked_hash_map_put = env->GetMethodID(
s_linked_hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
env->DeleteLocalRef(map_class);
env->DeleteLocalRef(linked_hash_map_class);

const jclass hash_map_class = env->FindClass("java/util/HashMap");
s_hash_map_class = reinterpret_cast<jclass>(env->NewGlobalRef(hash_map_class));
s_hash_map_init = env->GetMethodID(s_hash_map_class, "<init>", "(I)V");
s_hash_map_put = env->GetMethodID(s_hash_map_class, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
env->DeleteLocalRef(hash_map_class);

const jclass compress_cb_class =
env->FindClass("org/dolphinemu/dolphinemu/utils/CompressCallback");
Expand Down Expand Up @@ -741,6 +767,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
env->DeleteGlobalRef(s_game_file_cache_class);
env->DeleteGlobalRef(s_analytics_class);
env->DeleteGlobalRef(s_linked_hash_map_class);
env->DeleteGlobalRef(s_hash_map_class);
env->DeleteGlobalRef(s_ini_file_class);
env->DeleteGlobalRef(s_ini_file_section_class);
env->DeleteGlobalRef(s_compress_cb_class);
Expand Down
4 changes: 4 additions & 0 deletions Source/Android/jni/AndroidCommon/IDCache.h
Expand Up @@ -32,6 +32,10 @@ jclass GetLinkedHashMapClass();
jmethodID GetLinkedHashMapInit();
jmethodID GetLinkedHashMapPut();

jclass GetHashMapClass();
jmethodID GetHashMapInit();
jmethodID GetHashMapPut();

jclass GetIniFileClass();
jfieldID GetIniFilePointer();
jclass GetIniFileSectionClass();
Expand Down
1 change: 1 addition & 0 deletions Source/Android/jni/CMakeLists.txt
Expand Up @@ -10,6 +10,7 @@ add_library(main SHARED
GameList/GameFile.cpp
GameList/GameFile.h
GameList/GameFileCache.cpp
InfinityConfig.cpp
Input/Control.cpp
Input/Control.h
Input/ControlGroup.cpp
Expand Down
121 changes: 121 additions & 0 deletions Source/Android/jni/InfinityConfig.cpp
@@ -0,0 +1,121 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <jni.h>

#include <array>

#include "AndroidCommon/AndroidCommon.h"
#include "AndroidCommon/IDCache.h"
#include "Core/IOS/USB/Emulated/Infinity.h"
#include "Core/System.h"

extern "C" {

JNIEXPORT jobject JNICALL
Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_getFigureMap(JNIEnv* env,
jobject obj)
{
auto& system = Core::System::GetInstance();

jobject hash_map_obj = env->NewObject(IDCache::GetHashMapClass(), IDCache::GetHashMapInit(),
system.GetInfinityBase().GetFigureList().size());

jclass long_class = env->FindClass("java/lang/Long");
jmethodID long_init = env->GetMethodID(long_class, "<init>", "(J)V");

for (const auto& it : system.GetInfinityBase().GetFigureList())
{
const std::string& name = it.first;
jobject figure_number = env->NewObject(long_class, long_init, (jlong)it.second);
env->CallObjectMethod(hash_map_obj, IDCache::GetHashMapPut(), figure_number,
ToJString(env, name));
env->DeleteLocalRef(figure_number);
}

return hash_map_obj;
}

JNIEXPORT jobject JNICALL
Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_getInverseFigureMap(JNIEnv* env,
jobject obj)
{
auto& system = Core::System::GetInstance();

jobject hash_map_obj = env->NewObject(IDCache::GetHashMapClass(), IDCache::GetHashMapInit(),
system.GetInfinityBase().GetFigureList().size());

jclass long_class = env->FindClass("java/lang/Long");
jmethodID long_init = env->GetMethodID(long_class, "<init>", "(J)V");

for (const auto& it : system.GetInfinityBase().GetFigureList())
{
const std::string& name = it.first;
jobject figure_number = env->NewObject(long_class, long_init, (jlong)it.second);
env->CallObjectMethod(hash_map_obj, IDCache::GetHashMapPut(), ToJString(env, name),
figure_number);
env->DeleteLocalRef(figure_number);
}

return hash_map_obj;
}

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_removeFigure(JNIEnv* env,
jclass clazz,
jint position)
{
auto& system = Core::System::GetInstance();
system.GetInfinityBase().RemoveFigure(position);
}

JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_loadFigure(JNIEnv* env,
jclass clazz,
jint position,
jstring file_name)
{
File::IOFile inf_file(GetJString(env, file_name), "r+b");
if (!inf_file)
{
return nullptr;
}
std::array<u8, 0x14 * 0x10> file_data{};
if (!inf_file.ReadBytes(file_data.data(), file_data.size()))
{
return nullptr;
}

auto& system = Core::System::GetInstance();
system.GetInfinityBase().RemoveFigure(position);
return ToJString(env,
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file), position));
}

JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_infinitybase_InfinityConfig_createFigure(
JNIEnv* env, jclass clazz, jlong figure_number, jstring fileName, jint position)
{
u32 fig_num = static_cast<u32>(figure_number);

std::string file_name = GetJString(env, fileName);

auto& system = Core::System::GetInstance();
system.GetInfinityBase().CreateFigure(file_name, fig_num);
system.GetInfinityBase().RemoveFigure(position);

File::IOFile inf_file(file_name, "r+b");
if (!inf_file)
{
return nullptr;
}
std::array<u8, 0x14 * 0x10> file_data{};
if (!inf_file.ReadBytes(file_data.data(), file_data.size()))
{
return nullptr;
}

return ToJString(env,
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file), position));
}
}
19 changes: 7 additions & 12 deletions Source/Android/jni/SkylanderConfig.cpp
Expand Up @@ -6,6 +6,7 @@
#include <array>

#include "AndroidCommon/AndroidCommon.h"
#include "AndroidCommon/IDCache.h"
#include "Core/IOS/USB/Emulated/Skylander.h"
#include "Core/System.h"

Expand All @@ -15,12 +16,8 @@ JNIEXPORT jobject JNICALL
Java_org_dolphinemu_dolphinemu_features_skylanders_SkylanderConfig_getSkylanderMap(JNIEnv* env,
jclass clazz)
{
jclass hash_map_class = env->FindClass("java/util/HashMap");
jmethodID hash_map_init = env->GetMethodID(hash_map_class, "<init>", "(I)V");
jobject hash_map_obj = env->NewObject(hash_map_class, hash_map_init,
jobject hash_map_obj = env->NewObject(IDCache::GetHashMapClass(), IDCache::GetHashMapInit(),
static_cast<u16>(IOS::HLE::USB::list_skylanders.size()));
jmethodID hash_map_put = env->GetMethodID(
hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

jclass skylander_class =
env->FindClass("org/dolphinemu/dolphinemu/features/skylanders/model/SkylanderPair");
Expand All @@ -32,7 +29,8 @@ Java_org_dolphinemu_dolphinemu_features_skylanders_SkylanderConfig_getSkylanderM
const std::string& name = it.second;
jobject skylander_obj =
env->NewObject(skylander_class, skylander_init, it.first.first, it.first.second);
env->CallObjectMethod(hash_map_obj, hash_map_put, skylander_obj, ToJString(env, name));
env->CallObjectMethod(hash_map_obj, IDCache::GetHashMapPut(), skylander_obj,
ToJString(env, name));
env->DeleteLocalRef(skylander_obj);
}

Expand All @@ -43,12 +41,8 @@ JNIEXPORT jobject JNICALL
Java_org_dolphinemu_dolphinemu_features_skylanders_SkylanderConfig_getInverseSkylanderMap(
JNIEnv* env, jclass clazz)
{
jclass hash_map_class = env->FindClass("java/util/HashMap");
jmethodID hash_map_init = env->GetMethodID(hash_map_class, "<init>", "(I)V");
jobject hash_map_obj = env->NewObject(hash_map_class, hash_map_init,
jobject hash_map_obj = env->NewObject(IDCache::GetHashMapClass(), IDCache::GetHashMapInit(),
static_cast<u16>(IOS::HLE::USB::list_skylanders.size()));
jmethodID hash_map_put = env->GetMethodID(
hash_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

jclass skylander_class =
env->FindClass("org/dolphinemu/dolphinemu/features/skylanders/model/SkylanderPair");
Expand All @@ -60,7 +54,8 @@ Java_org_dolphinemu_dolphinemu_features_skylanders_SkylanderConfig_getInverseSky
const std::string& name = it.second;
jobject skylander_obj =
env->NewObject(skylander_class, skylander_init, it.first.first, it.first.second);
env->CallObjectMethod(hash_map_obj, hash_map_put, ToJString(env, name), skylander_obj);
env->CallObjectMethod(hash_map_obj, IDCache::GetHashMapPut(), ToJString(env, name),
skylander_obj);
env->DeleteLocalRef(skylander_obj);
}

Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Core/IOS/USB/Emulated/Infinity.cpp
Expand Up @@ -738,7 +738,7 @@ std::string InfinityBase::FindFigure(u32 number) const
return it->first;
}
}
return "";
return "Unknown Figure";
}

u8 InfinityBase::DeriveFigurePosition(u8 position)
Expand Down
2 changes: 0 additions & 2 deletions Source/Core/DolphinQt/InfinityBase/InfinityBaseWindow.cpp
Expand Up @@ -139,8 +139,6 @@ void InfinityBaseWindow::LoadFigure(u8 slot)

s_last_figure_path = QFileInfo(file_path).absolutePath() + QLatin1Char('/');

m_edit_figures[slot]->setText(QFileInfo(file_path).fileName());

LoadFigurePath(slot, file_path);
}

Expand Down