feat: bkon read

This commit is contained in:
TrèsAbhi committed 2024-02-04 11:06:26 -06:00
1 parent 559fb30da9
commit 43b6453fcb
5 files changed
+318 -3

No files matched your search

+3
View File
@@ -13,6 +13,7 @@
"bepaint",
"BINORMAL",
"Binormals",
"BKON",
"bkwd",
"blitzstars",
"Camo",
@@ -100,6 +101,8 @@
"statsfull",
"steamapps",
"Stringifying",
"struct",
"structs",
"strvk",
"subnation",
"Synx",
+163
View File
@@ -0,0 +1,163 @@
# Blitzkrieg Object Notation
A compact binary representation of JSON objects. This documentation uses a frankenstein version of C++ structs that I came up with. Deal with it.
## Primitives
```cpp
struct String {
uint32 length;
ascii[length] value;
}
```
## Reading
- Everything is little endian
- Magic is always `"BKON"`
```cpp
struct BlitzkriegObjectNotation {
ascii[4] magic;
Header header;
Body body;
}
```
### Header
- Version is always `1`
```cpp
struct Header {
uint16 version;
StringTable stringTable;
}
struct StringTable {
uint32 count;
uint32[count] ids;
String[count] strings;
}
```
### Body
```cpp
// identical to ValueObject except with no type
struct Body {
uint32 count;
(ValueString | ValueFastString)[count] keys;
Value[count] values;
}
enum ValueType {
Null,
Boolean,
Uint8,
Uint16,
Uint32,
Uint64,
Int8,
Int16,
Int32,
Int64,
Float32,
Float64,
String,
FastString,
Array,
Object,
}
struct Value {
uint8 type;
// what comes next depends on type
}
struct ValueNull {
ValueType.Null type;
}
struct ValueBoolean {
ValueType.Boolean type;
uint8 value;
}
struct ValueUint8 {
ValueType.Uint8 type;
uint8 value;
}
struct ValueUint16 {
ValueType.Uint16 type;
uint16 value;
}
struct ValueUint32 {
ValueType.Uint32 type;
uint32 value;
}
struct ValueUint64 {
ValueType.Uint64 type;
uint64 value;
}
struct ValueInt8 {
ValueType.Int8 type;
int8 value;
}
struct ValueInt16 {
ValueType.Int16 type;
int16 value;
}
struct ValueInt32 {
ValueType.Int32 type;
int32 value;
}
struct ValueInt64 {
ValueType.Int64 type;
int64 value;
}
struct ValueFloat32 {
ValueType.Float32 type;
float32 value;
}
struct ValueFloat64 {
ValueType.Float64 type;
float64 value;
}
struct ValueString {
ValueType.String type;
String value;
}
struct ValueFastString {
ValueType.FastString type;
uint32 id;
}
struct ValueArray {
ValueType.Array type;
uint32 count;
Value[count] values;
}
struct ValueObject {
ValueType.Object type;
uint32 count;
(ValueString | ValueFastString)[count] keys;
Value[count] values;
}
```
## Writing
It's pretty obvious how to write. Remember that a `FastString` should only be applied to the string table if the string is greater than 4 characters and shows up more than once in the body. Otherwise, there's no point and `String` should be used.
+146
View File
@@ -0,0 +1,146 @@
import { times } from 'lodash';
import { BufferStream } from './buffer';
enum ValueType {
Null,
Boolean,
Uint8,
Uint16,
Uint32,
Uint64,
Int8,
Int16,
Int32,
Int64,
Float32,
Float64,
String,
FastString,
Array,
Object,
}
type Value =
| null
| boolean
| number
| bigint
| string
| Value[]
| { [key: string]: Value };
export class BkonStream extends BufferStream {
bkon() {
this.magic();
const header = this.header();
const body = this.body(header.stringTable);
return body;
}
magic() {
if (this.ascii(4) !== 'BKON') {
throw new Error('Invalid BKON magic number');
}
}
header() {
return {
version: this.uint16(),
stringTable: this.stringTable(),
};
}
body(stringTable: Record<number, string>) {
const count = this.uint32();
const keys = times(count, () => this.value(stringTable) as string);
const values = times(count, () => this.value(stringTable));
const body: Record<string, Value> = {};
keys.forEach((key, index) => (body[key] = values[index]));
return body;
}
stringTable() {
const count = this.uint32();
const ids = times(count, this.uint32);
const strings = times(count, this.string);
const table: Record<number, string> = {};
ids.forEach((id, index) => (table[id] = strings[index]));
return table;
}
string() {
const length = this.uint32();
const string = this.ascii(length);
return string;
}
value(stringTable: Record<number, string>): Value {
const type = this.uint8();
switch (type) {
case ValueType.Null:
return null;
case ValueType.Boolean:
return this.boolean();
case ValueType.Uint8:
return this.uint8();
case ValueType.Uint16:
return this.uint16();
case ValueType.Uint32:
return this.uint32();
case ValueType.Uint64:
return this.uint64();
case ValueType.Int8:
return this.int8();
case ValueType.Int16:
return this.int16();
case ValueType.Int32:
return this.int32();
case ValueType.Int64:
return this.int64();
case ValueType.Float32:
return this.float32();
case ValueType.Float64:
return this.float64();
case ValueType.String:
return this.string();
case ValueType.FastString:
return stringTable[this.uint32()];
case ValueType.Array:
return times(this.uint32(), () => this.value(stringTable));
case ValueType.Object: {
const count = this.uint32();
const keys = times(count, () => this.value(stringTable) as string);
const values = times(count, () => this.value(stringTable));
const object: Record<string, Value> = {};
keys.forEach((string, index) => (object[string] = values[index]));
return object;
}
default:
throw new Error(`Invalid value type: ${type}`);
}
}
}
+4 -1
View File
@@ -60,9 +60,12 @@ export class BufferStream {
uint64() {
return this.buffer.readBigUInt64LE(this.increment(8));
}
float() {
float32() {
return this.buffer.readFloatLE(this.increment(4));
}
float64() {
return this.buffer.readDoubleLE(this.increment(8));
}
boolean() {
return this.buffer.readUInt8(this.increment(1)) === 1;
+2 -2
View File
@@ -57,7 +57,7 @@ export enum VertexAttribute {
export class ScpgStream extends BufferStream {
vectorN(size: number) {
return times(size, () => this.float());
return times(size, () => this.float32());
}
vector2() {
return this.vectorN(2);
@@ -107,7 +107,7 @@ export class ScpgStream extends BufferStream {
}
case KAType.FLOAT:
return this.float();
return this.float32();
case KAType.ARRAY: {
const length = this.uint32();