From 4311f2199d5e845ccc925fe85ad376527f3c2b03 Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Sun, 23 Jun 2024 10:56:22 -0400 Subject: [PATCH] Refactor + modularize entrypoint --- cmd/pang/main.go | 96 +--------- cmd/pang/shellstub.go | 13 -- cmd/pang/updatelist.go | 168 ------------------ internal/app/pang/main.go | 29 +++ internal/cli/commands/pak/extract.go | 72 ++++++++ .../cli/commands/pak/mount.go | 64 +------ internal/cli/commands/updatelist/decrypt.go | 52 ++++++ internal/cli/commands/updatelist/encrypt.go | 52 ++++++ internal/cli/commands/updatelist/util.go | 38 ++++ internal/cli/commands/updatelistsrv/serve.go | 65 +++++++ .../cli/commands/updatelistsrv/server.go | 14 +- internal/cli/commands/version/command.go | 29 +++ internal/region/key.go | 62 +++++++ {cmd/pang => internal/shell}/shell_darwin.go | 4 +- {cmd/pang => internal/shell}/shell_freebsd.go | 4 +- {cmd/pang => internal/shell}/shell_linux.go | 4 +- {cmd/pang => internal/shell}/shell_windows.go | 4 +- internal/shell/shellstub.go | 11 ++ version/version.go | 2 +- 19 files changed, 437 insertions(+), 346 deletions(-) delete mode 100644 cmd/pang/shellstub.go delete mode 100644 cmd/pang/updatelist.go create mode 100644 internal/app/pang/main.go create mode 100644 internal/cli/commands/pak/extract.go rename cmd/pang/pak.go => internal/cli/commands/pak/mount.go (54%) create mode 100644 internal/cli/commands/updatelist/decrypt.go create mode 100644 internal/cli/commands/updatelist/encrypt.go create mode 100644 internal/cli/commands/updatelist/util.go create mode 100644 internal/cli/commands/updatelistsrv/serve.go rename cmd/pang/updatelistsrv.go => internal/cli/commands/updatelistsrv/server.go (90%) create mode 100644 internal/cli/commands/version/command.go create mode 100644 internal/region/key.go rename {cmd/pang => internal/shell}/shell_darwin.go (55%) rename {cmd/pang => internal/shell}/shell_freebsd.go (56%) rename {cmd/pang => internal/shell}/shell_linux.go (56%) rename {cmd/pang => internal/shell}/shell_windows.go (78%) create mode 100644 internal/shell/shellstub.go diff --git a/cmd/pang/main.go b/cmd/pang/main.go index ce2274c..a87e29d 100644 --- a/cmd/pang/main.go +++ b/cmd/pang/main.go @@ -1,99 +1,7 @@ package main -import ( - "context" - "flag" - "fmt" - "log" - "os" - "strings" - - "github.com/google/subcommands" - "github.com/pangbox/pangfiles/crypto/pyxtea" - "github.com/pangbox/pangfiles/pak" - "github.com/pangbox/pangfiles/version" -) - -var xteaKeys = []pyxtea.Key{ - pyxtea.KeyUS, - pyxtea.KeyJP, - pyxtea.KeyTH, - pyxtea.KeyEU, - pyxtea.KeyID, - pyxtea.KeyKR, -} - -var regionToKey = map[string]pyxtea.Key{ - "us": pyxtea.KeyUS, - "jp": pyxtea.KeyJP, - "th": pyxtea.KeyTH, - "eu": pyxtea.KeyEU, - "id": pyxtea.KeyID, - "kr": pyxtea.KeyKR, -} - -var keyToRegion = map[pyxtea.Key]string{ - pyxtea.KeyUS: "us", - pyxtea.KeyJP: "jp", - pyxtea.KeyTH: "th", - pyxtea.KeyEU: "eu", - pyxtea.KeyID: "id", - pyxtea.KeyKR: "kr", -} - -func getRegionKey(regionCode string) pyxtea.Key { - key, ok := regionToKey[regionCode] - if !ok { - log.Fatalf("Invalid region %q (valid regions: us, jp, th, eu, id, kr)", regionCode) - } - return key -} - -func getKeyRegion(key pyxtea.Key) string { - region, ok := keyToRegion[key] - if !ok { - panic("programming error: unexpected key") - } - return region -} - -func getPakKey(region string, patterns []string) pyxtea.Key { - if region == "" { - log.Println("Auto-detecting pak region (use -region to improve startup delay.)") - key := pak.MustDetectRegion(patterns, xteaKeys) - log.Printf("Detected pak region as %s.", strings.ToUpper(getKeyRegion(key))) - return key - } - return getRegionKey(region) -} - -type versionCmd struct{} - -func (versionCmd) Name() string { return "version" } -func (versionCmd) Synopsis() string { return "Prints version information to stdout." } -func (v versionCmd) Usage() string { return fmt.Sprintf("%s:\n %s\n", v.Name(), v.Synopsis()) } -func (versionCmd) SetFlags(*flag.FlagSet) {} -func (versionCmd) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus { - versionStr := "v" + version.Release - if version.GitCommit != "" { - versionStr += "+" + version.GitCommit - } - fmt.Println(versionStr) - return subcommands.ExitSuccess -} +import "github.com/pangbox/pangfiles/internal/app/pang" func main() { - subcommands.Register(subcommands.HelpCommand(), "") - subcommands.Register(subcommands.FlagsCommand(), "") - subcommands.Register(subcommands.CommandsCommand(), "") - subcommands.Register(&versionCmd{}, "") - subcommands.Register(&cmdPakMount{}, "paks") - subcommands.Register(&cmdPakExtract{}, "paks") - subcommands.Register(&cmdUpdateListServe{}, "updatelists") - subcommands.Register(&cmdUpdateListEncrypt{}, "updatelists") - subcommands.Register(&cmdUpdateListDecrypt{}, "updatelists") - - flag.Parse() - ctx := context.Background() - os.Exit(int(subcommands.Execute(ctx))) + pang.Main() } diff --git a/cmd/pang/shellstub.go b/cmd/pang/shellstub.go deleted file mode 100644 index fce71d9..0000000 --- a/cmd/pang/shellstub.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build !darwin -// +build !freebsd -// +build !linux -// +build !windows - -package main - -import "log" - -func openfolder(folder string) error { - log.Println(folder) - return nil -} diff --git a/cmd/pang/updatelist.go b/cmd/pang/updatelist.go deleted file mode 100644 index 7c4e9a5..0000000 --- a/cmd/pang/updatelist.go +++ /dev/null @@ -1,168 +0,0 @@ -package main - -import ( - "context" - "flag" - "log" - "net/http" - "os" - "path/filepath" - - "github.com/google/subcommands" - "github.com/pangbox/pangfiles/crypto/pyxtea" -) - -func openfile(infile string) *os.File { - if infile == "" { - log.Println("Reading from stdin.") - return os.Stdin - } - in, err := os.Open(infile) - if err != nil { - log.Fatalf("Error opening input file %q: %s", infile, err) - } - return in -} - -func createfile(outfile string) *os.File { - if outfile == "" { - return os.Stdout - } - out, err := os.Create(outfile) - if err != nil { - log.Fatalf("Error opening output file %q: %s", outfile, err) - } - return out -} - -func closefiles(in *os.File, out *os.File) { - if err := in.Close(); err != nil { - log.Printf("Warning: an error occurred during close of input file: %s", err) - } - if err := out.Close(); err != nil { - log.Printf("Warning: an error occurred during close of output file: %s", err) - } -} - -type cmdUpdateListServe struct { - region string - listen string -} - -func (*cmdUpdateListServe) Name() string { return "updatelist-serve" } -func (*cmdUpdateListServe) Synopsis() string { - return "serves an updatelist for a game folder" -} -func (*cmdUpdateListServe) Usage() string { - return `pak-extract [-region ] [-listen
] : - Serves an automatically updating updatelist for a game folder. - -` -} - -func (p *cmdUpdateListServe) SetFlags(f *flag.FlagSet) { - f.StringVar(&p.region, "region", "", "region to use (us, jp, th, eu, id, kr)") - f.StringVar(&p.listen, "listen", ":8080", "address to listen on") -} - -func (p *cmdUpdateListServe) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - if f.NArg() > 1 { - log.Println("Too many arguments.") - return subcommands.ExitUsageError - } else if f.NArg() < 1 { - log.Println("Not enough arguments. Try specifying a game folder.") - return subcommands.ExitUsageError - } - - dir := f.Arg(0) - - key := getPakKey(p.region, []string{ - filepath.Join(dir, "projectg*.pak"), - filepath.Join(dir, "ProjectG*.pak"), - }) - - s := server{ - key: key, - dir: dir, - cache: map[string]cacheentry{}, - } - if err := http.ListenAndServe(p.listen, &s); err != nil { - log.Println(err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} - -type cmdUpdateListEncrypt struct { - region string -} - -func (*cmdUpdateListEncrypt) Name() string { return "updatelist-encrypt" } -func (*cmdUpdateListEncrypt) Synopsis() string { - return "encrypts an updatelist" -} -func (*cmdUpdateListEncrypt) Usage() string { - return `updatelist-encrypt [-region ] [input file] [output file]: - Encrypts an updatelist XML document for use with a client. - - When input file is not specified, it defaults to stdin. - When output file is not specified, it defaults to stdout. - -` -} - -func (p *cmdUpdateListEncrypt) SetFlags(f *flag.FlagSet) { - f.StringVar(&p.region, "region", "us", "region to use (us, jp, th, eu, id, kr)") -} - -func (p *cmdUpdateListEncrypt) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - if f.NArg() > 2 { - log.Println("Too many arguments specified.") - return subcommands.ExitUsageError - } - in := openfile(f.Arg(1)) - out := createfile(f.Arg(2)) - defer closefiles(in, out) - if err := pyxtea.EncipherStreamPadNull(getRegionKey(p.region), in, out); err != nil { - log.Println(err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} - -type cmdUpdateListDecrypt struct { - region string -} - -func (*cmdUpdateListDecrypt) Name() string { return "updatelist-decrypt" } -func (*cmdUpdateListDecrypt) Synopsis() string { - return "decrypts an updatelist" -} -func (*cmdUpdateListDecrypt) Usage() string { - return `updatelist-decrypt [-region ] [input file] [output file]: - Decrypts an encrypted updatelist back to plaintext XML. - - When input file is not specified, it defaults to stdin. - When output file is not specified, it defaults to stdout. - -` -} - -func (p *cmdUpdateListDecrypt) SetFlags(f *flag.FlagSet) { - f.StringVar(&p.region, "region", "us", "region to use (us, jp, th, eu, id, kr)") -} - -func (p *cmdUpdateListDecrypt) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - if f.NArg() > 2 { - log.Println("Too many arguments specified.") - return subcommands.ExitUsageError - } - in := openfile(f.Arg(0)) - out := createfile(f.Arg(1)) - defer closefiles(in, out) - if err := pyxtea.DecipherStreamTrimNull(getRegionKey(p.region), in, out); err != nil { - log.Println(err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} diff --git a/internal/app/pang/main.go b/internal/app/pang/main.go new file mode 100644 index 0000000..bdade86 --- /dev/null +++ b/internal/app/pang/main.go @@ -0,0 +1,29 @@ +package pang + +import ( + "context" + "flag" + "os" + + "github.com/google/subcommands" + "github.com/pangbox/pangfiles/internal/cli/commands/pak" + "github.com/pangbox/pangfiles/internal/cli/commands/updatelist" + "github.com/pangbox/pangfiles/internal/cli/commands/updatelistsrv" + "github.com/pangbox/pangfiles/internal/cli/commands/version" +) + +func Main() { + subcommands.Register(subcommands.HelpCommand(), "") + subcommands.Register(subcommands.FlagsCommand(), "") + subcommands.Register(subcommands.CommandsCommand(), "") + subcommands.Register(version.Command(), "") + subcommands.Register(pak.ExtractCommand(), "paks") + subcommands.Register(pak.MountCommand(), "paks") + subcommands.Register(updatelist.EncryptCommand(), "updatelists") + subcommands.Register(updatelist.DecryptCommand(), "updatelists") + subcommands.Register(updatelistsrv.ServeCommand(), "updatelists") + + flag.Parse() + ctx := context.Background() + os.Exit(int(subcommands.Execute(ctx))) +} diff --git a/internal/cli/commands/pak/extract.go b/internal/cli/commands/pak/extract.go new file mode 100644 index 0000000..31f05f3 --- /dev/null +++ b/internal/cli/commands/pak/extract.go @@ -0,0 +1,72 @@ +package pak + +import ( + "context" + "flag" + "log" + "os" + + "github.com/google/subcommands" + "github.com/pangbox/pangfiles/internal/region" + "github.com/pangbox/pangfiles/pak" +) + +type cmdPakExtract struct { + out string + region string + flat bool +} + +func (*cmdPakExtract) Name() string { return "pak-extract" } +func (*cmdPakExtract) Synopsis() string { return "extracts a set of pak files" } +func (*cmdPakExtract) Usage() string { + return `pak-extract [-flat] [-region ] [-o ] : + Extracts a set of pak files into a directory. + + This will treat the set of pak files as a single incremental archive. + +` +} + +func (p *cmdPakExtract) SetFlags(f *flag.FlagSet) { + f.StringVar(&p.out, "o", "", "destination to extract to") + f.BoolVar(&p.flat, "flat", false, "flatten the hierarchy (not implemented yet)") + f.StringVar(&p.region, "region", "", "region to use (us, jp, th, eu, id, kr)") +} + +func (p *cmdPakExtract) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if f.NArg() < 1 { + log.Println("Not enough arguments. Specify a pak or set of paks to extract.") + return subcommands.ExitUsageError + } + + if p.out != "" { + if err := os.MkdirAll(p.out, 0o775); err != nil { + log.Printf("Warning: couldn't make output dir: %v", err) + } + } + + fs, err := pak.LoadPaks(region.PakKey(p.region, f.Args()), f.Args()) + if err != nil { + log.Printf("Loading pak files: %v", err) + return subcommands.ExitFailure + } + + if p.flat { + if err = fs.ExtractFlat(p.out); err != nil { + log.Printf("Extracting pak files: %v", err) + return subcommands.ExitFailure + } + } else { + if err = fs.Extract(p.out); err != nil { + log.Printf("Extracting pak files: %v", err) + return subcommands.ExitFailure + } + } + + return subcommands.ExitSuccess +} + +func ExtractCommand() subcommands.Command { + return &cmdPakExtract{} +} diff --git a/cmd/pang/pak.go b/internal/cli/commands/pak/mount.go similarity index 54% rename from cmd/pang/pak.go rename to internal/cli/commands/pak/mount.go index 809daec..5eee94c 100644 --- a/cmd/pang/pak.go +++ b/internal/cli/commands/pak/mount.go @@ -1,4 +1,4 @@ -package main +package pak import ( "context" @@ -9,6 +9,8 @@ import ( "time" "github.com/google/subcommands" + "github.com/pangbox/pangfiles/internal/region" + "github.com/pangbox/pangfiles/internal/shell" "github.com/pangbox/pangfiles/pak" ) @@ -54,7 +56,7 @@ func (p *cmdPakMount) Execute(_ context.Context, f *flag.FlagSet, _ ...interface log.Printf("Warning: couldn't make mount dir: %v", err) } - fs, err := pak.LoadPaks(getPakKey(p.region, pakfiles), pakfiles) + fs, err := pak.LoadPaks(region.PakKey(p.region, pakfiles), pakfiles) if err != nil { log.Fatalf("Loading pak files: %v", err) } @@ -65,7 +67,7 @@ func (p *cmdPakMount) Execute(_ context.Context, f *flag.FlagSet, _ ...interface time.Sleep(100 * time.Millisecond) if stat, err := os.Stat(mountpoint); !os.IsNotExist(err) { if stat.IsDir() { - if err := openfolder(mountpoint); err != nil { + if err := shell.OpenFolder(mountpoint); err != nil { fmt.Printf("Tried to mount folder %s, failed: %v\n", mountpoint, err) } } @@ -82,58 +84,6 @@ func (p *cmdPakMount) Execute(_ context.Context, f *flag.FlagSet, _ ...interface return subcommands.ExitSuccess } -type cmdPakExtract struct { - out string - region string - flat bool -} - -func (*cmdPakExtract) Name() string { return "pak-extract" } -func (*cmdPakExtract) Synopsis() string { return "extracts a set of pak files" } -func (*cmdPakExtract) Usage() string { - return `pak-extract [-flat] [-region ] [-o ] : - Extracts a set of pak files into a directory. - - This will treat the set of pak files as a single incremental archive. - -` -} - -func (p *cmdPakExtract) SetFlags(f *flag.FlagSet) { - f.StringVar(&p.out, "o", "", "destination to extract to") - f.BoolVar(&p.flat, "flat", false, "flatten the hierarchy (not implemented yet)") - f.StringVar(&p.region, "region", "", "region to use (us, jp, th, eu, id, kr)") -} - -func (p *cmdPakExtract) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - if f.NArg() < 1 { - log.Println("Not enough arguments. Specify a pak or set of paks to extract.") - return subcommands.ExitUsageError - } - - if p.out != "" { - if err := os.MkdirAll(p.out, 0o775); err != nil { - log.Printf("Warning: couldn't make output dir: %v", err) - } - } - - fs, err := pak.LoadPaks(getPakKey(p.region, f.Args()), f.Args()) - if err != nil { - log.Printf("Loading pak files: %v", err) - return subcommands.ExitFailure - } - - if p.flat { - if err = fs.ExtractFlat(p.out); err != nil { - log.Printf("Extracting pak files: %v", err) - return subcommands.ExitFailure - } - } else { - if err = fs.Extract(p.out); err != nil { - log.Printf("Extracting pak files: %v", err) - return subcommands.ExitFailure - } - } - - return subcommands.ExitSuccess +func MountCommand() subcommands.Command { + return &cmdPakMount{} } diff --git a/internal/cli/commands/updatelist/decrypt.go b/internal/cli/commands/updatelist/decrypt.go new file mode 100644 index 0000000..e9e5a13 --- /dev/null +++ b/internal/cli/commands/updatelist/decrypt.go @@ -0,0 +1,52 @@ +package updatelist + +import ( + "context" + "flag" + "log" + + "github.com/google/subcommands" + "github.com/pangbox/pangfiles/crypto/pyxtea" + "github.com/pangbox/pangfiles/internal/region" +) + +type cmdUpdateListDecrypt struct { + region string +} + +func (*cmdUpdateListDecrypt) Name() string { return "updatelist-decrypt" } +func (*cmdUpdateListDecrypt) Synopsis() string { + return "decrypts an updatelist" +} +func (*cmdUpdateListDecrypt) Usage() string { + return `updatelist-decrypt [-region ] [input file] [output file]: + Decrypts an encrypted updatelist back to plaintext XML. + + When input file is not specified, it defaults to stdin. + When output file is not specified, it defaults to stdout. + +` +} + +func (p *cmdUpdateListDecrypt) SetFlags(f *flag.FlagSet) { + f.StringVar(&p.region, "region", "us", "region to use (us, jp, th, eu, id, kr)") +} + +func (p *cmdUpdateListDecrypt) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if f.NArg() > 2 { + log.Println("Too many arguments specified.") + return subcommands.ExitUsageError + } + in := openfile(f.Arg(0)) + out := createfile(f.Arg(1)) + defer closefiles(in, out) + if err := pyxtea.DecipherStreamTrimNull(region.Key(p.region), in, out); err != nil { + log.Println(err) + return subcommands.ExitFailure + } + return subcommands.ExitSuccess +} + +func DecryptCommand() subcommands.Command { + return &cmdUpdateListDecrypt{} +} diff --git a/internal/cli/commands/updatelist/encrypt.go b/internal/cli/commands/updatelist/encrypt.go new file mode 100644 index 0000000..ac2b8ac --- /dev/null +++ b/internal/cli/commands/updatelist/encrypt.go @@ -0,0 +1,52 @@ +package updatelist + +import ( + "context" + "flag" + "log" + + "github.com/google/subcommands" + "github.com/pangbox/pangfiles/crypto/pyxtea" + "github.com/pangbox/pangfiles/internal/region" +) + +type cmdUpdateListEncrypt struct { + region string +} + +func (*cmdUpdateListEncrypt) Name() string { return "updatelist-encrypt" } +func (*cmdUpdateListEncrypt) Synopsis() string { + return "encrypts an updatelist" +} +func (*cmdUpdateListEncrypt) Usage() string { + return `updatelist-encrypt [-region ] [input file] [output file]: + Encrypts an updatelist XML document for use with a client. + + When input file is not specified, it defaults to stdin. + When output file is not specified, it defaults to stdout. + +` +} + +func (p *cmdUpdateListEncrypt) SetFlags(f *flag.FlagSet) { + f.StringVar(&p.region, "region", "us", "region to use (us, jp, th, eu, id, kr)") +} + +func (p *cmdUpdateListEncrypt) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if f.NArg() > 2 { + log.Println("Too many arguments specified.") + return subcommands.ExitUsageError + } + in := openfile(f.Arg(1)) + out := createfile(f.Arg(2)) + defer closefiles(in, out) + if err := pyxtea.EncipherStreamPadNull(region.Key(p.region), in, out); err != nil { + log.Println(err) + return subcommands.ExitFailure + } + return subcommands.ExitSuccess +} + +func EncryptCommand() subcommands.Command { + return &cmdUpdateListEncrypt{} +} diff --git a/internal/cli/commands/updatelist/util.go b/internal/cli/commands/updatelist/util.go new file mode 100644 index 0000000..e1576ba --- /dev/null +++ b/internal/cli/commands/updatelist/util.go @@ -0,0 +1,38 @@ +package updatelist + +import ( + "log" + "os" +) + +func openfile(infile string) *os.File { + if infile == "" { + log.Println("Reading from stdin.") + return os.Stdin + } + in, err := os.Open(infile) + if err != nil { + log.Fatalf("Error opening input file %q: %s", infile, err) + } + return in +} + +func createfile(outfile string) *os.File { + if outfile == "" { + return os.Stdout + } + out, err := os.Create(outfile) + if err != nil { + log.Fatalf("Error opening output file %q: %s", outfile, err) + } + return out +} + +func closefiles(in *os.File, out *os.File) { + if err := in.Close(); err != nil { + log.Printf("Warning: an error occurred during close of input file: %s", err) + } + if err := out.Close(); err != nil { + log.Printf("Warning: an error occurred during close of output file: %s", err) + } +} diff --git a/internal/cli/commands/updatelistsrv/serve.go b/internal/cli/commands/updatelistsrv/serve.go new file mode 100644 index 0000000..3745a6f --- /dev/null +++ b/internal/cli/commands/updatelistsrv/serve.go @@ -0,0 +1,65 @@ +package updatelistsrv + +import ( + "context" + "flag" + "log" + "net/http" + "path/filepath" + + "github.com/google/subcommands" + "github.com/pangbox/pangfiles/internal/region" +) + +type cmdUpdateListServe struct { + region string + listen string +} + +func (*cmdUpdateListServe) Name() string { return "updatelist-serve" } +func (*cmdUpdateListServe) Synopsis() string { + return "serves an updatelist for a game folder" +} +func (*cmdUpdateListServe) Usage() string { + return `pak-extract [-region ] [-listen
] : + Serves an automatically updating updatelist for a game folder. + +` +} + +func (p *cmdUpdateListServe) SetFlags(f *flag.FlagSet) { + f.StringVar(&p.region, "region", "", "region to use (us, jp, th, eu, id, kr)") + f.StringVar(&p.listen, "listen", ":8080", "address to listen on") +} + +func (p *cmdUpdateListServe) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if f.NArg() > 1 { + log.Println("Too many arguments.") + return subcommands.ExitUsageError + } else if f.NArg() < 1 { + log.Println("Not enough arguments. Try specifying a game folder.") + return subcommands.ExitUsageError + } + + dir := f.Arg(0) + + key := region.PakKey(p.region, []string{ + filepath.Join(dir, "projectg*.pak"), + filepath.Join(dir, "ProjectG*.pak"), + }) + + s := server{ + key: key, + dir: dir, + cache: map[string]cacheentry{}, + } + if err := http.ListenAndServe(p.listen, &s); err != nil { + log.Println(err) + return subcommands.ExitFailure + } + return subcommands.ExitSuccess +} + +func ServeCommand() subcommands.Command { + return &cmdUpdateListServe{} +} diff --git a/cmd/pang/updatelistsrv.go b/internal/cli/commands/updatelistsrv/server.go similarity index 90% rename from cmd/pang/updatelistsrv.go rename to internal/cli/commands/updatelistsrv/server.go index 96c538c..d6b8925 100644 --- a/cmd/pang/updatelistsrv.go +++ b/internal/cli/commands/updatelistsrv/server.go @@ -1,9 +1,8 @@ -package main +package updatelistsrv import ( "bytes" "io" - "io/ioutil" "log" "net/http" "os" @@ -56,7 +55,7 @@ func (s *server) calcEntry(wg *sync.WaitGroup, entry *updatelist.FileInfo, f os. func (s *server) updateList(rw io.Writer) error { start := time.Now() - files, err := ioutil.ReadDir(s.dir) + files, err := os.ReadDir(s.dir) if err != nil { return err } @@ -78,12 +77,17 @@ func (s *server) updateList(rw io.Writer) error { continue } name := f.Name() + info, err := f.Info() + if err != nil { + log.Printf("Warning: error stat'ing file %q, skipping: %v", name, err) + continue + } s.mutex.RLock() cache, ok := s.cache[name] s.mutex.RUnlock() - if ok && cache.modTime == f.ModTime() && cache.fSize == f.Size() { + if ok && cache.modTime == info.ModTime() && cache.fSize == info.Size() { // Cache hit hit++ doc.UpdateFiles.Files = append(doc.UpdateFiles.Files, cache.fInfo) @@ -95,7 +99,7 @@ func (s *server) updateList(rw io.Writer) error { doc.UpdateFiles.Count++ entry := &doc.UpdateFiles.Files[len(doc.UpdateFiles.Files)-1] wg.Add(1) - go s.calcEntry(&wg, entry, f) + go s.calcEntry(&wg, entry, info) } } diff --git a/internal/cli/commands/version/command.go b/internal/cli/commands/version/command.go new file mode 100644 index 0000000..cca43ac --- /dev/null +++ b/internal/cli/commands/version/command.go @@ -0,0 +1,29 @@ +package version + +import ( + "context" + "flag" + "fmt" + + "github.com/google/subcommands" + "github.com/pangbox/pangfiles/version" +) + +type versionCmd struct{} + +func (versionCmd) Name() string { return "version" } +func (versionCmd) Synopsis() string { return "Prints version information to stdout." } +func (v versionCmd) Usage() string { return fmt.Sprintf("%s:\n %s\n", v.Name(), v.Synopsis()) } +func (versionCmd) SetFlags(*flag.FlagSet) {} +func (versionCmd) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus { + versionStr := "v" + version.Release + if version.GitCommit != "" { + versionStr += "+" + version.GitCommit + } + fmt.Println(versionStr) + return subcommands.ExitSuccess +} + +func Command() subcommands.Command { + return &versionCmd{} +} diff --git a/internal/region/key.go b/internal/region/key.go new file mode 100644 index 0000000..d3f66fc --- /dev/null +++ b/internal/region/key.go @@ -0,0 +1,62 @@ +package region + +import ( + "log" + "strings" + + "github.com/pangbox/pangfiles/crypto/pyxtea" + "github.com/pangbox/pangfiles/pak" +) + +var xteaKeys = []pyxtea.Key{ + pyxtea.KeyUS, + pyxtea.KeyJP, + pyxtea.KeyTH, + pyxtea.KeyEU, + pyxtea.KeyID, + pyxtea.KeyKR, +} + +var regionToKey = map[string]pyxtea.Key{ + "us": pyxtea.KeyUS, + "jp": pyxtea.KeyJP, + "th": pyxtea.KeyTH, + "eu": pyxtea.KeyEU, + "id": pyxtea.KeyID, + "kr": pyxtea.KeyKR, +} + +var keyToRegion = map[pyxtea.Key]string{ + pyxtea.KeyUS: "us", + pyxtea.KeyJP: "jp", + pyxtea.KeyTH: "th", + pyxtea.KeyEU: "eu", + pyxtea.KeyID: "id", + pyxtea.KeyKR: "kr", +} + +func Key(regionCode string) pyxtea.Key { + key, ok := regionToKey[regionCode] + if !ok { + log.Fatalf("Invalid region %q (valid regions: us, jp, th, eu, id, kr)", regionCode) + } + return key +} + +func ForKey(key pyxtea.Key) string { + region, ok := keyToRegion[key] + if !ok { + panic("programming error: unexpected key") + } + return region +} + +func PakKey(region string, patterns []string) pyxtea.Key { + if region == "" { + log.Println("Auto-detecting pak region (use -region to improve startup delay.)") + key := pak.MustDetectRegion(patterns, xteaKeys) + log.Printf("Detected pak region as %s.", strings.ToUpper(ForKey(key))) + return key + } + return Key(region) +} diff --git a/cmd/pang/shell_darwin.go b/internal/shell/shell_darwin.go similarity index 55% rename from cmd/pang/shell_darwin.go rename to internal/shell/shell_darwin.go index 78e24c3..f57cec9 100644 --- a/cmd/pang/shell_darwin.go +++ b/internal/shell/shell_darwin.go @@ -1,7 +1,7 @@ -package main +package shell import "os/exec" -func openfolder(folder string) error { +func OpenFolder(folder string) error { return exec.Command("open", folder).Start() } diff --git a/cmd/pang/shell_freebsd.go b/internal/shell/shell_freebsd.go similarity index 56% rename from cmd/pang/shell_freebsd.go rename to internal/shell/shell_freebsd.go index da43a54..94d4da5 100644 --- a/cmd/pang/shell_freebsd.go +++ b/internal/shell/shell_freebsd.go @@ -1,7 +1,7 @@ -package main +package shell import "os/exec" -func openfolder(folder string) error { +func OpenFolder(folder string) error { return exec.Command("xdg-open", folder).Start() } diff --git a/cmd/pang/shell_linux.go b/internal/shell/shell_linux.go similarity index 56% rename from cmd/pang/shell_linux.go rename to internal/shell/shell_linux.go index da43a54..94d4da5 100644 --- a/cmd/pang/shell_linux.go +++ b/internal/shell/shell_linux.go @@ -1,7 +1,7 @@ -package main +package shell import "os/exec" -func openfolder(folder string) error { +func OpenFolder(folder string) error { return exec.Command("xdg-open", folder).Start() } diff --git a/cmd/pang/shell_windows.go b/internal/shell/shell_windows.go similarity index 78% rename from cmd/pang/shell_windows.go rename to internal/shell/shell_windows.go index 52c73bd..12ccf29 100644 --- a/cmd/pang/shell_windows.go +++ b/internal/shell/shell_windows.go @@ -1,7 +1,7 @@ -package main +package shell import "golang.org/x/sys/windows" -func openfolder(folder string) error { +func OpenFolder(folder string) error { return windows.ShellExecute(windows.Handle(0), windows.StringToUTF16Ptr("explore"), windows.StringToUTF16Ptr(folder), nil, nil, windows.SW_SHOWNORMAL) } diff --git a/internal/shell/shellstub.go b/internal/shell/shellstub.go new file mode 100644 index 0000000..cd01bdc --- /dev/null +++ b/internal/shell/shellstub.go @@ -0,0 +1,11 @@ +//go:build !darwin && !freebsd && !linux && !windows +// +build !darwin,!freebsd,!linux,!windows + +package shell + +import "log" + +func OpenFolder(folder string) error { + log.Println(folder) + return nil +} diff --git a/version/version.go b/version/version.go index 041e97b..a02c68d 100644 --- a/version/version.go +++ b/version/version.go @@ -1,6 +1,6 @@ package version var ( - Release = "1.2.0" + Release = "0.1.0" GitCommit string )