Rework CLI and add tests

This commit is contained in:
Andreas Bielawski 2022-06-25 23:23:53 +02:00
parent 5a713daee6
commit 9b9b06d47a
Signed by: Brawl
GPG Key ID: 851D5FF3B79056CA
13 changed files with 631 additions and 279 deletions

View File

@ -42,10 +42,15 @@ jobs:
with:
go-version: ${{ env.GOVER }}
- name: Run tests
working-directory: pkg/stargazer
run: go test
- name: Build
env:
GOOS: ${{ matrix.GOOS }}
GOARCH: ${{ matrix.GOARCH }}
working-directory: cmd/stargazer
run: go build -ldflags="-s -w" -o dist/$NAME-$GOOS-$GOARCH
- name: Rename binaries (Windows)

10
CHANGES.md Normal file
View File

@ -0,0 +1,10 @@
## Changelog
### 2.0.0 (Future)
* Reworked CLI
* Can be used as a library
### 1.0.0 (2022-06-22)
* Initial release

View File

@ -1,36 +1,61 @@
# Stargazer
Tool to extract and repack STAR files from the PSX used by its package manager "PackmanJr".
Library to handle STAR files from the PSX used by its package manager "PackmanJr". Comes with a CLI!
More info: <https://playstationdev.wiki/ps2devwiki/index.php/STAR_Files>
## Usage
### General usage
```txt
Usage: stargazer <operation> <arguments>
To extract files:
stargazer x <star file> [output dir (optional)]
To pack a folder:
stargazer p <input dir> <star file>
NAME:
stargazer - A tool to handle PSX STAR files
USAGE:
stargazer [global options] command [command options] [arguments...]
COMMANDS:
unpack, u Unpacks files from a STAR file
info, i Shows information about a STAR file
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help (default: false)
--quiet, -q Do not print any messages (default: false)
--version, -v print the version (default: false)
```
### Unpack
```txt
NAME:
stargazer unpack - Unpacks files from a STAR file
USAGE:
stargazer unpack [command options] [arguments...]
OPTIONS:
--input value, -i value Path to STAR file
--output value, -o value Path to output directory. Defaults to '<input file without .star>_extracted'
```
If no output directory is given, the file is extracted to the file name minus the extension plus "`_extracted`" (
e.g. `xPackmanJr_0.105.star` -> `xPackmanJr_0.105_extracted`). Same goes for packing (it will append `_packed.star`).
**NOTE:** Packing is experimental since I have no way to test it and I'm not sure about the limitations of the system (e.g. filenames). I also don't know if the order of the files is relevant.
### Info
```txt
NAME:
stargazer info - Shows information about a STAR file
USAGE:
stargazer info [command options] [arguments...]
OPTIONS:
--input value, -i value Path to STAR file
```
## Credits
Thanks to @martravi for helping with reverse-engineering!
## Changelog
### v2.0
- Add re-packing (experimental)
### v1.0
- Initial release
Thanks to [@martravi](https://github.com/martravi) for helping with reverse-engineering!

121
cmd/stargazer/main.go Normal file
View File

@ -0,0 +1,121 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/Brawl345/stargazer/pkg/stargazer"
"github.com/urfave/cli/v2"
)
var input string
var output string
var quiet bool
func main() {
app := &cli.App{
Name: "stargazer",
Usage: "A tool to handle PSX STAR files",
Version: "2.0.0",
Suggest: true,
EnableBashCompletion: true,
Authors: []*cli.Author{
{
Name: "Brawl345",
},
},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Do not print any messages",
Destination: &quiet,
},
},
Commands: []*cli.Command{
{
Name: "unpack",
Aliases: []string{"u"},
Usage: "Unpacks files from a STAR file",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "input",
Aliases: []string{"i"},
Required: true,
Usage: "Path to STAR file",
Destination: &input,
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Required: false,
Usage: "Path to output directory. Defaults to '<input file without .star>_extracted'",
Destination: &output,
},
},
Action: unpack,
},
{
Name: "info",
Aliases: []string{"i"},
Usage: "Shows information about a STAR file",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "input",
Aliases: []string{"i"},
Required: true,
Usage: "Path to STAR file",
Destination: &input,
},
},
Action: info,
},
},
}
err := app.Run(os.Args)
if err != nil {
fmt.Println("ERROR: ", err)
os.Exit(1)
}
}
func unpack(_ *cli.Context) error {
star, err := stargazer.LoadSTARFromFile(input)
if err != nil {
return err
}
if output == "" {
output = fmt.Sprintf("%s_extracted", filepath.Base(strings.TrimSuffix(input, filepath.Ext(input))))
}
if !quiet {
log.Printf("Will unpack to '%s'", output)
}
for _, entry := range star.Entries {
if !quiet {
log.Printf("Unpacking '%s'...\n", entry.Filename)
}
err := entry.Unpack(output)
if err != nil {
return err
}
}
return nil
}
func info(_ *cli.Context) error {
star, err := stargazer.LoadSTARFromFile(input)
if err != nil {
return err
}
fmt.Println(star.Info())
return nil
}

