Skip to content

Instantly share code, notes, and snippets.

@GXTX
Last active July 19, 2020 18:53
Show Gist options
  • Save GXTX/fb36c2b1e75751dde53f53dd0fcd0775 to your computer and use it in GitHub Desktop.
Save GXTX/fb36c2b1e75751dde53f53dd0fcd0775 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"os"
"time"
)
const (
Magic string = "tnFS"
Version uint32 = 16
Padding uint32 = 0x00
HeaderLength int64 = 16
DirectorySliceSize int64 = 32
)
type Header struct {
Magic [4]byte
NumberOfFiles uint32
Version uint32 // This is not confirmed, but it's the same in all containers i've come across
DirectoryLength uint32
}
type DirectoryItem struct {
FileOffset uint32
FileSize uint32 // It's possible that fileSize2 is actually compressedSize but the game
FileSize2 uint32 // i've been testing with does not show any differing values
FileNameOffset uint32
Padding [4]byte
FileTime [8]byte
Checksum uint32 // Checksum is calculated by adding all bytes off the file together
}
type Filetime struct {
LowDateTime uint32
HighDateTime uint32
}
func main() {
file, err := os.Open("ETC.tfs")
check(err)
HeaderInformation := readHeader(file)
DirectoryItems := readDirectory(file, HeaderInformation)
// TODO: A better solution would be to seek to the location where DirectoryItems[X] says the name is
// and read until hitting 0x00
firstFile := DirectoryItems[0]
_, err = file.Seek(int64(firstFile.FileNameOffset) + HeaderLength, 0)
fileNameListReadBytes :=
HeaderInformation.DirectoryLength - HeaderInformation.NumberOfFiles * uint32(DirectorySliceSize)
fileListBuffer := make([]byte, fileNameListReadBytes)
_, err = file.Read(fileListBuffer)
FileNameList := bytes.Split(fileListBuffer, []byte{0x00})
createFiles(file, DirectoryItems, FileNameList)
}
// TODO: Look into maybe piping this instead of buffering it into mem?
func createFiles(file *os.File, dictionary []DirectoryItem, filenameList [][]byte) {
// Create the folder & change into it
os.Mkdir("ETC", 0700)
os.Chdir("ETC")
for count, item := range dictionary {
fmt.Printf("Filename: %v | Filesize: %v bytes | FILETIME: %v\n",
string(filenameList[count]), item.FileSize, time.Unix(0, filetimeToNano(item.FileTime)))
file.Seek(int64(item.FileOffset), 0)
fileBuffer := make([]byte, item.FileSize)
file.Read(fileBuffer)
ioutil.WriteFile(string(filenameList[count]), fileBuffer, 0644)
os.Chtimes(string(filenameList[count]),
time.Unix(0, filetimeToNano(item.FileTime)),
time.Unix(0, filetimeToNano(item.FileTime)))
}
return
}
func readHeader(file *os.File) Header {
headerInformation := Header{}
headerBuffer := make([]byte, HeaderLength)
// Make sure we're at the start of the file
file.Seek(0, 0)
file.Read(headerBuffer)
headerBytesBuffer := bytes.NewBuffer(headerBuffer)
binary.Read(headerBytesBuffer, binary.LittleEndian, &headerInformation)
if string(headerInformation.Magic[:]) != Magic || headerInformation.Version != Version {
fmt.Print("Missing magic bytes or version mismatch!\n")
os.Exit(1)
}
return headerInformation
}
func readDirectory(file *os.File, headerInformation Header) []DirectoryItem {
DirectoryItems := []DirectoryItem{}
for directoryNumber := uint32(1); directoryNumber <= headerInformation.NumberOfFiles; directoryNumber++ {
tempDirectoryItem := DirectoryItem{}
directoryBuffer := make([]byte, DirectorySliceSize)
file.Read(directoryBuffer)
directoryBytesBuffer := bytes.NewBuffer(directoryBuffer)
binary.Read(directoryBytesBuffer, binary.LittleEndian, &tempDirectoryItem)
DirectoryItems = append(DirectoryItems, tempDirectoryItem)
}
return DirectoryItems
}
//
// Helpers
//
//https://gobyexample.com/reading-files
func check(e error) {
if e != nil {
panic(e)
}
}
// My version of Go does not have this defined nor does it have ft.Nanoseconds()
// so we must manually convert Windows FILETIME to human readable.
// Taken from https://golang.org/src/syscall/types_windows.go
func filetimeToNano(t [8]byte) int64 {
ft := Filetime{
LowDateTime: binary.LittleEndian.Uint32(t[:4]),
HighDateTime: binary.LittleEndian.Uint32(t[4:]),
}
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
nsec -= 116444736000000000
nsec *= 100
return nsec
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment