launch.c
15.1 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
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
#include <PR/bcp.h>
#include <PR/R4300.h>
#include <PR/bbsim.h>
#include <PR/bbskapi.h>
#include <PR/os.h>
#include <PR/os_bbfs.h>
#include <PR/os_bbatb.h>
#include <PR/os_bbexec.h>
#include "common.h"
#include <PR/os_bbsa.h>
#include <PR/os_internal_reg.h>
/* 16 KB buffer for internal use */
static u8 gLaunchBuf[SK_RECRYPT_KEYLIST_SIZE] ALIGN_DCACHE;
static struct {
s32 sfd;
s32 dfd;
BbTicketBundle ticketBundle;
BbAppLaunchCrls appRls;
u32 recoveryBTgt; /* target for recovery. 0 for no recovery */
u32 destBProc; /* dest bytes processed since saGamePrelaunch() */
char launchAppName[16];
} gRecryptState;
/* bit flags for return */
#define SA_CID_EXT_NONE 0
#define SA_CID_EXT_APP 1
#define SA_CID_EXT_REC 2
int getAppExtensionsPresent(BbContentId cid)
{
char appName[16];
s32 fd;
int ret=0;
osBbSaCidToAppName(cid,gSaAppExt,appName);
if( (fd = osBbFOpen(appName,"r")) >= 0){
ret |= SA_CID_EXT_APP;
osBbFClose(fd);
}
osBbSaCidToAppName(cid,gSaRecExt,appName);
if( (fd = osBbFOpen(appName,"r")) >= 0){
OSBbStatBuf fsStat;
osBbFStat(fd, &fsStat, NULL, 0);
if(fsStat.size==0){
osBbFClose(fd);
osBbFDelete(appName);
}
else{
ret |= SA_CID_EXT_REC;
osBbFClose(fd);
}
}
return ret;
}
int readKeylist(u8 *keylist)
{
s32 fd;
int ret = SALAUNCH_OK;
if( (fd = osBbFOpen(gSaRecryptKeyFname,"r")) < 0){
return SALAUNCH_ERR_FILE_NOT_FOUND;
}
if(osBbFRead(fd, 0, keylist, KEYLIST_SIZE)<0){
ret = SALAUNCH_ERR_FS;
}
osBbFClose(fd);
return ret;
}
/* create if necessary */
int saveKeylist(u8 *keylist)
{
s32 fd;
int ret = SALAUNCH_OK;
char tmpFile[] = "_recrypt.sys";
osBbFDelete(tmpFile);
if( (fd = osBbFCreate(tmpFile,1,KEYLIST_SIZE)) < 0){
return SALAUNCH_ERR_FS;
}
if(osBbFWrite(fd, 0, keylist, KEYLIST_SIZE)<0){
ret = SALAUNCH_ERR_FS;
}
osBbFClose(fd);
osBbFRename(tmpFile,gSaRecryptKeyFname);
return ret;
}
/*
* convert cid to app name given desired extension
*/
void osBbSaCidToAppName(BbContentId cid, const char *ext, char *name)
{
int i,j;
u32 id=(u32)cid;
for(j=0,i=28;i>=0;i-=4)
name[j++]=NIBBLE_TO_ASCII((id>>i)&0xf);
name[j++]='.';
name[j++]=ext[0];
name[j++]=ext[1];
name[j++]=ext[2];
name[j++]='\0';
}
/*
* NOTE:
* ticketBundle (output) will not copy the ticket, so the memory
* used to hold the ticket ptr must not be altered until the
* ticketBundle is no longer needed.
*/
int osBbSaBundleTicket(BbTicket *ticket,
BbTicketBundle *ticketBundle,
BbAppLaunchCrls *appRls)
{
BbContentMetaDataHead *pCmdh;
ticketBundle->ticket = ticket;
pCmdh = &(ticketBundle->ticket->cmd.head);
PRINTF("content size: %08x\n", pCmdh->size);
/* create ticket bundle */
if(osBbSaCertCreateChain(ticket->head.issuer,
ticketBundle->ticketChain)<0){
PRINTF("osBbSaCertCreateChain() failure for ticket chain.\n");
return SALAUNCH_ERR_DATA;
}
if(osBbSaCertCreateChain(pCmdh->issuer, ticketBundle->cmdChain)<0){
PRINTF("osBbSaCertCreateChain() failure for cmd chain.\n");
return SALAUNCH_ERR_DATA;
}
if(osBbSaRlBundle(appRls)!=BB_SYSAPP_PASS)
return SALAUNCH_ERR_DATA;
return BB_SYSAPP_PASS;
}
static int prelaunchRecryptNotReq(BbContentMetaDataHead *pCmdh, u8 *keylist)
{
if(SK_API_SUCCESS != skLaunchSetup(&gRecryptState.ticketBundle,
&gRecryptState.appRls,
keylist)){
PRINTF("skLaunch() failure!\n");
return SALAUNCH_ERR_DATA;
}
osBbSaCidToAppName(pCmdh->id,gSaAppExt,gRecryptState.launchAppName);
return SALAUNCH_OK;
}
static int prelaunchFullRecrypt(BbContentMetaDataHead *pCmdh,u8 *keylist)
{
OSBbStatBuf fsStat;
char appName[16];
/* save keylist */
saveKeylist(keylist);
/* setup state for chunked personalization calls */
/* source fd */
osBbSaCidToAppName(pCmdh->id,gSaAppExt,appName);
if( (gRecryptState.sfd = osBbFOpen(appName,"r")) < 0){
PRINTF("Failed to open source app.\n");
return SALAUNCH_ERR_FS;
}
/* destination fd */
osBbSaCidToAppName(pCmdh->id,gSaRecExt,appName);
if((gRecryptState.dfd = osBbFCreate(appName, 1, 0)) < 0){
PRINTF("Failed to open dest app.\n");
return SALAUNCH_ERR_FS;
}
if((osBbFStat(gRecryptState.sfd, &fsStat, NULL, 0)) < 0){
return SALAUNCH_ERR_FS;
}
if(fsStat.size<pCmdh->size){
return SALAUNCH_ERR_FS;
}
gRecryptState.recoveryBTgt=0;
gRecryptState.destBProc=0;
return SALAUNCH_PERSONALIZE;
}
/* global state - no support for launching multiple games at a time.
* must be called from a single thread.
*/
int osBbSaGamePrelaunch(BbTicket *ticket)
{
BbTicketId tid;
BbContentMetaDataHead *pCmdh;
u8 *keylist=gLaunchBuf;
OSBbStatBuf fsStat;
char appName[16];
int i, ret;
u16 limit, window, cc[BB_MAX_CC];
pCmdh = &(ticket->cmd.head);
/* check limited play ticket*/
tid = ticket->head.tid;
limit = ticket->head.limit;
if (tid >= BB_TICKET_ID_LIMITED) {
skGetConsumption(&window, cc);
for (i=0; i<BB_MAX_CC; i++) {
if ( (tid & (BB_TICKET_ID_LIMITED-1)) == window+i ) {
if (cc[i]==limit && ticket->head.code < BB_LIMIT_CODE_EXTERN)
return SALAUNCH_ERR_EXPIRED;
else
break;
}
}
}
/* check keylist */
memset(keylist, 0, KEYLIST_SIZE);
if((ret = readKeylist(keylist)) == SALAUNCH_ERR_FS)
return ret;
if(BB_SYSAPP_PASS != osBbSaBundleTicket(ticket,
&gRecryptState.ticketBundle,
&gRecryptState.appRls)){
PRINTF("saBundleTicket() failure!\n");
return SALAUNCH_ERR_DATA;
}
/* default to recrypted app name for launch. change in code
* to follow if necessary.
*/
osBbSaCidToAppName(pCmdh->id,gSaRecExt,gRecryptState.launchAppName);
/* determine state of recryption by extension */
i = getAppExtensionsPresent(pCmdh->id);
switch(i){
case SA_CID_EXT_NONE:
return SALAUNCH_ERR_FS;
case SA_CID_EXT_APP:
/* original app directly from depot only */
ret = skRecryptBegin(&gRecryptState.ticketBundle,
&gRecryptState.appRls,
keylist);
switch(ret){
case SK_API_RECRYPT_NOT_REQUIRED:
return prelaunchRecryptNotReq(pCmdh, keylist);
case SK_API_RECRYPT_INCOMPLETE:
/* crossover call for inside SK */
if(skRecryptData(NULL, 0) != SK_API_SUCCESS)
return SALAUNCH_ERR_DATA;
return prelaunchFullRecrypt(pCmdh, keylist);
case SK_API_RECRYPT_NEW:
case SK_API_RECRYPT_COMPLETE:
/* full recrypt */
return prelaunchFullRecrypt(pCmdh, keylist);
default:
/* error case */
return SALAUNCH_ERR_DATA;
}
break;
case SA_CID_EXT_REC:
/* personalized version only */
ret = skLaunchSetup(&gRecryptState.ticketBundle,
&gRecryptState.appRls,
keylist);
switch(ret){
case SK_API_SUCCESS:
return SALAUNCH_OK;
case SK_API_RECRYPT_INCOMPLETE:
if(skRecryptBegin(&gRecryptState.ticketBundle,
&gRecryptState.appRls,
keylist)!=SK_API_RECRYPT_INCOMPLETE)
return SALAUNCH_ERR_DATA;
/* destination fd */
osBbSaCidToAppName(pCmdh->id,gSaRecExt,appName);
if((gRecryptState.dfd = osBbFOpen(appName,"rw")) < 0){
PRINTF("Failed to open dest app.\n");
return SALAUNCH_ERR_FS;
}
if((osBbFStat(gRecryptState.dfd, &fsStat, NULL, 0)) < 0){
return SALAUNCH_ERR_FS;
}
gRecryptState.recoveryBTgt=fsStat.size;
gRecryptState.destBProc=0;
return SALAUNCH_PERSONALIZE;
case SK_API_RECRYPT_NEW:
if((readKeylist(keylist) == SALAUNCH_ERR_FS) ||
(skRecryptListValid(keylist)==SK_API_FAIL)){
return SALAUNCH_ERR_KEYLIST;
}
default:
return SALAUNCH_ERR_DATA;
}
default:
/* both original and personalized versions (most likely recovery
* scenario)
*/
ret = skRecryptBegin(&gRecryptState.ticketBundle,
&gRecryptState.appRls,
keylist);
switch(ret){
case SK_API_RECRYPT_NOT_REQUIRED:
/* this case shouldn't occur */
/* remove <cid>.rec, since shouldn't exist */
osBbFDelete(gRecryptState.launchAppName);
return prelaunchRecryptNotReq(pCmdh, keylist);
case SK_API_RECRYPT_NEW:
/* allow flow-through to next case if keylist ok */
if((readKeylist(keylist) == SALAUNCH_ERR_FS) ||
(skRecryptListValid(keylist)==SK_API_FAIL)){
return SALAUNCH_ERR_KEYLIST;
}
case SK_API_RECRYPT_COMPLETE:
/* these cases shouldn't occur */
/* remove <cid>.rec, since shouldn't exist */
osBbFDelete(gRecryptState.launchAppName);
return prelaunchFullRecrypt(pCmdh, keylist);
case SK_API_RECRYPT_INCOMPLETE:
/* the recovery expected case */
/* setup state for chunked personalization calls */
/* source fd */
osBbSaCidToAppName(pCmdh->id,gSaAppExt,appName);
if( (gRecryptState.sfd = osBbFOpen(appName,"r")) < 0){
PRINTF("Failed to open source app.\n");
return SALAUNCH_ERR_FS;
}
/* destination fd */
osBbSaCidToAppName(pCmdh->id,gSaRecExt,appName);
if((gRecryptState.dfd = osBbFOpen(appName,"rw")) < 0){
PRINTF("Failed to open dest app.\n");
return SALAUNCH_ERR_FS;
}
if((osBbFStat(gRecryptState.dfd, &fsStat, NULL, 0)) < 0){
return SALAUNCH_ERR_FS;
}
if((gRecryptState.recoveryBTgt=fsStat.size)==0){
/* len=0 .rec file is illegal case (delete earlier) */
return SALAUNCH_ERR_FS;
}
gRecryptState.destBProc=0;
return SALAUNCH_PERSONALIZE;
default:
/* error case */
return SALAUNCH_ERR_DATA;
}
}
/* shouldn't come here */
return SALAUNCH_ERR_DATA;
}
/* return SALAUNCH_OK when done, SALAUNCH_PERSONALIZE indicates call again.
*
* chunkBuffer must always be of size RECRYPT_CHUNK_SIZE.
*/
int osBbSaGamePersonalize(u8 *chunkBuffer)
{
u32 chunkSize=RECRYPT_CHUNK_SIZE;
u32 contentSize=gRecryptState.ticketBundle.ticket->cmd.head.size;
int ret;
if(contentSize<(gRecryptState.destBProc+chunkSize))
chunkSize = contentSize-gRecryptState.destBProc;
if(gRecryptState.recoveryBTgt){
if(osBbFRead(gRecryptState.dfd,
gRecryptState.destBProc,
chunkBuffer, chunkSize) < 0) {
ret = SALAUNCH_ERR_FS;
goto chunkerr;
}
if(skRecryptComputeState(chunkBuffer,chunkSize)!=SK_API_SUCCESS){
ret = SALAUNCH_ERR_DATA;
goto chunkerr;
}
gRecryptState.destBProc += chunkSize;
/* need to crossover? */
if(gRecryptState.destBProc < contentSize &&
gRecryptState.destBProc == gRecryptState.recoveryBTgt){
gRecryptState.recoveryBTgt = 0;
if(osBbFRead(gRecryptState.sfd, 0, chunkBuffer, chunkSize) < 0){
ret = SALAUNCH_ERR_FS;
goto chunkerr;
}
if(skRecryptData(chunkBuffer,chunkSize)!=SK_API_SUCCESS){
ret = SALAUNCH_ERR_DATA;
goto chunkerr;
}
}
}
else{
if(osBbFRead(gRecryptState.sfd,
gRecryptState.destBProc ? RECRYPT_CHUNK_SIZE : 0,
chunkBuffer, chunkSize) < 0) {
ret = SALAUNCH_ERR_FS;
goto chunkerr;
}
if(skRecryptData(chunkBuffer, chunkSize)!=SK_API_SUCCESS){
ret = SALAUNCH_ERR_DATA;
goto chunkerr;
}
if(osBbFShuffle(gRecryptState.sfd, gRecryptState.dfd,
gRecryptState.destBProc!=0,
chunkBuffer, chunkSize) < 0) {
ret = SALAUNCH_ERR_FS;
goto chunkerr;
}
gRecryptState.destBProc += chunkSize;
}
/* finished? */
if(gRecryptState.destBProc>=contentSize){
/* done, prepare for launch */
char appName[16];
u8 *keylist = gLaunchBuf; /* keylist at beginning of heap from
* saGamePrelaunch()
*/
osBbSaCidToAppName(gRecryptState.ticketBundle.ticket->cmd.head.id,
gSaAppExt,appName);
osBbFClose(gRecryptState.sfd);
osBbFClose(gRecryptState.dfd);
osBbFDelete(appName);
if(skRecryptEnd(keylist)!=SK_API_SUCCESS){
osBbSaCidToAppName(gRecryptState.ticketBundle.ticket->cmd.head.id,
gSaRecExt,appName);
osBbFDelete(appName);
return SALAUNCH_ERR_DATA;
}
saveKeylist(keylist);
PRINTF("skLaunchSetup()\n");
if(skLaunchSetup(&gRecryptState.ticketBundle,
&gRecryptState.appRls, keylist)!=SK_API_SUCCESS){
return SALAUNCH_ERR_DATA;
}
return SALAUNCH_OK;
}
return SALAUNCH_PERSONALIZE;
chunkerr:
osBbFClose(gRecryptState.sfd);
osBbFClose(gRecryptState.dfd);
return ret;
}
int osBbSaGameLoadState(OSBbStateVector *sv, u32 bindings[4], BbTicketId tid)
{
return osBbLoadState(gRecryptState.launchAppName, tid, sv, bindings);
}
int osBbSaGameLaunch(OSBbLaunchMetaData *md)
{
u16 *blkList;
OSBbStatBuf fsStat;
s32 fsret,fd,listSize,loadAll;
u32 addr;
if( (fd = osBbFOpen(gRecryptState.launchAppName,"r")) < 0){
return SALAUNCH_ERR_FS;
}
memset(gLaunchBuf,0,sizeof(gLaunchBuf));
blkList = (u16 *)gLaunchBuf;
fsret = osBbFStat(fd, &fsStat, blkList, sizeof(gLaunchBuf)/2);
if(fsret !=0)
return SALAUNCH_ERR_FS;
listSize = fsStat.size/BB_FL_BLOCK_SIZE;
loadAll=!(gRecryptState.ticketBundle.ticket->cmd.head.execFlags &
BB_CMD_EXEC_RECRYPT);
osWritebackDCacheAll();
if ((addr = osBbLoadApp(md, blkList, listSize, loadAll)) != NULL) {
s32 mask;
mask = __osDisableInt();
skLaunch(addr);
__osRestoreInt(mask);
}
return SALAUNCH_ERR_DATA;
}