Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user