1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-18 14:36:44 -05:00
OpenDiablo2/d2core/d2systems/file_source_resolver.go
gravestench c52c6648dd refactor of d2components, d2systems
Systems now place all of their component factories into a `Components`
member. This improves code readability and makes it clear when we are
dealing specifically with ecs components.

The concrete ComponentFactory instances now have `Add` and `Get`
methods (as opposed to `AddAlpha` or `GetAlpha`). This enforces naming
of component factories as to avoid collisions when embedded in a struct
with other components.

Also, the ComponentFactory interface is embedded directly into the
concrete component factory without a name.
2020-12-08 18:45:00 -08:00

181 lines
4.3 KiB
Go

package d2systems
import (
"os"
"path/filepath"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/gravestench/akara"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
)
const (
logPrefixFileSourceResolver = "ComponentFactory Source Resolver"
)
// FileSourceResolver is responsible for determining if files can be used as a file source.
// If it can, it sets the file up as a source, and the file handle resolver system can
// then use the source to open files.
type FileSourceResolver struct {
akara.BaseSubscriberSystem
*d2util.Logger
filesToCheck *akara.Subscription
Components struct {
File d2components.FileFactory
FileType d2components.FileTypeFactory
FileSource d2components.FileSourceFactory
}
}
// Init initializes the file source resolver, injecting the necessary components into the world
func (m *FileSourceResolver) Init(world *akara.World) {
m.World = world
m.setupLogger()
m.Debug("initializing ...")
m.setupSubscriptions()
m.setupFactories()
m.Debug("... initialization complete!")
}
func (m *FileSourceResolver) setupLogger() {
m.Logger = d2util.NewLogger()
m.SetPrefix(logPrefixFileSourceResolver)
}
func (m *FileSourceResolver) setupSubscriptions() {
m.Debug("setting up component subscriptions")
// subscribe to entities with a file type and file path, but no file source type
filesToCheck := m.NewComponentFilter().
Require(
&d2components.File{},
&d2components.FileType{},
).
Forbid(
&d2components.FileSource{},
).
Build()
m.filesToCheck = m.World.AddSubscription(filesToCheck)
}
func (m *FileSourceResolver) setupFactories() {
m.Debug("setting up component factories")
m.InjectComponent(&d2components.File{}, &m.Components.File.ComponentFactory)
m.InjectComponent(&d2components.FileType{}, &m.Components.FileType.ComponentFactory)
m.InjectComponent(&d2components.FileSource{}, &m.Components.FileSource.ComponentFactory)
}
// Update iterates over entities from its subscription, and checks if it can be used as a file source
func (m *FileSourceResolver) Update() {
for _, eid := range m.filesToCheck.GetEntities() {
m.processSourceEntity(eid)
}
}
func (m *FileSourceResolver) processSourceEntity(id akara.EID) {
fp, found := m.Components.File.Get(id)
if !found {
return
}
ft, found := m.Components.FileType.Get(id)
if !found {
return
}
switch ft.Type {
case d2enum.FileTypeUnknown:
m.Errorf("unknown file type for file `%s`", fp.Path)
return
case d2enum.FileTypeMPQ:
instance, err := m.makeMpqSource(fp.Path)
if err != nil {
ft.Type = d2enum.FileTypeUnknown
break
}
m.Components.FileSource.Add(id).AbstractSource = instance
m.Debugf("adding MPQ source: `%s`", fp.Path)
case d2enum.FileTypeDirectory:
m.Components.FileSource.Add(id).AbstractSource = m.makeFileSystemSource(fp.Path)
m.Debugf("adding FILESYSTEM source: `%s`", fp.Path)
}
}
// filesystem source
func (m *FileSourceResolver) makeFileSystemSource(path string) d2components.AbstractSource {
return &fsSource{rootDir: path}
}
type fsSource struct {
rootDir string
}
func (s *fsSource) Open(path *d2components.File) (d2interface.DataStream, error) {
fileData, err := os.Open(s.fullPath(path.Path))
if err != nil {
return nil, err
}
return fileData, nil
}
func (s *fsSource) fullPath(path string) string {
return filepath.Clean(filepath.Join(s.rootDir, path))
}
func (s *fsSource) Path() string {
return filepath.Clean(s.rootDir)
}
// mpq source
func (m *FileSourceResolver) makeMpqSource(path string) (d2components.AbstractSource, error) {
mpq, err := d2mpq.Load(path)
if err != nil {
return nil, err
}
return &mpqSource{mpq: mpq}, nil
}
type mpqSource struct {
mpq d2interface.Archive
}
func (s *mpqSource) Open(path *d2components.File) (d2interface.DataStream, error) {
fileData, err := s.mpq.ReadFileStream(s.cleanMpqPath(path.Path))
if err != nil {
return nil, err
}
return fileData, nil
}
func (s *mpqSource) cleanMpqPath(path string) string {
path = strings.ReplaceAll(path, "/", "\\")
if string(path[0]) == "\\" {
path = path[1:]
}
return path
}
func (s *mpqSource) Path() string {
return filepath.Clean(s.mpq.Path())
}