mirror of
https://github.com/Brawl345/stargazer.git
synced 2024-09-30 00:38:36 +02:00
Add re-packing (WIP)
This commit is contained in:
parent
260c707b73
commit
5a713daee6
30
README.md
30
README.md
@ -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
|
||||||
|
188
main.go
188
main.go
@ -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 {
|
|
||||||
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 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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
inputFile := flag.Arg(0)
|
|
||||||
outputDir := flag.Arg(1)
|
func extract() {
|
||||||
|
inputFile := flag.Arg(1)
|
||||||
|
outputDir := flag.Arg(2)
|
||||||
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
118
star.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user