Jump to content
McKay Development

ClientMicroTxnAuthRequest (5504)


Recommended Posts

Posted (edited)

Hello, I'm trying to do in-game purchase in CS:GO through GameCoordinator, but it returns ClientMicroTxnAuthRequest (5504) Message after my GC k_EMsgGCStorePurchaseInit (2510) request. I've dumped all protos from Steam, but didn't found how to do this with ClientMicroTxnAuthorize (5505). In traffic dump I have the payload: 673C0250F269A90E02000000, but can't decode this. It's a bytebuffer.

post-413-0-46299500-1484606758_thumb.png

post-413-0-47958000-1484606759_thumb.png

post-413-0-85757900-1484606759_thumb.png

post-413-0-08695700-1484606760_thumb.png

Edited by chaot1c
Posted

Would you mind sending me your NetHook dump? It would probably be a good idea to zip it up, encrypt the zip, attach the zip to a reply here, and PM me the password. You can't attach files to PMs.

 

Also, for what purpose are you doing this?

Posted

My old GCStorePurchaseFinalize dump became unreadable, but I attached the older. (ID 81, calls with txn_id after success ClientMicroTxnAuthorizeResponse).

 

I would like to share this function after the finish

 

Would you mind sending me your NetHook dump? It would probably be a good idea to zip it up, encrypt the zip, attach the zip to a reply here, and PM me the password. You can't attach files to PMs.

 

Also, for what purpose are you doing this?

 

dump.zip

Posted

