前言
由于个人爱好,本教程使用 ClojureScript 编写。但请放心——文中所有二进制操作调用的都是标准 JavaScript API:ArrayBuffer、DataView、Uint8Array、TextEncoder/TextDecoder等。ClojureScript 的语法差异很小,看到 js/DataView. 就是 new DataView(),(.getInt32 dv 0 true) 就是 dv.getInt32(0, true),点号在前只是调用方式的写法不同。如果你熟悉 JS 的二进制操作,阅读本文自然不会有障碍。
首先先在页面里创建一个文件选择框
[:input {:type "file" :id "level"}]
然后把文件存到变量level里面去
(def level (js/document.getElementById "level"));;def是绑定变量
1,操作文件初步和读取头部
在js中操作二进制,只有三个对象
ArrayBuffer:一块原始内存,你不能直接读里面的数
DataView:用来从 ArrayBuffer 里按类型读数(Int8/Int16/Int32/Float)
Uint8Array:把 ArrayBuffer 当成字节数组来用,可以像数组一样取下标。
选了文件后,浏览器给的是 File 对象。用 FileReader 转成 ArrayBuffer:
(def reader (js/FileReader.))
(js是const reader = new FileReader();)
读取时不会保存到变量,所以我们需要设置:
(set! (.-onload reader)
(fn [event]
;; event.target.result 就是 ArrayBuffer
(def file-array (.. event -target -result))
(println "读取完成,大小:" (.-byteLength file-array)))
js里面是
reader.onload = (evt) => {
//赋值
};
然后用readAsArrayBuffer方法读取:(.readAsArrayBuffer reader (-> level .-files (aget 0)))
我们可以包成 Uint8Array来查看这个二进制文件
(def file-bytes (js/Uint8Array. file-array))
(println (vec file-bytes))
Little-Endian 读取规则:低位在前。
[85 10 0 0] = 85 + 10×256 + 0×65536 + 0×16777216 = 2645。
在处理时用Uint8Array需要手动写代码计算不怎么方便。
所以我们用dataview来读取头部。
(defn parse-header [array-buffer]
(let [dv (js/DataView. array-buffer 0 8)]
{:version (.getInt32 dv 0 true);;等价于js里的dv.getInt32(…)
:length (.getInt32 dv 4 true)}))
我们用getInt32读取32位整数,也就是[x x x x],在js里的用法是dataview.getInt32(byteOffset [, littleEndian])。基岩版使用了littleEndian,故使用时要将littleEndian设为true。
等等,上面那是什么意思?接下来我们来剖析level.dat文件本身。
基岩版 level.dat 只有两层:
整个文件 = 头部(8字节) + 身体(N字节)
[10 0 0 0 ;; offset 0: 版本号 (Little-Endian Int32) → 10
85 10 0 0 ;; offset 4: 身体长度 (Little-Endian Int32) → 2645
10 0 0 ...] ;; offset 8: 其他都是NBT 数据
NBT 标签的通用结构
每个 NBT 标签都是三段式:
[Type(1字节) NameLength(2字节LE) Name(UTF-8) Payload(变长)]
示例:一个 Byte 标签 "Difficulty" = 1
[1 ;; Type = 1 (Byte)
10 0 ;; NameLength = 10 (Little-Endian Int16)
68 105 102 102 105 ;; "Diffi"
99 117 108 116 121 ;; "culty"
1] ;; Payload = 1
注意⚠️level.dat未被zlib压缩。
下一篇教程我们来读取Body部分的nbt。