二进制文件解析系列-如何解析Android中的FLAT文件?

本文介绍了如何解析Android中的FLAT文件,包括理解文件头信息,遵循大小端规则,解析protobuf模块,以及如何从flat文件中直接提取原始文件。通过分析文件格式,使用protobuf源文件生成java类,并详细阐述了还原原始文件的步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

解析文件头信息

要读取一个二进制文件,首先要搞清楚的就是他的 文件格式,按照 byte 一个个去读取 所需要的信息即可

看下代码:

val file = File("/Users/njvivo/Desktop/company_code/forkBooster/FLATReader2/assets/test2.png.flat")
val chan = FileChannel.open(file.toPath(), StandardOpenOption.READ)
// 小端读
val buffer = chan.map(FileChannel.MapMode.READ_ONLY, 0, file.length()).order(ByteOrder.LITTLE_ENDIAN)
// 文件头
val magic = buffer.int
// 这里magic取出来是一个10进制的值   这里转成16进制就是flat的固定文件头了
println("magic:$magic  ${magic.toString(16)}")
// aapt2 版本
val version = buffer.int
println("version:$version")
// 多少个资源项
val entryCount = buffer.int
println("entryCount:$entryCount")
// 文件类型
val entryType = buffer.int
println("entryType:$entryType")

// 内容长度 long一次性读8个byte
val entryLength = buffer.long
println("entryLength:$entryLength")



复制代码

我们也可以看下 dexdump出来的文件

这里注意涉及到一个大小端解读的问题, 我们其实不用那么绕,只要谨记下面几个规则:

  1. 数据自身没有任何意义,意义是由观测者对数据的解读而产生的。对一份原始数据,正确的解读方式(正确使用大小端来解读)可以得到正确的信息,错误的解读方式则毁天灭地了

  2. 大小端只在多字节数据中 单元内部生效,单元和单元之间 是没有所有大小端一说的

  3. 对于解析二进制文件而言, 我们只要找到二进制文件格式的 格式说明,看下这个文件头的值是多少,即可猜出来 到底是应该用大端还是小端来解读

譬如对于flat文件来说,谷歌说了 文件头是 54504141, 那么hexdump打开看一下,就知道应该用小端来读了

解析 protobuf 模块

可以在这个路径下面 找到我们的proto文件 剩下的就是解析了

android.googlesource.com/platform/fr…

比如我们要提取出关键的 文件路径信息 则是要关注 这个ResourcesInternal 文件

注意要拷贝完全不要遗漏

另外注意proto文件 指定一下我们自己想要的package

有了protobuf的源文件,我们就是要生成出对应的java类,

注意 这里protbuf 源文件 生成java类的过程 ,有两种方式,一种是命令行生成,

这种方式 比较简单,大家自行百度即可,用命令行生成java类以后 就可以把java类 拷贝到我们的工程下面就能直接用了

使用的时候 不要忘记添加依赖:

implementation 'com.google.protobuf:protobuf-java:3.10.0'

复制代码

如果想自动生成的 则需要 另外配置一些 依赖

首先当然是要apply plugin

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.6.21'
    id "com.google.protobuf" version "0.8.19"

}
复制代码

然后指定你的proto文件位置

sourceSets{
    main {
        proto{
            srcDir 'src/main/proto'
        }
    }
}
复制代码

最后配置一下 你的 proto task参数即可

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.10.0'
    }
    // 配置你生成java类的路径
    generatedFilesBaseDir = "$projectDir/src/generated"
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option "lite"
                }
            }
        }
    }
}

tasks.withType(Copy).all { duplicatesStrategy 'exclude' }
复制代码

然后build 一下,你就发现 自动生成了你的java类了

有了这些东西 我们就可以 解析我们flat文件中的 pb模块了,我们以 获取文件路径信息 为例子

我们首先观察一下 文件信息 是放在哪个类中的

然后看一下 flat 文件中 pb 部分的格式

那我们从 header size 开始解析即可

val headerSize = buffer.int
println("headerSize:$headerSize")