7
go.mod
View File

@ -1,3 +1,10 @@
module github.com/Brawl345/stargazer
go 1.18
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/urfave/cli/v2 v2.10.3 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
)

8
go.sum Normal file
View File

@ -0,0 +1,8 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo=
github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=

141
main.go
View File

@ -1,141 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
const VERSION = "2.0"
func usage() {
fmt.Println("Usage: stargazer <operation> <arguments>")
fmt.Println("")
fmt.Println(" To extract files:")
fmt.Println(" stargazer x <star file> [output dir (optional)]")
fmt.Println("")
fmt.Println(" To pack a folder:")
fmt.Println(" stargazer p <input dir> <star file>")
fmt.Println("")
os.Exit(1)
}
func extract() {
inputFile := flag.Arg(1)
outputDir := flag.Arg(2)
if outputDir == "" {
outputDir = fmt.Sprintf("%s_extracted", filepath.Base(strings.TrimSuffix(inputFile, filepath.Ext(inputFile))))
}
file, err := os.Open(inputFile)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
var star Star
log.Printf("Parsing '%s'...\n", inputFile)
for {
entry, err := ParseEntry(file)
if err != nil {
if err == io.EOF {
break
}
log.Fatalln(err)
}
star.Entries = append(star.Entries, *entry)
}
log.Printf("Extracting to '%s'...\n", outputDir)
for _, entry := range star.Entries {
log.Printf("Extracting %s...\n", entry.GetFileName())
err := entry.Extract(outputDir)
if err != nil {
log.Fatalln(err)
}
}
log.Println("Extraction complete!")
}
func pack() {
log.Printf("WARNING!!! Packing is experimental and may not work properly!\n")
inputDir := flag.Arg(1)
outputFile := flag.Arg(2)
if outputFile == "" {
outputFile = fmt.Sprintf("%s_packed.star", filepath.Base(inputDir))
}
log.Println("Reading files...")
var star Star
err := filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
entry := Entry{}
fp := strings.TrimPrefix(path, inputDir)
fp = strings.TrimPrefix(fp, string(os.PathSeparator))
entry.FileName = []byte(strings.ReplaceAll(fp, "\\", "/"))
entry.Content, err = ioutil.ReadFile(path)
if err != nil {
return err
}
entry.Header.Headersize = uint8(8 + len(entry.GetFileName()))
entry.Header.Filesize = uint32(len(entry.Content))
entry.Header.FilenameSize = uint8(len(entry.GetFileName()))
copy(entry.Sha1[:], entry.CalculateSha1())
star.Entries = append(star.Entries, entry)
return nil
})
if err != nil {
log.Fatalln(err)
}
log.Println("Packing...")
file, err := os.Create(outputFile)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
for _, entry := range star.Entries {
log.Printf("Packing %s...\n", entry.GetFileName())
err := entry.Pack(file)
if err != nil {
log.Fatalln(err)
}
}
log.Println("Packing complete!")
}
func main() {
fmt.Printf("Stargazer v%s\n", VERSION)
flag.Parse()
if flag.NArg() < 1 || flag.NArg() > 3 {
usage()
os.Exit(1)
}
operation := flag.Arg(0)
switch operation {
case "x":
extract()
case "p":
pack()
default:
usage()
}
}

20
pkg/stargazer/errors.go Normal file
View File

@ -0,0 +1,20 @@
package stargazer
import (
"errors"
"fmt"
)
var ErrNoEntries = errors.New("invalid STAR file - no file entries found")
type (
ErrSHA1Mismatch struct {
Expected string
Actual string
Filename string
}
)
func (e ErrSHA1Mismatch) Error() string {
return fmt.Sprintf("SHA1 mismatch on file '%s': expected %s, actual %s", e.Filename, e.Expected, e.Actual)
}

88
pkg/stargazer/star.go Normal file
View File

