Use dmg2img to parse DMG file
This commit is contained in:
parent
34d8f52e1d
commit
8cba765dff
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.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
|
||||
|
|
|
@ -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?) {
|
||||
|
|
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.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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