#define NO_INCLUDE_UNION

#include "shellprv.h"
#include "ids.h"
#include "ole2dup.h"
#include <regstr.h>
#include "shlobjp.h"
#include "idlcomm.h"
#include "fstreex.h"
#include "views.h"
#include <mmsystem.h>
#include <shguidp.h>
#include "ids.h"

// I'm disabling this warning (truncation of constant value) because I don't
// want to touch this code.

#pragma warning(disable: 4310)

//----------------------------------------------------------------------------

static DWORD gdwRandSeed = 5610996;

__inline void
Seed_dwRand(DWORD dwSeed)
{
   gdwRandSeed = dwSeed;
}

__inline DWORD idwRand()
{
   // who cares if another thread clobbers us
   gdwRandSeed *= 69069;
   return ++gdwRandSeed;
}

DWORD dwRand()
{
   return idwRand();
}

DWORD dwRandom(DWORD dwRange)
{
   return idwRand() % dwRange;
}

///////////////////////////////////////////////////////////////////////////////
// Helpers and constants
///////////////////////////////////////////////////////////////////////////////

#define CLVM_ANIMATE        WM_APP

#define CLOUD_TITLE_FONT    (0)
#define CLOUD_NORMAL_FONT   (1)
#define CLOUD_GOODIE_FONT   (2)

#define NUM_CLOUD_FONTS     (3)

#define CLOUD_MIN_FONTSIZE  (8)
#define CLOUD_MAX_FONTSIZE  (72)

#define CLOUD_MIN_ELEMENTS  (1)
#define CLOUD_MAX_ELEMENTS  (10)

#define CLOUD_FONT_WNDSIZE_DIVISOR (10)

#define ACTIVE_COLORS       (240)
#define ROW_SIZE            (16)

#define NUM_ROWS            (ACTIVE_COLORS / ROW_SIZE)
#define TOP_ROW_START       (ACTIVE_COLORS - ROW_SIZE)

#define MAX_OPACITY         (999)

#define OFFSCREEN_STRIPS    (4)

inline BYTE
CalcRowData(int row, BYTE master)
{
    return (BYTE)(((NUM_ROWS - row) * master) / (2 * NUM_ROWS)) + (master / 2);
}

inline BYTE
OpacityRow(int opacity)
{
    return (BYTE)(((UINT)opacity * NUM_ROWS) / (1 + MAX_OPACITY));
}

inline BYTE
InterRowEffect(BYTE dstrow, BYTE srcrow)
{
    return (dstrow ^ srcrow) << 4;
}

inline BYTE
StdOpacityEffect(int opacity)
{
    return InterRowEffect(OpacityRow(opacity), OpacityRow(0));
}

HPALETTE PaletteFromDS(HDC hdc)
{
    DWORD adw[257];
    int i,n;

    n = GetDIBColorTable(hdc, 0, 256, (LPRGBQUAD)&adw[1]);

    for (i=1; i<=n; i++)
        adw[i] = RGB(GetBValue(adw[i]),GetGValue(adw[i]),GetRValue(adw[i]));

    adw[0] = MAKELONG(0x300, n);

    return CreatePalette((LPLOGPALETTE)&adw[0]);
}

typedef DWORD (WINAPI *DWVOIDFN)();
typedef MCIERROR (WINAPI *MCISSFN)(LPCTSTR, LPTSTR, UINT, HWND);

extern TCHAR const c_szWinMMDll[];

CHAR const c_sztimeGetTime[] = "timeGetTime";       // No TEXT()
CHAR const c_szmciSendString[] = "mciSendStringA";  // No TEXT()

static DWORD g_dwCloudGroup = 0xFFFFFFFF;

///////////////////////////////////////////////////////////////////////////////
// syntactic crutches
///////////////////////////////////////////////////////////////////////////////

template<WORD BPP, DWORD CLRUSED>
class BitmapInfo
{
public:
    BITMAPINFOHEADER header;
    RGBQUAD          colors[CLRUSED];

    BitmapInfo();
    BitmapInfo(LONG cx, LONG cy, DWORD important = 0);

    operator BITMAPINFO *() { return (BITMAPINFO *)&header; }
};

template<WORD BPP, DWORD CLRUSED>
BitmapInfo<BPP, CLRUSED>::BitmapInfo()
{
    ZeroMemory(&header, SIZEOF(BITMAPINFOHEADER));
    header.biSize     = SIZEOF(BITMAPINFOHEADER);
    header.biPlanes   = 1;
    header.biBitCount = BPP;
    header.biClrUsed  = CLRUSED;
}

template< WORD BPP, DWORD CLRUSED >
BitmapInfo< BPP, CLRUSED >::BitmapInfo( LONG cx, LONG cy, DWORD important )
{
    header.biSize          = SIZEOF(BITMAPINFOHEADER);
    header.biWidth         = cx;
    header.biHeight        = cy;
    header.biPlanes        = 1;
    header.biBitCount      = BPP;
    header.biCompression   = BI_RGB;
    header.biSizeImage     = 0;
    header.biXPelsPerMeter = 0;
    header.biYPelsPerMeter = 0;
    header.biClrUsed       = CLRUSED;
    header.biClrImportant  = important;
}

class Image
{
public:
    Image(HBITMAP newbmp = NULL);
    ~Image() { *this = NULL; }

    operator HDC () const { return dc; }
    Image &operator = (HBITMAP newbmp);

    HBITMAP Bitmap() const { return bmp; }

private:
    HBITMAP bmp, defbmp;
    HDC dc;
};

Image::Image(HBITMAP newbmp) :
    bmp(NULL), defbmp(NULL), dc(NULL)
{
    *this = newbmp;
}

Image &
Image::operator = (HBITMAP newbmp)
{
    if (newbmp != bmp)
    {
        if (bmp)
        {
            SelectBitmap(dc, defbmp);
            DeleteBitmap(bmp);
        }

        if (newbmp)
        {
            if (!dc)
                dc = CreateCompatibleDC(NULL);

            if (dc)
            {
                HBITMAP old = SelectBitmap(dc, newbmp);

                if (!defbmp)
                    defbmp = old;
            }
            else
            {
                DeleteBitmap(newbmp);
                newbmp = NULL;
            }
        }
        else if (dc)
        {
            DeleteDC(dc);
            dc = NULL;
        }

        bmp = newbmp;
    }

    return *this;
}

