chaot1c Posted January 16, 2017 Report Posted January 16, 2017 (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. Edited January 16, 2017 by chaot1c Quote
Dr. McKay Posted January 17, 2017 Report Posted January 17, 2017 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? Quote
chaot1c Posted January 17, 2017 Author Report Posted January 17, 2017 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 Quote
Dr. McKay Posted January 17, 2017 Report Posted January 17, 2017 Okay, here's the flow of how this works:CS:GO client sends a GCStorePurchaseInit to the GC with the items you want to buy 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) 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). 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). 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, If successful, send GCStorePurchaseFinalize to the GC containing the transaction ID you from the GC before (not transID) to get your items. headshot1k 1 Quote
chaot1c Posted January 17, 2017 Author Report Posted January 17, 2017 (edited) Okay, here's the flow of how this works:CS:GO client sends a GCStorePurchaseInit to the GC with the items you want to buy 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) 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). 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). 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, 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 January 17, 2017 by chaot1c headshot1k and wxz123 1 1 Quote
Dr. McKay Posted January 18, 2017 Report Posted January 18, 2017 Don't do it that way, that's a very bad idea. Write the entire 64-bit number to the buffer. headshot1k and wxz123 1 1 Quote
chaot1c Posted January 18, 2017 Author Report Posted January 18, 2017 (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 January 19, 2017 by chaot1c Quote
truered Posted April 22, 2017 Report Posted April 22, 2017 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. Quote
wxz123 Posted June 25 Report Posted June 25 On 1/18/2017 at 12:06 AM, Dr. McKay said: Okay, here's the flow of how this works: CS:GO client sends a GCStorePurchaseInit to the GC with the items you want to buy 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) 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). 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). 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, 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! Quote
Recommended Posts
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.