/* (C) 2003-2007 Willem Jan Hengeveld <itsme@xs4all.nl>
 * Web: http://www.xs4all.nl/~itsme/
 *      http://wiki.xda-developers.com/
 *
 * $Id$
 */

/* this file implements reading from and writing to
 * the m-sys tffs chip on the sp3
 *
 *
 * the disk device can be specified with:
 *    - a handle                     -h 0x12345678
 *    - a storename + partitionname  -s TrueFFS -p Part00
 *    - a devicename                 -d DSK1:
 *
 * a device can be read in the following ways:
 *    - wince diskread                  -w
 *    - tffs diskread                   *default*
 *    - tffs binary partition read      -n <nr>
 *    - tffs one time programmable read -o
 *
 * done: problem reading 0x10000 from bdk1 on sp3
 *       ... issue is that sector size is not the same everywhere
 *      -B option
 *
 * done: change interface, such that data can be read in other sizes than only sector sized chunks.
 *      -G option
 *
 * todo: pdocread 0x800 0x1aff800 os.nb
 *    sometimes crashes with activesync error.
 *
 * todo: pdocread -n 1 0 0x200
 *    from a disk with blocksize 0x10000  should still only do the 0x200 read.
 *
 * done: add option to specify password, to unlock a specific partition
 *      -u option
 *
 * pdocread -n 1  -b 0x30000 0x2D0000 0x30000
 *      crashes  ... because my code assumes sectorsize is a power of 2.
 *
 * pdocwrite -n 1 subsplash.bmp 0x30000
 *      CopyFileToTFFS(subsplash.bmp:0, 30000, 0000c036)
 *      ERROR: could not read 0x0 bytes of preceeding sector data at 0x30000
 * 
 * todo: pdocread from non-sector boundaries
 * pdocread 0x1234 0x4321 x.nb
 *
 */
#include <util/wintypes.h>
#include <util/rapitypes.h>
#include "itsutils.h"
#include "DiskReader.h"
#include "dllversion.h"
#include "debug.h"
#include "args.h"
#include <stdio.h>
#include <stdint.h>
#include "stringutils.h"
#include "vectorutils.h"
#include <algorithm>

DWORD g_dwReadChunkSize=65536;

bool g_ignoreerror= false;
bool g_bVerbose= false;
bool g_bProgressbar= false;
uint64_t g_totallength= 0;
DWORD g_dotsprinted= 0;

class TFFSDeviceSpec;

void FindDeviceSize(const TFFSDeviceSpec& tffs);

bool CopyTFFSToFile(const TFFSDeviceSpec& tffs, uint64_t llOffset, uint64_t llLength, const std::string& outfilename);
bool HexdumpTFFSToStdout(const TFFSDeviceSpec& tffs, uint64_t llOffset, uint64_t llLength);
bool CopyFileToTFFS(const std::string& infilename, uint64_t llFileOffset, const TFFSDeviceSpec& tffs, uint64_t llOffset, uint64_t llLength);

bool ITDiskProtect(const TFFSDeviceSpec& tffs, bool bInsert, const std::string& passwd);
bool ITReadDisk(const TFFSDeviceSpec& tffs, uint64_t llOffset, BYTE *buffer, DWORD dwBytesWanted, DWORD *pdwNumberOfBytesRead);
bool ITWriteDisk(const TFFSDeviceSpec& tffs, uint64_t llOffset, const BYTE *buffer, DWORD dwBytesWanted, DWORD *pdwNumberOfBytesRead);

bool ITTFFSGetInfo(const TFFSDeviceSpec& tffs, DWORD &dwSectorSize, uint64_t &llDiskSize);
bool ITTFFSGetUniqueid(const TFFSDeviceSpec& tffs);
bool ITLogDiskInfo(const TFFSDeviceSpec& tffs);

bool ITGetStoreMgrList(std::vector<DevStoreInfo>& list);
bool ITGetPartitionList(const TFFSDeviceSpec& tffs, std::vector<DevPartitionInfo>& list);

bool ITGetSTRGHandleList(std::vector<DWORD> &list);

std::string sizestring(uint64_t size);
uint64_t GetFileSize(const std::string& filename);
bool ListDevices();
bool ListSTRGHandles();
bool GetDefaultDiskName(TFFSDeviceSpec& tffs);
bool GetDefaultDiskHandle(TFFSDeviceSpec& tffs);

// may contain:
// "", "DSK1:", 1
// "TRUEFFS_DOC", "Part01", 1

void write_progress(uint64_t llOffset)
{
    while ((llOffset*80)>(g_totallength*g_dotsprinted)) {
        putchar('.');
        g_dotsprinted++;
    }
}

class TFFSDeviceSpec {
public:
    TFFSDeviceSpec() : dwBinaryPartitionNr(BP_TFFSSECTOR), dwHandle(0), dwSectorSize(0), dwBlockSize(0), llDiskSize(0), bdksign("BIPO"), pWriteEnableFlag(0) {}

    std::string devicename;
    std::string partitionname;
    DWORD dwBinaryPartitionNr;
    DWORD dwHandle;

    DWORD dwSectorSize; // used to calculate sector address
    DWORD dwBlockSize;  // data is read in blocksize chunks
    uint64_t llDiskSize;

