Author Topic: How to REALLY get the version hash..  (Read 8695 times)

0 Members and 2 Guests are viewing this topic.

Offline Joe

  • B&
  • x86
  • Hero Member
  • *****
  • Posts: 10319
  • In Soviet Russia, text read you!
    • View Profile
    • Github
How to REALLY get the version hash..
« on: May 01, 2007, 01:37:57 am »
Too many people hardcode it. Do it the real way! :)

Code: [Select]
using System;
using System.Collections.Generic;
using System.Text;

using System.Runtime.InteropServices;

namespace VersionHash
{
    class VersionHash
    {

        #region Win32 Imports
        /// <summary>
        /// Determines whether the operating system can retrieve version information for a
        /// specified file. If version information is available, GetFileVersionInfoSize returns
        /// the size, in bytes, of that information.
        /// </summary>
        /// <param name="lptstrFilename">[in] Pointer to a null-terminated string that specifies
        /// the name of the file of interest. The function uses the search sequence specified by
        /// the LoadLibrary function.</param>
        /// <param name="lpdwHandle">[out] Pointer to a variable that the function sets to
        /// zero.</param>
        /// <returns>If the function succeeds, the return value is the size, in bytes, of the
        /// file's version information. If the function fails, the return value is zero. To get
        /// extended error information, call GetLastError. </returns>
        [DllImport("version.dll")]
        private static extern int GetFileVersionInfoSize(string lptstrFilename, ref int lpdwHandle);

        /// <summary>
        /// Retrieves version information for the specified file.
        /// </summary>
        /// <param name="lptstrFilename">[in] Pointer to a null-terminated string that specifies the
        /// name of the file of interest. If a full path is not specified, the function uses the
        /// search sequence specified by the LoadLibrary function.</param>
        /// <param name="dwHandle">[in] This parameter is ignored.</param>
        /// <param name="dwLen">[in] Specifies the size, in bytes, of the buffer pointed to by the
        /// lpData parameter. Call the GetFileVersionInfoSize function first to determine the size,
        /// in bytes, of a file's version information. The dwLen member should be equal to or
        /// greater than that value. If the buffer pointed to by lpData is not large enough,
        /// the function truncates the file's version information to the size of the
        /// buffer.</param>
        /// <param name="lpData">[out] Pointer to a buffer that receives the file-version information.
        /// You can use this value in a subsequent call to the VerQueryValue function to retrieve
        /// data from the buffer.</param>
        /// <returns>f the function succeeds, the return value is nonzero. If the function fails,
        /// the return value is zero. To get extended error information, call
        /// GetLastError.</returns>
        [DllImport("version.dll")]
        private static extern bool GetFileVersionInfo(string lptstrFilename, int dwHandle, int dwLen, IntPtr lpData);

        /// <summary>
        /// The VerQueryValue function retrieves specified version information from the specified
        /// version-information resource. To retrieve the appropriate resource, before you call
        /// VerQueryValue, you must first call the GetFileVersionInfoSize function, and then the
        /// GetFileVersionInfo function.
        /// </summary>
        /// <param name="pBlock">[in] Pointer to the buffer containing the version-information
        /// resource returned by the GetFileVersionInfo function.</param>
        /// <param name="lpSubBlock">[in] Pointer to a zero-terminated string specifying which
        /// version-information value to retrieve. The string must consist of names separated by
        /// backslashes (\) and it must have one of
        /// <a href=http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/resources/versioninformation/versioninformationreference/versioninformationfunctions/verqueryvalue.asp?frame=true>these</a>
        /// forms.</param>
        /// <param name="lplpBuffer">When this method returns, contains the address of a pointer
        /// to the requested version information in the buffer pointed to by pBlock. The memory
        /// pointed to by lplpBuffer is freed when the associated pBlock memory is freed."</param>
        /// <param name="puLen">When this method returns, contains a pointer to the size of the
        /// requested data pointed to by lplpBuffer: for version information values, the length in
        /// TCHARs of the string stored at lplpBuffer; for translation array values, the size in
        /// bytes of the array stored at lplpBuffer; and for root block, the size in bytes of the
        /// structure.</param>
        [DllImport("version.dll")]
        private static extern bool VerQueryValue(IntPtr pBlock, char[] lpSubBlock, ref IntPtr lplpBuffer, ref int puLen);
        #endregion

        /// <summary>
        /// How obvious can this be?
        /// </summary>
        /// <param name="filename">Path to the EXE file</param>
        /// <returns>Version Hash</returns>
        public static int GetVersionHash(string filename)
        {
            int dwHandle = 0;   // This is never used for anything, but is needed anyhow -- stupid DLL

            int infoSize = GetFileVersionInfoSize(filename, ref dwHandle);
            if (infoSize > 0)
            {
                IntPtr infoBuffer = Marshal.AllocHGlobal(infoSize);
                if (GetFileVersionInfo(filename, dwHandle, infoSize, infoBuffer))
                {
                    IntPtr infoBuffer2 = Marshal.AllocHGlobal(infoSize);
                    int bufferSize = 0;
                    if (VerQueryValue(infoBuffer, new char[] { '\\' }, ref infoBuffer2, ref bufferSize))
                    {
                        byte[] buffer = new byte[bufferSize];
                        Marshal.Copy(infoBuffer2, buffer, 0, bufferSize);

                        return (buffer[18] << 24) | (buffer[16] << 16) | (buffer[22] << 8) | buffer[20];
                    }
                }
            }
            return 0;
        }

    }
}

Neat little wrapper:
Code: [Select]
using System;
using System.Collections.Generic;
using System.Text;

namespace VersionHash
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = "C:\\Program Files\\StarCraft\\starcraft.exe";
           
            Console.WriteLine(path);
            Console.WriteLine("Verhash: 0x" + VersionHash.GetVersionHash(path).ToString("X"));
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}
I'd personally do as Joe suggests

You might be right about that, Joe.


Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: How to REALLY get the version hash..
« Reply #1 on: May 01, 2007, 09:04:40 am »
Now, try it in Java!

Hint: you have to parse the PE header.

Code: [Select]
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.LinkedList;

public class PE
{
    /** The offset of the PE section in the file */
    final static private int PE_START = 0x3c;
    /** The code that identifies the version */
    final static private int RT_VERSION = 16;
    /** The string that identifies rsrc (".rsrc\0\0\0") */
    final static private long rsrc = 0x000000637273722EL;
   
    /** This is the entry point to the whole file version function set.  It takes the filename as a parameter
     * and returns the version.  If the version can't be returned for whatever reason, an IOException is
     * thrown.
     *
     * @param filename The filename to get the version of.
     * @return The version of the file, as an integer.
     * @throws IOException If the version can't be retrieved for some reason.
     */
    public static int getVersion(String filename) throws IOException
    {
        /* psStart is the pointer to the first byte in the PE data.  The byte is pointed to at 0x3c */
        int peStart;
        /* The signature of the pe file, PE\0\0 */
        int peSignature;
        /* The number of sections (.data, .text, .rsrc, etc) */
        short numberOfSections;
        /* A pointer to the "optional" header (which will always be present in .exe files */
        int ptrOptionalHeader;

        /* The file we're reading from.  TODO: Make this a random access file. */
        MappedByteBuffer file = new FileInputStream(filename).getChannel().map(FileChannel.MapMode.READ_ONLY, 0, new File(filename).length());
        /* Set the file ordering to little endian */
        file.order(ByteOrder.LITTLE_ENDIAN);

        /* The start of the PE is pointed at by the 0x3c'th byte */
        peStart = file.getInt(PE_START);

        /* The first 4 bytes are the signature */
        peSignature = file.getInt(peStart + 0);


        /* Verify that it's a valid pe file.  IF not, throw an exception */
        if(peSignature != 0x00004550)
            throw new IOException("Invalid PE file!");

        /* The number of sections is the short starting at the 6th byte */
        numberOfSections = file.getShort(peStart + 6);

        /* Get a pointer to the optional header */
        ptrOptionalHeader = peStart + 24;



        return processOptionalHeader(file, ptrOptionalHeader, numberOfSections);
    }

    /** This reads the optional header and returns the version, or throws an IOException if the version
     * could not be found.
     *
     * @param file The file we're reading from.
     * @param ptrOptionalHeader A pointer to the optional header.
     * @param numberOfSections The number of sections that we will need to process.
     * @return The version, always.
     * @throws IOException If the version couldn't be found.
     */
    private static int processOptionalHeader(MappedByteBuffer file, int ptrOptionalHeader, int numberOfSections) throws IOException
    {
        /* Set to true if this is a PE+ file.  Some addresses are slightly different.  Untested!  */
        boolean plus;
        /* The number of RVA entries.  We don't care what the entries are, so we just skip right over them.  */
        int numberOfRvaAndSizes;
        /* A pointer to the table of sections.  This, along with the number of sections, is passed to the next
         * function. */
        int ptrSectionTable;
        /* The version, which will eventually be returned. */
        int version;

        /* PE+ files have the first ("magic") byte set to 0x20b */
        plus = file.getShort(ptrOptionalHeader) == 0x020b;
        /* Get the RVA counts from the optional header */
        numberOfRvaAndSizes = file.getInt(ptrOptionalHeader + (plus ? 108 : 92));

        /* The optional header is 96 bytes, and each RVA is 8 bytes.  Skip over them all. */
        ptrSectionTable = ptrOptionalHeader + 96 + (numberOfRvaAndSizes * 8);

        /* Get the version from the sections */
        version = processSections(file, ptrSectionTable, numberOfSections);

        /* If the version wasn't found, throw an exception */
        if(version == 0)
            throw new IOException("Couldn't find .rsrc section!");

        return version;

    }

    /** Step through the table of sections, looking for .rsrc.  When the .rsrc sections is found, call
     * another function to process it.
     *
     * @param file The file we're reading from
     * @param sectionsBase A pointer to the beginning of the sections.
     * @param numberOfSections The number of sections.
     * @return The version number, or 0 if it couldn't be found.
     * @throws IOException If there is a problem finding the version.
     */
    private static int processSections(MappedByteBuffer file, int sectionsBase, int numberOfSections) throws IOException
    {
        /* The location where the loaded RVA will be */
        int virtualStart;
        /* The location in the file where the data starts */
        int rawStart;
        /* The difference between the virtual start and the raw start */
        int rsrcVirtualToRaw;
        /* A pointer to the beginning of the section */
        int sectionBase;

        /* Loop over the sections */
        for(int i = 0; i < numberOfSections; i++)
        {
            /* Get the virtual address where the section starts */
            virtualStart = file.getInt(sectionsBase + (i * 40) + 12);
            /* Get the raw address where the section starts */
            rawStart = file.getInt(sectionsBase + (i * 40) + 20);
            /* Get the base of the section */
            sectionBase = sectionsBase + (i * 40);
            /* Find the difference between the actual location of rsrc and the virtual location of it */
            rsrcVirtualToRaw = rawStart - virtualStart;

            /* If we've found the rsrc section, process it.  If not, we really don't care. */
            if(file.getLong(sectionsBase + (i * 40)) == rsrc)
                return processResourceRecord(new LinkedList<Integer>(), file, 0, file.getInt(sectionBase + 20), rsrcVirtualToRaw);
        }

        return 0;
    }

