2 * SetupAPI virtual copy operations
4 * FIXME: we now rely on builtin setupapi.dll for dialog resources.
5 * This is bad ! We ought to have 16bit resource handling working.
9 #include "debugtools.h"
14 #include "stackframe.h"
16 #include "setupapi_private.h"
18 DEFAULT_DEBUG_CHANNEL(setupapi);
20 /* ### start build ### */
21 extern WORD CALLBACK VCP_CallTo16_word_lwwll(FARPROC16,LPVOID,UINT16,WPARAM,LPARAM,LPARAM);
22 /* ### stop build ### */
24 static FARPROC16 VCP_Proc = NULL;
25 static LPARAM VCP_MsgRef = 0;
27 #define VCP_CALLBACK(obj,msg,wParam,lParam,lParamRef) \
29 VCP_CallTo16_word_lwwll(VCP_Proc, obj,msg,wParam,lParam,lParamRef) : OK;
31 static BOOL VCP_opened = FALSE;
33 static VCPSTATUS vcp_status;
35 static HINSTANCE SETUPAPI_hInstance;
37 /****************************** VHSTR management ******************************/
40 * This is a totally braindead implementation for now;
41 * I don't care about speed at all ! Size and implementation time
42 * is much more important IMHO. I could have created some sophisticated
43 * tree structure, but... what the hell ! :-)
50 static VHSTR_STRUCT **vhstrlist = NULL;
51 static VHSTR vhstr_alloc = 0;
53 #define VALID_VHSTR(x) ((x < vhstr_alloc) && (vhstrlist[x]) && (vhstrlist[x]->refcount))
55 /***********************************************************************
56 * vsmStringAdd (SETUPX.207)
58 VHSTR WINAPI vsmStringAdd16(LPCSTR lpszName)
64 TRACE("add string '%s'\n", lpszName);
65 /* search whether string already inserted */
66 TRACE("searching for existing string...\n");
67 for (n = 0; n < vhstr_alloc; n++)
69 if ((vhstrlist[n]) && (vhstrlist[n]->refcount))
71 TRACE("checking item: %d\n", n);
72 if (!strcmp(vhstrlist[n]->pStr, lpszName))
75 vhstrlist[n]->refcount++;
81 /* hmm, not found yet, let's insert it */
82 TRACE("inserting item\n");
83 for (n = 0; n < vhstr_alloc; n++)
85 if ((!(vhstrlist[n])) || (!(vhstrlist[n]->refcount)))
91 heap = GetProcessHeap();
92 if (n == vhstr_alloc) /* hmm, no free index found yet */
96 vhstrlist = HeapReAlloc(heap, HEAP_ZERO_MEMORY, vhstrlist,
97 sizeof(VHSTR_STRUCT *) * vhstr_alloc);
100 return 0xffff; /* failure */
101 if (!vhstrlist[index])
102 vhstrlist[index] = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(VHSTR_STRUCT));
103 vhstrlist[index]->refcount = 1;
104 vhstrlist[index]->pStr = HeapAlloc(heap, 0, strlen(lpszName)+1);
105 strcpy((LPSTR)vhstrlist[index]->pStr, lpszName);
109 /***********************************************************************
110 * vsmStringDelete (SETUPX.206)
112 INT16 WINAPI vsmStringDelete16(VHSTR vhstr)
114 if (VALID_VHSTR(vhstr))
116 vhstrlist[vhstr]->refcount--;
117 if (!vhstrlist[vhstr]->refcount)
119 HeapFree(GetProcessHeap(), 0, (LPSTR)vhstrlist[vhstr]->pStr);
120 vhstrlist[vhstr]->pStr = NULL;
125 /* string not found */
130 * vsmStringFind() - not exported from a standard SETUPX.DLL, it seems
132 VHSTR WINAPI vsmStringFind16(LPCSTR lpszName)
135 for (n = 0; n < vhstr_alloc; n++)
136 if ((vhstrlist[n]) && (vhstrlist[n]->refcount) && (!strcmp(vhstrlist[n]->pStr, lpszName)))
141 /***********************************************************************
142 * vsmGetStringName (SETUPX.205)
144 * Pretty correct, I guess
146 INT16 WINAPI vsmGetStringName16(VHSTR vhstr, LPSTR lpszBuffer, int cbBuffer)
148 if (VALID_VHSTR(vhstr))
150 int len = strlen(vhstrlist[vhstr]->pStr)+1;
154 strcpy(lpszBuffer, vhstrlist[vhstr]->pStr);
161 /***********************************************************************
162 * vsmStringCompare (not exported from a standard SETUPX.DLL, it seems)
164 INT16 WINAPI vsmStringCompare16(VHSTR vhstrA, VHSTR vhstrB)
166 if ((!VALID_VHSTR(vhstrA)) || (!VALID_VHSTR(vhstrB)))
167 return VCPN_FAIL; /* correct ? */
168 return strcmp(vhstrlist[vhstrA]->pStr, vhstrlist[vhstrB]->pStr);
171 /***********************************************************************
172 * vsmGetStringRawName (SETUPX.208)
174 LPCSTR WINAPI vsmGetStringRawName16(VHSTR vhstr)
176 return (VALID_VHSTR(vhstr)) ? vhstrlist[vhstr]->pStr : NULL;
180 /***************************** VIRTNODE management ****************************/
181 static LPVIRTNODE *pvnlist = NULL;
182 static DWORD vn_num = 0;
183 static DWORD vn_last = 0;
185 RETERR16 VCP_VirtnodeCreate(LPVCPFILESPEC vfsSrc, LPVCPFILESPEC vfsDst, WORD fl, LPARAM lParam, LPEXPANDVTBL lpExpandVtbl)
191 while (vn_last < vn_num)
193 if (pvnlist[vn_last] == NULL)
197 heap = GetProcessHeap();
198 if (vn_last == vn_num)
201 pvnlist = HeapReAlloc(heap, HEAP_ZERO_MEMORY, pvnlist,
202 sizeof(LPVIRTNODE *) * vn_num);
204 pvnlist[vn_last] = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(VIRTNODE));
205 lpvn = pvnlist[vn_last];
208 lpvn->cbSize = sizeof(VIRTNODE);
211 memcpy(&lpvn->vfsSrc, vfsSrc, sizeof(VCPFILESPEC));
214 memcpy(&lpvn->vfsDst, vfsDst, sizeof(VCPFILESPEC));
217 lpvn->lParam = lParam;
218 lpvn->lpExpandVtbl = lpExpandVtbl;
220 lpvn->vhstrDstFinalName = 0xffff; /* FIXME: what is this ? */
222 cbres = VCP_CALLBACK(lpvn, VCPM_NODECREATE, 0, 0, VCP_MsgRef);
223 lpvn->fl |= VFNL_CREATED;
224 cbres = VCP_CALLBACK(lpvn, VCPM_NODEACCEPT, 0, 0, VCP_MsgRef);
229 BOOL VCP_VirtnodeDelete(LPVIRTNODE lpvnDel)
234 for (n = 0; n < vn_last; n++)
236 if (pvnlist[n] == lpvnDel)
238 cbres = VCP_CALLBACK(lpvnDel, VCPM_NODEDESTROY, 0, 0, VCP_MsgRef);
239 HeapFree(GetProcessHeap(), 0, lpvnDel);
247 /***********************************************************************
248 * VcpOpen (SETUPX.200)
250 * Sets up a virtual copy operation.
251 * This means that functions such as GenInstall()
252 * create a VIRTNODE struct for every file to be touched in a .INF file
253 * instead of actually touching the file.
254 * The actual copy/move/rename gets started when VcpClose or
255 * VcpFlush is called; several different callbacks are made
256 * (copy, rename, open, close, version conflicts, ...) on every file copied.
258 RETERR16 WINAPI VcpOpen16(VIFPROC vifproc, LPARAM lparamMsgRef)
260 TRACE("(%p, %08lx)\n", vifproc, lparamMsgRef);
264 VCP_Proc = (FARPROC16)vifproc;
265 VCP_MsgRef = lparamMsgRef;
267 /* load SETUPAPI needed for dialog resources etc. */
268 SETUPAPI_hInstance = LoadLibraryA("setupapi.dll");
269 if (!SETUPAPI_hInstance)
271 ERR("Could not load sibling setupapi.dll\n");
272 return ERR_VCP_NOMEM;
278 /***********************************************************************
279 * VcpQueueCopy [SETUPX.13]
281 * lpExpandVtbl seems to be deprecated.
282 * fl are the CNFL_xxx and VNFL_xxx flags.
283 * lParam are the VNLP_xxx flags.
285 RETERR16 WINAPI VcpQueueCopy16(
286 LPCSTR lpszSrcFileName, LPCSTR lpszDstFileName,
287 LPCSTR lpszSrcDir, LPCSTR lpszDstDir,
288 LOGDISKID16 ldidSrc, LOGDISKID16 ldidDst,
289 LPEXPANDVTBL lpExpandVtbl,
290 WORD fl, LPARAM lParam
293 VCPFILESPEC vfsSrc, vfsDst;
296 return ERR_VCP_NOTOPEN;
298 TRACE("srcdir: %s, srcfile: %s, dstdir: %s, dstfile: %s\n",
299 lpszSrcDir, lpszSrcFileName, lpszDstDir, lpszDstFileName);
301 TRACE("ldidSrc == %d, ldidDst == %d\n", ldidSrc, ldidDst);
303 vfsSrc.ldid = ldidSrc;
304 vfsSrc.vhstrDir = vsmStringAdd16(lpszSrcDir);
305 vfsSrc.vhstrFileName = vsmStringAdd16(lpszSrcFileName);
307 vfsDst.ldid = ldidDst;
308 vfsDst.vhstrDir = vsmStringAdd16(lpszDstDir);
309 vfsDst.vhstrFileName = vsmStringAdd16(lpszDstFileName);
311 return VCP_VirtnodeCreate(&vfsSrc, &vfsDst, fl, lParam,
315 /***********************************************************************
316 * VcpQueueDelete [SETUPX.17]
318 * Is lParamRef the same as lParam in VcpQueueCopy ?
319 * Damn docu !! Err... which docu ?
321 RETERR16 WINAPI VcpQueueDelete16(
322 LPCSTR lpszDstFileName,
331 return ERR_VCP_NOTOPEN;
333 vfsDst.ldid = ldidDst;
334 vfsDst.vhstrDir = vsmStringAdd16(lpszDstDir);
335 vfsDst.vhstrFileName = vsmStringAdd16(lpszDstFileName);
337 return VCP_VirtnodeCreate(NULL, &vfsDst, VNFL_DELETE, lParamRef, 0);
340 /***********************************************************************
341 * VcpQueueRename [SETUPX.204]
344 RETERR16 WINAPI VcpQueueRename16(
345 LPCSTR lpszSrcFileName, LPCSTR lpszDstFileName,
346 LPCSTR lpszSrcDir, LPCSTR lpszDstDir,
347 LOGDISKID16 ldidSrc, LOGDISKID16 ldidDst,
351 VCPFILESPEC vfsSrc, vfsDst;
354 return ERR_VCP_NOTOPEN;
356 vfsSrc.ldid = ldidSrc;
357 vfsSrc.vhstrDir = vsmStringAdd16(lpszSrcDir);
358 vfsSrc.vhstrFileName = vsmStringAdd16(lpszSrcFileName);
360 vfsDst.ldid = ldidDst;
361 vfsDst.vhstrDir = vsmStringAdd16(lpszDstDir);
362 vfsDst.vhstrFileName = vsmStringAdd16(lpszDstFileName);
364 return VCP_VirtnodeCreate(&vfsSrc, &vfsDst, VNFL_RENAME, lParam,
368 /***********************************************************************
369 * VcpEnumFiles (SETUPX.@)
371 INT16 WINAPI VcpEnumFiles(VCPENUMPROC vep, LPARAM lParamRef)
375 for (n = 0; n < vn_last; n++)
376 vep(pvnlist[n], lParamRef);
378 return 0; /* FIXME: return value ? */
381 /***********************************************************************
382 * VcpExplain (SETUPX.?)
384 LPCSTR WINAPI VcpExplain16(LPVIRTNODE lpVn, DWORD dwWhat)
386 static char buffer[MAX_PATH]; /* FIXME: is this how it's done ? */
393 LPVCPFILESPEC lpvfs =
394 (dwWhat == VCPEX_SRC_FULL) ? &lpVn->vfsSrc : &lpVn->vfsDst;
396 /* if we have an ldid, use it, otherwise use the string */
397 /* from the vhstrlist array */
398 if (lpvfs->ldid != 0xffff)
399 CtlGetLddPath16(lpvfs->ldid, buffer);
401 strcat(buffer, vsmGetStringRawName16(lpvfs->vhstrDir));
403 strcat(buffer, "\\");
404 strcat(buffer, vsmGetStringRawName16(lpvfs->vhstrFileName));
408 FIXME("%ld unimplemented !\n", dwWhat);
409 strcpy(buffer, "Unknown error");
415 RETERR16 VCP_CheckPaths(void)
421 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATPATHCHECKSTART, 0, 0, VCP_MsgRef);
422 for (n = 0; n < vn_num; n++)
426 /* FIXME: check paths of all VIRTNODEs here ! */
427 cbres = VCP_CALLBACK(&lpvn->vfsDst, VCPM_CHECKPATH, 0, (DWORD)lpvn, VCP_MsgRef);
429 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATPATHCHECKEND, 0, 0, VCP_MsgRef);
433 RETERR16 VCP_CopyFiles(void)
435 char fn_src[MAX_PATH], fn_dst[MAX_PATH];
436 RETERR16 res = OK, cbres;
440 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATCOPYSTART, 0, 0, VCP_MsgRef);
441 for (n = 0; n < vn_num; n++)
444 if ((!lpvn) || ((lpvn->fl & VNFL_NODE_TYPE) != VNFL_COPY)) continue;
445 /* FIXME: need to send VCPM_VSTATNEWDISK notification sometimes */
446 strcpy(fn_src, VcpExplain16(lpvn, VCPEX_SRC_FULL));
447 strcpy(fn_dst, VcpExplain16(lpvn, VCPEX_DST_FULL));
448 /* FIXME: what is this VCPM_VSTATWRITE here for ?
449 * I guess it's to signal successful destination file creation */
450 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATWRITE, 0, 0, VCP_MsgRef);
452 /* FIXME: need to do the file copy in small chunks for notifications */
453 TRACE("copying '%s' to '%s'\n", fn_src, fn_dst);
454 /* perform the file copy */
455 if (!(CopyFileA(fn_src, fn_dst, TRUE)))
457 ERR("error copying, src: %s -> dst: %s\n", fn_src, fn_dst);
458 res = ERR_VCP_IOFAIL;
461 vcp_status.prgFileRead.dwSoFar++;
462 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATREAD, 0, 0, VCP_MsgRef);
463 vcp_status.prgFileWrite.dwSoFar++;
464 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATWRITE, 0, 0, VCP_MsgRef);
467 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATCOPYEND, 0, 0, VCP_MsgRef);
471 /***********************************************************************
472 * VcpFlush - internal (not exported), but documented
474 * VNFL_NOW is used for VcpFlush.
476 RETERR16 VcpFlush16(WORD fl, LPCSTR lpszBackupDest)
481 /***********************************************************************
482 * VcpClose (SETUPX.201)
484 * Does callbacks (-> vifproc) with VCPM_VSTATCLOSESTART,
485 * VCPM_VSTATCLOSEEND.
487 * fl gets VCPFL_xxx flags to indicate what to do with the
488 * VIRTNODEs (files to mess with) created by e.g. GenInstall()
490 RETERR16 WINAPI VcpClose16(WORD fl, LPCSTR lpszBackupDest)
493 WORD cbres = VCPN_PROCEED;
495 TRACE("(%04x, '%s')\n", fl, lpszBackupDest);
497 /* FIXME: needs to sort virtnodes in case VCPFL_INSPECIFIEDORDER
498 * is not set. This is done by VCP_CALLBACK(VCPM_NODECOMPARE) */
501 memset(&vcp_status, 0, sizeof(VCPSTATUS));
502 /* yes, vcp_status.cbSize is 0 ! */
504 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATCLOSESTART, 0, 0, VCP_MsgRef);
507 res = VCP_CheckPaths();
510 return res; /* is this ok ? */
514 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATCLOSEEND, 0, 0, VCP_MsgRef);
517 FreeLibrary(SETUPAPI_hInstance);
522 RETERR16 VCP_RenameFiles(void)
524 char fn_src[MAX_PATH], fn_dst[MAX_PATH];
525 RETERR16 res = OK, cbres;
529 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATRENAMESTART, 0, 0, VCP_MsgRef);
530 for (n = 0; n < vn_num; n++)
533 if ((!lpvn) || ((lpvn->fl & VNFL_NODE_TYPE) != VNFL_RENAME)) continue;
534 strcpy(fn_src, VcpExplain16(lpvn, VCPEX_SRC_FULL));
535 strcpy(fn_dst, VcpExplain16(lpvn, VCPEX_DST_FULL));
536 cbres = VCP_CALLBACK(&lpvn->vfsDst, VCPM_FILEOPENOUT, 0, (LPARAM)lpvn, VCP_MsgRef);
537 if (!(MoveFileExA(fn_src, fn_dst, MOVEFILE_REPLACE_EXISTING)))
538 res = ERR_VCP_IOFAIL;
540 VCP_VirtnodeDelete(lpvn);
542 cbres = VCP_CALLBACK(&vcp_status, VCPM_VSTATRENAMEEND, 0, 0, VCP_MsgRef);
546 /***********************************************************************
547 * vcpDefCallbackProc (SETUPX.202)
549 RETERR16 WINAPI vcpDefCallbackProc16(LPVOID lpvObj, UINT16 uMsg, WPARAM wParam,
550 LPARAM lParam, LPARAM lParamRef)
552 static int count = 0;
554 FIXME("(%p, %04x, %04x, %08lx, %08lx) - what to do here ?\n",
555 lpvObj, uMsg, wParam, lParam, lParamRef);
560 /********************* point-and-click stuff from here ***********************/
562 static HWND hDlgCopy = 0;
563 static HKEY hKeyFiles = 0, hKeyRename = 0, hKeyConflict = 0;
564 static char BackupDir[12];
566 static BOOL CALLBACK VCP_UI_FileCopyDlgProc(HWND hWndDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
570 if (iMsg == WM_INITDIALOG)
572 ShowWindow(hWndDlg, SW_SHOWNORMAL);
573 UpdateWindow(hWndDlg);
579 BOOL VCP_UI_GetDialogTemplate(LPCVOID *template32)
581 HANDLE hResInfo, hDlgTmpl32;
583 if (!(hResInfo = FindResourceA(SETUPAPI_hInstance, MAKEINTRESOURCEA(COPYFILEDLGORD), RT_DIALOGA)))
585 if (!(hDlgTmpl32 = LoadResource(SETUPAPI_hInstance, hResInfo )) ||
586 !(*template32 = LockResource( hDlgTmpl32 )))
591 static LRESULT WINAPI
592 VCP_UI_FileCopyWndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
594 if (uMsg != WM_CREATE)
595 return DefWindowProcA (hwnd, uMsg, wParam, lParam);
602 FIXME("%04x: unhandled.\n", uMsg);
608 void VCP_UI_RegisterProgressClass(void)
610 static BOOL registered = FALSE;
617 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
618 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
619 wndClass.lpfnWndProc = (WNDPROC)VCP_UI_FileCopyWndProc;
620 wndClass.cbClsExtra = 0;
621 wndClass.cbWndExtra = 0;
622 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
623 wndClass.hbrBackground = (HBRUSH)NULL;
624 wndClass.lpszClassName = "setupx_progress";
626 RegisterClassA (&wndClass);
629 RETERR16 VCP_UI_NodeCompare(LPVIRTNODE vn1, LPVIRTNODE vn2)
632 file1 = vsmGetStringRawName16(vn1->vfsSrc.vhstrFileName);
633 file2 = vsmGetStringRawName16(vn2->vfsSrc.vhstrFileName);
634 return (RETERR16)strcmp(file1, file2);
637 RETERR16 VCP_UI_CopyStart(void)
640 char buf[256]; /* plenty */
644 /* FIXME: should be registered at DLL startup instead */
645 VCP_UI_RegisterProgressClass();
646 if (!(VCP_UI_GetDialogTemplate(&template32)))
649 hDlgCopy = CreateDialogIndirectParamA(SETUPAPI_hInstance, template32, 0,
650 VCP_UI_FileCopyDlgProc, 0);
653 SetDlgItemTextA(hDlgCopy, SOURCESTRORD, "Scanning ...");
654 SetDlgItemTextA(hDlgCopy, DESTSTRORD, "NOT_IMPLEMENTED_YET");
655 strcpy(buf, REG_INSTALLEDFILES);
656 if (RegCreateKeyA(HKEY_LOCAL_MACHINE, buf, &hKeyFiles))
658 strcat(buf, REGPART_RENAME);
659 if (RegCreateKeyA(HKEY_LOCAL_MACHINE, buf, &hKeyRename))
661 if (RegCreateKeyA(HKEY_LOCAL_MACHINE, REG_VERSIONCONFLICT, &hKeyConflict))
664 if (!(RegQueryValueExA(hKeyConflict, "Dirty", NULL, 0, (LPBYTE)&dirty, &len)))
666 /* FIXME: what does SETUPX.DLL do in this case ? */
667 MESSAGE("Warning: another program using SETUPX is already running ! Failed.\n");
671 if (RegSetValueExA(hKeyConflict, "Dirty", 0, REG_BINARY, (LPBYTE)&dirty, 1))
674 if (!(RegQueryValueExA(hKeyConflict, "BackupDirectory", NULL, 0, BackupDir, &len)))
675 strcpy(BackupDir, "VCM");
677 /* create C:\WINDOWS\[BackupDir] and set registry key to it */
678 GetWindowsDirectoryA(buf, 256);
680 strcat(buf, BackupDir);
681 if (!(CreateDirectoryA(buf, NULL)))
683 if (RegSetValueExA(hKeyConflict, "BackupDirectory", 0, REG_SZ, (LPBYTE)buf, strlen(buf)+1))
685 RegCloseKey(hKeyConflict);
690 /***********************************************************************
691 * vcpUICallbackProc (SETUPX.213)
693 RETERR16 WINAPI vcpUICallbackProc16(LPVOID lpvObj, UINT16 uMsg, WPARAM wParam,
694 LPARAM lParam, LPARAM lParamRef)
696 static int count = 0;
697 RETERR16 res = VCPN_OK, cbres;
700 FIXME("(%p, %04x, %04x, %08lx, %08lx) - semi-stub\n",
701 lpvObj, uMsg, wParam, lParam, lParamRef);
705 /* unused messages, it seems */
706 case VCPM_DISKPREPINFO:
708 case VCPM_FILENEEDED:
710 case VCPM_NODECREATE:
711 case VCPM_NODEACCEPT:
713 case VCPM_VSTATCLOSESTART:
714 case VCPM_VSTATPATHCHECKSTART:
715 case VCPM_VSTATPATHCHECKEND:
721 case VCPM_NODECOMPARE:
722 res = VCP_UI_NodeCompare((LPVIRTNODE)lpvObj, (LPVIRTNODE)lParam);
726 case VCPM_VSTATWRITE:
727 cbres = VCP_CALLBACK(&vcp_status, VCPM_DISKPREPINFO, 0, 0, VCP_MsgRef);
729 case VCPM_VSTATCLOSEEND:
730 RegCloseKey(hKeyFiles);
731 RegCloseKey(hKeyRename);
732 RegDeleteKeyA(HKEY_LOCAL_MACHINE, REG_VERSIONCONFLICT);
734 case VCPM_VSTATCOPYSTART:
735 res = VCP_UI_CopyStart();
737 case VCPM_VSTATCOPYEND:
738 DestroyWindow(hDlgCopy);
741 FIXME("unhandled msg 0x%04x\n", uMsg);