@ -0,0 +1,88 @@
package stargazer
import (
"fmt"
"os"
"path/filepath"
"strings"
)
type (
Star struct {
Entries []Entry
}
Entry struct {
Header
Content []byte
SHA1 [20]byte
}
Header struct {
Headersize uint8
Padding1 uint8
Filesize uint32
FilenameSize uint8
Padding2 uint8
Filename string
}
)
func (e *Entry) SHA1String() string {
return fmt.Sprintf("%x", e.SHA1)
}
func (e *Entry) Unpack(outputDir string) error {
fp := filepath.Join(outputDir, e.Filename)
err := os.MkdirAll(filepath.Dir(fp), os.ModePerm)
if err != nil {
return err
}
f, err := os.Create(fp)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(e.Content)
if err != nil {
return err
}
return nil
}
func (e *Entry) Info() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("%s\n", e.Filename))
sb.WriteString(fmt.Sprintf(" Filesize: %d bytes\n", e.Header.Filesize))
sb.WriteString(fmt.Sprintf(" SHA1: %s\n", e.SHA1String()))
return sb.String()
}
func (s *Star) Unpack(outputDir string) error {
for _, e := range s.Entries {
err := e.Unpack(outputDir)
if err != nil {
return err
}
}
return nil
}
func (s *Star) Info() string {
var sb strings.Builder
var contentSize uint64
for _, e := range s.Entries {
contentSize += uint64(e.Header.Filesize)
sb.WriteString(e.Info())
sb.WriteString("\n")
}
sb.WriteString(fmt.Sprintf("Total contents: %d\n", len(s.Entries)))
sb.WriteString(fmt.Sprintf("Total content size: %d bytes\n", contentSize))
return sb.String()
}

124
pkg/stargazer/stargazer.go Normal file
View File

@ -0,0 +1,124 @@
package stargazer
import (
"bytes"
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"os"
)
func parseEntry(file io.Reader) (*Entry, error) {
entry := Entry{}
err := binary.Read(file, binary.LittleEndian, &entry.Header.Headersize)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading headersize: %v", err)
}
err = binary.Read(file, binary.LittleEndian, &entry.Header.Padding1)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading padding1: %v", err)
}
err = binary.Read(file, binary.LittleEndian, &entry.Header.Filesize)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading filesize: %v", err)
}
err = binary.Read(file, binary.LittleEndian, &entry.Header.FilenameSize)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading filename size: %v", err)
}
err = binary.Read(file, binary.LittleEndian, &entry.Header.Padding2)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading padding2: %v", err)
}
filename := make([]byte, entry.Header.FilenameSize)
err = binary.Read(file, binary.LittleEndian, &filename)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading filename: %v", err)
}
entry.Header.Filename = string(filename)
entry.Content = make([]byte, entry.Header.Filesize)
err = binary.Read(file, binary.LittleEndian, &entry.Content)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading content: %v", err)
}
err = binary.Read(file, binary.LittleEndian, &entry.SHA1)
if err != nil {
if err == io.EOF {
return nil, io.EOF
}
return nil, fmt.Errorf("error reading sha1: %v", err)
}
h := sha1.New()
h.Write(entry.Content)
calculatedHash := h.Sum(nil)
if !bytes.Equal(calculatedHash, entry.SHA1[:]) {
return nil, ErrSHA1Mismatch{
Filename: entry.Header.Filename,
Expected: hex.EncodeToString(entry.SHA1[:]),
Actual: hex.EncodeToString(calculatedHash),
}
}
return &entry, nil
}
func LoadSTARFromFile(fp string) (*Star, error) {
file, err := os.Open(fp)
if err != nil {
return nil, err
}
defer file.Close()
return LoadSTAR(file)
}
func LoadSTAR(file io.Reader) (*Star, error) {
star := &Star{}
for {
entry, err := parseEntry(file)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
star.Entries = append(star.Entries, *entry)
}
if len(star.Entries) == 0 {
return nil, ErrNoEntries
}
return star, nil
}

View File