    std::string bdksign;

    DWORD pWriteEnableFlag;

    void GetDiskInfo()
    {
        if (dwSectorSize==0 
                && !ITTFFSGetInfo(*this, dwSectorSize, llDiskSize)) {
            debug("WARNING: using default 512 bytes for sectorsize\n");
            dwSectorSize= 512;
            llDiskSize= 0;
        }
    }
    bool ReadTFFS(uint64_t llOffset, BYTE *buffer, DWORD dwBytesWanted, DWORD *pdwNumberOfBytesRead) const
    {
        if (g_bVerbose)
            printf("reading 0x%x bytes from 0x%I64x with %hs %hs\n", 
                    dwBytesWanted, llOffset,
                    accessname().c_str(), description().c_str());
        if (g_bProgressbar)
            write_progress(llOffset);
        return ITReadDisk(*this, llOffset, buffer, dwBytesWanted, pdwNumberOfBytesRead);
    }
    bool WriteTFFS(uint64_t llOffset, const BYTE *buffer, DWORD dwBufferSize, DWORD *pdwNumberOfBytesWritten) const
    {
        if (g_bVerbose)
            printf("writing 0x%x bytes to 0x%I64x with %hs %hs\n", 
                    dwBufferSize, llOffset,
                    accessname().c_str(), description().c_str());
        if (g_bProgressbar)
            write_progress(llOffset);
        return ITWriteDisk(*this, llOffset, buffer, dwBufferSize, pdwNumberOfBytesWritten);
    }

    std::string accessname() const
    {
        switch(dwBinaryPartitionNr)
        {
        case BP_TFFSSECTOR: return "tffsread";
        case BP_WINCESECTOR: return "winceread";
        case BP_OTPSECTOR: return "otpread";
        case BP_FLASHDRV: return "qualcomm";
        case BP_ONDISK: return "samsung";
        default: return stringformat("bdk%d", dwBinaryPartitionNr);
        }
    }
    std::string description() const
    {
        if (partitionname.empty())
            return devicename;
        else
            return devicename + "/" + partitionname;
    }
};

