InternetShortcutについて

※開発環境は、Windows2000sp4/IE6sp1/VC++6.0sp6/ATL3.0

InternetExplorerのインターネットショートカットである xxxx.url をメモ帳等で開くと、例えば、下記の内容が記載されている。

引用1:インターネットショートカット

[InternetShortcut]
URL=http://www.google.com/intl/ja/
Modified=80A0554EFCAEC0012E

ここで、URL= の個所は誰でも分かると思うが、Modified= については意味不明である。どうやら更新時刻らしいが、9バイト(注:16進数文字列2文字分を1バイトとして説明しています。)ある。ということは72ビットである。中途半端だ。このModified=についての情報を検索エンジンで検索してみたが、みんなよく分からないらしい(^^;

いくつか実験をしているうちに気が付いた。最初の4バイト分は大きく変化されるが、次の4バイト分はほとんど変化しない。この辺が何かヒントだろうか。最後の1バイトは頻繁に変更される。最初が時刻で次が日付で…最後は…不明。

ところで、インターネットショートカットごときでWindowsが猥雑な計算処理をしているとは考えにくい。時間関係でWindowsが即値で処理できるものをMSDNを見て実装を調べていると、FILETIME構造体がそれっぽい。実装は、

引用2:WinDef.h

typedef struct _FILETIME {
    DWORD dwLowDateTime;
    DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;
となっており、dwLowDateTimeが時刻で、dwHighDateTimeが日付で、の形にフィットするではないか。
# まぁ Oracleなどでは日付型は 年月日が整数部、時刻が小数部だというから似たようなものか。

そこで、まずは素直に値を埋めていった。

コード1:素直にぶっこめ!

    CString GetTime(LPCTSTR lpszModified)
    {
        union{
            BYTE byteValue[8];
            FILETIME ftmValue;
        } val = {0};

        TCHAR sz[3] = {0};
        for(int i = 0; i < 8; i++){
            sz[0] = lpszModified[0];
            sz[1] = lpszModified[1];
            val.byteValue[i] = static_cast<BYTE>(::strtoul(sz, NULL, 16));
            lpszModified += 2;
        }

        SYSTEMTIME sysTime = {0};
        ::FileTimeToSystemTime(&val.ftmValue, &sysTime);

        CString strTime;
        strTime.Format(_T("%04d/%02d/%02d(%.2s) %02d:%02d:%02d"),
            sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
            &_T("日月火水木金土")[sysTime.wDayOfWeek << 1], 
            sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
        return strTime;
    }

早速、about:blankのショートカットを作り(2004/04/24(土) 15:45:15)、実験してみる。

結果1:素直にぶっこまれた!

2004/04/24(土) 06:45:15

あれ? 9時間ズレてる。。9時間といえば、タイムゾーン。ってことはタイムゾーン分の時間を加減してやれば良い訳で。

コード2:タイムゾーンを考慮

    CString GetTime(LPCTSTR lpszModified)
    {
        union{
            BYTE byteValue[8];
            FILETIME ftmValue;
        } val = {0};

        TCHAR sz[3] = {0};
        for(int i = 0; i < 8; i++){
            sz[0] = lpszModified[0];
            sz[1] = lpszModified[1];
            val.byteValue[i] = static_cast<BYTE>(::strtoul(sz, NULL, 16));
            lpszModified += 2;
        }

        TIME_ZONE_INFORMATION tzi = {0};
        ::GetTimeZoneInformation(&tzi);

        SYSTEMTIME sysTime = {0};
        // WindowsNT3.5以降(2000,XP含)のみ使用可(MSDNより)
        ::SystemTimeToTzSpecificLocalTime(&tzi, &sysUniversalTime, &sysTime);

        CString strTime;
        strTime.Format(_T("%04d/%02d/%02d(%.2s) %02d:%02d:%02d"),
            sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
            &_T("日月火水木金土")[sysTime.wDayOfWeek << 1], 
            sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
        return strTime;
    }

先ほどの about:blankのショートカット(2004/04/24(土) 15:45:15)で再度実験してみる。

結果2:タームゾーンが考慮された!

2004/04/24(土) 15:45:15

ここで、ちょっと実験。時間を取得した全8バイトの値を加算合計してみよう。

実験

16進計算なので、Windows付属の関数電卓で計算。
Modified=405DD3075A2BC401C1
40+5D+D3+07+5A+2B+C4+01=2C1
2C1 & 0xFF == C1
ということで、最後の1バイトはチェックサムとの仮説が浮かんだ。いくつか試してみたところ、どうやら最後の1バイトはチェックサムであると判定できる結果が得られた。

残る問題点は、SystemTimeToTzSpecificLocalTime()関数はWinNT3.5以降のみ対応で、Win9Xには使えない模様。仕方ないので、そこは自分でコードを書くことにする。MSDNによれば、

引用3:SYSTEMTIME構造体

Remarks

It is not recommended that you add and subtract values from the SYSTEMTIME structure to obtain relative times. Instead, you should

  • Convert the SYSTEMTIME structure to a FILETIME structure.
  • Copy the resulting FILETIME structure to a LARGE_INTEGER structure.
  • Use normal 64-bit arithmetic on the LARGE_INTEGER value.
ということで、__int64型で演算すればいいらしい。

完成形を下記に示す。

コード3:Windows9Xにも対応

    // check OS Platform
    BOOL IsWinNT()
    {
        OSVERSIONINFO ovi = { sizeof(OSVERSIONINFO) };
        ::GetVersionEx(&ovi);
        return (VER_PLATFORM_WIN32_NT == ovi.dwPlatformId);   // defined in WinNT.H (2)

    }

    BOOL AddSecond(SYSTEMTIME* pSysTime, const LONG lSecond)
    {
        FILETIME fTime = {0};
        if(!::SystemTimeToFileTime(pSysTime, &fTime)){
            return FALSE;
        }

        // C言語では演算処理中の値がint型で表現できないとオーバーフローするため1つ1つ計算
        __int64 n64Value = 0, n64second = lSecond;
        n64second *= 10000000;
        n64Value = fTime.dwHighDateTime;
        n64Value <<= 32;
        n64Value += fTime.dwLowDateTime;
        n64Value += n64second;
        fTime.dwLowDateTime = n64Value & _UI32_MAX;  // defined in LIMITS.H (0xFFFFFFFFui32)
        fTime.dwHighDateTime = (n64Value >> 32);

        return ::FileTimeToSystemTime(&fTime, pSysTime);
    }

    // [InternetShortcut] の Modified= の値をローカルタイムに変換
    CString GetTime(LPCTSTR lpszModified)
    {
        union{
            BYTE byteValue[8];
            FILETIME ftmValue;
        } val = {0};

        DWORD dwCheckDigit = 0;

        TCHAR sz[3] = {0};
        for(int i = 0; i < 8; i++){
            sz[0] = lpszModified[0];
            sz[1] = lpszModified[1];
            val.byteValue[i] = static_cast<BYTE>(::strtoul(sz, NULL, 16));
            lpszModified += 2;
            dwCheckDigit += val.byteValue[i];
        }

        sz[0] = lpszModified[0];
        sz[1] = lpszModified[1];
        const DWORD dwCalc = static_cast<DWORD>(::strtoul(sz, NULL, 16));
        // 最後の1バイトは「他8バイトの加算合計の下位1バイト」
        dwCheckDigit &= 0xFF;
        ATLASSERT( dwCalc == dwCheckDigit );

        SYSTEMTIME sysUniversalTime = {0};
        ::FileTimeToSystemTime(&val.ftmValue, &sysUniversalTime);

        TIME_ZONE_INFORMATION tzi = {0};
        ::GetTimeZoneInformation(&tzi);

        SYSTEMTIME sysTime = {0};
        if(IsWinNT()){
            // WindowsNT3.5以降(2000,XP含)のみ使用可能
            ::SystemTimeToTzSpecificLocalTime(&tzi, &sysUniversalTime, &sysTime);
        }else{
            // Windows9X用
            sysTime = sysUniversalTime;
            AddSecond(&sysTime, tzi.Bias * -60);
        }

        CString strTime;
        strTime.Format(_T("%04d/%02d/%02d(%.2s) %02d:%02d:%02d"),
            sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
            &_T("日月火水木金土")[sysTime.wDayOfWeek << 1], 
            sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
        return strTime;
    }

[HOME]-[RTL]
Copyright© 2004 RAPT.