    /** This indirectly recursive function walks the resource tree by calling processEntry which, in turn,
     * calls it.  The function looks specifically for the version section.  As soon as it finds a leaf node
     * for a version section, it returns the data all the way back up the resursive stack.  The recursion
     * will never go deeper than 3 levels.   
     *
     * @param tree The "tree" up to this point -- a maximum of 3 levels.
     * @param file The file we're processing.
     * @param recordOffset The offset of the record that we're going to process.  It's added to the rsrcStart
     *  value to get a pointer.
     * @param rsrcStart The very beginning of the rsrc section.  Used as a base value.
     * @param rsrcVirtualToRaw The value that has to be added to the virtual section to get the raw section.
     * @return The version, or 0 if it couldn't be found.
     * @throws IOException If there's an error finding the version.
     */
    private static int processResourceRecord(LinkedList <Integer> tree, MappedByteBuffer file, int recordOffset, int rsrcStart, int rsrcVirtualToRaw) throws IOException
    {
        int i;

        /* The recordOffset is an offset from the start or rsrc, so calculate it as such */
        int recordAddress = recordOffset + rsrcStart;
        /* The number of name entries that we're going to have to skip over */
        short numberNameEntries = file.getShort(recordAddress + 12);
        /* The number of ID entires that we're going to have to search through */
        short numberIDEntries = file.getShort(recordAddress + 14);
        /* A pointer to the start of the ID entries, right after the name entries */
        int ptrIDEntriesBase;
        /* A pointer to the current entry we're looking at */
        int entry;
        /* Stores the version so we can check if we're finished */
        int version;

        /* The header is 16 bytes, and each name entry is 8 bytes.  Skip them. */
        ptrIDEntriesBase = recordAddress + 16  + (numberNameEntries * 8);

        /* Loop through the id entries, which come right after the name entries */
        for(i = 0; i < numberIDEntries; i++)
        {
            /* Each entry is 8 bytes, skip over the ones we've already seen */
            entry = ptrIDEntriesBase + (i * 8);
            /* Process the entry.  processEntry() will call processResourceRecord() again for branches. */
            version = processEntry(new LinkedList<Integer>(tree), file, entry, rsrcStart, rsrcVirtualToRaw);
            /* If we've found the version, return it immediately.  Otherwise, keep looping. */
            if(version != 0)
                return version;
        }


        return 0;
    }

    /** Process an entry recursively.  If a leaf node is found, and it's the version node, return it.
     *
     * @param tree The list of nodes we've been to.
     * @param file The file we're processing.
     * @param entry A pointer to the start of the entry.
     * @param rsrcStart A pointer to the beginning of the rsrc section.
     * @param rsrcVirtualToRaw The conversion between the virtual and raw address.
     * @return The version, or 0 if it wasn't found in this entry.
     * @throws IOException If there's an error finding the version.
     */
    private static int processEntry(LinkedList<Integer> tree, MappedByteBuffer file, int entry, int rsrcStart, int rsrcVirtualToRaw) throws IOException
    {
        /* The address of the next node, or the address of the data.  The left-most bit tells us which
         * address this actually is. */
        int nextAddress = file.getInt(entry + 4);
        /* The version is stored in this so it can be returned. */
        int version;
        /* The size of the data */
        int dataSize;
        /* The buffer where we store the data (will be dataSize bytes) */
        byte []buffer;
        /* The address of the data within the file (converted from the RVA) */
        int rawDataAddress;

        /* Add the identifier to the tree */
        tree.addLast(file.getInt(entry + 0));

        /* Check if it's a branch by checking the left-most bit.  If it's set, it's a branch. */
        if((nextAddress & 0x80000000) != 0)
        {
            /* It's a branch, so move down to the next level. */
            version = processResourceRecord(tree, file, nextAddress & 0x7FFFFFFF, rsrcStart, rsrcVirtualToRaw);

            /* We found the version and don't care about anything else */
            if(version != 0)
                return version;
        }
        else
        {
            /* Its a leaf, check if it's RT_VERSION.  If it is, we're done! */
            if(tree.get(0) == RT_VERSION)
            {
                /* Convert the relative address to the actual address by using rsrcVirtualToRaw */
                rawDataAddress = file.getInt(rsrcStart + nextAddress) + rsrcVirtualToRaw;
                /* Get the data size */
                dataSize = file.getInt(rsrcStart + nextAddress + 4);
                /* Allocate memory in the buffer to store the incoming data */
                buffer = new byte[dataSize];

                /* Set the position in the file to the address of the data */
                /* Get the data */
                file.position(rawDataAddress);
                file.get(buffer);

                /* Combine the data and return it. */
                return  (buffer[0x3C] <<  0)  & 0x000000FF |
                        (buffer[0x3E] <<  8)  & 0x0000FF00 |
                        (buffer[0x38] << 16)  & 0x00FF0000 |
                        (buffer[0x3A] << 24)  & 0xFF000000;
            }
        }

        return 0;
    }
// 
//  public static void main(String []args) throws IOException
//  {
//      System.out.println(String.format("%08x", getVersion("/home/ron/.hashes/STAR/starcraft.exe")));
//      System.out.println(String.format("%08x", getVersion("/home/ron/.hashes/W2BN/Warcraft II BNE.exe")));
//      System.out.println(String.format("%08x", getVersion("/home/ron/.hashes/WAR3/war3.exe")));
//      System.out.println(String.format("%08x", getVersion("/home/ron/.hashes/D2DV/Game.exe")));
//     
//      System.out.println(0x0b030101 == getVersion("/home/ron/.hashes/STAR/starcraft.exe"));
//      System.out.println(0x00020002 == getVersion("/home/ron/.hashes/W2BN/Warcraft II BNE.exe"));
//      System.out.println(0xb1021401 == getVersion("/home/ron/.hashes/WAR3/war3.exe"));
//      System.out.println(0x000b0001 == getVersion("/home/ron/.hashes/D2DV/Game.exe"));
//     
//      System.out.println(0x0101030b == getVersion("/home/ron/.hashes/STAR/starcraft.exe"));
//      System.out.println(0x02000200 == getVersion("/home/ron/.hashes/W2BN/Warcraft II BNE.exe"));
//      System.out.println(0x011402b1 == getVersion("/home/ron/.hashes/WAR3/war3.exe"));
//      System.out.println(0x01000b00 == getVersion("/home/ron/.hashes/D2DV/Game.exe"));
//  }
}