void usage(const std::string& cmd)
{
    printf("(C) 2003-2008 Willem jan Hengeveld  itsme@xs4all.nl\n");
    if (cmd.substr(0,8)=="pdocread") {
        printf("Usage: pdocread [options] start [ length [ filename ] ]\n");
        printf("    when no length is specified, 512 bytes are assumed\n");
        printf("    when no filename is specified, a hexdump is printed\n");
    }
    else if (cmd.substr(0,9)=="pdocwrite") {
        printf("Usage: pdocwrite [options] filename [ start [ length ] ]\n");
        printf("    when no start offset is specified, the start of the device is assumed\n");
        printf("    when no length is specified, the whole file ( minus the -s OFS ) is written\n");
    }
    else {
        printf("ERROR: expecting to be called either 'pdocread' or 'pdocwrite' - %hs\n", cmd.c_str());
        return;
    }

    printf("    -t     : find exact disk size\n");
    printf("    -l     : list all diskdevices\n");
    printf("    -v     : be verbose\n");
    printf("    -s OFS : seek into source file ( for writing only )\n");
    printf("    -b SIZE: specify sectorsize used to calculate sector offsets\n");
    printf("    -B SIZE: specify blocksize to use when reading the disk\n");
    printf("    -G SIZE: specify blocksize to use when transfering over activesync\n");
    printf("    -u PASSWD : unlock DOC device\n");
    printf("    -S BK1x : specify alternate disksignature ( e.g. BIPO, BK1A .. BK1G )\n");
    printf("    -W OFS : specify optional 'writeenable' flag ptr\n");

    printf("Source:\n");
    printf("    -d NAME : devicename or storename\n");
    printf("    -p NAME : partitionname\n");
    printf("    -h HANDLE : directly specify handle\n");
    printf("  either specify -d and optionally -p, or specify -h\n");

    printf("Method:\n");
    printf("    -n NUM : binarypartition number ( normal p if omitted )\n");
    printf("    -w     : read via windows disk api\n");
    printf("    -o     : read OTP area\n");
    printf("    -F     : read qualcomm flash using flashdrv.dll\n");
    printf("    -N     : read samsung flash using ondisk.dll\n");
    printf("       -ux : unlock the hidden area's for qualcomm or samsung flash\n");
    printf("\n");
    printf("if the filename is omitted, the data is hexdumped to stdout\n");
    printf("if no length is specified, 512 bytes are printed\n");
    printf("\n");
    printf("numbers can be specified as hex (ex: 0x8000)  or decimal (ex: 32768)\n");

}
std::string GetFileFromPath(const std::string& name)
{
    size_t lastslash= name.find_last_of("\\/");
    if (lastslash==name.npos)
        return name;

    return name.substr(lastslash+1);
}
bool needdiskhandle(DWORD devid)
{
	return (devid<0x1000 || devid>=BP_OTPSECTOR);
}
bool isnumber(const std::string& str)
{
    char *p;
    _strtoi64(str.c_str(), &p, 0);
    return *p==0;
}
int main( int argc, char *argv[])
{
    DebugStdOut();

    std::string cmd= tolower(GetFileFromPath(argv[0]));
    bool bWriting= false;
    if (cmd.substr(0,8)=="pdocread") {
        bWriting= false;
    }
    else if (cmd.substr(0,9)=="pdocwrite") {
        bWriting= true;
    }
    else {
        printf("ERROR: don't know what I am : %hs\n", cmd.c_str());
        return 1;
    }

    uint64_t llDiskOffset=0;
    uint64_t llLength=0;
    std::string filename;

    TFFSDeviceSpec tffs;

    uint64_t llFileOffset= 0;

    bool bDoListDevices= false;
    bool bDoFindDeviceSize= false;
    bool bUnprotect= false;
    std::string password;
    std::string handlespec;

    StringList args;

    for (int i=1 ; i<argc ; i++)
    {
        if (argv[i][0]=='-') switch(argv[i][1])
        {
            case 'l': bDoListDevices= true; break;

            case 'd': HANDLESTROPTION(tffs.devicename); break;
            case 'p': HANDLESTROPTION(tffs.partitionname); break;
            case 'u': bUnprotect= true; HANDLESTROPTION(password); break;
            case 'S': HANDLESTROPTION(tffs.bdksign); break;
            case 'h': HANDLESTROPTION(handlespec); break;
            case 'n': HANDLEULOPTION(tffs.dwBinaryPartitionNr, DWORD); break;
            case 'W': HANDLEULOPTION(tffs.pWriteEnableFlag, DWORD); break;
            case 'F': tffs.dwBinaryPartitionNr= BP_FLASHDRV; break;
            case 'N': tffs.dwBinaryPartitionNr= BP_ONDISK; break;
            case 'w': tffs.dwBinaryPartitionNr= BP_WINCESECTOR; break;
            case 'o': tffs.dwBinaryPartitionNr= BP_OTPSECTOR; break;

            case 'v': g_bVerbose= true; break;
            case 't': bDoFindDeviceSize= true; break;
            case 's': HANDLELLOPTION(llFileOffset, DWORD); break;
            case 'b': HANDLEULOPTION(tffs.dwSectorSize, DWORD); break;
            case 'B': HANDLEULOPTION(tffs.dwBlockSize, DWORD); break;
            case 'G': HANDLEULOPTION(g_dwReadChunkSize, DWORD); break;
            case 'i': g_bProgressbar= true; break;
            default:
                usage(cmd);
                return 1;
        }
        else 
            args.push_back(argv[i]);
    }

    if (g_dwReadChunkSize < tffs.dwSectorSize)
        g_dwReadChunkSize= tffs.dwSectorSize;

    CheckITSDll();

    StringList::iterator ai= args.begin();
    if (bDoListDevices) {
        if (ai!=args.end()) { usage(cmd); return 1; }
        ListDevices();
        ListSTRGHandles();

        return 0;
    }
    if (!handlespec.empty()) {
        if (handlespec[0]=='#') {
            std::vector<DWORD> hlist;
            if (!ITGetSTRGHandleList(hlist)) {
                error("ITGetSTRGHandleList");
                return false;
            }
            int hidx= strtoul(&handlespec[1], 0, 0);
            tffs.dwHandle= (hidx>=0 && hidx<hlist.size()) ? hlist[hidx] : 0;
        }
        else {
            tffs.dwHandle= strtoul(&handlespec[0], 0, 0);
        }
    }


    if (tffs.devicename.empty() 
        && tffs.partitionname.empty()
        && tffs.dwHandle==0
		&& needdiskhandle(tffs.dwBinaryPartitionNr)) {
            if (!GetDefaultDiskName(tffs) 
                    && !GetDefaultDiskHandle(tffs)) {
                debug("WARNING could not find default device\n");
                return 1;
            }
        }

    if (g_bVerbose)
        ITLogDiskInfo(tffs);

    tffs.GetDiskInfo();

    if (bDoFindDeviceSize) {
        FindDeviceSize(tffs);
        return 0;
    }

    if (ai==args.end()) { usage(cmd); return 1; }

    bool bRes= true;

    if (bUnprotect)
        ITDiskProtect(tffs, true, password);

    if (bWriting) {
        if (ai==args.end()) { usage(cmd); return 1; }
        filename= *ai++;
        if (filename.empty() || isnumber(filename)) {
            printf("NOTE: with pdocwrite you need to specify the filename first, then the offset\n");
            printf("    or if your filename is really a number, specify it as:\n\t./%s\n", filename.c_str());
            return 1;
        }
        if (ai!=args.end()) {
            llDiskOffset= _strtoi64((*ai++).c_str(), 0, 0);
        }
        else {
            llDiskOffset= 0;
        }
        if (ai!=args.end())
            llLength= _strtoi64((*ai++).c_str(), 0, 0);
        else
            llLength= (DWORD)(GetFileSize(filename)-llFileOffset);
        if (ai!=args.end()) { usage(cmd); return 1; }

        if (g_bProgressbar)
            g_totallength= llLength;
        bRes = bRes && CopyFileToTFFS(filename, llFileOffset, tffs, llDiskOffset, llLength);
    }
    else {
        llDiskOffset= _strtoi64((*ai++).c_str(), 0, 0);

        llLength= 0x200;
        if (ai!=args.end()) llLength= _strtoi64((*ai++).c_str(), 0, 0);
        if (ai!=args.end()) filename= *ai++;

        if (ai!=args.end()) { usage(cmd); return 1; }
        if (g_bProgressbar)
            g_totallength= llLength;
        if (filename.empty())
            bRes = bRes && HexdumpTFFSToStdout(tffs, llDiskOffset, llLength);
        else 
            bRes = bRes && CopyTFFSToFile(tffs, llDiskOffset, llLength, filename);
    }

    if (bUnprotect)
        ITDiskProtect(tffs, false, password);

    StopItsutils();
    return bRes ? 0 : 1;
}