///////////////////////////////////////////////////////////////////////////////
// CloudView
///////////////////////////////////////////////////////////////////////////////

class CloudView :
    public IShellView
{
    struct Element
    {
        Image image;
        COLORREF coloreffect;
        SIZE size;
        POINT position;
        int velocity;
        int opacity;
        int vopacity;
        DWORD fadebegin;
        DWORD mark;     // last time animation state was evaluated
    };

public:
    struct Gap
    {
        int top, size;
        Gap *next;

        class Manager
        {
        public:
            Manager(int size = 0);
            ~Manager();

            BOOL Split(Gap *gap, int top, int size);
            void Join(int top, int size);

            Gap *FindLargestThatFits(int size);
            void Reset(int size);

        private:
            Gap *head;
            Gap *free;
        };
    };

    CloudView(HWND _parent);
    ~CloudView();

    STDMETHODIMP QueryInterface(REFIID, LPVOID *);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    STDMETHODIMP GetWindow(HWND *);
    STDMETHODIMP ContextSensitiveHelp(BOOL);
    STDMETHODIMP TranslateAccelerator(LPMSG);
    STDMETHODIMP EnableModeless(BOOL);
    STDMETHODIMP UIActivate(UINT);
    STDMETHODIMP Refresh();
    STDMETHODIMP CreateViewWindow(IShellView *, LPCFOLDERSETTINGS,
        IShellBrowser *, RECT *, HWND *);
    STDMETHODIMP DestroyViewWindow();
    STDMETHODIMP GetCurrentInfo(LPFOLDERSETTINGS);
    STDMETHODIMP AddPropertySheetPages(DWORD, LPFNADDPROPSHEETPAGE, LPARAM);
    STDMETHODIMP SaveViewState();
    STDMETHODIMP SelectItem(LPCITEMIDLIST, UINT);
    STDMETHODIMP GetItemObject(UINT, REFIID, LPVOID *);

private:
    HWND window, parent;
    ULONG refs;

    HINSTANCE mmlib;
    DWVOIDFN pfntimeGetTime;
    MCISSFN pfnmciSendString;

    void Init(LPCREATESTRUCT);
    void Die();
    BOOL Erase(HDC);
    void Paint();
    void Realize(HDC = NULL);
    void RestartAnimation();
    void KillAnimation(BOOL repaint_on_kill = FALSE);

    BOOL InitElementForDisplay(Element *element, Gap *gap, const char *text);
    void DrawElement(HDC, Element *, int delta = 0);
    void AnimationLoop();
    static DWORD ThreadEntryPoint(CloudView *);

    void StartMusic();
    BOOL NextMusicLoop();
    void StopMusic();

    LRESULT MsgHandler(UINT msg, WPARAM wp, LPARAM lp);
    static LRESULT _export CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp);

    //
    // stuff for the animation
    //
    CRITICAL_SECTION lock;
    Image image;
    SIZE tile, imgsize, wndsize;
    HBRUSH eraser, oldbsh;
    HPALETTE palette;
    HANDLE animation;
    Element *elements;
    UINT count;
    HFONT fonts[NUM_CLOUD_FONTS];
    int maxheight;
    Gap::Manager gaps;
    DWORD state;

    int music;              // this view playing music? (0:no/1:yes/-1:was)
    static LONG anymusic;   // is any view playing music right now?
};

static const TCHAR c_szCloudViewClassname[] = TEXT("CloudedShellView");

///////////////////////////////////////////////////////////////////////////////
// CloudView::Gap::Manager
///////////////////////////////////////////////////////////////////////////////

CloudView::Gap::Manager::Manager(int size) :
    head(new Gap), free(NULL)
{
    if (head)
    {
        head->top = 0;
        head->size = size;
        head->next = NULL;
    }
}

CloudView::Gap::Manager::~Manager()
{
    while (head)
    {
        Gap *t = head->next;
        delete head;
        head = t;
    }
    while (free)
    {
        Gap *t = free->next;
        delete free;
        free = t;
    }
}

BOOL
CloudView::Gap::Manager::Split(Gap *gap, int pos, int size)
{
    Gap *t;

    if (free)
    {
        t = free;
        free = t->next;
    }
    else
        t = new Gap;

    if (t)
    {
        t->top = pos + size;
        t->size = gap->top + gap->size - t->top;
        t->next = gap->next;
        gap->size = pos - gap->top;
        gap->next = t;
        return TRUE;
    }

    return FALSE;
}

void
CloudView::Gap::Manager::Join(int pos, int size)
{
    //BUGBUG: this routine _really_ trusts its caller not to fuck up
    Gap *above = head;

    while (above && ((above->top + above->size) < pos))
        above = above->next;

    Gap *next = above->next;
    above->size += size + next->size;

    above->next = next->next;
    next->next = free;
    free = next;
}

CloudView::Gap *
CloudView::Gap::Manager::FindLargestThatFits(int size)
{
    Gap *gap = head;
    Gap *best = NULL;

    size++; // must be larger by 2 pixels

    while (gap)
    {
        if (gap->size > size)
        {
            best = gap;
            size = best->size;
        }

        gap = gap->next;
    }

    return best;
}

void
CloudView::Gap::Manager::Reset(int size)
{
    if (head)
    {
        while (head->next)
        {
            Gap *gap = head->next;
            head->next = gap->next;
            gap->next = free;
            free = gap;
        }

        head->top = 0;
        head->size = size;
    }
}

///////////////////////////////////////////////////////////////////////////////
// CloudView
///////////////////////////////////////////////////////////////////////////////
LONG CloudView::anymusic = 0;

CloudView::CloudView(HWND _p) :
    window(NULL), parent(_p), state(0), refs(1), pfntimeGetTime(NULL),
    pfnmciSendString(NULL), elements(NULL), count(0), palette(NULL),
    animation(NULL), music(0), eraser(NULL), oldbsh(NULL)
{
    DebugMsg(DM_TRACE, TEXT("CloudView::CloudView()"));

    UINT left = NUM_CLOUD_FONTS;
    while (left--)
        fonts[left] = NULL;

    mmlib = LoadLibrary(c_szWinMMDll);
    if (mmlib)
    {
        pfntimeGetTime = (DWVOIDFN)GetProcAddress(mmlib, c_sztimeGetTime);
        pfnmciSendString = (MCISSFN)GetProcAddress(mmlib, c_szmciSendString);
    }

    if (!pfntimeGetTime)
        pfntimeGetTime = (DWVOIDFN)GetTickCount;

    Seed_dwRand(pfntimeGetTime());
    InitializeCriticalSection(&lock);
}

