diff --git a/app/app.go b/app/app.go index cb4c7cb..106f2c1 100644 --- a/app/app.go +++ b/app/app.go @@ -79,21 +79,35 @@ var ConfigFormat = ConfigTemplate{ Fallback: "raw", } -type App struct { - Hashes map[stingray.Hash]string - gameDir string - dataDir *stingray.DataDir -} - -func New() *App { - a := &App{ - Hashes: make(map[stingray.Hash]string), +func parseWwiseDep(f *stingray.File) (string, error) { + r, err := f.Open(stingray.DataMain) + if err != nil { + return "", err + } + var magicNum [4]byte + if _, err := io.ReadFull(r, magicNum[:]); err != nil { + return "", err + } + if magicNum != [4]byte{0xd8, '/', 'v', 'x'} { + return "", errors.New("invalid magic number") + } + var textLen uint32 + if err := binary.Read(r, binary.LittleEndian, &textLen); err != nil { + return "", err + } + text := make([]byte, textLen-1) + if _, err := io.ReadFull(r, text); err != nil { + return "", err } + return string(text), nil +} - return a +// Returns error if steam path couldn't be found. +func DetectGameDir() (string, error) { + return steampath.GetAppPath("553850", "Helldivers 2") } -func (a *App) SetGameDir(path string) error { +func VerifyGameDir(path string) error { if info, err := os.Stat(path); err != nil || !info.IsDir() { return fmt.Errorf("invalid game directory: %v: not a directory", path) } @@ -104,94 +118,52 @@ func (a *App) SetGameDir(path string) error { if info, err := os.Stat(filepath.Join(path, "data", "settings.ini")); err != nil || !info.Mode().IsRegular() { return fmt.Errorf("invalid game directory: %v: valid data directory not found", path) } - path, err := filepath.Abs(path) - if err != nil { - return err - } - a.gameDir = path return nil } -func (a *App) DetectGameDir() (string, error) { - path, err := steampath.GetAppPath("553850", "Helldivers 2") - if err != nil { - return "", err - } - if err := a.SetGameDir(path); err != nil { - return "", err +func ParseHashes(str string) []string { + var res []string + sc := bufio.NewScanner(strings.NewReader(str)) + for sc.Scan() { + s := strings.TrimSpace(sc.Text()) + if s != "" && !strings.HasPrefix(s, "//") { + res = append(res, s) + } } - return path, nil + return res +} + +type App struct { + Hashes map[stingray.Hash]string + DataDir *stingray.DataDir } -func (a *App) OpenGameDir() error { - dataDir, err := stingray.OpenDataDir(filepath.Join(a.gameDir, "data")) +// Open game dir and read metadata. +func New(gameDir string, hashes []string) (*App, error) { + dataDir, err := stingray.OpenDataDir(filepath.Join(gameDir, "data")) if err != nil { - return err + return nil, err } - a.dataDir = dataDir + hashesMap := make(map[stingray.Hash]string) + for _, h := range hashes { + hashesMap[stingray.Sum64([]byte(h))] = h + } // wwise_dep files let us know the string of many of the wwise_banks for id, file := range dataDir.Files { if id.Type == stingray.Sum64([]byte("wwise_dep")) { - if err := a.addHashFromWwiseDep(*file); err != nil { - return fmt.Errorf("wwise_dep: %w", err) + h, err := parseWwiseDep(file) + if err != nil { + return nil, fmt.Errorf("wwise_dep: %w", err) } + hashesMap[stingray.Sum64([]byte(h))] = h } } - return nil -} - -func (a *App) addHashFromWwiseDep(f stingray.File) error { - r, err := f.Open(stingray.DataMain) - if err != nil { - return err - } - var magicNum [4]byte - if _, err := io.ReadFull(r, magicNum[:]); err != nil { - return err - } - if magicNum != [4]byte{0xd8, '/', 'v', 'x'} { - return errors.New("invalid magic number") - } - var textLen uint32 - if err := binary.Read(r, binary.LittleEndian, &textLen); err != nil { - return err - } - text := make([]byte, textLen-1) - if _, err := io.ReadFull(r, text); err != nil { - return err - } - a.AddHashFromString(string(text)) - return nil -} - -func (a *App) AddHashFromString(str string) { - a.Hashes[stingray.Sum64([]byte(str))] = str -} - -func (a *App) AddHashesFromString(str string) { - sc := bufio.NewScanner(strings.NewReader(str)) - for sc.Scan() { - s := strings.TrimSpace(sc.Text()) - if s != "" && !strings.HasPrefix(s, "//") { - a.AddHashFromString(s) - } - } -} - -func (a *App) AddHashesFromFile(path string) error { - b, err := os.ReadFile(path) - if err != nil { - return err - } - a.AddHashesFromString(string(b)) - return nil -} - -func (a *App) File(id stingray.FileID) (f *stingray.File, exists bool) { - f, exists = a.dataDir.Files[id] - return + return &App{ + Hashes: hashesMap, + DataDir: dataDir, + }, nil } func (a *App) matchFileID(id stingray.FileID, glb glob.Glob, nameOnly bool) bool { @@ -235,10 +207,6 @@ func (a *App) matchFileID(id stingray.FileID, glb glob.Glob, nameOnly bool) bool return false } -func (a *App) AllFiles() map[stingray.FileID]*stingray.File { - return a.dataDir.Files -} - func (a *App) MatchingFiles(includeGlob, excludeGlob string, cfgTemplate ConfigTemplate, cfg map[string]map[string]string) (map[stingray.FileID]*stingray.File, error) { var inclGlob glob.Glob inclGlobNameOnly := !strings.Contains(includeGlob, ".") @@ -260,7 +228,7 @@ func (a *App) MatchingFiles(includeGlob, excludeGlob string, cfgTemplate ConfigT } res := make(map[stingray.FileID]*stingray.File) - for id, file := range a.dataDir.Files { + for id, file := range a.DataDir.Files { shouldIncl := true if includeGlob != "" { shouldIncl = a.matchFileID(id, inclGlob, inclGlobNameOnly) @@ -327,7 +295,7 @@ func (c *extractContext) File() *stingray.File { return c.file } func (c *extractContext) Runner() *exec.Runner { return c.runner } func (c *extractContext) Config() map[string]string { return c.config } func (c *extractContext) GetResource(name, typ stingray.Hash) (file *stingray.File, exists bool) { - file, exists = c.app.AllFiles()[stingray.FileID{Name: name, Type: typ}] + file, exists = c.app.DataDir.Files[stingray.FileID{Name: name, Type: typ}] return } func (c *extractContext) CreateFile(suffix string) (io.WriteCloser, error) { @@ -352,7 +320,7 @@ func (a *App) ExtractFile(id stingray.FileID, outDir string, extrCfg map[string] typ = id.Type.String() } - file, ok := a.dataDir.Files[id] + file, ok := a.DataDir.Files[id] if !ok { return fmt.Errorf("extract %v.%v: file does not found", name, typ) } diff --git a/cmd/filediver-cli/main.go b/cmd/filediver-cli/main.go index 925ece5..78bdb64 100644 --- a/cmd/filediver-cli/main.go +++ b/cmd/filediver-cli/main.go @@ -84,28 +84,27 @@ extractor config: } defer runner.Close() - a := app.New() - if *gameDir == "" { - if path, err := a.DetectGameDir(); err == nil { - prt.Infof("Using game found at: \"%v\"", path) + var err error + *gameDir, err = app.DetectGameDir() + if err == nil { + prt.Infof("Using game found at: \"%v\"", *gameDir) } else { prt.Errorf("Helldivers 2 Steam installation path not found: %v", err) prt.Fatalf("Unable to detect game install directory. Please specify the game directory manually using the '-g' option.") } } else { - if err := a.SetGameDir(*gameDir); err != nil { - prt.Fatalf("%v", err) - } prt.Infof("Game directory: \"%v\"", *gameDir) } - if *knownHashesPath == "" { - a.AddHashesFromString(hashes.Hashes) - } else { - if err := a.AddHashesFromFile(*knownHashesPath); err != nil { + var knownHashes []string + knownHashes = append(knownHashes, app.ParseHashes(hashes.Hashes)...) + if *knownHashesPath != "" { + b, err := os.ReadFile(*knownHashesPath) + if err != nil { prt.Fatalf("%v", err) } + knownHashes = append(knownHashes, app.ParseHashes(string(b))...) } if !*modeList { @@ -113,9 +112,11 @@ extractor config: } prt.Infof("Reading metadata...") - if err := a.OpenGameDir(); err != nil { + a, err := app.New(*gameDir, knownHashes) + if err != nil { prt.Fatalf("%v", err) } + files, err := a.MatchingFiles(*extrInclGlob, *extrExclGlob, app.ConfigFormat, extrCfg) if err != nil { prt.Fatalf("%v", err) @@ -144,7 +145,7 @@ extractor config: { names := make(map[stingray.Hash]struct{}) types := make(map[stingray.Hash]struct{}) - for id := range a.AllFiles() { + for id := range a.DataDir.Files { names[id.Name] = struct{}{} types[id.Type] = struct{}{} } diff --git a/exec/exec_all.go b/exec/exec_all.go index be9b4d4..b5d6301 100644 --- a/exec/exec_all.go +++ b/exec/exec_all.go @@ -1,7 +1,7 @@ -//go:build !windows - -package exec - -import "os/exec" - -func applyOSSpecificCmdOpts(cmd *exec.Cmd) {} +//go:build !windows + +package exec + +import "os/exec" + +func applyOSSpecificCmdOpts(cmd *exec.Cmd) {} diff --git a/exec/exec_windows.go b/exec/exec_windows.go index a4aaa52..68b0cab 100644 --- a/exec/exec_windows.go +++ b/exec/exec_windows.go @@ -1,12 +1,12 @@ -//go:build windows - -package exec - -import ( - "os/exec" - "syscall" -) - -func applyOSSpecificCmdOpts(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} -} +//go:build windows + +package exec + +import ( + "os/exec" + "syscall" +) + +func applyOSSpecificCmdOpts(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} +} diff --git a/hashes/generate.go b/hashes/generate.go index d2792b2..c600ef5 100644 --- a/hashes/generate.go +++ b/hashes/generate.go @@ -1,3 +1,3 @@ -package hashes - -//go:generate go run github.com/xypwn/filediver/hashes/generate +package hashes + +//go:generate go run github.com/xypwn/filediver/hashes/generate diff --git a/hashes/hashes.go b/hashes/hashes.go index a5b5fb8..1186c3f 100644 --- a/hashes/hashes.go +++ b/hashes/hashes.go @@ -1,11 +1,11 @@ -package hashes - -import ( - _ "embed" -) - -//go:embed hashes.txt -var Hashes string - -//go:embed material_textures.txt -var MaterialTextures string +package hashes + +import ( + _ "embed" +) + +//go:embed hashes.txt +var Hashes string + +//go:embed material_textures.txt +var MaterialTextures string