Fix VPS freeze

This commit is contained in:
2026-04-02 18:28:37 +02:00
parent b7f188779a
commit 65b9a3dff8
4 changed files with 118 additions and 50 deletions
+9
View File
@@ -0,0 +1,9 @@
# Local Go build cache
.cache/
# Built tool output
/tools/nwn-tool
# OS/editor noise
.DS_Store
Thumbs.db
+66 -20
View File
@@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"os"
"sort" "sort"
"strings" "strings"
) )
@@ -27,6 +28,8 @@ type Resource struct {
Name string Name string
Type uint16 Type uint16
Data []byte Data []byte
SourcePath string
Size int64
} }
type header struct { type header struct {
@@ -139,7 +142,7 @@ func New(fileType string, resources []Resource) Archive {
} }
func ArchiveSize(resources []Resource) int64 { func ArchiveSize(resources []Resource) int64 {
return int64(headerSize + len(resources)*24 + len(resources)*8 + totalResourceBytes(resources)) return int64(headerSize+len(resources)*24+len(resources)*8) + totalResourceBytes(resources)
} }
func Write(w io.Writer, archive Archive) error { func Write(w io.Writer, archive Archive) error {
@@ -154,28 +157,30 @@ func Write(w io.Writer, archive Archive) error {
entries := make([]resourceEntry, 0, len(archive.Resources)) entries := make([]resourceEntry, 0, len(archive.Resources))
dataOffset := uint32(headerSize + len(archive.Resources)*24 + len(archive.Resources)*8) dataOffset := uint32(headerSize + len(archive.Resources)*24 + len(archive.Resources)*8)
data := bytes.Buffer{}
for index, resource := range archive.Resources { for index, resource := range archive.Resources {
if len(resource.Name) > 16 { if len(resource.Name) > 16 {
return fmt.Errorf("resource %q exceeds 16-byte resref limit", resource.Name) return fmt.Errorf("resource %q exceeds 16-byte resref limit", resource.Name)
} }
size, err := payloadSize(resource)
if err != nil {
return err
}
if size > int64(^uint32(0)) {
return fmt.Errorf("resource %q exceeds 4 GiB resource size limit", resource.Name)
}
entry := resourceEntry{ entry := resourceEntry{
Offset: dataOffset + uint32(data.Len()), Offset: dataOffset,
Size: uint32(len(resource.Data)), Size: uint32(size),
} }
entries = append(entries, entry) entries = append(entries, entry)
dataOffset += uint32(size)
var key keyEntry var key keyEntry
copy(key.ResRef[:], []byte(resource.Name)) copy(key.ResRef[:], []byte(resource.Name))
key.ResourceID = uint32(index) key.ResourceID = uint32(index)
key.ResourceType = resource.Type key.ResourceType = resource.Type
keys = append(keys, key) keys = append(keys, key)
if _, err := data.Write(resource.Data); err != nil {
return err
}
} }
hdr := header{ hdr := header{
@@ -192,22 +197,21 @@ func Write(w io.Writer, archive Archive) error {
copy(hdr.FileType[:], []byte(padFour(archive.FileType))) copy(hdr.FileType[:], []byte(padFour(archive.FileType)))
copy(hdr.Version[:], []byte(padFour(archive.Version))) copy(hdr.Version[:], []byte(padFour(archive.Version)))
var buf bytes.Buffer if err := binary.Write(w, binary.LittleEndian, hdr); err != nil {
if err := binary.Write(&buf, binary.LittleEndian, hdr); err != nil {
return err return err
} }
if err := binary.Write(&buf, binary.LittleEndian, keys); err != nil { if err := binary.Write(w, binary.LittleEndian, keys); err != nil {
return err return err
} }
if err := binary.Write(&buf, binary.LittleEndian, entries); err != nil { if err := binary.Write(w, binary.LittleEndian, entries); err != nil {
return err return err
} }
if _, err := buf.Write(data.Bytes()); err != nil { for _, resource := range archive.Resources {
if err := writeResourceData(w, resource); err != nil {
return err return err
} }
}
_, err := w.Write(buf.Bytes()) return nil
return err
} }
func Read(r io.Reader) (Archive, error) { func Read(r io.Reader) (Archive, error) {
@@ -260,6 +264,7 @@ func Read(r io.Reader) (Archive, error) {
Name: resref, Name: resref,
Type: key.ResourceType, Type: key.ResourceType,
Data: payload, Data: payload,
Size: int64(entry.Size),
}) })
} }
@@ -288,10 +293,51 @@ func padFour(value string) string {
return value + strings.Repeat(" ", 4-len(value)) return value + strings.Repeat(" ", 4-len(value))
} }
func totalResourceBytes(resources []Resource) int { func totalResourceBytes(resources []Resource) int64 {
total := 0 var total int64
for _, resource := range resources { for _, resource := range resources {
total += len(resource.Data) switch {
case resource.Size > 0:
total += resource.Size
default:
total += int64(len(resource.Data))
}
} }
return total return total
} }
func payloadSize(resource Resource) (int64, error) {
if resource.Size > 0 {
return resource.Size, nil
}
if resource.SourcePath != "" && len(resource.Data) == 0 {
info, err := os.Stat(resource.SourcePath)
if err != nil {
return 0, fmt.Errorf("stat resource %q: %w", resource.SourcePath, err)
}
return info.Size(), nil
}
return int64(len(resource.Data)), nil
}
func writeResourceData(w io.Writer, resource Resource) error {
if len(resource.Data) > 0 || resource.SourcePath == "" {
_, err := w.Write(resource.Data)
return err
}
file, err := os.Open(resource.SourcePath)
if err != nil {
return fmt.Errorf("open resource %q: %w", resource.SourcePath, err)
}
defer file.Close()
written, err := io.Copy(w, file)
if err != nil {
return fmt.Errorf("copy resource %q: %w", resource.SourcePath, err)
}
if resource.Size > 0 && written != resource.Size {
return fmt.Errorf("copy resource %q: expected %d bytes, wrote %d", resource.SourcePath, resource.Size, written)
}
return nil
}
+21 -8
View File
@@ -50,7 +50,6 @@ type hakChunk struct {
Name string Name string
Assets []assetResource Assets []assetResource
Size int64 Size int64
Resources []erf.Resource
} }
func Build(p *project.Project) (BuildResult, error) { func Build(p *project.Project) (BuildResult, error) {
@@ -132,7 +131,7 @@ func BuildHAKs(p *project.Project) (BuildResult, error) {
if err != nil { if err != nil {
return BuildResult{}, fmt.Errorf("create hak archive: %w", err) return BuildResult{}, fmt.Errorf("create hak archive: %w", err)
} }
if err := erf.Write(hakOutput, erf.New("HAK ", chunk.Resources)); err != nil { if err := erf.Write(hakOutput, erf.New("HAK ", resourceSlice(chunk.Assets))); err != nil {
hakOutput.Close() hakOutput.Close()
return BuildResult{}, fmt.Errorf("write hak archive: %w", err) return BuildResult{}, fmt.Errorf("write hak archive: %w", err)
} }
@@ -198,7 +197,7 @@ func collectAssetResources(p *project.Project) ([]assetResource, error) {
for _, rel := range p.Inventory.AssetFiles { for _, rel := range p.Inventory.AssetFiles {
abs := filepath.Join(p.AssetsDir(), filepath.FromSlash(rel)) abs := filepath.Join(p.AssetsDir(), filepath.FromSlash(rel))
resource, err := rawResource(abs) resource, err := pathResource(abs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -276,6 +275,25 @@ func resourceFromJSON(path string, moduleHakOrder []string) (erf.Resource, error
}, nil }, nil
} }
func pathResource(path string) (erf.Resource, error) {
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
resourceType, ok := erf.ResourceTypeForExtension(extension)
if !ok {
return erf.Resource{}, fmt.Errorf("unsupported resource extension %q", filepath.Ext(path))
}
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
info, err := os.Stat(path)
if err != nil {
return erf.Resource{}, fmt.Errorf("stat %s: %w", path, err)
}
return erf.Resource{
Name: name,
Type: resourceType,
SourcePath: path,
Size: info.Size(),
}, nil
}
func rawResource(path string) (erf.Resource, error) { func rawResource(path string) (erf.Resource, error) {
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".") extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
resourceType, ok := erf.ResourceTypeForExtension(extension) resourceType, ok := erf.ResourceTypeForExtension(extension)
@@ -321,7 +339,6 @@ func planHAKChunks(p *project.Project, assets []assetResource) ([]hakChunk, erro
Name: p.Config.Module.ResRef, Name: p.Config.Module.ResRef,
Size: erf.ArchiveSize(resourceSlice(assets)), Size: erf.ArchiveSize(resourceSlice(assets)),
Assets: assets, Assets: assets,
Resources: resourceSlice(assets),
} }
return []hakChunk{chunk}, nil return []hakChunk{chunk}, nil
} }
@@ -377,7 +394,6 @@ func splitHAKGroup(cfg project.HAKConfig, assets []assetResource) ([]hakChunk, e
Index: 1, Index: 1,
Name: cfg.Name, Name: cfg.Name,
Assets: assets, Assets: assets,
Resources: resourceSlice(assets),
Size: erf.ArchiveSize(resourceSlice(assets)), Size: erf.ArchiveSize(resourceSlice(assets)),
} }
if err := ensureUniqueChunkResources(chunk); err != nil { if err := ensureUniqueChunkResources(chunk); err != nil {
@@ -398,14 +414,12 @@ func splitHAKGroup(cfg project.HAKConfig, assets []assetResource) ([]hakChunk, e
return nil, fmt.Errorf("asset %s alone exceeds max_bytes for hak %s", asset.Rel, cfg.Name) return nil, fmt.Errorf("asset %s alone exceeds max_bytes for hak %s", asset.Rel, cfg.Name)
} }
current.Assets = candidateAssets current.Assets = candidateAssets
current.Resources = candidateResources
current.Size = candidateSize current.Size = candidateSize
continue continue
} }
if candidateSize <= maxBytes { if candidateSize <= maxBytes {
current.Assets = candidateAssets current.Assets = candidateAssets
current.Resources = candidateResources
current.Size = candidateSize current.Size = candidateSize
continue continue
} }
@@ -424,7 +438,6 @@ func splitHAKGroup(cfg project.HAKConfig, assets []assetResource) ([]hakChunk, e
Config: cfg, Config: cfg,
Index: current.Index + 1, Index: current.Index + 1,
Assets: []assetResource{asset}, Assets: []assetResource{asset},
Resources: []erf.Resource{asset.Resource},
Size: erf.ArchiveSize([]erf.Resource{asset.Resource}), Size: erf.ArchiveSize([]erf.Resource{asset.Resource}),
} }
if current.Size > maxBytes { if current.Size > maxBytes {
BIN
View File
Binary file not shown.