CloudView::~CloudView()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::~CloudView()"));

    DestroyViewWindow();
    DeleteCriticalSection(&lock);

    if (mmlib)
        FreeLibrary(mmlib);
}

void
CloudView::Init(LPCREATESTRUCT cs)
{
    wndsize.cx = cs->cx;
    wndsize.cy = cs->cy;

    Image source((HBITMAP)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDB_CLOUDS),
        IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION));

    if (source)
    {
        BITMAP bm;

        if (GetObject(source.Bitmap(), SIZEOF(BITMAP), &bm))
        {
            HDC screen = GetDC(NULL);

            tile.cx = bm.bmWidth;
            tile.cy = bm.bmHeight;

            imgsize.cx = GetSystemMetrics(SM_CXSCREEN) + tile.cx;
            imgsize.cy = OFFSCREEN_STRIPS * tile.cy;

            BitmapInfo<8, ACTIVE_COLORS> info(imgsize.cx, imgsize.cy);
            // set the lower 16 up with the source colors
            GetDIBColorTable(source, 0, ROW_SIZE, info.colors);

            // fill in the gradient
            RGBQUAD *pdst = info.colors + ROW_SIZE;
            for (int row = 1; row < NUM_ROWS; row++)
            {
                RGBQUAD *psrc = info.colors;

                for (int col = 0; col < ROW_SIZE; col++, psrc++, pdst++)
                {
                    pdst->rgbBlue  = CalcRowData(row, psrc->rgbBlue );
                    pdst->rgbGreen = CalcRowData(row, psrc->rgbGreen);
                    pdst->rgbRed   = CalcRowData(row, psrc->rgbRed  );
                }
            }
            *(DWORD *)info.colors = 0;

            eraser = CreateSolidBrush(DIBINDEX(0x0F));
            if (eraser)
            {
                // create the working background image
                image = CreateDIBSection(screen, info, DIB_RGB_COLORS, NULL,
                    NULL, 0);

                if (image)
                {
                    // tile the source bitmap onto the working image
                    for (int y = 0; y <= imgsize.cy; y += tile.cy)
                    {
                        for (int x = 0; x <= info.header.biWidth; x += tile.cx)
                        {
                            BitBlt(image, x, y, tile.cx, tile.cy, source, 0, 0,
                                SRCCOPY);
                        }
                    }

                    oldbsh = SelectBrush(image, eraser);

                    // create a palette for the image if required
                    if (GetDeviceCaps(screen, RASTERCAPS) & RC_PALETTE)
                        palette = PaletteFromDS(image);

                    // do this now for 'transparency' during mono->color XORs
                    SetBkColor(image, DIBINDEX(0));
                }

                ReleaseDC(NULL, screen);
            }
        }
    }
}

void
CloudView::Die()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::Die()"));

    if (oldbsh)
    {
        if (image)
            SelectObject(image, oldbsh);
        oldbsh = NULL;
    }

    if (eraser)
    {
        DeleteObject(eraser);
        eraser = NULL;
    }

    if (palette)
    {
        DeleteObject(palette);
        palette = NULL;
    }

    KillAnimation();
    StopMusic();

    window = NULL;
    image = NULL;
}

BOOL
CloudView::Erase(HDC dc)
{
    if (!image)
        return FALSE;

    DebugMsg(DM_TRACE, TEXT("CloudView::Erase(%08X)"), dc);

    EnterCriticalSection(&lock);

    RECT update;
    if(!GetUpdateRect(window, &update, FALSE))
        GetClientRect(window, &update);

    Realize(dc);

    int y = update.top / tile.cy * tile.cy;
    do
    {
        BitBlt(dc, 0, y, imgsize.cx, imgsize.cy, image, 0, 0, SRCCOPY);
        y += imgsize.cy;

    } while (y <= update.bottom);

    LeaveCriticalSection(&lock);

    return TRUE;
}

void
CloudView::Paint()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::Paint()"));

    EnterCriticalSection(&lock);

    PAINTSTRUCT ps;
    BeginPaint(window, &ps);

    if (elements)
    {
        Realize(ps.hdc);

        UINT left = count;
        Element *element = elements;

        while (left--)
        {
            if (element->image)
                DrawElement(ps.hdc, element);

            element++;
        }
    }

    EndPaint(window, &ps);

    LeaveCriticalSection(&lock);
}

void
CloudView::Realize(HDC theirdc)
{
    if (palette)
    {
        EnterCriticalSection(&lock);

        HDC dc = theirdc? theirdc : GetDC(window);
        if (dc)
        {
            SelectPalette(dc, palette, FALSE);
            BOOL repaint = (RealizePalette(dc) > 0);

            if (!theirdc)
                ReleaseDC(window, dc);

            if (repaint)
            {
                DebugMsg(DM_TRACE, TEXT("CloudView::Realize() changed entries, invalidating"));
                InvalidateRect(window, NULL, TRUE);
            }
        }

        LeaveCriticalSection(&lock);
    }
}

static const TCHAR c_szArial[] = TEXT("Arial");
static const TCHAR c_szTimesNewRoman[] = TEXT("Times New Roman");
static const TCHAR c_szWingdings[] = TEXT("Wingdings");

