implemented wap parsing and info extraction

This commit is contained in:
Doc
2025-08-18 12:29:17 +02:00
parent ca5fe83e32
commit 2af5bd222e
2 changed files with 282 additions and 3 deletions

View File

@@ -1,3 +1,196 @@
package wad
//init
import (
"encoding/binary"
"fmt"
"io"
"os"
"strings"
)
type header struct {
Ident [4]byte // "IWAD" or "PWAD"
NumLumps int32
DirOffset int32
}
type DirEntry struct {
FilePos int32
Size int32
Name8 [8]byte
}
func (d DirEntry) Name() string { return trimName(d.Name8) }
type Wad struct {
f *os.File
dir []DirEntry
fsize int64
isIWAD bool
isPWAD bool
path string
}
func Open(path string) (*Wad, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open wad: %w", err)
}
st, err := f.Stat()
if err != nil {
_ = f.Close()
return nil, fmt.Errorf("stat wad: %w", err)
}
var hdr header
if err := binary.Read(f, binary.LittleEndian, &hdr); err != nil {
_ = f.Close()
return nil, fmt.Errorf("read header: %w", err)
}
id := string(hdr.Ident[:])
if id != "IWAD" && id != "PWAD" {
_ = f.Close()
return nil, fmt.Errorf("not a WAD file (ident=%q)", id)
}
if hdr.DirOffset < 0 || int64(hdr.DirOffset) > st.Size() {
_ = f.Close()
return nil, fmt.Errorf("bad directory offset: %d", hdr.DirOffset)
}
if _, err := f.Seek(int64(hdr.DirOffset), io.SeekStart); err != nil {
_ = f.Close()
return nil, fmt.Errorf("seek directory: %w", err)
}
if hdr.NumLumps < 0 || hdr.NumLumps > 1<<20 {
_ = f.Close()
return nil, fmt.Errorf("unreasonable lump count: %d", hdr.NumLumps)
}
dir := make([]DirEntry, hdr.NumLumps)
if err := binary.Read(f, binary.LittleEndian, &dir); err != nil {
_ = f.Close()
return nil, fmt.Errorf("read directory: %w", err)
}
for i, d := range dir {
if d.FilePos < 0 || d.Size < 0 {
_ = f.Close()
return nil, fmt.Errorf("dir[%d]: negative pos/size", i)
}
end := int64(d.FilePos) + int64(d.Size)
if end < 0 || end > st.Size() {
_ = f.Close()
return nil, fmt.Errorf("dir[%d:%s]: out of file bounds", i, trimName(d.Name8))
}
}
w := &Wad{
f: f,
dir: dir,
fsize: st.Size(),
isIWAD: id == "IWAD",
isPWAD: id == "PWAD",
path: path,
}
return w, nil
}
func (w *Wad) Close() error {
if w == nil || w.f == nil {
return nil
}
err := w.f.Close()
w.f = nil
return err
}
func (w *Wad) Dir() []DirEntry { return w.dir }
func (w *Wad) ReadLump(i int) (name string, data []byte, err error) {
if i < 0 || i >= len(w.dir) {
return "", nil, fmt.Errorf("lump index out of range: %d", i)
}
de := w.dir[i]
name = trimName(de.Name8)
if _, err := w.f.Seek(int64(de.FilePos), io.SeekStart); err != nil {
return name, nil, fmt.Errorf("seek %s: %w", name, err)
}
buf := make([]byte, de.Size)
_, err = io.ReadFull(w.f, buf)
if err != nil {
return name, nil, fmt.Errorf("read %s: %w", name, err)
}
return name, buf, nil
}
func (w *Wad) ReadLumpByName(name string) ([]byte, int, error) {
want := strings.ToUpper(name)
for i, d := range w.dir {
if trimName(d.Name8) == want {
_, b, err := w.ReadLump(i)
return b, i, err
}
}
return nil, -1, fmt.Errorf("lump %q not found", want)
}
func (w *Wad) FindMap(marker string) (start, end int, err error) {
m := strings.ToUpper(marker)
start = -1
for i, d := range w.dir {
if d.Size == 0 && trimName(d.Name8) == m {
start = i
break
}
}
if start < 0 {
return -1, -1, fmt.Errorf("map marker %q not found", m)
}
end = len(w.dir)
for i := start + 1; i < len(w.dir); i++ {
if w.dir[i].Size == 0 {
end = i
break
}
}
return start, end, nil
}
func (w *Wad) LoadMapLumps(marker string, names ...string) (map[string][]byte, error) {
start, end, err := w.FindMap(marker)
if err != nil {
return nil, err
}
want := make(map[string]struct{}, len(names))
for _, n := range names {
want[strings.ToUpper(n)] = struct{}{}
}
out := make(map[string][]byte, len(names))
for i := start + 1; i < end; i++ {
n := trimName(w.dir[i].Name8)
if _, ok := want[n]; !ok {
continue
}
_, b, err := w.ReadLump(i)
if err != nil {
return nil, err
}
out[n] = b
if len(out) == len(want) {
break
}
}
missing := make([]string, 0)
for n := range want {
if _, ok := out[n]; !ok {
missing = append(missing, n)
}
}
if len(missing) > 0 {
return nil, fmt.Errorf("map %s: missing lumps: %v", strings.ToUpper(marker), missing)
}
return out, nil
}
func trimName(n8 [8]byte) string {
n := string(n8[:])
if i := strings.IndexByte(n, 0); i >= 0 {
n = n[:i]
}
return strings.ToUpper(strings.TrimSpace(n))
}

