Files
psn-js/src/decoder.ts
T
2024-12-19 21:07:57 -06:00

124 lines
4.3 KiB
TypeScript

import { Decoders } from '.';
import { DataPacketChunk, InfoPacketChunk, InfoTrackerChunk } from './models';
export class Decoder {
infoPacketFrames: { [key: number]: InfoPacketChunk[] } = {};
dataPacketFrames: { [key: number]: DataPacketChunk[] } = {};
trackers: { [key: string]: InfoTrackerChunk } = {};
system_name: string = '';
constructor() {}
updateInfo(framePackets: InfoPacketChunk[]) {
framePackets.forEach((packet) => {
if (packet.system_name) {
this.system_name = packet.system_name.system_name;
}
if (packet.tracker_list?.trackers) {
Object.entries(packet.tracker_list.trackers).forEach(([trackerId, tracker]) => {
if (!this.trackers[trackerId]) {
this.trackers[trackerId] = {};
}
this.trackers[trackerId].tracker_name = tracker.tracker_name;
});
}
});
}
updateData(framePackets: DataPacketChunk[]) {
framePackets.forEach((packet) => {
if (packet.tracker_list) {
Object.entries(packet.tracker_list.trackers).forEach(([trackerId, tracker]) => {
this.trackers[trackerId] = {
...this.trackers[trackerId],
...tracker,
};
});
}
});
}
// TODO(jwetzell): add invalid frame id decoding. Scenario where a frame id is reused before one is complete
decode(packetBuf: Uint8Array): DataPacketChunk | InfoPacketChunk | undefined {
const packet = Decoders.Packet(packetBuf);
if (packet === undefined) {
return packet;
}
if (packet.id === 0x6756) {
const infoPacket = packet as InfoPacketChunk;
if (infoPacket && infoPacket.has_subchunks) {
const currentInfoPacketHeader = infoPacket.packet_header;
if (!currentInfoPacketHeader) {
// NOTE(jwetzell): not sure that info packets without a header subchunk are valid?
throw new Error('malformed packet');
}
const systemSubChunk = infoPacket.system_name;
if (!systemSubChunk) {
// NOTE(jwetzell): not sure that info packets without a system subchunk are valid?
throw new Error('malformed packet');
}
if (currentInfoPacketHeader.frame_id) {
if (this.infoPacketFrames[currentInfoPacketHeader.frame_id] === undefined) {
this.infoPacketFrames[currentInfoPacketHeader.frame_id] = [];
}
this.infoPacketFrames[currentInfoPacketHeader.frame_id].push(infoPacket);
if (
this.infoPacketFrames[currentInfoPacketHeader.frame_id].length ===
currentInfoPacketHeader.frame_packet_count
) {
this.updateInfo(this.infoPacketFrames[currentInfoPacketHeader.frame_id]);
delete this.infoPacketFrames[currentInfoPacketHeader.frame_id];
}
}
}
} else if (packet.id === 0x6755) {
const dataPacket = packet as DataPacketChunk;
if (dataPacket.has_subchunks) {
const currentDataPacketHeader = dataPacket.packet_header;
if (!currentDataPacketHeader) {
// NOTE(jwetzell): not sure that info packets without a header subchunk are valid?
throw new Error('malformed packet');
}
if (currentDataPacketHeader.frame_id) {
if (this.dataPacketFrames[currentDataPacketHeader.frame_id] === undefined) {
this.dataPacketFrames[currentDataPacketHeader.frame_id] = [];
}
this.dataPacketFrames[currentDataPacketHeader.frame_id].push(dataPacket);
if (
this.dataPacketFrames[currentDataPacketHeader.frame_id].length ===
currentDataPacketHeader.frame_packet_count
) {
this.updateData(this.dataPacketFrames[currentDataPacketHeader.frame_id]);
delete this.dataPacketFrames[currentDataPacketHeader.frame_id];
}
}
}
}
return packet;
}
// NOTE(jwetzell): gets merged set of tracker field keys minus the guaranteed properties
getTrackerFields(): Set<string> {
const keys = new Set(
Object.values(this.trackers)
.map((tracker) => Object.keys(tracker))
.flat(1)
);
// NOTE(jwetzell): remove fields that definitely exist
keys.delete('id');
keys.delete('has_subchunks');
keys.delete('data_len');
keys.delete('chunk_data');
return keys;
}
}