diff --git a/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt b/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt index be2622b..412799a 100644 --- a/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt +++ b/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt @@ -7,6 +7,7 @@ import eu.depau.etchdroid.fragments.WizardFragment import eu.depau.etchdroid.enums.FlashMethod import eu.depau.etchdroid.enums.ImageLocation import eu.depau.etchdroid.enums.WizardStep +import eu.depau.etchdroid.img_types.Image object StateKeeper { var wizardStep: WizardStep = WizardStep.SELECT_FLASH_METHOD @@ -15,6 +16,7 @@ object StateKeeper { var imageLocation: ImageLocation? = null var streamingWrite: Boolean = false var imageFile: Uri? = null + var imageRepr: Image? = null var usbDevice: UsbDevice? = null var usbMassStorageDevice: UsbMassStorageDevice? = null diff --git a/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt index e0a695d..71b377e 100644 --- a/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt +++ b/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt @@ -12,7 +12,7 @@ abstract class WizardActivity : AppCompatActivity(), SimpleFilePickerDialog.Inte override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean { if (StateKeeper.currentFragment is SimpleFilePickerDialog.InteractionListenerString) return (StateKeeper.currentFragment as SimpleFilePickerDialog.InteractionListenerString).onResult(dialogTag, which, extras) - throw RuntimeException("Wrong fragment type") + throw RuntimeException("Wrong fragment fsType") } override fun showListItemDialog(title: String?, folderPath: String?, mode: SimpleFilePickerDialog.CompositeMode?, dialogTag: String?) { diff --git a/app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt b/app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt new file mode 100644 index 0000000..b1b066c --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt @@ -0,0 +1,39 @@ +package eu.depau.etchdroid.enums + +enum class FilesystemType { + // Microsoft + FAT12, + FAT16, + FAT32, + EXFAT, + NTFS, + REFS, + + // Apple + HFS, + HFSPLUS, + APFS, + APT_DATA, // Apple Partition Table stuff + + // ISO 9660 + ISO9660, + + // Linux + EXT2, + EXT3, + EXT4, + BTRFS, + F2FS, + LUKS, + LINUX_SWAP, + LINUX_LVM_PV, + + // BSD + UFS, + XFS, + ZFS, + + FREE, + UNFORMATTED, + UNKNOWN +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/enums/PartitionTableType.kt b/app/src/main/java/eu/depau/etchdroid/enums/PartitionTableType.kt new file mode 100644 index 0000000..20676be --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/enums/PartitionTableType.kt @@ -0,0 +1,14 @@ +package eu.depau.etchdroid.enums + +enum class PartitionTableType { + AIX, + AMIGA, + BSD, + DVH, + GPT, + LOOP, + MAC, + MSDOS, + PC98, + SUN +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/enums/PartitionType.kt b/app/src/main/java/eu/depau/etchdroid/enums/PartitionType.kt new file mode 100644 index 0000000..0f2ef3f --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/enums/PartitionType.kt @@ -0,0 +1,3 @@ +package eu.depau.etchdroid.enums + +enum class PartitionType {} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt b/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt index 345978c..72620cf 100644 --- a/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt +++ b/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt @@ -22,6 +22,7 @@ import eu.depau.etchdroid.kotlin_exts.snackbar import eu.depau.etchdroid.enums.FlashMethod import eu.depau.etchdroid.enums.ImageLocation import eu.depau.etchdroid.enums.WizardStep +import eu.depau.etchdroid.img_types.DMGImage import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_select_location.* import java.io.File @@ -80,7 +81,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti img_url_textview?.isEnabled = StateKeeper.imageLocation == ImageLocation.REMOTE setStreamingCheckBoxAvailability(activity as WizardActivity) - updateFileButtonLabel(activity as WizardActivity) + loadImageChanges(activity as WizardActivity) } override fun onButtonClicked(view: View) { @@ -200,16 +201,21 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti return Uri.parse(text) } - fun updateFileButtonLabel(context: WizardActivity) { + fun loadImageChanges(context: WizardActivity) { val button = pick_file_btn - val uri = StateKeeper.imageFile + val uri = StateKeeper.imageFile ?: return - val text = uri?.getFileName(context) + val text = uri.getFileName(context) if (text != null) button.text = text else button.text = getString(R.string.pick_a_file) + + if (StateKeeper.flashMethod == FlashMethod.FLASH_DMG_API) { + StateKeeper.imageRepr = DMGImage(uri, context) + Log.d(TAG, (StateKeeper.imageRepr as DMGImage).partitionTable.toString()) + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -223,7 +229,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti uri = data.getData() Log.d(TAG, "Uri: " + uri!!.toString()) StateKeeper.imageFile = uri - updateFileButtonLabel(activity as WizardActivity) + loadImageChanges(activity as WizardActivity) } } } @@ -235,7 +241,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti if (extras.containsKey(SimpleFilePickerDialog.SELECTED_SINGLE_PATH)) { val path = extras.getString(SimpleFilePickerDialog.SELECTED_SINGLE_PATH) StateKeeper.imageFile = Uri.fromFile(File(path)) - updateFileButtonLabel(activity as WizardActivity) + loadImageChanges(activity as WizardActivity) } } } diff --git a/app/src/main/java/eu/depau/etchdroid/img_types/DMGImage.kt b/app/src/main/java/eu/depau/etchdroid/img_types/DMGImage.kt new file mode 100644 index 0000000..7216bfe --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/img_types/DMGImage.kt @@ -0,0 +1,93 @@ +package eu.depau.etchdroid.img_types + +import android.content.Context +import android.net.Uri +import eu.depau.etchdroid.enums.FilesystemType +import eu.depau.etchdroid.enums.PartitionTableType +import eu.depau.etchdroid.kotlin_exts.getBinary +import eu.depau.etchdroid.kotlin_exts.readString +import eu.depau.etchdroid.utils.Partition +import eu.depau.etchdroid.utils.PartitionBuilder +import java.io.File + +private val partRegex = Regex("partition (\\d+): (.*) \\((.+) : \\d+\\)") + +private fun readPartitionTable(dmg2img: File, libDir: String, file: File): Pair?> { + val pt = ArrayList() + var ptType: PartitionTableType? = null + + val pb = ProcessBuilder(dmg2img.path, "-l", file.path) + pb.environment()["LD_LIBRARY_PATH"] = libDir + pb.redirectErrorStream(true) + + val p = pb.start() + val out = p.inputStream.readString() + p.waitFor() + val matches = partRegex.findAll(out) + + matchloop@ for (m in matches) { + val (number, label, type) = m.destructured + val pb = PartitionBuilder() + + pb.number = number.toInt() + + if (label.isNotEmpty()) + pb.fsLabel = label + + pb.partLabel = type + + when (type) { + "Apple_partition_map" -> { + ptType = PartitionTableType.MAC + } + "MBR" -> { + ptType = PartitionTableType.MSDOS + continue@matchloop + } + } + + pb.fsType = when { + type == "Apple_Empty" || type == "Apple_Scratch" || type == "Apple_Free" -> FilesystemType.FREE + type == "Apple_HFS" -> FilesystemType.HFSPLUS + type == "Apple_HFSX" -> FilesystemType.HFSPLUS + type == "Windows_FAT_32" -> FilesystemType.FAT32 + type.startsWith("Apple_") || type == "DDM" -> FilesystemType.APT_DATA + else -> FilesystemType.UNKNOWN + } + + pt.add(pb.build()) + } + + return Pair(ptType, pt) +} + +class DMGImage(private val uri: Uri, private val context: Context) : Image { + private val dmg2img: File = context.getBinary("dmg2img") + private val libDir: String = context.applicationInfo.nativeLibraryDir + private var loaded: Boolean = false + private var partTable: List? = null + private var partTableType: PartitionTableType? = null + + private fun readInfo() { + if (loaded) + return + val pair = readPartitionTable(dmg2img, libDir, File(uri.path)) + loaded = true + partTableType = pair.first + partTable = pair.second + } + + override val partitionTable: List? + get() { + readInfo() + return partTable + } + + + override val tableType: PartitionTableType? + get() { + readInfo() + return partTableType + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/img_types/Image.kt b/app/src/main/java/eu/depau/etchdroid/img_types/Image.kt new file mode 100644 index 0000000..5ea7bb7 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/img_types/Image.kt @@ -0,0 +1,9 @@ +package eu.depau.etchdroid.img_types + +import eu.depau.etchdroid.enums.PartitionTableType +import eu.depau.etchdroid.utils.Partition + +interface Image { + val partitionTable: List? + val tableType: PartitionTableType? +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ContextGetBinary.kt b/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ContextGetBinary.kt new file mode 100644 index 0000000..307825d --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ContextGetBinary.kt @@ -0,0 +1,57 @@ +package eu.depau.etchdroid.kotlin_exts + +import android.content.Context +import android.content.Context.MODE_PRIVATE +import android.os.Build +import java.io.File +import java.io.FileOutputStream + + +private val copiedBinaries: MutableMap = HashMap() + + +fun Context.getBinary(name: String): File { + if (name in copiedBinaries.keys) + return copiedBinaries[name]!! + + val abi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + Build.SUPPORTED_ABIS[0] + else + Build.CPU_ABI + + val arch = when { + abi.contains("armeabi-v7a") -> "armeabi-v7a" + abi.contains("x86_64") -> "x86_64" + abi.contains("x86") -> "x86" + abi.contains("arm64-v8a") -> "arm64-v8a" + else -> null!! + } + + val assetManager = assets + val assetIn = assetManager.open("bin/$arch/$name") + + val bin = File("$filesDir/bin/$arch/$name") + bin.parentFile?.mkdirs() + if (!bin.exists()) + bin.createNewFile() + val binOut = FileOutputStream(bin) + + // Copy executable + var size: Long = 0 + val buff = ByteArray(1024) + var nRead = assetIn.read(buff) + + while (nRead != -1) { + binOut.write(buff, 0, nRead) + size += nRead.toLong() + nRead = assetIn.read(buff) + } + assetIn.close() + binOut.close() + + bin.setExecutable(true) + + copiedBinaries[name] = bin + + return bin +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/kotlin_exts/InputStreamToString.kt b/app/src/main/java/eu/depau/etchdroid/kotlin_exts/InputStreamToString.kt new file mode 100644 index 0000000..807f4df --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/kotlin_exts/InputStreamToString.kt @@ -0,0 +1,18 @@ +package eu.depau.etchdroid.kotlin_exts + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream + +@Throws(IOException::class) +fun InputStream.readString(): String { + val baos = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var length = this.read(buffer) + + while (length != -1) { + baos.write(buffer, 0, length) + length = this.read(buffer) + } + return baos.toString("UTF-8") +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt b/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt new file mode 100644 index 0000000..29e6629 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt @@ -0,0 +1,14 @@ +package eu.depau.etchdroid.utils + +import eu.depau.etchdroid.enums.FilesystemType +import eu.depau.etchdroid.enums.PartitionType + +data class Partition( + val number: Int?, + val offset: Int?, + val size: Int?, + val partType: PartitionType?, + val partLabel: String?, + val fsType: FilesystemType?, + val fsLabel: String? +) \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt b/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt new file mode 100644 index 0000000..648240f --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt @@ -0,0 +1,16 @@ +package eu.depau.etchdroid.utils + +import eu.depau.etchdroid.enums.FilesystemType +import eu.depau.etchdroid.enums.PartitionType + +class PartitionBuilder { + var number: Int? = null + var offset: Int? = null + var size: Int? = null + var partType: PartitionType? = null + var partLabel: String? = null + var fsType: FilesystemType? = null + var fsLabel: String? = null + + fun build(): Partition = Partition(number, offset, size, partType, partLabel, fsType, fsLabel) +} \ No newline at end of file