diff --git a/README.md b/README.md index ae77914..fc75099 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # Rekado -Fusee launcher written in Kotlin and based on [NXLoader](https://github.com/DavidBuchanan314/NXLoader) injector codebase. +Payload and Lakka launcher written in Kotlin. **Application doesn't require Root on your device.** [SX Loader](https://sx.xecuter.com/) payload bundled as default. +[Lakka](https://github.com/lakka-switch/boot-scripts/tree/master/payloads) payload and coreboot files bundled in application. ## Usage * Launch application * Find a cable to connect your device to the Nintendo Switch. For proper work, this should be a cable that is designed for data transmission, not just for charging. It is advisable to use an **A-to-C** cable and an **USB OTG** adapter. * In the **"Payloads"** category, click the **"+"** button to select preloaded payload from your device's storage. Or simply transfer your payload to the Rekado folder in the device's memory. Or you can use the default payload (**SX OS**) * Enter your Nintendo Switch into **RCM** mode in any convenient way. Your Nintendo Switch will power on by itself when plugged in, be sure to hold **VOLUME +** -* Connect the device to the Nintendo Switch and allow permission for the **USB** access if necessary. Wait until the program prompts you to select payload and choose the one you need +* Connect the device to the Nintendo Switch and allow permission for the **USB** access if necessary. Wait until the program prompts you to select what do you want to inject, payload or Lakka. If you will select payload, than you will have to choose the one you need. If you will choose Lakka, you should simply wait for it to load. * Wait for payload to finish loading on your console ## Download @@ -23,11 +24,13 @@ Application doesn't require Root on your device. **Can it brick my device/console?** -This should not happen when using the "correct" loaders. But I am not responsible for possible problems. +This should not happen when using the "correct" payloads. But I am not responsible for possible problems. -**Will Recado ever run Linux?** +**Can Rekado launch Linux?** -In the near future, this is definitely not to be expected. Maybe in the future. +Yes, starting from 2.0 update Rekado can launch Lakka distribution. ## Thanks * [DavidBuchanan314](https://github.com/DavidBuchanan314) for creating NXLoader. +* [natinusala](https://github.com/natinusala) for creating lakka_linux_launcher. +* [ealfonso93](https://github.com/ealfonso93) for contributing in this project. \ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index c70f26f..785a573 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,3 +1,4 @@ cmake_minimum_required(VERSION 3.4.1) -add_library( native-lib SHARED src/main/cpp/native-lib.cpp ) +add_library( payload_launcher SHARED src/main/cpp/payload_launcher.cpp ) +add_library( lakka_launcher SHARED src/main/cpp/lakka_launcher.cpp ) include_directories(src/main/cpp/) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2e94962..7a56f00 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "com.pavelrekun.rekado" minSdkVersion minimumSDKVersion targetSdkVersion currentSDKVersion - versionCode 15 - versionName "1.3" + versionCode 20 + versionName "2.0" externalNativeBuild { cmake { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b4245..855face 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,3 +19,14 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile + +-keepattributes *Annotation* +-keepclassmembers class * { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +# Only required if you use AsyncExecutor +-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { + (java.lang.Throwable); +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 713a5ac..78c7667 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ android:name=".RekadoApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/RekadoBaseTheme"> @@ -40,7 +41,6 @@ android:exported="false" android:label="@string/app_name" android:noHistory="true" - android:process=":UsbEventReceiverActivityProcess" android:taskAffinity="com.example.taskAffinityUsbEventReceiver" android:theme="@style/RekadoInvisibleTheme"> diff --git a/app/src/main/assets/cbfs.bin b/app/src/main/assets/cbfs.bin new file mode 100644 index 0000000..a5b9192 Binary files /dev/null and b/app/src/main/assets/cbfs.bin differ diff --git a/app/src/main/assets/coreboot.rom b/app/src/main/assets/coreboot.rom new file mode 100644 index 0000000..79ba65b Binary files /dev/null and b/app/src/main/assets/coreboot.rom differ diff --git a/app/src/main/cpp/lakka_launcher.cpp b/app/src/main/cpp/lakka_launcher.cpp new file mode 100644 index 0000000..97a7a44 --- /dev/null +++ b/app/src/main/cpp/lakka_launcher.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +extern "C" JNIEXPORT void JNICALL +Java_com_pavelrekun_rekado_services_lakka_LakkaLoader_nativeControlReadUnbounded(JNIEnv *env, + jclass, + jint fd, + jint size) { + + + char* buffer = new char[sizeof(usb_ctrlrequest) + size]; + usb_ctrlrequest* header = (usb_ctrlrequest*) buffer; + header->bRequestType = USB_TYPE_STANDARD | USB_RECIP_ENDPOINT | USB_DIR_IN; + header->bRequest = 0; + header->wValue = 0; + header->wIndex = 0; + header->wLength = (__le16) size; + memset(&buffer[sizeof(usb_ctrlrequest)], 0, (size_t) size); + + usbdevfs_urb* urb = (usbdevfs_urb*) new char[sizeof(usbdevfs_urb) + 1024]; + memset(urb, 0, sizeof(usbdevfs_urb) + 1024); + urb->type = USBDEVFS_URB_TYPE_CONTROL; + urb->endpoint = 0; + urb->buffer = buffer; + urb->buffer_length = sizeof(usb_ctrlrequest) + size; + urb->usercontext = (void*) 0xf0f; + + + ioctl(fd, USBDEVFS_SUBMITURB, urb); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ioctl(fd, USBDEVFS_DISCARDURB, urb); + usbdevfs_urb* purb; + do { + if (ioctl(fd, USBDEVFS_REAPURB, &purb)) { + break; + } + } while (purb != urb); + + delete[] urb; + delete[] buffer; +} \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/payload_launcher.cpp similarity index 100% rename from app/src/main/cpp/native-lib.cpp rename to app/src/main/cpp/payload_launcher.cpp diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaContract.kt b/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaContract.kt new file mode 100644 index 0000000..84b1589 --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaContract.kt @@ -0,0 +1,15 @@ +package com.pavelrekun.rekado.screens.lakka_fragment + + interface LakkaContract { + + interface View { + + fun initViews() + + fun initCBFSCategory() + + fun initCorebootCategory() + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaFragment.kt b/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaFragment.kt new file mode 100644 index 0000000..034664b --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaFragment.kt @@ -0,0 +1,27 @@ +package com.pavelrekun.rekado.screens.lakka_fragment + +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.pavelrekun.rekado.R +import com.pavelrekun.rekado.base.BaseActivity + +class LakkaFragment : Fragment() { + + private lateinit var mvpView: LakkaContract.View + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_lakka, container, false) + val activity = activity as BaseActivity + + mvpView = LakkaView(activity) + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mvpView.initViews() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaView.kt b/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaView.kt new file mode 100644 index 0000000..5601c71 --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/screens/lakka_fragment/LakkaView.kt @@ -0,0 +1,45 @@ +package com.pavelrekun.rekado.screens.lakka_fragment + +import android.support.v4.content.ContextCompat +import com.pavelrekun.rekado.R +import com.pavelrekun.rekado.base.BaseActivity +import com.pavelrekun.rekado.services.lakka.LakkaHelper +import kotlinx.android.synthetic.main.fragment_lakka.* + +class LakkaView(private val activity: BaseActivity) : LakkaContract.View { + + override fun initViews() { + activity.setTitle(R.string.navigation_lakka) + + initCBFSCategory() + initCorebootCategory() + } + + override fun initCBFSCategory() { + if (LakkaHelper.checkCBFSPresent()) { + activity.lakkaCBFSStatus.text = activity.getString(R.string.lakka_tools_ready) + activity.lakkaCBFSStatus.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lakka_ready, 0, 0, 0) + activity.lakkaCBFSStatus.setTextColor(ContextCompat.getColor(activity, R.color.colorGreen)) + } else { + activity.lakkaCBFSStatus.text = activity.getString(R.string.lakka_tools_not_ready) + activity.lakkaCBFSStatus.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lakka_not_ready, 0, 0, 0) + activity.lakkaCBFSStatus.setTextColor(ContextCompat.getColor(activity, R.color.colorRed)) + } + + activity.lakkaCBFSDate.text = activity.getString(R.string.lakka_last_update, LakkaHelper.PAYLOAD_UPDATE_DATE) + } + + override fun initCorebootCategory() { + if (LakkaHelper.checkCorebootPresent()) { + activity.lakkaCorebootStatus.text = activity.getString(R.string.lakka_tools_ready) + activity.lakkaCorebootStatus.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lakka_ready, 0, 0, 0) + activity.lakkaCorebootStatus.setTextColor(ContextCompat.getColor(activity, R.color.colorGreen)) + } else { + activity.lakkaCorebootStatus.text = activity.getString(R.string.lakka_tools_not_ready) + activity.lakkaCorebootStatus.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lakka_not_ready, 0, 0, 0) + activity.lakkaCorebootStatus.setTextColor(ContextCompat.getColor(activity, R.color.colorRed)) + } + + activity.lakkaCorebootDate.text = activity.getString(R.string.lakka_last_update, LakkaHelper.COREBOOT_UPDATE_DATE) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/logs_fragment/adapters/LogsAdapter.kt b/app/src/main/java/com/pavelrekun/rekado/screens/logs_fragment/adapters/LogsAdapter.kt index d2598ea..7fc3fd8 100644 --- a/app/src/main/java/com/pavelrekun/rekado/screens/logs_fragment/adapters/LogsAdapter.kt +++ b/app/src/main/java/com/pavelrekun/rekado/screens/logs_fragment/adapters/LogsAdapter.kt @@ -9,6 +9,7 @@ import com.pavelrekun.rekado.R import com.pavelrekun.rekado.RekadoApplication import com.pavelrekun.rekado.data.Log import com.pavelrekun.rekado.services.logs.LogHelper +import com.pavelrekun.rekado.services.logs.LogHelper.ERROR import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.item_log.* @@ -36,7 +37,7 @@ class LogsAdapter(var data: MutableList) : RecyclerView.Adapter fragment = PayloadsFragment() + R.id.navigation_lakka -> fragment = LakkaFragment() R.id.navigation_instructions -> fragment = InstructionsFragment() R.id.navigation_logs -> fragment = LogsFragment() } diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsFragment.kt b/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsFragment.kt index 2c22664..f713a3f 100644 --- a/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsFragment.kt +++ b/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsFragment.kt @@ -33,7 +33,7 @@ class PayloadsFragment : Fragment() { mvpView.onRequestPermissionsResult(requestCode, permissions, grantResults) } - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + @Subscribe(threadMode = ThreadMode.MAIN) fun onEvent(event: Events.UpdateListEvent) { mvpView.updateList() } diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsView.kt b/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsView.kt index ecb328f..f4ff5b2 100644 --- a/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsView.kt +++ b/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/PayloadsView.kt @@ -1,7 +1,6 @@ package com.pavelrekun.rekado.screens.payload_fragment import android.Manifest -import android.content.DialogInterface import android.content.pm.PackageManager import android.support.v4.app.Fragment import android.support.v7.widget.LinearLayoutManager @@ -16,7 +15,6 @@ import com.pavelrekun.rekado.services.logs.LogHelper import com.pavelrekun.rekado.services.payloads.PayloadHelper import com.pavelrekun.rekado.services.utils.MemoryUtils import com.pavelrekun.rekado.services.utils.PermissionsUtils -import com.pavelrekun.rekado.services.utils.toFile import kotlinx.android.synthetic.main.fragment_payloads.* import org.greenrobot.eventbus.EventBus import java.io.File @@ -27,8 +25,6 @@ class PayloadsView(private val activity: BaseActivity, private val fragment: Fra private lateinit var adapter: PayloadsAdapter - private var chooserStorageInternal = true - override fun initViews() { activity.setTitle(R.string.navigation_payloads) @@ -104,13 +100,13 @@ class PayloadsView(private val activity: BaseActivity, private val fragment: Fra } try { - pathFile.toFile(PayloadHelper.FOLDER_PATH + "/" + payload.name) + MemoryUtils.toFile(pathFile, (PayloadHelper.FOLDER_PATH + "/" + payload.name)) - EventBus.getDefault().postSticky(Events.UpdateListEvent()) - LogHelper.log(1, "Added new payload: ${payload.name}") + EventBus.getDefault().post(Events.UpdateListEvent()) + LogHelper.log(LogHelper.INFO, "Added new payload: ${payload.name}") } catch (e: IOException) { e.printStackTrace() - LogHelper.log(0, "Failed to add payload: ${payload.name}") + LogHelper.log(LogHelper.ERROR, "Failed to add payload: ${payload.name}") } } } \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/adapters/PayloadsAdapter.kt b/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/adapters/PayloadsAdapter.kt index 6ed7ad9..505e357 100644 --- a/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/adapters/PayloadsAdapter.kt +++ b/app/src/main/java/com/pavelrekun/rekado/screens/payload_fragment/adapters/PayloadsAdapter.kt @@ -42,8 +42,8 @@ class PayloadsAdapter(var data: MutableList) : RecyclerView.Adapter(R.id.dialog_injector_selector_payload) + val bootLakka = view.findViewById(R.id.dialog_injector_selector_lakka) + + val dialog = builder.create() + dialog.show() + + dialog.setOnDismissListener { + EventBus.getDefault().post(Events.InjectorMethodNotSelected()) + } + + bootPayload.setOnClickListener { + EventBus.getDefault().post(Events.InjectorMethodPayloadSelected()) + dialog.hide() + } + + bootLakka.setOnClickListener { + EventBus.getDefault().post(Events.InjectorMethodLakkaSelected()) + dialog.hide() + } + + return dialog + } + + fun showPayloadsDialog(activity: BaseActivity): MaterialDialog { val dialog = MaterialDialog.Builder(activity) .title(R.string.dialog_loader_title) @@ -22,14 +54,19 @@ object Dialogs { .items(PayloadHelper.getNames()) .itemsCallback { dialog, _, _, name -> PayloadHelper.putChosen(PayloadHelper.find(name.toString()) as Payload) + EventBus.getDefault().post(Events.PayloadSelected()) dialog.hide() } + .dismissListener { - EventBus.getDefault().postSticky(Events.PayloadNotSelected()) + EventBus.getDefault().post(Events.PayloadNotSelected()) } + .build() dialog.show() + + return dialog } fun showPayloadsResetDialog(activity: BaseActivity): AlertDialog { diff --git a/app/src/main/java/com/pavelrekun/rekado/services/eventbus/Events.kt b/app/src/main/java/com/pavelrekun/rekado/services/eventbus/Events.kt index 5f4b1a7..c210830 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/eventbus/Events.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/eventbus/Events.kt @@ -8,4 +8,10 @@ class Events { class PayloadNotSelected + class InjectorMethodPayloadSelected + + class InjectorMethodLakkaSelected + + class InjectorMethodNotSelected + } \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/lakka/LakkaHelper.kt b/app/src/main/java/com/pavelrekun/rekado/services/lakka/LakkaHelper.kt new file mode 100644 index 0000000..5d4efb9 --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/services/lakka/LakkaHelper.kt @@ -0,0 +1,21 @@ +package com.pavelrekun.rekado.services.lakka + +object LakkaHelper { + + const val PAYLOAD_FILENAME = "cbfs.bin" + const val COREBOOT_FILENAME = "coreboot.rom" + + const val PAYLOAD_UPDATE_DATE = "01.07.2018" + const val COREBOOT_UPDATE_DATE = "05.05.2018" + + // TODO: For future releases + fun checkCBFSPresent(): Boolean { + return true + } + + // TODO: For future releases + fun checkCorebootPresent(): Boolean { + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/lakka/LakkaLoader.kt b/app/src/main/java/com/pavelrekun/rekado/services/lakka/LakkaLoader.kt new file mode 100644 index 0000000..14d3ae4 --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/services/lakka/LakkaLoader.kt @@ -0,0 +1,203 @@ +package com.pavelrekun.rekado.services.lakka + +import android.content.Context +import android.hardware.usb.* +import com.pavelrekun.rekado.RekadoApplication +import com.pavelrekun.rekado.services.logs.LogHelper +import com.pavelrekun.rekado.services.logs.LogHelper.ERROR +import com.pavelrekun.rekado.services.logs.LogHelper.INFO +import com.pavelrekun.rekado.services.usb.USBHandler +import com.pavelrekun.rekado.services.utils.BinaryUtils +import com.pavelrekun.rekado.services.utils.Utils +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream + +class LakkaLoader : USBHandler { + + companion object { + init { + System.loadLibrary("lakka_launcher") + } + + private const val SOURCE_BASE = 0x4000fc84 + private const val TARGET = SOURCE_BASE - 0xc - 2 * 4 - 2 * 4 + private const val DESTINATION_BASE = 0x40009000 + private const val OVERRIDE_LENGTH = TARGET - DESTINATION_BASE + private const val PAYLOAD_BASE = 0x40010000 + } + + private val context = RekadoApplication.instance.applicationContext + + private lateinit var usbConnection: UsbDeviceConnection + private lateinit var usbInterface: UsbInterface + private lateinit var startEndpoint: UsbEndpoint + private lateinit var endEndpoint: UsbEndpoint + private lateinit var usbManager: UsbManager + + override fun handleDevice(device: UsbDevice) { + usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager + usbInterface = device.getInterface(0) + + startEndpoint = usbInterface.getEndpoint(0) + endEndpoint = usbInterface.getEndpoint(1) + + usbConnection = usbManager.openDevice(device) + + claimInterface() + + readInitMessage() + + sanityCheck(SOURCE_BASE, DESTINATION_BASE) + + val header = ByteArray(4 + 0x2a4) + BinaryUtils.writeInt32(header, 0, 0x30008) + write(header, 0, header.size) + + val xferLength = 0x1000 + + val payload = ByteArrayOutputStream() + val buffer = ByteArray(0x1a3a * 4) + payload.write(buffer) + var entry = PAYLOAD_BASE + payload.size() + 4 + entry = entry or 1 + BinaryUtils.writeInt32(buffer, 0, entry) + payload.write(buffer, 0, 4) + readAdditionalFile(LakkaHelper.PAYLOAD_FILENAME, payload) + val payloadData = payload.toByteArray() + + var i = 0 + while (i < payloadData.size) { + write(payloadData, i, Math.min(xferLength, payloadData.size - i)) + i += xferLength + } + + try { + sanityCheck(SOURCE_BASE, DESTINATION_BASE) + } catch (e: RuntimeException) { + LogHelper.log(INFO, "Adding more data!") + val data = ByteArray(xferLength) + write(data, 0, data.size) + } + + LogHelper.log(INFO, "Triggering Lakka!") + + nativeControlReadUnbounded(usbConnection.fileDescriptor, OVERRIDE_LENGTH) + + val tempBuffer = ByteArray(4096) + while (true) { + val length = usbConnection.bulkTransfer(startEndpoint, tempBuffer, tempBuffer.size, 0) + + if (length < 0) { + continue + } + + val cmd = String(tempBuffer, 0, length).trim() + LogHelper.log(INFO, "Entering $cmd") + + if (cmd == "CBFS") { + cbfs() + break + } + + } + } + + private fun claimInterface() { + usbConnection.claimInterface(usbInterface, true) + } + + override fun releaseDevice() { + usbConnection.releaseInterface(usbInterface) + } + + @Throws(IOException::class) + private fun readAdditionalFile(name: String, outputStream: OutputStream) { + val inputStream = context.assets.open(name) + inputStream.copyTo(outputStream, 4096) + } + + private fun write(data: ByteArray, offset: Int, length: Int) { + val ret = usbConnection.bulkTransfer(endEndpoint, data, offset, length, 0) + if (ret < length) { + LogHelper.log(ERROR, "Write failed (ret = $ret, expected = $length)!") + } + } + + private fun readInitMessage() { + val data = ByteArray(0x10) + val length = usbConnection.bulkTransfer(startEndpoint, data, data.size, 20) + + if (length >= 0) { + LogHelper.log(INFO, "Device ID: ${Utils.bytesToHex(data)}") + } else { + LogHelper.log(ERROR, "Failed to get Device ID!") + } + } + + private fun sanityCheck(sourceBase: Int, destinationBase: Int) { + val buffer = ByteArray(0x1000) + val length = usbConnection.controlTransfer(0x82, 0, 0, 0, buffer, buffer.size, 0) + + if (length != 0x1000) { + LogHelper.log(ERROR, "Failed to read length: $length!") + } + + val currentSource = BinaryUtils.readInt32(buffer, 0xc) + val currentDestination = BinaryUtils.readInt32(buffer, 0x14) + + if (currentSource != sourceBase || currentDestination != destinationBase) { + throw RuntimeException("Sanity check failed (current source = $currentSource, current destination = $currentDestination)!") + } + } + + @Throws(IOException::class) + private fun cbfs() { + val dataStream = ByteArrayOutputStream() + readAdditionalFile(LakkaHelper.COREBOOT_FILENAME, dataStream) + + val data = dataStream.toByteArray() + + if (data.size < 20 * 1024) { + LogHelper.log(ERROR, "Invalid coreboot.rom!") + } + + val inBuffer = ByteArray(8) + + while (true) { + val inLength = usbConnection.bulkTransfer(startEndpoint, inBuffer, 8, 0) + if (inLength < 8) { + LogHelper.log(ERROR, "Failed to read coreboot.rom!") + } + + var offset = BinaryUtils.readInt32BE(inBuffer, 0) + var length = BinaryUtils.readInt32BE(inBuffer, 4) + + if (offset + length == 0) { + return + } + + LogHelper.log(INFO, "Sent 0x${length.toString(16)} bytes") + + while (length > 0) { + var tempLength = length + + if (tempLength > 32 * 1024) { + tempLength = 32 * 1024 + } + + val ret = usbConnection.bulkTransfer(endEndpoint, data, offset, tempLength, 0) + + if (ret < 0) { + LogHelper.log(ERROR, "Failed to transfer $ret!") + } + + offset += ret + length -= ret + } + + } + } + + private external fun nativeControlReadUnbounded(fd: Int, size: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/logs/LogHelper.kt b/app/src/main/java/com/pavelrekun/rekado/services/logs/LogHelper.kt index 6a63992..f15aedc 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/logs/LogHelper.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/logs/LogHelper.kt @@ -7,11 +7,14 @@ object LogHelper { private const val LOGS_LIST_KEY = "LOGS_LIST_KEY" + const val INFO = 1 + const val ERROR = 0 + private lateinit var logsList: MutableList fun init() { logsList = ArrayList() - log(1, "Application started!") + log(INFO, "Application started!") saveLogs() } diff --git a/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadHelper.kt b/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadHelper.kt index 89591e5..e15f004 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadHelper.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadHelper.kt @@ -70,7 +70,6 @@ object PayloadHelper { fun putChosen(payload: Payload) { Paper.book().write(CHOSEN_PAYLOAD, payload) - EventBus.getDefault().postSticky(Events.PayloadSelected()) } fun getChosen(): Payload { diff --git a/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadLoader.kt b/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadLoader.kt index 623f43e..10ecb32 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadLoader.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/payloads/PayloadLoader.kt @@ -1,10 +1,15 @@ package com.pavelrekun.rekado.services.payloads import android.content.Context +import android.content.ServiceConnection import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbDeviceConnection +import android.hardware.usb.UsbInterface import android.hardware.usb.UsbManager import com.pavelrekun.rekado.RekadoApplication import com.pavelrekun.rekado.services.logs.LogHelper +import com.pavelrekun.rekado.services.logs.LogHelper.ERROR +import com.pavelrekun.rekado.services.logs.LogHelper.INFO import com.pavelrekun.rekado.services.usb.USBHandler import com.pavelrekun.rekado.services.utils.Utils import java.io.FileInputStream @@ -16,7 +21,7 @@ class PayloadLoader : USBHandler { companion object { init { - System.loadLibrary("native-lib") + System.loadLibrary("payload_launcher") } private const val RCM_PAYLOAD_ADDR = 0x40010000 @@ -25,30 +30,33 @@ class PayloadLoader : USBHandler { private const val MAX_LENGTH = 0x30298 } + private lateinit var usbConnection: UsbDeviceConnection + private lateinit var usbInterface: UsbInterface + override fun handleDevice(device: UsbDevice) { - LogHelper.log(1, "Starting selected payload!") + LogHelper.log(INFO, "Triggering selected payload!") val context = RekadoApplication.instance.applicationContext val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager - val usbInterface = device.getInterface(0) + usbInterface = device.getInterface(0) val startEndpoint = usbInterface.getEndpoint(0) val endEndpoint = usbInterface.getEndpoint(1) - val usbConnection = usbManager.openDevice(device) + usbConnection = usbManager.openDevice(device) usbConnection.claimInterface(usbInterface, true) /* [1] - Read device ID */ val deviceID = ByteArray(16) if (usbConnection.bulkTransfer(startEndpoint, deviceID, deviceID.size, 999) != deviceID.size) { - LogHelper.log(0, "Failed to get Device ID!") + LogHelper.log(ERROR, "Failed to get Device ID!") return } - LogHelper.log(1, "Device ID: ${Utils.bytesToHex(deviceID)}") + LogHelper.log(INFO, "Device ID: ${Utils.bytesToHex(deviceID)}") /* [2] - Building payload */ @@ -83,7 +91,7 @@ class PayloadLoader : USBHandler { try { payload.put(getPayload()) } catch (e: IOException) { - LogHelper.log(0, "Failed to read payload: $e") + LogHelper.log(ERROR, "Failed to read payload: $e") return } @@ -97,36 +105,37 @@ class PayloadLoader : USBHandler { while (bytesSent < unPaddedLength || lowBuffer) { payload.get(chunk) if (usbConnection.bulkTransfer(endEndpoint, chunk, chunk.size, 999) != chunk.size) { - LogHelper.log(0, "Sending payload failed at offset $bytesSent") + LogHelper.log(ERROR, "Sending payload failed at offset $bytesSent") return } lowBuffer = lowBuffer xor true bytesSent += 0x1000 } - LogHelper.log(1, "Sent $bytesSent bytes") + LogHelper.log(INFO, "Sent $bytesSent bytes") // 0x7000 = STACK_END = high DMA buffer address when (nativeTriggerExploit(usbConnection.fileDescriptor, 0x7000)) { - 0 -> LogHelper.log(1, "Exploit triggered!") - -1 -> LogHelper.log(0, "SUBMITURB failed!") - -2 -> LogHelper.log(0, "DISCARDURB failed!") - -3 -> LogHelper.log(0, "REAPURB failed!") - -4 -> LogHelper.log(0, "Wrong URB reaped! Maybe that doesn't matter?") + 0 -> LogHelper.log(INFO, "Exploit triggered!") + -1 -> LogHelper.log(ERROR, "SUBMITURB failed!") + -2 -> LogHelper.log(ERROR, "DISCARDURB failed!") + -3 -> LogHelper.log(ERROR, "REAPURB failed!") + -4 -> LogHelper.log(ERROR, "Wrong URB reaped! Maybe that doesn't matter?") } + } + override fun releaseDevice() { usbConnection.releaseInterface(usbInterface) - usbConnection.close() } private fun getPayload(): ByteArray { val chosenPayload = PayloadHelper.getChosen() val chosenPayloadFile = FileInputStream(chosenPayload.path) - LogHelper.log(1, "Opening chosen payload: ${chosenPayload.name}") + LogHelper.log(INFO, "Opening chosen payload: ${chosenPayload.name}") val chosenPayloadData = ByteArray(chosenPayloadFile.available()) - LogHelper.log(1, "Read ${Integer.toString(chosenPayloadFile.read(chosenPayloadData))} bytes from payload file!") + LogHelper.log(INFO, "Read ${Integer.toString(chosenPayloadFile.read(chosenPayloadData))} bytes from payload file!") chosenPayloadFile.close() return chosenPayloadData @@ -134,8 +143,8 @@ class PayloadLoader : USBHandler { /** - * A native method that is implemented by the 'native-lib' native library, + * A native method that is implemented by the 'payload_launcher' native library, * which is packaged with this application. */ - external fun nativeTriggerExploit(fd: Int, length: Int): Int + private external fun nativeTriggerExploit(fd: Int, length: Int): Int } \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/usb/USBHandler.kt b/app/src/main/java/com/pavelrekun/rekado/services/usb/USBHandler.kt index c68c014..027d4f2 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/usb/USBHandler.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/usb/USBHandler.kt @@ -6,4 +6,6 @@ interface USBHandler { fun handleDevice(device: UsbDevice) + fun releaseDevice() + } \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/usb/USBReceiver.kt b/app/src/main/java/com/pavelrekun/rekado/services/usb/USBReceiver.kt index bde4c21..20f3bb9 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/usb/USBReceiver.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/usb/USBReceiver.kt @@ -3,14 +3,19 @@ package com.pavelrekun.rekado.services.usb import android.hardware.usb.UsbDevice import android.hardware.usb.UsbManager import android.os.Bundle -import android.os.Process +import android.support.v7.app.AlertDialog +import com.afollestad.materialdialogs.MaterialDialog import com.pavelrekun.rekado.base.BaseActivity import com.pavelrekun.rekado.services.dialogs.Dialogs import com.pavelrekun.rekado.services.eventbus.Events +import com.pavelrekun.rekado.services.lakka.LakkaLoader import com.pavelrekun.rekado.services.logs.LogHelper +import com.pavelrekun.rekado.services.logs.LogHelper.ERROR +import com.pavelrekun.rekado.services.logs.LogHelper.INFO import com.pavelrekun.rekado.services.payloads.PayloadHelper import com.pavelrekun.rekado.services.payloads.PayloadLoader import com.pavelrekun.rekado.services.utils.SettingsUtils +import com.pavelrekun.rekado.services.utils.SwitchUtils import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -18,71 +23,102 @@ import org.greenrobot.eventbus.ThreadMode class USBReceiver : BaseActivity() { - companion object { - private const val APX_VID = 0x0955 - private const val APX_PID = 0x7321 - } - - private var vid: Int = 0 - private var pid: Int = 0 - private lateinit var device: UsbDevice + private var usbHandler: USBHandler? = null + private lateinit var injectorChooserDialog: AlertDialog + private lateinit var payloadChooserDialog: MaterialDialog + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (intent.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) { device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) - vid = device.vendorId - pid = device.productId - - LogHelper.log(1, "USB device connected: ${device.deviceName}") + LogHelper.log(INFO, "USB device connected: ${device.deviceName}") if (SettingsUtils.checkAutoInjectorEnabled()) { PayloadHelper.putChosen(PayloadHelper.find(SettingsUtils.getAutoInjectorPayload())!!) injectPayload() } else { - Dialogs.showPayloadsDialog(this) + injectorChooserDialog = Dialogs.showInjectorSelectorDialog(this) } - } } private fun injectPayload() { - if (vid == APX_VID && pid == APX_PID) { + if (SwitchUtils.isRCM(device)) { usbHandler = PayloadLoader() } usbHandler?.handleDevice(device) - LogHelper.log(1, "Payload loading finished for device: " + device.deviceName) + LogHelper.log(INFO, "Payload loading finished for device: ${device.deviceName}") finishReceiver() } - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + private fun injectLakka() { + try { + if (SwitchUtils.isRCM(device)) { + usbHandler = LakkaLoader() + usbHandler?.handleDevice(device) + LogHelper.log(INFO, "Lakka loading finished for device: ${device.deviceName}") + } + } catch (t: Throwable) { + LogHelper.log(ERROR, "Failed to inject Lakka!") + } + + finishReceiver() + } + + @Subscribe(threadMode = ThreadMode.MAIN) fun onEvent(event: Events.PayloadSelected) { injectPayload() } - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + @Subscribe(threadMode = ThreadMode.MAIN) fun onEvent(event: Events.PayloadNotSelected) { finishReceiver() } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEvent(event: Events.InjectorMethodNotSelected) { + finishReceiver() + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onEvent(event: Events.InjectorMethodPayloadSelected) { + payloadChooserDialog = Dialogs.showPayloadsDialog(this) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEvent(event: Events.InjectorMethodLakkaSelected) { + injectLakka() + } + override fun onStart() { super.onStart() EventBus.getDefault().register(this) } override fun onStop() { - super.onStop() EventBus.getDefault().unregister(this) + super.onStop() } private fun finishReceiver() { - Process.killProcess(Process.myPid()) + if (this::injectorChooserDialog.isInitialized && injectorChooserDialog.isShowing) { + injectorChooserDialog.dismiss() + } + + if (this::payloadChooserDialog.isInitialized && payloadChooserDialog.isShowing) { + payloadChooserDialog.dismiss() + } + + usbHandler?.releaseDevice() + usbHandler = null + finish() } } \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/utils/BinaryUtils.java b/app/src/main/java/com/pavelrekun/rekado/services/utils/BinaryUtils.java new file mode 100644 index 0000000..7398cdb --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/services/utils/BinaryUtils.java @@ -0,0 +1,25 @@ +package com.pavelrekun.rekado.services.utils; + +public class BinaryUtils { + + public static int readInt32(byte[] buf, int off) { + return ((buf[off] & 0xff) | + ((buf[off + 1] & 0xff) << 8) | + ((buf[off + 2] & 0xff) << 16) | + ((buf[off + 3] & 0xff) << 24)); + } + + public static int readInt32BE(byte[] buf, int off) { + return ((buf[off + 3] & 0xff) | + ((buf[off + 2] & 0xff) << 8) | + ((buf[off + 1] & 0xff) << 16) | + ((buf[off] & 0xff) << 24)); + } + + public static void writeInt32(byte[] buf, int off, int i) { + buf[off] = (byte) (i & 0xff); + buf[off + 1] = (byte) ((i >> 8) & 0xff); + buf[off + 2] = (byte) ((i >> 16) & 0xff); + buf[off + 3] = (byte) ((i >> 24) & 0xff); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/utils/MemoryUtils.kt b/app/src/main/java/com/pavelrekun/rekado/services/utils/MemoryUtils.kt index 7bab0f3..95cdb33 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/utils/MemoryUtils.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/utils/MemoryUtils.kt @@ -1,6 +1,5 @@ package com.pavelrekun.rekado.services.utils -import android.os.Environment import com.pavelrekun.rekado.RekadoApplication import com.pavelrekun.rekado.services.eventbus.Events import com.pavelrekun.rekado.services.payloads.PayloadHelper @@ -31,62 +30,7 @@ object MemoryUtils { File(path).delete() } - fun checkExternalMemoryPresent() = getExternalMemoryPaths() != null - - private fun getExternalMemoryPaths(): ArrayList? { - val context = RekadoApplication.instance.applicationContext - val externalCacheDirs = context.externalCacheDirs - - if (externalCacheDirs == null || externalCacheDirs.isEmpty()) - return null - - if (externalCacheDirs.size == 1) { - if (externalCacheDirs[0] == null) - return null - - val storageState = Environment.getExternalStorageState(externalCacheDirs[0]) - - if (Environment.MEDIA_MOUNTED != storageState) - return null - - if (Environment.isExternalStorageEmulated()) - return null - } - - val result = ArrayList() - - if (externalCacheDirs.size == 1) - result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0])) - - for (i in 1 until externalCacheDirs.size) { - val file = externalCacheDirs[i] ?: continue - - val storageState = Environment.getExternalStorageState(file) - - if (Environment.MEDIA_MOUNTED == storageState) - result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i])) - } - - return if (result.isEmpty()) null else result - + fun toFile(file: File, path: String): File { + return file.copyTo(File(path), true) } - - private fun getRootOfInnerSdCardFolder(currentFile: File?): String? { - var file = currentFile ?: return null - - val totalSpace = file.totalSpace - - while (true) { - val parentFile = file.parentFile - - if (parentFile == null || parentFile.totalSpace != totalSpace) - return file.absolutePath - - file = parentFile - } - } -} - -fun File.toFile(path: String) { - this.copyTo(File(path), true) } \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/utils/SwitchUtils.kt b/app/src/main/java/com/pavelrekun/rekado/services/utils/SwitchUtils.kt new file mode 100644 index 0000000..94f8eb9 --- /dev/null +++ b/app/src/main/java/com/pavelrekun/rekado/services/utils/SwitchUtils.kt @@ -0,0 +1,19 @@ +package com.pavelrekun.rekado.services.utils + +import android.hardware.usb.UsbDevice + +object SwitchUtils { + + fun isRCM(device: UsbDevice): Boolean { + return device.vendorId == 0x0955 && device.productId == 0x7321 + } + + fun isUBoot(device: UsbDevice): Boolean { + return device.vendorId == 0x0955 && device.productId == 0x701a + } + + fun isCompatible(device: UsbDevice): Boolean { + return isRCM(device) || isUBoot(device) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/pavelrekun/rekado/services/utils/Utils.kt b/app/src/main/java/com/pavelrekun/rekado/services/utils/Utils.kt index 4c892f5..b51be68 100644 --- a/app/src/main/java/com/pavelrekun/rekado/services/utils/Utils.kt +++ b/app/src/main/java/com/pavelrekun/rekado/services/utils/Utils.kt @@ -18,7 +18,7 @@ object Utils { customTabsIntent.launchUrl(RekadoApplication.instance.applicationContext, Uri.parse(url)) } - fun bytesToHex(bytes: ByteArray) : String{ + fun bytesToHex(bytes: ByteArray): String { val result = StringBuffer() bytes.forEach { diff --git a/app/src/main/res/drawable-nodpi/ic_launcher_hi_resolution.png b/app/src/main/res/drawable-nodpi/ic_launcher_hi_resolution.png new file mode 100644 index 0000000..a26e6e7 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_launcher_hi_resolution.png differ diff --git a/app/src/main/res/drawable/ic_lakka_not_ready.xml b/app/src/main/res/drawable/ic_lakka_not_ready.xml new file mode 100644 index 0000000..1af2383 --- /dev/null +++ b/app/src/main/res/drawable/ic_lakka_not_ready.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lakka_ready.xml b/app/src/main/res/drawable/ic_lakka_ready.xml new file mode 100644 index 0000000..d97f6a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_lakka_ready.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_navigation_back.xml b/app/src/main/res/drawable/ic_navigation_back.xml index 17e22cb..81fcb16 100644 --- a/app/src/main/res/drawable/ic_navigation_back.xml +++ b/app/src/main/res/drawable/ic_navigation_back.xml @@ -6,6 +6,6 @@ android:viewportWidth="24"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_navigation_lakka.xml b/app/src/main/res/drawable/ic_navigation_lakka.xml new file mode 100644 index 0000000..a858748 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation_lakka.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index 67c8a8f..94b16e8 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -14,7 +14,7 @@ android:elevation="3dp" app:navigationIcon="@drawable/ic_navigation_back" app:title="@string/navigation_about" - app:titleTextColor="?attr/colorAccent" /> + app:titleTextColor="@color/colorWhite" /> + android:src="@drawable/ic_launcher_hi_resolution" /> @@ -122,11 +123,11 @@ + app:titleTextColor="@color/colorWhite" /> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_lakka.xml b/app/src/main/res/layout/fragment_lakka.xml new file mode 100644 index 0000000..78ece0c --- /dev/null +++ b/app/src/main/res/layout/fragment_lakka.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_bottom_navigation.xml b/app/src/main/res/menu/menu_bottom_navigation.xml index 4eeb60a..a2af2e0 100644 --- a/app/src/main/res/menu/menu_bottom_navigation.xml +++ b/app/src/main/res/menu/menu_bottom_navigation.xml @@ -9,6 +9,13 @@ android:title="@string/navigation_payloads" app:showAsAction="ifRoom" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 1d9df31..61a2e29 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..0628a6d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2d3e4e4 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 0315ca5..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index e729686..761befa 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..5c822b3 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5d4be77 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index c1849b8..d69ef12 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..1d1f271 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8f002ee Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index ce43dde..68eb71a 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..92ff124 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6ef81e2 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 280c837..0119927 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -10,7 +10,7 @@ Информация Разработчик - Приложение написано на Kotlin и C++ и использует код для иньекции загрузчиков на ваш Nintendo Switch из приложения NXLoader + Приложение написано на Kotlin и C++ и используется для иньекции загрузчиков или запуска Lakka на вашем Nintendo Switch. Кабель @@ -19,11 +19,16 @@ В категории \u0022Загрузчики\u0022 нажмите на кнопку \u0022+\u0022, чтобы выбрать интересующий вас загрузчик из памяти устройства. Так же вы можете вручную перенести загрузчик в папку Rekado в памяти вашего устройства. \n\nИли вы можете использовать загрузчик по умолчанию (SX Loader) Введите свой Nintendo Switch в режим RCM любым удобным вам способом. \n\nВаш Nintendo Switch включиться автоматически, не забудье удерживать кнопку ЗВУК + при этом. Финал - Подключите ваше устройство к Nintendo Switch и дайте разрешение на подключение по USB, если будет необходимо. Подождите, пока программа предложить вам необходимый загрузчик и ожидайте конца установки. + Подключите ваше устройство к Nintendo Switch и дайте разрешение на подключение по USB, если будет необходимо. Подождите, пока программа предложит вам выбрать, что именно вы хотите запустить, загрузчик или Lakka.\n\nЕсли вы выбирете загрузчик, вам необходимо выбрать необходимый загрузчик и ожидайть конца загрузки.\n\nЕсли вы выбирете Lakka, просто дождитесь конца загрузки. Список событий + + Готово + Не готово + Обновлено: %1$s + Автоинжектор Включить \u0022Автоинжектор\u0022 @@ -44,6 +49,9 @@ Очистить Вы действительно хотите удалить загрузчики? Отмена + Выбрать инжектор + Загрузка (Загрузчик) + Загрузка (Lakka) Вы уверены, что это правильный файл загрузчика? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c96fb60..757a966 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,9 +1,10 @@ Rekado - 1.3 + 2.0 Payloads + Lakka Instructions Logs About @@ -12,7 +13,7 @@ Information Developer - Application written in Kotlin and C++ and uses injector codebase from the NXLoader application. It is used to install payloads on the Nintendo Switch + Application written in Kotlin and C++. It is used to inject payloads or start Lakka on the Nintendo Switch GitHub Twitter @@ -24,11 +25,18 @@ RCM Enter your Nintendo Switch into RCM mode in any convenient way. \n\nYour Nintendo Switch will power on by itself when plugged in, be sure to hold VOLUME +. Final - Connect the device to the Nintendo Switch and allow permission for the USB access if necessary. Wait until the program prompts you to select payload and choose the one you need. + Connect the device to the Nintendo Switch and allow permission for the USB access if necessary. Wait until the program prompts you to select what do you want to inject, payload or Lakka.\n\nIf you will select payload, than you will have to choose the one you need.\n\nIf you will choose Lakka, you should simply wait for it to load. List of actions + + CBFS + Coreboot + Ready + Not ready + Last update: %1$s + Auto injector Enable \u0022Auto injector\u0022 @@ -50,6 +58,9 @@ Are you sure you want to delete payloads? OK Cancel + Select injector + Boot (Payload) + Boot (Lakka) Are you sure this is correct payload file? diff --git a/konae/src/main/java/com/pavelrekun/konae/Konae.kt b/konae/src/main/java/com/pavelrekun/konae/Konae.kt index 7b76f7e..a8d82af 100644 --- a/konae/src/main/java/com/pavelrekun/konae/Konae.kt +++ b/konae/src/main/java/com/pavelrekun/konae/Konae.kt @@ -105,10 +105,14 @@ class Konae : AdapterView.OnItemClickListener { val files = currentDir.listFiles(fileFilter) - // Add the ".." entry - if (currentDir.parent != null && currentDir != Environment.getExternalStorageDirectory()) { - if (StorageUtils.checkExternalStoragePresent(context) && currentDir != File(StorageUtils.getExternalMemoryPaths(context)?.get(0))) + if (!StorageUtils.checkExternalStoragePresent(context)) { + if (currentDir.parent != null && currentDir != Environment.getExternalStorageDirectory()) { fileList.add(File("..")) + } + } else { + if (currentDir != File(StorageUtils.getExternalMemoryPaths(context)?.get(0))) { + fileList.add(File("..")) + } } if (files != null) { diff --git a/updater/update.json b/updater/update.json index 866aefb..e2f0caa 100644 --- a/updater/update.json +++ b/updater/update.json @@ -1,10 +1,12 @@ { - "latestVersion": "1.3", - "latestVersionCode": 15, + "latestVersion": "2.0", + "latestVersionCode": 20, "url": "https://github.com/MenosGrante/Rekado/releases", "releaseNotes": [ - "- Added self updater to check for application updates" - "- Added ability to select payloads from external memory" - "- Improved design of payload chooser dialog" + "- Added Lakka loader", + "- Updated icon with support for Adaptive Icon", + "- Fixed bug that prevented from navigating the folder hierarchy when selecting payloads", + "- Fixed various bugs with "Auto-injector", + "- Fixed memory leak during "phone-switch" connection" ] } \ No newline at end of file