/*******************************************************************
 *
 * dvdmap.cpp
 *
// (c) 2004-2007 Electronic Arts Inc.
 *
 * 2004.11.27 RJ 1.0 original ps2parsemap by Ryan Jones for MVP
 * 2004.11.28 FB 1.1 renamed dvdmap for FIFAST. bumped default files to 2048
 * 2004.12.14 FB 1.2 Show count and size for diagnostics
 * 2004.12.16 FB 1.3 Rewrite of parser to fix missing folders, added option parsing, -v and 1 argument to just do test on existing mapfile
 *
 *******************************************************************/

//  Description: The purpose of the tool is to parse through the map
//  file created by the PS2 ISO and create a directory structure that
//  can be passed into the REAL FILESYS_init function to speed up the
//  initial load time on the PS2. If we do not use this, FILESYS_init
//  will take approx. 3.8 seconds.

//
//  ABSTRACT
//
//  dvdmap - process luxpak/dvdgen mapfile into binary format for real FILESYS_init
//
//  SUMMARY
//
//  DVDMap -options <luxpackemu.map> <ps2dir.bin> <alignment>
//   <luxpackemu.map> Name of luxpak map1 file
//   <ps2dir.bin>     Name of output dir file
//   <alignment>      Optional byte alignment.  Default 16
//   -noroot          Switch to exclude root files from the map. Default off
//   -invert          Switch to put files in opposite order. Default off
//   -verbose         Switch to enable more verbose output (aka -v)
//
//  DESCRIPTION
//
//  dvdmap takes a map file produces by luxpak and converts it into a binary form suitable for FILESYS_init
//  This helps reduce boot time on ps2.
//
//  RETURNS
//
//  0 always returns 0
//
//  SEE ALSO
//
//  makepad
//
// EXAMPLE
//
// The following script was used to build a padded ps2 disc on FIFA Street
//
// :::::::::::::::::::::::::::::::::::
// :: Make the iso
// :::::::::::::::::::::::::::::::::::
// Echo === Building iso ===
// cd /d %TP_DRIVE%:\fifast\build\ps2
//
// del slus_211.47.elf
// del data\dvdbuild.map
// del fifast-ps2.ccz
// del /f mvporide.ini
// del /f data\femovies\*_pal.mpc
//
// :: 2284232+10088=2294320 (dvdgen). Divisible by 16.  2283600 is free space with 18 directories.
// echo making padding disc to 2283600 sectors
// timex paddvd boot.pad 2283600 .
//
// mkdir \fifast\build\ps2-iso
//
//
// :: luxpak once to get the ps2dir.h.
// timex luxpak /end=2284216 /license=\fifast\tools\bin\layout\license-ntsc.txt @\fifast\tools\bin\layout\fifast.rsp e:\share\bball\fifast-ps2.ccz /map1=e:\share\bball\fifast-ps2.map /map2=e:\share\bball\fifast-ps2-map2.map
//
// dvdmap e:\share\bball\fifast-ps2.map e:\share\bball\ps2dir.bin -noroot -invert
// huff -tj e:\share\bball\ps2dir.bin =e:\share\bball\ps2dir.qbin
// p4 edit \fifast\source\common\main\ps2dir.h
// imagetohex e:\share\bball\ps2dir.qbin \fifast\source\common\main\ps2dir.h
//
// cd /d %TP_DRIVE%:\fifast\source
// timex make ntscfinal
// cd /d %TP_DRIVE%:\fifast\build\ps2
//
// del slus_211.47.elf
// del data\dvdbuild.map
//
// :: now build .ccz for dvdgen and .iso to burning
// timex luxpak /end=2284216 /license=\fifast\tools\bin\layout\license-ntsc.txt @\fifast\tools\bin\layout\fifast.rsp e:\share\bball\fifast-ps2.ccz /map1=e:\share\bball\fifast-ps2.map /map2=e:\share\bball\fifast-ps2-map2.map
// timex luxpak /end=2284216 /license=\fifast\tools\bin\layout\license-ntsc.txt @\fifast\tools\bin\layout\fifast.rsp e:\share\bball\fifast-ps2.iso
//
// END ABSTRACT
//

