Initial sow-tools import
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/gff"
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
||||
)
|
||||
|
||||
type ExtractResult struct {
|
||||
ModulePath string
|
||||
HAKPaths []string
|
||||
Written int
|
||||
Skipped int
|
||||
}
|
||||
|
||||
func Extract(p *project.Project) (ExtractResult, error) {
|
||||
modulePath := filepath.Join(p.BuildDir(), p.Config.Module.ResRef+".mod")
|
||||
input, err := os.Open(modulePath)
|
||||
if err != nil {
|
||||
return ExtractResult{}, fmt.Errorf("open module archive: %w", err)
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
archive, err := erf.Read(input)
|
||||
if err != nil {
|
||||
return ExtractResult{}, fmt.Errorf("read module archive: %w", err)
|
||||
}
|
||||
|
||||
var result ExtractResult
|
||||
result.ModulePath = modulePath
|
||||
var failures []error
|
||||
|
||||
written, skipped, errs := extractArchiveResources(p, archive)
|
||||
result.Written += written
|
||||
result.Skipped += skipped
|
||||
failures = append(failures, errs...)
|
||||
|
||||
hakPaths, err := filepath.Glob(filepath.Join(p.BuildDir(), "*.hak"))
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("scan hak archives: %w", err)
|
||||
}
|
||||
slices.Sort(hakPaths)
|
||||
for _, hakPath := range hakPaths {
|
||||
input, err := os.Open(hakPath)
|
||||
if err != nil {
|
||||
failures = append(failures, fmt.Errorf("open hak archive %s: %w", hakPath, err))
|
||||
continue
|
||||
}
|
||||
hakArchive, err := erf.Read(input)
|
||||
input.Close()
|
||||
if err != nil {
|
||||
failures = append(failures, fmt.Errorf("read hak archive %s: %w", hakPath, err))
|
||||
continue
|
||||
}
|
||||
|
||||
result.HAKPaths = append(result.HAKPaths, hakPath)
|
||||
written, skipped, errs := extractArchiveResources(p, hakArchive)
|
||||
result.Written += written
|
||||
result.Skipped += skipped
|
||||
failures = append(failures, errs...)
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
return result, errors.Join(failures...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func extractArchiveResources(p *project.Project, archive erf.Archive) (int, int, []error) {
|
||||
var failures []error
|
||||
writtenCount := 0
|
||||
skippedCount := 0
|
||||
|
||||
for _, resource := range archive.Resources {
|
||||
target, data, err := extractedFile(p, resource)
|
||||
if err != nil {
|
||||
failures = append(failures, err)
|
||||
continue
|
||||
}
|
||||
|
||||
written, err := writeSafely(target, data)
|
||||
if err != nil {
|
||||
failures = append(failures, err)
|
||||
continue
|
||||
}
|
||||
if written {
|
||||
writtenCount++
|
||||
} else {
|
||||
skippedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount, skippedCount, failures
|
||||
}
|
||||
|
||||
func extractedFile(p *project.Project, resource erf.Resource) (string, []byte, error) {
|
||||
extension, ok := erf.ExtensionForResourceType(resource.Type)
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("unsupported resource type 0x%04X for %s", resource.Type, resource.Name)
|
||||
}
|
||||
|
||||
switch extension {
|
||||
case "nss":
|
||||
return filepath.Join(p.SourceDir(), "scripts", resource.Name+".nss"), resource.Data, nil
|
||||
case "utc", "utd", "ute", "uti", "utm", "utp", "uts", "utt", "utw",
|
||||
"are", "dlg", "fac", "gic", "git", "ifo", "itp", "jrl":
|
||||
document, err := gff.Read(bytes.NewReader(resource.Data))
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("decode gff %s.%s: %w", resource.Name, extension, err)
|
||||
}
|
||||
formatted, err := json.MarshalIndent(document, "", " ")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("marshal json %s.%s: %w", resource.Name, extension, err)
|
||||
}
|
||||
formatted = append(formatted, '\n')
|
||||
return filepath.Join(p.SourceDir(), sourceSubdir(extension), resource.Name+"."+extension+".json"), formatted, nil
|
||||
default:
|
||||
return filepath.Join(p.AssetsDir(), extension, resource.Name+"."+extension), resource.Data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func writeSafely(path string, data []byte) (bool, error) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return false, fmt.Errorf("create parent directory for %s: %w", path, err)
|
||||
}
|
||||
|
||||
existing, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
if bytes.Equal(existing, data) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("refusing to overwrite existing file with different contents: %s", path)
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return false, fmt.Errorf("check existing file %s: %w", path, err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return false, fmt.Errorf("write %s: %w", path, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func sourceSubdir(extension string) string {
|
||||
switch strings.ToLower(extension) {
|
||||
case "are":
|
||||
return "areas"
|
||||
case "dlg":
|
||||
return "dialogs"
|
||||
case "fac":
|
||||
return "factions"
|
||||
case "gic":
|
||||
return "instance"
|
||||
case "git":
|
||||
return "instance"
|
||||
case "ifo":
|
||||
return "module"
|
||||
case "itp":
|
||||
return "palettes"
|
||||
case "jrl":
|
||||
return "journal"
|
||||
case "utc":
|
||||
return filepath.Join("blueprints", "creatures")
|
||||
case "utd":
|
||||
return filepath.Join("blueprints", "doors")
|
||||
case "ute":
|
||||
return filepath.Join("blueprints", "encounters")
|
||||
case "uti":
|
||||
return filepath.Join("blueprints", "items")
|
||||
case "utm":
|
||||
return filepath.Join("blueprints", "merchants")
|
||||
case "utp":
|
||||
return filepath.Join("blueprints", "placeables")
|
||||
case "uts":
|
||||
return filepath.Join("blueprints", "sounds")
|
||||
case "utt":
|
||||
return filepath.Join("blueprints", "triggers")
|
||||
case "utw":
|
||||
return filepath.Join("blueprints", "waypoints")
|
||||
default:
|
||||
return extension
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user