Skip to content

Instantly share code, notes, and snippets.

@Ne3tCode
Created December 23, 2017 20:04
Show Gist options
  • Save Ne3tCode/b3e6f7f6e399045843239c30a9c36a15 to your computer and use it in GitHub Desktop.
Save Ne3tCode/b3e6f7f6e399045843239c30a9c36a15 to your computer and use it in GitHub Desktop.
TF2 proto_defs.vpd parser
'use strict';
if (process.argv.length < 4) {
console.log('Usage: node parser.js <input> <output> [proto]');
console.log('Example: node parser.js proto_defs.vpd proto_defs.json path/to/tf_proto_def_messages.proto');
return;
}
const FS = require('fs');
const Protobuf = require('protobufjs'); // v6.8.3
const INPUT_FILE = process.argv[2]; // 'proto_defs.vpd'
const OUTPUT_FILE = process.argv[3]; // 'proto_defs.json'
const PROTO_FILE = process.argv[4] || 'tf_proto_def_messages.proto';
const TF2Schema = new Protobuf.Root().loadSync(PROTO_FILE, { keepCase: true });
/*
function deleteNulls(proto) {
Object.keys(proto).forEach(field => {
if (proto[field] === null)
delete proto[field];
else if (Array.isArray(proto[field]) && proto[field].length == 0)
delete proto[field];
else if (typeof proto[field] == 'object')
deleteNulls(proto[field]);
});
return proto;
}
*/
const TypeMap = new Map([
// Types, services and enums must start with an uppercase letter to become available as properties of the
// reflected types as well (i.e. to be able to use MyMessage.MyEnum instead of root.lookup("MyMessage.MyEnum")).
// (c) Docs
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST_MAP_NODE, TF2Schema.CMsgQuestMapNodeDef],
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST_THEME, TF2Schema.CMsgQuestTheme],
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST_MAP_REGION, TF2Schema.CMsgQuestMapRegionDef],
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST, TF2Schema.CMsgQuestDef],
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST_OBJECTIVE, TF2Schema.CMsgQuestObjectiveDef],
[TF2Schema.ProtoDefTypes.DEF_TYPE_PAINTKIT_VARIABLES, TF2Schema.CMsgPaintKit_Variables],
[TF2Schema.ProtoDefTypes.DEF_TYPE_PAINTKIT_OPERATION, TF2Schema.CMsgPaintKit_Operation],
[TF2Schema.ProtoDefTypes.DEF_TYPE_PAINTKIT_ITEM_DEFINITION, TF2Schema.CMsgPaintKit_ItemDefinition],
[TF2Schema.ProtoDefTypes.DEF_TYPE_PAINTKIT_DEFINITION, TF2Schema.CMsgPaintKit_Definition],
[TF2Schema.ProtoDefTypes.DEF_TYPE_HEADER_ONLY, TF2Schema.CMsgHeaderOnly],
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST_MAP_STORE_ITEM, TF2Schema.CMsgQuestMapStoreItem],
[TF2Schema.ProtoDefTypes.DEF_TYPE_QUEST_MAP_STAR_TYPE, TF2Schema.CMsgQuestMapStarType]
]);
let proto_defs_file = FS.readFileSync(INPUT_FILE);
let Definitions = Object.create(null), file_position = 0, def_type, num_records, msg_length, proto_msg;
/*
ProtoRecord {
MsgLength: Uint;
ProtoMsg: Byte[MsgLength];
}
ProtoDef {
DefType: Uint;
NumRecords: Uint;
Record: ProtoRecord[NumRecords]
}
FileStruct {
Definitions: ProtoDef[ProtoDefTypes];
}
*/
while (file_position < proto_defs_file.length) {
def_type = proto_defs_file.readUInt32LE(file_position);
Definitions[def_type] = [];
file_position += 4;
num_records = proto_defs_file.readUInt32LE(file_position);
file_position += 4;
let decoder = TypeMap.get(def_type);
while (num_records--) {
msg_length = proto_defs_file.readUInt32LE(file_position);
file_position += 4;
proto_msg = proto_defs_file.slice(file_position, file_position + msg_length);
if (decoder) {
proto_msg = decoder.decode(proto_msg); // enums as strings
//proto_msg = decoder.toObject(proto_msg, { enums: Number }); // enums as numbers
Definitions[def_type].push(proto_msg); // deleteNulls(proto_msg)
} else {
console.error(`Unknown DefType ${def_type}. Decoder not found!`);
Definitions[def_type].push(proto_msg.toString('hex'));
}
file_position += msg_length;
}
}
FS.writeFileSync(OUTPUT_FILE, JSON.stringify(Definitions, null, '\t'), 'utf8');
@renimg
Copy link

renimg commented Sep 27, 2022

Hello,

Would it be possible to make a version of this that converts the json back into proto_defs? I want to use this to create War Paints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment