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:
◱ PixelyIon 2020-04-03 17:17:32 +05:30 committed by ◱ PixelyIon
parent f4968793b8
commit 55a9f8e937
12 changed files with 317 additions and 113 deletions

View File

@ -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)
* 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):
#### 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
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.

View File

@ -44,7 +44,7 @@
android:value="emu.skyline.SettingsActivity" />
</activity>
<activity
android:name="emu.skyline.GameActivity"
android:name="emu.skyline.EmulationActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleInstance"
android:screenOrientation="landscape">

View File

@ -21,7 +21,7 @@ void signalHandler(int signal) {
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;
FaultCount = 0;
@ -43,9 +43,9 @@ extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_executeRom(JNIEnv *env,
try {
skyline::kernel::OS os(jvmManager, logger, settings);
const char *romString = env->GetStringUTFChars(romJstring, nullptr);
logger->Info("Launching ROM {}", romString);
env->ReleaseStringUTFChars(romJstring, romString);
const char *romUri = env->GetStringUTFChars(romUriJstring, nullptr);
logger->Info("Launching ROM {}", romUri);
env->ReleaseStringUTFChars(romUriJstring, romUri);
os.Execute(romFd, static_cast<skyline::TitleFormat>(romType));
} catch (std::exception &e) {
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()));
}
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);
Halt = halt;
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);
if (!env->IsSameObject(Surface, nullptr))
env->DeleteGlobalRef(Surface);

View File

@ -10,105 +10,175 @@ import android.net.Uri
import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.util.Log
import android.view.InputQueue
import android.view.Surface
import android.view.SurfaceHolder
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import emu.skyline.loader.getTitleFormat
import emu.skyline.loader.getRomFormat
import kotlinx.android.synthetic.main.game_activity.*
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 {
System.loadLibrary("skyline") // libskyline.so
}
/**
* The file descriptor of the ROM
*/
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)
/**
* 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?)
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")!!
gameThread = Thread {
emulationThread = Thread {
while ((surface == null))
Thread.yield()
executeRom(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, logFd.fd)
if (shouldFinish)
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?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.game_activity)
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
val log = File("${applicationInfo.dataDir}/skyline.log")
logFd = ParcelFileDescriptor.open(log, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_READ_WRITE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
game_view.holder.addCallback(this)
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?) {
shouldFinish = false
setHalt(true)
gameThread.join()
emulationThread.join()
shouldFinish = true
romFd.close()
executeRom(intent?.data!!)
super.onNewIntent(intent)
}
/**
* The onDestroy handler is used to halt emulation
*/
override fun onDestroy() {
shouldFinish = false
setHalt(true)
gameThread.join()
emulationThread.join()
romFd.close()
preferenceFd.close()
logFd.close()
super.onDestroy()
}
/**
* The surfaceCreated handler passes in the surface to libskyline
*/
override fun surfaceCreated(holder: SurfaceHolder?) {
Log.d("surfaceCreated", "Holder: ${holder.toString()}")
surface = holder!!.surface
setSurface(surface)
}
/**
* The surfaceChanged handler is purely used for debugging purposes
*/
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
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?) {
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}")
surface = null
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)
}
}

View File