void
CloudView::RestartAnimation()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::RestartAnimation()"));

    //
    // put ourselves in a known state before dorking with stuff
    //
    KillAnimation(TRUE);
    gaps.Reset(wndsize.cy);

    //
    // compute the 'ideal' font height and weight for the window size
    //
    int cxadjusted = (4 * wndsize.cx) / 5; //text is generally wide and short
    LONG tmp = min(cxadjusted, wndsize.cy) / CLOUD_FONT_WNDSIZE_DIVISOR;
    maxheight = min(max(tmp, CLOUD_MIN_FONTSIZE), CLOUD_MAX_FONTSIZE);
    LONG weight = ((FW_BLACK - FW_THIN) * (maxheight - CLOUD_MIN_FONTSIZE) /
        (CLOUD_MAX_FONTSIZE - CLOUD_MIN_FONTSIZE)) + FW_THIN;

    //
    // create the display fonts for animation
    //
    LOGFONT lf = { maxheight, 0, 0, 0, weight, FALSE, FALSE, FALSE,
        ANSI_CHARSET, OUT_STROKE_PRECIS, CLIP_DEFAULT_PRECIS,
        PROOF_QUALITY, VARIABLE_PITCH | FF_DONTCARE, 0 };
    lstrcpy(lf.lfFaceName, c_szTimesNewRoman);

    // we MUST have at least the name font
    fonts[CLOUD_NORMAL_FONT] = CreateFontIndirect(&lf);
    if (fonts[CLOUD_NORMAL_FONT])
    {
        // use different font for group titles
        lf.lfHeight = maxheight = (maxheight * 4) / 3;
        lf.lfItalic = TRUE;
        lstrcpy(lf.lfFaceName, c_szArial);
        // we can live without the title font if it fails
        fonts[CLOUD_TITLE_FONT] = CreateFontIndirect(&lf);

        // create a font for displaying the 'windows logo' glyph
        lf.lfHeight = wndsize.cy - tile.cy;
        tmp = max(maxheight, (tile.cy / 2));
        if (lf.lfHeight < tmp)
            lf.lfHeight = tmp;
        tmp = (tile.cy * (OFFSCREEN_STRIPS-1)) - 2;
        if (lf.lfHeight > tmp)
            lf.lfHeight = tmp;
        lf.lfEscapement = -200;
        lf.lfWeight = FW_NORMAL;
        lf.lfItalic = FALSE;
        lf.lfCharSet = SYMBOL_CHARSET;
        lstrcpy(lf.lfFaceName, c_szWingdings);
        // we can also live without the goodie font if it fails
        fonts[CLOUD_GOODIE_FONT] = CreateFontIndirect(&lf);

        //
        // compute max elements for simultaneous display
        //
        maxheight *= 5;
        maxheight /= 4;
        tmp = wndsize.cy / maxheight;
        count = (UINT)min(max(tmp, CLOUD_MIN_ELEMENTS), CLOUD_MAX_ELEMENTS);

        //
        // allocate display elements
        //
        elements = new Element[count];
        if (elements)
        {
            //
            // kick off the animation thread
            //
            DWORD dummy;
            animation = CreateThread( NULL, 0,
                (LPTHREAD_START_ROUTINE)ThreadEntryPoint, (LPVOID)this, 0,
                &dummy );
        }
    }

    //
    // did we succeed?
    //
    if (animation)
    {
        // go ahead an kick off the music, too
        StartMusic();
    }
    else
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::RestartAnimation() FAILED"));

        // clean up after ourselves
        KillAnimation();
    }
}

void
CloudView::KillAnimation(BOOL repaint_on_kill)
{
    DebugMsg(DM_TRACE, TEXT("CloudView::KillAnimation(%d)"), repaint_on_kill);

    if (!elements)
        return;

    //
    // grab the critical section and free the animation elements
    //
    EnterCriticalSection(&lock);
    delete[] elements;
    elements = NULL;
    count = 0;

    UINT left = NUM_CLOUD_FONTS;
    while (left--)
    {
        if (fonts[left])
        {
            DeleteObject(fonts[left]);
            fonts[left] = NULL;
        }
    }

    //
    // release the critical section and wait for the animation thread to die
    //
    LeaveCriticalSection(&lock);
    if (animation)
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::KillAnimation() waiting on thread"));
        WaitForSingleObject(animation, INFINITE);
        CloseHandle(animation);
        animation = NULL;

        if (repaint_on_kill)
        {
            DebugMsg(DM_TRACE, TEXT("CloudView::KillAnimation() repainting after killing an animation"));
            InvalidateRect(window, NULL, TRUE);
        }
    }
}

BOOL
CloudView::InitElementForDisplay(Element *element, Gap *gap, const char *text)
{
    char temp[2];
    //
    // choose a font
    //
    HFONT font = fonts[state % 2];
    if (!font)
        font = fonts[CLOUD_NORMAL_FONT];

    // if this is the *very* first item use the windows logo instead
    if (!state && fonts[CLOUD_GOODIE_FONT])
    {
        font = fonts[CLOUD_GOODIE_FONT];
        temp[0] = (char)(BYTE)0xFF;
        temp[1] = 0;
        text = temp;
    }

    //
    // compute the extents
    //
    POINT origin;
    int len = lstrlenA(text);

    HFONT oldfont = SelectFont(image, font);
    UINT align;

    if (font == fonts[CLOUD_GOODIE_FONT])
    {
        // special windows flag title
        MAT2 mat = { 0 }; mat.eM11.value = mat.eM22.value = 1;
        GLYPHMETRICS gm;
        if (GetGlyphOutlineA(image, (UINT)(BYTE)0xFF, GGO_METRICS, &gm, 0,
            NULL, &mat) != GDI_ERROR)
        {
            element->size.cx = gm.gmBlackBoxX;
            element->size.cy = gm.gmBlackBoxY;
            origin.x = -gm.gmptGlyphOrigin.x;
            origin.y = gm.gmptGlyphOrigin.y;
            align = TA_LEFT | TA_BASELINE;
        }
        else
            element->size.cx = element->size.cy = 0;
    }
    else
    {
        // normal horizontal text
        GetTextExtentPointA(image, text, len, &element->size);
        ABC abc;
        abc.abcA = 0;
        GetCharABCWidthsA(image, (BYTE)text[0], (BYTE)text[0], &abc);
        int firstA = -min(abc.abcA, 0);
        abc.abcC = 0;
        GetCharABCWidthsA(image, (BYTE)text[len-1], (BYTE)text[len-1], &abc);
        element->size.cx += firstA - min(abc.abcC, 0);
        origin.x = firstA;
        origin.y = 0;
        align = TA_LEFT | TA_TOP;
    }

    SelectFont(image, oldfont);

    //
    // create the bitmap
    //
    if (!element->size.cx || !element->size.cy)
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::InitElementForDisplay() could not compute bitmap dimensions"));
        return FALSE;
    }

    element->image = CreateBitmap(element->size.cx, element->size.cy,
        1, 1, NULL);

    if (!element->image)
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::InitElementForDisplay() could not create bitmap"));
        return FALSE;
    }

    //
    // render the text
    //
    RECT rect = { 0, 0, element->size.cx + 1, element->size.cy + 1 };
    SetTextColor(element->image, RGB(0, 0, 0));
    SetBkColor(element->image, RGB(255, 255, 255));
    oldfont = SelectFont(element->image, font);
    UINT oldalign = SetTextAlign(element->image, align);
    ExtTextOutA(element->image, origin.x, origin.y, ETO_OPAQUE, &rect, text,
        (UINT)len, NULL);
    SetTextAlign(element->image, oldalign);
    SelectFont(element->image, oldfont);

    //
    // position the element and initialize its motion state
    //
    int mag = (1000 * element->size.cx) / wndsize.cx;
    DWORD fadewait = 2000 + mag;

    if (font == fonts[CLOUD_GOODIE_FONT])
    {
        // special windows flag title
        element->position.x = (wndsize.cx - element->size.cx) / 2;
        element->position.y = (wndsize.cy - element->size.cy) / 2;
        element->velocity = 0;
        fadewait *= 2;
    }
    else
    {
        int cpos = dwRandom((wndsize.cx * 3) / 4) + wndsize.cx / 8;
        element->position.x = cpos - (element->size.cx / 2);

        if (gap->size == wndsize.cy)
        {
            element->position.y = (gap->size / 4) +
                dwRandom((gap->size - element->size.cy) / 2);
        }
        else
        {
            element->position.y = gap->top +
                dwRandom(gap->size - element->size.cy);
        }

        int magdiv = 3 + (state & 1);
        element->velocity = 50 + dwRandom(mag / magdiv);

        // scale/flip velocities as appropriate
        element->velocity *= wndsize.cx;
        element->velocity /= 1024;  // factor my (fmh) display out of the velocity
        if (cpos >= (wndsize.cx / 2))
            element->velocity *= -1;
    }

    // reserve this space on screen
    if (!gaps.Split(gap, element->position.y, element->size.cy))
        return FALSE;

    // init opacity, fade speed, Blt color and time stamps
    element->vopacity = 500 + dwRandom(mag / 2);
    element->opacity = 0;
    element->coloreffect = DIBINDEX(0);
    element->mark = pfntimeGetTime();
    element->fadebegin = element->mark + fadewait;
    return TRUE;
}

