-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
172 lines (144 loc) · 5.29 KB
/
index.js
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const fs = require('fs');
const path = require('path');
const hactool = require('./build/Release/node-hactool.node');
const inputTypes = [
'nca',
'pfs0',
'xci',
'romfs',
'hfs0',
'npdm',
'pk11',
'pk21',
'ini1',
'kip1',
'nax0',
'save',
'keygen',
];
const nodeHactool = {
error: function(errorMessage) {
return {
error: true,
errorMessage,
};
},
run: function(options, parameters) {
// Default the input file type to pfs0 (nsp).
let inputType = options?.type ?? 'pfs0';
// The type may not have been provided by the user.
const userProvidedType = typeof options?.type !== 'undefined';
// If we were given an input type make sure that it is one that could be processed.
if (userProvidedType && !inputTypes.includes(inputType)) {
return this.error(`The "${inputType}" input type is not a recognized input type.`);
}
// Make sure that the user provided a source file to process.
if (typeof options?.source === 'undefined' || typeof options.source !== 'string') {
return this.error('Provide a source file using the "source" option.');
}
// Make sure that the source file is readable.
try {
fs.accessSync(options.source, fs.constants.R_OK);
} catch {
return this.error(`The source file is not readable. Given: ${options.source}`);
}
if (userProvidedType) {
// Process only the type provided by the user.
try {
const results = JSON.parse(hactool.run(...[...parameters, '--intype', inputType, options.source]));
return results;
} catch (error) {
// Convert Napi::Error exceptions.
return this.error(error.message);
}
} else {
let results = {
warnings: [],
};
const sourceExtension = path.extname(options.source).replace('.', '').toLowerCase();
let orderedTypes = [];
let priorityType = '';
// We don't know the input file type because the user didn't provide the type.
// Guess the input type using the source file extension then put that input type
// on the top of the inputTypes list so we check that one first.
if (inputTypes[0] !== sourceExtension) {
if (['nsp', 'nsz'].includes(sourceExtension)) {
// This is most likely a PFS0 format file. Try that input type first.
priorityType = 'pfs0';
} else if (sourceExtension === 'xci') {
priorityType = 'xci';
}
if (priorityType !== '') {
orderedTypes = [priorityType, ...inputTypes.filter((t) => t !== priorityType)];
}
}
// The user did not specify an input type so try each type until we do not receive a type error.
orderedTypes.some((type) => {
let check = { error: true };
try {
check = JSON.parse(hactool.run(...[...parameters, '--intype', type, options.source]));
} catch (error) {
if (error instanceof TypeError) {
// There was a type error. This means the inputtype we just chose is incorrect.
// Append the message to the list of warnings then try the next file type.
results.warnings.push(`[${type}] ${error.message}`);
} else {
// There was an error, but not related to the filetype.
// This means the input type is correct, but an error occurred.
results = { ...this.error(error.message), ...results };
return true;
}
}
if (!check.error) {
// We received a valid response without an error.
// Combine the warnings from trying different file types as well as the warnings from the tool.
const warnings = [...results.warnings, ...check.warnings];
results = check;
results.warnings = warnings;
results.detectedType = type;
// Break out of the some loop.
return true;
}
});
return results;
}
},
information: function(options) {
const parameters = [
'hactool',
'--info',
];
return this.run(options, parameters);
},
extract: function(options) {
// Make sure that the user provided a source file to process.
if (typeof options?.outputDirectory === 'undefined') {
return this.error('Provide a full path to an output directory using the "outputDirectory " option.');
}
if (typeof options.outputDirectory !== 'string') {
return this.error('The path of the output directory must be a string.');
}
// Make sure that the user provided the file name of the file they want to extract.
if (typeof options?.fileName === 'undefined') {
return this.error('Provide the name of the file you want to extract using the "fileName" option.');
}
if (typeof options.fileName !== 'string') {
return this.error('The file name of the file you want to extract must be a string.');
}
// Make sure that the output directory is writable.
try {
fs.accessSync(options.outputDirectory, fs.constants.W_OK);
} catch {
return this.error(`The output directory is not writable. Given: ${options.outputDirectory}`);
}
const parameters = [
'hactool',
'--file',
options.fileName,
'--outdir',
options.outputDirectory,
];
return this.run(options, parameters);
},
};
module.exports = nodeHactool;