209 lines
4.8 KiB
Go
209 lines
4.8 KiB
Go
package world
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/inkyblackness/hacked/ss1/content/archive"
|
|
"github.com/inkyblackness/hacked/ss1/content/object"
|
|
"github.com/inkyblackness/hacked/ss1/content/texture"
|
|
"github.com/inkyblackness/hacked/ss1/resource"
|
|
"github.com/inkyblackness/hacked/ss1/resource/lgres"
|
|
"github.com/inkyblackness/hacked/ss1/serial"
|
|
"github.com/inkyblackness/hacked/ss1/world/ids"
|
|
)
|
|
|
|
var fileAllowlist = ids.FilenameList{
|
|
ids.Archive,
|
|
ids.CybStrng,
|
|
ids.CitALog,
|
|
ids.CitBark,
|
|
ids.CitMat,
|
|
ids.DigiFX,
|
|
ids.GamePal,
|
|
ids.GameScr,
|
|
ids.MfdArt,
|
|
ids.Obj3D,
|
|
ids.ObjArt,
|
|
ids.ObjArt2,
|
|
ids.ObjArt3,
|
|
ids.SideArt,
|
|
ids.Splash,
|
|
ids.SvgaDeth,
|
|
ids.SvgaEnd,
|
|
ids.SvgaIntr,
|
|
ids.Texture,
|
|
ids.VidMail,
|
|
}
|
|
|
|
type fileLoader struct {
|
|
resultMutex sync.Mutex
|
|
|
|
allowZips bool
|
|
|
|
result FileLoadResult
|
|
}
|
|
|
|
// FileLoadResult contains all the information of a LoadFiles attempt.
|
|
type FileLoadResult struct {
|
|
FailedFiles int
|
|
Savegames map[FileLocation]resource.Viewer
|
|
Resources map[FileLocation]resource.Viewer
|
|
|
|
ObjectProperties object.PropertiesTable
|
|
TextureProperties texture.PropertiesList
|
|
}
|
|
|
|
// LoadFiles attempts to load compatible files from the given set of filenames.
|
|
func LoadFiles(allowZips bool, names []string) FileLoadResult {
|
|
loader := fileLoader{
|
|
allowZips: allowZips,
|
|
result: FileLoadResult{
|
|
Resources: make(map[FileLocation]resource.Viewer),
|
|
Savegames: make(map[FileLocation]resource.Viewer),
|
|
},
|
|
}
|
|
loader.loadAll(names)
|
|
return loader.result
|
|
}
|
|
|
|
func (loader *fileLoader) loadAll(names []string) {
|
|
loader.loadList(names, len(names) == 1)
|
|
}
|
|
|
|
func (loader *fileLoader) loadList(names []string, isOnlyRequestedFile bool) {
|
|
var wg sync.WaitGroup
|
|
|
|
for _, name := range names {
|
|
wg.Add(1)
|
|
go func(name string) {
|
|
defer wg.Done()
|
|
loader.load(name, isOnlyRequestedFile)
|
|
}(name)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func (loader *fileLoader) load(name string, isOnlyRequestedFile bool) {
|
|
fileInfo, err := os.Stat(name)
|
|
if err != nil {
|
|
loader.markFailedFile()
|
|
return
|
|
}
|
|
file, err := os.Open(name)
|
|
if err != nil {
|
|
loader.markFailedFile()
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = file.Close() // nolint: gas
|
|
}()
|
|
|
|
if fileInfo.IsDir() {
|
|
if isOnlyRequestedFile {
|
|
subNames, _ := file.Readdirnames(0)
|
|
joinedSubNames := make([]string, len(subNames))
|
|
for index, subName := range subNames {
|
|
joinedSubNames[index] = filepath.Join(name, subName)
|
|
}
|
|
loader.loadList(joinedSubNames, false)
|
|
}
|
|
} else {
|
|
tryDirect := true
|
|
if loader.allowZips && isOnlyRequestedFile {
|
|
tryDirect = false
|
|
zipReader, err := zip.NewReader(file, fileInfo.Size())
|
|
if err != nil {
|
|
_, _ = file.Seek(0, io.SeekStart)
|
|
tryDirect = true
|
|
} else {
|
|
loader.loadFileArchive(zipReader)
|
|
}
|
|
}
|
|
|
|
if tryDirect {
|
|
loader.loadFile(name, isOnlyRequestedFile, file)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (loader *fileLoader) loadFileArchive(zipReader *zip.Reader) {
|
|
var wg sync.WaitGroup
|
|
|
|
for _, file := range zipReader.File {
|
|
wg.Add(1)
|
|
go func(entry *zip.File) {
|
|
defer wg.Done()
|
|
entryFile, err := entry.Open()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
_ = entryFile.Close()
|
|
}()
|
|
|
|
loader.loadFile(entry.Name, false, entryFile)
|
|
}(file)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func (loader *fileLoader) loadFile(name string, isOnlyStagedFile bool, file io.Reader) {
|
|
fileData, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
loader.markFailedFile()
|
|
}
|
|
|
|
reader, err := lgres.ReaderFrom(bytes.NewReader(fileData))
|
|
filename := filepath.Base(name)
|
|
if (err == nil) && (isOnlyStagedFile || fileAllowlist.Matches(filename)) {
|
|
location := FileLocation{DirPath: filepath.Dir(name), Name: filename}
|
|
loader.modify(func() {
|
|
if stateView, stateErr := reader.View(ids.GameState); (stateErr == nil) && archive.IsSavegame(stateView) {
|
|
loader.result.Savegames[location] = reader
|
|
} else {
|
|
loader.result.Resources[location] = reader
|
|
}
|
|
})
|
|
}
|
|
if strings.ToLower(filename) == ObjectPropertiesFilename {
|
|
decoder := serial.NewDecoder(bytes.NewReader(fileData))
|
|
properties := object.StandardPropertiesTable()
|
|
properties.Code(decoder)
|
|
err = decoder.FirstError()
|
|
if err == nil {
|
|
loader.modify(func() { loader.result.ObjectProperties = properties })
|
|
}
|
|
}
|
|
if strings.ToLower(filename) == TexturePropertiesFilename && (len(fileData) > 4) {
|
|
decoder := serial.NewDecoder(bytes.NewReader(fileData))
|
|
entryCount := (len(fileData) - 4) / texture.PropertiesSize
|
|
properties := make(texture.PropertiesList, entryCount)
|
|
properties.Code(decoder)
|
|
err = decoder.FirstError()
|
|
if err == nil {
|
|
loader.modify(func() { loader.result.TextureProperties = properties })
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
loader.markFailedFile()
|
|
}
|
|
}
|
|
|
|
func (loader *fileLoader) markFailedFile() {
|
|
loader.modify(func() { loader.result.FailedFiles++ })
|
|
}
|
|
|
|
func (loader *fileLoader) modify(modifier func()) {
|
|
loader.resultMutex.Lock()
|
|
defer loader.resultMutex.Unlock()
|
|
modifier()
|
|
}
|