1 /*
   2 * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
   3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4 *
   5 * This code is free software; you can redistribute it and/or modify it
   6 * under the terms of the GNU General Public License version 2 only, as
   7 * published by the Free Software Foundation.  Oracle designates this
   8 * particular file as subject to the "Classpath" exception as provided
   9 * by Oracle in the LICENSE file that accompanied this code.
  10 *
  11 * This code is distributed in the hope that it will be useful, but WITHOUT
  12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14 * version 2 for more details (a copy is included in the LICENSE file that
  15 * accompanied this code).
  16 *
  17 * You should have received a copy of the GNU General Public License version
  18 * 2 along with this work; if not, write to the Free Software Foundation,
  19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20 *
  21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22 * or visit www.oracle.com if you need additional information or have any
  23 * questions.
  24 */
  25 
  26 #include "VersionInfoSwap.h"
  27 
  28 #include <stdio.h>
  29 #include <tchar.h>
  30 
  31 #include <windows.h>
  32 #include <stdio.h>
  33 #include <Strsafe.h>
  34 #include <fstream>
  35 #include <locale>
  36 #include <codecvt>
  37 
  38 
  39 /*
  40  * Usage: VersionInfoSwap.exe [Property file] [Executable file]
  41  *
  42  * [Property file] contains key/value pairs
  43  * The swap tool uses these pairs to create new version resource
  44  *
  45  * See MSDN docs for VS_VERSIONINFO structure that
  46  * depicts organization of data in this version resource
  47  *    https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
  48  *
  49  * The swap tool makes changes in [Executable file]
  50  * The tool assumes that the executable file has no version resource
  51  * and it adds new resource in the executable file.
  52  * If the executable file has an existing version resource, then
  53  * the existing version resource will be replaced with new one.
  54  *
  55  */
  56 
  57 bool VersionInfoSwap::PatchExecutable() {
  58     bool b = LoadFromPropertyFile();
  59     if (!b) {
  60         return false;
  61     }
  62 
  63     ByteBuffer buf;
  64     CreateNewResource(&buf);
  65     b = this->UpdateResource(buf.getPtr(), static_cast<DWORD>(buf.getPos()));
  66     if (!b) {
  67         return false;
  68     }
  69     return true;
  70 }
  71 
  72 bool VersionInfoSwap::LoadFromPropertyFile() {
  73 
  74     bool result = false;
  75     std::wifstream stream(m_propFileName.data());
  76 
  77     const std::locale empty_locale = std::locale::empty();
  78     const std::locale utf8_locale =
  79             std::locale(empty_locale, new std::codecvt_utf8<wchar_t>());
  80     stream.imbue(utf8_locale);
  81 
  82     if (stream.is_open() == true) {
  83         int lineNumber = 1;
  84         while (stream.eof() == false) {
  85             wstring line;
  86             std::getline(stream, line);
  87 
  88             // # at the first character will comment out the line.
  89             if (line.empty() == false && line[0] != '#') {
  90                 wstring::size_type pos = line.find('=');
  91                 if (pos != wstring::npos) {
  92                     wstring name = line.substr(0, pos);
  93                     wstring value = line.substr(pos + 1);
  94                     m_props[name] = value;
  95                 } else {
  96                     fwprintf(stderr, TEXT("Unable to find delimiter at line %d\n"), lineNumber);
  97                 }
  98             }
  99             lineNumber++;
 100         }
 101         result = true;
 102     } else {
 103         fwprintf(stderr, TEXT("Unable to read property file\n"));
 104     }
 105 
 106     return result;
 107 }
 108 
 109 
 110 /*
 111  * Creates new version resource
 112  *
 113  * MSND docs for VS_VERSION_INFO structure
 114  *     https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
 115  */
 116 void VersionInfoSwap::CreateNewResource(ByteBuffer *buf) {
 117     size_t versionInfoStart = buf->getPos();
 118     buf->AppendWORD(0);
 119     buf->AppendWORD(sizeof VS_FIXEDFILEINFO);
 120     buf->AppendWORD(0);
 121     buf->AppendString(TEXT("VS_VERSION_INFO"));
 122     buf->Align(4);
 123 
 124     VS_FIXEDFILEINFO fxi;
 125     FillFixedFileInfo(&fxi);
 126     buf->AppendBytes((BYTE*)&fxi, sizeof (VS_FIXEDFILEINFO));
 127     buf->Align(4);
 128 
 129     // String File Info
 130     size_t stringFileInfoStart = buf->getPos();
 131     buf->AppendWORD(0);
 132     buf->AppendWORD(0);
 133     buf->AppendWORD(1);
 134     buf->AppendString(TEXT("StringFileInfo"));
 135     buf->Align(4);
 136 
 137     // String Table
 138     size_t stringTableStart = buf->getPos();
 139     buf->AppendWORD(0);
 140     buf->AppendWORD(0);
 141     buf->AppendWORD(1);
 142 
 143     // "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP
 144     buf->AppendString(TEXT("040904B0"));
 145     buf->Align(4);
 146 
 147     // Strings
 148     std::vector<wstring> keys;
 149     for (std::map<wstring, wstring>::const_iterator it =
 150             m_props.begin(); it != m_props.end(); ++it) {
 151         keys.push_back(it->first);
 152     }
 153 
 154     for (size_t index = 0; index < keys.size(); index++) {
 155         wstring name = keys[index];
 156         wstring value = m_props[name];
 157 
 158         size_t stringStart = buf->getPos();
 159         buf->AppendWORD(0);
 160         buf->AppendWORD(static_cast<WORD>(value.length()));
 161         buf->AppendWORD(1);
 162         buf->AppendString(name);
 163         buf->Align(4);
 164         buf->AppendString(value);
 165         buf->ReplaceWORD(stringStart,
 166                 static_cast<WORD>(buf->getPos() - stringStart));
 167         buf->Align(4);
 168     }
 169 
 170     buf->ReplaceWORD(stringTableStart,
 171             static_cast<WORD>(buf->getPos() - stringTableStart));
 172     buf->ReplaceWORD(stringFileInfoStart,
 173             static_cast<WORD>(buf->getPos() - stringFileInfoStart));
 174 
 175     // VarFileInfo
 176     size_t varFileInfoStart = buf->getPos();
 177     buf->AppendWORD(1);
 178     buf->AppendWORD(0);
 179     buf->AppendWORD(1);
 180     buf->AppendString(TEXT("VarFileInfo"));
 181     buf->Align(4);
 182 
 183     buf->AppendWORD(0x24);
 184     buf->AppendWORD(0x04);
 185     buf->AppendWORD(0x00);
 186     buf->AppendString(TEXT("Translation"));
 187     buf->Align(4);
 188     // "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP
 189     buf->AppendWORD(0x0409);
 190     buf->AppendWORD(0x04B0);
 191 
 192     buf->ReplaceWORD(varFileInfoStart,
 193             static_cast<WORD>(buf->getPos() - varFileInfoStart));
 194     buf->ReplaceWORD(versionInfoStart,
 195             static_cast<WORD>(buf->getPos() - versionInfoStart));
 196 }
 197 
 198 void VersionInfoSwap::FillFixedFileInfo(VS_FIXEDFILEINFO *fxi) {
 199     wstring fileVersion;
 200     wstring productVersion;
 201     int ret;
 202 
 203     fileVersion = m_props[TEXT("FileVersion")];
 204     productVersion = m_props[TEXT("ProductVersion")];
 205 
 206     unsigned fv_1 = 0, fv_2 = 0, fv_3 = 0, fv_4 = 0;
 207     unsigned pv_1 = 0, pv_2 = 0, pv_3 = 0, pv_4 = 0;
 208 
 209     ret = _stscanf_s(fileVersion.c_str(),
 210             TEXT("%d.%d.%d.%d"), &fv_1, &fv_2, &fv_3, &fv_4);
 211     if (ret <= 0 || ret > 4) {
 212         fwprintf(stderr, TEXT("Unable to parse FileVersion value\n"));
 213     }
 214 
 215     ret = _stscanf_s(productVersion.c_str(),
 216             TEXT("%d.%d.%d.%d"), &pv_1, &pv_2, &pv_3, &pv_4);
 217     if (ret <= 0 || ret > 4) {
 218         fwprintf(stderr, TEXT("Unable to parse ProductVersion value\n"));
 219     }
 220 
 221     fxi->dwSignature = 0xFEEF04BD;
 222     fxi->dwStrucVersion = 0x00010000;
 223 
 224     fxi->dwFileVersionMS = MAKELONG(fv_2, fv_1);
 225     fxi->dwFileVersionLS = MAKELONG(fv_4, fv_3);
 226     fxi->dwProductVersionMS = MAKELONG(pv_2, pv_1);
 227     fxi->dwProductVersionLS = MAKELONG(pv_4, pv_3);
 228 
 229     fxi->dwFileFlagsMask = 0;
 230     fxi->dwFileFlags = 0;
 231     if (m_props.count(TEXT("PrivateBuild"))) {
 232         fxi->dwFileFlags |= VS_FF_PRIVATEBUILD;
 233     }
 234     if (m_props.count(TEXT("SpecialBuild"))) {
 235         fxi->dwFileFlags |= VS_FF_SPECIALBUILD;
 236     }
 237     fxi->dwFileOS = VOS_NT_WINDOWS32;
 238 
 239     wstring exeExt =
 240             m_exeFileName.substr(m_exeFileName.find_last_of(TEXT(".")));
 241     if (exeExt == TEXT(".exe")) {
 242         fxi->dwFileType = VFT_APP;
 243     }
 244     else if (exeExt == TEXT(".dll")) {
 245         fxi->dwFileType = VFT_DLL;
 246     }
 247     else {
 248         fxi->dwFileType = VFT_UNKNOWN;
 249     }
 250     fxi->dwFileSubtype = 0;
 251 
 252     fxi->dwFileDateLS = 0;
 253     fxi->dwFileDateMS = 0;
 254 }
 255 
 256 /*
 257  * Adds new resource in the executable
 258  */
 259 bool VersionInfoSwap::UpdateResource(LPVOID lpResLock, DWORD size) {
 260 
 261     HANDLE hUpdateRes;
 262     BOOL r;
 263 
 264     hUpdateRes = ::BeginUpdateResource(m_exeFileName.c_str(), FALSE);
 265     if (hUpdateRes == NULL) {
 266         fwprintf(stderr, TEXT("Could not open file for writing\n"));
 267         return false;
 268     }
 269 
 270     r = ::UpdateResource(hUpdateRes,
 271         RT_VERSION,
 272         MAKEINTRESOURCE(VS_VERSION_INFO),
 273         MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
 274         lpResLock,
 275         size);
 276 
 277     if (!r) {
 278         fwprintf(stderr, TEXT("Could not add resource\n"));
 279         return false;
 280     }
 281 
 282     if (!::EndUpdateResource(hUpdateRes, FALSE)) {
 283         fwprintf(stderr, TEXT("Could not write changes to file\n"));
 284         return false;
 285     }
 286 
 287     return true;
 288 }
 289 
 290 VersionInfoSwap::VersionInfoSwap(TCHAR *propFileName, TCHAR *exeFileName)
 291 {
 292     m_propFileName = propFileName;
 293     m_exeFileName = exeFileName;
 294 }
 295 
 296 VersionInfoSwap::~VersionInfoSwap()
 297 {
 298 }