#include <windows.h>
#include "debug.h"
#include "stringutils.h"
#include "args.h"
#include "kernelmisc.h"
#include "cever_deps.h"
#include "deviceinfo.h"
#include "devicedriverloader.h"

// prun loadflashdrv -o 0x50000000 -l 0x0dc00000 -w x.nb
// prun loadflashdrv -o 0x50000000 -l 0x10000000 -s 0x00100000 -c 0x100
//
// dump kaiser flashdrv.dll memory:
//
// pmemdump -n filesys.exe 0x1fe4000 0x8000 -4 
//
//
//note that the other method of getting at this driver could be to open
//  the BDEV handle
//
#define IOCTL_FMD_NAND_READ_WLAN_DATA  0x71fe8


DWORD arm_mov(int dst, int src)
{
    return 0xe1a00000+(dst<<12)+src;
}
#if 0
bool finddmabuffer(BYTE ***pppbuffer, DWORD *psize, BYTE **ppbuffer2)
{
    deviceinfo dev;
    if (dev.getoemname()=="Kaiser") {
        *psize= 0x33C4;
        *pppbuffer= (BYTE**)0x01FEBBA8;
        *ppbuffer2= (BYTE*)0x01FE6270;
        return true;
    }
    else if (dev.getoemname()=="HTC Touch Diamond P3700") {
        *psize= 0x360C;
        *pppbuffer= (BYTE**)0x01FD3B34;
        *ppbuffer2= (BYTE*)0x01FCDA48;
        return true;
    }
    return 0;
}
#endif
// this finds a area to search for ADD x,y, #0x2400000
bool FindDriverOffsets(HMODULE hDll, DWORD **ppstart, DWORD *psize)
{
    // todo: find driver in memory
    debug("FindDriverOffsets(%08lx)\n", hDll);

    ModuleInfo mi;
    FillModuleInfo(mi, (MODULE*)hDll);
    debug("module %08lx %8x  %08lx %8x %s \n",
            mi.csegbase, mi.csegsize, mi.dsegbase, mi.dsegsize, mi.name.c_str());

    // todo: search in mi.csegbase .. mi.csegsize

    deviceinfo dev;
    DWORD vpatchaddr;
    if (dev.getoemname()=="Kaiser") {
        vpatchaddr= 0x3E42610;
        // 03E42678 ADD     R1, LR, #0x2400000
        // 03E428D4 ADD     R1, R3, #0x2400000
    }
    else if (dev.getoemname()=="HTC Touch Diamond P3700") {
        vpatchaddr= 0x03DE8910;
        // 03DE8978  ADD     R1, LR, #0x2400000  - in HTC_OEM_ReadNand
        // 03DE8BBC  ADD     R1, R3, #0x2400000  - in HTC_OEM_WriteNand
    }
    else if (dev.getoemname()=="X1i") {
        vpatchaddr= 0x03DE8960;
        // 03DE89CC  ADD     R1, LR, #0x2400000
        // 03DE8C10  ADD     R1, R3, #0x2400000
    }


    else {
        return false;
    }

    DWORD ppatchaddr= VirtToPhys(vpatchaddr);
    DWORD vvpatchaddr= PhysToVirt(ppatchaddr);
        
    debug("(v)%08lx -> v2p -> (p)%08lx -> p2v -> (v)%08lx\n", vpatchaddr, ppatchaddr, vvpatchaddr);
    *ppstart= (DWORD*)vvpatchaddr;
    *psize= 0x200;
    return true;
}

DWORD *searchforinstruction(HMODULE hDll, DWORD dwMask, DWORD dwValue)
{
    DWORD *pStart=0;
    DWORD dwSize=0;
    if (!FindDriverOffsets(hDll, &pStart, &dwSize))
        return 0;
    debug("searching range %08lx-%08lx for %08lx.%08lx\n", pStart, pStart+dwSize/sizeof(DWORD), dwMask, dwValue);
    for (DWORD *p= pStart ; p<pStart+dwSize/sizeof(DWORD) ; p++)
    {
        if (((*p)&dwMask)==(dwValue&dwMask)) {
            return p;
        }
    }
    debug("did not find %08lx.%08lx in %08lx-%08lx\n", dwMask, dwValue, pStart, pStart+dwSize/sizeof(DWORD));
    return 0;
}


