Skip to content

Commit 3cda7cd

Browse files
committed
Initial commit
0 parents  commit 3cda7cd

27 files changed

+2668
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# If you prefer the allow list template instead of the deny list, see community template:
2+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3+
#
4+
# Binaries for programs and plugins
5+
*.exe
6+
*.exe~
7+
*.dll
8+
*.so
9+
*.dylib
10+
11+
# Test binary, built with `go test -c`
12+
*.test
13+
14+
# Output of the go coverage tool, specifically when used with LiteIDE
15+
*.out
16+
17+
# Dependency directories (remove the comment below to include it)
18+
# vendor/
19+
20+
# Go workspace file
21+
go.work

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 sukus
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

example/nds/dump/main.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// This example picks apart a ROM file into its components.
2+
// The binaries are dumped, the file system exported, icon converted,
3+
// the header exported as JSON, etc.
4+
5+
package main
6+
7+
import (
8+
"bytes"
9+
"encoding/json"
10+
"fmt"
11+
"image/png"
12+
"io"
13+
"io/fs"
14+
"log"
15+
"os"
16+
"path/filepath"
17+
18+
"github.com/sukus21/nintil/nds"
19+
"github.com/sukus21/nintil/util"
20+
)
21+
22+
func main() {
23+
if len(os.Args) < 2 {
24+
log.Fatal("must specify ROM as command line parameter")
25+
}
26+
27+
f := util.Must1(os.Open(os.Args[1]))
28+
defer f.Close()
29+
rom := util.Must1(nds.OpenROM(f))
30+
31+
// Dump header to JSON
32+
header := util.Must1(json.MarshalIndent(rom.GetHeader(), "", "\t"))
33+
util.Must(os.WriteFile("header.json", header, os.ModePerm))
34+
35+
// Dump icon
36+
buf := &bytes.Buffer{}
37+
util.Must(png.Encode(buf, rom.GetIcon()))
38+
util.Must(os.WriteFile("icon.png", buf.Bytes(), os.ModePerm))
39+
40+
// Dump title(s)
41+
titles := map[string]string{}
42+
for i := nds.TitleLanguage(0); nds.TitleLanguage(i) < nds.TitleLanguage_Count; i++ {
43+
title, err := rom.GetTitle(i)
44+
if err != nil {
45+
break
46+
}
47+
titles[i.String()] = title
48+
}
49+
titleJson := util.Must1(json.MarshalIndent(titles, "", "\t"))
50+
util.Must(os.WriteFile("title.json", titleJson, os.ModePerm))
51+
52+
// Dump ARM7/ARM9 binaries
53+
util.Must(os.WriteFile("ARM9.bin", rom.Arm9Binary, os.ModePerm))
54+
util.Must(os.WriteFile("ARM7.bin", rom.Arm7Binary, os.ModePerm))
55+
56+
// Dump NitroFS filesystem
57+
util.Must(fs.WalkDir(rom.Filesystem, ".", func(path string, d fs.DirEntry, err error) error {
58+
if err != nil || d.IsDir() {
59+
return err
60+
}
61+
62+
outPath := filepath.Join("nitrofs", path)
63+
dname := filepath.Dir(outPath)
64+
util.Must(os.MkdirAll(dname, os.ModePerm))
65+
f := util.Must1(os.Create(outPath))
66+
defer f.Close()
67+
68+
nitroFile := util.Must1(rom.Filesystem.Open(path))
69+
defer nitroFile.Close()
70+
util.Must1(io.Copy(f, nitroFile))
71+
72+
return nil
73+
}))
74+
75+
// Dump overlays
76+
util.Must(os.MkdirAll("overlays9", os.ModePerm))
77+
for i, v := range rom.Filesystem.GetArm9Overlays() {
78+
util.Must(os.WriteFile(fmt.Sprintf("overlays9/%d", i), v.Data(), os.ModePerm))
79+
}
80+
util.Must(os.MkdirAll("overlays7", os.ModePerm))
81+
for i, v := range rom.Filesystem.GetArm7Overlays() {
82+
util.Must(os.WriteFile(fmt.Sprintf("overlays7/%d", i), v.Data(), os.ModePerm))
83+
}
84+
85+
// Print mapping
86+
mapFile := util.Must1(os.Create("mapping.txt"))
87+
defer mapFile.Close()
88+
fmt.Fprint(mapFile, rom.String())
89+
}

