skyline/app/src/main/java/emu/skyline/loader/RomFile.kt

153 lines
5.0 KiB
Kotlin

/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.loader
import android.content.ContentResolver
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.*
/**
* An enumeration of all supported ROM formats
*/
enum class RomFormat(val format : Int) {
NRO(0),
NSO(1),
NCA(2),
XCI(3),
NSP(4),
}
/**
* 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 RomFormat.valueOf(uriStr.substring(uriStr.lastIndexOf(".") + 1).toUpperCase(Locale.ROOT))
}
/**
* An enumeration of all possible results when populating [RomFile]
*/
enum class LoaderResult(val value : Int) {
Success(0),
ParsingError(1),
MissingHeaderKey(2),
MissingTitleKey(3),
MissingTitleKek(4),
MissingKeyArea(5);
companion object {
fun get(value : Int) = values().first { value == it.value }
}
}
/**
* This class is used to hold an application's metadata in a serializable way
*/
data class AppEntry(var name : String, var author : String?, var icon : Bitmap?, var format : RomFormat, var uri : Uri, var loaderResult : LoaderResult) : Serializable {
constructor(context : Context, format : RomFormat, uri : Uri, loaderResult : LoaderResult) : this(context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
cursor.getString(nameIndex)
}!!.dropLast(format.name.length + 1), null, null, format, uri, loaderResult)
private fun writeObject(output : ObjectOutputStream) {
output.writeUTF(name)
output.writeObject(format)
output.writeUTF(uri.toString())
output.writeBoolean(author != null)
if (author != null)
output.writeUTF(author)
output.writeInt(loaderResult.value)
output.writeBoolean(icon != null)
icon?.let {
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
it.compress(Bitmap.CompressFormat.WEBP_LOSSY, 100, output)
else
it.compress(Bitmap.CompressFormat.WEBP, 100, output)
}
}
private fun readObject(input : ObjectInputStream) {
name = input.readUTF()
format = input.readObject() as RomFormat
uri = Uri.parse(input.readUTF())
if (input.readBoolean())
author = input.readUTF()
loaderResult = LoaderResult.get(input.readInt())
if (input.readBoolean())
icon = BitmapFactory.decodeStream(input)
}
}
/**
* This class is used as interface between libskyline and Kotlin for loaders
*/
internal class RomFile(context : Context, format : RomFormat, uri : Uri) {
/**
* @note This field is filled in by native code
*/
private var applicationName : String? = null
/**
* @note This field is filled in by native code
*/
private var applicationAuthor : String? = null
/**
* @note This field is filled in by native code
*/
private var rawIcon : ByteArray? = null
val appEntry : AppEntry
var result = LoaderResult.Success
val valid : Boolean
get() = result == LoaderResult.Success
init {
System.loadLibrary("skyline")
context.contentResolver.openFileDescriptor(uri, "r")!!.use {
result = LoaderResult.get(populate(format.ordinal, it.fd, context.filesDir.canonicalPath + "/"))
}
appEntry = applicationName?.let { name ->
applicationAuthor?.let { author ->
rawIcon?.let { icon ->
AppEntry(name, author, BitmapFactory.decodeByteArray(icon, 0, icon.size), format, uri, result)
}
}
} ?: AppEntry(context, format, uri, result)
}
/**
* Parses ROM and writes its metadata to [applicationName], [applicationAuthor] and [rawIcon]
* @param format The format of the ROM
* @param romFd A file descriptor of the ROM
* @param appFilesPath Path to internal app data storage, needed to read imported keys
* @return A pointer to the newly allocated object, or 0 if the ROM is invalid
*/
private external fun populate(format : Int, romFd : Int, appFilesPath : String) : Int
}