]>
Commit | Line | Data |
---|---|---|
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 | } |