example/nds/pit/savephotos/main.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Exports the first 10 save photos as PNG images.
2+
// The input ROM should be a valid Partners in Time ROM.
3+
// Otherwise the program will fail.
4+
5+
package main
6+
7+
import (
8+
"fmt"
9+
"image/png"
10+
"os"
11+
12+
"github.com/sukus21/nintil/nds"
13+
"github.com/sukus21/nintil/nds/pit"
14+
"github.com/sukus21/nintil/util"
15+
)
16+
17+
func main() {
18+
if len(os.Args) < 2 {
19+
fmt.Println("usage:", os.Args[0], "<PiT ROM path>")
20+
return
21+
}
22+
23+
f := util.Must1(os.Open(os.Args[1]))
24+
defer f.Close()
25+
rom := util.Must1(nds.OpenROM(f))
26+
27+
util.Must(os.MkdirAll("savephoto", os.ModePerm))
28+
for i := 0; i < 10; i++ {
29+
img := pit.DecodeSavePhoto(rom, i)
30+
ipath := fmt.Sprintf("savephotos/%d.png", i)
31+
outf := util.Must1(os.Create(ipath))
32+
png.Encode(outf, img)
33+
outf.Close()
34+
}
35+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/sukus21/nintil
2+
3+
go 1.22.0

lz10/lz10.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package lz10
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
)
8+
9+
var ErrNotLZ10 = errors.New("data is not LZ10 compressed")
10+
11+
// Decompresses a blob of LZ10 compressed data.
12+
// If data is not LZ10 compressed, ErrNotLZ10 is returned.
13+
func Decompress(dat []byte) ([]byte, error) {
14+
buf := bytes.NewReader(dat)
15+
dec, err := NewReader(buf)
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
return io.ReadAll(dec)
21+
}

lz10/reader.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package lz10
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
7+
"github.com/sukus21/nintil/util/ezbin"
8+
)
9+
10+
type LZ10Reader struct {
11+
rewind [0x1000]byte
12+
rewindPos int
13+
buf []byte
14+
src io.Reader
15+
blocks uint32
16+
}
17+
18+
// Creates a reader from an LZ10 compressed reader.
19+
// The source reader passed in is now owned by LZ10Reader.
20+
// If data is not LZ10 compressed, returns ErrNotLZ10.
21+
func NewReader(src io.Reader) (io.Reader, error) {
22+
h := uint32(0)
23+
binary.Read(src, binary.LittleEndian, &h)
24+
magic := h & 0xFF
25+
if magic != 0x10 {
26+
return nil, ErrNotLZ10
27+
}
28+
29+
return &LZ10Reader{
30+
src: src,
31+
blocks: h >> 8,
32+
}, nil
33+
}
34+
35+
func (r *LZ10Reader) readSrcByte() (byte, error) {
36+
instb := [1]byte{}
37+
if _, err := io.ReadFull(r.src, instb[:]); err != nil {
38+
return 0, err
39+
}
40+
return instb[0], nil
41+
}
42+
43+
func (r *LZ10Reader) readBuffered(buf []byte) (int, error) {
44+
n := copy(buf, r.buf)
45+
r.buf = r.buf[n:]
46+
return n, nil
47+
}
48+
49+
func (r *LZ10Reader) getRewind(offset int) byte {
50+
pos := (r.rewindPos - offset) & 0x0FFF
51+
return r.rewind[pos]
52+
}
53+
54+
func (r *LZ10Reader) appendData(dat byte) {
55+
r.buf = append(r.buf, dat)
56+
pos := r.rewindPos & 0x0FFF
57+
r.rewind[pos] = dat
58+
r.rewindPos++
59+
}
60+
61+
func (r *LZ10Reader) Read(buf []byte) (int, error) {
62+
// Empty buffered content
63+
if len(r.buf) != 0 {
64+
return r.readBuffered(buf)
65+
}
66+
67+
// No more blocks?
68+
if r.blocks == 0 {
69+
return 0, io.EOF
70+
}
71+
72+
inst, err := r.readSrcByte()
73+
if err != nil {
74+
return 0, err
75+
}
76+
77+
// Decompress data
78+
r.buf = make([]byte, 0, 0x1000)
79+
for i := 0; i < 8; i++ {
80+
if inst&0x80 != 0 {
81+
conf := ezbin.ReadSingleBigEnd[uint16](r.src)
82+
83+
// Rewind and copy old data
84+
rewindCount := int((conf >> 12) + 3)
85+
offset := int(conf & 0x0FFF)
86+
for i := 0; i < rewindCount; i++ {
87+
r.appendData(r.getRewind(offset + 1))
88+
}
89+
r.blocks -= uint32(rewindCount)
90+
} else {
91+
92+
// New single byte
93+
v, err := r.readSrcByte()
94+
if err != nil {
95+
return 0, err
96+
}
97+
r.appendData(v)
98+
r.blocks--
99+
}
100+
101+
inst <<= 1
102+
if r.blocks == 0 {
103+
break
104+
}
105+
}
106+
107+
// Read data written to buffer
108+
return r.readBuffered(buf)
109+
}
110+
111+
func (r *LZ10Reader) ReadByte() (byte, error) {
112+
var buf [1]byte
113+
_, err := r.Read(buf[:])
114+
return buf[0], err
115+
}

0 commit comments

Comments
 (0)