// chewfat - a FAT12 filesystem reader
//
// This program reads an .ISO file containing a FAT12 filesystem dumped from
// the Dakota Digital Camera's flash memory, and outputs all available picture
// files. Raw memory dumps are in an interleaved/scrambled format due to the
// flash memory wear-leveling algorithm in use - use John Maushammer's flashdump2iso
// to unscramble to .ISO.
//
// If the filesystem does not begin at the beginning of the .ISO, chewfat will scan for it.
//
// Usage: chewfat <isoname.iso>
//
// Output: Picture files (DSC_????.JPG) to the default directory. On most systems
// this should be the directory you are in when the program is run.
//
// WARNING: Output will overwrite any existing picture file with the same name!
//          Move them out of the default directory if you want to keep them!
//
// Additional warnings: This program has not been idiot-proofed against bad filenames, full disks,
// corrupted/short input files, and the like; results in these cases will be unpredictable
// and probably not good. This program makes assumptions about the directory structure
// and cluster lengths that could be subject to change in future camera revisions.
// This is a crudely hacked-together program that has not been extensively tested
// and is not guaranteed to work properly, if at all. No lifeguard on duty.
//
// ------------
// Copyright 2003 T. R. Gipson
// sysadmin at http://cexx.org/
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//


#pragma hdrstop
#include <condefs.h>
//#include <string>
#include <iostream.h>
#include <stdio.h>
#include <conio.h>


//---------------------------------------------------------------------------
#pragma argsused

FILE *in;
FILE *out;
int clustlen;
int FATIsAt;
int RootIsAt;
int DataIsAt;
unsigned char FATBuf[1536];

int readword(char* buf, long int pos)
{
        unsigned short word=*((WORD*) &buf[pos]);
        return word;
}

int nextcluster (int StartingCluster)
{
        unsigned int nextcluster;
        unsigned int index=3*StartingCluster/2;
        unsigned short shorty=*((WORD*) &FATBuf[index]);

        if (StartingCluster%2 == 0) /* Even */
        {
                nextcluster=shorty & 0x0fff;
        }
        else /* Odd */
        {
                nextcluster=(shorty >> 4);
        }

        return nextcluster;
}

void getfile(long int CurrDirPos)
{
        string temp="";
        fseek(in, CurrDirPos, SEEK_SET);
        char buf[32];
        fread(buf, 1, 32, in);
        /* Get filename */
        for(int k=0; k<11; k++)
        {
                if (buf[k] != 0x20) /* Stop at spaces */
                temp+=buf[k];
                if (k==7)
                temp+=".";
        }

        /* Get file length */
        int filesize= *(unsigned int*)(&buf[28]);

        cout << "Grabbing file " << temp << "\n";
        cout << "File size: " << filesize << "\n";

        out=fopen(temp.c_str(),"wb");

        int StartingCluster=readword(buf,26);
        //cout << "Starting cluster: " << StartingCluster << "\n";

        int k=0;
        int next_cluster=0;
        char filebuf[16384];    /* clustlen=16384; damn constant expression thingy */
        while (StartingCluster<0xff0) /* Break it off if bad/reserved cluster or end of chain */
        {
                next_cluster=nextcluster(StartingCluster);
                int readfrom=DataIsAt+((StartingCluster-2)*clustlen);
                //cout << "Reading cluster starting at " << readfrom << "\n";
                fseek(in, readfrom, SEEK_SET);
                fread(filebuf, 1, clustlen, in);
                if (next_cluster<0xff0)
                {
                        fwrite(filebuf, 1, clustlen, out);
                }
                else
                {
                        fwrite(filebuf, 1, filesize%clustlen, out);
                }

                StartingCluster=next_cluster;
                k++;
        }

        fclose(out);
}

string GetCWD(long int CurrDirPos)
{
        string temp="";
        fseek(in, CurrDirPos, SEEK_SET);
        char buf[32];
        fread(buf, 1, 32, in);
        for(int k=0; k<11; k++)
        {
                if (buf[k] != 0x20) /* Stop at spaces */
                temp+=buf[k];
        }
        return temp;
}