bool GetDefaultDiskName(TFFSDeviceSpec& tffs)
{
    std::vector<DevStoreInfo> list;

    if (!ITGetStoreMgrList(list)) {
        error("GetDefaultDiskName: ITGetStoreMgrList");
        return false;
    }
    if (list.empty()) {
        if (g_bVerbose)
            debug("ERROR: could not find default device in \\StoreMgr\n");
        return false;
    }
    for (size_t iStore=0 ; iStore<list.size() ; iStore++) {
        std::vector<DevPartitionInfo> plist;

        tffs.devicename= ToString(list[iStore].szDeviceName);

        if (!ITGetPartitionList(tffs, plist)) {
            continue;
        }
        if (plist.empty()) {
            continue;
        }

        tffs.partitionname= ToString(plist[0].szPartitionName);
        return true;
    }

    if (g_bVerbose)
        debug("WARNING: could not find device with partitions to use as default\n");
    return false;
}
bool GetDefaultDiskHandle(TFFSDeviceSpec& tffs)
{
    std::vector<DWORD> hlist;
    if (!ITGetSTRGHandleList(hlist)) {
        error("ITGetSTRGHandleList");
        return false;
    }
    if (hlist.empty()) {
        if (g_bVerbose)
            debug("ERROR: could not find default device in STRG handle list\n");
        return false;
    }
    tffs.dwHandle= hlist[0];

    return true;
}

bool ListDevices()
{
    std::vector<DevStoreInfo> list;

    if (!ITGetStoreMgrList(list)) {
        error("ListDevices: ITGetStoreMgrList");
        return false;
    }
    for (size_t iStore=0 ; iStore<list.size() ; iStore++) {
        debug("%hs (0x%I64x) %ls\n", sizestring(list[iStore].llStoreSize).c_str(), list[iStore].llStoreSize, list[iStore].szDeviceName);

        std::vector<DevPartitionInfo> plist;

        TFFSDeviceSpec tffs;
        tffs.devicename= ToString(list[iStore].szDeviceName);

        if (!ITGetPartitionList(tffs, plist)) {
            error("ITGetPartitionList");
            continue;
        }

        for (size_t iPart=0 ; iPart<plist.size() ; iPart++) {
            debug("|         %hs (0x%I64x) %ls\n", sizestring(plist[iPart].llPartitionSize).c_str(), plist[iPart].llPartitionSize, plist[iPart].szPartitionName);

            tffs.partitionname= ToString(plist[iPart].szPartitionName);
        }
    }

    return true;
}
bool ListSTRGHandles()
{
    std::vector<DWORD> hlist;
    if (!ITGetSTRGHandleList(hlist)) {
        error("ITGetSTRGHandleList");
        return false;
    }
    if (hlist.size()) {
        debug("STRG handles: \n");
        for (size_t i=0 ; i<hlist.size() ; i++) {
            debug("handle#%d %08lx", i, hlist[i]);

            TFFSDeviceSpec tffs;
            tffs.dwHandle= hlist[i];

            tffs.GetDiskInfo();
            if (tffs.llDiskSize)
                debug(" %hs (0x%I64x)", sizestring(tffs.llDiskSize).c_str(), tffs.llDiskSize);
            debug("\n");
        }
        for (size_t i=0 ; i<hlist.size() ; i++) {
            debug("disk %08lx", hlist[i]);

            TFFSDeviceSpec tffs;
            tffs.dwHandle= hlist[i];

            ITTFFSGetUniqueid(tffs);
        }
        debug("\n");
    }

    return true;
}