#if defined(_MSC_VER)
#pragma warning(disable: 4996)  // warning C4996: 'fscanf' was declared deprecated
#endif
#if defined(__ICL)
#pragma warning(disable: 1478)  // (icl) warning #1478: function "strcpy" was declared "deprecated"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// stddef for intptr on pc
#include <stddef.h>
#if !defined(_MSC_VER)
#include <unistd.h>
#endif

#define MAX_STR             256
#define DEFAULT_NUM_FILES   2048

// structure taken from realfile.h
typedef struct DISCFILE
{
    char* name;         //!< ISO file name
    unsigned int pos;   //!< LSN position of file
    unsigned int len;   //!< Length of file in bytes
    char* dir;          //!< Full path of the directory
} DISCFILE;

// Format of our output file.
// # of files
// # of directories
// offset to the start of the DISCFILE section
// NULL terminated ISO name for each file
// NULL terminated paths for each directory
// Array of [# of files] of DISCFILE

typedef char STRING[ MAX_STR ];

const char kMapFile[] = "mapFile[";
const char kBasePath[] = "cdrom0:";

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void ShowStats( const char * file_name, int excluded, bool optVerbose )
{
    FILE * file = fopen( file_name, "rb" );
    char * buffer = NULL;
    int readsize;

    if( !file )
    {
        return;
    }

    fseek( file, 0, SEEK_END );
    readsize = ftell( file );
    fseek( file, 0, SEEK_SET );

    buffer = (char*) malloc( sizeof( char ) * readsize );

    fread( buffer, sizeof( char ), readsize, file );
    fclose( file );

    unsigned int *file_count;
    unsigned int *path_count;
    unsigned int *offset;
    DISCFILE * data;
    unsigned int i;

    file_count = (unsigned int*)buffer;
    path_count = file_count + 1;
    offset = path_count + 1;
    data = (DISCFILE*)(buffer + (*offset));
    double totalLen = 0.0;

    // update the string pointers
    for( i = 0; i <= (*file_count); ++i )
    {
        if (data[i].name)
        {
            data[i].name = (char*)((intptr_t)buffer + (intptr_t)data[i].name);
            data[i].dir = (char*)((intptr_t)buffer + (intptr_t)data[i].dir);
        }
        else
            data[i].name = data[i].dir = "";
        totalLen += (double)data[i].len;
        if (optVerbose)
            printf( "\"%s%s\", %i, %i, // toc %d\n", data[i].dir, data[i].name, data[i].pos, data[i].len, i );
    }

    printf( "//%s: %d files, %d excluded, %d folders, %.0f bytes.\n", file_name, *file_count, excluded, *path_count, totalLen);
}