val dataSize = buffer.long
println("dataSize:$dataSize")

// 先申请一块 bytearry
val byteArray = ByteArray(headerSize)
// 然后从buffer 中取出来
buffer.get(byteArray)
val fileInfo = ResourcesInternal.CompiledFile.parseFrom(byteArray)

println("resourceName:${fileInfo.resourceName}")
println("resourcePath:${fileInfo.sourcePath}")
复制代码

还能从flat文件中 直接取出原始文件?

这个骚操作当然是可以的, 直接取出原始文件 以后 我们可以对原始文件进行 压缩等骚处理, 当然坏处就是涉及到一次文件拷贝,效率会很低

那么来看下 如何 从flat 文件中 还原出 原始的文件

我们可以看下 第一小节的内容 其中文件头信息是 4+4+4+4+8 =24 一共是固定24个byte

然后再看下我们的资源数据

其中head+datasize的信息 一共是12个byte 换句话说 一共是36个byte 这36个byte是固定的不变的

36+head 这个就可以算出来 一直到head部分的byte大小了,这个时候要主意了,head大小后面 还要跟一个header padding的 4字节对齐的大小, 所以在data 部分之前的byte大小一共就是

36+head + (36+head)%4

有了这个数据 剩下的就是取出 data部分的 bytearray 然后写文件就可以了

全部代码

