meta.c 11.4 KB
#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 "common.h"
#include <PR/os_bbsa.h>
#include <bb_nn.h>
#include <sha1.h>

int getTicketStatus( BbTicket *pTick, u16 window, u16 *cc);
int updateLPWindow( OSBbSaTickets *tickets, u16 *window, u16 *cc);

int _osBbSaVerifyTickets(OSBbSaTickets *ticketset)
{
    BbTicketBundle ticketBundle;
    BbAppLaunchCrls appRls;
    SHA1Context sha;
    u32 compareStart = 0;
    BbShaHash hash_data;
    BbRsaPublicKey2048 authpublickey;
    BbRsaExponent authexponent;
    unsigned char  computedSig[sizeof(BbRsaPublicKey4096)];
    int i;
  
    PRINTF("_osBbSaVerifyTickets(), %d\n",ticketset->numTickets);
      
    /* zero public key and exponent until an authentic one is found */
    bzero((u8 *)authpublickey, sizeof(BbRsaPublicKey2048));
    bzero((u8 *)&authexponent, sizeof(BbRsaExponent));
    for(i=0; i< ticketset->numTickets; i++){
        PRINTF("    i = %d\n",i);
        if(BB_SYSAPP_PASS != osBbSaBundleTicket(&(ticketset->ticket[i]),
                                                &ticketBundle,
                                                &appRls)){
            return BB_SYSAPP_FAIL;
        }
        PRINTF("ticket bundled\n");

        /* compute hash */
        SHA1Reset(&sha);
        SHA1Input(&sha, (u8 *) ticketBundle.ticket,
                  (sizeof(BbTicket) - sizeof(BbRsaSig2048)));
        SHA1Result(&sha, (u8 *) hash_data);
        /* verify ticket */
        if(i==0){
            /* call sk function first time */
            if(skVerifyHash(hash_data,
                    (BbGenericSig *)&(ticketBundle.ticket->head).ticketSign,
                    ticketBundle.ticketChain,
                    &appRls) == SK_API_SUCCESS){
                /* if success, get authenticated public key and exponent */
                memcpy(authpublickey, 
                       ((BbRsaCert *)ticketBundle.ticketChain[0])->publicKey,
                       sizeof(BbRsaPublicKey2048));
                memcpy(&authexponent, 
                      &(((BbRsaCert *)ticketBundle.ticketChain[0])->exponent),
                       sizeof(BbRsaExponent));
            }
            else{
                return BB_SYSAPP_FAIL;
            }
        }
        /* other tickets */
        else{
            /* verify directly from libcrypto to avoid cert checks again*/
            bsl_rsa_verify(computedSig,
               (u32 *)(BbGenericSig *)&(ticketBundle.ticket->head).ticketSign,
               authpublickey,
               &authexponent,
               2048);
            compareStart = sizeof(BbRsaPublicKey2048) - sizeof(BbShaHash);
            if (memcmp(hash_data, computedSig+compareStart, 
                       sizeof(BbShaHash))!=0){
                return BB_SYSAPP_FAIL;
            }
        }
    }
    return BB_SYSAPP_PASS;
}


int osBbSaMetaGetTickets(OSBbSaTickets *tickets, int verify)
{
    OSBbStatBuf fsStat;
    s32 fd;
    u32 ret = BB_SYSAPP_PASS;
    int rv;

    fd = osBbFOpen(gSaTicketFname,"r");
    if(fd<0){
        PRINTF("osBbSaMetaGetTickets (%d): Can't open ticket file %s\n", fd, gSaTicketFname);
        return SAMETA_ERR_FS;
    }
    if( (rv=osBbFStat(fd, &fsStat, NULL, 0)) < 0){
        PRINTF("osBbSaMetaGetTickets: Can't stat ticket file\n");
        ret = SAMETA_ERR_FS;
        goto exit;
    }

    if(fsStat.size > sizeof(OSBbSaTickets)){
        PRINTF("osBbSaMetaGetTickets: ticket file size to big\n");
        ret = SAMETA_ERR_MEM;
        goto exit;
    }

    if(osBbFRead(fd, 0, (u8 *)tickets, fsStat.size)<0){
        PRINTF("osBbSaMetaGetTickets: Can't read ticket file\n");
        ret = SAMETA_ERR_FS;
        goto exit;
    }

    /* sanity-check number of tickets */
    if(((tickets->numTickets)*sizeof(BbTicket)+4) > fsStat.size){
        PRINTF("osBbSaMetaGetTickets: Bad numTickets\n");
        ret = SAMETA_ERR_FS;
        goto exit;
    }

    if (verify) {
        if(_osBbSaVerifyTickets(tickets) != BB_SYSAPP_PASS){
            ret = SAMETA_ERR_FS;
            goto exit;
        }
    }

 exit:
    osBbFClose(fd);
    return ret;
}

