Add re-packing (WIP)

This commit is contained in:
Andreas Bielawski 2022-06-22 23:18:53 +02:00
parent 260c707b73
commit 5a713daee6
Signed by: Brawl
GPG Key ID: 851D5FF3B79056CA
3 changed files with 233 additions and 101 deletions

View File

@ -1,14 +1,36 @@
# Stargazer # Stargazer
Tool to extract STAR files from the PSX used by its package manager "PackmanJr". Tool to extract and repack STAR files from the PSX used by its package manager "PackmanJr".
More info: <https://playstationdev.wiki/ps2devwiki/index.php/STAR_Files>
## Usage ## Usage
```bash ```txt
stargazer <file> [output dir (optional)] Usage: stargazer <operation> <arguments>
To extract files:
stargazer x <star file> [output dir (optional)]
To pack a folder:
stargazer p <input dir> <star file>
``` ```
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`). 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.
## Credits ## Credits
Thanks to @martravi for helping with reverse-engineering! Thanks to @martravi for helping with reverse-engineering!
## Changelog
### v2.0
- Add re-packing (experimental)
### v1.0
- Initial release

186
main.go
View File

@ -1,115 +1,33 @@
package main package main
import ( import (
"bytes"
"crypto/sha1"
"encoding/binary"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
) )
const VERSION = "1.0" const VERSION = "2.0"
type ( func usage() {
Header struct { fmt.Println("Usage: stargazer <operation> <arguments>")
Unknown1stByte int8 fmt.Println("")
Unknown2ndByte int8 fmt.Println(" To extract files:")
Filesize uint32 fmt.Println(" stargazer x <star file> [output dir (optional)]")
FilenameSize int8 fmt.Println("")
MaybePadding int8 fmt.Println(" To pack a folder:")
} fmt.Println(" stargazer p <input dir> <star file>")
fmt.Println("")
Entry struct { os.Exit(1)
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 { func extract() {
h := sha1.New() inputFile := flag.Arg(1)
h.Write(e.Content) outputDir := flag.Arg(2)
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 ParseEntry(file io.Reader) (*Entry, error) {
entry := Entry{}
err := binary.Read(file, binary.LittleEndian, &entry.Header.Unknown1stByte)
if err == io.EOF {
return nil, err
}
binary.Read(file, binary.LittleEndian, &entry.Header.Unknown2ndByte)
binary.Read(file, binary.LittleEndian, &entry.Header.Filesize)
binary.Read(file, binary.LittleEndian, &entry.Header.FilenameSize)
binary.Read(file, binary.LittleEndian, &entry.Header.MaybePadding)
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
}
func main() {
fmt.Printf("Stargazer v%s\n", VERSION)
flag.Parse()
if flag.NArg() < 1 || flag.NArg() > 2 {
fmt.Println("Usage: stargazer <file> [output dir (optional)]")
os.Exit(1)
}
inputFile := flag.Arg(0)
outputDir := flag.Arg(1)
if outputDir == "" { if outputDir == "" {
outputDir = fmt.Sprintf("%s_extracted", filepath.Base(strings.TrimSuffix(inputFile, filepath.Ext(inputFile)))) outputDir = fmt.Sprintf("%s_extracted", filepath.Base(strings.TrimSuffix(inputFile, filepath.Ext(inputFile))))
} }
@ -147,3 +65,77 @@ func main() {
log.Println("Extraction complete!") 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()
}
}

118
star.go Normal file
View File

@ -0,0 +1,118 @@
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
}