void
CloudView::DrawElement(HDC dc, Element *element, int delta)
{
    //
    // set up coordinates for drawing
    //
    POINT dst = element->position;
    SIZE size = element->size;
    int ex = 0;
    if (dst.x < 0)
    {
        size.cx += dst.x;
        ex = -dst.x;
        dst.x = 0;

        if (delta > 0)
            delta = 0;
    }
    POINT tmp = { dst.x % tile.cx, dst.y % tile.cy };
    if (delta > 0)
        tmp.x += (1 + (delta / tile.cx)) * tile.cx;

    //
    // apply translucency effect to composition area
    //
    SetTextColor(image, element->coloreffect);
    BitBlt(image, tmp.x, tmp.y, size.cx, size.cy, element->image, ex, 0,
        SRCINVERT);

    //
    // draw composed image to destination
    //
    if (delta)
    {
        // include the 'tail' when bltting to the screen
        if (delta > 0)
        {
            dst.x -= delta;
            tmp.x -= delta;
            size.cx += delta;
        }
        else
            size.cx -= delta;
    }
    BitBlt(dc, dst.x, dst.y, size.cx, size.cy, image, tmp.x, tmp.y,
        SRCCOPY);

    //
    // clean up after ourselves by ANDing erase pattern over image
    //
    PatBlt(image, tmp.x, tmp.y, size.cx, size.cy, (DWORD)0x00A000C9);
}

static const TCHAR c_szBIN[] = TEXT("BIN");

// change this to a real mask to (barely) shroud the data
#define NAME_MAGIC                  ((char)0x95)

// specail pseudo-names mark extra places where we should let the screen clear
#define NAME_WAIT_FOR_IDLE_MAGIC    ((char)('*' ^ NAME_MAGIC))

