├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── semantic.yml │ ├── add-to-project.yml │ └── ci.yml ├── .gitignore ├── .editorconfig ├── CMakeLists.txt ├── .releaserc.json ├── LICENSE ├── src ├── rcedit.rc ├── rescle.h ├── main.cc └── rescle.cc └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @electron/wg-ecosystem 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Default/ 2 | ipch/ 3 | build/ 4 | 5 | *.swp 6 | *.sdf 7 | *.opensdf 8 | *.suo 9 | *.vcxproj.user 10 | *.VC.db 11 | *.VC.VC.opendb 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded") 4 | 5 | # /Ox, full optimization 6 | # /Os, favour small code 7 | add_compile_options(/Ox /Os) 8 | 9 | project(rcedit) 10 | 11 | add_executable(rcedit src/main.cc src/rescle.cc src/rcedit.rc) 12 | target_link_libraries(rcedit version.lib) 13 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "semantic-release-export-data", 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | [ 7 | "@semantic-release/github", 8 | { 9 | "assets": [ 10 | { "path": "dist/rcedit-x64.exe" }, 11 | { "path": "dist/rcedit-x86.exe" } 12 | ], 13 | "draftRelease": true 14 | } 15 | ] 16 | ], 17 | "branches": [ "main" ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/semantic.yml: -------------------------------------------------------------------------------- 1 | name: "Check Semantic Commit" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | permissions: 16 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs 17 | statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR 18 | name: Validate PR Title 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: semantic-pull-request 22 | uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | validateSingleCommit: false 27 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Add to Ecosystem WG Project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request_target: 8 | types: 9 | - opened 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | add-to-project: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Generate GitHub App token 18 | uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1 19 | id: generate-token 20 | with: 21 | creds: ${{ secrets.ECOSYSTEM_ISSUE_TRIAGE_GH_APP_CREDS }} 22 | org: electron 23 | - name: Add to Project 24 | uses: dsanders11/project-actions/add-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0 25 | with: 26 | field: Opened 27 | field-value: ${{ github.event.pull_request.created_at || github.event.issue.created_at }} 28 | project-number: 89 29 | token: ${{ steps.generate-token.outputs.token }} 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/rcedit.rc: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////// 2 | // English (United States) resources 3 | 4 | #define APSTUDIO_READONLY_SYMBOLS 5 | ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Generated from the TEXTINCLUDE 2 resource. 8 | // 9 | #include 10 | 11 | ///////////////////////////////////////////////////////////////////////////// 12 | #undef APSTUDIO_READONLY_SYMBOLS 13 | 14 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 15 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 16 | 17 | ///////////////////////////////////////////////////////////////////////////// 18 | // 19 | // Version 20 | // 21 | 22 | VS_VERSION_INFO VERSIONINFO 23 | FILEVERSION 0,0,0,0 24 | PRODUCTVERSION 0,0,0,0 25 | FILEFLAGSMASK 0x3fL 26 | #ifdef _DEBUG 27 | FILEFLAGS 0x1L 28 | #else 29 | FILEFLAGS 0x0L 30 | #endif 31 | FILEOS 0x40004L 32 | FILETYPE 0x1L 33 | FILESUBTYPE 0x0L 34 | BEGIN 35 | BLOCK "StringFileInfo" 36 | BEGIN 37 | BLOCK "040904b0" 38 | BEGIN 39 | VALUE "CompanyName", "GitHub, Inc" 40 | VALUE "FileDescription", "Edit resources of exe" 41 | VALUE "FileVersion", "0.0.0" 42 | VALUE "LegalCopyright", "Copyright (C) 2013 GitHub, Inc. All rights reserved." 43 | VALUE "ProductName", "rcedit" 44 | VALUE "ProductVersion", "0.0.0" 45 | END 46 | END 47 | BLOCK "VarFileInfo" 48 | BEGIN 49 | VALUE "Translation", 0x409, 1200 50 | END 51 | END 52 | 53 | #endif // English (United States) resources 54 | ///////////////////////////////////////////////////////////////////////////// 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rcedit 2 | 3 | [![Continuous Integration](https://github.com/electron/rcedit/actions/workflows/ci.yml/badge.svg)](https://github.com/electron/rcedit/actions/workflows/ci.yml) 4 | 5 | Command line tool to edit resources of exe file on Windows. 6 | 7 | ## Executables 8 | 9 | Prebuilt binaries can be found in the [GitHub releases](https://github.com/electron/rcedit/releases). 10 | 11 | ## Building 12 | 13 | To build you need CMake 3.15+ and Visual Studio 2015 or above. 14 | 15 | 1. Clone the repository 16 | 2. Create a build directory: `cmake -E make_directory build` 17 | 3. Change to the build directory: `cd build` 18 | 4. Make the CMake project: `cmake ..` 19 | 5. Build: `cmake --build . --config RelWithDebInfo` 20 | 21 | ## Docs 22 | 23 | Show help: 24 | 25 | ```bash 26 | $ rcedit -h 27 | ``` 28 | 29 | Set version string: 30 | 31 | ```bash 32 | $ rcedit "path-to-exe-or-dll" --set-version-string "Comments" "This is an exe" 33 | ``` 34 | 35 | Use this option to change any supported properties, as described in the MSDN documentation [here](https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx) 36 | 37 | Set file version: 38 | 39 | ```bash 40 | $ rcedit "path-to-exe-or-dll" --set-file-version "10.7" 41 | ``` 42 | 43 | Set product version: 44 | 45 | ```bash 46 | $ rcedit "path-to-exe-or-dll" --set-product-version "10.7" 47 | ``` 48 | 49 | Set icon: 50 | 51 | ```bash 52 | $ rcedit "path-to-exe-or-dll" --set-icon "path-to-ico" 53 | ``` 54 | 55 | Set resource string: 56 | 57 | ```bash 58 | $ rcedit "path-to-exe-or-dll" --set-resource-string id_number "new string value" 59 | ``` 60 | 61 | Set [requested execution level](https://msdn.microsoft.com/en-us/library/6ad1fshk.aspx#Anchor_9) (`asInvoker` | `highestAvailable` | `requireAdministrator`) in the manifest: 62 | 63 | ```bash 64 | $ rcedit "path-to-exe-or-dll" --set-requested-execution-level "requireAdministrator" 65 | ``` 66 | 67 | Set [application manifest](https://msdn.microsoft.com/en-us/library/windows/desktop/aa374191.aspx): 68 | 69 | ```bash 70 | $ rcedit "path-to-exe-or-dll" --application-manifest ./path/to/manifest/file 71 | ``` 72 | 73 | And you can change multiple things in one command: 74 | 75 | ```bash 76 | $ rcedit "path-to-exe-or-dll" --set-icon "path-to-ico" --set-file-version "10.7" 77 | ``` 78 | 79 | Get version string: 80 | 81 | ```bash 82 | $ rcedit "path-to-exe-or-dll" --get-version-string "property" 83 | ``` 84 | 85 | Use the same properties as `--set-version-string`. Use `"FileVersion"` to get the results of `--set-file-version` and `"ProductVersion"` to get the results of `--get-product-version`. 86 | 87 | Get resource string: 88 | 89 | ```bash 90 | $ rcedit "path-to-exe-or-dll" --get-resource-string id_number 91 | ``` 92 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: windows-2022 18 | steps: 19 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 20 | with: 21 | fetch-depth: 1 22 | - name: Build 23 | run: | 24 | cmake -E make_directory build/x64 25 | cmake -E make_directory build/Win32 26 | cd build/x64 27 | cmake -A x64 ../../ 28 | cmake --build . --config RelWithDebInfo 29 | cd ../../build/Win32 30 | cmake -A Win32 ../../ 31 | cmake --build . --config RelWithDebInfo 32 | - name: Copy to dist 33 | run: | 34 | cmake -E make_directory dist 35 | cmake -E copy build/x64/RelWithDebInfo/rcedit.exe dist/rcedit-x64.exe 36 | cmake -E copy build/Win32/RelWithDebInfo/rcedit.exe dist/rcedit-x86.exe 37 | - name: Print help 38 | shell: bash 39 | run: | 40 | set -eo pipefail 41 | dist/rcedit-x86.exe -h 42 | dist/rcedit-x64.exe -h 43 | - name: Upload artifacts 44 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 45 | with: 46 | name: dist 47 | path: dist/ 48 | 49 | release: 50 | name: Release 51 | runs-on: windows-2022 52 | needs: build 53 | if: github.ref == 'refs/heads/main' 54 | permissions: 55 | contents: write 56 | steps: 57 | - name: Checkout 58 | id: checkout 59 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 60 | - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 61 | with: 62 | name: dist 63 | path: dist/ 64 | - run: npm install -g semantic-release@22.0.6 semantic-release-export-data@v1.0.1 65 | - run: npx semantic-release@22.0.6 --dry-run 66 | id: get-next-version 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | - name: Stamp version 70 | if: steps.get-next-version.outputs.new-release-published == 'true' 71 | shell: bash 72 | run: | 73 | set -eo pipefail 74 | dist/rcedit-x64.exe dist/rcedit-x86.exe --set-product-version $VERSION --set-file-version $VERSION 75 | dist/rcedit-x86.exe -h | grep -q "Rcedit v$VERSION" 76 | dist/rcedit-x86.exe dist/rcedit-x64.exe --set-product-version $VERSION --set-file-version $VERSION 77 | dist/rcedit-x64.exe -h | grep -q "Rcedit v$VERSION" 78 | env: 79 | VERSION: ${{ steps.get-next-version.outputs.new-release-version }} 80 | - name: Run semantic release 81 | run: npx semantic-release@22.0.6 82 | if: steps.get-next-version.outputs.new-release-published == 'true' 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | -------------------------------------------------------------------------------- /src/rescle.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 GitHub, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT license that can be found in the 3 | // LICENSE file. 4 | // 5 | // This file is modified from Rescle written by yoshio.okumura@gmail.com: 6 | // http://code.google.com/p/rescle/ 7 | 8 | #ifndef VERSION_INFO_UPDATER 9 | #define VERSION_INFO_UPDATER 10 | 11 | #ifndef _UNICODE 12 | #define _UNICODE 13 | #endif 14 | 15 | #ifndef UNICODE 16 | #define UNICODE 17 | #endif 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include // unique_ptr 25 | 26 | #define RU_VS_COMMENTS L"Comments" 27 | #define RU_VS_COMPANY_NAME L"CompanyName" 28 | #define RU_VS_FILE_DESCRIPTION L"FileDescription" 29 | #define RU_VS_FILE_VERSION L"FileVersion" 30 | #define RU_VS_INTERNAL_NAME L"InternalName" 31 | #define RU_VS_LEGAL_COPYRIGHT L"LegalCopyright" 32 | #define RU_VS_LEGAL_TRADEMARKS L"LegalTrademarks" 33 | #define RU_VS_ORIGINAL_FILENAME L"OriginalFilename" 34 | #define RU_VS_PRIVATE_BUILD L"PrivateBuild" 35 | #define RU_VS_PRODUCT_NAME L"ProductName" 36 | #define RU_VS_PRODUCT_VERSION L"ProductVersion" 37 | #define RU_VS_SPECIAL_BUILD L"SpecialBuild" 38 | 39 | namespace rescle { 40 | 41 | struct IconsValue { 42 | typedef struct _ICONENTRY { 43 | BYTE width; 44 | BYTE height; 45 | BYTE colorCount; 46 | BYTE reserved; 47 | WORD planes; 48 | WORD bitCount; 49 | DWORD bytesInRes; 50 | DWORD imageOffset; 51 | } ICONENTRY; 52 | 53 | typedef struct _ICONHEADER { 54 | WORD reserved; 55 | WORD type; 56 | WORD count; 57 | std::vector entries; 58 | } ICONHEADER; 59 | 60 | ICONHEADER header; 61 | std::vector> images; 62 | std::vector grpHeader; 63 | }; 64 | 65 | struct Translate { 66 | LANGID wLanguage; 67 | WORD wCodePage; 68 | }; 69 | 70 | typedef std::pair VersionString; 71 | typedef std::pair OffsetLengthPair; 72 | 73 | struct VersionStringTable { 74 | Translate encoding; 75 | std::vector strings; 76 | }; 77 | 78 | class VersionInfo { 79 | public: 80 | VersionInfo(); 81 | VersionInfo(HMODULE hModule, WORD languageId); 82 | 83 | std::vector Serialize() const; 84 | 85 | bool HasFixedFileInfo() const; 86 | VS_FIXEDFILEINFO& GetFixedFileInfo(); 87 | const VS_FIXEDFILEINFO& GetFixedFileInfo() const; 88 | void SetFixedFileInfo(const VS_FIXEDFILEINFO& value); 89 | 90 | std::vector stringTables; 91 | std::vector supportedTranslations; 92 | 93 | private: 94 | VS_FIXEDFILEINFO fixedFileInfo_; 95 | 96 | void FillDefaultData(); 97 | void DeserializeVersionInfo(const BYTE* pData, size_t size); 98 | 99 | VersionStringTable DeserializeVersionStringTable(const BYTE* tableData); 100 | void DeserializeVersionStringFileInfo(const BYTE* offset, size_t length, std::vector& stringTables); 101 | void DeserializeVarFileInfo(const unsigned char* offset, std::vector& translations); 102 | OffsetLengthPair GetChildrenData(const BYTE* entryData); 103 | }; 104 | 105 | class ResourceUpdater { 106 | public: 107 | typedef std::vector StringValues; 108 | typedef std::map StringTable; 109 | typedef std::map StringTableMap; 110 | typedef std::map VersionStampMap; 111 | typedef std::map> IconTable; 112 | typedef std::vector RcDataValue; 113 | typedef std::map RcDataMap; 114 | typedef std::map RcDataLangMap; 115 | 116 | struct IconResInfo { 117 | UINT maxIconId = 0; 118 | IconTable iconBundles; 119 | }; 120 | 121 | typedef std::map IconTableMap; 122 | 123 | ResourceUpdater(); 124 | ~ResourceUpdater(); 125 | 126 | bool Load(const WCHAR* filename); 127 | bool SetVersionString(WORD languageId, const WCHAR* name, const WCHAR* value); 128 | bool SetVersionString(const WCHAR* name, const WCHAR* value); 129 | const WCHAR* GetVersionString(WORD languageId, const WCHAR* name); 130 | const WCHAR* GetVersionString(const WCHAR* name); 131 | bool SetProductVersion(WORD languageId, UINT id, unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4); 132 | bool SetProductVersion(unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4); 133 | bool SetFileVersion(WORD languageId, UINT id, unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4); 134 | bool SetFileVersion(unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4); 135 | bool ChangeString(WORD languageId, UINT id, const WCHAR* value); 136 | bool ChangeString(UINT id, const WCHAR* value); 137 | bool ChangeRcData(UINT id, const WCHAR* pathToResource); 138 | const WCHAR* GetString(WORD languageId, UINT id); 139 | const WCHAR* GetString(UINT id); 140 | bool SetIcon(const WCHAR* path, const LANGID& langId, UINT iconBundle); 141 | bool SetIcon(const WCHAR* path, const LANGID& langId); 142 | bool SetIcon(const WCHAR* path); 143 | bool SetExecutionLevel(const WCHAR* value); 144 | bool IsExecutionLevelSet(); 145 | bool SetApplicationManifest(const WCHAR* value); 146 | bool IsApplicationManifestSet(); 147 | bool Commit(); 148 | 149 | private: 150 | bool SerializeStringTable(const StringValues& values, UINT blockId, std::vector* out); 151 | 152 | static BOOL CALLBACK OnEnumResourceName(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam); 153 | static BOOL CALLBACK OnEnumResourceManifest(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam); 154 | static BOOL CALLBACK OnEnumResourceLanguage(HANDLE hModule, LPCWSTR lpszType, LPCWSTR lpszName, WORD wIDLanguage, LONG_PTR lParam); 155 | 156 | HMODULE module_; 157 | std::wstring filename_; 158 | std::wstring executionLevel_; 159 | std::wstring originalExecutionLevel_; 160 | std::wstring applicationManifestPath_; 161 | std::wstring manifestString_; 162 | VersionStampMap versionStampMap_; 163 | StringTableMap stringTableMap_; 164 | IconTableMap iconBundleMap_; 165 | RcDataLangMap rcDataLngMap_; 166 | }; 167 | 168 | class ScopedResourceUpdater { 169 | public: 170 | ScopedResourceUpdater(const WCHAR* filename, bool deleteOld); 171 | ~ScopedResourceUpdater(); 172 | 173 | HANDLE Get() const; 174 | bool Commit(); 175 | 176 | private: 177 | bool EndUpdate(bool doesCommit); 178 | 179 | HANDLE handle_; 180 | bool commited_ = false; 181 | }; 182 | 183 | } // namespace rescle 184 | 185 | #endif // VERSION_INFO_UPDATER 186 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 GitHub, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT license that can be found in the 3 | // LICENSE file. 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "rescle.h" 12 | 13 | namespace { 14 | 15 | std::vector get_file_version_info() { 16 | DWORD zero = 0; 17 | std::vector filename(MAX_PATH); 18 | SetLastError(ERROR_SUCCESS); 19 | 20 | do { 21 | GetModuleFileNameW(NULL, filename.data(), filename.size()); 22 | 23 | // Double the buffer size in case the path is longer 24 | filename.resize(filename.size() * 2); 25 | } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); 26 | 27 | if (GetLastError() != ERROR_SUCCESS) { 28 | return std::vector(); 29 | } 30 | 31 | DWORD file_ver_info_size = GetFileVersionInfoSizeW(filename.data(), &zero); 32 | if (file_ver_info_size == 0) { 33 | return std::vector(); 34 | } 35 | 36 | std::vector file_ver_info(file_ver_info_size); 37 | if (!GetFileVersionInfoW(filename.data(), NULL, file_ver_info.size(), file_ver_info.data())) { 38 | return std::vector(); 39 | } 40 | 41 | return file_ver_info; 42 | } 43 | 44 | void print_help(VS_FIXEDFILEINFO* file_info) { 45 | fprintf(stdout, 46 | "Rcedit v%d.%d.%d: Edit resources of exe.\n\n" 47 | "Usage: rcedit [options...]\n\n" 48 | "Options:\n" 49 | " -h, --help Show this message\n" 50 | " --set-version-string Set version string\n" 51 | " --get-version-string Print version string\n" 52 | " --set-file-version Set FileVersion\n" 53 | " --set-product-version Set ProductVersion\n" 54 | " --set-icon Set file icon\n" 55 | " --set-requested-execution-level Pass nothing to see usage\n" 56 | " --application-manifest Set manifest file\n" 57 | " --set-resource-string Set resource string\n" 58 | " --get-resource-string Get resource string\n" 59 | " --set-rcdata Replace RCDATA by integer id\n", 60 | (file_info->dwProductVersionMS >> 16) & 0xff, 61 | (file_info->dwProductVersionMS >> 0) & 0xff, 62 | (file_info->dwProductVersionLS >> 16) & 0xff); 63 | } 64 | 65 | bool print_error(const char* message) { 66 | fprintf(stderr, "Fatal error: %s\n", message); 67 | return 1; 68 | } 69 | 70 | bool print_warning(const char* message) { 71 | fprintf(stderr, "Warning: %s\n", message); 72 | return 1; 73 | } 74 | 75 | bool parse_version_string(const wchar_t* str, unsigned short *v1, unsigned short *v2, unsigned short *v3, unsigned short *v4) { 76 | *v1 = *v2 = *v3 = *v4 = 0; 77 | return (swscanf_s(str, L"%hu.%hu.%hu.%hu", v1, v2, v3, v4) == 4) || 78 | (swscanf_s(str, L"%hu.%hu.%hu", v1, v2, v3) == 3) || 79 | (swscanf_s(str, L"%hu.%hu", v1, v2) == 2) || 80 | (swscanf_s(str, L"%hu", v1) == 1); 81 | } 82 | 83 | } // namespace 84 | 85 | int wmain(int argc, const wchar_t* argv[]) { 86 | bool loaded = false; 87 | rescle::ResourceUpdater updater; 88 | 89 | if (argc == 1 || 90 | (argc == 2 && wcscmp(argv[1], L"-h") == 0) || 91 | (argc == 2 && wcscmp(argv[1], L"--help") == 0)) { 92 | UINT ignored = 0; 93 | VS_FIXEDFILEINFO* file_info = nullptr; 94 | std::vector file_version_info = get_file_version_info(); 95 | 96 | if (file_version_info.size() == 0 || !VerQueryValueW(file_version_info.data(), L"\\", (LPVOID*) &file_info, &ignored)) { 97 | return print_error("Could not determine version of rcedit"); 98 | } 99 | 100 | print_help(file_info); 101 | return 0; 102 | } 103 | 104 | for (int i = 1; i < argc; ++i) { 105 | if (wcscmp(argv[i], L"--set-version-string") == 0 || 106 | wcscmp(argv[i], L"-svs") == 0) { 107 | if (argc - i < 3) 108 | return print_error("--set-version-string requires 'Key' and 'Value'"); 109 | 110 | const wchar_t* key = argv[++i]; 111 | const wchar_t* value = argv[++i]; 112 | if (!updater.SetVersionString(key, value)) 113 | return print_error("Unable to change version string"); 114 | 115 | } else if (wcscmp(argv[i], L"--get-version-string") == 0 || 116 | wcscmp(argv[i], L"-gvs") == 0) { 117 | if (argc - i < 2) 118 | return print_error("--get-version-string requires 'Key'"); 119 | const wchar_t* key = argv[++i]; 120 | const wchar_t* result = updater.GetVersionString(key); 121 | if (!result) 122 | return print_error("Unable to get version string"); 123 | 124 | fwprintf(stdout, L"%s", result); 125 | return 0; // no changes made 126 | 127 | } else if (wcscmp(argv[i], L"--set-file-version") == 0 || 128 | wcscmp(argv[i], L"-sfv") == 0) { 129 | if (argc - i < 2) 130 | return print_error("--set-file-version requires a version string"); 131 | 132 | unsigned short v1, v2, v3, v4; 133 | if (!parse_version_string(argv[++i], &v1, &v2, &v3, &v4)) 134 | return print_error("Unable to parse version string for FileVersion"); 135 | 136 | if (!updater.SetFileVersion(v1, v2, v3, v4)) 137 | return print_error("Unable to change file version"); 138 | 139 | if (!updater.SetVersionString(L"FileVersion", argv[i])) 140 | return print_error("Unable to change FileVersion string"); 141 | 142 | } else if (wcscmp(argv[i], L"--set-product-version") == 0 || 143 | wcscmp(argv[i], L"-spv") == 0) { 144 | if (argc - i < 2) 145 | return print_error("--set-product-version requires a version string"); 146 | 147 | unsigned short v1, v2, v3, v4; 148 | if (!parse_version_string(argv[++i], &v1, &v2, &v3, &v4)) 149 | return print_error("Unable to parse version string for ProductVersion"); 150 | 151 | if (!updater.SetProductVersion(v1, v2, v3, v4)) 152 | return print_error("Unable to change product version"); 153 | 154 | if (!updater.SetVersionString(L"ProductVersion", argv[i])) 155 | return print_error("Unable to change ProductVersion string"); 156 | 157 | } else if (wcscmp(argv[i], L"--set-icon") == 0 || 158 | wcscmp(argv[i], L"-si") == 0) { 159 | if (argc - i < 2) 160 | return print_error("--set-icon requires path to the icon"); 161 | 162 | if (!updater.SetIcon(argv[++i])) 163 | return print_error("Unable to set icon"); 164 | 165 | } else if (wcscmp(argv[i], L"--set-requested-execution-level") == 0 || 166 | wcscmp(argv[i], L"-srel") == 0) { 167 | if (argc - i < 2) 168 | return print_error("--set-requested-execution-level requires asInvoker, highestAvailable or requireAdministrator"); 169 | 170 | if (updater.IsApplicationManifestSet()) 171 | print_warning("--set-requested-execution-level is ignored if --application-manifest is set"); 172 | 173 | if (!updater.SetExecutionLevel(argv[++i])) 174 | return print_error("Unable to set execution level"); 175 | 176 | } else if (wcscmp(argv[i], L"--application-manifest") == 0 || 177 | wcscmp(argv[i], L"-am") == 0) { 178 | if (argc - i < 2) 179 | return print_error("--application-manifest requires local path"); 180 | 181 | if (updater.IsExecutionLevelSet()) 182 | print_warning("--set-requested-execution-level is ignored if --application-manifest is set"); 183 | 184 | if (!updater.SetApplicationManifest(argv[++i])) 185 | return print_error("Unable to set application manifest"); 186 | 187 | } else if (wcscmp(argv[i], L"--set-resource-string") == 0 || 188 | wcscmp(argv[i], L"--srs") == 0) { 189 | if (argc - i < 3) 190 | return print_error("--set-resource-string requires int 'Key' and string 'Value'"); 191 | 192 | const wchar_t* key = argv[++i]; 193 | unsigned int key_id = 0; 194 | if (swscanf_s(key, L"%d", &key_id) != 1) 195 | return print_error("Unable to parse id"); 196 | 197 | const wchar_t* value = argv[++i]; 198 | if (!updater.ChangeString(key_id, value)) 199 | return print_error("Unable to change string"); 200 | 201 | } else if (wcscmp(argv[i], L"--set-rcdata") == 0) { 202 | if (argc - i < 3) 203 | return print_error("--set-rcdata requires int 'Key' and path to resource 'Value'"); 204 | 205 | const wchar_t* key = argv[++i]; 206 | unsigned int key_id = 0; 207 | if (swscanf_s(key, L"%d", &key_id) != 1) 208 | return print_error("Unable to parse id"); 209 | 210 | const wchar_t* pathToResource = argv[++i]; 211 | if (!updater.ChangeRcData(key_id, pathToResource)) 212 | return print_error("Unable to change RCDATA"); 213 | } else if (wcscmp(argv[i], L"--get-resource-string") == 0 || 214 | wcscmp(argv[i], L"-grs") == 0) { 215 | if (argc - i < 2) 216 | return print_error("--get-resource-string requires int 'Key'"); 217 | 218 | const wchar_t* key = argv[++i]; 219 | unsigned int key_id = 0; 220 | if (swscanf_s(key, L"%d", &key_id) != 1) 221 | return print_error("Unable to parse id"); 222 | 223 | const wchar_t* result = updater.GetString(key_id); 224 | if (!result) 225 | return print_error("Unable to get resource string"); 226 | 227 | fwprintf(stdout, L"%s", result); 228 | return 0; // no changes made 229 | 230 | } else { 231 | if (loaded) { 232 | fprintf(stderr, "Unrecognized argument: \"%ls\"\n", argv[i]); 233 | return 1; 234 | } 235 | 236 | loaded = true; 237 | if (!updater.Load(argv[i])) { 238 | fprintf(stderr, "Unable to load file: \"%ls\"\n", argv[i]); 239 | return 1; 240 | } 241 | 242 | } 243 | } 244 | 245 | if (!loaded) 246 | return print_error("You should specify a exe/dll file"); 247 | 248 | if (!updater.Commit()) 249 | return print_error("Unable to commit changes"); 250 | 251 | return 0; 252 | } 253 | -------------------------------------------------------------------------------- /src/rescle.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 GitHub, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT license that can be found in the 3 | // LICENSE file. 4 | // 5 | // This file is modified from Rescle written by yoshio.okumura@gmail.com: 6 | // http://code.google.com/p/rescle/ 7 | 8 | #include "rescle.h" 9 | 10 | #include 11 | #include 12 | #include // wstringstream 13 | #include // setw, setfill 14 | #include 15 | #include 16 | #include 17 | 18 | namespace rescle { 19 | 20 | namespace { 21 | 22 | #pragma pack(push,2) 23 | typedef struct _GRPICONENTRY { 24 | BYTE width; 25 | BYTE height; 26 | BYTE colourCount; 27 | BYTE reserved; 28 | BYTE planes; 29 | BYTE bitCount; 30 | WORD bytesInRes; 31 | WORD bytesInRes2; 32 | WORD reserved2; 33 | WORD id; 34 | } GRPICONENTRY; 35 | #pragma pack(pop) 36 | 37 | #pragma pack(push,2) 38 | typedef struct _GRPICONHEADER { 39 | WORD reserved; 40 | WORD type; 41 | WORD count; 42 | GRPICONENTRY entries[1]; 43 | } GRPICONHEADER; 44 | #pragma pack(pop) 45 | 46 | #pragma pack(push,1) 47 | typedef struct _VS_VERSION_HEADER { 48 | WORD wLength; 49 | WORD wValueLength; 50 | WORD wType; 51 | } VS_VERSION_HEADER; 52 | #pragma pack(pop) 53 | 54 | #pragma pack(push,1) 55 | typedef struct _VS_VERSION_STRING { 56 | VS_VERSION_HEADER Header; 57 | WCHAR szKey[1]; 58 | } VS_VERSION_STRING; 59 | #pragma pack(pop) 60 | 61 | #pragma pack(push,1) 62 | typedef struct _VS_VERSION_ROOT_INFO { 63 | WCHAR szKey[16]; 64 | WORD Padding1[1]; 65 | VS_FIXEDFILEINFO Info; 66 | } VS_VERSION_ROOT_INFO; 67 | #pragma pack(pop) 68 | 69 | #pragma pack(push,1) 70 | typedef struct _VS_VERSION_ROOT { 71 | VS_VERSION_HEADER Header; 72 | VS_VERSION_ROOT_INFO Info; 73 | } VS_VERSION_ROOT; 74 | #pragma pack(pop) 75 | 76 | // The default en-us LANGID. 77 | LANGID kLangEnUs = 1033; 78 | LANGID kCodePageEnUs = 1200; 79 | UINT kDefaultIconBundle = 0; 80 | 81 | template 82 | inline T round(T value, int modula = 4) { 83 | return value + ((value % modula > 0) ? (modula - value % modula) : 0); 84 | } 85 | 86 | std::wstring ReadFileToString(const wchar_t* filename) { 87 | std::wifstream wif(filename); 88 | wif.imbue(std::locale(std::locale::empty(), new std::codecvt_utf8)); 89 | std::wstringstream wss; 90 | wss << wif.rdbuf(); 91 | return wss.str(); 92 | } 93 | 94 | class ScopedFile { 95 | public: 96 | ScopedFile(const WCHAR* path) 97 | : file_(CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) {} 98 | ~ScopedFile() { CloseHandle(file_); } 99 | 100 | operator HANDLE() { return file_; } 101 | 102 | private: 103 | HANDLE file_; 104 | }; 105 | 106 | struct VersionStampValue { 107 | WORD valueLength = 0; // stringfileinfo, stringtable: 0; string: Value size in WORD; var: Value size in bytes 108 | WORD type = 0; // 0: binary data; 1: text data 109 | std::wstring key; // stringtable: 8-digit hex stored as UTF-16 (hiword: hi6: sublang, lo10: majorlang; loword: code page); must include zero words to align next member on 32-bit boundary 110 | std::vector value; // string: zero-terminated string; var: array of language & code page ID pairs 111 | std::vector children; 112 | 113 | size_t GetLength() const; 114 | std::vector Serialize() const; 115 | }; 116 | 117 | } // namespace 118 | 119 | VersionInfo::VersionInfo() { 120 | FillDefaultData(); 121 | } 122 | 123 | VersionInfo::VersionInfo(HMODULE hModule, WORD languageId) { 124 | HRSRC hRsrc = FindResourceExW(hModule, RT_VERSION, MAKEINTRESOURCEW(1), languageId); 125 | 126 | if (hRsrc == NULL) { 127 | throw std::system_error(GetLastError(), std::system_category()); 128 | } 129 | 130 | HGLOBAL hGlobal = LoadResource(hModule, hRsrc); 131 | if (hGlobal == NULL) { 132 | throw std::system_error(GetLastError(), std::system_category()); 133 | } 134 | 135 | void* p = LockResource(hGlobal); 136 | if (p == NULL) { 137 | throw std::system_error(GetLastError(), std::system_category()); 138 | } 139 | 140 | DWORD size = SizeofResource(hModule, hRsrc); 141 | if (size == 0) { 142 | throw std::system_error(GetLastError(), std::system_category()); 143 | } 144 | 145 | DeserializeVersionInfo(static_cast(p), size); 146 | FillDefaultData(); 147 | } 148 | 149 | bool VersionInfo::HasFixedFileInfo() const { 150 | return fixedFileInfo_.dwSignature == 0xFEEF04BD; 151 | } 152 | 153 | VS_FIXEDFILEINFO& VersionInfo::GetFixedFileInfo() { 154 | return fixedFileInfo_; 155 | } 156 | 157 | const VS_FIXEDFILEINFO& VersionInfo::GetFixedFileInfo() const { 158 | return fixedFileInfo_; 159 | } 160 | 161 | void VersionInfo::SetFixedFileInfo(const VS_FIXEDFILEINFO& value) { 162 | fixedFileInfo_ = value; 163 | } 164 | 165 | std::vector VersionInfo::Serialize() const { 166 | VersionStampValue versionInfo; 167 | versionInfo.key = L"VS_VERSION_INFO"; 168 | versionInfo.type = 0; 169 | 170 | if (HasFixedFileInfo()) { 171 | auto size = sizeof(VS_FIXEDFILEINFO); 172 | versionInfo.valueLength = size; 173 | 174 | auto& dst = versionInfo.value; 175 | dst.resize(size); 176 | 177 | memcpy(&dst[0], &GetFixedFileInfo(), size); 178 | } 179 | 180 | { 181 | VersionStampValue stringFileInfo; 182 | stringFileInfo.key = L"StringFileInfo"; 183 | stringFileInfo.type = 1; 184 | stringFileInfo.valueLength = 0; 185 | 186 | for (const auto& iTable : stringTables) { 187 | VersionStampValue stringTableRaw; 188 | stringTableRaw.type = 1; 189 | stringTableRaw.valueLength = 0; 190 | 191 | { 192 | auto& translate = iTable.encoding; 193 | std::wstringstream ss; 194 | ss << std::hex << std::setw(8) << std::setfill(L'0') << (translate.wLanguage << 16 | translate.wCodePage); 195 | stringTableRaw.key = ss.str(); 196 | } 197 | 198 | for (const auto& iString : iTable.strings) { 199 | const auto& stringValue = iString.second; 200 | auto strLenNullTerminated = stringValue.length() + 1; 201 | 202 | VersionStampValue stringRaw; 203 | stringRaw.type = 1; 204 | stringRaw.key = iString.first; 205 | stringRaw.valueLength = strLenNullTerminated; 206 | 207 | auto size = strLenNullTerminated * sizeof(WCHAR); 208 | auto& dst = stringRaw.value; 209 | dst.resize(size); 210 | 211 | auto src = stringValue.c_str(); 212 | 213 | memcpy(&dst[0], src, size); 214 | 215 | stringTableRaw.children.push_back(std::move(stringRaw)); 216 | } 217 | 218 | stringFileInfo.children.push_back(std::move(stringTableRaw)); 219 | } 220 | 221 | versionInfo.children.push_back(std::move(stringFileInfo)); 222 | } 223 | 224 | { 225 | VersionStampValue varFileInfo; 226 | varFileInfo.key = L"VarFileInfo"; 227 | varFileInfo.type = 1; 228 | varFileInfo.valueLength = 0; 229 | 230 | { 231 | VersionStampValue varRaw; 232 | varRaw.key = L"Translation"; 233 | varRaw.type = 0; 234 | 235 | { 236 | auto newValueSize = sizeof(DWORD); 237 | auto& dst = varRaw.value; 238 | dst.resize(supportedTranslations.size() * newValueSize); 239 | 240 | for (auto iVar = 0; iVar < supportedTranslations.size(); ++iVar) { 241 | auto& translate = supportedTranslations[iVar]; 242 | auto var = DWORD(translate.wCodePage) << 16 | translate.wLanguage; 243 | memcpy(&dst[iVar * newValueSize], &var, newValueSize); 244 | } 245 | 246 | varRaw.valueLength = varRaw.value.size(); 247 | } 248 | 249 | varFileInfo.children.push_back(std::move(varRaw)); 250 | } 251 | 252 | versionInfo.children.push_back(std::move(varFileInfo)); 253 | } 254 | 255 | return std::move(versionInfo.Serialize()); 256 | } 257 | 258 | void VersionInfo::FillDefaultData() { 259 | if (stringTables.empty()) { 260 | Translate enUsTranslate = {kLangEnUs, kCodePageEnUs}; 261 | stringTables.push_back({enUsTranslate}); 262 | supportedTranslations.push_back(enUsTranslate); 263 | } 264 | if (!HasFixedFileInfo()) { 265 | fixedFileInfo_ = {0}; 266 | fixedFileInfo_.dwSignature = 0xFEEF04BD; 267 | fixedFileInfo_.dwFileType = VFT_APP; 268 | } 269 | } 270 | 271 | void VersionInfo::DeserializeVersionInfo(const BYTE* pData, size_t size) { 272 | auto pVersionInfo = reinterpret_cast(pData); 273 | WORD fixedFileInfoSize = pVersionInfo->Header.wValueLength; 274 | 275 | if (fixedFileInfoSize > 0) 276 | SetFixedFileInfo(pVersionInfo->Info.Info); 277 | 278 | const BYTE* fixedFileInfoEndOffset = reinterpret_cast(&pVersionInfo->Info.szKey) + (wcslen(pVersionInfo->Info.szKey) + 1) * sizeof(WCHAR) + fixedFileInfoSize; 279 | const BYTE* pVersionInfoChildren = reinterpret_cast(round(reinterpret_cast(fixedFileInfoEndOffset))); 280 | size_t versionInfoChildrenOffset = pVersionInfoChildren - pData; 281 | size_t versionInfoChildrenSize = pVersionInfo->Header.wLength - versionInfoChildrenOffset; 282 | 283 | const auto childrenEndOffset = pVersionInfoChildren + versionInfoChildrenSize; 284 | const auto resourceEndOffset = pData + size; 285 | for (auto p = pVersionInfoChildren; p < childrenEndOffset && p < resourceEndOffset;) { 286 | auto pKey = reinterpret_cast(p)->szKey; 287 | auto versionInfoChildData = GetChildrenData(p); 288 | if (wcscmp(pKey, L"StringFileInfo") == 0) { 289 | DeserializeVersionStringFileInfo(versionInfoChildData.first, versionInfoChildData.second, stringTables); 290 | } else if (wcscmp(pKey, L"VarFileInfo") == 0) { 291 | DeserializeVarFileInfo(versionInfoChildData.first, supportedTranslations); 292 | } 293 | 294 | p += round(reinterpret_cast(p)->Header.wLength); 295 | } 296 | } 297 | 298 | VersionStringTable VersionInfo::DeserializeVersionStringTable(const BYTE* tableData) { 299 | auto strings = GetChildrenData(tableData); 300 | auto stringTable = reinterpret_cast(tableData); 301 | auto end_ptr = const_cast(stringTable->szKey + (8 * sizeof(WCHAR))); 302 | auto langIdCodePagePair = static_cast(wcstol(stringTable->szKey, &end_ptr, 16)); 303 | 304 | VersionStringTable tableEntry; 305 | 306 | // unicode string of 8 hex digits 307 | tableEntry.encoding.wLanguage = langIdCodePagePair >> 16; 308 | tableEntry.encoding.wCodePage = langIdCodePagePair; 309 | 310 | for (auto posStrings = 0U; posStrings < strings.second;) { 311 | const auto stringEntry = reinterpret_cast(strings.first + posStrings); 312 | const auto stringData = GetChildrenData(strings.first + posStrings); 313 | tableEntry.strings.push_back(std::pair(stringEntry->szKey, std::wstring(reinterpret_cast(stringData.first), stringEntry->Header.wValueLength))); 314 | 315 | posStrings += round(stringEntry->Header.wLength); 316 | } 317 | 318 | return tableEntry; 319 | } 320 | 321 | void VersionInfo::DeserializeVersionStringFileInfo(const BYTE* offset, size_t length, std::vector& stringTables) { 322 | for (auto posStringTables = 0U; posStringTables < length;) { 323 | auto stringTableEntry = DeserializeVersionStringTable(offset + posStringTables); 324 | stringTables.push_back(stringTableEntry); 325 | posStringTables += round(reinterpret_cast(offset + posStringTables)->Header.wLength); 326 | } 327 | } 328 | 329 | void VersionInfo::DeserializeVarFileInfo(const unsigned char* offset, std::vector& translations) { 330 | const auto translatePairs = GetChildrenData(offset); 331 | 332 | const auto top = reinterpret_cast(translatePairs.first); 333 | for (auto pTranslatePair = top; pTranslatePair < top + translatePairs.second; pTranslatePair += sizeof(DWORD)) { 334 | auto codePageLangIdPair = *pTranslatePair; 335 | Translate translate; 336 | translate.wLanguage = codePageLangIdPair; 337 | translate.wCodePage = codePageLangIdPair >> 16; 338 | translations.push_back(translate); 339 | } 340 | } 341 | 342 | OffsetLengthPair VersionInfo::GetChildrenData(const BYTE* entryData) { 343 | auto entry = reinterpret_cast(entryData); 344 | auto headerOffset = entryData; 345 | auto headerSize = sizeof(VS_VERSION_HEADER); 346 | auto keySize = (wcslen(entry->szKey) + 1) * sizeof(WCHAR); 347 | auto childrenOffset = round(headerSize + keySize); 348 | 349 | auto pChildren = headerOffset + childrenOffset; 350 | auto childrenSize = entry->Header.wLength - childrenOffset; 351 | return OffsetLengthPair(pChildren, childrenSize); 352 | } 353 | 354 | size_t VersionStampValue::GetLength() const { 355 | size_t bytes = sizeof(VS_VERSION_HEADER); 356 | bytes += static_cast(key.length() + 1) * sizeof(WCHAR); 357 | if (!value.empty()) 358 | bytes = round(bytes) + value.size(); 359 | for (const auto& child : children) 360 | bytes = round(bytes) + static_cast(child.GetLength()); 361 | return bytes; 362 | } 363 | 364 | std::vector VersionStampValue::Serialize() const { 365 | std::vector data = std::vector(GetLength()); 366 | 367 | size_t offset = 0; 368 | 369 | VS_VERSION_HEADER header = { static_cast(data.size()), valueLength, type }; 370 | memcpy(&data[offset], &header, sizeof(header)); 371 | offset += sizeof(header); 372 | 373 | auto keySize = static_cast(key.length() + 1) * sizeof(WCHAR); 374 | memcpy(&data[offset], key.c_str(), keySize); 375 | offset += keySize; 376 | 377 | if (!value.empty()) { 378 | offset = round(offset); 379 | memcpy(&data[offset], &value[0], value.size()); 380 | offset += value.size(); 381 | } 382 | 383 | for (const auto& child : children) { 384 | offset = round(offset); 385 | size_t childLength = child.GetLength(); 386 | std::vector src = child.Serialize(); 387 | memcpy(&data[offset], &src[0], childLength); 388 | offset += childLength; 389 | } 390 | 391 | return std::move(data); 392 | } 393 | 394 | ResourceUpdater::ResourceUpdater() : module_(NULL) { 395 | } 396 | 397 | ResourceUpdater::~ResourceUpdater() { 398 | if (module_ != NULL) { 399 | FreeLibrary(module_); 400 | module_ = NULL; 401 | } 402 | } 403 | 404 | bool ResourceUpdater::Load(const WCHAR* filename) { 405 | wchar_t abspath[MAX_PATH] = {0}; 406 | if (_wfullpath(abspath, filename, MAX_PATH)) 407 | module_ = LoadLibraryExW(abspath, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); 408 | else 409 | module_ = LoadLibraryExW(filename, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE); 410 | 411 | if (module_ == NULL) { 412 | return false; 413 | } 414 | 415 | this->filename_ = filename; 416 | 417 | EnumResourceNamesW(module_, RT_STRING, OnEnumResourceName, reinterpret_cast(this)); 418 | EnumResourceNamesW(module_, RT_VERSION, OnEnumResourceName, reinterpret_cast(this)); 419 | EnumResourceNamesW(module_, RT_GROUP_ICON, OnEnumResourceName, reinterpret_cast(this)); 420 | EnumResourceNamesW(module_, RT_ICON, OnEnumResourceName, reinterpret_cast(this)); 421 | EnumResourceNamesW(module_, RT_MANIFEST, OnEnumResourceManifest, reinterpret_cast(this)); 422 | EnumResourceNamesW(module_, RT_RCDATA, OnEnumResourceName, reinterpret_cast(this)); 423 | 424 | return true; 425 | } 426 | 427 | bool ResourceUpdater::SetExecutionLevel(const WCHAR* value) { 428 | executionLevel_ = value; 429 | return true; 430 | } 431 | 432 | bool ResourceUpdater::IsExecutionLevelSet() { 433 | return !executionLevel_.empty(); 434 | } 435 | 436 | bool ResourceUpdater::SetApplicationManifest(const WCHAR* value) { 437 | applicationManifestPath_ = value; 438 | return true; 439 | } 440 | 441 | bool ResourceUpdater::IsApplicationManifestSet() { 442 | return !applicationManifestPath_.empty(); 443 | } 444 | 445 | bool ResourceUpdater::SetVersionString(WORD languageId, const WCHAR* name, const WCHAR* value) { 446 | std::wstring nameStr(name); 447 | std::wstring valueStr(value); 448 | 449 | auto& stringTables = versionStampMap_[languageId].stringTables; 450 | for (auto j = stringTables.begin(); j != stringTables.end(); ++j) { 451 | auto& stringPairs = j->strings; 452 | for (auto k = stringPairs.begin(); k != stringPairs.end(); ++k) { 453 | if (k->first == nameStr) { 454 | k->second = valueStr; 455 | return true; 456 | } 457 | } 458 | 459 | // Not found, append one for all tables. 460 | stringPairs.push_back(VersionString(nameStr, valueStr)); 461 | } 462 | 463 | return true; 464 | } 465 | 466 | bool ResourceUpdater::SetVersionString(const WCHAR* name, const WCHAR* value) { 467 | LANGID langId = versionStampMap_.empty() ? kLangEnUs 468 | : versionStampMap_.begin()->first; 469 | return SetVersionString(langId, name, value); 470 | } 471 | 472 | const WCHAR* ResourceUpdater::GetVersionString(WORD languageId, const WCHAR* name) { 473 | std::wstring nameStr(name); 474 | 475 | const auto& stringTables = versionStampMap_[languageId].stringTables; 476 | for (const auto& j : stringTables) { 477 | const auto& stringPairs = j.strings; 478 | for (const auto& k : stringPairs) { 479 | if (k.first == nameStr) { 480 | return k.second.c_str(); 481 | } 482 | } 483 | } 484 | 485 | return NULL; 486 | } 487 | 488 | const WCHAR* ResourceUpdater::GetVersionString(const WCHAR* name) { 489 | if (versionStampMap_.empty()) { 490 | return NULL; 491 | } else { 492 | return GetVersionString(versionStampMap_.begin()->first, name); 493 | } 494 | } 495 | 496 | bool ResourceUpdater::SetProductVersion(WORD languageId, UINT id, unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4) { 497 | VersionInfo& versionInfo = versionStampMap_[languageId]; 498 | if (!versionInfo.HasFixedFileInfo()) { 499 | return false; 500 | } 501 | 502 | VS_FIXEDFILEINFO& root = versionInfo.GetFixedFileInfo(); 503 | 504 | root.dwProductVersionMS = v1 << 16 | v2; 505 | root.dwProductVersionLS = v3 << 16 | v4; 506 | 507 | return true; 508 | } 509 | 510 | bool ResourceUpdater::SetProductVersion(unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4) { 511 | LANGID langId = versionStampMap_.empty() ? kLangEnUs 512 | : versionStampMap_.begin()->first; 513 | return SetProductVersion(langId, 1, v1, v2, v3, v4); 514 | } 515 | 516 | bool ResourceUpdater::SetFileVersion(WORD languageId, UINT id, unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4) { 517 | VersionInfo& versionInfo = versionStampMap_[languageId]; 518 | if (!versionInfo.HasFixedFileInfo()) { 519 | return false; 520 | } 521 | 522 | VS_FIXEDFILEINFO& root = versionInfo.GetFixedFileInfo(); 523 | 524 | root.dwFileVersionMS = v1 << 16 | v2; 525 | root.dwFileVersionLS = v3 << 16 | v4; 526 | return true; 527 | } 528 | 529 | bool ResourceUpdater::SetFileVersion(unsigned short v1, unsigned short v2, unsigned short v3, unsigned short v4) { 530 | LANGID langId = versionStampMap_.empty() ? kLangEnUs 531 | : versionStampMap_.begin()->first; 532 | return SetFileVersion(langId, 1, v1, v2, v3, v4); 533 | } 534 | 535 | bool ResourceUpdater::ChangeString(WORD languageId, UINT id, const WCHAR* value) { 536 | StringTable& table = stringTableMap_[languageId]; 537 | 538 | UINT blockId = id / 16; 539 | if (table.find(blockId) == table.end()) { 540 | // Fill the table until we reach the block. 541 | for (size_t i = table.size(); i <= blockId; ++i) { 542 | table[i] = std::vector(16); 543 | } 544 | } 545 | 546 | assert(table[blockId].size() == 16); 547 | UINT blockIndex = id % 16; 548 | table[blockId][blockIndex] = value; 549 | 550 | return true; 551 | } 552 | 553 | bool ResourceUpdater::ChangeString(UINT id, const WCHAR* value) { 554 | LANGID langId = stringTableMap_.empty() ? kLangEnUs 555 | : stringTableMap_.begin()->first; 556 | return ChangeString(langId, id, value); 557 | } 558 | 559 | bool ResourceUpdater::ChangeRcData(UINT id, const WCHAR* pathToResource) { 560 | auto rcDataLngPairIt = std::find_if(rcDataLngMap_.begin(), rcDataLngMap_.end(), [=](const auto& rcDataLngPair) { 561 | return rcDataLngPair.second.find(id) != rcDataLngPair.second.end(); 562 | }); 563 | 564 | if (rcDataLngPairIt == rcDataLngMap_.end()) { 565 | fprintf(stderr, "Cannot find RCDATA with id '%u'\n", id); 566 | return false; 567 | } 568 | 569 | wchar_t abspath[MAX_PATH] = { 0 }; 570 | const auto filePath = _wfullpath(abspath, pathToResource, MAX_PATH) ? abspath : pathToResource; 571 | ScopedFile newRcDataFile(filePath); 572 | if (newRcDataFile == INVALID_HANDLE_VALUE) { 573 | fprintf(stderr, "Cannot open new data file '%ws'\n", filePath); 574 | return false; 575 | } 576 | 577 | const auto dwFileSize = GetFileSize(newRcDataFile, NULL); 578 | if (dwFileSize == INVALID_FILE_SIZE) { 579 | fprintf(stderr, "Cannot get file size for '%ws'\n", filePath); 580 | return false; 581 | } 582 | 583 | auto& rcData = rcDataLngPairIt->second[id]; 584 | rcData.clear(); 585 | rcData.resize(dwFileSize); 586 | 587 | DWORD dwBytesRead{ 0 }; 588 | if (!ReadFile(newRcDataFile, rcData.data(), dwFileSize, &dwBytesRead, NULL)) { 589 | fprintf(stderr, "Cannot read file '%ws'\n", filePath); 590 | return false; 591 | } 592 | 593 | return true; 594 | } 595 | 596 | const WCHAR* ResourceUpdater::GetString(WORD languageId, UINT id) { 597 | StringTable& table = stringTableMap_[languageId]; 598 | 599 | UINT blockId = id / 16; 600 | if (table.find(blockId) == table.end()) { 601 | // Fill the table until we reach the block. 602 | for (size_t i = table.size(); i <= blockId; ++i) { 603 | table[i] = std::vector(16); 604 | } 605 | } 606 | 607 | assert(table[blockId].size() == 16); 608 | UINT blockIndex = id % 16; 609 | 610 | return table[blockId][blockIndex].c_str(); 611 | } 612 | 613 | const WCHAR* ResourceUpdater::GetString(UINT id) { 614 | LANGID langId = stringTableMap_.empty() ? kLangEnUs 615 | : stringTableMap_.begin()->first; 616 | return GetString(langId, id); 617 | } 618 | 619 | bool ResourceUpdater::SetIcon(const WCHAR* path, const LANGID& langId, 620 | UINT iconBundle) { 621 | std::unique_ptr& pIcon = iconBundleMap_[langId].iconBundles[iconBundle]; 622 | if (!pIcon) 623 | pIcon = std::make_unique(); 624 | 625 | auto& icon = *pIcon; 626 | DWORD bytes; 627 | 628 | ScopedFile file(path); 629 | if (file == INVALID_HANDLE_VALUE) { 630 | fwprintf(stderr, L"Cannot open icon file '%ls'\n", path); 631 | return false; 632 | } 633 | 634 | IconsValue::ICONHEADER& header = icon.header; 635 | if (!ReadFile(file, &header, 3 * sizeof(WORD), &bytes, NULL)) { 636 | fwprintf(stderr, L"Cannot read icon header for '%ls'\n", path); 637 | return false; 638 | } 639 | 640 | if (header.reserved != 0 || header.type != 1) { 641 | fwprintf(stderr, L"Reserved header is not 0 or image type is not icon for '%ls'\n", path); 642 | return false; 643 | } 644 | 645 | header.entries.resize(header.count); 646 | if (!ReadFile(file, header.entries.data(), header.count * sizeof(IconsValue::ICONENTRY), &bytes, NULL)) { 647 | fwprintf(stderr, L"Cannot read icon metadata for '%ls'\n", path); 648 | return false; 649 | } 650 | 651 | icon.images.resize(header.count); 652 | for (size_t i = 0; i < header.count; ++i) { 653 | icon.images[i].resize(header.entries[i].bytesInRes); 654 | SetFilePointer(file, header.entries[i].imageOffset, NULL, FILE_BEGIN); 655 | if (!ReadFile(file, icon.images[i].data(), icon.images[i].size(), &bytes, NULL)) { 656 | fwprintf(stderr, L"Cannot read icon data for '%ls'\n", path); 657 | return false; 658 | } 659 | } 660 | 661 | icon.grpHeader.resize(3 * sizeof(WORD) + header.count * sizeof(GRPICONENTRY)); 662 | GRPICONHEADER* pGrpHeader = reinterpret_cast(icon.grpHeader.data()); 663 | pGrpHeader->reserved = 0; 664 | pGrpHeader->type = 1; 665 | pGrpHeader->count = header.count; 666 | for (size_t i = 0; i < header.count; ++i) { 667 | GRPICONENTRY* entry = pGrpHeader->entries + i; 668 | entry->bitCount = 0; 669 | entry->bytesInRes = header.entries[i].bitCount; 670 | entry->bytesInRes2 = header.entries[i].bytesInRes; 671 | entry->colourCount = header.entries[i].colorCount; 672 | entry->height = header.entries[i].height; 673 | entry->id = i + 1; 674 | entry->planes = header.entries[i].planes; 675 | entry->reserved = header.entries[i].reserved; 676 | entry->width = header.entries[i].width; 677 | entry->reserved2 = 0; 678 | } 679 | 680 | return true; 681 | } 682 | 683 | bool ResourceUpdater::SetIcon(const WCHAR* path, const LANGID& langId) { 684 | if (iconBundleMap_[langId].iconBundles.empty()) { 685 | return SetIcon(path, langId, kDefaultIconBundle); 686 | } 687 | UINT iconBundle = iconBundleMap_[langId].iconBundles.begin()->first; 688 | return SetIcon(path, langId, iconBundle); 689 | } 690 | 691 | bool ResourceUpdater::SetIcon(const WCHAR* path) { 692 | LANGID langId = iconBundleMap_.empty() ? kLangEnUs 693 | : iconBundleMap_.begin()->first; 694 | return SetIcon(path, langId); 695 | } 696 | 697 | bool ResourceUpdater::Commit() { 698 | if (module_ == NULL) { 699 | return false; 700 | } 701 | FreeLibrary(module_); 702 | module_ = NULL; 703 | 704 | ScopedResourceUpdater ru(filename_.c_str(), false); 705 | if (ru.Get() == NULL) { 706 | return false; 707 | } 708 | 709 | // update version info. 710 | for (const auto& i : versionStampMap_) { 711 | LANGID langId = i.first; 712 | std::vector out = i.second.Serialize(); 713 | 714 | if (!UpdateResourceW(ru.Get(), RT_VERSION, MAKEINTRESOURCEW(1), langId, 715 | &out[0], static_cast(out.size()))) { 716 | return false; 717 | } 718 | } 719 | 720 | // update the execution level 721 | if (applicationManifestPath_.empty() && !executionLevel_.empty()) { 722 | // string replace with requested executionLevel 723 | std::wstring::size_type pos = 0u; 724 | while ((pos = manifestString_.find(originalExecutionLevel_, pos)) != std::string::npos) { 725 | manifestString_.replace(pos, originalExecutionLevel_.length(), executionLevel_); 726 | pos += executionLevel_.length(); 727 | } 728 | 729 | // clean old padding and add new padding, ensuring that the size is a multiple of 4 730 | std::wstring::size_type padPos = manifestString_.find(L""); 731 | // trim anything after the , 11 being the length of (ie, remove old padding) 732 | std::wstring trimmedStr = manifestString_.substr(0, padPos + 11); 733 | std::wstring padding = L"\n"; 734 | 735 | int offset = (trimmedStr.length() + padding.length()) % 4; 736 | // multiple X by the number in offset 737 | pos = 0u; 738 | for (int posCount = 0; posCount < offset; posCount = posCount + 1) { 739 | if ((pos = padding.find(L"X", pos)) != std::string::npos) { 740 | padding.replace(pos, 1, L"XX"); 741 | pos += executionLevel_.length(); 742 | } 743 | } 744 | 745 | // convert the wchar back into char, so that it encodes correctly for Windows to read the XML. 746 | std::wstring stringSectionW = trimmedStr + padding; 747 | std::wstring_convert, wchar_t> converter; 748 | std::string stringSection = converter.to_bytes(stringSectionW); 749 | 750 | if (!UpdateResourceW(ru.Get(), RT_MANIFEST, MAKEINTRESOURCEW(1), 751 | kLangEnUs, // this is hardcoded at 1033, ie, en-us, as that is what RT_MANIFEST default uses 752 | &stringSection.at(0), sizeof(char) * stringSection.size())) { 753 | return false; 754 | } 755 | } 756 | 757 | // load file contents and replace the manifest 758 | if (!applicationManifestPath_.empty()) { 759 | std::wstring fileContents = ReadFileToString(applicationManifestPath_.c_str()); 760 | 761 | // clean old padding and add new padding, ensuring that the size is a multiple of 4 762 | std::wstring::size_type padPos = fileContents.find(L""); 763 | // trim anything after the , 11 being the length of (ie, remove old padding) 764 | std::wstring trimmedStr = fileContents.substr(0, padPos + 11); 765 | std::wstring padding = L"\n"; 766 | 767 | int offset = (trimmedStr.length() + padding.length()) % 4; 768 | // multiple X by the number in offset 769 | std::wstring::size_type pos = 0u; 770 | for (int posCount = 0; posCount < offset; posCount = posCount + 1) { 771 | if ((pos = padding.find(L"X", pos)) != std::string::npos) { 772 | padding.replace(pos, 1, L"XX"); 773 | pos += executionLevel_.length(); 774 | } 775 | } 776 | 777 | // convert the wchar back into char, so that it encodes correctly for Windows to read the XML. 778 | std::wstring stringSectionW = fileContents + padding; 779 | std::wstring_convert, wchar_t> converter; 780 | std::string stringSection = converter.to_bytes(stringSectionW); 781 | 782 | if (!UpdateResourceW(ru.Get(), RT_MANIFEST, MAKEINTRESOURCEW(1), 783 | kLangEnUs, // this is hardcoded at 1033, ie, en-us, as that is what RT_MANIFEST default uses 784 | &stringSection.at(0), sizeof(char) * stringSection.size())) { 785 | return false; 786 | } 787 | } 788 | 789 | // update string table. 790 | for (const auto& i : stringTableMap_) { 791 | for (const auto& j : i.second) { 792 | std::vector stringTableBuffer; 793 | if (!SerializeStringTable(j.second, j.first, &stringTableBuffer)) { 794 | return false; 795 | } 796 | 797 | if (!UpdateResourceW(ru.Get(), RT_STRING, MAKEINTRESOURCEW(j.first + 1), i.first, 798 | &stringTableBuffer[0], static_cast(stringTableBuffer.size()))) { 799 | return false; 800 | } 801 | } 802 | } 803 | 804 | for (const auto& rcDataLangPair : rcDataLngMap_) { 805 | for (const auto&rcDataMap : rcDataLangPair.second) { 806 | if (!UpdateResourceW(ru.Get(), RT_RCDATA, reinterpret_cast(rcDataMap.first), 807 | rcDataLangPair.first, (LPVOID)rcDataMap.second.data(), rcDataMap.second.size())) { 808 | return false; 809 | } 810 | } 811 | } 812 | 813 | for (const auto& iLangIconInfoPair : iconBundleMap_) { 814 | auto langId = iLangIconInfoPair.first; 815 | auto maxIconId = iLangIconInfoPair.second.maxIconId; 816 | for (const auto& iNameBundlePair : iLangIconInfoPair.second.iconBundles) { 817 | UINT bundleId = iNameBundlePair.first; 818 | const std::unique_ptr& pIcon = iNameBundlePair.second; 819 | if (!pIcon) 820 | continue; 821 | 822 | auto& icon = *pIcon; 823 | // update icon. 824 | if (icon.grpHeader.size() > 0) { 825 | if (!UpdateResourceW(ru.Get(), RT_GROUP_ICON, MAKEINTRESOURCEW(bundleId), 826 | langId, icon.grpHeader.data(), icon.grpHeader.size())) { 827 | return false; 828 | } 829 | 830 | for (size_t i = 0; i < icon.header.count; ++i) { 831 | if (!UpdateResourceW(ru.Get(), RT_ICON, MAKEINTRESOURCEW(i + 1), 832 | langId, icon.images[i].data(), icon.images[i].size())) { 833 | 834 | return false; 835 | } 836 | } 837 | 838 | for (size_t i = icon.header.count; i < maxIconId; ++i) { 839 | if (!UpdateResourceW(ru.Get(), RT_ICON, MAKEINTRESOURCEW(i + 1), 840 | langId , nullptr , 0)) { 841 | return false; 842 | } 843 | } 844 | } 845 | } 846 | } 847 | 848 | return ru.Commit(); 849 | } 850 | 851 | bool ResourceUpdater::SerializeStringTable(const StringValues& values, UINT blockId, std::vector* out) { 852 | // calc total size. 853 | // string table is pascal string list. 854 | size_t size = 0; 855 | for (size_t i = 0; i < 16; i++) { 856 | size += sizeof(WORD); 857 | size += values[i].length() * sizeof(WCHAR); 858 | } 859 | 860 | out->resize(size); 861 | 862 | // write. 863 | char* pDst = &(*out)[0]; 864 | for (size_t i = 0; i < 16; i++) { 865 | WORD length = static_cast(values[i].length()); 866 | memcpy(pDst, &length, sizeof(length)); 867 | pDst += sizeof(WORD); 868 | 869 | if (length > 0) { 870 | WORD bytes = length * sizeof(WCHAR); 871 | memcpy(pDst, values[ i ].c_str(), bytes); 872 | pDst += bytes; 873 | } 874 | } 875 | 876 | return true; 877 | } 878 | 879 | // static 880 | BOOL CALLBACK ResourceUpdater::OnEnumResourceLanguage(HANDLE hModule, LPCWSTR lpszType, LPCWSTR lpszName, WORD wIDLanguage, LONG_PTR lParam) { 881 | ResourceUpdater* instance = reinterpret_cast(lParam); 882 | if (IS_INTRESOURCE(lpszName) && IS_INTRESOURCE(lpszType)) { 883 | switch (reinterpret_cast(lpszType)) { 884 | case reinterpret_cast(RT_VERSION): { 885 | try { 886 | instance->versionStampMap_[wIDLanguage] = VersionInfo(instance->module_, wIDLanguage); 887 | } catch (const std::system_error& e) { 888 | return false; 889 | } 890 | break; 891 | } 892 | case reinterpret_cast(RT_STRING): { 893 | UINT id = reinterpret_cast(lpszName) - 1; 894 | auto& vector = instance->stringTableMap_[wIDLanguage][id]; 895 | for (size_t k = 0; k < 16; k++) { 896 | CStringW buf; 897 | 898 | buf.LoadStringW(instance->module_, id * 16 + k, wIDLanguage); 899 | vector.push_back(buf.GetBuffer()); 900 | } 901 | break; 902 | } 903 | case reinterpret_cast(RT_ICON): { 904 | UINT iconId = reinterpret_cast(lpszName); 905 | UINT maxIconId = instance->iconBundleMap_[wIDLanguage].maxIconId; 906 | if (iconId > maxIconId) 907 | maxIconId = iconId; 908 | break; 909 | } 910 | case reinterpret_cast(RT_GROUP_ICON): { 911 | UINT iconId = reinterpret_cast(lpszName); 912 | instance->iconBundleMap_[wIDLanguage].iconBundles[iconId] = nullptr; 913 | break; 914 | } 915 | case reinterpret_cast(RT_RCDATA): { 916 | const auto moduleHandle = HMODULE(hModule); 917 | HRSRC hResInfo = FindResource(moduleHandle, lpszName, lpszType); 918 | DWORD cbResource = SizeofResource(moduleHandle, hResInfo); 919 | HGLOBAL hResData = LoadResource(moduleHandle, hResInfo); 920 | 921 | const auto *pResource = (const BYTE *)LockResource(hResData); 922 | const auto resId = reinterpret_cast(lpszName); 923 | instance->rcDataLngMap_[wIDLanguage][resId] = std::vector(pResource, pResource + cbResource); 924 | 925 | UnlockResource(hResData); 926 | FreeResource(hResData); 927 | } 928 | default: 929 | break; 930 | } 931 | } 932 | return TRUE; 933 | } 934 | 935 | // static 936 | BOOL CALLBACK ResourceUpdater::OnEnumResourceName(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam) { 937 | EnumResourceLanguagesW(hModule, lpszType, lpszName, (ENUMRESLANGPROCW) OnEnumResourceLanguage, lParam); 938 | return TRUE; 939 | } 940 | 941 | // static 942 | // courtesy of http://stackoverflow.com/questions/420852/reading-an-applications-manifest-file 943 | BOOL CALLBACK ResourceUpdater::OnEnumResourceManifest(HMODULE hModule, LPCTSTR lpType, LPWSTR lpName, LONG_PTR lParam) { 944 | ResourceUpdater* instance = reinterpret_cast(lParam); 945 | HRSRC hResInfo = FindResource(hModule, lpName, lpType); 946 | DWORD cbResource = SizeofResource(hModule, hResInfo); 947 | 948 | HGLOBAL hResData = LoadResource(hModule, hResInfo); 949 | const BYTE *pResource = (const BYTE *)LockResource(hResData); 950 | 951 | // FIXME(zcbenz): Do a real UTF string convertion. 952 | int len = strlen(reinterpret_cast(pResource)); 953 | std::wstring manifestStringLocal(pResource, pResource + len); 954 | 955 | // FIXME(zcbenz): Strip the BOM instead of doing string search. 956 | size_t start = manifestStringLocal.find(L" 0) { 958 | manifestStringLocal = manifestStringLocal.substr(start); 959 | } 960 | 961 | // Support alternative formatting, such as using " vs ' and level="..." on another line 962 | size_t found = manifestStringLocal.find(L"requestedExecutionLevel"); 963 | size_t level = manifestStringLocal.find(L"level=\"", found); 964 | size_t end = manifestStringLocal.find(L"\"", level + 7); 965 | if (level < 0) 966 | { 967 | level = manifestStringLocal.find(L"level=\'", found); 968 | end = manifestStringLocal.find(L"\'", level + 7); 969 | } 970 | 971 | instance->originalExecutionLevel_ = manifestStringLocal.substr(level + 7, end - level - 7); 972 | 973 | // also store original manifestString 974 | instance->manifestString_ = manifestStringLocal; 975 | 976 | UnlockResource(hResData); 977 | FreeResource(hResData); 978 | 979 | return TRUE; // Keep going 980 | } 981 | 982 | ScopedResourceUpdater::ScopedResourceUpdater(const WCHAR* filename, bool deleteOld) 983 | : handle_(BeginUpdateResourceW(filename, deleteOld)) { 984 | } 985 | 986 | ScopedResourceUpdater::~ScopedResourceUpdater() { 987 | if (!commited_) { 988 | EndUpdate(false); 989 | } 990 | } 991 | 992 | HANDLE ScopedResourceUpdater::Get() const { 993 | return handle_; 994 | } 995 | 996 | bool ScopedResourceUpdater::Commit() { 997 | commited_ = true; 998 | return EndUpdate(true); 999 | } 1000 | 1001 | bool ScopedResourceUpdater::EndUpdate(bool doesCommit) { 1002 | BOOL fDiscard = doesCommit ? FALSE : TRUE; 1003 | BOOL bResult = EndUpdateResourceW(handle_, fDiscard); 1004 | DWORD e = GetLastError(); 1005 | return bResult ? true : false; 1006 | } 1007 | 1008 | } // namespace rescle 1009 | --------------------------------------------------------------------------------