//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void PadFile( FILE * dst, unsigned int & bytes_written, unsigned int align )
{
    int needed = (align - bytes_written) % align;
    int i;

    for( i = 0; i < needed; ++i )
    {
        fputc( 0xfb, dst );
        bytes_written++;
    }
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
int main( int argc, char *argv[] )
{
    bool optVerbose=false;
    bool optNoRoot=false;
    bool optInvert=false;
    int optByteAlign = 0;
    char *inName = NULL;
    char *outName = NULL;
    for (int argi=1; argi<argc; ++argi)
    {
        if (!strcmp(argv[argi],"-verbose") || !strcmp(argv[argi],"-v"))
            optVerbose=true;
        else if (!strcmp(argv[argi],"-noroot"))
            optNoRoot=true;
        else if (!strcmp(argv[argi],"-invert"))
            optInvert=true;
        else if (!inName)
            inName = argv[argi];
        else if (!outName)
            outName = argv[argi];
        else if (!optByteAlign)
            optByteAlign = atoi( argv[3] );
        else
        {
            printf("DVDMap - ERROR Too Many arguments\n");
            exit(10);
        }
    }
    if (!optByteAlign) optByteAlign = 16;

    if (!inName)
    {
        printf(
            "DVDMap 2007.9.17 (Osoyoos) by Ryan Jones & Frank Barchard\n"
            "USAGE: DVDMap -options <luxpackemu.map> <ps2dir.bin> <alignment>\n"
            "  <luxpackemu.map> Name of luxpak map1 file\n"
            "  <ps2dir.bin>     Name of output dir file\n"
            "  <alignment>      Optional byte alignment.  Default 16\n"
            "  -noroot          Switch to exclude root files from the map. Default off\n"
            "  -invert          Switch to put files in opposite order. Default off\n"
            "  -verbose         Switch to enable more verbose output (aka -v)\n");

        exit(1);
    }

    char data[ MAX_STR ];
    char * pchar = NULL;
    FILE * src = NULL;
    FILE * dst = NULL;
    unsigned int num_files = 0;
    DISCFILE * file_list = NULL;
    STRING * name_list = NULL;
    STRING * path_list = NULL;
    unsigned int i;
    unsigned int j;
    unsigned int file_count = 0;
    unsigned int path_count = 0;
    unsigned int bytes_written = 0;
    unsigned int disc_pos = 0;

    int error_count = 0;
    int excluded=0;

    // if only 1 file specified, its the ps2dir.bin - just run ShowStats on it
    if (!outName)
    {
        outName = inName;
        inName = NULL;
    }
    else
    {

        src = fopen( inName, "r" );
        dst = fopen( outName, "wb+" );  // open a new file for writing

        if( !src )
        {
            printf( "Error: source file not found.\n" );
            return -1;
        }
        if( !dst )
        {
            printf( "Error: destination file not created.\n" );
            return -1;
        }

        // find the start of the data...
        while( !feof( src ) )
        {
            fscanf( src, "%s", data );

            // get the number of files
            if( strstr( data, kMapFile ) )
            {
                pchar = data + strlen( kMapFile );
                num_files = atoi( pchar );
                if( num_files <= 0 )
                {
                    num_files = DEFAULT_NUM_FILES;
                }
                file_list = (DISCFILE*)malloc( sizeof( DISCFILE ) * num_files );
                name_list = (STRING*)malloc( sizeof( STRING ) * num_files );
                path_list = (STRING*)malloc( sizeof( STRING ) * num_files );
            }

            // parse the data
            if( strlen( data ) > 1 && data[0] == '{' )
            {
                // parse pathname.  example line
                // {"\\DATA\\FEMOVIES\\INTRO.MPC;1", 1504384, 1512154, 7771, 15914144}, /* DATA\FEMOVIES\INTRO.MPC */

                pchar = strchr( data, '\"' )+1;
                char *d = path_list[ path_count ];

                for (; *pchar!=';'; ++pchar, ++d)
                {
                    *d = *pchar;
                    if (*d=='\\')
                    {
                        *d = '/';  // make slashes forwards
                        if (pchar[1] == '\\')
                            ++pchar;
                    }
                }
                *d = 0; // null terminate the file name, removing the ;1"

                // seperate path from file
                pchar = strrchr( path_list[ path_count ], '/' );
                strcpy( name_list[ file_count ], pchar+1 );
                *pchar = 0;

                fscanf( src, "%s", data );
                file_list[ file_count ].pos = atoi( data );

                // skip the ", end, sector size, get"
                fscanf( src, "%s", data );
                fscanf( src, "%s", data );
                fscanf( src, "%s\n", data );

                file_list[ file_count ].len = atoi( data );
                if( file_list[ file_count ].len == 0 )
                {
                    printf( "Error: %s is 0 length\n", name_list[ file_count ] );
                    error_count++;
                }

                if (optNoRoot && path_list[0][0]=='\0')
                {
                    ++excluded;
                }
                else
                {
                    path_count++;
                    file_count++;
                }
            }
        }

        if( file_count != num_files && num_files != DEFAULT_NUM_FILES )
        {
            printf( "Error: Parse error, not all files could be parsed.\n" );
            return -1;
        }

        // # of files
        fwrite( &file_count, sizeof( file_count ), 1, dst );
        bytes_written += sizeof( file_count );

        // # of paths
        fwrite( &path_count, sizeof( path_count ), 1, dst );
        bytes_written += sizeof( path_count );

        // offset to start of DISCFILE
        // we're writing a temp, 0, value here for now
        fwrite( &disc_pos, sizeof( disc_pos ), 1, dst );
        bytes_written += sizeof( disc_pos );

        // offset to start of DISCFILE
        // we're writing a temp, 0, value here for now
        fwrite( "DVDM", 4, 1, dst );
        bytes_written += 4;

        PadFile( dst, bytes_written, optByteAlign );

        // write out the file names
        for( i = 0; i < file_count; ++i )
        {
            bool uni=true;
            file_list[i].name = (char*)bytes_written;
            for( j = 0; j < i; ++j )
            {
                if (!strcmp(name_list[i], name_list[j]))
                {
                    file_list[i].name = file_list[j].name;
                    uni = false;
                    if (optVerbose)
                        printf("// warning - filename %s appears in both %s and %s folder\n",name_list[i],path_list[i],path_list[j]);
                    break;
                }
            }

            if (uni)
            {
                sprintf(data,"%s;1", name_list[i]);

                int len = 1 + (int)(strlen( data ));   // make sure we write the NULL terminator
                fwrite( data, sizeof( char ), len, dst );
                bytes_written += sizeof( char ) * len;
            }
        }

        // write out the path names
        int skipped = 0;
        for( i = 0; i < path_count; ++i )
        {
            bool uni=true;
            file_list[i].dir = (char*)bytes_written;
            for( j = 0; j < i; ++j )
            {
                if (!strcmp(path_list[i], path_list[j]))
                {
                    file_list[i].dir = file_list[j].dir;
                    uni = false;
                    ++skipped;
                    break;
                }
            }

            if (uni)
            {
                sprintf(data,"%s%s/", kBasePath,path_list[i]);

                int len = 1 + (int)(strlen( data ));   // make sure we write the NULL terminator
                fwrite( data, sizeof( char ), len, dst );
                bytes_written += sizeof( char ) * len;
            }
        }

        path_count -= skipped;

        PadFile( dst, bytes_written, optByteAlign );
        disc_pos = bytes_written;

        if (optInvert)
        {
            // write out the file names last to first
            for( i = 0; i < file_count; ++i )
            {
                // write out the disc layout data
                fwrite( &file_list[file_count-1-i], sizeof( DISCFILE ), 1, dst );
            }
        }
        else
        {
            // write out the disc layout data
            fwrite( file_list, sizeof( DISCFILE ), file_count, dst );
        }
        bytes_written += sizeof( DISCFILE ) * file_count;

        // make sure we write the NULL DISCFILE
        DISCFILE empty;
        memset( &empty, 0, sizeof( empty ) );
        fwrite( &empty, sizeof( DISCFILE ), 1, dst );
        bytes_written += sizeof( DISCFILE );

        // make sure we are 4 byte aligned
        PadFile( dst, bytes_written, optByteAlign );

        // now write the real value
        fseek( dst, 0 , SEEK_SET );

        // # of files
        fwrite( &file_count, sizeof( file_count ), 1, dst );
        bytes_written += sizeof( file_count );

        // # of paths
        fwrite( &path_count, sizeof( path_count ), 1, dst );
        bytes_written += sizeof( path_count );

        fwrite( &disc_pos, sizeof( disc_pos ), 1, dst );

        // close the files
        fclose( src );
        fclose( dst );

        // clean up memory
        free( file_list );
        free( name_list );
        free( path_list );
    }

    ShowStats( outName, excluded, optVerbose );

    if( error_count > 0 )
    {
        printf( "dvdmap Failed, %s contains invalid data.\n", outName );
    }

    return error_count;
}
