-
Notifications
You must be signed in to change notification settings - Fork 1
/
xci.go
156 lines (136 loc) · 5.19 KB
/
xci.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package formats
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"strings"
cnmt "github.com/ralim/switchhost/formats/CNMT"
nacp "github.com/ralim/switchhost/formats/NACP"
nca "github.com/ralim/switchhost/formats/NCA"
partitionfs "github.com/ralim/switchhost/formats/partitionFS"
"github.com/ralim/switchhost/keystore"
"github.com/ralim/switchhost/settings"
"github.com/rs/zerolog/log"
)
const (
XCIHeaderSize = 0x200
XCIHeaderMagicStringOffset = 0x100
XCIRootPartionHeaderOffset = 0x130
)
func ParseXCIToMetaData(keystore *keystore.Keystore, settings *settings.Settings, reader io.ReaderAt) (FileInfo, error) {
info := FileInfo{}
header := make([]byte, XCIHeaderSize)
if _, err := reader.ReadAt(header, 0); err != nil {
return info, fmt.Errorf("reading XCI header failed %w", err)
}
XCIHeaderString := string(header[XCIHeaderMagicStringOffset : XCIHeaderMagicStringOffset+4])
if XCIHeaderString != "HEAD" {
return info, fmt.Errorf("invalid XCI headerBytes. Expected 'HEAD', got >%s<", XCIHeaderString)
}
rootPartitionOffset := binary.LittleEndian.Uint64(header[XCIRootPartionHeaderOffset : XCIRootPartionHeaderOffset+8])
rootHfs0, err := partitionfs.ReadSection(reader, int64(rootPartitionOffset))
if err != nil {
return info, fmt.Errorf("reading XCI PartionFS failed with - %w", err)
}
secureHfs0, secureOffset, err := readSecurePartition(reader, rootHfs0, rootPartitionOffset)
if err != nil {
return info, err
}
for _, pfs0File := range secureHfs0.FileEntryTable {
fileOffset := secureOffset + int64(pfs0File.StartOffset)
if strings.Contains(pfs0File.Name, "cnmt.nca") {
NCAMetaHeader, err := nca.ParseNCAEncryptedHeader(keystore, reader, uint64(fileOffset))
if err != nil {
return info, fmt.Errorf("ParseNCAEncryptedHeader failed with - %w", err)
}
section, err := nca.DecryptMetaNCADataSection(keystore, reader, NCAMetaHeader, uint64(fileOffset))
if err != nil {
return info, fmt.Errorf("DecryptMetaNCADataSection failed with - %w", err)
}
currpfs0, err := partitionfs.ReadSection(bytes.NewReader(section), 0x0)
if err != nil {
return info, fmt.Errorf("ReadSection failed with - %w", err)
}
currCnmt, err := cnmt.ParseBinary(currpfs0, section)
if err != nil {
return info, fmt.Errorf("ParseBinary failed with - %w", err)
}
if currCnmt.Type != cnmt.DLC {
nacp, err := nacp.ExtractNACP(keystore, currCnmt, reader, secureHfs0, uint64(secureOffset))
if err != nil {
log.Warn().Int("type", int(currCnmt.Type)).Err(err).Msg("Failed to extract NACP info from file")
} else {
// currCnmt.Ncap = nacp
info.EmbeddedTitle = nacp.GetSuggestedTitle(settings)
}
}
//Update the info
info.TitleID = currCnmt.TitleId
info.Version = currCnmt.Version
info.Type = currCnmt.Type
}
}
return info, nil
}
func readSecurePartition(file io.ReaderAt, hfs0 *partitionfs.PartionFS, rootPartitionOffset uint64) (*partitionfs.PartionFS, int64, error) {
for _, hfs0File := range hfs0.FileEntryTable {
offset := int64(rootPartitionOffset) + int64(hfs0File.StartOffset)
if hfs0File.Name == "secure" {
securePartition, err := partitionfs.ReadSection(file, offset)
if err != nil {
return nil, 0, err
}
return securePartition, offset, nil
}
}
return nil, 0, nil
}
func ValidateXCIHash(keystore *keystore.Keystore, settings *settings.Settings, reader ReaderRequired) error {
header := make([]byte, XCIHeaderSize)
if _, err := reader.ReadAt(header, 0); err != nil {
return fmt.Errorf("reading XCI header failed %w", err)
}
XCIHeaderString := string(header[XCIHeaderMagicStringOffset : XCIHeaderMagicStringOffset+4])
if XCIHeaderString != "HEAD" {
return fmt.Errorf("invalid XCI headerBytes. Expected 'HEAD', got >%s<", XCIHeaderString)
}
rootPartitionOffset := binary.LittleEndian.Uint64(header[XCIRootPartionHeaderOffset : XCIRootPartionHeaderOffset+8])
rootHfs0, err := partitionfs.ReadSection(reader, int64(rootPartitionOffset))
if err != nil {
return fmt.Errorf("reading XCI PartionFS failed with - %w", err)
}
secureHfs0, secureOffset, err := readSecurePartition(reader, rootHfs0, rootPartitionOffset)
if err != nil {
return err
}
var fileCNMT *cnmt.ContentMetaAttributes
for _, pfs0File := range secureHfs0.FileEntryTable {
if strings.Contains(pfs0File.Name, "cnmt.nca") {
fileOffset := secureOffset + int64(pfs0File.StartOffset)
NCAMetaHeader, err := nca.ParseNCAEncryptedHeader(keystore, reader, uint64(fileOffset))
if err != nil {
return fmt.Errorf("ParseNCAEncryptedHeader failed with - %w", err)
}
section, err := nca.DecryptMetaNCADataSection(keystore, reader, NCAMetaHeader, uint64(fileOffset))
if err != nil {
return fmt.Errorf("DecryptMetaNCADataSection failed with - %w", err)
}
currpfs0, err := partitionfs.ReadSection(bytes.NewReader(section), 0x0)
if err != nil {
return fmt.Errorf("ReadSection failed with - %w", err)
}
currCnmt, err := cnmt.ParseBinary(currpfs0, section)
if err != nil {
return fmt.Errorf("ParseBinary failed with - %w", err)
}
fileCNMT = currCnmt
}
}
for _, pfs0File := range secureHfs0.FileEntryTable {
if err := validatePFS0File(pfs0File, reader, fileCNMT, secureOffset); err != nil {
return err
}
}
return nil
}