Believe me, it's fun++ to parse PE headers!

I actually learned a ton coding that, so I can't complain too much. :)

Offline Chavo

  • x86
  • Hero Member
  • *****
  • Posts: 2219
  • no u
    • View Profile
    • Chavoland
Re: How to REALLY get the version hash..
« Reply #2 on: May 01, 2007, 10:37:53 am »
l2blockcomment?

:P

Offline Joe

  • B&
  • x86
  • Hero Member
  • *****
  • Posts: 10319
  • In Soviet Russia, text read you!
    • View Profile
    • Github
Re: How to REALLY get the version hash..
« Reply #3 on: May 01, 2007, 11:38:45 am »
In my code? Sorry, no can do. That's how C# handles it's XML documentation. In the IDE, you can collapse it all though, so it's still good.
I'd personally do as Joe suggests

You might be right about that, Joe.


Offline Sidoh

  • Moderator
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: How to REALLY get the version hash..
« Reply #4 on: May 01, 2007, 12:22:20 pm »
l2blockcomment?

:P

I'm pretty sure there's a sequence in vim that does that for you (the line-style comments on selected text).

Errr... were you talking about the method at the end of iago's code?  I'd expect you to recognize the "///" comments from Doxygen/javadoc?  Maybe they're not as common as I've been led to believe.
« Last Edit: May 01, 2007, 12:25:29 pm by Sidoh »

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: How to REALLY get the version hash..
« Reply #5 on: May 01, 2007, 01:10:44 pm »
I doubt he was talking about my code, but I still feel the need to defend it.

I have a unique and fairly consistent commenting style. I comment functions with /** .. */ (which is a habit I picked up from JavaDoc), I comment lines with /* ... */ (which is a habit I picked up from C), and I comment test/disabled code with // at the start of the line. That way it's easy to recognize commented text, and it's also easy to comment or uncomment in vim (s/^/\/\//g to comment, and %s/^\/\///g to uncomment).

