blob: 7b0b8fa5a74aadbd745c1643a65f289c26aa9705 [file] [log] [blame]
/*
* 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;
}