/*---------------------------------------------------------------------------*
  Project:  TwlSDK - tools - showversion
  File:     main.c

  Copyright 2003-2008 Nintendo. All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law. They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Date:: 2008-12-03#$
  $Rev: 9495 $
  $Author: sasaki_yu $
 *---------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>

extern const unsigned long SDK_DATE_OF_LATEST_FILE;

#define  LE(a)   ((((a)<<24)&0xff000000)|(((a)<<8)&0x00ff0000)|\
                  (((a)>>8)&0x0000ff00)|(((a)>>24)&0x000000ff))

#define  MAGIC_CODE     0x2106c0de
#define  BUILD_MAGIC_CODE   0x3381c0de

#define ERROR_NO_VERSION_INFO 1
#define ERROR_BUILDTYPE_MISMATCH 2

typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned long int u32;
typedef union
{
    u8      byte[4];
    u32     word;
}
tFormat;

typedef struct
{
    u8      reserve[32];               // 
    u32     rom_offset;                // Transfer source ROM offset
    u32     entry_address;             // Execution start address
    u32     ram_address;               // Transfer destination RAM address
    u32     size;                      // Size of the transmission
}
tRomHeader;

#define ROMOFFSET_MODULE9       0x00000020      // ARM9 resident module information region

typedef struct
{
    u8      major;
    u8      minor;
    u16     relstep;
    u8      relclass;
    u8      relnumber_major;
    u8      relnumber_minor;
    char   *relname;
}
tVersion;


#define RELCLASS_TEST          0
#define RELCLASS_PR            1
#define RELCLASS_RC            2

#define RELSTEP_PR6            106
#define RELSTEP_FC             200
#define RELSTEP_RELEASE        300

int findMagicNumber(FILE *fp, u32 magic_code, u32 *buffer, u32 buffer_len);


//
// Search magic number and store preceding data in an array.
// Example: magic_code = 0x12345678, buffer_len = 4
//   When the data is lined up as data2, data1, 0x12345678, 0x87654321, the following are the results.
// buffer[0] = 0x87654321
// buffer[1] = 0x12345678
// buffer[2] = data1
// buffer[3] = data2
// 
// Return value: If the magic number is found, 1 is returned; if it is not found, 0 is returned.
int findMagicNumber(FILE *fp, u32 magic_code, u32 *buffer, u32 buffer_len)
{
    u32 i = 0;
    while (1 == fread(buffer, sizeof(u32), 1, fp))
    {
        if(buffer[1] == magic_code && buffer[0] == LE(magic_code))
        {
            return 1;
        }
        
        for(i = 1; i < buffer_len; ++i)
        {
            buffer[buffer_len - i] = buffer[buffer_len - i - 1];
        }
    }
    return 0;
}

int main(int argc, char *argv[])
{
    u32     buffer[4];
    FILE   *fp;
    tRomHeader romHeader;
    u32     magicCodeBE, magicCodeLE;
    u32     buildMagicCodeBE, buildMagicCodeLE;
    u16     one = 0x0001;
    tFormat f;
    tVersion v;
    u16     relnumber;
    u32     error = 0;
    magicCodeBE = *(u8 *)&one ? LE(MAGIC_CODE) : MAGIC_CODE;
    magicCodeLE = LE(magicCodeBE);
    buildMagicCodeBE = *(u8 *)&one ? LE(BUILD_MAGIC_CODE) : BUILD_MAGIC_CODE;
    buildMagicCodeLE = LE(buildMagicCodeBE);

    if (argc != 2)
    {
        fprintf(stderr,
                "NITRO-SDK Development Tool - showversion - Show version information\n"
                "Build %lu\n"
                "\n"
                "Usage: showversion ROMFILE\n"
                "\n"
                "  Show NITRO-SDK version information in ROMFILE\n" "\n", SDK_DATE_OF_LATEST_FILE);
        return 5;
    }

    if (NULL == (fp = fopen(argv[1], "rb")))
    {
        fprintf(stderr, "Error: Cannot open '%s'\n", argv[1]);
        return 1;
    }

    if (1 != fread(&romHeader, sizeof(romHeader), 1, fp))
    {
        fclose(fp);
        fprintf(stderr, "Error: Cannot open '%s'\n", argv[1]);
        return 1;
    }

    //
    // Determine whether it is a ROM file
    //          Here this determines whether execution start address and transfer destination RAM address are within the range 0x02000000 to 0x027fffff.
    //           
    //
    if ((romHeader.ram_address & 0xff800000) != 0x02000000 ||
        (romHeader.entry_address & 0xff800000) != 0x02000000)
    {
        //
        // If not a ROM file, destroy ROM header information (zero clears it) and return file pointer to top.
        //  
        //
        (void)memset(&romHeader, 0, sizeof(tRomHeader));
        rewind(fp);
    }

    v.major = 0;
    buffer[0] = buffer[1] = buffer[2] = buffer[3] = 0;

    //
    // Determine that magicCode is lined up in this order in ROM and display versionInfo:
    //      buffer[2] versionInfo
    //      buffer[1] magicCodeBE
    //      buffer[0] magicCodeLE
    //  
    //
    while (findMagicNumber(fp, magicCodeBE, buffer, 3))
    {
        f.word = buffer[2];
        v.major = f.byte[3];       // 31..24bit
        v.minor = f.byte[2];       // 23..16bit
        v.relstep = ((u16)f.byte[0]) | (((u16)f.byte[1]) << 8);     // 15...0bit
        v.relclass = v.relstep / 10000;
        relnumber = v.relstep % 10000;
        v.relnumber_major = relnumber / 100;
        v.relnumber_minor = relnumber % 100;

        switch (v.relstep / 100)   // Determine the last three digits
        {
            //
            // Special number determination
            //
        case RELSTEP_PR6:
            if (v.relnumber_minor == 50)
            {
                v.relname = "IPL";
                v.relnumber_major = 0;
                v.relnumber_minor = 0;
            }
            else
            {
                v.relname = "PR";
                if (v.relnumber_minor > 50)
                {
                    v.relnumber_minor -= 50;
                }
            }
            break;

        case RELSTEP_FC:
            v.relname = "FC";
            break;

        case RELSTEP_RELEASE:
            v.relname = "RELEASE";
            break;

        default:

            //
            // Determine normal numbers (postfix notation)
            //
            switch (v.relclass)
            {
            case RELCLASS_TEST:
                v.relname = "test";
                break;

            case RELCLASS_PR:
                v.relname = "PR";
                break;

            case RELCLASS_RC:
                v.relname = "RC";
                break;

            default:
                v.relname = NULL;
                break;
            }
            break;
        }

        printf("NITRO-SDK VERSION: %d.%02d", v.major, v.minor);

        //
        // Displaying release class name (test/PR/RC and the like)
        //
        if (v.relname)
        {
            printf(" %s", v.relname);

            if (v.relnumber_major)
            {
                printf("%d", v.relnumber_major);
            }
            if (v.relnumber_minor)
            {
                printf(" plus");

                if (v.relnumber_minor > 1)
                {
                    printf("%d", v.relnumber_minor);
                }
            }
        }
        else
        {
            printf("XXX [unknown RelID=%d]", v.relstep);
        }

        //
        // Determine whether within a permanent area
        //
        if ((u32)ftell(fp) - romHeader.rom_offset < romHeader.size)
        {
            printf(" [STATIC MODULE]");
        }

        printf("\n");

        //
        // Message for when the value had no setting
        //
        if (v.major == 0)
        {
            fprintf(stderr, "** Error: No version information in '%s'\n", argv[1]);
            fprintf(stderr, "(older than 2.0FC?)\n");
            error |= ERROR_NO_VERSION_INFO;
        }
    }
    
    //
    // Determine build type
    //      Determine ARM9 and ARM7
    //      Determine FINALROM, RELEASE, DEBUG
    // 
    // If FINALROM, RELEASE, DEBUG are mixed, display an error
    //
    {
        u32 dBuildType = 0xffffffff, buildType = 0, target = 0;
        int mismatch = 0;
        rewind(fp);
        
        while(findMagicNumber(fp, buildMagicCodeBE, buffer, 4))
        {
            u32 version = buffer[2] & 0x000000ff;
            char* buildName[] = {"FINALROM", "RELEASE", "DEBUG"};
            if(version == 1)
            {
                target = (buffer[3] >> 8) & 0x000000ff;
                buildType = buffer[3] & 0x000000ff;
                printf("BUILD_TYPE:ARM%d %s\n", (int)target, 
                    ((buildType != 0xff) ? buildName[buildType] : "Invalid"));

                // Detect whether a different build type is mixed in.
                if(dBuildType != 0xffffffff && dBuildType != buildType)
                {
                    mismatch = 1;
                }
                dBuildType = buildType;
            }
        }
        if(mismatch)
        {
            fprintf(stderr, "** Error: Build type mismatch\n");
            error |= ERROR_BUILDTYPE_MISMATCH;
        }
    }

    fclose(fp);

    return error;
}