long int FollowDir(long int CurrDirPos)
{
        fseek(in, CurrDirPos, SEEK_SET);
        char buf[32];
        fread(buf, 1, 32, in);
        int StartingCluster=readword(buf,26);
        //cout << "Starting cluster: " << StartingCluster << "\n";

        /* Laziness: Assuming directories don't exceed 1 cluster */
        CurrDirPos=DataIsAt+((StartingCluster-2)*clustlen);


        return CurrDirPos;
}

int main(int argc, char* argv[])
{

        //string isoname="c:\\MY2.ISO";
        const int sectsize=512; /* Pick a small safe value, 2^something */

        char sectbuf[sectsize];

        int fsfound=0;

        cout << "chewfat (c) 2003 T. R. Gipson. http://cexx.org/dakota\n\n";

        if ((in=fopen(argv[1], "rb")) == NULL)            //isoname.c_str()
        {
                cout << "Could not open input file." << "\n\n";

                cout << "Usage: chewfat <image.iso>\nExample: chewfat C:\\camera\\flash-image.iso\n";
                exit(1);
        }

        rewind(in);

        /* Find start of FAT12 filesystem */

        cout << "Searching for start of FAT12 filesystem...";

        while(!(fsfound))
        {
                fread(sectbuf, 1, sectsize, in);
                string temp="";

                for (int k=54; k<59;k++)
                {
                        temp+=sectbuf[k]; /* Crude kludge #973192 */
                }

                if (temp == "FAT12")
                {
                        fsfound=TRUE;
                        cout << "found it.\n\n";
                }
        }
        cout << "Found what appears to be a valid FAT12 FS at file position " << (ftell(in)-sectsize) << "\n";

        /* FS found, now read BPB info */

        /* Is it legal to define vars any old place? Can't remember... maybe only in CPP? */
        int BytesPerSector=readword(sectbuf,11);
        int SectorsPerCluster=sectbuf[13];
        int ReservedSectors=readword(sectbuf,14);
        int TotalFATs=sectbuf[16];
        int MaxRootEntries=readword(sectbuf,17);
        int SectorsPerFAT=readword(sectbuf,22);


        cout << "Bytes per sector: " << BytesPerSector << "\nSectors per cluster: " << SectorsPerCluster  << "\nReserved Sectors: " << ReservedSectors << "\nTotal FATs: " << TotalFATs << "\nMax Root Entries: " << MaxRootEntries << "\nSectors Per FAT: " << SectorsPerFAT << "\n\n";

        clustlen=BytesPerSector*SectorsPerCluster;
        FATIsAt=(ftell(in)-sectsize) + (ReservedSectors*BytesPerSector);
        RootIsAt=FATIsAt+(TotalFATs*SectorsPerFAT*BytesPerSector);
        DataIsAt=RootIsAt+(MaxRootEntries*32);

        cout << "FAT is at " << FATIsAt << "\nRoot is at " << RootIsAt << "\nData is at " << DataIsAt << "\n\n";

        /* Got BPB info, now read FAT to a buffer */


        fseek(in, FATIsAt+1536, SEEK_SET);
        fread(FATBuf, 1, BytesPerSector*SectorsPerFAT, in);

        /* We know where the image files are in the directory structure, so just drill down to them now. */
        /* A proper app would let you browse thru the directories yourself, but nobody said this was a proper app :) */

        long int CurrDirPos=RootIsAt;

        /* Drill until we get to a dir. with picture files */

        string temp="";
        while (1)
        {
                temp=GetCWD(CurrDirPos);

                if (temp=="DCIM" || temp=="100MEDIA")
                {
                        CurrDirPos=FollowDir(CurrDirPos);
                }
                else if (temp.substr(0,3)=="DSC")
                {
                        break;
                }

                //cout << "Directory: " << temp << "\n";
                CurrDirPos+=32;
        }

        while (GetCWD(CurrDirPos).substr(8,3) == "JPG")
        {
                if (GetCWD(CurrDirPos).substr(0,1) != "å") /* Skip deleted files */
                {
                        //cout << GetCWD(CurrDirPos) << "\n";
                        getfile(CurrDirPos);
                }
                CurrDirPos+=32;
        }

        //getch();
        fclose(in);
        return 0;
}





