Last active
July 19, 2020 18:53
-
-
Save GXTX/fb36c2b1e75751dde53f53dd0fcd0775 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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