]>
Commit | Line | Data |
---|---|---|
6988a057 JH |
1 | package hotline |
2 | ||
3 | import ( | |
9cf66aea | 4 | "bytes" |
6988a057 | 5 | "encoding/binary" |
7cd900d6 | 6 | "io" |
9cf66aea | 7 | "slices" |
6988a057 JH |
8 | ) |
9 | ||
10 | type flattenedFileObject struct { | |
11 | FlatFileHeader FlatFileHeader | |
7cd900d6 | 12 | FlatFileInformationForkHeader FlatFileForkHeader |
6988a057 | 13 | FlatFileInformationFork FlatFileInformationFork |
7cd900d6 JH |
14 | FlatFileDataForkHeader FlatFileForkHeader |
15 | FlatFileResForkHeader FlatFileForkHeader | |
45ca5d60 JH |
16 | |
17 | readOffset int // Internal offset to track read progress | |
6988a057 JH |
18 | } |
19 | ||
20 | // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values. | |
21 | type FlatFileHeader struct { | |
72dd37f1 JH |
22 | Format [4]byte // Always "FILP" |
23 | Version [2]byte // Always 1 | |
24 | RSVD [16]byte // Always empty zeros | |
7cd900d6 | 25 | ForkCount [2]byte // Number of forks, either 2 or 3 if there is a resource fork |
6988a057 JH |
26 | } |
27 | ||
28 | type FlatFileInformationFork struct { | |
a55350da JH |
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 | |
45ca5d60 JH |
42 | |
43 | readOffset int // Internal offset to track read progress | |
6988a057 JH |
44 | } |
45 | ||
a55350da | 46 | func NewFlatFileInformationFork(fileName string, modifyTime [8]byte, typeSignature string, creatorSignature string) FlatFileInformationFork { |
6988a057 | 47 | return FlatFileInformationFork{ |
a55350da JH |
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 | |
29f329ae | 53 | ModifyDate: modifyTime, |
6988a057 | 54 | Name: []byte(fileName), |
5218c782 | 55 | Comment: []byte{}, // TODO: implement (maybe?) |
6988a057 JH |
56 | } |
57 | } | |
58 | ||
2d52424e | 59 | func (ffif *FlatFileInformationFork) friendlyType() []byte { |
a55350da | 60 | if name, ok := friendlyCreatorNames[string(ffif.TypeSignature[:])]; ok { |
2d52424e JH |
61 | return []byte(name) |
62 | } | |
a55350da | 63 | return ffif.TypeSignature[:] |
7cd900d6 JH |
64 | } |
65 | ||
66 | func (ffif *FlatFileInformationFork) friendlyCreator() []byte { | |
a55350da | 67 | if name, ok := friendlyCreatorNames[string(ffif.CreatorSignature[:])]; ok { |
7cd900d6 JH |
68 | return []byte(name) |
69 | } | |
a55350da | 70 | return ffif.CreatorSignature[:] |
2d52424e JH |
71 | } |
72 | ||
7cd900d6 | 73 | func (ffif *FlatFileInformationFork) setComment(comment []byte) error { |
a55350da | 74 | commentSize := make([]byte, 2) |
7cd900d6 | 75 | ffif.Comment = comment |
a55350da JH |
76 | binary.BigEndian.PutUint16(commentSize, uint16(len(comment))) |
77 | ffif.CommentSize = [2]byte(commentSize) | |
7cd900d6 JH |
78 | // TODO: return err if comment is too long |
79 | return nil | |
80 | } | |
81 | ||
bb7fe19f JH |
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 | |
85767504 | 84 | func (ffif *FlatFileInformationFork) DataSize() []byte { |
6988a057 | 85 | size := make([]byte, 4) |
bb7fe19f | 86 | |
7cd900d6 | 87 | dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers |
6988a057 JH |
88 | |
89 | binary.BigEndian.PutUint32(size, uint32(dataSize)) | |
90 | ||
91 | return size | |
92 | } | |
93 | ||
7cd900d6 JH |
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 | |
9cf66aea JH |
106 | b, _ := io.ReadAll(ffo) |
107 | payloadSize := len(b) | |
7cd900d6 JH |
108 | |
109 | // length of data fork | |
85767504 | 110 | dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]) |
6988a057 | 111 | |
7cd900d6 JH |
112 | // length of resource fork |
113 | resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]) | |
114 | ||
115 | size := make([]byte, 4) | |
aeb97482 | 116 | binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset)) |
6988a057 | 117 | |
7cd900d6 | 118 | return size |
6988a057 JH |
119 | } |
120 | ||
85767504 | 121 | func (ffif *FlatFileInformationFork) ReadNameSize() []byte { |
6988a057 JH |
122 | size := make([]byte, 2) |
123 | binary.BigEndian.PutUint16(size, uint16(len(ffif.Name))) | |
124 | ||
125 | return size | |
126 | } | |
127 | ||
7cd900d6 JH |
128 | type FlatFileForkHeader struct { |
129 | ForkType [4]byte // Either INFO, DATA or MACR | |
85767504 JH |
130 | CompressionType [4]byte |
131 | RSVD [4]byte | |
132 | DataSize [4]byte | |
6988a057 JH |
133 | } |
134 | ||
9cf66aea | 135 | func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) { |
45ca5d60 | 136 | buf := slices.Concat( |
a55350da JH |
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[:], | |
45ca5d60 JH |
146 | ffif.ReadNameSize(), |
147 | ffif.Name, | |
a55350da | 148 | ffif.CommentSize[:], |
45ca5d60 JH |
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 | |
9cf66aea JH |
160 | } |
161 | ||
95159e55 | 162 | // Write implements the io.Writer interface for FlatFileInformationFork |
9cf66aea JH |
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 | ||
a55350da JH |
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]) | |
9cf66aea JH |
178 | ffif.Name = p[72:total] |
179 | ||
180 | if len(p) > int(total) { | |
a55350da JH |
181 | ffif.CommentSize = [2]byte(p[total : total+2]) |
182 | commentLen := binary.BigEndian.Uint16(ffif.CommentSize[:]) | |
9cf66aea JH |
183 | commentStartPos := int(total) + 2 |
184 | commentEndPos := int(total) + 2 + int(commentLen) | |
185 | ||
186 | ffif.Comment = p[commentStartPos:commentEndPos] | |
7cd900d6 | 187 | |
f8e4cd54 | 188 | //total = uint16(commentEndPos) |
9cf66aea JH |
189 | } |
190 | ||
45ca5d60 | 191 | return len(p), nil |
7cd900d6 JH |
192 | } |
193 | ||
85767504 | 194 | func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error { |
85767504 | 195 | nameSize := b[70:72] |
6988a057 | 196 | bs := binary.BigEndian.Uint16(nameSize) |
85767504 | 197 | nameEnd := 72 + bs |
6988a057 | 198 | |
a55350da JH |
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]) | |
85767504 | 209 | ffif.Name = b[72:nameEnd] |
050407a3 JH |
210 | |
211 | if len(b) > int(nameEnd) { | |
a55350da JH |
212 | ffif.CommentSize = [2]byte(b[nameEnd : nameEnd+2]) |
213 | commentLen := binary.BigEndian.Uint16(ffif.CommentSize[:]) | |
050407a3 JH |
214 | |
215 | commentStartPos := int(nameEnd) + 2 | |
216 | commentEndPos := int(nameEnd) + 2 + int(commentLen) | |
217 | ||
218 | ffif.Comment = b[commentStartPos:commentEndPos] | |
219 | } | |
85767504 JH |
220 | |
221 | return nil | |
6988a057 JH |
222 | } |
223 | ||
9cf66aea JH |
224 | // Read implements the io.Reader interface for flattenedFileObject |
225 | func (ffo *flattenedFileObject) Read(p []byte) (int, error) { | |
45ca5d60 | 226 | buf := slices.Concat( |
9cf66aea JH |
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(), | |
a55350da JH |
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[:], | |
9cf66aea JH |
244 | ffo.FlatFileInformationFork.ReadNameSize(), |
245 | ffo.FlatFileInformationFork.Name, | |
a55350da | 246 | ffo.FlatFileInformationFork.CommentSize[:], |
9cf66aea JH |
247 | ffo.FlatFileInformationFork.Comment, |
248 | ffo.FlatFileDataForkHeader.ForkType[:], | |
249 | ffo.FlatFileDataForkHeader.CompressionType[:], | |
250 | ffo.FlatFileDataForkHeader.RSVD[:], | |
251 | ffo.FlatFileDataForkHeader.DataSize[:], | |
45ca5d60 JH |
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 | |
6988a057 JH |
262 | } |
263 | ||
9cf66aea JH |
264 | func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) { |
265 | var n int64 | |
7cd900d6 JH |
266 | |
267 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil { | |
268 | return n, err | |
92a7e455 | 269 | } |
7cd900d6 JH |
270 | |
271 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil { | |
272 | return n, err | |
6988a057 | 273 | } |
16a4ad70 | 274 | |
7cd900d6 JH |
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 | } | |
6988a057 | 280 | |
9cf66aea JH |
281 | _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf)) |
282 | if err != nil { | |
7cd900d6 | 283 | return n, err |
6988a057 JH |
284 | } |
285 | ||
7cd900d6 JH |
286 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil { |
287 | return n, err | |
288 | } | |
6988a057 | 289 | |
7cd900d6 JH |
290 | return n, nil |
291 | } | |
29f329ae | 292 | |
7cd900d6 JH |
293 | func (ffo *flattenedFileObject) dataSize() int64 { |
294 | return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])) | |
295 | } | |
2d52424e | 296 | |
7cd900d6 JH |
297 | func (ffo *flattenedFileObject) rsrcSize() int64 { |
298 | return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])) | |
6988a057 | 299 | } |