/*
 * FILE:                  tools/nci/ncitool.c
 * COPYRIGHT:             See COPYING in the top level directory
 * PROJECT:               Native Call Interface Support Tool
 * PURPOSE:               Generates NCI Tables and Stubs.
 * PROGRAMMER;            Alex Ionescu (alex@relsoft.net)
 * CHANGE HISTORY:        14/01/05 - Created. Based on original code by 
 *                                   KJK::Hyperion and Emanuelle Aliberti.
 *
 */

/* INCLUDE ******************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(__FreeBSD__) && !defined(__APPLE__)
# include <malloc.h>
#endif // __FreeBSD__

/* DEFINES  ****************************************************************/

#define INPUT_BUFFER_SIZE 255
#define Arguments 7

/******* Table Indexes ************/
#define MAIN_INDEX 0x0
#define WIN32K_INDEX 0x1000

/******* Argument List ************/
/* First, define the Databases */
#define NativeSystemDb 0
#define NativeGuiDb 1

/* Now the Service Tables */
#define NtosServiceTable 2
#define Win32kServiceTable 3

/* And finally, the stub files. */
#define NtosUserStubs 4
#define NtosKernelStubs 5
#define Win32kStubs 6

/********** Stub Code ************/

/*
 * This stubs calls into KUSER_SHARED_DATA where either a 
 * sysenter or interrupt is performed, depending on CPU support.
 */    
#if defined(__GNUC__)
#define UserModeStub_x86    "    movl $0x%x, %%eax\n" \
                            "    movl $KUSER_SHARED_SYSCALL, %%ecx\n" \
                            "    call *(%%ecx)\n" \
                            "    ret $0x%x\n\n"

#define UserModeStub_ppc    "    mflr 0\n" \
                            "    addi 1,1,-16\n" \
                            "    li   0,%x\n" \
                            "    stw  0,1(0)\n" \
                            "    sc\n" \
                            "    lwz  0,1(0)\n" \
                            "    mtlr 0\n" \
                            "    addi 1,1,16\n" \
                            "    blr\n"

#define UserModeStub_mips   "    li $8, KUSER_SHARED_SYSCALL\n" \
                            "    lw $8,0($8)\n" \
                            "    j $8\n" \
                            "    nop\n"

#elif defined(_MSC_VER)
#define UserModeStub_x86    "    asm { \n" \
                            "        mov eax, %xh\n" \
                            "        mov ecx, KUSER_SHARED_SYSCALL\n" \
                            "        call [ecx]\n" \
                            "        ret %xh\n" \
                            "    }\n"
#else
#error Unknown compiler for inline assembler
#endif

/*
 * This stub calls KiSystemService directly with a fake INT2E stack.
 * Because EIP is pushed during the call, the handler will return here. 
 */
#if defined(__GNUC__)
#define KernelModeStub_x86  "    movl $0x%x, %%eax\n" \
                            "    leal 4(%%esp), %%edx\n" \
                            "    pushfl\n" \
                            "    pushl $KGDT_R0_CODE\n" \
                            "    call _KiSystemService\n" \
                            "    ret $0x%x\n\n"

#define KernelModeStub_ppc  "    bl KiSystemService\n" \
                            "    rfi\n"

#define KernelModeStub_mips "    j KiSystemService\n" \
                            "    nop\n"

#elif defined(_MSC_VER)
#define KernelModeStub_x86  "    asm { \n" \
                            "        mov eax, %xh\n" \
                            "        lea edx, [esp+4]\n" \
                            "        pushf\n" \
                            "        push KGDT_R0_CODE\n" \
                            "        call _KiSystemService\n" \
                            "        ret %xh\n" \
                            "    }\n"
#else
#error Unknown compiler for inline assembler
#endif

/***** Arch Dependent Stuff ******/
struct ncitool_data_t {
	const char *arch;
	int args_to_bytes;
	const char *km_stub;
	const char *um_stub;
	const char *global_header;
	const char *declaration;
};

