From a2008506909d06e73d324916929b030c2964489b Mon Sep 17 00:00:00 2001 From: GhidraKnight <48915474+GhidraKnight@users.noreply.github.com> Date: Mon, 6 Jun 2022 10:08:47 -0400 Subject: [PATCH] Fixes/Changes to support Android 12 OAT version 199, FBPK version 2, and VendorBootImage version 4. --- .../file/analyzers/FileFormatAnalyzer.java | 12 +- .../file/formats/android/art/ArtAnalyzer.java | 8 +- .../android/bootimg/BootImageAnalyzer.java | 202 +++++++++++------ .../bootimg/VendorBootImageFileSystem.java | 70 ++++-- .../bootimg/VendorBootImageHeader.java | 17 +- .../bootimg/VendorBootImageHeaderV3.java | 13 +- .../bootimg/VendorBootImageHeaderV4.java | 43 +++- .../bootimg/VendorRamdiskTableEntryV4.java | 17 +- .../file/formats/android/fbpk/FBPK.java | 78 ++----- .../formats/android/fbpk/FBPK_Analyzer.java | 105 +++------ .../formats/android/fbpk/FBPK_Constants.java | 50 +++- .../formats/android/fbpk/FBPK_Factory.java | 46 ++++ .../formats/android/fbpk/FBPK_FileSystem.java | 9 +- .../formats/android/fbpk/FBPK_Partition.java | 150 ++++++------ .../file/formats/android/fbpk/FBPT.java | 188 ++++----------- .../file/formats/android/fbpk/FBPT_Entry.java | 85 +------ .../file/formats/android/fbpk/v1/FBPKv1.java | 91 ++++++++ .../android/fbpk/v1/FBPKv1_Partition.java | 128 +++++++++++ .../file/formats/android/fbpk/v1/FBPTv1.java | 95 ++++++++ .../formats/android/fbpk/v1/FBPTv1_Entry.java | 108 +++++++++ .../file/formats/android/fbpk/v2/FBPKv2.java | 118 ++++++++++ .../android/fbpk/v2/FBPKv2_Partition.java | 214 ++++++++++++++++++ .../file/formats/android/fbpk/v2/FBPTv2.java | 88 +++++++ .../formats/android/fbpk/v2/FBPTv2_Entry.java | 94 ++++++++ .../file/formats/android/fbpk/v2/UFPK.java | 60 +++++ .../file/formats/android/fbpk/v2/UFSM.java | 58 +++++ .../file/formats/android/fbpk/v2/UFSP.java | 58 +++++ .../formats/android/oat/OatConstants.java | 103 +++------ .../android/oat/OatHeaderAnalyzer.java | 20 +- .../formats/android/oat/OatHeaderFactory.java | 1 + .../formats/android/oat/OatHeader_12.java | 2 + .../formats/android/oat/OatUtilities.java | 28 ++- .../android/oat/oatclass/OatClassFactory.java | 1 + .../oat/oatdexfile/OatDexFileFactory.java | 1 + .../OatQuickMethodHeaderFactory.java | 1 + .../oat/tlt/TypeLookupTableFactory.java | 1 + .../android/vdex/VdexHeaderAnalyzer.java | 24 +- .../file/formats/ext4/NewExt4Analyzer.java | 13 +- 38 files changed, 1750 insertions(+), 650 deletions(-) create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Factory.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1_Partition.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1_Entry.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2_Partition.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2_Entry.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFPK.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSM.java create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSP.java diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java index e42480c409..f1c384d3e1 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java @@ -30,8 +30,11 @@ import ghidra.program.model.data.DataType; import ghidra.program.model.data.StringDataType; import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.exception.CancelledException; +import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.NotEmptyException; +import ghidra.util.exception.NotFoundException; import ghidra.util.task.TaskMonitor; public abstract class FileFormatAnalyzer implements Analyzer { @@ -151,7 +154,7 @@ public abstract class FileFormatAnalyzer implements Analyzer { } protected ProgramFragment createFragment(Program program, String fragmentName, Address start, - Address end) throws Exception { + Address end) throws DuplicateNameException, NotFoundException { ProgramModule module = program.getListing().getDefaultRootModule(); ProgramFragment fragment = getFragment(module, fragmentName); if (fragment == null) { @@ -187,18 +190,17 @@ public abstract class FileFormatAnalyzer implements Analyzer { return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); } - protected Data createData(Program program, Address address, DataType datatype) - throws Exception { + protected Data createData(Program program, Address address, DataType datatype) throws CodeUnitInsertionException { if (datatype instanceof StringDataType) { CreateStringCmd cmd = new CreateStringCmd(address); if (!cmd.applyTo(program)) { - throw new RuntimeException(cmd.getStatusMsg()); + throw new CodeUnitInsertionException(cmd.getStatusMsg()); } } else { CreateDataCmd cmd = new CreateDataCmd(address, datatype); if (!cmd.applyTo(program)) { - throw new RuntimeException(cmd.getStatusMsg()); + throw new CodeUnitInsertionException(cmd.getStatusMsg()); } } return program.getListing().getDefinedDataAt(address); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java index 1e386edd9a..c82da005b4 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java @@ -15,11 +15,13 @@ */ package ghidra.file.formats.android.art; -import ghidra.app.util.bin.*; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.MemoryByteProvider; import ghidra.app.util.importer.MessageLog; import ghidra.app.util.opinion.BinaryLoader; import ghidra.file.analyzers.FileFormatAnalyzer; -import ghidra.file.formats.android.oat.OatConstants; +import ghidra.file.formats.android.oat.OatUtilities; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.DataType; @@ -49,7 +51,7 @@ public class ArtAnalyzer extends FileFormatAnalyzer { //HACK: //Make analyzer appear after ART is merged with OAT program //Currently, analyzers will not recognize the new ART block being added - || OatConstants.isOAT(program); + || OatUtilities.isOAT(program); } @Override diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java index ebff5bb123..628dee3b6a 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java @@ -15,6 +15,8 @@ */ package ghidra.file.formats.android.bootimg; +import java.io.IOException; + import ghidra.app.plugin.core.analysis.AnalysisWorker; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.util.bin.BinaryReader; @@ -27,7 +29,10 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.data.DataType; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; +import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.util.exception.CancelledException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.NotFoundException; import ghidra.util.task.TaskMonitor; public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWorker { @@ -47,6 +52,11 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor return "Annotates Android Boot, Recovery, or Vendor Image files."; } + @Override + public String getWorkerName() { + return "BootImageAnalyzer"; + } + @Override public boolean canAnalyze(Program program) { try { @@ -68,6 +78,7 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor @Override public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws Exception { + this.messageLog = log; AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(program); return manager.scheduleWorker(this, null, false, monitor); @@ -75,7 +86,8 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor @Override public boolean analysisWorkerCallback(Program program, Object workerContext, - TaskMonitor monitor) throws Exception, CancelledException { + TaskMonitor monitor) + throws Exception, CancelledException { Address address = program.getMinAddress(); @@ -83,76 +95,10 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor BinaryReader reader = new BinaryReader(provider, true); if (BootImageUtil.isBootImage(program)) { - - BootImageHeader header = BootImageHeaderFactory.getBootImageHeader(reader); - - if (!BootImageConstants.BOOT_MAGIC.equals(header.getMagic())) { - return false; - } - - DataType headerDataType = header.toDataType(); - - Data headerData = createData(program, address, headerDataType); - - if (headerData == null) { - messageLog.appendMsg("Unable to create header data."); - return false; - } - - createFragment(program, headerDataType.getName(), toAddr(program, 0), - toAddr(program, header.getPageSize())); - - if (header.getKernelSize() > 0) { - Address start = toAddr(program, header.getKernelOffset()); - Address end = toAddr(program, header.getKernelOffset() + header.getKernelSize()); - createFragment(program, BootImageConstants.KERNEL, start, end); - } - - if (header.getRamdiskSize() > 0) { - Address start = toAddr(program, header.getRamdiskOffset()); - Address end = toAddr(program, header.getRamdiskOffset() + header.getRamdiskSize()); - createFragment(program, BootImageConstants.RAMDISK, start, end); - } - - if (header.getSecondSize() > 0) { - Address start = toAddr(program, header.getSecondOffset()); - Address end = toAddr(program, header.getSecondOffset() + header.getSecondSize()); - createFragment(program, BootImageConstants.SECOND_STAGE, start, end); - } - - changeDataSettings(program, monitor); + markupBootImage(program, reader, monitor); } else if (BootImageUtil.isVendorBootImage(program)) { - VendorBootImageHeader header = - VendorBootImageHeaderFactory.getVendorBootImageHeader(reader); - - if (!header.getMagic().equals(BootImageConstants.VENDOR_BOOT_MAGIC)) { - return false; - } - - DataType headerDataType = header.toDataType(); - - Data headerData = createData(program, address, headerDataType); - - if (headerData == null) { - messageLog.appendMsg("Unable to create header data."); - } - - createFragment(program, headerDataType.getName(), toAddr(program, 0), - toAddr(program, headerData.getLength())); - - if (header.getVendorRamdiskSize() > 0) { - Address start = toAddr(program, header.getVendorRamdiskOffset()); - Address end = toAddr(program, - header.getVendorRamdiskOffset() + header.getVendorRamdiskSize()); - createFragment(program, BootImageConstants.RAMDISK, start, end); - } - - if (header.getDtbSize() > 0) { - Address start = toAddr(program, header.getDtbOffset()); - Address end = toAddr(program, header.getDtbOffset() + header.getDtbSize()); - createFragment(program, BootImageConstants.DTB, start, end); - } + markupVendorBootImage(program, reader, monitor); } removeEmptyFragments(program); @@ -160,8 +106,120 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor return true; } - @Override - public String getWorkerName() { - return "BootImageAnalyzer"; + private void markupBootImage(Program program, BinaryReader reader, TaskMonitor monitor) + throws IOException, DuplicateNameException, NotFoundException, + CodeUnitInsertionException { + + Address address = program.getMinAddress(); + + BootImageHeader header = BootImageHeaderFactory.getBootImageHeader(reader); + + DataType headerDataType = header.toDataType(); + Data headerData = createData(program, address, headerDataType); + if (headerData == null) { + messageLog.appendMsg("Unable to create header data."); + return; + } + createFragment(program, headerDataType.getName(), toAddr(program, 0), + toAddr(program, header.getPageSize())); + + if (header.getKernelSize() > 0) { + Address start = toAddr(program, header.getKernelOffset()); + Address end = toAddr(program, header.getKernelOffset() + header.getKernelSize()); + createFragment(program, BootImageConstants.KERNEL, start, end); + } + + if (header.getRamdiskSize() > 0) { + Address start = toAddr(program, header.getRamdiskOffset()); + Address end = toAddr(program, header.getRamdiskOffset() + header.getRamdiskSize()); + createFragment(program, BootImageConstants.RAMDISK, start, end); + } + + if (header.getSecondSize() > 0) { + Address start = toAddr(program, header.getSecondOffset()); + Address end = toAddr(program, header.getSecondOffset() + header.getSecondSize()); + createFragment(program, BootImageConstants.SECOND_STAGE, start, end); + } + + changeDataSettings(program, monitor); + } + + private void markupVendorBootImage(Program program, BinaryReader reader, TaskMonitor monitor) + throws IOException, DuplicateNameException, CodeUnitInsertionException, + NotFoundException, CancelledException { + + Address address = program.getMinAddress(); + + VendorBootImageHeader header = + VendorBootImageHeaderFactory.getVendorBootImageHeader(reader); + + DataType headerDataType = header.toDataType(); + Data headerData = createData(program, address, headerDataType); + if (headerData == null) { + messageLog.appendMsg("Unable to create header data."); + } + createFragment(program, headerDataType.getName(), toAddr(program, 0), + toAddr(program, headerData.getLength())); + + markupVendorRamdisk(program, header); + + if (header.getDtbSize() > 0) { + Address start = toAddr(program, header.getDtbOffset()); + Address end = toAddr(program, header.getDtbOffset() + header.getDtbSize()); + createFragment(program, BootImageConstants.DTB, start, end); + } + + markupVendorBootImageV4(header, program, monitor); + } + + private void markupVendorRamdisk(Program program, VendorBootImageHeader header) + throws IOException, DuplicateNameException, NotFoundException { + + if (header.getNestedVendorRamdiskCount() > 1) { + for (int i = 0; i < header.getNestedVendorRamdiskCount(); ++i) { + Address start = toAddr(program, header.getNestedVendorRamdiskOffset(i)); + Address end = toAddr(program, + header.getNestedVendorRamdiskOffset(i) + + header.getNestedVendorRamdiskSize(i)); + createFragment(program, BootImageConstants.RAMDISK + "_" + i, start, end); + } + } + else { + if (header.getVendorRamdiskSize() > 0) { + Address start = toAddr(program, header.getVendorRamdiskOffset()); + Address end = toAddr(program, + header.getVendorRamdiskOffset() + header.getVendorRamdiskSize()); + createFragment(program, BootImageConstants.RAMDISK, start, end); + } + } + } + + private void markupVendorBootImageV4(VendorBootImageHeader header, Program program, + TaskMonitor monitor) throws DuplicateNameException, NotFoundException, + CancelledException, IOException, CodeUnitInsertionException { + + if (header instanceof VendorBootImageHeaderV4) { + VendorBootImageHeaderV4 v4 = (VendorBootImageHeaderV4) header; + + if (v4.getVendorRamdiskTableEntrySize() > 0) { + Address start = toAddr(program, v4.getVendorRamdiskTableOffset()); + Address end = toAddr(program, + v4.getVendorRamdiskTableOffset() + v4.getVendorRamdiskTableSize()); + createFragment(program, "Ramdisk Table", start, end); + + for (VendorRamdiskTableEntryV4 entry : v4.getVendorRamdiskTableEntryList()) { + monitor.checkCanceled(); + DataType entryDataType = entry.toDataType(); + createData(program, start, entryDataType); + start = start.add(entryDataType.getLength()); + } + } + + if (v4.getBootConfigSize() > 0) { + Address start = toAddr(program, v4.getBootConfigOffset()); + Address end = toAddr(program, v4.getBootConfigOffset() + v4.getBootConfigSize()); + createFragment(program, "Boot Config", start, end); + } + } } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java index a5a109f1ce..e7dd171dbd 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java @@ -16,23 +16,34 @@ package ghidra.file.formats.android.bootimg; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProviderWrapper; -import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.GFile; +import ghidra.formats.gfilesystem.GFileImpl; +import ghidra.formats.gfilesystem.GFileSystemBase; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; -import ghidra.formats.gfilesystem.fileinfo.*; +import ghidra.formats.gfilesystem.fileinfo.FileAttribute; +import ghidra.formats.gfilesystem.fileinfo.FileAttributeType; +import ghidra.formats.gfilesystem.fileinfo.FileAttributes; import ghidra.util.exception.CancelledException; import ghidra.util.exception.CryptoException; import ghidra.util.task.TaskMonitor; -@FileSystemInfo(type = "androidvendorbootimg", description = "Android Vendor Boot Images", factory = GFileSystemBaseFactory.class) +@FileSystemInfo( + type = "androidvendorbootimg", + description = "Android Vendor Boot Images", + factory = GFileSystemBaseFactory.class +) public class VendorBootImageFileSystem extends GFileSystemBase { private VendorBootImageHeader header; - private GFileImpl ramdiskFile; + private List ramdiskFileList = new ArrayList<>(); private GFileImpl dtbFile; private List fileList = new ArrayList<>(); @@ -55,10 +66,24 @@ public class VendorBootImageFileSystem extends GFileSystemBase { throw new IOException("Invalid Android boot image file!"); } - if (header.getVendorRamdiskSize() > 0) { - ramdiskFile = GFileImpl.fromFilename(this, root, BootImageConstants.RAMDISK, false, - header.getVendorRamdiskSize(), null); - fileList.add(ramdiskFile); + if ( header.getNestedVendorRamdiskCount() > 1) { + for (int i = 0; i < header.getNestedVendorRamdiskCount(); ++i) { + if (header.getNestedVendorRamdiskSize(i) > 0) { + GFileImpl ramdiskFile = GFileImpl.fromFilename( + this, root, BootImageConstants.RAMDISK + "_" + i, + false, header.getNestedVendorRamdiskSize(i), null); + fileList.add(ramdiskFile); + ramdiskFileList.add(ramdiskFile); + } + } + } + else { + if (header.getVendorRamdiskSize() > 0) { + GFileImpl ramdiskFile = GFileImpl.fromFilename(this, root, BootImageConstants.RAMDISK, false, + header.getVendorRamdiskSize(), null); + fileList.add(ramdiskFile); + ramdiskFileList.add(ramdiskFile); + } } if (header.getDtbSize() > 0) { dtbFile = GFileImpl.fromFilename(this, root, BootImageConstants.DTB, false, @@ -69,7 +94,8 @@ public class VendorBootImageFileSystem extends GFileSystemBase { @Override public void close() throws IOException { - ramdiskFile = null; + fileList.clear(); + ramdiskFileList.clear(); dtbFile = null; header = null; super.close(); @@ -83,12 +109,14 @@ public class VendorBootImageFileSystem extends GFileSystemBase { @Override public FileAttributes getFileAttributes(GFile file, TaskMonitor monitor) { - if (file == ramdiskFile) { - return FileAttributes.of( - FileAttribute.create(FileAttributeType.COMMENT_ATTR, - "This is a ramdisk, it is a GZIP file containing a CPIO archive.")); + for (int i = 0; i < ramdiskFileList.size(); ++i) { + if (ramdiskFileList.get(i) == file) { + return FileAttributes.of( + FileAttribute.create(FileAttributeType.COMMENT_ATTR, + "This is a ramdisk, it is a GZIP file containing a CPIO archive.")); + } } - else if (file == dtbFile) { + if (file == dtbFile) { return FileAttributes.of( FileAttribute.create(FileAttributeType.COMMENT_ATTR, "This is a DTB file. It appears unused at this time.")); @@ -100,14 +128,18 @@ public class VendorBootImageFileSystem extends GFileSystemBase { public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException, CancelledException { - if (file == ramdiskFile) { - return new ByteProviderWrapper(provider, header.getVendorRamdiskOffset(), - Integer.toUnsignedLong(header.getVendorRamdiskSize()), file.getFSRL()); + for (int i = 0; i < ramdiskFileList.size(); ++i) { + if (ramdiskFileList.get(i) == file) { + return new ByteProviderWrapper(provider, header.getNestedVendorRamdiskOffset(i), + Integer.toUnsignedLong(header.getNestedVendorRamdiskSize(i)), file.getFSRL()); + } } - else if (file == dtbFile) { + + if (file == dtbFile) { return new ByteProviderWrapper(provider, header.getDtbOffset(), Integer.toUnsignedLong(header.getDtbSize()), file.getFSRL()); } + return null; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java index 120f1df42f..0ca16e6c27 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java @@ -15,10 +15,12 @@ */ package ghidra.file.formats.android.bootimg; +import java.io.IOException; + import ghidra.app.util.bin.StructConverter; /** - * + * Base class to represent a Vendor Boot Image header. */ public abstract class VendorBootImageHeader implements StructConverter { @@ -31,4 +33,17 @@ public abstract class VendorBootImageHeader implements StructConverter { public abstract long getDtbOffset(); public abstract int getDtbSize(); + + public long getNestedVendorRamdiskCount() { + return 1; + } + + public long getNestedVendorRamdiskOffset(int index) throws IOException { + return getVendorRamdiskOffset(); + } + + public int getNestedVendorRamdiskSize(int index) throws IOException { + return getVendorRamdiskSize(); + } + } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java index 235e572dae..fe3fad6105 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java @@ -21,6 +21,7 @@ import ghidra.app.util.bin.BinaryReader; import ghidra.program.model.data.DataType; import ghidra.program.model.data.Structure; import ghidra.program.model.data.StructureDataType; +import ghidra.util.NumericUtilities; import ghidra.util.exception.DuplicateNameException; /** @@ -86,6 +87,7 @@ public class VendorBootImageHeaderV3 extends VendorBootImageHeader { dtb_addr = reader.readNextLong(); } + @Override public String getMagic() { return magic; } @@ -106,13 +108,16 @@ public class VendorBootImageHeaderV3 extends VendorBootImageHeader { return ramdisk_addr; } + @Override public int getVendorRamdiskSize() { return vendor_ramdisk_size; } @Override public long getVendorRamdiskOffset() { - return page_size; + //NOTE: + //the header can be larger than 1 page due to cmd line + return NumericUtilities.getUnsignedAlignedValue(header_size, getPageSize()); } public String getCmdline() { @@ -131,6 +136,7 @@ public class VendorBootImageHeaderV3 extends VendorBootImageHeader { return header_size; } + @Override public int getDtbSize() { return dtb_size; } @@ -141,9 +147,8 @@ public class VendorBootImageHeaderV3 extends VendorBootImageHeader { @Override public long getDtbOffset() { - int o = ((2112 + page_size - 1) / page_size); - int p = ((vendor_ramdisk_size + page_size - 1) / page_size); - return (o + p) * page_size; + long value = getVendorRamdiskOffset() + getVendorRamdiskSize(); + return NumericUtilities.getUnsignedAlignedValue(value, getPageSize()); } @Override diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java index d82d162abd..cb74ebbd8e 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java @@ -16,15 +16,19 @@ package ghidra.file.formats.android.bootimg; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import ghidra.app.util.bin.BinaryReader; import ghidra.program.model.data.DataType; import ghidra.program.model.data.Structure; import ghidra.util.InvalidNameException; +import ghidra.util.NumericUtilities; import ghidra.util.exception.DuplicateNameException; /** * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#401 + * *
  * The structure of the vendor boot image version 4, which is required to be
  * present when a version 4 boot image is used, is as follows:
@@ -101,12 +105,40 @@ public class VendorBootImageHeaderV4 extends VendorBootImageHeaderV3 {
 	private int vendor_ramdisk_table_entry_size;
 	private int bootconfig_size;
 
+	private List ramdiskTableEntryList = new ArrayList<>();
+
 	public VendorBootImageHeaderV4(BinaryReader reader) throws IOException {
 		super(reader);
 		vendor_ramdisk_table_size = reader.readNextInt();
 		vendor_ramdisk_table_entry_num = reader.readNextInt();
 		vendor_ramdisk_table_entry_size = reader.readNextInt();
 		bootconfig_size = reader.readNextInt();
+
+		BinaryReader cloneReader = reader.clone(getVendorRamdiskTableOffset());
+		for (int i = 0; i < vendor_ramdisk_table_entry_num; ++i) {
+			ramdiskTableEntryList.add(new VendorRamdiskTableEntryV4(cloneReader));
+		}
+	}
+
+	@Override
+	public long getNestedVendorRamdiskCount() {
+		return vendor_ramdisk_table_entry_num;
+	}
+
+	@Override
+	public long getNestedVendorRamdiskOffset(int index) throws IOException {
+		return getVendorRamdiskOffset() +
+			ramdiskTableEntryList.get(index).getRamdiskOffset();
+	}
+
+	@Override
+	public int getNestedVendorRamdiskSize(int index) throws IOException {
+		return ramdiskTableEntryList.get(index).getRamdiskSize();
+	}
+
+	public long getVendorRamdiskTableOffset() {
+		long value = getDtbOffset() + getDtbSize();
+		return NumericUtilities.getUnsignedAlignedValue(value, getPageSize());
 	}
 
 	/**
@@ -133,6 +165,11 @@ public class VendorBootImageHeaderV4 extends VendorBootImageHeaderV3 {
 		return vendor_ramdisk_table_entry_size;
 	}
 
+	public long getBootConfigOffset() {
+		long value = getVendorRamdiskTableOffset() + getVendorRamdiskTableSize();
+		return NumericUtilities.getUnsignedAlignedValue(value, getPageSize());
+	}
+
 	/**
 	 * Size in bytes for the bootconfig section
 	 * @return size in bytes for the bootconfig section
@@ -141,6 +178,10 @@ public class VendorBootImageHeaderV4 extends VendorBootImageHeaderV3 {
 		return bootconfig_size;
 	}
 
+	public List getVendorRamdiskTableEntryList() {
+		return ramdiskTableEntryList;
+	}
+
 	@Override
 	public DataType toDataType() throws DuplicateNameException, IOException {
 		Structure structure = (Structure) super.toDataType();
@@ -148,7 +189,7 @@ public class VendorBootImageHeaderV4 extends VendorBootImageHeaderV3 {
 			structure.setName("vendor_boot_img_hdr_v4");
 		}
 		catch (InvalidNameException e) {
-			//ignore
+			// ignore
 		}
 		structure.add(DWORD, "vendor_ramdisk_table_size", null);
 		structure.add(DWORD, "vendor_ramdisk_table_entry_num", null);
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java
index 096a34f00b..ab0f6b2224 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java
@@ -35,13 +35,14 @@ public class VendorRamdiskTableEntryV4 implements StructConverter {
 		ramdisk_size = reader.readNextInt();
 		ramdisk_offset = reader.readNextInt();
 		ramdisk_type = reader.readNextInt();
-		ramdisk_name = reader.readNextAsciiString(BootImageConstants.VENDOR_RAMDISK_NAME_SIZE);
-		board_id =
-			reader.readNextIntArray(BootImageConstants.VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE);
+		ramdisk_name = reader.readNextAsciiString(
+			BootImageConstants.VENDOR_RAMDISK_NAME_SIZE);
+		board_id = reader.readNextIntArray(
+			BootImageConstants.VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE);
 	}
 
 	/**
-	 * Size in bytes for the ramdisk image
+	 * Size in bytes for the ramdisk image 
 	 * @return ramdisk size
 	 */
 	public int getRamdiskSize() {
@@ -57,7 +58,7 @@ public class VendorRamdiskTableEntryV4 implements StructConverter {
 	}
 
 	/**
-	 * Type of the ramdisk
+	 * Type of the ramdisk 
 	 * @return ramdisk type
 	 */
 	public int getRamdiskType() {
@@ -65,7 +66,7 @@ public class VendorRamdiskTableEntryV4 implements StructConverter {
 	}
 
 	/**
-	 * Ascii ramdisk name
+	 * Ascii ramdisk name 
 	 * @return the ascii ramdisk name
 	 */
 	public String getRamdiskName() {
@@ -73,8 +74,8 @@ public class VendorRamdiskTableEntryV4 implements StructConverter {
 	}
 
 	/**
-	 * Hardware identifiers describing the board, soc or platform 
-	 * which this ramdisk is intended to be loaded on.
+	 * Hardware identifiers describing the board, soc or platform which this ramdisk
+	 * is intended to be loaded on. 
 	 * @return the board ID
 	 */
 	public int[] getBoardID() {
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK.java
index c4e5c2b24c..6675b6615a 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK.java
@@ -15,70 +15,24 @@
  */
 package ghidra.file.formats.android.fbpk;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
-import ghidra.app.util.bin.BinaryReader;
 import ghidra.app.util.bin.StructConverter;
-import ghidra.program.model.data.*;
-import ghidra.util.exception.DuplicateNameException;
-
-public class FBPK implements StructConverter {
-	private String magic;
-	private int unknown1;
-	private String version;
-	private int partitionCount;
-	private int size;
-	private List partitions = new ArrayList<>();
-
-	public FBPK(BinaryReader reader) throws IOException {
-		magic = reader.readNextAsciiString(FBPK_Constants.FBPK.length());
-		unknown1 = reader.readNextInt();
-		version = reader.readNextAsciiString(FBPK_Constants.VERSION_MAX_LENGTH);
-		partitionCount = reader.readNextInt();
-		size = reader.readNextInt();
-
-		for (int i = 0; i < partitionCount; ++i) {
-			FBPK_Partition partition = new FBPK_Partition(reader);
-			partitions.add(partition);
-			reader.setPointerIndex(partition.getOffsetToNextPartitionTable());
-		}
-	}
-
-	public String getMagic() {
-		return magic;
-	}
-
-	public String getVersion() {
-		return version;
-	}
-
-	public int getPartitionCount() {
-		return partitionCount;
-	}
-
-	public int getSize() {
-		return size;
-	}
-
-	public List getPartitions() {
-		return partitions;
-	}
-
-	public int getUnknown1() {
-		return unknown1;
-	}
-
-	@Override
-	public DataType toDataType() throws DuplicateNameException, IOException {
-		Structure struct = new StructureDataType(FBPK.class.getSimpleName(), 0);
-		struct.add(STRING, FBPK_Constants.FBPK.length(), "magic", null);
-		struct.add(DWORD, "unknown1", null);
-		struct.add(STRING, FBPK_Constants.VERSION_MAX_LENGTH, "version", null);
-		struct.add(DWORD, "count", null);
-		struct.add(DWORD, "size", null);
-		return struct;
-	}
 
+public interface FBPK extends StructConverter {
+	/**
+	 * Returns the MAGIC value.
+	 * @return the MAGIC value
+	 */
+	public int getMagic();
+	/**
+	 * Returns the version.
+	 * @return the version
+	 */
+	public int getVersion();
+	/**
+	 * Returns the list of partitions.
+	 * @return the list of partitions
+	 */
+	public List getPartitions();
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Analyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Analyzer.java
index 9fbf122770..683324676f 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Analyzer.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Analyzer.java
@@ -15,23 +15,28 @@
  */
 package ghidra.file.formats.android.fbpk;
 
-import java.util.List;
-
-import ghidra.app.services.AbstractAnalyzer;
-import ghidra.app.services.AnalyzerType;
-import ghidra.app.util.bin.*;
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.MemoryByteProvider;
 import ghidra.app.util.importer.MessageLog;
+import ghidra.file.analyzers.FileFormatAnalyzer;
 import ghidra.program.model.address.Address;
 import ghidra.program.model.address.AddressSetView;
 import ghidra.program.model.data.DataType;
-import ghidra.program.model.listing.*;
-import ghidra.util.exception.CancelledException;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
 import ghidra.util.task.TaskMonitor;
 
-public class FBPK_Analyzer extends AbstractAnalyzer {
+public class FBPK_Analyzer extends FileFormatAnalyzer {
 
-	public FBPK_Analyzer() {
-		super("Android FBPK Analyzer", "Annotates Android FBPK Files", AnalyzerType.BYTE_ANALYZER);
+	@Override
+	public String getName() {
+		return "Android FBPK Analyzer";
+	}
+
+	@Override
+	public String getDescription() {
+		return "Annotates Android FBPK Files";
 	}
 
 	@Override
@@ -45,48 +50,39 @@ public class FBPK_Analyzer extends AbstractAnalyzer {
 	}
 
 	@Override
-	public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
-			throws CancelledException {
+	public boolean isPrototype() {
+		return false;
+	}
+
+	@Override
+	public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
+			throws Exception {
 
 		Address headerAddress = program.getMinAddress();
 		ByteProvider provider = new MemoryByteProvider(program.getMemory(), headerAddress);
 		BinaryReader reader = new BinaryReader(provider, !program.getLanguage().isBigEndian());
 		try {
-			FBPK header = new FBPK(reader);
+			FBPK header = FBPK_Factory.getFBPK(reader);
 			DataType headerDataType = header.toDataType();
 			Data headerData = program.getListing().createData(headerAddress, headerDataType);
 			if (headerData == null) {
 				log.appendMsg("Unable to apply FBPK data, stopping - " + headerAddress);
 				return false;
 			}
-
 			Address address = headerAddress.add(headerDataType.getLength());
 
-			List partitions = header.getPartitions();
-			for (int i = 0; i < partitions.size(); ++i) {
-				FBPK_Partition partition = partitions.get(i);
-
-				DataType partitionDataType = partition.toDataType();
-				Data partitionData = program.getListing().createData(address, partitionDataType);
-				if (partitionData == null) {
-					log.appendMsg("Unable to apply partition data, stopping - " + address);
-					return false;
+			monitor.initialize(header.getPartitions().size());
+			monitor.setMessage("Marking up paritions...");
+			for (FBPK_Partition partition : header.getPartitions()) {
+				monitor.checkCanceled();
+				monitor.incrementProgress(1);
+				partition.markup(program, address, monitor, log);
+				if (partition.getOffsetToNextPartitionTable() > 0) {
+					address = address.getNewAddress(partition.getOffsetToNextPartitionTable());
 				}
-				program.getListing()
-						.setComment(address, CodeUnit.PLATE_COMMENT,
-							partition.getName() + " - " + i);
-				address = address.add(partitionDataType.getLength());
-
-				if (partition.isDirectory()) {
-					if (!processFBPT(program, address, partition, monitor, log)) {
-						return false;
-					}
+				else {
+					address = address.add(partition.getHeaderSize());
 				}
-				else if (partition.isFile()) {
-					//unused, but leave as placeholder for future
-				}
-
-				address = address.getNewAddress(partition.getOffsetToNextPartitionTable());
 			}
 
 			return true;
@@ -97,39 +93,4 @@ public class FBPK_Analyzer extends AbstractAnalyzer {
 		return false;
 	}
 
-	private boolean processFBPT(Program program, Address address, FBPK_Partition partition,
-			TaskMonitor monitor, MessageLog log) throws Exception {
-
-		FBPT fbpt = partition.getFBPT();
-		DataType fbptDataType = fbpt.toDataType();
-		Data fbptData = program.getListing().createData(address, fbptDataType);
-		if (fbptData == null) {
-			log.appendMsg("Unable to apply FBPT data, stopping - " + address);
-			return false;
-		}
-		String comment = "FBPT" + "\n" + "Num of entries: " + fbpt.getNEntries();
-		program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, comment);
-		address = address.add(fbptDataType.getLength());
-
-		return processFbPtEntries(program, address, fbpt, monitor, log);
-	}
-
-	private boolean processFbPtEntries(Program program, Address address, FBPT fbpt,
-			TaskMonitor monitor, MessageLog log) throws Exception {
-		for (int i = 0; i < fbpt.getEntries().size(); ++i) {
-			FBPT_Entry entry = fbpt.getEntries().get(i);
-			monitor.checkCanceled();
-			DataType entryDataType = entry.toDataType();
-			Data entryData = program.getListing().createData(address, entryDataType);
-			if (entryData == null) {
-				log.appendMsg("Unable to apply FBPT Entry data, stopping - " + address);
-				return false;
-			}
-			program.getListing()
-					.setComment(address, CodeUnit.PLATE_COMMENT, entry.getName() + " - " + i);
-			address = address.add(entryDataType.getLength());
-		}
-		return true;
-	}
-
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Constants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Constants.java
index 77a90d4bc7..60fcdf8736 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Constants.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Constants.java
@@ -20,27 +20,53 @@ import ghidra.program.model.mem.Memory;
 
 public final class FBPK_Constants {
 
-	public final static String FBPK = "FBPK";
-	public final static String FBPT = "FBPT";
-	public final static String PARTITION_TABLE = "partition table";
-	public final static String LAST_PARTITION_ENTRY = "last_parti";
+	public static final int VERSION_1 = 1;
+	public static final int VERSION_2 = 2;
 
-	public final static int PARTITION_TYPE_DIRECTORY = 0;
-	public final static int PARTITION_TYPE_FILE = 1;
-	public final static int NAME_MAX_LENGTH = 36;
-	public final static int VERSION_MAX_LENGTH = 68;
+	public static final String FBPK = "FBPK";
+	public static final String FBPT = "FBPT";
+	public static final String UFPK = "UFPK";
+	public static final String UFSM = "UFSM";
+	public static final String UFSP = "UFSP";
 
+	public static final int FBPK_MAGIC = 0x4B504246;
+	public static final int FBPT_MAGIC = 0x54504246;
+	public static final int UFPK_MAGIC = 0x4B504655;
+	public static final int UFSM_MAGIC = 0x4D534655;
+	public static final int UFSP_MAGIC = 0x50534655;
+
+	public static final int NAME_MAX_LENGTH = 36;
+	public static final int PARTITION_TYPE_DIRECTORY = 0;
+	public static final int PARTITION_TYPE_FILE = 1;
+
+	public static final String PARTITION_TABLE = "partition table";
+	public static final String V1_LAST_PARTITION_ENTRY = "last_parti";
+
+
+	public static final int V1_VERSION_MAX_LENGTH = 68;
+	public static final int V1_PADDING_LENGTH = 2;
+
+	public static final String V2_PARTITION = "partition:";
+	public static final String V2_UFS = "ufs";
+	public static final Object V2_UFSFWUPDATE = "ufsfwupdate";
+
+	public static final int V2_PARTITION_NAME_MAX_LENGTH = 76;
+	public static final int V2_STRING1_MAX_LENGTH = 16;
+	public static final int V2_STRING2_MAX_LENGTH = 68;
+	public static final int V2_FORMAT_MAX_LENGTH = 14;
+	public static final int V2_GUID_MAX_LENGTH = 44;
+	public static final int V2_UFPK_STRING1_MAX_LENGTH = 76;
+	
 	public static boolean isFBPK(Program program) {
 		try {
 			Memory memory = program.getMemory();
-			byte[] bytes = new byte[FBPK.length()];
-			memory.getBytes(program.getMinAddress(), bytes);
-			String magic = new String(bytes).trim();
-			return FBPK.equals(magic);
+			int magic = memory.getInt(program.getMinAddress());
+			return magic == FBPK_MAGIC;
 		}
 		catch (Exception e) {
 			//ignore
 		}
 		return false;
 	}
+
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Factory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Factory.java
new file mode 100644
index 0000000000..c848d94a08
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Factory.java
@@ -0,0 +1,46 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.v1.FBPKv1;
+import ghidra.file.formats.android.fbpk.v2.FBPKv2;
+
+public class FBPK_Factory {
+	public final static FBPK getFBPK(BinaryReader reader)
+			throws IOException {
+
+		if (reader.length() > 8) {
+			int magic = reader.readInt(0);
+			int version = reader.readInt(4);
+
+			if (magic == FBPK_Constants.FBPK_MAGIC) {
+				switch (version) {
+					case FBPK_Constants.VERSION_1: {
+						return new FBPKv1(reader);
+					}
+					case FBPK_Constants.VERSION_2: {
+						return new FBPKv2(reader);
+					}
+				}
+			}
+			throw new IOException("Unsupported " + FBPK_Constants.FBPK + " version: " + version);
+		}
+		throw new IOException("Invalid " + FBPK_Constants.FBPK + " file");
+	}
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_FileSystem.java
index 2b0d45a667..3b948298ad 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_FileSystem.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_FileSystem.java
@@ -26,8 +26,11 @@ import ghidra.util.exception.CancelledException;
 import ghidra.util.exception.CryptoException;
 import ghidra.util.task.TaskMonitor;
 
-@FileSystemInfo(type = "androidbootloaderfbpk", // ([a-z0-9]+ only)
-		description = "Android Boot Loader Image (FBPK)", factory = GFileSystemBaseFactory.class)
+@FileSystemInfo(
+	type = "androidbootloaderfbpk", // ([a-z0-9]+ only)
+	description = "Android Boot Loader Image (FBPK)",
+	factory = GFileSystemBaseFactory.class
+)
 
 public class FBPK_FileSystem extends GFileSystemBase {
 	private List fileList = new ArrayList<>();
@@ -46,7 +49,7 @@ public class FBPK_FileSystem extends GFileSystemBase {
 	@Override
 	public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
 		BinaryReader reader = new BinaryReader(provider, true /*might not always be LE*/ );
-		FBPK header = new FBPK(reader);
+		FBPK header = FBPK_Factory.getFBPK(reader);
 		List partitions = header.getPartitions();
 		for (FBPK_Partition partition : partitions) {
 			if (partition.isFile()) {
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Partition.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Partition.java
index 9158adfc00..460ccb4ac5 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Partition.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPK_Partition.java
@@ -15,90 +15,114 @@
  */
 package ghidra.file.formats.android.fbpk;
 
-import java.io.IOException;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.program.flatapi.FlatProgramAPI;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.RefType;
+import ghidra.util.task.TaskMonitor;
 
-import ghidra.app.util.bin.*;
-import ghidra.program.model.data.*;
-import ghidra.util.exception.DuplicateNameException;
+public abstract class FBPK_Partition implements StructConverter {
 
-public class FBPK_Partition implements StructConverter {
-	private int type;
-	private String name;
-	private int dataSize;
-	private int unknown1;
-	private int offsetToNextPartitionTable;
-	private int unknown2;
-	private FBPT fbpt;
-	private long dataStartOffset;
+	protected int headerSize;
+	protected int type;
+	protected String name;
+	protected int partitionIndex;
 
-	public FBPK_Partition(BinaryReader reader) throws IOException {
-		type = reader.readNextInt();
-		name = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH);
-		dataSize = reader.readNextInt();
-		unknown1 = reader.readNextInt();
-		offsetToNextPartitionTable = reader.readNextInt();
-		unknown2 = reader.readNextInt();
-		if (type == FBPK_Constants.PARTITION_TYPE_DIRECTORY) {
-			fbpt = new FBPT(reader);
-		}
-		else if (type == FBPK_Constants.PARTITION_TYPE_FILE) {
-			dataStartOffset = reader.getPointerIndex();
-		}
+	/**
+	 * Returns the size of the partition's header, in bytes.
+	 * @return the size of the partition's header, in bytes
+	 */
+	public final int getHeaderSize() {
+		return headerSize;
 	}
 
-	public int getType() {
+	/**
+	 * Returns the partition's type.
+	 * @return the partition's type
+	 */
+	public final int getType() {
 		return type;
 	}
 
-	public String getName() {
+	/**
+	 * Returns the partition's name.
+	 * @return the partition's name
+	 */
+	public final String getName() {
 		return name;
 	}
 
 	/**
-	 * Returns the Fast Boot Partition Table
-	 * @return the Fast Boot Partition Table, could be null if file
+	 * Returns the offsets to the start of this
+	 * partition's data payload.
+	 * @return offsets to the partition's data
 	 */
-	public FBPT getFBPT() {
-		return fbpt;
+	public abstract long getDataStartOffset();
+
+	/**
+	 * Returns the partition's data payload size.
+	 * @return the partition's data payload size
+	 */
+	public abstract int getDataSize();
+
+	/**
+	 * Returns true if this partition represents a file.
+	 * @return true if this partition represents a file
+	 */
+	public abstract boolean isFile();
+
+	/**
+	 * Returns the offset to the next partition (for non-adjoining partitions).
+	 * Returns 0 is the next partition is adjoingin (immediately following the previous).
+	 * @return offset to the next partition, or 0
+	 */
+	public abstract int getOffsetToNextPartitionTable();
+
+	/**
+	 * Returns the partition's index.
+	 * @return the partition's index
+	 */
+	public final int getPartitionIndex() {
+		return partitionIndex;
 	}
 
-	public long getDataStartOffset() {
-		return dataStartOffset;
-	}
+	/**
+	 * Annotates the program with this partition's data structures.
+	 * @param program the program to markup
+	 * @param address the address of the partition
+	 * @param monitor the task monitor
+	 * @param log the message log
+	 * @throws Exception if any exception occurs during markup
+	 */
+	public void markup(Program program, Address address, TaskMonitor monitor, MessageLog log) throws Exception {
+		FlatProgramAPI api = new FlatProgramAPI(program);
 
-	public int getDataSize() {
-		return dataSize;
-	}
+		DataType partitionDataType = toDataType();
 
-	public int getOffsetToNextPartitionTable() {
-		return offsetToNextPartitionTable;
-	}
+		Data partitionData = program.getListing().createData(address, partitionDataType);
 
-	public boolean isDirectory() {
-		return getType() == FBPK_Constants.PARTITION_TYPE_DIRECTORY;
-	}
+		if (partitionData == null) {
+			log.appendMsg("Unable to apply partition data, stopping - " + address);
+			return;
+		}
 
-	public boolean isFile() {
-		return getType() == FBPK_Constants.PARTITION_TYPE_FILE;
-	}
+		program.getListing()
+				.setComment(address, CodeUnit.PLATE_COMMENT,
+					getName() + " - " + getPartitionIndex());
 
-	public int getUnknown1() {
-		return unknown1;
-	}
+		api.createFragment(getName(), address, partitionDataType.getLength());
 
-	public int getUnknown2() {
-		return unknown2;
-	}
+		Address dataStart = api.toAddr(getDataStartOffset());
+		api.createFragment(getName(), dataStart, getDataSize());
 
-	@Override
-	public DataType toDataType() throws DuplicateNameException, IOException {
-		Structure struct = new StructureDataType(FBPK_Partition.class.getSimpleName(), 0);
-		struct.add(DWORD, "type", null);
-		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH, "name", null);
-		struct.add(DWORD, "dataSize", null);
-		struct.add(DWORD, "unknown1", null);
-		struct.add(DWORD, "offsetToNextPartitionTable", null);
-		struct.add(DWORD, "unknown2", null);
-		return struct;
+		Data offsetData = partitionData.getComponent(2);
+		api.createMemoryReference(offsetData, dataStart, RefType.DATA);
+
+		address = address.add(partitionDataType.getLength());
 	}
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT.java
index dd3cfd6892..d722faa174 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT.java
@@ -15,159 +15,53 @@
  */
 package ghidra.file.formats.android.fbpk;
 
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 
-import ghidra.app.util.bin.BinaryReader;
 import ghidra.app.util.bin.StructConverter;
-import ghidra.program.model.data.*;
-import ghidra.util.exception.DuplicateNameException;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.program.flatapi.FlatProgramAPI;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.listing.*;
+import ghidra.util.task.TaskMonitor;
 
-public class FBPT implements StructConverter {
-	private String magic;
-	private int unknown1;
-	private int unknown2;
-	private int unknown3;
-	private int nEntries;
-	private int unknownA;
-	private int unknownB;
-	private int unknownC;
-	private int unknownD;
-	private int unknownE;
-	private int unknownF;
-	private int unknownG;
-	private int unknownH;
-	private int unknownI;
-	private int unknownJ;
-	private int unknownK;
-	private int unknownL;
-	private int unknownM;
-	private List entries = new ArrayList<>();
+public abstract class FBPT implements StructConverter {
 
-	public FBPT(BinaryReader reader) throws IOException {
-		magic = reader.readNextAsciiString(FBPK_Constants.FBPT.length());
-		unknown1 = reader.readNextInt();
-		unknown2 = reader.readNextInt();
-		unknown3 = reader.readNextInt();
-		nEntries = reader.readNextInt();
-		unknownA = reader.readNextInt();
-		unknownB = reader.readNextInt();
-		unknownC = reader.readNextInt();
-		unknownD = reader.readNextInt();
-		unknownE = reader.readNextInt();
-		unknownF = reader.readNextInt();
-		unknownG = reader.readNextInt();
-		unknownH = reader.readNextInt();
-		unknownI = reader.readNextInt();
-		unknownJ = reader.readNextInt();
-		unknownK = reader.readNextInt();
-		unknownL = reader.readNextInt();
-		unknownM = reader.readNextInt();
+	public abstract String getMagic();
 
-		for (int i = 0; i < nEntries; ++i) {
-			entries.add(new FBPT_Entry(reader, i == nEntries - 1));
+	public abstract List getEntries();
+
+	public void processFBPT(Program program, Address address, TaskMonitor monitor, MessageLog log) throws Exception {
+		FlatProgramAPI api = new FlatProgramAPI(program);
+
+		DataType fbptDataType = toDataType();
+		Data fbptData = program.getListing().createData(address, fbptDataType);
+		if (fbptData == null) {
+			log.appendMsg("Unable to apply FBPT data, stopping - " + address);
+			return;
+		}
+		String comment = "FBPT" + "\n" + "Num of entries: " + getEntries().size();
+		program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, comment);
+		api.createFragment(FBPK_Constants.FBPT, address, fbptDataType.getLength());
+		address = address.add(fbptDataType.getLength());
+
+		processFbPtEntries(program, address, monitor, log);
+	}
+
+	private void processFbPtEntries(Program program, Address address, TaskMonitor monitor, MessageLog log) throws Exception {
+		int i = 0;
+		FlatProgramAPI api = new FlatProgramAPI(program);
+		for (FBPT_Entry entry : getEntries()) {
+			monitor.checkCanceled();
+			DataType entryDataType = entry.toDataType();
+			Data entryData = program.getListing().createData(address, entryDataType);
+			if (entryData == null) {
+				log.appendMsg("Unable to apply FBPT Entry data, stopping - " + address);
+				return;
+			}
+			program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, entry.getName() + " - " + i++);
+			api.createFragment(FBPK_Constants.FBPT, address, entryDataType.getLength());
+			address = address.add(entryDataType.getLength());
 		}
 	}
-
-	public String getMagic() {
-		return magic;
-	}
-
-	public int getNEntries() {
-		return nEntries;
-	}
-
-	public List getEntries() {
-		return entries;
-	}
-
-	public int getUnknown1() {
-		return unknown1;
-	}
-
-	public int getUnknown2() {
-		return unknown2;
-	}
-
-	public int getUnknown3() {
-		return unknown3;
-	}
-
-	public int getUnknownA() {
-		return unknownA;
-	}
-
-	public int getUnknownB() {
-		return unknownB;
-	}
-
-	public int getUnknownC() {
-		return unknownC;
-	}
-
-	public int getUnknownD() {
-		return unknownD;
-	}
-
-	public int getUnknownE() {
-		return unknownE;
-	}
-
-	public int getUnknownF() {
-		return unknownF;
-	}
-
-	public int getUnknownG() {
-		return unknownG;
-	}
-
-	public int getUnknownH() {
-		return unknownH;
-	}
-
-	public int getUnknownI() {
-		return unknownI;
-	}
-
-	public int getUnknownJ() {
-		return unknownJ;
-	}
-
-	public int getUnknownK() {
-		return unknownK;
-	}
-
-	public int getUnknownL() {
-		return unknownL;
-	}
-
-	public int getUnknownM() {
-		return unknownM;
-	}
-
-	@Override
-	public DataType toDataType() throws DuplicateNameException, IOException {
-		Structure struct = new StructureDataType(FBPT.class.getSimpleName(), 0);
-		struct.add(STRING, FBPK_Constants.FBPT.length(), "magic", null);
-		struct.add(DWORD, "unknown1", null);
-		struct.add(DWORD, "unknown2", null);
-		struct.add(DWORD, "unknown3", null);
-		struct.add(DWORD, "nEntries", null);
-		struct.add(DWORD, "unknownA", null);
-		struct.add(DWORD, "unknownB", null);
-		struct.add(DWORD, "unknownC", null);
-		struct.add(DWORD, "unknownD", null);
-		struct.add(DWORD, "unknownE", null);
-		struct.add(DWORD, "unknownF", null);
-		struct.add(DWORD, "unknownG", null);
-		struct.add(DWORD, "unknownH", null);
-		struct.add(DWORD, "unknownI", null);
-		struct.add(DWORD, "unknownJ", null);
-		struct.add(DWORD, "unknownK", null);
-		struct.add(DWORD, "unknownL", null);
-		struct.add(DWORD, "unknownM", null);
-		return struct;
-	}
-
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT_Entry.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT_Entry.java
index 096e526414..d6a237f2e7 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT_Entry.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/FBPT_Entry.java
@@ -15,91 +15,10 @@
  */
 package ghidra.file.formats.android.fbpk;
 
-import java.io.IOException;
-
-import ghidra.app.util.bin.BinaryReader;
 import ghidra.app.util.bin.StructConverter;
-import ghidra.program.model.data.*;
-import ghidra.util.InvalidNameException;
-import ghidra.util.exception.DuplicateNameException;
 
-public class FBPT_Entry implements StructConverter {
-	private String name;
-	private String guid1;
-	private String guid2;
-	private String padding;
-	private int unknown1;
-	private int unknown2;
-	private int unknown3;
-	private boolean isLast;
+public abstract class FBPT_Entry implements StructConverter {
 
-	public FBPT_Entry(BinaryReader reader, boolean isLast) throws IOException {
-		this.isLast = isLast;
-		name = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH);//not +1
-		guid1 = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH + 1);
-		guid2 = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH + 1);
-		padding = reader.readNextAsciiString(2);
-		if (FBPK_Constants.LAST_PARTITION_ENTRY.equals(name)) {
-			return;
-		}
-		unknown1 = reader.readNextInt();
-		if (!isLast) {
-			unknown2 = reader.readNextInt();
-			unknown3 = reader.readNextInt();
-		}
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public String getGuid1() {
-		return guid1;
-	}
-
-	public String getGuid2() {
-		return guid2;
-	}
-
-	public String getPadding() {
-		return padding;
-	}
-
-	public int getUnknown1() {
-		return unknown1;
-	}
-
-	public int getUnknown2() {
-		return unknown2;
-	}
-
-	public int getUnknown3() {
-		return unknown3;
-	}
-
-	@Override
-	public DataType toDataType() throws DuplicateNameException, IOException {
-		Structure struct = new StructureDataType(FBPT_Entry.class.getSimpleName(), 0);
-		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH, "name", null);
-		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH + 1, "guid1", null);
-		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH + 1, "guid2", null);
-		struct.add(STRING, 2, "padding", null);
-		if (FBPK_Constants.LAST_PARTITION_ENTRY.equals(name) || isLast) {
-			try {
-				struct.setName(FBPT_Entry.class.getSimpleName() + "_last");
-			}
-			catch (InvalidNameException e) {
-				//ignore
-			}
-		}
-		else {
-			struct.add(DWORD, "unknown1", null);
-			if (!isLast) {
-				struct.add(DWORD, "unknown2", null);
-				struct.add(DWORD, "unknown3", null);
-			}
-		}
-		return struct;
-	}
+	public abstract String getName();
 
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1.java
new file mode 100644
index 0000000000..fa67b985fe
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1.java
@@ -0,0 +1,91 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v1;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.FBPK;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPK_Partition;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class FBPKv1 implements FBPK {
+	private int magic;
+	private int version;
+	private String string;
+	private int partitionCount;
+	private int size;
+	private List partitions = new ArrayList<>();
+
+	public FBPKv1(BinaryReader reader) throws IOException {
+		magic = reader.readNextInt();
+		version = reader.readNextInt();
+		string = reader.readNextAsciiString(FBPK_Constants.V1_VERSION_MAX_LENGTH);
+		partitionCount = reader.readNextInt();
+		size = reader.readNextInt();
+
+		for (int i = 0; i < partitionCount; ++i) {
+			FBPKv1_Partition partition = new FBPKv1_Partition(reader);
+			partitions.add(partition);
+			reader.setPointerIndex(partition.getOffsetToNextPartitionTable());
+		}
+	}
+
+	@Override
+	public int getMagic() {
+		return magic;
+	}
+
+	@Override
+	public int getVersion() {
+		return version;
+	}
+
+	public String getString() {
+		return string;
+	}
+
+	public int getPartitionCount() {
+		return partitionCount;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	@Override
+	public List getPartitions() {
+		return partitions;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPKv1.class.getSimpleName(), 0);
+		struct.add(STRING, FBPK_Constants.FBPK.length(), "magic", null);
+		struct.add(DWORD, "version", null);
+		struct.add(STRING, FBPK_Constants.V1_VERSION_MAX_LENGTH, "string", null);
+		struct.add(DWORD, "partitionCount", null);
+		struct.add(DWORD, "size", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1_Partition.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1_Partition.java
new file mode 100644
index 0000000000..9dc0a074c6
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPKv1_Partition.java
@@ -0,0 +1,128 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v1;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPK_Partition;
+import ghidra.file.formats.android.fbpk.FBPT;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.Program;
+import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.task.TaskMonitor;
+
+public class FBPKv1_Partition extends FBPK_Partition {
+
+	private int dataSize;
+	private int unknown1;
+	private int offsetToNextPartitionTable;
+	private int unknown2;
+	private FBPTv1 fbpt;
+	private long dataStartOffset;
+
+	public FBPKv1_Partition(BinaryReader reader) throws IOException {
+		long start = reader.getPointerIndex();
+
+		type = reader.readNextInt();
+		name = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH);
+		dataSize = reader.readNextInt();
+		unknown1 = reader.readNextInt();
+		offsetToNextPartitionTable = reader.readNextInt();
+		unknown2 = reader.readNextInt();
+
+		headerSize = (int) (reader.getPointerIndex() - start);
+
+		if (type == FBPK_Constants.PARTITION_TYPE_DIRECTORY) {
+			fbpt = new FBPTv1(reader);
+		}
+		else if (type == FBPK_Constants.PARTITION_TYPE_FILE) {
+			dataStartOffset = reader.getPointerIndex();
+		}
+	}
+
+	/**
+	 * Returns the FBPT.
+	 * Could be null if this partition is a FILE.
+	 * @return the FBPT
+	 */
+	public FBPT getFBPT() {
+		return fbpt;
+	}
+
+	@Override
+	public long getDataStartOffset() {
+		return dataStartOffset;
+	}
+
+	@Override
+	public int getDataSize() {
+		return dataSize;
+	}
+
+	public int getOffsetToNextPartitionTable() {
+		return offsetToNextPartitionTable;
+	}
+
+	public boolean isDirectory() {
+		return getType() == FBPK_Constants.PARTITION_TYPE_DIRECTORY;
+	}
+
+	@Override
+	public boolean isFile() {
+		return getType() == FBPK_Constants.PARTITION_TYPE_FILE;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	@Override
+	public void markup(Program program, Address address, TaskMonitor monitor, MessageLog log) throws Exception {
+
+		super.markup(program, address, monitor, log);
+
+		if (isDirectory()) {
+			if (fbpt != null) {
+				fbpt.processFBPT(program, address.add(headerSize), monitor, log);
+			}
+		}
+		else if (isFile()) {
+			//unused, but leave as placeholder for future
+		}
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPKv1_Partition.class.getSimpleName(), 0);
+		struct.add(DWORD, "type", null);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH, "name", null);
+		struct.add(DWORD, "dataSize", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(DWORD, "offsetToNextPartitionTable", null);
+		struct.add(DWORD, "unknown2", null);
+		return struct;
+	}
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1.java
new file mode 100644
index 0000000000..e08275eef5
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1.java
@@ -0,0 +1,95 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v1;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.*;
+import ghidra.program.model.data.*;
+import ghidra.util.exception.DuplicateNameException;
+
+public class FBPTv1 extends FBPT {
+	private String magic;
+	private int nEntries;
+	private List entries = new ArrayList<>();
+
+	public FBPTv1(BinaryReader reader) throws IOException {
+		magic = reader.readNextAsciiString(FBPK_Constants.FBPT.length());
+		reader.readNextInt();//unknown0
+		reader.readNextInt();//unknown1
+		reader.readNextInt();//unknown2
+		nEntries = reader.readNextInt();
+		reader.readNextInt();//unknown3
+		reader.readNextInt();//unknown4
+		reader.readNextInt();//unknown5
+		reader.readNextInt();//unknown6
+		reader.readNextInt();//unknown7
+		reader.readNextInt();//unknown8
+		reader.readNextInt();//unknown9
+		reader.readNextInt();//unknownA
+		reader.readNextInt();//unknownB
+		reader.readNextInt();//unknownC
+		reader.readNextInt();//unknownD
+		reader.readNextInt();//unknownE
+		reader.readNextInt();//unknownF
+
+		for (int i = 0; i < nEntries; ++i) {
+			entries.add(new FBPTv1_Entry(reader, i == nEntries - 1));
+		}
+	}
+
+	@Override
+	public String getMagic() {
+		return magic;
+	}
+
+	public int getNEntries() {
+		return nEntries;
+	}
+
+	@Override
+	public List getEntries() {
+		return entries;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPTv1.class.getSimpleName(), 0);
+		struct.add(STRING, FBPK_Constants.FBPT.length(), "magic", null);
+		struct.add(DWORD, "unknown0", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(DWORD, "unknown2", null);
+		struct.add(DWORD, "nEntries", null);
+		struct.add(DWORD, "unknown3", null);
+		struct.add(DWORD, "unknown4", null);
+		struct.add(DWORD, "unknown5", null);
+		struct.add(DWORD, "unknown6", null);
+		struct.add(DWORD, "unknown7", null);
+		struct.add(DWORD, "unknown8", null);
+		struct.add(DWORD, "unknown9", null);
+		struct.add(DWORD, "unknownA", null);
+		struct.add(DWORD, "unknownB", null);
+		struct.add(DWORD, "unknownC", null);
+		struct.add(DWORD, "unknownD", null);
+		struct.add(DWORD, "unknownE", null);
+		struct.add(DWORD, "unknownF", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1_Entry.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1_Entry.java
new file mode 100644
index 0000000000..7b1c2b5756
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v1/FBPTv1_Entry.java
@@ -0,0 +1,108 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v1;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPT_Entry;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.InvalidNameException;
+import ghidra.util.exception.DuplicateNameException;
+
+public class FBPTv1_Entry extends FBPT_Entry {
+	private String name;
+	private String guid1;
+	private String guid2;
+	private String padding;
+	private int unknown1;
+	private int unknown2;
+	private int unknown3;
+	private boolean isLast;
+
+	public FBPTv1_Entry(BinaryReader reader, boolean isLast) throws IOException {
+		this.isLast = isLast;
+		name = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH);//not +1
+		guid1 = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH + 1);
+		guid2 = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH + 1);
+		padding = reader.readNextAsciiString(FBPK_Constants.V1_PADDING_LENGTH);
+		if (FBPK_Constants.V1_LAST_PARTITION_ENTRY.equals(name)) {
+			return;
+		}
+		unknown1 = reader.readNextInt();
+		if (!isLast) {
+			unknown2 = reader.readNextInt();
+			unknown3 = reader.readNextInt();
+		}
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getGuid1() {
+		return guid1;
+	}
+
+	public String getGuid2() {
+		return guid2;
+	}
+
+	public String getPadding() {
+		return padding;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	public int getUnknown3() {
+		return unknown3;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPTv1_Entry.class.getSimpleName(), 0);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH, "name", null);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH + 1, "guid1", null);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH + 1, "guid2", null);
+		struct.add(STRING, FBPK_Constants.V1_PADDING_LENGTH, "padding", null);
+		if (FBPK_Constants.V1_LAST_PARTITION_ENTRY.equals(name) || isLast) {
+			try {
+				struct.setName(FBPTv1_Entry.class.getSimpleName() + "_last");
+			}
+			catch (InvalidNameException e) {
+				//ignore
+			}
+		}
+		else {
+			struct.add(DWORD, "unknown1", null);
+			if (!isLast) {
+				struct.add(DWORD, "unknown2", null);
+				struct.add(DWORD, "unknown3", null);
+			}
+		}
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2.java
new file mode 100644
index 0000000000..f234e949a0
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2.java
@@ -0,0 +1,118 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.FBPK;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPK_Partition;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class FBPKv2 implements FBPK {
+	private int magic;
+	private int version;
+	private int unknown1;
+	private int unknown2;
+	private String string1;
+	private String string2;
+	private int unknown3;
+	private int partitionCount;
+	private int size;
+
+	private List partitions = new ArrayList<>();
+
+	public FBPKv2(BinaryReader reader) throws IOException {
+		magic = reader.readNextInt();
+		version = reader.readNextInt();
+		unknown1 = reader.readNextInt();
+		unknown2 = reader.readNextInt();
+		string1 = reader.readNextAsciiString(FBPK_Constants.V2_STRING1_MAX_LENGTH);
+		string2 = reader.readNextAsciiString(FBPK_Constants.V2_STRING2_MAX_LENGTH);
+		unknown3 = reader.readNextInt();
+		partitionCount = reader.readNextInt();
+		size = reader.readNextInt();
+
+		for (int i = 0; i < partitionCount; ++i) {
+			partitions.add(new FBPKv2_Partition(reader));
+		}
+	}
+
+	@Override
+	public int getMagic() {
+		return magic;
+	}
+
+	@Override
+	public int getVersion() {
+		return version;
+	}
+
+	public int getPartitionCount() {
+		return partitionCount;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	@Override
+	public List getPartitions() {
+		return new ArrayList(partitions);
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	public int getUnknown3() {
+		return unknown3;
+	}
+
+	public String getString1() {
+		return string1;
+	}
+
+	public String getString2() {
+		return string2;
+	};
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPKv2.class.getSimpleName(), 0);
+		struct.add(STRING, FBPK_Constants.FBPK.length(), "magic", null);
+		struct.add(DWORD, "version", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(DWORD, "unknown2", null);
+		struct.add(STRING, FBPK_Constants.V2_STRING1_MAX_LENGTH, "string1", null);
+		struct.add(STRING, FBPK_Constants.V2_STRING2_MAX_LENGTH, "string2", null);
+		struct.add(DWORD, "unknown3", null);
+		struct.add(DWORD, "partitionCount", null);
+		struct.add(DWORD, "size", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2_Partition.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2_Partition.java
new file mode 100644
index 0000000000..a60797e602
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPKv2_Partition.java
@@ -0,0 +1,214 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.importer.MessageLog;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPK_Partition;
+import ghidra.file.formats.android.fbpk.FBPT;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
+import ghidra.util.exception.DuplicateNameException;
+import ghidra.util.task.TaskMonitor;
+
+public class FBPKv2_Partition extends FBPK_Partition {
+
+	private long offset;
+	private int unknown1;
+	private int size;
+	private int unknown2;
+	private int paritionType;
+	private int unknown3;
+
+	private FBPT fbpt;
+	private UFPK ufpk;
+	private UFSM ufsm;
+	private UFSP ufsp;
+
+	public FBPKv2_Partition(BinaryReader reader) throws IOException {
+		long start = reader.getPointerIndex();
+
+		type = reader.readNextInt();
+		name = reader.readNextAsciiString(FBPK_Constants.V2_PARTITION_NAME_MAX_LENGTH);
+		offset = reader.readNextInt();
+		unknown1 = reader.readNextInt();
+		size = reader.readNextInt();
+		unknown2 = reader.readNextInt();
+		paritionType = reader.readNextInt();
+		unknown3 = reader.readNextInt();
+
+		headerSize = (int) (reader.getPointerIndex() - start);
+
+		BinaryReader clone = reader.clone(offset);
+
+		if (paritionType == 0) {
+			if (name.startsWith(FBPK_Constants.V2_PARTITION)) {
+				fbpt = new FBPTv2(clone);
+			}
+			if (name.equals(FBPK_Constants.V2_UFS)) {
+				if (clone.peekNextInt() == FBPK_Constants.UFSM_MAGIC) {
+					ufsm = new UFSM(clone);
+				}
+				else if (clone.peekNextInt() == FBPK_Constants.UFSP_MAGIC) {
+					ufsp = new UFSP(clone);
+				}
+			}
+			if (name.equals(FBPK_Constants.V2_UFSFWUPDATE)) {
+				ufpk = new UFPK(clone);
+			}
+		}
+	}
+
+	@Override
+	public long getDataStartOffset() {
+		return offset;
+	}
+
+	@Override
+	public int getDataSize() {
+		return size;
+	}
+
+	public FBPT getFBPT() {
+		return fbpt;
+	}
+
+	public UFPK getUFPK() {
+		return ufpk;
+	}
+
+	public UFSM getUFSM() {
+		return ufsm;
+	}
+
+	public UFSP getUFSP() {
+		return ufsp;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	public int getParitionType() {
+		return paritionType;
+	}
+
+	public int getUnknown3() {
+		return unknown3;
+	}
+
+	@Override
+	public boolean isFile() {
+		return paritionType == FBPK_Constants.PARTITION_TYPE_FILE;
+	}
+
+	@Override
+	public int getOffsetToNextPartitionTable() {
+		return 0;
+	}
+
+	@Override
+	public void markup(Program program, Address address, TaskMonitor monitor, MessageLog log)
+			throws Exception {
+
+		super.markup(program, address, monitor, log);
+
+		processFBPT(program, monitor, log);
+		processUFPK(program, monitor, log);
+		processUFSM(program, monitor, log);
+		processUFSP(program, monitor, log);
+	}
+
+	private void processFBPT(Program program, TaskMonitor monitor, MessageLog log)
+			throws Exception {
+
+		Address address = program.getMinAddress().getNewAddress(offset);
+
+		if (fbpt != null) {
+			fbpt.processFBPT(program, address, monitor, log);
+		}
+	}
+
+	private void processUFPK(Program program, TaskMonitor monitor, MessageLog log)
+			throws Exception {
+
+		Address address = program.getMinAddress().getNewAddress(offset);
+
+		if (ufpk != null) {
+			DataType dataType = ufpk.toDataType();
+			Data data = program.getListing().createData(address, dataType);
+			if (data == null) {
+				log.appendMsg("Unable to apply " + FBPK_Constants.UFPK_MAGIC +
+					" data, stopping - " + address);
+			}
+		}
+	}
+
+	private void processUFSM(Program program, TaskMonitor monitor, MessageLog log)
+			throws Exception {
+
+		Address address = program.getMinAddress().getNewAddress(offset);
+
+		if (ufsm != null) {
+			DataType dataType = ufsm.toDataType();
+			Data data = program.getListing().createData(address, dataType);
+			if (data == null) {
+				log.appendMsg(
+					"Unable to apply " + FBPK_Constants.UFSM + " data, stopping - " + address);
+			}
+		}
+	}
+
+	private void processUFSP(Program program, TaskMonitor monitor, MessageLog log)
+			throws Exception {
+
+		Address address = program.getMinAddress().getNewAddress(offset);
+
+		if (ufsp != null) {
+			DataType dataType = ufsp.toDataType();
+			Data data = program.getListing().createData(address, dataType);
+			if (data == null) {
+				log.appendMsg(
+					"Unable to apply " + FBPK_Constants.UFSP + " data, stopping - " + address);
+			}
+		}
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPKv2_Partition.class.getSimpleName(), 0);
+		struct.add(DWORD, "type", null);
+		struct.add(STRING, FBPK_Constants.V2_PARTITION_NAME_MAX_LENGTH, "name", null);
+		struct.add(DWORD, "offset", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(DWORD, "size", null);
+		struct.add(DWORD, "unknown2", null);
+		struct.add(DWORD, "paritionType", null);
+		struct.add(DWORD, "unknown3", null);
+		return struct;
+	}
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2.java
new file mode 100644
index 0000000000..b6d07bbd5a
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2.java
@@ -0,0 +1,88 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPT;
+import ghidra.file.formats.android.fbpk.FBPT_Entry;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class FBPTv2 extends FBPT {
+	private String magic;
+	private int nEntries;
+	private String guid;
+
+	private List entries = new ArrayList<>();
+
+	public FBPTv2(BinaryReader reader) throws IOException {
+		magic = reader.readNextAsciiString(FBPK_Constants.FBPT.length());
+		reader.readNextInt();//unknown0
+		reader.readNextInt();//unknown1
+		reader.readNextInt();//unknown2
+		nEntries = reader.readNextInt();
+		guid = reader.readNextAsciiString(FBPK_Constants.V2_GUID_MAX_LENGTH);
+		reader.readNextInt();//unknown3
+		reader.readNextInt();//unknown4
+		reader.readNextInt();//unknown5
+
+		for (int i = 0; i < nEntries; ++i) {
+			entries.add(new FBPTv2_Entry(reader, i == nEntries - 1));
+		}
+	}
+
+	@Override
+	public String getMagic() {
+		return magic;
+	}
+
+	public int getNEntries() {
+		return nEntries;
+	}
+
+	@Override
+	public List getEntries() {
+		return entries;
+	}
+
+	public String getGUID() {
+		return guid;
+	}
+
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPTv2.class.getSimpleName(), 0);
+		struct.add(STRING, FBPK_Constants.FBPT.length(), "magic", null);
+		struct.add(DWORD, "unknown0", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(DWORD, "unknown2", null);
+		struct.add(DWORD, "nEntries", null);
+		struct.add(STRING, FBPK_Constants.V2_GUID_MAX_LENGTH, "guid", null);
+		struct.add(DWORD, "unknown3", null);
+		struct.add(DWORD, "unknown4", null);
+		struct.add(DWORD, "unknown5", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2_Entry.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2_Entry.java
new file mode 100644
index 0000000000..9c5091b7cb
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/FBPTv2_Entry.java
@@ -0,0 +1,94 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.file.formats.android.fbpk.FBPT_Entry;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class FBPTv2_Entry extends FBPT_Entry {
+	private String name;
+	private String guid1;
+	private String guid2;
+	private String format;
+	private int unknown1;
+	private int unknown2;
+	private int unknown3;
+	private boolean isLast;
+
+	public FBPTv2_Entry(BinaryReader reader, boolean isLast) throws IOException {
+		this.isLast = isLast;
+		name = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH);//not +1
+		guid1 = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH + 1);
+		guid2 = reader.readNextAsciiString(FBPK_Constants.NAME_MAX_LENGTH + 1);
+		format = reader.readNextAsciiString(FBPK_Constants.V2_FORMAT_MAX_LENGTH);
+		unknown1 = reader.readNextInt();
+		unknown2 = reader.readNextInt();
+		unknown3 = reader.readNextInt();
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public String getGuid1() {
+		return guid1;
+	}
+
+	public String getGuid2() {
+		return guid2;
+	}
+
+	public String getFormat() {
+		return format;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	public int getUnknown3() {
+		return unknown3;
+	}
+
+	public boolean isLast() {
+		return isLast;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(FBPTv2_Entry.class.getSimpleName(), 0);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH, "name", null);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH + 1, "guid1", null);
+		struct.add(STRING, FBPK_Constants.NAME_MAX_LENGTH + 1, "guid2", null);
+		struct.add(STRING, FBPK_Constants.V2_FORMAT_MAX_LENGTH, "format", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(DWORD, "unknown2", null);
+		struct.add(DWORD, "unknown3", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFPK.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFPK.java
new file mode 100644
index 0000000000..f0f2b8edb5
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFPK.java
@@ -0,0 +1,60 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UFPK implements StructConverter {
+	private String magic;
+	private int unknown1;
+	private String string1;
+
+	public UFPK(BinaryReader reader) throws IOException {
+		magic = reader.readNextAsciiString(FBPK_Constants.UFPK.length());
+		unknown1 = reader.readNextInt();
+		string1 = reader.readNextAsciiString(FBPK_Constants.V2_UFPK_STRING1_MAX_LENGTH);
+	}
+
+	public String getMagic() {
+		return magic;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public String getString1() {
+		return string1;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(UFPK.class.getSimpleName(), 0);
+		struct.add(STRING, magic.length(), "magic", null);
+		struct.add(DWORD, "unknown1", null);
+		struct.add(STRING, FBPK_Constants.V2_UFPK_STRING1_MAX_LENGTH, "string2", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSM.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSM.java
new file mode 100644
index 0000000000..2e70943b4c
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSM.java
@@ -0,0 +1,58 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UFSM implements StructConverter {
+	private String magic;
+	private int unknown1;
+	private String string1;
+
+	public UFSM(BinaryReader reader) throws IOException {
+		magic = reader.readNextAsciiString(FBPK_Constants.UFSM.length());
+		unknown1 = reader.readNextInt();
+	}
+
+	public String getMagic() {
+		return magic;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public String getString1() {
+		return string1;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(UFSM.class.getSimpleName(), 0);
+		struct.add(STRING, magic.length(), "magic", null);
+		struct.add(DWORD, "unknown1", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSP.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSP.java
new file mode 100644
index 0000000000..19a2d7de78
--- /dev/null
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/fbpk/v2/UFSP.java
@@ -0,0 +1,58 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.file.formats.android.fbpk.v2;
+
+import java.io.IOException;
+
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.StructConverter;
+import ghidra.file.formats.android.fbpk.FBPK_Constants;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.util.exception.DuplicateNameException;
+
+public class UFSP implements StructConverter {
+	private String magic;
+	private int unknown1;
+	private String string1;
+
+	public UFSP(BinaryReader reader) throws IOException {
+		magic = reader.readNextAsciiString(FBPK_Constants.UFSP.length());
+		unknown1 = reader.readNextInt();
+	}
+
+	public String getMagic() {
+		return magic;
+	}
+
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	public String getString1() {
+		return string1;
+	}
+
+	@Override
+	public DataType toDataType() throws DuplicateNameException, IOException {
+		Structure struct = new StructureDataType(UFSP.class.getSimpleName(), 0);
+		struct.add(STRING, magic.length(), "magic", null);
+		struct.add(DWORD, "unknown1", null);
+		return struct;
+	}
+
+}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatConstants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatConstants.java
index f0b93b61ff..b3b38a6058 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatConstants.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatConstants.java
@@ -16,12 +16,6 @@
 package ghidra.file.formats.android.oat;
 
 import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;
-import ghidra.app.util.opinion.ElfLoader;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.listing.Program;
-import ghidra.program.model.mem.MemoryBlock;
-import ghidra.program.model.symbol.Symbol;
-import ghidra.program.model.symbol.SymbolTable;
 
 /**
  * https://android.googlesource.com/platform/art/+/marshmallow-mr3-release/runtime/oat.h
@@ -47,6 +41,28 @@ public final class OatConstants {
 
 	public final static String DOT_OAT_PATCHES_SECTION_NAME = ".oat_patches";
 
+	/* Keys from the OAT header "key/value" store. */
+	public final static String kApexVersionsKey           = "apex-versions";
+	public final static String kBootClassPathKey          = "bootclasspath";
+	public final static String kBootClassPathChecksumsKey = "bootclasspath-checksums";
+	public final static String kClassPathKey              = "classpath";
+	public final static String kCompilationReasonKey      = "compilation-reason";
+	public final static String kCompilerFilter            = "compiler-filter";
+	public final static String kConcurrentCopying         = "concurrent-copying";	
+	public final static String kDebuggableKey             = "debuggable";
+	public final static String kDex2OatCmdLineKey         = "dex2oat-cmdline";
+	public final static String kDex2OatHostKey            = "dex2oat-host";
+	public final static String kHasPatchInfoKey           = "has-patch-info";
+	public final static String kImageLocationKey          = "image-location";
+	public final static String kNativeDebuggableKey       = "native-debuggable";
+	public final static String kPicKey                    = "pic";
+	public final static String kRequiresImage             = "requires-image";
+
+	/** Boolean value used in the Key/Value store for TRUE. */
+	public final static String kTrueValue  = "true";
+	/** Boolean value used in the Key/Value store for FALSE. */
+	public final static String kFalseValue = "false";
+
 	// * * * * * * * * * * * * * * * * * * * * * * * *
 	// NOTE: we plan to only support RELEASE versions...
 	// Upper case indicates supported version.
@@ -78,17 +94,13 @@ public final class OatConstants {
 	public final static String VERSION_10_RELEASE                 = "170";
 	public final static String VERSION_11_RELEASE                 = "183";
 	public final static String VERSION_12_RELEASE                 = "195";
+	public final static String VERSION_S_V2_PREVIEW               = "199";
 
-	public final static int VERSION_LENGTH = 3;//3 bytes in length
-
-	// * * * * * * * * * * * * * * * * * * * * * * * *
-
-	
 	/**
-	 * This array contains version that have been actively tested and verified.
-	 * All other version will be considered unsupported until tested on exemplar firmware.
+	 * This array contains versions that have been actively tested and verified.
+	 * All other versions will be considered unsupported until tested on exemplar firmware.
 	 */
-	public final static String [ ] SUPPORTED_VERSIONS = new String [ ] {
+	public final static String [] SUPPORTED_VERSIONS = new String [] {
 		VERSION_KITKAT_RELEASE,
 		VERSION_LOLLIPOP_RELEASE,
 		VERSION_LOLLIPOP_MR1_FI_RELEASE,
@@ -103,28 +115,9 @@ public final class OatConstants {
 		VERSION_10_RELEASE,
 		VERSION_11_RELEASE,
 		VERSION_12_RELEASE,
+		VERSION_S_V2_PREVIEW,
 	};
 
-	/** Keys from the OAT header "key/value" store. */
-	public final static String kImageLocationKey          = "image-location";
-	public final static String kDex2OatCmdLineKey         = "dex2oat-cmdline";
-	public final static String kDex2OatHostKey            = "dex2oat-host";
-	public final static String kPicKey                    = "pic";
-	public final static String kHasPatchInfoKey           = "has-patch-info";
-	public final static String kDebuggableKey             = "debuggable";
-	public final static String kNativeDebuggableKey       = "native-debuggable";
-	public final static String kCompilerFilter            = "compiler-filter";
-	public final static String kClassPathKey              = "classpath";
-	public final static String kBootClassPathKey          = "bootclasspath";
-	public final static String kBootClassPathChecksumsKey = "bootclasspath-checksums";
-	public final static String kConcurrentCopying         = "concurrent-copying";
-	public final static String kCompilationReasonKey      = "compilation-reason";
-
-	/** Boolean value used in the Key/Value store for TRUE. */
-	public final static String kTrueValue  = "true";
-	/** Boolean value used in the Key/Value store for FALSE. */
-	public final static String kFalseValue = "false";
-
 	//@formatter:on
 
 	/**
@@ -141,46 +134,4 @@ public final class OatConstants {
 		return false;
 	}
 
-	/**
-	 * Returns true if the given program contain OAT information.
-	 * Checks for the program being an ELF, and containing the three magic OAT symbols.
-	 * @param program the program to inspect
-	 * @return true if the program is OAT
-	 */
-	public final static boolean isOAT(Program program) {
-		if (program != null) {
-			String executableFormat = program.getExecutableFormat();
-			if (ElfLoader.ELF_NAME.equals(executableFormat)) {
-				MemoryBlock roDataBlock =
-					program.getMemory().getBlock(ElfSectionHeaderConstants.dot_rodata);
-				if (roDataBlock != null) {
-					SymbolTable symbolTable = program.getSymbolTable();
-					Symbol oatDataSymbol = symbolTable.getPrimarySymbol(roDataBlock.getStart());
-					return oatDataSymbol != null && oatDataSymbol.getName().equals(SYMBOL_OAT_DATA);
-				}
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the version string from the OAT program, or "unknown" if not found/valid.
-	 * @param program the program to inspect
-	 * @return the OAT version
-	 */
-	final static String getOatVersion(Program program) {
-		if (OatConstants.isOAT(program)) {
-			Symbol symbol = OatUtilities.getOatDataSymbol(program);
-			Address address = symbol.getAddress().add(MAGIC.length());
-			byte[] versionBytes = new byte[VERSION_LENGTH];
-			try {
-				program.getMemory().getBytes(address, versionBytes);
-				return new String(versionBytes).trim();
-			}
-			catch (Exception e) {
-				//ignore
-			}
-		}
-		return "unknown";
-	}
 }
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderAnalyzer.java
index 64c8efec78..784e49e9f3 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderAnalyzer.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderAnalyzer.java
@@ -25,12 +25,24 @@ import ghidra.file.formats.android.dex.format.DexHeader;
 import ghidra.file.formats.android.oat.oatdexfile.OatDexFile;
 import ghidra.program.model.address.Address;
 import ghidra.program.model.address.AddressSetView;
-import ghidra.program.model.data.*;
-import ghidra.program.model.listing.*;
+import ghidra.program.model.data.Array;
+import ghidra.program.model.data.ArrayDataType;
+import ghidra.program.model.data.DWordDataType;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.Undefined1DataType;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
 import ghidra.program.model.mem.Memory;
 import ghidra.program.model.mem.MemoryBlock;
 import ghidra.program.model.scalar.Scalar;
-import ghidra.program.model.symbol.*;
+import ghidra.program.model.symbol.Equate;
+import ghidra.program.model.symbol.EquateTable;
+import ghidra.program.model.symbol.RefType;
+import ghidra.program.model.symbol.ReferenceManager;
+import ghidra.program.model.symbol.SourceType;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolTable;
 import ghidra.util.exception.CancelledException;
 import ghidra.util.task.TaskMonitor;
 
@@ -53,7 +65,7 @@ public class OatHeaderAnalyzer extends FileFormatAnalyzer {
 
 	@Override
 	public boolean canAnalyze(Program program) {
-		return OatConstants.isOAT(program);
+		return OatUtilities.isOAT(program);
 	}
 
 	@Override
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderFactory.java
index f3c960afe2..8b1cf6b0fd 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderFactory.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeaderFactory.java
@@ -63,6 +63,7 @@ public final class OatHeaderFactory {
 					case OatConstants.VERSION_11_RELEASE:
 						return new OatHeader_11(reader);
 					case OatConstants.VERSION_12_RELEASE:
+					case OatConstants.VERSION_S_V2_PREVIEW:
 						return new OatHeader_12(reader);
 				}
 			}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeader_12.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeader_12.java
index 1281e524d3..611e1f191e 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeader_12.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatHeader_12.java
@@ -27,6 +27,8 @@ import ghidra.util.exception.DuplicateNameException;
  * https://android.googlesource.com/platform/art/+/refs/heads/android-s-beta-5/runtime/oat.h#125
  * 
  * https://android.googlesource.com/platform/art/+/refs/heads/android12-release/runtime/oat.h#125
+ * 
+ * https://android.googlesource.com/platform/art/+/refs/heads/android-s-v2-preview-1/runtime/oat.h#125
  */
 public class OatHeader_12 extends OatHeader {
 	protected int oat_checksum_;
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatUtilities.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatUtilities.java
index 72af489afa..73b5efa1de 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatUtilities.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/OatUtilities.java
@@ -19,7 +19,9 @@ import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.List;
 
-import ghidra.app.util.bin.*;
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.MemoryByteProvider;
 import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants;
 import ghidra.app.util.importer.MessageLog;
 import ghidra.app.util.opinion.ElfLoader;
@@ -42,7 +44,7 @@ public final class OatUtilities {
 	 * Returns null when the "oatdata" symbol does not exist.
 	 */
 	public static BinaryReader getBinaryReader(Program program) {
-		if (OatConstants.isOAT(program)) {
+		if (isOAT(program)) {
 			Symbol symbol = getOatDataSymbol(program);
 			if (symbol != null && symbol.getName().equals(OatConstants.SYMBOL_OAT_DATA)) {
 				ByteProvider provider =
@@ -53,6 +55,28 @@ public final class OatUtilities {
 		return null;
 	}
 
+	/**
+	 * Returns true if the given program contain OAT information.
+	 * Checks for the program being an ELF, and containing the three magic OAT symbols.
+	 * @param program the program to inspect
+	 * @return true if the program is OAT
+	 */
+	public static boolean isOAT(Program program) {
+		if (program != null) {
+			String executableFormat = program.getExecutableFormat();
+			if (ElfLoader.ELF_NAME.equals(executableFormat)) {
+				MemoryBlock roDataBlock =
+					program.getMemory().getBlock(ElfSectionHeaderConstants.dot_rodata);
+				if (roDataBlock != null) {
+					SymbolTable symbolTable = program.getSymbolTable();
+					Symbol oatDataSymbol = symbolTable.getPrimarySymbol(roDataBlock.getStart());
+					return oatDataSymbol != null && oatDataSymbol.getName().equals(OatConstants.SYMBOL_OAT_DATA);
+				}
+			}
+		}
+		return false;
+	}
+
 	public static boolean isELF(Program program) {
 		return ElfLoader.ELF_NAME.equals(program.getExecutableFormat());
 	}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatclass/OatClassFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatclass/OatClassFactory.java
index c230285ab0..8f2dd9a216 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatclass/OatClassFactory.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatclass/OatClassFactory.java
@@ -50,6 +50,7 @@ public class OatClassFactory {
 			case OatConstants.VERSION_11_RELEASE:
 				return new OatClass_Android11(reader, classDataItem, oatVersion);
 			case OatConstants.VERSION_12_RELEASE:
+			case OatConstants.VERSION_S_V2_PREVIEW:
 				return new OatClass_Android12(reader, classDataItem, oatVersion);
 			default:
 				throw new UnsupportedOatVersionException(
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatdexfile/OatDexFileFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatdexfile/OatDexFileFactory.java
index 5c6e7ea1eb..aeadcaf2fc 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatdexfile/OatDexFileFactory.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/oatdexfile/OatDexFileFactory.java
@@ -50,6 +50,7 @@ public final class OatDexFileFactory {
 			case OatConstants.VERSION_11_RELEASE:
 				return new OatDexFile_Android11(reader, bundle);
 			case OatConstants.VERSION_12_RELEASE:
+			case OatConstants.VERSION_S_V2_PREVIEW:
 				return new OatDexFile_Android12(reader, bundle);
 		}
 
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/quickmethod/OatQuickMethodHeaderFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/quickmethod/OatQuickMethodHeaderFactory.java
index 2c109bbdd0..901c949ba6 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/quickmethod/OatQuickMethodHeaderFactory.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/quickmethod/OatQuickMethodHeaderFactory.java
@@ -42,6 +42,7 @@ public final class OatQuickMethodHeaderFactory {
 			case OatConstants.VERSION_11_RELEASE:
 				return 8;
 			case OatConstants.VERSION_12_RELEASE:
+			case OatConstants.VERSION_S_V2_PREVIEW:
 				return 4;
 		}
 		throw new IOException("OatQuickMethodHeader unsupported OAT version: " + oatVersion);
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/tlt/TypeLookupTableFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/tlt/TypeLookupTableFactory.java
index 86cddf0ad6..a02fb29e16 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/tlt/TypeLookupTableFactory.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/oat/tlt/TypeLookupTableFactory.java
@@ -41,6 +41,7 @@ public final class TypeLookupTableFactory {
 			case OatConstants.VERSION_11_RELEASE:
 				return new TypeLookupTable_Android11(reader);
 			case OatConstants.VERSION_12_RELEASE:
+			case OatConstants.VERSION_S_V2_PREVIEW:
 				return new TypeLookupTable_Android12(reader);
 			default:
 				throw new IOException(new UnsupportedOatVersionException(
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/vdex/VdexHeaderAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/vdex/VdexHeaderAnalyzer.java
index de9a3cc0d2..2f70e0d3a3 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/vdex/VdexHeaderAnalyzer.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/vdex/VdexHeaderAnalyzer.java
@@ -15,14 +15,19 @@
  */
 package ghidra.file.formats.android.vdex;
 
-import ghidra.app.util.bin.*;
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.MemoryByteProvider;
 import ghidra.app.util.importer.MessageLog;
 import ghidra.file.analyzers.FileFormatAnalyzer;
 import ghidra.file.formats.android.dex.format.DexHeader;
-import ghidra.file.formats.android.oat.OatConstants;
+import ghidra.file.formats.android.oat.OatUtilities;
 import ghidra.program.model.address.Address;
 import ghidra.program.model.address.AddressSetView;
-import ghidra.program.model.data.*;
+import ghidra.program.model.data.ArrayDataType;
+import ghidra.program.model.data.ByteDataType;
+import ghidra.program.model.data.DWordDataType;
+import ghidra.program.model.data.DataType;
 import ghidra.program.model.listing.CodeUnit;
 import ghidra.program.model.listing.Program;
 import ghidra.util.task.TaskMonitor;
@@ -48,11 +53,11 @@ public class VdexHeaderAnalyzer extends FileFormatAnalyzer {
 
 	@Override
 	public boolean canAnalyze(Program program) {
-		return VdexConstants.isVDEX(program)
-		//HACK:
-		//Make analyzer appear after VDEX is merged with OAT program
-		//Currently, analyzers will not recognize the new VDEX block being added
-			|| OatConstants.isOAT(program);
+		// Return true if this program is just a VDEX, but also return
+		// true if the program is an OAT.
+		// On Android at runtime, VDEX is merged with OAT in memory.
+		// Allow this analyzer to also run on OAT files to look for existence of VDEX.
+		return VdexConstants.isVDEX(program) || OatUtilities.isOAT(program);
 	}
 
 	@Override
@@ -65,7 +70,8 @@ public class VdexHeaderAnalyzer extends FileFormatAnalyzer {
 			throws Exception {
 		Address address = VdexConstants.findVDEX(program);
 		if (address == null) {
-			log.appendMsg(getClass().getSimpleName() + " - no vdex header found in memory, skipping");
+			log.appendMsg(
+				getClass().getSimpleName() + " - no vdex header found in memory, skipping");
 			return true;
 		}
 		ByteProvider provider = new MemoryByteProvider(program.getMemory(), address);
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java
index ef8658b84a..f8920df620 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ext4/NewExt4Analyzer.java
@@ -21,14 +21,19 @@ import java.util.List;
 import ghidra.app.cmd.comments.SetCommentCmd;
 import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
 import ghidra.app.services.ProgramManager;
-import ghidra.app.util.bin.*;
+import ghidra.app.util.bin.BinaryReader;
+import ghidra.app.util.bin.ByteProvider;
+import ghidra.app.util.bin.MemoryByteProvider;
 import ghidra.app.util.importer.MessageLog;
 import ghidra.file.analyzers.FileFormatAnalyzer;
 import ghidra.program.model.address.Address;
 import ghidra.program.model.address.AddressSetView;
 import ghidra.program.model.data.DataType;
-import ghidra.program.model.listing.*;
+import ghidra.program.model.listing.CodeUnit;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.Program;
 import ghidra.program.model.symbol.SourceType;
+import ghidra.program.model.util.CodeUnitInsertionException;
 import ghidra.util.exception.DuplicateNameException;
 import ghidra.util.task.TaskMonitor;
 
@@ -188,14 +193,14 @@ public class NewExt4Analyzer extends FileFormatAnalyzer {
 	}
 
 	@Override
-	protected Data createData( Program program, Address address, DataType datatype ) throws Exception {
+	protected Data createData( Program program, Address address, DataType datatype ) throws CodeUnitInsertionException {
 		if ( program.getMemory( ).contains( address ) ) {
 			return super.createData( program, address, datatype );
 		}
 		if ( program2 != null && program2.getMemory( ).contains( address ) ) {
 			return super.createData( program2, address, datatype );
 		}
-		throw new RuntimeException( "Cannot create data, neither program contains that address." );
+		throw new CodeUnitInsertionException( "Cannot create data, neither program contains that address." );
 	}
 
 	@Override