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
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
```bash
stargazer <file> [output dir (optional)]
```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>
```
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
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
import (
"bytes"
"crypto/sha1"
"encoding/binary"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
const VERSION = "1.0"
const VERSION = "2.0"
type (
Header struct {
Unknown1stByte int8
Unknown2ndByte int8
Filesize uint32
FilenameSize int8
MaybePadding int8
}
Entry struct {
Header
FileName []byte
Content []byte
Sha1 [20]byte
}
Star struct {
Entries []Entry
}
)
func (e *Entry) GetFileName() string {
return string(e.FileName[:])
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 (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 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)
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))))
}
@ -147,3 +65,77 @@ func main() {
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
}