int writeTicketFile(OSBbSaTickets *tickets)
{
    const char newFname[]="temp.tmp";
    int size, block_size;
    s32 fd;

    size = 4 + sizeof(BbTicket) * (tickets->numTickets);
    block_size = (((size)+(BB_FL_BLOCK_SIZE-1)) & ~(BB_FL_BLOCK_SIZE-1));
    osBbFDelete(newFname);
    if ((fd = osBbFCreate(newFname, 1, block_size))<0) {
        PRINTF("temp.tmp creation failed %d\n", fd);
        return SAMETA_ERR_FS;
    } else {
        if( osBbFWrite(fd, 0, tickets, block_size)<0 ){
            PRINTF("failed to write temp.tmp\n");
            osBbFClose(fd);
            osBbFDelete(newFname);
            return SAMETA_ERR_FS;
         }
         osBbFClose(fd);
    }

    osBbFRename(newFname, gSaTicketFname);
    return BB_SYSAPP_PASS;
}

#define SWAP_TYPE(t, a, b) { t tmp; tmp = a; a = b; b = tmp; }
#define SWAP(a, b) \
    SWAP_TYPE(int, valid[(a)], valid[(b)]); \
    SWAP_TYPE(int, status[(a)], status[(b)]); \
    SWAP_TYPE(BbTicket, tickets->ticket[(a)], tickets->ticket[(b)])

#define GET_TITLE(a) \
        tickets->ticket[(a)].cmd.contentDesc + sizeof(OSBbLaunchMetaData) + 4 \
                 + *((u16*)(tickets->ticket[(a)].cmd.contentDesc + sizeof(OSBbLaunchMetaData)))  \
                 + *((u16*)(tickets->ticket[(a)].cmd.contentDesc + sizeof(OSBbLaunchMetaData) + 2))


int osBbSaMetaCleanTickets(OSBbSaTickets *tickets)
{
    int i, j, k;
    int valid[SA_META_MAX_TICKETS], status[SA_META_MAX_TICKETS];
    int gameTickets;
    u8  *ititle, *jtitle;
    u16 window, cc[BB_MAX_CC];
    int ret = BB_SYSAPP_PASS;

    // get raw tickets
    ret = osBbSaMetaGetTickets(tickets, 1);
    if (ret != BB_SYSAPP_PASS) return ret;

    /* get consumption window */
    skGetConsumption(&window, cc);

    /* get ticket status */
    for (i = 0; i < tickets->numTickets; ++i) {
        valid[i] = 1;
        status[i] = getTicketStatus( &(tickets->ticket[i]), window, cc); 
    }

    /* move club to the end */
    gameTickets = tickets->numTickets;
    for (i = 0; i < tickets->numTickets; ++i) {
        ititle = GET_TITLE(i);
        if (!strcmp(ititle, gSaClubTitle)) {
            --gameTickets;
            SWAP(i, gameTickets);
            break;
        }
    }

    /* sort by title */
    for (i = 0; i < gameTickets - 1; ++i) {
        for (j = i + 1; j < gameTickets; ++j) {
            if (strcmp(GET_TITLE(j), GET_TITLE(i)) < 0) {
                SWAP(i, j);
            }
        }
    }

    /* process same title tickets */
    i = 0;
    while (i < gameTickets - 1) {
        int perm = -1, limited = -1, expired = -1;

        /* search for consecutive tickets with the same title */
        ititle = GET_TITLE(i);
        for (j = i + 1; j < gameTickets; ++j) {
            jtitle = GET_TITLE(j);
            if (strcmp(ititle, jtitle)) {
                break;
            }
        }

        if (j == (i + 1))
            goto skip;

        /* Find highest "priority" ticket in the consecutive block [i, j) */
        for (k = i; k < j; ++k) {
            switch (status[k]) {
            case SAMETA_TICKET_PERM:
                if (perm == -1)
                    perm = k;
                break;
            case SAMETA_TICKET_LP_VALID:
                if (limited == -1)
                    limited = k;
                break;
            case SAMETA_TICKET_LP_EXPIRED:
                if (tickets->ticket[k].head.tid > expired)
                    expired = tickets->ticket[k].head.tid;
                break;
            }
        }

        /* mark unnecessary tickets as invalid */
        if (perm != -1) {
            for (k = i; k < j; ++k) {
                if (k != perm) {
                    valid[k] = 0;
                }
            }
        } else if (limited != -1) {
            for (k = i; k < j; ++k) {
                if (status[k] == SAMETA_TICKET_LP_EXPIRED) {
                    valid[k] = 0;
                }
            }
        } else {
            for (k = i; k < j; ++k) {
                if (tickets->ticket[k].head.tid != expired) {
                    valid[k] = 0;
                }
            }
        }
skip:
        i = j;
    }

    /* remove invalid tickets */
    for (i = 0, j = 0; i < tickets->numTickets; ++i) {
        if (valid[i]) {
            if (i != j) {
                bcopy(&tickets->ticket[i], &tickets->ticket[j], sizeof(BbTicket));
            }
            ++j;
        }
    }
    tickets->numTickets = j;

    return writeTicketFile(tickets);
}