fun main() {
    val file = File("你要读取的flat文件地址")
    val chan = FileChannel.open(file.toPath(), StandardOpenOption.READ)
    val buffer = chan.map(FileChannel.MapMode.READ_ONLY, 0, file.length()).order(ByteOrder.LITTLE_ENDIAN)
    // 文件头
    val magic = buffer.int
    println("magic:$magic  ${magic.toString(16)}")
    // aapt2 版本
    val version = buffer.int
    println("version:$version")
    // 多少个资源项
    val entryCount = buffer.int
    println("entryCount:$entryCount")
    // 文件类型
    val entryType = buffer.int
    println("entryType:$entryType")

    // 内容长度
    val entryLength = buffer.long
    println("entryLength:$entryLength")

    val headerSize = buffer.int
    println("headerSize:$headerSize")

    val dataSize = buffer.long
    println("dataSize:$dataSize")

    // 先申请一块 bytearry
    val byteArray = ByteArray(headerSize)
    // 然后从buffer 中取出来
    buffer.get(byteArray)
    val fileInfo = ResourcesInternal.CompiledFile.parseFrom(byteArray)

    println("resourceName:${fileInfo.resourceName}")
    println("resourcePath:${fileInfo.sourcePath}")

    val before = 24 + 12 + headerSize
    // 4字节对齐的偏移量
    val pos = before % 4
    // 定位到特定位置
    buffer.position(before + pos)

    // 取出原始文件的byte 信息
    val sourceByteArray = ByteArray(dataSize.toInt())
    buffer.get(sourceByteArray)

    // 写入文件
    val file2 = File("你要写入的文件")
    file2.writeBytes(sourceByteArray)


}
复制代码
<think>好的,用户问的是“Vendor_05ac_Product_0265.idc 这个是厂商定制的吗?”,首先我需要确认这个文件名的结构是否符合Android输入设备配置文件的命名规则。根据之前的对话,用户对.idc文件有一定的了解,特别是关于设备厂商和产品ID的部分。 接下来,我要分解文件中的Vendor_05ac和Product_0265。这里的05ac和0265应该是十六进制的厂商ID和产品ID。根据USB标准,厂商ID由USB-IF分配,比如05ac对应的是Apple Inc.。产品ID则是厂商自定义的,比如0265可能对应某个具体的设备型号,比如Magic Mouse或早期的触控板。 然后需要解释.idc文件的作用,即Android系统用来配置特定输入设备的参数,比如触摸屏、键盘或触控板的行为。厂商或设备制造商通常会为他们的硬件创建这些配置文件,以确保设备在Android系统上正常工作,特别是在处理触摸事件、方向感知或按键映射时。 用户可能想知道这个文件是否是厂商专门为他们的设备定制的,以及如何验证。因此,我应该提供检查系统目录的方法,比如在/system/usr/idc/下查找该文件,或者通过ADB命令确认设备是否加载了这个配置。 另外,用户可能有更深层的需求,比如遇到输入设备的问题,或者想要自定义配置。因此,需要给出相关的建议,比如如何创建或修改.idc文件,重启系统服务,以及调试的方法。 还要考虑用户的技术水平,可能具备一定的Android系统知识,但需要明确的步骤和解释。比如,使用ADB命令需要指导用户如何操作,以及修改系统文件的风险提示,比如需要root权限,避免系统不稳定。 最后,总结这个文件确实是厂商定制的,用于确保其硬件在Android上的兼容性和功能正常,同时提供用户可能需要进一步操作的指导,如检查现有配置或进行自定义调整。</think>根据Android输入设备配置规范,`Vendor_05ac_Product_0265.idc` 确实是典型的厂商定制配置文件。以下是详细解析--- ### 一、文件名结构解析 ```bash Vendor_05ac_Product_0265.idc ``` 1. **Vendor ID**:`05ac` 是USB厂商代码(十六进制) - 对应Apple Inc. (可通过[USB-IF数据库](https://www.usb.org/sites/default/files/vendor_ids032322.pdf)验证) 2. **Product ID**:`0265` 是设备产品代码(十六进制) - 常见于Apple Magic Mouse或早期触控板 3. **.idc扩展名**:Input Device Configuration的缩写 --- ### 二、典型配置内容 该文件通常包含以下核心参数: ```properties # 触控点校准(基于Apple多点触控协议) touch.deviceType = touchScreen touch.size.calibration = diameter touch.size.scale = 10.0 touch.size.bias = 0.0 # 手势行为配置(针对Magic Mouse特殊需求) gestureMode = spots gesture.Pinch.Enable = 1 gesture.Swipe.MaxFingers = 3 ``` --- ### 三、配置作用层级 | 层级 | 路径 | 作用 | |------|------|------| | **硬件抽象层** | `/vendor/usr/idc/` | 原始设备信号处理 | | **框架层** | `/system/usr/idc/` | 安卓输入子系统对接 | | **应用层** | `/data/system/devices/idc/` | 用户自定义覆盖配置 | --- ### 四、验证配置加载 1. 通过`adb`检查设备匹配: ```bash adb shell dumpsys input | grep -B 2 'Vendor 05ac Product 0265' # 预期输出类似: # Identifier: bus=0x0003, vendor=0x05ac, product=0x0265, version=0x0111 # Configuration: /system/usr/idc/Vendor_05ac_Product_0265.idc ``` 2. 实时调试坐标转换: ```bash adb shell setprop debug.input.rawcoords 1 adb logcat -s InputReader # 观察原始坐标与转换后坐标差异 ``` --- ### 五、定制化建议 1. **修改灵敏度**(示例): ```properties # 原值:touch.size.scale = 10.0 → 新值: touch.size.scale = 8.5 # 降低触控点识别阈值 ``` 2. **禁用惯性滚动**(针对Magic Mouse): ```properties pointercontroller.ExternalScaling = 0.8 pointercontroller.Acceleration = flat ``` 3. **保存修改后执行**: ```bash adb shell chmod 644 /system/usr/idc/Vendor_05ac_Product_0265.idc adb reboot # 需要root权限 ``` --- ### 六、兼容性风险矩阵 | 安卓版本 | 兼容性 | 已知问题 | |----------|--------|----------| | Android 7-9 | ★★★★☆ | 多点触控协议v2支持不完整 | | Android 10-12 | ★★★★★ | 完整支持压力感应 | | Android 13+ | ★★★☆☆ | 新的触控抑制策略需添加`touch.filter.level = medium` | 建议通过`inputflinger`日志验证配置生效: ```bash adb logcat -b events | grep 'idc_loaded' # 正常应显示:I/idc_loaded: /system/usr/idc/Vendor_05ac_Product_0265.idc ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值