@ -19,8 +19,8 @@ import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import emu.skyline.adapter.GameAdapter
import emu.skyline.adapter.GameItem
import emu.skyline.adapter.AppAdapter
import emu.skyline.adapter.AppItem
import emu.skyline.loader.BaseLoader
import emu.skyline.loader.NroLoader
import emu.skyline.utility.GameDialog
@ -32,7 +32,7 @@ import kotlin.concurrent.thread
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var sharedPreferences: SharedPreferences
private var adapter = GameAdapter(this)
private var adapter = AppAdapter(this)
private fun notifyUser(text: String) {
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)) {
val document = RandomAccessDocument(this, file)
if (loader.verifyFile(document)) {
val entry = loader.getTitleEntry(document, file.uri)
val entry = loader.getAppEntry(document, file.uri)
runOnUiThread {
if (!foundCurrent) {
adapter.addHeader(getString(R.string.nro))
adapter.addHeader(loader.format.name)
foundCurrent = true
}
adapter.addItem(GameItem(entry))
adapter.addItem(AppItem(entry))
}
}
document.close()
@ -75,25 +75,31 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
Log.w("refreshFiles", "Ran into exception while loading: ${e.message}")
}
}
thread(start = true) {
val snackbar = Snackbar.make(findViewById(android.R.id.content), getString(R.string.searching_roms), Snackbar.LENGTH_INDEFINITE)
runOnUiThread { snackbar.show() }
try {
runOnUiThread { adapter.clear() }
val foundNros = findFile("nro", NroLoader(this), DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!)
runOnUiThread {
if (!foundNros)
adapter.addHeader(getString(R.string.no_rom))
try {
adapter.save(File("${applicationInfo.dataDir}/roms.bin"))
} catch (e: IOException) {
Log.w("refreshFiles", "Ran into exception while saving: ${e.message}")
}
}
sharedPreferences.edit().putBoolean("refresh_required", false).apply()
} catch (e: IllegalArgumentException) {
runOnUiThread {
sharedPreferences.edit().remove("search_location").apply()
val intent = intent
finish()
startActivity(intent)
@ -103,6 +109,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
notifyUser(e.message!!)
}
}
runOnUiThread { snackbar.dismiss() }
}
}
@ -124,15 +131,15 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
game_list.adapter = adapter
game_list.onItemClickListener = OnItemClickListener { parent: AdapterView<*>, _: View?, position: Int, _: Long ->
val item = parent.getItemAtPosition(position)
if (item is GameItem) {
val intent = Intent(this, GameActivity::class.java)
if (item is AppItem) {
val intent = Intent(this, EmulationActivity::class.java)
intent.data = item.uri
startActivity(intent)
}
}
game_list.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, _, position, _ ->
val item = parent.getItemAtPosition(position)
if (item is GameItem) {
if (item is AppItem) {
val dialog = GameDialog(item)
dialog.show(supportFragmentManager, "game")
}
@ -205,7 +212,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
2 -> {
try {
val uri = (intent!!.data!!)
val intentGame = Intent(this, GameActivity::class.java)
val intentGame = Intent(this, EmulationActivity::class.java)
intentGame.data = uri
if (resultCode != 0)
startActivityForResult(intentGame, resultCode)

View File

@ -18,39 +18,69 @@ import android.view.Window
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.graphics.drawable.toBitmap
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?
get() = meta.icon
/**
* The title of the application
*/
val title: String
get() = meta.name + " (" + type + ")"
/**
* The string used as the sub-title, we currently use the author
*/
val subTitle: String?
get() = meta.author
/**
* The URI of the application's image file
*/
val uri: Uri
get() = meta.uri
/**
* The format of the application ROM as a string
*/
private val type: String
get() = meta.romType.name
get() = meta.format.name
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) {
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) {
val position = view.tag as Int
if (getItem(position) is GameItem) {
val item = getItem(position) as GameItem
if (getItem(position) is AppItem) {
val item = getItem(position) as AppItem
if (view.id == R.id.icon) {
val builder = Dialog(context!!)
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 {
var view = convertView
val viewHolder: ViewHolder
val item = elementArray[visibleArray[position]]
if (view == null) {
viewHolder = ViewHolder()
if (item is GameItem) {
if (item is AppItem) {
val inflater = LayoutInflater.from(context)
view = inflater.inflate(R.layout.game_item, parent, false)
viewHolder.icon = view.findViewById(R.id.icon)
viewHolder.txtTitle = view.findViewById(R.id.text_title)
viewHolder.txtSub = view.findViewById(R.id.text_subtitle)
viewHolder.title = view.findViewById(R.id.text_title)
viewHolder.subtitle = view.findViewById(R.id.text_subtitle)
view.tag = viewHolder
} else if (item is BaseHeader) {
val inflater = LayoutInflater.from(context)
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
}
} else {
viewHolder = view.tag as ViewHolder
}
if (item is GameItem) {
val data = getItem(position) as GameItem
viewHolder.txtTitle!!.text = data.title
viewHolder.txtSub!!.text = data.subTitle
viewHolder.icon!!.setImageBitmap(data.icon)
if (item is AppItem) {
val data = getItem(position) as AppItem
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!!.tag = position
} else {
viewHolder.txtTitle!!.text = (getItem(position) as BaseHeader).title
viewHolder.title!!.text = (getItem(position) as BaseHeader).title
}
return view!!
}
private class ViewHolder {
var icon: ImageView? = null
var txtTitle: TextView? = null
var txtSub: TextView? = null
}
/**
* The ViewHolder object is used to hold the views associated with an object
*
* @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)
}

View File

@ -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
/**
* @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)
/**
* @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) {
/**
* This function returns a string used for searching
*/
abstract fun key(): String?
}

View File

@ -11,8 +11,6 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.OpenableColumns
import androidx.core.graphics.drawable.toBitmap
import emu.skyline.R
import emu.skyline.utility.RandomAccessDocument
import java.io.IOException
import java.io.ObjectInputStream
@ -20,52 +18,119 @@ import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.*
enum class TitleFormat {
/**
* An enumeration of all supported ROM formats
*/
enum class RomFormat {
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 = ""
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
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)) {
context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
name = cursor.getString(nameIndex)
}
/**
* This class is used to hold an application's metadata in a serializable way
*/
class AppEntry : Serializable {
/**
* 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)
private fun writeObject(output: ObjectOutputStream) {
output.writeUTF(name)
output.writeUTF(author)
output.writeObject(romType)
output.writeObject(format)
output.writeUTF(uri.toString())
output.writeBoolean(valid)
icon.compress(Bitmap.CompressFormat.WEBP, 100, output)
output.writeBoolean(author != null)
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)
private fun readObject(input: ObjectInputStream) {
name = input.readUTF()
author = input.readUTF()
romType = input.readObject() as TitleFormat
format = input.readObject() as RomFormat
uri = Uri.parse(input.readUTF())
valid = input.readBoolean()
icon = BitmapFactory.decodeStream(input)
if (input.readBoolean())
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
}

View File

@ -8,45 +8,56 @@ package emu.skyline.loader
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import emu.skyline.R
import emu.skyline.utility.RandomAccessDocument
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 {
file.seek(0x18) // Skip to NroHeader.size
val asetOffset = Integer.reverseBytes(file.readInt())
file.seek(asetOffset.toLong()) // Skip to the offset specified by NroHeader.size
val buffer = ByteArray(4)
file.read(buffer)
if (String(buffer) != "ASET") throw IOException()
file.skipBytes(0x4)
val iconOffset = java.lang.Long.reverseBytes(file.readLong())
val iconSize = Integer.reverseBytes(file.readInt())
if (iconOffset == 0L || iconSize == 0) throw IOException()
file.seek(asetOffset + iconOffset)
val iconData = ByteArray(iconSize)
file.read(iconData)
val icon = BitmapFactory.decodeByteArray(iconData, 0, iconSize)
file.seek(asetOffset + 0x18.toLong())
val nacpOffset = java.lang.Long.reverseBytes(file.readLong())
val nacpSize = java.lang.Long.reverseBytes(file.readLong())
if (nacpOffset == 0L || nacpSize == 0L) throw IOException()
file.seek(asetOffset + nacpOffset)
val name = ByteArray(0x200)
file.read(name)
val author = ByteArray(0x100)
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) {
TitleEntry(context, context.getString(R.string.aset_missing), romType, false, uri)
AppEntry(context, format, uri)
}
}
override fun verifyFile(file: RandomAccessDocument): Boolean {
try {
file.seek(0x10) // Skip to NroHeader.magic
val buffer = ByteArray(4)
file.read(buffer)
if (String(buffer) != "NRO0") return false

View File

@ -15,15 +15,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import emu.skyline.GameActivity
import emu.skyline.EmulationActivity
import emu.skyline.R
import emu.skyline.adapter.GameItem
import emu.skyline.adapter.AppItem
import kotlinx.android.synthetic.main.game_dialog.*
class GameDialog() : DialogFragment() {
var item: GameItem? = null
var item: AppItem? = null
constructor(item: GameItem) : this() {
constructor(item: AppItem) : this() {
this.item = item
}
@ -33,7 +33,7 @@ class GameDialog() : DialogFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (item is GameItem) {
if (item is AppItem) {
game_icon.setImageBitmap(item?.icon)
game_title.text = item?.title
game_subtitle.text = item?.subTitle
@ -42,16 +42,16 @@ class GameDialog() : DialogFragment() {
game_pin.setOnClickListener {
val info = ShortcutInfo.Builder(context, item?.title)
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))
val intent = Intent(context, GameActivity::class.java)
val intent = Intent(context, EmulationActivity::class.java)
intent.data = item?.uri
intent.action = Intent.ACTION_VIEW
info.setIntent(intent)
shortcutManager.requestPinShortcut(info.build(), null)
}
game_play.setOnClickListener {
val intent = Intent(activity, GameActivity::class.java)
val intent = Intent(activity, EmulationActivity::class.java)
intent.data = item?.uri
startActivity(intent)
}

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".GameActivity">
tools:context=".EmulationActivity">
<SurfaceView
android:id="@+id/game_view"

View File

@ -8,10 +8,9 @@
<string name="refresh">Refresh</string>
<!-- Main -->
<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="no_rom">Cannot find any ROMs</string>
<string name="nro">NROs</string>
<string name="pin">Pin</string>
<string name="play">Play</string>
<!-- Toolbar Logger -->