]>
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 | ffoCopy := *ffo | |
106 | ||
107 | // get length of the flattenedFileObject, including the info fork | |
108 | b, _ := io.ReadAll(&ffoCopy) | |
109 | payloadSize := len(b) | |
110 | ||
111 | // length of data fork | |
112 | dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]) | |
113 | ||
114 | // length of resource fork | |
115 | resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]) | |
116 | ||
117 | size := make([]byte, 4) | |
118 | binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset)) | |
119 | ||
120 | return size | |
121 | } | |
122 | ||
123 | func (ffif *FlatFileInformationFork) ReadNameSize() []byte { | |
124 | size := make([]byte, 2) | |
125 | binary.BigEndian.PutUint16(size, uint16(len(ffif.Name))) | |
126 | ||
127 | return size | |
128 | } | |
129 | ||
130 | type FlatFileForkHeader struct { | |
131 | ForkType [4]byte // Either INFO, DATA or MACR | |
132 | CompressionType [4]byte | |
133 | RSVD [4]byte | |
134 | DataSize [4]byte | |
135 | } | |
136 | ||
137 | func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) { | |
138 | buf := slices.Concat( | |
139 | ffif.Platform[:], | |
140 | ffif.TypeSignature[:], | |
141 | ffif.CreatorSignature[:], | |
142 | ffif.Flags[:], | |
143 | ffif.PlatformFlags[:], | |
144 | ffif.RSVD[:], | |
145 | ffif.CreateDate[:], | |
146 | ffif.ModifyDate[:], | |
147 | ffif.NameScript[:], | |
148 | ffif.ReadNameSize(), | |
149 | ffif.Name, | |
150 | ffif.CommentSize[:], | |
151 | ffif.Comment, | |
152 | ) | |
153 | ||
154 | if ffif.readOffset >= len(buf) { | |
155 | return 0, io.EOF // All bytes have been read | |
156 | } | |
157 | ||
158 | n := copy(p, buf[ffif.readOffset:]) | |
159 | ffif.readOffset += n | |
160 | ||
161 | return n, nil | |
162 | } | |
163 | ||
164 | // Write implements the io.Writer interface for FlatFileInformationFork | |
165 | func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) { | |
166 | nameSize := p[70:72] | |
167 | bs := binary.BigEndian.Uint16(nameSize) | |
168 | total := 72 + bs | |
169 | ||
170 | ffif.Platform = [4]byte(p[0:4]) | |
171 | ffif.TypeSignature = [4]byte(p[4:8]) | |
172 | ffif.CreatorSignature = [4]byte(p[8:12]) | |
173 | ffif.Flags = [4]byte(p[12:16]) | |
174 | ffif.PlatformFlags = [4]byte(p[16:20]) | |
175 | ffif.RSVD = [32]byte(p[20:52]) | |
176 | ffif.CreateDate = [8]byte(p[52:60]) | |
177 | ffif.ModifyDate = [8]byte(p[60:68]) | |
178 | ffif.NameScript = [2]byte(p[68:70]) | |
179 | ffif.NameSize = [2]byte(p[70:72]) | |
180 | ffif.Name = p[72:total] | |
181 | ||
182 | if len(p) > int(total) { | |
183 | ffif.CommentSize = [2]byte(p[total : total+2]) | |
184 | commentLen := binary.BigEndian.Uint16(ffif.CommentSize[:]) | |
185 | commentStartPos := int(total) + 2 | |
186 | commentEndPos := int(total) + 2 + int(commentLen) | |
187 | ||
188 | ffif.Comment = p[commentStartPos:commentEndPos] | |
189 | ||
190 | //total = uint16(commentEndPos) | |
191 | } | |
192 | ||
193 | return len(p), nil | |
194 | } | |
195 | ||
196 | func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error { | |
197 | nameSize := b[70:72] | |
198 | bs := binary.BigEndian.Uint16(nameSize) | |
199 | nameEnd := 72 + bs | |
200 | ||
201 | ffif.Platform = [4]byte(b[0:4]) | |
202 | ffif.TypeSignature = [4]byte(b[4:8]) | |
203 | ffif.CreatorSignature = [4]byte(b[8:12]) | |
204 | ffif.Flags = [4]byte(b[12:16]) | |
205 | ffif.PlatformFlags = [4]byte(b[16:20]) | |
206 | ffif.RSVD = [32]byte(b[20:52]) | |
207 | ffif.CreateDate = [8]byte(b[52:60]) | |
208 | ffif.ModifyDate = [8]byte(b[60:68]) | |
209 | ffif.NameScript = [2]byte(b[68:70]) | |
210 | ffif.NameSize = [2]byte(b[70:72]) | |
211 | ffif.Name = b[72:nameEnd] | |
212 | ||
213 | if len(b) > int(nameEnd) { | |
214 | ffif.CommentSize = [2]byte(b[nameEnd : nameEnd+2]) | |
215 | commentLen := binary.BigEndian.Uint16(ffif.CommentSize[:]) | |
216 | ||
217 | commentStartPos := int(nameEnd) + 2 | |
218 | commentEndPos := int(nameEnd) + 2 + int(commentLen) | |
219 | ||
220 | ffif.Comment = b[commentStartPos:commentEndPos] | |
221 | } | |
222 | ||
223 | return nil | |
224 | } | |
225 | ||
226 | // Read implements the io.Reader interface for flattenedFileObject | |
227 | func (ffo *flattenedFileObject) Read(p []byte) (int, error) { | |
228 | buf := slices.Concat( | |
229 | ffo.FlatFileHeader.Format[:], | |
230 | ffo.FlatFileHeader.Version[:], | |
231 | ffo.FlatFileHeader.RSVD[:], | |
232 | ffo.FlatFileHeader.ForkCount[:], | |
233 | []byte("INFO"), | |
234 | []byte{0, 0, 0, 0}, | |
235 | make([]byte, 4), | |
236 | ffo.FlatFileInformationFork.DataSize(), | |
237 | ffo.FlatFileInformationFork.Platform[:], | |
238 | ffo.FlatFileInformationFork.TypeSignature[:], | |
239 | ffo.FlatFileInformationFork.CreatorSignature[:], | |
240 | ffo.FlatFileInformationFork.Flags[:], | |
241 | ffo.FlatFileInformationFork.PlatformFlags[:], | |
242 | ffo.FlatFileInformationFork.RSVD[:], | |
243 | ffo.FlatFileInformationFork.CreateDate[:], | |
244 | ffo.FlatFileInformationFork.ModifyDate[:], | |
245 | ffo.FlatFileInformationFork.NameScript[:], | |
246 | ffo.FlatFileInformationFork.ReadNameSize(), | |
247 | ffo.FlatFileInformationFork.Name, | |
248 | ffo.FlatFileInformationFork.CommentSize[:], | |
249 | ffo.FlatFileInformationFork.Comment, | |
250 | ffo.FlatFileDataForkHeader.ForkType[:], | |
251 | ffo.FlatFileDataForkHeader.CompressionType[:], | |
252 | ffo.FlatFileDataForkHeader.RSVD[:], | |
253 | ffo.FlatFileDataForkHeader.DataSize[:], | |
254 | ) | |
255 | ||
256 | if ffo.readOffset >= len(buf) { | |
257 | return 0, io.EOF // All bytes have been read | |
258 | } | |
259 | ||
260 | n := copy(p, buf[ffo.readOffset:]) | |
261 | ffo.readOffset += n | |
262 | ||
263 | return n, nil | |
264 | } | |
265 | ||
266 | func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) { | |
267 | var n int64 | |
268 | ||
269 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil { | |
270 | return n, err | |
271 | } | |
272 | ||
273 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil { | |
274 | return n, err | |
275 | } | |
276 | ||
277 | dataLen := binary.BigEndian.Uint32(ffo.FlatFileInformationForkHeader.DataSize[:]) | |
278 | ffifBuf := make([]byte, dataLen) | |
279 | if _, err := io.ReadFull(r, ffifBuf); err != nil { | |
280 | return n, err | |
281 | } | |
282 | ||
283 | _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf)) | |
284 | if err != nil { | |
285 | return n, err | |
286 | } | |
287 | ||
288 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil { | |
289 | return n, err | |
290 | } | |
291 | ||
292 | return n, nil | |
293 | } | |
294 | ||
295 | func (ffo *flattenedFileObject) dataSize() int64 { | |
296 | return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])) | |
297 | } | |
298 | ||
299 | func (ffo *flattenedFileObject) rsrcSize() int64 { | |
300 | return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])) | |
301 | } |