@ -0,0 +1,203 @@
package stargazer
import (
"os"
"path/filepath"
"testing"
)
func TestLoadSTARFromFile(t *testing.T) {
got, err := LoadSTARFromFile(filepath.Join("..", "..", "testdata", "testfile.star"))
if err != nil {
t.Error(err)
return
}
if len(got.Entries) != 3 {
t.Errorf("Expected 3 entries, got %d", len(got.Entries))
return
}
// Check first entry
if got.Entries[0].Header.Headersize != 22 {
t.Errorf("Expected header size of first entry to be 22 bytes long, got %d", got.Entries[0].Header.Headersize)
return
}
if got.Entries[0].Header.Padding1 != 0 {
t.Errorf("Expected first padding of first entry to be 0, got %d", got.Entries[0].Header.Padding1)
return
}
if got.Entries[0].Header.Filesize != 10928 {
t.Errorf("Expected filesize of first entry to be 10928, got %d", got.Entries[0].Header.Filesize)
return
}
if got.Entries[0].Header.FilenameSize != 14 {
t.Errorf("Expected filename size of first entry to be 14, got %d", got.Entries[0].Header.FilenameSize)
return
}
if got.Entries[0].Header.Padding2 != 0 {
t.Errorf("Expected second padding of first entry to be 0, got %d", got.Entries[0].Header.Padding2)
return
}
if got.Entries[0].Header.Filename != "NulledFile.rel" {
t.Errorf("Expected filename of first entry to be 'NulledFile.rel', got '%s'", got.Entries[0].Header.Filename)
return
}
if got.Entries[0].Content == nil {
t.Errorf("Expected content of first entry to be non-nil")
return
}
if uint32(len(got.Entries[0].Content)) != got.Entries[0].Header.Filesize {
t.Errorf("Expected content of first entry to be %d bytes long, got %d", got.Entries[0].Header.Filesize, len(got.Entries[0].Content))
return
}
if got.Entries[0].SHA1String() != "3d433fcbe9585b05ea877814bad60774ff8a9e7c" {
t.Errorf("Expected SHA1 of first entry to be '3d433fcbe9585b05ea877814bad60774ff8a9e7c', got '%s'", got.Entries[0].SHA1String())
return
}
// Check second entry
if got.Entries[1].Header.Headersize != 20 {
t.Errorf("Expected header size of second entry to be 22 bytes long, got %d", got.Entries[1].Header.Headersize)
return
}
if got.Entries[1].Header.Padding1 != 0 {
t.Errorf("Expected first padding of second entry to be 0, got %d", got.Entries[1].Header.Padding1)
return
}
if got.Entries[1].Header.Filesize != 313 {
t.Errorf("Expected filesize of second entry to be 313, got %d", got.Entries[1].Header.Filesize)
return
}
if got.Entries[1].Header.FilenameSize != 12 {
t.Errorf("Expected filename size of second entry to be 12, got %d", got.Entries[1].Header.FilenameSize)
return
}
if got.Entries[1].Header.Padding2 != 0 {
t.Errorf("Expected second padding of second entry to be 0, got %d", got.Entries[1].Header.Padding2)
return
}
if got.Entries[1].Header.Filename != "metadata.xml" {
t.Errorf("Expected filename of second entry to be 'metadata.xml', got '%s'", got.Entries[1].Header.Filename)
return
}
if got.Entries[1].Content == nil {
t.Errorf("Expected content of second entry to be non-nil")
return
}
if uint32(len(got.Entries[1].Content)) != got.Entries[1].Header.Filesize {
t.Errorf("Expected content of second entry to be %d bytes long, got %d", got.Entries[1].Header.Filesize, len(got.Entries[1].Content))
return
}
if got.Entries[1].SHA1String() != "2e59ec1846a50fb75042c6786299d13d8f5e39b6" {
t.Errorf("Expected SHA1 of second entry to be '2e59ec1846a50fb75042c6786299d13d8f5e39b6', got '%s'", got.Entries[1].SHA1String())
return
}
// Check third entry
if got.Entries[2].Header.Headersize != 19 {
t.Errorf("Expected header size of third entry to be 19 bytes long, got %d", got.Entries[2].Header.Headersize)
return
}
if got.Entries[2].Header.Padding1 != 0 {
t.Errorf("Expected first padding of third entry to be 0, got %d", got.Entries[2].Header.Padding1)
return
}
if got.Entries[2].Header.Filesize != 411 {
t.Errorf("Expected filesize of third entry to be 411, got %d", got.Entries[2].Header.Filesize)
return
}
if got.Entries[2].Header.FilenameSize != 11 {
t.Errorf("Expected filename size of third entry to be 11, got %d", got.Entries[2].Header.FilenameSize)
return
}
if got.Entries[2].Header.Padding2 != 0 {
t.Errorf("Expected second padding of third entry to be 0, got %d", got.Entries[2].Header.Padding2)
return
}
if got.Entries[2].Header.Filename != "install.txt" {
t.Errorf("Expected filename of third entry to be 'install.txt', got '%s'", got.Entries[2].Header.Filename)
return
}
if got.Entries[2].Content == nil {
t.Errorf("Expected content of third entry to be non-nil")
return
}
if uint32(len(got.Entries[2].Content)) != got.Entries[2].Header.Filesize {
t.Errorf("Expected content of third entry to be %d bytes long, got %d", got.Entries[2].Header.Filesize, len(got.Entries[2].Content))
return
}
if got.Entries[2].SHA1String() != "6c5768c3c82a174f0ea264c1c0e80450648da4c5" {
t.Errorf("Expected SHA1 of third entry to be '6c5768c3c82a174f0ea264c1c0e80450648da4c5', got '%s'", got.Entries[2].SHA1String())
return
}
}
func TestLoadSTARFromFileFail(t *testing.T) {
_, err := LoadSTARFromFile("invalid.star")
if err == nil {
t.Errorf("Expected error, got nil")
return
}
}
func TestStar_Unpack(t *testing.T) {
got, err := LoadSTARFromFile(filepath.Join("..", "..", "testdata", "testfile.star"))
if err != nil {
t.Error(err)
return
}
outputDir := t.TempDir()
err = got.Unpack(outputDir)
if err != nil {
t.Error(err)
return
}
if !fileExists(filepath.Join(outputDir, "NulledFile.rel")) {
t.Errorf("Expected file 'NulledFile.rel' to exist in output directory")
return
}
if !fileExists(filepath.Join(outputDir, "metadata.xml")) {
t.Errorf("Expected file 'metadata.xml' to exist in output directory")
return
}
if !fileExists(filepath.Join(outputDir, "install.txt")) {
t.Errorf("Expected file 'install.txt' to exist in output directory")
return
}
}
func fileExists(fileName string) bool {
if _, err := os.Stat(fileName); err == nil {
return true
}
return false
}

