10 type flattenedFileObject struct {
11 FlatFileHeader FlatFileHeader
12 FlatFileInformationForkHeader FlatFileForkHeader
13 FlatFileInformationFork FlatFileInformationFork
14 FlatFileDataForkHeader FlatFileForkHeader
15 FlatFileResForkHeader FlatFileForkHeader
18 // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
19 type FlatFileHeader struct {
20 Format [4]byte // Always "FILP"
21 Version [2]byte // Always 1
22 RSVD [16]byte // Always empty zeros
23 ForkCount [2]byte // Number of forks, either 2 or 3 if there is a resource fork
26 type FlatFileInformationFork struct {
27 Platform []byte // Operating System used. ("AMAC" or "MWIN")
28 TypeSignature []byte // File type signature
29 CreatorSignature []byte // File creator signature
36 NameSize []byte // Length of file name (Maximum 128 characters)
37 Name []byte // File name
38 CommentSize []byte // Length of the comment
39 Comment []byte // File comment
42 func NewFlatFileInformationFork(fileName string, modifyTime []byte, typeSignature string, creatorSignature string) FlatFileInformationFork {
43 return FlatFileInformationFork{
44 Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
45 TypeSignature: []byte(typeSignature), // TODO: Don't infer types from filename
46 CreatorSignature: []byte(creatorSignature), // TODO: Don't infer types from filename
47 Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
48 PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
49 RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
50 CreateDate: modifyTime, // some filesystems don't support createTime
51 ModifyDate: modifyTime,
52 NameScript: make([]byte, 2), // TODO: What is this?
53 Name: []byte(fileName),
54 CommentSize: []byte{0, 0},
55 Comment: []byte{}, // TODO: implement (maybe?)
59 func (ffif *FlatFileInformationFork) friendlyType() []byte {
60 if name, ok := friendlyCreatorNames[string(ffif.TypeSignature)]; ok {
63 return ffif.TypeSignature
66 func (ffif *FlatFileInformationFork) friendlyCreator() []byte {
67 if name, ok := friendlyCreatorNames[string(ffif.CreatorSignature)]; ok {
70 return ffif.CreatorSignature
73 func (ffif *FlatFileInformationFork) setComment(comment []byte) error {
74 ffif.CommentSize = make([]byte, 2)
75 ffif.Comment = comment
76 binary.BigEndian.PutUint16(ffif.CommentSize, uint16(len(comment)))
78 // TODO: return err if comment is too long
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)
87 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
89 binary.BigEndian.PutUint32(size, uint32(dataSize))
94 func (ffif *FlatFileInformationFork) Size() [4]byte {
97 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
99 binary.BigEndian.PutUint32(size[:], uint32(dataSize))
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)
109 // length of data fork
110 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
112 // length of resource fork
113 resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])
115 size := make([]byte, 4)
116 binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset))
121 func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
122 size := make([]byte, 2)
123 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
128 type FlatFileForkHeader struct {
129 ForkType [4]byte // Either INFO, DATA or MACR
130 CompressionType [4]byte
135 func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) {
140 ffif.CreatorSignature,
155 // Write implements the io.Writer interface for FlatFileInformationFork
156 func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) {
158 bs := binary.BigEndian.Uint16(nameSize)
161 ffif.Platform = p[0:4]
162 ffif.TypeSignature = p[4:8]
163 ffif.CreatorSignature = p[8:12]
164 ffif.Flags = p[12:16]
165 ffif.PlatformFlags = p[16:20]
167 ffif.CreateDate = p[52:60]
168 ffif.ModifyDate = p[60:68]
169 ffif.NameScript = p[68:70]
170 ffif.NameSize = p[70:72]
171 ffif.Name = p[72:total]
173 if len(p) > int(total) {
174 ffif.CommentSize = p[total : total+2]
175 commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
177 commentStartPos := int(total) + 2
178 commentEndPos := int(total) + 2 + int(commentLen)
180 ffif.Comment = p[commentStartPos:commentEndPos]
182 total = uint16(commentEndPos)
185 return int(total), nil
188 func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
190 bs := binary.BigEndian.Uint16(nameSize)
193 ffif.Platform = b[0:4]
194 ffif.TypeSignature = b[4:8]
195 ffif.CreatorSignature = b[8:12]
196 ffif.Flags = b[12:16]
197 ffif.PlatformFlags = b[16:20]
199 ffif.CreateDate = b[52:60]
200 ffif.ModifyDate = b[60:68]
201 ffif.NameScript = b[68:70]
202 ffif.NameSize = b[70:72]
203 ffif.Name = b[72:nameEnd]
205 if len(b) > int(nameEnd) {
206 ffif.CommentSize = b[nameEnd : nameEnd+2]
207 commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
209 commentStartPos := int(nameEnd) + 2
210 commentEndPos := int(nameEnd) + 2 + int(commentLen)
212 ffif.Comment = b[commentStartPos:commentEndPos]
218 // Read implements the io.Reader interface for flattenedFileObject
219 func (ffo *flattenedFileObject) Read(p []byte) (int, error) {
220 return copy(p, slices.Concat(
221 ffo.FlatFileHeader.Format[:],
222 ffo.FlatFileHeader.Version[:],
223 ffo.FlatFileHeader.RSVD[:],
224 ffo.FlatFileHeader.ForkCount[:],
228 ffo.FlatFileInformationFork.DataSize(),
229 ffo.FlatFileInformationFork.Platform,
230 ffo.FlatFileInformationFork.TypeSignature,
231 ffo.FlatFileInformationFork.CreatorSignature,
232 ffo.FlatFileInformationFork.Flags,
233 ffo.FlatFileInformationFork.PlatformFlags,
234 ffo.FlatFileInformationFork.RSVD,
235 ffo.FlatFileInformationFork.CreateDate,
236 ffo.FlatFileInformationFork.ModifyDate,
237 ffo.FlatFileInformationFork.NameScript,
238 ffo.FlatFileInformationFork.ReadNameSize(),
239 ffo.FlatFileInformationFork.Name,
240 ffo.FlatFileInformationFork.CommentSize,
241 ffo.FlatFileInformationFork.Comment,
242 ffo.FlatFileDataForkHeader.ForkType[:],
243 ffo.FlatFileDataForkHeader.CompressionType[:],
244 ffo.FlatFileDataForkHeader.RSVD[:],
245 ffo.FlatFileDataForkHeader.DataSize[:],
250 func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) {
253 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil {
257 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil {
261 dataLen := binary.BigEndian.Uint32(ffo.FlatFileInformationForkHeader.DataSize[:])
262 ffifBuf := make([]byte, dataLen)
263 if _, err := io.ReadFull(r, ffifBuf); err != nil {
267 _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf))
272 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil {
279 func (ffo *flattenedFileObject) dataSize() int64 {
280 return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]))
283 func (ffo *flattenedFileObject) rsrcSize() int64 {
284 return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]))