8 type flattenedFileObject struct {
9 FlatFileHeader FlatFileHeader
10 FlatFileInformationForkHeader FlatFileInformationForkHeader
11 FlatFileInformationFork FlatFileInformationFork
12 FlatFileDataForkHeader FlatFileDataForkHeader
16 // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
17 type FlatFileHeader struct {
18 Format [4]byte // Always "FILP"
19 Version [2]byte // Always 1
20 RSVD [16]byte // Always empty zeros
21 ForkCount [2]byte // Number of forks
24 // NewFlatFileHeader returns a FlatFileHeader struct
25 func NewFlatFileHeader() FlatFileHeader {
26 return FlatFileHeader{
27 Format: [4]byte{0x46, 0x49, 0x4c, 0x50}, // FILP
28 Version: [2]byte{0, 1},
30 ForkCount: [2]byte{0, 2},
34 // FlatFileInformationForkHeader is the second section of a "Flattened File Object"
35 type FlatFileInformationForkHeader struct {
36 ForkType [4]byte // Always "INFO"
37 CompressionType [4]byte // Always 0; Compression was never implemented in the Hotline protocol
38 RSVD [4]byte // Always zeros
39 DataSize [4]byte // Size of the flat file information fork
42 type FlatFileInformationFork struct {
43 Platform []byte // Operating System used. ("AMAC" or "MWIN")
44 TypeSignature []byte // File type signature
45 CreatorSignature []byte // File creator signature
51 NameScript []byte // TODO: what is this?
52 NameSize []byte // Length of file name (Maximum 128 characters)
53 Name []byte // File name
54 CommentSize []byte // Length of file comment
55 Comment []byte // File comment
58 func NewFlatFileInformationFork(fileName string, modifyTime []byte) FlatFileInformationFork {
59 return FlatFileInformationFork{
60 Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
61 TypeSignature: []byte(fileTypeFromFilename(fileName).TypeCode), // TODO: Don't infer types from filename
62 CreatorSignature: []byte(fileTypeFromFilename(fileName).CreatorCode), // TODO: Don't infer types from filename
63 Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
64 PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
65 RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
66 CreateDate: modifyTime, // some filesystems don't support createTime
67 ModifyDate: modifyTime,
68 NameScript: make([]byte, 2), // TODO: What is this?
69 Name: []byte(fileName),
70 CommentSize: []byte{0, 0},
71 Comment: []byte{}, // TODO: implement (maybe?)
75 // DataSize calculates the size of the flat file information fork, which is
76 // 72 bytes for the fixed length fields plus the length of the Name + Comment
77 func (ffif *FlatFileInformationFork) DataSize() []byte {
78 size := make([]byte, 4)
80 // TODO: Can I do math directly on two byte slices?
81 dataSize := len(ffif.Name) + len(ffif.Comment) + 74
83 binary.BigEndian.PutUint32(size, uint32(dataSize))
88 func (ffo *flattenedFileObject) TransferSize() []byte {
89 payloadSize := len(ffo.BinaryMarshal())
90 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
92 transferSize := make([]byte, 4)
93 binary.BigEndian.PutUint32(transferSize, dataSize+uint32(payloadSize))
98 func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
99 size := make([]byte, 2)
100 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
105 type FlatFileDataForkHeader struct {
107 CompressionType [4]byte
112 func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
115 bs := binary.BigEndian.Uint16(nameSize)
119 commentSize := b[nameEnd : nameEnd+2]
120 commentLen := binary.BigEndian.Uint16(commentSize)
122 commentStartPos := int(nameEnd) + 2
123 commentEndPos := int(nameEnd) + 2 + int(commentLen)
125 comment := b[commentStartPos:commentEndPos]
127 ffif.Platform = b[0:4]
128 ffif.TypeSignature = b[4:8]
129 ffif.CreatorSignature = b[8:12]
130 ffif.Flags = b[12:16]
131 ffif.PlatformFlags = b[16:20]
133 ffif.CreateDate = b[52:60]
134 ffif.ModifyDate = b[60:68]
135 ffif.NameScript = b[68:70]
136 ffif.NameSize = b[70:72]
137 ffif.Name = b[72:nameEnd]
138 ffif.CommentSize = b[nameEnd : nameEnd+2]
139 ffif.Comment = comment
144 func (f *flattenedFileObject) BinaryMarshal() []byte {
146 out = append(out, f.FlatFileHeader.Format[:]...)
147 out = append(out, f.FlatFileHeader.Version[:]...)
148 out = append(out, f.FlatFileHeader.RSVD[:]...)
149 out = append(out, f.FlatFileHeader.ForkCount[:]...)
151 out = append(out, []byte("INFO")...)
152 out = append(out, []byte{0, 0, 0, 0}...)
153 out = append(out, make([]byte, 4)...)
154 out = append(out, f.FlatFileInformationFork.DataSize()...)
156 out = append(out, f.FlatFileInformationFork.Platform...)
157 out = append(out, f.FlatFileInformationFork.TypeSignature...)
158 out = append(out, f.FlatFileInformationFork.CreatorSignature...)
159 out = append(out, f.FlatFileInformationFork.Flags...)
160 out = append(out, f.FlatFileInformationFork.PlatformFlags...)
161 out = append(out, f.FlatFileInformationFork.RSVD...)
162 out = append(out, f.FlatFileInformationFork.CreateDate...)
163 out = append(out, f.FlatFileInformationFork.ModifyDate...)
164 out = append(out, f.FlatFileInformationFork.NameScript...)
165 out = append(out, f.FlatFileInformationFork.ReadNameSize()...)
166 out = append(out, f.FlatFileInformationFork.Name...)
167 out = append(out, f.FlatFileInformationFork.CommentSize...)
168 out = append(out, f.FlatFileInformationFork.Comment...)
170 out = append(out, f.FlatFileDataForkHeader.ForkType[:]...)
171 out = append(out, f.FlatFileDataForkHeader.CompressionType[:]...)
172 out = append(out, f.FlatFileDataForkHeader.RSVD[:]...)
173 out = append(out, f.FlatFileDataForkHeader.DataSize[:]...)
178 func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte, dataOffset int64) (*flattenedFileObject, error) {
179 fullFilePath, err := readPath(fileRoot, filePath, fileName)
183 file, err := effectiveFile(fullFilePath)
188 defer func(file *os.File) { _ = file.Close() }(file)
190 fileInfo, err := file.Stat()
195 dataSize := make([]byte, 4)
196 binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size()-dataOffset))
198 mTime := toHotlineTime(fileInfo.ModTime())
200 return &flattenedFileObject{
201 FlatFileHeader: NewFlatFileHeader(),
202 FlatFileInformationFork: NewFlatFileInformationFork(string(fileName), mTime),
203 FlatFileDataForkHeader: FlatFileDataForkHeader{
204 ForkType: [4]byte{0x44, 0x41, 0x54, 0x41}, // "DATA"
205 CompressionType: [4]byte{},
207 DataSize: [4]byte{dataSize[0], dataSize[1], dataSize[2], dataSize[3]},