Buildings/loot missing after lag and server restart (error: “Skipping entity… player in the save multiple times”)

Hello,
There was a severe lag and the server went into a reboot. After it came back online, we found that some buildings and some players’ loot were missing.
The log contains the following error:
Skipping entity <id> - it's a player <steamId> who is in the save multiple times

Did you every find a solution? I am experiencing the same.

Merged post

I found a solution to my problem, and perhaps yours as well...

TL;DR - Run the Python script at the end of this post in Duplicates mode to sanitize your save file. Happy days after... (hopefully)

Here is my understanding of the Skipping entity mechanic:

When Rust loads the save file, it reads entities one by one. When it encounters a entity with a basePlayer, it records the userid. If it later encounters another basePlayer with the same userid, it says "this player is in the save multiple times" and starts skipping that entity and everything that follows it in that section of the save (basically goes nuclear imo). I guess that this is Rust's protection against corrupted saves with duplicate players... the idea being that if there are two copies of a player, something went wrong, so skipping the duplicates.

As implied earlier, this method comes across as very agressive, and everything but fool proof. It does not appear to look at each entity individually and validates the legitimacy. It can't as not every entity contains a basePlayer ident. My observation is that the file is essentially a flat sequential list of entities and Rust relies on ordering to associate entities with players. So when it hits a basePlayer entity, it effectively says "everything that follows belongs to this player". Which I guess is evidently flaud based on the issue we are experiencing :/

My solution:

So my issue was Plugin/NPC related, this made it quite easy to detect and remove everything that was not a legitimate player. I guess in your case you'd have to identify the player(s) that were causing the duplicate entities and surgically remove them from the save file.

I've used a python script to read the save file and surgically remove ALL entities that were created by NPC's. Prompted for some adjustments in your case. The script has a selector between two modes: All NPC's or only duplicate basePlayer entities. Hope it is of help!

import struct
import sys

def decode_varint(buf, pos):
    result = 0
    shift = 0
    while pos < len(buf):
        b = buf[pos]
        result |= (b & 0x7F) << shift
        pos += 1
        if not (b & 0x80):
            break
        shift += 7
    return result, pos

def parse_proto_fields(buf):
    fields = {}
    pos = 0
    while pos < len(buf):
        try:
            start = pos
            tag, new_pos = decode_varint(buf, pos)
            if tag == 0: break
            field_num = tag >> 3
            wire_type = tag & 7
            if wire_type == 0:
                val, pos = decode_varint(buf, new_pos)
                fields[field_num] = ('varint', val)
            elif wire_type == 1:
                pos = new_pos + 8
                fields[field_num] = ('fixed64', 0)
            elif wire_type == 2:
                length, dpos = decode_varint(buf, new_pos)
                fields[field_num] = ('bytes', buf[dpos:dpos+length])
                pos = dpos + length
            elif wire_type == 5:
                fields[field_num] = ('fixed32', struct.unpack_from('<I', buf, new_pos)[0])
                pos = new_pos + 4
            else:
                break
        except:
            break
    return fields

# ============================================================
# CONFIGURATION
# ============================================================
INPUT_FILE  = "proceduralmap.5000.1337.281.sav"
OUTPUT_FILE = "proceduralmap.5000.1337.281.fixed.sav"

# Mode: "all_npc"    = remove ALL basePlayer entities with non-Steam64 userids (my issue)
#        "duplicates" = only remove DUPLICATE basePlayer entities (keeps first, removes copies)
MODE = "duplicates"
# ============================================================

STEAM64_MIN = 76561############

print("=" * 70)
print("RUST SAVE FILE FIXER")
print(f"Mode: {MODE}")
print("=" * 70)

with open(INPUT_FILE, 'rb') as f:
    data = bytearray(f.read())

print(f"\nOriginal file size: {len(data):,} bytes")

json_end = data.find(b'}') + 1
header = bytes(data[:json_end + 9])
body = data[json_end:]

