OpenDiablo2/d2common/d2loader/loader.go

190 lines
4.8 KiB
Go
Raw Normal View History

package d2loader
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
)
const (
defaultCacheBudget = 1024 * 1024 * 512
defaultCacheEntryWeight = 1
errFmtFileNotFound = "file not found: %s"
)
const (
defaultLanguage = "ENG"
logPrefix = "File Loader"
)
const (
fontToken = d2resource.LanguageFontToken
tableToken = d2resource.LanguageTableToken
)
// NewLoader creates a new loader
func NewLoader(config *d2config.Configuration, l d2util.LogLevel) (*Loader, error) {
loader := &Loader{
config: config,
}
loader.Cache = d2cache.CreateCache(defaultCacheBudget)
loader.Logger = d2util.NewLogger()
loader.Logger.SetPrefix(logPrefix)
loader.Logger.SetLevel(l)
err := loader.initFromConfig()
return loader, err
}
// Loader represents the manager that handles loading and caching assets with the asset Sources
// that have been added
type Loader struct {
config *d2config.Configuration
d2interface.Cache
*d2util.Logger
Sources []asset.Source
}
const (
errConfigFileNotFound = "config file not found"
fmtErrSourceNotFound = `file not found: %s
Please check your config file at %s
Also, verify that the MPQ files exist at %s
Capitalization matters!
`
)
func (l *Loader) initFromConfig() error {
if l.config == nil {
return errors.New(errConfigFileNotFound)
}
for _, mpqName := range l.config.MpqLoadOrder {
cleanDir := filepath.Clean(l.config.MpqPath)
srcPath := filepath.Join(cleanDir, mpqName)
_, err := l.AddSource(srcPath)
if err != nil {
// nolint:stylecheck // we want a multiline error message here..
return fmt.Errorf(fmtErrSourceNotFound, srcPath, l.config.Path(), l.config.MpqPath)
}
}
return nil
}
// Load attempts to load an asset with the given sub-path. The sub-path is relative to the root
// of each asset source root (regardless of the type of asset source)
func (l *Loader) Load(subPath string) (asset.Asset, error) {
lang := defaultLanguage
if l.config != nil {
lang = l.config.Language
}
subPath = filepath.Clean(subPath)
subPath = strings.ReplaceAll(subPath, fontToken, "latin")
subPath = strings.ReplaceAll(subPath, tableToken, lang)
// first, we check the cache for an existing entry
if cached, found := l.Retrieve(subPath); found {
l.Debug(fmt.Sprintf("Retrieved `%s` from cache", subPath))
a := cached.(asset.Asset)
_, err := a.Seek(0, 0)
return a, err
}
// if it isn't in the cache, we check if each source can open the file
for idx := range l.Sources {
source := l.Sources[idx]
// if the source can open the file, then we cache it and return it
loadedAsset, err := source.Open(subPath)
if err != nil {
l.Debug(fmt.Sprintf("Checked `%s`, file not found", source.Path()))
continue
}
srcBase := filepath.Base(source.Path())
l.Info(fmt.Sprintf("from %s, loading %s", srcBase, subPath))
return loadedAsset, l.Insert(subPath, loadedAsset, defaultCacheEntryWeight)
}
return nil, fmt.Errorf(errFmtFileNotFound, subPath)
}
// AddSource adds an asset source with the given path. The path will either resolve to a directory
// or a file on the host filesystem. In the case that it is a file, the file extension is used
// to determine the type of asset source. In the case that the path points to a directory, a
// FileSystemSource will be added.
func (l *Loader) AddSource(path string) (asset.Source, error) {
if l.Sources == nil {
l.Sources = make([]asset.Source, 0)
}
cleanPath := filepath.Clean(path)
info, err := os.Lstat(cleanPath)
if err != nil {
l.Error(err.Error())
return nil, err
}
mode := info.Mode()
sourceType := types.AssetSourceUnknown
if mode.IsDir() {
sourceType = types.AssetSourceFileSystem
}
if mode.IsRegular() {
sourceType = types.CheckSourceType(cleanPath)
}
switch sourceType {
case types.AssetSourceMPQ:
source, err := mpq.NewSource(cleanPath)
if err == nil {
l.Info(fmt.Sprintf("adding MPQ source `%s`", cleanPath))
l.Sources = append(l.Sources, source)
return source, nil
}
case types.AssetSourceFileSystem:
source := &filesystem.Source{
Root: cleanPath,
}
l.Info(fmt.Sprintf("adding filesystem source `%s`", cleanPath))
l.Sources = append(l.Sources, source)
return source, nil
case types.AssetSourceUnknown:
l.Warning(fmt.Sprintf("unknown asset source `%s`", cleanPath))
}
return nil, fmt.Errorf("unknown asset source `%s`", cleanPath)
}