struct ncitool_data_t ncitool_data[] = {
	{ "i386", 4, KernelModeStub_x86, UserModeStub_x86,
	  ".global _%s@%d\n", "_%s@%d:\n" },
	{ "powerpc", 4, KernelModeStub_ppc, UserModeStub_ppc,
	  "\t.globl %s\n", "%s:\n" },
	{ "mips", 4, KernelModeStub_mips, UserModeStub_mips,
	  "\t.globl %s\n", "%s:\n" },
	{ 0, }
};
int arch_sel = 0;
#define ARGS_TO_BYTES(x) (x)*(ncitool_data[arch_sel].args_to_bytes)
#define UserModeStub ncitool_data[arch_sel].um_stub
#define KernelModeStub ncitool_data[arch_sel].km_stub
#define GlobalHeader ncitool_data[arch_sel].global_header
#define Declaration ncitool_data[arch_sel].declaration

/* FUNCTIONS ****************************************************************/

/*++
 * WriteFileHeader 
 *
 *     Prints out the File Header for a Stub File.
 *
 * Params:
 *     StubFile - Stub File to which to write the header.
 *
 *     FileDescription - Description of the Stub file to which to write the header.
 *
 *     FileLocation - Name of the Stub file to which to write the header.
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     FileLocation is only used for printing the header.
 *
 *--*/
void 
WriteFileHeader(FILE * StubFile,
                char* FileDescription,
                char* FileLocation)
{
    /* This prints out the file header */
    fprintf(StubFile,
            "/* FILE:            %s\n"
            " * COPYRIGHT:       See COPYING in the top level directory\n"
            " * PURPOSE:         %s\n"
            " * PROGRAMMER:      Computer Generated File. See tools/nci/ncitool.c\n"
            " * REMARK:          DO NOT EDIT OR COMMIT MODIFICATIONS TO THIS FILE\n"
            " */\n\n\n"
            "#include <ndk/asm.h>\n\n",
            FileDescription,
            FileLocation);
}

/*++
 * WriteFileHeader 
 *
 *     Prints out the File Header for a Stub File.
 *
 * Params:
 *     StubFile - Stub File to which to write the header.
 *
 *     FileDescription - Description of the Stub file to which to write the header.
 *
 *     FileLocation - Name of the Stub file to which to write the header.
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     FileLocation is only used for printing the header.
 *
 *--*/
void 
WriteStubHeader(FILE* StubFile, 
                char* SyscallName, 
                unsigned StackBytes)
{
    /* Export the function */
    fprintf(StubFile, GlobalHeader, SyscallName, StackBytes);
    
    /* Define it */
    fprintf(StubFile, Declaration, SyscallName, StackBytes);
}

    
/*++
 * WriteKernelModeStub 
 *
 *     Prints out the Kernel Mode Stub for a System Call.
 *
 * Params:
 *     StubFile - Stub File to which to write the header.
 *
 *     SyscallName - Name of System Call for which to add the stub.
 *
 *     StackBytes - Number of bytes on the stack to return after doing the system call.
 *
 *     SyscallId - Service Descriptor Table ID for this System Call.
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     On i386, StackBytes is the number of arguments x 4.
 *
 *--*/
void 
WriteKernelModeStub(FILE* StubFile, 
                    char* SyscallName, 
                    unsigned StackBytes,
                    unsigned int SyscallId)
{    
    /* Write the Stub Header and export the Function */
    WriteStubHeader(StubFile, SyscallName, StackBytes);

    /* Write the Stub Code */
    fprintf(StubFile, KernelModeStub, SyscallId, StackBytes);
}

/*++
 * WriteUserModeStub 
 *
 *     Prints out the User Mode Stub for a System Call.
 *
 * Params:
 *     StubFile - Stub File to which to write the header.
 *
 *     SyscallName - Name of System Call for which to add the stub.
 *
 *     StackBytes - Number of bytes on the stack to return after doing the system call.
 *
 *     SyscallId - Service Descriptor Table ID for this System Call.
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     On i386, StackBytes is the number of arguments x 4.
 *
 *--*/