I don't normally provide javadoc-style parameter comments, and I don't usually comment everything I do, but in this case the code was meant to be a demonstration for others (it's posted somewhere on the Tutorials board, I think), so I did that.

Again, I don't think he was talking about me, but I still wanted to talk about that. In the case of other people, I don't care how anybody comments. Some people like //, and some people like /* */. I prefer the latter, even when it's a single-line comment, but I'm old-fashioned that way. :)

Offline Chavo

  • x86
  • Hero Member
  • *****
  • Posts: 2219
  • no u
    • View Profile
    • Chavoland
Re: How to REALLY get the version hash..
« Reply #6 on: May 01, 2007, 02:43:21 pm »
I was talking about your code.  I think that your reason makes sense.....if you were forced to use vim.  Use a decent IDE so you don't have to do ridiculous things like come up with your own commenting conventions that add considerably more typing :)

Offline Sidoh

  • Moderator
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: How to REALLY get the version hash..
« Reply #7 on: May 01, 2007, 03:19:00 pm »
Wait, are you insulting vim?  vim is an extraordinary editor...

You can add macros to automate all of that. :P

Offline Chavo

  • x86
  • Hero Member
  • *****
  • Posts: 2219
  • no u
    • View Profile
    • Chavoland
Re: How to REALLY get the version hash..
« Reply #8 on: May 01, 2007, 03:39:25 pm »
I hate vim.  I know it has amazing potential but the learning curve and lack of intuitive design puts me completely off it.

Offline Sidoh

  • Moderator
  • Hero Member
  • *****
  • Posts: 17634
  • MHNATY ~~~~~
    • View Profile
    • sidoh
Re: How to REALLY get the version hash..
« Reply #9 on: May 01, 2007, 03:42:33 pm »
I hate vim.  I know it has amazing potential but the learning curve and lack of intuitive design puts me completely off it.

I love vim. :(

I have a cheat sheet I printed off taped to my wall for reference.

Haha, there's a guy in one of my CS classes who's been using it since he was like eight years old (he's 22 or something now).  Seriously, he's like an orchestrator on it. :-|

I agree, though, it's pretty hard to get used to.  That said, I find it difficult to use other text editors (for programming, anyway...) after using vim for a while.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: How to REALLY get the version hash..
« Reply #10 on: May 01, 2007, 04:12:43 pm »
I was talking about your code.  I think that your reason makes sense.....if you were forced to use vim.  Use a decent IDE so you don't have to do ridiculous things like come up with your own commenting conventions that add considerably more typing :)
I have no idea what you're talking about. Learn to quote.

Commenting conventions help me understand my code better, and I use the same conventions in vim, Eclipse, Visual Studio, Netbeans, and every other IDE I've ever used. What, exactly, is wrong with them?

And how do you mean more typing? If you're talking about the "main" function, I used a regex to comment it (or, if I was using Eclipse, I would do esc+ctrl c).

And incidentally, looking back, I WAS using Eclipse for that, which, like I just said, automates commenting.

Plus, vim is an amazing editor. If you're too lazy to learn how to use it right, that's fine, but don't go complaining to people who've bothered to learn useful tools. You'll find that vim users are defensive, and they have a good reason -- vim is the most amazing editor I've ever used, I love it way more than anything else. All it takes is a little bit of practice.

Also keep in mind how vim was designed -- to run over a slow network connection for people who are experienced with computers. As a result, they choose short and efficient commands over verbose and intuitive ones. That may put off beginners, but realistically it doesn't take more than a few hours of using it to master the easy commands.

Offline Chavo

  • x86
  • Hero Member
  • *****
  • Posts: 2219
  • no u
    • View Profile
    • Chavoland
Re: How to REALLY get the version hash..
« Reply #11 on: May 01, 2007, 04:23:42 pm »
Sigh... if my point isn't obvious than forget it.  It's not worth starting an argument that can't be won about.

Offline MyndFyre

  • Boticulator Extraordinaire
  • x86
  • Hero Member
  • *****
  • Posts: 4540
  • The wait is over.
    • View Profile
    • JinxBot :: the evolution in boticulation
Re: How to REALLY get the version hash..
« Reply #12 on: May 02, 2007, 12:11:16 pm »
What value does this give you?
I have a programming folder, and I have nothing of value there

Running with Code has a new home!

Our species really annoys me.

Offline iago

  • Leader
  • Administrator
  • Hero Member
  • *****
  • Posts: 17914
  • Fnord.
    • View Profile
    • SkullSecurity
Re: How to REALLY get the version hash..
« Reply #13 on: May 02, 2007, 02:11:23 pm »
The value that some call the "version hash". It's a derivative of the .exe file's version, and often look something like 01010e01.

Offline Joe

  • B&
  • x86
  • Hero Member
  • *****
  • Posts: 10319
  • In Soviet Russia, text read you!
    • View Profile
    • Github
Re: How to REALLY get the version hash..
« Reply #14 on: June 05, 2007, 09:36:58 am »
What value does this give you?

I've got to go to work, but when I get home I'll write a little program to calculate the version hash and display it.

And yeah, I know I'm more than a month late. :P

EDIT -
Another half a month, but I finally did it. 0x10F00001
« Last Edit: June 23, 2007, 07:23:51 pm by Joe[x86/64] »
I'd personally do as Joe suggests

You might be right about that, Joe.