| /* |
| * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| #include <sstream> |
| #include <iomanip> |
| #include "VersionInfo.h" |
| #include "ResourceEditor.h" |
| #include "ErrorHandling.h" |
| #include "Toolbox.h" |
| |
| |
| VersionInfo::VersionInfo() { |
| |
| } |
| |
| namespace { |
| |
| class FixedFileVersion { |
| public: |
| FixedFileVersion(const std::wstring& value) { |
| if (4 != swscanf_s(value.c_str(), L"%d.%d.%d.%d", components + 0, |
| components + 1, components + 2, components + 3)) { |
| JP_THROW(tstrings::any() |
| << "Malformed file version value: [" |
| << value |
| << "]"); |
| forEach(components, [&value](int component) -> void { |
| if (USHRT_MAX < component) { |
| JP_THROW(tstrings::any() |
| << "Invalid file version value: [" |
| << value |
| << "]"); |
| } |
| }); |
| } |
| } |
| |
| void apply(DWORD& ms, DWORD& ls) const { |
| ms = MAKELONG(components[1], components[0]); |
| ls = MAKELONG(components[3], components[2]); |
| } |
| |
| private: |
| int components[4]; |
| }; |
| |
| |
| std::ostream& writeWORD(std::ostream& cout, size_t v) { |
| if (USHRT_MAX < v) { |
| JP_THROW("Invalid WORD value"); |
| } |
| return cout.write(reinterpret_cast<const char*>(&v), sizeof(WORD)); |
| } |
| |
| |
| std::ostream& writeDWORD(std::ostream& cout, size_t v) { |
| if (UINT_MAX < v) { |
| JP_THROW("Invalid DWORD value"); |
| } |
| |
| return cout.write(reinterpret_cast<const char*>(&v), sizeof(DWORD)); |
| } |
| |
| |
| std::ostream& write(std::ostream& cout, const VS_FIXEDFILEINFO& v) { |
| return cout.write(reinterpret_cast<const char*>(&v), sizeof(v)); |
| } |
| |
| std::ostream& write(std::ostream& cout, const std::wstring& s) { |
| return cout.write(reinterpret_cast<const char*>(s.c_str()), |
| (s.size() + 1 /* trailing 0 */) * sizeof(wchar_t)); |
| } |
| |
| void add32bitPadding(std::ostream& cout) { |
| enum { WordAlign = 2 }; |
| const std::streampos pos = cout.tellp(); |
| if (pos % 2) { |
| JP_THROW("Invalid data written in the stream"); |
| } |
| const int padding = WordAlign - (pos / 2) % WordAlign; |
| if (WordAlign != padding) { |
| for (int i = 0; i < padding; ++i) { |
| writeWORD(cout, 0); |
| } |
| } |
| } |
| |
| |
| class StreamSize { |
| public: |
| StreamSize(std::ostream& out): stream(out), anchor(out.tellp()) { |
| writeWORD(stream, 0); // placeholder |
| } |
| |
| ~StreamSize() { |
| JP_TRY; |
| |
| const std::streampos curPos = stream.tellp(); |
| const std::streampos size = curPos - anchor; |
| stream.seekp(anchor); |
| if (size < 0) { |
| JP_THROW("Invalid negative size value"); |
| } |
| writeWORD(stream, (size_t) size); |
| stream.seekp(curPos); |
| |
| JP_CATCH_ALL; |
| } |
| |
| private: |
| std::ostream& stream; |
| std::streampos anchor; |
| }; |
| |
| } // namespace |
| |
| VersionInfo& VersionInfo::setProperty( |
| const std::wstring& id, const std::wstring& value) { |
| props[id] = value; |
| |
| if (id == L"FIXEDFILEINFO_FileVersion") { |
| // Validate input |
| const ::FixedFileVersion validate(value); |
| } |
| return *this; |
| } |
| |
| |
| const VersionInfo& VersionInfo::apply( |
| const ResourceEditor::FileLock& fileLock) const { |
| if (props.find(L"FIXEDFILEINFO_FileVersion") == props.end()) { |
| JP_THROW("Missing mandatory FILEVERSION property"); |
| } |
| |
| std::stringstream buf( |
| std::stringstream::in | std::stringstream::out | std::stringstream::binary); |
| buf.exceptions(std::ios::failbit | std::ios::badbit); |
| |
| fillBuffer(buf); |
| |
| buf.seekg(0); |
| |
| ResourceEditor() |
| .id(MAKEINTRESOURCE(VS_VERSION_INFO)) |
| .type(RT_VERSION) |
| .apply(fileLock, buf); |
| return *this; |
| } |
| |
| |
| void VersionInfo::fillBuffer(std::ostream& buf) const { |
| // Fill VS_VERSIONINFO pseudo structure |
| StreamSize versionInfoLength(buf); // wLength |
| writeWORD(buf, sizeof(VS_FIXEDFILEINFO)); // wValueLength |
| writeWORD(buf, 0); // wType |
| write(buf, L"VS_VERSION_INFO"); // szKey |
| add32bitPadding(buf); // Padding1 |
| write(buf, createFIXEDFILEINFO()); // Value |
| add32bitPadding(buf); // Padding2 |
| |
| const DWORD neutralLangId = (0x04b0 | MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) << 16); |
| const DWORD engLangId = (0x04b0 | MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16); |
| |
| do { |
| // Fill StringFileInfo pseudo structure |
| StreamSize stringFileInfoLength(buf); // wLength |
| writeWORD(buf, 0); // wValueLength |
| writeWORD(buf, 1); // wType |
| write(buf, L"StringFileInfo"); // szKey |
| add32bitPadding(buf); // Padding |
| |
| // Fill StringTable pseudo structure |
| StreamSize stringTableLength(buf); // wLength |
| writeWORD(buf, 0); // wValueLength |
| writeWORD(buf, 1); // wType |
| |
| std::wstringstream strLangIdBuf; |
| strLangIdBuf |
| << std::uppercase |
| << std::hex |
| << std::setw(8) |
| << std::setfill(L'0') |
| << engLangId; |
| const std::wstring strLangId = strLangIdBuf.str(); |
| write(buf, strLangId); // szKey |
| add32bitPadding(buf); // Padding |
| |
| forEach(props, [&buf](const PropertyMap::value_type& entry) -> void { |
| if (entry.first.rfind(L"FIXEDFILEINFO_", 0) == 0) { |
| // Ignore properties to be used to initialize data in |
| // VS_FIXEDFILEINFO structure. |
| return; |
| } |
| |
| // Fill String pseudo structure |
| StreamSize stringLength(buf); // wLength |
| writeWORD(buf, entry.second.size()); // wValueLength |
| writeWORD(buf, 1); // wType |
| write(buf, entry.first); // wKey |
| add32bitPadding(buf); // Padding1 |
| write(buf, entry.second); // Value |
| add32bitPadding(buf); // Padding2 |
| }); |
| } while (0); |
| |
| // Fill VarFileInfo pseudo structure |
| StreamSize varFileInfoLength(buf); // wLength |
| writeWORD(buf, 0); // wValueLength |
| writeWORD(buf, 1); // wType |
| write(buf, L"VarFileInfo"); // szKey |
| add32bitPadding(buf); // Padding |
| |
| // Fill Var pseudo structure |
| StreamSize varLength(buf); // wLength |
| writeWORD(buf, sizeof(DWORD)); // wValueLength |
| writeWORD(buf, 0); // wType |
| write(buf, L"Translation"); // szKey |
| add32bitPadding(buf); // Padding |
| writeDWORD(buf, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); // Value |
| } |
| |
| |
| VS_FIXEDFILEINFO VersionInfo::createFIXEDFILEINFO() const { |
| const ::FixedFileVersion fileVersion(props.find( |
| L"FIXEDFILEINFO_FileVersion")->second); |
| |
| VS_FIXEDFILEINFO result; |
| ZeroMemory(&result, sizeof(result)); |
| |
| result.dwSignature = 0xFEEF04BD; |
| result.dwStrucVersion = 0x00010000; |
| result.dwFileOS = VOS_NT_WINDOWS32; |
| result.dwFileType = VFT_APP; |
| |
| fileVersion.apply(result.dwFileVersionMS, result.dwFileVersionLS); |
| |
| PropertyMap::const_iterator entry = props.find( |
| L"FIXEDFILEINFO_ProductVersion"); |
| if (entry == props.end()) { |
| fileVersion.apply(result.dwProductVersionMS, result.dwProductVersionLS); |
| } else { |
| bool fatalError = false; |
| try { |
| const ::FixedFileVersion productVersion(entry->second); |
| fatalError = true; |
| productVersion.apply(result.dwProductVersionMS, |
| result.dwProductVersionLS); |
| } catch (const std::exception&) { |
| if (fatalError) { |
| throw; |
| } |
| // Failed to parse product version as four component version string. |
| fileVersion.apply(result.dwProductVersionMS, |
| result.dwProductVersionLS); |
| } |
| } |
| |
| return result; |
| } |