void
CloudView::AnimationLoop()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::AnimationLoop()"));

    //
    // get the list of names
    //
    HANDLE hnames = LoadResource(HINST_THISDLL, FindResource(HINST_THISDLL, MAKEINTRESOURCE(IDB_CLOUDS), c_szBIN));
    Assert(hnames != NULL);
    if (hnames == NULL)
        return;

    const char *names = (char *)LockResource(hnames);
    const char *lastgroup = names + g_dwCloudGroup;
    const char *name = lastgroup;

    //
    // some local state information
    //
    UINT index = 0;
    UINT visible = 0;
    DWORD namebegin = 0;

    //
    // don't let the gui thread mess with our data structures now
    //      (don't worry, we relese the critsec inside the loop below :)
    //
    EnterCriticalSection(&lock);
    state = g_dwCloudGroup? 2 : 0;

    //
    // are we strarting at the *very* start?
    //
    if (!state)
        namebegin = 2500 + pfntimeGetTime();     // initial delay

    //
    // main animation loop
    //
    while (elements)
    {
        //
        // get the current element and process it
        //
        Element *element = elements + index;

        // if the element is not in use, do special stuff
        if (!element->image)
        {
            //
            // advance to next group of names if required
            //
            if ((*name == NAME_MAGIC) && !visible)
            {
                if (*++name != NAME_MAGIC)
                    state++;
                else
                {
                    DebugMsg(DM_TRACE, TEXT("CloudView::AnimationLoop() restarting sequence"));
                    name = names;
                    state = 0;

                    // pause at the end before starting over
                    namebegin = 4000 + pfntimeGetTime();
                }

                // remember the last "title" group we started to show
                if (!(state & 1))
                    lastgroup = name;
            }

            //
            // check for the next name
            //
            if (*name != NAME_MAGIC)
            {
                BOOL waitforidle = (*name == NAME_WAIT_FOR_IDLE_MAGIC);
                DWORD time;

                //
                // decide if we should process it now or later
                //
                if (((time = pfntimeGetTime()) >= namebegin) &&
                    (!waitforidle || !visible))
                {
                    //
                    // are we restarting after an idle?
                    //
                    if (waitforidle)
                    {
                        //
                        // idles are pseuso-names in the data stream
                        // we can only proceed if there's a real name here
                        //
                        if (*++name == NAME_MAGIC)
                        {
                            // nope, we can't deal with this
                            // skip the delimiter and restart the loop
                            name++;
                            continue;
                        }
                    }

                    //
                    // we have a *real* name, search for a space on the screen
                    //
                    Gap *gap = gaps.FindLargestThatFits(maxheight);
                    if (gap)
                    {
                        //
                        // found a space -- decode the name
                        //
                        CHAR decoded[128], *p = decoded - 1;
                        do { *++p = *name++ ^ NAME_MAGIC; } while (*p);

                        // prepare it for painting
                        if (!InitElementForDisplay(element, gap, decoded))
                            break;

                        visible++;

                        // set a small wait before starting another item
                        namebegin = 333 + dwRandom(417) + pfntimeGetTime();
                    }
                }
            }

            //
            // if we're not doing anything, find an element that needs cycles
            //
            if (!element->image && visible)
            {
                do
                {
                    if (++index < count)
                        element++;
                    else
                    {
                        index = 0;
                        element = elements;
                    }
                }
                while (!element->image);
            }
        }

        //
        // if there are visble elements, 'element' should point at one of them
        //
        if (visible)
        {
            //
            // animate the current element
            //
            DWORD time = pfntimeGetTime();
            int elapsed = (int)(time - element->mark);
            int delta = (element->velocity * elapsed) / (int)1000;

            //
            // has this element moved yet (or is it stationary...)
            //
            if (delta || !element->velocity)
            {
                //
                // recompute the rest of its state
                //
                element->mark = time;
                element->position.x += delta;
                int temp = element->opacity +
                    ((element->vopacity * elapsed) / (int)1000);
                element->opacity = min(max(temp,0),MAX_OPACITY);
                element->coloreffect =
                    DIBINDEX(StdOpacityEffect(element->opacity));

                //
                // draw it at its new home
                //
                HDC dc = GetDC(window);
                if (dc)
                {
                    Realize(dc);
                    DrawElement(dc, element, delta);
                    ReleaseDC(window, dc);
                }

                //
                // check to see if the fade state is changing on this dude
                //
                if (time > element->fadebegin)
                {
                    if (element->opacity)
                    {
                        if (element->vopacity > 0)
                        {
                            // begin fading down
                            element->vopacity *= -1;
                        }
                    }
                    else
                    {
                        // element is done - mark it as such
                        element->image = NULL;

                        // free up display space
                        gaps.Join(element->position.y, element->size.cy);
                        visible--;
                    }
                }
            }

            //
            // queue the next element
            //
            if (++index >= count)
                index = 0;
        }

        //
        // prepare to sleep -- minimally giving up the rest of this timeslice
        //
        DWORD sleeptime = 0;

        //
        // if we have nothing to do then sleep a little longer
        //
        if (!visible)
        {
            DWORD time = pfntimeGetTime();
            if (namebegin > time)
            {
                sleeptime = namebegin - time;

                //
                // we should still wake up periodically
                // since the gui thread may be trying to kill us
                //
                if (sleeptime > 200)
                    sleeptime = 200;
            }
        }

        //
        // drop the critical section so the gui thread can communicate with us
        //
        LeaveCriticalSection(&lock);
        Sleep(sleeptime);
        EnterCriticalSection(&lock);
    }

    //
    // check to see if we broke while there was still work to do
    //
    if (count)
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::AnimationLoop() error exit"));

        // an error must have occurred, clean up
        delete[] elements;
        elements = NULL;
        count = 0;
        InvalidateRect(window, NULL, TRUE);
    }

    //
    // we're done messing around with the common data structures
    //
    LeaveCriticalSection(&lock);

    //
    // remember the last group we showed the user so we can restart there
    //
    g_dwCloudGroup = (DWORD)lastgroup - (DWORD)names;
}

DWORD
CloudView::ThreadEntryPoint(CloudView *view)
{
    DebugMsg(DM_TRACE, TEXT("CloudView::ThreadEntryPoint() enter view %08X thread %08X"), view, GetCurrentThreadId());
    view->AnimationLoop();
    DebugMsg(DM_TRACE, TEXT("CloudView::ThreadEntryPoint() exit view %08X thread %08X"), view, GetCurrentThreadId());
    return 0;
}

static const TCHAR c_szMID[] = TEXT("BYN");
static const TCHAR c_szMidiOpen[]  = TEXT("open ");
static const TCHAR c_szMidiSequencer[] = TEXT("sequencer");
static const TCHAR c_szMidiFile[] = TEXT("Clouds.mid");
static const TCHAR c_szMidiAlias[]  = TEXT(" alias shell");
static const TCHAR c_szMidiPlay[]  = TEXT("play shell from 0 notify");
static const TCHAR c_szMidiClose[] = TEXT("close shell wait");
static const TCHAR c_szMediaPath[] = REGSTR_VAL_MEDIA;

void
CloudView::StartMusic()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::StartMusic()"));

    if (!pfnmciSendString || music || InterlockedExchange(&anymusic, 1))
        return;

    TCHAR opencmd[MAX_PATH + ARRAYSIZE(c_szMidiOpen)], *filename;
    DWORD lencmd, lenbuf, temp;
    HANDLE file;
    HKEY hk;

    //
    // first try to open the sequencer device to see that its available
    //
    lstrcpy(opencmd, c_szMidiOpen);
    lstrcat(opencmd, c_szMidiSequencer);
    lstrcat(opencmd, c_szMidiAlias);
    if (pfnmciSendString(opencmd, NULL, 0, NULL) != 0)
        goto bail;
    pfnmciSendString(c_szMidiClose, NULL, 0, NULL);

    //
    // figure out where to put the file
    // sneak in the open command ahead of the filename (for later)
    //
    if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szRegSetup, &hk) != ERROR_SUCCESS)
        goto bail;
    lstrcpy(opencmd, c_szMidiOpen);
    lencmd = lstrlen(opencmd);
    lenbuf = ARRAYSIZE(opencmd) - lencmd;
    filename = opencmd + lencmd;
    temp = RegQueryValueEx(hk, c_szMediaPath, NULL, NULL,
        (LPBYTE)filename, &lenbuf);
    RegCloseKey(hk);
    if ((temp != ERROR_SUCCESS) || !*filename)
        goto bail;

    //
    // now create it (or verify it already exists)
    //
    PathAppend(filename, c_szMidiFile);

    file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    if (file != INVALID_HANDLE_VALUE)
    {
        HRSRC res = FindResource(HINST_THISDLL,
            MAKEINTRESOURCE(IDB_CLOUDS), c_szMID);
        HANDLE midi = res? LoadResource(HINST_THISDLL, res) : NULL;
        LPCVOID data = midi? LockResource(midi) : NULL;

        // okay so we'll write out a little extra - who cares
        if (data && !WriteFile(file, data, SizeofResource(HINST_THISDLL, res),
            &temp, NULL))
        {
            data = NULL;
        }

        CloseHandle(file);

        if (!data)
        {
            DeleteFile(filename);
            goto bail;
        }
    }
    else
    {
        DWORD err = GetLastError();
        if ((err != ERROR_ALREADY_EXISTS) && (err != ERROR_FILE_EXISTS))
            goto bail;
    }

    //
    // now play it
    //
    lstrcat(opencmd, c_szMidiAlias);
    if (pfnmciSendString(opencmd, NULL, 0, NULL) == 0)
    {
        music = 1;
        if (!NextMusicLoop())
        {
            DebugMsg(DM_TRACE, TEXT("CloudView::StartMusic() failed"));
            music = -1;
            pfnmciSendString(c_szMidiClose, NULL, 0, NULL);
        }
    }