# Parse all entities
entities = []
pos = 9
while pos + 4 < len(body):
    entity_len = struct.unpack_from('<I', body, pos)[0]
    if entity_len == 0 or entity_len > 10000000: break
    entities.append({'length': entity_len, 'data': bytes(body[pos+4:pos+4+entity_len])})
    pos = pos + 4 + entity_len

print(f"Total entities parsed: {len(entities):,}")

# Scan all entities for basePlayer data
seen_userids = {}  # userid -> first entity index
remove_indices = set()
all_baseplayer = []

for i, ent in enumerate(entities):
    fields = parse_proto_fields(ent['data'])
    if 3 not in fields or fields[3][0] != 'bytes':
        continue
    
    f3 = parse_proto_fields(fields[3][1])
    if 2 not in f3:
        continue
    
    userid = f3[2][1]
    
    # Get entity info
    uid = None; prefab = None
    if 1 in fields:
        bn = parse_proto_fields(fields[1][1])
        uid = bn.get(1, (None, None))[1]
        prefab = bn.get(3, (None, None))[1]
    
    is_steam = userid >= STEAM64_MIN
    
    all_baseplayer.append({
        'index': i, 'uid': uid, 'prefab': prefab,
        'userid': userid, 'steam64': is_steam
    })
    
    if MODE == "all_npc":
        if not is_steam:
            remove_indices.add(i)
            print(f"  REMOVE  entity #{i:>6}, uid={uid:>8}, userid={userid} (NPC)")
        else:
            print(f"  KEEP    entity #{i:>6}, uid={uid:>8}, userid={userid} (Steam64)")
    
    elif MODE == "duplicates":
        if userid in seen_userids:
            remove_indices.add(i)
            first = seen_userids[userid]
            label = "Steam64" if is_steam else "NPC"
            print(f"  REMOVE  entity #{i:>6}, uid={uid:>8}, userid={userid} ({label}) "
                  f"[duplicate of entity #{first}]")
        else:
            seen_userids[userid] = i
            label = "Steam64" if is_steam else "NPC"
            print(f"  KEEP    entity #{i:>6}, uid={uid:>8}, userid={userid} ({label}) [first occurrence]")

print(f"\n--- SUMMARY ---")
print(f"  BasePlayer entities found: {len(all_baseplayer)}")
print(f"  Entities to remove:        {len(remove_indices)}")
print(f"  Entities to keep:          {len(entities) - len(remove_indices):,}")

# Write fixed save
output = bytearray()
output.extend(header)

for i, ent in enumerate(entities):
    if i not in remove_indices:
        output.extend(struct.pack('<I', ent['length']))
        output.extend(ent['data'])

with open(OUTPUT_FILE, 'wb') as f:
    f.write(output)

print(f"\n  Original size: {len(data):>10,} bytes")
print(f"  Fixed size:    {len(output):>10,} bytes")
print(f"  Removed:       {len(data) - len(output):>10,} bytes")

# Verify
with open(OUTPUT_FILE, 'rb') as f:
    fixed = bytearray(f.read())
fixed_body = fixed[fixed.find(b'}') + 1:]
count = 0
vpos = 9
while vpos + 4 < len(fixed_body):
    el = struct.unpack_from('<I', fixed_body, vpos)[0]
    if el == 0 or el > 10000000: break
    count += 1
    vpos = vpos + 4 + el
print(f"\n  Verified: {count:,} entities, {len(fixed_body) - vpos} leftover bytes")
print(f"  {'VALID' if len(fixed_body) - vpos == 0 else 'WARNING: leftover bytes!'}")
print("=" * 70)

Hello, I've had the same problem since the Naval update after a server restart. It happens even when I start the server without plugins. Every time, something is removed from the players' accounts. In my opinion, this happens when you build a naval boat.

Merged post

This helped me with creating the LoadEntityBasePlayerFix.cs file.

https://umod.org/community/nteleportation/57248-skipping-entity-foundation-id-its-a-player-steamid-who-is-in-the-save-multiple-times-on-server-restart