int WINAPI WinMain( HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
    KernelMode _km;
    StringList args;
    if (!SplitString(ToString(lpCmdLine), args, false))
    {
        error("Error in commandline");
        return false;
    }
    DWORD dwOffset= 0x50000000;
    DWORD dwSize= 0;
    DWORD dwStepSize= 0;
    DWORD dwStepChunk= 0;
    bool bRemoveLimit= false;
    std::string savename;
    for (StringList::iterator i= args.begin() ; i!=args.end() ; ++i)
    {
        std::string& arg= *i;
        if (arg[0]=='-') switch(arg[1])
        {
            case 'o': HANDLESTLULOPTION(dwOffset, DWORD); break;
            case 'l': HANDLESTLULOPTION(dwSize, DWORD); break;
            case 's': HANDLESTLULOPTION(dwStepSize, DWORD); break;
            case 'c': HANDLESTLULOPTION(dwStepChunk, DWORD); break;
            case 'n': bRemoveLimit= true; break;
            case 'w': HANDLESTLSTROPTION(savename); break;
        }
    }
    // write log to the same disk as the savename.
    std::string logname= savename.substr(0,savename.find_last_of("\\/"));
    if (logname.find_last_of("\\/")<logname.size()-1)
        logname += "\\";
    logname += "loadflashdrv.log";
    DebugSetLogfile(logname.c_str());

    if (dwStepChunk==0 && dwStepSize!=0) {
        dwStepChunk=128;
    }
    else if (dwStepSize==0) {
        dwStepChunk=65536;
    }

    devicedriverloader flashdrv;
    if (!flashdrv.load(_T("Drivers\\BuiltIn\\FLASHDRV")))
        return 1;



    // patch away time consuming 'bad=0x...'  log line

//    *(DWORD*)0x3E426DC = 0xE3A00000;
//    *(DWORD*)0x3E42728 = 0x1A000025

    DWORD dwHandle= flashdrv.open(0,0);
    debugt("open: handle=%08lx stat=%08lx\n", dwHandle, GetLastError());
    debug("%s\n", hexdump((BYTE*)dwHandle-0x10, 0x40, 4).c_str());

    debug("saving to %s\n", savename.c_str());
    FILE *fsave=NULL;
    if (!savename.empty()) {
        fsave= fopen(savename.c_str(), "w+b");
        if (fsave==NULL) {
            error("fopen %s", savename.c_str());
            return 1;
        }
    }

//   .text:03DE8978 [E28E1509]   ADD R1, LR, #0x2400000 ; offset to 'APPSBL' 
    DWORD dwAddInstruction= 0xE28E1509;
    DWORD dwRegisterMask  = 0x000ff000;
    DWORD dwOperandMask  =  0x00000fff;
    // apply 'unlimited' patch
    //
    // ADD: 
    // cccc00I0100SnnnnddddOOOOOOOOOOOO
    // S : setflags bit
    // nnnn = source reg
    // dddd = dest reg
    // OOOOOOOOOOOO = shifter operand

    //
    // MOV:
    // cccc00I1101S0000ddddOOOOOOOOOOOO

    // I=1 : immediate : value = RRRRiiiiiiii : val=iiiiiiii>>(2*RRRR)
    // I=0 : O.654 = shifttype   
    //    0  LSL#   iiiii 000 mmmm
    //       MOV    00000 000 mmmm
    //    1  LSL    ssss0 001 mmmm
    //    2  LSR#   iiiii 010 mmmm
    //    3  LSR    ssss0 011 mmmm
    //    4  ASR#   iiiii 100 mmmm
    //    5  Asr    ssss0 101 mmmm
    //    6  ror#   iiiii 110 mmmm
    //       RRX    00000 110 mmmm
    //    7  Ror    ssss0 111 mmmm
    //    ?         ....1 ..1 ....  not dataprocessing insn.

    DWORD *PatchOffset= 0;
    DWORD dwOldValue= 0;
    if (bRemoveLimit) {
        PatchOffset= searchforinstruction(flashdrv.hlib(), ~dwRegisterMask, dwAddInstruction);

        // disable nand-mpu ( phys 0xa0b00000 )
        *(DWORD*)0xb1200000=0;
    }
    if (PatchOffset) {
        dwOldValue= *PatchOffset;
        DWORD dwNewValue= (dwOldValue&~dwOperandMask);  // change to ADD Rx,Ry,#0
        *PatchOffset= dwNewValue;
        debug("found %08lx at %08lx, changed to %08lx\n", dwOldValue, PatchOffset, dwNewValue);
    }
#if 0
    DWORD dmbuffersize= 0;
    BYTE **pdmbuffer=0;
    BYTE *tmpbuffer=0;
    finddmabuffer(&pdmbuffer, &dmbuffersize, &tmpbuffer);
    debug("dmabuffers: %08lx:%08lx/%04x,  %08lx\n", pdmbuffer, *pdmbuffer, dmbuffersize, tmpbuffer);
#endif
    while(dwSize)
    {
        ByteVector data; data.resize(dwSize<dwStepChunk?dwSize:dwStepChunk);
        DWORD req[3];
        req[0]= dwOffset;
        req[1]= data.size();
        req[2]= (DWORD)vectorptr(data);
        DWORD nRet;
        BOOL iores= flashdrv.DrvIOControl(dwHandle, IOCTL_FMD_NAND_READ_WLAN_DATA, NULL, 0, (PBYTE)req, 12, &nRet);
        debugt("ioctl(IOCTL_FMD_NAND_READ_WLAN_DATA, %08lx, req=%08lx): ret=%08lx  res=%08lx stat=%08lx  reqrec={%x,%x,%x}\n", dwOffset, data.size(), nRet, iores, GetLastError(),  req[0], req[1], req[2]);
/*
        if (pdmbuffer) {
            debug("dmabuffer : %08lx/%08lx : %d\n", *pdmbuffer, dmbuffersize, IsBadReadPtr(*pdmbuffer, dmbuffersize));
            bighexdump((DWORD)*pdmbuffer, ByteVector(*pdmbuffer, (*pdmbuffer)+dmbuffersize), hexdumpflags(DUMPUNIT_DWORD, 8, DUMP_HEX)|HEXDUMP_WITH_OFFSET|HEXDUMP_SUMMARIZE);
            debug("tmpbuffer : %08lx/%08lx : %d\n", tmpbuffer, 2*0x840, IsBadReadPtr(tmpbuffer, 2*0x840));
            bighexdump((DWORD)tmpbuffer, ByteVector(tmpbuffer, tmpbuffer+2*0x840), hexdumpflags(DUMPUNIT_DWORD, 8, DUMP_HEX)|HEXDUMP_WITH_OFFSET|HEXDUMP_SUMMARIZE);
            debug("\n");
        }
*/
        if (nRet==0)
            nRet= data.size();
        if (fsave) {
            fwrite(vectorptr(data), 1, data.size(), fsave);
        }
        else {
            bighexdump(dwOffset, data, hexdumpflags(DUMPUNIT_BYTE, dwStepSize?dwStepChunk:16, DUMP_HEX_ASCII)|HEXDUMP_SUMMARIZE|HEXDUMP_WITH_OFFSET);
        }

        dwOffset+=dwStepSize?dwStepSize:nRet;
        dwSize-=dwStepSize?dwStepSize:nRet;
    }

    // undo patch
    if (PatchOffset) *PatchOffset= dwOldValue;

    if (fsave)
        fclose(fsave);

    BOOL clres= flashdrv.DrvClose(dwHandle);
    debugt("close: res=%08lx stat=%08lx\n", clres, GetLastError());

    return 0;
}