Okay, here's the flow of how this works:

  1. CS:GO client sends a GCStorePurchaseInit to the GC with the items you want to buy
  2. You get back a GCStorePurchaseInitResponse containing a result (1 = OK) and a transaction ID (64-bit, even though it can currently fit into 32 bits)
  3. Concurrently, you get a ClientMicroTxnAuthRequest message via Steam. Discard the first byte of the payload (seems to be 1 in the dump you sent, not sure if that's always the case or what the significance of it is), then decode the remainder of the payload as Binary KeyValues (require('binarykvparser').parse(payload.toBuffer().slice(1))). The interesting stuff in there is the total/BillingTotal and transID (orderid is there if you want to make sure it matches what the GC sent back).
  4. Grab the transID and send it back in ClientMicroTxnAuthorize (little-endian encoded, as always) and append a 32-bit value of 2 (unsure what the significance of this is, or if it ever changes from 2. Could perhaps be an authorization result, where 1 is deny or something).
  5. You'll get back ClientMicroTxnAuthorizeResponse with a payload containing a 32-bit eresult value (1 = OK), and a 1-byte unknown value of 0. If successful,
  6. If successful, send GCStorePurchaseFinalize to the GC containing the transaction ID you from the GC before (not transID) to get your items.
Posted (edited)

 

Okay, here's the flow of how this works:

  1. CS:GO client sends a GCStorePurchaseInit to the GC with the items you want to buy
  2. You get back a GCStorePurchaseInitResponse containing a result (1 = OK) and a transaction ID (64-bit, even though it can currently fit into 32 bits)
  3. Concurrently, you get a ClientMicroTxnAuthRequest message via Steam. Discard the first byte of the payload (seems to be 1 in the dump you sent, not sure if that's always the case or what the significance of it is), then decode the remainder of the payload as Binary KeyValues (require('binarykvparser').parse(payload.toBuffer().slice(1))). The interesting stuff in there is the total/BillingTotal and transID (orderid is there if you want to make sure it matches what the GC sent back).
  4. Grab the transID and send it back in ClientMicroTxnAuthorize (little-endian encoded, as always) and append a 32-bit value of 2 (unsure what the significance of this is, or if it ever changes from 2. Could perhaps be an authorization result, where 1 is deny or something).
  5. You'll get back ClientMicroTxnAuthorizeResponse with a payload containing a 32-bit eresult value (1 = OK), and a 1-byte unknown value of 0. If successful,
  6. If successful, send GCStorePurchaseFinalize to the GC containing the transaction ID you from the GC before (not transID) to get your items.

 

Big thanks, on 4 step i've got a buffer for request: 146A5F50F269A90E02000000 and noticed that F269A90E (Int32: 245983730) doesn't change on ClientMicroTxnAuthorize and txn_id (on GCStorePurchaseInitResponse) equal to orderid, but still I didn't get the response after request, I'm using node-steam

var payload = new ByteBuffer(0, ByteBuffer.LITTLE_ENDIAN);
payload.writeUInt32(data.transID.toInt()); // data.transID.toInt() == 1144076083
payload.append('F269A90E02000000', 'hex'); // == 371DB34FF269A90E02000000
steamClient.send({ msg: EMsg.ClientMicroTxnAuthorize, proto: {} }, payload.buffer);
Edited by chaot1c
Posted (edited)

Don't do it that way, that's a very bad idea. Write the entire 64-bit number to the buffer.

Thank you for your help, I did that and it was successful, but on 4 step i needed 32-bit value of 1 (1 = OK, 2 = Fail) so here it is: 

let fs = require('fs');
let Steam = require('../node_modules/steam/lib/steam_client');
let SteamTotp = require('steam-totp');
let Language = require('./language.js');
let ByteBuffer = require('bytebuffer');
let BinaryKVParser = require('binarykvparser');
let Long = require('long');

if(fs.existsSync('servers')) Steam.servers = JSON.parse(fs.readFileSync('servers'));

let steamGameCoordinator, accountDetails;
let steamClient = new Steam.SteamClient();
let steamUser = new Steam.SteamUser(steamClient);
let steamFriends = new Steam.SteamFriends(steamClient);

let Protos = {
	Steam: Steam.Internal,
	CSGO: Steam.GC.CSGO.Internal
};

if(process.argv[2]) {
	accountDetails = {
		toLogOn: {
	  	account_name: process.argv[2],
		  password: process.argv[3],
		  two_factor_code: process.argv[4] ? SteamTotp.generateAuthCode(process.argv[4]) : null
		},
	  localAddress: process.argv[5] || null,
	  count: parseInt(process.argv[6]) || null
	};
} else throw new Error('Usage: node <script> "login" "password" "shared_secret" "ip" count');

steamClient.handlers = {};
steamClient.connect({ localAddress: accountDetails.localAddress });
steamClient.on('connected', () => steamUser.logOn(accountDetails.toLogOn));

steamClient.on('logOnResponse', logonResp => {
  if(logonResp.eresult == Steam.EResult.OK) {
    if(!process.send) console.log('Logged in!');
    steamFriends.setPersonaState(Steam.EPersonaState.Online);
    steamUser.gamesPlayed({ games_played: [ { game_id: 730 } ] });

    steamGameCoordinator = new Steam.SteamGameCoordinator(steamClient, 730);
    steamGameCoordinator.handlers = {};
    steamGameCoordinator.on('message', (header, body, callback) => {
    	let protobuf = !!header.proto;

			if(steamGameCoordinator.handlers[header.msg]) steamGameCoordinator.handlers[header.msg].call(null, body);
    	else {
    		let msgName = header.msg;
    		for(let i in Language) {
					if(Language.hasOwnProperty(i) && Language[i] == header.msg) {
						msgName = i;
						break;
					}
				}

				steamGameCoordinator.emit('debug', 'Got unhandled steamGameCoordinator message ' + msgName + (protobuf ? ' (protobuf)' : ''));
    	}
    });

    steamGameCoordinator.handlers[Protos.CSGO.EGCBaseClientMsg.k_EMsgGCClientWelcome] = function(body) {
			clearInterval(hello);
		  let proto = Protos.CSGO.CMsgClientWelcome.decode(body);
		  if(accountDetails.count) {
		  	buyItems([ { item_def_id: 1356, quantity: accountDetails.count, cost_in_local_currency: parseInt(accountDetails.count * 249), purchase_type: 0 } ]);
		  }
		};

		steamGameCoordinator.handlers[Protos.CSGO.EGCItemMsg.k_EMsgGCStorePurchaseFinalizeResponse] = function(body) {
			let proto = Protos.CSGO.CMsgGCStorePurchaseFinalizeResponse.decode(body);
			if(process.send) process.send(proto);
			else console.log(proto);
		};

    let hello = setInterval(() => steamGameCoordinator.send({ msg: Protos.CSGO.EGCBaseClientMsg.k_EMsgGCClientHello, proto: {} }, 
    	(new Protos.CSGO.CMsgClientHello()).toBuffer()), 5000);
  } else if(logonResp.eresult == Steam.EResult.TwoFactorCodeMismatch) setTimeout(() => steamUser.logOn(accountDetails.toLogOn), 30000);
  else if(process.send) process.send(logonResp);
  else console.log(logonResp);
});

steamClient.on('servers', servers => fs.writeFile('servers', JSON.stringify(servers)));
steamClient.on('message', (header, body, callback) => {
	let protobuf = !!header.proto;

  if(steamClient.handlers[header.msg]) steamClient.handlers[header.msg].call(null, body);
	else {
		let msgName = header.msg;
		for(let i in Steam.EMsg) {
			if(Steam.EMsg.hasOwnProperty(i) && Steam.EMsg[i] == header.msg) {
				msgName = i;
				break;
			}
		}

		steamClient.emit('debug', 'Got unhandled steamClient message ' + msgName + (protobuf ? ' (protobuf)' : ''));
	}
});

steamClient.handlers[Steam.EMsg.ClientMicroTxnAuthRequest] = function(body) {
	body = BinaryKVParser.parse(body.slice(1));
	let data = body.MessageObject;

	let txn_id = data.orderid;
	let payload = new ByteBuffer(12, ByteBuffer.LITTLE_ENDIAN);
	payload.writeInt64(data.transid);
	payload.writeInt32(1);
	steamClient.send({ msg: Steam.EMsg.ClientMicroTxnAuthorize, proto: null }, payload.buffer, (header, body) => {
		if(header.msg == Steam.EMsg.ClientMicroTxnAuthorizeResponse) {
			body = body.slice(0, 4);
			let result = body.readInt8();
			if(result == Steam.EResult.OK) {
				steamGameCoordinator.send({ msg: Protos.CSGO.EGCItemMsg.k_EMsgGCStorePurchaseFinalize, proto: {} }, 
					(new Protos.CSGO.CMsgGCStorePurchaseFinalize({ txn_id })).toBuffer());
			}
		} else if(process.send) process.send({ result: false });
		else console.log(header, body);
	});
};

steamClient.handlers[Steam.EMsg.ClientWalletInfoUpdate] = function(body) {
	let proto = Protos.Steam.CMsgClientWalletInfoUpdate.decode(body);
	steamClient.walletInfo = proto;
};

function buyItems(line_items) {
	steamGameCoordinator.send({ msg: Protos.CSGO.EGCItemMsg.k_EMsgGCStorePurchaseInit, proto: {} }, (new Protos.CSGO.CMsgGCStorePurchaseInit({ 
		country: '',
		language: 8,
		currency: 0,
		line_items
	})).toBuffer());
} 

Where Language is language.js from node-globaloffensive and Steam is node-steam with pull request (https://github.com/seishun/node-steam/pull/397). Would you adapt this to your node-globaloffensive?

Edited by chaot1c
  • 3 months later...
Posted

Thank you for your help, I did that and it was successful, but on 4 step i needed 32-bit value of 1 (1 = OK, 2 = Fail) so here it is: 

 

 

It was successful? Please, tell me how you got OK on 4 step.

 

I always got 2 on 4 step.

abe5ba13c3764b9e9a64ba0166ddc826.png

  • 7 years later...
Posted
On 1/18/2017 at 12:06 AM, Dr. McKay said:

Okay, here's the flow of how this works:

  1. CS:GO client sends a GCStorePurchaseInit to the GC with the items you want to buy
  2. You get back a GCStorePurchaseInitResponse containing a result (1 = OK) and a transaction ID (64-bit, even though it can currently fit into 32 bits)
  3. Concurrently, you get a ClientMicroTxnAuthRequest message via Steam. Discard the first byte of the payload (seems to be 1 in the dump you sent, not sure if that's always the case or what the significance of it is), then decode the remainder of the payload as Binary KeyValues (require('binarykvparser').parse(payload.toBuffer().slice(1))). The interesting stuff in there is the total/BillingTotal and transID (orderid is there if you want to make sure it matches what the GC sent back).
  4. Grab the transID and send it back in ClientMicroTxnAuthorize (little-endian encoded, as always) and append a 32-bit value of 2 (unsure what the significance of this is, or if it ever changes from 2. Could perhaps be an authorization result, where 1 is deny or something).
  5. You'll get back ClientMicroTxnAuthorizeResponse with a payload containing a 32-bit eresult value (1 = OK), and a 1-byte unknown value of 0. If successful,
  6. If successful, send GCStorePurchaseFinalize to the GC containing the transaction ID you from the GC before (not transID) to get your items.

You just saved me a ton of time! You're my hero!

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...