diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1c8524a..f3591e4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,13 @@ Changelog Note that they these tags will not actually close the issue/PR until they are merged into the "default" branch. +v0.6.7 +------- + +Fix: + +- Bug for Delete Player + v0.6.6 ------- @@ -245,7 +252,7 @@ v0.4.0 Feature: -- Item Editor with Autocomplete Combobox +- Item Editor with Autocomplete Combobox v0.3.10 ------- @@ -285,7 +292,7 @@ v0.3.6 Feature: -- Move Guild Owner Feature +- Move Guild Owner Feature v0.3.4 ------- diff --git a/palworld_server_toolkit/editor.py b/palworld_server_toolkit/editor.py index 0790bcd..571a7a5 100644 --- a/palworld_server_toolkit/editor.py +++ b/palworld_server_toolkit/editor.py @@ -487,48 +487,6 @@ def load_skipped_decode(_worldSaveData, skip_paths, recursive=True): SKP_PALWORLD_CUSTOM_PROPERTIES[".worldSaveData.ItemContainerSaveData.Value.BelongInfo"] = (skip_decode, skip_encode) SKP_PALWORLD_CUSTOM_PROPERTIES[".worldSaveData.ItemContainerSaveData.Value.Slots"] = (skip_decode, skip_encode) SKP_PALWORLD_CUSTOM_PROPERTIES[".worldSaveData.ItemContainerSaveData.Value.RawData"] = (skip_decode, skip_encode) -import palworld_save_tools.rawdata.group as palworld_save_group - - -def group_decode( - reader: FArchiveReader, type_name: str, size: int, path: str -) -> dict[str, Any]: - if type_name != "MapProperty": - raise Exception(f"Expected MapProperty, got {type_name}") - value = reader.property(type_name, size, path, nested_caller_path=path) - # Decode the raw bytes and replace the raw data - group_map = value["value"] - for group in group_map: - group_type = group["value"]["GroupType"]["value"]["value"] - if group_type != "EPalGroupType::Guild": - continue - group['value'] = parse_item(group['value'], "GroupSaveDataMap.Value") - group_bytes = group["value"]["RawData"]["value"]["values"] - group["value"]["RawData"]["value"] = palworld_save_group.decode_bytes( - reader, group_bytes, group_type - ) - return value - - -def group_encode( - writer: FArchiveWriter, property_type: str, properties: dict[str, Any] -) -> int: - if property_type != "MapProperty": - raise Exception(f"Expected MapProperty, got {property_type}") - del properties["custom_type"] - group_map = properties["value"] - for group in group_map: - group_type = group["value"]["GroupType"]["value"]["value"] - if group_type != "EPalGroupType::Guild": - continue - if "values" in group["value"]["RawData"]["value"]: - continue - p = group["value"]["RawData"]["value"] - encoded_bytes = palworld_save_group.encode_bytes(p) - group["value"]["RawData"]["value"] = {"values": [b for b in encoded_bytes]} - return writer.property_inner(property_type, properties) - - SKP_PALWORLD_CUSTOM_PROPERTIES[".worldSaveData.GroupSaveDataMap"] = (group_decode, group_encode) SKP_PALWORLD_CUSTOM_PROPERTIES[".worldSaveData.GroupSaveDataMap.Value.RawData"] = (skip_decode, skip_encode) @@ -2237,8 +2195,7 @@ def LoadFile(filename): def Statistics(): for key in wsd: - print("%40s\t%.3f MB\tLoading: %10.2fms\t%20s\t%s: %d" % (key, len(str(wsd[key])) / 1048576, - 1000 * loadingStatistics[key], + print("%40s\t%.3f MB\t%20s\t%s: %d" % (key, len(str(wsd[key])) / 1048576, wsd[key]['type'] if 'type' in wsd[key] else "", "Bytes" if isinstance(wsd[key]['value'], bytes) else "Key", @@ -3500,6 +3457,7 @@ def DeletePlayer(player_uid, InstanceId=None, dry_run=False): DeleteItemContainer(player_gvas['inventoryInfo']['value'][key]['value']['ID']['value']) player_container_ids.append(player_gvas['inventoryInfo']['value'][key]['value']['ID']['value']) # Remove item from CharacterSaveParameterMap + deleteCharacters = [] for item in wsd['CharacterSaveParameterMap']['value']: player = item['value']['RawData']['value']['object']['SaveParameter']['value'] if str(item['key']['PlayerUId']['value']) == player_uid \ @@ -3510,14 +3468,14 @@ def DeletePlayer(player_uid, InstanceId=None, dry_run=False): str(item['key']['InstanceId']['value']), player['Level']['value'] if 'Level' in player else -1, player['NickName']['value'] if 'NickName' in player else "*** INVALID ***")) if not dry_run: - DeleteCharacter(item['key']['InstanceId']['value']) + deleteCharacters.append(item['key']['InstanceId']['value']) elif 'OwnerPlayerUId' in player and str(player['OwnerPlayerUId']['value']) == player_uid and InstanceId is None: print( f"{terminalColor(31)}Delete Pal{terminalColor(0)} UUID: %s Owner: %s CharacterID: %s" % ( str(item['key']['InstanceId']['value']), str(player['OwnerPlayerUId']['value']), player['CharacterID']['value'])) if not dry_run: - DeleteCharacter(item['key']['InstanceId']['value']) + deleteCharacters.append(item['key']['InstanceId']['value']) elif 'SlotID' in player and player['SlotID']['value']['ContainerId']['value']['ID'][ 'value'] in player_container_ids and InstanceId is None: print( @@ -3526,7 +3484,9 @@ def DeletePlayer(player_uid, InstanceId=None, dry_run=False): str(player['SlotID']['value']['ContainerId']['value']['ID']['value']), player['CharacterID']['value'])) if not dry_run: - DeleteCharacter(item['key']['InstanceId']['value']) + deleteCharacters.append(item['key']['InstanceId']['value']) + for instance_id in deleteCharacters: + DeleteCharacter(instance_id) # Remove Item from GroupSaveDataMap remove_guilds = [] for group_data in wsd['GroupSaveDataMap']['value']: diff --git a/palworld_server_toolkit/palobject.py b/palworld_server_toolkit/palobject.py index d41043f..66ce450 100644 --- a/palworld_server_toolkit/palobject.py +++ b/palworld_server_toolkit/palobject.py @@ -2,6 +2,7 @@ from palworld_save_tools.archive import * from palworld_save_tools.paltypes import * +import palworld_save_tools.rawdata.group as palworld_save_group import json import copy import multiprocessing @@ -313,6 +314,7 @@ def __iter__(self): class MPMapProperty(list): def __init__(self, *args, **kwargs): + super().__init__() count = kwargs.get("count", 0) size = kwargs.get("size", 0) intsize = ctypes.sizeof(ctypes.c_ulong) @@ -336,7 +338,7 @@ def __init__(self, *args, **kwargs): self.memaddr + intsize * 4 + intsize * self.count.value) self.value_size = (ctypes.c_ulong * self.count.value).from_address( self.memaddr + intsize * 4 + intsize * self.count.value * 2) - super().__init__([None] * self.count.value) + super().extend([None] * self.count.value) def append(self, obj): if self.current.value < self.count.value: @@ -353,7 +355,7 @@ def append(self, obj): super().append(obj) def __iter__(self): - for i in range(self.current.value): + for i in range(len(self)): yield self.__getitem__(i) def __getitem__(self, item): @@ -364,12 +366,24 @@ def __getitem__(self, item): self.shm.buf[v_s:v_s + self.value_size[item]]) self.parsed_count.value += 1 if self.parsed_count.value == self.count.value: + self.__iter__ = super().__iter__ self.shm.buf.release() return super().__getitem__(item) + def load_all_items(self): + if self.parsed_count.value == self.count.value: + return + for i in range(self.current.value): + self.__getitem__(i) + + def __delitem__(self, item): + self.load_all_items() + self.current.value -= 1 + return super().__delitem__(item) class MPArrayProperty(list): def __init__(self, *args, **kwargs): + super().__init__() count = kwargs.get("count", 0) size = kwargs.get("size", 0) intsize = ctypes.sizeof(ctypes.c_ulong) @@ -391,7 +405,7 @@ def __init__(self, *args, **kwargs): self.index = (ctypes.c_ulong * self.count.value).from_address(self.memaddr + intsize * 4) self.value_size = (ctypes.c_ulong * self.count.value).from_address( self.memaddr + intsize * 4 + intsize * self.count.value) - super().__init__([None] * self.count.value) + self += [None] * self.count.value def append(self, obj): if self.current.value < self.count.value: @@ -404,10 +418,6 @@ def append(self, obj): else: super().append(obj) - def __iter__(self): - for i in range(self.current.value): - yield self.__getitem__(i) - def __getitem__(self, item): if super().__getitem__(item) is None: v_s = self.index[item] @@ -418,6 +428,26 @@ def __getitem__(self, item): self.shm.buf.release() return super().__getitem__(item) + def __iter__(self): + for i in range(len(self)): + yield self.__getitem__(i) + + def __delitem__(self, item): + del self.index[item] + del self.value_size[item] + self.current.value -= 1 + return super().__delitem__(item) + + def load_all_items(self): + if self.parsed_count.value == self.count.value: + return + for i in range(self.current.value): + self.__getitem__(i) + + def __delitem__(self, item): + self.load_all_items() + self.current.value -= 1 + return super().__delitem__(item) def skip_decode( reader: FArchiveReader, type_name: str, size: int, path: str @@ -710,6 +740,104 @@ def properties_until_end(self, path: str = "") -> dict[str, Any]: properties['worldSaveData']['value'][mp_path[15:]]["custom_type"] = mp_path return properties + def parse_item(self, properties, skip_path): + if isinstance(properties, dict): + if 'skip_type' in properties: + # print("Parsing worldSaveData.%s..." % skip_path, end="", flush=True) + properties_parsed = self.parse_skiped_item(properties, skip_path) + for k in properties_parsed: + properties[k] = properties_parsed[k] + # print("Done") + else: + for key in properties: + call_skip_path = skip_path + "." + key[0].upper() + key[1:] + properties[key] = self.parse_item(properties[key], call_skip_path) + elif isinstance(properties, list): + top_skip_path = ".".join(skip_path.split(".")[:-1]) + for idx, item in enumerate(properties): + properties[idx] = self.parse_item(item, top_skip_path) + return properties + + def parse_skiped_item(self, properties, skip_path): + if "skip_type" not in properties: + return properties + + writer = FArchiveWriter(PALWORLD_CUSTOM_PROPERTIES) + if properties["skip_type"] == "ArrayProperty": + writer.fstring(properties["array_type"]) + writer.optional_guid(properties.get("id", None)) + writer.write(properties['value']) + elif properties["skip_type"] == "MapProperty": + writer.fstring(properties["key_type"]) + writer.fstring(properties["value_type"]) + writer.optional_guid(properties.get("id", None)) + writer.write(properties["value"]) + elif properties["skip_type"] == "StructProperty": + writer.fstring(properties["struct_type"]) + writer.guid(properties["struct_id"]) + writer.optional_guid(properties.get("id", None)) + writer.write(properties["value"]) + + keep_custom_type = False + localProperties = copy.deepcopy(PALWORLD_CUSTOM_PROPERTIES) + if ".worldSaveData.%s" % skip_path in PALWORLD_CUSTOM_PROPERTIES: + localProperties[".worldSaveData.%s" % skip_path] = PALWORLD_CUSTOM_PROPERTIES[".worldSaveData.%s" % skip_path] + keep_custom_type = True + elif ".worldSaveData.%s" % skip_path in localProperties: + del localProperties[".worldSaveData.%s" % skip_path] + + with FProgressArchiveReader( + writer.bytes(), PALWORLD_TYPE_HINTS, + localProperties + ) as reader: + decoded_properties = reader.property(properties["skip_type"], len(properties['value']), + ".worldSaveData.%s" % skip_path) + for k in decoded_properties: + properties[k] = decoded_properties[k] + if not keep_custom_type: + del properties['custom_type'] + del properties["skip_type"] + return properties + + +def group_decode( + reader: FProgressArchiveReader, type_name: str, size: int, path: str +) -> dict[str, Any]: + if type_name != "MapProperty": + raise Exception(f"Expected MapProperty, got {type_name}") + value = reader.property(type_name, size, path, nested_caller_path=path) + # Decode the raw bytes and replace the raw data + group_map = value["value"] + for group in group_map: + group_type = group["value"]["GroupType"]["value"]["value"] + if group_type != "EPalGroupType::Guild": + continue + group['value'] = reader.parse_item(group['value'], "GroupSaveDataMap.Value") + group_bytes = group["value"]["RawData"]["value"]["values"] + group["value"]["RawData"]["value"] = palworld_save_group.decode_bytes( + reader, group_bytes, group_type + ) + return value + + +def group_encode( + writer: FArchiveWriter, property_type: str, properties: dict[str, Any] +) -> int: + if property_type != "MapProperty": + raise Exception(f"Expected MapProperty, got {property_type}") + del properties["custom_type"] + group_map = properties["value"] + for group in group_map: + group_type = group["value"]["GroupType"]["value"]["value"] + if group_type != "EPalGroupType::Guild": + continue + if "values" in group["value"]["RawData"]["value"]: + continue + p = group["value"]["RawData"]["value"] + encoded_bytes = palworld_save_group.encode_bytes(p) + group["value"]["RawData"]["value"] = {"values": [b for b in encoded_bytes]} + return writer.property_inner(property_type, properties) + class JsonPalSimpleObject: type = None