Valhalla Legends Forums Archive | Battle.net Bot Development | [Python] MPQ Stuff

AuthorMessageTime
Topaz
I've been porting over some of the code used to authenticate and extract the versioning libraries from the MPQ. I had no trouble porting over the earlier code, since it was relatively simple to decipher - but now that I recently found newer functions in Storm that are likely to do it better, I've tried to port them over (with some help by rob's vercheck1/2) -

peekaboo.py
[code]from ctypes import *

storm_handle = windll.LoadLibrary('./storm.dll')

def destroy():
    """BOOL WINAPI ORDINAL(0x100) SFileDdaDestroy()"""
    return storm_handle[262]()

def open_archive(archive):
    """BOOL WINAPI ORDINAL(0x10A) SFileOpenArchive(LPCSTR lpFileName,
        DWORD nArchivePriority, DWORD grfArchiveFlags, HSARCHIVE *lphMPQ)"""
    mpq_handle = c_void_p()

    result = storm_handle[266](archive, 0, 0, byref(mpq_handle))

    if result == 0:
        return -1
    else:
        return mpq_handle

def authenticate_archive(mpq_handle):
    """BOOL WINAPI ORDINAL(0xFB) SFileAuthenticateArchive(HSARCHIVE hMPQ,
                LPDWORD lpdwAuthenticationStatus)
    Status codes:
        0 = SFILE_AUTH_AUTHENTICATION_UNAVAILABLE,
        1 = SFILE_AUTH_NOT_SIGNED,
        2 = SFILE_AUTH_INVALID_SIGNATURE
        3 = SFILE_AUTH_UNSUPPORTED_SIGNATURE_FORMAT
        5 = SFILE_AUTH_AUTHENTICATED"""
    auth_status = c_long(0)
    result = storm_handle[251](mpq_handle, byref(auth_status))

    if result == 0:
        return -1
    else:
        return auth_status

def load_file(mpq_handle, file_name):
    """BOOL WINAPI ORDINAL(0x119) SFileLoadFileEx(HSARCHIVE hMPQ, LPCSTR
    lpszFileName, LPVOID *lplpFileData, LPDWORD lpnFileSize, DWORD
    nExtraBytesToAllocateAtEnd, DWORD grfSearchScope, LPOVERLAPPED lpOverlapped)"""
    file_data = c_void_p()
    file_size = c_long(2)
   
    result = storm_handle[281](mpq_handle, file_name, byref(file_data),
                            byref(file_size), 0, 0, 0)

    if result == 0:
        return -1
    else:
        return [file_data, file_size]
       
def set_locale(new_locale):
    """void WINAPI ORDINAL(0x110) SFileSetLocale(LANGID lnNewLanguage)"""
    storm_handle[272](new_locale)

destroy()
fefe = open_archive('./ver-IX86-0.mpq')

result = authenticate_archive(fefe)

if result == 5:
    file_junk = load_file(fefe, 'ver-IX86-0.dll')
[/code]
October 12, 2006, 6:43 AM
Topaz
The main reason for me posting this is to ask for help.


In SFileLoadFileEx, what is 'lplpFileData'? It appears to be a long (I'm getting 17301644), but what is it? Is it a filetime?
October 12, 2006, 6:47 AM
Myndfyr
Well, according to the notation it's a long pointer to a long pointer to the file data.

My guess, is that its data type is traditionally LPVOID.  It's being passed by-reference and is a return value.  It could be decorated LPVOID OUT *lplpFileData.  OUT is typically #defined to nothing, but is informational metadata.

You can:

[code]
LPVOID lpFileData = *lplpFileData;
[/code]

Then you have a pointer to the beginning of the file data.  :)
October 12, 2006, 8:02 AM
Topaz
[quote author=MyndFyre[vL] link=topic=15862.msg159723#msg159723 date=1160640158]
Well, according to the notation it's a long pointer to a long pointer to the file data.

My guess, is that its data type is traditionally LPVOID.  It's being passed by-reference and is a return value.  It could be decorated LPVOID OUT *lplpFileData.  OUT is typically #defined to nothing, but is informational metadata.

You can:

[code]
LPVOID lpFileData = *lplpFileData;
[/code]

Then you have a pointer to the beginning of the file data.  :)
[/quote]

Thanks... if only I knew how to do that with ctypes :(
October 14, 2006, 7:30 AM
Myndfyr
Use byref.  You're passing lpFileData by reference and getting a pointer (type LPVOID) to the beginning of the file data.

Look at it this way...

Let's say the file data starts at 0x0401c0c0

lpFileData is equal to 0x0401c0c0.

*lpFileData gets the value AT 0x0401c0c0, which is the first value in the file (depending on how LPVOID is dereferenced, I'd guess the first four bytes).

When you have LPVOID* lplpFileData, that means you're passing the pointer by reference.  That's because the function's return value is a status code (error/success), and since you want to get more than one value returned, you pass parameters by reference.  What usually happens is when you pass parameters by value, the value is copied onto the stack and then you never get the changed value from the stack.  However, when you pass by reference, the address of the value is passed onto the stack, and then the value at the address is updated.

So 0x0401c0c0 is where the file data is, let's say 0x04000da0 is where lpFileData is located.  If you look at the value AT 0x04000da0, it's 0x0401c0c0.

Using byref, you actually pass 0x04000da0 onto the stack.  The called function then writes the appropriate value in memory at 0x04000da0.

You did something similar with SFileAuthenticateArchive.  :)
October 14, 2006, 10:02 AM
Topaz
[quote author=MyndFyre[vL] link=topic=15862.msg159802#msg159802 date=1160820173]
Use byref.  You're passing lpFileData by reference and getting a pointer (type LPVOID) to the beginning of the file data.

Look at it this way...

Let's say the file data starts at 0x0401c0c0

lpFileData is equal to 0x0401c0c0.

*lpFileData gets the value AT 0x0401c0c0, which is the first value in the file (depending on how LPVOID is dereferenced, I'd guess the first four bytes).

When you have LPVOID* lplpFileData, that means you're passing the pointer by reference.  That's because the function's return value is a status code (error/success), and since you want to get more than one value returned, you pass parameters by reference.  What usually happens is when you pass parameters by value, the value is copied onto the stack and then you never get the changed value from the stack.  However, when you pass by reference, the address of the value is passed onto the stack, and then the value at the address is updated.

So 0x0401c0c0 is where the file data is, let's say 0x04000da0 is where lpFileData is located.  If you look at the value AT 0x04000da0, it's 0x0401c0c0.

Using byref, you actually pass 0x04000da0 onto the stack.  The called function then writes the appropriate value in memory at 0x04000da0.

You did something similar with SFileAuthenticateArchive.  :)
[/quote]

Maybe I'm thinking too hard... I'll try that.
October 14, 2006, 10:09 AM
Topaz
[s]Can someone upload the ver-IX86-#.dlls? I don't know what I'm actually looking for, so I think that would be a good place to start.[/s]

Edit:

[s]http://advancedcontent.net/topaz/etc/ver-IX86-n.rar[/s]

Files removed, Blizzard sent a DMCA takedown notice to Warrior, who hosts my FTP.
October 14, 2006, 10:45 AM
Topaz
[s]Looks like I won't be able to do this without using win32-specific API (like CreateFile and WriteFile). SFileLoadFileEx returns a pointer to the file data, rather than the data itself (and CreateFile and WriteFile are intended to use that pointer). I don't think I can do much with a barebones knowledge of C.[/s]

see below
October 16, 2006, 2:46 AM
Myndfyr
If you want to use MPQ functionality, I recommend using ShadowFlare's MPQ API (sfmpq.dll).  It implements the Storm functions such as SFileOpenFile and it's fairly documented.  Then it has SFileReadFileEx, which reads data.

The Storm functions (and the SFmpqAPI functions) are modeled after the Win32 API pattern of CreateFile and WriteFile. 
October 16, 2006, 4:49 AM
Topaz
[quote author=MyndFyre[vL] link=topic=15862.msg159866#msg159866 date=1160974159]
If you want to use MPQ functionality, I recommend using ShadowFlare's MPQ API (sfmpq.dll).  It implements the Storm functions such as SFileOpenFile and it's fairly documented.  Then it has SFileReadFileEx, which reads data.

The Storm functions (and the SFmpqAPI functions) are modeled after the Win32 API pattern of CreateFile and WriteFile. 
[/quote]

[s]I can still use the Storm functions, just not SFileLoadFileEx and some of the other ones. I was hoping to use the newer ones to do the same task just because I could.[/s]

see below
October 17, 2006, 2:35 AM
Topaz
By the way, here's the module I wrote that uses [s]SFileReadFile[/s], SFileReadFile and SFileLoadFileEx:

[code]from ctypes import *

storm_handle = windll.LoadLibrary('./storm.dll')

def destroy():
    """BOOL WINAPI ORDINAL(0x100) SFileDdaDestroy()"""
    result = storm_handle[262]()

    if result == 0:
        return -1
    else:
        return result

def open_archive(archive):
    """BOOL WINAPI ORDINAL(0x10A) SFileOpenArchive(LPCSTR lpFileName,
        DWORD nArchivePriority, DWORD grfArchiveFlags, HSARCHIVE *lphMPQ)"""
    mpq_handle = c_void_p()

    result = storm_handle[266](archive, 0, 0, byref(mpq_handle))

    if result == 0:
        return -1
    else:
        return mpq_handle

def authenticate_archive(mpq_handle):
    """BOOL WINAPI ORDINAL(0xFB) SFileAuthenticateArchive(HSARCHIVE hMPQ,
                LPDWORD lpdwAuthenticationStatus)
    Status codes:
        0 = SFILE_AUTH_AUTHENTICATION_UNAVAILABLE,
        1 = SFILE_AUTH_NOT_SIGNED,
        2 = SFILE_AUTH_INVALID_SIGNATURE
        3 = SFILE_AUTH_UNSUPPORTED_SIGNATURE_FORMAT
        5 = SFILE_AUTH_AUTHENTICATED"""
    auth_status = c_long(0)
    result = storm_handle[251](mpq_handle, byref(auth_status))

    if result == 0:
        return -1
    else:
        return auth_status

def load_file(mpq_handle, file_name):
    """BOOL WINAPI ORDINAL(0x119) SFileLoadFileEx(HSARCHIVE hMPQ, LPCSTR
    lpszFileName, LPVOID *lplpFileData, LPDWORD lpnFileSize, DWORD
    nExtraBytesToAllocateAtEnd, DWORD grfSearchScope, LPOVERLAPPED lpOverlapped)"""
    file_data = c_char_p()
    file_size = c_long(0)
   
    result = storm_handle[281](mpq_handle, file_name, byref(file_data),
                            byref(file_size), 0, 0, 0)

    if result == 0:
        return -1
    else:
        return file_data

def unload_file(file_data):
    """BOOL WINAPI ORDINAL(0x118) SFileUnloadFile(LPVOID lpFileData);"""
    result = storm_handle[280](file_data)

    if result == 0:
        return 1
    else:
        return result

def open_file(mpq_handle, file_name):
    file_handle = c_void_p()
    """BOOL WINAPI ORDINAL(0x10C) SFileOpenFileEx(HSARCHIVE hMPQ,
    LPCSTR lpFileName, DWORD grfSearchScope, HSFILE *lphFile);"""
    result = storm_handle[268](mpq_handle, file_name, 0, byref(file_handle))

    if result == 0:
        return -1
    else:
        return file_handle

def close_file(file_handle):
    """BOOL WINAPI ORDINAL(0xFD) SFileCloseFile(HSFILE hFile);"""
    result = storm_handle[253](file_handle)

    if result == 0:
        return -1
    else:
        return result

def write_data(file_data, file_name):
    new_file = open('./' + file_name, 'wb')
    new_file.write(file_data)
    new_file.close()

def read_file(file_handle, file_size):
    """BOOL WINAPI ORDINAL(0x10D) SFileReadFile(HSFILE hFile, LPVOID lpBuffer,
    DWORD nNumberOfBytesToRead, LPDWORD lpnNumberOfBytesRead,
    LPOVERLAPPED lpOverlapped);

    Notes:
        This function doesn't seem to exist in any of the Storm libraries,
        save the one for Warcraft III/Frozen Throne.
       
        Traceback:
            AttributeError: function ordinal 287 not found"""
    file_buffer = create_string_buffer(file_size)
   
    result = storm_handle[269](file_handle, file_buffer, file_size, 0, 0)

    if result == 0:
        return -1
    else:
        return file_buffer
   

def close_archive(mpq_handle):
    """BOOL WINAPI ORDINAL(0xFC) SFileCloseArchive(HSARCHIVE hMPQ);"""
    return storm_handle[252](mpq_handle)
       
def set_locale(new_locale):
    """void WINAPI ORDINAL(0x110) SFileSetLocale(LANGID lnNewLanguage)"""
    return storm_handle[272](new_locale)

def get_file_size(file_handle):
    return storm_handle[265](file_handle, 0)

destroy()
fefe = open_archive('./' + 'ver-IX86-0.mpq')
result = authenticate_archive(fefe)

if result <> -1:
    x = open_file(fefe, 'ver-IX86-0.dll')
    fsize = get_file_size(x)
    s = read_file(x, fsize)
    write_data(s, 'ver-IX86-0.dll')
    close_file(fefe)
    close_archive(fefe)[/code]

I'll write a class for it later, if anyone actually cares

Edit: I got SFileLoadFileEx to work, although the workaround I used is extremely dirty:

[code]def load_file(mpq_handle, file_name):
    """BOOL WINAPI ORDINAL(0x119) SFileLoadFileEx(HSARCHIVE hMPQ, LPCSTR
    lpszFileName, LPVOID *lplpFileData, LPDWORD lpnFileSize, DWORD
    nExtraBytesToAllocateAtEnd, DWORD grfSearchScope, LPOVERLAPPED lpOverlapped)"""
    file_data = create_string_buffer(0)
    file_size = c_long(0)
   
    result = storm_handle[281](mpq_handle, file_name, byref(file_data),
                            byref(file_size), 0, 0, 0)

    file_data = create_string_buffer(file_size.value)

    result = storm_handle[281](mpq_handle, file_name, byref(file_data),
                            byref(file_size), 0, 0, 0)

    if result == 0:
        return -1
    else:
        return file_data[/code]

It seems I was wrong about accessing file pointers, I was fooled into thinking there was only three bytes in the buffer (probably some exotic bug involving reading certain characters with ctypes) when there was actually all the file data I needed!
October 19, 2006, 6:20 AM

Search