mirror of
https://github.com/skyline-emu/skyline.git
synced 2025-01-09 20:49:24 +01:00
Refactor all Game-related Kotlin Classes/Objects
This refactors most game-related classes and objects mainly adding spacing, adding comments and making improvements if possible.
This commit is contained in:
parent
f4968793b8
commit
55a9f8e937
@ -28,7 +28,9 @@ Use doxygen style comments for:
|
|||||||
* Class/Struct Functions - Use `/**` block comments on their function with a brief, all arguments and the return value (The brief can be skipped if the function's arguments and return value alone explain what the function does)
|
* Class/Struct Functions - Use `/**` block comments on their function with a brief, all arguments and the return value (The brief can be skipped if the function's arguments and return value alone explain what the function does)
|
||||||
* Enumerations - Use a `/**` block comment with a brief for the enum itself and a `//!<` single-line comment for all the individual items
|
* Enumerations - Use a `/**` block comment with a brief for the enum itself and a `//!<` single-line comment for all the individual items
|
||||||
|
|
||||||
Note: The DeviceState object can be skipped from function argument documentation as well as class members in the constructor.
|
Notes:
|
||||||
|
* The DeviceState object can be skipped from function argument documentation as well as class members in the constructor
|
||||||
|
* Any class members don't need to be redundantly documented in the constructor
|
||||||
|
|
||||||
### Control flow statements (if, for and while):
|
### Control flow statements (if, for and while):
|
||||||
#### If a child control-flow statement has brackets, the parent statement must as well
|
#### If a child control-flow statement has brackets, the parent statement must as well
|
||||||
@ -176,4 +178,4 @@ Handle b = 0x10; // Handle is a specific type that won't be automatically assign
|
|||||||
### Constants
|
### Constants
|
||||||
If a variable is constant at compile time use `constexpr`, if it's only used in a local function then place it in the function but if it's used throughout a class then in the corresponding header add the variable to the `skyline::constant` namespace. If a constant is used throughout the codebase, add it to `common.h`.
|
If a variable is constant at compile time use `constexpr`, if it's only used in a local function then place it in the function but if it's used throughout a class then in the corresponding header add the variable to the `skyline::constant` namespace. If a constant is used throughout the codebase, add it to `common.h`.
|
||||||
|
|
||||||
In addition, try to `constexpr` as much as possible including constructors and functions so that they may be initialized at compile-time and have lesser runtime overhead during usage and certain values can be pre-calculated in advance.
|
In addition, try to `constexpr` as much as possible including constructors and functions so that they may be initialized at compile-time and have lesser runtime overhead during usage and certain values can be pre-calculated in advance.
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
android:value="emu.skyline.SettingsActivity" />
|
android:value="emu.skyline.SettingsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="emu.skyline.GameActivity"
|
android:name="emu.skyline.EmulationActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:screenOrientation="landscape">
|
android:screenOrientation="landscape">
|
||||||
|
@ -21,7 +21,7 @@ void signalHandler(int signal) {
|
|||||||
FaultCount++;
|
FaultCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_executeRom(JNIEnv *env, jobject instance, jstring romJstring, jint romType, jint romFd, jint preferenceFd, jint logFd) {
|
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeRom(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jint logFd) {
|
||||||
Halt = false;
|
Halt = false;
|
||||||
FaultCount = 0;
|
FaultCount = 0;
|
||||||
|
|
||||||
@ -43,9 +43,9 @@ extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_executeRom(JNIEnv *env,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
skyline::kernel::OS os(jvmManager, logger, settings);
|
skyline::kernel::OS os(jvmManager, logger, settings);
|
||||||
const char *romString = env->GetStringUTFChars(romJstring, nullptr);
|
const char *romUri = env->GetStringUTFChars(romUriJstring, nullptr);
|
||||||
logger->Info("Launching ROM {}", romString);
|
logger->Info("Launching ROM {}", romUri);
|
||||||
env->ReleaseStringUTFChars(romJstring, romString);
|
env->ReleaseStringUTFChars(romUriJstring, romUri);
|
||||||
os.Execute(romFd, static_cast<skyline::TitleFormat>(romType));
|
os.Execute(romFd, static_cast<skyline::TitleFormat>(romType));
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
logger->Error(e.what());
|
logger->Error(e.what());
|
||||||
@ -58,13 +58,13 @@ extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_executeRom(JNIEnv *env,
|
|||||||
logger->Info("Done in: {} ms", (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()));
|
logger->Info("Done in: {} ms", (std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_setHalt(JNIEnv *env, jobject instance, jboolean halt) {
|
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setHalt(JNIEnv *env, jobject instance, jboolean halt) {
|
||||||
JniMtx.lock(skyline::GroupMutex::Group::Group2);
|
JniMtx.lock(skyline::GroupMutex::Group::Group2);
|
||||||
Halt = halt;
|
Halt = halt;
|
||||||
JniMtx.unlock();
|
JniMtx.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_setSurface(JNIEnv *env, jobject instance, jobject surface) {
|
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_setSurface(JNIEnv *env, jobject instance, jobject surface) {
|
||||||
JniMtx.lock(skyline::GroupMutex::Group::Group2);
|
JniMtx.lock(skyline::GroupMutex::Group::Group2);
|
||||||
if (!env->IsSameObject(Surface, nullptr))
|
if (!env->IsSameObject(Surface, nullptr))
|
||||||
env->DeleteGlobalRef(Surface);
|
env->DeleteGlobalRef(Surface);
|
||||||
|
@ -10,105 +10,175 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.InputQueue
|
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import emu.skyline.loader.getTitleFormat
|
import emu.skyline.loader.getRomFormat
|
||||||
import kotlinx.android.synthetic.main.game_activity.*
|
import kotlinx.android.synthetic.main.game_activity.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.reflect.Method
|
|
||||||
|
|
||||||
class GameActivity : AppCompatActivity(), SurfaceHolder.Callback, InputQueue.Callback {
|
/**
|
||||||
|
* This activity is used for emulation using libskyline
|
||||||
|
*/
|
||||||
|
class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("skyline") // libskyline.so
|
System.loadLibrary("skyline") // libskyline.so
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file descriptor of the ROM
|
||||||
|
*/
|
||||||
private lateinit var romFd: ParcelFileDescriptor
|
private lateinit var romFd: ParcelFileDescriptor
|
||||||
private lateinit var preferenceFd: ParcelFileDescriptor
|
|
||||||
private lateinit var logFd: ParcelFileDescriptor
|
|
||||||
private var surface: Surface? = null
|
|
||||||
private var inputQueue: Long = 0L
|
|
||||||
private var shouldFinish: Boolean = true
|
|
||||||
private lateinit var gameThread: Thread
|
|
||||||
|
|
||||||
private external fun executeRom(romString: String, romType: Int, romFd: Int, preferenceFd: Int, logFd: Int)
|
/**
|
||||||
|
* The file descriptor of the application Preference XML
|
||||||
|
*/
|
||||||
|
private lateinit var preferenceFd: ParcelFileDescriptor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file descriptor of the Log file
|
||||||
|
*/
|
||||||
|
private lateinit var logFd: ParcelFileDescriptor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The surface object used for displaying frames
|
||||||
|
*/
|
||||||
|
private var surface: Surface? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean flag denoting if the emulation thread should call finish() or not
|
||||||
|
*/
|
||||||
|
private var shouldFinish: Boolean = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Kotlin thread on which emulation code executes
|
||||||
|
*/
|
||||||
|
private lateinit var emulationThread: Thread
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the entry point into the emulation code for libskyline
|
||||||
|
*
|
||||||
|
* @param romUri The URI of the ROM as a string, used to print out in the logs
|
||||||
|
* @param romType The type of the ROM as an enum value
|
||||||
|
* @param romFd The file descriptor of the ROM object
|
||||||
|
* @param preferenceFd The file descriptor of the Preference XML
|
||||||
|
* @param logFd The file descriptor of the Log file
|
||||||
|
*/
|
||||||
|
private external fun executeRom(romUri: String, romType: Int, romFd: Int, preferenceFd: Int, logFd: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sets the halt flag in libskyline to the provided value, if set to true it causes libskyline to halt emulation
|
||||||
|
*
|
||||||
|
* @param halt The value to set halt to
|
||||||
|
*/
|
||||||
private external fun setHalt(halt: Boolean)
|
private external fun setHalt(halt: Boolean)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sets the surface object in libskyline to the provided value, emulation is halted if set to null
|
||||||
|
*
|
||||||
|
* @param surface The value to set surface to
|
||||||
|
*/
|
||||||
private external fun setSurface(surface: Surface?)
|
private external fun setSurface(surface: Surface?)
|
||||||
|
|
||||||
fun executeRom(rom : Uri) {
|
/**
|
||||||
val romType = getTitleFormat(rom, contentResolver).ordinal
|
* This executes the specified ROM, [preferenceFd] and [logFd] are assumed to be valid beforehand
|
||||||
|
*
|
||||||
|
* @param rom The URI of the ROM to execute
|
||||||
|
*/
|
||||||
|
private fun executeRom(rom : Uri) {
|
||||||
|
val romType = getRomFormat(rom, contentResolver).ordinal
|
||||||
romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
||||||
gameThread = Thread {
|
|
||||||
|
emulationThread = Thread {
|
||||||
while ((surface == null))
|
while ((surface == null))
|
||||||
Thread.yield()
|
Thread.yield()
|
||||||
|
|
||||||
executeRom(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, logFd.fd)
|
executeRom(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, logFd.fd)
|
||||||
|
|
||||||
if (shouldFinish)
|
if (shouldFinish)
|
||||||
runOnUiThread { finish() }
|
runOnUiThread { finish() }
|
||||||
}
|
}
|
||||||
gameThread.start()
|
|
||||||
|
emulationThread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The onCreate handler for the activity, it sets up the FDs and calls [executeRom]
|
||||||
|
*/
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(R.layout.game_activity)
|
setContentView(R.layout.game_activity)
|
||||||
|
|
||||||
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
|
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
|
||||||
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
|
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
|
||||||
|
|
||||||
val log = File("${applicationInfo.dataDir}/skyline.log")
|
val log = File("${applicationInfo.dataDir}/skyline.log")
|
||||||
logFd = ParcelFileDescriptor.open(log, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_READ_WRITE)
|
logFd = ParcelFileDescriptor.open(log, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_READ_WRITE)
|
||||||
|
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||||
game_view.holder.addCallback(this)
|
game_view.holder.addCallback(this)
|
||||||
|
|
||||||
executeRom(intent.data!!)
|
executeRom(intent.data!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The onNewIntent handler is used to stop the currently executing ROM and replace it with the one specified in the new intent
|
||||||
|
*/
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
shouldFinish = false
|
shouldFinish = false
|
||||||
|
|
||||||
setHalt(true)
|
setHalt(true)
|
||||||
gameThread.join()
|
emulationThread.join()
|
||||||
|
|
||||||
shouldFinish = true
|
shouldFinish = true
|
||||||
|
|
||||||
romFd.close()
|
romFd.close()
|
||||||
|
|
||||||
executeRom(intent?.data!!)
|
executeRom(intent?.data!!)
|
||||||
|
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The onDestroy handler is used to halt emulation
|
||||||
|
*/
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
shouldFinish = false
|
shouldFinish = false
|
||||||
|
|
||||||
setHalt(true)
|
setHalt(true)
|
||||||
gameThread.join()
|
emulationThread.join()
|
||||||
|
|
||||||
romFd.close()
|
romFd.close()
|
||||||
preferenceFd.close()
|
preferenceFd.close()
|
||||||
logFd.close()
|
logFd.close()
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The surfaceCreated handler passes in the surface to libskyline
|
||||||
|
*/
|
||||||
override fun surfaceCreated(holder: SurfaceHolder?) {
|
override fun surfaceCreated(holder: SurfaceHolder?) {
|
||||||
Log.d("surfaceCreated", "Holder: ${holder.toString()}")
|
Log.d("surfaceCreated", "Holder: ${holder.toString()}")
|
||||||
surface = holder!!.surface
|
surface = holder!!.surface
|
||||||
setSurface(surface)
|
setSurface(surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The surfaceChanged handler is purely used for debugging purposes
|
||||||
|
*/
|
||||||
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
|
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
|
||||||
Log.d("surfaceChanged", "Holder: ${holder.toString()}, Format: $format, Width: $width, Height: $height")
|
Log.d("surfaceChanged", "Holder: ${holder.toString()}, Format: $format, Width: $width, Height: $height")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The surfaceDestroyed handler passes sets the surface to null
|
||||||
|
*/
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder?) {
|
override fun surfaceDestroyed(holder: SurfaceHolder?) {
|
||||||
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
|
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
|
||||||
surface = null
|
surface = null
|
||||||
setSurface(surface)
|
setSurface(surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInputQueueCreated(queue: InputQueue?) {
|
|
||||||
Log.i("onInputQueueCreated", "InputQueue: ${queue.toString()}")
|
|
||||||
val clazz = Class.forName("android.view.InputQueue")
|
|
||||||
val method: Method = clazz.getMethod("getNativePtr")
|
|
||||||
inputQueue = method.invoke(queue)!! as Long
|
|
||||||
//setQueue(inputQueue)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInputQueueDestroyed(queue: InputQueue?) {
|
|
||||||
Log.d("onInputQueueDestroyed", "InputQueue: ${queue.toString()}")
|
|
||||||
inputQueue = 0L
|
|
||||||
//setQueue(inputQueue)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -19,8 +19,8 @@ import androidx.appcompat.widget.SearchView
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import emu.skyline.adapter.GameAdapter
|
import emu.skyline.adapter.AppAdapter
|
||||||
import emu.skyline.adapter.GameItem
|
import emu.skyline.adapter.AppItem
|
||||||
import emu.skyline.loader.BaseLoader
|
import emu.skyline.loader.BaseLoader
|
||||||
import emu.skyline.loader.NroLoader
|
import emu.skyline.loader.NroLoader
|
||||||
import emu.skyline.utility.GameDialog
|
import emu.skyline.utility.GameDialog
|
||||||
@ -32,7 +32,7 @@ import kotlin.concurrent.thread
|
|||||||
|
|
||||||
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private var adapter = GameAdapter(this)
|
private var adapter = AppAdapter(this)
|
||||||
|
|
||||||
private fun notifyUser(text: String) {
|
private fun notifyUser(text: String) {
|
||||||
Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(findViewById(android.R.id.content), text, Snackbar.LENGTH_SHORT).show()
|
||||||
@ -49,13 +49,13 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
if (ext.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
|
if (ext.equals(file.name?.substringAfterLast("."), ignoreCase = true)) {
|
||||||
val document = RandomAccessDocument(this, file)
|
val document = RandomAccessDocument(this, file)
|
||||||
if (loader.verifyFile(document)) {
|
if (loader.verifyFile(document)) {
|
||||||
val entry = loader.getTitleEntry(document, file.uri)
|
val entry = loader.getAppEntry(document, file.uri)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (!foundCurrent) {
|
if (!foundCurrent) {
|
||||||
adapter.addHeader(getString(R.string.nro))
|
adapter.addHeader(loader.format.name)
|
||||||
foundCurrent = true
|
foundCurrent = true
|
||||||
}
|
}
|
||||||
adapter.addItem(GameItem(entry))
|
adapter.addItem(AppItem(entry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.close()
|
document.close()
|
||||||
@ -75,25 +75,31 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
Log.w("refreshFiles", "Ran into exception while loading: ${e.message}")
|
Log.w("refreshFiles", "Ran into exception while loading: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread(start = true) {
|
thread(start = true) {
|
||||||
val snackbar = Snackbar.make(findViewById(android.R.id.content), getString(R.string.searching_roms), Snackbar.LENGTH_INDEFINITE)
|
val snackbar = Snackbar.make(findViewById(android.R.id.content), getString(R.string.searching_roms), Snackbar.LENGTH_INDEFINITE)
|
||||||
runOnUiThread { snackbar.show() }
|
runOnUiThread { snackbar.show() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runOnUiThread { adapter.clear() }
|
runOnUiThread { adapter.clear() }
|
||||||
val foundNros = findFile("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
val foundNros = findFile("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (!foundNros)
|
if (!foundNros)
|
||||||
adapter.addHeader(getString(R.string.no_rom))
|
adapter.addHeader(getString(R.string.no_rom))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
adapter.save(File("${applicationInfo.dataDir}/roms.bin"))
|
adapter.save(File("${applicationInfo.dataDir}/roms.bin"))
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.w("refreshFiles", "Ran into exception while saving: ${e.message}")
|
Log.w("refreshFiles", "Ran into exception while saving: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedPreferences.edit().putBoolean("refresh_required", false).apply()
|
sharedPreferences.edit().putBoolean("refresh_required", false).apply()
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
sharedPreferences.edit().remove("search_location").apply()
|
sharedPreferences.edit().remove("search_location").apply()
|
||||||
|
|
||||||
val intent = intent
|
val intent = intent
|
||||||
finish()
|
finish()
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@ -103,6 +109,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
notifyUser(e.message!!)
|
notifyUser(e.message!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runOnUiThread { snackbar.dismiss() }
|
runOnUiThread { snackbar.dismiss() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,15 +131,15 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
game_list.adapter = adapter
|
game_list.adapter = adapter
|
||||||
game_list.onItemClickListener = OnItemClickListener { parent: AdapterView<*>, _: View?, position: Int, _: Long ->
|
game_list.onItemClickListener = OnItemClickListener { parent: AdapterView<*>, _: View?, position: Int, _: Long ->
|
||||||
val item = parent.getItemAtPosition(position)
|
val item = parent.getItemAtPosition(position)
|
||||||
if (item is GameItem) {
|
if (item is AppItem) {
|
||||||
val intent = Intent(this, GameActivity::class.java)
|
val intent = Intent(this, EmulationActivity::class.java)
|
||||||
intent.data = item.uri
|
intent.data = item.uri
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
game_list.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, _, position, _ ->
|
game_list.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, _, position, _ ->
|
||||||
val item = parent.getItemAtPosition(position)
|
val item = parent.getItemAtPosition(position)
|
||||||
if (item is GameItem) {
|
if (item is AppItem) {
|
||||||
val dialog = GameDialog(item)
|
val dialog = GameDialog(item)
|
||||||
dialog.show(supportFragmentManager, "game")
|
dialog.show(supportFragmentManager, "game")
|
||||||
}
|
}
|
||||||
@ -205,7 +212,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
2 -> {
|
2 -> {
|
||||||
try {
|
try {
|
||||||
val uri = (intent!!.data!!)
|
val uri = (intent!!.data!!)
|
||||||
val intentGame = Intent(this, GameActivity::class.java)
|
val intentGame = Intent(this, EmulationActivity::class.java)
|
||||||
intentGame.data = uri
|
intentGame.data = uri
|
||||||
if (resultCode != 0)
|
if (resultCode != 0)
|
||||||
startActivityForResult(intentGame, resultCode)
|
startActivityForResult(intentGame, resultCode)
|
||||||
|
@ -18,39 +18,69 @@ import android.view.Window
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.loader.TitleEntry
|
import emu.skyline.loader.AppEntry
|
||||||
|
|
||||||
class GameItem(val meta: TitleEntry) : BaseItem() {
|
/**
|
||||||
|
* This class is a wrapper around [AppEntry], it is used for passing around game metadata
|
||||||
|
*/
|
||||||
|
class AppItem(val meta: AppEntry) : BaseItem() {
|
||||||
|
/**
|
||||||
|
* The icon of the application
|
||||||
|
*/
|
||||||
val icon: Bitmap?
|
val icon: Bitmap?
|
||||||
get() = meta.icon
|
get() = meta.icon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The title of the application
|
||||||
|
*/
|
||||||
val title: String
|
val title: String
|
||||||
get() = meta.name + " (" + type + ")"
|
get() = meta.name + " (" + type + ")"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The string used as the sub-title, we currently use the author
|
||||||
|
*/
|
||||||
val subTitle: String?
|
val subTitle: String?
|
||||||
get() = meta.author
|
get() = meta.author
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URI of the application's image file
|
||||||
|
*/
|
||||||
val uri: Uri
|
val uri: Uri
|
||||||
get() = meta.uri
|
get() = meta.uri
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The format of the application ROM as a string
|
||||||
|
*/
|
||||||
private val type: String
|
private val type: String
|
||||||
get() = meta.romType.name
|
get() = meta.format.name
|
||||||
|
|
||||||
override fun key(): String? {
|
override fun key(): String? {
|
||||||
return if (meta.valid) meta.name + " " + meta.author else meta.name
|
return if (meta.author != null) meta.name + " " + meta.author else meta.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class GameAdapter(val context: Context?) : HeaderAdapter<GameItem, BaseHeader>(), View.OnClickListener {
|
/**
|
||||||
|
* This adapter is used to display all found applications using their metadata
|
||||||
|
*/
|
||||||
|
internal class AppAdapter(val context: Context?) : HeaderAdapter<AppItem, BaseHeader>(), View.OnClickListener {
|
||||||
|
/**
|
||||||
|
* This adds a string header to the view
|
||||||
|
*/
|
||||||
fun addHeader(string: String) {
|
fun addHeader(string: String) {
|
||||||
super.addHeader(BaseHeader(string))
|
super.addHeader(BaseHeader(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The onClick handler, it's for displaying the icon preview
|
||||||
|
*
|
||||||
|
* @param view The specific view that was clicked
|
||||||
|
*/
|
||||||
override fun onClick(view: View) {
|
override fun onClick(view: View) {
|
||||||
val position = view.tag as Int
|
val position = view.tag as Int
|
||||||
if (getItem(position) is GameItem) {
|
if (getItem(position) is AppItem) {
|
||||||
val item = getItem(position) as GameItem
|
val item = getItem(position) as AppItem
|
||||||
if (view.id == R.id.icon) {
|
if (view.id == R.id.icon) {
|
||||||
val builder = Dialog(context!!)
|
val builder = Dialog(context!!)
|
||||||
builder.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
builder.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
@ -63,44 +93,61 @@ internal class GameAdapter(val context: Context?) : HeaderAdapter<GameItem, Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the view for an element at a specific position
|
||||||
|
*
|
||||||
|
* @param position The position of the requested item
|
||||||
|
* @param convertView An existing view (If any)
|
||||||
|
* @param parent The parent view group used for layout inflation
|
||||||
|
*/
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
var view = convertView
|
var view = convertView
|
||||||
val viewHolder: ViewHolder
|
val viewHolder: ViewHolder
|
||||||
val item = elementArray[visibleArray[position]]
|
val item = elementArray[visibleArray[position]]
|
||||||
|
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
viewHolder = ViewHolder()
|
viewHolder = ViewHolder()
|
||||||
if (item is GameItem) {
|
if (item is AppItem) {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
view = inflater.inflate(R.layout.game_item, parent, false)
|
view = inflater.inflate(R.layout.game_item, parent, false)
|
||||||
|
|
||||||
viewHolder.icon = view.findViewById(R.id.icon)
|
viewHolder.icon = view.findViewById(R.id.icon)
|
||||||
viewHolder.txtTitle = view.findViewById(R.id.text_title)
|
viewHolder.title = view.findViewById(R.id.text_title)
|
||||||
viewHolder.txtSub = view.findViewById(R.id.text_subtitle)
|
viewHolder.subtitle = view.findViewById(R.id.text_subtitle)
|
||||||
view.tag = viewHolder
|
view.tag = viewHolder
|
||||||
} else if (item is BaseHeader) {
|
} else if (item is BaseHeader) {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
view = inflater.inflate(R.layout.section_item, parent, false)
|
view = inflater.inflate(R.layout.section_item, parent, false)
|
||||||
viewHolder.txtTitle = view.findViewById(R.id.text_title)
|
|
||||||
|
viewHolder.title = view.findViewById(R.id.text_title)
|
||||||
view.tag = viewHolder
|
view.tag = viewHolder
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewHolder = view.tag as ViewHolder
|
viewHolder = view.tag as ViewHolder
|
||||||
}
|
}
|
||||||
if (item is GameItem) {
|
|
||||||
val data = getItem(position) as GameItem
|
if (item is AppItem) {
|
||||||
viewHolder.txtTitle!!.text = data.title
|
val data = getItem(position) as AppItem
|
||||||
viewHolder.txtSub!!.text = data.subTitle
|
|
||||||
viewHolder.icon!!.setImageBitmap(data.icon)
|
viewHolder.title!!.text = data.title
|
||||||
|
viewHolder.subtitle!!.text = data.subTitle ?: context?.getString(R.string.metadata_missing)!!
|
||||||
|
|
||||||
|
viewHolder.icon!!.setImageBitmap(data.icon ?: context!!.resources.getDrawable(R.drawable.ic_missing, context.theme).toBitmap(256, 256))
|
||||||
viewHolder.icon!!.setOnClickListener(this)
|
viewHolder.icon!!.setOnClickListener(this)
|
||||||
viewHolder.icon!!.tag = position
|
viewHolder.icon!!.tag = position
|
||||||
} else {
|
} else {
|
||||||
viewHolder.txtTitle!!.text = (getItem(position) as BaseHeader).title
|
viewHolder.title!!.text = (getItem(position) as BaseHeader).title
|
||||||
}
|
}
|
||||||
|
|
||||||
return view!!
|
return view!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewHolder {
|
/**
|
||||||
var icon: ImageView? = null
|
* The ViewHolder object is used to hold the views associated with an object
|
||||||
var txtTitle: TextView? = null
|
*
|
||||||
var txtSub: TextView? = null
|
* @param icon The ImageView associated with the icon
|
||||||
}
|
* @param title The TextView associated with the title
|
||||||
|
* @param subtitle The TextView associated with the subtitle
|
||||||
|
*/
|
||||||
|
private class ViewHolder(var icon: ImageView? = null, var title: TextView? = null, var subtitle: TextView? = null)
|
||||||
}
|
}
|
@ -24,19 +24,22 @@ enum class ElementType(val type: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This is the interface class that all element classes inherit from
|
* This is an abstract class that all adapter element classes inherit from
|
||||||
*/
|
*/
|
||||||
abstract class BaseElement constructor(val elementType: ElementType) : Serializable
|
abstract class BaseElement constructor(val elementType: ElementType) : Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This is the interface class that all header classes inherit from
|
* This is an abstract class that all adapter header classes inherit from
|
||||||
*/
|
*/
|
||||||
class BaseHeader constructor(val title: String) : BaseElement(ElementType.Header)
|
class BaseHeader constructor(val title: String) : BaseElement(ElementType.Header)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This is the interface class that all item classes inherit from
|
* This is an abstract class that all adapter item classes inherit from
|
||||||
*/
|
*/
|
||||||
abstract class BaseItem : BaseElement(ElementType.Item) {
|
abstract class BaseItem : BaseElement(ElementType.Item) {
|
||||||
|
/**
|
||||||
|
* This function returns a string used for searching
|
||||||
|
*/
|
||||||
abstract fun key(): String?
|
abstract fun key(): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,6 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
|
||||||
import emu.skyline.R
|
|
||||||
import emu.skyline.utility.RandomAccessDocument
|
import emu.skyline.utility.RandomAccessDocument
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
@ -20,52 +18,119 @@ import java.io.ObjectOutputStream
|
|||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
enum class TitleFormat {
|
/**
|
||||||
|
* An enumeration of all supported ROM formats
|
||||||
|
*/
|
||||||
|
enum class RomFormat {
|
||||||
NRO, XCI, NSP
|
NRO, XCI, NSP
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTitleFormat(uri: Uri, contentResolver: ContentResolver): TitleFormat {
|
/**
|
||||||
|
* This resolves the format of a ROM from it's URI so we can determine formats for ROMs launched from arbitrary locations
|
||||||
|
*
|
||||||
|
* @param uri The URL of the ROM
|
||||||
|
* @param contentResolver The instance of ContentResolver associated with the current context
|
||||||
|
*/
|
||||||
|
fun getRomFormat(uri: Uri, contentResolver: ContentResolver): RomFormat {
|
||||||
var uriStr = ""
|
var uriStr = ""
|
||||||
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
cursor.moveToFirst()
|
cursor.moveToFirst()
|
||||||
uriStr = cursor.getString(nameIndex)
|
uriStr = cursor.getString(nameIndex)
|
||||||
}
|
}
|
||||||
return TitleFormat.valueOf(uriStr.substring(uriStr.lastIndexOf(".") + 1).toUpperCase(Locale.ROOT))
|
return RomFormat.valueOf(uriStr.substring(uriStr.lastIndexOf(".") + 1).toUpperCase(Locale.ROOT))
|
||||||
}
|
}
|
||||||
|
|
||||||
class TitleEntry(var name: String, var author: String, var romType: TitleFormat, var valid: Boolean, var uri: Uri, var icon: Bitmap) : Serializable {
|
/**
|
||||||
constructor(context: Context, author: String, romType: TitleFormat, valid: Boolean, uri: Uri) : this("", author, romType, valid, uri, context.resources.getDrawable(R.drawable.ic_missing, context.theme).toBitmap(256, 256)) {
|
* This class is used to hold an application's metadata in a serializable way
|
||||||
context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
*/
|
||||||
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
class AppEntry : Serializable {
|
||||||
cursor.moveToFirst()
|
/**
|
||||||
name = cursor.getString(nameIndex)
|
* The name of the application
|
||||||
}
|
*/
|
||||||
|
var name: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The author of the application, if it can be extracted from the metadata
|
||||||
|
*/
|
||||||
|
var author: String? = null
|
||||||
|
|
||||||
|
var icon: Bitmap? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The format of the application ROM
|
||||||
|
*/
|
||||||
|
var format: RomFormat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URI of the application ROM
|
||||||
|
*/
|
||||||
|
var uri: Uri
|
||||||
|
|
||||||
|
constructor(name: String, author: String, format: RomFormat, uri: Uri, icon: Bitmap) {
|
||||||
|
this.name = name
|
||||||
|
this.author = author
|
||||||
|
this.icon = icon
|
||||||
|
this.format = format
|
||||||
|
this.uri = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, format: RomFormat, uri: Uri) {
|
||||||
|
this.name = context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
|
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
cursor.moveToFirst()
|
||||||
|
cursor.getString(nameIndex)
|
||||||
|
}!!
|
||||||
|
this.format = format
|
||||||
|
this.uri = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This serializes this object into an OutputStream
|
||||||
|
*
|
||||||
|
* @param output The stream to which the object is written into
|
||||||
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun writeObject(output: ObjectOutputStream) {
|
private fun writeObject(output: ObjectOutputStream) {
|
||||||
output.writeUTF(name)
|
output.writeUTF(name)
|
||||||
output.writeUTF(author)
|
output.writeObject(format)
|
||||||
output.writeObject(romType)
|
|
||||||
output.writeUTF(uri.toString())
|
output.writeUTF(uri.toString())
|
||||||
output.writeBoolean(valid)
|
output.writeBoolean(author != null)
|
||||||
icon.compress(Bitmap.CompressFormat.WEBP, 100, output)
|
if (author != null)
|
||||||
|
output.writeUTF(author)
|
||||||
|
output.writeBoolean(icon != null)
|
||||||
|
if (icon != null)
|
||||||
|
icon!!.compress(Bitmap.CompressFormat.WEBP, 100, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This initializes the object from an InputStream
|
||||||
|
*
|
||||||
|
* @param input The stream from which the object data is retrieved from
|
||||||
|
*/
|
||||||
@Throws(IOException::class, ClassNotFoundException::class)
|
@Throws(IOException::class, ClassNotFoundException::class)
|
||||||
private fun readObject(input: ObjectInputStream) {
|
private fun readObject(input: ObjectInputStream) {
|
||||||
name = input.readUTF()
|
name = input.readUTF()
|
||||||
author = input.readUTF()
|
format = input.readObject() as RomFormat
|
||||||
romType = input.readObject() as TitleFormat
|
|
||||||
uri = Uri.parse(input.readUTF())
|
uri = Uri.parse(input.readUTF())
|
||||||
valid = input.readBoolean()
|
if (input.readBoolean())
|
||||||
icon = BitmapFactory.decodeStream(input)
|
author = input.readUTF()
|
||||||
|
if (input.readBoolean())
|
||||||
|
icon = BitmapFactory.decodeStream(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class BaseLoader(val context: Context, val romType: TitleFormat) {
|
/**
|
||||||
abstract fun getTitleEntry(file: RandomAccessDocument, uri: Uri): TitleEntry
|
* This class is used as the base class for all loaders
|
||||||
|
*/
|
||||||
|
internal abstract class BaseLoader(val context: Context, val format: RomFormat) {
|
||||||
|
/**
|
||||||
|
* This returns an AppEntry object for the supplied document
|
||||||
|
*/
|
||||||
|
abstract fun getAppEntry(file: RandomAccessDocument, uri: Uri): AppEntry
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns if the supplied document is a valid ROM or not
|
||||||
|
*/
|
||||||
abstract fun verifyFile(file: RandomAccessDocument): Boolean
|
abstract fun verifyFile(file: RandomAccessDocument): Boolean
|
||||||
}
|
}
|
||||||
|
@ -8,45 +8,56 @@ package emu.skyline.loader
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import emu.skyline.R
|
|
||||||
import emu.skyline.utility.RandomAccessDocument
|
import emu.skyline.utility.RandomAccessDocument
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
internal class NroLoader(context: Context) : BaseLoader(context, TitleFormat.NRO) {
|
/**
|
||||||
override fun getTitleEntry(file: RandomAccessDocument, uri: Uri): TitleEntry {
|
* This loader is used to load in NRO (Nintendo Relocatable Object) files (https://switchbrew.org/wiki/NRO)
|
||||||
|
*/
|
||||||
|
internal class NroLoader(context: Context) : BaseLoader(context, RomFormat.NRO) {
|
||||||
|
override fun getAppEntry(file: RandomAccessDocument, uri: Uri): AppEntry {
|
||||||
return try {
|
return try {
|
||||||
file.seek(0x18) // Skip to NroHeader.size
|
file.seek(0x18) // Skip to NroHeader.size
|
||||||
|
|
||||||
val asetOffset = Integer.reverseBytes(file.readInt())
|
val asetOffset = Integer.reverseBytes(file.readInt())
|
||||||
file.seek(asetOffset.toLong()) // Skip to the offset specified by NroHeader.size
|
file.seek(asetOffset.toLong()) // Skip to the offset specified by NroHeader.size
|
||||||
|
|
||||||
val buffer = ByteArray(4)
|
val buffer = ByteArray(4)
|
||||||
file.read(buffer)
|
file.read(buffer)
|
||||||
if (String(buffer) != "ASET") throw IOException()
|
if (String(buffer) != "ASET") throw IOException()
|
||||||
file.skipBytes(0x4)
|
file.skipBytes(0x4)
|
||||||
|
|
||||||
val iconOffset = java.lang.Long.reverseBytes(file.readLong())
|
val iconOffset = java.lang.Long.reverseBytes(file.readLong())
|
||||||
val iconSize = Integer.reverseBytes(file.readInt())
|
val iconSize = Integer.reverseBytes(file.readInt())
|
||||||
if (iconOffset == 0L || iconSize == 0) throw IOException()
|
if (iconOffset == 0L || iconSize == 0) throw IOException()
|
||||||
file.seek(asetOffset + iconOffset)
|
file.seek(asetOffset + iconOffset)
|
||||||
|
|
||||||
val iconData = ByteArray(iconSize)
|
val iconData = ByteArray(iconSize)
|
||||||
file.read(iconData)
|
file.read(iconData)
|
||||||
val icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize)
|
val icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize)
|
||||||
file.seek(asetOffset + 0x18.toLong())
|
file.seek(asetOffset + 0x18.toLong())
|
||||||
|
|
||||||
val nacpOffset = java.lang.Long.reverseBytes(file.readLong())
|
val nacpOffset = java.lang.Long.reverseBytes(file.readLong())
|
||||||
val nacpSize = java.lang.Long.reverseBytes(file.readLong())
|
val nacpSize = java.lang.Long.reverseBytes(file.readLong())
|
||||||
if (nacpOffset == 0L || nacpSize == 0L) throw IOException()
|
if (nacpOffset == 0L || nacpSize == 0L) throw IOException()
|
||||||
file.seek(asetOffset + nacpOffset)
|
file.seek(asetOffset + nacpOffset)
|
||||||
|
|
||||||
val name = ByteArray(0x200)
|
val name = ByteArray(0x200)
|
||||||
file.read(name)
|
file.read(name)
|
||||||
|
|
||||||
val author = ByteArray(0x100)
|
val author = ByteArray(0x100)
|
||||||
file.read(author)
|
file.read(author)
|
||||||
TitleEntry(String(name).substringBefore((0.toChar())), String(author).substringBefore((0.toChar())), romType, true, uri, icon)
|
|
||||||
|
AppEntry(String(name).substringBefore((0.toChar())), String(author).substringBefore((0.toChar())), format, uri, icon)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
TitleEntry(context, context.getString(R.string.aset_missing), romType, false, uri)
|
AppEntry(context, format, uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifyFile(file: RandomAccessDocument): Boolean {
|
override fun verifyFile(file: RandomAccessDocument): Boolean {
|
||||||
try {
|
try {
|
||||||
file.seek(0x10) // Skip to NroHeader.magic
|
file.seek(0x10) // Skip to NroHeader.magic
|
||||||
|
|
||||||
val buffer = ByteArray(4)
|
val buffer = ByteArray(4)
|
||||||
file.read(buffer)
|
file.read(buffer)
|
||||||
if (String(buffer) != "NRO0") return false
|
if (String(buffer) != "NRO0") return false
|
||||||
|
@ -15,15 +15,15 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import emu.skyline.GameActivity
|
import emu.skyline.EmulationActivity
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.adapter.GameItem
|
import emu.skyline.adapter.AppItem
|
||||||
import kotlinx.android.synthetic.main.game_dialog.*
|
import kotlinx.android.synthetic.main.game_dialog.*
|
||||||
|
|
||||||
class GameDialog() : DialogFragment() {
|
class GameDialog() : DialogFragment() {
|
||||||
var item: GameItem? = null
|
var item: AppItem? = null
|
||||||
|
|
||||||
constructor(item: GameItem) : this() {
|
constructor(item: AppItem) : this() {
|
||||||
this.item = item
|
this.item = item
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ class GameDialog() : DialogFragment() {
|
|||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
if (item is GameItem) {
|
if (item is AppItem) {
|
||||||
game_icon.setImageBitmap(item?.icon)
|
game_icon.setImageBitmap(item?.icon)
|
||||||
game_title.text = item?.title
|
game_title.text = item?.title
|
||||||
game_subtitle.text = item?.subTitle
|
game_subtitle.text = item?.subTitle
|
||||||
@ -42,16 +42,16 @@ class GameDialog() : DialogFragment() {
|
|||||||
game_pin.setOnClickListener {
|
game_pin.setOnClickListener {
|
||||||
val info = ShortcutInfo.Builder(context, item?.title)
|
val info = ShortcutInfo.Builder(context, item?.title)
|
||||||
info.setShortLabel(item?.meta?.name!!)
|
info.setShortLabel(item?.meta?.name!!)
|
||||||
info.setActivity(ComponentName(context!!, GameActivity::class.java))
|
info.setActivity(ComponentName(context!!, EmulationActivity::class.java))
|
||||||
info.setIcon(Icon.createWithBitmap(item?.icon))
|
info.setIcon(Icon.createWithBitmap(item?.icon))
|
||||||
val intent = Intent(context, GameActivity::class.java)
|
val intent = Intent(context, EmulationActivity::class.java)
|
||||||
intent.data = item?.uri
|
intent.data = item?.uri
|
||||||
intent.action = Intent.ACTION_VIEW
|
intent.action = Intent.ACTION_VIEW
|
||||||
info.setIntent(intent)
|
info.setIntent(intent)
|
||||||
shortcutManager.requestPinShortcut(info.build(), null)
|
shortcutManager.requestPinShortcut(info.build(), null)
|
||||||
}
|
}
|
||||||
game_play.setOnClickListener {
|
game_play.setOnClickListener {
|
||||||
val intent = Intent(activity, GameActivity::class.java)
|
val intent = Intent(activity, EmulationActivity::class.java)
|
||||||
intent.data = item?.uri
|
intent.data = item?.uri
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".GameActivity">
|
tools:context=".EmulationActivity">
|
||||||
|
|
||||||
<SurfaceView
|
<SurfaceView
|
||||||
android:id="@+id/game_view"
|
android:id="@+id/game_view"
|
||||||
|
@ -8,10 +8,9 @@
|
|||||||
<string name="refresh">Refresh</string>
|
<string name="refresh">Refresh</string>
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<string name="refreshed">The list of ROMs has been refreshed.</string>
|
<string name="refreshed">The list of ROMs has been refreshed.</string>
|
||||||
<string name="aset_missing">ASET Header Missing</string>
|
<string name="metadata_missing">Metadata Missing</string>
|
||||||
<string name="icon">Icon</string>
|
<string name="icon">Icon</string>
|
||||||
<string name="no_rom">Cannot find any ROMs</string>
|
<string name="no_rom">Cannot find any ROMs</string>
|
||||||
<string name="nro">NROs</string>
|
|
||||||
<string name="pin">Pin</string>
|
<string name="pin">Pin</string>
|
||||||
<string name="play">Play</string>
|
<string name="play">Play</string>
|
||||||
<!-- Toolbar Logger -->
|
<!-- Toolbar Logger -->
|
||||||
|
Loading…
x
Reference in New Issue
Block a user