Win32のバージョンリソース

EXEファイルにバージョンリソースを書き込むコードを書こうとググって見たところ、意外にもあまりまともな記事が見つからなかったので、自分で書いてみた。基本的には、VERSIONINFO ResourceからMSDNの記事をたどって、全体構造を把握するだけなんだけれども、何で、こんな基本的な事を手でやらないといけないのかはよく分からない。また、一部の数字は、実際のファイルをダンプして得られました。つまり、根拠が分からないものがあります。特に最後の2バイトとか。

いずれにしても、面倒なことは面倒なので、簡単なラッパークラスをつくった。ATLを使って手抜きをしているのはご愛敬。基本的には、CStringT::Formatと、CT2Wが使いたかっただけ。全部、_UNICODE/UNICODEでいいのであれば、適当に置換すれば使えます。

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <vector>
#include <map>
#include "atlbase.h"
#include "atlstr.h"

class VersionInfoBuilder
{
public:
  // Lang-ID: 0x409=英語(米国)
  VersionInfoBuilder(WORD langID = 0x409) : _langID(langID)
  {
    _fileVer[0] = _fileVer[1] = _fileVer[2] = _fileVer[3] = 0;
    _prodVer[0] = _prodVer[1] = _prodVer[2] = _prodVer[3] = 0;
  }

  void setProp(LPCTSTR name, LPCTSTR val)
  {
    _props[(LPCWSTR)CT2W(name)] = (LPCWSTR)CT2W(val);
  }
  
  void setFileVersion(WORD a, WORD b, WORD c, WORD d)
  {
    _fileVer[0] = a;
    _fileVer[1] = b;
    _fileVer[2] = c;
    _fileVer[3] = d;
  }

  void setProdVersion(WORD a, WORD b, WORD c, WORD d)
  {
    _prodVer[0] = a;
    _prodVer[1] = b;
    _prodVer[2] = c;
    _prodVer[3] = d;
  }
  
  void build(std::vector<BYTE>& buffer)
  {
    buildVerInfo();
    _buffer.swap(buffer);
  }
  
  bool updateExeFile(LPCTSTR fileName)
  {
    std::vector<BYTE> data;
    build(data);
    
    HANDLE hUpdate = BeginUpdateResource(fileName, FALSE);
    if(!hUpdate)
      return false;
    
    BOOL ok = UpdateResource(
      hUpdate, RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), _langID,
      &data[0], data.size());
    
    return EndUpdateResource(hUpdate, ok ? FALSE : TRUE) ? true : false;
  }