bool CopyFileToTFFS(const std::string& infilename, uint64_t llFileOffset, const TFFSDeviceSpec& tffs, uint64_t llOffset, uint64_t llLength)
{
    debug("CopyFileToTFFS(%s:%I64x, %I64x, %I64x)\n",
            infilename.c_str(), llFileOffset, llOffset, llLength);
    HANDLE hSrc = CreateFile(infilename.c_str(), GENERIC_READ, FILE_SHARE_READ,
                NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hSrc)
    {
        error("Unable to open host/destination file");
        return false;
    }

    LARGE_INTEGER li;  li.QuadPart= llFileOffset;
    if (INVALID_SET_FILE_POINTER==SetFilePointer(hSrc, li.LowPart, &li.HighPart, FILE_BEGIN))
    {
        error("Unable to seek to data start");
        return false;
    }

    ByteVector buffer; buffer.resize(g_dwReadChunkSize);
    while (llLength)
    {
        DWORD dwBufferOffset= 0;

        // if start offset not at sector boundary, first read until sector boundary.
        DWORD dwWanted= std::min(llLength, (llOffset&(tffs.dwSectorSize-1)) ? tffs.dwSectorSize-(llOffset&(tffs.dwSectorSize-1)): buffer.size());

        // if end offset not at sector boundary, first read complete sectors.
        if ((dwWanted&(tffs.dwSectorSize-1))!=0 && dwWanted>tffs.dwSectorSize)
            dwWanted &= ~(tffs.dwSectorSize-1);

        // if not at sector boundary, or less than one sector to read, first fill buffer
        if (llOffset&(tffs.dwSectorSize-1) || llLength<tffs.dwSectorSize) {
            dwBufferOffset= (DWORD)llOffset&(tffs.dwSectorSize-1);
            DWORD nRead;
            if (!tffs.ReadTFFS(llOffset&~(tffs.dwSectorSize-1), vectorptr(buffer), tffs.dwSectorSize, &nRead))
                return false;

            if (nRead!=tffs.dwSectorSize) {
                debug("ERROR: could not read 0x%x bytes of preceeding sector data at 0x%I64x\n", 
                        dwBufferOffset, llOffset&~(tffs.dwSectorSize-1));
                return false;
            }
        }

        DWORD dwNumRead;
        if (!ReadFile(hSrc, vectorptr(buffer)+dwBufferOffset, dwWanted, &dwNumRead, NULL))
        {
            error("Error Reading file");
            return false;
        }
        //printf("wanted=%08lx  bufofs=%08lx  ofs=%I64x read=%08lx\n", dwWanted, dwBufferOffset, llOffset, dwNumRead);

        DWORD dwToWrite= dwNumRead;
        if (llOffset&(tffs.dwSectorSize-1) || llLength<tffs.dwSectorSize) {
            dwToWrite= tffs.dwSectorSize;
        }
        DWORD dwNumberOfBytesWritten;
        if (!tffs.WriteTFFS(llOffset&~(tffs.dwSectorSize-1), vectorptr(buffer), dwToWrite, &dwNumberOfBytesWritten))
            return false;

        llLength -= dwNumRead;
        llOffset += dwNumRead;
    }

    CloseHandle (hSrc);

    return true;
}


bool testSector(const TFFSDeviceSpec& tffs, DWORD sectornr)
{
    ByteVector buf; buf.resize(tffs.dwSectorSize);

    DWORD dwNumberOfBytesRead;
    g_ignoreerror= true;
    bool bRes= tffs.ReadTFFS( UInt32x32To64(sectornr, tffs.dwSectorSize), vectorptr(buf), buf.size(), &dwNumberOfBytesRead);
    g_ignoreerror= false;

    if (!bRes) {
        if (g_bVerbose)
            printf("testsector 0x%x / offset %I64x : error\n", sectornr, UInt32x32To64(sectornr, tffs.dwSectorSize));
        return false;
    }

    if (g_bVerbose)
        printf("testsector 0x%x / offset %I64x : %d bytes read\n", sectornr, UInt32x32To64(sectornr, tffs.dwSectorSize), dwNumberOfBytesRead);
    return dwNumberOfBytesRead==tffs.dwSectorSize;
}

// dwLow is an existing sector, dwHigh is not.
DWORD findInRange(const TFFSDeviceSpec& tffs, DWORD dwLow, DWORD dwHigh)
{
    if (dwLow>=dwHigh)
        return 0;
    if (dwLow+1==dwHigh)
        return dwLow;

    DWORD dwMiddle = (dwLow + dwHigh)/2;

    if (testSector(tffs, dwMiddle))
        return findInRange(tffs, dwMiddle, dwHigh);
    else
        return findInRange(tffs, dwLow, dwMiddle);
}

std::string sizestring(uint64_t size)
{
    return (size>(uint64_t)1024*1024*1024*1024)? stringformat("%6.2fT", (double)size/((uint64_t)1024*1024*1024*1024))
			: (size>(uint64_t)1024*1024*1024)? stringformat("%6.2fG", (double)size/((uint64_t)1024*1024*1024))
			: (size>(uint64_t)1024*1024)? stringformat("%6.2fM", (double)size/((uint64_t)1024*1024))
			: (size>(uint64_t)1024)? stringformat("%6.2fk", (double)size/(uint64_t)1024)
			: stringformat("%6.2f", (double)size);
}
void FindDeviceSize(const TFFSDeviceSpec& tffs)
{
    DWORD sectornr= 0x40000000/tffs.dwSectorSize;  // random initial estimate : 1G
    if (testSector(tffs, sectornr)) {
        do {
            sectornr *= 2;
        } while (testSector(tffs, sectornr));

        sectornr /= 2;
    }
    else {
        do {
            sectornr /= 2;
        } while (sectornr && !testSector(tffs, sectornr));
    }
    sectornr= findInRange(tffs, sectornr, sectornr*2);
    uint64_t size= UInt32x32To64(sectornr+1,tffs.dwSectorSize);
    debug("real nr of sectors: %d  - %hsbyte, 0x%I64x\n", sectornr+1, sizestring(size).c_str(), size);
}

