forked from pnoom/cl-nbt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtag-io.lisp
265 lines (218 loc) · 7.68 KB
/
tag-io.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
;;;; tag-io.lisp - Readers and writers for nbt-tags of all types
(in-package #:cl-nbt)
;;; Readers
(defun read-header (stream)
"Read an id and name from STREAM, and return a tag initialized with those
values."
(let ((tag (instantiate-tag (read-si1 stream))))
(with-slots (name) tag
(when (not (typep tag 'tag-end))
(let ((x (read-utf-8-string stream (read-si2 stream))))
(setf name (if (string= x "") nil x)))))
tag))
(defgeneric read-payload (stream tag)
(:documentation
"Read payload from STREAM and bind its value to TAG's payload slot."))
(defun read-tag (stream)
"Read a tag from STREAM and return it."
(read-payload stream (read-header stream)))
(defmethod read-payload (stream (tag tag-end))
(declare (ignore stream)) ;tag-end has null payload, so do nothing.
tag)
(defmethod read-payload (stream (tag tag-byte))
(setf (payload tag) (read-si1 stream))
tag)
(defmethod read-payload (stream (tag tag-short))
(setf (payload tag) (read-si2 stream))
tag)
(defmethod read-payload (stream (tag tag-int))
(setf (payload tag) (read-si4 stream))
tag)
(defmethod read-payload (stream (tag tag-long))
(setf (payload tag) (read-si8 stream))
tag)
(defmethod read-payload (stream (tag tag-float))
(setf (payload tag) (read-f4 stream))
tag)
(defmethod read-payload (stream (tag tag-double))
(setf (payload tag) (read-f8 stream))
tag)
(defmethod read-payload (stream (tag tag-byte-array))
(let* ((len (read-si4 stream))
(arr (make-array len :element-type '(unsigned-byte 8))))
;specify element type?
(dotimes (i len)
(setf (aref arr i) (read-unsigned-int stream 1)))
(setf (payload tag) arr))
tag)
(defmethod read-payload (stream (tag tag-string))
(setf (payload tag)
(read-utf-8-string stream (read-si2 stream)))
tag)
;; If a tag-list is found to be empty, store in its payload slot the
;; de-facto element id of its non-existent entries (either 0 or 1).
(defmethod read-payload (stream (tag tag-list))
(let* ((element-id (read-si1 stream))
(num-elements (read-si4 stream)))
(with-slots (payload) tag
(cond ((zerop num-elements)
(setf payload element-id))
(t
(setf payload
(loop
for i below num-elements
collect (read-payload
stream
(instantiate-tag element-id)))))))
tag))
(defmethod read-payload (stream (tag tag-compound))
(setf (payload tag)
(loop
for x = (read-tag stream)
while (not (typep x 'tag-end))
collect x))
tag)
(defmethod read-payload (stream (tag tag-int-array))
(let* ((len (read-si4 stream))
(arr (make-array len :element-type '(signed-byte 32))))
(dotimes (i len)
(setf (aref arr i) (read-si4 stream)))
(setf (payload tag) arr))
tag)
;;; Writers
(defgeneric write-header (stream tag)
(:documentation
"Write to STREAM the id of TAG, and (unless it is a tag-end or is within a
tag-list) it's name."))
(defmethod write-header (stream (tag nbt-tag))
(write-si1 stream (id tag))
(cond ((name tag)
(write-si2 stream
(length (babel:string-to-octets (name tag)
:encoding :utf-8)))
(write-utf-8-string stream (name tag)))
(t
(write-si2 stream 0)
(write-utf-8-string stream ""))))
(defmethod write-header (stream (tag tag-end))
(write-si1 stream (id tag)))
(defgeneric write-payload (stream tag)
(:documentation "Write TAG's payload to STREAM."))
(defun write-tag (stream tag)
"Write TAG to STREAM."
(write-header stream tag)
(write-payload stream tag))
(defmethod write-payload (stream (tag tag-end))
(declare (ignore stream tag))
(values))
(defmethod write-payload (stream (tag tag-byte))
(write-si1 stream (payload tag)))
(defmethod write-payload (stream (tag tag-short))
(write-si2 stream (payload tag)))
(defmethod write-payload (stream (tag tag-int))
(write-si4 stream (payload tag)))
(defmethod write-payload (stream (tag tag-long))
(write-si8 stream (payload tag)))
(defmethod write-payload (stream (tag tag-float))
(write-f4 stream (payload tag)))
(defmethod write-payload (stream (tag tag-double))
(write-f8 stream (payload tag)))
;; Simple version (not compatible with .mca files)
(defmethod write-payload (stream (tag tag-byte-array))
(let ((payload (payload tag)))
(write-si4 stream (length payload))
(write-sequence payload stream)))
(defmethod write-payload (stream (tag tag-string))
(write-si2 stream
(length (babel:string-to-octets (payload tag)
:encoding :utf-8)))
(write-utf-8-string stream (payload tag)))
(defmethod write-payload (stream (tag tag-list))
(with-slots (payload) tag
(cond ((not (consp payload))
(write-si1 stream payload)
(write-si4 stream 0))
(t
(write-si1 stream (id (first payload)))
(write-si4 stream (length payload))
(loop
for x in payload
do (write-payload stream x))))))
(defmethod write-payload (stream (tag tag-compound))
(loop
for x in (payload tag)
do (write-tag stream x))
(write-tag stream (instantiate-tag 0)))
(defmethod write-payload (stream (tag tag-int-array))
(write-si4 stream (length (payload tag)))
(loop
for x across (payload tag)
do (write-si4 stream x)))
(defun octets-to-tag (seq)
"Convert the octet vector SEQ to the TAG that it represents."
(cl-fad:with-open-temporary-file (temp :direction :io
:element-type '(unsigned-byte 8))
(write-sequence seq temp)
(file-position temp 0)
(read-tag temp)))
(defun tag-to-octets (tag)
"Convert TAG to an octet vector."
(cl-fad:with-open-temporary-file (temp :direction :io
:element-type '(unsigned-byte 8))
(write-tag temp tag)
(file-position temp 0)
(let ((seq (make-array (file-length temp)
:element-type '(unsigned-byte 8))))
(read-sequence seq temp)
seq)))
;; See the anomaly in the region file spec:
;; https://minecraft.gamepedia.com/Chunk_format#Block_format
;; The "Blocks" case and the catch-all are almost identical. Refactor?
#+nil
(defmethod read-payload (stream (tag tag-byte-array))
(cond ((member (name tag)
'("BlockLight" "SkyLight" "Add" "Data")
:test #'string=)
(let* ((len (read-si4 stream))
(nibbles (make-array (* 2 len) :element-type '(unsigned-byte 4))))
(loop
for i below len
for j = (* 2 i)
for octet = (read-unsigned-int stream 1)
do (setf (aref nibbles j)
(logand #b00001111 octet)
(aref nibbles (1+ j))
(ash (logand #b11110000 octet) -4)))
(setf (payload tag) nibbles)))
((string= (name tag) "Blocks")
(let* ((len (read-si4 stream))
(arr (make-array len :element-type '(unsigned-byte 8))))
(dotimes (i len)
(setf (aref arr i) (read-unsigned-int stream 1)))
(setf (payload tag) arr)))
(t
(let* ((len (read-si4 stream))
(arr (make-array len :element-type '(signed-byte 8))))
(dotimes (i len)
(setf (aref arr i) (read-si1 stream)))
(setf (payload tag) arr))))
tag)
;; See comment above tag-byte-array's read-payload method
#+nil
(defmethod write-payload (stream (tag tag-byte-array))
(progn (write-si4 stream (length (payload tag)))
(write-sequence (payload tag) stream))
(cond ((member (name tag)
'("BlockLight" "SkyLight" "Add" "Data")
:test #'string=)
(let ((octets (make-array 2048 :element-type '(unsigned-byte 8))))
(loop
for i below 4096 by 2
for j below 2048
do (setf (aref octets j)
(+ (aref (payload tag) i)
(ash (aref (payload tag) (1+ i)) 4))))
(write-si4 stream 2048)
(write-sequence octets stream)))
(t
)))