private:
  static const WORD CODEPAGE_UCS2LE = 0x4b0;
  WORD _langID;
  WORD _fileVer[4], _prodVer[4];
  typedef std::map<CStringW, CStringW> StrMap;
  StrMap _props;
  std::vector<BYTE> _buffer;
  
  void clear()
  {
    _buffer.clear();
  }
  
  void writeWord(size_t ptr, WORD w)
  {
    _buffer[ptr] = (BYTE)(w & 255);
    _buffer[ptr + 1] = (BYTE)(w >> 8);
  }
  
  size_t pushWord(WORD w)
  {
    _buffer.push_back(w & 255);
    _buffer.push_back(w >> 8);
    return _buffer.size() - 2;
  }
  
  size_t pushByte4(BYTE a, BYTE b, BYTE c, BYTE d)
  {
    _buffer.push_back(a);
    _buffer.push_back(b);
    _buffer.push_back(c);
    _buffer.push_back(d);
    return _buffer.size() - 4;
  }
  
  void align4()
  {
    size_t pos = _buffer.size() & 3;
    if(pos == 0)
      return;
    size_t padLen = 4 - pos;
    static const BYTE pads[] = {0, 0, 0, 0};
    _buffer.insert(_buffer.end(), pads, pads + padLen);
  }
  
  size_t pushStr(LPCWSTR str)
  {
    size_t pos = getPtr();
    for(;;)
    {
      WORD w = *str++;
      pushWord(w);
      if(!w)
        break;
    }
    align4();
    return pos;
  }
  
  size_t getPtr() const
  {
    return _buffer.size();
  }
  
  void writeLength(size_t ptr)
  {
    writeWord(ptr, getPtr() - ptr - 2);
  }
  
  CStringW verStr(const WORD* v) const
  {
    CStringW ver;
    ver.Format(L"%u, %u, %u, %u", v[0], v[1], v[2], v[3]);
    return ver;
  }

  void buildVerInfo()
  {
    _props[L"FileVersion"] = verStr(_fileVer);
    _props[L"ProductVersion"] = verStr(_prodVer);
    
    clear();
    
    size_t ptrWholeSize = pushWord(0); // whole size (dummy)
    pushWord(0x34);
    pushWord(0x00);
    pushStr(L"VS_VERSION_INFO");
    pushByte4(0xbd, 0x04, 0xef, 0xfe); // Signature BD04EFFE
    pushByte4(0, 0, 1, 0); // struct version
    pushWord(_fileVer[1]);
    pushWord(_fileVer[0]);
    pushWord(_fileVer[3]);
    pushWord(_fileVer[2]);
    pushWord(_prodVer[1]);
    pushWord(_prodVer[0]);
    pushWord(_prodVer[3]);
    pushWord(_prodVer[2]);
    pushByte4(0x17, 0, 0, 0); // file flags mask (0x17)
    pushByte4(0, 0, 0, 0); // file flags (0)
    pushByte4(4, 0, 0, 0); // OS: 32-bit Windows (even on x64)
    pushByte4(1, 0, 0, 0); // file type: App(1)
    pushByte4(0, 0, 0, 0); // sub-file type: Unknown(0)
    pushByte4(0, 0, 0, 0);
    pushByte4(0, 0, 0, 0);
    
    size_t ptrStrFileInfoSize = pushWord(0);
    pushByte4(0, 0, 1, 0);
    pushStr(L"StringFileInfo");
    
    size_t ptrStrTable = pushWord(0);
    pushByte4(0, 0, 1, 0);
    
    CStringW loc;
    loc.Format(L"%04x%04x", _langID, CODEPAGE_UCS2LE);
    pushStr(loc);
    
    for(StrMap::const_iterator it = _props.begin();
      it != _props.end();
      ++it)
    {
      size_t ptr = pushWord(0);
      size_t ptrValLen = pushWord(0);
      pushWord(1);
      pushStr(it->first);
      size_t ptrVal = getPtr();
      pushStr(it->second);
      size_t vsize = getPtr() - ptrVal;
      writeWord(ptrValLen, vsize / 2);
      writeLength(ptr);
    }
    
    writeLength(ptrStrTable);
    writeLength(ptrStrFileInfoSize);
    
    pushWord(0x44);
    pushWord(0);
    pushWord(1);
    pushStr(L"VarFileInfo");
    pushWord(0x24);
    pushWord(4);
    pushWord(0);
    pushStr(L"Translation");
    pushWord(_langID);
    pushWord(CODEPAGE_UCS2LE);
    pushWord(0x613c); // ???
    
    writeLength(ptrWholeSize);
  }
};

int _tmain(int argc, TCHAR* argv[])
{
  VersionInfoBuilder vib(0x409);

  // それぞれのプロパティを直接設定する
  vib.setProp(_T("Comments"), _T("Comments!!!"));
  vib.setProp(_T("CompanyName"), _T("CompanyName!!!"));
  vib.setProp(_T("FileDescription"), _T("FileDescription!!!"));
  vib.setProp(_T("InternalName"), _T("InternalName!!!"));
  vib.setProp(_T("LegalCopyright"), _T("LegalCopyright!!!"));
  vib.setProp(_T("LegalTrademarks"), _T("LegalTrademarks!!!"));
  vib.setProp(_T("OriginalFilename"), _T("OriginalFilename!!!"));
  vib.setProp(_T("PrivateBuild"), _T("PrivateBuild!!!"));
  vib.setProp(_T("ProductName"), _T("ProductName!!!"));
  vib.setProp(_T("SpecialBuild"), _T("SpecialBuild!!!"));
  vib.setFileVersion(1, 2, 3, 4);
  vib.setProdVersion(5, 6, 7, 8);
  
  // 指定されたファイルに埋め込む
  vib.updateExeFile(argv[1]);

  return 0;
}