bool HexdumpTFFSToStdout(const TFFSDeviceSpec& tffs, uint64_t llOffset, uint64_t llLength)
{
    debug("HexdumpTFFSToStdout(0x%I64x, 0x%I64x)\n", llOffset, llLength);

    ByteVector buffer; buffer.resize(g_dwReadChunkSize);
    while (llLength)
    {
        DWORD dwWanted= std::min(llLength+(llOffset&(tffs.dwSectorSize-1)), (uint64_t)buffer.size());
        if (dwWanted&(tffs.dwSectorSize-1))
            dwWanted = (dwWanted|(tffs.dwSectorSize-1))+1;

        DWORD dwNumberOfBytesRead;
        if (!tffs.ReadTFFS(llOffset, vectorptr(buffer), dwWanted, &dwNumberOfBytesRead))
            return false;
        if (dwNumberOfBytesRead==0) {
            debug("WARNING: no more data\n");
            break;
        }

        DWORD dwBufferOffset= (DWORD)llOffset&(tffs.dwSectorSize-1);
        DWORD dwNeeded= std::min(llLength, (uint64_t)dwNumberOfBytesRead);

        //debug("ofs=%I64x len=%I64x want=%08lx read=%08lx buf=%08lx  need=%08lx\n", llOffset, llLength, dwWanted, dwNumberOfBytesRead, dwBufferOffset, dwNeeded);

        bighexdump((DWORD)llOffset, ByteVector(vectorptr(buffer)+dwBufferOffset, vectorptr(buffer)+dwBufferOffset+dwNeeded));

        llLength -= dwNeeded;
        llOffset += dwNeeded;
    }
    return true;
}

bool CopyTFFSToFile(const TFFSDeviceSpec& tffs, uint64_t llOffset, uint64_t llLength, const std::string& outfilename)
{
    debug("CopyTFFSToFile(0x%I64x, 0x%I64x, %s)\n", llOffset, llLength, outfilename.c_str());
    HANDLE hDest = CreateFile(outfilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
                NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hDest)
    {
        error("Unable to open host/destination file");
        return false;
    }

    ByteVector buffer; buffer.resize(g_dwReadChunkSize);
    while (llLength)
    {
        DWORD dwWanted= std::min(llLength+(llOffset&(tffs.dwSectorSize-1)), (uint64_t)buffer.size());
        if (dwWanted&(tffs.dwSectorSize-1))
            dwWanted = (dwWanted|(tffs.dwSectorSize-1))+1;

        DWORD dwNumberOfBytesRead;
        if (!tffs.ReadTFFS(llOffset&~(tffs.dwSectorSize-1), vectorptr(buffer), dwWanted, &dwNumberOfBytesRead))
            return false;
        if (dwNumberOfBytesRead==0) {
            debug("WARNING: no more data\n");
            break;
        }

        DWORD dwBufferOffset= (DWORD)llOffset&(tffs.dwSectorSize-1);
        DWORD dwNeeded= std::min(llLength, (uint64_t)dwNumberOfBytesRead);

        DWORD dwNumWritten;
        if (!WriteFile(hDest, vectorptr(buffer)+dwBufferOffset, dwNeeded, &dwNumWritten, NULL))
        {
            error("Error Writing file");
            return false;
        }

        llLength -= dwNeeded;
        llOffset += dwNeeded;
    }
    CloseHandle (hDest);

    return true;
}

uint64_t GetFileSize(const std::string& filename)
{
    HANDLE hSrc = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ,
                NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hSrc)
    {
        error("Unable to open file %hs", filename.c_str());
        return 0;
    }

    ULARGE_INTEGER uli;
    uli.LowPart= GetFileSize(hSrc, &uli.HighPart);

    CloseHandle(hSrc);

    return uli.QuadPart;
}

//******************************************************************************
//  functions calling cerapiinvoke.

