-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Duplicated from github.com/galaco/lambda-core
- Loading branch information
Showing
10 changed files
with
382 additions
and
2 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,23 @@ | ||
# filesystem | ||
Source engine filesystem manager | ||
[![GoDoc](https://godoc.org/github.com/galaco/lambda-core?status.svg)](https://godoc.org/github.com/galaco/lambda-core) | ||
[![Go report card](https://goreportcard.com/badge/github.com/galaco/lambda-core)](hhttps://goreportcard.com/report/github.com/galaco/lambda-core) | ||
[![GolangCI](https://golangci.com/badges/github.com/galaco/lambda-core.svg)](https://golangci.com/r/github.com/golang-source-engine) | ||
[![codecov](https://codecov.io/gh/golang-source-engine/branch/master/graph/badge.svg)](https://codecov.io/gh/golang-source-engine) | ||
[![CircleCI](https://circleci.com/gh/golang-source-engine.svg?style=svg)](https://circleci.com/gh/golang-source-engine) | ||
|
||
# Filesystem | ||
|
||
> A filesystem utility for reading Source engine game structures. | ||
Source Engine is a little annoying in that there are potentially unlimited possible | ||
locations that engine resources can be located. Filesystem provides a way to register | ||
and organise any potential resource path or filesystem, while preserving filesystem type | ||
search priority. | ||
|
||
A filesystem can either be manually defined, or created from a GameInfo.txt-derived KeyValues. | ||
|
||
### Features | ||
* Supports local directories | ||
* Supports VPK's | ||
* Supports BSP Pakfile | ||
* Respects Source Engines search priority (pakfile->local directory->vpk) | ||
* A ready to use Filesystem can be constructed from GameInfo.txt definitions |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package filesystem | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// FileNotFoundError | ||
type FileNotFoundError struct { | ||
fileName string | ||
} | ||
|
||
// Error | ||
func (err FileNotFoundError) Error() string { | ||
return fmt.Sprintf("%s not found in filesystem", err.fileName) | ||
} | ||
|
||
// NewFileNotFoundError | ||
func NewFileNotFoundError(filename string) *FileNotFoundError { | ||
return &FileNotFoundError{ | ||
fileName: filename, | ||
} | ||
} | ||
|
||
// InvalidResourcePathCollectionError | ||
type InvalidResourcePathCollectionError struct { | ||
paths []string | ||
} | ||
|
||
// Error will return a list of paths that could not be added. | ||
// This list is a pipe-separated(|) string | ||
func (err InvalidResourcePathCollectionError) Error() string { | ||
msg := "" | ||
for _,p := range err.paths { | ||
msg += p + "|" | ||
} | ||
return strings.Trim(msg, "|") | ||
} | ||
|
||
// AddPath adds a new path to this error colleciton | ||
func (err InvalidResourcePathCollectionError) AddPath(path string) { | ||
err.paths = append(err.paths, path) | ||
} | ||
|
||
// NewInvalidResourcePathCollectionError | ||
func NewInvalidResourcePathCollectionError() *InvalidResourcePathCollectionError { | ||
return &InvalidResourcePathCollectionError{ | ||
paths: make([]string, 0), | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package filesystem | ||
|
||
import ( | ||
"bytes" | ||
"github.com/galaco/bsp/lumps" | ||
"github.com/galaco/vpk2" | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// FileSystem | ||
type FileSystem struct { | ||
gameVPKs map[string]vpk.VPK | ||
localDirectories []string | ||
pakFile *lumps.Pakfile | ||
} | ||
|
||
// NewFileSystem returns a new filesystem | ||
func NewFileSystem() *FileSystem { | ||
return &FileSystem{ | ||
gameVPKs: map[string]vpk.VPK{}, | ||
localDirectories: make([]string, 0), | ||
pakFile: nil, | ||
} | ||
} | ||
|
||
// PakFile returns loaded pakfile | ||
// There can only be 1 registered pakfile at once. | ||
func (fs *FileSystem) PakFile() *lumps.Pakfile { | ||
return fs.pakFile | ||
} | ||
|
||
// RegisterVpk registers a vpk package as a valid | ||
// asset directory | ||
func (fs *FileSystem) RegisterVpk(path string, vpkFile *vpk.VPK) { | ||
fs.gameVPKs[path] = *vpkFile | ||
} | ||
|
||
func (fs *FileSystem) UnregisterVpk(path string) { | ||
for key := range fs.gameVPKs { | ||
if key == path { | ||
delete(fs.gameVPKs, key) | ||
} | ||
} | ||
} | ||
|
||
// RegisterLocalDirectory register a filesystem path as a valid | ||
// asset directory | ||
func (fs *FileSystem) RegisterLocalDirectory(directory string) { | ||
fs.localDirectories = append(fs.localDirectories, directory) | ||
} | ||
|
||
func (fs *FileSystem) UnregisterLocalDirectory(directory string) { | ||
for idx, dir := range fs.localDirectories { | ||
if dir == directory { | ||
if len(fs.localDirectories) == 1 { | ||
fs.localDirectories = make([]string, 0) | ||
return | ||
} | ||
fs.localDirectories = append(fs.localDirectories[:idx], fs.localDirectories[idx+1:]...) | ||
} | ||
} | ||
} | ||
|
||
// RegisterPakFile Set a pakfile to be used as an asset directory. | ||
// This would normally be called during each map load | ||
func (fs *FileSystem) RegisterPakFile(pakFile *lumps.Pakfile) { | ||
fs.pakFile = pakFile | ||
} | ||
|
||
// UnregisterPakFile removes the current pakfile from | ||
// available search locations | ||
func (fs *FileSystem) UnregisterPakFile() { | ||
fs.pakFile = nil | ||
} | ||
|
||
// EnumerateResourcePaths returns all registered resource paths. | ||
// PakFile is excluded. | ||
func (fs *FileSystem) EnumerateResourcePaths() []string { | ||
list := make([]string, 0) | ||
|
||
for idx := range fs.gameVPKs { | ||
list = append(list, string(idx)) | ||
} | ||
|
||
list = append(list, fs.localDirectories...) | ||
|
||
return list | ||
} | ||
|
||
// GetFile attempts to get stream for filename. | ||
// Search order is Pak->FileSystem->VPK | ||
func (fs *FileSystem) GetFile(filename string) (io.Reader, error) { | ||
// sanitise file | ||
searchPath := NormalisePath(strings.ToLower(filename)) | ||
|
||
// try to read from pakfile first | ||
if fs.pakFile != nil { | ||
f, err := fs.pakFile.GetFile(searchPath) | ||
if err == nil && f != nil && len(f) != 0 { | ||
return bytes.NewReader(f), nil | ||
} | ||
} | ||
|
||
// Fallback to local filesystem | ||
for _, dir := range fs.localDirectories { | ||
if _, err := os.Stat(dir + "\\" + searchPath); os.IsNotExist(err) { | ||
continue | ||
} | ||
file, err := ioutil.ReadFile(dir + searchPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return bytes.NewBuffer(file), nil | ||
} | ||
|
||
// Fall back to game vpk | ||
for _, vfs := range fs.gameVPKs { | ||
entry := vfs.Entry(searchPath) | ||
if entry != nil { | ||
return entry.Open() | ||
} | ||
} | ||
|
||
return nil, NewFileNotFoundError(filename) | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package filesystem | ||
|
||
import "testing" | ||
|
||
func TestGetFile(t *testing.T) { | ||
t.Skip() | ||
} | ||
|
||
func TestRegisterPakfile(t *testing.T) { | ||
t.Skip() | ||
} | ||
|
||
func TestRegisterVpk(t *testing.T) { | ||
t.Skip() | ||
} | ||
|
||
func TestUnregisterVpk(t *testing.T) { | ||
t.Skip() | ||
} | ||
|
||
func TestRegisterLocalDirectory(t *testing.T) { | ||
fs := NewFileSystem() | ||
dir := "foo/bar/baz" | ||
fs.RegisterLocalDirectory(dir) | ||
found := false | ||
for _, path := range fs.localDirectories { | ||
if path == dir { | ||
found = true | ||
break | ||
} | ||
} | ||
if found == false { | ||
t.Error("local filepath was not found in registered paths") | ||
} | ||
} | ||
|
||
func TestUnregisterLocalDirectory(t *testing.T) { | ||
fs := NewFileSystem() | ||
dir := "foo/bar/baz" | ||
fs.RegisterLocalDirectory(dir) | ||
fs.UnregisterLocalDirectory(dir) | ||
found := false | ||
for _, path := range fs.localDirectories { | ||
if path == dir { | ||
found = true | ||
break | ||
} | ||
} | ||
if found == true { | ||
t.Error("local filepath was not found in registered paths") | ||
} | ||
} | ||
|
||
func TestUnregisterPakfile(t *testing.T) { | ||
t.Skip() | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module github.com/golang-source-engine/filesystem | ||
|
||
go 1.13 | ||
|
||
require ( | ||
github.com/galaco/KeyValues v1.3.1 | ||
github.com/galaco/bsp v0.2.1 | ||
github.com/galaco/vpk2 v0.0.0-20181012095330-21e4d1f6c888 | ||
) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package filesystem | ||
|
||
import "strings" | ||
|
||
// NormalisePath ensures that the same filepath format is used for paths, | ||
// regardless of platform. | ||
func NormalisePath(filePath string) string { | ||
return strings.Replace(filePath, "\\", "/", -1) | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package filesystem | ||
|
||
import "testing" | ||
|
||
func TestNormalisePath(t *testing.T) { | ||
path := "foo\\bar\\baz" | ||
expected := "foo/bar/baz" | ||
actual := NormalisePath(path) | ||
if expected != actual { | ||
t.Errorf("incorrect path normalised. Expected %s, but received: %s", expected, actual) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package filesystem | ||
|
||
import ( | ||
"github.com/galaco/KeyValues" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// CreateFilesystemFromGameInfoDefinitions Reads game resource data paths | ||
// from gameinfo.txt | ||
// All games should ship with a gameinfo.txt, but it isn't actually mandatory. | ||
// GameInfo definitions are quite unreliable, there are often bad entries; | ||
// allowInvalidLocations will skip over bad paths, and an error collection | ||
// will be returned will all paths that are invalid. | ||
func CreateFilesystemFromGameInfoDefinitions(basePath string, gameInfo *keyvalues.KeyValue, allowInvalidLocations bool) (*FileSystem, error) { | ||
fs := NewFileSystem() | ||
gameInfoNode, _ := gameInfo.Find("GameInfo") | ||
fsNode, _ := gameInfoNode.Find("FileSystem") | ||
|
||
searchPathsNode, _ := fsNode.Find("SearchPaths") | ||
searchPaths, _ := searchPathsNode.Children() | ||
basePath, _ = filepath.Abs(basePath) | ||
basePath = strings.Replace(basePath, "\\", "/", -1) | ||
|
||
badPathErrorCollection := NewInvalidResourcePathCollectionError() | ||
|
||
for _, searchPath := range searchPaths { | ||
kv := searchPath | ||
path, _ := kv.AsString() | ||
path = strings.Trim(path, " ") | ||
|
||
// Current directory | ||
gameInfoPathRegex := regexp.MustCompile(`(?i)\|gameinfo_path\|`) | ||
if gameInfoPathRegex.MatchString(path) { | ||
path = gameInfoPathRegex.ReplaceAllString(path, basePath+"/") | ||
} | ||
|
||
// Executable directory | ||
allSourceEnginePathsRegex := regexp.MustCompile(`(?i)\|all_source_engine_paths\|`) | ||
if allSourceEnginePathsRegex.MatchString(path) { | ||
path = allSourceEnginePathsRegex.ReplaceAllString(path, basePath+"/../") | ||
} | ||
if strings.Contains(strings.ToLower(kv.Key()), "mod") && !strings.HasPrefix(path, basePath) { | ||
path = basePath + "/../" + path | ||
} | ||
|
||
// Strip vpk extension, then load it | ||
path = strings.Trim(strings.Trim(path, " "), "\"") | ||
if strings.HasSuffix(path, ".vpk") { | ||
path = strings.Replace(path, ".vpk", "", 1) | ||
vpkHandle, err := openVPK(path) | ||
if err != nil { | ||
if !allowInvalidLocations { | ||
return nil, err | ||
} | ||
badPathErrorCollection.AddPath(path) | ||
continue | ||
} | ||
fs.RegisterVpk(path, vpkHandle) | ||
} else { | ||
// wildcard suffixes not useful | ||
if strings.HasSuffix(path, "/*") { | ||
path = strings.Replace(path, "/*", "", -1) | ||
} | ||
fs.RegisterLocalDirectory(path) | ||
} | ||
} | ||
|
||
// A filesystem can be valid, even if some GameInfo defined locations | ||
// were not. | ||
if allowInvalidLocations && len(badPathErrorCollection.paths) > 0 { | ||
return fs, badPathErrorCollection | ||
} | ||
|
||
return fs, nil | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package filesystem | ||
|
||
import "testing" | ||
|
||
func TestRegisterGameResourcePaths(t *testing.T) { | ||
t.Skip() | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package filesystem | ||
|
||
import ( | ||
"github.com/galaco/vpk2" | ||
) | ||
|
||
// openVPK Basic wrapper around vpk library. | ||
// Just opens a multi-part vpk (ver 2 only) | ||
func openVPK(filepath string) (*vpk.VPK, error) { | ||
return vpk.Open(vpk.MultiVPK(filepath)) | ||
} |