-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathncch.go
More file actions
104 lines (86 loc) · 2.38 KB
/
ncch.go
File metadata and controls
104 lines (86 loc) · 2.38 KB
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
package ctrsigcheck
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"fmt"
"io"
"github.com/connesc/ctrsigcheck/ctrutil"
)
// NCCH describes the result of NCCH parsing.
type NCCH struct {
PartitionID Hex64
ProgramID Hex64
Encrypted bool
ExeFS *ExeFS
}
// ParseNCCH extracts some information from the given NCCH file.
//
// No integrity checks are performed.
func ParseNCCH(input io.Reader) (*NCCH, error) {
reader := ctrutil.NewReader(input)
header := make([]byte, 0x1e0)
_, err := io.ReadFull(reader, header)
if err != nil {
return nil, fmt.Errorf("ncch: failed to read header: %w", err)
}
signature := header[:0x100]
if string(header[0x100:0x104]) != "NCCH" {
return nil, fmt.Errorf("ncch: magic not found")
}
partitionID := binary.LittleEndian.Uint64(header[0x108:])
programID := binary.LittleEndian.Uint64(header[0x118:])
version := binary.LittleEndian.Uint16(header[0x112:])
if version >= 3 {
return nil, fmt.Errorf("ncch: version must be less than 3: %d", version)
}
flags := header[0x188:0x190]
encrypted := flags[7]&0x4 == 0
exefsOffset := int64(binary.LittleEndian.Uint32(header[0x1a0:])) * 0x200
exefsSize := int64(binary.LittleEndian.Uint32(header[0x1a4:])) * 0x200
var exefs *ExeFS
if exefsSize > 0 {
err = reader.Discard(int64(exefsOffset) - reader.Offset())
if err != nil {
return nil, fmt.Errorf("ncch: failed to jump to ExeFS: %w", err)
}
data := io.LimitReader(reader, exefsSize)
if encrypted {
var key []byte
switch {
case flags[7]&0x1 == 0:
key = keygen(ncchKeyX, signature[:0x10])
case partitionID&1000000000 != 0:
key = fixedSystemKey
default:
key = zeroKey
}
exefsCipher, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("ncch: failed to initialize ExeFS cipher: %w", err)
}
exefsIV := make([]byte, exefsCipher.BlockSize())
if version == 1 {
binary.LittleEndian.PutUint64(exefsIV, partitionID)
binary.BigEndian.PutUint64(exefsIV, uint64(exefsOffset))
} else {
binary.BigEndian.PutUint64(exefsIV, partitionID)
exefsIV[8] = 2
}
data = cipher.StreamReader{
S: cipher.NewCTR(exefsCipher, exefsIV),
R: data,
}
}
exefs, err = ParseExeFS(data)
if err != nil {
return nil, err
}
}
return &NCCH{
PartitionID: Hex64(partitionID),
ProgramID: Hex64(programID),
Encrypted: encrypted,
ExeFS: exefs,
}, nil
}