Repository: jixunmoe/mfcDuDownloadCodeGenerator Branch: main Commit: 7c5a6d30077d Files: 39 Total size: 82.4 KB Directory structure: gitextract_vof0guyl/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── msvc.yml ├── .gitignore ├── LICENSE ├── README.MD ├── generator/ │ ├── AdvEdit.cpp │ ├── AdvEdit.h │ ├── ClipboardHelper.cpp │ ├── ClipboardHelper.h │ ├── DPISupport.cpp │ ├── DPISupport.h │ ├── FileItem.cpp │ ├── FileItem.h │ ├── Hasher.cpp │ ├── Hasher.h │ ├── ListBoxEx.cpp │ ├── ListBoxEx.h │ ├── ProgressText.cpp │ ├── ProgressText.h │ ├── ReadMe.txt │ ├── appDlg.cpp │ ├── appDlg.h │ ├── base64.cpp │ ├── base64.h │ ├── encoding.cpp │ ├── encoding.h │ ├── generator.cpp │ ├── generator.h │ ├── generator.rc │ ├── generator.vcxproj │ ├── generator.vcxproj.filters │ ├── res/ │ │ └── generator.rc2 │ ├── resource.h │ ├── stdafx.cpp │ ├── stdafx.h │ ├── targetver.h │ ├── utils.cpp │ └── utils.h └── mfcDuDownloadCodeGenerator.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/workflows/msvc.yml ================================================ name: MSBuild on: push: pull_request: branches: [ "main" ] env: # Path to the solution file relative to the root of the project. SOLUTION_FILE_PATH: mfcDuDownloadCodeGenerator.sln BUILD_CONFIGURATION: Release permissions: contents: read jobs: build: strategy: matrix: build_config: - vs2019.x86_64 - vs2019.static.xp.x86 - vs2022.x86_64 - vs2022.static.x86_64 include: - build_config: vs2019.x86_64 os: windows-2019 - build_config: vs2019.static.xp.x86 os: windows-2019 - build_config: vs2022.x86_64 os: windows-2022 - build_config: vs2022.static.x86_64 os: windows-2022 runs-on: "${{ matrix.os }}" permissions: contents: write steps: - name: 🛎️ checkout uses: actions/checkout@v3 - name: 🏭 prepare MSBuild uses: microsoft/setup-msbuild@v1.0.2 - name: 🏭 prepare msys2 uses: msys2/setup-msys2@v2 with: update: true install: >- git upx zip - name: 🔧 build working-directory: ${{env.GITHUB_WORKSPACE}} # Add additional options to the MSBuild command line here (like platform or verbosity level). # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference run: | Set-PSDebug -Trace 1 $config = "${{ matrix.build_config }}" if ($config.Contains(".static.")) { $env:_CL_ += ' /MT' $mfc_type = 'Static' } else { $env:_CL_ += ' /MD' $mfc_type = 'Dynamic' } $platform = if ($config.EndsWith('x86_64')) { "x64" } else { "x86" } $toolset = if ($config.StartsWith('vs2022.')) { "v143" } elseif ($config.Contains('.xp.')) { "v141_xp" } else { "v142" } if ($config.Contains('.xp.')) { $env:_CL_ += ' /D__BUILD_FOR_XP=1' } msbuild /m ` /p:Configuration=${{ env.BUILD_CONFIGURATION }} ` /p:Platform=$platform ` /p:PlatformToolset=$toolset ` /p:UseOfMfc=$mfc_type ` ${{env.SOLUTION_FILE_PATH}} New-Item -ItemType Directory build Move-Item -Path ".\$platform\${{ env.BUILD_CONFIGURATION }}\*.exe" ` -Destination ".\build\" - name: 🗜️ rename + optional upx shell: msys2 {0} run: | set -ex cd "build" if [[ "${{ matrix.build_config }}" =~ ".static." ]]; then upx -9 *.exe fi cp "ducode.exe" "ducode.${{ matrix.build_config }}.exe" - name: 🗜️ archive binaries uses: actions/upload-artifact@v3 with: name: binaries path: build/ducode.${{ matrix.build_config }}.exe - name: 📦 package for release if: startsWith(github.ref, 'refs/tags/v') shell: msys2 {0} run: | set -ex cd "build" GIT_VERSION="${{github.ref}}" GIT_VERSION="${GIT_VERSION:10}" ZIP_NAME="ducode.${{ matrix.build_config }}.${GIT_VERSION}.zip" zip -9 "../${ZIP_NAME}" "ducode.exe" - name: 📝 draft release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: body: "" draft: true # note you'll typically need to create a personal access token # with permissions to create releases in the other repo token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} files: | *.zip ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc *.tlog vs2019_ci_static_xp/ ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2020, Jixun Wu All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.MD ================================================ # mfcDuDownloadCodeGenerator ڰٶ̵ı׼ȡ ![](https://cdn.jsdelivr.net/gh/JixunMoe/mfcDuDownloadCodeGenerator/imgs/sshot-stdcode.png) --- ģ * https://jixun.moe/post/du-code-gen * https://blog.jixun.moe/du-code-gen ================================================ FILE: generator/AdvEdit.cpp ================================================ // AdvEdit.cpp : implementation file // #include "stdafx.h" #include "AdvEdit.h" // CAdvEdit IMPLEMENT_DYNAMIC(CAdvEdit, CEdit) CAdvEdit::CAdvEdit() { } CAdvEdit::~CAdvEdit() { } void CAdvEdit::Append(const CString& text) { std::lock_guard guard(mutex); auto nLength = GetWindowTextLength(); SetSel(nLength, nLength); ReplaceSel(text); } void CAdvEdit::SelectAll() { SetSel(0, GetWindowTextLength(), FALSE); } BEGIN_MESSAGE_MAP(CAdvEdit, CEdit) ON_WM_KEYDOWN() END_MESSAGE_MAP() void CAdvEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CEdit::OnKeyDown(nChar, nRepCnt, nFlags); if (nChar == 'A') { BYTE keyState[256] = { 0 }; GetKeyboardState(keyState); bool useControl = (keyState[VK_CONTROL] & 0x80) != 0; bool useShift = (keyState[VK_SHIFT] & 0x80) != 0; bool useAlt = (keyState[VK_MENU] & 0x80) != 0; if (useControl && !useShift && !useAlt) { this->SelectAll(); } } } ================================================ FILE: generator/AdvEdit.h ================================================ #pragma once #include // CAdvEdit class CAdvEdit : public CEdit { DECLARE_DYNAMIC(CAdvEdit) std::mutex mutex; public: CAdvEdit(); virtual ~CAdvEdit(); void Append(const CString &text); void SelectAll(); protected: DECLARE_MESSAGE_MAP() public: afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); }; ================================================ FILE: generator/ClipboardHelper.cpp ================================================ #include "stdafx.h" #include "ClipboardHelper.h" void CopyStringToClipboard(CString& str, HWND hWnd) { auto pString = static_cast(str); auto dwStrByteLen = (str.GetLength() + 1) * sizeof TCHAR; HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, dwStrByteLen); if (!hMem) return; auto pLockedMem = GlobalLock(hMem); if (!pLockedMem) { GlobalFree(hMem); return; } memcpy(pLockedMem, pString, dwStrByteLen); GlobalUnlock(hMem); if (OpenClipboard(hWnd)) { EmptyClipboard(); #ifndef _UNICODE SetClipboardData(CF_TEXT, hMem); #else SetClipboardData(CF_UNICODETEXT, hMem); #endif CloseClipboard(); } } ================================================ FILE: generator/ClipboardHelper.h ================================================ #pragma once #include "stdafx.h" void CopyStringToClipboard(CString& str, HWND hWnd = nullptr); ================================================ FILE: generator/DPISupport.cpp ================================================ #include "stdafx.h" #include "DPISupport.h" #if !__BUILD_FOR_XP #include #include #pragma comment(lib, "Shcore") #endif uint32_t DPISupport::GetWindowDPI(HWND hWnd) { UINT xdpi = 0; UINT ydpi = 0; #if !__BUILD_FOR_XP // Windows 8.1 or higher if (IsWindows8Point1OrGreater()) { HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); LRESULT success = GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); return success == S_OK ? ydpi : 96; } #endif HDC hDC = GetDC(hWnd); ydpi = static_cast(GetDeviceCaps(hDC, LOGPIXELSY)); ReleaseDC(hWnd, hDC); return ydpi; } ================================================ FILE: generator/DPISupport.h ================================================ #pragma once #include #include namespace DPISupport { uint32_t GetWindowDPI(HWND hWnd); } ================================================ FILE: generator/FileItem.cpp ================================================ #include "stdafx.h" #include "FileItem.h" #include "encoding.h" #include "base64.h" #include "resource.h" CFileItem::CFileItem() { } CFileItem::~CFileItem() { SAFE_DELETE(m_pDirectory); SAFE_DELETE(m_pFilename); SAFE_DELETE(m_pFirstHash); SAFE_DELETE(m_pFullHash); if (m_hIcon) { DestroyIcon(m_hIcon); m_hIcon = nullptr; } } bool CFileItem::Done() const { return m_pFullHash != nullptr; } CString CFileItem::GetSizeString() const { CString str; if (m_nSize == 0) { str = TEXT("0"); return str; } TCHAR sizes[][6] = { _T("B"), _T("KB"), _T("MB"), _T("GB"), _T("TB"), _T("PB") }; auto power = log(double(m_nSize)) / log(1024); auto unit = int(floor(power)); auto r = m_nSize / pow(1024, unit); str.Format(_T("%.2f %s"), r, sizes[unit]); return str; } CString CFileItem::BDLink() const { CString result; CString dlCode = this->DownloadCode(); utf8_str str = {}; ToUTF8(str, dlCode.GetBuffer()); if (str.str == nullptr) { BOOL _ = result.LoadStringW(IDS_ERROR_ENCODE_FAIL); return result; } // ʹ strlen ַСֹ base64 仯ݺ NUL ֽ wchar_t* encoded = base64_encode(reinterpret_cast(str.str), strlen(str.str)); free(str.str); if (encoded == nullptr) { BOOL _ = result.LoadStringW(IDS_ERROR_ENCODE_FAIL); return result; } // ƴӵַ result.Append(_T("https://pan.baidu.com/#bdlink=")); result.Append(encoded); free(encoded); return result; } CString CFileItem::DownloadCode() const { CString str; if (Done()) { str.Format(_T("%s#%s#%llu#%s"), m_pFullHash->GetString(), m_pFirstHash->GetString(), m_nSize, m_pFilename->GetString() ); } else { str.Format(_T("%s#%s#%llu#%s\r\n"), _T(""), _T(""), m_nSize, m_pFilename->GetString() ); } return str; } ================================================ FILE: generator/FileItem.h ================================================ #pragma once class CFileItem { public: CString* m_pDirectory = nullptr; CString* m_pFilename = nullptr; HICON m_hIcon = nullptr; uint64_t m_nSize = 0; CString* m_pFirstHash = nullptr; CString* m_pFullHash = nullptr; public: CFileItem(); ~CFileItem(); bool Done() const; CString GetSizeString() const; CString BDLink() const; CString DownloadCode() const; }; ================================================ FILE: generator/Hasher.cpp ================================================ #include "stdafx.h" #include "Hasher.h" bool Hasher::Init() { auto bSuccess = false; if (CryptAcquireContext(&m_hCryptProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { bSuccess = CryptCreateHash(m_hCryptProv, this->m_alg, NULL, NULL, &m_phHash); if (bSuccess) return bSuccess; } Cleanup(); if (CryptAcquireContext(&m_hCryptProv, nullptr, nullptr, PROV_RSA_FULL, NULL)) { bSuccess = CryptCreateHash(m_hCryptProv, this->m_alg, NULL, NULL, &m_phHash); if (bSuccess) return bSuccess; } Cleanup(); if (CryptAcquireContext(&m_hCryptProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET)) { bSuccess = CryptCreateHash(m_hCryptProv, this->m_alg, NULL, NULL, &m_phHash); if (bSuccess) return bSuccess; } Cleanup(); return bSuccess; } Hasher::Hasher(ALG_ID alg) { this->m_alg = alg; } Hasher::~Hasher() { } void Hasher::Cleanup() { if (m_hKey) { CryptDestroyKey(m_hKey); m_hKey = NULL; } if (m_phHash) { CryptDestroyHash(m_phHash); m_phHash = NULL; } if (m_hCryptProv) { CryptReleaseContext(m_hCryptProv, NULL); m_hCryptProv = NULL; } } CString* Hasher::GetHashStr() { DWORD dwDataLen; auto r = new CString(""); if (CryptGetHashParam(this->m_phHash, HP_HASHVAL, nullptr, &dwDataLen, 0)) { BYTE* d = new BYTE[dwDataLen]; CryptGetHashParam(this->m_phHash, HP_HASHVAL, d, &dwDataLen, 0); for(DWORD i = 0; i < dwDataLen; i++) { r->AppendFormat(_T("%X%X"), d[i] >> 4, d[i] & 0x0F); } delete[] d; } Cleanup(); return r; } void Hasher::Feed(char* str, int n) const { CryptHashData(m_phHash, LPBYTE(str), n, NULL); } ================================================ FILE: generator/Hasher.h ================================================ #pragma once #include class Hasher { HCRYPTPROV m_hCryptProv = NULL; HCRYPTHASH m_phHash = NULL; HCRYPTKEY m_hKey = NULL; ALG_ID m_alg = NULL; public: bool Init(); Hasher(ALG_ID alg); ~Hasher(); void Cleanup(); CString* GetHashStr(); void Feed(char* str, int i) const; }; ================================================ FILE: generator/ListBoxEx.cpp ================================================ // ListBoxEx.cpp : implementation file // #include "stdafx.h" #include "ListBoxEx.h" #include "resource.h" #include "Hasher.h" #include "ClipboardHelper.h" #include "DPISupport.h" #include // std::ifstream #define BD_FILE_HEADER_SIZE (256 * 1024) constexpr double icon_base_dimention = 36.0; // CListBoxEx IMPLEMENT_DYNAMIC(CListBoxEx, CListBox) HICON CListBoxEx::m_hIconTick = nullptr; CString CListBoxEx::m_sPlaceholder; HFONT CListBoxEx::m_systemFont; CListBoxEx::CListBoxEx() { // init guard if (m_hIconTick == nullptr) { m_hIconTick = static_cast(LoadImage(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON_TICK), IMAGE_ICON, 16, 16, 0)); int _ = m_sPlaceholder.LoadStringW(IDS_LIST_PLACEHOLDER); NONCLIENTMETRICS metrics = {}; metrics.cbSize = sizeof(metrics); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, 0); m_systemFont = CreateFontIndirect(&metrics.lfCaptionFont); CString placeholder; } } CListBoxEx::~CListBoxEx() { } BEGIN_MESSAGE_MAP(CListBoxEx, CListBox) ON_WM_VKEYTOITEM_REFLECT() ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText) ON_WM_PAINT() END_MESSAGE_MAP() // CListBoxEx message handlers void CListBoxEx::GetVisibleRange(int& s, int& e) { auto maxIndex = this->GetCount() - 1; e = s = GetTopIndex(); RECT rect; this->GetClientRect(&rect); auto height = rect.bottom - rect.top; auto currentHeight = 0; while(e < maxIndex) { currentHeight += GetItemHeight(e); if (currentHeight >= height) break; e++; } } bool CListBoxEx::ItemIsVisible(int nIndex) { int s, e; GetVisibleRange(s, e); return s <= nIndex && nIndex <= e; } bool CListBoxEx::RedrawIfVisible(int i) { RECT rectItem; if (this->GetItemRect(i, &rectItem) != LB_ERR) { if (rectItem.bottom < 0) return false; RECT rectCtrl; this->GetClientRect(&rectCtrl); if (rectItem.top <= rectCtrl.bottom - rectCtrl.top) { this->InvalidateRect(&rectItem, FALSE); return true; } } return false; } // 4MB Buffer #define BUF_SIZE (1024*1024*4) void CListBoxEx::ProcessFiles(f_proc_file_callback cb, void* extra) { std::lock_guard guard(mutex); m_bStop = false; auto c = GetCount(); char* buffer = new char[BUF_SIZE]; Hasher hash(CALG_MD5); for(auto i = 0; i < c; i++) { auto data = reinterpret_cast(GetItemData(i)); if (data->Done()) { cb(ProcType::INC_FILE, 0, data, extra); continue; } auto path(*data->m_pDirectory + _T("\\") + *data->m_pFilename); std::ifstream is(path, std::ifstream::binary); if (!is) { cb(ProcType::ERR_FILE, 0, nullptr, extra); is.close(); break; } is.read(buffer, BD_FILE_HEADER_SIZE); hash.Init(); hash.Feed(buffer, int(is.gcount())); data->m_pFirstHash = hash.GetHashStr(); // 已经读完了? if (is.eof()) { is.close(); // 更新完整哈希 data->m_pFullHash = new CString(*data->m_pFirstHash); // 重绘 + 通知 this->RedrawIfVisible(i); cb(ProcType::INC_FILE, 0, data, extra); continue; } is.seekg(0, SEEK_SET); hash.Init(); double step = double(BUF_SIZE) / data->m_nSize; double prog = 0; do { if (m_bStop) { is.close(); delete[] buffer; return; } is.read(buffer, BUF_SIZE); auto eof = is.eof(); if (!eof && is.fail()) { #ifdef _DEBUG CString str; str.Format(_T("state: %x (goodbit: 0, eof: 1, fail: 2, badbit: 4)\n"), is.rdstate()); OutputDebugString(str); #endif cb(ProcType::ERR_FILE, 0, nullptr, extra); break; } hash.Feed(buffer, eof ? int(is.gcount()) : BUF_SIZE); if (eof) { data->m_pFullHash = hash.GetHashStr(); if (this->RedrawIfVisible(i)) { #ifdef _DEBUG OutputDebugString(_T("Redraw item.\n")); #endif } cb(ProcType::INC_FILE, 0, data, extra); break; } prog += step; cb(ProcType::FILE_PROG, prog, data, extra); } while (true); is.close(); } delete[] buffer; } void CListBoxEx::StopProcessing() { m_bStop = true; { std::lock_guard guard(mutex); } } void CListBoxEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { uint32_t dpi = DPISupport::GetWindowDPI(GetSafeHwnd()); auto dpi_scale = static_cast(dpi) / 96.0; auto icon_height = static_cast(dpi_scale * icon_base_dimention); auto icon_top_margin = static_cast(dpi_scale * 8); lpMeasureItemStruct->itemHeight = icon_height + icon_top_margin * 2; } void CListBoxEx::DrawItemData(LPDRAWITEMSTRUCT lpDrawItemStruct, CFileItem* pItem) { uint32_t dpi = DPISupport::GetWindowDPI(GetSafeHwnd()); auto dpi_scale = static_cast(dpi) / 96.0; auto icon_width = static_cast(dpi_scale * icon_base_dimention); auto icon_height = static_cast(dpi_scale * icon_base_dimention); auto icon_tick_width = static_cast(dpi_scale * 16.0); auto icon_tick_height = static_cast(dpi_scale * 16.0); auto icon_right_margin = static_cast(dpi_scale * 8.0); auto item_padding = static_cast(dpi_scale * 4.0); auto text_line_margin = static_cast(dpi_scale * 2.0); auto pDC = CDC::FromHandle(lpDrawItemStruct->hDC); pDC->SetBkMode(TRANSPARENT); CRect rectItem(lpDrawItemStruct->rcItem); rectItem.DeflateRect(item_padding, item_padding, item_padding, item_padding); CRect rectIcon(rectItem.left, rectItem.top, rectItem.left + icon_width, rectItem.top + icon_height); CRect rectIconTick( rectIcon.left, rectIcon.top + icon_height - icon_tick_height, rectIcon.left + icon_tick_width, rectItem.top + icon_height - icon_tick_height ); CRect rectText(rectIcon.right + icon_right_margin, rectItem.top, rectItem.right - icon_right_margin * 2, rectItem.bottom); auto action = lpDrawItemStruct->itemAction; auto state = lpDrawItemStruct->itemState; bool selected = (state & ODS_SELECTED) && (action & (ODA_SELECT | ODA_DRAWENTIRE)); bool redraw = (action & ODA_DRAWENTIRE) || (!(state & ODS_SELECTED) && (action & ODA_SELECT)); auto textColour = selected ? m_textSelected : m_text; auto descTextColour = selected ? m_descTextSelected : m_descText; CBrush brushBackground(selected ? m_bgSelected : m_bgClear); CBrush brushText(textColour); CBrush brushDescText(descTextColour); if (selected || redraw) { CString str; pDC->FillRect(&lpDrawItemStruct->rcItem, &brushBackground); pDC->DrawIcon(rectIcon.left, rectIcon.top, pItem->m_hIcon); if (pItem->m_pFullHash) { DrawIconEx(*pDC, rectIconTick.left, rectIconTick.top, m_hIconTick, icon_tick_width, icon_tick_height, NULL, nullptr, DI_NORMAL); } auto rect = rectText; pDC->SetTextColor(textColour); rect.OffsetRect(text_line_margin, 0); str.SetString(*pItem->m_pFilename); pDC->DrawText(str, str.GetLength(), rect, DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS); rect.OffsetRect(0, text_line_margin + pDC->GetTextExtent(str).cy); str.Format(IDS_DIR, *pItem->m_pDirectory); pDC->SetTextColor(descTextColour); pDC->DrawText(str, str.GetLength(), rect, DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS); rect.OffsetRect(0, text_line_margin + pDC->GetTextExtent(str).cy); str.Format(IDS_FILE_SIZE, pItem->GetSizeString()); pDC->DrawText(str, str.GetLength(), rect, DT_LEFT | DT_SINGLELINE); rect.OffsetRect(0, text_line_margin + pDC->GetTextExtent(str).cy); } } void CListBoxEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { auto pData = GetItemDataPtr(lpDrawItemStruct->itemID); if (pData != (void*)-1) { this->DrawItemData(lpDrawItemStruct, reinterpret_cast(pData)); } } uint64_t FileSize(const wchar_t* name) { auto hFile = CreateFileW( name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr ); if (hFile == INVALID_HANDLE_VALUE) { return 0; // could not open file } LARGE_INTEGER fs_large = {0}; if (!GetFileSizeEx(hFile, &fs_large)) { // get file size failed fs_large.QuadPart = 0; } CloseHandle(hFile); return static_cast(fs_large.QuadPart); } int CListBoxEx::AddItem(const CString& srcDir, const CString& filename) { std::lock_guard guard(this->mutex); auto fullPath(srcDir + _T("\\") + filename); auto fSize = FileSize(fullPath); auto index = this->AddString(_T("")); auto data = new CFileItem(); data->m_pDirectory = new CString(srcDir); data->m_pFilename = new CString(filename); SHFILEINFO shfi = {}; SHGetFileInfo(fullPath, FILE_ATTRIBUTE_NORMAL, &shfi, sizeof(SHFILEINFO), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SHELLICONSIZE); data->m_hIcon = shfi.hIcon; data->m_nSize = fSize; SetItemData(index, DWORD_PTR(data)); return 0; } void CListBoxEx::CopySelectedHashes() { CString str; for (int i = 0; i < GetCount(); i++) { if (GetSel(i)) { str.AppendFormat(L"%s\r\n", reinterpret_cast(GetItemData(i))->DownloadCode().GetString()); } } CopyStringToClipboard(str); } int CListBoxEx::VKeyToItem(UINT nKey, UINT nIndex) { // Returns –2 for no further action, // –1 for default action, or // a nonnegative number to specify an index of a // list box item on which to perform the default action for the keystroke. if(mutex.try_lock()) { switch (nKey) { case 'E': for (int i = 0; i < GetCount(); i++) SetSel(i, false); mutex.unlock(); return -2; case 'C': this->CopySelectedHashes(); mutex.unlock(); return -2; case 'D': case VK_DELETE: for (int i = GetCount() - 1; i >= 0; i--) { if (GetSel(i)) { DeleteString(i); SetSel(i, true); } } mutex.unlock(); return -2; } mutex.unlock(); return -1; } return -1; } void CListBoxEx::PreSubclassWindow() { CListBox::PreSubclassWindow(); EnableToolTips(TRUE); } INT_PTR CListBoxEx::OnToolHitTest(CPoint point, TOOLINFO* pTI) const { RECT itemRect; BOOL isOutside = FALSE; int row = ItemFromPoint(point, isOutside); if (row == -1 || isOutside) return -1; GetItemRect(row, &itemRect); pTI->rect = itemRect; pTI->hwnd = m_hWnd; pTI->uId = row; pTI->lpszText = LPSTR_TEXTCALLBACK; return pTI->uId; } BOOL CListBoxEx::OnToolTipText(UINT id, NMHDR * pNMHDR, LRESULT * pResult) { auto pToolTip = AfxGetModuleThreadState()->m_pToolTip; if (pToolTip) pToolTip->SetMaxTipWidth(SHRT_MAX); auto ptText = reinterpret_cast(pNMHDR); auto pItem = reinterpret_cast(this->GetItemData(static_cast(pNMHDR->idFrom))); CString str; str.Format(IDS_FILE_INFO, *pItem->m_pFilename, *pItem->m_pDirectory, pItem->GetSizeString()); auto strW = static_cast(str); auto memSize = str.GetLength() * sizeof TCHAR + (2 * sizeof TCHAR); auto lpszText = (TCHAR*)calloc(memSize, 1); if (!lpszText) { return FALSE; } memcpy(lpszText, strW, memSize); ptText->lpszText = lpszText; *pResult = 0; return TRUE; } void CListBoxEx::OnPaint() { if (GetCount() == 0) { CPaintDC dc(this); CRect rect; this->GetClientRect(rect); dc.SetTextColor(m_descText); dc.SelectObject(m_systemFont); dc.DrawText(m_sPlaceholder, m_sPlaceholder.GetLength(), rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); } else { Default(); } } ================================================ FILE: generator/ListBoxEx.h ================================================ #pragma once #include #include "FileItem.h" // CListBoxEx enum class ProcType { FILE_PROG, INC_FILE, ERR_FILE }; typedef void(*f_proc_file_callback) (ProcType type, double progress, CFileItem* pItem, void* extra); class CListBoxEx : public CListBox { DECLARE_DYNAMIC(CListBoxEx) private: std::mutex mutex; static HICON m_hIconTick; static CString m_sPlaceholder; static HFONT m_systemFont; bool m_bStop = false; public: CListBoxEx(); virtual ~CListBoxEx(); COLORREF m_bgSelected = RGB(204, 232, 255); // #cce8ff COLORREF m_bgClear = RGB(255, 255, 255); COLORREF m_text = RGB(0, 0, 0); COLORREF m_textSelected = RGB(0, 0, 0); COLORREF m_descText = RGB(109, 109, 109); COLORREF m_descTextSelected = RGB(109, 109, 109); protected: DECLARE_MESSAGE_MAP() public: void GetVisibleRange(int& s, int& e); bool ItemIsVisible(int nIndex); bool RedrawIfVisible(int i); void ProcessFiles(f_proc_file_callback cb, void* extra); void StopProcessing(); afx_msg void OnPaint(); virtual void MeasureItem(LPMEASUREITEMSTRUCT /*lpMeasureItemStruct*/); virtual void DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/); virtual void PreSubclassWindow(); void DrawItemData(LPDRAWITEMSTRUCT lpDrawItemStruct, CFileItem* pItem); int AddItem(const CString& srcDir, const CString& filename); void CopySelectedHashes(); int VKeyToItem(UINT /*nKey*/, UINT /*nIndex*/); virtual INT_PTR OnToolHitTest(CPoint point, TOOLINFO* pTI) const; BOOL OnToolTipText(UINT id, NMHDR* pNMHDR, LRESULT* pResult); }; ================================================ FILE: generator/ProgressText.cpp ================================================ // ProgressText.cpp : implementation file // #include "stdafx.h" #include "generator.h" #include "ProgressText.h" #include "DPISupport.h" void inline shrink(CRect* rect) { rect->left++; rect->top++; rect->right--; rect->bottom--; } // CProgressText IMPLEMENT_DYNAMIC(CProgressText, CProgressCtrl) CProgressText::CProgressText() { Init(); } CProgressText::~CProgressText() { SAFE_DELETE(text); } void CProgressText::Init() { text = new CString(""); progressForegroundBrush = new CBrush(progressForegroundColour); progressBackgroundBrush = new CBrush(progressBackgroundColour); componentBorderBrush = new CBrush(componentBorderColour); font = new CFont(); } void CProgressText::SetCurrent(uint current) { this->current = current; this->SetPos(this->current); this->UpdateText(); } void CProgressText::SetMax(uint max) { this->max = max; this->SetRange32(0, max); this->UpdateText(); } void CProgressText::Increase() { this->current++; this->SetPos(this->current); this->UpdateText(); } void CProgressText::Reset() { this->current = 0; this->max = 0; this->SetPos(0); this->UpdateText(); this->RedrawWindow(); } void CProgressText::UpdateText() { if (this->max == 0) { this->percent = 0; this->text->SetString(_T("")); } else { this->percent = double(this->current) / double(this->max); this->text->Format(_T("%d / %d (%.2f%%)"), this->current, this->max, this->percent * 100); } } BEGIN_MESSAGE_MAP(CProgressText, CProgressCtrl) ON_WM_PAINT() ON_WM_ERASEBKGND() END_MESSAGE_MAP() // CProgressText message handlers void CProgressText::OnPaint() { CPaintDC dc(this); // device context for painting // Do not call CProgressCtrl::OnPaint() for painting messages double dpi_scale = double(DPISupport::GetWindowDPI(GetSafeHwnd())) / 96.0; CRect rect; GetClientRect(&rect); CDC memDC; memDC.CreateCompatibleDC(&dc); memDC.SetMapMode(dc.GetMapMode()); memDC.SetViewportOrg(dc.GetViewportOrg()); memDC.IntersectClipRect(rect); CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height()); auto pOldBmp = memDC.SelectObject(&bmp); memDC.SetBkMode(TRANSPARENT); // 绘制一圈边框 memDC.SelectObject(GetStockObject(DC_PEN)); memDC.SetDCPenColor(this->componentBorderColour); memDC.Rectangle(rect); // 绘制四方形 { auto rectProgress = rect; shrink(&rectProgress); memDC.FillRect(rectProgress, this->progressBackgroundBrush); rectProgress.right = rectProgress.left + LONG(rectProgress.Width() * this->percent); memDC.FillRect(rectProgress, this->progressForegroundBrush); } auto font_height = static_cast((rect.Height() - 4) * dpi_scale * 0.75); font->DeleteObject(); VERIFY(font->CreateFont( font_height, // nHeight 0, // nWidth 0, // nEscapement 0, // nOrientation FW_NORMAL, // nWeight FALSE, // bItalic FALSE, // bUnderline 0, // cStrikeOut ANSI_CHARSET, // nCharSet OUT_DEFAULT_PRECIS, // nOutPrecision CLIP_DEFAULT_PRECIS, // nClipPrecision DEFAULT_QUALITY, // nQuality DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily _T("Consolas"))); // lpszFacename auto def_font = memDC.SelectObject(font); auto size = memDC.GetTextExtent(*text); auto x = rect.right - size.cx - 8; auto y = (rect.Height() - size.cy) / 2; memDC.TextOut(x, y, *text, this->text->GetLength()); memDC.SelectObject(def_font); // 拷贝图片 dc.BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &memDC, rect.left, rect.top, SRCCOPY); // 还原选择的对象 memDC.SelectObject(pOldBmp); } // ReSharper disable once CppMemberFunctionMayBeStatic // ReSharper disable once CppMemberFunctionMayBeConst BOOL CProgressText::OnEraseBkgnd(CDC* pDC) { return TRUE; } ================================================ FILE: generator/ProgressText.h ================================================ #pragma once // CProgressText class CProgressText : public CProgressCtrl { CFont* font; COLORREF progressForegroundColour = RGB(62, 231, 152); COLORREF progressBackgroundColour = RGB(0xE6, 0xE6, 0xE6); COLORREF componentBorderColour = RGB(0xBC, 0xBC, 0xBC); CBrush* progressForegroundBrush; CBrush* progressBackgroundBrush; CBrush* componentBorderBrush; void Init(); DECLARE_DYNAMIC(CProgressText) uint current = 0; uint max = 0; CString* text; double percent = 0; public: CProgressText(); virtual ~CProgressText(); void SetCurrent(uint current); void SetMax(uint max); void Increase(); void Reset(); protected: DECLARE_MESSAGE_MAP() void UpdateText(); public: afx_msg void OnPaint(); afx_msg BOOL OnEraseBkgnd(CDC* pDC); }; ================================================ FILE: generator/ReadMe.txt ================================================ ================================================================================ MICROSOFT FOUNDATION CLASS LIBRARY : generator Project Overview =============================================================================== The application wizard has created this generator application for you. This application not only demonstrates the basics of using the Microsoft Foundation Classes but is also a starting point for writing your application. This file contains a summary of what you will find in each of the files that make up your generator application. generator.vcxproj This is the main project file for VC++ projects generated using an application wizard. It contains information about the version of Visual C++ that generated the file, and information about the platforms, configurations, and project features selected with the application wizard. generator.vcxproj.filters This is the filters file for VC++ projects generated using an Application Wizard. It contains information about the assciation between the files in your project and the filters. This association is used in the IDE to show grouping of files with similar extensions under a specific node (for e.g. ".cpp" files are associated with the "Source Files" filter). generator.h This is the main header file for the application. It includes other project specific headers (including Resource.h) and declares the CApp application class. generator.cpp This is the main application source file that contains the application class CApp. generator.rc This is a listing of all of the Microsoft Windows resources that the program uses. It includes the icons, bitmaps, and cursors that are stored in the RES subdirectory. This file can be directly edited in Microsoft Visual C++. Your project resources are in 1033. res\generator.ico This is an icon file, which is used as the application's icon. This icon is included by the main resource file generator.rc. res\generator.rc2 This file contains resources that are not edited by Microsoft Visual C++. You should place all resources not editable by the resource editor in this file. ///////////////////////////////////////////////////////////////////////////// The application wizard creates one dialog class: appDlg.h, appDlg.cpp - the dialog These files contain your CAppDlg class. This class defines the behavior of your application's main dialog. The dialog's template is in generator.rc, which can be edited in Microsoft Visual C++. ///////////////////////////////////////////////////////////////////////////// Help Support: hlp\generator.hhp This file is a help project file. It contains the data needed to compile the help files into a .chm file. hlp\generator.hhc This file lists the contents of the help project. hlp\generator.hhk This file contains an index of the help topics. hlp\afxcore.htm This file contains the standard help topics for standard MFC commands and screen objects. Add your own help topics to this file. makehtmlhelp.bat This file is used by the build system to compile the help files. hlp\Images\*.gif These are bitmap files required by the standard help file topics for Microsoft Foundation Class Library standard commands. ///////////////////////////////////////////////////////////////////////////// Other standard files: StdAfx.h, StdAfx.cpp These files are used to build a precompiled header (PCH) file named generator.pch and a precompiled types file named StdAfx.obj. Resource.h This is the standard header file, which defines new resource IDs. Microsoft Visual C++ reads and updates this file. generator.manifest Application manifest files are used by Windows XP to describe an applications dependency on specific versions of Side-by-Side assemblies. The loader uses this information to load the appropriate assembly from the assembly cache or private from the application. The Application manifest maybe included for redistribution as an external .manifest file that is installed in the same folder as the application executable or it may be included in the executable in the form of a resource. ///////////////////////////////////////////////////////////////////////////// Other notes: The application wizard uses "TODO:" to indicate parts of the source code you should add to or customize. If your application uses MFC in a shared DLL, you will need to redistribute the MFC DLLs. If your application is in a language other than the operating system's locale, you will also have to redistribute the corresponding localized resources MFC100XXX.DLL. For more information on both of these topics, please see the section on redistributing Visual C++ applications in MSDN documentation. ///////////////////////////////////////////////////////////////////////////// ================================================ FILE: generator/appDlg.cpp ================================================  // appDlg.cpp : implementation file // #include "stdafx.h" #include "generator.h" #include "appDlg.h" #include "afxdialogex.h" #include "utils.h" #include "base64.h" #include "ClipboardHelper.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CAboutDlg dialog used for App About class CAboutDlg : public CDialogEx { public: CAboutDlg(); // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_ABOUTBOX }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: DECLARE_MESSAGE_MAP() public: // void AddFile(CString& srcDir, CString& filename); }; CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) END_MESSAGE_MAP() // CAppDlg dialog CAppDlg::CAppDlg(CWnd* pParent /*=NULL*/) : CDialogEx(IDD_GENERATOR_DIALOG, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CAppDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_PROG_FILE, m_progFile); DDX_Control(pDX, IDC_PROG_ALL, m_progAll); DDX_Control(pDX, IDC_LIST_FILES, m_listFiles); DDX_Control(pDX, IDC_EDIT_OUTPUT, m_editOutput); DDX_Control(pDX, IDC_CHK_RECURSIVE, m_chkRecursive); DDX_Control(pDX, IDC_CHK_URL, m_chkUrl); DDX_Control(pDX, IDC_SYSLINK_BLOG, m_linkBlog); DDX_Control(pDX, IDC_BTN_ADD_DIR, m_btnAddDir); DDX_Control(pDX, IDC_BTN_ADD_FILE, m_btnAddFile); DDX_Control(pDX, IDC_BTN_CLEAR, m_btnClear); DDX_Control(pDX, IDC_BTN_COPY, m_btnCopy); DDX_Control(pDX, IDC_BTN_GENERATE, m_btnGenerate); } // NM_CLICK = -2 constexpr UINT NM_CLICK_WITHOUT_WARNING = UINT(-2); BEGIN_MESSAGE_MAP(CAppDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BTN_GENERATE, &CAppDlg::OnBnClickedGenerate) ON_BN_CLICKED(IDC_BTN_ADD_DIR, &CAppDlg::OnClickedBtnAddDir) ON_BN_CLICKED(IDC_BTN_ADD_FILE, &CAppDlg::OnClickedBtnAddFile) ON_BN_CLICKED(IDC_BTN_CLEAR, &CAppDlg::OnBnClickedBtnClear) ON_WM_DROPFILES() ON_NOTIFY(NM_CLICK_WITHOUT_WARNING, IDC_SYSLINK_BLOG, &CAppDlg::OnClickSyslinkBlog) ON_BN_CLICKED(IDC_BTN_COPY, &CAppDlg::OnBnClickedBtnCopy) END_MESSAGE_MAP() // CAppDlg message handlers BOOL CAppDlg::OnInitDialog() { CDialogEx::OnInitDialog(); AppendVersionNumber(); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon CRegKey reg; if (reg.Open(HKEY_CURRENT_USER, _T("Software\\Jixun.Moe\\DuGenerator"), KEY_READ) == ERROR_SUCCESS) { DWORD dwLang; if (reg.QueryDWORDValue(_T("LANG_ID"), dwLang) == ERROR_SUCCESS) { SetThreadUILanguage(LANGID(dwLang)); } reg.Close(); } std::ignore = m_ofn_file_title.LoadString(IDS_PICK_FILE); std::ignore = m_ofn_dir_title.LoadString(IDS_PICK_DIR); m_progFile.SetRange(0, 10000); // 初始化目录选择 std::ignore = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); m_editOutput.SetLimitText(0); #if _DEBUG { char buffer[32]; sprintf_s(buffer, sizeof(buffer), "text limit: 0x%08x\n", m_editOutput.GetLimitText()); OutputDebugStringA(buffer); } #endif return TRUE; // return TRUE unless you set the focus to a control } void CAppDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialogEx::OnSysCommand(nID, lParam); } } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CAppDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } // The system calls this function to obtain the cursor to display while the user drags // the minimized window. HCURSOR CAppDlg::OnQueryDragIcon() { return static_cast(m_hIcon); } void WINAPI _thread_process_file(CAppDlg* app) { app->ProcessFiles(); } void CAppDlg::OnBnClickedGenerate() { CreateThread(nullptr, 0, LPTHREAD_START_ROUTINE(_thread_process_file), this, 0, nullptr); } void cb_add_file(const CString &srcDir, const CString &filename, void* extra) { static_cast(extra)->AddFile(srcDir, filename); } void CAppDlg::OnClickedBtnAddDir() { auto dirs = OpenDirectoryDialog(m_ofn_dir_title, GetSafeHwnd()); for(auto& dir : dirs) { EnumFiles(*dir, BST_CHECKED == m_chkRecursive.GetCheck(), cb_add_file, this); delete dir; } } void CAppDlg::OnClickedBtnAddFile() { auto v = OpenFileDialog(this->m_ofn_file_title, GetSafeHwnd()); for(auto file : v) { CString path(*file); auto pos = path.ReverseFind(_T('\\')); m_listFiles.AddItem(path.Left(pos), path.Right(path.GetLength() - pos - 1)); delete file; } } void CAppDlg::AddFile(const CString& srcDir, const CString& filename) { m_listFiles.AddItem(srcDir, filename); } void _proc_file_callback(ProcType type, double progress, CFileItem* lpItem, void* extra) { static_cast(extra)->ProcFile(type, progress, lpItem); } void CAppDlg::ProcessFiles() { std::lock_guard guard(mutex); OutputDebugString(_T("Begin read file...")); m_progAll.SetMax(m_listFiles.GetCount()); m_progAll.SetCurrent(0); m_progFile.SetPos(0); m_btnAddDir.EnableWindow(FALSE); m_btnAddFile.EnableWindow(FALSE); m_btnCopy.EnableWindow(FALSE); m_btnClear.EnableWindow(FALSE); m_btnGenerate.EnableWindow(FALSE); m_listFiles.ProcessFiles(_proc_file_callback, this); m_btnAddDir.EnableWindow(TRUE); m_btnAddFile.EnableWindow(TRUE); m_btnCopy.EnableWindow(TRUE); m_btnClear.EnableWindow(TRUE); m_btnGenerate.EnableWindow(TRUE); } void CAppDlg::AddHashEntry(CFileItem* data) { if (m_editOutput.GetWindowTextLengthW() > 0) { m_editOutput.Append(_T("\r\n")); } if (m_chkUrl.GetCheck() == BST_CHECKED) { m_editOutput.Append(data->BDLink()); } else { m_editOutput.Append(data->DownloadCode()); } } void CAppDlg::ProcFile(ProcType proc, double progress, CFileItem* lp_item) { switch(proc) { case ProcType::FILE_PROG: m_progFile.SetPos(int(progress * 10000)); break; case ProcType::INC_FILE: m_progFile.SetPos(0); m_progAll.Increase(); AddHashEntry(lp_item); break; case ProcType::ERR_FILE: OutputDebugString(_T("Failed to process file.\n")); m_progAll.Increase(); break; default: ; } } void CAppDlg::RealExit() { m_listFiles.StopProcessing(); // TODO: Clean up. CDialogEx::OnCancel(); } void CAppDlg::AppendVersionNumber() { CString csEntry; HMODULE hLib = AfxGetResourceHandle(); HRSRC hVersion = FindResource(hLib, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION); if (hVersion == nullptr) return; HGLOBAL hGlobal = LoadResource(hLib, hVersion); if (hGlobal == nullptr) { FreeResource(hVersion); return; } LPVOID versionInfo = LockResource(hGlobal); if (versionInfo == nullptr) { UnlockResource(hGlobal); FreeResource(hVersion); return; } LPVOID lpBuffer = nullptr; UINT dwCharCount = 0; if (VerQueryValue(versionInfo, L"\\StringFileInfo\\080004B0\\FileVersion", &lpBuffer, &dwCharCount)) { CString strTitle; GetWindowText(strTitle); auto strVersionString = new TCHAR[dwCharCount + 1]; memcpy(strVersionString, lpBuffer, dwCharCount * sizeof(TCHAR)); strVersionString[dwCharCount] = 0; auto pLastDot = _tcsrchr(strVersionString, _T('.')); if (pLastDot != nullptr) { *pLastDot = 0; } strTitle += _T(" (v"); strTitle += strVersionString; strTitle += _T(")"); SetWindowText(strTitle); delete[] strVersionString; } UnlockResource(hGlobal); FreeResource(hVersion); } DWORD WINAPI _thread_stop_process(void* param) { static_cast(param)->RealExit(); return 0; } void CAppDlg::OnCancel() { // 若为 ESC 导致的关闭窗口,无视它。 if ((GetKeyState(VK_ESCAPE) & 0x8000) != 0) { return; } CreateThread(nullptr, 0, _thread_stop_process, this, 0, nullptr); } void CAppDlg::OnBnClickedBtnClear() { m_listFiles.ResetContent(); m_progAll.Reset(); m_editOutput.SetWindowText(_T("")); } void CAppDlg::OnDropFiles(HDROP hDropInfo) { CString path; int nFilesDropped = DragQueryFile(hDropInfo, 0xFFFFFFFF, nullptr, 0); for (int i = 0; iitem.szUrl, nullptr, nullptr, SW_SHOWNORMAL); *pResult = 0; } // ReSharper disable once CppMemberFunctionMayBeConst void CAppDlg::OnBnClickedBtnCopy() { CString str; m_editOutput.GetWindowText(str); CopyStringToClipboard(str, GetSafeHwnd()); } ================================================ FILE: generator/appDlg.h ================================================ // appDlg.h : header file // #pragma once #include "ProgressText.h" #include "AdvEdit.h" #include "ListBoxEx.h" #include "afxwin.h" #include "afxcmn.h" // CAppDlg dialog class CAppDlg : public CDialogEx { CString m_ofn_file_title; CString m_ofn_dir_title; std::mutex mutex; // Construction public: CAppDlg(CWnd* pParent = NULL); // standard constructor void AddFile(const CString& srcDir, const CString& filename); void ProcessFiles(); void AddHashEntry(CFileItem* lp_item); void ProcFile(ProcType proc, double progress, CFileItem* lp_item); void RealExit(); void AppendVersionNumber(); std::vector files; // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_GENERATOR_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedGenerate(); // Current file Progress CProgressCtrl m_progFile; CProgressText m_progAll; CListBoxEx m_listFiles; CAdvEdit m_editOutput; afx_msg void OnClickedBtnAddDir(); afx_msg void OnClickedBtnAddFile(); virtual void OnCancel(); afx_msg void OnBnClickedBtnClear(); afx_msg void OnDropFiles(HDROP hDropInfo); CButton m_chkRecursive; CButton m_chkUrl; CLinkCtrl m_linkBlog; afx_msg void OnClickSyslinkBlog(NMHDR *pNMHDR, LRESULT *pResult); afx_msg void OnBnClickedBtnCopy(); CButton m_btnAddDir; CButton m_btnAddFile; CButton m_btnClear; CButton m_btnCopy; CButton m_btnGenerate; }; ================================================ FILE: generator/base64.cpp ================================================ /* * Base64 encoding/decoding (RFC1341) * Copyright (c) 2005-2011, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. */ // Change log: // 2016-12-12 - Gaspard Petit : Slightly modified to return a std::string // instead of a buffer allocated with malloc. // 2020-12-08 - Jixun: Taken from StackOverflow and adapted to project. // source: https://stackoverflow.com/a/41094722 #include "stdafx.h" #include "base64.h" static const wchar_t base64_table[] = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /** * base64_encode - Base64 encode * @src: Data to be encoded * @len: Length of the data to be encoded * @out_len: Pointer to output length variable, or %NULL if not used * Returns: Allocated buffer of out_len bytes of encoded data, * or empty string on failure */ wchar_t* base64_encode(const unsigned char* src, size_t len) { const unsigned char* end, * in; size_t olen; olen = 4 * ((len + 2) / 3); /* 3-byte blocks to 4-byte */ if (olen < len) return nullptr; wchar_t* out = (wchar_t*)calloc(olen + 1, sizeof(wchar_t)); if (out == nullptr) { return nullptr; } end = src + len; in = src; auto pos = out; while (end - in >= 3) { *pos++ = base64_table[in[0] >> 2]; *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; *pos++ = base64_table[in[2] & 0x3f]; in += 3; } if (end - in) { *pos++ = base64_table[in[0] >> 2]; if (end - in == 1) { *pos++ = base64_table[(in[0] & 0x03) << 4]; *pos++ = L'='; } else { *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = base64_table[(in[1] & 0x0f) << 2]; } *pos++ = L'='; } return out; } ================================================ FILE: generator/base64.h ================================================ #pragma once #include wchar_t* base64_encode(const unsigned char* src, size_t len); ================================================ FILE: generator/encoding.cpp ================================================ #include "stdafx.h" #include "encoding.h" void ToUTF8(utf8_str& output, wchar_t* input) { size_t out_size = WideCharToMultiByte(CP_UTF8, 0, input, -1, nullptr, 0, NULL, NULL); output.str = (char*)calloc(out_size + 1, sizeof(char)); output.size = out_size; WideCharToMultiByte(CP_UTF8, 0, input, -1, output.str, static_cast(out_size), NULL, NULL); } ================================================ FILE: generator/encoding.h ================================================ #pragma once struct utf8_str { char* str; size_t size; }; void ToUTF8(utf8_str& output, wchar_t* input); ================================================ FILE: generator/generator.cpp ================================================ // generator.cpp : Defines the class behaviors for the application. // #include "stdafx.h" #include "generator.h" #include "appDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CApp BEGIN_MESSAGE_MAP(CApp, CWinApp) ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP() // CApp construction CApp::CApp() { // support Restart Manager m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART; // TODO: add construction code here, // Place all significant initialization in InitInstance } // The one and only CApp object CApp theApp; // CApp initialization BOOL CApp::InitInstance() { // InitCommonControlsEx() is required on Windows XP if an application // manifest specifies use of ComCtl32.dll version 6 or later to enable // visual styles. Otherwise, any window creation will fail. INITCOMMONCONTROLSEX InitCtrls; InitCtrls.dwSize = sizeof(InitCtrls); // Set this to include all the common control classes you want to use // in your application. InitCtrls.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&InitCtrls); CWinApp::InitInstance(); // Create the shell manager, in case the dialog contains // any shell tree view or shell list view controls. CShellManager *pShellManager = new CShellManager; // Activate "Windows Native" visual manager for enabling themes in MFC controls CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need // Change the registry key under which our settings are stored // TODO: You should modify this string to be something appropriate // such as the name of your company or organization SetRegistryKey(_T("Local AppWizard-Generated Applications")); CAppDlg dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle when the dialog is // dismissed with Cancel } else if (nResponse == -1) { TRACE(traceAppMsg, 0, "Warning: dialog creation failed, so application is terminating unexpectedly.\n"); TRACE(traceAppMsg, 0, "Warning: if you are using MFC controls on the dialog, you cannot #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS.\n"); } // Delete the shell manager created above. if (pShellManager != NULL) { delete pShellManager; } #if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS) ControlBarCleanUp(); #endif // Since the dialog has been closed, return FALSE so that we exit the // application, rather than start the application's message pump. return FALSE; } ================================================ FILE: generator/generator.h ================================================ // generator.h : main header file for the PROJECT_NAME application // #pragma once #ifndef __AFXWIN_H__ #error "include 'stdafx.h' before including this file for PCH" #endif #include "resource.h" // main symbols // CApp: // See generator.cpp for the implementation of this class // class CApp : public CWinApp { public: CApp(); // Overrides public: virtual BOOL InitInstance(); // Implementation DECLARE_MESSAGE_MAP() }; extern CApp theApp; ================================================ FILE: generator/generator.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 15.0 {52BE8E83-C47E-469F-92B7-F078C51C0EBD} generator MFCProj ducode 10.0 Application true v143 Unicode Static Application false v143 true Unicode Dynamic Application true v143 Unicode Static Application false v143 true Unicode Dynamic true $(SolutionDir)$(PlatformTarget)\$(Configuration)\ true $(SolutionDir)$(PlatformTarget)\$(Configuration)\ false $(SolutionDir)$(PlatformTarget)\$(Configuration)\ false $(SolutionDir)$(PlatformTarget)\$(Configuration)\ Use Level3 Disabled WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) true Windows Version.lib;%(AdditionalDependencies) false true _DEBUG;%(PreprocessorDefinitions) 0x0409 _DEBUG;%(PreprocessorDefinitions) $(IntDir);%(AdditionalIncludeDirectories) PerMonitorHighDPIAware Use Level3 Disabled _WINDOWS;_DEBUG;%(PreprocessorDefinitions) true Windows Version.lib;%(AdditionalDependencies) false true _DEBUG;%(PreprocessorDefinitions) 0x0409 _DEBUG;%(PreprocessorDefinitions) $(IntDir);%(AdditionalIncludeDirectories) PerMonitorHighDPIAware Level3 Use MaxSpeed true true WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) true MultiThreadedDLL Windows true true Version.lib;%(AdditionalDependencies) false true NDEBUG;%(PreprocessorDefinitions) 0x0409 NDEBUG;%(PreprocessorDefinitions) $(IntDir);%(AdditionalIncludeDirectories) PerMonitorHighDPIAware Level3 Use MaxSpeed true true _WINDOWS;NDEBUG;%(PreprocessorDefinitions) true MultiThreadedDLL Windows true true Version.lib;%(AdditionalDependencies) false true NDEBUG;%(PreprocessorDefinitions) 0x0409 NDEBUG;%(PreprocessorDefinitions) $(IntDir);%(AdditionalIncludeDirectories) PerMonitorHighDPIAware Create Create Create Create ================================================ FILE: generator/generator.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms {d8e86c5e-c09b-40e0-9359-225308efac4d} Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Components Components Components Header Files Header Files Components Components Components Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Resource Files Resource Files Resource Files Resource Files ================================================ FILE: generator/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by generator.rc // #define IDM_ABOUTBOX 0x0010 #define IDD_ABOUTBOX 100 #define IDS_ABOUTBOX 101 #define IDD_GENERATOR_DIALOG 102 #define IDS_PICK_FILE 102 #define IDS_PICK_DIR 103 #define IDS_DIR 104 #define IDS_FILE_SIZE 105 #define IDS_FILE_INFO 106 #define IDS_LIST_PLACEHOLDER 107 #define IDS_ERROR_ENCODE_FAIL 108 #define IDR_MAINFRAME 128 #define IDI_ICON_TICK 131 #define IDC_LIST_FILES 1000 #define IDC_EDIT_OUTPUT 1001 #define IDC_PROG_FILE 1002 #define IDC_PROG_ALL 1003 #define IDC_BTN_ADD_DIR 1004 #define IDC_BTN_ADD_FILE 1005 #define IDC_SYSLINK_BLOG 1006 #define IDC_LB_STATUS 1007 #define IDC_BTN_CLEAR 1008 #define IDC_CHK_RECURSIVE 1009 #define IDC_BTN_COPY 1010 #define IDC_GENERATE 1011 #define IDC_BTN_GENERATE 1011 #define IDC_CHK_URL 1012 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 132 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1013 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: generator/stdafx.cpp ================================================ // stdafx.cpp : source file that includes just the standard includes // generator.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" ================================================ FILE: generator/stdafx.h ================================================ // stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, // but are changed infrequently #pragma once #ifndef VC_EXTRALEAN #define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers #endif #include "targetver.h" #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit // turns off MFC's hiding of some common and often safely ignored warning messages #define _AFX_ALL_WARNINGS #include // MFC core and standard components #include // MFC extensions #ifndef _AFX_NO_OLE_SUPPORT #include // MFC support for Internet Explorer 4 Common Controls #endif #ifndef _AFX_NO_AFXCMN_SUPPORT #include // MFC support for Windows Common Controls #endif // _AFX_NO_AFXCMN_SUPPORT #include // MFC support for ribbons and control bars #ifdef _UNICODE #if defined _M_IX86 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") #elif defined _M_X64 #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") #else #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #endif #endif #ifndef uint #define uint unsigned int #endif #include #if _MSC_VER < 1930 // less than vs2022, use the full Windows.h header #include #else // Other: use a subset of the headers. #include #include #endif #include "afxcmn.h" #include "afxwin.h" #define SAFE_DELETE(a) \ if( (a) != nullptr ) { \ delete (a); \ } \ (a) = nullptr; ================================================ FILE: generator/targetver.h ================================================ #pragma once // Including SDKDDKVer.h defines the highest available Windows platform. // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. #include ================================================ FILE: generator/utils.cpp ================================================ #include "stdafx.h" #include "utils.h" std::vector OldStyleFileDialog(const CString& title, HWND hwnd) { std::vector v; CString str; OPENFILENAME ofn = {0}; ofn.lStructSize = sizeof ofn; ofn.lpstrTitle = title; ofn.hwndOwner = hwnd; ofn.lpstrFile = str.GetBuffer(1024); ofn.nMaxFile = 1024; if (GetOpenFileName(&ofn)) { v.push_back(new CString(str)); } str.ReleaseBuffer(); return v; } inline std::vector OldStyleDirectoryDialog(CString &title, HWND hWnd) { std::vector v; BROWSEINFO bi; ZeroMemory(&bi, sizeof bi); bi.hwndOwner = hWnd; // bi.lpfn = cb_set_initial; bi.lpszTitle = title; bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE; TCHAR szPath[1024]; if (SHGetPathFromIDList(SHBrowseForFolder(&bi), szPath)) { v.push_back(new CString(szPath)); } return v; } DWORD OpenFileDialog(CString &title, HWND hWnd, DWORD flag, std::vector &v) { IFileOpenDialog* pFileOpen; do { if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen)))) { return 1; } FILEOPENDIALOGOPTIONS fos; if (FAILED(pFileOpen->GetOptions(&fos))) break; fos |= flag | FOS_ALLOWMULTISELECT | FOS_FORCEFILESYSTEM; if (FAILED(pFileOpen->SetOptions(fos))) break; if (FAILED(pFileOpen->SetTitle(title))) break; if (FAILED(pFileOpen->Show(hWnd))) break; IShellItem* pShellItem; pFileOpen->GetResult(&pShellItem); IShellItemArray* pResults; if (FAILED(pFileOpen->GetResults(&pResults))) break; DWORD dwCount; if (FAILED(pResults->GetCount(&dwCount))) break; v.clear(); for (uint i = 0; i < dwCount; i++) { IShellItem* pItem; pResults->GetItemAt(i, &pItem); TCHAR* filePath; pItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &filePath); v.push_back(new CString(filePath)); CoTaskMemFree(filePath); pItem->Release(); } if (pFileOpen) pFileOpen->Release(); return 0; } while (false); if (pFileOpen) pFileOpen->Release(); return 0; } std::vector OpenDirectoryDialog(CString &title, HWND hWnd) { std::vector r; auto err = OpenFileDialog(title, hWnd, FOS_PICKFOLDERS, r); if (err == 1) return OldStyleDirectoryDialog(title, hWnd); return r; } std::vector OpenFileDialog(CString &title, HWND hWnd) { std::vector r; auto err = OpenFileDialog(title, hWnd, 0, r); if (err == 1) return OldStyleFileDialog(title, hWnd); return r; } void EnumFiles(const CString& srcDir, bool recursive, f_file_callback cb, void* extra) { auto dir(srcDir); if (dir.Right(1).Compare(_T("\\")) == 0) { dir.AppendChar(_T('*')); } else { dir.Append(_T("\\*")); } WIN32_FIND_DATA ffd; auto hFind = FindFirstFile(dir, &ffd); if (hFind == INVALID_HANDLE_VALUE) { return; } do { if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (recursive) { if (_tcscmp(ffd.cFileName, _T(".")) != 0 && _tcscmp(ffd.cFileName, _T("..")) != 0) { #if _DEBUG CString str; str.Format(_T("enter dir: %s\n"), ffd.cFileName); OutputDebugString(str); #endif EnumFiles(srcDir + _T("\\") + ffd.cFileName, recursive, cb, extra); } } } else { #if _DEBUG CString str; str.Format(_T("add file: %s\n"), ffd.cFileName); OutputDebugString(str); #endif cb(srcDir, ffd.cFileName, extra); } } while (FindNextFile(hFind, &ffd) != 0); FindClose(hFind); } ================================================ FILE: generator/utils.h ================================================ #pragma once std::vector OpenFileDialog(CString &title, HWND hWnd); std::vector OpenDirectoryDialog(CString &title, HWND hWnd); typedef void(*f_file_callback) (const CString &strDir, const CString &strName, void* extra); void EnumFiles(const CString& srcDir, bool recursive, f_file_callback cb, void* extra = nullptr); ================================================ FILE: mfcDuDownloadCodeGenerator.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32922.545 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ducode", "generator\generator.vcxproj", "{52BE8E83-C47E-469F-92B7-F078C51C0EBD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Debug|x64.ActiveCfg = Debug|x64 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Debug|x64.Build.0 = Debug|x64 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Debug|x86.ActiveCfg = Debug|Win32 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Debug|x86.Build.0 = Debug|Win32 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Release|x64.ActiveCfg = Release|x64 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Release|x64.Build.0 = Release|x64 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Release|x86.ActiveCfg = Release|Win32 {52BE8E83-C47E-469F-92B7-F078C51C0EBD}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BC2322A7-E814-48A2-93F3-2D9DF1A6A0A4} EndGlobalSection EndGlobal