10 type flattenedFileObject struct {
11 FlatFileHeader FlatFileHeader
12 FlatFileInformationForkHeader FlatFileForkHeader
13 FlatFileInformationFork FlatFileInformationFork
14 FlatFileDataForkHeader FlatFileForkHeader
15 FlatFileResForkHeader FlatFileForkHeader
17 readOffset int // Internal offset to track read progress
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
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
38 NameSize []byte // Length of file name (Maximum 128 characters)
39 Name []byte // File name
40 CommentSize []byte // Length of the comment
41 Comment []byte // File comment
43 readOffset int // Internal offset to track read progress
46 func NewFlatFileInformationFork(fileName string, modifyTime []byte, typeSignature string, creatorSignature string) FlatFileInformationFork {
47 return FlatFileInformationFork{
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
55 ModifyDate: modifyTime,
56 NameScript: make([]byte, 2), // TODO: What is this?
57 Name: []byte(fileName),
58 CommentSize: []byte{0, 0},
59 Comment: []byte{}, // TODO: implement (maybe?)
63 func (ffif *FlatFileInformationFork) friendlyType() []byte {
64 if name, ok := friendlyCreatorNames[string(ffif.TypeSignature)]; ok {
67 return ffif.TypeSignature
70 func (ffif *FlatFileInformationFork) friendlyCreator() []byte {
71 if name, ok := friendlyCreatorNames[string(ffif.CreatorSignature)]; ok {
74 return ffif.CreatorSignature
77 func (ffif *FlatFileInformationFork) setComment(comment []byte) error {
78 ffif.CommentSize = make([]byte, 2)
79 ffif.Comment = comment
80 binary.BigEndian.PutUint16(ffif.CommentSize, uint16(len(comment)))
82 // TODO: return err if comment is too long
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
88 func (ffif *FlatFileInformationFork) DataSize() []byte {
89 size := make([]byte, 4)
91 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
93 binary.BigEndian.PutUint32(size, uint32(dataSize))
98 func (ffif *FlatFileInformationFork) Size() [4]byte {
101 dataSize := len(ffif.Name) + len(ffif.Comment) + 74 // 74 = len of fixed size headers
103 binary.BigEndian.PutUint32(size[:], uint32(dataSize))
108 func (ffo *flattenedFileObject) TransferSize(offset int64) []byte {
109 // get length of the flattenedFileObject, including the info fork
110 b, _ := io.ReadAll(ffo)
111 payloadSize := len(b)
113 // length of data fork
114 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
116 // length of resource fork
117 resForkSize := binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:])
119 size := make([]byte, 4)
120 binary.BigEndian.PutUint32(size, dataSize+resForkSize+uint32(payloadSize)-uint32(offset))
125 func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
126 size := make([]byte, 2)
127 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
132 type FlatFileForkHeader struct {
133 ForkType [4]byte // Either INFO, DATA or MACR
134 CompressionType [4]byte
139 func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) {
140 buf := slices.Concat(
143 ffif.CreatorSignature,
156 if ffif.readOffset >= len(buf) {
157 return 0, io.EOF // All bytes have been read
160 n := copy(p, buf[ffif.readOffset:])
166 // Write implements the io.Writer interface for FlatFileInformationFork
167 func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) {
169 bs := binary.BigEndian.Uint16(nameSize)
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]
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]
184 if len(p) > int(total) {
185 ffif.CommentSize = p[total : total+2]
186 commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
187 commentStartPos := int(total) + 2
188 commentEndPos := int(total) + 2 + int(commentLen)
190 ffif.Comment = p[commentStartPos:commentEndPos]
192 //total = uint16(commentEndPos)
198 func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
200 bs := binary.BigEndian.Uint16(nameSize)
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]
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]
215 if len(b) > int(nameEnd) {
216 ffif.CommentSize = b[nameEnd : nameEnd+2]
217 commentLen := binary.BigEndian.Uint16(ffif.CommentSize)
219 commentStartPos := int(nameEnd) + 2
220 commentEndPos := int(nameEnd) + 2 + int(commentLen)
222 ffif.Comment = b[commentStartPos:commentEndPos]
228 // Read implements the io.Reader interface for flattenedFileObject
229 func (ffo *flattenedFileObject) Read(p []byte) (int, error) {
230 buf := slices.Concat(
231 ffo.FlatFileHeader.Format[:],
232 ffo.FlatFileHeader.Version[:],
233 ffo.FlatFileHeader.RSVD[:],
234 ffo.FlatFileHeader.ForkCount[:],
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[:],
258 if ffo.readOffset >= len(buf) {
259 return 0, io.EOF // All bytes have been read
262 n := copy(p, buf[ffo.readOffset:])
268 func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) {
271 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil {
275 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileInformationForkHeader); err != nil {
279 dataLen := binary.BigEndian.Uint32(ffo.FlatFileInformationForkHeader.DataSize[:])
280 ffifBuf := make([]byte, dataLen)
281 if _, err := io.ReadFull(r, ffifBuf); err != nil {
285 _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf))
290 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileDataForkHeader); err != nil {
297 func (ffo *flattenedFileObject) dataSize() int64 {
298 return int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]))
301 func (ffo *flattenedFileObject) rsrcSize() int64 {
302 return int64(binary.BigEndian.Uint32(ffo.FlatFileResForkHeader.DataSize[:]))