mirror of
https://github.com/Xeeynamo/sotn-decomp.git
synced 2024-11-26 22:40:33 +00:00
Asset cutscene two stages (#1738)
I completely rewrote the cutscene asset handler. Now instead of parsing the data from the original overlay into a C-like header file, it instead follows a two-stage process. This works by extracting it in `asset/` with `make extract_assets`, to then allow modders to modify the file and build it as a C-like header with `make build_assets`. This also aims to fix #1701 as the build process takes account of the two-stage process. I created a framework where each asset type should only make available the two methods `Extract` and `Build`. The entire transformation process should be isolated to not create cognitive overload like what we can find in `build.go`. I would need to migrate all the existing asset types to properly use this new framework. The old code served well enough to understand how to build the entire infrastructure, but it needs to be migrated using the new pattern. Last, but not least, I renamed `config/assets.us.weapon.yaml` to `config/assets.us.yaml` as it is now used by all the overlays
This commit is contained in:
parent
02e4c62f6a
commit
30652db2dd
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,7 +12,7 @@ generated.symbols.*.txt
|
||||
__pycache__
|
||||
|
||||
asm/
|
||||
assets/
|
||||
/assets/
|
||||
build/
|
||||
expected/
|
||||
disks/
|
||||
|
@ -95,7 +95,7 @@ extract_assets: $(SOTNASSETS)
|
||||
$(SOTNASSETS) stage extract -stage_ovl disks/$(VERSION)/ST/WRP/WRP.BIN -o assets/st/wrp
|
||||
$(SOTNASSETS) stage extract -stage_ovl disks/$(VERSION)/ST/RWRP/RWRP.BIN -o assets/st/rwrp
|
||||
$(SOTNASSETS) stage extract -stage_ovl disks/$(VERSION)/BOSS/MAR/MAR.BIN -o assets/boss/mar
|
||||
$(SOTNASSETS) config extract config/assets.us.weapon.yaml
|
||||
$(SOTNASSETS) config extract config/assets.us.yaml
|
||||
extract_assets_hd: $(SOTNASSETS)
|
||||
cd tools/sotn-assets; $(GO) install
|
||||
$(SOTNASSETS) stage extract -stage_ovl disks/pspeu/PSP_GAME/USRDIR/res/ps/hdbin/cen.bin -o assets/st/cen
|
||||
@ -110,7 +110,7 @@ build_assets: $(SOTNASSETS)
|
||||
$(SOTNASSETS) stage build_all -i assets/st/wrp -o src/st/wrp/
|
||||
$(SOTNASSETS) stage build_all -i assets/st/rwrp -o src/st/rwrp/
|
||||
$(SOTNASSETS) stage build_all -i assets/boss/mar -o src/boss/mar/
|
||||
$(SOTNASSETS) config build config/assets.$(VERSION).weapon.yaml
|
||||
$(SOTNASSETS) config build config/assets.$(VERSION).yaml
|
||||
build_assets_hd: $(SOTNASSETS)
|
||||
$(SOTNASSETS) stage build_all -i assets/st/cen -o src/st/cen/
|
||||
$(SOTNASSETS) stage build_all -i assets/st/wrp -o src/st/wrp/
|
||||
|
@ -50,3 +50,4 @@ typedef enum {
|
||||
#define SET_FLAG(x) CSOP_SET_FLAG, x
|
||||
#define LOAD_PORTRAIT(addr, id) CSOP_LOAD_PORTRAIT, script_word(addr), id
|
||||
#define SCRIPT_UNKNOWN_20(x) CSOP_SCRIPT_UNKNOWN_20, script_half(x)
|
||||
#define SCRIPT_UNKNOWN_23() CSOP_SCRIPT_UNKNOWN_23
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/assets"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/assets/cutscene"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v2"
|
||||
@ -30,30 +32,13 @@ type assetConfig struct {
|
||||
Files []assetFileEntry `yaml:"files"`
|
||||
}
|
||||
|
||||
type assetEntry struct {
|
||||
data []byte
|
||||
start int
|
||||
end int
|
||||
assetDir string
|
||||
srcDir string
|
||||
name string
|
||||
args []string
|
||||
ramBase psx.Addr
|
||||
}
|
||||
|
||||
type assetBuildEntry struct {
|
||||
assetDir string
|
||||
srcDir string
|
||||
name string
|
||||
}
|
||||
|
||||
var extractHandlers = map[string]func(assetEntry) error{
|
||||
"frameset": func(e assetEntry) error {
|
||||
var extractHandlers = map[string]func(assets.ExtractEntry) error{
|
||||
"frameset": func(e assets.ExtractEntry) error {
|
||||
var set []*[]sprite
|
||||
var err error
|
||||
if e.start != e.end {
|
||||
r := bytes.NewReader(e.data)
|
||||
set, _, err = readFrameSet(r, e.ramBase, e.ramBase.Sum(e.start))
|
||||
if e.Start != e.End {
|
||||
r := bytes.NewReader(e.Data)
|
||||
set, _, err = readFrameSet(r, e.RamBase, e.RamBase.Sum(e.Start))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -65,7 +50,7 @@ var extractHandlers = map[string]func(assetEntry) error{
|
||||
return err
|
||||
}
|
||||
|
||||
outPath := path.Join(e.assetDir, fmt.Sprintf("%s.frameset.json", e.name))
|
||||
outPath := path.Join(e.AssetDir, fmt.Sprintf("%s.frameset.json", e.Name))
|
||||
dir := filepath.Dir(outPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
fmt.Printf("failed to create directory %s: %v\n", dir, err)
|
||||
@ -73,31 +58,16 @@ var extractHandlers = map[string]func(assetEntry) error{
|
||||
}
|
||||
return os.WriteFile(outPath, content, 0644)
|
||||
},
|
||||
"cutscene": func(e assetEntry) error {
|
||||
if e.start == e.end {
|
||||
return fmt.Errorf("cutscene cannot be 0 bytes")
|
||||
}
|
||||
r := bytes.NewReader(e.data)
|
||||
script, err := parseCutsceneAsC(r, e.ramBase, e.ramBase.Sum(e.start), e.end - e.start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outPath := path.Join(e.srcDir, fmt.Sprintf("%s.h", e.name))
|
||||
dir := filepath.Dir(outPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
fmt.Printf("failed to create directory %s: %v\n", dir, err)
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(outPath, []byte(script), 0644)
|
||||
},
|
||||
"cutscene": cutscene.Handler.Extract,
|
||||
}
|
||||
|
||||
var buildHandlers = map[string]func(assetBuildEntry) error{
|
||||
"frameset": func(e assetBuildEntry) error {
|
||||
inFileName := path.Join(e.assetDir, fmt.Sprintf("%s.frameset.json", e.name))
|
||||
outFileName := path.Join(e.srcDir, fmt.Sprintf("%s.h", e.name))
|
||||
return buildFrameSet(inFileName, outFileName, e.name)
|
||||
var buildHandlers = map[string]func(assets.BuildEntry) error{
|
||||
"frameset": func(e assets.BuildEntry) error {
|
||||
inFileName := path.Join(e.AssetDir, fmt.Sprintf("%s.frameset.json", e.Name))
|
||||
outFileName := path.Join(e.SrcDir, fmt.Sprintf("%s.h", e.Name))
|
||||
return buildFrameSet(inFileName, outFileName, e.Name)
|
||||
},
|
||||
"cutscene": cutscene.Handler.Build,
|
||||
}
|
||||
|
||||
func parseArgs(entry []string) (offset int64, kind string, args []string, err error) {
|
||||
@ -134,7 +104,7 @@ func readConfig(path string) (*assetConfig, error) {
|
||||
|
||||
func enqueueExtractAssetEntry(
|
||||
eg *errgroup.Group,
|
||||
handler func(assetEntry) error,
|
||||
handler func(assets.ExtractEntry) error,
|
||||
assetDir string,
|
||||
srcDir string,
|
||||
name string,
|
||||
@ -144,15 +114,14 @@ func enqueueExtractAssetEntry(
|
||||
args []string,
|
||||
ramBase psx.Addr) {
|
||||
eg.Go(func() error {
|
||||
if err := handler(assetEntry{
|
||||
data: data,
|
||||
start: start,
|
||||
end: end,
|
||||
assetDir: assetDir,
|
||||
srcDir: srcDir,
|
||||
ramBase: ramBase,
|
||||
name: name,
|
||||
args: args,
|
||||
if err := handler(assets.ExtractEntry{
|
||||
Data: data,
|
||||
Start: start,
|
||||
End: end,
|
||||
AssetDir: assetDir,
|
||||
RamBase: ramBase,
|
||||
Name: name,
|
||||
Args: args,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("unable to extract asset %q: %v", name, err)
|
||||
}
|
||||
@ -206,18 +175,18 @@ func extractAssetFile(file assetFileEntry) error {
|
||||
|
||||
func enqueueBuildAssetEntry(
|
||||
eg *errgroup.Group,
|
||||
handler func(assetBuildEntry) error,
|
||||
handler func(assets.BuildEntry) error,
|
||||
assetDir,
|
||||
sourceDir,
|
||||
name string) {
|
||||
eg.Go(func() error {
|
||||
err := handler(assetBuildEntry{
|
||||
assetDir: assetDir,
|
||||
srcDir: sourceDir,
|
||||
name: name,
|
||||
err := handler(assets.BuildEntry{
|
||||
AssetDir: assetDir,
|
||||
SrcDir: sourceDir,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build asset %q: %v", name, err)
|
||||
return fmt.Errorf("unable to build asset %q at %q: %v", name, assetDir, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
33
tools/sotn-assets/assets/assets.go
Normal file
33
tools/sotn-assets/assets/assets.go
Normal file
@ -0,0 +1,33 @@
|
||||
package assets
|
||||
|
||||
import "github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
|
||||
type ExtractEntry struct {
|
||||
Data []byte
|
||||
Start int
|
||||
End int
|
||||
AssetDir string
|
||||
Name string
|
||||
Args []string
|
||||
RamBase psx.Addr
|
||||
}
|
||||
|
||||
type BuildEntry struct {
|
||||
AssetDir string
|
||||
SrcDir string
|
||||
Name string
|
||||
}
|
||||
|
||||
type Extracter interface {
|
||||
Extract(e ExtractEntry) error
|
||||
}
|
||||
|
||||
type Builder interface {
|
||||
Build(e BuildEntry) error
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Name() string
|
||||
Extracter
|
||||
Builder
|
||||
}
|
224
tools/sotn-assets/assets/cutscene/handler.go
Normal file
224
tools/sotn-assets/assets/cutscene/handler.go
Normal file
@ -0,0 +1,224 @@
|
||||
package cutscene
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/assets"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type handler struct{}
|
||||
|
||||
var Handler = &handler{}
|
||||
|
||||
func (h *handler) Name() string { return "cutscene" }
|
||||
|
||||
func (h *handler) Extract(e assets.ExtractEntry) error {
|
||||
if e.Start == e.End {
|
||||
return fmt.Errorf("a cutscene script cannot be 0 bytes")
|
||||
}
|
||||
r := bytes.NewReader(e.Data)
|
||||
script, err := parseScript(r, e.RamBase, e.RamBase.Sum(e.Start), e.End-e.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outFileName := assetPath(e.AssetDir, e.Name)
|
||||
dir := filepath.Dir(outFileName)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
fmt.Printf("failed to create directory %s: %v\n", dir, err)
|
||||
return err
|
||||
}
|
||||
yaml := "script:\n"
|
||||
for _, command := range script {
|
||||
if len(command) == 0 {
|
||||
continue
|
||||
}
|
||||
switch command[0] {
|
||||
case "TEXT":
|
||||
yaml += fmt.Sprintf(" - [TEXT, \"%s\"]\n", command[1])
|
||||
case "BYTE":
|
||||
yaml += fmt.Sprintf(" - [BYTE, %s]\n", command[1])
|
||||
default:
|
||||
yaml += fmt.Sprintf(" - [%s]\n", strings.Join(command, ", "))
|
||||
}
|
||||
}
|
||||
return os.WriteFile(outFileName, []byte(yaml), 0644)
|
||||
}
|
||||
|
||||
type scriptSrc struct {
|
||||
Script [][]string `yaml:"script"`
|
||||
}
|
||||
|
||||
func (h *handler) Build(e assets.BuildEntry) error {
|
||||
inFileName := assetPath(e.AssetDir, e.Name)
|
||||
data, err := os.ReadFile(inFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read cutscene file: %w", err)
|
||||
}
|
||||
var script scriptSrc
|
||||
if err := yaml.Unmarshal(data, &script); err != nil {
|
||||
return fmt.Errorf("failed to parse cutscene file: %w", err)
|
||||
}
|
||||
pool := getCommandPool()
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("// clang-format off\n")
|
||||
for i, args := range script.Script {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
op := args[0]
|
||||
if op == "TEXT" {
|
||||
text := args[1]
|
||||
for i, _ := range text {
|
||||
if text[i] == '\'' {
|
||||
sb.WriteString("'\\'',")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("'%c',", text[i]))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
continue
|
||||
}
|
||||
if op == "BYTE" {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("BYTE must have exactly one argument")
|
||||
}
|
||||
sb.WriteString(args[1])
|
||||
sb.WriteString(",\n")
|
||||
continue
|
||||
}
|
||||
cmd, found := pool[op]
|
||||
if !found {
|
||||
return fmt.Errorf("script %q does not have a command", args[0])
|
||||
}
|
||||
sb.WriteString(args[0])
|
||||
sb.WriteString("(")
|
||||
if len(cmd.params) != len(args)-1 {
|
||||
return fmt.Errorf("command %q at line %d expects %d arguments but got %d",
|
||||
op, i+1, len(cmd.params), len(args)-1)
|
||||
}
|
||||
sb.WriteString(strings.Join(args[1:], ","))
|
||||
sb.WriteString("),\n")
|
||||
}
|
||||
return os.WriteFile(sourcePath(e.SrcDir, e.Name), []byte(sb.String()), 0644)
|
||||
}
|
||||
|
||||
func assetPath(dir, name string) string {
|
||||
if name == "" {
|
||||
name = "cutscene_script"
|
||||
}
|
||||
return path.Join(dir, fmt.Sprintf("%s.yaml", name))
|
||||
}
|
||||
|
||||
func sourcePath(dir, name string) string {
|
||||
if name == "" {
|
||||
name = "cutscene_script"
|
||||
}
|
||||
return path.Join(dir, fmt.Sprintf("%s.h", name))
|
||||
}
|
||||
|
||||
type cmdDef struct {
|
||||
name string
|
||||
params []int
|
||||
}
|
||||
|
||||
var commandDefinitions = []cmdDef{
|
||||
{name: "END_CUTSCENE", params: []int{}},
|
||||
{name: "LINE_BREAK", params: []int{}},
|
||||
{name: "SET_SPEED", params: []int{1}},
|
||||
{name: "SET_WAIT", params: []int{1}},
|
||||
{name: "HIDE_DIALOG", params: []int{}},
|
||||
{name: "SET_PORTRAIT", params: []int{1, 1}},
|
||||
{name: "NEXT_DIALOG", params: []int{}},
|
||||
{name: "SET_POS", params: []int{1, 1}},
|
||||
{name: "CLOSE_DIALOG", params: []int{}},
|
||||
{name: "PLAY_SOUND", params: []int{2}},
|
||||
{name: "WAIT_FOR_SOUND", params: []int{}},
|
||||
{name: "SCRIPT_UNKNOWN_11", params: []int{}},
|
||||
{name: "SET_END", params: []int{4}},
|
||||
{name: "SCRIPT_UNKNOWN_13", params: []int{}},
|
||||
{name: "SCRIPT_UNKNOWN_14", params: []int{4, 4, 4}},
|
||||
{name: "SCRIPT_UNKNOWN_15", params: []int{4}},
|
||||
{name: "WAIT_FOR_FLAG", params: []int{1}},
|
||||
{name: "SET_FLAG", params: []int{1}},
|
||||
{name: "SCRIPT_UNKNOWN_18", params: []int{}},
|
||||
{name: "LOAD_PORTRAIT", params: []int{4, 1}},
|
||||
{name: "SCRIPT_UNKNOWN_20", params: []int{2}},
|
||||
{name: "SCRIPT_UNKNOWN_21", params: []int{}},
|
||||
{name: "RESET_FLAG", params: []int{1}},
|
||||
{name: "SCRIPT_UNKNOWN_23", params: []int{}},
|
||||
{name: "WAIT_FOR_FLAG_RESET", params: []int{1}},
|
||||
}
|
||||
|
||||
func getCommandPool() map[string]cmdDef {
|
||||
cmdPool := map[string]cmdDef{}
|
||||
for _, command := range commandDefinitions {
|
||||
cmdPool[command.name] = command
|
||||
}
|
||||
return cmdPool
|
||||
}
|
||||
|
||||
func parseScript(r io.ReadSeeker, baseAddr, addr psx.Addr, length int) ([][]string, error) {
|
||||
if err := addr.MoveFile(r, baseAddr); err != nil {
|
||||
return nil, fmt.Errorf("unable to read cutscene script: %w", err)
|
||||
}
|
||||
|
||||
script := make([][]string, 0)
|
||||
text := ""
|
||||
flushText := func() {
|
||||
if len(text) > 0 {
|
||||
script = append(script, []string{"TEXT", text})
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
read1 := func(r io.ReadSeeker) byte {
|
||||
b := make([]byte, 1)
|
||||
_, _ = r.Read(b)
|
||||
length -= 1
|
||||
return b[0]
|
||||
}
|
||||
read2 := func(r io.ReadSeeker) int {
|
||||
b := make([]byte, 2)
|
||||
_, _ = r.Read(b)
|
||||
length -= 2
|
||||
return int(b[1]) | (int(b[0]) << 4)
|
||||
}
|
||||
read4 := func(r io.ReadSeeker) int {
|
||||
b := make([]byte, 4)
|
||||
_, _ = r.Read(b)
|
||||
length -= 4
|
||||
return int(b[3]) | (int(b[2]) << 4) | (int(b[1]) << 8) | (int(b[0]) << 12) | 0x80100000
|
||||
}
|
||||
for length > 0 {
|
||||
op := int(read1(r))
|
||||
if op < 0x20 {
|
||||
flushText()
|
||||
command := []string{commandDefinitions[op].name}
|
||||
for _, param := range commandDefinitions[op].params {
|
||||
switch param {
|
||||
case 1:
|
||||
command = append(command, strconv.FormatInt(int64(read1(r)), 10))
|
||||
case 2:
|
||||
command = append(command, "0x"+strconv.FormatInt(int64(read2(r)), 16))
|
||||
case 4:
|
||||
command = append(command, "0x"+strconv.FormatInt(int64(read4(r)), 16))
|
||||
}
|
||||
}
|
||||
script = append(script, command)
|
||||
} else if op < 0x7F {
|
||||
text += string([]byte{byte(op)})
|
||||
} else {
|
||||
strByte := "0x" + strconv.FormatInt(int64(op), 16)
|
||||
script = append(script, []string{"BYTE", strByte})
|
||||
}
|
||||
}
|
||||
flushText()
|
||||
return script, nil
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readCutscene(r io.ReadSeeker, baseAddr, addr psx.Addr, length int) ([]string, error) {
|
||||
if err := addr.MoveFile(r, baseAddr); err != nil {
|
||||
return []string{}, fmt.Errorf("unable to read cutscene: %w", err)
|
||||
}
|
||||
|
||||
read1 := func(r io.ReadSeeker) byte {
|
||||
b := make([]byte, 1)
|
||||
_, _ = r.Read(b)
|
||||
length -= 1
|
||||
return b[0]
|
||||
}
|
||||
read2 := func(r io.ReadSeeker) int {
|
||||
b := make([]byte, 2)
|
||||
_, _ = r.Read(b)
|
||||
length -= 2
|
||||
return int(b[1]) | (int(b[0]) << 4)
|
||||
}
|
||||
read4 := func(r io.ReadSeeker) int {
|
||||
b := make([]byte, 4)
|
||||
_, _ = r.Read(b)
|
||||
length -= 4
|
||||
return int(b[3]) | (int(b[2]) << 4) | (int(b[1]) << 8) | (int(b[0]) << 12) | 0x80100000
|
||||
}
|
||||
script := make([]string, 0)
|
||||
for length > 0 {
|
||||
op := read1(r)
|
||||
switch op {
|
||||
case 0:
|
||||
script = append(script, "END_CUTSCENE()")
|
||||
case 1:
|
||||
script = append(script, "LINE_BREAK()")
|
||||
case 2:
|
||||
script = append(script, fmt.Sprintf("SET_SPEED(%d)", read1(r)))
|
||||
case 3:
|
||||
script = append(script, fmt.Sprintf("SET_WAIT(%d)", read1(r)))
|
||||
case 4:
|
||||
script = append(script, "HIDE_DIALOG()")
|
||||
case 5:
|
||||
script = append(script, fmt.Sprintf("SET_PORTRAIT(%d, %d)", read1(r), read1(r)))
|
||||
case 6:
|
||||
script = append(script, "NEXT_DIALOG()")
|
||||
case 7:
|
||||
script = append(script, fmt.Sprintf("SET_POS(%d, %d)", read1(r), read1(r)))
|
||||
case 8:
|
||||
script = append(script, "CLOSE_DIALOG()")
|
||||
case 9:
|
||||
script = append(script, fmt.Sprintf("PLAY_SOUND(0x%X)", read2(r)))
|
||||
case 10:
|
||||
script = append(script, "WAIT_FOR_SOUND()")
|
||||
case 11:
|
||||
script = append(script, "SCRIPT_UNKNOWN_11()")
|
||||
case 12:
|
||||
script = append(script, fmt.Sprintf(
|
||||
"SET_END(0x%08X)", read4(r)))
|
||||
case 13:
|
||||
script = append(script, "SCRIPT_UNKNOWN_13()")
|
||||
case 14:
|
||||
script = append(script, fmt.Sprintf(
|
||||
"SCRIPT_UNKNOWN_14(0x%08X, 0x%08X, 0x%08X)", read4(r), read4(r), read4(r)))
|
||||
case 15:
|
||||
script = append(script, fmt.Sprintf(
|
||||
"SCRIPT_UNKNOWN_15(0x%08X)", read4(r)))
|
||||
case 16:
|
||||
script = append(script, fmt.Sprintf("WAIT_FOR_FLAG(%d)", read1(r)))
|
||||
case 17:
|
||||
script = append(script, fmt.Sprintf("SET_FLAG(%d)", read1(r)))
|
||||
case 18:
|
||||
script = append(script, "SCRIPT_UNKOWN_18()")
|
||||
case 19:
|
||||
script = append(script, fmt.Sprintf(
|
||||
"LOAD_PORTRAIT(0x%08X, %d)", read4(r), read1(r)))
|
||||
case 20:
|
||||
script = append(script, fmt.Sprintf("SCRIPT_UNKNOWN_20(0x%X)", read2(r)))
|
||||
case 0x27:
|
||||
script = append(script, "'\\''")
|
||||
case 0xFF:
|
||||
script = append(script, "0xFF")
|
||||
default:
|
||||
if op >= 0x20 && op <= 0x7E {
|
||||
script = append(script, fmt.Sprintf("'%s'", string([]byte{op})))
|
||||
} else {
|
||||
script = append(script, fmt.Sprintf("0x%02X", op))
|
||||
}
|
||||
}
|
||||
}
|
||||
return script, nil
|
||||
}
|
||||
|
||||
func parseCutsceneAsC(r io.ReadSeeker, baseAddr, addr psx.Addr, length int) (string, error) {
|
||||
script, err := readCutscene(r, baseAddr, addr, length)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "// clang-format off\n" + strings.ReplaceAll(strings.Join(script, ",\n"), "',\n'", "','"), nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package datarange
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -6,20 +6,43 @@ import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
type dataRange struct {
|
||||
type DataRange struct {
|
||||
begin psx.Addr
|
||||
end psx.Addr
|
||||
}
|
||||
|
||||
func (r dataRange) Format(f fmt.State, c rune) {
|
||||
f.Write([]byte(fmt.Sprintf("(%s, %s)", r.begin, r.end)))
|
||||
func New(begin, end psx.Addr) DataRange {
|
||||
return DataRange{
|
||||
begin: begin,
|
||||
end: end,
|
||||
}
|
||||
}
|
||||
|
||||
func (r dataRange) empty() bool {
|
||||
func FromAddr(addr psx.Addr, len int) DataRange {
|
||||
return New(addr, addr.Sum(len))
|
||||
}
|
||||
|
||||
func FromAlignedAddr(addr psx.Addr, len int) DataRange {
|
||||
return New(addr, addr.Sum(len).Align4())
|
||||
}
|
||||
|
||||
func (r DataRange) Align4() DataRange {
|
||||
return New(r.begin, r.end.Align4())
|
||||
}
|
||||
|
||||
func (r DataRange) Format(f fmt.State, c rune) {
|
||||
_, _ = f.Write([]byte(fmt.Sprintf("(%s, %s)", r.begin, r.end)))
|
||||
}
|
||||
|
||||
func (r DataRange) Begin() psx.Addr { return r.begin }
|
||||
|
||||
func (r DataRange) End() psx.Addr { return r.end }
|
||||
|
||||
func (r DataRange) Empty() bool {
|
||||
return r.begin == psx.RamNull && r.end == psx.RamNull
|
||||
}
|
||||
|
||||
func mergeDataRanges(ranges []dataRange) dataRange {
|
||||
func MergeDataRanges(ranges []DataRange) DataRange {
|
||||
if len(ranges) == 0 {
|
||||
err := fmt.Errorf("no datarange, bug?!")
|
||||
panic(err)
|
||||
@ -42,30 +65,30 @@ func mergeDataRanges(ranges []dataRange) dataRange {
|
||||
}
|
||||
}
|
||||
|
||||
return dataRange{
|
||||
return DataRange{
|
||||
begin: ranges[0].begin,
|
||||
end: ranges[len(ranges)-1].end,
|
||||
}
|
||||
}
|
||||
|
||||
func consolidateDataRanges(ranges []dataRange) []dataRange {
|
||||
func ConsolidateDataRanges(ranges []DataRange) []DataRange {
|
||||
if len(ranges) == 0 {
|
||||
return []dataRange{}
|
||||
return []DataRange{}
|
||||
}
|
||||
|
||||
sort.Slice(ranges, func(i, j int) bool {
|
||||
return ranges[i].begin < ranges[j].begin
|
||||
})
|
||||
|
||||
for ranges[0].empty() {
|
||||
for ranges[0].Empty() {
|
||||
ranges = ranges[1:]
|
||||
}
|
||||
|
||||
consolidated := []dataRange{}
|
||||
consolidated := []DataRange{}
|
||||
first := 0
|
||||
for i := 0; i < len(ranges)-1; i++ {
|
||||
if ranges[i].end != ranges[i+1].begin {
|
||||
consolidated = append(consolidated, dataRange{
|
||||
consolidated = append(consolidated, DataRange{
|
||||
begin: ranges[first].begin,
|
||||
end: ranges[i].end,
|
||||
})
|
||||
@ -73,7 +96,7 @@ func consolidateDataRanges(ranges []dataRange) []dataRange {
|
||||
}
|
||||
}
|
||||
|
||||
return append(consolidated, dataRange{
|
||||
return append(consolidated, DataRange{
|
||||
begin: ranges[first].begin,
|
||||
end: ranges[len(ranges)-1].end,
|
||||
})
|
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"os"
|
||||
)
|
||||
@ -35,9 +36,9 @@ type gfx struct {
|
||||
indices []int
|
||||
}
|
||||
|
||||
func readGraphics(file *os.File, off psx.Addr) (gfx, dataRange, error) {
|
||||
func readGraphics(file *os.File, off psx.Addr) (gfx, datarange.DataRange, error) {
|
||||
if err := off.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return gfx{}, dataRange{}, err
|
||||
return gfx{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
// all the offsets are before the array, so it is easy to find where the offsets array ends
|
||||
@ -45,7 +46,7 @@ func readGraphics(file *os.File, off psx.Addr) (gfx, dataRange, error) {
|
||||
for {
|
||||
var offBank psx.Addr
|
||||
if err := binary.Read(file, binary.LittleEndian, &offBank); err != nil {
|
||||
return gfx{}, dataRange{}, err
|
||||
return gfx{}, datarange.DataRange{}, err
|
||||
}
|
||||
if offBank >= off {
|
||||
break
|
||||
@ -57,36 +58,33 @@ func readGraphics(file *os.File, off psx.Addr) (gfx, dataRange, error) {
|
||||
pool := map[psx.Addr]int{}
|
||||
pool[psx.RamNull] = -1
|
||||
blocks := []gfxBlock{}
|
||||
ranges := []dataRange{}
|
||||
ranges := []datarange.DataRange{}
|
||||
for _, blockOffset := range sortUniqueOffsets(blockOffsets) {
|
||||
if blockOffset == psx.RamNull { // exception for ST0
|
||||
continue
|
||||
}
|
||||
if err := blockOffset.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return gfx{}, dataRange{}, err
|
||||
return gfx{}, datarange.DataRange{}, err
|
||||
}
|
||||
var block gfxBlock
|
||||
if err := binary.Read(file, binary.LittleEndian, &block.kind); err != nil {
|
||||
return gfx{}, dataRange{}, err
|
||||
return gfx{}, datarange.DataRange{}, err
|
||||
}
|
||||
if err := binary.Read(file, binary.LittleEndian, &block.flags); err != nil {
|
||||
return gfx{}, dataRange{}, err
|
||||
return gfx{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
if block.kind == gfxKind(0xFFFF) && block.flags == 0xFFFF { // exception for ST0
|
||||
pool[blockOffset] = len(blocks)
|
||||
blocks = append(blocks, block)
|
||||
ranges = append(ranges, dataRange{
|
||||
begin: blockOffset,
|
||||
end: blockOffset.Sum(4),
|
||||
})
|
||||
ranges = append(ranges, datarange.FromAddr(blockOffset, 4))
|
||||
continue
|
||||
}
|
||||
|
||||
for {
|
||||
var entry gfxEntry
|
||||
if err := binary.Read(file, binary.LittleEndian, &entry); err != nil {
|
||||
return gfx{}, dataRange{}, err
|
||||
return gfx{}, datarange.DataRange{}, err
|
||||
}
|
||||
if entry.X == 0xFFFF && entry.Y == 0xFFFF {
|
||||
break
|
||||
@ -95,10 +93,7 @@ func readGraphics(file *os.File, off psx.Addr) (gfx, dataRange, error) {
|
||||
}
|
||||
pool[blockOffset] = len(blocks)
|
||||
blocks = append(blocks, block)
|
||||
ranges = append(ranges, dataRange{
|
||||
begin: blockOffset,
|
||||
end: blockOffset.Sum(4 + len(block.entries)*12 + 4),
|
||||
})
|
||||
ranges = append(ranges, datarange.FromAddr(blockOffset, 4+len(block.entries)*12+4))
|
||||
}
|
||||
|
||||
var g gfx
|
||||
@ -106,8 +101,5 @@ func readGraphics(file *os.File, off psx.Addr) (gfx, dataRange, error) {
|
||||
g.indices = append(g.indices, pool[blockOffset])
|
||||
}
|
||||
|
||||
return g, mergeDataRanges(append(ranges, dataRange{
|
||||
begin: off,
|
||||
end: off.Sum(len(blockOffsets) * 4),
|
||||
})), nil
|
||||
return g, datarange.MergeDataRanges(append(ranges, datarange.FromAddr(off, len(blockOffsets)*4))), nil
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"os"
|
||||
"slices"
|
||||
@ -77,12 +78,12 @@ func (r roomLayers) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func readLayers(file *os.File, off psx.Addr) ([]roomLayers, dataRange, error) {
|
||||
func readLayers(file *os.File, off psx.Addr) ([]roomLayers, datarange.DataRange, error) {
|
||||
if off == 0 {
|
||||
return nil, dataRange{}, nil
|
||||
return nil, datarange.DataRange{}, nil
|
||||
}
|
||||
if err := off.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
// when the data starts to no longer makes sense, we can assume we reached the end of the array
|
||||
@ -90,7 +91,7 @@ func readLayers(file *os.File, off psx.Addr) ([]roomLayers, dataRange, error) {
|
||||
layersOff := make([]psx.Addr, 2)
|
||||
for {
|
||||
if err := binary.Read(file, binary.LittleEndian, layersOff); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
if layersOff[0] <= psx.RamStageBegin || layersOff[0] >= off ||
|
||||
layersOff[1] <= psx.RamStageBegin || layersOff[1] >= off {
|
||||
@ -108,11 +109,11 @@ func readLayers(file *os.File, off psx.Addr) ([]roomLayers, dataRange, error) {
|
||||
}
|
||||
|
||||
if err := layerOffset.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
var l layer
|
||||
if err := binary.Read(file, binary.LittleEndian, &l); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
if l.Data != psx.RamNull || l.Tiledef != psx.RamNull || l.PackedInfo != 0 {
|
||||
pool[layerOffset] = &l
|
||||
@ -128,8 +129,5 @@ func readLayers(file *os.File, off psx.Addr) ([]roomLayers, dataRange, error) {
|
||||
roomsLayers[i].fg = pool[layerOffsets[i*2+0]]
|
||||
roomsLayers[i].bg = pool[layerOffsets[i*2+1]]
|
||||
}
|
||||
return roomsLayers, dataRange{
|
||||
begin: slices.Min(layerOffsets),
|
||||
end: off.Sum(count * 8),
|
||||
}, nil
|
||||
return roomsLayers, datarange.New(slices.Min(layerOffsets), off.Sum(count*8)), nil
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"io"
|
||||
"os"
|
||||
@ -74,7 +75,7 @@ func hydrateYOrderFields(x layouts, y layouts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readEntityLayout(file *os.File, off psx.Addr, count int, isX bool) (layouts, []dataRange, error) {
|
||||
func readEntityLayout(file *os.File, off psx.Addr, count int, isX bool) (layouts, []datarange.DataRange, error) {
|
||||
if err := off.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return layouts{}, nil, err
|
||||
}
|
||||
@ -89,7 +90,7 @@ func readEntityLayout(file *os.File, off psx.Addr, count int, isX bool) (layouts
|
||||
// the order of each layout entry must be preserved
|
||||
pool := map[psx.Addr]int{}
|
||||
blocks := [][]layoutEntry{}
|
||||
xRanges := []dataRange{}
|
||||
xRanges := []datarange.DataRange{}
|
||||
for _, blockOffset := range sortUniqueOffsets(blockOffsets) {
|
||||
if err := blockOffset.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return layouts{}, nil, err
|
||||
@ -115,13 +116,10 @@ func readEntityLayout(file *os.File, off psx.Addr, count int, isX bool) (layouts
|
||||
|
||||
pool[blockOffset] = len(blocks)
|
||||
blocks = append(blocks, entries)
|
||||
xRanges = append(xRanges, dataRange{
|
||||
begin: blockOffset,
|
||||
end: blockOffset.Sum(len(entries) * 10),
|
||||
})
|
||||
xRanges = append(xRanges, datarange.FromAddr(blockOffset, len(entries)*10))
|
||||
}
|
||||
// the very last entry needs to be aligned by 4
|
||||
xRanges[len(xRanges)-1].end = xRanges[len(xRanges)-1].end.Align4()
|
||||
xRanges[len(xRanges)-1] = xRanges[len(xRanges)-1].Align4()
|
||||
|
||||
l := layouts{Entities: blocks}
|
||||
for _, blockOffset := range blockOffsets {
|
||||
@ -137,25 +135,13 @@ func readEntityLayout(file *os.File, off psx.Addr, count int, isX bool) (layouts
|
||||
if err := hydrateYOrderFields(l, yLayouts); err != nil {
|
||||
return layouts{}, nil, fmt.Errorf("unable to populate YOrder field: %w", err)
|
||||
}
|
||||
xMerged := mergeDataRanges(xRanges)
|
||||
xMerged := datarange.MergeDataRanges(xRanges)
|
||||
yMerged := yRanges[1]
|
||||
return l, []dataRange{
|
||||
mergeDataRanges([]dataRange{
|
||||
{
|
||||
begin: off,
|
||||
end: endOfArray,
|
||||
},
|
||||
yRanges[0],
|
||||
}),
|
||||
mergeDataRanges([]dataRange{xMerged, yMerged}),
|
||||
return l, []datarange.DataRange{
|
||||
datarange.MergeDataRanges([]datarange.DataRange{datarange.New(off, endOfArray), yRanges[0]}),
|
||||
datarange.MergeDataRanges([]datarange.DataRange{xMerged, yMerged}),
|
||||
}, nil
|
||||
} else {
|
||||
return l, []dataRange{
|
||||
{
|
||||
begin: off,
|
||||
end: endOfArray,
|
||||
},
|
||||
mergeDataRanges(xRanges),
|
||||
}, nil
|
||||
return l, []datarange.DataRange{datarange.New(off, endOfArray), datarange.MergeDataRanges(xRanges)}, nil
|
||||
}
|
||||
}
|
||||
|
@ -5,24 +5,25 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type dataContainer[T any] struct {
|
||||
dataRange dataRange
|
||||
dataRange datarange.DataRange
|
||||
content T
|
||||
}
|
||||
|
||||
type ovl struct {
|
||||
ranges []dataRange
|
||||
ranges []datarange.DataRange
|
||||
rooms dataContainer[[]room]
|
||||
layers dataContainer[[]roomLayers]
|
||||
sprites dataContainer[spriteDefs]
|
||||
graphics dataContainer[gfx]
|
||||
layouts dataContainer[layouts]
|
||||
layoutsExtraRange dataRange
|
||||
layoutsExtraRange datarange.DataRange
|
||||
tileMaps dataContainer[map[psx.Addr][]byte]
|
||||
tileDefs dataContainer[map[psx.Addr]tileDef]
|
||||
}
|
||||
@ -74,14 +75,14 @@ func getOvlAssets(fileName string) (ovl, error) {
|
||||
}
|
||||
|
||||
// check for unused tile defs (CEN has one)
|
||||
for tileMapsRange.end < tileDefsRange.begin {
|
||||
offset := tileDefsRange.begin.Sum(-0x10)
|
||||
for tileMapsRange.End() < tileDefsRange.Begin() {
|
||||
offset := tileDefsRange.Begin().Sum(-0x10)
|
||||
unusedTileDef, unusedTileDefRange, err := readTiledef(file, offset)
|
||||
if err != nil {
|
||||
return ovl{}, fmt.Errorf("there is a gap between tileMaps and tileDefs: %w", err)
|
||||
}
|
||||
tileDefs[offset] = unusedTileDef
|
||||
tileDefsRange = mergeDataRanges([]dataRange{tileDefsRange, unusedTileDefRange})
|
||||
tileDefsRange = datarange.MergeDataRanges([]datarange.DataRange{tileDefsRange, unusedTileDefRange})
|
||||
}
|
||||
|
||||
sprites, spritesRange, err := readSpritesBanks(file, psx.RamStageBegin, header.Sprites)
|
||||
@ -98,7 +99,7 @@ func getOvlAssets(fileName string) (ovl, error) {
|
||||
if layoutOff == psx.RamNull {
|
||||
// some overlays have this field nulled, we have to find the offset ourselves
|
||||
// it should be usually be right after header.Graphics
|
||||
layoutOff = graphicsRange.end // ⚠️ assumption
|
||||
layoutOff = graphicsRange.End() // ⚠️ assumption
|
||||
}
|
||||
nLayouts := maxBy(rooms, func(r room) int { // ⚠️ assumption
|
||||
return int(r.EntityLayoutID)
|
||||
@ -110,7 +111,7 @@ func getOvlAssets(fileName string) (ovl, error) {
|
||||
}
|
||||
|
||||
return ovl{
|
||||
ranges: consolidateDataRanges([]dataRange{
|
||||
ranges: datarange.ConsolidateDataRanges([]datarange.DataRange{
|
||||
roomsRange,
|
||||
layersRange,
|
||||
spritesRange,
|
||||
@ -229,7 +230,7 @@ func info(fileName string) error {
|
||||
}
|
||||
|
||||
entries := []struct {
|
||||
dataRange dataRange
|
||||
dataRange datarange.DataRange
|
||||
name string
|
||||
comment string
|
||||
}{
|
||||
@ -247,15 +248,15 @@ func info(fileName string) error {
|
||||
fmt.Println(" - [0x0, .data, header]")
|
||||
for i := 0; i < len(entries); i++ {
|
||||
e := entries[i]
|
||||
s := fmt.Sprintf(" - [0x%X, .data, %s]", e.dataRange.begin.Real(psx.RamStageBegin), e.name)
|
||||
s := fmt.Sprintf(" - [0x%X, .data, %s]", e.dataRange.Begin().Real(psx.RamStageBegin), e.name)
|
||||
if e.comment != "" {
|
||||
s = fmt.Sprintf("%s # %s", s, e.comment)
|
||||
}
|
||||
fmt.Println(s)
|
||||
|
||||
// if there is a gap between the current entry and the next one, mark it as unrecognized data
|
||||
if i == len(entries)-1 || e.dataRange.end != entries[i+1].dataRange.begin {
|
||||
fmt.Printf(" - [0x%X, data]\n", e.dataRange.end.Real(psx.RamStageBegin))
|
||||
if i == len(entries)-1 || e.dataRange.End() != entries[i+1].dataRange.Begin() {
|
||||
fmt.Printf(" - [0x%X, data]\n", e.dataRange.End().Real(psx.RamStageBegin))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"os"
|
||||
)
|
||||
@ -21,27 +22,24 @@ func (r room) isTerminator() bool {
|
||||
return r.Left == 0x40
|
||||
}
|
||||
|
||||
func readRooms(file *os.File, off psx.Addr) ([]room, dataRange, error) {
|
||||
func readRooms(file *os.File, off psx.Addr) ([]room, datarange.DataRange, error) {
|
||||
if off == 0 {
|
||||
return nil, dataRange{}, nil
|
||||
return nil, datarange.DataRange{}, nil
|
||||
}
|
||||
if err := off.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
rooms := []room{}
|
||||
for {
|
||||
var room room
|
||||
if err := binary.Read(file, binary.LittleEndian, &room); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
if room.isTerminator() {
|
||||
break
|
||||
}
|
||||
rooms = append(rooms, room)
|
||||
}
|
||||
return rooms, dataRange{
|
||||
begin: off,
|
||||
end: off.Sum(len(rooms)*8 + 4),
|
||||
}, nil
|
||||
return rooms, datarange.FromAddr(off, len(rooms)*8+4), nil
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"io"
|
||||
"sort"
|
||||
@ -27,30 +28,27 @@ type spriteDefs struct {
|
||||
Indices []int `json:"indices"`
|
||||
}
|
||||
|
||||
func readSprites(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]sprite, dataRange, error) {
|
||||
func readSprites(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]sprite, datarange.DataRange, error) {
|
||||
if err := addr.MoveFile(r, baseAddr); err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("invalid sprites: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("invalid sprites: %w", err)
|
||||
}
|
||||
|
||||
var count uint16
|
||||
if err := binary.Read(r, binary.LittleEndian, &count); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
sprites := make([]sprite, count)
|
||||
if err := binary.Read(r, binary.LittleEndian, sprites); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
return sprites, dataRange{
|
||||
begin: addr,
|
||||
end: addr.Sum(4 + 0x16*int(count)).Align4(),
|
||||
}, nil
|
||||
return sprites, datarange.FromAlignedAddr(addr, 4+0x16*int(count)), nil
|
||||
}
|
||||
|
||||
func readFrameSet(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]*[]sprite, dataRange, error) {
|
||||
func readFrameSet(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]*[]sprite, datarange.DataRange, error) {
|
||||
if err := addr.MoveFile(r, baseAddr); err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("invalid sprite Indices: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("invalid sprite Indices: %w", err)
|
||||
}
|
||||
|
||||
// the end of the sprite array is the beginning of the earliest sprite offset
|
||||
@ -65,24 +63,20 @@ func readFrameSet(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]*[]sprite, dataRa
|
||||
|
||||
var spriteOffset psx.Addr
|
||||
if err := binary.Read(r, binary.LittleEndian, &spriteOffset); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
spriteOffsets = append(spriteOffsets, spriteOffset)
|
||||
if spriteOffset != psx.RamNull {
|
||||
if !spriteOffset.InRange(baseAddr, psx.RamGameEnd) {
|
||||
err := fmt.Errorf("sprite offset %s is not valid", spriteOffset)
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
earliestSpriteOff = min(earliestSpriteOff, spriteOffset)
|
||||
}
|
||||
}
|
||||
headerRange := dataRange{
|
||||
begin: addr,
|
||||
end: earliestSpriteOff,
|
||||
}
|
||||
|
||||
headerRange := datarange.New(addr, earliestSpriteOff)
|
||||
spriteBank := make([]*[]sprite, len(spriteOffsets))
|
||||
spriteRanges := []dataRange{}
|
||||
spriteRanges := []datarange.DataRange{}
|
||||
for i, offset := range spriteOffsets {
|
||||
if offset == psx.RamNull {
|
||||
spriteBank[i] = nil
|
||||
@ -90,18 +84,18 @@ func readFrameSet(r io.ReadSeeker, baseAddr, addr psx.Addr) ([]*[]sprite, dataRa
|
||||
}
|
||||
sprites, ranges, err := readSprites(r, baseAddr, offset)
|
||||
if err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("unable to read sprites: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("unable to read sprites: %w", err)
|
||||
}
|
||||
spriteBank[i] = &sprites
|
||||
spriteRanges = append(spriteRanges, ranges)
|
||||
}
|
||||
|
||||
return spriteBank, mergeDataRanges(append(spriteRanges, headerRange)), nil
|
||||
return spriteBank, datarange.MergeDataRanges(append(spriteRanges, headerRange)), nil
|
||||
}
|
||||
|
||||
func readSpritesBanks(r io.ReadSeeker, baseAddr, addr psx.Addr) (spriteDefs, dataRange, error) {
|
||||
func readSpritesBanks(r io.ReadSeeker, baseAddr, addr psx.Addr) (spriteDefs, datarange.DataRange, error) {
|
||||
if err := addr.MoveFile(r, baseAddr); err != nil {
|
||||
return spriteDefs{}, dataRange{}, err
|
||||
return spriteDefs{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
// start with a capacity of 24 as that's the length for all the stage overlays
|
||||
@ -117,7 +111,7 @@ func readSpritesBanks(r io.ReadSeeker, baseAddr, addr psx.Addr) (spriteDefs, dat
|
||||
|
||||
// the order sprites are stored must be preserved
|
||||
pool := map[psx.Addr][]*[]sprite{}
|
||||
spriteRanges := []dataRange{}
|
||||
spriteRanges := []datarange.DataRange{}
|
||||
for _, spriteAddr := range offBanks {
|
||||
if spriteAddr == psx.RamNull {
|
||||
continue
|
||||
@ -127,7 +121,7 @@ func readSpritesBanks(r io.ReadSeeker, baseAddr, addr psx.Addr) (spriteDefs, dat
|
||||
}
|
||||
bank, bankRange, err := readFrameSet(r, baseAddr, spriteAddr)
|
||||
if err != nil {
|
||||
return spriteDefs{}, dataRange{}, fmt.Errorf("unable to read sprite Indices: %w", err)
|
||||
return spriteDefs{}, datarange.DataRange{}, fmt.Errorf("unable to read sprite Indices: %w", err)
|
||||
}
|
||||
pool[spriteAddr] = bank
|
||||
spriteRanges = append(spriteRanges, bankRange)
|
||||
@ -162,5 +156,5 @@ func readSpritesBanks(r io.ReadSeeker, baseAddr, addr psx.Addr) (spriteDefs, dat
|
||||
return spriteDefs{
|
||||
Banks: banks,
|
||||
Indices: indices,
|
||||
}, mergeDataRanges(spriteRanges), nil
|
||||
}, datarange.MergeDataRanges(spriteRanges), nil
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"os"
|
||||
)
|
||||
@ -21,14 +22,14 @@ type tileDefPaths struct {
|
||||
Collisions string `json:"collisions"`
|
||||
}
|
||||
|
||||
func readTiledef(file *os.File, off psx.Addr) (tileDef, dataRange, error) {
|
||||
func readTiledef(file *os.File, off psx.Addr) (tileDef, datarange.DataRange, error) {
|
||||
if err := off.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
offsets := make([]psx.Addr, 4)
|
||||
if err := binary.Read(file, binary.LittleEndian, offsets); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
td := tileDef{
|
||||
@ -39,48 +40,45 @@ func readTiledef(file *os.File, off psx.Addr) (tileDef, dataRange, error) {
|
||||
}
|
||||
|
||||
if err := offsets[0].MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
if _, err := file.Read(td.tiles); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
if err := offsets[1].MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
if _, err := file.Read(td.pages); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
if err := offsets[2].MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
if _, err := file.Read(td.cluts); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
if err := offsets[3].MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
if _, err := file.Read(td.cols); err != nil {
|
||||
return tileDef{}, dataRange{}, err
|
||||
return tileDef{}, datarange.DataRange{}, err
|
||||
}
|
||||
|
||||
return td, dataRange{
|
||||
begin: offsets[0],
|
||||
end: off.Sum(0x10),
|
||||
}, nil
|
||||
return td, datarange.New(offsets[0], off.Sum(0x10)), nil
|
||||
}
|
||||
|
||||
func readAllTiledefs(file *os.File, roomLayers []roomLayers) (map[psx.Addr]tileDef, dataRange, error) {
|
||||
ranges := []dataRange{}
|
||||
func readAllTiledefs(file *os.File, roomLayers []roomLayers) (map[psx.Addr]tileDef, datarange.DataRange, error) {
|
||||
ranges := []datarange.DataRange{}
|
||||
processed := map[psx.Addr]tileDef{}
|
||||
for _, rl := range roomLayers {
|
||||
if rl.fg != nil {
|
||||
if _, found := processed[rl.fg.Tiledef]; !found {
|
||||
td, r, err := readTiledef(file, rl.fg.Tiledef)
|
||||
if err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("unable to read fg tiledef: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("unable to read fg tiledef: %w", err)
|
||||
}
|
||||
processed[rl.fg.Tiledef] = td
|
||||
ranges = append(ranges, r)
|
||||
@ -90,12 +88,12 @@ func readAllTiledefs(file *os.File, roomLayers []roomLayers) (map[psx.Addr]tileD
|
||||
if _, found := processed[rl.bg.Tiledef]; !found {
|
||||
td, r, err := readTiledef(file, rl.bg.Tiledef)
|
||||
if err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("unable to read fg tiledef: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("unable to read fg tiledef: %w", err)
|
||||
}
|
||||
processed[rl.bg.Tiledef] = td
|
||||
ranges = append(ranges, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return processed, mergeDataRanges(ranges), nil
|
||||
return processed, datarange.MergeDataRanges(ranges), nil
|
||||
}
|
||||
|
@ -2,33 +2,31 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/datarange"
|
||||
"github.com/xeeynamo/sotn-decomp/tools/sotn-assets/psx"
|
||||
"os"
|
||||
)
|
||||
|
||||
func readTilemap(file *os.File, layer *layer) ([]byte, dataRange, error) {
|
||||
func readTilemap(file *os.File, layer *layer) ([]byte, datarange.DataRange, error) {
|
||||
if err := layer.Data.MoveFile(file, psx.RamStageBegin); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
data := make([]byte, layer.tilemapFileSize())
|
||||
if _, err := file.Read(data); err != nil {
|
||||
return nil, dataRange{}, err
|
||||
return nil, datarange.DataRange{}, err
|
||||
}
|
||||
return data, dataRange{
|
||||
begin: layer.Data,
|
||||
end: layer.Data.Sum(len(data)),
|
||||
}, nil
|
||||
return data, datarange.FromAddr(layer.Data, len(data)), nil
|
||||
}
|
||||
|
||||
func readAllTileMaps(file *os.File, roomLayers []roomLayers) (map[psx.Addr][]byte, dataRange, error) {
|
||||
ranges := []dataRange{}
|
||||
func readAllTileMaps(file *os.File, roomLayers []roomLayers) (map[psx.Addr][]byte, datarange.DataRange, error) {
|
||||
ranges := []datarange.DataRange{}
|
||||
processed := map[psx.Addr][]byte{}
|
||||
for _, rl := range roomLayers {
|
||||
if rl.fg != nil {
|
||||
if _, found := processed[rl.fg.Data]; !found {
|
||||
td, r, err := readTilemap(file, rl.fg)
|
||||
if err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("unable to read fg tilemap: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("unable to read fg tilemap: %w", err)
|
||||
}
|
||||
processed[rl.fg.Data] = td
|
||||
ranges = append(ranges, r)
|
||||
@ -38,12 +36,12 @@ func readAllTileMaps(file *os.File, roomLayers []roomLayers) (map[psx.Addr][]byt
|
||||
if _, found := processed[rl.bg.Data]; !found {
|
||||
td, r, err := readTilemap(file, rl.bg)
|
||||
if err != nil {
|
||||
return nil, dataRange{}, fmt.Errorf("unable to read fg tilemap: %w", err)
|
||||
return nil, datarange.DataRange{}, fmt.Errorf("unable to read fg tilemap: %w", err)
|
||||
}
|
||||
processed[rl.bg.Data] = td
|
||||
ranges = append(ranges, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return processed, mergeDataRanges(ranges), nil
|
||||
return processed, datarange.MergeDataRanges(ranges), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user