duangsuse::Echo
717 subscribers
4.26K photos
130 videos
583 files
6.48K links
import this:
美而不丑、明而不暗、短而不凡、长而不乱,扁平不宽,读而后码,行之天下,勿托地上天国。
异常勿吞,难过勿过,叹一真理。效率是很重要,盲目最是低效。
简明是可靠的先验,不是可靠的祭品。
知其变,守其恒,为天下式;穷其变,知不穷,得地上势。知变守恒却穷变知新,我认真理,我不认真。

技术相干订阅~
另外有 throws 闲杂频道 @dsuset
转载频道 @dsusep
极小可能会有批评zf的消息 如有不适可退出
suse小站(面向运气编程): https://WOJS.org/#/
Download Telegram
duangsuse::Echo
顺手发点截图 #elec #PL #blog #cs
体验了 EPL(懒得检查表达式了)、Fritzing(这个玩意是用来进行嵌入式设计的,当然你也可以理解为开发板编程什么的,可以画原理图、建面包版和写串口通讯代码)
因为打印了很多博文的缘故,已经开始手动处理网页来尽可能压榨一面 A4 纸的信息量
htmlPrint.zip
96.5 KB
顺手分享 #doc #share #cs 包含很多 PLT(依赖类型 Agda) 的内容和一个 PL 和编译原理的内容(R大 09 年的一个博客)
duangsuse::Echo
htmlPrint.zip
This media is not supported in your browser
VIEW IN TELEGRAM
R 大 09 年就那么大佬了,难怪现在我都看不见他一样了... 这世界有代沟啊
duangsuse::Echo
htmlPrint.zip
Agda 的 coinductive record 么... 我还没学会,emmm。
Agda 环境我还没建立,到时候建立了会发一些简单的证明帮助大家入门。
立即 公开维护(
duangsuse::Echo
立即 公开维护(
AXMLParser parser = new AXMLParser(apkFileInputStream);
int eventType = parser.getType();
while (eventType != AXMLParser.END_DOCUMENT) {
String parserName = parser.getName();
boolean isManifest = "manifest".equals(parserName);
[...]
eventType = parser.next();
}

这个原来的玩意是用流模式的...
我也打算这么做,当然也会提供扫描整个文档的辅助方法

这种方式也被 LLVM Cookbook 里的 TOY 语言 Lexer 采用(get_token() 函数从输入流扫描,然后返回词条类型,业务代码判断词条类型访问相应静态变量拿信息)

就作为 Iterable 吧,因为我觉得这种方式(eventType + static field 存储 AXML 结构信息)不够面向对象,反而能嗅出点过程式的端倪。

换句话说,我觉得我应该这样封装:

val parser = AxmlSerializer.Reader(axmlFileInputStream)

for (node in parser.treeIterator) {
when (node is AxmlTag && node.tag == "manifest") {
// [...]
}
}

虽然这样会导致它不够『底层』以至于不是所有 xmlparser 可以处理的文档它都可以处理,但我还是觉得... 不错
不过... 其实鱼和熊掌可以兼得,先做一个流处理最底层的 AxmlSequencer,再在上面封装 AxmlSerializer.Reader 不就好了吗?
流处理,最下面是 Binary 数据的 Reader (Extension),提供最底层的二进制格式 DataView
中间一层是文件大格式的 Scanner (Reader),扫描 AXML 『大体』的文件格式(Chunk)
最顶端一层提供 AxmlTree 流接口和帮助函数,每次需要新 node 时就看看自己的缓冲区里有没有剩下的对象可供返回(数据指针移动)
如果没有了,向底层 ask 新的 chunck,拆分,入队,否则返回已经扫描出来的对象

val treeIterator: Iterator<AxmlNode>
get() = asIterator()

fun asIterator(): AxmlNodeIterator<AxmlNode> {
return /* 实现 next 和 hasNext,如果队列为空则从 Sequencer 里读取下一块,拆分,存到 NodeIterator 的队列里,否则返回出队元素 */
}
duangsuse::Echo
AXMLParser parser = new AXMLParser(apkFileInputStream); int eventType = parser.getType(); while (eventType != AXMLParser.END_DOCUMENT) { String parserName = parser.getName(); boolean isManifest = "manifest".equals(parserName); [...] eventType…
https://github.com/duangsuse/AxmlSerializer/wiki/Binary-Serialization-%E7%B1%BB%E8%A6%81%E6%8F%90%E4%BE%9B%E7%9A%84%E6%88%90%E5%91%98%E5%92%8C%E6%96%B9%E6%B3%95%E6%93%8D%E4%BD%9C#binary-serializatoin

我正在准备分析手头上的资料总结出『后面』的文件格式,等我验证规范有效之后,就会开始写一个二进制序列化类库,使用这个线性字节流结构提取式类库解决 AxmlSerializer 的问题

新的库有糖能够使得文件格式的表达更具定义式风格,避免了使用旧式的 byte array 一大堆算偏移量、提取字节数组组装某种数值、裁切子序列的操作(一些不嫌麻烦的库现在依然选择这种方式)

@ByteStruct
class ResChunkHeader {
@Type(Unsigned16) ChunkType type;
@Type(Unsigned32) long size;
}

这样对于简单的结构体,在 parser 里面调用 reader.readStruct(ResChunkHeader.class) 就可以了
🤔

AAPT2 AXML 文件格式的总结,将在这里讨论

为了方便快速了解格式详情,会使用弱类型的『脚本语言』Ruby 进行 STDIN IO 解析 AXML 文件基础结构。
axml.rb
1.8 KB
AndroidManifest.xml
48.6 KB
简单的思路,虽然写了我很久 🤔...
duangsuse::Echo
简单的思路,虽然写了我很久 🤔...
其实有个大小端的问题... 虽然 AXML 作为一种二进制文件格式并不复杂(没有添加 alignment、不需要搞基流定位)
但是还是要求以小端格式存取,至少好像是 String Pool 必须这样 🤔

emmm... 唉
乘着 Telegram 还能够连上赶快

== BinaryDataReader 的必须基础操作,我所希望的

+ skipNBytes(n)
+ readNBytes(n)
+ readU8 / readI8
+ readU16 / readI16
+ readU32 / readI32
+ readU64 / readI64
+ readCStr / readXPrefixStr

+ readNX
+ skipX
+ checkX

和 java 的 DataInputStream 相比,它缺少:

+ readUTF
+ readLine
+ readFully <1 overloads>
+ read Float/Double/Char/Boolean

它实现了 ExtDataInput 扩展类的

+ skipInt(skipX)
+ readIntArray(readNX)
+ skipCheckX(checkX)
+ skipBytes(skipNBytes)(更努力地跳过这些字节,或者说坚持一定要跳过而不因为被打断而终止,定义如下)

proc int skipNBytes(int n) {
int skipped = 0, now;
while skipped < n AND now = super.skipBytes(n - skipped) >0 { total += now; }
return skipped;
}

+ readNullEndedString (readCStr)

因为没有必要(只是处理二进制数据)。
duangsuse::Echo
以下是基于这个文件和 https://gist.github.com/duangsuse/3ae94e339eb188fa4ec8a87b6e105331 分析出来确认有效的文件结构: 当然还有 https://github.com/aosp-mirror/platform_frameworks_base/tree/pie-release/tools/aapt2 AAPT2 https://github.com/aosp-mirror/platform_frameworks_base/blob/pie…
首先:AXMLParser 它怎么看 AXML 文件格式:

Axml -> little-endian
int 0x00080003 (其实这种定义是不足够健壮的,它利用了 Top Chunk 的性质,硬编码了 Chunk Type 和 Chunk Header Length)
int chunkSize

StringBlock strb

StringBlock -> little-endian
skipCheckChunkTypeInt(CHUNK_STRINGPOOL_TYPE, CHUNK_NULL_TYPE)
int chunkSize

int stringCount
int styleCount
int flags
int stringsOffset
int stylesOffset
prop isUTF8 = flags & 0x00000100

int[stringCount] stringOffsets
prop stringOwns = [stringCount; -1]

if styleCount != 0 then int[styleCount] styleOffsets

prop size = if styleCount != 0 then stylesOffset else chunkSize end - stringsOffset
byte[size] strings
strings = readFully

if stylesOffset != 0 then
prop size = (chunkSize - stylesOffset)
styles = int[size / 4]
prop remaining = size % 4
if remaining >= 1 then while remaining-- >0 do skipByte
啊这种烂代码我真的不想再看下去了,写时一时爽,重构火葬场,需要那么复杂么?
This media is not supported in your browser
VIEW IN TELEGRAM
我真的搞不懂为啥要算那么多... 而且为啥这些结构不在开始创建数据对象的时候就计算好,非得存下二进制每次使用的时候再去算... 连这点开销都受不起还是说从 C++ 翻译过来的...

好吧... 其实算和控制结构是必要的,所以我要吐槽的其实是为什么不『完整反序列化』、为什么要将他们单独字节数组存下来重新解析而不是用 stream reader 处理好了...
要知道,一个问题,两种解决方案风格可是很难看的...
写着觉得自己爽了,你看还用了个 ++i 哦、还用到了 block scoping 哦

过几天再读一下就爽了,还不如写简单点... 多用命名和列表处理方法... 🤔
这还没有解析完... 还是子字节数组... emmmm...
可见是这种代码写开心了... 23333333 难怪最后没机会写出整个完整的解析器了,二进制操作和控制流写的自我感觉超级棒,但是人脑也是有极限的...
🤔 AAPT 和 AAPT2,我觉得 AAPT 就已经足够,因为它已经能够 handle 很多软件包的 Manifest 和 AXML 了,ARSC 文件我下次再说