diff --git a/pkg/demoinfocs/datatables.go b/pkg/demoinfocs/datatables.go index 1d773ccc..88ed35be 100644 --- a/pkg/demoinfocs/datatables.go +++ b/pkg/demoinfocs/datatables.go @@ -703,7 +703,7 @@ func (p *parser) bindPlayerWeapons(playerEntity st.Entity, pl *common.Player) { func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { const inventoryCapacity = 64 - var inventorySize uint64 = 64 + inventorySize := 64 type eq struct { *common.Equipment @@ -728,7 +728,7 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { setPlayerInventory := func() { inventory := make(map[int]*common.Equipment, inventorySize) - for i := uint64(0); i < inventorySize; i++ { + for i := 0; i < inventorySize; i++ { val := pawnEntity.Property(playerWeaponPrefixS2 + fmt.Sprintf("%04d", i)).Value() if val.Any == nil { continue @@ -742,7 +742,7 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { } pawnEntity.Property("m_pWeaponServices.m_hMyWeapons").OnUpdate(func(pv st.PropertyValue) { - inventorySize = pv.S2UInt64() + inventorySize = len(pv.S2Array()) setPlayerInventory() }) @@ -758,7 +758,7 @@ func (p *parser) bindPlayerWeaponsS2(pawnEntity st.Entity, pl *common.Player) { entityWasCreated := entityID != constants.EntityHandleIndexMaskSource2 - if uint64(i) < inventorySize { + if i < inventorySize { if entityWasCreated { existingWeapon, exists := playerInventory[i] if exists { diff --git a/pkg/demoinfocs/sendtables/propdecoder.go b/pkg/demoinfocs/sendtables/propdecoder.go index bc2db024..e7c3b6ae 100644 --- a/pkg/demoinfocs/sendtables/propdecoder.go +++ b/pkg/demoinfocs/sendtables/propdecoder.go @@ -152,6 +152,10 @@ func (v PropertyValue) S2UInt64() uint64 { return v.Any.(uint64) } +func (v PropertyValue) S2Array() []any { + return v.Any.([]any) +} + func (v PropertyValue) S2UInt32() uint32 { return v.Any.(uint32) } diff --git a/pkg/demoinfocs/sendtables2/entity.go b/pkg/demoinfocs/sendtables2/entity.go index e86d6424..0b0706f9 100644 --- a/pkg/demoinfocs/sendtables2/entity.go +++ b/pkg/demoinfocs/sendtables2/entity.go @@ -58,6 +58,13 @@ func (p property) Name() string { } func (p property) Value() st.PropertyValue { + v := p.entity.Get(p.name) + + fs, ok := v.(*fieldState) + if ok { + v = fs.state + } + return st.PropertyValue{ VectorVal: r3.Vector{}, IntVal: 0, @@ -65,7 +72,7 @@ func (p property) Value() st.PropertyValue { ArrayVal: nil, StringVal: "", FloatVal: 0, - Any: p.entity.Get(p.name), + Any: v, S2: true, } } @@ -416,12 +423,30 @@ func (e *Entity) readFields(r *reader, paths *[]*fieldPath) { readFieldPaths(r, paths) for _, fp := range *paths { - decoder := e.class.serializer.getDecoderForFieldPath(fp, 0) + f := e.class.serializer.getFieldForFieldPath(fp, 0) + name := e.class.getNameForFieldPath(fp) + decoder, base := e.class.serializer.getDecoderForFieldPath2(fp, 0) val := decoder(r) - e.state.set(fp, val) - for _, h := range e.updateHandlers[e.class.getNameForFieldPath(fp)] { + if base && (f.model == fieldModelVariableArray || f.model == fieldModelVariableTable) { + oldFS := e.state.get(fp) + fs := newFieldState() + + fs.state = make([]interface{}, val.(uint64)) + + if oldFS != nil { + copy(fs.state, oldFS.(*fieldState).state[:min(len(fs.state), len(oldFS.(*fieldState).state))]) + } + + e.state.set(fp, fs) + + val = fs.state + } else { + e.state.set(fp, val) + } + + for _, h := range e.updateHandlers[name] { h(st.PropertyValue{ VectorVal: r3.Vector{}, IntVal: 0, @@ -446,7 +471,7 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error { index = int32(-1) updates = int(m.GetUpdatedEntries()) cmd uint32 - classId int32 + classID int32 serial int32 ) @@ -485,18 +510,18 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error { } if cmd&0x01 == 0 { if cmd&0x02 != 0 { - classId = int32(r.readBits(p.classIdSize)) + classID = int32(r.readBits(p.classIdSize)) serial = int32(r.readBits(17)) r.readVarUint32() - class := p.classesById[classId] + class := p.classesById[classID] if class == nil { - _panicf("unable to find new class %d", classId) + _panicf("unable to find new class %d", classID) } - baseline := p.classBaselines[classId] + baseline := p.classBaselines[classID] if baseline == nil { - _panicf("unable to find new baseline %d", classId) + _panicf("unable to find new baseline %d", classID) } e = newEntity(index, serial, class) diff --git a/pkg/demoinfocs/sendtables2/field.go b/pkg/demoinfocs/sendtables2/field.go index 4a24871f..da4903ad 100644 --- a/pkg/demoinfocs/sendtables2/field.go +++ b/pkg/demoinfocs/sendtables2/field.go @@ -208,31 +208,34 @@ func (f *field) getTypeForFieldPath(fp *fieldPath, pos int) *fieldType { return f.fieldType } -func (f *field) getDecoderForFieldPath(fp *fieldPath, pos int) fieldDecoder { +func (f *field) getDecoderForFieldPath(fp *fieldPath, pos int) (fieldDecoder, bool) { switch f.model { case fieldModelFixedArray: - return f.decoder + return f.decoder, false case fieldModelFixedTable: if fp.last == pos-1 { - return f.baseDecoder + return f.baseDecoder, true } - return f.serializer.getDecoderForFieldPath(fp, pos) + + return f.serializer.getDecoderForFieldPath2(fp, pos) case fieldModelVariableArray: if fp.last == pos { - return f.childDecoder + return f.childDecoder, false } - return f.baseDecoder + + return f.baseDecoder, true case fieldModelVariableTable: if fp.last >= pos+1 { - return f.serializer.getDecoderForFieldPath(fp, pos+1) + return f.serializer.getDecoderForFieldPath2(fp, pos+1) } - return f.baseDecoder + + return f.baseDecoder, true } - return f.decoder + return f.decoder, false } func (f *field) getFieldPathForName(fp *fieldPath, name string) bool { diff --git a/pkg/demoinfocs/sendtables2/field_state.go b/pkg/demoinfocs/sendtables2/field_state.go index 44a17a9f..b587da5c 100644 --- a/pkg/demoinfocs/sendtables2/field_state.go +++ b/pkg/demoinfocs/sendtables2/field_state.go @@ -15,7 +15,7 @@ func (s *fieldState) get(fp *fieldPath) interface{} { z := 0 for i := 0; i <= fp.last; i++ { z = fp.path[i] - if len(x.state) < z+2 { + if len(x.state) < z+1 { return nil } if i == fp.last { @@ -34,10 +34,16 @@ func (s *fieldState) set(fp *fieldPath, v interface{}) { z := 0 for i := 0; i <= fp.last; i++ { z = fp.path[i] - if y := len(x.state); y < z+2 { - z := make([]interface{}, max(z+2, y*2)) - copy(z, x.state) - x.state = z + if y := len(x.state); y <= z { + newCap := max(z+2, y*2) + if newCap > cap(x.state) { + newSlice := make([]interface{}, z+1, newCap) + copy(newSlice, x.state) + x.state = newSlice + } else { + // Re-slice to update the length without allocating new memory + x.state = x.state[:z+1] + } } if i == fp.last { if _, ok := x.state[z].(*fieldState); !ok { @@ -56,5 +62,14 @@ func max(a, b int) int { if a > b { return a } + + return b +} + +func min(a, b int) int { + if a < b { + return a + } + return b } diff --git a/pkg/demoinfocs/sendtables2/serializer.go b/pkg/demoinfocs/sendtables2/serializer.go index 801805be..47bec701 100644 --- a/pkg/demoinfocs/sendtables2/serializer.go +++ b/pkg/demoinfocs/sendtables2/serializer.go @@ -45,6 +45,18 @@ func (s *serializer) getDecoderForFieldPath(fp *fieldPath, pos int) fieldDecoder if len(s.fields) <= index { _panicf("serializer %s: field path %s has no field (%d)", s.name, fp, index) } + + dec, _ := s.fields[index].getDecoderForFieldPath(fp, pos+1) + + return dec +} + +func (s *serializer) getDecoderForFieldPath2(fp *fieldPath, pos int) (fieldDecoder, bool) { + index := fp.path[pos] + if len(s.fields) <= index { + _panicf("serializer %s: field path %s has no field (%d)", s.name, fp, index) + } + return s.fields[index].getDecoderForFieldPath(fp, pos+1) }