bail:
    if (music < 1)
        InterlockedExchange(&anymusic, 0);
}

BOOL
CloudView::NextMusicLoop()
{
    BOOL result = FALSE;

    //
    // make sure we are actually playing music before we do something rash
    //
    if (music > 0)
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::NextMusicLoop() starting async play"));

        //
        // starting midi tends to spike and momentarily starve the animation
        // fight back against the tryranny of mci!
        //
        DWORD oldpri;
        if (animation)
        {
            oldpri = GetThreadPriority(animation);
            SetThreadPriority(animation, THREAD_PRIORITY_HIGHEST);
        }

        //
        // ask mci to start playing the file
        //
        if (pfnmciSendString(c_szMidiPlay, NULL, 0, window) == 0)
            result = TRUE;

        //
        // after a moment, restore the animation thread's original priority
        //
        if (animation)
        {
            Sleep(500);
            SetThreadPriority(animation, oldpri);
        }
    }

    return result;
}

void
CloudView::StopMusic()
{
    DebugMsg(DM_TRACE, TEXT("CloudView::StopMusic()"));

    if (music > 0)
    {
        DebugMsg(DM_TRACE, TEXT("CloudView::StopMusic() actually stopping"));
        music = -1;
        pfnmciSendString(c_szMidiClose, NULL, 0, NULL);
        InterlockedExchange(&anymusic, 0);
    }
}

LRESULT
CloudView::MsgHandler(UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg)
    {
        case WM_NCCREATE:
            Init((LPCREATESTRUCT)lp);
            goto DoDefault;

        case WM_DESTROY:
        case WM_NCDESTROY:
            Die();
            goto DoDefault;

        case WM_ERASEBKGND:
            if (Erase((HDC)wp))
                return TRUE;
            goto DoDefault;

        case WM_PAINT:
            Paint();
            break;

        case WM_SIZE:
            KillAnimation(TRUE);
            if ((wp != SIZE_MINIMIZED) && (wp != SIZE_MAXHIDE))
            {
                wndsize.cx = (int)(short)LOWORD(lp);
                wndsize.cy = (int)(short)HIWORD(lp);
                SetTimer(window, 1, 500, NULL);
            }
            break;

        case WM_TIMER:
            if (wp == 1)
            {
                KillTimer(window, 1);
                PostMessage(window, CLVM_ANIMATE, 0, 0);
            }
            break;

        case WM_PALETTECHANGED:
            if ((HWND)wp == window)
                break;
            // fall through
        case WM_QUERYNEWPALETTE:
            Realize();
            break;

        case CLVM_ANIMATE:
            RestartAnimation();
            break;

        case MM_MCINOTIFY:
            if ((wp == MCI_NOTIFY_SUCCESSFUL) && !NextMusicLoop())
            {
                DebugMsg(DM_TRACE, TEXT("CloudView::NextMusicLoop() failed"));
                StopMusic();
            }
            break;

        default:
        DoDefault:
            return DefWindowProc(window, msg, wp, lp);
    }

    return 0;
}

LRESULT _export CALLBACK
CloudView::WndProc(HWND window, UINT msg, WPARAM wp, LPARAM lp)
{
    CloudView *view;

    if (msg == WM_NCCREATE)
    {
        view = (CloudView *)((LPCREATESTRUCT)lp)->lpCreateParams;
        SetWindowLong(window, GWL_USERDATA, (LONG)view);
        view->window = window;
    }
    else
        view = (CloudView *)GetWindowLong(window, GWL_USERDATA);

    return view->MsgHandler(msg, wp, lp);
}

///////////////////////////////////////////////////////////////////////////////
// CloudView IShellView implementation
///////////////////////////////////////////////////////////////////////////////

STDMETHODIMP
CloudView::QueryInterface(REFIID id, LPVOID *out)
{
    if ((id == IID_IUnknown) || (id == IID_IShellView))
    {
        *out = (IShellView *)this;
        refs++;
        return NOERROR;
    }

    *out = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG)
CloudView::AddRef()
{
    return ++refs;
}

STDMETHODIMP_(ULONG)
CloudView::Release()
{
    if (!--refs)
    {
        delete this;
        return 0UL;
    }

    return refs;
}

STDMETHODIMP
CloudView::GetWindow(HWND *out)
{
    *out = window;
    return NOERROR;
}

STDMETHODIMP
CloudView::ContextSensitiveHelp(BOOL)
{
    return E_NOTIMPL;
}

STDMETHODIMP
CloudView::TranslateAccelerator(LPMSG)
{
    return S_FALSE;
}

STDMETHODIMP
CloudView::EnableModeless(BOOL)
{
    return NOERROR;
}

STDMETHODIMP
CloudView::UIActivate(UINT)
{
    return NOERROR;
}

STDMETHODIMP
CloudView::Refresh()
{
    return NOERROR;
}