bool ITWriteDisk(const TFFSDeviceSpec& tffs, uint64_t llDiskOffset, const BYTE *buffer, DWORD dwBufferSize, DWORD *pdwNumberOfBytesWritten)
{
    int insize= sizeof(WriteDiskParams)+dwBufferSize;
    WriteDiskParams *inbuf= (WriteDiskParams*)RapiAlloc(insize);
    DWORD outsize=0;
    WriteDiskResult *outbuf=NULL;
    wcsncpy(inbuf->szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    wcsncpy(inbuf->szPartitionName, ToWString(tffs.partitionname).c_str(), PARTITIONNAMESIZE);
    memcpy(inbuf->bdksign, tffs.bdksign.c_str(), 4);
    inbuf->dwBinaryPartitionNr= tffs.dwBinaryPartitionNr;
    inbuf->pWriteEnableFlag= tffs.pWriteEnableFlag;
    inbuf->hDisk= tffs.dwHandle;

    inbuf->llOffset= llDiskOffset;
    inbuf->dwSize= dwBufferSize;
    inbuf->dwSectorSize= tffs.dwSectorSize;
    inbuf->dwBlockSize= tffs.dwBlockSize;

    memcpy(inbuf->buffer, buffer, dwBufferSize);

    HRESULT res= ItsutilsInvoke("ITWriteDisk", 
            insize, (BYTE*)inbuf,
            &outsize, (BYTE**)&outbuf);
    if (res)
    {
        error(res, "ITWriteDisk");
        return false;
    }
    if (outbuf==NULL) {
        debug("ERROR: ITWriteDisk: outbuf==NULL\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        return false;
    }
    verify_size(outbuf, outbuf+1, outsize, "ITWriteDisk");
    *pdwNumberOfBytesWritten= outbuf->dwNumberOfBytesWritten;
    RapiFree(outbuf);

    return true;
}


// ........................................
// reading
bool ITReadDisk(const TFFSDeviceSpec& tffs, uint64_t llOffset, BYTE *buffer, DWORD dwBytesWanted, DWORD *pdwNumberOfBytesRead)
{
    ReadDiskParams inbuf;
    DWORD outsize=0;
    ReadDiskResult *outbuf=NULL;

    if (g_bVerbose)
        debug("readdisk('%s', '%s', %d, %08lx, %08lx)\n", tffs.devicename.c_str(), tffs.partitionname.c_str(), tffs.dwBinaryPartitionNr, llOffset, dwBytesWanted);

    wcsncpy(inbuf.szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    wcsncpy(inbuf.szPartitionName, ToWString(tffs.partitionname).c_str(), PARTITIONNAMESIZE);
    memcpy(inbuf.bdksign, tffs.bdksign.c_str(), 4);
    inbuf.dwBinaryPartitionNr= tffs.dwBinaryPartitionNr;
    inbuf.hDisk= tffs.dwHandle;

    inbuf.llOffset= llOffset;
    inbuf.dwSize= dwBytesWanted;
    inbuf.dwSectorSize= tffs.dwSectorSize;
    inbuf.dwBlockSize= tffs.dwBlockSize;

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITReadDisk",
            sizeof(ReadDiskParams), (BYTE*)&inbuf,
            &outsize, (BYTE**)&outbuf);

    if (outbuf==NULL) {
        if (!g_ignoreerror)
            error(res, "ITReadDisk: outbuf==NULL\n");
        SetLastError(res ? res : ERROR_INTERNAL_ERROR);
        return false;
    }
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITReadDisk : read %08lx bytes", outbuf->dwNumberOfBytesRead);
        return false;
    }
    //verify_size(outbuf, outbuf->buffer+outbuf->dwNumberOfBytesRead, outsize, "ITReadDisk");
    if (g_bVerbose)
        debug("readdisk: %08lx actually read\n", outbuf->dwNumberOfBytesRead);

    memcpy(buffer, outbuf->buffer, outbuf->dwNumberOfBytesRead);
    *pdwNumberOfBytesRead= outbuf->dwNumberOfBytesRead;

    RapiFree(outbuf);

    return true;
}
bool ITDiskProtect(const TFFSDeviceSpec& tffs, bool bInsert, const std::string& passwd)
{
    DiskProtectParams inbuf;
    DWORD outsize=0;
    DiskProtectResult *outbuf=NULL;

    if (g_bVerbose)
        debug("protect('%s', '%s', %d, %d, %s)\n", tffs.devicename.c_str(), tffs.partitionname.c_str(), tffs.dwBinaryPartitionNr, bInsert, passwd.c_str());

    wcsncpy(inbuf.szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    wcsncpy(inbuf.szPartitionName, ToWString(tffs.partitionname).c_str(), PARTITIONNAMESIZE);
    memcpy(inbuf.bdksign, tffs.bdksign.c_str(), 4);
    inbuf.dwBinaryPartitionNr= tffs.dwBinaryPartitionNr;
    inbuf.hDisk= tffs.dwHandle;

    inbuf.bInsert= bInsert;
    memset(inbuf.password, 0, 8);
    for (size_t i=0 ; i<8 && i<passwd.size() ; i++)
        inbuf.password[i]= passwd[i];

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITDiskProtect",
            sizeof(DiskProtectParams), (BYTE*)&inbuf,
            &outsize, (BYTE**)&outbuf);

//  if (outbuf==NULL) {
//      if (!g_ignoreerror)
//          error(res, "ITDiskProtect: outbuf==NULL\n");
//      SetLastError(res ? res : ERROR_INTERNAL_ERROR);
//      return false;
//  }
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITDiskProtect");
        return false;
    }
    RapiFree(outbuf);

    return true;
}


//....................................
bool ITGetStoreMgrList(std::vector<DevStoreInfo>& list)
{
    DWORD outsize=0;
    GetStoreMgrListResult *outbuf=NULL;

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITGetStoreMgrList",
            0, NULL,
            &outsize, (BYTE**)&outbuf);
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITGetStoreMgrList");
        return false;
    }
    if (outbuf==NULL) {
        debug("ERROR: ITGetStoreMgrList: outbuf==NULL\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        return false;
    }

    list.clear();
    for (int i=0 ; i<outbuf->nStores ; i++)
        list.push_back(outbuf->storeInfo[i]);

    RapiFree(outbuf);

    return true;
}