90
main.go
View File

@@ -1,6 +1,92 @@
package main
// init
func main() {
import (
"bspviz/internal/wad"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
// Flags
wadPath := flag.String("wad", "", "Pfad zur WAD (required)")
mapMarker := flag.String("map", "MAP01", "Map-Marker (z.B. MAP01, E1M1, MYMAP)")
listOnly := flag.Bool("list", false, "Nur Directory auflisten und beenden")
info := flag.Bool("info", false, "Roh-Infos zur Map (Counts von VERTEXES/LINEDEFS)")
extract := flag.String("extract", "", "Kommagetrennte Lump-Namen aus der Map extrahieren (z.B. VERTEXES,LINEDEFS)")
outdir := flag.String("out", ".", "Zielordner für -extract")
flag.Parse()
if *wadPath == "" {
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, " go run ./cmd/bspviz -wad MYMAP.wad -list\n")
fmt.Fprintf(os.Stderr, " go run ./cmd/bspviz -wad MYMAP.wad -map MYMAP -info\n")
fmt.Fprintf(os.Stderr, " go run ./cmd/bspviz -wad MYMAP.wad -map MYMAP -extract VERTEXES,LINEDEFS -out dumps/\n")
os.Exit(2)
}
w, err := wad.Open(*wadPath)
if err != nil {
log.Fatalf("open wad: %v", err)
}
defer w.Close()
if *listOnly {
fmt.Printf("WAD: %s\n", *wadPath)
for i, d := range w.Dir() {
fmt.Printf("%3d: %-8s size=%-7d pos=%-8d\n", i, d.Name(), d.Size, d.FilePos)
}
return
}
start, end, err := w.FindMap(*mapMarker)
if err != nil {
log.Fatalf("find map: %v", err)
}
fmt.Printf("Map %s: Directory [%d, %d)\n", strings.ToUpper(*mapMarker), start, end)
if *info {
lumps, err := w.LoadMapLumps(*mapMarker, "VERTEXES", "LINEDEFS")
if err != nil {
log.Fatalf("load map lumps: %v", err)
}
vb := lumps["VERTEXES"]
lb := lumps["LINEDEFS"]
verts := len(vb) / 4
lines := len(lb) / 14
fmt.Printf("VERTEXES: bytes=%d count=%d\n", len(vb), verts)
fmt.Printf("LINEDEFS: bytes=%d count=%d\n", len(lb), lines)
if len(vb)%4 != 0 {
fmt.Println("WARN: VERTEXES size ist kein Vielfaches von 4 → Format prüfen")
}
if len(lb)%14 != 0 {
fmt.Println("WARN: LINEDEFS size ist kein Vielfaches von 14 → Format prüfen")
}
}
if *extract != "" {
want := strings.Split(*extract, ",")
for i := range want {
want[i] = strings.ToUpper(strings.TrimSpace(want[i]))
}
lumps, err := w.LoadMapLumps(*mapMarker, want...)
if err != nil {
log.Fatalf("extract: %v", err)
}
if err := os.MkdirAll(*outdir, 0o755); err != nil {
log.Fatalf("mkdir %s: %v", *outdir, err)
}
for name, data := range lumps {
dst := filepath.Join(*outdir, fmt.Sprintf("%s.lmp", name))
if err := os.WriteFile(dst, data, 0o644); err != nil {
log.Fatalf("write %s: %v", dst, err)
}
fmt.Printf("wrote %s (%d bytes)\n", dst, len(data))
}
}
}