void 
WriteUserModeStub(FILE* StubFile, 
                  char* SyscallName, 
                  unsigned StackBytes,
                  unsigned int SyscallId)
{   
    /* Write the Stub Header and export the Function */
    WriteStubHeader(StubFile, SyscallName, StackBytes);

    /* Write the Stub Code */
    fprintf(StubFile, UserModeStub, SyscallId, StackBytes);
}

/*++
 * GetNameAndArgumentsFromDb 
 *
 *     Parses an entry from a System Call Database, extracting
 *     the function's name and arguments that it takes.
 *
 * Params:
 *     Line - Entry from the Database to parse.
 *
 *     NtSyscallName - Output string to which to save the Function Name
 *
 *     SyscallArguments - Output string to which to save the number of
 *                        arguments that the function takes.
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     On i386, StackBytes is the number of arguments x 4.
 *
 *--*/
void 
GetNameAndArgumentsFromDb(char Line[],
                          char ** NtSyscallName,
                          char ** SyscallArguments)
{
    char *s;
    char *stmp;
    
    /* Remove new line */
    if ((s = (char *) strchr(Line,'\r')) != NULL) {
        *s = '\0';
    }
        
    /* Skip comments (#) and empty lines */
    s = &Line[0];
    if ((*s) != '#' && (*s) != '\0') {
            
        /* Extract the NtXXX name */
        *NtSyscallName = (char *)strtok(s," \t");

        /* Extract the argument count */
        *SyscallArguments = (char *)strtok(NULL," \t");

        /* Remove, if present, the trailing LF */
        if ((stmp = strchr(*SyscallArguments, '\n')) != NULL) {
            *stmp = '\0';
        }
        
    } else {
    
        /* Skip this entry */
        *NtSyscallName = NULL;
        *SyscallArguments = NULL;
    }
}

/*++
 * CreateStubs 
 *
 *     Parses a System Call Database and creates stubs for all the entries.
 *
 * Params:
 *     SyscallDb - System Call Database to parse.
 *
 *     UserModeFiles - Array of Usermode Stub Files to which to write the stubs.
 *
 *     KernelModeFile - Kernelmode Stub Files to which to write the stubs.
 *
 *     Index - Number of first syscall
 *
 *     UserFiles - Number of Usermode Stub Files to create
 *
 *     NeedsZw - Write Zw prefix?
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     None.
 *
 *--*/
void
CreateStubs(FILE * SyscallDb,
            FILE * UserModeFiles[],
            FILE * KernelModeFile,
            unsigned Index,
            unsigned UserFiles,
            unsigned NeedsZw)
{
    char Line[INPUT_BUFFER_SIZE];
    char *NtSyscallName;
    char *SyscallArguments;
    int SyscallId;
    unsigned StackBytes;
    
    /* We loop, incrementing the System Call Index, until the end of the file  */
    for (SyscallId = 0; ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL));) {
             
        /* Extract the Name and Arguments */
        GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments); 
        if (SyscallArguments != NULL)
            StackBytes = ARGS_TO_BYTES(strtoul(SyscallArguments, NULL, 0));
        else
            StackBytes = 0;
 
        /* Make sure we really extracted something */
        if (NtSyscallName) {
     
            /* Create Usermode Stubs for Nt/Zw syscalls in each Usermode file */
            int i;
            for (i= 0; i < UserFiles; i++) {
    
                /* Write the Nt Version */
                WriteUserModeStub(UserModeFiles[i], 
                                  NtSyscallName, 
                                  StackBytes, 
                                  SyscallId | Index);

                /* If a Zw Version is needed (was specified), write it too */
                if (NeedsZw) {

                    NtSyscallName[0] = 'Z';
                    NtSyscallName[1] = 'w';
                    WriteUserModeStub(UserModeFiles[i],
                                      NtSyscallName,
                                      StackBytes,
                                      SyscallId | Index);
                }

            }

            /* Create the Kernel coutnerparts (only Zw*, Nt* are the real functions!) */
            if (KernelModeFile) {

                NtSyscallName[0] = 'Z';
                NtSyscallName[1] = 'w';
                WriteKernelModeStub(KernelModeFile,
                                    NtSyscallName,
                                    StackBytes,
                                    SyscallId | Index);
            }
        
            /* Only increase if we actually added something */
            SyscallId++;
        }
    }
}

