2020-03-27 15:36:02 -04:00
|
|
|
/*
|
2020-04-19 17:04:05 -04:00
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
2020-03-27 15:36:02 -04:00
|
|
|
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
|
|
|
*/
|
|
|
|
|
2019-12-02 08:41:23 -05:00
|
|
|
package emu.skyline.loader
|
|
|
|
|
2019-12-10 19:14:16 -05:00
|
|
|
import android.content.ContentResolver
|
2019-12-02 08:41:23 -05:00
|
|
|
import android.content.Context
|
|
|
|
import android.graphics.Bitmap
|
|
|
|
import android.graphics.BitmapFactory
|
|
|
|
import android.net.Uri
|
2020-10-03 05:58:34 -04:00
|
|
|
import android.os.Build
|
2019-12-02 08:41:23 -05:00
|
|
|
import android.provider.OpenableColumns
|
2020-10-03 05:58:34 -04:00
|
|
|
import java.io.ObjectInputStream
|
|
|
|
import java.io.ObjectOutputStream
|
2019-12-02 08:41:23 -05:00
|
|
|
import java.io.Serializable
|
2019-12-10 19:14:16 -05:00
|
|
|
import java.util.*
|
2019-12-02 08:41:23 -05:00
|
|
|
|
2020-04-03 07:47:32 -04:00
|
|
|
/**
|
|
|
|
* An enumeration of all supported ROM formats
|
|
|
|
*/
|
2020-07-21 14:09:59 -04:00
|
|
|
enum class RomFormat(val format : Int) {
|
|
|
|
NRO(0),
|
|
|
|
NSO(1),
|
|
|
|
NCA(2),
|
|
|
|
XCI(3),
|
|
|
|
NSP(4),
|
2019-12-02 08:41:23 -05:00
|
|
|
}
|
|
|
|
|
2020-04-03 07:47:32 -04:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2020-04-24 07:39:13 -04:00
|
|
|
fun getRomFormat(uri : Uri, contentResolver : ContentResolver) : RomFormat {
|
2019-12-10 19:14:16 -05:00
|
|
|
var uriStr = ""
|
|
|
|
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
2020-04-24 07:39:13 -04:00
|
|
|
val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
2019-12-10 19:14:16 -05:00
|
|
|
cursor.moveToFirst()
|
|
|
|
uriStr = cursor.getString(nameIndex)
|
|
|
|
}
|
2020-04-03 07:47:32 -04:00
|
|
|
return RomFormat.valueOf(uriStr.substring(uriStr.lastIndexOf(".") + 1).toUpperCase(Locale.ROOT))
|
2019-12-10 19:14:16 -05:00
|
|
|
}
|
|
|
|
|
2020-04-03 07:47:32 -04:00
|
|
|
/**
|
2020-09-14 09:53:40 -04:00
|
|
|
* An enumeration of all possible results when populating [RomFile]
|
2020-04-03 07:47:32 -04:00
|
|
|
*/
|
2020-09-14 09:53:40 -04:00
|
|
|
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 }
|
2019-12-02 08:41:23 -05:00
|
|
|
}
|
2020-09-14 09:53:40 -04:00
|
|
|
}
|
2019-12-02 08:41:23 -05:00
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
/**
|
|
|
|
* This class is used to hold an application's metadata in a serializable way
|
|
|
|
*/
|
2021-01-30 08:59:11 -05:00
|
|
|
data class AppEntry(var name : String, var author : String?, var icon : Bitmap?, var format : RomFormat, var uri : Uri, var loaderResult : LoaderResult) : Serializable {
|
2020-09-14 09:53:40 -04:00
|
|
|
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)
|
2020-10-03 05:58:34 -04:00
|
|
|
|
|
|
|
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 {
|
2020-10-05 06:04:57 -04:00
|
|
|
@Suppress("DEPRECATION")
|
2020-10-03 05:58:34 -04:00
|
|
|
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)
|
|
|
|
}
|
2019-12-02 08:41:23 -05:00
|
|
|
}
|
|
|
|
|
2020-04-03 07:47:32 -04:00
|
|
|
/**
|
2020-06-19 16:18:33 -04:00
|
|
|
* This class is used as interface between libskyline and Kotlin for loaders
|
2020-04-03 07:47:32 -04:00
|
|
|
*/
|
2020-09-14 09:53:40 -04:00
|
|
|
internal class RomFile(context : Context, format : RomFormat, uri : Uri) {
|
2020-04-03 07:47:32 -04:00
|
|
|
/**
|
2020-09-14 09:53:40 -04:00
|
|
|
* @note This field is filled in by native code
|
2020-04-03 07:47:32 -04:00
|
|
|
*/
|
2020-09-14 09:53:40 -04:00
|
|
|
private var applicationName : String? = null
|
2019-12-02 08:41:23 -05:00
|
|
|
|
2020-04-03 07:47:32 -04:00
|
|
|
/**
|
2020-09-14 09:53:40 -04:00
|
|
|
* @note This field is filled in by native code
|
2020-04-03 07:47:32 -04:00
|
|
|
*/
|
2020-09-14 09:53:40 -04:00
|
|
|
private var applicationAuthor : String? = null
|
2020-06-19 16:18:33 -04:00
|
|
|
|
|
|
|
/**
|
2020-09-14 09:53:40 -04:00
|
|
|
* @note This field is filled in by native code
|
2020-06-19 16:18:33 -04:00
|
|
|
*/
|
2020-09-14 09:53:40 -04:00
|
|
|
private var rawIcon : ByteArray? = null
|
2020-06-19 16:18:33 -04:00
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
val appEntry : AppEntry
|
2020-06-19 16:18:33 -04:00
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
var result = LoaderResult.Success
|
2020-06-19 16:18:33 -04:00
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
val valid : Boolean
|
|
|
|
get() = result == LoaderResult.Success
|
2020-06-19 16:18:33 -04:00
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
init {
|
|
|
|
System.loadLibrary("skyline")
|
2020-06-19 16:18:33 -04:00
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
context.contentResolver.openFileDescriptor(uri, "r")!!.use {
|
|
|
|
result = LoaderResult.get(populate(format.ordinal, it.fd, context.filesDir.canonicalPath + "/"))
|
2020-06-19 16:18:33 -04:00
|
|
|
}
|
|
|
|
|
2020-09-14 09:53:40 -04:00
|
|
|
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)
|
2020-06-19 16:18:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-14 09:53:40 -04:00
|
|
|
* 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
|
2020-06-19 16:18:33 -04:00
|
|
|
*/
|
2020-09-14 09:53:40 -04:00
|
|
|
private external fun populate(format : Int, romFd : Int, appFilesPath : String) : Int
|
2020-06-19 16:18:33 -04:00
|
|
|
}
|