STDMETHODIMP
CloudView::CreateViewWindow(IShellView *, LPCFOLDERSETTINGS lpfs,
    IShellBrowser *, RECT *rect, HWND *out)
{
    WNDCLASS wc;

    *out = NULL;

    if (!window)
    {
        if (!GetClassInfo(HINST_THISDLL, c_szCloudViewClassname, &wc))
        {
            // if two pages put one up, share one dc
            wc.style = 0;
            wc.lpfnWndProc = (WNDPROC)CloudView::WndProc;
            wc.cbClsExtra = wc.cbWndExtra = 0;
            wc.hInstance = HINST_THISDLL;
            wc.hIcon = NULL;
            wc.hCursor = LoadCursor(NULL, IDC_ARROW);
            wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
            wc.lpszMenuName = NULL;
            wc.lpszClassName = c_szCloudViewClassname;

            if (!RegisterClass(&wc))
                return E_UNEXPECTED;
        }

        // reassign here in case the nccreate failed...
        window = CreateWindowEx(WS_EX_CLIENTEDGE, c_szCloudViewClassname,
            szNULL, WS_CHILD | WS_VISIBLE, rect->left, rect->top,
            rect->right-rect->left, rect->bottom-rect->top,
            parent, NULL, HINST_THISDLL, this);

        if (!window)
            return E_OUTOFMEMORY;

        // schedule animation -- paranoia
        SetTimer(window, 1, 500, NULL);
    }

    *out = window;
    return NOERROR;
}

STDMETHODIMP
CloudView::DestroyViewWindow()
{
    if (window && IsWindow(window))
        DestroyWindow(window);

    return NOERROR;
}

STDMETHODIMP
CloudView::GetCurrentInfo(LPFOLDERSETTINGS)
{
    return E_NOTIMPL;
}

STDMETHODIMP
CloudView::AddPropertySheetPages(DWORD, LPFNADDPROPSHEETPAGE, LPARAM)
{
    return NOERROR;
}

STDMETHODIMP
CloudView::SaveViewState()
{
    return E_NOTIMPL;
}

STDMETHODIMP
CloudView::SelectItem(LPCITEMIDLIST, UINT)
{
    return E_NOTIMPL;
}

STDMETHODIMP
CloudView::GetItemObject(UINT, REFIID, LPVOID *out)
{
    *out = NULL;
    return E_NOTIMPL;
}


///////////////////////////////////////////////////////////////////////////////
// CloudFolder_CreateViewObject  (see fstreex.c)
///////////////////////////////////////////////////////////////////////////////

// from fstreex.c
//extern "C" STDMETHODIMP
//    CFSFolder_CreateViewObject(LPSHELLFOLDER, HWND, REFIID, LPVOID *);

extern "C" STDMETHODIMP
CloudFolder_CreateViewObject(LPSHELLFOLDER folder, HWND parent, REFIID id,
    LPVOID *out)
{
#ifdef DEBUG
    //
    // so all you weenies can build it and see it run without hacking...
    //
    if (g_dwCloudGroup == 0xFFFFFFFF)
        g_dwCloudGroup = 0;
#endif

    if ((id == IID_IShellView) && (g_dwCloudGroup != 0xFFFFFFFF))
    {
        CloudView *view = new CloudView(parent);

        if (view)
        {
            *out = (IShellView *)view;
            return S_OK;
        }
    }

    return CFSFolder_CreateViewObject(folder, parent, id, out);
}

///////////////////////////////////////////////////////////////////////////////
// CloudHook stuff (how we get to the cloud folder in the first place...)
///////////////////////////////////////////////////////////////////////////////

inline int PASCAL
CloudHookHashText(LPTSTR text)
{
    int data = 0;
    int count = 0;

    while (*text)
    {
        int temp = (data << 3) ^ (data >> 23);
        int crap = count++ - *text++;

        temp ^= crap;
        crap <<= 5;
        temp ^= crap;
        crap <<= 6;
        temp ^= crap;
        crap <<= 6;
        temp ^= crap;

        data = temp;
    }

    return data;
}

LPSHFILEOPSTRUCT HackedRename = NULL;
UINT CloudHookState = 0;

#ifdef WINNT
static const int CloudHookData[] = { -1375898046, 1722450348, -1184499671 };
#else
static const int CloudHookData[] = { -1375898046, 1722450348, -263390037 };
#endif

#define CLOUD_HOOK_NAMES  (SIZEOF(CloudHookData) / SIZEOF(CloudHookData[0]))
#define CLOUD_HOOK_STATES ((2 * CLOUD_HOOK_NAMES) - 1)

extern "C" void PASCAL
CloudHookBeginRename(LPTSTR newname, HWND listview, int item)
{
    if (!(CloudHookState & 1) && (CloudHookState < CLOUD_HOOK_STATES))
    {
        TCHAR buffer[2 * MAX_PATH + 1];

        ListView_GetItemText(listview, item, 0, buffer, ARRAYSIZE(buffer));
        lstrcat(buffer, newname);

        if (CloudHookHashText(buffer) == CloudHookData[CloudHookState / 2])
        {
            CloudHookState++;
            return;
        }
    }

    CloudHookState = 0;
}

extern "C" void PASCAL
CloudHookEndRename(void)
{
    if (CloudHookState & 1)
        CloudHookState = 0;

    if (HackedRename)
    {
        LocalFree(HackedRename);
        HackedRename = NULL;
    }
}

extern "C" void PASCAL
CloudHookFileOperation(LPSHFILEOPSTRUCT *ppop)
{
    if (((*ppop)->wFunc == FO_RENAME) && (CloudHookState & 1))
    {
        if (CloudHookState == CLOUD_HOOK_STATES)
        {
            g_dwCloudGroup = 0;

            HackedRename = (SHFILEOPSTRUCT *)
                LocalAlloc(LPTR, SIZEOF(SHFILEOPSTRUCT) + MAX_PATH);

            if (HackedRename)
            {
                hmemcpy(HackedRename, *ppop, SIZEOF(SHFILEOPSTRUCT));

                LPTSTR newname =
                    (LPTSTR)( (LPBYTE)HackedRename + SIZEOF(SHFILEOPSTRUCT));
                lstrcpy(newname, (*ppop)->pTo);

                TCHAR szClass[GUIDSTR_MAX + 1];
                *szClass = TEXT('.');
                StringFromGUID2A(CLSID_Clouds, szClass + 1,
                    ARRAYSIZE(szClass) - 1);
                lstrcat(newname, szClass);

                HackedRename->pTo = newname;
                *ppop = HackedRename;
            }

            CloudHookState = 0;
        }
        else
            CloudHookState++;
    }
    else
        CloudHookState = 0;
}