This blog has been inactive for quite a while, but users where still commenting. I didn't have the time to respond, but I kept watching the progress. Now is the time to sum up some of the comments.
Recently the idea came up if the communication between the client and the server might be compressed (additionally to being encrypted). I don't think this is the case. From my understanding the game does most likely use opcodes for data exchange. The amount of data is not very high so having the cpu overhead of doing stream encrypting does not really bring much benefit.
Our commenters have also tested to use a "verbose" version of the zlib that writes the protocol of the called method. From what we could observe only calls to "decompress" were made. Therefore it's most likely that the communication is only RC4 encrypted data.
I also was contacted by an Austrian developer that is looking into doing the same attempt we are doing for DDO. He is using easyhook (http://easyhook.codeplex.com/) to trace the calls. He focused on CryptImportKey() and CryptGenKey() to receive the private/public keys and the RC4 stream cipher. This might be an option for Windows users to figure out what the client is doing (Linux users can always use wine and hack it in whatever way they prefer).
I'm looking forward to what else our readers might come up with. Feel free to comment on this blog for whatever reason you like, e.g. connecting to other developers.
Samstag, 10. Dezember 2011
Samstag, 9. Oktober 2010
On to the character selection
As you might have seen in this this video we got to the point of entering the character selection screen. This is a really good start. Today I got the latest copy of code by the videos author. I'm currently reviewing it and will hopefully put it to gitorious later.
The code itself is written using C#. This allows rapid prototyping of applications and good development speed. The code is capable of handling the login but you can't play the game yet. It basically is split into 2 states
The biggest downside right now is that we can send the packets the client expects but don't know what they mean in detail.
So stay tuned for further updates.
The code itself is written using C#. This allows rapid prototyping of applications and good development speed. The code is capable of handling the login but you can't play the game yet. It basically is split into 2 states
- loginStage
- marginStage
The biggest downside right now is that we can send the packets the client expects but don't know what they mean in detail.
So stay tuned for further updates.
Mittwoch, 7. April 2010
Moving the code to gitorious
I wanted to test gitorious for a while now so I decided it was a good idea to host the code I wrote there. This allows regular readers to follow the progress and even to contribute back.
Right now it's a big junkyard but I'll continue cleaning it up as soon as time permits.
A few words to the repositories:
http://gitorious.org/lotro/patches
Right now it's a big junkyard but I'll continue cleaning it up as soon as time permits.
A few words to the repositories:
- gls is the meta server written in C# which provides the infratructure to select a server from the client
- htdocs is the content the client needs to properly display something
- server is where the server code will be written
- patches is the set of wine patches I wrote to trace what lotro is doing
http://gitorious.org/lotro/patches
Sonntag, 17. Januar 2010
Mimicing the clients behaviour
I'm still trying to understand what exactly the client is doing during startup. I wrote the following program to rebuild the behaviour of the client. It's not really good yet, especially the part that is mimicing the winsock stuff. I'm certainly unclear about what the client is sending. First I thought it was constant data, but then again it was completely different. Anyway, here is the program:
Based on what I learned I tried to look at what the server needs to do as initial setup. I came up with the following dummy:
/* winegcc lotro-behaviour.c -Isource/wine/include/ -lws2_32 -o lotro-behaviour && ./lotro-behaviour.exe */ #include#include #include /* Copy from rsaenh.c */ #define RSAENH_MAGIC_RSA2 0x32415352 #define RSAENH_MAGIC_RSA1 0x31415352 /* This method is used to dump a region of memory */ static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) { DWORD i; if (pbData && dwDataLen > 0) { for (i = 0; i < dwDataLen; i++) { printf("0x%02x (%d)\n", pbData[i], pbData[i]); } } } /* Load the clients private key into the given pointer */ static void load_private_key(CONST BYTE * pbData) { BLOBHEADER * pHeader = (BLOBHEADER *) pbData; pHeader->bType = PRIVATEKEYBLOB; pHeader->bVersion = 0x2; pHeader->reserved = 0; pHeader->aiKeyAlg = CALG_RSA_KEYX; RSAPUBKEY * pRsaPubKey = (RSAPUBKEY *) (pbData + sizeof(BLOBHEADER)); pRsaPubKey->magic = RSAENH_MAGIC_RSA2; pRsaPubKey->bitlen = 512; pRsaPubKey->pubexp = 1; BYTE pbSrc[288] = { 0xab, 0xef, 0xfa, 0xc6, 0x7d, 0xe8, 0xde, 0xfb, 0x68, 0x38, 0x09, 0x92, 0xd9, 0x42, 0x7e, 0x6b, 0x89, 0x9e, 0x21, 0xd7, 0x52, 0x1c, 0x99, 0x3c, 0x17, 0x48, 0x4e, 0x3a, 0x44, 0x02, 0xf2, 0xfa, 0x74, 0x57, 0xda, 0xe4, 0xd3, 0xc0, 0x35, 0x67, 0xfa, 0x6e, 0xdf, 0x78, 0x4c, 0x75, 0x35, 0x1c, 0xa0, 0x74, 0x49, 0xe3, 0x20, 0x13, 0x71, 0x35, 0x65, 0xdf, 0x12, 0x20, 0xf5, 0xf5, 0xf5, 0xc1, 0xed, 0x5c, 0x91, 0x36, 0x75, 0xb0, 0xa9, 0x9c, 0x04, 0xdb, 0x0c, 0x8c, 0xbf, 0x99, 0x75, 0x13, 0x7e, 0x87, 0x80, 0x4b, 0x71, 0x94, 0xb8, 0x00, 0xa0, 0x7d, 0xb7, 0x53, 0xdd, 0x20, 0x63, 0xee, 0xf7, 0x83, 0x41, 0xfe, 0x16, 0xa7, 0x6e, 0xdf, 0x21, 0x7d, 0x76, 0xc0, 0x85, 0xd5, 0x65, 0x7f, 0x00, 0x23, 0x57, 0x45, 0x52, 0x02, 0x9d, 0xea, 0x69, 0xac, 0x1f, 0xfd, 0x3f, 0x8c, 0x4a, 0xd0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xd5, 0xaa, 0xb1, 0xa6, 0x03, 0x18, 0x92, 0x03, 0xaa, 0x31, 0x2e, 0x48, 0x4b, 0x65, 0x20, 0x99, 0xcd, 0xc6, 0x0c, 0x15, 0x0c, 0xbf, 0x3e, 0xff, 0x78, 0x95, 0x67, 0xb1, 0x74, 0x5b, 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; memcpy((void *)pbData + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), &pbSrc, 288); } /* Load the servers public key into the given pointer */ static void load_public_key(CONST BYTE * pbData) { BLOBHEADER * pHeader = (BLOBHEADER *) pbData; pHeader->bType = PUBLICKEYBLOB; pHeader->bVersion = 0x2; pHeader->reserved = 0; pHeader->aiKeyAlg = CALG_RSA_KEYX; RSAPUBKEY * pRsaPubKey = (RSAPUBKEY *) (pbData + sizeof(BLOBHEADER)); pRsaPubKey->magic = RSAENH_MAGIC_RSA1; pRsaPubKey->bitlen = 1024; pRsaPubKey->pubexp = 0x010001; BYTE pbSrc[128] = { 0x4d, 0x03, 0x8d, 0xd7, 0xd8, 0xa8, 0x85, 0x97, 0x2d, 0x04, 0xde, 0x57, 0xe2, 0x77, 0xc4, 0xbf, 0xc6, 0x13, 0x15, 0x0e, 0x26, 0x72, 0x99, 0x11, 0xa1, 0xcd, 0xf3, 0x8f, 0xd5, 0x5d, 0x63, 0x4c, 0xf3, 0x75, 0xb7, 0x62, 0x9d, 0xee, 0xe8, 0xd5, 0xc5, 0xa7, 0x0e, 0x3c, 0x9c, 0x51, 0xd4, 0x3f, 0x00, 0x45, 0xb2, 0xed, 0x19, 0xb1, 0xca, 0xda, 0x84, 0x1c, 0x6b, 0x35, 0x41, 0x17, 0x46, 0xd0, 0x56, 0xb9, 0x4c, 0x06, 0xcd, 0x8d, 0xc5, 0x50, 0x39, 0x36, 0x4e, 0xe3, 0x9b, 0x5e, 0x3c, 0x44, 0xe8, 0x04, 0x0e, 0x90, 0xc4, 0x89, 0xfc, 0x24, 0xe9, 0xc4, 0xff, 0xc6, 0x97, 0xfd, 0xd6, 0x5d, 0x2b, 0x67, 0x5c, 0xa2, 0x7f, 0x8d, 0x2a, 0xb9, 0x5a, 0x52, 0x3c, 0xec, 0x0d, 0xc1, 0x55, 0xc8, 0xc1, 0xb0, 0x07, 0x3a, 0xdc, 0x03, 0x9c, 0x38, 0x34, 0x08, 0x0e, 0x19, 0x27, 0xcd, 0x7b, 0xd0 }; memcpy((void *)pbData + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), &pbSrc, 128); } void main() { int retval; char* localIP; BOOLEAN first = TRUE; BYTE bDataPrivKey[sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + 288]; BYTE bDataPubKey[sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + 128]; WSADATA wsaData; retval = WSAStartup(2, &wsaData); if (retval != 0) { printf("WSAStartup returned %d\n", retval); return; } SOCKET socket = WSASocket(AF_INET, SOCK_DGRAM, 0, (LPWSAPROTOCOL_INFO) NULL, 0, 0x1 /* What is this for */); BYTE name[256]; gethostname(&name, 256); struct hostent* localHost = gethostbyname((const CHAR *) &name); localIP = inet_ntoa (*(struct in_addr *)*localHost->h_addr_list); struct sockaddr_in addr; addr.sin_family = AF_INET; /* addr.sin_addr.s_addr = inet_addr(localIP); */ addr.sin_addr.s_addr = inet_addr("0.0.0.0"); addr.sin_port = htons(10691); retval = bind(socket, (const struct sockaddr *) &addr, sizeof(addr)); if (retval != 0) { printf("bind returned %d\n", retval); closesocket(socket); WSACleanup(); return; } CHAR optval[4]; int optlen = 4; retval = getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&optval, &optlen); if (retval != 0) { printf("bind returned %d\n", retval); closesocket(socket); WSACleanup(); return; } //byte_dump((CONST BYTE *) &optval, 4); retval = getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&optval, &optlen); //byte_dump((CONST BYTE *) &optval, 4); HCRYPTPROV hProv; CryptAcquireContext(&hProv, "", MS_ENHANCED_PROV, 1, CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET); /* Loads the private key */ BYTE bDataPriv[512]; DWORD dwDataPrivLen = 512; first = TRUE; do { if (!CryptGetProvParam(hProv, PP_ENUMALGS_EX, (BYTE *)&bDataPriv, &dwDataPrivLen, first ? CRYPT_FIRST : CRYPT_NEXT)) { printf("No more CryptProvParam to fullfill CALG_RSA_KEYX for Priv\n"); break; } first = FALSE; } while (((PROV_ENUMALGS_EX *) &bDataPriv)->aiAlgid != CALG_RSA_KEYX); load_private_key((CONST BYTE * ) &bDataPrivKey); HCRYPTKEY hPrivKey; CryptImportKey(hProv, (BYTE *)&bDataPrivKey, sizeof(bDataPrivKey), 0, CRYPT_EXPORTABLE, &hPrivKey); /* Loads the public key */ BYTE bDataPub1[512]; DWORD dwDataPubLen1 = 512; first = TRUE; do { if (!CryptGetProvParam(hProv, PP_ENUMALGS_EX, (BYTE *)&bDataPub1, &dwDataPubLen1, first ? CRYPT_FIRST : CRYPT_NEXT)) { printf("No more CryptProvParam to fullfill CALG_RSA_KEYX for Pub\n"); break; } first = FALSE; } while (((PROV_ENUMALGS_EX *) &bDataPub1)->aiAlgid != CALG_RSA_KEYX); BYTE bDataPub2[512]; DWORD dwDataPubLen2 = 512; first = TRUE; do { if (!CryptGetProvParam(hProv, PP_ENUMALGS_EX, (BYTE *)&bDataPub2, &dwDataPubLen2, first ? CRYPT_FIRST : CRYPT_NEXT)) { printf("No more CryptProvParam to fullfill CALG_RC4 for Pub\n"); break; } first = FALSE; } while (((PROV_ENUMALGS_EX *) &bDataPub2)->aiAlgid != CALG_RC4); load_public_key((CONST BYTE * ) &bDataPubKey); HCRYPTKEY hPubKey; CryptImportKey(hProv, (BYTE *)&bDataPubKey, sizeof(bDataPubKey), 0, CRYPT_EXPORTABLE, &hPubKey); /* CryptGenKey */ HCRYPTKEY hRcKey; CryptGenKey (hProv, CALG_RC4, CRYPT_EXPORTABLE, &hRcKey); /* CryptExportKey */ DWORD dwDataLen; CryptExportKey(hRcKey, hPubKey, SIMPLEBLOB, 0, 0, &dwDataLen); BYTE * pbData = malloc(dwDataLen); CryptExportKey(hRcKey, hPubKey, SIMPLEBLOB, 0, pbData, &dwDataLen); BYTE encryptData1[] = { 0x01, 0x32, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x31 }; DWORD encryptData1Len = 9; CryptEncrypt(hRcKey, 0, TRUE, 0, 0, &encryptData1Len, 9); CryptEncrypt(hRcKey, 0, TRUE, 0, (BYTE *) &encryptData1, &encryptData1Len, 9); char buffer0[] = { 0x54, 0xa7 }; char buffer1[] = { 0xb0, 0x02, 0x0f, 0x08, 0xed, 0x00, 0x00, 0x00, 0x48, 0xfe, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; char buffer2[] = { 0x48, 0xfe, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; WSABUF wsabufs[3]; wsabufs[0].len = sizeof(buffer0); wsabufs[0].buf = malloc(sizeof(buffer0)); memcpy(wsabufs[0].buf, &buffer0, sizeof(buffer0)); wsabufs[1].len = sizeof(buffer1); wsabufs[1].buf = malloc(sizeof(buffer1)); memcpy(wsabufs[1].buf, &buffer1, sizeof(buffer1)); wsabufs[2].len = sizeof(buffer2); wsabufs[2].buf = malloc(sizeof(buffer2)); memcpy(wsabufs[2].buf, &buffer2, sizeof(buffer2)); struct sockaddr_in to; to.sin_family = AF_INET; to.sin_port = htons(9000); to.sin_addr.s_addr = inet_addr("192.168.1.140"); DWORD bytesSent; WSASendTo(socket, (LPWSABUF)&wsabufs, 3, &bytesSent, 0, (const struct sockaddr *) &to, 16, 0, 0); free(pbData); CryptReleaseContext(hProv, 0); closesocket(socket); WSACleanup(); }
Based on what I learned I tried to look at what the server needs to do as initial setup. I came up with the following dummy:
/* Run with: winegcc lotro-server.c -Isource/wine/include/ -lws2_32 -o lotro-server && ./lotro-server.exe */ #include#include #include #include /* This method is used to dump a region of memory */ static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) { DWORD i; if (pbData && dwDataLen > 0) { for (i = 0; i < dwDataLen; i++) { printf("0x%02x (%d)\n", pbData[i], pbData[i]); } } } int main() { WORD wVersionRequested; WSADATA wsaData; int err = 0, remoteAddrLen = sizeof(SOCKADDR_IN); SOCKET s = INVALID_SOCKET; struct addrinfo hints, *result = NULL, *remoteAddr; char buf[256]; /* Initialize winsock with version 2.2 */ wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != NO_ERROR) { printf("WSAStartup failed: %d\n", err); return -1; } /* Verify the system is winsock 2.2 capable */ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("WSAStartup returned wrong version\n"); WSACleanup(); return -2; } /* Determine our local address */ hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_flags = AI_PASSIVE; err = getaddrinfo(NULL, "9000", &hints, &result); if (err != 0) { printf("getaddrinfo failed: %d\n", err); WSACleanup(); return -3; } s = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (s == INVALID_SOCKET) { printf("Creating the socket failed: %d\n", WSAGetLastError()); freeaddrinfo(result); WSACleanup(); return -4; } err = bind(s, result->ai_addr, (int)result->ai_addrlen); if (err == SOCKET_ERROR) { printf("Binding failed: %d\n", WSAGetLastError()); closesocket(s); freeaddrinfo(result); WSACleanup(); return -5; } printf("Waiting for client\n"); while(1) { err = recvfrom(s, buf, 256, 0, (SOCKADDR *) &remoteAddr, &remoteAddrLen); if (err == SOCKET_ERROR) { printf("Recieve failed: %d\n", WSAGetLastError()); closesocket(s); freeaddrinfo(result); WSACleanup(); return -6; } printf("Read %d bytes\n", err); byte_dump((CONST BYTE *) &buf, err); err = sendto(s ,buf, err, 0, (SOCKADDR*)&remoteAddr, remoteAddrLen); if (err == SOCKET_ERROR) { printf("Send failed: %d\n", WSAGetLastError()); closesocket(s); freeaddrinfo(result); WSACleanup(); return -7; } printf("Sent %d bytes\n", err); } closesocket(s); freeaddrinfo(result); WSACleanup(); return 0; }
Dienstag, 12. Januar 2010
Houston, we have a key
I finally got some time to hack around in the source code of wine to allow it to dump the key that will be imported. This hacky patch is capable of dumping the private RSA key that will be used for exchanging the RC4 key that will be used during client and server communication.
It is really getting complicated.
diff --git a/dlls/rsaenh/rsaenh.c b/dlls/rsaenh/rsaenh.c
index 665bc40..9c9dd5e 100644
--- a/dlls/rsaenh/rsaenh.c
+++ b/dlls/rsaenh/rsaenh.c
@@ -3016,9 +3016,16 @@ static BOOL import_key(HCRYPTPROV hProv, CONST BYTE *pbData, DWORD dwDataLen,
BOOL WINAPI RSAENH_CPImportKey(HCRYPTPROV hProv, CONST BYTE *pbData, DWORD dwDataLen,
HCRYPTKEY hPubKey, DWORD dwFlags, HCRYPTKEY *phKey)
{
+ DWORD i;
TRACE("(hProv=%08lx, pbData=%p, dwDataLen=%d, hPubKey=%08lx, dwFlags=%08x, phKey=%p)\n",
hProv, pbData, dwDataLen, hPubKey, dwFlags, phKey);
+ if (pbData && dwDataLen > 0) {
+ for (i = 0; i < dwDataLen; i++) {
+ printf("0x%02x (%d)\n", pbData[i], pbData[i]);
+ }
+ }
+
if (dwFlags & CRYPT_IPSEC_HMAC_KEY)
{
FIXME("unimplemented for CRYPT_IPSEC_HMAC_KEY\n");
This patch dumps the 308 bytes of data that are the exported private key. So you are wondering how the private key looks like?
0x07 (7) 0x02 (2) 0x00 (0) 0x00 (0) 0x00 (0) 0xa4 (164) 0x00 (0) 0x00 (0) 0x52 (82) 0x53 (83) 0x41 (65) 0x32 (50) 0x00 (0) 0x02 (2) 0x00 (0) 0x00 (0) 0x01 (1) 0x00 (0) 0x00 (0) 0x00 (0) 0xab (171) 0xef (239) 0xfa (250) 0xc6 (198) 0x7d (125) 0xe8 (232) 0xde (222) 0xfb (251) 0x68 (104) 0x38 (56) 0x09 (9) 0x92 (146) 0xd9 (217) 0x42 (66) 0x7e (126) 0x6b (107) 0x89 (137) 0x9e (158) 0x21 (33) 0xd7 (215) 0x52 (82) 0x1c (28) 0x99 (153) 0x3c (60) 0x17 (23) 0x48 (72) 0x4e (78) 0x3a (58) 0x44 (68) 0x02 (2) 0xf2 (242) 0xfa (250) 0x74 (116) 0x57 (87) 0xda (218) 0xe4 (228) 0xd3 (211) 0xc0 (192) 0x35 (53) 0x67 (103) 0xfa (250) 0x6e (110) 0xdf (223) 0x78 (120) 0x4c (76) 0x75 (117) 0x35 (53) 0x1c (28) 0xa0 (160) 0x74 (116) 0x49 (73) 0xe3 (227) 0x20 (32) 0x13 (19) 0x71 (113) 0x35 (53) 0x65 (101) 0xdf (223) 0x12 (18) 0x20 (32) 0xf5 (245) 0xf5 (245) 0xf5 (245) 0xc1 (193) 0xed (237) 0x5c (92) 0x91 (145) 0x36 (54) 0x75 (117) 0xb0 (176) 0xa9 (169) 0x9c (156) 0x04 (4) 0xdb (219) 0x0c (12) 0x8c (140) 0xbf (191) 0x99 (153) 0x75 (117) 0x13 (19) 0x7e (126) 0x87 (135) 0x80 (128) 0x4b (75) 0x71 (113) 0x94 (148) 0xb8 (184) 0x00 (0) 0xa0 (160) 0x7d (125) 0xb7 (183) 0x53 (83) 0xdd (221) 0x20 (32) 0x63 (99) 0xee (238) 0xf7 (247) 0x83 (131) 0x41 (65) 0xfe (254) 0x16 (22) 0xa7 (167) 0x6e (110) 0xdf (223) 0x21 (33) 0x7d (125) 0x76 (118) 0xc0 (192) 0x85 (133) 0xd5 (213) 0x65 (101) 0x7f (127) 0x00 (0) 0x23 (35) 0x57 (87) 0x45 (69) 0x52 (82) 0x02 (2) 0x9d (157) 0xea (234) 0x69 (105) 0xac (172) 0x1f (31) 0xfd (253) 0x3f (63) 0x8c (140) 0x4a (74) 0xd0 (208) 0x01 (1) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x01 (1) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x64 (100) 0xd5 (213) 0xaa (170) 0xb1 (177) 0xa6 (166) 0x03 (3) 0x18 (24) 0x92 (146) 0x03 (3) 0xaa (170) 0x31 (49) 0x2e (46) 0x48 (72) 0x4b (75) 0x65 (101) 0x20 (32) 0x99 (153) 0xcd (205) 0xc6 (198) 0x0c (12) 0x15 (21) 0x0c (12) 0xbf (191) 0x3e (62) 0xff (255) 0x78 (120) 0x95 (149) 0x67 (103) 0xb1 (177) 0x74 (116) 0x5b (91) 0x60 (96) 0x01 (1) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0) 0x00 (0)The first 20 bytes are header data and the rest is the key. This is really good! Now we can use the private key to generate a public key from it. But we need to code to recreated this key first. I reengineerd the code and it looks like this:
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) {
DWORD i;
if (pbData && dwDataLen > 0) {
for (i = 0; i < dwDataLen; i++) {
printf("0x%02x (%d)\n", pbData[i], pbData[i]);
}
}
}
int main() {
BLOBHEADER header;
header.bType = 0x7; /* PRIVATEKEYBLOB */
header.bVersion = 2; /* Must be 2 */
header.reserved = 0; /* Must be 0 */
header.aiKeyAlg = 0xa400;
byte_dump((CONST BYTE *) &header, sizeof(BLOBHEADER));
RSAPUBKEY rsaPubKey;
rsaPubKey.magic = 0x32415352; /* RSAENH_MAGIC_RSA2 */
rsaPubKey.bitlen = 0x00000200; /* 512 bit long */
rsaPubKey.pubexp = 0x00000001;
byte_dump((CONST BYTE *) &rsaPubKey, sizeof(RSAPUBKEY));
BYTE pbSrc[288] = {
0xab, 0xef, 0xfa, 0xc6,
0x7d, 0xe8, 0xde, 0xfb, 0x68, 0x38, 0x09, 0x92,
0xd9, 0x42, 0x7e, 0x6b, 0x89, 0x9e, 0x21, 0xd7,
0x52, 0x1c, 0x99, 0x3c, 0x17, 0x48, 0x4e, 0x3a,
0x44, 0x02, 0xf2, 0xfa, 0x74, 0x57, 0xda, 0xe4,
0xd3, 0xc0, 0x35, 0x67, 0xfa, 0x6e, 0xdf, 0x78,
0x4c, 0x75, 0x35, 0x1c, 0xa0, 0x74, 0x49, 0xe3,
0x20, 0x13, 0x71, 0x35, 0x65, 0xdf, 0x12, 0x20,
0xf5, 0xf5, 0xf5, 0xc1, 0xed, 0x5c, 0x91, 0x36,
0x75, 0xb0, 0xa9, 0x9c, 0x04, 0xdb, 0x0c, 0x8c,
0xbf, 0x99, 0x75, 0x13, 0x7e, 0x87, 0x80, 0x4b,
0x71, 0x94, 0xb8, 0x00, 0xa0, 0x7d, 0xb7, 0x53,
0xdd, 0x20, 0x63, 0xee, 0xf7, 0x83, 0x41, 0xfe,
0x16, 0xa7, 0x6e, 0xdf, 0x21, 0x7d, 0x76, 0xc0,
0x85, 0xd5, 0x65, 0x7f, 0x00, 0x23, 0x57, 0x45,
0x52, 0x02, 0x9d, 0xea, 0x69, 0xac, 0x1f, 0xfd,
0x3f, 0x8c, 0x4a, 0xd0, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x64, 0xd5, 0xaa, 0xb1,
0xa6, 0x03, 0x18, 0x92, 0x03, 0xaa, 0x31, 0x2e,
0x48, 0x4b, 0x65, 0x20, 0x99, 0xcd, 0xc6, 0x0c,
0x15, 0x0c, 0xbf, 0x3e, 0xff, 0x78, 0x95, 0x67,
0xb1, 0x74, 0x5b, 0x60, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
byte_dump((CONST BYTE *) &pbSrc, 288);
}
Now we can pass this to CPImportKey and calculate the public (which is always possible from the private key). But first I need to figure out how to do so using the wincrypt API. Once we've done that we can do the following things.
- Create a server component that is using the public key to decrypt the packages passed to it.
- Dump what is returned by CryptGenRandom
- Find out what CryptCreateHash does and especially why it is used here
- Dump what is encrypted by CryptEncrypt
- Hack wine to use a faked public key that is imported during the second CPImportKey (as there is no way to calculate the private key for this)
0x06 (6) 0x02 (2) 0x00 (0) 0x00 (0) 0x00 (0) 0xa4 (164) 0x00 (0) 0x00 (0) 0x52 (82) 0x53 (83) 0x41 (65) 0x31 (49) 0x00 (0) 0x04 (4) 0x00 (0) 0x00 (0) 0x01 (1) 0x00 (0) 0x01 (1) 0x00 (0) 0x4d (77) 0x03 (3) 0x8d (141) 0xd7 (215) 0xd8 (216) 0xa8 (168) 0x85 (133) 0x97 (151) 0x2d (45) 0x04 (4) 0xde (222) 0x57 (87) 0xe2 (226) 0x77 (119) 0xc4 (196) 0xbf (191) 0xc6 (198) 0x13 (19) 0x15 (21) 0x0e (14) 0x26 (38) 0x72 (114) 0x99 (153) 0x11 (17) 0xa1 (161) 0xcd (205) 0xf3 (243) 0x8f (143) 0xd5 (213) 0x5d (93) 0x63 (99) 0x4c (76) 0xf3 (243) 0x75 (117) 0xb7 (183) 0x62 (98) 0x9d (157) 0xee (238) 0xe8 (232) 0xd5 (213) 0xc5 (197) 0xa7 (167) 0x0e (14) 0x3c (60) 0x9c (156) 0x51 (81) 0xd4 (212) 0x3f (63) 0x00 (0) 0x45 (69) 0xb2 (178) 0xed (237) 0x19 (25) 0xb1 (177) 0xca (202) 0xda (218) 0x84 (132) 0x1c (28) 0x6b (107) 0x35 (53) 0x41 (65) 0x17 (23) 0x46 (70) 0xd0 (208) 0x56 (86) 0xb9 (185) 0x4c (76) 0x06 (6) 0xcd (205) 0x8d (141) 0xc5 (197) 0x50 (80) 0x39 (57) 0x36 (54) 0x4e (78) 0xe3 (227) 0x9b (155) 0x5e (94) 0x3c (60) 0x44 (68) 0xe8 (232) 0x04 (4) 0x0e (14) 0x90 (144) 0xc4 (196) 0x89 (137) 0xfc (252) 0x24 (36) 0xe9 (233) 0xc4 (196) 0xff (255) 0xc6 (198) 0x97 (151) 0xfd (253) 0xd6 (214) 0x5d (93) 0x2b (43) 0x67 (103) 0x5c (92) 0xa2 (162) 0x7f (127) 0x8d (141) 0x2a (42) 0xb9 (185) 0x5a (90) 0x52 (82) 0x3c (60) 0xec (236) 0x0d (13) 0xc1 (193) 0x55 (85) 0xc8 (200) 0xc1 (193) 0xb0 (176) 0x07 (7) 0x3a (58) 0xdc (220) 0x03 (3) 0x9c (156) 0x38 (56) 0x34 (52) 0x08 (8) 0x0e (14) 0x19 (25) 0x27 (39) 0xcd (205) 0x7b (123) 0xd0 (208)Oh, and here is the code to dump the private key of the client and the public key of the server.
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) {
DWORD i;
if (pbData && dwDataLen > 0) {
for (i = 0; i < dwDataLen; i++) {
printf("0x%02x (%d)\n", pbData[i], pbData[i]);
}
}
}
static void dump_private_key() {
BLOBHEADER header;
header.bType = 0x7; /* PRIVATEKEYBLOB */
header.bVersion = 2; /* Must be 2 */
header.reserved = 0; /* Must be 0 */
header.aiKeyAlg = 0xa400;
byte_dump((CONST BYTE *) &header, sizeof(BLOBHEADER));
RSAPUBKEY rsaPubKey;
rsaPubKey.magic = 0x32415352; /* RSAENH_MAGIC_RSA2 */
rsaPubKey.bitlen = 0x00000200; /* 512 bit long */
rsaPubKey.pubexp = 0x00000001;
byte_dump((CONST BYTE *) &rsaPubKey, sizeof(RSAPUBKEY));
BYTE pbSrc[288] = {
0xab, 0xef, 0xfa, 0xc6,
0x7d, 0xe8, 0xde, 0xfb, 0x68, 0x38, 0x09, 0x92,
0xd9, 0x42, 0x7e, 0x6b, 0x89, 0x9e, 0x21, 0xd7,
0x52, 0x1c, 0x99, 0x3c, 0x17, 0x48, 0x4e, 0x3a,
0x44, 0x02, 0xf2, 0xfa, 0x74, 0x57, 0xda, 0xe4,
0xd3, 0xc0, 0x35, 0x67, 0xfa, 0x6e, 0xdf, 0x78,
0x4c, 0x75, 0x35, 0x1c, 0xa0, 0x74, 0x49, 0xe3,
0x20, 0x13, 0x71, 0x35, 0x65, 0xdf, 0x12, 0x20,
0xf5, 0xf5, 0xf5, 0xc1, 0xed, 0x5c, 0x91, 0x36,
0x75, 0xb0, 0xa9, 0x9c, 0x04, 0xdb, 0x0c, 0x8c,
0xbf, 0x99, 0x75, 0x13, 0x7e, 0x87, 0x80, 0x4b,
0x71, 0x94, 0xb8, 0x00, 0xa0, 0x7d, 0xb7, 0x53,
0xdd, 0x20, 0x63, 0xee, 0xf7, 0x83, 0x41, 0xfe,
0x16, 0xa7, 0x6e, 0xdf, 0x21, 0x7d, 0x76, 0xc0,
0x85, 0xd5, 0x65, 0x7f, 0x00, 0x23, 0x57, 0x45,
0x52, 0x02, 0x9d, 0xea, 0x69, 0xac, 0x1f, 0xfd,
0x3f, 0x8c, 0x4a, 0xd0, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x64, 0xd5, 0xaa, 0xb1,
0xa6, 0x03, 0x18, 0x92, 0x03, 0xaa, 0x31, 0x2e,
0x48, 0x4b, 0x65, 0x20, 0x99, 0xcd, 0xc6, 0x0c,
0x15, 0x0c, 0xbf, 0x3e, 0xff, 0x78, 0x95, 0x67,
0xb1, 0x74, 0x5b, 0x60, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};
byte_dump((CONST BYTE *) &pbSrc, 288);
}
static void dump_public_key() {
BLOBHEADER header;
header.bType = 0x6; /* PUBLICKEYBLOB */
header.bVersion = 2; /* Must be 2 */
header.reserved = 0; /* Must be 0 */
header.aiKeyAlg = 0xa400;
byte_dump((CONST BYTE *) &header, sizeof(BLOBHEADER));
RSAPUBKEY rsaPubKey;
rsaPubKey.magic = 0x32415352; /* RSAENH_MAGIC_RSA2 */
rsaPubKey.bitlen = 0x00000400; /* 512 bit long */
rsaPubKey.pubexp = 0x00010001;
byte_dump((CONST BYTE *) &rsaPubKey, sizeof(RSAPUBKEY));
BYTE pbSrc[128] = {
0x4d, 0x03, 0x8d, 0xd7, 0xd8, 0xa8, 0x85, 0x97,
0x2d, 0x04, 0xde, 0x57, 0xe2, 0x77, 0xc4, 0xbf,
0xc6, 0x13, 0x15, 0x0e, 0x26, 0x72, 0x99, 0x11,
0xa1, 0xcd, 0xf3, 0x8f, 0xd5, 0x5d, 0x63, 0x4c,
0xf3, 0x75, 0xb7, 0x62, 0x9d, 0xee, 0xe8, 0xd5,
0xc5, 0xa7, 0x0e, 0x3c, 0x9c, 0x51, 0xd4, 0x3f,
0x00, 0x45, 0xb2, 0xed, 0x19, 0xb1, 0xca, 0xda,
0x84, 0x1c, 0x6b, 0x35, 0x41, 0x17, 0x46, 0xd0,
0x56, 0xb9, 0x4c, 0x06, 0xcd, 0x8d, 0xc5, 0x50,
0x39, 0x36, 0x4e, 0xe3, 0x9b, 0x5e, 0x3c, 0x44,
0xe8, 0x04, 0x0e, 0x90, 0xc4, 0x89, 0xfc, 0x24,
0xe9, 0xc4, 0xff, 0xc6, 0x97, 0xfd, 0xd6, 0x5d,
0x2b, 0x67, 0x5c, 0xa2, 0x7f, 0x8d, 0x2a, 0xb9,
0x5a, 0x52, 0x3c, 0xec, 0x0d, 0xc1, 0x55, 0xc8,
0xc1, 0xb0, 0x07, 0x3a, 0xdc, 0x03, 0x9c, 0x38,
0x34, 0x08, 0x0e, 0x19, 0x27, 0xcd, 0x7b, 0xd0
};
byte_dump((CONST BYTE *) &pbSrc, 128);
}
int main() {
dump_private_key();
printf("\n");
dump_public_key();
}
So it looks to me that:
- The client has it's own private and public RSA-keyset
- The server has a different RSA-one, too
- These keys are used to exchange the symetric RC4 key
It is really getting complicated.
Donnerstag, 17. Dezember 2009
How to start a LOTRO client
I'm currently looking into understanding the network protocol better. I use the following command to see what the client is doing:
Note that this assumes you are running on a system with a hacked /etc/hosts file so you don't actually connect to lotroeugls.com!
I'll explain the options I used in detail to show you what I am doing:
The other options don't really matter right now (as we don't have another side to talk to this will never be used). This generates the inital network traffic the client is performing to setup a connection to the server.
Once you trace the crypt and networking code (using wine as described below) you are able to trace what ever the client was doing.
lotroclient.exe
-a 2
-h 192.168.1.123:9000
--glsticketdirect 1
--chatserver 192.168.1.124:2900
--language fr
--bugurl http://192.168.1.125/bug.php
--supporturl http://192.168.1.126/TSSTrowser/Trowser.aspx
-GameType LOTROEU
--authserverurl https://lotroeugls.com/gls.authserver/service.asmx
--glsticketlifetime 86400
Note that this assumes you are running on a system with a hacked /etc/hosts file so you don't actually connect to lotroeugls.com!
I'll explain the options I used in detail to show you what I am doing:
-a 2: I just a invalid username (I don't want to use a real one for privacy reasons). You can change this to whatever you like. This allows you to find the name in the network traffic.
-h 192.168.1.123: This is bogus IP on the same local network as I am using. This is necessary to filter packets by IP to identify which host a client sends packets to
--glsticketdirect 1: A bogus GLS ticket. Could be whatever you like. I chose a short one to minimize the logs.
--chatserver 192.168.1.124:2900: Another bogus host to be used as a chatserver.
The other options don't really matter right now (as we don't have another side to talk to this will never be used). This generates the inital network traffic the client is performing to setup a connection to the server.
Once you trace the crypt and networking code (using wine as described below) you are able to trace what ever the client was doing.
Samstag, 11. Juli 2009
Listing the available options
While playing around with the bits and pieces to launch the client I accidentially got it to print all options. Here is a short list of all available commandline options of the game client.
You must specify an account name
-a, --account : <string>: Specifies the account name to logon with.
--authserverurl : Auth server URL for refreshing the GLS ticket.
--bugurl : <url> : Specifies the url that should be used for reporting bugs.
--chatserver : Specify the chat server to use.
--connspeed : <0.0-640.0>:Connection speed in Kilobits/sec for the server-client connection. 0 Defaults to speed searching
-r, --create : <name> : Character Name you would like to create/play
--dbwlib : <wlib DID> : Specifies the exact wlib did contained in the datfiles to use.
--debug : <32 bitfield>: Controls what kinds of debug outputs are enabled.
-f, --franchise : <string>: Specifies the franchise name.
--gametype : <string> : Specifies the game type used for supporturl and supportserviceurl.
--glsticket : Load gls ticket data from specified registry key.
-z, --glsticketdirect: <string>: Specify ticket data.
--glsticketlifetime: The lifetime of GLS tickets.
--HighResOutOfDate: Tells the client that the high resolution texture dat file was not updated. We will not switch into very high texture detail..
-h, --host : [host/IP]:Specifies where to find the server to talk to.
--keymap : <file> : Base file for the keymap. Will also look for <filename>c.extension and <filename>s.extension for meta keys
--language : <string>: Language to run the client in.
-m, --mps : <monster play session>.wc : Monster Play Session to start with
--nodbwlib : Tells the client not to use the database cache for locating the wlib. Instead, the wlib will be loaded directly from the hard drive.
--outport : <1-65535>: Specify the outgoing network port to use.
-p, --port : <1-65535>: Specify the server port to contact. See 'host'
--prefs : <string>: Specify the preferences file to use.
--resource-set : <string>: A comma separated list of available resource sets. The last set is the default.
--rodat : Opens the DAT files readonly
--safe : Force SAFE display settings.
-s, --specify : <race>,<class> : Race Class pair for a character you would like to create
--supportserviceurl: <url> : Specifies the url that should be used for auto submission of in game support tickets.
--supporturl : <url> : Specifies the url that should be used for in game support.
--surveyurl : <url> : URL to use for quest surveys
--usechat : Specify that the client should connect to a chat server.
--useexiturl : launch a browser using an exit url
-u, --user : <name> : Character Name you would like to play
--usetdm : Enable the download manager
--voicenetdelay : <int>: Specifies the voice network delay threshold in milliseconds.
--voiceoff : Disables the Voice chat system.
--webjournalurl : <url> : URL to use for the web journal
--wfilelog : <64-bitmask> : activates file logging for the specified weenie event types. Alternately, logtype enums seperated by ',' are enummapped and or'ed together.
--wprintlog : <64-bitmask> : activates print logging for the specified weenie event types. Alternately, logtype enums seperated by ',' are enummapped and or'ed together.
Abonnieren
Posts (Atom)