/*++
 * CreateSystemServiceTable 
 *
 *     Parses a System Call Database and creates a System Call Service Table for it.
 *
 * Params:
 *     SyscallDb - System Call Database to parse.
 *
 *     SyscallTable - File in where to create System Call Service Table.
 *
 *     Name - Name of the Service Table.
 *
 *     FileLocation - Filename containing the Table.
 *
 * Returns:
 *     None.
 *
 * Remarks:
 *     FileLocation is only used for the header generation.
 *
 *--*/
void
CreateSystemServiceTable(FILE *SyscallDb, 
                         FILE *SyscallTable,
                         char * Name,
                         char * FileLocation)
{
    char Line[INPUT_BUFFER_SIZE];
    char *NtSyscallName;
    char *SyscallArguments;
    int SyscallId;

    /* Print the Header */
    WriteFileHeader(SyscallTable, "System Call Table for Native API", FileLocation);

    /* First we build the SSDT */
    fprintf(SyscallTable,"\n\n\n");
    fprintf(SyscallTable,"ULONG_PTR %sSSDT[] = {\n", Name);

    /* We loop, incrementing the System Call Index, until the end of the file */
    for (SyscallId = 0; ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL));) {

        /* Extract the Name and Arguments */
        GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments);
        
        /* Make sure we really extracted something */
        if (NtSyscallName) {
            
            /* Add a new line */
            if (SyscallId > 0) fprintf(SyscallTable,",\n");
        
            /* Write the syscall name in the service table. */
            fprintf(SyscallTable,"\t\t(ULONG_PTR)%s", NtSyscallName);
            
            /* Only increase if we actually added something */
            SyscallId++;
        }
    }
    
    /* Close the service table (C syntax) */
    fprintf(SyscallTable,"\n};\n");

    /* Now we build the SSPT */
    rewind(SyscallDb);
    fprintf(SyscallTable,"\n\n\n");
    fprintf(SyscallTable,"UCHAR %sSSPT[] = {\n", Name);

    for (SyscallId = 0; ((!feof(SyscallDb)) && (fgets(Line, sizeof(Line), SyscallDb) != NULL));) {

        /* Extract the Name and Arguments */
        GetNameAndArgumentsFromDb(Line, &NtSyscallName, &SyscallArguments);
        
        /* Make sure we really extracted something */
        if (NtSyscallName) {
            
            /* Add a new line */
            if (SyscallId > 0) fprintf(SyscallTable,",\n");
            
            /* Write the syscall arguments in the argument table. */
            if (SyscallArguments != NULL)
                fprintf(SyscallTable,"\t\t%lu * sizeof(void *)",strtoul(SyscallArguments, NULL, 0));
            else
                fprintf(SyscallTable,"\t\t0");
                    
            /* Only increase if we actually added something */
            SyscallId++;
        }
    }
    
    /* Close the service table (C syntax) */
    fprintf(SyscallTable,"\n};\n");

    /*
     * We write some useful defines
     */
    fprintf(SyscallTable, "\n\n#define MIN_SYSCALL_NUMBER    0\n");
    fprintf(SyscallTable, "#define MAX_SYSCALL_NUMBER    %d\n", SyscallId - 1);
    fprintf(SyscallTable, "#define NUMBER_OF_SYSCALLS    %d\n", SyscallId);
    fprintf(SyscallTable, "ULONG %sNumberOfSysCalls = %d;\n", Name, SyscallId);
}

