Skip to content

Commit

Permalink
cmd/chflg: Implement chflg command
Browse files Browse the repository at this point in the history
  • Loading branch information
pg9182 committed Apr 14, 2024
1 parent 9da1a6e commit f487005
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 0 deletions.
184 changes: 184 additions & 0 deletions cmd/chflg/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package cat

import (
"encoding/binary"
"fmt"
"io"
"io/fs"
"os"
"strconv"
"strings"

"github.com/pg9182/tf2vpk"
"github.com/pg9182/tf2vpk/cmd/root"
"github.com/spf13/cobra"
)

var Flags struct {
VPK tf2vpk.ValvePakRef
Flags string
Files []string
Verbose bool
DryRun bool
}

var Command = &cobra.Command{
GroupID: root.GroupVPKWrite.ID,
Use: "chflg vpk_path { load_flags:texture_flags | @reference_file } file...",
Short: "Sets flags for VPK entries",
Long: `Sets flags for VPK entries
Flags are specified as a fixed-width bit string, as hex prefixed with 0x, or as a path to another in the VPK to copy flags from.
The provided file can also be a directory to change all files under it (use / to change everything).
`,
Args: cobra.MinimumNArgs(3),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
if toComplete, ok := strings.CutPrefix(toComplete, "@"); ok {
cs, rc := root.ArgVPKFileCompletions(args, toComplete, false, true)
for i := range cs {
cs[i] = "@" + cs[i]
}
return cs, rc
}
}
return nil, cobra.ShellCompDirectiveDefault
},
Run: func(cmd *cobra.Command, args []string) {
Flags.Flags = args[1]
Flags.Files = args[2:]
main()
},
}

func init() {
root.ArgVPK(&Flags.VPK, Command, 2, true, true, true)
Command.Flags().BoolVarP(&Flags.DryRun, "dry-run", "n", false, "do not write changes")
Command.Flags().BoolVarP(&Flags.Verbose, "verbose", "v", false, "print information about each processed file")
root.Command.AddCommand(Command)
}

func main() {
openFlag := os.O_RDWR
if Flags.DryRun {
openFlag = os.O_RDONLY
}

f, err := os.OpenFile(Flags.VPK.Resolve(tf2vpk.ValvePakIndexDir), openFlag, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "error: open vpk dir index: %v\n", err)
os.Exit(1)
}
defer f.Close()

var root tf2vpk.ValvePakDir
if err := root.Deserialize(io.NewSectionReader(f, 0, 1<<63-1)); err != nil {
fmt.Fprintf(os.Stderr, "error: read vpk dir index: %v\n", err)
os.Exit(1)
}

var (
loadFlags uint32
textureFlags uint16
)
if p, ok := strings.CutPrefix(Flags.Flags, "@"); ok {
var found bool
for _, f := range root.File {
if f.Path == strings.TrimPrefix(p, "/") {
if loadFlags, err = f.LoadFlags(); err != nil {
fmt.Fprintf(os.Stderr, "error: failed to compute load flags for reference file %q: %v\n", p, err)
os.Exit(1)
}
if textureFlags, err = f.TextureFlags(); err != nil {
fmt.Fprintf(os.Stderr, "error: failed to compute texture flags for reference file %q: %v\n", p, err)
os.Exit(1)
}
found = true
break
}
}
if !found {
fmt.Fprintf(os.Stderr, "error: reference file %q does not exist in vpk\n", p)
os.Exit(1)
}
} else {
load, texture, ok := strings.Cut(Flags.Flags, ":")
if !ok {
fmt.Fprintf(os.Stderr, "error: invalid flags %q: expected load and texture flags separated by a colon\n", Flags.Flags)
os.Exit(1)
}
loadFlags, err = parseFlag[uint32](load)
if err != nil {
fmt.Fprintf(os.Stderr, "error: invalid flags %q: parse load flags: %v\n", Flags.Flags, err)
os.Exit(1)
}
textureFlags, err = parseFlag[uint16](texture)
if err != nil {
fmt.Fprintf(os.Stderr, "error: invalid flags %q: parse texture flags: %v\n", Flags.Flags, err)
os.Exit(1)
}
}

var failed int
for _, name := range Flags.Files {
if err := func() error {
var matched bool
for i, f := range root.File {
if name == "/" || strings.HasPrefix(f.Path+"/", name+"/") {
loadFlagsOrig, _ := f.LoadFlags()
textureFlagsOrig, _ := f.TextureFlags()
for j := range f.Chunk {
root.File[i].Chunk[j].LoadFlags = loadFlags
root.File[i].Chunk[j].TextureFlags = textureFlags
}
if Flags.Verbose {
var what string
if loadFlagsOrig != loadFlags || textureFlagsOrig != textureFlags {
what = "set flags to"
} else {
what = "retained flags"
}
fmt.Printf("%s: %s 0x%08X:0x%04X (load=%s texture=%s)\n", f.Path, what, loadFlags, textureFlags, tf2vpk.DescribeLoadFlags(loadFlags), tf2vpk.DescribeTextureFlags(textureFlags))
}
matched = true
}
}
if !matched {
return fs.ErrNotExist
}
return nil
}(); err != nil {
fmt.Fprintf(os.Stderr, "error: set flags for file %q: %v\n", name, err)
failed++
}
}
if !Flags.DryRun {
if err := root.Serialize(io.NewOffsetWriter(f, 0)); err != nil { // note: this is fine since the length doesn't change
fmt.Fprintf(os.Stderr, "error: write vpk dir index: %v\n", err)
os.Exit(1)
}
}
if failed != 0 {
os.Exit(1)
}
}

func parseFlag[T uint16 | uint32](s string) (T, error) {
bits := binary.Size(T(0)) * 8
if s, ok := strings.CutPrefix(s, "0x"); ok {
if v, err := strconv.ParseUint(s, 16, bits); err != nil {
return 0, fmt.Errorf("parse hex flags %q: %w", s, err)
} else {
return T(v), nil
}
}
if v, err := strconv.ParseUint(s, 2, bits); err == nil {
if len(s) != bits {
return 0, fmt.Errorf("parse binary flags %q: must be exactly %d bits", s, bits)
} else {
return T(v), nil
}
}
return 0, fmt.Errorf("unknown flag format %q (expected 0x hex or fixed-width binary)", s)
}
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/pg9182/tf2vpk/cmd/root"

_ "github.com/pg9182/tf2vpk/cmd/cat"
_ "github.com/pg9182/tf2vpk/cmd/chflg"
_ "github.com/pg9182/tf2vpk/cmd/init"
_ "github.com/pg9182/tf2vpk/cmd/lzham"
_ "github.com/pg9182/tf2vpk/cmd/tarzip"
Expand Down

0 comments on commit f487005

Please sign in to comment.