118
star.go
View File

@ -1,118 +0,0 @@
package main
import (
"bytes"
"crypto/sha1"
"encoding/binary"
"fmt"
"io"
"log"
"os"
"path/filepath"
)
type (
Header struct {
Headersize uint8
Padding1 uint8
Filesize uint32
FilenameSize uint8
Padding2 uint8
}
Entry struct {
Header
FileName []byte
Content []byte
Sha1 [20]byte
}
Star struct {
Entries []Entry
}
)
func (e *Entry) GetFileName() string {
return string(e.FileName[:])
}
func (e *Entry) CalculateSha1() []byte {
h := sha1.New()
h.Write(e.Content)
return h.Sum(nil)
}
func (e *Entry) GetSha1() string {
return fmt.Sprintf("%x", e.Sha1)
}
func (e *Entry) Extract(outputDir string) error {
fp := filepath.Join(outputDir, e.GetFileName())
err := os.MkdirAll(filepath.Dir(fp), os.ModePerm)
if err != nil {
return err
}
f, err := os.Create(fp)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(e.Content)
if err != nil {
return err
}
return nil
}
func (e *Entry) Pack(file *os.File) error {
err := binary.Write(file, binary.LittleEndian, e.Header)
if err != nil {
return err
}
_, err = file.Write(e.FileName)
if err != nil {
return err
}
_, err = file.Write(e.Content)
if err != nil {
return err
}
_, err = file.Write(e.Sha1[:])
if err != nil {
return err
}
return nil
}
func ParseEntry(file io.Reader) (*Entry, error) {
entry := Entry{}
err := binary.Read(file, binary.LittleEndian, &entry.Header.Headersize)
if err == io.EOF {
return nil, err
}
binary.Read(file, binary.LittleEndian, &entry.Header.Padding1)
binary.Read(file, binary.LittleEndian, &entry.Header.Filesize)
binary.Read(file, binary.LittleEndian, &entry.Header.FilenameSize)
binary.Read(file, binary.LittleEndian, &entry.Header.Padding2)
filename := make([]byte, entry.Header.FilenameSize)
binary.Read(file, binary.LittleEndian, &filename)
entry.FileName = filename
entry.Content = make([]byte, entry.Header.Filesize)
binary.Read(file, binary.LittleEndian, &entry.Content)
binary.Read(file, binary.LittleEndian, &entry.Sha1)
calculatedHash := entry.CalculateSha1()
if !bytes.Equal(calculatedHash, entry.Sha1[:]) {
log.Fatalln("Hash mismatch")
}
return &entry, nil
}

BIN
testdata/testfile.star vendored Normal file

Binary file not shown.