]>
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 [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 | } |