import (
"encoding/binary"
- "fmt"
"os"
)
// FlatFileHeader is the first section of a "Flattened File Object". All fields have static values.
type FlatFileHeader struct {
- Format []byte // Always "FILP"
- Version []byte // Always 1
- RSVD []byte // Always empty zeros
- ForkCount []byte // Always 2
+ Format [4]byte // Always "FILP"
+ Version [2]byte // Always 1
+ RSVD [16]byte // Always empty zeros
+ ForkCount [2]byte // Number of forks
}
// NewFlatFileHeader returns a FlatFileHeader struct
func NewFlatFileHeader() FlatFileHeader {
return FlatFileHeader{
- Format: []byte("FILP"),
- Version: []byte{0, 1},
- RSVD: make([]byte, 16),
- ForkCount: []byte{0, 2},
+ Format: [4]byte{0x46, 0x49, 0x4c, 0x50}, // FILP
+ Version: [2]byte{0, 1},
+ RSVD: [16]byte{},
+ ForkCount: [2]byte{0, 2},
}
}
// FlatFileInformationForkHeader is the second section of a "Flattened File Object"
type FlatFileInformationForkHeader struct {
- ForkType []byte // Always "INFO"
- CompressionType []byte // Always 0; Compression was never implemented in the Hotline protocol
- RSVD []byte // Always zeros
- DataSize []byte // Size of the flat file information fork
+ ForkType [4]byte // Always "INFO"
+ CompressionType [4]byte // Always 0; Compression was never implemented in the Hotline protocol
+ RSVD [4]byte // Always zeros
+ DataSize [4]byte // Size of the flat file information fork
}
type FlatFileInformationFork struct {
Comment []byte // File comment
}
-func NewFlatFileInformationFork(fileName string) FlatFileInformationFork {
+func NewFlatFileInformationFork(fileName string, modifyTime []byte) FlatFileInformationFork {
return FlatFileInformationFork{
- Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
- TypeSignature: []byte(fileTypeFromFilename(fileName)), // TODO: Don't infer types from filename
- CreatorSignature: []byte(fileCreatorFromFilename(fileName)), // TODO: Don't infer types from filename
- Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
- PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
- RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
- CreateDate: []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}, // TODO: implement
- ModifyDate: []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}, // TODO: implement
- NameScript: make([]byte, 2), // TODO: What is this?
+ Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?)
+ TypeSignature: []byte(fileTypeFromFilename(fileName).TypeCode), // TODO: Don't infer types from filename
+ CreatorSignature: []byte(fileTypeFromFilename(fileName).CreatorCode), // TODO: Don't infer types from filename
+ Flags: []byte{0, 0, 0, 0}, // TODO: What is this?
+ PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this?
+ RSVD: make([]byte, 32), // Unimplemented in Hotline Protocol
+ CreateDate: modifyTime, // some filesystems don't support createTime
+ ModifyDate: modifyTime,
+ NameScript: make([]byte, 2), // TODO: What is this?
Name: []byte(fileName),
- Comment: []byte("TODO"), // TODO: implement (maybe?)
+ CommentSize: []byte{0, 0},
+ Comment: []byte{}, // TODO: implement (maybe?)
}
}
-// Size of the flat file information fork, which is the fixed size of 72 bytes
-// plus the number of bytes in the FileName
-// TODO: plus the size of the Comment!
-func (ffif FlatFileInformationFork) DataSize() []byte {
+// DataSize calculates the size of the flat file information fork, which is
+// 72 bytes for the fixed length fields plus the length of the Name + Comment
+func (ffif *FlatFileInformationFork) DataSize() []byte {
size := make([]byte, 4)
- nameLen := len(ffif.Name)
- //TODO: Can I do math directly on two byte slices?
- dataSize := nameLen + 74
+
+ // TODO: Can I do math directly on two byte slices?
+ dataSize := len(ffif.Name) + len(ffif.Comment) + 74
binary.BigEndian.PutUint32(size, uint32(dataSize))
return size
}
-func (ffo flattenedFileObject) TransferSize() []byte {
- payloadSize := len(ffo.Payload())
- dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize)
+func (ffo *flattenedFileObject) TransferSize() []byte {
+ payloadSize := len(ffo.BinaryMarshal())
+ dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:])
transferSize := make([]byte, 4)
binary.BigEndian.PutUint32(transferSize, dataSize+uint32(payloadSize))
return transferSize
}
-func (ffif FlatFileInformationFork) ReadNameSize() []byte {
+func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
size := make([]byte, 2)
binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
}
type FlatFileDataForkHeader struct {
- ForkType []byte
- CompressionType []byte
- RSVD []byte
- DataSize []byte
+ ForkType [4]byte
+ CompressionType [4]byte
+ RSVD [4]byte
+ DataSize [4]byte
}
-func NewFlatFileDataForkHeader() FlatFileDataForkHeader {
- return FlatFileDataForkHeader{
- ForkType: []byte("DATA"),
- CompressionType: []byte{0, 0, 0, 0},
- RSVD: []byte{0, 0, 0, 0},
- // DataSize: []byte{0, 0, 0x03, 0xc3},
- }
-}
+func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error {
-// ReadFlattenedFileObject parses a byte slice into a flattenedFileObject
-func ReadFlattenedFileObject(bytes []byte) flattenedFileObject {
- nameSize := bytes[110:112]
+ nameSize := b[70:72]
bs := binary.BigEndian.Uint16(nameSize)
- nameEnd := 112 + bs
+ nameEnd := 72 + bs
- commentSize := bytes[nameEnd : nameEnd+2]
+ commentSize := b[nameEnd : nameEnd+2]
commentLen := binary.BigEndian.Uint16(commentSize)
commentStartPos := int(nameEnd) + 2
commentEndPos := int(nameEnd) + 2 + int(commentLen)
- comment := bytes[commentStartPos:commentEndPos]
-
- //dataSizeField := bytes[nameEnd+14+commentLen : nameEnd+18+commentLen]
- //dataSize := binary.BigEndian.Uint32(dataSizeField)
-
- ffo := flattenedFileObject{
- FlatFileHeader: FlatFileHeader{
- Format: bytes[0:4],
- Version: bytes[4:6],
- RSVD: bytes[6:22],
- ForkCount: bytes[22:24],
- },
- FlatFileInformationForkHeader: FlatFileInformationForkHeader{
- ForkType: bytes[24:28],
- CompressionType: bytes[28:32],
- RSVD: bytes[32:36],
- DataSize: bytes[36:40],
- },
- FlatFileInformationFork: FlatFileInformationFork{
- Platform: bytes[40:44],
- TypeSignature: bytes[44:48],
- CreatorSignature: bytes[48:52],
- Flags: bytes[52:56],
- PlatformFlags: bytes[56:60],
- RSVD: bytes[60:92],
- CreateDate: bytes[92:100],
- ModifyDate: bytes[100:108],
- NameScript: bytes[108:110],
- NameSize: bytes[110:112],
- Name: bytes[112:nameEnd],
- CommentSize: bytes[nameEnd : nameEnd+2],
- Comment: comment,
- },
- FlatFileDataForkHeader: FlatFileDataForkHeader{
- ForkType: bytes[commentEndPos : commentEndPos+4],
- CompressionType: bytes[commentEndPos+4 : commentEndPos+8],
- RSVD: bytes[commentEndPos+8 : commentEndPos+12],
- DataSize: bytes[commentEndPos+12 : commentEndPos+16],
- },
- }
-
- return ffo
+ comment := b[commentStartPos:commentEndPos]
+
+ ffif.Platform = b[0:4]
+ ffif.TypeSignature = b[4:8]
+ ffif.CreatorSignature = b[8:12]
+ ffif.Flags = b[12:16]
+ ffif.PlatformFlags = b[16:20]
+ ffif.RSVD = b[20:52]
+ ffif.CreateDate = b[52:60]
+ ffif.ModifyDate = b[60:68]
+ ffif.NameScript = b[68:70]
+ ffif.NameSize = b[70:72]
+ ffif.Name = b[72:nameEnd]
+ ffif.CommentSize = b[nameEnd : nameEnd+2]
+ ffif.Comment = comment
+
+ return nil
}
-func (f flattenedFileObject) Payload() []byte {
+func (f *flattenedFileObject) BinaryMarshal() []byte {
var out []byte
- out = append(out, f.FlatFileHeader.Format...)
- out = append(out, f.FlatFileHeader.Version...)
- out = append(out, f.FlatFileHeader.RSVD...)
- out = append(out, f.FlatFileHeader.ForkCount...)
+ out = append(out, f.FlatFileHeader.Format[:]...)
+ out = append(out, f.FlatFileHeader.Version[:]...)
+ out = append(out, f.FlatFileHeader.RSVD[:]...)
+ out = append(out, f.FlatFileHeader.ForkCount[:]...)
out = append(out, []byte("INFO")...)
out = append(out, []byte{0, 0, 0, 0}...)
out = append(out, f.FlatFileInformationFork.NameScript...)
out = append(out, f.FlatFileInformationFork.ReadNameSize()...)
out = append(out, f.FlatFileInformationFork.Name...)
+ out = append(out, f.FlatFileInformationFork.CommentSize...)
+ out = append(out, f.FlatFileInformationFork.Comment...)
- // TODO: Implement commentlen and comment field
- out = append(out, []byte{0, 0}...)
-
- out = append(out, f.FlatFileDataForkHeader.ForkType...)
- out = append(out, f.FlatFileDataForkHeader.CompressionType...)
- out = append(out, f.FlatFileDataForkHeader.RSVD...)
- out = append(out, f.FlatFileDataForkHeader.DataSize...)
+ out = append(out, f.FlatFileDataForkHeader.ForkType[:]...)
+ out = append(out, f.FlatFileDataForkHeader.CompressionType[:]...)
+ out = append(out, f.FlatFileDataForkHeader.RSVD[:]...)
+ out = append(out, f.FlatFileDataForkHeader.DataSize[:]...)
return out
}
-func NewFlattenedFileObject(filePath string, fileName string) (flattenedFileObject, error) {
- file, err := os.Open(fmt.Sprintf("%v/%v", filePath, fileName))
+func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte, dataOffset int64) (*flattenedFileObject, error) {
+ fullFilePath, err := readPath(fileRoot, filePath, fileName)
if err != nil {
- return flattenedFileObject{}, err
+ return nil, err
+ }
+ file, err := effectiveFile(fullFilePath)
+ if err != nil {
+ return nil, err
}
- defer file.Close()
+
+ defer func(file *os.File) { _ = file.Close() }(file)
fileInfo, err := file.Stat()
if err != nil {
- return flattenedFileObject{}, err
+ return nil, err
}
dataSize := make([]byte, 4)
- binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size()))
+ binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size()-dataOffset))
+
+ mTime := toHotlineTime(fileInfo.ModTime())
- return flattenedFileObject{
+ return &flattenedFileObject{
FlatFileHeader: NewFlatFileHeader(),
- FlatFileInformationFork: NewFlatFileInformationFork(fileName),
+ FlatFileInformationFork: NewFlatFileInformationFork(string(fileName), mTime),
FlatFileDataForkHeader: FlatFileDataForkHeader{
- ForkType: []byte("DATA"),
- CompressionType: []byte{0, 0, 0, 0},
- RSVD: []byte{0, 0, 0, 0},
- DataSize: dataSize,
+ ForkType: [4]byte{0x44, 0x41, 0x54, 0x41}, // "DATA"
+ CompressionType: [4]byte{},
+ RSVD: [4]byte{},
+ DataSize: [4]byte{dataSize[0], dataSize[1], dataSize[2], dataSize[3]},
},
}, nil
}