Use dmg2img to parse DMG file
This commit is contained in:
parent
37d963c8bc
commit
816eedf966
12 changed files with 278 additions and 7 deletions
|
@ -7,6 +7,7 @@ import eu.depau.etchdroid.fragments.WizardFragment
|
||||||
import eu.depau.etchdroid.enums.FlashMethod
|
import eu.depau.etchdroid.enums.FlashMethod
|
||||||
import eu.depau.etchdroid.enums.ImageLocation
|
import eu.depau.etchdroid.enums.ImageLocation
|
||||||
import eu.depau.etchdroid.enums.WizardStep
|
import eu.depau.etchdroid.enums.WizardStep
|
||||||
|
import eu.depau.etchdroid.img_types.Image
|
||||||
|
|
||||||
object StateKeeper {
|
object StateKeeper {
|
||||||
var wizardStep: WizardStep = WizardStep.SELECT_FLASH_METHOD
|
var wizardStep: WizardStep = WizardStep.SELECT_FLASH_METHOD
|
||||||
|
@ -15,6 +16,7 @@ object StateKeeper {
|
||||||
var imageLocation: ImageLocation? = null
|
var imageLocation: ImageLocation? = null
|
||||||
var streamingWrite: Boolean = false
|
var streamingWrite: Boolean = false
|
||||||
var imageFile: Uri? = null
|
var imageFile: Uri? = null
|
||||||
|
var imageRepr: Image? = null
|
||||||
|
|
||||||
var usbDevice: UsbDevice? = null
|
var usbDevice: UsbDevice? = null
|
||||||
var usbMassStorageDevice: UsbMassStorageDevice? = null
|
var usbMassStorageDevice: UsbMassStorageDevice? = null
|
||||||
|
|
|
@ -12,7 +12,7 @@ abstract class WizardActivity : AppCompatActivity(), SimpleFilePickerDialog.Inte
|
||||||
override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
|
override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
|
||||||
if (StateKeeper.currentFragment is SimpleFilePickerDialog.InteractionListenerString)
|
if (StateKeeper.currentFragment is SimpleFilePickerDialog.InteractionListenerString)
|
||||||
return (StateKeeper.currentFragment as SimpleFilePickerDialog.InteractionListenerString).onResult(dialogTag, which, extras)
|
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?) {
|
override fun showListItemDialog(title: String?, folderPath: String?, mode: SimpleFilePickerDialog.CompositeMode?, dialogTag: String?) {
|
||||||
|
|
39
app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt
Normal file
39
app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package eu.depau.etchdroid.enums
|
||||||
|
|
||||||
|
enum class PartitionTableType {
|
||||||
|
AIX,
|
||||||
|
AMIGA,
|
||||||
|
BSD,
|
||||||
|
DVH,
|
||||||
|
GPT,
|
||||||
|
LOOP,
|
||||||
|
MAC,
|
||||||
|
MSDOS,
|
||||||
|
PC98,
|
||||||
|
SUN
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package eu.depau.etchdroid.enums
|
||||||
|
|
||||||
|
enum class PartitionType {}
|
|
@ -22,6 +22,7 @@ import eu.depau.etchdroid.kotlin_exts.snackbar
|
||||||
import eu.depau.etchdroid.enums.FlashMethod
|
import eu.depau.etchdroid.enums.FlashMethod
|
||||||
import eu.depau.etchdroid.enums.ImageLocation
|
import eu.depau.etchdroid.enums.ImageLocation
|
||||||
import eu.depau.etchdroid.enums.WizardStep
|
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.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.fragment_select_location.*
|
import kotlinx.android.synthetic.main.fragment_select_location.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -80,7 +81,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
|
||||||
img_url_textview?.isEnabled = StateKeeper.imageLocation == ImageLocation.REMOTE
|
img_url_textview?.isEnabled = StateKeeper.imageLocation == ImageLocation.REMOTE
|
||||||
|
|
||||||
setStreamingCheckBoxAvailability(activity as WizardActivity)
|
setStreamingCheckBoxAvailability(activity as WizardActivity)
|
||||||
updateFileButtonLabel(activity as WizardActivity)
|
loadImageChanges(activity as WizardActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onButtonClicked(view: View) {
|
override fun onButtonClicked(view: View) {
|
||||||
|
@ -200,16 +201,21 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
|
||||||
return Uri.parse(text)
|
return Uri.parse(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFileButtonLabel(context: WizardActivity) {
|
fun loadImageChanges(context: WizardActivity) {
|
||||||
val button = pick_file_btn
|
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)
|
if (text != null)
|
||||||
button.text = text
|
button.text = text
|
||||||
else
|
else
|
||||||
button.text = getString(R.string.pick_a_file)
|
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?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
@ -223,7 +229,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
|
||||||
uri = data.getData()
|
uri = data.getData()
|
||||||
Log.d(TAG, "Uri: " + uri!!.toString())
|
Log.d(TAG, "Uri: " + uri!!.toString())
|
||||||
StateKeeper.imageFile = uri
|
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)) {
|
if (extras.containsKey(SimpleFilePickerDialog.SELECTED_SINGLE_PATH)) {
|
||||||
val path = extras.getString(SimpleFilePickerDialog.SELECTED_SINGLE_PATH)
|
val path = extras.getString(SimpleFilePickerDialog.SELECTED_SINGLE_PATH)
|
||||||
StateKeeper.imageFile = Uri.fromFile(File(path))
|
StateKeeper.imageFile = Uri.fromFile(File(path))
|
||||||
updateFileButtonLabel(activity as WizardActivity)
|
loadImageChanges(activity as WizardActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
93
app/src/main/java/eu/depau/etchdroid/img_types/DMGImage.kt
Normal file
93
app/src/main/java/eu/depau/etchdroid/img_types/DMGImage.kt
Normal file
|
@ -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<PartitionTableType?, List<Partition>?> {
|
||||||
|
val pt = ArrayList<Partition>()
|
||||||
|
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<Partition>? = 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<Partition>?
|
||||||
|
get() {
|
||||||
|
readInfo()
|
||||||
|
return partTable
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override val tableType: PartitionTableType?
|
||||||
|
get() {
|
||||||
|
readInfo()
|
||||||
|
return partTableType
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
app/src/main/java/eu/depau/etchdroid/img_types/Image.kt
Normal file
9
app/src/main/java/eu/depau/etchdroid/img_types/Image.kt
Normal file
|
@ -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<Partition>?
|
||||||
|
val tableType: PartitionTableType?
|
||||||
|
}
|
|
@ -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<String, File> = 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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
14
app/src/main/java/eu/depau/etchdroid/utils/Partition.kt
Normal file
14
app/src/main/java/eu/depau/etchdroid/utils/Partition.kt
Normal file
|
@ -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?
|
||||||
|
)
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in a new issue