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