Add engine-debug-tools and fix critter animation rendering bugs
build / build (push) Successful in 2m52s
build / build (push) Successful in 2m52s
- engine-debug-tools/: a debug dump hotkey + dump2png to inspect decoded critter frames independent of the renderer (see root README.md's "Debugging" section). - engine-patches/04,05: fix critter sprite scale and an OpenGL texture cache collision, found using that tooling. - mdlp: fix encodeFrames RLE-diffing upscaled frames against a previous-frame reference the real RSD8 decoder never implements (verified against Night Dive's original Mac source down to the 68K assembly) - was rendering as scattered static in-game. - Upscale pipeline: better color quantization (dithering, pure-ramp exclusion) and per-resource model selection.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
module questshock/dump2png
|
||||
|
||||
go 1.25.0
|
||||
|
||||
// hacked/ is vendored under modding/src/ (see ../../modding/README.md);
|
||||
// dump2png reuses its resource/palette reading code (ss1/world,
|
||||
// ss1/content/bitmap) instead of duplicating System Shock's file formats.
|
||||
require github.com/inkyblackness/hacked v0.0.0
|
||||
|
||||
require github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
|
||||
replace github.com/inkyblackness/hacked => ../../modding/src/hacked
|
||||
@@ -0,0 +1,10 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -0,0 +1,105 @@
|
||||
// dump2png converts the raw indexed-pixel frame dumps written by the
|
||||
// engine's debug dump hotkey (see ../engine-patches/ and the root
|
||||
// README.md's "Debugging" section) into viewable PNGs, using the real game
|
||||
// palette - see that same README section for the dump file format.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/bitmap"
|
||||
"github.com/inkyblackness/hacked/ss1/resource"
|
||||
"github.com/inkyblackness/hacked/ss1/world"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 4 {
|
||||
fmt.Fprintln(os.Stderr, "usage: dump2png <game-data-dir> <dumps-dir> <out-dir>")
|
||||
os.Exit(1)
|
||||
}
|
||||
dataDir, inDir, outDir := os.Args[1], os.Args[2], os.Args[3]
|
||||
|
||||
entries, err := os.ReadDir(dataDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
merged := &world.ManifestEntry{Origin: []string{dataDir}, ID: dataDir}
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(e.Name()))
|
||||
if ext != ".res" && ext != ".dat" {
|
||||
continue
|
||||
}
|
||||
fe, err := world.NewManifestEntryFrom([]string{filepath.Join(dataDir, e.Name())})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
merged.Resources = append(merged.Resources, fe.Resources...)
|
||||
}
|
||||
sort.Slice(merged.Resources, func(i, j int) bool { return merged.Resources[i].ID < merged.Resources[j].ID })
|
||||
mod := world.NewMod(func([]resource.ID, []resource.ID) {}, func() {})
|
||||
if err := mod.World().InsertEntry(0, merged); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pal, err := bitmap.NewPaletteCache(mod).Palette(resource.KeyOf(0x02BC, resource.LangDefault, 0))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
colors := pal.ColorPalette(false)
|
||||
|
||||
if err := os.MkdirAll(outDir, 0o755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
files, err := os.ReadDir(inDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, fi := range files {
|
||||
if !strings.HasSuffix(fi.Name(), ".raw") {
|
||||
continue
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join(inDir, fi.Name()))
|
||||
if err != nil {
|
||||
fmt.Println("skip", fi.Name(), err)
|
||||
continue
|
||||
}
|
||||
if len(raw) < 4 {
|
||||
continue
|
||||
}
|
||||
w := int(binary.LittleEndian.Uint16(raw[0:2]))
|
||||
h := int(binary.LittleEndian.Uint16(raw[2:4]))
|
||||
pixels := raw[4:]
|
||||
if len(pixels) < w*h {
|
||||
fmt.Println("truncated", fi.Name())
|
||||
continue
|
||||
}
|
||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
for i := 0; i < w*h; i++ {
|
||||
r, g, b, _ := colors[pixels[i]].RGBA()
|
||||
img.Set(i%w, i/w, color.RGBA{R: byte(r >> 8), G: byte(g >> 8), B: byte(b >> 8), A: 0xff})
|
||||
}
|
||||
outPath := filepath.Join(outDir, strings.TrimSuffix(fi.Name(), ".raw")+".png")
|
||||
of, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
fmt.Println("create failed", outPath, err)
|
||||
continue
|
||||
}
|
||||
err = png.Encode(of, img)
|
||||
of.Close()
|
||||
if err != nil {
|
||||
fmt.Println("encode failed", outPath, err)
|
||||
continue
|
||||
}
|
||||
fmt.Println("wrote", outPath, w, h)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user