int osBbSaMetaGetData(OSBbSaTickets *tickets, OSBbSaGameMetaData *metaData, int trial)
{
    int i;
    s32 fd;
    char appName[16];
    BbTicketId tid;
    u16 window, cc[BB_MAX_CC];

    if (trial)
        skGetConsumption(&window, cc);

    for(i=0; i<tickets->numTickets; i++){
        u8 *p = tickets->ticket[i].cmd.contentDesc;
        metaData[i].launch = *((OSBbLaunchMetaData*)p);
        p += sizeof(OSBbLaunchMetaData);
        metaData[i].thumb_len = *((u16*)p);
        p += sizeof(u16);
        metaData[i].title_img_len = *((u16*)p);
        p += sizeof(u16);
        metaData[i].thumb_data = p;
        p += metaData[i].thumb_len;
        metaData[i].title_img = p;
        p += metaData[i].title_img_len;
        metaData[i].title = p; while (*p!='\0') p++; p++;
        metaData[i].isbn = p; while (*p!='\0') p++; p++;
        metaData[i].crid = p; while (*p!='\0') p++; p++;
        metaData[i].oadid = p; 
        metaData[i].size  = tickets->ticket[i].cmd.head.size;
        metaData[i].type = 0;

        /* is present on card? */
        osBbSaCidToAppName(tickets->ticket[i].cmd.head.id,gSaAppExt,appName);
        if( (fd = osBbFOpen(appName,"r")) >= 0){
            metaData[i].type |= SA_META_TYPE_ONCARD;
            osBbFClose(fd);
        }
        else{
            osBbSaCidToAppName(tickets->ticket[i].cmd.head.id,gSaRecExt,
                               appName);
            if( (fd = osBbFOpen(appName,"r")) >= 0){
                metaData[i].type |= SA_META_TYPE_ONCARD;
                osBbFClose(fd);
            }
        }

        metaData[i].limit = tickets->ticket[i].head.limit;
        /* trial games? */
        tid = tickets->ticket[i].head.tid;
        if (tid >= BB_TICKET_ID_LIMITED && trial) {
            metaData[i].type |= SA_META_TYPE_TRIAL;
            metaData[i].code = tickets->ticket[i].head.code;
            
            tid = tid & (BB_TICKET_ID_LIMITED-1);
            if( tid < window ) // kicked out of window, expired
                metaData[i].cc = tickets->ticket[i].head.limit;
            else if( (tid - window) >= BB_MAX_CC ) // not yet played
                metaData[i].cc = 0;
            else 
                metaData[i].cc = cc[tid-window];
        }
    }

    return BB_SYSAPP_PASS;
}
                                 
int getTicketStatus(BbTicket* pTick, u16 window, u16* cc)
{
    u16 tid; 
    int status = SAMETA_TICKET_PERM;

    if ( pTick->head.tid >= BB_TICKET_ID_LIMITED ) {
        tid = (pTick->head.tid) & (BB_TICKET_ID_LIMITED-1);
        if( tid < window ) // kicked out of window, expired
            status = SAMETA_TICKET_LP_EXPIRED;
        else if( (tid - window) >= BB_MAX_CC ) // not yet played
            status = SAMETA_TICKET_LP_VALID;
        else {
            if ( pTick->head.limit <= cc[tid - window] ) 
                status = SAMETA_TICKET_LP_EXPIRED;
            else 
                status =  SAMETA_TICKET_LP_VALID;
        }
    }
    
    return status; 
}