void usage(char * argv0)
{
    printf("Usage: %s [-arch <arch>] sysfuncs.lst w32ksvc.db napi.h ssdt.h napi.S zw.S win32k.S win32k.S\n"
           "  sysfuncs.lst  native system functions database\n"
           "  w32ksvc.db    native graphic functions database\n"
           "  napi.h        NTOSKRNL service table\n"
           "  ssdt.h        WIN32K service table\n"
           "  napi.S        NTDLL stubs\n"
           "  zw.S          NTOSKRNL Zw stubs\n"
           "  win32k.S      GDI32 stubs\n"
           "  win32k.S      USER32 stubs\n"
           "  -arch is optional, default is %s\n",
           argv0,
           ncitool_data[0].arch
           );
}

int main(int argc, char* argv[])
{
    FILE * Files[Arguments] = { };
    int FileNumber, ArgOffset = 1;
    char * OpenType = "r";

    /* Catch architecture argument */
    if (argc > 3 && !strcmp(argv[1],"-arch")) {
        for( arch_sel = 0; ncitool_data[arch_sel].arch; arch_sel++ )
            if (strcmp(argv[2],ncitool_data[arch_sel].arch) == 0)
                break;
        if (!ncitool_data[arch_sel].arch) {
            printf("Invalid arch '%s'\n", argv[2]);
            usage(argv[0]);
            return 1;
        }
        ArgOffset = 3;
    }
    /* Make sure all arguments all there */
    if (argc != Arguments + ArgOffset) {
        usage(argv[0]);
        return(1);
    }
  
    /* Open all Output and bail out if any fail */
    for (FileNumber = 0; FileNumber < Arguments; FileNumber++) {
    
        /* Open the File */
        if (FileNumber == 2) OpenType = "wb";
        Files[FileNumber] = fopen(argv[FileNumber + ArgOffset], OpenType);
        
        /* Check for failure and error out if so */
        if (!Files[FileNumber]) {
            perror(argv[FileNumber + ArgOffset]);
            return (1);
        }
    }

    /* Write the File Headers */
    WriteFileHeader(Files[NtosUserStubs], 
                    "System Call Stubs for Native API", 
                    argv[NtosUserStubs + ArgOffset]);
    
    WriteFileHeader(Files[NtosKernelStubs], 
                    "System Call Stubs for Native API", 
                    argv[NtosKernelStubs + ArgOffset]);
    fputs("#include <ndk/asm.h>\n\n", Files[NtosKernelStubs]);

    WriteFileHeader(Files[Win32kStubs], 
                    "System Call Stubs for Native API", 
                    argv[Win32kStubs + ArgOffset]);

    /* Create the System Stubs */
    CreateStubs(Files[NativeSystemDb],
                &Files[NtosUserStubs], 
                Files[NtosKernelStubs], 
                MAIN_INDEX, 
                1,
                1);

    /* Create the Graphics Stubs */
    CreateStubs(Files[NativeGuiDb], 
                &Files[Win32kStubs], 
                NULL, 
                WIN32K_INDEX, 
                1,
                0);

    /* Rewind the databases */
    rewind(Files[NativeSystemDb]);
    rewind(Files[NativeGuiDb]);

    /* Create the Service Tables */
    CreateSystemServiceTable(Files[NativeSystemDb], 
                            Files[NtosServiceTable],
                            "Main",
                            argv[NtosServiceTable + ArgOffset]);
    
    CreateSystemServiceTable(Files[NativeGuiDb], 
                            Files[Win32kServiceTable],
                            "Win32k",
                            argv[Win32kServiceTable + ArgOffset]);

    /* Close all files */
    for (FileNumber = 0; FileNumber < Arguments-ArgOffset; FileNumber++) {
    
        /* Close the File */
        fclose(Files[FileNumber]);
        
    }

    return(0);
}
