]> git.r.bdr.sh - rbdr/mobius/blob - hotline/flattened_file_object.go
Adopt more fixed size array types for struct fields
[rbdr/mobius] / hotline / flattened_file_object.go
1 package hotline
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "io"
7 "slices"
8 )
9
10 type flattenedFileObject struct {
11 FlatFileHeader FlatFileHeader
12 FlatFileInformationForkHeader FlatFileForkHeader
13 FlatFileInformationFork FlatFileInformationFork
14 FlatFileDataForkHeader FlatFileForkHeader
15 FlatFileResForkHeader FlatFileForkHeader
16
17 readOffset int // Internal offset to track read progress
18 }
19
20 // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
21 type FlatFileHeader struct {
22 Format [4]byte // Always "FILP"
23 Version [2]byte // Always 1
24 RSVD [16]byte // Always empty zeros
25 ForkCount [2]byte // Number of forks, either 2 or 3 if there is a resource fork
26 }
27
28 type FlatFileInformationFork struct {
29 Platform [4]byte // Operating System used. ("AMAC" or "MWIN")
30 TypeSignature [4]byte // File type signature
31 CreatorSignature [4]byte // File creator signature
32 Flags [4]byte
33 PlatformFlags [4]byte
34 RSVD [32]byte
35 CreateDate [8]byte
36 ModifyDate [8]byte
37 NameScript [2]byte
38 NameSize [2]byte // Length of file name (Maximum 128 characters)
39 Name []byte // File name
40 CommentSize [2]byte // Length of the comment
41 Comment []byte // File comment
42
43 readOffset int // Internal offset to track read progress
44 }
45
46 func NewFlatFileInformationFork(fileName string, modifyTime [8]byte, typeSignature string, creatorSignature string) FlatFileInformationFork {
47 return FlatFileInformationFork{
48 Platform: [4]byte{0x41, 0x4D, 0x41, 0x43}, // "AMAC" TODO: Remove hardcode to support "AWIN" Platform (maybe?)
49 TypeSignature: [4]byte([]byte(typeSignature)), // TODO: Don't infer types from filename
50 CreatorSignature: [4]byte([]byte(creatorSignature)), // TODO: Don't infer types from filename
51 PlatformFlags: [4]byte{0, 0, 1, 0}, // TODO: What is this?
52 CreateDate: modifyTime, // some filesystems don't support createTime
53 ModifyDate: modifyTime,
54 Name: []byte(fileName),
55 Comment: []byte{}, // TODO: implement (maybe?)
56 }
57 }
58
59 func (ffif *FlatFileInformationFork) friendlyType() []byte {
60 if name, ok := friendlyCreatorNames[string(ffif.TypeSignature[:])]; ok {
61 return []byte(name)
62 }
63 return ffif.TypeSignature[:]
64 }
65
66 func (ffif *FlatFileInformationFork) friendlyCreator() []byte {
67 if name, ok := friendlyCreatorNames[string(ffif.CreatorSignature[:])]; ok {
68 return []byte(name)
69 }
70 return ffif.CreatorSignature[:]
71 }
72
73 func (ffif *FlatFileInformationFork) setComment(comment []byte) error {
74 commentSize := make([]byte, 2)
75 ffif.Comment = comment
76 binary.BigEndian.PutUint16(commentSize, uint16(len(comment)))
77 ffif.CommentSize = [2]byte(commentSize)
78 // TODO: return err if comment is too long
79 return nil
80 }
81
82 // DataSize calculates the size of the flat file information fork, which is
83 // 72 bytes for the fixed length fields plus the length of the Name + Comment
84 func (ffif *FlatFileInformationFork) DataSize() []byte {
85 size := make([]byte, 4)
86
87 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
88
89 binary.BigEndian.PutUint32(size, uint32(dataSize))
90
91 return size
92 }
93
94 func (ffif *FlatFileInformationFork) Size() [4]byte {
95 size := [4]byte{}
96
97 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
98
99 binary.BigEndian.PutUint32(size[:], uint32(dataSize))
100
101 return size
102 }
103
104 func (ffo *flattenedFileObject) TransferSize(offset int64) []byte {
105 // get length of the flattenedFileObject, including the info fork
106 b, _ := io.ReadAll(ffo)
107 payloadSize := len(b)
108
109 // length of data fork
110 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
111
112 // length of resource fork
113 resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])
114
115 size := make([]byte, 4)
116 binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset))
117
118 return size
119 }
120
121 func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
122 size := make([]byte, 2)
123 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
124
125 return size
126 }
127
128 type FlatFileForkHeader struct {
129 ForkType [4]byte // Either INFO, DATA or MACR
130 CompressionType [4]byte
131 RSVD [4]byte
132 DataSize [4]byte
133 }
134
135 func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) {
136 buf := slices.Concat(
137 ffif.Platform[:],
138 ffif.TypeSignature[:],
139 ffif.CreatorSignature[:],
140 ffif.Flags[:],
141 ffif.PlatformFlags[:],
142 ffif.RSVD[:],
143 ffif.CreateDate[:],
144 ffif.ModifyDate[:],
145 ffif.NameScript[:],
146 ffif.ReadNameSize(),
147 ffif.Name,
148 ffif.CommentSize[:],
149 ffif.Comment,
150 )
151
152 if ffif.readOffset >= len(buf) {
153 return 0, io.EOF // All bytes have been read
154 }
155
156 n := copy(p, buf[ffif.readOffset:])
157 ffif.readOffset += n
158
159 return n, nil
160 }
161
162 // Write implements the io.Writer interface for FlatFileInformationFork
163 func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) {
164 nameSize := p[70:72]
165 bs := binary.BigEndian.Uint16(nameSize)
166 total := 72 + bs
167
168 ffif.Platform = [4]byte(p[0:4])
169 ffif.TypeSignature = [4]byte(p[4:8])
170 ffif.CreatorSignature = [4]byte(p[8:12])
171 ffif.Flags = [4]byte(p[12:16])
172 ffif.PlatformFlags = [4]byte(p[16:20])
173 ffif.RSVD = [32]byte(p[20:52])
174 ffif.CreateDate = [8]byte(p[52:60])
175 ffif.ModifyDate = [8]byte(p[60:68])
176 ffif.NameScript = [2]byte(p[68:70])
177 ffif.NameSize = [2]byte(p[70:72])
178 ffif.Name = p[72:total]
179
180 if len(p) > int(total) {
181 ffif.CommentSize = [2]byte(p[total : total+2])
182 commentLen := binary.BigEndian.Uint16(ffif.CommentSize[:])
183 commentStartPos := int(total) + 2
184 commentEndPos := int(total) + 2 + int(commentLen)
185
186 ffif.Comment = p[commentStartPos:commentEndPos]
187
188 //total = uint16(commentEndPos)
189 }
190
191 return len(p), nil
192 }
193
194 func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
195 nameSize := b[70:72]
196 bs := binary.BigEndian.Uint16(nameSize)
197 nameEnd := 72 + bs
198
199 ffif.Platform = [4]byte(b[0:4])
200 ffif.TypeSignature = [4]byte(b[4:8])
201 ffif.CreatorSignature = [4]byte(b[8:12])
202 ffif.Flags = [4]byte(b[12:16])
203 ffif.PlatformFlags = [4]byte(b[16:20])
204 ffif.RSVD = [32]byte(b[20:52])
205 ffif.CreateDate = [8]byte(b[52:60])
206 ffif.ModifyDate = [8]byte(b[60:68])
207 ffif.NameScript = [2]byte(b[68:70])
208 ffif.NameSize = [2]byte(b[70:72])
209 ffif.Name = b[72:nameEnd]
210
211 if len(b) > int(nameEnd) {
212 ffif.CommentSize = [2]byte(b[nameEnd : nameEnd+2])
213 commentLen := binary.BigEndian.Uint16(ffif.CommentSize[:])
214
215 commentStartPos := int(nameEnd) + 2
216 commentEndPos := int(nameEnd) + 2 + int(commentLen)
217
218 ffif.Comment = b[commentStartPos:commentEndPos]
219 }
220
221 return nil
222 }
223
224 // Read implements the io.Reader interface for flattenedFileObject
225 func (ffo *flattenedFileObject) Read(p []byte) (int, error) {
226 buf := slices.Concat(
227 ffo.FlatFileHeader.Format[:],
228 ffo.FlatFileHeader.Version[:],
229 ffo.FlatFileHeader.RSVD[:],
230 ffo.FlatFileHeader.ForkCount[:],
231 []byte("INFO"),
232 []byte{0, 0, 0, 0},
233 make([]byte, 4),
234 ffo.FlatFileInformationFork.DataSize(),
235 ffo.FlatFileInformationFork.Platform[:],
236 ffo.FlatFileInformationFork.TypeSignature[:],
237 ffo.FlatFileInformationFork.CreatorSignature[:],
238 ffo.FlatFileInformationFork.Flags[:],
239 ffo.FlatFileInformationFork.PlatformFlags[:],
240 ffo.FlatFileInformationFork.RSVD[:],
241 ffo.FlatFileInformationFork.CreateDate[:],
242 ffo.FlatFileInformationFork.ModifyDate[:],
243 ffo.FlatFileInformationFork.NameScript[:],
244 ffo.FlatFileInformationFork.ReadNameSize(),
245 ffo.FlatFileInformationFork.Name,
246 ffo.FlatFileInformationFork.CommentSize[:],
247 ffo.FlatFileInformationFork.Comment,
248 ffo.FlatFileDataForkHeader.ForkType[:],
249 ffo.FlatFileDataForkHeader.CompressionType[:],
250 ffo.FlatFileDataForkHeader.RSVD[:],
251 ffo.FlatFileDataForkHeader.DataSize[:],
252 )
253
254 if ffo.readOffset >= len(buf) {
255 return 0, io.EOF // All bytes have been read
256 }
257
258 n := copy(p, buf[ffo.readOffset:])
259 ffo.readOffset += n
260
261 return n, nil
262 }
263
264 func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) {
265 var n int64
266
267 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil {
268 return n, err
269 }
270
271 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil {
272 return n, err
273 }
274
275 dataLen := binary.BigEndian.Uint32(ffo.FlatFileInformationForkHeader.DataSize[:])
276 ffifBuf := make([]byte, dataLen)
277 if _, err := io.ReadFull(r, ffifBuf); err != nil {
278 return n, err
279 }
280
281 _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf))
282 if err != nil {
283 return n, err
284 }
285
286 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil {
287 return n, err
288 }
289
290 return n, nil
291 }
292
293 func (ffo *flattenedFileObject) dataSize() int64 {
294 return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]))
295 }
296
297 func (ffo *flattenedFileObject) rsrcSize() int64 {
298 return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]))
299 }