]>
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 { | |
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 | |
7cd900d6 | 37 | NameScript []byte |
6988a057 JH |
38 | NameSize []byte // Length of file name (Maximum 128 characters) |
39 | Name []byte // File name | |
7cd900d6 | 40 | CommentSize []byte // Length of the comment |
6988a057 | 41 | Comment []byte // File comment |
45ca5d60 JH |
42 | |
43 | readOffset int // Internal offset to track read progress | |
6988a057 JH |
44 | } |
45 | ||
2d52424e | 46 | func NewFlatFileInformationFork(fileName string, modifyTime []byte, typeSignature string, creatorSignature string) FlatFileInformationFork { |
6988a057 | 47 | return FlatFileInformationFork{ |
2d52424e JH |
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 | |
29f329ae JH |
55 | ModifyDate: modifyTime, |
56 | NameScript: make([]byte, 2), // TODO: What is this? | |
6988a057 | 57 | Name: []byte(fileName), |
5218c782 JH |
58 | CommentSize: []byte{0, 0}, |
59 | Comment: []byte{}, // TODO: implement (maybe?) | |
6988a057 JH |
60 | } |
61 | } | |
62 | ||
2d52424e | 63 | func (ffif *FlatFileInformationFork) friendlyType() []byte { |
2d52424e JH |
64 | if name, ok := friendlyCreatorNames[string(ffif.TypeSignature)]; ok { |
65 | return []byte(name) | |
66 | } | |
7cd900d6 JH |
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 | } | |
2d52424e JH |
74 | return ffif.CreatorSignature |
75 | } | |
76 | ||
7cd900d6 | 77 | func (ffif *FlatFileInformationFork) setComment(comment []byte) error { |
a1ac9a6f | 78 | ffif.CommentSize = make([]byte, 2) |
7cd900d6 JH |
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 | ||
bb7fe19f JH |
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 | |
85767504 | 88 | func (ffif *FlatFileInformationFork) DataSize() []byte { |
6988a057 | 89 | size := make([]byte, 4) |
bb7fe19f | 90 | |
7cd900d6 | 91 | dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers |
6988a057 JH |
92 | |
93 | binary.BigEndian.PutUint32(size, uint32(dataSize)) | |
94 | ||
95 | return size | |
96 | } | |
97 | ||
7cd900d6 JH |
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 | |
9cf66aea JH |
110 | b, _ := io.ReadAll(ffo) |
111 | payloadSize := len(b) | |
7cd900d6 JH |
112 | |
113 | // length of data fork | |
85767504 | 114 | dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]) |
6988a057 | 115 | |
7cd900d6 JH |
116 | // length of resource fork |
117 | resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]) | |
118 | ||
119 | size := make([]byte, 4) | |
aeb97482 | 120 | binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset)) |
6988a057 | 121 | |
7cd900d6 | 122 | return size |
6988a057 JH |
123 | } |
124 | ||
85767504 | 125 | func (ffif *FlatFileInformationFork) ReadNameSize() []byte { |
6988a057 JH |
126 | size := make([]byte, 2) |
127 | binary.BigEndian.PutUint16(size, uint16(len(ffif.Name))) | |
128 | ||
129 | return size | |
130 | } | |
131 | ||
7cd900d6 JH |
132 | type FlatFileForkHeader struct { |
133 | ForkType [4]byte // Either INFO, DATA or MACR | |
85767504 JH |
134 | CompressionType [4]byte |
135 | RSVD [4]byte | |
136 | DataSize [4]byte | |
6988a057 JH |
137 | } |
138 | ||
9cf66aea | 139 | func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) { |
45ca5d60 JH |
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 | |
9cf66aea JH |
164 | } |
165 | ||
95159e55 | 166 | // Write implements the io.Writer interface for FlatFileInformationFork |
9cf66aea JH |
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) | |
9cf66aea JH |
187 | commentStartPos := int(total) + 2 |
188 | commentEndPos := int(total) + 2 + int(commentLen) | |
189 | ||
190 | ffif.Comment = p[commentStartPos:commentEndPos] | |
7cd900d6 | 191 | |
f8e4cd54 | 192 | //total = uint16(commentEndPos) |
9cf66aea JH |
193 | } |
194 | ||
45ca5d60 | 195 | return len(p), nil |
7cd900d6 JH |
196 | } |
197 | ||
85767504 | 198 | func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error { |
85767504 | 199 | nameSize := b[70:72] |
6988a057 | 200 | bs := binary.BigEndian.Uint16(nameSize) |
85767504 | 201 | nameEnd := 72 + bs |
6988a057 | 202 | |
85767504 JH |
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] | |
050407a3 JH |
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 | } | |
85767504 JH |
224 | |
225 | return nil | |
6988a057 JH |
226 | } |
227 | ||
9cf66aea JH |
228 | // Read implements the io.Reader interface for flattenedFileObject |
229 | func (ffo *flattenedFileObject) Read(p []byte) (int, error) { | |
45ca5d60 | 230 | buf := slices.Concat( |
9cf66aea JH |
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[:], | |
45ca5d60 JH |
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 | |
6988a057 JH |
266 | } |
267 | ||
9cf66aea JH |
268 | func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) { |
269 | var n int64 | |
7cd900d6 JH |
270 | |
271 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil { | |
272 | return n, err | |
92a7e455 | 273 | } |
7cd900d6 JH |
274 | |
275 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil { | |
276 | return n, err | |
6988a057 | 277 | } |
16a4ad70 | 278 | |
7cd900d6 JH |
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 | } | |
6988a057 | 284 | |
9cf66aea JH |
285 | _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf)) |
286 | if err != nil { | |
7cd900d6 | 287 | return n, err |
6988a057 JH |
288 | } |
289 | ||
7cd900d6 JH |
290 | if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil { |
291 | return n, err | |
292 | } | |
6988a057 | 293 | |
7cd900d6 JH |
294 | return n, nil |
295 | } | |
29f329ae | 296 | |
7cd900d6 JH |
297 | func (ffo *flattenedFileObject) dataSize() int64 { |
298 | return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])) | |
299 | } | |
2d52424e | 300 | |
7cd900d6 JH |
301 | func (ffo *flattenedFileObject) rsrcSize() int64 { |
302 | return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])) | |
6988a057 | 303 | } |