]> git.r.bdr.sh - rbdr/mobius/blob - hotline/flattened_file_object.go
Add tests and clean up
[rbdr/mobius] / hotline / flattened_file_object.go
1 package hotline
2
3 import (
4 "encoding/binary"
5 "fmt"
6 "os"
7 )
8
9 type flattenedFileObject struct {
10 FlatFileHeader FlatFileHeader
11 FlatFileInformationForkHeader FlatFileInformationForkHeader
12 FlatFileInformationFork FlatFileInformationFork
13 FlatFileDataForkHeader FlatFileDataForkHeader
14 FileData []byte
15 }
16
17 // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
18 type FlatFileHeader struct {
19 Format [4]byte // Always "FILP"
20 Version [2]byte // Always 1
21 RSVD [16]byte // Always empty zeros
22 ForkCount [2]byte // Always 2
23 }
24
25 // NewFlatFileHeader returns a FlatFileHeader struct
26 func NewFlatFileHeader() FlatFileHeader {
27 return FlatFileHeader{
28 Format: [4]byte{0x46, 0x49, 0x4c, 0x50}, // FILP
29 Version: [2]byte{0, 1},
30 RSVD: [16]byte{},
31 ForkCount: [2]byte{0, 2},
32 }
33 }
34
35 // FlatFileInformationForkHeader is the second section of a "Flattened File Object"
36 type FlatFileInformationForkHeader struct {
37 ForkType []byte // Always "INFO"
38 CompressionType []byte // Always 0; Compression was never implemented in the Hotline protocol
39 RSVD []byte // Always zeros
40 DataSize []byte // Size of the flat file information fork
41 }
42
43 type FlatFileInformationFork struct {
44 Platform []byte // Operating System used. ("AMAC" or "MWIN")
45 TypeSignature []byte // File type signature
46 CreatorSignature []byte // File creator signature
47 Flags []byte
48 PlatformFlags []byte
49 RSVD []byte
50 CreateDate []byte
51 ModifyDate []byte
52 NameScript []byte // TODO: what is this?
53 NameSize []byte // Length of file name (Maximum 128 characters)
54 Name []byte // File name
55 CommentSize []byte // Length of file comment
56 Comment []byte // File comment
57 }
58
59 func NewFlatFileInformationFork(fileName string) FlatFileInformationFork {
60 return FlatFileInformationFork{
61 Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
62 TypeSignature: []byte(fileTypeFromFilename(fileName)), // TODO: Don't infer types from filename
63 CreatorSignature: []byte(fileCreatorFromFilename(fileName)), // TODO: Don't infer types from filename
64 Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
65 PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
66 RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
67 CreateDate: []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}, // TODO: implement
68 ModifyDate: []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}, // TODO: implement
69 NameScript: make([]byte, 2), // TODO: What is this?
70 Name: []byte(fileName),
71 Comment: []byte("TODO"), // TODO: implement (maybe?)
72 }
73 }
74
75 // Size of the flat file information fork, which is the fixed size of 72 bytes
76 // plus the number of bytes in the FileName
77 // TODO: plus the size of the Comment!
78 func (ffif FlatFileInformationFork) DataSize() []byte {
79 size := make([]byte, 4)
80 nameLen := len(ffif.Name)
81 //TODO: Can I do math directly on two byte slices?
82 dataSize := nameLen + 74
83
84 binary.BigEndian.PutUint32(size, uint32(dataSize))
85
86 return size
87 }
88
89 func (ffo flattenedFileObject) TransferSize() []byte {
90 payloadSize := len(ffo.Payload())
91 dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize)
92
93 transferSize := make([]byte, 4)
94 binary.BigEndian.PutUint32(transferSize, dataSize+uint32(payloadSize))
95
96 return transferSize
97 }
98
99 func (ffif FlatFileInformationFork) ReadNameSize() []byte {
100 size := make([]byte, 2)
101 binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
102
103 return size
104 }
105
106 type FlatFileDataForkHeader struct {
107 ForkType []byte
108 CompressionType []byte
109 RSVD []byte
110 DataSize []byte
111 }
112
113 func NewFlatFileDataForkHeader() FlatFileDataForkHeader {
114 return FlatFileDataForkHeader{
115 ForkType: []byte("DATA"),
116 CompressionType: []byte{0, 0, 0, 0},
117 RSVD: []byte{0, 0, 0, 0},
118 // DataSize: []byte{0, 0, 0x03, 0xc3},
119 }
120 }
121
122 // ReadFlattenedFileObject parses a byte slice into a flattenedFileObject
123 func ReadFlattenedFileObject(bytes []byte) flattenedFileObject {
124 nameSize := bytes[110:112]
125 bs := binary.BigEndian.Uint16(nameSize)
126
127 nameEnd := 112 + bs
128
129 commentSize := bytes[nameEnd : nameEnd+2]
130 commentLen := binary.BigEndian.Uint16(commentSize)
131
132 commentStartPos := int(nameEnd) + 2
133 commentEndPos := int(nameEnd) + 2 + int(commentLen)
134
135 comment := bytes[commentStartPos:commentEndPos]
136
137 //dataSizeField := bytes[nameEnd+14+commentLen : nameEnd+18+commentLen]
138 //dataSize := binary.BigEndian.Uint32(dataSizeField)
139
140 ffo := flattenedFileObject{
141 FlatFileHeader: NewFlatFileHeader(),
142 FlatFileInformationForkHeader: FlatFileInformationForkHeader{
143 ForkType: bytes[24:28],
144 CompressionType: bytes[28:32],
145 RSVD: bytes[32:36],
146 DataSize: bytes[36:40],
147 },
148 FlatFileInformationFork: FlatFileInformationFork{
149 Platform: bytes[40:44],
150 TypeSignature: bytes[44:48],
151 CreatorSignature: bytes[48:52],
152 Flags: bytes[52:56],
153 PlatformFlags: bytes[56:60],
154 RSVD: bytes[60:92],
155 CreateDate: bytes[92:100],
156 ModifyDate: bytes[100:108],
157 NameScript: bytes[108:110],
158 NameSize: bytes[110:112],
159 Name: bytes[112:nameEnd],
160 CommentSize: bytes[nameEnd : nameEnd+2],
161 Comment: comment,
162 },
163 FlatFileDataForkHeader: FlatFileDataForkHeader{
164 ForkType: bytes[commentEndPos : commentEndPos+4],
165 CompressionType: bytes[commentEndPos+4 : commentEndPos+8],
166 RSVD: bytes[commentEndPos+8 : commentEndPos+12],
167 DataSize: bytes[commentEndPos+12 : commentEndPos+16],
168 },
169 }
170
171 return ffo
172 }
173
174 func (f flattenedFileObject) Payload() []byte {
175 var out []byte
176 out = append(out, f.FlatFileHeader.Format[:]...)
177 out = append(out, f.FlatFileHeader.Version[:]...)
178 out = append(out, f.FlatFileHeader.RSVD[:]...)
179 out = append(out, f.FlatFileHeader.ForkCount[:]...)
180
181 out = append(out, []byte("INFO")...)
182 out = append(out, []byte{0, 0, 0, 0}...)
183 out = append(out, make([]byte, 4)...)
184 out = append(out, f.FlatFileInformationFork.DataSize()...)
185
186 out = append(out, f.FlatFileInformationFork.Platform...)
187 out = append(out, f.FlatFileInformationFork.TypeSignature...)
188 out = append(out, f.FlatFileInformationFork.CreatorSignature...)
189 out = append(out, f.FlatFileInformationFork.Flags...)
190 out = append(out, f.FlatFileInformationFork.PlatformFlags...)
191 out = append(out, f.FlatFileInformationFork.RSVD...)
192 out = append(out, f.FlatFileInformationFork.CreateDate...)
193 out = append(out, f.FlatFileInformationFork.ModifyDate...)
194 out = append(out, f.FlatFileInformationFork.NameScript...)
195 out = append(out, f.FlatFileInformationFork.ReadNameSize()...)
196 out = append(out, f.FlatFileInformationFork.Name...)
197
198 // TODO: Implement commentlen and comment field
199 out = append(out, []byte{0, 0}...)
200
201 out = append(out, f.FlatFileDataForkHeader.ForkType...)
202 out = append(out, f.FlatFileDataForkHeader.CompressionType...)
203 out = append(out, f.FlatFileDataForkHeader.RSVD...)
204 out = append(out, f.FlatFileDataForkHeader.DataSize...)
205
206 return out
207 }
208
209 func NewFlattenedFileObject(filePath, fileName string) (*flattenedFileObject, error) {
210 file, err := os.Open(fmt.Sprintf("%v/%v", filePath, fileName))
211 if err != nil {
212 return nil, err
213 }
214 defer file.Close()
215
216 fileInfo, err := file.Stat()
217 if err != nil {
218 return nil, err
219 }
220
221 dataSize := make([]byte, 4)
222 binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size()))
223
224 return &flattenedFileObject{
225 FlatFileHeader: NewFlatFileHeader(),
226 FlatFileInformationFork: NewFlatFileInformationFork(fileName),
227 FlatFileDataForkHeader: FlatFileDataForkHeader{
228 ForkType: []byte("DATA"),
229 CompressionType: []byte{0, 0, 0, 0},
230 RSVD: []byte{0, 0, 0, 0},
231 DataSize: dataSize,
232 },
233 }, nil
234 }