bool ITGetPartitionList(const TFFSDeviceSpec& tffs, std::vector<DevPartitionInfo>& list)
{
    GetPartitionListParams inbuf;
    DWORD outsize=0;
    GetPartitionListResult *outbuf=NULL;

    wcsncpy(inbuf.szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    inbuf.hStore= tffs.dwHandle;

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITGetPartitionList",
            sizeof(GetPartitionListParams), (BYTE*)&inbuf,
            &outsize, (BYTE**)&outbuf);
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITGetPartitionList");
        return false;
    }
    if (outbuf==NULL) {
        debug("ERROR: ITGetPartitionList: outbuf==NULL\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        return false;
    }

    list.clear();
    for (int i=0 ; i<outbuf->nPartitions ; i++)
        list.push_back(outbuf->partInfo[i]);

    RapiFree(outbuf);

    return true;
}

bool ITGetSTRGHandleList(std::vector<DWORD> &list)
{
    DWORD outsize=0;
    GetSTRGHandleListResult *outbuf=NULL;

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITGetSTRGHandleList",
            0, NULL,
            &outsize, (BYTE**)&outbuf);
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITGetSTRGHandleList");
        return false;
    }
    if (outbuf==NULL) {
        debug("ERROR: ITGetSTRGHandleList: outbuf==NULL\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        return false;
    }

    list.clear();
    for (int i=0 ; i<outbuf->nHandles ; i++)
        list.push_back(outbuf->handle[i]);

    RapiFree(outbuf);

    return true;
}

bool ITTFFSGetInfo(const TFFSDeviceSpec& tffs, DWORD &dwSectorSize, uint64_t &llDiskSize)
{
    TFFSGetInfoParams inbuf;
    DWORD outsize=0;
    TFFSGetInfoResult *outbuf=NULL;

    wcsncpy(inbuf.szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    wcsncpy(inbuf.szPartitionName, ToWString(tffs.partitionname).c_str(), PARTITIONNAMESIZE);
    memcpy(inbuf.bdksign, tffs.bdksign.c_str(), 4);
    inbuf.dwBinaryPartitionNr= tffs.dwBinaryPartitionNr;
    inbuf.hDisk= tffs.dwHandle;

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITTFFSGetInfo",
            sizeof(TFFSGetInfoParams), (BYTE*)&inbuf,
            &outsize, (BYTE**)&outbuf);
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITTFFSGetInfo");
        return false;
    }
    if (outbuf==NULL) {
        debug("ERROR: ITTFFSGetInfo: outbuf==NULL\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        return false;
    }

    dwSectorSize= outbuf->dwSectorSize;
    llDiskSize= outbuf->llDiskSize;

    RapiFree(outbuf);

    return true;
}
bool ITTFFSGetUniqueid(const TFFSDeviceSpec& tffs)
{
    TFFSGetInfoParams inbuf;
    DWORD outsize=0;
    TFFSGetInfoResult *outbuf=NULL;

    wcsncpy(inbuf.szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    wcsncpy(inbuf.szPartitionName, ToWString(tffs.partitionname).c_str(), PARTITIONNAMESIZE);
    memcpy(inbuf.bdksign, tffs.bdksign.c_str(), 4);
    inbuf.dwBinaryPartitionNr= tffs.dwBinaryPartitionNr;
    inbuf.hDisk= tffs.dwHandle;

    outbuf= NULL; outsize= 0;
    HRESULT res= ItsutilsInvoke("ITTFFSGetInfo",
            sizeof(TFFSGetInfoParams), (BYTE*)&inbuf,
            &outsize, (BYTE**)&outbuf);
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITTFFSGetInfo");
        return false;
    }
    if (outbuf==NULL) {
        debug("ERROR: ITTFFSGetInfo: outbuf==NULL\n");
        SetLastError(ERROR_INTERNAL_ERROR);
        return false;
    }

    debug("\n%d partitions, %d binary partitions\ncustomerid=%08lx uniqueid=%s\n",
            outbuf->dwNrPartitions,
            outbuf->dwNrofBinPartitions,
            outbuf->customerid,
            hexdump((BYTE*)&outbuf->uniqueid, 16).c_str());

    RapiFree(outbuf);

    return true;
}

bool ITLogDiskInfo(const TFFSDeviceSpec& tffs)
{
    LogDiskInfoParams inbuf;
    DWORD outsize=0;

    wcsncpy(inbuf.szDeviceName, ToWString(tffs.devicename).c_str(), DEVICENAMESIZE);
    wcsncpy(inbuf.szPartitionName, ToWString(tffs.partitionname).c_str(), PARTITIONNAMESIZE);
    memcpy(inbuf.bdksign, tffs.bdksign.c_str(), 4);
    inbuf.dwBinaryPartitionNr= tffs.dwBinaryPartitionNr;
    inbuf.hDisk= tffs.dwHandle;

    HRESULT res= ItsutilsInvoke("ITLogDiskInfo",
            sizeof(TFFSGetInfoParams), (BYTE*)&inbuf,
            &outsize, NULL);
    if (res)
    {
        if (!g_ignoreerror)
            error(res, "ITLogDiskInfo");
        return false;
    }

    return true;
}
